Merge tag 'android-13.0.0_r3' into android-12.1

Android 13.0.0 Release 3 (TP1A.220624.021.A1)
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..540e149
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,23 @@
+- [ ] I am running an official build of TWRP, downloaded from https://twrp.me/Devices/
+- [ ] I am running the latest version of TWRP
+- [ ] I have read the FAQ (https://twrp.me/FAQ/)
+- [ ] I have searched for my issue and it does not already exist
+
+**Device codename**: <!-- Device codename -->
+**TWRP version**: <!-- TWRP version installed -->
+
+#### WHAT STEPS WILL REPRODUCE THE PROBLEM?
+<!-- Explain the steps necessary to reproduce the problem, as completely as possible -->
+
+#### WHAT IS THE EXPECTED RESULT?
+<!-- Explain what the expected result is, as completely as possible -->
+
+#### WHAT HAPPENS INSTEAD?
+<!-- Explain what happens instead, as completely as possible -->
+
+#### ADDITIONAL INFORMATION
+<!-- Add any additional information you know about the issue, such as possible causes and solutions -->
+
+<!-- Use https://paste.omnirom.org/ and upload `/tmp/recovery.log` and the output of `dmesg` -->
+`/tmp/recovery.log`: <!-- Link here -->
+`dmesg`: <!-- Link here -->
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..d04c212
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,7 @@
+# WE DO NOT MERGE PULL REQUESTS SUBMITTED HERE
+
+You will need to submit it through [OmniRom Gerrit](https://gerrit.omnirom.org/#/admin/projects/android_bootable_recovery/)
+
+For changes to device trees, use [TWRP Gerrit](https://gerrit.twrp.me/)
+
+This guide explani how to use [Gerrit code review](https://forum.xda-developers.com/general/xda-university/guide-using-gerrit-code-review-t3720802)
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..480f547
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.*.swp
+*~
+tags
+.vscode
diff --git a/Android.bp b/Android.bp
old mode 100644
new mode 100755
index bd95705..f20a551
--- a/Android.bp
+++ b/Android.bp
@@ -1,16 +1,6 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+subdirs = [
+    "bootloader_message",
+]
 
 // *** THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS.  PLEASE
 //     CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE
@@ -48,90 +38,96 @@
 
 cc_defaults {
     name: "recovery_defaults",
-
     cflags: [
         "-D_FILE_OFFSET_BITS=64",
-
         // Must be the same as RECOVERY_API_VERSION.
         "-DRECOVERY_API_VERSION=3",
-
         "-Wall",
         "-Werror",
     ],
+    cpp_std: "c++17",
 }
 
-cc_library_static {
-    name: "librecovery_fastboot",
-    recovery_available: true,
-    defaults: [
-        "recovery_defaults",
+bootstrap_go_package {
+    name: "soong-libaosprecovery_defaults",
+    pkgPath: "bootable/recovery/libaosprecovery",
+    deps: [
+        "soong",
+        "soong-android",
+        "soong-cc"
     ],
-
     srcs: [
-        "fastboot/fastboot.cpp",
+        "libaosprecovery_defaults.go",
+        "soong/makevars.go"
     ],
-
-    shared_libs: [
-        "libbase",
-        "libbootloader_message",
-        "libcutils",
-        "liblog",
-        "librecovery_ui",
-    ],
-
-    static_libs: [
-        "librecovery_ui_default",
-    ],
+    pluginFor: ["soong_build"]
 }
 
-cc_defaults {
-    name: "librecovery_defaults",
+libaosprecovery_defaults {
+    name: "libaosprecovery_defaults"
+}
 
-    defaults: [
-        "recovery_defaults",
+cc_library_shared {
+    name: "libaosprecovery",
+    defaults: ["libaosprecovery_defaults"],
+    cflags: [
+        "-std=gnu++2a",
+        "-DRECOVERY_API_VERSION=3"
     ],
-
+    include_dirs: [
+        "bootable/recovery/install/include",
+        "bootable/recovery/recovery_ui/include",
+        "bootable/recovery/recovery_utils/include",
+        "bootable/recovery/otautil/include",
+        "bootable/recovery/minadbd",
+        "bootable/recovery/minadbd/include",
+        "bootable/recovery/minzip",
+        "bootable/recovery/twrpinstall/include",
+        "system/libvintf/include"
+    ],
+    srcs: [
+        "install/adb_install.cpp",
+        "install/get_args.cpp", 
+        "install/install.cpp",
+        "install/spl_check.cpp",
+        "install/wipe_data.cpp",
+        "install/set_metadata.cpp",
+        "install/ZipUtil.cpp"
+    ],
     shared_libs: [
-        "android.hardware.boot@1.0",
-        "android.hardware.boot@1.1",
         "libbase",
         "libbootloader_message",
         "libcrypto",
-        "libcutils",
-        "libfs_mgr",
-        "liblp",
+        "libext4_utils",
+        "libfs_mgr", 
+        "libfusesideload", 
+        "libhidl-gen-utils",
+        "libhidlbase",
         "liblog",
-        "libprotobuf-cpp-lite",
+        "libselinux",
+        "libtinyxml2",
+        "libutils",
+        "libz",
         "libziparchive",
+        "libcutils",
+        "libc++"
     ],
-
     static_libs: [
-        "libc++fs",
-        "libinstall",
-        "librecovery_fastboot",
-        "libminui",
-        "librecovery_utils",
         "libotautil",
-        "libsnapshot_nobinder",
-        "update_metadata-protos",
+        "libvintf", 
+        "libhidl-gen-utils",
+        "librecovery_utils",
+        "libc++fs",
+        "libprotobuf-cpp-lite",
+        "ota_metadata_proto_cc",
+        "update_metadata-protos"
     ],
-}
-
-cc_library_static {
-    name: "librecovery",
-    recovery_available: true,
-
-    defaults: [
-        "librecovery_defaults",
-    ],
-
-    srcs: [
-        "recovery.cpp",
-    ],
-
-    shared_libs: [
-        "librecovery_ui",
-    ],
+    required: [
+        "init_recovery.rc",
+        "ueventd.rc.recovery",
+        "libdl_android.bootstrap",
+        "ziptool.recovery"
+    ]
 }
 
 prebuilt_etc {
@@ -141,99 +137,3 @@
     sub_dir: "init/hw",
     recovery: true,
 }
-
-cc_binary {
-    name: "recovery",
-    recovery: true,
-
-    defaults: [
-        "libinstall_defaults",
-        "librecovery_defaults",
-        "librecovery_utils_defaults",
-    ],
-
-    srcs: [
-        "recovery_main.cpp",
-    ],
-
-    shared_libs: [
-        "android.hardware.health-V1-ndk", // from librecovery_utils
-        "librecovery_ui",
-    ],
-
-    static_libs: [
-        "librecovery",
-        "librecovery_ui_default",
-    ],
-
-    required: [
-        "e2fsdroid.recovery",
-        "init_recovery.rc",
-        "librecovery_ui_ext",
-        "minadbd",
-        "mke2fs.conf.recovery",
-        "mke2fs.recovery",
-        "recovery_deps",
-        "ueventd.rc.recovery",
-    ],
-}
-
-// The dynamic executable that runs after /data mounts.
-cc_binary {
-    name: "recovery-persist",
-
-    defaults: [
-        "recovery_defaults",
-    ],
-
-    srcs: [
-        "recovery-persist.cpp",
-    ],
-
-    shared_libs: [
-        "libbase",
-        "liblog",
-    ],
-
-    static_libs: [
-        "librecovery_utils",
-    ],
-
-    init_rc: [
-        "recovery-persist.rc",
-    ],
-}
-
-// The dynamic executable that runs at init.
-cc_binary {
-    name: "recovery-refresh",
-
-    defaults: [
-        "recovery_defaults",
-    ],
-
-    srcs: [
-        "recovery-refresh.cpp",
-    ],
-
-    shared_libs: [
-        "libbase",
-        "liblog",
-    ],
-
-    static_libs: [
-        "librecovery_utils",
-    ],
-
-    init_rc: [
-        "recovery-refresh.rc",
-    ],
-}
-
-filegroup {
-    name: "res-testdata",
-
-    srcs: [
-        "res-*/images/*_text.png",
-    ],
-}
diff --git a/Android.mk b/Android.mk
old mode 100644
new mode 100755
index 8506040..210a1e3
--- a/Android.mk
+++ b/Android.mk
@@ -13,60 +13,596 @@
 # limitations under the License.
 
 LOCAL_PATH := $(call my-dir)
+commands_TWRP_local_path := $(LOCAL_PATH)
 
-# Needed by build/make/core/Makefile. Must be consistent with the value in Android.bp.
+ifneq ($(project-path-for),)
+    ifeq ($(LOCAL_PATH),$(call project-path-for,recovery))
+        PROJECT_PATH_AGREES := true
+        BOARD_VENDOR_SEPOLICY_DIRS += $(call project-path-for,recovery)/sepolicy
+    endif
+else
+    $(warning BOARD_VENDOR_SEPOLICY_DIRS: $(LOCAL_PATH))
+    ifeq ($(LOCAL_PATH),bootable/recovery)
+        PROJECT_PATH_AGREES := true
+        BOARD_VENDOR_SEPOLICY_DIRS += bootable/recovery/sepolicy
+        $(warning BOARD_VENDOR_SEPOLICY_DIRS2: $(BOARD_VENDOR_SEPOLICY_DIRS))
+    else
+        ifeq ($(LOCAL_PATH),bootable/recovery-twrp)
+            ifeq ($(RECOVERY_VARIANT),twrp)
+                PROJECT_PATH_AGREES := true
+                BOARD_VENDOR_SEPOLICY_DIRS += bootable/recovery-twrp/sepolicy
+            endif
+        endif
+    endif
+endif
+
+ifeq ($(PROJECT_PATH_AGREES),true)
+
+ifeq ($(CM_PLATFORM_SDK_VERSION),)
+    CM_PLATFORM_SDK_VERSION := 0
+endif
+
+include $(CLEAR_VARS)
+
+TARGET_RECOVERY_GUI := true
+
+LOCAL_STATIC_LIBRARIES :=
+LOCAL_SHARED_LIBRARIES :=
+
+ifneq ($(TW_DEVICE_VERSION),)
+    LOCAL_CFLAGS += -DTW_DEVICE_VERSION='"-$(TW_DEVICE_VERSION)"'
+else
+    LOCAL_CFLAGS += -DTW_DEVICE_VERSION='"-0"'
+endif
+LOCAL_CFLAGS += -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION)
+
+LOCAL_SRC_FILES := \
+    twrp.cpp \
+    fixContexts.cpp \
+    twrpTar.cpp \
+    exclude.cpp \
+    find_file.cpp \
+    infomanager.cpp \
+    data.cpp \
+    partition.cpp \
+    partitionmanager.cpp \
+    progresstracking.cpp \
+    startupArgs.cpp \
+    twrp-functions.cpp \
+    twrpDigestDriver.cpp \
+    openrecoveryscript.cpp \
+    tarWrite.c \
+    twrpAdbBuFifo.cpp \
+    twrpRepacker.cpp
+
+ifeq ($(TW_EXCLUDE_APEX),)
+    LOCAL_SRC_FILES += twrpApex.cpp
+else
+    LOCAL_CFLAGS += -DTW_EXCLUDE_APEX
+endif
+
+LOCAL_STATIC_LIBRARIES += libavb libtwrpinstall libminadbd_services libinit libsnapshot_nobinder update_metadata-protos
+LOCAL_SHARED_LIBRARIES += libfs_mgr libhardware android.hardware.boot@1.0 android.hardware.boot@1.1 android.hardware.boot@1.2 libprotobuf-cpp-lite liblp libutils libhidlbase
+LOCAL_C_INCLUDES += \
+    system/core/fs_mgr/libfs_avb/include/ \
+    system/core/fs_mgr/include_fstab/ \
+    system/core/fs_mgr/include/ \
+    system/core/fs_mgr/libdm/include/ \
+    system/core/fs_mgr/liblp/include/ \
+    system/gsid/include/ \
+    system/core/init/ \
+    system/extras/ext4_utils/include \
+    system/vold
+
+ifneq ($(TARGET_RECOVERY_REBOOT_SRC),)
+  LOCAL_SRC_FILES += $(TARGET_RECOVERY_REBOOT_SRC)
+endif
+
+LOCAL_MODULE := recovery
+
 RECOVERY_API_VERSION := 3
 RECOVERY_FSTAB_VERSION := 2
+LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
+LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function
+LOCAL_CLANG := true
 
-# TARGET_RECOVERY_UI_LIB should be one of librecovery_ui_{default,wear,vr,ethernet} or a
-# device-specific module that defines make_device() and the exact RecoveryUI class for the
-# target. It defaults to librecovery_ui_default, which uses ScreenRecoveryUI.
-TARGET_RECOVERY_UI_LIB ?= librecovery_ui_default
+LOCAL_C_INCLUDES += \
+    bionic \
+    system/extras \
+    packages/modules/adb \
+    system/core/libsparse \
+    system/vold \
+    external/zlib \
+    system/libziparchive/include \
+    external/freetype/include \
+    external/boringssl/include \
+    external/libcxx/include \
+    external/libselinux/include \
+    external/libpng \
+    $(LOCAL_PATH)/gui/include \
+    $(LOCAL_PATH)/recovery_ui/include \
+    $(LOCAL_PATH)/otautil/include \
+    $(LOCAL_PATH)/install/include \
+    $(LOCAL_PATH)/fuse_sideload/include \
+    $(LOCAL_PATH)/install/include \
+    $(LOCAL_PATH)/twrpinstall/include \
+    $(LOCAL_PATH)/recovery_utils/include \
+    $(LOCAL_PATH)/libpixelflinger/include \
+    $(LOCAL_PATH)/minuitwrp/include \
+    $(LOCAL_PATH)/twinstall/include
 
-# librecovery_ui_ext (shared library)
-# ===================================
-include $(CLEAR_VARS)
+LOCAL_STATIC_LIBRARIES += libguitwrp libvold
+LOCAL_SHARED_LIBRARIES += libz libc libcutils libstdc++ libtar libblkid libminuitwrp libmtdutils libtwadbbu 
+LOCAL_SHARED_LIBRARIES += libbootloader_message libcrecovery libtwrpdigest libc++ libaosprecovery libcrypto libbase 
+LOCAL_SHARED_LIBRARIES += libziparchive libselinux libdl_android.bootstrap
 
-LOCAL_MODULE := librecovery_ui_ext
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-OFL
-LOCAL_LICENSE_CONDITIONS := by_exception_only notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
+ifneq ($(wildcard system/core/libsparse/Android.mk),)
+LOCAL_SHARED_LIBRARIES += libsparse
+endif
 
-# LOCAL_MODULE_PATH for shared libraries is unsupported in multiarch builds.
-LOCAL_MULTILIB := first
+ifeq ($(TW_OEM_BUILD),true)
+    LOCAL_CFLAGS += -DTW_OEM_BUILD
+    BOARD_HAS_NO_REAL_SDCARD := true
+    TW_USE_TOOLBOX := true
+    TW_EXCLUDE_MTP := true
+    TW_EXCLUDE_TZDATA := true
+    TW_EXCLUDE_NANO := true
+    TW_EXCLUDE_BASH := true
+endif
 
-ifeq ($(TARGET_IS_64_BIT),true)
-LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib64
+ifeq ($(AB_OTA_UPDATER),true)
+    LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+    TWRP_REQUIRED_MODULES += libhardware android.hardware.boot@1.0-service android.hardware.boot@1.0-service.rc \
+    android.hardware.boot@1.1-service android.hardware.boot@1.1-service.rc android.hardware.boot@1.1.xml \
+    android.hardware.boot@1.2-service android.hardware.boot@1.2-service.rc android.hardware.boot@1.2.xml
+endif
+
+ifeq ($(PRODUCT_USE_DYNAMIC_PARTITIONS),true)
+    LOCAL_CFLAGS += -DPRODUCT_USE_DYNAMIC_PARTITIONS=1
+    TWRP_REQUIRED_MODULES += android.hardware.health@2.1-service android.hardware.health@2.1-impl.recovery android.hardware.health@2.1-service.rc android.hardware.health@2.1.xml
+    TWRP_REQUIRED_MODULES += android.hardware.health@2.0-service android.hardware.health@2.0-impl.recovery android.hardware.health@2.0-service.rc
+    ifeq ($(TW_EXCLUDE_LPDUMP),)
+        TWRP_REQUIRED_MODULES += lpdump lpdumpd.rc
+    endif
+    ifeq ($(TW_EXCLUDE_LPTOOLS),)
+        TWRP_REQUIRED_MODULES += lptools
+    endif
+endif
+
+ifneq ($(TW_SYSTEM_BUILD_PROP_ADDITIONAL_PATHS),)
+    LOCAL_CFLAGS += -DTW_SYSTEM_BUILD_PROP_ADDITIONAL_PATHS='"$(TW_SYSTEM_BUILD_PROP_ADDITIONAL_PATHS)"'
+endif
+
+ifeq ($(TW_USES_VENDOR_LIBS),true)
+    LOCAL_CFLAGS += -DUSE_VENDOR_LIBS=1
+endif
+
+ifeq ($(TW_NO_BIND_SYSTEM),true)
+    LOCAL_CFLAGS += -DTW_NO_BIND_SYSTEM
+endif
+
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+
+ifeq ($(TARGET_RECOVERY_TWRP_LIB),)
+    LOCAL_SRC_FILES += BasePartition.cpp
 else
-LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib
+    LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_TWRP_LIB)
 endif
 
-LOCAL_WHOLE_STATIC_LIBRARIES := \
-    $(TARGET_RECOVERY_UI_LIB)
+LOCAL_C_INCLUDES += system/extras/ext4_utils
 
-LOCAL_SHARED_LIBRARIES := \
-    libbase.recovery \
-    liblog.recovery \
-    librecovery_ui.recovery
-
-include $(BUILD_SHARED_LIBRARY)
-
-# recovery_deps: A phony target that's depended on by `recovery`, which
-# builds additional modules conditionally based on Makefile variables.
-# ======================================================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := recovery_deps
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-OFL
-LOCAL_LICENSE_CONDITIONS := by_exception_only notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
-
-ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
-LOCAL_REQUIRED_MODULES += \
-    make_f2fs.recovery \
-    fsck.f2fs.recovery \
-    sload_f2fs.recovery
+tw_git_revision := $(shell git -C $(LOCAL_PATH) rev-parse --short=8 HEAD 2>/dev/null)
+ifeq ($(shell git -C $(LOCAL_PATH) diff --quiet; echo $$?),1)
+    tw_git_revision := $(tw_git_revision)-dirty
 endif
+LOCAL_CFLAGS += -DTW_GIT_REVISION='"$(tw_git_revision)"'
+
+ifeq ($(TW_FORCE_USE_BUSYBOX), true)
+    TW_USE_TOOLBOX := false
+else
+    TW_USE_TOOLBOX := true
+endif
+ifeq ($(TW_EXCLUDE_MTP),)
+    LOCAL_SHARED_LIBRARIES += libtwrpmtp-ffs
+endif
+ifeq ($(BOARD_USES_RECOVERY_AS_BOOT), true)
+    LOCAL_CFLAGS += -DBOARD_USES_RECOVERY_AS_BOOT
+endif
+ifeq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE), true)
+    LOCAL_CFLAGS += -DBOARD_BUILD_SYSTEM_ROOT_IMAGE
+endif
+
+#TWRP Build Flags
+ifeq ($(TW_EXCLUDE_MTP),)
+    LOCAL_CFLAGS += -DTW_HAS_MTP
+endif
+ifneq ($(TW_NO_SCREEN_TIMEOUT),)
+    LOCAL_CFLAGS += -DTW_NO_SCREEN_TIMEOUT
+endif
+ifeq ($(BOARD_HAS_NO_REAL_SDCARD), true)
+    LOCAL_CFLAGS += -DBOARD_HAS_NO_REAL_SDCARD
+endif
+ifneq ($(RECOVERY_SDCARD_ON_DATA),)
+	LOCAL_CFLAGS += -DRECOVERY_SDCARD_ON_DATA
+endif
+ifneq ($(TW_INCLUDE_DUMLOCK),)
+	LOCAL_CFLAGS += -DTW_INCLUDE_DUMLOCK
+endif
+ifneq ($(TW_INTERNAL_STORAGE_PATH),)
+	LOCAL_CFLAGS += -DTW_INTERNAL_STORAGE_PATH=$(TW_INTERNAL_STORAGE_PATH)
+endif
+ifneq ($(TW_INTERNAL_STORAGE_MOUNT_POINT),)
+	LOCAL_CFLAGS += -DTW_INTERNAL_STORAGE_MOUNT_POINT=$(TW_INTERNAL_STORAGE_MOUNT_POINT)
+endif
+ifneq ($(TW_EXTERNAL_STORAGE_PATH),)
+	LOCAL_CFLAGS += -DTW_EXTERNAL_STORAGE_PATH=$(TW_EXTERNAL_STORAGE_PATH)
+endif
+ifneq ($(TW_EXTERNAL_STORAGE_MOUNT_POINT),)
+	LOCAL_CFLAGS += -DTW_EXTERNAL_STORAGE_MOUNT_POINT=$(TW_EXTERNAL_STORAGE_MOUNT_POINT)
+endif
+ifeq ($(TW_HAS_NO_BOOT_PARTITION), true)
+    LOCAL_CFLAGS += -DTW_HAS_NO_BOOT_PARTITION
+endif
+ifeq ($(TW_NO_REBOOT_BOOTLOADER), true)
+    LOCAL_CFLAGS += -DTW_NO_REBOOT_BOOTLOADER
+endif
+ifeq ($(TW_NO_REBOOT_RECOVERY), true)
+    LOCAL_CFLAGS += -DTW_NO_REBOOT_RECOVERY
+endif
+ifeq ($(TW_NO_BATT_PERCENT), true)
+    LOCAL_CFLAGS += -DTW_NO_BATT_PERCENT
+endif
+ifeq ($(TW_NO_CPU_TEMP), true)
+    LOCAL_CFLAGS += -DTW_NO_CPU_TEMP
+endif
+ifneq ($(TW_CUSTOM_POWER_BUTTON),)
+	LOCAL_CFLAGS += -DTW_CUSTOM_POWER_BUTTON=$(TW_CUSTOM_POWER_BUTTON)
+endif
+ifeq ($(TW_ALWAYS_RMRF), true)
+    LOCAL_CFLAGS += -DTW_ALWAYS_RMRF
+endif
+ifeq ($(TW_NEVER_UNMOUNT_SYSTEM), true)
+    LOCAL_CFLAGS += -DTW_NEVER_UNMOUNT_SYSTEM
+endif
+ifeq ($(TW_NO_USB_STORAGE), true)
+    LOCAL_CFLAGS += -DTW_NO_USB_STORAGE
+endif
+ifeq ($(TW_INCLUDE_INJECTTWRP), true)
+    LOCAL_CFLAGS += -DTW_INCLUDE_INJECTTWRP
+endif
+ifeq ($(TW_INCLUDE_BLOBPACK), true)
+    LOCAL_CFLAGS += -DTW_INCLUDE_BLOBPACK
+endif
+ifneq ($(TARGET_USE_CUSTOM_LUN_FILE_PATH),)
+    LOCAL_CFLAGS += -DCUSTOM_LUN_FILE=\"$(TARGET_USE_CUSTOM_LUN_FILE_PATH)\"
+endif
+ifneq ($(BOARD_UMS_LUNFILE),)
+    LOCAL_CFLAGS += -DCUSTOM_LUN_FILE=\"$(BOARD_UMS_LUNFILE)\"
+endif
+ifeq ($(TW_HAS_DOWNLOAD_MODE), true)
+    LOCAL_CFLAGS += -DTW_HAS_DOWNLOAD_MODE
+endif
+ifeq ($(TW_HAS_EDL_MODE), true)
+    LOCAL_CFLAGS += -DTW_HAS_EDL_MODE
+endif
+ifeq ($(TW_NO_SCREEN_BLANK), true)
+    LOCAL_CFLAGS += -DTW_NO_SCREEN_BLANK
+endif
+ifeq ($(TW_SDEXT_NO_EXT4), true)
+    LOCAL_CFLAGS += -DTW_SDEXT_NO_EXT4
+endif
+ifeq ($(TW_FORCE_CPUINFO_FOR_DEVICE_ID), true)
+    LOCAL_CFLAGS += -DTW_FORCE_CPUINFO_FOR_DEVICE_ID
+endif
+ifeq ($(TW_NO_EXFAT_FUSE), true)
+    LOCAL_CFLAGS += -DTW_NO_EXFAT_FUSE
+endif
+ifeq ($(TW_NO_HAPTICS), true)
+    LOCAL_CFLAGS += -DTW_NO_HAPTICS
+endif
+ifneq ($(TW_ADDITIONAL_APEX_FILES),)
+    LOCAL_CFLAGS += -DTW_ADDITIONAL_APEX_FILES=$(TW_ADDITIONAL_APEX_FILES)
+endif
+ifneq ($(TW_LOAD_VENDOR_MODULES),)
+    LOCAL_SRC_FILES += kernel_module_loader.cpp
+    LOCAL_C_INCLUDES += system/core/libmodprobe/include
+    LOCAL_STATIC_LIBRARIES += libmodprobe
+    LOCAL_CFLAGS += -DTW_LOAD_VENDOR_MODULES=$(TW_LOAD_VENDOR_MODULES)
+endif
+ifeq ($(TW_INCLUDE_CRYPTO), true)
+    LOCAL_CFLAGS += -DTW_INCLUDE_CRYPTO -DUSE_FSCRYPT -Wno-macro-redefined
+    LOCAL_SHARED_LIBRARIES += libgpt_twrp
+    LOCAL_C_INCLUDES += external/boringssl/src/include bootable/recovery/crypto
+    TW_INCLUDE_CRYPTO_FBE := true
+    LOCAL_CFLAGS += -DTW_INCLUDE_FBE
+    LOCAL_SHARED_LIBRARIES += android.frameworks.stats@1.0 android.hardware.authsecret@1.0 \
+	android.security.authorization-ndk \
+        android.hardware.oemlock@1.0 libf2fs_sparseblock libbinder libbinder_ndk \
+        libandroidicu.recovery \
+        android.hardware.gatekeeper@1.0 \
+        android.hardware.weaver@1.0 \
+        android.frameworks.stats@1.0 \
+        android.security.maintenance-ndk \
+        android.system.keystore2-V1-ndk \
+        libkeyutils \
+        liblog \
+        libsqlite.recovery \
+        libkeystoreinfo.recovery \
+        libgatekeeper_aidl
+
+    LOCAL_STATIC_LIBRARIES += libkeymint_support
+
+    LOCAL_CFLAGS += -DTW_INCLUDE_FBE_METADATA_DECRYPT
+    ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),)
+    ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),false)
+		TW_INCLUDE_LIBRESETPROP := true
+        LOCAL_CFLAGS += -DTW_CRYPTO_USE_SYSTEM_VOLD
+        LOCAL_STATIC_LIBRARIES += libvolddecrypt
+    endif
+    endif
+
+    ifeq ($(TARGET_HW_DISK_ENCRYPTION),true)
+        ifeq ($(TARGET_CRYPTFS_HW_PATH),)
+            LOCAL_C_INCLUDES += device/qcom/common/cryptfs_hw
+        else
+            LOCAL_C_INCLUDES += $(TARGET_CRYPTFS_HW_PATH)
+        endif
+        LOCAL_SHARED_LIBRARIES += libcryptfs_hw
+    endif
+endif
+WITH_CRYPTO_UTILS := \
+    $(if $(wildcard system/core/libcrypto_utils/android_pubkey.c),true)
+ifeq ($(TW_USE_MODEL_HARDWARE_ID_FOR_DEVICE_ID), true)
+    LOCAL_CFLAGS += -DTW_USE_MODEL_HARDWARE_ID_FOR_DEVICE_ID
+endif
+ifeq ($(TW_USE_SERIALNO_PROPERTY_FOR_DEVICE_ID), true)
+    LOCAL_CFLAGS += -DTW_USE_SERIALNO_PROPERTY_FOR_DEVICE_ID
+endif
+ifneq ($(TW_BRIGHTNESS_PATH),)
+	LOCAL_CFLAGS += -DTW_BRIGHTNESS_PATH=$(TW_BRIGHTNESS_PATH)
+endif
+ifneq ($(TW_SECONDARY_BRIGHTNESS_PATH),)
+	LOCAL_CFLAGS += -DTW_SECONDARY_BRIGHTNESS_PATH=$(TW_SECONDARY_BRIGHTNESS_PATH)
+endif
+ifneq ($(TW_MAX_BRIGHTNESS),)
+	LOCAL_CFLAGS += -DTW_MAX_BRIGHTNESS=$(TW_MAX_BRIGHTNESS)
+endif
+ifneq ($(TW_DEFAULT_BRIGHTNESS),)
+	LOCAL_CFLAGS += -DTW_DEFAULT_BRIGHTNESS=$(TW_DEFAULT_BRIGHTNESS)
+endif
+ifneq ($(TW_CUSTOM_BATTERY_PATH),)
+	LOCAL_CFLAGS += -DTW_CUSTOM_BATTERY_PATH=$(TW_CUSTOM_BATTERY_PATH)
+endif
+ifneq ($(TW_BATTERY_SYSFS_WAIT_SECONDS),)
+	LOCAL_CFLAGS += -DTW_BATTERY_SYSFS_WAIT_SECONDS=$(TW_BATTERY_SYSFS_WAIT_SECONDS)
+else
+	LOCAL_CFLAGS += -DTW_BATTERY_SYSFS_WAIT_SECONDS=3
+endif
+ifneq ($(TW_CUSTOM_CPU_TEMP_PATH),)
+	LOCAL_CFLAGS += -DTW_CUSTOM_CPU_TEMP_PATH=$(TW_CUSTOM_CPU_TEMP_PATH)
+endif
+ifneq ($(TW_EXCLUDE_ENCRYPTED_BACKUPS),)
+    LOCAL_SHARED_LIBRARIES += libopenaes
+else
+    LOCAL_CFLAGS += -DTW_EXCLUDE_ENCRYPTED_BACKUPS
+endif
+ifeq ($(TARGET_RECOVERY_QCOM_RTC_FIX),)
+  ifneq ($(filter msm8226 msm8x26 msm8610 msm8974 msm8x74 msm8084 msm8x84 apq8084 msm8909 msm8916 msm8992 msm8994 msm8952 msm8996 msm8937 msm8953 msm8998,$(TARGET_BOARD_PLATFORM)),)
+    LOCAL_CFLAGS += -DQCOM_RTC_FIX
+  else ifeq ($(TARGET_CPU_VARIANT),krait)
+    LOCAL_CFLAGS += -DQCOM_RTC_FIX
+  endif
+else ifeq ($(TARGET_RECOVERY_QCOM_RTC_FIX),true)
+    LOCAL_CFLAGS += -DQCOM_RTC_FIX
+endif
+ifneq ($(TW_NO_LEGACY_PROPS),)
+	LOCAL_CFLAGS += -DTW_NO_LEGACY_PROPS
+endif
+ifneq ($(wildcard bionic/libc/include/sys/capability.h),)
+    LOCAL_CFLAGS += -DHAVE_CAPABILITIES
+endif
+ifneq ($(TARGET_RECOVERY_INITRC),)
+    TW_EXCLUDE_DEFAULT_USB_INIT := true
+endif
+LOCAL_CFLAGS += -DTW_USE_NEW_MINADBD
+ifneq ($(TW_DEFAULT_LANGUAGE),)
+    LOCAL_CFLAGS += -DTW_DEFAULT_LANGUAGE=$(TW_DEFAULT_LANGUAGE)
+else
+    LOCAL_CFLAGS += -DTW_DEFAULT_LANGUAGE=en
+endif
+ifneq ($(TW_QCOM_ATS_OFFSET),)
+	LOCAL_CFLAGS += -DTW_QCOM_ATS_OFFSET=$(TW_QCOM_ATS_OFFSET)
+endif
+ifneq ($(TW_CLOCK_OFFSET),)
+	LOCAL_CFLAGS += -DTW_CLOCK_OFFSET=$(TW_CLOCK_OFFSET)
+endif
+ifneq ($(TW_OVERRIDE_SYSTEM_PROPS),)
+    TW_INCLUDE_LIBRESETPROP := true
+    LOCAL_CFLAGS += -DTW_OVERRIDE_SYSTEM_PROPS=$(TW_OVERRIDE_SYSTEM_PROPS)
+endif
+ifneq ($(TW_OVERRIDE_PROPS_ADDITIONAL_PARTITIONS),)
+    LOCAL_CFLAGS += -DTW_OVERRIDE_PROPS_ADDITIONAL_PARTITIONS='"$(TW_OVERRIDE_PROPS_ADDITIONAL_PARTITIONS)"'
+endif
+ifneq ($(TW_INCLUDE_LIBRESETPROP),)
+    LOCAL_SHARED_LIBRARIES += libresetprop
+    LOCAL_C_INCLUDES += external/magisk-prebuilt/include
+    LOCAL_CFLAGS += -DTW_INCLUDE_LIBRESETPROP
+endif
+ifeq ($(TW_EXCLUDE_NANO), true)
+    LOCAL_CFLAGS += -DTW_EXCLUDE_NANO
+endif
+ifneq ($(TARGET_OTA_ASSERT_DEVICE),)
+    LOCAL_CFLAGS += -DTARGET_OTA_ASSERT_DEVICE='"$(TARGET_OTA_ASSERT_DEVICE)"'
+endif
+ifneq ($(TW_BACKUP_EXCLUSIONS),)
+	LOCAL_CFLAGS += -DTW_BACKUP_EXCLUSIONS='"$(TW_BACKUP_EXCLUSIONS)"'
+endif
+ifeq ($(TW_INCLUDE_FASTBOOTD), true)
+    LOCAL_CFLAGS += -DTW_INCLUDE_FASTBOOTD
+endif
+
+LOCAL_C_INCLUDES += system/vold \
+
+TWRP_REQUIRED_MODULES += \
+    relink_libraries \
+    relink_binaries \
+    relink_vendor_hw_binaries \
+    twrp_ramdisk \
+    bc \
+    dump_image \
+    erase_image \
+    flash_image \
+    mke2fs.conf \
+    pigz \
+    teamwin \
+    twrp \
+    fsck.fat \
+    fatlabel \
+    mkfs.fat \
+    permissive.sh \
+    simg2img_twrp \
+    libbootloader_message \
+    init.recovery.hlthchrg.rc \
+    init.recovery.service.rc \
+    init.recovery.ldconfig.rc \
+    awk \
+    toybox \
+    toolbox \
+    mkshrc_twrp \
+    plat_hwservice_contexts \
+    vendor_hwservice_contexts \
+    minadbd \
+    twrpbu \
+    adbd_system_api_recovery \
+    me.twrp.twrpapp.apk \
+    privapp-permissions-twrpapp.xml \
+    adbd_system_api_recovery \
+    libsync.recovery \
+    libandroidicu.recovery
+
+ifneq ($(TW_EXCLUDE_TZDATA), true)
+TWRP_REQUIRED_MODULES += \
+    tzdata_twrp
+endif
+
+ifneq ($(TW_EXCLUDE_NANO), true)
+TWRP_REQUIRED_MODULES += \
+    nano_twrp \
+    nano.rc
+endif
+
+ifneq ($(TW_EXCLUDE_BASH), true)
+    ifneq ($(wildcard external/bash/.),)
+    TWRP_REQUIRED_MODULES += \
+        bash_twrp
+    endif
+endif
+
+ifeq ($(TW_INCLUDE_REPACKTOOLS), true)
+TWRP_REQUIRED_MODULES += \
+    magiskboot
+endif
+
+ifeq ($(TW_INCLUDE_RESETPROP), true)
+TWRP_REQUIRED_MODULES += \
+    resetprop
+endif
+
+TWRP_REQUIRED_MODULES += \
+    hwservicemanager \
+    hwservicemanager.rc \
+    vndservicemanager \
+    vndservicemanager.rc
+
+ifneq ($(TW_INCLUDE_CRYPTO),)
+TWRP_REQUIRED_MODULES += \
+    vold_prepare_subdirs \
+    task_recovery_profiles.json \
+    fscryptpolicyget.recovery \
+    keystore_auth \
+    keystore2 \
+    android.system.keystore2-service.xml \
+    keystore2.rc \
+    plat_keystore2_key_contexts
+
+    ifneq ($(TW_INCLUDE_CRYPTO_FBE),)
+    TWRP_REQUIRED_MODULES += \
+        plat_service_contexts \
+        servicemanager \
+        servicemanager.rc
+    endif
+endif
+
+ifneq ($(wildcard external/zip/Android.mk),)
+    TWRP_REQUIRED_MODULES += zip
+endif
+ifneq ($(wildcard external/unzip/Android.mk),)
+    TWRP_REQUIRED_MODULES += unzip
+endif
+
+ifneq ($(TW_NO_EXFAT), true)
+    TWRP_REQUIRED_MODULES += mkexfatfs fsckexfat
+    ifneq ($(TW_NO_EXFAT_FUSE), true)
+        TWRP_REQUIRED_MODULES += exfat-fuse
+    endif
+endif
+ifeq ($(BOARD_HAS_NO_REAL_SDCARD),)
+    TWRP_REQUIRED_MODULES += sgdisk
+endif
+ifneq ($(TW_EXCLUDE_ENCRYPTED_BACKUPS),)
+    TWRP_REQUIRED_MODULES += openaes openaes_license
+endif
+ifeq ($(TW_INCLUDE_DUMLOCK), true)
+    TWRP_REQUIRED_MODULES += \
+        htcdumlock htcdumlocksys flash_imagesys dump_imagesys libbmlutils.so \
+        libflashutils.so libmmcutils.so libmtdutils.so HTCDumlock.apk
+endif
+ifeq ($(TW_INCLUDE_FB2PNG), true)
+    TWRP_REQUIRED_MODULES += fb2png
+endif
+ifneq ($(TW_OEM_BUILD),true)
+    TWRP_REQUIRED_MODULES += orscmd
+endif
+ifeq ($(BOARD_USES_BML_OVER_MTD),true)
+    TWRP_REQUIRED_MODULES += bml_over_mtd
+endif
+ifeq ($(TW_INCLUDE_INJECTTWRP), true)
+    TWRP_REQUIRED_MODULES += injecttwrp
+endif
+ifneq ($(TW_EXCLUDE_DEFAULT_USB_INIT), true)
+    TWRP_REQUIRED_MODULES += init.recovery.usb.rc
+endif
+ifeq ($(TWRP_INCLUDE_LOGCAT), true)
+    TWRP_REQUIRED_MODULES += logcat event-log-tags
+    ifeq ($(TARGET_USES_LOGD), true)
+        TWRP_REQUIRED_MODULES += logd libsysutils libnl init.recovery.logd.rc
+    endif
+endif
+# Allow devices to specify device-specific recovery dependencies
+ifneq ($(TARGET_RECOVERY_DEVICE_MODULES),)
+    TWRP_REQUIRED_MODULES += $(TARGET_RECOVERY_DEVICE_MODULES)
+endif
+ifeq ($(TW_INCLUDE_NTFS_3G),true)
+    TWRP_REQUIRED_MODULES += \
+        mount.ntfs \
+        fsck.ntfs \
+        mkfs.ntfs
+endif
+ifeq ($(TARGET_USERIMAGES_USE_F2FS), true)
+    TWRP_REQUIRED_MODULES += sload_f2fs \
+        libfs_mgr \
+        fs_mgr \
+        liblz4 \
+        libinit
+endif
+ifneq ($(TW_LOAD_VENDOR_MODULES),)
+    TWRP_REQUIRED_MODULES += libmodprobe
+endif
+ifeq ($(TW_INCLUDE_PYTHON),true)
+    TWRP_REQUIRED_MODULES += python3_twrp
+endif
+
+TWRP_REQUIRED_MODULES += file_contexts_text
 
 LOCAL_REQUIRED_MODULES += \
     mkfs.erofs.recovery \
@@ -79,10 +615,155 @@
 # will be deteleted after the report.
 LOCAL_REQUIRED_MODULES += recovery-persist
 ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),)
-LOCAL_REQUIRED_MODULES += recovery-refresh
+    TWRP_REQUIRED_MODULES += recovery-persist recovery-refresh
 endif
 
+LOCAL_REQUIRED_MODULES += $(TWRP_REQUIRED_MODULES)
+
+include $(BUILD_EXECUTABLE)
+
+# Symlink for file_contexts
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := file_contexts_text
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := file_contexts.bin
+
+LOCAL_POST_INSTALL_CMD := \
+    $(hide) cp -f $(PRODUCT_OUT)/obj/ETC/file_contexts.bin_intermediates/file_contexts.concat.tmp $(TARGET_RECOVERY_ROOT_OUT)/file_contexts
+
 include $(BUILD_PHONY_PACKAGE)
 
+# recovery-persist (system partition dynamic executable run after /data mounts)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    recovery-persist.cpp 
+LOCAL_MODULE := recovery-persist
+LOCAL_SHARED_LIBRARIES := liblog libbase 
+LOCAL_STATIC_LIBRARIES := libotautil librecovery_utils
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/otautil/include
+LOCAL_C_INCLUDES += system/core/libstats/include
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/recovery_utils/include
+LOCAL_CFLAGS := -Werror
+LOCAL_INIT_RC := recovery-persist.rc
+include $(BUILD_EXECUTABLE)
+
+# recovery-refresh (system partition dynamic executable run at init)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    recovery-refresh.cpp
+LOCAL_MODULE := recovery-refresh
+LOCAL_SHARED_LIBRARIES := liblog libbase
+LOCAL_STATIC_LIBRARIES := libotautil librecovery_utils
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/otautil/include
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/recovery_utils/include
+LOCAL_CFLAGS := -Werror
+LOCAL_INIT_RC := recovery-refresh.rc
+include $(BUILD_EXECUTABLE)
+
+# libmounts (static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := mounts.cpp
+LOCAL_CFLAGS := \
+    -Wall \
+    -Werror
+LOCAL_MODULE := libmounts
+LOCAL_STATIC_LIBRARIES := libbase
+include $(BUILD_STATIC_LIBRARY)
+
+# librecovery (static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    install.cpp
+LOCAL_CFLAGS := -Wall -Werror
+LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
+
+ifeq ($(AB_OTA_UPDATER),true)
+    LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+endif
+
+LOCAL_MODULE := librecovery
+LOCAL_STATIC_LIBRARIES := \
+    libminui \
+    libotautil \
+    libvintf \
+    libcrypto_utils \
+    libcrypto_static \
+    libbase \
+    libziparchive \
+
+include $(BUILD_STATIC_LIBRARY)
+
+commands_recovery_local_path := $(LOCAL_PATH)
+
 include \
-    $(LOCAL_PATH)/updater/Android.mk \
+    $(commands_TWRP_local_path)/updater/Android.mk
+
+include $(commands_TWRP_local_path)/mtp/ffs/Android.mk \
+    $(commands_TWRP_local_path)/minui/Android.mk
+
+#includes for TWRP
+include $(commands_TWRP_local_path)/injecttwrp/Android.mk \
+    $(commands_TWRP_local_path)/htcdumlock/Android.mk \
+    $(commands_TWRP_local_path)/mmcutils/Android.mk \
+    $(commands_TWRP_local_path)/bmlutils/Android.mk \
+    $(commands_TWRP_local_path)/prebuilt/Android.mk \
+    $(commands_TWRP_local_path)/mtdutils/Android.mk \
+    $(commands_TWRP_local_path)/flashutils/Android.mk \
+    $(commands_TWRP_local_path)/pigz/Android.mk \
+    $(commands_TWRP_local_path)/libtar/Android.mk \
+    $(commands_TWRP_local_path)/libcrecovery/Android.mk \
+    $(commands_TWRP_local_path)/libblkid/Android.mk \
+    $(commands_TWRP_local_path)/openaes/Android.mk \
+    $(commands_TWRP_local_path)/twrpTarMain/Android.mk \
+    $(commands_TWRP_local_path)/minzip/Android.mk \
+    $(commands_TWRP_local_path)/dosfstools/Android.mk \
+    $(commands_TWRP_local_path)/etc/Android.mk \
+    $(commands_TWRP_local_path)/simg2img/Android.mk \
+    $(commands_TWRP_local_path)/adbbu/Android.mk \
+    $(commands_TWRP_local_path)/twrpDigest/Android.mk \
+    $(commands_TWRP_local_path)/attr/Android.mk
+
+ifneq ($(TW_OZIP_DECRYPT_KEY),)
+    TWRP_REQUIRED_MODULES += ozip_decrypt
+    include $(commands_TWRP_local_path)/ozip_decrypt/Android.mk
+endif
+
+ifeq ($(TW_INCLUDE_CRYPTO), true)
+    # include $(commands_TWRP_local_path)/crypto/fde/Android.mk
+    include $(commands_TWRP_local_path)/crypto/scrypt/Android.mk
+    ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),)
+    ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),false)
+        include $(commands_TWRP_local_path)/crypto/vold_decrypt/Android.mk
+    endif
+    endif
+    include $(commands_TWRP_local_path)/gpt/Android.mk
+endif
+ifeq ($(BUILD_ID), GINGERBREAD)
+    TW_NO_EXFAT := true
+endif
+ifneq ($(TW_NO_EXFAT), true)
+    include $(commands_TWRP_local_path)/exfat/mkfs/Android.mk \
+            $(commands_TWRP_local_path)/exfat/fsck/Android.mk \
+            $(commands_TWRP_local_path)/fuse/Android.mk \
+            $(commands_TWRP_local_path)/exfat/libexfat/Android.mk
+    ifneq ($(TW_NO_EXFAT_FUSE), true)
+        include $(commands_TWRP_local_path)/exfat/fuse/Android.mk
+    endif
+endif
+ifneq ($(TW_OEM_BUILD),true)
+    include $(commands_TWRP_local_path)/orscmd/Android.mk
+endif
+
+# FB2PNG
+ifeq ($(TW_INCLUDE_FB2PNG), true)
+    include $(commands_TWRP_local_path)/fb2png/Android.mk
+endif
+
+endif
+
+commands_TWRP_local_path :=
diff --git a/BasePartition.cpp b/BasePartition.cpp
new file mode 100644
index 0000000..3b34d75
--- /dev/null
+++ b/BasePartition.cpp
@@ -0,0 +1,28 @@
+/*
+	Copyright 2018 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "partitions.hpp"
+
+// On devices that need it, this is supposed to be overridden using
+// TARGET_RECOVERY_TWRP_LIB to allow device-specific pre and post
+// wipe encryption calls.
+
+BasePartition* make_partition() {
+  return new BasePartition();
+}
diff --git a/NOTICE b/NOTICE
index c5b1efa..346e71a 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,190 +1,634 @@
 
-   Copyright (c) 2005-2008, The Android Open Source Project
+    Copyright (c) 2011-2016, Dees_Troy, bigbiff, Team Win
 
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
+    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
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
 
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
 
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
 
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
 
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+                            Preamble
 
-   1. Definitions.
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
 
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
 
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
 
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
 
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
 
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
 
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
 
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
 
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
 
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
+  The precise terms and conditions for copying, distribution and
+modification follow.
 
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
+                       TERMS AND CONDITIONS
 
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
+  0. Definitions.
 
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
+  "This License" refers to version 3 of the GNU General Public License.
 
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
 
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
 
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
 
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
 
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
 
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
 
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
 
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
+  1. Source Code.
 
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
 
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
 
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
 
-   END OF TERMS AND CONDITIONS
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
 
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 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.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
index 5ab19d1..820a8b7
--- a/README.md
+++ b/README.md
@@ -1,159 +1,3 @@
-The Recovery Image
-==================
+**Team Win Recovery Project (TWRP)**
 
-Quick turn-around testing
--------------------------
-
-* Devices using recovery-as-boot (e.g. Pixels, which set BOARD\_USES\_RECOVERY\_AS\_BOOT)
-
-      # After setting up environment and lunch.
-      m -j bootimage
-      adb reboot bootloader
-
-      # Pixel devices don't support booting into recovery mode with `fastboot boot`.
-      fastboot flash boot
-
-      # Manually choose `Recovery mode` from bootloader menu.
-
-* Devices with a separate recovery image (e.g. Nexus)
-
-      # After setting up environment and lunch.
-      mm -j && m ramdisk-nodeps && m recoveryimage-nodeps
-      adb reboot bootloader
-
-      # To boot into the new recovery image without flashing the recovery partition:
-      fastboot boot $ANDROID_PRODUCT_OUT/recovery.img
-
-Running the tests
------------------
-
-    # After setting up environment and lunch.
-    mmma -j bootable/recovery
-
-    # Running the tests on device (under normal boot).
-    adb root
-    adb sync data
-
-    # 32-bit device
-    adb shell /data/nativetest/recovery_unit_test/recovery_unit_test
-
-    # Or 64-bit device
-    adb shell /data/nativetest64/recovery_unit_test/recovery_unit_test
-
-Running the manual tests
-------------------------
-
-`recovery-refresh` and `recovery-persist` executables exist only on systems without
-/cache partition. And we need to follow special steps to run tests for them.
-
-- Execute the test on an A/B device first. The test should fail but it will log
-  some contents to pmsg.
-
-- Reboot the device immediately and run the test again. The test should save the
-  contents of pmsg buffer into /data/misc/recovery/inject.txt. Test will pass if
-  this file has expected contents.
-
-Using `adb` under recovery
---------------------------
-
-When running recovery image from debuggable builds (i.e. `-eng` or `-userdebug` build variants, or
-`ro.debuggable=1` in `/prop.default`), `adbd` service is enabled and started by default, which
-allows `adb` communication. A device should be listed under `adb devices`, either in `recovery` or
-`sideload` state.
-
-    $ adb devices
-    List of devices attached
-    1234567890abcdef    recovery
-
-Although `/system/bin/adbd` is built from the same code base as the one in the normal boot, only a
-subset of `adb` commands are meaningful under recovery, such as `adb root`, `adb shell`, `adb push`,
-`adb pull` etc. Since Android Q, `adb shell` no longer requires manually mounting `/system` from
-recovery menu.
-
-## Troubleshooting
-
-### `adb devices` doesn't show the device.
-
-    $ adb devices
-    List of devices attached
-
- * Ensure `adbd` is built and running.
-
-By default, `adbd` is always included into recovery image, as `/system/bin/adbd`. `init` starts
-`adbd` service automatically only in debuggable builds. This behavior is controlled by the recovery
-specific `/init.rc`, whose source code is at `bootable/recovery/etc/init.rc`.
-
-The best way to confirm a running `adbd` is by checking the serial output, which shows a service
-start log as below.
-
-    [   18.961986] c1      1 init: starting service 'adbd'...
-
- * Ensure USB gadget has been enabled.
-
-If `adbd` service has been started but device not shown under `adb devices`, use `lsusb(8)` (on
-host) to check if the device is visible to the host.
-
-`bootable/recovery/etc/init.rc` disables Android USB gadget (via sysfs) as part of the `fs` action
-trigger, and will only re-enable it in debuggable builds (the `on property` rule will always run
-_after_ `on fs`).
-
-    on fs
-        write /sys/class/android_usb/android0/enable 0
-
-    # Always start adbd on userdebug and eng builds
-    on property:ro.debuggable=1
-        write /sys/class/android_usb/android0/enable 1
-        start adbd
-
-If device is using [configfs](https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt),
-check if configfs has been properly set up in init rc scripts. See the [example
-configuration](https://android.googlesource.com/device/google/wahoo/+/master/init.recovery.hardware.rc)
-for Pixel 2 devices. Note that the flag set via sysfs (i.e. the one above) is no-op when using
-configfs.
-
-### `adb devices` shows the device, but in `unauthorized` state.
-
-    $ adb devices
-    List of devices attached
-    1234567890abcdef    unauthorized
-
-recovery image doesn't honor the USB debugging toggle and the authorizations added under normal boot
-(because such authorization data stays in /data, which recovery doesn't mount), nor does it support
-authorizing a host device under recovery. We can use one of the following options instead.
-
- * **Option 1 (Recommended):** Authorize a host device with adb vendor keys.
-
-For debuggable builds, an RSA keypair can be used to authorize a host device that has the private
-key. The public key, defined via `PRODUCT_ADB_KEYS`, will be copied to `/adb_keys`. When starting
-the host-side `adbd`, make sure the filename (or the directory) of the matching private key has been
-added to `$ADB_VENDOR_KEYS`.
-
-    $ export ADB_VENDOR_KEYS=/path/to/adb/private/key
-    $ adb kill-server
-    $ adb devices
-
-`-user` builds filter out `PRODUCT_ADB_KEYS`, so no `/adb_keys` will be included there.
-
-Note that this mechanism applies to both of normal boot and recovery modes.
-
- * **Option 2:** Allow `adbd` to connect without authentication.
-   * `adbd` is compiled with `ALLOW_ADBD_NO_AUTH` (only on debuggable builds).
-   * `ro.adb.secure` has a value of `0`.
-
-Both of the two conditions need to be satisfied. Although `ro.adb.secure` is a runtime property, its
-value is set at build time (written into `/prop.default`). It defaults to `1` on `-user` builds, and
-`0` for other build variants. The value is overridable via `PRODUCT_DEFAULT_PROPERTY_OVERRIDES`.
-
-Localization of the background texts
-------------------------------------
-
-The recovery image supports localization of several background texts, e.g. installing, error,
-factory reset warnings, etc. For devices using `xxhdpi` and `xxxhdpi`, the build system generates
-these localization images dynamically since android-10 when building the recovery image. While
-the static images under res-*dpi/images/ is used for other display resolutions and as a
-backup.
-
-Check the invocation of the image_generator tool in the [makefile]. And the detailed usage of the
-image_generator is documented [here](./tools/image_generator/README.md).
-
-[makefile]: https://android.googlesource.com/platform/build/+/refs/heads/master/core/Makefile#1800
+You can find a compiling guide [here](http://forum.xda-developers.com/showthread.php?t=1943625 "Guide").
diff --git a/abx-functions.hpp b/abx-functions.hpp
new file mode 100755
index 0000000..003586d
--- /dev/null
+++ b/abx-functions.hpp
@@ -0,0 +1,289 @@
+#ifndef _ABXFUNCTIONS_HPP
+#define _ABXFUNCTIONS_HPP
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <cstring>
+#include <vector>
+#include <algorithm>
+#include <openssl/evp.h>
+
+// ABX (Android Binary XML) to XML converter
+// Copyright (c) 2022, _that
+// SPDX-License-Identifier: GPL-3.0-only
+
+// test: g++ -g abxtoxml.cpp `pkg-config --libs openssl` && ./a.out packages.xml
+
+// based on https://www.cclsolutionsgroup.com/post/android-abx-binary-xml
+
+using namespace std;
+
+class AbxToXml
+{
+	std::istream& m_is;
+	std::ostream& m_os;
+	bool mTagOpen = false;
+	std::vector<std::string> mInternedStrings;
+	bool mError = true;
+
+public:
+	AbxToXml(std::istream& is, std::ostream& os) : m_is(is), m_os(os) {}
+
+	bool run()
+	{
+		mError = true;
+		char buffer[4];
+		m_is.read(buffer, 4);
+		if (memcmp(buffer, "ABX", 3) != 0)
+			// TODO: handle error: not an ABX file
+			return false;
+
+		if (buffer[3] != 0)
+			// TODO: handle error: we only understand ABX version 0
+			return false;
+
+		mError = false;
+		return convert_abx_content();
+	}
+
+private:
+	enum Event
+	{
+		// based on XmlPullParser.java in libcore
+		START_DOCUMENT = 0,
+		END_DOCUMENT = 1,
+		START_TAG = 2,
+		END_TAG = 3,
+		TEXT = 4,
+		CDSECT = 5,
+		ENTITY_REF = 6,
+		IGNORABLE_WHITESPACE = 7,
+		PROCESSING_INSTRUCTION = 8,
+		COMMENT = 9,
+		DOCDECL = 10,
+
+		// based on BinaryXmlSerializer.java
+		ATTRIBUTE = 15,
+	};
+
+	// based on BinaryXmlSerializer.java
+	enum Type
+	{
+		TYPE_NULL = 1 << 4,
+		TYPE_STRING = 2 << 4,
+		TYPE_STRING_INTERNED = 3 << 4,
+		TYPE_BYTES_HEX = 4 << 4,
+		TYPE_BYTES_BASE64 = 5 << 4,
+		TYPE_INT = 6 << 4,
+		TYPE_INT_HEX = 7 << 4,
+		TYPE_LONG = 8 << 4,
+		TYPE_LONG_HEX = 9 << 4,
+		TYPE_FLOAT = 10 << 4,
+		TYPE_DOUBLE = 11 << 4,
+		TYPE_BOOLEAN_TRUE = 12 << 4,
+		TYPE_BOOLEAN_FALSE = 13 << 4,
+	};
+
+
+	bool convert_abx_content()
+	{
+		char b;
+		while (m_is.get(b))
+		{
+			Event ev = Event(b & 0xf);
+			Type ty = Type(b & 0xf0);
+
+			if (ev == ATTRIBUTE)
+			{
+				// TODO: verify that tag is still open
+				m_os << " " << read_data(TYPE_STRING_INTERNED);
+				m_os << "=\"";
+				m_os << read_data(ty);		// TODO: escaping?
+				m_os << '"';
+				continue;
+			}
+			if (mTagOpen)
+			{
+				m_os << ">";
+				mTagOpen = false;
+			}
+			std::string data = read_data(ty);
+			dispatch_event(ev, data);
+		}
+		return !mError;
+	}
+
+	void dispatch_event(Event ev, const std::string& data)
+	{
+		switch (ev)
+		{
+			case START_DOCUMENT:
+			case END_DOCUMENT:
+				// TODO: track that we actually started the document
+				break;
+
+			case START_TAG:
+				m_os << "<" << data;
+				mTagOpen = true;
+				break;
+
+			case END_TAG:
+				m_os << "</" << data << ">";
+				break;
+
+			case TEXT:
+				m_os << data;
+				break;
+
+			case CDSECT:
+				m_os << "<![CDATA[" <<  data << "]]>";
+				break;
+
+			case ENTITY_REF:
+			case IGNORABLE_WHITESPACE:
+			case PROCESSING_INSTRUCTION:
+			case COMMENT:
+			case DOCDECL:
+				m_os << data;
+				// TODO
+				break;
+
+			default:
+				m_os << "#error: Invalid event " << int(ev);
+				mError = true;
+				break;
+		}
+	}
+
+	template <typename T> static T read_bswap(std::istream& is)
+	{
+		char buffer[sizeof(T)];
+		is.read(buffer, sizeof(T));
+		std::reverse(std::begin(buffer), std::end(buffer));
+		return *reinterpret_cast<T*>(buffer);
+	}
+
+	static uint16_t read_uint16(std::istream& is)
+	{
+		return read_bswap<uint16_t>(is);
+	}
+
+	static std::string read_string(std::istream& is)
+	{
+		uint16_t length = read_uint16(is);
+		std::string s;
+		s.resize(length);
+		is.read(&s[0], length);
+		return s;
+	}
+
+
+	std::string read_data(Type ty)
+	{
+		switch (ty)
+		{
+			case TYPE_NULL:
+				return {};
+
+			case TYPE_STRING:
+				return read_string(m_is);
+
+			case TYPE_STRING_INTERNED:
+				{
+					uint16_t id = read_uint16(m_is);
+					if (id == 0xffff)
+					{
+						std::string s = read_string(m_is);
+						mInternedStrings.push_back(s);
+						return s;
+					}
+					if (id >= mInternedStrings.size())
+					{
+						// TODO: handle error
+						mError = true;
+						return "#error: invalid string ID";
+					}
+					return mInternedStrings.at(id);
+				}
+
+			case TYPE_BYTES_HEX:
+				{
+					std::string s = read_string(m_is);
+					std::string hex;
+					static const char* hexdigits = "0123456789abcdef";
+					for (unsigned char c : s)
+					{
+						hex += hexdigits[c >> 4];
+						hex += hexdigits[c & 0xf];
+					}
+					return hex;
+				}
+
+			case TYPE_BYTES_BASE64:
+				{
+					std::string s = read_string(m_is);
+					std::string b64;
+					auto outlen = ((s.length() + 2) / 3) * 4;
+					b64.resize(outlen+1);		// +1 for null terminator
+					auto got = EVP_EncodeBlock(
+						reinterpret_cast<unsigned char *>(&b64[0]),
+						reinterpret_cast<const unsigned char*>(s.c_str()), s.length());
+					if (got != outlen)
+					{
+						mError = true;
+						return "#error: base64 encoding failed";  // TODO
+					}
+					b64.resize(outlen);
+					return b64;
+				}
+
+			case TYPE_INT_HEX:
+				// TODO: output hex instead of dec (what is the exact format?)
+			case TYPE_INT:
+				{
+					int32_t val = read_bswap<int>(m_is);
+					std::stringstream ss;
+					ss << val;
+					return ss.str();
+				}
+
+			case TYPE_LONG_HEX:
+				// TODO: output hex instead of dec (what is the exact format?)
+			case TYPE_LONG:
+				{
+					int64_t val = read_bswap<long>(m_is);
+					std::stringstream ss;
+					ss << val;
+					return ss.str();
+				}
+
+			case TYPE_FLOAT:
+				{
+					float val = read_bswap<float>(m_is);
+					std::stringstream ss;
+					ss << val;
+					return ss.str();
+				}
+
+			case TYPE_DOUBLE:
+				{
+					double val = read_bswap<double>(m_is);
+					std::stringstream ss;
+					ss << val;
+					return ss.str();
+				}
+
+			case TYPE_BOOLEAN_TRUE:
+				return "1";
+
+			case TYPE_BOOLEAN_FALSE:
+				return "0";
+		}
+		// TODO
+		mError = true;
+		return "#error: invalid type";
+	}
+};
+#endif // _ABXFUNCTIONS_HPP
diff --git a/adbbu/Android.mk b/adbbu/Android.mk
new file mode 100755
index 0000000..c05f265
--- /dev/null
+++ b/adbbu/Android.mk
@@ -0,0 +1,47 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libtwadbbu
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS = -fno-strict-aliasing -D_LARGFILE_SOURCE #-D_DEBUG_ADB_BACKUP
+LOCAL_C_INCLUDES += bionic external/zlib
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
+    LOCAL_C_INCLUDES += external/stlport/stlport
+endif
+
+LOCAL_SRC_FILES = \
+        libtwadbbu.cpp \
+        twrpback.cpp
+
+LOCAL_SHARED_LIBRARIES += libz libc libstdc++ libtwrpdigest
+
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
+    LOCAL_SHARED_LIBRARIES += libstlport
+else
+    LOCAL_SHARED_LIBRARIES += libc++
+endif
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+        adbbumain.cpp
+
+LOCAL_SHARED_LIBRARIES += libstdc++ libz libtwadbbu
+
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
+    LOCAL_C_INCLUDES += external/stlport/stlport
+    LOCAL_SHARED_LIBRARIES += libstlport
+else
+    LOCAL_SHARED_LIBRARIES += libc++
+endif
+
+LOCAL_C_INCLUDES += bionic external/zlib
+LOCAL_CFLAGS:= -c -W
+LOCAL_MODULE:= twrpbu
+LOCAL_MODULE_STEM := bu
+LOCAL_MODULE_TAGS:= optional
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+include $(BUILD_EXECUTABLE)
diff --git a/adbbu/adbbumain.cpp b/adbbu/adbbumain.cpp
new file mode 100644
index 0000000..bd96b20
--- /dev/null
+++ b/adbbu/adbbumain.cpp
@@ -0,0 +1,95 @@
+/*
+		Copyright 2013 to 2017 TeamWin
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string>
+#include <sstream>
+#include <algorithm>
+
+#include "twrpback.hpp"
+#include "twadbstream.h"
+
+
+int main(int argc, char **argv) {
+	int index;
+	size_t pos = 0;
+	bool ret = false;
+	size_t maxpos = strlen(TWRPARG) + 2;
+	std::string command;
+	twrpback tw;
+
+	tw.adblogwrite("Starting adb backup and restore\n");
+	command = argv[1];
+	for (index = 2; index < argc; index++) {
+		command = command + " " + argv[index];
+	}
+
+	pos = command.find(TWRP_BACKUP_ARG);
+	if (pos == std::string::npos || pos > (maxpos + strlen(TWRP_BACKUP_ARG) + 1)) {
+		pos = command.find(TWRP_RESTORE_ARG);
+	}
+	if (pos == std::string::npos || pos > maxpos + strlen(TWRP_STREAM_ARG) + 1) {
+		pos = command.find(TWRP_STREAM_ARG);
+	}
+
+	tw.adblogwrite("command: " + command + "\n");
+	command.erase(0, pos);
+	command.erase(std::remove(command.begin(), command.end(), '\''), command.end());
+
+	if (command.substr(0, sizeof(TWRP_BACKUP_ARG) - 1) == TWRP_BACKUP_ARG) {
+		tw.adblogwrite("Starting adb backup\n");
+		if (isdigit(*argv[1]))
+			tw.adbd_fd = atoi(argv[1]);
+		else
+			tw.adbd_fd = 1;
+		ret = tw.backup(command);
+	}
+	else if (command.substr(0, sizeof(TWRP_RESTORE_ARG) - 1) == TWRP_RESTORE_ARG) {
+		tw.adblogwrite("Starting adb restore\n");
+		if (isdigit(*argv[1]))
+			tw.adbd_fd = atoi(argv[1]);
+		else
+			tw.adbd_fd = 0;
+		ret = tw.restore();
+	}
+	else if (command.substr(0, sizeof(TWRP_STREAM_ARG) - 1) == TWRP_STREAM_ARG) {
+		tw.setStreamFileName(argv[3]);
+		tw.threadStream();
+		ret = true;
+	}
+	if (ret)
+		tw.adblogwrite("Adb backup/restore completed\n");
+	else
+		tw.adblogwrite("Adb backup/restore failed\n");
+
+	if (unlink(TW_ADB_BU_CONTROL) < 0) {
+		std::stringstream str;
+		str << strerror(errno);
+		tw.adblogwrite("Unable to remove TW_ADB_BU_CONTROL: " + str.str());
+	}
+	unlink(TW_ADB_TWRP_CONTROL);
+	if (ret)
+		return 0;
+	else
+		return -1;
+}
diff --git a/adbbu/libtwadbbu.cpp b/adbbu/libtwadbbu.cpp
new file mode 100644
index 0000000..39803b0
--- /dev/null
+++ b/adbbu/libtwadbbu.cpp
@@ -0,0 +1,307 @@
+/*
+		Copyright 2013 to 2017 TeamWin
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <zlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <string>
+#include <vector>
+#include <fstream>
+#include <sstream>
+#include <assert.h>
+
+#include "twadbstream.h"
+#include "libtwadbbu.hpp"
+#include "twrpback.hpp"
+
+bool twadbbu::Check_ADB_Backup_File(std::string fname) {
+	struct AdbBackupStreamHeader adbbuhdr;
+	uint32_t crc, adbbuhdrcrc;
+	unsigned char buf[MAX_ADB_READ];
+	int bytes;
+
+	int fd = open(fname.c_str(), O_RDONLY);
+	if (fd < 0) {
+		printf("Unable to open %s for reading: %s.\n", fname.c_str(), strerror(errno));
+		close(fd);
+		return false;
+	}
+	bytes = read(fd, &buf, sizeof(buf));
+	close(fd);
+
+	if (memcpy(&adbbuhdr, buf, sizeof(adbbuhdr)) == NULL) {
+		printf("Unable to memcpy: %s (%s).\n", fname.c_str(), strerror(errno));
+		return false;
+	}
+	adbbuhdrcrc = adbbuhdr.crc;
+	memset(&adbbuhdr.crc, 0, sizeof(adbbuhdr.crc));
+	crc = crc32(0L, Z_NULL, 0);
+	crc = crc32(crc, (const unsigned char*) &adbbuhdr, sizeof(adbbuhdr));
+
+	return (crc == adbbuhdrcrc);
+}
+
+std::vector<std::string> twadbbu::Get_ADB_Backup_Files(std::string fname) {
+	unsigned char buf[MAX_ADB_READ];
+	struct AdbBackupControlType structcmd;
+	std::vector<std::string> adb_partitions;
+
+	int fd = open(fname.c_str(), O_RDONLY);
+	if (fd < 0) {
+		printf("Unable to open %s for reading: %s\n", fname.c_str(), strerror(errno));
+		close(fd);
+		return std::vector<std::string>();
+	}
+
+	while (true) {
+		std::string cmdstr;
+		int readbytes;
+		if ((readbytes = read(fd, &buf, sizeof(buf))) > 0) {
+			memcpy(&structcmd, buf, sizeof(structcmd));
+			assert(structcmd.type == TWENDADB || structcmd.type == TWIMG || structcmd.type == TWFN);
+			cmdstr = structcmd.type;
+			std::string cmdtype = cmdstr.substr(0, sizeof(structcmd.type) - 1);
+			if (cmdtype == TWENDADB) {
+				struct AdbBackupControlType endadb;
+				uint32_t crc, endadbcrc;
+
+				memcpy(&endadb, buf, sizeof(endadb));
+				endadbcrc = endadb.crc;
+				memset(&endadb.crc, 0, sizeof(endadb.crc));
+				crc = crc32(0L, Z_NULL, 0);
+				crc = crc32(crc, (const unsigned char*) &endadb, sizeof(endadb));
+
+				if (crc == endadbcrc) {
+					break;
+				}
+				else {
+					printf("ADB TWENDADB crc header doesn't match\n");
+					close(fd);
+					return std::vector<std::string>();
+				}
+			}
+			else if (cmdtype == TWIMG || cmdtype == TWFN) {
+				struct twfilehdr twfilehdr;
+				uint32_t crc, twfilehdrcrc;
+
+				memcpy(&twfilehdr, buf, sizeof(twfilehdr));
+				twfilehdrcrc = twfilehdr.crc;
+				memset(&twfilehdr.crc, 0, sizeof(twfilehdr.crc));
+
+				crc = crc32(0L, Z_NULL, 0);
+				crc = crc32(crc, (const unsigned char*) &twfilehdr, sizeof(twfilehdr));
+				if (crc == twfilehdrcrc) {
+					std::string adbfile = twfilehdr.name;
+					int pos = adbfile.find_last_of("/") + 1;
+					adbfile = adbfile.substr(pos, adbfile.size());
+					adb_partitions.push_back(adbfile);
+				}
+				else {
+					printf("ADB crc header doesn't match\n");
+					close(fd);
+					return std::vector<std::string>();
+				}
+			}
+		}
+	}
+	close(fd);
+	return adb_partitions;
+}
+
+bool twadbbu::Write_ADB_Stream_Header(uint64_t partition_count) {
+	struct AdbBackupStreamHeader twhdr;
+	int adb_control_bu_fd;
+
+	memset(&twhdr, 0, sizeof(twhdr));
+	adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY | O_NONBLOCK);
+	if (adb_control_bu_fd < 0) {
+		printf("Cannot write to TW_ADB_BU_CONTROL: %s\n", strerror(errno));
+		return false;
+	}
+
+	strncpy(twhdr.start_of_header, TWRP, sizeof(twhdr.start_of_header));
+	strncpy(twhdr.type, TWSTREAMHDR, sizeof(twhdr.type));
+	twhdr.partition_count = partition_count;
+	twhdr.version = ADB_BACKUP_VERSION;
+	memset(twhdr.space, 0, sizeof(twhdr.space));
+	twhdr.crc = crc32(0L, Z_NULL, 0);
+	twhdr.crc = crc32(twhdr.crc, (const unsigned char*) &twhdr, sizeof(twhdr));
+	if (write(adb_control_bu_fd, &twhdr, sizeof(twhdr)) < 0) {
+		printf("Cannot write to adb control channel: %s\n", strerror(errno));
+		close(adb_control_bu_fd);
+		return false;
+	}
+	return true;
+}
+
+bool twadbbu::Write_ADB_Stream_Trailer() {
+	int adb_control_bu_fd;
+	struct AdbBackupControlType endadb;
+
+	memset(&endadb, 0, sizeof(endadb));
+
+	adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY);
+	if (adb_control_bu_fd < 0) {
+		printf("Error opening adb_control_bu_fd: %s\n", strerror(errno));
+		return false;
+	}
+	strncpy(endadb.start_of_header, TWRP, sizeof(endadb.start_of_header));
+	strncpy(endadb.type, TWENDADB, sizeof(endadb.type));
+	endadb.crc = crc32(0L, Z_NULL, 0);
+	endadb.crc = crc32(endadb.crc, (const unsigned char*) &endadb, sizeof(endadb));
+	if (write(adb_control_bu_fd, &endadb, sizeof(endadb)) < 0) {
+		printf("Cannot write to ADB control: %s\n", strerror(errno));
+		close(adb_control_bu_fd);
+		return false;
+	}
+	close(adb_control_bu_fd);
+	return true;
+}
+
+bool twadbbu::Write_TWFN(std::string Backup_FileName, uint64_t file_size, bool use_compression) {
+	int adb_control_bu_fd;
+	adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY | O_NONBLOCK);
+	struct twfilehdr twfilehdr;
+	strncpy(twfilehdr.start_of_header, TWRP, sizeof(twfilehdr.start_of_header));
+	strncpy(twfilehdr.type, TWFN, sizeof(twfilehdr.type));
+	strncpy(twfilehdr.name, Backup_FileName.c_str(), sizeof(twfilehdr.name));
+	twfilehdr.size = (file_size == 0 ? 1024 : file_size);
+	twfilehdr.compressed = use_compression;
+	twfilehdr.crc = crc32(0L, Z_NULL, 0);
+	twfilehdr.crc = crc32(twfilehdr.crc, (const unsigned char*) &twfilehdr, sizeof(twfilehdr));
+
+	printf("Sending TWFN to adb\n");
+	if (write(adb_control_bu_fd, &twfilehdr, sizeof(twfilehdr)) < 1) {
+		printf("Cannot that write to adb_control_bu_fd: %s\n", strerror(errno));
+		close(adb_control_bu_fd);
+		return false;
+	}
+	fsync(adb_control_bu_fd);
+	close(adb_control_bu_fd);
+	return true;
+}
+
+bool twadbbu::Write_TWIMG(std::string Backup_FileName, uint64_t file_size) {
+	int adb_control_bu_fd;
+	struct twfilehdr twimghdr;
+
+	adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY | O_NONBLOCK);
+	strncpy(twimghdr.start_of_header, TWRP, sizeof(twimghdr.start_of_header));
+	strncpy(twimghdr.type, TWIMG, sizeof(twimghdr.type));
+	twimghdr.size = file_size;
+	strncpy(twimghdr.name, Backup_FileName.c_str(), sizeof(twimghdr.name));
+	twimghdr.crc = crc32(0L, Z_NULL, 0);
+	twimghdr.crc = crc32(twimghdr.crc, (const unsigned char*) &twimghdr, sizeof(twimghdr));
+	printf("Sending TWIMG to adb\n");
+	if (write(adb_control_bu_fd, &twimghdr, sizeof(twimghdr)) < 1) {
+		printf("Cannot write to adb control channel: %s\n", strerror(errno));
+		return false;
+	}
+
+	return true;
+}
+
+bool twadbbu::Write_TWEOF() {
+	struct AdbBackupControlType tweof;
+	int adb_control_bu_fd;
+	int errctr = 0;
+
+	printf("opening TW_ADB_BU_CONTROL\n");
+	adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY | O_NONBLOCK);
+	while (adb_control_bu_fd < 0) {
+		printf("failed to open TW_ADB_BU_CONTROL. Retrying: %s\n", strerror(errno));
+		adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY | O_NONBLOCK);
+		usleep(10000);
+		errctr++;
+		if (errctr > ADB_BU_MAX_ERROR) {
+			printf("Cannot write to adb_control_bu_fd: %s.\n", strerror(errno));
+			close(adb_control_bu_fd);
+			return false;
+		}
+	}
+	memset(&tweof, 0, sizeof(tweof));
+	strncpy(tweof.start_of_header, TWRP, sizeof(tweof.start_of_header));
+	strncpy(tweof.type, TWEOF, sizeof(tweof.type));
+	tweof.crc = crc32(0L, Z_NULL, 0);
+	tweof.crc = crc32(tweof.crc, (const unsigned char*) &tweof, sizeof(tweof));
+	printf("Sending TWEOF to adb backup\n");
+	if (write(adb_control_bu_fd, &tweof, sizeof(tweof)) < 0) {
+		printf("Cannot write to adb_control_bu_fd: %s.\n", strerror(errno));
+		close(adb_control_bu_fd);
+		return false;
+	}
+	close(adb_control_bu_fd);
+	return true;
+}
+
+bool twadbbu::Write_TWERROR() {
+	struct AdbBackupControlType twerror;
+	int adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY | O_NONBLOCK);
+
+	strncpy(twerror.start_of_header, TWRP, sizeof(twerror.start_of_header));
+	strncpy(twerror.type, TWERROR, sizeof(twerror.type));
+	memset(twerror.space, 0, sizeof(twerror.space));
+	twerror.crc = crc32(0L, Z_NULL, 0);
+	twerror.crc = crc32(twerror.crc, (const unsigned char*) &twerror, sizeof(twerror));
+	if (write(adb_control_bu_fd, &twerror, sizeof(twerror)) < 0) {
+		printf("Cannot write to adb control channel: %s\n", strerror(errno));
+		return false;
+	}
+	close(adb_control_bu_fd);
+	return true;
+}
+
+bool twadbbu::Write_TWENDADB() {
+	struct AdbBackupControlType endadb;
+	int adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY | O_NONBLOCK);
+
+	memset(&endadb, 0, sizeof(endadb));
+	strncpy(endadb.start_of_header, TWRP, sizeof(endadb.start_of_header));
+	strncpy(endadb.type, TWENDADB, sizeof(endadb.type));
+	endadb.crc = crc32(0L, Z_NULL, 0);
+	endadb.crc = crc32(endadb.crc, (const unsigned char*) &endadb, sizeof(endadb));
+
+	printf("Sending TWENDADB to ADB Backup\n");
+	if (write(adb_control_bu_fd, &endadb, sizeof(endadb)) < 1) {
+		printf("Cannot write to ADB_CONTROL_BU_FD: %s\n", strerror(errno));
+		return false;
+	}
+
+	close(adb_control_bu_fd);
+	return true;
+}
+
+bool twadbbu::Write_TWDATA(FILE* adbd_fp) {
+	struct AdbBackupControlType data_block;
+	memset(&data_block, 0, sizeof(data_block));
+	strncpy(data_block.start_of_header, TWRP, sizeof(data_block.start_of_header));
+	strncpy(data_block.type, TWDATA, sizeof(data_block.type));
+	data_block.crc = crc32(0L, Z_NULL, 0);
+	data_block.crc = crc32(data_block.crc, (const unsigned char*) &data_block, sizeof(data_block));
+	if (fwrite(&data_block, 1, sizeof(data_block), adbd_fp) != sizeof(data_block))  {
+		return false;
+	}
+	return true;
+}
diff --git a/adbbu/libtwadbbu.hpp b/adbbu/libtwadbbu.hpp
new file mode 100644
index 0000000..9244bb5
--- /dev/null
+++ b/adbbu/libtwadbbu.hpp
@@ -0,0 +1,51 @@
+/*
+		Copyright 2013 to 2017 TeamWin
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef _LIBTWADBBU_HPP
+#define _LIBTWADBBU_HPP
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <string>
+#include <vector>
+#include <fstream>
+#include <sstream>
+
+#include "twadbstream.h"
+
+class twadbbu {
+public:
+	static bool Check_ADB_Backup_File(std::string fname);                                          //Check if file is ADB Backup file
+	static std::vector<std::string> Get_ADB_Backup_Files(std::string fname);                       //List ADB Files in String Vector
+	static bool Write_ADB_Stream_Header(uint64_t partition_count);                                 //Write ADB Stream Header to stream
+	static bool Write_ADB_Stream_Trailer();                                                        //Write ADB Stream Trailer to stream
+	static bool Write_TWFN(std::string Backup_FileName, uint64_t file_size, bool use_compression); //Write a tar image to stream
+	static bool Write_TWIMG(std::string Backup_FileName, uint64_t file_size);                      //Write a partition image to stream
+	static bool Write_TWEOF();                                                                     //Write ADB End-Of-File marker to stream
+	static bool Write_TWERROR();                                                                   //Write error message occurred to stream
+	static bool Write_TWENDADB();                                                                  //Write ADB End-Of-Stream command to stream
+	static bool Write_TWDATA(FILE* adbd_fp);                                                       //Write TWDATA separator
+};
+
+#endif //__LIBTWADBBU_HPP
diff --git a/adbbu/twadbstream.h b/adbbu/twadbstream.h
new file mode 100644
index 0000000..bef463c
--- /dev/null
+++ b/adbbu/twadbstream.h
@@ -0,0 +1,112 @@
+/*
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __TWADBSTREAM_H
+#define __TWADBSTREAM_H
+
+#define TWRPARG "--twrp"
+#define TWRP_BACKUP_ARG "backup"
+#define TWRP_RESTORE_ARG "restore"
+#define TWRP_STREAM_ARG "stream"
+#define TW_ADB_BACKUP "/tmp/twadbbackup"		//FIFO for adb backup
+#define TW_ADB_RESTORE "/tmp/twadbrestore"		//FIFO for adb restore
+#define TW_ADB_BU_CONTROL "/tmp/twadbbucontrol"		//FIFO for sending control from TWRP to ADB Backup
+#define TW_ADB_TWRP_CONTROL "/tmp/twadbtwrpcontrol"	//FIFO for sending control from ADB Backup to TWRP
+#define TWRP "TWRP"					//Magic Value
+#define ADB_BU_MAX_ERROR 20				//Max amount of errors for while loops
+#define ADB_BACKUP_OP "adbbackup"
+#define ADB_RESTORE_OP "adbrestore"
+
+//ADB Backup Control Commands
+#define TWSTREAMHDR "twstreamheader"			//TWRP Parititon Count Control
+#define TWFN "twfilename"				//TWRP Filename Control
+#define TWIMG "twimage"					//TWRP Image name Control
+#define TWEOF "tweof"					//End of File for Image/File
+#define MD5TRAILER "md5trailer"				//Image/File MD5 Trailer
+#define TWDATA "twdatablock"				// twrp adb backup data block header
+#define TWMD5 "twverifymd5"				//This command is compared to the md5trailer by ORS to verify transfer
+#define TWENDADB "twendadb"				//End Protocol
+#define TWERROR "twerror"				//Send error
+#define ADB_BACKUP_VERSION 3				//Backup Version
+#define DATA_MAX_CHUNK_SIZE 1048576			//Maximum size between each data header
+#define MAX_ADB_READ 512				//align with default tar size for amount to read fom adb stream
+
+/*
+structs for adb backup need to align to 512 bytes for reading 512
+bytes at a time
+Each struct contains a crc field so that when we are checking for commands
+and the crc doesn't match we still assume it's data matching the command
+struct but not really a command
+*/
+
+/*  stream format:
+  | TW ADB Backup Header   |
+  | TW File Stream Header  |
+  | File Data              |
+  | File/Image MD5 Trailer |
+  | TW File Stream Header  |
+  | File Data              |
+  | File/Image MD5 Trailer |
+  | etc...                 |
+*/
+
+//determine whether struct is 512 bytes, if not fail compilation
+#define ADBSTRUCT_STATIC_ASSERT(structure) typedef char adb_assertion[( !!(structure) )*2-1 ]
+
+//generic cmd structure to align fields for sending commands to and from adb backup
+struct AdbBackupControlType {
+	char start_of_header[8];			//stores the magic value #define TWRP
+	char type[16];					//stores the type of command, TWENDADB, TWCNT, TWEOF, TWMD5, TWDATA and TWERROR
+	uint32_t crc;					//stores the zlib 32 bit crc of the AdbBackupControlType struct to allow for making sure we are processing metadata
+	char space[484];				//stores space to align the struct to 512 bytes
+
+	//return a C++ string while not reading outside the type char array
+	std::string get_type() {
+		return std::string(type, strnlen(type, sizeof(type)-1));
+	}
+};
+
+//general info for file metadata stored in adb backup header
+struct twfilehdr {
+	char start_of_header[8];			//stores the magic value #define TWRP
+	char type[16];					//stores the type of file header, TWFN or TWIMG
+	uint64_t size;					//stores the size of the file contained after this header in the backup file
+	uint64_t compressed;				//stores whether the file is compressed or not. 1 == compressed and 0 == uncompressed
+	uint32_t crc;					//stores the zlib 32 bit crc of the twfilehdr struct to allow for making sure we are processing metadata
+	char name[468];					//stores the filename of the file
+};
+
+//md5 for files stored as a trailer to files in the adb backup file to check
+//that they are restored correctly
+struct AdbBackupFileTrailer {
+	char start_of_trailer[8];			//stores the magic value #define TWRP
+	char type[16];					//stores the AdbBackupFileTrailer type MD5TRAILER
+	uint32_t crc;					//stores the zlib 32 bit crc of the AdbBackupFileTrailer struct to allow for making sure we are processing metadata
+	uint32_t ident;					//stores crc to determine if header is encapsulated in stream as data
+	char md5[40];					//stores the md5 computation of the file
+	char space[440];				//stores space to align the struct to 512 bytes
+};
+
+//info for version and number of partitions backed up
+struct AdbBackupStreamHeader {
+	char start_of_header[8];			//stores the magic value #define TWRP
+	char type[16];					//stores the AdbBackupStreamHeader value TWCNT
+	uint64_t partition_count;			//stores the number of partitions to restore in the stream
+	uint64_t version;				//stores the version of adb backup. increment ADB_BACKUP_VERSION each time the metadata is updated
+	uint32_t crc;					//stores the zlib 32 bit crc of the AdbBackupStreamHeader struct to allow for making sure we are processing metadata
+	char space[468];				//stores space to align the struct to 512 bytes
+};
+
+#endif //__TWADBSTREAM_H
diff --git a/adbbu/twrpback.cpp b/adbbu/twrpback.cpp
new file mode 100644
index 0000000..b3de76d
--- /dev/null
+++ b/adbbu/twrpback.cpp
@@ -0,0 +1,935 @@
+/*
+		Copyright 2013 to 2017 TeamWin
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <zlib.h>
+#include <ctype.h>
+#include <semaphore.h>
+#include <string>
+#include <sstream>
+#include <fstream>
+#include <algorithm>
+#include <utils/threads.h>
+#include <pthread.h>
+
+#include "twadbstream.h"
+#include "twrpback.hpp"
+#include "libtwadbbu.hpp"
+#include "../twrpDigest/twrpDigest.hpp"
+#include "../twrpDigest/twrpMD5.hpp"
+#include "../twrpAdbBuFifo.hpp"
+
+twrpback::twrpback(void) {
+	adbd_fp = NULL;
+	read_fd = 0;
+	write_fd = 0;
+	adb_control_twrp_fd = 0;
+	adb_control_bu_fd = 0;
+	adb_read_fd = 0;
+	adb_write_fd = 0;
+	ors_fd = 0;
+	debug_adb_fd = 0;
+	firstPart = true;
+	createFifos();
+	adbloginit();
+}
+
+twrpback::~twrpback(void) {
+	adblogfile.close();
+	closeFifos();
+}
+
+void twrpback::printErrMsg(std::string msg, int errNum) {
+	std::stringstream str;
+	str << strerror(errNum);
+	adblogwrite(msg +  " " + str.str() + "\n");
+}
+
+void twrpback::createFifos(void) {
+        if (mkfifo(TW_ADB_BU_CONTROL, 0666) < 0) {
+                std::string msg = "Unable to create TW_ADB_BU_CONTROL fifo: ";
+		printErrMsg(msg, errno);
+        }
+        if (mkfifo(TW_ADB_TWRP_CONTROL, 0666) < 0) {
+                std::string msg = "Unable to create TW_ADB_TWRP_CONTROL fifo: ";
+		printErrMsg(msg, errno);
+                unlink(TW_ADB_BU_CONTROL);
+        }
+}
+
+void twrpback::closeFifos(void) {
+        if (unlink(TW_ADB_BU_CONTROL) < 0) {
+                std::string msg = "Unable to remove TW_ADB_BU_CONTROL: ";
+		printErrMsg(msg, errno);
+        }
+        if (unlink(TW_ADB_TWRP_CONTROL) < 0) {
+                std::string msg = "Unable to remove TW_ADB_TWRP_CONTROL: ";
+		printErrMsg(msg, errno);
+	}
+}
+
+void twrpback::adbloginit(void) {
+	adblogfile.open("/tmp/adb.log", std::fstream::app);
+}
+
+void twrpback::adblogwrite(std::string writemsg) {
+	adblogfile << writemsg << std::flush;
+}
+
+void twrpback::close_backup_fds() {
+	if (ors_fd > 0)
+		close(ors_fd);
+	if (write_fd > 0)
+		close(write_fd);
+	if (adb_read_fd > 0)
+		close(adb_read_fd);
+	if (adb_control_bu_fd > 0)
+		close(adb_control_bu_fd);
+	#ifdef _DEBUG_ADB_BACKUP
+		if (debug_adb_fd > 0)
+			close(debug_adb_fd);
+	#endif
+	if (adbd_fp != NULL)
+		fclose(adbd_fp);
+	if (access(TW_ADB_BACKUP, F_OK) == 0)
+		unlink(TW_ADB_BACKUP);
+}
+
+void twrpback::close_restore_fds() {
+	if (ors_fd > 0)
+		close(ors_fd);
+	if (write_fd > 0)
+		close(write_fd);
+	if (adb_control_bu_fd > 0)
+		close(adb_control_bu_fd);
+	if (adb_control_twrp_fd > 0)
+		close(adb_control_twrp_fd);
+	if (adbd_fp != NULL)
+		fclose(adbd_fp);
+	if (access(TW_ADB_RESTORE, F_OK) == 0)
+		unlink(TW_ADB_RESTORE);
+	#ifdef _DEBUG_ADB_BACKUP
+	if (debug_adb_fd > 0)
+		close(debug_adb_fd);
+	#endif
+}
+
+bool twrpback::backup(std::string command) {
+	twrpMD5 digest;
+	int bytes = 0, errctr = 0;
+	char adbReadStream[MAX_ADB_READ];
+	uint64_t totalbytes = 0, dataChunkBytes = 0, fileBytes = 0;
+	uint64_t md5fnsize = 0;
+	struct AdbBackupControlType endadb;
+
+	//ADBSTRUCT_STATIC_ASSERT(sizeof(endadb) == MAX_ADB_READ);
+
+	bool writedata = true;
+	bool compressed = false;
+	bool firstDataPacket = true;
+
+	adbd_fp = fdopen(adbd_fd, "w");
+	if (adbd_fp == NULL) {
+		adblogwrite("Unable to open adb_fp\n");
+		return false;
+	}
+
+	if (mkfifo(TW_ADB_BACKUP, 0666) < 0) {
+		adblogwrite("Unable to create TW_ADB_BACKUP fifo\n");
+		return false;
+	}
+
+	adblogwrite("opening TW_ADB_FIFO\n");
+
+	write_fd = open(TW_ADB_FIFO, O_WRONLY);
+	while (write_fd < 0) {
+		write_fd = open(TW_ADB_FIFO, O_WRONLY);
+		usleep(10000);
+		errctr++;
+		if (errctr > ADB_BU_MAX_ERROR) {
+			std::string msg = "Unable to open TW_ADB_FIFO";
+			printErrMsg(msg, errno);
+			close_backup_fds();
+			return false;
+		}
+	}
+
+	memset(operation, 0, sizeof(operation));
+	if (snprintf(operation, sizeof(operation), "adbbackup %s", command.c_str()) >= (int)sizeof(operation)) {
+		adblogwrite("Operation too big to write to ORS_INPUT_FILE\n");
+		close_backup_fds();
+		return false;
+	}
+	if (write(write_fd, operation, sizeof(operation)) != sizeof(operation)) {
+		adblogwrite("Unable to write to ORS_INPUT_FILE\n");
+		close_backup_fds();
+		return false;
+	}
+
+	memset(&adbReadStream, 0, sizeof(adbReadStream));
+	memset(&cmd, 0, sizeof(cmd));
+
+	adblogwrite("opening TW_ADB_BU_CONTROL\n");
+	adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_RDONLY | O_NONBLOCK);
+	if (adb_control_bu_fd < 0) {
+		adblogwrite("Unable to open TW_ADB_BU_CONTROL for reading.\n");
+		close_backup_fds();
+		return false;
+	}
+
+	adblogwrite("opening TW_ADB_BACKUP\n");
+	adb_read_fd = open(TW_ADB_BACKUP, O_RDONLY | O_NONBLOCK);
+	if (adb_read_fd < 0) {
+		adblogwrite("Unable to open TW_ADB_BACKUP for reading.\n");
+		close_backup_fds();
+		return false;
+	}
+
+	//loop until TWENDADB sent
+	while (true) {
+		if (read(adb_control_bu_fd, &cmd, sizeof(cmd)) > 0) {
+			struct AdbBackupControlType structcmd;
+
+			memcpy(&structcmd, cmd, sizeof(cmd));
+			std::string cmdtype = structcmd.get_type();
+
+			//we received an error, exit and unlink
+			if (cmdtype == TWERROR) {
+				writedata = false;
+				adblogwrite("Error received. Quitting...\n");
+				close_backup_fds();
+				return false;
+			}
+			//we received the end of adb backup stream so we should break the loop
+			else if (cmdtype == TWENDADB) {
+				writedata = false;
+				adblogwrite("Recieved TWENDADB\n");
+				memcpy(&endadb, cmd, sizeof(cmd));
+				std::stringstream str;
+				str << totalbytes;
+				adblogwrite(str.str() + " total bytes written\n");
+				break;
+			}
+			//we recieved the TWSTREAMHDR structure metadata to write to adb
+			else if (cmdtype == TWSTREAMHDR) {
+				writedata = false;
+				adblogwrite("writing TWSTREAMHDR\n");
+				if (fwrite(cmd, 1, sizeof(cmd), adbd_fp) != sizeof(cmd)) {
+					std::string msg = "Error writing TWSTREAMHDR to adbd";
+					printErrMsg(msg, errno);
+					close_backup_fds();
+					return false;
+				}
+				fflush(adbd_fp);
+			}
+			//we will be writing an image from TWRP
+			else if (cmdtype == TWIMG) {
+				struct twfilehdr twimghdr;
+
+				adblogwrite("writing TWIMG\n");
+				digest.init();
+				memset(&twimghdr, 0, sizeof(twimghdr));
+				memcpy(&twimghdr, cmd, sizeof(cmd));
+				md5fnsize = twimghdr.size;
+				compressed = false;
+
+				#ifdef _DEBUG_ADB_BACKUP
+				std::string debug_fname = "/data/media/";
+				debug_fname.append(basename(twimghdr.name));
+				debug_fname.append("-backup.img");
+				debug_adb_fd = open(debug_fname.c_str(), O_WRONLY | O_CREAT, 0666);
+				adblogwrite("Opening adb debug tar\n");
+				#endif
+
+				if (fwrite(cmd, 1, sizeof(cmd), adbd_fp) != sizeof(cmd)) {
+					adblogwrite("Error writing TWIMG to adbd\n");
+					close_backup_fds();
+					return false;
+				}
+				fflush(adbd_fp);
+				writedata = true;
+			}
+			//we will be writing a tar from TWRP
+			else if (cmdtype == TWFN) {
+				struct twfilehdr twfilehdr;
+
+				adblogwrite("writing TWFN\n");
+				digest.init();
+
+				//ADBSTRUCT_STATIC_ASSERT(sizeof(twfilehdr) == MAX_ADB_READ);
+
+				memset(&twfilehdr, 0, sizeof(twfilehdr));
+				memcpy(&twfilehdr, cmd, sizeof(cmd));
+				md5fnsize = twfilehdr.size;
+
+				compressed = twfilehdr.compressed == 1 ? true: false;
+
+				#ifdef _DEBUG_ADB_BACKUP
+				std::string debug_fname = "/data/media/";
+				debug_fname.append(basename(twfilehdr.name));
+				debug_fname.append("-backup.tar");
+				debug_adb_fd = open(debug_fname.c_str(), O_WRONLY | O_CREAT, 0666);
+				adblogwrite("Opening adb debug tar\n");
+				#endif
+
+				if (fwrite(cmd, 1, sizeof(cmd), adbd_fp) != sizeof(cmd)) {
+					adblogwrite("Error writing TWFN to adbd\n");
+					close_backup_fds();
+					return false;
+				}
+				fflush(adbd_fp);
+				writedata = true;
+			}
+			/*
+			We received the command that we are done with the file stream.
+			We will flush the remaining data stream.
+			Update md5 and write final results to adb stream.
+			If we need padding because the total bytes are not a multiple
+			of 512, we pad the end with 0s to we reach 512.
+			We also write the final md5 to the adb stream.
+			*/
+			else if (cmdtype == TWEOF) {
+				adblogwrite("received TWEOF\n");
+				while ((bytes = read(adb_read_fd, &adbReadStream, sizeof(adbReadStream)) != 0)) {
+					totalbytes += bytes;
+					fileBytes += bytes;
+					dataChunkBytes += bytes;
+
+					char *writeAdbReadStream = new char [bytes];
+					memcpy(writeAdbReadStream, adbReadStream, bytes);
+
+					digest.update((unsigned char *) writeAdbReadStream, bytes);
+					if (fwrite(writeAdbReadStream, 1, bytes, adbd_fp) < 0) {
+						std::string msg = "Cannot write to adbd stream: ";
+						printErrMsg(msg, errno);
+					}
+					#if defined(_DEBUG_ADB_BACKUP)
+					if (write(debug_adb_fd, writeAdbReadStream, bytes) < 1) {
+						std::string msg = "Cannot write to ADB_CONTROL_READ_FD: ";
+						printErrMsg(msg, errno);
+						close_restore_fds();
+						return false;
+					}
+					#endif
+					fflush(adbd_fp);
+					delete [] writeAdbReadStream;
+					memset(adbReadStream, 0, sizeof(adbReadStream));
+				}
+
+				if (fileBytes % DATA_MAX_CHUNK_SIZE != 0) {
+					int64_t count = fileBytes / DATA_MAX_CHUNK_SIZE + 1;
+					uint64_t ceilingBytes = count * DATA_MAX_CHUNK_SIZE;
+					char padding[ceilingBytes - fileBytes];
+					int paddingBytes = sizeof(padding);
+					memset(padding, 0, paddingBytes);
+					std::stringstream paddingStr;
+					paddingStr << paddingBytes;
+					adblogwrite("writing padding to stream: " + paddingStr.str() + " bytes\n");
+					if (fwrite(padding, 1, paddingBytes, adbd_fp) != sizeof(padding)) {
+						adblogwrite("Error writing padding to adbd\n");
+						close_backup_fds();
+						return false;
+					}
+					#if defined(_DEBUG_ADB_BACKUP)
+					if (write(debug_adb_fd, padding, paddingBytes) < 1) {
+						std::string msg = "Cannot write to ADB_CONTROL_READ_FD: ";
+						printErrMsg(msg, errno);
+						close_restore_fds();
+						return false;
+					}
+					#endif
+					totalbytes += paddingBytes;
+					digest.update((unsigned char *) padding, paddingBytes);
+					fflush(adbd_fp);
+				}
+
+				AdbBackupFileTrailer md5trailer;
+
+				memset(&md5trailer, 0, sizeof(md5trailer));
+
+				std::string md5string = digest.return_digest_string();
+
+				strncpy(md5trailer.start_of_trailer, TWRP, sizeof(md5trailer.start_of_trailer));
+				strncpy(md5trailer.type, MD5TRAILER, sizeof(md5trailer.type));
+				strncpy(md5trailer.md5, md5string.c_str(), sizeof(md5trailer.md5));
+
+				md5trailer.crc = crc32(0L, Z_NULL, 0);
+				md5trailer.crc = crc32(md5trailer.crc, (const unsigned char*) &md5trailer, sizeof(md5trailer));
+
+				md5trailer.ident = crc32(0L, Z_NULL, 0);
+				md5trailer.ident = crc32(md5trailer.ident, (const unsigned char*) &md5trailer, sizeof(md5trailer));
+				md5trailer.ident = crc32(md5trailer.ident, (const unsigned char*) &md5fnsize, sizeof(md5fnsize));
+
+				if (fwrite(&md5trailer, 1, sizeof(md5trailer), adbd_fp) != sizeof(md5trailer))  {
+					adblogwrite("Error writing md5trailer to adbd\n");
+					close_backup_fds();
+					return false;
+				}
+				fflush(adbd_fp);
+				writedata = false;
+				firstDataPacket = true;
+				fileBytes = 0;
+			}
+			memset(&cmd, 0, sizeof(cmd));
+			dataChunkBytes = 0;
+		}
+		//If we are to write data because of a new file stream, lets write all the data.
+		//This will allow us to not write data after a command structure has been written
+		//to the adb stream.
+		//If the stream is compressed, we need to always write the data.
+		if (writedata || compressed) {
+			while ((bytes = read(adb_read_fd, &adbReadStream, sizeof(adbReadStream))) > 0) {
+				if (firstDataPacket) {
+					if (!twadbbu::Write_TWDATA(adbd_fp)) {
+						close_backup_fds();
+						return false;
+					}
+					fileBytes += MAX_ADB_READ;
+					fflush(adbd_fp);
+					firstDataPacket = false;
+					dataChunkBytes += sizeof(adbReadStream);
+				}
+				char *writeAdbReadStream = new char [bytes];
+				memcpy(writeAdbReadStream, adbReadStream, bytes);
+
+				digest.update((unsigned char *) writeAdbReadStream, bytes);
+
+				totalbytes += bytes;
+				fileBytes += bytes;
+				dataChunkBytes += bytes;
+
+				if (fwrite(writeAdbReadStream, 1, bytes, adbd_fp) != (unsigned long long)bytes) {
+					adblogwrite("Error writing backup data to adbd\n");
+					close_backup_fds();
+					return false;
+				}
+				#ifdef _DEBUG_ADB_BACKUP
+				if (write(debug_adb_fd, writeAdbReadStream, bytes) < 1) {
+					std::string msg = "Cannot write to ADB_CONTROL_READ_FD: ";
+					printErrMsg(msg, errno);
+					close_restore_fds();
+					return false;
+				}
+				#endif
+				fflush(adbd_fp);
+				delete [] writeAdbReadStream;
+
+				memset(&adbReadStream, 0, sizeof(adbReadStream));
+
+				if (dataChunkBytes == DATA_MAX_CHUNK_SIZE) {
+					dataChunkBytes = 0;
+					firstDataPacket = true;
+				}
+				else if (dataChunkBytes > (DATA_MAX_CHUNK_SIZE - sizeof(adbReadStream))) {
+					int bytesLeft = DATA_MAX_CHUNK_SIZE - dataChunkBytes;
+					char extraData[bytesLeft];
+
+					memset(&extraData, 0, bytesLeft);
+					while ((bytes = read(adb_read_fd, &extraData, bytesLeft)) != 0) {
+						if (bytes > 0) {
+							totalbytes += bytes;
+							fileBytes += bytes;
+							dataChunkBytes += bytes;
+
+							bytesLeft -= bytes;
+							char *writeAdbReadStream = new char [bytes];
+							memcpy(writeAdbReadStream, extraData, bytes);
+
+							digest.update((unsigned char *) writeAdbReadStream, bytes);
+							if (fwrite(writeAdbReadStream, 1, bytes, adbd_fp) < 0) {
+								std::string msg = "Cannot write to adbd stream: ";
+								printErrMsg(msg, errno);
+								close_restore_fds();
+								return false;
+							}
+							#ifdef _DEBUG_ADB_BACKUP
+							if (write(debug_adb_fd, writeAdbReadStream, bytes) < 1) {
+								std::string msg = "Cannot write to ADB_CONTROL_READ_FD: ";
+								printErrMsg(msg, errno);
+								close_restore_fds();
+								return false;
+							}
+							#endif
+							fflush(adbd_fp);
+							delete [] writeAdbReadStream;
+						}
+						memset(&extraData, 0, bytesLeft);
+						if (bytesLeft == 0) {
+							break;
+						}
+					}
+
+					fflush(adbd_fp);
+					dataChunkBytes = 0;
+					firstDataPacket = true;
+				}
+			}
+		}
+	}
+
+	//Write the final end adb structure to the adb stream
+	if (fwrite(&endadb, 1, sizeof(endadb), adbd_fp) != sizeof(endadb)) {
+		adblogwrite("Error writing endadb to adbd\n");
+		close_backup_fds();
+		return false;
+	}
+	fflush(adbd_fp);
+	close_backup_fds();
+	return true;
+}
+
+bool twrpback::restore(void) {
+	twrpMD5 digest;
+	char cmd[MAX_ADB_READ];
+	char readAdbStream[MAX_ADB_READ];
+	struct AdbBackupControlType structcmd;
+	int errctr = 0;
+	uint64_t totalbytes = 0, dataChunkBytes = 0;
+	uint64_t md5fnsize = 0, fileBytes = 0;
+	bool read_from_adb;
+	bool md5sumdata;
+	bool compressed, tweofrcvd, extraData;
+
+	read_from_adb = true;
+
+	signal(SIGPIPE, SIG_IGN);
+	signal(SIGHUP, SIG_IGN);
+
+	adbd_fp = fdopen(adbd_fd, "r");
+	if (adbd_fp == NULL) {
+		adblogwrite("Unable to open adb_fp\n");
+		close_restore_fds();
+		return false;
+	}
+
+	if(mkfifo(TW_ADB_RESTORE, 0666)) {
+		adblogwrite("Unable to create TW_ADB_RESTORE fifo\n");
+		close_restore_fds();
+		return false;
+	}
+
+	adblogwrite("opening TW_ADB_FIFO\n");
+	write_fd = open(TW_ADB_FIFO, O_WRONLY);
+
+	while (write_fd < 0) {
+		write_fd = open(TW_ADB_FIFO, O_WRONLY);
+		errctr++;
+		if (errctr > ADB_BU_MAX_ERROR) {
+			std::string msg = "Unable to open TW_ADB_FIFO.";
+			printErrMsg(msg, errno);
+			close_restore_fds();
+			return false;
+		}
+	}
+
+	memset(operation, 0, sizeof(operation));
+	sprintf(operation, "adbrestore");
+	if (write(write_fd, operation, sizeof(operation)) != sizeof(operation)) {
+		adblogwrite("Unable to write to TW_ADB_FIFO\n");
+		close_restore_fds();
+		return false;
+	}
+
+	memset(&readAdbStream, 0, sizeof(readAdbStream));
+	memset(&cmd, 0, sizeof(cmd));
+
+	adblogwrite("opening TW_ADB_BU_CONTROL\n");
+	adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_RDONLY | O_NONBLOCK);
+	if (adb_control_bu_fd < 0) {
+		std::string msg = "Unable to open TW_ADB_BU_CONTROL for writing.";
+		printErrMsg(msg, errno);
+		close_restore_fds();
+		return false;
+	}
+
+	adblogwrite("opening TW_ADB_TWRP_CONTROL\n");
+	adb_control_twrp_fd = open(TW_ADB_TWRP_CONTROL, O_WRONLY | O_NONBLOCK);
+	if (adb_control_twrp_fd < 0) {
+		std::string msg = "Unable to open TW_ADB_TWRP_CONTROL for writing. Retrying...";
+		printErrMsg(msg, errno);
+		while (adb_control_twrp_fd < 0) {
+			adb_control_twrp_fd = open(TW_ADB_TWRP_CONTROL, O_WRONLY | O_NONBLOCK);
+			usleep(10000);
+			errctr++;
+			if (errctr > ADB_BU_MAX_ERROR) {
+				adblogwrite("Unable to open TW_ADB_TWRP_CONTROL\n");
+				close_backup_fds();
+				return false;
+			}
+		}
+	}
+
+	//Loop until we receive TWENDADB from TWRP
+	while (true) {
+		memset(&cmd, 0, sizeof(cmd));
+		if (read(adb_control_bu_fd, &cmd, sizeof(cmd)) > 0) {
+			struct AdbBackupControlType structcmd;
+			memcpy(&structcmd, cmd, sizeof(cmd));
+			std::string cmdtype = structcmd.get_type();
+
+			//If we receive TWEOF from TWRP close adb data fifo
+			if (cmdtype == TWEOF) {
+				adblogwrite("Received TWEOF\n");
+				read_from_adb = true;
+				tweofrcvd = true;
+				close(adb_write_fd);
+			}
+			//Break when TWRP sends TWENDADB
+			else if (cmdtype == TWENDADB) {
+				adblogwrite("Received TWENDADB\n");
+				break;
+			}
+			//we received an error, exit and unlink
+			else if (cmdtype == TWERROR) {
+				adblogwrite("Error received. Quitting...\n");
+				close_restore_fds();
+				return false;
+			}
+		}
+		//If we should read from the adb stream, write commands and data to TWRP
+		if (read_from_adb) {
+			int readbytes;
+			if ((readbytes = fread(readAdbStream, 1, sizeof(readAdbStream), adbd_fp)) == sizeof(readAdbStream)) {
+				memcpy(&structcmd, readAdbStream, sizeof(readAdbStream));
+				std::string cmdtype = structcmd.get_type();
+
+				//Tell TWRP we have read the entire adb stream
+				if (cmdtype == TWENDADB) {
+					struct AdbBackupControlType endadb;
+					uint32_t crc, endadbcrc;
+
+					md5sumdata = false;
+					memset(&endadb, 0, sizeof(endadb));
+					memcpy(&endadb, readAdbStream, sizeof(readAdbStream));
+					endadbcrc = endadb.crc;
+					memset(&endadb.crc, 0, sizeof(endadb.crc));
+					crc = crc32(0L, Z_NULL, 0);
+					crc = crc32(crc, (const unsigned char*) &endadb, sizeof(endadb));
+
+					if (crc == endadbcrc) {
+						adblogwrite("sending TWENDADB\n");
+						if (write(adb_control_twrp_fd, &endadb, sizeof(endadb)) < 1) {
+							std::string msg = "Cannot write to ADB_CONTROL_READ_FD: ";
+							printErrMsg(msg, errno);
+							close_restore_fds();
+							return false;
+						}
+						read_from_adb = false;
+					}
+					else {
+						adblogwrite("ADB TWENDADB crc header doesn't match\n");
+						close_restore_fds();
+						return false;
+					}
+				}
+				//Send TWRP partition metadata
+				else if (cmdtype == TWSTREAMHDR) {
+					struct AdbBackupStreamHeader cnthdr;
+					uint32_t crc, cnthdrcrc;
+
+					//ADBSTRUCT_STATIC_ASSERT(sizeof(cnthdr) == MAX_ADB_READ);
+
+					md5sumdata = false;
+					memset(&cnthdr, 0, sizeof(cnthdr));
+					memcpy(&cnthdr, readAdbStream, sizeof(readAdbStream));
+					cnthdrcrc = cnthdr.crc;
+					memset(&cnthdr.crc, 0, sizeof(cnthdr.crc));
+					crc = crc32(0L, Z_NULL, 0);
+					crc = crc32(crc, (const unsigned char*) &cnthdr, sizeof(cnthdr));
+
+					if (crc == cnthdrcrc) {
+						adblogwrite("Restoring TWSTREAMHDR\n");
+						if (write(adb_control_twrp_fd, readAdbStream, sizeof(readAdbStream)) < 0) {
+							std::string msg = "Cannot write to adb_control_twrp_fd: ";
+							printErrMsg(msg, errno);
+							close_restore_fds();
+							return false;
+						}
+					}
+					else {
+						adblogwrite("ADB TWSTREAMHDR crc header doesn't match\n");
+						close_restore_fds();
+						return false;
+					}
+				}
+				//Tell TWRP we are sending a partition image
+				else if (cmdtype == TWIMG) {
+					struct twfilehdr twimghdr;
+					uint32_t crc, twimghdrcrc;
+					md5sumdata = false;
+					fileBytes = 0;
+					read_from_adb = true;
+					dataChunkBytes = 0;
+					extraData = false;
+
+					digest.init();
+					adblogwrite("Restoring TWIMG\n");
+					memset(&twimghdr, 0, sizeof(twimghdr));
+					memcpy(&twimghdr, readAdbStream, sizeof(readAdbStream));
+					md5fnsize = twimghdr.size;
+					twimghdrcrc = twimghdr.crc;
+					memset(&twimghdr.crc, 0, sizeof(twimghdr.crc));
+
+					crc = crc32(0L, Z_NULL, 0);
+					crc = crc32(crc, (const unsigned char*) &twimghdr, sizeof(twimghdr));
+					if (crc == twimghdrcrc) {
+						if (write(adb_control_twrp_fd, readAdbStream, sizeof(readAdbStream)) < 1) {
+							std::string msg = "Cannot write to adb_control_twrp_fd: ";
+							printErrMsg(msg, errno);
+							close_restore_fds();
+							return false;
+						}
+					}
+					else {
+						adblogwrite("ADB TWIMG crc header doesn't match\n");
+						close_restore_fds();
+						return false;
+					}
+
+					#ifdef _DEBUG_ADB_BACKUP
+					std::string debug_fname = "/data/media/";
+					debug_fname.append(basename(twimghdr.name));
+					debug_fname.append("-restore.img");
+					adblogwrite("image: " + debug_fname + "\n");
+					debug_adb_fd = open(debug_fname.c_str(), O_WRONLY | O_CREAT, 0666);
+					adblogwrite("Opened restore image\n");
+					#endif
+
+					adblogwrite("opening TW_ADB_RESTORE\n");
+					adb_write_fd = open(TW_ADB_RESTORE, O_WRONLY);
+				}
+				//Tell TWRP we are sending a tar stream
+				else if (cmdtype == TWFN) {
+					struct twfilehdr twfilehdr;
+					uint32_t crc, twfilehdrcrc;
+					fileBytes = 0;
+					md5sumdata = false;
+					read_from_adb = true;
+					dataChunkBytes = 0;
+					extraData = false;
+
+					digest.init();
+					adblogwrite("Restoring TWFN\n");
+					memset(&twfilehdr, 0, sizeof(twfilehdr));
+					memcpy(&twfilehdr, readAdbStream, sizeof(readAdbStream));
+					md5fnsize = twfilehdr.size;
+					twfilehdrcrc = twfilehdr.crc;
+					memset(&twfilehdr.crc, 0, sizeof(twfilehdr.crc));
+
+					crc = crc32(0L, Z_NULL, 0);
+					crc = crc32(crc, (const unsigned char*) &twfilehdr, sizeof(twfilehdr));
+
+					if (crc == twfilehdrcrc) {
+						if (write(adb_control_twrp_fd, readAdbStream, sizeof(readAdbStream)) < 1) {
+							std::string msg = "Cannot write to adb_control_twrp_fd: ";
+							printErrMsg(msg, errno);
+							close_restore_fds();
+							return false;
+						}
+					}
+					else {
+						adblogwrite("ADB TWFN crc header doesn't match\n");
+						close_restore_fds();
+						return false;
+					}
+
+					#ifdef _DEBUG_ADB_BACKUP
+					std::string debug_fname = "/data/media/";
+					debug_fname.append(basename(twfilehdr.name));
+					debug_fname.append("-restore.tar");
+					adblogwrite("tar: " + debug_fname + "\n");
+					debug_adb_fd = open(debug_fname.c_str(), O_WRONLY | O_CREAT, 0666);
+					adblogwrite("Opened restore tar\n");
+					#endif
+
+					compressed = twfilehdr.compressed == 1 ? true: false;
+					adblogwrite("opening TW_ADB_RESTORE\n");
+					adb_write_fd = open(TW_ADB_RESTORE, O_WRONLY);
+				}
+				else if (cmdtype == MD5TRAILER) {
+					if (fileBytes >= md5fnsize)
+						close(adb_write_fd);
+					if (tweofrcvd) {
+						read_from_adb = true;
+						tweofrcvd = false;
+					}
+					else
+						read_from_adb = false; //don't read from adb until TWRP sends TWEOF
+					md5sumdata = false;
+					if (!checkMD5Trailer(readAdbStream, md5fnsize, &digest)) {
+						close_restore_fds();
+						break;
+					}
+					continue;
+				}
+				//Send the tar or partition image md5 to TWRP
+				else if (cmdtype == TWDATA) {
+					dataChunkBytes += sizeof(readAdbStream);
+					while (true) {
+						if ((readbytes = fread(readAdbStream, 1, sizeof(readAdbStream), adbd_fp)) != sizeof(readAdbStream)) {
+							close_restore_fds();
+							return false;
+						}
+						memcpy(&structcmd, readAdbStream, sizeof(readAdbStream));
+						std::string cmdtype = structcmd.get_type();
+
+						dataChunkBytes += readbytes;
+						totalbytes += readbytes;
+						fileBytes += readbytes;
+
+						if (cmdtype == MD5TRAILER) {
+							if (fileBytes >= md5fnsize)
+								close(adb_write_fd);
+							if (tweofrcvd) {
+								tweofrcvd = false;
+								read_from_adb = true;
+							}
+							else
+								read_from_adb = false; //don't read from adb until TWRP sends TWEOF
+							if (!checkMD5Trailer(readAdbStream, md5fnsize, &digest)) {
+								close_restore_fds();
+								break;
+							}
+							break;
+						}
+
+						digest.update((unsigned char*)readAdbStream, readbytes);
+
+						read_from_adb = true;
+
+						#ifdef _DEBUG_ADB_BACKUP
+						if (write(debug_adb_fd, readAdbStream, sizeof(readAdbStream)) < 0) {
+							std::string msg = "Cannot write to ADB_CONTROL_READ_FD: ";
+							printErrMsg(msg, errno);
+							close_restore_fds();
+							return false;
+						}
+						#endif
+
+						if (write(adb_write_fd, readAdbStream, sizeof(readAdbStream)) < 0) {
+							std::string msg = "Cannot write to TWRP ADB FIFO: ";
+							md5sumdata = true;
+							printErrMsg(msg, errno);
+							adblogwrite("end of stream reached.\n"); 
+							break;
+						}
+
+						if (dataChunkBytes == DATA_MAX_CHUNK_SIZE) {
+							dataChunkBytes = 0;
+							md5sumdata = false;
+							break;
+						}
+					}
+				}
+				else if (md5sumdata) {
+					digest.update((unsigned char*)readAdbStream, sizeof(readAdbStream));
+					md5sumdata = true;
+				}
+			}
+		}
+	}
+	std::stringstream str;
+	str << totalbytes;
+	close_restore_fds();
+	adblogwrite(str.str() + " bytes restored from adbbackup\n");
+	return true;
+}
+
+void twrpback::streamFileForTWRP(void) {
+	adblogwrite("streamFileForTwrp" + streamFn + "\n");
+}
+
+void twrpback::setStreamFileName(std::string fn) {
+	streamFn = fn;
+	adbd_fd = open(fn.c_str(), O_RDONLY);
+	if (adbd_fd < 0) {
+		adblogwrite("Unable to open adb_fd\n");
+		close(adbd_fd);
+		return;
+	}
+	restore();
+}
+
+void twrpback::threadStream(void) {
+	pthread_t thread;
+	ThreadPtr streamPtr = &twrpback::streamFileForTWRP;
+	PThreadPtr p = *(PThreadPtr*)&streamPtr;
+	pthread_create(&thread, NULL, p, this);
+	pthread_join(thread, NULL);
+}
+
+bool twrpback::checkMD5Trailer(char readAdbStream[], uint64_t md5fnsize, twrpMD5 *digest) {
+	struct AdbBackupFileTrailer md5tr;
+	uint32_t crc, md5trcrc, md5ident, md5identmatch;
+
+	//ADBSTRUCT_STATIC_ASSERT(sizeof(md5tr) == MAX_ADB_READ);
+	memcpy(&md5tr, readAdbStream, MAX_ADB_READ);
+	md5ident = md5tr.ident;
+
+	memset(&md5tr.ident, 0, sizeof(md5tr.ident));
+
+	md5identmatch = crc32(0L, Z_NULL, 0);
+	md5identmatch = crc32(md5identmatch, (const unsigned char*) &md5tr, sizeof(md5tr));
+	md5identmatch = crc32(md5identmatch, (const unsigned char*) &md5fnsize, sizeof(md5fnsize));
+
+	if (md5identmatch == md5ident) {
+		adblogwrite("checking MD5TRAILER\n");
+		md5trcrc = md5tr.crc;
+		memset(&md5tr.crc, 0, sizeof(md5tr.crc));
+		crc = crc32(0L, Z_NULL, 0);
+		crc = crc32(crc, (const unsigned char*) &md5tr, sizeof(md5tr));
+		if (crc == md5trcrc) {
+			if (write(adb_control_twrp_fd, &md5tr, sizeof(md5tr)) < 1) {
+				std::string msg = "Cannot write to adb_control_twrp_fd: ";
+				printErrMsg(msg, errno);
+				close_restore_fds();
+				return false;
+			}
+		}
+		else {
+			adblogwrite("ADB MD5TRAILER crc header doesn't match\n");
+			close_restore_fds();
+			return false;
+		}
+
+		AdbBackupFileTrailer md5;
+
+		memset(&md5, 0, sizeof(md5));
+		strncpy(md5.start_of_trailer, TWRP, sizeof(md5.start_of_trailer));
+		strncpy(md5.type, TWMD5, sizeof(md5.type));
+		std::string md5string = digest->return_digest_string();
+		strncpy(md5.md5, md5string.c_str(), sizeof(md5.md5));
+
+		adblogwrite("sending MD5 verification: " + md5string + "\n");
+		if (write(adb_control_twrp_fd, &md5, sizeof(md5)) < 1) {
+			std::string msg = "Cannot write to adb_control_twrp_fd: ";
+			printErrMsg(msg, errno);
+			close_restore_fds();
+			return false;
+		}
+		return true;
+	}
+	return false;
+}
diff --git a/adbbu/twrpback.hpp b/adbbu/twrpback.hpp
new file mode 100644
index 0000000..edc1626
--- /dev/null
+++ b/adbbu/twrpback.hpp
@@ -0,0 +1,61 @@
+/*
+		Copyright 2013 to 2017 TeamWin
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _TWRPBACK_HPP
+#define _TWRPBACK_HPP
+
+#include <fstream>
+#include "../twrpDigest/twrpMD5.hpp"
+
+class twrpback {
+public:
+	int adbd_fd;                                                             // adbd data stream
+	twrpback(void);
+	virtual ~twrpback(void);
+	bool backup(std::string command);                                        // adb backup stream
+	bool restore(void);                                                      // adb restore stream
+	void adblogwrite(std::string writemsg);                                  // adb debugging log function
+	void createFifos(void);                                                  // create fifos needed for adb backup
+	void closeFifos(void);                                                   // close created fifos
+	void streamFileForTWRP(void);                                            // stream file to twrp via bu
+	void setStreamFileName(std::string fn);                                  // tell adb backup what file to load on storage
+	void threadStream(void);                                                 // thread bu for streaming
+
+private:
+	int read_fd;                                                             // ors input fd
+	int write_fd;                                                            // ors operation fd
+	int ors_fd;                                                              // ors output fd
+	int adb_control_twrp_fd;                                                 // fd for bu to twrp communication
+	int adb_control_bu_fd;                                                   // fd for twrp to bu communication
+	int adb_read_fd;                                                         // adb read data stream
+	int adb_write_fd;                                                        // adb write data stream
+	int debug_adb_fd;                                                        // fd to write debug tars
+	bool firstPart;                                                          // first partition in the stream
+	FILE *adbd_fp;                                                           // file pointer for adb stream
+	char cmd[512];                                                           // store result of commands
+	char operation[512];                                                     // operation to send to ors
+	std::ofstream adblogfile;                                                // adb stream log file
+	std::string streamFn;
+	typedef void (twrpback::*ThreadPtr)(void);
+	typedef void* (*PThreadPtr)(void *);
+	void adbloginit(void);                                                   // setup adb log stream file
+	void close_backup_fds();                                                 // close backup resources
+	void close_restore_fds();                                                // close restore resources
+	bool checkMD5Trailer(char adbReadStream[], uint64_t md5fnsize, twrpMD5* digest); // Check MD5 Trailer
+	void printErrMsg(std::string msg, int errNum);                          // print error msg to adb log
+};
+
+#endif // _TWRPBACK_HPP
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
new file mode 100644
index 0000000..44112fd
--- /dev/null
+++ b/applypatch/Android.mk
@@ -0,0 +1,192 @@
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# libapplypatch (static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    applypatch.cpp \
+    bspatch.cpp \
+    freecache.cpp \
+    imgpatch.cpp
+LOCAL_MODULE := libapplypatch
+LOCAL_MODULE_TAGS := optional
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include \
+    $(commands_recovery_local_path)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_STATIC_LIBRARIES := \
+    libotafault \
+    libbase \
+    libcrypto \
+    libbspatch \
+    libbz \
+    libz
+LOCAL_WHOLE_STATIC_LIBRARIES += libmtdutils
+LOCAL_CFLAGS := \
+    -DZLIB_CONST \
+    -Werror
+
+BOARD_RECOVERY_DEFINES := BOARD_BML_BOOT BOARD_BML_RECOVERY
+
+$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \
+  $(if $($(board_define)), \
+    $(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \
+  ) \
+  )
+
+include $(BUILD_STATIC_LIBRARY)
+
+# libimgpatch (static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    bspatch.cpp \
+    imgpatch.cpp
+LOCAL_MODULE := libimgpatch
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include \
+    $(commands_recovery_local_path)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto \
+    libbspatch \
+    libbase \
+    libbz \
+    libz
+LOCAL_CFLAGS := \
+    -DZLIB_CONST \
+    -Werror
+include $(BUILD_STATIC_LIBRARY)
+
+# libimgpatch (host static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    bspatch.cpp \
+    imgpatch.cpp
+LOCAL_MODULE := libimgpatch
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include \
+    $(commands_recovery_local_path)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto \
+    libbspatch \
+    libbase \
+    libbz \
+    libz
+LOCAL_CFLAGS := \
+    -DZLIB_CONST \
+    -Werror
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+# libapplypatch_modes (static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    applypatch_modes.cpp
+LOCAL_MODULE := libapplypatch_modes
+LOCAL_C_INCLUDES := $(commands_recovery_local_path)
+LOCAL_STATIC_LIBRARIES := \
+    libapplypatch \
+    libbase \
+    libedify \
+    libcrypto
+LOCAL_CFLAGS := -Werror
+include $(BUILD_STATIC_LIBRARY)
+
+# applypatch (target executable)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := applypatch_main.cpp
+LOCAL_MODULE := applypatch
+LOCAL_C_INCLUDES := $(commands_recovery_local_path)
+LOCAL_STATIC_LIBRARIES := \
+    libapplypatch_modes \
+    libapplypatch \
+    libbase \
+    libedify \
+    libotafault \
+    libcrypto \
+    libbspatch \
+    libbz
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    libz \
+    libcutils
+LOCAL_CFLAGS := -Werror
+include $(BUILD_EXECUTABLE)
+
+libimgdiff_src_files := imgdiff.cpp
+
+# libbsdiff is compiled with -D_FILE_OFFSET_BITS=64.
+libimgdiff_cflags := \
+    -Werror \
+    -D_FILE_OFFSET_BITS=64
+
+libimgdiff_static_libraries := \
+    libbsdiff \
+    libdivsufsort \
+    libdivsufsort64 \
+    libziparchive \
+    libutils \
+    liblog \
+    libbase \
+    libz
+
+# libimgdiff (static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    $(libimgdiff_src_files)
+LOCAL_MODULE := libimgdiff
+LOCAL_CFLAGS := \
+    $(libimgdiff_cflags)
+LOCAL_STATIC_LIBRARIES := \
+    $(libimgdiff_static_libraries)
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+include $(BUILD_STATIC_LIBRARY)
+
+# libimgdiff (host static library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    $(libimgdiff_src_files)
+LOCAL_MODULE := libimgdiff
+LOCAL_CFLAGS := \
+    $(libimgdiff_cflags)
+LOCAL_STATIC_LIBRARIES := \
+    $(libimgdiff_static_libraries)
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+# imgdiff (host static executable)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := imgdiff_main.cpp
+LOCAL_MODULE := imgdiff
+LOCAL_CFLAGS := -Werror
+LOCAL_STATIC_LIBRARIES := \
+    libimgdiff \
+    $(libimgdiff_static_libraries) \
+    libbz
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp
old mode 100644
new mode 100755
index adda697..206a3a3
--- a/applypatch/applypatch.cpp
+++ b/applypatch/applypatch.cpp
@@ -40,6 +40,9 @@
 #include <android-base/unique_fd.h>
 #include <openssl/sha.h>
 
+#include "bmlutils/bmlutils.h"
+#include "mtdutils/mtdutils.h"
+
 #include "edify/expr.h"
 #include "otautil/paths.h"
 #include "otautil/print_sha1.h"
@@ -186,15 +189,8 @@
       return false;
     }
 
-    unsigned char buffer[4096];
-    start = len;
-    for (size_t p = 0; p < len; p += sizeof(buffer)) {
-      size_t to_read = len - p;
-      if (to_read > sizeof(buffer)) {
-        to_read = sizeof(buffer);
-      }
+    const char* partition = pieces[1].c_str();
 
-      if (!android::base::ReadFully(fd, buffer, to_read)) {
         PLOG(ERROR) << "Failed to verify-read " << partition << " at " << p;
         return false;
       }
diff --git a/attr/Android.mk b/attr/Android.mk
new file mode 100755
index 0000000..121ae02
--- /dev/null
+++ b/attr/Android.mk
@@ -0,0 +1,22 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := listxattr.c
+LOCAL_CFLAGS := -c -W
+LOCAL_MODULE := listxattr
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+LOCAL_PACK_MODULE_RELOCATIONS := false
+
+ifneq ($(TARGET_ARCH), arm64)
+    ifneq ($(TARGET_ARCH), x86_64)
+        LOCAL_LDFLAGS += -Wl,-dynamic-linker,/system/bin/linker
+    else
+        LOCAL_LDFLAGS += -Wl,-dynamic-linker,/system/bin/linker64
+    endif
+else
+    LOCAL_LDFLAGS += -Wl,-dynamic-linker,/system/bin/linker64
+endif
+
+include $(BUILD_EXECUTABLE)
diff --git a/attr/listxattr.c b/attr/listxattr.c
new file mode 100644
index 0000000..1aa8e14
--- /dev/null
+++ b/attr/listxattr.c
@@ -0,0 +1,202 @@
+/****************************************************************************
+ |  (C) Copyright 2008 Novell, Inc. All Rights Reserved.
+ |
+ |  GPLv2: This program is free software; you can redistribute it
+ |  and/or modify it under the terms of version 2 of the GNU General
+ |  Public License as published by the Free Software Foundation.
+ |
+ |  This program is distributed in the hope that it will be useful,
+ |  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ |  GNU General Public License for more details.
+ +-------------------------------------------------------------------------*/
+/*
+ * NOTE from Dees_Troy: modified source to display values along with xattr names
+ * Below code comments about usage are no longer accurate but came from the
+ * original source code from the chromium project and combine features of
+ * listxattr and getfattr:
+ * https://chromium.googlesource.com/chromiumos/platform/punybench/+/factory-1235.B/file.m/listxattr.c
+ * https://chromium.googlesource.com/chromiumos/platform/punybench/+/factory-1235.B/file.m/getxattr.c
+ */
+/*
+ * LISTXATTR(2)             Linux Programmer's Manual            LISTXATTR(2)
+ *
+ *
+ *
+ * NAME
+ *        listxattr, llistxattr, flistxattr - list extended attribute names
+ *
+ * SYNOPSIS
+ *        #include <sys/types.h>
+ *        #include <attr/xattr.h>
+ *
+ *        ssize_t listxattr (const char *path,
+ *                             char *list, size_t size);
+ *        ssize_t llistxattr (const char *path,
+ *                             char *list, size_t size);
+ *        ssize_t flistxattr (int filedes,
+ *                             char *list, size_t size);
+ *
+ * DESCRIPTION
+ *        Extended  attributes  are  name:value  pairs associated with inodes
+ *        (files, directories, symlinks, etc).  They are  extensions  to  the
+ *        normal  attributes which are associated with all inodes in the sys-
+ *        tem (i.e. the stat(2)  data).   A  complete  overview  of  extended
+ *        attributes concepts can be found in attr(5).
+ *
+ *        listxattr retrieves the list of extended attribute names associated
+ *        with the given path in the filesystem.  The  list  is  the  set  of
+ *        (NULL-terminated)  names,  one  after the other.  Names of extended
+ *        attributes to which the calling process does not have access may be
+ *        omitted  from  the  list.  The length of the attribute name list is
+ *        returned.
+ *
+ *        llistxattr is identical to listxattr, except in the case of a  sym-
+ *        bolic  link, where the list of names of extended attributes associ-
+ *        ated with the link itself is retrieved, not the file that it refers
+ *        to.
+ *
+ *        flistxattr is identical to listxattr, only the open file pointed to
+ *        by filedes (as returned by open(2)) is  interrogated  in  place  of
+ *        path.
+ *
+ *        A  single  extended  attribute  name  is  a  simple NULL-terminated
+ *        string.  The name includes a namespace prefix; there  may  be  sev-
+ *        eral, disjoint namespaces associated with an individual inode.
+ *
+ *        An  empty  buffer  of  size  zero can be passed into these calls to
+ *        return the current size of the list of  extended  attribute  names,
+ *        which  can be used to estimate the size of a buffer which is suffi-
+ *        ciently large to hold the list of names.
+ *
+ * EXAMPLES
+ *        The list of names is returned as an unordered array of  NULL-termi-
+ *        nated  character  strings  (attribute  names  are separated by NULL
+ *        characters), like this:
+ *               user.name1\0system.name1\0user.name2\0
+ *
+ *        Filesystems like ext2, ext3 and  XFS  which  implement  POSIX  ACLs
+ *        using extended attributes, might return a list like this:
+ *               system.posix_acl_access\0system.posix_acl_default\0
+ *
+ * RETURN VALUE
+ *        On  success,  a  positive number is returned indicating the size of
+ *        the extended attribute name list.  On failure, -1 is  returned  and
+ *        errno is set appropriately.
+ *
+ *        If  the  size  of  the list buffer is too small to hold the result,
+ *        errno is set to ERANGE.
+ *
+ *        If extended attributes are not supported by the filesystem, or  are
+ *        disabled, errno is set to ENOTSUP.
+ *
+ *        The errors documented for the stat(2) system call are also applica-
+ *        ble here.
+ *
+ * AUTHORS
+ *        Andreas Gruenbacher, <a.gruenbacher@computer.org> and the  SGI  XFS
+ *        development  team,  <linux-xfs@oss.sgi.com>.   Please  send any bug
+ *        reports or comments to these addresses.
+ *
+ * SEE ALSO
+ *        getfattr(1),  setfattr(1),  getxattr(2),  open(2),  removexattr(2),
+ *        setxattr(2), stat(2), attr(5)
+ *
+ *
+ *
+ * Dec 2001                    Extended Attributes               LISTXATTR(2)
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+//#include <eprintf.h>
+//#include <puny.h>
+
+/* dumpmem: dumps an n byte area of memory to screen */
+void dumpmem (const void *mem, unsigned int n)
+{
+	const char *c = mem;
+	unsigned i;
+	int all_text = 1;
+	if (n == 0) {
+		printf("<empty>");
+		return;
+	}
+	for (i = 0; i < n - 1; i++, c++) {
+		if (!isprint(*c)) {
+			all_text = 0;
+			break;
+		}
+	}
+	c = mem;
+	if (all_text) {
+		for (i = 0; i < n - 1; i++, c++) {
+			putchar(*c);
+		}
+		return;
+	} else {
+		char hex[(n * 2) + 1];
+		for(i = 0; i < n; i++, c++)
+			sprintf(hex + (i * 2), "%02X", *c);
+		hex[n] = 0;
+		printf("0x%s", hex);
+		return;
+	}
+}
+
+void dump_list (char *file, char *list, ssize_t size)
+{
+	int	c;
+	int	i;
+	int	first = 1;
+	int j = 0;
+	char xattr[1024];
+	char value[1024];
+	ssize_t	size2;
+	for (i = 0; i < size; i++) {
+		c = list[i];
+		if (c) {
+			if (first) {
+				putchar('\t');
+				first = 0;
+				j = 0;
+			}
+			putchar(c);
+			xattr[j++] = list[i];
+		} else {
+			xattr[j] = '\0';
+			size2 = getxattr(file, xattr, value, sizeof(value));
+			if (size2 < 0) {
+				printf("file=%s xattr=%s returned:", file, xattr);
+			} else {
+				putchar('=');
+				dumpmem(value, size2);
+			}
+			putchar('\n');
+			first = 1;
+		}
+	}
+}
+void usage (void)
+{
+	printf("listxattr <file>");
+}
+char	List[1<<17];
+int main (int argc, char *argv[])
+{
+	ssize_t	size;
+	if (argc < 2) {
+		usage();
+		exit(2);
+	}
+	size = listxattr(argv[1], List, sizeof(List));
+	if (size == -1) {
+		perror(argv[1]);
+		exit(2);
+	}
+	printf("xattrs for %s:\n", argv[1]);
+	dump_list(argv[1], List, size);
+	return 0;
+}
diff --git a/bmlutils/Android.mk b/bmlutils/Android.mk
new file mode 100644
index 0000000..9cbada0
--- /dev/null
+++ b/bmlutils/Android.mk
@@ -0,0 +1,34 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+BOARD_RECOVERY_DEFINES := BOARD_BML_BOOT BOARD_BML_RECOVERY
+LOCAL_STATIC_LIBRARY := libcrecovery
+
+$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \
+  $(if $($(board_define)), \
+    $(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \
+  ) \
+  )
+
+LOCAL_SRC_FILES := bmlutils.c
+LOCAL_MODULE := libbmlutils
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_STATIC_LIBRARY)
+
+#Added for building TWRP dynamic:
+include $(CLEAR_VARS)
+
+BOARD_RECOVERY_DEFINES := BOARD_BML_BOOT BOARD_BML_RECOVERY
+LOCAL_SHARED_LIBRARIES := libcrecovery
+
+$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \
+  $(if $($(board_define)), \
+    $(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \
+  ) \
+  )
+
+LOCAL_SRC_FILES := bmlutils.c
+LOCAL_MODULE := libbmlutils
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_SHARED_LIBRARY)
diff --git a/bmlutils/bmlutils.c b/bmlutils/bmlutils.c
new file mode 100755
index 0000000..382c0a5
--- /dev/null
+++ b/bmlutils/bmlutils.c
@@ -0,0 +1,197 @@
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/input.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/wait.h>
+#include <sys/limits.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <signal.h>
+#include <sys/wait.h>
+
+#include <bmlutils.h>
+
+#include "../libcrecovery/common.h"
+
+static int restore_internal(const char* bml, const char* filename)
+{
+    char buf[4096];
+    int dstfd, srcfd, bytes_read, total_read = 0;
+    if (filename == NULL)
+        srcfd = 0;
+    else {
+        srcfd = open(filename, O_RDONLY | O_LARGEFILE);
+        if (srcfd < 0)
+            return 2;
+    }
+    dstfd = open(bml, O_RDWR | O_LARGEFILE);
+    if (dstfd < 0)
+        return 3;
+    if (ioctl(dstfd, BML_UNLOCK_ALL, 0))
+        return 4;
+    do {
+        total_read += bytes_read = read(srcfd, buf, 4096);
+        if (!bytes_read)
+            break;
+        if (bytes_read < 4096)
+            memset(&buf[bytes_read], 0, 4096 - bytes_read);
+        if (write(dstfd, buf, 4096) < 4096)
+            return 5;
+    } while(bytes_read == 4096);
+    
+    close(dstfd);
+    close(srcfd);
+    
+    return 0;
+}
+
+int cmd_bml_restore_raw_partition(const char *partition, const char *filename)
+{
+    if (strcmp(partition, "boot") != 0 && strcmp(partition, "recovery") != 0 && strcmp(partition, "recoveryonly") != 0 && partition[0] != '/')
+        return 6;
+
+    int ret = -1;
+    if (strcmp(partition, "recoveryonly") != 0) {
+        // always restore boot, regardless of whether recovery or boot is flashed.
+        // this is because boot and recovery are the same on some samsung phones.
+        // unless of course, recoveryonly is explictly chosen (bml8)
+        ret = restore_internal(BOARD_BML_BOOT, filename);
+        if (ret != 0)
+            return ret;
+    }
+
+    if (strcmp(partition, "recovery") == 0 || strcmp(partition, "recoveryonly") == 0)
+        ret = restore_internal(BOARD_BML_RECOVERY, filename);
+
+    // support explicitly provided device paths
+    if (partition[0] == '/')
+        ret = restore_internal(partition, filename);
+    return ret;
+}
+
+int cmd_bml_backup_raw_partition(const char *partition, const char *out_file)
+{
+    const char* bml;
+    if (strcmp("boot", partition) == 0)
+        bml = BOARD_BML_BOOT;
+    else if (strcmp("recovery", partition) == 0)
+        bml = BOARD_BML_RECOVERY;
+    else if (partition[0] == '/') {
+        // support explicitly provided device paths
+        bml = partition;
+    }
+    else {
+        printf("Invalid partition.\n");
+        return -1;
+    }
+
+    int ch;
+    FILE *in;
+    FILE *out;
+    char buf[512];
+    unsigned sz = 0;
+    unsigned i;
+    int ret = -1;
+    const char *in_file = bml;
+
+    in  = fopen ( in_file,  "r" );
+    if (in == NULL)
+        goto ERROR3;
+
+    out = fopen ( out_file,  "w" );
+    if (out == NULL)
+        goto ERROR2;
+
+    fseek(in, 0L, SEEK_END);
+    sz = ftell(in);
+    fseek(in, 0L, SEEK_SET);
+
+    if (sz % 512)
+    {
+        while ( ( ch = fgetc ( in ) ) != EOF )
+            fputc ( ch, out );
+    }
+    else
+    {
+        for (i=0; i< (sz/512); i++)
+        {
+            if ((fread(buf, 512, 1, in)) != 1)
+                goto ERROR1;
+            if ((fwrite(buf, 512, 1, out)) != 1)
+                goto ERROR1;
+        }
+    }
+
+    fsync(fileno(out));
+    ret = 0;
+ERROR1:
+    fclose ( out );
+ERROR2:
+    fclose ( in );
+ERROR3:
+    return ret;
+}
+
+int cmd_bml_erase_raw_partition(const char *partition __unused)
+{
+    // TODO: implement raw wipe
+    return 0;
+}
+
+int cmd_bml_erase_partition(const char *partition __unused, const char *filesystem __unused)
+{
+    return -1;
+}
+
+int cmd_bml_mount_partition(const char *partition __unused, const char *mount_point __unused, const char *filesystem __unused, int read_only __unused)
+{
+    return -1;
+}
+
+int cmd_bml_get_partition_device(const char *partition __unused, char *device __unused)
+{
+    return -1;
+}
+
+int format_rfs_device (const char *device, const char *path) {
+    const char *fatsize = "32";
+    const char *sectorsize = "1";
+
+    if (strcmp(path, "/datadata") == 0 || strcmp(path, "/cache") == 0) {
+        fatsize = "16";
+    }
+
+    // Just in case /data sector size needs to be altered
+    else if (strcmp(path, "/data") == 0 ) {
+        sectorsize = "1";
+    } 
+
+    // dump 10KB of zeros to partition before format due to fat.format bug
+    char cmd[PATH_MAX];
+
+    sprintf(cmd, "/system/bin/dd if=/dev/zero of=%s bs=4096 count=10", device);
+    if(__system(cmd)) {
+        printf("failure while zeroing rfs partition.\n");
+        return -1;
+    }
+
+    // Run fat.format
+    sprintf(cmd, "/system/bin/fat.format -F %s -S 4096 -s %s %s", fatsize, sectorsize, device);
+    if(__system(cmd)) {
+        printf("failure while running fat.format\n");
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/bmlutils/bmlutils.h b/bmlutils/bmlutils.h
new file mode 100644
index 0000000..1e85f08
--- /dev/null
+++ b/bmlutils/bmlutils.h
@@ -0,0 +1,16 @@
+#ifndef BMLUTILS_H_
+#define BMLUTILS_H_
+
+int format_rfs_device (const char *device, const char *path);
+
+#define BML_UNLOCK_ALL				0x8A29		///< unlock all partition RO -> RW
+
+#ifndef BOARD_BML_BOOT
+#define BOARD_BML_BOOT              "/dev/block/bml7"
+#endif
+
+#ifndef BOARD_BML_RECOVERY
+#define BOARD_BML_RECOVERY          "/dev/block/bml8"
+#endif
+
+#endif // BMLUTILS_H_
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
old mode 100644
new mode 100755
diff --git a/crypto/scrypt/Android.bp b/crypto/scrypt/Android.bp
new file mode 100755
index 0000000..2a3f025
--- /dev/null
+++ b/crypto/scrypt/Android.bp
@@ -0,0 +1,6 @@
+build = ["sources.bp"]

+

+cc_library_static {

+    name: "libscrypttwrp_static",

+    include_dirs: ["bootable/recovery/crypto/scrypt/lib/util"]

+}
\ No newline at end of file
diff --git a/crypto/scrypt/Android.mk b/crypto/scrypt/Android.mk
new file mode 100644
index 0000000..4514f94
--- /dev/null
+++ b/crypto/scrypt/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH := $(call my-dir)
+
+# Enable to be able to use ALOG* with #include "cutils/log.h"
+#log_c_includes += system/core/include
+#log_shared_libraries := liblog
+
+# These makefiles are here instead of being Android.mk files in the
+# respective crypto, ssl, and apps directories so
+# that import_openssl.sh import won't remove them.
+include $(LOCAL_PATH)/build-config.mk
+include $(LOCAL_PATH)/Scrypt.mk
+
+include $(LOCAL_PATH)/tests/Android.mk
diff --git a/crypto/scrypt/MODULE_LICENSE_BSD_LIKE b/crypto/scrypt/MODULE_LICENSE_BSD_LIKE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/crypto/scrypt/MODULE_LICENSE_BSD_LIKE
diff --git a/crypto/scrypt/NOTICE b/crypto/scrypt/NOTICE
new file mode 100644
index 0000000..b0b9311
--- /dev/null
+++ b/crypto/scrypt/NOTICE
@@ -0,0 +1,36 @@
+/*-
+ * Copyright 2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This file was originally written by Colin Percival as part of the Tarsnap
+ * online backup system.
+ */
+
+/*
+ * version 20110505
+ * D. J. Bernstein
+ * Public domain.
+ *
+ * Based on crypto_core/salsa208/armneon/core.c from SUPERCOP 20130419
+ */
diff --git a/crypto/scrypt/Scrypt-config.mk b/crypto/scrypt/Scrypt-config.mk
new file mode 100644
index 0000000..e33cf26
--- /dev/null
+++ b/crypto/scrypt/Scrypt-config.mk
@@ -0,0 +1,105 @@
+# Auto-generated - DO NOT EDIT!
+# To regenerate, edit scrypt.config, then run:
+#     ./import_scrypt.sh import /path/to/scrypt-1.1.6.tar.gz
+#
+# Before including this file, the local Android.mk must define the following
+# variables:
+#
+#    local_c_flags
+#    local_c_includes
+#    local_additional_dependencies
+#
+# This script will define the following variables:
+#
+#    target_c_flags
+#    target_c_includes
+#    target_src_files
+#
+#    host_c_flags
+#    host_c_includes
+#    host_src_files
+#
+
+# Ensure these are empty.
+unknown_arch_c_flags :=
+unknown_arch_src_files :=
+unknown_arch_exclude_files :=
+
+
+common_c_flags :=
+
+common_src_files := \
+  lib/crypto/crypto_scrypt-ref.c \
+
+common_c_includes := \
+  lib/crypto \
+  lib/util \
+
+arm_c_flags :=
+
+arm_src_files :=
+
+arm_exclude_files :=
+
+arm_neon_c_flags :=
+
+arm_neon_src_files := \
+  lib/crypto/crypto_scrypt-neon.c \
+
+arm_neon_exclude_files := \
+  lib/crypto/crypto_scrypt-ref.c \
+
+x86_c_flags :=
+
+x86_src_files := \
+  lib/crypto/crypto_scrypt-sse.c \
+
+x86_exclude_files := \
+  lib/crypto/crypto_scrypt-ref.c \
+
+x86_64_c_flags :=
+
+x86_64_src_files := \
+  lib/crypto/crypto_scrypt-sse.c \
+
+x86_64_exclude_files := \
+  lib/crypto/crypto_scrypt-ref.c \
+
+mips_c_flags :=
+
+mips_src_files :=
+
+mips_exclude_files :=
+
+target_arch := $(TARGET_ARCH)
+ifeq ($(target_arch)-$(TARGET_HAS_BIGENDIAN),mips-true)
+target_arch := unknown_arch
+endif
+
+target_c_flags    := $(common_c_flags) $($(target_arch)_c_flags) $(local_c_flags)
+target_c_includes := $(addprefix external/scrypt/,$(common_c_includes)) $(local_c_includes)
+target_src_files  := $(common_src_files) $($(target_arch)_src_files)
+target_src_files  := $(filter-out $($(target_arch)_exclude_files), $(target_src_files))
+
+# Hacks for ARM NEON support
+ifneq (,$(filter $(target_arch), arm arm64))
+ifeq ($(ARCH_ARM_HAVE_NEON),true)
+target_c_flags   += $(arm_neon_c_flags)
+target_src_files += $(arm_neon_src_files)
+target_src_files := $(filter-out $(arm_neon_exclude_files), $(target_src_files))
+endif
+endif
+
+ifeq ($(HOST_OS)-$(HOST_ARCH),linux-x86)
+host_arch := x86
+else
+host_arch := unknown_arch
+endif
+
+host_c_flags    := $(common_c_flags) $($(host_arch)_c_flags) $(local_c_flags)
+host_c_includes := $(addprefix external/scrypt/,$(common_c_includes)) $(local_c_includes)
+host_src_files  := $(common_src_files) $($(host_arch)_src_files)
+host_src_files  := $(filter-out $($(host_arch)_exclude_files), $(host_src_files))
+
+local_additional_dependencies += $(LOCAL_PATH)/Scrypt-config.mk
+
diff --git a/crypto/scrypt/Scrypt.mk b/crypto/scrypt/Scrypt.mk
new file mode 100644
index 0000000..bf50cc0
--- /dev/null
+++ b/crypto/scrypt/Scrypt.mk
@@ -0,0 +1,7 @@
+local_c_flags := -DUSE_OPENSSL_PBKDF2
+
+local_c_includes := $(log_c_includes) external/openssl/include external/boringssl/src/include
+
+local_additional_dependencies := $(LOCAL_PATH)/android-config.mk $(LOCAL_PATH)/Scrypt.mk
+
+include $(LOCAL_PATH)/Scrypt-config.mk
diff --git a/crypto/scrypt/android-config.mk b/crypto/scrypt/android-config.mk
new file mode 100644
index 0000000..326e113
--- /dev/null
+++ b/crypto/scrypt/android-config.mk
@@ -0,0 +1,16 @@
+#
+# These flags represent the build-time configuration of scrypt for Android
+#
+# The value of $(scrypt_cflags) was pruned from the Makefile generated
+# by running ./configure from import_scrypt.sh.
+#
+# This script performs minor but required patching for the Android build.
+#
+
+LOCAL_CFLAGS += $(scrypt_cflags)
+
+# Add in flags to let config.h be read properly
+LOCAL_CFLAGS += "-DHAVE_CONFIG_H"
+
+# Add clang here when it works on host
+# LOCAL_CLANG := true
diff --git a/crypto/scrypt/build-config.mk b/crypto/scrypt/build-config.mk
new file mode 100644
index 0000000..3d2ab91
--- /dev/null
+++ b/crypto/scrypt/build-config.mk
@@ -0,0 +1,6 @@
+# Auto-generated - DO NOT EDIT!
+# To regenerate, edit scrypt.config, then run:
+#     ./import_scrypt.sh import /path/to/scrypt-1.1.6.tar.gz
+#
+scrypt_cflags := \
+
diff --git a/crypto/scrypt/config.h b/crypto/scrypt/config.h
new file mode 100644
index 0000000..3514f39
--- /dev/null
+++ b/crypto/scrypt/config.h
@@ -0,0 +1,99 @@
+/* config.h.  Generated from config.h.in by configure.  */
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define to 1 if you have the `clock_gettime' function. */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 if you have the declaration of `be64enc', and to 0 if you
+   don't. */
+#define HAVE_DECL_BE64ENC 0
+
+/* Define to 1 if you have the <err.h> header file. */
+#define HAVE_ERR_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `rt' library (-lrt). */
+#define HAVE_LIBRT 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `posix_memalign' function. */
+#define HAVE_POSIX_MEMALIGN 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if the system has the type `struct sysinfo'. */
+#define HAVE_STRUCT_SYSINFO 1
+
+/* Define to 1 if `mem_unit' is member of `struct sysinfo'. */
+#define HAVE_STRUCT_SYSINFO_MEM_UNIT 1
+
+/* Define to 1 if `totalram' is member of `struct sysinfo'. */
+#define HAVE_STRUCT_SYSINFO_TOTALRAM 1
+
+/* Define to 1 if the OS has a hw.usermem sysctl */
+/* #undef HAVE_SYSCTL_HW_USERMEM */
+
+/* Define to 1 if you have the `sysinfo' function. */
+#define HAVE_SYSINFO 1
+
+/* Define to 1 if you have the <sys/endian.h> header file. */
+/* #undef HAVE_SYS_ENDIAN_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/sysinfo.h> header file. */
+#define HAVE_SYS_SYSINFO_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Name of package */
+#define PACKAGE "scrypt"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "scrypt"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "scrypt 1.1.6"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "scrypt"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.1.6"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#define VERSION "1.1.6"
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
diff --git a/crypto/scrypt/import_scrypt.sh b/crypto/scrypt/import_scrypt.sh
new file mode 100755
index 0000000..324eae6
--- /dev/null
+++ b/crypto/scrypt/import_scrypt.sh
@@ -0,0 +1,493 @@
+#!/bin/bash
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# This script imports new versions of scrypt (http://www.tarsnap.com/scrypt/) into the
+# Android source tree.  To run, (1) fetch the appropriate tarball from the scrypt repository,
+# (2) check the gpg/pgp signature, and then (3) run:
+#   ./import_scrypt.sh import scrypt-*.tar.gz
+#
+# IMPORTANT: See README.android for additional details.
+
+# turn on exit on error as well as a warning when it happens
+set -e
+set -x
+trap  "echo WARNING: Exiting on non-zero subprocess exit code" ERR;
+
+# Ensure consistent sorting order / tool output.
+export LANG=C
+export LC_ALL=C
+
+export DIRNAME=$(dirname $0)
+
+function die() {
+  declare -r message=$1
+
+  echo $message
+  exit 1
+}
+
+function usage() {
+  declare -r message=$1
+
+  if [ ! "$message" = "" ]; then
+    echo $message
+  fi
+  echo "Usage:"
+  echo "  ./import_scrypt.sh import </path/to/scrypt-*.tar.gz>"
+  echo "  ./import_scrypt.sh regenerate <patch/*.patch>"
+  echo "  ./import_scrypt.sh generate <patch/*.patch> </path/to/scrypt-*.tar.gz>"
+  exit 1
+}
+
+function main() {
+  if [ ! -d patches ]; then
+    die "scrypt patch directory patches/ not found"
+  fi
+
+  if [ ! -f scrypt.version ]; then
+    die "scrypt.version not found"
+  fi
+
+  source $DIRNAME/scrypt.version
+  if [ "$SCRYPT_VERSION" == "" ]; then
+    die "Invalid scrypt.version; see README.android for more information"
+  fi
+
+  SCRYPT_DIR=scrypt-$SCRYPT_VERSION
+  SCRYPT_DIR_ORIG=$SCRYPT_DIR.orig
+
+  if [ ! -f scrypt.config ]; then
+    die "scrypt.config not found"
+  fi
+
+  source $DIRNAME/scrypt.config
+  if [ "$CONFIGURE_ARGS" == "" -o "$UNNEEDED_SOURCES" == "" -o "$NEEDED_SOURCES" == "" ]; then
+    die "Invalid scrypt.config; see README.android for more information"
+  fi
+
+  declare -r command=$1
+  shift || usage "No command specified. Try import, regenerate, or generate."
+  if [ "$command" = "import" ]; then
+    declare -r tar=$1
+    shift || usage "No tar file specified."
+    import $tar
+  elif [ "$command" = "regenerate" ]; then
+    declare -r patch=$1
+    shift || usage "No patch file specified."
+    [ -d $SCRYPT_DIR ] || usage "$SCRYPT_DIR not found, did you mean to use generate?"
+    [ -d $SCRYPT_DIR_ORIG_ORIG ] || usage "$SCRYPT_DIR_ORIG not found, did you mean to use generate?"
+    regenerate $patch
+  elif [ "$command" = "generate" ]; then
+    declare -r patch=$1
+    shift || usage "No patch file specified."
+    declare -r tar=$1
+    shift || usage "No tar file specified."
+    generate $patch $tar
+  else
+    usage "Unknown command specified $command. Try import, regenerate, or generate."
+  fi
+}
+
+# Compute the name of an assembly source file generated by one of the
+# gen_asm_xxxx() functions below. The logic is the following:
+# - if "$2" is not empty, output it directly
+# - otherwise, change the file extension of $1 from .pl to .S and output
+#   it.
+# Usage: default_asm_file "$1" "$2"
+#     or default_asm_file "$@"
+#
+# $1: generator path (perl script)
+# $2: optional output file name.
+function default_asm_file () {
+  if [ "$2" ]; then
+    echo "$2"
+  else
+    echo "${1%%.pl}.S"
+  fi
+}
+
+# Generate an ARM assembly file.
+# $1: generator (perl script)
+# $2: [optional] output file name
+function gen_asm_arm () {
+  local OUT
+  OUT=$(default_asm_file "$@")
+  perl "$1" > "$OUT"
+}
+
+function gen_asm_mips () {
+  local OUT
+  OUT=$(default_asm_file "$@")
+  # The perl scripts expect to run the target compiler as $CC to determine
+  # the endianess of the target. Setting CC to true is a hack that forces the scripts
+  # to generate little endian output
+  CC=true perl "$1" o32 > "$OUT"
+}
+
+function gen_asm_x86 () {
+  local OUT
+  OUT=$(default_asm_file "$@")
+  perl "$1" elf -fPIC > "$OUT"
+}
+
+function gen_asm_x86_64 () {
+  local OUT
+  OUT=$(default_asm_file "$@")
+  perl "$1" elf "$OUT" > "$OUT"
+}
+
+
+# Filter all items in a list that match a given pattern.
+# $1: space-separated list
+# $2: egrep pattern.
+# Out: items in $1 that match $2
+function filter_by_egrep() {
+  declare -r pattern=$1
+  shift
+  echo "$@" | tr ' ' '\n' | grep -e "$pattern" | tr '\n' ' '
+}
+
+# Sort and remove duplicates in a space-separated list
+# $1: space-separated list
+# Out: new space-separated list
+function uniq_sort () {
+  echo "$@" | tr ' ' '\n' | sort -u | tr '\n' ' '
+}
+
+function print_autogenerated_header() {
+  echo "# Auto-generated - DO NOT EDIT!"
+  echo "# To regenerate, edit scrypt.config, then run:"
+  echo "#     ./import_scrypt.sh import /path/to/scrypt-$SCRYPT_VERSION.tar.gz"
+  echo "#"
+}
+
+function generate_build_config_mk() {
+  ./configure $CONFIGURE_ARGS
+  #rm -f apps/CA.pl.bak crypto/scryptconf.h.bak
+
+  declare -r tmpfile=$(mktemp)
+  (grep -e -D Makefile | grep -v CONFIGURE_ARGS= | grep -v OPTIONS=) > $tmpfile
+
+  declare -r cflags=$(filter_by_egrep "^-D" $(grep -e "^CFLAG=" $tmpfile))
+  declare -r depflags=$(filter_by_egrep "^-D" $(grep -e "^DEPFLAG=" $tmpfile))
+  rm -f $tmpfile
+
+  echo "Generating $(basename $1)"
+  (
+    print_autogenerated_header
+
+    echo "scrypt_cflags := \\"
+    for cflag in $cflags $depflags; do
+      echo "  $cflag \\"
+    done
+    echo ""
+  ) > $1
+}
+
+# Return the value of a computed variable name.
+# E.g.:
+#   FOO=foo
+#   BAR=bar
+#   echo $(var_value FOO_$BAR)   -> prints the value of ${FOO_bar}
+# $1: Variable name
+# Out: variable value
+var_value() {
+  # Note: don't use 'echo' here, because it's sensitive to values
+  #       that begin with an underscore (e.g. "-n")
+  eval printf \"%s\\n\" \$$1
+}
+
+# Same as var_value, but returns sorted output without duplicates.
+# $1: Variable name
+# Out: variable value (if space-separated list, sorted with no duplicates)
+var_sorted_value() {
+  uniq_sort $(var_value $1)
+}
+
+# Print the definition of a given variable in a GNU Make build file.
+# $1: Variable name (e.g. common_src_files)
+# $2+: Variable value (e.g. list of sources)
+print_vardef_in_mk() {
+  declare -r varname=$1
+  shift
+  if [ -z "$1" ]; then
+    echo "$varname :="
+  else
+    echo "$varname := \\"
+    for src; do
+      echo "  $src \\"
+    done
+  fi
+  echo ""
+}
+
+# Same as print_vardef_in_mk, but print a CFLAGS definition from
+# a list of compiler defines.
+# $1: Variable name (e.g. common_c_flags)
+# $2: List of defines (e.g. SCRYPT_NO_DONKEYS ...)
+print_defines_in_mk() {
+  declare -r varname=$1
+  shift
+  if [ -z "$1" ]; then
+    echo "$varname :="
+  else
+    echo "$varname := \\"
+    for def; do
+    echo "  -D$def \\"
+    done
+  fi
+  echo ""
+}
+
+# Generate a configuration file like Scrypt-config.mk
+# This uses variable definitions from scrypt.config to build a config
+# file that can compute the list of target- and host-specific sources /
+# compiler flags for a given component.
+#
+# $1: Target file name.  (e.g. Scrypt-config.mk)
+function generate_config_mk() {
+  declare -r output="$1"
+  declare -r all_archs="arm arm_neon x86 x86_64 mips"
+
+  echo "Generating $(basename $output)"
+  (
+    print_autogenerated_header
+    echo \
+"# Before including this file, the local Android.mk must define the following
+# variables:
+#
+#    local_c_flags
+#    local_c_includes
+#    local_additional_dependencies
+#
+# This script will define the following variables:
+#
+#    target_c_flags
+#    target_c_includes
+#    target_src_files
+#
+#    host_c_flags
+#    host_c_includes
+#    host_src_files
+#
+
+# Ensure these are empty.
+unknown_arch_c_flags :=
+unknown_arch_src_files :=
+unknown_arch_exclude_files :=
+
+"
+    common_defines=$(var_sorted_value SCRYPT_DEFINES)
+    print_defines_in_mk common_c_flags $common_defines
+
+    common_sources=$(var_sorted_value SCRYPT_SOURCES)
+    print_vardef_in_mk common_src_files $common_sources
+
+    common_includes=$(var_sorted_value SCRYPT_INCLUDES)
+    print_vardef_in_mk common_c_includes $common_includes
+
+    for arch in $all_archs; do
+      arch_defines=$(var_sorted_value SCRYPT_DEFINES_${arch})
+      print_defines_in_mk ${arch}_c_flags $arch_defines
+
+      arch_sources=$(var_sorted_value SCRYPT_SOURCES_${arch})
+      print_vardef_in_mk ${arch}_src_files $arch_sources
+
+      arch_exclude_sources=$(var_sorted_value SCRYPT_SOURCES_EXCLUDES_${arch})
+      print_vardef_in_mk ${arch}_exclude_files $arch_exclude_sources
+
+    done
+
+    echo "\
+target_arch := \$(TARGET_ARCH)
+ifeq (\$(target_arch)-\$(TARGET_HAS_BIGENDIAN),mips-true)
+target_arch := unknown_arch
+endif
+
+target_c_flags    := \$(common_c_flags) \$(\$(target_arch)_c_flags) \$(local_c_flags)
+target_c_includes := \$(addprefix external/scrypt/,\$(common_c_includes)) \$(local_c_includes)
+target_src_files  := \$(common_src_files) \$(\$(target_arch)_src_files)
+target_src_files  := \$(filter-out \$(\$(target_arch)_exclude_files), \$(target_src_files))
+
+# Hacks for ARM NEON support
+ifeq (\$(target_arch),arm)
+ifeq (\$(ARCH_ARM_HAVE_NEON),true)
+target_c_flags   += \$(arm_neon_c_flags)
+target_src_files += \$(arm_neon_src_files)
+target_src_files := \$(filter-out \$(arm_neon_exclude_files), \$(target_src_files))
+endif
+endif
+
+ifeq (\$(HOST_OS)-\$(HOST_ARCH),linux-x86)
+host_arch := x86
+else
+host_arch := unknown_arch
+endif
+
+host_c_flags    := \$(common_c_flags) \$(\$(host_arch)_c_flags) \$(local_c_flags)
+host_c_includes := \$(addprefix external/scrypt/,\$(common_c_includes)) \$(local_c_includes)
+host_src_files  := \$(common_src_files) \$(\$(host_arch)_src_files)
+host_src_files  := \$(filter-out \$(\$(host_arch)_exclude_files), \$(host_src_files))
+
+local_additional_dependencies += \$(LOCAL_PATH)/$(basename $output)
+"
+
+  ) > "$output"
+}
+
+function import() {
+  declare -r SCRYPT_SOURCE=$1
+
+  untar $SCRYPT_SOURCE readonly
+  applypatches $SCRYPT_DIR
+
+  cd $SCRYPT_DIR
+
+  generate_build_config_mk ../build-config.mk
+
+  touch ../MODULE_LICENSE_BSD_LIKE
+
+  cd ..
+
+  generate_config_mk Scrypt-config.mk
+
+  # Prune unnecessary sources
+  prune
+
+  NEEDED_SOURCES="$NEEDED_SOURCES"
+  for i in $NEEDED_SOURCES; do
+    echo "Updating $i"
+    rm -r $i
+    mv $SCRYPT_DIR/$i .
+  done
+
+  cleantar
+}
+
+function regenerate() {
+  declare -r patch=$1
+
+  generatepatch $patch
+}
+
+function generate() {
+  declare -r patch=$1
+  declare -r SCRYPT_SOURCE=$2
+
+  untar $SCRYPT_SOURCE
+  applypatches $SCRYPT_DIR_ORIG $patch
+  prune
+
+  for i in $NEEDED_SOURCES; do
+    echo "Restoring $i"
+    rm -r $SCRYPT_DIR/$i
+    cp -rf $i $SCRYPT_DIR/$i
+  done
+
+  generatepatch $patch
+  cleantar
+}
+
+# Find all files in a sub-directory that are encoded in ISO-8859
+# $1: Directory.
+# Out: list of files in $1 that are encoded as ISO-8859.
+function find_iso8859_files() {
+  find $1 -type f -print0 | xargs -0 file | fgrep "ISO-8859" | cut -d: -f1
+}
+
+# Convert all ISO-8859 files in a given subdirectory to UTF-8
+# $1: Directory name
+function convert_iso8859_to_utf8() {
+  declare -r iso_files=$(find_iso8859_files "$1")
+  for iso_file in $iso_files; do
+    iconv --from-code iso-8859-1 --to-code utf-8 $iso_file > $iso_file.tmp
+    rm -f $iso_file
+    mv $iso_file.tmp $iso_file
+  done
+}
+
+function untar() {
+  declare -r SCRYPT_SOURCE=$1
+  declare -r readonly=$2
+
+  # Remove old source
+  cleantar
+
+  # Process new source
+  tar -zxf $SCRYPT_SOURCE
+  convert_iso8859_to_utf8 $SCRYPT_DIR
+  cp -rfP $SCRYPT_DIR $SCRYPT_DIR_ORIG
+  if [ ! -z $readonly ]; then
+    find $SCRYPT_DIR_ORIG -type f -print0 | xargs -0 chmod a-w
+  fi
+}
+
+function prune() {
+  echo "Removing $UNNEEDED_SOURCES"
+  (cd $SCRYPT_DIR_ORIG && rm -rf $UNNEEDED_SOURCES)
+  (cd $SCRYPT_DIR      && rm -r  $UNNEEDED_SOURCES)
+}
+
+function cleantar() {
+  rm -rf $SCRYPT_DIR_ORIG
+  rm -rf $SCRYPT_DIR
+}
+
+function applypatches () {
+  declare -r dir=$1
+  declare -r skip_patch=$2
+
+  cd $dir
+
+  # Apply appropriate patches
+  for i in $SCRYPT_PATCHES; do
+    if [ ! "$skip_patch" = "patches/$i" ]; then
+      echo "Applying patch $i"
+      patch -p1 --merge < ../patches/$i || die "Could not apply patches/$i. Fix source and run: $0 regenerate patches/$i"
+    else
+      echo "Skiping patch $i"
+    fi
+
+  done
+
+  # Cleanup patch output
+  find . \( -type f -o -type l \) -name "*.orig" -print0 | xargs -0 rm -f
+
+  cd ..
+}
+
+function generatepatch() {
+  declare -r patch=$1
+
+  # Cleanup stray files before generating patch
+  find $SCRYPT_DIR -type f -name "*.orig" -print0 | xargs -0 rm -f
+  find $SCRYPT_DIR -type f -name "*~" -print0 | xargs -0 rm -f
+
+  declare -r variable_name=SCRYPT_PATCHES_`basename $patch .patch | sed s/-/_/`_SOURCES
+  # http://tldp.org/LDP/abs/html/ivr.html
+  eval declare -r sources=\$$variable_name
+  rm -f $patch
+  touch $patch
+  for i in $sources; do
+    LC_ALL=C TZ=UTC0 diff -aup $SCRYPT_DIR_ORIG/$i $SCRYPT_DIR/$i >> $patch && die "ERROR: No diff for patch $path in file $i"
+  done
+  echo "Generated patch $patch"
+  echo "NOTE To make sure there are not unwanted changes from conflicting patches, be sure to review the generated patch."
+}
+
+main $@
diff --git a/crypto/scrypt/lib/README b/crypto/scrypt/lib/README
new file mode 100644
index 0000000..3bb211e
--- /dev/null
+++ b/crypto/scrypt/lib/README
@@ -0,0 +1,6 @@
+The source code under this directory is taken from the client for the
+Tarsnap online backup system (and released under the 2-clause BSD license
+with permission of the author); keeping this code in sync with the Tarsnap
+code is highly desirable and explains why there is some functionality
+included here which is not actually used by the scrypt file encryption
+utility.
diff --git a/crypto/scrypt/lib/crypto/crypto_scrypt-neon-salsa208.h b/crypto/scrypt/lib/crypto/crypto_scrypt-neon-salsa208.h
new file mode 100644
index 0000000..a3b1019
--- /dev/null
+++ b/crypto/scrypt/lib/crypto/crypto_scrypt-neon-salsa208.h
@@ -0,0 +1,120 @@
+/*
+ * version 20110505
+ * D. J. Bernstein
+ * Public domain.
+ *
+ * Based on crypto_core/salsa208/armneon/core.c from SUPERCOP 20130419
+ */
+
+#define ROUNDS 8
+static void
+salsa20_8_intrinsic(void * input)
+{
+  int i;
+
+  const uint32x4_t abab = {-1,0,-1,0};
+
+  /*
+   * This is modified since we only have one argument. Usually you'd rearrange
+   * the constant, key, and input bytes, but we just have one linear array to
+   * rearrange which is a bit easier.
+   */
+
+  /*
+   * Change the input to be diagonals as if it's a 4x4 matrix of 32-bit values.
+   */
+  uint32x4_t x0x5x10x15;
+  uint32x4_t x12x1x6x11;
+  uint32x4_t x8x13x2x7;
+  uint32x4_t x4x9x14x3;
+
+  uint32x4_t x0x1x10x11;
+  uint32x4_t x12x13x6x7;
+  uint32x4_t x8x9x2x3;
+  uint32x4_t x4x5x14x15;
+
+  uint32x4_t x0x1x2x3;
+  uint32x4_t x4x5x6x7;
+  uint32x4_t x8x9x10x11;
+  uint32x4_t x12x13x14x15;
+
+  x0x1x2x3 = vld1q_u8((uint8_t *) input);
+  x4x5x6x7 = vld1q_u8(16 + (uint8_t *) input);
+  x8x9x10x11 = vld1q_u8(32 + (uint8_t *) input);
+  x12x13x14x15 = vld1q_u8(48 + (uint8_t *) input);
+
+  x0x1x10x11 = vcombine_u32(vget_low_u32(x0x1x2x3), vget_high_u32(x8x9x10x11));
+  x4x5x14x15 = vcombine_u32(vget_low_u32(x4x5x6x7), vget_high_u32(x12x13x14x15));
+  x8x9x2x3 = vcombine_u32(vget_low_u32(x8x9x10x11), vget_high_u32(x0x1x2x3));
+  x12x13x6x7 = vcombine_u32(vget_low_u32(x12x13x14x15), vget_high_u32(x4x5x6x7));
+
+  x0x5x10x15 = vbslq_u32(abab,x0x1x10x11,x4x5x14x15);
+  x8x13x2x7 = vbslq_u32(abab,x8x9x2x3,x12x13x6x7);
+  x4x9x14x3 = vbslq_u32(abab,x4x5x14x15,x8x9x2x3);
+  x12x1x6x11 = vbslq_u32(abab,x12x13x6x7,x0x1x10x11);
+
+  uint32x4_t start0 = x0x5x10x15;
+  uint32x4_t start1 = x12x1x6x11;
+  uint32x4_t start3 = x4x9x14x3;
+  uint32x4_t start2 = x8x13x2x7;
+
+  /* From here on this should be the same as the SUPERCOP version. */
+
+  uint32x4_t diag0 = start0;
+  uint32x4_t diag1 = start1;
+  uint32x4_t diag2 = start2;
+  uint32x4_t diag3 = start3;
+
+  uint32x4_t a0;
+  uint32x4_t a1;
+  uint32x4_t a2;
+  uint32x4_t a3;
+
+  for (i = ROUNDS;i > 0;i -= 2) {
+    a0 = diag1 + diag0;
+    diag3 ^= vsriq_n_u32(vshlq_n_u32(a0,7),a0,25);
+    a1 = diag0 + diag3;
+    diag2 ^= vsriq_n_u32(vshlq_n_u32(a1,9),a1,23);
+    a2 = diag3 + diag2;
+    diag1 ^= vsriq_n_u32(vshlq_n_u32(a2,13),a2,19);
+    a3 = diag2 + diag1;
+    diag0 ^= vsriq_n_u32(vshlq_n_u32(a3,18),a3,14);
+
+    diag3 = vextq_u32(diag3,diag3,3);
+    diag2 = vextq_u32(diag2,diag2,2);
+    diag1 = vextq_u32(diag1,diag1,1);
+
+    a0 = diag3 + diag0;
+    diag1 ^= vsriq_n_u32(vshlq_n_u32(a0,7),a0,25);
+    a1 = diag0 + diag1;
+    diag2 ^= vsriq_n_u32(vshlq_n_u32(a1,9),a1,23);
+    a2 = diag1 + diag2;
+    diag3 ^= vsriq_n_u32(vshlq_n_u32(a2,13),a2,19);
+    a3 = diag2 + diag3;
+    diag0 ^= vsriq_n_u32(vshlq_n_u32(a3,18),a3,14);
+
+    diag1 = vextq_u32(diag1,diag1,3);
+    diag2 = vextq_u32(diag2,diag2,2);
+    diag3 = vextq_u32(diag3,diag3,1);
+  }
+
+  x0x5x10x15 = diag0 + start0;
+  x12x1x6x11 = diag1 + start1;
+  x8x13x2x7 = diag2 + start2;
+  x4x9x14x3 = diag3 + start3;
+
+  x0x1x10x11 = vbslq_u32(abab,x0x5x10x15,x12x1x6x11);
+  x12x13x6x7 = vbslq_u32(abab,x12x1x6x11,x8x13x2x7);
+  x8x9x2x3 = vbslq_u32(abab,x8x13x2x7,x4x9x14x3);
+  x4x5x14x15 = vbslq_u32(abab,x4x9x14x3,x0x5x10x15);
+
+  x0x1x2x3 = vcombine_u32(vget_low_u32(x0x1x10x11),vget_high_u32(x8x9x2x3));
+  x4x5x6x7 = vcombine_u32(vget_low_u32(x4x5x14x15),vget_high_u32(x12x13x6x7));
+  x8x9x10x11 = vcombine_u32(vget_low_u32(x8x9x2x3),vget_high_u32(x0x1x10x11));
+  x12x13x14x15 = vcombine_u32(vget_low_u32(x12x13x6x7),vget_high_u32(x4x5x14x15));
+
+  vst1q_u8((uint8_t *) input,(uint8x16_t) x0x1x2x3);
+  vst1q_u8(16 + (uint8_t *) input,(uint8x16_t) x4x5x6x7);
+  vst1q_u8(32 + (uint8_t *) input,(uint8x16_t) x8x9x10x11);
+  vst1q_u8(48 + (uint8_t *) input,(uint8x16_t) x12x13x14x15);
+}
diff --git a/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c b/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c
new file mode 100644
index 0000000..158bf96
--- /dev/null
+++ b/crypto/scrypt/lib/crypto/crypto_scrypt-neon.c
@@ -0,0 +1,302 @@
+/*-
+ * Copyright 2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This file was originally written by Colin Percival as part of the Tarsnap
+ * online backup system.
+ */
+#include "scrypt_platform.h"
+
+#include <arm_neon.h>
+
+#include <errno.h>
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef USE_OPENSSL_PBKDF2
+#include <openssl/evp.h>
+#else
+#include "sha256.h"
+#endif
+#include "sysendian.h"
+
+#include "crypto_scrypt.h"
+
+#include "crypto_scrypt-neon-salsa208.h"
+
+static void blkcpy(void *, void *, size_t);
+static void blkxor(void *, void *, size_t);
+void crypto_core_salsa208_armneon2(void *);
+static void blockmix_salsa8(uint8x16_t *, uint8x16_t *, uint8x16_t *, size_t);
+static uint64_t integerify(void *, size_t);
+static void smix(uint8_t *, size_t, uint64_t, void *, void *);
+
+static void
+blkcpy(void * dest, void * src, size_t len)
+{
+	uint8x16_t * D = dest;
+	uint8x16_t * S = src;
+	size_t L = len / 16;
+	size_t i;
+
+	for (i = 0; i < L; i++)
+		D[i] = S[i];
+}
+
+static void
+blkxor(void * dest, void * src, size_t len)
+{
+	uint8x16_t * D = dest;
+	uint8x16_t * S = src;
+	size_t L = len / 16;
+	size_t i;
+
+	for (i = 0; i < L; i++)
+		D[i] = veorq_u8(D[i], S[i]);
+}
+
+/**
+ * blockmix_salsa8(B, Y, r):
+ * Compute B = BlockMix_{salsa20/8, r}(B).  The input B must be 128r bytes in
+ * length; the temporary space Y must also be the same size.
+ */
+static void
+blockmix_salsa8(uint8x16_t * Bin, uint8x16_t * Bout, uint8x16_t * X, size_t r)
+{
+	size_t i;
+
+	/* 1: X <-- B_{2r - 1} */
+	blkcpy(X, &Bin[8 * r - 4], 64);
+
+	/* 2: for i = 0 to 2r - 1 do */
+	for (i = 0; i < r; i++) {
+		/* 3: X <-- H(X \xor B_i) */
+		blkxor(X, &Bin[i * 8], 64);
+                salsa20_8_intrinsic((void *) X);
+
+		/* 4: Y_i <-- X */
+		/* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
+		blkcpy(&Bout[i * 4], X, 64);
+
+		/* 3: X <-- H(X \xor B_i) */
+		blkxor(X, &Bin[i * 8 + 4], 64);
+                salsa20_8_intrinsic((void *) X);
+
+		/* 4: Y_i <-- X */
+		/* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
+		blkcpy(&Bout[(r + i) * 4], X, 64);
+	}
+}
+
+/**
+ * integerify(B, r):
+ * Return the result of parsing B_{2r-1} as a little-endian integer.
+ */
+static uint64_t
+integerify(void * B, size_t r)
+{
+	uint8_t * X = (void*)((uintptr_t)(B) + (2 * r - 1) * 64);
+
+	return (le64dec(X));
+}
+
+/**
+ * smix(B, r, N, V, XY):
+ * Compute B = SMix_r(B, N).  The input B must be 128r bytes in length; the
+ * temporary storage V must be 128rN bytes in length; the temporary storage
+ * XY must be 256r bytes in length.  The value N must be a power of 2.
+ */
+static void
+smix(uint8_t * B, size_t r, uint64_t N, void * V, void * XY)
+{
+	uint8x16_t * X = XY;
+	uint8x16_t * Y = (void *)((uintptr_t)(XY) + 128 * r);
+        uint8x16_t * Z = (void *)((uintptr_t)(XY) + 256 * r);
+	uint64_t i, j;
+
+	/* 1: X <-- B */
+	blkcpy(X, B, 128 * r);
+
+	/* 2: for i = 0 to N - 1 do */
+	for (i = 0; i < N; i += 2) {
+		/* 3: V_i <-- X */
+		blkcpy((void *)((uintptr_t)(V) + i * 128 * r), X, 128 * r);
+
+		/* 4: X <-- H(X) */
+		blockmix_salsa8(X, Y, Z, r);
+
+		/* 3: V_i <-- X */
+		blkcpy((void *)((uintptr_t)(V) + (i + 1) * 128 * r),
+		    Y, 128 * r);
+
+		/* 4: X <-- H(X) */
+		blockmix_salsa8(Y, X, Z, r);
+	}
+
+	/* 6: for i = 0 to N - 1 do */
+	for (i = 0; i < N; i += 2) {
+		/* 7: j <-- Integerify(X) mod N */
+		j = integerify(X, r) & (N - 1);
+
+		/* 8: X <-- H(X \xor V_j) */
+		blkxor(X, (void *)((uintptr_t)(V) + j * 128 * r), 128 * r);
+		blockmix_salsa8(X, Y, Z, r);
+
+		/* 7: j <-- Integerify(X) mod N */
+		j = integerify(Y, r) & (N - 1);
+
+		/* 8: X <-- H(X \xor V_j) */
+		blkxor(Y, (void *)((uintptr_t)(V) + j * 128 * r), 128 * r);
+		blockmix_salsa8(Y, X, Z, r);
+	}
+
+	/* 10: B' <-- X */
+	blkcpy(B, X, 128 * r);
+}
+
+/**
+ * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen):
+ * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r,
+ * p, buflen) and write the result into buf.  The parameters r, p, and buflen
+ * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32.  The parameter N
+ * must be a power of 2.
+ *
+ * Return 0 on success; or -1 on error.
+ */
+int
+crypto_scrypt(const uint8_t * passwd, size_t passwdlen,
+    const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p,
+    uint8_t * buf, size_t buflen)
+{
+	void * B0, * V0, * XY0;
+	uint8_t * B;
+	uint32_t * V;
+	uint32_t * XY;
+	uint32_t i;
+
+	/* Sanity-check parameters. */
+#if SIZE_MAX > UINT32_MAX
+	if (buflen > (((uint64_t)(1) << 32) - 1) * 32) {
+		errno = EFBIG;
+		goto err0;
+	}
+#endif
+	if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) {
+		errno = EFBIG;
+		goto err0;
+	}
+	if (((N & (N - 1)) != 0) || (N == 0)) {
+		errno = EINVAL;
+		goto err0;
+	}
+	if ((r > SIZE_MAX / 128 / p) ||
+#if SIZE_MAX / 256 <= UINT32_MAX
+	    (r > SIZE_MAX / 256) ||
+#endif
+	    (N > SIZE_MAX / 128 / r)) {
+		errno = ENOMEM;
+		goto err0;
+	}
+
+	/* Allocate memory. */
+#ifdef HAVE_POSIX_MEMALIGN
+	if ((errno = posix_memalign(&B0, 64, 128 * r * p)) != 0)
+		goto err0;
+	B = (uint8_t *)(B0);
+	if ((errno = posix_memalign(&XY0, 64, 256 * r + 64)) != 0)
+		goto err1;
+	XY = (uint32_t *)(XY0);
+#ifndef MAP_ANON
+	if ((errno = posix_memalign(&V0, 64, 128 * r * N)) != 0)
+		goto err2;
+	V = (uint32_t *)(V0);
+#endif
+#else
+	if ((B0 = malloc(128 * r * p + 63)) == NULL)
+		goto err0;
+	B = (uint8_t *)(((uintptr_t)(B0) + 63) & ~ (uintptr_t)(63));
+	if ((XY0 = malloc(256 * r + 64 + 63)) == NULL)
+		goto err1;
+	XY = (uint32_t *)(((uintptr_t)(XY0) + 63) & ~ (uintptr_t)(63));
+#ifndef MAP_ANON
+	if ((V0 = malloc(128 * r * N + 63)) == NULL)
+		goto err2;
+	V = (uint32_t *)(((uintptr_t)(V0) + 63) & ~ (uintptr_t)(63));
+#endif
+#endif
+#ifdef MAP_ANON
+	if ((V0 = mmap(NULL, 128 * r * N, PROT_READ | PROT_WRITE,
+#ifdef MAP_NOCORE
+	    MAP_ANON | MAP_PRIVATE | MAP_NOCORE,
+#else
+	    MAP_ANON | MAP_PRIVATE,
+#endif
+	    -1, 0)) == MAP_FAILED)
+		goto err2;
+	V = (uint32_t *)(V0);
+#endif
+
+	/* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */
+#ifdef USE_OPENSSL_PBKDF2
+	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, salt, saltlen, 1, EVP_sha256(), p * 128 * r, B);
+#else
+	PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, p * 128 * r);
+#endif
+
+	/* 2: for i = 0 to p - 1 do */
+	for (i = 0; i < p; i++) {
+		/* 3: B_i <-- MF(B_i, N) */
+		smix(&B[i * 128 * r], r, N, V, XY);
+	}
+
+	/* 5: DK <-- PBKDF2(P, B, 1, dkLen) */
+#ifdef USE_OPENSSL_PBKDF2
+	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, B, p * 128 * r, 1, EVP_sha256(), buflen, buf);
+#else
+	PBKDF2_SHA256(passwd, passwdlen, B, p * 128 * r, 1, buf, buflen);
+#endif
+
+	/* Free memory. */
+#ifdef MAP_ANON
+	if (munmap(V0, 128 * r * N))
+		goto err2;
+#else
+	free(V0);
+#endif
+	free(XY0);
+	free(B0);
+
+	/* Success! */
+	return (0);
+
+err2:
+	free(XY0);
+err1:
+	free(B0);
+err0:
+	/* Failure! */
+	return (-1);
+}
diff --git a/crypto/scrypt/lib/crypto/crypto_scrypt-ref.c b/crypto/scrypt/lib/crypto/crypto_scrypt-ref.c
new file mode 100644
index 0000000..abe23ea
--- /dev/null
+++ b/crypto/scrypt/lib/crypto/crypto_scrypt-ref.c
@@ -0,0 +1,296 @@
+/*-
+ * Copyright 2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This file was originally written by Colin Percival as part of the Tarsnap
+ * online backup system.
+ */
+#include "scrypt_platform.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef USE_OPENSSL_PBKDF2
+#include <openssl/evp.h>
+#else
+#include "sha256.h"
+#endif
+#include "sysendian.h"
+
+#include "crypto_scrypt.h"
+
+static void blkcpy(uint8_t *, uint8_t *, size_t);
+static void blkxor(uint8_t *, uint8_t *, size_t);
+static void salsa20_8(uint8_t[64]);
+static void blockmix_salsa8(uint8_t *, uint8_t *, size_t);
+static uint64_t integerify(uint8_t *, size_t);
+static void smix(uint8_t *, size_t, uint64_t, uint8_t *, uint8_t *);
+
+static void
+blkcpy(uint8_t * dest, uint8_t * src, size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len; i++)
+		dest[i] = src[i];
+}
+
+static void
+blkxor(uint8_t * dest, uint8_t * src, size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len; i++)
+		dest[i] ^= src[i];
+}
+
+/**
+ * salsa20_8(B):
+ * Apply the salsa20/8 core to the provided block.
+ */
+static void
+salsa20_8(uint8_t B[64])
+{
+	uint32_t B32[16];
+	uint32_t x[16];
+	size_t i;
+
+	/* Convert little-endian values in. */
+	for (i = 0; i < 16; i++)
+		B32[i] = le32dec(&B[i * 4]);
+
+	/* Compute x = doubleround^4(B32). */
+	for (i = 0; i < 16; i++)
+		x[i] = B32[i];
+	for (i = 0; i < 8; i += 2) {
+#define R(a,b) (((a) << (b)) | ((a) >> (32 - (b))))
+		/* Operate on columns. */
+		x[ 4] ^= R(x[ 0]+x[12], 7);  x[ 8] ^= R(x[ 4]+x[ 0], 9);
+		x[12] ^= R(x[ 8]+x[ 4],13);  x[ 0] ^= R(x[12]+x[ 8],18);
+
+		x[ 9] ^= R(x[ 5]+x[ 1], 7);  x[13] ^= R(x[ 9]+x[ 5], 9);
+		x[ 1] ^= R(x[13]+x[ 9],13);  x[ 5] ^= R(x[ 1]+x[13],18);
+
+		x[14] ^= R(x[10]+x[ 6], 7);  x[ 2] ^= R(x[14]+x[10], 9);
+		x[ 6] ^= R(x[ 2]+x[14],13);  x[10] ^= R(x[ 6]+x[ 2],18);
+
+		x[ 3] ^= R(x[15]+x[11], 7);  x[ 7] ^= R(x[ 3]+x[15], 9);
+		x[11] ^= R(x[ 7]+x[ 3],13);  x[15] ^= R(x[11]+x[ 7],18);
+
+		/* Operate on rows. */
+		x[ 1] ^= R(x[ 0]+x[ 3], 7);  x[ 2] ^= R(x[ 1]+x[ 0], 9);
+		x[ 3] ^= R(x[ 2]+x[ 1],13);  x[ 0] ^= R(x[ 3]+x[ 2],18);
+
+		x[ 6] ^= R(x[ 5]+x[ 4], 7);  x[ 7] ^= R(x[ 6]+x[ 5], 9);
+		x[ 4] ^= R(x[ 7]+x[ 6],13);  x[ 5] ^= R(x[ 4]+x[ 7],18);
+
+		x[11] ^= R(x[10]+x[ 9], 7);  x[ 8] ^= R(x[11]+x[10], 9);
+		x[ 9] ^= R(x[ 8]+x[11],13);  x[10] ^= R(x[ 9]+x[ 8],18);
+
+		x[12] ^= R(x[15]+x[14], 7);  x[13] ^= R(x[12]+x[15], 9);
+		x[14] ^= R(x[13]+x[12],13);  x[15] ^= R(x[14]+x[13],18);
+#undef R
+	}
+
+	/* Compute B32 = B32 + x. */
+	for (i = 0; i < 16; i++)
+		B32[i] += x[i];
+
+	/* Convert little-endian values out. */
+	for (i = 0; i < 16; i++)
+		le32enc(&B[4 * i], B32[i]);
+}
+
+/**
+ * blockmix_salsa8(B, Y, r):
+ * Compute B = BlockMix_{salsa20/8, r}(B).  The input B must be 128r bytes in
+ * length; the temporary space Y must also be the same size.
+ */
+static void
+blockmix_salsa8(uint8_t * B, uint8_t * Y, size_t r)
+{
+	uint8_t X[64];
+	size_t i;
+
+	/* 1: X <-- B_{2r - 1} */
+	blkcpy(X, &B[(2 * r - 1) * 64], 64);
+
+	/* 2: for i = 0 to 2r - 1 do */
+	for (i = 0; i < 2 * r; i++) {
+		/* 3: X <-- H(X \xor B_i) */
+		blkxor(X, &B[i * 64], 64);
+		salsa20_8(X);
+
+		/* 4: Y_i <-- X */
+		blkcpy(&Y[i * 64], X, 64);
+	}
+
+	/* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
+	for (i = 0; i < r; i++)
+		blkcpy(&B[i * 64], &Y[(i * 2) * 64], 64);
+	for (i = 0; i < r; i++)
+		blkcpy(&B[(i + r) * 64], &Y[(i * 2 + 1) * 64], 64);
+}
+
+/**
+ * integerify(B, r):
+ * Return the result of parsing B_{2r-1} as a little-endian integer.
+ */
+static uint64_t
+integerify(uint8_t * B, size_t r)
+{
+	uint8_t * X = &B[(2 * r - 1) * 64];
+
+	return (le64dec(X));
+}
+
+/**
+ * smix(B, r, N, V, XY):
+ * Compute B = SMix_r(B, N).  The input B must be 128r bytes in length; the
+ * temporary storage V must be 128rN bytes in length; the temporary storage
+ * XY must be 256r bytes in length.  The value N must be a power of 2.
+ */
+static void
+smix(uint8_t * B, size_t r, uint64_t N, uint8_t * V, uint8_t * XY)
+{
+	uint8_t * X = XY;
+	uint8_t * Y = &XY[128 * r];
+	uint64_t i;
+	uint64_t j;
+
+	/* 1: X <-- B */
+	blkcpy(X, B, 128 * r);
+
+	/* 2: for i = 0 to N - 1 do */
+	for (i = 0; i < N; i++) {
+		/* 3: V_i <-- X */
+		blkcpy(&V[i * (128 * r)], X, 128 * r);
+
+		/* 4: X <-- H(X) */
+		blockmix_salsa8(X, Y, r);
+	}
+
+	/* 6: for i = 0 to N - 1 do */
+	for (i = 0; i < N; i++) {
+		/* 7: j <-- Integerify(X) mod N */
+		j = integerify(X, r) & (N - 1);
+
+		/* 8: X <-- H(X \xor V_j) */
+		blkxor(X, &V[j * (128 * r)], 128 * r);
+		blockmix_salsa8(X, Y, r);
+	}
+
+	/* 10: B' <-- X */
+	blkcpy(B, X, 128 * r);
+}
+
+/**
+ * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen):
+ * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r,
+ * p, buflen) and write the result into buf.  The parameters r, p, and buflen
+ * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32.  The parameter N
+ * must be a power of 2.
+ *
+ * Return 0 on success; or -1 on error.
+ */
+int
+crypto_scrypt(const uint8_t * passwd, size_t passwdlen,
+    const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p,
+    uint8_t * buf, size_t buflen)
+{
+	uint8_t * B;
+	uint8_t * V;
+	uint8_t * XY;
+	uint32_t i;
+
+	/* Sanity-check parameters. */
+#if SIZE_MAX > UINT32_MAX
+	if (buflen > (((uint64_t)(1) << 32) - 1) * 32) {
+		errno = EFBIG;
+		goto err0;
+	}
+#endif
+	if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) {
+		errno = EFBIG;
+		goto err0;
+	}
+	if (((N & (N - 1)) != 0) || (N == 0)) {
+		errno = EINVAL;
+		goto err0;
+	}
+	if ((r > SIZE_MAX / 128 / p) ||
+#if SIZE_MAX / 256 <= UINT32_MAX
+	    (r > SIZE_MAX / 256) ||
+#endif
+	    (N > SIZE_MAX / 128 / r)) {
+		errno = ENOMEM;
+		goto err0;
+	}
+
+	/* Allocate memory. */
+	if ((B = malloc(128 * r * p)) == NULL)
+		goto err0;
+	if ((XY = malloc(256 * r)) == NULL)
+		goto err1;
+	if ((V = malloc(128 * r * N)) == NULL)
+		goto err2;
+
+	/* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */
+#ifdef USE_OPENSSL_PBKDF2
+	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, salt, saltlen, 1, EVP_sha256(), p * 128 * r, B);
+#else
+	PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, p * 128 * r);
+#endif
+
+	/* 2: for i = 0 to p - 1 do */
+	for (i = 0; i < p; i++) {
+		/* 3: B_i <-- MF(B_i, N) */
+		smix(&B[i * 128 * r], r, N, V, XY);
+	}
+
+	/* 5: DK <-- PBKDF2(P, B, 1, dkLen) */
+#ifdef USE_OPENSSL_PBKDF2
+	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, B, p * 128 * r, 1, EVP_sha256(), buflen, buf);
+#else
+	PBKDF2_SHA256(passwd, passwdlen, B, p * 128 * r, 1, buf, buflen);
+#endif
+
+	/* Free memory. */
+	free(V);
+	free(XY);
+	free(B);
+
+	/* Success! */
+	return (0);
+
+err2:
+	free(XY);
+err1:
+	free(B);
+err0:
+	/* Failure! */
+	return (-1);
+}
diff --git a/crypto/scrypt/lib/crypto/crypto_scrypt-sse.c b/crypto/scrypt/lib/crypto/crypto_scrypt-sse.c
new file mode 100644
index 0000000..dd18f29
--- /dev/null
+++ b/crypto/scrypt/lib/crypto/crypto_scrypt-sse.c
@@ -0,0 +1,378 @@
+/*-
+ * Copyright 2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This file was originally written by Colin Percival as part of the Tarsnap
+ * online backup system.
+ */
+#include "scrypt_platform.h"
+
+#include <sys/types.h>
+#include <sys/mman.h>
+
+#include <emmintrin.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef USE_OPENSSL_PBKDF2
+#include <openssl/evp.h>
+#else
+#include "sha256.h"
+#endif
+#include "sysendian.h"
+
+#include "crypto_scrypt.h"
+
+static void blkcpy(void *, void *, size_t);
+static void blkxor(void *, void *, size_t);
+static void salsa20_8(__m128i *);
+static void blockmix_salsa8(__m128i *, __m128i *, __m128i *, size_t);
+static uint64_t integerify(void *, size_t);
+static void smix(uint8_t *, size_t, uint64_t, void *, void *);
+
+static void
+blkcpy(void * dest, void * src, size_t len)
+{
+	__m128i * D = dest;
+	__m128i * S = src;
+	size_t L = len / 16;
+	size_t i;
+
+	for (i = 0; i < L; i++)
+		D[i] = S[i];
+}
+
+static void
+blkxor(void * dest, void * src, size_t len)
+{
+	__m128i * D = dest;
+	__m128i * S = src;
+	size_t L = len / 16;
+	size_t i;
+
+	for (i = 0; i < L; i++)
+		D[i] = _mm_xor_si128(D[i], S[i]);
+}
+
+/**
+ * salsa20_8(B):
+ * Apply the salsa20/8 core to the provided block.
+ */
+static void
+salsa20_8(__m128i B[4])
+{
+	__m128i X0, X1, X2, X3;
+	__m128i T;
+	size_t i;
+
+	X0 = B[0];
+	X1 = B[1];
+	X2 = B[2];
+	X3 = B[3];
+
+	for (i = 0; i < 8; i += 2) {
+		/* Operate on "columns". */
+		T = _mm_add_epi32(X0, X3);
+		X1 = _mm_xor_si128(X1, _mm_slli_epi32(T, 7));
+		X1 = _mm_xor_si128(X1, _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X1, X0);
+		X2 = _mm_xor_si128(X2, _mm_slli_epi32(T, 9));
+		X2 = _mm_xor_si128(X2, _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X1);
+		X3 = _mm_xor_si128(X3, _mm_slli_epi32(T, 13));
+		X3 = _mm_xor_si128(X3, _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X3, X2);
+		X0 = _mm_xor_si128(X0, _mm_slli_epi32(T, 18));
+		X0 = _mm_xor_si128(X0, _mm_srli_epi32(T, 14));
+
+		/* Rearrange data. */
+		X1 = _mm_shuffle_epi32(X1, 0x93);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x39);
+
+		/* Operate on "rows". */
+		T = _mm_add_epi32(X0, X1);
+		X3 = _mm_xor_si128(X3, _mm_slli_epi32(T, 7));
+		X3 = _mm_xor_si128(X3, _mm_srli_epi32(T, 25));
+		T = _mm_add_epi32(X3, X0);
+		X2 = _mm_xor_si128(X2, _mm_slli_epi32(T, 9));
+		X2 = _mm_xor_si128(X2, _mm_srli_epi32(T, 23));
+		T = _mm_add_epi32(X2, X3);
+		X1 = _mm_xor_si128(X1, _mm_slli_epi32(T, 13));
+		X1 = _mm_xor_si128(X1, _mm_srli_epi32(T, 19));
+		T = _mm_add_epi32(X1, X2);
+		X0 = _mm_xor_si128(X0, _mm_slli_epi32(T, 18));
+		X0 = _mm_xor_si128(X0, _mm_srli_epi32(T, 14));
+
+		/* Rearrange data. */
+		X1 = _mm_shuffle_epi32(X1, 0x39);
+		X2 = _mm_shuffle_epi32(X2, 0x4E);
+		X3 = _mm_shuffle_epi32(X3, 0x93);
+	}
+
+	B[0] = _mm_add_epi32(B[0], X0);
+	B[1] = _mm_add_epi32(B[1], X1);
+	B[2] = _mm_add_epi32(B[2], X2);
+	B[3] = _mm_add_epi32(B[3], X3);
+}
+
+/**
+ * blockmix_salsa8(Bin, Bout, X, r):
+ * Compute Bout = BlockMix_{salsa20/8, r}(Bin).  The input Bin must be 128r
+ * bytes in length; the output Bout must also be the same size.  The
+ * temporary space X must be 64 bytes.
+ */
+static void
+blockmix_salsa8(__m128i * Bin, __m128i * Bout, __m128i * X, size_t r)
+{
+	size_t i;
+
+	/* 1: X <-- B_{2r - 1} */
+	blkcpy(X, &Bin[8 * r - 4], 64);
+
+	/* 2: for i = 0 to 2r - 1 do */
+	for (i = 0; i < r; i++) {
+		/* 3: X <-- H(X \xor B_i) */
+		blkxor(X, &Bin[i * 8], 64);
+		salsa20_8(X);
+
+		/* 4: Y_i <-- X */
+		/* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
+		blkcpy(&Bout[i * 4], X, 64);
+
+		/* 3: X <-- H(X \xor B_i) */
+		blkxor(X, &Bin[i * 8 + 4], 64);
+		salsa20_8(X);
+
+		/* 4: Y_i <-- X */
+		/* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
+		blkcpy(&Bout[(r + i) * 4], X, 64);
+	}
+}
+
+/**
+ * integerify(B, r):
+ * Return the result of parsing B_{2r-1} as a little-endian integer.
+ */
+static uint64_t
+integerify(void * B, size_t r)
+{
+	uint32_t * X = (void *)((uintptr_t)(B) + (2 * r - 1) * 64);
+
+	return (((uint64_t)(X[13]) << 32) + X[0]);
+}
+
+/**
+ * smix(B, r, N, V, XY):
+ * Compute B = SMix_r(B, N).  The input B must be 128r bytes in length;
+ * the temporary storage V must be 128rN bytes in length; the temporary
+ * storage XY must be 256r + 64 bytes in length.  The value N must be a
+ * power of 2 greater than 1.  The arrays B, V, and XY must be aligned to a
+ * multiple of 64 bytes.
+ */
+static void
+smix(uint8_t * B, size_t r, uint64_t N, void * V, void * XY)
+{
+	__m128i * X = XY;
+	__m128i * Y = (void *)((uintptr_t)(XY) + 128 * r);
+	__m128i * Z = (void *)((uintptr_t)(XY) + 256 * r);
+	uint32_t * X32 = (void *)X;
+	uint64_t i, j;
+	size_t k;
+
+	/* 1: X <-- B */
+	for (k = 0; k < 2 * r; k++) {
+		for (i = 0; i < 16; i++) {
+			X32[k * 16 + i] =
+			    le32dec(&B[(k * 16 + (i * 5 % 16)) * 4]);
+		}
+	}
+
+	/* 2: for i = 0 to N - 1 do */
+	for (i = 0; i < N; i += 2) {
+		/* 3: V_i <-- X */
+		blkcpy((void *)((uintptr_t)(V) + i * 128 * r), X, 128 * r);
+
+		/* 4: X <-- H(X) */
+		blockmix_salsa8(X, Y, Z, r);
+
+		/* 3: V_i <-- X */
+		blkcpy((void *)((uintptr_t)(V) + (i + 1) * 128 * r),
+		    Y, 128 * r);
+
+		/* 4: X <-- H(X) */
+		blockmix_salsa8(Y, X, Z, r);
+	}
+
+	/* 6: for i = 0 to N - 1 do */
+	for (i = 0; i < N; i += 2) {
+		/* 7: j <-- Integerify(X) mod N */
+		j = integerify(X, r) & (N - 1);
+
+		/* 8: X <-- H(X \xor V_j) */
+		blkxor(X, (void *)((uintptr_t)(V) + j * 128 * r), 128 * r);
+		blockmix_salsa8(X, Y, Z, r);
+
+		/* 7: j <-- Integerify(X) mod N */
+		j = integerify(Y, r) & (N - 1);
+
+		/* 8: X <-- H(X \xor V_j) */
+		blkxor(Y, (void *)((uintptr_t)(V) + j * 128 * r), 128 * r);
+		blockmix_salsa8(Y, X, Z, r);
+	}
+
+	/* 10: B' <-- X */
+	for (k = 0; k < 2 * r; k++) {
+		for (i = 0; i < 16; i++) {
+			le32enc(&B[(k * 16 + (i * 5 % 16)) * 4],
+			    X32[k * 16 + i]);
+		}
+	}
+}
+
+/**
+ * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen):
+ * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r,
+ * p, buflen) and write the result into buf.  The parameters r, p, and buflen
+ * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32.  The parameter N
+ * must be a power of 2 greater than 1.
+ *
+ * Return 0 on success; or -1 on error.
+ */
+int
+crypto_scrypt(const uint8_t * passwd, size_t passwdlen,
+    const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p,
+    uint8_t * buf, size_t buflen)
+{
+	void * B0, * V0, * XY0;
+	uint8_t * B;
+	uint32_t * V;
+	uint32_t * XY;
+	uint32_t i;
+
+	/* Sanity-check parameters. */
+#if SIZE_MAX > UINT32_MAX
+	if (buflen > (((uint64_t)(1) << 32) - 1) * 32) {
+		errno = EFBIG;
+		goto err0;
+	}
+#endif
+	if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) {
+		errno = EFBIG;
+		goto err0;
+	}
+	if (((N & (N - 1)) != 0) || (N == 0)) {
+		errno = EINVAL;
+		goto err0;
+	}
+	if ((r > SIZE_MAX / 128 / p) ||
+#if SIZE_MAX / 256 <= UINT32_MAX
+	    (r > (SIZE_MAX - 64) / 256) ||
+#endif
+	    (N > SIZE_MAX / 128 / r)) {
+		errno = ENOMEM;
+		goto err0;
+	}
+
+	/* Allocate memory. */
+#ifdef HAVE_POSIX_MEMALIGN
+	if ((errno = posix_memalign(&B0, 64, 128 * r * p)) != 0)
+		goto err0;
+	B = (uint8_t *)(B0);
+	if ((errno = posix_memalign(&XY0, 64, 256 * r + 64)) != 0)
+		goto err1;
+	XY = (uint32_t *)(XY0);
+#ifndef MAP_ANON
+	if ((errno = posix_memalign(&V0, 64, 128 * r * N)) != 0)
+		goto err2;
+	V = (uint32_t *)(V0);
+#endif
+#else
+	if ((B0 = malloc(128 * r * p + 63)) == NULL)
+		goto err0;
+	B = (uint8_t *)(((uintptr_t)(B0) + 63) & ~ (uintptr_t)(63));
+	if ((XY0 = malloc(256 * r + 64 + 63)) == NULL)
+		goto err1;
+	XY = (uint32_t *)(((uintptr_t)(XY0) + 63) & ~ (uintptr_t)(63));
+#ifndef MAP_ANON
+	if ((V0 = malloc(128 * r * N + 63)) == NULL)
+		goto err2;
+	V = (uint32_t *)(((uintptr_t)(V0) + 63) & ~ (uintptr_t)(63));
+#endif
+#endif
+#ifdef MAP_ANON
+	if ((V0 = mmap(NULL, 128 * r * N, PROT_READ | PROT_WRITE,
+#ifdef MAP_NOCORE
+	    MAP_ANON | MAP_PRIVATE | MAP_NOCORE,
+#else
+	    MAP_ANON | MAP_PRIVATE,
+#endif
+	    -1, 0)) == MAP_FAILED)
+		goto err2;
+	V = (uint32_t *)(V0);
+#endif
+
+	/* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */
+#ifdef USE_OPENSSL_PBKDF2
+	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, salt, saltlen, 1, EVP_sha256(), p * 128 * r, B);
+#else
+	PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, p * 128 * r);
+#endif
+
+	/* 2: for i = 0 to p - 1 do */
+	for (i = 0; i < p; i++) {
+		/* 3: B_i <-- MF(B_i, N) */
+		smix(&B[i * 128 * r], r, N, V, XY);
+	}
+
+	/* 5: DK <-- PBKDF2(P, B, 1, dkLen) */
+#ifdef USE_OPENSSL_PBKDF2
+	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, B, p * 128 * r, 1, EVP_sha256(), buflen, buf);
+#else
+	PBKDF2_SHA256(passwd, passwdlen, B, p * 128 * r, 1, buf, buflen);
+#endif
+
+	/* Free memory. */
+#ifdef MAP_ANON
+	if (munmap(V0, 128 * r * N))
+		goto err2;
+#else
+	free(V0);
+#endif
+	free(XY0);
+	free(B0);
+
+	/* Success! */
+	return (0);
+
+err2:
+	free(XY0);
+err1:
+	free(B0);
+err0:
+	/* Failure! */
+	return (-1);
+}
diff --git a/crypto/scrypt/lib/crypto/crypto_scrypt.h b/crypto/scrypt/lib/crypto/crypto_scrypt.h
new file mode 100644
index 0000000..f72e1f4
--- /dev/null
+++ b/crypto/scrypt/lib/crypto/crypto_scrypt.h
@@ -0,0 +1,46 @@
+/*-
+ * Copyright 2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This file was originally written by Colin Percival as part of the Tarsnap
+ * online backup system.
+ */
+#ifndef _CRYPTO_SCRYPT_H_
+#define _CRYPTO_SCRYPT_H_
+
+#include <stdint.h>
+
+/**
+ * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen):
+ * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r,
+ * p, buflen) and write the result into buf.  The parameters r, p, and buflen
+ * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32.  The parameter N
+ * must be a power of 2 greater than 1.
+ *
+ * Return 0 on success; or -1 on error.
+ */
+int crypto_scrypt(const uint8_t *, size_t, const uint8_t *, size_t, uint64_t,
+    uint32_t, uint32_t, uint8_t *, size_t);
+
+#endif /* !_CRYPTO_SCRYPT_H_ */
diff --git a/crypto/scrypt/lib/util/sysendian.h b/crypto/scrypt/lib/util/sysendian.h
new file mode 100644
index 0000000..62ef31a
--- /dev/null
+++ b/crypto/scrypt/lib/util/sysendian.h
@@ -0,0 +1,140 @@
+/*-
+ * Copyright 2007-2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This file was originally written by Colin Percival as part of the Tarsnap
+ * online backup system.
+ */
+#ifndef _SYSENDIAN_H_
+#define _SYSENDIAN_H_
+
+#include "scrypt_platform.h"
+
+/* If we don't have be64enc, the <sys/endian.h> we have isn't usable. */
+#if !HAVE_DECL_BE64ENC
+#undef HAVE_SYS_ENDIAN_H
+#endif
+
+#ifdef HAVE_SYS_ENDIAN_H
+
+#include <sys/endian.h>
+
+#else
+
+#include <stdint.h>
+
+static inline uint32_t
+be32dec(const void *pp)
+{
+	const uint8_t *p = (uint8_t const *)pp;
+
+	return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) +
+	    ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24));
+}
+
+static inline void
+be32enc(void *pp, uint32_t x)
+{
+	uint8_t * p = (uint8_t *)pp;
+
+	p[3] = x & 0xff;
+	p[2] = (x >> 8) & 0xff;
+	p[1] = (x >> 16) & 0xff;
+	p[0] = (x >> 24) & 0xff;
+}
+
+static inline uint64_t
+be64dec(const void *pp)
+{
+	const uint8_t *p = (uint8_t const *)pp;
+
+	return ((uint64_t)(p[7]) + ((uint64_t)(p[6]) << 8) +
+	    ((uint64_t)(p[5]) << 16) + ((uint64_t)(p[4]) << 24) +
+	    ((uint64_t)(p[3]) << 32) + ((uint64_t)(p[2]) << 40) +
+	    ((uint64_t)(p[1]) << 48) + ((uint64_t)(p[0]) << 56));
+}
+
+static inline void
+be64enc(void *pp, uint64_t x)
+{
+	uint8_t * p = (uint8_t *)pp;
+
+	p[7] = x & 0xff;
+	p[6] = (x >> 8) & 0xff;
+	p[5] = (x >> 16) & 0xff;
+	p[4] = (x >> 24) & 0xff;
+	p[3] = (x >> 32) & 0xff;
+	p[2] = (x >> 40) & 0xff;
+	p[1] = (x >> 48) & 0xff;
+	p[0] = (x >> 56) & 0xff;
+}
+
+static inline uint32_t
+le32dec(const void *pp)
+{
+	const uint8_t *p = (uint8_t const *)pp;
+
+	return ((uint32_t)(p[0]) + ((uint32_t)(p[1]) << 8) +
+	    ((uint32_t)(p[2]) << 16) + ((uint32_t)(p[3]) << 24));
+}
+
+static inline void
+le32enc(void *pp, uint32_t x)
+{
+	uint8_t * p = (uint8_t *)pp;
+
+	p[0] = x & 0xff;
+	p[1] = (x >> 8) & 0xff;
+	p[2] = (x >> 16) & 0xff;
+	p[3] = (x >> 24) & 0xff;
+}
+
+static inline uint64_t
+le64dec(const void *pp)
+{
+	const uint8_t *p = (uint8_t const *)pp;
+
+	return ((uint64_t)(p[0]) + ((uint64_t)(p[1]) << 8) +
+	    ((uint64_t)(p[2]) << 16) + ((uint64_t)(p[3]) << 24) +
+	    ((uint64_t)(p[4]) << 32) + ((uint64_t)(p[5]) << 40) +
+	    ((uint64_t)(p[6]) << 48) + ((uint64_t)(p[7]) << 56));
+}
+
+static inline void
+le64enc(void *pp, uint64_t x)
+{
+	uint8_t * p = (uint8_t *)pp;
+
+	p[0] = x & 0xff;
+	p[1] = (x >> 8) & 0xff;
+	p[2] = (x >> 16) & 0xff;
+	p[3] = (x >> 24) & 0xff;
+	p[4] = (x >> 32) & 0xff;
+	p[5] = (x >> 40) & 0xff;
+	p[6] = (x >> 48) & 0xff;
+	p[7] = (x >> 56) & 0xff;
+}
+#endif /* !HAVE_SYS_ENDIAN_H */
+
+#endif /* !_SYSENDIAN_H_ */
diff --git a/crypto/scrypt/patches/README b/crypto/scrypt/patches/README
new file mode 100644
index 0000000..353ddbb
--- /dev/null
+++ b/crypto/scrypt/patches/README
@@ -0,0 +1,11 @@
+bionic.patch:
+
+Allows scrypt to compile against bionic.
+
+use_openssl_pbkdf2.patch:
+
+Uses the PBKDF2 function from OpenSSL (it uses accelerated SHA256)
+
+arm-neon.patch:
+
+Adds NEON acceleration for the Salsa20/8 mixing function.
diff --git a/crypto/scrypt/patches/arm-neon.patch b/crypto/scrypt/patches/arm-neon.patch
new file mode 100644
index 0000000..02ff357
--- /dev/null
+++ b/crypto/scrypt/patches/arm-neon.patch
@@ -0,0 +1,436 @@
+diff --git a/lib/crypto/crypto_scrypt-neon-salsa208.h b/lib/crypto/crypto_scrypt-neon-salsa208.h
+new file mode 100644
+index 0000000..a3b1019
+--- /dev/null
++++ b/lib/crypto/crypto_scrypt-neon-salsa208.h
+@@ -0,0 +1,120 @@
++/*
++ * version 20110505
++ * D. J. Bernstein
++ * Public domain.
++ *
++ * Based on crypto_core/salsa208/armneon/core.c from SUPERCOP 20130419
++ */
++
++#define ROUNDS 8
++static void
++salsa20_8_intrinsic(void * input)
++{
++  int i;
++
++  const uint32x4_t abab = {-1,0,-1,0};
++
++  /*
++   * This is modified since we only have one argument. Usually you'd rearrange
++   * the constant, key, and input bytes, but we just have one linear array to
++   * rearrange which is a bit easier.
++   */
++
++  /*
++   * Change the input to be diagonals as if it's a 4x4 matrix of 32-bit values.
++   */
++  uint32x4_t x0x5x10x15;
++  uint32x4_t x12x1x6x11;
++  uint32x4_t x8x13x2x7;
++  uint32x4_t x4x9x14x3;
++
++  uint32x4_t x0x1x10x11;
++  uint32x4_t x12x13x6x7;
++  uint32x4_t x8x9x2x3;
++  uint32x4_t x4x5x14x15;
++
++  uint32x4_t x0x1x2x3;
++  uint32x4_t x4x5x6x7;
++  uint32x4_t x8x9x10x11;
++  uint32x4_t x12x13x14x15;
++
++  x0x1x2x3 = vld1q_u8((uint8_t *) input);
++  x4x5x6x7 = vld1q_u8(16 + (uint8_t *) input);
++  x8x9x10x11 = vld1q_u8(32 + (uint8_t *) input);
++  x12x13x14x15 = vld1q_u8(48 + (uint8_t *) input);
++
++  x0x1x10x11 = vcombine_u32(vget_low_u32(x0x1x2x3), vget_high_u32(x8x9x10x11));
++  x4x5x14x15 = vcombine_u32(vget_low_u32(x4x5x6x7), vget_high_u32(x12x13x14x15));
++  x8x9x2x3 = vcombine_u32(vget_low_u32(x8x9x10x11), vget_high_u32(x0x1x2x3));
++  x12x13x6x7 = vcombine_u32(vget_low_u32(x12x13x14x15), vget_high_u32(x4x5x6x7));
++
++  x0x5x10x15 = vbslq_u32(abab,x0x1x10x11,x4x5x14x15);
++  x8x13x2x7 = vbslq_u32(abab,x8x9x2x3,x12x13x6x7);
++  x4x9x14x3 = vbslq_u32(abab,x4x5x14x15,x8x9x2x3);
++  x12x1x6x11 = vbslq_u32(abab,x12x13x6x7,x0x1x10x11);
++
++  uint32x4_t start0 = x0x5x10x15;
++  uint32x4_t start1 = x12x1x6x11;
++  uint32x4_t start3 = x4x9x14x3;
++  uint32x4_t start2 = x8x13x2x7;
++
++  /* From here on this should be the same as the SUPERCOP version. */
++
++  uint32x4_t diag0 = start0;
++  uint32x4_t diag1 = start1;
++  uint32x4_t diag2 = start2;
++  uint32x4_t diag3 = start3;
++
++  uint32x4_t a0;
++  uint32x4_t a1;
++  uint32x4_t a2;
++  uint32x4_t a3;
++
++  for (i = ROUNDS;i > 0;i -= 2) {
++    a0 = diag1 + diag0;
++    diag3 ^= vsriq_n_u32(vshlq_n_u32(a0,7),a0,25);
++    a1 = diag0 + diag3;
++    diag2 ^= vsriq_n_u32(vshlq_n_u32(a1,9),a1,23);
++    a2 = diag3 + diag2;
++    diag1 ^= vsriq_n_u32(vshlq_n_u32(a2,13),a2,19);
++    a3 = diag2 + diag1;
++    diag0 ^= vsriq_n_u32(vshlq_n_u32(a3,18),a3,14);
++
++    diag3 = vextq_u32(diag3,diag3,3);
++    diag2 = vextq_u32(diag2,diag2,2);
++    diag1 = vextq_u32(diag1,diag1,1);
++
++    a0 = diag3 + diag0;
++    diag1 ^= vsriq_n_u32(vshlq_n_u32(a0,7),a0,25);
++    a1 = diag0 + diag1;
++    diag2 ^= vsriq_n_u32(vshlq_n_u32(a1,9),a1,23);
++    a2 = diag1 + diag2;
++    diag3 ^= vsriq_n_u32(vshlq_n_u32(a2,13),a2,19);
++    a3 = diag2 + diag3;
++    diag0 ^= vsriq_n_u32(vshlq_n_u32(a3,18),a3,14);
++
++    diag1 = vextq_u32(diag1,diag1,3);
++    diag2 = vextq_u32(diag2,diag2,2);
++    diag3 = vextq_u32(diag3,diag3,1);
++  }
++
++  x0x5x10x15 = diag0 + start0;
++  x12x1x6x11 = diag1 + start1;
++  x8x13x2x7 = diag2 + start2;
++  x4x9x14x3 = diag3 + start3;
++
++  x0x1x10x11 = vbslq_u32(abab,x0x5x10x15,x12x1x6x11);
++  x12x13x6x7 = vbslq_u32(abab,x12x1x6x11,x8x13x2x7);
++  x8x9x2x3 = vbslq_u32(abab,x8x13x2x7,x4x9x14x3);
++  x4x5x14x15 = vbslq_u32(abab,x4x9x14x3,x0x5x10x15);
++
++  x0x1x2x3 = vcombine_u32(vget_low_u32(x0x1x10x11),vget_high_u32(x8x9x2x3));
++  x4x5x6x7 = vcombine_u32(vget_low_u32(x4x5x14x15),vget_high_u32(x12x13x6x7));
++  x8x9x10x11 = vcombine_u32(vget_low_u32(x8x9x2x3),vget_high_u32(x0x1x10x11));
++  x12x13x14x15 = vcombine_u32(vget_low_u32(x12x13x6x7),vget_high_u32(x4x5x14x15));
++
++  vst1q_u8((uint8_t *) input,(uint8x16_t) x0x1x2x3);
++  vst1q_u8(16 + (uint8_t *) input,(uint8x16_t) x4x5x6x7);
++  vst1q_u8(32 + (uint8_t *) input,(uint8x16_t) x8x9x10x11);
++  vst1q_u8(48 + (uint8_t *) input,(uint8x16_t) x12x13x14x15);
++}
+diff --git a/lib/crypto/crypto_scrypt-neon.c b/lib/crypto/crypto_scrypt-neon.c
+new file mode 100644
+index 0000000..a3bf052
+--- /dev/null
++++ b/lib/crypto/crypto_scrypt-neon.c
+@@ -0,0 +1,304 @@
++/*-
++ * Copyright 2009 Colin Percival
++ * All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ *    notice, this list of conditions and the following disclaimer.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ *    notice, this list of conditions and the following disclaimer in the
++ *    documentation and/or other materials provided with the distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
++ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
++ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
++ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
++ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
++ * SUCH DAMAGE.
++ *
++ * This file was originally written by Colin Percival as part of the Tarsnap
++ * online backup system.
++ */
++#include "scrypt_platform.h"
++
++#include <arm_neon.h>
++
++#include <errno.h>
++#include <stdint.h>
++#include <limits.h>
++#include <stdlib.h>
++#include <string.h>
++
++#ifdef USE_OPENSSL_PBKDF2
++#include <openssl/evp.h>
++#else
++#include "sha256.h"
++#endif
++#include "sysendian.h"
++
++#include "crypto_scrypt.h"
++
++#include "crypto_scrypt-neon-salsa208.h"
++
++static void blkcpy(void *, void *, size_t);
++static void blkxor(void *, void *, size_t);
++void crypto_core_salsa208_armneon2(void *);
++static void blockmix_salsa8(uint8x16_t *, uint8x16_t *, uint8x16_t *, size_t);
++static uint64_t integerify(void *, size_t);
++static void smix(uint8_t *, size_t, uint64_t, void *, void *);
++
++static void
++blkcpy(void * dest, void * src, size_t len)
++{
++	uint8x16_t * D = dest;
++	uint8x16_t * S = src;
++	size_t L = len / 16;
++	size_t i;
++
++	for (i = 0; i < L; i++)
++		D[i] = S[i];
++}
++
++static void
++blkxor(void * dest, void * src, size_t len)
++{
++	uint8x16_t * D = dest;
++	uint8x16_t * S = src;
++	size_t L = len / 16;
++	size_t i;
++
++	for (i = 0; i < L; i++)
++		D[i] = veorq_u8(D[i], S[i]);
++}
++
++/**
++ * blockmix_salsa8(B, Y, r):
++ * Compute B = BlockMix_{salsa20/8, r}(B).  The input B must be 128r bytes in
++ * length; the temporary space Y must also be the same size.
++ */
++static void
++blockmix_salsa8(uint8x16_t * Bin, uint8x16_t * Bout, uint8x16_t * X, size_t r)
++{
++	size_t i;
++
++	/* 1: X <-- B_{2r - 1} */
++	blkcpy(X, &Bin[8 * r - 4], 64);
++
++	/* 2: for i = 0 to 2r - 1 do */
++	for (i = 0; i < r; i++) {
++		/* 3: X <-- H(X \xor B_i) */
++		blkxor(X, &Bin[i * 8], 64);
++                salsa20_8_intrinsic((void *) X);
++
++		/* 4: Y_i <-- X */
++		/* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
++		blkcpy(&Bout[i * 4], X, 64);
++
++		/* 3: X <-- H(X \xor B_i) */
++		blkxor(X, &Bin[i * 8 + 4], 64);
++                salsa20_8_intrinsic((void *) X);
++
++		/* 4: Y_i <-- X */
++		/* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
++		blkcpy(&Bout[(r + i) * 4], X, 64);
++	}
++}
++
++/**
++ * integerify(B, r):
++ * Return the result of parsing B_{2r-1} as a little-endian integer.
++ */
++static uint64_t
++integerify(void * B, size_t r)
++{
++	uint8_t * X = (void*)((uintptr_t)(B) + (2 * r - 1) * 64);
++
++	return (le64dec(X));
++}
++
++/**
++ * smix(B, r, N, V, XY):
++ * Compute B = SMix_r(B, N).  The input B must be 128r bytes in length; the
++ * temporary storage V must be 128rN bytes in length; the temporary storage
++ * XY must be 256r bytes in length.  The value N must be a power of 2.
++ */
++static void
++smix(uint8_t * B, size_t r, uint64_t N, void * V, void * XY)
++{
++	uint8x16_t * X = XY;
++	uint8x16_t * Y = (void *)((uintptr_t)(XY) + 128 * r);
++        uint8x16_t * Z = (void *)((uintptr_t)(XY) + 256 * r);
++        uint32_t * X32 = (void *)X;
++	uint64_t i, j;
++        size_t k;
++
++	/* 1: X <-- B */
++	blkcpy(X, B, 128 * r);
++
++	/* 2: for i = 0 to N - 1 do */
++	for (i = 0; i < N; i += 2) {
++		/* 3: V_i <-- X */
++		blkcpy((void *)((uintptr_t)(V) + i * 128 * r), X, 128 * r);
++
++		/* 4: X <-- H(X) */
++		blockmix_salsa8(X, Y, Z, r);
++
++		/* 3: V_i <-- X */
++		blkcpy((void *)((uintptr_t)(V) + (i + 1) * 128 * r),
++		    Y, 128 * r);
++
++		/* 4: X <-- H(X) */
++		blockmix_salsa8(Y, X, Z, r);
++	}
++
++	/* 6: for i = 0 to N - 1 do */
++	for (i = 0; i < N; i += 2) {
++		/* 7: j <-- Integerify(X) mod N */
++		j = integerify(X, r) & (N - 1);
++
++		/* 8: X <-- H(X \xor V_j) */
++		blkxor(X, (void *)((uintptr_t)(V) + j * 128 * r), 128 * r);
++		blockmix_salsa8(X, Y, Z, r);
++
++		/* 7: j <-- Integerify(X) mod N */
++		j = integerify(Y, r) & (N - 1);
++
++		/* 8: X <-- H(X \xor V_j) */
++		blkxor(Y, (void *)((uintptr_t)(V) + j * 128 * r), 128 * r);
++		blockmix_salsa8(Y, X, Z, r);
++	}
++
++	/* 10: B' <-- X */
++	blkcpy(B, X, 128 * r);
++}
++
++/**
++ * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen):
++ * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r,
++ * p, buflen) and write the result into buf.  The parameters r, p, and buflen
++ * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32.  The parameter N
++ * must be a power of 2.
++ *
++ * Return 0 on success; or -1 on error.
++ */
++int
++crypto_scrypt(const uint8_t * passwd, size_t passwdlen,
++    const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p,
++    uint8_t * buf, size_t buflen)
++{
++	void * B0, * V0, * XY0;
++	uint8_t * B;
++	uint32_t * V;
++	uint32_t * XY;
++	uint32_t i;
++
++	/* Sanity-check parameters. */
++#if SIZE_MAX > UINT32_MAX
++	if (buflen > (((uint64_t)(1) << 32) - 1) * 32) {
++		errno = EFBIG;
++		goto err0;
++	}
++#endif
++	if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) {
++		errno = EFBIG;
++		goto err0;
++	}
++	if (((N & (N - 1)) != 0) || (N == 0)) {
++		errno = EINVAL;
++		goto err0;
++	}
++	if ((r > SIZE_MAX / 128 / p) ||
++#if SIZE_MAX / 256 <= UINT32_MAX
++	    (r > SIZE_MAX / 256) ||
++#endif
++	    (N > SIZE_MAX / 128 / r)) {
++		errno = ENOMEM;
++		goto err0;
++	}
++
++	/* Allocate memory. */
++#ifdef HAVE_POSIX_MEMALIGN
++	if ((errno = posix_memalign(&B0, 64, 128 * r * p)) != 0)
++		goto err0;
++	B = (uint8_t *)(B0);
++	if ((errno = posix_memalign(&XY0, 64, 256 * r + 64)) != 0)
++		goto err1;
++	XY = (uint32_t *)(XY0);
++#ifndef MAP_ANON
++	if ((errno = posix_memalign(&V0, 64, 128 * r * N)) != 0)
++		goto err2;
++	V = (uint32_t *)(V0);
++#endif
++#else
++	if ((B0 = malloc(128 * r * p + 63)) == NULL)
++		goto err0;
++	B = (uint8_t *)(((uintptr_t)(B0) + 63) & ~ (uintptr_t)(63));
++	if ((XY0 = malloc(256 * r + 64 + 63)) == NULL)
++		goto err1;
++	XY = (uint32_t *)(((uintptr_t)(XY0) + 63) & ~ (uintptr_t)(63));
++#ifndef MAP_ANON
++	if ((V0 = malloc(128 * r * N + 63)) == NULL)
++		goto err2;
++	V = (uint32_t *)(((uintptr_t)(V0) + 63) & ~ (uintptr_t)(63));
++#endif
++#endif
++#ifdef MAP_ANON
++	if ((V0 = mmap(NULL, 128 * r * N, PROT_READ | PROT_WRITE,
++#ifdef MAP_NOCORE
++	    MAP_ANON | MAP_PRIVATE | MAP_NOCORE,
++#else
++	    MAP_ANON | MAP_PRIVATE,
++#endif
++	    -1, 0)) == MAP_FAILED)
++		goto err2;
++	V = (uint32_t *)(V0);
++#endif
++
++	/* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */
++#ifdef USE_OPENSSL_PBKDF2
++	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, salt, saltlen, 1, EVP_sha256(), p * 128 * r, B);
++#else
++	PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, p * 128 * r);
++#endif
++
++	/* 2: for i = 0 to p - 1 do */
++	for (i = 0; i < p; i++) {
++		/* 3: B_i <-- MF(B_i, N) */
++		smix(&B[i * 128 * r], r, N, V, XY);
++	}
++
++	/* 5: DK <-- PBKDF2(P, B, 1, dkLen) */
++#ifdef USE_OPENSSL_PBKDF2
++	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, B, p * 128 * r, 1, EVP_sha256(), buflen, buf);
++#else
++	PBKDF2_SHA256(passwd, passwdlen, B, p * 128 * r, 1, buf, buflen);
++#endif
++
++	/* Free memory. */
++#ifdef MAP_ANON
++	if (munmap(V0, 128 * r * N))
++		goto err2;
++#else
++	free(V0);
++#endif
++	free(XY0);
++	free(B0);
++
++	/* Success! */
++	return (0);
++
++err2:
++	free(XY0);
++err1:
++	free(B0);
++err0:
++	/* Failure! */
++	return (-1);
++}
diff --git a/crypto/scrypt/patches/use_openssl_pbkdf2.patch b/crypto/scrypt/patches/use_openssl_pbkdf2.patch
new file mode 100644
index 0000000..0a1328c
--- /dev/null
+++ b/crypto/scrypt/patches/use_openssl_pbkdf2.patch
@@ -0,0 +1,80 @@
+diff --git a/lib/crypto/crypto_scrypt-ref.c b/lib/crypto/crypto_scrypt-ref.c
+index 79a6f8f..60ef2aa 100644
+--- a/lib/crypto/crypto_scrypt-ref.c
++++ b/lib/crypto/crypto_scrypt-ref.c
+@@ -34,7 +34,11 @@
+ #include <stdlib.h>
+ #include <string.h>
+ 
++#ifdef USE_OPENSSL_PBKDF2
++#include <openssl/evp.h>
++#else
+ #include "sha256.h"
++#endif
+ #include "sysendian.h"
+ 
+ #include "crypto_scrypt.h"
+@@ -256,7 +260,11 @@ crypto_scrypt(const uint8_t * passwd, size_t passwdlen,
+ 		goto err2;
+ 
+ 	/* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */
++#ifdef USE_OPENSSL_PBKDF2
++	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, salt, saltlen, 1, EVP_sha256(), p * 128 * r, B);
++#else
+ 	PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, p * 128 * r);
++#endif
+ 
+ 	/* 2: for i = 0 to p - 1 do */
+ 	for (i = 0; i < p; i++) {
+@@ -265,7 +273,11 @@ crypto_scrypt(const uint8_t * passwd, size_t passwdlen,
+ 	}
+ 
+ 	/* 5: DK <-- PBKDF2(P, B, 1, dkLen) */
++#ifdef USE_OPENSSL_PBKDF2
++	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, B, p * 128 * r, 1, EVP_sha256(), buflen, buf);
++#else
+ 	PBKDF2_SHA256(passwd, passwdlen, B, p * 128 * r, 1, buf, buflen);
++#endif
+ 
+ 	/* Free memory. */
+ 	free(V);
+diff --git a/lib/crypto/crypto_scrypt-sse.c b/lib/crypto/crypto_scrypt-sse.c
+index 875175e..dd18f29 100644
+--- a/lib/crypto/crypto_scrypt-sse.c
++++ b/lib/crypto/crypto_scrypt-sse.c
+@@ -37,7 +37,11 @@
+ #include <stdlib.h>
+ #include <string.h>
+ 
++#ifdef USE_OPENSSL_PBKDF2
++#include <openssl/evp.h>
++#else
+ #include "sha256.h"
++#endif
+ #include "sysendian.h"
+ 
+ #include "crypto_scrypt.h"
+@@ -332,7 +336,11 @@ crypto_scrypt(const uint8_t * passwd, size_t passwdlen,
+ #endif
+ 
+ 	/* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */
++#ifdef USE_OPENSSL_PBKDF2
++	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, salt, saltlen, 1, EVP_sha256(), p * 128 * r, B);
++#else
+ 	PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, p * 128 * r);
++#endif
+ 
+ 	/* 2: for i = 0 to p - 1 do */
+ 	for (i = 0; i < p; i++) {
+@@ -341,7 +349,11 @@ crypto_scrypt(const uint8_t * passwd, size_t passwdlen,
+ 	}
+ 
+ 	/* 5: DK <-- PBKDF2(P, B, 1, dkLen) */
++#ifdef USE_OPENSSL_PBKDF2
++	PKCS5_PBKDF2_HMAC((const char *)passwd, passwdlen, B, p * 128 * r, 1, EVP_sha256(), buflen, buf);
++#else
+ 	PBKDF2_SHA256(passwd, passwdlen, B, p * 128 * r, 1, buf, buflen);
++#endif
+ 
+ 	/* Free memory. */
+ #ifdef MAP_ANON
diff --git a/crypto/scrypt/scrypt.config b/crypto/scrypt/scrypt.config
new file mode 100644
index 0000000..3ccb4d0
--- /dev/null
+++ b/crypto/scrypt/scrypt.config
@@ -0,0 +1,94 @@
+CONFIGURE_ARGS="\
+ \
+"
+
+# unneeded directories
+UNNEEDED_SOURCES="\
+lib/scryptenc \
+"
+
+# unneeded files
+UNNEEDED_SOURCES+="\
+config.h.in \
+configure \
+FORMAT \
+main.c \
+Makefile.in \
+scrypt.1 \
+lib/crypto/crypto_aesctr.c \
+lib/crypto/crypto_aesctr.h \
+lib/crypto/crypto_scrypt-nosse.c \
+lib/crypto/sha256.c \
+lib/crypto/sha256.h \
+lib/util/memlimit.c \
+lib/util/memlimit.h \
+lib/util/readpass.c \
+lib/util/readpass.h \
+lib/util/warn.c \
+lib/util/warn.h \
+"
+
+NEEDED_SOURCES="\
+config.h \
+lib \
+scrypt_platform.h \
+"
+
+SCRYPT_INCLUDES="\
+lib/crypto \
+lib/util \
+"
+
+SCRYPT_SOURCES="\
+lib/crypto/crypto_scrypt-ref.c \
+"
+
+SCRYPT_SOURCES_arm="\
+"
+
+SCRYPT_SOURCES_EXCLUDES_arm="\
+"
+
+SCRYPT_SOURCES_arm_neon="\
+lib/crypto/crypto_scrypt-neon.c \
+"
+
+SCRYPT_SOURCES_EXCLUDES_arm_neon="\
+lib/crypto/crypto_scrypt-ref.c \
+"
+
+SCRYPT_SOURCES_mips="\
+"
+
+SCRYPT_SOURCES_EXCLUDES_mips="\
+"
+
+SCRYPT_SOURCES_x86="\
+lib/crypto/crypto_scrypt-sse.c \
+"
+
+SCRYPT_SOURCES_EXCLUDES_x86="\
+lib/crypto/crypto_scrypt-ref.c \
+"
+
+SCRYPT_SOURCES_x86_64="\
+lib/crypto/crypto_scrypt-sse.c \
+"
+
+SCRYPT_SOURCES_EXCLUDES_x86_64="\
+lib/crypto/crypto_scrypt-ref.c \
+"
+
+SCRYPT_PATCHES="\
+use_openssl_pbkdf2.patch \
+arm-neon.patch \
+"
+
+SCRYPT_PATCHES_use_openssl_pbkdf2_SOURCES="\
+lib/crypto/crypto_scrypt-ref.c \
+"
+
+SCRYPT_PATCHES_bionic_SOURCES="\
+lib/crypto/crypto_scrypt-neon.c \
+lib/crypto/crypto_scrypt-neon-salsa208.h \
+"
diff --git a/crypto/scrypt/scrypt.version b/crypto/scrypt/scrypt.version
new file mode 100644
index 0000000..155e260
--- /dev/null
+++ b/crypto/scrypt/scrypt.version
@@ -0,0 +1 @@
+SCRYPT_VERSION=1.1.6
diff --git a/crypto/scrypt/scrypt_platform.h b/crypto/scrypt/scrypt_platform.h
new file mode 100644
index 0000000..5cec236
--- /dev/null
+++ b/crypto/scrypt/scrypt_platform.h
@@ -0,0 +1,12 @@
+#ifndef _SCRYPT_PLATFORM_H_
+#define	_SCRYPT_PLATFORM_H_
+
+#if defined(CONFIG_H_FILE)
+#include CONFIG_H_FILE
+#elif defined(HAVE_CONFIG_H)
+#include "config.h"
+#else
+#error Need either CONFIG_H_FILE or HAVE_CONFIG_H defined.
+#endif
+
+#endif /* !_SCRYPT_PLATFORM_H_ */
diff --git a/crypto/scrypt/sources.bp b/crypto/scrypt/sources.bp
new file mode 100644
index 0000000..d21344f
--- /dev/null
+++ b/crypto/scrypt/sources.bp
@@ -0,0 +1,82 @@
+// Auto-generated - DO NOT EDIT!
+// To regenerate, edit scrypt.config, then run:
+//     ./import_scrypt.sh import /path/to/scrypt-1.1.6.tar.gz
+//
+
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_defaults {
+    name: "libscrypttwrp_sources",
+
+    cflags: [
+        "-DHAVE_CONFIG_H",
+        "-DUSE_OPENSSL_PBKDF2",
+        "-Wall",
+        "-Werror",
+        "-Wno-implicit-function-declaration",
+        "-Wno-unused-variable",
+    ],
+
+    export_include_dirs: [
+        "lib/crypto",
+    ],
+
+    local_include_dirs: [
+        "lib/util",
+    ],
+
+    srcs: [
+        "lib/crypto/crypto_scrypt-ref.c",
+    ],
+
+    arch: {
+        arm: {
+            neon: {
+                srcs: [
+                    "lib/crypto/crypto_scrypt-neon.c",
+                ],
+                exclude_srcs: [
+                    "lib/crypto/crypto_scrypt-ref.c",
+                ],
+            },
+        },
+        arm64: {
+            srcs: [
+                "lib/crypto/crypto_scrypt-neon.c",
+            ],
+            exclude_srcs: [
+                "lib/crypto/crypto_scrypt-ref.c",
+            ],
+        },
+        x86: {
+            srcs: [
+                "lib/crypto/crypto_scrypt-sse.c",
+            ],
+            exclude_srcs: [
+                "lib/crypto/crypto_scrypt-ref.c",
+            ],
+        },
+        x86_64: {
+            srcs: [
+                "lib/crypto/crypto_scrypt-sse.c",
+            ],
+            exclude_srcs: [
+                "lib/crypto/crypto_scrypt-ref.c",
+            ],
+        },
+    },
+}
diff --git a/crypto/scrypt/tests/Android.mk b/crypto/scrypt/tests/Android.mk
new file mode 100644
index 0000000..b49d8a1
--- /dev/null
+++ b/crypto/scrypt/tests/Android.mk
@@ -0,0 +1,24 @@
+# Build the scrypt unit tests
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_SRC_FILES:= \
+    scrypt_test.cpp
+
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../lib/crypto
+
+LOCAL_SHARED_LIBRARIES := \
+    libcrypto
+
+LOCAL_STATIC_LIBRARIES := \
+    libscrypt_static \
+    libgtest \
+    libgtest_main
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := scrypttwrp_test
+
+include $(BUILD_NATIVE_TEST)
diff --git a/crypto/scrypt/tests/scrypt_test.cpp b/crypto/scrypt/tests/scrypt_test.cpp
new file mode 100644
index 0000000..7dfffe3
--- /dev/null
+++ b/crypto/scrypt/tests/scrypt_test.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "scrypt_test"
+#include <nativehelper/UniquePtr.h>
+#include <utils/Log.h>
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <iostream>
+
+extern "C" {
+#include <crypto_scrypt.h>
+}
+
+namespace android {
+
+typedef struct scrypt_test_setting_t {
+        const char *pw, *salt;
+        uint32_t Nfactor, rfactor, pfactor;
+} scrypt_test_setting;
+
+static const scrypt_test_setting post_settings[] = {
+        {"", "", 16, 1, 1},
+        {"password", "NaCl", 1024, 8, 16},
+        {"pleaseletmein", "SodiumChloride", 16384, 8, 1},
+        {0, 0, 0, 0, 0}
+};
+
+static const uint8_t post_vectors[][64] = {
+        {0x77,0xd6,0x57,0x62,0x38,0x65,0x7b,0x20,0x3b,0x19,0xca,0x42,0xc1,0x8a,0x04,0x97,
+         0xf1,0x6b,0x48,0x44,0xe3,0x07,0x4a,0xe8,0xdf,0xdf,0xfa,0x3f,0xed,0xe2,0x14,0x42,
+         0xfc,0xd0,0x06,0x9d,0xed,0x09,0x48,0xf8,0x32,0x6a,0x75,0x3a,0x0f,0xc8,0x1f,0x17,
+         0xe8,0xd3,0xe0,0xfb,0x2e,0x0d,0x36,0x28,0xcf,0x35,0xe2,0x0c,0x38,0xd1,0x89,0x06},
+        {0xfd,0xba,0xbe,0x1c,0x9d,0x34,0x72,0x00,0x78,0x56,0xe7,0x19,0x0d,0x01,0xe9,0xfe,
+         0x7c,0x6a,0xd7,0xcb,0xc8,0x23,0x78,0x30,0xe7,0x73,0x76,0x63,0x4b,0x37,0x31,0x62,
+         0x2e,0xaf,0x30,0xd9,0x2e,0x22,0xa3,0x88,0x6f,0xf1,0x09,0x27,0x9d,0x98,0x30,0xda,
+         0xc7,0x27,0xaf,0xb9,0x4a,0x83,0xee,0x6d,0x83,0x60,0xcb,0xdf,0xa2,0xcc,0x06,0x40},
+        {0x70,0x23,0xbd,0xcb,0x3a,0xfd,0x73,0x48,0x46,0x1c,0x06,0xcd,0x81,0xfd,0x38,0xeb,
+         0xfd,0xa8,0xfb,0xba,0x90,0x4f,0x8e,0x3e,0xa9,0xb5,0x43,0xf6,0x54,0x5d,0xa1,0xf2,
+         0xd5,0x43,0x29,0x55,0x61,0x3f,0x0f,0xcf,0x62,0xd4,0x97,0x05,0x24,0x2a,0x9a,0xf9,
+         0xe6,0x1e,0x85,0xdc,0x0d,0x65,0x1e,0x40,0xdf,0xcf,0x01,0x7b,0x45,0x57,0x58,0x87},
+};
+
+class ScryptTest : public ::testing::Test {
+};
+
+TEST_F(ScryptTest, TestVectors) {
+    int i;
+
+    for (i = 0; post_settings[i].pw != NULL; i++) {
+        uint8_t output[64];
+
+        scrypt_test_setting_t s = post_settings[i];
+        ASSERT_EQ(0,
+                crypto_scrypt((const uint8_t*) s.pw, strlen(s.pw), (const uint8_t*) s.salt,
+                        strlen(s.salt), s.Nfactor, s.rfactor, s.pfactor, output, sizeof(output)))
+                << "scrypt call should succeed for " << i << "; error=" << strerror(errno);
+        ASSERT_EQ(0, memcmp(post_vectors[i], output, sizeof(output)))
+                << "Should match expected output";
+    }
+}
+
+}
diff --git a/crypto/vold_decrypt/Android.bp--skip_soong-- b/crypto/vold_decrypt/Android.bp--skip_soong--
new file mode 100644
index 0000000..f16942d
--- /dev/null
+++ b/crypto/vold_decrypt/Android.bp--skip_soong--
@@ -0,0 +1,15 @@
+cc_binary {
+    name: "vdc_pie",
+    defaults: ["vold_default_flags"],
+
+    srcs: ["vdc_pie.cpp"],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libcutils",
+        "libutils",
+    ],
+    static_libs: [
+        "libvold_binder",
+    ],
+}
diff --git a/crypto/vold_decrypt/Android.mk b/crypto/vold_decrypt/Android.mk
new file mode 100755
index 0000000..a36abf4
--- /dev/null
+++ b/crypto/vold_decrypt/Android.mk
@@ -0,0 +1,156 @@
+# Copyright (C) 2017 TeamWin Recovery Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+ifeq ($(TW_INCLUDE_CRYPTO), true)
+    ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),)
+
+        # Parse TW_CRYPTO_USE_SYSTEM_VOLD
+        ifeq ($(TW_CRYPTO_USE_SYSTEM_VOLD),true)
+            # Just enabled, so only vold + vdc
+            services :=
+        else
+            # Additional services needed by vold
+            services := $(TW_CRYPTO_USE_SYSTEM_VOLD)
+        endif
+
+        # Parse TW_CRYPTO_SYSTEM_VOLD_MOUNT
+        ifneq ($(TW_CRYPTO_SYSTEM_VOLD_MOUNT),)
+            # Per device additional partitions to mount
+            partitions := $(TW_CRYPTO_SYSTEM_VOLD_MOUNT)
+        endif
+
+        # List of .rc files for each additional service
+        rc_files := $(foreach item,$(services),init.recovery.vold_decrypt.$(item).rc)
+
+
+        include $(CLEAR_VARS)
+        LOCAL_MODULE := init.recovery.vold_decrypt.rc
+        LOCAL_MODULE_TAGS := optional
+        LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+
+        # Cannot send to TARGET_RECOVERY_ROOT_OUT since build system wipes init*.rc
+        # during ramdisk creation and only allows init.recovery.*.rc files to be copied
+        # from TARGET_ROOT_OUT thereafter
+        LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
+
+        LOCAL_SRC_FILES := $(LOCAL_MODULE)
+
+        # Add additional .rc files and imports into init.recovery.vold_decrypt.rc
+        # Note: any init.recovery.vold_decrypt.{service}.rc that are not default
+        #       in crypto/vold_decrypt should be in the device tree
+        LOCAL_POST_INSTALL_CMD := $(hide) \
+            $(foreach item, $(rc_files), \
+                sed -i '1iimport \/$(item)' "$(TARGET_ROOT_OUT)/$(LOCAL_MODULE)"; \
+                if [ -f "$(LOCAL_PATH)/$(item)" ]; then \
+                    cp -f "$(LOCAL_PATH)/$(item)" "$(TARGET_ROOT_OUT)"/; \
+                fi; \
+            )
+
+        ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 26; echo $$?),0)
+            # Truncate service_name to max 16 characters
+            LOCAL_POST_INSTALL_CMD += \
+                $(foreach item, $(rc_files), \
+                    if [ -f "$(TARGET_ROOT_OUT)/$(item)" ]; then \
+                        sed -i 's/\([ \t]*service[ \t]*\)\(.\{16\}\).*\([ \t].*\)/\1\2\3/' "$(TARGET_ROOT_OUT)/$(item)"; \
+                    fi; \
+                )
+        endif
+
+        include $(BUILD_PREBUILT)
+
+
+        include $(CLEAR_VARS)
+        LOCAL_MODULE := libvolddecrypt
+        LOCAL_MODULE_TAGS := optional
+        LOCAL_CFLAGS := -Wall
+        ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
+            LOCAL_C_INCLUDES += external/stlport/stlport bionic bionic/libstdc++/include
+        endif
+
+        ifneq ($(services),)
+            ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 26; echo $$?),0)
+                # Truncate service_name to max 12 characters due to the 4 character prefix
+                truncated_services := $(foreach item,$(services),$(shell echo -n "$(item)" | sed 's/\(.\{12\}\).*/\1/'))
+                LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_SERVICES='"$(truncated_services)"'
+                LOCAL_CFLAGS += -D_USING_SHORT_SERVICE_NAMES
+            else
+                LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_SERVICES='"$(services)"'
+            endif
+        endif
+
+        ifneq ($(partitions),)
+            LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_MOUNT='"$(partitions)"'
+        endif
+
+        ifeq ($(TW_CRYPTO_SYSTEM_VOLD_DEBUG),true)
+            # Enabling strace will expose the password in the strace logs!!
+            LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_DEBUG
+        else
+            ifneq ($(TW_CRYPTO_SYSTEM_VOLD_DEBUG),)
+                # Specify strace path
+                LOCAL_CFLAGS += -DTW_CRYPTO_SYSTEM_VOLD_DEBUG
+                LOCAL_CFLAGS += -DVD_STRACE_BIN=\"$(TW_CRYPTO_SYSTEM_VOLD_DEBUG)\"
+            endif
+        endif
+
+        ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0)
+            ifeq ($(TW_INCLUDE_LIBRESETPROP), true)
+                LOCAL_CFLAGS += -DTW_INCLUDE_LIBRESETPROP
+            endif
+        endif
+
+        LOCAL_SRC_FILES = vold_decrypt.cpp
+        LOCAL_SHARED_LIBRARIES := libcutils
+        LOCAL_C_INCLUDES += system/extras/ext4_utils/include
+        ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 29; echo $$?),0)
+            LOCAL_C_INCLUDES += bootable/recovery/crypto/fscrypt
+        else
+            LOCAL_C_INCLUDES += bootable/recovery/crypto/ext4crypt
+        endif
+        include $(BUILD_STATIC_LIBRARY)
+
+        ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 28; echo $$?),0)
+            include $(CLEAR_VARS)
+            LOCAL_MODULE := vdc_pie
+            LOCAL_SRC_FILES := vdc_pie.cpp
+            LOCAL_MODULE_TAGS := optional
+            LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+            LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+            LOCAL_CLANG := true
+            LOCAL_TIDY := true
+            LOCAL_TIDY_FLAGS := -warnings-as-errors=clang-analyzer-security*,cert-*
+            LOCAL_TIDY_CHECKS := -*,cert-*,clang,-analyzer-security*
+            LOCAL_STATIC_LIBRARIES := libvold_binder
+            LOCAL_SHARED_LIBRARIES := libbase libcutils libutils libbinder
+            LOCAL_CFLAGS := -Wall
+            ifeq ($(TWRP_INCLUDE_LOGCAT), true)
+                LOCAL_CFLAGS += -DTWRP_INCLUDE_LOGCAT
+            endif
+            ifneq ($(TARGET_ARCH), arm64)
+                ifneq ($(TARGET_ARCH), x86_64)
+                    LOCAL_LDFLAGS += -Wl,-dynamic-linker,/system/bin/linker
+                else
+                    LOCAL_LDFLAGS += -Wl,-dynamic-linker,/system/bin/linker64
+                endif
+            else
+                LOCAL_LDFLAGS += -Wl,-dynamic-linker,/system/bin/linker64
+            endif
+
+            include $(BUILD_EXECUTABLE)
+        endif
+
+    endif
+endif
diff --git a/crypto/vold_decrypt/init.recovery.vold_decrypt.hwservicemanager.rc b/crypto/vold_decrypt/init.recovery.vold_decrypt.hwservicemanager.rc
new file mode 100755
index 0000000..ab8b4ac
--- /dev/null
+++ b/crypto/vold_decrypt/init.recovery.vold_decrypt.hwservicemanager.rc
@@ -0,0 +1,18 @@
+# Service names must be less than 16 characters in android-7.1 and
+# below. The makefile will truncate service names if needed in any
+# init.recovery.vold_decryp.*.rc file found in the vold_decrypt
+# directory.
+# It cannot however do this for any .rc file(s) that may be
+# overridden by the device tree files!
+
+# The seclabels are not needed when built in Android 8.0 tree
+# in 7.1 however the below do not exist, so run them under vold
+service sys_hwservicemanager /system/bin/hwservicemanager
+    user root
+    group root
+    setenv PATH /system/bin
+    setenv LD_LIBRARY_PATH /system/lib64:/system/lib
+    onrestart setprop hwservicemanager.ready false
+    disabled
+    oneshot
+    seclabel u:r:vold:s0
diff --git a/crypto/vold_decrypt/init.recovery.vold_decrypt.keymaster-3-0.rc b/crypto/vold_decrypt/init.recovery.vold_decrypt.keymaster-3-0.rc
new file mode 100755
index 0000000..e9f0b02
--- /dev/null
+++ b/crypto/vold_decrypt/init.recovery.vold_decrypt.keymaster-3-0.rc
@@ -0,0 +1,17 @@
+# Service names must be less than 16 characters in android-7.1 and
+# below. The makefile will truncate service names if needed in any
+# init.recovery.vold_decryp.*.rc file found in the vold_decrypt
+# directory.
+# It cannot however do this for any .rc file(s) that may be
+# overridden by the device tree files!
+
+# The seclabels are not needed when built in Android 8.0 tree
+# in 7.1 however the below do not exist, so run them under vold
+service ven_keymaster-3-0 /vendor/bin/hw/android.hardware.keymaster@3.0-service
+    user root
+    group root
+    setenv PATH /vendor/bin:/system/bin
+    setenv LD_LIBRARY_PATH /vendor/lib64:/system/lib64:/vendor/lib:/system/lib
+    disabled
+    oneshot
+    seclabel u:r:vold:s0
diff --git a/crypto/vold_decrypt/init.recovery.vold_decrypt.qseecomd.rc b/crypto/vold_decrypt/init.recovery.vold_decrypt.qseecomd.rc
new file mode 100755
index 0000000..0866608
--- /dev/null
+++ b/crypto/vold_decrypt/init.recovery.vold_decrypt.qseecomd.rc
@@ -0,0 +1,26 @@
+on fs
+    # needed to make qseecomd work in recovery
+    chmod 0660 /dev/qseecom
+    chown system drmrpc /dev/qseecom
+    chmod 0664 /dev/ion
+    chown system system /dev/ion
+
+
+# Oreo has qseecomd in /vendor/bin so add the additional
+# service. Only an existing binary will be started, never both.
+
+service sys_qseecomd /system/bin/qseecomd
+    user root
+    group root
+    setenv PATH /system/bin
+    setenv LD_LIBRARY_PATH /system/lib64:/system/lib
+    disabled
+    oneshot
+
+service ven_qseecomd /vendor/bin/qseecomd
+    user root
+    group root
+    setenv PATH /vendor/bin:/system/bin
+    setenv LD_LIBRARY_PATH /vendor/lib64:/system/lib64:/vendor/lib:/system/lib
+    disabled
+    oneshot
diff --git a/crypto/vold_decrypt/init.recovery.vold_decrypt.rc b/crypto/vold_decrypt/init.recovery.vold_decrypt.rc
new file mode 100755
index 0000000..65983eb
--- /dev/null
+++ b/crypto/vold_decrypt/init.recovery.vold_decrypt.rc
@@ -0,0 +1,10 @@
+
+service sys_vold /system/bin/vold \
+        --blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \
+        --fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
+    socket vold stream 0660 root mount
+    socket cryptd stream 0660 root mount
+    setenv PATH /system/bin
+    setenv LD_LIBRARY_PATH /system/lib64:/system/lib
+    disabled
+    oneshot
diff --git a/crypto/vold_decrypt/init.recovery.vold_decrypt.servicemanager.rc b/crypto/vold_decrypt/init.recovery.vold_decrypt.servicemanager.rc
new file mode 100644
index 0000000..40672d7
--- /dev/null
+++ b/crypto/vold_decrypt/init.recovery.vold_decrypt.servicemanager.rc
@@ -0,0 +1,8 @@
+service sys_servicemanager /system/bin/servicemanager
+    user root
+    group root
+    setenv PATH /system/bin
+    setenv LD_LIBRARY_PATH /system/lib64:/system/lib
+    disabled
+    oneshot
+    seclabel u:r:vold:s0
diff --git a/crypto/vold_decrypt/vdc_pie.cpp b/crypto/vold_decrypt/vdc_pie.cpp
new file mode 100644
index 0000000..a840712
--- /dev/null
+++ b/crypto/vold_decrypt/vdc_pie.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <poll.h>
+
+#include <cutils/properties.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include "android/os/IVold.h"
+
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+
+#include <private/android_filesystem_config.h>
+
+static void usage();
+
+static android::sp<android::IBinder> getServiceAggressive() {
+    static char prop_value[PROPERTY_VALUE_MAX];
+    property_get("init.svc.sys_vold", prop_value, "error");
+    if (strncmp(prop_value, "running", strlen("running")) != 0) {
+        printf("vdc_pie: vold is not running, init.svc.sys_vold=%s\n", prop_value);
+        exit(EINVAL);
+    }
+
+    android::sp<android::IBinder> res;
+    auto sm = android::defaultServiceManager();
+    auto name = android::String16("vold");
+    for (int i = 0; i < 5000; i++) {
+        res = sm->checkService(name);
+        if (res) {
+            printf("vdc_pie: Got vold, waited %d ms\n", (i * 10));
+            break;
+        }
+        usleep(10000); // 10ms
+    }
+    return res;
+}
+
+static int checkStatus(android::binder::Status status) {
+    if (status.isOk()) return 0;
+    std::string ret = status.toString8().string();
+#ifdef TWRP_INCLUDE_LOGCAT
+    printf("vdc_pie: Decryption failed, vold service returned: %s,"
+		" see logcat for details\n", ret.c_str());
+#else
+	printf("vdc_pie: Decryption failed, vold service returned: %s\n", ret.c_str());
+#endif
+    return -1;
+}
+
+int main(int argc, char** argv) {
+    std::vector<std::string> args(argv + 1, argv + argc);
+
+    if (args.size() > 0 && args[0] == "--wait") {
+        // Just ignore the --wait flag
+        args.erase(args.begin());
+    }
+
+    if (args.size() < 2) {
+        usage();
+        exit(5);
+    }
+    android::sp<android::IBinder> binder = getServiceAggressive();
+    if (!binder) {
+        printf("vdc_pie: Failed to obtain vold Binder\n");
+        exit(EINVAL);
+    }
+    auto vold = android::interface_cast<android::os::IVold>(binder);
+
+    if (args[0] == "cryptfs" && args[1] == "checkpw" && args.size() == 3) {
+        return checkStatus(vold->fdeCheckPassword(args[2]));
+    } else {
+        usage();
+        exit(EINVAL);
+    }
+    return 0;
+}
+
+static void usage() {
+    printf("vdc_pie: Usage: vold_pie cryptfs checkpw <password>\n");
+}
diff --git a/crypto/vold_decrypt/vold_decrypt.cpp b/crypto/vold_decrypt/vold_decrypt.cpp
new file mode 100755
index 0000000..4a76405
--- /dev/null
+++ b/crypto/vold_decrypt/vold_decrypt.cpp
@@ -0,0 +1,1441 @@
+/*
+    Copyright 2017 TeamWin
+    This file is part of TWRP/TeamWin Recovery Project.
+
+    TWRP is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    TWRP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mount.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <fnmatch.h>
+
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <fstream>
+#include <string>
+#include <vector>
+#include <sstream>
+
+#ifdef _USING_SHORT_SERVICE_NAMES
+#include <map>
+#endif
+
+#include "../../partitions.hpp"
+#include "../../twrp-functions.hpp"
+
+using namespace std;
+
+extern "C" {
+	#include <cutils/properties.h>
+}
+
+#include "vold_decrypt.h"
+
+namespace {
+
+/* Timeouts as defined by ServiceManager */
+
+/* The maximum amount of time to wait for a service to start or stop,
+ * in micro-seconds (really an approximation) */
+#define  SLEEP_MAX_USEC     2000000  /* 2 seconds */
+/* The minimal sleeping interval between checking for the service's state
+ * when looping for SLEEP_MAX_USEC */
+#define  SLEEP_MIN_USEC      200000  /* 200 msec */
+
+
+/* vold response codes defined in ResponseCode.h */
+// 200 series - Requested action has been successfully completed
+#define COMMAND_OKAY           200
+#define PASSWORD_TYPE_RESULT   213
+
+
+#define LOGINFO(...)  do { printf(__VA_ARGS__); if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]I:" __VA_ARGS__); fflush(fp_kmsg); } } while (0)
+#define LOGKMSG(...)  do { if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]K:" __VA_ARGS__); fflush(fp_kmsg); } } while (0)
+#define LOGERROR(...) do { printf(__VA_ARGS__); if (fp_kmsg) { fprintf(fp_kmsg, "[VOLD_DECRYPT]E:" __VA_ARGS__); fflush(fp_kmsg); } } while (0)
+
+FILE *fp_kmsg = NULL;
+int sdkver = 20;
+
+
+/* Debugging Functions */
+#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG
+
+#ifndef VD_STRACE_BIN
+#define VD_STRACE_BIN "/system/bin/strace"
+#endif
+
+bool has_strace = false;
+pid_t pid_strace = 0;
+
+void Strace_init_Start(void) {
+	has_strace = TWFunc::Path_Exists(VD_STRACE_BIN);
+	if (!has_strace) {
+		LOGINFO("strace binary (%s) not found, disabling strace in vold_decrypt!\n", VD_STRACE_BIN);
+		return;
+	}
+
+	pid_t pid;
+	switch(pid = fork())
+	{
+		case -1:
+			LOGKMSG("forking strace_init failed: %d (%s)!\n", errno, strerror(errno));
+			return;
+		case 0: // child
+			execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", "/tmp/strace_init.log", "-p", "1" , NULL);
+			LOGKMSG("strace_init fork failed: %d (%s)!\n", errno, strerror(errno));
+			exit(-1);
+		default:
+			LOGKMSG("Starting strace_init (pid=%d)\n", pid);
+			pid_strace = pid;
+			return;
+	}
+}
+
+void Strace_init_Stop(void) {
+	if (pid_strace > 0) {
+		LOGKMSG("Stopping strace_init (pid=%d)\n", pid_strace);
+		int timeout;
+		int status;
+		pid_t retpid = waitpid(pid_strace, &status, WNOHANG);
+
+		kill(pid_strace, SIGTERM);
+		for (timeout = 5; retpid == 0 && timeout; --timeout) {
+			sleep(1);
+			retpid = waitpid(pid_strace, &status, WNOHANG);
+		}
+		if (retpid)
+			LOGKMSG("strace_init terminated successfully\n");
+		else {
+			// SIGTERM didn't work, kill it instead
+			kill(pid_strace, SIGKILL);
+			for (timeout = 5; retpid == 0 && timeout; --timeout) {
+				sleep(1);
+				retpid = waitpid(pid_strace, &status, WNOHANG);
+			}
+			if (retpid)
+				LOGKMSG("strace_init killed successfully\n");
+			else
+				LOGKMSG("strace_init took too long to kill, may be a zombie process\n");
+		}
+	}
+}
+#endif // TW_CRYPTO_SYSTEM_VOLD_DEBUG
+
+
+/* Convert a binary key of specified length into an ascii hex string equivalent,
+ * without the leading 0x and with null termination
+ *
+ * Original code from cryptfs.c
+ */
+string convert_key_to_hex_ascii(const string& master_key) {
+	size_t i;
+	unsigned char nibble;
+	string master_key_ascii = "";
+
+	for (i = 0; i < master_key.size(); ++i) {
+		nibble = (master_key[i] >> 4) & 0xf;
+		nibble += nibble > 9 ? 0x57 : 0x30;
+		master_key_ascii += nibble;
+
+		nibble = master_key[i] & 0xf;
+		nibble += nibble > 9 ? 0x57 : 0x30;
+		master_key_ascii += nibble;
+	}
+
+	return master_key_ascii;
+}
+
+/* Helper Functions */
+#define PATH_EXISTS(path)  (access(path, F_OK) >= 0)
+
+int vrename(const string& oldname, const string& newname, bool verbose = false) {
+	const char *old_name = oldname.c_str();
+	const char *new_name = newname.c_str();
+
+	if (!PATH_EXISTS(old_name))
+		return 0;
+
+	if (rename(old_name, new_name) < 0) {
+		LOGERROR("Moving %s to %s failed: %d (%s)\n", old_name, new_name, errno, strerror(errno));
+		return -1;
+	} else if (verbose)
+		LOGINFO("Renamed %s to %s\n", old_name, new_name);
+	else
+		LOGKMSG("Renamed %s to %s\n", old_name, new_name);
+	return 0;
+}
+
+int vsymlink(const string& oldname, const string& newname, bool verbose = false) {
+	const char *old_name = oldname.c_str();
+	const char *new_name = newname.c_str();
+
+	if (!PATH_EXISTS(old_name))
+		return 0;
+
+	if (symlink(old_name, new_name) < 0) {
+		LOGERROR("Symlink %s -> %s failed: %d (%s)\n", new_name, old_name, errno, strerror(errno));
+		return -1;
+	} else if (verbose)
+		LOGINFO("Symlinked %s -> %s\n", new_name, old_name);
+	else
+		LOGKMSG("Symlinked %s -> %s\n", new_name, old_name);
+	return 0;
+}
+
+
+/* Properties and Services Functions */
+string Wait_For_Property(const string& property_name, int utimeout = SLEEP_MAX_USEC, const string& expected_value = "not_empty") {
+	char prop_value[PROPERTY_VALUE_MAX];
+
+	if (expected_value == "not_empty") {
+		while (utimeout > 0) {
+			property_get(property_name.c_str(), prop_value, "error");
+			if (strcmp(prop_value, "error") != 0)
+				break;
+			LOGKMSG("waiting for %s to get set\n", property_name.c_str());
+			utimeout -= SLEEP_MIN_USEC;
+			usleep(SLEEP_MIN_USEC);;
+		}
+	}
+	else {
+		while (utimeout > 0) {
+			property_get(property_name.c_str(), prop_value, "error");
+			if (strcmp(prop_value, expected_value.c_str()) == 0)
+				break;
+			LOGKMSG("waiting for %s to change from '%s' to '%s'\n", property_name.c_str(), prop_value, expected_value.c_str());
+			utimeout -= SLEEP_MIN_USEC;
+			usleep(SLEEP_MIN_USEC);
+		}
+	}
+	property_get(property_name.c_str(), prop_value, "error");
+
+	return prop_value;
+}
+
+string Get_Service_State(const string& initrc_svc) {
+	char prop_value[PROPERTY_VALUE_MAX];
+	string init_svc = "init.svc." + initrc_svc;
+	property_get(init_svc.c_str(), prop_value, "error");
+	return prop_value;
+}
+
+bool Service_Exists(const string& initrc_svc) {
+	return (Get_Service_State(initrc_svc) != "error");
+}
+
+bool Is_Service_Running(const string& initrc_svc) {
+	return (Get_Service_State(initrc_svc) == "running");
+}
+
+bool Is_Service_Stopped(const string& initrc_svc) {
+	return (Get_Service_State(initrc_svc) == "stopped");
+}
+
+bool Start_Service(const string& initrc_svc, int utimeout = SLEEP_MAX_USEC) {
+	string res = "error";
+	string init_svc = "init.svc." + initrc_svc;
+
+	property_set("ctl.start", initrc_svc.c_str());
+
+	res = Wait_For_Property(init_svc, utimeout, "running");
+
+	LOGINFO("Start service %s: %s.\n", initrc_svc.c_str(), res.c_str());
+
+	return (res == "running");
+}
+
+bool Stop_Service(const string& initrc_svc, int utimeout = SLEEP_MAX_USEC) {
+	string res = "error";
+
+	if (Service_Exists(initrc_svc)) {
+		string init_svc = "init.svc." + initrc_svc;
+		property_set("ctl.stop", initrc_svc.c_str());
+		res = Wait_For_Property(init_svc, utimeout, "stopped");
+		LOGINFO("Stop service %s: %s.\n", initrc_svc.c_str(), res.c_str());
+	}
+
+	return (res == "stopped");
+}
+
+
+/* Vendor, Firmware and fstab symlink Functions */
+bool is_Vendor_Mounted(void) {
+	static int is_mounted = -1;
+	if (is_mounted < 0)
+		is_mounted = PartitionManager.Is_Mounted_By_Path("/vendor") ? 1 : 0;
+	return is_mounted;
+}
+
+bool is_Firmware_Mounted(void) {
+	static int is_mounted = -1;
+	if (is_mounted < 0)
+		is_mounted = PartitionManager.Is_Mounted_By_Path("/firmware") ? 1 : 0;
+	return is_mounted;
+}
+
+bool will_VendorBin_Be_Symlinked(void) {
+	return (!is_Vendor_Mounted() && TWFunc::Path_Exists("/system/vendor"));
+}
+
+bool Symlink_Vendor_Folder(void) {
+	bool is_vendor_symlinked = false;
+
+	if (is_Vendor_Mounted()) {
+		LOGINFO("vendor partition mounted, skipping /vendor substitution\n");
+	}
+	else if (TWFunc::Path_Exists("/system/vendor")) {
+		LOGINFO("Symlinking vendor folder...\n");
+		if (!TWFunc::Path_Exists("/vendor") || vrename("/vendor", "/vendor-orig") == 0) {
+			TWFunc::Recursive_Mkdir("/vendor/firmware/keymaster");
+			vsymlink("/system/vendor/lib64", "/vendor/lib64", true);
+			vsymlink("/system/vendor/lib", "/vendor/lib", true);
+			vsymlink("/system/vendor/bin", "/vendor/bin", true);
+			is_vendor_symlinked = true;
+			property_set("vold_decrypt.symlinked_vendor", "1");
+		}
+	}
+	return is_vendor_symlinked;
+}
+
+void Restore_Vendor_Folder(void) {
+	property_set("vold_decrypt.symlinked_vendor", "0");
+	TWFunc::removeDir("/vendor", false);
+	vrename("/vendor-orig", "/vendor");
+}
+
+bool Symlink_Firmware_Folder(void) {
+	bool is_firmware_symlinked = false;
+
+	if (is_Firmware_Mounted()) {
+		LOGINFO("firmware partition mounted, skipping /firmware substitution\n");
+	}
+	else {
+		LOGINFO("Symlinking firmware folder...\n");
+		if (!TWFunc::Path_Exists("/firmware") || vrename("/firmware", "/firmware-orig") == 0) {
+			TWFunc::Recursive_Mkdir("/firmware/image");
+			is_firmware_symlinked = true;
+			property_set("vold_decrypt.symlinked_firmware", "1");
+		}
+	}
+	return is_firmware_symlinked;
+}
+
+void Restore_Firmware_Folder(void) {
+	property_set("vold_decrypt.symlinked_firmware", "0");
+	TWFunc::removeDir("/firmware", false);
+	vrename("/firmware-orig", "/firmware");
+}
+
+int Find_Firmware_Files(const string& Path, vector<string> *FileList) {
+	int ret;
+	DIR* d;
+	struct dirent* de;
+	string FileName;
+
+	d = opendir(Path.c_str());
+	if (d == NULL) {
+		closedir(d);
+		return -1;
+	}
+	while ((de = readdir(d)) != NULL) {
+		if (de->d_type == DT_DIR) {
+			if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
+				continue;
+			FileName = Path + "/" + de->d_name;
+			ret = Find_Firmware_Files(FileName, FileList);
+			if (ret < 0)
+				return -1;
+		} else if (de->d_type == DT_REG) {
+			if (fnmatch("keymaste*.*", de->d_name, 0) == 0 || fnmatch("cmnlib.*", de->d_name, 0) == 0) {
+				FileName = Path + "/" + de->d_name;
+				FileList->push_back(FileName);
+			}
+		}
+	}
+	closedir(d);
+	return 0;
+}
+
+void Symlink_Firmware_Files(bool is_vendor_symlinked, bool is_firmware_symlinked) {
+	if (!is_vendor_symlinked && !is_firmware_symlinked)
+		return;
+
+	LOGINFO("Symlinking firmware files...\n");
+
+	vector<string> FirmwareFiles;
+	Find_Firmware_Files("/system", &FirmwareFiles);
+
+	for (size_t i = 0; i < FirmwareFiles.size(); ++i) {
+		string base_name = TWFunc::Get_Filename(FirmwareFiles[i]);
+
+		if (is_firmware_symlinked)
+			vsymlink(FirmwareFiles[i], "/firmware/image/" + base_name);
+
+		if (is_vendor_symlinked) {
+			vsymlink(FirmwareFiles[i], "/vendor/firmware/" + base_name);
+			vsymlink(FirmwareFiles[i], "/vendor/firmware/keymaster/" + base_name);
+		}
+	}
+	LOGINFO("%d file(s) symlinked.\n", (int)FirmwareFiles.size());
+}
+
+// Android 8.0 fs_mgr checks for "/system/bin/recovery", in which case it will
+// use /etc/recovery.fstab -> symlink it temporarily. Reference:
+// https://android.googlesource.com/platform/system/core/+/android-8.0.0_r17/fs_mgr/fs_mgr_fstab.cpp#693
+bool Symlink_Recovery_Fstab(void) {
+	bool is_fstab_symlinked = false;
+
+	if (vrename("/etc/recovery.fstab", "/etc/recovery-fstab-orig") == 0) {
+		is_fstab_symlinked = true;
+
+		// now attempt to symlink to /fstab.{ro.hardware}, but even if that
+		// fails, keep TWRP's fstab hidden since it cannot be parsed by fs_mgr
+		char prop_value[PROPERTY_VALUE_MAX];
+		property_get("ro.hardware", prop_value, "error");
+		if (strcmp(prop_value, "error")) {
+			string fstab_device = "/fstab."; fstab_device += prop_value;
+			vsymlink(fstab_device, "/etc/recovery.fstab");
+		}
+	}
+	return is_fstab_symlinked;
+}
+
+void Restore_Recovery_Fstab(void) {
+	unlink("/etc/recovery.fstab");
+	vrename("/etc/recovery-fstab-orig", "/etc/recovery.fstab");
+}
+
+
+/* Additional Services Functions */
+#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
+typedef struct {
+	string Service_Name;
+	string Service_Path;
+	string Service_Binary;
+
+	string VOLD_Service_Name;
+	string TWRP_Service_Name;
+	bool is_running;
+	bool resume;
+	bool bin_exists;
+	bool svc_exists;
+} AdditionalService;
+
+typedef struct {
+	string Service_Name;
+	string Service_Path;
+	string Service_Binary;
+} RC_Service;
+
+// expand_props() courtesy of platform_system_core_init_util.cpp
+bool expand_props(const std::string& src, std::string* dst) {
+	const char* src_ptr = src.c_str();
+
+	if (!dst) {
+		return false;
+	}
+
+	/* - variables can either be $x.y or ${x.y}, in case they are only part
+	 *   of the string.
+	 * - will accept $$ as a literal $.
+	 * - no nested property expansion, i.e. ${foo.${bar}} is not supported,
+	 *   bad things will happen
+	 * - ${x.y:-default} will return default value if property empty.
+	 */
+	while (*src_ptr) {
+		const char* c;
+
+		c = strchr(src_ptr, '$');
+		if (!c) {
+			dst->append(src_ptr);
+			return true;
+		}
+
+		dst->append(src_ptr, c);
+		c++;
+
+		if (*c == '$') {
+			dst->push_back(*(c++));
+			src_ptr = c;
+			continue;
+		} else if (*c == '\0') {
+			return true;
+		}
+
+		std::string prop_name;
+		std::string def_val;
+		if (*c == '{') {
+			c++;
+			const char* end = strchr(c, '}');
+			if (!end) {
+				// failed to find closing brace, abort.
+				return false;
+			}
+			prop_name = std::string(c, end);
+			c = end + 1;
+			size_t def = prop_name.find(":-");
+			if (def < prop_name.size()) {
+				def_val = prop_name.substr(def + 2);
+				prop_name = prop_name.substr(0, def);
+			}
+		} else {
+			prop_name = c;
+			c += prop_name.size();
+		}
+
+		if (prop_name.empty()) {
+			return false;
+		}
+
+		char prop_value[PROPERTY_VALUE_MAX];
+		property_get(prop_name.c_str(), prop_value, "");
+		std::string prop_val = prop_value;
+		if (prop_val.empty()) {
+			if (def_val.empty()) {
+				return false;
+			}
+			prop_val = def_val;
+		}
+
+		dst->append(prop_val);
+		src_ptr = c;
+	}
+
+	return true;
+}
+
+string GetArgument(const string& line, size_t argument_number, bool expand_properties) {
+	size_t beg;
+	size_t end;
+	string argument;
+
+	beg = line.find_first_not_of(" \t\r");
+	if (beg == string::npos)
+		return "";
+
+	for (size_t i = 0; i < argument_number; ++i) {
+		end = line.find_first_of(" \t\r", beg);
+		if (end == string::npos)
+			return "";
+
+		beg = line.find_first_not_of(" \t\r", end);
+		if (beg == string::npos)
+			return "";
+	}
+
+	end = line.find_first_of(" \t\r", beg);
+	if (end == string::npos)
+		argument = line.substr(beg);
+	else
+		argument = line.substr(beg, end - beg); // exclude trailing whitespace
+
+	if (expand_properties) {
+		string expanded_property_argument;
+		if (expand_props(argument, &expanded_property_argument))
+			return expanded_property_argument;
+		else
+			return "";
+	} else {
+		return argument;
+	}
+}
+
+// Very simplified .rc parser to get services
+void Parse_RC_File(const string& rc_file, vector<RC_Service>& RC_Services) {
+	ifstream file;
+
+	file.open(rc_file.c_str(), ios::in);
+	if (!file.is_open())
+		return;
+
+	size_t beg;                 // left trim
+	size_t end;                 // right trim
+	bool continuation = false;  // backslash continuation
+	string line;                // line
+	string real_line;           // trimmed line with backslash continuation removal
+	vector<string> imports;     // file names of imports (we don't want to recursively do while the file is open)
+
+	while (getline(file, line)) {
+		beg = line.find_first_not_of(" \t\r");
+		end = line.find_last_not_of(" \t\r");
+		if (end == string::npos)
+			end = line.length();
+
+		if (beg == string::npos) {
+			if (continuation)
+				continuation = false;
+			else
+				continue;
+		} else if (line[end] == '\\') {
+			continuation = true;
+			real_line += line.substr(beg, end - beg); // excluding backslash
+			continue;
+		} else if (continuation) {
+			continuation = false;
+			real_line += line.substr(beg, end - beg + 1);
+		} else {
+			real_line = line.substr(beg, end - beg + 1);
+		}
+
+		if (GetArgument(real_line, 0, false) == "import") {
+			// handle: import <file>
+			string file_name = GetArgument(real_line, 1, true);
+			if (file_name.empty()) {
+				// INVALID IMPORT
+			} else
+				imports.push_back(file_name);
+		} else if (GetArgument(real_line, 0, false) == "service") {
+			// handle: service <name> <path>
+			RC_Service svc;
+
+			svc.Service_Name = GetArgument(real_line, 1, false);
+			svc.Service_Path = GetArgument(real_line, 2, true);
+
+			if (svc.Service_Name.empty() || svc.Service_Path.empty()) {
+				// INVALID SERVICE ENTRY
+			} else {
+				beg = svc.Service_Path.find_last_of("/");
+				if (beg == string::npos)
+					svc.Service_Binary = svc.Service_Path;
+				else
+					svc.Service_Binary = svc.Service_Path.substr(beg + 1);
+
+/*
+#ifdef _USING_SHORT_SERVICE_NAMES
+				if (svc.Service_Name.length() > 16) {
+					LOGERROR("WARNING: Ignoring service %s (-> %s)\n"
+					         "         defined in %s is greater than 16 characters and will\n"
+					         "         not be able to be run by init on android-7.1 or below!\n",
+					         svc.Service_Name.c_str(), svc.Service_Path.c_str(), rc_file.c_str()
+					        );
+				}
+				else
+#endif
+*/
+				RC_Services.push_back(svc);
+			}
+		}
+		real_line.clear();
+	}
+	file.close();
+
+	for (size_t i = 0; i < imports.size(); ++i) {
+		Parse_RC_File(imports[i], RC_Services);
+	}
+}
+
+vector<AdditionalService> Get_List_Of_Additional_Services(void) {
+	vector<AdditionalService> services;
+
+	// Additional Services needed by vold_decrypt (eg qseecomd, hwservicemanager, etc)
+	vector<string> service_names = TWFunc::Split_String(TW_CRYPTO_SYSTEM_VOLD_SERVICES, " ");
+	for (size_t i = 0; i < service_names.size(); ++i) {
+		AdditionalService svc;
+		svc.Service_Name = service_names[i];
+		svc.bin_exists = false;
+		svc.svc_exists = false;
+		services.push_back(svc);
+
+#ifdef _USING_SHORT_SERVICE_NAMES
+		// Fallback code for >16 character service names which
+		// allows for multiple definitions in custom .rc files
+		if (service_names[i].length() > 12) {
+			svc.Service_Name = service_names[i].substr(0, 12); // 16-4(prefix)=12
+			svc.bin_exists = false;
+			svc.svc_exists = false;
+			services.push_back(svc);
+		}
+#endif
+	}
+
+	// Read list of all services defined in all .rc files
+	vector<RC_Service> RC_Services;
+	Parse_RC_File("/init.rc", RC_Services);
+
+
+	// Cross reference Additional Services against the .rc Services and establish
+	// availability of the binaries, otherwise disable it to avoid unnecessary
+	// delays and log spam.
+	// Also check for duplicate entries between TWRP and vold_decrypt so we can
+	// stop and restart any conflicting services.
+	for (size_t i = 0; i < RC_Services.size(); ++i) {
+		string prefix = RC_Services[i].Service_Name.substr(0, 4);
+
+#ifdef _USING_SHORT_SERVICE_NAMES
+		map<string,size_t> rc_indeces;
+#endif
+
+		if (prefix != "sys_" && prefix != "ven_") {
+#ifdef _USING_SHORT_SERVICE_NAMES
+			if (RC_Services[i].Service_Name.length() > 12) {
+				// save this entry for potential binary name match
+				rc_indeces.insert(pair<string,size_t>(RC_Services[i].Service_Binary, i));
+			}
+#endif
+			continue;
+		}
+
+		for (size_t j = 0; j < services.size(); ++j) {
+			string path = RC_Services[i].Service_Path;
+			if (prefix == "ven_" && will_VendorBin_Be_Symlinked()) {
+				path = "/system" + path; // vendor is going to get symlinked to /system/vendor
+			}
+
+			if (RC_Services[i].Service_Name == prefix + services[j].Service_Name) {
+				services[j].svc_exists = true;
+
+				if (!services[j].VOLD_Service_Name.empty() && TWFunc::Path_Exists(path)) {
+					// Duplicate match, log but use previous definition
+					LOGERROR("Service %s: VOLD_Service_Name already defined as %s\n", RC_Services[i].Service_Name.c_str(), services[j].VOLD_Service_Name.c_str());
+				}
+				else if (TWFunc::Path_Exists(path)) {
+					services[j].bin_exists = true;
+					services[j].VOLD_Service_Name = RC_Services[i].Service_Name; // prefix + service_name
+					services[j].Service_Path = RC_Services[i].Service_Path;
+					services[j].Service_Binary = RC_Services[i].Service_Binary;
+
+					if (Service_Exists(services[j].Service_Name))
+						services[j].TWRP_Service_Name = services[j].Service_Name;
+					else if (Service_Exists("system/bin" + services[j].Service_Name))
+						services[j].TWRP_Service_Name = "system/bin" + services[j].Service_Name;
+					else
+						services[j].TWRP_Service_Name.clear();
+
+#ifdef _USING_SHORT_SERVICE_NAMES
+					if (services[j].TWRP_Service_Name.empty()) {
+						// Try matching Service_Binary (due to 16 character service_name limit in 7.1 and below)
+						map<string,size_t>::iterator it = rc_indeces.find(services[j].Service_Binary);
+						if (it != rc_indeces.end()) {
+							services[j].TWRP_Service_Name = RC_Services[it->second].Service_Name;
+						}
+					}
+#endif
+				}
+				break;
+			}
+		}
+	}
+
+	LOGINFO("List of additional services for vold_decrypt:\n");
+	for (size_t i = 0; i < services.size(); ++i) {
+		if (!services[i].svc_exists) {
+			LOGINFO("    %s: Disabled due to lack of .rc service entry\n", services[i].Service_Name.c_str());
+		} else if (services[i].bin_exists) {
+			if (services[i].TWRP_Service_Name.empty()) {
+				LOGINFO("    %s: Enabled as %s -> %s\n",
+				        services[i].Service_Name.c_str(),
+				        services[i].VOLD_Service_Name.c_str(), services[i].Service_Path.c_str()
+				       );
+			} else {
+				LOGINFO("    %s: Enabled as %s -> %s (temporarily replacing TWRP's %s service)\n",
+				        services[i].Service_Name.c_str(),
+				        services[i].VOLD_Service_Name.c_str(), services[i].Service_Path.c_str(),
+				        services[i].TWRP_Service_Name.c_str()
+				       );
+			}
+		}
+		else {
+			LOGINFO("    %s: Disabled due to lack of matching binary\n", services[i].Service_Name.c_str());
+		}
+	}
+	return services;
+}
+#endif
+
+
+/* Misc Functions */
+void Set_Needed_Properties(void) {
+	// vold won't start without ro.storage_structure on Kitkat
+	string sdkverstr = TWFunc::System_Property_Get("ro.build.version.sdk");
+	if (!sdkverstr.empty()) {
+		sdkver = atoi(sdkverstr.c_str());
+	}
+	if (sdkver <= 19) {
+		string ro_storage_structure = TWFunc::System_Property_Get("ro.storage_structure");
+		if (!ro_storage_structure.empty())
+			property_set("ro.storage_structure", ro_storage_structure.c_str());
+	}
+	property_set("hwservicemanager.ready", "false");
+	property_set("sys.listeners.registered", "false");
+	property_set("vendor.sys.listeners.registered", "false");
+}
+
+void Update_Patch_Level(void) {
+	// On Oreo and above, keymaster requires Android version & patch level to match installed system
+	string sdkverstr = TWFunc::System_Property_Get("ro.build.version.sdk");
+	if (!sdkverstr.empty()) {
+		sdkver = atoi(sdkverstr.c_str());
+	}
+	if (sdkver <= 25) {
+		property_set("vold_decrypt.legacy_system", "true");
+	} else {
+		LOGINFO("Current system is Oreo or above. Setting OS version and security patch level from installed system...\n");
+		property_set("vold_decrypt.legacy_system", "false");
+	}
+
+	char prop_value[PROPERTY_VALUE_MAX];
+	char legacy_system_value[PROPERTY_VALUE_MAX] = "false";
+	property_get("vold_decrypt.legacy_system", prop_value, "");
+
+	// Only set OS ver and patch level if device uses Oreo+ system
+	if (strcmp(prop_value, legacy_system_value) == 0) {
+		property_get("ro.build.version.release", prop_value, "");
+		std::string osver_orig = prop_value;
+		property_set("vold_decrypt.osver_orig", osver_orig.c_str());
+		LOGINFO("Current OS version: %s\n", osver_orig.c_str());
+
+		int error = 0;
+		std::string osver = TWFunc::System_Property_Get("ro.build.version.release");
+		if (!(osver == osver_orig)) {
+			if (!(error = TWFunc::Property_Override("ro.build.version.release", osver))) {
+				LOGINFO("Property override successful! New OS version: %s\n", osver.c_str());
+			} else {
+				LOGERROR("Property override failed, code %d\n", error);
+				return;
+			}
+			// TODO: Confirm whether we actually need to update the props in prop.default
+			std::string sed_osver = "sed -i 's/ro.build.version.release=.*/ro.build.version.release=" + osver + "/g' /prop.default";
+			TWFunc::Exec_Cmd(sed_osver);
+			property_set("vold_decrypt.osver_set", "true");
+		} else {
+			LOGINFO("Current OS version & System OS version already match. Proceeding to next step.\n");
+			property_set("vold_decrypt.osver_set", "false");
+		}
+
+		property_get("ro.build.version.security_patch", prop_value, "");
+		std::string patchlevel_orig = prop_value;
+		property_set("vold_decrypt.patchlevel_orig", patchlevel_orig.c_str());
+		LOGINFO("Current security patch level: %s\n", patchlevel_orig.c_str());
+
+		std::string patchlevel = TWFunc::System_Property_Get("ro.build.version.security_patch");
+		if (!(patchlevel == patchlevel_orig)) {
+			if (!(error = TWFunc::Property_Override("ro.build.version.security_patch", patchlevel))) {
+				LOGINFO("Property override successful! New security patch level: %s\n", patchlevel.c_str());
+			} else {
+				LOGERROR("Property override failed, code %d\n", error);
+				return;
+			}
+			// TODO: Confirm whether we actually need to update the props in prop.default
+			std::string sed_patchlevel = "sed -i 's/ro.build.version.security_patch=.*/ro.build.version.security_patch=" + patchlevel + "/g' /prop.default";
+			TWFunc::Exec_Cmd(sed_patchlevel);
+			property_set("vold_decrypt.patchlevel_set", "true");
+		} else {
+			LOGINFO("Current security patch level & System security patch level already match. Proceeding to next step.\n");
+			property_set("vold_decrypt.patchlevel_set", "false");
+		}
+		return;
+	} else {
+		LOGINFO("Current system is Nougat or older. Skipping OS version and security patch level setting...\n");
+		return;
+	}
+}
+
+void Revert_Patch_Level(void) {
+	char osver_set[PROPERTY_VALUE_MAX];
+	char patchlevel_set[PROPERTY_VALUE_MAX];
+	char osver_patchlevel_set[PROPERTY_VALUE_MAX] = "false";
+
+	property_get("vold_decrypt.osver_set", osver_set, "");
+	property_get("vold_decrypt.patchlevel_set", patchlevel_set, "");
+
+	int osver_result = strcmp(osver_set, osver_patchlevel_set);
+	int patchlevel_result = strcmp(patchlevel_set, osver_patchlevel_set);
+	if (!(osver_result == 0 && patchlevel_result == 0)) {
+		char prop_value[PROPERTY_VALUE_MAX];
+		LOGINFO("Reverting OS version and security patch level to original TWRP values...\n");
+		property_get("vold_decrypt.osver_orig", prop_value, "");
+		std::string osver_orig = prop_value;
+		property_get("ro.build.version.release", prop_value, "");
+		std::string osver = prop_value;
+
+		int error = 0;
+		if (!(osver == osver_orig)) {
+			if (!(error = TWFunc::Property_Override("ro.build.version.release", osver_orig))) {
+				LOGINFO("Property override successful! Original OS version: %s\n", osver_orig.c_str());
+			} else {
+				LOGERROR("Property override failed, code %d\n", error);
+				return;
+			}
+			// TODO: Confirm whether we actually need to update the props in prop.default
+			std::string sed_osver_orig = "sed -i 's/ro.build.version.release=.*/ro.build.version.release=" + osver_orig + "/g' /prop.default";
+			TWFunc::Exec_Cmd(sed_osver_orig);
+		}
+
+		property_get("vold_decrypt.patchlevel_orig", prop_value, "");
+		std::string patchlevel_orig = prop_value;
+		property_get("ro.build.version.security_patch", prop_value, "");
+		std::string patchlevel = prop_value;
+
+		if (!(patchlevel == patchlevel_orig)) {
+			if (!(error = TWFunc::Property_Override("ro.build.version.security_patch", patchlevel_orig))) {
+				LOGINFO("Property override successful! Original security patch level: %s\n", patchlevel_orig.c_str());
+			} else {
+				LOGERROR("Property override failed, code %d\n", error);
+				return;
+			}
+			// TODO: Confirm whether we actually need to update the props in prop.default
+			std::string sed_patchlevel_orig = "sed -i 's/ro.build.version.security_patch=.*/ro.build.version.security_patch=" + patchlevel_orig + "/g' /prop.default";
+			TWFunc::Exec_Cmd(sed_patchlevel_orig);
+		}
+	} else {
+		return;
+	}
+}
+
+static unsigned int get_blkdev_size(int fd) {
+	unsigned long nr_sec;
+
+	if ( (ioctl(fd, BLKGETSIZE, &nr_sec)) == -1) {
+		nr_sec = 0;
+	}
+
+	return (unsigned int) nr_sec;
+}
+
+#define CRYPT_FOOTER_OFFSET 0x4000
+static char footer[16 * 1024];
+const char* userdata_path;
+static off64_t offset;
+
+int footer_br(const string& command) {
+	int fd;
+
+	if (command == "backup") {
+		unsigned int nr_sec;
+		TWPartition* userdata = PartitionManager.Find_Partition_By_Path("/data");
+		userdata_path = userdata->Actual_Block_Device.c_str();
+		fd = open(userdata_path, O_RDONLY);
+		if (fd < 0) {
+			LOGERROR("E:footer_backup: Cannot open '%s': %s\n", userdata_path, strerror(errno));
+			return -1;
+		}
+		if ((nr_sec = get_blkdev_size(fd))) {
+			offset = ((off64_t)nr_sec * 512) - CRYPT_FOOTER_OFFSET;
+		} else {
+			LOGERROR("E:footer_br: Failed to get offset\n");
+			close(fd);
+			return -1;
+		}
+		if (lseek64(fd, offset, SEEK_SET) == -1) {
+			LOGERROR("E:footer_backup: Failed to lseek64\n");
+			close(fd);
+			return -1;
+		}
+		if (read(fd, footer, sizeof(footer)) != sizeof(footer)) {
+			LOGERROR("E:footer_br: Failed to read: %s\n", strerror(errno));
+			close(fd);
+			return -1;
+		}
+		close(fd);
+	} else if (command == "restore") {
+		fd = open(userdata_path, O_WRONLY);
+		if (fd < 0) {
+			LOGERROR("E:footer_restore: Cannot open '%s': %s\n", userdata_path, strerror(errno));
+			return -1;
+		}
+		if (lseek64(fd, offset, SEEK_SET) == -1) {
+			LOGERROR("E:footer_restore: Failed to lseek64\n");
+			close(fd);
+			return -1;
+		}
+		if (write(fd, footer, sizeof(footer)) != sizeof(footer)) {
+			LOGERROR("E:footer_br: Failed to write: %s\n", strerror(errno));
+			close(fd);
+			return -1;
+		}
+		close(fd);
+	} else {
+		LOGERROR("E:footer_br: wrong command argument: %s\n", command.c_str());
+		return -1;
+	}
+	return 0;
+}
+
+/* vdc Functions */
+typedef struct {
+	string Output;     // Entire line excluding \n
+	int ResponseCode;  // ResponseCode.h (int)
+	int Sequence;      // Sequence (int)
+	int Message;       // Message (string) but we're only interested in int
+} vdc_ReturnValues;
+
+int Exec_vdc_cryptfs(const string& command, const string& argument, vdc_ReturnValues* vdcResult) {
+	pid_t pid;
+	int status;
+	int pipe_fd[2][2];
+
+	vdcResult->Output.clear();
+	vdcResult->ResponseCode = vdcResult->Sequence = vdcResult->Message = -1;
+
+	for (int i = 0; i < 2; ++i) {
+		if (pipe(pipe_fd[i])) {
+			LOGERROR("exec_vdc_cryptfs: pipe() error!\n");
+			return -1;
+		}
+	}
+
+	// getpwtype and checkpw commands are removed from Pie vdc, using modified vdc_pie
+	const char *cmd[] = { "/system/bin/vdc_pie", "cryptfs" };
+	if (sdkver < 28)
+		cmd[0] = "/system/bin/vdc";
+	const char *env[] = { "LD_LIBRARY_PATH=/system/lib64:/system/lib", NULL };
+
+	LOGINFO("sdkver: %d, using %s\n", sdkver, cmd[0]);
+
+#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG
+	string log_name = "/tmp/strace_vdc_" + command;
+#endif
+
+	switch(pid = fork())
+	{
+		case -1:
+			LOGERROR("exec_vdc_cryptfs: fork failed: %d (%s)!\n", errno, strerror(errno));
+			return -1;
+
+		case 0: // child
+			fflush(stdout); fflush(stderr);
+			for (int i = 0; i < 2; ++i) {
+				close(pipe_fd[i][0]);
+				dup2(pipe_fd[i][1], ((i == 0) ? STDOUT_FILENO : STDERR_FILENO));
+				close(pipe_fd[i][1]);
+			}
+
+#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG
+			if (has_strace) {
+				if (argument.empty())
+					execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", log_name.c_str(),
+						"-E", env[0], cmd[0], cmd[1], command.c_str(), NULL);
+				else
+					execl(VD_STRACE_BIN, "strace", "-q", "-tt", "-ff", "-v", "-y", "-s", "1000", "-o", log_name.c_str(),
+						  "-E", env[0], cmd[0], cmd[1], command.c_str(), argument.c_str(), NULL);
+			} else
+#endif
+			if (argument.empty())
+				execle(cmd[0], cmd[0], cmd[1], command.c_str(), NULL, env);
+			else
+				execle(cmd[0], cmd[0], cmd[1], command.c_str(), argument.c_str(), NULL, env);
+			_exit(127);
+			break;
+
+		default:
+		{
+			int timeout = 30*100;
+
+			for (int i = 0; i < 2; ++i) {
+				close(pipe_fd[i][1]);
+
+				// Non-blocking read loop with timeout
+				int flags = fcntl(pipe_fd[i][0], F_GETFL, 0);
+				fcntl(pipe_fd[i][0], F_SETFL, flags | O_NONBLOCK);
+			}
+
+			char buffer[128];
+			ssize_t count;
+			string strout[2];
+			pid_t retpid = waitpid(pid, &status, WNOHANG);
+			while (true) {
+				for (int i = 0; i < 2; ++i) {
+					count = read(pipe_fd[i][0], buffer, sizeof(buffer));
+					if (count == -1) {
+						if (errno == EINTR)
+							continue;
+						else if (errno != EAGAIN)
+							LOGERROR("exec_vdc_cryptfs: read() error %d (%s)\n!", errno, strerror(errno));
+					} else if (count > 0) {
+						strout[i].append(buffer, count);
+					}
+				}
+
+				retpid = waitpid(pid, &status, WNOHANG);
+				if (retpid == 0 && --timeout)
+					usleep(10000);
+				else
+					break;
+			};
+
+			for (int i = 0; i < 2; ++i) {
+				close(pipe_fd[i][0]);
+			}
+
+			if (!strout[0].empty()) {
+				sscanf(strout[0].c_str(), "%d %d %d", &vdcResult->ResponseCode, &vdcResult->Sequence, &vdcResult->Message);
+				vdcResult->Output = "I:" + strout[0];
+			}
+			if (!strout[1].empty()) {
+				vdcResult->Output += "E:" + strout[1];
+			}
+			std::replace(vdcResult->Output.begin(), vdcResult->Output.end(), '\n', '|');
+
+			if (!vdcResult->Output.empty() && vdcResult->Output[vdcResult->Output.length() - 1] != '|')
+				vdcResult->Output += "|";
+			vdcResult->Output += "RC=" + TWFunc::to_string(WEXITSTATUS(status));
+
+			// Error handling
+			if (retpid == 0 && timeout == 0) {
+				LOGERROR("exec_vdc_cryptfs: took too long, killing process\n");
+				kill(pid, SIGKILL);
+				for (timeout = 5; retpid == 0 && timeout; --timeout) {
+					sleep(1);
+					retpid = waitpid(pid, &status, WNOHANG);
+				}
+				if (retpid)
+					LOGINFO("exec_vdc_cryptfs: process killed successfully\n");
+				else
+					LOGERROR("exec_vdc_cryptfs: process took too long to kill, may be a zombie process\n");
+				return VD_ERR_VOLD_OPERATION_TIMEDOUT;
+			} else if (retpid > 0) {
+				if (WIFSIGNALED(status)) {
+					LOGERROR("exec_vdc_cryptfs: process ended with signal: %d\n", WTERMSIG(status)); // Seg fault or some other non-graceful termination
+					return -1;
+				}
+			} else if (retpid < 0) { // no PID returned
+				if (errno == ECHILD)
+					LOGINFO("exec_vdc_cryptfs: no child process exist\n");
+				else {
+					LOGERROR("exec_vdc_cryptfs: Unexpected error %d (%s)\n", errno, strerror(errno));
+					return -1;
+				}
+			}
+			if (sdkver >= 28) {
+				return WEXITSTATUS(status);
+			}
+			return 0;
+		}
+	}
+}
+
+int Run_vdc(const string& Password) {
+	int res;
+	struct timeval t1, t2;
+	vdc_ReturnValues vdcResult;
+
+	LOGINFO("About to run vdc...\n");
+
+	// Pie vdc communicates with vold directly, no socket so lets not waste time
+	if (sdkver < 28) {
+		// Wait for vold connection
+		gettimeofday(&t1, NULL);
+		t2 = t1;
+		while ((t2.tv_sec - t1.tv_sec) < 5) {
+			// cryptfs getpwtype returns: R1=213(PasswordTypeResult)   R2=?   R3="password", "pattern", "pin", "default"
+			res = Exec_vdc_cryptfs("getpwtype", "", &vdcResult);
+			if (vdcResult.ResponseCode == PASSWORD_TYPE_RESULT) {
+				res = 0;
+				break;
+			}
+			LOGINFO("Retrying connection to vold (Reason: %s)\n", vdcResult.Output.c_str());
+			usleep(SLEEP_MIN_USEC); // vdc usually usleep(10000), but that causes too many unnecessary attempts
+			gettimeofday(&t2, NULL);
+		}
+
+		if (res == 0 && (t2.tv_sec - t1.tv_sec) < 5)
+			LOGINFO("Connected to vold: %s\n", vdcResult.Output.c_str());
+		else if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT)
+			return VD_ERR_VOLD_OPERATION_TIMEDOUT; // should never happen for getpwtype
+		else if (res)
+			return VD_ERR_FORK_EXECL_ERROR;
+		else if (vdcResult.ResponseCode != -1)
+			return VD_ERR_VOLD_UNEXPECTED_RESPONSE;
+		else
+			return VD_ERR_VDC_FAILED_TO_CONNECT;
+	}
+
+	// Input password from GUI, or default password
+	res = Exec_vdc_cryptfs("checkpw", Password, &vdcResult);
+	if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT)
+		return VD_ERR_VOLD_OPERATION_TIMEDOUT;
+	else if (res)
+		return VD_ERR_FORK_EXECL_ERROR;
+
+	LOGINFO("vdc cryptfs result (passwd): %s\n", vdcResult.Output.c_str());
+	/*
+	if (res == 0 && vdcResult.ResponseCode != COMMAND_OKAY)
+		return VD_ERR_VOLD_UNEXPECTED_RESPONSE;
+	*/
+
+	// our vdc returns vold binder op status,
+    // we care about status.ok() only which is 0
+	if (sdkver >= 28) {
+		vdcResult.Message = res;
+	}
+
+	if (vdcResult.Message != 0) {
+		// try falling back to Lollipop hex passwords
+		string hexPassword = convert_key_to_hex_ascii(Password);
+		res = Exec_vdc_cryptfs("checkpw", hexPassword, &vdcResult);
+		if (res == VD_ERR_VOLD_OPERATION_TIMEDOUT)
+			return VD_ERR_VOLD_OPERATION_TIMEDOUT;
+		else if (res)
+			return VD_ERR_FORK_EXECL_ERROR;
+
+		LOGINFO("vdc cryptfs result (hex_pw): %s\n", vdcResult.Output.c_str());
+		/*
+		if (res == 0 && vdcResult.ResponseCode != COMMAND_OKAY)
+			return VD_ERR_VOLD_UNEXPECTED_RESPONSE;
+		*/
+	}
+
+	// sdk < 28 vdc's return value is dependant upon source origin, it will either
+	// return 0 or ResponseCode, so disregard and focus on decryption instead
+	// our vdc always returns 0 on success
+	if (vdcResult.Message == 0) {
+		// Decryption successful wait for crypto blk dev
+		Wait_For_Property("ro.crypto.fs_crypto_blkdev");
+		res = VD_SUCCESS;
+	} else if (vdcResult.ResponseCode != COMMAND_OKAY) {
+		res = VD_ERR_VOLD_UNEXPECTED_RESPONSE;
+	} else {
+		res = VD_ERR_DECRYPTION_FAILED;
+	}
+
+	return res;
+}
+
+int Vold_Decrypt_Core(const string& Password) {
+	int res;
+	bool is_vendor_symlinked = false;
+	bool is_firmware_symlinked = false;
+	bool is_fstab_symlinked = false;
+	bool is_vold_running = false;
+
+	if (Password.empty()) {
+		LOGINFO("vold_decrypt: password is empty!\n");
+		return VD_ERR_PASSWORD_EMPTY;
+	}
+
+	// Mount ANDROID_ROOT and check for vold and vdc
+	if (!PartitionManager.Mount_By_Path(PartitionManager.Get_Android_Root_Path(), true)) {
+		return VD_ERR_UNABLE_TO_MOUNT_SYSTEM;
+	} else if ((!TWFunc::Path_Exists("/system/bin/vold")) && (!TWFunc::Path_Exists(PartitionManager.Get_Android_Root_Path() + "/system/bin/vold"))) {
+		LOGINFO("ERROR: vold not found, aborting.\n");
+		return VD_ERR_MISSING_VOLD;
+	} else if ((!TWFunc::Path_Exists("/system/bin/vdc")) && (!TWFunc::Path_Exists(PartitionManager.Get_Android_Root_Path() + "/system/bin/vdc"))) {
+		LOGINFO("ERROR: vdc not found, aborting.\n");
+		return VD_ERR_MISSING_VDC;
+	}
+
+#ifdef TW_CRYPTO_SYSTEM_VOLD_MOUNT
+	vector<string> partitions = TWFunc::Split_String(TW_CRYPTO_SYSTEM_VOLD_MOUNT, " ");
+	for (size_t i = 0; i < partitions.size(); ++i) {
+		string mnt_point = "/" + partitions[i];
+		if(PartitionManager.Find_Partition_By_Path(mnt_point)) {
+			if (!PartitionManager.Mount_By_Path(mnt_point, true)) {
+				LOGERROR("Unable to mount %s\n", mnt_point.c_str());
+				return VD_ERR_UNABLE_TO_MOUNT_EXTRA;
+			}
+			LOGINFO("%s partition mounted\n", partitions[i].c_str());
+		}
+	}
+#endif
+
+	fp_kmsg = fopen("/dev/kmsg", "a");
+
+	LOGINFO("TW_CRYPTO_USE_SYSTEM_VOLD := true\n");
+
+	// just cache the result to avoid unneeded duplicates in recovery.log
+	LOGINFO("Checking existence of vendor and firmware partitions...\n");
+	is_Vendor_Mounted();
+	is_Firmware_Mounted();
+
+	LOGINFO("Attempting to use system's vold for decryption...\n");
+
+#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG
+	Strace_init_Start();
+#endif
+
+#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
+	vector<AdditionalService> Services = Get_List_Of_Additional_Services();
+
+	// Check if TWRP is running any of the services
+	for (size_t i = 0; i < Services.size(); ++i) {
+		if (!Services[i].TWRP_Service_Name.empty() && !Is_Service_Stopped(Services[i].TWRP_Service_Name)) {
+			Services[i].resume = true;
+			Stop_Service(Services[i].TWRP_Service_Name);
+		} else
+			Services[i].resume = false;
+	}
+#endif
+
+	LOGINFO("Setting up folders and permissions...\n");
+	is_fstab_symlinked = Symlink_Recovery_Fstab();
+	is_vendor_symlinked = Symlink_Vendor_Folder();
+	is_firmware_symlinked = Symlink_Firmware_Folder();
+	Symlink_Firmware_Files(is_vendor_symlinked, is_firmware_symlinked);
+
+	Set_Needed_Properties();
+#ifdef TW_INCLUDE_LIBRESETPROP
+	Update_Patch_Level();
+#endif
+
+	// Start services needed for vold decrypt
+	LOGINFO("Starting services...\n");
+#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
+	for (size_t i = 0; i < Services.size(); ++i) {
+		if (Services[i].bin_exists) {
+			if (Services[i].Service_Binary.find("keymaster") != string::npos) {
+				Wait_For_Property("hwservicemanager.ready", 500000, "true");
+				LOGINFO("    hwservicemanager is ready.\n");
+			}
+
+			Services[i].is_running = Start_Service(Services[i].VOLD_Service_Name);
+
+			if (Services[i].Service_Binary == "qseecomd") {
+				if (Wait_For_Property("sys.listeners.registered", 500000, "true") == "true"
+						|| Wait_For_Property("vendor.sys.listeners.registered", 500000, "true") == "true")
+					LOGINFO("    qseecomd listeners registered.\n");
+			}
+		}
+	}
+#endif
+	is_vold_running = Start_Service("sys_vold");
+
+	if (is_vold_running) {
+#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
+		for (size_t i = 0; i < Services.size(); ++i) {
+			if (Services[i].bin_exists && !Is_Service_Running(Services[i].VOLD_Service_Name) && Services[i].resume) {
+				// if system_service has died restart the twrp_service
+				LOGINFO("%s is not running, resuming %s!\n", Services[i].VOLD_Service_Name.c_str(), Services[i].TWRP_Service_Name.c_str());
+				Start_Service(Services[i].TWRP_Service_Name);
+			}
+		}
+#endif
+
+		/*
+		* Oreo and Pie vold on some devices alters footer causing
+		* system to ask for decryption password at next boot although
+		* password haven't changed so we save footer before and restore it
+		* after vold operations
+		*/
+		if (sdkver > 25) {
+			if (footer_br("backup") == 0) {
+				LOGINFO("footer_br: crypto footer backed up\n");
+				res = Run_vdc(Password);
+				if (footer_br("restore") == 0)
+					LOGINFO("footer_br: crypto footer restored\n");
+				else
+					LOGERROR("footer_br: Failed to restore crypto footer\n");
+			} else {
+				LOGERROR("footer_br: Failed to backup crypto footer, \
+					skipping decrypt to prevent data loss. Reboot recovery to try again...\n");
+				res = -1;
+			}
+		} else {
+			res = Run_vdc(Password);
+		}
+
+		if (res != 0) {
+			LOGINFO("Decryption failed\n");
+		}
+	} else {
+		LOGINFO("Failed to start vold\n");
+		res = VD_ERR_VOLD_FAILED_TO_START;
+	}
+#ifdef TW_INCLUDE_LIBRESETPROP
+	Revert_Patch_Level();
+#endif
+	// Stop services needed for vold decrypt so /system can be unmounted
+	LOGINFO("Stopping services...\n");
+	Stop_Service("sys_vold");
+#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
+	for (size_t i = 0; i < Services.size(); ++i) {
+		if (!Is_Service_Running(Services[i].VOLD_Service_Name) && Services[i].resume)
+			Stop_Service(Services[i].TWRP_Service_Name);
+		else if (Services[i].bin_exists)
+			Stop_Service(Services[i].VOLD_Service_Name);
+	}
+#endif
+
+	if (is_firmware_symlinked)
+		Restore_Firmware_Folder();
+	if (is_vendor_symlinked)
+		Restore_Vendor_Folder();
+	if (is_fstab_symlinked)
+		Restore_Recovery_Fstab();
+
+	if (!PartitionManager.UnMount_By_Path(PartitionManager.Get_Android_Root_Path(), true)) {
+		// PartitionManager failed to unmount ANDROID_ROOT, this should not happen,
+		// but in case it does, do a lazy unmount
+		LOGINFO("WARNING: '%s' could not be unmounted normally!\n", PartitionManager.Get_Android_Root_Path().c_str());
+		umount2(PartitionManager.Get_Android_Root_Path().c_str(), MNT_DETACH);
+	}
+
+#ifdef TW_CRYPTO_SYSTEM_VOLD_MOUNT
+	for (size_t i = 0; i < partitions.size(); ++i) {
+		string mnt_point = "/" + partitions[i];
+		if(PartitionManager.Is_Mounted_By_Path(mnt_point)) {
+			if (!PartitionManager.UnMount_By_Path(mnt_point, true)) {
+				LOGINFO("WARNING: %s partition could not be unmounted normally!\n", partitions[i].c_str());
+				umount2(mnt_point.c_str(), MNT_DETACH);
+			}
+		}
+	}
+#endif
+
+	LOGINFO("Finished.\n");
+
+#ifdef TW_CRYPTO_SYSTEM_VOLD_SERVICES
+	Set_Needed_Properties();
+	// Restart previously running services
+	for (size_t i = 0; i < Services.size(); ++i) {
+		if (Services[i].resume) {
+			if (Services[i].Service_Binary.find("keymaster") != string::npos) {
+				Wait_For_Property("hwservicemanager.ready", 500000, "true");
+				LOGINFO("    hwservicemanager is ready.\n");
+			}
+
+			Start_Service(Services[i].TWRP_Service_Name);
+
+			if (Services[i].Service_Binary == "qseecomd") {
+				if (Wait_For_Property("sys.listeners.registered", 500000, "true") == "true"
+						|| Wait_For_Property("vendor.sys.listeners.registered", 500000, "true") == "true")
+					LOGINFO("    qseecomd listeners registered.\n");
+			}
+		}
+	}
+#endif
+
+#ifdef TW_CRYPTO_SYSTEM_VOLD_DEBUG
+	Strace_init_Stop();
+#endif
+
+	// Finish up and exit
+	if (fp_kmsg) {
+		fflush(fp_kmsg);
+		fclose(fp_kmsg);
+	}
+
+	return res;
+}
+
+} // namespace
+
+/*
+ * Common vold Response Codes / Errors:
+ * 406 (OpFailedStorageNotFound) -> Problem reading or parsing fstab
+ *
+ */
+
+/* Main function separated from core in case we want to return error info */
+int vold_decrypt(const string& Password) {
+	return Vold_Decrypt_Core(Password);
+}
diff --git a/crypto/vold_decrypt/vold_decrypt.h b/crypto/vold_decrypt/vold_decrypt.h
new file mode 100644
index 0000000..248a427
--- /dev/null
+++ b/crypto/vold_decrypt/vold_decrypt.h
@@ -0,0 +1,43 @@
+/*
+    Copyright 2017 TeamWin
+    This file is part of TWRP/TeamWin Recovery Project.
+
+    TWRP is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    TWRP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VOLD_DECRYPT_H
+#define _VOLD_DECRYPT_H
+
+#include <string>
+
+// -_-
+enum {
+	VD_SUCCESS                      = 0,
+	VD_ERR_DECRYPTION_FAILED        = -1,
+	VD_ERR_UNABLE_TO_MOUNT_SYSTEM   = -2,
+	VD_ERR_MISSING_VOLD             = -3,
+	VD_ERR_MISSING_VDC              = -4,
+	VD_ERR_VDC_FAILED_TO_CONNECT    = -5,
+	VD_ERR_VOLD_FAILED_TO_START     = -6,
+	VD_ERR_VOLD_UNEXPECTED_RESPONSE = -7,
+	VD_ERR_VOLD_OPERATION_TIMEDOUT  = -8,
+	VD_ERR_FORK_EXECL_ERROR         = -9,
+	VD_ERR_PASSWORD_EMPTY           = -10,
+    VD_ERR_UNABLE_TO_MOUNT_EXTRA   = -11,
+};
+
+
+int vold_decrypt(const std::string& Password);
+
+#endif // _VOLD_DECRYPT_H
diff --git a/data.cpp b/data.cpp
new file mode 100755
index 0000000..4e32dc4
--- /dev/null
+++ b/data.cpp
@@ -0,0 +1,1195 @@
+/*
+	Copyright 2012 to 2021 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <pthread.h>
+#include <time.h>
+#include <string>
+#include <sstream>
+#include <fstream>
+#include <cctype>
+#include <cutils/properties.h>
+#include <unistd.h>
+
+#include "variables.h"
+#include "data.hpp"
+#include "partitions.hpp"
+#include "twrp-functions.hpp"
+#ifndef TW_NO_SCREEN_TIMEOUT
+#include "gui/blanktimer.hpp"
+#endif
+#include "find_file.hpp"
+#include "set_metadata.h"
+#include "gui/gui.hpp"
+#include "infomanager.hpp"
+
+#define DEVID_MAX 64
+#define HWID_MAX 32
+
+extern "C"
+{
+	#include "twcommon.h"
+	#include "gui/pages.h"
+	void gui_notifyVarChange(const char *name, const char* value);
+}
+#include "minuitwrp/minui.h"
+
+#define FILE_VERSION 0x00010010 // Do not set to 0
+
+using namespace std;
+
+string                                  DataManager::mBackingFile;
+int                                     DataManager::mInitialized = 0;
+InfoManager                             DataManager::mPersist;  // Data that that is not constant and will be saved to the settings file
+InfoManager                             DataManager::mData;     // Data that is not constant and will not be saved to settings file
+InfoManager                             DataManager::mConst;    // Data that is constant and will not be saved to settings file
+
+extern bool datamedia;
+
+#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+pthread_mutex_t DataManager::m_valuesLock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
+#else
+pthread_mutex_t DataManager::m_valuesLock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+#endif
+
+// Device ID functions
+void DataManager::sanitize_device_id(char* device_id) {
+	const char* whitelist ="-._";
+	char str[DEVID_MAX];
+	char* c = str;
+
+	snprintf(str, DEVID_MAX, "%s", device_id);
+	memset(device_id, 0, strlen(device_id));
+	while (*c) {
+		if (isalnum(*c) || strchr(whitelist, *c))
+			strncat(device_id, c, 1);
+		c++;
+	}
+	return;
+}
+
+#define CMDLINE_SERIALNO		"androidboot.serialno="
+#define CMDLINE_SERIALNO_LEN	(strlen(CMDLINE_SERIALNO))
+#define CPUINFO_SERIALNO		"Serial"
+#define CPUINFO_SERIALNO_LEN	(strlen(CPUINFO_SERIALNO))
+#define CPUINFO_HARDWARE		"Hardware"
+#define CPUINFO_HARDWARE_LEN	(strlen(CPUINFO_HARDWARE))
+
+void DataManager::get_device_id(void) {
+	FILE *fp;
+	char line[2048];
+	char hardware_id[HWID_MAX] = { 0 };
+	char device_id[DEVID_MAX] = { 0 };
+	char* token;
+
+#ifdef TW_USE_MODEL_HARDWARE_ID_FOR_DEVICE_ID
+	// Use (product_model)_(hardware_id) as device id
+	char model_id[PROPERTY_VALUE_MAX];
+	property_get("ro.product.model", model_id, "error");
+	if (strcmp(model_id, "error") != 0) {
+		LOGINFO("=> product model: '%s'\n", model_id);
+		// Replace spaces with underscores
+		for (size_t i = 0; i < strlen(model_id); i++) {
+			if (model_id[i] == ' ')
+				model_id[i] = '_';
+		}
+		snprintf(device_id, DEVID_MAX, "%s", model_id);
+
+		if (strlen(device_id) < DEVID_MAX) {
+			fp = fopen("proc_cpuinfo.txt", "rt");
+			if (fp != NULL) {
+				while (fgets(line, sizeof(line), fp) != NULL) {
+					if (memcmp(line, CPUINFO_HARDWARE,
+							CPUINFO_HARDWARE_LEN) == 0) {
+						// skip past "Hardware", spaces, and colon
+						token = line + CPUINFO_HARDWARE_LEN;
+						while (*token && (!isgraph(*token) || *token == ':'))
+							token++;
+
+						if (*token && *token != '\n'
+								&& strcmp("UNKNOWN\n", token)) {
+							snprintf(hardware_id, HWID_MAX, "%s", token);
+							if (hardware_id[strlen(hardware_id)-1] == '\n')
+								hardware_id[strlen(hardware_id)-1] = 0;
+							LOGINFO("=> hardware id from cpuinfo: '%s'\n",
+									hardware_id);
+						}
+						break;
+					}
+				}
+				fclose(fp);
+			}
+		}
+
+		if (hardware_id[0] != 0)
+			snprintf(device_id, DEVID_MAX, "%s_%s", model_id, hardware_id);
+
+		sanitize_device_id(device_id);
+		mConst.SetValue("device_id", device_id);
+		LOGINFO("=> using device id: '%s'\n", device_id);
+		return;
+	}
+#endif
+
+#ifndef TW_FORCE_CPUINFO_FOR_DEVICE_ID
+#ifdef TW_USE_SERIALNO_PROPERTY_FOR_DEVICE_ID
+	// Check serial number system property
+	if (property_get("ro.serialno", line, "")) {
+		snprintf(device_id, DEVID_MAX, "%s", line);
+		sanitize_device_id(device_id);
+		mConst.SetValue("device_id", device_id);
+		return;
+	}
+#endif
+
+	// Check the cmdline to see if the serial number was supplied
+	fp = fopen("/proc/cmdline", "rt");
+	if (fp != NULL) {
+		fgets(line, sizeof(line), fp);
+		fclose(fp); // cmdline is only one line long
+
+		token = strtok(line, " ");
+		while (token) {
+			if (memcmp(token, CMDLINE_SERIALNO, CMDLINE_SERIALNO_LEN) == 0) {
+				token += CMDLINE_SERIALNO_LEN;
+				snprintf(device_id, DEVID_MAX, "%s", token);
+				sanitize_device_id(device_id); // also removes newlines
+				mConst.SetValue("device_id", device_id);
+				return;
+			}
+			token = strtok(NULL, " ");
+		}
+	}
+#endif
+	// Check cpuinfo for serial number; if found, use as device_id
+	// If serial number is not found, fallback to hardware_id for the device_id
+	fp = fopen("/proc/cpuinfo", "rt");
+	if (fp != NULL) {
+		while (fgets(line, sizeof(line), fp) != NULL) {
+			if (memcmp(line, CPUINFO_SERIALNO, CPUINFO_SERIALNO_LEN) == 0) {
+				// skip past "Serial", spaces, and colon
+				token = line + CPUINFO_SERIALNO_LEN;
+				while (*token && (!isgraph(*token) || *token == ':'))
+					token++;
+
+				if (*token && *token != '\n') {
+					snprintf(device_id, DEVID_MAX, "%s", token);
+					sanitize_device_id(device_id); // also removes newlines
+					LOGINFO("=> serial from cpuinfo: '%s'\n", device_id);
+					mConst.SetValue("device_id", device_id);
+					fclose(fp);
+					return;
+				}
+			} else if (memcmp(line, CPUINFO_HARDWARE,
+					CPUINFO_HARDWARE_LEN) == 0) {
+				// skip past "Hardware", spaces, and colon
+				token = line + CPUINFO_HARDWARE_LEN;
+				while (*token && (!isgraph(*token) || *token == ':'))
+					token++;
+
+				if (*token && *token != '\n') {
+					snprintf(hardware_id, HWID_MAX, "%s", token);
+					if (hardware_id[strlen(hardware_id)-1] == '\n')
+						hardware_id[strlen(hardware_id)-1] = 0;
+					LOGINFO("=> hardware id from cpuinfo: '%s'\n", hardware_id);
+				}
+			}
+		}
+		fclose(fp);
+	}
+
+	if (hardware_id[0] != 0) {
+		LOGINFO("\nusing hardware id for device id: '%s'\n", hardware_id);
+		snprintf(device_id, DEVID_MAX, "%s", hardware_id);
+		sanitize_device_id(device_id);
+		mConst.SetValue("device_id", device_id);
+		return;
+	}
+
+	strcpy(device_id, "serialno");
+	LOGINFO("=> device id not found, using '%s'\n", device_id);
+	mConst.SetValue("device_id", device_id);
+	return;
+}
+
+int DataManager::ResetDefaults()
+{
+	pthread_mutex_lock(&m_valuesLock);
+	mPersist.Clear();
+	mData.Clear();
+	mConst.Clear();
+	pthread_mutex_unlock(&m_valuesLock);
+
+	SetDefaultValues();
+	return 0;
+}
+
+int DataManager::LoadValues(const string& filename)
+{
+	string dev_id;
+
+	if (!mInitialized)
+		SetDefaultValues();
+
+	GetValue("device_id", dev_id);
+	// Save off the backing file for set operations
+	mBackingFile = filename;
+	mPersist.SetFile(filename);
+	mPersist.SetFileVersion(FILE_VERSION);
+
+	// Read in the file, if possible
+	pthread_mutex_lock(&m_valuesLock);
+	mPersist.LoadValues();
+
+#ifndef TW_NO_SCREEN_TIMEOUT
+	blankTimer.setTime(mPersist.GetIntValue("tw_screen_timeout_secs"));
+#endif
+
+	pthread_mutex_unlock(&m_valuesLock);
+	string current = GetCurrentStoragePath();
+	TWPartition* Part = PartitionManager.Find_Partition_By_Path(current);
+	if (!Part)
+		Part = PartitionManager.Get_Default_Storage_Partition();
+	if (Part && current != Part->Storage_Path && Part->Mount(false)) {
+		LOGINFO("LoadValues setting storage path to '%s'\n", Part->Storage_Path.c_str());
+		SetValue("tw_storage_path", Part->Storage_Path);
+	} else {
+		SetBackupFolder();
+	}
+	return 0;
+}
+
+int DataManager::Flush()
+{
+	return SaveValues();
+}
+
+int DataManager::SaveValues()
+{
+#ifndef TW_OEM_BUILD
+	if (mBackingFile.empty())
+		return -1;
+
+	string mount_path = GetSettingsStoragePath();
+	PartitionManager.Mount_By_Path(mount_path.c_str(), 1);
+
+	mPersist.SetFile(mBackingFile);
+	mPersist.SetFileVersion(FILE_VERSION);
+	pthread_mutex_lock(&m_valuesLock);
+	mPersist.SaveValues();
+	pthread_mutex_unlock(&m_valuesLock);
+
+	tw_set_default_metadata(mBackingFile.c_str());
+	LOGINFO("Saved settings file values to '%s'\n", mBackingFile.c_str());
+#endif // ifdef TW_OEM_BUILD
+	return 0;
+}
+
+int DataManager::GetValue(const string& varName, string& value)
+{
+	string localStr = varName;
+	int ret = 0;
+
+	if (!mInitialized)
+		SetDefaultValues();
+
+	// Strip off leading and trailing '%' if provided
+	if (localStr.length() > 2 && localStr[0] == '%' && localStr[localStr.length()-1] == '%')
+	{
+		localStr.erase(0, 1);
+		localStr.erase(localStr.length() - 1, 1);
+	}
+
+	// Handle magic values
+	if (GetMagicValue(localStr, value) == 0)
+		return 0;
+
+	// Handle property
+	if (localStr.length() > 9 && localStr.substr(0, 9) == "property.") {
+		char property_value[PROPERTY_VALUE_MAX];
+		property_get(localStr.substr(9).c_str(), property_value, "");
+		value = property_value;
+		return 0;
+	}
+
+	pthread_mutex_lock(&m_valuesLock);
+	ret = mConst.GetValue(localStr, value);
+	if (ret == 0)
+		goto exit;
+
+	ret = mPersist.GetValue(localStr, value);
+	if (ret == 0)
+		goto exit;
+
+	ret = mData.GetValue(localStr, value);
+exit:
+	pthread_mutex_unlock(&m_valuesLock);
+	return ret;
+}
+
+int DataManager::GetValue(const string& varName, int& value)
+{
+	string data;
+
+	if (GetValue(varName,data) != 0)
+		return -1;
+
+	value = atoi(data.c_str());
+	return 0;
+}
+
+int DataManager::GetValue(const string& varName, float& value)
+{
+	string data;
+
+	if (GetValue(varName,data) != 0)
+		return -1;
+
+	value = atof(data.c_str());
+	return 0;
+}
+
+int DataManager::GetValue(const string& varName, unsigned long long& value)
+{
+	string data;
+
+	if (GetValue(varName,data) != 0)
+		return -1;
+
+	value = strtoull(data.c_str(), NULL, 10);
+	return 0;
+}
+
+// This function will return an empty string if the value doesn't exist
+string DataManager::GetStrValue(const string& varName)
+{
+	string retVal;
+
+	GetValue(varName, retVal);
+	return retVal;
+}
+
+// This function will return 0 if the value doesn't exist
+int DataManager::GetIntValue(const string& varName)
+{
+	string retVal;
+
+	GetValue(varName, retVal);
+	return atoi(retVal.c_str());
+}
+
+int DataManager::SetValue(const string& varName, const string& value, const int persist /* = 0 */)
+{
+	if (!mInitialized)
+		SetDefaultValues();
+
+	// Handle property
+	if (varName.length() > 9 && varName.substr(0, 9) == "property.") {
+		int ret = property_set(varName.substr(9).c_str(), value.c_str());
+		if (ret)
+			LOGERR("Error setting property '%s' to '%s'\n", varName.substr(9).c_str(), value.c_str());
+		return ret;
+	}
+
+	// Don't allow empty values or numerical starting values
+	if (varName.empty() || (varName[0] >= '0' && varName[0] <= '9'))
+		return -1;
+
+	string test;
+	pthread_mutex_lock(&m_valuesLock);
+	int constChk = mConst.GetValue(varName, test);
+	if (constChk == 0) {
+		pthread_mutex_unlock(&m_valuesLock);
+		return -1;
+	}
+
+	if (persist) {
+		mPersist.SetValue(varName, value);
+	} else {
+		int persistChk = mPersist.GetValue(varName, test);
+		if (persistChk == 0) {
+			mPersist.SetValue(varName, value);
+		} else {
+			mData.SetValue(varName, value);
+		}
+	}
+
+	pthread_mutex_unlock(&m_valuesLock);
+
+#ifndef TW_NO_SCREEN_TIMEOUT
+	if (varName == "tw_screen_timeout_secs") {
+		blankTimer.setTime(atoi(value.c_str()));
+	} else
+#endif
+	if (varName == "tw_storage_path") {
+		SetBackupFolder();
+	}
+	gui_notifyVarChange(varName.c_str(), value.c_str());
+	return 0;
+}
+
+int DataManager::SetValue(const string& varName, const int value, const int persist /* = 0 */)
+{
+	ostringstream valStr;
+	valStr << value;
+	return SetValue(varName, valStr.str(), persist);
+}
+
+int DataManager::SetValue(const string& varName, const float value, const int persist /* = 0 */)
+{
+	ostringstream valStr;
+	valStr << value;
+	return SetValue(varName, valStr.str(), persist);;
+}
+
+int DataManager::SetValue(const string& varName, const unsigned long long& value, const int persist /* = 0 */)
+{
+	ostringstream valStr;
+	valStr << value;
+	return SetValue(varName, valStr.str(), persist);
+}
+
+// For legacy code that doesn't set a scope
+int DataManager::SetProgress(const float Fraction) {
+	if (SetValue("ui_portion_size", 0) != 0)
+		return -1;
+	if (SetValue("ui_portion_start", 0) != 0)
+		return -1;
+	ShowProgress(1, 0);
+	int res = _SetProgress(Fraction);
+	if (SetValue("ui_portion_size", 0) != 0)
+		return -1;
+	if (SetValue("ui_portion_start", 0) != 0)
+		return -1;
+	return res;
+}
+
+int DataManager::_SetProgress(float Fraction) {
+	float Portion_Start, Portion_Size;
+	GetValue("ui_portion_size", Portion_Size);
+	GetValue("ui_portion_start", Portion_Start);
+	//LOGINFO("SetProgress(%.2lf): Portion_Size: %.2lf Portion_Start: %.2lf\n", Fraction, Portion_Size, Portion_Start);
+	if (Fraction < 0.0)
+		Fraction = 0;
+	if (Fraction > 1.0)
+		Fraction = 1;
+	if (SetValue("ui_progress", (float) ((Portion_Start + (Portion_Size * Fraction)) * 100.0)) != 0)
+		return -1;
+	return (SetValue("ui_progress_portion", 0) != 0);
+}
+
+int DataManager::ShowProgress(float Portion, const float Seconds)
+{
+	float Portion_Start, Portion_Size;
+	GetValue("ui_portion_size", Portion_Size);
+	GetValue("ui_portion_start", Portion_Start);
+	Portion_Start += Portion_Size;
+	if(Portion + Portion_Start > 1.0)
+		Portion = 1.0 - Portion_Start;
+	//LOGINFO("ShowProgress(%.2lf, %.2lf): Portion_Start: %.2lf\n", Portion, Seconds, Portion_Start);
+	if (SetValue("ui_portion_start", Portion_Start) != 0)
+		return -1;
+	if (SetValue("ui_portion_size", Portion) != 0)
+		return -1;
+	if (SetValue("ui_progress", (float)(Portion_Start * 100.0)) != 0)
+		return -1;
+	if(Seconds) {
+		if (SetValue("ui_progress_portion", (float)((Portion * 100.0) + Portion_Start)) != 0)
+			return -1;
+		if (SetValue("ui_progress_frames", Seconds * 48) != 0)
+			return -1;
+	}
+	return 0;
+}
+
+void DataManager::update_tz_environment_variables(void)
+{
+	setenv("TZ", GetStrValue(TW_TIME_ZONE_VAR).c_str(), 1);
+	tzset();
+}
+
+void DataManager::SetBackupFolder()
+{
+	string str = GetCurrentStoragePath();
+	TWPartition* partition = PartitionManager.Find_Partition_By_Path(str);
+	str += TWFunc::Check_For_TwrpFolder() + "/BACKUPS/";
+
+	string dev_id;
+	GetValue("device_id", dev_id);
+
+	str += dev_id;
+	LOGINFO("Backup folder set to '%s'\n", str.c_str());
+	SetValue(TW_BACKUPS_FOLDER_VAR, str, 0);
+	if (partition != NULL) {
+		SetValue("tw_storage_display_name", partition->Storage_Name);
+		char free_space[255];
+		sprintf(free_space, "%llu", partition->Free / 1024 / 1024);
+		SetValue("tw_storage_free_size", free_space);
+		string zip_path, zip_root, storage_path;
+		GetValue(TW_ZIP_LOCATION_VAR, zip_path);
+		if (partition->Has_Data_Media && !partition->Symlink_Mount_Point.empty())
+			storage_path = partition->Symlink_Mount_Point;
+		else
+			storage_path = partition->Storage_Path;
+		if (zip_path.size() < storage_path.size()) {
+			SetValue(TW_ZIP_LOCATION_VAR, storage_path);
+		} else {
+			zip_root = TWFunc::Get_Root_Path(zip_path);
+			if (zip_root != storage_path) {
+				LOGINFO("DataManager::SetBackupFolder zip path was %s changing to %s, %s\n", zip_path.c_str(), storage_path.c_str(), zip_root.c_str());
+				SetValue(TW_ZIP_LOCATION_VAR, storage_path);
+			}
+		}
+	} else {
+		if (PartitionManager.Fstab_Processed() != 0) {
+			LOGINFO("Storage partition '%s' not found\n", str.c_str());
+			gui_err("unable_locate_storage=Unable to locate storage device.");
+		}
+	}
+}
+
+void DataManager::SetDefaultValues()
+{
+	string str, path;
+
+	mConst.SetConst();
+
+	get_device_id();
+
+	pthread_mutex_lock(&m_valuesLock);
+
+	mInitialized = 1;
+
+	mConst.SetValue("true", "1");
+	mConst.SetValue("false", "0");
+
+	mConst.SetValue(TW_VERSION_VAR, TW_VERSION_STR);
+
+#ifndef TW_NO_HAPTICS
+	mPersist.SetValue("tw_button_vibrate", "80");
+	mPersist.SetValue("tw_keyboard_vibrate", "40");
+	mPersist.SetValue("tw_action_vibrate", "160");
+	mConst.SetValue("tw_disable_haptics", "0");
+#else
+	LOGINFO("TW_NO_HAPTICS := true\n");
+	mConst.SetValue("tw_disable_haptics", "1");
+#endif
+
+	TWPartition *store = PartitionManager.Get_Default_Storage_Partition();
+	if (store)
+		mPersist.SetValue("tw_storage_path", store->Storage_Path);
+	else
+		mPersist.SetValue("tw_storage_path", "/");
+
+#ifdef TW_FORCE_CPUINFO_FOR_DEVICE_ID
+	printf("TW_FORCE_CPUINFO_FOR_DEVICE_ID := true\n");
+#endif
+
+#ifdef BOARD_HAS_NO_REAL_SDCARD
+	printf("BOARD_HAS_NO_REAL_SDCARD := true\n");
+	mConst.SetValue(TW_ALLOW_PARTITION_SDCARD, "0");
+#else
+	mConst.SetValue(TW_ALLOW_PARTITION_SDCARD, "1");
+#endif
+
+#ifdef TW_INCLUDE_DUMLOCK
+	printf("TW_INCLUDE_DUMLOCK := true\n");
+	mConst.SetValue(TW_SHOW_DUMLOCK, "1");
+#else
+	mConst.SetValue(TW_SHOW_DUMLOCK, "0");
+#endif
+
+	mData.SetValue(TW_RECOVERY_FOLDER_VAR, TW_DEFAULT_RECOVERY_FOLDER);
+
+	str = GetCurrentStoragePath();
+	mPersist.SetValue(TW_ZIP_LOCATION_VAR, str);
+	str += DataManager::GetStrValue(TW_RECOVERY_FOLDER_VAR) + "/BACKUPS/";
+
+	string dev_id;
+	mConst.GetValue("device_id", dev_id);
+
+	str += dev_id;
+	mData.SetValue(TW_BACKUPS_FOLDER_VAR, str);
+
+	mConst.SetValue(TW_REBOOT_SYSTEM, "1");
+#ifdef TW_NO_REBOOT_RECOVERY
+	printf("TW_NO_REBOOT_RECOVERY := true\n");
+	mConst.SetValue(TW_REBOOT_RECOVERY, "0");
+#else
+	mConst.SetValue(TW_REBOOT_RECOVERY, "1");
+#endif
+	mConst.SetValue(TW_REBOOT_POWEROFF, "1");
+#ifdef TW_NO_REBOOT_BOOTLOADER
+	printf("TW_NO_REBOOT_BOOTLOADER := true\n");
+	mConst.SetValue(TW_REBOOT_BOOTLOADER, "0");
+#else
+	mConst.SetValue(TW_REBOOT_BOOTLOADER, "1");
+#endif
+#ifdef RECOVERY_SDCARD_ON_DATA
+	printf("RECOVERY_SDCARD_ON_DATA := true\n");
+	mConst.SetValue(TW_HAS_DATA_MEDIA, "1");
+	datamedia = true;
+#else
+	mData.SetValue(TW_HAS_DATA_MEDIA, "0");
+#endif
+#ifdef TW_NO_BATT_PERCENT
+	printf("TW_NO_BATT_PERCENT := true\n");
+	mConst.SetValue(TW_NO_BATTERY_PERCENT, "1");
+#else
+	mConst.SetValue(TW_NO_BATTERY_PERCENT, "0");
+#endif
+#ifdef TW_NO_CPU_TEMP
+	printf("TW_NO_CPU_TEMP := true\n");
+	mConst.SetValue("tw_no_cpu_temp", "1");
+#else
+	string cpu_temp_file;
+#ifdef TW_CUSTOM_CPU_TEMP_PATH
+	cpu_temp_file = EXPAND(TW_CUSTOM_CPU_TEMP_PATH);
+#else
+	cpu_temp_file = "/sys/class/thermal/thermal_zone0/temp";
+#endif
+	if (TWFunc::Path_Exists(cpu_temp_file)) {
+		mConst.SetValue("tw_no_cpu_temp", "0");
+	} else {
+		LOGINFO("CPU temperature file '%s' not found, disabling CPU temp.\n", cpu_temp_file.c_str());
+		mConst.SetValue("tw_no_cpu_temp", "1");
+	}
+#endif
+#ifdef TW_CUSTOM_POWER_BUTTON
+	printf("TW_POWER_BUTTON := %s\n", EXPAND(TW_CUSTOM_POWER_BUTTON));
+	mConst.SetValue(TW_POWER_BUTTON, EXPAND(TW_CUSTOM_POWER_BUTTON));
+#else
+	mConst.SetValue(TW_POWER_BUTTON, "0");
+#endif
+#ifdef TW_ALWAYS_RMRF
+	printf("TW_ALWAYS_RMRF := true\n");
+	mConst.SetValue(TW_RM_RF_VAR, "1");
+#endif
+#ifdef TW_NEVER_UNMOUNT_SYSTEM
+	printf("TW_NEVER_UNMOUNT_SYSTEM := true\n");
+	mConst.SetValue(TW_DONT_UNMOUNT_SYSTEM, "1");
+#else
+	mConst.SetValue(TW_DONT_UNMOUNT_SYSTEM, "0");
+#endif
+#ifdef TW_NO_USB_STORAGE
+	printf("TW_NO_USB_STORAGE := true\n");
+	mConst.SetValue(TW_HAS_USB_STORAGE, "0");
+#else
+	char lun_file[255];
+	string Lun_File_str = CUSTOM_LUN_FILE;
+	size_t found = Lun_File_str.find("%");
+	if (found != string::npos) {
+		sprintf(lun_file, CUSTOM_LUN_FILE, 0);
+		Lun_File_str = lun_file;
+	}
+	if (!TWFunc::Path_Exists(Lun_File_str)) {
+		LOGINFO("Lun file '%s' does not exist, USB storage mode disabled\n", Lun_File_str.c_str());
+		mConst.SetValue(TW_HAS_USB_STORAGE, "0");
+	} else {
+		LOGINFO("Lun file '%s'\n", Lun_File_str.c_str());
+		mData.SetValue(TW_HAS_USB_STORAGE, "1");
+	}
+#endif
+#ifdef TW_INCLUDE_INJECTTWRP
+	printf("TW_INCLUDE_INJECTTWRP := true\n");
+	mConst.SetValue(TW_HAS_INJECTTWRP, "1");
+	mPersist(TW_INJECT_AFTER_ZIP, "1");
+#else
+	mConst.SetValue(TW_HAS_INJECTTWRP, "0");
+#endif
+#ifdef TW_HAS_DOWNLOAD_MODE
+	printf("TW_HAS_DOWNLOAD_MODE := true\n");
+	mConst.SetValue(TW_DOWNLOAD_MODE, "1");
+#endif
+#ifdef TW_HAS_EDL_MODE
+	printf("TW_HAS_EDL_MODE := true\n");
+	mConst.SetValue(TW_EDL_MODE, "1");
+#endif
+#ifdef TW_INCLUDE_FASTBOOTD
+	printf("TW_INCLUDE_FASTBOOTD := true\n");
+	mConst.SetValue(TW_FASTBOOT_MODE, "1");
+#endif
+#ifdef PRODUCT_USE_DYNAMIC_PARTITIONS
+	printf("PRODUCT_USE_DYNAMIC_PARTITIONS := true\n");
+	mConst.SetValue(TW_FASTBOOT_MODE, "1");
+	mConst.SetValue(TW_IS_SUPER, "1");
+#else
+	mConst.SetValue(TW_IS_SUPER, "0");
+#endif
+#ifdef TW_INCLUDE_CRYPTO
+	mConst.SetValue(TW_HAS_CRYPTO, "1");
+	printf("TW_INCLUDE_CRYPTO := true\n");
+#endif
+#ifdef TW_SDEXT_NO_EXT4
+	printf("TW_SDEXT_NO_EXT4 := true\n");
+	mConst.SetValue(TW_SDEXT_DISABLE_EXT4, "1");
+#else
+	mConst.SetValue(TW_SDEXT_DISABLE_EXT4, "0");
+#endif
+
+#ifdef TW_HAS_NO_BOOT_PARTITION
+	mPersist.SetValue("tw_backup_list", "/system;/data;");
+#else
+#ifdef PRODUCT_USE_DYNAMIC_PARTITIONS
+	mPersist.SetValue("tw_backup_list", "/data;");
+#else
+	mPersist.SetValue("tw_backup_list", "/system;/data;/boot;");
+#endif
+#endif
+	mConst.SetValue(TW_MIN_SYSTEM_VAR, TW_MIN_SYSTEM_SIZE);
+	mData.SetValue(TW_BACKUP_NAME, "(Auto Generate)");
+
+	mPersist.SetValue(TW_INSTALL_REBOOT_VAR, "0");
+	mPersist.SetValue(TW_SIGNED_ZIP_VERIFY_VAR, "0");
+	mPersist.SetValue(TW_DISABLE_FREE_SPACE_VAR, "0");
+	mPersist.SetValue(TW_FORCE_DIGEST_CHECK_VAR, "0");
+	mPersist.SetValue(TW_USE_COMPRESSION_VAR, "0");
+	mPersist.SetValue(TW_TIME_ZONE_VAR, "CST6CDT,M3.2.0,M11.1.0");
+	mPersist.SetValue(TW_GUI_SORT_ORDER, "1");
+	mPersist.SetValue(TW_RM_RF_VAR, "0");
+	mPersist.SetValue(TW_SKIP_DIGEST_CHECK_VAR, "0");
+	mPersist.SetValue(TW_SKIP_DIGEST_CHECK_ZIP_VAR, "1");
+	mPersist.SetValue(TW_SKIP_DIGEST_GENERATE_VAR, "0");
+	mPersist.SetValue(TW_SDEXT_SIZE, "0");
+	mPersist.SetValue(TW_SWAP_SIZE, "0");
+	mPersist.SetValue(TW_SDPART_FILE_SYSTEM, "ext3");
+	mPersist.SetValue(TW_TIME_ZONE_GUISEL, "CST6;CDT,M3.2.0,M11.1.0");
+	mPersist.SetValue(TW_TIME_ZONE_GUIOFFSET, "0");
+	mPersist.SetValue(TW_TIME_ZONE_GUIDST, "1");
+	mPersist.SetValue(TW_AUTO_REFLASHTWRP_VAR, "0");
+
+	mData.SetValue(TW_ACTION_BUSY, "0");
+	mData.SetValue("tw_wipe_cache", "0");
+	mData.SetValue("tw_wipe_dalvik", "0");
+	mData.SetValue(TW_ZIP_INDEX, "0");
+	mData.SetValue(TW_ZIP_QUEUE_COUNT, "0");
+	mData.SetValue(TW_FILENAME, "/sdcard");
+	mData.SetValue(TW_SIMULATE_ACTIONS, "0");
+	mData.SetValue(TW_SIMULATE_FAIL, "0");
+	mData.SetValue(TW_IS_ENCRYPTED, "0");
+	mData.SetValue(TW_IS_DECRYPTED, "0");
+	mData.SetValue(TW_CRYPTO_PASSWORD, "0");
+	mData.SetValue(TW_CRYPTO_PWTYPE, "0"); // Set initial value so that recovery will not be confused when using unencrypted data or failed to decrypt data
+	mData.SetValue("tw_terminal_state", "0");
+	mData.SetValue("tw_background_thread_running", "0");
+	mData.SetValue(TW_RESTORE_FILE_DATE, "0");
+	mPersist.SetValue("tw_military_time", "0");
+
+#ifdef TW_INCLUDE_CRYPTO
+	mPersist.SetValue(TW_USE_SHA2, "1");
+	mPersist.SetValue(TW_NO_SHA2, "0");
+#else
+	mPersist.SetValue(TW_NO_SHA2, "1");
+#endif
+#ifdef AB_OTA_UPDATER
+	mPersist.SetValue(TW_UNMOUNT_SYSTEM, "0");
+#else
+	mPersist.SetValue(TW_UNMOUNT_SYSTEM, "1");
+#endif
+#if defined BOARD_USES_RECOVERY_AS_BOOT && defined BOARD_BUILD_SYSTEM_ROOT_IMAGE
+	mConst.SetValue("tw_uses_initramfs", "1");
+#else
+	mConst.SetValue("tw_uses_initramfs", "0");
+#endif
+#ifdef TW_NO_SCREEN_TIMEOUT
+	mConst.SetValue("tw_screen_timeout_secs", "0");
+	mConst.SetValue("tw_no_screen_timeout", "1");
+#else
+	mPersist.SetValue("tw_screen_timeout_secs", "60");
+	mPersist.SetValue("tw_no_screen_timeout", "0");
+#endif
+	mData.SetValue("tw_gui_done", "0");
+	mData.SetValue("tw_encrypt_backup", "0");
+	mData.SetValue("tw_sleep_total", "5");
+	mData.SetValue("tw_sleep", "5");
+	mData.SetValue("tw_enable_fastboot", "0");
+
+
+	if (android::base::GetBoolProperty("ro.virtual_ab.enabled", false))
+		mConst.SetValue("tw_virtual_ab.enabled", "1");
+	else
+		mConst.SetValue("tw_virtual_ab.enabled", "0");
+	// Brightness handling
+	string findbright;
+#ifdef TW_BRIGHTNESS_PATH
+	findbright = EXPAND(TW_BRIGHTNESS_PATH);
+	LOGINFO("TW_BRIGHTNESS_PATH := %s\n", findbright.c_str());
+	if (!TWFunc::Path_Exists(findbright)) {
+		LOGINFO("Specified brightness file '%s' not found.\n", findbright.c_str());
+		findbright = "";
+	}
+#endif
+	if (findbright.empty()) {
+		// Attempt to locate the brightness file
+		findbright = Find_File::Find("brightness", "/sys/class/backlight");
+		if (findbright.empty()) findbright = Find_File::Find("brightness", "/sys/class/leds/lcd-backlight");
+	}
+	if (findbright.empty()) {
+		LOGINFO("Unable to locate brightness file\n");
+		mConst.SetValue("tw_has_brightnesss_file", "0");
+	} else {
+		LOGINFO("Found brightness file at '%s'\n", findbright.c_str());
+		mConst.SetValue("tw_has_brightnesss_file", "1");
+		mConst.SetValue("tw_brightness_file", findbright);
+		string maxBrightness;
+#ifdef TW_MAX_BRIGHTNESS
+		ostringstream maxVal;
+		maxVal << TW_MAX_BRIGHTNESS;
+		maxBrightness = maxVal.str();
+#else
+		// Attempt to locate the max_brightness file
+		string maxbrightpath = findbright.insert(findbright.rfind('/') + 1, "max_");
+		if (TWFunc::Path_Exists(maxbrightpath)) {
+			ifstream maxVal(maxbrightpath.c_str());
+			if (maxVal >> maxBrightness) {
+				LOGINFO("Got max brightness %s from '%s'\n", maxBrightness.c_str(), maxbrightpath.c_str());
+			} else {
+				// Something went wrong, set that to indicate error
+				maxBrightness = "-1";
+			}
+		}
+		if (atoi(maxBrightness.c_str()) <= 0)
+		{
+			// Fallback into default
+			ostringstream maxVal;
+			maxVal << 255;
+			maxBrightness = maxVal.str();
+		}
+#endif
+		mConst.SetValue("tw_brightness_max", maxBrightness);
+		mPersist.SetValue("tw_brightness", maxBrightness);
+		mPersist.SetValue("tw_brightness_pct", "100");
+#ifdef TW_SECONDARY_BRIGHTNESS_PATH
+		string secondfindbright = EXPAND(TW_SECONDARY_BRIGHTNESS_PATH);
+		if (secondfindbright != "" && TWFunc::Path_Exists(secondfindbright)) {
+			LOGINFO("Will use a second brightness file at '%s'\n", secondfindbright.c_str());
+			mConst.SetValue("tw_secondary_brightness_file", secondfindbright);
+		} else {
+			LOGINFO("Specified secondary brightness file '%s' not found.\n", secondfindbright.c_str());
+		}
+#endif
+#ifdef TW_DEFAULT_BRIGHTNESS
+		int defValInt = TW_DEFAULT_BRIGHTNESS;
+		int maxValInt = atoi(maxBrightness.c_str());
+		// Deliberately int so the % is always a whole number
+		int defPctInt = ( ( (double)defValInt / maxValInt ) * 100 );
+		ostringstream defPct;
+		defPct << defPctInt;
+		mPersist.SetValue("tw_brightness_pct", defPct.str());
+
+		ostringstream defVal;
+		defVal << TW_DEFAULT_BRIGHTNESS;
+		mPersist.SetValue("tw_brightness", defVal.str());
+		TWFunc::Set_Brightness(defVal.str());
+#else
+		TWFunc::Set_Brightness(maxBrightness);
+#endif
+	}
+
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+	mConst.SetValue("tw_include_encrypted_backup", "1");
+#else
+	LOGINFO("TW_EXCLUDE_ENCRYPTED_BACKUPS := true\n");
+	mConst.SetValue("tw_include_encrypted_backup", "0");
+#endif
+#ifdef TW_HAS_MTP
+	mConst.SetValue("tw_has_mtp", "1");
+	mPersist.SetValue("tw_mtp_enabled", "1");
+	mPersist.SetValue("tw_mtp_debug", "0");
+#else
+	LOGINFO("TW_EXCLUDE_MTP := true\n");
+	mConst.SetValue("tw_has_mtp", "0");
+	mConst.SetValue("tw_mtp_enabled", "0");
+#endif
+	mPersist.SetValue("tw_mount_system_ro", "2");
+	mPersist.SetValue("tw_never_show_system_ro_page", "0");
+	mPersist.SetValue("tw_language", EXPAND(TW_DEFAULT_LANGUAGE));
+	LOGINFO("LANG: %s\n", EXPAND(TW_DEFAULT_LANGUAGE));
+
+	mData.SetValue("tw_has_adopted_storage", "0");
+
+#ifdef AB_OTA_UPDATER
+	LOGINFO("AB_OTA_UPDATER := true\n");
+	mConst.SetValue("tw_has_boot_slots", "1");
+#else
+	mConst.SetValue("tw_has_boot_slots", "0");
+#endif
+
+#ifdef TW_NO_LEGACY_PROPS
+	LOGINFO("TW_NO_LEGACY_PROPS := true\n");
+#endif
+
+#ifdef TW_OEM_BUILD
+	LOGINFO("TW_OEM_BUILD := true\n");
+	mConst.SetValue("tw_oem_build", "1");
+	mConst.SetValue("tw_app_installed_in_system", "0");
+#else
+	mConst.SetValue("tw_oem_build", "0");
+	mPersist.SetValue("tw_app_prompt", "1");
+	mPersist.SetValue("tw_app_install_system", "1");
+	mData.SetValue("tw_app_install_status", "0"); // 0 = no status, 1 = not installed, 2 = already installed
+	mData.SetValue("tw_app_installed_in_system", "0");
+#endif
+#ifndef TW_EXCLUDE_NANO
+	mConst.SetValue("tw_include_nano", "1");
+#else
+	LOGINFO("TW_EXCLUDE_NANO := true\n");
+	mConst.SetValue("tw_include_nano", "0");
+#endif
+
+	mData.SetValue("tw_flash_both_slots", "0");
+	mData.SetValue("tw_is_slot_part", "0");
+
+	mData.SetValue("tw_enable_adb_backup", "0");
+
+	if (TWFunc::Path_Exists("/system/bin/logcat"))
+		mConst.SetValue("tw_logcat_exists", "1");
+	else
+		mConst.SetValue("tw_logcat_exists", "0");
+
+	if (TWFunc::Path_Exists("/system/bin/magiskboot"))
+		mConst.SetValue("tw_has_repack_tools", "1");
+	else
+		mConst.SetValue("tw_has_repack_tools", "0");
+
+	pthread_mutex_unlock(&m_valuesLock);
+}
+
+// Magic Values
+int DataManager::GetMagicValue(const string& varName, string& value)
+{
+	// Handle special dynamic cases
+	if (varName == "tw_time")
+	{
+		char tmp[32];
+
+		struct tm *current;
+		time_t now;
+		int tw_military_time;
+		now = time(0);
+		current = localtime(&now);
+		GetValue(TW_MILITARY_TIME, tw_military_time);
+		if (current->tm_hour >= 12)
+		{
+			if (tw_military_time == 1)
+				sprintf(tmp, "%d:%02d", current->tm_hour, current->tm_min);
+			else
+				sprintf(tmp, "%d:%02d PM", current->tm_hour == 12 ? 12 : current->tm_hour - 12, current->tm_min);
+		}
+		else
+		{
+			if (tw_military_time == 1)
+				sprintf(tmp, "%d:%02d", current->tm_hour, current->tm_min);
+			else
+				sprintf(tmp, "%d:%02d AM", current->tm_hour == 0 ? 12 : current->tm_hour, current->tm_min);
+		}
+		value = tmp;
+		return 0;
+	}
+	else if (varName == "tw_cpu_temp")
+	{
+		int tw_no_cpu_temp;
+		GetValue("tw_no_cpu_temp", tw_no_cpu_temp);
+		if (tw_no_cpu_temp == 1) return -1;
+
+		string cpu_temp_file;
+		static unsigned long convert_temp = 0;
+		static time_t cpuSecCheck = 0;
+		struct timeval curTime;
+		string results;
+
+		gettimeofday(&curTime, NULL);
+		if (curTime.tv_sec > cpuSecCheck)
+		{
+#ifdef TW_CUSTOM_CPU_TEMP_PATH
+			cpu_temp_file = EXPAND(TW_CUSTOM_CPU_TEMP_PATH);
+			if (TWFunc::read_file(cpu_temp_file, results) != 0)
+				return -1;
+#else
+			cpu_temp_file = "/sys/class/thermal/thermal_zone0/temp";
+			if (TWFunc::read_file(cpu_temp_file, results) != 0)
+				return -1;
+#endif
+			convert_temp = strtoul(results.c_str(), NULL, 0) / 1000;
+			if (convert_temp <= 0)
+				convert_temp = strtoul(results.c_str(), NULL, 0);
+			if (convert_temp >= 150)
+				convert_temp = strtoul(results.c_str(), NULL, 0) / 10;
+			cpuSecCheck = curTime.tv_sec + 5;
+		}
+		value = TWFunc::to_string(convert_temp);
+		return 0;
+	}
+	else if (varName == "tw_battery")
+	{
+		char tmp[16];
+		static char charging = ' ';
+		static int lastVal = -1;
+		static time_t nextSecCheck = 0;
+		struct timeval curTime;
+		gettimeofday(&curTime, NULL);
+		if (curTime.tv_sec > nextSecCheck)
+		{
+			char cap_s[4];
+#ifdef TW_CUSTOM_BATTERY_PATH
+			string capacity_file = EXPAND(TW_CUSTOM_BATTERY_PATH);
+			capacity_file += "/capacity";
+			FILE * cap = fopen(capacity_file.c_str(),"rt");
+#else
+			FILE * cap = fopen("/sys/class/power_supply/battery/capacity","rt");
+#endif
+			if (cap) {
+				fgets(cap_s, 4, cap);
+				fclose(cap);
+				lastVal = atoi(cap_s);
+				if (lastVal > 100)	lastVal = 101;
+				if (lastVal < 0)	lastVal = 0;
+			}
+#ifdef TW_CUSTOM_BATTERY_PATH
+			string status_file = EXPAND(TW_CUSTOM_BATTERY_PATH);
+			status_file += "/status";
+			cap = fopen(status_file.c_str(),"rt");
+#else
+			cap = fopen("/sys/class/power_supply/battery/status","rt");
+#endif
+			if (cap) {
+				fgets(cap_s, 2, cap);
+				fclose(cap);
+				if (cap_s[0] == 'C')
+					charging = '+';
+				else
+					charging = ' ';
+			}
+			nextSecCheck = curTime.tv_sec + 60;
+		}
+
+		sprintf(tmp, "%i%%%c", lastVal, charging);
+		value = tmp;
+		return 0;
+	}
+	return -1;
+}
+
+void DataManager::Output_Version(void)
+{
+#ifndef TW_OEM_BUILD
+	string Path;
+	char version[255];
+
+	std::string logDir = TWFunc::get_log_dir();
+	if (logDir.empty()) {
+		LOGINFO("Unable to find cache directory\n");
+		return;
+	}
+
+	std::string recoveryLogDir = logDir + "recovery/";
+
+	if (logDir == CACHE_LOGS_DIR) {
+		if (!PartitionManager.Mount_By_Path(CACHE_LOGS_DIR, false)) {
+			LOGINFO("Unable to mount '%s' to write version number.\n", Path.c_str());
+			return;
+		}
+
+		if (!TWFunc::Path_Exists(recoveryLogDir)) {
+			LOGINFO("Recreating %s folder.\n", recoveryLogDir.c_str());
+			if (!TWFunc::Create_Dir_Recursive(recoveryLogDir.c_str(), S_IRWXU | S_IRWXG | S_IWGRP | S_IXGRP, 0, 0)) {
+				LOGERR("DataManager::Output_Version -- Unable to make %s: %s\n", recoveryLogDir.c_str(), strerror(errno));
+				return;
+			}
+		}
+	}
+
+	std::string verPath = recoveryLogDir + ".version";
+	if (TWFunc::Path_Exists(verPath)) {
+		unlink(verPath.c_str());
+	}
+	FILE *fp = fopen(verPath.c_str(), "w");
+	if (fp == NULL) {
+		LOGINFO("Unable to open: %s. Data may be unmounted. Error: %s\n", verPath.c_str(), strerror(errno));
+		return;
+	}
+	strcpy(version, TW_VERSION_STR);
+	fwrite(version, sizeof(version[0]), strlen(version) / sizeof(version[0]), fp);
+	fclose(fp);
+	TWFunc::copy_file("/etc/recovery.fstab", recoveryLogDir + "recovery.fstab", 0644);
+	PartitionManager.Output_Storage_Fstab();
+	sync();
+	LOGINFO("Version number saved to '%s'\n", verPath.c_str());
+#endif
+}
+
+void DataManager::ReadSettingsFile(void)
+{
+#ifndef TW_OEM_BUILD
+	// Load up the values for TWRP - Sleep to let the card be ready
+	char mkdir_path[255], settings_file[255];
+	int is_enc, has_data_media;
+
+	GetValue(TW_IS_ENCRYPTED, is_enc);
+	GetValue(TW_HAS_DATA_MEDIA, has_data_media);
+
+	memset(mkdir_path, 0, sizeof(mkdir_path));
+	memset(settings_file, 0, sizeof(settings_file));
+	sprintf(mkdir_path, "%s%s", GetSettingsStoragePath().c_str(), GetStrValue(TW_RECOVERY_FOLDER_VAR).c_str());
+	sprintf(settings_file, "%s/%s", mkdir_path, TW_SETTINGS_FILE);
+
+	if (!PartitionManager.Mount_Settings_Storage(false))
+	{
+		usleep(500000);
+		if (!PartitionManager.Mount_Settings_Storage(false))
+			gui_msg(Msg(msg::kError, "unable_to_mount=Unable to mount {1}")(settings_file));
+	}
+
+	mkdir(mkdir_path, 0777);
+
+	LOGINFO("Attempt to load settings from settings file...\n");
+	LoadValues(settings_file);
+	Output_Version();
+#endif // ifdef TW_OEM_BUILD
+	PartitionManager.Mount_All_Storage();
+	update_tz_environment_variables();
+	TWFunc::Set_Brightness(GetStrValue("tw_brightness"));
+}
+
+string DataManager::GetCurrentStoragePath(void)
+{
+	return GetStrValue("tw_storage_path");
+}
+
+string DataManager::GetSettingsStoragePath(void)
+{
+	return GetStrValue("tw_settings_path");
+}
+
+void DataManager::Vibrate(const string& varName)
+{
+#ifndef TW_NO_HAPTICS
+	int vib_value = 0;
+	GetValue(varName, vib_value);
+	if (vib_value) {
+		vibrate(vib_value);
+	}
+#endif
+}
+
+
+void DataManager::LoadTWRPFolderInfo(void)
+{
+	string mainPath = GetCurrentStoragePath();
+	SetValue(TW_RECOVERY_FOLDER_VAR, TWFunc::Check_For_TwrpFolder());
+	mBackingFile = mainPath + GetStrValue(TW_RECOVERY_FOLDER_VAR) + '/' + TW_SETTINGS_FILE;
+}
diff --git a/data.hpp b/data.hpp
new file mode 100755
index 0000000..d89966e
--- /dev/null
+++ b/data.hpp
@@ -0,0 +1,91 @@
+/*
+	Copyright 2012 to 2021 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _DATAMANAGER_HPP_HEADER
+#define _DATAMANAGER_HPP_HEADER
+
+#include <string>
+#include <pthread.h>
+#include "infomanager.hpp"
+
+using namespace std;
+
+class DataManager
+{
+public:
+	static int ResetDefaults();
+	static int LoadValues(const string& filename);
+	static int LoadPersistValues(void);
+	static int Flush();
+	static void LoadTWRPFolderInfo(void);
+
+	// Core get routines
+	static int GetValue(const string& varName, string& value);
+	static int GetValue(const string& varName, int& value);
+	static int GetValue(const string& varName, float& value);
+	static int GetValue(const string& varName, unsigned long long& value);
+
+	// Helper functions
+	static string GetStrValue(const string& varName);
+	static int GetIntValue(const string& varName);
+
+	// Core set routines
+	static int SetValue(const string& varName, const string& value, const int persist = 0);
+	static int SetValue(const string& varName, const int value, const int persist = 0);
+	static int SetValue(const string& varName, const float value, const int persist = 0);
+	static int SetValue(const string& varName, const unsigned long long& value, const int persist = 0);
+	static int SetProgress(const float Fraction);
+	static int _SetProgress(float Fraction);
+	static int ShowProgress(float Portion, const float Seconds);
+
+	static void DumpValues();
+	static void update_tz_environment_variables();
+	static void Vibrate(const string& varName);
+	static void SetBackupFolder();
+	static void SetDefaultValues();
+	static void Output_Version(void); // Outputs the version to a file in the TWRP folder
+	static void ReadSettingsFile(void);
+
+	static string GetCurrentStoragePath(void);
+	static string GetSettingsStoragePath(void);
+
+public:
+	static string mBackingFile;
+
+protected:
+	static int mInitialized;
+	static InfoManager mPersist;
+	static InfoManager mData;
+	static InfoManager mConst;
+
+	static map<string, string> mConstValues;
+
+protected:
+	static int SaveValues();
+
+	static int GetMagicValue(const string& varName, string& value);
+
+private:
+	static void sanitize_device_id(char* device_id);
+	static void get_device_id(void);
+
+	static pthread_mutex_t m_valuesLock;
+};
+
+#endif // _DATAMANAGER_HPP_HEADER
+
diff --git a/dosfstools/Android.mk b/dosfstools/Android.mk
new file mode 100755
index 0000000..d4b85a0
--- /dev/null
+++ b/dosfstools/Android.mk
@@ -0,0 +1,58 @@
+ifneq ($(TARGET_SIMULATOR),true)
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    src/boot.c \
+    src/check.c \
+    src/common.c \
+    src/fat.c \
+    src/file.c \
+    src/io.c \
+    src/lfn.c \
+    src/fsck.fat.c
+
+LOCAL_SHARED_LIBRARIES := libc
+LOCAL_CFLAGS += -D_USING_BIONIC_
+LOCAL_CFLAGS += -DUSE_ANDROID_RETVALS
+LOCAL_CFLAGS += -Wno-sign-compare
+LOCAL_MODULE = fsck.fat
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    src/boot.c \
+    src/check.c \
+    src/common.c \
+    src/fat.c \
+    src/file.c \
+    src/io.c \
+    src/lfn.c \
+    src/fatlabel.c
+
+LOCAL_C_INCLUDES += bionic/libc/kernel/common
+LOCAL_SHARED_LIBRARIES := libc
+LOCAL_CFLAGS += -D_USING_BIONIC_
+LOCAL_CFLAGS += -Wno-sign-compare
+LOCAL_MODULE = fatlabel
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := src/mkfs.fat.c
+
+LOCAL_SHARED_LIBRARIES := libc
+LOCAL_CFLAGS += -D_USING_BIONIC_
+LOCAL_CFLAGS += -Wno-sign-compare
+LOCAL_MODULE = mkfs.fat
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+include $(BUILD_EXECUTABLE)
+
+endif
diff --git a/dosfstools/COPYING b/dosfstools/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/dosfstools/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 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.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     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
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    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, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program 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.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU 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 Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/dosfstools/ChangeLog b/dosfstools/ChangeLog
new file mode 100644
index 0000000..f6a1e3d
--- /dev/null
+++ b/dosfstools/ChangeLog
@@ -0,0 +1,2096 @@
+commit ad1342e
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Sat May 16 02:10:18 2015 +0200
+
+    manpages: Mark MT and ME tags as untranslated for po4a
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 3ed9ec2
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Sun May 10 19:59:02 2015 +0200
+
+    mkfs: Small changes to FAT32 cluster size selection
+    
+    Put the cluster size selection back in line with the table used in
+    Microsoft's fatgen103.pdf and fix the comment. This only involved
+    changing some comparison operators, all values stayed the same.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 9984552
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Mon Apr 20 23:41:34 2015 +0200
+
+    fsck: Mention -r is default in usage message
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 3eaca68
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Mon Apr 20 23:30:56 2015 +0200
+
+    manpages: Remove obsolete information about Linux FAT support
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit a5e34de
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Thu Apr 16 23:16:08 2015 +0200
+
+    manpages: Convert the rest of argument placeholders to upper case
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 2303765
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Thu Apr 16 22:51:20 2015 +0200
+
+    manpages: Fix formatting
+    
+    Make the manpages conform to the rules for groff sources. First,
+    eliminate empty lines because these create vertical spaces where it
+    isn't desired. Man page sources should not contain empty lines. Second,
+    put a line break between sentences in a paragraph. A period is
+    recognized and formatted as a full stop period only when a line break
+    comes right after it.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit d3969b8
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Fri Apr 10 20:32:50 2015 +0200
+
+    fsck: Verify first cluster of a file is not 1
+    
+    Previously the FAT was checked for any out of bounds entries which were
+    cleared. However the first cluster of a file, as specified in its
+    directory entry, was not verified to not be 1.
+    
+    In addition to missing this filesystem error, code that correctly
+    assumes the FAT contained no bad entries anymore could still look up
+    invalid table indices depending on the value stored in entry 1. With
+    the right values and FAT size this can lead to a segfault by accessing
+    unallocated memory.
+    
+    Now test_file() will ignore files where the first cluster equals 1 and
+    an additional check in check_file() will truncate them.
+    
+    This bug was reported in http://bugs.debian.org/773885 by Jakub Wilk.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 2a56575
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Thu Apr 2 23:35:00 2015 +0200
+
+    Simplify synopses of man pages
+    
+    Since there is only one mode of invocation for the tools, it is
+    somewhat pointless to list every possible option in the synopsis.
+    
+    Fix a few style inconsistencies in addition, specifically that
+    placeholders for option arguments are in capital letters and should be
+    shown where the option is described.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 40f1436
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Wed Apr 1 20:55:58 2015 +0200
+
+    Update homepage and maintainer sections of man pages
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 0169db8
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Wed Apr 1 20:05:01 2015 +0200
+
+    mkfs: Show a clearer message that file already exists with -C
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 2967e27
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Mon Mar 30 21:12:51 2015 +0200
+
+    mkfs: Remove O_TRUNC from file creation call
+    
+    open() is called with O_EXCL | O_CREAT, so it will definitely create a
+    new, empty file or error out. O_TRUNC is superfluous and may be harmless
+    in practice but its effect under these circumstances is unspecified.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 90612df
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Mon Mar 30 21:09:51 2015 +0200
+
+    mkfs: Use ftruncate() for expanding created file
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 58e8e09
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Tue Mar 24 21:32:40 2015 +0100
+
+    mkfs: Add --invariant option
+    
+    The --invariant option is designed to prevent random or time based
+    differences to end up in generated filesystems so that multiple runs
+    generate on the same file or device create completely identical results.
+    
+    This is intended for debugging or regression testing of mkfs.fat.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 354feaf
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Thu Mar 19 21:35:21 2015 +0100
+
+    mkfs: Add long option parsing
+    
+    Add long option parsing with getopt_long() to mkfs and define --help as
+    the first long option. The usage() function now takes an exit code
+    parameter so that the --help option handling can exit the program with
+    a success exit code.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit b611549
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Mon Mar 16 21:08:35 2015 +0100
+
+    Do not expect EOF as return value of getopt()
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit a41fc32
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Wed Mar 11 21:45:04 2015 +0100
+
+    fsck.fat: Fix read beyond end of array on FAT12
+    
+    When a FAT12 filesystem contains an odd number of clusters, setting the
+    last cluster with set_fat() will trigger a read of the next entry,
+    which does not exist in the fat array allocated for this.
+    
+    Round up the allocation to an even number of FAT entries for FAT12 so
+    that this is fixed without introducing special casing in get_fat().
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit e18d72f
+Author: Álvaro Fernández Rojas <noltari@gmail.com>
+Date:   Sat Mar 7 13:23:23 2015 +0100
+
+    Remove linux/msdos_fs.h includes
+    
+    Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 245d0cc
+Author: Álvaro Fernández Rojas <noltari@gmail.com>
+Date:   Thu Feb 26 19:22:54 2015 +0100
+
+    Remove non standard int types
+    
+    Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 628b3ec
+Author: Álvaro Fernández Rojas <noltari@gmail.com>
+Date:   Thu Feb 26 19:41:19 2015 +0100
+
+    Improve .gitignore
+    
+    Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 02b5a6d
+Author: Álvaro Fernández Rojas <noltari@gmail.com>
+Date:   Thu Feb 26 12:51:23 2015 +0100
+
+    Makefile: avoid using install -D
+    
+    OS X and FreeBSD are not compatible with this option
+    
+    Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit fb6aea6
+Author: Álvaro Fernández Rojas <noltari@gmail.com>
+Date:   Thu Feb 26 12:50:57 2015 +0100
+
+    Makefile: fix typo in uninstall-man
+    
+    Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 357ab07
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Thu Feb 19 21:22:54 2015 +0100
+
+    fsck.fat: Make -r option default
+    
+    Default mode is now to interactively repair with the option to write
+    the changes back at the end (like the -r option) instead of the previous
+    default mode of interactively correcting but never writing back the
+    changes.
+    
+    The -r option continues to be recognized by fsck.fat.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 266a5fc
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Fri Feb 20 03:19:28 2015 +0100
+
+    mkfs.fat: Allow 0xF0 to be specified as media byte
+    
+    Let the -M option accept 0xF0, which should be the proper descriptor
+    byte for 3.5" 1.44 MB and 2.88 MB floppies.
+    
+    Also split the error reporting for -M between badly formatted and
+    invalid numbers.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 16e97e9
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Wed Jan 28 15:07:18 2015 +0100
+
+    Make all char* that may take literals const
+    
+    Every char* variable or function argument that may be given a literal
+    string is now made const. Additionally add -Wwrite-strings to CFLAGS to
+    enable a warning where const would be missing.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 8b8948c
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Mon Dec 29 18:24:54 2014 +0100
+
+    mkfs.fat.c: Use unsigned char for binary data
+    
+    Simple char technically works for the dummy_boot_jump variables, but
+    some compiler warning settings would give a warning over a signed char
+    overflowing with the values given as initializers.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 45aeed0
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Sun Nov 16 19:49:22 2014 +0100
+
+    Fix indentation of "fix power loss damage" commit
+    
+    I fixed the (sometimes misleading) indentation of the code introduced in
+    commit 6893c45 to be in line with the reset of the code and also removed
+    the /* PATCH ED+DL */ comments which are quite meaningless to the wider
+    world.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 75c5446
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Sat Nov 15 16:48:48 2014 +0100
+
+    Add tags and editor backup files to .gitignore
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit cb98ae2 (tag: v3.0.27)
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Wed Nov 12 01:10:23 2014 +0100
+
+    Releasing version 3.0.27.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit e2c8f06
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Wed Nov 12 00:22:17 2014 +0100
+
+    fsck.fat: Don't print version string every time -v is encountered
+    
+    Remove the printing of the version string every time -v is seen during
+    command line parsing in fsck.fat. The version string is printed anyway
+    before opening the filesystem device/image.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 82076b6
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Tue Nov 11 23:25:30 2014 +0100
+
+    Fix attempt to rename root dir in fsck due to uninitialized fields
+    
+    When add_file() is called with offset 0, it will construct a DIR_ENT for
+    the root directory instead of reading the contents from the filesystem.
+    It did not initialize the whole DIR_ENT on the stack, just select
+    values.
+    
+    In particular, the lcase field was left with an undefined value. If
+    that value happened to include the FAT_NO_83NAME bit, the "neither long
+    nor short file name" check in bad_name() added in 3.0.26 would trigger
+    and cause an attempt to rename the entry (which is not possible).
+    Example run:
+    
+        $ /sbin/fsck.fat -y bad.img
+        fsck.fat 3.0.26 (2014-03-07)
+        /
+          Bad short file name ().
+          Auto-renaming it.
+          Renamed to
+        bad.img: 14 files, 19388/403266 clusters
+    
+    This commit changes the initialization zeroize the whole struct before
+    setting individual fields. Thanks to AlexisM, who found the cause and
+    posted a patch on the Debian bug http://bugs.debian.org/764992 .
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit c24ecb6
+Author: Andreas Bombe <aeb@debian.org>
+Date:   Tue Nov 11 22:49:50 2014 +0100
+
+    Support long file names in volume labeling code
+    
+    The code to find the volume label directory entry in find_volume_de()
+    did not consider long file names so far. Directory entries that make up
+    long file names have four attribute bits set, including the "volume"
+    bit.
+    
+    This caused the code to mistake a directory entry that is part of a
+    long file name as the volume name entry. If such an entry is found
+    first, fatlabel would print garbage when asked to display the label and
+    mangle the long file name when asked to set it. The latter would lead
+    to the loss of the long file name and require a fsck to clean up.
+    
+    Change so that the set of attributes equal that of LFN entries will no
+    langer match as a volume label.
+    
+    Signed-off-by: Andreas Bombe <aeb@debian.org>
+
+commit 1646f6e (tag: v3.0.26)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Mar 7 18:40:13 2014 +0100
+
+    Releasing version 3.0.26.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 6893c45
+Author: Dir Lotter <dirk.lotter@siemens.com>
+Date:   Fri Mar 7 18:25:39 2014 +0100
+
+    Fix "odd" files created by frequent power-loss.
+    
+    After running many power losses the filesystem can degrate, containing "odd"
+    files making the filesystem corrupt that could not be solved by fsck:
+    
+      * file was not visible in a ls -l
+      * ls -i reported a "invalid file name" on the console
+      * a test program with diropen/dirread showed the file, a stat on this file
+        failed
+      * file was not accessible and could not be deleted
+    
+    After digging into the code we found why fsck didn't repair the file system:
+    One thing was we don't have short filenames. Another issue was that the LFN
+    pointer was set to NULL and so it looked like we didn't have short and long
+    filenames.
+    
+    Our patch of check.c includes:
+    
+      * returns 1 from function bad_name() in case no short and no long filename
+        exist
+      * auto_rename() and rename_file() got a special handling for the case no short
+        file name exist:
+        - it enables the short file name (we think here was a weakness of the old
+          code: it changed the short filename but didn't enabled it in the
+          file->dir_ent.lcase entry)
+        - it reset all attributes except ATTR_DIR and ATTR_VOLUME
+    
+    This solved our problem pretty well.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 621e11f
+Author: Natanael Copa <ncopa@alpinelinux.org>
+Date:   Sat Feb 8 18:53:30 2014 +0100
+
+    Build fixes for musl libc.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 52588b7 (tag: v3.0.25)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Jan 17 07:11:11 2014 +0100
+
+    Releasing version 3.0.25.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit acf64ae
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Jan 17 07:09:54 2014 +0100
+
+    Updating copyright headers for 2014.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 21fe921
+Author: Andrew Tridgell <tridge@samba.org>
+Date:   Tue Jan 14 09:37:51 2014 +1100
+
+    Fixed remaining 64 bit build warnings.
+    
+    Some of these may be real bugs.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 9e3a2b1
+Author: Andrew Tridgell <tridge@samba.org>
+Date:   Tue Jan 14 09:25:28 2014 +1100
+
+    Prevent corruption of FAT during fsck on 64 bit platforms.
+    
+    unsigned long is 64 bit on x86-64, which means set_fat was writing two
+    entries, which corrupts the next entry. This can cause loss of data in
+    another file.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 0d2c9bc (tag: v3.0.24)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sat Nov 23 10:36:55 2013 +0100
+
+    Releasing version 3.0.24.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 55bd7b7
+Author: Jaroslav Skarvada <jskarvad@redhat.com>
+Date:   Sat Nov 23 10:34:48 2013 +0100
+
+    Fixed dosfsck on big endian platforms (Resolves: rhbz#1029695).
+    
+    It seems there is problem in the double conversion on big endians.
+    The first conversion is done by the explicit conversion to __u16
+    in the GET_UNALIGNED_W macro, so the secondary conversion by le16toh
+    seems to be redundant (and wrong).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 6debb4a (tag: v3.0.23)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Tue Oct 15 08:05:46 2013 +0200
+
+    Releasing version 3.0.23.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 07d85ff
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Tue Oct 15 08:04:11 2013 +0200
+
+    Reformating mkfs.fat manpage.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 137552f
+Author: Michael Shigorin <mike@altlinux.org>
+Date:   Tue Oct 15 01:29:33 2013 +0400
+
+    Fixing "Fixing default sectors per cluster for FAT32" for UEFI.
+    
+    FAT32 "EFI System Partition" is basically required for UEFI boot;
+    commit ge048a8d broke that for me with both virtualbox-4.2 and
+    real hardware (ASUS C60M1-I to be exact) given ~250Mb filesystem.
+    
+    This commit amends that one by reverting its effects for these
+    small sizes by restoring 512b cluster size for <= 260Mb FAT32.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 2000696
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Aug 9 09:38:13 2013 +0200
+
+    Also allowing lowercase labels in mkfs (with warning message) consistent with the recent fsck change, thanks to Michael Baum <mbaum@devonit.com>.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 9b04807
+Author: Tim Harder <radhermit@gentoo.org>
+Date:   Fri Jul 19 18:15:21 2013 +0200
+
+    Add install-man dependency to install-symlinks Makefile target.
+    
+    This fixes a race condition during parallel installs where man page
+    symlinks won't be installed because install-man hasn't been run yet.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 651f91c (tag: v3.0.22)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Jul 19 07:01:19 2013 +0200
+
+    Releasing version 3.0.22.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 3dc5560
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Jul 19 06:55:24 2013 +0200
+
+    Addding install-symlinks target to phony targets in Makefile.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit c6c0581
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Jul 19 06:55:00 2013 +0200
+
+    Adding uninstall-symlinks target in Makefile.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 465dd8c
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Jul 19 06:45:40 2013 +0200
+
+    Allowing fatlabel to write labels in all lowercase but give a warning about DOS/Windows (Closes: #714971).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 3621b30
+Author: John S Gruber <JohnSGruber@gmail.com>
+Date:   Fri Jul 19 06:40:21 2013 +0200
+
+    Add options and make dos boot sector more compatible with reference system (Closes: #552673).
+    
+    Unless overridden by the user sets the DOS boot sector's
+    hidden-sectors field to match the start of a hard disk's
+    partition.
+    
+    Initialize DOS boot sector drive_number according to FAT media type
+    Addresses LP: #398241 and Debian #552673
+    
+    Adds options to override the DOS boot sector device_number and
+    the FAT media type.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit be1eed5
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jul 17 12:52:20 2013 +0200
+
+    Correcting wrong check preventing installation of fatlabel legacy manpage symlink.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit d0065d3 (tag: v3.0.21)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Tue Jul 16 08:34:28 2013 +0200
+
+    Releasing version 3.0.21.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit a74c12c
+Author: Jaroslav Skarvada <jskarvad@redhat.com>
+Date:   Tue Jun 25 14:53:14 2013 +0200
+
+    Adding the missing -p option to the fsck manpage (to be consistent with the output of the tool).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 25e03c9
+Author: Patrick J. Volkerding <volkerdi@slackware.com>
+Date:   Mon Jun 24 14:23:00 2013 +0200
+
+    Using $MANDIR instead of hardcoded ${PREFIX}/share/man in the Makefile.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 7fd9cf7
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Jun 14 18:50:31 2013 +0200
+
+    Making install-symlinks Makefile target depend on install-bin to not break when using make in parallel, thanks to David Walser <luigiwalser@yahoo.com>.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit a76bbcd
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jun 12 13:00:10 2013 +0200
+
+    Using US digit date format in version date, rather than name abbrev.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit a64195f (tag: v3.0.20)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jun 12 12:25:32 2013 +0200
+
+    Releasing version 3.0.20.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 1a5d99f
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jun 12 12:07:58 2013 +0200
+
+    Softening message about different boot sectors a bit (Closes: #704198).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 4727286
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jun 12 11:42:52 2013 +0200
+
+    Harmonizing program name output.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 17c956c
+Author: Martin Wilck <mwilck@arcor.de>
+Date:   Wed Jun 12 11:38:00 2013 +0200
+
+    Don't align FAT to cluster size.
+    
+    See previous patch for explanation.
+    
+    With this patch and the previous two, the
+    mkdosfs generated FAT32 file systems work well in my extremely
+    picky TechniSat device. Of course, they're also detected cleanly
+    by Linux and Windows.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit d63e0d6
+Author: Martin Wilck <mwilck@arcor.de>
+Date:   Wed Jun 12 11:36:08 2013 +0200
+
+    Don't align FAT32 reserved sectors to cluster size.
+    
+    For certain file system sizes (in particular, exact GB sizes -
+    don't ask me why) a Technisat HD S2 Plus DVB receiver will still
+    choke on mkdosfs generated file systems, even if the sectors per
+    cluster problem is fixed.
+    
+    By comparing the properties of generated FAT32 FS with results
+    of the Windows tool "h2format" (www.heise.de/download/h2format.html),
+    I found that the remaining problems were caused by rounding of the
+    reserved sectors and FAT space to cluster size (the h2format tool
+    doesn't do this).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit e048a8d
+Author: Martin Wilck <mwilck@arcor.de>
+Date:   Wed Jun 12 11:33:33 2013 +0200
+
+    Fixing default sectors per cluster for FAT32 (Closes: #690062).
+    
+    The default sectors per cluster calculated by mkdosfs are outdated,
+    see http://technet.microsoft.com/en-us/library/cc938438.aspx.
+    
+    The deviations may cause some 3rd party devices (e.g. TechniSat DVB
+    receivers) to hang when reading mkdosfs generated file systems.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 86509aa
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Tue Jun 11 20:19:09 2013 +0200
+
+    Splitting out legacy symlink creation in toplevel Makefile to own target.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit da37dd1
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jun 12 11:29:12 2013 +0200
+
+    Correcting wrong toolname in fsck.fat.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit b29a722
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Tue Jun 11 19:51:47 2013 +0200
+
+    Consistently spelling filesystem as filesystem, and not file system.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 977d7aa
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Tue Jun 11 19:30:19 2013 +0200
+
+    Removing Debian reference in GPL license headers.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 5505cc2 (tag: v3.0.19)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Tue Jun 11 18:46:03 2013 +0200
+
+    Releasing version 3.0.19.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 2c88f35
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Tue Jun 11 18:44:50 2013 +0200
+
+    Running indent on source files.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit d495d43
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Tue Jun 11 18:41:41 2013 +0200
+
+    Using memcpy instead of strcpy to fix segfault with fortify, thanks to Dave Reisner <falconindy@jabber.org>.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 9fb4ffc
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Jun 9 13:17:16 2013 +0200
+
+    Correcting fsck.fat spelling error in manpages, thanks to E.J.M. Hartman <E.J.M.Hartman@tudelft.nl>.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 2d8ef9b (tag: v3.0.18)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Thu Jun 6 09:49:00 2013 +0200
+
+    Releasing version 3.0.18.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit d4e1180
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Thu Jun 6 09:38:45 2013 +0200
+
+    Adding initial i18n support for manpages with po4a.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit ea8f712
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Thu Jun 6 09:17:13 2013 +0200
+
+    Renaming tools to sane namespace and keeping legacy symlinks in place.
+    
+    dosfslabel becomes fatlabel,
+    dosfsck becomes fsck.fat,
+    and mkdosfs becomes mkfs.fat.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit a42b127
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jun 5 07:12:03 2013 +0200
+
+    Correcting wrong spelling of Debian in mkdosfs manpage.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 2749084
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jun 5 07:10:50 2013 +0200
+
+    Correcting spelling typo in boot.c.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 6461c83
+Author: Martin Pitt <martinpitt@gnome.org>
+Date:   Fri May 24 09:35:44 2013 +0200
+
+    dosfslabel: Do not read beyond string length (Closes: #709587).
+    
+    When checking whether the label contains any lower-case characters, do not read
+    beyond the end of the string.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 4203a90 (tag: v3.0.17)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed May 29 10:14:09 2013 +0200
+
+    Releasing version 3.0.17.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 3aa88ed
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed May 29 09:48:24 2013 +0200
+
+    Updating maximal lenght of a label in manpage to talk about bytes instead of characters, thanks to Francois Wendling <frwendling@gmail.com> (Closes: #655953).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 0916f8a
+Author: Jaroslav Skarvada <jskarvad@redhat.com>
+Date:   Wed May 29 09:56:08 2013 +0200
+
+    Fixing segfault in dosfslabel.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 4a265c6
+Author: James Byrne <jbyrne@aminocom.com>
+Date:   Mon Apr 22 12:29:51 2013 +0100
+
+    Allow operation on SH4 CPUs and remove compiler warnings.
+    
+    Simplify the GET_UNALIGNED_W macro and use it in all cases instead of making it
+    conditional on CPU types. This missed some CPUs that needed it (e.g. SH4), and
+    in any case the implementation caused "dereferencing type-punned pointer will
+    break strict-aliasing rules" warnings.
+    
+    Enable extra warnings, but disable signed comparison and missing field
+    initializer warnings as these are not helpful.
+    
+    Update write_boot_label() so that the boot_sector_16 and boot_sector cases are
+    handled separately instead of using an aliased pointer, as that causes
+    "dereferencing type-punned pointer will break strict-aliasing rules" warnings.
+    
+    Make date_dos2unix(), usage() and cdiv() static functions as they are only used
+    in the files in which they are declared.
+    
+    Update bad_name() and lfn_get() so that the extension is processed separately
+    instead of by indexing past the end of the name field as that causes "array
+    subscript is above array bounds" warnings.
+    
+    Update the dosfsck() main function to avoid a warning that free_clusters may
+    be used uninitialized. Do not print the final count of files and clusters when
+    dosfsck is run with the "-b" option because the used files and clusters have
+    not been counted in this case.
+    
+    Alter the setup_tables() function so that it does not cause an "array subscript
+    is below array bounds" warning.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit eb68a14
+Author: James Byrne <jbyrne@aminocom.com>
+Date:   Mon Apr 22 13:32:01 2013 +0100
+
+    Add a .gitignore file.
+    
+    Add a .gitignore file so that the results of compilation do not appear as
+    changes.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 336e8f1
+Author: James Byrne <jbyrne@aminocom.com>
+Date:   Mon Apr 22 12:38:52 2013 +0100
+
+    Finish cleanup of byteswap code.
+    
+    Commit 9ba8992 left three references to the old CT_LE_W macro.
+    Remove these since no conversion was needed as the value being
+    converted was zero.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 64b6227
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Thu Apr 4 08:08:00 2013 +0200
+
+    Shortening links to upstream homepage.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 76304be
+Author: Cristian Rodríguez <crrodriguez@opensuse.org>
+Date:   Fri Mar 1 08:23:34 2013 +0100
+
+    Fix offsetof definition.
+    
+      * include stddef.h to get the correct offsetof definition.
+      * remove local offsetof definition, systems not having it on stddef.h
+        are in violation of C89, C99, POSIX.1-2001.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 9ba8992
+Author: Cristian Rodríguez <crrodriguez@opensuse.org>
+Date:   Fri Mar 1 08:58:36 2013 +0100
+
+    Cleanup byteswap code.
+    
+    Remove all duplicate macro definitions for byteswapping routines
+    and replace them for proper usage of userspace endian(3).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 8733e12 (tag: v3.0.16)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed May 29 10:06:01 2013 +0200
+
+    Releasing version 3.0.16.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit a9fa87e
+Author: Petr Gajdos <pgajdos@suse.cz>
+Date:   Fri Mar 1 08:34:12 2013 +0100
+
+    Create rootdir entry volume label with mkdosfs, create it when
+    it doesn't exist with dosfslabel.
+    
+    See https://bugzilla.novell.com/show_bug.cgi?id=657011#c4
+    for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 92057f1
+Author: Petr Gajdos <pgajdos@suse.cz>
+Date:   Fri Mar 1 08:33:18 2013 +0100
+
+    Forbid lowercase letters in label.
+    
+    See https://bugzilla.novell.com/show_bug.cgi?id=657011#c4 and
+    http://support.microsoft.com/kb/71715/en-us for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 5e265c4
+Author: Petr Gajdos <pgajdos@suse.cz>
+Date:   Fri Mar 1 08:32:02 2013 +0100
+
+    Read label also from rootdir entry.
+    
+    See https://bugzilla.novell.com/show_bug.cgi?id=657011#c4
+    for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 5cec53c
+Author: Petr Gajdos <pgajdos@suse.cz>
+Date:   Fri Mar 1 08:30:21 2013 +0100
+
+    alloc_rootdir_entry() is intended to be called with pattern == "FSCK%04dREC",
+    the old code (probably c&p from auto_rename()) doesn't reflect this.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 63938f0
+Author: Petr Gajdos <pgajdos@suse.cz>
+Date:   Fri Mar 1 08:29:00 2013 +0100
+
+    Instead of eleven blanks, fill in "NO NAME    " as specification tells.
+    
+    See https://bugzilla.novell.com/show_bug.cgi?id=657011#c4 and
+    http://www.win.tue.nl/~aeb/linux/fs/fat/fat-1.html for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 10c1c41
+Author: Petr Gajdos <pgajdos@suse.cz>
+Date:   Fri Mar 1 08:58:15 2013 +0100
+
+    Write uppercase letters in label.
+    
+    See https://bugzilla.novell.com/show_bug.cgi?id=657011#c4 and
+    http://support.microsoft.com/kb/71715/en-us for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit a75fb1c (tag: v3.0.15)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Thu Feb 21 15:06:52 2013 +0100
+
+    Releasing version 3.0.15.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit c8f84fd
+Author: Alexander Korolkov <alexander.korolkov@gmail.com>
+Date:   Mon Feb 4 00:22:34 2013 +0400
+
+    Using wcstombs() to convert LFN unicode characters to printable text.
+    
+    This closes Debian bug #596336.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 1546545
+Author: Alexander Korolkov <alexander.korolkov@gmail.com>
+Date:   Sun Sep 5 18:59:47 2010 +0400
+
+    Recode short filenames from DOS codepage (default 437).
+    
+    Recode short filenames from DOS codepage (default 437) to the current
+    character encoding. This makes messages of dosfsck more readable.
+    Partially closes Debian bug #596336.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit ad76cae
+Author: Jaroslav Skarvada <jskarvad@redhat.com>
+Date:   Thu Feb 21 14:40:52 2013 +0100
+
+    Fixing root directory allocation.
+    
+    See https://bugzilla.redhat.com/show_bug.cgi?id=674095 for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit b8201b3
+Author: Jaroslav Skarvada <jskarvad@redhat.com>
+Date:   Thu Feb 21 14:40:25 2013 +0100
+
+    Fixing device detection.
+    
+    See https://bugzilla.redhat.com/show_bug.cgi?id=710480 for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 7a75638 (tag: v3.0.14)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jan 23 13:22:01 2013 +0100
+
+    Releasing version 3.0.14.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 5bdd7ef
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jan 23 13:16:20 2013 +0100
+
+    Documenting dosfsck -b in its manpage.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit a307be2
+Author: Oleksij Rempel <bug-track@fisher-privat.net>
+Date:   Wed Jan 23 12:36:56 2013 +0100
+
+    Adding option for bootsector read-only check.
+    
+    Most boot sectors may contains marker for filesystem state. We can this
+    bit on every mount and warn user if some thing wrong, without checking
+    complete filesystem.
+    
+    Signed-off-by: Oleksij Rempel <bug-track@fisher-privat.net>
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit ce2f8dc
+Author: Oleksij Rempel <bug-track@fisher-privat.net>
+Date:   Wed Jan 23 12:35:13 2013 +0100
+
+    Checking boot sector for dirty bit.
+    
+    Some OSos use reseved byte of boot sector to set state of the file
+    system. If first bit set, then filesystem is proably damaged - write
+    operation was not finished/cache not snycted/...
+    
+    Signed-off-by: Oleksij Rempel <bug-track@fisher-privat.net>
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit f33ee8c
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jan 23 12:25:59 2013 +0100
+
+    Completing and updating all copyright headers for 2013.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit bfe6d25
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jan 23 12:17:20 2013 +0100
+
+    Updating my email address.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 13cdb4d (tag: v3.0.13)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sat Jun 30 19:10:44 2012 +0200
+
+    Releasing version 3.0.13.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit d039482
+Author: Jaroslav Škarvada <jskarvad@redhat.com>
+Date:   Sat Jun 30 19:09:11 2012 +0200
+
+    Fix 'dosfslabel throws "Seek to 114116076544:Invalid argument" error when labeling'.
+    
+    See https://bugzilla.redhat.com/show_bug.cgi?id=693662 for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit e243612 (tag: v3.0.12)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sat Oct 29 08:40:53 2011 +0200
+
+    Releasing version 3.0.12.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 025b4f0
+Author: Michael Casadevall <mcasadevall@ubuntu.com>
+Date:   Tue Jun 7 19:19:30 2011 +0200
+
+    Correcting miscalculation of sector number in some cases.
+    
+    mkdosfs will incorrectly calculate the number of sectors of a
+    given FAT partition if the number sectors are odd due to
+    count_blocks incorrectly handling the remainder of a division
+    operation. This miscalculation causes the OMAP4 bootloader to
+    fail to boot.
+    
+    This bug can be observed by comparing the total sector size in
+    fdisk expert more to fsck.msdos; this discrepancy only shows up
+    when the number of sectors are odd.
+    
+    See https://bugs.launchpad.net/ubuntu/+source/dosfstools/+bug/794043
+    for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 91a1fb9
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sat Jan 8 23:38:59 2011 +0100
+
+    Re-running Nindent.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 0390c4c
+Author: Sergey Gusarov <laborer2008@gmail.com>
+Date:   Sat Jan 8 23:36:11 2011 +0100
+
+    Fixing compiler warnings related to the mismatch of types "char *" / "unsigned
+    char *".
+    
+    These warnings appear when you compile the project with the option "-Wall", what
+    is done with the current default Makefile.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 4a8f8a6
+Author: Jaroslav Skarvada <jskarvad@redhat.com>
+Date:   Thu Jan 6 22:35:00 2011 +0100
+
+    Fixing overflow bug in reclaim_file function, see
+    https://bugzilla.redhat.com/show_bug.cgi?id=660154 for more information.
+    
+    The problem is that alloc_rootdir_entry counts with 10000 files at max, but the
+    filename buffer is only 8 chars long. Due to pattern mask used it results to
+    only 10 files at max (FSCK0-9REC). If there is more than 10 files, it overflows
+    and hangs.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit e0366da
+Author: Sergey Gusarov <laborer2008@gmail.com>
+Date:   Thu Jan 6 22:31:39 2011 +0100
+
+    Fixing conversion specifiers in accordance with the type of expressions.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 2d8be9c
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Jan 2 15:41:44 2011 +0100
+
+    Indenting source files.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 697af85
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Jan 2 15:39:03 2011 +0100
+
+    Adding Nindent script from syslinux.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 66d55cd (tag: v3.0.11)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Dec 24 17:58:29 2010 +0100
+
+    Releasing version 3.0.11.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit d579802
+Author: Michael Stapelberg <michael@stapelberg.de>
+Date:   Fri Nov 19 14:09:36 2010 +0100
+
+    Add better error message when the device cannot be opened.
+    
+    This is helpful for SD cards or other removable media which have an enabled
+    write lock -- without the "Permission denied" message, the user has to strace
+    mkdosfs to find out what's going on.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit bb6000f
+Author: Jaroslav Skarvada <jskarvad@redhat.com>
+Date:   Fri Oct 8 13:38:16 2010 +0200
+
+    Unalign on s390x, see http://bugzilla.redhat.com/show_bug.cgi?id=624596 for
+    more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 5ef7f1f (tag: v3.0.10)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Sep 12 09:35:47 2010 +0200
+
+    Releasing version 3.0.10.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit ea41797
+Author: Alexander Korolkov <alexander.korolkov@gmail.com>
+Date:   Sun Sep 12 09:29:12 2010 +0200
+
+    Modify LFN direntries when file is renamed or deleted, see
+    Debian bug #596329.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit e56ff72
+Author: Alexander Korolkov <alexander.korolkov@gmail.com>
+Date:   Sun Sep 12 09:27:07 2010 +0200
+
+    If the test of short filename fails, dosfsck could complain about
+    bad long filename, see Debian bug #596327.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit f0a42d0
+Author: Alexander Korolkov <alexander.korolkov@gmail.com>
+Date:   Sun Sep 12 09:24:47 2010 +0200
+
+    dosfsck: don't complain about bad filenames when short filename
+    contains 7 or more characters with codes 128-255, see Debian
+    bug #596327.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 0113c5b
+Author: Mitch Rybczynski <mrybczynski@miovision.com>
+Date:   Mon Jul 5 14:45:54 2010 +0200
+
+    Adding __arm__ define check for some crosscompile toolchains.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 88cb84f
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Mar 14 16:42:32 2010 +0100
+
+    Modernizing dosfslabel manpage.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 5aa7ec4
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Mar 14 16:33:47 2010 +0100
+
+    Modernizing dosfsck manpage.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 807ed80
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Mar 14 16:05:32 2010 +0100
+
+    Fixing spelling error in boot.c.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 5b6849d (tag: v3.0.9)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Jan 31 08:31:32 2010 +0100
+
+    Releasing version 3.0.9.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 33bca7d
+Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+Date:   Sun Jan 31 00:11:41 2010 -0500
+
+    Be sure to store the updated reserved_sector count in the boot sector,
+    see Debian bug #567337.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 2a3bef8 (tag: v3.0.8)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sat Jan 23 10:16:18 2010 +0100
+
+    Releasing version 3.0.8.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 726c02d
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sat Jan 23 10:15:01 2010 +0100
+
+    Removing some cruft in end-comments.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit a5961d7
+Author: Steven J. Magnani <steve@digidescorp.com>
+Date:   Thu Jan 21 16:58:11 2010 +0100
+
+    When compiling a 32-bit version of dosfstools on an x86_64 machine,
+    the resulting applications report strange errors on "large" (> 2 GiB)
+    partitions:
+    
+      Seek to -2118967808:Invalid argument
+    
+      Warning: Filesystem is FAT32 according to fat_length and fat32_length fields,
+      but has only 8613 clusters, less than the required minimum of 65525.
+      This may lead to problems on some systems.
+    
+    This appears to be due to compilation with a 32-bit off_t and lseek() library
+    function.
+    
+    Use lseek64 for positioning, and change some suspect uses of off_t to loff_t.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit bbb25bf
+Author: Steven J. Magnani <steve@digidescorp.com>
+Date:   Thu Jan 21 16:56:26 2010 +0100
+
+    If dosfsck is run in read-only mode (-n), exit with code 0
+    if the only issue found is an uninitialized free cluster summary.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 1cae726
+Author: Steven J. Magnani <steve@digidescorp.com>
+Date:   Thu Jan 21 16:55:30 2010 +0100
+
+    On x86_64, dosfsck incorrectly claims that a free_cluster summary of
+    0xFFFFFFFF, defined by Microsoft to be "uninitialized," is wrong.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 62f806a
+Author: H. Peter Anvin <hpa@zytor.com>
+Date:   Fri Jan 8 09:16:38 2010 +0100
+
+    mkdosfs: correct alignment of the root directory.
+    
+    Correct the code to align the root directory; it was broken before
+    since bs.dir_entries had already been set at the point of alignment.
+    This patch removes the dual use of bs.dir_entries and root_dir_entries
+    to carry the same information: the information is carried in
+    root_dir_entires exclusively, and then bs.dir_entries is set inside
+    setup_tables() at a late point.
+    
+    The code to align the root directory is also wrapped in
+    if (align_structures); this avoids rounding the number of root
+    directory entries up to a whole sector when used with -a
+    (i.e. preserves the previous behavior.)
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 8825bda
+Author: H. Peter Anvin <hpa@zytor.com>
+Date:   Wed Jan 6 20:55:36 2010 +0100
+
+    mkdosfs: improve wording in the man page for the -a option.
+    
+    Improve the English language used in the man page for the -a (no
+    align) option to mkdosfs.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 21d3f81
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Wed Jan 6 11:27:25 2010 +0100
+
+    Adding reference to dosfslable in mkdosfs manpage.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 247ba06
+Author: H. Peter Anvin <hpa@zytor.com>
+Date:   Wed Jan 6 11:18:55 2010 +0100
+
+    mkdosfs: by default align all structures to cluster boundaries
+    
+    Align all data structures (reserved sectors, FATs, root directory for
+    FAT12/16) to an even multiple of the cluster size. This means that if
+    the partition is aligned, so will all clusters be. This adds
+    significant performance for anything where the physical sector size is
+    larger than the logical sector size, e.g. flash media or large-sector
+    hard disks.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 171bc07 (tag: v3.0.7)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Thu Dec 24 10:53:36 2009 +0100
+
+    Releasing version 3.0.7.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 28708fc
+Author: Ben Hutchings <ben@decadent.org.uk>
+Date:   Thu Dec 24 09:55:52 2009 +0100
+
+    Fixing dosfslabel to set volume label in the right place,
+    see Debian bug #559985.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 2c405dd
+Author: Lubomir Rintel <lkundrak@v3.sk>
+Date:   Thu Dec 24 09:39:39 2009 +0100
+
+    Fixing out-of bound writes.
+    
+    Firstly, packed attribute is added to the structure so that extension
+    is guarranteed to immediately follow name for the cross-name-extension
+    reads to succeed.
+    
+    Secondly, writes into dir_entry->name that span through the extension as
+    well are split into two, so that FORTIFY_SOURCE's bound checking does
+    not abort dosfsck. There also was an off-by-one error in auto_rename()'s
+    sprintf().
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit b8f3efe
+Author: San Mehat <san@google.com>
+Date:   Thu Dec 24 09:31:41 2009 +0100
+
+    Adding custom exit code in dosfsck for the case where the FS is read only.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 0657e01 (tag: v3.0.6)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Oct 4 10:59:33 2009 +0200
+
+    Releasing version 3.0.6.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit bc84254
+Author: Steven J. Magnani <steve@digidescorp.com>
+Date:   Sun Oct 4 10:58:43 2009 +0200
+
+    Attempt to improve clarity of the orphan cluster reclaim code.
+    Minor optimization - remove some unnecessary checking.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 8054b4a
+Author: Steven J. Magnani <steve@digidescorp.com>
+Date:   Sun Oct 4 08:37:19 2009 +0200
+
+    Close hole that permitted clusters to link to (invalid) cluster 1.
+    
+    If an orphan chain that linked to cluster 1 was reclaimed to a file,
+    deletion of the file would result in a filesystem panic.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit e51af88
+Author: Steven J. Magnani <steve@digidescorp.com>
+Date:   Sun Oct 4 08:32:30 2009 +0200
+
+    Fix erroneous report of huge number of clusters in use on big-endian
+    systems when the FSINFO free cluster count is reset.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 16ba63f (tag: v3.0.5)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Mon Jul 27 14:26:11 2009 +0200
+
+    Releasing version 3.0.5.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 28ff9d9
+Author: Piotr Kaczuba <pepe@attika.ath.cx>
+Date:   Sun Jul 26 22:21:25 2009 +0200
+
+    Signed/unsigned char mismatch in check.c causes false positives
+    in bad_name() and can result in data loss, see Debian bug #538758.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit d42a273
+Author: Andrew Tridgell <tridge@samba.org>
+Date:   Sun Jul 26 22:12:06 2009 +0200
+
+    Update to new kernel patches that add FAT_NO_83NAME flag.
+    
+    See http://lkml.org/lkml/2009/7/20/425 and
+    http://lkml.org/lkml/2009/7/20/424 for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit dd0f0b5 (tag: v3.0.4)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Tue Jul 21 08:10:52 2009 +0200
+
+    Releasing version 3.0.4.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit b9f37a6
+Author: Andrew Tridgell <tridge@samba.org>
+Date:   Tue Jul 21 07:59:22 2009 +0200
+
+    Modify dosfstools to support the dummy 8.3 short filename values
+    used by Linux systems with the VFAT_FS_DUALNAMES option disabled.
+    
+    See http://lkml.org/lkml/2009/6/26/313 and
+    http://lkml.org/lkml/2009/6/26/314 for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit ecd15e8
+Author: Paul Rupe <prupe@nc.rr.com>
+Date:   Tue May 19 10:37:52 2009 +0200
+
+    Fixing "Too many files need repair" error during fsck.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 7c16098 (tag: v3.0.3)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Mon May 18 15:12:04 2009 +0200
+
+    Releasing version 3.0.3.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit b396dcf
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Mon May 18 15:10:55 2009 +0200
+
+    Also declaring arm as an unaligned architecture, see Debian bug #502961.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit ff1b24e
+Author: Steven J. Magnani <steve@digidescorp.com>
+Date:   Mon May 18 15:01:49 2009 +0200
+
+    Adding support for limited-memory embedded systems.
+    
+    This patch reorganizes heap memory usage by dosfsck and mkdosfs
+    to support limited-memory embedded systems - in particular, those
+    based on Xilinx's Microblaze processor. It also adds a few comments.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 89f0b72
+Author: Mike Frysinger <vapier@gentoo.org>
+Date:   Thu Mar 5 07:03:36 2009 +0100
+
+    Declaring Blackfin as an unaligned architecture.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit b54a8a4 (tag: v3.0.2)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sat Feb 28 09:48:04 2009 +0100
+
+    Releasing version 3.0.2.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 9500529
+Author: Hiroaki Ishizawa <hiroaki.ishizawa@gmail.com>
+Date:   Fri Feb 13 10:00:46 2009 +0100
+
+    dosfsck corrupts root directory when fs->nfats is 1.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 043f8a8
+Author: Stepan Kasal <skasal@redhat.com>
+Date:   Fri Jan 30 14:56:33 2009 +0100
+
+    src/dosfslabel.c (main): After writing the label, exit code should be 0.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 017da27
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Jan 30 14:06:01 2009 +0100
+
+    Also installing ChangeLog in install-doc target of Makefile.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 1c76f0f
+Author: Stepan Kasal <skasal@redhat.com>
+Date:   Fri Jan 30 14:05:12 2009 +0100
+
+    Makefile: Do not clobber time stamps of doc files.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit df2d2f1 (tag: v3.0.1)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Nov 23 22:45:45 2008 +0100
+
+    Releasing version 3.0.1.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 17b269b
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Nov 23 18:41:01 2008 +0100
+
+    Applying Fedoras dosfstools-vfat-timingfix.diff from Bill Nottingham
+    <notting@redhat.com> to fix vfat timing issue. See
+    https://bugzilla.redhat.com/show_bug.cgi?id=448247 for more information.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit e597caf
+Author: Ulrich Mueller <ulm@gentoo.org>
+Date:   Tue Oct 7 07:55:37 2008 +0200
+
+    Patch to check for bad number of clusters in dosfsck:
+    
+      * FAT16 filesystems with 65525 clusters or more will be rejected
+        (Before, this was not tested for. Up to 65535 clusters were accepted
+        as good).
+    
+      * For FAT32 filesystems with less than 65525 a warning message will be
+        output.
+    
+    Macro MSDOS_FAT12 is now replaced by FAT12_THRESHOLD to make it
+    consistent with the definition in mkdosfs and to remove the dependency
+    on the kernel version.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 90102bc
+Author: Dann Frazier <dannf@hp.com>
+Date:   Tue Sep 30 07:25:19 2008 +0200
+
+    Changing some wording to make the indended meaning of "full-disk device"
+    more obvious.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 21e9ba0 (tag: v3.0.0)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Sep 28 11:43:19 2008 +0200
+
+    Releasing version 3.0.0.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit eaf145d
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Sep 28 11:29:01 2008 +0200
+
+    Adding GPL headers to all files.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 0826117
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sun Sep 28 10:51:55 2008 +0200
+
+    Adding new GPL license file.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit f8d6127
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 23:31:12 2008 +0200
+
+    Redoing Makefile from scratch.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit b4feb73
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Sat Sep 27 00:17:38 2008 +0200
+
+    Removing whitespaces in all files at EOL and EOF.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 1410138
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 23:48:56 2008 +0200
+
+    Adding Debians dosfslabel.8 manpage from Francois Wendling
+    <frwendling@free.fr>.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit f62e7f2
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 18:36:04 2008 +0200
+
+    Updating version.h includes to new location of version.h file.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 32e5952
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 18:19:36 2008 +0200
+
+    Removing old lsm file.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 25a433b
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 18:07:47 2008 +0200
+
+    Removing old cvsignore files.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit acac13f
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 18:18:39 2008 +0200
+
+    Removing old build file.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 3ecdd21
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 18:19:16 2008 +0200
+
+    Removing old GPL license files.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit f183d0e
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 18:21:57 2008 +0200
+
+    Unifying dosfsck and mkdosfs Makefiles in common src/Makefile.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 61e7466
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 18:04:02 2008 +0200
+
+    Unifying dosfsck and mkdosfs sources in common src directory.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 7552d57
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 18:05:27 2008 +0200
+
+    Unifying dosfsck and mkdosfs manpages in common man directory.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 124598b
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 18:12:29 2008 +0200
+
+    Unifying dosfsck and mkdosfs documents in common doc directory.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit fb9c46b
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 15:39:51 2008 +0200
+
+    Applying Gentoos dosfstools-2.11-preen.patch from Roy Marples
+    <uberlord@gentoo.org> to alias dosfsck -p to -a:
+    
+      * Map -p to -a for baselayout-2, #177514.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit aaa40a9
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 15:49:43 2008 +0200
+
+    Applying Gentoos dosfstools-2.11-build.patch from Mike Frysinger
+    <vapier@gentoo.org> to improve Makefile:
+    
+      * Respect user settings #157785/#157786 by Diego Petteno.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 251626d
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 15:37:34 2008 +0200
+
+    Applying Gentoos dosfstools-2.11-verify-double-count-fix.patch from
+    Robin H. Johnson <robbat2@gentoo.org> to fix double count of files
+    during verification:
+    
+      * Don't double-count n_files during a verification pass.
+        Bugzilla: http://bugs.gentoo.org/show_bug.cgi?id=99845
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit e670ea8
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 15:33:36 2008 +0200
+
+    Applying Gentoos dosfstools-2.11-fat32size.patch from Mike Frysinger
+    <vapier@gentoo.org> to fix generation of filesystems on 256meg devices:
+    
+      * Fix generation of FAT filesystems on devices that are 256meg in size
+        Patch by Ulrich Mueller and accepted upstream
+        http://bugs.gentoo.org/112504
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit a6dc6a4
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 15:22:06 2008 +0200
+
+    Applying Suses dosfstools-2.11-unsupported-sector-size.patch from Petr
+    Gajdos <pgajdos@suse.cz> to add sector size warning:
+    
+      * added warning for creation msdos on filesystem with sector size
+        greater than 4096 [fate#303325]
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit f746956
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 15:18:35 2008 +0200
+
+    Applying Suses dosfstools-2.11-mkdosfs-geo0.diff from Ludwig Nussel
+    <lnussel@suse.de> to fix handling of zero heads and sectors:
+    
+      * the HDIO_GETGEO ioctl works on device mapper devices but returns
+        zero heads and sectors. Therefore let's a) assume dummy values in
+        that case in mkdosfs and b) don't consider such fat file systems as
+        invalid in dosfsck. The Linux kernel accepts them anyways.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit cf243e4
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 15:15:40 2008 +0200
+
+    Applying Suses dosfstools-2.11-linuxfs.patch from Ruediger Oertel
+    <ro@suse.de> to not include linux/fs.h.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 2d4f184
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 15:11:50 2008 +0200
+
+    Applying Fedoras dosfstools-2.11-assumeKernel26.patch from Peter Vrabec
+    <pvrabec@redhat.com> to remove linux 2.6 conditionals:
+    
+      * LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) does not work with
+        glibc-kernheaders-2.4-9.1.94
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 739a6fb
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 15:05:00 2008 +0200
+
+    Applying Debians 99-conglomeration.dpatch (no other information
+    available).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 3b5ed8a
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:26:41 2008 +0200
+
+    Applying Debians 15-manpage-files.dpatch from Daniel Baumann
+    <daniel@debian.org> to improve dosfsck manpage:
+    
+      * Lists fsckNNNN.rec files in FILES section (Closes: #444596).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 3b6a863
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:34:42 2008 +0200
+
+    Applying Debians 13-getopt.dpatch from Adonikam Virgo
+    <adonikam@virgonet.org> to fix mkdosfs getopt:
+    
+      * Fixes backup sector getopt (Closes: #232387, #479794).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 1b2c8ca
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:34:17 2008 +0200
+
+    Applying Debians 12-zero-slot.dpatch by Karl Tomlinson
+    <karlt@karlt.net> to fix dosfsck zero slot crashes:
+    
+      * Fixes crashes due to zero slot numbers causing a negative offset in
+        the call to copy_lfn_part in lfn_add_slot. On amd64 this results in
+        a SIGSEGV in copy_lfn_part. On x86 the result is heap corruption and
+        thus sometimes a SIGSEGV or double free abort later. (Closes:
+        #152550, #353198, #356377, #401798).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit eec8585
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:33:54 2008 +0200
+
+    Applying Debians 11-memory-efficiency.dpatch from Eero Tamminen
+    <eero.tamminen@nokia.com> to improve dosfsck memory efficiency:
+    
+      * Improves memory efficiency when checking filesystems.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 06bd669
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:33:28 2008 +0200
+
+    Applying Debians 10-manpage-synopsis.dpatch from Daniel Baumann
+    <daniel@debian.org> to fix manpage synopsis:
+    
+      * List alternative binary names in manpage synopsis (Closes: #284983).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 42d340d
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:32:46 2008 +0200
+
+    Applying Debians 09-manpage-fat32.dpatch from Daniel Baumann
+    <daniel@debian.org> to improve mkdosfs manpage:
+    
+      * Don't claim that FAT32 is not choosed automatically (Closes:
+        #414183).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 0f5ce0d
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:32:23 2008 +0200
+
+    Applying Debians 08-manpage-drop.dpatch from Daniel Baumann
+    <daniel@debian.org> to improve dosfsck manpage:
+    
+      * Don't use confusing word 'drop' when 'delete' is meant (Closes:
+        #134100).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 8ec54dd
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:31:50 2008 +0200
+
+    Applying Debians 07-manpage-spelling.dpatch from Justin Pryzby
+    <justinpryzby@users.sourceforge.net> to fix mkdosfs manpage typos.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 4371588
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:30:31 2008 +0200
+
+    Applying Suses dosfstools-2.11_determine-sector-size.patch from Petr
+    Gajdos <pgajdos@suse.cz> to determine mkdosfs sector size automatically:
+    
+      * determine sector size of device automatically or if -S parameter
+        present, verify, that it's not under physical sector size
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit fc92e19
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:30:03 2008 +0200
+
+    Applying Suses dosfstools-2.11-o_excl.patch from Pavol Rusnak
+    <prusnak@suse.cz> to use O_EXCL in mkdosfs:
+    
+      * mkdosfs now opens device with O_EXCL [#238687]
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 3084697
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 14:29:36 2008 +0200
+
+    Applying Debians 04-unaligned-memory.dpatch from Khalid Aziz
+    <khalid_aziz@hp.com> to fix dosfsck unaligned memory accesses:
+    
+      * Fix unaligned memory accesses which cause warnings to appear
+        everytime the elilo bootloader script runs. This has led a number of
+        users to believe their install has failed (Closes: #258839).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 6d5c091
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 13:47:40 2008 +0200
+
+    Applying Fedoras dosfstools-2.11-label.patch from Jeremy Katz
+    <katzj@redhat.com> to add dosfslabel (originally by Peter Jones).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 07ef487
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 13:41:14 2008 +0200
+
+    Applying Fedoras dosfstools-2.11-fortify.patch from Jakub Jelinek
+    <jakub@redhat.com> to make it build with -D_FORTIFY_SOURCE=2:
+    
+      * This violates -D_FORTIFY_SOURCE=2 (which is stricter than C
+        standard), but isn't actually any buffer overflow. But using memcpy
+        is more efficient anyway.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit 78f9dca
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Fri Sep 26 13:40:47 2008 +0200
+
+    Applying Fedoras dosfstools-2.7-argfix.patch (no other information
+    available).
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
+
+commit ba6774a (tag: v2.11)
+Author: Daniel Baumann <mail@daniel-baumann.ch>
+Date:   Thu Jun 26 12:45:36 2008 +0200
+
+    Adding version 2.11.
+    
+    Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
diff --git a/dosfstools/Makefile b/dosfstools/Makefile
new file mode 100644
index 0000000..fa99355
--- /dev/null
+++ b/dosfstools/Makefile
@@ -0,0 +1,170 @@
+# Makefile
+#
+# Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+#
+# 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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# The complete text of the GNU General Public License
+# can be found in /usr/share/common-licenses/GPL-3 file.
+
+SHELL := sh -e
+LANGUAGES = $(shell cd manpages/po && ls)
+
+DESTDIR =
+PREFIX = /usr/local
+SBINDIR = $(PREFIX)/sbin
+DOCDIR = $(PREFIX)/share/doc
+MANDIR = $(PREFIX)/share/man
+
+#OPTFLAGS = -O2 -fomit-frame-pointer -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
+OPTFLAGS = -O2 -fomit-frame-pointer -D_GNU_SOURCE $(shell getconf LFS_CFLAGS)
+#WARNFLAGS = -Wall -pedantic -std=c99
+WARNFLAGS = -Wall -Wextra -Wno-sign-compare -Wno-missing-field-initializers -Wmissing-prototypes -Wstrict-prototypes -Wwrite-strings
+DEBUGFLAGS = -g
+CFLAGS += $(OPTFLAGS) $(WARNFLAGS) $(DEBUGFLAGS)
+
+VPATH = src
+
+all: build
+
+build: fatlabel fsck.fat mkfs.fat
+
+fatlabel: boot.o check.o common.o fat.o file.o io.o lfn.o fatlabel.o
+
+fsck.fat: boot.o check.o common.o fat.o file.o io.o lfn.o fsck.fat.o
+
+mkfs.fat: mkfs.fat.o
+
+rebuild: distclean build
+
+install: install-bin install-doc install-man install-symlinks
+
+install-bin: build
+	install -d -m 0755 $(DESTDIR)/$(SBINDIR)
+	install -m 0755 fatlabel fsck.fat mkfs.fat $(DESTDIR)/$(SBINDIR)
+
+install-doc:
+	install -d -m 0755 $(DESTDIR)/$(DOCDIR)/dosfstools
+	install -p -m 0644 ChangeLog doc/* $(DESTDIR)/$(DOCDIR)/dosfstools
+
+install-man:
+	for MANPAGE in manpages/en/*; \
+	do \
+		SECTION="8"; \
+		mkdir -p $(DESTDIR)/$(MANDIR)/man$${SECTION}/; \
+		install -m 0644 $${MANPAGE} $(DESTDIR)/$(MANDIR)/man$${SECTION}/$$(basename $${MANPAGE}); \
+	done
+
+	for LANGUAGE in $(LANGUAGES); \
+	do \
+		for MANPAGE in manpages/$${LANGUAGE}/*; \
+		do \
+			SECTION="8"; \
+			mkdir -p $(DESTDIR)/$(MANDIR)/$${LANGUAGE}/man$${SECTION}/; \
+			install -m 0644 $${MANPAGE} $(DESTDIR)/$(MANDIR)/$${LANGUAGE}/man$${SECTION}/$$(basename $${MANPAGE} .$${LANGUAGE}.$${SECTION}).$${SECTION}; \
+		done; \
+	done
+install-symlinks: install-bin install-man
+	if [ -e $(DESTDIR)/$(SBINDIR)/fatlabel ]; \
+	then \
+		ln -sf fatlabel $(DESTDIR)/$(SBINDIR)/dosfslabel; \
+		if [ -e $(DESTDIR)/$(MANDIR)/man8/fatlabel.8 ]; \
+		then \
+			ln -sf fatlabel.8 $(DESTDIR)/$(MANDIR)/man8/dosfslabel.8; \
+		fi; \
+	fi
+
+	if [ -e $(DESTDIR)/$(SBINDIR)/fsck.fat ]; \
+	then \
+		ln -sf fsck.fat $(DESTDIR)/$(SBINDIR)/dosfsck; \
+		ln -sf fsck.fat $(DESTDIR)/$(SBINDIR)/fsck.msdos; \
+		ln -sf fsck.fat $(DESTDIR)/$(SBINDIR)/fsck.vfat; \
+		if [ -e $(DESTDIR)/$(MANDIR)/man8/fsck.fat.8 ]; \
+		then \
+			ln -sf fsck.fat.8 $(DESTDIR)/$(MANDIR)/man8/dosfsck.8; \
+			ln -sf fsck.fat.8 $(DESTDIR)/$(MANDIR)/man8/fsck.msdos.8; \
+			ln -sf fsck.fat.8 $(DESTDIR)/$(MANDIR)/man8/fsck.vfat.8; \
+		fi; \
+	fi
+
+	if [ -e $(DESTDIR)/$(SBINDIR)/mkfs.fat ]; \
+	then \
+		ln -sf mkfs.fat $(DESTDIR)/$(SBINDIR)/mkdosfs; \
+		ln -sf mkfs.fat $(DESTDIR)/$(SBINDIR)/mkfs.msdos; \
+		ln -sf mkfs.fat $(DESTDIR)/$(SBINDIR)/mkfs.vfat; \
+		if [ -e $(DESTDIR)/$(MANDIR)/man8/mkfs.fat.8 ]; \
+		then \
+			ln -sf mkfs.fat.8 $(DESTDIR)/$(MANDIR)/man8/mkdosfs.8; \
+			ln -sf mkfs.fat.8 $(DESTDIR)/$(MANDIR)/man8/mkfs.msdos.8; \
+			ln -sf mkfs.fat.8 $(DESTDIR)/$(MANDIR)/man8/mkfs.vfat.8; \
+		fi; \
+	fi
+
+uninstall: uninstall-symlinks uninstall-man uninstall-doc uninstall-bin
+
+uninstall-bin:
+	rm -f $(DESTDIR)/$(SBINDIR)/fatlabel
+	rm -f $(DESTDIR)/$(SBINDIR)/fsck.fat
+	rm -f $(DESTDIR)/$(SBINDIR)/mkfs.fat
+
+	rmdir --ignore-fail-on-non-empty $(DESTDIR)/$(SBINDIR)
+
+uninstall-doc:
+	rm -rf $(DESTDIR)/$(DOCDIR)/dosfstools
+
+	rmdir --ignore-fail-on-non-empty $(DESTDIR)/$(DOCDIR)
+
+uninstall-man:
+	for MANPAGE in manpages/en/*; \
+	do \
+		SECTION="8"; \
+		rm -f $(DESTDIR)/$(MANDIR)/man$${SECTION}/$$(basename $${MANPAGE} .en.$${SECTION}).$${SECTION}; \
+	done
+
+	for LANGUAGE in $(LANGUAGES); \
+	do \
+		for MANPAGE in manpages/$${LANGUAGE}/*; \
+		do \
+			SECTION="8"; \
+			rm -f $(DESTDIR)/$(MANDIR)/$${LANGUAGE}/man$${SECTION}/$$(basename $${MANPAGE} .$${LANGUAGE}.$${SECTION}).$${SECTION}; \
+		done; \
+	done
+
+uninstall-symlinks:
+	rm -f $(DESTDIR)/$(SBINDIR)/dosfslabel
+	rm -f $(DESTDIR)/$(MANDIR)/man8/dosfslabel.8
+
+	rm -f $(DESTDIR)/$(SBINDIR)/dosfsck
+	rm -f $(DESTDIR)/$(MANDIR)/man8/dosfsck.8
+	rm -f $(DESTDIR)/$(SBINDIR)/fsck.msdos
+	rm -f $(DESTDIR)/$(MANDIR)/man8/fsck.msdos.8
+	rm -f $(DESTDIR)/$(SBINDIR)/fsck.vfat
+	rm -f $(DESTDIR)/$(MANDIR)/man8/fsck.vfat.8
+
+	rm -f $(DESTDIR)/$(SBINDIR)/mkdosfs
+	rm -f $(DESTDIR)/$(MANDIR)/man8/mkdosfs.8
+	rm -f $(DESTDIR)/$(SBINDIR)/mkfs.msdos
+	rm -f $(DESTDIR)/$(MANDIR)/man8/mkfs.msdos.8
+	rm -f $(DESTDIR)/$(SBINDIR)/mkfs.vfat
+	rm -f $(DESTDIR)/$(MANDIR)/man8/mkfs.vfat.8
+
+reinstall: distclean install
+
+clean:
+	rm -f *.o
+
+distclean: clean
+	rm -f fatlabel fsck.fat mkfs.fat
+
+.PHONY: build rebuild install install-bin install-doc install-man install-symlinks uninstall uninstall-bin uninstall-doc uninstall-man uninstall-symlinks reinstall clean distclean
diff --git a/dosfstools/VERSION b/dosfstools/VERSION
new file mode 100644
index 0000000..0baec4d
--- /dev/null
+++ b/dosfstools/VERSION
@@ -0,0 +1 @@
+3.0.28
diff --git a/dosfstools/bin/Nindent b/dosfstools/bin/Nindent
new file mode 100755
index 0000000..cf8ecfd
--- /dev/null
+++ b/dosfstools/bin/Nindent
@@ -0,0 +1,18 @@
+#!/bin/sh
+PARAM="-npro -kr -i4 -ts8 -sob -l80 -ss -ncs -cp1"
+RES=`indent --version`
+V1=`echo $RES | cut -d' ' -f3 | cut -d'.' -f1`
+V2=`echo $RES | cut -d' ' -f3 | cut -d'.' -f2`
+V3=`echo $RES | cut -d' ' -f3 | cut -d'.' -f3`
+if [ $V1 -gt 2 ]; then
+  PARAM="$PARAM -il0"
+elif [ $V1 -eq 2 ]; then
+  if [ $V2 -gt 2 ]; then
+    PARAM="$PARAM -il0";
+  elif [ $V2 -eq 2 ]; then
+    if [ $V3 -ge 10 ]; then
+      PARAM="$PARAM -il0"
+    fi
+  fi
+fi
+exec indent $PARAM "$@"
diff --git a/dosfstools/doc/ANNOUNCE.mkdosfs b/dosfstools/doc/ANNOUNCE.mkdosfs
new file mode 100644
index 0000000..2e01716
--- /dev/null
+++ b/dosfstools/doc/ANNOUNCE.mkdosfs
@@ -0,0 +1,41 @@
+Announcing the release of mkdosfs version 0.3b (Yggdrasil)
+
+It seems I didn't get the bug completely fixed in 0.3a.  Some
+borderline cases would still allocate too many sectors for the FAT.
+Again, nothing to worry about, just a nitpick -- this one would only
+in certain cases add one sector per FAT.
+
+Announcing the release of mkdosfs version 0.3a (Yggdrasil)
+
+Fixed a bug which would cause too many sectors to be reserved for the
+FAT (filesystem will still work fine, but have slightly less space
+available).
+
+Announcing the release of mkdosfs version 0.3 (Yggdrasil)
+
+This version correctly handles even very large filesystems, and
+properly supports the modern (3.3+) DOS bootsector format, including a
+message printed on boot attempts.
+
+Peter Anvin
+Yggdrasil Computing, Inc.
+hpa@yggdrasil.com
+
+			    --------------
+
+Announcing the release of mkdosfs version 0.2
+
+
+I've just uploaded mkdosfs to sunsite.unc.edu.  It works in a similar way
+to Remy Card's mke2fs, but creates an MS-DOS filesystem.
+
+The filename is mkdosfs-0.2.tar.gz.
+
+This second release should fix a small bug that could lead to FAT sizes that
+Linux's dosfs would accept but MS-DOS wouldn't.
+
+The archive contains a manual page, binary and source versions.
+
+
+Dave Hudson
+dave@humbug.demon.co.uk
diff --git a/dosfstools/doc/ChangeLog.dosfsck b/dosfstools/doc/ChangeLog.dosfsck
new file mode 100644
index 0000000..f628ac2
--- /dev/null
+++ b/dosfstools/doc/ChangeLog.dosfsck
@@ -0,0 +1,10 @@
+Changes from version 0 to 1
+===========================
+
+  - fixed an off-by-two error in check.c:check_file
+  - fixed marking clusters bad in fat.c:set_fat
+  - fat.c:reclaim_free was also reclaiming bad clusters.
+  - fixed many incorrect byte sex conversions in check.c and fat.c
+  - -t and -w now require -a or -r
+  - added option -d to drop files.
+  - added option -u to try to "undelete" non-directory files.
diff --git a/dosfstools/doc/ChangeLog.dosfstools-2.x b/dosfstools/doc/ChangeLog.dosfstools-2.x
new file mode 100644
index 0000000..7ed9efe
--- /dev/null
+++ b/dosfstools/doc/ChangeLog.dosfstools-2.x
@@ -0,0 +1,161 @@
+version 2.11
+============
+
+ - all: don't use own llseek() anymore, glibc lseek() does everything we need
+ - dosfsck: lfn.c: avoid segfault
+ - dosfsck: check.c, lfn.c: check for orphaned LFN slots
+ - dosfsck: check.c alloc_rootdir_entry(): set owner of newly alloced clusters
+ - dosfsck: dosfsck.h: better use <byteswap.h> for byte swapping
+ - dosfsck: io.c: added code for real DOS
+ - mkdosfs: raised FAT12_THRESHOLD from 4078 to 4085, introduced MIN_CLUST_32
+ - mkdosfs: fix loop device size
+ - mkdosfs: by default, use FAT32 on devices >= 512MB
+ - mkdosfs: fix a memory leak (blank_sector)
+ - mkdosfs: fix parsing of number of blocks on command line, so that numbers
+   >2G can be used
+ - mkdosfs: add 'b' to getopt() string so this option can be used :)
+ - mkdosfs: fix parsing of -i arg (should be unsigned)
+ - mkdosfs: change default permissions of created images (-C) to 0666 & ~umask
+ - mkdosfs: relax geometry check: if HDIO_GETGEO fails, print a warning and
+   default to H=255,S=63
+ - dosfsck: new option -n (no-op): just check non-interactively, but
+   don't write anything to filesystem
+ - A few #include changes to support compilation with linux 2.6
+   headers (thanks to Jim Gifford <jim@jg555.com>)
+ - dosfsck: remove directory entries pointing to start cluster 0, if they're
+   not "." or ".." entries that should actually point to the root dir
+   (pointed out by Thomas Winkler <twinkler@sysgo.de>)
+ - mkdosfs: new option -h to set number of hidden sectors
+   (thanks to Godwin Stewart <gstewart@spamcop.net>)
+ - all: updated my mail address everywhere...
+
+version 2.10
+============
+
+ - dosfsck: various 64-bit fixes and removed some warnings by Michal
+   Cihar <mcihar@suse.cz>
+ - mkdosfs: better error message if called without parameters (also
+   suggested by Michal)
+
+version 2.9
+===========
+
+ - dosfsck: if EOF from stdin, exit with error code
+ - dosfsck: Fix potential for "Internal error: next_cluster on bad cluster".
+ - dosfsck: When clearing long file names, don't overwrite the dir
+   entries with all zeros, but put 0xe5 into the first byte.
+   Otherwise, some OSes stop reading the directory at that point...
+ - dosfsck: in statistics printed by -v, fix 32bit overflow in number
+   of data bytes.
+ - dosfsck: fix an potential overflow in "too many clusters" check
+ - dosfsck: fix 64bit problem in fat.c (Debian bug #152769)
+ - dosfsck: allow FAT size > 32MB.
+ - dosfsck: allow for only one FAT
+ - dosfsck: with -v, also check that last sector of the filesystem can
+   be read (in case a partition is smaller than the fs thinks)
+ - mkdosfs: add note in manpage that creating bootable filesystems is
+   not supported.
+ - mkdosfs: better error message with pointer to -I if target is a
+   full-disk device.
+
+version 2.8
+===========
+
+ - dosfsck: Fixed endless loop whenever a volume label was present.
+
+version 2.7
+===========
+
+ - dosfsck: Don't check volume label for bad characters, everything
+   seems to be allowed there... Also ignore duplicate names where one of
+   them is a volume label.
+
+version 2.6
+===========
+
+ - mkdosfs: Added correct heads definition for 2.88M floppies if not
+   created via loopback.
+ - dosfsck: If boot sector and its backup are different (FAT32), offer
+   to write the backup to sector 0. (tnx to Pavel Roskin for this)
+ - For 64 bit alpha, struct bootsector in dosfsck.h must be defined
+   with __attribute__((packed)).
+ - mkdosfs now actually accepts -R option. (tnx to David Kerrawn)
+ - Fixed typo in dosfsck boot.c (recognition of boot signature in FSINFO)
+ - Various compilation fixes for 2.4 kernel headers and for ia64.
+
+version 2.5
+===========
+
+ - The llseek() implementation for alpha didn't really work; fixed it.
+
+version 2.4
+===========
+
+ - Fix compiling problem on alpha (made a silly typo...)
+
+version 2.3
+===========
+
+ - mkdosfs: Fixed usage message (printed only "bad address").
+ - both: made man pages and usage statements more consistent.
+ - both: fix llseek function for alpha.
+ - dosfsck: fix reading of unaligned fields in boot sector for alpha.
+ - dosfsck: fixed renaming of files (extension wasn't really written).
+
+version 2.2
+===========
+
+ - Added dosfsck/COPYING, putting dosfsck officially under GPL (Werner
+   and I agree that it should be GPL).
+ - mkdosfs: Allow creation of a 16 bit FAT on filesystems that are too
+   small for it if the user explicitly selected FAT16 (but a warning
+   is printed). Formerly, you got the misleading error message "make
+   the fs a bit smaller".
+ - dosfsck: new option -y as synonym for -y; for compability with
+   other fs checkers, which also accept this option.
+ - dosfsck: Now prints messages similar to e2fsck: at start version
+   and feature list; at end number of files (and directories) and
+   number of used/total clusters. This makes the printouts of *fsck at
+   boot time nicer.
+ - dosfsck: -a (auto repair) now turns on -f (salvage files), too. -a
+   should act as non-destructive as possible, so lost clusters should
+   be assigned to files. Otherwise the data in them might be
+   overwritten later.
+ - dosfsck: Don't drop a directory with lots of bad entries in
+   auto-repair mode for the same reason as above.
+ - dosfsck: avoid deleting the whole FAT32 root dir if something is
+   wrong with it (bad start cluster or the like).
+ - general: also create symlinks {mkfs,fsck}.vfat.8 to the respective
+   real man pages.
+
+version 2.1
+===========
+
+ - Fix some forgotten loff_t's for filesystems > 4GB. (Thanks to
+   <ki@kretz.co.at>).
+ - Fix typo in mkdosfs manpage.
+ - Removed inclusion of <linux/loop.h> from mkdosfs.c; it's unnecessary and
+   caused problems in some environments.
+ - Fix condition when to expect . and .. entries in a directory. (Was
+   wrong for non-FAT32 if first entry in root dir was a directory also.)
+ - Also create mkfs.vfat and fsck.vfat symlinks, so that also
+   filesystems listed with type "vfat" in /etc/fstab can be
+   automatically checked.
+
+version 2.0
+===========
+
+ - merge of mkdosfs and dosfstools in one package
+ - new maintainer: Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+ - FAT32 support in both mkdosfs and dosfsck
+ - VFAT (long filename) support in dosfsck
+ - Support for Atari variant of MS-DOS filesystem in both tools
+ - Working support for big-endian systems in both tools
+ - Better support for loop devices in mkdosfs: usual floppy sizes are
+   detected and media byte etc. set accordingly; if loop fs has no
+   standard floppy size, use hd params
+   (mainly by Giuliano Procida <gpp10@cus.cam.ac.uk>)
+ - Removed lots of gcc warnings
+ - Fixed some minor calculation bugs in mkdosfs.
+
+For change logs previous to 2.0, see the CHANGES files in the subdirectories.
diff --git a/dosfstools/doc/ChangeLog.mkdosfs b/dosfstools/doc/ChangeLog.mkdosfs
new file mode 100644
index 0000000..e39d9d6
--- /dev/null
+++ b/dosfstools/doc/ChangeLog.mkdosfs
@@ -0,0 +1,18 @@
+28th January 1995		H. Peter Anvin (hpa@yggdrasil.com)
+
+	Better algorithm to select cluster sizes on large filesystems.
+	Added bogus boot sector code that on attempts to boot prints a
+	message (which can be chosen at mkdosfs time) and lets the user
+	press any key and try again.  Corrected support for 1.2 Mb
+	floppies.  mkdosfs now generates the extended bootsector
+	(superblock) format of DOS 3.3+, with support for volume ID's and
+	volume labels (volume labels are also written to the root
+	directory, as they should).
+
+18th February 1994		Dave Hudson (dave@humbug.demon.co.uk)
+
+	Released version 0.2 - clears a bug in the FAT sizing code.
+
+1st September 1993		Dave Hudson (dave@humbug.demon.co.uk)
+
+	Released version 0.1 - ALPHA release of mkdosfs
diff --git a/dosfstools/doc/README.dosfsck b/dosfstools/doc/README.dosfsck
new file mode 100644
index 0000000..3038f36
--- /dev/null
+++ b/dosfstools/doc/README.dosfsck
@@ -0,0 +1,60 @@
+dosfsck, version 1
+==================
+
+WARNING: This is ALPHA test software. Use at your own risk.
+
+dosfsck is the Linux equivalent of PC/MS-DOS' CHKDSK. It checks the
+consistency of PC/MS-DOS filesystems and optionally tries to repair
+them. The tests dosfsck performs are described in the man page.
+
+dosfsck needs header files from dosfs.9 (or later) to compile.
+
+Before using dosfsck to repair a filesystem that contains data of any
+value, you should verify that dosfsck is able to correct all reported
+errors. (Except fatal errors and those reported as unfixable, of
+course.) In order to do this, run it with the -V option, e.g.
+
+   dosfsck -V /dev/sda1		(automatic check)
+or dosfsck -V -r /dev/sda1	(interactive check and repair)
+
+dosfsck will perform two passes: in the first pass, inconsistencies are
+detected and a list of changes to correct the problems is generated. In
+the second pass, those changes are applied whenever dosfsck reads data
+from disk. Hence no fixable errors should be reported in the second
+pass if the first pass was successful.
+
+Please notify the author if fixable errors are reported in the second
+pass.
+
+After verifying that dosfsck appears to be able to perform the desired
+operations, either confirm that you want the changes to be performed
+(if dosfsck was started with -r) or re-run dosfsck with the -a option
+(if it was started without -r).
+
+Please send bug reports, comments, flames, etc. to
+almesber@nessie.cs.id.ethz.ch  or  almesber@bernina.ethz.ch
+
+- Werner
+
+FAT32 and LFN support
+=====================
+
+I've finally implemented some of the new features of MS-DOS
+filesystems: FAT32 and long filenames.
+
+FAT32 is automatically detected and of course the different FAT
+structure is handled. (Internally many changes were needed, so 32 bit
+variables for all cluster numbers and 64 bit vars for offsets inside
+the filesystem.) New checks for FAT32 are most notably on the backup
+boot sector and the new info sector. Also the possibility that the
+root directory resides in a cluster chain (instead of in a static
+area) on FAT32 is handled.
+
+dosfscheck also knows about VFAT long filenames now. It parses those
+names and uses them in listings etc. when available. There are also
+some checks on the (cruel) structure of how LFNs are stored and some
+attempts to fix problems.
+
+- Roman <roman@hodek.net>
+
+BTW, version 2 isn't ALPHA anymore :-)
diff --git a/dosfstools/doc/README.dosfstools-2.x b/dosfstools/doc/README.dosfstools-2.x
new file mode 100644
index 0000000..5fb00ed
--- /dev/null
+++ b/dosfstools/doc/README.dosfstools-2.x
@@ -0,0 +1,60 @@
+
+Atari format support
+====================
+
+Both mkdosfs and dosfsck now can also handle the Atari variation of
+the MS-DOS filesystem format. The Atari format has some minor
+differences, some caused by the different machine architecture (m68k),
+some being "historic" (Atari didn't change some things that M$
+changed).
+
+Both tools automatically select Atari format if they run on an Atari.
+Additionally the -A switch toggles between Atari and MS-DOS format.
+I.e., on an Atari it selects plain DOS format, on any other machine it
+switches to Atari format.
+
+The differences are in detail:
+
+ - Atari TOS doesn't like cluster sizes != 2, so the usual solution
+   for bigger partitions was to increase the logical sector size. So
+   mkdosfs can handle sector sizes != 512 now, you can also manually
+   select it with the -S option. On filesystems larger than approx. 32
+   MB, the sector size is automatically increased (stead of the
+   cluster size) to make the filesystem fit. mkdosfs will always use 2
+   sectors per cluster (also with the floppy standard configurations),
+   except when directed otherwise on the command line.
+
+ - From the docs, all values between 0xfff8 and 0xffff in the FAT mark
+   an end-of-file. However, DOS usually uses 0xfff8 and Atari 0xffff.
+   This seems to be only an consmetic difference. At least TOS doesn't
+   complain about 0xffff EOF marks. Don't know what DOS thinks of
+   0xfff8 :-) Anyway, both tools use the EOF mark common to the
+   system (DOS/Atari).
+
+ - Something similar of the bad cluster marks: On Atari the FAT values
+   0xfff0 to 0xfff7 are used for this, under DOS only 0xfff7 (the
+   others can be normal cluster numbers, allowing 7 more clusters :-)
+   However, both systems usually mark with 0xfff7. Just dosfsck has to
+   interpret 0xfff0...0xfff7 differently.
+
+ - Some fields in the boot sector are interpreted differently. For
+   example, Atari has a disk serial number (used to aid disk change
+   detection) where DOS stores the system name; the 'hidden' field is
+   32 bit for DOS, but 16 bit for Atari, and there's no 'total_sect'
+   field; the 12/16 bit FAT decision is different: it's not based on
+   the number of clusters, but always FAT12 on floppies and FAT16 on
+   hard disks. mkdosfs nows about these differences and constructs the
+   boot sector accordingly.
+
+ - In dosfsck, the boot sector differences also have to known, to not
+   warn about things that are no error on Atari. In addition, most
+   Atari formatting tools fill the 'tracks' and 'heads' fields with 0
+   for hard disks, because they're meaningless on SCSI disks (Atari
+   has/had no IDE). Due to this, the check that they should be
+   non-zero is switched off.
+
+ - Under Atari TOS, some other characters are illegal in filenames:
+   '<', '>', '|', '"', and ':' are allowed, but all non-ASCII chars
+   (codes >= 128) are forbidden.
+
+- Roman <Roman.Hodek@informatik.uni-erlangen.de>
diff --git a/dosfstools/doc/README.mkdosfs b/dosfstools/doc/README.mkdosfs
new file mode 100644
index 0000000..5bd9b21
--- /dev/null
+++ b/dosfstools/doc/README.mkdosfs
@@ -0,0 +1,50 @@
+mkdosfs - Make DOS filesystem utilty.
+
+
+I wrote this, partially to complement the dosfsck utility written by Werner
+Almesberger (who graciously gave me some pointers when I asked for some
+advice about writing this code), and also to avoid me having to boot DOS
+just to create data partitions (I use Linux to back up DOS :-) ).
+
+The code is really derived from Remy Card's mke2fs utility - I used this as a
+framework, although all of the filesystem specific stuff was removed and the
+DOS stuff inserted.  I believe originally mke2fs was based on Linus' mkfs
+code, hence the acknowledgements in the source code.
+
+Neither Remy nor Linus have had any involvement with mkdosfs, so if there are
+any bugs they're almost certainly "all my own work".
+
+The code has been available for ftp since 1st September 1993, and I have yet
+to receive any bug reports from users.  I don't know of any bugs, but if you
+do find a bug or have any constructive comments, please mail me!
+
+The only bug I found with version 0.1 was an obscure fault that could lead
+to an invalid (for MS-DOS, not Linux's dos fs) number of sectors used in the
+file allocation table(s).
+
+
+Dave Hudson
+dave@humbug.demon.co.uk
+
+
+FAT32 support
+=============
+
+mkdosfs now can also create filesystems in the new FAT32 format. To do
+this, give mkdosfs a "-F 32" option. FAT32 isn't selected
+automatically (yet), even if very large clusters are needed with
+FAT16. With FAT32 you have two additional options, -R to select the
+number of reserved sectors (usually 32), and -b to select the location
+of the backup boot sector (default 6). Of course such a backup is
+created, as well as the new info sector. On FAT32, the root directory
+is always created as a cluster chain. Sorry, there's no switch to
+generate an old static root dir.
+
+One bigger bug fix besides FAT32 was to reject filesystems that need a
+16 bit FAT to fit all possible clusters, but the bigger FAT needs some
+more sectors, so the total number of clusters drop below the border
+where MS-DOS expects a 12 bit FAT. So such filesystems would be FAT16,
+but interpreted as FAT32 by DOS. The fix is to reduce filesystem size
+a bit.
+
+- Roman <roman@hodek.net>
diff --git a/dosfstools/doc/TODO.dosfstools-2.x b/dosfstools/doc/TODO.dosfstools-2.x
new file mode 100644
index 0000000..dbc2de0
--- /dev/null
+++ b/dosfstools/doc/TODO.dosfstools-2.x
@@ -0,0 +1,14 @@
+                                                    -*- mode: indented-text -*-
+
+ - dosfsck: Better checking of file times: ctime <= mtime <= atime
+
+ - mkdosfs: If /etc/bootsect.dos (or similar) exists, use it as a
+   template for generating boot sectors. This way, you can, e.g., make
+   bootable DOS disks.
+
+   Addendum: Don't know if that's so wise... There are really many
+   variants of DOS/Windows bootcode out in the wild, and the code is
+   proprietary, too.
+
+ - dosfsck: read-only sector test (-t without -a or -r); just print
+   out errors.
diff --git a/dosfstools/manpages/Makefile b/dosfstools/manpages/Makefile
new file mode 100644
index 0000000..ee8a478
--- /dev/null
+++ b/dosfstools/manpages/Makefile
@@ -0,0 +1,50 @@
+# Makefile
+
+## dosfstools(7)
+## Copyright (C) 2006-2014 Daniel Baumann <mail@daniel-baumann.ch>
+##
+## This program comes with ABSOLUTELY NO WARRANTY; for details see COPYING.
+## This is free software, and you are welcome to redistribute it
+## under certain conditions; see COPYING for details.
+
+
+SHELL := sh -e
+
+LANGUAGES = $(shell cd po && ls)
+
+all: build
+
+po4a.cfg:
+	echo "[po4a_langs] $(LANGUAGES)" > po4a.cfg
+	echo "[po4a_paths] pot/\$$master.pot \$$lang:po/\$$lang/\$$master.po" >> po4a.cfg
+
+	for MANPAGE in en/*; \
+	do \
+		SECTION="$$(basename $${MANPAGE} | sed -e 's|\.|\n|g' | tail -n1)"; \
+		echo "[type: man] $${MANPAGE} \$$lang:\$$lang/$$(basename $${MANPAGE} .$${SECTION}).\$$lang.$${SECTION}" >> po4a.cfg; \
+	done
+
+update:
+	./bin/update-version.sh
+
+build: po4a.cfg
+	@if [ ! -x "$$(which po4a 2>/dev/null)" ]; \
+	then \
+		echo "E: po4a - command not found"; \
+		echo "I: po4a can be obtained from:"; \
+		echo "I:   http://po4a.alioth.debian.org/"; \
+		echo "I: On Debian based systems, po4a can be installed with:"; \
+		echo "I:   apt-get install po4a"; \
+		exit 1; \
+	fi
+
+	po4a --keep 0 --no-backups -o untranslated=MT,ME \
+		--package-name dosfstools po4a.cfg
+
+clean:
+	rm -rf $(LANGUAGES)
+
+distclean: clean
+	rm -f po4a.cfg
+
+rebuild: distclean update build
diff --git a/dosfstools/manpages/bin/update-version.sh b/dosfstools/manpages/bin/update-version.sh
new file mode 100755
index 0000000..48e9c08
--- /dev/null
+++ b/dosfstools/manpages/bin/update-version.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+## dosfstools(7)
+## Copyright (C) 2006-2014 Daniel Baumann <mail@daniel-baumann.ch>
+##
+## This program comes with ABSOLUTELY NO WARRANTY; for details see COPYING.
+## This is free software, and you are welcome to redistribute it
+## under certain conditions; see COPYING for details.
+
+
+set -e
+
+PROJECT="dosfstools"
+VERSION="$(cat ../VERSION)"
+
+DATE="$(LC_ALL=C date +%Y\\\\-%m\\\\-%d)"
+
+DAY="$(LC_ALL=C date +%d)"
+MONTH="$(LC_ALL=C date +%m)"
+YEAR="$(LC_ALL=C date +%Y)"
+
+echo "Updating version headers..."
+
+for MANPAGE in en/*
+do
+	PROGRAM="$(basename ${MANPAGE} | sed -e 's|\(.*\).[0-9]$|\1|' | tr [a-z] [A-Z])"
+	SECTION="$(basename ${MANPAGE} | sed -e 's|.*.\([0-9]\)$|\1|')"
+
+	sed -i -e "s|^.TH.*$|.TH ${PROGRAM} ${SECTION} ${DATE} ${VERSION} \"${PROJECT}\"|" ${MANPAGE}
+done
+
+# European date format
+for _LANGUAGE in de es fr it
+do
+	if ls po/${_LANGUAGE}/*.po > /dev/null 2>&1
+	then
+		for _FILE in po/${_LANGUAGE}/*.po
+		do
+			sed -i  -e "s|^msgstr .*.2014-.*$|msgstr \"${DAY}.${MONTH}.${YEAR}\"|g" \
+				-e "s|^msgstr .*.2014\"$|msgstr \"${DAY}.${MONTH}.${YEAR}\"|g" \
+			"${_FILE}"
+		done
+	fi
+done
+
+# Brazilian date format
+if ls po/pt_BR/*.po > /dev/null 2>&1
+then
+	for _FILE in po/pt_BR/*.po
+	do
+		sed -i  -e "s|^msgstr .*.2014-.*$|msgstr \"${DAY}-${MONTH}-${YEAR}\"|g" \
+			-e "s|^msgstr .*-2014\"$|msgstr \"${DAY}-${MONTH}-${YEAR}\"|g" \
+		"${_FILE}"
+	done
+fi
diff --git a/dosfstools/manpages/de/fatlabel.de.8 b/dosfstools/manpages/de/fatlabel.de.8
new file mode 100644
index 0000000..53f5515
--- /dev/null
+++ b/dosfstools/manpages/de/fatlabel.de.8
@@ -0,0 +1,68 @@
+.\" fatlabel.8 - manpage for fatlabel
+.\"
+.\" Copyright (C) 2006-2014 Daniel Baumann <daniel@debian.org>
+.\"
+.\" 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
+.\" the Free Software Foundation, either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" 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, see <http://www.gnu.org/licenses/>.
+.\"
+.\" The complete text of the GNU General Public License
+.\" can be found in /usr/share/common-licenses/GPL-3 file.
+.\"
+.\"
+.\"*******************************************************************
+.\"
+.\" This file was generated with po4a. Translate the source file.
+.\"
+.\"*******************************************************************
+.TH FATLABEL 8 2015\-05\-16 3.0.28 dosfstools
+.SH NAME
+.\" ----------------------------------------------------------------------------
+\fBfatlabel\fP \- set or get MS\-DOS filesystem label
+.SH SYNOPSIS
+.\" ----------------------------------------------------------------------------
+\fBfatlabel\fP \fIDEVICE\fP [\fILABEL\fP]
+.SH DESCRIPTION
+\fBfatlabel\fP set or gets a MS\-DOS filesystem label from a given device.
+.PP
+.\" ----------------------------------------------------------------------------
+If \fILABEL\fP is omitted, then the label name of the specified device is
+written on the standard output.  A label can't be longer than 11 bytes.
+.SH OPTIONS
+.IP "\fB\-h\fP, \fB\-\-help\fP" 4
+Displays a help message.
+.IP "\fB\-V\fP, \fB\-\-version\fP" 4
+.\" ----------------------------------------------------------------------------
+Shows version.
+.SH "SEE ALSO"
+\fBfsck.fat\fP(8)
+.br
+.\" ----------------------------------------------------------------------------
+\fBmkfs.fat\fP(8)
+.SH HOMEPAGE
+.\" ----------------------------------------------------------------------------
+The home for the \fBdosfstools\fP project is its
+.UR https://github.com/dosfstools/dosfstools
+GitHub project page
+.UE .
+.SH AUTHORS
+\fBdosfstools\fP were written by
+.MT werner.almesberger@\:lrc.di.epfl.ch
+Werner Almesberger
+.ME ,
+.MT Roman.Hodek@\:informatik.\:uni-erlangen.de
+Roman Hodek
+.ME ,
+and others.  The current maintainer is
+.MT aeb@\:debian.org
+Andreas Bombe
+.ME .
diff --git a/dosfstools/manpages/de/fsck.fat.de.8 b/dosfstools/manpages/de/fsck.fat.de.8
new file mode 100644
index 0000000..869b806
--- /dev/null
+++ b/dosfstools/manpages/de/fsck.fat.de.8
@@ -0,0 +1,189 @@
+.\" fsck.fat.8 - manpage for fsck.fat
+.\"
+.\" Copyright (C) 2006-2014 Daniel Baumann <daniel@debian.org>
+.\"
+.\" 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
+.\" the Free Software Foundation, either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" 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, see <http://www.gnu.org/licenses/>.
+.\"
+.\" The complete text of the GNU General Public License
+.\" can be found in /usr/share/common-licenses/GPL-3 file.
+.\"
+.\"
+.\"*******************************************************************
+.\"
+.\" This file was generated with po4a. Translate the source file.
+.\"
+.\"*******************************************************************
+.TH FSCK.FAT 8 2015\-05\-16 3.0.28 dosfstools
+.SH NAME
+.\" ----------------------------------------------------------------------------
+\fBfsck.fat\fP \- check and repair MS\-DOS filesystems
+.SH SYNOPSIS
+.\" ----------------------------------------------------------------------------
+\fBfsck.fat\fP [\fIOPTIONS\fP] \fIDEVICE\fP
+.SH DESCRIPTION
+\fBfsck.fat\fP verifies the consistency of MS\-DOS filesystems and optionally
+tries to repair them.
+.PP
+The following filesystem problems can be corrected (in this order):
+.IP * 4
+FAT contains invalid cluster numbers.  Cluster is changed to EOF.
+.IP * 4
+File's cluster chain contains a loop.  The loop is broken.
+.IP * 4
+Bad clusters (read errors).  The clusters are marked bad and they are
+removed from files owning them.  This check is optional.
+.IP * 4
+Directories with a large number of bad entries (probably corrupt).  The
+directory can be deleted.
+.IP * 4
+Files . and .. are non\-directories.  They can be deleted or renamed.
+.IP * 4
+Directories . and .. in root directory.  They are deleted.
+.IP * 4
+Bad filenames.  They can be renamed.
+.IP * 4
+Duplicate directory entries.  They can be deleted or renamed.
+.IP * 4
+Directories with non\-zero size field.  Size is set to zero.
+.IP * 4
+Directory . does not point to parent directory.  The start pointer is
+adjusted.
+.IP * 4
+Directory .. does not point to parent of parent directory.  The start
+pointer is adjusted.
+.IP * 4
+Start cluster number of a file is invalid.  The file is truncated.
+.IP * 4
+File contains bad or free clusters.  The file is truncated.
+.IP * 4
+File's cluster chain is longer than indicated by the size fields.  The file
+is truncated.
+.IP * 4
+Two or more files share the same cluster(s).  All but one of the files are
+truncated.  If the file being truncated is a directory file that has already
+been read, the filesystem check is restarted after truncation.
+.IP * 4
+File's cluster chain is shorter than indicated by the size fields.  The file
+is truncated.
+.IP * 4
+Clusters are marked as used but are not owned by a file.  They are marked as
+free.
+.PP
+Additionally, the following problems are detected, but not repaired:
+.IP * 4
+Invalid parameters in boot sector
+.IP * 4
+Absence of . and .. entries in non\-root directories
+.PP
+.\" ----------------------------------------------------------------------------
+When \fBfsck.fat\fP checks a filesystem, it accumulates all changes in memory
+and performs them only after all checks are complete.  This can be disabled
+with the \fB\-w\fP option.
+.SH OPTIONS
+.IP \fB\-a\fP 4
+Automatically repair the filesystem.  No user intervention is necessary.
+Whenever there is more than one method to solve a problem, the least
+destructive approach is used.
+.IP \fB\-A\fP 4
+Use Atari variation of the MS\-DOS filesystem.  This is default if
+\fBfsck.fat\fP is run on an Atari, then this option turns off Atari format.
+There are some minor differences in Atari format: Some boot sector fields
+are interpreted slightly different, and the special FAT entries for
+end\-of\-file and bad cluster can be different.  Under MS\-DOS 0xfff8 is used
+for EOF and Atari employs 0xffff by default, but both systems recognize all
+values from 0xfff8...0xffff as end\-of\-file.  MS\-DOS uses only 0xfff7 for bad
+clusters, where on Atari values 0xfff0...0xfff7 are for this purpose (but
+the standard value is still 0xfff7).
+.IP \fB\-b\fP 4
+Make read\-only boot sector check.
+.IP "\fB\-d\fP \fIPATH\fP" 4
+Delete the specified file.  If more than one file with that name exist, the
+first one is deleted.  This option can be given more than once.
+.IP \fB\-f\fP 4
+Salvage unused cluster chains to files.  By default, unused clusters are
+added to the free disk space except in auto mode (\fB\-a\fP).
+.IP \fB\-l\fP 4
+List path names of files being processed.
+.IP \fB\-n\fP 4
+No\-operation mode: non\-interactively check for errors, but don't write
+anything to the filesystem.
+.IP \fB\-p\fP 4
+Same as \fB\-a\fP, for compatibility with other *fsck.
+.IP \fB\-r\fP 4
+Interactively repair the filesystem.  The user is asked for advice whenever
+there is more than one approach to fix an inconsistency.  This is the
+default mode and the option is only retained for backwards compatibility.
+.IP \fB\-t\fP 4
+Mark unreadable clusters as bad.
+.IP "\fB\-u\fP \fIPATH\fP" 4
+Try to undelete the specified file.  \fBfsck.fat\fP tries to allocate a chain
+of contiguous unallocated clusters beginning with the start cluster of the
+undeleted file.  This option can be given more than once.
+.IP \fB\-v\fP 4
+Verbose mode.  Generates slightly more output.
+.IP \fB\-V\fP 4
+Perform a verification pass.  The filesystem check is repeated after the
+first run.  The second pass should never report any fixable errors.  It may
+take considerably longer than the first pass, because the first pass may
+have generated long list of modifications that have to be scanned for each
+disk read.
+.IP \fB\-w\fP 4
+Write changes to disk immediately.
+.IP \fB\-y\fP 4
+.\" ----------------------------------------------------------------------------
+Same as \fB\-a\fP (automatically repair filesystem) for compatibility with other
+fsck tools.
+.SH "EXIT STATUS"
+.IP 0 4
+No recoverable errors have been detected.
+.IP 1 4
+Recoverable errors have been detected or \fBfsck.fat\fP has discovered an
+internal inconsistency.
+.IP 2 4
+.\" ----------------------------------------------------------------------------
+Usage error.  \fBfsck.fat\fP did not access the filesystem.
+.SH FILES
+.IP "fsck0000.rec, fsck0001.rec, ..." 4
+.\" ----------------------------------------------------------------------------
+When recovering from a corrupted filesystem, \fBfsck.fat\fP dumps recovered
+data into files named 'fsckNNNN.rec' in the top level directory of the
+filesystem.
+.SH BUGS
+.\" ----------------------------------------------------------------------------
+Does not create . and .. files where necessary.  Does not remove entirely
+empty directories.  Should give more diagnostic messages.  Undeleting files
+should use a more sophisticated algorithm.
+.SH "SEE ALSO"
+\fBfatlabel\fP(8)
+.br
+.\" ----------------------------------------------------------------------------
+\fBmkfs.fat\fP(8)
+.SH HOMEPAGE
+.\" ----------------------------------------------------------------------------
+The home for the \fBdosfstools\fP project is its
+.UR https://github.com/dosfstools/dosfstools
+GitHub project page
+.UE .
+.SH AUTHORS
+\fBdosfstools\fP were written by
+.MT werner.almesberger@\:lrc.di.epfl.ch
+Werner Almesberger
+.ME ,
+.MT Roman.Hodek@\:informatik.\:uni-erlangen.de
+Roman Hodek
+.ME ,
+and others.  The current maintainer is
+.MT aeb@\:debian.org
+Andreas Bombe
+.ME .
diff --git a/dosfstools/manpages/de/mkfs.fat.de.8 b/dosfstools/manpages/de/mkfs.fat.de.8
new file mode 100644
index 0000000..a1b51d4
--- /dev/null
+++ b/dosfstools/manpages/de/mkfs.fat.de.8
@@ -0,0 +1,172 @@
+.\" mkfs.fat.8 - manpage for fs.fatck
+.\"
+.\" Copyright (C) 2006-2014 Daniel Baumann <daniel@debian.org>
+.\"
+.\" 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
+.\" the Free Software Foundation, either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" 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, see <http://www.gnu.org/licenses/>.
+.\"
+.\" The complete text of the GNU General Public License
+.\" can be found in /usr/share/common-licenses/GPL-3 file.
+.\"
+.\"
+.\"*******************************************************************
+.\"
+.\" This file was generated with po4a. Translate the source file.
+.\"
+.\"*******************************************************************
+.TH MKFS.FAT 8 2015\-05\-16 3.0.28 dosfstools
+.SH NAME
+.\" ----------------------------------------------------------------------------
+\fBmkfs.fat\fP \- create an MS\-DOS filesystem under Linux
+.SH SYNOPSIS
+.\" ----------------------------------------------------------------------------
+\fBmkfs.fat\fP [\fIOPTIONS\fP] \fIDEVICE\fP [\fIBLOCK\-COUNT\fP]
+.SH DESCRIPTION
+.\" ----------------------------------------------------------------------------
+\fBmkfs.fat\fP is used to create an MS\-DOS filesystem under Linux on a device
+(usually a disk partition).  \fIDEVICE\fP is the special file corresponding to
+the device (e.g. /dev/sdXX).  \fIBLOCK\-COUNT\fP is the number of blocks on the
+device.  If omitted, \fBmkfs.fat\fP automatically determines the filesystem
+size.
+.SH OPTIONS
+.IP \fB\-a\fP 4
+Normally, for any filesystem except very small ones, \fBmkfs.fat\fP will align
+all the data structures to cluster size, to make sure that as long as the
+partition is properly aligned, so will all the data structures in the
+filesystem.  This option disables alignment; this may provide a handful of
+additional clusters of storage at the expense of a significant performance
+degradation on RAIDs, flash media or large\-sector hard disks.
+.IP "\fB \-A\fP" 4
+Use Atari variation of the MS\-DOS filesystem.  This is default if
+\fBmkfs.fat\fP is run on an Atari, then this option turns off Atari format.
+There are some differences when using Atari format: If not directed
+otherwise by the user, \fBmkfs.fat\fP will always use 2 sectors per cluster,
+since GEMDOS doesn't like other values very much.  It will also obey the
+maximum number of sectors GEMDOS can handle.  Larger filesystems are managed
+by raising the logical sector size.  Under Atari format, an Atari\-compatible
+serial number for the filesystem is generated, and a 12 bit FAT is used only
+for filesystems that have one of the usual floppy sizes (720k, 1.2M, 1.44M,
+2.88M), a 16 bit FAT otherwise.  This can be overridden with the \fB\-F\fP
+option.  Some PC\-specific boot sector fields aren't written, and a boot
+message (option \fB\-m\fP) is ignored.
+.IP "\fB\-b\fP \fISECTOR\-OF\-BACKUP\fP" 4
+Selects the location of the backup boot sector for FAT32.  Default depends
+on number of reserved sectors, but usually is sector 6.  The backup must be
+within the range of reserved sectors.
+.IP \fB\-c\fP 4
+Check the device for bad blocks before creating the filesystem.
+.IP \fB\-C\fP 4
+Create the file given as \fIDEVICE\fP on the command line, and write the
+to\-be\-created filesystem to it.  This can be used to create the new
+filesystem in a file instead of on a real device, and to avoid using \fBdd\fP
+in advance to create a file of appropriate size.  With this option, the
+\fIBLOCK\-COUNT\fP must be given, because otherwise the intended size of the
+filesystem wouldn't be known.  The file created is a sparse file, which
+actually only contains the meta\-data areas (boot sector, FATs, and root
+directory).  The data portions won't be stored on the disk, but the file
+nevertheless will have the correct size.  The resulting file can be copied
+later to a floppy disk or other device, or mounted through a loop device.
+.IP "\fB\-D\fP \fIDRIVE\-NUMBER\fP" 4
+Specify the BIOS drive number to be stored in the FAT boot sector.  This
+value is usually 0x80 for hard disks and 0x00 for floppy devices or
+partitions to be used for floppy emulation.
+.IP "\fB\-f\fP \fINUMBER\-OF\-FATS\fP" 4
+Specify the number of file allocation tables in the filesystem.  The default
+is 2.
+.IP "\fB\-F\fP \fIFAT\-SIZE\fP" 4
+Specifies the type of file allocation tables used (12, 16 or 32 bit).  If
+nothing is specified, \fBmkfs.fat\fP will automatically select between 12, 16
+and 32 bit, whatever fits better for the filesystem size.
+.IP "\fB\-h\fP \fINUMBER\-OF\-HIDDEN\-SECTORS\fP" 4
+Select the number of hidden sectors in the volume.  Apparently some digital
+cameras get indigestion if you feed them a CF card without such hidden
+sectors, this option allows you to satisfy them.
+.IP "\fB\-i\fP \fIVOLUME\-ID\fP" 4
+Sets the volume ID of the newly created filesystem; \fIVOLUME\-ID\fP is a 32\-bit
+hexadecimal number (for example, 2e24ec82).  The default is a number which
+depends on the filesystem creation time.
+.IP \fB\-I\fP 4
+It is typical for fixed disk devices to be partitioned so, by default, you
+are not permitted to create a filesystem across the entire device.
+\fBmkfs.fat\fP will complain and tell you that it refuses to work.  This is
+different when using MO disks.  One doesn't always need partitions on MO
+disks.  The filesystem can go directly to the whole disk.  Under other OSes
+this is known as the 'superfloppy' format.  This switch will force
+\fBmkfs.fat\fP to work properly.
+.IP "\fB\-l\fP \fIFILENAME\fP" 4
+Read the bad blocks list from \fIFILENAME\fP.
+.IP "\fB\-m\fP \fIMESSAGE\-FILE\fP" 4
+Sets the message the user receives on attempts to boot this filesystem
+without having properly installed an operating system.  The message file
+must not exceed 418 bytes once line feeds have been converted to carriage
+return\-line feed combinations, and tabs have been expanded.  If the filename
+is a hyphen (\-), the text is taken from standard input.
+.IP "\fB\-M\fP \fIFAT\-MEDIA\-TYPE\fP" 4
+Specify the media type to be stored in the FAT boot sector.  This value is
+usually 0xF8 for hard disks and is 0xF0 or a value from 0xF9 to 0xFF for
+floppies or partitions to be used for floppy emulation.
+.IP "\fB\-n\fP \fIVOLUME\-NAME\fP" 4
+Sets the volume name (label) of the filesystem.  The volume name can be up
+to 11 characters long.  The default is no label.
+.IP "\fB\-r\fP \fIROOT\-DIR\-ENTRIES\fP" 4
+Select the number of entries available in the root directory.  The default
+is 112 or 224 for floppies and 512 for hard disks.
+.IP "\fB\-R\fP \fINUMBER\-OF\-RESERVED\-SECTORS\fP" 4
+Select the number of reserved sectors.  With FAT32 format at least 2
+reserved sectors are needed, the default is 32.  Otherwise the default is 1
+(only the boot sector).
+.IP "\fB\-s\fP \fISECTORS\-PER\-CLUSTER\fP" 4
+Specify the number of disk sectors per cluster.  Must be a power of 2,
+i.e. 1, 2, 4, 8, ... 128.
+.IP "\fB\-S\fP \fILOGICAL\-SECTOR\-SIZE\fP" 4
+Specify the number of bytes per logical sector.  Must be a power of 2 and
+greater than or equal to 512, i.e. 512, 1024, 2048, 4096, 8192, 16384, or
+32768.
+.IP \fB\-v\fP 4
+Verbose execution.
+.IP \fB\-\-invariant\fP 4
+Use constants for normally randomly generated or time based data such as
+volume ID and creation time.  Multiple runs of \fBmkfs.fat\fP on the same
+device create identical results with this option.  Its main purpose is
+testing \fBmkfs.fat\fP.
+.IP \fB\-\-help\fP 4
+.\" ----------------------------------------------------------------------------
+Display option summary and exit.
+.SH BUGS
+.\" ----------------------------------------------------------------------------
+\fBmkfs.fat\fP can not create boot\-able filesystems.  This isn't as easy as you
+might think at first glance for various reasons and has been discussed a lot
+already.  \fBmkfs.fat\fP simply will not support it ;)
+.SH "SEE ALSO"
+\fBfatlabel\fP(8)
+.br
+.\" ----------------------------------------------------------------------------
+\fBfsck.fat\fP(8)
+.SH HOMEPAGE
+.\" ----------------------------------------------------------------------------
+The home for the \fBdosfstools\fP project is its
+.UR https://github.com/dosfstools/dosfstools
+GitHub project page
+.UE .
+.SH AUTHORS
+\fBdosfstools\fP were written by
+.MT werner.almesberger@\:lrc.di.epfl.ch
+Werner Almesberger
+.ME ,
+.MT Roman.Hodek@\:informatik.\:uni-erlangen.de
+Roman Hodek
+.ME ,
+and others.  The current maintainer is
+.MT aeb@\:debian.org
+Andreas Bombe
+.ME .
diff --git a/dosfstools/manpages/en/fatlabel.8 b/dosfstools/manpages/en/fatlabel.8
new file mode 100644
index 0000000..c00a795
--- /dev/null
+++ b/dosfstools/manpages/en/fatlabel.8
@@ -0,0 +1,65 @@
+.\" fatlabel.8 - manpage for fatlabel
+.\"
+.\" Copyright (C) 2006-2014 Daniel Baumann <daniel@debian.org>
+.\"
+.\" 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
+.\" the Free Software Foundation, either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" 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, see <http://www.gnu.org/licenses/>.
+.\"
+.\" The complete text of the GNU General Public License
+.\" can be found in /usr/share/common-licenses/GPL-3 file.
+.\"
+.\"
+.TH FATLABEL 8 2015\-05\-16 3.0.28 "dosfstools"
+.SH NAME
+\fBfatlabel\fR \- set or get MS\-DOS filesystem label
+.\" ----------------------------------------------------------------------------
+.SH SYNOPSIS
+\fBfatlabel\fR \fIDEVICE\fR [\fILABEL\fR]
+.\" ----------------------------------------------------------------------------
+.SH DESCRIPTION
+\fBfatlabel\fR set or gets a MS\-DOS filesystem label from a given device.
+.PP
+If \fILABEL\fR is omitted, then the label name of the specified device is
+written on the standard output.
+A label can't be longer than 11 bytes.
+.\" ----------------------------------------------------------------------------
+.SH OPTIONS
+.IP "\fB\-h\fR, \fB\-\-help\fR" 4
+Displays a help message.
+.IP "\fB\-V\fR, \fB\-\-version\fR" 4
+Shows version.
+.\" ----------------------------------------------------------------------------
+.SH SEE ALSO
+\fBfsck.fat\fR(8)
+.br
+\fBmkfs.fat\fR(8)
+.\" ----------------------------------------------------------------------------
+.SH HOMEPAGE
+The home for the \fBdosfstools\fR project is its
+.UR https://github.com/dosfstools/dosfstools
+GitHub project page
+.UE .
+.\" ----------------------------------------------------------------------------
+.SH AUTHORS
+\fBdosfstools\fR were written by
+.MT werner.almesberger@\:lrc.di.epfl.ch
+Werner Almesberger
+.ME ,
+.MT Roman.Hodek@\:informatik.\:uni-erlangen.de
+Roman Hodek
+.ME ,
+and others.
+The current maintainer is
+.MT aeb@\:debian.org
+Andreas Bombe
+.ME .
diff --git a/dosfstools/manpages/en/fsck.fat.8 b/dosfstools/manpages/en/fsck.fat.8
new file mode 100644
index 0000000..f2d44d0
--- /dev/null
+++ b/dosfstools/manpages/en/fsck.fat.8
@@ -0,0 +1,206 @@
+.\" fsck.fat.8 - manpage for fsck.fat
+.\"
+.\" Copyright (C) 2006-2014 Daniel Baumann <daniel@debian.org>
+.\"
+.\" 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
+.\" the Free Software Foundation, either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" 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, see <http://www.gnu.org/licenses/>.
+.\"
+.\" The complete text of the GNU General Public License
+.\" can be found in /usr/share/common-licenses/GPL-3 file.
+.\"
+.\"
+.TH FSCK.FAT 8 2015\-05\-16 3.0.28 "dosfstools"
+.SH NAME
+\fBfsck.fat\fR \- check and repair MS\-DOS filesystems
+.\" ----------------------------------------------------------------------------
+.SH SYNOPSIS
+\fBfsck.fat\fR [\fIOPTIONS\fR] \fIDEVICE\fR
+.\" ----------------------------------------------------------------------------
+.SH DESCRIPTION
+\fBfsck.fat\fR verifies the consistency of MS\-DOS filesystems and optionally
+tries to repair them.
+.PP
+The following filesystem problems can be corrected (in this order):
+.IP "*" 4
+FAT contains invalid cluster numbers.
+Cluster is changed to EOF.
+.IP "*" 4
+File's cluster chain contains a loop.
+The loop is broken.
+.IP "*" 4
+Bad clusters (read errors).
+The clusters are marked bad and they are removed from files owning them.
+This check is optional.
+.IP "*" 4
+Directories with a large number of bad entries (probably corrupt).
+The directory can be deleted.
+.IP "*" 4
+Files . and .. are non\-directories.
+They can be deleted or renamed.
+.IP "*" 4
+Directories . and .. in root directory.
+They are deleted.
+.IP "*" 4
+Bad filenames.
+They can be renamed.
+.IP "*" 4
+Duplicate directory entries.
+They can be deleted or renamed.
+.IP "*" 4
+Directories with non\-zero size field.
+Size is set to zero.
+.IP "*" 4
+Directory . does not point to parent directory.
+The start pointer is adjusted.
+.IP "*" 4
+Directory .. does not point to parent of parent directory.
+The start pointer is adjusted.
+.IP "*" 4
+Start cluster number of a file is invalid.
+The file is truncated.
+.IP "*" 4
+File contains bad or free clusters.
+The file is truncated.
+.IP "*" 4
+File's cluster chain is longer than indicated by the size fields.
+The file is truncated.
+.IP "*" 4
+Two or more files share the same cluster(s).
+All but one of the files are truncated.
+If the file being truncated is a directory file that has already been read, the
+filesystem check is restarted after truncation.
+.IP "*" 4
+File's cluster chain is shorter than indicated by the size fields.
+The file is truncated.
+.IP "*" 4
+Clusters are marked as used but are not owned by a file.
+They are marked as free.
+.PP
+Additionally, the following problems are detected, but not repaired:
+.IP "*" 4
+Invalid parameters in boot sector
+.IP "*" 4
+Absence of . and .. entries in non\-root directories
+.PP
+When \fBfsck.fat\fR checks a filesystem, it accumulates all changes in memory
+and performs them only after all checks are complete.
+This can be disabled with the \fB\-w\fR option.
+.\" ----------------------------------------------------------------------------
+.SH OPTIONS
+.IP "\fB\-a\fR" 4
+Automatically repair the filesystem.
+No user intervention is necessary.
+Whenever there is more than one method to solve a problem, the least
+destructive approach is used.
+.IP "\fB\-A\fR" 4
+Use Atari variation of the MS\-DOS filesystem.
+This is default if \fBfsck.fat\fR is run on an Atari, then this option turns
+off Atari format.
+There are some minor differences in Atari format:
+Some boot sector fields are interpreted slightly different, and the special FAT
+entries for end\-of\-file and bad cluster can be different.
+Under MS\-DOS 0xfff8 is used for EOF and Atari employs 0xffff by default, but
+both systems recognize all values from 0xfff8...0xffff as end\-of\-file.
+MS\-DOS uses only 0xfff7 for bad clusters, where on Atari values 0xfff0...0xfff7
+are for this purpose (but the standard value is still 0xfff7).
+.IP "\fB-b\fR" 4
+Make read-only boot sector check.
+.IP "\fB\-d\fR \fIPATH\fR" 4
+Delete the specified file.
+If more than one file with that name exist, the first one is deleted.
+This option can be given more than once.
+.IP "\fB\-f\fR" 4
+Salvage unused cluster chains to files.
+By default, unused clusters are added to the free disk space except in auto mode
+(\fB\-a\fR).
+.IP "\fB\-l\fR" 4
+List path names of files being processed.
+.IP "\fB\-n\fR" 4
+No\-operation mode: non\-interactively check for errors, but don't write
+anything to the filesystem.
+.IP "\fB\-p\fR" 4
+Same as \fB\-a\fR, for compatibility with other *fsck.
+.IP "\fB\-r\fR" 4
+Interactively repair the filesystem.
+The user is asked for advice whenever there is more than one approach to fix an
+inconsistency.
+This is the default mode and the option is only retained for backwards
+compatibility.
+.IP "\fB\-t\fR" 4
+Mark unreadable clusters as bad.
+.IP "\fB\-u\fR \fIPATH\fR" 4
+Try to undelete the specified file.
+\fBfsck.fat\fR tries to allocate a chain of contiguous unallocated clusters
+beginning with the start cluster of the undeleted file.
+This option can be given more than once.
+.IP "\fB\-v\fR" 4
+Verbose mode.
+Generates slightly more output.
+.IP "\fB\-V\fR" 4
+Perform a verification pass.
+The filesystem check is repeated after the first run.
+The second pass should never report any fixable errors.
+It may take considerably longer than the first pass, because the first pass may
+have generated long list of modifications that have to be scanned for each disk
+read.
+.IP "\fB\-w\fR" 4
+Write changes to disk immediately.
+.IP "\fB\-y\fR" 4
+Same as \fB\-a\fR (automatically repair filesystem) for compatibility with other
+fsck tools.
+.\" ----------------------------------------------------------------------------
+.SH "EXIT STATUS"
+.IP "0" 4
+No recoverable errors have been detected.
+.IP "1" 4
+Recoverable errors have been detected or \fBfsck.fat\fR has discovered an
+internal inconsistency.
+.IP "2" 4
+Usage error.
+\fBfsck.fat\fR did not access the filesystem.
+.\" ----------------------------------------------------------------------------
+.SH FILES
+.IP "fsck0000.rec, fsck0001.rec, ..." 4
+When recovering from a corrupted filesystem, \fBfsck.fat\fR dumps recovered data
+into files named 'fsckNNNN.rec' in the top level directory of the filesystem.
+.\" ----------------------------------------------------------------------------
+.SH BUGS
+Does not create . and .. files where necessary.
+Does not remove entirely empty directories.
+Should give more diagnostic messages.
+Undeleting files should use a more sophisticated algorithm.
+.\" ----------------------------------------------------------------------------
+.SH SEE ALSO
+\fBfatlabel\fR(8)
+.br
+\fBmkfs.fat\fR(8)
+.\" ----------------------------------------------------------------------------
+.SH HOMEPAGE
+The home for the \fBdosfstools\fR project is its
+.UR https://github.com/dosfstools/dosfstools
+GitHub project page
+.UE .
+.\" ----------------------------------------------------------------------------
+.SH AUTHORS
+\fBdosfstools\fR were written by
+.MT werner.almesberger@\:lrc.di.epfl.ch
+Werner Almesberger
+.ME ,
+.MT Roman.Hodek@\:informatik.\:uni-erlangen.de
+Roman Hodek
+.ME ,
+and others.
+The current maintainer is
+.MT aeb@\:debian.org
+Andreas Bombe
+.ME .
diff --git a/dosfstools/manpages/en/mkfs.fat.8 b/dosfstools/manpages/en/mkfs.fat.8
new file mode 100644
index 0000000..5a5086e
--- /dev/null
+++ b/dosfstools/manpages/en/mkfs.fat.8
@@ -0,0 +1,178 @@
+.\" mkfs.fat.8 - manpage for fs.fatck
+.\"
+.\" Copyright (C) 2006-2014 Daniel Baumann <daniel@debian.org>
+.\"
+.\" 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
+.\" the Free Software Foundation, either version 3 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful,
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
+.\" 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, see <http://www.gnu.org/licenses/>.
+.\"
+.\" The complete text of the GNU General Public License
+.\" can be found in /usr/share/common-licenses/GPL-3 file.
+.\"
+.\"
+.TH MKFS.FAT 8 2015\-05\-16 3.0.28 "dosfstools"
+.SH NAME
+\fBmkfs.fat\fR \- create an MS-DOS filesystem under Linux
+.\" ----------------------------------------------------------------------------
+.SH SYNOPSIS
+\fBmkfs.fat\fR [\fIOPTIONS\fR] \fIDEVICE\fR [\fIBLOCK-COUNT\fR]
+.\" ----------------------------------------------------------------------------
+.SH DESCRIPTION
+\fBmkfs.fat\fR is used to create an MS-DOS filesystem under Linux on a device
+(usually a disk partition).
+\fIDEVICE\fR is the special file corresponding to the device (e.g. /dev/sdXX).
+\fIBLOCK-COUNT\fR is the number of blocks on the device.
+If omitted, \fBmkfs.fat\fR automatically determines the filesystem size.
+.\" ----------------------------------------------------------------------------
+.SH OPTIONS
+.IP "\fB\-a\fR" 4
+Normally, for any filesystem except very small ones, \fBmkfs.fat\fR will align
+all the data structures to cluster size, to make sure that as long as the
+partition is properly aligned, so will all the data structures in the
+filesystem.
+This option disables alignment; this may provide a handful of additional
+clusters of storage at the expense of a significant performance degradation on
+RAIDs, flash media or large-sector hard disks.
+.IP "\fB \-A\fR" 4
+Use Atari variation of the MS-DOS filesystem.
+This is default if \fBmkfs.fat\fR is run on an Atari, then this option turns off
+Atari format.
+There are some differences when using Atari format:
+If not directed otherwise by the user, \fBmkfs.fat\fR will always use 2 sectors
+per cluster, since GEMDOS doesn't like other values very much.
+It will also obey the maximum number of sectors GEMDOS can handle.
+Larger filesystems are managed by raising the logical sector size.
+Under Atari format, an Atari-compatible serial number for the filesystem is
+generated, and a 12 bit FAT is used only for filesystems that have one of the
+usual floppy sizes (720k, 1.2M, 1.44M, 2.88M), a 16 bit FAT otherwise.
+This can be overridden with the \fB\-F\fR option.
+Some PC-specific boot sector fields aren't written, and a boot message (option
+\fB\-m\fR) is ignored.
+.IP "\fB\-b\fR \fISECTOR-OF-BACKUP\fR" 4
+Selects the location of the backup boot sector for FAT32.
+Default depends on number of reserved sectors, but usually is sector 6.
+The backup must be within the range of reserved sectors.
+.IP "\fB\-c" 4
+Check the device for bad blocks before creating the filesystem.
+.IP "\fB\-C\fR" 4
+Create the file given as \fIDEVICE\fR on the command line, and write the
+to-be-created filesystem to it.
+This can be used to create the new filesystem in a file instead of on a real
+device, and to avoid using \fBdd\fR in advance to create a file of appropriate
+size.
+With this option, the \fIBLOCK-COUNT\fR must be given, because otherwise the
+intended size of the filesystem wouldn't be known.
+The file created is a sparse file, which actually only contains the meta-data
+areas (boot sector, FATs, and root directory).
+The data portions won't be stored on the disk, but the file nevertheless will
+have the correct size.
+The resulting file can be copied later to a floppy disk or other device, or
+mounted through a loop device.
+.IP "\fB\-D\fR \fIDRIVE-NUMBER\fR" 4
+Specify the BIOS drive number to be stored in the FAT boot sector.
+This value is usually 0x80 for hard disks and 0x00 for floppy devices or
+partitions to be used for floppy emulation.
+.IP "\fB\-f\fR \fINUMBER-OF-FATS\fR" 4
+Specify the number of file allocation tables in the filesystem.
+The default is 2.
+.IP "\fB\-F\fR \fIFAT-SIZE\fR" 4
+Specifies the type of file allocation tables used (12, 16 or 32 bit).
+If nothing is specified, \fBmkfs.fat\fR will automatically select between 12, 16
+and 32 bit, whatever fits better for the filesystem size.
+.IP "\fB\-h\fR \fINUMBER-OF-HIDDEN-SECTORS\fR" 4
+Select the number of hidden sectors in the volume.
+Apparently some digital cameras get indigestion if you feed them a CF card
+without such hidden sectors, this option allows you to satisfy them.
+.IP "\fB\-i\fR \fIVOLUME-ID\fR" 4
+Sets the volume ID of the newly created filesystem; \fIVOLUME-ID\fR is a 32-bit
+hexadecimal number (for example, 2e24ec82).
+The default is a number which depends on the filesystem creation time.
+.IP "\fB\-I\fR" 4
+It is typical for fixed disk devices to be partitioned so, by default, you are
+not permitted to create a filesystem across the entire device.
+\fBmkfs.fat\fR will complain and tell you that it refuses to work.
+This is different when using MO disks.
+One doesn't always need partitions on MO disks.
+The filesystem can go directly to the whole disk.
+Under other OSes this is known as the 'superfloppy' format.
+This switch will force \fBmkfs.fat\fR to work properly.
+.IP "\fB\-l\fR \fIFILENAME\fR" 4
+Read the bad blocks list from \fIFILENAME\fR.
+.IP "\fB\-m\fR \fIMESSAGE-FILE\fR" 4
+Sets the message the user receives on attempts to boot this filesystem without
+having properly installed an operating system.
+The message file must not exceed 418 bytes once line feeds have been converted
+to carriage return-line feed combinations, and tabs have been expanded.
+If the filename is a hyphen (-), the text is taken from standard input.
+.IP "\fB\-M\fR \fIFAT-MEDIA-TYPE\fR" 4
+Specify the media type to be stored in the FAT boot sector.
+This value is usually 0xF8 for hard disks and is 0xF0 or a value from 0xF9 to
+0xFF for floppies or partitions to be used for floppy emulation.
+.IP "\fB\-n\fR \fIVOLUME-NAME\fR" 4
+Sets the volume name (label) of the filesystem.
+The volume name can be up to 11 characters long.
+The default is no label.
+.IP "\fB\-r\fR \fIROOT-DIR-ENTRIES\fR" 4
+Select the number of entries available in the root directory.
+The default is 112 or 224 for floppies and 512 for hard disks.
+.IP "\fB\-R\fR \fINUMBER-OF-RESERVED-SECTORS\fR" 4
+Select the number of reserved sectors.
+With FAT32 format at least 2 reserved sectors are needed, the default is 32.
+Otherwise the default is 1 (only the boot sector).
+.IP "\fB\-s\fR \fISECTORS-PER-CLUSTER\fR" 4
+Specify the number of disk sectors per cluster.
+Must be a power of 2, i.e. 1, 2, 4, 8, ... 128.
+.IP "\fB\-S\fR \fILOGICAL-SECTOR-SIZE\fR" 4
+Specify the number of bytes per logical sector.
+Must be a power of 2 and greater than or equal to 512, i.e. 512, 1024, 2048,
+4096, 8192, 16384, or 32768.
+.IP "\fB\-v\fR" 4
+Verbose execution.
+.IP "\fB\-\-invariant\fR" 4
+Use constants for normally randomly generated or time based data such as
+volume ID and creation time.
+Multiple runs of \fBmkfs.fat\fR on the same device create identical results
+with this option.
+Its main purpose is testing \fBmkfs.fat\fR.
+.IP "\fB\-\-help\fR" 4
+Display option summary and exit.
+.\" ----------------------------------------------------------------------------
+.SH BUGS
+\fBmkfs.fat\fR can not create boot-able filesystems.
+This isn't as easy as you might think at first glance for various reasons and
+has been discussed a lot already.
+\fBmkfs.fat\fR simply will not support it ;)
+.\" ----------------------------------------------------------------------------
+.SH SEE ALSO
+\fBfatlabel\fR(8)
+.br
+\fBfsck.fat\fR(8)
+.\" ----------------------------------------------------------------------------
+.SH HOMEPAGE
+The home for the \fBdosfstools\fR project is its
+.UR https://github.com/dosfstools/dosfstools
+GitHub project page
+.UE .
+.\" ----------------------------------------------------------------------------
+.SH AUTHORS
+\fBdosfstools\fR were written by
+.MT werner.almesberger@\:lrc.di.epfl.ch
+Werner Almesberger
+.ME ,
+.MT Roman.Hodek@\:informatik.\:uni-erlangen.de
+Roman Hodek
+.ME ,
+and others.
+The current maintainer is
+.MT aeb@\:debian.org
+Andreas Bombe
+.ME .
diff --git a/dosfstools/manpages/po/de/fatlabel.8.po b/dosfstools/manpages/po/de/fatlabel.8.po
new file mode 100644
index 0000000..a2b6da2
--- /dev/null
+++ b/dosfstools/manpages/po/de/fatlabel.8.po
@@ -0,0 +1,173 @@
+# German translations for dosfstools package
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: dosfstools VERSION\n"
+"POT-Creation-Date: 2015-05-16 00:40+0200\n"
+"PO-Revision-Date: 2013-06-06 09:34+0300\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ASCII\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. type: TH
+#: en/fatlabel.8:22
+#, no-wrap
+msgid "FATLABEL"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "2015-05-16"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "3.0.28"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "dosfstools"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:23 en/fsck.fat.8:23 en/mkfs.fat.8:23
+#, no-wrap
+msgid "NAME"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:26
+msgid "B<fatlabel> - set or get MS-DOS filesystem label"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:26 en/fsck.fat.8:26 en/mkfs.fat.8:26
+#, no-wrap
+msgid "SYNOPSIS"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:29
+msgid "B<fatlabel> I<DEVICE> [I<LABEL>]"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:29 en/fsck.fat.8:29 en/mkfs.fat.8:29
+#, no-wrap
+msgid "DESCRIPTION"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:31
+msgid "B<fatlabel> set or gets a MS-DOS filesystem label from a given device."
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:36
+msgid ""
+"If I<LABEL> is omitted, then the label name of the specified device is "
+"written on the standard output.  A label can't be longer than 11 bytes."
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:36 en/fsck.fat.8:99 en/mkfs.fat.8:36
+#, no-wrap
+msgid "OPTIONS"
+msgstr ""
+
+#. type: IP
+#: en/fatlabel.8:37
+#, no-wrap
+msgid "B<-h>, B<--help>"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:39
+msgid "Displays a help message."
+msgstr ""
+
+#. type: IP
+#: en/fatlabel.8:39
+#, no-wrap
+msgid "B<-V>, B<--version>"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:42
+msgid "Shows version."
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:42 en/fsck.fat.8:183 en/mkfs.fat.8:155
+#, no-wrap
+msgid "SEE ALSO"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:44 en/mkfs.fat.8:160
+msgid "B<fsck.fat>(8)"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:47 en/fsck.fat.8:188
+msgid "B<mkfs.fat>(8)"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:47 en/fsck.fat.8:188 en/mkfs.fat.8:160
+#, no-wrap
+msgid "HOMEPAGE"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+msgid ""
+"The home for the B<dosfstools> project is its E<.UR https://github.com/"
+"dosfstools/dosfstools> GitHub project page E<.UE .>"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+#, no-wrap
+msgid "AUTHORS"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:55 en/fsck.fat.8:196 en/mkfs.fat.8:168
+msgid "B<dosfstools> were written by"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:57 en/fsck.fat.8:198 en/mkfs.fat.8:170
+msgid "Werner Almesberger"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:60 en/fsck.fat.8:201 en/mkfs.fat.8:173
+msgid "Roman Hodek"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:63 en/fsck.fat.8:204 en/mkfs.fat.8:176
+msgid "and others.  The current maintainer is"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:65 en/fsck.fat.8:206 en/mkfs.fat.8:178
+msgid "Andreas Bombe"
+msgstr ""
diff --git a/dosfstools/manpages/po/de/fsck.fat.8.po b/dosfstools/manpages/po/de/fsck.fat.8.po
new file mode 100644
index 0000000..1487e9f
--- /dev/null
+++ b/dosfstools/manpages/po/de/fsck.fat.8.po
@@ -0,0 +1,559 @@
+# German translations for dosfstools package
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: dosfstools VERSION\n"
+"POT-Creation-Date: 2015-05-16 00:40+0200\n"
+"PO-Revision-Date: 2013-06-06 09:34+0300\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ASCII\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "2015-05-16"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "3.0.28"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "dosfstools"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:23 en/fsck.fat.8:23 en/mkfs.fat.8:23
+#, no-wrap
+msgid "NAME"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:26 en/fsck.fat.8:26 en/mkfs.fat.8:26
+#, no-wrap
+msgid "SYNOPSIS"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:29 en/fsck.fat.8:29 en/mkfs.fat.8:29
+#, no-wrap
+msgid "DESCRIPTION"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:36 en/fsck.fat.8:99 en/mkfs.fat.8:36
+#, no-wrap
+msgid "OPTIONS"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:42 en/fsck.fat.8:183 en/mkfs.fat.8:155
+#, no-wrap
+msgid "SEE ALSO"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:47 en/fsck.fat.8:188
+msgid "B<mkfs.fat>(8)"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:47 en/fsck.fat.8:188 en/mkfs.fat.8:160
+#, no-wrap
+msgid "HOMEPAGE"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+msgid ""
+"The home for the B<dosfstools> project is its E<.UR https://github.com/"
+"dosfstools/dosfstools> GitHub project page E<.UE .>"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+#, no-wrap
+msgid "AUTHORS"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:55 en/fsck.fat.8:196 en/mkfs.fat.8:168
+msgid "B<dosfstools> were written by"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:57 en/fsck.fat.8:198 en/mkfs.fat.8:170
+msgid "Werner Almesberger"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:60 en/fsck.fat.8:201 en/mkfs.fat.8:173
+msgid "Roman Hodek"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:63 en/fsck.fat.8:204 en/mkfs.fat.8:176
+msgid "and others.  The current maintainer is"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:65 en/fsck.fat.8:206 en/mkfs.fat.8:178
+msgid "Andreas Bombe"
+msgstr ""
+
+#. type: TH
+#: en/fsck.fat.8:22
+#, no-wrap
+msgid "FSCK.FAT"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:26
+msgid "B<fsck.fat> - check and repair MS-DOS filesystems"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:29
+msgid "B<fsck.fat> [I<OPTIONS>] I<DEVICE>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:32
+msgid ""
+"B<fsck.fat> verifies the consistency of MS-DOS filesystems and optionally "
+"tries to repair them."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:34
+msgid "The following filesystem problems can be corrected (in this order):"
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:34 en/fsck.fat.8:37 en/fsck.fat.8:40 en/fsck.fat.8:44
+#: en/fsck.fat.8:47 en/fsck.fat.8:50 en/fsck.fat.8:53 en/fsck.fat.8:56
+#: en/fsck.fat.8:59 en/fsck.fat.8:62 en/fsck.fat.8:65 en/fsck.fat.8:68
+#: en/fsck.fat.8:71 en/fsck.fat.8:74 en/fsck.fat.8:77 en/fsck.fat.8:82
+#: en/fsck.fat.8:85 en/fsck.fat.8:90 en/fsck.fat.8:92
+#, no-wrap
+msgid "*"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:37
+msgid "FAT contains invalid cluster numbers.  Cluster is changed to EOF."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:40
+msgid "File's cluster chain contains a loop.  The loop is broken."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:44
+msgid ""
+"Bad clusters (read errors).  The clusters are marked bad and they are "
+"removed from files owning them.  This check is optional."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:47
+msgid ""
+"Directories with a large number of bad entries (probably corrupt).  The "
+"directory can be deleted."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:50
+msgid "Files . and .. are non-directories.  They can be deleted or renamed."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:53
+msgid "Directories . and .. in root directory.  They are deleted."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:56
+msgid "Bad filenames.  They can be renamed."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:59
+msgid "Duplicate directory entries.  They can be deleted or renamed."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:62
+msgid "Directories with non-zero size field.  Size is set to zero."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:65
+msgid ""
+"Directory . does not point to parent directory.  The start pointer is "
+"adjusted."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:68
+msgid ""
+"Directory .. does not point to parent of parent directory.  The start "
+"pointer is adjusted."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:71
+msgid "Start cluster number of a file is invalid.  The file is truncated."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:74
+msgid "File contains bad or free clusters.  The file is truncated."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:77
+msgid ""
+"File's cluster chain is longer than indicated by the size fields.  The file "
+"is truncated."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:82
+msgid ""
+"Two or more files share the same cluster(s).  All but one of the files are "
+"truncated.  If the file being truncated is a directory file that has already "
+"been read, the filesystem check is restarted after truncation."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:85
+msgid ""
+"File's cluster chain is shorter than indicated by the size fields.  The file "
+"is truncated."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:88
+msgid ""
+"Clusters are marked as used but are not owned by a file.  They are marked as "
+"free."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:90
+msgid "Additionally, the following problems are detected, but not repaired:"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:92
+msgid "Invalid parameters in boot sector"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:94
+msgid "Absence of . and .. entries in non-root directories"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:99
+msgid ""
+"When B<fsck.fat> checks a filesystem, it accumulates all changes in memory "
+"and performs them only after all checks are complete.  This can be disabled "
+"with the B<-w> option."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:100 en/mkfs.fat.8:37
+#, no-wrap
+msgid "B<-a>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:105
+msgid ""
+"Automatically repair the filesystem.  No user intervention is necessary.  "
+"Whenever there is more than one method to solve a problem, the least "
+"destructive approach is used."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:105
+#, no-wrap
+msgid "B<-A>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:116
+msgid ""
+"Use Atari variation of the MS-DOS filesystem.  This is default if B<fsck."
+"fat> is run on an Atari, then this option turns off Atari format.  There are "
+"some minor differences in Atari format: Some boot sector fields are "
+"interpreted slightly different, and the special FAT entries for end-of-file "
+"and bad cluster can be different.  Under MS-DOS 0xfff8 is used for EOF and "
+"Atari employs 0xffff by default, but both systems recognize all values from "
+"0xfff8...0xffff as end-of-file.  MS-DOS uses only 0xfff7 for bad clusters, "
+"where on Atari values 0xfff0...0xfff7 are for this purpose (but the standard "
+"value is still 0xfff7)."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:116
+#, no-wrap
+msgid "B<-b>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:118
+msgid "Make read-only boot sector check."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:118
+#, no-wrap
+msgid "B<-d> I<PATH>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:122
+msgid ""
+"Delete the specified file.  If more than one file with that name exist, the "
+"first one is deleted.  This option can be given more than once."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:122
+#, no-wrap
+msgid "B<-f>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:126
+msgid ""
+"Salvage unused cluster chains to files.  By default, unused clusters are "
+"added to the free disk space except in auto mode (B<-a>)."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:126
+#, no-wrap
+msgid "B<-l>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:128
+msgid "List path names of files being processed."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:128
+#, no-wrap
+msgid "B<-n>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:131
+msgid ""
+"No-operation mode: non-interactively check for errors, but don't write "
+"anything to the filesystem."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:131
+#, no-wrap
+msgid "B<-p>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:133
+msgid "Same as B<-a>, for compatibility with other *fsck."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:133
+#, no-wrap
+msgid "B<-r>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:139
+msgid ""
+"Interactively repair the filesystem.  The user is asked for advice whenever "
+"there is more than one approach to fix an inconsistency.  This is the "
+"default mode and the option is only retained for backwards compatibility."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:139
+#, no-wrap
+msgid "B<-t>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:141
+msgid "Mark unreadable clusters as bad."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:141
+#, no-wrap
+msgid "B<-u> I<PATH>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:146
+msgid ""
+"Try to undelete the specified file.  B<fsck.fat> tries to allocate a chain "
+"of contiguous unallocated clusters beginning with the start cluster of the "
+"undeleted file.  This option can be given more than once."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:146 en/mkfs.fat.8:138
+#, no-wrap
+msgid "B<-v>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:149
+msgid "Verbose mode.  Generates slightly more output."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:149
+#, no-wrap
+msgid "B<-V>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:156
+msgid ""
+"Perform a verification pass.  The filesystem check is repeated after the "
+"first run.  The second pass should never report any fixable errors.  It may "
+"take considerably longer than the first pass, because the first pass may "
+"have generated long list of modifications that have to be scanned for each "
+"disk read."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:156
+#, no-wrap
+msgid "B<-w>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:158
+msgid "Write changes to disk immediately."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:158
+#, no-wrap
+msgid "B<-y>"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:162
+msgid ""
+"Same as B<-a> (automatically repair filesystem) for compatibility with other "
+"fsck tools."
+msgstr ""
+
+#. type: SH
+#: en/fsck.fat.8:162
+#, no-wrap
+msgid "EXIT STATUS"
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:163
+#, no-wrap
+msgid "0"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:165
+msgid "No recoverable errors have been detected."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:165
+#, no-wrap
+msgid "1"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:168
+msgid ""
+"Recoverable errors have been detected or B<fsck.fat> has discovered an "
+"internal inconsistency."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:168
+#, no-wrap
+msgid "2"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:172
+msgid "Usage error.  B<fsck.fat> did not access the filesystem."
+msgstr ""
+
+#. type: SH
+#: en/fsck.fat.8:172
+#, no-wrap
+msgid "FILES"
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:173
+#, no-wrap
+msgid "fsck0000.rec, fsck0001.rec, ..."
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:177
+msgid ""
+"When recovering from a corrupted filesystem, B<fsck.fat> dumps recovered "
+"data into files named 'fsckNNNN.rec' in the top level directory of the "
+"filesystem."
+msgstr ""
+
+#. type: SH
+#: en/fsck.fat.8:177 en/mkfs.fat.8:149
+#, no-wrap
+msgid "BUGS"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:183
+msgid ""
+"Does not create . and .. files where necessary.  Does not remove entirely "
+"empty directories.  Should give more diagnostic messages.  Undeleting files "
+"should use a more sophisticated algorithm."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:185 en/mkfs.fat.8:157
+msgid "B<fatlabel>(8)"
+msgstr ""
diff --git a/dosfstools/manpages/po/de/mkfs.fat.8.po b/dosfstools/manpages/po/de/mkfs.fat.8.po
new file mode 100644
index 0000000..3344f71
--- /dev/null
+++ b/dosfstools/manpages/po/de/mkfs.fat.8.po
@@ -0,0 +1,481 @@
+# German translations for dosfstools package
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: dosfstools VERSION\n"
+"POT-Creation-Date: 2015-05-16 00:40+0200\n"
+"PO-Revision-Date: 2013-06-06 09:34+0300\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ASCII\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "2015-05-16"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "3.0.28"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "dosfstools"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:23 en/fsck.fat.8:23 en/mkfs.fat.8:23
+#, no-wrap
+msgid "NAME"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:26 en/fsck.fat.8:26 en/mkfs.fat.8:26
+#, no-wrap
+msgid "SYNOPSIS"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:29 en/fsck.fat.8:29 en/mkfs.fat.8:29
+#, no-wrap
+msgid "DESCRIPTION"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:36 en/fsck.fat.8:99 en/mkfs.fat.8:36
+#, no-wrap
+msgid "OPTIONS"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:42 en/fsck.fat.8:183 en/mkfs.fat.8:155
+#, no-wrap
+msgid "SEE ALSO"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:44 en/mkfs.fat.8:160
+msgid "B<fsck.fat>(8)"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:47 en/fsck.fat.8:188 en/mkfs.fat.8:160
+#, no-wrap
+msgid "HOMEPAGE"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+msgid ""
+"The home for the B<dosfstools> project is its E<.UR https://github.com/"
+"dosfstools/dosfstools> GitHub project page E<.UE .>"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+#, no-wrap
+msgid "AUTHORS"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:55 en/fsck.fat.8:196 en/mkfs.fat.8:168
+msgid "B<dosfstools> were written by"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:57 en/fsck.fat.8:198 en/mkfs.fat.8:170
+msgid "Werner Almesberger"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:60 en/fsck.fat.8:201 en/mkfs.fat.8:173
+msgid "Roman Hodek"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:63 en/fsck.fat.8:204 en/mkfs.fat.8:176
+msgid "and others.  The current maintainer is"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:65 en/fsck.fat.8:206 en/mkfs.fat.8:178
+msgid "Andreas Bombe"
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:100 en/mkfs.fat.8:37
+#, no-wrap
+msgid "B<-a>"
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:146 en/mkfs.fat.8:138
+#, no-wrap
+msgid "B<-v>"
+msgstr ""
+
+#. type: SH
+#: en/fsck.fat.8:177 en/mkfs.fat.8:149
+#, no-wrap
+msgid "BUGS"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:185 en/mkfs.fat.8:157
+msgid "B<fatlabel>(8)"
+msgstr ""
+
+#. type: TH
+#: en/mkfs.fat.8:22
+#, no-wrap
+msgid "MKFS.FAT"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/mkfs.fat.8:26
+msgid "B<mkfs.fat> - create an MS-DOS filesystem under Linux"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/mkfs.fat.8:29
+msgid "B<mkfs.fat> [I<OPTIONS>] I<DEVICE> [I<BLOCK-COUNT>]"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/mkfs.fat.8:36
+msgid ""
+"B<mkfs.fat> is used to create an MS-DOS filesystem under Linux on a device "
+"(usually a disk partition).  I<DEVICE> is the special file corresponding to "
+"the device (e.g. /dev/sdXX).  I<BLOCK-COUNT> is the number of blocks on the "
+"device.  If omitted, B<mkfs.fat> automatically determines the filesystem "
+"size."
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:45
+msgid ""
+"Normally, for any filesystem except very small ones, B<mkfs.fat> will align "
+"all the data structures to cluster size, to make sure that as long as the "
+"partition is properly aligned, so will all the data structures in the "
+"filesystem.  This option disables alignment; this may provide a handful of "
+"additional clusters of storage at the expense of a significant performance "
+"degradation on RAIDs, flash media or large-sector hard disks."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:45
+#, no-wrap
+msgid "B< -A>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:60
+msgid ""
+"Use Atari variation of the MS-DOS filesystem.  This is default if B<mkfs."
+"fat> is run on an Atari, then this option turns off Atari format.  There are "
+"some differences when using Atari format: If not directed otherwise by the "
+"user, B<mkfs.fat> will always use 2 sectors per cluster, since GEMDOS "
+"doesn't like other values very much.  It will also obey the maximum number "
+"of sectors GEMDOS can handle.  Larger filesystems are managed by raising the "
+"logical sector size.  Under Atari format, an Atari-compatible serial number "
+"for the filesystem is generated, and a 12 bit FAT is used only for "
+"filesystems that have one of the usual floppy sizes (720k, 1.2M, 1.44M, "
+"2.88M), a 16 bit FAT otherwise.  This can be overridden with the B<-F> "
+"option.  Some PC-specific boot sector fields aren't written, and a boot "
+"message (option B<-m>) is ignored."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:60
+#, no-wrap
+msgid "B<-b> I<SECTOR-OF-BACKUP>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:64
+msgid ""
+"Selects the location of the backup boot sector for FAT32.  Default depends "
+"on number of reserved sectors, but usually is sector 6.  The backup must be "
+"within the range of reserved sectors."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:64
+#, no-wrap
+msgid "B<-c>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:66
+msgid "Check the device for bad blocks before creating the filesystem."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:66
+#, no-wrap
+msgid "B<-C>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:80
+msgid ""
+"Create the file given as I<DEVICE> on the command line, and write the to-be-"
+"created filesystem to it.  This can be used to create the new filesystem in "
+"a file instead of on a real device, and to avoid using B<dd> in advance to "
+"create a file of appropriate size.  With this option, the I<BLOCK-COUNT> "
+"must be given, because otherwise the intended size of the filesystem "
+"wouldn't be known.  The file created is a sparse file, which actually only "
+"contains the meta-data areas (boot sector, FATs, and root directory).  The "
+"data portions won't be stored on the disk, but the file nevertheless will "
+"have the correct size.  The resulting file can be copied later to a floppy "
+"disk or other device, or mounted through a loop device."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:80
+#, no-wrap
+msgid "B<-D> I<DRIVE-NUMBER>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:84
+msgid ""
+"Specify the BIOS drive number to be stored in the FAT boot sector.  This "
+"value is usually 0x80 for hard disks and 0x00 for floppy devices or "
+"partitions to be used for floppy emulation."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:84
+#, no-wrap
+msgid "B<-f> I<NUMBER-OF-FATS>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:87
+msgid ""
+"Specify the number of file allocation tables in the filesystem.  The default "
+"is 2."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:87
+#, no-wrap
+msgid "B<-F> I<FAT-SIZE>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:91
+msgid ""
+"Specifies the type of file allocation tables used (12, 16 or 32 bit).  If "
+"nothing is specified, B<mkfs.fat> will automatically select between 12, 16 "
+"and 32 bit, whatever fits better for the filesystem size."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:91
+#, no-wrap
+msgid "B<-h> I<NUMBER-OF-HIDDEN-SECTORS>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:95
+msgid ""
+"Select the number of hidden sectors in the volume.  Apparently some digital "
+"cameras get indigestion if you feed them a CF card without such hidden "
+"sectors, this option allows you to satisfy them."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:95
+#, no-wrap
+msgid "B<-i> I<VOLUME-ID>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:99
+msgid ""
+"Sets the volume ID of the newly created filesystem; I<VOLUME-ID> is a 32-bit "
+"hexadecimal number (for example, 2e24ec82).  The default is a number which "
+"depends on the filesystem creation time."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:99
+#, no-wrap
+msgid "B<-I>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:108
+msgid ""
+"It is typical for fixed disk devices to be partitioned so, by default, you "
+"are not permitted to create a filesystem across the entire device.  B<mkfs."
+"fat> will complain and tell you that it refuses to work.  This is different "
+"when using MO disks.  One doesn't always need partitions on MO disks.  The "
+"filesystem can go directly to the whole disk.  Under other OSes this is "
+"known as the 'superfloppy' format.  This switch will force B<mkfs.fat> to "
+"work properly."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:108
+#, no-wrap
+msgid "B<-l> I<FILENAME>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:110
+msgid "Read the bad blocks list from I<FILENAME>."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:110
+#, no-wrap
+msgid "B<-m> I<MESSAGE-FILE>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:116
+msgid ""
+"Sets the message the user receives on attempts to boot this filesystem "
+"without having properly installed an operating system.  The message file "
+"must not exceed 418 bytes once line feeds have been converted to carriage "
+"return-line feed combinations, and tabs have been expanded.  If the filename "
+"is a hyphen (-), the text is taken from standard input."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:116
+#, no-wrap
+msgid "B<-M> I<FAT-MEDIA-TYPE>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:120
+msgid ""
+"Specify the media type to be stored in the FAT boot sector.  This value is "
+"usually 0xF8 for hard disks and is 0xF0 or a value from 0xF9 to 0xFF for "
+"floppies or partitions to be used for floppy emulation."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:120
+#, no-wrap
+msgid "B<-n> I<VOLUME-NAME>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:124
+msgid ""
+"Sets the volume name (label) of the filesystem.  The volume name can be up "
+"to 11 characters long.  The default is no label."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:124
+#, no-wrap
+msgid "B<-r> I<ROOT-DIR-ENTRIES>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:127
+msgid ""
+"Select the number of entries available in the root directory.  The default "
+"is 112 or 224 for floppies and 512 for hard disks."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:127
+#, no-wrap
+msgid "B<-R> I<NUMBER-OF-RESERVED-SECTORS>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:131
+msgid ""
+"Select the number of reserved sectors.  With FAT32 format at least 2 "
+"reserved sectors are needed, the default is 32.  Otherwise the default is 1 "
+"(only the boot sector)."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:131
+#, no-wrap
+msgid "B<-s> I<SECTORS-PER-CLUSTER>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:134
+msgid ""
+"Specify the number of disk sectors per cluster.  Must be a power of 2, i.e. "
+"1, 2, 4, 8, ... 128."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:134
+#, no-wrap
+msgid "B<-S> I<LOGICAL-SECTOR-SIZE>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:138
+msgid ""
+"Specify the number of bytes per logical sector.  Must be a power of 2 and "
+"greater than or equal to 512, i.e. 512, 1024, 2048, 4096, 8192, 16384, or "
+"32768."
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:140
+msgid "Verbose execution."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:140
+#, no-wrap
+msgid "B<--invariant>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:146
+msgid ""
+"Use constants for normally randomly generated or time based data such as "
+"volume ID and creation time.  Multiple runs of B<mkfs.fat> on the same "
+"device create identical results with this option.  Its main purpose is "
+"testing B<mkfs.fat>."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:146
+#, no-wrap
+msgid "B<--help>"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/mkfs.fat.8:149
+msgid "Display option summary and exit."
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/mkfs.fat.8:155
+msgid ""
+"B<mkfs.fat> can not create boot-able filesystems.  This isn't as easy as you "
+"might think at first glance for various reasons and has been discussed a lot "
+"already.  B<mkfs.fat> simply will not support it ;)"
+msgstr ""
diff --git a/dosfstools/manpages/po4a.cfg b/dosfstools/manpages/po4a.cfg
new file mode 100644
index 0000000..5d16f63
--- /dev/null
+++ b/dosfstools/manpages/po4a.cfg
@@ -0,0 +1,5 @@
+[po4a_langs] de
+[po4a_paths] pot/$master.pot $lang:po/$lang/$master.po
+[type: man] en/fatlabel.8 $lang:$lang/fatlabel.$lang.8
+[type: man] en/fsck.fat.8 $lang:$lang/fsck.fat.$lang.8
+[type: man] en/mkfs.fat.8 $lang:$lang/mkfs.fat.$lang.8
diff --git a/dosfstools/manpages/pot/fatlabel.8.pot b/dosfstools/manpages/pot/fatlabel.8.pot
new file mode 100644
index 0000000..2eafa05
--- /dev/null
+++ b/dosfstools/manpages/pot/fatlabel.8.pot
@@ -0,0 +1,176 @@
+# SOME DESCRIPTIVE TITLE
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# This file is distributed under the same license as the dosfstools package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: dosfstools VERSION\n"
+"POT-Creation-Date: 2015-05-16 00:40+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. type: TH
+#: en/fatlabel.8:22
+#, no-wrap
+msgid "FATLABEL"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "2015-05-16"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "3.0.28"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "dosfstools"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:23 en/fsck.fat.8:23 en/mkfs.fat.8:23
+#, no-wrap
+msgid "NAME"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:26
+msgid "B<fatlabel> - set or get MS-DOS filesystem label"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:26 en/fsck.fat.8:26 en/mkfs.fat.8:26
+#, no-wrap
+msgid "SYNOPSIS"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:29
+msgid "B<fatlabel> I<DEVICE> [I<LABEL>]"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:29 en/fsck.fat.8:29 en/mkfs.fat.8:29
+#, no-wrap
+msgid "DESCRIPTION"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:31
+msgid "B<fatlabel> set or gets a MS-DOS filesystem label from a given device."
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:36
+msgid ""
+"If I<LABEL> is omitted, then the label name of the specified device is "
+"written on the standard output.  A label can't be longer than 11 bytes."
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:36 en/fsck.fat.8:99 en/mkfs.fat.8:36
+#, no-wrap
+msgid "OPTIONS"
+msgstr ""
+
+#. type: IP
+#: en/fatlabel.8:37
+#, no-wrap
+msgid "B<-h>, B<--help>"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:39
+msgid "Displays a help message."
+msgstr ""
+
+#. type: IP
+#: en/fatlabel.8:39
+#, no-wrap
+msgid "B<-V>, B<--version>"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:42
+msgid "Shows version."
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:42 en/fsck.fat.8:183 en/mkfs.fat.8:155
+#, no-wrap
+msgid "SEE ALSO"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:44 en/mkfs.fat.8:160
+msgid "B<fsck.fat>(8)"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:47 en/fsck.fat.8:188
+msgid "B<mkfs.fat>(8)"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:47 en/fsck.fat.8:188 en/mkfs.fat.8:160
+#, no-wrap
+msgid "HOMEPAGE"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+msgid ""
+"The home for the B<dosfstools> project is its E<.UR https://github.com/"
+"dosfstools/dosfstools> GitHub project page E<.UE .>"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+#, no-wrap
+msgid "AUTHORS"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:55 en/fsck.fat.8:196 en/mkfs.fat.8:168
+msgid "B<dosfstools> were written by"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:57 en/fsck.fat.8:198 en/mkfs.fat.8:170
+msgid "Werner Almesberger"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:60 en/fsck.fat.8:201 en/mkfs.fat.8:173
+msgid "Roman Hodek"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:63 en/fsck.fat.8:204 en/mkfs.fat.8:176
+msgid "and others.  The current maintainer is"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:65 en/fsck.fat.8:206 en/mkfs.fat.8:178
+msgid "Andreas Bombe"
+msgstr ""
diff --git a/dosfstools/manpages/pot/fsck.fat.8.pot b/dosfstools/manpages/pot/fsck.fat.8.pot
new file mode 100644
index 0000000..65ef52a
--- /dev/null
+++ b/dosfstools/manpages/pot/fsck.fat.8.pot
@@ -0,0 +1,562 @@
+# SOME DESCRIPTIVE TITLE
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# This file is distributed under the same license as the dosfstools package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: dosfstools VERSION\n"
+"POT-Creation-Date: 2015-05-16 00:40+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "2015-05-16"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "3.0.28"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "dosfstools"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:23 en/fsck.fat.8:23 en/mkfs.fat.8:23
+#, no-wrap
+msgid "NAME"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:26 en/fsck.fat.8:26 en/mkfs.fat.8:26
+#, no-wrap
+msgid "SYNOPSIS"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:29 en/fsck.fat.8:29 en/mkfs.fat.8:29
+#, no-wrap
+msgid "DESCRIPTION"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:36 en/fsck.fat.8:99 en/mkfs.fat.8:36
+#, no-wrap
+msgid "OPTIONS"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:42 en/fsck.fat.8:183 en/mkfs.fat.8:155
+#, no-wrap
+msgid "SEE ALSO"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:47 en/fsck.fat.8:188
+msgid "B<mkfs.fat>(8)"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:47 en/fsck.fat.8:188 en/mkfs.fat.8:160
+#, no-wrap
+msgid "HOMEPAGE"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+msgid ""
+"The home for the B<dosfstools> project is its E<.UR https://github.com/"
+"dosfstools/dosfstools> GitHub project page E<.UE .>"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+#, no-wrap
+msgid "AUTHORS"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:55 en/fsck.fat.8:196 en/mkfs.fat.8:168
+msgid "B<dosfstools> were written by"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:57 en/fsck.fat.8:198 en/mkfs.fat.8:170
+msgid "Werner Almesberger"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:60 en/fsck.fat.8:201 en/mkfs.fat.8:173
+msgid "Roman Hodek"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:63 en/fsck.fat.8:204 en/mkfs.fat.8:176
+msgid "and others.  The current maintainer is"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:65 en/fsck.fat.8:206 en/mkfs.fat.8:178
+msgid "Andreas Bombe"
+msgstr ""
+
+#. type: TH
+#: en/fsck.fat.8:22
+#, no-wrap
+msgid "FSCK.FAT"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:26
+msgid "B<fsck.fat> - check and repair MS-DOS filesystems"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:29
+msgid "B<fsck.fat> [I<OPTIONS>] I<DEVICE>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:32
+msgid ""
+"B<fsck.fat> verifies the consistency of MS-DOS filesystems and optionally "
+"tries to repair them."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:34
+msgid "The following filesystem problems can be corrected (in this order):"
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:34 en/fsck.fat.8:37 en/fsck.fat.8:40 en/fsck.fat.8:44
+#: en/fsck.fat.8:47 en/fsck.fat.8:50 en/fsck.fat.8:53 en/fsck.fat.8:56
+#: en/fsck.fat.8:59 en/fsck.fat.8:62 en/fsck.fat.8:65 en/fsck.fat.8:68
+#: en/fsck.fat.8:71 en/fsck.fat.8:74 en/fsck.fat.8:77 en/fsck.fat.8:82
+#: en/fsck.fat.8:85 en/fsck.fat.8:90 en/fsck.fat.8:92
+#, no-wrap
+msgid "*"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:37
+msgid "FAT contains invalid cluster numbers.  Cluster is changed to EOF."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:40
+msgid "File's cluster chain contains a loop.  The loop is broken."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:44
+msgid ""
+"Bad clusters (read errors).  The clusters are marked bad and they are "
+"removed from files owning them.  This check is optional."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:47
+msgid ""
+"Directories with a large number of bad entries (probably corrupt).  The "
+"directory can be deleted."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:50
+msgid "Files . and .. are non-directories.  They can be deleted or renamed."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:53
+msgid "Directories . and .. in root directory.  They are deleted."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:56
+msgid "Bad filenames.  They can be renamed."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:59
+msgid "Duplicate directory entries.  They can be deleted or renamed."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:62
+msgid "Directories with non-zero size field.  Size is set to zero."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:65
+msgid ""
+"Directory . does not point to parent directory.  The start pointer is "
+"adjusted."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:68
+msgid ""
+"Directory .. does not point to parent of parent directory.  The start "
+"pointer is adjusted."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:71
+msgid "Start cluster number of a file is invalid.  The file is truncated."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:74
+msgid "File contains bad or free clusters.  The file is truncated."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:77
+msgid ""
+"File's cluster chain is longer than indicated by the size fields.  The file "
+"is truncated."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:82
+msgid ""
+"Two or more files share the same cluster(s).  All but one of the files are "
+"truncated.  If the file being truncated is a directory file that has already "
+"been read, the filesystem check is restarted after truncation."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:85
+msgid ""
+"File's cluster chain is shorter than indicated by the size fields.  The file "
+"is truncated."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:88
+msgid ""
+"Clusters are marked as used but are not owned by a file.  They are marked as "
+"free."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:90
+msgid "Additionally, the following problems are detected, but not repaired:"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:92
+msgid "Invalid parameters in boot sector"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:94
+msgid "Absence of . and .. entries in non-root directories"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:99
+msgid ""
+"When B<fsck.fat> checks a filesystem, it accumulates all changes in memory "
+"and performs them only after all checks are complete.  This can be disabled "
+"with the B<-w> option."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:100 en/mkfs.fat.8:37
+#, no-wrap
+msgid "B<-a>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:105
+msgid ""
+"Automatically repair the filesystem.  No user intervention is necessary.  "
+"Whenever there is more than one method to solve a problem, the least "
+"destructive approach is used."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:105
+#, no-wrap
+msgid "B<-A>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:116
+msgid ""
+"Use Atari variation of the MS-DOS filesystem.  This is default if B<fsck."
+"fat> is run on an Atari, then this option turns off Atari format.  There are "
+"some minor differences in Atari format: Some boot sector fields are "
+"interpreted slightly different, and the special FAT entries for end-of-file "
+"and bad cluster can be different.  Under MS-DOS 0xfff8 is used for EOF and "
+"Atari employs 0xffff by default, but both systems recognize all values from "
+"0xfff8...0xffff as end-of-file.  MS-DOS uses only 0xfff7 for bad clusters, "
+"where on Atari values 0xfff0...0xfff7 are for this purpose (but the standard "
+"value is still 0xfff7)."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:116
+#, no-wrap
+msgid "B<-b>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:118
+msgid "Make read-only boot sector check."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:118
+#, no-wrap
+msgid "B<-d> I<PATH>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:122
+msgid ""
+"Delete the specified file.  If more than one file with that name exist, the "
+"first one is deleted.  This option can be given more than once."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:122
+#, no-wrap
+msgid "B<-f>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:126
+msgid ""
+"Salvage unused cluster chains to files.  By default, unused clusters are "
+"added to the free disk space except in auto mode (B<-a>)."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:126
+#, no-wrap
+msgid "B<-l>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:128
+msgid "List path names of files being processed."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:128
+#, no-wrap
+msgid "B<-n>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:131
+msgid ""
+"No-operation mode: non-interactively check for errors, but don't write "
+"anything to the filesystem."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:131
+#, no-wrap
+msgid "B<-p>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:133
+msgid "Same as B<-a>, for compatibility with other *fsck."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:133
+#, no-wrap
+msgid "B<-r>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:139
+msgid ""
+"Interactively repair the filesystem.  The user is asked for advice whenever "
+"there is more than one approach to fix an inconsistency.  This is the "
+"default mode and the option is only retained for backwards compatibility."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:139
+#, no-wrap
+msgid "B<-t>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:141
+msgid "Mark unreadable clusters as bad."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:141
+#, no-wrap
+msgid "B<-u> I<PATH>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:146
+msgid ""
+"Try to undelete the specified file.  B<fsck.fat> tries to allocate a chain "
+"of contiguous unallocated clusters beginning with the start cluster of the "
+"undeleted file.  This option can be given more than once."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:146 en/mkfs.fat.8:138
+#, no-wrap
+msgid "B<-v>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:149
+msgid "Verbose mode.  Generates slightly more output."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:149
+#, no-wrap
+msgid "B<-V>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:156
+msgid ""
+"Perform a verification pass.  The filesystem check is repeated after the "
+"first run.  The second pass should never report any fixable errors.  It may "
+"take considerably longer than the first pass, because the first pass may "
+"have generated long list of modifications that have to be scanned for each "
+"disk read."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:156
+#, no-wrap
+msgid "B<-w>"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:158
+msgid "Write changes to disk immediately."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:158
+#, no-wrap
+msgid "B<-y>"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:162
+msgid ""
+"Same as B<-a> (automatically repair filesystem) for compatibility with other "
+"fsck tools."
+msgstr ""
+
+#. type: SH
+#: en/fsck.fat.8:162
+#, no-wrap
+msgid "EXIT STATUS"
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:163
+#, no-wrap
+msgid "0"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:165
+msgid "No recoverable errors have been detected."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:165
+#, no-wrap
+msgid "1"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:168
+msgid ""
+"Recoverable errors have been detected or B<fsck.fat> has discovered an "
+"internal inconsistency."
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:168
+#, no-wrap
+msgid "2"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:172
+msgid "Usage error.  B<fsck.fat> did not access the filesystem."
+msgstr ""
+
+#. type: SH
+#: en/fsck.fat.8:172
+#, no-wrap
+msgid "FILES"
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:173
+#, no-wrap
+msgid "fsck0000.rec, fsck0001.rec, ..."
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:177
+msgid ""
+"When recovering from a corrupted filesystem, B<fsck.fat> dumps recovered "
+"data into files named 'fsckNNNN.rec' in the top level directory of the "
+"filesystem."
+msgstr ""
+
+#. type: SH
+#: en/fsck.fat.8:177 en/mkfs.fat.8:149
+#, no-wrap
+msgid "BUGS"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fsck.fat.8:183
+msgid ""
+"Does not create . and .. files where necessary.  Does not remove entirely "
+"empty directories.  Should give more diagnostic messages.  Undeleting files "
+"should use a more sophisticated algorithm."
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:185 en/mkfs.fat.8:157
+msgid "B<fatlabel>(8)"
+msgstr ""
diff --git a/dosfstools/manpages/pot/mkfs.fat.8.pot b/dosfstools/manpages/pot/mkfs.fat.8.pot
new file mode 100644
index 0000000..c9d5bba
--- /dev/null
+++ b/dosfstools/manpages/pot/mkfs.fat.8.pot
@@ -0,0 +1,484 @@
+# SOME DESCRIPTIVE TITLE
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# This file is distributed under the same license as the dosfstools package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: dosfstools VERSION\n"
+"POT-Creation-Date: 2015-05-16 00:40+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "2015-05-16"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "3.0.28"
+msgstr ""
+
+#. type: TH
+#: en/fatlabel.8:22 en/fsck.fat.8:22 en/mkfs.fat.8:22
+#, no-wrap
+msgid "dosfstools"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:23 en/fsck.fat.8:23 en/mkfs.fat.8:23
+#, no-wrap
+msgid "NAME"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:26 en/fsck.fat.8:26 en/mkfs.fat.8:26
+#, no-wrap
+msgid "SYNOPSIS"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:29 en/fsck.fat.8:29 en/mkfs.fat.8:29
+#, no-wrap
+msgid "DESCRIPTION"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:36 en/fsck.fat.8:99 en/mkfs.fat.8:36
+#, no-wrap
+msgid "OPTIONS"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:42 en/fsck.fat.8:183 en/mkfs.fat.8:155
+#, no-wrap
+msgid "SEE ALSO"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:44 en/mkfs.fat.8:160
+msgid "B<fsck.fat>(8)"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:47 en/fsck.fat.8:188 en/mkfs.fat.8:160
+#, no-wrap
+msgid "HOMEPAGE"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+msgid ""
+"The home for the B<dosfstools> project is its E<.UR https://github.com/"
+"dosfstools/dosfstools> GitHub project page E<.UE .>"
+msgstr ""
+
+#. type: SH
+#: en/fatlabel.8:53 en/fsck.fat.8:194 en/mkfs.fat.8:166
+#, no-wrap
+msgid "AUTHORS"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:55 en/fsck.fat.8:196 en/mkfs.fat.8:168
+msgid "B<dosfstools> were written by"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:57 en/fsck.fat.8:198 en/mkfs.fat.8:170
+msgid "Werner Almesberger"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:60 en/fsck.fat.8:201 en/mkfs.fat.8:173
+msgid "Roman Hodek"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:63 en/fsck.fat.8:204 en/mkfs.fat.8:176
+msgid "and others.  The current maintainer is"
+msgstr ""
+
+#. type: Plain text
+#: en/fatlabel.8:65 en/fsck.fat.8:206 en/mkfs.fat.8:178
+msgid "Andreas Bombe"
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:100 en/mkfs.fat.8:37
+#, no-wrap
+msgid "B<-a>"
+msgstr ""
+
+#. type: IP
+#: en/fsck.fat.8:146 en/mkfs.fat.8:138
+#, no-wrap
+msgid "B<-v>"
+msgstr ""
+
+#. type: SH
+#: en/fsck.fat.8:177 en/mkfs.fat.8:149
+#, no-wrap
+msgid "BUGS"
+msgstr ""
+
+#. type: Plain text
+#: en/fsck.fat.8:185 en/mkfs.fat.8:157
+msgid "B<fatlabel>(8)"
+msgstr ""
+
+#. type: TH
+#: en/mkfs.fat.8:22
+#, no-wrap
+msgid "MKFS.FAT"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/mkfs.fat.8:26
+msgid "B<mkfs.fat> - create an MS-DOS filesystem under Linux"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/mkfs.fat.8:29
+msgid "B<mkfs.fat> [I<OPTIONS>] I<DEVICE> [I<BLOCK-COUNT>]"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/mkfs.fat.8:36
+msgid ""
+"B<mkfs.fat> is used to create an MS-DOS filesystem under Linux on a device "
+"(usually a disk partition).  I<DEVICE> is the special file corresponding to "
+"the device (e.g. /dev/sdXX).  I<BLOCK-COUNT> is the number of blocks on the "
+"device.  If omitted, B<mkfs.fat> automatically determines the filesystem "
+"size."
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:45
+msgid ""
+"Normally, for any filesystem except very small ones, B<mkfs.fat> will align "
+"all the data structures to cluster size, to make sure that as long as the "
+"partition is properly aligned, so will all the data structures in the "
+"filesystem.  This option disables alignment; this may provide a handful of "
+"additional clusters of storage at the expense of a significant performance "
+"degradation on RAIDs, flash media or large-sector hard disks."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:45
+#, no-wrap
+msgid "B< -A>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:60
+msgid ""
+"Use Atari variation of the MS-DOS filesystem.  This is default if B<mkfs."
+"fat> is run on an Atari, then this option turns off Atari format.  There are "
+"some differences when using Atari format: If not directed otherwise by the "
+"user, B<mkfs.fat> will always use 2 sectors per cluster, since GEMDOS "
+"doesn't like other values very much.  It will also obey the maximum number "
+"of sectors GEMDOS can handle.  Larger filesystems are managed by raising the "
+"logical sector size.  Under Atari format, an Atari-compatible serial number "
+"for the filesystem is generated, and a 12 bit FAT is used only for "
+"filesystems that have one of the usual floppy sizes (720k, 1.2M, 1.44M, "
+"2.88M), a 16 bit FAT otherwise.  This can be overridden with the B<-F> "
+"option.  Some PC-specific boot sector fields aren't written, and a boot "
+"message (option B<-m>) is ignored."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:60
+#, no-wrap
+msgid "B<-b> I<SECTOR-OF-BACKUP>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:64
+msgid ""
+"Selects the location of the backup boot sector for FAT32.  Default depends "
+"on number of reserved sectors, but usually is sector 6.  The backup must be "
+"within the range of reserved sectors."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:64
+#, no-wrap
+msgid "B<-c>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:66
+msgid "Check the device for bad blocks before creating the filesystem."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:66
+#, no-wrap
+msgid "B<-C>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:80
+msgid ""
+"Create the file given as I<DEVICE> on the command line, and write the to-be-"
+"created filesystem to it.  This can be used to create the new filesystem in "
+"a file instead of on a real device, and to avoid using B<dd> in advance to "
+"create a file of appropriate size.  With this option, the I<BLOCK-COUNT> "
+"must be given, because otherwise the intended size of the filesystem "
+"wouldn't be known.  The file created is a sparse file, which actually only "
+"contains the meta-data areas (boot sector, FATs, and root directory).  The "
+"data portions won't be stored on the disk, but the file nevertheless will "
+"have the correct size.  The resulting file can be copied later to a floppy "
+"disk or other device, or mounted through a loop device."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:80
+#, no-wrap
+msgid "B<-D> I<DRIVE-NUMBER>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:84
+msgid ""
+"Specify the BIOS drive number to be stored in the FAT boot sector.  This "
+"value is usually 0x80 for hard disks and 0x00 for floppy devices or "
+"partitions to be used for floppy emulation."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:84
+#, no-wrap
+msgid "B<-f> I<NUMBER-OF-FATS>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:87
+msgid ""
+"Specify the number of file allocation tables in the filesystem.  The default "
+"is 2."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:87
+#, no-wrap
+msgid "B<-F> I<FAT-SIZE>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:91
+msgid ""
+"Specifies the type of file allocation tables used (12, 16 or 32 bit).  If "
+"nothing is specified, B<mkfs.fat> will automatically select between 12, 16 "
+"and 32 bit, whatever fits better for the filesystem size."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:91
+#, no-wrap
+msgid "B<-h> I<NUMBER-OF-HIDDEN-SECTORS>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:95
+msgid ""
+"Select the number of hidden sectors in the volume.  Apparently some digital "
+"cameras get indigestion if you feed them a CF card without such hidden "
+"sectors, this option allows you to satisfy them."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:95
+#, no-wrap
+msgid "B<-i> I<VOLUME-ID>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:99
+msgid ""
+"Sets the volume ID of the newly created filesystem; I<VOLUME-ID> is a 32-bit "
+"hexadecimal number (for example, 2e24ec82).  The default is a number which "
+"depends on the filesystem creation time."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:99
+#, no-wrap
+msgid "B<-I>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:108
+msgid ""
+"It is typical for fixed disk devices to be partitioned so, by default, you "
+"are not permitted to create a filesystem across the entire device.  B<mkfs."
+"fat> will complain and tell you that it refuses to work.  This is different "
+"when using MO disks.  One doesn't always need partitions on MO disks.  The "
+"filesystem can go directly to the whole disk.  Under other OSes this is "
+"known as the 'superfloppy' format.  This switch will force B<mkfs.fat> to "
+"work properly."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:108
+#, no-wrap
+msgid "B<-l> I<FILENAME>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:110
+msgid "Read the bad blocks list from I<FILENAME>."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:110
+#, no-wrap
+msgid "B<-m> I<MESSAGE-FILE>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:116
+msgid ""
+"Sets the message the user receives on attempts to boot this filesystem "
+"without having properly installed an operating system.  The message file "
+"must not exceed 418 bytes once line feeds have been converted to carriage "
+"return-line feed combinations, and tabs have been expanded.  If the filename "
+"is a hyphen (-), the text is taken from standard input."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:116
+#, no-wrap
+msgid "B<-M> I<FAT-MEDIA-TYPE>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:120
+msgid ""
+"Specify the media type to be stored in the FAT boot sector.  This value is "
+"usually 0xF8 for hard disks and is 0xF0 or a value from 0xF9 to 0xFF for "
+"floppies or partitions to be used for floppy emulation."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:120
+#, no-wrap
+msgid "B<-n> I<VOLUME-NAME>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:124
+msgid ""
+"Sets the volume name (label) of the filesystem.  The volume name can be up "
+"to 11 characters long.  The default is no label."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:124
+#, no-wrap
+msgid "B<-r> I<ROOT-DIR-ENTRIES>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:127
+msgid ""
+"Select the number of entries available in the root directory.  The default "
+"is 112 or 224 for floppies and 512 for hard disks."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:127
+#, no-wrap
+msgid "B<-R> I<NUMBER-OF-RESERVED-SECTORS>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:131
+msgid ""
+"Select the number of reserved sectors.  With FAT32 format at least 2 "
+"reserved sectors are needed, the default is 32.  Otherwise the default is 1 "
+"(only the boot sector)."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:131
+#, no-wrap
+msgid "B<-s> I<SECTORS-PER-CLUSTER>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:134
+msgid ""
+"Specify the number of disk sectors per cluster.  Must be a power of 2, i.e. "
+"1, 2, 4, 8, ... 128."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:134
+#, no-wrap
+msgid "B<-S> I<LOGICAL-SECTOR-SIZE>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:138
+msgid ""
+"Specify the number of bytes per logical sector.  Must be a power of 2 and "
+"greater than or equal to 512, i.e. 512, 1024, 2048, 4096, 8192, 16384, or "
+"32768."
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:140
+msgid "Verbose execution."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:140
+#, no-wrap
+msgid "B<--invariant>"
+msgstr ""
+
+#. type: Plain text
+#: en/mkfs.fat.8:146
+msgid ""
+"Use constants for normally randomly generated or time based data such as "
+"volume ID and creation time.  Multiple runs of B<mkfs.fat> on the same "
+"device create identical results with this option.  Its main purpose is "
+"testing B<mkfs.fat>."
+msgstr ""
+
+#. type: IP
+#: en/mkfs.fat.8:146
+#, no-wrap
+msgid "B<--help>"
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/mkfs.fat.8:149
+msgid "Display option summary and exit."
+msgstr ""
+
+#.  ----------------------------------------------------------------------------
+#. type: Plain text
+#: en/mkfs.fat.8:155
+msgid ""
+"B<mkfs.fat> can not create boot-able filesystems.  This isn't as easy as you "
+"might think at first glance for various reasons and has been discussed a lot "
+"already.  B<mkfs.fat> simply will not support it ;)"
+msgstr ""
diff --git a/dosfstools/src/boot.c b/dosfstools/src/boot.c
new file mode 100644
index 0000000..0c0918f
--- /dev/null
+++ b/dosfstools/src/boot.c
@@ -0,0 +1,568 @@
+/* boot.c - Read and analyze ia PC/MS-DOS boot sector
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+/* FAT32, VFAT, Atari format support, and various fixes additions May 1998
+ * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "common.h"
+#include "fsck.fat.h"
+#include "fat.h"
+#include "io.h"
+#include "boot.h"
+#include "check.h"
+
+#define ROUND_TO_MULTIPLE(n,m) ((n) && (m) ? (n)+(m)-1-((n)-1)%(m) : 0)
+    /* don't divide by zero */
+
+/* cut-over cluster counts for FAT12 and FAT16 */
+#define FAT12_THRESHOLD  4085
+#define FAT16_THRESHOLD 65525
+
+static struct {
+    uint8_t media;
+    const char *descr;
+} mediabytes[] = {
+    {
+    0xf0, "5.25\" or 3.5\" HD floppy"}, {
+    0xf8, "hard disk"}, {
+    0xf9, "3,5\" 720k floppy 2s/80tr/9sec or "
+	    "5.25\" 1.2M floppy 2s/80tr/15sec"}, {
+    0xfa, "5.25\" 320k floppy 1s/80tr/8sec"}, {
+    0xfb, "3.5\" 640k floppy 2s/80tr/8sec"}, {
+    0xfc, "5.25\" 180k floppy 1s/40tr/9sec"}, {
+    0xfd, "5.25\" 360k floppy 2s/40tr/9sec"}, {
+    0xfe, "5.25\" 160k floppy 1s/40tr/8sec"}, {
+0xff, "5.25\" 320k floppy 2s/40tr/8sec"},};
+
+/* Unaligned fields must first be accessed byte-wise */
+#define GET_UNALIGNED_W(f)			\
+    ( (uint16_t)f[0] | ((uint16_t)f[1]<<8) )
+
+static const char *get_media_descr(unsigned char media)
+{
+    int i;
+
+    for (i = 0; i < sizeof(mediabytes) / sizeof(*mediabytes); ++i) {
+	if (mediabytes[i].media == media)
+	    return (mediabytes[i].descr);
+    }
+    return ("undefined");
+}
+
+static void dump_boot(DOS_FS * fs, struct boot_sector *b, unsigned lss)
+{
+    unsigned short sectors;
+
+    printf("Boot sector contents:\n");
+    if (!atari_format) {
+	char id[9];
+	strncpy(id, (const char *)b->system_id, 8);
+	id[8] = 0;
+	printf("System ID \"%s\"\n", id);
+    } else {
+	/* On Atari, a 24 bit serial number is stored at offset 8 of the boot
+	 * sector */
+	printf("Serial number 0x%x\n",
+	       b->system_id[5] | (b->system_id[6] << 8) | (b->
+							   system_id[7] << 16));
+    }
+    printf("Media byte 0x%02x (%s)\n", b->media, get_media_descr(b->media));
+    printf("%10d bytes per logical sector\n", GET_UNALIGNED_W(b->sector_size));
+    printf("%10d bytes per cluster\n", fs->cluster_size);
+    printf("%10d reserved sector%s\n", le16toh(b->reserved),
+	   le16toh(b->reserved) == 1 ? "" : "s");
+    printf("First FAT starts at byte %llu (sector %llu)\n",
+	   (unsigned long long)fs->fat_start,
+	   (unsigned long long)fs->fat_start / lss);
+    printf("%10d FATs, %d bit entries\n", b->fats, fs->fat_bits);
+    printf("%10d bytes per FAT (= %u sectors)\n", fs->fat_size,
+	   fs->fat_size / lss);
+    if (!fs->root_cluster) {
+	printf("Root directory starts at byte %llu (sector %llu)\n",
+	       (unsigned long long)fs->root_start,
+	       (unsigned long long)fs->root_start / lss);
+	printf("%10d root directory entries\n", fs->root_entries);
+    } else {
+	printf("Root directory start at cluster %lu (arbitrary size)\n",
+	       (unsigned long)fs->root_cluster);
+    }
+    printf("Data area starts at byte %llu (sector %llu)\n",
+	   (unsigned long long)fs->data_start,
+	   (unsigned long long)fs->data_start / lss);
+    printf("%10lu data clusters (%llu bytes)\n", (unsigned long)fs->clusters,
+	   (unsigned long long)fs->clusters * fs->cluster_size);
+    printf("%u sectors/track, %u heads\n", le16toh(b->secs_track),
+	   le16toh(b->heads));
+    printf("%10u hidden sectors\n", atari_format ?
+	   /* On Atari, the hidden field is only 16 bit wide and unused */
+	   (((unsigned char *)&b->hidden)[0] |
+	    ((unsigned char *)&b->hidden)[1] << 8) : le32toh(b->hidden));
+    sectors = GET_UNALIGNED_W(b->sectors);
+    printf("%10u sectors total\n", sectors ? sectors : le32toh(b->total_sect));
+}
+
+static void check_backup_boot(DOS_FS * fs, struct boot_sector *b, int lss)
+{
+    struct boot_sector b2;
+
+    if (!fs->backupboot_start) {
+	printf("There is no backup boot sector.\n");
+	if (le16toh(b->reserved) < 3) {
+	    printf("And there is no space for creating one!\n");
+	    return;
+	}
+	if (interactive)
+	    printf("1) Create one\n2) Do without a backup\n");
+	else
+	    printf("  Auto-creating backup boot block.\n");
+	if (!interactive || get_key("12", "?") == '1') {
+	    int bbs;
+	    /* The usual place for the backup boot sector is sector 6. Choose
+	     * that or the last reserved sector. */
+	    if (le16toh(b->reserved) >= 7 && le16toh(b->info_sector) != 6)
+		bbs = 6;
+	    else {
+		bbs = le16toh(b->reserved) - 1;
+		if (bbs == le16toh(b->info_sector))
+		    --bbs;	/* this is never 0, as we checked reserved >= 3! */
+	    }
+	    fs->backupboot_start = bbs * lss;
+	    b->backup_boot = htole16(bbs);
+	    fs_write(fs->backupboot_start, sizeof(*b), b);
+	    fs_write((loff_t) offsetof(struct boot_sector, backup_boot),
+		     sizeof(b->backup_boot), &b->backup_boot);
+	    printf("Created backup of boot sector in sector %d\n", bbs);
+	    return;
+	} else
+	    return;
+    }
+
+    fs_read(fs->backupboot_start, sizeof(b2), &b2);
+    if (memcmp(b, &b2, sizeof(b2)) != 0) {
+	/* there are any differences */
+	uint8_t *p, *q;
+	int i, pos, first = 1;
+	char buf[20];
+
+	printf("There are differences between boot sector and its backup.\n");
+	printf("This is mostly harmless. Differences: (offset:original/backup)\n  ");
+	pos = 2;
+	for (p = (uint8_t *) b, q = (uint8_t *) & b2, i = 0; i < sizeof(b2);
+	     ++p, ++q, ++i) {
+	    if (*p != *q) {
+		sprintf(buf, "%s%u:%02x/%02x", first ? "" : ", ",
+			(unsigned)(p - (uint8_t *) b), *p, *q);
+		if (pos + strlen(buf) > 78)
+		    printf("\n  "), pos = 2;
+		printf("%s", buf);
+		pos += strlen(buf);
+		first = 0;
+	    }
+	}
+	printf("\n");
+
+	if (interactive)
+	    printf("1) Copy original to backup\n"
+		   "2) Copy backup to original\n" "3) No action\n");
+	else
+	    printf("  Not automatically fixing this.\n");
+	switch (interactive ? get_key("123", "?") : '3') {
+	case '1':
+	    fs_write(fs->backupboot_start, sizeof(*b), b);
+	    break;
+	case '2':
+	    fs_write(0, sizeof(b2), &b2);
+	    break;
+	default:
+	    break;
+	}
+    }
+}
+
+static void init_fsinfo(struct info_sector *i)
+{
+    i->magic = htole32(0x41615252);
+    i->signature = htole32(0x61417272);
+    i->free_clusters = htole32(-1);
+    i->next_cluster = htole32(2);
+    i->boot_sign = htole16(0xaa55);
+}
+
+static void read_fsinfo(DOS_FS * fs, struct boot_sector *b, int lss)
+{
+    struct info_sector i;
+
+    if (!b->info_sector) {
+	printf("No FSINFO sector\n");
+	if (interactive)
+	    printf("1) Create one\n2) Do without FSINFO\n");
+	else
+	    printf("  Not automatically creating it.\n");
+	if (interactive && get_key("12", "?") == '1') {
+	    /* search for a free reserved sector (not boot sector and not
+	     * backup boot sector) */
+	    uint32_t s;
+	    for (s = 1; s < le16toh(b->reserved); ++s)
+		if (s != le16toh(b->backup_boot))
+		    break;
+	    if (s > 0 && s < le16toh(b->reserved)) {
+		init_fsinfo(&i);
+		fs_write((loff_t) s * lss, sizeof(i), &i);
+		b->info_sector = htole16(s);
+		fs_write((loff_t) offsetof(struct boot_sector, info_sector),
+			 sizeof(b->info_sector), &b->info_sector);
+		if (fs->backupboot_start)
+		    fs_write(fs->backupboot_start +
+			     offsetof(struct boot_sector, info_sector),
+			     sizeof(b->info_sector), &b->info_sector);
+	    } else {
+		printf("No free reserved sector found -- "
+		       "no space for FSINFO sector!\n");
+		return;
+	    }
+	} else
+	    return;
+    }
+
+    fs->fsinfo_start = le16toh(b->info_sector) * lss;
+    fs_read(fs->fsinfo_start, sizeof(i), &i);
+
+    if (i.magic != htole32(0x41615252) ||
+	i.signature != htole32(0x61417272) || i.boot_sign != htole16(0xaa55)) {
+	printf("FSINFO sector has bad magic number(s):\n");
+	if (i.magic != htole32(0x41615252))
+	    printf("  Offset %llu: 0x%08x != expected 0x%08x\n",
+		   (unsigned long long)offsetof(struct info_sector, magic),
+		   le32toh(i.magic), 0x41615252);
+	if (i.signature != htole32(0x61417272))
+	    printf("  Offset %llu: 0x%08x != expected 0x%08x\n",
+		   (unsigned long long)offsetof(struct info_sector, signature),
+		   le32toh(i.signature), 0x61417272);
+	if (i.boot_sign != htole16(0xaa55))
+	    printf("  Offset %llu: 0x%04x != expected 0x%04x\n",
+		   (unsigned long long)offsetof(struct info_sector, boot_sign),
+		   le16toh(i.boot_sign), 0xaa55);
+	if (interactive)
+	    printf("1) Correct\n2) Don't correct (FSINFO invalid then)\n");
+	else
+	    printf("  Auto-correcting it.\n");
+	if (!interactive || get_key("12", "?") == '1') {
+	    init_fsinfo(&i);
+	    fs_write(fs->fsinfo_start, sizeof(i), &i);
+	} else
+	    fs->fsinfo_start = 0;
+    }
+
+    if (fs->fsinfo_start)
+	fs->free_clusters = le32toh(i.free_clusters);
+}
+
+static char print_fat_dirty_state(void)
+{
+    printf("Dirty bit is set. Fs was not properly unmounted and"
+	   " some data may be corrupt.\n");
+
+    if (interactive) {
+	printf("1) Remove dirty bit\n" "2) No action\n");
+	return get_key("12", "?");
+    } else
+	printf(" Automatically removing dirty bit.\n");
+    return '1';
+}
+
+static void check_fat_state_bit(DOS_FS * fs, void *b)
+{
+    if (fs->fat_bits == 32) {
+	struct boot_sector *b32 = b;
+
+	if (b32->reserved3 & FAT_STATE_DIRTY) {
+	    printf("0x41: ");
+	    if (print_fat_dirty_state() == '1') {
+		b32->reserved3 &= ~FAT_STATE_DIRTY;
+		fs_write(0, sizeof(*b32), b32);
+	    }
+	}
+    } else {
+	struct boot_sector_16 *b16 = b;
+
+	if (b16->reserved2 & FAT_STATE_DIRTY) {
+	    printf("0x25: ");
+	    if (print_fat_dirty_state() == '1') {
+		b16->reserved2 &= ~FAT_STATE_DIRTY;
+		fs_write(0, sizeof(*b16), b16);
+	    }
+	}
+    }
+}
+
+void read_boot(DOS_FS * fs)
+{
+    struct boot_sector b;
+    unsigned total_sectors;
+    unsigned short logical_sector_size, sectors;
+    unsigned fat_length;
+    loff_t data_size;
+
+    fs_read(0, sizeof(b), &b);
+    logical_sector_size = GET_UNALIGNED_W(b.sector_size);
+    if (!logical_sector_size)
+	die("Logical sector size is zero.");
+
+    /* This was moved up because it's the first thing that will fail */
+    /* if the platform needs special handling of unaligned multibyte accesses */
+    /* but such handling isn't being provided. See GET_UNALIGNED_W() above. */
+    if (logical_sector_size & (SECTOR_SIZE - 1))
+	die("Logical sector size (%d bytes) is not a multiple of the physical "
+	    "sector size.", logical_sector_size);
+
+    fs->cluster_size = b.cluster_size * logical_sector_size;
+    if (!fs->cluster_size)
+	die("Cluster size is zero.");
+    if (b.fats != 2 && b.fats != 1)
+	die("Currently, only 1 or 2 FATs are supported, not %d.\n", b.fats);
+    fs->nfats = b.fats;
+    sectors = GET_UNALIGNED_W(b.sectors);
+    total_sectors = sectors ? sectors : le32toh(b.total_sect);
+    if (verbose)
+	printf("Checking we can access the last sector of the filesystem\n");
+    /* Can't access last odd sector anyway, so round down */
+    fs_test((loff_t) ((total_sectors & ~1) - 1) * (loff_t) logical_sector_size,
+	    logical_sector_size);
+    fat_length = le16toh(b.fat_length) ?
+	le16toh(b.fat_length) : le32toh(b.fat32_length);
+    fs->fat_start = (loff_t) le16toh(b.reserved) * logical_sector_size;
+    fs->root_start = ((loff_t) le16toh(b.reserved) + b.fats * fat_length) *
+	logical_sector_size;
+    fs->root_entries = GET_UNALIGNED_W(b.dir_entries);
+    fs->data_start = fs->root_start + ROUND_TO_MULTIPLE(fs->root_entries <<
+							MSDOS_DIR_BITS,
+							logical_sector_size);
+    data_size = (loff_t) total_sectors *logical_sector_size - fs->data_start;
+    fs->clusters = data_size / fs->cluster_size;
+    fs->root_cluster = 0;	/* indicates standard, pre-FAT32 root dir */
+    fs->fsinfo_start = 0;	/* no FSINFO structure */
+    fs->free_clusters = -1;	/* unknown */
+    if (!b.fat_length && b.fat32_length) {
+	fs->fat_bits = 32;
+	fs->root_cluster = le32toh(b.root_cluster);
+	if (!fs->root_cluster && fs->root_entries)
+	    /* M$ hasn't specified this, but it looks reasonable: If
+	     * root_cluster is 0 but there is a separate root dir
+	     * (root_entries != 0), we handle the root dir the old way. Give a
+	     * warning, but convertig to a root dir in a cluster chain seems
+	     * to complex for now... */
+	    printf("Warning: FAT32 root dir not in cluster chain! "
+		   "Compatibility mode...\n");
+	else if (!fs->root_cluster && !fs->root_entries)
+	    die("No root directory!");
+	else if (fs->root_cluster && fs->root_entries)
+	    printf("Warning: FAT32 root dir is in a cluster chain, but "
+		   "a separate root dir\n"
+		   "  area is defined. Cannot fix this easily.\n");
+	if (fs->clusters < FAT16_THRESHOLD)
+	    printf("Warning: Filesystem is FAT32 according to fat_length "
+		   "and fat32_length fields,\n"
+		   "  but has only %lu clusters, less than the required "
+		   "minimum of %d.\n"
+		   "  This may lead to problems on some systems.\n",
+		   (unsigned long)fs->clusters, FAT16_THRESHOLD);
+
+	check_fat_state_bit(fs, &b);
+	fs->backupboot_start = le16toh(b.backup_boot) * logical_sector_size;
+	check_backup_boot(fs, &b, logical_sector_size);
+
+	read_fsinfo(fs, &b, logical_sector_size);
+    } else if (!atari_format) {
+	/* On real MS-DOS, a 16 bit FAT is used whenever there would be too
+	 * much clusers otherwise. */
+	fs->fat_bits = (fs->clusters >= FAT12_THRESHOLD) ? 16 : 12;
+	if (fs->clusters >= FAT16_THRESHOLD)
+	    die("Too many clusters (%lu) for FAT16 filesystem.", fs->clusters);
+	check_fat_state_bit(fs, &b);
+    } else {
+	/* On Atari, things are more difficult: GEMDOS always uses 12bit FATs
+	 * on floppies, and always 16 bit on harddisks. */
+	fs->fat_bits = 16;	/* assume 16 bit FAT for now */
+	/* If more clusters than fat entries in 16-bit fat, we assume
+	 * it's a real MSDOS FS with 12-bit fat. */
+	if (fs->clusters + 2 > fat_length * logical_sector_size * 8 / 16 ||
+	    /* if it's a floppy disk --> 12bit fat */
+	    device_no == 2 ||
+	    /* if it's a ramdisk or loopback device and has one of the usual
+	     * floppy sizes -> 12bit FAT  */
+	    ((device_no == 1 || device_no == 7) &&
+	     (total_sectors == 720 || total_sectors == 1440 ||
+	      total_sectors == 2880)))
+	    fs->fat_bits = 12;
+    }
+    /* On FAT32, the high 4 bits of a FAT entry are reserved */
+    fs->eff_fat_bits = (fs->fat_bits == 32) ? 28 : fs->fat_bits;
+    fs->fat_size = fat_length * logical_sector_size;
+
+    fs->label = calloc(12, sizeof(uint8_t));
+    if (fs->fat_bits == 12 || fs->fat_bits == 16) {
+	struct boot_sector_16 *b16 = (struct boot_sector_16 *)&b;
+	if (b16->extended_sig == 0x29)
+	    memmove(fs->label, b16->label, 11);
+	else
+	    fs->label = NULL;
+    } else if (fs->fat_bits == 32) {
+	if (b.extended_sig == 0x29)
+	    memmove(fs->label, &b.label, 11);
+	else
+	    fs->label = NULL;
+    }
+
+    if (fs->clusters >
+	((uint64_t)fs->fat_size * 8 / fs->fat_bits) - 2)
+	die("Filesystem has %d clusters but only space for %d FAT entries.",
+	    fs->clusters,
+	    ((unsigned long long)fs->fat_size * 8 / fs->fat_bits) - 2);
+    if (!fs->root_entries && !fs->root_cluster)
+	die("Root directory has zero size.");
+    if (fs->root_entries & (MSDOS_DPS - 1))
+	die("Root directory (%d entries) doesn't span an integral number of "
+	    "sectors.", fs->root_entries);
+    if (logical_sector_size & (SECTOR_SIZE - 1))
+	die("Logical sector size (%d bytes) is not a multiple of the physical "
+	    "sector size.", logical_sector_size);
+#if 0				/* linux kernel doesn't check that either */
+    /* ++roman: On Atari, these two fields are often left uninitialized */
+    if (!atari_format && (!b.secs_track || !b.heads))
+	die("Invalid disk format in boot sector.");
+#endif
+    if (verbose)
+	dump_boot(fs, &b, logical_sector_size);
+}
+
+static void write_boot_label(DOS_FS * fs, char *label)
+{
+    if (fs->fat_bits == 12 || fs->fat_bits == 16) {
+	struct boot_sector_16 b16;
+
+	fs_read(0, sizeof(b16), &b16);
+	if (b16.extended_sig != 0x29) {
+	    b16.extended_sig = 0x29;
+	    b16.serial = 0;
+	    memmove(b16.fs_type, fs->fat_bits == 12 ? "FAT12   " : "FAT16   ",
+		    8);
+	}
+	memmove(b16.label, label, 11);
+	fs_write(0, sizeof(b16), &b16);
+    } else if (fs->fat_bits == 32) {
+	struct boot_sector b;
+
+	fs_read(0, sizeof(b), &b);
+	if (b.extended_sig != 0x29) {
+	    b.extended_sig = 0x29;
+	    b.serial = 0;
+	    memmove(b.fs_type, "FAT32   ", 8);
+	}
+	memmove(b.label, label, 11);
+	fs_write(0, sizeof(b), &b);
+	if (fs->backupboot_start)
+	    fs_write(fs->backupboot_start, sizeof(b), &b);
+    }
+}
+
+loff_t find_volume_de(DOS_FS * fs, DIR_ENT * de)
+{
+    uint32_t cluster;
+    loff_t offset;
+    int i;
+
+    if (fs->root_cluster) {
+	for (cluster = fs->root_cluster;
+	     cluster != 0 && cluster != -1;
+	     cluster = next_cluster(fs, cluster)) {
+	    offset = cluster_start(fs, cluster);
+	    for (i = 0; i * sizeof(DIR_ENT) < fs->cluster_size; i++) {
+		fs_read(offset, sizeof(DIR_ENT), de);
+		if (de->attr != VFAT_LN_ATTR && de->attr & ATTR_VOLUME)
+		    return offset;
+		offset += sizeof(DIR_ENT);
+	    }
+	}
+    } else {
+	for (i = 0; i < fs->root_entries; i++) {
+	    offset = fs->root_start + i * sizeof(DIR_ENT);
+	    fs_read(offset, sizeof(DIR_ENT), de);
+	    if (de->attr != VFAT_LN_ATTR && de->attr & ATTR_VOLUME)
+		return offset;
+	}
+    }
+
+    return 0;
+}
+
+static void write_volume_label(DOS_FS * fs, char *label)
+{
+    time_t now = time(NULL);
+    struct tm *mtime = localtime(&now);
+    loff_t offset;
+    int created;
+    DIR_ENT de;
+
+    created = 0;
+    offset = find_volume_de(fs, &de);
+    if (offset == 0) {
+	created = 1;
+	offset = alloc_rootdir_entry(fs, &de, label);
+    }
+    memcpy(de.name, label, 11);
+    de.time = htole16((unsigned short)((mtime->tm_sec >> 1) +
+				       (mtime->tm_min << 5) +
+				       (mtime->tm_hour << 11)));
+    de.date = htole16((unsigned short)(mtime->tm_mday +
+				       ((mtime->tm_mon + 1) << 5) +
+				       ((mtime->tm_year - 80) << 9)));
+    if (created) {
+	de.attr = ATTR_VOLUME;
+	de.ctime_ms = 0;
+	de.ctime = de.time;
+	de.cdate = de.date;
+	de.adate = de.date;
+	de.starthi = 0;
+	de.start = 0;
+	de.size = 0;
+    }
+
+    fs_write(offset, sizeof(DIR_ENT), &de);
+}
+
+void write_label(DOS_FS * fs, char *label)
+{
+    int l = strlen(label);
+
+    while (l < 11)
+	label[l++] = ' ';
+
+    write_boot_label(fs, label);
+    write_volume_label(fs, label);
+}
diff --git a/dosfstools/src/boot.h b/dosfstools/src/boot.h
new file mode 100644
index 0000000..d52e624
--- /dev/null
+++ b/dosfstools/src/boot.h
@@ -0,0 +1,32 @@
+/* boot.h - Read and analyze ia PC/MS-DOS boot sector
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+#ifndef _BOOT_H
+#define _BOOT_H
+
+void read_boot(DOS_FS * fs);
+void write_label(DOS_FS * fs, char *label);
+loff_t find_volume_de(DOS_FS * fs, DIR_ENT * de);
+
+/* Reads the boot sector from the currently open device and initializes *FS */
+
+#endif
diff --git a/dosfstools/src/check.c b/dosfstools/src/check.c
new file mode 100644
index 0000000..add8222
--- /dev/null
+++ b/dosfstools/src/check.c
@@ -0,0 +1,1089 @@
+/* check.c - Check and repair a PC/MS-DOS filesystem
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+/* FAT32, VFAT, Atari format support, and various fixes additions May 1998
+ * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+
+#include "common.h"
+#include "fsck.fat.h"
+#include "io.h"
+#include "fat.h"
+#include "file.h"
+#include "lfn.h"
+#include "check.h"
+
+static DOS_FILE *root;
+
+/* get start field of a dir entry */
+#define FSTART(p,fs) \
+  ((uint32_t)le16toh(p->dir_ent.start) | \
+   (fs->fat_bits == 32 ? le16toh(p->dir_ent.starthi) << 16 : 0))
+
+#define MODIFY(p,i,v)					\
+  do {							\
+    if (p->offset) {					\
+	p->dir_ent.i = v;				\
+	fs_write(p->offset+offsetof(DIR_ENT,i),		\
+		 sizeof(p->dir_ent.i),&p->dir_ent.i);	\
+    }							\
+  } while(0)
+
+#define MODIFY_START(p,v,fs)						\
+  do {									\
+    uint32_t __v = (v);						\
+    if (!p->offset) {							\
+	/* writing to fake entry for FAT32 root dir */			\
+	if (!__v) die("Oops, deleting FAT32 root dir!");		\
+	fs->root_cluster = __v;						\
+	p->dir_ent.start = htole16(__v&0xffff);				\
+	p->dir_ent.starthi = htole16(__v>>16);				\
+	__v = htole32(__v);						\
+	fs_write((loff_t)offsetof(struct boot_sector,root_cluster),	\
+	         sizeof(((struct boot_sector *)0)->root_cluster),	\
+		 &__v);							\
+    }									\
+    else {								\
+	MODIFY(p,start,htole16((__v)&0xffff));				\
+	if (fs->fat_bits == 32)						\
+	    MODIFY(p,starthi,htole16((__v)>>16));			\
+    }									\
+  } while(0)
+
+loff_t alloc_rootdir_entry(DOS_FS * fs, DIR_ENT * de, const char *pattern)
+{
+    static int curr_num = 0;
+    loff_t offset;
+
+    if (fs->root_cluster) {
+	DIR_ENT d2;
+	int i = 0, got = 0;
+	uint32_t clu_num, prev = 0;
+	loff_t offset2;
+
+	clu_num = fs->root_cluster;
+	offset = cluster_start(fs, clu_num);
+	while (clu_num > 0 && clu_num != -1) {
+	    fs_read(offset, sizeof(DIR_ENT), &d2);
+	    if (IS_FREE(d2.name) && d2.attr != VFAT_LN_ATTR) {
+		got = 1;
+		break;
+	    }
+	    i += sizeof(DIR_ENT);
+	    offset += sizeof(DIR_ENT);
+	    if ((i % fs->cluster_size) == 0) {
+		prev = clu_num;
+		if ((clu_num = next_cluster(fs, clu_num)) == 0 || clu_num == -1)
+		    break;
+		offset = cluster_start(fs, clu_num);
+	    }
+	}
+	if (!got) {
+	    /* no free slot, need to extend root dir: alloc next free cluster
+	     * after previous one */
+	    if (!prev)
+		die("Root directory has no cluster allocated!");
+	    for (clu_num = prev + 1; clu_num != prev; clu_num++) {
+		FAT_ENTRY entry;
+
+		if (clu_num >= fs->clusters + 2)
+		    clu_num = 2;
+		get_fat(&entry, fs->fat, clu_num, fs);
+		if (!entry.value)
+		    break;
+	    }
+	    if (clu_num == prev)
+		die("Root directory full and no free cluster");
+	    set_fat(fs, prev, clu_num);
+	    set_fat(fs, clu_num, -1);
+	    set_owner(fs, clu_num, get_owner(fs, fs->root_cluster));
+	    /* clear new cluster */
+	    memset(&d2, 0, sizeof(d2));
+	    offset = cluster_start(fs, clu_num);
+	    for (i = 0; i < fs->cluster_size; i += sizeof(DIR_ENT))
+		fs_write(offset + i, sizeof(d2), &d2);
+	}
+	memset(de, 0, sizeof(DIR_ENT));
+	while (1) {
+	    char expanded[12];
+	    sprintf(expanded, pattern, curr_num);
+	    memcpy(de->name, expanded, 8);
+	    memcpy(de->ext, expanded + 8, 3);
+	    clu_num = fs->root_cluster;
+	    i = 0;
+	    offset2 = cluster_start(fs, clu_num);
+	    while (clu_num > 0 && clu_num != -1) {
+		fs_read(offset2, sizeof(DIR_ENT), &d2);
+		if (offset2 != offset &&
+		    !strncmp((const char *)d2.name, (const char *)de->name,
+			     MSDOS_NAME))
+		    break;
+		i += sizeof(DIR_ENT);
+		offset2 += sizeof(DIR_ENT);
+		if ((i % fs->cluster_size) == 0) {
+		    if ((clu_num = next_cluster(fs, clu_num)) == 0 ||
+			clu_num == -1)
+			break;
+		    offset2 = cluster_start(fs, clu_num);
+		}
+	    }
+	    if (clu_num == 0 || clu_num == -1)
+		break;
+	    if (++curr_num >= 10000)
+		die("Unable to create unique name");
+	}
+    } else {
+	DIR_ENT *root;
+	int next_free = 0, scan;
+
+	root = alloc(fs->root_entries * sizeof(DIR_ENT));
+	fs_read(fs->root_start, fs->root_entries * sizeof(DIR_ENT), root);
+
+	while (next_free < fs->root_entries)
+	    if (IS_FREE(root[next_free].name) &&
+		root[next_free].attr != VFAT_LN_ATTR)
+		break;
+	    else
+		next_free++;
+	if (next_free == fs->root_entries)
+	    die("Root directory is full.");
+	offset = fs->root_start + next_free * sizeof(DIR_ENT);
+	memset(de, 0, sizeof(DIR_ENT));
+	while (1) {
+	    char expanded[12];
+	    sprintf(expanded, pattern, curr_num);
+	    memcpy(de->name, expanded, 8);
+	    memcpy(de->ext, expanded + 8, 3);
+	    for (scan = 0; scan < fs->root_entries; scan++)
+		if (scan != next_free &&
+		    !strncmp((const char *)root[scan].name,
+			     (const char *)de->name, MSDOS_NAME))
+		    break;
+	    if (scan == fs->root_entries)
+		break;
+	    if (++curr_num >= 10000)
+		die("Unable to create unique name");
+	}
+	free(root);
+    }
+    ++n_files;
+    return offset;
+}
+
+/**
+ * Construct a full path (starting with '/') for the specified dentry,
+ * relative to the partition. All components are "long" names where possible.
+ *
+ * @param[in]   file    Information about dentry (file or directory) of interest
+ *
+ * return       Pointer to static string containing file's full path
+ */
+static char *path_name(DOS_FILE * file)
+{
+    static char path[PATH_MAX * 2];
+
+    if (!file)
+	*path = 0;		/* Reached the root directory */
+    else {
+	if (strlen(path_name(file->parent)) > PATH_MAX)
+	    die("Path name too long.");
+	if (strcmp(path, "/") != 0)
+	    strcat(path, "/");
+
+	/* Append the long name to the path,
+	 * or the short name if there isn't a long one
+	 */
+	strcpy(strrchr(path, 0),
+	       file->lfn ? file->lfn : file_name(file->dir_ent.name));
+    }
+    return path;
+}
+
+static const int day_n[] =
+    {   0,  31,  59,  90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0, 0 };
+/*    Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec              */
+
+/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */
+
+static time_t date_dos2unix(unsigned short time, unsigned short date)
+{
+    int month, year;
+    time_t secs;
+
+    month = ((date >> 5) & 15) - 1;
+    if (month < 0) {
+	/* make sure that nothing bad happens if the month bits were zero */
+	month = 0;
+    }
+    year = date >> 9;
+    secs =
+	(time & 31) * 2 + 60 * ((time >> 5) & 63) + (time >> 11) * 3600 +
+	86400 * ((date & 31) - 1 + day_n[month] + (year / 4) + year * 365 -
+		 ((year & 3) == 0 && month < 2 ? 1 : 0) + 3653);
+    /* days since 1.1.70 plus 80's leap day */
+    return secs;
+}
+
+static char *file_stat(DOS_FILE * file)
+{
+    static char temp[100];
+    struct tm *tm;
+    char tmp[100];
+    time_t date;
+
+    date =
+	date_dos2unix(le16toh(file->dir_ent.time), le16toh(file->dir_ent.date));
+    tm = localtime(&date);
+    strftime(tmp, 99, "%H:%M:%S %b %d %Y", tm);
+    sprintf(temp, "  Size %u bytes, date %s", le32toh(file->dir_ent.size), tmp);
+    return temp;
+}
+
+static int bad_name(DOS_FILE * file)
+{
+    int i, spc, suspicious = 0;
+    const char *bad_chars = atari_format ? "*?\\/:" : "*?<>|\"\\/:";
+    const unsigned char *name = file->dir_ent.name;
+    const unsigned char *ext = file->dir_ent.ext;
+
+    /* Do not complain about (and auto-correct) the extended attribute files
+     * of OS/2. */
+    if (strncmp((const char *)name, "EA DATA  SF", 11) == 0 ||
+	strncmp((const char *)name, "WP ROOT  SF", 11) == 0)
+	return 0;
+
+    /* check if we have neither a long filename nor a short name */
+    if ((file->lfn == NULL) && (file->dir_ent.lcase & FAT_NO_83NAME)) {
+	return 1;
+    }
+
+    /* don't complain about the dummy 11 bytes used by patched Linux
+       kernels */
+    if (file->dir_ent.lcase & FAT_NO_83NAME)
+	return 0;
+
+    for (i = 0; i < 8; i++) {
+	if (name[i] < ' ' || name[i] == 0x7f)
+	    return 1;
+	if (name[i] > 0x7f)
+	    ++suspicious;
+	if (strchr(bad_chars, name[i]))
+	    return 1;
+    }
+
+    for (i = 0; i < 3; i++) {
+	if (ext[i] < ' ' || ext[i] == 0x7f)
+	    return 1;
+	if (ext[i] > 0x7f)
+	    ++suspicious;
+	if (strchr(bad_chars, ext[i]))
+	    return 1;
+    }
+
+    spc = 0;
+    for (i = 0; i < 8; i++) {
+	if (name[i] == ' ')
+	    spc = 1;
+	else if (spc)
+	    /* non-space after a space not allowed, space terminates the name
+	     * part */
+	    return 1;
+    }
+
+    spc = 0;
+    for (i = 0; i < 3; i++) {
+	if (ext[i] == ' ')
+	    spc = 1;
+	else if (spc)
+	    /* non-space after a space not allowed, space terminates the ext
+	     * part */
+	    return 1;
+    }
+
+    /* Under GEMDOS, chars >= 128 are never allowed. */
+    if (atari_format && suspicious)
+	return 1;
+
+    /* Under MS-DOS and Windows, chars >= 128 in short names are valid
+     * (but these characters can be visualised differently depending on
+     * local codepage: CP437, CP866, etc). The chars are all basically ok,
+     * so we shouldn't auto-correct such names. */
+    return 0;
+}
+
+static void lfn_remove(loff_t from, loff_t to)
+{
+    DIR_ENT empty;
+
+    /* New dir entry is zeroed except first byte, which is set to 0xe5.
+     * This is to avoid that some FAT-reading OSes (not Linux! ;) stop reading
+     * a directory at the first zero entry...
+     */
+    memset(&empty, 0, sizeof(empty));
+    empty.name[0] = DELETED_FLAG;
+
+    for (; from < to; from += sizeof(empty)) {
+	fs_write(from, sizeof(DIR_ENT), &empty);
+    }
+}
+
+static void drop_file(DOS_FS * fs, DOS_FILE * file)
+{
+    uint32_t cluster;
+
+    MODIFY(file, name[0], DELETED_FLAG);
+    if (file->lfn)
+	lfn_remove(file->lfn_offset, file->offset);
+    for (cluster = FSTART(file, fs); cluster > 0 && cluster <
+	 fs->clusters + 2; cluster = next_cluster(fs, cluster))
+	set_owner(fs, cluster, NULL);
+    --n_files;
+}
+
+static void truncate_file(DOS_FS * fs, DOS_FILE * file, uint32_t clusters)
+{
+    int deleting;
+    uint32_t walk, next;
+
+    walk = FSTART(file, fs);
+    if ((deleting = !clusters))
+	MODIFY_START(file, 0, fs);
+    while (walk > 0 && walk != -1) {
+	next = next_cluster(fs, walk);
+	if (deleting)
+	    set_fat(fs, walk, 0);
+	else if ((deleting = !--clusters))
+	    set_fat(fs, walk, -1);
+	walk = next;
+    }
+}
+
+static void auto_rename(DOS_FILE * file)
+{
+    DOS_FILE *first, *walk;
+    uint32_t number;
+
+    if (!file->offset)
+	return;			/* cannot rename FAT32 root dir */
+    first = file->parent ? file->parent->first : root;
+    number = 0;
+    while (1) {
+	char num[8];
+	sprintf(num, "%07lu", (unsigned long)number);
+	memcpy(file->dir_ent.name, "FSCK", 4);
+	memcpy(file->dir_ent.name + 4, num, 4);
+	memcpy(file->dir_ent.ext, num + 4, 3);
+	for (walk = first; walk; walk = walk->next)
+	    if (walk != file
+		&& !strncmp((const char *)walk->dir_ent.name,
+			    (const char *)file->dir_ent.name, MSDOS_NAME))
+		break;
+	if (!walk) {
+	    if (file->dir_ent.lcase & FAT_NO_83NAME) {
+		/* as we only assign a new 8.3 filename, reset flag that 8.3 name is not
+		   present */
+		file->dir_ent.lcase &= ~FAT_NO_83NAME;
+		/* reset the attributes, only keep DIR and VOLUME */
+		file->dir_ent.attr &= ~(ATTR_DIR | ATTR_VOLUME);
+		fs_write(file->offset, MSDOS_NAME + 2, file->dir_ent.name);
+	    } else {
+		fs_write(file->offset, MSDOS_NAME, file->dir_ent.name);
+	    }
+	    if (file->lfn)
+		lfn_fix_checksum(file->lfn_offset, file->offset,
+				 (const char *)file->dir_ent.name);
+	    return;
+	}
+	number++;
+	if (number > 9999999) {
+	    die("Too many files need repair.");
+	}
+    }
+    die("Can't generate a unique name.");
+}
+
+static void rename_file(DOS_FILE * file)
+{
+    unsigned char name[46];
+    unsigned char *walk, *here;
+
+    if (!file->offset) {
+	printf("Cannot rename FAT32 root dir\n");
+	return;			/* cannot rename FAT32 root dir */
+    }
+    while (1) {
+	printf("New name: ");
+	fflush(stdout);
+	if (fgets((char *)name, 45, stdin)) {
+	    if ((here = (unsigned char *)strchr((const char *)name, '\n')))
+		*here = 0;
+	    for (walk = (unsigned char *)strrchr((const char *)name, 0);
+		 walk >= name && (*walk == ' ' || *walk == '\t'); walk--) ;
+	    walk[1] = 0;
+	    for (walk = name; *walk == ' ' || *walk == '\t'; walk++) ;
+	    if (file_cvt(walk, file->dir_ent.name)) {
+		if (file->dir_ent.lcase & FAT_NO_83NAME) {
+		    /* as we only assign a new 8.3 filename, reset flag that 8.3 name is not
+		       present */
+		    file->dir_ent.lcase &= ~FAT_NO_83NAME;
+		    /* reset the attributes, only keep DIR and VOLUME */
+		    file->dir_ent.attr &= ~(ATTR_DIR | ATTR_VOLUME);
+		    fs_write(file->offset, MSDOS_NAME + 2, file->dir_ent.name);
+		} else {
+		    fs_write(file->offset, MSDOS_NAME, file->dir_ent.name);
+		}
+		if (file->lfn)
+		    lfn_fix_checksum(file->lfn_offset, file->offset,
+				     (const char *)file->dir_ent.name);
+		return;
+	    }
+	}
+    }
+}
+
+static int handle_dot(DOS_FS * fs, DOS_FILE * file, int dots)
+{
+    const char *name;
+
+    name =
+	strncmp((const char *)file->dir_ent.name, MSDOS_DOT,
+		MSDOS_NAME) ? ".." : ".";
+    if (!(file->dir_ent.attr & ATTR_DIR)) {
+	printf("%s\n  Is a non-directory.\n", path_name(file));
+	if (interactive)
+	    printf("1) Drop it\n2) Auto-rename\n3) Rename\n"
+		   "4) Convert to directory\n");
+	else
+	    printf("  Auto-renaming it.\n");
+	switch (interactive ? get_key("1234", "?") : '2') {
+	case '1':
+	    drop_file(fs, file);
+	    return 1;
+	case '2':
+	    auto_rename(file);
+	    printf("  Renamed to %s\n", file_name(file->dir_ent.name));
+	    return 0;
+	case '3':
+	    rename_file(file);
+	    return 0;
+	case '4':
+	    MODIFY(file, size, htole32(0));
+	    MODIFY(file, attr, file->dir_ent.attr | ATTR_DIR);
+	    break;
+	}
+    }
+    if (!dots) {
+	printf("Root contains directory \"%s\". Dropping it.\n", name);
+	drop_file(fs, file);
+	return 1;
+    }
+    return 0;
+}
+
+static int check_file(DOS_FS * fs, DOS_FILE * file)
+{
+    DOS_FILE *owner;
+    int restart;
+    uint32_t expect, curr, this, clusters, prev, walk, clusters2;
+
+    if (file->dir_ent.attr & ATTR_DIR) {
+	if (le32toh(file->dir_ent.size)) {
+	    printf("%s\n  Directory has non-zero size. Fixing it.\n",
+		   path_name(file));
+	    MODIFY(file, size, htole32(0));
+	}
+	if (file->parent
+	    && !strncmp((const char *)file->dir_ent.name, MSDOS_DOT,
+			MSDOS_NAME)) {
+	    expect = FSTART(file->parent, fs);
+	    if (FSTART(file, fs) != expect) {
+		printf("%s\n  Start (%lu) does not point to parent (%lu)\n",
+		       path_name(file), (unsigned long)FSTART(file, fs), (long)expect);
+		MODIFY_START(file, expect, fs);
+	    }
+	    return 0;
+	}
+	if (file->parent
+	    && !strncmp((const char *)file->dir_ent.name, MSDOS_DOTDOT,
+			MSDOS_NAME)) {
+	    expect =
+		file->parent->parent ? FSTART(file->parent->parent, fs) : 0;
+	    if (fs->root_cluster && expect == fs->root_cluster)
+		expect = 0;
+	    if (FSTART(file, fs) != expect) {
+		printf("%s\n  Start (%lu) does not point to .. (%lu)\n",
+		       path_name(file), (unsigned long)FSTART(file, fs), (unsigned long)expect);
+		MODIFY_START(file, expect, fs);
+	    }
+	    return 0;
+	}
+	if (FSTART(file, fs) == 0) {
+	    printf("%s\n Start does point to root directory. Deleting dir. \n",
+		   path_name(file));
+	    MODIFY(file, name[0], DELETED_FLAG);
+	    return 0;
+	}
+    }
+    if (FSTART(file, fs) == 1) {
+	printf("%s\n  Bad start cluster 1. Truncating file.\n",
+	       path_name(file));
+	if (!file->offset)
+	    die("Bad FAT32 root directory! (bad start cluster 1)\n");
+	MODIFY_START(file, 0, fs);
+    }
+    if (FSTART(file, fs) >= fs->clusters + 2) {
+	printf
+	    ("%s\n  Start cluster beyond limit (%lu > %lu). Truncating file.\n",
+	     path_name(file), (unsigned long)FSTART(file, fs), (unsigned long)(fs->clusters + 1));
+	if (!file->offset)
+	    die("Bad FAT32 root directory! (start cluster beyond limit: %lu > %lu)\n",
+		(unsigned long)FSTART(file, fs), (unsigned long)(fs->clusters + 1));
+	MODIFY_START(file, 0, fs);
+    }
+    clusters = prev = 0;
+    for (curr = FSTART(file, fs) ? FSTART(file, fs) :
+	 -1; curr != -1; curr = next_cluster(fs, curr)) {
+	FAT_ENTRY curEntry;
+	get_fat(&curEntry, fs->fat, curr, fs);
+
+	if (!curEntry.value || bad_cluster(fs, curr)) {
+	    printf("%s\n  Contains a %s cluster (%lu). Assuming EOF.\n",
+		   path_name(file), curEntry.value ? "bad" : "free", (unsigned long)curr);
+	    if (prev)
+		set_fat(fs, prev, -1);
+	    else if (!file->offset)
+		die("FAT32 root dir starts with a bad cluster!");
+	    else
+		MODIFY_START(file, 0, fs);
+	    break;
+	}
+	if (!(file->dir_ent.attr & ATTR_DIR) && le32toh(file->dir_ent.size) <=
+	    (uint64_t)clusters * fs->cluster_size) {
+	    printf
+		("%s\n  File size is %u bytes, cluster chain length is > %llu "
+		 "bytes.\n  Truncating file to %u bytes.\n", path_name(file),
+		 le32toh(file->dir_ent.size),
+		 (unsigned long long)clusters * fs->cluster_size,
+		 le32toh(file->dir_ent.size));
+	    truncate_file(fs, file, clusters);
+	    break;
+	}
+	if ((owner = get_owner(fs, curr))) {
+	    int do_trunc = 0;
+	    printf("%s  and\n", path_name(owner));
+	    printf("%s\n  share clusters.\n", path_name(file));
+	    clusters2 = 0;
+	    for (walk = FSTART(owner, fs); walk > 0 && walk != -1; walk =
+		 next_cluster(fs, walk))
+		if (walk == curr)
+		    break;
+		else
+		    clusters2++;
+	    restart = file->dir_ent.attr & ATTR_DIR;
+	    if (!owner->offset) {
+		printf("  Truncating second to %llu bytes because first "
+		       "is FAT32 root dir.\n",
+		       (unsigned long long)clusters2 * fs->cluster_size);
+		do_trunc = 2;
+	    } else if (!file->offset) {
+		printf("  Truncating first to %llu bytes because second "
+		       "is FAT32 root dir.\n",
+		       (unsigned long long)clusters * fs->cluster_size);
+		do_trunc = 1;
+	    } else if (interactive)
+		printf("1) Truncate first to %llu bytes%s\n"
+		       "2) Truncate second to %llu bytes\n",
+		       (unsigned long long)clusters * fs->cluster_size,
+		       restart ? " and restart" : "",
+		       (unsigned long long)clusters2 * fs->cluster_size);
+	    else
+		printf("  Truncating second to %llu bytes.\n",
+		       (unsigned long long)clusters2 * fs->cluster_size);
+	    if (do_trunc != 2
+		&& (do_trunc == 1
+		    || (interactive && get_key("12", "?") == '1'))) {
+		prev = 0;
+		clusters = 0;
+		for (this = FSTART(owner, fs); this > 0 && this != -1; this =
+		     next_cluster(fs, this)) {
+		    if (this == curr) {
+			if (prev)
+			    set_fat(fs, prev, -1);
+			else
+			    MODIFY_START(owner, 0, fs);
+			MODIFY(owner, size,
+			       htole32((uint64_t)clusters *
+				       fs->cluster_size));
+			if (restart)
+			    return 1;
+			while (this > 0 && this != -1) {
+			    set_owner(fs, this, NULL);
+			    this = next_cluster(fs, this);
+			}
+			this = curr;
+			break;
+		    }
+		    clusters++;
+		    prev = this;
+		}
+		if (this != curr)
+		    die("Internal error: didn't find cluster %d in chain"
+			" starting at %d", curr, FSTART(owner, fs));
+	    } else {
+		if (prev)
+		    set_fat(fs, prev, -1);
+		else
+		    MODIFY_START(file, 0, fs);
+		break;
+	    }
+	}
+	set_owner(fs, curr, file);
+	clusters++;
+	prev = curr;
+    }
+    if (!(file->dir_ent.attr & ATTR_DIR) && le32toh(file->dir_ent.size) >
+	(uint64_t)clusters * fs->cluster_size) {
+	printf
+	    ("%s\n  File size is %u bytes, cluster chain length is %llu bytes."
+	     "\n  Truncating file to %llu bytes.\n", path_name(file),
+	     le32toh(file->dir_ent.size),
+	     (unsigned long long)clusters * fs->cluster_size,
+	     (unsigned long long)clusters * fs->cluster_size);
+	MODIFY(file, size,
+	       htole32((uint64_t)clusters * fs->cluster_size));
+    }
+    return 0;
+}
+
+static int check_files(DOS_FS * fs, DOS_FILE * start)
+{
+    while (start) {
+	if (check_file(fs, start))
+	    return 1;
+	start = start->next;
+    }
+    return 0;
+}
+
+static int check_dir(DOS_FS * fs, DOS_FILE ** root, int dots)
+{
+    DOS_FILE *parent, **walk, **scan;
+    int dot, dotdot, skip, redo;
+    int good, bad;
+
+    if (!*root)
+	return 0;
+    parent = (*root)->parent;
+    good = bad = 0;
+    for (walk = root; *walk; walk = &(*walk)->next)
+	if (bad_name(*walk))
+	    bad++;
+	else
+	    good++;
+    if (*root && parent && good + bad > 4 && bad > good / 2) {
+	printf("%s\n  Has a large number of bad entries. (%d/%d)\n",
+	       path_name(parent), bad, good + bad);
+	if (!dots)
+	    printf("  Not dropping root directory.\n");
+	else if (!interactive)
+	    printf("  Not dropping it in auto-mode.\n");
+	else if (get_key("yn", "Drop directory ? (y/n)") == 'y') {
+	    truncate_file(fs, parent, 0);
+	    MODIFY(parent, name[0], DELETED_FLAG);
+	    /* buglet: deleted directory stays in the list. */
+	    return 1;
+	}
+    }
+    dot = dotdot = redo = 0;
+    walk = root;
+    while (*walk) {
+	if (!strncmp
+	    ((const char *)((*walk)->dir_ent.name), MSDOS_DOT, MSDOS_NAME)
+	    || !strncmp((const char *)((*walk)->dir_ent.name), MSDOS_DOTDOT,
+			MSDOS_NAME)) {
+	    if (handle_dot(fs, *walk, dots)) {
+		*walk = (*walk)->next;
+		continue;
+	    }
+	    if (!strncmp
+		((const char *)((*walk)->dir_ent.name), MSDOS_DOT, MSDOS_NAME))
+		dot++;
+	    else
+		dotdot++;
+	}
+	if (!((*walk)->dir_ent.attr & ATTR_VOLUME) && bad_name(*walk)) {
+	    puts(path_name(*walk));
+	    printf("  Bad short file name (%s).\n",
+		   file_name((*walk)->dir_ent.name));
+	    if (interactive)
+		printf("1) Drop file\n2) Rename file\n3) Auto-rename\n"
+		       "4) Keep it\n");
+	    else
+		printf("  Auto-renaming it.\n");
+	    switch (interactive ? get_key("1234", "?") : '3') {
+	    case '1':
+		drop_file(fs, *walk);
+		walk = &(*walk)->next;
+		continue;
+	    case '2':
+		rename_file(*walk);
+		redo = 1;
+		break;
+	    case '3':
+		auto_rename(*walk);
+		printf("  Renamed to %s\n", file_name((*walk)->dir_ent.name));
+		break;
+	    case '4':
+		break;
+	    }
+	}
+	/* don't check for duplicates of the volume label */
+	if (!((*walk)->dir_ent.attr & ATTR_VOLUME)) {
+	    scan = &(*walk)->next;
+	    skip = 0;
+	    while (*scan && !skip) {
+		if (!((*scan)->dir_ent.attr & ATTR_VOLUME) &&
+		    !memcmp((*walk)->dir_ent.name, (*scan)->dir_ent.name,
+			    MSDOS_NAME)) {
+		    printf("%s\n  Duplicate directory entry.\n  First  %s\n",
+			   path_name(*walk), file_stat(*walk));
+		    printf("  Second %s\n", file_stat(*scan));
+		    if (interactive)
+			printf
+			    ("1) Drop first\n2) Drop second\n3) Rename first\n"
+			     "4) Rename second\n5) Auto-rename first\n"
+			     "6) Auto-rename second\n");
+		    else
+			printf("  Auto-renaming second.\n");
+		    switch (interactive ? get_key("123456", "?") : '6') {
+		    case '1':
+			drop_file(fs, *walk);
+			*walk = (*walk)->next;
+			skip = 1;
+			break;
+		    case '2':
+			drop_file(fs, *scan);
+			*scan = (*scan)->next;
+			continue;
+		    case '3':
+			rename_file(*walk);
+			printf("  Renamed to %s\n", path_name(*walk));
+			redo = 1;
+			break;
+		    case '4':
+			rename_file(*scan);
+			printf("  Renamed to %s\n", path_name(*walk));
+			redo = 1;
+			break;
+		    case '5':
+			auto_rename(*walk);
+			printf("  Renamed to %s\n",
+			       file_name((*walk)->dir_ent.name));
+			break;
+		    case '6':
+			auto_rename(*scan);
+			printf("  Renamed to %s\n",
+			       file_name((*scan)->dir_ent.name));
+			break;
+		    }
+		}
+		scan = &(*scan)->next;
+	    }
+	    if (skip)
+		continue;
+	}
+	if (!redo)
+	    walk = &(*walk)->next;
+	else {
+	    walk = root;
+	    dot = dotdot = redo = 0;
+	}
+    }
+    if (dots && !dot)
+	printf("%s\n  \".\" is missing. Can't fix this yet.\n",
+	       path_name(parent));
+    if (dots && !dotdot)
+	printf("%s\n  \"..\" is missing. Can't fix this yet.\n",
+	       path_name(parent));
+    return 0;
+}
+
+/**
+ * Check a dentry's cluster chain for bad clusters.
+ * If requested, we verify readability and mark unreadable clusters as bad.
+ *
+ * @param[inout]    fs          Information about the filesystem
+ * @param[in]       file        dentry to check
+ * @param[in]       read_test   Nonzero == verify that dentry's clusters can
+ *                              be read
+ */
+static void test_file(DOS_FS * fs, DOS_FILE * file, int read_test)
+{
+    DOS_FILE *owner;
+    uint32_t walk, prev, clusters, next_clu;
+
+    prev = clusters = 0;
+    for (walk = FSTART(file, fs); walk > 1 && walk < fs->clusters + 2;
+	 walk = next_clu) {
+	next_clu = next_cluster(fs, walk);
+
+	/* In this stage we are checking only for a loop within our own
+	 * cluster chain.
+	 * Cross-linking of clusters is handled in check_file()
+	 */
+	if ((owner = get_owner(fs, walk))) {
+	    if (owner == file) {
+		printf("%s\n  Circular cluster chain. Truncating to %lu "
+		       "cluster%s.\n", path_name(file), (unsigned long)clusters,
+		       clusters == 1 ? "" : "s");
+		if (prev)
+		    set_fat(fs, prev, -1);
+		else if (!file->offset)
+		    die("Bad FAT32 root directory! (bad start cluster)\n");
+		else
+		    MODIFY_START(file, 0, fs);
+	    }
+	    break;
+	}
+	if (bad_cluster(fs, walk))
+	    break;
+	if (read_test) {
+	    if (fs_test(cluster_start(fs, walk), fs->cluster_size)) {
+		prev = walk;
+		clusters++;
+	    } else {
+		printf("%s\n  Cluster %lu (%lu) is unreadable. Skipping it.\n",
+		       path_name(file), (unsigned long)clusters, (unsigned long)walk);
+		if (prev)
+		    set_fat(fs, prev, next_cluster(fs, walk));
+		else
+		    MODIFY_START(file, next_cluster(fs, walk), fs);
+		set_fat(fs, walk, -2);
+	    }
+	}
+	set_owner(fs, walk, file);
+    }
+    /* Revert ownership (for now) */
+    for (walk = FSTART(file, fs); walk > 1 && walk < fs->clusters + 2;
+	 walk = next_cluster(fs, walk))
+	if (bad_cluster(fs, walk))
+	    break;
+	else if (get_owner(fs, walk) == file)
+	    set_owner(fs, walk, NULL);
+	else
+	    break;
+}
+
+static void undelete(DOS_FS * fs, DOS_FILE * file)
+{
+    uint32_t clusters, left, prev, walk;
+
+    clusters = left = (le32toh(file->dir_ent.size) + fs->cluster_size - 1) /
+	fs->cluster_size;
+    prev = 0;
+
+    walk = FSTART(file, fs);
+
+    while (left && (walk >= 2) && (walk < fs->clusters + 2)) {
+
+	FAT_ENTRY curEntry;
+	get_fat(&curEntry, fs->fat, walk, fs);
+
+	if (!curEntry.value)
+	    break;
+
+	left--;
+	if (prev)
+	    set_fat(fs, prev, walk);
+	prev = walk;
+	walk++;
+    }
+    if (prev)
+	set_fat(fs, prev, -1);
+    else
+	MODIFY_START(file, 0, fs);
+    if (left)
+	printf("Warning: Did only undelete %lu of %lu cluster%s.\n",
+	       (unsigned long)clusters - left, (unsigned long)clusters, clusters == 1 ? "" : "s");
+
+}
+
+static void new_dir(void)
+{
+    lfn_reset();
+}
+
+/**
+ * Create a description for a referenced dentry and insert it in our dentry
+ * tree. Then, go check the dentry's cluster chain for bad clusters and
+ * cluster loops.
+ *
+ * @param[inout]    fs      Information about the filesystem
+ * @param[out]      chain
+ * @param[in]       parent  Information about parent directory of this file
+ *                          NULL == no parent ('file' is root directory)
+ * @param[in]       offset  Partition-relative byte offset of directory entry of interest
+ *                          0 == Root directory
+ * @param           cp
+ */
+static void add_file(DOS_FS * fs, DOS_FILE *** chain, DOS_FILE * parent,
+		     loff_t offset, FDSC ** cp)
+{
+    DOS_FILE *new;
+    DIR_ENT de;
+    FD_TYPE type;
+
+    if (offset)
+	fs_read(offset, sizeof(DIR_ENT), &de);
+    else {
+	/* Construct a DIR_ENT for the root directory */
+	memset(&de, 0, sizeof de);
+	memcpy(de.name, "           ", MSDOS_NAME);
+	de.attr = ATTR_DIR;
+	de.start = htole16(fs->root_cluster & 0xffff);
+	de.starthi = htole16((fs->root_cluster >> 16) & 0xffff);
+    }
+    if ((type = file_type(cp, (char *)de.name)) != fdt_none) {
+	if (type == fdt_undelete && (de.attr & ATTR_DIR))
+	    die("Can't undelete directories.");
+	file_modify(cp, (char *)de.name);
+	fs_write(offset, 1, &de);
+    }
+    if (IS_FREE(de.name)) {
+	lfn_check_orphaned();
+	return;
+    }
+    if (de.attr == VFAT_LN_ATTR) {
+	lfn_add_slot(&de, offset);
+	return;
+    }
+    new = qalloc(&mem_queue, sizeof(DOS_FILE));
+    new->lfn = lfn_get(&de, &new->lfn_offset);
+    new->offset = offset;
+    memcpy(&new->dir_ent, &de, sizeof(de));
+    new->next = new->first = NULL;
+    new->parent = parent;
+    if (type == fdt_undelete)
+	undelete(fs, new);
+    **chain = new;
+    *chain = &new->next;
+    if (list) {
+	printf("Checking file %s", path_name(new));
+	if (new->lfn)
+	    printf(" (%s)", file_name(new->dir_ent.name));	/* (8.3) */
+	printf("\n");
+    }
+    /* Don't include root directory, '.', or '..' in the total file count */
+    if (offset &&
+	strncmp((const char *)de.name, MSDOS_DOT, MSDOS_NAME) != 0 &&
+	strncmp((const char *)de.name, MSDOS_DOTDOT, MSDOS_NAME) != 0)
+	++n_files;
+    test_file(fs, new, test);	/* Bad cluster check */
+}
+
+static int subdirs(DOS_FS * fs, DOS_FILE * parent, FDSC ** cp);
+
+static int scan_dir(DOS_FS * fs, DOS_FILE * this, FDSC ** cp)
+{
+    DOS_FILE **chain;
+    int i;
+    uint32_t clu_num;
+
+    chain = &this->first;
+    i = 0;
+    clu_num = FSTART(this, fs);
+    new_dir();
+    while (clu_num > 0 && clu_num != -1) {
+	add_file(fs, &chain, this,
+		 cluster_start(fs, clu_num) + (i % fs->cluster_size), cp);
+	i += sizeof(DIR_ENT);
+	if (!(i % fs->cluster_size))
+	    if ((clu_num = next_cluster(fs, clu_num)) == 0 || clu_num == -1)
+		break;
+    }
+    lfn_check_orphaned();
+    if (check_dir(fs, &this->first, this->offset))
+	return 0;
+    if (check_files(fs, this->first))
+	return 1;
+    return subdirs(fs, this, cp);
+}
+
+/**
+ * Recursively scan subdirectories of the specified parent directory.
+ *
+ * @param[inout]    fs      Information about the filesystem
+ * @param[in]       parent  Identifies the directory to scan
+ * @param[in]       cp
+ *
+ * @return  0   Success
+ * @return  1   Error
+ */
+static int subdirs(DOS_FS * fs, DOS_FILE * parent, FDSC ** cp)
+{
+    DOS_FILE *walk;
+
+    for (walk = parent ? parent->first : root; walk; walk = walk->next)
+	if (walk->dir_ent.attr & ATTR_DIR)
+	    if (strncmp((const char *)walk->dir_ent.name, MSDOS_DOT, MSDOS_NAME)
+		&& strncmp((const char *)walk->dir_ent.name, MSDOS_DOTDOT,
+			   MSDOS_NAME))
+		if (scan_dir(fs, walk, file_cd(cp, (char *)walk->dir_ent.name)))
+		    return 1;
+    return 0;
+}
+
+/**
+ * Scan all directory and file information for errors.
+ *
+ * @param[inout]    fs      Information about the filesystem
+ *
+ * @return  0   Success
+ * @return  1   Error
+ */
+int scan_root(DOS_FS * fs)
+{
+    DOS_FILE **chain;
+    int i;
+
+    root = NULL;
+    chain = &root;
+    new_dir();
+    if (fs->root_cluster) {
+	add_file(fs, &chain, NULL, 0, &fp_root);
+    } else {
+	for (i = 0; i < fs->root_entries; i++)
+	    add_file(fs, &chain, NULL, fs->root_start + i * sizeof(DIR_ENT),
+		     &fp_root);
+    }
+    lfn_check_orphaned();
+    (void)check_dir(fs, &root, 0);
+    if (check_files(fs, root))
+	return 1;
+    return subdirs(fs, NULL, &fp_root);
+}
diff --git a/dosfstools/src/check.h b/dosfstools/src/check.h
new file mode 100644
index 0000000..fcb6bea
--- /dev/null
+++ b/dosfstools/src/check.h
@@ -0,0 +1,40 @@
+/* check.h - Check and repair a PC/MS-DOS filesystem
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+#ifndef _CHECK_H
+#define _CHECK_H
+
+loff_t alloc_rootdir_entry(DOS_FS * fs, DIR_ENT * de, const char *pattern);
+
+/* Allocate a free slot in the root directory for a new file. The file name is
+   constructed after 'pattern', which must include a %d type format for printf
+   and expand to exactly 11 characters. The name actually used is written into
+   the 'de' structure, the rest of *de is cleared. The offset returned is to
+   where in the filesystem the entry belongs. */
+
+int scan_root(DOS_FS * fs);
+
+/* Scans the root directory and recurses into all subdirectories. See check.c
+   for all the details. Returns a non-zero integer if the filesystem has to
+   be checked again. */
+
+#endif
diff --git a/dosfstools/src/common.c b/dosfstools/src/common.c
new file mode 100644
index 0000000..9d11193
--- /dev/null
+++ b/dosfstools/src/common.c
@@ -0,0 +1,119 @@
+/* common.c - Common functions
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+/* FAT32, VFAT, Atari format support, and various fixes additions May 1998
+ * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include "common.h"
+
+typedef struct _link {
+    void *data;
+    struct _link *next;
+} LINK;
+
+void die(const char *msg, ...)
+{
+    va_list args;
+
+    va_start(args, msg);
+    vfprintf(stderr, msg, args);
+    va_end(args);
+    fprintf(stderr, "\n");
+    exit(1);
+}
+
+void pdie(const char *msg, ...)
+{
+    va_list args;
+
+    va_start(args, msg);
+    vfprintf(stderr, msg, args);
+    va_end(args);
+    fprintf(stderr, ":%s\n", strerror(errno));
+    exit(1);
+}
+
+void *alloc(int size)
+{
+    void *this;
+
+    if ((this = malloc(size)))
+	return this;
+    pdie("malloc");
+    return NULL;		/* for GCC */
+}
+
+void *qalloc(void **root, int size)
+{
+    LINK *link;
+
+    link = alloc(sizeof(LINK));
+    link->next = *root;
+    *root = link;
+    return link->data = alloc(size);
+}
+
+void qfree(void **root)
+{
+    LINK *this;
+
+    while (*root) {
+	this = (LINK *) * root;
+	*root = this->next;
+	free(this->data);
+	free(this);
+    }
+}
+
+int min(int a, int b)
+{
+    return a < b ? a : b;
+}
+
+char get_key(const char *valid, const char *prompt)
+{
+    int ch, okay;
+
+    while (1) {
+	if (prompt)
+	    printf("%s ", prompt);
+	fflush(stdout);
+	while (ch = getchar(), ch == ' ' || ch == '\t') ;
+	if (ch == EOF)
+	    exit(1);
+	if (!strchr(valid, okay = ch))
+	    okay = 0;
+	while (ch = getchar(), ch != '\n' && ch != EOF) ;
+	if (ch == EOF)
+	    exit(1);
+	if (okay)
+	    return okay;
+	printf("Invalid input.\n");
+    }
+}
diff --git a/dosfstools/src/common.h b/dosfstools/src/common.h
new file mode 100644
index 0000000..c15efb5
--- /dev/null
+++ b/dosfstools/src/common.h
@@ -0,0 +1,56 @@
+/* common.h - Common functions
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+#ifndef _COMMON_H
+#define _COMMON_H
+
+void die(const char *msg, ...) __attribute((noreturn));
+
+/* Displays a prinf-style message and terminates the program. */
+
+void pdie(const char *msg, ...) __attribute((noreturn));
+
+/* Like die, but appends an error message according to the state of errno. */
+
+void *alloc(int size);
+
+/* mallocs SIZE bytes and returns a pointer to the data. Terminates the program
+   if malloc fails. */
+
+void *qalloc(void **root, int size);
+
+/* Like alloc, but registers the data area in a list described by ROOT. */
+
+void qfree(void **root);
+
+/* Deallocates all qalloc'ed data areas described by ROOT. */
+
+int min(int a, int b);
+
+/* Returns the smaller integer value of a and b. */
+
+char get_key(const char *valid, const char *prompt);
+
+/* Displays PROMPT and waits for user input. Only characters in VALID are
+   accepted. Terminates the program on EOF. Returns the character. */
+
+#endif
diff --git a/dosfstools/src/fat.c b/dosfstools/src/fat.c
new file mode 100644
index 0000000..5a92f56
--- /dev/null
+++ b/dosfstools/src/fat.c
@@ -0,0 +1,558 @@
+/* fat.c - Read/write access to the FAT
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+/* FAT32, VFAT, Atari format support, and various fixes additions May 1998
+ * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "fsck.fat.h"
+#include "io.h"
+#include "check.h"
+#include "fat.h"
+
+/**
+ * Fetch the FAT entry for a specified cluster.
+ *
+ * @param[out]  entry	    Cluster to which cluster of interest is linked
+ * @param[in]	fat	    FAT table for the partition
+ * @param[in]	cluster     Cluster of interest
+ * @param[in]	fs          Information from the FAT boot sectors (bits per FAT entry)
+ */
+void get_fat(FAT_ENTRY * entry, void *fat, uint32_t cluster, DOS_FS * fs)
+{
+    unsigned char *ptr;
+
+    switch (fs->fat_bits) {
+    case 12:
+	ptr = &((unsigned char *)fat)[cluster * 3 / 2];
+	entry->value = 0xfff & (cluster & 1 ? (ptr[0] >> 4) | (ptr[1] << 4) :
+				(ptr[0] | ptr[1] << 8));
+	break;
+    case 16:
+	entry->value = le16toh(((unsigned short *)fat)[cluster]);
+	break;
+    case 32:
+	/* According to M$, the high 4 bits of a FAT32 entry are reserved and
+	 * are not part of the cluster number. So we cut them off. */
+	{
+	    uint32_t e = le32toh(((unsigned int *)fat)[cluster]);
+	    entry->value = e & 0xfffffff;
+	    entry->reserved = e >> 28;
+	}
+	break;
+    default:
+	die("Bad FAT entry size: %d bits.", fs->fat_bits);
+    }
+}
+
+/**
+ * Build a bookkeeping structure from the partition's FAT table.
+ * If the partition has multiple FATs and they don't agree, try to pick a winner,
+ * and queue a command to overwrite the loser.
+ * One error that is fixed here is a cluster that links to something out of range.
+ *
+ * @param[inout]    fs      Information about the filesystem
+ */
+void read_fat(DOS_FS * fs)
+{
+    int eff_size, alloc_size;
+    uint32_t i;
+    void *first, *second = NULL;
+    int first_ok, second_ok;
+    uint32_t total_num_clusters;
+
+    /* Clean up from previous pass */
+    if (fs->fat)
+	free(fs->fat);
+    if (fs->cluster_owner)
+	free(fs->cluster_owner);
+    fs->fat = NULL;
+    fs->cluster_owner = NULL;
+
+    total_num_clusters = fs->clusters + 2UL;
+    eff_size = (total_num_clusters * fs->fat_bits + 7) / 8ULL;
+
+    if (fs->fat_bits != 12)
+	    alloc_size = eff_size;
+    else
+	    /* round up to an even number of FAT entries to avoid special
+	     * casing the last entry in get_fat() */
+	    alloc_size = (total_num_clusters * 12 + 23) / 24 * 3;
+
+    first = alloc(alloc_size);
+    fs_read(fs->fat_start, eff_size, first);
+    if (fs->nfats > 1) {
+	second = alloc(alloc_size);
+	fs_read(fs->fat_start + fs->fat_size, eff_size, second);
+    }
+    if (second && memcmp(first, second, eff_size) != 0) {
+	FAT_ENTRY first_media, second_media;
+	get_fat(&first_media, first, 0, fs);
+	get_fat(&second_media, second, 0, fs);
+	first_ok = (first_media.value & FAT_EXTD(fs)) == FAT_EXTD(fs);
+	second_ok = (second_media.value & FAT_EXTD(fs)) == FAT_EXTD(fs);
+	if (first_ok && !second_ok) {
+	    printf("FATs differ - using first FAT.\n");
+	    fs_write(fs->fat_start + fs->fat_size, eff_size, first);
+	}
+	if (!first_ok && second_ok) {
+	    printf("FATs differ - using second FAT.\n");
+	    fs_write(fs->fat_start, eff_size, second);
+	    memcpy(first, second, eff_size);
+	}
+	if (first_ok && second_ok) {
+	    if (interactive) {
+		printf("FATs differ but appear to be intact. Use which FAT ?\n"
+		       "1) Use first FAT\n2) Use second FAT\n");
+		if (get_key("12", "?") == '1') {
+		    fs_write(fs->fat_start + fs->fat_size, eff_size, first);
+		} else {
+		    fs_write(fs->fat_start, eff_size, second);
+		    memcpy(first, second, eff_size);
+		}
+	    } else {
+		printf("FATs differ but appear to be intact. Using first "
+		       "FAT.\n");
+		fs_write(fs->fat_start + fs->fat_size, eff_size, first);
+	    }
+	}
+	if (!first_ok && !second_ok) {
+	    printf("Both FATs appear to be corrupt. Giving up.\n");
+	    exit(1);
+	}
+    }
+    if (second) {
+	free(second);
+    }
+    fs->fat = (unsigned char *)first;
+
+    fs->cluster_owner = alloc(total_num_clusters * sizeof(DOS_FILE *));
+    memset(fs->cluster_owner, 0, (total_num_clusters * sizeof(DOS_FILE *)));
+
+    /* Truncate any cluster chains that link to something out of range */
+    for (i = 2; i < fs->clusters + 2; i++) {
+	FAT_ENTRY curEntry;
+	get_fat(&curEntry, fs->fat, i, fs);
+	if (curEntry.value == 1) {
+	    printf("Cluster %ld out of range (1). Setting to EOF.\n", (long)(i - 2));
+	    set_fat(fs, i, -1);
+	}
+	if (curEntry.value >= fs->clusters + 2 &&
+	    (curEntry.value < FAT_MIN_BAD(fs))) {
+	    printf("Cluster %ld out of range (%ld > %ld). Setting to EOF.\n",
+		   (long)(i - 2), (long)curEntry.value, (long)(fs->clusters + 2 - 1));
+	    set_fat(fs, i, -1);
+	}
+    }
+}
+
+/**
+ * Update the FAT entry for a specified cluster
+ * (i.e., change the cluster it links to).
+ * Queue a command to write out this change.
+ *
+ * @param[in,out]   fs          Information about the filesystem
+ * @param[in]	    cluster     Cluster to change
+ * @param[in]       new	        Cluster to link to
+ *				Special values:
+ *				   0 == free cluster
+ *				  -1 == end-of-chain
+ *				  -2 == bad cluster
+ */
+void set_fat(DOS_FS * fs, uint32_t cluster, int32_t new)
+{
+    unsigned char *data = NULL;
+    int size;
+    loff_t offs;
+
+    if (new == -1)
+	new = FAT_EOF(fs);
+    else if ((long)new == -2)
+	new = FAT_BAD(fs);
+    switch (fs->fat_bits) {
+    case 12:
+	data = fs->fat + cluster * 3 / 2;
+	offs = fs->fat_start + cluster * 3 / 2;
+	if (cluster & 1) {
+	    FAT_ENTRY prevEntry;
+	    get_fat(&prevEntry, fs->fat, cluster - 1, fs);
+	    data[0] = ((new & 0xf) << 4) | (prevEntry.value >> 8);
+	    data[1] = new >> 4;
+	} else {
+	    FAT_ENTRY subseqEntry;
+	    get_fat(&subseqEntry, fs->fat, cluster + 1, fs);
+	    data[0] = new & 0xff;
+	    data[1] = (new >> 8) | (cluster == fs->clusters - 1 ? 0 :
+				    (0xff & subseqEntry.value) << 4);
+	}
+	size = 2;
+	break;
+    case 16:
+	data = fs->fat + cluster * 2;
+	offs = fs->fat_start + cluster * 2;
+	*(unsigned short *)data = htole16(new);
+	size = 2;
+	break;
+    case 32:
+	{
+	    FAT_ENTRY curEntry;
+	    get_fat(&curEntry, fs->fat, cluster, fs);
+
+	    data = fs->fat + cluster * 4;
+	    offs = fs->fat_start + cluster * 4;
+	    /* According to M$, the high 4 bits of a FAT32 entry are reserved and
+	     * are not part of the cluster number. So we never touch them. */
+	    *(uint32_t *)data = htole32((new & 0xfffffff) |
+					     (curEntry.reserved << 28));
+	    size = 4;
+	}
+	break;
+    default:
+	die("Bad FAT entry size: %d bits.", fs->fat_bits);
+    }
+    fs_write(offs, size, data);
+    if (fs->nfats > 1) {
+	fs_write(offs + fs->fat_size, size, data);
+    }
+}
+
+int bad_cluster(DOS_FS * fs, uint32_t cluster)
+{
+    FAT_ENTRY curEntry;
+    get_fat(&curEntry, fs->fat, cluster, fs);
+
+    return FAT_IS_BAD(fs, curEntry.value);
+}
+
+/**
+ * Get the cluster to which the specified cluster is linked.
+ * If the linked cluster is marked bad, abort.
+ *
+ * @param[in]   fs          Information about the filesystem
+ * @param[in]	cluster     Cluster to follow
+ *
+ * @return  -1              'cluster' is at the end of the chain
+ * @return  Other values    Next cluster in this chain
+ */
+uint32_t next_cluster(DOS_FS * fs, uint32_t cluster)
+{
+    uint32_t value;
+    FAT_ENTRY curEntry;
+
+    get_fat(&curEntry, fs->fat, cluster, fs);
+
+    value = curEntry.value;
+    if (FAT_IS_BAD(fs, value))
+	die("Internal error: next_cluster on bad cluster");
+    return FAT_IS_EOF(fs, value) ? -1 : value;
+}
+
+loff_t cluster_start(DOS_FS * fs, uint32_t cluster)
+{
+    return fs->data_start + ((loff_t) cluster -
+			     2) * (uint64_t)fs->cluster_size;
+}
+
+/**
+ * Update internal bookkeeping to show that the specified cluster belongs
+ * to the specified dentry.
+ *
+ * @param[in,out]   fs          Information about the filesystem
+ * @param[in]	    cluster     Cluster being assigned
+ * @param[in]	    owner       Information on dentry that owns this cluster
+ *                              (may be NULL)
+ */
+void set_owner(DOS_FS * fs, uint32_t cluster, DOS_FILE * owner)
+{
+    if (fs->cluster_owner == NULL)
+	die("Internal error: attempt to set owner in non-existent table");
+
+    if (owner && fs->cluster_owner[cluster]
+	&& (fs->cluster_owner[cluster] != owner))
+	die("Internal error: attempt to change file owner");
+    fs->cluster_owner[cluster] = owner;
+}
+
+DOS_FILE *get_owner(DOS_FS * fs, uint32_t cluster)
+{
+    if (fs->cluster_owner == NULL)
+	return NULL;
+    else
+	return fs->cluster_owner[cluster];
+}
+
+void fix_bad(DOS_FS * fs)
+{
+    uint32_t i;
+
+    if (verbose)
+	printf("Checking for bad clusters.\n");
+    for (i = 2; i < fs->clusters + 2; i++) {
+	FAT_ENTRY curEntry;
+	get_fat(&curEntry, fs->fat, i, fs);
+
+	if (!get_owner(fs, i) && !FAT_IS_BAD(fs, curEntry.value))
+	    if (!fs_test(cluster_start(fs, i), fs->cluster_size)) {
+		printf("Cluster %lu is unreadable.\n", (unsigned long)i);
+		set_fat(fs, i, -2);
+	    }
+    }
+}
+
+void reclaim_free(DOS_FS * fs)
+{
+    int reclaimed;
+    uint32_t i;
+
+    if (verbose)
+	printf("Checking for unused clusters.\n");
+    reclaimed = 0;
+    for (i = 2; i < fs->clusters + 2; i++) {
+	FAT_ENTRY curEntry;
+	get_fat(&curEntry, fs->fat, i, fs);
+
+	if (!get_owner(fs, i) && curEntry.value &&
+	    !FAT_IS_BAD(fs, curEntry.value)) {
+	    set_fat(fs, i, 0);
+	    reclaimed++;
+	}
+    }
+    if (reclaimed)
+	printf("Reclaimed %d unused cluster%s (%llu bytes).\n", (int)reclaimed,
+	       reclaimed == 1 ? "" : "s",
+	       (unsigned long long)reclaimed * fs->cluster_size);
+}
+
+/**
+ * Assign the specified owner to all orphan chains (except cycles).
+ * Break cross-links between orphan chains.
+ *
+ * @param[in,out]   fs             Information about the filesystem
+ * @param[in]	    owner          dentry to be assigned ownership of orphans
+ * @param[in,out]   num_refs	   For each orphan cluster [index], how many
+ *				   clusters link to it.
+ * @param[in]	    start_cluster  Where to start scanning for orphans
+ */
+static void tag_free(DOS_FS * fs, DOS_FILE * owner, uint32_t *num_refs,
+		     uint32_t start_cluster)
+{
+    int prev;
+    uint32_t i, walk;
+
+    if (start_cluster == 0)
+	start_cluster = 2;
+
+    for (i = start_cluster; i < fs->clusters + 2; i++) {
+	FAT_ENTRY curEntry;
+	get_fat(&curEntry, fs->fat, i, fs);
+
+	/* If the current entry is the head of an un-owned chain... */
+	if (curEntry.value && !FAT_IS_BAD(fs, curEntry.value) &&
+	    !get_owner(fs, i) && !num_refs[i]) {
+	    prev = 0;
+	    /* Walk the chain, claiming ownership as we go */
+	    for (walk = i; walk != -1; walk = next_cluster(fs, walk)) {
+		if (!get_owner(fs, walk)) {
+		    set_owner(fs, walk, owner);
+		} else {
+		    /* We've run into cross-links between orphaned chains,
+		     * or a cycle with a tail.
+		     * Terminate this orphan chain (break the link)
+		     */
+		    set_fat(fs, prev, -1);
+
+		    /* This is not necessary because 'walk' is owned and thus
+		     * will never become the head of a chain (the only case
+		     * that would matter during reclaim to files).
+		     * It's easier to decrement than to prove that it's
+		     * unnecessary.
+		     */
+		    num_refs[walk]--;
+		    break;
+		}
+		prev = walk;
+	    }
+	}
+    }
+}
+
+/**
+ * Recover orphan chains to files, handling any cycles or cross-links.
+ *
+ * @param[in,out]   fs             Information about the filesystem
+ */
+void reclaim_file(DOS_FS * fs)
+{
+    DOS_FILE orphan;
+    int reclaimed, files;
+    int changed = 0;
+    uint32_t i, next, walk;
+    uint32_t *num_refs = NULL;	/* Only for orphaned clusters */
+    uint32_t total_num_clusters;
+
+    if (verbose)
+	printf("Reclaiming unconnected clusters.\n");
+
+    total_num_clusters = fs->clusters + 2UL;
+    num_refs = alloc(total_num_clusters * sizeof(uint32_t));
+    memset(num_refs, 0, (total_num_clusters * sizeof(uint32_t)));
+
+    /* Guarantee that all orphan chains (except cycles) end cleanly
+     * with an end-of-chain mark.
+     */
+
+    for (i = 2; i < total_num_clusters; i++) {
+	FAT_ENTRY curEntry;
+	get_fat(&curEntry, fs->fat, i, fs);
+
+	next = curEntry.value;
+	if (!get_owner(fs, i) && next && next < fs->clusters + 2) {
+	    /* Cluster is linked, but not owned (orphan) */
+	    FAT_ENTRY nextEntry;
+	    get_fat(&nextEntry, fs->fat, next, fs);
+
+	    /* Mark it end-of-chain if it links into an owned cluster,
+	     * a free cluster, or a bad cluster.
+	     */
+	    if (get_owner(fs, next) || !nextEntry.value ||
+		FAT_IS_BAD(fs, nextEntry.value))
+		set_fat(fs, i, -1);
+	    else
+		num_refs[next]++;
+	}
+    }
+
+    /* Scan until all the orphans are accounted for,
+     * and all cycles and cross-links are broken
+     */
+    do {
+	tag_free(fs, &orphan, num_refs, changed);
+	changed = 0;
+
+	/* Any unaccounted-for orphans must be part of a cycle */
+	for (i = 2; i < total_num_clusters; i++) {
+	    FAT_ENTRY curEntry;
+	    get_fat(&curEntry, fs->fat, i, fs);
+
+	    if (curEntry.value && !FAT_IS_BAD(fs, curEntry.value) &&
+		!get_owner(fs, i)) {
+		if (!num_refs[curEntry.value]--)
+		    die("Internal error: num_refs going below zero");
+		set_fat(fs, i, -1);
+		changed = curEntry.value;
+		printf("Broke cycle at cluster %lu in free chain.\n", (unsigned long)i);
+
+		/* If we've created a new chain head,
+		 * tag_free() can claim it
+		 */
+		if (num_refs[curEntry.value] == 0)
+		    break;
+	    }
+	}
+    }
+    while (changed);
+
+    /* Now we can start recovery */
+    files = reclaimed = 0;
+    for (i = 2; i < total_num_clusters; i++)
+	/* If this cluster is the head of an orphan chain... */
+	if (get_owner(fs, i) == &orphan && !num_refs[i]) {
+	    DIR_ENT de;
+	    loff_t offset;
+	    files++;
+	    offset = alloc_rootdir_entry(fs, &de, "FSCK%04dREC");
+	    de.start = htole16(i & 0xffff);
+	    if (fs->fat_bits == 32)
+		de.starthi = htole16(i >> 16);
+	    for (walk = i; walk > 0 && walk != -1;
+		 walk = next_cluster(fs, walk)) {
+		de.size = htole32(le32toh(de.size) + fs->cluster_size);
+		reclaimed++;
+	    }
+	    fs_write(offset, sizeof(DIR_ENT), &de);
+	}
+    if (reclaimed)
+	printf("Reclaimed %d unused cluster%s (%llu bytes) in %d chain%s.\n",
+	       reclaimed, reclaimed == 1 ? "" : "s",
+	       (unsigned long long)reclaimed * fs->cluster_size, files,
+	       files == 1 ? "" : "s");
+
+    free(num_refs);
+}
+
+uint32_t update_free(DOS_FS * fs)
+{
+    uint32_t i;
+    uint32_t free = 0;
+    int do_set = 0;
+
+    for (i = 2; i < fs->clusters + 2; i++) {
+	FAT_ENTRY curEntry;
+	get_fat(&curEntry, fs->fat, i, fs);
+
+	if (!get_owner(fs, i) && !FAT_IS_BAD(fs, curEntry.value))
+	    ++free;
+    }
+
+    if (!fs->fsinfo_start)
+	return free;
+
+    if (verbose)
+	printf("Checking free cluster summary.\n");
+    if (fs->free_clusters != 0xFFFFFFFF) {
+	if (free != fs->free_clusters) {
+	    printf("Free cluster summary wrong (%ld vs. really %ld)\n",
+		   (long)fs->free_clusters, (long)free);
+	    if (interactive)
+		printf("1) Correct\n2) Don't correct\n");
+	    else
+		printf("  Auto-correcting.\n");
+	    if (!interactive || get_key("12", "?") == '1')
+		do_set = 1;
+	}
+    } else {
+	printf("Free cluster summary uninitialized (should be %ld)\n", (long)free);
+	if (rw) {
+	    if (interactive)
+		printf("1) Set it\n2) Leave it uninitialized\n");
+	    else
+		printf("  Auto-setting.\n");
+	    if (!interactive || get_key("12", "?") == '1')
+		do_set = 1;
+	}
+    }
+
+    if (do_set) {
+	uint32_t le_free = htole32(free);
+	fs->free_clusters = free;
+	fs_write(fs->fsinfo_start + offsetof(struct info_sector, free_clusters),
+		 sizeof(le_free), &le_free);
+    }
+
+    return free;
+}
diff --git a/dosfstools/src/fat.h b/dosfstools/src/fat.h
new file mode 100644
index 0000000..b50ed4a
--- /dev/null
+++ b/dosfstools/src/fat.h
@@ -0,0 +1,85 @@
+/* fat.h - Read/write access to the FAT
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   THe complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+#ifndef _FAT_H
+#define _FAT_H
+
+void read_fat(DOS_FS * fs);
+
+/* Loads the FAT of the filesystem described by FS. Initializes the FAT,
+   replaces broken FATs and rejects invalid cluster entries. */
+
+void get_fat(FAT_ENTRY * entry, void *fat, uint32_t cluster, DOS_FS * fs);
+
+/* Retrieve the FAT entry (next chained cluster) for CLUSTER. */
+
+void set_fat(DOS_FS * fs, uint32_t cluster, int32_t new);
+
+/* Changes the value of the CLUSTERth cluster of the FAT of FS to NEW. Special
+   values of NEW are -1 (EOF, 0xff8 or 0xfff8) and -2 (bad sector, 0xff7 or
+   0xfff7) */
+
+int bad_cluster(DOS_FS * fs, uint32_t cluster);
+
+/* Returns a non-zero integer if the CLUSTERth cluster is marked as bad or zero
+   otherwise. */
+
+uint32_t next_cluster(DOS_FS * fs, uint32_t cluster);
+
+/* Returns the number of the cluster following CLUSTER, or -1 if this is the
+   last cluster of the respective cluster chain. CLUSTER must not be a bad
+   cluster. */
+
+loff_t cluster_start(DOS_FS * fs, uint32_t cluster);
+
+/* Returns the byte offset of CLUSTER, relative to the respective device. */
+
+void set_owner(DOS_FS * fs, uint32_t cluster, DOS_FILE * owner);
+
+/* Sets the owner pointer of the respective cluster to OWNER. If OWNER was NULL
+   before, it can be set to NULL or any non-NULL value. Otherwise, only NULL is
+   accepted as the new value. */
+
+DOS_FILE *get_owner(DOS_FS * fs, uint32_t cluster);
+
+/* Returns the owner of the repective cluster or NULL if the cluster has no
+   owner. */
+
+void fix_bad(DOS_FS * fs);
+
+/* Scans the disk for currently unused bad clusters and marks them as bad. */
+
+void reclaim_free(DOS_FS * fs);
+
+/* Marks all allocated, but unused clusters as free. */
+
+void reclaim_file(DOS_FS * fs);
+
+/* Scans the FAT for chains of allocated, but unused clusters and creates files
+   for them in the root directory. Also tries to fix all inconsistencies (e.g.
+   loops, shared clusters, etc.) in the process. */
+
+uint32_t update_free(DOS_FS * fs);
+
+/* Updates free cluster count in FSINFO sector. */
+
+#endif
diff --git a/dosfstools/src/fatlabel.c b/dosfstools/src/fatlabel.c
new file mode 100644
index 0000000..1484ba5
--- /dev/null
+++ b/dosfstools/src/fatlabel.c
@@ -0,0 +1,144 @@
+/* fatlabel.c - User interface
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2007 Red Hat, Inc.
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+#include "version.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <ctype.h>
+
+#include "common.h"
+#include "fsck.fat.h"
+#include "io.h"
+#include "boot.h"
+#include "fat.h"
+#include "file.h"
+#include "check.h"
+
+int interactive = 0, rw = 0, list = 0, test = 0, verbose = 0, write_immed = 0;
+int atari_format = 0;
+unsigned n_files = 0;
+void *mem_queue = NULL;
+
+static void usage(int error)
+{
+    FILE *f = error ? stderr : stdout;
+    int status = error ? 1 : 0;
+
+    fprintf(f, "usage: fatlabel device [label]\n");
+    exit(status);
+}
+
+/*
+ * ++roman: On m68k, check if this is an Atari; if yes, turn on Atari variant
+ * of MS-DOS filesystem by default.
+ */
+static void check_atari(void)
+{
+#ifdef __mc68000__
+    FILE *f;
+    char line[128], *p;
+
+    if (!(f = fopen("/proc/hardware", "r"))) {
+	perror("/proc/hardware");
+	return;
+    }
+
+    while (fgets(line, sizeof(line), f)) {
+	if (strncmp(line, "Model:", 6) == 0) {
+	    p = line + 6;
+	    p += strspn(p, " \t");
+	    if (strncmp(p, "Atari ", 6) == 0)
+		atari_format = 1;
+	    break;
+	}
+    }
+    fclose(f);
+#endif
+}
+
+int main(int argc, char *argv[])
+{
+    DOS_FS fs = { 0 };
+    rw = 0;
+
+    int i;
+
+    char *device = NULL;
+    char label[12] = { 0 };
+
+    loff_t offset;
+    DIR_ENT de;
+
+    check_atari();
+
+    if (argc < 2 || argc > 3)
+	usage(1);
+
+    if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
+	usage(0);
+    else if (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version")) {
+	printf("fatlabel " VERSION " (" VERSION_DATE ")\n");
+	exit(0);
+    }
+
+    device = argv[1];
+    if (argc == 3) {
+	strncpy(label, argv[2], 11);
+	if (strlen(argv[2]) > 11) {
+	    fprintf(stderr,
+		    "fatlabel: labels can be no longer than 11 characters\n");
+	    exit(1);
+	}
+	for (i = 0; label[i] && i < 11; i++)
+	    /* don't know if here should be more strict !uppercase(label[i]) */
+	    if (islower(label[i])) {
+		fprintf(stderr,
+			"fatlabel: warning - lowercase labels might not work properly with DOS or Windows\n");
+		break;
+	    }
+	rw = 1;
+    }
+
+    fs_open(device, rw);
+    read_boot(&fs);
+    if (fs.fat_bits == 32)
+	read_fat(&fs);
+    if (!rw) {
+	offset = find_volume_de(&fs, &de);
+	if (offset == 0)
+	    fprintf(stdout, "%s\n", fs.label);
+	else
+	    fprintf(stdout, "%.8s%.3s\n", de.name, de.ext);
+	exit(0);
+    }
+
+    write_label(&fs, label);
+    fs_close(rw);
+    return 0;
+}
diff --git a/dosfstools/src/file.c b/dosfstools/src/file.c
new file mode 100644
index 0000000..dffcec1
--- /dev/null
+++ b/dosfstools/src/file.c
@@ -0,0 +1,276 @@
+/* file.c - Additional file attributes
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+/* FAT32, VFAT, Atari format support, and various fixes additions May 1998
+ * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "file.h"
+#include "msdos_fs.h"
+
+FDSC *fp_root = NULL;
+
+static void put_char(char **p, unsigned char c)
+{
+    if ((c >= ' ' && c < 0x7f) || c >= 0xa0)
+	*(*p)++ = c;
+    else {
+	*(*p)++ = '\\';
+	*(*p)++ = '0' + (c >> 6);
+	*(*p)++ = '0' + ((c >> 3) & 7);
+	*(*p)++ = '0' + (c & 7);
+    }
+}
+
+/**
+ * Construct the "pretty-printed" representation of the name in a short directory entry.
+ *
+ * @param[in]    fixed  Pointer to name[0] of a DIR_ENT
+ *
+ * @return  Pointer to static string containing pretty "8.3" equivalent of the
+ *          name in the directory entry.
+ */
+char *file_name(unsigned char *fixed)
+{
+    static char path[MSDOS_NAME * 4 + 2];
+    char *p;
+    int i, j;
+
+    p = path;
+    for (i = j = 0; i < 8; i++)
+	if (fixed[i] != ' ') {
+	    while (j++ < i)
+		*p++ = ' ';
+	    put_char(&p, fixed[i]);
+	}
+    if (strncmp((const char *)(fixed + 8), "   ", 3)) {
+	*p++ = '.';
+	for (i = j = 0; i < 3; i++)
+	    if (fixed[i + 8] != ' ') {
+		while (j++ < i)
+		    *p++ = ' ';
+		put_char(&p, fixed[i + 8]);
+	    }
+    }
+    *p = 0;
+    return path;
+}
+
+int file_cvt(unsigned char *name, unsigned char *fixed)
+{
+    unsigned char c;
+    int size, ext, cnt;
+
+    size = 8;
+    ext = 0;
+    while (*name) {
+	c = *name;
+	if (c < ' ' || c > 0x7e || strchr("*?<>|\"/", c)) {
+	    printf("Invalid character in name. Use \\ooo for special "
+		   "characters.\n");
+	    return 0;
+	}
+	if (c == '.') {
+	    if (ext) {
+		printf("Duplicate dots in name.\n");
+		return 0;
+	    }
+	    while (size--)
+		*fixed++ = ' ';
+	    size = 3;
+	    ext = 1;
+	    name++;
+	    continue;
+	}
+	if (c == '\\') {
+	    c = 0;
+	    for (cnt = 3; cnt; cnt--) {
+		if (*name < '0' || *name > '7') {
+		    printf("Invalid octal character.\n");
+		    return 0;
+		}
+		c = c * 8 + *name++ - '0';
+	    }
+	    if (cnt < 4) {
+		printf("Expected three octal digits.\n");
+		return 0;
+	    }
+	    name += 3;
+	}
+	if (islower(c))
+	    c = toupper(c);
+	if (size) {
+	    *fixed++ = c;
+	    size--;
+	}
+	name++;
+    }
+    if (*name || size == 8)
+	return 0;
+    if (!ext) {
+	while (size--)
+	    *fixed++ = ' ';
+	size = 3;
+    }
+    while (size--)
+	*fixed++ = ' ';
+    return 1;
+}
+
+void file_add(char *path, FD_TYPE type)
+{
+    FDSC **current, *walk;
+    char name[MSDOS_NAME];
+    char *here;
+
+    current = &fp_root;
+    if (*path != '/')
+	die("%s: Absolute path required.", path);
+    path++;
+    while (1) {
+	if ((here = strchr(path, '/')))
+	    *here = 0;
+	if (!file_cvt((unsigned char *)path, (unsigned char *)name))
+	    exit(2);
+	for (walk = *current; walk; walk = walk->next)
+	    if (!here && (!strncmp(name, walk->name, MSDOS_NAME) || (type ==
+								     fdt_undelete
+								     &&
+								     !strncmp
+								     (name + 1,
+								      walk->name
+								      + 1,
+								      MSDOS_NAME
+								      - 1))))
+		die("Ambiguous name: \"%s\"", path);
+	    else if (here && !strncmp(name, walk->name, MSDOS_NAME))
+		break;
+	if (!walk) {
+	    walk = alloc(sizeof(FDSC));
+	    strncpy(walk->name, name, MSDOS_NAME);
+	    walk->type = here ? fdt_none : type;
+	    walk->first = NULL;
+	    walk->next = *current;
+	    *current = walk;
+	}
+	current = &walk->first;
+	if (!here)
+	    break;
+	*here = '/';
+	path = here + 1;
+    }
+}
+
+FDSC **file_cd(FDSC ** curr, char *fixed)
+{
+    FDSC **walk;
+
+    if (!curr || !*curr)
+	return NULL;
+    for (walk = curr; *walk; walk = &(*walk)->next)
+	if (!strncmp((*walk)->name, fixed, MSDOS_NAME) && (*walk)->first)
+	    return &(*walk)->first;
+    return NULL;
+}
+
+static FDSC **file_find(FDSC ** dir, char *fixed)
+{
+    if (!dir || !*dir)
+	return NULL;
+    if (*(unsigned char *)fixed == DELETED_FLAG) {
+	while (*dir) {
+	    if (!strncmp((*dir)->name + 1, fixed + 1, MSDOS_NAME - 1)
+		&& !(*dir)->first)
+		return dir;
+	    dir = &(*dir)->next;
+	}
+	return NULL;
+    }
+    while (*dir) {
+	if (!strncmp((*dir)->name, fixed, MSDOS_NAME) && !(*dir)->first)
+	    return dir;
+	dir = &(*dir)->next;
+    }
+    return NULL;
+}
+
+/* Returns the attribute of the file FIXED in directory CURR or FDT_NONE if no
+   such file exists or if CURR is NULL. */
+FD_TYPE file_type(FDSC ** curr, char *fixed)
+{
+    FDSC **this;
+
+    if ((this = file_find(curr, fixed)))
+	return (*this)->type;
+    return fdt_none;
+}
+
+void file_modify(FDSC ** curr, char *fixed)
+{
+    FDSC **this, *next;
+
+    if (!(this = file_find(curr, fixed)))
+	die("Internal error: file_find failed");
+    switch ((*this)->type) {
+    case fdt_drop:
+	printf("Dropping %s\n", file_name((unsigned char *)fixed));
+	*(unsigned char *)fixed = DELETED_FLAG;
+	break;
+    case fdt_undelete:
+	*fixed = *(*this)->name;
+	printf("Undeleting %s\n", file_name((unsigned char *)fixed));
+	break;
+    default:
+	die("Internal error: file_modify");
+    }
+    next = (*this)->next;
+    free(*this);
+    *this = next;
+}
+
+static void report_unused(FDSC * this)
+{
+    FDSC *next;
+
+    while (this) {
+	next = this->next;
+	if (this->first)
+	    report_unused(this->first);
+	else if (this->type != fdt_none)
+	    printf("Warning: did not %s file %s\n", this->type == fdt_drop ?
+		   "drop" : "undelete", file_name((unsigned char *)this->name));
+	free(this);
+	this = next;
+    }
+}
+
+void file_unused(void)
+{
+    report_unused(fp_root);
+}
diff --git a/dosfstools/src/file.h b/dosfstools/src/file.h
new file mode 100644
index 0000000..eaaf356
--- /dev/null
+++ b/dosfstools/src/file.h
@@ -0,0 +1,72 @@
+/* file.h - Additional file attributes
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+#ifndef _FILE_H
+#define _FILE_H
+
+#include "msdos_fs.h"
+
+typedef enum { fdt_none, fdt_drop, fdt_undelete } FD_TYPE;
+
+typedef struct _fptr {
+    char name[MSDOS_NAME];
+    FD_TYPE type;
+    struct _fptr *first;	/* first entry */
+    struct _fptr *next;		/* next file in directory */
+} FDSC;
+
+extern FDSC *fp_root;
+
+char *file_name(unsigned char *fixed);
+
+/* Returns a pointer to a pretty-printed representation of a fixed MS-DOS file
+   name. */
+
+int file_cvt(unsigned char *name, unsigned char *fixed);
+
+/* Converts a pretty-printed file name to the fixed MS-DOS format. Returns a
+   non-zero integer on success, zero on failure. */
+
+void file_add(char *path, FD_TYPE type);
+
+/* Define special attributes for a path. TYPE can be either FDT_DROP or
+   FDT_UNDELETE. */
+
+FDSC **file_cd(FDSC ** curr, char *fixed);
+
+/* Returns a pointer to the directory descriptor of the subdirectory FIXED of
+   CURR, or NULL if no such subdirectory exists. */
+
+FD_TYPE file_type(FDSC ** curr, char *fixed);
+
+/* Returns the attribute of the file FIXED in directory CURR or FDT_NONE if no
+   such file exists or if CURR is NULL. */
+
+void file_modify(FDSC ** curr, char *fixed);
+
+/* Performs the necessary operation on the entry of CURR that is named FIXED. */
+
+void file_unused(void);
+
+/* Displays warnings for all unused file attributes. */
+
+#endif
diff --git a/dosfstools/src/fsck.fat.c b/dosfstools/src/fsck.fat.c
new file mode 100644
index 0000000..2bc3dc2
--- /dev/null
+++ b/dosfstools/src/fsck.fat.c
@@ -0,0 +1,215 @@
+/* fsck.fat.c - User interface
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+/* FAT32, VFAT, Atari format support, and various fixes additions May 1998
+ * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
+
+#include "version.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include "common.h"
+#include "fsck.fat.h"
+#include "io.h"
+#include "boot.h"
+#include "fat.h"
+#include "file.h"
+#include "check.h"
+
+int interactive = 0, rw = 0, list = 0, test = 0, verbose = 0, write_immed = 0;
+int atari_format = 0, boot_only = 0;
+unsigned n_files = 0;
+void *mem_queue = NULL;
+
+static void usage(char *name)
+{
+    fprintf(stderr, "usage: %s [-aAbflrtvVwy] [-d path -d ...] "
+	    "[-u path -u ...]\n%15sdevice\n", name, "");
+    fprintf(stderr, "  -a       automatically repair the filesystem\n");
+    fprintf(stderr, "  -A       toggle Atari filesystem format\n");
+    fprintf(stderr, "  -b       make read-only boot sector check\n");
+    fprintf(stderr, "  -d path  drop that file\n");
+    fprintf(stderr, "  -f       salvage unused chains to files\n");
+    fprintf(stderr, "  -l       list path names\n");
+    fprintf(stderr,
+	    "  -n       no-op, check non-interactively without changing\n");
+    fprintf(stderr, "  -p       same as -a, for compat with other *fsck\n");
+    fprintf(stderr, "  -r       interactively repair the filesystem (default)\n");
+    fprintf(stderr, "  -t       test for bad clusters\n");
+    fprintf(stderr, "  -u path  try to undelete that (non-directory) file\n");
+    fprintf(stderr, "  -v       verbose mode\n");
+    fprintf(stderr, "  -V       perform a verification pass\n");
+    fprintf(stderr, "  -w       write changes to disk immediately\n");
+    fprintf(stderr, "  -y       same as -a, for compat with other *fsck\n");
+    exit(2);
+}
+
+/*
+ * ++roman: On m68k, check if this is an Atari; if yes, turn on Atari variant
+ * of MS-DOS filesystem by default.
+ */
+static void check_atari(void)
+{
+#ifdef __mc68000__
+    FILE *f;
+    char line[128], *p;
+
+    if (!(f = fopen("/proc/hardware", "r"))) {
+	perror("/proc/hardware");
+	return;
+    }
+
+    while (fgets(line, sizeof(line), f)) {
+	if (strncmp(line, "Model:", 6) == 0) {
+	    p = line + 6;
+	    p += strspn(p, " \t");
+	    if (strncmp(p, "Atari ", 6) == 0)
+		atari_format = 1;
+	    break;
+	}
+    }
+    fclose(f);
+#endif
+}
+
+int main(int argc, char **argv)
+{
+    DOS_FS fs;
+    int salvage_files, verify, c;
+    uint32_t free_clusters = 0;
+
+    memset(&fs, 0, sizeof(fs));
+    salvage_files = verify = 0;
+    rw = interactive = 1;
+    check_atari();
+
+    while ((c = getopt(argc, argv, "Aad:bflnprtu:vVwy")) != -1)
+	switch (c) {
+	case 'A':		/* toggle Atari format */
+	    atari_format = !atari_format;
+	    break;
+	case 'a':
+	case 'p':
+	case 'y':
+	    rw = 1;
+	    interactive = 0;
+	    salvage_files = 1;
+	    break;
+	case 'b':
+	    rw = 0;
+	    interactive = 0;
+	    boot_only = 1;
+	    break;
+	case 'd':
+	    file_add(optarg, fdt_drop);
+	    break;
+	case 'f':
+	    salvage_files = 1;
+	    break;
+	case 'l':
+	    list = 1;
+	    break;
+	case 'n':
+	    rw = 0;
+	    interactive = 0;
+	    break;
+	case 'r':
+	    rw = 1;
+	    interactive = 1;
+	    break;
+	case 't':
+	    test = 1;
+	    break;
+	case 'u':
+	    file_add(optarg, fdt_undelete);
+	    break;
+	case 'v':
+	    verbose = 1;
+	    break;
+	case 'V':
+	    verify = 1;
+	    break;
+	case 'w':
+	    write_immed = 1;
+	    break;
+	default:
+	    usage(argv[0]);
+	}
+    if ((test || write_immed) && !rw) {
+	fprintf(stderr, "-t and -w can not be used in read only mode\n");
+	exit(2);
+    }
+    if (optind != argc - 1)
+	usage(argv[0]);
+
+    printf("fsck.fat " VERSION " (" VERSION_DATE ")\n");
+    fs_open(argv[optind], rw);
+
+    read_boot(&fs);
+    if (boot_only)
+	goto exit;
+
+    if (verify)
+	printf("Starting check/repair pass.\n");
+    while (read_fat(&fs), scan_root(&fs))
+	qfree(&mem_queue);
+    if (test)
+	fix_bad(&fs);
+    if (salvage_files)
+	reclaim_file(&fs);
+    else
+	reclaim_free(&fs);
+    free_clusters = update_free(&fs);
+    file_unused();
+    qfree(&mem_queue);
+    if (verify) {
+	n_files = 0;
+	printf("Starting verification pass.\n");
+	read_fat(&fs);
+	scan_root(&fs);
+	reclaim_free(&fs);
+	qfree(&mem_queue);
+    }
+
+exit:
+    if (fs_changed()) {
+	if (rw) {
+	    if (interactive)
+		rw = get_key("yn", "Perform changes ? (y/n)") == 'y';
+	    else
+		printf("Performing changes.\n");
+	} else
+	    printf("Leaving filesystem unchanged.\n");
+    }
+
+    if (!boot_only)
+	printf("%s: %u files, %lu/%lu clusters\n", argv[optind],
+	       n_files, (unsigned long)fs.clusters - free_clusters, (unsigned long)fs.clusters);
+
+    return fs_close(rw) ? 1 : 0;
+}
diff --git a/dosfstools/src/fsck.fat.h b/dosfstools/src/fsck.fat.h
new file mode 100644
index 0000000..e5f6178
--- /dev/null
+++ b/dosfstools/src/fsck.fat.h
@@ -0,0 +1,191 @@
+/* fsck.fat.h  -  Common data structures and global variables
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+/* FAT32, VFAT, Atari format support, and various fixes additions May 1998
+ * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
+
+#ifndef _DOSFSCK_H
+#define _DOSFSCK_H
+
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <endian.h>
+
+#include "msdos_fs.h"
+
+#define VFAT_LN_ATTR (ATTR_RO | ATTR_HIDDEN | ATTR_SYS | ATTR_VOLUME)
+
+#define FAT_STATE_DIRTY 0x01
+
+/* ++roman: Use own definition of boot sector structure -- the kernel headers'
+ * name for it is msdos_boot_sector in 2.0 and fat_boot_sector in 2.1 ... */
+struct boot_sector {
+    uint8_t ignored[3];		/* Boot strap short or near jump */
+    uint8_t system_id[8];	/* Name - can be used to special case
+				   partition manager volumes */
+    uint8_t sector_size[2];	/* bytes per logical sector */
+    uint8_t cluster_size;	/* sectors/cluster */
+    uint16_t reserved;		/* reserved sectors */
+    uint8_t fats;		/* number of FATs */
+    uint8_t dir_entries[2];	/* root directory entries */
+    uint8_t sectors[2];		/* number of sectors */
+    uint8_t media;		/* media code (unused) */
+    uint16_t fat_length;	/* sectors/FAT */
+    uint16_t secs_track;	/* sectors per track */
+    uint16_t heads;		/* number of heads */
+    uint32_t hidden;		/* hidden sectors (unused) */
+    uint32_t total_sect;	/* number of sectors (if sectors == 0) */
+
+    /* The following fields are only used by FAT32 */
+    uint32_t fat32_length;	/* sectors/FAT */
+    uint16_t flags;		/* bit 8: fat mirroring, low 4: active fat */
+    uint8_t version[2];		/* major, minor filesystem version */
+    uint32_t root_cluster;	/* first cluster in root directory */
+    uint16_t info_sector;	/* filesystem info sector */
+    uint16_t backup_boot;	/* backup boot sector */
+    uint8_t reserved2[12];	/* Unused */
+
+    uint8_t drive_number;	/* Logical Drive Number */
+    uint8_t reserved3;		/* Unused */
+
+    uint8_t extended_sig;	/* Extended Signature (0x29) */
+    uint32_t serial;		/* Serial number */
+    uint8_t label[11];		/* FS label */
+    uint8_t fs_type[8];		/* FS Type */
+
+    /* fill up to 512 bytes */
+    uint8_t junk[422];
+} __attribute__ ((packed));
+
+struct boot_sector_16 {
+    uint8_t ignored[3];		/* Boot strap short or near jump */
+    uint8_t system_id[8];	/* Name - can be used to special case
+				   partition manager volumes */
+    uint8_t sector_size[2];	/* bytes per logical sector */
+    uint8_t cluster_size;	/* sectors/cluster */
+    uint16_t reserved;		/* reserved sectors */
+    uint8_t fats;		/* number of FATs */
+    uint8_t dir_entries[2];	/* root directory entries */
+    uint8_t sectors[2];		/* number of sectors */
+    uint8_t media;		/* media code (unused) */
+    uint16_t fat_length;	/* sectors/FAT */
+    uint16_t secs_track;	/* sectors per track */
+    uint16_t heads;		/* number of heads */
+    uint32_t hidden;		/* hidden sectors (unused) */
+    uint32_t total_sect;	/* number of sectors (if sectors == 0) */
+
+    uint8_t drive_number;	/* Logical Drive Number */
+    uint8_t reserved2;		/* Unused */
+
+    uint8_t extended_sig;	/* Extended Signature (0x29) */
+    uint32_t serial;		/* Serial number */
+    uint8_t label[11];		/* FS label */
+    uint8_t fs_type[8];		/* FS Type */
+
+    /* fill up to 512 bytes */
+    uint8_t junk[450];
+} __attribute__ ((packed));
+
+struct info_sector {
+    uint32_t magic;		/* Magic for info sector ('RRaA') */
+    uint8_t junk[0x1dc];
+    uint32_t reserved1;		/* Nothing as far as I can tell */
+    uint32_t signature;		/* 0x61417272 ('rrAa') */
+    uint32_t free_clusters;	/* Free cluster count.  -1 if unknown */
+    uint32_t next_cluster;	/* Most recently allocated cluster. */
+    uint32_t reserved2[3];
+    uint16_t reserved3;
+    uint16_t boot_sign;
+};
+
+typedef struct {
+    uint8_t name[8], ext[3];	/* name and extension */
+    uint8_t attr;		/* attribute bits */
+    uint8_t lcase;		/* Case for base and extension */
+    uint8_t ctime_ms;		/* Creation time, milliseconds */
+    uint16_t ctime;		/* Creation time */
+    uint16_t cdate;		/* Creation date */
+    uint16_t adate;		/* Last access date */
+    uint16_t starthi;		/* High 16 bits of cluster in FAT32 */
+    uint16_t time, date, start;	/* time, date and first cluster */
+    uint32_t size;		/* file size (in bytes) */
+} __attribute__ ((packed)) DIR_ENT;
+
+typedef struct _dos_file {
+    DIR_ENT dir_ent;
+    char *lfn;
+    loff_t offset;
+    loff_t lfn_offset;
+    struct _dos_file *parent;	/* parent directory */
+    struct _dos_file *next;	/* next entry */
+    struct _dos_file *first;	/* first entry (directory only) */
+} DOS_FILE;
+
+typedef struct {
+    uint32_t value;
+    uint32_t reserved;
+} FAT_ENTRY;
+
+typedef struct {
+    int nfats;
+    loff_t fat_start;
+    unsigned int fat_size;	/* unit is bytes */
+    unsigned int fat_bits;	/* size of a FAT entry */
+    unsigned int eff_fat_bits;	/* # of used bits in a FAT entry */
+    uint32_t root_cluster;	/* 0 for old-style root dir */
+    loff_t root_start;
+    unsigned int root_entries;
+    loff_t data_start;
+    unsigned int cluster_size;
+    uint32_t clusters;
+    loff_t fsinfo_start;	/* 0 if not present */
+    long free_clusters;
+    loff_t backupboot_start;	/* 0 if not present */
+    unsigned char *fat;
+    DOS_FILE **cluster_owner;
+    char *label;
+} DOS_FS;
+
+extern int interactive, rw, list, verbose, test, write_immed;
+extern int atari_format;
+extern unsigned n_files;
+extern void *mem_queue;
+
+/* value to use as end-of-file marker */
+#define FAT_EOF(fs)	((atari_format ? 0xfff : 0xff8) | FAT_EXTD(fs))
+#define FAT_IS_EOF(fs,v) ((uint32_t)(v) >= (0xff8|FAT_EXTD(fs)))
+/* value to mark bad clusters */
+#define FAT_BAD(fs)	(0xff7 | FAT_EXTD(fs))
+/* range of values used for bad clusters */
+#define FAT_MIN_BAD(fs)	((atari_format ? 0xff0 : 0xff7) | FAT_EXTD(fs))
+#define FAT_MAX_BAD(fs)	((atari_format ? 0xff7 : 0xff7) | FAT_EXTD(fs))
+#define FAT_IS_BAD(fs,v) ((v) >= FAT_MIN_BAD(fs) && (v) <= FAT_MAX_BAD(fs))
+
+/* return -16 as a number with fs->fat_bits bits */
+#define FAT_EXTD(fs)	(((1 << fs->eff_fat_bits)-1) & ~0xf)
+
+/* marker for files with no 8.3 name */
+#define FAT_NO_83NAME 32
+
+#endif
diff --git a/dosfstools/src/io.c b/dosfstools/src/io.c
new file mode 100644
index 0000000..450432c
--- /dev/null
+++ b/dosfstools/src/io.c
@@ -0,0 +1,232 @@
+/* io.c - Virtual disk input/output
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+/*
+ * Thu Feb 26 01:15:36 CET 1998: Martin Schulze <joey@infodrom.north.de>
+ *	Fixed nasty bug that caused every file with a name like
+ *	xxxxxxxx.xxx to be treated as bad name that needed to be fixed.
+ */
+
+/* FAT32, VFAT, Atari format support, and various fixes additions May 1998
+ * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
+
+#define _LARGEFILE64_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fd.h>
+
+#include "fsck.fat.h"
+#include "common.h"
+#include "io.h"
+
+typedef struct _change {
+    void *data;
+    loff_t pos;
+    int size;
+    struct _change *next;
+} CHANGE;
+
+static CHANGE *changes, *last;
+static int fd, did_change = 0;
+
+unsigned device_no;
+
+#ifdef __DJGPP__
+#include "volume.h"		/* DOS lowlevel disk access functions */
+loff_t llseek(int fd, loff_t offset, int whence)
+{
+    if ((whence != SEEK_SET) || (fd == 4711))
+	return -1;		/* only those supported */
+    return VolumeSeek(offset);
+}
+
+#define open OpenVolume
+#define close CloseVolume
+#define read(a,b,c) ReadVolume(b,c)
+#define write(a,b,c) WriteVolume(b,c)
+#else
+loff_t llseek(int fd, loff_t offset, int whence)
+{
+    return (loff_t) lseek64(fd, (off64_t) offset, whence);
+}
+#endif
+
+void fs_open(char *path, int rw)
+{
+    struct stat stbuf;
+
+    if ((fd = open(path, rw ? O_RDWR : O_RDONLY)) < 0) {
+	perror("open");
+	exit(6);
+    }
+    changes = last = NULL;
+    did_change = 0;
+
+#ifndef _DJGPP_
+    if (fstat(fd, &stbuf) < 0)
+	pdie("fstat %s", path);
+    device_no = S_ISBLK(stbuf.st_mode) ? (stbuf.st_rdev >> 8) & 0xff : 0;
+#else
+    if (IsWorkingOnImageFile()) {
+	if (fstat(GetVolumeHandle(), &stbuf) < 0)
+	    pdie("fstat image %s", path);
+	device_no = 0;
+    } else {
+	/* return 2 for floppy, 1 for ramdisk, 7 for loopback  */
+	/* used by boot.c in Atari mode: floppy always FAT12,  */
+	/* loopback / ramdisk only FAT12 if usual floppy size, */
+	/* harddisk always FAT16 on Atari... */
+	device_no = (GetVolumeHandle() < 2) ? 2 : 1;
+	/* telling "floppy" for A:/B:, "ramdisk" for the rest */
+    }
+#endif
+}
+
+/**
+ * Read data from the partition, accounting for any pending updates that are
+ * queued for writing.
+ *
+ * @param[in]   pos     Byte offset, relative to the beginning of the partition,
+ *                      at which to read
+ * @param[in]   size    Number of bytes to read
+ * @param[out]  data    Where to put the data read
+ */
+void fs_read(loff_t pos, int size, void *data)
+{
+    CHANGE *walk;
+    int got;
+
+    if (llseek(fd, pos, 0) != pos)
+	pdie("Seek to %lld", pos);
+    if ((got = read(fd, data, size)) < 0)
+	pdie("Read %d bytes at %lld", size, pos);
+    if (got != size)
+	die("Got %d bytes instead of %d at %lld", got, size, pos);
+    for (walk = changes; walk; walk = walk->next) {
+	if (walk->pos < pos + size && walk->pos + walk->size > pos) {
+	    if (walk->pos < pos)
+		memcpy(data, (char *)walk->data + pos - walk->pos, min(size,
+								       walk->
+								       size -
+								       pos +
+								       walk->
+								       pos));
+	    else
+		memcpy((char *)data + walk->pos - pos, walk->data,
+		       min(walk->size, size + pos - walk->pos));
+	}
+    }
+}
+
+int fs_test(loff_t pos, int size)
+{
+    void *scratch;
+    int okay;
+
+    if (llseek(fd, pos, 0) != pos)
+	pdie("Seek to %lld", pos);
+    scratch = alloc(size);
+    okay = read(fd, scratch, size) == size;
+    free(scratch);
+    return okay;
+}
+
+void fs_write(loff_t pos, int size, void *data)
+{
+    CHANGE *new;
+    int did;
+
+    if (write_immed) {
+	did_change = 1;
+	if (llseek(fd, pos, 0) != pos)
+	    pdie("Seek to %lld", pos);
+	if ((did = write(fd, data, size)) == size)
+	    return;
+	if (did < 0)
+	    pdie("Write %d bytes at %lld", size, pos);
+	die("Wrote %d bytes instead of %d at %lld", did, size, pos);
+    }
+    new = alloc(sizeof(CHANGE));
+    new->pos = pos;
+    memcpy(new->data = alloc(new->size = size), data, size);
+    new->next = NULL;
+    if (last)
+	last->next = new;
+    else
+	changes = new;
+    last = new;
+}
+
+static void fs_flush(void)
+{
+    CHANGE *this;
+    int size;
+
+    while (changes) {
+	this = changes;
+	changes = changes->next;
+	if (llseek(fd, this->pos, 0) != this->pos)
+	    fprintf(stderr,
+		    "Seek to %lld failed: %s\n  Did not write %d bytes.\n",
+		    (long long)this->pos, strerror(errno), this->size);
+	else if ((size = write(fd, this->data, this->size)) < 0)
+	    fprintf(stderr, "Writing %d bytes at %lld failed: %s\n", this->size,
+		    (long long)this->pos, strerror(errno));
+	else if (size != this->size)
+	    fprintf(stderr, "Wrote %d bytes instead of %d bytes at %lld."
+		    "\n", size, this->size, (long long)this->pos);
+	free(this->data);
+	free(this);
+    }
+}
+
+int fs_close(int write)
+{
+    CHANGE *next;
+    int changed;
+
+    changed = ! !changes;
+    if (write)
+	fs_flush();
+    else
+	while (changes) {
+	    next = changes->next;
+	    free(changes->data);
+	    free(changes);
+	    changes = next;
+	}
+    if (close(fd) < 0)
+	pdie("closing filesystem");
+    return changed || did_change;
+}
+
+int fs_changed(void)
+{
+    return ! !changes || did_change;
+}
diff --git a/dosfstools/src/io.h b/dosfstools/src/io.h
new file mode 100644
index 0000000..d23d07e
--- /dev/null
+++ b/dosfstools/src/io.h
@@ -0,0 +1,71 @@
+/* io.h - Virtual disk input/output
+
+   Copyright (C) 1993 Werner Almesberger <werner.almesberger@lrc.di.epfl.ch>
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+/* FAT32, VFAT, Atari format support, and various fixes additions May 1998
+ * by Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de> */
+
+#ifndef _IO_H
+#define _IO_H
+
+#include <fcntl.h>		/* for loff_t */
+
+loff_t llseek(int fd, loff_t offset, int whence);
+
+/* lseek() analogue for large offsets. */
+
+void fs_open(char *path, int rw);
+
+/* Opens the filesystem PATH. If RW is zero, the filesystem is opened
+   read-only, otherwise, it is opened read-write. */
+
+void fs_read(loff_t pos, int size, void *data);
+
+/* Reads SIZE bytes starting at POS into DATA. Performs all applicable
+   changes. */
+
+int fs_test(loff_t pos, int size);
+
+/* Returns a non-zero integer if SIZE bytes starting at POS can be read without
+   errors. Otherwise, it returns zero. */
+
+void fs_write(loff_t pos, int size, void *data);
+
+/* If write_immed is non-zero, SIZE bytes are written from DATA to the disk,
+   starting at POS. If write_immed is zero, the change is added to a list in
+   memory. */
+
+int fs_close(int write);
+
+/* Closes the filesystem, performs all pending changes if WRITE is non-zero
+   and removes the list of changes. Returns a non-zero integer if the file
+   system has been changed since the last fs_open, zero otherwise. */
+
+int fs_changed(void);
+
+/* Determines whether the filesystem has changed. See fs_close. */
+
+extern unsigned device_no;
+
+/* Major number of device (0 if file) and size (in 512 byte sectors) */
+
+#endif
diff --git a/dosfstools/src/lfn.c b/dosfstools/src/lfn.c
new file mode 100644
index 0000000..2601172
--- /dev/null
+++ b/dosfstools/src/lfn.c
@@ -0,0 +1,528 @@
+/* lfn.c - Functions for handling VFAT long filenames
+
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+
+#include "common.h"
+#include "io.h"
+#include "fsck.fat.h"
+#include "lfn.h"
+#include "file.h"
+
+typedef struct {
+    uint8_t id;			/* sequence number for slot */
+    uint8_t name0_4[10];	/* first 5 characters in name */
+    uint8_t attr;		/* attribute byte */
+    uint8_t reserved;		/* always 0 */
+    uint8_t alias_checksum;	/* checksum for 8.3 alias */
+    uint8_t name5_10[12];	/* 6 more characters in name */
+    uint16_t start;		/* starting cluster number, 0 in long slots */
+    uint8_t name11_12[4];	/* last 2 characters in name */
+} LFN_ENT;
+
+#define LFN_ID_START	0x40
+#define LFN_ID_SLOTMASK	0x1f
+
+#define CHARS_PER_LFN	13
+
+/* These modul-global vars represent the state of the LFN parser */
+unsigned char *lfn_unicode = NULL;
+unsigned char lfn_checksum;
+int lfn_slot = -1;
+loff_t *lfn_offsets = NULL;
+int lfn_parts = 0;
+
+static unsigned char fat_uni2esc[64] = {
+    '0', '1', '2', '3', '4', '5', '6', '7',
+    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+    'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+    'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+    'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
+    'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+    'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+    'u', 'v', 'w', 'x', 'y', 'z', '+', '-'
+};
+
+/* This defines which unicode chars are directly convertable to ISO-8859-1 */
+#define UNICODE_CONVERTABLE(cl,ch)	(ch == 0 && (cl < 0x80 || cl >= 0xa0))
+
+/* for maxlen param */
+#define UNTIL_0		INT_MAX
+
+/* Convert name part in 'lfn' from unicode to ASCII */
+#define CNV_THIS_PART(lfn)				\
+    ({							\
+	unsigned char __part_uni[CHARS_PER_LFN*2];		\
+	copy_lfn_part( __part_uni, lfn );		\
+	cnv_unicode( __part_uni, CHARS_PER_LFN, 0 );	\
+    })
+
+/* Convert name parts collected so far (from previous slots) from unicode to
+ * ASCII */
+#define CNV_PARTS_SO_FAR()					\
+	(cnv_unicode( lfn_unicode+(lfn_slot*CHARS_PER_LFN*2),	\
+		      lfn_parts*CHARS_PER_LFN, 0 ))
+
+#define BYTES_TO_WCHAR(cl,ch) ((wchar_t)((unsigned)(cl) + ((unsigned)(ch) << 8)))
+static size_t mbslen(wchar_t x)
+{
+    wchar_t wstr[] = { x, 0 };
+    return wcstombs(NULL, wstr, 0);
+}
+
+static size_t wctombs(char *dest, wchar_t x)
+{
+    wchar_t wstr[] = { x, 0 };
+    size_t size = wcstombs(NULL, wstr, 0);
+    if (size != (size_t) - 1)
+	size = wcstombs(dest, wstr, size + 1);
+    return size;
+}
+
+/* This function converts an unicode string to a normal ASCII string, assuming
+ * ISO-8859-1 charset. Characters not in 8859-1 are converted to the same
+ * escape notation as used by the kernel, i.e. the uuencode-like ":xxx" */
+static char *cnv_unicode(const unsigned char *uni, int maxlen, int use_q)
+{
+    const unsigned char *up;
+    unsigned char *out, *cp;
+    int len, val;
+    size_t x;
+
+    for (len = 0, up = uni; (up - uni) / 2 < maxlen && (up[0] || up[1]);
+	 up += 2) {
+	if ((x = mbslen(BYTES_TO_WCHAR(up[0], up[1]))) != (size_t) - 1)
+	    len += x;
+	else if (UNICODE_CONVERTABLE(up[0], up[1]))
+	    ++len;
+	else
+	    len += 4;
+    }
+    cp = out = use_q ? qalloc(&mem_queue, len + 1) : alloc(len + 1);
+
+    for (up = uni; (up - uni) / 2 < maxlen && (up[0] || up[1]); up += 2) {
+	if ((x =
+	     wctombs((char *)cp, BYTES_TO_WCHAR(up[0], up[1]))) != (size_t) - 1)
+	    cp += x;
+	else if (UNICODE_CONVERTABLE(up[0], up[1]))
+	    *cp++ = up[0];
+	else {
+	    /* here the same escape notation is used as in the Linux kernel */
+	    *cp++ = ':';
+	    val = (up[1] << 8) + up[0];
+	    cp[2] = fat_uni2esc[val & 0x3f];
+	    val >>= 6;
+	    cp[1] = fat_uni2esc[val & 0x3f];
+	    val >>= 6;
+	    cp[0] = fat_uni2esc[val & 0x3f];
+	    cp += 3;
+	}
+    }
+    *cp = 0;
+
+    return (char *)out;
+}
+
+static void copy_lfn_part(unsigned char *dst, LFN_ENT * lfn)
+{
+    memcpy(dst, lfn->name0_4, 10);
+    memcpy(dst + 10, lfn->name5_10, 12);
+    memcpy(dst + 22, lfn->name11_12, 4);
+}
+
+static void clear_lfn_slots(int start, int end)
+{
+    int i;
+    LFN_ENT empty;
+
+    /* New dir entry is zeroed except first byte, which is set to 0xe5.
+     * This is to avoid that some FAT-reading OSes (not Linux! ;) stop reading
+     * a directory at the first zero entry...
+     */
+    memset(&empty, 0, sizeof(empty));
+    empty.id = DELETED_FLAG;
+
+    for (i = start; i <= end; ++i) {
+	fs_write(lfn_offsets[i], sizeof(LFN_ENT), &empty);
+    }
+}
+
+void lfn_fix_checksum(loff_t from, loff_t to, const char *short_name)
+{
+    int i;
+    uint8_t sum;
+    for (sum = 0, i = 0; i < 11; i++)
+	sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + short_name[i];
+
+    for (; from < to; from += sizeof(LFN_ENT)) {
+	fs_write(from + offsetof(LFN_ENT, alias_checksum), sizeof(sum), &sum);
+    }
+}
+
+void lfn_reset(void)
+{
+    if (lfn_unicode)
+	free(lfn_unicode);
+    lfn_unicode = NULL;
+    if (lfn_offsets)
+	free(lfn_offsets);
+    lfn_offsets = NULL;
+    lfn_slot = -1;
+}
+
+/* This function is only called with de->attr == VFAT_LN_ATTR. It stores part
+ * of the long name. */
+void lfn_add_slot(DIR_ENT * de, loff_t dir_offset)
+{
+    LFN_ENT *lfn = (LFN_ENT *) de;
+    int slot = lfn->id & LFN_ID_SLOTMASK;
+    unsigned offset;
+
+    if (lfn_slot == 0)
+	lfn_check_orphaned();
+
+    if (de->attr != VFAT_LN_ATTR)
+	die("lfn_add_slot called with non-LFN directory entry");
+
+    if (lfn->id & LFN_ID_START && slot != 0) {
+	if (lfn_slot != -1) {
+	    int can_clear = 0;
+	    /* There is already a LFN "in progess", so it is an error that a
+	     * new start entry is here. */
+	    /* Causes: 1) if slot# == expected: start bit set mysteriously, 2)
+	     *         old LFN overwritten by new one */
+	    /* Fixes: 1) delete previous LFN 2) if slot# == expected and
+	     *        checksum ok: clear start bit */
+	    /* XXX: Should delay that until next LFN known (then can better
+	     * display the name) */
+	    printf("A new long file name starts within an old one.\n");
+	    if (slot == lfn_slot && lfn->alias_checksum == lfn_checksum) {
+		char *part1 = CNV_THIS_PART(lfn);
+		char *part2 = CNV_PARTS_SO_FAR();
+		printf("  It could be that the LFN start bit is wrong here\n"
+		       "  if \"%s\" seems to match \"%s\".\n", part1, part2);
+		free(part1);
+		free(part2);
+		can_clear = 1;
+	    }
+	    if (interactive) {
+		printf("1: Delete previous LFN\n2: Leave it as it is.\n");
+		if (can_clear)
+		    printf("3: Clear start bit and concatenate LFNs\n");
+	    } else
+		printf("  Not auto-correcting this.\n");
+	    if (interactive) {
+		switch (get_key(can_clear ? "123" : "12", "?")) {
+		case '1':
+		    clear_lfn_slots(0, lfn_parts - 1);
+		    lfn_reset();
+		    break;
+		case '2':
+		    break;
+		case '3':
+		    lfn->id &= ~LFN_ID_START;
+		    fs_write(dir_offset + offsetof(LFN_ENT, id),
+			     sizeof(lfn->id), &lfn->id);
+		    break;
+		}
+	    }
+	}
+	lfn_slot = slot;
+	lfn_checksum = lfn->alias_checksum;
+	lfn_unicode = alloc((lfn_slot * CHARS_PER_LFN + 1) * 2);
+	lfn_offsets = alloc(lfn_slot * sizeof(loff_t));
+	lfn_parts = 0;
+    } else if (lfn_slot == -1 && slot != 0) {
+	/* No LFN in progress, but slot found; start bit missing */
+	/* Causes: 1) start bit got lost, 2) Previous slot with start bit got
+	 *         lost */
+	/* Fixes: 1) delete LFN, 2) set start bit */
+	char *part = CNV_THIS_PART(lfn);
+	printf("Long filename fragment \"%s\" found outside a LFN "
+	       "sequence.\n  (Maybe the start bit is missing on the "
+	       "last fragment)\n", part);
+	if (interactive) {
+	    printf("1: Delete fragment\n2: Leave it as it is.\n"
+		   "3: Set start bit\n");
+	} else
+	    printf("  Not auto-correcting this.\n");
+	switch (interactive ? get_key("123", "?") : '2') {
+	case '1':
+	    if (!lfn_offsets)
+		lfn_offsets = alloc(sizeof(loff_t));
+	    lfn_offsets[0] = dir_offset;
+	    clear_lfn_slots(0, 0);
+	    lfn_reset();
+	    return;
+	case '2':
+	    lfn_reset();
+	    return;
+	case '3':
+	    lfn->id |= LFN_ID_START;
+	    fs_write(dir_offset + offsetof(LFN_ENT, id),
+		     sizeof(lfn->id), &lfn->id);
+	    lfn_slot = slot;
+	    lfn_checksum = lfn->alias_checksum;
+	    lfn_unicode = alloc((lfn_slot * CHARS_PER_LFN + 1) * 2);
+	    lfn_offsets = alloc(lfn_slot * sizeof(loff_t));
+	    lfn_parts = 0;
+	    break;
+	}
+    } else if (slot != lfn_slot) {
+	/* wrong sequence number */
+	/* Causes: 1) seq-no destroyed */
+	/* Fixes: 1) delete LFN, 2) fix number (maybe only if following parts
+	 *        are ok?, maybe only if checksum is ok?) (Attention: space
+	 *        for name was allocated before!) */
+	int can_fix = 0;
+	printf("Unexpected long filename sequence number "
+	       "(%d vs. expected %d).\n", slot, lfn_slot);
+	if (lfn->alias_checksum == lfn_checksum && lfn_slot > 0) {
+	    char *part1 = CNV_THIS_PART(lfn);
+	    char *part2 = CNV_PARTS_SO_FAR();
+	    printf("  It could be that just the number is wrong\n"
+		   "  if \"%s\" seems to match \"%s\".\n", part1, part2);
+	    free(part1);
+	    free(part2);
+	    can_fix = 1;
+	}
+	if (interactive) {
+	    printf
+		("1: Delete LFN\n2: Leave it as it is (and ignore LFN so far)\n");
+	    if (can_fix)
+		printf("3: Correct sequence number\n");
+	} else
+	    printf("  Not auto-correcting this.\n");
+	switch (interactive ? get_key(can_fix ? "123" : "12", "?") : '2') {
+	case '1':
+	    if (!lfn_offsets) {
+		lfn_offsets = alloc(sizeof(loff_t));
+		lfn_parts = 0;
+	    }
+	    lfn_offsets[lfn_parts++] = dir_offset;
+	    clear_lfn_slots(0, lfn_parts - 1);
+	    lfn_reset();
+	    return;
+	case '2':
+	    lfn_reset();
+	    return;
+	case '3':
+	    lfn->id = (lfn->id & ~LFN_ID_SLOTMASK) | lfn_slot;
+	    fs_write(dir_offset + offsetof(LFN_ENT, id),
+		     sizeof(lfn->id), &lfn->id);
+	    break;
+	}
+    }
+
+    if (lfn->alias_checksum != lfn_checksum) {
+	/* checksum mismatch */
+	/* Causes: 1) checksum field here destroyed */
+	/* Fixes: 1) delete LFN, 2) fix checksum */
+	printf("Checksum in long filename part wrong "
+	       "(%02x vs. expected %02x).\n",
+	       lfn->alias_checksum, lfn_checksum);
+	if (interactive) {
+	    printf("1: Delete LFN\n2: Leave it as it is.\n"
+		   "3: Correct checksum\n");
+	} else
+	    printf("  Not auto-correcting this.\n");
+	if (interactive) {
+	    switch (get_key("123", "?")) {
+	    case '1':
+		lfn_offsets[lfn_parts++] = dir_offset;
+		clear_lfn_slots(0, lfn_parts - 1);
+		lfn_reset();
+		return;
+	    case '2':
+		break;
+	    case '3':
+		lfn->alias_checksum = lfn_checksum;
+		fs_write(dir_offset + offsetof(LFN_ENT, alias_checksum),
+			 sizeof(lfn->alias_checksum), &lfn->alias_checksum);
+		break;
+	    }
+	}
+    }
+
+    if (lfn_slot != -1) {
+	lfn_slot--;
+	offset = lfn_slot * CHARS_PER_LFN * 2;
+	copy_lfn_part(lfn_unicode + offset, lfn);
+	if (lfn->id & LFN_ID_START)
+	    lfn_unicode[offset + 26] = lfn_unicode[offset + 27] = 0;
+	lfn_offsets[lfn_parts++] = dir_offset;
+    }
+
+    if (lfn->reserved != 0) {
+	printf("Reserved field in VFAT long filename slot is not 0 "
+	       "(but 0x%02x).\n", lfn->reserved);
+	if (interactive)
+	    printf("1: Fix.\n2: Leave it.\n");
+	else
+	    printf("Auto-setting to 0.\n");
+	if (!interactive || get_key("12", "?") == '1') {
+	    lfn->reserved = 0;
+	    fs_write(dir_offset + offsetof(LFN_ENT, reserved),
+		     sizeof(lfn->reserved), &lfn->reserved);
+	}
+    }
+    if (lfn->start != htole16(0)) {
+	printf("Start cluster field in VFAT long filename slot is not 0 "
+	       "(but 0x%04x).\n", lfn->start);
+	if (interactive)
+	    printf("1: Fix.\n2: Leave it.\n");
+	else
+	    printf("Auto-setting to 0.\n");
+	if (!interactive || get_key("12", "?") == '1') {
+	    lfn->start = htole16(0);
+	    fs_write(dir_offset + offsetof(LFN_ENT, start),
+		     sizeof(lfn->start), &lfn->start);
+	}
+    }
+}
+
+/* This function is always called when de->attr != VFAT_LN_ATTR is found, to
+ * retrieve the previously constructed LFN. */
+char *lfn_get(DIR_ENT * de, loff_t * lfn_offset)
+{
+    char *lfn;
+    uint8_t sum;
+    int i;
+
+    *lfn_offset = 0;
+    if (de->attr == VFAT_LN_ATTR)
+	die("lfn_get called with LFN directory entry");
+
+#if 0
+    if (de->lcase)
+	printf("lcase=%02x\n", de->lcase);
+#endif
+
+    if (lfn_slot == -1)
+	/* no long name for this file */
+	return NULL;
+
+    if (lfn_slot != 0) {
+	/* The long name isn't finished yet. */
+	/* Causes: 1) LFN slot overwritten by non-VFAT aware tool */
+	/* Fixes: 1) delete LFN 2) move overwriting entry to somewhere else
+	 * and let user enter missing part of LFN (hard to do :-()
+	 * 3) renumber entries and truncate name */
+	char *long_name = CNV_PARTS_SO_FAR();
+	char *short_name = file_name(de->name);
+	printf("Unfinished long file name \"%s\".\n"
+	       "  (Start may have been overwritten by %s)\n",
+	       long_name, short_name);
+	free(long_name);
+	if (interactive) {
+	    printf("1: Delete LFN\n2: Leave it as it is.\n"
+		   "3: Fix numbering (truncates long name and attaches "
+		   "it to short name %s)\n", short_name);
+	} else
+	    printf("  Not auto-correcting this.\n");
+	switch (interactive ? get_key("123", "?") : '2') {
+	case '1':
+	    clear_lfn_slots(0, lfn_parts - 1);
+	    lfn_reset();
+	    return NULL;
+	case '2':
+	    lfn_reset();
+	    return NULL;
+	case '3':
+	    for (i = 0; i < lfn_parts; ++i) {
+		uint8_t id = (lfn_parts - i) | (i == 0 ? LFN_ID_START : 0);
+		fs_write(lfn_offsets[i] + offsetof(LFN_ENT, id),
+			 sizeof(id), &id);
+	    }
+	    memmove(lfn_unicode, lfn_unicode + lfn_slot * CHARS_PER_LFN * 2,
+		    lfn_parts * CHARS_PER_LFN * 2);
+	    break;
+	}
+    }
+
+    for (sum = 0, i = 0; i < 8; i++)
+	sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + de->name[i];
+    for (i = 0; i < 3; i++)
+	sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + de->ext[i];
+    if (sum != lfn_checksum) {
+	/* checksum doesn't match, long name doesn't apply to this alias */
+	/* Causes: 1) alias renamed */
+	/* Fixes: 1) Fix checksum in LFN entries */
+	char *long_name = CNV_PARTS_SO_FAR();
+	char *short_name = file_name(de->name);
+	printf("Wrong checksum for long file name \"%s\".\n"
+	       "  (Short name %s may have changed without updating the long name)\n",
+	       long_name, short_name);
+	free(long_name);
+	if (interactive) {
+	    printf("1: Delete LFN\n2: Leave it as it is.\n"
+		   "3: Fix checksum (attaches to short name %s)\n", short_name);
+	} else
+	    printf("  Not auto-correcting this.\n");
+	if (interactive) {
+	    switch (get_key("123", "?")) {
+	    case '1':
+		clear_lfn_slots(0, lfn_parts - 1);
+		lfn_reset();
+		return NULL;
+	    case '2':
+		lfn_reset();
+		return NULL;
+	    case '3':
+		for (i = 0; i < lfn_parts; ++i) {
+		    fs_write(lfn_offsets[i] + offsetof(LFN_ENT, alias_checksum),
+			     sizeof(sum), &sum);
+		}
+		break;
+	    }
+	}
+    }
+
+    *lfn_offset = lfn_offsets[0];
+    lfn = cnv_unicode(lfn_unicode, UNTIL_0, 1);
+    lfn_reset();
+    return (lfn);
+}
+
+void lfn_check_orphaned(void)
+{
+    char *long_name;
+
+    if (lfn_slot == -1)
+	return;
+
+    long_name = CNV_PARTS_SO_FAR();
+    printf("Orphaned long file name part \"%s\"\n", long_name);
+    if (interactive)
+	printf("1: Delete.\n2: Leave it.\n");
+    else
+	printf("  Auto-deleting.\n");
+    if (!interactive || get_key("12", "?") == '1') {
+	clear_lfn_slots(0, lfn_parts - 1);
+    }
+    lfn_reset();
+}
diff --git a/dosfstools/src/lfn.h b/dosfstools/src/lfn.h
new file mode 100644
index 0000000..e5c3991
--- /dev/null
+++ b/dosfstools/src/lfn.h
@@ -0,0 +1,39 @@
+/* lfn.h - Functions for handling VFAT long filenames
+
+   Copyright (C) 1998 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+#ifndef _LFN_H
+#define _LFN_H
+
+void lfn_reset(void);
+/* Reset the state of the LFN parser. */
+
+void lfn_add_slot(DIR_ENT * de, loff_t dir_offset);
+/* Process a dir slot that is a VFAT LFN entry. */
+
+char *lfn_get(DIR_ENT * de, loff_t * lfn_offset);
+/* Retrieve the long name for the proper dir entry. */
+
+void lfn_check_orphaned(void);
+
+void lfn_fix_checksum(loff_t from, loff_t to, const char *short_name);
+
+#endif
diff --git a/dosfstools/src/mkfs.fat.c b/dosfstools/src/mkfs.fat.c
new file mode 100644
index 0000000..2dad236
--- /dev/null
+++ b/dosfstools/src/mkfs.fat.c
@@ -0,0 +1,1757 @@
+/* mkfs.fat.c - utility to create FAT/MS-DOS filesystems
+
+   Copyright (C) 1991 Linus Torvalds <torvalds@klaava.helsinki.fi>
+   Copyright (C) 1992-1993 Remy Card <card@masi.ibp.fr>
+   Copyright (C) 1993-1994 David Hudson <dave@humbug.demon.co.uk>
+   Copyright (C) 1998 H. Peter Anvin <hpa@zytor.com>
+   Copyright (C) 1998-2005 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+/* Description: Utility to allow an MS-DOS filesystem to be created
+   under Linux.  A lot of the basic structure of this program has been
+   borrowed from Remy Card's "mke2fs" code.
+
+   As far as possible the aim here is to make the "mkfs.fat" command
+   look almost identical to the other Linux filesystem make utilties,
+   eg bad blocks are still specified as blocks, not sectors, but when
+   it comes down to it, DOS is tied to the idea of a sector (512 bytes
+   as a rule), and not the block.  For example the boot block does not
+   occupy a full cluster.
+
+   Fixes/additions May 1998 by Roman Hodek
+   <Roman.Hodek@informatik.uni-erlangen.de>:
+   - Atari format support
+   - New options -A, -S, -C
+   - Support for filesystems > 2GB
+   - FAT32 support */
+
+/* Include the header files */
+
+#include "version.h"
+
+#include <fcntl.h>
+#include <linux/hdreg.h>
+#include <sys/mount.h>
+#include <linux/fs.h>
+#include <linux/fd.h>
+#include <endian.h>
+#include <mntent.h>
+#include <signal.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <endian.h>
+#include <getopt.h>
+
+#include "msdos_fs.h"
+
+/* In earlier versions, an own llseek() was used, but glibc lseek() is
+ * sufficient (or even better :) for 64 bit offsets in the meantime */
+#define llseek lseek64
+
+/* Constant definitions */
+
+#define TRUE 1			/* Boolean constants */
+#define FALSE 0
+
+#define TEST_BUFFER_BLOCKS 16
+#define HARD_SECTOR_SIZE   512
+#define SECTORS_PER_BLOCK ( BLOCK_SIZE / HARD_SECTOR_SIZE )
+
+#define NO_NAME "NO NAME    "
+
+/* Macro definitions */
+
+/* Report a failure message and return a failure error code */
+
+#define die( str ) fatal_error( "%s: " str "\n" )
+
+/* Mark a cluster in the FAT as bad */
+
+#define mark_sector_bad( sector ) mark_FAT_sector( sector, FAT_BAD )
+
+/* Compute ceil(a/b) */
+
+static inline int cdiv(int a, int b)
+{
+    return (a + b - 1) / b;
+}
+
+/* FAT values */
+#define FAT_EOF      (atari_format ? 0x0fffffff : 0x0ffffff8)
+#define FAT_BAD      0x0ffffff7
+
+#define MSDOS_EXT_SIGN 0x29	/* extended boot sector signature */
+#define MSDOS_FAT12_SIGN "FAT12   "	/* FAT12 filesystem signature */
+#define MSDOS_FAT16_SIGN "FAT16   "	/* FAT16 filesystem signature */
+#define MSDOS_FAT32_SIGN "FAT32   "	/* FAT32 filesystem signature */
+
+#define BOOT_SIGN 0xAA55	/* Boot sector magic number */
+
+#define MAX_CLUST_12	((1 << 12) - 16)
+#define MAX_CLUST_16	((1 << 16) - 16)
+#define MIN_CLUST_32    65529
+/* M$ says the high 4 bits of a FAT32 FAT entry are reserved and don't belong
+ * to the cluster number. So the max. cluster# is based on 2^28 */
+#define MAX_CLUST_32	((1 << 28) - 16)
+
+#define FAT12_THRESHOLD	4085
+
+#define OLDGEMDOS_MAX_SECTORS	32765
+#define GEMDOS_MAX_SECTORS	65531
+#define GEMDOS_MAX_SECTOR_SIZE	(16*1024)
+
+#define BOOTCODE_SIZE		448
+#define BOOTCODE_FAT32_SIZE	420
+
+/* __attribute__ ((packed)) is used on all structures to make gcc ignore any
+ * alignments */
+
+struct msdos_volume_info {
+    uint8_t drive_number;	/* BIOS drive number */
+    uint8_t RESERVED;		/* Unused */
+    uint8_t ext_boot_sign;	/* 0x29 if fields below exist (DOS 3.3+) */
+    uint8_t volume_id[4];	/* Volume ID number */
+    uint8_t volume_label[11];	/* Volume label */
+    uint8_t fs_type[8];		/* Typically FAT12 or FAT16 */
+} __attribute__ ((packed));
+
+struct msdos_boot_sector {
+    uint8_t boot_jump[3];	/* Boot strap short or near jump */
+    uint8_t system_id[8];	/* Name - can be used to special case
+				   partition manager volumes */
+    uint8_t sector_size[2];	/* bytes per logical sector */
+    uint8_t cluster_size;	/* sectors/cluster */
+    uint16_t reserved;		/* reserved sectors */
+    uint8_t fats;		/* number of FATs */
+    uint8_t dir_entries[2];	/* root directory entries */
+    uint8_t sectors[2];		/* number of sectors */
+    uint8_t media;		/* media code (unused) */
+    uint16_t fat_length;	/* sectors/FAT */
+    uint16_t secs_track;	/* sectors per track */
+    uint16_t heads;		/* number of heads */
+    uint32_t hidden;		/* hidden sectors (unused) */
+    uint32_t total_sect;	/* number of sectors (if sectors == 0) */
+    union {
+	struct {
+	    struct msdos_volume_info vi;
+	    uint8_t boot_code[BOOTCODE_SIZE];
+	} __attribute__ ((packed)) _oldfat;
+	struct {
+	    uint32_t fat32_length;	/* sectors/FAT */
+	    uint16_t flags;		/* bit 8: fat mirroring, low 4: active fat */
+	    uint8_t version[2];		/* major, minor filesystem version */
+	    uint32_t root_cluster;	/* first cluster in root directory */
+	    uint16_t info_sector;	/* filesystem info sector */
+	    uint16_t backup_boot;	/* backup boot sector */
+	    uint16_t reserved2[6];	/* Unused */
+	    struct msdos_volume_info vi;
+	    uint8_t boot_code[BOOTCODE_FAT32_SIZE];
+	} __attribute__ ((packed)) _fat32;
+    } __attribute__ ((packed)) fstype;
+    uint16_t boot_sign;
+} __attribute__ ((packed));
+#define fat32	fstype._fat32
+#define oldfat	fstype._oldfat
+
+struct fat32_fsinfo {
+    uint32_t reserved1;		/* Nothing as far as I can tell */
+    uint32_t signature;		/* 0x61417272L */
+    uint32_t free_clusters;	/* Free cluster count.  -1 if unknown */
+    uint32_t next_cluster;	/* Most recently allocated cluster.
+				 * Unused under Linux. */
+    uint32_t reserved2[4];
+};
+
+/* The "boot code" we put into the filesystem... it writes a message and
+   tells the user to try again */
+
+unsigned char dummy_boot_jump[3] = { 0xeb, 0x3c, 0x90 };
+
+unsigned char dummy_boot_jump_m68k[2] = { 0x60, 0x1c };
+
+#define MSG_OFFSET_OFFSET 3
+char dummy_boot_code[BOOTCODE_SIZE] = "\x0e"	/* push cs */
+    "\x1f"			/* pop ds */
+    "\xbe\x5b\x7c"		/* mov si, offset message_txt */
+    /* write_msg: */
+    "\xac"			/* lodsb */
+    "\x22\xc0"			/* and al, al */
+    "\x74\x0b"			/* jz key_press */
+    "\x56"			/* push si */
+    "\xb4\x0e"			/* mov ah, 0eh */
+    "\xbb\x07\x00"		/* mov bx, 0007h */
+    "\xcd\x10"			/* int 10h */
+    "\x5e"			/* pop si */
+    "\xeb\xf0"			/* jmp write_msg */
+    /* key_press: */
+    "\x32\xe4"			/* xor ah, ah */
+    "\xcd\x16"			/* int 16h */
+    "\xcd\x19"			/* int 19h */
+    "\xeb\xfe"			/* foo: jmp foo */
+    /* message_txt: */
+    "This is not a bootable disk.  Please insert a bootable floppy and\r\n"
+    "press any key to try again ... \r\n";
+
+#define MESSAGE_OFFSET 29	/* Offset of message in above code */
+
+/* Global variables - the root of all evil :-) - see these and weep! */
+
+static const char *program_name = "mkfs.fat";	/* Name of the program */
+static char *device_name = NULL;	/* Name of the device on which to create the filesystem */
+static int atari_format = 0;	/* Use Atari variation of MS-DOS FS format */
+static int check = FALSE;	/* Default to no readablity checking */
+static int verbose = 0;		/* Default to verbose mode off */
+static long volume_id;		/* Volume ID number */
+static time_t create_time;	/* Creation time */
+static char volume_name[] = NO_NAME;	/* Volume name */
+static uint64_t blocks;	/* Number of blocks in filesystem */
+static int sector_size = 512;	/* Size of a logical sector */
+static int sector_size_set = 0;	/* User selected sector size */
+static int backup_boot = 0;	/* Sector# of backup boot sector */
+static int reserved_sectors = 0;	/* Number of reserved sectors */
+static int badblocks = 0;	/* Number of bad blocks in the filesystem */
+static int nr_fats = 2;		/* Default number of FATs to produce */
+static int size_fat = 0;	/* Size in bits of FAT entries */
+static int size_fat_by_user = 0;	/* 1 if FAT size user selected */
+static int dev = -1;		/* FS block device file handle */
+static int ignore_full_disk = 0;	/* Ignore warning about 'full' disk devices */
+static off_t currently_testing = 0;	/* Block currently being tested (if autodetect bad blocks) */
+static struct msdos_boot_sector bs;	/* Boot sector data */
+static int start_data_sector;	/* Sector number for the start of the data area */
+static int start_data_block;	/* Block number for the start of the data area */
+static unsigned char *fat;	/* File allocation table */
+static unsigned alloced_fat_length;	/* # of FAT sectors we can keep in memory */
+static unsigned char *info_sector;	/* FAT32 info sector */
+static struct msdos_dir_entry *root_dir;	/* Root directory */
+static int size_root_dir;	/* Size of the root directory in bytes */
+static int sectors_per_cluster = 0;	/* Number of sectors per disk cluster */
+static int root_dir_entries = 0;	/* Number of root directory entries */
+static char *blank_sector;	/* Blank sector - all zeros */
+static int hidden_sectors = 0;	/* Number of hidden sectors */
+static int hidden_sectors_by_user = 0;	/* -h option invoked */
+static int drive_number_option = 0;	/* drive number */
+static int drive_number_by_user = 0;	/* drive number option invoked */
+static int fat_media_byte = 0;	/* media byte in header and starting FAT */
+static int malloc_entire_fat = FALSE;	/* Whether we should malloc() the entire FAT or not */
+static int align_structures = TRUE;	/* Whether to enforce alignment */
+static int orphaned_sectors = 0;	/* Sectors that exist in the last block of filesystem */
+static int invariant = 0;		/* Whether to set normally randomized or
+					   current time based values to
+					   constants */
+
+/* Function prototype definitions */
+
+static void fatal_error(const char *fmt_string) __attribute__ ((noreturn));
+static void mark_FAT_cluster(int cluster, unsigned int value);
+static void mark_FAT_sector(int sector, unsigned int value);
+static long do_check(char *buffer, int try, off_t current_block);
+static void alarm_intr(int alnum);
+static void check_blocks(void);
+static void get_list_blocks(char *filename);
+static int valid_offset(int fd, loff_t offset);
+static uint64_t count_blocks(char *filename, int *remainder);
+static void check_mount(char *device_name);
+static void establish_params(int device_num, int size);
+static void setup_tables(void);
+static void write_tables(void);
+
+/* The function implementations */
+
+/* Handle the reporting of fatal errors.  Volatile to let gcc know that this doesn't return */
+
+static void fatal_error(const char *fmt_string)
+{
+    fprintf(stderr, fmt_string, program_name, device_name);
+    exit(1);			/* The error exit code is 1! */
+}
+
+/* Mark the specified cluster as having a particular value */
+
+static void mark_FAT_cluster(int cluster, unsigned int value)
+{
+    switch (size_fat) {
+    case 12:
+	value &= 0x0fff;
+	if (((cluster * 3) & 0x1) == 0) {
+	    fat[3 * cluster / 2] = (unsigned char)(value & 0x00ff);
+	    fat[(3 * cluster / 2) + 1] =
+		(unsigned char)((fat[(3 * cluster / 2) + 1] & 0x00f0)
+				| ((value & 0x0f00) >> 8));
+	} else {
+	    fat[3 * cluster / 2] =
+		(unsigned char)((fat[3 * cluster / 2] & 0x000f) |
+				((value & 0x000f) << 4));
+	    fat[(3 * cluster / 2) + 1] = (unsigned char)((value & 0x0ff0) >> 4);
+	}
+	break;
+
+    case 16:
+	value &= 0xffff;
+	fat[2 * cluster] = (unsigned char)(value & 0x00ff);
+	fat[(2 * cluster) + 1] = (unsigned char)(value >> 8);
+	break;
+
+    case 32:
+	value &= 0xfffffff;
+	fat[4 * cluster] = (unsigned char)(value & 0x000000ff);
+	fat[(4 * cluster) + 1] = (unsigned char)((value & 0x0000ff00) >> 8);
+	fat[(4 * cluster) + 2] = (unsigned char)((value & 0x00ff0000) >> 16);
+	fat[(4 * cluster) + 3] = (unsigned char)((value & 0xff000000) >> 24);
+	break;
+
+    default:
+	die("Bad FAT size (not 12, 16, or 32)");
+    }
+}
+
+/* Mark a specified sector as having a particular value in it's FAT entry */
+
+static void mark_FAT_sector(int sector, unsigned int value)
+{
+    int cluster;
+
+    cluster = (sector - start_data_sector) / (int)(bs.cluster_size) /
+	(sector_size / HARD_SECTOR_SIZE);
+    if (cluster < 0)
+	die("Invalid cluster number in mark_FAT_sector: probably bug!");
+
+    mark_FAT_cluster(cluster, value);
+}
+
+/* Perform a test on a block.  Return the number of blocks that could be read successfully */
+
+static long do_check(char *buffer, int try, off_t current_block)
+{
+    long got;
+
+    if (llseek(dev, current_block * BLOCK_SIZE, SEEK_SET)	/* Seek to the correct location */
+	!=current_block * BLOCK_SIZE)
+	die("seek failed during testing for blocks");
+
+    got = read(dev, buffer, try * BLOCK_SIZE);	/* Try reading! */
+    if (got < 0)
+	got = 0;
+
+    if (got & (BLOCK_SIZE - 1))
+	printf("Unexpected values in do_check: probably bugs\n");
+    got /= BLOCK_SIZE;
+
+    return got;
+}
+
+/* Alarm clock handler - display the status of the quest for bad blocks!  Then retrigger the alarm for five senconds
+   later (so we can come here again) */
+
+static void alarm_intr(int alnum)
+{
+    (void)alnum;
+
+    if (currently_testing >= blocks)
+	return;
+
+    signal(SIGALRM, alarm_intr);
+    alarm(5);
+    if (!currently_testing)
+	return;
+
+    printf("%lld... ", (unsigned long long)currently_testing);
+    fflush(stdout);
+}
+
+static void check_blocks(void)
+{
+    int try, got;
+    int i;
+    static char blkbuf[BLOCK_SIZE * TEST_BUFFER_BLOCKS];
+
+    if (verbose) {
+	printf("Searching for bad blocks ");
+	fflush(stdout);
+    }
+    currently_testing = 0;
+    if (verbose) {
+	signal(SIGALRM, alarm_intr);
+	alarm(5);
+    }
+    try = TEST_BUFFER_BLOCKS;
+    while (currently_testing < blocks) {
+	if (currently_testing + try > blocks)
+	    try = blocks - currently_testing;
+	got = do_check(blkbuf, try, currently_testing);
+	currently_testing += got;
+	if (got == try) {
+	    try = TEST_BUFFER_BLOCKS;
+	    continue;
+	} else
+	    try = 1;
+	if (currently_testing < start_data_block)
+	    die("bad blocks before data-area: cannot make fs");
+
+	for (i = 0; i < SECTORS_PER_BLOCK; i++)	/* Mark all of the sectors in the block as bad */
+	    mark_sector_bad(currently_testing * SECTORS_PER_BLOCK + i);
+	badblocks++;
+	currently_testing++;
+    }
+
+    if (verbose)
+	printf("\n");
+
+    if (badblocks)
+	printf("%d bad block%s\n", badblocks, (badblocks > 1) ? "s" : "");
+}
+
+static void get_list_blocks(char *filename)
+{
+    int i;
+    FILE *listfile;
+    long blockno;
+
+    listfile = fopen(filename, "r");
+    if (listfile == (FILE *) NULL)
+	die("Can't open file of bad blocks");
+
+    while (!feof(listfile)) {
+	fscanf(listfile, "%ld\n", &blockno);
+	for (i = 0; i < SECTORS_PER_BLOCK; i++)	/* Mark all of the sectors in the block as bad */
+	    mark_sector_bad(blockno * SECTORS_PER_BLOCK + i);
+	badblocks++;
+    }
+    fclose(listfile);
+
+    if (badblocks)
+	printf("%d bad block%s\n", badblocks, (badblocks > 1) ? "s" : "");
+}
+
+/* Given a file descriptor and an offset, check whether the offset is a valid offset for the file - return FALSE if it
+   isn't valid or TRUE if it is */
+
+static int valid_offset(int fd, loff_t offset)
+{
+    char ch;
+
+    if (llseek(fd, offset, SEEK_SET) < 0)
+	return FALSE;
+    if (read(fd, &ch, 1) < 1)
+	return FALSE;
+    return TRUE;
+}
+
+/* Given a filename, look to see how many blocks of BLOCK_SIZE are present, returning the answer */
+
+static uint64_t count_blocks(char *filename, int *remainder)
+{
+    loff_t high, low;
+    int fd;
+
+    if ((fd = open(filename, O_RDONLY)) < 0) {
+	perror(filename);
+	exit(1);
+    }
+
+    /* first try SEEK_END, which should work on most devices nowadays */
+    if ((low = llseek(fd, 0, SEEK_END)) <= 0) {
+	low = 0;
+	for (high = 1; valid_offset(fd, high); high *= 2)
+	    low = high;
+	while (low < high - 1) {
+	    const loff_t mid = (low + high) / 2;
+	    if (valid_offset(fd, mid))
+		low = mid;
+	    else
+		high = mid;
+	}
+	++low;
+    }
+
+    close(fd);
+    *remainder = (low % BLOCK_SIZE) / sector_size;
+    return (low / BLOCK_SIZE);
+}
+
+/* Check to see if the specified device is currently mounted - abort if it is */
+
+static void check_mount(char *device_name)
+{
+/* older versions of Bionic don't have setmntent (4.x) or an incomplete impl (5.x) */
+#ifdef MOUNTED
+    FILE *f;
+    struct mntent *mnt;
+
+    if ((f = setmntent(MOUNTED, "r")) == NULL)
+	return;
+    while ((mnt = getmntent(f)) != NULL)
+	if (strcmp(device_name, mnt->mnt_fsname) == 0)
+	    die("%s contains a mounted filesystem.");
+    endmntent(f);
+#endif
+}
+
+/* Establish the geometry and media parameters for the device */
+
+static void establish_params(int device_num, int size)
+{
+    long loop_size;
+    struct hd_geometry geometry;
+    struct floppy_struct param;
+    int def_root_dir_entries = 512;
+
+    if ((0 == device_num) || ((device_num & 0xff00) == 0x0200))
+	/* file image or floppy disk */
+    {
+	if (0 == device_num) {
+	    param.size = size / 512;
+	    switch (param.size) {
+	    case 720:
+		param.sect = 9;
+		param.head = 2;
+		break;
+	    case 1440:
+		param.sect = 9;
+		param.head = 2;
+		break;
+	    case 2400:
+		param.sect = 15;
+		param.head = 2;
+		break;
+	    case 2880:
+		param.sect = 18;
+		param.head = 2;
+		break;
+	    case 5760:
+		param.sect = 36;
+		param.head = 2;
+		break;
+	    default:
+		/* fake values */
+		param.sect = 32;
+		param.head = 64;
+		break;
+	    }
+
+	} else {		/* is a floppy diskette */
+
+	    if (ioctl(dev, FDGETPRM, &param))	/*  Can we get the diskette geometry? */
+		die("unable to get diskette geometry for '%s'");
+	}
+	bs.secs_track = htole16(param.sect);	/*  Set up the geometry information */
+	bs.heads = htole16(param.head);
+	switch (param.size) {	/*  Set up the media descriptor byte */
+	case 720:		/* 5.25", 2, 9, 40 - 360K */
+	    bs.media = (char)0xfd;
+	    bs.cluster_size = (char)2;
+	    def_root_dir_entries = 112;
+	    break;
+
+	case 1440:		/* 3.5", 2, 9, 80 - 720K */
+	    bs.media = (char)0xf9;
+	    bs.cluster_size = (char)2;
+	    def_root_dir_entries = 112;
+	    break;
+
+	case 2400:		/* 5.25", 2, 15, 80 - 1200K */
+	    bs.media = (char)0xf9;
+	    bs.cluster_size = (char)(atari_format ? 2 : 1);
+	    def_root_dir_entries = 224;
+	    break;
+
+	case 5760:		/* 3.5", 2, 36, 80 - 2880K */
+	    bs.media = (char)0xf0;
+	    bs.cluster_size = (char)2;
+	    def_root_dir_entries = 224;
+	    break;
+
+	case 2880:		/* 3.5", 2, 18, 80 - 1440K */
+floppy_default:
+	    bs.media = (char)0xf0;
+	    bs.cluster_size = (char)(atari_format ? 2 : 1);
+	    def_root_dir_entries = 224;
+	    break;
+
+	default:		/* Anything else */
+	    if (0 == device_num)
+		goto def_hd_params;
+	    else
+		goto floppy_default;
+	}
+    } else if ((device_num & 0xff00) == 0x0700) {	/* This is a loop device */
+	if (ioctl(dev, BLKGETSIZE, &loop_size))
+	    die("unable to get loop device size");
+
+	switch (loop_size) {	/* Assuming the loop device -> floppy later */
+	case 720:		/* 5.25", 2, 9, 40 - 360K */
+	    bs.secs_track = le16toh(9);
+	    bs.heads = le16toh(2);
+	    bs.media = (char)0xfd;
+	    bs.cluster_size = (char)2;
+	    def_root_dir_entries = 112;
+	    break;
+
+	case 1440:		/* 3.5", 2, 9, 80 - 720K */
+	    bs.secs_track = le16toh(9);
+	    bs.heads = le16toh(2);
+	    bs.media = (char)0xf9;
+	    bs.cluster_size = (char)2;
+	    def_root_dir_entries = 112;
+	    break;
+
+	case 2400:		/* 5.25", 2, 15, 80 - 1200K */
+	    bs.secs_track = le16toh(15);
+	    bs.heads = le16toh(2);
+	    bs.media = (char)0xf9;
+	    bs.cluster_size = (char)(atari_format ? 2 : 1);
+	    def_root_dir_entries = 224;
+	    break;
+
+	case 5760:		/* 3.5", 2, 36, 80 - 2880K */
+	    bs.secs_track = le16toh(36);
+	    bs.heads = le16toh(2);
+	    bs.media = (char)0xf0;
+	    bs.cluster_size = (char)2;
+	    bs.dir_entries[0] = (char)224;
+	    bs.dir_entries[1] = (char)0;
+	    break;
+
+	case 2880:		/* 3.5", 2, 18, 80 - 1440K */
+	    bs.secs_track = le16toh(18);
+	    bs.heads = le16toh(2);
+	    bs.media = (char)0xf0;
+	    bs.cluster_size = (char)(atari_format ? 2 : 1);
+	    def_root_dir_entries = 224;
+	    break;
+
+	default:		/* Anything else: default hd setup */
+	    printf("Loop device does not match a floppy size, using "
+		   "default hd params\n");
+	    bs.secs_track = htole16(32);	/* these are fake values... */
+	    bs.heads = htole16(64);
+	    goto def_hd_params;
+	}
+    } else
+	/* Must be a hard disk then! */
+    {
+	/* Can we get the drive geometry? (Note I'm not too sure about */
+	/* whether to use HDIO_GETGEO or HDIO_REQ) */
+	if (ioctl(dev, HDIO_GETGEO, &geometry) || geometry.sectors == 0
+	    || geometry.heads == 0) {
+	    printf("unable to get drive geometry, using default 255/63\n");
+	    bs.secs_track = htole16(63);
+	    bs.heads = htole16(255);
+	} else {
+	    bs.secs_track = htole16(geometry.sectors);	/* Set up the geometry information */
+	    bs.heads = htole16(geometry.heads);
+	    if (!hidden_sectors_by_user)
+		hidden_sectors = htole32(geometry.start);
+	}
+def_hd_params:
+	bs.media = (char)0xf8;	/* Set up the media descriptor for a hard drive */
+	if (!size_fat && blocks * SECTORS_PER_BLOCK > 1064960) {
+	    if (verbose)
+		printf("Auto-selecting FAT32 for large filesystem\n");
+	    size_fat = 32;
+	}
+	if (size_fat == 32) {
+	    /* For FAT32, try to do the same as M$'s format command
+	     * (see http://www.win.tue.nl/~aeb/linux/fs/fat/fatgen103.pdf p. 20):
+	     * fs size <= 260M: 0.5k clusters
+	     * fs size <=   8G:   4k clusters
+	     * fs size <=  16G:   8k clusters
+	     * fs size <=  32G:  16k clusters
+	     * fs size >   32G:  32k clusters
+	     */
+	    uint32_t sz_mb =
+		(blocks + (1 << (20 - BLOCK_SIZE_BITS)) - 1) >> (20 -
+								 BLOCK_SIZE_BITS);
+	    bs.cluster_size =
+		sz_mb > 32 * 1024 ? 64 : sz_mb > 16 * 1024 ? 32 : sz_mb >
+		8 * 1024 ? 16 : sz_mb > 260 ? 8 : 1;
+	} else {
+	    /* FAT12 and FAT16: start at 4 sectors per cluster */
+	    bs.cluster_size = (char)4;
+	}
+    }
+
+    if (!root_dir_entries)
+	root_dir_entries = def_root_dir_entries;
+}
+
+/*
+ * If alignment is enabled, round the first argument up to the second; the
+ * latter must be a power of two.
+ */
+static unsigned int align_object(unsigned int sectors, unsigned int clustsize)
+{
+    if (align_structures)
+	return (sectors + clustsize - 1) & ~(clustsize - 1);
+    else
+	return sectors;
+}
+
+/* Create the filesystem data tables */
+
+static void setup_tables(void)
+{
+    unsigned num_sectors;
+    unsigned cluster_count = 0, fat_length;
+    struct tm *ctime;
+    struct msdos_volume_info *vi =
+	(size_fat == 32 ? &bs.fat32.vi : &bs.oldfat.vi);
+
+    if (atari_format) {
+	/* On Atari, the first few bytes of the boot sector are assigned
+	 * differently: The jump code is only 2 bytes (and m68k machine code
+	 * :-), then 6 bytes filler (ignored), then 3 byte serial number. */
+	bs.boot_jump[2] = 'm';
+	memcpy((char *)bs.system_id, "kdosf", strlen("kdosf"));
+    } else
+	memcpy((char *)bs.system_id, "mkfs.fat", strlen("mkfs.fat"));
+    if (sectors_per_cluster)
+	bs.cluster_size = (char)sectors_per_cluster;
+
+    if (fat_media_byte)
+	bs.media = (char) fat_media_byte;
+
+    if (bs.media == 0xf8)
+	vi->drive_number=0x80;
+    else
+	vi->drive_number=0x00;
+
+    if (drive_number_by_user)
+	vi->drive_number= (char) drive_number_option;
+
+    if (size_fat == 32) {
+	/* Under FAT32, the root dir is in a cluster chain, and this is
+	 * signalled by bs.dir_entries being 0. */
+	root_dir_entries = 0;
+    }
+
+    if (atari_format) {
+	bs.system_id[5] = (unsigned char)(volume_id & 0x000000ff);
+	bs.system_id[6] = (unsigned char)((volume_id & 0x0000ff00) >> 8);
+	bs.system_id[7] = (unsigned char)((volume_id & 0x00ff0000) >> 16);
+    } else {
+	vi->volume_id[0] = (unsigned char)(volume_id & 0x000000ff);
+	vi->volume_id[1] = (unsigned char)((volume_id & 0x0000ff00) >> 8);
+	vi->volume_id[2] = (unsigned char)((volume_id & 0x00ff0000) >> 16);
+	vi->volume_id[3] = (unsigned char)(volume_id >> 24);
+    }
+
+    if (!atari_format) {
+	memcpy(vi->volume_label, volume_name, 11);
+
+	memcpy(bs.boot_jump, dummy_boot_jump, 3);
+	/* Patch in the correct offset to the boot code */
+	bs.boot_jump[1] = ((size_fat == 32 ?
+			    (char *)&bs.fat32.boot_code :
+			    (char *)&bs.oldfat.boot_code) - (char *)&bs) - 2;
+
+	if (size_fat == 32) {
+	    int offset = (char *)&bs.fat32.boot_code -
+		(char *)&bs + MESSAGE_OFFSET + 0x7c00;
+	    if (dummy_boot_code[BOOTCODE_FAT32_SIZE - 1])
+		printf("Warning: message too long; truncated\n");
+	    dummy_boot_code[BOOTCODE_FAT32_SIZE - 1] = 0;
+	    memcpy(bs.fat32.boot_code, dummy_boot_code, BOOTCODE_FAT32_SIZE);
+	    bs.fat32.boot_code[MSG_OFFSET_OFFSET] = offset & 0xff;
+	    bs.fat32.boot_code[MSG_OFFSET_OFFSET + 1] = offset >> 8;
+	} else {
+	    memcpy(bs.oldfat.boot_code, dummy_boot_code, BOOTCODE_SIZE);
+	}
+	bs.boot_sign = htole16(BOOT_SIGN);
+    } else {
+	memcpy(bs.boot_jump, dummy_boot_jump_m68k, 2);
+    }
+    if (verbose >= 2)
+	printf("Boot jump code is %02x %02x\n",
+	       bs.boot_jump[0], bs.boot_jump[1]);
+
+    if (!reserved_sectors)
+	reserved_sectors = (size_fat == 32) ? 32 : 1;
+    else {
+	if (size_fat == 32 && reserved_sectors < 2)
+	    die("On FAT32 at least 2 reserved sectors are needed.");
+    }
+    bs.reserved = htole16(reserved_sectors);
+    if (verbose >= 2)
+	printf("Using %d reserved sectors\n", reserved_sectors);
+    bs.fats = (char)nr_fats;
+    if (!atari_format || size_fat == 32)
+	bs.hidden = htole32(hidden_sectors);
+    else {
+	/* In Atari format, hidden is a 16 bit field */
+	uint16_t hidden = htole16(hidden_sectors);
+	if (hidden_sectors & ~0xffff)
+	    die("#hidden doesn't fit in 16bit field of Atari format\n");
+	memcpy(&bs.hidden, &hidden, 2);
+    }
+
+    num_sectors =
+	(long long)(blocks * BLOCK_SIZE / sector_size) + orphaned_sectors;
+
+    if (!atari_format) {
+	unsigned fatdata1216;	/* Sectors for FATs + data area (FAT12/16) */
+	unsigned fatdata32;	/* Sectors for FATs + data area (FAT32) */
+	unsigned fatlength12, fatlength16, fatlength32;
+	unsigned maxclust12, maxclust16, maxclust32;
+	unsigned clust12, clust16, clust32;
+	int maxclustsize;
+	unsigned root_dir_sectors = cdiv(root_dir_entries * 32, sector_size);
+
+	/*
+	 * If the filesystem is 8192 sectors or less (4 MB with 512-byte
+	 * sectors, i.e. floppy size), don't align the data structures.
+	 */
+	if (num_sectors <= 8192) {
+	    if (align_structures && verbose >= 2)
+		printf("Disabling alignment due to tiny filesystem\n");
+
+	    align_structures = FALSE;
+	}
+
+	if (sectors_per_cluster)
+	    bs.cluster_size = maxclustsize = sectors_per_cluster;
+	else
+	    /* An initial guess for bs.cluster_size should already be set */
+	    maxclustsize = 128;
+
+	do {
+	    fatdata32 = num_sectors - reserved_sectors;
+	    fatdata1216 = fatdata32
+		- align_object(root_dir_sectors, bs.cluster_size);
+
+	    if (verbose >= 2)
+		printf("Trying with %d sectors/cluster:\n", bs.cluster_size);
+
+	    /* The factor 2 below avoids cut-off errors for nr_fats == 1.
+	     * The "nr_fats*3" is for the reserved first two FAT entries */
+	    clust12 = 2 * ((long long)fatdata1216 * sector_size + nr_fats * 3) /
+		(2 * (int)bs.cluster_size * sector_size + nr_fats * 3);
+	    fatlength12 = cdiv(((clust12 + 2) * 3 + 1) >> 1, sector_size);
+	    fatlength12 = align_object(fatlength12, bs.cluster_size);
+	    /* Need to recalculate number of clusters, since the unused parts of the
+	     * FATS and data area together could make up space for an additional,
+	     * not really present cluster. */
+	    clust12 = (fatdata1216 - nr_fats * fatlength12) / bs.cluster_size;
+	    maxclust12 = (fatlength12 * 2 * sector_size) / 3;
+	    if (maxclust12 > MAX_CLUST_12)
+		maxclust12 = MAX_CLUST_12;
+	    if (verbose >= 2)
+		printf("FAT12: #clu=%u, fatlen=%u, maxclu=%u, limit=%u\n",
+		       clust12, fatlength12, maxclust12, MAX_CLUST_12);
+	    if (clust12 > maxclust12 - 2) {
+		clust12 = 0;
+		if (verbose >= 2)
+		    printf("FAT12: too much clusters\n");
+	    }
+
+	    clust16 = ((long long)fatdata1216 * sector_size + nr_fats * 4) /
+		((int)bs.cluster_size * sector_size + nr_fats * 2);
+	    fatlength16 = cdiv((clust16 + 2) * 2, sector_size);
+	    fatlength16 = align_object(fatlength16, bs.cluster_size);
+	    /* Need to recalculate number of clusters, since the unused parts of the
+	     * FATS and data area together could make up space for an additional,
+	     * not really present cluster. */
+	    clust16 = (fatdata1216 - nr_fats * fatlength16) / bs.cluster_size;
+	    maxclust16 = (fatlength16 * sector_size) / 2;
+	    if (maxclust16 > MAX_CLUST_16)
+		maxclust16 = MAX_CLUST_16;
+	    if (verbose >= 2)
+		printf("FAT16: #clu=%u, fatlen=%u, maxclu=%u, limit=%u\n",
+		       clust16, fatlength16, maxclust16, MAX_CLUST_16);
+	    if (clust16 > maxclust16 - 2) {
+		if (verbose >= 2)
+		    printf("FAT16: too much clusters\n");
+		clust16 = 0;
+	    }
+	    /* The < 4078 avoids that the filesystem will be misdetected as having a
+	     * 12 bit FAT. */
+	    if (clust16 < FAT12_THRESHOLD
+		&& !(size_fat_by_user && size_fat == 16)) {
+		if (verbose >= 2)
+		    printf(clust16 < FAT12_THRESHOLD ?
+			   "FAT16: would be misdetected as FAT12\n" :
+			   "FAT16: too much clusters\n");
+		clust16 = 0;
+	    }
+
+	    clust32 = ((long long)fatdata32 * sector_size + nr_fats * 8) /
+		((int)bs.cluster_size * sector_size + nr_fats * 4);
+	    fatlength32 = cdiv((clust32 + 2) * 4, sector_size);
+	    /* Need to recalculate number of clusters, since the unused parts of the
+	     * FATS and data area together could make up space for an additional,
+	     * not really present cluster. */
+	    clust32 = (fatdata32 - nr_fats * fatlength32) / bs.cluster_size;
+	    maxclust32 = (fatlength32 * sector_size) / 4;
+	    if (maxclust32 > MAX_CLUST_32)
+		maxclust32 = MAX_CLUST_32;
+	    if (clust32 && clust32 < MIN_CLUST_32
+		&& !(size_fat_by_user && size_fat == 32)) {
+		clust32 = 0;
+		if (verbose >= 2)
+		    printf("FAT32: not enough clusters (%d)\n", MIN_CLUST_32);
+	    }
+	    if (verbose >= 2)
+		printf("FAT32: #clu=%u, fatlen=%u, maxclu=%u, limit=%u\n",
+		       clust32, fatlength32, maxclust32, MAX_CLUST_32);
+	    if (clust32 > maxclust32) {
+		clust32 = 0;
+		if (verbose >= 2)
+		    printf("FAT32: too much clusters\n");
+	    }
+
+	    if ((clust12 && (size_fat == 0 || size_fat == 12)) ||
+		(clust16 && (size_fat == 0 || size_fat == 16)) ||
+		(clust32 && size_fat == 32))
+		break;
+
+	    bs.cluster_size <<= 1;
+	} while (bs.cluster_size && bs.cluster_size <= maxclustsize);
+
+	/* Use the optimal FAT size if not specified;
+	 * FAT32 is (not yet) choosen automatically */
+	if (!size_fat) {
+	    size_fat = (clust16 > clust12) ? 16 : 12;
+	    if (verbose >= 2)
+		printf("Choosing %d bits for FAT\n", size_fat);
+	}
+
+	switch (size_fat) {
+	case 12:
+	    cluster_count = clust12;
+	    fat_length = fatlength12;
+	    bs.fat_length = htole16(fatlength12);
+	    memcpy(vi->fs_type, MSDOS_FAT12_SIGN, 8);
+	    break;
+
+	case 16:
+	    if (clust16 < FAT12_THRESHOLD) {
+		if (size_fat_by_user) {
+		    fprintf(stderr, "WARNING: Not enough clusters for a "
+			    "16 bit FAT! The filesystem will be\n"
+			    "misinterpreted as having a 12 bit FAT without "
+			    "mount option \"fat=16\".\n");
+		} else {
+		    fprintf(stderr, "This filesystem has an unfortunate size. "
+			    "A 12 bit FAT cannot provide\n"
+			    "enough clusters, but a 16 bit FAT takes up a little "
+			    "bit more space so that\n"
+			    "the total number of clusters becomes less than the "
+			    "threshold value for\n"
+			    "distinction between 12 and 16 bit FATs.\n");
+		    die("Make the filesystem a bit smaller manually.");
+		}
+	    }
+	    cluster_count = clust16;
+	    fat_length = fatlength16;
+	    bs.fat_length = htole16(fatlength16);
+	    memcpy(vi->fs_type, MSDOS_FAT16_SIGN, 8);
+	    break;
+
+	case 32:
+	    if (clust32 < MIN_CLUST_32)
+		fprintf(stderr,
+			"WARNING: Not enough clusters for a 32 bit FAT!\n");
+	    cluster_count = clust32;
+	    fat_length = fatlength32;
+	    bs.fat_length = htole16(0);
+	    bs.fat32.fat32_length = htole32(fatlength32);
+	    memcpy(vi->fs_type, MSDOS_FAT32_SIGN, 8);
+	    root_dir_entries = 0;
+	    break;
+
+	default:
+	    die("FAT not 12, 16 or 32 bits");
+	}
+
+	/* Adjust the number of root directory entries to help enforce alignment */
+	if (align_structures) {
+	    root_dir_entries = align_object(root_dir_sectors, bs.cluster_size)
+		* (sector_size >> 5);
+	}
+    } else {
+	unsigned clusters, maxclust, fatdata;
+
+	/* GEMDOS always uses a 12 bit FAT on floppies, and always a 16 bit FAT on
+	 * hard disks. So use 12 bit if the size of the filesystem suggests that
+	 * this fs is for a floppy disk, if the user hasn't explicitly requested a
+	 * size.
+	 */
+	if (!size_fat)
+	    size_fat = (num_sectors == 1440 || num_sectors == 2400 ||
+			num_sectors == 2880 || num_sectors == 5760) ? 12 : 16;
+	if (verbose >= 2)
+	    printf("Choosing %d bits for FAT\n", size_fat);
+
+	/* Atari format: cluster size should be 2, except explicitly requested by
+	 * the user, since GEMDOS doesn't like other cluster sizes very much.
+	 * Instead, tune the sector size for the FS to fit.
+	 */
+	bs.cluster_size = sectors_per_cluster ? sectors_per_cluster : 2;
+	if (!sector_size_set) {
+	    while (num_sectors > GEMDOS_MAX_SECTORS) {
+		num_sectors >>= 1;
+		sector_size <<= 1;
+	    }
+	}
+	if (verbose >= 2)
+	    printf("Sector size must be %d to have less than %d log. sectors\n",
+		   sector_size, GEMDOS_MAX_SECTORS);
+
+	/* Check if there are enough FAT indices for how much clusters we have */
+	do {
+	    fatdata = num_sectors - cdiv(root_dir_entries * 32, sector_size) -
+		reserved_sectors;
+	    /* The factor 2 below avoids cut-off errors for nr_fats == 1 and
+	     * size_fat == 12
+	     * The "2*nr_fats*size_fat/8" is for the reserved first two FAT entries
+	     */
+	    clusters =
+		(2 *
+		 ((long long)fatdata * sector_size -
+		  2 * nr_fats * size_fat / 8)) / (2 * ((int)bs.cluster_size *
+						       sector_size +
+						       nr_fats * size_fat / 8));
+	    fat_length = cdiv((clusters + 2) * size_fat / 8, sector_size);
+	    /* Need to recalculate number of clusters, since the unused parts of the
+	     * FATS and data area together could make up space for an additional,
+	     * not really present cluster. */
+	    clusters = (fatdata - nr_fats * fat_length) / bs.cluster_size;
+	    maxclust = (fat_length * sector_size * 8) / size_fat;
+	    if (verbose >= 2)
+		printf("ss=%d: #clu=%d, fat_len=%d, maxclu=%d\n",
+		       sector_size, clusters, fat_length, maxclust);
+
+	    /* last 10 cluster numbers are special (except FAT32: 4 high bits rsvd);
+	     * first two numbers are reserved */
+	    if (maxclust <=
+		(size_fat == 32 ? MAX_CLUST_32 : (1 << size_fat) - 0x10)
+		&& clusters <= maxclust - 2)
+		break;
+	    if (verbose >= 2)
+		printf(clusters > maxclust - 2 ?
+		       "Too many clusters\n" : "FAT too big\n");
+
+	    /* need to increment sector_size once more to  */
+	    if (sector_size_set)
+		die("With this sector size, the maximum number of FAT entries "
+		    "would be exceeded.");
+	    num_sectors >>= 1;
+	    sector_size <<= 1;
+	} while (sector_size <= GEMDOS_MAX_SECTOR_SIZE);
+
+	if (sector_size > GEMDOS_MAX_SECTOR_SIZE)
+	    die("Would need a sector size > 16k, which GEMDOS can't work with");
+
+	cluster_count = clusters;
+	if (size_fat != 32)
+	    bs.fat_length = htole16(fat_length);
+	else {
+	    bs.fat_length = 0;
+	    bs.fat32.fat32_length = htole32(fat_length);
+	}
+    }
+
+    bs.sector_size[0] = (char)(sector_size & 0x00ff);
+    bs.sector_size[1] = (char)((sector_size & 0xff00) >> 8);
+
+    bs.dir_entries[0] = (char)(root_dir_entries & 0x00ff);
+    bs.dir_entries[1] = (char)((root_dir_entries & 0xff00) >> 8);
+
+    if (size_fat == 32) {
+	/* set up additional FAT32 fields */
+	bs.fat32.flags = htole16(0);
+	bs.fat32.version[0] = 0;
+	bs.fat32.version[1] = 0;
+	bs.fat32.root_cluster = htole32(2);
+	bs.fat32.info_sector = htole16(1);
+	if (!backup_boot)
+	    backup_boot = (reserved_sectors >= 7) ? 6 :
+		(reserved_sectors >= 2) ? reserved_sectors - 1 : 0;
+	else {
+	    if (backup_boot == 1)
+		die("Backup boot sector must be after sector 1");
+	    else if (backup_boot >= reserved_sectors)
+		die("Backup boot sector must be a reserved sector");
+	}
+	if (verbose >= 2)
+	    printf("Using sector %d as backup boot sector (0 = none)\n",
+		   backup_boot);
+	bs.fat32.backup_boot = htole16(backup_boot);
+	memset(&bs.fat32.reserved2, 0, sizeof(bs.fat32.reserved2));
+    }
+
+    if (atari_format) {
+	/* Just some consistency checks */
+	if (num_sectors >= GEMDOS_MAX_SECTORS)
+	    die("GEMDOS can't handle more than 65531 sectors");
+	else if (num_sectors >= OLDGEMDOS_MAX_SECTORS)
+	    printf("Warning: More than 32765 sector need TOS 1.04 "
+		   "or higher.\n");
+    }
+    if (num_sectors >= 65536) {
+	bs.sectors[0] = (char)0;
+	bs.sectors[1] = (char)0;
+	bs.total_sect = htole32(num_sectors);
+    } else {
+	bs.sectors[0] = (char)(num_sectors & 0x00ff);
+	bs.sectors[1] = (char)((num_sectors & 0xff00) >> 8);
+	if (!atari_format)
+	    bs.total_sect = htole32(0);
+    }
+
+    if (!atari_format)
+	vi->ext_boot_sign = MSDOS_EXT_SIGN;
+
+    if (!cluster_count) {
+	if (sectors_per_cluster)	/* If yes, die if we'd spec'd sectors per cluster */
+	    die("Too many clusters for filesystem - try more sectors per cluster");
+	else
+	    die("Attempting to create a too large filesystem");
+    }
+
+    /* The two following vars are in hard sectors, i.e. 512 byte sectors! */
+    start_data_sector = (reserved_sectors + nr_fats * fat_length) *
+	(sector_size / HARD_SECTOR_SIZE);
+    start_data_block = (start_data_sector + SECTORS_PER_BLOCK - 1) /
+	SECTORS_PER_BLOCK;
+
+    if (blocks < start_data_block + 32)	/* Arbitrary undersize filesystem! */
+	die("Too few blocks for viable filesystem");
+
+    if (verbose) {
+	printf("%s has %d head%s and %d sector%s per track,\n",
+	       device_name, le16toh(bs.heads),
+	       (le16toh(bs.heads) != 1) ? "s" : "", le16toh(bs.secs_track),
+	       (le16toh(bs.secs_track) != 1) ? "s" : "");
+	printf("hidden sectors 0x%04x;\n",  hidden_sectors);
+	printf("logical sector size is %d,\n", sector_size);
+	printf("using 0x%02x media descriptor, with %d sectors;\n",
+	       (int)(bs.media), num_sectors);
+	printf("drive number 0x%02x;\n", (int) (vi->drive_number));
+	printf("filesystem has %d %d-bit FAT%s and %d sector%s per cluster.\n",
+	       (int)(bs.fats), size_fat, (bs.fats != 1) ? "s" : "",
+	       (int)(bs.cluster_size), (bs.cluster_size != 1) ? "s" : "");
+	printf("FAT size is %d sector%s, and provides %d cluster%s.\n",
+	       fat_length, (fat_length != 1) ? "s" : "",
+	       cluster_count, (cluster_count != 1) ? "s" : "");
+	printf("There %s %u reserved sector%s.\n",
+	       (reserved_sectors != 1) ? "are" : "is",
+	       reserved_sectors, (reserved_sectors != 1) ? "s" : "");
+
+	if (size_fat != 32) {
+	    unsigned root_dir_entries =
+		bs.dir_entries[0] + ((bs.dir_entries[1]) * 256);
+	    unsigned root_dir_sectors =
+		cdiv(root_dir_entries * 32, sector_size);
+	    printf("Root directory contains %u slots and uses %u sectors.\n",
+		   root_dir_entries, root_dir_sectors);
+	}
+	printf("Volume ID is %08lx, ", volume_id &
+	       (atari_format ? 0x00ffffff : 0xffffffff));
+	if (strcmp(volume_name, NO_NAME))
+	    printf("volume label %s.\n", volume_name);
+	else
+	    printf("no volume label.\n");
+    }
+
+    /* Make the file allocation tables! */
+
+    if (malloc_entire_fat)
+	alloced_fat_length = fat_length;
+    else
+	alloced_fat_length = 1;
+
+    if ((fat =
+	 (unsigned char *)malloc(alloced_fat_length * sector_size)) == NULL)
+	die("unable to allocate space for FAT image in memory");
+
+    memset(fat, 0, alloced_fat_length * sector_size);
+
+    mark_FAT_cluster(0, 0xffffffff);	/* Initial fat entries */
+    mark_FAT_cluster(1, 0xffffffff);
+    fat[0] = (unsigned char)bs.media;	/* Put media type in first byte! */
+    if (size_fat == 32) {
+	/* Mark cluster 2 as EOF (used for root dir) */
+	mark_FAT_cluster(2, FAT_EOF);
+    }
+
+    /* Make the root directory entries */
+
+    size_root_dir = (size_fat == 32) ?
+	bs.cluster_size * sector_size :
+	(((int)bs.dir_entries[1] * 256 + (int)bs.dir_entries[0]) *
+	 sizeof(struct msdos_dir_entry));
+    if ((root_dir = (struct msdos_dir_entry *)malloc(size_root_dir)) == NULL) {
+	free(fat);		/* Tidy up before we die! */
+	die("unable to allocate space for root directory in memory");
+    }
+
+    memset(root_dir, 0, size_root_dir);
+    if (memcmp(volume_name, NO_NAME, 11)) {
+	struct msdos_dir_entry *de = &root_dir[0];
+	memcpy(de->name, volume_name, 8);
+	memcpy(de->ext, volume_name + 8, 3);
+	de->attr = ATTR_VOLUME;
+	if (!invariant)
+		ctime = localtime(&create_time);
+	else
+		ctime = gmtime(&create_time);
+	de->time = htole16((unsigned short)((ctime->tm_sec >> 1) +
+					    (ctime->tm_min << 5) +
+					    (ctime->tm_hour << 11)));
+	de->date =
+	    htole16((unsigned short)(ctime->tm_mday +
+				     ((ctime->tm_mon + 1) << 5) +
+				     ((ctime->tm_year - 80) << 9)));
+	de->ctime_cs = 0;
+	de->ctime = de->time;
+	de->cdate = de->date;
+	de->adate = de->date;
+	de->starthi = htole16(0);
+	de->start = htole16(0);
+	de->size = htole32(0);
+    }
+
+    if (size_fat == 32) {
+	/* For FAT32, create an info sector */
+	struct fat32_fsinfo *info;
+
+	if (!(info_sector = malloc(sector_size)))
+	    die("Out of memory");
+	memset(info_sector, 0, sector_size);
+	/* fsinfo structure is at offset 0x1e0 in info sector by observation */
+	info = (struct fat32_fsinfo *)(info_sector + 0x1e0);
+
+	/* Info sector magic */
+	info_sector[0] = 'R';
+	info_sector[1] = 'R';
+	info_sector[2] = 'a';
+	info_sector[3] = 'A';
+
+	/* Magic for fsinfo structure */
+	info->signature = htole32(0x61417272);
+	/* We've allocated cluster 2 for the root dir. */
+	info->free_clusters = htole32(cluster_count - 1);
+	info->next_cluster = htole32(2);
+
+	/* Info sector also must have boot sign */
+	*(uint16_t *) (info_sector + 0x1fe) = htole16(BOOT_SIGN);
+    }
+
+    if (!(blank_sector = malloc(sector_size)))
+	die("Out of memory");
+    memset(blank_sector, 0, sector_size);
+}
+
+/* Write the new filesystem's data tables to wherever they're going to end up! */
+
+#define error(str)				\
+  do {						\
+    free (fat);					\
+    if (info_sector) free (info_sector);	\
+    free (root_dir);				\
+    die (str);					\
+  } while(0)
+
+#define seekto(pos,errstr)						\
+  do {									\
+    loff_t __pos = (pos);						\
+    if (llseek (dev, __pos, SEEK_SET) != __pos)				\
+	error ("seek to " errstr " failed whilst writing tables");	\
+  } while(0)
+
+#define writebuf(buf,size,errstr)			\
+  do {							\
+    int __size = (size);				\
+    if (write (dev, buf, __size) != __size)		\
+	error ("failed whilst writing " errstr);	\
+  } while(0)
+
+static void write_tables(void)
+{
+    int x;
+    int fat_length;
+
+    fat_length = (size_fat == 32) ?
+	le32toh(bs.fat32.fat32_length) : le16toh(bs.fat_length);
+
+    seekto(0, "start of device");
+    /* clear all reserved sectors */
+    for (x = 0; x < reserved_sectors; ++x)
+	writebuf(blank_sector, sector_size, "reserved sector");
+    /* seek back to sector 0 and write the boot sector */
+    seekto(0, "boot sector");
+    writebuf((char *)&bs, sizeof(struct msdos_boot_sector), "boot sector");
+    /* on FAT32, write the info sector and backup boot sector */
+    if (size_fat == 32) {
+	seekto(le16toh(bs.fat32.info_sector) * sector_size, "info sector");
+	writebuf(info_sector, 512, "info sector");
+	if (backup_boot != 0) {
+	    seekto(backup_boot * sector_size, "backup boot sector");
+	    writebuf((char *)&bs, sizeof(struct msdos_boot_sector),
+		     "backup boot sector");
+	}
+    }
+    /* seek to start of FATS and write them all */
+    seekto(reserved_sectors * sector_size, "first FAT");
+    for (x = 1; x <= nr_fats; x++) {
+	int y;
+	int blank_fat_length = fat_length - alloced_fat_length;
+	writebuf(fat, alloced_fat_length * sector_size, "FAT");
+	for (y = 0; y < blank_fat_length; y++)
+	    writebuf(blank_sector, sector_size, "FAT");
+    }
+    /* Write the root directory directly after the last FAT. This is the root
+     * dir area on FAT12/16, and the first cluster on FAT32. */
+    writebuf((char *)root_dir, size_root_dir, "root directory");
+
+    if (blank_sector)
+	free(blank_sector);
+    if (info_sector)
+	free(info_sector);
+    free(root_dir);		/* Free up the root directory space from setup_tables */
+    free(fat);			/* Free up the fat table space reserved during setup_tables */
+}
+
+/* Report the command usage and exit with the given error code */
+
+static void usage(int exitval)
+{
+    fprintf(stderr, "\
+Usage: mkfs.fat [-a][-A][-c][-C][-v][-I][-l bad-block-file][-b backup-boot-sector]\n\
+       [-m boot-msg-file][-n volume-name][-i volume-id]\n\
+       [-s sectors-per-cluster][-S logical-sector-size][-f number-of-FATs]\n\
+       [-h hidden-sectors][-F fat-size][-r root-dir-entries][-R reserved-sectors]\n\
+       [-M FAT-media-byte][-D drive_number]\n\
+       [--invariant]\n\
+       [--help]\n\
+       /dev/name [blocks]\n");
+    exit(exitval);
+}
+
+/*
+ * ++roman: On m68k, check if this is an Atari; if yes, turn on Atari variant
+ * of MS-DOS filesystem by default.
+ */
+static void check_atari(void)
+{
+#ifdef __mc68000__
+    FILE *f;
+    char line[128], *p;
+
+    if (!(f = fopen("/proc/hardware", "r"))) {
+	perror("/proc/hardware");
+	return;
+    }
+
+    while (fgets(line, sizeof(line), f)) {
+	if (strncmp(line, "Model:", 6) == 0) {
+	    p = line + 6;
+	    p += strspn(p, " \t");
+	    if (strncmp(p, "Atari ", 6) == 0)
+		atari_format = 1;
+	    break;
+	}
+    }
+    fclose(f);
+#endif
+}
+
+/* The "main" entry point into the utility - we pick up the options and attempt to process them in some sort of sensible
+   way.  In the event that some/all of the options are invalid we need to tell the user so that something can be done! */
+
+int main(int argc, char **argv)
+{
+    int c;
+    char *tmp;
+    char *listfile = NULL;
+    FILE *msgfile;
+    struct stat statbuf;
+    int i = 0, pos, ch;
+    int create = 0;
+    uint64_t cblocks = 0;
+    int min_sector_size;
+    int bad_block_count = 0;
+    struct timeval create_timeval;
+
+    enum {OPT_HELP=1000, OPT_INVARIANT,};
+    const struct option long_options[] = {
+	    {"help", no_argument, NULL, OPT_HELP},
+	    {"invariant", no_argument, NULL, OPT_INVARIANT},
+	    {0,}
+    };
+
+    if (argc && *argv) {	/* What's the program name? */
+	char *p;
+	program_name = *argv;
+	if ((p = strrchr(program_name, '/')))
+	    program_name = p + 1;
+    }
+
+    gettimeofday(&create_timeval, NULL);
+    create_time = create_timeval.tv_sec;
+    volume_id = (uint32_t) ((create_timeval.tv_sec << 20) | create_timeval.tv_usec);	/* Default volume ID = creation time, fudged for more uniqueness */
+    check_atari();
+
+    printf("mkfs.fat " VERSION " (" VERSION_DATE ")\n");
+
+    while ((c = getopt_long(argc, argv, "aAb:cCf:D:F:Ii:l:m:M:n:r:R:s:S:h:v",
+				    long_options, NULL)) != -1)
+	/* Scan the command line for options */
+	switch (c) {
+	case 'A':		/* toggle Atari format */
+	    atari_format = !atari_format;
+	    break;
+
+	case 'a':		/* a : skip alignment */
+	    align_structures = FALSE;
+	    break;
+
+	case 'b':		/* b : location of backup boot sector */
+	    backup_boot = (int)strtol(optarg, &tmp, 0);
+	    if (*tmp || backup_boot < 2 || backup_boot > 0xffff) {
+		printf("Bad location for backup boot sector : %s\n", optarg);
+		usage(1);
+	    }
+	    break;
+
+	case 'c':		/* c : Check FS as we build it */
+	    check = TRUE;
+	    malloc_entire_fat = TRUE;	/* Need to be able to mark clusters bad */
+	    break;
+
+	case 'C':		/* C : Create a new file */
+	    create = TRUE;
+	    break;
+
+	case 'D':		/* D : Choose Drive Number */
+	    drive_number_option = (int) strtol (optarg, &tmp, 0);
+	    if (*tmp || (drive_number_option != 0 && drive_number_option != 0x80)) {
+		printf ("Drive number must be 0 or 0x80: %s\n", optarg);
+		usage(1);
+	    }
+	    drive_number_by_user=1;
+	    break;
+
+	case 'f':		/* f : Choose number of FATs */
+	    nr_fats = (int)strtol(optarg, &tmp, 0);
+	    if (*tmp || nr_fats < 1 || nr_fats > 4) {
+		printf("Bad number of FATs : %s\n", optarg);
+		usage(1);
+	    }
+	    break;
+
+	case 'F':		/* F : Choose FAT size */
+	    size_fat = (int)strtol(optarg, &tmp, 0);
+	    if (*tmp || (size_fat != 12 && size_fat != 16 && size_fat != 32)) {
+		printf("Bad FAT type : %s\n", optarg);
+		usage(1);
+	    }
+	    size_fat_by_user = 1;
+	    break;
+
+	case 'h':		/* h : number of hidden sectors */
+	    hidden_sectors = (int)strtol(optarg, &tmp, 0);
+	    if (*tmp || hidden_sectors < 0) {
+		printf("Bad number of hidden sectors : %s\n", optarg);
+		usage(1);
+	    }
+	    hidden_sectors_by_user = 1;
+	    break;
+
+	case 'I':
+	    ignore_full_disk = 1;
+	    break;
+
+	case 'i':		/* i : specify volume ID */
+	    volume_id = strtoul(optarg, &tmp, 16);
+	    if (*tmp) {
+		printf("Volume ID must be a hexadecimal number\n");
+		usage(1);
+	    }
+	    break;
+
+	case 'l':		/* l : Bad block filename */
+	    listfile = optarg;
+	    malloc_entire_fat = TRUE;	/* Need to be able to mark clusters bad */
+	    break;
+
+	case 'm':		/* m : Set boot message */
+	    if (strcmp(optarg, "-")) {
+		msgfile = fopen(optarg, "r");
+		if (!msgfile)
+		    perror(optarg);
+	    } else
+		msgfile = stdin;
+
+	    if (msgfile) {
+		/* The boot code ends at offset 448 and needs a null terminator */
+		i = MESSAGE_OFFSET;
+		pos = 0;	/* We are at beginning of line */
+		do {
+		    ch = getc(msgfile);
+		    switch (ch) {
+		    case '\r':	/* Ignore CRs */
+		    case '\0':	/* and nulls */
+			break;
+
+		    case '\n':	/* LF -> CR+LF if necessary */
+			if (pos) {	/* If not at beginning of line */
+			    dummy_boot_code[i++] = '\r';
+			    pos = 0;
+			}
+			dummy_boot_code[i++] = '\n';
+			break;
+
+		    case '\t':	/* Expand tabs */
+			do {
+			    dummy_boot_code[i++] = ' ';
+			    pos++;
+			}
+			while (pos % 8 && i < BOOTCODE_SIZE - 1);
+			break;
+
+		    case EOF:
+			dummy_boot_code[i++] = '\0';	/* Null terminator */
+			break;
+
+		    default:
+			dummy_boot_code[i++] = ch;	/* Store character */
+			pos++;	/* Advance position */
+			break;
+		    }
+		}
+		while (ch != EOF && i < BOOTCODE_SIZE - 1);
+
+		/* Fill up with zeros */
+		while (i < BOOTCODE_SIZE - 1)
+		    dummy_boot_code[i++] = '\0';
+		dummy_boot_code[BOOTCODE_SIZE - 1] = '\0';	/* Just in case */
+
+		if (ch != EOF)
+		    printf("Warning: message too long; truncated\n");
+
+		if (msgfile != stdin)
+		    fclose(msgfile);
+	    }
+	    break;
+
+	case 'M':		/* M : FAT Media byte */
+	    fat_media_byte = (int)strtol(optarg, &tmp, 0);
+	    if (*tmp) {
+		printf("Bad number for media descriptor : %s\n", optarg);
+		usage(1);
+	    }
+	    if (fat_media_byte != 0xf0 && (fat_media_byte < 0xf8 || fat_media_byte > 0xff)) {
+		printf("FAT Media byte must either be between 0xF8 and 0xFF or be 0xF0 : %s\n", optarg);
+		usage(1);
+	    }
+	    break;
+
+	case 'n':		/* n : Volume name */
+	    sprintf(volume_name, "%-11.11s", optarg);
+	    for (i = 0; volume_name[i] && i < 11; i++)
+		/* don't know if here should be more strict !uppercase(label[i]) */
+		if (islower(volume_name[i])) {
+		    fprintf(stderr,
+		            "mkfs.fat: warning - lowercase labels might not work properly with DOS or Windows\n");
+		    break;
+		}
+
+	    break;
+
+	case 'r':		/* r : Root directory entries */
+	    root_dir_entries = (int)strtol(optarg, &tmp, 0);
+	    if (*tmp || root_dir_entries < 16 || root_dir_entries > 32768) {
+		printf("Bad number of root directory entries : %s\n", optarg);
+		usage(1);
+	    }
+	    break;
+
+	case 'R':		/* R : number of reserved sectors */
+	    reserved_sectors = (int)strtol(optarg, &tmp, 0);
+	    if (*tmp || reserved_sectors < 1 || reserved_sectors > 0xffff) {
+		printf("Bad number of reserved sectors : %s\n", optarg);
+		usage(1);
+	    }
+	    break;
+
+	case 's':		/* s : Sectors per cluster */
+	    sectors_per_cluster = (int)strtol(optarg, &tmp, 0);
+	    if (*tmp || (sectors_per_cluster != 1 && sectors_per_cluster != 2
+			 && sectors_per_cluster != 4 && sectors_per_cluster != 8
+			 && sectors_per_cluster != 16
+			 && sectors_per_cluster != 32
+			 && sectors_per_cluster != 64
+			 && sectors_per_cluster != 128)) {
+		printf("Bad number of sectors per cluster : %s\n", optarg);
+		usage(1);
+	    }
+	    break;
+
+	case 'S':		/* S : Sector size */
+	    sector_size = (int)strtol(optarg, &tmp, 0);
+	    if (*tmp || (sector_size != 512 && sector_size != 1024 &&
+			 sector_size != 2048 && sector_size != 4096 &&
+			 sector_size != 8192 && sector_size != 16384 &&
+			 sector_size != 32768)) {
+		printf("Bad logical sector size : %s\n", optarg);
+		usage(1);
+	    }
+	    sector_size_set = 1;
+	    break;
+
+	case 'v':		/* v : Verbose execution */
+	    ++verbose;
+	    break;
+
+	case OPT_HELP:
+	    usage(0);
+	    break;
+
+	case OPT_INVARIANT:
+	    invariant = 1;
+	    volume_id = 0x1234abcd;
+	    create_time = 1426325213;
+	    break;
+
+	default:
+	    printf("Unknown option: %c\n", c);
+	    usage(1);
+	}
+    if (optind < argc) {
+	device_name = argv[optind];	/* Determine the number of blocks in the FS */
+
+	if (!device_name) {
+	    printf("No device specified.\n");
+	    usage(1);
+	}
+
+	if (!create)
+	    cblocks = count_blocks(device_name, &orphaned_sectors);	/*  Have a look and see! */
+    }
+    if (optind == argc - 2) {	/*  Either check the user specified number */
+	blocks = strtoull(argv[optind + 1], &tmp, 0);
+	if (!create && blocks != cblocks) {
+	    fprintf(stderr, "Warning: block count mismatch: ");
+	    fprintf(stderr, "found %llu but assuming %llu.\n", (unsigned long long)cblocks, (unsigned long long)blocks);
+	}
+	if (*tmp)
+	    bad_block_count = 1;
+    } else if (optind == argc - 1) {	/*  Or use value found */
+	if (create)
+	    die("Need intended size with -C.");
+	blocks = cblocks;
+    } else {
+	fprintf(stderr, "No device specified!\n");
+	usage(1);
+    }
+    if (bad_block_count) {
+	printf("Bad block count : %s\n", argv[optind + 1]);
+	usage(1);
+    }
+
+    if (check && listfile)	/* Auto and specified bad block handling are mutually */
+	die("-c and -l are incompatible");	/* exclusive of each other! */
+
+    if (!create) {
+	check_mount(device_name);	/* Is the device already mounted? */
+	dev = open(device_name, O_EXCL | O_RDWR);	/* Is it a suitable device to build the FS on? */
+	if (dev < 0) {
+	    fprintf(stderr, "%s: unable to open %s: %s\n", program_name,
+		    device_name, strerror(errno));
+	    exit(1);		/* The error exit code is 1! */
+	}
+    } else {
+	/* create the file */
+	dev = open(device_name, O_EXCL | O_RDWR | O_CREAT, 0666);
+	if (dev < 0) {
+	    if (errno == EEXIST)
+		die("file %s already exists");
+	    else
+		die("unable to create %s");
+	}
+	/* expand to desired size */
+	if (ftruncate(dev, blocks * BLOCK_SIZE))
+	    die("unable to resize %s");
+    }
+
+    if (fstat(dev, &statbuf) < 0)
+	die("unable to stat %s");
+    if (!S_ISBLK(statbuf.st_mode)) {
+	statbuf.st_rdev = 0;
+	check = 0;
+    } else
+	/*
+	 * Ignore any 'full' fixed disk devices, if -I is not given.
+	 * On a MO-disk one doesn't need partitions.  The filesytem can go
+	 * directly to the whole disk.  Under other OSes this is known as
+	 * the 'superfloppy' format.  As I don't know how to find out if
+	 * this is a MO disk I introduce a -I (ignore) switch.  -Joey
+	 */
+	if (!ignore_full_disk && ((statbuf.st_rdev & 0xffffff3f) == 0x0300 ||	/* hda, hdb */
+				  (statbuf.st_rdev & 0xffffff0f) == 0x0800 ||	/* sd */
+				  (statbuf.st_rdev & 0xffffff3f) == 0x0d00 ||	/* xd */
+				  (statbuf.st_rdev & 0xffffff3f) == 0x1600)	/* hdc, hdd */
+	)
+	die("Device partition expected, not making filesystem on entire device '%s' (use -I to override)");
+
+    if (sector_size_set) {
+	if (ioctl(dev, BLKSSZGET, &min_sector_size) >= 0)
+	    if (sector_size < min_sector_size) {
+		sector_size = min_sector_size;
+		fprintf(stderr,
+			"Warning: sector size was set to %d (minimal for this device)\n",
+			sector_size);
+	    }
+    } else {
+	if (ioctl(dev, BLKSSZGET, &min_sector_size) >= 0) {
+	    sector_size = min_sector_size;
+	    sector_size_set = 1;
+	}
+    }
+
+    if (sector_size > 4096)
+	fprintf(stderr,
+		"Warning: sector size is set to %d > 4096, such filesystem will not propably mount\n",
+		sector_size);
+
+    establish_params(statbuf.st_rdev, statbuf.st_size);
+    /* Establish the media parameters */
+
+    setup_tables();		/* Establish the filesystem tables */
+
+    if (check)			/* Determine any bad block locations and mark them */
+	check_blocks();
+    else if (listfile)
+	get_list_blocks(listfile);
+
+    write_tables();		/* Write the filesystem tables away! */
+
+    exit(0);			/* Terminate with no errors! */
+}
diff --git a/dosfstools/src/msdos_fs.h b/dosfstools/src/msdos_fs.h
new file mode 100644
index 0000000..54b2a34
--- /dev/null
+++ b/dosfstools/src/msdos_fs.h
@@ -0,0 +1,61 @@
+/* msdos_fs.h - MS-DOS filesystem constants/structures
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+#ifndef _MSDOS_FS_H
+#define _MSDOS_FS_H
+
+#include <stdint.h>
+
+#define SECTOR_SIZE 512		/* sector size (bytes) */
+#define MSDOS_DPS (SECTOR_SIZE / sizeof(struct msdos_dir_entry))
+#define MSDOS_DPS_BITS 4	/* log2(MSDOS_DPS) */
+#define MSDOS_DIR_BITS 5	/* log2(sizeof(struct msdos_dir_entry)) */
+
+#define ATTR_NONE 0	/* no attribute bits */
+#define ATTR_RO 1	/* read-only */
+#define ATTR_HIDDEN 2	/* hidden */
+#define ATTR_SYS 4	/* system */
+#define ATTR_VOLUME 8	/* volume label */
+#define ATTR_DIR 16	/* directory */
+#define ATTR_ARCH 32	/* archived */
+
+/* attribute bits that are copied "as is" */
+#define ATTR_UNUSED (ATTR_VOLUME | ATTR_ARCH | ATTR_SYS | ATTR_HIDDEN)
+
+#define DELETED_FLAG 0xe5	/* marks file as deleted when in name[0] */
+#define IS_FREE(n) (!*(n) || *(n) == DELETED_FLAG)
+
+#define MSDOS_NAME 11			/* maximum name length */
+#define MSDOS_DOT ".          "		/* ".", padded to MSDOS_NAME chars */
+#define MSDOS_DOTDOT "..         "	/* "..", padded to MSDOS_NAME chars */
+
+struct msdos_dir_entry {
+    uint8_t name[8], ext[3];	/* name and extension */
+    uint8_t attr;		/* attribute bits */
+    uint8_t lcase;		/* Case for base and extension */
+    uint8_t ctime_cs;		/* Creation time, centiseconds (0-199) */
+    uint16_t ctime;		/* Creation time */
+    uint16_t cdate;		/* Creation date */
+    uint16_t adate;		/* Last access date */
+    uint16_t starthi;		/* High 16 bits of cluster in FAT32 */
+    uint16_t time, date, start;	/* time, date and first cluster */
+    uint32_t size;		/* file size (in bytes) */
+} __attribute__ ((packed));
+
+#endif /* _MSDOS_FS_H */
diff --git a/dosfstools/src/version.h b/dosfstools/src/version.h
new file mode 100644
index 0000000..f0716d3
--- /dev/null
+++ b/dosfstools/src/version.h
@@ -0,0 +1,29 @@
+/* version.h
+
+   Copyright (C) 1998-2005 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+   Copyright (C) 2008-2014 Daniel Baumann <mail@daniel-baumann.ch>
+
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.
+
+   The complete text of the GNU General Public License
+   can be found in /usr/share/common-licenses/GPL-3 file.
+*/
+
+#ifndef _version_h
+#define _version_h
+
+#define VERSION "3.0.28"
+#define VERSION_DATE "2015-05-16"
+
+#endif
diff --git a/edify/Android.mk b/edify/Android.mk
new file mode 100644
index 0000000..d8058c1
--- /dev/null
+++ b/edify/Android.mk
@@ -0,0 +1,58 @@
+# Copyright 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+edify_src_files := \
+    lexer.ll \
+    parser.yy \
+    expr.cpp
+
+#
+# Build the host-side command line tool (host executable)
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    $(edify_src_files) \
+    edify_parser.cpp
+
+LOCAL_CFLAGS := -Werror
+LOCAL_CPPFLAGS := -g -O0
+LOCAL_MODULE := edify_parser
+LOCAL_YACCFLAGS := -v
+LOCAL_CPPFLAGS += -Wno-unused-parameter
+LOCAL_CPPFLAGS += -Wno-deprecated-register
+LOCAL_CLANG := true
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
+LOCAL_STATIC_LIBRARIES += libbase
+
+include $(BUILD_HOST_EXECUTABLE)
+
+#
+# Build the device-side library (static library)
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(edify_src_files)
+
+LOCAL_CFLAGS := -Werror
+LOCAL_CPPFLAGS := -Wno-unused-parameter
+LOCAL_CPPFLAGS += -Wno-deprecated-register
+LOCAL_MODULE := libedify
+LOCAL_CLANG := true
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
+LOCAL_STATIC_LIBRARIES += libbase
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/etc/Android.mk b/etc/Android.mk
new file mode 100755
index 0000000..4aa769c
--- /dev/null
+++ b/etc/Android.mk
@@ -0,0 +1,227 @@
+# Copyright (C) 2015 TeamWin Recovery Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+ifneq ($(TW_EXCLUDE_DEFAULT_USB_INIT), true)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := init.recovery.usb.rc
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+# Cannot send to TARGET_RECOVERY_ROOT_OUT since build system wipes init*.rc
+# during ramdisk creation and only allows init.recovery.*.rc files to be copied
+# from TARGET_ROOT_OUT thereafter
+LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
+
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+endif
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := init.recovery.service.rc
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
+
+LOCAL_SRC_FILES := init.recovery.service22.rc
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := init.recovery.hlthchrg.rc
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
+
+LOCAL_SRC_FILES := init.recovery.hlthchrg26.rc
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := init.recovery.ldconfig.rc
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
+
+LOCAL_SRC_FILES := init.recovery.ldconfig.rc
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := nano.rc
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+
+LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+ifneq ($(filter $(AB_OTA_UPDATER) $(PRODUCT_USE_DYNAMIC_PARTITIONS) $(TW_INCLUDE_CRYPTO), true),)
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := hwservicemanager.rc
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := vndservicemanager.rc
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := keystore2.rc
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := android.system.keystore2-service.xml
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/vintf/manifest
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+endif
+
+ifeq ($(AB_OTA_UPDATER),true)
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := android.hardware.boot@1.0-service.rc
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := android.hardware.boot@1.1-service.rc
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := android.hardware.boot@1.1.xml
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/vendor/etc/vintf/manifest
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := android.hardware.boot@1.2-service.rc
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := android.hardware.boot@1.2.xml
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/vendor/etc/vintf/manifest
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+endif
+
+ifeq ($(PRODUCT_USE_DYNAMIC_PARTITIONS),true)
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := android.hardware.health@2.1-service.rc
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := android.hardware.health@2.1.xml
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/vendor/etc/vintf/manifest
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := android.hardware.health@2.0-service.rc
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := lpdumpd.rc
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+	LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+	include $(BUILD_PREBUILT)
+endif
+
+ifneq ($(TW_INCLUDE_CRYPTO),)
+	ifneq ($(TW_INCLUDE_CRYPTO_FBE),)
+		include $(CLEAR_VARS)
+		LOCAL_MODULE := servicemanager.rc
+		LOCAL_MODULE_TAGS := optional
+		LOCAL_MODULE_CLASS := EXECUTABLES
+		LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc/init
+
+		LOCAL_SRC_FILES := init/$(LOCAL_MODULE)
+		include $(BUILD_PREBUILT)
+	endif
+endif
+
+ifeq ($(TWRP_INCLUDE_LOGCAT), true)
+    ifeq ($(TARGET_USES_LOGD), true)
+
+        include $(CLEAR_VARS)
+        LOCAL_MODULE := init.recovery.logd.rc
+        LOCAL_MODULE_TAGS := optional
+        LOCAL_MODULE_CLASS := EXECUTABLES
+
+        # Cannot send to TARGET_RECOVERY_ROOT_OUT since build system wipes init*.rc
+        # during ramdisk creation and only allows init.recovery.*.rc files to be copied
+        # from TARGET_ROOT_OUT thereafter
+        LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
+
+        LOCAL_SRC_FILES := $(LOCAL_MODULE)
+        include $(BUILD_PREBUILT)
+    endif
+endif
+
+ifeq ($(TW_USE_TOOLBOX), true)
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := init.recovery.mksh.rc
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := EXECUTABLES
+
+    # Cannot send to TARGET_RECOVERY_ROOT_OUT since build system wipes init*.rc
+    # during ramdisk creation and only allows init.recovery.*.rc files to be copied
+    # from TARGET_ROOT_OUT thereafter
+    LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
+
+    LOCAL_SRC_FILES := $(LOCAL_MODULE)
+    include $(BUILD_PREBUILT)
+endif
diff --git a/etc/init.rc b/etc/init.rc
index e4afecf..c7e8874 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -1,9 +1,18 @@
+import /init.recovery.logd.rc
+import /init.recovery.ldconfig.rc
+import /init.recovery.mksh.rc
+import /init.recovery.usb.rc
+import /init.recovery.service.rc
+import /init.recovery.vold_decrypt.rc
 import /init.recovery.${ro.hardware}.rc
 
 on early-init
     # Set the security context of /postinstall if present.
     restorecon /postinstall
 
+    # ueventd wants to write to /acct
+    mount cgroup none /acct cpuacct
+    mkdir /acct/uid
     # Copy prebuilt ld.config.txt into linkerconfig directory
     copy /system/etc/ld.config.txt /linkerconfig/ld.config.txt
     chmod 444 /linkerconfig/ld.config.txt
@@ -13,6 +22,9 @@
     setprop sys.usb.configfs 0
 
 on init
+    export PATH /sbin:/system/bin
+    export LD_LIBRARY_PATH /system/lib64:/vendor/lib64/hw
+
     export ANDROID_ROOT /system
     export ANDROID_DATA /data
     export EXTERNAL_STORAGE /sdcard
@@ -24,30 +36,38 @@
     symlink /system/bin /bin
     symlink /system/etc /etc
 
+    mount cgroup none /acct cpuacct
+    mkdir /acct/uid
+
     mkdir /sdcard
     mkdir /system
     mkdir /data
-    mkdir /cache
+    symlink /data/cache /cache
     mkdir /sideload
     mkdir /mnt/system
     mount tmpfs tmpfs /tmp
 
     chown root shell /tmp
     chmod 0775 /tmp
+    mkdir /tmp/misc
+    mkdir /tmp/misc/keystore/
 
     write /proc/sys/kernel/panic_on_oops 1
     write /proc/sys/vm/max_map_count 1000000
 
-    # Mount binderfs
     mkdir /dev/binderfs
     mount binder binder /dev/binderfs stats=global
     chmod 0755 /dev/binderfs
-
-    symlink /dev/binderfs/binder /dev/binder
+    chmod 0666 /dev/binderfs/hwbinder
     chmod 0666 /dev/binderfs/binder
+    chmod 0666 /dev/binderfs/vndbinder
+    symlink /dev/binderfs/binder /dev/binder
+    symlink /dev/binderfs/hwbinder /dev/hwbinder
+    symlink /dev/binderfs/vndbinder /dev/vndbinder
 
-    # Start essential services
-    start servicemanager
+    # Create location for fs_mgr to store abbreviated output from filesystem
+    # checker programs.
+    mkdir /dev/fscklogs 0770 root system
 
 on boot
     ifup lo
@@ -58,7 +78,7 @@
     class_start hal
 
 on firmware_mounts_complete
-   rm /dev/.booting
+    rm /dev/.booting
 
 # Mount filesystems and start core system services.
 on late-init
@@ -73,23 +93,30 @@
     trigger early-boot
     trigger boot
 
+on post-fs-data
+    # Set fscklog permission
+    chown root system /dev/fscklogs/log
+    chmod 0770 /dev/fscklogs/log
+
 service ueventd /system/bin/ueventd
     critical
     seclabel u:r:ueventd:s0
 
-service charger /system/bin/charger
-    critical
-    seclabel u:r:charger:s0
-
-service recovery /system/bin/recovery
-    socket recovery stream 422 system system
-    seclabel u:r:recovery:s0
-
 service adbd /system/bin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery
     disabled
     socket adbd stream 660 system system
     seclabel u:r:adbd:s0
 
+# Always start adbd on userdebug and eng builds
+on property:ro.debuggable=1
+    #write /sys/class/android_usb/android0/enable 1
+    #start adbd
+    setprop service.adb.root 1
+
+# Always start adbd on userdebug and eng builds
+on fs && property:ro.debuggable=1
+    setprop sys.usb.config adb
+
 service fastbootd /system/bin/fastbootd
     disabled
     group system
@@ -120,7 +147,8 @@
     write /sys/class/android_usb/android0/iSerial ${ro.serialno}
 
 on fs
-    mkdir /dev/usb-ffs 0775 shell shell
+    mount pstore pstore /sys/fs/pstore
+    mkdir /dev/usb-ffs 0770 shell shell
     mkdir /dev/usb-ffs/adb 0770 shell shell
     mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000
     mkdir /dev/usb-ffs/fastboot 0770 system system
diff --git a/etc/init.recovery.hlthchrg25.rc b/etc/init.recovery.hlthchrg25.rc
new file mode 100755
index 0000000..99a8baa
--- /dev/null
+++ b/etc/init.recovery.hlthchrg25.rc
@@ -0,0 +1,5 @@
+# healthd for pre Android 8.0
+
+service healthd /system/bin/healthd -r
+    critical
+    seclabel u:r:healthd:s0
diff --git a/etc/init.recovery.hlthchrg26.rc b/etc/init.recovery.hlthchrg26.rc
new file mode 100644
index 0000000..8a49c86
--- /dev/null
+++ b/etc/init.recovery.hlthchrg26.rc
@@ -0,0 +1,5 @@
+# charger for Android 8.0 and up
+
+service charger /charger -r
+    critical
+    seclabel u:r:charger:s0
diff --git a/etc/init.recovery.ldconfig.rc b/etc/init.recovery.ldconfig.rc
new file mode 100755
index 0000000..c99f802
--- /dev/null
+++ b/etc/init.recovery.ldconfig.rc
@@ -0,0 +1,2 @@
+on fs
+    export LD_CONFIG_FILE /system/etc/ld.config.txt
diff --git a/etc/init.recovery.logd.rc b/etc/init.recovery.logd.rc
new file mode 100644
index 0000000..ed41391
--- /dev/null
+++ b/etc/init.recovery.logd.rc
@@ -0,0 +1,13 @@
+on init
+    start logd
+
+service logd /system/bin/logd
+    class core
+    socket logdr seqpacket 0666 root root
+    socket logdw dgram+passcred 0222 root root
+    file /proc/kmsg r
+    file /dev/kmsg w
+    user root
+    group root
+    capabilities SYSLOG AUDIT_CONTROL SETGID SETUID
+    seclabel u:r:logd:s0
diff --git a/etc/init.recovery.mksh.rc b/etc/init.recovery.mksh.rc
new file mode 100755
index 0000000..3afd946
--- /dev/null
+++ b/etc/init.recovery.mksh.rc
@@ -0,0 +1,2 @@
+on init
+    export ENV /etc/mkshrc
diff --git a/etc/init.recovery.service22.rc b/etc/init.recovery.service22.rc
new file mode 100755
index 0000000..7ba997a
--- /dev/null
+++ b/etc/init.recovery.service22.rc
@@ -0,0 +1,6 @@
+on boot
+
+# For starting recovery on 5.0 and newer
+service recovery /system/bin/recovery
+    socket recovery stream 422 system system
+    seclabel u:r:recovery:s0
diff --git a/etc/init.recovery.usb.rc b/etc/init.recovery.usb.rc
new file mode 100644
index 0000000..8ef20e0
--- /dev/null
+++ b/etc/init.recovery.usb.rc
@@ -0,0 +1,30 @@
+on fs
+    write /sys/class/android_usb/android0/enable 0
+    write /sys/class/android_usb/android0/idVendor 18D1
+    write /sys/class/android_usb/android0/idProduct D001
+    write /sys/class/android_usb/android0/f_ffs/aliases adb
+    write /sys/class/android_usb/android0/functions adb
+    write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer}
+    write /sys/class/android_usb/android0/iProduct ${ro.product.model}
+    write /sys/class/android_usb/android0/iSerial ${ro.serialno}
+
+on property:sys.usb.config=none
+    write /sys/class/android_usb/android0/enable 0
+    write /sys/class/android_usb/android0/bDeviceClass 0
+
+on property:sys.usb.config=mass_storage,adb
+    write /sys/class/android_usb/android0/enable 0
+    write /sys/class/android_usb/android0/functions ${sys.usb.config}
+    write /sys/class/android_usb/android0/enable 1
+
+on property:sys.usb.config=mtp,adb
+    write /sys/class/android_usb/android0/enable 0
+    write /sys/class/android_usb/android0/functions ${sys.usb.config}
+    write /sys/class/android_usb/android0/enable 1
+    start adbd
+
+on property:sys.usb.config=adb
+    write /sys/class/android_usb/android0/enable 0
+    write /sys/class/android_usb/android0/functions ${sys.usb.config}
+    write /sys/class/android_usb/android0/enable ${service.adb.root}
+    start adbd
diff --git a/etc/init/android.hardware.boot@1.0-service.rc b/etc/init/android.hardware.boot@1.0-service.rc
new file mode 100644
index 0000000..3050d9b
--- /dev/null
+++ b/etc/init/android.hardware.boot@1.0-service.rc
@@ -0,0 +1,6 @@
+service boot-hal-1-0 /system/bin/android.hardware.boot@1.0-service
+    user root
+    group root
+    setenv LD_LIBRARY_PATH /vendor/lib64:/vendor/lib:/system/lib64:/system/lib
+    disabled
+    seclabel u:r:recovery:s0
diff --git a/etc/init/android.hardware.boot@1.1-service.rc b/etc/init/android.hardware.boot@1.1-service.rc
new file mode 100644
index 0000000..c929d14
--- /dev/null
+++ b/etc/init/android.hardware.boot@1.1-service.rc
@@ -0,0 +1,5 @@
+service boot-hal-1-1 /system/bin/android.hardware.boot@1.1-service
+    user root
+    group root
+    disabled
+    seclabel u:r:recovery:s0
diff --git a/etc/init/android.hardware.boot@1.1.xml b/etc/init/android.hardware.boot@1.1.xml
new file mode 100644
index 0000000..83d5d2e
--- /dev/null
+++ b/etc/init/android.hardware.boot@1.1.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="hidl">
+        <name>android.hardware.boot</name>
+        <transport>hwbinder</transport>
+        <fqname>@1.1::IBootControl/default</fqname>
+    </hal>
+</manifest>
diff --git a/etc/init/android.hardware.boot@1.2-service.rc b/etc/init/android.hardware.boot@1.2-service.rc
new file mode 100644
index 0000000..17eea68
--- /dev/null
+++ b/etc/init/android.hardware.boot@1.2-service.rc
@@ -0,0 +1,5 @@
+service boot-hal-1-2 /system/bin/android.hardware.boot@1.2-service
+    user root
+    group root
+    disabled
+    seclabel u:r:recovery:s0
diff --git a/etc/init/android.hardware.boot@1.2.xml b/etc/init/android.hardware.boot@1.2.xml
new file mode 100644
index 0000000..ba91e8f
--- /dev/null
+++ b/etc/init/android.hardware.boot@1.2.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="hidl">
+        <name>android.hardware.boot</name>
+        <transport>hwbinder</transport>
+        <fqname>@1.2::IBootControl/default</fqname>
+    </hal>
+</manifest>
diff --git a/etc/init/android.hardware.health@2.0-service.rc b/etc/init/android.hardware.health@2.0-service.rc
new file mode 100644
index 0000000..ee14d8e
--- /dev/null
+++ b/etc/init/android.hardware.health@2.0-service.rc
@@ -0,0 +1,7 @@
+service health-hal-2-0 /system/bin/android.hardware.health@2.0-service
+    disabled
+    user root
+    group root
+    capabilities WAKE_ALARM
+    file /dev/kmsg w
+    seclabel u:r:recovery:s0
diff --git a/etc/init/android.hardware.health@2.1-service.rc b/etc/init/android.hardware.health@2.1-service.rc
new file mode 100644
index 0000000..6b0b041
--- /dev/null
+++ b/etc/init/android.hardware.health@2.1-service.rc
@@ -0,0 +1,7 @@
+service health-hal-2-1 /system/bin/android.hardware.health@2.1-service
+    disabled
+    user root
+    group root
+    capabilities WAKE_ALARM
+    file /dev/kmsg w
+    seclabel u:r:recovery:s0
diff --git a/etc/init/android.hardware.health@2.1.xml b/etc/init/android.hardware.health@2.1.xml
new file mode 100644
index 0000000..34fdca6
--- /dev/null
+++ b/etc/init/android.hardware.health@2.1.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="hidl">
+        <name>android.hardware.health</name>
+        <transport>hwbinder</transport>
+        <fqname>@2.1::IHealth/default</fqname>
+    </hal>
+</manifest>
diff --git a/etc/init/android.system.keystore2-service.xml b/etc/init/android.system.keystore2-service.xml
new file mode 100644
index 0000000..6b8d0cb
--- /dev/null
+++ b/etc/init/android.system.keystore2-service.xml
@@ -0,0 +1,9 @@
+<manifest version="1.0" type="framework">
+    <hal format="aidl">
+        <name>android.system.keystore2</name>
+        <interface>
+            <name>IKeystoreService</name>
+            <instance>default</instance>
+        </interface>
+    </hal>
+</manifest>
diff --git a/etc/init/hwservicemanager.rc b/etc/init/hwservicemanager.rc
new file mode 100644
index 0000000..bbafb6d
--- /dev/null
+++ b/etc/init/hwservicemanager.rc
@@ -0,0 +1,9 @@
+on init
+    start hwservicemanager
+
+service hwservicemanager /system/bin/hwservicemanager
+    user root
+    group root
+    onrestart setprop hwservicemanager.ready false
+    disabled
+    seclabel u:r:recovery:s0
diff --git a/etc/init/keystore2.rc b/etc/init/keystore2.rc
new file mode 100644
index 0000000..fa62662
--- /dev/null
+++ b/etc/init/keystore2.rc
@@ -0,0 +1,17 @@
+# Start the keystore2 service.
+# Keystore 2.0 changes its working directory to the first positional
+# command line option, i.e., /data/misc/keystore, where it stores its
+# database.
+# Keystore shall run as user keystore and groups keystore, readproc, and log.
+#
+# See system/core/init/README.md for information on the init.rc language.
+
+on late-init
+    start keystore2
+
+service keystore2 /system/bin/keystore2 /tmp/misc/keystore
+    class early_hal
+    user root
+    group keystore readproc log
+    writepid /dev/cpuset/foreground/tasks
+    seclabel u:r:recovery:s0
diff --git a/etc/init/lpdumpd.rc b/etc/init/lpdumpd.rc
new file mode 100644
index 0000000..63b6c9d
--- /dev/null
+++ b/etc/init/lpdumpd.rc
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+service lpdumpd /system/bin/lpdumpd
+    # TODO(b/129011369): make this killable by lmkd
+    oneshot
+    disabled
+    user root
+    group root
+    # On Launch devices, assume "super". On virtual devices, ${ro.boot.super_partition}
+    # might be something else.
+    file /dev/block/by-name/${ro.boot.super_partition:-super} r
+    # On retrofit devices, ${ro.boot.super_partition} is slot-suffixed.
+    # Use NO_SUCH_DEVICE_NO_SUCH_SUFFIX as default values so that host_init_verifier does
+    # not complain about missing sysprops
+    file /dev/block/by-name/${ro.boot.super_partition:-NO_SUCH_DEVICE}${ro.boot.slot_suffix:-_NO_SUCH_SUFFIX} r
+    seclabel u:r:recovery:s0
+
+on property:sys.lpdumpd=start
+    start lpdumpd
+
+on property:sys.lpdumpd=stop
+    stop lpdumpd
diff --git a/etc/init/nano.rc b/etc/init/nano.rc
new file mode 100644
index 0000000..daf8215
--- /dev/null
+++ b/etc/init/nano.rc
@@ -0,0 +1,3 @@
+on fs
+    export TERMINFO /system/etc/terminfo
+    export TERM bg1.25
diff --git a/etc/init/servicemanager.rc b/etc/init/servicemanager.rc
new file mode 100644
index 0000000..40ed84d
--- /dev/null
+++ b/etc/init/servicemanager.rc
@@ -0,0 +1,8 @@
+on init
+    start servicemanager
+
+service servicemanager /system/bin/servicemanager
+    user root
+    group root readproc
+    disabled
+    seclabel u:r:recovery:s0
diff --git a/etc/init/vndservicemanager.rc b/etc/init/vndservicemanager.rc
new file mode 100644
index 0000000..149a378
--- /dev/null
+++ b/etc/init/vndservicemanager.rc
@@ -0,0 +1,10 @@
+on init
+    start vndservicemanager
+
+service vndservicemanager /system/bin/vndservicemanager /dev/vndbinder
+    disabled
+    user root
+    group root readproc
+    writepid /dev/cpuset/system-background/tasks
+    shutdown critical
+    seclabel u:r:recovery:s0
diff --git a/exclude.cpp b/exclude.cpp
new file mode 100644
index 0000000..8ce138a
--- /dev/null
+++ b/exclude.cpp
@@ -0,0 +1,108 @@
+/*
+		Copyright 2013 to 2016 TeamWin
+		This file is part of TWRP/TeamWin Recovery Project.
+
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+extern "C" {
+	#include "libtar/libtar.h"
+}
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string>
+#include <vector>
+#include "exclude.hpp"
+#include "twrp-functions.hpp"
+#include "gui/gui.hpp"
+#include "twcommon.h"
+
+using namespace std;
+
+extern bool datamedia;
+
+TWExclude::TWExclude() {
+	add_relative_dir(".");
+	add_relative_dir("..");
+	add_relative_dir("lost+found");
+}
+
+void TWExclude::add_relative_dir(const string& dir) {
+	relativedir.push_back(dir);
+}
+
+void TWExclude::clear_relative_dir(string dir) {
+	vector<string>::iterator iter = relativedir.begin();
+	while (iter != relativedir.end()) {
+		if (*iter == dir)
+			iter = relativedir.erase(iter);
+		else
+			iter++;
+	}
+}
+
+void TWExclude::add_absolute_dir(const string& dir) {
+	absolutedir.push_back(TWFunc::Remove_Trailing_Slashes(dir));
+}
+
+uint64_t TWExclude::Get_Folder_Size(const string& Path) {
+	DIR* d;
+	struct dirent* de;
+	struct stat st;
+	uint64_t dusize = 0;
+	string FullPath;
+
+	d = opendir(Path.c_str());
+	if (d == NULL) {
+		gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(Path)(strerror(errno)));
+		return 0;
+	}
+
+	while ((de = readdir(d)) != NULL) {
+		FullPath = Path + "/";
+		FullPath += de->d_name;
+		if (lstat(FullPath.c_str(), &st)) {
+			gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(FullPath)(strerror(errno)));
+			LOGINFO("Real error: Unable to stat '%s'\n", FullPath.c_str());
+			continue;
+		}
+		if ((st.st_mode & S_IFDIR) && !check_skip_dirs(FullPath) && de->d_type != DT_SOCK) {
+			dusize += Get_Folder_Size(FullPath);
+		} else if (st.st_mode & S_IFREG || st.st_mode & S_IFLNK) {
+			dusize += (uint64_t)(st.st_size);
+		}
+	}
+	closedir(d);
+	return dusize;
+}
+
+bool TWExclude::check_relative_skip_dirs(const string& dir) {
+	return std::find(relativedir.begin(), relativedir.end(), dir) != relativedir.end();
+}
+
+bool TWExclude::check_absolute_skip_dirs(const string& path) {
+	return std::find(absolutedir.begin(), absolutedir.end(), path) != absolutedir.end();
+}
+
+bool TWExclude::check_skip_dirs(const string& path) {
+	string normalized = TWFunc::Remove_Trailing_Slashes(path);
+	size_t slashIdx = normalized.find_last_of('/');
+	if (slashIdx != std::string::npos && slashIdx+1 < normalized.size()) {
+		if (check_relative_skip_dirs(normalized.substr(slashIdx+1)))
+			return true;
+	}
+	return check_absolute_skip_dirs(normalized);
+}
diff --git a/exclude.hpp b/exclude.hpp
new file mode 100644
index 0000000..5cdc416
--- /dev/null
+++ b/exclude.hpp
@@ -0,0 +1,43 @@
+/*
+        Copyright 2013 to 2016 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TWEXCLUDE_HPP
+#define TWEXCLUDE_HPP
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+class TWExclude {
+
+public:
+	TWExclude();
+	uint64_t Get_Folder_Size(const string& Path); // Gets the folder's size using stat
+	void add_absolute_dir(const string& Path);
+	void add_relative_dir(const string& Path);
+	bool check_relative_skip_dirs(const string& dir);
+	bool check_absolute_skip_dirs(const string& path);
+	bool check_skip_dirs(const string& path);
+	void clear_relative_dir(string dir);
+private:
+	vector<string> absolutedir;
+	vector<string> relativedir;
+};
+
+#endif
diff --git a/exfat/COPYING b/exfat/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/exfat/COPYING
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ 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
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+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 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
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+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
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    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
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+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
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+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
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+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
+
+  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
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+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
+
+  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
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    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.,
+    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) 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.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+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 Lesser General
+Public License instead of this License.
diff --git a/exfat/ChangeLog b/exfat/ChangeLog
new file mode 100644
index 0000000..d6f99d0
--- /dev/null
+++ b/exfat/ChangeLog
@@ -0,0 +1,146 @@
+1.2.2 (2015-11-09)
+
+* Improved reliability in case of a sudden unplug: FS will be in a clean state
+after closing all files and performing sync(1).
+* Fixed compilation on Debian GNU/kFreeBSD and GNU/Hurd platforms.
+* Updated mount.exfat-fuse man page.
+
+1.2.1 (2015-09-24)
+
+* Fixed compatibility with Zalman VE-200: now newly created directories do not
+have archive bit set.
+* Fixed heap corruption: malformed FS can use invalid sector or cluster size.
+* Fixed hang on mount: malformed FS can have cyclic references in the clusters
+map.
+
+1.2.0 (2015-08-26)
+
+* Switched from SCons to autotools.
+* Added musl libc support [Brendan Heading].
+* Worked around "FS is larger than device" error for memory cards formatted by
+Panasonic Lumix cameras.
+* Worked around "unknown entry type 0xe1" error for memory cards formatted by
+Sony cameras.
+
+1.1.1 (2014-11-15)
+
+* Fixed mkfs crash on some sectors-per-cluster (-s option) values.
+
+1.1.0 (2014-07-08)
+
+* Relicensed the project from GPLv3+ to GPLv2+.
+* OpenBSD support [Helg Bredow].
+* Improved I/O errors handling.
+* Implemented fsync() and fsyncdir().
+* Fixed crash on Mac OS X 10.5 caused by non-standard use of realpath(). Also
+fixed TrueCrypt disks unmounting.
+* Avoid extra erase on writes to the end of a file. This should improve linear
+write speed.
+* Allow arbitrary changing of lower 9 bits of mode. Allow owner/group changing
+to the same owner/group. This fixes rsync.
+* Fixed buffers overflows when handling lengthy file names.
+* Fixed "real size does not equal to size" error on volumes with pagefile.sys.
+* Fixed negative IUsed in "df -i" output.
+
+1.0.1 (2013-02-02)
+
+* Fixed unexpected removal of a directory if it is moved into itself.
+* Fixed "Operation not permitted" error on reading an empty file.
+
+1.0.0 (2013-01-19)
+
+* Fixed crash when renaming a file within a single directory and a new name
+differs only in case.
+* Fixed clusters allocation: a cluster beyond valid clusters range could be
+allocated.
+* Fixed crash when a volume is unmounted while some files are open.
+* SConscript now respects AR and RANLIB environment variables.
+* Improved error handling.
+
+Linux:
+
+* Enabled big_writes. This improves write speed (larger block size means less
+switches between kernel- and user-space).
+* Do BLKROGET ioctl to make sure the device is not read-only: after
+"blockdev --setro" kernel still allows to open the device in read-write mode
+but fails writes.
+
+OS X:
+
+* Fixed OS X 10.8 support.
+* Switched to 64-bit inode numbers (now Mac OS X 10.5 or later is required).
+* Switched from unmaintained MacFUSE to OSXFUSE (http://osxfuse.github.com).
+* Fixed device size detection. Now mkfs works.
+* Workarounded some utilities failures due to missing chmod() support.
+* Disabled (senseless) permission checks made by FUSE.
+
+0.9.8 (2012-08-09)
+
+* The mkfs utility can now create huge file systems (up to several exabytes).
+* Fixed handling of characters beyond Basic Multilingual Plane.
+* Echo messages to syslog only if stderr is not connected to a terminal.
+
+0.9.7 (2012-03-08)
+
+* Out-of-the-box FreeBSD support (via ublio library).
+* Fixed "missing EOD entry" error (could happen while reading directory that
+consists of several clusters).
+* Fixed interpretation of minutes field in files timestamps (minutes could be
+displayed incorrectly).
+* Fixed mtime seconds field initialization for newly created file (mtime could
+be 1 sec less than creation time).
+* SConscript now respects CC, CCFLAGS and LDFLAGS environment variables.
+
+0.9.6 (2012-01-14)
+
+* Fixed write performance regression introduced in 0.9.4.
+* Mount in read-only mode if the device is write-protected.
+* Set ctime to mtime to ensure we don't break programs that rely on ctime
+(e.g. rsync considered that all files are outdated) [Eldad Zack].
+* Indicate that FS in not clean when it was not cleanly unmounted.
+* Utilities are now compatible with GNU/Hurd.
+* Fixed several memory leaks that could occur on error handling paths.
+* Improved handling of corrupted file systems.
+
+0.9.5 (2011-05-15)
+
+* Fixed erasing of the root directory cluster when creating a new FS with
+mkexfatfs. This bug could cause mkexfatfs to produce invalid FS.
+* Utilities are not linked with libfuse anymore.
+* Ensure that the path being opened is either a device or a regular file.
+
+0.9.4 (2011-03-05)
+
+* Introduced exfat-utils: dumpexfat, exfatfsck, mkexfatfs, exfatlabel.
+* Fixed "Invalid argument" error while mounting a volume from a disk with sector size greater than 512 bytes.
+* Wait for all data to be flushed to disk on unmount.
+* Kernel cache is no longer flushed on open. This can slightly improve read performance by avoiding extra read requests from kernel to user-space.
+* Allow to unmount volumes as user (fusermount -u) if they were mounted from the very same user [Tino Lange].
+* Errors and warnings are now duplicated to syslog.
+
+0.9.3 (2010-09-25)
+
+* Directories now can shrink.
+* Improved timestamps resolution from 2 sec to 1 sec.
+* Fixed timestamps displaying under Mac OS X when compiled for i386 or ppc.
+* Fixed FS size displaying for non-GNU systems.
+
+0.9.2 (2010-07-24)
+
+* Fixed a bug which could cause the whole directory to become unreadable after renaming a file in it.
+* Support for Solaris and various *BSD [Albert Lee].
+* Improved error handling on corrupted volumes.
+* Improved allowed file name characters filter.
+* Added man page.
+
+0.9.1 (2010-06-12)
+
+* Implemented automounting (util-linux-ng 2.18 or later is required).
+* Fixed mounting when cluster bitmap is larger than expected.
+* Fixed crash on statfs() when root directory contains error.
+* Fixed bugs specific to big-endian machines.
+* Other bugfixes.
+
+0.9.0 (2010-03-21)
+
+* Initial release.
diff --git a/exfat/README.md b/exfat/README.md
new file mode 100644
index 0000000..3cf58b7
--- /dev/null
+++ b/exfat/README.md
@@ -0,0 +1,60 @@
+About
+-----
+
+This project aims to provide a full-featured [exFAT][1] file system implementation for Unix-like systems. It consists of a [FUSE][2] module (fuse-exfat) and a set of utilities (exfat-utils).
+
+Supported operating systems:
+
+* GNU/Linux
+* Mac OS X 10.5 or later
+* FreeBSD
+* OpenBSD
+
+Most GNU/Linux distributions already have fuse-exfat and exfat-utils in their repositories, so you can just install and use them. The next chapter describes how to compile them from source.
+
+Compiling
+---------
+
+To build this project under GNU/Linux you need to install the following packages:
+
+* git
+* autoconf
+* automake
+* pkg-config
+* fuse-devel (or libfuse-dev)
+* gcc
+* make
+
+Get the source code, change directory and compile:
+
+    git clone https://github.com/relan/exfat.git
+    cd exfat
+    autoreconf --install
+    ./configure --prefix=/usr
+    make
+
+Then install driver and utilities:
+
+    sudo make install
+
+You can remove them using this command:
+
+    sudo make uninstall
+
+Mounting
+--------
+
+Modern GNU/Linux distributions will mount exFAT volumes automatically—util-linux-ng 2.18 (was renamed to util-linux in 2.19) is required for this. Anyway, you can mount manually (you will need root privileges):
+
+    sudo mount.exfat-fuse /dev/sdXn /mnt/exfat
+
+where /dev/sdXn is the partition special file, /mnt/exfat is a mountpoint.
+
+Feedback
+--------
+
+If you have any questions, issues, suggestions, bug reports, etc. please create an [issue][3]. Pull requests are also welcome!
+
+[1]: http://en.wikipedia.org/wiki/ExFAT
+[2]: http://en.wikipedia.org/wiki/Filesystem_in_Userspace
+[3]: https://github.com/relan/exfat/issues
diff --git a/exfat/dump/dumpexfat.8 b/exfat/dump/dumpexfat.8
new file mode 100644
index 0000000..5cf3995
--- /dev/null
+++ b/exfat/dump/dumpexfat.8
@@ -0,0 +1,46 @@
+.\" Copyright (C) 2011-2015  Andrew Nayenko
+.\"
+.TH DUMPEXFAT 8 "February 2011"
+.SH NAME
+.B dumpexfat
+\- dump exFAT file system
+.SH SYNOPSIS
+.B dumpexfat
+[
+.B \-s
+]
+[
+.B \-u
+]
+[
+.B \-V
+]
+.I device
+
+.SH DESCRIPTION
+.B dumpexfat
+dumps details about exFAT file system including low-level info. All sizes are
+in bytes.
+
+.SH OPTIONS
+Command line options available:
+.TP
+.B \-s
+Dump only info from super block. May be useful for heavily corrupted file
+systems.
+.TP
+.B \-u
+Dump ranges of used sectors starting from 0 and separated with spaces. May be
+useful for backup tools.
+.TP
+.BI \-V
+Print version and copyright.
+
+.SH EXIT CODES
+Zero is returned on success. Any other code means an error.
+
+.SH AUTHOR
+Andrew Nayenko
+
+.SH SEE ALSO
+.BR mkexfatfs (8)
diff --git a/exfat/dump/main.c b/exfat/dump/main.c
new file mode 100644
index 0000000..3ed3d8f
--- /dev/null
+++ b/exfat/dump/main.c
@@ -0,0 +1,183 @@
+/*
+	main.c (08.11.10)
+	Prints detailed information about exFAT volume.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <exfat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+static void print_generic_info(const struct exfat_super_block* sb)
+{
+	printf("Volume serial number      0x%08x\n",
+			le32_to_cpu(sb->volume_serial));
+	printf("FS version                       %hhu.%hhu\n",
+			sb->version.major, sb->version.minor);
+	printf("Sector size               %10u\n",
+			SECTOR_SIZE(*sb));
+	printf("Cluster size              %10u\n",
+			CLUSTER_SIZE(*sb));
+}
+
+static void print_sector_info(const struct exfat_super_block* sb)
+{
+	printf("Sectors count             %10"PRIu64"\n",
+			le64_to_cpu(sb->sector_count));
+}
+
+static void print_cluster_info(const struct exfat_super_block* sb)
+{
+	printf("Clusters count            %10u\n",
+			le32_to_cpu(sb->cluster_count));
+}
+
+static void print_other_info(const struct exfat_super_block* sb)
+{
+	printf("First sector              %10"PRIu64"\n",
+			le64_to_cpu(sb->sector_start));
+	printf("FAT first sector          %10u\n",
+			le32_to_cpu(sb->fat_sector_start));
+	printf("FAT sectors count         %10u\n",
+			le32_to_cpu(sb->fat_sector_count));
+	printf("First cluster sector      %10u\n",
+			le32_to_cpu(sb->cluster_sector_start));
+	printf("Root directory cluster    %10u\n",
+			le32_to_cpu(sb->rootdir_cluster));
+	printf("Volume state                  0x%04hx\n",
+			le16_to_cpu(sb->volume_state));
+	printf("FATs count                %10hhu\n",
+			sb->fat_count);
+	printf("Drive number                    0x%02hhx\n",
+			sb->drive_no);
+	printf("Allocated space           %9hhu%%\n",
+			sb->allocated_percent);
+}
+
+static int dump_sb(const char* spec)
+{
+	struct exfat_dev* dev;
+	struct exfat_super_block sb;
+
+	dev = exfat_open(spec, EXFAT_MODE_RO);
+	if (dev == NULL)
+		return 1;
+
+	if (exfat_read(dev, &sb, sizeof(struct exfat_super_block)) < 0)
+	{
+		exfat_close(dev);
+		exfat_error("failed to read from '%s'", spec);
+		return 1;
+	}
+	if (memcmp(sb.oem_name, "EXFAT   ", sizeof(sb.oem_name)) != 0)
+	{
+		exfat_close(dev);
+		exfat_error("exFAT file system is not found on '%s'", spec);
+		return 1;
+	}
+
+	print_generic_info(&sb);
+	print_sector_info(&sb);
+	print_cluster_info(&sb);
+	print_other_info(&sb);
+
+	exfat_close(dev);
+	return 0;
+}
+
+static void dump_sectors(struct exfat* ef)
+{
+	loff_t a = 0, b = 0;
+
+	printf("Used sectors ");
+	while (exfat_find_used_sectors(ef, &a, &b) == 0)
+		printf(" %"PRIu64"-%"PRIu64, a, b);
+	puts("");
+}
+
+static int dump_full(const char* spec, bool used_sectors)
+{
+	struct exfat ef;
+	uint32_t free_clusters;
+	uint64_t free_sectors;
+
+	if (exfat_mount(&ef, spec, "ro") != 0)
+		return 1;
+
+	free_clusters = exfat_count_free_clusters(&ef);
+	free_sectors = (uint64_t) free_clusters << ef.sb->spc_bits;
+
+	printf("Volume label         %15s\n", exfat_get_label(&ef));
+	print_generic_info(ef.sb);
+	print_sector_info(ef.sb);
+	printf("Free sectors              %10"PRIu64"\n", free_sectors);
+	print_cluster_info(ef.sb);
+	printf("Free clusters             %10u\n", free_clusters);
+	print_other_info(ef.sb);
+	if (used_sectors)
+		dump_sectors(&ef);
+
+	exfat_unmount(&ef);
+	return 0;
+}
+
+static void usage(const char* prog)
+{
+	fprintf(stderr, "Usage: %s [-s] [-u] [-V] <device>\n", prog);
+	exit(1);
+}
+
+int main(int argc, char* argv[])
+{
+	int opt;
+	const char* spec = NULL;
+	bool sb_only = false;
+	bool used_sectors = false;
+
+	printf("dumpexfat %s\n", VERSION);
+
+	while ((opt = getopt(argc, argv, "suV")) != -1)
+	{
+		switch (opt)
+		{
+		case 's':
+			sb_only = true;
+			break;
+		case 'u':
+			used_sectors = true;
+			break;
+		case 'V':
+			puts("Copyright (C) 2011-2015  Andrew Nayenko");
+			return 0;
+		default:
+			usage(argv[0]);
+		}
+	}
+	if (argc - optind != 1)
+		usage(argv[0]);
+	spec = argv[optind];
+
+	if (sb_only)
+		return dump_sb(spec);
+
+	return dump_full(spec, used_sectors);
+}
diff --git a/exfat/fsck/Android.mk b/exfat/fsck/Android.mk
new file mode 100755
index 0000000..085dda4
--- /dev/null
+++ b/exfat/fsck/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := fsckexfat
+LOCAL_MODULE_STEM := fsck.exfat
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -Wno-sign-compare
+LOCAL_SRC_FILES =  main.c
+LOCAL_C_INCLUDES += $(LOCAL_PATH) \
+					$(commands_recovery_local_path)/exfat/libexfat \
+					$(commands_recovery_local_path)/fuse/include
+LOCAL_SHARED_LIBRARIES := libexfat_twrp
+
+include $(BUILD_EXECUTABLE)
diff --git a/exfat/fsck/exfatfsck.8 b/exfat/fsck/exfatfsck.8
new file mode 100644
index 0000000..e950488
--- /dev/null
+++ b/exfat/fsck/exfatfsck.8
@@ -0,0 +1,32 @@
+.\" Copyright (C) 2011-2015  Andrew Nayenko
+.\"
+.TH EXFATFSCK 8 "February 2011"
+.SH NAME
+.B exfatfsck
+\- check an exFAT file system
+.SH SYNOPSIS
+.B exfatfsck
+[
+.B \-V
+]
+.I device
+
+.SH DESCRIPTION
+.B exfatfsck
+checks an exFAT file system for errors. Note that it cannot repair corrupted
+FS, it just reports found errors.
+
+.SH COMMAND LINE OPTIONS
+Command line options available:
+.TP
+.BI \-V
+Print version and copyright.
+
+.SH EXIT CODES
+Zero is returned if errors were not found. Any other code means an error.
+
+.SH AUTHOR
+Andrew Nayenko
+
+.SH SEE ALSO
+.BR fsck (8)
diff --git a/exfat/fsck/main.c b/exfat/fsck/main.c
new file mode 100644
index 0000000..ded76b2
--- /dev/null
+++ b/exfat/fsck/main.c
@@ -0,0 +1,178 @@
+/*
+	main.c (02.09.09)
+	exFAT file system checker.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <exfat.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#define exfat_debug(format, ...)
+
+uint64_t files_count, directories_count;
+
+static int nodeck(struct exfat* ef, struct exfat_node* node)
+{
+	const cluster_t cluster_size = CLUSTER_SIZE(*ef->sb);
+	cluster_t clusters = (node->size + cluster_size - 1) / cluster_size;
+	cluster_t c = node->start_cluster;
+	int rc = 0;
+
+	while (clusters--)
+	{
+		if (CLUSTER_INVALID(c))
+		{
+			char name[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
+			exfat_get_name(node, name, sizeof(name) - 1);
+			exfat_error("file '%s' has invalid cluster 0x%x", name, c);
+			rc = 1;
+			break;
+		}
+		if (BMAP_GET(ef->cmap.chunk, c - EXFAT_FIRST_DATA_CLUSTER) == 0)
+		{
+			char name[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
+			exfat_get_name(node, name, sizeof(name) - 1);
+			exfat_error("cluster 0x%x of file '%s' is not allocated", c, name);
+			rc = 1;
+		}
+		c = exfat_next_cluster(ef, node, c);
+	}
+	return rc;
+}
+
+static void dirck(struct exfat* ef, const char* path)
+{
+	struct exfat_node* parent;
+	struct exfat_node* node;
+	struct exfat_iterator it;
+	int rc;
+	size_t path_length;
+	char* entry_path;
+
+	if (exfat_lookup(ef, &parent, path) != 0)
+		exfat_bug("directory '%s' is not found", path);
+	if (!(parent->flags & EXFAT_ATTRIB_DIR))
+		exfat_bug("'%s' is not a directory (0x%x)", path, parent->flags);
+	if (nodeck(ef, parent) != 0)
+	{
+		exfat_put_node(ef, parent);
+		return;
+	}
+
+	path_length = strlen(path);
+	entry_path = malloc(path_length + 1 + UTF8_BYTES(EXFAT_NAME_MAX) + 1);
+	if (entry_path == NULL)
+	{
+		exfat_put_node(ef, parent);
+		exfat_error("out of memory");
+		return;
+	}
+	strcpy(entry_path, path);
+	strcat(entry_path, "/");
+
+	rc = exfat_opendir(ef, parent, &it);
+	if (rc != 0)
+	{
+		free(entry_path);
+		exfat_put_node(ef, parent);
+		return;
+	}
+	while ((node = exfat_readdir(ef, &it)))
+	{
+		exfat_get_name(node, entry_path + path_length + 1,
+				UTF8_BYTES(EXFAT_NAME_MAX));
+		exfat_debug("%s: %s, %"PRIu64" bytes, cluster %u", entry_path,
+				IS_CONTIGUOUS(*node) ? "contiguous" : "fragmented",
+				node->size, node->start_cluster);
+		if (node->flags & EXFAT_ATTRIB_DIR)
+		{
+			directories_count++;
+			dirck(ef, entry_path);
+		}
+		else
+		{
+			files_count++;
+			nodeck(ef, node);
+		}
+		exfat_put_node(ef, node);
+	}
+	exfat_closedir(ef, &it);
+	exfat_put_node(ef, parent);
+	free(entry_path);
+}
+
+static void fsck(struct exfat* ef)
+{
+	exfat_print_info(ef->sb, exfat_count_free_clusters(ef));
+	dirck(ef, "");
+}
+
+static void usage(const char* prog)
+{
+	fprintf(stderr, "Usage: %s [-V] <device>\n", prog);
+	exit(1);
+}
+
+int main(int argc, char* argv[])
+{
+	int opt;
+	const char* spec = NULL;
+	struct exfat ef;
+
+	printf("exfatfsck %s\n", VERSION);
+
+	while ((opt = getopt(argc, argv, "V")) != -1)
+	{
+		switch (opt)
+		{
+		case 'V':
+			puts("Copyright (C) 2011-2015  Andrew Nayenko");
+			return 0;
+		default:
+			usage(argv[0]);
+			break;
+		}
+	}
+	if (argc - optind != 1)
+		usage(argv[0]);
+	spec = argv[optind];
+
+	if (exfat_mount(&ef, spec, "ro") != 0)
+		return 1;
+
+	printf("Checking file system on %s.\n", spec);
+	fsck(&ef);
+	exfat_unmount(&ef);
+	printf("Totally %"PRIu64" directories and %"PRIu64" files.\n",
+			directories_count, files_count);
+
+	fputs("File system checking finished. ", stdout);
+	if (exfat_errors != 0)
+	{
+		printf("ERRORS FOUND: %d.\n", exfat_errors);
+		return 1;
+	}
+	puts("No errors found.");
+	return 0;
+}
diff --git a/exfat/fuse/Android.mk b/exfat/fuse/Android.mk
new file mode 100755
index 0000000..b0cca7d
--- /dev/null
+++ b/exfat/fuse/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := exfat-fuse
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -Wno-sign-compare -Wno-unused-parameter
+LOCAL_SRC_FILES = main.c 
+LOCAL_C_INCLUDES += $(LOCAL_PATH) \
+					$(commands_recovery_local_path)/exfat/libexfat \
+					$(commands_recovery_local_path)/fuse/include \
+					$(commands_recovery_local_path)/fuse/android
+LOCAL_SHARED_LIBRARIES := libexfat_twrp
+LOCAL_STATIC_LIBRARIES := libfusetwrp
+
+include $(BUILD_EXECUTABLE)
diff --git a/exfat/fuse/main.c b/exfat/fuse/main.c
new file mode 100644
index 0000000..30958f6
--- /dev/null
+++ b/exfat/fuse/main.c
@@ -0,0 +1,618 @@
+/*
+	main.c (01.09.09)
+	FUSE-based exFAT implementation. Requires FUSE 2.6 or later.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <exfat.h>
+#define FUSE_USE_VERSION 26
+#include <fuse.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#ifndef DEBUG
+	#define exfat_debug(format, ...)
+#endif
+
+#if !defined(FUSE_VERSION) || (FUSE_VERSION < 26)
+	#error FUSE 2.6 or later is required
+#endif
+
+const char* default_options = "ro_fallback,allow_other,blkdev,big_writes,"
+		"default_permissions";
+
+struct exfat ef;
+
+static struct exfat_node* get_node(const struct fuse_file_info* fi)
+{
+	return (struct exfat_node*) (size_t) fi->fh;
+}
+
+static void set_node(struct fuse_file_info* fi, struct exfat_node* node)
+{
+	fi->fh = (uint64_t) (size_t) node;
+	fi->keep_cache = 1;
+}
+
+static int fuse_exfat_getattr(const char* path, struct stat* stbuf)
+{
+	struct exfat_node* node;
+	int rc;
+
+	exfat_debug("[%s] %s", __func__, path);
+
+	rc = exfat_lookup(&ef, &node, path);
+	if (rc != 0)
+		return rc;
+
+	exfat_stat(&ef, node, stbuf);
+	exfat_put_node(&ef, node);
+	return 0;
+}
+
+static int fuse_exfat_truncate(const char* path, loff_t size)
+{
+	struct exfat_node* node;
+	int rc;
+
+	exfat_debug("[%s] %s, %"PRId64, __func__, path, size);
+
+	rc = exfat_lookup(&ef, &node, path);
+	if (rc != 0)
+		return rc;
+
+	rc = exfat_truncate(&ef, node, size, true);
+	if (rc != 0)
+	{
+		exfat_flush_node(&ef, node);	/* ignore return code */
+		exfat_put_node(&ef, node);
+		return rc;
+	}
+	rc = exfat_flush_node(&ef, node);
+	exfat_put_node(&ef, node);
+	return rc;
+}
+
+static int fuse_exfat_readdir(const char* path, void* buffer,
+		fuse_fill_dir_t filler, loff_t offset, struct fuse_file_info* fi)
+{
+	struct exfat_node* parent;
+	struct exfat_node* node;
+	struct exfat_iterator it;
+	int rc;
+	char name[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
+	exfat_debug("[%s] %s", __func__, path);
+
+	rc = exfat_lookup(&ef, &parent, path);
+	if (rc != 0)
+		return rc;
+	if (!(parent->flags & EXFAT_ATTRIB_DIR))
+	{
+		exfat_put_node(&ef, parent);
+		exfat_error("'%s' is not a directory (0x%x)", path, parent->flags);
+		return -ENOTDIR;
+	}
+
+	filler(buffer, ".", NULL, 0);
+	filler(buffer, "..", NULL, 0);
+
+	rc = exfat_opendir(&ef, parent, &it);
+	if (rc != 0)
+	{
+		exfat_put_node(&ef, parent);
+		exfat_error("failed to open directory '%s'", path);
+		return rc;
+	}
+	while ((node = exfat_readdir(&ef, &it)))
+	{
+		exfat_get_name(node, name, sizeof(name) - 1);
+		exfat_debug("[%s] %s: %s, %"PRId64" bytes, cluster 0x%x", __func__,
+				name, IS_CONTIGUOUS(*node) ? "contiguous" : "fragmented",
+				node->size, node->start_cluster);
+		filler(buffer, name, NULL, 0);
+		exfat_put_node(&ef, node);
+	}
+	exfat_closedir(&ef, &it);
+	exfat_put_node(&ef, parent);
+	return 0;
+}
+
+static int fuse_exfat_open(const char* path, struct fuse_file_info* fi)
+{
+	struct exfat_node* node;
+	int rc;
+
+	exfat_debug("[%s] %s", __func__, path);
+
+	rc = exfat_lookup(&ef, &node, path);
+	if (rc != 0)
+		return rc;
+	set_node(fi, node);
+	return 0;
+}
+
+static int fuse_exfat_create(const char* path, mode_t mode,
+		struct fuse_file_info* fi)
+{
+	struct exfat_node* node;
+	int rc;
+
+	exfat_debug("[%s] %s 0%ho", __func__, path, mode);
+
+	rc = exfat_mknod(&ef, path);
+	if (rc != 0)
+		return rc;
+	rc = exfat_lookup(&ef, &node, path);
+	if (rc != 0)
+		return rc;
+	set_node(fi, node);
+	return 0;
+}
+
+static int fuse_exfat_release(const char* path, struct fuse_file_info* fi)
+{
+	/*
+	   This handler is called by FUSE on close() syscall. If the FUSE
+	   implementation does not call flush handler, we will flush node here.
+	   But in this case we will not be able to return an error to the caller.
+	   See fuse_exfat_flush() below.
+	*/
+	exfat_debug("[%s] %s", __func__, path);
+ 	exfat_flush_node(&ef, get_node(fi));
+	exfat_put_node(&ef, get_node(fi));
+	return 0; /* FUSE ignores this return value */
+}
+
+static int fuse_exfat_flush(const char* path, struct fuse_file_info* fi)
+{
+	/*
+	   This handler may be called by FUSE on close() syscall. FUSE also deals
+	   with removals of open files, so we don't free clusters on close but
+	   only on rmdir and unlink. If the FUSE implementation does not call this
+	   handler we will flush node on release. See fuse_exfat_relase() above.
+	*/
+	exfat_debug("[%s] %s", __func__, path);
+	return exfat_flush_node(&ef, get_node(fi));
+}
+
+static int fuse_exfat_fsync(const char* path, int datasync,
+		struct fuse_file_info *fi)
+{
+	int rc;
+
+	exfat_debug("[%s] %s", __func__, path);
+	rc = exfat_flush_nodes(&ef);
+	if (rc != 0)
+		return rc;
+	rc = exfat_flush(&ef);
+	if (rc != 0)
+		return rc;
+	return exfat_fsync(ef.dev);
+}
+
+static int fuse_exfat_read(const char* path, char* buffer, size_t size,
+		loff_t offset, struct fuse_file_info* fi)
+{
+	ssize_t ret;
+
+	exfat_debug("[%s] %s (%zu bytes)", __func__, path, size);
+	ret = exfat_generic_pread(&ef, get_node(fi), buffer, size, offset);
+	if (ret < 0)
+		return -EIO;
+	return ret;
+}
+
+static int fuse_exfat_write(const char* path, const char* buffer, size_t size,
+		loff_t offset, struct fuse_file_info* fi)
+{
+	ssize_t ret;
+
+	exfat_debug("[%s] %s (%zu bytes)", __func__, path, size);
+	ret = exfat_generic_pwrite(&ef, get_node(fi), buffer, size, offset);
+	if (ret < 0)
+		return -EIO;
+	return ret;
+}
+
+static int fuse_exfat_unlink(const char* path)
+{
+	struct exfat_node* node;
+	int rc;
+
+	exfat_debug("[%s] %s", __func__, path);
+
+	rc = exfat_lookup(&ef, &node, path);
+	if (rc != 0)
+		return rc;
+
+	rc = exfat_unlink(&ef, node);
+	exfat_put_node(&ef, node);
+	if (rc != 0)
+		return rc;
+	return exfat_cleanup_node(&ef, node);
+}
+
+static int fuse_exfat_rmdir(const char* path)
+{
+	struct exfat_node* node;
+	int rc;
+
+	exfat_debug("[%s] %s", __func__, path);
+
+	rc = exfat_lookup(&ef, &node, path);
+	if (rc != 0)
+		return rc;
+
+	rc = exfat_rmdir(&ef, node);
+	exfat_put_node(&ef, node);
+	if (rc != 0)
+		return rc;
+	return exfat_cleanup_node(&ef, node);
+}
+
+static int fuse_exfat_mknod(const char* path, mode_t mode, dev_t dev)
+{
+	exfat_debug("[%s] %s 0%ho", __func__, path, mode);
+	return exfat_mknod(&ef, path);
+}
+
+static int fuse_exfat_mkdir(const char* path, mode_t mode)
+{
+	exfat_debug("[%s] %s 0%ho", __func__, path, mode);
+	return exfat_mkdir(&ef, path);
+}
+
+static int fuse_exfat_rename(const char* old_path, const char* new_path)
+{
+	exfat_debug("[%s] %s => %s", __func__, old_path, new_path);
+	return exfat_rename(&ef, old_path, new_path);
+}
+
+static int fuse_exfat_utimens(const char* path, const struct timespec tv[2])
+{
+	struct exfat_node* node;
+	int rc;
+
+	exfat_debug("[%s] %s", __func__, path);
+
+	rc = exfat_lookup(&ef, &node, path);
+	if (rc != 0)
+		return rc;
+
+	exfat_utimes(node, tv);
+	rc = exfat_flush_node(&ef, node);
+	exfat_put_node(&ef, node);
+	return rc;
+}
+
+static int fuse_exfat_chmod(const char* path, mode_t mode)
+{
+	const mode_t VALID_MODE_MASK = S_IFREG | S_IFDIR |
+			S_IRWXU | S_IRWXG | S_IRWXO;
+
+	exfat_debug("[%s] %s 0%ho", __func__, path, mode);
+	if (mode & ~VALID_MODE_MASK)
+		return -EPERM;
+	return 0;
+}
+
+static int fuse_exfat_chown(const char* path, uid_t uid, gid_t gid)
+{
+	exfat_debug("[%s] %s %u:%u", __func__, path, uid, gid);
+	if (uid != ef.uid || gid != ef.gid)
+		return -EPERM;
+	return 0;
+}
+
+static int fuse_exfat_statfs(const char* path, struct statvfs* sfs)
+{
+	exfat_debug("[%s]", __func__);
+
+	sfs->f_bsize = CLUSTER_SIZE(*ef.sb);
+	sfs->f_frsize = CLUSTER_SIZE(*ef.sb);
+	sfs->f_blocks = le64_to_cpu(ef.sb->sector_count) >> ef.sb->spc_bits;
+	sfs->f_bavail = exfat_count_free_clusters(&ef);
+	sfs->f_bfree = sfs->f_bavail;
+	sfs->f_namemax = EXFAT_NAME_MAX;
+
+	/*
+	   Below are fake values because in exFAT there is
+	   a) no simple way to count files;
+	   b) no such thing as inode;
+	   So here we assume that inode = cluster.
+	*/
+	sfs->f_files = le32_to_cpu(ef.sb->cluster_count);
+	sfs->f_favail = sfs->f_bfree >> ef.sb->spc_bits;
+	sfs->f_ffree = sfs->f_bavail;
+
+	return 0;
+}
+
+static void* fuse_exfat_init(struct fuse_conn_info* fci)
+{
+	exfat_debug("[%s]", __func__);
+#ifdef FUSE_CAP_BIG_WRITES
+	fci->want |= FUSE_CAP_BIG_WRITES;
+#endif
+	return NULL;
+}
+
+static void fuse_exfat_destroy(void* unused)
+{
+	exfat_debug("[%s]", __func__);
+	exfat_unmount(&ef);
+}
+
+static void usage(const char* prog)
+{
+	fprintf(stderr, "Usage: %s [-d] [-o options] [-V] <device> <dir>\n", prog);
+	exit(1);
+}
+
+static struct fuse_operations fuse_exfat_ops =
+{
+	.getattr	= fuse_exfat_getattr,
+	.truncate	= fuse_exfat_truncate,
+	.readdir	= fuse_exfat_readdir,
+	.open		= fuse_exfat_open,
+	.create		= fuse_exfat_create,
+	.release	= fuse_exfat_release,
+	.flush		= fuse_exfat_flush,
+	.fsync		= fuse_exfat_fsync,
+	.fsyncdir	= fuse_exfat_fsync,
+	.read		= fuse_exfat_read,
+	.write		= fuse_exfat_write,
+	.unlink		= fuse_exfat_unlink,
+	.rmdir		= fuse_exfat_rmdir,
+	.mknod		= fuse_exfat_mknod,
+	.mkdir		= fuse_exfat_mkdir,
+	.rename		= fuse_exfat_rename,
+	.utimens	= fuse_exfat_utimens,
+	.chmod		= fuse_exfat_chmod,
+	.chown		= fuse_exfat_chown,
+	.statfs		= fuse_exfat_statfs,
+	.init		= fuse_exfat_init,
+	.destroy	= fuse_exfat_destroy,
+};
+
+static char* add_option(char* options, const char* name, const char* value)
+{
+	size_t size;
+	char* optionsf = options;
+
+	if (value)
+		size = strlen(options) + strlen(name) + strlen(value) + 3;
+	else
+		size = strlen(options) + strlen(name) + 2;
+
+	options = realloc(options, size);
+	if (options == NULL)
+	{
+		free(optionsf);
+		exfat_error("failed to reallocate options string");
+		return NULL;
+	}
+	strcat(options, ",");
+	strcat(options, name);
+	if (value)
+	{
+		strcat(options, "=");
+		strcat(options, value);
+	}
+	return options;
+}
+
+static char* add_user_option(char* options)
+{
+	struct passwd* pw;
+
+	if (getuid() == 0)
+		return options;
+
+	pw = getpwuid(getuid());
+	if (pw == NULL || pw->pw_name == NULL)
+	{
+		free(options);
+		exfat_error("failed to determine username");
+		return NULL;
+	}
+	return add_option(options, "user", pw->pw_name);
+}
+
+static char* add_blksize_option(char* options, long cluster_size)
+{
+	long page_size = sysconf(_SC_PAGESIZE);
+	char blksize[20];
+
+	if (page_size < 1)
+		page_size = 0x1000;
+
+	snprintf(blksize, sizeof(blksize), "%ld", MIN(page_size, cluster_size));
+	return add_option(options, "blksize", blksize);
+}
+
+static char* add_fuse_options(char* options, const char* spec)
+{
+	options = add_option(options, "fsname", spec);
+	if (options == NULL)
+		return NULL;
+	options = add_user_option(options);
+	if (options == NULL)
+		return NULL;
+	options = add_blksize_option(options, CLUSTER_SIZE(*ef.sb));
+	if (options == NULL)
+		return NULL;
+
+	return options;
+}
+
+int main(int argc, char* argv[])
+{
+	struct fuse_args mount_args = FUSE_ARGS_INIT(0, NULL);
+	struct fuse_args newfs_args = FUSE_ARGS_INIT(0, NULL);
+	const char* spec = NULL;
+	const char* mount_point = NULL;
+	char* mount_options;
+	int debug = 0;
+	struct fuse_chan* fc = NULL;
+	struct fuse* fh = NULL;
+	int opt;
+
+	printf("FUSE exfat %s\n", VERSION);
+
+	mount_options = strdup(default_options);
+	if (mount_options == NULL)
+	{
+		exfat_error("failed to allocate options string");
+		return 1;
+	}
+
+	while ((opt = getopt(argc, argv, "dno:Vv")) != -1)
+	{
+		switch (opt)
+		{
+		case 'd':
+			debug = 1;
+			break;
+		case 'n':
+			break;
+		case 'o':
+			mount_options = add_option(mount_options, optarg, NULL);
+			if (mount_options == NULL)
+				return 1;
+			break;
+		case 'V':
+			free(mount_options);
+			puts("Copyright (C) 2010-2015  Andrew Nayenko");
+			return 0;
+		case 'v':
+			break;
+		default:
+			free(mount_options);
+			usage(argv[0]);
+			break;
+		}
+	}
+	if (argc - optind != 2)
+	{
+		free(mount_options);
+		usage(argv[0]);
+	}
+	spec = argv[optind];
+	mount_point = argv[optind + 1];
+
+	if (exfat_mount(&ef, spec, mount_options) != 0)
+	{
+		free(mount_options);
+		return 1;
+	}
+
+	if (ef.ro == -1) /* read-only fallback was used */
+	{
+		mount_options = add_option(mount_options, "ro", NULL);
+		if (mount_options == NULL)
+		{
+			exfat_unmount(&ef);
+			return 1;
+		}
+	}
+
+	mount_options = add_fuse_options(mount_options, spec);
+	if (mount_options == NULL)
+	{
+		exfat_unmount(&ef);
+		return 1;
+	}
+
+	/* create arguments for fuse_mount() */
+	if (fuse_opt_add_arg(&mount_args, "exfat") != 0 ||
+		fuse_opt_add_arg(&mount_args, "-o") != 0 ||
+		fuse_opt_add_arg(&mount_args, mount_options) != 0)
+	{
+		exfat_unmount(&ef);
+		free(mount_options);
+		return 1;
+	}
+
+	free(mount_options);
+
+	/* create FUSE mount point */
+	fc = fuse_mount(mount_point, &mount_args);
+	fuse_opt_free_args(&mount_args);
+	if (fc == NULL)
+	{
+		exfat_unmount(&ef);
+		return 1;
+	}
+
+	/* create arguments for fuse_new() */
+	if (fuse_opt_add_arg(&newfs_args, "") != 0 ||
+		(debug && fuse_opt_add_arg(&newfs_args, "-d") != 0))
+	{
+		fuse_unmount(mount_point, fc);
+		exfat_unmount(&ef);
+		return 1;
+	}
+
+	/* create new FUSE file system */
+	fh = fuse_new(fc, &newfs_args, &fuse_exfat_ops,
+			sizeof(struct fuse_operations), NULL);
+	fuse_opt_free_args(&newfs_args);
+	if (fh == NULL)
+	{
+		fuse_unmount(mount_point, fc);
+		exfat_unmount(&ef);
+		return 1;
+	}
+
+	/* exit session on HUP, TERM and INT signals and ignore PIPE signal */
+	if (fuse_set_signal_handlers(fuse_get_session(fh)) != 0)
+	{
+		fuse_unmount(mount_point, fc);
+		fuse_destroy(fh);
+		exfat_unmount(&ef);
+		exfat_error("failed to set signal handlers");
+		return 1;
+	}
+
+	/* go to background (unless "-d" option is passed) and run FUSE
+	   main loop */
+	if (fuse_daemonize(debug) == 0)
+	{
+		if (fuse_loop(fh) != 0)
+			exfat_error("FUSE loop failure");
+	}
+	else
+		exfat_error("failed to daemonize");
+
+	fuse_remove_signal_handlers(fuse_get_session(fh));
+	/* note that fuse_unmount() must be called BEFORE fuse_destroy() */
+	fuse_unmount(mount_point, fc);
+	fuse_destroy(fh);
+	return 0;
+}
diff --git a/exfat/fuse/mount.exfat-fuse.8 b/exfat/fuse/mount.exfat-fuse.8
new file mode 100644
index 0000000..38586ca
--- /dev/null
+++ b/exfat/fuse/mount.exfat-fuse.8
@@ -0,0 +1,108 @@
+.\" Copyright (C) 2010-2015  Andrew Nayenko
+.\"
+.TH EXFAT-FUSE 8 "July 2010"
+.SH NAME
+mount.exfat-fuse \- mount an exFAT file system
+.SH SYNOPSIS
+.B mount.exfat-fuse
+[
+.B \-d
+]
+[
+.B \-n
+]
+[
+.B \-o
+.I options
+]
+[
+.B \-V
+]
+[
+.B \-v
+]
+.I device dir
+
+.SH DESCRIPTION
+.B mount.exfat-fuse
+is a free exFAT file system implementation with write support. exFAT is a
+simple file system created by Microsoft. It is intended to replace FAT32
+removing some of its limitations. exFAT is a standard FS for SDXC memory
+cards.
+
+.SH COMMAND LINE OPTIONS
+Command line options available:
+.TP
+.BI \-d
+Enable debug logging and do not detach from shell.
+.TP
+.BI \-n
+Ignored.
+.TP
+.BI \-o " options"
+File system specific options. For more details see
+.B FILE SYSTEM OPTIONS
+section below.
+.TP
+.BI \-V
+Print version and copyright.
+.TP
+.BI \-v
+Ignored.
+
+.SH FILE SYSTEM OPTIONS
+.TP
+.BI umask= value
+Set the umask (the bitmask of the permissions that are
+.B not
+present, in octal).
+The default is 0.
+.TP
+.BI dmask= value
+Set the umask for directories only.
+.TP
+.BI fmask= value
+Set the umask for files only.
+.TP
+.BI uid= n
+Set the owner for all files and directories.
+The default is the owner of the current process.
+.TP
+.BI gid= n
+Set the group for all files and directories.
+The default is the group of the current process.
+.TP
+.BI ro
+Mount the file system in read only mode.
+.TP
+.BI noatime
+Do not update access time when file is read.
+
+.SH EXIT CODES
+Zero is returned on successful mount. Any other code means an error.
+
+.SH BUGS
+exFAT is a case-insensitive file system. Some things can behave unexpectedly,
+e.g. directory renaming that changes only case of some characters:
+
+.B \t$ mv FOO Foo
+.br
+.B \tmv: cannot move \(cqFOO\(cq to a subdirectory of itself, \(cqFoo/FOO\(cq
+
+This happens because
+.B mv
+finds that destination exists (for case-insensitive file
+systems
+.B FOO
+and
+.B Foo
+are the same thing) and adds source basename to the destination. The file
+system gets
+.B rename(\(dqFOO\(dq,\ \(dqFoo/FOO\(dq)
+syscall and returns an error.
+
+.SH AUTHOR
+Andrew Nayenko
+
+.SH SEE ALSO
+.BR mount (8)
diff --git a/exfat/label/exfatlabel.8 b/exfat/label/exfatlabel.8
new file mode 100644
index 0000000..bff6e79
--- /dev/null
+++ b/exfat/label/exfatlabel.8
@@ -0,0 +1,48 @@
+.\" Copyright (C) 2011-2015  Andrew Nayenko
+.\"
+.TH EXFATLABEL 8 "February 2011"
+.SH NAME
+.B exfatlabel
+\- get or set an exFAT file system label
+.SH SYNOPSIS
+.B exfatlabel
+[
+.B \-V
+]
+.I device
+[
+.I label
+]
+
+.SH DESCRIPTION
+.B exfatlabel
+reads or changes an exFAT file system label (volume name).
+
+If
+.I label
+argument is present,
+.B exfatlabel
+sets the new volume name. Label can be up to 15 characters. This limit is
+shorter if characters beyond Unicode BMP are used because internally label
+is stored in UTF-16.
+
+If
+.I label
+argument is omitted,
+.B exfatlabel
+just prints current volume name.
+
+.SH COMMAND LINE OPTIONS
+Command line options available:
+.TP
+.BI \-V
+Print version and copyright.
+
+.SH EXIT CODES
+Zero is returned on success. Any other code means an error.
+
+.SH AUTHOR
+Andrew Nayenko
+
+.SH SEE ALSO
+.BR mkexfatfs (8)
diff --git a/exfat/label/main.c b/exfat/label/main.c
new file mode 100644
index 0000000..b219d4e
--- /dev/null
+++ b/exfat/label/main.c
@@ -0,0 +1,62 @@
+/*
+	main.c (20.01.11)
+	Prints or changes exFAT volume label.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <exfat.h>
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char* argv[])
+{
+	char** pp;
+	struct exfat ef;
+	int rc = 0;
+
+	for (pp = argv + 1; *pp; pp++)
+		if (strcmp(*pp, "-V") == 0)
+		{
+			printf("exfatlabel %s\n", VERSION);
+			puts("Copyright (C) 2011-2015  Andrew Nayenko");
+			return 0;
+		}
+
+	if (argc != 2 && argc != 3)
+	{
+		fprintf(stderr, "Usage: %s [-V] <device> [label]\n", argv[0]);
+		return 1;
+	}
+
+	if (argv[2])
+	{
+		if (exfat_mount(&ef, argv[1], "") != 0)
+			return 1;
+		rc = (exfat_set_label(&ef, argv[2]) != 0);
+	}
+	else
+	{
+		if (exfat_mount(&ef, argv[1], "ro") != 0)
+			return 1;
+		puts(exfat_get_label(&ef));
+	}
+
+	exfat_unmount(&ef);
+	return rc;
+}
diff --git a/exfat/libexfat/Android.mk b/exfat/libexfat/Android.mk
new file mode 100644
index 0000000..64adef7
--- /dev/null
+++ b/exfat/libexfat/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libexfat_twrp
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -Wno-sign-compare -Wno-address-of-packed-member -Wno-missing-field-initializers
+LOCAL_SRC_FILES = cluster.c io.c log.c lookup.c mount.c node.c time.c utf.c utils.c
+LOCAL_C_INCLUDES += $(LOCAL_PATH)
+LOCAL_SHARED_LIBRARIES += libc
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/exfat/libexfat/android_config.h b/exfat/libexfat/android_config.h
new file mode 100644
index 0000000..09c3346
--- /dev/null
+++ b/exfat/libexfat/android_config.h
@@ -0,0 +1,37 @@
+/* libexfat/config.h.  Generated from config.h.in by configure.  */
+/* libexfat/config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Name of package */
+#define PACKAGE "exfat"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "relan@users.noreply.github.com"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "Free exFAT implementation"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "Free exFAT implementation 1.2.2"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "exfat"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "https://github.com/relan/exfat"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.2.2"
+
+/* Version number of package */
+#define VERSION "1.2.2"
+
+/* Enable large inode numbers on Mac OS X 10.5.  */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
diff --git a/exfat/libexfat/byteorder.h b/exfat/libexfat/byteorder.h
new file mode 100644
index 0000000..472cb48
--- /dev/null
+++ b/exfat/libexfat/byteorder.h
@@ -0,0 +1,68 @@
+/*
+	byteorder.h (12.01.10)
+	Endianness stuff. exFAT uses little-endian byte order.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef BYTEORDER_H_INCLUDED
+#define BYTEORDER_H_INCLUDED
+
+#include "platform.h"
+#include <stdint.h>
+#include <stddef.h>
+
+typedef struct { uint16_t __u16; } le16_t;
+typedef struct { uint32_t __u32; } le32_t;
+typedef struct { uint64_t __u64; } le64_t;
+
+#if EXFAT_BYTE_ORDER == EXFAT_LITTLE_ENDIAN
+
+static inline uint16_t le16_to_cpu(le16_t v) { return v.__u16; }
+static inline uint32_t le32_to_cpu(le32_t v) { return v.__u32; }
+static inline uint64_t le64_to_cpu(le64_t v) { return v.__u64; }
+
+static inline le16_t cpu_to_le16(uint16_t v) { le16_t t = {v}; return t; }
+static inline le32_t cpu_to_le32(uint32_t v) { le32_t t = {v}; return t; }
+static inline le64_t cpu_to_le64(uint64_t v) { le64_t t = {v}; return t; }
+
+typedef size_t bitmap_t;
+
+#elif EXFAT_BYTE_ORDER == EXFAT_BIG_ENDIAN
+
+static inline uint16_t le16_to_cpu(le16_t v)
+	{ return exfat_bswap16(v.__u16); }
+static inline uint32_t le32_to_cpu(le32_t v)
+	{ return exfat_bswap32(v.__u32); }
+static inline uint64_t le64_to_cpu(le64_t v)
+	{ return exfat_bswap64(v.__u64); }
+
+static inline le16_t cpu_to_le16(uint16_t v)
+	{ le16_t t = {exfat_bswap16(v)}; return t; }
+static inline le32_t cpu_to_le32(uint32_t v)
+	{ le32_t t = {exfat_bswap32(v)}; return t; }
+static inline le64_t cpu_to_le64(uint64_t v)
+	{ le64_t t = {exfat_bswap64(v)}; return t; }
+
+typedef unsigned char bitmap_t;
+
+#else
+#error Wow! You have a PDP machine?!
+#endif
+
+#endif /* ifndef BYTEORDER_H_INCLUDED */
diff --git a/exfat/libexfat/cluster.c b/exfat/libexfat/cluster.c
new file mode 100644
index 0000000..394ca4a
--- /dev/null
+++ b/exfat/libexfat/cluster.c
@@ -0,0 +1,492 @@
+/*
+	cluster.c (03.09.09)
+	exFAT file system implementation library.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "exfat.h"
+#include <errno.h>
+#include <string.h>
+#include <inttypes.h>
+
+/*
+ * Sector to absolute offset.
+ */
+static loff_t s2o(const struct exfat* ef, loff_t sector)
+{
+	return sector << ef->sb->sector_bits;
+}
+
+/*
+ * Cluster to sector.
+ */
+static loff_t c2s(const struct exfat* ef, cluster_t cluster)
+{
+	if (cluster < EXFAT_FIRST_DATA_CLUSTER)
+		exfat_bug("invalid cluster number %u", cluster);
+	return le32_to_cpu(ef->sb->cluster_sector_start) +
+		((loff_t) (cluster - EXFAT_FIRST_DATA_CLUSTER) << ef->sb->spc_bits);
+}
+
+/*
+ * Cluster to absolute offset.
+ */
+loff_t exfat_c2o(const struct exfat* ef, cluster_t cluster)
+{
+	return s2o(ef, c2s(ef, cluster));
+}
+
+/*
+ * Sector to cluster.
+ */
+static cluster_t s2c(const struct exfat* ef, loff_t sector)
+{
+	return ((sector - le32_to_cpu(ef->sb->cluster_sector_start)) >>
+			ef->sb->spc_bits) + EXFAT_FIRST_DATA_CLUSTER;
+}
+
+/*
+ * Size in bytes to size in clusters (rounded upwards).
+ */
+static uint32_t bytes2clusters(const struct exfat* ef, uint64_t bytes)
+{
+	uint64_t cluster_size = CLUSTER_SIZE(*ef->sb);
+	return (bytes + cluster_size - 1) / cluster_size;
+}
+
+cluster_t exfat_next_cluster(const struct exfat* ef,
+		const struct exfat_node* node, cluster_t cluster)
+{
+	le32_t next;
+	loff_t fat_offset;
+
+	if (cluster < EXFAT_FIRST_DATA_CLUSTER)
+		exfat_bug("bad cluster 0x%x", cluster);
+
+	if (IS_CONTIGUOUS(*node))
+		return cluster + 1;
+	fat_offset = s2o(ef, le32_to_cpu(ef->sb->fat_sector_start))
+		+ cluster * sizeof(cluster_t);
+	if (exfat_pread(ef->dev, &next, sizeof(next), fat_offset) < 0)
+		return EXFAT_CLUSTER_BAD; /* the caller should handle this and print
+		                             appropriate error message */
+	return le32_to_cpu(next);
+}
+
+cluster_t exfat_advance_cluster(const struct exfat* ef,
+		struct exfat_node* node, uint32_t count)
+{
+	uint32_t i;
+
+	if (node->fptr_index > count)
+	{
+		node->fptr_index = 0;
+		node->fptr_cluster = node->start_cluster;
+	}
+
+	for (i = node->fptr_index; i < count; i++)
+	{
+		node->fptr_cluster = exfat_next_cluster(ef, node, node->fptr_cluster);
+		if (CLUSTER_INVALID(node->fptr_cluster))
+			break; /* the caller should handle this and print appropriate 
+			          error message */
+	}
+	node->fptr_index = count;
+	return node->fptr_cluster;
+}
+
+static cluster_t find_bit_and_set(bitmap_t* bitmap, size_t start, size_t end)
+{
+	const size_t start_index = start / sizeof(bitmap_t) / 8;
+	const size_t end_index = DIV_ROUND_UP(end, sizeof(bitmap_t) * 8);
+	size_t i;
+	size_t start_bitindex;
+	size_t end_bitindex;
+	size_t c;
+
+	for (i = start_index; i < end_index; i++)
+	{
+		if (bitmap[i] == ~((bitmap_t) 0))
+			continue;
+		start_bitindex = MAX(i * sizeof(bitmap_t) * 8, start);
+		end_bitindex = MIN((i + 1) * sizeof(bitmap_t) * 8, end);
+		for (c = start_bitindex; c < end_bitindex; c++)
+			if (BMAP_GET(bitmap, c) == 0)
+			{
+				BMAP_SET(bitmap, c);
+				return c + EXFAT_FIRST_DATA_CLUSTER;
+			}
+	}
+	return EXFAT_CLUSTER_END;
+}
+
+static int flush_nodes(struct exfat* ef, struct exfat_node* node)
+{
+	struct exfat_node* p;
+
+	for (p = node->child; p != NULL; p = p->next)
+	{
+		int rc = flush_nodes(ef, p);
+		if (rc != 0)
+			return rc;
+	}
+	return exfat_flush_node(ef, node);
+}
+
+int exfat_flush_nodes(struct exfat* ef)
+{
+	return flush_nodes(ef, ef->root);
+}
+
+int exfat_flush(struct exfat* ef)
+{
+	if (ef->cmap.dirty)
+	{
+		if (exfat_pwrite(ef->dev, ef->cmap.chunk,
+				BMAP_SIZE(ef->cmap.chunk_size),
+				exfat_c2o(ef, ef->cmap.start_cluster)) < 0)
+		{
+			exfat_error("failed to write clusters bitmap");
+			return -EIO;
+		}
+		ef->cmap.dirty = false;
+	}
+
+	return 0;
+}
+
+static bool set_next_cluster(const struct exfat* ef, bool contiguous,
+		cluster_t current, cluster_t next)
+{
+	loff_t fat_offset;
+	le32_t next_le32;
+
+	if (contiguous)
+		return true;
+	fat_offset = s2o(ef, le32_to_cpu(ef->sb->fat_sector_start))
+		+ current * sizeof(cluster_t);
+	next_le32 = cpu_to_le32(next);
+	if (exfat_pwrite(ef->dev, &next_le32, sizeof(next_le32), fat_offset) < 0)
+	{
+		exfat_error("failed to write the next cluster %#x after %#x", next,
+				current);
+		return false;
+	}
+	return true;
+}
+
+static cluster_t allocate_cluster(struct exfat* ef, cluster_t hint)
+{
+	cluster_t cluster;
+
+	hint -= EXFAT_FIRST_DATA_CLUSTER;
+	if (hint >= ef->cmap.chunk_size)
+		hint = 0;
+
+	cluster = find_bit_and_set(ef->cmap.chunk, hint, ef->cmap.chunk_size);
+	if (cluster == EXFAT_CLUSTER_END)
+		cluster = find_bit_and_set(ef->cmap.chunk, 0, hint);
+	if (cluster == EXFAT_CLUSTER_END)
+	{
+		exfat_error("no free space left");
+		return EXFAT_CLUSTER_END;
+	}
+
+	ef->cmap.dirty = true;
+	return cluster;
+}
+
+static void free_cluster(struct exfat* ef, cluster_t cluster)
+{
+	if (CLUSTER_INVALID(cluster))
+		exfat_bug("freeing invalid cluster 0x%x", cluster);
+	if (cluster - EXFAT_FIRST_DATA_CLUSTER >= ef->cmap.size)
+		exfat_bug("freeing non-existing cluster 0x%x (0x%x)", cluster,
+				ef->cmap.size);
+
+	BMAP_CLR(ef->cmap.chunk, cluster - EXFAT_FIRST_DATA_CLUSTER);
+	ef->cmap.dirty = true;
+}
+
+static bool make_noncontiguous(const struct exfat* ef, cluster_t first,
+		cluster_t last)
+{
+	cluster_t c;
+
+	for (c = first; c < last; c++)
+		if (!set_next_cluster(ef, false, c, c + 1))
+			return false;
+	return true;
+}
+
+static int shrink_file(struct exfat* ef, struct exfat_node* node,
+		uint32_t current, uint32_t difference);
+
+static int grow_file(struct exfat* ef, struct exfat_node* node,
+		uint32_t current, uint32_t difference)
+{
+	cluster_t previous;
+	cluster_t next;
+	uint32_t allocated = 0;
+
+	if (difference == 0)
+		exfat_bug("zero clusters count passed");
+
+	if (node->start_cluster != EXFAT_CLUSTER_FREE)
+	{
+		/* get the last cluster of the file */
+		previous = exfat_advance_cluster(ef, node, current - 1);
+		if (CLUSTER_INVALID(previous))
+		{
+			exfat_error("invalid cluster 0x%x while growing", previous);
+			return -EIO;
+		}
+	}
+	else
+	{
+		if (node->fptr_index != 0)
+			exfat_bug("non-zero pointer index (%u)", node->fptr_index);
+		/* file does not have clusters (i.e. is empty), allocate
+		   the first one for it */
+		previous = allocate_cluster(ef, 0);
+		if (CLUSTER_INVALID(previous))
+			return -ENOSPC;
+		node->fptr_cluster = node->start_cluster = previous;
+		allocated = 1;
+		/* file consists of only one cluster, so it's contiguous */
+		node->flags |= EXFAT_ATTRIB_CONTIGUOUS;
+	}
+
+	while (allocated < difference)
+	{
+		next = allocate_cluster(ef, previous + 1);
+		if (CLUSTER_INVALID(next))
+		{
+			if (allocated != 0)
+				shrink_file(ef, node, current + allocated, allocated);
+			return -ENOSPC;
+		}
+		if (next != previous - 1 && IS_CONTIGUOUS(*node))
+		{
+			/* it's a pity, but we are not able to keep the file contiguous
+			   anymore */
+			if (!make_noncontiguous(ef, node->start_cluster, previous))
+				return -EIO;
+			node->flags &= ~EXFAT_ATTRIB_CONTIGUOUS;
+			node->flags |= EXFAT_ATTRIB_DIRTY;
+		}
+		if (!set_next_cluster(ef, IS_CONTIGUOUS(*node), previous, next))
+			return -EIO;
+		previous = next;
+		allocated++;
+	}
+
+	if (!set_next_cluster(ef, IS_CONTIGUOUS(*node), previous,
+			EXFAT_CLUSTER_END))
+		return -EIO;
+	return 0;
+}
+
+static int shrink_file(struct exfat* ef, struct exfat_node* node,
+		uint32_t current, uint32_t difference)
+{
+	cluster_t previous;
+	cluster_t next;
+
+	if (difference == 0)
+		exfat_bug("zero difference passed");
+	if (node->start_cluster == EXFAT_CLUSTER_FREE)
+		exfat_bug("unable to shrink empty file (%u clusters)", current);
+	if (current < difference)
+		exfat_bug("file underflow (%u < %u)", current, difference);
+
+	/* crop the file */
+	if (current > difference)
+	{
+		cluster_t last = exfat_advance_cluster(ef, node,
+				current - difference - 1);
+		if (CLUSTER_INVALID(last))
+		{
+			exfat_error("invalid cluster 0x%x while shrinking", last);
+			return -EIO;
+		}
+		previous = exfat_next_cluster(ef, node, last);
+		if (!set_next_cluster(ef, IS_CONTIGUOUS(*node), last,
+				EXFAT_CLUSTER_END))
+			return -EIO;
+	}
+	else
+	{
+		previous = node->start_cluster;
+		node->start_cluster = EXFAT_CLUSTER_FREE;
+		node->flags |= EXFAT_ATTRIB_DIRTY;
+	}
+	node->fptr_index = 0;
+	node->fptr_cluster = node->start_cluster;
+
+	/* free remaining clusters */
+	while (difference--)
+	{
+		if (CLUSTER_INVALID(previous))
+		{
+			exfat_error("invalid cluster 0x%x while freeing after shrink",
+					previous);
+			return -EIO;
+		}
+		next = exfat_next_cluster(ef, node, previous);
+		if (!set_next_cluster(ef, IS_CONTIGUOUS(*node), previous,
+				EXFAT_CLUSTER_FREE))
+			return -EIO;
+		free_cluster(ef, previous);
+		previous = next;
+	}
+	return 0;
+}
+
+static bool erase_raw(struct exfat* ef, size_t size, loff_t offset)
+{
+	if (exfat_pwrite(ef->dev, ef->zero_cluster, size, offset) < 0)
+	{
+		exfat_error("failed to erase %zu bytes at %"PRId64, size, offset);
+		return false;
+	}
+	return true;
+}
+
+static int erase_range(struct exfat* ef, struct exfat_node* node,
+		uint64_t begin, uint64_t end)
+{
+	uint64_t cluster_boundary;
+	cluster_t cluster;
+
+	if (begin >= end)
+		return 0;
+
+	cluster_boundary = (begin | (CLUSTER_SIZE(*ef->sb) - 1)) + 1;
+	cluster = exfat_advance_cluster(ef, node,
+			begin / CLUSTER_SIZE(*ef->sb));
+	if (CLUSTER_INVALID(cluster))
+	{
+		exfat_error("invalid cluster 0x%x while erasing", cluster);
+		return -EIO;
+	}
+	/* erase from the beginning to the closest cluster boundary */
+	if (!erase_raw(ef, MIN(cluster_boundary, end) - begin,
+			exfat_c2o(ef, cluster) + begin % CLUSTER_SIZE(*ef->sb)))
+		return -EIO;
+	/* erase whole clusters */
+	while (cluster_boundary < end)
+	{
+		cluster = exfat_next_cluster(ef, node, cluster);
+		/* the cluster cannot be invalid because we have just allocated it */
+		if (CLUSTER_INVALID(cluster))
+			exfat_bug("invalid cluster 0x%x after allocation", cluster);
+		if (!erase_raw(ef, CLUSTER_SIZE(*ef->sb), exfat_c2o(ef, cluster)))
+			return -EIO;
+		cluster_boundary += CLUSTER_SIZE(*ef->sb);
+	}
+	return 0;
+}
+
+int exfat_truncate(struct exfat* ef, struct exfat_node* node, uint64_t size,
+		bool erase)
+{
+	uint32_t c1 = bytes2clusters(ef, node->size);
+	uint32_t c2 = bytes2clusters(ef, size);
+	int rc = 0;
+
+	if (node->references == 0 && node->parent)
+		exfat_bug("no references, node changes can be lost");
+
+	if (node->size == size)
+		return 0;
+
+	if (c1 < c2)
+		rc = grow_file(ef, node, c1, c2 - c1);
+	else if (c1 > c2)
+		rc = shrink_file(ef, node, c1, c1 - c2);
+
+	if (rc != 0)
+		return rc;
+
+	if (erase)
+	{
+		rc = erase_range(ef, node, node->size, size);
+		if (rc != 0)
+			return rc;
+	}
+
+	exfat_update_mtime(node);
+	node->size = size;
+	node->flags |= EXFAT_ATTRIB_DIRTY;
+	return 0;
+}
+
+uint32_t exfat_count_free_clusters(const struct exfat* ef)
+{
+	uint32_t free_clusters = 0;
+	uint32_t i;
+
+	for (i = 0; i < ef->cmap.size; i++)
+		if (BMAP_GET(ef->cmap.chunk, i) == 0)
+			free_clusters++;
+	return free_clusters;
+}
+
+static int find_used_clusters(const struct exfat* ef,
+		cluster_t* a, cluster_t* b)
+{
+	const cluster_t end = le32_to_cpu(ef->sb->cluster_count);
+
+	/* find first used cluster */
+	for (*a = *b + 1; *a < end; (*a)++)
+		if (BMAP_GET(ef->cmap.chunk, *a - EXFAT_FIRST_DATA_CLUSTER))
+			break;
+	if (*a >= end)
+		return 1;
+
+	/* find last contiguous used cluster */
+	for (*b = *a; *b < end; (*b)++)
+		if (BMAP_GET(ef->cmap.chunk, *b - EXFAT_FIRST_DATA_CLUSTER) == 0)
+		{
+			(*b)--;
+			break;
+		}
+
+	return 0;
+}
+
+int exfat_find_used_sectors(const struct exfat* ef, loff_t* a, loff_t* b)
+{
+	cluster_t ca, cb;
+
+	if (*a == 0 && *b == 0)
+		ca = cb = EXFAT_FIRST_DATA_CLUSTER - 1;
+	else
+	{
+		ca = s2c(ef, *a);
+		cb = s2c(ef, *b);
+	}
+	if (find_used_clusters(ef, &ca, &cb) != 0)
+		return 1;
+	if (*a != 0 || *b != 0)
+		*a = c2s(ef, ca);
+	*b = c2s(ef, cb) + (CLUSTER_SIZE(*ef->sb) - 1) / SECTOR_SIZE(*ef->sb);
+	return 0;
+}
diff --git a/exfat/libexfat/compiler.h b/exfat/libexfat/compiler.h
new file mode 100644
index 0000000..3b092a9
--- /dev/null
+++ b/exfat/libexfat/compiler.h
@@ -0,0 +1,66 @@
+/*
+	compiler.h (09.06.13)
+	Compiler-specific definitions. Note that unknown compiler is not a
+	showstopper.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef COMPILER_H_INCLUDED
+#define COMPILER_H_INCLUDED
+
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ < 199901L)
+#error C99-compliant compiler is required
+#endif
+
+#if defined(__clang__)
+
+#define PRINTF __attribute__((format(printf, 1, 2)))
+#define NORETURN __attribute__((noreturn))
+#define PACKED __attribute__((packed))
+#if __has_extension(c_static_assert)
+#define USE_C11_STATIC_ASSERT
+#endif
+
+#elif defined(__GNUC__)
+
+#define PRINTF __attribute__((format(printf, 1, 2)))
+#define NORETURN __attribute__((noreturn))
+#define PACKED __attribute__((packed))
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#define USE_C11_STATIC_ASSERT
+#endif
+
+#else
+
+#define PRINTF
+#define NORETURN
+#define PACKED
+
+#endif
+
+#ifdef USE_C11_STATIC_ASSERT
+#define STATIC_ASSERT(cond) _Static_assert(cond, #cond)
+#else
+#define CONCAT2(a, b) a ## b
+#define CONCAT1(a, b) CONCAT2(a, b)
+#define STATIC_ASSERT(cond) \
+	extern void CONCAT1(static_assert, __LINE__)(int x[(cond) ? 1 : -1])
+#endif
+
+#endif /* ifndef COMPILER_H_INCLUDED */
diff --git a/exfat/libexfat/exfat.h b/exfat/libexfat/exfat.h
new file mode 100644
index 0000000..009a0c0
--- /dev/null
+++ b/exfat/libexfat/exfat.h
@@ -0,0 +1,228 @@
+/*
+	exfat.h (29.08.09)
+	Definitions of structures and constants used in exFAT file system
+	implementation.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef EXFAT_H_INCLUDED
+#define EXFAT_H_INCLUDED
+
+#if defined(__ANDROID__)
+#include "android_config.h"
+#else
+#include "config.h"
+#endif
+#include "compiler.h"
+#include "exfatfs.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define EXFAT_NAME_MAX 256
+#define EXFAT_ATTRIB_CONTIGUOUS 0x10000
+#define EXFAT_ATTRIB_CACHED     0x20000
+#define EXFAT_ATTRIB_DIRTY      0x40000
+#define EXFAT_ATTRIB_UNLINKED   0x80000
+#define IS_CONTIGUOUS(node) (((node).flags & EXFAT_ATTRIB_CONTIGUOUS) != 0)
+#define SECTOR_SIZE(sb) (1 << (sb).sector_bits)
+#define CLUSTER_SIZE(sb) (SECTOR_SIZE(sb) << (sb).spc_bits)
+#define CLUSTER_INVALID(c) \
+	((c) < EXFAT_FIRST_DATA_CLUSTER || (c) > EXFAT_LAST_DATA_CLUSTER)
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define DIV_ROUND_UP(x, d) (((x) + (d) - 1) / (d))
+#define ROUND_UP(x, d) (DIV_ROUND_UP(x, d) * (d))
+#define UTF8_BYTES(c) ((c) * 6) /* UTF-8 character can occupy up to 6 bytes */
+
+#define BMAP_SIZE(count) (ROUND_UP(count, sizeof(bitmap_t) * 8) / 8)
+#define BMAP_BLOCK(index) ((index) / sizeof(bitmap_t) / 8)
+#define BMAP_MASK(index) ((bitmap_t) 1 << ((index) % (sizeof(bitmap_t) * 8)))
+#define BMAP_GET(bitmap, index) \
+	((bitmap)[BMAP_BLOCK(index)] & BMAP_MASK(index))
+#define BMAP_SET(bitmap, index) \
+	((bitmap)[BMAP_BLOCK(index)] |= BMAP_MASK(index))
+#define BMAP_CLR(bitmap, index) \
+	((bitmap)[BMAP_BLOCK(index)] &= ~BMAP_MASK(index))
+
+/* The size of off_t type must be 64 bits. File systems larger than 2 GB will
+   be corrupted with 32-bit off_t. So, we use loff_t here.*/
+STATIC_ASSERT(sizeof(loff_t) == 8);
+
+struct exfat_node
+{
+	struct exfat_node* parent;
+	struct exfat_node* child;
+	struct exfat_node* next;
+	struct exfat_node* prev;
+
+	int references;
+	uint32_t fptr_index;
+	cluster_t fptr_cluster;
+	cluster_t entry_cluster;
+	loff_t entry_offset;
+	cluster_t start_cluster;
+	int flags;
+	uint64_t size;
+	time_t mtime, atime;
+	le16_t name[EXFAT_NAME_MAX + 1];
+};
+
+enum exfat_mode
+{
+	EXFAT_MODE_RO,
+	EXFAT_MODE_RW,
+	EXFAT_MODE_ANY,
+};
+
+struct exfat_dev;
+
+struct exfat
+{
+	struct exfat_dev* dev;
+	struct exfat_super_block* sb;
+	le16_t* upcase;
+	size_t upcase_chars;
+	struct exfat_node* root;
+	struct
+	{
+		cluster_t start_cluster;
+		uint32_t size;				/* in bits */
+		bitmap_t* chunk;
+		uint32_t chunk_size;		/* in bits */
+		bool dirty;
+	}
+	cmap;
+	char label[UTF8_BYTES(EXFAT_ENAME_MAX) + 1];
+	void* zero_cluster;
+	int dmask, fmask;
+	uid_t uid;
+	gid_t gid;
+	int ro;
+	bool noatime;
+};
+
+/* in-core nodes iterator */
+struct exfat_iterator
+{
+	struct exfat_node* parent;
+	struct exfat_node* current;
+};
+
+struct exfat_human_bytes
+{
+	uint64_t value;
+	const char* unit;
+};
+
+extern int exfat_errors;
+
+void exfat_bug(const char* format, ...) PRINTF NORETURN;
+void exfat_error(const char* format, ...) PRINTF;
+void exfat_warn(const char* format, ...) PRINTF;
+void exfat_debug(const char* format, ...) PRINTF;
+
+struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode);
+int exfat_close(struct exfat_dev* dev);
+int exfat_fsync(struct exfat_dev* dev);
+enum exfat_mode exfat_get_mode(const struct exfat_dev* dev);
+loff_t exfat_get_size(const struct exfat_dev* dev);
+loff_t exfat_seek(struct exfat_dev* dev, loff_t offset, int whence);
+ssize_t exfat_read(struct exfat_dev* dev, void* buffer, size_t size);
+ssize_t exfat_write(struct exfat_dev* dev, const void* buffer, size_t size);
+ssize_t exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
+		loff_t offset);
+ssize_t exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
+		loff_t offset);
+ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
+		void* buffer, size_t size, loff_t offset);
+ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
+		const void* buffer, size_t size, loff_t offset);
+
+int exfat_opendir(struct exfat* ef, struct exfat_node* dir,
+		struct exfat_iterator* it);
+void exfat_closedir(struct exfat* ef, struct exfat_iterator* it);
+struct exfat_node* exfat_readdir(struct exfat* ef, struct exfat_iterator* it);
+int exfat_lookup(struct exfat* ef, struct exfat_node** node,
+		const char* path);
+int exfat_split(struct exfat* ef, struct exfat_node** parent,
+		struct exfat_node** node, le16_t* name, const char* path);
+
+loff_t exfat_c2o(const struct exfat* ef, cluster_t cluster);
+cluster_t exfat_next_cluster(const struct exfat* ef,
+		const struct exfat_node* node, cluster_t cluster);
+cluster_t exfat_advance_cluster(const struct exfat* ef,
+		struct exfat_node* node, uint32_t count);
+int exfat_flush_nodes(struct exfat* ef);
+int exfat_flush(struct exfat* ef);
+int exfat_truncate(struct exfat* ef, struct exfat_node* node, uint64_t size,
+		bool erase);
+uint32_t exfat_count_free_clusters(const struct exfat* ef);
+int exfat_find_used_sectors(const struct exfat* ef, loff_t* a, loff_t* b);
+
+void exfat_stat(const struct exfat* ef, const struct exfat_node* node,
+		struct stat* stbuf);
+void exfat_get_name(const struct exfat_node* node, char* buffer, size_t n);
+uint16_t exfat_start_checksum(const struct exfat_entry_meta1* entry);
+uint16_t exfat_add_checksum(const void* entry, uint16_t sum);
+le16_t exfat_calc_checksum(const struct exfat_entry_meta1* meta1,
+		const struct exfat_entry_meta2* meta2, const le16_t* name);
+uint32_t exfat_vbr_start_checksum(const void* sector, size_t size);
+uint32_t exfat_vbr_add_checksum(const void* sector, size_t size, uint32_t sum);
+le16_t exfat_calc_name_hash(const struct exfat* ef, const le16_t* name);
+void exfat_humanize_bytes(uint64_t value, struct exfat_human_bytes* hb);
+void exfat_print_info(const struct exfat_super_block* sb,
+		uint32_t free_clusters);
+
+int utf16_to_utf8(char* output, const le16_t* input, size_t outsize,
+		size_t insize);
+int utf8_to_utf16(le16_t* output, const char* input, size_t outsize,
+		size_t insize);
+size_t utf16_length(const le16_t* str);
+
+struct exfat_node* exfat_get_node(struct exfat_node* node);
+void exfat_put_node(struct exfat* ef, struct exfat_node* node);
+int exfat_cleanup_node(struct exfat* ef, struct exfat_node* node);
+int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir);
+void exfat_reset_cache(struct exfat* ef);
+int exfat_flush_node(struct exfat* ef, struct exfat_node* node);
+int exfat_unlink(struct exfat* ef, struct exfat_node* node);
+int exfat_rmdir(struct exfat* ef, struct exfat_node* node);
+int exfat_mknod(struct exfat* ef, const char* path);
+int exfat_mkdir(struct exfat* ef, const char* path);
+int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path);
+void exfat_utimes(struct exfat_node* node, const struct timespec tv[2]);
+void exfat_update_atime(struct exfat_node* node);
+void exfat_update_mtime(struct exfat_node* node);
+const char* exfat_get_label(struct exfat* ef);
+int exfat_set_label(struct exfat* ef, const char* label);
+
+int exfat_mount(struct exfat* ef, const char* spec, const char* options);
+void exfat_unmount(struct exfat* ef);
+
+time_t exfat_exfat2unix(le16_t date, le16_t time, uint8_t centisec);
+void exfat_unix2exfat(time_t unix_time, le16_t* date, le16_t* time,
+		uint8_t* centisec);
+void exfat_tzset(void);
+
+#endif /* ifndef EXFAT_H_INCLUDED */
diff --git a/exfat/libexfat/exfatfs.h b/exfat/libexfat/exfatfs.h
new file mode 100644
index 0000000..eca2cac
--- /dev/null
+++ b/exfat/libexfat/exfatfs.h
@@ -0,0 +1,175 @@
+/*
+	exfatfs.h (29.08.09)
+	Definitions of structures and constants used in exFAT file system.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef EXFATFS_H_INCLUDED
+#define EXFATFS_H_INCLUDED
+
+#include "byteorder.h"
+#include "compiler.h"
+
+typedef uint32_t cluster_t;		/* cluster number */
+
+#define EXFAT_FIRST_DATA_CLUSTER 2
+#define EXFAT_LAST_DATA_CLUSTER 0xfffffff6
+
+#define EXFAT_CLUSTER_FREE         0 /* free cluster */
+#define EXFAT_CLUSTER_BAD 0xfffffff7 /* cluster contains bad sector */
+#define EXFAT_CLUSTER_END 0xffffffff /* final cluster of file or directory */
+
+#define EXFAT_STATE_MOUNTED 2
+
+struct exfat_super_block
+{
+	uint8_t jump[3];				/* 0x00 jmp and nop instructions */
+	uint8_t oem_name[8];			/* 0x03 "EXFAT   " */
+	uint8_t	__unused1[53];			/* 0x0B always 0 */
+	le64_t sector_start;			/* 0x40 partition first sector */
+	le64_t sector_count;			/* 0x48 partition sectors count */
+	le32_t fat_sector_start;		/* 0x50 FAT first sector */
+	le32_t fat_sector_count;		/* 0x54 FAT sectors count */
+	le32_t cluster_sector_start;	/* 0x58 first cluster sector */
+	le32_t cluster_count;			/* 0x5C total clusters count */
+	le32_t rootdir_cluster;			/* 0x60 first cluster of the root dir */
+	le32_t volume_serial;			/* 0x64 volume serial number */
+	struct							/* 0x68 FS version */
+	{
+		uint8_t minor;
+		uint8_t major;
+	}
+	version;
+	le16_t volume_state;			/* 0x6A volume state flags */
+	uint8_t sector_bits;			/* 0x6C sector size as (1 << n) */
+	uint8_t spc_bits;				/* 0x6D sectors per cluster as (1 << n) */
+	uint8_t fat_count;				/* 0x6E always 1 */
+	uint8_t drive_no;				/* 0x6F always 0x80 */
+	uint8_t allocated_percent;		/* 0x70 percentage of allocated space */
+	uint8_t __unused2[397];			/* 0x71 always 0 */
+	le16_t boot_signature;			/* the value of 0xAA55 */
+}
+PACKED;
+STATIC_ASSERT(sizeof(struct exfat_super_block) == 512);
+
+#define EXFAT_ENTRY_VALID     0x80
+#define EXFAT_ENTRY_CONTINUED 0x40
+#define EXFAT_ENTRY_OPTIONAL  0x20
+
+#define EXFAT_ENTRY_BITMAP    (0x01 | EXFAT_ENTRY_VALID)
+#define EXFAT_ENTRY_UPCASE    (0x02 | EXFAT_ENTRY_VALID)
+#define EXFAT_ENTRY_LABEL     (0x03 | EXFAT_ENTRY_VALID)
+#define EXFAT_ENTRY_FILE      (0x05 | EXFAT_ENTRY_VALID)
+#define EXFAT_ENTRY_FILE_INFO (0x00 | EXFAT_ENTRY_VALID | EXFAT_ENTRY_CONTINUED)
+#define EXFAT_ENTRY_FILE_NAME (0x01 | EXFAT_ENTRY_VALID | EXFAT_ENTRY_CONTINUED)
+
+struct exfat_entry					/* common container for all entries */
+{
+	uint8_t type;					/* any of EXFAT_ENTRY_xxx */
+	uint8_t data[31];
+}
+PACKED;
+STATIC_ASSERT(sizeof(struct exfat_entry) == 32);
+
+#define EXFAT_ENAME_MAX 15
+
+struct exfat_entry_bitmap			/* allocated clusters bitmap */
+{
+	uint8_t type;					/* EXFAT_ENTRY_BITMAP */
+	uint8_t __unknown1[19];
+	le32_t start_cluster;
+	le64_t size;					/* in bytes */
+}
+PACKED;
+STATIC_ASSERT(sizeof(struct exfat_entry_bitmap) == 32);
+
+struct exfat_entry_upcase			/* upper case translation table */
+{
+	uint8_t type;					/* EXFAT_ENTRY_UPCASE */
+	uint8_t __unknown1[3];
+	le32_t checksum;
+	uint8_t __unknown2[12];
+	le32_t start_cluster;
+	le64_t size;					/* in bytes */
+}
+PACKED;
+STATIC_ASSERT(sizeof(struct exfat_entry_upcase) == 32);
+
+struct exfat_entry_label			/* volume label */
+{
+	uint8_t type;					/* EXFAT_ENTRY_LABEL */
+	uint8_t length;					/* number of characters */
+	le16_t name[EXFAT_ENAME_MAX];	/* in UTF-16LE */
+}
+PACKED;
+STATIC_ASSERT(sizeof(struct exfat_entry_label) == 32);
+
+#define EXFAT_ATTRIB_RO     0x01
+#define EXFAT_ATTRIB_HIDDEN 0x02
+#define EXFAT_ATTRIB_SYSTEM 0x04
+#define EXFAT_ATTRIB_VOLUME 0x08
+#define EXFAT_ATTRIB_DIR    0x10
+#define EXFAT_ATTRIB_ARCH   0x20
+
+struct exfat_entry_meta1			/* file or directory info (part 1) */
+{
+	uint8_t type;					/* EXFAT_ENTRY_FILE */
+	uint8_t continuations;
+	le16_t checksum;
+	le16_t attrib;					/* combination of EXFAT_ATTRIB_xxx */
+	le16_t __unknown1;
+	le16_t crtime, crdate;			/* creation date and time */
+	le16_t mtime, mdate;			/* latest modification date and time */
+	le16_t atime, adate;			/* latest access date and time */
+	uint8_t crtime_cs;				/* creation time in cs (centiseconds) */
+	uint8_t mtime_cs;				/* latest modification time in cs */
+	uint8_t __unknown2[10];
+}
+PACKED;
+STATIC_ASSERT(sizeof(struct exfat_entry_meta1) == 32);
+
+#define EXFAT_FLAG_ALWAYS1		(1u << 0)
+#define EXFAT_FLAG_CONTIGUOUS	(1u << 1)
+
+struct exfat_entry_meta2			/* file or directory info (part 2) */
+{
+	uint8_t type;					/* EXFAT_ENTRY_FILE_INFO */
+	uint8_t flags;					/* combination of EXFAT_FLAG_xxx */
+	uint8_t __unknown1;
+	uint8_t name_length;
+	le16_t name_hash;
+	le16_t __unknown2;
+	le64_t valid_size;				/* in bytes, less or equal to size */
+	uint8_t __unknown3[4];
+	le32_t start_cluster;
+	le64_t size;					/* in bytes */
+}
+PACKED;
+STATIC_ASSERT(sizeof(struct exfat_entry_meta2) == 32);
+
+struct exfat_entry_name				/* file or directory name */
+{
+	uint8_t type;					/* EXFAT_ENTRY_FILE_NAME */
+	uint8_t __unknown;
+	le16_t name[EXFAT_ENAME_MAX];	/* in UTF-16LE */
+}
+PACKED;
+STATIC_ASSERT(sizeof(struct exfat_entry_name) == 32);
+
+#endif /* ifndef EXFATFS_H_INCLUDED */
diff --git a/exfat/libexfat/io.c b/exfat/libexfat/io.c
new file mode 100644
index 0000000..60aabb5
--- /dev/null
+++ b/exfat/libexfat/io.c
@@ -0,0 +1,440 @@
+/*
+	io.c (02.09.09)
+	exFAT file system implementation library.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "exfat.h"
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#if defined(__APPLE__)
+#include <sys/disk.h>
+#elif defined(__OpenBSD__)
+#include <sys/param.h>
+#include <sys/disklabel.h>
+#include <sys/dkio.h>
+#include <sys/ioctl.h>
+#endif
+#include <sys/mount.h>
+#ifdef USE_UBLIO
+#include <sys/uio.h>
+#include <ublio.h>
+#endif
+
+struct exfat_dev
+{
+	int fd;
+	enum exfat_mode mode;
+	loff_t size; /* in bytes */
+#ifdef USE_UBLIO
+	loff_t pos;
+	ublio_filehandle_t ufh;
+#endif
+};
+
+static int open_ro(const char* spec)
+{
+	return open(spec, O_RDONLY);
+}
+
+static int open_rw(const char* spec)
+{
+	int fd = open(spec, O_RDWR);
+#ifdef __linux__
+	int ro = 0;
+
+	/*
+	   This ioctl is needed because after "blockdev --setro" kernel still
+	   allows to open the device in read-write mode but fails writes.
+	*/
+	if (fd != -1 && ioctl(fd, BLKROGET, &ro) == 0 && ro)
+	{
+		close(fd);
+		errno = EROFS;
+		return -1;
+	}
+#endif
+	return fd;
+}
+
+struct exfat_dev* exfat_open(const char* spec, enum exfat_mode mode)
+{
+	struct exfat_dev* dev;
+	struct stat stbuf;
+#ifdef USE_UBLIO
+	struct ublio_param up;
+#endif
+
+	dev = malloc(sizeof(struct exfat_dev));
+	if (dev == NULL)
+	{
+		exfat_error("failed to allocate memory for device structure");
+		return NULL;
+	}
+
+	switch (mode)
+	{
+	case EXFAT_MODE_RO:
+		dev->fd = open_ro(spec);
+		if (dev->fd == -1)
+		{
+			free(dev);
+			exfat_error("failed to open '%s' in read-only mode: %s", spec,
+					strerror(errno));
+			return NULL;
+		}
+		dev->mode = EXFAT_MODE_RO;
+		break;
+	case EXFAT_MODE_RW:
+		dev->fd = open_rw(spec);
+		if (dev->fd == -1)
+		{
+			free(dev);
+			exfat_error("failed to open '%s' in read-write mode: %s", spec,
+					strerror(errno));
+			return NULL;
+		}
+		dev->mode = EXFAT_MODE_RW;
+		break;
+	case EXFAT_MODE_ANY:
+		dev->fd = open_rw(spec);
+		if (dev->fd != -1)
+		{
+			dev->mode = EXFAT_MODE_RW;
+			break;
+		}
+		dev->fd = open_ro(spec);
+		if (dev->fd != -1)
+		{
+			dev->mode = EXFAT_MODE_RO;
+			exfat_warn("'%s' is write-protected, mounting read-only", spec);
+			break;
+		}
+		free(dev);
+		exfat_error("failed to open '%s': %s", spec, strerror(errno));
+		return NULL;
+	}
+
+	if (fstat(dev->fd, &stbuf) != 0)
+	{
+		close(dev->fd);
+		free(dev);
+		exfat_error("failed to fstat '%s'", spec);
+		return NULL;
+	}
+	if (!S_ISBLK(stbuf.st_mode) &&
+		!S_ISCHR(stbuf.st_mode) &&
+		!S_ISREG(stbuf.st_mode))
+	{
+		close(dev->fd);
+		free(dev);
+		exfat_error("'%s' is neither a device, nor a regular file", spec);
+		return NULL;
+	}
+
+#if defined(__APPLE__)
+	if (!S_ISREG(stbuf.st_mode))
+	{
+		uint32_t block_size = 0;
+		uint64_t blocks = 0;
+
+		if (ioctl(dev->fd, DKIOCGETBLOCKSIZE, &block_size) != 0)
+		{
+			close(dev->fd);
+			free(dev);
+			exfat_error("failed to get block size");
+			return NULL;
+		}
+		if (ioctl(dev->fd, DKIOCGETBLOCKCOUNT, &blocks) != 0)
+		{
+			close(dev->fd);
+			free(dev);
+			exfat_error("failed to get blocks count");
+			return NULL;
+		}
+		dev->size = blocks * block_size;
+	}
+	else
+#elif defined(__OpenBSD__)
+	if (!S_ISREG(stbuf.st_mode))
+	{
+		struct disklabel lab;
+		struct partition* pp;
+		char* partition;
+
+		if (ioctl(dev->fd, DIOCGDINFO, &lab) == -1)
+		{
+			close(dev->fd);
+			free(dev);
+			exfat_error("failed to get disklabel");
+			return NULL;
+		}
+
+		/* Don't need to check that partition letter is valid as we won't get
+		   this far otherwise. */
+		partition = strchr(spec, '\0') - 1;
+		pp = &(lab.d_partitions[*partition - 'a']);
+		dev->size = DL_GETPSIZE(pp) * lab.d_secsize;
+
+		if (pp->p_fstype != FS_NTFS)
+			exfat_warn("partition type is not 0x07 (NTFS/exFAT); "
+					"you can fix this with fdisk(8)");
+	}
+	else
+#endif
+	{
+		/* works for Linux, FreeBSD, Solaris */
+		dev->size = exfat_seek(dev, 0, SEEK_END);
+		if (dev->size <= 0)
+		{
+			close(dev->fd);
+			free(dev);
+			exfat_error("failed to get size of '%s'", spec);
+			return NULL;
+		}
+		if (exfat_seek(dev, 0, SEEK_SET) == -1)
+		{
+			close(dev->fd);
+			free(dev);
+			exfat_error("failed to seek to the beginning of '%s'", spec);
+			return NULL;
+		}
+	}
+
+#ifdef USE_UBLIO
+	memset(&up, 0, sizeof(struct ublio_param));
+	up.up_blocksize = 256 * 1024;
+	up.up_items = 64;
+	up.up_grace = 32;
+	up.up_priv = &dev->fd;
+
+	dev->pos = 0;
+	dev->ufh = ublio_open(&up);
+	if (dev->ufh == NULL)
+	{
+		close(dev->fd);
+		free(dev);
+		exfat_error("failed to initialize ublio");
+		return NULL;
+	}
+#endif
+
+	return dev;
+}
+
+int exfat_close(struct exfat_dev* dev)
+{
+	int rc = 0;
+
+#ifdef USE_UBLIO
+	if (ublio_close(dev->ufh) != 0)
+	{
+		exfat_error("failed to close ublio");
+		rc = -EIO;
+	}
+#endif
+	if (close(dev->fd) != 0)
+	{
+		exfat_error("failed to close device: %s", strerror(errno));
+		rc = -EIO;
+	}
+	free(dev);
+	return rc;
+}
+
+int exfat_fsync(struct exfat_dev* dev)
+{
+	int rc = 0;
+
+#ifdef USE_UBLIO
+	if (ublio_fsync(dev->ufh) != 0)
+	{
+		exfat_error("ublio fsync failed");
+		rc = -EIO;
+	}
+#endif
+	if (fsync(dev->fd) != 0)
+	{
+		exfat_error("fsync failed: %s", strerror(errno));
+		rc = -EIO;
+	}
+	return rc;
+}
+
+enum exfat_mode exfat_get_mode(const struct exfat_dev* dev)
+{
+	return dev->mode;
+}
+
+loff_t exfat_get_size(const struct exfat_dev* dev)
+{
+	return dev->size;
+}
+
+loff_t exfat_seek(struct exfat_dev* dev, loff_t offset, int whence)
+{
+#ifdef USE_UBLIO
+	/* XXX SEEK_CUR will be handled incorrectly */
+	return dev->pos = lseek(dev->fd, offset, whence);
+#else
+	return lseek64(dev->fd, offset, whence);
+#endif
+}
+
+ssize_t exfat_read(struct exfat_dev* dev, void* buffer, size_t size)
+{
+#ifdef USE_UBLIO
+	ssize_t result = ublio_pread(dev->ufh, buffer, size, dev->pos);
+	if (result >= 0)
+		dev->pos += size;
+	return result;
+#else
+	return read(dev->fd, buffer, size);
+#endif
+}
+
+ssize_t exfat_write(struct exfat_dev* dev, const void* buffer, size_t size)
+{
+#ifdef USE_UBLIO
+	ssize_t result = ublio_pwrite(dev->ufh, buffer, size, dev->pos);
+	if (result >= 0)
+		dev->pos += size;
+	return result;
+#else
+	return write(dev->fd, buffer, size);
+#endif
+}
+
+ssize_t exfat_pread(struct exfat_dev* dev, void* buffer, size_t size,
+		loff_t offset)
+{
+#ifdef USE_UBLIO
+	return ublio_pread(dev->ufh, buffer, size, offset);
+#else
+	return pread64(dev->fd, buffer, size, offset);
+#endif
+}
+
+ssize_t exfat_pwrite(struct exfat_dev* dev, const void* buffer, size_t size,
+		loff_t offset)
+{
+#ifdef USE_UBLIO
+	return ublio_pwrite(dev->ufh, buffer, size, offset);
+#else
+	return pwrite64(dev->fd, buffer, size, offset);
+#endif
+}
+
+ssize_t exfat_generic_pread(const struct exfat* ef, struct exfat_node* node,
+		void* buffer, size_t size, loff_t offset)
+{
+	cluster_t cluster;
+	char* bufp = buffer;
+	loff_t lsize, loffset, remainder;
+
+	if (offset >= node->size)
+		return 0;
+	if (size == 0)
+		return 0;
+
+	cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
+	if (CLUSTER_INVALID(cluster))
+	{
+		exfat_error("invalid cluster 0x%x while reading", cluster);
+		return -1;
+	}
+
+	loffset = offset % CLUSTER_SIZE(*ef->sb);
+	remainder = MIN(size, node->size - offset);
+	while (remainder > 0)
+	{
+		if (CLUSTER_INVALID(cluster))
+		{
+			exfat_error("invalid cluster 0x%x while reading", cluster);
+			return -1;
+		}
+		lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
+		if (exfat_pread(ef->dev, bufp, lsize,
+					exfat_c2o(ef, cluster) + loffset) < 0)
+		{
+			exfat_error("failed to read cluster %#x", cluster);
+			return -1;
+		}
+		bufp += lsize;
+		loffset = 0;
+		remainder -= lsize;
+		cluster = exfat_next_cluster(ef, node, cluster);
+	}
+	if (!ef->ro && !ef->noatime)
+		exfat_update_atime(node);
+	return MIN(size, node->size - offset) - remainder;
+}
+
+ssize_t exfat_generic_pwrite(struct exfat* ef, struct exfat_node* node,
+		const void* buffer, size_t size, loff_t offset)
+{
+	cluster_t cluster;
+	const char* bufp = buffer;
+	loff_t lsize, loffset, remainder;
+
+ 	if (offset > node->size)
+ 		if (exfat_truncate(ef, node, offset, true) != 0)
+ 			return -1;
+  	if (offset + size > node->size)
+ 		if (exfat_truncate(ef, node, offset + size, false) != 0)
+ 			return -1;
+	if (size == 0)
+		return 0;
+
+	cluster = exfat_advance_cluster(ef, node, offset / CLUSTER_SIZE(*ef->sb));
+	if (CLUSTER_INVALID(cluster))
+	{
+		exfat_error("invalid cluster 0x%x while writing", cluster);
+		return -1;
+	}
+
+	loffset = offset % CLUSTER_SIZE(*ef->sb);
+	remainder = size;
+	while (remainder > 0)
+	{
+		if (CLUSTER_INVALID(cluster))
+		{
+			exfat_error("invalid cluster 0x%x while writing", cluster);
+			return -1;
+		}
+		lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
+		if (exfat_pwrite(ef->dev, bufp, lsize,
+				exfat_c2o(ef, cluster) + loffset) < 0)
+		{
+			exfat_error("failed to write cluster %#x", cluster);
+			return -1;
+		}
+		bufp += lsize;
+		loffset = 0;
+		remainder -= lsize;
+		cluster = exfat_next_cluster(ef, node, cluster);
+	}
+	exfat_update_mtime(node);
+	return size - remainder;
+}
diff --git a/exfat/libexfat/log.c b/exfat/libexfat/log.c
new file mode 100644
index 0000000..38c79d4
--- /dev/null
+++ b/exfat/libexfat/log.c
@@ -0,0 +1,114 @@
+/*
+	log.c (02.09.09)
+	exFAT file system implementation library.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "exfat.h"
+#include <stdarg.h>
+#include <syslog.h>
+#include <unistd.h>
+
+int exfat_errors;
+
+/*
+ * This message means an internal bug in exFAT implementation.
+ */
+void exfat_bug(const char* format, ...)
+{
+	va_list ap, aq;
+
+	va_start(ap, format);
+	va_copy(aq, ap);
+
+	fflush(stdout);
+	fputs("BUG: ", stderr);
+	vfprintf(stderr, format, ap);
+	va_end(ap);
+	fputs(".\n", stderr);
+
+	if (!isatty(STDERR_FILENO))
+		vsyslog(LOG_CRIT, format, aq);
+	va_end(aq);
+
+#if defined(__ANDROID__)
+    exit(-1);
+#else
+	abort();
+#endif
+}
+
+/*
+ * This message means an error in exFAT file system.
+ */
+void exfat_error(const char* format, ...)
+{
+	va_list ap, aq;
+
+	exfat_errors++;
+	va_start(ap, format);
+	va_copy(aq, ap);
+
+	fflush(stdout);
+	fputs("ERROR: ", stderr);
+	vfprintf(stderr, format, ap);
+	va_end(ap);
+	fputs(".\n", stderr);
+
+	if (!isatty(STDERR_FILENO))
+		vsyslog(LOG_ERR, format, aq);
+	va_end(aq);
+}
+
+/*
+ * This message means that there is something unexpected in exFAT file system
+ * that can be a potential problem.
+ */
+void exfat_warn(const char* format, ...)
+{
+	va_list ap, aq;
+
+	va_start(ap, format);
+	va_copy(aq, ap);
+
+	fflush(stdout);
+	fputs("WARN: ", stderr);
+	vfprintf(stderr, format, ap);
+	va_end(ap);
+	fputs(".\n", stderr);
+
+	if (!isatty(STDERR_FILENO))
+		vsyslog(LOG_WARNING, format, aq);
+	va_end(aq);
+}
+
+/*
+ * Just debug message. Disabled by default.
+ */
+void exfat_debug(const char* format, ...)
+{
+	va_list ap;
+
+	fflush(stdout);
+	fputs("DEBUG: ", stderr);
+	va_start(ap, format);
+	vfprintf(stderr, format, ap);
+	va_end(ap);
+	fputs(".\n", stderr);
+}
diff --git a/exfat/libexfat/lookup.c b/exfat/libexfat/lookup.c
new file mode 100644
index 0000000..3caef74
--- /dev/null
+++ b/exfat/libexfat/lookup.c
@@ -0,0 +1,225 @@
+/*
+	lookup.c (02.09.09)
+	exFAT file system implementation library.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "exfat.h"
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+
+int exfat_opendir(struct exfat* ef, struct exfat_node* dir,
+		struct exfat_iterator* it)
+{
+	int rc;
+
+	exfat_get_node(dir);
+	it->parent = dir;
+	it->current = NULL;
+	rc = exfat_cache_directory(ef, dir);
+	if (rc != 0)
+		exfat_put_node(ef, dir);
+	return rc;
+}
+
+void exfat_closedir(struct exfat* ef, struct exfat_iterator* it)
+{
+	exfat_put_node(ef, it->parent);
+	it->parent = NULL;
+	it->current = NULL;
+}
+
+struct exfat_node* exfat_readdir(struct exfat* ef __unused, struct exfat_iterator* it)
+{
+	if (it->current == NULL)
+		it->current = it->parent->child;
+	else
+		it->current = it->current->next;
+
+	if (it->current != NULL)
+		return exfat_get_node(it->current);
+	else
+		return NULL;
+}
+
+static int compare_char(struct exfat* ef, uint16_t a, uint16_t b)
+{
+	if (a >= ef->upcase_chars || b >= ef->upcase_chars)
+		return (int) a - (int) b;
+
+	return (int) le16_to_cpu(ef->upcase[a]) - (int) le16_to_cpu(ef->upcase[b]);
+}
+
+static int compare_name(struct exfat* ef, const le16_t* a, const le16_t* b)
+{
+	while (le16_to_cpu(*a) && le16_to_cpu(*b))
+	{
+		int rc = compare_char(ef, le16_to_cpu(*a), le16_to_cpu(*b));
+		if (rc != 0)
+			return rc;
+		a++;
+		b++;
+	}
+	return compare_char(ef, le16_to_cpu(*a), le16_to_cpu(*b));
+}
+
+static int lookup_name(struct exfat* ef, struct exfat_node* parent,
+		struct exfat_node** node, const char* name, size_t n)
+{
+	struct exfat_iterator it;
+	le16_t buffer[EXFAT_NAME_MAX + 1];
+	int rc;
+
+	*node = NULL;
+
+	rc = utf8_to_utf16(buffer, name, EXFAT_NAME_MAX, n);
+	if (rc != 0)
+		return rc;
+
+	rc = exfat_opendir(ef, parent, &it);
+	if (rc != 0)
+		return rc;
+	while ((*node = exfat_readdir(ef, &it)))
+	{
+		if (compare_name(ef, buffer, (*node)->name) == 0)
+		{
+			exfat_closedir(ef, &it);
+			return 0;
+		}
+		exfat_put_node(ef, *node);
+	}
+	exfat_closedir(ef, &it);
+	return -ENOENT;
+}
+
+static size_t get_comp(const char* path, const char** comp)
+{
+	const char* end;
+
+	*comp = path + strspn(path, "/");				/* skip leading slashes */
+	end = strchr(*comp, '/');
+	if (end == NULL)
+		return strlen(*comp);
+	else
+		return end - *comp;
+}
+
+int exfat_lookup(struct exfat* ef, struct exfat_node** node,
+		const char* path)
+{
+	struct exfat_node* parent;
+	const char* p;
+	size_t n;
+	int rc;
+
+	/* start from the root directory */
+	parent = *node = exfat_get_node(ef->root);
+	for (p = path; (n = get_comp(p, &p)); p += n)
+	{
+		if (n == 1 && *p == '.')				/* skip "." component */
+			continue;
+		rc = lookup_name(ef, parent, node, p, n);
+		if (rc != 0)
+		{
+			exfat_put_node(ef, parent);
+			return rc;
+		}
+		exfat_put_node(ef, parent);
+		parent = *node;
+	}
+	return 0;
+}
+
+static bool is_last_comp(const char* comp, size_t length)
+{
+	const char* p = comp + length;
+
+	return get_comp(p, &p) == 0;
+}
+
+static bool is_allowed(const char* comp, size_t length)
+{
+	size_t i;
+
+	for (i = 0; i < length; i++)
+		switch (comp[i])
+		{
+		case 0x01 ... 0x1f:
+		case '/':
+		case '\\':
+		case ':':
+		case '*':
+		case '?':
+		case '"':
+		case '<':
+		case '>':
+		case '|':
+			return false;
+		}
+	return true;
+}
+
+int exfat_split(struct exfat* ef, struct exfat_node** parent,
+		struct exfat_node** node, le16_t* name, const char* path)
+{
+	const char* p;
+	size_t n;
+	int rc;
+
+	memset(name, 0, (EXFAT_NAME_MAX + 1) * sizeof(le16_t));
+	*parent = *node = exfat_get_node(ef->root);
+	for (p = path; (n = get_comp(p, &p)); p += n)
+	{
+		if (n == 1 && *p == '.')
+			continue;
+		if (is_last_comp(p, n))
+		{
+			if (!is_allowed(p, n))
+			{
+				/* contains characters that are not allowed */
+				exfat_put_node(ef, *parent);
+				return -ENOENT;
+			}
+			rc = utf8_to_utf16(name, p, EXFAT_NAME_MAX, n);
+			if (rc != 0)
+			{
+				exfat_put_node(ef, *parent);
+				return rc;
+			}
+
+			rc = lookup_name(ef, *parent, node, p, n);
+			if (rc != 0 && rc != -ENOENT)
+			{
+				exfat_put_node(ef, *parent);
+				return rc;
+			}
+			return 0;
+		}
+		rc = lookup_name(ef, *parent, node, p, n);
+		if (rc != 0)
+		{
+			exfat_put_node(ef, *parent);
+			return rc;
+		}
+		exfat_put_node(ef, *parent);
+		*parent = *node;
+	}
+	exfat_bug("impossible");
+}
diff --git a/exfat/libexfat/mount.c b/exfat/libexfat/mount.c
new file mode 100644
index 0000000..45a5d49
--- /dev/null
+++ b/exfat/libexfat/mount.c
@@ -0,0 +1,380 @@
+/*
+	mount.c (22.10.09)
+	exFAT file system implementation library.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "exfat.h"
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+static uint64_t rootdir_size(const struct exfat* ef)
+{
+	uint32_t clusters = 0;
+	uint32_t clusters_max = le32_to_cpu(ef->sb->cluster_count);
+	cluster_t rootdir_cluster = le32_to_cpu(ef->sb->rootdir_cluster);
+
+	/* Iterate all clusters of the root directory to calculate its size.
+	   It can't be contiguous because there is no flag to indicate this. */
+	do
+	{
+		if (clusters == clusters_max) /* infinite loop detected */
+		{
+			exfat_error("root directory cannot occupy all %d clusters",
+					clusters);
+			return 0;
+		}
+		if (CLUSTER_INVALID(rootdir_cluster))
+		{
+			exfat_error("bad cluster %#x while reading root directory",
+					rootdir_cluster);
+			return 0;
+		}
+		rootdir_cluster = exfat_next_cluster(ef, ef->root, rootdir_cluster);
+		clusters++;
+	}
+	while (rootdir_cluster != EXFAT_CLUSTER_END);
+
+	return (uint64_t) clusters * CLUSTER_SIZE(*ef->sb);
+}
+
+static const char* get_option(const char* options, const char* option_name)
+{
+	const char* p;
+	size_t length = strlen(option_name);
+
+	for (p = strstr(options, option_name); p; p = strstr(p + 1, option_name))
+		if ((p == options || p[-1] == ',') && p[length] == '=')
+			return p + length + 1;
+	return NULL;
+}
+
+static int get_int_option(const char* options, const char* option_name,
+		int base, int default_value)
+{
+	const char* p = get_option(options, option_name);
+
+	if (p == NULL)
+		return default_value;
+	return strtol(p, NULL, base);
+}
+
+static bool match_option(const char* options, const char* option_name)
+{
+	const char* p;
+	size_t length = strlen(option_name);
+
+	for (p = strstr(options, option_name); p; p = strstr(p + 1, option_name))
+		if ((p == options || p[-1] == ',') &&
+				(p[length] == ',' || p[length] == '\0'))
+			return true;
+	return false;
+}
+
+static void parse_options(struct exfat* ef, const char* options)
+{
+	int opt_umask;
+
+	opt_umask = get_int_option(options, "umask", 8, 0);
+	ef->dmask = get_int_option(options, "dmask", 8, opt_umask);
+	ef->fmask = get_int_option(options, "fmask", 8, opt_umask);
+
+	ef->uid = get_int_option(options, "uid", 10, geteuid());
+	ef->gid = get_int_option(options, "gid", 10, getegid());
+
+	ef->noatime = match_option(options, "noatime");
+}
+
+static bool verify_vbr_checksum(struct exfat_dev* dev, void* sector,
+		loff_t sector_size)
+{
+	uint32_t vbr_checksum;
+	int i;
+
+	if (exfat_pread(dev, sector, sector_size, 0) < 0)
+	{
+		exfat_error("failed to read boot sector");
+		return false;
+	}
+	vbr_checksum = exfat_vbr_start_checksum(sector, sector_size);
+	for (i = 1; i < 11; i++)
+	{
+		if (exfat_pread(dev, sector, sector_size, i * sector_size) < 0)
+		{
+			exfat_error("failed to read VBR sector");
+			return false;
+		}
+		vbr_checksum = exfat_vbr_add_checksum(sector, sector_size,
+				vbr_checksum);
+	}
+	if (exfat_pread(dev, sector, sector_size, i * sector_size) < 0)
+	{
+		exfat_error("failed to read VBR checksum sector");
+		return false;
+	}
+	for (i = 0; i < sector_size / sizeof(vbr_checksum); i++)
+		if (le32_to_cpu(((const le32_t*) sector)[i]) != vbr_checksum)
+		{
+			exfat_error("invalid VBR checksum 0x%x (expected 0x%x)",
+					le32_to_cpu(((const le32_t*) sector)[i]), vbr_checksum);
+			return false;
+		}
+	return true;
+}
+
+static int commit_super_block(const struct exfat* ef)
+{
+	if (exfat_pwrite(ef->dev, ef->sb, sizeof(struct exfat_super_block), 0) < 0)
+	{
+		exfat_error("failed to write super block");
+		return 1;
+	}
+	return exfat_fsync(ef->dev);
+}
+
+static int prepare_super_block(const struct exfat* ef)
+{
+	if (le16_to_cpu(ef->sb->volume_state) & EXFAT_STATE_MOUNTED)
+		exfat_warn("volume was not unmounted cleanly");
+
+	if (ef->ro)
+		return 0;
+
+	ef->sb->volume_state = cpu_to_le16(
+			le16_to_cpu(ef->sb->volume_state) | EXFAT_STATE_MOUNTED);
+	return commit_super_block(ef);
+}
+
+int exfat_mount(struct exfat* ef, const char* spec, const char* options)
+{
+	int rc;
+	enum exfat_mode mode;
+
+	exfat_tzset();
+	memset(ef, 0, sizeof(struct exfat));
+
+	parse_options(ef, options);
+
+	if (match_option(options, "ro"))
+		mode = EXFAT_MODE_RO;
+	else if (match_option(options, "ro_fallback"))
+		mode = EXFAT_MODE_ANY;
+	else
+		mode = EXFAT_MODE_RW;
+	ef->dev = exfat_open(spec, mode);
+	if (ef->dev == NULL)
+		return -EIO;
+	if (exfat_get_mode(ef->dev) == EXFAT_MODE_RO)
+	{
+		if (mode == EXFAT_MODE_ANY)
+			ef->ro = -1;
+		else
+			ef->ro = 1;
+	}
+
+	ef->sb = malloc(sizeof(struct exfat_super_block));
+	if (ef->sb == NULL)
+	{
+		exfat_close(ef->dev);
+		exfat_error("failed to allocate memory for the super block");
+		return -ENOMEM;
+	}
+	memset(ef->sb, 0, sizeof(struct exfat_super_block));
+
+	if (exfat_pread(ef->dev, ef->sb, sizeof(struct exfat_super_block), 0) < 0)
+	{
+		exfat_close(ef->dev);
+		free(ef->sb);
+		exfat_error("failed to read boot sector");
+		return -EIO;
+	}
+	if (memcmp(ef->sb->oem_name, "EXFAT   ", 8) != 0)
+	{
+		exfat_close(ef->dev);
+		free(ef->sb);
+		exfat_error("exFAT file system is not found");
+		return -EIO;
+	}
+	/* sector cannot be smaller than 512 bytes */
+	if (ef->sb->sector_bits < 9)
+	{
+		exfat_close(ef->dev);
+		exfat_error("too small sector size: 2^%hhd", ef->sb->sector_bits);
+		free(ef->sb);
+		return -EIO;
+	}
+	/* officially exFAT supports cluster size up to 32 MB */
+	if ((int) ef->sb->sector_bits + (int) ef->sb->spc_bits > 25)
+	{
+		exfat_close(ef->dev);
+		exfat_error("too big cluster size: 2^(%hhd+%hhd)",
+				ef->sb->sector_bits, ef->sb->spc_bits);
+		free(ef->sb);
+		return -EIO;
+	}
+	ef->zero_cluster = malloc(CLUSTER_SIZE(*ef->sb));
+	if (ef->zero_cluster == NULL)
+	{
+		exfat_close(ef->dev);
+		free(ef->sb);
+		exfat_error("failed to allocate zero sector");
+		return -ENOMEM;
+	}
+	/* use zero_cluster as a temporary buffer for VBR checksum verification */
+	if (!verify_vbr_checksum(ef->dev, ef->zero_cluster, SECTOR_SIZE(*ef->sb)))
+	{
+		free(ef->zero_cluster);
+		exfat_close(ef->dev);
+		free(ef->sb);
+		return -EIO;
+	}
+	memset(ef->zero_cluster, 0, CLUSTER_SIZE(*ef->sb));
+	if (ef->sb->version.major != 1 || ef->sb->version.minor != 0)
+	{
+		free(ef->zero_cluster);
+		exfat_close(ef->dev);
+		exfat_error("unsupported exFAT version: %hhu.%hhu",
+				ef->sb->version.major, ef->sb->version.minor);
+		free(ef->sb);
+		return -EIO;
+	}
+	if (ef->sb->fat_count != 1)
+	{
+		free(ef->zero_cluster);
+		exfat_close(ef->dev);
+		exfat_error("unsupported FAT count: %hhu", ef->sb->fat_count);
+		free(ef->sb);
+		return -EIO;
+	}
+	if (le64_to_cpu(ef->sb->sector_count) * SECTOR_SIZE(*ef->sb) >
+			exfat_get_size(ef->dev))
+	{
+		/* this can cause I/O errors later but we don't fail mounting to let
+		   user rescue data */
+		exfat_warn("file system is larger than underlying device: "
+				"%"PRIu64" > %"PRIu64,
+				le64_to_cpu(ef->sb->sector_count) * SECTOR_SIZE(*ef->sb),
+				exfat_get_size(ef->dev));
+	}
+
+	ef->root = malloc(sizeof(struct exfat_node));
+	if (ef->root == NULL)
+	{
+		free(ef->zero_cluster);
+		exfat_close(ef->dev);
+		free(ef->sb);
+		exfat_error("failed to allocate root node");
+		return -ENOMEM;
+	}
+	memset(ef->root, 0, sizeof(struct exfat_node));
+	ef->root->flags = EXFAT_ATTRIB_DIR;
+	ef->root->start_cluster = le32_to_cpu(ef->sb->rootdir_cluster);
+	ef->root->fptr_cluster = ef->root->start_cluster;
+	ef->root->name[0] = cpu_to_le16('\0');
+	ef->root->size = rootdir_size(ef);
+	if (ef->root->size == 0)
+	{
+		free(ef->root);
+		free(ef->zero_cluster);
+		exfat_close(ef->dev);
+		free(ef->sb);
+		return -EIO;
+	}
+	/* exFAT does not have time attributes for the root directory */
+	ef->root->mtime = 0;
+	ef->root->atime = 0;
+	/* always keep at least 1 reference to the root node */
+	exfat_get_node(ef->root);
+
+	rc = exfat_cache_directory(ef, ef->root);
+	if (rc != 0)
+		goto error;
+	if (ef->upcase == NULL)
+	{
+		exfat_error("upcase table is not found");
+		goto error;
+	}
+	if (ef->cmap.chunk == NULL)
+	{
+		exfat_error("clusters bitmap is not found");
+		goto error;
+	}
+
+	if (prepare_super_block(ef) != 0)
+		goto error;
+
+	return 0;
+
+error:
+	exfat_put_node(ef, ef->root);
+	exfat_reset_cache(ef);
+	free(ef->root);
+	free(ef->zero_cluster);
+	exfat_close(ef->dev);
+	free(ef->sb);
+	return -EIO;
+}
+
+static void finalize_super_block(struct exfat* ef)
+{
+	if (ef->ro)
+		return;
+
+	ef->sb->volume_state = cpu_to_le16(
+			le16_to_cpu(ef->sb->volume_state) & ~EXFAT_STATE_MOUNTED);
+
+	/* Some implementations set the percentage of allocated space to 0xff
+	   on FS creation and never update it. In this case leave it as is. */
+	if (ef->sb->allocated_percent != 0xff)
+	{
+		uint32_t free, total;
+
+		free = exfat_count_free_clusters(ef);
+		total = le32_to_cpu(ef->sb->cluster_count);
+		ef->sb->allocated_percent = ((total - free) * 100 + total / 2) / total;
+	}
+
+	commit_super_block(ef);	/* ignore return code */
+}
+
+void exfat_unmount(struct exfat* ef)
+{
+	exfat_flush_nodes(ef);	/* ignore return code */
+	exfat_flush(ef);		/* ignore return code */
+	exfat_put_node(ef, ef->root);
+	exfat_reset_cache(ef);
+	free(ef->root);
+	ef->root = NULL;
+	finalize_super_block(ef);
+	exfat_close(ef->dev);	/* close descriptor immediately after fsync */
+	ef->dev = NULL;
+	free(ef->zero_cluster);
+	ef->zero_cluster = NULL;
+	free(ef->cmap.chunk);
+	ef->cmap.chunk = NULL;
+	free(ef->sb);
+	ef->sb = NULL;
+	free(ef->upcase);
+	ef->upcase = NULL;
+	ef->upcase_chars = 0;
+}
diff --git a/exfat/libexfat/node.c b/exfat/libexfat/node.c
new file mode 100644
index 0000000..e4fd32f
--- /dev/null
+++ b/exfat/libexfat/node.c
@@ -0,0 +1,1246 @@
+/*
+	node.c (09.10.09)
+	exFAT file system implementation library.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "exfat.h"
+#include <errno.h>
+#include <string.h>
+#include <inttypes.h>
+
+/* on-disk nodes iterator */
+struct iterator
+{
+	cluster_t cluster;
+	loff_t offset;
+	int contiguous;
+	char* chunk;
+};
+
+struct exfat_node* exfat_get_node(struct exfat_node* node)
+{
+	/* if we switch to multi-threaded mode we will need atomic
+	   increment here and atomic decrement in exfat_put_node() */
+	node->references++;
+	return node;
+}
+
+void exfat_put_node(struct exfat* ef, struct exfat_node* node)
+{
+	char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
+	--node->references;
+	if (node->references < 0)
+	{
+		exfat_get_name(node, buffer, sizeof(buffer) - 1);
+		exfat_bug("reference counter of '%s' is below zero", buffer);
+	}
+	else if (node->references == 0 && node != ef->root)
+	{
+		if (node->flags & EXFAT_ATTRIB_DIRTY)
+		{
+			exfat_get_name(node, buffer, sizeof(buffer) - 1);
+			exfat_warn("dirty node '%s' with zero references", buffer);
+		}
+	}
+}
+
+/**
+ * This function must be called on rmdir and unlink (after the last
+ * exfat_put_node()) to free clusters.
+ */
+int exfat_cleanup_node(struct exfat* ef, struct exfat_node* node)
+{
+	int rc = 0;
+
+	if (node->references != 0)
+		exfat_bug("unable to cleanup a node with %d references",
+				node->references);
+
+	if (node->flags & EXFAT_ATTRIB_UNLINKED)
+	{
+		/* free all clusters and node structure itself */
+		rc = exfat_truncate(ef, node, 0, true);
+		/* free the node even in case of error or its memory will be lost */
+		free(node);
+	}
+	return rc;
+}
+
+/**
+ * Cluster + offset from the beginning of the directory to absolute offset.
+ */
+static loff_t co2o(struct exfat* ef, cluster_t cluster, loff_t offset)
+{
+	return exfat_c2o(ef, cluster) + offset % CLUSTER_SIZE(*ef->sb);
+}
+
+static int opendir(struct exfat* ef, const struct exfat_node* dir,
+		struct iterator* it)
+{
+	if (!(dir->flags & EXFAT_ATTRIB_DIR))
+		exfat_bug("not a directory");
+	it->cluster = dir->start_cluster;
+	it->offset = 0;
+	it->contiguous = IS_CONTIGUOUS(*dir);
+	it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
+	if (it->chunk == NULL)
+	{
+		exfat_error("out of memory");
+		return -ENOMEM;
+	}
+	if (exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
+			exfat_c2o(ef, it->cluster)) < 0)
+	{
+		exfat_error("failed to read directory cluster %#x", it->cluster);
+		return -EIO;
+	}
+	return 0;
+}
+
+static void closedir(struct iterator* it)
+{
+	it->cluster = 0;
+	it->offset = 0;
+	it->contiguous = 0;
+	free(it->chunk);
+	it->chunk = NULL;
+}
+
+static bool fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
+		struct iterator* it)
+{
+	/* move iterator to the next entry in the directory */
+	it->offset += sizeof(struct exfat_entry);
+	/* fetch the next cluster if needed */
+	if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0)
+	{
+		/* reached the end of directory; the caller should check this
+		   condition too */
+		if (it->offset >= parent->size)
+			return true;
+		it->cluster = exfat_next_cluster(ef, parent, it->cluster);
+		if (CLUSTER_INVALID(it->cluster))
+		{
+			exfat_error("invalid cluster 0x%x while reading directory",
+					it->cluster);
+			return false;
+		}
+		if (exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
+				exfat_c2o(ef, it->cluster)) < 0)
+		{
+			exfat_error("failed to read the next directory cluster %#x",
+					it->cluster);
+			return false;
+		}
+	}
+	return true;
+}
+
+static struct exfat_node* allocate_node(void)
+{
+	struct exfat_node* node = malloc(sizeof(struct exfat_node));
+	if (node == NULL)
+	{
+		exfat_error("failed to allocate node");
+		return NULL;
+	}
+	memset(node, 0, sizeof(struct exfat_node));
+	return node;
+}
+
+static void init_node_meta1(struct exfat_node* node,
+		const struct exfat_entry_meta1* meta1)
+{
+	node->flags = le16_to_cpu(meta1->attrib);
+	node->mtime = exfat_exfat2unix(meta1->mdate, meta1->mtime,
+			meta1->mtime_cs);
+	/* there is no centiseconds field for atime */
+	node->atime = exfat_exfat2unix(meta1->adate, meta1->atime, 0);
+}
+
+static void init_node_meta2(struct exfat_node* node,
+		const struct exfat_entry_meta2* meta2)
+{
+	node->size = le64_to_cpu(meta2->size);
+	node->start_cluster = le32_to_cpu(meta2->start_cluster);
+	node->fptr_cluster = node->start_cluster;
+	if (meta2->flags & EXFAT_FLAG_CONTIGUOUS)
+		node->flags |= EXFAT_ATTRIB_CONTIGUOUS;
+}
+
+static const struct exfat_entry* get_entry_ptr(const struct exfat* ef,
+		const struct iterator* it)
+{
+	return (const struct exfat_entry*)
+			(it->chunk + it->offset % CLUSTER_SIZE(*ef->sb));
+}
+
+static bool check_node(const struct exfat_node* node, uint16_t actual_checksum,
+		uint16_t reference_checksum, uint64_t valid_size)
+{
+	char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
+	/*
+	   Validate checksum first. If it's invalid all other fields probably
+	   contain just garbage.
+	*/
+	if (actual_checksum != reference_checksum)
+	{
+		exfat_get_name(node, buffer, sizeof(buffer) - 1);
+		exfat_error("'%s' has invalid checksum (%#hx != %#hx)", buffer,
+				actual_checksum, reference_checksum);
+		return false;
+	}
+
+	/*
+	   exFAT does not support sparse files but allows files with uninitialized
+	   clusters. For such files valid_size means initialized data size and
+	   cannot be greater than file size. See SetFileValidData() function
+	   description in MSDN.
+	*/
+	if (valid_size > node->size)
+	{
+		exfat_get_name(node, buffer, sizeof(buffer) - 1);
+		exfat_error("'%s' has valid size (%"PRIu64") greater than size "
+				"(%"PRIu64")", buffer, valid_size, node->size);
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Reads one entry in directory at position pointed by iterator and fills
+ * node structure.
+ */
+static int readdir(struct exfat* ef, const struct exfat_node* parent,
+		struct exfat_node** node, struct iterator* it)
+{
+	int rc = -EIO;
+	const struct exfat_entry* entry;
+	const struct exfat_entry_meta1* meta1;
+	const struct exfat_entry_meta2* meta2;
+	const struct exfat_entry_name* file_name;
+	const struct exfat_entry_upcase* upcase;
+	const struct exfat_entry_bitmap* bitmap;
+	const struct exfat_entry_label* label;
+	uint8_t continuations = 0;
+	le16_t* namep = NULL;
+	uint16_t reference_checksum = 0;
+	uint16_t actual_checksum = 0;
+	uint64_t valid_size = 0;
+
+	*node = NULL;
+
+	for (;;)
+	{
+		if (it->offset >= parent->size)
+		{
+			if (continuations != 0)
+			{
+				exfat_error("expected %hhu continuations", continuations);
+				goto error;
+			}
+			return -ENOENT; /* that's OK, means end of directory */
+		}
+
+		entry = get_entry_ptr(ef, it);
+		switch (entry->type)
+		{
+		case EXFAT_ENTRY_FILE:
+			if (continuations != 0)
+			{
+				exfat_error("expected %hhu continuations before new entry",
+						continuations);
+				goto error;
+			}
+			meta1 = (const struct exfat_entry_meta1*) entry;
+			continuations = meta1->continuations;
+			/* each file entry must have at least 2 continuations:
+			   info and name */
+			if (continuations < 2)
+			{
+				exfat_error("too few continuations (%hhu)", continuations);
+				goto error;
+			}
+			if (continuations > 1 +
+					DIV_ROUND_UP(EXFAT_NAME_MAX, EXFAT_ENAME_MAX))
+			{
+				exfat_error("too many continuations (%hhu)", continuations);
+				goto error;
+			}
+			reference_checksum = le16_to_cpu(meta1->checksum);
+			actual_checksum = exfat_start_checksum(meta1);
+			*node = allocate_node();
+			if (*node == NULL)
+			{
+				rc = -ENOMEM;
+				goto error;
+			}
+			/* new node has zero reference counter */
+			(*node)->entry_cluster = it->cluster;
+			(*node)->entry_offset = it->offset;
+			init_node_meta1(*node, meta1);
+			namep = (*node)->name;
+			break;
+
+		case EXFAT_ENTRY_FILE_INFO:
+			if (continuations < 2)
+			{
+				exfat_error("unexpected continuation (%hhu)",
+						continuations);
+				goto error;
+			}
+			meta2 = (const struct exfat_entry_meta2*) entry;
+			if (meta2->flags & ~(EXFAT_FLAG_ALWAYS1 | EXFAT_FLAG_CONTIGUOUS))
+			{
+				exfat_error("unknown flags in meta2 (0x%hhx)", meta2->flags);
+				goto error;
+			}
+			init_node_meta2(*node, meta2);
+			actual_checksum = exfat_add_checksum(entry, actual_checksum);
+			valid_size = le64_to_cpu(meta2->valid_size);
+			/* empty files must be marked as non-contiguous */
+			if ((*node)->size == 0 && (meta2->flags & EXFAT_FLAG_CONTIGUOUS))
+			{
+				exfat_error("empty file marked as contiguous (0x%hhx)",
+						meta2->flags);
+				goto error;
+			}
+			/* directories must be aligned on at cluster boundary */
+			if (((*node)->flags & EXFAT_ATTRIB_DIR) &&
+				(*node)->size % CLUSTER_SIZE(*ef->sb) != 0)
+			{
+				exfat_error("directory has invalid size %"PRIu64" bytes",
+						(*node)->size);
+				goto error;
+			}
+			--continuations;
+			break;
+
+		case EXFAT_ENTRY_FILE_NAME:
+			if (continuations == 0)
+			{
+				exfat_error("unexpected continuation");
+				goto error;
+			}
+			file_name = (const struct exfat_entry_name*) entry;
+			actual_checksum = exfat_add_checksum(entry, actual_checksum);
+
+			memcpy(namep, file_name->name,
+					MIN(EXFAT_ENAME_MAX,
+						((*node)->name + EXFAT_NAME_MAX - namep)) *
+					sizeof(le16_t));
+			namep += EXFAT_ENAME_MAX;
+			if (--continuations == 0)
+			{
+				if (!check_node(*node, actual_checksum, reference_checksum,
+						valid_size))
+					goto error;
+				if (!fetch_next_entry(ef, parent, it))
+					goto error;
+				return 0; /* entry completed */
+			}
+			break;
+
+		case EXFAT_ENTRY_UPCASE:
+			if (ef->upcase != NULL)
+				break;
+			upcase = (const struct exfat_entry_upcase*) entry;
+			if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
+			{
+				exfat_error("invalid cluster 0x%x in upcase table",
+						le32_to_cpu(upcase->start_cluster));
+				goto error;
+			}
+			if (le64_to_cpu(upcase->size) == 0 ||
+				le64_to_cpu(upcase->size) > 0xffff * sizeof(uint16_t) ||
+				le64_to_cpu(upcase->size) % sizeof(uint16_t) != 0)
+			{
+				exfat_error("bad upcase table size (%"PRIu64" bytes)",
+						le64_to_cpu(upcase->size));
+				goto error;
+			}
+			ef->upcase = malloc(le64_to_cpu(upcase->size));
+			if (ef->upcase == NULL)
+			{
+				exfat_error("failed to allocate upcase table (%"PRIu64" bytes)",
+						le64_to_cpu(upcase->size));
+				rc = -ENOMEM;
+				goto error;
+			}
+			ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t);
+
+			if (exfat_pread(ef->dev, ef->upcase, le64_to_cpu(upcase->size),
+					exfat_c2o(ef, le32_to_cpu(upcase->start_cluster))) < 0)
+			{
+				exfat_error("failed to read upper case table "
+						"(%"PRIu64" bytes starting at cluster %#x)",
+						le64_to_cpu(upcase->size),
+						le32_to_cpu(upcase->start_cluster));
+				goto error;
+			}
+			break;
+
+		case EXFAT_ENTRY_BITMAP:
+			bitmap = (const struct exfat_entry_bitmap*) entry;
+			ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
+			if (CLUSTER_INVALID(ef->cmap.start_cluster))
+			{
+				exfat_error("invalid cluster 0x%x in clusters bitmap",
+						ef->cmap.start_cluster);
+				goto error;
+			}
+			ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
+				EXFAT_FIRST_DATA_CLUSTER;
+			if (le64_to_cpu(bitmap->size) < DIV_ROUND_UP(ef->cmap.size, 8))
+			{
+				exfat_error("invalid clusters bitmap size: %"PRIu64
+						" (expected at least %u)",
+						le64_to_cpu(bitmap->size),
+						DIV_ROUND_UP(ef->cmap.size, 8));
+				goto error;
+			}
+			/* FIXME bitmap can be rather big, up to 512 MB */
+			ef->cmap.chunk_size = ef->cmap.size;
+			ef->cmap.chunk = malloc(BMAP_SIZE(ef->cmap.chunk_size));
+			if (ef->cmap.chunk == NULL)
+			{
+				exfat_error("failed to allocate clusters bitmap chunk "
+						"(%"PRIu64" bytes)", le64_to_cpu(bitmap->size));
+				rc = -ENOMEM;
+				goto error;
+			}
+
+			if (exfat_pread(ef->dev, ef->cmap.chunk,
+					BMAP_SIZE(ef->cmap.chunk_size),
+					exfat_c2o(ef, ef->cmap.start_cluster)) < 0)
+			{
+				exfat_error("failed to read clusters bitmap "
+						"(%"PRIu64" bytes starting at cluster %#x)",
+						le64_to_cpu(bitmap->size), ef->cmap.start_cluster);
+				goto error;
+			}
+			break;
+
+		case EXFAT_ENTRY_LABEL:
+			label = (const struct exfat_entry_label*) entry;
+			if (label->length > EXFAT_ENAME_MAX)
+			{
+				exfat_error("too long label (%hhu chars)", label->length);
+				goto error;
+			}
+			if (utf16_to_utf8(ef->label, label->name,
+						sizeof(ef->label) - 1, EXFAT_ENAME_MAX) != 0)
+				goto error;
+			break;
+
+		default:
+			if (!(entry->type & EXFAT_ENTRY_VALID))
+				break; /* deleted entry, ignore it */
+			if (!(entry->type & EXFAT_ENTRY_OPTIONAL))
+			{
+				exfat_error("unknown entry type %#hhx", entry->type);
+				goto error;
+			}
+			/* optional entry, warn and skip */
+			exfat_warn("unknown entry type %#hhx", entry->type);
+			if (continuations == 0)
+			{
+				exfat_error("unexpected continuation");
+				goto error;
+			}
+			--continuations;
+			break;
+		}
+
+		if (!fetch_next_entry(ef, parent, it))
+			goto error;
+	}
+	/* we never reach here */
+
+error:
+	free(*node);
+	*node = NULL;
+	return rc;
+}
+
+int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
+{
+	struct iterator it;
+	int rc;
+	struct exfat_node* node;
+	struct exfat_node* current = NULL;
+
+	if (dir->flags & EXFAT_ATTRIB_CACHED)
+		return 0; /* already cached */
+
+	rc = opendir(ef, dir, &it);
+	if (rc != 0)
+		return rc;
+	while ((rc = readdir(ef, dir, &node, &it)) == 0)
+	{
+		node->parent = dir;
+		if (current != NULL)
+		{
+			current->next = node;
+			node->prev = current;
+		}
+		else
+			dir->child = node;
+
+		current = node;
+	}
+	closedir(&it);
+
+	if (rc != -ENOENT)
+	{
+		/* rollback */
+		for (current = dir->child; current; current = node)
+		{
+			node = current->next;
+			free(current);
+		}
+		dir->child = NULL;
+		return rc;
+	}
+
+	dir->flags |= EXFAT_ATTRIB_CACHED;
+	return 0;
+}
+
+static void tree_attach(struct exfat_node* dir, struct exfat_node* node)
+{
+	node->parent = dir;
+	if (dir->child)
+	{
+		dir->child->prev = node;
+		node->next = dir->child;
+	}
+	dir->child = node;
+}
+
+static void tree_detach(struct exfat_node* node)
+{
+	if (node->prev)
+		node->prev->next = node->next;
+	else /* this is the first node in the list */
+		node->parent->child = node->next;
+	if (node->next)
+		node->next->prev = node->prev;
+	node->parent = NULL;
+	node->prev = NULL;
+	node->next = NULL;
+}
+
+static void reset_cache(struct exfat* ef, struct exfat_node* node)
+{
+	char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
+	while (node->child)
+	{
+		struct exfat_node* p = node->child;
+		reset_cache(ef, p);
+		tree_detach(p);
+		free(p);
+	}
+	node->flags &= ~EXFAT_ATTRIB_CACHED;
+	if (node->references != 0)
+	{
+		exfat_get_name(node, buffer, sizeof(buffer) - 1);
+		exfat_warn("non-zero reference counter (%d) for '%s'",
+				node->references, buffer);
+	}
+	if (node != ef->root && (node->flags & EXFAT_ATTRIB_DIRTY))
+	{
+		exfat_get_name(node, buffer, sizeof(buffer) - 1);
+		exfat_bug("node '%s' is dirty", buffer);
+	}
+	while (node->references)
+		exfat_put_node(ef, node);
+}
+
+void exfat_reset_cache(struct exfat* ef)
+{
+	reset_cache(ef, ef->root);
+}
+
+static bool next_entry(struct exfat* ef, const struct exfat_node* parent,
+		cluster_t* cluster, loff_t* offset)
+{
+	*offset += sizeof(struct exfat_entry);
+	if (*offset % CLUSTER_SIZE(*ef->sb) == 0)
+	{
+		*cluster = exfat_next_cluster(ef, parent, *cluster);
+		if (CLUSTER_INVALID(*cluster))
+		{
+			exfat_error("invalid cluster %#x while getting next entry",
+					*cluster);
+			return false;
+		}
+	}
+	return true;
+}
+
+int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
+{
+	cluster_t cluster;
+	loff_t offset;
+	loff_t meta1_offset, meta2_offset;
+	struct exfat_entry_meta1 meta1;
+	struct exfat_entry_meta2 meta2;
+
+	if (!(node->flags & EXFAT_ATTRIB_DIRTY))
+		return 0; /* no need to flush */
+
+	if (ef->ro)
+		exfat_bug("unable to flush node to read-only FS");
+
+	if (node->parent == NULL)
+		return 0; /* do not flush unlinked node */
+
+	cluster = node->entry_cluster;
+	offset = node->entry_offset;
+	meta1_offset = co2o(ef, cluster, offset);
+	if (!next_entry(ef, node->parent, &cluster, &offset))
+		return -EIO;
+	meta2_offset = co2o(ef, cluster, offset);
+
+	if (exfat_pread(ef->dev, &meta1, sizeof(meta1), meta1_offset) < 0)
+	{
+		exfat_error("failed to read meta1 entry on flush");
+		return -EIO;
+	}
+	if (meta1.type != EXFAT_ENTRY_FILE)
+		exfat_bug("invalid type of meta1: 0x%hhx", meta1.type);
+	meta1.attrib = cpu_to_le16(node->flags);
+	exfat_unix2exfat(node->mtime, &meta1.mdate, &meta1.mtime, &meta1.mtime_cs);
+	exfat_unix2exfat(node->atime, &meta1.adate, &meta1.atime, NULL);
+
+	if (exfat_pread(ef->dev, &meta2, sizeof(meta2), meta2_offset) < 0)
+	{
+		exfat_error("failed to read meta2 entry on flush");
+		return -EIO;
+	}
+	if (meta2.type != EXFAT_ENTRY_FILE_INFO)
+		exfat_bug("invalid type of meta2: 0x%hhx", meta2.type);
+	meta2.size = meta2.valid_size = cpu_to_le64(node->size);
+	meta2.start_cluster = cpu_to_le32(node->start_cluster);
+	meta2.flags = EXFAT_FLAG_ALWAYS1;
+	/* empty files must not be marked as contiguous */
+	if (node->size != 0 && IS_CONTIGUOUS(*node))
+		meta2.flags |= EXFAT_FLAG_CONTIGUOUS;
+	/* name hash remains unchanged, no need to recalculate it */
+
+	meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
+
+	if (exfat_pwrite(ef->dev, &meta1, sizeof(meta1), meta1_offset) < 0)
+	{
+		exfat_error("failed to write meta1 entry on flush");
+		return -EIO;
+	}
+	if (exfat_pwrite(ef->dev, &meta2, sizeof(meta2), meta2_offset) < 0)
+	{
+		exfat_error("failed to write meta2 entry on flush");
+		return -EIO;
+	}
+
+	node->flags &= ~EXFAT_ATTRIB_DIRTY;
+	return exfat_flush(ef);
+}
+
+static bool erase_entry(struct exfat* ef, struct exfat_node* node)
+{
+	cluster_t cluster = node->entry_cluster;
+	loff_t offset = node->entry_offset;
+	int name_entries = DIV_ROUND_UP(utf16_length(node->name), EXFAT_ENAME_MAX);
+	uint8_t entry_type;
+
+	entry_type = EXFAT_ENTRY_FILE & ~EXFAT_ENTRY_VALID;
+	if (exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset)) < 0)
+	{
+		exfat_error("failed to erase meta1 entry");
+		return false;
+	}
+
+	if (!next_entry(ef, node->parent, &cluster, &offset))
+		return false;
+	entry_type = EXFAT_ENTRY_FILE_INFO & ~EXFAT_ENTRY_VALID;
+	if (exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset)) < 0)
+	{
+		exfat_error("failed to erase meta2 entry");
+		return false;
+	}
+
+	while (name_entries--)
+	{
+		if (!next_entry(ef, node->parent, &cluster, &offset))
+			return false;
+		entry_type = EXFAT_ENTRY_FILE_NAME & ~EXFAT_ENTRY_VALID;
+		if (exfat_pwrite(ef->dev, &entry_type, 1,
+				co2o(ef, cluster, offset)) < 0)
+		{
+			exfat_error("failed to erase name entry");
+			return false;
+		}
+	}
+	return true;
+}
+
+static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
+		loff_t deleted_offset)
+{
+	const struct exfat_node* node;
+	const struct exfat_node* last_node;
+	uint64_t entries = 0;
+	uint64_t new_size;
+
+	if (!(dir->flags & EXFAT_ATTRIB_DIR))
+		exfat_bug("attempted to shrink a file");
+	if (!(dir->flags & EXFAT_ATTRIB_CACHED))
+		exfat_bug("attempted to shrink uncached directory");
+
+	for (last_node = node = dir->child; node; node = node->next)
+	{
+		if (deleted_offset < node->entry_offset)
+		{
+			/* there are other entries after the removed one, no way to shrink
+			   this directory */
+			return 0;
+		}
+		if (last_node->entry_offset < node->entry_offset)
+			last_node = node;
+	}
+
+	if (last_node)
+	{
+		/* offset of the last entry */
+		entries += last_node->entry_offset / sizeof(struct exfat_entry);
+		/* two subentries with meta info */
+		entries += 2;
+		/* subentries with file name */
+		entries += DIV_ROUND_UP(utf16_length(last_node->name),
+				EXFAT_ENAME_MAX);
+	}
+
+	new_size = DIV_ROUND_UP(entries * sizeof(struct exfat_entry),
+				 CLUSTER_SIZE(*ef->sb)) * CLUSTER_SIZE(*ef->sb);
+	if (new_size == 0) /* directory always has at least 1 cluster */
+		new_size = CLUSTER_SIZE(*ef->sb);
+	if (new_size == dir->size)
+		return 0;
+	return exfat_truncate(ef, dir, new_size, true);
+}
+
+static int delete(struct exfat* ef, struct exfat_node* node)
+{
+	struct exfat_node* parent = node->parent;
+	loff_t deleted_offset = node->entry_offset;
+	int rc;
+
+	exfat_get_node(parent);
+	if (!erase_entry(ef, node))
+	{
+		exfat_put_node(ef, parent);
+		return -EIO;
+	}
+	exfat_update_mtime(parent);
+	tree_detach(node);
+	rc = shrink_directory(ef, parent, deleted_offset);
+	node->flags |= EXFAT_ATTRIB_UNLINKED;
+	if (rc != 0)
+	{
+		exfat_flush_node(ef, parent);
+		exfat_put_node(ef, parent);
+		return rc;
+	}
+	rc = exfat_flush_node(ef, parent);
+	exfat_put_node(ef, parent);
+	return rc;
+}
+
+int exfat_unlink(struct exfat* ef, struct exfat_node* node)
+{
+	if (node->flags & EXFAT_ATTRIB_DIR)
+		return -EISDIR;
+	return delete(ef, node);
+}
+
+int exfat_rmdir(struct exfat* ef, struct exfat_node* node)
+{
+	int rc;
+
+	if (!(node->flags & EXFAT_ATTRIB_DIR))
+		return -ENOTDIR;
+	/* check that directory is empty */
+	rc = exfat_cache_directory(ef, node);
+	if (rc != 0)
+		return rc;
+	if (node->child)
+		return -ENOTEMPTY;
+	return delete(ef, node);
+}
+
+static int grow_directory(struct exfat* ef, struct exfat_node* dir,
+		uint64_t asize, uint32_t difference)
+{
+	return exfat_truncate(ef, dir,
+			DIV_ROUND_UP(asize + difference, CLUSTER_SIZE(*ef->sb))
+				* CLUSTER_SIZE(*ef->sb), true);
+}
+
+static int find_slot(struct exfat* ef, struct exfat_node* dir,
+		cluster_t* cluster, loff_t* offset, int subentries)
+{
+	struct iterator it;
+	int rc;
+	const struct exfat_entry* entry;
+	int contiguous = 0;
+
+	rc = opendir(ef, dir, &it);
+	if (rc != 0)
+		return rc;
+	for (;;)
+	{
+		if (contiguous == 0)
+		{
+			*cluster = it.cluster;
+			*offset = it.offset;
+		}
+		entry = get_entry_ptr(ef, &it);
+		if (entry->type & EXFAT_ENTRY_VALID)
+			contiguous = 0;
+		else
+			contiguous++;
+		if (contiguous == subentries)
+			break;	/* suitable slot is found */
+		if (it.offset + sizeof(struct exfat_entry) >= dir->size)
+		{
+			rc = grow_directory(ef, dir, dir->size,
+					(subentries - contiguous) * sizeof(struct exfat_entry));
+			if (rc != 0)
+			{
+				closedir(&it);
+				return rc;
+			}
+		}
+		if (!fetch_next_entry(ef, dir, &it))
+		{
+			closedir(&it);
+			return -EIO;
+		}
+	}
+	closedir(&it);
+	return 0;
+}
+
+static int write_entry(struct exfat* ef, struct exfat_node* dir,
+		const le16_t* name, cluster_t cluster, loff_t offset, uint16_t attrib)
+{
+	struct exfat_node* node;
+	struct exfat_entry_meta1 meta1;
+	struct exfat_entry_meta2 meta2;
+	const size_t name_length = utf16_length(name);
+	const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
+	int i;
+
+	node = allocate_node();
+	if (node == NULL)
+		return -ENOMEM;
+	node->entry_cluster = cluster;
+	node->entry_offset = offset;
+	memcpy(node->name, name, name_length * sizeof(le16_t));
+
+	memset(&meta1, 0, sizeof(meta1));
+	meta1.type = EXFAT_ENTRY_FILE;
+	meta1.continuations = 1 + name_entries;
+	meta1.attrib = cpu_to_le16(attrib);
+	exfat_unix2exfat(time(NULL), &meta1.crdate, &meta1.crtime,
+			&meta1.crtime_cs);
+	meta1.adate = meta1.mdate = meta1.crdate;
+	meta1.atime = meta1.mtime = meta1.crtime;
+	meta1.mtime_cs = meta1.crtime_cs; /* there is no atime_cs */
+
+	memset(&meta2, 0, sizeof(meta2));
+	meta2.type = EXFAT_ENTRY_FILE_INFO;
+	meta2.flags = EXFAT_FLAG_ALWAYS1;
+	meta2.name_length = name_length;
+	meta2.name_hash = exfat_calc_name_hash(ef, node->name);
+	meta2.start_cluster = cpu_to_le32(EXFAT_CLUSTER_FREE);
+
+	meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
+
+	if (exfat_pwrite(ef->dev, &meta1, sizeof(meta1),
+			co2o(ef, cluster, offset)) < 0)
+	{
+		exfat_error("failed to write meta1 entry");
+		return -EIO;
+	}
+	if (!next_entry(ef, dir, &cluster, &offset))
+		return -EIO;
+	if (exfat_pwrite(ef->dev, &meta2, sizeof(meta2),
+			co2o(ef, cluster, offset)) < 0)
+	{
+		exfat_error("failed to write meta2 entry");
+		return -EIO;
+	}
+	for (i = 0; i < name_entries; i++)
+	{
+		struct exfat_entry_name name_entry = {EXFAT_ENTRY_FILE_NAME, 0};
+		memcpy(name_entry.name, node->name + i * EXFAT_ENAME_MAX,
+				MIN(EXFAT_ENAME_MAX, EXFAT_NAME_MAX - i * EXFAT_ENAME_MAX) *
+				sizeof(le16_t));
+		if (!next_entry(ef, dir, &cluster, &offset))
+			return -EIO;
+		if (exfat_pwrite(ef->dev, &name_entry, sizeof(name_entry),
+				co2o(ef, cluster, offset)) < 0)
+		{
+			exfat_error("failed to write name entry");
+			return -EIO;
+		}
+	}
+
+	init_node_meta1(node, &meta1);
+	init_node_meta2(node, &meta2);
+
+	tree_attach(dir, node);
+	exfat_update_mtime(dir);
+	return 0;
+}
+
+static int create(struct exfat* ef, const char* path, uint16_t attrib)
+{
+	struct exfat_node* dir;
+	struct exfat_node* existing;
+	cluster_t cluster = EXFAT_CLUSTER_BAD;
+	loff_t offset = -1;
+	le16_t name[EXFAT_NAME_MAX + 1];
+	int rc;
+
+	rc = exfat_split(ef, &dir, &existing, name, path);
+	if (rc != 0)
+		return rc;
+	if (existing != NULL)
+	{
+		exfat_put_node(ef, existing);
+		exfat_put_node(ef, dir);
+		return -EEXIST;
+	}
+
+	rc = find_slot(ef, dir, &cluster, &offset,
+			2 + DIV_ROUND_UP(utf16_length(name), EXFAT_ENAME_MAX));
+	if (rc != 0)
+	{
+		exfat_put_node(ef, dir);
+		return rc;
+	}
+	rc = write_entry(ef, dir, name, cluster, offset, attrib);
+	if (rc != 0)
+	{
+		exfat_put_node(ef, dir);
+		return rc;
+	}
+	rc = exfat_flush_node(ef, dir);
+	exfat_put_node(ef, dir);
+	return rc;
+}
+
+int exfat_mknod(struct exfat* ef, const char* path)
+{
+	return create(ef, path, EXFAT_ATTRIB_ARCH);
+}
+
+int exfat_mkdir(struct exfat* ef, const char* path)
+{
+	int rc;
+	struct exfat_node* node;
+
+	rc = create(ef, path, EXFAT_ATTRIB_DIR);
+	if (rc != 0)
+		return rc;
+	rc = exfat_lookup(ef, &node, path);
+	if (rc != 0)
+		return 0;
+	/* directories always have at least one cluster */
+	rc = exfat_truncate(ef, node, CLUSTER_SIZE(*ef->sb), true);
+	if (rc != 0)
+	{
+		delete(ef, node);
+		exfat_put_node(ef, node);
+		return rc;
+	}
+	rc = exfat_flush_node(ef, node);
+	if (rc != 0)
+	{
+		delete(ef, node);
+		exfat_put_node(ef, node);
+		return rc;
+	}
+	exfat_put_node(ef, node);
+	return 0;
+}
+
+static int rename_entry(struct exfat* ef, struct exfat_node* dir,
+		struct exfat_node* node, const le16_t* name, cluster_t new_cluster,
+		loff_t new_offset)
+{
+	struct exfat_entry_meta1 meta1;
+	struct exfat_entry_meta2 meta2;
+	cluster_t old_cluster = node->entry_cluster;
+	loff_t old_offset = node->entry_offset;
+	const size_t name_length = utf16_length(name);
+	const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
+	int i;
+
+	if (exfat_pread(ef->dev, &meta1, sizeof(meta1),
+			co2o(ef, old_cluster, old_offset)) < 0)
+	{
+		exfat_error("failed to read meta1 entry on rename");
+		return -EIO;
+	}
+	if (!next_entry(ef, node->parent, &old_cluster, &old_offset))
+		return -EIO;
+	if (exfat_pread(ef->dev, &meta2, sizeof(meta2),
+			co2o(ef, old_cluster, old_offset)) < 0)
+	{
+		exfat_error("failed to read meta2 entry on rename");
+		return -EIO;
+	}
+	meta1.continuations = 1 + name_entries;
+	meta2.name_hash = exfat_calc_name_hash(ef, name);
+	meta2.name_length = name_length;
+	meta1.checksum = exfat_calc_checksum(&meta1, &meta2, name);
+
+	if (!erase_entry(ef, node))
+		return -EIO;
+
+	node->entry_cluster = new_cluster;
+	node->entry_offset = new_offset;
+
+	if (exfat_pwrite(ef->dev, &meta1, sizeof(meta1),
+			co2o(ef, new_cluster, new_offset)) < 0)
+	{
+		exfat_error("failed to write meta1 entry on rename");
+		return -EIO;
+	}
+	if (!next_entry(ef, dir, &new_cluster, &new_offset))
+		return -EIO;
+	if (exfat_pwrite(ef->dev, &meta2, sizeof(meta2),
+			co2o(ef, new_cluster, new_offset)) < 0)
+	{
+		exfat_error("failed to write meta2 entry on rename");
+		return -EIO;
+	}
+
+	for (i = 0; i < name_entries; i++)
+	{
+		struct exfat_entry_name name_entry = {EXFAT_ENTRY_FILE_NAME, 0};
+		memcpy(name_entry.name, name + i * EXFAT_ENAME_MAX,
+				EXFAT_ENAME_MAX * sizeof(le16_t));
+		if (!next_entry(ef, dir, &new_cluster, &new_offset))
+			return -EIO;
+		if (exfat_pwrite(ef->dev, &name_entry, sizeof(name_entry),
+				co2o(ef, new_cluster, new_offset)) < 0)
+		{
+			exfat_error("failed to write name entry on rename");
+			return -EIO;
+		}
+	}
+
+	memcpy(node->name, name, (EXFAT_NAME_MAX + 1) * sizeof(le16_t));
+	tree_detach(node);
+	tree_attach(dir, node);
+	return 0;
+}
+
+int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
+{
+	struct exfat_node* node;
+	struct exfat_node* existing;
+	struct exfat_node* dir;
+	cluster_t cluster = EXFAT_CLUSTER_BAD;
+	loff_t offset = -1;
+	le16_t name[EXFAT_NAME_MAX + 1];
+	int rc;
+
+	rc = exfat_lookup(ef, &node, old_path);
+	if (rc != 0)
+		return rc;
+
+	rc = exfat_split(ef, &dir, &existing, name, new_path);
+	if (rc != 0)
+	{
+		exfat_put_node(ef, node);
+		return rc;
+	}
+
+	/* check that target is not a subdirectory of the source */
+	if (node->flags & EXFAT_ATTRIB_DIR)
+	{
+		struct exfat_node* p;
+
+		for (p = dir; p; p = p->parent)
+			if (node == p)
+			{
+				if (existing != NULL)
+					exfat_put_node(ef, existing);
+				exfat_put_node(ef, dir);
+				exfat_put_node(ef, node);
+				return -EINVAL;
+			}
+	}
+
+	if (existing != NULL)
+	{
+		/* remove target if it's not the same node as source */
+		if (existing != node)
+		{
+			if (existing->flags & EXFAT_ATTRIB_DIR)
+			{
+				if (node->flags & EXFAT_ATTRIB_DIR)
+					rc = exfat_rmdir(ef, existing);
+				else
+					rc = -ENOTDIR;
+			}
+			else
+			{
+				if (!(node->flags & EXFAT_ATTRIB_DIR))
+					rc = exfat_unlink(ef, existing);
+				else
+					rc = -EISDIR;
+			}
+			exfat_put_node(ef, existing);
+			if (rc != 0)
+			{
+				exfat_put_node(ef, dir);
+				exfat_put_node(ef, node);
+				return rc;
+			}
+		}
+		else
+			exfat_put_node(ef, existing);
+	}
+
+	rc = find_slot(ef, dir, &cluster, &offset,
+			2 + DIV_ROUND_UP(utf16_length(name), EXFAT_ENAME_MAX));
+	if (rc != 0)
+	{
+		exfat_put_node(ef, dir);
+		exfat_put_node(ef, node);
+		return rc;
+	}
+	rc = rename_entry(ef, dir, node, name, cluster, offset);
+	exfat_put_node(ef, dir);
+	exfat_put_node(ef, node);
+	return rc;
+}
+
+void exfat_utimes(struct exfat_node* node, const struct timespec tv[2])
+{
+	node->atime = tv[0].tv_sec;
+	node->mtime = tv[1].tv_sec;
+	node->flags |= EXFAT_ATTRIB_DIRTY;
+}
+
+void exfat_update_atime(struct exfat_node* node)
+{
+	node->atime = time(NULL);
+	node->flags |= EXFAT_ATTRIB_DIRTY;
+}
+
+void exfat_update_mtime(struct exfat_node* node)
+{
+	node->mtime = time(NULL);
+	node->flags |= EXFAT_ATTRIB_DIRTY;
+}
+
+const char* exfat_get_label(struct exfat* ef)
+{
+	return ef->label;
+}
+
+static int find_label(struct exfat* ef, cluster_t* cluster, loff_t* offset)
+{
+	struct iterator it;
+	int rc;
+
+	rc = opendir(ef, ef->root, &it);
+	if (rc != 0)
+		return rc;
+
+	for (;;)
+	{
+		if (it.offset >= ef->root->size)
+		{
+			closedir(&it);
+			return -ENOENT;
+		}
+
+		if (get_entry_ptr(ef, &it)->type == EXFAT_ENTRY_LABEL)
+		{
+			*cluster = it.cluster;
+			*offset = it.offset;
+			closedir(&it);
+			return 0;
+		}
+
+		if (!fetch_next_entry(ef, ef->root, &it))
+		{
+			closedir(&it);
+			return -EIO;
+		}
+	}
+}
+
+int exfat_set_label(struct exfat* ef, const char* label)
+{
+	le16_t label_utf16[EXFAT_ENAME_MAX + 1];
+	int rc;
+	cluster_t cluster;
+	loff_t offset;
+	struct exfat_entry_label entry;
+
+	memset(label_utf16, 0, sizeof(label_utf16));
+	rc = utf8_to_utf16(label_utf16, label, EXFAT_ENAME_MAX, strlen(label));
+	if (rc != 0)
+		return rc;
+
+	rc = find_label(ef, &cluster, &offset);
+	if (rc == -ENOENT)
+		rc = find_slot(ef, ef->root, &cluster, &offset, 1);
+	if (rc != 0)
+		return rc;
+
+	entry.type = EXFAT_ENTRY_LABEL;
+	entry.length = utf16_length(label_utf16);
+	memcpy(entry.name, label_utf16, sizeof(entry.name));
+	if (entry.length == 0)
+		entry.type ^= EXFAT_ENTRY_VALID;
+
+	if (exfat_pwrite(ef->dev, &entry, sizeof(struct exfat_entry_label),
+			co2o(ef, cluster, offset)) < 0)
+	{
+		exfat_error("failed to write label entry");
+		return -EIO;
+	}
+	strcpy(ef->label, label);
+	return 0;
+}
diff --git a/exfat/libexfat/platform.h b/exfat/libexfat/platform.h
new file mode 100644
index 0000000..d2ad6d1
--- /dev/null
+++ b/exfat/libexfat/platform.h
@@ -0,0 +1,63 @@
+/*
+	platform.h (14.05.13)
+	OS-specific code (libc-specific in fact). Note that systems with the
+	same kernel can use different libc implementations.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef PLATFORM_H_INCLUDED
+#define PLATFORM_H_INCLUDED
+
+#if defined(__linux__) || defined(__GLIBC__) || defined(__GNU__)
+
+#include <endian.h>
+#include <byteswap.h>
+#define exfat_bswap16(x) bswap_16(x)
+#define exfat_bswap32(x) bswap_32(x)
+#define exfat_bswap64(x) bswap_64(x)
+#define EXFAT_BYTE_ORDER __BYTE_ORDER
+#define EXFAT_LITTLE_ENDIAN __LITTLE_ENDIAN
+#define EXFAT_BIG_ENDIAN __BIG_ENDIAN
+
+#elif defined(__APPLE__)
+
+#include <machine/endian.h>
+#include <libkern/OSByteOrder.h>
+#define exfat_bswap16(x) OSSwapInt16(x)
+#define exfat_bswap32(x) OSSwapInt32(x)
+#define exfat_bswap64(x) OSSwapInt64(x)
+#define EXFAT_BYTE_ORDER BYTE_ORDER
+#define EXFAT_LITTLE_ENDIAN LITTLE_ENDIAN
+#define EXFAT_BIG_ENDIAN BIG_ENDIAN
+
+#elif defined(__ANDROID__) || defined(__FreeBSD__) || defined(__DragonFlyBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+
+#include <sys/endian.h>
+#define exfat_bswap16(x) bswap16(x)
+#define exfat_bswap32(x) bswap32(x)
+#define exfat_bswap64(x) bswap64(x)
+#define EXFAT_BYTE_ORDER _BYTE_ORDER
+#define EXFAT_LITTLE_ENDIAN _LITTLE_ENDIAN
+#define EXFAT_BIG_ENDIAN _BIG_ENDIAN
+
+#else 
+#error Unknown platform
+#endif
+
+#endif /* ifndef PLATFORM_H_INCLUDED */
diff --git a/exfat/libexfat/time.c b/exfat/libexfat/time.c
new file mode 100644
index 0000000..45c4aff
--- /dev/null
+++ b/exfat/libexfat/time.c
@@ -0,0 +1,158 @@
+/*
+	time.c (03.02.12)
+	exFAT file system implementation library.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "exfat.h"
+
+/* timezone offset from UTC in seconds; positive for western timezones,
+   negative for eastern ones */
+static long exfat_timezone;
+
+#define SEC_IN_MIN 60ll
+#define SEC_IN_HOUR (60 * SEC_IN_MIN)
+#define SEC_IN_DAY (24 * SEC_IN_HOUR)
+#define SEC_IN_YEAR (365 * SEC_IN_DAY) /* not leap year */
+/* Unix epoch started at 0:00:00 UTC 1 January 1970 */
+#define UNIX_EPOCH_YEAR 1970
+/* exFAT epoch started at 0:00:00 UTC 1 January 1980 */
+#define EXFAT_EPOCH_YEAR 1980
+/* number of years from Unix epoch to exFAT epoch */
+#define EPOCH_DIFF_YEAR (EXFAT_EPOCH_YEAR - UNIX_EPOCH_YEAR)
+/* number of days from Unix epoch to exFAT epoch (considering leap days) */
+#define EPOCH_DIFF_DAYS (EPOCH_DIFF_YEAR * 365 + EPOCH_DIFF_YEAR / 4)
+/* number of seconds from Unix epoch to exFAT epoch (considering leap days) */
+#define EPOCH_DIFF_SEC (EPOCH_DIFF_DAYS * SEC_IN_DAY)
+/* number of leap years passed from exFAT epoch to the specified year
+   (excluding the specified year itself) */
+#define LEAP_YEARS(year) ((EXFAT_EPOCH_YEAR + (year) - 1) / 4 \
+		- (EXFAT_EPOCH_YEAR - 1) / 4)
+/* checks whether the specified year is leap */
+#define IS_LEAP_YEAR(year) ((EXFAT_EPOCH_YEAR + (year)) % 4 == 0)
+
+static const time_t days_in_year[] =
+{
+	/* Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec */
+	0,   0,  31,  59,  90, 120, 151, 181, 212, 243, 273, 304, 334
+};
+
+time_t exfat_exfat2unix(le16_t date, le16_t time, uint8_t centisec)
+{
+	time_t unix_time = EPOCH_DIFF_SEC;
+	uint16_t ndate = le16_to_cpu(date);
+	uint16_t ntime = le16_to_cpu(time);
+
+	uint16_t day    = ndate & 0x1f;      /* 5 bits, 1-31 */
+	uint16_t month  = ndate >> 5 & 0xf;  /* 4 bits, 1-12 */
+	uint16_t year   = ndate >> 9;        /* 7 bits, 1-127 (+1980) */
+
+	uint16_t twosec = ntime & 0x1f;      /* 5 bits, 0-29 (2 sec granularity) */
+	uint16_t min    = ntime >> 5 & 0x3f; /* 6 bits, 0-59 */
+	uint16_t hour   = ntime >> 11;       /* 5 bits, 0-23 */
+
+	if (day == 0 || month == 0 || month > 12)
+	{
+		exfat_error("bad date %u-%02hu-%02hu",
+				year + EXFAT_EPOCH_YEAR, month, day);
+		return 0;
+	}
+	if (hour > 23 || min > 59 || twosec > 29)
+	{
+		exfat_error("bad time %hu:%02hu:%02u",
+				hour, min, twosec * 2);
+		return 0;
+	}
+	if (centisec > 199)
+	{
+		exfat_error("bad centiseconds count %hhu", centisec);
+		return 0;
+	}
+
+	/* every 4th year between 1904 and 2096 is leap */
+	unix_time += year * SEC_IN_YEAR + LEAP_YEARS(year) * SEC_IN_DAY;
+	unix_time += days_in_year[month] * SEC_IN_DAY;
+	/* if it's leap year and February has passed we should add 1 day */
+	if ((EXFAT_EPOCH_YEAR + year) % 4 == 0 && month > 2)
+		unix_time += SEC_IN_DAY;
+	unix_time += (day - 1) * SEC_IN_DAY;
+
+	unix_time += hour * SEC_IN_HOUR;
+	unix_time += min * SEC_IN_MIN;
+	/* exFAT represents time with 2 sec granularity */
+	unix_time += twosec * 2;
+	unix_time += centisec / 100;
+
+	/* exFAT stores timestamps in local time, so we correct it to UTC */
+	unix_time += exfat_timezone;
+
+	return unix_time;
+}
+
+void exfat_unix2exfat(time_t unix_time, le16_t* date, le16_t* time,
+		uint8_t* centisec)
+{
+	time_t shift = EPOCH_DIFF_SEC + exfat_timezone;
+	uint16_t day, month, year;
+	uint16_t twosec, min, hour;
+	int days;
+	int i;
+
+	/* time before exFAT epoch cannot be represented */
+	if (unix_time < shift)
+		unix_time = shift;
+
+	unix_time -= shift;
+
+	days = unix_time / SEC_IN_DAY;
+	year = (4 * days) / (4 * 365 + 1);
+	days -= year * 365 + LEAP_YEARS(year);
+	month = 0;
+	for (i = 1; i <= 12; i++)
+	{
+		int leap_day = (IS_LEAP_YEAR(year) && i == 2);
+		int leap_sub = (IS_LEAP_YEAR(year) && i >= 3);
+
+		if (i == 12 || days - leap_sub < days_in_year[i + 1] + leap_day)
+		{
+			month = i;
+			days -= days_in_year[i] + leap_sub;
+			break;
+		}
+	}
+	day = days + 1;
+
+	hour = (unix_time % SEC_IN_DAY) / SEC_IN_HOUR;
+	min = (unix_time % SEC_IN_HOUR) / SEC_IN_MIN;
+	twosec = (unix_time % SEC_IN_MIN) / 2;
+
+	*date = cpu_to_le16(day | (month << 5) | (year << 9));
+	*time = cpu_to_le16(twosec | (min << 5) | (hour << 11));
+	if (centisec)
+		*centisec = (unix_time % 2) * 100;
+}
+
+void exfat_tzset(void)
+{
+	time_t now;
+
+	tzset();
+	now = time(NULL);
+	exfat_timezone = mktime(gmtime(&now)) - now;
+}
diff --git a/exfat/libexfat/utf.c b/exfat/libexfat/utf.c
new file mode 100644
index 0000000..6f14882
--- /dev/null
+++ b/exfat/libexfat/utf.c
@@ -0,0 +1,231 @@
+/*
+	utf.c (13.09.09)
+	exFAT file system implementation library.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "exfat.h"
+#include <errno.h>
+
+static char* wchar_to_utf8(char* output, wchar_t wc, size_t outsize)
+{
+	if (wc <= 0x7f)
+	{
+		if (outsize < 1)
+			return NULL;
+		*output++ = (char) wc;
+	}
+	else if (wc <= 0x7ff)
+	{
+		if (outsize < 2)
+			return NULL;
+		*output++ = 0xc0 | (wc >> 6);
+		*output++ = 0x80 | (wc & 0x3f);
+	}
+	else if (wc <= 0xffff)
+	{
+		if (outsize < 3)
+			return NULL;
+		*output++ = 0xe0 | (wc >> 12);
+		*output++ = 0x80 | ((wc >> 6) & 0x3f);
+		*output++ = 0x80 | (wc & 0x3f);
+	}
+	else if (wc <= 0x1fffff)
+	{
+		if (outsize < 4)
+			return NULL;
+		*output++ = 0xf0 | (wc >> 18);
+		*output++ = 0x80 | ((wc >> 12) & 0x3f);
+		*output++ = 0x80 | ((wc >> 6) & 0x3f);
+		*output++ = 0x80 | (wc & 0x3f);
+	}
+	else if (wc <= 0x3ffffff)
+	{
+		if (outsize < 5)
+			return NULL;
+		*output++ = 0xf8 | (wc >> 24);
+		*output++ = 0x80 | ((wc >> 18) & 0x3f);
+		*output++ = 0x80 | ((wc >> 12) & 0x3f);
+		*output++ = 0x80 | ((wc >> 6) & 0x3f);
+		*output++ = 0x80 | (wc & 0x3f);
+	}
+	else if (wc <= 0x7fffffff)
+	{
+		if (outsize < 6)
+			return NULL;
+		*output++ = 0xfc | (wc >> 30);
+		*output++ = 0x80 | ((wc >> 24) & 0x3f);
+		*output++ = 0x80 | ((wc >> 18) & 0x3f);
+		*output++ = 0x80 | ((wc >> 12) & 0x3f);
+		*output++ = 0x80 | ((wc >> 6) & 0x3f);
+		*output++ = 0x80 | (wc & 0x3f);
+	}
+	else
+		return NULL;
+
+	return output;
+}
+
+static const le16_t* utf16_to_wchar(const le16_t* input, wchar_t* wc,
+		size_t insize)
+{
+	if ((le16_to_cpu(input[0]) & 0xfc00) == 0xd800)
+	{
+		if (insize < 2 || (le16_to_cpu(input[1]) & 0xfc00) != 0xdc00)
+			return NULL;
+		*wc = ((wchar_t) (le16_to_cpu(input[0]) & 0x3ff) << 10);
+		*wc |= (le16_to_cpu(input[1]) & 0x3ff);
+		*wc += 0x10000;
+		return input + 2;
+	}
+	else
+	{
+		*wc = le16_to_cpu(*input);
+		return input + 1;
+	}
+}
+
+int utf16_to_utf8(char* output, const le16_t* input, size_t outsize,
+		size_t insize)
+{
+	const le16_t* inp = input;
+	char* outp = output;
+	wchar_t wc;
+
+	while (inp - input < insize && le16_to_cpu(*inp))
+	{
+		inp = utf16_to_wchar(inp, &wc, insize - (inp - input));
+		if (inp == NULL)
+		{
+			exfat_error("illegal UTF-16 sequence");
+			return -EILSEQ;
+		}
+		outp = wchar_to_utf8(outp, wc, outsize - (outp - output));
+		if (outp == NULL)
+		{
+			exfat_error("name is too long");
+			return -ENAMETOOLONG;
+		}
+	}
+	*outp = '\0';
+	return 0;
+}
+
+static const char* utf8_to_wchar(const char* input, wchar_t* wc,
+		size_t insize)
+{
+	if ((input[0] & 0x80) == 0 && insize >= 1)
+	{
+		*wc = (wchar_t) input[0];
+		return input + 1;
+	}
+	if ((input[0] & 0xe0) == 0xc0 && insize >= 2)
+	{
+		*wc = (((wchar_t) input[0] & 0x1f) << 6) |
+		       ((wchar_t) input[1] & 0x3f);
+		return input + 2;
+	}
+	if ((input[0] & 0xf0) == 0xe0 && insize >= 3)
+	{
+		*wc = (((wchar_t) input[0] & 0x0f) << 12) |
+		      (((wchar_t) input[1] & 0x3f) << 6) |
+		       ((wchar_t) input[2] & 0x3f);
+		return input + 3;
+	}
+	if ((input[0] & 0xf8) == 0xf0 && insize >= 4)
+	{
+		*wc = (((wchar_t) input[0] & 0x07) << 18) |
+		      (((wchar_t) input[1] & 0x3f) << 12) |
+		      (((wchar_t) input[2] & 0x3f) << 6) |
+		       ((wchar_t) input[3] & 0x3f);
+		return input + 4;
+	}
+	if ((input[0] & 0xfc) == 0xf8 && insize >= 5)
+	{
+		*wc = (((wchar_t) input[0] & 0x03) << 24) |
+		      (((wchar_t) input[1] & 0x3f) << 18) |
+		      (((wchar_t) input[2] & 0x3f) << 12) |
+		      (((wchar_t) input[3] & 0x3f) << 6) |
+		       ((wchar_t) input[4] & 0x3f);
+		return input + 5;
+	}
+	if ((input[0] & 0xfe) == 0xfc && insize >= 6)
+	{
+		*wc = (((wchar_t) input[0] & 0x01) << 30) |
+		      (((wchar_t) input[1] & 0x3f) << 24) |
+		      (((wchar_t) input[2] & 0x3f) << 18) |
+		      (((wchar_t) input[3] & 0x3f) << 12) |
+		      (((wchar_t) input[4] & 0x3f) << 6) |
+		       ((wchar_t) input[5] & 0x3f);
+		return input + 6;
+	}
+	return NULL;
+}
+
+static le16_t* wchar_to_utf16(le16_t* output, wchar_t wc, size_t outsize)
+{
+	if (wc <= 0xffff) /* if character is from BMP */
+	{
+		if (outsize == 0)
+			return NULL;
+		output[0] = cpu_to_le16(wc);
+		return output + 1;
+	}
+	if (outsize < 2)
+		return NULL;
+	wc -= 0x10000;
+	output[0] = cpu_to_le16(0xd800 | ((wc >> 10) & 0x3ff));
+	output[1] = cpu_to_le16(0xdc00 | (wc & 0x3ff));
+	return output + 2;
+}
+
+int utf8_to_utf16(le16_t* output, const char* input, size_t outsize,
+		size_t insize)
+{
+	const char* inp = input;
+	le16_t* outp = output;
+	wchar_t wc;
+
+	while (inp - input < insize && *inp)
+	{
+		inp = utf8_to_wchar(inp, &wc, insize - (inp - input));
+		if (inp == NULL)
+		{
+			exfat_error("illegal UTF-8 sequence");
+			return -EILSEQ;
+		}
+		outp = wchar_to_utf16(outp, wc, outsize - (outp - output));
+		if (outp == NULL)
+		{
+			exfat_error("name is too long");
+			return -ENAMETOOLONG;
+		}
+	}
+	*outp = cpu_to_le16(0);
+	return 0;
+}
+
+size_t utf16_length(const le16_t* str)
+{
+	size_t i = 0;
+
+	while (le16_to_cpu(str[i]))
+		i++;
+	return i;
+}
diff --git a/exfat/libexfat/utils.c b/exfat/libexfat/utils.c
new file mode 100644
index 0000000..39d708e
--- /dev/null
+++ b/exfat/libexfat/utils.c
@@ -0,0 +1,179 @@
+/*
+	utils.c (04.09.09)
+	exFAT file system implementation library.
+
+	Free exFAT implementation.
+	Copyright (C) 2010-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "exfat.h"
+#include <string.h>
+#include <stdio.h>
+#include <inttypes.h>
+
+void exfat_stat(const struct exfat* ef, const struct exfat_node* node,
+		struct stat* stbuf)
+{
+	memset(stbuf, 0, sizeof(struct stat));
+	if (node->flags & EXFAT_ATTRIB_DIR)
+		stbuf->st_mode = S_IFDIR | (0777 & ~ef->dmask);
+	else
+		stbuf->st_mode = S_IFREG | (0777 & ~ef->fmask);
+	stbuf->st_nlink = 1;
+	stbuf->st_uid = ef->uid;
+	stbuf->st_gid = ef->gid;
+	stbuf->st_size = node->size;
+	stbuf->st_blocks = DIV_ROUND_UP(node->size, CLUSTER_SIZE(*ef->sb)) *
+		CLUSTER_SIZE(*ef->sb) / 512;
+	stbuf->st_mtime = node->mtime;
+	stbuf->st_atime = node->atime;
+	/* set ctime to mtime to ensure we don't break programs that rely on ctime
+	   (e.g. rsync) */
+	stbuf->st_ctime = node->mtime;
+}
+
+void exfat_get_name(const struct exfat_node* node, char* buffer, size_t n)
+{
+	if (utf16_to_utf8(buffer, node->name, n, EXFAT_NAME_MAX) != 0)
+		exfat_bug("failed to convert name to UTF-8");
+}
+
+uint16_t exfat_start_checksum(const struct exfat_entry_meta1* entry)
+{
+	uint16_t sum = 0;
+	int i;
+
+	for (i = 0; i < sizeof(struct exfat_entry); i++)
+		if (i != 2 && i != 3) /* skip checksum field itself */
+			sum = ((sum << 15) | (sum >> 1)) + ((const uint8_t*) entry)[i];
+	return sum;
+}
+
+uint16_t exfat_add_checksum(const void* entry, uint16_t sum)
+{
+	int i;
+
+	for (i = 0; i < sizeof(struct exfat_entry); i++)
+		sum = ((sum << 15) | (sum >> 1)) + ((const uint8_t*) entry)[i];
+	return sum;
+}
+
+le16_t exfat_calc_checksum(const struct exfat_entry_meta1* meta1,
+		const struct exfat_entry_meta2* meta2, const le16_t* name)
+{
+	uint16_t checksum;
+	const int name_entries = DIV_ROUND_UP(utf16_length(name), EXFAT_ENAME_MAX);
+	int i;
+
+	checksum = exfat_start_checksum(meta1);
+	checksum = exfat_add_checksum(meta2, checksum);
+	for (i = 0; i < name_entries; i++)
+	{
+		struct exfat_entry_name name_entry = {EXFAT_ENTRY_FILE_NAME, 0};
+		memcpy(name_entry.name, name + i * EXFAT_ENAME_MAX,
+				MIN(EXFAT_ENAME_MAX, EXFAT_NAME_MAX - i * EXFAT_ENAME_MAX) *
+				sizeof(le16_t));
+		checksum = exfat_add_checksum(&name_entry, checksum);
+	}
+	return cpu_to_le16(checksum);
+}
+
+uint32_t exfat_vbr_start_checksum(const void* sector, size_t size)
+{
+	size_t i;
+	uint32_t sum = 0;
+
+	for (i = 0; i < size; i++)
+		/* skip volume_state and allocated_percent fields */
+		if (i != 0x6a && i != 0x6b && i != 0x70)
+			sum = ((sum << 31) | (sum >> 1)) + ((const uint8_t*) sector)[i];
+	return sum;
+}
+
+uint32_t exfat_vbr_add_checksum(const void* sector, size_t size, uint32_t sum)
+{
+	size_t i;
+
+	for (i = 0; i < size; i++)
+		sum = ((sum << 31) | (sum >> 1)) + ((const uint8_t*) sector)[i];
+	return sum;
+}
+
+le16_t exfat_calc_name_hash(const struct exfat* ef, const le16_t* name)
+{
+	size_t i;
+	size_t length = utf16_length(name);
+	uint16_t hash = 0;
+
+	for (i = 0; i < length; i++)
+	{
+		uint16_t c = le16_to_cpu(name[i]);
+
+		/* convert to upper case */
+		if (c < ef->upcase_chars)
+			c = le16_to_cpu(ef->upcase[c]);
+
+		hash = ((hash << 15) | (hash >> 1)) + (c & 0xff);
+		hash = ((hash << 15) | (hash >> 1)) + (c >> 8);
+	}
+	return cpu_to_le16(hash);
+}
+
+void exfat_humanize_bytes(uint64_t value, struct exfat_human_bytes* hb)
+{
+	size_t i;
+	/* 16 EB (minus 1 byte) is the largest size that can be represented by
+	   uint64_t */
+	const char* units[] = {"bytes", "KB", "MB", "GB", "TB", "PB", "EB"};
+	uint64_t divisor = 1;
+	uint64_t temp = 0;
+
+	for (i = 0; ; i++, divisor *= 1024)
+	{
+		temp = (value + divisor / 2) / divisor;
+
+		if (temp == 0)
+			break;
+		if (temp / 1024 * 1024 == temp)
+			continue;
+		if (temp < 10240)
+			break;
+	}
+	hb->value = temp;
+	hb->unit = units[i];
+}
+
+void exfat_print_info(const struct exfat_super_block* sb,
+		uint32_t free_clusters)
+{
+	struct exfat_human_bytes hb;
+	loff_t total_space = le64_to_cpu(sb->sector_count) * SECTOR_SIZE(*sb);
+	loff_t avail_space = (loff_t) free_clusters * CLUSTER_SIZE(*sb);
+
+	printf("File system version           %hhu.%hhu\n",
+			sb->version.major, sb->version.minor);
+	exfat_humanize_bytes(SECTOR_SIZE(*sb), &hb);
+	printf("Sector size          %10"PRIu64" %s\n", hb.value, hb.unit);
+	exfat_humanize_bytes(CLUSTER_SIZE(*sb), &hb);
+	printf("Cluster size         %10"PRIu64" %s\n", hb.value, hb.unit);
+	exfat_humanize_bytes(total_space, &hb);
+	printf("Volume size          %10"PRIu64" %s\n", hb.value, hb.unit);
+	exfat_humanize_bytes(total_space - avail_space, &hb);
+	printf("Used space           %10"PRIu64" %s\n", hb.value, hb.unit);
+	exfat_humanize_bytes(avail_space, &hb);
+	printf("Available space      %10"PRIu64" %s\n", hb.value, hb.unit);
+}
diff --git a/exfat/mkfs/Android.mk b/exfat/mkfs/Android.mk
new file mode 100755
index 0000000..e1158f2
--- /dev/null
+++ b/exfat/mkfs/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := mkexfatfs
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -Wno-sign-compare
+LOCAL_SRC_FILES = cbm.c fat.c main.c mkexfat.c rootdir.c uct.c uctc.c vbr.c
+LOCAL_C_INCLUDES += $(LOCAL_PATH) \
+					$(commands_recovery_local_path)/exfat/libexfat \
+					$(commands_recovery_local_path)/fuse/include
+LOCAL_SHARED_LIBRARIES := libexfat_twrp
+LOCAL_STATIC_LIBRARIES := libfusetwrp
+
+include $(BUILD_EXECUTABLE)
diff --git a/exfat/mkfs/cbm.c b/exfat/mkfs/cbm.c
new file mode 100644
index 0000000..eb67592
--- /dev/null
+++ b/exfat/mkfs/cbm.c
@@ -0,0 +1,79 @@
+/*
+	cbm.c (09.11.10)
+	Clusters Bitmap creation code.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "cbm.h"
+#include "fat.h"
+#include "uct.h"
+#include "rootdir.h"
+#include <limits.h>
+#include <string.h>
+
+static loff_t cbm_alignment(void)
+{
+	return get_cluster_size();
+}
+
+static loff_t cbm_size(void)
+{
+	return DIV_ROUND_UP(
+			(get_volume_size() - get_position(&cbm)) / get_cluster_size(),
+			CHAR_BIT);
+}
+
+static int cbm_write(struct exfat_dev* dev)
+{
+	uint32_t allocated_clusters =
+			DIV_ROUND_UP(cbm.get_size(), get_cluster_size()) +
+			DIV_ROUND_UP(uct.get_size(), get_cluster_size()) +
+			DIV_ROUND_UP(rootdir.get_size(), get_cluster_size());
+	size_t bitmap_size = ROUND_UP(allocated_clusters, CHAR_BIT);
+	bitmap_t* bitmap = malloc(BMAP_SIZE(bitmap_size));
+	size_t i;
+
+	if (bitmap == NULL)
+	{
+		exfat_error("failed to allocate bitmap of %zu bytes",
+				BMAP_SIZE(bitmap_size));
+		return 1;
+	}
+	memset(bitmap, 0, BMAP_SIZE(bitmap_size));
+
+	for (i = 0; i < bitmap_size; i++)
+		if (i < allocated_clusters)
+			BMAP_SET(bitmap, i);
+	if (exfat_write(dev, bitmap, bitmap_size / CHAR_BIT) < 0)
+	{
+		free(bitmap);
+		exfat_error("failed to write bitmap of %zu bytes",
+				bitmap_size / CHAR_BIT);
+		return 1;
+	}
+	free(bitmap);
+	return 0;
+}
+
+const struct fs_object cbm =
+{
+	.get_alignment = cbm_alignment,
+	.get_size = cbm_size,
+	.write = cbm_write,
+};
diff --git a/exfat/mkfs/cbm.h b/exfat/mkfs/cbm.h
new file mode 100644
index 0000000..414b0d7
--- /dev/null
+++ b/exfat/mkfs/cbm.h
@@ -0,0 +1,30 @@
+/*
+	cbm.h (09.11.10)
+	Clusters Bitmap creation code.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef MKFS_CBM_H_INCLUDED
+#define MKFS_CBM_H_INCLUDED
+
+#include "mkexfat.h"
+
+extern const struct fs_object cbm;
+
+#endif /* ifndef MKFS_CBM_H_INCLUDED */
diff --git a/exfat/mkfs/fat.c b/exfat/mkfs/fat.c
new file mode 100644
index 0000000..d8dff2b
--- /dev/null
+++ b/exfat/mkfs/fat.c
@@ -0,0 +1,88 @@
+/*
+	fat.c (09.11.10)
+	File Allocation Table creation code.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "fat.h"
+#include "cbm.h"
+#include "uct.h"
+#include "rootdir.h"
+#include <unistd.h>
+
+static loff_t fat_alignment(void)
+{
+	return (loff_t) 128 * get_sector_size();
+}
+
+static loff_t fat_size(void)
+{
+	return get_volume_size() / get_cluster_size() * sizeof(cluster_t);
+}
+
+static cluster_t fat_write_entry(struct exfat_dev* dev, cluster_t cluster,
+		cluster_t value)
+{
+	le32_t fat_entry = cpu_to_le32(value);
+	if (exfat_write(dev, &fat_entry, sizeof(fat_entry)) < 0)
+	{
+		exfat_error("failed to write FAT entry 0x%x", value);
+		return 0;
+	}
+	return cluster + 1;
+}
+
+static cluster_t fat_write_entries(struct exfat_dev* dev, cluster_t cluster,
+		uint64_t length)
+{
+	cluster_t end = cluster + DIV_ROUND_UP(length, get_cluster_size());
+
+	while (cluster < end - 1)
+	{
+		cluster = fat_write_entry(dev, cluster, cluster + 1);
+		if (cluster == 0)
+			return 0;
+	}
+	return fat_write_entry(dev, cluster, EXFAT_CLUSTER_END);
+}
+
+static int fat_write(struct exfat_dev* dev)
+{
+	cluster_t c = 0;
+
+	if (!(c = fat_write_entry(dev, c, 0xfffffff8))) /* media type */
+		return 1;
+	if (!(c = fat_write_entry(dev, c, 0xffffffff))) /* some weird constant */
+		return 1;
+	if (!(c = fat_write_entries(dev, c, cbm.get_size())))
+		return 1;
+	if (!(c = fat_write_entries(dev, c, uct.get_size())))
+		return 1;
+	if (!(c = fat_write_entries(dev, c, rootdir.get_size())))
+		return 1;
+
+	return 0;
+}
+
+const struct fs_object fat =
+{
+	.get_alignment = fat_alignment,
+	.get_size = fat_size,
+	.write = fat_write,
+};
diff --git a/exfat/mkfs/fat.h b/exfat/mkfs/fat.h
new file mode 100644
index 0000000..9370cf3
--- /dev/null
+++ b/exfat/mkfs/fat.h
@@ -0,0 +1,30 @@
+/*
+	fat.h (09.11.10)
+	File Allocation Table creation code.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef MKFS_FAT_H_INCLUDED
+#define MKFS_FAT_H_INCLUDED
+
+#include "mkexfat.h"
+
+extern const struct fs_object fat;
+
+#endif /* ifndef MKFS_FAT_H_INCLUDED */
diff --git a/exfat/mkfs/main.c b/exfat/mkfs/main.c
new file mode 100644
index 0000000..43bc0fb
--- /dev/null
+++ b/exfat/mkfs/main.c
@@ -0,0 +1,255 @@
+/*
+	main.c (15.08.10)
+	Creates exFAT file system.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mkexfat.h"
+#include "vbr.h"
+#include "fat.h"
+#include "cbm.h"
+#include "uct.h"
+#include "rootdir.h"
+#include <exfat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+const struct fs_object* objects[] =
+{
+	&vbr,
+	&vbr,
+	&fat,
+	/* clusters heap */
+	&cbm,
+	&uct,
+	&rootdir,
+	NULL,
+};
+
+static struct
+{
+	int sector_bits;
+	int spc_bits;
+	loff_t volume_size;
+	le16_t volume_label[EXFAT_ENAME_MAX + 1];
+	uint32_t volume_serial;
+	uint64_t first_sector;
+}
+param;
+
+int get_sector_bits(void)
+{
+	return param.sector_bits;
+}
+
+int get_spc_bits(void)
+{
+	return param.spc_bits;
+}
+
+loff_t get_volume_size(void)
+{
+	return param.volume_size;
+}
+
+const le16_t* get_volume_label(void)
+{
+	return param.volume_label;
+}
+
+uint32_t get_volume_serial(void)
+{
+	return param.volume_serial;
+}
+
+uint64_t get_first_sector(void)
+{
+	return param.first_sector;
+}
+
+int get_sector_size(void)
+{
+	return 1 << get_sector_bits();
+}
+
+int get_cluster_size(void)
+{
+	return get_sector_size() << get_spc_bits();
+}
+
+static int setup_spc_bits(int sector_bits, int user_defined, loff_t volume_size)
+{
+	int i;
+
+	if (user_defined != -1)
+	{
+		loff_t cluster_size = 1 << sector_bits << user_defined;
+		if (volume_size / cluster_size > EXFAT_LAST_DATA_CLUSTER)
+		{
+			struct exfat_human_bytes chb, vhb;
+
+			exfat_humanize_bytes(cluster_size, &chb);
+			exfat_humanize_bytes(volume_size, &vhb);
+			exfat_error("cluster size %"PRIu64" %s is too small for "
+					"%"PRIu64" %s volume, try -s %d",
+					chb.value, chb.unit,
+					vhb.value, vhb.unit,
+					1 << setup_spc_bits(sector_bits, -1, volume_size));
+			return -1;
+		}
+		return user_defined;
+	}
+
+	if (volume_size < 256ull * 1024 * 1024)
+		return MAX(0, 12 - sector_bits);	/* 4 KB */
+	if (volume_size < 32ull * 1024 * 1024 * 1024)
+		return MAX(0, 15 - sector_bits);	/* 32 KB */
+
+	for (i = 17; ; i++)						/* 128 KB or more */
+		if (DIV_ROUND_UP(volume_size, 1 << i) <= EXFAT_LAST_DATA_CLUSTER)
+			return MAX(0, i - sector_bits);
+}
+
+static int setup_volume_label(le16_t label[EXFAT_ENAME_MAX + 1], const char* s)
+{
+	memset(label, 0, (EXFAT_ENAME_MAX + 1) * sizeof(le16_t));
+	if (s == NULL)
+		return 0;
+	return utf8_to_utf16(label, s, EXFAT_ENAME_MAX, strlen(s));
+}
+
+static uint32_t setup_volume_serial(uint32_t user_defined)
+{
+	struct timeval now;
+
+	if (user_defined != 0)
+		return user_defined;
+
+	if (gettimeofday(&now, NULL) != 0)
+	{
+		exfat_error("failed to form volume id");
+		return 0;
+	}
+	return (now.tv_sec << 20) | now.tv_usec;
+}
+
+static int setup(struct exfat_dev* dev, int sector_bits, int spc_bits,
+		const char* volume_label, uint32_t volume_serial,
+		uint64_t first_sector)
+{
+	param.sector_bits = sector_bits;
+	param.first_sector = first_sector;
+	param.volume_size = exfat_get_size(dev);
+
+	param.spc_bits = setup_spc_bits(sector_bits, spc_bits, param.volume_size);
+	if (param.spc_bits == -1)
+		return 1;
+
+	if (setup_volume_label(param.volume_label, volume_label) != 0)
+		return 1;
+
+	param.volume_serial = setup_volume_serial(volume_serial);
+	if (param.volume_serial == 0)
+		return 1;
+
+	return mkfs(dev, param.volume_size);
+}
+
+static int logarithm2(int n)
+{
+	int i;
+
+	for (i = 0; i < sizeof(int) * CHAR_BIT - 1; i++)
+		if ((1 << i) == n)
+			return i;
+	return -1;
+}
+
+static void usage(const char* prog)
+{
+	fprintf(stderr, "Usage: %s [-i volume-id] [-n label] "
+			"[-p partition-first-sector] "
+			"[-s sectors-per-cluster] [-V] <device>\n", prog);
+	exit(1);
+}
+
+int main(int argc, char* argv[])
+{
+	const char* spec = NULL;
+	int opt;
+	int spc_bits = -1;
+	const char* volume_label = NULL;
+	uint32_t volume_serial = 0;
+	uint64_t first_sector = 0;
+	struct exfat_dev* dev;
+
+	printf("mkexfatfs %s\n", VERSION);
+
+	while ((opt = getopt(argc, argv, "i:n:p:s:V")) != -1)
+	{
+		switch (opt)
+		{
+		case 'i':
+			volume_serial = strtol(optarg, NULL, 16);
+			break;
+		case 'n':
+			volume_label = optarg;
+			break;
+		case 'p':
+			first_sector = strtoll(optarg, NULL, 10);
+			break;
+		case 's':
+			spc_bits = logarithm2(atoi(optarg));
+			if (spc_bits < 0)
+			{
+				exfat_error("invalid option value: '%s'", optarg);
+				return 1;
+			}
+			break;
+		case 'V':
+			puts("Copyright (C) 2011-2015  Andrew Nayenko");
+			return 0;
+		default:
+			usage(argv[0]);
+			break;
+		}
+	}
+	if (argc - optind != 1)
+		usage(argv[0]);
+	spec = argv[optind];
+
+	dev = exfat_open(spec, EXFAT_MODE_RW);
+	if (dev == NULL)
+		return 1;
+	if (setup(dev, 9, spc_bits, volume_label, volume_serial,
+				first_sector) != 0)
+	{
+		exfat_close(dev);
+		return 1;
+	}
+	if (exfat_close(dev) != 0)
+		return 1;
+	printf("File system created successfully.\n");
+	return 0;
+}
diff --git a/exfat/mkfs/mkexfat.c b/exfat/mkfs/mkexfat.c
new file mode 100644
index 0000000..0929062
--- /dev/null
+++ b/exfat/mkfs/mkexfat.c
@@ -0,0 +1,162 @@
+/*
+	mkexfat.c (22.04.12)
+	FS creation engine.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mkexfat.h"
+#include <sys/types.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+static int check_size(loff_t volume_size)
+{
+	const struct fs_object** pp;
+	loff_t position = 0;
+
+	for (pp = objects; *pp; pp++)
+	{
+		position = ROUND_UP(position, (*pp)->get_alignment());
+		position += (*pp)->get_size();
+	}
+
+	if (position > volume_size)
+	{
+		struct exfat_human_bytes vhb;
+
+		exfat_humanize_bytes(volume_size, &vhb);
+		exfat_error("too small device (%"PRIu64" %s)", vhb.value, vhb.unit);
+		return 1;
+	}
+
+	return 0;
+
+}
+
+static int erase_object(struct exfat_dev* dev, const void* block,
+		size_t block_size, loff_t start, loff_t size)
+{
+	const loff_t block_count = DIV_ROUND_UP(size, block_size);
+	loff_t i;
+
+	if (exfat_seek(dev, start, SEEK_SET) == (loff_t) -1)
+	{
+		exfat_error("seek to 0x%"PRIx64" failed", start);
+		return 1;
+	}
+	for (i = 0; i < size; i += block_size)
+	{
+		if (exfat_write(dev, block, MIN(size - i, block_size)) < 0)
+		{
+			exfat_error("failed to erase block %"PRIu64"/%"PRIu64
+					" at 0x%"PRIx64, i + 1, block_count, start);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int erase(struct exfat_dev* dev)
+{
+	const struct fs_object** pp;
+	loff_t position = 0;
+	const size_t block_size = 1024 * 1024;
+	void* block = malloc(block_size);
+
+	if (block == NULL)
+	{
+		exfat_error("failed to allocate erase block of %zu bytes", block_size);
+		return 1;
+	}
+	memset(block, 0, block_size);
+
+	for (pp = objects; *pp; pp++)
+	{
+		position = ROUND_UP(position, (*pp)->get_alignment());
+		if (erase_object(dev, block, block_size, position,
+				(*pp)->get_size()) != 0)
+		{
+			free(block);
+			return 1;
+		}
+		position += (*pp)->get_size();
+	}
+
+	free(block);
+	return 0;
+}
+
+static int create(struct exfat_dev* dev)
+{
+	const struct fs_object** pp;
+	loff_t position = 0;
+
+	for (pp = objects; *pp; pp++)
+	{
+		position = ROUND_UP(position, (*pp)->get_alignment());
+		if (exfat_seek(dev, position, SEEK_SET) == (loff_t) -1)
+		{
+			exfat_error("seek to 0x%"PRIx64" failed", position);
+			return 1;
+		}
+		if ((*pp)->write(dev) != 0)
+			return 1;
+		position += (*pp)->get_size();
+	}
+	return 0;
+}
+
+int mkfs(struct exfat_dev* dev, loff_t volume_size)
+{
+	if (check_size(volume_size) != 0)
+		return 1;
+
+	fputs("Creating... ", stdout);
+	fflush(stdout);
+	if (erase(dev) != 0)
+		return 1;
+	if (create(dev) != 0)
+		return 1;
+	puts("done.");
+
+	fputs("Flushing... ", stdout);
+	fflush(stdout);
+	if (exfat_fsync(dev) != 0)
+		return 1;
+	puts("done.");
+
+	return 0;
+}
+
+loff_t get_position(const struct fs_object* object)
+{
+	const struct fs_object** pp;
+	loff_t position = 0;
+
+	for (pp = objects; *pp; pp++)
+	{
+		position = ROUND_UP(position, (*pp)->get_alignment());
+		if (*pp == object)
+			return position;
+		position += (*pp)->get_size();
+	}
+	exfat_bug("unknown object");
+}
diff --git a/exfat/mkfs/mkexfat.h b/exfat/mkfs/mkexfat.h
new file mode 100644
index 0000000..6e15c59
--- /dev/null
+++ b/exfat/mkfs/mkexfat.h
@@ -0,0 +1,49 @@
+/*
+	mkexfat.h (09.11.10)
+	FS creation engine.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef MKFS_MKEXFAT_H_INCLUDED
+#define MKFS_MKEXFAT_H_INCLUDED
+
+#include <exfat.h>
+
+struct fs_object
+{
+	loff_t (*get_alignment)(void);
+	loff_t (*get_size)(void);
+	int (*write)(struct exfat_dev* dev);
+};
+
+extern const struct fs_object* objects[];
+
+int get_sector_bits(void);
+int get_spc_bits(void);
+loff_t get_volume_size(void);
+const le16_t* get_volume_label(void);
+uint32_t get_volume_serial(void);
+uint64_t get_first_sector(void);
+int get_sector_size(void);
+int get_cluster_size(void);
+
+int mkfs(struct exfat_dev* dev, loff_t volume_size);
+loff_t get_position(const struct fs_object* object);
+
+#endif /* ifndef MKFS_MKEXFAT_H_INCLUDED */
diff --git a/exfat/mkfs/mkexfatfs.8 b/exfat/mkfs/mkexfatfs.8
new file mode 100644
index 0000000..e339468
--- /dev/null
+++ b/exfat/mkfs/mkexfatfs.8
@@ -0,0 +1,71 @@
+.\" Copyright (C) 2011-2015  Andrew Nayenko
+.\"
+.TH MKEXFATFS 8 "January 2011"
+.SH NAME
+.B mkexfatfs
+\- create an exFAT file system
+.SH SYNOPSIS
+.B mkexfatfs
+[
+.B \-i
+.I volume-id
+]
+[
+.B \-n
+.I volume-name
+]
+[
+.B \-p
+.I partition-first-sector
+]
+[
+.B \-s
+.I sectors-per-cluster
+]
+[
+.B \-V
+]
+.I device
+
+.SH DESCRIPTION
+.B mkexfatfs
+creates an exFAT file system on a block device.
+.I device
+is a special file corresponding to the partition on the device. Note that if
+this is an MBR partition then the file system type should be set to 0x07
+(NTFS/exFAT) otherwise other operating systems may refuse to mount the
+file system.
+
+.SH OPTIONS
+Command line options available:
+.TP
+.BI \-i " volume-id"
+A 32-bit hexadecimal number. By default a value based on current time is set.
+.TP
+.BI \-n " volume-name"
+Volume name (label), up to 15 characters. By default no label is set.
+.TP
+.BI \-p " partition-first-sector"
+First sector of the partition starting from the beginning of the whole disk.
+exFAT super block has a field for this value but in fact it's optional and
+does not affect anything. Default is 0.
+.TP
+.BI \-s " sectors-per-cluster"
+Number of physical sectors per cluster (cluster is an allocation unit in
+exFAT). Must be a power of 2, i.e. 1, 2, 4, 8, etc. Cluster size can not
+exceed 32 MB. Default cluster sizes are:
+4 KB if volume size is less than 256 MB,
+32 KB if volume size is from 256 MB to 32 GB,
+128 KB if volume size is 32 GB or larger.
+.TP
+.BI \-V
+Print version and copyright.
+
+.SH EXIT CODES
+Zero is returned on successful creation. Any other code means an error.
+
+.SH AUTHOR
+Andrew Nayenko
+
+.SH SEE ALSO
+.BR mkfs (8), fdisk (8)
diff --git a/exfat/mkfs/rootdir.c b/exfat/mkfs/rootdir.c
new file mode 100644
index 0000000..33ac3f9
--- /dev/null
+++ b/exfat/mkfs/rootdir.c
@@ -0,0 +1,102 @@
+/*
+	rootdir.c (09.11.10)
+	Root directory creation code.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "rootdir.h"
+#include "uct.h"
+#include "cbm.h"
+#include "uctc.h"
+#include <string.h>
+
+static loff_t rootdir_alignment(void)
+{
+	return get_cluster_size();
+}
+
+static loff_t rootdir_size(void)
+{
+	return get_cluster_size();
+}
+
+static void init_label_entry(struct exfat_entry_label* label_entry)
+{
+	memset(label_entry, 0, sizeof(struct exfat_entry_label));
+	label_entry->type = EXFAT_ENTRY_LABEL ^ EXFAT_ENTRY_VALID;
+
+	if (utf16_length(get_volume_label()) == 0)
+		return;
+
+	memcpy(label_entry->name, get_volume_label(),
+			EXFAT_ENAME_MAX * sizeof(le16_t));
+	label_entry->length = utf16_length(get_volume_label());
+	label_entry->type |= EXFAT_ENTRY_VALID;
+}
+
+static void init_bitmap_entry(struct exfat_entry_bitmap* bitmap_entry)
+{
+	memset(bitmap_entry, 0, sizeof(struct exfat_entry_bitmap));
+	bitmap_entry->type = EXFAT_ENTRY_BITMAP;
+	bitmap_entry->start_cluster = cpu_to_le32(EXFAT_FIRST_DATA_CLUSTER);
+	bitmap_entry->size = cpu_to_le64(cbm.get_size());
+}
+
+static void init_upcase_entry(struct exfat_entry_upcase* upcase_entry)
+{
+	size_t i;
+	uint32_t sum = 0;
+
+	for (i = 0; i < sizeof(upcase_table); i++)
+		sum = ((sum << 31) | (sum >> 1)) + upcase_table[i];
+
+	memset(upcase_entry, 0, sizeof(struct exfat_entry_upcase));
+	upcase_entry->type = EXFAT_ENTRY_UPCASE;
+	upcase_entry->checksum = cpu_to_le32(sum);
+	upcase_entry->start_cluster = cpu_to_le32(
+			(get_position(&uct) - get_position(&cbm)) / get_cluster_size() +
+			EXFAT_FIRST_DATA_CLUSTER);
+	upcase_entry->size = cpu_to_le64(sizeof(upcase_table));
+}
+
+static int rootdir_write(struct exfat_dev* dev)
+{
+	struct exfat_entry_label label_entry;
+	struct exfat_entry_bitmap bitmap_entry;
+	struct exfat_entry_upcase upcase_entry;
+
+	init_label_entry(&label_entry);
+	init_bitmap_entry(&bitmap_entry);
+	init_upcase_entry(&upcase_entry);
+
+	if (exfat_write(dev, &label_entry, sizeof(struct exfat_entry)) < 0)
+		return 1;
+	if (exfat_write(dev, &bitmap_entry, sizeof(struct exfat_entry)) < 0)
+		return 1;
+	if (exfat_write(dev, &upcase_entry, sizeof(struct exfat_entry)) < 0)
+		return 1;
+	return 0;
+}
+
+const struct fs_object rootdir =
+{
+	.get_alignment = rootdir_alignment,
+	.get_size = rootdir_size,
+	.write = rootdir_write,
+};
diff --git a/exfat/mkfs/rootdir.h b/exfat/mkfs/rootdir.h
new file mode 100644
index 0000000..56db5b0
--- /dev/null
+++ b/exfat/mkfs/rootdir.h
@@ -0,0 +1,30 @@
+/*
+	rootdir.h (09.11.10)
+	Root directory creation code.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef MKFS_ROOTDIR_H_INCLUDED
+#define MKFS_ROOTDIR_H_INCLUDED
+
+#include "mkexfat.h"
+
+extern const struct fs_object rootdir;
+
+#endif /* ifndef MKFS_ROOTDIR_H_INCLUDED */
diff --git a/exfat/mkfs/uct.c b/exfat/mkfs/uct.c
new file mode 100644
index 0000000..14fa56f
--- /dev/null
+++ b/exfat/mkfs/uct.c
@@ -0,0 +1,52 @@
+/*
+	uct.c (09.11.10)
+	Upper Case Table creation code.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "uct.h"
+#include "uctc.h"
+
+static loff_t uct_alignment(void)
+{
+	return get_cluster_size();
+}
+
+static loff_t uct_size(void)
+{
+	return sizeof(upcase_table);
+}
+
+static int uct_write(struct exfat_dev* dev)
+{
+	if (exfat_write(dev, upcase_table, sizeof(upcase_table)) < 0)
+	{
+		exfat_error("failed to write upcase table of %zu bytes",
+				sizeof(upcase_table));
+		return 1;
+	}
+	return 0;
+}
+
+const struct fs_object uct =
+{
+	.get_alignment = uct_alignment,
+	.get_size = uct_size,
+	.write = uct_write,
+};
diff --git a/exfat/mkfs/uct.h b/exfat/mkfs/uct.h
new file mode 100644
index 0000000..3a08db9
--- /dev/null
+++ b/exfat/mkfs/uct.h
@@ -0,0 +1,30 @@
+/*
+	uct.h (09.11.10)
+	Upper Case Table creation code.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef MKFS_UCT_H_INCLUDED
+#define MKFS_UCT_H_INCLUDED
+
+#include "mkexfat.h"
+
+extern const struct fs_object uct;
+
+#endif /* ifndef MKFS_UCT_H_INCLUDED */
diff --git a/exfat/mkfs/uctc.c b/exfat/mkfs/uctc.c
new file mode 100644
index 0000000..5849f37
--- /dev/null
+++ b/exfat/mkfs/uctc.c
@@ -0,0 +1,757 @@
+/*
+	uctc.c (30.04.12)
+	Upper Case Table contents.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "uctc.h"
+
+uint8_t upcase_table[5836] =
+{
+	0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00,
+	0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00,
+	0x08, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00,
+	0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x0f, 0x00,
+	0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x00,
+	0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x17, 0x00,
+	0x18, 0x00, 0x19, 0x00, 0x1a, 0x00, 0x1b, 0x00,
+	0x1c, 0x00, 0x1d, 0x00, 0x1e, 0x00, 0x1f, 0x00,
+	0x20, 0x00, 0x21, 0x00, 0x22, 0x00, 0x23, 0x00,
+	0x24, 0x00, 0x25, 0x00, 0x26, 0x00, 0x27, 0x00,
+	0x28, 0x00, 0x29, 0x00, 0x2a, 0x00, 0x2b, 0x00,
+	0x2c, 0x00, 0x2d, 0x00, 0x2e, 0x00, 0x2f, 0x00,
+	0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00,
+	0x34, 0x00, 0x35, 0x00, 0x36, 0x00, 0x37, 0x00,
+	0x38, 0x00, 0x39, 0x00, 0x3a, 0x00, 0x3b, 0x00,
+	0x3c, 0x00, 0x3d, 0x00, 0x3e, 0x00, 0x3f, 0x00,
+	0x40, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00,
+	0x44, 0x00, 0x45, 0x00, 0x46, 0x00, 0x47, 0x00,
+	0x48, 0x00, 0x49, 0x00, 0x4a, 0x00, 0x4b, 0x00,
+	0x4c, 0x00, 0x4d, 0x00, 0x4e, 0x00, 0x4f, 0x00,
+	0x50, 0x00, 0x51, 0x00, 0x52, 0x00, 0x53, 0x00,
+	0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57, 0x00,
+	0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, 0x5b, 0x00,
+	0x5c, 0x00, 0x5d, 0x00, 0x5e, 0x00, 0x5f, 0x00,
+	0x60, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00,
+	0x44, 0x00, 0x45, 0x00, 0x46, 0x00, 0x47, 0x00,
+	0x48, 0x00, 0x49, 0x00, 0x4a, 0x00, 0x4b, 0x00,
+	0x4c, 0x00, 0x4d, 0x00, 0x4e, 0x00, 0x4f, 0x00,
+	0x50, 0x00, 0x51, 0x00, 0x52, 0x00, 0x53, 0x00,
+	0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57, 0x00,
+	0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, 0x7b, 0x00,
+	0x7c, 0x00, 0x7d, 0x00, 0x7e, 0x00, 0x7f, 0x00,
+	0x80, 0x00, 0x81, 0x00, 0x82, 0x00, 0x83, 0x00,
+	0x84, 0x00, 0x85, 0x00, 0x86, 0x00, 0x87, 0x00,
+	0x88, 0x00, 0x89, 0x00, 0x8a, 0x00, 0x8b, 0x00,
+	0x8c, 0x00, 0x8d, 0x00, 0x8e, 0x00, 0x8f, 0x00,
+	0x90, 0x00, 0x91, 0x00, 0x92, 0x00, 0x93, 0x00,
+	0x94, 0x00, 0x95, 0x00, 0x96, 0x00, 0x97, 0x00,
+	0x98, 0x00, 0x99, 0x00, 0x9a, 0x00, 0x9b, 0x00,
+	0x9c, 0x00, 0x9d, 0x00, 0x9e, 0x00, 0x9f, 0x00,
+	0xa0, 0x00, 0xa1, 0x00, 0xa2, 0x00, 0xa3, 0x00,
+	0xa4, 0x00, 0xa5, 0x00, 0xa6, 0x00, 0xa7, 0x00,
+	0xa8, 0x00, 0xa9, 0x00, 0xaa, 0x00, 0xab, 0x00,
+	0xac, 0x00, 0xad, 0x00, 0xae, 0x00, 0xaf, 0x00,
+	0xb0, 0x00, 0xb1, 0x00, 0xb2, 0x00, 0xb3, 0x00,
+	0xb4, 0x00, 0xb5, 0x00, 0xb6, 0x00, 0xb7, 0x00,
+	0xb8, 0x00, 0xb9, 0x00, 0xba, 0x00, 0xbb, 0x00,
+	0xbc, 0x00, 0xbd, 0x00, 0xbe, 0x00, 0xbf, 0x00,
+	0xc0, 0x00, 0xc1, 0x00, 0xc2, 0x00, 0xc3, 0x00,
+	0xc4, 0x00, 0xc5, 0x00, 0xc6, 0x00, 0xc7, 0x00,
+	0xc8, 0x00, 0xc9, 0x00, 0xca, 0x00, 0xcb, 0x00,
+	0xcc, 0x00, 0xcd, 0x00, 0xce, 0x00, 0xcf, 0x00,
+	0xd0, 0x00, 0xd1, 0x00, 0xd2, 0x00, 0xd3, 0x00,
+	0xd4, 0x00, 0xd5, 0x00, 0xd6, 0x00, 0xd7, 0x00,
+	0xd8, 0x00, 0xd9, 0x00, 0xda, 0x00, 0xdb, 0x00,
+	0xdc, 0x00, 0xdd, 0x00, 0xde, 0x00, 0xdf, 0x00,
+	0xc0, 0x00, 0xc1, 0x00, 0xc2, 0x00, 0xc3, 0x00,
+	0xc4, 0x00, 0xc5, 0x00, 0xc6, 0x00, 0xc7, 0x00,
+	0xc8, 0x00, 0xc9, 0x00, 0xca, 0x00, 0xcb, 0x00,
+	0xcc, 0x00, 0xcd, 0x00, 0xce, 0x00, 0xcf, 0x00,
+	0xd0, 0x00, 0xd1, 0x00, 0xd2, 0x00, 0xd3, 0x00,
+	0xd4, 0x00, 0xd5, 0x00, 0xd6, 0x00, 0xf7, 0x00,
+	0xd8, 0x00, 0xd9, 0x00, 0xda, 0x00, 0xdb, 0x00,
+	0xdc, 0x00, 0xdd, 0x00, 0xde, 0x00, 0x78, 0x01,
+	0x00, 0x01, 0x00, 0x01, 0x02, 0x01, 0x02, 0x01,
+	0x04, 0x01, 0x04, 0x01, 0x06, 0x01, 0x06, 0x01,
+	0x08, 0x01, 0x08, 0x01, 0x0a, 0x01, 0x0a, 0x01,
+	0x0c, 0x01, 0x0c, 0x01, 0x0e, 0x01, 0x0e, 0x01,
+	0x10, 0x01, 0x10, 0x01, 0x12, 0x01, 0x12, 0x01,
+	0x14, 0x01, 0x14, 0x01, 0x16, 0x01, 0x16, 0x01,
+	0x18, 0x01, 0x18, 0x01, 0x1a, 0x01, 0x1a, 0x01,
+	0x1c, 0x01, 0x1c, 0x01, 0x1e, 0x01, 0x1e, 0x01,
+	0x20, 0x01, 0x20, 0x01, 0x22, 0x01, 0x22, 0x01,
+	0x24, 0x01, 0x24, 0x01, 0x26, 0x01, 0x26, 0x01,
+	0x28, 0x01, 0x28, 0x01, 0x2a, 0x01, 0x2a, 0x01,
+	0x2c, 0x01, 0x2c, 0x01, 0x2e, 0x01, 0x2e, 0x01,
+	0x30, 0x01, 0x31, 0x01, 0x32, 0x01, 0x32, 0x01,
+	0x34, 0x01, 0x34, 0x01, 0x36, 0x01, 0x36, 0x01,
+	0x38, 0x01, 0x39, 0x01, 0x39, 0x01, 0x3b, 0x01,
+	0x3b, 0x01, 0x3d, 0x01, 0x3d, 0x01, 0x3f, 0x01,
+	0x3f, 0x01, 0x41, 0x01, 0x41, 0x01, 0x43, 0x01,
+	0x43, 0x01, 0x45, 0x01, 0x45, 0x01, 0x47, 0x01,
+	0x47, 0x01, 0x49, 0x01, 0x4a, 0x01, 0x4a, 0x01,
+	0x4c, 0x01, 0x4c, 0x01, 0x4e, 0x01, 0x4e, 0x01,
+	0x50, 0x01, 0x50, 0x01, 0x52, 0x01, 0x52, 0x01,
+	0x54, 0x01, 0x54, 0x01, 0x56, 0x01, 0x56, 0x01,
+	0x58, 0x01, 0x58, 0x01, 0x5a, 0x01, 0x5a, 0x01,
+	0x5c, 0x01, 0x5c, 0x01, 0x5e, 0x01, 0x5e, 0x01,
+	0x60, 0x01, 0x60, 0x01, 0x62, 0x01, 0x62, 0x01,
+	0x64, 0x01, 0x64, 0x01, 0x66, 0x01, 0x66, 0x01,
+	0x68, 0x01, 0x68, 0x01, 0x6a, 0x01, 0x6a, 0x01,
+	0x6c, 0x01, 0x6c, 0x01, 0x6e, 0x01, 0x6e, 0x01,
+	0x70, 0x01, 0x70, 0x01, 0x72, 0x01, 0x72, 0x01,
+	0x74, 0x01, 0x74, 0x01, 0x76, 0x01, 0x76, 0x01,
+	0x78, 0x01, 0x79, 0x01, 0x79, 0x01, 0x7b, 0x01,
+	0x7b, 0x01, 0x7d, 0x01, 0x7d, 0x01, 0x7f, 0x01,
+	0x43, 0x02, 0x81, 0x01, 0x82, 0x01, 0x82, 0x01,
+	0x84, 0x01, 0x84, 0x01, 0x86, 0x01, 0x87, 0x01,
+	0x87, 0x01, 0x89, 0x01, 0x8a, 0x01, 0x8b, 0x01,
+	0x8b, 0x01, 0x8d, 0x01, 0x8e, 0x01, 0x8f, 0x01,
+	0x90, 0x01, 0x91, 0x01, 0x91, 0x01, 0x93, 0x01,
+	0x94, 0x01, 0xf6, 0x01, 0x96, 0x01, 0x97, 0x01,
+	0x98, 0x01, 0x98, 0x01, 0x3d, 0x02, 0x9b, 0x01,
+	0x9c, 0x01, 0x9d, 0x01, 0x20, 0x02, 0x9f, 0x01,
+	0xa0, 0x01, 0xa0, 0x01, 0xa2, 0x01, 0xa2, 0x01,
+	0xa4, 0x01, 0xa4, 0x01, 0xa6, 0x01, 0xa7, 0x01,
+	0xa7, 0x01, 0xa9, 0x01, 0xaa, 0x01, 0xab, 0x01,
+	0xac, 0x01, 0xac, 0x01, 0xae, 0x01, 0xaf, 0x01,
+	0xaf, 0x01, 0xb1, 0x01, 0xb2, 0x01, 0xb3, 0x01,
+	0xb3, 0x01, 0xb5, 0x01, 0xb5, 0x01, 0xb7, 0x01,
+	0xb8, 0x01, 0xb8, 0x01, 0xba, 0x01, 0xbb, 0x01,
+	0xbc, 0x01, 0xbc, 0x01, 0xbe, 0x01, 0xf7, 0x01,
+	0xc0, 0x01, 0xc1, 0x01, 0xc2, 0x01, 0xc3, 0x01,
+	0xc4, 0x01, 0xc5, 0x01, 0xc4, 0x01, 0xc7, 0x01,
+	0xc8, 0x01, 0xc7, 0x01, 0xca, 0x01, 0xcb, 0x01,
+	0xca, 0x01, 0xcd, 0x01, 0xcd, 0x01, 0xcf, 0x01,
+	0xcf, 0x01, 0xd1, 0x01, 0xd1, 0x01, 0xd3, 0x01,
+	0xd3, 0x01, 0xd5, 0x01, 0xd5, 0x01, 0xd7, 0x01,
+	0xd7, 0x01, 0xd9, 0x01, 0xd9, 0x01, 0xdb, 0x01,
+	0xdb, 0x01, 0x8e, 0x01, 0xde, 0x01, 0xde, 0x01,
+	0xe0, 0x01, 0xe0, 0x01, 0xe2, 0x01, 0xe2, 0x01,
+	0xe4, 0x01, 0xe4, 0x01, 0xe6, 0x01, 0xe6, 0x01,
+	0xe8, 0x01, 0xe8, 0x01, 0xea, 0x01, 0xea, 0x01,
+	0xec, 0x01, 0xec, 0x01, 0xee, 0x01, 0xee, 0x01,
+	0xf0, 0x01, 0xf1, 0x01, 0xf2, 0x01, 0xf1, 0x01,
+	0xf4, 0x01, 0xf4, 0x01, 0xf6, 0x01, 0xf7, 0x01,
+	0xf8, 0x01, 0xf8, 0x01, 0xfa, 0x01, 0xfa, 0x01,
+	0xfc, 0x01, 0xfc, 0x01, 0xfe, 0x01, 0xfe, 0x01,
+	0x00, 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+	0x04, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x02,
+	0x08, 0x02, 0x08, 0x02, 0x0a, 0x02, 0x0a, 0x02,
+	0x0c, 0x02, 0x0c, 0x02, 0x0e, 0x02, 0x0e, 0x02,
+	0x10, 0x02, 0x10, 0x02, 0x12, 0x02, 0x12, 0x02,
+	0x14, 0x02, 0x14, 0x02, 0x16, 0x02, 0x16, 0x02,
+	0x18, 0x02, 0x18, 0x02, 0x1a, 0x02, 0x1a, 0x02,
+	0x1c, 0x02, 0x1c, 0x02, 0x1e, 0x02, 0x1e, 0x02,
+	0x20, 0x02, 0x21, 0x02, 0x22, 0x02, 0x22, 0x02,
+	0x24, 0x02, 0x24, 0x02, 0x26, 0x02, 0x26, 0x02,
+	0x28, 0x02, 0x28, 0x02, 0x2a, 0x02, 0x2a, 0x02,
+	0x2c, 0x02, 0x2c, 0x02, 0x2e, 0x02, 0x2e, 0x02,
+	0x30, 0x02, 0x30, 0x02, 0x32, 0x02, 0x32, 0x02,
+	0x34, 0x02, 0x35, 0x02, 0x36, 0x02, 0x37, 0x02,
+	0x38, 0x02, 0x39, 0x02, 0x65, 0x2c, 0x3b, 0x02,
+	0x3b, 0x02, 0x3d, 0x02, 0x66, 0x2c, 0x3f, 0x02,
+	0x40, 0x02, 0x41, 0x02, 0x41, 0x02, 0x43, 0x02,
+	0x44, 0x02, 0x45, 0x02, 0x46, 0x02, 0x46, 0x02,
+	0x48, 0x02, 0x48, 0x02, 0x4a, 0x02, 0x4a, 0x02,
+	0x4c, 0x02, 0x4c, 0x02, 0x4e, 0x02, 0x4e, 0x02,
+	0x50, 0x02, 0x51, 0x02, 0x52, 0x02, 0x81, 0x01,
+	0x86, 0x01, 0x55, 0x02, 0x89, 0x01, 0x8a, 0x01,
+	0x58, 0x02, 0x8f, 0x01, 0x5a, 0x02, 0x90, 0x01,
+	0x5c, 0x02, 0x5d, 0x02, 0x5e, 0x02, 0x5f, 0x02,
+	0x93, 0x01, 0x61, 0x02, 0x62, 0x02, 0x94, 0x01,
+	0x64, 0x02, 0x65, 0x02, 0x66, 0x02, 0x67, 0x02,
+	0x97, 0x01, 0x96, 0x01, 0x6a, 0x02, 0x62, 0x2c,
+	0x6c, 0x02, 0x6d, 0x02, 0x6e, 0x02, 0x9c, 0x01,
+	0x70, 0x02, 0x71, 0x02, 0x9d, 0x01, 0x73, 0x02,
+	0x74, 0x02, 0x9f, 0x01, 0x76, 0x02, 0x77, 0x02,
+	0x78, 0x02, 0x79, 0x02, 0x7a, 0x02, 0x7b, 0x02,
+	0x7c, 0x02, 0x64, 0x2c, 0x7e, 0x02, 0x7f, 0x02,
+	0xa6, 0x01, 0x81, 0x02, 0x82, 0x02, 0xa9, 0x01,
+	0x84, 0x02, 0x85, 0x02, 0x86, 0x02, 0x87, 0x02,
+	0xae, 0x01, 0x44, 0x02, 0xb1, 0x01, 0xb2, 0x01,
+	0x45, 0x02, 0x8d, 0x02, 0x8e, 0x02, 0x8f, 0x02,
+	0x90, 0x02, 0x91, 0x02, 0xb7, 0x01, 0x93, 0x02,
+	0x94, 0x02, 0x95, 0x02, 0x96, 0x02, 0x97, 0x02,
+	0x98, 0x02, 0x99, 0x02, 0x9a, 0x02, 0x9b, 0x02,
+	0x9c, 0x02, 0x9d, 0x02, 0x9e, 0x02, 0x9f, 0x02,
+	0xa0, 0x02, 0xa1, 0x02, 0xa2, 0x02, 0xa3, 0x02,
+	0xa4, 0x02, 0xa5, 0x02, 0xa6, 0x02, 0xa7, 0x02,
+	0xa8, 0x02, 0xa9, 0x02, 0xaa, 0x02, 0xab, 0x02,
+	0xac, 0x02, 0xad, 0x02, 0xae, 0x02, 0xaf, 0x02,
+	0xb0, 0x02, 0xb1, 0x02, 0xb2, 0x02, 0xb3, 0x02,
+	0xb4, 0x02, 0xb5, 0x02, 0xb6, 0x02, 0xb7, 0x02,
+	0xb8, 0x02, 0xb9, 0x02, 0xba, 0x02, 0xbb, 0x02,
+	0xbc, 0x02, 0xbd, 0x02, 0xbe, 0x02, 0xbf, 0x02,
+	0xc0, 0x02, 0xc1, 0x02, 0xc2, 0x02, 0xc3, 0x02,
+	0xc4, 0x02, 0xc5, 0x02, 0xc6, 0x02, 0xc7, 0x02,
+	0xc8, 0x02, 0xc9, 0x02, 0xca, 0x02, 0xcb, 0x02,
+	0xcc, 0x02, 0xcd, 0x02, 0xce, 0x02, 0xcf, 0x02,
+	0xd0, 0x02, 0xd1, 0x02, 0xd2, 0x02, 0xd3, 0x02,
+	0xd4, 0x02, 0xd5, 0x02, 0xd6, 0x02, 0xd7, 0x02,
+	0xd8, 0x02, 0xd9, 0x02, 0xda, 0x02, 0xdb, 0x02,
+	0xdc, 0x02, 0xdd, 0x02, 0xde, 0x02, 0xdf, 0x02,
+	0xe0, 0x02, 0xe1, 0x02, 0xe2, 0x02, 0xe3, 0x02,
+	0xe4, 0x02, 0xe5, 0x02, 0xe6, 0x02, 0xe7, 0x02,
+	0xe8, 0x02, 0xe9, 0x02, 0xea, 0x02, 0xeb, 0x02,
+	0xec, 0x02, 0xed, 0x02, 0xee, 0x02, 0xef, 0x02,
+	0xf0, 0x02, 0xf1, 0x02, 0xf2, 0x02, 0xf3, 0x02,
+	0xf4, 0x02, 0xf5, 0x02, 0xf6, 0x02, 0xf7, 0x02,
+	0xf8, 0x02, 0xf9, 0x02, 0xfa, 0x02, 0xfb, 0x02,
+	0xfc, 0x02, 0xfd, 0x02, 0xfe, 0x02, 0xff, 0x02,
+	0x00, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, 0x03,
+	0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x07, 0x03,
+	0x08, 0x03, 0x09, 0x03, 0x0a, 0x03, 0x0b, 0x03,
+	0x0c, 0x03, 0x0d, 0x03, 0x0e, 0x03, 0x0f, 0x03,
+	0x10, 0x03, 0x11, 0x03, 0x12, 0x03, 0x13, 0x03,
+	0x14, 0x03, 0x15, 0x03, 0x16, 0x03, 0x17, 0x03,
+	0x18, 0x03, 0x19, 0x03, 0x1a, 0x03, 0x1b, 0x03,
+	0x1c, 0x03, 0x1d, 0x03, 0x1e, 0x03, 0x1f, 0x03,
+	0x20, 0x03, 0x21, 0x03, 0x22, 0x03, 0x23, 0x03,
+	0x24, 0x03, 0x25, 0x03, 0x26, 0x03, 0x27, 0x03,
+	0x28, 0x03, 0x29, 0x03, 0x2a, 0x03, 0x2b, 0x03,
+	0x2c, 0x03, 0x2d, 0x03, 0x2e, 0x03, 0x2f, 0x03,
+	0x30, 0x03, 0x31, 0x03, 0x32, 0x03, 0x33, 0x03,
+	0x34, 0x03, 0x35, 0x03, 0x36, 0x03, 0x37, 0x03,
+	0x38, 0x03, 0x39, 0x03, 0x3a, 0x03, 0x3b, 0x03,
+	0x3c, 0x03, 0x3d, 0x03, 0x3e, 0x03, 0x3f, 0x03,
+	0x40, 0x03, 0x41, 0x03, 0x42, 0x03, 0x43, 0x03,
+	0x44, 0x03, 0x45, 0x03, 0x46, 0x03, 0x47, 0x03,
+	0x48, 0x03, 0x49, 0x03, 0x4a, 0x03, 0x4b, 0x03,
+	0x4c, 0x03, 0x4d, 0x03, 0x4e, 0x03, 0x4f, 0x03,
+	0x50, 0x03, 0x51, 0x03, 0x52, 0x03, 0x53, 0x03,
+	0x54, 0x03, 0x55, 0x03, 0x56, 0x03, 0x57, 0x03,
+	0x58, 0x03, 0x59, 0x03, 0x5a, 0x03, 0x5b, 0x03,
+	0x5c, 0x03, 0x5d, 0x03, 0x5e, 0x03, 0x5f, 0x03,
+	0x60, 0x03, 0x61, 0x03, 0x62, 0x03, 0x63, 0x03,
+	0x64, 0x03, 0x65, 0x03, 0x66, 0x03, 0x67, 0x03,
+	0x68, 0x03, 0x69, 0x03, 0x6a, 0x03, 0x6b, 0x03,
+	0x6c, 0x03, 0x6d, 0x03, 0x6e, 0x03, 0x6f, 0x03,
+	0x70, 0x03, 0x71, 0x03, 0x72, 0x03, 0x73, 0x03,
+	0x74, 0x03, 0x75, 0x03, 0x76, 0x03, 0x77, 0x03,
+	0x78, 0x03, 0x79, 0x03, 0x7a, 0x03, 0xfd, 0x03,
+	0xfe, 0x03, 0xff, 0x03, 0x7e, 0x03, 0x7f, 0x03,
+	0x80, 0x03, 0x81, 0x03, 0x82, 0x03, 0x83, 0x03,
+	0x84, 0x03, 0x85, 0x03, 0x86, 0x03, 0x87, 0x03,
+	0x88, 0x03, 0x89, 0x03, 0x8a, 0x03, 0x8b, 0x03,
+	0x8c, 0x03, 0x8d, 0x03, 0x8e, 0x03, 0x8f, 0x03,
+	0x90, 0x03, 0x91, 0x03, 0x92, 0x03, 0x93, 0x03,
+	0x94, 0x03, 0x95, 0x03, 0x96, 0x03, 0x97, 0x03,
+	0x98, 0x03, 0x99, 0x03, 0x9a, 0x03, 0x9b, 0x03,
+	0x9c, 0x03, 0x9d, 0x03, 0x9e, 0x03, 0x9f, 0x03,
+	0xa0, 0x03, 0xa1, 0x03, 0xa2, 0x03, 0xa3, 0x03,
+	0xa4, 0x03, 0xa5, 0x03, 0xa6, 0x03, 0xa7, 0x03,
+	0xa8, 0x03, 0xa9, 0x03, 0xaa, 0x03, 0xab, 0x03,
+	0x86, 0x03, 0x88, 0x03, 0x89, 0x03, 0x8a, 0x03,
+	0xb0, 0x03, 0x91, 0x03, 0x92, 0x03, 0x93, 0x03,
+	0x94, 0x03, 0x95, 0x03, 0x96, 0x03, 0x97, 0x03,
+	0x98, 0x03, 0x99, 0x03, 0x9a, 0x03, 0x9b, 0x03,
+	0x9c, 0x03, 0x9d, 0x03, 0x9e, 0x03, 0x9f, 0x03,
+	0xa0, 0x03, 0xa1, 0x03, 0xa3, 0x03, 0xa3, 0x03,
+	0xa4, 0x03, 0xa5, 0x03, 0xa6, 0x03, 0xa7, 0x03,
+	0xa8, 0x03, 0xa9, 0x03, 0xaa, 0x03, 0xab, 0x03,
+	0x8c, 0x03, 0x8e, 0x03, 0x8f, 0x03, 0xcf, 0x03,
+	0xd0, 0x03, 0xd1, 0x03, 0xd2, 0x03, 0xd3, 0x03,
+	0xd4, 0x03, 0xd5, 0x03, 0xd6, 0x03, 0xd7, 0x03,
+	0xd8, 0x03, 0xd8, 0x03, 0xda, 0x03, 0xda, 0x03,
+	0xdc, 0x03, 0xdc, 0x03, 0xde, 0x03, 0xde, 0x03,
+	0xe0, 0x03, 0xe0, 0x03, 0xe2, 0x03, 0xe2, 0x03,
+	0xe4, 0x03, 0xe4, 0x03, 0xe6, 0x03, 0xe6, 0x03,
+	0xe8, 0x03, 0xe8, 0x03, 0xea, 0x03, 0xea, 0x03,
+	0xec, 0x03, 0xec, 0x03, 0xee, 0x03, 0xee, 0x03,
+	0xf0, 0x03, 0xf1, 0x03, 0xf9, 0x03, 0xf3, 0x03,
+	0xf4, 0x03, 0xf5, 0x03, 0xf6, 0x03, 0xf7, 0x03,
+	0xf7, 0x03, 0xf9, 0x03, 0xfa, 0x03, 0xfa, 0x03,
+	0xfc, 0x03, 0xfd, 0x03, 0xfe, 0x03, 0xff, 0x03,
+	0x00, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x04,
+	0x04, 0x04, 0x05, 0x04, 0x06, 0x04, 0x07, 0x04,
+	0x08, 0x04, 0x09, 0x04, 0x0a, 0x04, 0x0b, 0x04,
+	0x0c, 0x04, 0x0d, 0x04, 0x0e, 0x04, 0x0f, 0x04,
+	0x10, 0x04, 0x11, 0x04, 0x12, 0x04, 0x13, 0x04,
+	0x14, 0x04, 0x15, 0x04, 0x16, 0x04, 0x17, 0x04,
+	0x18, 0x04, 0x19, 0x04, 0x1a, 0x04, 0x1b, 0x04,
+	0x1c, 0x04, 0x1d, 0x04, 0x1e, 0x04, 0x1f, 0x04,
+	0x20, 0x04, 0x21, 0x04, 0x22, 0x04, 0x23, 0x04,
+	0x24, 0x04, 0x25, 0x04, 0x26, 0x04, 0x27, 0x04,
+	0x28, 0x04, 0x29, 0x04, 0x2a, 0x04, 0x2b, 0x04,
+	0x2c, 0x04, 0x2d, 0x04, 0x2e, 0x04, 0x2f, 0x04,
+	0x10, 0x04, 0x11, 0x04, 0x12, 0x04, 0x13, 0x04,
+	0x14, 0x04, 0x15, 0x04, 0x16, 0x04, 0x17, 0x04,
+	0x18, 0x04, 0x19, 0x04, 0x1a, 0x04, 0x1b, 0x04,
+	0x1c, 0x04, 0x1d, 0x04, 0x1e, 0x04, 0x1f, 0x04,
+	0x20, 0x04, 0x21, 0x04, 0x22, 0x04, 0x23, 0x04,
+	0x24, 0x04, 0x25, 0x04, 0x26, 0x04, 0x27, 0x04,
+	0x28, 0x04, 0x29, 0x04, 0x2a, 0x04, 0x2b, 0x04,
+	0x2c, 0x04, 0x2d, 0x04, 0x2e, 0x04, 0x2f, 0x04,
+	0x00, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x04,
+	0x04, 0x04, 0x05, 0x04, 0x06, 0x04, 0x07, 0x04,
+	0x08, 0x04, 0x09, 0x04, 0x0a, 0x04, 0x0b, 0x04,
+	0x0c, 0x04, 0x0d, 0x04, 0x0e, 0x04, 0x0f, 0x04,
+	0x60, 0x04, 0x60, 0x04, 0x62, 0x04, 0x62, 0x04,
+	0x64, 0x04, 0x64, 0x04, 0x66, 0x04, 0x66, 0x04,
+	0x68, 0x04, 0x68, 0x04, 0x6a, 0x04, 0x6a, 0x04,
+	0x6c, 0x04, 0x6c, 0x04, 0x6e, 0x04, 0x6e, 0x04,
+	0x70, 0x04, 0x70, 0x04, 0x72, 0x04, 0x72, 0x04,
+	0x74, 0x04, 0x74, 0x04, 0x76, 0x04, 0x76, 0x04,
+	0x78, 0x04, 0x78, 0x04, 0x7a, 0x04, 0x7a, 0x04,
+	0x7c, 0x04, 0x7c, 0x04, 0x7e, 0x04, 0x7e, 0x04,
+	0x80, 0x04, 0x80, 0x04, 0x82, 0x04, 0x83, 0x04,
+	0x84, 0x04, 0x85, 0x04, 0x86, 0x04, 0x87, 0x04,
+	0x88, 0x04, 0x89, 0x04, 0x8a, 0x04, 0x8a, 0x04,
+	0x8c, 0x04, 0x8c, 0x04, 0x8e, 0x04, 0x8e, 0x04,
+	0x90, 0x04, 0x90, 0x04, 0x92, 0x04, 0x92, 0x04,
+	0x94, 0x04, 0x94, 0x04, 0x96, 0x04, 0x96, 0x04,
+	0x98, 0x04, 0x98, 0x04, 0x9a, 0x04, 0x9a, 0x04,
+	0x9c, 0x04, 0x9c, 0x04, 0x9e, 0x04, 0x9e, 0x04,
+	0xa0, 0x04, 0xa0, 0x04, 0xa2, 0x04, 0xa2, 0x04,
+	0xa4, 0x04, 0xa4, 0x04, 0xa6, 0x04, 0xa6, 0x04,
+	0xa8, 0x04, 0xa8, 0x04, 0xaa, 0x04, 0xaa, 0x04,
+	0xac, 0x04, 0xac, 0x04, 0xae, 0x04, 0xae, 0x04,
+	0xb0, 0x04, 0xb0, 0x04, 0xb2, 0x04, 0xb2, 0x04,
+	0xb4, 0x04, 0xb4, 0x04, 0xb6, 0x04, 0xb6, 0x04,
+	0xb8, 0x04, 0xb8, 0x04, 0xba, 0x04, 0xba, 0x04,
+	0xbc, 0x04, 0xbc, 0x04, 0xbe, 0x04, 0xbe, 0x04,
+	0xc0, 0x04, 0xc1, 0x04, 0xc1, 0x04, 0xc3, 0x04,
+	0xc3, 0x04, 0xc5, 0x04, 0xc5, 0x04, 0xc7, 0x04,
+	0xc7, 0x04, 0xc9, 0x04, 0xc9, 0x04, 0xcb, 0x04,
+	0xcb, 0x04, 0xcd, 0x04, 0xcd, 0x04, 0xc0, 0x04,
+	0xd0, 0x04, 0xd0, 0x04, 0xd2, 0x04, 0xd2, 0x04,
+	0xd4, 0x04, 0xd4, 0x04, 0xd6, 0x04, 0xd6, 0x04,
+	0xd8, 0x04, 0xd8, 0x04, 0xda, 0x04, 0xda, 0x04,
+	0xdc, 0x04, 0xdc, 0x04, 0xde, 0x04, 0xde, 0x04,
+	0xe0, 0x04, 0xe0, 0x04, 0xe2, 0x04, 0xe2, 0x04,
+	0xe4, 0x04, 0xe4, 0x04, 0xe6, 0x04, 0xe6, 0x04,
+	0xe8, 0x04, 0xe8, 0x04, 0xea, 0x04, 0xea, 0x04,
+	0xec, 0x04, 0xec, 0x04, 0xee, 0x04, 0xee, 0x04,
+	0xf0, 0x04, 0xf0, 0x04, 0xf2, 0x04, 0xf2, 0x04,
+	0xf4, 0x04, 0xf4, 0x04, 0xf6, 0x04, 0xf6, 0x04,
+	0xf8, 0x04, 0xf8, 0x04, 0xfa, 0x04, 0xfa, 0x04,
+	0xfc, 0x04, 0xfc, 0x04, 0xfe, 0x04, 0xfe, 0x04,
+	0x00, 0x05, 0x00, 0x05, 0x02, 0x05, 0x02, 0x05,
+	0x04, 0x05, 0x04, 0x05, 0x06, 0x05, 0x06, 0x05,
+	0x08, 0x05, 0x08, 0x05, 0x0a, 0x05, 0x0a, 0x05,
+	0x0c, 0x05, 0x0c, 0x05, 0x0e, 0x05, 0x0e, 0x05,
+	0x10, 0x05, 0x10, 0x05, 0x12, 0x05, 0x12, 0x05,
+	0x14, 0x05, 0x15, 0x05, 0x16, 0x05, 0x17, 0x05,
+	0x18, 0x05, 0x19, 0x05, 0x1a, 0x05, 0x1b, 0x05,
+	0x1c, 0x05, 0x1d, 0x05, 0x1e, 0x05, 0x1f, 0x05,
+	0x20, 0x05, 0x21, 0x05, 0x22, 0x05, 0x23, 0x05,
+	0x24, 0x05, 0x25, 0x05, 0x26, 0x05, 0x27, 0x05,
+	0x28, 0x05, 0x29, 0x05, 0x2a, 0x05, 0x2b, 0x05,
+	0x2c, 0x05, 0x2d, 0x05, 0x2e, 0x05, 0x2f, 0x05,
+	0x30, 0x05, 0x31, 0x05, 0x32, 0x05, 0x33, 0x05,
+	0x34, 0x05, 0x35, 0x05, 0x36, 0x05, 0x37, 0x05,
+	0x38, 0x05, 0x39, 0x05, 0x3a, 0x05, 0x3b, 0x05,
+	0x3c, 0x05, 0x3d, 0x05, 0x3e, 0x05, 0x3f, 0x05,
+	0x40, 0x05, 0x41, 0x05, 0x42, 0x05, 0x43, 0x05,
+	0x44, 0x05, 0x45, 0x05, 0x46, 0x05, 0x47, 0x05,
+	0x48, 0x05, 0x49, 0x05, 0x4a, 0x05, 0x4b, 0x05,
+	0x4c, 0x05, 0x4d, 0x05, 0x4e, 0x05, 0x4f, 0x05,
+	0x50, 0x05, 0x51, 0x05, 0x52, 0x05, 0x53, 0x05,
+	0x54, 0x05, 0x55, 0x05, 0x56, 0x05, 0x57, 0x05,
+	0x58, 0x05, 0x59, 0x05, 0x5a, 0x05, 0x5b, 0x05,
+	0x5c, 0x05, 0x5d, 0x05, 0x5e, 0x05, 0x5f, 0x05,
+	0x60, 0x05, 0x31, 0x05, 0x32, 0x05, 0x33, 0x05,
+	0x34, 0x05, 0x35, 0x05, 0x36, 0x05, 0x37, 0x05,
+	0x38, 0x05, 0x39, 0x05, 0x3a, 0x05, 0x3b, 0x05,
+	0x3c, 0x05, 0x3d, 0x05, 0x3e, 0x05, 0x3f, 0x05,
+	0x40, 0x05, 0x41, 0x05, 0x42, 0x05, 0x43, 0x05,
+	0x44, 0x05, 0x45, 0x05, 0x46, 0x05, 0x47, 0x05,
+	0x48, 0x05, 0x49, 0x05, 0x4a, 0x05, 0x4b, 0x05,
+	0x4c, 0x05, 0x4d, 0x05, 0x4e, 0x05, 0x4f, 0x05,
+	0x50, 0x05, 0x51, 0x05, 0x52, 0x05, 0x53, 0x05,
+	0x54, 0x05, 0x55, 0x05, 0x56, 0x05, 0xff, 0xff,
+	0xf6, 0x17, 0x63, 0x2c, 0x7e, 0x1d, 0x7f, 0x1d,
+	0x80, 0x1d, 0x81, 0x1d, 0x82, 0x1d, 0x83, 0x1d,
+	0x84, 0x1d, 0x85, 0x1d, 0x86, 0x1d, 0x87, 0x1d,
+	0x88, 0x1d, 0x89, 0x1d, 0x8a, 0x1d, 0x8b, 0x1d,
+	0x8c, 0x1d, 0x8d, 0x1d, 0x8e, 0x1d, 0x8f, 0x1d,
+	0x90, 0x1d, 0x91, 0x1d, 0x92, 0x1d, 0x93, 0x1d,
+	0x94, 0x1d, 0x95, 0x1d, 0x96, 0x1d, 0x97, 0x1d,
+	0x98, 0x1d, 0x99, 0x1d, 0x9a, 0x1d, 0x9b, 0x1d,
+	0x9c, 0x1d, 0x9d, 0x1d, 0x9e, 0x1d, 0x9f, 0x1d,
+	0xa0, 0x1d, 0xa1, 0x1d, 0xa2, 0x1d, 0xa3, 0x1d,
+	0xa4, 0x1d, 0xa5, 0x1d, 0xa6, 0x1d, 0xa7, 0x1d,
+	0xa8, 0x1d, 0xa9, 0x1d, 0xaa, 0x1d, 0xab, 0x1d,
+	0xac, 0x1d, 0xad, 0x1d, 0xae, 0x1d, 0xaf, 0x1d,
+	0xb0, 0x1d, 0xb1, 0x1d, 0xb2, 0x1d, 0xb3, 0x1d,
+	0xb4, 0x1d, 0xb5, 0x1d, 0xb6, 0x1d, 0xb7, 0x1d,
+	0xb8, 0x1d, 0xb9, 0x1d, 0xba, 0x1d, 0xbb, 0x1d,
+	0xbc, 0x1d, 0xbd, 0x1d, 0xbe, 0x1d, 0xbf, 0x1d,
+	0xc0, 0x1d, 0xc1, 0x1d, 0xc2, 0x1d, 0xc3, 0x1d,
+	0xc4, 0x1d, 0xc5, 0x1d, 0xc6, 0x1d, 0xc7, 0x1d,
+	0xc8, 0x1d, 0xc9, 0x1d, 0xca, 0x1d, 0xcb, 0x1d,
+	0xcc, 0x1d, 0xcd, 0x1d, 0xce, 0x1d, 0xcf, 0x1d,
+	0xd0, 0x1d, 0xd1, 0x1d, 0xd2, 0x1d, 0xd3, 0x1d,
+	0xd4, 0x1d, 0xd5, 0x1d, 0xd6, 0x1d, 0xd7, 0x1d,
+	0xd8, 0x1d, 0xd9, 0x1d, 0xda, 0x1d, 0xdb, 0x1d,
+	0xdc, 0x1d, 0xdd, 0x1d, 0xde, 0x1d, 0xdf, 0x1d,
+	0xe0, 0x1d, 0xe1, 0x1d, 0xe2, 0x1d, 0xe3, 0x1d,
+	0xe4, 0x1d, 0xe5, 0x1d, 0xe6, 0x1d, 0xe7, 0x1d,
+	0xe8, 0x1d, 0xe9, 0x1d, 0xea, 0x1d, 0xeb, 0x1d,
+	0xec, 0x1d, 0xed, 0x1d, 0xee, 0x1d, 0xef, 0x1d,
+	0xf0, 0x1d, 0xf1, 0x1d, 0xf2, 0x1d, 0xf3, 0x1d,
+	0xf4, 0x1d, 0xf5, 0x1d, 0xf6, 0x1d, 0xf7, 0x1d,
+	0xf8, 0x1d, 0xf9, 0x1d, 0xfa, 0x1d, 0xfb, 0x1d,
+	0xfc, 0x1d, 0xfd, 0x1d, 0xfe, 0x1d, 0xff, 0x1d,
+	0x00, 0x1e, 0x00, 0x1e, 0x02, 0x1e, 0x02, 0x1e,
+	0x04, 0x1e, 0x04, 0x1e, 0x06, 0x1e, 0x06, 0x1e,
+	0x08, 0x1e, 0x08, 0x1e, 0x0a, 0x1e, 0x0a, 0x1e,
+	0x0c, 0x1e, 0x0c, 0x1e, 0x0e, 0x1e, 0x0e, 0x1e,
+	0x10, 0x1e, 0x10, 0x1e, 0x12, 0x1e, 0x12, 0x1e,
+	0x14, 0x1e, 0x14, 0x1e, 0x16, 0x1e, 0x16, 0x1e,
+	0x18, 0x1e, 0x18, 0x1e, 0x1a, 0x1e, 0x1a, 0x1e,
+	0x1c, 0x1e, 0x1c, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	0x20, 0x1e, 0x20, 0x1e, 0x22, 0x1e, 0x22, 0x1e,
+	0x24, 0x1e, 0x24, 0x1e, 0x26, 0x1e, 0x26, 0x1e,
+	0x28, 0x1e, 0x28, 0x1e, 0x2a, 0x1e, 0x2a, 0x1e,
+	0x2c, 0x1e, 0x2c, 0x1e, 0x2e, 0x1e, 0x2e, 0x1e,
+	0x30, 0x1e, 0x30, 0x1e, 0x32, 0x1e, 0x32, 0x1e,
+	0x34, 0x1e, 0x34, 0x1e, 0x36, 0x1e, 0x36, 0x1e,
+	0x38, 0x1e, 0x38, 0x1e, 0x3a, 0x1e, 0x3a, 0x1e,
+	0x3c, 0x1e, 0x3c, 0x1e, 0x3e, 0x1e, 0x3e, 0x1e,
+	0x40, 0x1e, 0x40, 0x1e, 0x42, 0x1e, 0x42, 0x1e,
+	0x44, 0x1e, 0x44, 0x1e, 0x46, 0x1e, 0x46, 0x1e,
+	0x48, 0x1e, 0x48, 0x1e, 0x4a, 0x1e, 0x4a, 0x1e,
+	0x4c, 0x1e, 0x4c, 0x1e, 0x4e, 0x1e, 0x4e, 0x1e,
+	0x50, 0x1e, 0x50, 0x1e, 0x52, 0x1e, 0x52, 0x1e,
+	0x54, 0x1e, 0x54, 0x1e, 0x56, 0x1e, 0x56, 0x1e,
+	0x58, 0x1e, 0x58, 0x1e, 0x5a, 0x1e, 0x5a, 0x1e,
+	0x5c, 0x1e, 0x5c, 0x1e, 0x5e, 0x1e, 0x5e, 0x1e,
+	0x60, 0x1e, 0x60, 0x1e, 0x62, 0x1e, 0x62, 0x1e,
+	0x64, 0x1e, 0x64, 0x1e, 0x66, 0x1e, 0x66, 0x1e,
+	0x68, 0x1e, 0x68, 0x1e, 0x6a, 0x1e, 0x6a, 0x1e,
+	0x6c, 0x1e, 0x6c, 0x1e, 0x6e, 0x1e, 0x6e, 0x1e,
+	0x70, 0x1e, 0x70, 0x1e, 0x72, 0x1e, 0x72, 0x1e,
+	0x74, 0x1e, 0x74, 0x1e, 0x76, 0x1e, 0x76, 0x1e,
+	0x78, 0x1e, 0x78, 0x1e, 0x7a, 0x1e, 0x7a, 0x1e,
+	0x7c, 0x1e, 0x7c, 0x1e, 0x7e, 0x1e, 0x7e, 0x1e,
+	0x80, 0x1e, 0x80, 0x1e, 0x82, 0x1e, 0x82, 0x1e,
+	0x84, 0x1e, 0x84, 0x1e, 0x86, 0x1e, 0x86, 0x1e,
+	0x88, 0x1e, 0x88, 0x1e, 0x8a, 0x1e, 0x8a, 0x1e,
+	0x8c, 0x1e, 0x8c, 0x1e, 0x8e, 0x1e, 0x8e, 0x1e,
+	0x90, 0x1e, 0x90, 0x1e, 0x92, 0x1e, 0x92, 0x1e,
+	0x94, 0x1e, 0x94, 0x1e, 0x96, 0x1e, 0x97, 0x1e,
+	0x98, 0x1e, 0x99, 0x1e, 0x9a, 0x1e, 0x9b, 0x1e,
+	0x9c, 0x1e, 0x9d, 0x1e, 0x9e, 0x1e, 0x9f, 0x1e,
+	0xa0, 0x1e, 0xa0, 0x1e, 0xa2, 0x1e, 0xa2, 0x1e,
+	0xa4, 0x1e, 0xa4, 0x1e, 0xa6, 0x1e, 0xa6, 0x1e,
+	0xa8, 0x1e, 0xa8, 0x1e, 0xaa, 0x1e, 0xaa, 0x1e,
+	0xac, 0x1e, 0xac, 0x1e, 0xae, 0x1e, 0xae, 0x1e,
+	0xb0, 0x1e, 0xb0, 0x1e, 0xb2, 0x1e, 0xb2, 0x1e,
+	0xb4, 0x1e, 0xb4, 0x1e, 0xb6, 0x1e, 0xb6, 0x1e,
+	0xb8, 0x1e, 0xb8, 0x1e, 0xba, 0x1e, 0xba, 0x1e,
+	0xbc, 0x1e, 0xbc, 0x1e, 0xbe, 0x1e, 0xbe, 0x1e,
+	0xc0, 0x1e, 0xc0, 0x1e, 0xc2, 0x1e, 0xc2, 0x1e,
+	0xc4, 0x1e, 0xc4, 0x1e, 0xc6, 0x1e, 0xc6, 0x1e,
+	0xc8, 0x1e, 0xc8, 0x1e, 0xca, 0x1e, 0xca, 0x1e,
+	0xcc, 0x1e, 0xcc, 0x1e, 0xce, 0x1e, 0xce, 0x1e,
+	0xd0, 0x1e, 0xd0, 0x1e, 0xd2, 0x1e, 0xd2, 0x1e,
+	0xd4, 0x1e, 0xd4, 0x1e, 0xd6, 0x1e, 0xd6, 0x1e,
+	0xd8, 0x1e, 0xd8, 0x1e, 0xda, 0x1e, 0xda, 0x1e,
+	0xdc, 0x1e, 0xdc, 0x1e, 0xde, 0x1e, 0xde, 0x1e,
+	0xe0, 0x1e, 0xe0, 0x1e, 0xe2, 0x1e, 0xe2, 0x1e,
+	0xe4, 0x1e, 0xe4, 0x1e, 0xe6, 0x1e, 0xe6, 0x1e,
+	0xe8, 0x1e, 0xe8, 0x1e, 0xea, 0x1e, 0xea, 0x1e,
+	0xec, 0x1e, 0xec, 0x1e, 0xee, 0x1e, 0xee, 0x1e,
+	0xf0, 0x1e, 0xf0, 0x1e, 0xf2, 0x1e, 0xf2, 0x1e,
+	0xf4, 0x1e, 0xf4, 0x1e, 0xf6, 0x1e, 0xf6, 0x1e,
+	0xf8, 0x1e, 0xf8, 0x1e, 0xfa, 0x1e, 0xfb, 0x1e,
+	0xfc, 0x1e, 0xfd, 0x1e, 0xfe, 0x1e, 0xff, 0x1e,
+	0x08, 0x1f, 0x09, 0x1f, 0x0a, 0x1f, 0x0b, 0x1f,
+	0x0c, 0x1f, 0x0d, 0x1f, 0x0e, 0x1f, 0x0f, 0x1f,
+	0x08, 0x1f, 0x09, 0x1f, 0x0a, 0x1f, 0x0b, 0x1f,
+	0x0c, 0x1f, 0x0d, 0x1f, 0x0e, 0x1f, 0x0f, 0x1f,
+	0x18, 0x1f, 0x19, 0x1f, 0x1a, 0x1f, 0x1b, 0x1f,
+	0x1c, 0x1f, 0x1d, 0x1f, 0x16, 0x1f, 0x17, 0x1f,
+	0x18, 0x1f, 0x19, 0x1f, 0x1a, 0x1f, 0x1b, 0x1f,
+	0x1c, 0x1f, 0x1d, 0x1f, 0x1e, 0x1f, 0x1f, 0x1f,
+	0x28, 0x1f, 0x29, 0x1f, 0x2a, 0x1f, 0x2b, 0x1f,
+	0x2c, 0x1f, 0x2d, 0x1f, 0x2e, 0x1f, 0x2f, 0x1f,
+	0x28, 0x1f, 0x29, 0x1f, 0x2a, 0x1f, 0x2b, 0x1f,
+	0x2c, 0x1f, 0x2d, 0x1f, 0x2e, 0x1f, 0x2f, 0x1f,
+	0x38, 0x1f, 0x39, 0x1f, 0x3a, 0x1f, 0x3b, 0x1f,
+	0x3c, 0x1f, 0x3d, 0x1f, 0x3e, 0x1f, 0x3f, 0x1f,
+	0x38, 0x1f, 0x39, 0x1f, 0x3a, 0x1f, 0x3b, 0x1f,
+	0x3c, 0x1f, 0x3d, 0x1f, 0x3e, 0x1f, 0x3f, 0x1f,
+	0x48, 0x1f, 0x49, 0x1f, 0x4a, 0x1f, 0x4b, 0x1f,
+	0x4c, 0x1f, 0x4d, 0x1f, 0x46, 0x1f, 0x47, 0x1f,
+	0x48, 0x1f, 0x49, 0x1f, 0x4a, 0x1f, 0x4b, 0x1f,
+	0x4c, 0x1f, 0x4d, 0x1f, 0x4e, 0x1f, 0x4f, 0x1f,
+	0x50, 0x1f, 0x59, 0x1f, 0x52, 0x1f, 0x5b, 0x1f,
+	0x54, 0x1f, 0x5d, 0x1f, 0x56, 0x1f, 0x5f, 0x1f,
+	0x58, 0x1f, 0x59, 0x1f, 0x5a, 0x1f, 0x5b, 0x1f,
+	0x5c, 0x1f, 0x5d, 0x1f, 0x5e, 0x1f, 0x5f, 0x1f,
+	0x68, 0x1f, 0x69, 0x1f, 0x6a, 0x1f, 0x6b, 0x1f,
+	0x6c, 0x1f, 0x6d, 0x1f, 0x6e, 0x1f, 0x6f, 0x1f,
+	0x68, 0x1f, 0x69, 0x1f, 0x6a, 0x1f, 0x6b, 0x1f,
+	0x6c, 0x1f, 0x6d, 0x1f, 0x6e, 0x1f, 0x6f, 0x1f,
+	0xba, 0x1f, 0xbb, 0x1f, 0xc8, 0x1f, 0xc9, 0x1f,
+	0xca, 0x1f, 0xcb, 0x1f, 0xda, 0x1f, 0xdb, 0x1f,
+	0xf8, 0x1f, 0xf9, 0x1f, 0xea, 0x1f, 0xeb, 0x1f,
+	0xfa, 0x1f, 0xfb, 0x1f, 0x7e, 0x1f, 0x7f, 0x1f,
+	0x88, 0x1f, 0x89, 0x1f, 0x8a, 0x1f, 0x8b, 0x1f,
+	0x8c, 0x1f, 0x8d, 0x1f, 0x8e, 0x1f, 0x8f, 0x1f,
+	0x88, 0x1f, 0x89, 0x1f, 0x8a, 0x1f, 0x8b, 0x1f,
+	0x8c, 0x1f, 0x8d, 0x1f, 0x8e, 0x1f, 0x8f, 0x1f,
+	0x98, 0x1f, 0x99, 0x1f, 0x9a, 0x1f, 0x9b, 0x1f,
+	0x9c, 0x1f, 0x9d, 0x1f, 0x9e, 0x1f, 0x9f, 0x1f,
+	0x98, 0x1f, 0x99, 0x1f, 0x9a, 0x1f, 0x9b, 0x1f,
+	0x9c, 0x1f, 0x9d, 0x1f, 0x9e, 0x1f, 0x9f, 0x1f,
+	0xa8, 0x1f, 0xa9, 0x1f, 0xaa, 0x1f, 0xab, 0x1f,
+	0xac, 0x1f, 0xad, 0x1f, 0xae, 0x1f, 0xaf, 0x1f,
+	0xa8, 0x1f, 0xa9, 0x1f, 0xaa, 0x1f, 0xab, 0x1f,
+	0xac, 0x1f, 0xad, 0x1f, 0xae, 0x1f, 0xaf, 0x1f,
+	0xb8, 0x1f, 0xb9, 0x1f, 0xb2, 0x1f, 0xbc, 0x1f,
+	0xb4, 0x1f, 0xb5, 0x1f, 0xb6, 0x1f, 0xb7, 0x1f,
+	0xb8, 0x1f, 0xb9, 0x1f, 0xba, 0x1f, 0xbb, 0x1f,
+	0xbc, 0x1f, 0xbd, 0x1f, 0xbe, 0x1f, 0xbf, 0x1f,
+	0xc0, 0x1f, 0xc1, 0x1f, 0xc2, 0x1f, 0xc3, 0x1f,
+	0xc4, 0x1f, 0xc5, 0x1f, 0xc6, 0x1f, 0xc7, 0x1f,
+	0xc8, 0x1f, 0xc9, 0x1f, 0xca, 0x1f, 0xcb, 0x1f,
+	0xc3, 0x1f, 0xcd, 0x1f, 0xce, 0x1f, 0xcf, 0x1f,
+	0xd8, 0x1f, 0xd9, 0x1f, 0xd2, 0x1f, 0xd3, 0x1f,
+	0xd4, 0x1f, 0xd5, 0x1f, 0xd6, 0x1f, 0xd7, 0x1f,
+	0xd8, 0x1f, 0xd9, 0x1f, 0xda, 0x1f, 0xdb, 0x1f,
+	0xdc, 0x1f, 0xdd, 0x1f, 0xde, 0x1f, 0xdf, 0x1f,
+	0xe8, 0x1f, 0xe9, 0x1f, 0xe2, 0x1f, 0xe3, 0x1f,
+	0xe4, 0x1f, 0xec, 0x1f, 0xe6, 0x1f, 0xe7, 0x1f,
+	0xe8, 0x1f, 0xe9, 0x1f, 0xea, 0x1f, 0xeb, 0x1f,
+	0xec, 0x1f, 0xed, 0x1f, 0xee, 0x1f, 0xef, 0x1f,
+	0xf0, 0x1f, 0xf1, 0x1f, 0xf2, 0x1f, 0xf3, 0x1f,
+	0xf4, 0x1f, 0xf5, 0x1f, 0xf6, 0x1f, 0xf7, 0x1f,
+	0xf8, 0x1f, 0xf9, 0x1f, 0xfa, 0x1f, 0xfb, 0x1f,
+	0xf3, 0x1f, 0xfd, 0x1f, 0xfe, 0x1f, 0xff, 0x1f,
+	0x00, 0x20, 0x01, 0x20, 0x02, 0x20, 0x03, 0x20,
+	0x04, 0x20, 0x05, 0x20, 0x06, 0x20, 0x07, 0x20,
+	0x08, 0x20, 0x09, 0x20, 0x0a, 0x20, 0x0b, 0x20,
+	0x0c, 0x20, 0x0d, 0x20, 0x0e, 0x20, 0x0f, 0x20,
+	0x10, 0x20, 0x11, 0x20, 0x12, 0x20, 0x13, 0x20,
+	0x14, 0x20, 0x15, 0x20, 0x16, 0x20, 0x17, 0x20,
+	0x18, 0x20, 0x19, 0x20, 0x1a, 0x20, 0x1b, 0x20,
+	0x1c, 0x20, 0x1d, 0x20, 0x1e, 0x20, 0x1f, 0x20,
+	0x20, 0x20, 0x21, 0x20, 0x22, 0x20, 0x23, 0x20,
+	0x24, 0x20, 0x25, 0x20, 0x26, 0x20, 0x27, 0x20,
+	0x28, 0x20, 0x29, 0x20, 0x2a, 0x20, 0x2b, 0x20,
+	0x2c, 0x20, 0x2d, 0x20, 0x2e, 0x20, 0x2f, 0x20,
+	0x30, 0x20, 0x31, 0x20, 0x32, 0x20, 0x33, 0x20,
+	0x34, 0x20, 0x35, 0x20, 0x36, 0x20, 0x37, 0x20,
+	0x38, 0x20, 0x39, 0x20, 0x3a, 0x20, 0x3b, 0x20,
+	0x3c, 0x20, 0x3d, 0x20, 0x3e, 0x20, 0x3f, 0x20,
+	0x40, 0x20, 0x41, 0x20, 0x42, 0x20, 0x43, 0x20,
+	0x44, 0x20, 0x45, 0x20, 0x46, 0x20, 0x47, 0x20,
+	0x48, 0x20, 0x49, 0x20, 0x4a, 0x20, 0x4b, 0x20,
+	0x4c, 0x20, 0x4d, 0x20, 0x4e, 0x20, 0x4f, 0x20,
+	0x50, 0x20, 0x51, 0x20, 0x52, 0x20, 0x53, 0x20,
+	0x54, 0x20, 0x55, 0x20, 0x56, 0x20, 0x57, 0x20,
+	0x58, 0x20, 0x59, 0x20, 0x5a, 0x20, 0x5b, 0x20,
+	0x5c, 0x20, 0x5d, 0x20, 0x5e, 0x20, 0x5f, 0x20,
+	0x60, 0x20, 0x61, 0x20, 0x62, 0x20, 0x63, 0x20,
+	0x64, 0x20, 0x65, 0x20, 0x66, 0x20, 0x67, 0x20,
+	0x68, 0x20, 0x69, 0x20, 0x6a, 0x20, 0x6b, 0x20,
+	0x6c, 0x20, 0x6d, 0x20, 0x6e, 0x20, 0x6f, 0x20,
+	0x70, 0x20, 0x71, 0x20, 0x72, 0x20, 0x73, 0x20,
+	0x74, 0x20, 0x75, 0x20, 0x76, 0x20, 0x77, 0x20,
+	0x78, 0x20, 0x79, 0x20, 0x7a, 0x20, 0x7b, 0x20,
+	0x7c, 0x20, 0x7d, 0x20, 0x7e, 0x20, 0x7f, 0x20,
+	0x80, 0x20, 0x81, 0x20, 0x82, 0x20, 0x83, 0x20,
+	0x84, 0x20, 0x85, 0x20, 0x86, 0x20, 0x87, 0x20,
+	0x88, 0x20, 0x89, 0x20, 0x8a, 0x20, 0x8b, 0x20,
+	0x8c, 0x20, 0x8d, 0x20, 0x8e, 0x20, 0x8f, 0x20,
+	0x90, 0x20, 0x91, 0x20, 0x92, 0x20, 0x93, 0x20,
+	0x94, 0x20, 0x95, 0x20, 0x96, 0x20, 0x97, 0x20,
+	0x98, 0x20, 0x99, 0x20, 0x9a, 0x20, 0x9b, 0x20,
+	0x9c, 0x20, 0x9d, 0x20, 0x9e, 0x20, 0x9f, 0x20,
+	0xa0, 0x20, 0xa1, 0x20, 0xa2, 0x20, 0xa3, 0x20,
+	0xa4, 0x20, 0xa5, 0x20, 0xa6, 0x20, 0xa7, 0x20,
+	0xa8, 0x20, 0xa9, 0x20, 0xaa, 0x20, 0xab, 0x20,
+	0xac, 0x20, 0xad, 0x20, 0xae, 0x20, 0xaf, 0x20,
+	0xb0, 0x20, 0xb1, 0x20, 0xb2, 0x20, 0xb3, 0x20,
+	0xb4, 0x20, 0xb5, 0x20, 0xb6, 0x20, 0xb7, 0x20,
+	0xb8, 0x20, 0xb9, 0x20, 0xba, 0x20, 0xbb, 0x20,
+	0xbc, 0x20, 0xbd, 0x20, 0xbe, 0x20, 0xbf, 0x20,
+	0xc0, 0x20, 0xc1, 0x20, 0xc2, 0x20, 0xc3, 0x20,
+	0xc4, 0x20, 0xc5, 0x20, 0xc6, 0x20, 0xc7, 0x20,
+	0xc8, 0x20, 0xc9, 0x20, 0xca, 0x20, 0xcb, 0x20,
+	0xcc, 0x20, 0xcd, 0x20, 0xce, 0x20, 0xcf, 0x20,
+	0xd0, 0x20, 0xd1, 0x20, 0xd2, 0x20, 0xd3, 0x20,
+	0xd4, 0x20, 0xd5, 0x20, 0xd6, 0x20, 0xd7, 0x20,
+	0xd8, 0x20, 0xd9, 0x20, 0xda, 0x20, 0xdb, 0x20,
+	0xdc, 0x20, 0xdd, 0x20, 0xde, 0x20, 0xdf, 0x20,
+	0xe0, 0x20, 0xe1, 0x20, 0xe2, 0x20, 0xe3, 0x20,
+	0xe4, 0x20, 0xe5, 0x20, 0xe6, 0x20, 0xe7, 0x20,
+	0xe8, 0x20, 0xe9, 0x20, 0xea, 0x20, 0xeb, 0x20,
+	0xec, 0x20, 0xed, 0x20, 0xee, 0x20, 0xef, 0x20,
+	0xf0, 0x20, 0xf1, 0x20, 0xf2, 0x20, 0xf3, 0x20,
+	0xf4, 0x20, 0xf5, 0x20, 0xf6, 0x20, 0xf7, 0x20,
+	0xf8, 0x20, 0xf9, 0x20, 0xfa, 0x20, 0xfb, 0x20,
+	0xfc, 0x20, 0xfd, 0x20, 0xfe, 0x20, 0xff, 0x20,
+	0x00, 0x21, 0x01, 0x21, 0x02, 0x21, 0x03, 0x21,
+	0x04, 0x21, 0x05, 0x21, 0x06, 0x21, 0x07, 0x21,
+	0x08, 0x21, 0x09, 0x21, 0x0a, 0x21, 0x0b, 0x21,
+	0x0c, 0x21, 0x0d, 0x21, 0x0e, 0x21, 0x0f, 0x21,
+	0x10, 0x21, 0x11, 0x21, 0x12, 0x21, 0x13, 0x21,
+	0x14, 0x21, 0x15, 0x21, 0x16, 0x21, 0x17, 0x21,
+	0x18, 0x21, 0x19, 0x21, 0x1a, 0x21, 0x1b, 0x21,
+	0x1c, 0x21, 0x1d, 0x21, 0x1e, 0x21, 0x1f, 0x21,
+	0x20, 0x21, 0x21, 0x21, 0x22, 0x21, 0x23, 0x21,
+	0x24, 0x21, 0x25, 0x21, 0x26, 0x21, 0x27, 0x21,
+	0x28, 0x21, 0x29, 0x21, 0x2a, 0x21, 0x2b, 0x21,
+	0x2c, 0x21, 0x2d, 0x21, 0x2e, 0x21, 0x2f, 0x21,
+	0x30, 0x21, 0x31, 0x21, 0x32, 0x21, 0x33, 0x21,
+	0x34, 0x21, 0x35, 0x21, 0x36, 0x21, 0x37, 0x21,
+	0x38, 0x21, 0x39, 0x21, 0x3a, 0x21, 0x3b, 0x21,
+	0x3c, 0x21, 0x3d, 0x21, 0x3e, 0x21, 0x3f, 0x21,
+	0x40, 0x21, 0x41, 0x21, 0x42, 0x21, 0x43, 0x21,
+	0x44, 0x21, 0x45, 0x21, 0x46, 0x21, 0x47, 0x21,
+	0x48, 0x21, 0x49, 0x21, 0x4a, 0x21, 0x4b, 0x21,
+	0x4c, 0x21, 0x4d, 0x21, 0x32, 0x21, 0x4f, 0x21,
+	0x50, 0x21, 0x51, 0x21, 0x52, 0x21, 0x53, 0x21,
+	0x54, 0x21, 0x55, 0x21, 0x56, 0x21, 0x57, 0x21,
+	0x58, 0x21, 0x59, 0x21, 0x5a, 0x21, 0x5b, 0x21,
+	0x5c, 0x21, 0x5d, 0x21, 0x5e, 0x21, 0x5f, 0x21,
+	0x60, 0x21, 0x61, 0x21, 0x62, 0x21, 0x63, 0x21,
+	0x64, 0x21, 0x65, 0x21, 0x66, 0x21, 0x67, 0x21,
+	0x68, 0x21, 0x69, 0x21, 0x6a, 0x21, 0x6b, 0x21,
+	0x6c, 0x21, 0x6d, 0x21, 0x6e, 0x21, 0x6f, 0x21,
+	0x60, 0x21, 0x61, 0x21, 0x62, 0x21, 0x63, 0x21,
+	0x64, 0x21, 0x65, 0x21, 0x66, 0x21, 0x67, 0x21,
+	0x68, 0x21, 0x69, 0x21, 0x6a, 0x21, 0x6b, 0x21,
+	0x6c, 0x21, 0x6d, 0x21, 0x6e, 0x21, 0x6f, 0x21,
+	0x80, 0x21, 0x81, 0x21, 0x82, 0x21, 0x83, 0x21,
+	0x83, 0x21, 0xff, 0xff, 0x4b, 0x03, 0xb6, 0x24,
+	0xb7, 0x24, 0xb8, 0x24, 0xb9, 0x24, 0xba, 0x24,
+	0xbb, 0x24, 0xbc, 0x24, 0xbd, 0x24, 0xbe, 0x24,
+	0xbf, 0x24, 0xc0, 0x24, 0xc1, 0x24, 0xc2, 0x24,
+	0xc3, 0x24, 0xc4, 0x24, 0xc5, 0x24, 0xc6, 0x24,
+	0xc7, 0x24, 0xc8, 0x24, 0xc9, 0x24, 0xca, 0x24,
+	0xcb, 0x24, 0xcc, 0x24, 0xcd, 0x24, 0xce, 0x24,
+	0xcf, 0x24, 0xff, 0xff, 0x46, 0x07, 0x00, 0x2c,
+	0x01, 0x2c, 0x02, 0x2c, 0x03, 0x2c, 0x04, 0x2c,
+	0x05, 0x2c, 0x06, 0x2c, 0x07, 0x2c, 0x08, 0x2c,
+	0x09, 0x2c, 0x0a, 0x2c, 0x0b, 0x2c, 0x0c, 0x2c,
+	0x0d, 0x2c, 0x0e, 0x2c, 0x0f, 0x2c, 0x10, 0x2c,
+	0x11, 0x2c, 0x12, 0x2c, 0x13, 0x2c, 0x14, 0x2c,
+	0x15, 0x2c, 0x16, 0x2c, 0x17, 0x2c, 0x18, 0x2c,
+	0x19, 0x2c, 0x1a, 0x2c, 0x1b, 0x2c, 0x1c, 0x2c,
+	0x1d, 0x2c, 0x1e, 0x2c, 0x1f, 0x2c, 0x20, 0x2c,
+	0x21, 0x2c, 0x22, 0x2c, 0x23, 0x2c, 0x24, 0x2c,
+	0x25, 0x2c, 0x26, 0x2c, 0x27, 0x2c, 0x28, 0x2c,
+	0x29, 0x2c, 0x2a, 0x2c, 0x2b, 0x2c, 0x2c, 0x2c,
+	0x2d, 0x2c, 0x2e, 0x2c, 0x5f, 0x2c, 0x60, 0x2c,
+	0x60, 0x2c, 0x62, 0x2c, 0x63, 0x2c, 0x64, 0x2c,
+	0x65, 0x2c, 0x66, 0x2c, 0x67, 0x2c, 0x67, 0x2c,
+	0x69, 0x2c, 0x69, 0x2c, 0x6b, 0x2c, 0x6b, 0x2c,
+	0x6d, 0x2c, 0x6e, 0x2c, 0x6f, 0x2c, 0x70, 0x2c,
+	0x71, 0x2c, 0x72, 0x2c, 0x73, 0x2c, 0x74, 0x2c,
+	0x75, 0x2c, 0x75, 0x2c, 0x77, 0x2c, 0x78, 0x2c,
+	0x79, 0x2c, 0x7a, 0x2c, 0x7b, 0x2c, 0x7c, 0x2c,
+	0x7d, 0x2c, 0x7e, 0x2c, 0x7f, 0x2c, 0x80, 0x2c,
+	0x80, 0x2c, 0x82, 0x2c, 0x82, 0x2c, 0x84, 0x2c,
+	0x84, 0x2c, 0x86, 0x2c, 0x86, 0x2c, 0x88, 0x2c,
+	0x88, 0x2c, 0x8a, 0x2c, 0x8a, 0x2c, 0x8c, 0x2c,
+	0x8c, 0x2c, 0x8e, 0x2c, 0x8e, 0x2c, 0x90, 0x2c,
+	0x90, 0x2c, 0x92, 0x2c, 0x92, 0x2c, 0x94, 0x2c,
+	0x94, 0x2c, 0x96, 0x2c, 0x96, 0x2c, 0x98, 0x2c,
+	0x98, 0x2c, 0x9a, 0x2c, 0x9a, 0x2c, 0x9c, 0x2c,
+	0x9c, 0x2c, 0x9e, 0x2c, 0x9e, 0x2c, 0xa0, 0x2c,
+	0xa0, 0x2c, 0xa2, 0x2c, 0xa2, 0x2c, 0xa4, 0x2c,
+	0xa4, 0x2c, 0xa6, 0x2c, 0xa6, 0x2c, 0xa8, 0x2c,
+	0xa8, 0x2c, 0xaa, 0x2c, 0xaa, 0x2c, 0xac, 0x2c,
+	0xac, 0x2c, 0xae, 0x2c, 0xae, 0x2c, 0xb0, 0x2c,
+	0xb0, 0x2c, 0xb2, 0x2c, 0xb2, 0x2c, 0xb4, 0x2c,
+	0xb4, 0x2c, 0xb6, 0x2c, 0xb6, 0x2c, 0xb8, 0x2c,
+	0xb8, 0x2c, 0xba, 0x2c, 0xba, 0x2c, 0xbc, 0x2c,
+	0xbc, 0x2c, 0xbe, 0x2c, 0xbe, 0x2c, 0xc0, 0x2c,
+	0xc0, 0x2c, 0xc2, 0x2c, 0xc2, 0x2c, 0xc4, 0x2c,
+	0xc4, 0x2c, 0xc6, 0x2c, 0xc6, 0x2c, 0xc8, 0x2c,
+	0xc8, 0x2c, 0xca, 0x2c, 0xca, 0x2c, 0xcc, 0x2c,
+	0xcc, 0x2c, 0xce, 0x2c, 0xce, 0x2c, 0xd0, 0x2c,
+	0xd0, 0x2c, 0xd2, 0x2c, 0xd2, 0x2c, 0xd4, 0x2c,
+	0xd4, 0x2c, 0xd6, 0x2c, 0xd6, 0x2c, 0xd8, 0x2c,
+	0xd8, 0x2c, 0xda, 0x2c, 0xda, 0x2c, 0xdc, 0x2c,
+	0xdc, 0x2c, 0xde, 0x2c, 0xde, 0x2c, 0xe0, 0x2c,
+	0xe0, 0x2c, 0xe2, 0x2c, 0xe2, 0x2c, 0xe4, 0x2c,
+	0xe5, 0x2c, 0xe6, 0x2c, 0xe7, 0x2c, 0xe8, 0x2c,
+	0xe9, 0x2c, 0xea, 0x2c, 0xeb, 0x2c, 0xec, 0x2c,
+	0xed, 0x2c, 0xee, 0x2c, 0xef, 0x2c, 0xf0, 0x2c,
+	0xf1, 0x2c, 0xf2, 0x2c, 0xf3, 0x2c, 0xf4, 0x2c,
+	0xf5, 0x2c, 0xf6, 0x2c, 0xf7, 0x2c, 0xf8, 0x2c,
+	0xf9, 0x2c, 0xfa, 0x2c, 0xfb, 0x2c, 0xfc, 0x2c,
+	0xfd, 0x2c, 0xfe, 0x2c, 0xff, 0x2c, 0xa0, 0x10,
+	0xa1, 0x10, 0xa2, 0x10, 0xa3, 0x10, 0xa4, 0x10,
+	0xa5, 0x10, 0xa6, 0x10, 0xa7, 0x10, 0xa8, 0x10,
+	0xa9, 0x10, 0xaa, 0x10, 0xab, 0x10, 0xac, 0x10,
+	0xad, 0x10, 0xae, 0x10, 0xaf, 0x10, 0xb0, 0x10,
+	0xb1, 0x10, 0xb2, 0x10, 0xb3, 0x10, 0xb4, 0x10,
+	0xb5, 0x10, 0xb6, 0x10, 0xb7, 0x10, 0xb8, 0x10,
+	0xb9, 0x10, 0xba, 0x10, 0xbb, 0x10, 0xbc, 0x10,
+	0xbd, 0x10, 0xbe, 0x10, 0xbf, 0x10, 0xc0, 0x10,
+	0xc1, 0x10, 0xc2, 0x10, 0xc3, 0x10, 0xc4, 0x10,
+	0xc5, 0x10, 0xff, 0xff, 0x1b, 0xd2, 0x21, 0xff,
+	0x22, 0xff, 0x23, 0xff, 0x24, 0xff, 0x25, 0xff,
+	0x26, 0xff, 0x27, 0xff, 0x28, 0xff, 0x29, 0xff,
+	0x2a, 0xff, 0x2b, 0xff, 0x2c, 0xff, 0x2d, 0xff,
+	0x2e, 0xff, 0x2f, 0xff, 0x30, 0xff, 0x31, 0xff,
+	0x32, 0xff, 0x33, 0xff, 0x34, 0xff, 0x35, 0xff,
+	0x36, 0xff, 0x37, 0xff, 0x38, 0xff, 0x39, 0xff,
+	0x3a, 0xff, 0x5b, 0xff, 0x5c, 0xff, 0x5d, 0xff,
+	0x5e, 0xff, 0x5f, 0xff, 0x60, 0xff, 0x61, 0xff,
+	0x62, 0xff, 0x63, 0xff, 0x64, 0xff, 0x65, 0xff,
+	0x66, 0xff, 0x67, 0xff, 0x68, 0xff, 0x69, 0xff,
+	0x6a, 0xff, 0x6b, 0xff, 0x6c, 0xff, 0x6d, 0xff,
+	0x6e, 0xff, 0x6f, 0xff, 0x70, 0xff, 0x71, 0xff,
+	0x72, 0xff, 0x73, 0xff, 0x74, 0xff, 0x75, 0xff,
+	0x76, 0xff, 0x77, 0xff, 0x78, 0xff, 0x79, 0xff,
+	0x7a, 0xff, 0x7b, 0xff, 0x7c, 0xff, 0x7d, 0xff,
+	0x7e, 0xff, 0x7f, 0xff, 0x80, 0xff, 0x81, 0xff,
+	0x82, 0xff, 0x83, 0xff, 0x84, 0xff, 0x85, 0xff,
+	0x86, 0xff, 0x87, 0xff, 0x88, 0xff, 0x89, 0xff,
+	0x8a, 0xff, 0x8b, 0xff, 0x8c, 0xff, 0x8d, 0xff,
+	0x8e, 0xff, 0x8f, 0xff, 0x90, 0xff, 0x91, 0xff,
+	0x92, 0xff, 0x93, 0xff, 0x94, 0xff, 0x95, 0xff,
+	0x96, 0xff, 0x97, 0xff, 0x98, 0xff, 0x99, 0xff,
+	0x9a, 0xff, 0x9b, 0xff, 0x9c, 0xff, 0x9d, 0xff,
+	0x9e, 0xff, 0x9f, 0xff, 0xa0, 0xff, 0xa1, 0xff,
+	0xa2, 0xff, 0xa3, 0xff, 0xa4, 0xff, 0xa5, 0xff,
+	0xa6, 0xff, 0xa7, 0xff, 0xa8, 0xff, 0xa9, 0xff,
+	0xaa, 0xff, 0xab, 0xff, 0xac, 0xff, 0xad, 0xff,
+	0xae, 0xff, 0xaf, 0xff, 0xb0, 0xff, 0xb1, 0xff,
+	0xb2, 0xff, 0xb3, 0xff, 0xb4, 0xff, 0xb5, 0xff,
+	0xb6, 0xff, 0xb7, 0xff, 0xb8, 0xff, 0xb9, 0xff,
+	0xba, 0xff, 0xbb, 0xff, 0xbc, 0xff, 0xbd, 0xff,
+	0xbe, 0xff, 0xbf, 0xff, 0xc0, 0xff, 0xc1, 0xff,
+	0xc2, 0xff, 0xc3, 0xff, 0xc4, 0xff, 0xc5, 0xff,
+	0xc6, 0xff, 0xc7, 0xff, 0xc8, 0xff, 0xc9, 0xff,
+	0xca, 0xff, 0xcb, 0xff, 0xcc, 0xff, 0xcd, 0xff,
+	0xce, 0xff, 0xcf, 0xff, 0xd0, 0xff, 0xd1, 0xff,
+	0xd2, 0xff, 0xd3, 0xff, 0xd4, 0xff, 0xd5, 0xff,
+	0xd6, 0xff, 0xd7, 0xff, 0xd8, 0xff, 0xd9, 0xff,
+	0xda, 0xff, 0xdb, 0xff, 0xdc, 0xff, 0xdd, 0xff,
+	0xde, 0xff, 0xdf, 0xff, 0xe0, 0xff, 0xe1, 0xff,
+	0xe2, 0xff, 0xe3, 0xff, 0xe4, 0xff, 0xe5, 0xff,
+	0xe6, 0xff, 0xe7, 0xff, 0xe8, 0xff, 0xe9, 0xff,
+	0xea, 0xff, 0xeb, 0xff, 0xec, 0xff, 0xed, 0xff,
+	0xee, 0xff, 0xef, 0xff, 0xf0, 0xff, 0xf1, 0xff,
+	0xf2, 0xff, 0xf3, 0xff, 0xf4, 0xff, 0xf5, 0xff,
+	0xf6, 0xff, 0xf7, 0xff, 0xf8, 0xff, 0xf9, 0xff,
+	0xfa, 0xff, 0xfb, 0xff, 0xfc, 0xff, 0xfd, 0xff,
+	0xfe, 0xff, 0xff, 0xff
+};
diff --git a/exfat/mkfs/uctc.h b/exfat/mkfs/uctc.h
new file mode 100644
index 0000000..3e200f1
--- /dev/null
+++ b/exfat/mkfs/uctc.h
@@ -0,0 +1,30 @@
+/*
+	uctc.h (30.10.10)
+	Upper Case Table declaration.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef MKFS_UCTC_H_INCLUDED
+#define MKFS_UCTC_H_INCLUDED
+
+#include <stdint.h>
+
+extern uint8_t upcase_table[5836];
+
+#endif /* ifndef MKFS_UCTC_H_INCLUDED */
diff --git a/exfat/mkfs/vbr.c b/exfat/mkfs/vbr.c
new file mode 100644
index 0000000..a45cc90
--- /dev/null
+++ b/exfat/mkfs/vbr.c
@@ -0,0 +1,148 @@
+/*
+	vbr.c (09.11.10)
+	Volume Boot Record creation code.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "vbr.h"
+#include "fat.h"
+#include "cbm.h"
+#include "uct.h"
+#include "rootdir.h"
+#include <string.h>
+
+static loff_t vbr_alignment(void)
+{
+	return get_sector_size();
+}
+
+static loff_t vbr_size(void)
+{
+	return 12 * get_sector_size();
+}
+
+static void init_sb(struct exfat_super_block* sb)
+{
+	uint32_t clusters_max;
+	uint32_t fat_sectors;
+
+	clusters_max = get_volume_size() / get_cluster_size();
+	fat_sectors = DIV_ROUND_UP((loff_t) clusters_max * sizeof(cluster_t),
+			get_sector_size());
+
+	memset(sb, 0, sizeof(struct exfat_super_block));
+	sb->jump[0] = 0xeb;
+	sb->jump[1] = 0x76;
+	sb->jump[2] = 0x90;
+	memcpy(sb->oem_name, "EXFAT   ", sizeof(sb->oem_name));
+	sb->sector_start = cpu_to_le64(get_first_sector());
+	sb->sector_count = cpu_to_le64(get_volume_size() / get_sector_size());
+	sb->fat_sector_start = cpu_to_le32(
+			fat.get_alignment() / get_sector_size());
+	sb->fat_sector_count = cpu_to_le32(ROUND_UP(
+			le32_to_cpu(sb->fat_sector_start) + fat_sectors,
+				1 << get_spc_bits()) -
+			le32_to_cpu(sb->fat_sector_start));
+	sb->cluster_sector_start = cpu_to_le32(
+			get_position(&cbm) / get_sector_size());
+	sb->cluster_count = cpu_to_le32(clusters_max -
+			((le32_to_cpu(sb->fat_sector_start) +
+			  le32_to_cpu(sb->fat_sector_count)) >> get_spc_bits()));
+	sb->rootdir_cluster = cpu_to_le32(
+			(get_position(&rootdir) - get_position(&cbm)) / get_cluster_size()
+			+ EXFAT_FIRST_DATA_CLUSTER);
+	sb->volume_serial = cpu_to_le32(get_volume_serial());
+	sb->version.major = 1;
+	sb->version.minor = 0;
+	sb->volume_state = cpu_to_le16(0);
+	sb->sector_bits = get_sector_bits();
+	sb->spc_bits = get_spc_bits();
+	sb->fat_count = 1;
+	sb->drive_no = 0x80;
+	sb->allocated_percent = 0;
+	sb->boot_signature = cpu_to_le16(0xaa55);
+}
+
+static int vbr_write(struct exfat_dev* dev)
+{
+	struct exfat_super_block sb;
+	uint32_t checksum;
+	le32_t* sector = malloc(get_sector_size());
+	size_t i;
+
+	if (sector == NULL)
+	{
+		exfat_error("failed to allocate sector-sized block of memory");
+		return 1;
+	}
+
+	init_sb(&sb);
+	if (exfat_write(dev, &sb, sizeof(struct exfat_super_block)) < 0)
+	{
+		free(sector);
+		exfat_error("failed to write super block sector");
+		return 1;
+	}
+	checksum = exfat_vbr_start_checksum(&sb, sizeof(struct exfat_super_block));
+
+	memset(sector, 0, get_sector_size());
+	sector[get_sector_size() / sizeof(sector[0]) - 1] =
+			cpu_to_le32(0xaa550000);
+	for (i = 0; i < 8; i++)
+	{
+		if (exfat_write(dev, sector, get_sector_size()) < 0)
+		{
+			free(sector);
+			exfat_error("failed to write a sector with boot signature");
+			return 1;
+		}
+		checksum = exfat_vbr_add_checksum(sector, get_sector_size(), checksum);
+	}
+
+	memset(sector, 0, get_sector_size());
+	for (i = 0; i < 2; i++)
+	{
+		if (exfat_write(dev, sector, get_sector_size()) < 0)
+		{
+			free(sector);
+			exfat_error("failed to write an empty sector");
+			return 1;
+		}
+		checksum = exfat_vbr_add_checksum(sector, get_sector_size(), checksum);
+	}
+
+	for (i = 0; i < get_sector_size() / sizeof(sector[0]); i++)
+		sector[i] = cpu_to_le32(checksum);
+	if (exfat_write(dev, sector, get_sector_size()) < 0)
+	{
+		free(sector);
+		exfat_error("failed to write checksum sector");
+		return 1;
+	}
+
+	free(sector);
+	return 0;
+}
+
+const struct fs_object vbr =
+{
+	.get_alignment = vbr_alignment,
+	.get_size = vbr_size,
+	.write = vbr_write,
+};
diff --git a/exfat/mkfs/vbr.h b/exfat/mkfs/vbr.h
new file mode 100644
index 0000000..74e60e5
--- /dev/null
+++ b/exfat/mkfs/vbr.h
@@ -0,0 +1,30 @@
+/*
+	vbr.h (09.11.10)
+	Volume Boot Record creation code.
+
+	Free exFAT implementation.
+	Copyright (C) 2011-2015  Andrew Nayenko
+
+	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
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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.,
+	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef MKFS_VBR_H_INCLUDED
+#define MKFS_VBR_H_INCLUDED
+
+#include "mkexfat.h"
+
+extern const struct fs_object vbr;
+
+#endif /* ifndef MKFS_VBR_H_INCLUDED */
diff --git a/fb2png/.gitignore b/fb2png/.gitignore
new file mode 100644
index 0000000..e32c432
--- /dev/null
+++ b/fb2png/.gitignore
@@ -0,0 +1,5 @@
+*.o
+fb2png
+adb_screenshoot
+libs
+obj
diff --git a/fb2png/Android.mk b/fb2png/Android.mk
new file mode 100644
index 0000000..8d19868
--- /dev/null
+++ b/fb2png/Android.mk
@@ -0,0 +1,78 @@
+# Makefile for Android to build fb2png
+#
+# Copyright (C) 2012  Kyan <kyan.ql.he@gmail.com>
+#
+# 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# 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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+#Ported to CWM source for PhilZ Touch recovery
+#Special thanks to talustus for his help in cross compiling and the Makefile
+#Thanks to McKael @xda for his help in fixing for Nexus 4
+
+LOCAL_PATH:= $(call my-dir)
+
+# We need to build this for both the device (as a shared library)
+# and the host (as a static library for tools to use).
+
+# <-- Build libpng
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libpng
+LOCAL_SRC_FILES := libpng/lib/libpng.a
+
+include $(PREBUILT_STATIC_LIBRARY)
+# -->
+
+
+# <-- Build libfb2png
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libfb2png
+LOCAL_SRC_FILES := \
+    fb2png.c \
+    img_process.c \
+    fb.c
+
+LOCAL_C_INCLUDES +=\
+    external/libpng\
+    external/zlib
+
+LOCAL_CFLAGS += -DANDROID
+LOCAL_STATIC_LIBRARIES := libpng libz
+
+include $(BUILD_STATIC_LIBRARY)
+# -->
+
+
+# <-- Build fb2png bin
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := main.c
+LOCAL_MODULE := fb2png
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS += -DANDROID
+LOCAL_STATIC_LIBRARIES := libfb2png libpng libz libc
+
+LOCAL_C_INCLUDES +=\
+    external/libpng\
+    external/zlib
+
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+LOCAL_PACK_MODULE_RELOCATIONS := false
+
+include $(BUILD_EXECUTABLE)
+# -->
diff --git a/fb2png/AndroidManifest.xml b/fb2png/AndroidManifest.xml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fb2png/AndroidManifest.xml
diff --git a/fb2png/COPYING b/fb2png/COPYING
new file mode 100644
index 0000000..0c5d923
--- /dev/null
+++ b/fb2png/COPYING
@@ -0,0 +1,17 @@
+/*
+ *   Author: Kyan He <kyan.ql.he@gmail.com>
+ *
+ *
+ *   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
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   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, see <http://www.gnu.org/licenses/>.
+ */
diff --git a/fb2png/Makefile b/fb2png/Makefile
new file mode 100644
index 0000000..5ee843c
--- /dev/null
+++ b/fb2png/Makefile
@@ -0,0 +1,17 @@
+# NDK
+CC := arm-linux-androideabi-gcc
+CFLAGS += -g -static -DANDROID
+LDFLAGS += -lpng -lz -lm
+
+ALL: fb2png adb_screenshoot
+
+fb2png: main.o fb.o img_process.o fb2png.o
+	$(CC) $(CFLAGS) main.o fb.o img_process.o fb2png.o -o fb2png $(LDFLAGS)
+	# $(CC) $(CFLAGS) main.o fb.o img_process.o fb2png.o -o fb2png
+
+adb_screenshoot: adb_screenshoot.o fb.o img_process.o
+	 $(CC) $(CFLAGS) adb_screenshoot.o fb.o img_process.o -o adb_screenshoot $(LDFLAGS)
+
+clean:
+	rm -f *.o
+	rm -f fb2png adb_screenshoot
diff --git a/fb2png/adb_screenshoot.c b/fb2png/adb_screenshoot.c
new file mode 100644
index 0000000..afd54ca
--- /dev/null
+++ b/fb2png/adb_screenshoot.c
@@ -0,0 +1,191 @@
+/*
+ *   -- http://android-fb2png.googlecode.com/svn/trunk/adb_screenshoot.c --
+ *
+ *   Copyright 2011, Kyan He <kyan.ql.he@gmail.com>
+ *
+ *
+ *   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
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+
+#include "fb.h"
+#include "log.h"
+
+#define DEFAULT_SAVE_PATH "fbdump.png"
+
+/* defined in $T/system/core/adb/framebuffer_service.c */
+#define DDMS_RAWIMAGE_VERSION 1
+struct fbinfo {
+    unsigned int version;
+    unsigned int bpp;
+    unsigned int size;
+    unsigned int width;
+    unsigned int height;
+    unsigned int red_offset;
+    unsigned int red_length;
+    unsigned int blue_offset;
+    unsigned int blue_length;
+    unsigned int green_offset;
+    unsigned int green_length;
+    unsigned int alpha_offset;
+    unsigned int alpha_length;
+} __attribute__((packed));
+
+static int remote_socket(const char *host, int port)
+{
+    struct sockaddr_in sa;
+    struct hostent *hp;
+    int s;
+
+    if(!(hp = gethostbyname(host))){ return -1; }
+
+    memset(&sa, 0, sizeof(sa));
+    sa.sin_port = htons(port);
+    sa.sin_family = hp->h_addrtype;
+    memcpy((void*) &sa.sin_addr, (void*) hp->h_addr, hp->h_length);
+
+    if((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {
+        return -1;
+    }
+
+    if(connect(s, (struct sockaddr*) &sa, sizeof(sa)) != 0){
+        close(s);
+        return -1;
+    }
+
+    return s;
+}
+
+char *target = "usb";
+static int adb_fd;
+
+/**
+ * Write command through adb protocol.
+ * Return
+ *      Bytes have been wrote.
+ */
+static int adb_write(const char *cmd)
+{
+    char buf[1024];
+    int sz;
+
+    /* Construct command. */
+    sz = sprintf(buf, "%04x%s", strlen(cmd), cmd);
+
+    write(adb_fd, buf, sz);
+
+#if 0
+    D("<< %s", buf);
+#endif
+    return sz;
+}
+
+/**
+ * Read data through adb protocol.
+ * Return
+ *      Bytes have been read.
+ */
+static int adb_read(char *buf, int sz)
+{
+    sz = read(adb_fd, buf, sz);
+    if (sz < 0) {
+        E("Fail to read from adb socket, %s", strerror(errno));
+    }
+    buf[sz] = '\0';
+#if 0
+    D(">> %d", sz);
+#endif
+    return sz;
+}
+
+static int get_fb_from_adb(struct fb *fb)
+{
+    char buf[1024];
+    const struct fbinfo* fbinfo;
+
+    /* Init socket */
+    adb_fd = remote_socket("localhost", 5037);
+    if (adb_fd < 0) {
+        E("Fail to create socket, %s", strerror(errno));
+    }
+
+    adb_write("host:transport-");
+    adb_read(buf, 1024);
+
+    adb_write("framebuffer:");
+    adb_read(buf, 1024);
+
+    /* Parse FB header. */
+    adb_read(buf, sizeof(struct fbinfo));
+    fbinfo = (struct fbinfo*) buf;
+
+    if (fbinfo->version != DDMS_RAWIMAGE_VERSION) {
+        E("unspport adb version");
+    }
+
+    /* Assemble struct fb */
+    memcpy(fb, &fbinfo->bpp, sizeof(struct fbinfo) - 4);
+    fb_dump(fb);
+
+    fb->data = malloc(fb->size);
+    if (!fb->data) return -1;
+
+    /* Read out the whole framebuffer */
+    int bytes_read = 0;
+    while (bytes_read < fb->size) {
+        bytes_read += adb_read(fb->data + bytes_read, fb->size - bytes_read);
+    }
+
+    return 0;
+}
+
+int fb2png(const char* path)
+{
+    struct fb fb;
+
+    if (get_fb_from_adb(&fb)) {
+        D("cannot get framebuffer.");
+        return -1;
+    }
+
+    return fb_save_png(&fb, path);
+}
+
+int main(int argc, char *argv[])
+{
+    char fn[128];
+
+    if (argc == 2) {
+        //if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+        if (argv[1][0] == '-') {
+            printf(
+                "Usage: fb2png [path/to/output.png]\n"
+                "    The default output path is ./fbdump.png\n"
+                );
+            exit(0);
+        } else {
+            sprintf(fn, "%s", argv[1]);
+        }
+    } else {
+        sprintf(fn, "%s", DEFAULT_SAVE_PATH);
+    }
+
+    return fb2png(fn);
+}
diff --git a/fb2png/ascreenshooter.py b/fb2png/ascreenshooter.py
new file mode 100644
index 0000000..1e99484
--- /dev/null
+++ b/fb2png/ascreenshooter.py
@@ -0,0 +1,106 @@
+#!/usr/bin/python
+
+import socket
+import sys
+import struct
+import time
+
+# debug
+VERBOSE = True
+
+def D(msg):
+    if VERBOSE: print(msg)
+
+# "struct fbinfo" is defined in $T/system/core/adb/framebuffer_service.c
+def fbinfo_unpack(data):
+    keys = ("version",
+            "bpp",
+            "size",
+            "width",
+            "height",
+            "red_offset",
+            "red_length",
+            "blue_offset",
+            "blue_length",
+            "green_offset",
+            "green_length",
+            "alpha_offset",
+            "alpha_length"
+        )
+    # the data is little-endian
+    values = struct.unpack("<IIIIIIIIIIIII",data)
+
+    D("dump struct fbinfo")
+    i = 0
+    for key in keys:
+        D("%14s: %-12d" % (key, values[i]))
+        i = i + 1
+
+
+
+def save():
+    f = open('dump', 'w')
+    while True:
+        data = s.recv(4096 * 16)
+        if data == "":
+            break
+        f.write(data)
+    f.close()
+
+
+def communicate(cmd=None):
+    if cmd != None:
+        buf = "%04x%s" % (len(cmd), cmd)
+        D("<< " + buf)
+        s.send(buf)
+    data = s.recv(4096)
+
+    D(">> [%s]" % len(data))
+    D(data)
+
+    if data[0:4] == 'FAIL':
+        return False
+    else:
+        return True
+
+
+target = ''
+# use getopt module in future
+for arg in sys.argv:
+    if arg == '-q':
+        VERBOSE = False
+    if target != 'any':
+        # compatiable with "adb -d", redirect commands to usb
+        if arg == '-d':
+            target = 'usb'
+        # compatiable with "adb -e", redirect commands to emulator
+        elif arg == '-e':
+            target = 'local'
+
+if target == '': target ='any'
+
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+D("connecting")
+try:
+    s.connect(("localhost", 5037))
+except socket.error:
+    print 'Cannot connect to localhost:5037'
+    print socket.error
+    sys.exit(0)
+
+D("connected")
+
+if not communicate("host:transport-%s" % target):
+    sys.exit(1)
+#communicate("host:transport-usb:shell:ls /data")
+communicate("framebuffer:")
+
+data = s.recv(52)
+fbinfo_unpack(data)
+
+t0 = float(time.time())
+save()
+t1 = float(time.time())
+print t1 - t0
+
diff --git a/fb2png/fb.c b/fb2png/fb.c
new file mode 100644
index 0000000..a17f801
--- /dev/null
+++ b/fb2png/fb.c
@@ -0,0 +1,131 @@
+/*
+ *   -- http://android-fb2png.googlecode.com/svn/trunk/fb.c --
+ *
+ *   Copyright 2011, Kyan He <kyan.ql.he@gmail.com>
+ *
+ *
+ *   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
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "log.h"
+#include "fb.h"
+#include "img_process.h"
+
+void fb_dump(const struct fb* fb)
+{
+    D("%12s : %d", "bpp", fb->bpp);
+    D("%12s : %d", "size", fb->size);
+    D("%12s : %d", "width", fb->width);
+    D("%12s : %d", "height", fb->height);
+    D("%12s : %d %d %d %d", "ARGB offset",
+            fb->alpha_offset, fb->red_offset,
+            fb->green_offset, fb->blue_offset);
+    D("%12s : %d %d %d %d", "ARGB length",
+            fb->alpha_length, fb->red_length,
+            fb->green_length, fb->blue_length);
+}
+
+/**
+ * Returns the format of fb.
+ */
+static int fb_get_format(const struct fb *fb)
+{
+    int ao = fb->alpha_offset;
+    int ro = fb->red_offset;
+    int bo = fb->blue_offset;
+
+#define FB_FORMAT_UNKNOWN   0
+#define FB_FORMAT_RGB565    1
+#define FB_FORMAT_ARGB8888  2
+#define FB_FORMAT_RGBA8888  3
+#define FB_FORMAT_ABGR8888  4
+#define FB_FORMAT_BGRA8888  5
+
+    /* TODO: use offset */
+    if (fb->bpp == 16)
+        return FB_FORMAT_RGB565;
+
+    /* TODO: validate */
+    if (ao == 0 && ro == 8)
+        return FB_FORMAT_ARGB8888;
+
+    if (ao == 0 && bo == 8)
+        return FB_FORMAT_ABGR8888;
+
+    if (ro == 0)
+        return FB_FORMAT_RGBA8888;
+
+    if (bo == 0)
+        return FB_FORMAT_BGRA8888;
+
+    /* fallback */
+    return FB_FORMAT_UNKNOWN;
+}
+
+int fb_save_png(const struct fb *fb, const char *path)
+{
+    char *rgb_matrix;
+    int ret = -1;
+
+    /* Allocate RGB Matrix. */
+    rgb_matrix = malloc(fb->width * fb->height * 3);
+    if(!rgb_matrix) {
+        free(rgb_matrix);
+        return -1;
+    }
+
+    int fmt = fb_get_format(fb);
+    D("Framebuffer Pixel Format: %d", fmt);
+
+    switch(fmt) {
+        case FB_FORMAT_RGB565:
+            /* emulator use rgb565 */
+            ret = rgb565_to_rgb888(fb->data,
+                    rgb_matrix, fb->width * fb->height);
+            break;
+        case FB_FORMAT_ARGB8888:
+            /* most devices use argb8888 */
+            ret = argb8888_to_rgb888(fb->data,
+                    rgb_matrix, fb->width * fb->height);
+            break;
+        case FB_FORMAT_ABGR8888:
+            ret = abgr8888_to_rgb888(fb->data,
+                    rgb_matrix, fb->width * fb->height);
+            break;
+        case FB_FORMAT_BGRA8888:
+            ret = bgra8888_to_rgb888(fb->data,
+                    rgb_matrix, fb->width * fb->height);
+            break;
+        case FB_FORMAT_RGBA8888:
+            ret = rgba8888_to_rgb888(fb->data,
+                    rgb_matrix, fb->width * fb->height);
+            break;
+        default:
+            D("Unsupported framebuffer type.");
+            break;
+    }
+
+    if (ret != 0)
+        D("Error while processing input image.");
+    else if (0 != (ret = save_png(path, rgb_matrix, fb->width, fb->height)))
+        D("Failed to save in PNG format.");
+
+    free(rgb_matrix);
+    return ret;
+}
diff --git a/fb2png/fb.h b/fb2png/fb.h
new file mode 100644
index 0000000..b82acba
--- /dev/null
+++ b/fb2png/fb.h
@@ -0,0 +1,43 @@
+/*
+ *   -- http://android-fb2png.googlecode.com/svn/trunk/fb.h --
+ *
+ *   Copyright 2011, Kyan He <kyan.ql.he@gmail.com>
+ *
+ *
+ *   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
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __FB_H__
+#define __FB_H__
+
+struct fb {
+    unsigned int bpp;
+    unsigned int size;
+    unsigned int width;
+    unsigned int height;
+    unsigned int red_offset;
+    unsigned int red_length;
+    unsigned int blue_offset;
+    unsigned int blue_length;
+    unsigned int green_offset;
+    unsigned int green_length;
+    unsigned int alpha_offset;
+    unsigned int alpha_length;
+    void* data;
+};
+
+void fb_dump(const struct fb* fb);
+int fb_save_png(const struct fb *fb, const char *path);
+
+#endif
diff --git a/fb2png/fb2png-jni.c b/fb2png/fb2png-jni.c
new file mode 100644
index 0000000..d2de33c
--- /dev/null
+++ b/fb2png/fb2png-jni.c
@@ -0,0 +1,30 @@
+/*
+ *   -- http://android-fb2png.googlecode.com/svn/trunk/fb2png-jni.c --
+ *
+ *   Copyright 2011, Kyan He <kyan.ql.he@gmail.com>
+ *
+ *
+ *   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
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <jni.h>
+
+#include "fb2png.h"
+
+jint Java_im_kyan_android_graphics_FrameBuffer_captureScreen( JNIEnv *env,
+                                          jobject this,
+                                          jstring path )
+{
+    return fb2png("/data/local/fbdump.png");
+}
diff --git a/fb2png/fb2png.c b/fb2png/fb2png.c
new file mode 100644
index 0000000..1123b05
--- /dev/null
+++ b/fb2png/fb2png.c
@@ -0,0 +1,123 @@
+/**
+ * fb2png  Save screenshot into .png.
+ *
+ * Copyright (C) 2012  Kyan <kyan.ql.he@gmail.com>
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "fb2png.h"
+#include "fb.h"
+
+/**
+ * Get the {@code struct fb} from device's framebuffer.
+ * Return
+ *      0 for success.
+ */
+int get_device_fb(const char* path, struct fb *fb)
+{
+    int fd;
+    int bytespp;
+    int offset;
+    char *x;
+    struct fb_var_screeninfo vinfo;
+
+    fd = open(path, O_RDONLY);
+    if (fd < 0) return -1;
+
+    if(ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) < 0) {
+        D("ioctl failed, %s\n", strerror(errno));
+        return -1;
+    }
+
+    bytespp = vinfo.bits_per_pixel / 8;
+
+    fb->bpp = vinfo.bits_per_pixel;
+    fb->size = vinfo.xres * vinfo.yres * bytespp;
+    fb->width = vinfo.xres;
+    fb->height = vinfo.yres;
+    fb->red_offset = vinfo.red.offset;
+    fb->red_length = vinfo.red.length;
+    fb->green_offset = vinfo.green.offset;
+    fb->green_length = vinfo.green.length;
+    fb->blue_offset = vinfo.blue.offset;
+    fb->blue_length = vinfo.blue.length;
+    fb->alpha_offset = vinfo.transp.offset;
+    fb->alpha_length = vinfo.transp.length;
+
+#ifdef ANDROID
+    /* HACK: for several of 3d cores a specific alignment
+     * is required so the start of the fb may not be an integer number of lines
+     * from the base.  As a result we are storing the additional offset in
+     * xoffset. This is not the correct usage for xoffset, it should be added
+     * to each line, not just once at the beginning */
+
+    offset = vinfo.xoffset * bytespp;
+
+    /* Android use double-buffer, capture 2nd */
+    offset += vinfo.xres * vinfo.yoffset * bytespp;
+#else
+    offset = 0;
+#endif
+
+    x = malloc(fb->size);
+    if (!x) return -1;
+
+    lseek(fd, offset, SEEK_SET);
+
+    if (read(fd, x ,fb->size) != fb->size) goto oops;
+
+    fb->data = x;
+    close(fd);
+
+    return 0;
+
+oops:
+    close(fd);
+    free(x);
+    return -1;
+}
+
+int fb2png(const char *path)
+{
+    struct fb fb;
+    int ret;
+
+#ifdef ANDROID
+    ret = get_device_fb("/dev/graphics/fb0", &fb);
+#else
+    ret = get_device_fb("/dev/fb0", &fb);
+#endif
+
+    if (ret) {
+        D("Failed to read framebuffer.");
+        return -1;
+    }
+
+    fb_dump(&fb);
+
+    return fb_save_png(&fb, path);
+}
+
diff --git a/fb2png/fb2png.h b/fb2png/fb2png.h
new file mode 100644
index 0000000..c5eea9c
--- /dev/null
+++ b/fb2png/fb2png.h
@@ -0,0 +1,26 @@
+/*
+ *   -- http://android-fb2png.googlecode.com/svn/trunk/fb2png.h --
+ *
+ *   Copyright 2011, Kyan He <kyan.ql.he@gmail.com>
+ *
+ *
+ *   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
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __FB2PNG_H__
+#define __FB2PNG_H__
+
+int fb2png(const char *path);
+
+#endif
diff --git a/fb2png/img_process.c b/fb2png/img_process.c
new file mode 100644
index 0000000..535ec22
--- /dev/null
+++ b/fb2png/img_process.c
@@ -0,0 +1,254 @@
+/*
+ *   -- http://android-fb2png.googlecode.com/svn/trunk/img_process.c --
+ *
+ *   Copyright 2011, Kyan He <kyan.ql.he@gmail.com>
+ *
+ *
+ *   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
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <png.h>
+
+#include "img_process.h"
+#include "log.h"
+
+int rgb565_to_rgb888(const char* src, char* dst, size_t pixel)
+{
+    struct rgb565  *from;
+    struct rgb888  *to;
+
+    from = (struct rgb565 *) src;
+    to = (struct rgb888 *) dst;
+
+    size_t i = 0;
+    /* traverse pixel of the row */
+    while(i++ < pixel) {
+
+        to->r = from->r;
+        to->g = from->g;
+        to->b = from->b;
+        /* scale */
+        to->r <<= 3;
+        to->g <<= 2;
+        to->b <<= 3;
+
+        to++;
+        from++;
+    }
+
+    return 0;
+}
+
+int argb8888_to_rgb888(const char* src, char* dst, size_t pixel)
+{
+    size_t i;
+    struct argb8888  *from;
+    struct rgb888  *to;
+
+    from = (struct argb8888 *) src;
+    to = (struct rgb888 *) dst;
+
+    i = 0;
+    /* traverse pixel of the row */
+    while(i++ < pixel) {
+
+        to->r = from->r;
+        to->g = from->g;
+        to->b = from->b;
+
+        to++;
+        from++;
+    }
+
+    return 0;
+}
+
+int abgr8888_to_rgb888(const char* src, char* dst, size_t pixel)
+{
+    size_t i;
+    struct abgr8888  *from;
+    struct rgb888  *to;
+
+    from = (struct abgr8888 *) src;
+    to = (struct rgb888 *) dst;
+
+    i = 0;
+    /* traverse pixel of the row */
+    while(i++ < pixel) {
+
+        to->r = from->r;
+        to->g = from->g;
+        to->b = from->b;
+
+        to++;
+        from++;
+    }
+
+    return 0;
+}
+
+int bgra8888_to_rgb888(const char* src, char* dst, size_t pixel)
+{
+    size_t i;
+    struct bgra8888  *from;
+    struct rgb888  *to;
+
+    from = (struct bgra8888 *) src;
+    to = (struct rgb888 *) dst;
+
+    i = 0;
+    /* traverse pixel of the row */
+    while(i++ < pixel) {
+
+        to->r = from->r;
+        to->g = from->g;
+        to->b = from->b;
+
+        to++;
+        from++;
+    }
+
+    return 0;
+}
+
+int rgba8888_to_rgb888(const char* src, char* dst, size_t pixel)
+{
+    size_t i;
+    struct rgba8888  *from;
+    struct rgb888  *to;
+
+    from = (struct rgba8888 *) src;
+    to = (struct rgb888 *) dst;
+
+    i = 0;
+    /* traverse pixel of the row */
+    while(i++ < pixel) {
+
+        to->r = from->r;
+        to->g = from->g;
+        to->b = from->b;
+
+        to++;
+        from++;
+    }
+
+    return 0;
+}
+
+static void
+stdio_write_func (png_structp png, png_bytep data, png_size_t size)
+{
+    FILE *fp;
+    size_t ret;
+
+    fp = png_get_io_ptr (png);
+    while (size) {
+        ret = fwrite (data, 1, size, fp);
+        size -= ret;
+        data += ret;
+      if (size && ferror (fp))
+         E("write: %m\n");
+   }
+}
+
+static void
+png_simple_output_flush_fn (__attribute__((unused)) png_structp png_ptr)
+{
+}
+
+static void
+png_simple_error_callback (__attribute__((unused)) png_structp png,
+                       png_const_charp error_msg)
+{
+    E("png error: %s\n", error_msg);
+}
+
+static void
+png_simple_warning_callback (__attribute__((unused)) png_structp png,
+                         png_const_charp error_msg)
+{
+    fprintf(stderr, "png warning: %s\n", error_msg);
+}
+
+/* save rgb888 to png format in fp */
+int save_png(const char* path, const char* data, int width, int height)
+{
+    FILE *fp;
+    png_byte **volatile rows;
+    png_struct *png;
+    png_info *info;
+
+    fp = fopen(path, "w");
+    if (!fp) {
+        int errsv = errno;
+        E("Cannot open file %s for writing.\n", path);
+        return errsv;
+    }
+
+    rows = malloc(height * sizeof rows[0]);
+    if (!rows) goto oops;
+
+    int i;
+    for (i = 0; i < height; i++)
+        rows[i] = (png_byte *) data + i * width * 3 /*fb.stride*/;
+
+    png = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL,
+                               png_simple_error_callback,
+                               png_simple_warning_callback);
+    if (!png) {
+        E("png_create_write_struct failed\n");
+        goto oops;
+    }
+
+    info = png_create_info_struct (png);
+    if (!info) {
+        E("png_create_info_struct failed\n");
+        png_destroy_write_struct (&png, NULL);
+        goto oops;
+    }
+
+    png_set_write_fn (png, fp, stdio_write_func, png_simple_output_flush_fn);
+    png_set_IHDR (png, info,
+            width,
+            height,
+#define DEPTH 8
+            DEPTH,
+        PNG_COLOR_TYPE_RGB,
+        PNG_INTERLACE_NONE,
+        PNG_COMPRESSION_TYPE_DEFAULT,
+        PNG_FILTER_TYPE_DEFAULT);
+
+    png_color_16 white;
+
+    white.gray = (1 << DEPTH) - 1;
+    white.red = white.blue = white.green = white.gray;
+
+    png_set_bKGD (png, info, &white);
+    png_write_info (png, info);
+
+    png_write_image (png, rows);
+    png_write_end (png, info);
+
+    png_destroy_write_struct (&png, &info);
+
+    fclose(fp);
+    free (rows);
+    return 0;
+
+oops:
+    fclose(fp);
+    free (rows);
+    return -1;
+}
diff --git a/fb2png/img_process.h b/fb2png/img_process.h
new file mode 100644
index 0000000..871cfc9
--- /dev/null
+++ b/fb2png/img_process.h
@@ -0,0 +1,84 @@
+/*
+ *   -- http://android-fb2png.googlecode.com/svn/trunk/img_process.h --
+ *
+ *   Copyright 2011, Kyan He <kyan.ql.he@gmail.com>
+ *
+ *
+ *   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
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __IMG_PROCESS_H__
+#define __IMG_PROCESS_H__
+
+/**
+ * rgba8888 is found on Desire HD Linux localhost 2.6.35.10-g931a37e #1
+ *      PREEMPT Wed Nov 9 14:04:03 CST 2011 armv7l GNU/Linux
+ */
+
+
+typedef struct rgb888 {
+        char r;
+        char g;
+        char b;
+} rgb888_t;
+
+typedef rgb888_t rgb24_t;
+
+typedef struct argb8888 {
+        char a;
+        char r;
+        char g;
+        char b;
+} argb8888_t;
+
+typedef struct abgr8888 {
+        char a;
+        char b;
+        char g;
+        char r;
+} abgr8888_t;
+
+typedef struct bgra8888 {
+        char b;
+        char g;
+        char r;
+        char a;
+} bgra8888_t;
+
+typedef struct rgba8888 {
+        char r;
+        char g;
+        char b;
+        char a;
+} rgba8888_t;
+
+typedef struct rgb565 {
+        short b:5;
+        short g:6;
+        short r:5;
+} rgb565_t;
+
+int rgb565_to_rgb888(const char* src, char* dst, size_t pixel);
+
+int argb8888_to_rgb888(const char* src, char* dst, size_t pixel);
+
+int abgr8888_to_rgb888(const char* src, char* dst, size_t pixel);
+
+int bgra8888_to_rgb888(const char* src, char* dst, size_t pixel);
+
+int rgba8888_to_rgb888(const char* src, char* dst, size_t pixel);
+
+int save_png(const char* path, const char* data, int width, int height);
+
+#endif
diff --git a/fb2png/jni/Application.mk b/fb2png/jni/Application.mk
new file mode 100644
index 0000000..a720a09
--- /dev/null
+++ b/fb2png/jni/Application.mk
@@ -0,0 +1 @@
+APP_BUILD_SCRIPT := $(call my-dir)/../Android.mk
diff --git a/fb2png/libpng/include/png.h b/fb2png/libpng/include/png.h
new file mode 100644
index 0000000..0ad09eb
--- /dev/null
+++ b/fb2png/libpng/include/png.h
@@ -0,0 +1,3301 @@
+
+/* png.h - header file for PNG reference library
+ *
+ * libpng version 1.6.1 - March 28, 2013
+ * Copyright (c) 1998-2013 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license (See LICENSE, below)
+ *
+ * Authors and maintainers:
+ *   libpng versions 0.71, May 1995, through 0.88, January 1996: Guy Schalnat
+ *   libpng versions 0.89c, June 1996, through 0.96, May 1997: Andreas Dilger
+ *   libpng versions 0.97, January 1998, through 1.6.1 - March 28, 2013: Glenn
+ *   See also "Contributing Authors", below.
+ *
+ * Note about libpng version numbers:
+ *
+ *   Due to various miscommunications, unforeseen code incompatibilities
+ *   and occasional factors outside the authors' control, version numbering
+ *   on the library has not always been consistent and straightforward.
+ *   The following table summarizes matters since version 0.89c, which was
+ *   the first widely used release:
+ *
+ *    source                 png.h  png.h  shared-lib
+ *    version                string   int  version
+ *    -------                ------ -----  ----------
+ *    0.89c "1.0 beta 3"     0.89      89  1.0.89
+ *    0.90  "1.0 beta 4"     0.90      90  0.90  [should have been 2.0.90]
+ *    0.95  "1.0 beta 5"     0.95      95  0.95  [should have been 2.0.95]
+ *    0.96  "1.0 beta 6"     0.96      96  0.96  [should have been 2.0.96]
+ *    0.97b "1.00.97 beta 7" 1.00.97   97  1.0.1 [should have been 2.0.97]
+ *    0.97c                  0.97      97  2.0.97
+ *    0.98                   0.98      98  2.0.98
+ *    0.99                   0.99      98  2.0.99
+ *    0.99a-m                0.99      99  2.0.99
+ *    1.00                   1.00     100  2.1.0 [100 should be 10000]
+ *    1.0.0      (from here on, the   100  2.1.0 [100 should be 10000]
+ *    1.0.1       png.h string is   10001  2.1.0
+ *    1.0.1a-e    identical to the  10002  from here on, the shared library
+ *    1.0.2       source version)   10002  is 2.V where V is the source code
+ *    1.0.2a-b                      10003  version, except as noted.
+ *    1.0.3                         10003
+ *    1.0.3a-d                      10004
+ *    1.0.4                         10004
+ *    1.0.4a-f                      10005
+ *    1.0.5 (+ 2 patches)           10005
+ *    1.0.5a-d                      10006
+ *    1.0.5e-r                      10100 (not source compatible)
+ *    1.0.5s-v                      10006 (not binary compatible)
+ *    1.0.6 (+ 3 patches)           10006 (still binary incompatible)
+ *    1.0.6d-f                      10007 (still binary incompatible)
+ *    1.0.6g                        10007
+ *    1.0.6h                        10007  10.6h (testing xy.z so-numbering)
+ *    1.0.6i                        10007  10.6i
+ *    1.0.6j                        10007  2.1.0.6j (incompatible with 1.0.0)
+ *    1.0.7beta11-14        DLLNUM  10007  2.1.0.7beta11-14 (binary compatible)
+ *    1.0.7beta15-18           1    10007  2.1.0.7beta15-18 (binary compatible)
+ *    1.0.7rc1-2               1    10007  2.1.0.7rc1-2 (binary compatible)
+ *    1.0.7                    1    10007  (still compatible)
+ *    1.0.8beta1-4             1    10008  2.1.0.8beta1-4
+ *    1.0.8rc1                 1    10008  2.1.0.8rc1
+ *    1.0.8                    1    10008  2.1.0.8
+ *    1.0.9beta1-6             1    10009  2.1.0.9beta1-6
+ *    1.0.9rc1                 1    10009  2.1.0.9rc1
+ *    1.0.9beta7-10            1    10009  2.1.0.9beta7-10
+ *    1.0.9rc2                 1    10009  2.1.0.9rc2
+ *    1.0.9                    1    10009  2.1.0.9
+ *    1.0.10beta1              1    10010  2.1.0.10beta1
+ *    1.0.10rc1                1    10010  2.1.0.10rc1
+ *    1.0.10                   1    10010  2.1.0.10
+ *    1.0.11beta1-3            1    10011  2.1.0.11beta1-3
+ *    1.0.11rc1                1    10011  2.1.0.11rc1
+ *    1.0.11                   1    10011  2.1.0.11
+ *    1.0.12beta1-2            2    10012  2.1.0.12beta1-2
+ *    1.0.12rc1                2    10012  2.1.0.12rc1
+ *    1.0.12                   2    10012  2.1.0.12
+ *    1.1.0a-f                 -    10100  2.1.1.0a-f (branch abandoned)
+ *    1.2.0beta1-2             2    10200  2.1.2.0beta1-2
+ *    1.2.0beta3-5             3    10200  3.1.2.0beta3-5
+ *    1.2.0rc1                 3    10200  3.1.2.0rc1
+ *    1.2.0                    3    10200  3.1.2.0
+ *    1.2.1beta1-4             3    10201  3.1.2.1beta1-4
+ *    1.2.1rc1-2               3    10201  3.1.2.1rc1-2
+ *    1.2.1                    3    10201  3.1.2.1
+ *    1.2.2beta1-6            12    10202  12.so.0.1.2.2beta1-6
+ *    1.0.13beta1             10    10013  10.so.0.1.0.13beta1
+ *    1.0.13rc1               10    10013  10.so.0.1.0.13rc1
+ *    1.2.2rc1                12    10202  12.so.0.1.2.2rc1
+ *    1.0.13                  10    10013  10.so.0.1.0.13
+ *    1.2.2                   12    10202  12.so.0.1.2.2
+ *    1.2.3rc1-6              12    10203  12.so.0.1.2.3rc1-6
+ *    1.2.3                   12    10203  12.so.0.1.2.3
+ *    1.2.4beta1-3            13    10204  12.so.0.1.2.4beta1-3
+ *    1.0.14rc1               13    10014  10.so.0.1.0.14rc1
+ *    1.2.4rc1                13    10204  12.so.0.1.2.4rc1
+ *    1.0.14                  10    10014  10.so.0.1.0.14
+ *    1.2.4                   13    10204  12.so.0.1.2.4
+ *    1.2.5beta1-2            13    10205  12.so.0.1.2.5beta1-2
+ *    1.0.15rc1-3             10    10015  10.so.0.1.0.15rc1-3
+ *    1.2.5rc1-3              13    10205  12.so.0.1.2.5rc1-3
+ *    1.0.15                  10    10015  10.so.0.1.0.15
+ *    1.2.5                   13    10205  12.so.0.1.2.5
+ *    1.2.6beta1-4            13    10206  12.so.0.1.2.6beta1-4
+ *    1.0.16                  10    10016  10.so.0.1.0.16
+ *    1.2.6                   13    10206  12.so.0.1.2.6
+ *    1.2.7beta1-2            13    10207  12.so.0.1.2.7beta1-2
+ *    1.0.17rc1               10    10017  12.so.0.1.0.17rc1
+ *    1.2.7rc1                13    10207  12.so.0.1.2.7rc1
+ *    1.0.17                  10    10017  12.so.0.1.0.17
+ *    1.2.7                   13    10207  12.so.0.1.2.7
+ *    1.2.8beta1-5            13    10208  12.so.0.1.2.8beta1-5
+ *    1.0.18rc1-5             10    10018  12.so.0.1.0.18rc1-5
+ *    1.2.8rc1-5              13    10208  12.so.0.1.2.8rc1-5
+ *    1.0.18                  10    10018  12.so.0.1.0.18
+ *    1.2.8                   13    10208  12.so.0.1.2.8
+ *    1.2.9beta1-3            13    10209  12.so.0.1.2.9beta1-3
+ *    1.2.9beta4-11           13    10209  12.so.0.9[.0]
+ *    1.2.9rc1                13    10209  12.so.0.9[.0]
+ *    1.2.9                   13    10209  12.so.0.9[.0]
+ *    1.2.10beta1-7           13    10210  12.so.0.10[.0]
+ *    1.2.10rc1-2             13    10210  12.so.0.10[.0]
+ *    1.2.10                  13    10210  12.so.0.10[.0]
+ *    1.4.0beta1-5            14    10400  14.so.0.0[.0]
+ *    1.2.11beta1-4           13    10211  12.so.0.11[.0]
+ *    1.4.0beta7-8            14    10400  14.so.0.0[.0]
+ *    1.2.11                  13    10211  12.so.0.11[.0]
+ *    1.2.12                  13    10212  12.so.0.12[.0]
+ *    1.4.0beta9-14           14    10400  14.so.0.0[.0]
+ *    1.2.13                  13    10213  12.so.0.13[.0]
+ *    1.4.0beta15-36          14    10400  14.so.0.0[.0]
+ *    1.4.0beta37-87          14    10400  14.so.14.0[.0]
+ *    1.4.0rc01               14    10400  14.so.14.0[.0]
+ *    1.4.0beta88-109         14    10400  14.so.14.0[.0]
+ *    1.4.0rc02-08            14    10400  14.so.14.0[.0]
+ *    1.4.0                   14    10400  14.so.14.0[.0]
+ *    1.4.1beta01-03          14    10401  14.so.14.1[.0]
+ *    1.4.1rc01               14    10401  14.so.14.1[.0]
+ *    1.4.1beta04-12          14    10401  14.so.14.1[.0]
+ *    1.4.1                   14    10401  14.so.14.1[.0]
+ *    1.4.2                   14    10402  14.so.14.2[.0]
+ *    1.4.3                   14    10403  14.so.14.3[.0]
+ *    1.4.4                   14    10404  14.so.14.4[.0]
+ *    1.5.0beta01-58          15    10500  15.so.15.0[.0]
+ *    1.5.0rc01-07            15    10500  15.so.15.0[.0]
+ *    1.5.0                   15    10500  15.so.15.0[.0]
+ *    1.5.1beta01-11          15    10501  15.so.15.1[.0]
+ *    1.5.1rc01-02            15    10501  15.so.15.1[.0]
+ *    1.5.1                   15    10501  15.so.15.1[.0]
+ *    1.5.2beta01-03          15    10502  15.so.15.2[.0]
+ *    1.5.2rc01-03            15    10502  15.so.15.2[.0]
+ *    1.5.2                   15    10502  15.so.15.2[.0]
+ *    1.5.3beta01-10          15    10503  15.so.15.3[.0]
+ *    1.5.3rc01-02            15    10503  15.so.15.3[.0]
+ *    1.5.3beta11             15    10503  15.so.15.3[.0]
+ *    1.5.3 [omitted]
+ *    1.5.4beta01-08          15    10504  15.so.15.4[.0]
+ *    1.5.4rc01               15    10504  15.so.15.4[.0]
+ *    1.5.4                   15    10504  15.so.15.4[.0]
+ *    1.5.5beta01-08          15    10505  15.so.15.5[.0]
+ *    1.5.5rc01               15    10505  15.so.15.5[.0]
+ *    1.5.5                   15    10505  15.so.15.5[.0]
+ *    1.5.6beta01-07          15    10506  15.so.15.6[.0]
+ *    1.5.6rc01-03            15    10506  15.so.15.6[.0]
+ *    1.5.6                   15    10506  15.so.15.6[.0]
+ *    1.5.7beta01-05          15    10507  15.so.15.7[.0]
+ *    1.5.7rc01-03            15    10507  15.so.15.7[.0]
+ *    1.5.7                   15    10507  15.so.15.7[.0]
+ *    1.6.0beta01-40          16    10600  16.so.16.0[.0]
+ *    1.6.0rc01-08            16    10600  16.so.16.0[.0]
+ *    1.6.0                   16    10600  16.so.16.0[.0]
+ *    1.6.1beta01-10          16    10601  16.so.16.1[.0]
+ *    1.6.1rc01               16    10601  16.so.16.1[.0]
+ *    1.6.1                   16    10601  16.so.16.1[.0]
+ *
+ *   Henceforth the source version will match the shared-library major
+ *   and minor numbers; the shared-library major version number will be
+ *   used for changes in backward compatibility, as it is intended.  The
+ *   PNG_LIBPNG_VER macro, which is not used within libpng but is available
+ *   for applications, is an unsigned integer of the form xyyzz corresponding
+ *   to the source version x.y.z (leading zeros in y and z).  Beta versions
+ *   were given the previous public release number plus a letter, until
+ *   version 1.0.6j; from then on they were given the upcoming public
+ *   release number plus "betaNN" or "rcNN".
+ *
+ *   Binary incompatibility exists only when applications make direct access
+ *   to the info_ptr or png_ptr members through png.h, and the compiled
+ *   application is loaded with a different version of the library.
+ *
+ *   DLLNUM will change each time there are forward or backward changes
+ *   in binary compatibility (e.g., when a new feature is added).
+ *
+ * See libpng-manual.txt or libpng.3 for more information.  The PNG
+ * specification is available as a W3C Recommendation and as an ISO
+ * Specification, <http://www.w3.org/TR/2003/REC-PNG-20031110/
+ */
+
+/*
+ * COPYRIGHT NOTICE, DISCLAIMER, and LICENSE:
+ *
+ * If you modify libpng you may insert additional notices immediately following
+ * this sentence.
+ *
+ * This code is released under the libpng license.
+ *
+ * libpng versions 1.2.6, August 15, 2004, through 1.6.1, March 28, 2013, are
+ * Copyright (c) 2004, 2006-2013 Glenn Randers-Pehrson, and are
+ * distributed according to the same disclaimer and license as libpng-1.2.5
+ * with the following individual added to the list of Contributing Authors:
+ *
+ *    Cosmin Truta
+ *
+ * libpng versions 1.0.7, July 1, 2000, through 1.2.5, October 3, 2002, are
+ * Copyright (c) 2000-2002 Glenn Randers-Pehrson, and are
+ * distributed according to the same disclaimer and license as libpng-1.0.6
+ * with the following individuals added to the list of Contributing Authors:
+ *
+ *    Simon-Pierre Cadieux
+ *    Eric S. Raymond
+ *    Gilles Vollant
+ *
+ * and with the following additions to the disclaimer:
+ *
+ *    There is no warranty against interference with your enjoyment of the
+ *    library or against infringement.  There is no warranty that our
+ *    efforts or the library will fulfill any of your particular purposes
+ *    or needs.  This library is provided with all faults, and the entire
+ *    risk of satisfactory quality, performance, accuracy, and effort is with
+ *    the user.
+ *
+ * libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are
+ * Copyright (c) 1998, 1999, 2000 Glenn Randers-Pehrson, and are
+ * distributed according to the same disclaimer and license as libpng-0.96,
+ * with the following individuals added to the list of Contributing Authors:
+ *
+ *    Tom Lane
+ *    Glenn Randers-Pehrson
+ *    Willem van Schaik
+ *
+ * libpng versions 0.89, June 1996, through 0.96, May 1997, are
+ * Copyright (c) 1996, 1997 Andreas Dilger
+ * Distributed according to the same disclaimer and license as libpng-0.88,
+ * with the following individuals added to the list of Contributing Authors:
+ *
+ *    John Bowler
+ *    Kevin Bracey
+ *    Sam Bushell
+ *    Magnus Holmgren
+ *    Greg Roelofs
+ *    Tom Tanner
+ *
+ * libpng versions 0.5, May 1995, through 0.88, January 1996, are
+ * Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.
+ *
+ * For the purposes of this copyright and license, "Contributing Authors"
+ * is defined as the following set of individuals:
+ *
+ *    Andreas Dilger
+ *    Dave Martindale
+ *    Guy Eric Schalnat
+ *    Paul Schmidt
+ *    Tim Wegner
+ *
+ * The PNG Reference Library is supplied "AS IS".  The Contributing Authors
+ * and Group 42, Inc. disclaim all warranties, expressed or implied,
+ * including, without limitation, the warranties of merchantability and of
+ * fitness for any purpose.  The Contributing Authors and Group 42, Inc.
+ * assume no liability for direct, indirect, incidental, special, exemplary,
+ * or consequential damages, which may result from the use of the PNG
+ * Reference Library, even if advised of the possibility of such damage.
+ *
+ * Permission is hereby granted to use, copy, modify, and distribute this
+ * source code, or portions hereof, for any purpose, without fee, subject
+ * to the following restrictions:
+ *
+ *   1. The origin of this source code must not be misrepresented.
+ *
+ *   2. Altered versions must be plainly marked as such and must not
+ *      be misrepresented as being the original source.
+ *
+ *   3. This Copyright notice may not be removed or altered from
+ *      any source or altered source distribution.
+ *
+ * The Contributing Authors and Group 42, Inc. specifically permit, without
+ * fee, and encourage the use of this source code as a component to
+ * supporting the PNG file format in commercial products.  If you use this
+ * source code in a product, acknowledgment is not required but would be
+ * appreciated.
+ */
+
+/*
+ * A "png_get_copyright" function is available, for convenient use in "about"
+ * boxes and the like:
+ *
+ *     printf("%s", png_get_copyright(NULL));
+ *
+ * Also, the PNG logo (in PNG format, of course) is supplied in the
+ * files "pngbar.png" and "pngbar.jpg (88x31) and "pngnow.png" (98x31).
+ */
+
+/*
+ * Libpng is OSI Certified Open Source Software.  OSI Certified is a
+ * certification mark of the Open Source Initiative.
+ */
+
+/*
+ * The contributing authors would like to thank all those who helped
+ * with testing, bug fixes, and patience.  This wouldn't have been
+ * possible without all of you.
+ *
+ * Thanks to Frank J. T. Wojcik for helping with the documentation.
+ */
+
+/*
+ * Y2K compliance in libpng:
+ * =========================
+ *
+ *    March 28, 2013
+ *
+ *    Since the PNG Development group is an ad-hoc body, we can't make
+ *    an official declaration.
+ *
+ *    This is your unofficial assurance that libpng from version 0.71 and
+ *    upward through 1.6.1 are Y2K compliant.  It is my belief that
+ *    earlier versions were also Y2K compliant.
+ *
+ *    Libpng only has two year fields.  One is a 2-byte unsigned integer
+ *    that will hold years up to 65535.  The other, which is deprecated,
+ *    holds the date in text format, and will hold years up to 9999.
+ *
+ *    The integer is
+ *        "png_uint_16 year" in png_time_struct.
+ *
+ *    The string is
+ *        "char time_buffer[29]" in png_struct.  This is no longer used
+ *    in libpng-1.6.x and will be removed from libpng-1.7.0.
+ *
+ *    There are seven time-related functions:
+ *        png.c: png_convert_to_rfc_1123_buffer() in png.c
+ *          (formerly png_convert_to_rfc_1123() prior to libpng-1.5.x and
+ *          png_convert_to_rfc_1152() in error prior to libpng-0.98)
+ *        png_convert_from_struct_tm() in pngwrite.c, called in pngwrite.c
+ *        png_convert_from_time_t() in pngwrite.c
+ *        png_get_tIME() in pngget.c
+ *        png_handle_tIME() in pngrutil.c, called in pngread.c
+ *        png_set_tIME() in pngset.c
+ *        png_write_tIME() in pngwutil.c, called in pngwrite.c
+ *
+ *    All handle dates properly in a Y2K environment.  The
+ *    png_convert_from_time_t() function calls gmtime() to convert from system
+ *    clock time, which returns (year - 1900), which we properly convert to
+ *    the full 4-digit year.  There is a possibility that libpng applications
+ *    are not passing 4-digit years into the png_convert_to_rfc_1123_buffer()
+ *    function, or that they are incorrectly passing only a 2-digit year
+ *    instead of "year - 1900" into the png_convert_from_struct_tm() function,
+ *    but this is not under our control.  The libpng documentation has always
+ *    stated that it works with 4-digit years, and the APIs have been
+ *    documented as such.
+ *
+ *    The tIME chunk itself is also Y2K compliant.  It uses a 2-byte unsigned
+ *    integer to hold the year, and can hold years as large as 65535.
+ *
+ *    zlib, upon which libpng depends, is also Y2K compliant.  It contains
+ *    no date-related code.
+ *
+ *       Glenn Randers-Pehrson
+ *       libpng maintainer
+ *       PNG Development Group
+ */
+
+#ifndef PNG_H
+#define PNG_H
+
+/* This is not the place to learn how to use libpng. The file libpng-manual.txt
+ * describes how to use libpng, and the file example.c summarizes it
+ * with some code on which to build.  This file is useful for looking
+ * at the actual function definitions and structure components.
+ *
+ * If you just need to read a PNG file and don't want to read the documentation
+ * skip to the end of this file and read the section entitled 'simplified API'.
+ */
+
+/* Version information for png.h - this should match the version in png.c */
+#define PNG_LIBPNG_VER_STRING "1.6.1"
+#define PNG_HEADER_VERSION_STRING \
+     " libpng version 1.6.1 - March 28, 2013\n"
+
+#define PNG_LIBPNG_VER_SONUM   16
+#define PNG_LIBPNG_VER_DLLNUM  16
+
+/* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */
+#define PNG_LIBPNG_VER_MAJOR   1
+#define PNG_LIBPNG_VER_MINOR   6
+#define PNG_LIBPNG_VER_RELEASE 1
+
+/* This should match the numeric part of the final component of
+ * PNG_LIBPNG_VER_STRING, omitting any leading zero:
+ */
+
+#define PNG_LIBPNG_VER_BUILD  0
+
+/* Release Status */
+#define PNG_LIBPNG_BUILD_ALPHA    1
+#define PNG_LIBPNG_BUILD_BETA     2
+#define PNG_LIBPNG_BUILD_RC       3
+#define PNG_LIBPNG_BUILD_STABLE   4
+#define PNG_LIBPNG_BUILD_RELEASE_STATUS_MASK 7
+
+/* Release-Specific Flags */
+#define PNG_LIBPNG_BUILD_PATCH    8 /* Can be OR'ed with
+                                       PNG_LIBPNG_BUILD_STABLE only */
+#define PNG_LIBPNG_BUILD_PRIVATE 16 /* Cannot be OR'ed with
+                                       PNG_LIBPNG_BUILD_SPECIAL */
+#define PNG_LIBPNG_BUILD_SPECIAL 32 /* Cannot be OR'ed with
+                                       PNG_LIBPNG_BUILD_PRIVATE */
+
+#define PNG_LIBPNG_BUILD_BASE_TYPE PNG_LIBPNG_BUILD_STABLE
+
+/* Careful here.  At one time, Guy wanted to use 082, but that would be octal.
+ * We must not include leading zeros.
+ * Versions 0.7 through 1.0.0 were in the range 0 to 100 here (only
+ * version 1.0.0 was mis-numbered 100 instead of 10000).  From
+ * version 1.0.1 it's    xxyyzz, where x=major, y=minor, z=release
+ */
+#define PNG_LIBPNG_VER 10601 /* 1.6.1 */
+
+/* Library configuration: these options cannot be changed after
+ * the library has been built.
+ */
+#ifndef PNGLCONF_H
+    /* If pnglibconf.h is missing, you can
+     * copy scripts/pnglibconf.h.prebuilt to pnglibconf.h
+     */
+#   include "pnglibconf.h"
+#endif
+
+#ifndef PNG_VERSION_INFO_ONLY
+   /* Machine specific configuration. */
+#  include "pngconf.h"
+#endif
+
+/*
+ * Added at libpng-1.2.8
+ *
+ * Ref MSDN: Private as priority over Special
+ * VS_FF_PRIVATEBUILD File *was not* built using standard release
+ * procedures. If this value is given, the StringFileInfo block must
+ * contain a PrivateBuild string.
+ *
+ * VS_FF_SPECIALBUILD File *was* built by the original company using
+ * standard release procedures but is a variation of the standard
+ * file of the same version number. If this value is given, the
+ * StringFileInfo block must contain a SpecialBuild string.
+ */
+
+#ifdef PNG_USER_PRIVATEBUILD /* From pnglibconf.h */
+#  define PNG_LIBPNG_BUILD_TYPE \
+       (PNG_LIBPNG_BUILD_BASE_TYPE | PNG_LIBPNG_BUILD_PRIVATE)
+#else
+#  ifdef PNG_LIBPNG_SPECIALBUILD
+#    define PNG_LIBPNG_BUILD_TYPE \
+         (PNG_LIBPNG_BUILD_BASE_TYPE | PNG_LIBPNG_BUILD_SPECIAL)
+#  else
+#    define PNG_LIBPNG_BUILD_TYPE (PNG_LIBPNG_BUILD_BASE_TYPE)
+#  endif
+#endif
+
+#ifndef PNG_VERSION_INFO_ONLY
+
+/* Inhibit C++ name-mangling for libpng functions but not for system calls. */
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Version information for C files, stored in png.c.  This had better match
+ * the version above.
+ */
+#define png_libpng_ver png_get_header_ver(NULL)
+
+/* This file is arranged in several sections:
+ *
+ * 1. Any configuration options that can be specified by for the application
+ *    code when it is built.  (Build time configuration is in pnglibconf.h)
+ * 2. Type definitions (base types are defined in pngconf.h), structure
+ *    definitions.
+ * 3. Exported library functions.
+ * 4. Simplified API.
+ *
+ * The library source code has additional files (principally pngpriv.h) that
+ * allow configuration of the library.
+ */
+/* Section 1: run time configuration
+ * See pnglibconf.h for build time configuration
+ *
+ * Run time configuration allows the application to choose between
+ * implementations of certain arithmetic APIs.  The default is set
+ * at build time and recorded in pnglibconf.h, but it is safe to
+ * override these (and only these) settings.  Note that this won't
+ * change what the library does, only application code, and the
+ * settings can (and probably should) be made on a per-file basis
+ * by setting the #defines before including png.h
+ *
+ * Use macros to read integers from PNG data or use the exported
+ * functions?
+ *   PNG_USE_READ_MACROS: use the macros (see below)  Note that
+ *     the macros evaluate their argument multiple times.
+ *   PNG_NO_USE_READ_MACROS: call the relevant library function.
+ *
+ * Use the alternative algorithm for compositing alpha samples that
+ * does not use division?
+ *   PNG_READ_COMPOSITE_NODIV_SUPPORTED: use the 'no division'
+ *      algorithm.
+ *   PNG_NO_READ_COMPOSITE_NODIV: use the 'division' algorithm.
+ *
+ * How to handle benign errors if PNG_ALLOW_BENIGN_ERRORS is
+ * false?
+ *   PNG_ALLOW_BENIGN_ERRORS: map calls to the benign error
+ *      APIs to png_warning.
+ * Otherwise the calls are mapped to png_error.
+ */
+
+/* Section 2: type definitions, including structures and compile time
+ * constants.
+ * See pngconf.h for base types that vary by machine/system
+ */
+
+/* This triggers a compiler error in png.c, if png.c and png.h
+ * do not agree upon the version number.
+ */
+typedef char* png_libpng_version_1_6_1;
+
+/* Basic control structions.  Read libpng-manual.txt or libpng.3 for more info.
+ *
+ * png_struct is the cache of information used while reading or writing a single
+ * PNG file.  One of these is always required, although the simplified API
+ * (below) hides the creation and destruction of it.
+ */
+typedef struct png_struct_def png_struct;
+typedef const png_struct * png_const_structp;
+typedef png_struct * png_structp;
+typedef png_struct * * png_structpp;
+
+/* png_info contains information read from or to be written to a PNG file.  One
+ * or more of these must exist while reading or creating a PNG file.  The
+ * information is not used by libpng during read but is used to control what
+ * gets written when a PNG file is created.  "png_get_" function calls read
+ * information during read and "png_set_" functions calls write information
+ * when creating a PNG.
+ * been moved into a separate header file that is not accessible to
+ * applications.  Read libpng-manual.txt or libpng.3 for more info.
+ */
+typedef struct png_info_def png_info;
+typedef png_info * png_infop;
+typedef const png_info * png_const_infop;
+typedef png_info * * png_infopp;
+
+/* Types with names ending 'p' are pointer types.  The corresponding types with
+ * names ending 'rp' are identical pointer types except that the pointer is
+ * marked 'restrict', which means that it is the only pointer to the object
+ * passed to the function.  Applications should not use the 'restrict' types;
+ * it is always valid to pass 'p' to a pointer with a function argument of the
+ * corresponding 'rp' type.  Different compilers have different rules with
+ * regard to type matching in the presence of 'restrict'.  For backward
+ * compatibility libpng callbacks never have 'restrict' in their parameters and,
+ * consequentially, writing portable application code is extremely difficult if
+ * an attempt is made to use 'restrict'.
+ */
+typedef png_struct * PNG_RESTRICT png_structrp;
+typedef const png_struct * PNG_RESTRICT png_const_structrp;
+typedef png_info * PNG_RESTRICT png_inforp;
+typedef const png_info * PNG_RESTRICT png_const_inforp;
+
+/* Three color definitions.  The order of the red, green, and blue, (and the
+ * exact size) is not important, although the size of the fields need to
+ * be png_byte or png_uint_16 (as defined below).
+ */
+typedef struct png_color_struct
+{
+   png_byte red;
+   png_byte green;
+   png_byte blue;
+} png_color;
+typedef png_color * png_colorp;
+typedef const png_color * png_const_colorp;
+typedef png_color * * png_colorpp;
+
+typedef struct png_color_16_struct
+{
+   png_byte index;    /* used for palette files */
+   png_uint_16 red;   /* for use in red green blue files */
+   png_uint_16 green;
+   png_uint_16 blue;
+   png_uint_16 gray;  /* for use in grayscale files */
+} png_color_16;
+typedef png_color_16 * png_color_16p;
+typedef const png_color_16 * png_const_color_16p;
+typedef png_color_16 * * png_color_16pp;
+
+typedef struct png_color_8_struct
+{
+   png_byte red;   /* for use in red green blue files */
+   png_byte green;
+   png_byte blue;
+   png_byte gray;  /* for use in grayscale files */
+   png_byte alpha; /* for alpha channel files */
+} png_color_8;
+typedef png_color_8 * png_color_8p;
+typedef const png_color_8 * png_const_color_8p;
+typedef png_color_8 * * png_color_8pp;
+
+/*
+ * The following two structures are used for the in-core representation
+ * of sPLT chunks.
+ */
+typedef struct png_sPLT_entry_struct
+{
+   png_uint_16 red;
+   png_uint_16 green;
+   png_uint_16 blue;
+   png_uint_16 alpha;
+   png_uint_16 frequency;
+} png_sPLT_entry;
+typedef png_sPLT_entry * png_sPLT_entryp;
+typedef const png_sPLT_entry * png_const_sPLT_entryp;
+typedef png_sPLT_entry * * png_sPLT_entrypp;
+
+/*  When the depth of the sPLT palette is 8 bits, the color and alpha samples
+ *  occupy the LSB of their respective members, and the MSB of each member
+ *  is zero-filled.  The frequency member always occupies the full 16 bits.
+ */
+
+typedef struct png_sPLT_struct
+{
+   png_charp name;           /* palette name */
+   png_byte depth;           /* depth of palette samples */
+   png_sPLT_entryp entries;  /* palette entries */
+   png_int_32 nentries;      /* number of palette entries */
+} png_sPLT_t;
+typedef png_sPLT_t * png_sPLT_tp;
+typedef const png_sPLT_t * png_const_sPLT_tp;
+typedef png_sPLT_t * * png_sPLT_tpp;
+
+#ifdef PNG_TEXT_SUPPORTED
+/* png_text holds the contents of a text/ztxt/itxt chunk in a PNG file,
+ * and whether that contents is compressed or not.  The "key" field
+ * points to a regular zero-terminated C string.  The "text" fields can be a
+ * regular C string, an empty string, or a NULL pointer.
+ * However, the structure returned by png_get_text() will always contain
+ * the "text" field as a regular zero-terminated C string (possibly
+ * empty), never a NULL pointer, so it can be safely used in printf() and
+ * other string-handling functions.  Note that the "itxt_length", "lang", and
+ * "lang_key" members of the structure only exist when the library is built
+ * with iTXt chunk support.  Prior to libpng-1.4.0 the library was built by
+ * default without iTXt support. Also note that when iTXt *is* supported,
+ * the "lang" and "lang_key" fields contain NULL pointers when the
+ * "compression" field contains * PNG_TEXT_COMPRESSION_NONE or
+ * PNG_TEXT_COMPRESSION_zTXt. Note that the "compression value" is not the
+ * same as what appears in the PNG tEXt/zTXt/iTXt chunk's "compression flag"
+ * which is always 0 or 1, or its "compression method" which is always 0.
+ */
+typedef struct png_text_struct
+{
+   int  compression;       /* compression value:
+                             -1: tEXt, none
+                              0: zTXt, deflate
+                              1: iTXt, none
+                              2: iTXt, deflate  */
+   png_charp key;          /* keyword, 1-79 character description of "text" */
+   png_charp text;         /* comment, may be an empty string (ie "")
+                              or a NULL pointer */
+   png_size_t text_length; /* length of the text string */
+   png_size_t itxt_length; /* length of the itxt string */
+   png_charp lang;         /* language code, 0-79 characters
+                              or a NULL pointer */
+   png_charp lang_key;     /* keyword translated UTF-8 string, 0 or more
+                              chars or a NULL pointer */
+} png_text;
+typedef png_text * png_textp;
+typedef const png_text * png_const_textp;
+typedef png_text * * png_textpp;
+#endif
+
+/* Supported compression types for text in PNG files (tEXt, and zTXt).
+ * The values of the PNG_TEXT_COMPRESSION_ defines should NOT be changed. */
+#define PNG_TEXT_COMPRESSION_NONE_WR -3
+#define PNG_TEXT_COMPRESSION_zTXt_WR -2
+#define PNG_TEXT_COMPRESSION_NONE    -1
+#define PNG_TEXT_COMPRESSION_zTXt     0
+#define PNG_ITXT_COMPRESSION_NONE     1
+#define PNG_ITXT_COMPRESSION_zTXt     2
+#define PNG_TEXT_COMPRESSION_LAST     3  /* Not a valid value */
+
+/* png_time is a way to hold the time in an machine independent way.
+ * Two conversions are provided, both from time_t and struct tm.  There
+ * is no portable way to convert to either of these structures, as far
+ * as I know.  If you know of a portable way, send it to me.  As a side
+ * note - PNG has always been Year 2000 compliant!
+ */
+typedef struct png_time_struct
+{
+   png_uint_16 year; /* full year, as in, 1995 */
+   png_byte month;   /* month of year, 1 - 12 */
+   png_byte day;     /* day of month, 1 - 31 */
+   png_byte hour;    /* hour of day, 0 - 23 */
+   png_byte minute;  /* minute of hour, 0 - 59 */
+   png_byte second;  /* second of minute, 0 - 60 (for leap seconds) */
+} png_time;
+typedef png_time * png_timep;
+typedef const png_time * png_const_timep;
+typedef png_time * * png_timepp;
+
+#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED
+/* png_unknown_chunk is a structure to hold queued chunks for which there is
+ * no specific support.  The idea is that we can use this to queue
+ * up private chunks for output even though the library doesn't actually
+ * know about their semantics.
+ *
+ * The data in the structure is set by libpng on read and used on write.
+ */
+typedef struct png_unknown_chunk_t
+{
+    png_byte name[5]; /* Textual chunk name with '\0' terminator */
+    png_byte *data;   /* Data, should not be modified on read! */
+    png_size_t size;
+
+    /* On write 'location' must be set using the flag values listed below.
+     * Notice that on read it is set by libpng however the values stored have
+     * more bits set than are listed below.  Always treat the value as a
+     * bitmask.  On write set only one bit - setting multiple bits may cause the
+     * chunk to be written in multiple places.
+     */
+    png_byte location; /* mode of operation at read time */
+}
+png_unknown_chunk;
+
+typedef png_unknown_chunk * png_unknown_chunkp;
+typedef const png_unknown_chunk * png_const_unknown_chunkp;
+typedef png_unknown_chunk * * png_unknown_chunkpp;
+#endif
+
+/* Flag values for the unknown chunk location byte. */
+#define PNG_HAVE_IHDR  0x01
+#define PNG_HAVE_PLTE  0x02
+#define PNG_AFTER_IDAT 0x08
+
+/* Maximum positive integer used in PNG is (2^31)-1 */
+#define PNG_UINT_31_MAX ((png_uint_32)0x7fffffffL)
+#define PNG_UINT_32_MAX ((png_uint_32)(-1))
+#define PNG_SIZE_MAX ((png_size_t)(-1))
+
+/* These are constants for fixed point values encoded in the
+ * PNG specification manner (x100000)
+ */
+#define PNG_FP_1    100000
+#define PNG_FP_HALF  50000
+#define PNG_FP_MAX  ((png_fixed_point)0x7fffffffL)
+#define PNG_FP_MIN  (-PNG_FP_MAX)
+
+/* These describe the color_type field in png_info. */
+/* color type masks */
+#define PNG_COLOR_MASK_PALETTE    1
+#define PNG_COLOR_MASK_COLOR      2
+#define PNG_COLOR_MASK_ALPHA      4
+
+/* color types.  Note that not all combinations are legal */
+#define PNG_COLOR_TYPE_GRAY 0
+#define PNG_COLOR_TYPE_PALETTE  (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE)
+#define PNG_COLOR_TYPE_RGB        (PNG_COLOR_MASK_COLOR)
+#define PNG_COLOR_TYPE_RGB_ALPHA  (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_ALPHA)
+#define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA)
+/* aliases */
+#define PNG_COLOR_TYPE_RGBA  PNG_COLOR_TYPE_RGB_ALPHA
+#define PNG_COLOR_TYPE_GA  PNG_COLOR_TYPE_GRAY_ALPHA
+
+/* This is for compression type. PNG 1.0-1.2 only define the single type. */
+#define PNG_COMPRESSION_TYPE_BASE 0 /* Deflate method 8, 32K window */
+#define PNG_COMPRESSION_TYPE_DEFAULT PNG_COMPRESSION_TYPE_BASE
+
+/* This is for filter type. PNG 1.0-1.2 only define the single type. */
+#define PNG_FILTER_TYPE_BASE      0 /* Single row per-byte filtering */
+#define PNG_INTRAPIXEL_DIFFERENCING 64 /* Used only in MNG datastreams */
+#define PNG_FILTER_TYPE_DEFAULT   PNG_FILTER_TYPE_BASE
+
+/* These are for the interlacing type.  These values should NOT be changed. */
+#define PNG_INTERLACE_NONE        0 /* Non-interlaced image */
+#define PNG_INTERLACE_ADAM7       1 /* Adam7 interlacing */
+#define PNG_INTERLACE_LAST        2 /* Not a valid value */
+
+/* These are for the oFFs chunk.  These values should NOT be changed. */
+#define PNG_OFFSET_PIXEL          0 /* Offset in pixels */
+#define PNG_OFFSET_MICROMETER     1 /* Offset in micrometers (1/10^6 meter) */
+#define PNG_OFFSET_LAST           2 /* Not a valid value */
+
+/* These are for the pCAL chunk.  These values should NOT be changed. */
+#define PNG_EQUATION_LINEAR       0 /* Linear transformation */
+#define PNG_EQUATION_BASE_E       1 /* Exponential base e transform */
+#define PNG_EQUATION_ARBITRARY    2 /* Arbitrary base exponential transform */
+#define PNG_EQUATION_HYPERBOLIC   3 /* Hyperbolic sine transformation */
+#define PNG_EQUATION_LAST         4 /* Not a valid value */
+
+/* These are for the sCAL chunk.  These values should NOT be changed. */
+#define PNG_SCALE_UNKNOWN         0 /* unknown unit (image scale) */
+#define PNG_SCALE_METER           1 /* meters per pixel */
+#define PNG_SCALE_RADIAN          2 /* radians per pixel */
+#define PNG_SCALE_LAST            3 /* Not a valid value */
+
+/* These are for the pHYs chunk.  These values should NOT be changed. */
+#define PNG_RESOLUTION_UNKNOWN    0 /* pixels/unknown unit (aspect ratio) */
+#define PNG_RESOLUTION_METER      1 /* pixels/meter */
+#define PNG_RESOLUTION_LAST       2 /* Not a valid value */
+
+/* These are for the sRGB chunk.  These values should NOT be changed. */
+#define PNG_sRGB_INTENT_PERCEPTUAL 0
+#define PNG_sRGB_INTENT_RELATIVE   1
+#define PNG_sRGB_INTENT_SATURATION 2
+#define PNG_sRGB_INTENT_ABSOLUTE   3
+#define PNG_sRGB_INTENT_LAST       4 /* Not a valid value */
+
+/* This is for text chunks */
+#define PNG_KEYWORD_MAX_LENGTH     79
+
+/* Maximum number of entries in PLTE/sPLT/tRNS arrays */
+#define PNG_MAX_PALETTE_LENGTH    256
+
+/* These determine if an ancillary chunk's data has been successfully read
+ * from the PNG header, or if the application has filled in the corresponding
+ * data in the info_struct to be written into the output file.  The values
+ * of the PNG_INFO_<chunk> defines should NOT be changed.
+ */
+#define PNG_INFO_gAMA 0x0001
+#define PNG_INFO_sBIT 0x0002
+#define PNG_INFO_cHRM 0x0004
+#define PNG_INFO_PLTE 0x0008
+#define PNG_INFO_tRNS 0x0010
+#define PNG_INFO_bKGD 0x0020
+#define PNG_INFO_hIST 0x0040
+#define PNG_INFO_pHYs 0x0080
+#define PNG_INFO_oFFs 0x0100
+#define PNG_INFO_tIME 0x0200
+#define PNG_INFO_pCAL 0x0400
+#define PNG_INFO_sRGB 0x0800   /* GR-P, 0.96a */
+#define PNG_INFO_iCCP 0x1000   /* ESR, 1.0.6 */
+#define PNG_INFO_sPLT 0x2000   /* ESR, 1.0.6 */
+#define PNG_INFO_sCAL 0x4000   /* ESR, 1.0.6 */
+#define PNG_INFO_IDAT 0x8000   /* ESR, 1.0.6 */
+
+/* This is used for the transformation routines, as some of them
+ * change these values for the row.  It also should enable using
+ * the routines for other purposes.
+ */
+typedef struct png_row_info_struct
+{
+   png_uint_32 width;    /* width of row */
+   png_size_t rowbytes;  /* number of bytes in row */
+   png_byte color_type;  /* color type of row */
+   png_byte bit_depth;   /* bit depth of row */
+   png_byte channels;    /* number of channels (1, 2, 3, or 4) */
+   png_byte pixel_depth; /* bits per pixel (depth * channels) */
+} png_row_info;
+
+typedef png_row_info * png_row_infop;
+typedef png_row_info * * png_row_infopp;
+
+/* These are the function types for the I/O functions and for the functions
+ * that allow the user to override the default I/O functions with his or her
+ * own.  The png_error_ptr type should match that of user-supplied warning
+ * and error functions, while the png_rw_ptr type should match that of the
+ * user read/write data functions.  Note that the 'write' function must not
+ * modify the buffer it is passed. The 'read' function, on the other hand, is
+ * expected to return the read data in the buffer.
+ */
+typedef PNG_CALLBACK(void, *png_error_ptr, (png_structp, png_const_charp));
+typedef PNG_CALLBACK(void, *png_rw_ptr, (png_structp, png_bytep, png_size_t));
+typedef PNG_CALLBACK(void, *png_flush_ptr, (png_structp));
+typedef PNG_CALLBACK(void, *png_read_status_ptr, (png_structp, png_uint_32,
+    int));
+typedef PNG_CALLBACK(void, *png_write_status_ptr, (png_structp, png_uint_32,
+    int));
+
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+typedef PNG_CALLBACK(void, *png_progressive_info_ptr, (png_structp, png_infop));
+typedef PNG_CALLBACK(void, *png_progressive_end_ptr, (png_structp, png_infop));
+
+/* The following callback receives png_uint_32 row_number, int pass for the
+ * png_bytep data of the row.  When transforming an interlaced image the
+ * row number is the row number within the sub-image of the interlace pass, so
+ * the value will increase to the height of the sub-image (not the full image)
+ * then reset to 0 for the next pass.
+ *
+ * Use PNG_ROW_FROM_PASS_ROW(row, pass) and PNG_COL_FROM_PASS_COL(col, pass) to
+ * find the output pixel (x,y) given an interlaced sub-image pixel
+ * (row,col,pass).  (See below for these macros.)
+ */
+typedef PNG_CALLBACK(void, *png_progressive_row_ptr, (png_structp, png_bytep,
+    png_uint_32, int));
+#endif
+
+#if defined(PNG_READ_USER_TRANSFORM_SUPPORTED) || \
+    defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED)
+typedef PNG_CALLBACK(void, *png_user_transform_ptr, (png_structp, png_row_infop,
+    png_bytep));
+#endif
+
+#ifdef PNG_USER_CHUNKS_SUPPORTED
+typedef PNG_CALLBACK(int, *png_user_chunk_ptr, (png_structp,
+    png_unknown_chunkp));
+#endif
+#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED
+/* not used anywhere */
+/* typedef PNG_CALLBACK(void, *png_unknown_chunk_ptr, (png_structp)); */
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+/* This must match the function definition in <setjmp.h>, and the application
+ * must include this before png.h to obtain the definition of jmp_buf.  The
+ * function is required to be PNG_NORETURN, but this is not checked.  If the
+ * function does return the application will crash via an abort() or similar
+ * system level call.
+ *
+ * If you get a warning here while building the library you may need to make
+ * changes to ensure that pnglibconf.h records the calling convention used by
+ * your compiler.  This may be very difficult - try using a different compiler
+ * to build the library!
+ */
+PNG_FUNCTION(void, (PNGCAPI *png_longjmp_ptr), PNGARG((jmp_buf, int)), typedef);
+#endif
+
+/* Transform masks for the high-level interface */
+#define PNG_TRANSFORM_IDENTITY       0x0000    /* read and write */
+#define PNG_TRANSFORM_STRIP_16       0x0001    /* read only */
+#define PNG_TRANSFORM_STRIP_ALPHA    0x0002    /* read only */
+#define PNG_TRANSFORM_PACKING        0x0004    /* read and write */
+#define PNG_TRANSFORM_PACKSWAP       0x0008    /* read and write */
+#define PNG_TRANSFORM_EXPAND         0x0010    /* read only */
+#define PNG_TRANSFORM_INVERT_MONO    0x0020    /* read and write */
+#define PNG_TRANSFORM_SHIFT          0x0040    /* read and write */
+#define PNG_TRANSFORM_BGR            0x0080    /* read and write */
+#define PNG_TRANSFORM_SWAP_ALPHA     0x0100    /* read and write */
+#define PNG_TRANSFORM_SWAP_ENDIAN    0x0200    /* read and write */
+#define PNG_TRANSFORM_INVERT_ALPHA   0x0400    /* read and write */
+#define PNG_TRANSFORM_STRIP_FILLER   0x0800    /* write only */
+/* Added to libpng-1.2.34 */
+#define PNG_TRANSFORM_STRIP_FILLER_BEFORE PNG_TRANSFORM_STRIP_FILLER
+#define PNG_TRANSFORM_STRIP_FILLER_AFTER 0x1000 /* write only */
+/* Added to libpng-1.4.0 */
+#define PNG_TRANSFORM_GRAY_TO_RGB   0x2000      /* read only */
+/* Added to libpng-1.5.4 */
+#define PNG_TRANSFORM_EXPAND_16     0x4000      /* read only */
+#define PNG_TRANSFORM_SCALE_16      0x8000      /* read only */
+
+/* Flags for MNG supported features */
+#define PNG_FLAG_MNG_EMPTY_PLTE     0x01
+#define PNG_FLAG_MNG_FILTER_64      0x04
+#define PNG_ALL_MNG_FEATURES        0x05
+
+/* NOTE: prior to 1.5 these functions had no 'API' style declaration,
+ * this allowed the zlib default functions to be used on Windows
+ * platforms.  In 1.5 the zlib default malloc (which just calls malloc and
+ * ignores the first argument) should be completely compatible with the
+ * following.
+ */
+typedef PNG_CALLBACK(png_voidp, *png_malloc_ptr, (png_structp,
+    png_alloc_size_t));
+typedef PNG_CALLBACK(void, *png_free_ptr, (png_structp, png_voidp));
+
+/* Section 3: exported functions
+ * Here are the function definitions most commonly used.  This is not
+ * the place to find out how to use libpng.  See libpng-manual.txt for the
+ * full explanation, see example.c for the summary.  This just provides
+ * a simple one line description of the use of each function.
+ *
+ * The PNG_EXPORT() and PNG_EXPORTA() macros used below are defined in
+ * pngconf.h and in the *.dfn files in the scripts directory.
+ *
+ *   PNG_EXPORT(ordinal, type, name, (args));
+ *
+ *       ordinal:    ordinal that is used while building
+ *                   *.def files. The ordinal value is only
+ *                   relevant when preprocessing png.h with
+ *                   the *.dfn files for building symbol table
+ *                   entries, and are removed by pngconf.h.
+ *       type:       return type of the function
+ *       name:       function name
+ *       args:       function arguments, with types
+ *
+ * When we wish to append attributes to a function prototype we use
+ * the PNG_EXPORTA() macro instead.
+ *
+ *   PNG_EXPORTA(ordinal, type, name, (args), attributes);
+ *
+ *       ordinal, type, name, and args: same as in PNG_EXPORT().
+ *       attributes: function attributes
+ */
+
+/* Returns the version number of the library */
+PNG_EXPORT(1, png_uint_32, png_access_version_number, (void));
+
+/* Tell lib we have already handled the first <num_bytes> magic bytes.
+ * Handling more than 8 bytes from the beginning of the file is an error.
+ */
+PNG_EXPORT(2, void, png_set_sig_bytes, (png_structrp png_ptr, int num_bytes));
+
+/* Check sig[start] through sig[start + num_to_check - 1] to see if it's a
+ * PNG file.  Returns zero if the supplied bytes match the 8-byte PNG
+ * signature, and non-zero otherwise.  Having num_to_check == 0 or
+ * start > 7 will always fail (ie return non-zero).
+ */
+PNG_EXPORT(3, int, png_sig_cmp, (png_const_bytep sig, png_size_t start,
+    png_size_t num_to_check));
+
+/* Simple signature checking function.  This is the same as calling
+ * png_check_sig(sig, n) := !png_sig_cmp(sig, 0, n).
+ */
+#define png_check_sig(sig, n) !png_sig_cmp((sig), 0, (n))
+
+/* Allocate and initialize png_ptr struct for reading, and any other memory. */
+PNG_EXPORTA(4, png_structp, png_create_read_struct,
+    (png_const_charp user_png_ver, png_voidp error_ptr,
+    png_error_ptr error_fn, png_error_ptr warn_fn),
+    PNG_ALLOCATED);
+
+/* Allocate and initialize png_ptr struct for writing, and any other memory */
+PNG_EXPORTA(5, png_structp, png_create_write_struct,
+    (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn,
+    png_error_ptr warn_fn),
+    PNG_ALLOCATED);
+
+PNG_EXPORT(6, png_size_t, png_get_compression_buffer_size,
+    (png_const_structrp png_ptr));
+
+PNG_EXPORT(7, void, png_set_compression_buffer_size, (png_structrp png_ptr,
+    png_size_t size));
+
+/* Moved from pngconf.h in 1.4.0 and modified to ensure setjmp/longjmp
+ * match up.
+ */
+#ifdef PNG_SETJMP_SUPPORTED
+/* This function returns the jmp_buf built in to *png_ptr.  It must be
+ * supplied with an appropriate 'longjmp' function to use on that jmp_buf
+ * unless the default error function is overridden in which case NULL is
+ * acceptable.  The size of the jmp_buf is checked against the actual size
+ * allocated by the library - the call will return NULL on a mismatch
+ * indicating an ABI mismatch.
+ */
+PNG_EXPORT(8, jmp_buf*, png_set_longjmp_fn, (png_structrp png_ptr,
+    png_longjmp_ptr longjmp_fn, size_t jmp_buf_size));
+#  define png_jmpbuf(png_ptr) \
+      (*png_set_longjmp_fn((png_ptr), longjmp, (sizeof (jmp_buf))))
+#else
+#  define png_jmpbuf(png_ptr) \
+      (LIBPNG_WAS_COMPILED_WITH__PNG_NO_SETJMP)
+#endif
+/* This function should be used by libpng applications in place of
+ * longjmp(png_ptr->jmpbuf, val).  If longjmp_fn() has been set, it
+ * will use it; otherwise it will call PNG_ABORT().  This function was
+ * added in libpng-1.5.0.
+ */
+PNG_EXPORTA(9, void, png_longjmp, (png_const_structrp png_ptr, int val),
+    PNG_NORETURN);
+
+#ifdef PNG_READ_SUPPORTED
+/* Reset the compression stream */
+PNG_EXPORTA(10, int, png_reset_zstream, (png_structrp png_ptr), PNG_DEPRECATED);
+#endif
+
+/* New functions added in libpng-1.0.2 (not enabled by default until 1.2.0) */
+#ifdef PNG_USER_MEM_SUPPORTED
+PNG_EXPORTA(11, png_structp, png_create_read_struct_2,
+    (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn,
+    png_error_ptr warn_fn,
+    png_voidp mem_ptr, png_malloc_ptr malloc_fn, png_free_ptr free_fn),
+    PNG_ALLOCATED);
+PNG_EXPORTA(12, png_structp, png_create_write_struct_2,
+    (png_const_charp user_png_ver, png_voidp error_ptr, png_error_ptr error_fn,
+    png_error_ptr warn_fn,
+    png_voidp mem_ptr, png_malloc_ptr malloc_fn, png_free_ptr free_fn),
+    PNG_ALLOCATED);
+#endif
+
+/* Write the PNG file signature. */
+PNG_EXPORT(13, void, png_write_sig, (png_structrp png_ptr));
+
+/* Write a PNG chunk - size, type, (optional) data, CRC. */
+PNG_EXPORT(14, void, png_write_chunk, (png_structrp png_ptr, png_const_bytep
+    chunk_name, png_const_bytep data, png_size_t length));
+
+/* Write the start of a PNG chunk - length and chunk name. */
+PNG_EXPORT(15, void, png_write_chunk_start, (png_structrp png_ptr,
+    png_const_bytep chunk_name, png_uint_32 length));
+
+/* Write the data of a PNG chunk started with png_write_chunk_start(). */
+PNG_EXPORT(16, void, png_write_chunk_data, (png_structrp png_ptr,
+    png_const_bytep data, png_size_t length));
+
+/* Finish a chunk started with png_write_chunk_start() (includes CRC). */
+PNG_EXPORT(17, void, png_write_chunk_end, (png_structrp png_ptr));
+
+/* Allocate and initialize the info structure */
+PNG_EXPORTA(18, png_infop, png_create_info_struct, (png_const_structrp png_ptr),
+    PNG_ALLOCATED);
+
+/* DEPRECATED: this function allowed init structures to be created using the
+ * default allocation method (typically malloc).  Use is deprecated in 1.6.0 and
+ * the API will be removed in the future.
+ */
+PNG_EXPORTA(19, void, png_info_init_3, (png_infopp info_ptr,
+    png_size_t png_info_struct_size), PNG_DEPRECATED);
+
+/* Writes all the PNG information before the image. */
+PNG_EXPORT(20, void, png_write_info_before_PLTE,
+    (png_structrp png_ptr, png_const_inforp info_ptr));
+PNG_EXPORT(21, void, png_write_info,
+    (png_structrp png_ptr, png_const_inforp info_ptr));
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Read the information before the actual image data. */
+PNG_EXPORT(22, void, png_read_info,
+    (png_structrp png_ptr, png_inforp info_ptr));
+#endif
+
+#ifdef PNG_TIME_RFC1123_SUPPORTED
+   /* Convert to a US string format: there is no localization support in this
+    * routine.  The original implementation used a 29 character buffer in
+    * png_struct, this will be removed in future versions.
+    */
+#if PNG_LIBPNG_VER < 10700
+/* To do: remove this from libpng17 (and from libpng17/png.c and pngstruct.h) */
+PNG_EXPORTA(23, png_const_charp, png_convert_to_rfc1123, (png_structrp png_ptr,
+    png_const_timep ptime),PNG_DEPRECATED);
+#endif
+PNG_EXPORT(241, int, png_convert_to_rfc1123_buffer, (char out[29],
+    png_const_timep ptime));
+#endif
+
+#ifdef PNG_CONVERT_tIME_SUPPORTED
+/* Convert from a struct tm to png_time */
+PNG_EXPORT(24, void, png_convert_from_struct_tm, (png_timep ptime,
+    const struct tm * ttime));
+
+/* Convert from time_t to png_time.  Uses gmtime() */
+PNG_EXPORT(25, void, png_convert_from_time_t, (png_timep ptime, time_t ttime));
+#endif /* PNG_CONVERT_tIME_SUPPORTED */
+
+#ifdef PNG_READ_EXPAND_SUPPORTED
+/* Expand data to 24-bit RGB, or 8-bit grayscale, with alpha if available. */
+PNG_EXPORT(26, void, png_set_expand, (png_structrp png_ptr));
+PNG_EXPORT(27, void, png_set_expand_gray_1_2_4_to_8, (png_structrp png_ptr));
+PNG_EXPORT(28, void, png_set_palette_to_rgb, (png_structrp png_ptr));
+PNG_EXPORT(29, void, png_set_tRNS_to_alpha, (png_structrp png_ptr));
+#endif
+
+#ifdef PNG_READ_EXPAND_16_SUPPORTED
+/* Expand to 16-bit channels, forces conversion of palette to RGB and expansion
+ * of a tRNS chunk if present.
+ */
+PNG_EXPORT(221, void, png_set_expand_16, (png_structrp png_ptr));
+#endif
+
+#if defined(PNG_READ_BGR_SUPPORTED) || defined(PNG_WRITE_BGR_SUPPORTED)
+/* Use blue, green, red order for pixels. */
+PNG_EXPORT(30, void, png_set_bgr, (png_structrp png_ptr));
+#endif
+
+#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED
+/* Expand the grayscale to 24-bit RGB if necessary. */
+PNG_EXPORT(31, void, png_set_gray_to_rgb, (png_structrp png_ptr));
+#endif
+
+#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
+/* Reduce RGB to grayscale. */
+#define PNG_ERROR_ACTION_NONE  1
+#define PNG_ERROR_ACTION_WARN  2
+#define PNG_ERROR_ACTION_ERROR 3
+#define PNG_RGB_TO_GRAY_DEFAULT (-1)/*for red/green coefficients*/
+
+PNG_FP_EXPORT(32, void, png_set_rgb_to_gray, (png_structrp png_ptr,
+    int error_action, double red, double green))
+PNG_FIXED_EXPORT(33, void, png_set_rgb_to_gray_fixed, (png_structrp png_ptr,
+    int error_action, png_fixed_point red, png_fixed_point green))
+
+PNG_EXPORT(34, png_byte, png_get_rgb_to_gray_status, (png_const_structrp
+    png_ptr));
+#endif
+
+#ifdef PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED
+PNG_EXPORT(35, void, png_build_grayscale_palette, (int bit_depth,
+    png_colorp palette));
+#endif
+
+#ifdef PNG_READ_ALPHA_MODE_SUPPORTED
+/* How the alpha channel is interpreted - this affects how the color channels of
+ * a PNG file are returned when an alpha channel, or tRNS chunk in a palette
+ * file, is present.
+ *
+ * This has no effect on the way pixels are written into a PNG output
+ * datastream. The color samples in a PNG datastream are never premultiplied
+ * with the alpha samples.
+ *
+ * The default is to return data according to the PNG specification: the alpha
+ * channel is a linear measure of the contribution of the pixel to the
+ * corresponding composited pixel.  The gamma encoded color channels must be
+ * scaled according to the contribution and to do this it is necessary to undo
+ * the encoding, scale the color values, perform the composition and reencode
+ * the values.  This is the 'PNG' mode.
+ *
+ * The alternative is to 'associate' the alpha with the color information by
+ * storing color channel values that have been scaled by the alpha.  The
+ * advantage is that the color channels can be resampled (the image can be
+ * scaled) in this form.  The disadvantage is that normal practice is to store
+ * linear, not (gamma) encoded, values and this requires 16-bit channels for
+ * still images rather than the 8-bit channels that are just about sufficient if
+ * gamma encoding is used.  In addition all non-transparent pixel values,
+ * including completely opaque ones, must be gamma encoded to produce the final
+ * image.  This is the 'STANDARD', 'ASSOCIATED' or 'PREMULTIPLIED' mode (the
+ * latter being the two common names for associated alpha color channels.)
+ *
+ * Since it is not necessary to perform arithmetic on opaque color values so
+ * long as they are not to be resampled and are in the final color space it is
+ * possible to optimize the handling of alpha by storing the opaque pixels in
+ * the PNG format (adjusted for the output color space) while storing partially
+ * opaque pixels in the standard, linear, format.  The accuracy required for
+ * standard alpha composition is relatively low, because the pixels are
+ * isolated, therefore typically the accuracy loss in storing 8-bit linear
+ * values is acceptable.  (This is not true if the alpha channel is used to
+ * simulate transparency over large areas - use 16 bits or the PNG mode in
+ * this case!)  This is the 'OPTIMIZED' mode.  For this mode a pixel is
+ * treated as opaque only if the alpha value is equal to the maximum value.
+ *
+ * The final choice is to gamma encode the alpha channel as well.  This is
+ * broken because, in practice, no implementation that uses this choice
+ * correctly undoes the encoding before handling alpha composition.  Use this
+ * choice only if other serious errors in the software or hardware you use
+ * mandate it; the typical serious error is for dark halos to appear around
+ * opaque areas of the composited PNG image because of arithmetic overflow.
+ *
+ * The API function png_set_alpha_mode specifies which of these choices to use
+ * with an enumerated 'mode' value and the gamma of the required output:
+ */
+#define PNG_ALPHA_PNG           0 /* according to the PNG standard */
+#define PNG_ALPHA_STANDARD      1 /* according to Porter/Duff */
+#define PNG_ALPHA_ASSOCIATED    1 /* as above; this is the normal practice */
+#define PNG_ALPHA_PREMULTIPLIED 1 /* as above */
+#define PNG_ALPHA_OPTIMIZED     2 /* 'PNG' for opaque pixels, else 'STANDARD' */
+#define PNG_ALPHA_BROKEN        3 /* the alpha channel is gamma encoded */
+
+PNG_FP_EXPORT(227, void, png_set_alpha_mode, (png_structrp png_ptr, int mode,
+    double output_gamma))
+PNG_FIXED_EXPORT(228, void, png_set_alpha_mode_fixed, (png_structrp png_ptr,
+    int mode, png_fixed_point output_gamma))
+#endif
+
+#if defined(PNG_GAMMA_SUPPORTED) || defined(PNG_READ_ALPHA_MODE_SUPPORTED)
+/* The output_gamma value is a screen gamma in libpng terminology: it expresses
+ * how to decode the output values, not how they are encoded.  The values used
+ * correspond to the normal numbers used to describe the overall gamma of a
+ * computer display system; for example 2.2 for an sRGB conformant system.  The
+ * values are scaled by 100000 in the _fixed version of the API (so 220000 for
+ * sRGB.)
+ *
+ * The inverse of the value is always used to provide a default for the PNG file
+ * encoding if it has no gAMA chunk and if png_set_gamma() has not been called
+ * to override the PNG gamma information.
+ *
+ * When the ALPHA_OPTIMIZED mode is selected the output gamma is used to encode
+ * opaque pixels however pixels with lower alpha values are not encoded,
+ * regardless of the output gamma setting.
+ *
+ * When the standard Porter Duff handling is requested with mode 1 the output
+ * encoding is set to be linear and the output_gamma value is only relevant
+ * as a default for input data that has no gamma information.  The linear output
+ * encoding will be overridden if png_set_gamma() is called - the results may be
+ * highly unexpected!
+ *
+ * The following numbers are derived from the sRGB standard and the research
+ * behind it.  sRGB is defined to be approximated by a PNG gAMA chunk value of
+ * 0.45455 (1/2.2) for PNG.  The value implicitly includes any viewing
+ * correction required to take account of any differences in the color
+ * environment of the original scene and the intended display environment; the
+ * value expresses how to *decode* the image for display, not how the original
+ * data was *encoded*.
+ *
+ * sRGB provides a peg for the PNG standard by defining a viewing environment.
+ * sRGB itself, and earlier TV standards, actually use a more complex transform
+ * (a linear portion then a gamma 2.4 power law) than PNG can express.  (PNG is
+ * limited to simple power laws.)  By saying that an image for direct display on
+ * an sRGB conformant system should be stored with a gAMA chunk value of 45455
+ * (11.3.3.2 and 11.3.3.5 of the ISO PNG specification) the PNG specification
+ * makes it possible to derive values for other display systems and
+ * environments.
+ *
+ * The Mac value is deduced from the sRGB based on an assumption that the actual
+ * extra viewing correction used in early Mac display systems was implemented as
+ * a power 1.45 lookup table.
+ *
+ * Any system where a programmable lookup table is used or where the behavior of
+ * the final display device characteristics can be changed requires system
+ * specific code to obtain the current characteristic.  However this can be
+ * difficult and most PNG gamma correction only requires an approximate value.
+ *
+ * By default, if png_set_alpha_mode() is not called, libpng assumes that all
+ * values are unencoded, linear, values and that the output device also has a
+ * linear characteristic.  This is only very rarely correct - it is invariably
+ * better to call png_set_alpha_mode() with PNG_DEFAULT_sRGB than rely on the
+ * default if you don't know what the right answer is!
+ *
+ * The special value PNG_GAMMA_MAC_18 indicates an older Mac system (pre Mac OS
+ * 10.6) which used a correction table to implement a somewhat lower gamma on an
+ * otherwise sRGB system.
+ *
+ * Both these values are reserved (not simple gamma values) in order to allow
+ * more precise correction internally in the future.
+ *
+ * NOTE: the following values can be passed to either the fixed or floating
+ * point APIs, but the floating point API will also accept floating point
+ * values.
+ */
+#define PNG_DEFAULT_sRGB -1       /* sRGB gamma and color space */
+#define PNG_GAMMA_MAC_18 -2       /* Old Mac '1.8' gamma and color space */
+#define PNG_GAMMA_sRGB   220000   /* Television standards--matches sRGB gamma */
+#define PNG_GAMMA_LINEAR PNG_FP_1 /* Linear */
+#endif
+
+/* The following are examples of calls to png_set_alpha_mode to achieve the
+ * required overall gamma correction and, where necessary, alpha
+ * premultiplication.
+ *
+ * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB);
+ *    This is the default libpng handling of the alpha channel - it is not
+ *    pre-multiplied into the color components.  In addition the call states
+ *    that the output is for a sRGB system and causes all PNG files without gAMA
+ *    chunks to be assumed to be encoded using sRGB.
+ *
+ * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_GAMMA_MAC);
+ *    In this case the output is assumed to be something like an sRGB conformant
+ *    display preceeded by a power-law lookup table of power 1.45.  This is how
+ *    early Mac systems behaved.
+ *
+ * png_set_alpha_mode(pp, PNG_ALPHA_STANDARD, PNG_GAMMA_LINEAR);
+ *    This is the classic Jim Blinn approach and will work in academic
+ *    environments where everything is done by the book.  It has the shortcoming
+ *    of assuming that input PNG data with no gamma information is linear - this
+ *    is unlikely to be correct unless the PNG files where generated locally.
+ *    Most of the time the output precision will be so low as to show
+ *    significant banding in dark areas of the image.
+ *
+ * png_set_expand_16(pp);
+ * png_set_alpha_mode(pp, PNG_ALPHA_STANDARD, PNG_DEFAULT_sRGB);
+ *    This is a somewhat more realistic Jim Blinn inspired approach.  PNG files
+ *    are assumed to have the sRGB encoding if not marked with a gamma value and
+ *    the output is always 16 bits per component.  This permits accurate scaling
+ *    and processing of the data.  If you know that your input PNG files were
+ *    generated locally you might need to replace PNG_DEFAULT_sRGB with the
+ *    correct value for your system.
+ *
+ * png_set_alpha_mode(pp, PNG_ALPHA_OPTIMIZED, PNG_DEFAULT_sRGB);
+ *    If you just need to composite the PNG image onto an existing background
+ *    and if you control the code that does this you can use the optimization
+ *    setting.  In this case you just copy completely opaque pixels to the
+ *    output.  For pixels that are not completely transparent (you just skip
+ *    those) you do the composition math using png_composite or png_composite_16
+ *    below then encode the resultant 8-bit or 16-bit values to match the output
+ *    encoding.
+ *
+ * Other cases
+ *    If neither the PNG nor the standard linear encoding work for you because
+ *    of the software or hardware you use then you have a big problem.  The PNG
+ *    case will probably result in halos around the image.  The linear encoding
+ *    will probably result in a washed out, too bright, image (it's actually too
+ *    contrasty.)  Try the ALPHA_OPTIMIZED mode above - this will probably
+ *    substantially reduce the halos.  Alternatively try:
+ *
+ * png_set_alpha_mode(pp, PNG_ALPHA_BROKEN, PNG_DEFAULT_sRGB);
+ *    This option will also reduce the halos, but there will be slight dark
+ *    halos round the opaque parts of the image where the background is light.
+ *    In the OPTIMIZED mode the halos will be light halos where the background
+ *    is dark.  Take your pick - the halos are unavoidable unless you can get
+ *    your hardware/software fixed!  (The OPTIMIZED approach is slightly
+ *    faster.)
+ *
+ * When the default gamma of PNG files doesn't match the output gamma.
+ *    If you have PNG files with no gamma information png_set_alpha_mode allows
+ *    you to provide a default gamma, but it also sets the ouput gamma to the
+ *    matching value.  If you know your PNG files have a gamma that doesn't
+ *    match the output you can take advantage of the fact that
+ *    png_set_alpha_mode always sets the output gamma but only sets the PNG
+ *    default if it is not already set:
+ *
+ * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB);
+ * png_set_alpha_mode(pp, PNG_ALPHA_PNG, PNG_GAMMA_MAC);
+ *    The first call sets both the default and the output gamma values, the
+ *    second call overrides the output gamma without changing the default.  This
+ *    is easier than achieving the same effect with png_set_gamma.  You must use
+ *    PNG_ALPHA_PNG for the first call - internal checking in png_set_alpha will
+ *    fire if more than one call to png_set_alpha_mode and png_set_background is
+ *    made in the same read operation, however multiple calls with PNG_ALPHA_PNG
+ *    are ignored.
+ */
+
+#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED
+PNG_EXPORT(36, void, png_set_strip_alpha, (png_structrp png_ptr));
+#endif
+
+#if defined(PNG_READ_SWAP_ALPHA_SUPPORTED) || \
+    defined(PNG_WRITE_SWAP_ALPHA_SUPPORTED)
+PNG_EXPORT(37, void, png_set_swap_alpha, (png_structrp png_ptr));
+#endif
+
+#if defined(PNG_READ_INVERT_ALPHA_SUPPORTED) || \
+    defined(PNG_WRITE_INVERT_ALPHA_SUPPORTED)
+PNG_EXPORT(38, void, png_set_invert_alpha, (png_structrp png_ptr));
+#endif
+
+#if defined(PNG_READ_FILLER_SUPPORTED) || defined(PNG_WRITE_FILLER_SUPPORTED)
+/* Add a filler byte to 8-bit Gray or 24-bit RGB images. */
+PNG_EXPORT(39, void, png_set_filler, (png_structrp png_ptr, png_uint_32 filler,
+    int flags));
+/* The values of the PNG_FILLER_ defines should NOT be changed */
+#  define PNG_FILLER_BEFORE 0
+#  define PNG_FILLER_AFTER 1
+/* Add an alpha byte to 8-bit Gray or 24-bit RGB images. */
+PNG_EXPORT(40, void, png_set_add_alpha, (png_structrp png_ptr,
+    png_uint_32 filler, int flags));
+#endif /* PNG_READ_FILLER_SUPPORTED || PNG_WRITE_FILLER_SUPPORTED */
+
+#if defined(PNG_READ_SWAP_SUPPORTED) || defined(PNG_WRITE_SWAP_SUPPORTED)
+/* Swap bytes in 16-bit depth files. */
+PNG_EXPORT(41, void, png_set_swap, (png_structrp png_ptr));
+#endif
+
+#if defined(PNG_READ_PACK_SUPPORTED) || defined(PNG_WRITE_PACK_SUPPORTED)
+/* Use 1 byte per pixel in 1, 2, or 4-bit depth files. */
+PNG_EXPORT(42, void, png_set_packing, (png_structrp png_ptr));
+#endif
+
+#if defined(PNG_READ_PACKSWAP_SUPPORTED) || \
+    defined(PNG_WRITE_PACKSWAP_SUPPORTED)
+/* Swap packing order of pixels in bytes. */
+PNG_EXPORT(43, void, png_set_packswap, (png_structrp png_ptr));
+#endif
+
+#if defined(PNG_READ_SHIFT_SUPPORTED) || defined(PNG_WRITE_SHIFT_SUPPORTED)
+/* Converts files to legal bit depths. */
+PNG_EXPORT(44, void, png_set_shift, (png_structrp png_ptr, png_const_color_8p
+    true_bits));
+#endif
+
+#if defined(PNG_READ_INTERLACING_SUPPORTED) || \
+    defined(PNG_WRITE_INTERLACING_SUPPORTED)
+/* Have the code handle the interlacing.  Returns the number of passes.
+ * MUST be called before png_read_update_info or png_start_read_image,
+ * otherwise it will not have the desired effect.  Note that it is still
+ * necessary to call png_read_row or png_read_rows png_get_image_height
+ * times for each pass.
+*/
+PNG_EXPORT(45, int, png_set_interlace_handling, (png_structrp png_ptr));
+#endif
+
+#if defined(PNG_READ_INVERT_SUPPORTED) || defined(PNG_WRITE_INVERT_SUPPORTED)
+/* Invert monochrome files */
+PNG_EXPORT(46, void, png_set_invert_mono, (png_structrp png_ptr));
+#endif
+
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+/* Handle alpha and tRNS by replacing with a background color.  Prior to
+ * libpng-1.5.4 this API must not be called before the PNG file header has been
+ * read.  Doing so will result in unexpected behavior and possible warnings or
+ * errors if the PNG file contains a bKGD chunk.
+ */
+PNG_FP_EXPORT(47, void, png_set_background, (png_structrp png_ptr,
+    png_const_color_16p background_color, int background_gamma_code,
+    int need_expand, double background_gamma))
+PNG_FIXED_EXPORT(215, void, png_set_background_fixed, (png_structrp png_ptr,
+    png_const_color_16p background_color, int background_gamma_code,
+    int need_expand, png_fixed_point background_gamma))
+#endif
+#ifdef PNG_READ_BACKGROUND_SUPPORTED
+#  define PNG_BACKGROUND_GAMMA_UNKNOWN 0
+#  define PNG_BACKGROUND_GAMMA_SCREEN  1
+#  define PNG_BACKGROUND_GAMMA_FILE    2
+#  define PNG_BACKGROUND_GAMMA_UNIQUE  3
+#endif
+
+#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED
+/* Scale a 16-bit depth file down to 8-bit, accurately. */
+PNG_EXPORT(229, void, png_set_scale_16, (png_structrp png_ptr));
+#endif
+
+#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED
+#define PNG_READ_16_TO_8 SUPPORTED /* Name prior to 1.5.4 */
+/* Strip the second byte of information from a 16-bit depth file. */
+PNG_EXPORT(48, void, png_set_strip_16, (png_structrp png_ptr));
+#endif
+
+#ifdef PNG_READ_QUANTIZE_SUPPORTED
+/* Turn on quantizing, and reduce the palette to the number of colors
+ * available.
+ */
+PNG_EXPORT(49, void, png_set_quantize, (png_structrp png_ptr,
+    png_colorp palette, int num_palette, int maximum_colors,
+    png_const_uint_16p histogram, int full_quantize));
+#endif
+
+#ifdef PNG_READ_GAMMA_SUPPORTED
+/* The threshold on gamma processing is configurable but hard-wired into the
+ * library.  The following is the floating point variant.
+ */
+#define PNG_GAMMA_THRESHOLD (PNG_GAMMA_THRESHOLD_FIXED*.00001)
+
+/* Handle gamma correction. Screen_gamma=(display_exponent).
+ * NOTE: this API simply sets the screen and file gamma values. It will
+ * therefore override the value for gamma in a PNG file if it is called after
+ * the file header has been read - use with care  - call before reading the PNG
+ * file for best results!
+ *
+ * These routines accept the same gamma values as png_set_alpha_mode (described
+ * above).  The PNG_GAMMA_ defines and PNG_DEFAULT_sRGB can be passed to either
+ * API (floating point or fixed.)  Notice, however, that the 'file_gamma' value
+ * is the inverse of a 'screen gamma' value.
+ */
+PNG_FP_EXPORT(50, void, png_set_gamma, (png_structrp png_ptr,
+    double screen_gamma, double override_file_gamma))
+PNG_FIXED_EXPORT(208, void, png_set_gamma_fixed, (png_structrp png_ptr,
+    png_fixed_point screen_gamma, png_fixed_point override_file_gamma))
+#endif
+
+#ifdef PNG_WRITE_FLUSH_SUPPORTED
+/* Set how many lines between output flushes - 0 for no flushing */
+PNG_EXPORT(51, void, png_set_flush, (png_structrp png_ptr, int nrows));
+/* Flush the current PNG output buffer */
+PNG_EXPORT(52, void, png_write_flush, (png_structrp png_ptr));
+#endif
+
+/* Optional update palette with requested transformations */
+PNG_EXPORT(53, void, png_start_read_image, (png_structrp png_ptr));
+
+/* Optional call to update the users info structure */
+PNG_EXPORT(54, void, png_read_update_info, (png_structrp png_ptr,
+    png_inforp info_ptr));
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Read one or more rows of image data. */
+PNG_EXPORT(55, void, png_read_rows, (png_structrp png_ptr, png_bytepp row,
+    png_bytepp display_row, png_uint_32 num_rows));
+#endif
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Read a row of data. */
+PNG_EXPORT(56, void, png_read_row, (png_structrp png_ptr, png_bytep row,
+    png_bytep display_row));
+#endif
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Read the whole image into memory at once. */
+PNG_EXPORT(57, void, png_read_image, (png_structrp png_ptr, png_bytepp image));
+#endif
+
+/* Write a row of image data */
+PNG_EXPORT(58, void, png_write_row, (png_structrp png_ptr,
+    png_const_bytep row));
+
+/* Write a few rows of image data: (*row) is not written; however, the type
+ * is declared as writeable to maintain compatibility with previous versions
+ * of libpng and to allow the 'display_row' array from read_rows to be passed
+ * unchanged to write_rows.
+ */
+PNG_EXPORT(59, void, png_write_rows, (png_structrp png_ptr, png_bytepp row,
+    png_uint_32 num_rows));
+
+/* Write the image data */
+PNG_EXPORT(60, void, png_write_image, (png_structrp png_ptr, png_bytepp image));
+
+/* Write the end of the PNG file. */
+PNG_EXPORT(61, void, png_write_end, (png_structrp png_ptr,
+    png_inforp info_ptr));
+
+#ifdef PNG_SEQUENTIAL_READ_SUPPORTED
+/* Read the end of the PNG file. */
+PNG_EXPORT(62, void, png_read_end, (png_structrp png_ptr, png_inforp info_ptr));
+#endif
+
+/* Free any memory associated with the png_info_struct */
+PNG_EXPORT(63, void, png_destroy_info_struct, (png_const_structrp png_ptr,
+    png_infopp info_ptr_ptr));
+
+/* Free any memory associated with the png_struct and the png_info_structs */
+PNG_EXPORT(64, void, png_destroy_read_struct, (png_structpp png_ptr_ptr,
+    png_infopp info_ptr_ptr, png_infopp end_info_ptr_ptr));
+
+/* Free any memory associated with the png_struct and the png_info_structs */
+PNG_EXPORT(65, void, png_destroy_write_struct, (png_structpp png_ptr_ptr,
+    png_infopp info_ptr_ptr));
+
+/* Set the libpng method of handling chunk CRC errors */
+PNG_EXPORT(66, void, png_set_crc_action, (png_structrp png_ptr, int crit_action,
+    int ancil_action));
+
+/* Values for png_set_crc_action() say how to handle CRC errors in
+ * ancillary and critical chunks, and whether to use the data contained
+ * therein.  Note that it is impossible to "discard" data in a critical
+ * chunk.  For versions prior to 0.90, the action was always error/quit,
+ * whereas in version 0.90 and later, the action for CRC errors in ancillary
+ * chunks is warn/discard.  These values should NOT be changed.
+ *
+ *      value                       action:critical     action:ancillary
+ */
+#define PNG_CRC_DEFAULT       0  /* error/quit          warn/discard data */
+#define PNG_CRC_ERROR_QUIT    1  /* error/quit          error/quit        */
+#define PNG_CRC_WARN_DISCARD  2  /* (INVALID)           warn/discard data */
+#define PNG_CRC_WARN_USE      3  /* warn/use data       warn/use data     */
+#define PNG_CRC_QUIET_USE     4  /* quiet/use data      quiet/use data    */
+#define PNG_CRC_NO_CHANGE     5  /* use current value   use current value */
+
+/* These functions give the user control over the scan-line filtering in
+ * libpng and the compression methods used by zlib.  These functions are
+ * mainly useful for testing, as the defaults should work with most users.
+ * Those users who are tight on memory or want faster performance at the
+ * expense of compression can modify them.  See the compression library
+ * header file (zlib.h) for an explination of the compression functions.
+ */
+
+/* Set the filtering method(s) used by libpng.  Currently, the only valid
+ * value for "method" is 0.
+ */
+PNG_EXPORT(67, void, png_set_filter, (png_structrp png_ptr, int method,
+    int filters));
+
+/* Flags for png_set_filter() to say which filters to use.  The flags
+ * are chosen so that they don't conflict with real filter types
+ * below, in case they are supplied instead of the #defined constants.
+ * These values should NOT be changed.
+ */
+#define PNG_NO_FILTERS     0x00
+#define PNG_FILTER_NONE    0x08
+#define PNG_FILTER_SUB     0x10
+#define PNG_FILTER_UP      0x20
+#define PNG_FILTER_AVG     0x40
+#define PNG_FILTER_PAETH   0x80
+#define PNG_ALL_FILTERS (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP | \
+                         PNG_FILTER_AVG | PNG_FILTER_PAETH)
+
+/* Filter values (not flags) - used in pngwrite.c, pngwutil.c for now.
+ * These defines should NOT be changed.
+ */
+#define PNG_FILTER_VALUE_NONE  0
+#define PNG_FILTER_VALUE_SUB   1
+#define PNG_FILTER_VALUE_UP    2
+#define PNG_FILTER_VALUE_AVG   3
+#define PNG_FILTER_VALUE_PAETH 4
+#define PNG_FILTER_VALUE_LAST  5
+
+#ifdef PNG_WRITE_WEIGHTED_FILTER_SUPPORTED /* EXPERIMENTAL */
+/* The "heuristic_method" is given by one of the PNG_FILTER_HEURISTIC_
+ * defines, either the default (minimum-sum-of-absolute-differences), or
+ * the experimental method (weighted-minimum-sum-of-absolute-differences).
+ *
+ * Weights are factors >= 1.0, indicating how important it is to keep the
+ * filter type consistent between rows.  Larger numbers mean the current
+ * filter is that many times as likely to be the same as the "num_weights"
+ * previous filters.  This is cumulative for each previous row with a weight.
+ * There needs to be "num_weights" values in "filter_weights", or it can be
+ * NULL if the weights aren't being specified.  Weights have no influence on
+ * the selection of the first row filter.  Well chosen weights can (in theory)
+ * improve the compression for a given image.
+ *
+ * Costs are factors >= 1.0 indicating the relative decoding costs of a
+ * filter type.  Higher costs indicate more decoding expense, and are
+ * therefore less likely to be selected over a filter with lower computational
+ * costs.  There needs to be a value in "filter_costs" for each valid filter
+ * type (given by PNG_FILTER_VALUE_LAST), or it can be NULL if you aren't
+ * setting the costs.  Costs try to improve the speed of decompression without
+ * unduly increasing the compressed image size.
+ *
+ * A negative weight or cost indicates the default value is to be used, and
+ * values in the range [0.0, 1.0) indicate the value is to remain unchanged.
+ * The default values for both weights and costs are currently 1.0, but may
+ * change if good general weighting/cost heuristics can be found.  If both
+ * the weights and costs are set to 1.0, this degenerates the WEIGHTED method
+ * to the UNWEIGHTED method, but with added encoding time/computation.
+ */
+PNG_FP_EXPORT(68, void, png_set_filter_heuristics, (png_structrp png_ptr,
+    int heuristic_method, int num_weights, png_const_doublep filter_weights,
+    png_const_doublep filter_costs))
+PNG_FIXED_EXPORT(209, void, png_set_filter_heuristics_fixed,
+    (png_structrp png_ptr, int heuristic_method, int num_weights,
+    png_const_fixed_point_p filter_weights,
+    png_const_fixed_point_p filter_costs))
+#endif /*  PNG_WRITE_WEIGHTED_FILTER_SUPPORTED */
+
+/* Heuristic used for row filter selection.  These defines should NOT be
+ * changed.
+ */
+#define PNG_FILTER_HEURISTIC_DEFAULT    0  /* Currently "UNWEIGHTED" */
+#define PNG_FILTER_HEURISTIC_UNWEIGHTED 1  /* Used by libpng < 0.95 */
+#define PNG_FILTER_HEURISTIC_WEIGHTED   2  /* Experimental feature */
+#define PNG_FILTER_HEURISTIC_LAST       3  /* Not a valid value */
+
+#ifdef PNG_WRITE_SUPPORTED
+/* Set the library compression level.  Currently, valid values range from
+ * 0 - 9, corresponding directly to the zlib compression levels 0 - 9
+ * (0 - no compression, 9 - "maximal" compression).  Note that tests have
+ * shown that zlib compression levels 3-6 usually perform as well as level 9
+ * for PNG images, and do considerably fewer caclulations.  In the future,
+ * these values may not correspond directly to the zlib compression levels.
+ */
+PNG_EXPORT(69, void, png_set_compression_level, (png_structrp png_ptr,
+    int level));
+
+PNG_EXPORT(70, void, png_set_compression_mem_level, (png_structrp png_ptr,
+    int mem_level));
+
+PNG_EXPORT(71, void, png_set_compression_strategy, (png_structrp png_ptr,
+    int strategy));
+
+/* If PNG_WRITE_OPTIMIZE_CMF_SUPPORTED is defined, libpng will use a
+ * smaller value of window_bits if it can do so safely.
+ */
+PNG_EXPORT(72, void, png_set_compression_window_bits, (png_structrp png_ptr,
+    int window_bits));
+
+PNG_EXPORT(73, void, png_set_compression_method, (png_structrp png_ptr,
+    int method));
+#endif
+
+#ifdef PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED
+/* Also set zlib parameters for compressing non-IDAT chunks */
+PNG_EXPORT(222, void, png_set_text_compression_level, (png_structrp png_ptr,
+    int level));
+
+PNG_EXPORT(223, void, png_set_text_compression_mem_level, (png_structrp png_ptr,
+    int mem_level));
+
+PNG_EXPORT(224, void, png_set_text_compression_strategy, (png_structrp png_ptr,
+    int strategy));
+
+/* If PNG_WRITE_OPTIMIZE_CMF_SUPPORTED is defined, libpng will use a
+ * smaller value of window_bits if it can do so safely.
+ */
+PNG_EXPORT(225, void, png_set_text_compression_window_bits,
+    (png_structrp png_ptr, int window_bits));
+
+PNG_EXPORT(226, void, png_set_text_compression_method, (png_structrp png_ptr,
+    int method));
+#endif /* PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED */
+
+/* These next functions are called for input/output, memory, and error
+ * handling.  They are in the file pngrio.c, pngwio.c, and pngerror.c,
+ * and call standard C I/O routines such as fread(), fwrite(), and
+ * fprintf().  These functions can be made to use other I/O routines
+ * at run time for those applications that need to handle I/O in a
+ * different manner by calling png_set_???_fn().  See libpng-manual.txt for
+ * more information.
+ */
+
+#ifdef PNG_STDIO_SUPPORTED
+/* Initialize the input/output for the PNG file to the default functions. */
+PNG_EXPORT(74, void, png_init_io, (png_structrp png_ptr, png_FILE_p fp));
+#endif
+
+/* Replace the (error and abort), and warning functions with user
+ * supplied functions.  If no messages are to be printed you must still
+ * write and use replacement functions. The replacement error_fn should
+ * still do a longjmp to the last setjmp location if you are using this
+ * method of error handling.  If error_fn or warning_fn is NULL, the
+ * default function will be used.
+ */
+
+PNG_EXPORT(75, void, png_set_error_fn, (png_structrp png_ptr,
+    png_voidp error_ptr, png_error_ptr error_fn, png_error_ptr warning_fn));
+
+/* Return the user pointer associated with the error functions */
+PNG_EXPORT(76, png_voidp, png_get_error_ptr, (png_const_structrp png_ptr));
+
+/* Replace the default data output functions with a user supplied one(s).
+ * If buffered output is not used, then output_flush_fn can be set to NULL.
+ * If PNG_WRITE_FLUSH_SUPPORTED is not defined at libpng compile time
+ * output_flush_fn will be ignored (and thus can be NULL).
+ * It is probably a mistake to use NULL for output_flush_fn if
+ * write_data_fn is not also NULL unless you have built libpng with
+ * PNG_WRITE_FLUSH_SUPPORTED undefined, because in this case libpng's
+ * default flush function, which uses the standard *FILE structure, will
+ * be used.
+ */
+PNG_EXPORT(77, void, png_set_write_fn, (png_structrp png_ptr, png_voidp io_ptr,
+    png_rw_ptr write_data_fn, png_flush_ptr output_flush_fn));
+
+/* Replace the default data input function with a user supplied one. */
+PNG_EXPORT(78, void, png_set_read_fn, (png_structrp png_ptr, png_voidp io_ptr,
+    png_rw_ptr read_data_fn));
+
+/* Return the user pointer associated with the I/O functions */
+PNG_EXPORT(79, png_voidp, png_get_io_ptr, (png_const_structrp png_ptr));
+
+PNG_EXPORT(80, void, png_set_read_status_fn, (png_structrp png_ptr,
+    png_read_status_ptr read_row_fn));
+
+PNG_EXPORT(81, void, png_set_write_status_fn, (png_structrp png_ptr,
+    png_write_status_ptr write_row_fn));
+
+#ifdef PNG_USER_MEM_SUPPORTED
+/* Replace the default memory allocation functions with user supplied one(s). */
+PNG_EXPORT(82, void, png_set_mem_fn, (png_structrp png_ptr, png_voidp mem_ptr,
+    png_malloc_ptr malloc_fn, png_free_ptr free_fn));
+/* Return the user pointer associated with the memory functions */
+PNG_EXPORT(83, png_voidp, png_get_mem_ptr, (png_const_structrp png_ptr));
+#endif
+
+#ifdef PNG_READ_USER_TRANSFORM_SUPPORTED
+PNG_EXPORT(84, void, png_set_read_user_transform_fn, (png_structrp png_ptr,
+    png_user_transform_ptr read_user_transform_fn));
+#endif
+
+#ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED
+PNG_EXPORT(85, void, png_set_write_user_transform_fn, (png_structrp png_ptr,
+    png_user_transform_ptr write_user_transform_fn));
+#endif
+
+#ifdef PNG_USER_TRANSFORM_PTR_SUPPORTED
+PNG_EXPORT(86, void, png_set_user_transform_info, (png_structrp png_ptr,
+    png_voidp user_transform_ptr, int user_transform_depth,
+    int user_transform_channels));
+/* Return the user pointer associated with the user transform functions */
+PNG_EXPORT(87, png_voidp, png_get_user_transform_ptr,
+    (png_const_structrp png_ptr));
+#endif
+
+#ifdef PNG_USER_TRANSFORM_INFO_SUPPORTED
+/* Return information about the row currently being processed.  Note that these
+ * APIs do not fail but will return unexpected results if called outside a user
+ * transform callback.  Also note that when transforming an interlaced image the
+ * row number is the row number within the sub-image of the interlace pass, so
+ * the value will increase to the height of the sub-image (not the full image)
+ * then reset to 0 for the next pass.
+ *
+ * Use PNG_ROW_FROM_PASS_ROW(row, pass) and PNG_COL_FROM_PASS_COL(col, pass) to
+ * find the output pixel (x,y) given an interlaced sub-image pixel
+ * (row,col,pass).  (See below for these macros.)
+ */
+PNG_EXPORT(217, png_uint_32, png_get_current_row_number, (png_const_structrp));
+PNG_EXPORT(218, png_byte, png_get_current_pass_number, (png_const_structrp));
+#endif
+
+#ifdef PNG_READ_USER_CHUNKS_SUPPORTED
+/* This callback is called only for *unknown* chunks.  If
+ * PNG_HANDLE_AS_UNKNOWN_SUPPORTED is set then it is possible to set known
+ * chunks to be treated as unknown, however in this case the callback must do
+ * any processing required by the chunk (e.g. by calling the appropriate
+ * png_set_ APIs.)
+ *
+ * There is no write support - on write, by default, all the chunks in the
+ * 'unknown' list are written in the specified position.
+ *
+ * The integer return from the callback function is interpreted thus:
+ *
+ * negative: An error occured, png_chunk_error will be called.
+ *     zero: The chunk was not handled, the chunk will be saved. A critical
+ *           chunk will cause an error at this point unless it is to be saved.
+ * positive: The chunk was handled, libpng will ignore/discard it.
+ *
+ * See "INTERACTION WTIH USER CHUNK CALLBACKS" below for important notes about
+ * how this behavior will change in libpng 1.7
+ */
+PNG_EXPORT(88, void, png_set_read_user_chunk_fn, (png_structrp png_ptr,
+    png_voidp user_chunk_ptr, png_user_chunk_ptr read_user_chunk_fn));
+#endif
+
+#ifdef PNG_USER_CHUNKS_SUPPORTED
+PNG_EXPORT(89, png_voidp, png_get_user_chunk_ptr, (png_const_structrp png_ptr));
+#endif
+
+#ifdef PNG_PROGRESSIVE_READ_SUPPORTED
+/* Sets the function callbacks for the push reader, and a pointer to a
+ * user-defined structure available to the callback functions.
+ */
+PNG_EXPORT(90, void, png_set_progressive_read_fn, (png_structrp png_ptr,
+    png_voidp progressive_ptr, png_progressive_info_ptr info_fn,
+    png_progressive_row_ptr row_fn, png_progressive_end_ptr end_fn));
+
+/* Returns the user pointer associated with the push read functions */
+PNG_EXPORT(91, png_voidp, png_get_progressive_ptr,
+    (png_const_structrp png_ptr));
+
+/* Function to be called when data becomes available */
+PNG_EXPORT(92, void, png_process_data, (png_structrp png_ptr,
+    png_inforp info_ptr, png_bytep buffer, png_size_t buffer_size));
+
+/* A function which may be called *only* within png_process_data to stop the
+ * processing of any more data.  The function returns the number of bytes
+ * remaining, excluding any that libpng has cached internally.  A subsequent
+ * call to png_process_data must supply these bytes again.  If the argument
+ * 'save' is set to true the routine will first save all the pending data and
+ * will always return 0.
+ */
+PNG_EXPORT(219, png_size_t, png_process_data_pause, (png_structrp, int save));
+
+/* A function which may be called *only* outside (after) a call to
+ * png_process_data.  It returns the number of bytes of data to skip in the
+ * input.  Normally it will return 0, but if it returns a non-zero value the
+ * application must skip than number of bytes of input data and pass the
+ * following data to the next call to png_process_data.
+ */
+PNG_EXPORT(220, png_uint_32, png_process_data_skip, (png_structrp));
+
+#ifdef PNG_READ_INTERLACING_SUPPORTED
+/* Function that combines rows.  'new_row' is a flag that should come from
+ * the callback and be non-NULL if anything needs to be done; the library
+ * stores its own version of the new data internally and ignores the passed
+ * in value.
+ */
+PNG_EXPORT(93, void, png_progressive_combine_row, (png_const_structrp png_ptr,
+    png_bytep old_row, png_const_bytep new_row));
+#endif /* PNG_READ_INTERLACING_SUPPORTED */
+#endif /* PNG_PROGRESSIVE_READ_SUPPORTED */
+
+PNG_EXPORTA(94, png_voidp, png_malloc, (png_const_structrp png_ptr,
+    png_alloc_size_t size), PNG_ALLOCATED);
+/* Added at libpng version 1.4.0 */
+PNG_EXPORTA(95, png_voidp, png_calloc, (png_const_structrp png_ptr,
+    png_alloc_size_t size), PNG_ALLOCATED);
+
+/* Added at libpng version 1.2.4 */
+PNG_EXPORTA(96, png_voidp, png_malloc_warn, (png_const_structrp png_ptr,
+    png_alloc_size_t size), PNG_ALLOCATED);
+
+/* Frees a pointer allocated by png_malloc() */
+PNG_EXPORT(97, void, png_free, (png_const_structrp png_ptr, png_voidp ptr));
+
+/* Free data that was allocated internally */
+PNG_EXPORT(98, void, png_free_data, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_uint_32 free_me, int num));
+
+/* Reassign responsibility for freeing existing data, whether allocated
+ * by libpng or by the application; this works on the png_info structure passed
+ * in, it does not change the state for other png_info structures.
+ *
+ * It is unlikely that this function works correctly as of 1.6.0 and using it
+ * may result either in memory leaks or double free of allocated data.
+ */
+PNG_EXPORTA(99, void, png_data_freer, (png_const_structrp png_ptr,
+    png_inforp info_ptr, int freer, png_uint_32 mask), PNG_DEPRECATED);
+
+/* Assignments for png_data_freer */
+#define PNG_DESTROY_WILL_FREE_DATA 1
+#define PNG_SET_WILL_FREE_DATA 1
+#define PNG_USER_WILL_FREE_DATA 2
+/* Flags for png_ptr->free_me and info_ptr->free_me */
+#define PNG_FREE_HIST 0x0008
+#define PNG_FREE_ICCP 0x0010
+#define PNG_FREE_SPLT 0x0020
+#define PNG_FREE_ROWS 0x0040
+#define PNG_FREE_PCAL 0x0080
+#define PNG_FREE_SCAL 0x0100
+#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED
+#  define PNG_FREE_UNKN 0x0200
+#endif
+/*      PNG_FREE_LIST 0x0400    removed in 1.6.0 because it is ignored */
+#define PNG_FREE_PLTE 0x1000
+#define PNG_FREE_TRNS 0x2000
+#define PNG_FREE_TEXT 0x4000
+#define PNG_FREE_ALL  0x7fff
+#define PNG_FREE_MUL  0x4220 /* PNG_FREE_SPLT|PNG_FREE_TEXT|PNG_FREE_UNKN */
+
+#ifdef PNG_USER_MEM_SUPPORTED
+PNG_EXPORTA(100, png_voidp, png_malloc_default, (png_const_structrp png_ptr,
+    png_alloc_size_t size), PNG_ALLOCATED PNG_DEPRECATED);
+PNG_EXPORTA(101, void, png_free_default, (png_const_structrp png_ptr,
+    png_voidp ptr), PNG_DEPRECATED);
+#endif
+
+#ifdef PNG_ERROR_TEXT_SUPPORTED
+/* Fatal error in PNG image of libpng - can't continue */
+PNG_EXPORTA(102, void, png_error, (png_const_structrp png_ptr,
+    png_const_charp error_message), PNG_NORETURN);
+
+/* The same, but the chunk name is prepended to the error string. */
+PNG_EXPORTA(103, void, png_chunk_error, (png_const_structrp png_ptr,
+    png_const_charp error_message), PNG_NORETURN);
+
+#else
+/* Fatal error in PNG image of libpng - can't continue */
+PNG_EXPORTA(104, void, png_err, (png_const_structrp png_ptr), PNG_NORETURN);
+#endif
+
+#ifdef PNG_WARNINGS_SUPPORTED
+/* Non-fatal error in libpng.  Can continue, but may have a problem. */
+PNG_EXPORT(105, void, png_warning, (png_const_structrp png_ptr,
+    png_const_charp warning_message));
+
+/* Non-fatal error in libpng, chunk name is prepended to message. */
+PNG_EXPORT(106, void, png_chunk_warning, (png_const_structrp png_ptr,
+    png_const_charp warning_message));
+#endif
+
+#ifdef PNG_BENIGN_ERRORS_SUPPORTED
+/* Benign error in libpng.  Can continue, but may have a problem.
+ * User can choose whether to handle as a fatal error or as a warning. */
+PNG_EXPORT(107, void, png_benign_error, (png_const_structrp png_ptr,
+    png_const_charp warning_message));
+
+#ifdef PNG_READ_SUPPORTED
+/* Same, chunk name is prepended to message (only during read) */
+PNG_EXPORT(108, void, png_chunk_benign_error, (png_const_structrp png_ptr,
+    png_const_charp warning_message));
+#endif
+
+PNG_EXPORT(109, void, png_set_benign_errors,
+    (png_structrp png_ptr, int allowed));
+#else
+#  ifdef PNG_ALLOW_BENIGN_ERRORS
+#    define png_benign_error png_warning
+#    define png_chunk_benign_error png_chunk_warning
+#  else
+#    define png_benign_error png_error
+#    define png_chunk_benign_error png_chunk_error
+#  endif
+#endif
+
+/* The png_set_<chunk> functions are for storing values in the png_info_struct.
+ * Similarly, the png_get_<chunk> calls are used to read values from the
+ * png_info_struct, either storing the parameters in the passed variables, or
+ * setting pointers into the png_info_struct where the data is stored.  The
+ * png_get_<chunk> functions return a non-zero value if the data was available
+ * in info_ptr, or return zero and do not change any of the parameters if the
+ * data was not available.
+ *
+ * These functions should be used instead of directly accessing png_info
+ * to avoid problems with future changes in the size and internal layout of
+ * png_info_struct.
+ */
+/* Returns "flag" if chunk data is valid in info_ptr. */
+PNG_EXPORT(110, png_uint_32, png_get_valid, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr, png_uint_32 flag));
+
+/* Returns number of bytes needed to hold a transformed row. */
+PNG_EXPORT(111, png_size_t, png_get_rowbytes, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+
+#ifdef PNG_INFO_IMAGE_SUPPORTED
+/* Returns row_pointers, which is an array of pointers to scanlines that was
+ * returned from png_read_png().
+ */
+PNG_EXPORT(112, png_bytepp, png_get_rows, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+
+/* Set row_pointers, which is an array of pointers to scanlines for use
+ * by png_write_png().
+ */
+PNG_EXPORT(113, void, png_set_rows, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_bytepp row_pointers));
+#endif
+
+/* Returns number of color channels in image. */
+PNG_EXPORT(114, png_byte, png_get_channels, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+
+#ifdef PNG_EASY_ACCESS_SUPPORTED
+/* Returns image width in pixels. */
+PNG_EXPORT(115, png_uint_32, png_get_image_width, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+
+/* Returns image height in pixels. */
+PNG_EXPORT(116, png_uint_32, png_get_image_height, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+
+/* Returns image bit_depth. */
+PNG_EXPORT(117, png_byte, png_get_bit_depth, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+
+/* Returns image color_type. */
+PNG_EXPORT(118, png_byte, png_get_color_type, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+
+/* Returns image filter_type. */
+PNG_EXPORT(119, png_byte, png_get_filter_type, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+
+/* Returns image interlace_type. */
+PNG_EXPORT(120, png_byte, png_get_interlace_type, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+
+/* Returns image compression_type. */
+PNG_EXPORT(121, png_byte, png_get_compression_type, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+
+/* Returns image resolution in pixels per meter, from pHYs chunk data. */
+PNG_EXPORT(122, png_uint_32, png_get_pixels_per_meter,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr));
+PNG_EXPORT(123, png_uint_32, png_get_x_pixels_per_meter,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr));
+PNG_EXPORT(124, png_uint_32, png_get_y_pixels_per_meter,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr));
+
+/* Returns pixel aspect ratio, computed from pHYs chunk data.  */
+PNG_FP_EXPORT(125, float, png_get_pixel_aspect_ratio,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr))
+PNG_FIXED_EXPORT(210, png_fixed_point, png_get_pixel_aspect_ratio_fixed,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr))
+
+/* Returns image x, y offset in pixels or microns, from oFFs chunk data. */
+PNG_EXPORT(126, png_int_32, png_get_x_offset_pixels,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr));
+PNG_EXPORT(127, png_int_32, png_get_y_offset_pixels,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr));
+PNG_EXPORT(128, png_int_32, png_get_x_offset_microns,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr));
+PNG_EXPORT(129, png_int_32, png_get_y_offset_microns,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr));
+
+#endif /* PNG_EASY_ACCESS_SUPPORTED */
+
+#ifdef PNG_READ_SUPPORTED
+/* Returns pointer to signature string read from PNG header */
+PNG_EXPORT(130, png_const_bytep, png_get_signature, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr));
+#endif
+
+#ifdef PNG_bKGD_SUPPORTED
+PNG_EXPORT(131, png_uint_32, png_get_bKGD, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_color_16p *background));
+#endif
+
+#ifdef PNG_bKGD_SUPPORTED
+PNG_EXPORT(132, void, png_set_bKGD, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_const_color_16p background));
+#endif
+
+#ifdef PNG_cHRM_SUPPORTED
+PNG_FP_EXPORT(133, png_uint_32, png_get_cHRM, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr, double *white_x, double *white_y, double *red_x,
+    double *red_y, double *green_x, double *green_y, double *blue_x,
+    double *blue_y))
+PNG_FP_EXPORT(230, png_uint_32, png_get_cHRM_XYZ, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr, double *red_X, double *red_Y, double *red_Z,
+    double *green_X, double *green_Y, double *green_Z, double *blue_X,
+    double *blue_Y, double *blue_Z))
+PNG_FIXED_EXPORT(134, png_uint_32, png_get_cHRM_fixed,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr,
+    png_fixed_point *int_white_x, png_fixed_point *int_white_y,
+    png_fixed_point *int_red_x, png_fixed_point *int_red_y,
+    png_fixed_point *int_green_x, png_fixed_point *int_green_y,
+    png_fixed_point *int_blue_x, png_fixed_point *int_blue_y))
+PNG_FIXED_EXPORT(231, png_uint_32, png_get_cHRM_XYZ_fixed,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr,
+    png_fixed_point *int_red_X, png_fixed_point *int_red_Y,
+    png_fixed_point *int_red_Z, png_fixed_point *int_green_X,
+    png_fixed_point *int_green_Y, png_fixed_point *int_green_Z,
+    png_fixed_point *int_blue_X, png_fixed_point *int_blue_Y,
+    png_fixed_point *int_blue_Z))
+#endif
+
+#ifdef PNG_cHRM_SUPPORTED
+PNG_FP_EXPORT(135, void, png_set_cHRM, (png_const_structrp png_ptr,
+    png_inforp info_ptr,
+    double white_x, double white_y, double red_x, double red_y, double green_x,
+    double green_y, double blue_x, double blue_y))
+PNG_FP_EXPORT(232, void, png_set_cHRM_XYZ, (png_const_structrp png_ptr,
+    png_inforp info_ptr, double red_X, double red_Y, double red_Z,
+    double green_X, double green_Y, double green_Z, double blue_X,
+    double blue_Y, double blue_Z))
+PNG_FIXED_EXPORT(136, void, png_set_cHRM_fixed, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_fixed_point int_white_x,
+    png_fixed_point int_white_y, png_fixed_point int_red_x,
+    png_fixed_point int_red_y, png_fixed_point int_green_x,
+    png_fixed_point int_green_y, png_fixed_point int_blue_x,
+    png_fixed_point int_blue_y))
+PNG_FIXED_EXPORT(233, void, png_set_cHRM_XYZ_fixed, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_fixed_point int_red_X, png_fixed_point int_red_Y,
+    png_fixed_point int_red_Z, png_fixed_point int_green_X,
+    png_fixed_point int_green_Y, png_fixed_point int_green_Z,
+    png_fixed_point int_blue_X, png_fixed_point int_blue_Y,
+    png_fixed_point int_blue_Z))
+#endif
+
+#ifdef PNG_gAMA_SUPPORTED
+PNG_FP_EXPORT(137, png_uint_32, png_get_gAMA, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr, double *file_gamma))
+PNG_FIXED_EXPORT(138, png_uint_32, png_get_gAMA_fixed,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr,
+    png_fixed_point *int_file_gamma))
+#endif
+
+#ifdef PNG_gAMA_SUPPORTED
+PNG_FP_EXPORT(139, void, png_set_gAMA, (png_const_structrp png_ptr,
+    png_inforp info_ptr, double file_gamma))
+PNG_FIXED_EXPORT(140, void, png_set_gAMA_fixed, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_fixed_point int_file_gamma))
+#endif
+
+#ifdef PNG_hIST_SUPPORTED
+PNG_EXPORT(141, png_uint_32, png_get_hIST, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_uint_16p *hist));
+#endif
+
+#ifdef PNG_hIST_SUPPORTED
+PNG_EXPORT(142, void, png_set_hIST, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_const_uint_16p hist));
+#endif
+
+PNG_EXPORT(143, png_uint_32, png_get_IHDR, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr, png_uint_32 *width, png_uint_32 *height,
+    int *bit_depth, int *color_type, int *interlace_method,
+    int *compression_method, int *filter_method));
+
+PNG_EXPORT(144, void, png_set_IHDR, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_uint_32 width, png_uint_32 height, int bit_depth,
+    int color_type, int interlace_method, int compression_method,
+    int filter_method));
+
+#ifdef PNG_oFFs_SUPPORTED
+PNG_EXPORT(145, png_uint_32, png_get_oFFs, (png_const_structrp png_ptr,
+   png_const_inforp info_ptr, png_int_32 *offset_x, png_int_32 *offset_y,
+   int *unit_type));
+#endif
+
+#ifdef PNG_oFFs_SUPPORTED
+PNG_EXPORT(146, void, png_set_oFFs, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_int_32 offset_x, png_int_32 offset_y,
+    int unit_type));
+#endif
+
+#ifdef PNG_pCAL_SUPPORTED
+PNG_EXPORT(147, png_uint_32, png_get_pCAL, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_charp *purpose, png_int_32 *X0,
+    png_int_32 *X1, int *type, int *nparams, png_charp *units,
+    png_charpp *params));
+#endif
+
+#ifdef PNG_pCAL_SUPPORTED
+PNG_EXPORT(148, void, png_set_pCAL, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_const_charp purpose, png_int_32 X0, png_int_32 X1,
+    int type, int nparams, png_const_charp units, png_charpp params));
+#endif
+
+#ifdef PNG_pHYs_SUPPORTED
+PNG_EXPORT(149, png_uint_32, png_get_pHYs, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr, png_uint_32 *res_x, png_uint_32 *res_y,
+    int *unit_type));
+#endif
+
+#ifdef PNG_pHYs_SUPPORTED
+PNG_EXPORT(150, void, png_set_pHYs, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_uint_32 res_x, png_uint_32 res_y, int unit_type));
+#endif
+
+PNG_EXPORT(151, png_uint_32, png_get_PLTE, (png_const_structrp png_ptr,
+   png_inforp info_ptr, png_colorp *palette, int *num_palette));
+
+PNG_EXPORT(152, void, png_set_PLTE, (png_structrp png_ptr,
+    png_inforp info_ptr, png_const_colorp palette, int num_palette));
+
+#ifdef PNG_sBIT_SUPPORTED
+PNG_EXPORT(153, png_uint_32, png_get_sBIT, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_color_8p *sig_bit));
+#endif
+
+#ifdef PNG_sBIT_SUPPORTED
+PNG_EXPORT(154, void, png_set_sBIT, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_const_color_8p sig_bit));
+#endif
+
+#ifdef PNG_sRGB_SUPPORTED
+PNG_EXPORT(155, png_uint_32, png_get_sRGB, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr, int *file_srgb_intent));
+#endif
+
+#ifdef PNG_sRGB_SUPPORTED
+PNG_EXPORT(156, void, png_set_sRGB, (png_const_structrp png_ptr,
+    png_inforp info_ptr, int srgb_intent));
+PNG_EXPORT(157, void, png_set_sRGB_gAMA_and_cHRM, (png_const_structrp png_ptr,
+    png_inforp info_ptr, int srgb_intent));
+#endif
+
+#ifdef PNG_iCCP_SUPPORTED
+PNG_EXPORT(158, png_uint_32, png_get_iCCP, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_charpp name, int *compression_type,
+    png_bytepp profile, png_uint_32 *proflen));
+#endif
+
+#ifdef PNG_iCCP_SUPPORTED
+PNG_EXPORT(159, void, png_set_iCCP, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_const_charp name, int compression_type,
+    png_const_bytep profile, png_uint_32 proflen));
+#endif
+
+#ifdef PNG_sPLT_SUPPORTED
+PNG_EXPORT(160, int, png_get_sPLT, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_sPLT_tpp entries));
+#endif
+
+#ifdef PNG_sPLT_SUPPORTED
+PNG_EXPORT(161, void, png_set_sPLT, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_const_sPLT_tp entries, int nentries));
+#endif
+
+#ifdef PNG_TEXT_SUPPORTED
+/* png_get_text also returns the number of text chunks in *num_text */
+PNG_EXPORT(162, int, png_get_text, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_textp *text_ptr, int *num_text));
+#endif
+
+/* Note while png_set_text() will accept a structure whose text,
+ * language, and  translated keywords are NULL pointers, the structure
+ * returned by png_get_text will always contain regular
+ * zero-terminated C strings.  They might be empty strings but
+ * they will never be NULL pointers.
+ */
+
+#ifdef PNG_TEXT_SUPPORTED
+PNG_EXPORT(163, void, png_set_text, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_const_textp text_ptr, int num_text));
+#endif
+
+#ifdef PNG_tIME_SUPPORTED
+PNG_EXPORT(164, png_uint_32, png_get_tIME, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_timep *mod_time));
+#endif
+
+#ifdef PNG_tIME_SUPPORTED
+PNG_EXPORT(165, void, png_set_tIME, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_const_timep mod_time));
+#endif
+
+#ifdef PNG_tRNS_SUPPORTED
+PNG_EXPORT(166, png_uint_32, png_get_tRNS, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_bytep *trans_alpha, int *num_trans,
+    png_color_16p *trans_color));
+#endif
+
+#ifdef PNG_tRNS_SUPPORTED
+PNG_EXPORT(167, void, png_set_tRNS, (png_structrp png_ptr,
+    png_inforp info_ptr, png_const_bytep trans_alpha, int num_trans,
+    png_const_color_16p trans_color));
+#endif
+
+#ifdef PNG_sCAL_SUPPORTED
+PNG_FP_EXPORT(168, png_uint_32, png_get_sCAL, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr, int *unit, double *width, double *height))
+#if defined(PNG_FLOATING_ARITHMETIC_SUPPORTED) || \
+   defined(PNG_FLOATING_POINT_SUPPORTED)
+/* NOTE: this API is currently implemented using floating point arithmetic,
+ * consequently it can only be used on systems with floating point support.
+ * In any case the range of values supported by png_fixed_point is small and it
+ * is highly recommended that png_get_sCAL_s be used instead.
+ */
+PNG_FIXED_EXPORT(214, png_uint_32, png_get_sCAL_fixed,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr, int *unit,
+    png_fixed_point *width, png_fixed_point *height))
+#endif
+PNG_EXPORT(169, png_uint_32, png_get_sCAL_s,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr, int *unit,
+    png_charpp swidth, png_charpp sheight));
+
+PNG_FP_EXPORT(170, void, png_set_sCAL, (png_const_structrp png_ptr,
+    png_inforp info_ptr, int unit, double width, double height))
+PNG_FIXED_EXPORT(213, void, png_set_sCAL_fixed, (png_const_structrp png_ptr,
+   png_inforp info_ptr, int unit, png_fixed_point width,
+   png_fixed_point height))
+PNG_EXPORT(171, void, png_set_sCAL_s, (png_const_structrp png_ptr,
+    png_inforp info_ptr, int unit,
+    png_const_charp swidth, png_const_charp sheight));
+#endif /* PNG_sCAL_SUPPORTED */
+
+#ifdef PNG_SET_UNKNOWN_CHUNKS_SUPPORTED
+/* Provide the default handling for all unknown chunks or, optionally, for
+ * specific unknown chunks.
+ *
+ * NOTE: prior to 1.6.0 the handling specified for particular chunks on read was
+ * ignored and the default was used, the per-chunk setting only had an effect on
+ * write.  If you wish to have chunk-specific handling on read in code that must
+ * work on earlier versions you must use a user chunk callback to specify the
+ * desired handling (keep or discard.)
+ *
+ * The 'keep' parameter is a PNG_HANDLE_CHUNK_ value as listed below.  The
+ * parameter is interpreted as follows:
+ *
+ * READ:
+ *    PNG_HANDLE_CHUNK_AS_DEFAULT:
+ *       Known chunks: do normal libpng processing, do not keep the chunk (but
+ *          see the comments below about PNG_HANDLE_AS_UNKNOWN_SUPPORTED)
+ *       Unknown chunks: for a specific chunk use the global default, when used
+ *          as the default discard the chunk data.
+ *    PNG_HANDLE_CHUNK_NEVER:
+ *       Discard the chunk data.
+ *    PNG_HANDLE_CHUNK_IF_SAFE:
+ *       Keep the chunk data if the chunk is not critical else raise a chunk
+ *       error.
+ *    PNG_HANDLE_CHUNK_ALWAYS:
+ *       Keep the chunk data.
+ *
+ * If the chunk data is saved it can be retrieved using png_get_unknown_chunks,
+ * below.  Notice that specifying "AS_DEFAULT" as a global default is equivalent
+ * to specifying "NEVER", however when "AS_DEFAULT" is used for specific chunks
+ * it simply resets the behavior to the libpng default.
+ *
+ * INTERACTION WTIH USER CHUNK CALLBACKS:
+ * The per-chunk handling is always used when there is a png_user_chunk_ptr
+ * callback and the callback returns 0; the chunk is then always stored *unless*
+ * it is critical and the per-chunk setting is other than ALWAYS.  Notice that
+ * the global default is *not* used in this case.  (In effect the per-chunk
+ * value is incremented to at least IF_SAFE.)
+ *
+ * IMPORTANT NOTE: this behavior will change in libpng 1.7 - the global and
+ * per-chunk defaults will be honored.  If you want to preserve the current
+ * behavior when your callback returns 0 you must set PNG_HANDLE_CHUNK_IF_SAFE
+ * as the default - if you don't do this libpng 1.6 will issue a warning.
+ *
+ * If you want unhandled unknown chunks to be discarded in libpng 1.6 and
+ * earlier simply return '1' (handled).
+ *
+ * PNG_HANDLE_AS_UNKNOWN_SUPPORTED:
+ *    If this is *not* set known chunks will always be handled by libpng and
+ *    will never be stored in the unknown chunk list.  Known chunks listed to
+ *    png_set_keep_unknown_chunks will have no effect.  If it is set then known
+ *    chunks listed with a keep other than AS_DEFAULT will *never* be processed
+ *    by libpng, in addition critical chunks must either be processed by the
+ *    callback or saved.
+ *
+ *    The IHDR and IEND chunks must not be listed.  Because this turns off the
+ *    default handling for chunks that would otherwise be recognized the
+ *    behavior of libpng transformations may well become incorrect!
+ *
+ * WRITE:
+ *    When writing chunks the options only apply to the chunks specified by
+ *    png_set_unknown_chunks (below), libpng will *always* write known chunks
+ *    required by png_set_ calls and will always write the core critical chunks
+ *    (as required for PLTE).
+ *
+ *    Each chunk in the png_set_unknown_chunks list is looked up in the
+ *    png_set_keep_unknown_chunks list to find the keep setting, this is then
+ *    interpreted as follows:
+ *
+ *    PNG_HANDLE_CHUNK_AS_DEFAULT:
+ *       Write safe-to-copy chunks and write other chunks if the global
+ *       default is set to _ALWAYS, otherwise don't write this chunk.
+ *    PNG_HANDLE_CHUNK_NEVER:
+ *       Do not write the chunk.
+ *    PNG_HANDLE_CHUNK_IF_SAFE:
+ *       Write the chunk if it is safe-to-copy, otherwise do not write it.
+ *    PNG_HANDLE_CHUNK_ALWAYS:
+ *       Write the chunk.
+ *
+ * Note that the default behavior is effectively the opposite of the read case -
+ * in read unknown chunks are not stored by default, in write they are written
+ * by default.  Also the behavior of PNG_HANDLE_CHUNK_IF_SAFE is very different
+ * - on write the safe-to-copy bit is checked, on read the critical bit is
+ * checked and on read if the chunk is critical an error will be raised.
+ *
+ * num_chunks:
+ * ===========
+ *    If num_chunks is positive, then the "keep" parameter specifies the manner
+ *    for handling only those chunks appearing in the chunk_list array,
+ *    otherwise the chunk list array is ignored.
+ *
+ *    If num_chunks is 0 the "keep" parameter specifies the default behavior for
+ *    unknown chunks, as described above.
+ *
+ *    If num_chunks is negative, then the "keep" parameter specifies the manner
+ *    for handling all unknown chunks plus all chunks recognized by libpng
+ *    except for the IHDR, PLTE, tRNS, IDAT, and IEND chunks (which continue to
+ *    be processed by libpng.
+ */
+PNG_EXPORT(172, void, png_set_keep_unknown_chunks, (png_structrp png_ptr,
+    int keep, png_const_bytep chunk_list, int num_chunks));
+
+/* The "keep" PNG_HANDLE_CHUNK_ parameter for the specified chunk is returned;
+ * the result is therefore true (non-zero) if special handling is required,
+ * false for the default handling.
+ */
+PNG_EXPORT(173, int, png_handle_as_unknown, (png_const_structrp png_ptr,
+    png_const_bytep chunk_name));
+#endif
+
+#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED
+PNG_EXPORT(174, void, png_set_unknown_chunks, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_const_unknown_chunkp unknowns,
+    int num_unknowns));
+   /* NOTE: prior to 1.6.0 this routine set the 'location' field of the added
+    * unknowns to the location currently stored in the png_struct.  This is
+    * invariably the wrong value on write.  To fix this call the following API
+    * for each chunk in the list with the correct location.  If you know your
+    * code won't be compiled on earlier versions you can rely on
+    * png_set_unknown_chunks(write-ptr, png_get_unknown_chunks(read-ptr)) doing
+    * the correct thing.
+    */
+
+PNG_EXPORT(175, void, png_set_unknown_chunk_location,
+    (png_const_structrp png_ptr, png_inforp info_ptr, int chunk, int location));
+
+PNG_EXPORT(176, int, png_get_unknown_chunks, (png_const_structrp png_ptr,
+    png_inforp info_ptr, png_unknown_chunkpp entries));
+#endif
+
+/* Png_free_data() will turn off the "valid" flag for anything it frees.
+ * If you need to turn it off for a chunk that your application has freed,
+ * you can use png_set_invalid(png_ptr, info_ptr, PNG_INFO_CHNK);
+ */
+PNG_EXPORT(177, void, png_set_invalid, (png_const_structrp png_ptr,
+    png_inforp info_ptr, int mask));
+
+#ifdef PNG_INFO_IMAGE_SUPPORTED
+/* The "params" pointer is currently not used and is for future expansion. */
+PNG_EXPORT(178, void, png_read_png, (png_structrp png_ptr, png_inforp info_ptr,
+    int transforms, png_voidp params));
+PNG_EXPORT(179, void, png_write_png, (png_structrp png_ptr, png_inforp info_ptr,
+    int transforms, png_voidp params));
+#endif
+
+PNG_EXPORT(180, png_const_charp, png_get_copyright,
+    (png_const_structrp png_ptr));
+PNG_EXPORT(181, png_const_charp, png_get_header_ver,
+    (png_const_structrp png_ptr));
+PNG_EXPORT(182, png_const_charp, png_get_header_version,
+    (png_const_structrp png_ptr));
+PNG_EXPORT(183, png_const_charp, png_get_libpng_ver,
+    (png_const_structrp png_ptr));
+
+#ifdef PNG_MNG_FEATURES_SUPPORTED
+PNG_EXPORT(184, png_uint_32, png_permit_mng_features, (png_structrp png_ptr,
+    png_uint_32 mng_features_permitted));
+#endif
+
+/* For use in png_set_keep_unknown, added to version 1.2.6 */
+#define PNG_HANDLE_CHUNK_AS_DEFAULT   0
+#define PNG_HANDLE_CHUNK_NEVER        1
+#define PNG_HANDLE_CHUNK_IF_SAFE      2
+#define PNG_HANDLE_CHUNK_ALWAYS       3
+#define PNG_HANDLE_CHUNK_LAST         4
+
+/* Strip the prepended error numbers ("#nnn ") from error and warning
+ * messages before passing them to the error or warning handler.
+ */
+#ifdef PNG_ERROR_NUMBERS_SUPPORTED
+PNG_EXPORT(185, void, png_set_strip_error_numbers, (png_structrp png_ptr,
+    png_uint_32 strip_mode));
+#endif
+
+/* Added in libpng-1.2.6 */
+#ifdef PNG_SET_USER_LIMITS_SUPPORTED
+PNG_EXPORT(186, void, png_set_user_limits, (png_structrp png_ptr,
+    png_uint_32 user_width_max, png_uint_32 user_height_max));
+PNG_EXPORT(187, png_uint_32, png_get_user_width_max,
+    (png_const_structrp png_ptr));
+PNG_EXPORT(188, png_uint_32, png_get_user_height_max,
+    (png_const_structrp png_ptr));
+/* Added in libpng-1.4.0 */
+PNG_EXPORT(189, void, png_set_chunk_cache_max, (png_structrp png_ptr,
+    png_uint_32 user_chunk_cache_max));
+PNG_EXPORT(190, png_uint_32, png_get_chunk_cache_max,
+    (png_const_structrp png_ptr));
+/* Added in libpng-1.4.1 */
+PNG_EXPORT(191, void, png_set_chunk_malloc_max, (png_structrp png_ptr,
+    png_alloc_size_t user_chunk_cache_max));
+PNG_EXPORT(192, png_alloc_size_t, png_get_chunk_malloc_max,
+    (png_const_structrp png_ptr));
+#endif
+
+#if defined(PNG_INCH_CONVERSIONS_SUPPORTED)
+PNG_EXPORT(193, png_uint_32, png_get_pixels_per_inch,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr));
+
+PNG_EXPORT(194, png_uint_32, png_get_x_pixels_per_inch,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr));
+
+PNG_EXPORT(195, png_uint_32, png_get_y_pixels_per_inch,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr));
+
+PNG_FP_EXPORT(196, float, png_get_x_offset_inches,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr))
+#ifdef PNG_FIXED_POINT_SUPPORTED /* otherwise not implemented. */
+PNG_FIXED_EXPORT(211, png_fixed_point, png_get_x_offset_inches_fixed,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr))
+#endif
+
+PNG_FP_EXPORT(197, float, png_get_y_offset_inches, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr))
+#ifdef PNG_FIXED_POINT_SUPPORTED /* otherwise not implemented. */
+PNG_FIXED_EXPORT(212, png_fixed_point, png_get_y_offset_inches_fixed,
+    (png_const_structrp png_ptr, png_const_inforp info_ptr))
+#endif
+
+#  ifdef PNG_pHYs_SUPPORTED
+PNG_EXPORT(198, png_uint_32, png_get_pHYs_dpi, (png_const_structrp png_ptr,
+    png_const_inforp info_ptr, png_uint_32 *res_x, png_uint_32 *res_y,
+    int *unit_type));
+#  endif /* PNG_pHYs_SUPPORTED */
+#endif  /* PNG_INCH_CONVERSIONS_SUPPORTED */
+
+/* Added in libpng-1.4.0 */
+#ifdef PNG_IO_STATE_SUPPORTED
+PNG_EXPORT(199, png_uint_32, png_get_io_state, (png_const_structrp png_ptr));
+
+/* Removed from libpng 1.6; use png_get_io_chunk_type. */
+PNG_REMOVED(200, png_const_bytep, png_get_io_chunk_name, (png_structrp png_ptr),
+    PNG_DEPRECATED)
+
+PNG_EXPORT(216, png_uint_32, png_get_io_chunk_type,
+    (png_const_structrp png_ptr));
+
+/* The flags returned by png_get_io_state() are the following: */
+#  define PNG_IO_NONE        0x0000   /* no I/O at this moment */
+#  define PNG_IO_READING     0x0001   /* currently reading */
+#  define PNG_IO_WRITING     0x0002   /* currently writing */
+#  define PNG_IO_SIGNATURE   0x0010   /* currently at the file signature */
+#  define PNG_IO_CHUNK_HDR   0x0020   /* currently at the chunk header */
+#  define PNG_IO_CHUNK_DATA  0x0040   /* currently at the chunk data */
+#  define PNG_IO_CHUNK_CRC   0x0080   /* currently at the chunk crc */
+#  define PNG_IO_MASK_OP     0x000f   /* current operation: reading/writing */
+#  define PNG_IO_MASK_LOC    0x00f0   /* current location: sig/hdr/data/crc */
+#endif /* ?PNG_IO_STATE_SUPPORTED */
+
+/* Interlace support.  The following macros are always defined so that if
+ * libpng interlace handling is turned off the macros may be used to handle
+ * interlaced images within the application.
+ */
+#define PNG_INTERLACE_ADAM7_PASSES 7
+
+/* Two macros to return the first row and first column of the original,
+ * full, image which appears in a given pass.  'pass' is in the range 0
+ * to 6 and the result is in the range 0 to 7.
+ */
+#define PNG_PASS_START_ROW(pass) (((1&~(pass))<<(3-((pass)>>1)))&7)
+#define PNG_PASS_START_COL(pass) (((1& (pass))<<(3-(((pass)+1)>>1)))&7)
+
+/* A macro to return the offset between pixels in the output row for a pair of
+ * pixels in the input - effectively the inverse of the 'COL_SHIFT' macro that
+ * follows.  Note that ROW_OFFSET is the offset from one row to the next whereas
+ * COL_OFFSET is from one column to the next, within a row.
+ */
+#define PNG_PASS_ROW_OFFSET(pass) ((pass)>2?(8>>(((pass)-1)>>1)):8)
+#define PNG_PASS_COL_OFFSET(pass) (1<<((7-(pass))>>1))
+
+/* Two macros to help evaluate the number of rows or columns in each
+ * pass.  This is expressed as a shift - effectively log2 of the number or
+ * rows or columns in each 8x8 tile of the original image.
+ */
+#define PNG_PASS_ROW_SHIFT(pass) ((pass)>2?(8-(pass))>>1:3)
+#define PNG_PASS_COL_SHIFT(pass) ((pass)>1?(7-(pass))>>1:3)
+
+/* Hence two macros to determine the number of rows or columns in a given
+ * pass of an image given its height or width.  In fact these macros may
+ * return non-zero even though the sub-image is empty, because the other
+ * dimension may be empty for a small image.
+ */
+#define PNG_PASS_ROWS(height, pass) (((height)+(((1<<PNG_PASS_ROW_SHIFT(pass))\
+   -1)-PNG_PASS_START_ROW(pass)))>>PNG_PASS_ROW_SHIFT(pass))
+#define PNG_PASS_COLS(width, pass) (((width)+(((1<<PNG_PASS_COL_SHIFT(pass))\
+   -1)-PNG_PASS_START_COL(pass)))>>PNG_PASS_COL_SHIFT(pass))
+
+/* For the reader row callbacks (both progressive and sequential) it is
+ * necessary to find the row in the output image given a row in an interlaced
+ * image, so two more macros:
+ */
+#define PNG_ROW_FROM_PASS_ROW(y_in, pass) \
+   (((y_in)<<PNG_PASS_ROW_SHIFT(pass))+PNG_PASS_START_ROW(pass))
+#define PNG_COL_FROM_PASS_COL(x_in, pass) \
+   (((x_in)<<PNG_PASS_COL_SHIFT(pass))+PNG_PASS_START_COL(pass))
+
+/* Two macros which return a boolean (0 or 1) saying whether the given row
+ * or column is in a particular pass.  These use a common utility macro that
+ * returns a mask for a given pass - the offset 'off' selects the row or
+ * column version.  The mask has the appropriate bit set for each column in
+ * the tile.
+ */
+#define PNG_PASS_MASK(pass,off) ( \
+   ((0x110145AF>>(((7-(off))-(pass))<<2)) & 0xF) | \
+   ((0x01145AF0>>(((7-(off))-(pass))<<2)) & 0xF0))
+
+#define PNG_ROW_IN_INTERLACE_PASS(y, pass) \
+   ((PNG_PASS_MASK(pass,0) >> ((y)&7)) & 1)
+#define PNG_COL_IN_INTERLACE_PASS(x, pass) \
+   ((PNG_PASS_MASK(pass,1) >> ((x)&7)) & 1)
+
+#ifdef PNG_READ_COMPOSITE_NODIV_SUPPORTED
+/* With these routines we avoid an integer divide, which will be slower on
+ * most machines.  However, it does take more operations than the corresponding
+ * divide method, so it may be slower on a few RISC systems.  There are two
+ * shifts (by 8 or 16 bits) and an addition, versus a single integer divide.
+ *
+ * Note that the rounding factors are NOT supposed to be the same!  128 and
+ * 32768 are correct for the NODIV code; 127 and 32767 are correct for the
+ * standard method.
+ *
+ * [Optimized code by Greg Roelofs and Mark Adler...blame us for bugs. :-) ]
+ */
+
+ /* fg and bg should be in `gamma 1.0' space; alpha is the opacity */
+
+#  define png_composite(composite, fg, alpha, bg)         \
+     { png_uint_16 temp = (png_uint_16)((png_uint_16)(fg) \
+           * (png_uint_16)(alpha)                         \
+           + (png_uint_16)(bg)*(png_uint_16)(255          \
+           - (png_uint_16)(alpha)) + 128);                \
+       (composite) = (png_byte)((temp + (temp >> 8)) >> 8); }
+
+#  define png_composite_16(composite, fg, alpha, bg)       \
+     { png_uint_32 temp = (png_uint_32)((png_uint_32)(fg)  \
+           * (png_uint_32)(alpha)                          \
+           + (png_uint_32)(bg)*(65535                      \
+           - (png_uint_32)(alpha)) + 32768);               \
+       (composite) = (png_uint_16)((temp + (temp >> 16)) >> 16); }
+
+#else  /* Standard method using integer division */
+
+#  define png_composite(composite, fg, alpha, bg)                          \
+     (composite) = (png_byte)(((png_uint_16)(fg) * (png_uint_16)(alpha) +  \
+     (png_uint_16)(bg) * (png_uint_16)(255 - (png_uint_16)(alpha)) +       \
+     127) / 255)
+
+#  define png_composite_16(composite, fg, alpha, bg)                         \
+     (composite) = (png_uint_16)(((png_uint_32)(fg) * (png_uint_32)(alpha) + \
+     (png_uint_32)(bg)*(png_uint_32)(65535 - (png_uint_32)(alpha)) +         \
+     32767) / 65535)
+#endif /* PNG_READ_COMPOSITE_NODIV_SUPPORTED */
+
+#ifdef PNG_READ_INT_FUNCTIONS_SUPPORTED
+PNG_EXPORT(201, png_uint_32, png_get_uint_32, (png_const_bytep buf));
+PNG_EXPORT(202, png_uint_16, png_get_uint_16, (png_const_bytep buf));
+PNG_EXPORT(203, png_int_32, png_get_int_32, (png_const_bytep buf));
+#endif
+
+PNG_EXPORT(204, png_uint_32, png_get_uint_31, (png_const_structrp png_ptr,
+    png_const_bytep buf));
+/* No png_get_int_16 -- may be added if there's a real need for it. */
+
+/* Place a 32-bit number into a buffer in PNG byte order (big-endian). */
+#ifdef PNG_WRITE_INT_FUNCTIONS_SUPPORTED
+PNG_EXPORT(205, void, png_save_uint_32, (png_bytep buf, png_uint_32 i));
+#endif
+#ifdef PNG_SAVE_INT_32_SUPPORTED
+PNG_EXPORT(206, void, png_save_int_32, (png_bytep buf, png_int_32 i));
+#endif
+
+/* Place a 16-bit number into a buffer in PNG byte order.
+ * The parameter is declared unsigned int, not png_uint_16,
+ * just to avoid potential problems on pre-ANSI C compilers.
+ */
+#ifdef PNG_WRITE_INT_FUNCTIONS_SUPPORTED
+PNG_EXPORT(207, void, png_save_uint_16, (png_bytep buf, unsigned int i));
+/* No png_save_int_16 -- may be added if there's a real need for it. */
+#endif
+
+#ifdef PNG_USE_READ_MACROS
+/* Inline macros to do direct reads of bytes from the input buffer.
+ * The png_get_int_32() routine assumes we are using two's complement
+ * format for negative values, which is almost certainly true.
+ */
+#  define PNG_get_uint_32(buf) \
+     (((png_uint_32)(*(buf)) << 24) + \
+      ((png_uint_32)(*((buf) + 1)) << 16) + \
+      ((png_uint_32)(*((buf) + 2)) << 8) + \
+      ((png_uint_32)(*((buf) + 3))))
+
+   /* From libpng-1.4.0 until 1.4.4, the png_get_uint_16 macro (but not the
+    * function) incorrectly returned a value of type png_uint_32.
+    */
+#  define PNG_get_uint_16(buf) \
+     ((png_uint_16) \
+      (((unsigned int)(*(buf)) << 8) + \
+       ((unsigned int)(*((buf) + 1)))))
+
+#  define PNG_get_int_32(buf) \
+     ((png_int_32)((*(buf) & 0x80) \
+      ? -((png_int_32)((png_get_uint_32(buf) ^ 0xffffffffL) + 1)) \
+      : (png_int_32)png_get_uint_32(buf)))
+
+   /* If PNG_PREFIX is defined the same thing as below happens in pnglibconf.h,
+    * but defining a macro name prefixed with PNG_PREFIX.
+    */
+#  ifndef PNG_PREFIX
+#     define png_get_uint_32(buf) PNG_get_uint_32(buf)
+#     define png_get_uint_16(buf) PNG_get_uint_16(buf)
+#     define png_get_int_32(buf)  PNG_get_int_32(buf)
+#  endif
+#else
+#  ifdef PNG_PREFIX
+      /* No macros; revert to the (redefined) function */
+#     define PNG_get_uint_32 (png_get_uint_32)
+#     define PNG_get_uint_16 (png_get_uint_16)
+#     define PNG_get_int_32  (png_get_int_32)
+#  endif
+#endif
+
+/*******************************************************************************
+ *  SIMPLIFIED API
+ *******************************************************************************
+ *
+ * Please read the documentation in libpng-manual.txt (TODO: write said
+ * documentation) if you don't understand what follows.
+ *
+ * The simplified API hides the details of both libpng and the PNG file format
+ * itself.  It allows PNG files to be read into a very limited number of
+ * in-memory bitmap formats or to be written from the same formats.  If these
+ * formats do not accomodate your needs then you can, and should, use the more
+ * sophisticated APIs above - these support a wide variety of in-memory formats
+ * and a wide variety of sophisticated transformations to those formats as well
+ * as a wide variety of APIs to manipulate ancillary information.
+ *
+ * To read a PNG file using the simplified API:
+ *
+ * 1) Declare a 'png_image' structure (see below) on the stack and set the
+ *    version field to PNG_IMAGE_VERSION.
+ * 2) Call the appropriate png_image_begin_read... function.
+ * 3) Set the png_image 'format' member to the required sample format.
+ * 4) Allocate a buffer for the image and, if required, the color-map.
+ * 5) Call png_image_finish_read to read the image and, if required, the
+ *    color-map into your buffers.
+ *
+ * There are no restrictions on the format of the PNG input itself; all valid
+ * color types, bit depths, and interlace methods are acceptable, and the
+ * input image is transformed as necessary to the requested in-memory format
+ * during the png_image_finish_read() step.  The only caveat is that if you
+ * request a color-mapped image from a PNG that is full-color or makes
+ * complex use of an alpha channel the transformation is extremely lossy and the
+ * result may look terrible.
+ *
+ * To write a PNG file using the simplified API:
+ *
+ * 1) Declare a 'png_image' structure on the stack and memset() it to all zero.
+ * 2) Initialize the members of the structure that describe the image, setting
+ *    the 'format' member to the format of the image samples.
+ * 3) Call the appropriate png_image_write... function with a pointer to the
+ *    image and, if necessary, the color-map to write the PNG data.
+ *
+ * png_image is a structure that describes the in-memory format of an image
+ * when it is being read or defines the in-memory format of an image that you
+ * need to write:
+ */
+#define PNG_IMAGE_VERSION 1
+
+typedef struct png_control *png_controlp;
+typedef struct
+{
+   png_controlp opaque;    /* Initialize to NULL, free with png_image_free */
+   png_uint_32  version;   /* Set to PNG_IMAGE_VERSION */
+   png_uint_32  width;     /* Image width in pixels (columns) */
+   png_uint_32  height;    /* Image height in pixels (rows) */
+   png_uint_32  format;    /* Image format as defined below */
+   png_uint_32  flags;     /* A bit mask containing informational flags */
+   png_uint_32  colormap_entries;
+                           /* Number of entries in the color-map */
+
+   /* In the event of an error or warning the following field will be set to a
+    * non-zero value and the 'message' field will contain a '\0' terminated
+    * string with the libpng error or warning message.  If both warnings and
+    * an error were encountered, only the error is recorded.  If there
+    * are multiple warnings, only the first one is recorded.
+    *
+    * The upper 30 bits of this value are reserved, the low two bits contain
+    * a value as follows:
+    */
+#  define PNG_IMAGE_WARNING 1
+#  define PNG_IMAGE_ERROR 2
+   /*
+    * The result is a two bit code such that a value more than 1 indicates
+    * a failure in the API just called:
+    *
+    *    0 - no warning or error
+    *    1 - warning
+    *    2 - error
+    *    3 - error preceded by warning
+    */
+#  define PNG_IMAGE_FAILED(png_cntrl) ((((png_cntrl).warning_or_error)&0x03)>1)
+
+   png_uint_32  warning_or_error;
+
+   char         message[64];
+} png_image, *png_imagep;
+
+/* The samples of the image have one to four channels whose components have
+ * original values in the range 0 to 1.0:
+ *
+ * 1: A single gray or luminance channel (G).
+ * 2: A gray/luminance channel and an alpha channel (GA).
+ * 3: Three red, green, blue color channels (RGB).
+ * 4: Three color channels and an alpha channel (RGBA).
+ *
+ * The components are encoded in one of two ways:
+ *
+ * a) As a small integer, value 0..255, contained in a single byte.  For the
+ * alpha channel the original value is simply value/255.  For the color or
+ * luminance channels the value is encoded according to the sRGB specification
+ * and matches the 8-bit format expected by typical display devices.
+ *
+ * The color/gray channels are not scaled (pre-multiplied) by the alpha
+ * channel and are suitable for passing to color management software.
+ *
+ * b) As a value in the range 0..65535, contained in a 2-byte integer.  All
+ * channels can be converted to the original value by dividing by 65535; all
+ * channels are linear.  Color channels use the RGB encoding (RGB end-points) of
+ * the sRGB specification.  This encoding is identified by the
+ * PNG_FORMAT_FLAG_LINEAR flag below.
+ *
+ * When the simplified API needs to convert between sRGB and linear colorspaces,
+ * the actual sRGB transfer curve defined in the sRGB specification (see the
+ * article at http://en.wikipedia.org/wiki/SRGB) is used, not the gamma=1/2.2
+ * approximation used elsewhere in libpng.
+ *
+ * When an alpha channel is present it is expected to denote pixel coverage
+ * of the color or luminance channels and is returned as an associated alpha
+ * channel: the color/gray channels are scaled (pre-multiplied) by the alpha
+ * value.
+ *
+ * The samples are either contained directly in the image data, between 1 and 8
+ * bytes per pixel according to the encoding, or are held in a color-map indexed
+ * by bytes in the image data.  In the case of a color-map the color-map entries
+ * are individual samples, encoded as above, and the image data has one byte per
+ * pixel to select the relevant sample from the color-map.
+ */
+
+/* PNG_FORMAT_*
+ *
+ * #defines to be used in png_image::format.  Each #define identifies a
+ * particular layout of sample data and, if present, alpha values.  There are
+ * separate defines for each of the two component encodings.
+ *
+ * A format is built up using single bit flag values.  All combinations are
+ * valid.  Formats can be built up from the flag values or you can use one of
+ * the predefined values below.  When testing formats always use the FORMAT_FLAG
+ * macros to test for individual features - future versions of the library may
+ * add new flags.
+ *
+ * When reading or writing color-mapped images the format should be set to the
+ * format of the entries in the color-map then png_image_{read,write}_colormap
+ * called to read or write the color-map and set the format correctly for the
+ * image data.  Do not set the PNG_FORMAT_FLAG_COLORMAP bit directly!
+ *
+ * NOTE: libpng can be built with particular features disabled, if you see
+ * compiler errors because the definition of one of the following flags has been
+ * compiled out it is because libpng does not have the required support.  It is
+ * possible, however, for the libpng configuration to enable the format on just
+ * read or just write; in that case you may see an error at run time.  You can
+ * guard against this by checking for the definition of the appropriate
+ * "_SUPPORTED" macro, one of:
+ *
+ *    PNG_SIMPLIFIED_{READ,WRITE}_{BGR,AFIRST}_SUPPORTED
+ */
+#define PNG_FORMAT_FLAG_ALPHA    0x01U /* format with an alpha channel */
+#define PNG_FORMAT_FLAG_COLOR    0x02U /* color format: otherwise grayscale */
+#define PNG_FORMAT_FLAG_LINEAR   0x04U /* 2 byte channels else 1 byte */
+#define PNG_FORMAT_FLAG_COLORMAP 0x08U /* image data is color-mapped */
+
+#ifdef PNG_FORMAT_BGR_SUPPORTED
+#  define PNG_FORMAT_FLAG_BGR    0x10U /* BGR colors, else order is RGB */
+#endif
+
+#ifdef PNG_FORMAT_AFIRST_SUPPORTED
+#  define PNG_FORMAT_FLAG_AFIRST 0x20U /* alpha channel comes first */
+#endif
+
+/* Commonly used formats have predefined macros.
+ *
+ * First the single byte (sRGB) formats:
+ */
+#define PNG_FORMAT_GRAY 0
+#define PNG_FORMAT_GA   PNG_FORMAT_FLAG_ALPHA
+#define PNG_FORMAT_AG   (PNG_FORMAT_GA|PNG_FORMAT_FLAG_AFIRST)
+#define PNG_FORMAT_RGB  PNG_FORMAT_FLAG_COLOR
+#define PNG_FORMAT_BGR  (PNG_FORMAT_FLAG_COLOR|PNG_FORMAT_FLAG_BGR)
+#define PNG_FORMAT_RGBA (PNG_FORMAT_RGB|PNG_FORMAT_FLAG_ALPHA)
+#define PNG_FORMAT_ARGB (PNG_FORMAT_RGBA|PNG_FORMAT_FLAG_AFIRST)
+#define PNG_FORMAT_BGRA (PNG_FORMAT_BGR|PNG_FORMAT_FLAG_ALPHA)
+#define PNG_FORMAT_ABGR (PNG_FORMAT_BGRA|PNG_FORMAT_FLAG_AFIRST)
+
+/* Then the linear 2-byte formats.  When naming these "Y" is used to
+ * indicate a luminance (gray) channel.
+ */
+#define PNG_FORMAT_LINEAR_Y PNG_FORMAT_FLAG_LINEAR
+#define PNG_FORMAT_LINEAR_Y_ALPHA (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_ALPHA)
+#define PNG_FORMAT_LINEAR_RGB (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_COLOR)
+#define PNG_FORMAT_LINEAR_RGB_ALPHA \
+   (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_COLOR|PNG_FORMAT_FLAG_ALPHA)
+
+/* With color-mapped formats the image data is one byte for each pixel, the byte
+ * is an index into the color-map which is formatted as above.  To obtain a
+ * color-mapped format it is sufficient just to add the PNG_FOMAT_FLAG_COLORMAP
+ * to one of the above definitions, or you can use one of the definitions below.
+ */
+#define PNG_FORMAT_RGB_COLORMAP  (PNG_FORMAT_RGB|PNG_FORMAT_FLAG_COLORMAP)
+#define PNG_FORMAT_BGR_COLORMAP  (PNG_FORMAT_BGR|PNG_FORMAT_FLAG_COLORMAP)
+#define PNG_FORMAT_RGBA_COLORMAP (PNG_FORMAT_RGBA|PNG_FORMAT_FLAG_COLORMAP)
+#define PNG_FORMAT_ARGB_COLORMAP (PNG_FORMAT_ARGB|PNG_FORMAT_FLAG_COLORMAP)
+#define PNG_FORMAT_BGRA_COLORMAP (PNG_FORMAT_BGRA|PNG_FORMAT_FLAG_COLORMAP)
+#define PNG_FORMAT_ABGR_COLORMAP (PNG_FORMAT_ABGR|PNG_FORMAT_FLAG_COLORMAP)
+
+/* PNG_IMAGE macros
+ *
+ * These are convenience macros to derive information from a png_image
+ * structure.  The PNG_IMAGE_SAMPLE_ macros return values appropriate to the
+ * actual image sample values - either the entries in the color-map or the
+ * pixels in the image.  The PNG_IMAGE_PIXEL_ macros return corresponding values
+ * for the pixels and will always return 1 for color-mapped formats.  The
+ * remaining macros return information about the rows in the image and the
+ * complete image.
+ *
+ * NOTE: All the macros that take a png_image::format parameter are compile time
+ * constants if the format parameter is, itself, a constant.  Therefore these
+ * macros can be used in array declarations and case labels where required.
+ * Similarly the macros are also pre-processor constants (sizeof is not used) so
+ * they can be used in #if tests.
+ *
+ * First the information about the samples.
+ */
+#define PNG_IMAGE_SAMPLE_CHANNELS(fmt)\
+   (((fmt)&(PNG_FORMAT_FLAG_COLOR|PNG_FORMAT_FLAG_ALPHA))+1)
+   /* Return the total number of channels in a given format: 1..4 */
+
+#define PNG_IMAGE_SAMPLE_COMPONENT_SIZE(fmt)\
+   ((((fmt) & PNG_FORMAT_FLAG_LINEAR) >> 2)+1)
+   /* Return the size in bytes of a single component of a pixel or color-map
+    * entry (as appropriate) in the image: 1 or 2.
+    */
+
+#define PNG_IMAGE_SAMPLE_SIZE(fmt)\
+   (PNG_IMAGE_SAMPLE_CHANNELS(fmt) * PNG_IMAGE_SAMPLE_COMPONENT_SIZE(fmt))
+   /* This is the size of the sample data for one sample.  If the image is
+    * color-mapped it is the size of one color-map entry (and image pixels are
+    * one byte in size), otherwise it is the size of one image pixel.
+    */
+
+#define PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(fmt)\
+   (PNG_IMAGE_SAMPLE_CHANNELS(fmt) * 256)
+   /* The maximum size of the color-map required by the format expressed in a
+    * count of components.  This can be used to compile-time allocate a
+    * color-map:
+    *
+    * png_uint_16 colormap[PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(linear_fmt)];
+    *
+    * png_byte colormap[PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(sRGB_fmt)];
+    *
+    * Alternatively use the PNG_IMAGE_COLORMAP_SIZE macro below to use the
+    * information from one of the png_image_begin_read_ APIs and dynamically
+    * allocate the required memory.
+    */
+
+/* Corresponding information about the pixels */
+#define PNG_IMAGE_PIXEL_(test,fmt)\
+   (((fmt)&PNG_FORMAT_FLAG_COLORMAP)?1:test(fmt))
+
+#define PNG_IMAGE_PIXEL_CHANNELS(fmt)\
+   PNG_IMAGE_PIXEL_(PNG_IMAGE_SAMPLE_CHANNELS,fmt)
+   /* The number of separate channels (components) in a pixel; 1 for a
+    * color-mapped image.
+    */
+
+#define PNG_IMAGE_PIXEL_COMPONENT_SIZE(fmt)\
+   PNG_IMAGE_PIXEL_(PNG_IMAGE_SAMPLE_COMPONENT_SIZE,fmt)
+   /* The size, in bytes, of each component in a pixel; 1 for a color-mapped
+    * image.
+    */
+
+#define PNG_IMAGE_PIXEL_SIZE(fmt) PNG_IMAGE_PIXEL_(PNG_IMAGE_SAMPLE_SIZE,fmt)
+   /* The size, in bytes, of a complete pixel; 1 for a color-mapped image. */
+
+/* Information about the whole row, or whole image */
+#define PNG_IMAGE_ROW_STRIDE(image)\
+   (PNG_IMAGE_PIXEL_CHANNELS((image).format) * (image).width)
+   /* Return the total number of components in a single row of the image; this
+    * is the minimum 'row stride', the minimum count of components between each
+    * row.  For a color-mapped image this is the minimum number of bytes in a
+    * row.
+    */
+
+#define PNG_IMAGE_BUFFER_SIZE(image, row_stride)\
+   (PNG_IMAGE_PIXEL_COMPONENT_SIZE((image).format)*(image).height*(row_stride))
+   /* Return the size, in bytes, of an image buffer given a png_image and a row
+    * stride - the number of components to leave space for in each row.
+    */
+
+#define PNG_IMAGE_SIZE(image)\
+   PNG_IMAGE_BUFFER_SIZE(image, PNG_IMAGE_ROW_STRIDE(image))
+   /* Return the size, in bytes, of the image in memory given just a png_image;
+    * the row stride is the minimum stride required for the image.
+    */
+
+#define PNG_IMAGE_COLORMAP_SIZE(image)\
+   (PNG_IMAGE_SAMPLE_SIZE((image).format) * (image).colormap_entries)
+   /* Return the size, in bytes, of the color-map of this image.  If the image
+    * format is not a color-map format this will return a size sufficient for
+    * 256 entries in the given format; check PNG_FORMAT_FLAG_COLORMAP if
+    * you don't want to allocate a color-map in this case.
+    */
+
+/* PNG_IMAGE_FLAG_*
+ *
+ * Flags containing additional information about the image are held in the
+ * 'flags' field of png_image.
+ */
+#define PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB 0x01
+   /* This indicates the the RGB values of the in-memory bitmap do not
+    * correspond to the red, green and blue end-points defined by sRGB.
+    */
+
+#define PNG_IMAGE_FLAG_FAST 0x02
+   /* On write emphasise speed over compression; the resultant PNG file will be
+    * larger but will be produced significantly faster, particular for large
+    * images.  Do not use this option for images which will be distributed, only
+    * used it when producing intermediate files that will be read back in
+    * repeatedly.  For a typical 24-bit image the option will double the read
+    * speed at the cost of increasing the image size by 25%, however for many
+    * more compressible images the PNG file can be 10 times larger with only a
+    * slight speed gain.
+    */
+
+#define PNG_IMAGE_FLAG_16BIT_sRGB 0x04
+   /* On read if the image is a 16-bit per component image and there is no gAMA
+    * or sRGB chunk assume that the components are sRGB encoded.  Notice that
+    * images output by the simplified API always have gamma information; setting
+    * this flag only affects the interpretation of 16-bit images from an
+    * external source.  It is recommended that the application expose this flag
+    * to the user; the user can normally easily recognize the difference between
+    * linear and sRGB encoding.  This flag has no effect on write - the data
+    * passed to the write APIs must have the correct encoding (as defined
+    * above.)
+    *
+    * If the flag is not set (the default) input 16-bit per component data is
+    * assumed to be linear.
+    *
+    * NOTE: the flag can only be set after the png_image_begin_read_ call,
+    * because that call initializes the 'flags' field.
+    */
+
+#ifdef PNG_SIMPLIFIED_READ_SUPPORTED
+/* READ APIs
+ * ---------
+ *
+ * The png_image passed to the read APIs must have been initialized by setting
+ * the png_controlp field 'opaque' to NULL (or, safer, memset the whole thing.)
+ */
+#ifdef PNG_STDIO_SUPPORTED
+PNG_EXPORT(234, int, png_image_begin_read_from_file, (png_imagep image,
+   const char *file_name));
+   /* The named file is opened for read and the image header is filled in
+    * from the PNG header in the file.
+    */
+
+PNG_EXPORT(235, int, png_image_begin_read_from_stdio, (png_imagep image,
+   FILE* file));
+   /* The PNG header is read from the stdio FILE object. */
+#endif /* PNG_STDIO_SUPPORTED */
+
+PNG_EXPORT(236, int, png_image_begin_read_from_memory, (png_imagep image,
+   png_const_voidp memory, png_size_t size));
+   /* The PNG header is read from the given memory buffer. */
+
+PNG_EXPORT(237, int, png_image_finish_read, (png_imagep image,
+   png_const_colorp background, void *buffer, png_int_32 row_stride,
+   void *colormap));
+   /* Finish reading the image into the supplied buffer and clean up the
+    * png_image structure.
+    *
+    * row_stride is the step, in byte or 2-byte units as appropriate,
+    * between adjacent rows.  A positive stride indicates that the top-most row
+    * is first in the buffer - the normal top-down arrangement.  A negative
+    * stride indicates that the bottom-most row is first in the buffer.
+    *
+    * background need only be supplied if an alpha channel must be removed from
+    * a png_byte format and the removal is to be done by compositing on a solid
+    * color; otherwise it may be NULL and any composition will be done directly
+    * onto the buffer.  The value is an sRGB color to use for the background,
+    * for grayscale output the green channel is used.
+    *
+    * background must be supplied when an alpha channel must be removed from a
+    * single byte color-mapped output format, in other words if:
+    *
+    * 1) The original format from png_image_begin_read_from_* had
+    *    PNG_FORMAT_FLAG_ALPHA set.
+    * 2) The format set by the application does not.
+    * 3) The format set by the application has PNG_FORMAT_FLAG_COLORMAP set and
+    *    PNG_FORMAT_FLAG_LINEAR *not* set.
+    *
+    * For linear output removing the alpha channel is always done by compositing
+    * on black and background is ignored.
+    *
+    * colormap must be supplied when PNG_FORMAT_FLAG_COLORMAP is set.  It must
+    * be at least the size (in bytes) returned by PNG_IMAGE_COLORMAP_SIZE.
+    * image->colormap_entries will be updated to the actual number of entries
+    * written to the colormap; this may be less than the original value.
+    */
+
+PNG_EXPORT(238, void, png_image_free, (png_imagep image));
+   /* Free any data allocated by libpng in image->opaque, setting the pointer to
+    * NULL.  May be called at any time after the structure is initialized.
+    */
+#endif /* PNG_SIMPLIFIED_READ_SUPPORTED */
+
+#ifdef PNG_SIMPLIFIED_WRITE_SUPPORTED
+/* WRITE APIS
+ * ----------
+ * For write you must initialize a png_image structure to describe the image to
+ * be written.  To do this use memset to set the whole structure to 0 then
+ * initialize fields describing your image.
+ *
+ * version: must be set to PNG_IMAGE_VERSION
+ * opaque: must be initialized to NULL
+ * width: image width in pixels
+ * height: image height in rows
+ * format: the format of the data (image and color-map) you wish to write
+ * flags: set to 0 unless one of the defined flags applies; set
+ *    PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB for color format images where the RGB
+ *    values do not correspond to the colors in sRGB.
+ * colormap_entries: set to the number of entries in the color-map (0 to 256)
+ */
+PNG_EXPORT(239, int, png_image_write_to_file, (png_imagep image,
+   const char *file, int convert_to_8bit, const void *buffer,
+   png_int_32 row_stride, const void *colormap));
+   /* Write the image to the named file. */
+
+PNG_EXPORT(240, int, png_image_write_to_stdio, (png_imagep image, FILE *file,
+   int convert_to_8_bit, const void *buffer, png_int_32 row_stride,
+   const void *colormap));
+   /* Write the image to the given (FILE*). */
+
+/* With both write APIs if image is in one of the linear formats with 16-bit
+ * data then setting convert_to_8_bit will cause the output to be an 8-bit PNG
+ * gamma encoded according to the sRGB specification, otherwise a 16-bit linear
+ * encoded PNG file is written.
+ *
+ * With color-mapped data formats the colormap parameter point to a color-map
+ * with at least image->colormap_entries encoded in the specified format.  If
+ * the format is linear the written PNG color-map will be converted to sRGB
+ * regardless of the convert_to_8_bit flag.
+ *
+ * With all APIs row_stride is handled as in the read APIs - it is the spacing
+ * from one row to the next in component sized units (1 or 2 bytes) and if
+ * negative indicates a bottom-up row layout in the buffer.
+ *
+ * Note that the write API does not support interlacing or sub-8-bit pixels.
+ */
+#endif /* PNG_SIMPLIFIED_WRITE_SUPPORTED */
+/*******************************************************************************
+ *  END OF SIMPLIFIED API
+ ******************************************************************************/
+
+#ifdef PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED
+PNG_EXPORT(242, void, png_set_check_for_invalid_index,
+    (png_structrp png_ptr, int allowed));
+#  ifdef PNG_GET_PALETTE_MAX_SUPPORTED
+PNG_EXPORT(243, int, png_get_palette_max, (png_const_structp png_ptr,
+    png_const_infop info_ptr));
+#  endif
+#endif /* CHECK_FOR_INVALID_INDEX */
+
+/*******************************************************************************
+ *  IMPLEMENTATION OPTIONS
+ *******************************************************************************
+ *
+ * Support for arbitrary implementation-specific optimizations.  The API allows
+ * particular options to be turned on or off.  'Option' is the number of the
+ * option and 'onoff' is 0 (off) or non-0 (on).  The value returned is given
+ * by the PNG_OPTION_ defines below.
+ *
+ * HARDWARE: normally hardware capabilites, such as the Intel SSE instructions,
+ *           are detected at run time, however sometimes it may be impossible
+ *           to do this in user mode, in which case it is necessary to discover
+ *           the capabilities in an OS specific way.  Such capabilities are
+ *           listed here when libpng has support for them and must be turned
+ *           ON by the application if present.
+ *
+ * SOFTWARE: sometimes software optimizations actually result in performance
+ *           decrease on some architectures or systems, or with some sets of
+ *           PNG images.  'Software' options allow such optimizations to be
+ *           selected at run time.
+ */
+#ifdef PNG_SET_OPTION_SUPPORTED
+#ifdef PNG_ARM_NEON_API_SUPPORTED
+#  define PNG_ARM_NEON   0 /* HARDWARE: ARM Neon SIMD instructions supported */
+#endif
+#define PNG_OPTION_NEXT  2 /* Next option - numbers must be even */
+
+/* Return values: NOTE: there are four values and 'off' is *not* zero */
+#define PNG_OPTION_UNSET   0 /* Unset - defaults to off */
+#define PNG_OPTION_INVALID 1 /* Option number out of range */
+#define PNG_OPTION_OFF     2
+#define PNG_OPTION_ON      3
+
+PNG_EXPORT(244, int, png_set_option, (png_structrp png_ptr, int option,
+   int onoff));
+#endif
+
+/*******************************************************************************
+ *  END OF HARDWARE OPTIONS
+ ******************************************************************************/
+
+/* Maintainer: Put new public prototypes here ^, in libpng.3, and project
+ * defs, scripts/pnglibconf.h, and scripts/pnglibconf.h.prebuilt
+ */
+
+/* The last ordinal number (this is the *last* one already used; the next
+ * one to use is one more than this.)  Maintainer, remember to add an entry to
+ * scripts/symbols.def as well.
+ */
+#ifdef PNG_EXPORT_LAST_ORDINAL
+  PNG_EXPORT_LAST_ORDINAL(244);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PNG_VERSION_INFO_ONLY */
+/* Do not put anything past this line */
+#endif /* PNG_H */
diff --git a/fb2png/libpng/include/pngconf.h b/fb2png/libpng/include/pngconf.h
new file mode 100644
index 0000000..d9dbff0
--- /dev/null
+++ b/fb2png/libpng/include/pngconf.h
@@ -0,0 +1,616 @@
+
+/* pngconf.h - machine configurable file for libpng
+ *
+ * libpng version 1.6.1 - March 28, 2013
+ *
+ * Copyright (c) 1998-2013 Glenn Randers-Pehrson
+ * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
+ * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ */
+
+/* Any machine specific code is near the front of this file, so if you
+ * are configuring libpng for a machine, you may want to read the section
+ * starting here down to where it starts to typedef png_color, png_text,
+ * and png_info.
+ */
+
+#ifndef PNGCONF_H
+#define PNGCONF_H
+
+/* To do: Do all of this in scripts/pnglibconf.dfa */
+#ifdef PNG_SAFE_LIMITS_SUPPORTED
+#  ifdef PNG_USER_WIDTH_MAX
+#    undef PNG_USER_WIDTH_MAX
+#    define PNG_USER_WIDTH_MAX 1000000L
+#  endif
+#  ifdef PNG_USER_HEIGHT_MAX
+#    undef PNG_USER_HEIGHT_MAX
+#    define PNG_USER_HEIGHT_MAX 1000000L
+#  endif
+#  ifdef PNG_USER_CHUNK_MALLOC_MAX
+#    undef PNG_USER_CHUNK_MALLOC_MAX
+#    define PNG_USER_CHUNK_MALLOC_MAX 4000000L
+#  endif
+#  ifdef PNG_USER_CHUNK_CACHE_MAX
+#    undef PNG_USER_CHUNK_CACHE_MAX
+#    define PNG_USER_CHUNK_CACHE_MAX 128
+#  endif
+#endif
+
+#ifndef PNG_BUILDING_SYMBOL_TABLE /* else includes may cause problems */
+
+/* From libpng 1.6.0 libpng requires an ANSI X3.159-1989 ("ISOC90") compliant C
+ * compiler for correct compilation.  The following header files are required by
+ * the standard.  If your compiler doesn't provide these header files, or they
+ * do not match the standard, you will need to provide/improve them.
+ */
+#include <limits.h>
+#include <stddef.h>
+
+/* Library header files.  These header files are all defined by ISOC90; libpng
+ * expects conformant implementations, however, an ISOC90 conformant system need
+ * not provide these header files if the functionality cannot be implemented.
+ * In this case it will be necessary to disable the relevant parts of libpng in
+ * the build of pnglibconf.h.
+ *
+ * Prior to 1.6.0 string.h was included here; the API changes in 1.6.0 to not
+ * include this unnecessary header file.
+ */
+
+#ifdef PNG_STDIO_SUPPORTED
+   /* Required for the definition of FILE: */
+#  include <stdio.h>
+#endif
+
+#ifdef PNG_SETJMP_SUPPORTED
+   /* Required for the definition of jmp_buf and the declaration of longjmp: */
+#  include <setjmp.h>
+#endif
+
+#ifdef PNG_CONVERT_tIME_SUPPORTED
+   /* Required for struct tm: */
+#  include <time.h>
+#endif
+
+#endif /* PNG_BUILDING_SYMBOL_TABLE */
+
+/* Prior to 1.6.0 it was possible to turn off 'const' in declarations using
+ * PNG_NO_CONST; this is no longer supported except for data declarations which
+ * apparently still cause problems in 2011 on some compilers.
+ */
+#define PNG_CONST const /* backward compatibility only */
+
+/* This controls optimization of the reading of 16 and 32 bit values
+ * from PNG files.  It can be set on a per-app-file basis - it
+ * just changes whether a macro is used when the function is called.
+ * The library builder sets the default; if read functions are not
+ * built into the library the macro implementation is forced on.
+ */
+#ifndef PNG_READ_INT_FUNCTIONS_SUPPORTED
+#  define PNG_USE_READ_MACROS
+#endif
+#if !defined(PNG_NO_USE_READ_MACROS) && !defined(PNG_USE_READ_MACROS)
+#  if PNG_DEFAULT_READ_MACROS
+#    define PNG_USE_READ_MACROS
+#  endif
+#endif
+
+/* COMPILER SPECIFIC OPTIONS.
+ *
+ * These options are provided so that a variety of difficult compilers
+ * can be used.  Some are fixed at build time (e.g. PNG_API_RULE
+ * below) but still have compiler specific implementations, others
+ * may be changed on a per-file basis when compiling against libpng.
+ */
+
+/* The PNGARG macro was used in versions of libpng prior to 1.6.0 to protect
+ * against legacy (pre ISOC90) compilers that did not understand function
+ * prototypes.  It is not required for modern C compilers.
+ */
+#ifndef PNGARG
+#  define PNGARG(arglist) arglist
+#endif
+
+/* Function calling conventions.
+ * =============================
+ * Normally it is not necessary to specify to the compiler how to call
+ * a function - it just does it - however on x86 systems derived from
+ * Microsoft and Borland C compilers ('IBM PC', 'DOS', 'Windows' systems
+ * and some others) there are multiple ways to call a function and the
+ * default can be changed on the compiler command line.  For this reason
+ * libpng specifies the calling convention of every exported function and
+ * every function called via a user supplied function pointer.  This is
+ * done in this file by defining the following macros:
+ *
+ * PNGAPI    Calling convention for exported functions.
+ * PNGCBAPI  Calling convention for user provided (callback) functions.
+ * PNGCAPI   Calling convention used by the ANSI-C library (required
+ *           for longjmp callbacks and sometimes used internally to
+ *           specify the calling convention for zlib).
+ *
+ * These macros should never be overridden.  If it is necessary to
+ * change calling convention in a private build this can be done
+ * by setting PNG_API_RULE (which defaults to 0) to one of the values
+ * below to select the correct 'API' variants.
+ *
+ * PNG_API_RULE=0 Use PNGCAPI - the 'C' calling convention - throughout.
+ *                This is correct in every known environment.
+ * PNG_API_RULE=1 Use the operating system convention for PNGAPI and
+ *                the 'C' calling convention (from PNGCAPI) for
+ *                callbacks (PNGCBAPI).  This is no longer required
+ *                in any known environment - if it has to be used
+ *                please post an explanation of the problem to the
+ *                libpng mailing list.
+ *
+ * These cases only differ if the operating system does not use the C
+ * calling convention, at present this just means the above cases
+ * (x86 DOS/Windows sytems) and, even then, this does not apply to
+ * Cygwin running on those systems.
+ *
+ * Note that the value must be defined in pnglibconf.h so that what
+ * the application uses to call the library matches the conventions
+ * set when building the library.
+ */
+
+/* Symbol export
+ * =============
+ * When building a shared library it is almost always necessary to tell
+ * the compiler which symbols to export.  The png.h macro 'PNG_EXPORT'
+ * is used to mark the symbols.  On some systems these symbols can be
+ * extracted at link time and need no special processing by the compiler,
+ * on other systems the symbols are flagged by the compiler and just
+ * the declaration requires a special tag applied (unfortunately) in a
+ * compiler dependent way.  Some systems can do either.
+ *
+ * A small number of older systems also require a symbol from a DLL to
+ * be flagged to the program that calls it.  This is a problem because
+ * we do not know in the header file included by application code that
+ * the symbol will come from a shared library, as opposed to a statically
+ * linked one.  For this reason the application must tell us by setting
+ * the magic flag PNG_USE_DLL to turn on the special processing before
+ * it includes png.h.
+ *
+ * Four additional macros are used to make this happen:
+ *
+ * PNG_IMPEXP The magic (if any) to cause a symbol to be exported from
+ *            the build or imported if PNG_USE_DLL is set - compiler
+ *            and system specific.
+ *
+ * PNG_EXPORT_TYPE(type) A macro that pre or appends PNG_IMPEXP to
+ *                       'type', compiler specific.
+ *
+ * PNG_DLL_EXPORT Set to the magic to use during a libpng build to
+ *                make a symbol exported from the DLL.  Not used in the
+ *                public header files; see pngpriv.h for how it is used
+ *                in the libpng build.
+ *
+ * PNG_DLL_IMPORT Set to the magic to force the libpng symbols to come
+ *                from a DLL - used to define PNG_IMPEXP when
+ *                PNG_USE_DLL is set.
+ */
+
+/* System specific discovery.
+ * ==========================
+ * This code is used at build time to find PNG_IMPEXP, the API settings
+ * and PNG_EXPORT_TYPE(), it may also set a macro to indicate the DLL
+ * import processing is possible.  On Windows systems it also sets
+ * compiler-specific macros to the values required to change the calling
+ * conventions of the various functions.
+ */
+#if defined(_Windows) || defined(_WINDOWS) || defined(WIN32) ||\
+    defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
+  /* Windows system (DOS doesn't support DLLs).  Includes builds under Cygwin or
+   * MinGW on any architecture currently supported by Windows.  Also includes
+   * Watcom builds but these need special treatment because they are not
+   * compatible with GCC or Visual C because of different calling conventions.
+   */
+#  if PNG_API_RULE == 2
+    /* If this line results in an error, either because __watcall is not
+     * understood or because of a redefine just below you cannot use *this*
+     * build of the library with the compiler you are using.  *This* build was
+     * build using Watcom and applications must also be built using Watcom!
+     */
+#    define PNGCAPI __watcall
+#  endif
+
+#  if defined(__GNUC__) || (defined(_MSC_VER) && (_MSC_VER >= 800))
+#    define PNGCAPI __cdecl
+#    if PNG_API_RULE == 1
+       /* If this line results in an error __stdcall is not understood and
+        * PNG_API_RULE should not have been set to '1'.
+        */
+#      define PNGAPI __stdcall
+#    endif
+#  else
+    /* An older compiler, or one not detected (erroneously) above,
+     * if necessary override on the command line to get the correct
+     * variants for the compiler.
+     */
+#    ifndef PNGCAPI
+#      define PNGCAPI _cdecl
+#    endif
+#    if PNG_API_RULE == 1 && !defined(PNGAPI)
+#      define PNGAPI _stdcall
+#    endif
+#  endif /* compiler/api */
+  /* NOTE: PNGCBAPI always defaults to PNGCAPI. */
+
+#  if defined(PNGAPI) && !defined(PNG_USER_PRIVATEBUILD)
+#     error "PNG_USER_PRIVATEBUILD must be defined if PNGAPI is changed"
+#  endif
+
+#  if (defined(_MSC_VER) && _MSC_VER < 800) ||\
+      (defined(__BORLANDC__) && __BORLANDC__ < 0x500)
+    /* older Borland and MSC
+     * compilers used '__export' and required this to be after
+     * the type.
+     */
+#    ifndef PNG_EXPORT_TYPE
+#      define PNG_EXPORT_TYPE(type) type PNG_IMPEXP
+#    endif
+#    define PNG_DLL_EXPORT __export
+#  else /* newer compiler */
+#    define PNG_DLL_EXPORT __declspec(dllexport)
+#    ifndef PNG_DLL_IMPORT
+#      define PNG_DLL_IMPORT __declspec(dllimport)
+#    endif
+#  endif /* compiler */
+
+#else /* !Windows */
+#  if (defined(__IBMC__) || defined(__IBMCPP__)) && defined(__OS2__)
+#    define PNGAPI _System
+#  else /* !Windows/x86 && !OS/2 */
+    /* Use the defaults, or define PNG*API on the command line (but
+     * this will have to be done for every compile!)
+     */
+#  endif /* other system, !OS/2 */
+#endif /* !Windows/x86 */
+
+/* Now do all the defaulting . */
+#ifndef PNGCAPI
+#  define PNGCAPI
+#endif
+#ifndef PNGCBAPI
+#  define PNGCBAPI PNGCAPI
+#endif
+#ifndef PNGAPI
+#  define PNGAPI PNGCAPI
+#endif
+
+/* PNG_IMPEXP may be set on the compilation system command line or (if not set)
+ * then in an internal header file when building the library, otherwise (when
+ * using the library) it is set here.
+ */
+#ifndef PNG_IMPEXP
+#  if defined(PNG_USE_DLL) && defined(PNG_DLL_IMPORT)
+     /* This forces use of a DLL, disallowing static linking */
+#    define PNG_IMPEXP PNG_DLL_IMPORT
+#  endif
+
+#  ifndef PNG_IMPEXP
+#    define PNG_IMPEXP
+#  endif
+#endif
+
+/* In 1.5.2 the definition of PNG_FUNCTION has been changed to always treat
+ * 'attributes' as a storage class - the attributes go at the start of the
+ * function definition, and attributes are always appended regardless of the
+ * compiler.  This considerably simplifies these macros but may cause problems
+ * if any compilers both need function attributes and fail to handle them as
+ * a storage class (this is unlikely.)
+ */
+#ifndef PNG_FUNCTION
+#  define PNG_FUNCTION(type, name, args, attributes) attributes type name args
+#endif
+
+#ifndef PNG_EXPORT_TYPE
+#  define PNG_EXPORT_TYPE(type) PNG_IMPEXP type
+#endif
+
+   /* The ordinal value is only relevant when preprocessing png.h for symbol
+    * table entries, so we discard it here.  See the .dfn files in the
+    * scripts directory.
+    */
+#ifndef PNG_EXPORTA
+
+#  define PNG_EXPORTA(ordinal, type, name, args, attributes)\
+      PNG_FUNCTION(PNG_EXPORT_TYPE(type),(PNGAPI name),PNGARG(args), \
+        extern attributes)
+#endif
+
+/* ANSI-C (C90) does not permit a macro to be invoked with an empty argument,
+ * so make something non-empty to satisfy the requirement:
+ */
+#define PNG_EMPTY /*empty list*/
+
+#define PNG_EXPORT(ordinal, type, name, args)\
+   PNG_EXPORTA(ordinal, type, name, args, PNG_EMPTY)
+
+/* Use PNG_REMOVED to comment out a removed interface. */
+#ifndef PNG_REMOVED
+#  define PNG_REMOVED(ordinal, type, name, args, attributes)
+#endif
+
+#ifndef PNG_CALLBACK
+#  define PNG_CALLBACK(type, name, args) type (PNGCBAPI name) PNGARG(args)
+#endif
+
+/* Support for compiler specific function attributes.  These are used
+ * so that where compiler support is available incorrect use of API
+ * functions in png.h will generate compiler warnings.
+ *
+ * Added at libpng-1.2.41.
+ */
+
+#ifndef PNG_NO_PEDANTIC_WARNINGS
+#  ifndef PNG_PEDANTIC_WARNINGS_SUPPORTED
+#    define PNG_PEDANTIC_WARNINGS_SUPPORTED
+#  endif
+#endif
+
+#ifdef PNG_PEDANTIC_WARNINGS_SUPPORTED
+  /* Support for compiler specific function attributes.  These are used
+   * so that where compiler support is available, incorrect use of API
+   * functions in png.h will generate compiler warnings.  Added at libpng
+   * version 1.2.41.  Disabling these removes the warnings but may also produce
+   * less efficient code.
+   */
+#  if defined(__GNUC__)
+#    ifndef PNG_USE_RESULT
+#      define PNG_USE_RESULT __attribute__((__warn_unused_result__))
+#    endif
+#    ifndef PNG_NORETURN
+#      define PNG_NORETURN   __attribute__((__noreturn__))
+#    endif
+#    if __GNUC__ >= 3
+#      ifndef PNG_ALLOCATED
+#        define PNG_ALLOCATED  __attribute__((__malloc__))
+#      endif
+#      ifndef PNG_DEPRECATED
+#        define PNG_DEPRECATED __attribute__((__deprecated__))
+#      endif
+#      ifndef PNG_PRIVATE
+#        if 0 /* Doesn't work so we use deprecated instead*/
+#          define PNG_PRIVATE \
+            __attribute__((warning("This function is not exported by libpng.")))
+#        else
+#          define PNG_PRIVATE \
+            __attribute__((__deprecated__))
+#        endif
+#      endif
+#      if ((__GNUC__ != 3) || !defined(__GNUC_MINOR__) || (__GNUC_MINOR__ >= 1))
+#        ifndef PNG_RESTRICT
+#          define PNG_RESTRICT __restrict
+#        endif
+#      endif /*  __GNUC__ == 3.0 */
+#    endif /*  __GNUC__ >= 3 */
+
+#  elif defined(_MSC_VER)  && (_MSC_VER >= 1300)
+#    ifndef PNG_USE_RESULT
+#      define PNG_USE_RESULT /* not supported */
+#    endif
+#    ifndef PNG_NORETURN
+#      define PNG_NORETURN   __declspec(noreturn)
+#    endif
+#    ifndef PNG_ALLOCATED
+#      if (_MSC_VER >= 1400)
+#        define PNG_ALLOCATED __declspec(restrict)
+#      endif
+#    endif
+#    ifndef PNG_DEPRECATED
+#      define PNG_DEPRECATED __declspec(deprecated)
+#    endif
+#    ifndef PNG_PRIVATE
+#      define PNG_PRIVATE __declspec(deprecated)
+#    endif
+#    ifndef PNG_RESTRICT
+#      if (_MSC_VER >= 1400)
+#        define PNG_RESTRICT __restrict
+#      endif
+#    endif
+
+#  elif defined(__WATCOMC__)
+#    ifndef PNG_RESTRICT
+#      define PNG_RESTRICT __restrict
+#    endif
+#  endif /* _MSC_VER */
+#endif /* PNG_PEDANTIC_WARNINGS */
+
+#ifndef PNG_DEPRECATED
+#  define PNG_DEPRECATED  /* Use of this function is deprecated */
+#endif
+#ifndef PNG_USE_RESULT
+#  define PNG_USE_RESULT  /* The result of this function must be checked */
+#endif
+#ifndef PNG_NORETURN
+#  define PNG_NORETURN    /* This function does not return */
+#endif
+#ifndef PNG_ALLOCATED
+#  define PNG_ALLOCATED   /* The result of the function is new memory */
+#endif
+#ifndef PNG_PRIVATE
+#  define PNG_PRIVATE     /* This is a private libpng function */
+#endif
+#ifndef PNG_RESTRICT
+#  define PNG_RESTRICT    /* The C99 "restrict" feature */
+#endif
+#ifndef PNG_FP_EXPORT     /* A floating point API. */
+#  ifdef PNG_FLOATING_POINT_SUPPORTED
+#     define PNG_FP_EXPORT(ordinal, type, name, args)\
+         PNG_EXPORT(ordinal, type, name, args);
+#  else                   /* No floating point APIs */
+#     define PNG_FP_EXPORT(ordinal, type, name, args)
+#  endif
+#endif
+#ifndef PNG_FIXED_EXPORT  /* A fixed point API. */
+#  ifdef PNG_FIXED_POINT_SUPPORTED
+#     define PNG_FIXED_EXPORT(ordinal, type, name, args)\
+         PNG_EXPORT(ordinal, type, name, args);
+#  else                   /* No fixed point APIs */
+#     define PNG_FIXED_EXPORT(ordinal, type, name, args)
+#  endif
+#endif
+
+#ifndef PNG_BUILDING_SYMBOL_TABLE
+/* Some typedefs to get us started.  These should be safe on most of the common
+ * platforms.
+ *
+ * png_uint_32 and png_int_32 may, currently, be larger than required to hold a
+ * 32-bit value however this is not normally advisable.
+ *
+ * png_uint_16 and png_int_16 should always be two bytes in size - this is
+ * verified at library build time.
+ *
+ * png_byte must always be one byte in size.
+ *
+ * The checks below use constants from limits.h, as defined by the ISOC90
+ * standard.
+ */
+#if CHAR_BIT == 8 && UCHAR_MAX == 255
+   typedef unsigned char png_byte;
+#else
+#  error "libpng requires 8 bit bytes"
+#endif
+
+#if INT_MIN == -32768 && INT_MAX == 32767
+   typedef int png_int_16;
+#elif SHRT_MIN == -32768 && SHRT_MAX == 32767
+   typedef short png_int_16;
+#else
+#  error "libpng requires a signed 16 bit type"
+#endif
+
+#if UINT_MAX == 65535
+   typedef unsigned int png_uint_16;
+#elif USHRT_MAX == 65535
+   typedef unsigned short png_uint_16;
+#else
+#  error "libpng requires an unsigned 16 bit type"
+#endif
+
+#if INT_MIN < -2147483646 && INT_MAX > 2147483646
+   typedef int png_int_32;
+#elif LONG_MIN < -2147483646 && LONG_MAX > 2147483646
+   typedef long int png_int_32;
+#else
+#  error "libpng requires a signed 32 bit (or more) type"
+#endif
+
+#if UINT_MAX > 4294967294
+   typedef unsigned int png_uint_32;
+#elif ULONG_MAX > 4294967294
+   typedef unsigned long int png_uint_32;
+#else
+#  error "libpng requires an unsigned 32 bit (or more) type"
+#endif
+
+/* Prior to 1.6.0 it was possible to disable the use of size_t, 1.6.0, however,
+ * requires an ISOC90 compiler and relies on consistent behavior of sizeof.
+ */
+typedef size_t png_size_t;
+typedef ptrdiff_t png_ptrdiff_t;
+
+/* libpng needs to know the maximum value of 'size_t' and this controls the
+ * definition of png_alloc_size_t, below.  This maximum value of size_t limits
+ * but does not control the maximum allocations the library makes - there is
+ * direct application control of this through png_set_user_limits().
+ */
+#ifndef PNG_SMALL_SIZE_T
+   /* Compiler specific tests for systems where size_t is known to be less than
+    * 32 bits (some of these systems may no longer work because of the lack of
+    * 'far' support; see above.)
+    */
+#  if (defined(__TURBOC__) && !defined(__FLAT__)) ||\
+   (defined(_MSC_VER) && defined(MAXSEG_64K))
+#     define PNG_SMALL_SIZE_T
+#  endif
+#endif
+
+/* png_alloc_size_t is guaranteed to be no smaller than png_size_t, and no
+ * smaller than png_uint_32.  Casts from png_size_t or png_uint_32 to
+ * png_alloc_size_t are not necessary; in fact, it is recommended not to use
+ * them at all so that the compiler can complain when something turns out to be
+ * problematic.
+ *
+ * Casts in the other direction (from png_alloc_size_t to png_size_t or
+ * png_uint_32) should be explicitly applied; however, we do not expect to
+ * encounter practical situations that require such conversions.
+ *
+ * PNG_SMALL_SIZE_T must be defined if the maximum value of size_t is less than
+ * 4294967295 - i.e. less than the maximum value of png_uint_32.
+ */
+#ifdef PNG_SMALL_SIZE_T
+   typedef png_uint_32 png_alloc_size_t;
+#else
+   typedef png_size_t png_alloc_size_t;
+#endif
+
+/* Prior to 1.6.0 libpng offered limited support for Microsoft C compiler
+ * implementations of Intel CPU specific support of user-mode segmented address
+ * spaces, where 16-bit pointers address more than 65536 bytes of memory using
+ * separate 'segment' registers.  The implementation requires two different
+ * types of pointer (only one of which includes the segment value.)
+ *
+ * If required this support is available in version 1.2 of libpng and may be
+ * available in versions through 1.5, although the correctness of the code has
+ * not been verified recently.
+ */
+
+/* Typedef for floating-point numbers that are converted to fixed-point with a
+ * multiple of 100,000, e.g., gamma
+ */
+typedef png_int_32 png_fixed_point;
+
+/* Add typedefs for pointers */
+typedef void                  * png_voidp;
+typedef const void            * png_const_voidp;
+typedef png_byte              * png_bytep;
+typedef const png_byte        * png_const_bytep;
+typedef png_uint_32           * png_uint_32p;
+typedef const png_uint_32     * png_const_uint_32p;
+typedef png_int_32            * png_int_32p;
+typedef const png_int_32      * png_const_int_32p;
+typedef png_uint_16           * png_uint_16p;
+typedef const png_uint_16     * png_const_uint_16p;
+typedef png_int_16            * png_int_16p;
+typedef const png_int_16      * png_const_int_16p;
+typedef char                  * png_charp;
+typedef const char            * png_const_charp;
+typedef png_fixed_point       * png_fixed_point_p;
+typedef const png_fixed_point * png_const_fixed_point_p;
+typedef png_size_t            * png_size_tp;
+typedef const png_size_t      * png_const_size_tp;
+
+#ifdef PNG_STDIO_SUPPORTED
+typedef FILE            * png_FILE_p;
+#endif
+
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+typedef double       * png_doublep;
+typedef const double * png_const_doublep;
+#endif
+
+/* Pointers to pointers; i.e. arrays */
+typedef png_byte        * * png_bytepp;
+typedef png_uint_32     * * png_uint_32pp;
+typedef png_int_32      * * png_int_32pp;
+typedef png_uint_16     * * png_uint_16pp;
+typedef png_int_16      * * png_int_16pp;
+typedef const char      * * png_const_charpp;
+typedef char            * * png_charpp;
+typedef png_fixed_point * * png_fixed_point_pp;
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+typedef double          * * png_doublepp;
+#endif
+
+/* Pointers to pointers to pointers; i.e., pointer to array */
+typedef char            * * * png_charppp;
+
+#endif /* PNG_BUILDING_SYMBOL_TABLE */
+
+#endif /* PNGCONF_H */
diff --git a/fb2png/libpng/include/pnglibconf.h b/fb2png/libpng/include/pnglibconf.h
new file mode 100644
index 0000000..b9c75e8
--- /dev/null
+++ b/fb2png/libpng/include/pnglibconf.h
@@ -0,0 +1,209 @@
+/* pnglibconf.h - library build configuration */
+
+/* libpng version 1.6.1 - March 28, 2013 */
+
+/* Copyright (c) 1998-2012 Glenn Randers-Pehrson */
+
+/* This code is released under the libpng license. */
+/* For conditions of distribution and use, see the disclaimer */
+/* and license in png.h */
+
+/* pnglibconf.h */
+/* Machine generated file: DO NOT EDIT */
+/* Derived from: scripts/pnglibconf.dfa */
+#ifndef PNGLCONF_H
+#define PNGLCONF_H
+/* options */
+#define PNG_16BIT_SUPPORTED
+#define PNG_ALIGNED_MEMORY_SUPPORTED
+/*#undef PNG_ARM_NEON_API_SUPPORTED*/
+/*#undef PNG_ARM_NEON_CHECK_SUPPORTED*/
+/*#undef PNG_ARM_NEON_SUPPORTED*/
+#define PNG_BENIGN_ERRORS_SUPPORTED
+#define PNG_BENIGN_READ_ERRORS_SUPPORTED
+/*#undef PNG_BENIGN_WRITE_ERRORS_SUPPORTED*/
+#define PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED
+#define PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED
+#define PNG_COLORSPACE_SUPPORTED
+#define PNG_CONSOLE_IO_SUPPORTED
+#define PNG_CONVERT_tIME_SUPPORTED
+#define PNG_EASY_ACCESS_SUPPORTED
+/*#undef PNG_ERROR_NUMBERS_SUPPORTED*/
+#define PNG_ERROR_TEXT_SUPPORTED
+#define PNG_FIXED_POINT_SUPPORTED
+#define PNG_FLOATING_ARITHMETIC_SUPPORTED
+#define PNG_FLOATING_POINT_SUPPORTED
+#define PNG_FORMAT_AFIRST_SUPPORTED
+#define PNG_FORMAT_BGR_SUPPORTED
+#define PNG_GAMMA_SUPPORTED
+#define PNG_GET_PALETTE_MAX_SUPPORTED
+#define PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+#define PNG_INCH_CONVERSIONS_SUPPORTED
+#define PNG_INFO_IMAGE_SUPPORTED
+#define PNG_IO_STATE_SUPPORTED
+#define PNG_MNG_FEATURES_SUPPORTED
+#define PNG_POINTER_INDEXING_SUPPORTED
+#define PNG_PROGRESSIVE_READ_SUPPORTED
+#define PNG_READ_16BIT_SUPPORTED
+#define PNG_READ_ALPHA_MODE_SUPPORTED
+#define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED
+#define PNG_READ_BACKGROUND_SUPPORTED
+#define PNG_READ_BGR_SUPPORTED
+#define PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
+#define PNG_READ_COMPOSITE_NODIV_SUPPORTED
+#define PNG_READ_COMPRESSED_TEXT_SUPPORTED
+#define PNG_READ_EXPAND_16_SUPPORTED
+#define PNG_READ_EXPAND_SUPPORTED
+#define PNG_READ_FILLER_SUPPORTED
+#define PNG_READ_GAMMA_SUPPORTED
+#define PNG_READ_GET_PALETTE_MAX_SUPPORTED
+#define PNG_READ_GRAY_TO_RGB_SUPPORTED
+#define PNG_READ_INTERLACING_SUPPORTED
+#define PNG_READ_INT_FUNCTIONS_SUPPORTED
+#define PNG_READ_INVERT_ALPHA_SUPPORTED
+#define PNG_READ_INVERT_SUPPORTED
+#define PNG_READ_OPT_PLTE_SUPPORTED
+#define PNG_READ_PACKSWAP_SUPPORTED
+#define PNG_READ_PACK_SUPPORTED
+#define PNG_READ_QUANTIZE_SUPPORTED
+#define PNG_READ_RGB_TO_GRAY_SUPPORTED
+#define PNG_READ_SCALE_16_TO_8_SUPPORTED
+#define PNG_READ_SHIFT_SUPPORTED
+#define PNG_READ_STRIP_16_TO_8_SUPPORTED
+#define PNG_READ_STRIP_ALPHA_SUPPORTED
+#define PNG_READ_SUPPORTED
+#define PNG_READ_SWAP_ALPHA_SUPPORTED
+#define PNG_READ_SWAP_SUPPORTED
+#define PNG_READ_TEXT_SUPPORTED
+#define PNG_READ_TRANSFORMS_SUPPORTED
+#define PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+#define PNG_READ_USER_CHUNKS_SUPPORTED
+#define PNG_READ_USER_TRANSFORM_SUPPORTED
+#define PNG_READ_bKGD_SUPPORTED
+#define PNG_READ_cHRM_SUPPORTED
+#define PNG_READ_gAMA_SUPPORTED
+#define PNG_READ_hIST_SUPPORTED
+#define PNG_READ_iCCP_SUPPORTED
+#define PNG_READ_iTXt_SUPPORTED
+#define PNG_READ_oFFs_SUPPORTED
+#define PNG_READ_pCAL_SUPPORTED
+#define PNG_READ_pHYs_SUPPORTED
+#define PNG_READ_sBIT_SUPPORTED
+#define PNG_READ_sCAL_SUPPORTED
+#define PNG_READ_sPLT_SUPPORTED
+#define PNG_READ_sRGB_SUPPORTED
+#define PNG_READ_tEXt_SUPPORTED
+#define PNG_READ_tIME_SUPPORTED
+#define PNG_READ_tRNS_SUPPORTED
+#define PNG_READ_zTXt_SUPPORTED
+/*#undef PNG_SAFE_LIMITS_SUPPORTED*/
+#define PNG_SAVE_INT_32_SUPPORTED
+#define PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED
+#define PNG_SEQUENTIAL_READ_SUPPORTED
+#define PNG_SETJMP_SUPPORTED
+#define PNG_SET_CHUNK_CACHE_LIMIT_SUPPORTED
+#define PNG_SET_CHUNK_MALLOC_LIMIT_SUPPORTED
+/*#undef PNG_SET_OPTION_SUPPORTED*/
+#define PNG_SET_UNKNOWN_CHUNKS_SUPPORTED
+#define PNG_SET_USER_LIMITS_SUPPORTED
+#define PNG_SIMPLIFIED_READ_AFIRST_SUPPORTED
+#define PNG_SIMPLIFIED_READ_BGR_SUPPORTED
+#define PNG_SIMPLIFIED_READ_SUPPORTED
+#define PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED
+#define PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED
+#define PNG_SIMPLIFIED_WRITE_SUPPORTED
+#define PNG_STDIO_SUPPORTED
+#define PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED
+#define PNG_TEXT_SUPPORTED
+#define PNG_TIME_RFC1123_SUPPORTED
+#define PNG_UNKNOWN_CHUNKS_SUPPORTED
+#define PNG_USER_CHUNKS_SUPPORTED
+#define PNG_USER_LIMITS_SUPPORTED
+#define PNG_USER_MEM_SUPPORTED
+#define PNG_USER_TRANSFORM_INFO_SUPPORTED
+#define PNG_USER_TRANSFORM_PTR_SUPPORTED
+#define PNG_WARNINGS_SUPPORTED
+#define PNG_WRITE_16BIT_SUPPORTED
+#define PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED
+#define PNG_WRITE_BGR_SUPPORTED
+#define PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED
+#define PNG_WRITE_COMPRESSED_TEXT_SUPPORTED
+#define PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED
+#define PNG_WRITE_FILLER_SUPPORTED
+#define PNG_WRITE_FILTER_SUPPORTED
+#define PNG_WRITE_FLUSH_SUPPORTED
+#define PNG_WRITE_GET_PALETTE_MAX_SUPPORTED
+#define PNG_WRITE_INTERLACING_SUPPORTED
+#define PNG_WRITE_INT_FUNCTIONS_SUPPORTED
+#define PNG_WRITE_INVERT_ALPHA_SUPPORTED
+#define PNG_WRITE_INVERT_SUPPORTED
+#define PNG_WRITE_OPTIMIZE_CMF_SUPPORTED
+#define PNG_WRITE_PACKSWAP_SUPPORTED
+#define PNG_WRITE_PACK_SUPPORTED
+#define PNG_WRITE_SHIFT_SUPPORTED
+#define PNG_WRITE_SUPPORTED
+#define PNG_WRITE_SWAP_ALPHA_SUPPORTED
+#define PNG_WRITE_SWAP_SUPPORTED
+#define PNG_WRITE_TEXT_SUPPORTED
+#define PNG_WRITE_TRANSFORMS_SUPPORTED
+#define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED
+#define PNG_WRITE_USER_TRANSFORM_SUPPORTED
+#define PNG_WRITE_WEIGHTED_FILTER_SUPPORTED
+#define PNG_WRITE_bKGD_SUPPORTED
+#define PNG_WRITE_cHRM_SUPPORTED
+#define PNG_WRITE_gAMA_SUPPORTED
+#define PNG_WRITE_hIST_SUPPORTED
+#define PNG_WRITE_iCCP_SUPPORTED
+#define PNG_WRITE_iTXt_SUPPORTED
+#define PNG_WRITE_oFFs_SUPPORTED
+#define PNG_WRITE_pCAL_SUPPORTED
+#define PNG_WRITE_pHYs_SUPPORTED
+#define PNG_WRITE_sBIT_SUPPORTED
+#define PNG_WRITE_sCAL_SUPPORTED
+#define PNG_WRITE_sPLT_SUPPORTED
+#define PNG_WRITE_sRGB_SUPPORTED
+#define PNG_WRITE_tEXt_SUPPORTED
+#define PNG_WRITE_tIME_SUPPORTED
+#define PNG_WRITE_tRNS_SUPPORTED
+#define PNG_WRITE_zTXt_SUPPORTED
+#define PNG_bKGD_SUPPORTED
+#define PNG_cHRM_SUPPORTED
+#define PNG_gAMA_SUPPORTED
+#define PNG_hIST_SUPPORTED
+#define PNG_iCCP_SUPPORTED
+#define PNG_iTXt_SUPPORTED
+#define PNG_oFFs_SUPPORTED
+#define PNG_pCAL_SUPPORTED
+#define PNG_pHYs_SUPPORTED
+#define PNG_sBIT_SUPPORTED
+#define PNG_sCAL_SUPPORTED
+#define PNG_sPLT_SUPPORTED
+#define PNG_sRGB_SUPPORTED
+#define PNG_tEXt_SUPPORTED
+#define PNG_tIME_SUPPORTED
+#define PNG_tRNS_SUPPORTED
+#define PNG_zTXt_SUPPORTED
+/* end of options */
+/* settings */
+#define PNG_API_RULE 0
+#define PNG_CALLOC_SUPPORTED
+#define PNG_COST_SHIFT 3
+#define PNG_DEFAULT_READ_MACROS 1
+#define PNG_GAMMA_THRESHOLD_FIXED 5000
+#define PNG_IDAT_READ_SIZE PNG_ZBUF_SIZE
+#define PNG_INFLATE_BUF_SIZE 1024
+#define PNG_MAX_GAMMA_8 11
+#define PNG_QUANTIZE_BLUE_BITS 5
+#define PNG_QUANTIZE_GREEN_BITS 5
+#define PNG_QUANTIZE_RED_BITS 5
+#define PNG_TEXT_Z_DEFAULT_COMPRESSION (-1)
+#define PNG_TEXT_Z_DEFAULT_STRATEGY 0
+#define PNG_WEIGHT_SHIFT 8
+#define PNG_ZBUF_SIZE 8192
+#define PNG_Z_DEFAULT_COMPRESSION (-1)
+#define PNG_Z_DEFAULT_NOFILTER_STRATEGY 0
+#define PNG_Z_DEFAULT_STRATEGY 1
+#define PNG_sCAL_PRECISION 5
+#define PNG_sRGB_PROFILE_CHECKS 2
+/* end of settings */
+#endif /* PNGLCONF_H */
diff --git a/fb2png/libpng/lib/libpng.a b/fb2png/libpng/lib/libpng.a
new file mode 100644
index 0000000..5b6288c
--- /dev/null
+++ b/fb2png/libpng/lib/libpng.a
Binary files differ
diff --git a/fb2png/log.h b/fb2png/log.h
new file mode 100644
index 0000000..a2e3f5f
--- /dev/null
+++ b/fb2png/log.h
@@ -0,0 +1,86 @@
+/**
+ * fb2png  Save screenshot into .png.
+ *
+ * Copyright (C) 2012  Kyan <kyan.ql.he@gmail.com>
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef __KYAN_LOG_H__
+#define __KYAN_LOG_H__
+
+#include <errno.h>
+#include <string.h>
+
+#ifdef ANDROID_XXX
+
+#ifndef LOG_TAG
+#define LOG_TAG "tag"
+#endif
+
+#include <android/log.h>
+
+#define D LOGD
+#define E LOGE
+
+#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , LOG_TAG, __VA_ARGS__)
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO   , LOG_TAG, __VA_ARGS__)
+#define LOGW(...) __android_log_print(ANDROID_LOG_WARN   , LOG_TAG, __VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__)
+
+#else /* ANDROID */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#if DEBUG == 1
+
+#define LOG_FUNCTION_NAME \
+    fprintf(stderr, "\033[0;1;31m__func__: %s\033[0;0m\n", __FUNCTION__);
+
+#else
+
+#define LOG_FUNCTION_NAME
+
+#endif
+
+__attribute__((unused)) static void
+D(const char *msg, ...)
+{
+    va_list ap;
+
+    va_start (ap, msg);
+    vfprintf(stdout, msg, ap);
+    fprintf(stdout, "\n");
+    va_end (ap);
+    fflush(stdout);
+}
+
+__attribute__((unused)) static void
+E(const char *msg, ...)
+{
+    va_list ap;
+
+    va_start (ap, msg);
+    vfprintf(stderr, msg, ap);
+    fprintf(stderr, ", %s", strerror(errno));
+    fprintf(stderr, "\n");
+    va_end (ap);
+}
+
+#endif /* ANDROID */
+
+#endif
diff --git a/fb2png/main.c b/fb2png/main.c
new file mode 100644
index 0000000..235e958
--- /dev/null
+++ b/fb2png/main.c
@@ -0,0 +1,64 @@
+/**
+ * fb2png  Save screenshot into .png.
+ *
+ * Copyright (C) 2012  Kyan <kyan.ql.he@gmail.com>
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "fb2png.h"
+
+#ifdef ANDROID
+    #define DEFAULT_SAVE_PATH "/data/local/fbdump.png"
+#else
+    #define DEFAULT_SAVE_PATH "fbdump.png"
+#endif
+
+int main(int argc, char *argv[])
+{
+    char fn[PATH_MAX];
+    int ret;
+
+    if (argc == 2 && argv[1][0] != '-') {
+        if (strlen(argv[1]) >= PATH_MAX) {
+            printf("Output path is too long!\n");
+            exit(-1);
+        }
+        sprintf(fn, "%s", argv[1]);
+    } else if (argc == 1) {
+        sprintf(fn, "%s", DEFAULT_SAVE_PATH);
+    } else {
+        //if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
+        printf(
+            "Android Screenshooter - fb2png\n"
+            "Author: Kyan He <kyan.ql.he@gmail.com>\n"
+            "Modified by  Phil3759 & McKael @xda\n"
+            "Base version 0.0.2 ---> v0.0.5  <2013>\n"
+            "Usage: fb2png [path/to/output.png]\n"
+            "    The default output path is /data/local/fbdump.png\n"
+            );
+        exit(0);
+    }
+
+    if (0 == (ret = fb2png(fn)))
+        printf("Saved image to %s\n", fn);
+
+    exit(ret);
+}
diff --git a/fb2png/run.sh b/fb2png/run.sh
new file mode 100644
index 0000000..e7e0354
--- /dev/null
+++ b/fb2png/run.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# helper script for capture picture on device
+#
+# Kyan He <kyan.ql.he@gmail.com> @ Tue Feb 15 12:42:48 CST 2011
+#
+#
+
+ADB_OPTIONS=
+PNG="/data/local/fbdump.png"
+
+if [ ! "$FB2PNG" = "" ];
+then
+
+adb $ADB_OPTIONS push $FB2PNG /data/local
+adb $ADB_OPTIONS shell chmod 777 /data/local
+adb $ADB_OPTIONS shell /data/local/fb2png
+
+adb $ADB_OPTIONS pull $PNG
+adb $ADB_OPTIONS shell rm $PNG
+else
+    echo "define \$FB2PNG first"
+fi
diff --git a/fb2png/view888 b/fb2png/view888
new file mode 100644
index 0000000..6e10dcd
--- /dev/null
+++ b/fb2png/view888
@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+# view argb8888
+#
+# Modified: 
+# Kyan He <kyan.ql.he@gmail.com> @ Tue Feb 15 01:45:54 CST 2011
+#
+# Initial version
+# Kyan He <kyan.ql.he@gmail.com> @ Mon Sep 20 11:45:54 CST 2010
+#
+
+if ! which ffmpeg >/dev/null;
+then
+    echo "no ffmpeg found"
+elif [[ ! $# -eq 2 ]];
+then
+    echo "Usage: `basename $0` <data.argb8888> <width>x<height>"
+else
+    ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt rgb24 -s $2 -i $1 -f image2 -vcodec png $1.png
+fi
+
diff --git a/find_file.cpp b/find_file.cpp
new file mode 100644
index 0000000..7f4191e
--- /dev/null
+++ b/find_file.cpp
@@ -0,0 +1,91 @@
+/*
+		Copyright 2014 TeamWin
+		This file is part of TWRP/TeamWin Recovery Project.
+
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string>
+#include <vector>
+#include <dirent.h>
+#include <stdlib.h>
+#include "find_file.hpp"
+#include "twrp-functions.hpp"
+#include "twcommon.h"
+
+using namespace std;
+
+string Find_File::Find(const string& file_name, const string& start_path) {
+	return Find_File().Find_Internal(file_name, start_path);
+}
+
+Find_File::Find_File() {
+}
+
+string Find_File::Find_Internal(const string& filename, const string& starting_path) {
+	DIR *d;
+	string new_path, return_path;
+	vector<string> dirs;
+	vector<string> symlinks;
+	unsigned index;
+
+	// Check to see if we have already searched this directory to prevent infinite loops
+	if (std::find(searched_dirs.begin(), searched_dirs.end(), starting_path) != searched_dirs.end()) {
+		return "";
+	}
+	searched_dirs.push_back(starting_path);
+
+	d = opendir(starting_path.c_str());
+	if (d == NULL) {
+		LOGINFO("Find_File: Error opening '%s'\n", starting_path.c_str());
+		return "";
+	}
+
+	struct dirent *p;
+	while ((p = readdir(d))) {
+		if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, ".."))
+			continue;
+		new_path = starting_path + "/";
+		new_path.append(p->d_name);
+		if (p->d_type == DT_DIR) {
+			// Add dir to search list for later
+			dirs.push_back(new_path);
+		} else if (p->d_type == DT_LNK) {
+			// Add symlink to search list for later
+			symlinks.push_back(new_path);
+		} else if (p->d_type == DT_REG && filename == p->d_name) {
+			// We found a match!
+			closedir(d);
+			return new_path;
+		}
+	}
+	closedir(d);
+
+	// Scan real directories first if no match found in this path
+	for (index = 0; index < dirs.size(); index++) {
+		return_path = Find_Internal(filename, dirs.at(index));
+		if (!return_path.empty()) return return_path;
+	}
+	// Scan symlinks after scanning real directories
+	for (index = 0; index < symlinks.size(); index++) {
+		char buf[PATH_MAX];
+		// Resolve symlink to a real path
+		char* ret = realpath(symlinks.at(index).c_str(), buf);
+		if (ret) {
+			return_path = Find_Internal(filename, buf);
+			if (!return_path.empty()) return return_path;
+		}
+	}
+	return "";
+}
diff --git a/find_file.hpp b/find_file.hpp
new file mode 100644
index 0000000..8833e9a
--- /dev/null
+++ b/find_file.hpp
@@ -0,0 +1,37 @@
+/*
+        Copyright 2014 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef Find_File_HPP
+#define Find_File_HPP
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+class Find_File {
+
+public:
+	static string Find(const string& file_name, const string& start_path);
+private:
+	Find_File();
+	string Find_Internal(const string& filename, const string& starting_path);
+	vector<string> searched_dirs;
+};
+
+#endif
diff --git a/fixContexts.cpp b/fixContexts.cpp
new file mode 100644
index 0000000..7e650c2
--- /dev/null
+++ b/fixContexts.cpp
@@ -0,0 +1,145 @@
+/*
+	Copyright 2012-2016 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <cctype>
+#include "fixContexts.hpp"
+#include "twrp-functions.hpp"
+#include "twcommon.h"
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#include <selinux/android.h>
+#include <selinux/label.h>
+
+using namespace std;
+
+struct selabel_handle *sehandle;
+struct selinux_opt selinux_options[] = {
+	{ SELABEL_OPT_PATH, "/file_contexts" }
+};
+
+int fixContexts::restorecon(string entry, struct stat *sb) {
+	char *oldcontext, *newcontext;
+
+	if (lgetfilecon(entry.c_str(), &oldcontext) < 0) {
+		LOGINFO("Couldn't get selinux context for %s\n", entry.c_str());
+		return -1;
+	}
+	if (selabel_lookup(sehandle, &newcontext, entry.c_str(), sb->st_mode) < 0) {
+		LOGINFO("Couldn't lookup selinux context for %s\n", entry.c_str());
+		return -1;
+	}
+	if (strcmp(oldcontext, newcontext) != 0) {
+		LOGINFO("Relabeling %s from %s to %s\n", entry.c_str(), oldcontext, newcontext);
+		if (lsetfilecon(entry.c_str(), newcontext) < 0) {
+			LOGINFO("Couldn't label %s with %s: %s\n", entry.c_str(), newcontext, strerror(errno));
+		}
+	}
+	freecon(oldcontext);
+	freecon(newcontext);
+	return 0;
+}
+
+int fixContexts::fixContextsRecursively(string name, int level) {
+	DIR *d;
+	struct dirent *de;
+	struct stat sb;
+	string path;
+
+	if (!(d = opendir(name.c_str())))
+		return -1;
+	if (!(de = readdir(d)))
+		return -1;
+
+	do {
+		if (de->d_type ==  DT_DIR) {
+			if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
+				continue;
+			path = name + "/" + de->d_name;
+			restorecon(path, &sb);
+			fixContextsRecursively(path, level + 1);
+		}
+		else {
+			path = name + "/" + de->d_name;
+			restorecon(path, &sb);
+		}
+	} while ((de = readdir(d)));
+	closedir(d);
+	return 0;
+}
+
+int fixContexts::fixDataMediaContexts(string Mount_Point) {
+	DIR *d;
+	struct dirent *de;
+	struct stat sb;
+
+	LOGINFO("Fixing media contexts on '%s'\n", Mount_Point.c_str());
+
+	sehandle = selabel_open(SELABEL_CTX_FILE, selinux_options, 1);
+	if (!sehandle) {
+		LOGINFO("Unable to open /file_contexts\n");
+		return 0;
+	}
+
+	if (TWFunc::Path_Exists(Mount_Point + "/media/0")) {
+		string dir = Mount_Point + "/media";
+		if (!(d = opendir(dir.c_str()))) {
+			LOGINFO("opendir failed (%s)\n", strerror(errno));
+			return -1;
+		}
+		if (!(de = readdir(d))) {
+			LOGINFO("readdir failed (%s)\n", strerror(errno));
+			closedir(d);
+			return -1;
+		}
+
+		do {
+			if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0 || de->d_type != DT_DIR)
+				continue;
+			size_t len = strlen(de->d_name);
+			bool is_numeric = true;
+			char* folder_name = de->d_name;
+			for (size_t i = 0; i < len; i++) {
+				if (!isdigit(*folder_name)) {
+					is_numeric = false;
+					break;
+				}
+				folder_name++;
+			}
+			if (is_numeric) {
+				dir = Mount_Point + "/media/";
+				dir += de->d_name;
+				restorecon(dir, &sb);
+				fixContextsRecursively(dir, 0);
+			}
+		} while ((de = readdir(d)));
+		closedir(d);
+	} else if (TWFunc::Path_Exists(Mount_Point + "/media")) {
+		restorecon(Mount_Point + "/media", &sb);
+		fixContextsRecursively(Mount_Point + "/media", 0);
+	} else {
+		LOGINFO("fixDataMediaContexts: %s/media does not exist!\n", Mount_Point.c_str());
+		return 0;
+	}
+	selabel_close(sehandle);
+	return 0;
+}
diff --git a/fixContexts.hpp b/fixContexts.hpp
new file mode 100644
index 0000000..e5e05de
--- /dev/null
+++ b/fixContexts.hpp
@@ -0,0 +1,35 @@
+/*
+	Copyright 2012-2016 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __FIXCONTEXTS_HPP
+#define __FIXCONTEXTS_HPP
+
+#include <string>
+
+using namespace std;
+
+class fixContexts {
+	public:
+		static int fixDataMediaContexts(string Mount_Point);
+
+	private:
+		static int restorecon(string entry, struct stat *sb);
+		static int fixContextsRecursively(string path, int level);
+};
+
+#endif
diff --git a/flashutils/Android.mk b/flashutils/Android.mk
new file mode 100755
index 0000000..0a04509
--- /dev/null
+++ b/flashutils/Android.mk
@@ -0,0 +1,125 @@
+LOCAL_PATH := $(call my-dir)
+
+ifneq ($(TARGET_SIMULATOR),true)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := flashutils.c
+LOCAL_MODULE := libflashutils
+LOCAL_MODULE_TAGS := optional
+LOCAL_C_INCLUDES += $(commands_recovery_local_path)
+LOCAL_STATIC_LIBRARIES := libmmcutils libmtdutils libbmlutils libcrecovery
+
+BOARD_RECOVERY_DEFINES := BOARD_BML_BOOT BOARD_BML_RECOVERY
+
+$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \
+  $(if $($(board_define)), \
+    $(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \
+  ) \
+  )
+
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := flash_image.c
+LOCAL_MODULE := libflash_image
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS += -Dmain=flash_image_main
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := dump_image.c
+LOCAL_MODULE := libdump_image
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS += -Dmain=dump_image_main
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := erase_image.c
+LOCAL_MODULE := liberase_image
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS += -Dmain=erase_image_main
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := dump_image.c
+LOCAL_MODULE := utility_dump_image
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities
+LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities
+LOCAL_MODULE_STEM := dump_image
+LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcutils libc
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := flash_image.c
+LOCAL_MODULE := utility_flash_image
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities
+LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities
+LOCAL_MODULE_STEM := flash_image
+LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcutils libc
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := erase_image.c
+LOCAL_MODULE := utility_erase_image
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities
+LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities
+LOCAL_MODULE_STEM := erase_image
+LOCAL_STATIC_LIBRARIES := libflashutils libmtdutils libmmcutils libbmlutils libcutils libc
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(BUILD_EXECUTABLE)
+
+#Added for dynamic building for TWRP:
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := flashutils.c
+LOCAL_MODULE := libflashutils
+LOCAL_MODULE_TAGS := optional
+LOCAL_C_INCLUDES += $(commands_recovery_local_path)
+LOCAL_SHARED_LIBRARIES := libc libmtdutils libmmcutils libbmlutils libcrecovery
+
+BOARD_RECOVERY_DEFINES := BOARD_BML_BOOT BOARD_BML_RECOVERY
+
+$(foreach board_define,$(BOARD_RECOVERY_DEFINES), \
+  $(if $($(board_define)), \
+    $(eval LOCAL_CFLAGS += -D$(board_define)=\"$($(board_define))\") \
+  ) \
+  )
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := flash_image
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_SRC_FILES := flash_image.c
+LOCAL_SHARED_LIBRARIES := libmtdutils libflashutils libmmcutils libbmlutils libcutils libc
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := dump_image
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_SRC_FILES := dump_image.c
+LOCAL_SHARED_LIBRARIES := libmtdutils libflashutils libmmcutils libbmlutils libcutils libc
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := erase_image
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_SRC_FILES := erase_image.c
+LOCAL_SHARED_LIBRARIES := libmtdutils libflashutils libmmcutils libbmlutils libcutils libc
+include $(BUILD_EXECUTABLE)
+
+endif	# !TARGET_SIMULATOR
diff --git a/flashutils/dump_image.c b/flashutils/dump_image.c
new file mode 100644
index 0000000..64c4e1c
--- /dev/null
+++ b/flashutils/dump_image.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include "cutils/log.h"
+#include "flashutils.h"
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+
+#if 0
+
+#define LOG_TAG "dump_image"
+
+#define BLOCK_SIZE    2048
+#define SPARE_SIZE    (BLOCK_SIZE >> 5)
+
+static int die(const char *msg, ...) {
+    int err = errno;
+    va_list args;
+    va_start(args, msg);
+    char buf[1024];
+    vsnprintf(buf, sizeof(buf), msg, args);
+    va_end(args);
+
+    if (err != 0) {
+        strlcat(buf, ": ", sizeof(buf));
+        strlcat(buf, strerror(err), sizeof(buf));
+    }
+
+    fprintf(stderr, "%s\n", buf);
+    return 1;
+}
+
+/* Read a flash partition and write it to an image file. */
+
+int dump_image(char* partition_name, char* filename, dump_image_callback callback) {
+    MtdReadContext *in;
+    const MtdPartition *partition;
+    char buf[BLOCK_SIZE + SPARE_SIZE];
+    size_t partition_size;
+    size_t read_size;
+    size_t total;
+    int fd;
+    int wrote;
+    int len;
+    
+    if (mtd_scan_partitions() <= 0)
+        return die("error scanning partitions");
+
+    partition = mtd_find_partition_by_name(partition_name);
+    if (partition == NULL)
+        return die("can't find %s partition", partition_name);
+
+    if (mtd_partition_info(partition, &partition_size, NULL, NULL)) {
+        return die("can't get info of partition %s", partition_name);
+    }
+
+    if (!strcmp(filename, "-")) {
+        fd = fileno(stdout);
+    } 
+    else {
+        fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+    }
+
+    if (fd < 0)
+        return die("error opening %s", filename);
+
+    in = mtd_read_partition(partition);
+    if (in == NULL) {
+        close(fd);
+        unlink(filename);
+        return die("error opening %s: %s\n", partition_name, strerror(errno));
+    }
+
+    total = 0;
+    while ((len = mtd_read_data(in, buf, BLOCK_SIZE)) > 0) {
+        wrote = write(fd, buf, len);
+        if (wrote != len) {
+            close(fd);
+            unlink(filename);
+            return die("error writing %s", filename);
+        }
+        total += BLOCK_SIZE;
+        if (callback != NULL)
+            callback(total, partition_size);
+    }
+
+    mtd_read_close(in);
+
+    if (close(fd)) {
+        unlink(filename);
+        return die("error closing %s", filename);
+    }
+    return 0;
+}
+
+int main(int argc, char **argv)
+{
+    ssize_t (*read_func) (MtdReadContext *, char *, size_t);
+    MtdReadContext *in;
+    const MtdPartition *partition;
+    char buf[BLOCK_SIZE + SPARE_SIZE];
+    size_t partition_size;
+    size_t read_size;
+    size_t total;
+    int fd;
+    int wrote;
+    int len;
+
+    if (argc != 3) {
+        fprintf(stderr, "usage: %s partition file.img\n", argv[0]);
+        return 2;
+    }
+
+    return dump_image(argv[1], argv[2], NULL);
+}
+
+#endif
+
+int main(int argc, char **argv)
+{
+    if (argc != 3) {
+        fprintf(stderr, "usage: %s partition file.img\n", argv[0]);
+        return 2;
+    }
+
+    return backup_raw_partition(NULL, argv[1], argv[2]);
+}
diff --git a/flashutils/erase_image.c b/flashutils/erase_image.c
new file mode 100644
index 0000000..b09a424
--- /dev/null
+++ b/flashutils/erase_image.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Portions Copyright (C) 2010 Magnus Eriksson <packetlss@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+
+#include "cutils/log.h"
+#include "flashutils.h"
+
+#if 0
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+
+
+#define LOG_TAG "erase_image"
+
+static int die(const char *msg, ...) {
+    int err = errno;
+    va_list args;
+    va_start(args, msg);
+    char buf[1024];
+    vsnprintf(buf, sizeof(buf), msg, args);
+    va_end(args);
+
+    if (err != 0) {
+        strlcat(buf, ": ", sizeof(buf));
+        strlcat(buf, strerror(err), sizeof(buf));
+    }
+
+    fprintf(stderr, "%s\n", buf);
+    LOGE("%s\n", buf);
+    return 3;
+}
+
+
+int erase_image(char* partition_name) {
+    MtdWriteContext *out;
+    size_t erased;
+    size_t total_size;
+    size_t erase_size;
+
+    if (mtd_scan_partitions() <= 0) die("error scanning partitions");
+    const MtdPartition *partition = mtd_find_partition_by_name(partition_name);
+    if (partition == NULL) return die("can't find %s partition", partition_name);
+
+    out = mtd_write_partition(partition);
+    if (out == NULL) return die("could not estabilish write context for %s", partition_name);
+
+    // do the actual erase, -1 = full partition erase
+    erased = mtd_erase_blocks(out, -1);
+
+    // erased = bytes erased, if zero, something borked
+    if (!erased) return die("error erasing %s", partition_name);
+
+    return 0;
+}
+
+
+/* Erase a mtd partition */
+
+int main(int argc, char **argv) {
+    if (argc != 2) {
+        fprintf(stderr, "usage: %s <partition>\n", argv[0]);
+        return 2;
+    }
+    
+    return erase_image(argv[1]);
+}
+
+#endif
+
+
+int main(int argc, char **argv)
+{
+    if (argc != 2) {
+        fprintf(stderr, "usage: %s partition\n", argv[0]);
+        return 2;
+    }
+
+    return erase_raw_partition(NULL, argv[1]);
+}
diff --git a/flashutils/flash_image.c b/flashutils/flash_image.c
new file mode 100644
index 0000000..c9f3683
--- /dev/null
+++ b/flashutils/flash_image.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "cutils/log.h"
+#include "flashutils.h"
+
+#if 0
+#define LOG_TAG "flash_image"
+
+#define HEADER_SIZE 2048  // size of header to compare for equality
+
+void die(const char *msg, ...) {
+    int err = errno;
+    va_list args;
+    va_start(args, msg);
+    char buf[1024];
+    vsnprintf(buf, sizeof(buf), msg, args);
+    va_end(args);
+
+    if (err != 0) {
+        strlcat(buf, ": ", sizeof(buf));
+        strlcat(buf, strerror(err), sizeof(buf));
+    }
+
+    fprintf(stderr, "%s\n", buf);
+    LOGE("%s\n", buf);
+    exit(1);
+}
+
+/* Read an image file and write it to a flash partition. */
+
+int main(int argc, char **argv) {
+    const MtdPartition *ptn;
+    MtdWriteContext *write;
+    void *data;
+    unsigned sz;
+
+    if (argc != 3) {
+        fprintf(stderr, "usage: %s partition file.img\n", argv[0]);
+        return 2;
+    }
+
+    if (mtd_scan_partitions() <= 0) die("error scanning partitions");
+    const MtdPartition *partition = mtd_find_partition_by_name(argv[1]);
+    if (partition == NULL) die("can't find %s partition", argv[1]);
+
+    // If the first part of the file matches the partition, skip writing
+
+    int fd = open(argv[2], O_RDONLY);
+    if (fd < 0) die("error opening %s", argv[2]);
+
+    char header[HEADER_SIZE];
+    int headerlen = read(fd, header, sizeof(header));
+    if (headerlen <= 0) die("error reading %s header", argv[2]);
+
+    MtdReadContext *in = mtd_read_partition(partition);
+    if (in == NULL) {
+        LOGW("error opening %s: %s\n", argv[1], strerror(errno));
+        // just assume it needs re-writing
+    } else {
+        char check[HEADER_SIZE];
+        int checklen = mtd_read_data(in, check, sizeof(check));
+        if (checklen <= 0) {
+            LOGW("error reading %s: %s\n", argv[1], strerror(errno));
+            // just assume it needs re-writing
+        } else if (checklen == headerlen && !memcmp(header, check, headerlen)) {
+            LOGI("header is the same, not flashing %s\n", argv[1]);
+            return 0;
+        }
+        mtd_read_close(in);
+    }
+
+    // Skip the header (we'll come back to it), write everything else
+    LOGI("flashing %s from %s\n", argv[1], argv[2]);
+
+    MtdWriteContext *out = mtd_write_partition(partition);
+    if (out == NULL) die("error writing %s", argv[1]);
+
+    char buf[HEADER_SIZE];
+    memset(buf, 0, headerlen);
+    int wrote = mtd_write_data(out, buf, headerlen);
+    if (wrote != headerlen) die("error writing %s", argv[1]);
+
+    int len;
+    while ((len = read(fd, buf, sizeof(buf))) > 0) {
+        wrote = mtd_write_data(out, buf, len);
+        if (wrote != len) die("error writing %s", argv[1]);
+    }
+    if (len < 0) die("error reading %s", argv[2]);
+
+    if (mtd_write_close(out)) die("error closing %s", argv[1]);
+
+    // Now come back and write the header last
+
+    out = mtd_write_partition(partition);
+    if (out == NULL) die("error re-opening %s", argv[1]);
+
+    wrote = mtd_write_data(out, header, headerlen);
+    if (wrote != headerlen) die("error re-writing %s", argv[1]);
+
+    // Need to write a complete block, so write the rest of the first block
+    size_t block_size;
+    if (mtd_partition_info(partition, NULL, &block_size, NULL))
+        die("error getting %s block size", argv[1]);
+
+    if (lseek(fd, headerlen, SEEK_SET) != headerlen)
+        die("error rewinding %s", argv[2]);
+
+    int left = block_size - headerlen;
+    while (left < 0) left += block_size;
+    while (left > 0) {
+        len = read(fd, buf, left > (int)sizeof(buf) ? (int)sizeof(buf) : left);
+        if (len <= 0) die("error reading %s", argv[2]);
+        if (mtd_write_data(out, buf, len) != len)
+            die("error writing %s", argv[1]);
+        left -= len;
+    }
+
+    if (mtd_write_close(out)) die("error closing %s", argv[1]);
+    return 0;
+}
+#endif
+
+int main(int argc, char **argv)
+{
+    if (argc != 3) {
+        fprintf(stderr, "usage: %s partition file.img\n", argv[0]);
+        return 2;
+    }
+
+    int ret = restore_raw_partition(NULL, argv[1], argv[2]);
+    if (ret != 0)
+        fprintf(stderr, "failed with error: %d\n", ret);
+    return ret;
+}
diff --git a/flashutils/flashutils.c b/flashutils/flashutils.c
new file mode 100644
index 0000000..6401939
--- /dev/null
+++ b/flashutils/flashutils.c
@@ -0,0 +1,159 @@
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "flashutils/flashutils.h"
+
+#ifndef BOARD_BML_BOOT
+#define BOARD_BML_BOOT              "/dev/block/bml7"
+#endif
+
+#ifndef BOARD_BML_RECOVERY
+#define BOARD_BML_RECOVERY          "/dev/block/bml8"
+#endif
+
+int the_flash_type = UNKNOWN;
+
+int device_flash_type()
+{
+    if (the_flash_type == UNKNOWN) {
+        if (access(BOARD_BML_BOOT, F_OK) == 0) {
+            the_flash_type = BML;
+        } else if (access("/proc/mtd", F_OK) == 0) {
+            the_flash_type = MTD;
+        } else if (access("/proc/emmc", F_OK) == 0 ||
+                   access("/dev/block/mmcblk0", F_OK) == 0 ||
+                   access("/dev/block/sda", F_OK) == 0) {
+            the_flash_type = MMC;
+        } else {
+            the_flash_type = UNSUPPORTED;
+        }
+    }
+    return the_flash_type;
+}
+
+char* get_default_filesystem()
+{
+    return device_flash_type() == MMC ? "ext3" : "yaffs2";
+}
+
+int get_flash_type(const char* partitionType) {
+    int type = UNSUPPORTED;
+    if (strcmp(partitionType, "mtd") == 0)
+        type = MTD;
+    else if (strcmp(partitionType, "emmc") == 0)
+        type = MMC;
+    else if (strcmp(partitionType, "bml") == 0)
+        type = BML;
+    return type;
+}
+
+static int detect_partition(const char *partitionType, const char *partition)
+{
+    int type = device_flash_type();
+    if (strstr(partition, "/dev/block/mtd") != NULL)
+        type = MTD;
+    else if (strstr(partition, "/dev/block/mmc") != NULL || strstr(partition, "/dev/block/sd") != NULL)
+        type = MMC;
+    else if (strstr(partition, "/dev/block/bml") != NULL)
+        type = BML;
+
+    if (partitionType != NULL) {
+        type = get_flash_type(partitionType);
+    }
+
+    return type;
+}
+int restore_raw_partition(const char* partitionType, const char *partition, const char *filename)
+{
+    int type = detect_partition(partitionType, partition);
+    switch (type) {
+        case MTD:
+            return cmd_mtd_restore_raw_partition(partition, filename);
+        case MMC:
+            return cmd_mmc_restore_raw_partition(partition, filename);
+        case BML:
+            return cmd_bml_restore_raw_partition(partition, filename);
+        default:
+            return -1;
+    }
+}
+
+int backup_raw_partition(const char* partitionType, const char *partition, const char *filename)
+{
+    int type = detect_partition(partitionType, partition);
+    switch (type) {
+        case MTD:
+            return cmd_mtd_backup_raw_partition(partition, filename);
+        case MMC:
+            return cmd_mmc_backup_raw_partition(partition, filename);
+        case BML:
+            return cmd_bml_backup_raw_partition(partition, filename);
+        default:
+            printf("unable to detect device type");
+            return -1;
+    }
+}
+
+int erase_raw_partition(const char* partitionType, const char *partition)
+{
+    int type = detect_partition(partitionType, partition);
+    switch (type) {
+        case MTD:
+            return cmd_mtd_erase_raw_partition(partition);
+        case MMC:
+            return cmd_mmc_erase_raw_partition(partition);
+        case BML:
+            return cmd_bml_erase_raw_partition(partition);
+        default:
+            return -1;
+    }
+}
+
+int erase_partition(const char *partition, const char *filesystem)
+{
+    int type = detect_partition(NULL, partition);
+    switch (type) {
+        case MTD:
+            return cmd_mtd_erase_partition(partition, filesystem);
+        case MMC:
+            return cmd_mmc_erase_partition(partition, filesystem);
+        case BML:
+            return cmd_bml_erase_partition(partition, filesystem);
+        default:
+            return -1;
+    }
+}
+
+int mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only)
+{
+    int type = detect_partition(NULL, partition);
+    switch (type) {
+        case MTD:
+            return cmd_mtd_mount_partition(partition, mount_point, filesystem, read_only);
+        case MMC:
+            return cmd_mmc_mount_partition(partition, mount_point, filesystem, read_only);
+        case BML:
+            return cmd_bml_mount_partition(partition, mount_point, filesystem, read_only);
+        default:
+            return -1;
+    }
+}
+
+int get_partition_device(const char *partition, char *device)
+{
+    int type = device_flash_type();
+    switch (type) {
+        case MTD:
+            return cmd_mtd_get_partition_device(partition, device);
+        case MMC:
+            return cmd_mmc_get_partition_device(partition, device);
+        case BML:
+            return cmd_bml_get_partition_device(partition, device);
+        default:
+            return -1;
+    }
+}
diff --git a/flashutils/flashutils.h b/flashutils/flashutils.h
new file mode 100644
index 0000000..d112a31
--- /dev/null
+++ b/flashutils/flashutils.h
@@ -0,0 +1,50 @@
+#ifndef FLASHUTILS_H
+#define FLASHUTILS_H
+
+int restore_raw_partition(const char* partitionType, const char *partition, const char *filename);
+int backup_raw_partition(const char* partitionType, const char *partition, const char *filename);
+int erase_raw_partition(const char* partitionType, const char *partition);
+int erase_partition(const char *partition, const char *filesystem);
+int mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only);
+int get_partition_device(const char *partition, char *device);
+
+#define FLASH_MTD 0
+#define FLASH_MMC 1
+#define FLASH_BML 2
+
+int is_mtd_device();
+char* get_default_filesystem();
+
+extern int cmd_mtd_restore_raw_partition(const char *partition, const char *filename);
+extern int cmd_mtd_backup_raw_partition(const char *partition, const char *filename);
+extern int cmd_mtd_erase_raw_partition(const char *partition);
+extern int cmd_mtd_erase_partition(const char *partition, const char *filesystem);
+extern int cmd_mtd_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only);
+extern int cmd_mtd_get_partition_device(const char *partition, char *device);
+
+extern int cmd_mmc_restore_raw_partition(const char *partition, const char *filename);
+extern int cmd_mmc_backup_raw_partition(const char *partition, const char *filename);
+extern int cmd_mmc_erase_raw_partition(const char *partition);
+extern int cmd_mmc_erase_partition(const char *partition, const char *filesystem);
+extern int cmd_mmc_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only);
+extern int cmd_mmc_get_partition_device(const char *partition, char *device);
+
+extern int cmd_bml_restore_raw_partition(const char *partition, const char *filename);
+extern int cmd_bml_backup_raw_partition(const char *partition, const char *filename);
+extern int cmd_bml_erase_raw_partition(const char *partition);
+extern int cmd_bml_erase_partition(const char *partition, const char *filesystem);
+extern int cmd_bml_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only);
+extern int cmd_bml_get_partition_device(const char *partition, char *device);
+
+extern int device_flash_type();
+extern int get_flash_type(const char* fs_type);
+
+enum flash_type {
+    UNSUPPORTED = -1,
+    UNKNOWN = 0,
+    MTD = 1,
+    MMC = 2,
+    BML = 3
+};
+
+#endif
\ No newline at end of file
diff --git a/fonts/12x22.png b/fonts/12x22.png
index ae826be..0f2e603 100644
--- a/fonts/12x22.png
+++ b/fonts/12x22.png
Binary files differ
diff --git a/fonts/18x32.png b/fonts/18x32.png
index d95408a..292d9d3 100644
--- a/fonts/18x32.png
+++ b/fonts/18x32.png
Binary files differ
diff --git a/fuse.h b/fuse.h
new file mode 100644
index 0000000..8135d47
--- /dev/null
+++ b/fuse.h
@@ -0,0 +1,574 @@
+/****************************************************************************
+ ****************************************************************************
+ ***
+ ***   This header was automatically generated from a Linux kernel header
+ ***   of the same name, to make information necessary for userspace to
+ ***   call into the kernel available to libc.  It contains only constants,
+ ***   structures, and macros generated from the original header, and thus,
+ ***   contains no copyrightable information.
+ ***
+ ***   To edit the content of this header, modify the corresponding
+ ***   source file (e.g. under external/kernel-headers/original/) then
+ ***   run bionic/libc/kernel/tools/update_all.py
+ ***
+ ***   Any manual change here will be lost the next time this script will
+ ***   be run. You've been warned!
+ ***
+ ****************************************************************************
+ ****************************************************************************/
+#ifndef _LINUX_FUSE_H
+#define _LINUX_FUSE_H
+#include <stdint.h>
+#define FUSE_KERNEL_VERSION 7
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_KERNEL_MINOR_VERSION 22
+#define FUSE_ROOT_ID 1
+struct fuse_attr {
+ uint64_t ino;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t size;
+ uint64_t blocks;
+ uint64_t atime;
+ uint64_t mtime;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t ctime;
+ uint32_t atimensec;
+ uint32_t mtimensec;
+ uint32_t ctimensec;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t mode;
+ uint32_t nlink;
+ uint32_t uid;
+ uint32_t gid;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t rdev;
+ uint32_t blksize;
+ uint32_t padding;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_kstatfs {
+ uint64_t blocks;
+ uint64_t bfree;
+ uint64_t bavail;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t files;
+ uint64_t ffree;
+ uint32_t bsize;
+ uint32_t namelen;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t frsize;
+ uint32_t padding;
+ uint32_t spare[6];
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_file_lock {
+ uint64_t start;
+ uint64_t end;
+ uint32_t type;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t pid;
+};
+#define FATTR_MODE (1 << 0)
+#define FATTR_UID (1 << 1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FATTR_GID (1 << 2)
+#define FATTR_SIZE (1 << 3)
+#define FATTR_ATIME (1 << 4)
+#define FATTR_MTIME (1 << 5)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FATTR_FH (1 << 6)
+#define FATTR_ATIME_NOW (1 << 7)
+#define FATTR_MTIME_NOW (1 << 8)
+#define FATTR_LOCKOWNER (1 << 9)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FOPEN_DIRECT_IO (1 << 0)
+#define FOPEN_KEEP_CACHE (1 << 1)
+#define FOPEN_NONSEEKABLE (1 << 2)
+#define FUSE_ASYNC_READ (1 << 0)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_POSIX_LOCKS (1 << 1)
+#define FUSE_FILE_OPS (1 << 2)
+#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+#define FUSE_EXPORT_SUPPORT (1 << 4)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_BIG_WRITES (1 << 5)
+#define FUSE_DONT_MASK (1 << 6)
+#define FUSE_SPLICE_WRITE (1 << 7)
+#define FUSE_SPLICE_MOVE (1 << 8)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_SPLICE_READ (1 << 9)
+#define FUSE_FLOCK_LOCKS (1 << 10)
+#define FUSE_HAS_IOCTL_DIR (1 << 11)
+#define FUSE_AUTO_INVAL_DATA (1 << 12)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_DO_READDIRPLUS (1 << 13)
+#define FUSE_READDIRPLUS_AUTO (1 << 14)
+#define FUSE_ASYNC_DIO (1 << 15)
+#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_RELEASE_FLUSH (1 << 0)
+#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+#define FUSE_GETATTR_FH (1 << 0)
+#define FUSE_LK_FLOCK (1 << 0)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_WRITE_CACHE (1 << 0)
+#define FUSE_WRITE_LOCKOWNER (1 << 1)
+#define FUSE_READ_LOCKOWNER (1 << 1)
+#define FUSE_IOCTL_COMPAT (1 << 0)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+#define FUSE_IOCTL_RETRY (1 << 2)
+#define FUSE_IOCTL_32BIT (1 << 3)
+#define FUSE_IOCTL_DIR (1 << 4)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_IOCTL_MAX_IOV 256
+#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+enum fuse_opcode {
+ FUSE_LOOKUP = 1,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_FORGET = 2,
+ FUSE_GETATTR = 3,
+ FUSE_SETATTR = 4,
+ FUSE_READLINK = 5,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_SYMLINK = 6,
+ FUSE_MKNOD = 8,
+ FUSE_MKDIR = 9,
+ FUSE_UNLINK = 10,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_RMDIR = 11,
+ FUSE_RENAME = 12,
+ FUSE_LINK = 13,
+ FUSE_OPEN = 14,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_READ = 15,
+ FUSE_WRITE = 16,
+ FUSE_STATFS = 17,
+ FUSE_RELEASE = 18,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_FSYNC = 20,
+ FUSE_SETXATTR = 21,
+ FUSE_GETXATTR = 22,
+ FUSE_LISTXATTR = 23,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_REMOVEXATTR = 24,
+ FUSE_FLUSH = 25,
+ FUSE_INIT = 26,
+ FUSE_OPENDIR = 27,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_READDIR = 28,
+ FUSE_RELEASEDIR = 29,
+ FUSE_FSYNCDIR = 30,
+ FUSE_GETLK = 31,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_SETLK = 32,
+ FUSE_SETLKW = 33,
+ FUSE_ACCESS = 34,
+ FUSE_CREATE = 35,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_INTERRUPT = 36,
+ FUSE_BMAP = 37,
+ FUSE_DESTROY = 38,
+ FUSE_IOCTL = 39,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_POLL = 40,
+ FUSE_NOTIFY_REPLY = 41,
+ FUSE_BATCH_FORGET = 42,
+ FUSE_FALLOCATE = 43,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_READDIRPLUS = 44,
+ CUSE_INIT = 4096,
+};
+enum fuse_notify_code {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_NOTIFY_POLL = 1,
+ FUSE_NOTIFY_INVAL_INODE = 2,
+ FUSE_NOTIFY_INVAL_ENTRY = 3,
+ FUSE_NOTIFY_STORE = 4,
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ FUSE_NOTIFY_RETRIEVE = 5,
+ FUSE_NOTIFY_DELETE = 6,
+ FUSE_NOTIFY_CODE_MAX,
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_MIN_READ_BUFFER 8192
+#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+struct fuse_entry_out {
+ uint64_t nodeid;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t generation;
+ uint64_t entry_valid;
+ uint64_t attr_valid;
+ uint32_t entry_valid_nsec;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t attr_valid_nsec;
+ struct fuse_attr attr;
+};
+struct fuse_forget_in {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t nlookup;
+};
+struct fuse_forget_one {
+ uint64_t nodeid;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t nlookup;
+};
+struct fuse_batch_forget_in {
+ uint32_t count;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t dummy;
+};
+struct fuse_getattr_in {
+ uint32_t getattr_flags;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t dummy;
+ uint64_t fh;
+};
+#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_attr_out {
+ uint64_t attr_valid;
+ uint32_t attr_valid_nsec;
+ uint32_t dummy;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ struct fuse_attr attr;
+};
+#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+struct fuse_mknod_in {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t mode;
+ uint32_t rdev;
+ uint32_t umask;
+ uint32_t padding;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct fuse_mkdir_in {
+ uint32_t mode;
+ uint32_t umask;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct fuse_rename_in {
+ uint64_t newdir;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_link_in {
+ uint64_t oldnodeid;
+};
+struct fuse_setattr_in {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t valid;
+ uint32_t padding;
+ uint64_t fh;
+ uint64_t size;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t lock_owner;
+ uint64_t atime;
+ uint64_t mtime;
+ uint64_t unused2;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t atimensec;
+ uint32_t mtimensec;
+ uint32_t unused3;
+ uint32_t mode;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t unused4;
+ uint32_t uid;
+ uint32_t gid;
+ uint32_t unused5;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct fuse_open_in {
+ uint32_t flags;
+ uint32_t unused;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct fuse_create_in {
+ uint32_t flags;
+ uint32_t mode;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t umask;
+ uint32_t padding;
+};
+struct fuse_open_out {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t fh;
+ uint32_t open_flags;
+ uint32_t padding;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_release_in {
+ uint64_t fh;
+ uint32_t flags;
+ uint32_t release_flags;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t lock_owner;
+};
+struct fuse_flush_in {
+ uint64_t fh;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t unused;
+ uint32_t padding;
+ uint64_t lock_owner;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_read_in {
+ uint64_t fh;
+ uint64_t offset;
+ uint32_t size;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t read_flags;
+ uint64_t lock_owner;
+ uint32_t flags;
+ uint32_t padding;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+#define FUSE_COMPAT_WRITE_IN_SIZE 24
+struct fuse_write_in {
+ uint64_t fh;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t offset;
+ uint32_t size;
+ uint32_t write_flags;
+ uint64_t lock_owner;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t flags;
+ uint32_t padding;
+};
+struct fuse_write_out {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t size;
+ uint32_t padding;
+};
+#define FUSE_COMPAT_STATFS_SIZE 48
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_statfs_out {
+ struct fuse_kstatfs st;
+};
+struct fuse_fsync_in {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t fh;
+ uint32_t fsync_flags;
+ uint32_t padding;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_setxattr_in {
+ uint32_t size;
+ uint32_t flags;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_getxattr_in {
+ uint32_t size;
+ uint32_t padding;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_getxattr_out {
+ uint32_t size;
+ uint32_t padding;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_lk_in {
+ uint64_t fh;
+ uint64_t owner;
+ struct fuse_file_lock lk;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t lk_flags;
+ uint32_t padding;
+};
+struct fuse_lk_out {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ struct fuse_file_lock lk;
+};
+struct fuse_access_in {
+ uint32_t mask;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t padding;
+};
+struct fuse_init_in {
+ uint32_t major;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t minor;
+ uint32_t max_readahead;
+ uint32_t flags;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_init_out {
+ uint32_t major;
+ uint32_t minor;
+ uint32_t max_readahead;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t flags;
+ uint16_t max_background;
+ uint16_t congestion_threshold;
+ uint32_t max_write;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+#define CUSE_INIT_INFO_MAX 4096
+struct cuse_init_in {
+ uint32_t major;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t minor;
+ uint32_t unused;
+ uint32_t flags;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct cuse_init_out {
+ uint32_t major;
+ uint32_t minor;
+ uint32_t unused;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t flags;
+ uint32_t max_read;
+ uint32_t max_write;
+ uint32_t dev_major;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t dev_minor;
+ uint32_t spare[10];
+};
+struct fuse_interrupt_in {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t unique;
+};
+struct fuse_bmap_in {
+ uint64_t block;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t blocksize;
+ uint32_t padding;
+};
+struct fuse_bmap_out {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t block;
+};
+struct fuse_ioctl_in {
+ uint64_t fh;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t flags;
+ uint32_t cmd;
+ uint64_t arg;
+ uint32_t in_size;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t out_size;
+};
+struct fuse_ioctl_iovec {
+ uint64_t base;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t len;
+};
+struct fuse_ioctl_out {
+ int32_t result;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t flags;
+ uint32_t in_iovs;
+ uint32_t out_iovs;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_poll_in {
+ uint64_t fh;
+ uint64_t kh;
+ uint32_t flags;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t events;
+};
+struct fuse_poll_out {
+ uint32_t revents;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t padding;
+};
+struct fuse_notify_poll_wakeup_out {
+ uint64_t kh;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct fuse_fallocate_in {
+ uint64_t fh;
+ uint64_t offset;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t length;
+ uint32_t mode;
+ uint32_t padding;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_in_header {
+ uint32_t len;
+ uint32_t opcode;
+ uint64_t unique;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t nodeid;
+ uint32_t uid;
+ uint32_t gid;
+ uint32_t pid;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t padding;
+};
+struct fuse_out_header {
+ uint32_t len;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ int32_t error;
+ uint64_t unique;
+};
+struct fuse_dirent {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t ino;
+ uint64_t off;
+ uint32_t namelen;
+ uint32_t type;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ char name[];
+};
+#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+#define FUSE_DIRENT_ALIGN(x)   (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FUSE_DIRENT_SIZE(d)   FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+struct fuse_direntplus {
+ struct fuse_entry_out entry_out;
+ struct fuse_dirent dirent;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+#define FUSE_NAME_OFFSET_DIRENTPLUS   offsetof(struct fuse_direntplus, dirent.name)
+#define FUSE_DIRENTPLUS_SIZE(d)   FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+struct fuse_notify_inval_inode_out {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t ino;
+ int64_t off;
+ int64_t len;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_notify_inval_entry_out {
+ uint64_t parent;
+ uint32_t namelen;
+ uint32_t padding;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct fuse_notify_delete_out {
+ uint64_t parent;
+ uint64_t child;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t namelen;
+ uint32_t padding;
+};
+struct fuse_notify_store_out {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t nodeid;
+ uint64_t offset;
+ uint32_t size;
+ uint32_t padding;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct fuse_notify_retrieve_out {
+ uint64_t notify_unique;
+ uint64_t nodeid;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint64_t offset;
+ uint32_t size;
+ uint32_t padding;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct fuse_notify_retrieve_in {
+ uint64_t dummy1;
+ uint64_t offset;
+ uint32_t size;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint32_t dummy2;
+ uint64_t dummy3;
+ uint64_t dummy4;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#endif
diff --git a/fuse/AUTHORS b/fuse/AUTHORS
new file mode 100644
index 0000000..8c1e88f
--- /dev/null
+++ b/fuse/AUTHORS
@@ -0,0 +1,9 @@
+FUSE
+----
+
+Miklos Szeredi <miklos@szeredi.hu>
+
+CUSE
+----
+
+Tejun Heo <teheo@suse.de>
diff --git a/fuse/Android.mk b/fuse/Android.mk
new file mode 100644
index 0000000..00763ea
--- /dev/null
+++ b/fuse/Android.mk
@@ -0,0 +1,53 @@
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	android/statvfs.c \
+	buffer.c \
+	cuse_lowlevel.c \
+	fuse.c \
+	fuse_kern_chan.c \
+	fuse_loop.c \
+	fuse_loop_mt.c \
+	fuse_lowlevel.c \
+	fuse_mt.c fuse_opt.c \
+	fuse_session.c \
+	fuse_signals.c \
+	helper.c \
+	mount.c \
+	mount_util.c \
+	ulockmgr.c
+
+LOCAL_C_INCLUDES := \
+	$(LOCAL_PATH)/android \
+	$(LOCAL_PATH)/include
+
+LOCAL_SHARED_LIBRARIES := \
+	libutils
+
+LOCAL_CFLAGS := \
+	-D_FILE_OFFSET_BITS=64 \
+	-DFUSE_USE_VERSION=26 \
+	-fno-strict-aliasing
+
+LOCAL_CFLAGS += -Wno-pointer-arith -Wno-sign-compare -Wno-unused-parameter
+
+LOCAL_MODULE := libfusetwrp
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/fuse/COPYING b/fuse/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/fuse/COPYING
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ 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
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+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 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
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+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
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    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
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+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
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+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
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+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
+
+  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
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+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
+
+  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
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    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.,
+    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) 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.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+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 Lesser General
+Public License instead of this License.
diff --git a/fuse/COPYING.LIB b/fuse/COPYING.LIB
new file mode 100644
index 0000000..4362b49
--- /dev/null
+++ b/fuse/COPYING.LIB
@@ -0,0 +1,502 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 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.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes 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
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; 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.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/fuse/ChangeLog b/fuse/ChangeLog
new file mode 100644
index 0000000..f2e5d02
--- /dev/null
+++ b/fuse/ChangeLog
@@ -0,0 +1,3535 @@
+2015-05-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.9.4
+
+2015-05-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: fix exec environment for mount and umount.  Found by
+	Tavis Ormandy (CVE-2015-3202).
+
+2015-02-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: fix fuse_remove_signal_handlers() to properly restore
+	the default signal handler.  Reported by: Chris Johnson
+
+2014-07-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: highlevel API: fix directory file handle passed to
+	ioctl() method.  Reported by Eric Biggers
+
+2014-07-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: document deadlock avoidance for
+	fuse_notify_inval_entry() and fuse_notify_delete()
+
+	* fusermount, libfuse: send value as unsigned in "user_id=" and
+	"group_id=" options.  Uids/gids larger than 2147483647 would
+	result in EINVAL when mounting the filesystem.  This also needs a
+	fix in the kernel.
+
+2014-03-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Initilaize stat buffer passed to ->getattr() and ->fgetattr() to
+	zero in all cases.  Reported by Daniel Iwan
+
+2013-08-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: Add missing includes.  This allows compiling fuse with
+	musl.  Patch by Daniel Thau
+
+2013-07-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.9.3
+
+2013-06-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: fix multiple close of device fd.  Reported by Dan
+	Greenfield
+
+2013-03-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: fix thread cancel race.  Exiting a worker my race with
+	cancelling that same worker.  This caused a segmenation
+	fault. Reported and tested by Anatol Pomozov
+
+2013-02-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: fix crash in unlock_path().  Patch by Ratna Manoj
+
+	* libfuse: fix the 'remember' option.  The lru list was not
+	initialized for the "/" path.  This resulted in remove_node_lru()
+	crashing on LOOKUP-DOTDOT.  Patch by Madan Valluri
+
+	* libfuse: configure: detect new util-linux
+
+	* libfuse: Use AC_CONFIG_HEADERS instead of AM_CONFIG_HEADER.
+	Patch by Anatol Pomozov
+
+	* libfuse: rename ./configure.in to ./configure.ac.  Patch by
+	Anatol Pomozov
+
+2012-10-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.9.2
+
+2012-10-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix deadlock in libfuse.  Running "svn update" on a fuse
+	filesystem could deadlock because of a bug in the way the paths
+	are locked.  Reported by Kazuaki Anami
+
+2012-08-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix missing config.h in buffer.c.  Reported by Matthew Gabeler-Lee
+
+2012-08-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Not unhashing the name in forget (commit on 2011-12-09) broke
+	the forget logic in a subtle way, resulting in "fuse internal
+	error: node NNN not found" and causing the filesystem daemon to
+	abort.  Fix by incrementing the node refcount if nlookup goes from
+	zero to one.  Reported by Kyle Lippincott
+
+2012-08-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix linking against GNU libiconv.  Patch by Natanael Copa
+
+2012-07-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.9.1
+
+2012-07-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix crash caused by freeing a stack address.  Reported by Itay
+	Perl
+
+2012-07-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix install of mount.fuse from out-of-tree build.  Patch by
+	Olivier Blin
+
+	* Fix build with automake >= 1.12.1.  Patch by Olivier Blin
+
+2012-04-24  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add fallocate operation.  Only works on linux kernels 3.5 or
+	later.  Patch by Anatol Pomozov
+
+2012-05-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Linking to a library that uses threads requires the application
+	to be linked with -pthreads otherwise some pthread functions will
+	be linked to stubs in glibc.  So move -pthread from Libs.private
+	to Libs in fuse.pc.  Reported by Werner Fink
+
+	* Fix the compile command in the examples. Reported by Luciano
+	Dalle Ore
+
+2012-04-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.9.0
+
+2012-04-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add missing fuse_fs_flock to fuse_versionscript
+
+2012-04-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check protocol version before sending notifications and return
+	-ENOSYS if a particular notification is not supported.
+
+	* Add 'flag_utime_omit_ok' flag to fuse_operations.  If the
+	filesystem sets this flag then ->utimens() will receive UTIME_OMIT
+	and UTIME_NOW values as specified in utimensat(2).
+
+2012-01-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Interpret octal escape codes in options.  Requested by Jan
+	Engelhardt
+
+2012-01-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add man pages for fusermount, mount.fuse and ulockmgr_server.
+	Lifted from the Debian package.  The man pages were written by
+	Daniel Baumann and Bastien Roucaries
+
+2012-01-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Disable symbol versions on MacOSX.  Patch by Anatol Pomozov
+
+2012-01-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Remove unnecessary mutex unlock at the end of multithreaded
+	event loop.
+
+2011-12-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix hang in wait_on_path().  Reported by Ville Silventoinen
+
+	* Don't unhash name in FORGET.  This resulted in ENOENT being
+	returned for unlinked but still open files if the kernel sent a
+	FORGET request for the parent directory.
+
+	* Free request in fuse_reply_data().
+
+2011-12-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix build if FUSE_NODE_SLAB is not defined.  Patch by Emmanuel
+	Dreyfus
+
+	* Check for availability of utimensat() function.  Patch by
+	Emmanuel Dreyfus
+
+2011-12-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add fuse_lowlevel_notify_delete() which tells the kernel that a
+	file or directory is deleted.  Patch by John Muir
+
+2011-12-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Update retrieve_reply() method
+
+2011-12-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Low level API: lock argument of fuse_reply_lock should have a
+	'const' qualifier.  Reported by Shachar Sharon
+
+	* Add support for ioctl on directories.  Reported by Antonio SJ
+	Musumeci
+
+2011-10-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Reply to request with ENOMEM in case of failure to allocate
+	request structure.  Otherwise the task issuing the request will
+	just freeze up until the filesystem daemon is killed.  Reported by
+	Stephan Kulow
+
+2011-09-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Replace daemon() function with fork().  Patch by Anatol Pomozov
+
+2011-08-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* If configured with --disable-mtab then don't call mount(8) from
+	libfuse to update the mtab.  Reported by: James Sierp
+
+2011-08-24  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Use LRU list for cleaning up the cache if the "remember=T"
+	option was given.  Patch by therealneworld@gmail.com
+
+2011-07-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add ->flock() operation to low and high level interfaces.  This
+	fixes problems with emulating flock() with POSIX locking.
+	Reported by Sebastian Pipping.  As with lock/setlk/getlk most
+	filesystems don't need to implement this, as the kernel takes care
+	of file locking.  The only reason to implement locking operations
+	is for network filesystems which want file locking to work between
+	clients.
+
+2011-07-02  Sebastian Pipping <sebastian@pipping.org>
+
+	* Make xmp_utimens of examples "fusexmp" and "fusexmp_fh"
+	not follow symlinks as other layers do that already.
+
+2011-06-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add "remember" option.  This works similar to "noforget" except
+	that eventually the node will be allowed to expire from the cache.
+	Patch by therealneworld@gmail.com
+
+2011-05-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check if splice/vmsplice are supported
+
+2011-05-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Remove -lrt -ldl from fuse.pc for dynamic linking since
+	libfuse.so is already linked with these libraries.  Reported by:
+	Nikolaus Rath
+
+2011-05-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Cleaner build output.  Patch by Reuben Hawkins
+
+2011-05-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Disable splice by default, add "splice_read", "splice_write" and
+	"splice_move" options.  Keep the "no_splice_*" variants, which can
+	disable splice even if the filesystem explicitly enables it.
+
+2011-04-15  Max Krasnyansky <maxk@kernel.org>
+	* Added support for "auto_unmount" option which unmounts the
+	filesystem automatically on process exit (or crash).
+
+2011-03-30  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Patches by Laszlo Papp fixing various issues found by the
+	Coverity checker
+
+2011-03-11  Miklos Szeredi <miklos@szeredi.hu>
+
+	* In case of failure to add to /etc/mtab don't umount.  Reported
+	by Marc Deslauriers
+
+2011-02-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: In fuse_session_loop_mt() don't pause when exiting the
+	worker threads.  The pause() was added in 2.2.1 to prevent
+	segfault on pthread_cancel() on an exited, detached thread.  Now
+	worker threads are not detached and pthread_cancel() should work
+	fine even after the thread exited.  Reported by Boris Protopopov
+
+2011-01-31  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: chdir to / before performing mount/umount
+
+	* fusermount: only allow mount and umount if util-linux supports
+	--no-canonicalize
+
+2010-12-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Highlevel lib: allow hash tables to shrink
+
+	* Highlevel lib: add slab allocation for node cache.  This will
+	allow the memory used by the filesystem to grow and shrink
+	depending on how many inodes are currently cached.
+
+2010-12-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Highlevel lib: use dynamically resized hash table for looking up
+	by name and node ID.
+
+2010-12-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Allow batching of forget requests.  This allows forget requests
+	to be processed faster and doesn't require a modification to fuse
+	filesystems.  Reported by Terje Malmedal
+
+	* Add ->forget_multi() operation to the lowlevel API.  The
+	filesystem may implement this to process multiple forget requests
+	in one call
+
+	* Fix the ambiguity of ioctl ABI on the kernel/userspace boundary
+	for 32bit vs. 64bit userspace
+
+2010-11-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add new write_buf() method to the highlevel API.  Similarly to
+	the lowlevel write_buf() method, this allows implementing zero
+	copy writes.
+
+	* Add a new read_buf() method to the highlevel API.  This allows
+	returning a generic buffer from the read method, which in turn
+	allows zero copy reads.
+
+	* In fusexmp_fh implement the ->read_buf() and ->write_buf()
+	methods.  Leave the ->read() and ->write() implementations for
+	reference, even though they are not necessary.
+
+2010-11-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix check for read-only fs in mtab update
+
+	* Open /dev/null for write instead of read for redirecting stdout
+	and stderr
+
+	* If umount(8) supports --fake and --no-canonicalize (util-linux-ng
+	version 2.18 or later), and umount(2) supports the
+	UMOUNT_NOFOLLOW flag (linux kernel version 2.6.35 or later)  then,
+	"fusermount -u" will call the umount(2) system call and use
+	"umount --fake ..." to update /etc/mtab
+
+	* Added --disable-legacy-umount option to configure.  This
+	disables the runtime checking of umount(8) version.  When built
+	with this option then "fusermount -u" will fail if umount(8)
+	doesn't support the --fake and --no-canonicalize options.
+
+	* Fix fuse_buf_copy() if already at the end of the buffers
+
+	* Add new ->write_buf() method to low level interface.  This
+	allows passig a generic buffer, either containing a memory buffer
+	or a file descriptor.  This allows implementing zero copy writes.
+
+	* Add fuse_session_receive_buf() and fuse_session_process_buf()
+	which may be used in event loop implementations to replace
+	fuse_chan_recv() and fuse_session_process() respectively.
+
+	* Remove unnecessary restoring of current working directory in
+	"fusermount -u"
+
+	* Add ctx->pid to debug output
+
+	* Fix st_nlink value in high level lib if file is unlinked but
+	still open
+
+	* libfuse: add store request.  Request data to be stored in the
+	kernel buffers for a given inode.
+
+	* libfuse: add retrieve request.  Retrieve data stored in the
+	kernel buffers for a given inode.
+
+2010-10-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Use LTLIBICONV when linking libfuse.  This fixes building against
+	uclibc + libiconv.  Patch by Natanael Copa
+
+2010-10-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add missing argument check in ulockmgr.c to prevent calling
+	ulockmgr_server with illegal arguments. This would cause an ever
+	growing list of ulockmgr_server processes with an endless list of
+	open files which finally exceeds the open file handle limit.
+	Patch by Markus Ammer
+
+2010-09-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix ambiguous symbol version for fuse_chan_new.
+	fuse_versionscript included fuse_chan_new in both FUSE_2.4 and
+	FUSE_2.6.  Remove the FUSE_2.4, which is invalid.
+
+2010-09-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix option escaping for fusermount.  If the "fsname=" option
+	contained a comma then the option parser in fusermount was
+	confused (Novell bugzilla #641480).  Fix by escaping commas when
+	passing them over to fusermount.  Reported by Jan Engelhardt
+
+2010-08-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add NetBSD support.  Patch from Emmanuel Dreyfus
+
+2010-07-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: add buffer interface.  Add a generic buffer interface
+	for use with I/O.  Buffer vectors are supplied and each buffer in
+	the vector may be a memory pointer or a file descriptor.
+
+	* The fuse_reply_fd() interface is converted to using buffers.
+
+2010-06-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Make the number of max background requests and congestion
+	threshold tunable.  New options are "max_background" and
+	"congestion_threshold".  Only effective on linux kernel versions
+	2.6.32 or greater.  Patch by Csaba Henk
+
+2010-06-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add fuse_reply_fd() reply function to the low level interface.
+	On linux version 2.6.35 or greater this will use splice() to move
+	data directly from a file descriptor to the fuse device without
+	needing to go though a userspace buffer.  With the
+	FUSE_REPLY_FD_MOVE flag the kernel will attempt to move the data
+	directly into the filesystem's cache.  On earlier kernels it will
+	fall back to an intermediate buffer.  The options
+	"no_splice_write" and "no_splice_move" can be used to disable
+	splicing and moving respectively.
+ 
+2010-06-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix out-of-source build.  Patch by Jörg Faschingbauer
+
+	* Add a "nopath" option and flag, indicating that path argument
+	need not be calculated for the following operations: read, write,
+	flush, release, fsync, readdir, releasedir, fsyncdir, ftruncate,
+	fgetattr, lock, ioctl and poll.
+
+2010-05-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Remove "chmod root" from install of fusermount.  Reported by
+	Lucas C. Villa Real
+
+2010-04-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.8.4
+
+2010-04-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix checking for symlinks in umount from /tmp.  Reported by Al
+	Viro
+
+	* Fix umounting if /tmp is a symlink.  Reported by Franco Broi
+
+2010-02-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix definition of FUSE_OPT_END for C++.  Reported by Tim
+	Bruylants
+
+2010-02-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix stack alignment for clone()
+
+2010-02-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.8.3
+
+2010-02-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Using "--no-canonicalize" with umount(8) conflicts with the race
+	fix, sinceit assumes the supplied path is absolute, while the race
+	fix relies on the path being relative to the current directory.
+	Reported by Tom Rindborg
+
+2010-01-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.8.2
+
+2010-01-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix race if two "fusermount -u" instances are run in parallel.
+	Reported by Dan Rosenberg
+
+	* Make sure that the path to be unmounted doesn't refer to a
+	symlink
+
+2010-01-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix compile error on FreeBSD.  Patch by Jay Sullivan
+
+2009-12-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Use '--no-canonicalize' option of mount(8) (available in
+	util-linux-ng version 2.17 or greater) to avoid calling
+	readling(2) on the newly mounted filesystem before the mount
+	procedure is finished.  This has caused a deadlock if "audit" was
+	enabled in the kernel.  Also use '--no-canonicalize' for umount to
+	avoid touching the mounted filesystem.
+
+2009-09-11  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.8.1
+
+2009-08-25  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix missing versioned symbol fuse_get_context@FUSE_2.2
+
+2009-08-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.8.0
+
+2009-08-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add missing fuse_session_data to versionscript
+
+	* Make sure all global symbols are prefixed with "fuse_" or "cuse_"
+
+2009-07-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Clarify how the protocol version should be negotiated between
+	kernel and userspace.  Notably libfuse didn't correctly handle the
+	case when the supported major versions didn't match
+
+	* Add missing pthread link for libulockmgr.  Patch by  Petr Salinger
+
+2009-07-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* The context is extended with a 'umask' field.  The umask is sent
+	for mknod, mkdir and create requests by linux kernel version
+	2.6.31 or later, otherwise the umask is set to zero.  Also
+	introduce a new feature flag: FUSE_CAP_DONT_MASK.  If the kernel
+	supports this feature, then this flag will be set in conn->capable
+	in the ->init() method.  If the filesystem sets this flag in in
+	conn->want, then the create modes will not be masked.
+
+	* Add low level interfaces for lookup cache and attribute
+	invalidation.  This feature is available in linux kernels 2.6.31
+	or later.  Patch by John Muir
+
+	* Kernel interface version is now 7.12
+
+	* fusermount: Do not silently ignore command line arguments.
+	Patch by Sebastian Harl
+
+2009-06-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.8.0-pre3
+
+2009-06-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add fuse_getgroups (high level lib) and fuse_req_getgroups (low
+	level lib) functions to query the supplementary group IDs for the
+	current request.  Currently this is implemented on Linux by
+	reading from the /proc filesystem.
+
+2009-06-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add "noforget" option to high level lib to prevent ESTALE errors
+	on NFS exported filesystems.  This result in paths being cached
+	forever, resulting in ever growing memory usage.  Use with care.
+
+	* Add "no_remote_lock" option to disable remote file locking even
+	if the filesystem implements it.  With this option locking
+	primitives (flock, lockf, fcntl(F_SETLK)) will still work, but
+	will ignore remotely locked files.
+
+	* CUSE patches from Tejun Heo:
+
+	* Unrestricted ioctl support left some debris.  Clean them up:
+	  o No reason to pass around pointer to flags.  Pass flags directly.
+	  o Clean up comment and prototype parameter names.
+	  o fuse_lib_ioctl() didn't reply when get_path() failed.  Fix it.
+	  o Remove unused variables {in|out}_iov from fuse_lib_ioctl().
+
+	* Add fuse_reply_ioctl_iov()
+
+	* Move fuse_session, fuse_req and fuse_ll definitions to fuse_i.h
+	and make send_reply_iov() and fuse_setup_common() global (also in
+	fuse_i.h).  These will be used by CUSE support.
+
+	* Restructure fuse_ll_process()
+
+	* Implement libfuse side of CUSE support.  CUSE uses subset of FUSE
+	operations as dir operations don't make sense for CUSE where one
+	instance implements single character device.
+
+	CUSE support comes with its own cuse_lowevel_ops and related
+	initialization and helper functions.  Except for initialization, it
+	usage is basically identical to FUSE.
+
+	This patch also adds example/cusexmp.c which can create a character
+	device with name and device number specified on command line.  The
+	created device itself is pretty boring.  It's a bit bucket supporting
+	read, write and access via ioctl.
+
+2009-06-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add missing fuse_reply_bmap to versionscript.  Debian
+	Bug#531329.  Reported by Goswin Brederlow
+
+2009-05-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Don't call forget_node() if the lookup was negative and write()
+	for the reply returned ENOENT.  Reported by John Haxby
+
+2009-05-25  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add FUSE_CAP_EXPORT_SUPPORT to fuse_common.h
+
+2009-05-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix missing newlines in some printfs
+
+	* Fix 'make install-strip'.  Reported by Dominick Layfield
+
+2009-01-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.8.0-pre2
+
+2008-12-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Implement poll support.  Patch by Tejun Heo
+
+	* Add missing setattr flags to <fuse_lowlevel.h>.
+
+	* Only pass valid flags to ->setattr().
+
+2008-12-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Implement ioctl support.  On high level interface only
+	"restricted" ioctls are supported (which are defined with the
+	_IO(), _IOR(), _IOW() or _IOWR() macros).  Unrestricted ioctls
+	will only be allwed to CUSE (Character Device in Userspace)
+	servers.  Patch by Tejun Heo
+
+2008-11-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* If open sets fi->nonseekable, libfuse will tell the kernel that
+	the file is not seekable.  Patch by Tejun Heo
+
+2008-11-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lowlevel lib: fix deadlock if fuse_reply_* is called from the
+	interrupt handling function.  Reported by Tero Marttila
+
+2008-10-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Allow commas in options to be escaped with a backslash
+
+	* Add new function: fuse_opt_add_opt_escaped()
+
+	* Add missing fuse_reply_bmap() to the version script
+
+2008-10-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Pass current file flags to read and write operations
+
+2008-07-24  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Clean up debug output in highlevel lib
+
+2008-07-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.8.0-pre1
+
+2008-06-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix handling of (no)suid and (no)dev options if filesystem is
+	mounted from /etc/fstab or via mount(8).  Reported by Jan Ondrej.
+
+	* Skip calling mount(8) if /etc/mtab doesn't exist or if it's on a
+	read-only filesystem.  This works around issues with certain mount
+	implementations.  Reported by Szabolcs Szakacsits.
+
+2008-06-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Remove fuse kernel module sources.  Linux 2.6.27 will support
+	NFS exporting.
+
+2008-06-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix theoretical infinite loops in libfuse.  Reported by Szabolcs
+	Szakacsits
+
+	* Fix missing <sys/param.h> include for PATH_MAX.  Reported by
+	Szabolcs Szakacsits
+
+2008-05-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix mounting over symlink.  Reported by Szabolcs Szakacsits
+
+2008-05-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Don't allow bigger than 4kB writes by default on 2.6.26 and
+	later kernels, so that filesystems not expecting this are not
+	broken on a kernel upgrade.  Provide a 'big_writes' mount option
+	to enable this feature.  In future API revisions this may become
+	the default.
+
+2008-04-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Update warning message for missing newline at end of fuse.conf
+
+	* Update debug message for successful operation to not include the
+	string "error:"
+
+2008-04-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Update error message for missing mountpoint parameter.  Reported
+	by Allen Pulsifer
+
+2008-04-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Print library version information to debug output
+
+	* Highlevel lib: don't limit paths to 4095 characters
+
+2008-03-25  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix memory leaks on mount.  Patch by Szabolcs Szakacsits
+
+2008-03-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix missing pthread_mutex_destroy in error path of
+	fuse_lib_opendir().  Patch by Szabolcs Szakacsits
+
+2008-03-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add queuing on contention to per-node lock algorithm, to avoid
+	starvation.
+
+	* Only enable cancelation when reading a request, otherwise
+	cancellation could happen with a mutex held, which could hang the
+	process on umount
+
+2008-02-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Block SIGCHLD when executing mount and umount
+
+	* fusexmp_fh: avoid unnecessary seeking in readdir
+
+	* Update kernel interface to 7.9:
+
+	* Support receiving file handle from kernel in GETATTR request
+
+	* Allow operations with a NULL path argument, if the filesystem
+	supports it
+
+	* Add support atomic open(O_TRUNC)
+
+	* Support the st_blksize field in struct stat
+
+	* If the "FUSE_THREAD_STACK" environment is set, initialize the
+	stack size of threads by this value.  Patch by Florin Malita
+
+	* Add per-node locking, instead of a global tree lock to protect
+	the path from changing during operations.  Original patch by
+	Rodrigo Castro
+
+2008-02-03  Csaba Henk <csaba.henk@creo.hu>
+
+	* lib/mount_bsd.c:
+	- string formatting fixes
+	- exit if mounting has failed
+	  (in FreeBSD a mount failure is not critical per se, as the daemon
+	  still could be mounted externally, but waiting for such an event
+	  is more confusing than fruitful)
+	- ditch the kvm(8) stuff and simply use forced unmount which just
+	  won't block
+	- prettify option specifications
+	- add "-onosync_unmount" kernel option
+
+2008-01-07  Csaba Henk <csaba.henk@creo.hu>
+
+	* lib/mount_bsd.c:
+	- refine device closing in a race-free way
+	- add support for "-osubtype" on FreeBSD
+
+	* makeconf.sh: make it work under FreeBSD
+
+2008-01-03  Csaba Henk <csaba.henk@creo.hu>
+
+	* lib/mount_bsd.c: close device before unmount
+	(cf. lib/mount.c rev. 1.43) and fix some warnings 
+
+2007-12-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix './configure --disable-static'.  Patch from Ismail Dönmez
+
+2007-12-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.7.2
+
+2007-12-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix kernel module compile for 2.6.24
+
+	* Invalidate attributes of parent directory after create(), since
+	the modification time changes.  Invalidate attributes on rename,
+	since some filesystems may update st_ctime.  Reported by Szabolcs
+	Szakacsits
+
+	* Fix NFS exporting to handle 64bit node IDs
+
+	* Disable old symbol versions if __UCLIBC__ is defined.  If a
+	symbol in a library has multiple versions, the runtime linker in
+	uClibc seems to randomly choose between them.
+
+	* Remove erroneous 'fuse_opt_insert_arg@FUSE_2_5' from
+	fuse_version_script.  fuse_opt_free_args() was added in fuse-2.6.
+
+	* Close fuse device file descriptor before calling umount(),
+	preventing a deadlock when umount is synchronous.  Reported by
+	Szabolcs Szakacsits
+
+2007-11-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* 'fusermount -u' did not umount the filesystem if /etc/mtab was a
+	symlink.  This bug was introduced in 2.7.1 by "Don't call
+	/bin/[u]mount if /etc/mtab is a symlink".  Found by robertsong.
+
+2007-10-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.7.1
+
+2007-10-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Clarify licence version to be "LGPLv2" for the library
+
+	* kernel fixes:
+
+	* After mount set nlink attribute for the root inode to 1
+
+	* Fix wake up of task waiting for a reserved request
+
+	* Fix allowing setattr, listxattr and statfs for other users
+
+2007-09-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add missing context initialization in fuse_fs_chmod().  Bug
+	found by "iohead"
+
+	* Fix kernel module compilation for 2.6.23.  Based on patch by
+	Marian Marinov
+
+2007-09-04  Philippe Elie  <phil.el@wanadoo.fr>
+
+	* lib/fuse_lowlevel.c: fix a fuse_req leak in do_forget()
+
+2007-07-31  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Work around hotplug issue, that it calls filesystem with file
+	descriptors 0, 1 and 2 not open.  Tracked down by Leif Johnson
+
+2007-07-25  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Don't call /bin/[u]mount if /etc/mtab is a symlink.  Reported by
+	Tomas M
+
+	* Also don't touch /etc/mtab if it is within the mounted
+	filesystem.  Suggested by Jeffrey Law
+
+2007-07-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Reset args->argc in fuse_opt_free_args().  Patch by Lucas
+	C. Villa Real
+
+2007-07-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.7.0
+
+2007-07-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Accept a NULL "op" for fuse_main(), etc.  This is useful if
+	filesystem is only invoking fuse to print a help message, or
+	version.  Fixes RedHat bugzilla #217343
+
+2007-06-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: fix locking when loading a filesystem module
+
+2007-06-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add fs subtype support to mount.fuse
+
+2007-06-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add fs subtype support to libfuse and fusermount
+
+2007-06-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: sync with mainline (2.6.22)
+
+2007-06-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Send debug output to stderr instead of stdout.  Patch by Jan
+	Engelhardt
+
+2007-06-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libulockmgr: Work around a kernel bug in recv(), causing it to
+	sometimes return zero even if data was available on the socket.
+
+2007-05-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: optimization: store parent pointer in node instead of
+	parent id
+
+2007-05-25  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: don't create new thread for each FORGET request.  FORGET
+	messages sometimes caused so many threads to be created, that
+	process virtual memory space ran out.  Reported by Chris AtLee
+
+2007-05-24  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: fix memory leak on thread creation failure in multithreaded
+	event loop.  Found by Chris AtLee
+
+2007-05-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lowlevel lib: add fuse_reply_iov function, which is similar to
+	fuse_reply_buf, but accepts a vector of buffers.  Patch by Roger
+	Willcocks
+
+2007-05-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix Oops or error if a regular file is created with mknod(2) on
+	a fuse filesystem.  Kernels 2.6.18 onward are affected.  Thanks to
+	J. Cameijo Cerdeira for the report
+
+2007-05-11  Csaba Henk <csaba.henk@creo.hu>
+
+	* libfuse: fix return value of fuse_loop()/fuse_loop_mt().
+	Error reported by Csaba Henk, fix by Miklos Szeredi
+
+	* libfuse: fix unlock in flush
+
+	* libfuse: do unlocking on RELEASE+FLUSH
+ 
+2007-05-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.7.0-rc1
+
+2007-05-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: sync with mainline:
+
+	* Use invalidate_mapping_pages() if available
+
+	* Fix BUG when invalid file type is supplied in mount. Patch by
+	Timo Savola
+
+2007-04-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: call umount(8) directly instead of fusermount if
+	possible
+
+	* Clean up init script, make it LSB compliant
+
+2007-04-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* In multithreaded loop, use a semaphore instead of SIGHUP to wake
+	up the main thread on umount.  This is more elegant, and works
+	even if signals are blocked.
+
+2007-04-25  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Improve mounting support in libfuse:
+	 - check non-empty mountpoint
+	 - only fall back to fusermount when necessary
+
+2007-04-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Don't chdir to "/" in foreground mode, it causes more trouble
+	than it's worth
+
+2007-04-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Replace utils/mount.fuse "sh" script with a "C" program
+
+2007-04-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add -lulockmgr to compilation comment in fusexmp_fh.c
+
+2007-04-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check for iconv.  Patch by Csaba Henk
+
+	* Add direct umounting
+
+	* Use "fusectl" as the device for the fusectl filesystem.  Debian
+	Bug#417945.  Reported by Laurent Bonnaud
+
+2007-04-01  Csaba Henk <csaba.henk@creo.hu>
+
+	* Fix some FreeBSD related macros.
+
+2007-03-30  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add support for direct mounting by libfuse.  Fall back on
+	calling fusermount if it doesn't work
+
+2007-03-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.7.0-pre1
+
+2007-03-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Correctly handle O_APPEND in direct IO mode.  Reported by Greg
+	Bruno
+
+	* mount.fuse should use /bin/bash.  Debian Bug#413403.  Reported
+	by Thomas Weinbrenner
+
+2007-02-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix detection of installed fuse in init script.  Reported and
+	fix suggested by Davide Canova
+
+2007-02-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix 2.6.9 RHEL kernels, which have compatibility mutex.h, but
+	don't define mutex_destroy(), bummer.  Patch from Phil Schwan
+
+2007-02-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Compile fuseblk for kernels which don't have an option to turn
+	off the block layer (CONFIG_BLOCK).  Reported by Szakacsits
+	Szabolcs
+
+2007-02-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add filesystem stacking support to high level API.  Filesystem
+	modules can be built into libfuse or loaded from shared object
+	(.so) files
+
+	* Add 'subdir' and 'iconv' built in modules
+
+	* lib/fuse.c: Fix locking for the reply code in create and open
+
+2007-02-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: make it compile on "strange" kernels which have emulated
+	mutexes via <linux/mutex.h> but no i_mutex.  Reported by Tomasz
+	Mateja
+
+2007-01-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: fix BUG in control filesystem if it is umounted and
+	mounted again, while some fuse filesystems are present.
+	Bugreport from Florent Mertens
+
+	* kernel: sync with mainline, support 2.6.20
+
+2007-01-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib/Makefile.am: actually link libfuse against libfuse_libs
+
+2007-01-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Build fix for 2.6.16 vanila and 2.6.15 FC5 kernels.  Patch from
+	Ian Abbott
+
+2007-01-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix abort in fuse_new() compatibility API for opts == NULL case.
+	Novell bugzilla #233870.  Patch from Takashi Iwai.
+
+2007-01-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix option parsing in mount.fuse.  Patch from Jens M. Noedler
+
+2007-01-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix unaligned access in file desctriptor passing in libfuse,
+	fusermount and ulockmgr.  Debian bug ID: 404904.  Reported and
+	tested by Sebastian Fontius
+
+2006-12-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: don't keep unreferenced inodes in the icache.
+
+2006-12-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: Fix detection of fuseblk.  Reported by Szakacsits
+	Szabolcs
+
+	* lib: Fix use after free in fuse_flush().  Reported by Ron
+	Lindman
+
+2006-12-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* mount.fuse: add "setuid=USER" option which does a "su - USER"
+	for the filesystem
+
+	* fusermount: use "/bin/mount -f" to add entry to /etc/mtab, and
+	"/bin/umount" to remove entry from /etc/mtab.  This gets rid of
+	the ugly code dealing with mtab, as well as a possible race
+	between fusermount and mount trying to modify /etc/mtab at the
+	same time
+
+	* Fix "buffer size too small: 4" warning for users of the
+	fuse_loop_mt_proc() function.
+
+2006-12-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix warnings with gcc-4.1 on 64bit archs.  Report from
+	Harshavardhana
+
+	* Add extra warning options, and fix resulting warnings
+
+	* Really fix fuse_teardown problem
+
+2006-12-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add -lrt to fuse.pc (if needed) to fix static linking against
+	libfuse.  Reported by Szakacsits Szabolcs
+
+2006-12-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.6.1
+
+2006-11-30  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix API version 21 and 22 compatibility for fuse_teardown.
+	Reported by Bgs
+
+2006-11-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: Print a more helpful message in case the kernel
+	doesn't support the 'fuseblk' filesystem type.  This has been
+	biting ntfs-3g users.  Reported by Yura Pakhuchiy
+
+	* kernel: fix build problem for "make -C ...".  Reported by
+	Stephen Bryant
+
+2006-11-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix bug in certain error paths of lookup routines.  The request
+	object was reused for sending FORGET, which is illegal.  This bug
+	could cause an Oops in linux-2.6.18 or in fuse-2.6.0, and might
+	silently corrupt memory in earlier versions.  Report and test
+	program by Russ Cox
+
+2006-11-11  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Print an error if an incompatible kernel interface version is
+	detected in INIT.  This will only show if filesystem is started
+	with -d or -f
+
+	* Fix order of fuse_destroy()/fuse_unmount() in error cleanup of
+	fuse_setup_common().  Reported by Szakacsits Szabolcs
+
+2006-11-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix recursive locking in fuse_create().  Thanks to Takuya
+	Ishibashi for the bug report
+
+2006-10-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix automake problem.  Patch from Nix
+
+2006-10-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix mount.fuse to use /bin/sh instead of /bin/bash, which is not
+	always available on embedded systems.  Patch from Paul Smith
+
+	* Fix util/Makefile.am, so that failure to run update-rc.d or
+	device creation doesn't cause make to fail.  Reported by Paul
+	Smith
+
+2006-10-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.6.0
+
+2006-10-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: don't try to create a lock file if /etc/mtab is a
+	symlink.  Report and patch from Alexei Sheplyakov (debian bug
+	#393693)
+
+2006-10-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Minor changes, sync with mainline tree
+
+2006-10-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.6.0-rc3
+
+2006-10-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: cleanups
+
+2006-10-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: Fix compilation on patched 2.6.18 (fc6) and 2.6.19.
+	Report from David Shaw
+
+	* lib: Fix lost error on renaming a file. Report from David Shaw
+
+	* lib: Fix lost error on hiding open files (renaming to
+	.fuse_hiddenXXXX)
+
+	* kernel: Fix a rare hang on SMP/32bit on heavy filesystem
+	activity.  The cause of the bug was that some calls to
+	i_size_write() were not protected by a lock, and hence
+	i_size_seqcount could become corrupted.  This caused subsequent
+	calls to i_size_read() to spin forever.  This is a long standing
+	bug was probably introduced in version 2.2, and thought to be
+	related to NFS exporting (it's not).  It was reported by various
+	people, but Dana Henriksen has finally helped me to track it down,
+	so big thanks to him
+
+	* kernel: Protect against truncation of a swapfile
+
+2006-10-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: Check for signature of super_operations->umount_begin().
+	Ubuntu kernel 2.6.17 seems to use the new signature found in
+	2.6.18.  Thanks to Florent Mertens for the report
+
+2006-10-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Make sure inode numers wrap around at 2^32.  This is needed on
+	dual 64bit/32bit architectures, because 32bit applications using
+	the non-largefile interface would otherwise break (EOVERFLOW error
+	would be returned by the stat() system call family)
+
+	* ulockmgr: handle the case, when a locking operation fails
+	because no more file desctriptors are available in
+	ulockmgr_server.  Also work around a Linux kernel bug (known to
+	exist for all Linux kernel versions <= 2.6.18) which may cause
+	sent file descriptors to be lost in the above case
+
+	* ulockmgr: optimize file descriptor use
+
+	* restore needed cpp flags to util/Makefile.am
+
+	* Install udev rules as 99-fuse.rules instead of 60-fuse.rules
+
+	* Minor clean up of udev rules
+
+	* Add a synchronous DESTROY message to kernel interface.  This is
+	invoked from umount, when the final instance of the filesystem is
+	released.  It is only sent for filesystems mounted with the
+	'blkdev' option for security reasons.
+
+	* If the DESTROY message is received, call the filesystem's
+	->destroy() method.  In this case it's not called from session
+	destruction as it would be otherwise.
+
+2006-10-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.6.0-rc2
+
+2006-10-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add support for FLUSH+RELEASE operation for FreeBSD.  Original
+	patch by Csaba Henk
+
+	* Add init script to insert fuse module and mount the control
+	filesystem.  The script is installed as /etc/init.d/fuse and on
+	debian based systems (where update-rc.d is available) symlinks
+	from /etc/rc*.d/ are also installed.
+
+	* Include '#define FUSE_USE_VERSION=XX' into examples so they
+	become more self contained.
+
+2006-09-30  Miklos Szeredi <miklos@szeredi.hu>
+
+	* API changes:
+
+	* Move lock_owner from a separate argument into fuse_file_info
+
+	* Add a flag to fuse_file_info indicating (1) a highlevel lock
+	operation (unlock all) was initiated by a flush, (2) a lowlevel
+	release operation should perform a flush as well.
+
+	* fusermount: revert modprobe change (2006-08-18) since it
+	doesn't work reliably with udev
+
+	* Add support for block device backed filesystems.  This mode is
+	selected with the 'blkdev' option, which is privileged.
+
+	* Add support for the bmap (FIBMAP ioctl) operation on block
+	device backed filesystems.  This allows swapon and lilo to work on
+	such filesystems.
+
+	* kernel changes:
+
+	* Drop support for kernels earlier than 2.6.9.  Kernel module from
+	previous (2.5.x) release can be used with library from this
+	release
+
+	* In fuse_dentry_revalidate() use dget_parent() instead of
+	dereferencing d_parent, since there's no protection against parent
+	changing and going away
+
+	* Protect nlookup from concurrent updates
+
+	* In lookup if a directory alias exists but is unused,
+	then get rid of it, otherwise return -EBUSY.
+
+	* In mkdir if a directory alias exists, return success, but leave
+	dentry negative.  In reality this could happen if a remote rename
+	immediately followed the mkdir.
+
+	* Don't BUG in fuse_iget() if multiple retries are needed to get a
+	good inode.  This could happen if several lookups are racing for
+	the same inode.
+
+2006-09-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix compilation on 2.6.9.  Report from Troy Ayers
+
+2006-09-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix Oops in fuse_readpages().  Reported by David Shaw
+
+2006-09-24  Csaba Henk <csaba.henk@creo.hu>
+
+	* Add support for nanosec times on FreeBSD
+
+	* Fix FreeBSD compatibility issues
+
+2006-09-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix one more compatibility bug.  Thanks to Ricardo Correia
+
+	* Fix utimens compilation with uClibc.  Patch from Jamie Guinan
+
+2006-09-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fixed several compatibility bugs in low level interface.
+	Reported by Ricardo Correia
+
+	* Add workaround for ARM caching bug
+
+2006-09-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Rename new utimes() method to more logical utimens()
+
+2006-09-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fuse tried to unlink already unlinked hidden files.  Bug
+	reported by Milan Svoboda
+
+2006-09-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.6.0-rc1
+
+2006-09-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: Fix unlock on close for kernels < 2.6.18
+
+	* Add ulockmgr library & server.  This can be used for handling
+	file locking requests either directly from libfuse or over a
+	network, etc.  This first version is not optimized and the number
+	of file descriptors it uses may get out of hand
+
+2006-09-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: Add interrupt support to high level library, which may be
+	enabled with the 'intr' mount option.
+
+	* When an operation is interrupted the thread handling that
+	operation will receive SIGUSR1 (or other signal specified with the
+	'intr_signal=N' option).  The library installs a no-op signal
+	handler for this signal, unless there's already a handler
+	installed.
+
+	* The filesystem may query interrupt status (regardless of 'intr')
+	with the fuse_interrupted() function.
+
+	* mount.fuse: initialize $HOME if not set.  Report from Sven Goldt
+
+2006-09-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: Multithreaded loop now allows unlimited number of threads.
+	This is needed for locking operations which may block
+	indefinitely.  Also the kernel now doesn't limit the number of
+	outstanding requests so the library shouldn't do so either.
+
+2006-09-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix recursive lock bug in interrupt handling
+
+	* Add utimes() method to highlevel interface, which supports
+	setting times with nanosecond resolution
+
+2006-08-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: fix page leak if fuse_readpages() failed in it's
+	initialization.  Bug found and original patch from Alexander
+	Zarochentsev
+
+	* For linux kernels >=2.6.18 (2.6.19 if using the fuse module from
+	the kernel tree) the statfs method will receive the path within
+	the filesystem on which the stat(v)fs syscall was called
+
+	* fusermount: try to modprobe fuse module if invoked by root and
+	unable to open device.  This is needed with udev, since the device
+	node will be created only when the module is inserted, hence
+	module autoloading won't work.  Reported by Szakacsits Szabolcs
+
+2006-07-30  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: if selinux is active, restore the original file's
+	security context in unmount_rename().  Redhat bugzilla id 188561.
+	Patch from Yves Perrenoud
+
+	* Add POSIX file locking operation to high level library
+
+	* Initialize context for unlink of hidden files on umount.  Bug
+	reported by Tim Stoakes
+
+2006-07-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Multiple release() calls can race with each other, resulting in
+	the hidden file being deleted before the last release finishes.
+	Bug found and patch tested by Mark Huijgen
+
+2006-07-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: if /dev/fuse doesn't exist, suggest modprobing fuse;
+	this makes sense on systems using udev.  Reported by Szakacsits
+	Szabolcs
+
+2006-06-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.6.0-pre3
+
+2006-06-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Support in kernel module for file locking and interruption.  The
+	same functionality is available in official kernels >= 2.6.18
+
+2006-06-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add POSIX file locking support
+
+	* Add request interruption
+
+2006-06-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add missing pthread_rwlock_destroy().  Patch from Remy Blank
+
+2006-06-05  Remy Blank <remy.blank@pobox.com>
+
+	* lib: canonicalize mount point in fuse_helper_opt_proc() so that
+	unmounting succeeds even if mount point was relative.
+
+2006-06-04  Csaba Henk <csaba.henk@creo.hu>
+
+	* lib: fix emergency umount in helper.c when malloc fails.
+	(The way it was done would end up in a segfault.)
+
+2006-06-01  Csaba Henk <csaba.henk@creo.hu>
+
+	* lib: adjust threading related compiler flags.
+	Switch to "-pthread" from "-lpthread" as that's the preferred
+	one on several platforms. Consulted with Terrence Cole and
+	Miklos Szeredi
+
+2006-05-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: search fusermount in installation directory (bindir) as
+	well as in PATH.
+
+2006-05-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: fix compilation if CLOCK_MONOTONIC is not defined.
+	Reported by Christian Magnusson
+
+2006-04-23  Csaba Henk <csaba.henk@creo.hu>
+
+	* lib: make FreeBSD mount routine recognize if kernel features
+        backgrounded init and if it does, run the mount util in foreground
+        (similarly to Linux)
+
+2006-04-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: fix fput deadlock fix, the lockless solution could lead
+	to "VFS: busy inodes after umount..."
+
+	* kernel: fix race between checking and setting file->private_data
+	for the device.  Found by Al Viro
+
+2006-04-11  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: remove request pool, instead allocate requests on
+	demand.  Account the number of background requests, and if they go
+	over a limit, block the allocation of new requests.
+
+	* kernel: fix deadlock if backgrounded request holds the last
+	reference to the super block
+
+	* kernel: don't use fuse_reset_request() during direct I/O
+
+2006-04-06  Csaba Henk <csaba.henk@creo.hu>
+
+	* lib: Let FreeBSD mount option parsing routine recognize "no"
+	prefixes for FUSE specific options as well
+
+2006-04-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: Add missing rwlock initialization.  Patch by Ryan Bradetich
+
+2006-03-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* API changes:
+
+	* fuse_main(), fuse_setup() and fuse_new() have an additionl
+	user_data parameter
+
+	* fuse_mount() returns a 'struct fuse_chan' pointer instead of a
+	file descriptor
+
+	* fuse_unmount() receives a 'struct fuse_chan' pointer.  It
+	destroys the given channel
+
+	* fuse_teardown() no longer has a file descriptor parameter
+
+	* new exported functions: fuse_session_remove_chan(),
+	fuse_get_session(), fuse_daemonize()
+
+	* fuse_chan_recv() may now return a new channel which will be used
+	to send the reply
+
+2006-03-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.6.0-pre2
+
+2006-03-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Don't unmount if already unmounted.  This fixes a problem seen
+	in the following situation: Lazy unmount a busy filesystem; Mount
+	a new one in top; When the first finally unmounts, the second also
+	unmounts.  Reported by Franco Broi
+
+2006-03-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lowlevel lib: use indirect function calls instead of a
+	switch/case construct.  Besides increased efficiency it helps
+	maintainability & readability too.  Patch from Florin Malita
+
+2006-03-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: replace global spinlock with a per-connection spinlock
+
+2006-03-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix source compatibility breakage for fuse_unmount().  Report
+	from Yura Pakhuchiy
+
+2006-03-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix O_ASYNC handling in fuse_dev_release().  From Jeff Dike
+
+2006-03-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add O_ASYNC and O_NONBLOCK support to FUSE device.  Patch by
+	Jeff Dike
+
+	* Renamed fuse_chan_receive() to fuse_chan_recv() and changed
+	interface to return -errno in case of error.
+
+2006-03-01  Csaba Henk <csaba.henk@creo.hu>
+
+	* libfuse: pass device file descriptor to fuse_unmount(), rewrite
+	FreeBSD implementation so that it uses libc (sysctl backed) instead
+	of an embdedded script (kmem backed). Adjust the control flow of
+	hello_ll so that device doesn't get closed before unmount attempt.
+
+2006-02-25  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Lowlevel lib: return all-zero statvfs data if filesystem doesn't
+	implement method.  This is needed on FreeBSD, and nicer on Linux
+	too.  Highlevel lib already did this.  Reported by Csaba Henk
+
+	* Fix negative entry handling.  There was a bug, that negative
+	lookups with timeouts (nodeid == 0) returned -EIO.
+
+2006-02-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix race between RELEASE and UNLINK, which might leave
+	.fuse_hidden* files around
+
+2006-02-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusexmp_fh: implement flush() method and call close() on the
+	open file descriptor.  This is needed if used on an NFS
+	filesystem, which buffers data until file is closed.  Franco Broi
+	spotted the situation when 'cp -p' failed to set the modification
+	time because of this.
+
+2006-02-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.6.0-pre1
+
+2006-02-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: fix use-after-free bug in interruptred reply_entry().
+	Patch from John Muir
+
+	* libfuse: fix wrong symbol versioning for fuse_mount.  Debian bug
+	ID: 352631.  Found by Stéphane Rosi
+
+2006-02-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Lowlevel lib: Unify fuse_dirent_size() and fuse_add_dirent()
+	into a single function fuse_add_direntry().  This cleans up the
+	interface and makes it possible to do stacking.
+
+2006-02-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix rare race betweeen abort and release caused by failed iget()
+	in fuse_create_open().
+
+	* Add 'ac_attr_timeout' option e.g. for filesystems which do their
+	own attribute caching.
+
+2006-02-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Work around FreeBSD runtime linker "feature" which binds an old
+	version of a symbol to internal references if the symbol has more
+	than one version.  This resulted in infinite recursion in
+	fuse_lowlevel_new_compat25().
+
+2006-02-10  Csaba Henk <csaba.henk@creo.hu>
+
+	* Refine clock_gettime() querying so that linker options
+	shall be set as it's appropriate for the target platform.
+
+2006-02-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix udev rule syntax.  Reported by Nix
+
+2006-02-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* In some cases udev rule seems to be ineffective when installed
+	as 40-fuse.rules but work as 60-fuse.rules.  Reported by John Hunt
+
+2006-02-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix compilation when build directory is different from source
+	directory.  Reported by Frédéric L. W. Meunier
+
+2006-02-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix even bigger bug introduced in fix for request_end() on
+	2006-01-14.  Reported by Gal Rosen
+
+2006-01-30  Miklos Szeredi <miklos@szeredi.hu>
+
+	* highlevel-lib: add 'auto_cache' option.  This caches file data
+	based on modification time and size
+
+2006-01-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Sanitize storage type and help message in mount_bsd.c.  Patch
+	from Csaba Henk
+
+	* fuse_opt: add new helper constants FUSE_OPT_KEY_KEEP and
+	FUSE_OPT_KEY_DISCARD
+
+	* Add options 'max_readahead', 'sync_read' and 'async_read'
+
+	* Kernel ABI version 7.6:
+
+	* Negotiate the 'max_readahead' value and 'async_read' flags with
+	userspace in the INIT method
+
+	* Add connection info to ->init() methods to both lowlevel and
+	highlevel API
+
+	* Fall back to synchronous read() behavior if either library or
+	userspace filesystem is using the old interface version.  This is
+	needed so non-updated filesystems won't be confused by the
+	different read() behavior
+
+2006-01-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: if "fsname=" option was given, pass it to fusermount
+
+	* fuse_opt: add new fuse_opt_insert_arg() function, which is
+	needed by filesystems to implement some argument manipulations
+	correctly
+
+	* fuse_opt: fix memory leak in handling "--" option
+
+2006-01-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: fix detection of case when fuse is not configured into
+	the kernel either as module or built-in
+
+	* fuse_opt.h: fix incompatibility with C++ compilers by renaming
+	'template' structure member to 'templ'.  Reported by Takashi Iwai
+
+	* fuse.h: fix compatibility bugs.  Patch by Yura Pakhuchiy
+
+	* kernel: support version 2.6.16 (i_sem -> i_mutex)
+
+2006-01-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added (again) asynchronous readpages support
+
+	* Each connection now shows up under /sys/fs/fuse/connections
+
+	* Connection attributes exported to sysfs: 'waiting' number of
+	waiting requests; 'abort' abort the connection
+
+	* Connection may be aborted through either the sysfs interface or
+	with 'umount -f mountpoint'
+
+2006-01-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.5.0
+
+2006-01-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: fix a couple of bugs
+
+	* Order of request_end() and fuse_copy_finish() was wrong.
+	Posthumous note: Franco Broi managed to exploit this, though it
+	seemed quite impossible
+
+	* request_end() used request pointer after decrementing refcount
+
+	* Clearing ->connected or ->mounted connection flags could race
+	with setting other bitfields not protected with a lock
+
+2006-01-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: add necessary compile flags for 2.4.X/x86_64.
+	Report from Sean Ziegeler
+
+2006-01-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.5.0-pre2
+
+2006-01-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Applied patch from Csaba Henk, to update mount_bsd to new
+	fuse_mount() semantics
+
+	* Ignore auto,noauto,... options in mount.fuse.  Reported by Frank
+	Steiner and Don Taber
+
+	* fusermount: add 'dirsync' mount option
+
+2006-01-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Improved help reporting and added version reporting to library
+
+2006-01-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Change working directory to "/" even if running in the
+	foreground.  Patch from Jonathan Brandmeyer
+
+	* Changed lots of functions to use 'struct fuse_args' instead of
+	separate argc and argv
+
+	* Added fuse_parse_cmdline(), fuse_set_signal_handlers() and
+	fuse_remove_signal_handlers() functions, so that it's now pretty
+	easy to get all the functionality of fuse_main() with a filesystem
+	using the lowlevel API.
+
+2006-01-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* mount.fuse: the 'user' option should be ignored. Report and
+	solution from Mattd.
+
+	* mount.fuse: export PATH in the right place. Report and patch
+	from Hannes Schweizer
+
+2005-12-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Clean up the option parsing interface slightly, by creating an
+	"argument list" structure, that contains the argument vector and
+	count
+
+2005-12-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: check if /mnt/mtab is a symlink and don't modify it
+	in that case
+
+	* kernel: simplify request size limiting. INIT only contains
+	maximum write size, maximum path component size remains fixed at
+	1024 bytes, and maximum xattr size depends on read buffer.
+
+2005-12-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix readdir() failure on x86_64, of 32bit programs compiled
+	without largefile support.  Bug report and help from Anthony
+	Kolasny
+
+	* If lookup returns invalid mode, return -EIO instead of creating
+	a regular file
+
+	* Add current output argument vector to option processing
+	function
+
+2005-12-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix stale code in ifdef FreeBSD.  Patch from Csaba Henk
+
+2005-12-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.5.0-pre1
+
+2005-12-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: added option parsing interface, defined in
+	<fuse_opt.h>.
+
+2005-12-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Return EIO for file operations (read, write, fsync, flush) on
+	open files whose inode has become "bad".  Inodes will be marked
+	"bad" if their type changes.  Bug report by Csaba Henk
+
+2005-12-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Use bigger request buffer size.  write() did not work on archs
+	with > 4k page size, Bug report by Mark Haney
+
+	* ABI version 7.5:
+
+	* Extend INIT reply with data size limits
+
+2005-12-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix memory leak in fuse_read_cmd()/fuse_process_cmd().  Bug
+	reported by Vincenzo Ciancia
+
+	* Handle exit-by-umount in fuse_read_cmd()
+
+2005-11-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check if '-msoft-float' option is supported by compiler when
+	configuring for a 2.4.x kernel.  Bug report by Mark Haney
+
+	* In multithreaded loop send a TERM signal to the main thread if
+	one of the other threads exit.  Needed on FreeBSD for a clean exit
+	on umount.  Should not cause any harm on Linux either
+
+2005-11-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix bug in 32-bit file handle compatibility
+
+2005-11-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Block TERM, INT, HUP and QUIT signals in all but the main
+	thread.  According to POSIX it's not specified which thread will
+	receive these signals.
+
+	* Kernel changes:
+
+	* Check for directory aliasing on mkdir, not just on lookup
+
+	* Check for special node ID values in create+open operation
+
+	* Sync with -mm: readv, writev, aio_read and aio_write methods
+	added to file operations
+
+	* Cleanups: lookup code, page offset calculation
+
+	* ABI stepped to 7.4, changes:
+
+	* frsize member added to fuse_kstatfs structure
+
+	* added support for negative entry caching: on lowlevel API if
+	fuse_entry_param::ino is set to zero in reply to a lookup request,
+	the kernel will cache the dentry for the specified amount of time.
+
+	* libfuse: added 'negative_timeout' option: specifies how much
+	negative entries should be cached.  Default is zero, to be
+	compatible with prior versions
+
+2005-11-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add detection of mainline FUSE code in running kernel
+
+2005-11-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Don't use async cancelation in multithreaded loop.  This makes
+	it more portable to systems where read() is not async cancel safe.
+	Report from Andriy Gapon
+
+2005-11-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Warn if API version 11 compatibility is requested
+
+2005-11-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* More FreeBSD merge
+
+	* fusermount: don't allow mountpoints with '\n', '\t', or '\\' in
+	them, because it corrupts /etc/mtab.  Found by Thomas Biege
+	CVE-2005-3531
+
+	* libfuse: don't use system() to invoke 'fusermount -u ...'
+	because it breaks mountpoints with spaces in them into multiple
+	arguments
+
+2005-11-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Merge library part of FreeBSD port.  Patch by Csaba Henk
+
+2005-11-11  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Use 64bit type for file handle, so the full range supported by
+	the kernel interface is available to applications
+
+2005-11-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Moved mountpoint argument checking from fuse_parse_cmdline() to
+	fuse_mount() in preparation to FreeBSD merge.
+
+2005-11-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Remove unneeded close() from fuse_teardown().  Spotted by Csaba
+	Henk.
+
+2005-11-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Make the statfs change backwards compatible.
+
+2005-11-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Change ->statfs() method to use 'struct statvfs' instead of
+	'struct statfs'.  This makes the API more portable since statvfs()
+	is defined by POSIX.
+
+2005-10-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add fgetattr() method, which currently will only be called after
+	a successful call to a create() method.
+
+2005-10-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Change kernel ABI version to 7.3
+
+	* Add ACCESS operation.  This is called from the access() system
+	call if 'default_permissions' mount option is not given, and is
+	not called on kernels 2.4.*
+
+	* Add atomic CREATE+OPEN operation.  This will only work with
+	2.6.15 (presumably) or later Linux kernels.
+
+	* Add ftruncate() method.  This will only work with 2.6.15
+	(presumably) or later Linux kernels.
+
+	* Fix kernel module compile if kernel source and build directories
+	differ.  Report and initial patch by John Eastman
+
+2005-10-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: optimize buffer reallocation in fill_dir.
+
+2005-10-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.4.1
+
+2005-10-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: add debug for write result (by Shaun Jackman) and
+	warnings for too large read/write result
+
+2005-10-11  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Spelling fixes, thanks to Ioannis Barkas
+
+2005-10-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fuse_common.h: use extern "C".  Thanks to Valient Gough for the
+	patch
+
+2005-10-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* highlevel-lib: init() and destroy() methods didn't have an
+	initialized fuse_context.  Bug reported by Tim Stoakes
+
+2005-10-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.4.0
+
+2005-10-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add documentation to fuse_lowlevel.h
+
+	* API cleanups:
+
+	* Remove definitions of unused FATTR_CTIME / FUSE_SET_ATTR_CTIME
+
+	* Move fuse_mount() and fuse_unmount() to fuse_common.h
+
+	* Change the return type of fuse_reply_none() from int to void.
+
+2005-09-30  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: NFS exporting leaked dentries.  Bug found and fixed by
+	Akshat Aranya.
+
+2005-09-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: fix error message, when unable to open /dev/fuse.
+	Report by Balázs Pozsár
+
+2005-09-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* UClibc fixes from Christian Magnusson
+
+2005-09-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added NAME="%k" to util/udev.rules.  Fix by Mattias Wadman.
+
+2005-09-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.4.0-rc1
+
+2005-09-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: allow user umount in the case when /etc/mtab is a
+	symlink to /proc/mounts.  Reported by Balázs Pozsár.
+
+2005-09-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check for special node ID values in lookup and creation
+
+2005-09-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Slight optimization in returning EINVAL error in case of an open
+	with O_DIRECT flag.
+
+2005-09-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Remove '--enable-auto-modprobe' configure flag.  Module
+	auto-loading is now handled by the kernel.
+
+2005-09-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Install UDEV rule file, so /dev/fuse is created with mode 0666.
+	Help from Jens M. Noedler.
+
+2005-09-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add memory cleanup on thread exit
+
+2005-09-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Set umask to zero in fusexmp and fusexmp_fh, so that
+	files/directories are created with the requested mode.
+
+2005-09-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Don't ignore read error in multithreaded loop
+
+2005-09-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.4.0-pre2
+
+2005-09-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Revert lock and access operations.  Postpone these until 2.5.
+
+2005-09-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix compile warning on 2.6.13 and later
+
+	* Fix compilation on old kernels
+
+2005-08-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: always refresh directory contents after rewinddir() to
+	conform to SUS.  Bug found by John Muir.
+
+2005-08-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.4.0-pre1
+
+2005-08-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: cleaned up (or messed up, depending on your POV) the low
+	level library API.  Hopefully this is close to the final form.
+
+2005-08-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: don't allow empty mountpoint argument, which defeats
+	automatic umounting in fuse_main().  Bugreport by Václav Jůza
+
+2005-08-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fix warnings in fuse.h and fuse_lowlevel.h if -Wshadow compiler
+	option is used (Paul Alfille).
+
+2005-08-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* highlevel-lib: added mount options "attr_timeout" and
+	"entry_timeout".  These options control the length of time file
+	attributes and entries (names) are cached.  Both default to 1.0
+	second.
+
+	* kernel: correctly handle zero timeout for attributes and entries
+
+2005-08-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added missing symbols to versionscript (Joshua J. Berry)
+
+	* kernel: implement two flags, open can set: 'direct_io' and
+	'keep_cache'.  These correspond exactly to mount options
+	'direct_io' and 'kernel_cache', but allow a per-open setting.
+
+	* Move 'direct_io' and 'kernel_cache' mount option handling to
+	userspace.  For both mount options, if the option is given, then
+	the respective open flag is set, otherwise the open flag is left
+	unmodified (so the filesystem can set it).
+
+	* lib (highlevel): make open method optional
+
+2005-07-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: invalidate attributes for read/readdir/readlink
+	operations
+
+	* kernel: detect newer UML kernels
+
+2005-07-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Make the installation path of fuse.ko and mount.fuse
+	configurable through INSTALL_MOD_PATH and MOUNT_FUSE_PATH
+	environment variables.  Requirement and help from Csaba Henk.
+
+2005-07-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix bug, that causes filesystem requests to hang when unique
+	request counter becomes negative.  This happens after
+	2,147,483,648 operations, so most people won't care.  Thanks to
+	Franco Broi for the report and testing.
+
+2005-07-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Don't change mtime/ctime/atime to local time on read/write.
+	Bug reported by Ben Grimm
+
+	* Install fuse_common.h and fuse_lowlevel.h.  Report by Christian
+	Magnusson
+
+	* fusermount: use getopt_long() for option parsing.  It allows the
+	use of '--' to stop argument scanning, so fusermount can now
+	operate on directories whose names begin with a '-'.  Patch by
+	Adam Connell
+
+2005-07-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: add '-v', '--version' and '--help' options
+
+	* add inode based API
+
+2005-07-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: don't block signals in worker threads.  Problem noticed by
+	Usarin Heininga
+
+2005-07-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: don't allow both 'allow_other' and 'allow_root' options to
+	be given
+
+2005-07-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: check if mountpoint is empty (only '.' and '..' for
+	directories, and size = 0 for regular files).  If "nonempty"
+	option is given, omit this check.  This is useful, so users don't
+	accidentally hide data (e.g. from backup programs).  Thanks to
+	Frank van Maarseveen for pointing this out.
+
+	* kernel: check if mandatory mount options ('fd', 'rootmode',
+	'user_id', 'group_id') are all given
+
+	* lib: simplify 'readdir_ino' handling
+
+	* lib: add mount options 'umask=M', 'uid=N', 'gid=N'
+
+2005-07-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: clean up 'direct_io' code
+
+2005-06-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add 'mount.fuse' written by Petr Klima
+
+	* '/dev/fuse' is created by 'make install' if does not yet exist
+
+2005-06-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix UCLIBC compile error.  Patch by Christian Magnusson
+
+2005-06-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Enable the auto-loading of the module via access to the
+	corresponding device file.  Patch by Takashi Iwai.
+
+	* Allow mounting a regular file (over a regular file) for
+	unprivleged users.
+
+	* Do not create temporary device file.  Require "/dev/fuse" to
+	exist, and be readable/writable by the mounting user.
+
+2005-06-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.3.0
+
+2005-06-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix serious information leak: if the filesystem returns a short
+	byte count to a read request, and there are non-zero number of
+	pages which are not filled at all, these pages will not be zeroed.
+	Hence the user can read out previous memory contents.  Found by
+	Sven Tantau.
+
+2005-05-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add "readdir_ino" mount option, which tries to fill in the d_ino
+	field in struct dirent.  This mount option is ignored if "use_ino"
+	is used.  It helps some programs (e.g. 'pwd' used over NFS from a
+	non-Linux OS).  Patch by David Shaw.
+
+2005-05-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.3-rc1
+
+2005-05-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* File save in krusader and other editors doesn't work with sshfs,
+	because open() is interrupted by a periodic signal, and open()
+	restarts forever, without any progress.  This could just be fixed
+	in open(), but the problem is more generic: if signals are
+	received more often than the filesystem can get the request to
+	userspace, it will never finish.  This is probably only a
+	theoretical problem, nevertheless I'm removing the possibility to
+	interrupt requests with anything other than SIGKILL, even before
+	being sent to userspace.  Bugreport by Eduard Czimbalmos.
+
+2005-05-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: add "tree_lock" rwlock, that is locked for write in
+	rename, unlink and rmdir, and locked for read in all other
+	operations.  This should fix the rename/release race reported by
+	Valient Gough and others.  The solution is very coarse, a finer
+	grained locking scheme could be implemented, but it would be much
+	more complex.  Let's see whether this is good enough.
+
+2005-05-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.3-pre7
+
+2005-05-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Better fix for out of order FORGET messages.  Now the
+	LOOKUP/FORGET messages are balanced exactly (one FORGET can
+	balance many lookups), so the order no longer matters.  This
+	changes the kernel ABI slightly, but the library remains backward
+	compatible.
+
+2005-05-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix abort for out of order FORGET messages.  Again.  Spotted by
+	Franco Broi again.  Sorry :)
+
+2005-04-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.3-pre6
+
+2005-04-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Make fusermount work with fuse kernel modules not yet supporting
+	the "group_id" option (added for the purpose of stricter
+	permission checking).
+
+2005-04-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check for hard-linked directories in lookup.  This could cause
+	problems in the VFS, which assumes that such objects never exist.
+
+	* Make checking of permission for other users more strict.  Now
+	the same privilege is required for the mount owner as for ptrace
+	on the process performing the filesystem operation.
+
+2005-04-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.3-pre5
+
+2005-04-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add -msoft-float to kernel module compile flags for 2.4.X.  This
+	is needed on certain architectures.  Report from Chris Kirby
+
+	* Fix buggy behavior of open(..., O_CREAT|O_EXCL) if interrupted.
+	Reported by David Shaw
+
+	* Remove "allow_root" option from kernel module, and implement
+	it's functionality in the library
+
+	* Fix Oops caused by premature release of fuse_conn.  Clean up
+	related code, to be more readable
+
+	* Sendfile should not use page cache if "direct_io" mount option
+	is given
+
+2005-04-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix Oops in case of nfs export.  Spotted by David Shaw
+
+	* Fix another Oops in case of write over nfs with direct_io turned
+	on.  Again spotted by David Shaw
+
+2005-04-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.3-pre4
+
+2005-04-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* lib: finalized new readdir() interface, which now supersedes the
+	getdir() method.
+
+2005-04-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.3-pre3
+
+2005-04-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Implement backward compatibility with version 5 kernel ABI
+
+2005-04-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.3-pre2
+
+2005-04-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: fix dirent offset handling
+
+	* lib: add readdir and releasedir methods
+
+	* lib: use fh field of fuse_file_info in opendir, readdir,
+	releasedir and fsyncdir methods
+
+	* lib: check kernel API version and bail out of it's old.  This
+	will be properly fixed in the next release
+
+2005-03-31  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.3-pre1
+
+2005-03-31  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel API: add padding to structures, so 64bit and 32bit
+	compiler will return the same size
+
+	* kernel API: add offset field to fuse_dirent.  This will allow
+	more sophisticated readdir interface for userspace
+
+	* kernel API: change major number to 6
+
+	* kernel: fix warnings on 64bit archs
+
+	* kernel: in case of API version mismatch, return ECONNREFUSED
+
+2005-03-24  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: trivial cleanups
+
+2005-03-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add fsyncdir() operation
+
+2005-03-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: add locking to background list (fixes previous fix)
+
+2005-03-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: fix bug which could cause leave busy inodes after
+	unmount, and Oops.
+
+2005-03-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* examples: add -lpthread to link flags to work around valgrind
+	quirk
+
+	* lib: don't exit threads, so cancelation doesn't cause segfault
+
+2005-03-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: fix nasty bug which could cause an Oops under certain
+	situations.  Found by Magnus Johansson
+
+2005-02-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: added opendir() method.  This can be used in case
+	permission checking in getdir() is too late.  Thanks to Usarin
+	Heininga for pointing out this deficiency
+
+	* libfuse: added init() and destroy() methods to fuse_operations
+
+	* kernel: llseek() method for files and directories made explicit
+
+	* kernel: fixed inode leak in NFS export in case of nodeid
+	wrapping
+
+2005-02-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse: clean up some unitialized memory found with valgrind
+
+	* Add -lpthread to Libs in fuse.pc.  Valgrind seems to need an
+	explicitly linked libpthread for applications
+
+2005-02-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: set umask, otherwise /etc/mtab will have
+	unpredictable permission.  Spotted by Jindrich Kolorenc
+
+	* fusermount: set owner and group of /etc/mtab to original values
+	on unmount
+
+	* libfuse: add 'use_ino' option to help.  Patch by Valient Gough
+
+2005-02-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Cleaned up directory reading (temporary file is not used)
+
+2005-02-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.2
+
+2005-02-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix possible race when operation is interrupted
+
+2005-01-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix compilation on 2.6.7
+
+2005-01-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.2-pre6
+
+2005-01-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix bug in link() operation which caused the wrong path to be
+	passed as the first argument.  Found by Anton Altaparmakov
+
+2005-01-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* LIB: fix double reply in readdir operation
+
+	* fusermount: fix uid checking bug.  Patch by Adam Connell
+
+	* KERNEL: fix compile on various RedHat patched 2.4 kernels.
+	Patch by Keshava Gowda
+
+2005-01-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* KERNEL: provide correct llseek semantics for fuse device (fixes
+	a bug on Progeny 2.4.20 kernel).  Reported by Valient Gough
+
+2005-01-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.2-pre5 (matches kernel 2.6.11-rc1-mm2)
+
+2005-01-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* KERNEL ABI: remove GETDIR operation, and add OPENDIR, READDIR
+	and RELEASEDIR.  This ends the ugly hack of passing a file
+	descriptor to the kernel, and actually makes the code simpler.
+
+2005-01-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.2-pre4
+
+2005-01-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: remove capability setting, which was the cause of
+	problems for some users.  It seems that FS related capabilities
+	are removed by setfsuid(), so this isn't even needed.
+
+2005-01-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fix compilation on 2.4 kernels (reported by Valient Gough)
+
+	* fix failure to unmount bug (found by David Shaw)
+
+	* fusermount: improve parsing of /etc/fuse.conf
+
+2005-01-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Remove 'mount_max' and 'user_allow_other' module options.  These
+	are now checked by fusermount, and can be set in /etc/fuse.conf
+
+	* KERNEL: change check for fsid == 0 to capable(CAP_DAC_OVERRIDE)
+
+2005-01-11  Miklos Szeredi <miklos@szeredi.hu>
+
+	* KERNEL: fix possible inode allocation problem, where
+	sizeof(struct inode) is not aligned (found by Mike Waychison)
+
+	* KERNEL: use new follow_link/put_link methods
+
+	* KERNEL: cosmetic fixes
+
+2005-01-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.2-pre3
+
+2005-01-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add missing code that was accidently left out
+
+2005-01-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.2-pre2
+
+2005-01-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Change "uid" mount option to "user_id" to avoid confusion with a
+	mount option "uid" commonly used by many filesystems
+
+2005-01-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.2-pre1
+
+2005-01-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* If FUSE is configured in the kernel, don't build it by default
+
+2005-01-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Compile fix by Christian Magnusson
+
+2005-01-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix compilation for 2.6.{0-5} kernels
+
+2005-01-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* KERNEL: if request is interrupted, still keep reference to used
+	inode(s) and file, so that FORGET and RELEASE are not sent until
+	userspace finishes the request.
+
+	* remove /{sys,proc}/fs/fuse/version, and instead add an INIT
+	request with the same information, which is more flexible,
+	simpler, works on embedded systems.
+
+2004-12-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* KERNEL ABI: update interface to make it independent of type
+	sizes.  This will help on 64 bit architectures which can run
+	legacy 32 bit applications.
+
+	* KERNEL ABI: add "len" field to request headers.  This will allow
+	sending/receiving requests in multiple chunks.
+
+	* KERNEL: handle file type change more intelligently
+
+	* LIB: "-o debug" option should disable backgrounding (fix by
+	Fabien Reygrobellet)
+
+2004-12-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* KERNEL: invalidate dentry/attributes if interrupted request
+	could leave filesystem in an unknown state.
+
+2004-12-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* KERNEL: lots of cleanups related to avoiding possible deadlocks.
+	These will cause some regressions, but stability is considered
+	more important.  If any of these features turns out to be
+	important, it can be readded with the deadlock problems addressed.
+
+	* Make all requests interruptible (only with SIGKILL currently).
+	This can be used to break any deadlock produced by the userspace
+	filesystem accessing it's own exported files.  The RELEASE request
+	is special, because if it's interrupted before sending it to
+	userspace it is still sent, but the reply is not awaited.
+
+	* If request is interrupted before being sent to userspace, and if
+	it hasn't yet got any side effects, it is always restarted,
+	regardless of the SA_RESTART flag.  This makes these interruptions
+	transparent to the process.
+
+	* Remove shared-writable mmap support, which was prone to an
+	out-of-memory deadlock situation
+
+	* Remove INVALIDATE userspace initiated request
+
+	* Make readpages() synchronous.  Asynchronous requests are
+	deadlock prone, since they cannot be interrupted.
+
+	* Add readv/writev support to fuse device operations
+
+	* Remove some printks, which userspace FS can use for a DoS
+	against syslog
+
+	* Remove 'large_read' mount option from 2.6 in kernel, check it in
+	fusermount instead
+
+	* LIB: improve compatibility with a fuse.h header installed in
+	${prefix}/include which in turn includes the real header.
+
+	* LIB: improve compatibility by defining fuse_main() (which is now
+	not used), so old configure scripts find it.
+
+2004-12-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* When mounting on a subdirectory of / don't duplicate slashes at
+	the beggining of path (spotted by David Shaw)
+
+2004-12-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix bug causing garbage in mount options (spotted by David Shaw)
+
+2004-12-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add 'writepage' flag to 'fuse_file_info'.
+
+	* More comments in fuse.h
+
+	* Get rid of double underscores
+
+2004-12-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add -D_FILE_OFFSET_BITS=64 to cflags provided by pkg-config
+
+	* helper.c: add -ho option, which only displays the options not
+	the usage header.  This can be used by filesystems which have
+	their own options.
+
+2004-12-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add source compatibility to 2.1 and 1.1 APIs.  To select betwen
+	versions simply define FUSE_USE_VERSION to 22, 21 or 11 before
+	including the fuse header
+
+	* Add binary compatibility to 2.1 version of library with symbol
+	versioning
+
+2004-12-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.1
+
+2004-12-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: clean up writing functions
+
+	* kernel: no allocation on write in direct_io mode
+
+	* move linux/fuse.h to fuse_kernel.h
+
+2004-11-30  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: clean up reading functions
+
+2004-11-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel: make readpage() uninterruptible
+
+	* kernel: check readonly filesystem flag in fuse_permission
+
+	* lib: don't die if version file not found and new style device
+	exists
+
+	* lib: add '-r' option, which is short for '-o ro'
+
+	* fusermount: simplify device opening
+
+	* kernel: when direct_io is turend on, copy data directly to
+	destination without itermediate buffer.  More efficient and safer,
+	since no allocation is done.
+
+	* fusermount: fix warning if fuse module is not loaded
+
+	* kernel: use /dev/fuse on 2.4 too
+
+2004-11-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* libfuse API change: open, read, write, flush, fsync and release
+	are passed a 'struct fuse_file_info' pointer containing the open
+	flags (open and release), and the file handle.  Verion changed to
+	3.0.
+
+2004-11-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* More cleanups in the kernel
+
+	* The 10,229 charater device number has been assigned for FUSE
+
+	* Version file checking fix (reported by Christian Magnusson)
+
+	* fusermount: opening the fuse device now doesn't need /sys.
+
+	* Optimize reading by controlling the maximum readahead based on
+	the 'max_read' mount option
+
+	* fixes for UCLIBC (Christian Magnusson)
+
+2004-11-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Cleaned up kernel in preparation for merge into mainline:
+
+	* Use /sys/fs/fuse/version instead of /proc/fs/fuse/version
+
+	* Use real device (/dev/fuse) instead of /proc/fs/fuse/dev
+
+	* __user annotations for sparse
+
+	* allocate individual pages instead of kmalloc in fuse_readdir,
+	fuse_read and fuse_write.
+
+	* Fix NFS export in case "use_ino" mount option is given
+
+	* Make libfuse and fusermount compatible with future versions
+
+	* fusermount: properly add mount options to /etc/mtab
+
+2004-11-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fusermount: do not resolve last component of mountpoint on if it
+	is '.' or '..'.  This new path resolvation is now done on mount as
+	well as unmount.  This enables relative paths to work on unmount.
+
+	* fusermount: parse common mount options like "ro", "rw", etc...
+
+	* Allow module params to be changed through sysfs
+
+2004-11-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 2.1-pre1
+
+2004-11-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix bug in fuse_readpages() causing Oops in certain situations.
+	Bug found by Vincenzo Ciancia.
+
+	* Fix compilation with kernels versions > 2.6.9.
+
+2004-11-11  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check kernel interface version in fusermount to prevent
+	strangeness in case of mismatch.
+
+	* No need to allocate fuse_conn until actual mount happens
+
+	* Fix potential race between umount and fuse_invalidate
+
+	* Check superblock of proc file in addition to inode number
+
+	* Fix race between request_send_noreply() and fuse_dev_release()
+
+2004-11-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Separate configure for the kernel directory
+
+	* Don't allow write to return more than 'count'
+
+	* Extend kernel interface for future use
+
+2004-11-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix 'makeconf.sh' to use autoreconf if available
+
+2004-11-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add ino argument to 'fuse_dirfil_t'.  NOTE: This breaks source
+	compatibility with earlier versions.  To compile earier versions
+	just add '-DFUSE_DIRFIL_COMPAT' compile flag or fix the source.
+	Do not use the "use_ino" mount flag with filesystems compiled with
+	FUSE_DIRFIL_COMPAT.
+
+	* Add pkg-config support.  To compile a FUSE based filesystem you
+	can do  "gcc -Wall `pkg-config --cflags --libs fuse` myfs.c -o myfs"
+	or similar.  Note, that the PKG_CONFIG_PATH environment variable
+	usually needs to be set to "/usr/local/lib/pkgconfig".
+
+	* fuse.h is now installed in ${prefix}/include/fuse/
+
+2004-11-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added "use_ino" mount option.  This enables the filesystems to
+	set the st_ino field on files
+
+2004-11-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix compile problems with ancient (<=2.4.18) kernels (reported
+	by Jeremy Smith)
+
+	* Add "allow_root" mount option.  Patch by Yaroslav Rastrigin
+
+	* Clear the 'exited' flag when mail loop is finished
+
+2004-10-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Make xattr functions work under 2.6 (bug found by Vincenzo
+	Ciancia)
+
+2004-10-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Reset request in fuse_flush() (bugreport by David Shaw)
+
+2004-10-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fuse_main() now does not exit on error, rather it returns an
+	error code
+
+	* Exported __fuse_setup() and __fuse_teardown() functions, which
+	make it easier to implement a custom event loop.
+
+	* Use daemon() call to background the filesystem after mounting.
+	This function closes the standard input, output and error and
+	changes the current working directory to "/".
+
+2004-10-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 1.9
+
+2004-10-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Don't allow fuse_flush() to be interrupted (bug found by David
+	Shaw)
+
+2004-09-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add PID to fuse_context.  Patch by Steven James
+
+	* Change file handle type to 'unsigned long' in kernel interface
+
+2004-09-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* A slight API change: fuse_get_context() doesn't need the "fuse"
+	pointer, but the returned context contains it instead.  The
+	fuse_get() function is not needed anymore, so it's removed.
+
+	* Fix mounting and umounting FUSE filesystem under another FUSE
+	filesystem by non-root (bug spotted by Valient Gough)
+
+2004-09-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix deadlock in case of memory allocation failure.  Patch by
+	Christian Magnusson
+
+2004-09-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check memory allocation failures in libfuse
+
+2004-09-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check temporary file creation failure in do_getdir().  Bug
+	spotted by Terje Oseberg
+
+2004-09-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Allow "large_read" option for 2.6 kernels but warn of deprecation
+
+	* Make requests non-interruptible so race with FORGET is avoided.
+	This is only a temporary solution
+
+	* Support compiling FUSE kernel module on 2.4.x UML kernels
+
+2004-09-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix bug in case two FORGETs for the same node are executed in
+	the wrong order.  Bug spotted and endured for months by Franco
+	Broi, and logfile for solution provided by Terje Oseberg
+
+2004-09-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add -D_REENTRANT to the compile flags
+
+	* Add documentation of fuse internals by Terje Oseberg
+
+2004-08-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Change release method to be non-interruptible.  Fixes bug
+	causing missing release() call when program which has opened files
+	is killed (reported by Franco Broi and David Shaw)
+
+2004-07-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add fuse_invalidate() to library API
+
+2004-07-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check permissions in setattr if 'default_permissions' flag is
+	set.  Bug spotted by Damjan Lango
+
+2004-07-24  Miklos Szeredi <miklos@szeredi.hu>
+
+	* 'large_read' mount option removed for 2.6 kernels, since the
+	default (dynamic read size) is better
+
+	* Extend kernel API with file handles.  A file handle is returned
+	by open, and passed to read, write, flush, fsync and release.
+	This is currently only used for debug output in the library.
+
+	* Security changes:
+
+	* Change the current directory to the mountpoint before checking
+	the permissions and mount filesystem on "."
+
+	* By default don't modprobe the fuse module for non-root.  The old
+	behavior can be restored with the '--enable-auto-modprobe' flag of
+	./configure
+
+	* By default don't allow shared writable mappings for non-root.
+	The old behavior can be restored with the 'user_mmap=1' module
+	parameter
+
+2004-07-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Clean up mount option passing to fusermount and to fuse_new()
+	BEWARE: this changes the userspace API slightly, and the command
+	line usage of programs using fuse_main()
+
+2004-07-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Optimize reading under 2.6 kernels by issuing multiple page
+	asynchronous read requests
+
+2004-07-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Only use redirty_page_for_writepage() for kernels >= 2.6.6
+
+2004-07-16  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Separate directory entry and inode attribute validity timer
+
+	* New write semaphore to stop page writeback during truncate
+
+	* Fsync now waits for all writes to complete before sending the
+	request
+
+	* Optimization: if a page is completely written by
+	fuse_commit_write(), clear the dirty flag and set the uptodate
+	flag for that page
+
+	* Some memory cleanup at exit
+
+2004-07-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add FUSE_HARD_REMOVE flag, and '-i' option to fuse main, which
+	disable the "hide if open" behavior of unlink/rename.
+
+	* If temporary buffer allocation fails in raw read, fall back to a
+	smaller buffer
+
+2004-07-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix bug in do_open() in libfuse: open count was incremented
+	after the reply is sent so it could race with unlink/forget and
+	cause an abort.
+
+2004-07-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* When performing create or remove operation, refresh the parent's
+	attributes on next revalidate, as i_nlink (and maybe size/time)
+	could be inacurate.
+
+	* Use redirty_page_for_writepage() in fuse_writepage() for skipped
+	pages (2.6 only)
+
+	* Set set_page_dirty address space operation (2.6 only)
+
+2004-07-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Minor fix in read:  print debug info even if read size is zero
+
+2004-07-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix race between truncate and writepage (fsx-linux now runs
+	without error)
+
+2004-07-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix kernel hang on mkfifo under 2.4 kernels (spotted and patch
+	by Mattias Wadman)
+
+	* Added option for direct read/write (-r)
+
+	* Fix revalidate time setting for newly created inodes
+
+	* Remove uid==0 check for '-x' option in fusermount (kernel checks
+	this)
+
+	* fuse_main() only installs handlers for signals (out of INT, HUP,
+	TERM, PIPE), for which no handler has yet been installed
+
+	* Add module option 'user_allow_other' which if set to non-zero
+	will allow non root user to specify the 'allow_other' mount option
+	('-x' option of fusermount)
+
+	* Fix deadlock between page writeback completion and truncate
+	(bug found by Valient Gough with the fsx-linux utility)
+
+2004-07-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Change passing fuse include dir to 2.6 kernel make system more
+	robust (fixes compile problems seen on SuSE 9.1 with updated 2.6
+	kernel)
+
+2004-06-30  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Acquire inode->i_sem before open and release methods to prevent
+	concurrent rename or unlink operations.
+
+	* Make __fuse_read_cmd() read only one command.  This allows
+	multiplexing the fuse file descriptor with other event sources
+	using select() or poll() (patch by Jeff Harris)
+
+	* Export 'exited' flag with __fuse_exited() (patch by Jeff Harris)
+
+2004-06-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix file offset wrap around at 4G when doing large reads
+
+2004-06-24  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix memory leak in open (Valient Gough)
+
+2004-06-24  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add "close after delete" support to libfuse (patch by Valient
+	Gough)
+
+	* Cancel all worker threads before exit in multithreaded mode
+
+2004-06-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix locking bugs
+
+	* Don't send reply to RELEASE
+
+	* Work with newer libtool (1.5a)
+
+	* Check for st_atim member of struct stat
+
+2004-06-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* No request allocation needed on inode and file release
+
+2004-06-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix possible inode leak in userspace in case of unfinished
+	lookup/mknod/mkdir/symlink/link operation.
+
+2004-06-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix some races and cleanups in fuse_read_super()
+
+2004-06-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Requests are allocated at open time
+
+2004-06-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Build shared library as well as static (using libtool)
+
+	* Change FUSE_MINOR_VERSION from 1 to 0.  I know it's illegal but
+	there has not been a release with the previous minor number, and I
+	hope nobody is using it for anything.
+
+	* Change fuse_main(), so that default behavior is to go into
+	background if mount is successful.  '-f' and '-d' options disable
+	backgrounding.  This fixes the "Why does my FUSE daemon hang?"
+	newbie complaint.
+
+	* Cache ENOSYS (function not implemented) errors on *xattr, flush
+	and fsync
+
+	* Don't call getdir method from open() only from first readdir().
+	Open is sometimes just used to store the current directory
+	(e.g. find)
+
+2004-05-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added flush() call
+
+2004-05-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Extended attributes support for 2.4 (patch by Cody Pisto)
+
+2004-04-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fixed parser with modversions (Mattias Wadman)
+
+2004-04-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added mount option parser to 2.4 build
+
+2004-04-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Replaced binary mount data with text options
+
+	* Show FUSE specific mount options in /proc/mounts
+
+	* Check in fuse.h whether _FILE_OFFSET_BITS is set to 64
+
+2004-04-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Check some limits so userspace won't get too big requests
+
+2004-04-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Kill compile warning
+
+	* Upgraded user-mount patch for 2.6.5
+
+2004-04-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add detection of user-mode-linux to configure
+
+2004-03-31  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fixed zero size case for getxattr and listxattr
+
+2004-03-30  Miklos Szeredi <miklos@szeredi.hu>
+
+	* new fusermount flag '-z': lazy unmount, default is not lazy
+
+	* Extended attributes operations added (getxattr, setxattr,
+	listxattr, removexattr)
+
+2004-03-25  Miklos Szeredi <miklos@szeredi.hu>
+
+	* If filesystem doesn't define a statfs operation, then an
+	all-zero default statfs is returned instead of ENOSYS
+
+2004-03-24  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add FS_BINARY_MOUNTDATA filesystem flag for kernels > 2.6.4
+
+2004-03-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix for uClinux (Christian Magnusson)
+
+2004-03-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fuse_main() adds "-n progname" to the fusermount command line
+
+	* More kernel interface changes:
+
+	* Lookup/getattr return cache timeout values
+
+2004-02-25  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Clean up option parsing in fuse_main()
+
+	* Added fuse_get() function which returns the fuse object created
+	by fuse_main()
+
+2004-02-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* removed old way of mounting (fusermount mountpoint program)
+
+	* more kernel interface changes:
+
+	* added nanosecond precision to file times
+
+	* removed interface version from mount data
+
+	* added /proc/fs/fuse/version which contains MAJOR.MINOR
+
+2004-02-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* statfs library API changed to match other methods.  Since this
+	  is not backward compatible FUSE_MAJOR_VERSION is changed to 2
+
+	* kernel interface changes follow:
+
+	* statfs changed to 64 bits, added 'bavail' field
+
+	* add generation number to lookup result
+
+	* optimized mknod/mkdir/symlink/link (no separate lookup is
+	needed)
+
+	* rdev size increased to 32 bits for mknod
+
+	* kernel interface version changed to 3.1
+
+2004-02-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* user-mount upgraded for 2.6.3 kernel
+
+2004-02-17  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added user-mount.2.6.2-rc3.patch
+
+	* Add FS_SAFE flag to fuse filesystem
+
+	* fusermount should allow (un)mounting for non-root even if not
+	suid-root
+
+2004-02-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Remove MS_PERMISSION mount flag (that means something else now)
+
+2004-02-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added check for i_size_read/write functions to configure.in
+	(patch by Valient Gough)
+
+2004-02-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fixed writing >= 2G files
+
+	* Check file size on open (with generic_file_open())
+
+	* Readpage calls flush_dcache_page() after storing data
+
+	* Use i_size_read/write for accessing inode->i_size
+
+	* Make loopback mount of a fuse file work
+
+2004-02-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 1.1
+
+2004-01-29  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Properly check if the inode exists in fuse_invalidate
+
+2004-01-27  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added -q option for fusermount
+
+	* fuse_unmount() now uses -q option of fusermount, so no error is
+	printed if the cause of the program exit is that the filesystem
+	has already been unmounted
+
+	* Fix i_nlink correctness after rmdir/unlink
+
+2004-01-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 1.1-pre2
+
+2004-01-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix typo (thanks Marcos Dione)
+
+	* Compile fixes for 2.4 kernels
+
+2004-01-23  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix CONFIG_MODVERSIONS compile on 2.6
+
+2004-01-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Write all pending data before a RELEASE operation
+
+	* Suppress 'Bad file descriptor' warning on exit
+
+	* Replaced fusermount option '-d xxx' with '-n xxx' so it doesn't
+	get confused with '-d' of fuse_main() (sorry about this change)
+
+	* New fusermount option '-l' which enables big reads.  Big reads
+	are now disabled by default.
+
+	* fuse_main() can accept fusermount arguments after a '--'
+
+2004-01-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Support for exporting filesystem over NFS (see README.NFS)
+
+2004-01-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Support non-blocking writepage on 2.6.  This makes FUSE behave
+	much more nicely in low-memory situations
+
+	* Fix 32-bit dev handling in getattr and mknod for 2.6 kernels.
+	(Note: the mknod method does not yet use 32bit device number)
+
+2004-01-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Code cleanups
+
+2004-01-07  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Released 1.1-pre1
+
+2004-01-06  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Integrated 2.6 kernel support patch by Michael Grigoriev
+
+	* Improvements and cleanups for 2.6 kernels
+
+2004-01-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added -d option to fusermount
+
+2003-12-15  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added major+minor version to library API, and minor version to
+	  kernel API
+
+2003-12-13  David McNab <david@rebirthing.co.nz>
+
+	* Implemented fsync support in examples/example.py
+
+	* Implemented 'fsync' and 'statfs' methods in python
+	  interface
+
+2003-12-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Make it compile on 2.4.19.
+
+	* Add fsync operation (write file failed on xemacs & vi)
+
+2003-12-12  David McNab <david@rebirthing.co.nz>
+
+	* Added distutils support to the python module, as per standard
+	  python development practice
+
+2003-12-11  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Add file locking for mount/unmount (based on patch by Valient
+	Gough)
+
+2003-12-11  David McNab <david@rebirthing.co.nz>
+
+	* Python filesystem - was broken with python2.3, now fixed:
+	   - changed PyTuple_* calls to PySequence_*, because os.lstat
+	     is no longer returning a pure tuple
+	   - changed PyInt_Check() calls to also call PyLong_Check,
+	     to cover for cases (eg os.lstat) where longs are returned
+	   - Added support for file 'release' handling, which IMO is
+	     essential since this signals to a FS that writes to a file
+	     are complete (and therefore the file can now be disposed of
+	     meaningfully at the python filesystem's discretion)
+	   - Added '__init__' handler to base Fuse class, which allows
+	     your Python class to know the mountpoint and mount args,
+	     as attributes myfs.mountpoint, myfs.optlist, myfs.optdict
+
+	* General:
+	   - added 'mount.fuse' script (in util/ dir), which is meant to be
+	     symlinked from /sbin, and which allows FUSE filesystems to
+	     be mounted with the 'mount' command, and listed in fstab;
+	     also, mount arguments get passed to your filesystem
+
+
+2003-11-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix kernel version detection (again).  Bugreport by Peter Levart
+
+2003-11-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Applied read combining patch by Michael Grigoriev (tested by
+	Valient Gough and Vincent Wagelaar)
+
+2003-10-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Mtab handling fix in fusermount by "Valient Gough" (SF patch
+	#766443)
+
+2003-10-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Error code fixes in kernel module
+
+2003-10-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* kernel version detection fix
+
+	* fusermount now uses "lazy" umount option
+
+	* fusermount can use modprobe with module-init-tools
+
+2003-09-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Integrated caching patch by Michael Grigoriev
+
+	* Added "Filesystems" file with descriptions of projects using
+	FUSE
+
+	* Added patch by Michael Grigoriev to allow compliation of FUSE
+	kernel module for 2.6 kernels
+
+2003-06-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* And another spec-file fix by Achim Settelmeier
+
+2003-05-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Spec-file fix by Achim Settelmeier
+
+2003-03-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fix umount oops (found by Samuli Kärkkäinen)
+
+2003-03-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Merge of fuse_redhat.spec and fuse.spec by Achim Settelmeier
+
+2003-03-04  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Updated fuse.spec file (Achim Settelmeier)
+
+2003-02-19  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Version 1.0 released
+
+2003-02-12  Miklos Szeredi <miklos@szeredi.hu>
+
+	* SuSE compilation fix by Juan-Mariano de Goyeneche
+
+2002-12-10  Miklos Szeredi <miklos@szeredi.hu>
+
+	* The release() VFS call is now exported to the FUSE interface
+
+2002-12-05  Miklos Szeredi <miklos@szeredi.hu>
+
+	* 64 bit file offset fixes in the fuse kernel module
+
+	* Added function 'fuse_exit()' which can be used to exit the main
+	loop
+
+2002-12-03  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added _FILE_OFFSET_BITS=64 define to fuse.h.  Note, that this is
+	an incompatible interface change.
+
+2002-10-28  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Portablility fix (bug reported by C. Chris Erway)
+
+2002-10-25  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Use Mark Glines' fd passing method for default operation instead
+	of old reexec
+
+2002-10-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fix "Stale NFS file handle" bug caused by changes in 2.4.19
+
+2002-10-22  Miklos Szeredi <miklos@szeredi.hu>
+
+	* fix incompatiblity with Red Hat kernels, with help from Nathan
+	Thompson-Amato.
+
+2002-04-18  Mark Glines <mark@glines.org>
+
+	* added an alternative to fuse_mount(), called
+      fuse_mount_ioslave(), which does not need to reexec the
+      FUSE program.
+	* added a small helper util needed by fuse_mount_ioslave().
+
+2002-03-16  Mark Glines <mark@glines.org>
+
+	* use struct fuse_statfs everywhere possible to avoid problems
+      with the headerfiles changing struct statfs member sizes
+
+2002-03-01  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Another RPM spec file for RedHat >= 7 by Ian Pilcher
+
+2002-01-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* RPM support by Achim Settelmeier
+
+2002-01-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Version 0.95 released
+
+2002-01-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Revaidate all path components not just the last, this means a
+	very small performance penalty for being more up-to-date.
+
+2002-01-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Update and fix python interface
+
+2002-01-07  Mark Glines <mark@glines.org>
+
+	* Added statfs() support to kernel, lib, examples, and perl!
+
+2001-12-26  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Better cross compilation support
+
+	* Ported to Compaq IPAQ
+
+2001-12-20  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added function fuse_get_context() to library API (inspired by
+	patch from Matt Ryan)
+
+	* Added flags to fusermount and to kernel interface to control
+	permission checking
+
+	* Integrated fuse_set_operations() into fuse_new()
+
+2001-12-08  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Applied header protection + extern "C" patch by Roland
+	Bauerschmidt
+
+2001-12-02  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Added perl bindings by Mark Glines
+
+2001-11-21  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Cleaned up way of mounting simple filesystems.
+
+	* fuse_main() helper function added
+
+2001-11-18  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Optimized read/write operations, so that minimal copying of data
+	is done
+
+2001-11-14  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Python bindings by Jeff Epler added
+
+2001-11-13  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Fixed vfsmount reference leak in fuse_follow_link
+
+	* FS blocksize is set to PAGE_CACHE_SIZE, blksize attribute from
+	userspace is ignored
+
+2001-11-09  Miklos Szeredi <miklos@szeredi.hu>
+
+	* Started ChangeLog
diff --git a/fuse/README b/fuse/README
new file mode 100644
index 0000000..398dd65
--- /dev/null
+++ b/fuse/README
@@ -0,0 +1,380 @@
+General Information
+===================
+
+FUSE (Filesystem in Userspace) is a simple interface for userspace
+programs to export a virtual filesystem to the Linux kernel.  FUSE
+also aims to provide a secure method for non privileged users to
+create and mount their own filesystem implementations.
+
+You can download the source code releases from
+
+  http://sourceforge.net/projects/fuse
+
+or alternatively you can use CVS to get the very latest development
+version:
+
+  cvs -d :pserver:anonymous@fuse.cvs.sourceforge.net:/cvsroot/fuse co fuse
+
+
+Dependencies
+============
+
+Linux kernel version 2.6.X where X >= 9.
+
+Alternatively a kernel module from FUSE release 2.5.* can be used with
+this release, which supports kernels >= 2.4.21.
+
+Installation
+============
+
+./configure
+make
+make install
+modprobe fuse
+
+You may also need to add '/usr/local/lib' to '/etc/ld.so.conf' and/or
+run ldconfig.
+
+You'll also need a fuse kernel module, Linux kernels 2.6.14 or later
+contain FUSE support.
+
+For more details see the file 'INSTALL'
+
+How To Use
+==========
+
+FUSE is made up of three main parts:
+
+ - A kernel filesystem module
+
+ - A userspace library
+
+ - A mount/unmount program
+
+
+Here's how to create your very own virtual filesystem in five easy
+steps (after installing FUSE):
+
+  1) Edit the file example/fusexmp.c to do whatever you want...
+
+  2) Build the fusexmp program
+
+  3) run 'example/fusexmp /mnt/fuse -d'
+
+  4) ls -al /mnt/fuse
+
+  5) Be glad
+
+If it doesn't work out, please ask!  Also see the file 'include/fuse.h' for
+detailed documentation of the library interface.
+
+Security
+========
+
+If you run 'make install', the fusermount program is installed
+set-user-id to root.  This is done to allow normal users to mount
+their own filesystem implementations.
+
+There must however be some limitations, in order to prevent Bad User from
+doing nasty things.  Currently those limitations are:
+
+  - The user can only mount on a mountpoint, for which it has write
+    permission
+
+  - The mountpoint is not a sticky directory which isn't owned by the
+    user (like /tmp usually is)
+
+  - No other user (including root) can access the contents of the mounted
+    filesystem.
+
+Configuration
+=============
+
+Some options regarding mount policy can be set in the file
+'/etc/fuse.conf'
+
+Currently these options are:
+
+mount_max = NNN
+
+  Set the maximum number of FUSE mounts allowed to non-root users.
+  The default is 1000.
+
+user_allow_other
+
+  Allow non-root users to specify the 'allow_other' or 'allow_root'
+  mount options.
+
+
+Mount options
+=============
+
+Most of the generic mount options described in 'man mount' are
+supported (ro, rw, suid, nosuid, dev, nodev, exec, noexec, atime,
+noatime, sync async, dirsync).  Filesystems are mounted with
+'-onodev,nosuid' by default, which can only be overridden by a
+privileged user.
+
+These are FUSE specific mount options that can be specified for all
+filesystems:
+
+default_permissions
+
+  By default FUSE doesn't check file access permissions, the
+  filesystem is free to implement it's access policy or leave it to
+  the underlying file access mechanism (e.g. in case of network
+  filesystems).  This option enables permission checking, restricting
+  access based on file mode.  This is option is usually useful
+  together with the 'allow_other' mount option.
+
+allow_other
+
+  This option overrides the security measure restricting file access
+  to the user mounting the filesystem.  So all users (including root)
+  can access the files.  This option is by default only allowed to
+  root, but this restriction can be removed with a configuration
+  option described in the previous section.
+
+allow_root
+
+  This option is similar to 'allow_other' but file access is limited
+  to the user mounting the filesystem and root.  This option and
+  'allow_other' are mutually exclusive.
+
+kernel_cache
+
+  This option disables flushing the cache of the file contents on
+  every open().  This should only be enabled on filesystems, where the
+  file data is never changed externally (not through the mounted FUSE
+  filesystem).  Thus it is not suitable for network filesystems and
+  other "intermediate" filesystems.
+
+  NOTE: if this option is not specified (and neither 'direct_io') data
+  is still cached after the open(), so a read() system call will not
+  always initiate a read operation.
+
+auto_cache
+
+  This option enables automatic flushing of the data cache on open().
+  The cache will only be flushed if the modification time or the size
+  of the file has changed.
+
+large_read
+
+  Issue large read requests.  This can improve performance for some
+  filesystems, but can also degrade performance.  This option is only
+  useful on 2.4.X kernels, as on 2.6 kernels requests size is
+  automatically determined for optimum performance.
+
+direct_io
+
+  This option disables the use of page cache (file content cache) in
+  the kernel for this filesystem.  This has several affects:
+
+     - Each read() or write() system call will initiate one or more
+       read or write operations, data will not be cached in the
+       kernel.
+
+     - The return value of the read() and write() system calls will
+       correspond to the return values of the read and write
+       operations.  This is useful for example if the file size is not
+       known in advance (before reading it).
+
+max_read=N
+
+  With this option the maximum size of read operations can be set.
+  The default is infinite.  Note that the size of read requests is
+  limited anyway to 32 pages (which is 128kbyte on i386).
+
+max_readahead=N
+
+  Set the maximum number of bytes to read-ahead.  The default is
+  determined by the kernel.  On linux-2.6.22 or earlier it's 131072
+  (128kbytes)
+
+max_write=N
+
+  Set the maximum number of bytes in a single write operation.  The
+  default is 128kbytes.  Note, that due to various limitations, the
+  size of write requests can be much smaller (4kbytes).  This
+  limitation will be removed in the future.
+
+async_read
+
+  Perform reads asynchronously. This is the default
+
+sync_read
+
+  Perform all reads (even read-ahead) synchronously.
+
+hard_remove
+
+  The default behavior is that if an open file is deleted, the file is
+  renamed to a hidden file (.fuse_hiddenXXX), and only removed when
+  the file is finally released.  This relieves the filesystem
+  implementation of having to deal with this problem.  This option
+  disables the hiding behavior, and files are removed immediately in
+  an unlink operation (or in a rename operation which overwrites an
+  existing file).
+
+  It is recommended that you not use the hard_remove option. When
+  hard_remove is set, the following libc functions fail on unlinked
+  files (returning errno of ENOENT):
+     - read()
+     - write()
+     - fsync()
+     - close()
+     - f*xattr()
+     - ftruncate()
+     - fstat()
+     - fchmod()
+     - fchown()
+
+debug
+
+  Turns on debug information printing by the library.
+
+fsname=NAME
+
+  Sets the filesystem source (first field in /etc/mtab).  The default
+  is the program name.
+
+subtype=TYPE
+
+  Sets the filesystem type (third field in /etc/mtab).  The default is
+  the program name.
+
+  If the kernel suppports it, /etc/mtab and /proc/mounts will show the
+  filesystem type as "fuse.TYPE"
+
+  If the kernel doesn't support subtypes, the source filed will be
+  "TYPE#NAME", or if fsname option is not specified, just "TYPE".
+
+use_ino
+
+  Honor the 'st_ino' field in getattr() and fill_dir().  This value is
+  used to fill in the 'st_ino' field in the stat()/lstat()/fstat()
+  functions and the 'd_ino' field in the readdir() function.  The
+  filesystem does not have to guarantee uniqueness, however some
+  applications rely on this value being unique for the whole
+  filesystem.
+
+readdir_ino
+
+  If 'use_ino' option is not given, still try to fill in the 'd_ino'
+  field in readdir().  If the name was previously looked up, and is
+  still in the cache, the inode number found there will be used.
+  Otherwise it will be set to '-1'.  If 'use_ino' option is given,
+  this option is ignored.
+
+nonempty
+
+  Allows mounts over a non-empty file or directory.  By default these
+  mounts are rejected (from version 2.3.1) to prevent accidental
+  covering up of data, which could for example prevent automatic
+  backup.
+
+umask=M
+
+  Override the permission bits in 'st_mode' set by the filesystem.
+  The resulting permission bits are the ones missing from the given
+  umask value.  The value is given in octal representation.
+
+uid=N
+
+  Override the 'st_uid' field set by the filesystem.
+
+gid=N
+
+  Override the 'st_gid' field set by the filesystem.
+
+blkdev
+
+  Mount a filesystem backed by a block device.  This is a privileged
+  option.  The device must be specified with the 'fsname=NAME' option.
+
+entry_timeout=T
+
+  The timeout in seconds for which name lookups will be cached. The
+  default is 1.0 second.  For all the timeout options, it is possible
+  to give fractions of a second as well (e.g. "-oentry_timeout=2.8")
+
+negative_timeout=T
+
+  The timeout in seconds for which a negative lookup will be cached.
+  This means, that if file did not exist (lookup retuned ENOENT), the
+  lookup will only be redone after the timeout, and the file/directory
+  will be assumed to not exist until then.  The default is 0.0 second,
+  meaning that caching negative lookups are disabled.
+
+attr_timeout=T
+
+  The timeout in seconds for which file/directory attributes are
+  cached.  The default is 1.0 second.
+
+ac_attr_timeout=T
+
+  The timeout in seconds for which file attributes are cached for the
+  purpose of checking if "auto_cache" should flush the file data on
+  open.   The default is the value of 'attr_timeout'
+
+intr
+
+  Allow requests to be interrupted.  Turning on this option may result
+  in unexpected behavior, if the filesystem does not support request
+  interruption.
+
+intr_signal=NUM
+
+  Specify which signal number to send to the filesystem when a request
+  is interrupted.  The default is 10 (USR1).
+
+modules=M1[:M2...]
+
+  Add modules to the filesystem stack.  Modules are pushed in the
+  order they are specified, with the original filesystem being on the
+  bottom of the stack.
+
+
+Modules distributed with fuse
+-----------------------------
+
+iconv
+`````
+Perform file name character set conversion.  Options are:
+
+from_code=CHARSET
+
+  Character set to convert from (see iconv -l for a list of possible
+  values).  Default is UTF-8.
+
+to_code=CHARSET
+
+  Character set to convert to.  Default is determined by the current
+  locale.
+
+
+subdir
+``````
+Prepend a given directory to each path. Options are:
+
+subdir=DIR
+
+  Directory to prepend to all paths.  This option is mandatory.
+
+rellinks
+
+  Transform absolute symlinks into relative
+
+norellinks
+
+  Do not transform absolute symlinks into relative.  This is the default.
+
+
+Reporting bugs
+==============
+
+Please send bug reports to the <fuse-devel@lists.sourceforge.net>
+mailing list.
+
+The list is open, you need not be subscribed to post.
diff --git a/fuse/android/config.h b/fuse/android/config.h
new file mode 100644
index 0000000..dac8c16
--- /dev/null
+++ b/fuse/android/config.h
@@ -0,0 +1,87 @@
+/* include/config.h.  Generated from config.h.in by configure.  */
+/* include/config.h.in.  Generated from configure.in by autoheader.  */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the `fdatasync' function. */
+#define HAVE_FDATASYNC 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define if you have the iconv() function and it works. */
+#define HAVE_ICONV 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `setxattr' function. */
+#define HAVE_SETXATTR 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if `st_atim' is member of `struct stat'. */
+//#define HAVE_STRUCT_STAT_ST_ATIM 0
+
+/* Define to 1 if `st_atimespec' is member of `struct stat'. */
+/* #undef HAVE_STRUCT_STAT_ST_ATIMESPEC */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define as const if the declaration of iconv() needs const. */
+#define ICONV_CONST 
+
+/* Don't update /etc/mtab */
+/* #undef IGNORE_MTAB */
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+   */
+#define LT_OBJDIR ".libs/"
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of package */
+#define PACKAGE "fuse"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "fuse"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "fuse 2.9.3"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "fuse"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "2.9.3"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#define VERSION "2.9.3"
diff --git a/fuse/android/statvfs.c b/fuse/android/statvfs.c
new file mode 100644
index 0000000..7cec574
--- /dev/null
+++ b/fuse/android/statvfs.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 Freescale Semiconductor, Inc.
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 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
+ *
+ */
+
+#include "sys/statvfs.h"
+#include <sys/statfs.h>
+
+#define MAP(to,from) vfs->to = sfs.from
+
+int statvfs(const char *path, struct statvfs *vfs) {
+    struct statfs sfs;
+    int ret;
+    int *fsid;
+    if ((ret = statfs(path, &sfs)) != 0)
+        return ret;
+
+    MAP(f_bsize,   f_bsize);
+    MAP(f_frsize,  f_frsize);
+    MAP(f_blocks,  f_blocks);
+    MAP(f_bfree,   f_bfree);
+    MAP(f_bavail,  f_bavail);
+    MAP(f_files,   f_files);
+    MAP(f_ffree,   f_ffree);
+    MAP(f_namemax, f_namelen);
+
+    vfs->f_favail = 0;
+    vfs->f_flag   = 0;
+
+    fsid = (int *)&sfs.f_fsid;
+    vfs->f_fsid   = (fsid[0] << sizeof(fsid[0])) | fsid[1];
+
+    return ret;
+}
diff --git a/fuse/android/sys/statvfs.h b/fuse/android/sys/statvfs.h
new file mode 100644
index 0000000..0d770dd
--- /dev/null
+++ b/fuse/android/sys/statvfs.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 Freescale Semiconductor, Inc.
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 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
+ *
+ */
+
+#ifndef _SYS_STATVFS_H_
+#define _SYS_STATVFS_H_
+#include <sys/types.h>
+
+struct statvfs {
+	unsigned long  f_bsize;    /* file system block size */
+	unsigned long  f_frsize;   /* fragment size */
+	fsblkcnt_t     f_blocks;   /* size of fs in f_frsize units */
+	fsblkcnt_t     f_bfree;    /* # free blocks */
+	fsblkcnt_t     f_bavail;   /* # free blocks for non-root */
+	fsfilcnt_t     f_files;    /* # inodes */
+	fsfilcnt_t     f_ffree;    /* # free inodes */
+	fsfilcnt_t     f_favail;   /* # free inodes for non-root */
+	unsigned long  f_fsid;     /* file system ID */
+	unsigned long  f_flag;     /* mount flags */
+	unsigned long  f_namemax;  /* maximum filename length */
+};
+
+int statvfs(const char *, struct statvfs *);
+#endif
diff --git a/fuse/buffer.c b/fuse/buffer.c
new file mode 100644
index 0000000..6fa55c9
--- /dev/null
+++ b/fuse/buffer.c
@@ -0,0 +1,318 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2010  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#define _GNU_SOURCE
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_lowlevel.h"
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+{
+	size_t i;
+	size_t size = 0;
+
+	for (i = 0; i < bufv->count; i++) {
+		if (bufv->buf[i].size == SIZE_MAX)
+			size = SIZE_MAX;
+		else
+			size += bufv->buf[i].size;
+	}
+
+	return size;
+}
+
+static size_t min_size(size_t s1, size_t s2)
+{
+	return s1 < s2 ? s1 : s2;
+}
+
+static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+			      const struct fuse_buf *src, size_t src_off,
+			      size_t len)
+{
+	ssize_t res = 0;
+	size_t copied = 0;
+
+	while (len) {
+		if (dst->flags & FUSE_BUF_FD_SEEK) {
+			res = pwrite64(dst->fd, src->mem + src_off, len,
+				     dst->pos + dst_off);
+		} else {
+			res = write(dst->fd, src->mem + src_off, len);
+		}
+		if (res == -1) {
+			if (!copied)
+				return -errno;
+			break;
+		}
+		if (res == 0)
+			break;
+
+		copied += res;
+		if (!(dst->flags & FUSE_BUF_FD_RETRY))
+			break;
+
+		src_off += res;
+		dst_off += res;
+		len -= res;
+	}
+
+	return copied;
+}
+
+static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+			     const struct fuse_buf *src, size_t src_off,
+			     size_t len)
+{
+	ssize_t res = 0;
+	size_t copied = 0;
+
+	while (len) {
+		if (src->flags & FUSE_BUF_FD_SEEK) {
+			res = pread(src->fd, dst->mem + dst_off, len,
+				     src->pos + src_off);
+		} else {
+			res = read(src->fd, dst->mem + dst_off, len);
+		}
+		if (res == -1) {
+			if (!copied)
+				return -errno;
+			break;
+		}
+		if (res == 0)
+			break;
+
+		copied += res;
+		if (!(src->flags & FUSE_BUF_FD_RETRY))
+			break;
+
+		dst_off += res;
+		src_off += res;
+		len -= res;
+	}
+
+	return copied;
+}
+
+static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+				 const struct fuse_buf *src, size_t src_off,
+				 size_t len)
+{
+	char buf[4096];
+	struct fuse_buf tmp = {
+		.size = sizeof(buf),
+		.flags = 0,
+	};
+	ssize_t res;
+	size_t copied = 0;
+
+	tmp.mem = buf;
+
+	while (len) {
+		size_t this_len = min_size(tmp.size, len);
+		size_t read_len;
+
+		res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+		if (res < 0) {
+			if (!copied)
+				return res;
+			break;
+		}
+		if (res == 0)
+			break;
+
+		read_len = res;
+		res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+		if (res < 0) {
+			if (!copied)
+				return res;
+			break;
+		}
+		if (res == 0)
+			break;
+
+		copied += res;
+
+		if (res < this_len)
+			break;
+
+		dst_off += res;
+		src_off += res;
+		len -= res;
+	}
+
+	return copied;
+}
+
+#ifdef HAVE_SPLICE
+static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+			       const struct fuse_buf *src, size_t src_off,
+			       size_t len, enum fuse_buf_copy_flags flags)
+{
+	int splice_flags = 0;
+	loff_t *srcpos = NULL;
+	loff_t *dstpos = NULL;
+	loff_t srcpos_val;
+	loff_t dstpos_val;
+	ssize_t res;
+	size_t copied = 0;
+
+	if (flags & FUSE_BUF_SPLICE_MOVE)
+		splice_flags |= SPLICE_F_MOVE;
+	if (flags & FUSE_BUF_SPLICE_NONBLOCK)
+		splice_flags |= SPLICE_F_NONBLOCK;
+
+	if (src->flags & FUSE_BUF_FD_SEEK) {
+		srcpos_val = src->pos + src_off;
+		srcpos = &srcpos_val;
+	}
+	if (dst->flags & FUSE_BUF_FD_SEEK) {
+		dstpos_val = dst->pos + dst_off;
+		dstpos = &dstpos_val;
+	}
+
+	while (len) {
+		res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+			     splice_flags);
+		if (res == -1) {
+			if (copied)
+				break;
+
+			if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+				return -errno;
+
+			/* Maybe splice is not supported for this combination */
+			return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+						 len);
+		}
+		if (res == 0)
+			break;
+
+		copied += res;
+		if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+		    !(dst->flags & FUSE_BUF_FD_RETRY)) {
+			break;
+		}
+
+		len -= res;
+	}
+
+	return copied;
+}
+#else
+static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+			       const struct fuse_buf *src, size_t src_off,
+			       size_t len, enum fuse_buf_copy_flags flags)
+{
+	(void) flags;
+
+	return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+}
+#endif
+
+
+static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+				 const struct fuse_buf *src, size_t src_off,
+				 size_t len, enum fuse_buf_copy_flags flags)
+{
+	int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+	int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
+	if (!src_is_fd && !dst_is_fd) {
+		void *dstmem = dst->mem + dst_off;
+		void *srcmem = src->mem + src_off;
+
+		if (dstmem != srcmem) {
+			if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+				memcpy(dstmem, srcmem, len);
+			else
+				memmove(dstmem, srcmem, len);
+		}
+
+		return len;
+	} else if (!src_is_fd) {
+		return fuse_buf_write(dst, dst_off, src, src_off, len);
+	} else if (!dst_is_fd) {
+		return fuse_buf_read(dst, dst_off, src, src_off, len);
+	} else if (flags & FUSE_BUF_NO_SPLICE) {
+		return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+	} else {
+		return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+	}
+}
+
+static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+{
+	if (bufv->idx < bufv->count)
+		return &bufv->buf[bufv->idx];
+	else
+		return NULL;
+}
+
+static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+{
+	const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
+	bufv->off += len;
+	assert(bufv->off <= buf->size);
+	if (bufv->off == buf->size) {
+		assert(bufv->idx < bufv->count);
+		bufv->idx++;
+		if (bufv->idx == bufv->count)
+			return 0;
+		bufv->off = 0;
+	}
+	return 1;
+}
+
+ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+		      enum fuse_buf_copy_flags flags)
+{
+	size_t copied = 0;
+
+	if (dstv == srcv)
+		return fuse_buf_size(dstv);
+
+	for (;;) {
+		const struct fuse_buf *src = fuse_bufvec_current(srcv);
+		const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+		size_t src_len;
+		size_t dst_len;
+		size_t len;
+		ssize_t res;
+
+		if (src == NULL || dst == NULL)
+			break;
+
+		src_len = src->size - srcv->off;
+		dst_len = dst->size - dstv->off;
+		len = min_size(src_len, dst_len);
+
+		res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+		if (res < 0) {
+			if (!copied)
+				return res;
+			break;
+		}
+		copied += res;
+
+		if (!fuse_bufvec_advance(srcv, res) ||
+		    !fuse_bufvec_advance(dstv, res))
+			break;
+
+		if (res < len)
+			break;
+	}
+
+	return copied;
+}
diff --git a/fuse/cuse_lowlevel.c b/fuse/cuse_lowlevel.c
new file mode 100644
index 0000000..ae08ed4
--- /dev/null
+++ b/fuse/cuse_lowlevel.c
@@ -0,0 +1,371 @@
+/*
+  CUSE: Character device in Userspace
+  Copyright (C) 2008       SUSE Linux Products GmbH
+  Copyright (C) 2008       Tejun Heo <teheo@suse.de>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#include "cuse_lowlevel.h"
+#include "fuse_kernel.h"
+#include "fuse_i.h"
+#include "fuse_opt.h"
+#include "fuse_misc.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <errno.h>
+#include <unistd.h>
+
+struct cuse_data {
+	struct cuse_lowlevel_ops	clop;
+	unsigned			max_read;
+	unsigned			dev_major;
+	unsigned			dev_minor;
+	unsigned			flags;
+	unsigned			dev_info_len;
+	char				dev_info[];
+};
+
+static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+{
+	return &req->f->cuse_data->clop;
+}
+
+static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+			  struct fuse_file_info *fi)
+{
+	(void)ino;
+	req_clop(req)->open(req, fi);
+}
+
+static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+			  loff_t off, struct fuse_file_info *fi)
+{
+	(void)ino;
+	req_clop(req)->read(req, size, off, fi);
+}
+
+static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+			   size_t size, loff_t off, struct fuse_file_info *fi)
+{
+	(void)ino;
+	req_clop(req)->write(req, buf, size, off, fi);
+}
+
+static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+			   struct fuse_file_info *fi)
+{
+	(void)ino;
+	req_clop(req)->flush(req, fi);
+}
+
+static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+			     struct fuse_file_info *fi)
+{
+	(void)ino;
+	req_clop(req)->release(req, fi);
+}
+
+static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+			   struct fuse_file_info *fi)
+{
+	(void)ino;
+	req_clop(req)->fsync(req, datasync, fi);
+}
+
+static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg,
+		       struct fuse_file_info *fi, unsigned int flags,
+		       const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+{
+	(void)ino;
+	req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+			     out_bufsz);
+}
+
+static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+			  struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+{
+	(void)ino;
+	req_clop(req)->poll(req, fi, ph);
+}
+
+static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+{
+	size_t size = 0;
+	int i;
+
+	for (i = 0; i < argc; i++) {
+		size_t len;
+
+		len = strlen(argv[i]) + 1;
+		size += len;
+		if (buf) {
+			memcpy(buf, argv[i], len);
+			buf += len;
+		}
+	}
+
+	return size;
+}
+
+static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+					const struct cuse_lowlevel_ops *clop)
+{
+	struct cuse_data *cd;
+	size_t dev_info_len;
+
+	dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+				      NULL);
+
+	if (dev_info_len > CUSE_INIT_INFO_MAX) {
+		fprintf(stderr, "cuse: dev_info (%zu) too large, limit=%u\n",
+			dev_info_len, CUSE_INIT_INFO_MAX);
+		return NULL;
+	}
+
+	cd = calloc(1, sizeof(*cd) + dev_info_len);
+	if (!cd) {
+		fprintf(stderr, "cuse: failed to allocate cuse_data\n");
+		return NULL;
+	}
+
+	memcpy(&cd->clop, clop, sizeof(cd->clop));
+	cd->max_read = 131072;
+	cd->dev_major = ci->dev_major;
+	cd->dev_minor = ci->dev_minor;
+	cd->dev_info_len = dev_info_len;
+	cd->flags = ci->flags;
+	cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
+	return cd;
+}
+
+struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+				       const struct cuse_info *ci,
+				       const struct cuse_lowlevel_ops *clop,
+				       void *userdata)
+{
+	struct fuse_lowlevel_ops lop;
+	struct cuse_data *cd;
+	struct fuse_session *se;
+	struct fuse_ll *ll;
+
+	cd = cuse_prep_data(ci, clop);
+	if (!cd)
+		return NULL;
+
+	memset(&lop, 0, sizeof(lop));
+	lop.init	= clop->init;
+	lop.destroy	= clop->destroy;
+	lop.open	= clop->open		? cuse_fll_open		: NULL;
+	lop.read	= clop->read		? cuse_fll_read		: NULL;
+	lop.write	= clop->write		? cuse_fll_write	: NULL;
+	lop.flush	= clop->flush		? cuse_fll_flush	: NULL;
+	lop.release	= clop->release		? cuse_fll_release	: NULL;
+	lop.fsync	= clop->fsync		? cuse_fll_fsync	: NULL;
+	lop.ioctl	= clop->ioctl		? cuse_fll_ioctl	: NULL;
+	lop.poll	= clop->poll		? cuse_fll_poll		: NULL;
+
+	se = fuse_lowlevel_new_common(args, &lop, sizeof(lop), userdata);
+	if (!se) {
+		free(cd);
+		return NULL;
+	}
+	ll = se->data;
+	ll->cuse_data = cd;
+
+	return se;
+}
+
+static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+			   char *dev_info, unsigned dev_info_len)
+{
+	struct iovec iov[3];
+
+	iov[1].iov_base = arg;
+	iov[1].iov_len = sizeof(struct cuse_init_out);
+	iov[2].iov_base = dev_info;
+	iov[2].iov_len = dev_info_len;
+
+	return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+}
+
+void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+	struct cuse_init_out outarg;
+	struct fuse_ll *f = req->f;
+	struct cuse_data *cd = f->cuse_data;
+	size_t bufsize = fuse_chan_bufsize(req->ch);
+	struct cuse_lowlevel_ops *clop = req_clop(req);
+
+	(void) nodeid;
+	if (f->debug) {
+		fprintf(stderr, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+		fprintf(stderr, "flags=0x%08x\n", arg->flags);
+	}
+	f->conn.proto_major = arg->major;
+	f->conn.proto_minor = arg->minor;
+	f->conn.capable = 0;
+	f->conn.want = 0;
+
+	if (arg->major < 7) {
+		fprintf(stderr, "cuse: unsupported protocol version: %u.%u\n",
+			arg->major, arg->minor);
+		fuse_reply_err(req, EPROTO);
+		return;
+	}
+
+	if (bufsize < FUSE_MIN_READ_BUFFER) {
+		fprintf(stderr, "cuse: warning: buffer size too small: %zu\n",
+			bufsize);
+		bufsize = FUSE_MIN_READ_BUFFER;
+	}
+
+	bufsize -= 4096;
+	if (bufsize < f->conn.max_write)
+		f->conn.max_write = bufsize;
+
+	f->got_init = 1;
+	if (f->op.init)
+		f->op.init(f->userdata, &f->conn);
+
+	memset(&outarg, 0, sizeof(outarg));
+	outarg.major = FUSE_KERNEL_VERSION;
+	outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+	outarg.flags = cd->flags;
+	outarg.max_read = cd->max_read;
+	outarg.max_write = f->conn.max_write;
+	outarg.dev_major = cd->dev_major;
+	outarg.dev_minor = cd->dev_minor;
+
+	if (f->debug) {
+		fprintf(stderr, "   CUSE_INIT: %u.%u\n",
+			outarg.major, outarg.minor);
+		fprintf(stderr, "   flags=0x%08x\n", outarg.flags);
+		fprintf(stderr, "   max_read=0x%08x\n", outarg.max_read);
+		fprintf(stderr, "   max_write=0x%08x\n", outarg.max_write);
+		fprintf(stderr, "   dev_major=%u\n", outarg.dev_major);
+		fprintf(stderr, "   dev_minor=%u\n", outarg.dev_minor);
+		fprintf(stderr, "   dev_info: %.*s\n", cd->dev_info_len,
+			cd->dev_info);
+	}
+
+	cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
+	if (clop->init_done)
+		clop->init_done(f->userdata);
+
+	fuse_free_req(req);
+}
+
+struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+					 const struct cuse_info *ci,
+					 const struct cuse_lowlevel_ops *clop,
+					 int *multithreaded, void *userdata)
+{
+	const char *devname = "/dev/cuse";
+	static const struct fuse_opt kill_subtype_opts[] = {
+		FUSE_OPT_KEY("subtype=",  FUSE_OPT_KEY_DISCARD),
+		FUSE_OPT_END
+	};
+	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+	struct fuse_session *se;
+	struct fuse_chan *ch;
+	int fd;
+	int foreground;
+	int res;
+
+	res = fuse_parse_cmdline(&args, NULL, multithreaded, &foreground);
+	if (res == -1)
+		goto err_args;
+
+	res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+	if (res == -1)
+		goto err_args;
+
+	/*
+	 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+	 * would ensue.
+	 */
+	do {
+		fd = open("/dev/null", O_RDWR);
+		if (fd > 2)
+			close(fd);
+	} while (fd >= 0 && fd <= 2);
+
+	se = cuse_lowlevel_new(&args, ci, clop, userdata);
+	fuse_opt_free_args(&args);
+	if (se == NULL)
+		goto err_args;
+
+	fd = open(devname, O_RDWR);
+	if (fd == -1) {
+		if (errno == ENODEV || errno == ENOENT)
+			fprintf(stderr, "cuse: device not found, try 'modprobe cuse' first\n");
+		else
+			fprintf(stderr, "cuse: failed to open %s: %s\n",
+				devname, strerror(errno));
+		goto err_se;
+	}
+
+	ch = fuse_kern_chan_new(fd);
+	if (!ch) {
+		close(fd);
+		goto err_se;
+	}
+
+	fuse_session_add_chan(se, ch);
+
+	res = fuse_set_signal_handlers(se);
+	if (res == -1)
+		goto err_se;
+
+	res = fuse_daemonize(foreground);
+	if (res == -1)
+		goto err_sig;
+
+	return se;
+
+err_sig:
+	fuse_remove_signal_handlers(se);
+err_se:
+	fuse_session_destroy(se);
+err_args:
+	fuse_opt_free_args(&args);
+	return NULL;
+}
+
+void cuse_lowlevel_teardown(struct fuse_session *se)
+{
+	fuse_remove_signal_handlers(se);
+	fuse_session_destroy(se);
+}
+
+int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+		       const struct cuse_lowlevel_ops *clop, void *userdata)
+{
+	struct fuse_session *se;
+	int multithreaded;
+	int res;
+
+	se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+				 userdata);
+	if (se == NULL)
+		return 1;
+
+	if (multithreaded)
+		res = fuse_session_loop_mt(se);
+	else
+		res = fuse_session_loop(se);
+
+	cuse_lowlevel_teardown(se);
+	if (res == -1)
+		return 1;
+
+	return 0;
+}
diff --git a/fuse/fuse.c b/fuse/fuse.c
new file mode 100644
index 0000000..2c1aa17
--- /dev/null
+++ b/fuse/fuse.c
@@ -0,0 +1,4942 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+
+/* For pthread_rwlock_t */
+#define _GNU_SOURCE
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_lowlevel.h"
+#include "fuse_opt.h"
+#include "fuse_misc.h"
+#include "fuse_common_compat.h"
+#include "fuse_compat.h"
+#include "fuse_kernel.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <time.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include <signal.h>
+#include <assert.h>
+#include <poll.h>
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/file.h>
+
+#ifdef USE_MODULES
+#include <dlfcn.h>
+#endif
+
+#define FUSE_NODE_SLAB 1
+
+#ifndef MAP_ANONYMOUS
+#undef FUSE_NODE_SLAB
+#endif
+
+#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
+#define FUSE_UNKNOWN_INO 0xffffffff
+#define OFFSET_MAX 0x7fffffffffffffffLL
+
+#define NODE_TABLE_MIN_SIZE 8192
+
+struct fuse_config {
+	unsigned int uid;
+	unsigned int gid;
+	unsigned int  umask;
+	double entry_timeout;
+	double negative_timeout;
+	double attr_timeout;
+	double ac_attr_timeout;
+	int ac_attr_timeout_set;
+	int remember;
+	int nopath;
+	int debug;
+	int hard_remove;
+	int use_ino;
+	int readdir_ino;
+	int set_mode;
+	int set_uid;
+	int set_gid;
+	int direct_io;
+	int kernel_cache;
+	int auto_cache;
+	int intr;
+	int intr_signal;
+	int help;
+	char *modules;
+};
+
+struct fuse_fs {
+	struct fuse_operations op;
+	struct fuse_module *m;
+	void *user_data;
+	int compat;
+	int debug;
+};
+
+struct fusemod_so {
+	void *handle;
+	int ctr;
+};
+
+struct lock_queue_element {
+	struct lock_queue_element *next;
+	pthread_cond_t cond;
+	fuse_ino_t nodeid1;
+	const char *name1;
+	char **path1;
+	struct node **wnode1;
+	fuse_ino_t nodeid2;
+	const char *name2;
+	char **path2;
+	struct node **wnode2;
+	int err;
+	bool first_locked : 1;
+	bool second_locked : 1;
+	bool done : 1;
+};
+
+struct node_table {
+	struct node **array;
+	size_t use;
+	size_t size;
+	size_t split;
+};
+
+#define container_of(ptr, type, member) ({                              \
+                        const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+                        (type *)( (char *)__mptr - offsetof(type,member) );})
+
+#define list_entry(ptr, type, member)           \
+        container_of(ptr, type, member)
+
+struct list_head {
+	struct list_head *next;
+	struct list_head *prev;
+};
+
+struct node_slab {
+	struct list_head list;  /* must be the first member */
+	struct list_head freelist;
+	int used;
+};
+
+struct fuse {
+	struct fuse_session *se;
+	struct node_table name_table;
+	struct node_table id_table;
+	struct list_head lru_table;
+	fuse_ino_t ctr;
+	unsigned int generation;
+	unsigned int hidectr;
+	pthread_mutex_t lock;
+	struct fuse_config conf;
+	int intr_installed;
+	struct fuse_fs *fs;
+	int nullpath_ok;
+	int utime_omit_ok;
+	struct lock_queue_element *lockq;
+	int pagesize;
+	struct list_head partial_slabs;
+	struct list_head full_slabs;
+	pthread_t prune_thread;
+};
+
+struct lock {
+	int type;
+	loff_t start;
+	loff_t end;
+	pid_t pid;
+	uint64_t owner;
+	struct lock *next;
+};
+
+struct node {
+	struct node *name_next;
+	struct node *id_next;
+	fuse_ino_t nodeid;
+	unsigned int generation;
+	int refctr;
+	struct node *parent;
+	char *name;
+	uint64_t nlookup;
+	int open_count;
+	struct timespec stat_updated;
+	struct timespec mtime;
+	loff_t size;
+	struct lock *locks;
+	unsigned int is_hidden : 1;
+	unsigned int cache_valid : 1;
+	int treelock;
+	char inline_name[32];
+};
+
+#define TREELOCK_WRITE -1
+#define TREELOCK_WAIT_OFFSET INT_MIN
+
+struct node_lru {
+	struct node node;
+	struct list_head lru;
+	struct timespec forget_time;
+};
+
+struct fuse_dh {
+	pthread_mutex_t lock;
+	struct fuse *fuse;
+	fuse_req_t req;
+	char *contents;
+	int allocated;
+	unsigned len;
+	unsigned size;
+	unsigned needlen;
+	int filled;
+	uint64_t fh;
+	int error;
+	fuse_ino_t nodeid;
+};
+
+/* old dir handle */
+struct fuse_dirhandle {
+	fuse_fill_dir_t filler;
+	void *buf;
+};
+
+struct fuse_context_i {
+	struct fuse_context ctx;
+	fuse_req_t req;
+};
+
+static pthread_key_t fuse_context_key;
+static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+static int fuse_context_ref;
+
+#ifdef USE_MODULES
+static struct fusemod_so *fuse_current_so;
+static struct fuse_module *fuse_modules;
+
+static int fuse_load_so_name(const char *soname)
+{
+	struct fusemod_so *so;
+
+	so = calloc(1, sizeof(struct fusemod_so));
+	if (!so) {
+		fprintf(stderr, "fuse: memory allocation failed\n");
+		return -1;
+	}
+
+	fuse_current_so = so;
+	so->handle = dlopen(soname, RTLD_NOW);
+	fuse_current_so = NULL;
+	if (!so->handle) {
+		fprintf(stderr, "fuse: %s\n", dlerror());
+		goto err;
+	}
+	if (!so->ctr) {
+		fprintf(stderr, "fuse: %s did not register any modules\n",
+			soname);
+		goto err;
+	}
+	return 0;
+
+err:
+	if (so->handle)
+		dlclose(so->handle);
+	free(so);
+	return -1;
+}
+
+static int fuse_load_so_module(const char *module)
+{
+	int res;
+	char *soname = malloc(strlen(module) + 64);
+	if (!soname) {
+		fprintf(stderr, "fuse: memory allocation failed\n");
+		return -1;
+	}
+	sprintf(soname, "libfusemod_%s.so", module);
+	res = fuse_load_so_name(soname);
+	free(soname);
+	return res;
+}
+
+static struct fuse_module *fuse_find_module(const char *module)
+{
+	struct fuse_module *m;
+	for (m = fuse_modules; m; m = m->next) {
+		if (strcmp(module, m->name) == 0) {
+			m->ctr++;
+			break;
+		}
+	}
+	return m;
+}
+
+static struct fuse_module *fuse_get_module(const char *module)
+{
+	struct fuse_module *m;
+
+	pthread_mutex_lock(&fuse_context_lock);
+	m = fuse_find_module(module);
+	if (!m) {
+		int err = fuse_load_so_module(module);
+		if (!err)
+			m = fuse_find_module(module);
+	}
+	pthread_mutex_unlock(&fuse_context_lock);
+	return m;
+}
+
+static void fuse_put_module(struct fuse_module *m)
+{
+	pthread_mutex_lock(&fuse_context_lock);
+	assert(m->ctr > 0);
+	m->ctr--;
+	if (!m->ctr && m->so) {
+		struct fusemod_so *so = m->so;
+		assert(so->ctr > 0);
+		so->ctr--;
+		if (!so->ctr) {
+			struct fuse_module **mp;
+			for (mp = &fuse_modules; *mp;) {
+				if ((*mp)->so == so)
+					*mp = (*mp)->next;
+				else
+					mp = &(*mp)->next;
+			}
+			dlclose(so->handle);
+			free(so);
+		}
+	}
+	pthread_mutex_unlock(&fuse_context_lock);
+}
+#endif
+
+static void init_list_head(struct list_head *list)
+{
+	list->next = list;
+	list->prev = list;
+}
+
+static int list_empty(const struct list_head *head)
+{
+	return head->next == head;
+}
+
+static void list_add(struct list_head *new, struct list_head *prev,
+		     struct list_head *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+static inline void list_add_head(struct list_head *new, struct list_head *head)
+{
+	list_add(new, head, head->next);
+}
+
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+	list_add(new, head->prev, head);
+}
+
+static inline void list_del(struct list_head *entry)
+{
+	struct list_head *prev = entry->prev;
+	struct list_head *next = entry->next;
+
+	next->prev = prev;
+	prev->next = next;
+}
+
+static inline int lru_enabled(struct fuse *f)
+{
+	return f->conf.remember > 0;
+}
+
+static struct node_lru *node_lru(struct node *node)
+{
+	return (struct node_lru *) node;
+}
+
+static size_t get_node_size(struct fuse *f)
+{
+	if (lru_enabled(f))
+		return sizeof(struct node_lru);
+	else
+		return sizeof(struct node);
+}
+
+#ifdef FUSE_NODE_SLAB
+static struct node_slab *list_to_slab(struct list_head *head)
+{
+	return (struct node_slab *) head;
+}
+
+static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+{
+	return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+}
+
+static int alloc_slab(struct fuse *f)
+{
+	void *mem;
+	struct node_slab *slab;
+	char *start;
+	size_t num;
+	size_t i;
+	size_t node_size = get_node_size(f);
+
+	mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+		   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+	if (mem == MAP_FAILED)
+		return -1;
+
+	slab = mem;
+	init_list_head(&slab->freelist);
+	slab->used = 0;
+	num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
+	start = (char *) mem + f->pagesize - num * node_size;
+	for (i = 0; i < num; i++) {
+		struct list_head *n;
+
+		n = (struct list_head *) (start + i * node_size);
+		list_add_tail(n, &slab->freelist);
+	}
+	list_add_tail(&slab->list, &f->partial_slabs);
+
+	return 0;
+}
+
+static struct node *alloc_node(struct fuse *f)
+{
+	struct node_slab *slab;
+	struct list_head *node;
+
+	if (list_empty(&f->partial_slabs)) {
+		int res = alloc_slab(f);
+		if (res != 0)
+			return NULL;
+	}
+	slab = list_to_slab(f->partial_slabs.next);
+	slab->used++;
+	node = slab->freelist.next;
+	list_del(node);
+	if (list_empty(&slab->freelist)) {
+		list_del(&slab->list);
+		list_add_tail(&slab->list, &f->full_slabs);
+	}
+	memset(node, 0, sizeof(struct node));
+
+	return (struct node *) node;
+}
+
+static void free_slab(struct fuse *f, struct node_slab *slab)
+{
+	int res;
+
+	list_del(&slab->list);
+	res = munmap(slab, f->pagesize);
+	if (res == -1)
+		fprintf(stderr, "fuse warning: munmap(%p) failed\n", slab);
+}
+
+static void free_node_mem(struct fuse *f, struct node *node)
+{
+	struct node_slab *slab = node_to_slab(f, node);
+	struct list_head *n = (struct list_head *) node;
+
+	slab->used--;
+	if (slab->used) {
+		if (list_empty(&slab->freelist)) {
+			list_del(&slab->list);
+			list_add_tail(&slab->list, &f->partial_slabs);
+		}
+		list_add_head(n, &slab->freelist);
+	} else {
+		free_slab(f, slab);
+	}
+}
+#else
+static struct node *alloc_node(struct fuse *f)
+{
+	return (struct node *) calloc(1, get_node_size(f));
+}
+
+static void free_node_mem(struct fuse *f, struct node *node)
+{
+	(void) f;
+	free(node);
+}
+#endif
+
+static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+{
+	uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+	uint64_t oldhash = hash % (f->id_table.size / 2);
+
+	if (oldhash >= f->id_table.split)
+		return oldhash;
+	else
+		return hash;
+}
+
+static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+{
+	size_t hash = id_hash(f, nodeid);
+	struct node *node;
+
+	for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+		if (node->nodeid == nodeid)
+			return node;
+
+	return NULL;
+}
+
+static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+{
+	struct node *node = get_node_nocheck(f, nodeid);
+	if (!node) {
+		fprintf(stderr, "fuse internal error: node %llu not found\n",
+			(unsigned long long) nodeid);
+		abort();
+	}
+	return node;
+}
+
+static void curr_time(struct timespec *now);
+static double diff_timespec(const struct timespec *t1,
+			   const struct timespec *t2);
+
+static void remove_node_lru(struct node *node)
+{
+	struct node_lru *lnode = node_lru(node);
+	list_del(&lnode->lru);
+	init_list_head(&lnode->lru);
+}
+
+static void set_forget_time(struct fuse *f, struct node *node)
+{
+	struct node_lru *lnode = node_lru(node);
+
+	list_del(&lnode->lru);
+	list_add_tail(&lnode->lru, &f->lru_table);
+	curr_time(&lnode->forget_time);
+}
+
+static void free_node(struct fuse *f, struct node *node)
+{
+	if (node->name != node->inline_name)
+		free(node->name);
+	free_node_mem(f, node);
+}
+
+static void node_table_reduce(struct node_table *t)
+{
+	size_t newsize = t->size / 2;
+	void *newarray;
+
+	if (newsize < NODE_TABLE_MIN_SIZE)
+		return;
+
+	newarray = realloc(t->array, sizeof(struct node *) * newsize);
+	if (newarray != NULL)
+		t->array = newarray;
+
+	t->size = newsize;
+	t->split = t->size / 2;
+}
+
+static void remerge_id(struct fuse *f)
+{
+	struct node_table *t = &f->id_table;
+	int iter;
+
+	if (t->split == 0)
+		node_table_reduce(t);
+
+	for (iter = 8; t->split > 0 && iter; iter--) {
+		struct node **upper;
+
+		t->split--;
+		upper = &t->array[t->split + t->size / 2];
+		if (*upper) {
+			struct node **nodep;
+
+			for (nodep = &t->array[t->split]; *nodep;
+			     nodep = &(*nodep)->id_next);
+
+			*nodep = *upper;
+			*upper = NULL;
+			break;
+		}
+	}
+}
+
+static void unhash_id(struct fuse *f, struct node *node)
+{
+	struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
+	for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+		if (*nodep == node) {
+			*nodep = node->id_next;
+			f->id_table.use--;
+
+			if(f->id_table.use < f->id_table.size / 4)
+				remerge_id(f);
+			return;
+		}
+}
+
+static int node_table_resize(struct node_table *t)
+{
+	size_t newsize = t->size * 2;
+	void *newarray;
+
+	newarray = realloc(t->array, sizeof(struct node *) * newsize);
+	if (newarray == NULL)
+		return -1;
+
+	t->array = newarray;
+	memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+	t->size = newsize;
+	t->split = 0;
+
+	return 0;
+}
+
+static void rehash_id(struct fuse *f)
+{
+	struct node_table *t = &f->id_table;
+	struct node **nodep;
+	struct node **next;
+	size_t hash;
+
+	if (t->split == t->size / 2)
+		return;
+
+	hash = t->split;
+	t->split++;
+	for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+		struct node *node = *nodep;
+		size_t newhash = id_hash(f, node->nodeid);
+
+		if (newhash != hash) {
+			next = nodep;
+			*nodep = node->id_next;
+			node->id_next = t->array[newhash];
+			t->array[newhash] = node;
+		} else {
+			next = &node->id_next;
+		}
+	}
+	if (t->split == t->size / 2)
+		node_table_resize(t);
+}
+
+static void hash_id(struct fuse *f, struct node *node)
+{
+	size_t hash = id_hash(f, node->nodeid);
+	node->id_next = f->id_table.array[hash];
+	f->id_table.array[hash] = node;
+	f->id_table.use++;
+
+	if (f->id_table.use >= f->id_table.size / 2)
+		rehash_id(f);
+}
+
+static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+			const char *name)
+{
+	uint64_t hash = parent;
+	uint64_t oldhash;
+
+	for (; *name; name++)
+		hash = hash * 31 + (unsigned char) *name;
+
+	hash %= f->name_table.size;
+	oldhash = hash % (f->name_table.size / 2);
+	if (oldhash >= f->name_table.split)
+		return oldhash;
+	else
+		return hash;
+}
+
+static void unref_node(struct fuse *f, struct node *node);
+
+static void remerge_name(struct fuse *f)
+{
+	struct node_table *t = &f->name_table;
+	int iter;
+
+	if (t->split == 0)
+		node_table_reduce(t);
+
+	for (iter = 8; t->split > 0 && iter; iter--) {
+		struct node **upper;
+
+		t->split--;
+		upper = &t->array[t->split + t->size / 2];
+		if (*upper) {
+			struct node **nodep;
+
+			for (nodep = &t->array[t->split]; *nodep;
+			     nodep = &(*nodep)->name_next);
+
+			*nodep = *upper;
+			*upper = NULL;
+			break;
+		}
+	}
+}
+
+static void unhash_name(struct fuse *f, struct node *node)
+{
+	if (node->name) {
+		size_t hash = name_hash(f, node->parent->nodeid, node->name);
+		struct node **nodep = &f->name_table.array[hash];
+
+		for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+			if (*nodep == node) {
+				*nodep = node->name_next;
+				node->name_next = NULL;
+				unref_node(f, node->parent);
+				if (node->name != node->inline_name)
+					free(node->name);
+				node->name = NULL;
+				node->parent = NULL;
+				f->name_table.use--;
+
+				if (f->name_table.use < f->name_table.size / 4)
+					remerge_name(f);
+				return;
+			}
+		fprintf(stderr,
+			"fuse internal error: unable to unhash node: %llu\n",
+			(unsigned long long) node->nodeid);
+		abort();
+	}
+}
+
+static void rehash_name(struct fuse *f)
+{
+	struct node_table *t = &f->name_table;
+	struct node **nodep;
+	struct node **next;
+	size_t hash;
+
+	if (t->split == t->size / 2)
+		return;
+
+	hash = t->split;
+	t->split++;
+	for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+		struct node *node = *nodep;
+		size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
+		if (newhash != hash) {
+			next = nodep;
+			*nodep = node->name_next;
+			node->name_next = t->array[newhash];
+			t->array[newhash] = node;
+		} else {
+			next = &node->name_next;
+		}
+	}
+	if (t->split == t->size / 2)
+		node_table_resize(t);
+}
+
+static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+		     const char *name)
+{
+	size_t hash = name_hash(f, parentid, name);
+	struct node *parent = get_node(f, parentid);
+	if (strlen(name) < sizeof(node->inline_name)) {
+		strcpy(node->inline_name, name);
+		node->name = node->inline_name;
+	} else {
+		node->name = strdup(name);
+		if (node->name == NULL)
+			return -1;
+	}
+
+	parent->refctr ++;
+	node->parent = parent;
+	node->name_next = f->name_table.array[hash];
+	f->name_table.array[hash] = node;
+	f->name_table.use++;
+
+	if (f->name_table.use >= f->name_table.size / 2)
+		rehash_name(f);
+
+	return 0;
+}
+
+static void delete_node(struct fuse *f, struct node *node)
+{
+	if (f->conf.debug)
+		fprintf(stderr, "DELETE: %llu\n",
+			(unsigned long long) node->nodeid);
+
+	assert(node->treelock == 0);
+	unhash_name(f, node);
+	if (lru_enabled(f))
+		remove_node_lru(node);
+	unhash_id(f, node);
+	free_node(f, node);
+}
+
+static void unref_node(struct fuse *f, struct node *node)
+{
+	assert(node->refctr > 0);
+	node->refctr --;
+	if (!node->refctr)
+		delete_node(f, node);
+}
+
+static fuse_ino_t next_id(struct fuse *f)
+{
+	do {
+		f->ctr = (f->ctr + 1) & 0xffffffff;
+		if (!f->ctr)
+			f->generation ++;
+	} while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+		 get_node_nocheck(f, f->ctr) != NULL);
+	return f->ctr;
+}
+
+static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+				const char *name)
+{
+	size_t hash = name_hash(f, parent, name);
+	struct node *node;
+
+	for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+		if (node->parent->nodeid == parent &&
+		    strcmp(node->name, name) == 0)
+			return node;
+
+	return NULL;
+}
+
+static void inc_nlookup(struct node *node)
+{
+	if (!node->nlookup)
+		node->refctr++;
+	node->nlookup++;
+}
+
+static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+			      const char *name)
+{
+	struct node *node;
+
+	pthread_mutex_lock(&f->lock);
+	if (!name)
+		node = get_node(f, parent);
+	else
+		node = lookup_node(f, parent, name);
+	if (node == NULL) {
+		node = alloc_node(f);
+		if (node == NULL)
+			goto out_err;
+
+		node->nodeid = next_id(f);
+		node->generation = f->generation;
+		if (f->conf.remember)
+			inc_nlookup(node);
+
+		if (hash_name(f, node, parent, name) == -1) {
+			free_node(f, node);
+			node = NULL;
+			goto out_err;
+		}
+		hash_id(f, node);
+		if (lru_enabled(f)) {
+			struct node_lru *lnode = node_lru(node);
+			init_list_head(&lnode->lru);
+		}
+	} else if (lru_enabled(f) && node->nlookup == 1) {
+		remove_node_lru(node);
+	}
+	inc_nlookup(node);
+out_err:
+	pthread_mutex_unlock(&f->lock);
+	return node;
+}
+
+static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+{
+	size_t len = strlen(name);
+
+	if (s - len <= *buf) {
+		unsigned pathlen = *bufsize - (s - *buf);
+		unsigned newbufsize = *bufsize;
+		char *newbuf;
+
+		while (newbufsize < pathlen + len + 1) {
+			if (newbufsize >= 0x80000000)
+				newbufsize = 0xffffffff;
+			else
+				newbufsize *= 2;
+		}
+
+		newbuf = realloc(*buf, newbufsize);
+		if (newbuf == NULL)
+			return NULL;
+
+		*buf = newbuf;
+		s = newbuf + newbufsize - pathlen;
+		memmove(s, newbuf + *bufsize - pathlen, pathlen);
+		*bufsize = newbufsize;
+	}
+	s -= len;
+	strncpy(s, name, len);
+	s--;
+	*s = '/';
+
+	return s;
+}
+
+static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+			struct node *end)
+{
+	struct node *node;
+
+	if (wnode) {
+		assert(wnode->treelock == TREELOCK_WRITE);
+		wnode->treelock = 0;
+	}
+
+	for (node = get_node(f, nodeid);
+	     node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+		assert(node->treelock != 0);
+		assert(node->treelock != TREELOCK_WAIT_OFFSET);
+		assert(node->treelock != TREELOCK_WRITE);
+		node->treelock--;
+		if (node->treelock == TREELOCK_WAIT_OFFSET)
+			node->treelock = 0;
+	}
+}
+
+static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+			char **path, struct node **wnodep, bool need_lock)
+{
+	unsigned bufsize = 256;
+	char *buf;
+	char *s;
+	struct node *node;
+	struct node *wnode = NULL;
+	int err;
+
+	*path = NULL;
+
+	err = -ENOMEM;
+	buf = malloc(bufsize);
+	if (buf == NULL)
+		goto out_err;
+
+	s = buf + bufsize - 1;
+	*s = '\0';
+
+	if (name != NULL) {
+		s = add_name(&buf, &bufsize, s, name);
+		err = -ENOMEM;
+		if (s == NULL)
+			goto out_free;
+	}
+
+	if (wnodep) {
+		assert(need_lock);
+		wnode = lookup_node(f, nodeid, name);
+		if (wnode) {
+			if (wnode->treelock != 0) {
+				if (wnode->treelock > 0)
+					wnode->treelock += TREELOCK_WAIT_OFFSET;
+				err = -EAGAIN;
+				goto out_free;
+			}
+			wnode->treelock = TREELOCK_WRITE;
+		}
+	}
+
+	for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+	     node = node->parent) {
+		err = -ENOENT;
+		if (node->name == NULL || node->parent == NULL)
+			goto out_unlock;
+
+		err = -ENOMEM;
+		s = add_name(&buf, &bufsize, s, node->name);
+		if (s == NULL)
+			goto out_unlock;
+
+		if (need_lock) {
+			err = -EAGAIN;
+			if (node->treelock < 0)
+				goto out_unlock;
+
+			node->treelock++;
+		}
+	}
+
+	if (s[0])
+		memmove(buf, s, bufsize - (s - buf));
+	else
+		strcpy(buf, "/");
+
+	*path = buf;
+	if (wnodep)
+		*wnodep = wnode;
+
+	return 0;
+
+ out_unlock:
+	if (need_lock)
+		unlock_path(f, nodeid, wnode, node);
+ out_free:
+	free(buf);
+
+ out_err:
+	return err;
+}
+
+static void queue_element_unlock(struct fuse *f, struct lock_queue_element *qe)
+{
+	struct node *wnode;
+
+	if (qe->first_locked) {
+		wnode = qe->wnode1 ? *qe->wnode1 : NULL;
+		unlock_path(f, qe->nodeid1, wnode, NULL);
+		qe->first_locked = false;
+	}
+	if (qe->second_locked) {
+		wnode = qe->wnode2 ? *qe->wnode2 : NULL;
+		unlock_path(f, qe->nodeid2, wnode, NULL);
+		qe->second_locked = false;
+	}
+}
+
+static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+{
+	int err;
+	bool first = (qe == f->lockq);
+
+	if (!qe->path1) {
+		/* Just waiting for it to be unlocked */
+		if (get_node(f, qe->nodeid1)->treelock == 0)
+			pthread_cond_signal(&qe->cond);
+
+		return;
+	}
+
+	if (!qe->first_locked) {
+		err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+				   qe->wnode1, true);
+		if (!err)
+			qe->first_locked = true;
+		else if (err != -EAGAIN)
+			goto err_unlock;
+	}
+	if (!qe->second_locked && qe->path2) {
+		err = try_get_path(f, qe->nodeid2, qe->name2, qe->path2,
+				   qe->wnode2, true);
+		if (!err)
+			qe->second_locked = true;
+		else if (err != -EAGAIN)
+			goto err_unlock;
+	}
+
+	if (qe->first_locked && (qe->second_locked || !qe->path2)) {
+		err = 0;
+		goto done;
+	}
+
+	/*
+	 * Only let the first element be partially locked otherwise there could
+	 * be a deadlock.
+	 *
+	 * But do allow the first element to be partially locked to prevent
+	 * starvation.
+	 */
+	if (!first)
+		queue_element_unlock(f, qe);
+
+	/* keep trying */
+	return;
+
+err_unlock:
+	queue_element_unlock(f, qe);
+done:
+	qe->err = err;
+	qe->done = true;
+	pthread_cond_signal(&qe->cond);
+}
+
+static void wake_up_queued(struct fuse *f)
+{
+	struct lock_queue_element *qe;
+
+	for (qe = f->lockq; qe != NULL; qe = qe->next)
+		queue_element_wakeup(f, qe);
+}
+
+static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+		       const char *name, bool wr)
+{
+	if (f->conf.debug) {
+		struct node *wnode = NULL;
+
+		if (wr)
+			wnode = lookup_node(f, nodeid, name);
+
+		if (wnode)
+			fprintf(stderr, "%s %li (w)\n",	msg, wnode->nodeid);
+		else
+			fprintf(stderr, "%s %li\n", msg, nodeid);
+	}
+}
+
+static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+{
+	struct lock_queue_element **qp;
+
+	qe->done = false;
+	qe->first_locked = false;
+	qe->second_locked = false;
+	pthread_cond_init(&qe->cond, NULL);
+	qe->next = NULL;
+	for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+	*qp = qe;
+}
+
+static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+{
+	struct lock_queue_element **qp;
+
+	pthread_cond_destroy(&qe->cond);
+	for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+	*qp = qe->next;
+}
+
+static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+{
+	queue_path(f, qe);
+
+	do {
+		pthread_cond_wait(&qe->cond, &f->lock);
+	} while (!qe->done);
+
+	dequeue_path(f, qe);
+
+	return qe->err;
+}
+
+static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+			   char **path, struct node **wnode)
+{
+	int err;
+
+	pthread_mutex_lock(&f->lock);
+	err = try_get_path(f, nodeid, name, path, wnode, true);
+	if (err == -EAGAIN) {
+		struct lock_queue_element qe = {
+			.nodeid1 = nodeid,
+			.name1 = name,
+			.path1 = path,
+			.wnode1 = wnode,
+		};
+		debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+		err = wait_path(f, &qe);
+		debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+	}
+	pthread_mutex_unlock(&f->lock);
+
+	return err;
+}
+
+static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+{
+	return get_path_common(f, nodeid, NULL, path, NULL);
+}
+
+static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+{
+	int err = 0;
+
+	if (f->conf.nopath) {
+		*path = NULL;
+	} else {
+		err = get_path_common(f, nodeid, NULL, path, NULL);
+		if (err == -ENOENT && f->nullpath_ok)
+			err = 0;
+	}
+
+	return err;
+}
+
+static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+			 char **path)
+{
+	return get_path_common(f, nodeid, name, path, NULL);
+}
+
+static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+			   char **path, struct node **wnode)
+{
+	return get_path_common(f, nodeid, name, path, wnode);
+}
+
+static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+			 fuse_ino_t nodeid2, const char *name2,
+			 char **path1, char **path2,
+			 struct node **wnode1, struct node **wnode2)
+{
+	int err;
+
+	/* FIXME: locking two paths needs deadlock checking */
+	err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+	if (!err) {
+		err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+		if (err) {
+			struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
+			unlock_path(f, nodeid1, wn1, NULL);
+			free(*path1);
+		}
+	}
+	return err;
+}
+
+static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+		     fuse_ino_t nodeid2, const char *name2,
+		     char **path1, char **path2,
+		     struct node **wnode1, struct node **wnode2)
+{
+	int err;
+
+	pthread_mutex_lock(&f->lock);
+	err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+			    path1, path2, wnode1, wnode2);
+	if (err == -EAGAIN) {
+		struct lock_queue_element qe = {
+			.nodeid1 = nodeid1,
+			.name1 = name1,
+			.path1 = path1,
+			.wnode1 = wnode1,
+			.nodeid2 = nodeid2,
+			.name2 = name2,
+			.path2 = path2,
+			.wnode2 = wnode2,
+		};
+
+		debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+		debug_path(f, "      PATH2", nodeid2, name2, !!wnode2);
+		err = wait_path(f, &qe);
+		debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+		debug_path(f, "        PATH2", nodeid2, name2, !!wnode2);
+	}
+	pthread_mutex_unlock(&f->lock);
+
+	return err;
+}
+
+static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+			     struct node *wnode, char *path)
+{
+	pthread_mutex_lock(&f->lock);
+	unlock_path(f, nodeid, wnode, NULL);
+	if (f->lockq)
+		wake_up_queued(f);
+	pthread_mutex_unlock(&f->lock);
+	free(path);
+}
+
+static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+{
+	if (path)
+		free_path_wrlock(f, nodeid, NULL, path);
+}
+
+static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+		       struct node *wnode1, struct node *wnode2,
+		       char *path1, char *path2)
+{
+	pthread_mutex_lock(&f->lock);
+	unlock_path(f, nodeid1, wnode1, NULL);
+	unlock_path(f, nodeid2, wnode2, NULL);
+	wake_up_queued(f);
+	pthread_mutex_unlock(&f->lock);
+	free(path1);
+	free(path2);
+}
+
+static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+{
+	struct node *node;
+	if (nodeid == FUSE_ROOT_ID)
+		return;
+	pthread_mutex_lock(&f->lock);
+	node = get_node(f, nodeid);
+
+	/*
+	 * Node may still be locked due to interrupt idiocy in open,
+	 * create and opendir
+	 */
+	while (node->nlookup == nlookup && node->treelock) {
+		struct lock_queue_element qe = {
+			.nodeid1 = nodeid,
+		};
+
+		debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+		queue_path(f, &qe);
+
+		do {
+			pthread_cond_wait(&qe.cond, &f->lock);
+		} while (node->nlookup == nlookup && node->treelock);
+
+		dequeue_path(f, &qe);
+		debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+	}
+
+	assert(node->nlookup >= nlookup);
+	node->nlookup -= nlookup;
+	if (!node->nlookup) {
+		unref_node(f, node);
+	} else if (lru_enabled(f) && node->nlookup == 1) {
+		set_forget_time(f, node);
+	}
+	pthread_mutex_unlock(&f->lock);
+}
+
+static void unlink_node(struct fuse *f, struct node *node)
+{
+	if (f->conf.remember) {
+		assert(node->nlookup > 1);
+		node->nlookup--;
+	}
+	unhash_name(f, node);
+}
+
+static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+{
+	struct node *node;
+
+	pthread_mutex_lock(&f->lock);
+	node = lookup_node(f, dir, name);
+	if (node != NULL)
+		unlink_node(f, node);
+	pthread_mutex_unlock(&f->lock);
+}
+
+static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+		       fuse_ino_t newdir, const char *newname, int hide)
+{
+	struct node *node;
+	struct node *newnode;
+	int err = 0;
+
+	pthread_mutex_lock(&f->lock);
+	node  = lookup_node(f, olddir, oldname);
+	newnode	 = lookup_node(f, newdir, newname);
+	if (node == NULL)
+		goto out;
+
+	if (newnode != NULL) {
+		if (hide) {
+			fprintf(stderr, "fuse: hidden file got created during hiding\n");
+			err = -EBUSY;
+			goto out;
+		}
+		unlink_node(f, newnode);
+	}
+
+	unhash_name(f, node);
+	if (hash_name(f, node, newdir, newname) == -1) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	if (hide)
+		node->is_hidden = 1;
+
+out:
+	pthread_mutex_unlock(&f->lock);
+	return err;
+}
+
+static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+{
+	if (!f->conf.use_ino)
+		stbuf->st_ino = nodeid;
+	if (f->conf.set_mode)
+		stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+				 (0777 & ~f->conf.umask);
+	if (f->conf.set_uid)
+		stbuf->st_uid = f->conf.uid;
+	if (f->conf.set_gid)
+		stbuf->st_gid = f->conf.gid;
+}
+
+static struct fuse *req_fuse(fuse_req_t req)
+{
+	return (struct fuse *) fuse_req_userdata(req);
+}
+
+static void fuse_intr_sighandler(int sig)
+{
+	(void) sig;
+	/* Nothing to do */
+}
+
+struct fuse_intr_data {
+	pthread_t id;
+	pthread_cond_t cond;
+	int finished;
+};
+
+static void fuse_interrupt(fuse_req_t req, void *d_)
+{
+	struct fuse_intr_data *d = d_;
+	struct fuse *f = req_fuse(req);
+
+	if (d->id == pthread_self())
+		return;
+
+	pthread_mutex_lock(&f->lock);
+	while (!d->finished) {
+		struct timeval now;
+		struct timespec timeout;
+
+		pthread_kill(d->id, f->conf.intr_signal);
+		gettimeofday(&now, NULL);
+		timeout.tv_sec = now.tv_sec + 1;
+		timeout.tv_nsec = now.tv_usec * 1000;
+		pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+	}
+	pthread_mutex_unlock(&f->lock);
+}
+
+static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+				     struct fuse_intr_data *d)
+{
+	pthread_mutex_lock(&f->lock);
+	d->finished = 1;
+	pthread_cond_broadcast(&d->cond);
+	pthread_mutex_unlock(&f->lock);
+	fuse_req_interrupt_func(req, NULL, NULL);
+	pthread_cond_destroy(&d->cond);
+}
+
+static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+{
+	d->id = pthread_self();
+	pthread_cond_init(&d->cond, NULL);
+	d->finished = 0;
+	fuse_req_interrupt_func(req, fuse_interrupt, d);
+}
+
+static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+					 struct fuse_intr_data *d)
+{
+	if (f->conf.intr)
+		fuse_do_finish_interrupt(f, req, d);
+}
+
+static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+					  struct fuse_intr_data *d)
+{
+	if (f->conf.intr)
+		fuse_do_prepare_interrupt(req, d);
+}
+
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
+
+static int fuse_compat_open(struct fuse_fs *fs, const char *path,
+			    struct fuse_file_info *fi)
+{
+	int err;
+	if (!fs->compat || fs->compat >= 25)
+		err = fs->op.open(path, fi);
+	else if (fs->compat == 22) {
+		struct fuse_file_info_compat tmp;
+		memcpy(&tmp, fi, sizeof(tmp));
+		err = ((struct fuse_operations_compat22 *) &fs->op)->open(path,
+									  &tmp);
+		memcpy(fi, &tmp, sizeof(tmp));
+		fi->fh = tmp.fh;
+	} else
+		err = ((struct fuse_operations_compat2 *) &fs->op)
+			->open(path, fi->flags);
+	return err;
+}
+
+static int fuse_compat_release(struct fuse_fs *fs, const char *path,
+			       struct fuse_file_info *fi)
+{
+	if (!fs->compat || fs->compat >= 22)
+		return fs->op.release(path, fi);
+	else
+		return ((struct fuse_operations_compat2 *) &fs->op)
+			->release(path, fi->flags);
+}
+
+static int fuse_compat_opendir(struct fuse_fs *fs, const char *path,
+			       struct fuse_file_info *fi)
+{
+	if (!fs->compat || fs->compat >= 25)
+		return fs->op.opendir(path, fi);
+	else {
+		int err;
+		struct fuse_file_info_compat tmp;
+		memcpy(&tmp, fi, sizeof(tmp));
+		err = ((struct fuse_operations_compat22 *) &fs->op)
+			->opendir(path, &tmp);
+		memcpy(fi, &tmp, sizeof(tmp));
+		fi->fh = tmp.fh;
+		return err;
+	}
+}
+
+static void convert_statfs_compat(struct fuse_statfs_compat1 *compatbuf,
+				  struct statvfs *stbuf)
+{
+	stbuf->f_bsize	 = compatbuf->block_size;
+	stbuf->f_blocks	 = compatbuf->blocks;
+	stbuf->f_bfree	 = compatbuf->blocks_free;
+	stbuf->f_bavail	 = compatbuf->blocks_free;
+	stbuf->f_files	 = compatbuf->files;
+	stbuf->f_ffree	 = compatbuf->files_free;
+	stbuf->f_namemax = compatbuf->namelen;
+}
+
+static void convert_statfs_old(struct statfs *oldbuf, struct statvfs *stbuf)
+{
+	stbuf->f_bsize	 = oldbuf->f_bsize;
+	stbuf->f_blocks	 = oldbuf->f_blocks;
+	stbuf->f_bfree	 = oldbuf->f_bfree;
+	stbuf->f_bavail	 = oldbuf->f_bavail;
+	stbuf->f_files	 = oldbuf->f_files;
+	stbuf->f_ffree	 = oldbuf->f_ffree;
+	stbuf->f_namemax = oldbuf->f_namelen;
+}
+
+static int fuse_compat_statfs(struct fuse_fs *fs, const char *path,
+			      struct statvfs *buf)
+{
+	int err;
+
+	if (!fs->compat || fs->compat >= 25) {
+		err = fs->op.statfs(fs->compat == 25 ? "/" : path, buf);
+	} else if (fs->compat > 11) {
+		struct statfs oldbuf;
+		err = ((struct fuse_operations_compat22 *) &fs->op)
+			->statfs("/", &oldbuf);
+		if (!err)
+			convert_statfs_old(&oldbuf, buf);
+	} else {
+		struct fuse_statfs_compat1 compatbuf;
+		memset(&compatbuf, 0, sizeof(struct fuse_statfs_compat1));
+		err = ((struct fuse_operations_compat1 *) &fs->op)
+			->statfs(&compatbuf);
+		if (!err)
+			convert_statfs_compat(&compatbuf, buf);
+	}
+	return err;
+}
+
+#else /* __FreeBSD__ || __NetBSD__ */
+
+static inline int fuse_compat_open(struct fuse_fs *fs, char *path,
+				   struct fuse_file_info *fi)
+{
+	return fs->op.open(path, fi);
+}
+
+static inline int fuse_compat_release(struct fuse_fs *fs, const char *path,
+				      struct fuse_file_info *fi)
+{
+	return fs->op.release(path, fi);
+}
+
+static inline int fuse_compat_opendir(struct fuse_fs *fs, const char *path,
+				      struct fuse_file_info *fi)
+{
+	return fs->op.opendir(path, fi);
+}
+
+static inline int fuse_compat_statfs(struct fuse_fs *fs, const char *path,
+				     struct statvfs *buf)
+{
+	return fs->op.statfs(fs->compat == 25 ? "/" : path, buf);
+}
+
+#endif /* __FreeBSD__ || __NetBSD__ */
+
+int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.getattr) {
+		if (fs->debug)
+			fprintf(stderr, "getattr %s\n", path);
+
+		return fs->op.getattr(path, buf);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_fgetattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+		     struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.fgetattr) {
+		if (fs->debug)
+			fprintf(stderr, "fgetattr[%llu] %s\n",
+				(unsigned long long) fi->fh, path);
+
+		return fs->op.fgetattr(path, buf, fi);
+	} else if (path && fs->op.getattr) {
+		if (fs->debug)
+			fprintf(stderr, "getattr %s\n", path);
+
+		return fs->op.getattr(path, buf);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+		   const char *newpath)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.rename) {
+		if (fs->debug)
+			fprintf(stderr, "rename %s %s\n", oldpath, newpath);
+
+		return fs->op.rename(oldpath, newpath);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.unlink) {
+		if (fs->debug)
+			fprintf(stderr, "unlink %s\n", path);
+
+		return fs->op.unlink(path);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.rmdir) {
+		if (fs->debug)
+			fprintf(stderr, "rmdir %s\n", path);
+
+		return fs->op.rmdir(path);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.symlink) {
+		if (fs->debug)
+			fprintf(stderr, "symlink %s %s\n", linkname, path);
+
+		return fs->op.symlink(linkname, path);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.link) {
+		if (fs->debug)
+			fprintf(stderr, "link %s %s\n", oldpath, newpath);
+
+		return fs->op.link(oldpath, newpath);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_release(struct fuse_fs *fs,	 const char *path,
+		    struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.release) {
+		if (fs->debug)
+			fprintf(stderr, "release%s[%llu] flags: 0x%x\n",
+				fi->flush ? "+flush" : "",
+				(unsigned long long) fi->fh, fi->flags);
+
+		return fuse_compat_release(fs, path, fi);
+	} else {
+		return 0;
+	}
+}
+
+int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+		    struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.opendir) {
+		int err;
+
+		if (fs->debug)
+			fprintf(stderr, "opendir flags: 0x%x %s\n", fi->flags,
+				path);
+
+		err = fuse_compat_opendir(fs, path, fi);
+
+		if (fs->debug && !err)
+			fprintf(stderr, "   opendir[%lli] flags: 0x%x %s\n",
+				(unsigned long long) fi->fh, fi->flags, path);
+
+		return err;
+	} else {
+		return 0;
+	}
+}
+
+int fuse_fs_open(struct fuse_fs *fs, const char *path,
+		 struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.open) {
+		int err;
+
+		if (fs->debug)
+			fprintf(stderr, "open flags: 0x%x %s\n", fi->flags,
+				path);
+
+		err = fuse_compat_open(fs, path, fi);
+
+		if (fs->debug && !err)
+			fprintf(stderr, "   open[%lli] flags: 0x%x %s\n",
+				(unsigned long long) fi->fh, fi->flags, path);
+
+		return err;
+	} else {
+		return 0;
+	}
+}
+
+static void fuse_free_buf(struct fuse_bufvec *buf)
+{
+	if (buf != NULL) {
+		size_t i;
+
+		for (i = 0; i < buf->count; i++)
+			free(buf->buf[i].mem);
+		free(buf);
+	}
+}
+
+int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+		     struct fuse_bufvec **bufp, size_t size, loff_t off,
+		     struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.read || fs->op.read_buf) {
+		int res;
+
+		if (fs->debug)
+			fprintf(stderr,
+				"read[%llu] %zu bytes from %llu flags: 0x%x\n",
+				(unsigned long long) fi->fh,
+				size, (unsigned long long) off, fi->flags);
+
+		if (fs->op.read_buf) {
+			res = fs->op.read_buf(path, bufp, size, off, fi);
+		} else {
+			struct fuse_bufvec *buf;
+			void *mem;
+
+			buf = malloc(sizeof(struct fuse_bufvec));
+			if (buf == NULL)
+				return -ENOMEM;
+
+			mem = malloc(size);
+			if (mem == NULL) {
+				free(buf);
+				return -ENOMEM;
+			}
+			*buf = FUSE_BUFVEC_INIT(size);
+			buf->buf[0].mem = mem;
+			*bufp = buf;
+
+			res = fs->op.read(path, mem, size, off, fi);
+			if (res >= 0)
+				buf->buf[0].size = res;
+		}
+
+		if (fs->debug && res >= 0)
+			fprintf(stderr, "   read[%llu] %zu bytes from %llu\n",
+				(unsigned long long) fi->fh,
+				fuse_buf_size(*bufp),
+				(unsigned long long) off);
+		if (res >= 0 && fuse_buf_size(*bufp) > (int) size)
+			fprintf(stderr, "fuse: read too many bytes\n");
+
+		if (res < 0)
+			return res;
+
+		return 0;
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+		 loff_t off, struct fuse_file_info *fi)
+{
+	int res;
+	struct fuse_bufvec *buf = NULL;
+
+	res = fuse_fs_read_buf(fs, path, &buf, size, off, fi);
+	if (res == 0) {
+		struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
+		dst.buf[0].mem = mem;
+		res = fuse_buf_copy(&dst, buf, 0);
+	}
+	fuse_free_buf(buf);
+
+	return res;
+}
+
+int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+		      struct fuse_bufvec *buf, loff_t off,
+		      struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.write_buf || fs->op.write) {
+		int res;
+		size_t size = fuse_buf_size(buf);
+
+		assert(buf->idx == 0 && buf->off == 0);
+		if (fs->debug)
+			fprintf(stderr,
+				"write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+				fi->writepage ? "page" : "",
+				(unsigned long long) fi->fh,
+				size,
+				(unsigned long long) off,
+				fi->flags);
+
+		if (fs->op.write_buf) {
+			res = fs->op.write_buf(path, buf, off, fi);
+		} else {
+			void *mem = NULL;
+			struct fuse_buf *flatbuf;
+			struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
+			if (buf->count == 1 &&
+			    !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+				flatbuf = &buf->buf[0];
+			} else {
+				res = -ENOMEM;
+				mem = malloc(size);
+				if (mem == NULL)
+					goto out;
+
+				tmp.buf[0].mem = mem;
+				res = fuse_buf_copy(&tmp, buf, 0);
+				if (res <= 0)
+					goto out_free;
+
+				tmp.buf[0].size = res;
+				flatbuf = &tmp.buf[0];
+			}
+
+			res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+					   off, fi);
+out_free:
+			free(mem);
+		}
+out:
+		if (fs->debug && res >= 0)
+			fprintf(stderr, "   write%s[%llu] %u bytes to %llu\n",
+				fi->writepage ? "page" : "",
+				(unsigned long long) fi->fh, res,
+				(unsigned long long) off);
+		if (res > (int) size)
+			fprintf(stderr, "fuse: wrote too many bytes\n");
+
+		return res;
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+		  size_t size, loff_t off, struct fuse_file_info *fi)
+{
+	struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
+	bufv.buf[0].mem = (void *) mem;
+
+	return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+}
+
+int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+		  struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.fsync) {
+		if (fs->debug)
+			fprintf(stderr, "fsync[%llu] datasync: %i\n",
+				(unsigned long long) fi->fh, datasync);
+
+		return fs->op.fsync(path, datasync, fi);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+		     struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.fsyncdir) {
+		if (fs->debug)
+			fprintf(stderr, "fsyncdir[%llu] datasync: %i\n",
+				(unsigned long long) fi->fh, datasync);
+
+		return fs->op.fsyncdir(path, datasync, fi);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+		  struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.flush) {
+		if (fs->debug)
+			fprintf(stderr, "flush[%llu]\n",
+				(unsigned long long) fi->fh);
+
+		return fs->op.flush(path, fi);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.statfs) {
+		if (fs->debug)
+			fprintf(stderr, "statfs %s\n", path);
+
+		return fuse_compat_statfs(fs, path, buf);
+	} else {
+		buf->f_namemax = 255;
+		buf->f_bsize = 512;
+		return 0;
+	}
+}
+
+int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+		       struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.releasedir) {
+		if (fs->debug)
+			fprintf(stderr, "releasedir[%llu] flags: 0x%x\n",
+				(unsigned long long) fi->fh, fi->flags);
+
+		return fs->op.releasedir(path, fi);
+	} else {
+		return 0;
+	}
+}
+
+static int fill_dir_old(struct fuse_dirhandle *dh, const char *name, int type,
+			ino_t ino)
+{
+	int res;
+	struct stat stbuf;
+
+	memset(&stbuf, 0, sizeof(stbuf));
+	stbuf.st_mode = type << 12;
+	stbuf.st_ino = ino;
+
+	res = dh->filler(dh->buf, name, &stbuf, 0);
+	return res ? -ENOMEM : 0;
+}
+
+int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+		    fuse_fill_dir_t filler, loff_t off,
+		    struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.readdir) {
+		if (fs->debug)
+			fprintf(stderr, "readdir[%llu] from %llu\n",
+				(unsigned long long) fi->fh,
+				(unsigned long long) off);
+
+		return fs->op.readdir(path, buf, filler, off, fi);
+	} else if (fs->op.getdir) {
+		struct fuse_dirhandle dh;
+
+		if (fs->debug)
+			fprintf(stderr, "getdir[%llu]\n",
+				(unsigned long long) fi->fh);
+
+		dh.filler = filler;
+		dh.buf = buf;
+		return fs->op.getdir(path, &dh, fill_dir_old);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+		   struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.create) {
+		int err;
+
+		if (fs->debug)
+			fprintf(stderr,
+				"create flags: 0x%x %s 0%o umask=0%03o\n",
+				fi->flags, path, mode,
+				fuse_get_context()->umask);
+
+		err = fs->op.create(path, mode, fi);
+
+		if (fs->debug && !err)
+			fprintf(stderr, "   create[%llu] flags: 0x%x %s\n",
+				(unsigned long long) fi->fh, fi->flags, path);
+
+		return err;
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+		 struct fuse_file_info *fi, int cmd, struct flock *lock)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.lock) {
+		if (fs->debug)
+			fprintf(stderr, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+				(unsigned long long) fi->fh,
+				(cmd == F_GETLK ? "F_GETLK" :
+				 (cmd == F_SETLK ? "F_SETLK" :
+				  (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+				(lock->l_type == F_RDLCK ? "F_RDLCK" :
+				 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+				  (lock->l_type == F_UNLCK ? "F_UNLCK" :
+				   "???"))),
+				(unsigned long long) lock->l_start,
+				(unsigned long long) lock->l_len,
+				(unsigned long long) lock->l_pid);
+
+		return fs->op.lock(path, fi, cmd, lock);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+		  struct fuse_file_info *fi, int op)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.flock) {
+		if (fs->debug) {
+			int xop = op & ~LOCK_NB;
+
+			fprintf(stderr, "lock[%llu] %s%s\n",
+				(unsigned long long) fi->fh,
+				xop == LOCK_SH ? "LOCK_SH" :
+				(xop == LOCK_EX ? "LOCK_EX" :
+				 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+				(op & LOCK_NB) ? "|LOCK_NB" : "");
+		}
+		return fs->op.flock(path, fi, op);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.chown) {
+		if (fs->debug)
+			fprintf(stderr, "chown %s %lu %lu\n", path,
+				(unsigned long) uid, (unsigned long) gid);
+
+		return fs->op.chown(path, uid, gid);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_truncate(struct fuse_fs *fs, const char *path, loff_t size)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.truncate) {
+		if (fs->debug)
+			fprintf(stderr, "truncate %s %llu\n", path,
+				(unsigned long long) size);
+
+		return fs->op.truncate(path, size);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_ftruncate(struct fuse_fs *fs, const char *path, loff_t size,
+		      struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.ftruncate) {
+		if (fs->debug)
+			fprintf(stderr, "ftruncate[%llu] %llu\n",
+				(unsigned long long) fi->fh,
+				(unsigned long long) size);
+
+		return fs->op.ftruncate(path, size, fi);
+	} else if (path && fs->op.truncate) {
+		if (fs->debug)
+			fprintf(stderr, "truncate %s %llu\n", path,
+				(unsigned long long) size);
+
+		return fs->op.truncate(path, size);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+		    const struct timespec tv[2])
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.utimens) {
+		if (fs->debug)
+			fprintf(stderr, "utimens %s %li.%09lu %li.%09lu\n",
+				path, tv[0].tv_sec, tv[0].tv_nsec,
+				tv[1].tv_sec, tv[1].tv_nsec);
+
+		return fs->op.utimens(path, tv);
+	} else if(fs->op.utime) {
+		struct utimbuf buf;
+
+		if (fs->debug)
+			fprintf(stderr, "utime %s %li %li\n", path,
+				tv[0].tv_sec, tv[1].tv_sec);
+
+		buf.actime = tv[0].tv_sec;
+		buf.modtime = tv[1].tv_sec;
+		return fs->op.utime(path, &buf);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.access) {
+		if (fs->debug)
+			fprintf(stderr, "access %s 0%o\n", path, mask);
+
+		return fs->op.access(path, mask);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+		     size_t len)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.readlink) {
+		if (fs->debug)
+			fprintf(stderr, "readlink %s %lu\n", path,
+				(unsigned long) len);
+
+		return fs->op.readlink(path, buf, len);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+		  dev_t rdev)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.mknod) {
+		if (fs->debug)
+			fprintf(stderr, "mknod %s 0%o 0x%llx umask=0%03o\n",
+				path, mode, (unsigned long long) rdev,
+				fuse_get_context()->umask);
+
+		return fs->op.mknod(path, mode, rdev);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.mkdir) {
+		if (fs->debug)
+			fprintf(stderr, "mkdir %s 0%o umask=0%03o\n",
+				path, mode, fuse_get_context()->umask);
+
+		return fs->op.mkdir(path, mode);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+		     const char *value, size_t size, int flags)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.setxattr) {
+		if (fs->debug)
+			fprintf(stderr, "setxattr %s %s %lu 0x%x\n",
+				path, name, (unsigned long) size, flags);
+
+		return fs->op.setxattr(path, name, value, size, flags);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+		     char *value, size_t size)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.getxattr) {
+		if (fs->debug)
+			fprintf(stderr, "getxattr %s %s %lu\n",
+				path, name, (unsigned long) size);
+
+		return fs->op.getxattr(path, name, value, size);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+		      size_t size)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.listxattr) {
+		if (fs->debug)
+			fprintf(stderr, "listxattr %s %lu\n",
+				path, (unsigned long) size);
+
+		return fs->op.listxattr(path, list, size);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+		 uint64_t *idx)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.bmap) {
+		if (fs->debug)
+			fprintf(stderr, "bmap %s blocksize: %lu index: %llu\n",
+				path, (unsigned long) blocksize,
+				(unsigned long long) *idx);
+
+		return fs->op.bmap(path, blocksize, idx);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.removexattr) {
+		if (fs->debug)
+			fprintf(stderr, "removexattr %s %s\n", path, name);
+
+		return fs->op.removexattr(path, name);
+	} else {
+		return -ENOSYS;
+	}
+}
+
+int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, void *arg,
+		  struct fuse_file_info *fi, unsigned int flags, void *data)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.ioctl) {
+		if (fs->debug)
+			fprintf(stderr, "ioctl[%llu] 0x%x flags: 0x%x\n",
+				(unsigned long long) fi->fh, cmd, flags);
+
+		return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+	} else
+		return -ENOSYS;
+}
+
+int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+		 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+		 unsigned *reventsp)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.poll) {
+		int res;
+
+		if (fs->debug)
+			fprintf(stderr, "poll[%llu] ph: %p\n",
+				(unsigned long long) fi->fh, ph);
+
+		res = fs->op.poll(path, fi, ph, reventsp);
+
+		if (fs->debug && !res)
+			fprintf(stderr, "   poll[%llu] revents: 0x%x\n",
+				(unsigned long long) fi->fh, *reventsp);
+
+		return res;
+	} else
+		return -ENOSYS;
+}
+
+int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+		loff_t offset, loff_t length, struct fuse_file_info *fi)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.fallocate) {
+		if (fs->debug)
+			fprintf(stderr, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+				path,
+				mode,
+				(unsigned long long) offset,
+				(unsigned long long) length);
+
+		return fs->op.fallocate(path, mode, offset, length, fi);
+	} else
+		return -ENOSYS;
+}
+
+static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+{
+	struct node *node;
+	int isopen = 0;
+	pthread_mutex_lock(&f->lock);
+	node = lookup_node(f, dir, name);
+	if (node && node->open_count > 0)
+		isopen = 1;
+	pthread_mutex_unlock(&f->lock);
+	return isopen;
+}
+
+static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+			 char *newname, size_t bufsize)
+{
+	struct stat buf;
+	struct node *node;
+	struct node *newnode;
+	char *newpath;
+	int res;
+	int failctr = 10;
+
+	do {
+		pthread_mutex_lock(&f->lock);
+		node = lookup_node(f, dir, oldname);
+		if (node == NULL) {
+			pthread_mutex_unlock(&f->lock);
+			return NULL;
+		}
+		do {
+			f->hidectr ++;
+			snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+				 (unsigned int) node->nodeid, f->hidectr);
+			newnode = lookup_node(f, dir, newname);
+		} while(newnode);
+
+		res = try_get_path(f, dir, newname, &newpath, NULL, false);
+		pthread_mutex_unlock(&f->lock);
+		if (res)
+			break;
+
+		memset(&buf, 0, sizeof(buf));
+		res = fuse_fs_getattr(f->fs, newpath, &buf);
+		if (res == -ENOENT)
+			break;
+		free(newpath);
+		newpath = NULL;
+	} while(res == 0 && --failctr);
+
+	return newpath;
+}
+
+static int hide_node(struct fuse *f, const char *oldpath,
+		     fuse_ino_t dir, const char *oldname)
+{
+	char newname[64];
+	char *newpath;
+	int err = -EBUSY;
+
+	newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+	if (newpath) {
+		err = fuse_fs_rename(f->fs, oldpath, newpath);
+		if (!err)
+			err = rename_node(f, dir, oldname, dir, newname, 1);
+		free(newpath);
+	}
+	return err;
+}
+
+static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+{
+	return stbuf->st_mtime == ts->tv_sec &&
+		ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+}
+
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC CLOCK_REALTIME
+#endif
+
+static void curr_time(struct timespec *now)
+{
+	static clockid_t clockid = CLOCK_MONOTONIC;
+	int res = clock_gettime(clockid, now);
+	if (res == -1 && errno == EINVAL) {
+		clockid = CLOCK_REALTIME;
+		res = clock_gettime(clockid, now);
+	}
+	if (res == -1) {
+		perror("fuse: clock_gettime");
+		abort();
+	}
+}
+
+static void update_stat(struct node *node, const struct stat *stbuf)
+{
+	if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+				  stbuf->st_size != node->size))
+		node->cache_valid = 0;
+	node->mtime.tv_sec = stbuf->st_mtime;
+	node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+	node->size = stbuf->st_size;
+	curr_time(&node->stat_updated);
+}
+
+static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+		       const char *name, const char *path,
+		       struct fuse_entry_param *e, struct fuse_file_info *fi)
+{
+	int res;
+
+	memset(e, 0, sizeof(struct fuse_entry_param));
+	if (fi)
+		res = fuse_fs_fgetattr(f->fs, path, &e->attr, fi);
+	else
+		res = fuse_fs_getattr(f->fs, path, &e->attr);
+	if (res == 0) {
+		struct node *node;
+
+		node = find_node(f, nodeid, name);
+		if (node == NULL)
+			res = -ENOMEM;
+		else {
+			e->ino = node->nodeid;
+			e->generation = node->generation;
+			e->entry_timeout = f->conf.entry_timeout;
+			e->attr_timeout = f->conf.attr_timeout;
+			if (f->conf.auto_cache) {
+				pthread_mutex_lock(&f->lock);
+				update_stat(node, &e->attr);
+				pthread_mutex_unlock(&f->lock);
+			}
+			set_stat(f, e->ino, &e->attr);
+			if (f->conf.debug)
+				fprintf(stderr, "   NODEID: %lu\n",
+					(unsigned long) e->ino);
+		}
+	}
+	return res;
+}
+
+static struct fuse_context_i *fuse_get_context_internal(void)
+{
+	struct fuse_context_i *c;
+
+	c = (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+	if (c == NULL) {
+		c = (struct fuse_context_i *)
+			calloc(1, sizeof(struct fuse_context_i));
+		if (c == NULL) {
+			/* This is hard to deal with properly, so just
+			   abort.  If memory is so low that the
+			   context cannot be allocated, there's not
+			   much hope for the filesystem anyway */
+			fprintf(stderr, "fuse: failed to allocate thread specific data\n");
+			abort();
+		}
+		pthread_setspecific(fuse_context_key, c);
+	}
+	return c;
+}
+
+static void fuse_freecontext(void *data)
+{
+	free(data);
+}
+
+static int fuse_create_context_key(void)
+{
+	int err = 0;
+	pthread_mutex_lock(&fuse_context_lock);
+	if (!fuse_context_ref) {
+		err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+		if (err) {
+			fprintf(stderr, "fuse: failed to create thread specific key: %s\n",
+				strerror(err));
+			pthread_mutex_unlock(&fuse_context_lock);
+			return -1;
+		}
+	}
+	fuse_context_ref++;
+	pthread_mutex_unlock(&fuse_context_lock);
+	return 0;
+}
+
+static void fuse_delete_context_key(void)
+{
+	pthread_mutex_lock(&fuse_context_lock);
+	fuse_context_ref--;
+	if (!fuse_context_ref) {
+		free(pthread_getspecific(fuse_context_key));
+		pthread_key_delete(fuse_context_key);
+	}
+	pthread_mutex_unlock(&fuse_context_lock);
+}
+
+static struct fuse *req_fuse_prepare(fuse_req_t req)
+{
+	struct fuse_context_i *c = fuse_get_context_internal();
+	const struct fuse_ctx *ctx = fuse_req_ctx(req);
+	c->req = req;
+	c->ctx.fuse = req_fuse(req);
+	c->ctx.uid = ctx->uid;
+	c->ctx.gid = ctx->gid;
+	c->ctx.pid = ctx->pid;
+	c->ctx.umask = ctx->umask;
+	return c->ctx.fuse;
+}
+
+static inline void reply_err(fuse_req_t req, int err)
+{
+	/* fuse_reply_err() uses non-negated errno values */
+	fuse_reply_err(req, -err);
+}
+
+static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+			int err)
+{
+	if (!err) {
+		struct fuse *f = req_fuse(req);
+		if (fuse_reply_entry(req, e) == -ENOENT) {
+			/* Skip forget for negative result */
+			if  (e->ino != 0)
+				forget_node(f, e->ino, 1);
+		}
+	} else
+		reply_err(req, err);
+}
+
+void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (!fs->op.write_buf)
+		conn->want &= ~FUSE_CAP_SPLICE_READ;
+	if (!fs->op.lock)
+		conn->want &= ~FUSE_CAP_POSIX_LOCKS;
+	if (!fs->op.flock)
+		conn->want &= ~FUSE_CAP_FLOCK_LOCKS;
+	if (fs->op.init)
+		fs->user_data = fs->op.init(conn);
+}
+
+static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+{
+	struct fuse *f = (struct fuse *) data;
+	struct fuse_context_i *c = fuse_get_context_internal();
+
+	memset(c, 0, sizeof(*c));
+	c->ctx.fuse = f;
+	conn->want |= FUSE_CAP_EXPORT_SUPPORT;
+	fuse_fs_init(f->fs, conn);
+}
+
+void fuse_fs_destroy(struct fuse_fs *fs)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.destroy)
+		fs->op.destroy(fs->user_data);
+#ifdef USE_MODULES
+	if (fs->m)
+		fuse_put_module(fs->m);
+#endif
+	free(fs);
+}
+
+static void fuse_lib_destroy(void *data)
+{
+	struct fuse *f = (struct fuse *) data;
+	struct fuse_context_i *c = fuse_get_context_internal();
+
+	memset(c, 0, sizeof(*c));
+	c->ctx.fuse = f;
+	fuse_fs_destroy(f->fs);
+	f->fs = NULL;
+}
+
+static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+			    const char *name)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_entry_param e;
+	char *path;
+	int err;
+	struct node *dot = NULL;
+
+	if (name[0] == '.') {
+		int len = strlen(name);
+
+		if (len == 1 || (name[1] == '.' && len == 2)) {
+			pthread_mutex_lock(&f->lock);
+			if (len == 1) {
+				if (f->conf.debug)
+					fprintf(stderr, "LOOKUP-DOT\n");
+				dot = get_node_nocheck(f, parent);
+				if (dot == NULL) {
+					pthread_mutex_unlock(&f->lock);
+					reply_entry(req, &e, -ESTALE);
+					return;
+				}
+				dot->refctr++;
+			} else {
+				if (f->conf.debug)
+					fprintf(stderr, "LOOKUP-DOTDOT\n");
+				parent = get_node(f, parent)->parent->nodeid;
+			}
+			pthread_mutex_unlock(&f->lock);
+			name = NULL;
+		}
+	}
+
+	err = get_path_name(f, parent, name, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+		if (f->conf.debug)
+			fprintf(stderr, "LOOKUP %s\n", path);
+		fuse_prepare_interrupt(f, req, &d);
+		err = lookup_path(f, parent, name, path, &e, NULL);
+		if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+			e.ino = 0;
+			e.entry_timeout = f->conf.negative_timeout;
+			err = 0;
+		}
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, parent, path);
+	}
+	if (dot) {
+		pthread_mutex_lock(&f->lock);
+		unref_node(f, dot);
+		pthread_mutex_unlock(&f->lock);
+	}
+	reply_entry(req, &e, err);
+}
+
+static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+{
+	if (f->conf.debug)
+		fprintf(stderr, "FORGET %llu/%llu\n", (unsigned long long)ino,
+			(unsigned long long) nlookup);
+	forget_node(f, ino, nlookup);
+}
+
+static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino,
+			    unsigned long nlookup)
+{
+	do_forget(req_fuse(req), ino, nlookup);
+	fuse_reply_none(req);
+}
+
+static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+				  struct fuse_forget_data *forgets)
+{
+	struct fuse *f = req_fuse(req);
+	size_t i;
+
+	for (i = 0; i < count; i++)
+		do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
+	fuse_reply_none(req);
+}
+
+
+static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+			     struct fuse_file_info *fi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct stat buf;
+	char *path;
+	int err;
+
+	memset(&buf, 0, sizeof(buf));
+
+	if (fi != NULL && f->fs->op.fgetattr)
+		err = get_path_nullok(f, ino, &path);
+	else
+		err = get_path(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		if (fi)
+			err = fuse_fs_fgetattr(f->fs, path, &buf, fi);
+		else
+			err = fuse_fs_getattr(f->fs, path, &buf);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	if (!err) {
+		struct node *node;
+
+		pthread_mutex_lock(&f->lock);
+		node = get_node(f, ino);
+		if (node->is_hidden && buf.st_nlink > 0)
+			buf.st_nlink--;
+		if (f->conf.auto_cache)
+			update_stat(node, &buf);
+		pthread_mutex_unlock(&f->lock);
+		set_stat(f, ino, &buf);
+		fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+	} else
+		reply_err(req, err);
+}
+
+int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode)
+{
+	fuse_get_context()->private_data = fs->user_data;
+	if (fs->op.chmod)
+		return fs->op.chmod(path, mode);
+	else
+		return -ENOSYS;
+}
+
+static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+			     int valid, struct fuse_file_info *fi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct stat buf;
+	char *path;
+	int err;
+
+	memset(&buf, 0, sizeof(buf));
+	if (valid == FUSE_SET_ATTR_SIZE && fi != NULL &&
+	    f->fs->op.ftruncate && f->fs->op.fgetattr)
+		err = get_path_nullok(f, ino, &path);
+	else
+		err = get_path(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		err = 0;
+		if (!err && (valid & FUSE_SET_ATTR_MODE))
+			err = fuse_fs_chmod(f->fs, path, attr->st_mode);
+		if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+			uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+				attr->st_uid : (uid_t) -1;
+			gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+				attr->st_gid : (gid_t) -1;
+			err = fuse_fs_chown(f->fs, path, uid, gid);
+		}
+		if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+			if (fi)
+				err = fuse_fs_ftruncate(f->fs, path,
+							attr->st_size, fi);
+			else
+				err = fuse_fs_truncate(f->fs, path,
+						       attr->st_size);
+		}
+#ifdef HAVE_UTIMENSAT
+		if (!err && f->utime_omit_ok &&
+		    (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+			struct timespec tv[2];
+
+			tv[0].tv_sec = 0;
+			tv[1].tv_sec = 0;
+			tv[0].tv_nsec = UTIME_OMIT;
+			tv[1].tv_nsec = UTIME_OMIT;
+
+			if (valid & FUSE_SET_ATTR_ATIME_NOW)
+				tv[0].tv_nsec = UTIME_NOW;
+			else if (valid & FUSE_SET_ATTR_ATIME)
+				tv[0] = attr->st_atim;
+
+			if (valid & FUSE_SET_ATTR_MTIME_NOW)
+				tv[1].tv_nsec = UTIME_NOW;
+			else if (valid & FUSE_SET_ATTR_MTIME)
+				tv[1] = attr->st_mtim;
+
+			err = fuse_fs_utimens(f->fs, path, tv);
+		} else
+#endif
+		if (!err &&
+		    (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+		    (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+			struct timespec tv[2];
+			tv[0].tv_sec = attr->st_atime;
+			tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+			tv[1].tv_sec = attr->st_mtime;
+			tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+			err = fuse_fs_utimens(f->fs, path, tv);
+		}
+		if (!err) {
+			if (fi)
+				err = fuse_fs_fgetattr(f->fs, path, &buf, fi);
+			else
+				err = fuse_fs_getattr(f->fs, path, &buf);
+		}
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	if (!err) {
+		if (f->conf.auto_cache) {
+			pthread_mutex_lock(&f->lock);
+			update_stat(get_node(f, ino), &buf);
+			pthread_mutex_unlock(&f->lock);
+		}
+		set_stat(f, ino, &buf);
+		fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+	} else
+		reply_err(req, err);
+}
+
+static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	char *path;
+	int err;
+
+	err = get_path(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_access(f->fs, path, mask);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	reply_err(req, err);
+}
+
+static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	char linkname[PATH_MAX + 1];
+	char *path;
+	int err;
+
+	err = get_path(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	if (!err) {
+		linkname[PATH_MAX] = '\0';
+		fuse_reply_readlink(req, linkname);
+	} else
+		reply_err(req, err);
+}
+
+static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+			   mode_t mode, dev_t rdev)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_entry_param e;
+	char *path;
+	int err;
+
+	err = get_path_name(f, parent, name, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+
+		fuse_prepare_interrupt(f, req, &d);
+		err = -ENOSYS;
+		if (S_ISREG(mode)) {
+			struct fuse_file_info fi;
+
+			memset(&fi, 0, sizeof(fi));
+			fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+			err = fuse_fs_create(f->fs, path, mode, &fi);
+			if (!err) {
+				err = lookup_path(f, parent, name, path, &e,
+						  &fi);
+				fuse_fs_release(f->fs, path, &fi);
+			}
+		}
+		if (err == -ENOSYS) {
+			err = fuse_fs_mknod(f->fs, path, mode, rdev);
+			if (!err)
+				err = lookup_path(f, parent, name, path, &e,
+						  NULL);
+		}
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, parent, path);
+	}
+	reply_entry(req, &e, err);
+}
+
+static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+			   mode_t mode)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_entry_param e;
+	char *path;
+	int err;
+
+	err = get_path_name(f, parent, name, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_mkdir(f->fs, path, mode);
+		if (!err)
+			err = lookup_path(f, parent, name, path, &e, NULL);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, parent, path);
+	}
+	reply_entry(req, &e, err);
+}
+
+static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+			    const char *name)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct node *wnode;
+	char *path;
+	int err;
+
+	err = get_path_wrlock(f, parent, name, &path, &wnode);
+	if (!err) {
+		struct fuse_intr_data d;
+
+		fuse_prepare_interrupt(f, req, &d);
+		if (!f->conf.hard_remove && is_open(f, parent, name)) {
+			err = hide_node(f, path, parent, name);
+		} else {
+			err = fuse_fs_unlink(f->fs, path);
+			if (!err)
+				remove_node(f, parent, name);
+		}
+		fuse_finish_interrupt(f, req, &d);
+		free_path_wrlock(f, parent, wnode, path);
+	}
+	reply_err(req, err);
+}
+
+static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct node *wnode;
+	char *path;
+	int err;
+
+	err = get_path_wrlock(f, parent, name, &path, &wnode);
+	if (!err) {
+		struct fuse_intr_data d;
+
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_rmdir(f->fs, path);
+		fuse_finish_interrupt(f, req, &d);
+		if (!err)
+			remove_node(f, parent, name);
+		free_path_wrlock(f, parent, wnode, path);
+	}
+	reply_err(req, err);
+}
+
+static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+			     fuse_ino_t parent, const char *name)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_entry_param e;
+	char *path;
+	int err;
+
+	err = get_path_name(f, parent, name, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_symlink(f->fs, linkname, path);
+		if (!err)
+			err = lookup_path(f, parent, name, path, &e, NULL);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, parent, path);
+	}
+	reply_entry(req, &e, err);
+}
+
+static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+			    const char *oldname, fuse_ino_t newdir,
+			    const char *newname)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	char *oldpath;
+	char *newpath;
+	struct node *wnode1;
+	struct node *wnode2;
+	int err;
+
+	err = get_path2(f, olddir, oldname, newdir, newname,
+			&oldpath, &newpath, &wnode1, &wnode2);
+	if (!err) {
+		struct fuse_intr_data d;
+		err = 0;
+		fuse_prepare_interrupt(f, req, &d);
+		if (!f->conf.hard_remove && is_open(f, newdir, newname))
+			err = hide_node(f, newpath, newdir, newname);
+		if (!err) {
+			err = fuse_fs_rename(f->fs, oldpath, newpath);
+			if (!err)
+				err = rename_node(f, olddir, oldname, newdir,
+						  newname, 0);
+		}
+		fuse_finish_interrupt(f, req, &d);
+		free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+	}
+	reply_err(req, err);
+}
+
+static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+			  const char *newname)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_entry_param e;
+	char *oldpath;
+	char *newpath;
+	int err;
+
+	err = get_path2(f, ino, NULL, newparent, newname,
+			&oldpath, &newpath, NULL, NULL);
+	if (!err) {
+		struct fuse_intr_data d;
+
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_link(f->fs, oldpath, newpath);
+		if (!err)
+			err = lookup_path(f, newparent, newname, newpath,
+					  &e, NULL);
+		fuse_finish_interrupt(f, req, &d);
+		free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+	}
+	reply_entry(req, &e, err);
+}
+
+static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+			    struct fuse_file_info *fi)
+{
+	struct node *node;
+	int unlink_hidden = 0;
+	const char *compatpath;
+
+	if (path != NULL || f->nullpath_ok || f->conf.nopath)
+		compatpath = path;
+	else
+		compatpath = "-";
+
+	fuse_fs_release(f->fs, compatpath, fi);
+
+	pthread_mutex_lock(&f->lock);
+	node = get_node(f, ino);
+	assert(node->open_count > 0);
+	--node->open_count;
+	if (node->is_hidden && !node->open_count) {
+		unlink_hidden = 1;
+		node->is_hidden = 0;
+	}
+	pthread_mutex_unlock(&f->lock);
+
+	if(unlink_hidden) {
+		if (path) {
+			fuse_fs_unlink(f->fs, path);
+		} else if (f->conf.nopath) {
+			char *unlinkpath;
+
+			if (get_path(f, ino, &unlinkpath) == 0)
+				fuse_fs_unlink(f->fs, unlinkpath);
+
+			free_path(f, ino, unlinkpath);
+		}
+	}
+}
+
+static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+			    const char *name, mode_t mode,
+			    struct fuse_file_info *fi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_intr_data d;
+	struct fuse_entry_param e;
+	char *path;
+	int err;
+
+	err = get_path_name(f, parent, name, &path);
+	if (!err) {
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_create(f->fs, path, mode, fi);
+		if (!err) {
+			err = lookup_path(f, parent, name, path, &e, fi);
+			if (err)
+				fuse_fs_release(f->fs, path, fi);
+			else if (!S_ISREG(e.attr.st_mode)) {
+				err = -EIO;
+				fuse_fs_release(f->fs, path, fi);
+				forget_node(f, e.ino, 1);
+			} else {
+				if (f->conf.direct_io)
+					fi->direct_io = 1;
+				if (f->conf.kernel_cache)
+					fi->keep_cache = 1;
+
+			}
+		}
+		fuse_finish_interrupt(f, req, &d);
+	}
+	if (!err) {
+		pthread_mutex_lock(&f->lock);
+		get_node(f, e.ino)->open_count++;
+		pthread_mutex_unlock(&f->lock);
+		if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+			/* The open syscall was interrupted, so it
+			   must be cancelled */
+			fuse_do_release(f, e.ino, path, fi);
+			forget_node(f, e.ino, 1);
+		}
+	} else {
+		reply_err(req, err);
+	}
+
+	free_path(f, parent, path);
+}
+
+static double diff_timespec(const struct timespec *t1,
+			    const struct timespec *t2)
+{
+	return (t1->tv_sec - t2->tv_sec) +
+		((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+}
+
+static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+			    struct fuse_file_info *fi)
+{
+	struct node *node;
+
+	pthread_mutex_lock(&f->lock);
+	node = get_node(f, ino);
+	if (node->cache_valid) {
+		struct timespec now;
+
+		curr_time(&now);
+		if (diff_timespec(&now, &node->stat_updated) >
+		    f->conf.ac_attr_timeout) {
+			struct stat stbuf;
+			int err;
+			pthread_mutex_unlock(&f->lock);
+			err = fuse_fs_fgetattr(f->fs, path, &stbuf, fi);
+			pthread_mutex_lock(&f->lock);
+			if (!err)
+				update_stat(node, &stbuf);
+			else
+				node->cache_valid = 0;
+		}
+	}
+	if (node->cache_valid)
+		fi->keep_cache = 1;
+
+	node->cache_valid = 1;
+	pthread_mutex_unlock(&f->lock);
+}
+
+static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+			  struct fuse_file_info *fi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_intr_data d;
+	char *path;
+	int err;
+
+	err = get_path(f, ino, &path);
+	if (!err) {
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_open(f->fs, path, fi);
+		if (!err) {
+			if (f->conf.direct_io)
+				fi->direct_io = 1;
+			if (f->conf.kernel_cache)
+				fi->keep_cache = 1;
+
+			if (f->conf.auto_cache)
+				open_auto_cache(f, ino, path, fi);
+		}
+		fuse_finish_interrupt(f, req, &d);
+	}
+	if (!err) {
+		pthread_mutex_lock(&f->lock);
+		get_node(f, ino)->open_count++;
+		pthread_mutex_unlock(&f->lock);
+		if (fuse_reply_open(req, fi) == -ENOENT) {
+			/* The open syscall was interrupted, so it
+			   must be cancelled */
+			fuse_do_release(f, ino, path, fi);
+		}
+	} else
+		reply_err(req, err);
+
+	free_path(f, ino, path);
+}
+
+static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+			  loff_t off, struct fuse_file_info *fi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_bufvec *buf = NULL;
+	char *path;
+	int res;
+
+	res = get_path_nullok(f, ino, &path);
+	if (res == 0) {
+		struct fuse_intr_data d;
+
+		fuse_prepare_interrupt(f, req, &d);
+		res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+
+	if (res == 0)
+		fuse_reply_data(req, buf, FUSE_BUF_SPLICE_MOVE);
+	else
+		reply_err(req, res);
+
+	fuse_free_buf(buf);
+}
+
+static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+			       struct fuse_bufvec *buf, loff_t off,
+			       struct fuse_file_info *fi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	char *path;
+	int res;
+
+	res = get_path_nullok(f, ino, &path);
+	if (res == 0) {
+		struct fuse_intr_data d;
+
+		fuse_prepare_interrupt(f, req, &d);
+		res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+
+	if (res >= 0)
+		fuse_reply_write(req, res);
+	else
+		reply_err(req, res);
+}
+
+static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+			   struct fuse_file_info *fi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	char *path;
+	int err;
+
+	err = get_path_nullok(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_fsync(f->fs, path, datasync, fi);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	reply_err(req, err);
+}
+
+static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+				     struct fuse_file_info *fi)
+{
+	struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+	memset(fi, 0, sizeof(struct fuse_file_info));
+	fi->fh = dh->fh;
+	fi->fh_old = dh->fh;
+	return dh;
+}
+
+static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+			     struct fuse_file_info *llfi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_intr_data d;
+	struct fuse_dh *dh;
+	struct fuse_file_info fi;
+	char *path;
+	int err;
+
+	dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+	if (dh == NULL) {
+		reply_err(req, -ENOMEM);
+		return;
+	}
+	memset(dh, 0, sizeof(struct fuse_dh));
+	dh->fuse = f;
+	dh->contents = NULL;
+	dh->len = 0;
+	dh->filled = 0;
+	dh->nodeid = ino;
+	fuse_mutex_init(&dh->lock);
+
+	llfi->fh = (uintptr_t) dh;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.flags = llfi->flags;
+
+	err = get_path(f, ino, &path);
+	if (!err) {
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_opendir(f->fs, path, &fi);
+		fuse_finish_interrupt(f, req, &d);
+		dh->fh = fi.fh;
+	}
+	if (!err) {
+		if (fuse_reply_open(req, llfi) == -ENOENT) {
+			/* The opendir syscall was interrupted, so it
+			   must be cancelled */
+			fuse_fs_releasedir(f->fs, path, &fi);
+			pthread_mutex_destroy(&dh->lock);
+			free(dh);
+		}
+	} else {
+		reply_err(req, err);
+		pthread_mutex_destroy(&dh->lock);
+		free(dh);
+	}
+	free_path(f, ino, path);
+}
+
+static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+{
+	if (minsize > dh->size) {
+		char *newptr;
+		unsigned newsize = dh->size;
+		if (!newsize)
+			newsize = 1024;
+		while (newsize < minsize) {
+			if (newsize >= 0x80000000)
+				newsize = 0xffffffff;
+			else
+				newsize *= 2;
+		}
+
+		newptr = (char *) realloc(dh->contents, newsize);
+		if (!newptr) {
+			dh->error = -ENOMEM;
+			return -1;
+		}
+		dh->contents = newptr;
+		dh->size = newsize;
+	}
+	return 0;
+}
+
+static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+		    loff_t off)
+{
+	struct fuse_dh *dh = (struct fuse_dh *) dh_;
+	struct stat stbuf;
+	size_t newlen;
+
+	if (statp)
+		stbuf = *statp;
+	else {
+		memset(&stbuf, 0, sizeof(stbuf));
+		stbuf.st_ino = FUSE_UNKNOWN_INO;
+	}
+
+	if (!dh->fuse->conf.use_ino) {
+		stbuf.st_ino = FUSE_UNKNOWN_INO;
+		if (dh->fuse->conf.readdir_ino) {
+			struct node *node;
+			pthread_mutex_lock(&dh->fuse->lock);
+			node = lookup_node(dh->fuse, dh->nodeid, name);
+			if (node)
+				stbuf.st_ino  = (ino_t) node->nodeid;
+			pthread_mutex_unlock(&dh->fuse->lock);
+		}
+	}
+
+	if (off) {
+		if (extend_contents(dh, dh->needlen) == -1)
+			return 1;
+
+		dh->filled = 0;
+		newlen = dh->len +
+			fuse_add_direntry(dh->req, dh->contents + dh->len,
+					  dh->needlen - dh->len, name,
+					  &stbuf, off);
+		if (newlen > dh->needlen)
+			return 1;
+	} else {
+		newlen = dh->len +
+			fuse_add_direntry(dh->req, NULL, 0, name, NULL, 0);
+		if (extend_contents(dh, newlen) == -1)
+			return 1;
+
+		fuse_add_direntry(dh->req, dh->contents + dh->len,
+				  dh->size - dh->len, name, &stbuf, newlen);
+	}
+	dh->len = newlen;
+	return 0;
+}
+
+static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+			size_t size, loff_t off, struct fuse_dh *dh,
+			struct fuse_file_info *fi)
+{
+	char *path;
+	int err;
+
+	if (f->fs->op.readdir)
+		err = get_path_nullok(f, ino, &path);
+	else
+		err = get_path(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+
+		dh->len = 0;
+		dh->error = 0;
+		dh->needlen = size;
+		dh->filled = 1;
+		dh->req = req;
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_readdir(f->fs, path, dh, fill_dir, off, fi);
+		fuse_finish_interrupt(f, req, &d);
+		dh->req = NULL;
+		if (!err)
+			err = dh->error;
+		if (err)
+			dh->filled = 0;
+		free_path(f, ino, path);
+	}
+	return err;
+}
+
+static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+			     loff_t off, struct fuse_file_info *llfi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_file_info fi;
+	struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
+	pthread_mutex_lock(&dh->lock);
+	/* According to SUS, directory contents need to be refreshed on
+	   rewinddir() */
+	if (!off)
+		dh->filled = 0;
+
+	if (!dh->filled) {
+		int err = readdir_fill(f, req, ino, size, off, dh, &fi);
+		if (err) {
+			reply_err(req, err);
+			goto out;
+		}
+	}
+	if (dh->filled) {
+		if (off < dh->len) {
+			if (off + size > dh->len)
+				size = dh->len - off;
+		} else
+			size = 0;
+	} else {
+		size = dh->len;
+		off = 0;
+	}
+	fuse_reply_buf(req, dh->contents + off, size);
+out:
+	pthread_mutex_unlock(&dh->lock);
+}
+
+static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+				struct fuse_file_info *llfi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_intr_data d;
+	struct fuse_file_info fi;
+	struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+	char *path;
+	const char *compatpath;
+
+	get_path_nullok(f, ino, &path);
+	if (path != NULL || f->nullpath_ok || f->conf.nopath)
+		compatpath = path;
+	else
+		compatpath = "-";
+
+	fuse_prepare_interrupt(f, req, &d);
+	fuse_fs_releasedir(f->fs, compatpath, &fi);
+	fuse_finish_interrupt(f, req, &d);
+	free_path(f, ino, path);
+
+	pthread_mutex_lock(&dh->lock);
+	pthread_mutex_unlock(&dh->lock);
+	pthread_mutex_destroy(&dh->lock);
+	free(dh->contents);
+	free(dh);
+	reply_err(req, 0);
+}
+
+static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+			      struct fuse_file_info *llfi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_file_info fi;
+	char *path;
+	int err;
+
+	get_dirhandle(llfi, &fi);
+
+	err = get_path_nullok(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	reply_err(req, err);
+}
+
+static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct statvfs buf;
+	char *path = NULL;
+	int err = 0;
+
+	memset(&buf, 0, sizeof(buf));
+	if (ino)
+		err = get_path(f, ino, &path);
+
+	if (!err) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+
+	if (!err)
+		fuse_reply_statfs(req, &buf);
+	else
+		reply_err(req, err);
+}
+
+static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+			      const char *value, size_t size, int flags)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	char *path;
+	int err;
+
+	err = get_path(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	reply_err(req, err);
+}
+
+static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+			   const char *name, char *value, size_t size)
+{
+	int err;
+	char *path;
+
+	err = get_path(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_getxattr(f->fs, path, name, value, size);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	return err;
+}
+
+static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+			      size_t size)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	int res;
+
+	if (size) {
+		char *value = (char *) malloc(size);
+		if (value == NULL) {
+			reply_err(req, -ENOMEM);
+			return;
+		}
+		res = common_getxattr(f, req, ino, name, value, size);
+		if (res > 0)
+			fuse_reply_buf(req, value, res);
+		else
+			reply_err(req, res);
+		free(value);
+	} else {
+		res = common_getxattr(f, req, ino, name, NULL, 0);
+		if (res >= 0)
+			fuse_reply_xattr(req, res);
+		else
+			reply_err(req, res);
+	}
+}
+
+static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+			    char *list, size_t size)
+{
+	char *path;
+	int err;
+
+	err = get_path(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_listxattr(f->fs, path, list, size);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	return err;
+}
+
+static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	int res;
+
+	if (size) {
+		char *list = (char *) malloc(size);
+		if (list == NULL) {
+			reply_err(req, -ENOMEM);
+			return;
+		}
+		res = common_listxattr(f, req, ino, list, size);
+		if (res > 0)
+			fuse_reply_buf(req, list, res);
+		else
+			reply_err(req, res);
+		free(list);
+	} else {
+		res = common_listxattr(f, req, ino, NULL, 0);
+		if (res >= 0)
+			fuse_reply_xattr(req, res);
+		else
+			reply_err(req, res);
+	}
+}
+
+static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+				 const char *name)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	char *path;
+	int err;
+
+	err = get_path(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_removexattr(f->fs, path, name);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	reply_err(req, err);
+}
+
+static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+{
+	struct lock *l;
+
+	for (l = node->locks; l; l = l->next)
+		if (l->owner != lock->owner &&
+		    lock->start <= l->end && l->start <= lock->end &&
+		    (l->type == F_WRLCK || lock->type == F_WRLCK))
+			break;
+
+	return l;
+}
+
+static void delete_lock(struct lock **lockp)
+{
+	struct lock *l = *lockp;
+	*lockp = l->next;
+	free(l);
+}
+
+static void insert_lock(struct lock **pos, struct lock *lock)
+{
+	lock->next = *pos;
+	*pos = lock;
+}
+
+static int locks_insert(struct node *node, struct lock *lock)
+{
+	struct lock **lp;
+	struct lock *newl1 = NULL;
+	struct lock *newl2 = NULL;
+
+	if (lock->type != F_UNLCK || lock->start != 0 ||
+	    lock->end != OFFSET_MAX) {
+		newl1 = malloc(sizeof(struct lock));
+		newl2 = malloc(sizeof(struct lock));
+
+		if (!newl1 || !newl2) {
+			free(newl1);
+			free(newl2);
+			return -ENOLCK;
+		}
+	}
+
+	for (lp = &node->locks; *lp;) {
+		struct lock *l = *lp;
+		if (l->owner != lock->owner)
+			goto skip;
+
+		if (lock->type == l->type) {
+			if (l->end < lock->start - 1)
+				goto skip;
+			if (lock->end < l->start - 1)
+				break;
+			if (l->start <= lock->start && lock->end <= l->end)
+				goto out;
+			if (l->start < lock->start)
+				lock->start = l->start;
+			if (lock->end < l->end)
+				lock->end = l->end;
+			goto delete;
+		} else {
+			if (l->end < lock->start)
+				goto skip;
+			if (lock->end < l->start)
+				break;
+			if (lock->start <= l->start && l->end <= lock->end)
+				goto delete;
+			if (l->end <= lock->end) {
+				l->end = lock->start - 1;
+				goto skip;
+			}
+			if (lock->start <= l->start) {
+				l->start = lock->end + 1;
+				break;
+			}
+			*newl2 = *l;
+			newl2->start = lock->end + 1;
+			l->end = lock->start - 1;
+			insert_lock(&l->next, newl2);
+			newl2 = NULL;
+		}
+	skip:
+		lp = &l->next;
+		continue;
+
+	delete:
+		delete_lock(lp);
+	}
+	if (lock->type != F_UNLCK) {
+		*newl1 = *lock;
+		insert_lock(lp, newl1);
+		newl1 = NULL;
+	}
+out:
+	free(newl1);
+	free(newl2);
+	return 0;
+}
+
+static void flock_to_lock(struct flock *flock, struct lock *lock)
+{
+	memset(lock, 0, sizeof(struct lock));
+	lock->type = flock->l_type;
+	lock->start = flock->l_start;
+	lock->end =
+		flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+	lock->pid = flock->l_pid;
+}
+
+static void lock_to_flock(struct lock *lock, struct flock *flock)
+{
+	flock->l_type = lock->type;
+	flock->l_start = lock->start;
+	flock->l_len =
+		(lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+	flock->l_pid = lock->pid;
+}
+
+static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+			     const char *path, struct fuse_file_info *fi)
+{
+	struct fuse_intr_data d;
+	struct flock lock;
+	struct lock l;
+	int err;
+	int errlock;
+
+	fuse_prepare_interrupt(f, req, &d);
+	memset(&lock, 0, sizeof(lock));
+	lock.l_type = F_UNLCK;
+	lock.l_whence = SEEK_SET;
+	err = fuse_fs_flush(f->fs, path, fi);
+	errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+	fuse_finish_interrupt(f, req, &d);
+
+	if (errlock != -ENOSYS) {
+		flock_to_lock(&lock, &l);
+		l.owner = fi->lock_owner;
+		pthread_mutex_lock(&f->lock);
+		locks_insert(get_node(f, ino), &l);
+		pthread_mutex_unlock(&f->lock);
+
+		/* if op.lock() is defined FLUSH is needed regardless
+		   of op.flush() */
+		if (err == -ENOSYS)
+			err = 0;
+	}
+	return err;
+}
+
+static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+			     struct fuse_file_info *fi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_intr_data d;
+	char *path;
+	int err = 0;
+
+	get_path_nullok(f, ino, &path);
+	if (fi->flush) {
+		err = fuse_flush_common(f, req, ino, path, fi);
+		if (err == -ENOSYS)
+			err = 0;
+	}
+
+	fuse_prepare_interrupt(f, req, &d);
+	fuse_do_release(f, ino, path, fi);
+	fuse_finish_interrupt(f, req, &d);
+	free_path(f, ino, path);
+
+	reply_err(req, err);
+}
+
+static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+			   struct fuse_file_info *fi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	char *path;
+	int err;
+
+	get_path_nullok(f, ino, &path);
+	err = fuse_flush_common(f, req, ino, path, fi);
+	free_path(f, ino, path);
+
+	reply_err(req, err);
+}
+
+static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+			    struct fuse_file_info *fi, struct flock *lock,
+			    int cmd)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	char *path;
+	int err;
+
+	err = get_path_nullok(f, ino, &path);
+	if (!err) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	return err;
+}
+
+static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+			   struct fuse_file_info *fi, struct flock *lock)
+{
+	int err;
+	struct lock l;
+	struct lock *conflict;
+	struct fuse *f = req_fuse(req);
+
+	flock_to_lock(lock, &l);
+	l.owner = fi->lock_owner;
+	pthread_mutex_lock(&f->lock);
+	conflict = locks_conflict(get_node(f, ino), &l);
+	if (conflict)
+		lock_to_flock(conflict, lock);
+	pthread_mutex_unlock(&f->lock);
+	if (!conflict)
+		err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+	else
+		err = 0;
+
+	if (!err)
+		fuse_reply_lock(req, lock);
+	else
+		reply_err(req, err);
+}
+
+static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+			   struct fuse_file_info *fi, struct flock *lock,
+			   int sleep)
+{
+	int err = fuse_lock_common(req, ino, fi, lock,
+				   sleep ? F_SETLKW : F_SETLK);
+	if (!err) {
+		struct fuse *f = req_fuse(req);
+		struct lock l;
+		flock_to_lock(lock, &l);
+		l.owner = fi->lock_owner;
+		pthread_mutex_lock(&f->lock);
+		locks_insert(get_node(f, ino), &l);
+		pthread_mutex_unlock(&f->lock);
+	}
+	reply_err(req, err);
+}
+
+static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+			   struct fuse_file_info *fi, int op)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	char *path;
+	int err;
+
+	err = get_path_nullok(f, ino, &path);
+	if (err == 0) {
+		struct fuse_intr_data d;
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_flock(f->fs, path, fi, op);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	reply_err(req, err);
+}
+
+static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+			  uint64_t idx)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_intr_data d;
+	char *path;
+	int err;
+
+	err = get_path(f, ino, &path);
+	if (!err) {
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	if (!err)
+		fuse_reply_bmap(req, idx);
+	else
+		reply_err(req, err);
+}
+
+static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg,
+			   struct fuse_file_info *llfi, unsigned int flags,
+			   const void *in_buf, size_t in_bufsz,
+			   size_t out_bufsz)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_intr_data d;
+	struct fuse_file_info fi;
+	char *path, *out_buf = NULL;
+	int err;
+
+	err = -EPERM;
+	if (flags & FUSE_IOCTL_UNRESTRICTED)
+		goto err;
+
+	if (flags & FUSE_IOCTL_DIR)
+		get_dirhandle(llfi, &fi);
+	else
+		fi = *llfi;
+
+	if (out_bufsz) {
+		err = -ENOMEM;
+		out_buf = malloc(out_bufsz);
+		if (!out_buf)
+			goto err;
+	}
+
+	assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+	if (out_buf)
+		memcpy(out_buf, in_buf, in_bufsz);
+
+	err = get_path_nullok(f, ino, &path);
+	if (err)
+		goto err;
+
+	fuse_prepare_interrupt(f, req, &d);
+
+	err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+			    out_buf ?: (void *)in_buf);
+
+	fuse_finish_interrupt(f, req, &d);
+	free_path(f, ino, path);
+
+	fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+	goto out;
+err:
+	reply_err(req, err);
+out:
+	free(out_buf);
+}
+
+static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+			  struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_intr_data d;
+	char *path;
+	int err;
+	unsigned revents = 0;
+
+	err = get_path_nullok(f, ino, &path);
+	if (!err) {
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	if (!err)
+		fuse_reply_poll(req, revents);
+	else
+		reply_err(req, err);
+}
+
+static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+		loff_t offset, loff_t length, struct fuse_file_info *fi)
+{
+	struct fuse *f = req_fuse_prepare(req);
+	struct fuse_intr_data d;
+	char *path;
+	int err;
+
+	err = get_path_nullok(f, ino, &path);
+	if (!err) {
+		fuse_prepare_interrupt(f, req, &d);
+		err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+		fuse_finish_interrupt(f, req, &d);
+		free_path(f, ino, path);
+	}
+	reply_err(req, err);
+}
+
+static int clean_delay(struct fuse *f)
+{
+	/*
+	 * This is calculating the delay between clean runs.  To
+	 * reduce the number of cleans we are doing them 10 times
+	 * within the remember window.
+	 */
+	int min_sleep = 60;
+	int max_sleep = 3600;
+	int sleep_time = f->conf.remember / 10;
+
+	if (sleep_time > max_sleep)
+		return max_sleep;
+	if (sleep_time < min_sleep)
+		return min_sleep;
+	return sleep_time;
+}
+
+int fuse_clean_cache(struct fuse *f)
+{
+	struct node_lru *lnode;
+	struct list_head *curr, *next;
+	struct node *node;
+	struct timespec now;
+
+	pthread_mutex_lock(&f->lock);
+
+	curr_time(&now);
+
+	for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+		double age;
+
+		next = curr->next;
+		lnode = list_entry(curr, struct node_lru, lru);
+		node = &lnode->node;
+
+		age = diff_timespec(&now, &lnode->forget_time);
+		if (age <= f->conf.remember)
+			break;
+
+		assert(node->nlookup == 1);
+
+		/* Don't forget active directories */
+		if (node->refctr > 1)
+			continue;
+
+		node->nlookup = 0;
+		unhash_name(f, node);
+		unref_node(f, node);
+	}
+	pthread_mutex_unlock(&f->lock);
+
+	return clean_delay(f);
+}
+
+static struct fuse_lowlevel_ops fuse_path_ops = {
+	.init = fuse_lib_init,
+	.destroy = fuse_lib_destroy,
+	.lookup = fuse_lib_lookup,
+	.forget = fuse_lib_forget,
+	.forget_multi = fuse_lib_forget_multi,
+	.getattr = fuse_lib_getattr,
+	.setattr = fuse_lib_setattr,
+	.access = fuse_lib_access,
+	.readlink = fuse_lib_readlink,
+	.mknod = fuse_lib_mknod,
+	.mkdir = fuse_lib_mkdir,
+	.unlink = fuse_lib_unlink,
+	.rmdir = fuse_lib_rmdir,
+	.symlink = fuse_lib_symlink,
+	.rename = fuse_lib_rename,
+	.link = fuse_lib_link,
+	.create = fuse_lib_create,
+	.open = fuse_lib_open,
+	.read = fuse_lib_read,
+	.write_buf = fuse_lib_write_buf,
+	.flush = fuse_lib_flush,
+	.release = fuse_lib_release,
+	.fsync = fuse_lib_fsync,
+	.opendir = fuse_lib_opendir,
+	.readdir = fuse_lib_readdir,
+	.releasedir = fuse_lib_releasedir,
+	.fsyncdir = fuse_lib_fsyncdir,
+	.statfs = fuse_lib_statfs,
+	.setxattr = fuse_lib_setxattr,
+	.getxattr = fuse_lib_getxattr,
+	.listxattr = fuse_lib_listxattr,
+	.removexattr = fuse_lib_removexattr,
+	.getlk = fuse_lib_getlk,
+	.setlk = fuse_lib_setlk,
+	.flock = fuse_lib_flock,
+	.bmap = fuse_lib_bmap,
+	.ioctl = fuse_lib_ioctl,
+	.poll = fuse_lib_poll,
+	.fallocate = fuse_lib_fallocate,
+};
+
+int fuse_notify_poll(struct fuse_pollhandle *ph)
+{
+	return fuse_lowlevel_notify_poll(ph);
+}
+
+static void free_cmd(struct fuse_cmd *cmd)
+{
+	free(cmd->buf);
+	free(cmd);
+}
+
+void fuse_process_cmd(struct fuse *f, struct fuse_cmd *cmd)
+{
+	fuse_session_process(f->se, cmd->buf, cmd->buflen, cmd->ch);
+	free_cmd(cmd);
+}
+
+int fuse_exited(struct fuse *f)
+{
+	return fuse_session_exited(f->se);
+}
+
+struct fuse_session *fuse_get_session(struct fuse *f)
+{
+	return f->se;
+}
+
+static struct fuse_cmd *fuse_alloc_cmd(size_t bufsize)
+{
+	struct fuse_cmd *cmd = (struct fuse_cmd *) malloc(sizeof(*cmd));
+	if (cmd == NULL) {
+		fprintf(stderr, "fuse: failed to allocate cmd\n");
+		return NULL;
+	}
+	cmd->buf = (char *) malloc(bufsize);
+	if (cmd->buf == NULL) {
+		fprintf(stderr, "fuse: failed to allocate read buffer\n");
+		free(cmd);
+		return NULL;
+	}
+	return cmd;
+}
+
+struct fuse_cmd *fuse_read_cmd(struct fuse *f)
+{
+	struct fuse_chan *ch = fuse_session_next_chan(f->se, NULL);
+	size_t bufsize = fuse_chan_bufsize(ch);
+	struct fuse_cmd *cmd = fuse_alloc_cmd(bufsize);
+	if (cmd != NULL) {
+		int res = fuse_chan_recv(&ch, cmd->buf, bufsize);
+		if (res <= 0) {
+			free_cmd(cmd);
+			if (res < 0 && res != -EINTR && res != -EAGAIN)
+				fuse_exit(f);
+			return NULL;
+		}
+		cmd->buflen = res;
+		cmd->ch = ch;
+	}
+	return cmd;
+}
+
+static int fuse_session_loop_remember(struct fuse *f)
+{
+	struct fuse_session *se = f->se;
+	int res = 0;
+	struct timespec now;
+	time_t next_clean;
+	struct fuse_chan *ch = fuse_session_next_chan(se, NULL);
+	size_t bufsize = fuse_chan_bufsize(ch);
+	char *buf = (char *) malloc(bufsize);
+	struct pollfd fds = {
+		.fd = fuse_chan_fd(ch),
+		.events = POLLIN
+	};
+
+	if (!buf) {
+		fprintf(stderr, "fuse: failed to allocate read buffer\n");
+		return -1;
+	}
+
+	curr_time(&now);
+	next_clean = now.tv_sec;
+	while (!fuse_session_exited(se)) {
+		struct fuse_chan *tmpch = ch;
+		struct fuse_buf fbuf = {
+			.mem = buf,
+			.size = bufsize,
+		};
+		unsigned timeout;
+
+		curr_time(&now);
+		if (now.tv_sec < next_clean)
+			timeout = next_clean - now.tv_sec;
+		else
+			timeout = 0;
+
+		res = poll(&fds, 1, timeout * 1000);
+		if (res == -1) {
+			if (errno == -EINTR)
+				continue;
+			else
+				break;
+		} else if (res > 0) {
+			res = fuse_session_receive_buf(se, &fbuf, &tmpch);
+
+			if (res == -EINTR)
+				continue;
+			if (res <= 0)
+				break;
+
+			fuse_session_process_buf(se, &fbuf, tmpch);
+		} else {
+			timeout = fuse_clean_cache(f);
+			curr_time(&now);
+			next_clean = now.tv_sec + timeout;
+		}
+	}
+
+	free(buf);
+	fuse_session_reset(se);
+	return res < 0 ? -1 : 0;
+}
+
+int fuse_loop(struct fuse *f)
+{
+	if (!f)
+		return -1;
+
+	if (lru_enabled(f))
+		return fuse_session_loop_remember(f);
+
+	return fuse_session_loop(f->se);
+}
+
+int fuse_invalidate(struct fuse *f, const char *path)
+{
+	(void) f;
+	(void) path;
+	return -EINVAL;
+}
+
+void fuse_exit(struct fuse *f)
+{
+	fuse_session_exit(f->se);
+}
+
+struct fuse_context *fuse_get_context(void)
+{
+	return &fuse_get_context_internal()->ctx;
+}
+
+/*
+ * The size of fuse_context got extended, so need to be careful about
+ * incompatibility (i.e. a new binary cannot work with an old
+ * library).
+ */
+struct fuse_context *fuse_get_context_compat22(void);
+struct fuse_context *fuse_get_context_compat22(void)
+{
+	return &fuse_get_context_internal()->ctx;
+}
+FUSE_SYMVER(".symver fuse_get_context_compat22,fuse_get_context@FUSE_2.2");
+
+int fuse_getgroups(int size, gid_t list[])
+{
+	fuse_req_t req = fuse_get_context_internal()->req;
+	return fuse_req_getgroups(req, size, list);
+}
+
+int fuse_interrupted(void)
+{
+	return fuse_req_interrupted(fuse_get_context_internal()->req);
+}
+
+void fuse_set_getcontext_func(struct fuse_context *(*func)(void))
+{
+	(void) func;
+	/* no-op */
+}
+
+enum {
+	KEY_HELP,
+};
+
+#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
+static const struct fuse_opt fuse_lib_opts[] = {
+	FUSE_OPT_KEY("-h",		      KEY_HELP),
+	FUSE_OPT_KEY("--help",		      KEY_HELP),
+	FUSE_OPT_KEY("debug",		      FUSE_OPT_KEY_KEEP),
+	FUSE_OPT_KEY("-d",		      FUSE_OPT_KEY_KEEP),
+	FUSE_LIB_OPT("debug",		      debug, 1),
+	FUSE_LIB_OPT("-d",		      debug, 1),
+	FUSE_LIB_OPT("hard_remove",	      hard_remove, 1),
+	FUSE_LIB_OPT("use_ino",		      use_ino, 1),
+	FUSE_LIB_OPT("readdir_ino",	      readdir_ino, 1),
+	FUSE_LIB_OPT("direct_io",	      direct_io, 1),
+	FUSE_LIB_OPT("kernel_cache",	      kernel_cache, 1),
+	FUSE_LIB_OPT("auto_cache",	      auto_cache, 1),
+	FUSE_LIB_OPT("noauto_cache",	      auto_cache, 0),
+	FUSE_LIB_OPT("umask=",		      set_mode, 1),
+	FUSE_LIB_OPT("umask=%o",	      umask, 0),
+	FUSE_LIB_OPT("uid=",		      set_uid, 1),
+	FUSE_LIB_OPT("uid=%d",		      uid, 0),
+	FUSE_LIB_OPT("gid=",		      set_gid, 1),
+	FUSE_LIB_OPT("gid=%d",		      gid, 0),
+	FUSE_LIB_OPT("entry_timeout=%lf",     entry_timeout, 0),
+	FUSE_LIB_OPT("attr_timeout=%lf",      attr_timeout, 0),
+	FUSE_LIB_OPT("ac_attr_timeout=%lf",   ac_attr_timeout, 0),
+	FUSE_LIB_OPT("ac_attr_timeout=",      ac_attr_timeout_set, 1),
+	FUSE_LIB_OPT("negative_timeout=%lf",  negative_timeout, 0),
+	FUSE_LIB_OPT("noforget",              remember, -1),
+	FUSE_LIB_OPT("remember=%u",           remember, 0),
+	FUSE_LIB_OPT("nopath",                nopath, 1),
+	FUSE_LIB_OPT("intr",		      intr, 1),
+	FUSE_LIB_OPT("intr_signal=%d",	      intr_signal, 0),
+	FUSE_LIB_OPT("modules=%s",	      modules, 0),
+	FUSE_OPT_END
+};
+
+static void fuse_lib_help(void)
+{
+	fprintf(stderr,
+"    -o hard_remove         immediate removal (don't hide files)\n"
+"    -o use_ino             let filesystem set inode numbers\n"
+"    -o readdir_ino         try to fill in d_ino in readdir\n"
+"    -o direct_io           use direct I/O\n"
+"    -o kernel_cache        cache files in kernel\n"
+"    -o [no]auto_cache      enable caching based on modification times (off)\n"
+"    -o umask=M             set file permissions (octal)\n"
+"    -o uid=N               set file owner\n"
+"    -o gid=N               set file group\n"
+"    -o entry_timeout=T     cache timeout for names (1.0s)\n"
+"    -o negative_timeout=T  cache timeout for deleted names (0.0s)\n"
+"    -o attr_timeout=T      cache timeout for attributes (1.0s)\n"
+"    -o ac_attr_timeout=T   auto cache timeout for attributes (attr_timeout)\n"
+"    -o noforget            never forget cached inodes\n"
+"    -o remember=T          remember cached inodes for T seconds (0s)\n"
+"    -o nopath              don't supply path if not necessary\n"
+"    -o intr                allow requests to be interrupted\n"
+"    -o intr_signal=NUM     signal to send on interrupt (%i)\n"
+"    -o modules=M1[:M2...]  names of modules to push onto filesystem stack\n"
+"\n", FUSE_DEFAULT_INTR_SIGNAL);
+}
+
+#ifdef USE_MODULES
+static void fuse_lib_help_modules(void)
+{
+	struct fuse_module *m;
+	fprintf(stderr, "\nModule options:\n");
+	pthread_mutex_lock(&fuse_context_lock);
+	for (m = fuse_modules; m; m = m->next) {
+		struct fuse_fs *fs = NULL;
+		struct fuse_fs *newfs;
+		struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+		if (fuse_opt_add_arg(&args, "") != -1 &&
+		    fuse_opt_add_arg(&args, "-h") != -1) {
+			fprintf(stderr, "\n[%s]\n", m->name);
+			newfs = m->factory(&args, &fs);
+			assert(newfs == NULL);
+		}
+		fuse_opt_free_args(&args);
+	}
+	pthread_mutex_unlock(&fuse_context_lock);
+}
+#endif
+
+static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+			     struct fuse_args *outargs)
+{
+	(void) arg; (void) outargs;
+
+	if (key == KEY_HELP) {
+		struct fuse_config *conf = (struct fuse_config *) data;
+		fuse_lib_help();
+		conf->help = 1;
+	}
+
+	return 1;
+}
+
+int fuse_is_lib_option(const char *opt)
+{
+	return fuse_lowlevel_is_lib_option(opt) ||
+		fuse_opt_match(fuse_lib_opts, opt);
+}
+
+static int fuse_init_intr_signal(int signum, int *installed)
+{
+	struct sigaction old_sa;
+
+	if (sigaction(signum, NULL, &old_sa) == -1) {
+		perror("fuse: cannot get old signal handler");
+		return -1;
+	}
+
+	if (old_sa.sa_handler == SIG_DFL) {
+		struct sigaction sa;
+
+		memset(&sa, 0, sizeof(struct sigaction));
+		sa.sa_handler = fuse_intr_sighandler;
+		sigemptyset(&sa.sa_mask);
+
+		if (sigaction(signum, &sa, NULL) == -1) {
+			perror("fuse: cannot set interrupt signal handler");
+			return -1;
+		}
+		*installed = 1;
+	}
+	return 0;
+}
+
+static void fuse_restore_intr_signal(int signum)
+{
+	struct sigaction sa;
+
+	memset(&sa, 0, sizeof(struct sigaction));
+	sa.sa_handler = SIG_DFL;
+	sigaction(signum, &sa, NULL);
+}
+
+#ifdef USE_MODULES
+static int fuse_push_module(struct fuse *f, const char *module,
+			    struct fuse_args *args)
+{
+	struct fuse_fs *fs[2] = { f->fs, NULL };
+	struct fuse_fs *newfs;
+	struct fuse_module *m = fuse_get_module(module);
+
+	if (!m)
+		return -1;
+
+	newfs = m->factory(args, fs);
+	if (!newfs) {
+		fuse_put_module(m);
+		return -1;
+	}
+	newfs->m = m;
+	f->fs = newfs;
+	f->nullpath_ok = newfs->op.flag_nullpath_ok && f->nullpath_ok;
+	f->conf.nopath = newfs->op.flag_nopath && f->conf.nopath;
+	f->utime_omit_ok = newfs->op.flag_utime_omit_ok && f->utime_omit_ok;
+	return 0;
+}
+#endif
+
+struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+			    void *user_data)
+{
+	struct fuse_fs *fs;
+
+	if (sizeof(struct fuse_operations) < op_size) {
+		fprintf(stderr, "fuse: warning: library too old, some operations may not not work\n");
+		op_size = sizeof(struct fuse_operations);
+	}
+
+	fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+	if (!fs) {
+		fprintf(stderr, "fuse: failed to allocate fuse_fs object\n");
+		return NULL;
+	}
+
+	fs->user_data = user_data;
+	if (op)
+		memcpy(&fs->op, op, op_size);
+	return fs;
+}
+
+static int node_table_init(struct node_table *t)
+{
+	t->size = NODE_TABLE_MIN_SIZE;
+	t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+	if (t->array == NULL) {
+		fprintf(stderr, "fuse: memory allocation failed\n");
+		return -1;
+	}
+	t->use = 0;
+	t->split = 0;
+
+	return 0;
+}
+
+static void thread_exit_handler(int sig)
+{
+	pthread_exit(0);
+}
+
+static void *fuse_prune_nodes(void *fuse)
+{
+	struct fuse *f = fuse;
+	int sleep_time;
+
+#if defined(__ANDROID__)
+	struct sigaction actions;
+	memset(&actions, 0, sizeof(actions));
+	sigemptyset(&actions.sa_mask);
+	actions.sa_flags = 0;
+	actions.sa_handler = thread_exit_handler;
+	sigaction(SIGUSR1, &actions, NULL);
+
+	sigset_t setusr1;
+	sigemptyset(&setusr1);
+	sigaddset(&setusr1, SIGUSR1);
+	pthread_sigmask(SIG_BLOCK, &setusr1, NULL);
+#endif
+
+	while(1) {
+		sleep_time = fuse_clean_cache(f);
+		sleep(sleep_time);
+	}
+	return NULL;
+}
+
+int fuse_start_cleanup_thread(struct fuse *f)
+{
+	if (lru_enabled(f))
+		return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
+	return 0;
+}
+
+void fuse_stop_cleanup_thread(struct fuse *f)
+{
+	if (lru_enabled(f)) {
+		pthread_mutex_lock(&f->lock);
+#if defined(__ANDROID__)
+		pthread_kill(f->prune_thread, SIGUSR1);
+#else
+		pthread_cancel(f->prune_thread);
+#endif
+		pthread_mutex_unlock(&f->lock);
+		pthread_join(f->prune_thread, NULL);
+	}
+}
+
+struct fuse *fuse_new_common(struct fuse_chan *ch, struct fuse_args *args,
+			     const struct fuse_operations *op,
+			     size_t op_size, void *user_data, int compat)
+{
+	struct fuse *f;
+	struct node *root;
+	struct fuse_fs *fs;
+	struct fuse_lowlevel_ops llop = fuse_path_ops;
+
+	if (fuse_create_context_key() == -1)
+		goto out;
+
+	f = (struct fuse *) calloc(1, sizeof(struct fuse));
+	if (f == NULL) {
+		fprintf(stderr, "fuse: failed to allocate fuse object\n");
+		goto out_delete_context_key;
+	}
+
+	fs = fuse_fs_new(op, op_size, user_data);
+	if (!fs)
+		goto out_free;
+
+	fs->compat = compat;
+	f->fs = fs;
+	f->nullpath_ok = fs->op.flag_nullpath_ok;
+	f->conf.nopath = fs->op.flag_nopath;
+	f->utime_omit_ok = fs->op.flag_utime_omit_ok;
+
+	/* Oh f**k, this is ugly! */
+	if (!fs->op.lock) {
+		llop.getlk = NULL;
+		llop.setlk = NULL;
+	}
+
+	f->conf.entry_timeout = 1.0;
+	f->conf.attr_timeout = 1.0;
+	f->conf.negative_timeout = 0.0;
+	f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
+	f->pagesize = getpagesize();
+	init_list_head(&f->partial_slabs);
+	init_list_head(&f->full_slabs);
+	init_list_head(&f->lru_table);
+
+	if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+			   fuse_lib_opt_proc) == -1)
+		goto out_free_fs;
+
+#ifdef USE_MODULES
+	if (f->conf.modules) {
+		char *module;
+		char *next;
+
+		for (module = f->conf.modules; module; module = next) {
+			char *p;
+			for (p = module; *p && *p != ':'; p++);
+			next = *p ? p + 1 : NULL;
+			*p = '\0';
+			if (module[0] &&
+			    fuse_push_module(f, module, args) == -1)
+				goto out_free_fs;
+		}
+	}
+#endif
+
+	if (!f->conf.ac_attr_timeout_set)
+		f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
+#if defined(__FreeBSD__) || defined(__NetBSD__)
+	/*
+	 * In FreeBSD, we always use these settings as inode numbers
+	 * are needed to make getcwd(3) work.
+	 */
+	f->conf.readdir_ino = 1;
+#endif
+
+	if (compat && compat <= 25) {
+		if (fuse_sync_compat_args(args) == -1)
+			goto out_free_fs;
+	}
+
+	f->se = fuse_lowlevel_new_common(args, &llop, sizeof(llop), f);
+	if (f->se == NULL) {
+#ifdef USE_MODULES
+		if (f->conf.help)
+			fuse_lib_help_modules();
+#endif
+		goto out_free_fs;
+	}
+
+	fuse_session_add_chan(f->se, ch);
+
+	if (f->conf.debug) {
+		fprintf(stderr, "nullpath_ok: %i\n", f->nullpath_ok);
+		fprintf(stderr, "nopath: %i\n", f->conf.nopath);
+		fprintf(stderr, "utime_omit_ok: %i\n", f->utime_omit_ok);
+	}
+
+	/* Trace topmost layer by default */
+	f->fs->debug = f->conf.debug;
+	f->ctr = 0;
+	f->generation = 0;
+	if (node_table_init(&f->name_table) == -1)
+		goto out_free_session;
+
+	if (node_table_init(&f->id_table) == -1)
+		goto out_free_name_table;
+
+	fuse_mutex_init(&f->lock);
+
+	root = alloc_node(f);
+	if (root == NULL) {
+		fprintf(stderr, "fuse: memory allocation failed\n");
+		goto out_free_id_table;
+	}
+	if (lru_enabled(f)) {
+		struct node_lru *lnode = node_lru(root);
+		init_list_head(&lnode->lru);
+	}
+
+	strcpy(root->inline_name, "/");
+	root->name = root->inline_name;
+
+	if (f->conf.intr &&
+	    fuse_init_intr_signal(f->conf.intr_signal,
+				  &f->intr_installed) == -1)
+		goto out_free_root;
+
+	root->parent = NULL;
+	root->nodeid = FUSE_ROOT_ID;
+	inc_nlookup(root);
+	hash_id(f, root);
+
+	return f;
+
+out_free_root:
+	free(root);
+out_free_id_table:
+	free(f->id_table.array);
+out_free_name_table:
+	free(f->name_table.array);
+out_free_session:
+	fuse_session_destroy(f->se);
+out_free_fs:
+	/* Horrible compatibility hack to stop the destructor from being
+	   called on the filesystem without init being called first */
+	fs->op.destroy = NULL;
+	fuse_fs_destroy(f->fs);
+	free(f->conf.modules);
+out_free:
+	free(f);
+out_delete_context_key:
+	fuse_delete_context_key();
+out:
+	return NULL;
+}
+
+struct fuse *fuse_new(struct fuse_chan *ch, struct fuse_args *args,
+		      const struct fuse_operations *op, size_t op_size,
+		      void *user_data)
+{
+	return fuse_new_common(ch, args, op, op_size, user_data, 0);
+}
+
+void fuse_destroy(struct fuse *f)
+{
+	size_t i;
+
+	if (f->conf.intr && f->intr_installed)
+		fuse_restore_intr_signal(f->conf.intr_signal);
+
+	if (f->fs) {
+		struct fuse_context_i *c = fuse_get_context_internal();
+
+		memset(c, 0, sizeof(*c));
+		c->ctx.fuse = f;
+
+		for (i = 0; i < f->id_table.size; i++) {
+			struct node *node;
+
+			for (node = f->id_table.array[i]; node != NULL;
+			     node = node->id_next) {
+				if (node->is_hidden) {
+					char *path;
+					if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+						fuse_fs_unlink(f->fs, path);
+						free(path);
+					}
+				}
+			}
+		}
+	}
+	for (i = 0; i < f->id_table.size; i++) {
+		struct node *node;
+		struct node *next;
+
+		for (node = f->id_table.array[i]; node != NULL; node = next) {
+			next = node->id_next;
+			free_node(f, node);
+			f->id_table.use--;
+		}
+	}
+	assert(list_empty(&f->partial_slabs));
+	assert(list_empty(&f->full_slabs));
+
+	free(f->id_table.array);
+	free(f->name_table.array);
+	pthread_mutex_destroy(&f->lock);
+	fuse_session_destroy(f->se);
+	free(f->conf.modules);
+	free(f);
+	fuse_delete_context_key();
+}
+
+static struct fuse *fuse_new_common_compat25(int fd, struct fuse_args *args,
+					     const struct fuse_operations *op,
+					     size_t op_size, int compat)
+{
+	struct fuse *f = NULL;
+	struct fuse_chan *ch = fuse_kern_chan_new(fd);
+
+	if (ch)
+		f = fuse_new_common(ch, args, op, op_size, NULL, compat);
+
+	return f;
+}
+
+#ifdef USE_MODULES
+/* called with fuse_context_lock held or during initialization (before
+   main() has been called) */
+void fuse_register_module(struct fuse_module *mod)
+{
+	mod->ctr = 0;
+	mod->so = fuse_current_so;
+	if (mod->so)
+		mod->so->ctr++;
+	mod->next = fuse_modules;
+	fuse_modules = mod;
+}
+#endif
+
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
+
+static struct fuse *fuse_new_common_compat(int fd, const char *opts,
+					   const struct fuse_operations *op,
+					   size_t op_size, int compat)
+{
+	struct fuse *f;
+	struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+
+	if (fuse_opt_add_arg(&args, "") == -1)
+		return NULL;
+	if (opts &&
+	    (fuse_opt_add_arg(&args, "-o") == -1 ||
+	     fuse_opt_add_arg(&args, opts) == -1)) {
+		fuse_opt_free_args(&args);
+		return NULL;
+	}
+	f = fuse_new_common_compat25(fd, &args, op, op_size, compat);
+	fuse_opt_free_args(&args);
+
+	return f;
+}
+
+struct fuse *fuse_new_compat22(int fd, const char *opts,
+			       const struct fuse_operations_compat22 *op,
+			       size_t op_size)
+{
+	return fuse_new_common_compat(fd, opts, (struct fuse_operations *) op,
+				      op_size, 22);
+}
+
+struct fuse *fuse_new_compat2(int fd, const char *opts,
+			      const struct fuse_operations_compat2 *op)
+{
+	return fuse_new_common_compat(fd, opts, (struct fuse_operations *) op,
+				      sizeof(struct fuse_operations_compat2),
+				      21);
+}
+
+struct fuse *fuse_new_compat1(int fd, int flags,
+			      const struct fuse_operations_compat1 *op)
+{
+	const char *opts = NULL;
+	if (flags & FUSE_DEBUG_COMPAT1)
+		opts = "debug";
+	return fuse_new_common_compat(fd, opts, (struct fuse_operations *) op,
+				      sizeof(struct fuse_operations_compat1),
+				      11);
+}
+
+FUSE_SYMVER(".symver fuse_exited,__fuse_exited@FUSE_UNVERSIONED");
+FUSE_SYMVER(".symver fuse_process_cmd,__fuse_process_cmd@FUSE_UNVERSIONED");
+FUSE_SYMVER(".symver fuse_read_cmd,__fuse_read_cmd@FUSE_UNVERSIONED");
+FUSE_SYMVER(".symver fuse_set_getcontext_func,__fuse_set_getcontext_func@FUSE_UNVERSIONED");
+FUSE_SYMVER(".symver fuse_new_compat2,fuse_new@FUSE_UNVERSIONED");
+FUSE_SYMVER(".symver fuse_new_compat22,fuse_new@FUSE_2.2");
+
+#endif /* __FreeBSD__ || __NetBSD__  */
+
+struct fuse *fuse_new_compat25(int fd, struct fuse_args *args,
+			       const struct fuse_operations_compat25 *op,
+			       size_t op_size)
+{
+	return fuse_new_common_compat25(fd, args, (struct fuse_operations *) op,
+					op_size, 25);
+}
+
+FUSE_SYMVER(".symver fuse_new_compat25,fuse_new@FUSE_2.5");
diff --git a/fuse/fuse_i.h b/fuse/fuse_i.h
new file mode 100644
index 0000000..fa37156
--- /dev/null
+++ b/fuse/fuse_i.h
@@ -0,0 +1,130 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#include "fuse.h"
+#include "fuse_lowlevel.h"
+
+struct fuse_chan;
+struct fuse_ll;
+
+struct fuse_session {
+	struct fuse_session_ops op;
+
+	int (*receive_buf)(struct fuse_session *se, struct fuse_buf *buf,
+			   struct fuse_chan **chp);
+
+	void (*process_buf)(void *data, const struct fuse_buf *buf,
+			    struct fuse_chan *ch);
+
+	void *data;
+
+	volatile int exited;
+
+	struct fuse_chan *ch;
+};
+
+struct fuse_req {
+	struct fuse_ll *f;
+	uint64_t unique;
+	int ctr;
+	pthread_mutex_t lock;
+	struct fuse_ctx ctx;
+	struct fuse_chan *ch;
+	int interrupted;
+	unsigned int ioctl_64bit : 1;
+	union {
+		struct {
+			uint64_t unique;
+		} i;
+		struct {
+			fuse_interrupt_func_t func;
+			void *data;
+		} ni;
+	} u;
+	struct fuse_req *next;
+	struct fuse_req *prev;
+};
+
+struct fuse_notify_req {
+	uint64_t unique;
+	void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+		      const void *, const struct fuse_buf *);
+	struct fuse_notify_req *next;
+	struct fuse_notify_req *prev;
+};
+
+struct fuse_ll {
+	int debug;
+	int allow_root;
+	int atomic_o_trunc;
+	int no_remote_posix_lock;
+	int no_remote_flock;
+	int big_writes;
+	int splice_write;
+	int splice_move;
+	int splice_read;
+	int no_splice_write;
+	int no_splice_move;
+	int no_splice_read;
+	struct fuse_lowlevel_ops op;
+	int got_init;
+	struct cuse_data *cuse_data;
+	void *userdata;
+	uid_t owner;
+	struct fuse_conn_info conn;
+	struct fuse_req list;
+	struct fuse_req interrupts;
+	pthread_mutex_t lock;
+	int got_destroy;
+	pthread_key_t pipe_key;
+	int broken_splice_nonblock;
+	uint64_t notify_ctr;
+	struct fuse_notify_req notify_list;
+};
+
+struct fuse_cmd {
+	char *buf;
+	size_t buflen;
+	struct fuse_chan *ch;
+};
+
+struct fuse *fuse_new_common(struct fuse_chan *ch, struct fuse_args *args,
+			     const struct fuse_operations *op,
+			     size_t op_size, void *user_data, int compat);
+
+int fuse_sync_compat_args(struct fuse_args *args);
+
+struct fuse_chan *fuse_kern_chan_new(int fd);
+
+struct fuse_session *fuse_lowlevel_new_common(struct fuse_args *args,
+					const struct fuse_lowlevel_ops *op,
+					size_t op_size, void *userdata);
+
+void fuse_kern_unmount_compat22(const char *mountpoint);
+int fuse_chan_clearfd(struct fuse_chan *ch);
+
+void fuse_kern_unmount(const char *mountpoint, int fd);
+int fuse_kern_mount(const char *mountpoint, struct fuse_args *args);
+
+int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+			       int count);
+void fuse_free_req(fuse_req_t req);
+
+
+struct fuse *fuse_setup_common(int argc, char *argv[],
+			       const struct fuse_operations *op,
+			       size_t op_size,
+			       char **mountpoint,
+			       int *multithreaded,
+			       int *fd,
+			       void *user_data,
+			       int compat);
+
+void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
+int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
diff --git a/fuse/fuse_kern_chan.c b/fuse/fuse_kern_chan.c
new file mode 100644
index 0000000..4a9beb8
--- /dev/null
+++ b/fuse/fuse_kern_chan.c
@@ -0,0 +1,98 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#include "fuse_lowlevel.h"
+#include "fuse_kernel.h"
+#include "fuse_i.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+
+static int fuse_kern_chan_receive(struct fuse_chan **chp, char *buf,
+				  size_t size)
+{
+	struct fuse_chan *ch = *chp;
+	int err;
+	ssize_t res;
+	struct fuse_session *se = fuse_chan_session(ch);
+	assert(se != NULL);
+
+restart:
+	res = read(fuse_chan_fd(ch), buf, size);
+	err = errno;
+
+	if (fuse_session_exited(se))
+		return 0;
+	if (res == -1) {
+		/* ENOENT means the operation was interrupted, it's safe
+		   to restart */
+		if (err == ENOENT)
+			goto restart;
+
+		if (err == ENODEV) {
+			fuse_session_exit(se);
+			return 0;
+		}
+		/* Errors occurring during normal operation: EINTR (read
+		   interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+		   umounted) */
+		if (err != EINTR && err != EAGAIN)
+			perror("fuse: reading device");
+		return -err;
+	}
+	if ((size_t) res < sizeof(struct fuse_in_header)) {
+		fprintf(stderr, "short read on fuse device\n");
+		return -EIO;
+	}
+	return res;
+}
+
+static int fuse_kern_chan_send(struct fuse_chan *ch, const struct iovec iov[],
+			       size_t count)
+{
+	if (iov) {
+		ssize_t res = writev(fuse_chan_fd(ch), iov, count);
+		int err = errno;
+
+		if (res == -1) {
+			struct fuse_session *se = fuse_chan_session(ch);
+
+			assert(se != NULL);
+
+			/* ENOENT means the operation was interrupted */
+			if (!fuse_session_exited(se) && err != ENOENT)
+				perror("fuse: writing device");
+			return -err;
+		}
+	}
+	return 0;
+}
+
+static void fuse_kern_chan_destroy(struct fuse_chan *ch)
+{
+	int fd = fuse_chan_fd(ch);
+
+	if (fd != -1)
+		close(fd);
+}
+
+#define MIN_BUFSIZE 0x21000
+
+struct fuse_chan *fuse_kern_chan_new(int fd)
+{
+	struct fuse_chan_ops op = {
+		.receive = fuse_kern_chan_receive,
+		.send = fuse_kern_chan_send,
+		.destroy = fuse_kern_chan_destroy,
+	};
+	size_t bufsize = getpagesize() + 0x1000;
+	bufsize = bufsize < MIN_BUFSIZE ? MIN_BUFSIZE : bufsize;
+	return fuse_chan_new(&op, fd, bufsize, NULL);
+}
diff --git a/fuse/fuse_loop.c b/fuse/fuse_loop.c
new file mode 100644
index 0000000..b7b4ca4
--- /dev/null
+++ b/fuse/fuse_loop.c
@@ -0,0 +1,46 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#include "fuse_lowlevel.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+int fuse_session_loop(struct fuse_session *se)
+{
+	int res = 0;
+	struct fuse_chan *ch = fuse_session_next_chan(se, NULL);
+	size_t bufsize = fuse_chan_bufsize(ch);
+	char *buf = (char *) malloc(bufsize);
+	if (!buf) {
+		fprintf(stderr, "fuse: failed to allocate read buffer\n");
+		return -1;
+	}
+
+	while (!fuse_session_exited(se)) {
+		struct fuse_chan *tmpch = ch;
+		struct fuse_buf fbuf = {
+			.mem = buf,
+			.size = bufsize,
+		};
+
+		res = fuse_session_receive_buf(se, &fbuf, &tmpch);
+
+		if (res == -EINTR)
+			continue;
+		if (res <= 0)
+			break;
+
+		fuse_session_process_buf(se, &fbuf, tmpch);
+	}
+
+	free(buf);
+	fuse_session_reset(se);
+	return res < 0 ? -1 : 0;
+}
diff --git a/fuse/fuse_loop_mt.c b/fuse/fuse_loop_mt.c
new file mode 100644
index 0000000..90fc1e6
--- /dev/null
+++ b/fuse/fuse_loop_mt.c
@@ -0,0 +1,291 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#include "fuse_lowlevel.h"
+#include "fuse_misc.h"
+#include "fuse_kernel.h"
+#include "fuse_i.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <semaphore.h>
+#include <errno.h>
+#include <sys/time.h>
+
+/* Environment var controlling the thread stack size */
+#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
+struct fuse_worker {
+	struct fuse_worker *prev;
+	struct fuse_worker *next;
+	pthread_t thread_id;
+	size_t bufsize;
+	char *buf;
+	struct fuse_mt *mt;
+};
+
+struct fuse_mt {
+	pthread_mutex_t lock;
+	int numworker;
+	int numavail;
+	struct fuse_session *se;
+	struct fuse_chan *prevch;
+	struct fuse_worker main;
+	sem_t finish;
+	int exit;
+	int error;
+};
+
+static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+{
+	struct fuse_worker *prev = next->prev;
+	w->next = next;
+	w->prev = prev;
+	prev->next = w;
+	next->prev = w;
+}
+
+static void list_del_worker(struct fuse_worker *w)
+{
+	struct fuse_worker *prev = w->prev;
+	struct fuse_worker *next = w->next;
+	prev->next = next;
+	next->prev = prev;
+}
+
+static int fuse_loop_start_thread(struct fuse_mt *mt);
+
+static void thread_exit_handler(int sig)
+{
+	pthread_exit(0);
+}
+
+static void *fuse_do_work(void *data)
+{
+	struct fuse_worker *w = (struct fuse_worker *) data;
+	struct fuse_mt *mt = w->mt;
+
+#if defined(__ANDROID__)
+	struct sigaction actions;
+	memset(&actions, 0, sizeof(actions));
+	sigemptyset(&actions.sa_mask);
+	actions.sa_flags = 0;
+	actions.sa_handler = thread_exit_handler;
+	sigaction(SIGUSR1, &actions, NULL);
+
+	sigset_t setusr1;
+	sigemptyset(&setusr1);
+	sigaddset(&setusr1, SIGUSR1);
+	pthread_sigmask(SIG_BLOCK, &setusr1, NULL);
+#endif
+
+	while (!fuse_session_exited(mt->se)) {
+		int isforget = 0;
+		struct fuse_chan *ch = mt->prevch;
+		struct fuse_buf fbuf = {
+			.mem = w->buf,
+			.size = w->bufsize,
+		};
+		int res;
+
+#if defined(__ANDROID__)
+		pthread_sigmask(SIG_UNBLOCK, &setusr1, NULL);
+#else
+		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+#endif
+		res = fuse_session_receive_buf(mt->se, &fbuf, &ch);
+#if defined(__ANDROID__)
+		pthread_sigmask(SIG_BLOCK, &setusr1, NULL);
+#else
+		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+#endif
+		if (res == -EINTR)
+			continue;
+		if (res <= 0) {
+			if (res < 0) {
+				fuse_session_exit(mt->se);
+				mt->error = -1;
+			}
+			break;
+		}
+
+		pthread_mutex_lock(&mt->lock);
+		if (mt->exit) {
+			pthread_mutex_unlock(&mt->lock);
+			return NULL;
+		}
+
+		/*
+		 * This disgusting hack is needed so that zillions of threads
+		 * are not created on a burst of FORGET messages
+		 */
+		if (!(fbuf.flags & FUSE_BUF_IS_FD)) {
+			struct fuse_in_header *in = fbuf.mem;
+
+			if (in->opcode == FUSE_FORGET ||
+			    in->opcode == FUSE_BATCH_FORGET)
+				isforget = 1;
+		}
+
+		if (!isforget)
+			mt->numavail--;
+		if (mt->numavail == 0)
+			fuse_loop_start_thread(mt);
+		pthread_mutex_unlock(&mt->lock);
+
+		fuse_session_process_buf(mt->se, &fbuf, ch);
+
+		pthread_mutex_lock(&mt->lock);
+		if (!isforget)
+			mt->numavail++;
+		if (mt->numavail > 10) {
+			if (mt->exit) {
+				pthread_mutex_unlock(&mt->lock);
+				return NULL;
+			}
+			list_del_worker(w);
+			mt->numavail--;
+			mt->numworker--;
+			pthread_mutex_unlock(&mt->lock);
+
+			pthread_detach(w->thread_id);
+			free(w->buf);
+			free(w);
+			return NULL;
+		}
+		pthread_mutex_unlock(&mt->lock);
+	}
+
+	sem_post(&mt->finish);
+
+	return NULL;
+}
+
+int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+{
+	sigset_t oldset;
+	sigset_t newset;
+	int res;
+	pthread_attr_t attr;
+	char *stack_size;
+
+	/* Override default stack size */
+	pthread_attr_init(&attr);
+	stack_size = getenv(ENVNAME_THREAD_STACK);
+	if (stack_size && pthread_attr_setstacksize(&attr, atoi(stack_size)))
+		fprintf(stderr, "fuse: invalid stack size: %s\n", stack_size);
+
+	/* Disallow signal reception in worker threads */
+	sigemptyset(&newset);
+	sigaddset(&newset, SIGTERM);
+	sigaddset(&newset, SIGINT);
+	sigaddset(&newset, SIGHUP);
+	sigaddset(&newset, SIGQUIT);
+	pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+	res = pthread_create(thread_id, &attr, func, arg);
+	pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+	pthread_attr_destroy(&attr);
+	if (res != 0) {
+		fprintf(stderr, "fuse: error creating thread: %s\n",
+			strerror(res));
+		return -1;
+	}
+
+	return 0;
+}
+
+static int fuse_loop_start_thread(struct fuse_mt *mt)
+{
+	int res;
+	struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+	if (!w) {
+		fprintf(stderr, "fuse: failed to allocate worker structure\n");
+		return -1;
+	}
+	memset(w, 0, sizeof(struct fuse_worker));
+	w->bufsize = fuse_chan_bufsize(mt->prevch);
+	w->buf = malloc(w->bufsize);
+	w->mt = mt;
+	if (!w->buf) {
+		fprintf(stderr, "fuse: failed to allocate read buffer\n");
+		free(w);
+		return -1;
+	}
+
+	res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+	if (res == -1) {
+		free(w->buf);
+		free(w);
+		return -1;
+	}
+	list_add_worker(w, &mt->main);
+	mt->numavail ++;
+	mt->numworker ++;
+
+	return 0;
+}
+
+static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+{
+	pthread_join(w->thread_id, NULL);
+	pthread_mutex_lock(&mt->lock);
+	list_del_worker(w);
+	pthread_mutex_unlock(&mt->lock);
+	free(w->buf);
+	free(w);
+}
+
+int fuse_session_loop_mt(struct fuse_session *se)
+{
+	int err;
+	struct fuse_mt mt;
+	struct fuse_worker *w;
+
+	memset(&mt, 0, sizeof(struct fuse_mt));
+	mt.se = se;
+	mt.prevch = fuse_session_next_chan(se, NULL);
+	mt.error = 0;
+	mt.numworker = 0;
+	mt.numavail = 0;
+	mt.main.thread_id = pthread_self();
+	mt.main.prev = mt.main.next = &mt.main;
+	sem_init(&mt.finish, 0, 0);
+	fuse_mutex_init(&mt.lock);
+
+	pthread_mutex_lock(&mt.lock);
+	err = fuse_loop_start_thread(&mt);
+	pthread_mutex_unlock(&mt.lock);
+	if (!err) {
+		/* sem_wait() is interruptible */
+		while (!fuse_session_exited(se))
+			sem_wait(&mt.finish);
+
+		pthread_mutex_lock(&mt.lock);
+		for (w = mt.main.next; w != &mt.main; w = w->next)
+#if defined(__ANDROID__)
+			pthread_kill(w->thread_id, SIGUSR1);
+#else
+			pthread_cancel(w->thread_id);
+#endif
+		mt.exit = 1;
+		pthread_mutex_unlock(&mt.lock);
+
+		while (mt.main.next != &mt.main)
+			fuse_join_worker(&mt, mt.main.next);
+
+		err = mt.error;
+	}
+
+	pthread_mutex_destroy(&mt.lock);
+	sem_destroy(&mt.finish);
+	fuse_session_reset(se);
+	return err;
+}
diff --git a/fuse/fuse_lowlevel.c b/fuse/fuse_lowlevel.c
new file mode 100644
index 0000000..5f223c9
--- /dev/null
+++ b/fuse/fuse_lowlevel.c
@@ -0,0 +1,2968 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#define _GNU_SOURCE
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_kernel.h"
+#include "fuse_opt.h"
+#include "fuse_misc.h"
+#include "fuse_common_compat.h"
+#include "fuse_lowlevel_compat.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/file.h>
+
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE       1024
+#endif
+#ifndef F_SETPIPE_SZ
+#define F_SETPIPE_SZ	(F_LINUX_SPECIFIC_BASE + 7)
+#endif
+
+
+#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+#define OFFSET_MAX 0x7fffffffffffffffLL
+
+#define container_of(ptr, type, member) ({				\
+			const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+			(type *)( (char *)__mptr - offsetof(type,member) );})
+
+struct fuse_pollhandle {
+	uint64_t kh;
+	struct fuse_chan *ch;
+	struct fuse_ll *f;
+};
+
+static size_t pagesize;
+
+static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+{
+	pagesize = getpagesize();
+}
+
+static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+{
+	attr->ino	= stbuf->st_ino;
+	attr->mode	= stbuf->st_mode;
+	attr->nlink	= stbuf->st_nlink;
+	attr->uid	= stbuf->st_uid;
+	attr->gid	= stbuf->st_gid;
+	attr->rdev	= stbuf->st_rdev;
+	attr->size	= stbuf->st_size;
+	attr->blksize	= stbuf->st_blksize;
+	attr->blocks	= stbuf->st_blocks;
+	attr->atime	= stbuf->st_atime;
+	attr->mtime	= stbuf->st_mtime;
+	attr->ctime	= stbuf->st_ctime;
+	attr->atimensec = ST_ATIM_NSEC(stbuf);
+	attr->mtimensec = ST_MTIM_NSEC(stbuf);
+	attr->ctimensec = ST_CTIM_NSEC(stbuf);
+}
+
+static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+{
+	stbuf->st_mode	       = attr->mode;
+	stbuf->st_uid	       = attr->uid;
+	stbuf->st_gid	       = attr->gid;
+	stbuf->st_size	       = attr->size;
+	stbuf->st_atime	       = attr->atime;
+	stbuf->st_mtime	       = attr->mtime;
+	ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+	ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+}
+
+static	size_t iov_length(const struct iovec *iov, size_t count)
+{
+	size_t seg;
+	size_t ret = 0;
+
+	for (seg = 0; seg < count; seg++)
+		ret += iov[seg].iov_len;
+	return ret;
+}
+
+static void list_init_req(struct fuse_req *req)
+{
+	req->next = req;
+	req->prev = req;
+}
+
+static void list_del_req(struct fuse_req *req)
+{
+	struct fuse_req *prev = req->prev;
+	struct fuse_req *next = req->next;
+	prev->next = next;
+	next->prev = prev;
+}
+
+static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+{
+	struct fuse_req *prev = next->prev;
+	req->next = next;
+	req->prev = prev;
+	prev->next = req;
+	next->prev = req;
+}
+
+static void destroy_req(fuse_req_t req)
+{
+	pthread_mutex_destroy(&req->lock);
+	free(req);
+}
+
+void fuse_free_req(fuse_req_t req)
+{
+	int ctr;
+	struct fuse_ll *f = req->f;
+
+	pthread_mutex_lock(&f->lock);
+	req->u.ni.func = NULL;
+	req->u.ni.data = NULL;
+	list_del_req(req);
+	ctr = --req->ctr;
+	pthread_mutex_unlock(&f->lock);
+	if (!ctr)
+		destroy_req(req);
+}
+
+static struct fuse_req *fuse_ll_alloc_req(struct fuse_ll *f)
+{
+	struct fuse_req *req;
+
+	req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+	if (req == NULL) {
+		fprintf(stderr, "fuse: failed to allocate request\n");
+	} else {
+		req->f = f;
+		req->ctr = 1;
+		list_init_req(req);
+		fuse_mutex_init(&req->lock);
+	}
+
+	return req;
+}
+
+
+static int fuse_send_msg(struct fuse_ll *f, struct fuse_chan *ch,
+			 struct iovec *iov, int count)
+{
+	struct fuse_out_header *out = iov[0].iov_base;
+
+	out->len = iov_length(iov, count);
+	if (f->debug) {
+		if (out->unique == 0) {
+			fprintf(stderr, "NOTIFY: code=%d length=%u\n",
+				out->error, out->len);
+		} else if (out->error) {
+			fprintf(stderr,
+				"   unique: %llu, error: %i (%s), outsize: %i\n",
+				(unsigned long long) out->unique, out->error,
+				strerror(-out->error), out->len);
+		} else {
+			fprintf(stderr,
+				"   unique: %llu, success, outsize: %i\n",
+				(unsigned long long) out->unique, out->len);
+		}
+	}
+
+	return fuse_chan_send(ch, iov, count);
+}
+
+int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+			       int count)
+{
+	struct fuse_out_header out;
+
+	if (error <= -1000 || error > 0) {
+		fprintf(stderr, "fuse: bad error value: %i\n",	error);
+		error = -ERANGE;
+	}
+
+	out.unique = req->unique;
+	out.error = error;
+
+	iov[0].iov_base = &out;
+	iov[0].iov_len = sizeof(struct fuse_out_header);
+
+	return fuse_send_msg(req->f, req->ch, iov, count);
+}
+
+static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+			  int count)
+{
+	int res;
+
+	res = fuse_send_reply_iov_nofree(req, error, iov, count);
+	fuse_free_req(req);
+	return res;
+}
+
+static int send_reply(fuse_req_t req, int error, const void *arg,
+		      size_t argsize)
+{
+	struct iovec iov[2];
+	int count = 1;
+	if (argsize) {
+		iov[1].iov_base = (void *) arg;
+		iov[1].iov_len = argsize;
+		count++;
+	}
+	return send_reply_iov(req, error, iov, count);
+}
+
+int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+{
+	int res;
+	struct iovec *padded_iov;
+
+	padded_iov = malloc((count + 1) * sizeof(struct iovec));
+	if (padded_iov == NULL)
+		return fuse_reply_err(req, ENOMEM);
+
+	memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+	count++;
+
+	res = send_reply_iov(req, 0, padded_iov, count);
+	free(padded_iov);
+
+	return res;
+}
+
+size_t fuse_dirent_size(size_t namelen)
+{
+	return FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + namelen);
+}
+
+char *fuse_add_dirent(char *buf, const char *name, const struct stat *stbuf,
+		      loff_t off)
+{
+	unsigned namelen = strlen(name);
+	unsigned entlen = FUSE_NAME_OFFSET + namelen;
+	unsigned entsize = fuse_dirent_size(namelen);
+	unsigned padlen = entsize - entlen;
+	struct fuse_dirent *dirent = (struct fuse_dirent *) buf;
+
+	dirent->ino = stbuf->st_ino;
+	dirent->off = off;
+	dirent->namelen = namelen;
+	dirent->type = (stbuf->st_mode & 0170000) >> 12;
+	strncpy(dirent->name, name, namelen);
+	if (padlen)
+		memset(buf + entlen, 0, padlen);
+
+	return buf + entsize;
+}
+
+size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+			 const char *name, const struct stat *stbuf, loff_t off)
+{
+	size_t entsize;
+
+	(void) req;
+	entsize = fuse_dirent_size(strlen(name));
+	if (entsize <= bufsize && buf)
+		fuse_add_dirent(buf, name, stbuf, off);
+	return entsize;
+}
+
+static void convert_statfs(const struct statvfs *stbuf,
+			   struct fuse_kstatfs *kstatfs)
+{
+	kstatfs->bsize	 = stbuf->f_bsize;
+	kstatfs->frsize	 = stbuf->f_frsize;
+	kstatfs->blocks	 = stbuf->f_blocks;
+	kstatfs->bfree	 = stbuf->f_bfree;
+	kstatfs->bavail	 = stbuf->f_bavail;
+	kstatfs->files	 = stbuf->f_files;
+	kstatfs->ffree	 = stbuf->f_ffree;
+	kstatfs->namelen = stbuf->f_namemax;
+}
+
+static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+{
+	return send_reply(req, 0, arg, argsize);
+}
+
+int fuse_reply_err(fuse_req_t req, int err)
+{
+	return send_reply(req, -err, NULL, 0);
+}
+
+void fuse_reply_none(fuse_req_t req)
+{
+	if (req->ch)
+		fuse_chan_send(req->ch, NULL, 0);
+	fuse_free_req(req);
+}
+
+static unsigned long calc_timeout_sec(double t)
+{
+	if (t > (double) ULONG_MAX)
+		return ULONG_MAX;
+	else if (t < 0.0)
+		return 0;
+	else
+		return (unsigned long) t;
+}
+
+static unsigned int calc_timeout_nsec(double t)
+{
+	double f = t - (double) calc_timeout_sec(t);
+	if (f < 0.0)
+		return 0;
+	else if (f >= 0.999999999)
+		return 999999999;
+	else
+		return (unsigned int) (f * 1.0e9);
+}
+
+static void fill_entry(struct fuse_entry_out *arg,
+		       const struct fuse_entry_param *e)
+{
+	arg->nodeid = e->ino;
+	arg->generation = e->generation;
+	arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+	arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+	arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+	arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+	convert_stat(&e->attr, &arg->attr);
+}
+
+static void fill_open(struct fuse_open_out *arg,
+		      const struct fuse_file_info *f)
+{
+	arg->fh = f->fh;
+	if (f->direct_io)
+		arg->open_flags |= FOPEN_DIRECT_IO;
+	if (f->keep_cache)
+		arg->open_flags |= FOPEN_KEEP_CACHE;
+	if (f->nonseekable)
+		arg->open_flags |= FOPEN_NONSEEKABLE;
+}
+
+int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+{
+	struct fuse_entry_out arg;
+	size_t size = req->f->conn.proto_minor < 9 ?
+		FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
+	/* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+	   negative entry */
+	if (!e->ino && req->f->conn.proto_minor < 4)
+		return fuse_reply_err(req, ENOENT);
+
+	memset(&arg, 0, sizeof(arg));
+	fill_entry(&arg, e);
+	return send_reply_ok(req, &arg, size);
+}
+
+int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+		      const struct fuse_file_info *f)
+{
+	char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+	size_t entrysize = req->f->conn.proto_minor < 9 ?
+		FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+	struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+	struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
+	memset(buf, 0, sizeof(buf));
+	fill_entry(earg, e);
+	fill_open(oarg, f);
+	return send_reply_ok(req, buf,
+			     entrysize + sizeof(struct fuse_open_out));
+}
+
+int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+		    double attr_timeout)
+{
+	struct fuse_attr_out arg;
+	size_t size = req->f->conn.proto_minor < 9 ?
+		FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
+	memset(&arg, 0, sizeof(arg));
+	arg.attr_valid = calc_timeout_sec(attr_timeout);
+	arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+	convert_stat(attr, &arg.attr);
+
+	return send_reply_ok(req, &arg, size);
+}
+
+int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+{
+	return send_reply_ok(req, linkname, strlen(linkname));
+}
+
+int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f)
+{
+	struct fuse_open_out arg;
+
+	memset(&arg, 0, sizeof(arg));
+	fill_open(&arg, f);
+	return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+int fuse_reply_write(fuse_req_t req, size_t count)
+{
+	struct fuse_write_out arg;
+
+	memset(&arg, 0, sizeof(arg));
+	arg.size = count;
+
+	return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+{
+	return send_reply_ok(req, buf, size);
+}
+
+static int fuse_send_data_iov_fallback(struct fuse_ll *f, struct fuse_chan *ch,
+				       struct iovec *iov, int iov_count,
+				       struct fuse_bufvec *buf,
+				       size_t len)
+{
+	struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+	void *mbuf;
+	int res;
+
+	/* Optimize common case */
+	if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+	    !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+		/* FIXME: also avoid memory copy if there are multiple buffers
+		   but none of them contain an fd */
+
+		iov[iov_count].iov_base = buf->buf[0].mem;
+		iov[iov_count].iov_len = len;
+		iov_count++;
+		return fuse_send_msg(f, ch, iov, iov_count);
+	}
+
+	res = posix_memalign(&mbuf, pagesize, len);
+	if (res != 0)
+		return res;
+
+	mem_buf.buf[0].mem = mbuf;
+	res = fuse_buf_copy(&mem_buf, buf, 0);
+	if (res < 0) {
+		free(mbuf);
+		return -res;
+	}
+	len = res;
+
+	iov[iov_count].iov_base = mbuf;
+	iov[iov_count].iov_len = len;
+	iov_count++;
+	res = fuse_send_msg(f, ch, iov, iov_count);
+	free(mbuf);
+
+	return res;
+}
+
+struct fuse_ll_pipe {
+	size_t size;
+	int can_grow;
+	int pipe[2];
+};
+
+static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+{
+	close(llp->pipe[0]);
+	close(llp->pipe[1]);
+	free(llp);
+}
+
+#ifdef HAVE_SPLICE
+static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_ll *f)
+{
+	struct fuse_ll_pipe *llp = pthread_getspecific(f->pipe_key);
+	if (llp == NULL) {
+		int res;
+
+		llp = malloc(sizeof(struct fuse_ll_pipe));
+		if (llp == NULL)
+			return NULL;
+
+		res = pipe(llp->pipe);
+		if (res == -1) {
+			free(llp);
+			return NULL;
+		}
+
+		if (fcntl(llp->pipe[0], F_SETFL, O_NONBLOCK) == -1 ||
+		    fcntl(llp->pipe[1], F_SETFL, O_NONBLOCK) == -1) {
+			close(llp->pipe[0]);
+			close(llp->pipe[1]);
+			free(llp);
+			return NULL;
+		}
+
+		/*
+		 *the default size is 16 pages on linux
+		 */
+		llp->size = pagesize * 16;
+		llp->can_grow = 1;
+
+		pthread_setspecific(f->pipe_key, llp);
+	}
+
+	return llp;
+}
+#endif
+
+static void fuse_ll_clear_pipe(struct fuse_ll *f)
+{
+	struct fuse_ll_pipe *llp = pthread_getspecific(f->pipe_key);
+	if (llp) {
+		pthread_setspecific(f->pipe_key, NULL);
+		fuse_ll_pipe_free(llp);
+	}
+}
+
+#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+static int read_back(int fd, char *buf, size_t len)
+{
+	int res;
+
+	res = read(fd, buf, len);
+	if (res == -1) {
+		fprintf(stderr, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno));
+		return -EIO;
+	}
+	if (res != len) {
+		fprintf(stderr, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len);
+		return -EIO;
+	}
+	return 0;
+}
+
+static int fuse_send_data_iov(struct fuse_ll *f, struct fuse_chan *ch,
+			       struct iovec *iov, int iov_count,
+			       struct fuse_bufvec *buf, unsigned int flags)
+{
+	int res;
+	size_t len = fuse_buf_size(buf);
+	struct fuse_out_header *out = iov[0].iov_base;
+	struct fuse_ll_pipe *llp;
+	int splice_flags;
+	size_t pipesize;
+	size_t total_fd_size;
+	size_t idx;
+	size_t headerlen;
+	struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
+	if (f->broken_splice_nonblock)
+		goto fallback;
+
+	if (flags & FUSE_BUF_NO_SPLICE)
+		goto fallback;
+
+	total_fd_size = 0;
+	for (idx = buf->idx; idx < buf->count; idx++) {
+		if (buf->buf[idx].flags & FUSE_BUF_IS_FD) {
+			total_fd_size = buf->buf[idx].size;
+			if (idx == buf->idx)
+				total_fd_size -= buf->off;
+		}
+	}
+	if (total_fd_size < 2 * pagesize)
+		goto fallback;
+
+	if (f->conn.proto_minor < 14 ||
+	    !(f->conn.want & FUSE_CAP_SPLICE_WRITE))
+		goto fallback;
+
+	llp = fuse_ll_get_pipe(f);
+	if (llp == NULL)
+		goto fallback;
+
+
+	headerlen = iov_length(iov, iov_count);
+
+	out->len = headerlen + len;
+
+	/*
+	 * Heuristic for the required pipe size, does not work if the
+	 * source contains less than page size fragments
+	 */
+	pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
+	if (llp->size < pipesize) {
+		if (llp->can_grow) {
+			res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+			if (res == -1) {
+				llp->can_grow = 0;
+				goto fallback;
+			}
+			llp->size = res;
+		}
+		if (llp->size < pipesize)
+			goto fallback;
+	}
+
+
+	res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+	if (res == -1)
+		goto fallback;
+
+	if (res != headerlen) {
+		res = -EIO;
+		fprintf(stderr, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+			headerlen);
+		goto clear_pipe;
+	}
+
+	pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+	pipe_buf.buf[0].fd = llp->pipe[1];
+
+	res = fuse_buf_copy(&pipe_buf, buf,
+			    FUSE_BUF_FORCE_SPLICE | FUSE_BUF_SPLICE_NONBLOCK);
+	if (res < 0) {
+		if (res == -EAGAIN || res == -EINVAL) {
+			/*
+			 * Should only get EAGAIN on kernels with
+			 * broken SPLICE_F_NONBLOCK support (<=
+			 * 2.6.35) where this error or a short read is
+			 * returned even if the pipe itself is not
+			 * full
+			 *
+			 * EINVAL might mean that splice can't handle
+			 * this combination of input and output.
+			 */
+			if (res == -EAGAIN)
+				f->broken_splice_nonblock = 1;
+
+			pthread_setspecific(f->pipe_key, NULL);
+			fuse_ll_pipe_free(llp);
+			goto fallback;
+		}
+		res = -res;
+		goto clear_pipe;
+	}
+
+	if (res != 0 && res < len) {
+		struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+		void *mbuf;
+		size_t now_len = res;
+		/*
+		 * For regular files a short count is either
+		 *  1) due to EOF, or
+		 *  2) because of broken SPLICE_F_NONBLOCK (see above)
+		 *
+		 * For other inputs it's possible that we overflowed
+		 * the pipe because of small buffer fragments.
+		 */
+
+		res = posix_memalign(&mbuf, pagesize, len);
+		if (res != 0)
+			goto clear_pipe;
+
+		mem_buf.buf[0].mem = mbuf;
+		mem_buf.off = now_len;
+		res = fuse_buf_copy(&mem_buf, buf, 0);
+		if (res > 0) {
+			char *tmpbuf;
+			size_t extra_len = res;
+			/*
+			 * Trickiest case: got more data.  Need to get
+			 * back the data from the pipe and then fall
+			 * back to regular write.
+			 */
+			tmpbuf = malloc(headerlen);
+			if (tmpbuf == NULL) {
+				free(mbuf);
+				res = ENOMEM;
+				goto clear_pipe;
+			}
+			res = read_back(llp->pipe[0], tmpbuf, headerlen);
+			if (res != 0) {
+				free(mbuf);
+				goto clear_pipe;
+			}
+			free(tmpbuf);
+			res = read_back(llp->pipe[0], mbuf, now_len);
+			if (res != 0) {
+				free(mbuf);
+				goto clear_pipe;
+			}
+			len = now_len + extra_len;
+			iov[iov_count].iov_base = mbuf;
+			iov[iov_count].iov_len = len;
+			iov_count++;
+			res = fuse_send_msg(f, ch, iov, iov_count);
+			free(mbuf);
+			return res;
+		}
+		free(mbuf);
+		res = now_len;
+	}
+	len = res;
+	out->len = headerlen + len;
+
+	if (f->debug) {
+		fprintf(stderr,
+			"   unique: %llu, success, outsize: %i (splice)\n",
+			(unsigned long long) out->unique, out->len);
+	}
+
+	splice_flags = 0;
+	if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+	    (f->conn.want & FUSE_CAP_SPLICE_MOVE))
+		splice_flags |= SPLICE_F_MOVE;
+
+	res = splice(llp->pipe[0], NULL,
+		     fuse_chan_fd(ch), NULL, out->len, splice_flags);
+	if (res == -1) {
+		res = -errno;
+		perror("fuse: splice from pipe");
+		goto clear_pipe;
+	}
+	if (res != out->len) {
+		res = -EIO;
+		fprintf(stderr, "fuse: short splice from pipe: %u/%u\n",
+			res, out->len);
+		goto clear_pipe;
+	}
+	return 0;
+
+clear_pipe:
+	fuse_ll_clear_pipe(f);
+	return res;
+
+fallback:
+	return fuse_send_data_iov_fallback(f, ch, iov, iov_count, buf, len);
+}
+#else
+static int fuse_send_data_iov(struct fuse_ll *f, struct fuse_chan *ch,
+			       struct iovec *iov, int iov_count,
+			       struct fuse_bufvec *buf, unsigned int flags)
+{
+	size_t len = fuse_buf_size(buf);
+	(void) flags;
+
+	return fuse_send_data_iov_fallback(f, ch, iov, iov_count, buf, len);
+}
+#endif
+
+int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+		    enum fuse_buf_copy_flags flags)
+{
+	struct iovec iov[2];
+	struct fuse_out_header out;
+	int res;
+
+	iov[0].iov_base = &out;
+	iov[0].iov_len = sizeof(struct fuse_out_header);
+
+	out.unique = req->unique;
+	out.error = 0;
+
+	res = fuse_send_data_iov(req->f, req->ch, iov, 1, bufv, flags);
+	if (res <= 0) {
+		fuse_free_req(req);
+		return res;
+	} else {
+		return fuse_reply_err(req, res);
+	}
+}
+
+int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+{
+	struct fuse_statfs_out arg;
+	size_t size = req->f->conn.proto_minor < 4 ?
+		FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
+	memset(&arg, 0, sizeof(arg));
+	convert_statfs(stbuf, &arg.st);
+
+	return send_reply_ok(req, &arg, size);
+}
+
+int fuse_reply_xattr(fuse_req_t req, size_t count)
+{
+	struct fuse_getxattr_out arg;
+
+	memset(&arg, 0, sizeof(arg));
+	arg.size = count;
+
+	return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+{
+	struct fuse_lk_out arg;
+
+	memset(&arg, 0, sizeof(arg));
+	arg.lk.type = lock->l_type;
+	if (lock->l_type != F_UNLCK) {
+		arg.lk.start = lock->l_start;
+		if (lock->l_len == 0)
+			arg.lk.end = OFFSET_MAX;
+		else
+			arg.lk.end = lock->l_start + lock->l_len - 1;
+	}
+	arg.lk.pid = lock->l_pid;
+	return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+{
+	struct fuse_bmap_out arg;
+
+	memset(&arg, 0, sizeof(arg));
+	arg.block = idx;
+
+	return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+						      size_t count)
+{
+	struct fuse_ioctl_iovec *fiov;
+	size_t i;
+
+	fiov = malloc(sizeof(fiov[0]) * count);
+	if (!fiov)
+		return NULL;
+
+	for (i = 0; i < count; i++) {
+		fiov[i].base = (uintptr_t) iov[i].iov_base;
+		fiov[i].len = iov[i].iov_len;
+	}
+
+	return fiov;
+}
+
+int fuse_reply_ioctl_retry(fuse_req_t req,
+			   const struct iovec *in_iov, size_t in_count,
+			   const struct iovec *out_iov, size_t out_count)
+{
+	struct fuse_ioctl_out arg;
+	struct fuse_ioctl_iovec *in_fiov = NULL;
+	struct fuse_ioctl_iovec *out_fiov = NULL;
+	struct iovec iov[4];
+	size_t count = 1;
+	int res;
+
+	memset(&arg, 0, sizeof(arg));
+	arg.flags |= FUSE_IOCTL_RETRY;
+	arg.in_iovs = in_count;
+	arg.out_iovs = out_count;
+	iov[count].iov_base = &arg;
+	iov[count].iov_len = sizeof(arg);
+	count++;
+
+	if (req->f->conn.proto_minor < 16) {
+		if (in_count) {
+			iov[count].iov_base = (void *)in_iov;
+			iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+			count++;
+		}
+
+		if (out_count) {
+			iov[count].iov_base = (void *)out_iov;
+			iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+			count++;
+		}
+	} else {
+		/* Can't handle non-compat 64bit ioctls on 32bit */
+		if (sizeof(void *) == 4 && req->ioctl_64bit) {
+			res = fuse_reply_err(req, EINVAL);
+			goto out;
+		}
+
+		if (in_count) {
+			in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+			if (!in_fiov)
+				goto enomem;
+
+			iov[count].iov_base = (void *)in_fiov;
+			iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+			count++;
+		}
+		if (out_count) {
+			out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+			if (!out_fiov)
+				goto enomem;
+
+			iov[count].iov_base = (void *)out_fiov;
+			iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+			count++;
+		}
+	}
+
+	res = send_reply_iov(req, 0, iov, count);
+out:
+	free(in_fiov);
+	free(out_fiov);
+
+	return res;
+
+enomem:
+	res = fuse_reply_err(req, ENOMEM);
+	goto out;
+}
+
+int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+{
+	struct fuse_ioctl_out arg;
+	struct iovec iov[3];
+	size_t count = 1;
+
+	memset(&arg, 0, sizeof(arg));
+	arg.result = result;
+	iov[count].iov_base = &arg;
+	iov[count].iov_len = sizeof(arg);
+	count++;
+
+	if (size) {
+		iov[count].iov_base = (char *) buf;
+		iov[count].iov_len = size;
+		count++;
+	}
+
+	return send_reply_iov(req, 0, iov, count);
+}
+
+int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+			 int count)
+{
+	struct iovec *padded_iov;
+	struct fuse_ioctl_out arg;
+	int res;
+
+	padded_iov = malloc((count + 2) * sizeof(struct iovec));
+	if (padded_iov == NULL)
+		return fuse_reply_err(req, ENOMEM);
+
+	memset(&arg, 0, sizeof(arg));
+	arg.result = result;
+	padded_iov[1].iov_base = &arg;
+	padded_iov[1].iov_len = sizeof(arg);
+
+	memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
+	res = send_reply_iov(req, 0, padded_iov, count + 2);
+	free(padded_iov);
+
+	return res;
+}
+
+int fuse_reply_poll(fuse_req_t req, unsigned revents)
+{
+	struct fuse_poll_out arg;
+
+	memset(&arg, 0, sizeof(arg));
+	arg.revents = revents;
+
+	return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	char *name = (char *) inarg;
+
+	if (req->f->op.lookup)
+		req->f->op.lookup(req, nodeid, name);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg;
+
+	if (req->f->op.forget)
+		req->f->op.forget(req, nodeid, arg->nlookup);
+	else
+		fuse_reply_none(req);
+}
+
+static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid,
+			    const void *inarg)
+{
+	struct fuse_batch_forget_in *arg = (void *) inarg;
+	struct fuse_forget_one *param = (void *) PARAM(arg);
+	unsigned int i;
+
+	(void) nodeid;
+
+	if (req->f->op.forget_multi) {
+		req->f->op.forget_multi(req, arg->count,
+				     (struct fuse_forget_data *) param);
+	} else if (req->f->op.forget) {
+		for (i = 0; i < arg->count; i++) {
+			struct fuse_forget_one *forget = &param[i];
+			struct fuse_req *dummy_req;
+
+			dummy_req = fuse_ll_alloc_req(req->f);
+			if (dummy_req == NULL)
+				break;
+
+			dummy_req->unique = req->unique;
+			dummy_req->ctx = req->ctx;
+			dummy_req->ch = NULL;
+
+			req->f->op.forget(dummy_req, forget->nodeid,
+					  forget->nlookup);
+		}
+		fuse_reply_none(req);
+	} else {
+		fuse_reply_none(req);
+	}
+}
+
+static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_file_info *fip = NULL;
+	struct fuse_file_info fi;
+
+	if (req->f->conn.proto_minor >= 9) {
+		struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg;
+
+		if (arg->getattr_flags & FUSE_GETATTR_FH) {
+			memset(&fi, 0, sizeof(fi));
+			fi.fh = arg->fh;
+			fi.fh_old = fi.fh;
+			fip = &fi;
+		}
+	}
+
+	if (req->f->op.getattr)
+		req->f->op.getattr(req, nodeid, fip);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg;
+
+	if (req->f->op.setattr) {
+		struct fuse_file_info *fi = NULL;
+		struct fuse_file_info fi_store;
+		struct stat stbuf;
+		memset(&stbuf, 0, sizeof(stbuf));
+		convert_attr(arg, &stbuf);
+		if (arg->valid & FATTR_FH) {
+			arg->valid &= ~FATTR_FH;
+			memset(&fi_store, 0, sizeof(fi_store));
+			fi = &fi_store;
+			fi->fh = arg->fh;
+			fi->fh_old = fi->fh;
+		}
+		arg->valid &=
+			FUSE_SET_ATTR_MODE	|
+			FUSE_SET_ATTR_UID	|
+			FUSE_SET_ATTR_GID	|
+			FUSE_SET_ATTR_SIZE	|
+			FUSE_SET_ATTR_ATIME	|
+			FUSE_SET_ATTR_MTIME	|
+			FUSE_SET_ATTR_ATIME_NOW	|
+			FUSE_SET_ATTR_MTIME_NOW;
+
+		req->f->op.setattr(req, nodeid, &stbuf, arg->valid, fi);
+	} else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_access_in *arg = (struct fuse_access_in *) inarg;
+
+	if (req->f->op.access)
+		req->f->op.access(req, nodeid, arg->mask);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	(void) inarg;
+
+	if (req->f->op.readlink)
+		req->f->op.readlink(req, nodeid);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg;
+	char *name = PARAM(arg);
+
+	if (req->f->conn.proto_minor >= 12)
+		req->ctx.umask = arg->umask;
+	else
+		name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
+	if (req->f->op.mknod)
+		req->f->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg;
+
+	if (req->f->conn.proto_minor >= 12)
+		req->ctx.umask = arg->umask;
+
+	if (req->f->op.mkdir)
+		req->f->op.mkdir(req, nodeid, PARAM(arg), arg->mode);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	char *name = (char *) inarg;
+
+	if (req->f->op.unlink)
+		req->f->op.unlink(req, nodeid, name);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	char *name = (char *) inarg;
+
+	if (req->f->op.rmdir)
+		req->f->op.rmdir(req, nodeid, name);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	char *name = (char *) inarg;
+	char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1;
+
+	if (req->f->op.symlink)
+		req->f->op.symlink(req, linkname, nodeid, name);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg;
+	char *oldname = PARAM(arg);
+	char *newname = oldname + strlen(oldname) + 1;
+
+	if (req->f->op.rename)
+		req->f->op.rename(req, nodeid, oldname, arg->newdir, newname);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_link_in *arg = (struct fuse_link_in *) inarg;
+
+	if (req->f->op.link)
+		req->f->op.link(req, arg->oldnodeid, nodeid, PARAM(arg));
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
+	if (req->f->op.create) {
+		struct fuse_file_info fi;
+		char *name = PARAM(arg);
+
+		memset(&fi, 0, sizeof(fi));
+		fi.flags = arg->flags;
+
+		if (req->f->conn.proto_minor >= 12)
+			req->ctx.umask = arg->umask;
+		else
+			name = (char *) inarg + sizeof(struct fuse_open_in);
+
+		req->f->op.create(req, nodeid, name, arg->mode, &fi);
+	} else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.flags = arg->flags;
+
+	if (req->f->op.open)
+		req->f->op.open(req, nodeid, &fi);
+	else
+		fuse_reply_open(req, &fi);
+}
+
+static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
+	if (req->f->op.read) {
+		struct fuse_file_info fi;
+
+		memset(&fi, 0, sizeof(fi));
+		fi.fh = arg->fh;
+		fi.fh_old = fi.fh;
+		if (req->f->conn.proto_minor >= 9) {
+			fi.lock_owner = arg->lock_owner;
+			fi.flags = arg->flags;
+		}
+		req->f->op.read(req, nodeid, arg->size, arg->offset, &fi);
+	} else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+	struct fuse_file_info fi;
+	char *param;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+	fi.fh_old = fi.fh;
+	fi.writepage = arg->write_flags & 1;
+
+	if (req->f->conn.proto_minor < 9) {
+		param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+	} else {
+		fi.lock_owner = arg->lock_owner;
+		fi.flags = arg->flags;
+		param = PARAM(arg);
+	}
+
+	if (req->f->op.write)
+		req->f->op.write(req, nodeid, param, arg->size,
+				 arg->offset, &fi);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg,
+			 const struct fuse_buf *ibuf)
+{
+	struct fuse_ll *f = req->f;
+	struct fuse_bufvec bufv = {
+		.buf[0] = *ibuf,
+		.count = 1,
+	};
+	struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+	fi.fh_old = fi.fh;
+	fi.writepage = arg->write_flags & 1;
+
+	if (req->f->conn.proto_minor < 9) {
+		bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+		bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+			FUSE_COMPAT_WRITE_IN_SIZE;
+		assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+	} else {
+		fi.lock_owner = arg->lock_owner;
+		fi.flags = arg->flags;
+		if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+			bufv.buf[0].mem = PARAM(arg);
+
+		bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+			sizeof(struct fuse_write_in);
+	}
+	if (bufv.buf[0].size < arg->size) {
+		fprintf(stderr, "fuse: do_write_buf: buffer size too small\n");
+		fuse_reply_err(req, EIO);
+		goto out;
+	}
+	bufv.buf[0].size = arg->size;
+
+	req->f->op.write_buf(req, nodeid, &bufv, arg->offset, &fi);
+
+out:
+	/* Need to reset the pipe if ->write_buf() didn't consume all data */
+	if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+		fuse_ll_clear_pipe(f);
+}
+
+static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+	fi.fh_old = fi.fh;
+	fi.flush = 1;
+	if (req->f->conn.proto_minor >= 7)
+		fi.lock_owner = arg->lock_owner;
+
+	if (req->f->op.flush)
+		req->f->op.flush(req, nodeid, &fi);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.flags = arg->flags;
+	fi.fh = arg->fh;
+	fi.fh_old = fi.fh;
+	if (req->f->conn.proto_minor >= 8) {
+		fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+		fi.lock_owner = arg->lock_owner;
+	}
+	if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+		fi.flock_release = 1;
+		fi.lock_owner = arg->lock_owner;
+	}
+
+	if (req->f->op.release)
+		req->f->op.release(req, nodeid, &fi);
+	else
+		fuse_reply_err(req, 0);
+}
+
+static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+	fi.fh_old = fi.fh;
+
+	if (req->f->op.fsync)
+		req->f->op.fsync(req, nodeid, arg->fsync_flags & 1, &fi);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.flags = arg->flags;
+
+	if (req->f->op.opendir)
+		req->f->op.opendir(req, nodeid, &fi);
+	else
+		fuse_reply_open(req, &fi);
+}
+
+static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+	fi.fh_old = fi.fh;
+
+	if (req->f->op.readdir)
+		req->f->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.flags = arg->flags;
+	fi.fh = arg->fh;
+	fi.fh_old = fi.fh;
+
+	if (req->f->op.releasedir)
+		req->f->op.releasedir(req, nodeid, &fi);
+	else
+		fuse_reply_err(req, 0);
+}
+
+static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+	fi.fh_old = fi.fh;
+
+	if (req->f->op.fsyncdir)
+		req->f->op.fsyncdir(req, nodeid, arg->fsync_flags & 1, &fi);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	(void) nodeid;
+	(void) inarg;
+
+	if (req->f->op.statfs)
+		req->f->op.statfs(req, nodeid);
+	else {
+		struct statvfs buf = {
+			.f_namemax = 255,
+			.f_bsize = 512,
+		};
+		fuse_reply_statfs(req, &buf);
+	}
+}
+
+static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg;
+	char *name = PARAM(arg);
+	char *value = name + strlen(name) + 1;
+
+	if (req->f->op.setxattr)
+		req->f->op.setxattr(req, nodeid, name, value, arg->size,
+				    arg->flags);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
+	if (req->f->op.getxattr)
+		req->f->op.getxattr(req, nodeid, PARAM(arg), arg->size);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
+	if (req->f->op.listxattr)
+		req->f->op.listxattr(req, nodeid, arg->size);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	char *name = (char *) inarg;
+
+	if (req->f->op.removexattr)
+		req->f->op.removexattr(req, nodeid, name);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void convert_fuse_file_lock(struct fuse_file_lock *fl,
+				   struct flock *flock)
+{
+	memset(flock, 0, sizeof(struct flock));
+	flock->l_type = fl->type;
+	flock->l_whence = SEEK_SET;
+	flock->l_start = fl->start;
+	if (fl->end == OFFSET_MAX)
+		flock->l_len = 0;
+	else
+		flock->l_len = fl->end - fl->start + 1;
+	flock->l_pid = fl->pid;
+}
+
+static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+	struct fuse_file_info fi;
+	struct flock flock;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+	fi.lock_owner = arg->owner;
+
+	convert_fuse_file_lock(&arg->lk, &flock);
+	if (req->f->op.getlk)
+		req->f->op.getlk(req, nodeid, &fi, &flock);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid,
+			    const void *inarg, int sleep)
+{
+	struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+	struct fuse_file_info fi;
+	struct flock flock;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+	fi.lock_owner = arg->owner;
+
+	if (arg->lk_flags & FUSE_LK_FLOCK) {
+		int op = 0;
+
+		switch (arg->lk.type) {
+		case F_RDLCK:
+			op = LOCK_SH;
+			break;
+		case F_WRLCK:
+			op = LOCK_EX;
+			break;
+		case F_UNLCK:
+			op = LOCK_UN;
+			break;
+		}
+		if (!sleep)
+			op |= LOCK_NB;
+
+		if (req->f->op.flock)
+			req->f->op.flock(req, nodeid, &fi, op);
+		else
+			fuse_reply_err(req, ENOSYS);
+	} else {
+		convert_fuse_file_lock(&arg->lk, &flock);
+		if (req->f->op.setlk)
+			req->f->op.setlk(req, nodeid, &fi, &flock, sleep);
+		else
+			fuse_reply_err(req, ENOSYS);
+	}
+}
+
+static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	do_setlk_common(req, nodeid, inarg, 0);
+}
+
+static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	do_setlk_common(req, nodeid, inarg, 1);
+}
+
+static int find_interrupted(struct fuse_ll *f, struct fuse_req *req)
+{
+	struct fuse_req *curr;
+
+	for (curr = f->list.next; curr != &f->list; curr = curr->next) {
+		if (curr->unique == req->u.i.unique) {
+			fuse_interrupt_func_t func;
+			void *data;
+
+			curr->ctr++;
+			pthread_mutex_unlock(&f->lock);
+
+			/* Ugh, ugly locking */
+			pthread_mutex_lock(&curr->lock);
+			pthread_mutex_lock(&f->lock);
+			curr->interrupted = 1;
+			func = curr->u.ni.func;
+			data = curr->u.ni.data;
+			pthread_mutex_unlock(&f->lock);
+			if (func)
+				func(curr, data);
+			pthread_mutex_unlock(&curr->lock);
+
+			pthread_mutex_lock(&f->lock);
+			curr->ctr--;
+			if (!curr->ctr)
+				destroy_req(curr);
+
+			return 1;
+		}
+	}
+	for (curr = f->interrupts.next; curr != &f->interrupts;
+	     curr = curr->next) {
+		if (curr->u.i.unique == req->u.i.unique)
+			return 1;
+	}
+	return 0;
+}
+
+static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg;
+	struct fuse_ll *f = req->f;
+
+	(void) nodeid;
+	if (f->debug)
+		fprintf(stderr, "INTERRUPT: %llu\n",
+			(unsigned long long) arg->unique);
+
+	req->u.i.unique = arg->unique;
+
+	pthread_mutex_lock(&f->lock);
+	if (find_interrupted(f, req))
+		destroy_req(req);
+	else
+		list_add_req(req, &f->interrupts);
+	pthread_mutex_unlock(&f->lock);
+}
+
+static struct fuse_req *check_interrupt(struct fuse_ll *f, struct fuse_req *req)
+{
+	struct fuse_req *curr;
+
+	for (curr = f->interrupts.next; curr != &f->interrupts;
+	     curr = curr->next) {
+		if (curr->u.i.unique == req->unique) {
+			req->interrupted = 1;
+			list_del_req(curr);
+			free(curr);
+			return NULL;
+		}
+	}
+	curr = f->interrupts.next;
+	if (curr != &f->interrupts) {
+		list_del_req(curr);
+		list_init_req(curr);
+		return curr;
+	} else
+		return NULL;
+}
+
+static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg;
+
+	if (req->f->op.bmap)
+		req->f->op.bmap(req, nodeid, arg->blocksize, arg->block);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg;
+	unsigned int flags = arg->flags;
+	void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+	struct fuse_file_info fi;
+
+	if (flags & FUSE_IOCTL_DIR &&
+	    !(req->f->conn.want & FUSE_CAP_IOCTL_DIR)) {
+		fuse_reply_err(req, ENOTTY);
+		return;
+	}
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+	fi.fh_old = fi.fh;
+
+	if (sizeof(void *) == 4 && req->f->conn.proto_minor >= 16 &&
+	    !(flags & FUSE_IOCTL_32BIT)) {
+		req->ioctl_64bit = 1;
+	}
+
+	if (req->f->op.ioctl)
+		req->f->op.ioctl(req, nodeid, arg->cmd,
+				 (void *)(uintptr_t)arg->arg, &fi, flags,
+				 in_buf, arg->in_size, arg->out_size);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+{
+	free(ph);
+}
+
+static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+	fi.fh_old = fi.fh;
+
+	if (req->f->op.poll) {
+		struct fuse_pollhandle *ph = NULL;
+
+		if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+			ph = malloc(sizeof(struct fuse_pollhandle));
+			if (ph == NULL) {
+				fuse_reply_err(req, ENOMEM);
+				return;
+			}
+			ph->kh = arg->kh;
+			ph->ch = req->ch;
+			ph->f = req->f;
+		}
+
+		req->f->op.poll(req, nodeid, &fi, ph);
+	} else {
+		fuse_reply_err(req, ENOSYS);
+	}
+}
+
+static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg;
+	struct fuse_file_info fi;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+
+	if (req->f->op.fallocate)
+		req->f->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi);
+	else
+		fuse_reply_err(req, ENOSYS);
+}
+
+static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+	struct fuse_init_out outarg;
+	struct fuse_ll *f = req->f;
+	size_t bufsize = fuse_chan_bufsize(req->ch);
+
+	(void) nodeid;
+	if (f->debug) {
+		fprintf(stderr, "INIT: %u.%u\n", arg->major, arg->minor);
+		if (arg->major == 7 && arg->minor >= 6) {
+			fprintf(stderr, "flags=0x%08x\n", arg->flags);
+			fprintf(stderr, "max_readahead=0x%08x\n",
+				arg->max_readahead);
+		}
+	}
+	f->conn.proto_major = arg->major;
+	f->conn.proto_minor = arg->minor;
+	f->conn.capable = 0;
+	f->conn.want = 0;
+
+	memset(&outarg, 0, sizeof(outarg));
+	outarg.major = FUSE_KERNEL_VERSION;
+	outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
+	if (arg->major < 7) {
+		fprintf(stderr, "fuse: unsupported protocol version: %u.%u\n",
+			arg->major, arg->minor);
+		fuse_reply_err(req, EPROTO);
+		return;
+	}
+
+	if (arg->major > 7) {
+		/* Wait for a second INIT request with a 7.X version */
+		send_reply_ok(req, &outarg, sizeof(outarg));
+		return;
+	}
+
+	if (arg->minor >= 6) {
+		if (f->conn.async_read)
+			f->conn.async_read = arg->flags & FUSE_ASYNC_READ;
+		if (arg->max_readahead < f->conn.max_readahead)
+			f->conn.max_readahead = arg->max_readahead;
+		if (arg->flags & FUSE_ASYNC_READ)
+			f->conn.capable |= FUSE_CAP_ASYNC_READ;
+		if (arg->flags & FUSE_POSIX_LOCKS)
+			f->conn.capable |= FUSE_CAP_POSIX_LOCKS;
+		if (arg->flags & FUSE_ATOMIC_O_TRUNC)
+			f->conn.capable |= FUSE_CAP_ATOMIC_O_TRUNC;
+		if (arg->flags & FUSE_EXPORT_SUPPORT)
+			f->conn.capable |= FUSE_CAP_EXPORT_SUPPORT;
+		if (arg->flags & FUSE_BIG_WRITES)
+			f->conn.capable |= FUSE_CAP_BIG_WRITES;
+		if (arg->flags & FUSE_DONT_MASK)
+			f->conn.capable |= FUSE_CAP_DONT_MASK;
+		if (arg->flags & FUSE_FLOCK_LOCKS)
+			f->conn.capable |= FUSE_CAP_FLOCK_LOCKS;
+	} else {
+		f->conn.async_read = 0;
+		f->conn.max_readahead = 0;
+	}
+
+	if (req->f->conn.proto_minor >= 14) {
+#ifdef HAVE_SPLICE
+#ifdef HAVE_VMSPLICE
+		f->conn.capable |= FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE;
+		if (f->splice_write)
+			f->conn.want |= FUSE_CAP_SPLICE_WRITE;
+		if (f->splice_move)
+			f->conn.want |= FUSE_CAP_SPLICE_MOVE;
+#endif
+		f->conn.capable |= FUSE_CAP_SPLICE_READ;
+		if (f->splice_read)
+			f->conn.want |= FUSE_CAP_SPLICE_READ;
+#endif
+	}
+	if (req->f->conn.proto_minor >= 18)
+		f->conn.capable |= FUSE_CAP_IOCTL_DIR;
+
+	if (f->atomic_o_trunc)
+		f->conn.want |= FUSE_CAP_ATOMIC_O_TRUNC;
+	if (f->op.getlk && f->op.setlk && !f->no_remote_posix_lock)
+		f->conn.want |= FUSE_CAP_POSIX_LOCKS;
+	if (f->op.flock && !f->no_remote_flock)
+		f->conn.want |= FUSE_CAP_FLOCK_LOCKS;
+	if (f->big_writes)
+		f->conn.want |= FUSE_CAP_BIG_WRITES;
+
+	if (bufsize < FUSE_MIN_READ_BUFFER) {
+		fprintf(stderr, "fuse: warning: buffer size too small: %zu\n",
+			bufsize);
+		bufsize = FUSE_MIN_READ_BUFFER;
+	}
+
+	bufsize -= 4096;
+	if (bufsize < f->conn.max_write)
+		f->conn.max_write = bufsize;
+
+	f->got_init = 1;
+	if (f->op.init)
+		f->op.init(f->userdata, &f->conn);
+
+	if (f->no_splice_read)
+		f->conn.want &= ~FUSE_CAP_SPLICE_READ;
+	if (f->no_splice_write)
+		f->conn.want &= ~FUSE_CAP_SPLICE_WRITE;
+	if (f->no_splice_move)
+		f->conn.want &= ~FUSE_CAP_SPLICE_MOVE;
+
+	if (f->conn.async_read || (f->conn.want & FUSE_CAP_ASYNC_READ))
+		outarg.flags |= FUSE_ASYNC_READ;
+	if (f->conn.want & FUSE_CAP_POSIX_LOCKS)
+		outarg.flags |= FUSE_POSIX_LOCKS;
+	if (f->conn.want & FUSE_CAP_ATOMIC_O_TRUNC)
+		outarg.flags |= FUSE_ATOMIC_O_TRUNC;
+	if (f->conn.want & FUSE_CAP_EXPORT_SUPPORT)
+		outarg.flags |= FUSE_EXPORT_SUPPORT;
+	if (f->conn.want & FUSE_CAP_BIG_WRITES)
+		outarg.flags |= FUSE_BIG_WRITES;
+	if (f->conn.want & FUSE_CAP_DONT_MASK)
+		outarg.flags |= FUSE_DONT_MASK;
+	if (f->conn.want & FUSE_CAP_FLOCK_LOCKS)
+		outarg.flags |= FUSE_FLOCK_LOCKS;
+	outarg.max_readahead = f->conn.max_readahead;
+	outarg.max_write = f->conn.max_write;
+	if (f->conn.proto_minor >= 13) {
+		if (f->conn.max_background >= (1 << 16))
+			f->conn.max_background = (1 << 16) - 1;
+		if (f->conn.congestion_threshold > f->conn.max_background)
+			f->conn.congestion_threshold = f->conn.max_background;
+		if (!f->conn.congestion_threshold) {
+			f->conn.congestion_threshold =
+				f->conn.max_background * 3 / 4;
+		}
+
+		outarg.max_background = f->conn.max_background;
+		outarg.congestion_threshold = f->conn.congestion_threshold;
+	}
+
+	if (f->debug) {
+		fprintf(stderr, "   INIT: %u.%u\n", outarg.major, outarg.minor);
+		fprintf(stderr, "   flags=0x%08x\n", outarg.flags);
+		fprintf(stderr, "   max_readahead=0x%08x\n",
+			outarg.max_readahead);
+		fprintf(stderr, "   max_write=0x%08x\n", outarg.max_write);
+		fprintf(stderr, "   max_background=%i\n",
+			outarg.max_background);
+		fprintf(stderr, "   congestion_threshold=%i\n",
+		        outarg.congestion_threshold);
+	}
+
+	send_reply_ok(req, &outarg, arg->minor < 5 ? 8 : sizeof(outarg));
+}
+
+static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+	struct fuse_ll *f = req->f;
+
+	(void) nodeid;
+	(void) inarg;
+
+	f->got_destroy = 1;
+	if (f->op.destroy)
+		f->op.destroy(f->userdata);
+
+	send_reply_ok(req, NULL, 0);
+}
+
+static void list_del_nreq(struct fuse_notify_req *nreq)
+{
+	struct fuse_notify_req *prev = nreq->prev;
+	struct fuse_notify_req *next = nreq->next;
+	prev->next = next;
+	next->prev = prev;
+}
+
+static void list_add_nreq(struct fuse_notify_req *nreq,
+			  struct fuse_notify_req *next)
+{
+	struct fuse_notify_req *prev = next->prev;
+	nreq->next = next;
+	nreq->prev = prev;
+	prev->next = nreq;
+	next->prev = nreq;
+}
+
+static void list_init_nreq(struct fuse_notify_req *nreq)
+{
+	nreq->next = nreq;
+	nreq->prev = nreq;
+}
+
+static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+			    const void *inarg, const struct fuse_buf *buf)
+{
+	struct fuse_ll *f = req->f;
+	struct fuse_notify_req *nreq;
+	struct fuse_notify_req *head;
+
+	pthread_mutex_lock(&f->lock);
+	head = &f->notify_list;
+	for (nreq = head->next; nreq != head; nreq = nreq->next) {
+		if (nreq->unique == req->unique) {
+			list_del_nreq(nreq);
+			break;
+		}
+	}
+	pthread_mutex_unlock(&f->lock);
+
+	if (nreq != head)
+		nreq->reply(nreq, req, nodeid, inarg, buf);
+}
+
+static int send_notify_iov(struct fuse_ll *f, struct fuse_chan *ch,
+			   int notify_code, struct iovec *iov, int count)
+{
+	struct fuse_out_header out;
+
+	if (!f->got_init)
+		return -ENOTCONN;
+
+	out.unique = 0;
+	out.error = notify_code;
+	iov[0].iov_base = &out;
+	iov[0].iov_len = sizeof(struct fuse_out_header);
+
+	return fuse_send_msg(f, ch, iov, count);
+}
+
+int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+{
+	if (ph != NULL) {
+		struct fuse_notify_poll_wakeup_out outarg;
+		struct iovec iov[2];
+
+		outarg.kh = ph->kh;
+
+		iov[1].iov_base = &outarg;
+		iov[1].iov_len = sizeof(outarg);
+
+		return send_notify_iov(ph->f, ph->ch, FUSE_NOTIFY_POLL, iov, 2);
+	} else {
+		return 0;
+	}
+}
+
+int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino,
+                                     loff_t off, loff_t len)
+{
+	struct fuse_notify_inval_inode_out outarg;
+	struct fuse_ll *f;
+	struct iovec iov[2];
+
+	if (!ch)
+		return -EINVAL;
+
+	f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch));
+	if (!f)
+		return -ENODEV;
+
+	outarg.ino = ino;
+	outarg.off = off;
+	outarg.len = len;
+
+	iov[1].iov_base = &outarg;
+	iov[1].iov_len = sizeof(outarg);
+
+	return send_notify_iov(f, ch, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+}
+
+int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, fuse_ino_t parent,
+                                     const char *name, size_t namelen)
+{
+	struct fuse_notify_inval_entry_out outarg;
+	struct fuse_ll *f;
+	struct iovec iov[3];
+
+	if (!ch)
+		return -EINVAL;
+
+	f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch));
+	if (!f)
+		return -ENODEV;
+
+	outarg.parent = parent;
+	outarg.namelen = namelen;
+	outarg.padding = 0;
+
+	iov[1].iov_base = &outarg;
+	iov[1].iov_len = sizeof(outarg);
+	iov[2].iov_base = (void *)name;
+	iov[2].iov_len = namelen + 1;
+
+	return send_notify_iov(f, ch, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+}
+
+int fuse_lowlevel_notify_delete(struct fuse_chan *ch,
+				fuse_ino_t parent, fuse_ino_t child,
+				const char *name, size_t namelen)
+{
+	struct fuse_notify_delete_out outarg;
+	struct fuse_ll *f;
+	struct iovec iov[3];
+
+	if (!ch)
+		return -EINVAL;
+
+	f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch));
+	if (!f)
+		return -ENODEV;
+
+	if (f->conn.proto_minor < 18)
+		return -ENOSYS;
+
+	outarg.parent = parent;
+	outarg.child = child;
+	outarg.namelen = namelen;
+	outarg.padding = 0;
+
+	iov[1].iov_base = &outarg;
+	iov[1].iov_len = sizeof(outarg);
+	iov[2].iov_base = (void *)name;
+	iov[2].iov_len = namelen + 1;
+
+	return send_notify_iov(f, ch, FUSE_NOTIFY_DELETE, iov, 3);
+}
+
+int fuse_lowlevel_notify_store(struct fuse_chan *ch, fuse_ino_t ino,
+			       loff_t offset, struct fuse_bufvec *bufv,
+			       enum fuse_buf_copy_flags flags)
+{
+	struct fuse_out_header out;
+	struct fuse_notify_store_out outarg;
+	struct fuse_ll *f;
+	struct iovec iov[3];
+	size_t size = fuse_buf_size(bufv);
+	int res;
+
+	if (!ch)
+		return -EINVAL;
+
+	f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch));
+	if (!f)
+		return -ENODEV;
+
+	if (f->conn.proto_minor < 15)
+		return -ENOSYS;
+
+	out.unique = 0;
+	out.error = FUSE_NOTIFY_STORE;
+
+	outarg.nodeid = ino;
+	outarg.offset = offset;
+	outarg.size = size;
+
+	iov[0].iov_base = &out;
+	iov[0].iov_len = sizeof(out);
+	iov[1].iov_base = &outarg;
+	iov[1].iov_len = sizeof(outarg);
+
+	res = fuse_send_data_iov(f, ch, iov, 2, bufv, flags);
+	if (res > 0)
+		res = -res;
+
+	return res;
+}
+
+struct fuse_retrieve_req {
+	struct fuse_notify_req nreq;
+	void *cookie;
+};
+
+static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+				   fuse_req_t req, fuse_ino_t ino,
+				   const void *inarg,
+				   const struct fuse_buf *ibuf)
+{
+	struct fuse_ll *f = req->f;
+	struct fuse_retrieve_req *rreq =
+		container_of(nreq, struct fuse_retrieve_req, nreq);
+	const struct fuse_notify_retrieve_in *arg = inarg;
+	struct fuse_bufvec bufv = {
+		.buf[0] = *ibuf,
+		.count = 1,
+	};
+
+	if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+		bufv.buf[0].mem = PARAM(arg);
+
+	bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+		sizeof(struct fuse_notify_retrieve_in);
+
+	if (bufv.buf[0].size < arg->size) {
+		fprintf(stderr, "fuse: retrieve reply: buffer size too small\n");
+		fuse_reply_none(req);
+		goto out;
+	}
+	bufv.buf[0].size = arg->size;
+
+	if (req->f->op.retrieve_reply) {
+		req->f->op.retrieve_reply(req, rreq->cookie, ino,
+					  arg->offset, &bufv);
+	} else {
+		fuse_reply_none(req);
+	}
+out:
+	free(rreq);
+	if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+		fuse_ll_clear_pipe(f);
+}
+
+int fuse_lowlevel_notify_retrieve(struct fuse_chan *ch, fuse_ino_t ino,
+				  size_t size, loff_t offset, void *cookie)
+{
+	struct fuse_notify_retrieve_out outarg;
+	struct fuse_ll *f;
+	struct iovec iov[2];
+	struct fuse_retrieve_req *rreq;
+	int err;
+
+	if (!ch)
+		return -EINVAL;
+
+	f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch));
+	if (!f)
+		return -ENODEV;
+
+	if (f->conn.proto_minor < 15)
+		return -ENOSYS;
+
+	rreq = malloc(sizeof(*rreq));
+	if (rreq == NULL)
+		return -ENOMEM;
+
+	pthread_mutex_lock(&f->lock);
+	rreq->cookie = cookie;
+	rreq->nreq.unique = f->notify_ctr++;
+	rreq->nreq.reply = fuse_ll_retrieve_reply;
+	list_add_nreq(&rreq->nreq, &f->notify_list);
+	pthread_mutex_unlock(&f->lock);
+
+	outarg.notify_unique = rreq->nreq.unique;
+	outarg.nodeid = ino;
+	outarg.offset = offset;
+	outarg.size = size;
+
+	iov[1].iov_base = &outarg;
+	iov[1].iov_len = sizeof(outarg);
+
+	err = send_notify_iov(f, ch, FUSE_NOTIFY_RETRIEVE, iov, 2);
+	if (err) {
+		pthread_mutex_lock(&f->lock);
+		list_del_nreq(&rreq->nreq);
+		pthread_mutex_unlock(&f->lock);
+		free(rreq);
+	}
+
+	return err;
+}
+
+void *fuse_req_userdata(fuse_req_t req)
+{
+	return req->f->userdata;
+}
+
+const struct fuse_ctx *fuse_req_ctx(fuse_req_t req)
+{
+	return &req->ctx;
+}
+
+/*
+ * The size of fuse_ctx got extended, so need to be careful about
+ * incompatibility (i.e. a new binary cannot work with an old
+ * library).
+ */
+const struct fuse_ctx *fuse_req_ctx_compat24(fuse_req_t req);
+const struct fuse_ctx *fuse_req_ctx_compat24(fuse_req_t req)
+{
+	return fuse_req_ctx(req);
+}
+#ifndef __NetBSD__
+FUSE_SYMVER(".symver fuse_req_ctx_compat24,fuse_req_ctx@FUSE_2.4");
+#endif
+
+
+void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func,
+			     void *data)
+{
+	pthread_mutex_lock(&req->lock);
+	pthread_mutex_lock(&req->f->lock);
+	req->u.ni.func = func;
+	req->u.ni.data = data;
+	pthread_mutex_unlock(&req->f->lock);
+	if (req->interrupted && func)
+		func(req, data);
+	pthread_mutex_unlock(&req->lock);
+}
+
+int fuse_req_interrupted(fuse_req_t req)
+{
+	int interrupted;
+
+	pthread_mutex_lock(&req->f->lock);
+	interrupted = req->interrupted;
+	pthread_mutex_unlock(&req->f->lock);
+
+	return interrupted;
+}
+
+static struct {
+	void (*func)(fuse_req_t, fuse_ino_t, const void *);
+	const char *name;
+} fuse_ll_ops[] = {
+	[FUSE_LOOKUP]	   = { do_lookup,      "LOOKUP"	     },
+	[FUSE_FORGET]	   = { do_forget,      "FORGET"	     },
+	[FUSE_GETATTR]	   = { do_getattr,     "GETATTR"     },
+	[FUSE_SETATTR]	   = { do_setattr,     "SETATTR"     },
+	[FUSE_READLINK]	   = { do_readlink,    "READLINK"    },
+	[FUSE_SYMLINK]	   = { do_symlink,     "SYMLINK"     },
+	[FUSE_MKNOD]	   = { do_mknod,       "MKNOD"	     },
+	[FUSE_MKDIR]	   = { do_mkdir,       "MKDIR"	     },
+	[FUSE_UNLINK]	   = { do_unlink,      "UNLINK"	     },
+	[FUSE_RMDIR]	   = { do_rmdir,       "RMDIR"	     },
+	[FUSE_RENAME]	   = { do_rename,      "RENAME"	     },
+	[FUSE_LINK]	   = { do_link,	       "LINK"	     },
+	[FUSE_OPEN]	   = { do_open,	       "OPEN"	     },
+	[FUSE_READ]	   = { do_read,	       "READ"	     },
+	[FUSE_WRITE]	   = { do_write,       "WRITE"	     },
+	[FUSE_STATFS]	   = { do_statfs,      "STATFS"	     },
+	[FUSE_RELEASE]	   = { do_release,     "RELEASE"     },
+	[FUSE_FSYNC]	   = { do_fsync,       "FSYNC"	     },
+	[FUSE_SETXATTR]	   = { do_setxattr,    "SETXATTR"    },
+	[FUSE_GETXATTR]	   = { do_getxattr,    "GETXATTR"    },
+	[FUSE_LISTXATTR]   = { do_listxattr,   "LISTXATTR"   },
+	[FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+	[FUSE_FLUSH]	   = { do_flush,       "FLUSH"	     },
+	[FUSE_INIT]	   = { do_init,	       "INIT"	     },
+	[FUSE_OPENDIR]	   = { do_opendir,     "OPENDIR"     },
+	[FUSE_READDIR]	   = { do_readdir,     "READDIR"     },
+	[FUSE_RELEASEDIR]  = { do_releasedir,  "RELEASEDIR"  },
+	[FUSE_FSYNCDIR]	   = { do_fsyncdir,    "FSYNCDIR"    },
+	[FUSE_GETLK]	   = { do_getlk,       "GETLK"	     },
+	[FUSE_SETLK]	   = { do_setlk,       "SETLK"	     },
+	[FUSE_SETLKW]	   = { do_setlkw,      "SETLKW"	     },
+	[FUSE_ACCESS]	   = { do_access,      "ACCESS"	     },
+	[FUSE_CREATE]	   = { do_create,      "CREATE"	     },
+	[FUSE_INTERRUPT]   = { do_interrupt,   "INTERRUPT"   },
+	[FUSE_BMAP]	   = { do_bmap,	       "BMAP"	     },
+	[FUSE_IOCTL]	   = { do_ioctl,       "IOCTL"	     },
+	[FUSE_POLL]	   = { do_poll,        "POLL"	     },
+	[FUSE_FALLOCATE]   = { do_fallocate,   "FALLOCATE"   },
+	[FUSE_DESTROY]	   = { do_destroy,     "DESTROY"     },
+	[FUSE_NOTIFY_REPLY] = { (void *) 1,    "NOTIFY_REPLY" },
+	[FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+	[CUSE_INIT]	   = { cuse_lowlevel_init, "CUSE_INIT"   },
+};
+
+#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
+static const char *opname(enum fuse_opcode opcode)
+{
+	if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+		return "???";
+	else
+		return fuse_ll_ops[opcode].name;
+}
+
+static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+				  struct fuse_bufvec *src)
+{
+	int res = fuse_buf_copy(dst, src, 0);
+	if (res < 0) {
+		fprintf(stderr, "fuse: copy from pipe: %s\n", strerror(-res));
+		return res;
+	}
+	if (res < fuse_buf_size(dst)) {
+		fprintf(stderr, "fuse: copy from pipe: short read\n");
+		return -1;
+	}
+	return 0;
+}
+
+static void fuse_ll_process_buf(void *data, const struct fuse_buf *buf,
+				struct fuse_chan *ch)
+{
+	struct fuse_ll *f = (struct fuse_ll *) data;
+	const size_t write_header_size = sizeof(struct fuse_in_header) +
+		sizeof(struct fuse_write_in);
+	struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+	struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+	struct fuse_in_header *in;
+	const void *inarg;
+	struct fuse_req *req;
+	void *mbuf = NULL;
+	int err;
+	int res;
+
+	if (buf->flags & FUSE_BUF_IS_FD) {
+		if (buf->size < tmpbuf.buf[0].size)
+			tmpbuf.buf[0].size = buf->size;
+
+		mbuf = malloc(tmpbuf.buf[0].size);
+		if (mbuf == NULL) {
+			fprintf(stderr, "fuse: failed to allocate header\n");
+			goto clear_pipe;
+		}
+		tmpbuf.buf[0].mem = mbuf;
+
+		res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+		if (res < 0)
+			goto clear_pipe;
+
+		in = mbuf;
+	} else {
+		in = buf->mem;
+	}
+
+	if (f->debug) {
+		fprintf(stderr,
+			"unique: %llu, opcode: %s (%i), nodeid: %lu, insize: %zu, pid: %u\n",
+			(unsigned long long) in->unique,
+			opname((enum fuse_opcode) in->opcode), in->opcode,
+			(unsigned long) in->nodeid, buf->size, in->pid);
+	}
+
+	req = fuse_ll_alloc_req(f);
+	if (req == NULL) {
+		struct fuse_out_header out = {
+			.unique = in->unique,
+			.error = -ENOMEM,
+		};
+		struct iovec iov = {
+			.iov_base = &out,
+			.iov_len = sizeof(struct fuse_out_header),
+		};
+
+		fuse_send_msg(f, ch, &iov, 1);
+		goto clear_pipe;
+	}
+
+	req->unique = in->unique;
+	req->ctx.uid = in->uid;
+	req->ctx.gid = in->gid;
+	req->ctx.pid = in->pid;
+	req->ch = ch;
+
+	err = EIO;
+	if (!f->got_init) {
+		enum fuse_opcode expected;
+
+		expected = f->cuse_data ? CUSE_INIT : FUSE_INIT;
+		if (in->opcode != expected)
+			goto reply_err;
+	} else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT)
+		goto reply_err;
+
+	err = EACCES;
+	if (f->allow_root && in->uid != f->owner && in->uid != 0 &&
+		 in->opcode != FUSE_INIT && in->opcode != FUSE_READ &&
+		 in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC &&
+		 in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR &&
+		 in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR &&
+		 in->opcode != FUSE_NOTIFY_REPLY)
+		goto reply_err;
+
+	err = ENOSYS;
+	if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+		goto reply_err;
+	if (in->opcode != FUSE_INTERRUPT) {
+		struct fuse_req *intr;
+		pthread_mutex_lock(&f->lock);
+		intr = check_interrupt(f, req);
+		list_add_req(req, &f->list);
+		pthread_mutex_unlock(&f->lock);
+		if (intr)
+			fuse_reply_err(intr, EAGAIN);
+	}
+
+	if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+	    (in->opcode != FUSE_WRITE || !f->op.write_buf) &&
+	    in->opcode != FUSE_NOTIFY_REPLY) {
+		void *newmbuf;
+
+		err = ENOMEM;
+		newmbuf = realloc(mbuf, buf->size);
+		if (newmbuf == NULL)
+			goto reply_err;
+		mbuf = newmbuf;
+
+		tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+		tmpbuf.buf[0].mem = mbuf + write_header_size;
+
+		res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+		err = -res;
+		if (res < 0)
+			goto reply_err;
+
+		in = mbuf;
+	}
+
+	inarg = (void *) &in[1];
+	if (in->opcode == FUSE_WRITE && f->op.write_buf)
+		do_write_buf(req, in->nodeid, inarg, buf);
+	else if (in->opcode == FUSE_NOTIFY_REPLY)
+		do_notify_reply(req, in->nodeid, inarg, buf);
+	else
+		fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
+out_free:
+	free(mbuf);
+	return;
+
+reply_err:
+	fuse_reply_err(req, err);
+clear_pipe:
+	if (buf->flags & FUSE_BUF_IS_FD)
+		fuse_ll_clear_pipe(f);
+	goto out_free;
+}
+
+static void fuse_ll_process(void *data, const char *buf, size_t len,
+			    struct fuse_chan *ch)
+{
+	struct fuse_buf fbuf = {
+		.mem = (void *) buf,
+		.size = len,
+	};
+
+	fuse_ll_process_buf(data, &fbuf, ch);
+}
+
+enum {
+	KEY_HELP,
+	KEY_VERSION,
+};
+
+static const struct fuse_opt fuse_ll_opts[] = {
+	{ "debug", offsetof(struct fuse_ll, debug), 1 },
+	{ "-d", offsetof(struct fuse_ll, debug), 1 },
+	{ "allow_root", offsetof(struct fuse_ll, allow_root), 1 },
+	{ "max_write=%u", offsetof(struct fuse_ll, conn.max_write), 0 },
+	{ "max_readahead=%u", offsetof(struct fuse_ll, conn.max_readahead), 0 },
+	{ "max_background=%u", offsetof(struct fuse_ll, conn.max_background), 0 },
+	{ "congestion_threshold=%u",
+	  offsetof(struct fuse_ll, conn.congestion_threshold), 0 },
+	{ "async_read", offsetof(struct fuse_ll, conn.async_read), 1 },
+	{ "sync_read", offsetof(struct fuse_ll, conn.async_read), 0 },
+	{ "atomic_o_trunc", offsetof(struct fuse_ll, atomic_o_trunc), 1},
+	{ "no_remote_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1},
+	{ "no_remote_lock", offsetof(struct fuse_ll, no_remote_flock), 1},
+	{ "no_remote_flock", offsetof(struct fuse_ll, no_remote_flock), 1},
+	{ "no_remote_posix_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1},
+	{ "big_writes", offsetof(struct fuse_ll, big_writes), 1},
+	{ "splice_write", offsetof(struct fuse_ll, splice_write), 1},
+	{ "no_splice_write", offsetof(struct fuse_ll, no_splice_write), 1},
+	{ "splice_move", offsetof(struct fuse_ll, splice_move), 1},
+	{ "no_splice_move", offsetof(struct fuse_ll, no_splice_move), 1},
+	{ "splice_read", offsetof(struct fuse_ll, splice_read), 1},
+	{ "no_splice_read", offsetof(struct fuse_ll, no_splice_read), 1},
+	FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_DISCARD),
+	FUSE_OPT_KEY("-h", KEY_HELP),
+	FUSE_OPT_KEY("--help", KEY_HELP),
+	FUSE_OPT_KEY("-V", KEY_VERSION),
+	FUSE_OPT_KEY("--version", KEY_VERSION),
+	FUSE_OPT_END
+};
+
+static void fuse_ll_version(void)
+{
+	fprintf(stderr, "using FUSE kernel interface version %i.%i\n",
+		FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+}
+
+static void fuse_ll_help(void)
+{
+	fprintf(stderr,
+"    -o max_write=N         set maximum size of write requests\n"
+"    -o max_readahead=N     set maximum readahead\n"
+"    -o max_background=N    set number of maximum background requests\n"
+"    -o congestion_threshold=N  set kernel's congestion threshold\n"
+"    -o async_read          perform reads asynchronously (default)\n"
+"    -o sync_read           perform reads synchronously\n"
+"    -o atomic_o_trunc      enable atomic open+truncate support\n"
+"    -o big_writes          enable larger than 4kB writes\n"
+"    -o no_remote_lock      disable remote file locking\n"
+"    -o no_remote_flock     disable remote file locking (BSD)\n"
+"    -o no_remote_posix_lock disable remove file locking (POSIX)\n"
+"    -o [no_]splice_write   use splice to write to the fuse device\n"
+"    -o [no_]splice_move    move data while splicing to the fuse device\n"
+"    -o [no_]splice_read    use splice to read from the fuse device\n"
+);
+}
+
+static int fuse_ll_opt_proc(void *data, const char *arg, int key,
+			    struct fuse_args *outargs)
+{
+	(void) data; (void) outargs;
+
+	switch (key) {
+	case KEY_HELP:
+		fuse_ll_help();
+		break;
+
+	case KEY_VERSION:
+		fuse_ll_version();
+		break;
+
+	default:
+		fprintf(stderr, "fuse: unknown option `%s'\n", arg);
+	}
+
+	return -1;
+}
+
+int fuse_lowlevel_is_lib_option(const char *opt)
+{
+	return fuse_opt_match(fuse_ll_opts, opt);
+}
+
+static void fuse_ll_destroy(void *data)
+{
+	struct fuse_ll *f = (struct fuse_ll *) data;
+	struct fuse_ll_pipe *llp;
+
+	if (f->got_init && !f->got_destroy) {
+		if (f->op.destroy)
+			f->op.destroy(f->userdata);
+	}
+	llp = pthread_getspecific(f->pipe_key);
+	if (llp != NULL)
+		fuse_ll_pipe_free(llp);
+	pthread_key_delete(f->pipe_key);
+	pthread_mutex_destroy(&f->lock);
+	free(f->cuse_data);
+	free(f);
+}
+
+static void fuse_ll_pipe_destructor(void *data)
+{
+	struct fuse_ll_pipe *llp = data;
+	fuse_ll_pipe_free(llp);
+}
+
+#ifdef HAVE_SPLICE
+static int fuse_ll_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
+			       struct fuse_chan **chp)
+{
+	struct fuse_chan *ch = *chp;
+	struct fuse_ll *f = fuse_session_data(se);
+	size_t bufsize = buf->size;
+	struct fuse_ll_pipe *llp;
+	struct fuse_buf tmpbuf;
+	int err;
+	int res;
+
+	if (f->conn.proto_minor < 14 || !(f->conn.want & FUSE_CAP_SPLICE_READ))
+		goto fallback;
+
+	llp = fuse_ll_get_pipe(f);
+	if (llp == NULL)
+		goto fallback;
+
+	if (llp->size < bufsize) {
+		if (llp->can_grow) {
+			res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+			if (res == -1) {
+				llp->can_grow = 0;
+				goto fallback;
+			}
+			llp->size = res;
+		}
+		if (llp->size < bufsize)
+			goto fallback;
+	}
+
+	res = splice(fuse_chan_fd(ch), NULL, llp->pipe[1], NULL, bufsize, 0);
+	err = errno;
+
+	if (fuse_session_exited(se))
+		return 0;
+
+	if (res == -1) {
+		if (err == ENODEV) {
+			fuse_session_exit(se);
+			return 0;
+		}
+		if (err != EINTR && err != EAGAIN)
+			perror("fuse: splice from device");
+		return -err;
+	}
+
+	if (res < sizeof(struct fuse_in_header)) {
+		fprintf(stderr, "short splice from fuse device\n");
+		return -EIO;
+	}
+
+	tmpbuf = (struct fuse_buf) {
+		.size = res,
+		.flags = FUSE_BUF_IS_FD,
+		.fd = llp->pipe[0],
+	};
+
+	/*
+	 * Don't bother with zero copy for small requests.
+	 * fuse_loop_mt() needs to check for FORGET so this more than
+	 * just an optimization.
+	 */
+	if (res < sizeof(struct fuse_in_header) +
+	    sizeof(struct fuse_write_in) + pagesize) {
+		struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+		struct fuse_bufvec dst = { .buf[0] = *buf, .count = 1 };
+
+		res = fuse_buf_copy(&dst, &src, 0);
+		if (res < 0) {
+			fprintf(stderr, "fuse: copy from pipe: %s\n",
+				strerror(-res));
+			fuse_ll_clear_pipe(f);
+			return res;
+		}
+		if (res < tmpbuf.size) {
+			fprintf(stderr, "fuse: copy from pipe: short read\n");
+			fuse_ll_clear_pipe(f);
+			return -EIO;
+		}
+		buf->size = tmpbuf.size;
+		return buf->size;
+	}
+
+	*buf = tmpbuf;
+
+	return res;
+
+fallback:
+	res = fuse_chan_recv(chp, buf->mem, bufsize);
+	if (res <= 0)
+		return res;
+
+	buf->size = res;
+
+	return res;
+}
+#else
+static int fuse_ll_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
+			       struct fuse_chan **chp)
+{
+	(void) se;
+
+	int res = fuse_chan_recv(chp, buf->mem, buf->size);
+	if (res <= 0)
+		return res;
+
+	buf->size = res;
+
+	return res;
+}
+#endif
+
+
+/*
+ * always call fuse_lowlevel_new_common() internally, to work around a
+ * misfeature in the FreeBSD runtime linker, which links the old
+ * version of a symbol to internal references.
+ */
+struct fuse_session *fuse_lowlevel_new_common(struct fuse_args *args,
+					      const struct fuse_lowlevel_ops *op,
+					      size_t op_size, void *userdata)
+{
+	int err;
+	struct fuse_ll *f;
+	struct fuse_session *se;
+	struct fuse_session_ops sop = {
+		.process = fuse_ll_process,
+		.destroy = fuse_ll_destroy,
+	};
+
+	if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+		fprintf(stderr, "fuse: warning: library too old, some operations may not work\n");
+		op_size = sizeof(struct fuse_lowlevel_ops);
+	}
+
+	f = (struct fuse_ll *) calloc(1, sizeof(struct fuse_ll));
+	if (f == NULL) {
+		fprintf(stderr, "fuse: failed to allocate fuse object\n");
+		goto out;
+	}
+
+	f->conn.async_read = 1;
+	f->conn.max_write = UINT_MAX;
+	f->conn.max_readahead = UINT_MAX;
+	f->atomic_o_trunc = 0;
+	list_init_req(&f->list);
+	list_init_req(&f->interrupts);
+	list_init_nreq(&f->notify_list);
+	f->notify_ctr = 1;
+	fuse_mutex_init(&f->lock);
+
+	err = pthread_key_create(&f->pipe_key, fuse_ll_pipe_destructor);
+	if (err) {
+		fprintf(stderr, "fuse: failed to create thread specific key: %s\n",
+			strerror(err));
+		goto out_free;
+	}
+
+	if (fuse_opt_parse(args, f, fuse_ll_opts, fuse_ll_opt_proc) == -1)
+		goto out_key_destroy;
+
+	if (f->debug)
+		fprintf(stderr, "FUSE library version: %s\n", PACKAGE_VERSION);
+
+	memcpy(&f->op, op, op_size);
+	f->owner = getuid();
+	f->userdata = userdata;
+
+	se = fuse_session_new(&sop, f);
+	if (!se)
+		goto out_key_destroy;
+
+	se->receive_buf = fuse_ll_receive_buf;
+	se->process_buf = fuse_ll_process_buf;
+
+	return se;
+
+out_key_destroy:
+	pthread_key_delete(f->pipe_key);
+out_free:
+	pthread_mutex_destroy(&f->lock);
+	free(f);
+out:
+	return NULL;
+}
+
+
+struct fuse_session *fuse_lowlevel_new(struct fuse_args *args,
+				       const struct fuse_lowlevel_ops *op,
+				       size_t op_size, void *userdata)
+{
+	return fuse_lowlevel_new_common(args, op, op_size, userdata);
+}
+
+#ifdef linux
+int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+{
+	char *buf;
+	size_t bufsize = 1024;
+	char path[128];
+	int ret;
+	int fd;
+	unsigned long pid = req->ctx.pid;
+	char *s;
+
+	sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
+retry:
+	buf = malloc(bufsize);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	ret = -EIO;
+	fd = open(path, O_RDONLY);
+	if (fd == -1)
+		goto out_free;
+
+	ret = read(fd, buf, bufsize);
+	close(fd);
+	if (ret == -1) {
+		ret = -EIO;
+		goto out_free;
+	}
+
+	if (ret == bufsize) {
+		free(buf);
+		bufsize *= 4;
+		goto retry;
+	}
+
+	ret = -EIO;
+	s = strstr(buf, "\nGroups:");
+	if (s == NULL)
+		goto out_free;
+
+	s += 8;
+	ret = 0;
+	while (1) {
+		char *end;
+		unsigned long val = strtoul(s, &end, 0);
+		if (end == s)
+			break;
+
+		s = end;
+		if (ret < size)
+			list[ret] = val;
+		ret++;
+	}
+
+out_free:
+	free(buf);
+	return ret;
+}
+#else /* linux */
+/*
+ * This is currently not implemented on other than Linux...
+ */
+int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+{
+	return -ENOSYS;
+}
+#endif
+
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
+
+static void fill_open_compat(struct fuse_open_out *arg,
+			     const struct fuse_file_info_compat *f)
+{
+	arg->fh = f->fh;
+	if (f->direct_io)
+		arg->open_flags |= FOPEN_DIRECT_IO;
+	if (f->keep_cache)
+		arg->open_flags |= FOPEN_KEEP_CACHE;
+}
+
+static void convert_statfs_compat(const struct statfs *compatbuf,
+				  struct statvfs *buf)
+{
+	buf->f_bsize	= compatbuf->f_bsize;
+	buf->f_blocks	= compatbuf->f_blocks;
+	buf->f_bfree	= compatbuf->f_bfree;
+	buf->f_bavail	= compatbuf->f_bavail;
+	buf->f_files	= compatbuf->f_files;
+	buf->f_ffree	= compatbuf->f_ffree;
+	buf->f_namemax	= compatbuf->f_namelen;
+}
+
+int fuse_reply_open_compat(fuse_req_t req,
+			   const struct fuse_file_info_compat *f)
+{
+	struct fuse_open_out arg;
+
+	memset(&arg, 0, sizeof(arg));
+	fill_open_compat(&arg, f);
+	return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+int fuse_reply_statfs_compat(fuse_req_t req, const struct statfs *stbuf)
+{
+	struct statvfs newbuf;
+
+	memset(&newbuf, 0, sizeof(newbuf));
+	convert_statfs_compat(stbuf, &newbuf);
+
+	return fuse_reply_statfs(req, &newbuf);
+}
+
+struct fuse_session *fuse_lowlevel_new_compat(const char *opts,
+				const struct fuse_lowlevel_ops_compat *op,
+				size_t op_size, void *userdata)
+{
+	struct fuse_session *se;
+	struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+
+	if (opts &&
+	    (fuse_opt_add_arg(&args, "") == -1 ||
+	     fuse_opt_add_arg(&args, "-o") == -1 ||
+	     fuse_opt_add_arg(&args, opts) == -1)) {
+		fuse_opt_free_args(&args);
+		return NULL;
+	}
+	se = fuse_lowlevel_new(&args, (const struct fuse_lowlevel_ops *) op,
+			       op_size, userdata);
+	fuse_opt_free_args(&args);
+
+	return se;
+}
+
+struct fuse_ll_compat_conf {
+	unsigned max_read;
+	int set_max_read;
+};
+
+static const struct fuse_opt fuse_ll_opts_compat[] = {
+	{ "max_read=", offsetof(struct fuse_ll_compat_conf, set_max_read), 1 },
+	{ "max_read=%u", offsetof(struct fuse_ll_compat_conf, max_read), 0 },
+	FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_KEEP),
+	FUSE_OPT_END
+};
+
+int fuse_sync_compat_args(struct fuse_args *args)
+{
+	struct fuse_ll_compat_conf conf;
+
+	memset(&conf, 0, sizeof(conf));
+	if (fuse_opt_parse(args, &conf, fuse_ll_opts_compat, NULL) == -1)
+		return -1;
+
+	if (fuse_opt_insert_arg(args, 1, "-osync_read"))
+		return -1;
+
+	if (conf.set_max_read) {
+		char tmpbuf[64];
+
+		sprintf(tmpbuf, "-omax_readahead=%u", conf.max_read);
+		if (fuse_opt_insert_arg(args, 1, tmpbuf) == -1)
+			return -1;
+	}
+	return 0;
+}
+
+FUSE_SYMVER(".symver fuse_reply_statfs_compat,fuse_reply_statfs@FUSE_2.4");
+FUSE_SYMVER(".symver fuse_reply_open_compat,fuse_reply_open@FUSE_2.4");
+FUSE_SYMVER(".symver fuse_lowlevel_new_compat,fuse_lowlevel_new@FUSE_2.4");
+
+#else /* __FreeBSD__ || __NetBSD__ */
+
+int fuse_sync_compat_args(struct fuse_args *args)
+{
+	(void) args;
+	return 0;
+}
+
+#endif /* __FreeBSD__ || __NetBSD__ */
+
+struct fuse_session *fuse_lowlevel_new_compat25(struct fuse_args *args,
+				const struct fuse_lowlevel_ops_compat25 *op,
+				size_t op_size, void *userdata)
+{
+	if (fuse_sync_compat_args(args) == -1)
+		return NULL;
+
+	return fuse_lowlevel_new_common(args,
+					(const struct fuse_lowlevel_ops *) op,
+					op_size, userdata);
+}
+
+FUSE_SYMVER(".symver fuse_lowlevel_new_compat25,fuse_lowlevel_new@FUSE_2.5");
diff --git a/fuse/fuse_misc.h b/fuse/fuse_misc.h
new file mode 100644
index 0000000..eedf0e0
--- /dev/null
+++ b/fuse/fuse_misc.h
@@ -0,0 +1,57 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#include "config.h"
+#include <pthread.h>
+
+/*
+  Versioned symbols cannot be used in some cases because it
+    - confuse the dynamic linker in uClibc
+    - not supported on MacOSX (in MachO binary format)
+*/
+#if (!defined(__UCLIBC__) && !defined(__APPLE__))
+#define FUSE_SYMVER(x) __asm__(x)
+#else
+#define FUSE_SYMVER(x)
+#endif
+
+#ifndef USE_UCLIBC
+#define fuse_mutex_init(mut) pthread_mutex_init(mut, NULL)
+#else
+/* Is this hack still needed? */
+static inline void fuse_mutex_init(pthread_mutex_t *mut)
+{
+	pthread_mutexattr_t attr;
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP);
+	pthread_mutex_init(mut, &attr);
+	pthread_mutexattr_destroy(&attr);
+}
+#endif
+
+#ifdef HAVE_STRUCT_STAT_ST_ATIM
+/* Linux */
+#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+/* FreeBSD */
+#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+#else
+#define ST_ATIM_NSEC(stbuf) 0
+#define ST_CTIM_NSEC(stbuf) 0
+#define ST_MTIM_NSEC(stbuf) 0
+#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+#endif
diff --git a/fuse/fuse_mt.c b/fuse/fuse_mt.c
new file mode 100644
index 0000000..fd5ac23
--- /dev/null
+++ b/fuse/fuse_mt.c
@@ -0,0 +1,122 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#include "fuse_i.h"
+#include "fuse_misc.h"
+#include "fuse_lowlevel.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include <assert.h>
+
+struct procdata {
+	struct fuse *f;
+	struct fuse_chan *prevch;
+	struct fuse_session *prevse;
+	fuse_processor_t proc;
+	void *data;
+};
+
+static void mt_session_proc(void *data, const char *buf, size_t len,
+			    struct fuse_chan *ch)
+{
+	struct procdata *pd = (struct procdata *) data;
+	struct fuse_cmd *cmd = *(struct fuse_cmd **) buf;
+
+	(void) len;
+	(void) ch;
+	pd->proc(pd->f, cmd, pd->data);
+}
+
+static void mt_session_exit(void *data, int val)
+{
+	struct procdata *pd = (struct procdata *) data;
+	if (val)
+		fuse_session_exit(pd->prevse);
+	else
+		fuse_session_reset(pd->prevse);
+}
+
+static int mt_session_exited(void *data)
+{
+	struct procdata *pd = (struct procdata *) data;
+	return fuse_session_exited(pd->prevse);
+}
+
+static int mt_chan_receive(struct fuse_chan **chp, char *buf, size_t size)
+{
+	struct fuse_cmd *cmd;
+	struct procdata *pd = (struct procdata *) fuse_chan_data(*chp);
+
+	assert(size >= sizeof(cmd));
+
+	cmd = fuse_read_cmd(pd->f);
+	if (cmd == NULL)
+		return 0;
+
+	*(struct fuse_cmd **) buf = cmd;
+
+	return sizeof(cmd);
+}
+
+int fuse_loop_mt_proc(struct fuse *f, fuse_processor_t proc, void *data)
+{
+	int res;
+	struct procdata pd;
+	struct fuse_session *prevse = fuse_get_session(f);
+	struct fuse_session *se;
+	struct fuse_chan *prevch = fuse_session_next_chan(prevse, NULL);
+	struct fuse_chan *ch;
+	struct fuse_session_ops sop = {
+		.exit = mt_session_exit,
+		.exited = mt_session_exited,
+		.process = mt_session_proc,
+	};
+	struct fuse_chan_ops cop = {
+		.receive = mt_chan_receive,
+	};
+
+	pd.f = f;
+	pd.prevch = prevch;
+	pd.prevse = prevse;
+	pd.proc = proc;
+	pd.data = data;
+
+	se = fuse_session_new(&sop, &pd);
+	if (se == NULL)
+		return -1;
+
+	ch = fuse_chan_new(&cop, fuse_chan_fd(prevch),
+			   sizeof(struct fuse_cmd *), &pd);
+	if (ch == NULL) {
+		fuse_session_destroy(se);
+		return -1;
+	}
+	fuse_session_add_chan(se, ch);
+	res = fuse_session_loop_mt(se);
+	fuse_session_destroy(se);
+	return res;
+}
+
+int fuse_loop_mt(struct fuse *f)
+{
+	if (f == NULL)
+		return -1;
+
+	int res = fuse_start_cleanup_thread(f);
+	if (res)
+		return -1;
+
+	res = fuse_session_loop_mt(fuse_get_session(f));
+	fuse_stop_cleanup_thread(f);
+	return res;
+}
+
+FUSE_SYMVER(".symver fuse_loop_mt_proc,__fuse_loop_mt@FUSE_UNVERSIONED");
diff --git a/fuse/fuse_opt.c b/fuse/fuse_opt.c
new file mode 100644
index 0000000..a2118ce
--- /dev/null
+++ b/fuse/fuse_opt.c
@@ -0,0 +1,426 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#include "fuse_opt.h"
+#include "fuse_misc.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+struct fuse_opt_context {
+	void *data;
+	const struct fuse_opt *opt;
+	fuse_opt_proc_t proc;
+	int argctr;
+	int argc;
+	char **argv;
+	struct fuse_args outargs;
+	char *opts;
+	int nonopt;
+};
+
+void fuse_opt_free_args(struct fuse_args *args)
+{
+	if (args) {
+		if (args->argv && args->allocated) {
+			int i;
+			for (i = 0; i < args->argc; i++)
+				free(args->argv[i]);
+			free(args->argv);
+		}
+		args->argc = 0;
+		args->argv = NULL;
+		args->allocated = 0;
+	}
+}
+
+static int alloc_failed(void)
+{
+	fprintf(stderr, "fuse: memory allocation failed\n");
+	return -1;
+}
+
+int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+{
+	char **newargv;
+	char *newarg;
+
+	assert(!args->argv || args->allocated);
+
+	newarg = strdup(arg);
+	if (!newarg)
+		return alloc_failed();
+
+	newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+	if (!newargv) {
+		free(newarg);
+		return alloc_failed();
+	}
+
+	args->argv = newargv;
+	args->allocated = 1;
+	args->argv[args->argc++] = newarg;
+	args->argv[args->argc] = NULL;
+	return 0;
+}
+
+static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+				      const char *arg)
+{
+	assert(pos <= args->argc);
+	if (fuse_opt_add_arg(args, arg) == -1)
+		return -1;
+
+	if (pos != args->argc - 1) {
+		char *newarg = args->argv[args->argc - 1];
+		memmove(&args->argv[pos + 1], &args->argv[pos],
+			sizeof(char *) * (args->argc - pos - 1));
+		args->argv[pos] = newarg;
+	}
+	return 0;
+}
+
+int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+{
+	return fuse_opt_insert_arg_common(args, pos, arg);
+}
+
+int fuse_opt_insert_arg_compat(struct fuse_args *args, int pos,
+			       const char *arg);
+int fuse_opt_insert_arg_compat(struct fuse_args *args, int pos, const char *arg)
+{
+	return fuse_opt_insert_arg_common(args, pos, arg);
+}
+
+static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+{
+	if (ctx->argctr + 1 >= ctx->argc) {
+		fprintf(stderr, "fuse: missing argument after `%s'\n", opt);
+		return -1;
+	}
+	ctx->argctr++;
+	return 0;
+}
+
+static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+{
+	return fuse_opt_add_arg(&ctx->outargs, arg);
+}
+
+static int add_opt_common(char **opts, const char *opt, int esc)
+{
+	unsigned oldlen = *opts ? strlen(*opts) : 0;
+	char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
+	if (!d)
+		return alloc_failed();
+
+	*opts = d;
+	if (oldlen) {
+		d += oldlen;
+		*d++ = ',';
+	}
+
+	for (; *opt; opt++) {
+		if (esc && (*opt == ',' || *opt == '\\'))
+			*d++ = '\\';
+		*d++ = *opt;
+	}
+	*d = '\0';
+
+	return 0;
+}
+
+int fuse_opt_add_opt(char **opts, const char *opt)
+{
+	return add_opt_common(opts, opt, 0);
+}
+
+int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+{
+	return add_opt_common(opts, opt, 1);
+}
+
+static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+{
+	return add_opt_common(&ctx->opts, opt, 1);
+}
+
+static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+		     int iso)
+{
+	if (key == FUSE_OPT_KEY_DISCARD)
+		return 0;
+
+	if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+		int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+		if (res == -1 || !res)
+			return res;
+	}
+	if (iso)
+		return add_opt(ctx, arg);
+	else
+		return add_arg(ctx, arg);
+}
+
+static int match_template(const char *t, const char *arg, unsigned *sepp)
+{
+	int arglen = strlen(arg);
+	const char *sep = strchr(t, '=');
+	sep = sep ? sep : strchr(t, ' ');
+	if (sep && (!sep[1] || sep[1] == '%')) {
+		int tlen = sep - t;
+		if (sep[0] == '=')
+			tlen ++;
+		if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+			*sepp = sep - t;
+			return 1;
+		}
+	}
+	if (strcmp(t, arg) == 0) {
+		*sepp = 0;
+		return 1;
+	}
+	return 0;
+}
+
+static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+				       const char *arg, unsigned *sepp)
+{
+	for (; opt && opt->templ; opt++)
+		if (match_template(opt->templ, arg, sepp))
+			return opt;
+	return NULL;
+}
+
+int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+{
+	unsigned dummy;
+	return find_opt(opts, opt, &dummy) ? 1 : 0;
+}
+
+static int process_opt_param(void *var, const char *format, const char *param,
+			     const char *arg)
+{
+	assert(format[0] == '%');
+	if (format[1] == 's') {
+		char *copy = strdup(param);
+		if (!copy)
+			return alloc_failed();
+
+		*(char **) var = copy;
+	} else {
+		if (sscanf(param, format, var) != 1) {
+			fprintf(stderr, "fuse: invalid parameter in option `%s'\n", arg);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int process_opt(struct fuse_opt_context *ctx,
+		       const struct fuse_opt *opt, unsigned sep,
+		       const char *arg, int iso)
+{
+	if (opt->offset == -1U) {
+		if (call_proc(ctx, arg, opt->value, iso) == -1)
+			return -1;
+	} else {
+		void *var = ctx->data + opt->offset;
+		if (sep && opt->templ[sep + 1]) {
+			const char *param = arg + sep;
+			if (opt->templ[sep] == '=')
+				param ++;
+			if (process_opt_param(var, opt->templ + sep + 1,
+					      param, arg) == -1)
+				return -1;
+		} else
+			*(int *)var = opt->value;
+	}
+	return 0;
+}
+
+static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+			       const struct fuse_opt *opt, unsigned sep,
+			       const char *arg, int iso)
+{
+	int res;
+	char *newarg;
+	char *param;
+
+	if (next_arg(ctx, arg) == -1)
+		return -1;
+
+	param = ctx->argv[ctx->argctr];
+	newarg = malloc(sep + strlen(param) + 1);
+	if (!newarg)
+		return alloc_failed();
+
+	memcpy(newarg, arg, sep);
+	strcpy(newarg + sep, param);
+	res = process_opt(ctx, opt, sep, newarg, iso);
+	free(newarg);
+
+	return res;
+}
+
+static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+{
+	unsigned sep;
+	const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+	if (opt) {
+		for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+			int res;
+			if (sep && opt->templ[sep] == ' ' && !arg[sep])
+				res = process_opt_sep_arg(ctx, opt, sep, arg,
+							  iso);
+			else
+				res = process_opt(ctx, opt, sep, arg, iso);
+			if (res == -1)
+				return -1;
+		}
+		return 0;
+	} else
+		return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+}
+
+static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+{
+	char *s = opts;
+	char *d = s;
+	int end = 0;
+
+	while (!end) {
+		if (*s == '\0')
+			end = 1;
+		if (*s == ',' || end) {
+			int res;
+
+			*d = '\0';
+			res = process_gopt(ctx, opts, 1);
+			if (res == -1)
+				return -1;
+			d = opts;
+		} else {
+			if (s[0] == '\\' && s[1] != '\0') {
+				s++;
+				if (s[0] >= '0' && s[0] <= '3' &&
+				    s[1] >= '0' && s[1] <= '7' &&
+				    s[2] >= '0' && s[2] <= '7') {
+					*d++ = (s[0] - '0') * 0100 +
+						(s[1] - '0') * 0010 +
+						(s[2] - '0');
+					s += 2;
+				} else {
+					*d++ = *s;
+				}
+			} else {
+				*d++ = *s;
+			}
+		}
+		s++;
+	}
+
+	return 0;
+}
+
+static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+{
+	int res;
+	char *copy = strdup(opts);
+
+	if (!copy) {
+		fprintf(stderr, "fuse: memory allocation failed\n");
+		return -1;
+	}
+	res = process_real_option_group(ctx, copy);
+	free(copy);
+	return res;
+}
+
+static int process_one(struct fuse_opt_context *ctx, const char *arg)
+{
+	if (ctx->nonopt || arg[0] != '-')
+		return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+	else if (arg[1] == 'o') {
+		if (arg[2])
+			return process_option_group(ctx, arg + 2);
+		else {
+			if (next_arg(ctx, arg) == -1)
+				return -1;
+
+			return process_option_group(ctx,
+						    ctx->argv[ctx->argctr]);
+		}
+	} else if (arg[1] == '-' && !arg[2]) {
+		if (add_arg(ctx, arg) == -1)
+			return -1;
+		ctx->nonopt = ctx->outargs.argc;
+		return 0;
+	} else
+		return process_gopt(ctx, arg, 0);
+}
+
+static int opt_parse(struct fuse_opt_context *ctx)
+{
+	if (ctx->argc) {
+		if (add_arg(ctx, ctx->argv[0]) == -1)
+			return -1;
+	}
+
+	for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+		if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+			return -1;
+
+	if (ctx->opts) {
+		if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+		    fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+			return -1;
+	}
+
+	/* If option separator ("--") is the last argument, remove it */
+	if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+	    strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+		free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+		ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+	}
+
+	return 0;
+}
+
+int fuse_opt_parse(struct fuse_args *args, void *data,
+		   const struct fuse_opt opts[], fuse_opt_proc_t proc)
+{
+	int res;
+	struct fuse_opt_context ctx = {
+		.data = data,
+		.opt = opts,
+		.proc = proc,
+	};
+
+	if (!args || !args->argv || !args->argc)
+		return 0;
+
+	ctx.argc = args->argc;
+	ctx.argv = args->argv;
+
+	res = opt_parse(&ctx);
+	if (res != -1) {
+		struct fuse_args tmp = *args;
+		*args = ctx.outargs;
+		ctx.outargs = tmp;
+	}
+	free(ctx.opts);
+	fuse_opt_free_args(&ctx.outargs);
+	return res;
+}
+
+/* This symbol version was mistakenly added to the version script */
+FUSE_SYMVER(".symver fuse_opt_insert_arg_compat,fuse_opt_insert_arg@FUSE_2.5");
diff --git a/fuse/fuse_session.c b/fuse/fuse_session.c
new file mode 100644
index 0000000..18c8c42
--- /dev/null
+++ b/fuse/fuse_session.c
@@ -0,0 +1,243 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#include "fuse_i.h"
+#include "fuse_misc.h"
+#include "fuse_common_compat.h"
+#include "fuse_lowlevel_compat.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+struct fuse_chan {
+	struct fuse_chan_ops op;
+
+	struct fuse_session *se;
+
+	int fd;
+
+	size_t bufsize;
+
+	void *data;
+
+	int compat;
+};
+
+struct fuse_session *fuse_session_new(struct fuse_session_ops *op, void *data)
+{
+	struct fuse_session *se = (struct fuse_session *) malloc(sizeof(*se));
+	if (se == NULL) {
+		fprintf(stderr, "fuse: failed to allocate session\n");
+		return NULL;
+	}
+
+	memset(se, 0, sizeof(*se));
+	se->op = *op;
+	se->data = data;
+
+	return se;
+}
+
+void fuse_session_add_chan(struct fuse_session *se, struct fuse_chan *ch)
+{
+	assert(se->ch == NULL);
+	assert(ch->se == NULL);
+	se->ch = ch;
+	ch->se = se;
+}
+
+void fuse_session_remove_chan(struct fuse_chan *ch)
+{
+	struct fuse_session *se = ch->se;
+	if (se) {
+		assert(se->ch == ch);
+		se->ch = NULL;
+		ch->se = NULL;
+	}
+}
+
+struct fuse_chan *fuse_session_next_chan(struct fuse_session *se,
+					 struct fuse_chan *ch)
+{
+	assert(ch == NULL || ch == se->ch);
+	if (ch == NULL)
+		return se->ch;
+	else
+		return NULL;
+}
+
+void fuse_session_process(struct fuse_session *se, const char *buf, size_t len,
+			  struct fuse_chan *ch)
+{
+	se->op.process(se->data, buf, len, ch);
+}
+
+void fuse_session_process_buf(struct fuse_session *se,
+			      const struct fuse_buf *buf, struct fuse_chan *ch)
+{
+	if (se->process_buf) {
+		se->process_buf(se->data, buf, ch);
+	} else {
+		assert(!(buf->flags & FUSE_BUF_IS_FD));
+		fuse_session_process(se->data, buf->mem, buf->size, ch);
+	}
+}
+
+int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
+			     struct fuse_chan **chp)
+{
+	int res;
+
+	if (se->receive_buf) {
+		res = se->receive_buf(se, buf, chp);
+	} else {
+		res = fuse_chan_recv(chp, buf->mem, buf->size);
+		if (res > 0)
+			buf->size = res;
+	}
+
+	return res;
+}
+
+
+void fuse_session_destroy(struct fuse_session *se)
+{
+	if (se->op.destroy)
+		se->op.destroy(se->data);
+	if (se->ch != NULL)
+		fuse_chan_destroy(se->ch);
+	free(se);
+}
+
+void fuse_session_exit(struct fuse_session *se)
+{
+	if (se->op.exit)
+		se->op.exit(se->data, 1);
+	se->exited = 1;
+}
+
+void fuse_session_reset(struct fuse_session *se)
+{
+	if (se->op.exit)
+		se->op.exit(se->data, 0);
+	se->exited = 0;
+}
+
+int fuse_session_exited(struct fuse_session *se)
+{
+	if (se->op.exited)
+		return se->op.exited(se->data);
+	else
+		return se->exited;
+}
+
+void *fuse_session_data(struct fuse_session *se)
+{
+	return se->data;
+}
+
+static struct fuse_chan *fuse_chan_new_common(struct fuse_chan_ops *op, int fd,
+					      size_t bufsize, void *data,
+					      int compat)
+{
+	struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+	if (ch == NULL) {
+		fprintf(stderr, "fuse: failed to allocate channel\n");
+		return NULL;
+	}
+
+	memset(ch, 0, sizeof(*ch));
+	ch->op = *op;
+	ch->fd = fd;
+	ch->bufsize = bufsize;
+	ch->data = data;
+	ch->compat = compat;
+
+	return ch;
+}
+
+struct fuse_chan *fuse_chan_new(struct fuse_chan_ops *op, int fd,
+				size_t bufsize, void *data)
+{
+	return fuse_chan_new_common(op, fd, bufsize, data, 0);
+}
+
+struct fuse_chan *fuse_chan_new_compat24(struct fuse_chan_ops_compat24 *op,
+					 int fd, size_t bufsize, void *data)
+{
+	return fuse_chan_new_common((struct fuse_chan_ops *) op, fd, bufsize,
+				    data, 24);
+}
+
+int fuse_chan_fd(struct fuse_chan *ch)
+{
+	return ch->fd;
+}
+
+int fuse_chan_clearfd(struct fuse_chan *ch)
+{
+	if (ch == NULL)
+		return -1;
+
+	int fd = ch->fd;
+	ch->fd = -1;
+	return fd;
+}
+
+size_t fuse_chan_bufsize(struct fuse_chan *ch)
+{
+	return ch->bufsize;
+}
+
+void *fuse_chan_data(struct fuse_chan *ch)
+{
+	return ch->data;
+}
+
+struct fuse_session *fuse_chan_session(struct fuse_chan *ch)
+{
+	return ch->se;
+}
+
+int fuse_chan_recv(struct fuse_chan **chp, char *buf, size_t size)
+{
+	struct fuse_chan *ch = *chp;
+	if (ch->compat)
+		return ((struct fuse_chan_ops_compat24 *) &ch->op)
+			->receive(ch, buf, size);
+	else
+		return ch->op.receive(chp, buf, size);
+}
+
+int fuse_chan_receive(struct fuse_chan *ch, char *buf, size_t size)
+{
+	int res;
+
+	res = fuse_chan_recv(&ch, buf, size);
+	return res >= 0 ? res : (res != -EINTR && res != -EAGAIN) ? -1 : 0;
+}
+
+int fuse_chan_send(struct fuse_chan *ch, const struct iovec iov[], size_t count)
+{
+	return ch->op.send(ch, iov, count);
+}
+
+void fuse_chan_destroy(struct fuse_chan *ch)
+{
+	fuse_session_remove_chan(ch);
+	if (ch->op.destroy)
+		ch->op.destroy(ch);
+	free(ch);
+}
+
+#ifndef __FreeBSD__
+FUSE_SYMVER(".symver fuse_chan_new_compat24,fuse_chan_new@FUSE_2.4");
+#endif
diff --git a/fuse/fuse_signals.c b/fuse/fuse_signals.c
new file mode 100644
index 0000000..353cb24
--- /dev/null
+++ b/fuse/fuse_signals.c
@@ -0,0 +1,72 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#include "fuse_lowlevel.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+
+static struct fuse_session *fuse_instance;
+
+static void exit_handler(int sig)
+{
+	(void) sig;
+	if (fuse_instance)
+		fuse_session_exit(fuse_instance);
+}
+
+static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+{
+	struct sigaction sa;
+	struct sigaction old_sa;
+
+	memset(&sa, 0, sizeof(struct sigaction));
+	sa.sa_handler = remove ? SIG_DFL : handler;
+	sigemptyset(&(sa.sa_mask));
+	sa.sa_flags = 0;
+
+	if (sigaction(sig, NULL, &old_sa) == -1) {
+		perror("fuse: cannot get old signal handler");
+		return -1;
+	}
+
+	if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+	    sigaction(sig, &sa, NULL) == -1) {
+		perror("fuse: cannot set signal handler");
+		return -1;
+	}
+	return 0;
+}
+
+int fuse_set_signal_handlers(struct fuse_session *se)
+{
+	if (set_one_signal_handler(SIGHUP, exit_handler, 0) == -1 ||
+	    set_one_signal_handler(SIGINT, exit_handler, 0) == -1 ||
+	    set_one_signal_handler(SIGTERM, exit_handler, 0) == -1 ||
+	    set_one_signal_handler(SIGPIPE, SIG_IGN, 0) == -1)
+		return -1;
+
+	fuse_instance = se;
+	return 0;
+}
+
+void fuse_remove_signal_handlers(struct fuse_session *se)
+{
+	if (fuse_instance != se)
+		fprintf(stderr,
+			"fuse: fuse_remove_signal_handlers: unknown session\n");
+	else
+		fuse_instance = NULL;
+
+	set_one_signal_handler(SIGHUP, exit_handler, 1);
+	set_one_signal_handler(SIGINT, exit_handler, 1);
+	set_one_signal_handler(SIGTERM, exit_handler, 1);
+	set_one_signal_handler(SIGPIPE, SIG_IGN, 1);
+}
+
diff --git a/fuse/fuse_versionscript b/fuse/fuse_versionscript
new file mode 100644
index 0000000..de16ab2
--- /dev/null
+++ b/fuse/fuse_versionscript
@@ -0,0 +1,210 @@
+FUSE_UNVERSIONED {
+};
+
+FUSE_2.2 {
+	global:
+		fuse_destroy;
+		fuse_exit;
+		fuse_exited;
+		fuse_invalidate;
+		fuse_is_lib_option;
+		fuse_loop;
+		fuse_loop_mt;
+		fuse_loop_mt_proc;
+		fuse_main;
+		fuse_main_compat1;
+		fuse_main_compat2;
+		fuse_mount_compat1;
+		fuse_new_compat1;
+		fuse_new_compat2;
+		fuse_process_cmd;
+		fuse_read_cmd;
+		fuse_set_getcontext_func;
+		fuse_setup_compat2;
+};
+
+FUSE_2.4 {
+	global:
+		fuse_add_dirent;
+		fuse_chan_bufsize;
+		fuse_chan_data;
+		fuse_chan_destroy;
+		fuse_chan_fd;
+		fuse_chan_receive;
+		fuse_chan_send;
+		fuse_chan_session;
+		fuse_dirent_size;
+		fuse_kern_chan_new;
+		fuse_lowlevel_is_lib_option;
+		fuse_reply_attr;
+		fuse_reply_buf;
+		fuse_reply_entry;
+		fuse_reply_err;
+		fuse_reply_none;
+		fuse_reply_readlink;
+		fuse_reply_write;
+		fuse_reply_xattr;
+		fuse_req_userdata;
+		fuse_session_add_chan;
+		fuse_session_destroy;
+		fuse_session_exit;
+		fuse_session_exited;
+		fuse_session_loop;
+		fuse_session_loop_mt;
+		fuse_session_new;
+		fuse_session_next_chan;
+		fuse_session_process;
+		fuse_session_reset;
+} FUSE_2.2;
+
+FUSE_2.5 {
+	global:
+		fuse_lowlevel_new_compat;
+		fuse_main_real_compat22;
+		fuse_mount_compat22;
+		fuse_new_compat22;
+		fuse_opt_parse;
+		fuse_opt_add_opt;
+		fuse_opt_add_arg;
+		fuse_opt_free_args;
+		fuse_opt_match;
+		fuse_parse_cmdline;
+		fuse_remove_signal_handlers;
+		fuse_reply_create;
+		fuse_reply_open;
+		fuse_reply_open_compat;
+		fuse_reply_statfs;
+		fuse_reply_statfs_compat;
+		fuse_setup_compat22;
+		fuse_set_signal_handlers;
+} FUSE_2.4;
+
+FUSE_2.6 {
+	global:
+		fuse_add_direntry;
+		fuse_chan_new;
+		fuse_chan_new_compat24;
+		fuse_chan_recv;
+		fuse_daemonize;
+		fuse_get_session;
+		fuse_interrupted;
+		fuse_lowlevel_new;
+		fuse_lowlevel_new_compat25;
+		fuse_main_real;
+		fuse_main_real_compat25;
+		fuse_mount;
+		fuse_mount_compat25;
+		fuse_new;
+		fuse_new_compat25;
+		fuse_opt_insert_arg;
+		fuse_reply_lock;
+		fuse_req_interrupt_func;
+		fuse_req_interrupted;
+		fuse_session_remove_chan;
+		fuse_setup;
+		fuse_setup_compat25;
+		fuse_teardown;
+		fuse_teardown_compat22;
+		fuse_unmount;
+		fuse_unmount_compat22;
+} FUSE_2.5;
+
+FUSE_2.7 {
+	global:
+		fuse_fs_access;
+		fuse_fs_bmap;
+		fuse_fs_chmod;
+		fuse_fs_chown;
+		fuse_fs_create;
+		fuse_fs_destroy;
+		fuse_fs_fgetattr;
+		fuse_fs_flush;
+		fuse_fs_fsync;
+		fuse_fs_fsyncdir;
+		fuse_fs_ftruncate;
+		fuse_fs_getattr;
+		fuse_fs_getxattr;
+		fuse_fs_init;
+		fuse_fs_link;
+		fuse_fs_listxattr;
+		fuse_fs_lock;
+		fuse_fs_mkdir;
+		fuse_fs_mknod;
+		fuse_fs_new;
+		fuse_fs_open;
+		fuse_fs_opendir;
+		fuse_fs_read;
+		fuse_fs_readdir;
+		fuse_fs_readlink;
+		fuse_fs_release;
+		fuse_fs_releasedir;
+		fuse_fs_removexattr;
+		fuse_fs_rename;
+		fuse_fs_rmdir;
+		fuse_fs_setxattr;
+		fuse_fs_statfs;
+		fuse_fs_symlink;
+		fuse_fs_truncate;
+		fuse_fs_unlink;
+		fuse_fs_utimens;
+		fuse_fs_write;
+		fuse_register_module;
+		fuse_reply_iov;
+		fuse_version;
+} FUSE_2.6;
+
+FUSE_2.7.5 {
+	global:
+		fuse_reply_bmap;
+} FUSE_2.7;
+
+FUSE_2.8 {
+	global:
+		cuse_lowlevel_new;
+		cuse_lowlevel_main;
+		cuse_lowlevel_setup;
+		cuse_lowlevel_teardown;
+		fuse_fs_ioctl;
+		fuse_fs_poll;
+		fuse_get_context;
+		fuse_getgroups;
+		fuse_lowlevel_notify_inval_entry;
+		fuse_lowlevel_notify_inval_inode;
+		fuse_lowlevel_notify_poll;
+		fuse_notify_poll;
+		fuse_opt_add_opt_escaped;
+		fuse_pollhandle_destroy;
+		fuse_reply_ioctl;
+		fuse_reply_ioctl_iov;
+		fuse_reply_ioctl_retry;
+		fuse_reply_poll;
+		fuse_req_ctx;
+		fuse_req_getgroups;
+		fuse_session_data;
+} FUSE_2.7.5;
+
+FUSE_2.9 {
+	global:
+		fuse_buf_copy;
+		fuse_buf_size;
+		fuse_fs_read_buf;
+		fuse_fs_write_buf;
+		fuse_lowlevel_notify_retrieve;
+		fuse_lowlevel_notify_store;
+		fuse_reply_data;
+		fuse_session_process_buf;
+		fuse_session_receive_buf;
+		fuse_start_cleanup_thread;
+		fuse_stop_cleanup_thread;
+		fuse_clean_cache;
+		fuse_lowlevel_notify_delete;
+		fuse_fs_flock;
+} FUSE_2.8;
+
+FUSE_2.9.1 {
+	global:
+		fuse_fs_fallocate;
+
+	local:
+		*;
+} FUSE_2.9;
diff --git a/fuse/helper.c b/fuse/helper.c
new file mode 100644
index 0000000..c5349bf
--- /dev/null
+++ b/fuse/helper.c
@@ -0,0 +1,480 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_misc.h"
+#include "fuse_opt.h"
+#include "fuse_lowlevel.h"
+#include "fuse_common_compat.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/param.h>
+
+enum  {
+	KEY_HELP,
+	KEY_HELP_NOHEADER,
+	KEY_VERSION,
+};
+
+struct helper_opts {
+	int singlethread;
+	int foreground;
+	int nodefault_subtype;
+	char *mountpoint;
+};
+
+#define FUSE_HELPER_OPT(t, p) { t, offsetof(struct helper_opts, p), 1 }
+
+static const struct fuse_opt fuse_helper_opts[] = {
+	FUSE_HELPER_OPT("-d",		foreground),
+	FUSE_HELPER_OPT("debug",	foreground),
+	FUSE_HELPER_OPT("-f",		foreground),
+	FUSE_HELPER_OPT("-s",		singlethread),
+	FUSE_HELPER_OPT("fsname=",	nodefault_subtype),
+	FUSE_HELPER_OPT("subtype=",	nodefault_subtype),
+
+	FUSE_OPT_KEY("-h",		KEY_HELP),
+	FUSE_OPT_KEY("--help",		KEY_HELP),
+	FUSE_OPT_KEY("-ho",		KEY_HELP_NOHEADER),
+	FUSE_OPT_KEY("-V",		KEY_VERSION),
+	FUSE_OPT_KEY("--version",	KEY_VERSION),
+	FUSE_OPT_KEY("-d",		FUSE_OPT_KEY_KEEP),
+	FUSE_OPT_KEY("debug",		FUSE_OPT_KEY_KEEP),
+	FUSE_OPT_KEY("fsname=",		FUSE_OPT_KEY_KEEP),
+	FUSE_OPT_KEY("subtype=",	FUSE_OPT_KEY_KEEP),
+	FUSE_OPT_END
+};
+
+static void usage(const char *progname)
+{
+	fprintf(stderr,
+		"usage: %s mountpoint [options]\n\n", progname);
+	fprintf(stderr,
+		"general options:\n"
+		"    -o opt,[opt...]        mount options\n"
+		"    -h   --help            print help\n"
+		"    -V   --version         print version\n"
+		"\n");
+}
+
+static void helper_help(void)
+{
+	fprintf(stderr,
+		"FUSE options:\n"
+		"    -d   -o debug          enable debug output (implies -f)\n"
+		"    -f                     foreground operation\n"
+		"    -s                     disable multi-threaded operation\n"
+		"\n"
+		);
+}
+
+static void helper_version(void)
+{
+	fprintf(stderr, "FUSE library version: %s\n", PACKAGE_VERSION);
+}
+
+static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+				struct fuse_args *outargs)
+{
+	struct helper_opts *hopts = data;
+
+	switch (key) {
+	case KEY_HELP:
+		usage(outargs->argv[0]);
+		/* fall through */
+
+	case KEY_HELP_NOHEADER:
+		helper_help();
+		return fuse_opt_add_arg(outargs, "-h");
+
+	case KEY_VERSION:
+		helper_version();
+		return 1;
+
+	case FUSE_OPT_KEY_NONOPT:
+		if (!hopts->mountpoint) {
+			char mountpoint[PATH_MAX];
+			if (realpath(arg, mountpoint) == NULL) {
+				fprintf(stderr,
+					"fuse: bad mount point `%s': %s\n",
+					arg, strerror(errno));
+				return -1;
+			}
+			return fuse_opt_add_opt(&hopts->mountpoint, mountpoint);
+		} else {
+			fprintf(stderr, "fuse: invalid argument `%s'\n", arg);
+			return -1;
+		}
+
+	default:
+		return 1;
+	}
+}
+
+static int add_default_subtype(const char *progname, struct fuse_args *args)
+{
+	int res;
+	char *subtype_opt;
+	const char *basename = strrchr(progname, '/');
+	if (basename == NULL)
+		basename = progname;
+	else if (basename[1] != '\0')
+		basename++;
+
+	subtype_opt = (char *) malloc(strlen(basename) + 64);
+	if (subtype_opt == NULL) {
+		fprintf(stderr, "fuse: memory allocation failed\n");
+		return -1;
+	}
+	sprintf(subtype_opt, "-osubtype=%s", basename);
+	res = fuse_opt_add_arg(args, subtype_opt);
+	free(subtype_opt);
+	return res;
+}
+
+int fuse_parse_cmdline(struct fuse_args *args, char **mountpoint,
+		       int *multithreaded, int *foreground)
+{
+	int res;
+	struct helper_opts hopts;
+
+	memset(&hopts, 0, sizeof(hopts));
+	res = fuse_opt_parse(args, &hopts, fuse_helper_opts,
+			     fuse_helper_opt_proc);
+	if (res == -1)
+		return -1;
+
+	if (!hopts.nodefault_subtype) {
+		res = add_default_subtype(args->argv[0], args);
+		if (res == -1)
+			goto err;
+	}
+	if (mountpoint)
+		*mountpoint = hopts.mountpoint;
+	else
+		free(hopts.mountpoint);
+
+	if (multithreaded)
+		*multithreaded = !hopts.singlethread;
+	if (foreground)
+		*foreground = hopts.foreground;
+	return 0;
+
+err:
+	free(hopts.mountpoint);
+	return -1;
+}
+
+int fuse_daemonize(int foreground)
+{
+	if (!foreground) {
+		int nullfd;
+
+		/*
+		 * demonize current process by forking it and killing the
+		 * parent.  This makes current process as a child of 'init'.
+		 */
+		switch(fork()) {
+		case -1:
+			perror("fuse_daemonize: fork");
+			return -1;
+		case 0:
+			break;
+		default:
+			_exit(0);
+		}
+
+		if (setsid() == -1) {
+			perror("fuse_daemonize: setsid");
+			return -1;
+		}
+
+		(void) chdir("/");
+
+		nullfd = open("/dev/null", O_RDWR, 0);
+		if (nullfd != -1) {
+			(void) dup2(nullfd, 0);
+			(void) dup2(nullfd, 1);
+			(void) dup2(nullfd, 2);
+			if (nullfd > 2)
+				close(nullfd);
+		}
+	}
+	return 0;
+}
+
+static struct fuse_chan *fuse_mount_common(const char *mountpoint,
+					   struct fuse_args *args)
+{
+	struct fuse_chan *ch;
+	int fd;
+
+	/*
+	 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+	 * would ensue.
+	 */
+	do {
+		fd = open("/dev/null", O_RDWR);
+		if (fd > 2)
+			close(fd);
+	} while (fd >= 0 && fd <= 2);
+
+	fd = fuse_mount_compat25(mountpoint, args);
+	if (fd == -1)
+		return NULL;
+
+	ch = fuse_kern_chan_new(fd);
+	if (!ch)
+		fuse_kern_unmount(mountpoint, fd);
+
+	return ch;
+}
+
+struct fuse_chan *fuse_mount(const char *mountpoint, struct fuse_args *args)
+{
+	return fuse_mount_common(mountpoint, args);
+}
+
+static void fuse_unmount_common(const char *mountpoint, struct fuse_chan *ch)
+{
+	if (mountpoint) {
+		int fd = ch ? fuse_chan_clearfd(ch) : -1;
+		fuse_kern_unmount(mountpoint, fd);
+		if (ch)
+			fuse_chan_destroy(ch);
+	}
+}
+
+void fuse_unmount(const char *mountpoint, struct fuse_chan *ch)
+{
+	fuse_unmount_common(mountpoint, ch);
+}
+
+struct fuse *fuse_setup_common(int argc, char *argv[],
+			       const struct fuse_operations *op,
+			       size_t op_size,
+			       char **mountpoint,
+			       int *multithreaded,
+			       int *fd,
+			       void *user_data,
+			       int compat)
+{
+	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+	struct fuse_chan *ch;
+	struct fuse *fuse;
+	int foreground;
+	int res;
+
+	res = fuse_parse_cmdline(&args, mountpoint, multithreaded, &foreground);
+	if (res == -1)
+		return NULL;
+
+	ch = fuse_mount_common(*mountpoint, &args);
+	if (!ch) {
+		fuse_opt_free_args(&args);
+		goto err_free;
+	}
+
+	fuse = fuse_new_common(ch, &args, op, op_size, user_data, compat);
+	fuse_opt_free_args(&args);
+	if (fuse == NULL)
+		goto err_unmount;
+
+	res = fuse_daemonize(foreground);
+	if (res == -1)
+		goto err_unmount;
+
+	res = fuse_set_signal_handlers(fuse_get_session(fuse));
+	if (res == -1)
+		goto err_unmount;
+
+	if (fd)
+		*fd = fuse_chan_fd(ch);
+
+	return fuse;
+
+err_unmount:
+	fuse_unmount_common(*mountpoint, ch);
+	if (fuse)
+		fuse_destroy(fuse);
+err_free:
+	free(*mountpoint);
+	return NULL;
+}
+
+struct fuse *fuse_setup(int argc, char *argv[],
+			const struct fuse_operations *op, size_t op_size,
+			char **mountpoint, int *multithreaded, void *user_data)
+{
+	return fuse_setup_common(argc, argv, op, op_size, mountpoint,
+				 multithreaded, NULL, user_data, 0);
+}
+
+static void fuse_teardown_common(struct fuse *fuse, char *mountpoint)
+{
+	struct fuse_session *se = fuse_get_session(fuse);
+	struct fuse_chan *ch = fuse_session_next_chan(se, NULL);
+	fuse_remove_signal_handlers(se);
+	fuse_unmount_common(mountpoint, ch);
+	fuse_destroy(fuse);
+	free(mountpoint);
+}
+
+void fuse_teardown(struct fuse *fuse, char *mountpoint)
+{
+	fuse_teardown_common(fuse, mountpoint);
+}
+
+static int fuse_main_common(int argc, char *argv[],
+			    const struct fuse_operations *op, size_t op_size,
+			    void *user_data, int compat)
+{
+	struct fuse *fuse;
+	char *mountpoint;
+	int multithreaded;
+	int res;
+
+	fuse = fuse_setup_common(argc, argv, op, op_size, &mountpoint,
+				 &multithreaded, NULL, user_data, compat);
+	if (fuse == NULL)
+		return 1;
+
+	if (multithreaded)
+		res = fuse_loop_mt(fuse);
+	else
+		res = fuse_loop(fuse);
+
+	fuse_teardown_common(fuse, mountpoint);
+	if (res == -1)
+		return 1;
+
+	return 0;
+}
+
+int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+		   size_t op_size, void *user_data)
+{
+	return fuse_main_common(argc, argv, op, op_size, user_data, 0);
+}
+
+#undef fuse_main
+int fuse_main(void);
+int fuse_main(void)
+{
+	fprintf(stderr, "fuse_main(): This function does not exist\n");
+	return -1;
+}
+
+int fuse_version(void)
+{
+	return FUSE_VERSION;
+}
+
+#include "fuse_compat.h"
+
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
+
+struct fuse *fuse_setup_compat22(int argc, char *argv[],
+				 const struct fuse_operations_compat22 *op,
+				 size_t op_size, char **mountpoint,
+				 int *multithreaded, int *fd)
+{
+	return fuse_setup_common(argc, argv, (struct fuse_operations *) op,
+				 op_size, mountpoint, multithreaded, fd, NULL,
+				 22);
+}
+
+struct fuse *fuse_setup_compat2(int argc, char *argv[],
+				const struct fuse_operations_compat2 *op,
+				char **mountpoint, int *multithreaded,
+				int *fd)
+{
+	return fuse_setup_common(argc, argv, (struct fuse_operations *) op,
+				 sizeof(struct fuse_operations_compat2),
+				 mountpoint, multithreaded, fd, NULL, 21);
+}
+
+int fuse_main_real_compat22(int argc, char *argv[],
+			    const struct fuse_operations_compat22 *op,
+			    size_t op_size)
+{
+	return fuse_main_common(argc, argv, (struct fuse_operations *) op,
+				op_size, NULL, 22);
+}
+
+void fuse_main_compat1(int argc, char *argv[],
+		       const struct fuse_operations_compat1 *op)
+{
+	fuse_main_common(argc, argv, (struct fuse_operations *) op,
+			 sizeof(struct fuse_operations_compat1), NULL, 11);
+}
+
+int fuse_main_compat2(int argc, char *argv[],
+		      const struct fuse_operations_compat2 *op)
+{
+	return fuse_main_common(argc, argv, (struct fuse_operations *) op,
+				sizeof(struct fuse_operations_compat2), NULL,
+				21);
+}
+
+int fuse_mount_compat1(const char *mountpoint, const char *args[])
+{
+	/* just ignore mount args for now */
+	(void) args;
+	return fuse_mount_compat22(mountpoint, NULL);
+}
+
+FUSE_SYMVER(".symver fuse_setup_compat2,__fuse_setup@FUSE_UNVERSIONED");
+FUSE_SYMVER(".symver fuse_setup_compat22,fuse_setup@FUSE_2.2");
+FUSE_SYMVER(".symver fuse_teardown,__fuse_teardown@FUSE_UNVERSIONED");
+FUSE_SYMVER(".symver fuse_main_compat2,fuse_main@FUSE_UNVERSIONED");
+FUSE_SYMVER(".symver fuse_main_real_compat22,fuse_main_real@FUSE_2.2");
+
+#endif /* __FreeBSD__ || __NetBSD__ */
+
+
+struct fuse *fuse_setup_compat25(int argc, char *argv[],
+				 const struct fuse_operations_compat25 *op,
+				 size_t op_size, char **mountpoint,
+				 int *multithreaded, int *fd)
+{
+	return fuse_setup_common(argc, argv, (struct fuse_operations *) op,
+				 op_size, mountpoint, multithreaded, fd, NULL,
+				 25);
+}
+
+int fuse_main_real_compat25(int argc, char *argv[],
+			    const struct fuse_operations_compat25 *op,
+			    size_t op_size)
+{
+	return fuse_main_common(argc, argv, (struct fuse_operations *) op,
+				op_size, NULL, 25);
+}
+
+void fuse_teardown_compat22(struct fuse *fuse, int fd, char *mountpoint)
+{
+	(void) fd;
+	fuse_teardown_common(fuse, mountpoint);
+}
+
+int fuse_mount_compat25(const char *mountpoint, struct fuse_args *args)
+{
+	return fuse_kern_mount(mountpoint, args);
+}
+
+FUSE_SYMVER(".symver fuse_setup_compat25,fuse_setup@FUSE_2.5");
+FUSE_SYMVER(".symver fuse_teardown_compat22,fuse_teardown@FUSE_2.2");
+FUSE_SYMVER(".symver fuse_main_real_compat25,fuse_main_real@FUSE_2.5");
+FUSE_SYMVER(".symver fuse_mount_compat25,fuse_mount@FUSE_2.5");
diff --git a/fuse/include/Makefile.am b/fuse/include/Makefile.am
new file mode 100644
index 0000000..663e164
--- /dev/null
+++ b/fuse/include/Makefile.am
@@ -0,0 +1,17 @@
+## Process this file with automake to produce Makefile.in
+
+fuseincludedir=$(includedir)/fuse
+
+fuseinclude_HEADERS = \
+	fuse.h			\
+	fuse_compat.h		\
+	fuse_common.h		\
+	fuse_common_compat.h    \
+	fuse_lowlevel.h		\
+	fuse_lowlevel_compat.h	\
+	fuse_opt.h		\
+	cuse_lowlevel.h
+
+include_HEADERS = old/fuse.h ulockmgr.h
+
+noinst_HEADERS = fuse_kernel.h
diff --git a/fuse/include/cuse_lowlevel.h b/fuse/include/cuse_lowlevel.h
new file mode 100644
index 0000000..fb445a7
--- /dev/null
+++ b/fuse/include/cuse_lowlevel.h
@@ -0,0 +1,87 @@
+/*
+  CUSE: Character device in Userspace
+  Copyright (C) 2008-2009  SUSE Linux Products GmbH
+  Copyright (C) 2008-2009  Tejun Heo <tj@kernel.org>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+
+  Read example/cusexmp.c for usages.
+*/
+
+#ifndef _CUSE_LOWLEVEL_H_
+#define _CUSE_LOWLEVEL_H_
+
+#ifndef FUSE_USE_VERSION
+#define FUSE_USE_VERSION 29
+#endif
+
+#include "fuse_lowlevel.h"
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CUSE_UNRESTRICTED_IOCTL		(1 << 0) /* use unrestricted ioctl */
+
+struct fuse_session;
+
+struct cuse_info {
+	unsigned	dev_major;
+	unsigned	dev_minor;
+	unsigned	dev_info_argc;
+	const char	**dev_info_argv;
+	unsigned	flags;
+};
+
+/*
+ * Most ops behave almost identically to the matching fuse_lowlevel
+ * ops except that they don't take @ino.
+ *
+ * init_done	: called after initialization is complete
+ * read/write	: always direct IO, simultaneous operations allowed
+ * ioctl	: might be in unrestricted mode depending on ci->flags
+ */
+struct cuse_lowlevel_ops {
+	void (*init) (void *userdata, struct fuse_conn_info *conn);
+	void (*init_done) (void *userdata);
+	void (*destroy) (void *userdata);
+	void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+	void (*read) (fuse_req_t req, size_t size, loff_t off,
+		      struct fuse_file_info *fi);
+	void (*write) (fuse_req_t req, const char *buf, size_t size, loff_t off,
+		       struct fuse_file_info *fi);
+	void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+	void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+	void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+	void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+		       struct fuse_file_info *fi, unsigned int flags,
+		       const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+	void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+		      struct fuse_pollhandle *ph);
+};
+
+struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+				       const struct cuse_info *ci,
+				       const struct cuse_lowlevel_ops *clop,
+				       void *userdata);
+
+struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+					 const struct cuse_info *ci,
+					 const struct cuse_lowlevel_ops *clop,
+					 int *multithreaded, void *userdata);
+
+void cuse_lowlevel_teardown(struct fuse_session *se);
+
+int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+		       const struct cuse_lowlevel_ops *clop, void *userdata);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CUSE_LOWLEVEL_H_ */
diff --git a/fuse/include/fuse.h b/fuse/include/fuse.h
new file mode 100644
index 0000000..b82325e
--- /dev/null
+++ b/fuse/include/fuse.h
@@ -0,0 +1,1064 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#ifndef _FUSE_H_
+#define _FUSE_H_
+
+/** @file
+ *
+ * This file defines the library interface of FUSE
+ *
+ * IMPORTANT: you should define FUSE_USE_VERSION before including this
+ * header.  To use the newest API define it to 26 (recommended for any
+ * new application), to use the old API define it to 21 (default) 22
+ * or 25, to use the even older 1.X API define it to 11.
+ */
+
+#ifndef FUSE_USE_VERSION
+#define FUSE_USE_VERSION 21
+#endif
+
+#include "fuse_common.h"
+
+#include <fcntl.h>
+#include <time.h>
+#include <utime.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#if defined(__ANDROID__)
+#include <pthread.h>
+#endif
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ----------------------------------------------------------- *
+ * Basic FUSE API					       *
+ * ----------------------------------------------------------- */
+
+/** Handle for a FUSE filesystem */
+struct fuse;
+
+/** Structure containing a raw command */
+struct fuse_cmd;
+
+/** Function to add an entry in a readdir() operation
+ *
+ * @param buf the buffer passed to the readdir() operation
+ * @param name the file name of the directory entry
+ * @param stat file attributes, can be NULL
+ * @param off offset of the next entry or zero
+ * @return 1 if buffer is full, zero otherwise
+ */
+typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+				const struct stat *stbuf, loff_t off);
+
+/* Used by deprecated getdir() method */
+typedef struct fuse_dirhandle *fuse_dirh_t;
+typedef int (*fuse_dirfil_t) (fuse_dirh_t h, const char *name, int type,
+			      ino_t ino);
+
+/**
+ * The file system operations:
+ *
+ * Most of these should work very similarly to the well known UNIX
+ * file system operations.  A major exception is that instead of
+ * returning an error in 'errno', the operation should return the
+ * negated error value (-errno) directly.
+ *
+ * All methods are optional, but some are essential for a useful
+ * filesystem (e.g. getattr).  Open, flush, release, fsync, opendir,
+ * releasedir, fsyncdir, access, create, ftruncate, fgetattr, lock,
+ * init and destroy are special purpose methods, without which a full
+ * featured filesystem can still be implemented.
+ *
+ * Almost all operations take a path which can be of any length.
+ *
+ * Changed in fuse 2.8.0 (regardless of API version)
+ * Previously, paths were limited to a length of PATH_MAX.
+ *
+ * See http://fuse.sourceforge.net/wiki/ for more information.  There
+ * is also a snapshot of the relevant wiki pages in the doc/ folder.
+ */
+struct fuse_operations {
+	/** Get file attributes.
+	 *
+	 * Similar to stat().  The 'st_dev' and 'st_blksize' fields are
+	 * ignored.	 The 'st_ino' field is ignored except if the 'use_ino'
+	 * mount option is given.
+	 */
+	int (*getattr) (const char *, struct stat *);
+
+	/** Read the target of a symbolic link
+	 *
+	 * The buffer should be filled with a null terminated string.  The
+	 * buffer size argument includes the space for the terminating
+	 * null character.	If the linkname is too long to fit in the
+	 * buffer, it should be truncated.	The return value should be 0
+	 * for success.
+	 */
+	int (*readlink) (const char *, char *, size_t);
+
+	/* Deprecated, use readdir() instead */
+	int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);
+
+	/** Create a file node
+	 *
+	 * This is called for creation of all non-directory, non-symlink
+	 * nodes.  If the filesystem defines a create() method, then for
+	 * regular files that will be called instead.
+	 */
+	int (*mknod) (const char *, mode_t, dev_t);
+
+	/** Create a directory 
+	 *
+	 * Note that the mode argument may not have the type specification
+	 * bits set, i.e. S_ISDIR(mode) can be false.  To obtain the
+	 * correct directory type bits use  mode|S_IFDIR
+	 * */
+	int (*mkdir) (const char *, mode_t);
+
+	/** Remove a file */
+	int (*unlink) (const char *);
+
+	/** Remove a directory */
+	int (*rmdir) (const char *);
+
+	/** Create a symbolic link */
+	int (*symlink) (const char *, const char *);
+
+	/** Rename a file */
+	int (*rename) (const char *, const char *);
+
+	/** Create a hard link to a file */
+	int (*link) (const char *, const char *);
+
+	/** Change the permission bits of a file */
+	int (*chmod) (const char *, mode_t);
+
+	/** Change the owner and group of a file */
+	int (*chown) (const char *, uid_t, gid_t);
+
+	/** Change the size of a file */
+	int (*truncate) (const char *, loff_t);
+
+	/** Change the access and/or modification times of a file
+	 *
+	 * Deprecated, use utimens() instead.
+	 */
+	int (*utime) (const char *, struct utimbuf *);
+
+	/** File open operation
+	 *
+	 * No creation (O_CREAT, O_EXCL) and by default also no
+	 * truncation (O_TRUNC) flags will be passed to open(). If an
+	 * application specifies O_TRUNC, fuse first calls truncate()
+	 * and then open(). Only if 'atomic_o_trunc' has been
+	 * specified and kernel version is 2.6.24 or later, O_TRUNC is
+	 * passed on to open.
+	 *
+	 * Unless the 'default_permissions' mount option is given,
+	 * open should check if the operation is permitted for the
+	 * given flags. Optionally open may also return an arbitrary
+	 * filehandle in the fuse_file_info structure, which will be
+	 * passed to all file operations.
+	 *
+	 * Changed in version 2.2
+	 */
+	int (*open) (const char *, struct fuse_file_info *);
+
+	/** Read data from an open file
+	 *
+	 * Read should return exactly the number of bytes requested except
+	 * on EOF or error, otherwise the rest of the data will be
+	 * substituted with zeroes.	 An exception to this is when the
+	 * 'direct_io' mount option is specified, in which case the return
+	 * value of the read system call will reflect the return value of
+	 * this operation.
+	 *
+	 * Changed in version 2.2
+	 */
+	int (*read) (const char *, char *, size_t, loff_t,
+		     struct fuse_file_info *);
+
+	/** Write data to an open file
+	 *
+	 * Write should return exactly the number of bytes requested
+	 * except on error.	 An exception to this is when the 'direct_io'
+	 * mount option is specified (see read operation).
+	 *
+	 * Changed in version 2.2
+	 */
+	int (*write) (const char *, const char *, size_t, loff_t,
+		      struct fuse_file_info *);
+
+	/** Get file system statistics
+	 *
+	 * The 'f_frsize', 'f_favail', 'f_fsid' and 'f_flag' fields are ignored
+	 *
+	 * Replaced 'struct statfs' parameter with 'struct statvfs' in
+	 * version 2.5
+	 */
+	int (*statfs) (const char *, struct statvfs *);
+
+	/** Possibly flush cached data
+	 *
+	 * BIG NOTE: This is not equivalent to fsync().  It's not a
+	 * request to sync dirty data.
+	 *
+	 * Flush is called on each close() of a file descriptor.  So if a
+	 * filesystem wants to return write errors in close() and the file
+	 * has cached dirty data, this is a good place to write back data
+	 * and return any errors.  Since many applications ignore close()
+	 * errors this is not always useful.
+	 *
+	 * NOTE: The flush() method may be called more than once for each
+	 * open().	This happens if more than one file descriptor refers
+	 * to an opened file due to dup(), dup2() or fork() calls.	It is
+	 * not possible to determine if a flush is final, so each flush
+	 * should be treated equally.  Multiple write-flush sequences are
+	 * relatively rare, so this shouldn't be a problem.
+	 *
+	 * Filesystems shouldn't assume that flush will always be called
+	 * after some writes, or that if will be called at all.
+	 *
+	 * Changed in version 2.2
+	 */
+	int (*flush) (const char *, struct fuse_file_info *);
+
+	/** Release an open file
+	 *
+	 * Release is called when there are no more references to an open
+	 * file: all file descriptors are closed and all memory mappings
+	 * are unmapped.
+	 *
+	 * For every open() call there will be exactly one release() call
+	 * with the same flags and file descriptor.	 It is possible to
+	 * have a file opened more than once, in which case only the last
+	 * release will mean, that no more reads/writes will happen on the
+	 * file.  The return value of release is ignored.
+	 *
+	 * Changed in version 2.2
+	 */
+	int (*release) (const char *, struct fuse_file_info *);
+
+	/** Synchronize file contents
+	 *
+	 * If the datasync parameter is non-zero, then only the user data
+	 * should be flushed, not the meta data.
+	 *
+	 * Changed in version 2.2
+	 */
+	int (*fsync) (const char *, int, struct fuse_file_info *);
+
+	/** Set extended attributes */
+	int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
+	/** Get extended attributes */
+	int (*getxattr) (const char *, const char *, char *, size_t);
+
+	/** List extended attributes */
+	int (*listxattr) (const char *, char *, size_t);
+
+	/** Remove extended attributes */
+	int (*removexattr) (const char *, const char *);
+
+	/** Open directory
+	 *
+	 * Unless the 'default_permissions' mount option is given,
+	 * this method should check if opendir is permitted for this
+	 * directory. Optionally opendir may also return an arbitrary
+	 * filehandle in the fuse_file_info structure, which will be
+	 * passed to readdir, closedir and fsyncdir.
+	 *
+	 * Introduced in version 2.3
+	 */
+	int (*opendir) (const char *, struct fuse_file_info *);
+
+	/** Read directory
+	 *
+	 * This supersedes the old getdir() interface.  New applications
+	 * should use this.
+	 *
+	 * The filesystem may choose between two modes of operation:
+	 *
+	 * 1) The readdir implementation ignores the offset parameter, and
+	 * passes zero to the filler function's offset.  The filler
+	 * function will not return '1' (unless an error happens), so the
+	 * whole directory is read in a single readdir operation.  This
+	 * works just like the old getdir() method.
+	 *
+	 * 2) The readdir implementation keeps track of the offsets of the
+	 * directory entries.  It uses the offset parameter and always
+	 * passes non-zero offset to the filler function.  When the buffer
+	 * is full (or an error happens) the filler function will return
+	 * '1'.
+	 *
+	 * Introduced in version 2.3
+	 */
+	int (*readdir) (const char *, void *, fuse_fill_dir_t, loff_t,
+			struct fuse_file_info *);
+
+	/** Release directory
+	 *
+	 * Introduced in version 2.3
+	 */
+	int (*releasedir) (const char *, struct fuse_file_info *);
+
+	/** Synchronize directory contents
+	 *
+	 * If the datasync parameter is non-zero, then only the user data
+	 * should be flushed, not the meta data
+	 *
+	 * Introduced in version 2.3
+	 */
+	int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
+	/**
+	 * Initialize filesystem
+	 *
+	 * The return value will passed in the private_data field of
+	 * fuse_context to all file operations and as a parameter to the
+	 * destroy() method.
+	 *
+	 * Introduced in version 2.3
+	 * Changed in version 2.6
+	 */
+	void *(*init) (struct fuse_conn_info *conn);
+
+	/**
+	 * Clean up filesystem
+	 *
+	 * Called on filesystem exit.
+	 *
+	 * Introduced in version 2.3
+	 */
+	void (*destroy) (void *);
+
+	/**
+	 * Check file access permissions
+	 *
+	 * This will be called for the access() system call.  If the
+	 * 'default_permissions' mount option is given, this method is not
+	 * called.
+	 *
+	 * This method is not called under Linux kernel versions 2.4.x
+	 *
+	 * Introduced in version 2.5
+	 */
+	int (*access) (const char *, int);
+
+	/**
+	 * Create and open a file
+	 *
+	 * If the file does not exist, first create it with the specified
+	 * mode, and then open it.
+	 *
+	 * If this method is not implemented or under Linux kernel
+	 * versions earlier than 2.6.15, the mknod() and open() methods
+	 * will be called instead.
+	 *
+	 * Introduced in version 2.5
+	 */
+	int (*create) (const char *, mode_t, struct fuse_file_info *);
+
+	/**
+	 * Change the size of an open file
+	 *
+	 * This method is called instead of the truncate() method if the
+	 * truncation was invoked from an ftruncate() system call.
+	 *
+	 * If this method is not implemented or under Linux kernel
+	 * versions earlier than 2.6.15, the truncate() method will be
+	 * called instead.
+	 *
+	 * Introduced in version 2.5
+	 */
+	int (*ftruncate) (const char *, loff_t, struct fuse_file_info *);
+
+	/**
+	 * Get attributes from an open file
+	 *
+	 * This method is called instead of the getattr() method if the
+	 * file information is available.
+	 *
+	 * Currently this is only called after the create() method if that
+	 * is implemented (see above).  Later it may be called for
+	 * invocations of fstat() too.
+	 *
+	 * Introduced in version 2.5
+	 */
+	int (*fgetattr) (const char *, struct stat *, struct fuse_file_info *);
+
+	/**
+	 * Perform POSIX file locking operation
+	 *
+	 * The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW.
+	 *
+	 * For the meaning of fields in 'struct flock' see the man page
+	 * for fcntl(2).  The l_whence field will always be set to
+	 * SEEK_SET.
+	 *
+	 * For checking lock ownership, the 'fuse_file_info->owner'
+	 * argument must be used.
+	 *
+	 * For F_GETLK operation, the library will first check currently
+	 * held locks, and if a conflicting lock is found it will return
+	 * information without calling this method.	 This ensures, that
+	 * for local locks the l_pid field is correctly filled in.	The
+	 * results may not be accurate in case of race conditions and in
+	 * the presence of hard links, but it's unlikely that an
+	 * application would rely on accurate GETLK results in these
+	 * cases.  If a conflicting lock is not found, this method will be
+	 * called, and the filesystem may fill out l_pid by a meaningful
+	 * value, or it may leave this field zero.
+	 *
+	 * For F_SETLK and F_SETLKW the l_pid field will be set to the pid
+	 * of the process performing the locking operation.
+	 *
+	 * Note: if this method is not implemented, the kernel will still
+	 * allow file locking to work locally.  Hence it is only
+	 * interesting for network filesystems and similar.
+	 *
+	 * Introduced in version 2.6
+	 */
+	int (*lock) (const char *, struct fuse_file_info *, int cmd,
+		     struct flock *);
+
+	/**
+	 * Change the access and modification times of a file with
+	 * nanosecond resolution
+	 *
+	 * This supersedes the old utime() interface.  New applications
+	 * should use this.
+	 *
+	 * See the utimensat(2) man page for details.
+	 *
+	 * Introduced in version 2.6
+	 */
+	int (*utimens) (const char *, const struct timespec tv[2]);
+
+	/**
+	 * Map block index within file to block index within device
+	 *
+	 * Note: This makes sense only for block device backed filesystems
+	 * mounted with the 'blkdev' option
+	 *
+	 * Introduced in version 2.6
+	 */
+	int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
+	/**
+	 * Flag indicating that the filesystem can accept a NULL path
+	 * as the first argument for the following operations:
+	 *
+	 * read, write, flush, release, fsync, readdir, releasedir,
+	 * fsyncdir, ftruncate, fgetattr, lock, ioctl and poll
+	 *
+	 * If this flag is set these operations continue to work on
+	 * unlinked files even if "-ohard_remove" option was specified.
+	 */
+	unsigned int flag_nullpath_ok:1;
+
+	/**
+	 * Flag indicating that the path need not be calculated for
+	 * the following operations:
+	 *
+	 * read, write, flush, release, fsync, readdir, releasedir,
+	 * fsyncdir, ftruncate, fgetattr, lock, ioctl and poll
+	 *
+	 * Closely related to flag_nullpath_ok, but if this flag is
+	 * set then the path will not be calculaged even if the file
+	 * wasn't unlinked.  However the path can still be non-NULL if
+	 * it needs to be calculated for some other reason.
+	 */
+	unsigned int flag_nopath:1;
+
+	/**
+	 * Flag indicating that the filesystem accepts special
+	 * UTIME_NOW and UTIME_OMIT values in its utimens operation.
+	 */
+	unsigned int flag_utime_omit_ok:1;
+
+	/**
+	 * Reserved flags, don't set
+	 */
+	unsigned int flag_reserved:29;
+
+	/**
+	 * Ioctl
+	 *
+	 * flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in
+	 * 64bit environment.  The size and direction of data is
+	 * determined by _IOC_*() decoding of cmd.  For _IOC_NONE,
+	 * data will be NULL, for _IOC_WRITE data is out area, for
+	 * _IOC_READ in area and if both are set in/out area.  In all
+	 * non-NULL cases, the area is of _IOC_SIZE(cmd) bytes.
+	 *
+	 * If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a
+	 * directory file handle.
+	 *
+	 * Introduced in version 2.8
+	 */
+	int (*ioctl) (const char *, int cmd, void *arg,
+		      struct fuse_file_info *, unsigned int flags, void *data);
+
+	/**
+	 * Poll for IO readiness events
+	 *
+	 * Note: If ph is non-NULL, the client should notify
+	 * when IO readiness events occur by calling
+	 * fuse_notify_poll() with the specified ph.
+	 *
+	 * Regardless of the number of times poll with a non-NULL ph
+	 * is received, single notification is enough to clear all.
+	 * Notifying more times incurs overhead but doesn't harm
+	 * correctness.
+	 *
+	 * The callee is responsible for destroying ph with
+	 * fuse_pollhandle_destroy() when no longer in use.
+	 *
+	 * Introduced in version 2.8
+	 */
+	int (*poll) (const char *, struct fuse_file_info *,
+		     struct fuse_pollhandle *ph, unsigned *reventsp);
+
+	/** Write contents of buffer to an open file
+	 *
+	 * Similar to the write() method, but data is supplied in a
+	 * generic buffer.  Use fuse_buf_copy() to transfer data to
+	 * the destination.
+	 *
+	 * Introduced in version 2.9
+	 */
+	int (*write_buf) (const char *, struct fuse_bufvec *buf, loff_t off,
+			  struct fuse_file_info *);
+
+	/** Store data from an open file in a buffer
+	 *
+	 * Similar to the read() method, but data is stored and
+	 * returned in a generic buffer.
+	 *
+	 * No actual copying of data has to take place, the source
+	 * file descriptor may simply be stored in the buffer for
+	 * later data transfer.
+	 *
+	 * The buffer must be allocated dynamically and stored at the
+	 * location pointed to by bufp.  If the buffer contains memory
+	 * regions, they too must be allocated using malloc().  The
+	 * allocated memory will be freed by the caller.
+	 *
+	 * Introduced in version 2.9
+	 */
+	int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+			 size_t size, loff_t off, struct fuse_file_info *);
+	/**
+	 * Perform BSD file locking operation
+	 *
+	 * The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN
+	 *
+	 * Nonblocking requests will be indicated by ORing LOCK_NB to
+	 * the above operations
+	 *
+	 * For more information see the flock(2) manual page.
+	 *
+	 * Additionally fi->owner will be set to a value unique to
+	 * this open file.  This same value will be supplied to
+	 * ->release() when the file is released.
+	 *
+	 * Note: if this method is not implemented, the kernel will still
+	 * allow file locking to work locally.  Hence it is only
+	 * interesting for network filesystems and similar.
+	 *
+	 * Introduced in version 2.9
+	 */
+	int (*flock) (const char *, struct fuse_file_info *, int op);
+
+	/**
+	 * Allocates space for an open file
+	 *
+	 * This function ensures that required space is allocated for specified
+	 * file.  If this function returns success then any subsequent write
+	 * request to specified range is guaranteed not to fail because of lack
+	 * of space on the file system media.
+	 *
+	 * Introduced in version 2.9.1
+	 */
+	int (*fallocate) (const char *, int, loff_t, loff_t,
+			  struct fuse_file_info *);
+};
+
+/** Extra context that may be needed by some filesystems
+ *
+ * The uid, gid and pid fields are not filled in case of a writepage
+ * operation.
+ */
+struct fuse_context {
+	/** Pointer to the fuse object */
+	struct fuse *fuse;
+
+	/** User ID of the calling process */
+	uid_t uid;
+
+	/** Group ID of the calling process */
+	gid_t gid;
+
+	/** Thread ID of the calling process */
+	pid_t pid;
+
+	/** Private filesystem data */
+	void *private_data;
+
+	/** Umask of the calling process (introduced in version 2.8) */
+	mode_t umask;
+};
+
+/**
+ * Main function of FUSE.
+ *
+ * This is for the lazy.  This is all that has to be called from the
+ * main() function.
+ *
+ * This function does the following:
+ *   - parses command line options (-d -s and -h)
+ *   - passes relevant mount options to the fuse_mount()
+ *   - installs signal handlers for INT, HUP, TERM and PIPE
+ *   - registers an exit handler to unmount the filesystem on program exit
+ *   - creates a fuse handle
+ *   - registers the operations
+ *   - calls either the single-threaded or the multi-threaded event loop
+ *
+ * Note: this is currently implemented as a macro.
+ *
+ * @param argc the argument counter passed to the main() function
+ * @param argv the argument vector passed to the main() function
+ * @param op the file system operation
+ * @param user_data user data supplied in the context during the init() method
+ * @return 0 on success, nonzero on failure
+ */
+/*
+  int fuse_main(int argc, char *argv[], const struct fuse_operations *op,
+  void *user_data);
+*/
+#define fuse_main(argc, argv, op, user_data)				\
+	fuse_main_real(argc, argv, op, sizeof(*(op)), user_data)
+
+/* ----------------------------------------------------------- *
+ * More detailed API					       *
+ * ----------------------------------------------------------- */
+
+/**
+ * Create a new FUSE filesystem.
+ *
+ * @param ch the communication channel
+ * @param args argument vector
+ * @param op the filesystem operations
+ * @param op_size the size of the fuse_operations structure
+ * @param user_data user data supplied in the context during the init() method
+ * @return the created FUSE handle
+ */
+struct fuse *fuse_new(struct fuse_chan *ch, struct fuse_args *args,
+		      const struct fuse_operations *op, size_t op_size,
+		      void *user_data);
+
+/**
+ * Destroy the FUSE handle.
+ *
+ * The communication channel attached to the handle is also destroyed.
+ *
+ * NOTE: This function does not unmount the filesystem.	 If this is
+ * needed, call fuse_unmount() before calling this function.
+ *
+ * @param f the FUSE handle
+ */
+void fuse_destroy(struct fuse *f);
+
+/**
+ * FUSE event loop.
+ *
+ * Requests from the kernel are processed, and the appropriate
+ * operations are called.
+ *
+ * @param f the FUSE handle
+ * @return 0 if no error occurred, -1 otherwise
+ */
+int fuse_loop(struct fuse *f);
+
+/**
+ * Exit from event loop
+ *
+ * @param f the FUSE handle
+ */
+void fuse_exit(struct fuse *f);
+
+/**
+ * FUSE event loop with multiple threads
+ *
+ * Requests from the kernel are processed, and the appropriate
+ * operations are called.  Request are processed in parallel by
+ * distributing them between multiple threads.
+ *
+ * Calling this function requires the pthreads library to be linked to
+ * the application.
+ *
+ * @param f the FUSE handle
+ * @return 0 if no error occurred, -1 otherwise
+ */
+int fuse_loop_mt(struct fuse *f);
+
+/**
+ * Get the current context
+ *
+ * The context is only valid for the duration of a filesystem
+ * operation, and thus must not be stored and used later.
+ *
+ * @return the context
+ */
+struct fuse_context *fuse_get_context(void);
+
+/**
+ * Get the current supplementary group IDs for the current request
+ *
+ * Similar to the getgroups(2) system call, except the return value is
+ * always the total number of group IDs, even if it is larger than the
+ * specified size.
+ *
+ * The current fuse kernel module in linux (as of 2.6.30) doesn't pass
+ * the group list to userspace, hence this function needs to parse
+ * "/proc/$TID/task/$TID/status" to get the group IDs.
+ *
+ * This feature may not be supported on all operating systems.  In
+ * such a case this function will return -ENOSYS.
+ *
+ * @param size size of given array
+ * @param list array of group IDs to be filled in
+ * @return the total number of supplementary group IDs or -errno on failure
+ */
+int fuse_getgroups(int size, gid_t list[]);
+
+/**
+ * Check if the current request has already been interrupted
+ *
+ * @return 1 if the request has been interrupted, 0 otherwise
+ */
+int fuse_interrupted(void);
+
+/**
+ * Obsolete, doesn't do anything
+ *
+ * @return -EINVAL
+ */
+int fuse_invalidate(struct fuse *f, const char *path);
+
+/* Deprecated, don't use */
+int fuse_is_lib_option(const char *opt);
+
+/**
+ * The real main function
+ *
+ * Do not call this directly, use fuse_main()
+ */
+int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+		   size_t op_size, void *user_data);
+
+/**
+ * Start the cleanup thread when using option "remember".
+ *
+ * This is done automatically by fuse_loop_mt()
+ * @param fuse struct fuse pointer for fuse instance
+ * @return 0 on success and -1 on error
+ */
+int fuse_start_cleanup_thread(struct fuse *fuse);
+
+/**
+ * Stop the cleanup thread when using option "remember".
+ *
+ * This is done automatically by fuse_loop_mt()
+ * @param fuse struct fuse pointer for fuse instance
+ */
+void fuse_stop_cleanup_thread(struct fuse *fuse);
+
+/**
+ * Iterate over cache removing stale entries
+ * use in conjunction with "-oremember"
+ *
+ * NOTE: This is already done for the standard sessions
+ *
+ * @param fuse struct fuse pointer for fuse instance
+ * @return the number of seconds until the next cleanup
+ */
+int fuse_clean_cache(struct fuse *fuse);
+
+/*
+ * Stacking API
+ */
+
+/**
+ * Fuse filesystem object
+ *
+ * This is opaque object represents a filesystem layer
+ */
+struct fuse_fs;
+
+/*
+ * These functions call the relevant filesystem operation, and return
+ * the result.
+ *
+ * If the operation is not defined, they return -ENOSYS, with the
+ * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+ * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+ */
+
+int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf);
+int fuse_fs_fgetattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+		     struct fuse_file_info *fi);
+int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+		   const char *newpath);
+int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+		    const char *path);
+int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+int fuse_fs_release(struct fuse_fs *fs,	 const char *path,
+		    struct fuse_file_info *fi);
+int fuse_fs_open(struct fuse_fs *fs, const char *path,
+		 struct fuse_file_info *fi);
+int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+		 loff_t off, struct fuse_file_info *fi);
+int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+		     struct fuse_bufvec **bufp, size_t size, loff_t off,
+		     struct fuse_file_info *fi);
+int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+		  size_t size, loff_t off, struct fuse_file_info *fi);
+int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+		      struct fuse_bufvec *buf, loff_t off,
+		      struct fuse_file_info *fi);
+int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+		  struct fuse_file_info *fi);
+int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+		  struct fuse_file_info *fi);
+int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+		    struct fuse_file_info *fi);
+int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+		    fuse_fill_dir_t filler, loff_t off,
+		    struct fuse_file_info *fi);
+int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+		     struct fuse_file_info *fi);
+int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+		       struct fuse_file_info *fi);
+int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+		   struct fuse_file_info *fi);
+int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+		 struct fuse_file_info *fi, int cmd, struct flock *lock);
+int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+		  struct fuse_file_info *fi, int op);
+int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode);
+int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid);
+int fuse_fs_truncate(struct fuse_fs *fs, const char *path, loff_t size);
+int fuse_fs_ftruncate(struct fuse_fs *fs, const char *path, loff_t size,
+		      struct fuse_file_info *fi);
+int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+		    const struct timespec tv[2]);
+int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+		     size_t len);
+int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+		  dev_t rdev);
+int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+		     const char *value, size_t size, int flags);
+int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+		     char *value, size_t size);
+int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+		      size_t size);
+int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+			const char *name);
+int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+		 uint64_t *idx);
+int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, void *arg,
+		  struct fuse_file_info *fi, unsigned int flags, void *data);
+int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+		 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+		 unsigned *reventsp);
+int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+		 loff_t offset, loff_t length, struct fuse_file_info *fi);
+void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn);
+void fuse_fs_destroy(struct fuse_fs *fs);
+
+int fuse_notify_poll(struct fuse_pollhandle *ph);
+
+/**
+ * Create a new fuse filesystem object
+ *
+ * This is usually called from the factory of a fuse module to create
+ * a new instance of a filesystem.
+ *
+ * @param op the filesystem operations
+ * @param op_size the size of the fuse_operations structure
+ * @param user_data user data supplied in the context during the init() method
+ * @return a new filesystem object
+ */
+struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+			    void *user_data);
+
+/**
+ * Filesystem module
+ *
+ * Filesystem modules are registered with the FUSE_REGISTER_MODULE()
+ * macro.
+ *
+ * If the "-omodules=modname:..." option is present, filesystem
+ * objects are created and pushed onto the stack with the 'factory'
+ * function.
+ */
+struct fuse_module {
+	/**
+	 * Name of filesystem
+	 */
+	const char *name;
+
+	/**
+	 * Factory for creating filesystem objects
+	 *
+	 * The function may use and remove options from 'args' that belong
+	 * to this module.
+	 *
+	 * For now the 'fs' vector always contains exactly one filesystem.
+	 * This is the filesystem which will be below the newly created
+	 * filesystem in the stack.
+	 *
+	 * @param args the command line arguments
+	 * @param fs NULL terminated filesystem object vector
+	 * @return the new filesystem object
+	 */
+	struct fuse_fs *(*factory)(struct fuse_args *args,
+				   struct fuse_fs *fs[]);
+
+	struct fuse_module *next;
+	struct fusemod_so *so;
+	int ctr;
+};
+
+/**
+ * Register a filesystem module
+ *
+ * This function is used by FUSE_REGISTER_MODULE and there's usually
+ * no need to call it directly
+ */
+void fuse_register_module(struct fuse_module *mod);
+
+/**
+ * Register filesystem module
+ *
+ * For the parameters, see description of the fields in 'struct
+ * fuse_module'
+ */
+#define FUSE_REGISTER_MODULE(name_, factory_)				  \
+	static __attribute__((constructor)) void name_ ## _register(void) \
+	{								  \
+		static struct fuse_module mod =				  \
+			{ #name_, factory_, NULL, NULL, 0 };		  \
+		fuse_register_module(&mod);				  \
+	}
+
+
+/* ----------------------------------------------------------- *
+ * Advanced API for event handling, don't worry about this...  *
+ * ----------------------------------------------------------- */
+
+/* NOTE: the following functions are deprecated, and will be removed
+   from the 3.0 API.  Use the lowlevel session functions instead */
+
+/** Function type used to process commands */
+typedef void (*fuse_processor_t)(struct fuse *, struct fuse_cmd *, void *);
+
+/** This is the part of fuse_main() before the event loop */
+struct fuse *fuse_setup(int argc, char *argv[],
+			const struct fuse_operations *op, size_t op_size,
+			char **mountpoint, int *multithreaded,
+			void *user_data);
+
+/** This is the part of fuse_main() after the event loop */
+void fuse_teardown(struct fuse *fuse, char *mountpoint);
+
+/** Read a single command.  If none are read, return NULL */
+struct fuse_cmd *fuse_read_cmd(struct fuse *f);
+
+/** Process a single command */
+void fuse_process_cmd(struct fuse *f, struct fuse_cmd *cmd);
+
+/** Multi threaded event loop, which calls the custom command
+    processor function */
+int fuse_loop_mt_proc(struct fuse *f, fuse_processor_t proc, void *data);
+
+/** Return the exited flag, which indicates if fuse_exit() has been
+    called */
+int fuse_exited(struct fuse *f);
+
+/** This function is obsolete and implemented as a no-op */
+void fuse_set_getcontext_func(struct fuse_context *(*func)(void));
+
+/** Get session from fuse object */
+struct fuse_session *fuse_get_session(struct fuse *f);
+
+/* ----------------------------------------------------------- *
+ * Compatibility stuff					       *
+ * ----------------------------------------------------------- */
+
+#if FUSE_USE_VERSION < 26
+#  include "fuse_compat.h"
+#  undef fuse_main
+#  if FUSE_USE_VERSION == 25
+#    define fuse_main(argc, argv, op)				\
+	fuse_main_real_compat25(argc, argv, op, sizeof(*(op)))
+#    define fuse_new fuse_new_compat25
+#    define fuse_setup fuse_setup_compat25
+#    define fuse_teardown fuse_teardown_compat22
+#    define fuse_operations fuse_operations_compat25
+#  elif FUSE_USE_VERSION == 22
+#    define fuse_main(argc, argv, op)				\
+	fuse_main_real_compat22(argc, argv, op, sizeof(*(op)))
+#    define fuse_new fuse_new_compat22
+#    define fuse_setup fuse_setup_compat22
+#    define fuse_teardown fuse_teardown_compat22
+#    define fuse_operations fuse_operations_compat22
+#    define fuse_file_info fuse_file_info_compat
+#  elif FUSE_USE_VERSION == 24
+#    error Compatibility with high-level API version 24 not supported
+#  else
+#    define fuse_dirfil_t fuse_dirfil_t_compat
+#    define __fuse_read_cmd fuse_read_cmd
+#    define __fuse_process_cmd fuse_process_cmd
+#    define __fuse_loop_mt fuse_loop_mt_proc
+#    if FUSE_USE_VERSION == 21
+#      define fuse_operations fuse_operations_compat2
+#      define fuse_main fuse_main_compat2
+#      define fuse_new fuse_new_compat2
+#      define __fuse_setup fuse_setup_compat2
+#      define __fuse_teardown fuse_teardown_compat22
+#      define __fuse_exited fuse_exited
+#      define __fuse_set_getcontext_func fuse_set_getcontext_func
+#    else
+#      define fuse_statfs fuse_statfs_compat1
+#      define fuse_operations fuse_operations_compat1
+#      define fuse_main fuse_main_compat1
+#      define fuse_new fuse_new_compat1
+#      define FUSE_DEBUG FUSE_DEBUG_COMPAT1
+#    endif
+#  endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FUSE_H_ */
diff --git a/fuse/include/fuse_common.h b/fuse/include/fuse_common.h
new file mode 100644
index 0000000..f08778b
--- /dev/null
+++ b/fuse/include/fuse_common.h
@@ -0,0 +1,505 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+/** @file */
+
+#if !defined(_FUSE_H_) && !defined(_FUSE_LOWLEVEL_H_)
+#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+#endif
+
+#ifndef _FUSE_COMMON_H_
+#define _FUSE_COMMON_H_
+
+#include "fuse_opt.h"
+#include <stdint.h>
+#include <sys/types.h>
+
+/** Major version of FUSE library interface */
+#define FUSE_MAJOR_VERSION 2
+
+/** Minor version of FUSE library interface */
+#define FUSE_MINOR_VERSION 9
+
+#define FUSE_MAKE_VERSION(maj, min)  ((maj) * 10 + (min))
+#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
+/* This interface uses 64 bit off_t */
+#if _FILE_OFFSET_BITS != 64
+#error Please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Information about open files
+ *
+ * Changed in version 2.5
+ */
+struct fuse_file_info {
+	/** Open flags.	 Available in open() and release() */
+	int flags;
+
+	/** Old file handle, don't use */
+	unsigned long fh_old;
+
+	/** In case of a write operation indicates if this was caused by a
+	    writepage */
+	int writepage;
+
+	/** Can be filled in by open, to use direct I/O on this file.
+	    Introduced in version 2.4 */
+	unsigned int direct_io : 1;
+
+	/** Can be filled in by open, to indicate, that cached file data
+	    need not be invalidated.  Introduced in version 2.4 */
+	unsigned int keep_cache : 1;
+
+	/** Indicates a flush operation.  Set in flush operation, also
+	    maybe set in highlevel lock operation and lowlevel release
+	    operation.	Introduced in version 2.6 */
+	unsigned int flush : 1;
+
+	/** Can be filled in by open, to indicate that the file is not
+	    seekable.  Introduced in version 2.8 */
+	unsigned int nonseekable : 1;
+
+	/* Indicates that flock locks for this file should be
+	   released.  If set, lock_owner shall contain a valid value.
+	   May only be set in ->release().  Introduced in version
+	   2.9 */
+	unsigned int flock_release : 1;
+
+	/** Padding.  Do not use*/
+	unsigned int padding : 27;
+
+	/** File handle.  May be filled in by filesystem in open().
+	    Available in all other file operations */
+	uint64_t fh;
+
+	/** Lock owner id.  Available in locking operations and flush */
+	uint64_t lock_owner;
+};
+
+/**
+ * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want'
+ *
+ * FUSE_CAP_ASYNC_READ: filesystem supports asynchronous read requests
+ * FUSE_CAP_POSIX_LOCKS: filesystem supports "remote" locking
+ * FUSE_CAP_ATOMIC_O_TRUNC: filesystem handles the O_TRUNC open flag
+ * FUSE_CAP_EXPORT_SUPPORT: filesystem handles lookups of "." and ".."
+ * FUSE_CAP_BIG_WRITES: filesystem can handle write size larger than 4kB
+ * FUSE_CAP_DONT_MASK: don't apply umask to file mode on create operations
+ * FUSE_CAP_SPLICE_WRITE: ability to use splice() to write to the fuse device
+ * FUSE_CAP_SPLICE_MOVE: ability to move data to the fuse device with splice()
+ * FUSE_CAP_SPLICE_READ: ability to use splice() to read from the fuse device
+ * FUSE_CAP_IOCTL_DIR: ioctl support on directories
+ */
+#define FUSE_CAP_ASYNC_READ	(1 << 0)
+#define FUSE_CAP_POSIX_LOCKS	(1 << 1)
+#define FUSE_CAP_ATOMIC_O_TRUNC	(1 << 3)
+#define FUSE_CAP_EXPORT_SUPPORT	(1 << 4)
+#define FUSE_CAP_BIG_WRITES	(1 << 5)
+#define FUSE_CAP_DONT_MASK	(1 << 6)
+#define FUSE_CAP_SPLICE_WRITE	(1 << 7)
+#define FUSE_CAP_SPLICE_MOVE	(1 << 8)
+#define FUSE_CAP_SPLICE_READ	(1 << 9)
+#define FUSE_CAP_FLOCK_LOCKS	(1 << 10)
+#define FUSE_CAP_IOCTL_DIR	(1 << 11)
+
+/**
+ * Ioctl flags
+ *
+ * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine
+ * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed
+ * FUSE_IOCTL_RETRY: retry with new iovecs
+ * FUSE_IOCTL_DIR: is a directory
+ *
+ * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs
+ */
+#define FUSE_IOCTL_COMPAT	(1 << 0)
+#define FUSE_IOCTL_UNRESTRICTED	(1 << 1)
+#define FUSE_IOCTL_RETRY	(1 << 2)
+#define FUSE_IOCTL_DIR		(1 << 4)
+
+#define FUSE_IOCTL_MAX_IOV	256
+
+/**
+ * Connection information, passed to the ->init() method
+ *
+ * Some of the elements are read-write, these can be changed to
+ * indicate the value requested by the filesystem.  The requested
+ * value must usually be smaller than the indicated value.
+ */
+struct fuse_conn_info {
+	/**
+	 * Major version of the protocol (read-only)
+	 */
+	unsigned proto_major;
+
+	/**
+	 * Minor version of the protocol (read-only)
+	 */
+	unsigned proto_minor;
+
+	/**
+	 * Is asynchronous read supported (read-write)
+	 */
+	unsigned async_read;
+
+	/**
+	 * Maximum size of the write buffer
+	 */
+	unsigned max_write;
+
+	/**
+	 * Maximum readahead
+	 */
+	unsigned max_readahead;
+
+	/**
+	 * Capability flags, that the kernel supports
+	 */
+	unsigned capable;
+
+	/**
+	 * Capability flags, that the filesystem wants to enable
+	 */
+	unsigned want;
+
+	/**
+	 * Maximum number of backgrounded requests
+	 */
+	unsigned max_background;
+
+	/**
+	 * Kernel congestion threshold parameter
+	 */
+	unsigned congestion_threshold;
+
+	/**
+	 * For future use.
+	 */
+	unsigned reserved[23];
+};
+
+struct fuse_session;
+struct fuse_chan;
+struct fuse_pollhandle;
+
+/**
+ * Create a FUSE mountpoint
+ *
+ * Returns a control file descriptor suitable for passing to
+ * fuse_new()
+ *
+ * @param mountpoint the mount point path
+ * @param args argument vector
+ * @return the communication channel on success, NULL on failure
+ */
+struct fuse_chan *fuse_mount(const char *mountpoint, struct fuse_args *args);
+
+/**
+ * Umount a FUSE mountpoint
+ *
+ * @param mountpoint the mount point path
+ * @param ch the communication channel
+ */
+void fuse_unmount(const char *mountpoint, struct fuse_chan *ch);
+
+/**
+ * Parse common options
+ *
+ * The following options are parsed:
+ *
+ *   '-f'	     foreground
+ *   '-d' '-odebug'  foreground, but keep the debug option
+ *   '-s'	     single threaded
+ *   '-h' '--help'   help
+ *   '-ho'	     help without header
+ *   '-ofsname=..'   file system name, if not present, then set to the program
+ *		     name
+ *
+ * All parameters may be NULL
+ *
+ * @param args argument vector
+ * @param mountpoint the returned mountpoint, should be freed after use
+ * @param multithreaded set to 1 unless the '-s' option is present
+ * @param foreground set to 1 if one of the relevant options is present
+ * @return 0 on success, -1 on failure
+ */
+int fuse_parse_cmdline(struct fuse_args *args, char **mountpoint,
+		       int *multithreaded, int *foreground);
+
+/**
+ * Go into the background
+ *
+ * @param foreground if true, stay in the foreground
+ * @return 0 on success, -1 on failure
+ */
+int fuse_daemonize(int foreground);
+
+/**
+ * Get the version of the library
+ *
+ * @return the version
+ */
+int fuse_version(void);
+
+/**
+ * Destroy poll handle
+ *
+ * @param ph the poll handle
+ */
+void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
+/* ----------------------------------------------------------- *
+ * Data buffer						       *
+ * ----------------------------------------------------------- */
+
+/**
+ * Buffer flags
+ */
+enum fuse_buf_flags {
+	/**
+	 * Buffer contains a file descriptor
+	 *
+	 * If this flag is set, the .fd field is valid, otherwise the
+	 * .mem fields is valid.
+	 */
+	FUSE_BUF_IS_FD		= (1 << 1),
+
+	/**
+	 * Seek on the file descriptor
+	 *
+	 * If this flag is set then the .pos field is valid and is
+	 * used to seek to the given offset before performing
+	 * operation on file descriptor.
+	 */
+	FUSE_BUF_FD_SEEK	= (1 << 2),
+
+	/**
+	 * Retry operation on file descriptor
+	 *
+	 * If this flag is set then retry operation on file descriptor
+	 * until .size bytes have been copied or an error or EOF is
+	 * detected.
+	 */
+	FUSE_BUF_FD_RETRY	= (1 << 3),
+};
+
+/**
+ * Buffer copy flags
+ */
+enum fuse_buf_copy_flags {
+	/**
+	 * Don't use splice(2)
+	 *
+	 * Always fall back to using read and write instead of
+	 * splice(2) to copy data from one file descriptor to another.
+	 *
+	 * If this flag is not set, then only fall back if splice is
+	 * unavailable.
+	 */
+	FUSE_BUF_NO_SPLICE	= (1 << 1),
+
+	/**
+	 * Force splice
+	 *
+	 * Always use splice(2) to copy data from one file descriptor
+	 * to another.  If splice is not available, return -EINVAL.
+	 */
+	FUSE_BUF_FORCE_SPLICE	= (1 << 2),
+
+	/**
+	 * Try to move data with splice.
+	 *
+	 * If splice is used, try to move pages from the source to the
+	 * destination instead of copying.  See documentation of
+	 * SPLICE_F_MOVE in splice(2) man page.
+	 */
+	FUSE_BUF_SPLICE_MOVE	= (1 << 3),
+
+	/**
+	 * Don't block on the pipe when copying data with splice
+	 *
+	 * Makes the operations on the pipe non-blocking (if the pipe
+	 * is full or empty).  See SPLICE_F_NONBLOCK in the splice(2)
+	 * man page.
+	 */
+	FUSE_BUF_SPLICE_NONBLOCK= (1 << 4),
+};
+
+/**
+ * Single data buffer
+ *
+ * Generic data buffer for I/O, extended attributes, etc...  Data may
+ * be supplied as a memory pointer or as a file descriptor
+ */
+struct fuse_buf {
+	/**
+	 * Size of data in bytes
+	 */
+	size_t size;
+
+	/**
+	 * Buffer flags
+	 */
+	enum fuse_buf_flags flags;
+
+	/**
+	 * Memory pointer
+	 *
+	 * Used unless FUSE_BUF_IS_FD flag is set.
+	 */
+	void *mem;
+
+	/**
+	 * File descriptor
+	 *
+	 * Used if FUSE_BUF_IS_FD flag is set.
+	 */
+	int fd;
+
+	/**
+	 * File position
+	 *
+	 * Used if FUSE_BUF_FD_SEEK flag is set.
+	 */
+	loff_t pos;
+};
+
+/**
+ * Data buffer vector
+ *
+ * An array of data buffers, each containing a memory pointer or a
+ * file descriptor.
+ *
+ * Allocate dynamically to add more than one buffer.
+ */
+struct fuse_bufvec {
+	/**
+	 * Number of buffers in the array
+	 */
+	size_t count;
+
+	/**
+	 * Index of current buffer within the array
+	 */
+	size_t idx;
+
+	/**
+	 * Current offset within the current buffer
+	 */
+	size_t off;
+
+	/**
+	 * Array of buffers
+	 */
+	struct fuse_buf buf[1];
+};
+
+/* Initialize bufvec with a single buffer of given size */
+#define FUSE_BUFVEC_INIT(size__) 				\
+	((struct fuse_bufvec) {					\
+		/* .count= */ 1,				\
+		/* .idx =  */ 0,				\
+		/* .off =  */ 0,				\
+		/* .buf =  */ { /* [0] = */ {			\
+			/* .size =  */ (size__),		\
+			/* .flags = */ (enum fuse_buf_flags) 0,	\
+			/* .mem =   */ NULL,			\
+			/* .fd =    */ -1,			\
+			/* .pos =   */ 0,			\
+		} }						\
+	} )
+
+/**
+ * Get total size of data in a fuse buffer vector
+ *
+ * @param bufv buffer vector
+ * @return size of data
+ */
+size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
+/**
+ * Copy data from one buffer vector to another
+ *
+ * @param dst destination buffer vector
+ * @param src source buffer vector
+ * @param flags flags controlling the copy
+ * @return actual number of bytes copied or -errno on error
+ */
+ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+		      enum fuse_buf_copy_flags flags);
+
+/* ----------------------------------------------------------- *
+ * Signal handling					       *
+ * ----------------------------------------------------------- */
+
+/**
+ * Exit session on HUP, TERM and INT signals and ignore PIPE signal
+ *
+ * Stores session in a global variable.	 May only be called once per
+ * process until fuse_remove_signal_handlers() is called.
+ *
+ * @param se the session to exit
+ * @return 0 on success, -1 on failure
+ */
+int fuse_set_signal_handlers(struct fuse_session *se);
+
+/**
+ * Restore default signal handlers
+ *
+ * Resets global session.  After this fuse_set_signal_handlers() may
+ * be called again.
+ *
+ * @param se the same session as given in fuse_set_signal_handlers()
+ */
+void fuse_remove_signal_handlers(struct fuse_session *se);
+
+/* ----------------------------------------------------------- *
+ * Compatibility stuff					       *
+ * ----------------------------------------------------------- */
+
+#if FUSE_USE_VERSION < 26
+#    ifdef __FreeBSD__
+#	 if FUSE_USE_VERSION < 25
+#	     error On FreeBSD API version 25 or greater must be used
+#	 endif
+#    endif
+#    include "fuse_common_compat.h"
+#    undef FUSE_MINOR_VERSION
+#    undef fuse_main
+#    define fuse_unmount fuse_unmount_compat22
+#    if FUSE_USE_VERSION == 25
+#	 define FUSE_MINOR_VERSION 5
+#	 define fuse_mount fuse_mount_compat25
+#    elif FUSE_USE_VERSION == 24 || FUSE_USE_VERSION == 22
+#	 define FUSE_MINOR_VERSION 4
+#	 define fuse_mount fuse_mount_compat22
+#    elif FUSE_USE_VERSION == 21
+#	 define FUSE_MINOR_VERSION 1
+#	 define fuse_mount fuse_mount_compat22
+#    elif FUSE_USE_VERSION == 11
+#	 warning Compatibility with API version 11 is deprecated
+#	 undef FUSE_MAJOR_VERSION
+#	 define FUSE_MAJOR_VERSION 1
+#	 define FUSE_MINOR_VERSION 1
+#	 define fuse_mount fuse_mount_compat1
+#    else
+#	 error Compatibility with API version other than 21, 22, 24, 25 and 11 not supported
+#    endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FUSE_COMMON_H_ */
diff --git a/fuse/include/fuse_common_compat.h b/fuse/include/fuse_common_compat.h
new file mode 100644
index 0000000..34440ff
--- /dev/null
+++ b/fuse/include/fuse_common_compat.h
@@ -0,0 +1,26 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+/* these definitions provide source compatibility to prior versions.
+   Do not include this file directly! */
+
+struct fuse_file_info_compat {
+	int flags;
+	unsigned long fh;
+	int writepage;
+	unsigned int direct_io : 1;
+	unsigned int keep_cache : 1;
+};
+
+int fuse_mount_compat25(const char *mountpoint, struct fuse_args *args);
+
+int fuse_mount_compat22(const char *mountpoint, const char *opts);
+
+int fuse_mount_compat1(const char *mountpoint, const char *args[]);
+
+void fuse_unmount_compat22(const char *mountpoint);
diff --git a/fuse/include/fuse_compat.h b/fuse/include/fuse_compat.h
new file mode 100644
index 0000000..b825dee
--- /dev/null
+++ b/fuse/include/fuse_compat.h
@@ -0,0 +1,203 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+/* these definitions provide source compatibility to prior versions.
+   Do not include this file directly! */
+
+#include "fuse_lowlevel.h"
+
+struct fuse_operations_compat25 {
+	int (*getattr) (const char *, struct stat *);
+	int (*readlink) (const char *, char *, size_t);
+	int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);
+	int (*mknod) (const char *, mode_t, dev_t);
+	int (*mkdir) (const char *, mode_t);
+	int (*unlink) (const char *);
+	int (*rmdir) (const char *);
+	int (*symlink) (const char *, const char *);
+	int (*rename) (const char *, const char *);
+	int (*link) (const char *, const char *);
+	int (*chmod) (const char *, mode_t);
+	int (*chown) (const char *, uid_t, gid_t);
+	int (*truncate) (const char *, loff_t);
+	int (*utime) (const char *, struct utimbuf *);
+	int (*open) (const char *, struct fuse_file_info *);
+	int (*read) (const char *, char *, size_t, loff_t,
+		     struct fuse_file_info *);
+	int (*write) (const char *, const char *, size_t, loff_t,
+		      struct fuse_file_info *);
+	int (*statfs) (const char *, struct statvfs *);
+	int (*flush) (const char *, struct fuse_file_info *);
+	int (*release) (const char *, struct fuse_file_info *);
+	int (*fsync) (const char *, int, struct fuse_file_info *);
+	int (*setxattr) (const char *, const char *, const char *, size_t, int);
+	int (*getxattr) (const char *, const char *, char *, size_t);
+	int (*listxattr) (const char *, char *, size_t);
+	int (*removexattr) (const char *, const char *);
+	int (*opendir) (const char *, struct fuse_file_info *);
+	int (*readdir) (const char *, void *, fuse_fill_dir_t, loff_t,
+			struct fuse_file_info *);
+	int (*releasedir) (const char *, struct fuse_file_info *);
+	int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+	void *(*init) (void);
+	void (*destroy) (void *);
+	int (*access) (const char *, int);
+	int (*create) (const char *, mode_t, struct fuse_file_info *);
+	int (*ftruncate) (const char *, loff_t, struct fuse_file_info *);
+	int (*fgetattr) (const char *, struct stat *, struct fuse_file_info *);
+};
+
+struct fuse *fuse_new_compat25(int fd, struct fuse_args *args,
+			       const struct fuse_operations_compat25 *op,
+			       size_t op_size);
+
+int fuse_main_real_compat25(int argc, char *argv[],
+			    const struct fuse_operations_compat25 *op,
+			    size_t op_size);
+
+struct fuse *fuse_setup_compat25(int argc, char *argv[],
+				 const struct fuse_operations_compat25 *op,
+				 size_t op_size, char **mountpoint,
+				 int *multithreaded, int *fd);
+
+void fuse_teardown_compat22(struct fuse *fuse, int fd, char *mountpoint);
+
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
+#include <sys/statfs.h>
+
+struct fuse_operations_compat22 {
+	int (*getattr) (const char *, struct stat *);
+	int (*readlink) (const char *, char *, size_t);
+	int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);
+	int (*mknod) (const char *, mode_t, dev_t);
+	int (*mkdir) (const char *, mode_t);
+	int (*unlink) (const char *);
+	int (*rmdir) (const char *);
+	int (*symlink) (const char *, const char *);
+	int (*rename) (const char *, const char *);
+	int (*link) (const char *, const char *);
+	int (*chmod) (const char *, mode_t);
+	int (*chown) (const char *, uid_t, gid_t);
+	int (*truncate) (const char *, loff_t);
+	int (*utime) (const char *, struct utimbuf *);
+	int (*open) (const char *, struct fuse_file_info_compat *);
+	int (*read) (const char *, char *, size_t, loff_t,
+		     struct fuse_file_info_compat *);
+	int (*write) (const char *, const char *, size_t, loff_t,
+		      struct fuse_file_info_compat *);
+	int (*statfs) (const char *, struct statfs *);
+	int (*flush) (const char *, struct fuse_file_info_compat *);
+	int (*release) (const char *, struct fuse_file_info_compat *);
+	int (*fsync) (const char *, int, struct fuse_file_info_compat *);
+	int (*setxattr) (const char *, const char *, const char *, size_t, int);
+	int (*getxattr) (const char *, const char *, char *, size_t);
+	int (*listxattr) (const char *, char *, size_t);
+	int (*removexattr) (const char *, const char *);
+	int (*opendir) (const char *, struct fuse_file_info_compat *);
+	int (*readdir) (const char *, void *, fuse_fill_dir_t, loff_t,
+			struct fuse_file_info_compat *);
+	int (*releasedir) (const char *, struct fuse_file_info_compat *);
+	int (*fsyncdir) (const char *, int, struct fuse_file_info_compat *);
+	void *(*init) (void);
+	void (*destroy) (void *);
+};
+
+struct fuse *fuse_new_compat22(int fd, const char *opts,
+			       const struct fuse_operations_compat22 *op,
+			       size_t op_size);
+
+struct fuse *fuse_setup_compat22(int argc, char *argv[],
+				 const struct fuse_operations_compat22 *op,
+				 size_t op_size, char **mountpoint,
+				 int *multithreaded, int *fd);
+
+int fuse_main_real_compat22(int argc, char *argv[],
+			    const struct fuse_operations_compat22 *op,
+			    size_t op_size);
+
+typedef int (*fuse_dirfil_t_compat) (fuse_dirh_t h, const char *name, int type);
+struct fuse_operations_compat2 {
+	int (*getattr)	   (const char *, struct stat *);
+	int (*readlink)	   (const char *, char *, size_t);
+	int (*getdir)	   (const char *, fuse_dirh_t, fuse_dirfil_t_compat);
+	int (*mknod)	   (const char *, mode_t, dev_t);
+	int (*mkdir)	   (const char *, mode_t);
+	int (*unlink)	   (const char *);
+	int (*rmdir)	   (const char *);
+	int (*symlink)	   (const char *, const char *);
+	int (*rename)	   (const char *, const char *);
+	int (*link)	   (const char *, const char *);
+	int (*chmod)	   (const char *, mode_t);
+	int (*chown)	   (const char *, uid_t, gid_t);
+	int (*truncate)	   (const char *, loff_t);
+	int (*utime)	   (const char *, struct utimbuf *);
+	int (*open)	   (const char *, int);
+	int (*read)	   (const char *, char *, size_t, loff_t);
+	int (*write)	   (const char *, const char *, size_t, loff_t);
+	int (*statfs)	   (const char *, struct statfs *);
+	int (*flush)	   (const char *);
+	int (*release)	   (const char *, int);
+	int (*fsync)	   (const char *, int);
+	int (*setxattr)	   (const char *, const char *, const char *,
+			    size_t, int);
+	int (*getxattr)	   (const char *, const char *, char *, size_t);
+	int (*listxattr)   (const char *, char *, size_t);
+	int (*removexattr) (const char *, const char *);
+};
+
+int fuse_main_compat2(int argc, char *argv[],
+		      const struct fuse_operations_compat2 *op);
+
+struct fuse *fuse_new_compat2(int fd, const char *opts,
+			      const struct fuse_operations_compat2 *op);
+
+struct fuse *fuse_setup_compat2(int argc, char *argv[],
+				const struct fuse_operations_compat2 *op,
+				char **mountpoint, int *multithreaded, int *fd);
+
+struct fuse_statfs_compat1 {
+	long block_size;
+	long blocks;
+	long blocks_free;
+	long files;
+	long files_free;
+	long namelen;
+};
+
+struct fuse_operations_compat1 {
+	int (*getattr)	(const char *, struct stat *);
+	int (*readlink) (const char *, char *, size_t);
+	int (*getdir)	(const char *, fuse_dirh_t, fuse_dirfil_t_compat);
+	int (*mknod)	(const char *, mode_t, dev_t);
+	int (*mkdir)	(const char *, mode_t);
+	int (*unlink)	(const char *);
+	int (*rmdir)	(const char *);
+	int (*symlink)	(const char *, const char *);
+	int (*rename)	(const char *, const char *);
+	int (*link)	(const char *, const char *);
+	int (*chmod)	(const char *, mode_t);
+	int (*chown)	(const char *, uid_t, gid_t);
+	int (*truncate) (const char *, loff_t);
+	int (*utime)	(const char *, struct utimbuf *);
+	int (*open)	(const char *, int);
+	int (*read)	(const char *, char *, size_t, loff_t);
+	int (*write)	(const char *, const char *, size_t, loff_t);
+	int (*statfs)	(struct fuse_statfs_compat1 *);
+	int (*release)	(const char *, int);
+	int (*fsync)	(const char *, int);
+};
+
+#define FUSE_DEBUG_COMPAT1	 (1 << 1)
+
+struct fuse *fuse_new_compat1(int fd, int flags,
+			      const struct fuse_operations_compat1 *op);
+
+void fuse_main_compat1(int argc, char *argv[],
+		       const struct fuse_operations_compat1 *op);
+
+#endif /* __FreeBSD__ || __NetBSD__ */
diff --git a/fuse/include/fuse_kernel.h b/fuse/include/fuse_kernel.h
new file mode 100644
index 0000000..c632b58
--- /dev/null
+++ b/fuse/include/fuse_kernel.h
@@ -0,0 +1,691 @@
+/*
+    This file defines the kernel interface of FUSE
+    Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
+
+    This program can be distributed under the terms of the GNU GPL.
+    See the file COPYING.
+
+    This -- and only this -- header file may also be distributed under
+    the terms of the BSD Licence as follows:
+
+    Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+    1. Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+    ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+*/
+
+/*
+ * This file defines the kernel interface of FUSE
+ *
+ * Protocol changelog:
+ *
+ * 7.9:
+ *  - new fuse_getattr_in input argument of GETATTR
+ *  - add lk_flags in fuse_lk_in
+ *  - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+ *  - add blksize field to fuse_attr
+ *  - add file flags field to fuse_read_in and fuse_write_in
+ *
+ * 7.10
+ *  - add nonseekable open flag
+ *
+ * 7.11
+ *  - add IOCTL message
+ *  - add unsolicited notification support
+ *  - add POLL message and NOTIFY_POLL notification
+ *
+ * 7.12
+ *  - add umask flag to input argument of open, mknod and mkdir
+ *  - add notification messages for invalidation of inodes and
+ *    directory entries
+ *
+ * 7.13
+ *  - make max number of background requests and congestion threshold
+ *    tunables
+ *
+ * 7.14
+ *  - add splice support to fuse device
+ *
+ * 7.15
+ *  - add store notify
+ *  - add retrieve notify
+ *
+ * 7.16
+ *  - add BATCH_FORGET request
+ *  - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+ *    fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+ *  - add FUSE_IOCTL_32BIT flag
+ *
+ * 7.17
+ *  - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+ *
+ * 7.18
+ *  - add FUSE_IOCTL_DIR flag
+ *  - add FUSE_NOTIFY_DELETE
+ *
+ * 7.19
+ *  - add FUSE_FALLOCATE
+ */
+
+#ifndef _LINUX_FUSE_H
+#define _LINUX_FUSE_H
+
+#include <sys/types.h>
+#define __u64 uint64_t
+#define __s64 int64_t
+#define __u32 uint32_t
+#define __s32 int32_t
+#define __u16 uint16_t
+
+/*
+ * Version negotiation:
+ *
+ * Both the kernel and userspace send the version they support in the
+ * INIT request and reply respectively.
+ *
+ * If the major versions match then both shall use the smallest
+ * of the two minor versions for communication.
+ *
+ * If the kernel supports a larger major version, then userspace shall
+ * reply with the major version it supports, ignore the rest of the
+ * INIT message and expect a new INIT message from the kernel with a
+ * matching major version.
+ *
+ * If the library supports a larger major version, then it shall fall
+ * back to the major protocol version sent by the kernel for
+ * communication and reply with that major version (and an arbitrary
+ * supported minor version).
+ */
+
+/** Version number of this interface */
+#define FUSE_KERNEL_VERSION 7
+
+/** Minor version number of this interface */
+#define FUSE_KERNEL_MINOR_VERSION 19
+
+/** The node ID of the root inode */
+#define FUSE_ROOT_ID 1
+
+/* Make sure all structures are padded to 64bit boundary, so 32bit
+   userspace works under 64bit kernels */
+
+struct fuse_attr {
+	__u64	ino;
+	__u64	size;
+	__u64	blocks;
+	__u64	atime;
+	__u64	mtime;
+	__u64	ctime;
+	__u32	atimensec;
+	__u32	mtimensec;
+	__u32	ctimensec;
+	__u32	mode;
+	__u32	nlink;
+	__u32	uid;
+	__u32	gid;
+	__u32	rdev;
+	__u32	blksize;
+	__u32	padding;
+};
+
+struct fuse_kstatfs {
+	__u64	blocks;
+	__u64	bfree;
+	__u64	bavail;
+	__u64	files;
+	__u64	ffree;
+	__u32	bsize;
+	__u32	namelen;
+	__u32	frsize;
+	__u32	padding;
+	__u32	spare[6];
+};
+
+struct fuse_file_lock {
+	__u64	start;
+	__u64	end;
+	__u32	type;
+	__u32	pid; /* tgid */
+};
+
+/**
+ * Bitmasks for fuse_setattr_in.valid
+ */
+#define FATTR_MODE	(1 << 0)
+#define FATTR_UID	(1 << 1)
+#define FATTR_GID	(1 << 2)
+#define FATTR_SIZE	(1 << 3)
+#define FATTR_ATIME	(1 << 4)
+#define FATTR_MTIME	(1 << 5)
+#define FATTR_FH	(1 << 6)
+#define FATTR_ATIME_NOW	(1 << 7)
+#define FATTR_MTIME_NOW	(1 << 8)
+#define FATTR_LOCKOWNER	(1 << 9)
+
+/**
+ * Flags returned by the OPEN request
+ *
+ * FOPEN_DIRECT_IO: bypass page cache for this open file
+ * FOPEN_KEEP_CACHE: don't invalidate the data cache on open
+ * FOPEN_NONSEEKABLE: the file is not seekable
+ */
+#define FOPEN_DIRECT_IO		(1 << 0)
+#define FOPEN_KEEP_CACHE	(1 << 1)
+#define FOPEN_NONSEEKABLE	(1 << 2)
+
+/**
+ * INIT request/reply flags
+ *
+ * FUSE_POSIX_LOCKS: remote locking for POSIX file locks
+ * FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".."
+ * FUSE_DONT_MASK: don't apply umask to file mode on create operations
+ * FUSE_FLOCK_LOCKS: remote locking for BSD style file locks
+ */
+#define FUSE_ASYNC_READ		(1 << 0)
+#define FUSE_POSIX_LOCKS	(1 << 1)
+#define FUSE_FILE_OPS		(1 << 2)
+#define FUSE_ATOMIC_O_TRUNC	(1 << 3)
+#define FUSE_EXPORT_SUPPORT	(1 << 4)
+#define FUSE_BIG_WRITES		(1 << 5)
+#define FUSE_DONT_MASK		(1 << 6)
+#define FUSE_FLOCK_LOCKS	(1 << 10)
+
+/**
+ * CUSE INIT request/reply flags
+ *
+ * CUSE_UNRESTRICTED_IOCTL:  use unrestricted ioctl
+ */
+#define CUSE_UNRESTRICTED_IOCTL	(1 << 0)
+
+/**
+ * Release flags
+ */
+#define FUSE_RELEASE_FLUSH	(1 << 0)
+#define FUSE_RELEASE_FLOCK_UNLOCK	(1 << 1)
+
+/**
+ * Getattr flags
+ */
+#define FUSE_GETATTR_FH		(1 << 0)
+
+/**
+ * Lock flags
+ */
+#define FUSE_LK_FLOCK		(1 << 0)
+
+/**
+ * WRITE flags
+ *
+ * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed
+ * FUSE_WRITE_LOCKOWNER: lock_owner field is valid
+ */
+#define FUSE_WRITE_CACHE	(1 << 0)
+#define FUSE_WRITE_LOCKOWNER	(1 << 1)
+
+/**
+ * Read flags
+ */
+#define FUSE_READ_LOCKOWNER	(1 << 1)
+
+/**
+ * Ioctl flags
+ *
+ * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine
+ * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed
+ * FUSE_IOCTL_RETRY: retry with new iovecs
+ * FUSE_IOCTL_32BIT: 32bit ioctl
+ * FUSE_IOCTL_DIR: is a directory
+ *
+ * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs
+ */
+#define FUSE_IOCTL_COMPAT	(1 << 0)
+#define FUSE_IOCTL_UNRESTRICTED	(1 << 1)
+#define FUSE_IOCTL_RETRY	(1 << 2)
+#define FUSE_IOCTL_32BIT	(1 << 3)
+#define FUSE_IOCTL_DIR		(1 << 4)
+
+#define FUSE_IOCTL_MAX_IOV	256
+
+/**
+ * Poll flags
+ *
+ * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify
+ */
+#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
+enum fuse_opcode {
+	FUSE_LOOKUP	   = 1,
+	FUSE_FORGET	   = 2,  /* no reply */
+	FUSE_GETATTR	   = 3,
+	FUSE_SETATTR	   = 4,
+	FUSE_READLINK	   = 5,
+	FUSE_SYMLINK	   = 6,
+	FUSE_MKNOD	   = 8,
+	FUSE_MKDIR	   = 9,
+	FUSE_UNLINK	   = 10,
+	FUSE_RMDIR	   = 11,
+	FUSE_RENAME	   = 12,
+	FUSE_LINK	   = 13,
+	FUSE_OPEN	   = 14,
+	FUSE_READ	   = 15,
+	FUSE_WRITE	   = 16,
+	FUSE_STATFS	   = 17,
+	FUSE_RELEASE       = 18,
+	FUSE_FSYNC         = 20,
+	FUSE_SETXATTR      = 21,
+	FUSE_GETXATTR      = 22,
+	FUSE_LISTXATTR     = 23,
+	FUSE_REMOVEXATTR   = 24,
+	FUSE_FLUSH         = 25,
+	FUSE_INIT          = 26,
+	FUSE_OPENDIR       = 27,
+	FUSE_READDIR       = 28,
+	FUSE_RELEASEDIR    = 29,
+	FUSE_FSYNCDIR      = 30,
+	FUSE_GETLK         = 31,
+	FUSE_SETLK         = 32,
+	FUSE_SETLKW        = 33,
+	FUSE_ACCESS        = 34,
+	FUSE_CREATE        = 35,
+	FUSE_INTERRUPT     = 36,
+	FUSE_BMAP          = 37,
+	FUSE_DESTROY       = 38,
+	FUSE_IOCTL         = 39,
+	FUSE_POLL          = 40,
+	FUSE_NOTIFY_REPLY  = 41,
+	FUSE_BATCH_FORGET  = 42,
+	FUSE_FALLOCATE     = 43,
+
+	/* CUSE specific operations */
+	CUSE_INIT          = 4096,
+};
+
+enum fuse_notify_code {
+	FUSE_NOTIFY_POLL   = 1,
+	FUSE_NOTIFY_INVAL_INODE = 2,
+	FUSE_NOTIFY_INVAL_ENTRY = 3,
+	FUSE_NOTIFY_STORE = 4,
+	FUSE_NOTIFY_RETRIEVE = 5,
+	FUSE_NOTIFY_DELETE = 6,
+	FUSE_NOTIFY_CODE_MAX,
+};
+
+/* The read buffer is required to be at least 8k, but may be much larger */
+#define FUSE_MIN_READ_BUFFER 8192
+
+#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
+struct fuse_entry_out {
+	__u64	nodeid;		/* Inode ID */
+	__u64	generation;	/* Inode generation: nodeid:gen must
+				   be unique for the fs's lifetime */
+	__u64	entry_valid;	/* Cache timeout for the name */
+	__u64	attr_valid;	/* Cache timeout for the attributes */
+	__u32	entry_valid_nsec;
+	__u32	attr_valid_nsec;
+	struct fuse_attr attr;
+};
+
+struct fuse_forget_in {
+	__u64	nlookup;
+};
+
+struct fuse_forget_one {
+	__u64	nodeid;
+	__u64	nlookup;
+};
+
+struct fuse_batch_forget_in {
+	__u32	count;
+	__u32	dummy;
+};
+
+struct fuse_getattr_in {
+	__u32	getattr_flags;
+	__u32	dummy;
+	__u64	fh;
+};
+
+#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
+struct fuse_attr_out {
+	__u64	attr_valid;	/* Cache timeout for the attributes */
+	__u32	attr_valid_nsec;
+	__u32	dummy;
+	struct fuse_attr attr;
+};
+
+#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
+struct fuse_mknod_in {
+	__u32	mode;
+	__u32	rdev;
+	__u32	umask;
+	__u32	padding;
+};
+
+struct fuse_mkdir_in {
+	__u32	mode;
+	__u32	umask;
+};
+
+struct fuse_rename_in {
+	__u64	newdir;
+};
+
+struct fuse_link_in {
+	__u64	oldnodeid;
+};
+
+struct fuse_setattr_in {
+	__u32	valid;
+	__u32	padding;
+	__u64	fh;
+	__u64	size;
+	__u64	lock_owner;
+	__u64	atime;
+	__u64	mtime;
+	__u64	unused2;
+	__u32	atimensec;
+	__u32	mtimensec;
+	__u32	unused3;
+	__u32	mode;
+	__u32	unused4;
+	__u32	uid;
+	__u32	gid;
+	__u32	unused5;
+};
+
+struct fuse_open_in {
+	__u32	flags;
+	__u32	unused;
+};
+
+struct fuse_create_in {
+	__u32	flags;
+	__u32	mode;
+	__u32	umask;
+	__u32	padding;
+};
+
+struct fuse_open_out {
+	__u64	fh;
+	__u32	open_flags;
+	__u32	padding;
+};
+
+struct fuse_release_in {
+	__u64	fh;
+	__u32	flags;
+	__u32	release_flags;
+	__u64	lock_owner;
+};
+
+struct fuse_flush_in {
+	__u64	fh;
+	__u32	unused;
+	__u32	padding;
+	__u64	lock_owner;
+};
+
+struct fuse_read_in {
+	__u64	fh;
+	__u64	offset;
+	__u32	size;
+	__u32	read_flags;
+	__u64	lock_owner;
+	__u32	flags;
+	__u32	padding;
+};
+
+#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
+struct fuse_write_in {
+	__u64	fh;
+	__u64	offset;
+	__u32	size;
+	__u32	write_flags;
+	__u64	lock_owner;
+	__u32	flags;
+	__u32	padding;
+};
+
+struct fuse_write_out {
+	__u32	size;
+	__u32	padding;
+};
+
+#define FUSE_COMPAT_STATFS_SIZE 48
+
+struct fuse_statfs_out {
+	struct fuse_kstatfs st;
+};
+
+struct fuse_fsync_in {
+	__u64	fh;
+	__u32	fsync_flags;
+	__u32	padding;
+};
+
+struct fuse_setxattr_in {
+	__u32	size;
+	__u32	flags;
+};
+
+struct fuse_getxattr_in {
+	__u32	size;
+	__u32	padding;
+};
+
+struct fuse_getxattr_out {
+	__u32	size;
+	__u32	padding;
+};
+
+struct fuse_lk_in {
+	__u64	fh;
+	__u64	owner;
+	struct fuse_file_lock lk;
+	__u32	lk_flags;
+	__u32	padding;
+};
+
+struct fuse_lk_out {
+	struct fuse_file_lock lk;
+};
+
+struct fuse_access_in {
+	__u32	mask;
+	__u32	padding;
+};
+
+struct fuse_init_in {
+	__u32	major;
+	__u32	minor;
+	__u32	max_readahead;
+	__u32	flags;
+};
+
+struct fuse_init_out {
+	__u32	major;
+	__u32	minor;
+	__u32	max_readahead;
+	__u32	flags;
+	__u16   max_background;
+	__u16   congestion_threshold;
+	__u32	max_write;
+};
+
+#define CUSE_INIT_INFO_MAX 4096
+
+struct cuse_init_in {
+	__u32	major;
+	__u32	minor;
+	__u32	unused;
+	__u32	flags;
+};
+
+struct cuse_init_out {
+	__u32	major;
+	__u32	minor;
+	__u32	unused;
+	__u32	flags;
+	__u32	max_read;
+	__u32	max_write;
+	__u32	dev_major;		/* chardev major */
+	__u32	dev_minor;		/* chardev minor */
+	__u32	spare[10];
+};
+
+struct fuse_interrupt_in {
+	__u64	unique;
+};
+
+struct fuse_bmap_in {
+	__u64	block;
+	__u32	blocksize;
+	__u32	padding;
+};
+
+struct fuse_bmap_out {
+	__u64	block;
+};
+
+struct fuse_ioctl_in {
+	__u64	fh;
+	__u32	flags;
+	__u32	cmd;
+	__u64	arg;
+	__u32	in_size;
+	__u32	out_size;
+};
+
+struct fuse_ioctl_iovec {
+	__u64	base;
+	__u64	len;
+};
+
+struct fuse_ioctl_out {
+	__s32	result;
+	__u32	flags;
+	__u32	in_iovs;
+	__u32	out_iovs;
+};
+
+struct fuse_poll_in {
+	__u64	fh;
+	__u64	kh;
+	__u32	flags;
+	__u32   padding;
+};
+
+struct fuse_poll_out {
+	__u32	revents;
+	__u32	padding;
+};
+
+struct fuse_notify_poll_wakeup_out {
+	__u64	kh;
+};
+
+struct fuse_fallocate_in {
+	__u64	fh;
+	__u64	offset;
+	__u64	length;
+	__u32	mode;
+	__u32	padding;
+};
+
+struct fuse_in_header {
+	__u32	len;
+	__u32	opcode;
+	__u64	unique;
+	__u64	nodeid;
+	__u32	uid;
+	__u32	gid;
+	__u32	pid;
+	__u32	padding;
+};
+
+struct fuse_out_header {
+	__u32	len;
+	__s32	error;
+	__u64	unique;
+};
+
+struct fuse_dirent {
+	__u64	ino;
+	__u64	off;
+	__u32	namelen;
+	__u32	type;
+	char name[];
+};
+
+#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+#define FUSE_DIRENT_ALIGN(x) (((x) + sizeof(__u64) - 1) & ~(sizeof(__u64) - 1))
+#define FUSE_DIRENT_SIZE(d) \
+	FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
+struct fuse_notify_inval_inode_out {
+	__u64	ino;
+	__s64	off;
+	__s64	len;
+};
+
+struct fuse_notify_inval_entry_out {
+	__u64	parent;
+	__u32	namelen;
+	__u32	padding;
+};
+
+struct fuse_notify_delete_out {
+	__u64	parent;
+	__u64	child;
+	__u32	namelen;
+	__u32	padding;
+};
+
+struct fuse_notify_store_out {
+	__u64	nodeid;
+	__u64	offset;
+	__u32	size;
+	__u32	padding;
+};
+
+struct fuse_notify_retrieve_out {
+	__u64	notify_unique;
+	__u64	nodeid;
+	__u64	offset;
+	__u32	size;
+	__u32	padding;
+};
+
+/* Matches the size of fuse_write_in */
+struct fuse_notify_retrieve_in {
+	__u64	dummy1;
+	__u64	offset;
+	__u32	size;
+	__u32	dummy2;
+	__u64	dummy3;
+	__u64	dummy4;
+};
+
+#endif /* _LINUX_FUSE_H */
diff --git a/fuse/include/fuse_lowlevel.h b/fuse/include/fuse_lowlevel.h
new file mode 100644
index 0000000..6971f73
--- /dev/null
+++ b/fuse/include/fuse_lowlevel.h
@@ -0,0 +1,1845 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#ifndef _FUSE_LOWLEVEL_H_
+#define _FUSE_LOWLEVEL_H_
+
+/** @file
+ *
+ * Low level API
+ *
+ * IMPORTANT: you should define FUSE_USE_VERSION before including this
+ * header.  To use the newest API define it to 26 (recommended for any
+ * new application), to use the old API define it to 24 (default) or
+ * 25
+ */
+
+#ifndef FUSE_USE_VERSION
+#define FUSE_USE_VERSION 24
+#endif
+
+#include "fuse_common.h"
+
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ----------------------------------------------------------- *
+ * Miscellaneous definitions				       *
+ * ----------------------------------------------------------- */
+
+/** The node ID of the root inode */
+#define FUSE_ROOT_ID 1
+
+/** Inode number type */
+typedef unsigned long fuse_ino_t;
+
+/** Request pointer type */
+typedef struct fuse_req *fuse_req_t;
+
+/**
+ * Session
+ *
+ * This provides hooks for processing requests, and exiting
+ */
+struct fuse_session;
+
+/**
+ * Channel
+ *
+ * A communication channel, providing hooks for sending and receiving
+ * messages
+ */
+struct fuse_chan;
+
+/** Directory entry parameters supplied to fuse_reply_entry() */
+struct fuse_entry_param {
+	/** Unique inode number
+	 *
+	 * In lookup, zero means negative entry (from version 2.5)
+	 * Returning ENOENT also means negative entry, but by setting zero
+	 * ino the kernel may cache negative entries for entry_timeout
+	 * seconds.
+	 */
+	fuse_ino_t ino;
+
+	/** Generation number for this entry.
+	 *
+	 * If the file system will be exported over NFS, the
+	 * ino/generation pairs need to be unique over the file
+	 * system's lifetime (rather than just the mount time). So if
+	 * the file system reuses an inode after it has been deleted,
+	 * it must assign a new, previously unused generation number
+	 * to the inode at the same time.
+	 *
+	 * The generation must be non-zero, otherwise FUSE will treat
+	 * it as an error.
+	 *
+	 */
+	unsigned long generation;
+
+	/** Inode attributes.
+	 *
+	 * Even if attr_timeout == 0, attr must be correct. For example,
+	 * for open(), FUSE uses attr.st_size from lookup() to determine
+	 * how many bytes to request. If this value is not correct,
+	 * incorrect data will be returned.
+	 */
+	struct stat attr;
+
+	/** Validity timeout (in seconds) for the attributes */
+	double attr_timeout;
+
+	/** Validity timeout (in seconds) for the name */
+	double entry_timeout;
+};
+
+/** Additional context associated with requests */
+struct fuse_ctx {
+	/** User ID of the calling process */
+	uid_t uid;
+
+	/** Group ID of the calling process */
+	gid_t gid;
+
+	/** Thread ID of the calling process */
+	pid_t pid;
+
+	/** Umask of the calling process (introduced in version 2.8) */
+	mode_t umask;
+};
+
+struct fuse_forget_data {
+	uint64_t ino;
+	uint64_t nlookup;
+};
+
+/* 'to_set' flags in setattr */
+#define FUSE_SET_ATTR_MODE	(1 << 0)
+#define FUSE_SET_ATTR_UID	(1 << 1)
+#define FUSE_SET_ATTR_GID	(1 << 2)
+#define FUSE_SET_ATTR_SIZE	(1 << 3)
+#define FUSE_SET_ATTR_ATIME	(1 << 4)
+#define FUSE_SET_ATTR_MTIME	(1 << 5)
+#define FUSE_SET_ATTR_ATIME_NOW	(1 << 7)
+#define FUSE_SET_ATTR_MTIME_NOW	(1 << 8)
+
+/* ----------------------------------------------------------- *
+ * Request methods and replies				       *
+ * ----------------------------------------------------------- */
+
+/**
+ * Low level filesystem operations
+ *
+ * Most of the methods (with the exception of init and destroy)
+ * receive a request handle (fuse_req_t) as their first argument.
+ * This handle must be passed to one of the specified reply functions.
+ *
+ * This may be done inside the method invocation, or after the call
+ * has returned.  The request handle is valid until one of the reply
+ * functions is called.
+ *
+ * Other pointer arguments (name, fuse_file_info, etc) are not valid
+ * after the call has returned, so if they are needed later, their
+ * contents have to be copied.
+ *
+ * The filesystem sometimes needs to handle a return value of -ENOENT
+ * from the reply function, which means, that the request was
+ * interrupted, and the reply discarded.  For example if
+ * fuse_reply_open() return -ENOENT means, that the release method for
+ * this file will not be called.
+ */
+struct fuse_lowlevel_ops {
+	/**
+	 * Initialize filesystem
+	 *
+	 * Called before any other filesystem method
+	 *
+	 * There's no reply to this function
+	 *
+	 * @param userdata the user data passed to fuse_lowlevel_new()
+	 */
+	void (*init) (void *userdata, struct fuse_conn_info *conn);
+
+	/**
+	 * Clean up filesystem
+	 *
+	 * Called on filesystem exit
+	 *
+	 * There's no reply to this function
+	 *
+	 * @param userdata the user data passed to fuse_lowlevel_new()
+	 */
+	void (*destroy) (void *userdata);
+
+	/**
+	 * Look up a directory entry by name and get its attributes.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_entry
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param parent inode number of the parent directory
+	 * @param name the name to look up
+	 */
+	void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
+	/**
+	 * Forget about an inode
+	 *
+	 * This function is called when the kernel removes an inode
+	 * from its internal caches.
+	 *
+	 * The inode's lookup count increases by one for every call to
+	 * fuse_reply_entry and fuse_reply_create. The nlookup parameter
+	 * indicates by how much the lookup count should be decreased.
+	 *
+	 * Inodes with a non-zero lookup count may receive request from
+	 * the kernel even after calls to unlink, rmdir or (when
+	 * overwriting an existing file) rename. Filesystems must handle
+	 * such requests properly and it is recommended to defer removal
+	 * of the inode until the lookup count reaches zero. Calls to
+	 * unlink, remdir or rename will be followed closely by forget
+	 * unless the file or directory is open, in which case the
+	 * kernel issues forget only after the release or releasedir
+	 * calls.
+	 *
+	 * Note that if a file system will be exported over NFS the
+	 * inodes lifetime must extend even beyond forget. See the
+	 * generation field in struct fuse_entry_param above.
+	 *
+	 * On unmount the lookup count for all inodes implicitly drops
+	 * to zero. It is not guaranteed that the file system will
+	 * receive corresponding forget messages for the affected
+	 * inodes.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_none
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param nlookup the number of lookups to forget
+	 */
+	void (*forget) (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup);
+
+	/**
+	 * Get file attributes
+	 *
+	 * Valid replies:
+	 *   fuse_reply_attr
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param fi for future use, currently always NULL
+	 */
+	void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+			 struct fuse_file_info *fi);
+
+	/**
+	 * Set file attributes
+	 *
+	 * In the 'attr' argument only members indicated by the 'to_set'
+	 * bitmask contain valid values.  Other members contain undefined
+	 * values.
+	 *
+	 * If the setattr was invoked from the ftruncate() system call
+	 * under Linux kernel versions 2.6.15 or later, the fi->fh will
+	 * contain the value set by the open method or will be undefined
+	 * if the open method didn't set any value.  Otherwise (not
+	 * ftruncate call, or kernel version earlier than 2.6.15) the fi
+	 * parameter will be NULL.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_attr
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param attr the attributes
+	 * @param to_set bit mask of attributes which should be set
+	 * @param fi file information, or NULL
+	 *
+	 * Changed in version 2.5:
+	 *     file information filled in for ftruncate
+	 */
+	void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+			 int to_set, struct fuse_file_info *fi);
+
+	/**
+	 * Read symbolic link
+	 *
+	 * Valid replies:
+	 *   fuse_reply_readlink
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 */
+	void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
+	/**
+	 * Create file node
+	 *
+	 * Create a regular file, character device, block device, fifo or
+	 * socket node.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_entry
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param parent inode number of the parent directory
+	 * @param name to create
+	 * @param mode file type and mode with which to create the new file
+	 * @param rdev the device number (only valid if created file is a device)
+	 */
+	void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+		       mode_t mode, dev_t rdev);
+
+	/**
+	 * Create a directory
+	 *
+	 * Valid replies:
+	 *   fuse_reply_entry
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param parent inode number of the parent directory
+	 * @param name to create
+	 * @param mode with which to create the new file
+	 */
+	void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+		       mode_t mode);
+
+	/**
+	 * Remove a file
+	 *
+	 * If the file's inode's lookup count is non-zero, the file
+	 * system is expected to postpone any removal of the inode
+	 * until the lookup count reaches zero (see description of the
+	 * forget function).
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param parent inode number of the parent directory
+	 * @param name to remove
+	 */
+	void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
+	/**
+	 * Remove a directory
+	 *
+	 * If the directory's inode's lookup count is non-zero, the
+	 * file system is expected to postpone any removal of the
+	 * inode until the lookup count reaches zero (see description
+	 * of the forget function).
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param parent inode number of the parent directory
+	 * @param name to remove
+	 */
+	void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
+	/**
+	 * Create a symbolic link
+	 *
+	 * Valid replies:
+	 *   fuse_reply_entry
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param link the contents of the symbolic link
+	 * @param parent inode number of the parent directory
+	 * @param name to create
+	 */
+	void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+			 const char *name);
+
+	/** Rename a file
+	 *
+	 * If the target exists it should be atomically replaced. If
+	 * the target's inode's lookup count is non-zero, the file
+	 * system is expected to postpone any removal of the inode
+	 * until the lookup count reaches zero (see description of the
+	 * forget function).
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param parent inode number of the old parent directory
+	 * @param name old name
+	 * @param newparent inode number of the new parent directory
+	 * @param newname new name
+	 */
+	void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+			fuse_ino_t newparent, const char *newname);
+
+	/**
+	 * Create a hard link
+	 *
+	 * Valid replies:
+	 *   fuse_reply_entry
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the old inode number
+	 * @param newparent inode number of the new parent directory
+	 * @param newname new name to create
+	 */
+	void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+		      const char *newname);
+
+	/**
+	 * Open a file
+	 *
+	 * Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and
+	 * O_TRUNC) are available in fi->flags.
+	 *
+	 * Filesystem may store an arbitrary file handle (pointer, index,
+	 * etc) in fi->fh, and use this in other all other file operations
+	 * (read, write, flush, release, fsync).
+	 *
+	 * Filesystem may also implement stateless file I/O and not store
+	 * anything in fi->fh.
+	 *
+	 * There are also some flags (direct_io, keep_cache) which the
+	 * filesystem may set in fi, to change the way the file is opened.
+	 * See fuse_file_info structure in <fuse_common.h> for more details.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_open
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param fi file information
+	 */
+	void (*open) (fuse_req_t req, fuse_ino_t ino,
+		      struct fuse_file_info *fi);
+
+	/**
+	 * Read data
+	 *
+	 * Read should send exactly the number of bytes requested except
+	 * on EOF or error, otherwise the rest of the data will be
+	 * substituted with zeroes.  An exception to this is when the file
+	 * has been opened in 'direct_io' mode, in which case the return
+	 * value of the read system call will reflect the return value of
+	 * this operation.
+	 *
+	 * fi->fh will contain the value set by the open method, or will
+	 * be undefined if the open method didn't set any value.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_buf
+	 *   fuse_reply_iov
+	 *   fuse_reply_data
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param size number of bytes to read
+	 * @param off offset to read from
+	 * @param fi file information
+	 */
+	void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off,
+		      struct fuse_file_info *fi);
+
+	/**
+	 * Write data
+	 *
+	 * Write should return exactly the number of bytes requested
+	 * except on error.  An exception to this is when the file has
+	 * been opened in 'direct_io' mode, in which case the return value
+	 * of the write system call will reflect the return value of this
+	 * operation.
+	 *
+	 * fi->fh will contain the value set by the open method, or will
+	 * be undefined if the open method didn't set any value.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_write
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param buf data to write
+	 * @param size number of bytes to write
+	 * @param off offset to write to
+	 * @param fi file information
+	 */
+	void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+		       size_t size, loff_t off, struct fuse_file_info *fi);
+
+	/**
+	 * Flush method
+	 *
+	 * This is called on each close() of the opened file.
+	 *
+	 * Since file descriptors can be duplicated (dup, dup2, fork), for
+	 * one open call there may be many flush calls.
+	 *
+	 * Filesystems shouldn't assume that flush will always be called
+	 * after some writes, or that if will be called at all.
+	 *
+	 * fi->fh will contain the value set by the open method, or will
+	 * be undefined if the open method didn't set any value.
+	 *
+	 * NOTE: the name of the method is misleading, since (unlike
+	 * fsync) the filesystem is not forced to flush pending writes.
+	 * One reason to flush data, is if the filesystem wants to return
+	 * write errors.
+	 *
+	 * If the filesystem supports file locking operations (setlk,
+	 * getlk) it should remove all locks belonging to 'fi->owner'.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param fi file information
+	 */
+	void (*flush) (fuse_req_t req, fuse_ino_t ino,
+		       struct fuse_file_info *fi);
+
+	/**
+	 * Release an open file
+	 *
+	 * Release is called when there are no more references to an open
+	 * file: all file descriptors are closed and all memory mappings
+	 * are unmapped.
+	 *
+	 * For every open call there will be exactly one release call.
+	 *
+	 * The filesystem may reply with an error, but error values are
+	 * not returned to close() or munmap() which triggered the
+	 * release.
+	 *
+	 * fi->fh will contain the value set by the open method, or will
+	 * be undefined if the open method didn't set any value.
+	 * fi->flags will contain the same flags as for open.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param fi file information
+	 */
+	void (*release) (fuse_req_t req, fuse_ino_t ino,
+			 struct fuse_file_info *fi);
+
+	/**
+	 * Synchronize file contents
+	 *
+	 * If the datasync parameter is non-zero, then only the user data
+	 * should be flushed, not the meta data.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param datasync flag indicating if only data should be flushed
+	 * @param fi file information
+	 */
+	void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+		       struct fuse_file_info *fi);
+
+	/**
+	 * Open a directory
+	 *
+	 * Filesystem may store an arbitrary file handle (pointer, index,
+	 * etc) in fi->fh, and use this in other all other directory
+	 * stream operations (readdir, releasedir, fsyncdir).
+	 *
+	 * Filesystem may also implement stateless directory I/O and not
+	 * store anything in fi->fh, though that makes it impossible to
+	 * implement standard conforming directory stream operations in
+	 * case the contents of the directory can change between opendir
+	 * and releasedir.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_open
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param fi file information
+	 */
+	void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+			 struct fuse_file_info *fi);
+
+	/**
+	 * Read directory
+	 *
+	 * Send a buffer filled using fuse_add_direntry(), with size not
+	 * exceeding the requested size.  Send an empty buffer on end of
+	 * stream.
+	 *
+	 * fi->fh will contain the value set by the opendir method, or
+	 * will be undefined if the opendir method didn't set any value.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_buf
+	 *   fuse_reply_data
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param size maximum number of bytes to send
+	 * @param off offset to continue reading the directory stream
+	 * @param fi file information
+	 */
+	void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off,
+			 struct fuse_file_info *fi);
+
+	/**
+	 * Release an open directory
+	 *
+	 * For every opendir call there will be exactly one releasedir
+	 * call.
+	 *
+	 * fi->fh will contain the value set by the opendir method, or
+	 * will be undefined if the opendir method didn't set any value.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param fi file information
+	 */
+	void (*releasedir) (fuse_req_t req, fuse_ino_t ino,
+			    struct fuse_file_info *fi);
+
+	/**
+	 * Synchronize directory contents
+	 *
+	 * If the datasync parameter is non-zero, then only the directory
+	 * contents should be flushed, not the meta data.
+	 *
+	 * fi->fh will contain the value set by the opendir method, or
+	 * will be undefined if the opendir method didn't set any value.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param datasync flag indicating if only data should be flushed
+	 * @param fi file information
+	 */
+	void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+			  struct fuse_file_info *fi);
+
+	/**
+	 * Get file system statistics
+	 *
+	 * Valid replies:
+	 *   fuse_reply_statfs
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number, zero means "undefined"
+	 */
+	void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
+	/**
+	 * Set an extended attribute
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 */
+	void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+			  const char *value, size_t size, int flags);
+
+	/**
+	 * Get an extended attribute
+	 *
+	 * If size is zero, the size of the value should be sent with
+	 * fuse_reply_xattr.
+	 *
+	 * If the size is non-zero, and the value fits in the buffer, the
+	 * value should be sent with fuse_reply_buf.
+	 *
+	 * If the size is too small for the value, the ERANGE error should
+	 * be sent.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_buf
+	 *   fuse_reply_data
+	 *   fuse_reply_xattr
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param name of the extended attribute
+	 * @param size maximum size of the value to send
+	 */
+	void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+			  size_t size);
+
+	/**
+	 * List extended attribute names
+	 *
+	 * If size is zero, the total size of the attribute list should be
+	 * sent with fuse_reply_xattr.
+	 *
+	 * If the size is non-zero, and the null character separated
+	 * attribute list fits in the buffer, the list should be sent with
+	 * fuse_reply_buf.
+	 *
+	 * If the size is too small for the list, the ERANGE error should
+	 * be sent.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_buf
+	 *   fuse_reply_data
+	 *   fuse_reply_xattr
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param size maximum size of the list to send
+	 */
+	void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
+	/**
+	 * Remove an extended attribute
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param name of the extended attribute
+	 */
+	void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
+	/**
+	 * Check file access permissions
+	 *
+	 * This will be called for the access() system call.  If the
+	 * 'default_permissions' mount option is given, this method is not
+	 * called.
+	 *
+	 * This method is not called under Linux kernel versions 2.4.x
+	 *
+	 * Introduced in version 2.5
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param mask requested access mode
+	 */
+	void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
+	/**
+	 * Create and open a file
+	 *
+	 * If the file does not exist, first create it with the specified
+	 * mode, and then open it.
+	 *
+	 * Open flags (with the exception of O_NOCTTY) are available in
+	 * fi->flags.
+	 *
+	 * Filesystem may store an arbitrary file handle (pointer, index,
+	 * etc) in fi->fh, and use this in other all other file operations
+	 * (read, write, flush, release, fsync).
+	 *
+	 * There are also some flags (direct_io, keep_cache) which the
+	 * filesystem may set in fi, to change the way the file is opened.
+	 * See fuse_file_info structure in <fuse_common.h> for more details.
+	 *
+	 * If this method is not implemented or under Linux kernel
+	 * versions earlier than 2.6.15, the mknod() and open() methods
+	 * will be called instead.
+	 *
+	 * Introduced in version 2.5
+	 *
+	 * Valid replies:
+	 *   fuse_reply_create
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param parent inode number of the parent directory
+	 * @param name to create
+	 * @param mode file type and mode with which to create the new file
+	 * @param fi file information
+	 */
+	void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+			mode_t mode, struct fuse_file_info *fi);
+
+	/**
+	 * Test for a POSIX file lock
+	 *
+	 * Introduced in version 2.6
+	 *
+	 * Valid replies:
+	 *   fuse_reply_lock
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param fi file information
+	 * @param lock the region/type to test
+	 */
+	void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+		       struct fuse_file_info *fi, struct flock *lock);
+
+	/**
+	 * Acquire, modify or release a POSIX file lock
+	 *
+	 * For POSIX threads (NPTL) there's a 1-1 relation between pid and
+	 * owner, but otherwise this is not always the case.  For checking
+	 * lock ownership, 'fi->owner' must be used.  The l_pid field in
+	 * 'struct flock' should only be used to fill in this field in
+	 * getlk().
+	 *
+	 * Note: if the locking methods are not implemented, the kernel
+	 * will still allow file locking to work locally.  Hence these are
+	 * only interesting for network filesystems and similar.
+	 *
+	 * Introduced in version 2.6
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param fi file information
+	 * @param lock the region/type to set
+	 * @param sleep locking operation may sleep
+	 */
+	void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+		       struct fuse_file_info *fi,
+		       struct flock *lock, int sleep);
+
+	/**
+	 * Map block index within file to block index within device
+	 *
+	 * Note: This makes sense only for block device backed filesystems
+	 * mounted with the 'blkdev' option
+	 *
+	 * Introduced in version 2.6
+	 *
+	 * Valid replies:
+	 *   fuse_reply_bmap
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param blocksize unit of block index
+	 * @param idx block index within file
+	 */
+	void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+		      uint64_t idx);
+
+	/**
+	 * Ioctl
+	 *
+	 * Note: For unrestricted ioctls (not allowed for FUSE
+	 * servers), data in and out areas can be discovered by giving
+	 * iovs and setting FUSE_IOCTL_RETRY in @flags.  For
+	 * restricted ioctls, kernel prepares in/out data area
+	 * according to the information encoded in cmd.
+	 *
+	 * Introduced in version 2.8
+	 *
+	 * Valid replies:
+	 *   fuse_reply_ioctl_retry
+	 *   fuse_reply_ioctl
+	 *   fuse_reply_ioctl_iov
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param cmd ioctl command
+	 * @param arg ioctl argument
+	 * @param fi file information
+	 * @param flags for FUSE_IOCTL_* flags
+	 * @param in_buf data fetched from the caller
+	 * @param in_bufsz number of fetched bytes
+	 * @param out_bufsz maximum size of output data
+	 */
+	void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd, void *arg,
+		       struct fuse_file_info *fi, unsigned flags,
+		       const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
+	/**
+	 * Poll for IO readiness
+	 *
+	 * Introduced in version 2.8
+	 *
+	 * Note: If ph is non-NULL, the client should notify
+	 * when IO readiness events occur by calling
+	 * fuse_lowelevel_notify_poll() with the specified ph.
+	 *
+	 * Regardless of the number of times poll with a non-NULL ph
+	 * is received, single notification is enough to clear all.
+	 * Notifying more times incurs overhead but doesn't harm
+	 * correctness.
+	 *
+	 * The callee is responsible for destroying ph with
+	 * fuse_pollhandle_destroy() when no longer in use.
+	 *
+	 * Valid replies:
+	 *   fuse_reply_poll
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param fi file information
+	 * @param ph poll handle to be used for notification
+	 */
+	void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+		      struct fuse_pollhandle *ph);
+
+	/**
+	 * Write data made available in a buffer
+	 *
+	 * This is a more generic version of the ->write() method.  If
+	 * FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the
+	 * kernel supports splicing from the fuse device, then the
+	 * data will be made available in pipe for supporting zero
+	 * copy data transfer.
+	 *
+	 * Introduced in version 2.9
+	 *
+	 * Valid replies:
+	 *   fuse_reply_write
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param bufv buffer containing the data
+	 * @param off offset to write to
+	 * @param fi file information
+	 */
+	void (*write_buf) (fuse_req_t req, fuse_ino_t ino,
+			   struct fuse_bufvec *bufv, loff_t off,
+			   struct fuse_file_info *fi);
+
+	/**
+	 * Callback function for the retrieve request
+	 *
+	 * Introduced in version 2.9
+	 *
+	 * Valid replies:
+	 *	fuse_reply_none
+	 *
+	 * @param req request handle
+	 * @param cookie user data supplied to fuse_lowlevel_notify_retrieve()
+	 * @param ino the inode number supplied to fuse_lowlevel_notify_retrieve()
+	 * @param offset the offset supplied to fuse_lowlevel_notify_retrieve()
+	 * @param bufv the buffer containing the returned data
+	 */
+	void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+				loff_t offset, struct fuse_bufvec *bufv);
+
+	/**
+	 * Forget about multiple inodes
+	 *
+	 * See description of the forget function for more
+	 * information.
+	 *
+	 * Introduced in version 2.9
+	 *
+	 * Valid replies:
+	 *   fuse_reply_none
+	 *
+	 * @param req request handle
+	 */
+	void (*forget_multi) (fuse_req_t req, size_t count,
+			      struct fuse_forget_data *forgets);
+
+	/**
+	 * Acquire, modify or release a BSD file lock
+	 *
+	 * Note: if the locking methods are not implemented, the kernel
+	 * will still allow file locking to work locally.  Hence these are
+	 * only interesting for network filesystems and similar.
+	 *
+	 * Introduced in version 2.9
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param fi file information
+	 * @param op the locking operation, see flock(2)
+	 */
+	void (*flock) (fuse_req_t req, fuse_ino_t ino,
+		       struct fuse_file_info *fi, int op);
+
+	/**
+	 * Allocate requested space. If this function returns success then
+	 * subsequent writes to the specified range shall not fail due to the lack
+	 * of free space on the file system storage media.
+	 *
+	 * Introduced in version 2.9
+	 *
+	 * Valid replies:
+	 *   fuse_reply_err
+	 *
+	 * @param req request handle
+	 * @param ino the inode number
+	 * @param offset starting point for allocated region
+	 * @param length size of allocated region
+	 * @param mode determines the operation to be performed on the given range,
+	 *             see fallocate(2)
+	 */
+	void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+		       loff_t offset, loff_t length, struct fuse_file_info *fi);
+};
+
+/**
+ * Reply with an error code or success
+ *
+ * Possible requests:
+ *   all except forget
+ *
+ * unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr,
+ * removexattr and setlk may send a zero code
+ *
+ * @param req request handle
+ * @param err the positive error value, or zero for success
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_err(fuse_req_t req, int err);
+
+/**
+ * Don't send reply
+ *
+ * Possible requests:
+ *   forget
+ *
+ * @param req request handle
+ */
+void fuse_reply_none(fuse_req_t req);
+
+/**
+ * Reply with a directory entry
+ *
+ * Possible requests:
+ *   lookup, mknod, mkdir, symlink, link
+ *
+ * Side effects:
+ *   increments the lookup count on success
+ *
+ * @param req request handle
+ * @param e the entry parameters
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
+/**
+ * Reply with a directory entry and open parameters
+ *
+ * currently the following members of 'fi' are used:
+ *   fh, direct_io, keep_cache
+ *
+ * Possible requests:
+ *   create
+ *
+ * Side effects:
+ *   increments the lookup count on success
+ *
+ * @param req request handle
+ * @param e the entry parameters
+ * @param fi file information
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+		      const struct fuse_file_info *fi);
+
+/**
+ * Reply with attributes
+ *
+ * Possible requests:
+ *   getattr, setattr
+ *
+ * @param req request handle
+ * @param attr the attributes
+ * @param attr_timeout	validity timeout (in seconds) for the attributes
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+		    double attr_timeout);
+
+/**
+ * Reply with the contents of a symbolic link
+ *
+ * Possible requests:
+ *   readlink
+ *
+ * @param req request handle
+ * @param link symbolic link contents
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_readlink(fuse_req_t req, const char *link);
+
+/**
+ * Reply with open parameters
+ *
+ * currently the following members of 'fi' are used:
+ *   fh, direct_io, keep_cache
+ *
+ * Possible requests:
+ *   open, opendir
+ *
+ * @param req request handle
+ * @param fi file information
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
+/**
+ * Reply with number of bytes written
+ *
+ * Possible requests:
+ *   write
+ *
+ * @param req request handle
+ * @param count the number of bytes written
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_write(fuse_req_t req, size_t count);
+
+/**
+ * Reply with data
+ *
+ * Possible requests:
+ *   read, readdir, getxattr, listxattr
+ *
+ * @param req request handle
+ * @param buf buffer containing data
+ * @param size the size of data in bytes
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
+/**
+ * Reply with data copied/moved from buffer(s)
+ *
+ * Possible requests:
+ *   read, readdir, getxattr, listxattr
+ *
+ * @param req request handle
+ * @param bufv buffer vector
+ * @param flags flags controlling the copy
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+		    enum fuse_buf_copy_flags flags);
+
+/**
+ * Reply with data vector
+ *
+ * Possible requests:
+ *   read, readdir, getxattr, listxattr
+ *
+ * @param req request handle
+ * @param iov the vector containing the data
+ * @param count the size of vector
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
+/**
+ * Reply with filesystem statistics
+ *
+ * Possible requests:
+ *   statfs
+ *
+ * @param req request handle
+ * @param stbuf filesystem statistics
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
+/**
+ * Reply with needed buffer size
+ *
+ * Possible requests:
+ *   getxattr, listxattr
+ *
+ * @param req request handle
+ * @param count the buffer size needed in bytes
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_xattr(fuse_req_t req, size_t count);
+
+/**
+ * Reply with file lock information
+ *
+ * Possible requests:
+ *   getlk
+ *
+ * @param req request handle
+ * @param lock the lock information
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
+/**
+ * Reply with block index
+ *
+ * Possible requests:
+ *   bmap
+ *
+ * @param req request handle
+ * @param idx block index within device
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
+/* ----------------------------------------------------------- *
+ * Filling a buffer in readdir				       *
+ * ----------------------------------------------------------- */
+
+/**
+ * Add a directory entry to the buffer
+ *
+ * Buffer needs to be large enough to hold the entry.  If it's not,
+ * then the entry is not filled in but the size of the entry is still
+ * returned.  The caller can check this by comparing the bufsize
+ * parameter with the returned entry size.  If the entry size is
+ * larger than the buffer size, the operation failed.
+ *
+ * From the 'stbuf' argument the st_ino field and bits 12-15 of the
+ * st_mode field are used.  The other fields are ignored.
+ *
+ * Note: offsets do not necessarily represent physical offsets, and
+ * could be any marker, that enables the implementation to find a
+ * specific point in the directory stream.
+ *
+ * @param req request handle
+ * @param buf the point where the new entry will be added to the buffer
+ * @param bufsize remaining size of the buffer
+ * @param name the name of the entry
+ * @param stbuf the file attributes
+ * @param off the offset of the next entry
+ * @return the space needed for the entry
+ */
+size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+			 const char *name, const struct stat *stbuf,
+			 loff_t off);
+
+/**
+ * Reply to ask for data fetch and output buffer preparation.  ioctl
+ * will be retried with the specified input data fetched and output
+ * buffer prepared.
+ *
+ * Possible requests:
+ *   ioctl
+ *
+ * @param req request handle
+ * @param in_iov iovec specifying data to fetch from the caller
+ * @param in_count number of entries in in_iov
+ * @param out_iov iovec specifying addresses to write output to
+ * @param out_count number of entries in out_iov
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_ioctl_retry(fuse_req_t req,
+			   const struct iovec *in_iov, size_t in_count,
+			   const struct iovec *out_iov, size_t out_count);
+
+/**
+ * Reply to finish ioctl
+ *
+ * Possible requests:
+ *   ioctl
+ *
+ * @param req request handle
+ * @param result result to be passed to the caller
+ * @param buf buffer containing output data
+ * @param size length of output data
+ */
+int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
+/**
+ * Reply to finish ioctl with iov buffer
+ *
+ * Possible requests:
+ *   ioctl
+ *
+ * @param req request handle
+ * @param result result to be passed to the caller
+ * @param iov the vector containing the data
+ * @param count the size of vector
+ */
+int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+			 int count);
+
+/**
+ * Reply with poll result event mask
+ *
+ * @param req request handle
+ * @param revents poll result event mask
+ */
+int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
+/* ----------------------------------------------------------- *
+ * Notification						       *
+ * ----------------------------------------------------------- */
+
+/**
+ * Notify IO readiness event
+ *
+ * For more information, please read comment for poll operation.
+ *
+ * @param ph poll handle to notify IO readiness event for
+ */
+int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
+/**
+ * Notify to invalidate cache for an inode
+ *
+ * @param ch the channel through which to send the invalidation
+ * @param ino the inode number
+ * @param off the offset in the inode where to start invalidating
+ *            or negative to invalidate attributes only
+ * @param len the amount of cache to invalidate or 0 for all
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_t ino,
+                                     loff_t off, loff_t len);
+
+/**
+ * Notify to invalidate parent attributes and the dentry matching
+ * parent/name
+ *
+ * To avoid a deadlock don't call this function from a filesystem operation and
+ * don't call it with a lock held that can also be held by a filesystem
+ * operation.
+ *
+ * @param ch the channel through which to send the invalidation
+ * @param parent inode number
+ * @param name file name
+ * @param namelen strlen() of file name
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, fuse_ino_t parent,
+                                     const char *name, size_t namelen);
+
+/**
+ * Notify to invalidate parent attributes and delete the dentry matching
+ * parent/name if the dentry's inode number matches child (otherwise it
+ * will invalidate the matching dentry).
+ *
+ * To avoid a deadlock don't call this function from a filesystem operation and
+ * don't call it with a lock held that can also be held by a filesystem
+ * operation.
+ *
+ * @param ch the channel through which to send the notification
+ * @param parent inode number
+ * @param child inode number
+ * @param name file name
+ * @param namelen strlen() of file name
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_delete(struct fuse_chan *ch,
+				fuse_ino_t parent, fuse_ino_t child,
+				const char *name, size_t namelen);
+
+/**
+ * Store data to the kernel buffers
+ *
+ * Synchronously store data in the kernel buffers belonging to the
+ * given inode.  The stored data is marked up-to-date (no read will be
+ * performed against it, unless it's invalidated or evicted from the
+ * cache).
+ *
+ * If the stored data overflows the current file size, then the size
+ * is extended, similarly to a write(2) on the filesystem.
+ *
+ * If this function returns an error, then the store wasn't fully
+ * completed, but it may have been partially completed.
+ *
+ * @param ch the channel through which to send the invalidation
+ * @param ino the inode number
+ * @param offset the starting offset into the file to store to
+ * @param bufv buffer vector
+ * @param flags flags controlling the copy
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_store(struct fuse_chan *ch, fuse_ino_t ino,
+			       loff_t offset, struct fuse_bufvec *bufv,
+			       enum fuse_buf_copy_flags flags);
+/**
+ * Retrieve data from the kernel buffers
+ *
+ * Retrieve data in the kernel buffers belonging to the given inode.
+ * If successful then the retrieve_reply() method will be called with
+ * the returned data.
+ *
+ * Only present pages are returned in the retrieve reply.  Retrieving
+ * stops when it finds a non-present page and only data prior to that is
+ * returned.
+ *
+ * If this function returns an error, then the retrieve will not be
+ * completed and no reply will be sent.
+ *
+ * This function doesn't change the dirty state of pages in the kernel
+ * buffer.  For dirty pages the write() method will be called
+ * regardless of having been retrieved previously.
+ *
+ * @param ch the channel through which to send the invalidation
+ * @param ino the inode number
+ * @param size the number of bytes to retrieve
+ * @param offset the starting offset into the file to retrieve from
+ * @param cookie user data to supply to the reply callback
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_retrieve(struct fuse_chan *ch, fuse_ino_t ino,
+				  size_t size, loff_t offset, void *cookie);
+
+
+/* ----------------------------------------------------------- *
+ * Utility functions					       *
+ * ----------------------------------------------------------- */
+
+/**
+ * Get the userdata from the request
+ *
+ * @param req request handle
+ * @return the user data passed to fuse_lowlevel_new()
+ */
+void *fuse_req_userdata(fuse_req_t req);
+
+/**
+ * Get the context from the request
+ *
+ * The pointer returned by this function will only be valid for the
+ * request's lifetime
+ *
+ * @param req request handle
+ * @return the context structure
+ */
+const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
+/**
+ * Get the current supplementary group IDs for the specified request
+ *
+ * Similar to the getgroups(2) system call, except the return value is
+ * always the total number of group IDs, even if it is larger than the
+ * specified size.
+ *
+ * The current fuse kernel module in linux (as of 2.6.30) doesn't pass
+ * the group list to userspace, hence this function needs to parse
+ * "/proc/$TID/task/$TID/status" to get the group IDs.
+ *
+ * This feature may not be supported on all operating systems.  In
+ * such a case this function will return -ENOSYS.
+ *
+ * @param req request handle
+ * @param size size of given array
+ * @param list array of group IDs to be filled in
+ * @return the total number of supplementary group IDs or -errno on failure
+ */
+int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
+/**
+ * Callback function for an interrupt
+ *
+ * @param req interrupted request
+ * @param data user data
+ */
+typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
+/**
+ * Register/unregister callback for an interrupt
+ *
+ * If an interrupt has already happened, then the callback function is
+ * called from within this function, hence it's not possible for
+ * interrupts to be lost.
+ *
+ * @param req request handle
+ * @param func the callback function or NULL for unregister
+ * @param data user data passed to the callback function
+ */
+void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func,
+			     void *data);
+
+/**
+ * Check if a request has already been interrupted
+ *
+ * @param req request handle
+ * @return 1 if the request has been interrupted, 0 otherwise
+ */
+int fuse_req_interrupted(fuse_req_t req);
+
+/* ----------------------------------------------------------- *
+ * Filesystem setup					       *
+ * ----------------------------------------------------------- */
+
+/* Deprecated, don't use */
+int fuse_lowlevel_is_lib_option(const char *opt);
+
+/**
+ * Create a low level session
+ *
+ * @param args argument vector
+ * @param op the low level filesystem operations
+ * @param op_size sizeof(struct fuse_lowlevel_ops)
+ * @param userdata user data
+ * @return the created session object, or NULL on failure
+ */
+struct fuse_session *fuse_lowlevel_new(struct fuse_args *args,
+				       const struct fuse_lowlevel_ops *op,
+				       size_t op_size, void *userdata);
+
+/* ----------------------------------------------------------- *
+ * Session interface					       *
+ * ----------------------------------------------------------- */
+
+/**
+ * Session operations
+ *
+ * This is used in session creation
+ */
+struct fuse_session_ops {
+	/**
+	 * Hook to process a request (mandatory)
+	 *
+	 * @param data user data passed to fuse_session_new()
+	 * @param buf buffer containing the raw request
+	 * @param len request length
+	 * @param ch channel on which the request was received
+	 */
+	void (*process) (void *data, const char *buf, size_t len,
+			 struct fuse_chan *ch);
+
+	/**
+	 * Hook for session exit and reset (optional)
+	 *
+	 * @param data user data passed to fuse_session_new()
+	 * @param val exited status (1 - exited, 0 - not exited)
+	 */
+	void (*exit) (void *data, int val);
+
+	/**
+	 * Hook for querying the current exited status (optional)
+	 *
+	 * @param data user data passed to fuse_session_new()
+	 * @return 1 if exited, 0 if not exited
+	 */
+	int (*exited) (void *data);
+
+	/**
+	 * Hook for cleaning up the channel on destroy (optional)
+	 *
+	 * @param data user data passed to fuse_session_new()
+	 */
+	void (*destroy) (void *data);
+};
+
+/**
+ * Create a new session
+ *
+ * @param op session operations
+ * @param data user data
+ * @return new session object, or NULL on failure
+ */
+struct fuse_session *fuse_session_new(struct fuse_session_ops *op, void *data);
+
+/**
+ * Assign a channel to a session
+ *
+ * Note: currently only a single channel may be assigned.  This may
+ * change in the future
+ *
+ * If a session is destroyed, the assigned channel is also destroyed
+ *
+ * @param se the session
+ * @param ch the channel
+ */
+void fuse_session_add_chan(struct fuse_session *se, struct fuse_chan *ch);
+
+/**
+ * Remove a channel from a session
+ *
+ * If the channel is not assigned to a session, then this is a no-op
+ *
+ * @param ch the channel to remove
+ */
+void fuse_session_remove_chan(struct fuse_chan *ch);
+
+/**
+ * Iterate over the channels assigned to a session
+ *
+ * The iterating function needs to start with a NULL channel, and
+ * after that needs to pass the previously returned channel to the
+ * function.
+ *
+ * @param se the session
+ * @param ch the previous channel, or NULL
+ * @return the next channel, or NULL if no more channels exist
+ */
+struct fuse_chan *fuse_session_next_chan(struct fuse_session *se,
+					 struct fuse_chan *ch);
+
+/**
+ * Process a raw request
+ *
+ * @param se the session
+ * @param buf buffer containing the raw request
+ * @param len request length
+ * @param ch channel on which the request was received
+ */
+void fuse_session_process(struct fuse_session *se, const char *buf, size_t len,
+			  struct fuse_chan *ch);
+
+/**
+ * Process a raw request supplied in a generic buffer
+ *
+ * This is a more generic version of fuse_session_process().  The
+ * fuse_buf may contain a memory buffer or a pipe file descriptor.
+ *
+ * @param se the session
+ * @param buf the fuse_buf containing the request
+ * @param ch channel on which the request was received
+ */
+void fuse_session_process_buf(struct fuse_session *se,
+			      const struct fuse_buf *buf, struct fuse_chan *ch);
+
+/**
+ * Receive a raw request supplied in a generic buffer
+ *
+ * This is a more generic version of fuse_chan_recv().  The fuse_buf
+ * supplied to this function contains a suitably allocated memory
+ * buffer.  This may be overwritten with a file descriptor buffer.
+ *
+ * @param se the session
+ * @param buf the fuse_buf to store the request in
+ * @param chp pointer to the channel
+ * @return the actual size of the raw request, or -errno on error
+ */
+int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
+			     struct fuse_chan **chp);
+
+/**
+ * Destroy a session
+ *
+ * @param se the session
+ */
+void fuse_session_destroy(struct fuse_session *se);
+
+/**
+ * Exit a session
+ *
+ * @param se the session
+ */
+void fuse_session_exit(struct fuse_session *se);
+
+/**
+ * Reset the exited status of a session
+ *
+ * @param se the session
+ */
+void fuse_session_reset(struct fuse_session *se);
+
+/**
+ * Query the exited status of a session
+ *
+ * @param se the session
+ * @return 1 if exited, 0 if not exited
+ */
+int fuse_session_exited(struct fuse_session *se);
+
+/**
+ * Get the user data provided to the session
+ *
+ * @param se the session
+ * @return the user data
+ */
+void *fuse_session_data(struct fuse_session *se);
+
+/**
+ * Enter a single threaded event loop
+ *
+ * @param se the session
+ * @return 0 on success, -1 on error
+ */
+int fuse_session_loop(struct fuse_session *se);
+
+/**
+ * Enter a multi-threaded event loop
+ *
+ * @param se the session
+ * @return 0 on success, -1 on error
+ */
+int fuse_session_loop_mt(struct fuse_session *se);
+
+/* ----------------------------------------------------------- *
+ * Channel interface					       *
+ * ----------------------------------------------------------- */
+
+/**
+ * Channel operations
+ *
+ * This is used in channel creation
+ */
+struct fuse_chan_ops {
+	/**
+	 * Hook for receiving a raw request
+	 *
+	 * @param ch pointer to the channel
+	 * @param buf the buffer to store the request in
+	 * @param size the size of the buffer
+	 * @return the actual size of the raw request, or -1 on error
+	 */
+	int (*receive)(struct fuse_chan **chp, char *buf, size_t size);
+
+	/**
+	 * Hook for sending a raw reply
+	 *
+	 * A return value of -ENOENT means, that the request was
+	 * interrupted, and the reply was discarded
+	 *
+	 * @param ch the channel
+	 * @param iov vector of blocks
+	 * @param count the number of blocks in vector
+	 * @return zero on success, -errno on failure
+	 */
+	int (*send)(struct fuse_chan *ch, const struct iovec iov[],
+		    size_t count);
+
+	/**
+	 * Destroy the channel
+	 *
+	 * @param ch the channel
+	 */
+	void (*destroy)(struct fuse_chan *ch);
+};
+
+/**
+ * Create a new channel
+ *
+ * @param op channel operations
+ * @param fd file descriptor of the channel
+ * @param bufsize the minimal receive buffer size
+ * @param data user data
+ * @return the new channel object, or NULL on failure
+ */
+struct fuse_chan *fuse_chan_new(struct fuse_chan_ops *op, int fd,
+				size_t bufsize, void *data);
+
+/**
+ * Query the file descriptor of the channel
+ *
+ * @param ch the channel
+ * @return the file descriptor passed to fuse_chan_new()
+ */
+int fuse_chan_fd(struct fuse_chan *ch);
+
+/**
+ * Query the minimal receive buffer size
+ *
+ * @param ch the channel
+ * @return the buffer size passed to fuse_chan_new()
+ */
+size_t fuse_chan_bufsize(struct fuse_chan *ch);
+
+/**
+ * Query the user data
+ *
+ * @param ch the channel
+ * @return the user data passed to fuse_chan_new()
+ */
+void *fuse_chan_data(struct fuse_chan *ch);
+
+/**
+ * Query the session to which this channel is assigned
+ *
+ * @param ch the channel
+ * @return the session, or NULL if the channel is not assigned
+ */
+struct fuse_session *fuse_chan_session(struct fuse_chan *ch);
+
+/**
+ * Receive a raw request
+ *
+ * A return value of -ENODEV means, that the filesystem was unmounted
+ *
+ * @param ch pointer to the channel
+ * @param buf the buffer to store the request in
+ * @param size the size of the buffer
+ * @return the actual size of the raw request, or -errno on error
+ */
+int fuse_chan_recv(struct fuse_chan **ch, char *buf, size_t size);
+
+/**
+ * Send a raw reply
+ *
+ * A return value of -ENOENT means, that the request was
+ * interrupted, and the reply was discarded
+ *
+ * @param ch the channel
+ * @param iov vector of blocks
+ * @param count the number of blocks in vector
+ * @return zero on success, -errno on failure
+ */
+int fuse_chan_send(struct fuse_chan *ch, const struct iovec iov[],
+		   size_t count);
+
+/**
+ * Destroy a channel
+ *
+ * @param ch the channel
+ */
+void fuse_chan_destroy(struct fuse_chan *ch);
+
+/* ----------------------------------------------------------- *
+ * Compatibility stuff					       *
+ * ----------------------------------------------------------- */
+
+#if FUSE_USE_VERSION < 26
+#  include "fuse_lowlevel_compat.h"
+#  define fuse_chan_ops fuse_chan_ops_compat24
+#  define fuse_chan_new fuse_chan_new_compat24
+#  if FUSE_USE_VERSION == 25
+#    define fuse_lowlevel_ops fuse_lowlevel_ops_compat25
+#    define fuse_lowlevel_new fuse_lowlevel_new_compat25
+#  elif FUSE_USE_VERSION == 24
+#    define fuse_lowlevel_ops fuse_lowlevel_ops_compat
+#    define fuse_lowlevel_new fuse_lowlevel_new_compat
+#    define fuse_file_info fuse_file_info_compat
+#    define fuse_reply_statfs fuse_reply_statfs_compat
+#    define fuse_reply_open fuse_reply_open_compat
+#  else
+#    error Compatibility with low-level API version < 24 not supported
+#  endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FUSE_LOWLEVEL_H_ */
diff --git a/fuse/include/fuse_lowlevel_compat.h b/fuse/include/fuse_lowlevel_compat.h
new file mode 100644
index 0000000..f13adbd
--- /dev/null
+++ b/fuse/include/fuse_lowlevel_compat.h
@@ -0,0 +1,157 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+/* these definitions provide source compatibility to prior versions.
+   Do not include this file directly! */
+
+#include "fuse_common.h"
+
+struct fuse_lowlevel_ops_compat25 {
+	void (*init) (void *userdata);
+	void (*destroy) (void *userdata);
+	void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+	void (*forget) (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup);
+	void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+			 struct fuse_file_info *fi);
+	void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+			 int to_set, struct fuse_file_info *fi);
+	void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+	void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+		       mode_t mode, dev_t rdev);
+	void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+		       mode_t mode);
+	void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+	void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+	void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+			 const char *name);
+	void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+			fuse_ino_t newparent, const char *newname);
+	void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+		      const char *newname);
+	void (*open) (fuse_req_t req, fuse_ino_t ino,
+		      struct fuse_file_info *fi);
+	void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off,
+		      struct fuse_file_info *fi);
+	void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+		       size_t size, loff_t off, struct fuse_file_info *fi);
+	void (*flush) (fuse_req_t req, fuse_ino_t ino,
+		       struct fuse_file_info *fi);
+	void (*release) (fuse_req_t req, fuse_ino_t ino,
+			 struct fuse_file_info *fi);
+	void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+		       struct fuse_file_info *fi);
+	void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+			 struct fuse_file_info *fi);
+	void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off,
+			 struct fuse_file_info *fi);
+	void (*releasedir) (fuse_req_t req, fuse_ino_t ino,
+			    struct fuse_file_info *fi);
+	void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+			  struct fuse_file_info *fi);
+	void (*statfs) (fuse_req_t req);
+	void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+			  const char *value, size_t size, int flags);
+	void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+			  size_t size);
+	void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+	void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+	void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+	void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+			mode_t mode, struct fuse_file_info *fi);
+};
+
+struct fuse_session *fuse_lowlevel_new_compat25(struct fuse_args *args,
+				const struct fuse_lowlevel_ops_compat25 *op,
+				size_t op_size, void *userdata);
+
+size_t fuse_dirent_size(size_t namelen);
+
+char *fuse_add_dirent(char *buf, const char *name, const struct stat *stbuf,
+		      loff_t off);
+
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
+
+#include <sys/statfs.h>
+
+struct fuse_lowlevel_ops_compat {
+	void (*init) (void *userdata);
+	void (*destroy) (void *userdata);
+	void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+	void (*forget) (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup);
+	void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+			 struct fuse_file_info_compat *fi);
+	void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+			 int to_set, struct fuse_file_info_compat *fi);
+	void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+	void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+		       mode_t mode, dev_t rdev);
+	void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+		       mode_t mode);
+	void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+	void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+	void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+			 const char *name);
+	void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+			fuse_ino_t newparent, const char *newname);
+	void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+		      const char *newname);
+	void (*open) (fuse_req_t req, fuse_ino_t ino,
+		      struct fuse_file_info_compat *fi);
+	void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off,
+		      struct fuse_file_info_compat *fi);
+	void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+		       size_t size, loff_t off, struct fuse_file_info_compat *fi);
+	void (*flush) (fuse_req_t req, fuse_ino_t ino,
+		       struct fuse_file_info_compat *fi);
+	void (*release) (fuse_req_t req, fuse_ino_t ino,
+			 struct fuse_file_info_compat *fi);
+	void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+		       struct fuse_file_info_compat *fi);
+	void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+			 struct fuse_file_info_compat *fi);
+	void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, loff_t off,
+			 struct fuse_file_info_compat *fi);
+	void (*releasedir) (fuse_req_t req, fuse_ino_t ino,
+			    struct fuse_file_info_compat *fi);
+	void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+			  struct fuse_file_info_compat *fi);
+	void (*statfs) (fuse_req_t req);
+	void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+			  const char *value, size_t size, int flags);
+	void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+			  size_t size);
+	void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+	void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+	void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+	void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+			mode_t mode, struct fuse_file_info_compat *fi);
+};
+
+int fuse_reply_statfs_compat(fuse_req_t req, const struct statfs *stbuf);
+
+int fuse_reply_open_compat(fuse_req_t req,
+			   const struct fuse_file_info_compat *fi);
+
+struct fuse_session *fuse_lowlevel_new_compat(const char *opts,
+				const struct fuse_lowlevel_ops_compat *op,
+				size_t op_size, void *userdata);
+
+#endif /* __FreeBSD__ || __NetBSD__ */
+
+struct fuse_chan_ops_compat24 {
+	int (*receive)(struct fuse_chan *ch, char *buf, size_t size);
+	int (*send)(struct fuse_chan *ch, const struct iovec iov[],
+		    size_t count);
+	void (*destroy)(struct fuse_chan *ch);
+};
+
+struct fuse_chan *fuse_chan_new_compat24(struct fuse_chan_ops_compat24 *op,
+					 int fd, size_t bufsize, void *data);
+
+int fuse_chan_receive(struct fuse_chan *ch, char *buf, size_t size);
+struct fuse_chan *fuse_kern_chan_new(int fd);
diff --git a/fuse/include/fuse_opt.h b/fuse/include/fuse_opt.h
new file mode 100644
index 0000000..add0a30
--- /dev/null
+++ b/fuse/include/fuse_opt.h
@@ -0,0 +1,270 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#ifndef _FUSE_OPT_H_
+#define _FUSE_OPT_H_
+
+/** @file
+ *
+ * This file defines the option parsing interface of FUSE
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Option description
+ *
+ * This structure describes a single option, and action associated
+ * with it, in case it matches.
+ *
+ * More than one such match may occur, in which case the action for
+ * each match is executed.
+ *
+ * There are three possible actions in case of a match:
+ *
+ * i) An integer (int or unsigned) variable determined by 'offset' is
+ *    set to 'value'
+ *
+ * ii) The processing function is called, with 'value' as the key
+ *
+ * iii) An integer (any) or string (char *) variable determined by
+ *    'offset' is set to the value of an option parameter
+ *
+ * 'offset' should normally be either set to
+ *
+ *  - 'offsetof(struct foo, member)'  actions i) and iii)
+ *
+ *  - -1			      action ii)
+ *
+ * The 'offsetof()' macro is defined in the <stddef.h> header.
+ *
+ * The template determines which options match, and also have an
+ * effect on the action.  Normally the action is either i) or ii), but
+ * if a format is present in the template, then action iii) is
+ * performed.
+ *
+ * The types of templates are:
+ *
+ * 1) "-x", "-foo", "--foo", "--foo-bar", etc.	These match only
+ *   themselves.  Invalid values are "--" and anything beginning
+ *   with "-o"
+ *
+ * 2) "foo", "foo-bar", etc.  These match "-ofoo", "-ofoo-bar" or
+ *    the relevant option in a comma separated option list
+ *
+ * 3) "bar=", "--foo=", etc.  These are variations of 1) and 2)
+ *    which have a parameter
+ *
+ * 4) "bar=%s", "--foo=%lu", etc.  Same matching as above but perform
+ *    action iii).
+ *
+ * 5) "-x ", etc.  Matches either "-xparam" or "-x param" as
+ *    two separate arguments
+ *
+ * 6) "-x %s", etc.  Combination of 4) and 5)
+ *
+ * If the format is "%s", memory is allocated for the string unlike
+ * with scanf().
+ */
+struct fuse_opt {
+	/** Matching template and optional parameter formatting */
+	const char *templ;
+
+	/**
+	 * Offset of variable within 'data' parameter of fuse_opt_parse()
+	 * or -1
+	 */
+	unsigned long offset;
+
+	/**
+	 * Value to set the variable to, or to be passed as 'key' to the
+	 * processing function.	 Ignored if template has a format
+	 */
+	int value;
+};
+
+/**
+ * Key option.	In case of a match, the processing function will be
+ * called with the specified key.
+ */
+#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
+/**
+ * Last option.	 An array of 'struct fuse_opt' must end with a NULL
+ * template value
+ */
+#define FUSE_OPT_END { NULL, 0, 0 }
+
+/**
+ * Argument list
+ */
+struct fuse_args {
+	/** Argument count */
+	int argc;
+
+	/** Argument vector.  NULL terminated */
+	char **argv;
+
+	/** Is 'argv' allocated? */
+	int allocated;
+};
+
+/**
+ * Initializer for 'struct fuse_args'
+ */
+#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
+/**
+ * Key value passed to the processing function if an option did not
+ * match any template
+ */
+#define FUSE_OPT_KEY_OPT     -1
+
+/**
+ * Key value passed to the processing function for all non-options
+ *
+ * Non-options are the arguments beginning with a character other than
+ * '-' or all arguments after the special '--' option
+ */
+#define FUSE_OPT_KEY_NONOPT  -2
+
+/**
+ * Special key value for options to keep
+ *
+ * Argument is not passed to processing function, but behave as if the
+ * processing function returned 1
+ */
+#define FUSE_OPT_KEY_KEEP -3
+
+/**
+ * Special key value for options to discard
+ *
+ * Argument is not passed to processing function, but behave as if the
+ * processing function returned zero
+ */
+#define FUSE_OPT_KEY_DISCARD -4
+
+/**
+ * Processing function
+ *
+ * This function is called if
+ *    - option did not match any 'struct fuse_opt'
+ *    - argument is a non-option
+ *    - option did match and offset was set to -1
+ *
+ * The 'arg' parameter will always contain the whole argument or
+ * option including the parameter if exists.  A two-argument option
+ * ("-x foo") is always converted to single argument option of the
+ * form "-xfoo" before this function is called.
+ *
+ * Options of the form '-ofoo' are passed to this function without the
+ * '-o' prefix.
+ *
+ * The return value of this function determines whether this argument
+ * is to be inserted into the output argument vector, or discarded.
+ *
+ * @param data is the user data passed to the fuse_opt_parse() function
+ * @param arg is the whole argument or option
+ * @param key determines why the processing function was called
+ * @param outargs the current output argument list
+ * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ */
+typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+			       struct fuse_args *outargs);
+
+/**
+ * Option parsing function
+ *
+ * If 'args' was returned from a previous call to fuse_opt_parse() or
+ * it was constructed from
+ *
+ * A NULL 'args' is equivalent to an empty argument vector
+ *
+ * A NULL 'opts' is equivalent to an 'opts' array containing a single
+ * end marker
+ *
+ * A NULL 'proc' is equivalent to a processing function always
+ * returning '1'
+ *
+ * @param args is the input and output argument list
+ * @param data is the user data
+ * @param opts is the option description array
+ * @param proc is the processing function
+ * @return -1 on error, 0 on success
+ */
+int fuse_opt_parse(struct fuse_args *args, void *data,
+		   const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
+/**
+ * Add an option to a comma separated option list
+ *
+ * @param opts is a pointer to an option list, may point to a NULL value
+ * @param opt is the option to add
+ * @return -1 on allocation error, 0 on success
+ */
+int fuse_opt_add_opt(char **opts, const char *opt);
+
+/**
+ * Add an option, escaping commas, to a comma separated option list
+ *
+ * @param opts is a pointer to an option list, may point to a NULL value
+ * @param opt is the option to add
+ * @return -1 on allocation error, 0 on success
+ */
+int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
+/**
+ * Add an argument to a NULL terminated argument vector
+ *
+ * @param args is the structure containing the current argument list
+ * @param arg is the new argument to add
+ * @return -1 on allocation error, 0 on success
+ */
+int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
+/**
+ * Add an argument at the specified position in a NULL terminated
+ * argument vector
+ *
+ * Adds the argument to the N-th position.  This is useful for adding
+ * options at the beginning of the array which must not come after the
+ * special '--' option.
+ *
+ * @param args is the structure containing the current argument list
+ * @param pos is the position at which to add the argument
+ * @param arg is the new argument to add
+ * @return -1 on allocation error, 0 on success
+ */
+int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
+/**
+ * Free the contents of argument list
+ *
+ * The structure itself is not freed
+ *
+ * @param args is the structure containing the argument list
+ */
+void fuse_opt_free_args(struct fuse_args *args);
+
+
+/**
+ * Check if an option matches
+ *
+ * @param opts is the option description array
+ * @param opt is the option to match
+ * @return 1 if a match is found, 0 if not
+ */
+int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FUSE_OPT_H_ */
diff --git a/fuse/include/old/fuse.h b/fuse/include/old/fuse.h
new file mode 100644
index 0000000..3db0945
--- /dev/null
+++ b/fuse/include/old/fuse.h
@@ -0,0 +1,9 @@
+/*
+   This header is for compatibility with older software using FUSE.
+
+   Please use 'pkg-config --cflags fuse' to set include path.  The
+   correct usage is still '#include <fuse.h>', not '#include
+   <fuse/fuse.h>'.
+*/
+
+#include "fuse/fuse.h"
diff --git a/fuse/include/ulockmgr.h b/fuse/include/ulockmgr.h
new file mode 100644
index 0000000..c3ceef5
--- /dev/null
+++ b/fuse/include/ulockmgr.h
@@ -0,0 +1,26 @@
+/*
+  libulockmgr: Userspace Lock Manager Library
+  Copyright (C) 2006  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#include "fuse_lowlevel.h"
+
+#include <stdint.h>
+#include <fcntl.h>
+#include <sys/types.h>
+
+/**
+ * Perform POSIX locking operation
+ *
+ * @param fd the file descriptor
+ * @param cmd the locking command (F_GETFL, F_SETLK or F_SETLKW)
+ * @param lock the lock parameters
+ * @param owner the lock owner ID cookie
+ * @param owner_len length of the lock owner ID cookie
+ * @return 0 on success -errno on error
+ */
+int ulockmgr_op(int fd, int cmd, struct flock *lock, const void *owner,
+		size_t owner_len);
diff --git a/fuse/modules/iconv.c b/fuse/modules/iconv.c
new file mode 100644
index 0000000..9b78cfd
--- /dev/null
+++ b/fuse/modules/iconv.c
@@ -0,0 +1,739 @@
+/*
+  fuse iconv module: file name charset conversion
+  Copyright (C) 2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#define FUSE_USE_VERSION 26
+
+#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <iconv.h>
+#include <pthread.h>
+#include <locale.h>
+#include <langinfo.h>
+
+struct iconv {
+	struct fuse_fs *next;
+	pthread_mutex_t lock;
+	char *from_code;
+	char *to_code;
+	iconv_t tofs;
+	iconv_t fromfs;
+};
+
+struct iconv_dh {
+	struct iconv *ic;
+	void *prev_buf;
+	fuse_fill_dir_t prev_filler;
+};
+
+static struct iconv *iconv_get(void)
+{
+	return fuse_get_context()->private_data;
+}
+
+static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+			  int fromfs)
+{
+	size_t pathlen;
+	size_t newpathlen;
+	char *newpath;
+	size_t plen;
+	char *p;
+	size_t res;
+	int err;
+
+	if (path == NULL) {
+		*newpathp = NULL;
+		return 0;
+	}
+
+	pathlen = strlen(path);
+	newpathlen = pathlen * 4;
+	newpath = malloc(newpathlen + 1);
+	if (!newpath)
+		return -ENOMEM;
+
+	plen = newpathlen;
+	p = newpath;
+	pthread_mutex_lock(&ic->lock);
+	do {
+		res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+			    &pathlen, &p, &plen);
+		if (res == (size_t) -1) {
+			char *tmp;
+			size_t inc;
+
+			err = -EILSEQ;
+			if (errno != E2BIG)
+				goto err;
+
+			inc = (pathlen + 1) * 4;
+			newpathlen += inc;
+			tmp = realloc(newpath, newpathlen + 1);
+			err = -ENOMEM;
+			if (!tmp)
+				goto err;
+
+			p = tmp + (p - newpath);
+			plen += inc;
+			newpath = tmp;
+		}
+	} while (res == (size_t) -1);
+	pthread_mutex_unlock(&ic->lock);
+	*p = '\0';
+	*newpathp = newpath;
+	return 0;
+
+err:
+	iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+	pthread_mutex_unlock(&ic->lock);
+	free(newpath);
+	return err;
+}
+
+static int iconv_getattr(const char *path, struct stat *stbuf)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_getattr(ic->next, newpath, stbuf);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_fgetattr(const char *path, struct stat *stbuf,
+			  struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_fgetattr(ic->next, newpath, stbuf, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_access(const char *path, int mask)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_access(ic->next, newpath, mask);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_readlink(const char *path, char *buf, size_t size)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_readlink(ic->next, newpath, buf, size);
+		if (!err) {
+			char *newlink;
+			err = iconv_convpath(ic, buf, &newlink, 1);
+			if (!err) {
+				strncpy(buf, newlink, size - 1);
+				buf[size - 1] = '\0';
+				free(newlink);
+			}
+		}
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_opendir(ic->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_dir_fill(void *buf, const char *name,
+			  const struct stat *stbuf, loff_t off)
+{
+	struct iconv_dh *dh = buf;
+	char *newname;
+	int res = 0;
+	if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+		res = dh->prev_filler(dh->prev_buf, newname, stbuf, off);
+		free(newname);
+	}
+	return res;
+}
+
+static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+			 loff_t offset, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		struct iconv_dh dh;
+		dh.ic = ic;
+		dh.prev_buf = buf;
+		dh.prev_filler = filler;
+		err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+				      offset, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_releasedir(ic->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_mkdir(const char *path, mode_t mode)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_mkdir(ic->next, newpath, mode);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_unlink(const char *path)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_unlink(ic->next, newpath);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_rmdir(const char *path)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_rmdir(ic->next, newpath);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_symlink(const char *from, const char *to)
+{
+	struct iconv *ic = iconv_get();
+	char *newfrom;
+	char *newto;
+	int err = iconv_convpath(ic, from, &newfrom, 0);
+	if (!err) {
+		err = iconv_convpath(ic, to, &newto, 0);
+		if (!err) {
+			err = fuse_fs_symlink(ic->next, newfrom, newto);
+			free(newto);
+		}
+		free(newfrom);
+	}
+	return err;
+}
+
+static int iconv_rename(const char *from, const char *to)
+{
+	struct iconv *ic = iconv_get();
+	char *newfrom;
+	char *newto;
+	int err = iconv_convpath(ic, from, &newfrom, 0);
+	if (!err) {
+		err = iconv_convpath(ic, to, &newto, 0);
+		if (!err) {
+			err = fuse_fs_rename(ic->next, newfrom, newto);
+			free(newto);
+		}
+		free(newfrom);
+	}
+	return err;
+}
+
+static int iconv_link(const char *from, const char *to)
+{
+	struct iconv *ic = iconv_get();
+	char *newfrom;
+	char *newto;
+	int err = iconv_convpath(ic, from, &newfrom, 0);
+	if (!err) {
+		err = iconv_convpath(ic, to, &newto, 0);
+		if (!err) {
+			err = fuse_fs_link(ic->next, newfrom, newto);
+			free(newto);
+		}
+		free(newfrom);
+	}
+	return err;
+}
+
+static int iconv_chmod(const char *path, mode_t mode)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_chmod(ic->next, newpath, mode);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_chown(const char *path, uid_t uid, gid_t gid)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_chown(ic->next, newpath, uid, gid);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_truncate(const char *path, loff_t size)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_truncate(ic->next, newpath, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_ftruncate(const char *path, loff_t size,
+			   struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_ftruncate(ic->next, newpath, size, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_utimens(const char *path, const struct timespec ts[2])
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_utimens(ic->next, newpath, ts);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_create(const char *path, mode_t mode,
+			struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_create(ic->next, newpath, mode, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_open(ic->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+			  size_t size, loff_t offset, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+			   loff_t offset, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_statfs(const char *path, struct statvfs *stbuf)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_statfs(ic->next, newpath, stbuf);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_flush(const char *path, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_flush(ic->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_release(const char *path, struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_release(ic->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_fsync(const char *path, int isdatasync,
+		       struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_fsyncdir(const char *path, int isdatasync,
+			  struct fuse_file_info *fi)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_setxattr(const char *path, const char *name,
+			  const char *value, size_t size, int flags)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+				       flags);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_getxattr(const char *path, const char *name, char *value,
+			  size_t size)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_listxattr(const char *path, char *list, size_t size)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_listxattr(ic->next, newpath, list, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_removexattr(const char *path, const char *name)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_removexattr(ic->next, newpath, name);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+		      struct flock *lock)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_flock(ic->next, newpath, fi, op);
+		free(newpath);
+	}
+	return err;
+}
+
+static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+{
+	struct iconv *ic = iconv_get();
+	char *newpath;
+	int err = iconv_convpath(ic, path, &newpath, 0);
+	if (!err) {
+		err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+		free(newpath);
+	}
+	return err;
+}
+
+static void *iconv_init(struct fuse_conn_info *conn)
+{
+	struct iconv *ic = iconv_get();
+	fuse_fs_init(ic->next, conn);
+	return ic;
+}
+
+static void iconv_destroy(void *data)
+{
+	struct iconv *ic = data;
+	fuse_fs_destroy(ic->next);
+	iconv_close(ic->tofs);
+	iconv_close(ic->fromfs);
+	pthread_mutex_destroy(&ic->lock);
+	free(ic->from_code);
+	free(ic->to_code);
+	free(ic);
+}
+
+static const struct fuse_operations iconv_oper = {
+	.destroy	= iconv_destroy,
+	.init		= iconv_init,
+	.getattr	= iconv_getattr,
+	.fgetattr	= iconv_fgetattr,
+	.access		= iconv_access,
+	.readlink	= iconv_readlink,
+	.opendir	= iconv_opendir,
+	.readdir	= iconv_readdir,
+	.releasedir	= iconv_releasedir,
+	.mknod		= iconv_mknod,
+	.mkdir		= iconv_mkdir,
+	.symlink	= iconv_symlink,
+	.unlink		= iconv_unlink,
+	.rmdir		= iconv_rmdir,
+	.rename		= iconv_rename,
+	.link		= iconv_link,
+	.chmod		= iconv_chmod,
+	.chown		= iconv_chown,
+	.truncate	= iconv_truncate,
+	.ftruncate	= iconv_ftruncate,
+	.utimens	= iconv_utimens,
+	.create		= iconv_create,
+	.open		= iconv_open_file,
+	.read_buf	= iconv_read_buf,
+	.write_buf	= iconv_write_buf,
+	.statfs		= iconv_statfs,
+	.flush		= iconv_flush,
+	.release	= iconv_release,
+	.fsync		= iconv_fsync,
+	.fsyncdir	= iconv_fsyncdir,
+	.setxattr	= iconv_setxattr,
+	.getxattr	= iconv_getxattr,
+	.listxattr	= iconv_listxattr,
+	.removexattr	= iconv_removexattr,
+	.lock		= iconv_lock,
+	.flock		= iconv_flock,
+	.bmap		= iconv_bmap,
+
+	.flag_nullpath_ok = 1,
+	.flag_nopath = 1,
+};
+
+static const struct fuse_opt iconv_opts[] = {
+	FUSE_OPT_KEY("-h", 0),
+	FUSE_OPT_KEY("--help", 0),
+	{ "from_code=%s", offsetof(struct iconv, from_code), 0 },
+	{ "to_code=%s", offsetof(struct iconv, to_code), 1 },
+	FUSE_OPT_END
+};
+
+static void iconv_help(void)
+{
+	char *old = strdup(setlocale(LC_CTYPE, ""));
+	char *charmap = strdup(nl_langinfo(CODESET));
+	setlocale(LC_CTYPE, old);
+	free(old);
+	fprintf(stderr,
+"    -o from_code=CHARSET   original encoding of file names (default: UTF-8)\n"
+"    -o to_code=CHARSET	    new encoding of the file names (default: %s)\n",
+		charmap);
+	free(charmap);
+}
+
+static int iconv_opt_proc(void *data, const char *arg, int key,
+			  struct fuse_args *outargs)
+{
+	(void) data; (void) arg; (void) outargs;
+
+	if (!key) {
+		iconv_help();
+		return -1;
+	}
+
+	return 1;
+}
+
+static struct fuse_fs *iconv_new(struct fuse_args *args,
+				 struct fuse_fs *next[])
+{
+	struct fuse_fs *fs;
+	struct iconv *ic;
+	char *old = NULL;
+	const char *from;
+	const char *to;
+
+	ic = calloc(1, sizeof(struct iconv));
+	if (ic == NULL) {
+		fprintf(stderr, "fuse-iconv: memory allocation failed\n");
+		return NULL;
+	}
+
+	if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+		goto out_free;
+
+	if (!next[0] || next[1]) {
+		fprintf(stderr, "fuse-iconv: exactly one next filesystem required\n");
+		goto out_free;
+	}
+
+	from = ic->from_code ? ic->from_code : "UTF-8";
+	to = ic->to_code ? ic->to_code : "";
+	/* FIXME: detect charset equivalence? */
+	if (!to[0])
+		old = strdup(setlocale(LC_CTYPE, ""));
+	ic->tofs = iconv_open(from, to);
+	if (ic->tofs == (iconv_t) -1) {
+		fprintf(stderr, "fuse-iconv: cannot convert from %s to %s\n",
+			to, from);
+		goto out_free;
+	}
+	ic->fromfs = iconv_open(to, from);
+	if (ic->tofs == (iconv_t) -1) {
+		fprintf(stderr, "fuse-iconv: cannot convert from %s to %s\n",
+			from, to);
+		goto out_iconv_close_to;
+	}
+	if (old) {
+		setlocale(LC_CTYPE, old);
+		free(old);
+	}
+
+	ic->next = next[0];
+	fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+	if (!fs)
+		goto out_iconv_close_from;
+
+	return fs;
+
+out_iconv_close_from:
+	iconv_close(ic->fromfs);
+out_iconv_close_to:
+	iconv_close(ic->tofs);
+out_free:
+	free(ic->from_code);
+	free(ic->to_code);
+	free(ic);
+	if (old) {
+		setlocale(LC_CTYPE, old);
+		free(old);
+	}
+	return NULL;
+}
+
+FUSE_REGISTER_MODULE(iconv, iconv_new);
diff --git a/fuse/modules/subdir.c b/fuse/modules/subdir.c
new file mode 100644
index 0000000..05b3379
--- /dev/null
+++ b/fuse/modules/subdir.c
@@ -0,0 +1,697 @@
+/*
+  fuse subdir module: offset paths with a base directory
+  Copyright (C) 2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+#define FUSE_USE_VERSION 26
+
+#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+
+struct subdir {
+	char *base;
+	size_t baselen;
+	int rellinks;
+	struct fuse_fs *next;
+};
+
+static struct subdir *subdir_get(void)
+{
+	return fuse_get_context()->private_data;
+}
+
+static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+{
+	char *newpath = NULL;
+
+	if (path != NULL) {
+		unsigned newlen = d->baselen + strlen(path);
+
+		newpath = malloc(newlen + 2);
+		if (!newpath)
+			return -ENOMEM;
+
+		if (path[0] == '/')
+			path++;
+		strcpy(newpath, d->base);
+		strcpy(newpath + d->baselen, path);
+		if (!newpath[0])
+			strcpy(newpath, ".");
+	}
+	*newpathp = newpath;
+
+	return 0;
+}
+
+static int subdir_getattr(const char *path, struct stat *stbuf)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_getattr(d->next, newpath, stbuf);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_fgetattr(const char *path, struct stat *stbuf,
+			   struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_fgetattr(d->next, newpath, stbuf, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_access(const char *path, int mask)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_access(d->next, newpath, mask);
+		free(newpath);
+	}
+	return err;
+}
+
+
+static int count_components(const char *p)
+{
+	int ctr;
+
+	for (; *p == '/'; p++);
+	for (ctr = 0; *p; ctr++) {
+		for (; *p && *p != '/'; p++);
+		for (; *p == '/'; p++);
+	}
+	return ctr;
+}
+
+static void strip_common(const char **sp, const char **tp)
+{
+	const char *s = *sp;
+	const char *t = *tp;
+	do {
+		for (; *s == '/'; s++);
+		for (; *t == '/'; t++);
+		*tp = t;
+		*sp = s;
+		for (; *s == *t && *s && *s != '/'; s++, t++);
+	} while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+}
+
+static void transform_symlink(struct subdir *d, const char *path,
+			      char *buf, size_t size)
+{
+	const char *l = buf;
+	size_t llen;
+	char *s;
+	int dotdots;
+	int i;
+
+	if (l[0] != '/' || d->base[0] != '/')
+		return;
+
+	strip_common(&l, &path);
+	if (l - buf < (long) d->baselen)
+		return;
+
+	dotdots = count_components(path);
+	if (!dotdots)
+		return;
+	dotdots--;
+
+	llen = strlen(l);
+	if (dotdots * 3 + llen + 2 > size)
+		return;
+
+	s = buf + dotdots * 3;
+	if (llen)
+		memmove(s, l, llen + 1);
+	else if (!dotdots)
+		strcpy(s, ".");
+	else
+		*s = '\0';
+
+	for (s = buf, i = 0; i < dotdots; i++, s += 3)
+		memcpy(s, "../", 3);
+}
+
+
+static int subdir_readlink(const char *path, char *buf, size_t size)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_readlink(d->next, newpath, buf, size);
+		if (!err && d->rellinks)
+			transform_symlink(d, newpath, buf, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_opendir(d->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_readdir(const char *path, void *buf,
+			  fuse_fill_dir_t filler, loff_t offset,
+			  struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+				      fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_releasedir(d->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_mkdir(const char *path, mode_t mode)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_mkdir(d->next, newpath, mode);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_unlink(const char *path)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_unlink(d->next, newpath);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_rmdir(const char *path)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_rmdir(d->next, newpath);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_symlink(const char *from, const char *path)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_symlink(d->next, from, newpath);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_rename(const char *from, const char *to)
+{
+	struct subdir *d = subdir_get();
+	char *newfrom;
+	char *newto;
+	int err = subdir_addpath(d, from, &newfrom);
+	if (!err) {
+		err = subdir_addpath(d, to, &newto);
+		if (!err) {
+			err = fuse_fs_rename(d->next, newfrom, newto);
+			free(newto);
+		}
+		free(newfrom);
+	}
+	return err;
+}
+
+static int subdir_link(const char *from, const char *to)
+{
+	struct subdir *d = subdir_get();
+	char *newfrom;
+	char *newto;
+	int err = subdir_addpath(d, from, &newfrom);
+	if (!err) {
+		err = subdir_addpath(d, to, &newto);
+		if (!err) {
+			err = fuse_fs_link(d->next, newfrom, newto);
+			free(newto);
+		}
+		free(newfrom);
+	}
+	return err;
+}
+
+static int subdir_chmod(const char *path, mode_t mode)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_chmod(d->next, newpath, mode);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_chown(const char *path, uid_t uid, gid_t gid)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_chown(d->next, newpath, uid, gid);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_truncate(const char *path, loff_t size)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_truncate(d->next, newpath, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_ftruncate(const char *path, loff_t size,
+			    struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_ftruncate(d->next, newpath, size, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_utimens(const char *path, const struct timespec ts[2])
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_utimens(d->next, newpath, ts);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_create(const char *path, mode_t mode,
+			 struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_create(d->next, newpath, mode, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_open(const char *path, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_open(d->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+			   size_t size, loff_t offset, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+			loff_t offset, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_statfs(const char *path, struct statvfs *stbuf)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_statfs(d->next, newpath, stbuf);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_flush(const char *path, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_flush(d->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_release(const char *path, struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_release(d->next, newpath, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_fsync(const char *path, int isdatasync,
+			struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_fsyncdir(const char *path, int isdatasync,
+			   struct fuse_file_info *fi)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_setxattr(const char *path, const char *name,
+			   const char *value, size_t size, int flags)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+				       flags);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_getxattr(const char *path, const char *name, char *value,
+			   size_t size)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_listxattr(const char *path, char *list, size_t size)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_listxattr(d->next, newpath, list, size);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_removexattr(const char *path, const char *name)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_removexattr(d->next, newpath, name);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+		       struct flock *lock)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_flock(d->next, newpath, fi, op);
+		free(newpath);
+	}
+	return err;
+}
+
+static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+{
+	struct subdir *d = subdir_get();
+	char *newpath;
+	int err = subdir_addpath(d, path, &newpath);
+	if (!err) {
+		err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+		free(newpath);
+	}
+	return err;
+}
+
+static void *subdir_init(struct fuse_conn_info *conn)
+{
+	struct subdir *d = subdir_get();
+	fuse_fs_init(d->next, conn);
+	return d;
+}
+
+static void subdir_destroy(void *data)
+{
+	struct subdir *d = data;
+	fuse_fs_destroy(d->next);
+	free(d->base);
+	free(d);
+}
+
+static const struct fuse_operations subdir_oper = {
+	.destroy	= subdir_destroy,
+	.init		= subdir_init,
+	.getattr	= subdir_getattr,
+	.fgetattr	= subdir_fgetattr,
+	.access		= subdir_access,
+	.readlink	= subdir_readlink,
+	.opendir	= subdir_opendir,
+	.readdir	= subdir_readdir,
+	.releasedir	= subdir_releasedir,
+	.mknod		= subdir_mknod,
+	.mkdir		= subdir_mkdir,
+	.symlink	= subdir_symlink,
+	.unlink		= subdir_unlink,
+	.rmdir		= subdir_rmdir,
+	.rename		= subdir_rename,
+	.link		= subdir_link,
+	.chmod		= subdir_chmod,
+	.chown		= subdir_chown,
+	.truncate	= subdir_truncate,
+	.ftruncate	= subdir_ftruncate,
+	.utimens	= subdir_utimens,
+	.create		= subdir_create,
+	.open		= subdir_open,
+	.read_buf	= subdir_read_buf,
+	.write_buf	= subdir_write_buf,
+	.statfs		= subdir_statfs,
+	.flush		= subdir_flush,
+	.release	= subdir_release,
+	.fsync		= subdir_fsync,
+	.fsyncdir	= subdir_fsyncdir,
+	.setxattr	= subdir_setxattr,
+	.getxattr	= subdir_getxattr,
+	.listxattr	= subdir_listxattr,
+	.removexattr	= subdir_removexattr,
+	.lock		= subdir_lock,
+	.flock		= subdir_flock,
+	.bmap		= subdir_bmap,
+
+	.flag_nullpath_ok = 1,
+	.flag_nopath = 1,
+};
+
+static const struct fuse_opt subdir_opts[] = {
+	FUSE_OPT_KEY("-h", 0),
+	FUSE_OPT_KEY("--help", 0),
+	{ "subdir=%s", offsetof(struct subdir, base), 0 },
+	{ "rellinks", offsetof(struct subdir, rellinks), 1 },
+	{ "norellinks", offsetof(struct subdir, rellinks), 0 },
+	FUSE_OPT_END
+};
+
+static void subdir_help(void)
+{
+	fprintf(stderr,
+"    -o subdir=DIR	    prepend this directory to all paths (mandatory)\n"
+"    -o [no]rellinks	    transform absolute symlinks to relative\n");
+}
+
+static int subdir_opt_proc(void *data, const char *arg, int key,
+			   struct fuse_args *outargs)
+{
+	(void) data; (void) arg; (void) outargs;
+
+	if (!key) {
+		subdir_help();
+		return -1;
+	}
+
+	return 1;
+}
+
+static struct fuse_fs *subdir_new(struct fuse_args *args,
+				  struct fuse_fs *next[])
+{
+	struct fuse_fs *fs;
+	struct subdir *d;
+
+	d = calloc(1, sizeof(struct subdir));
+	if (d == NULL) {
+		fprintf(stderr, "fuse-subdir: memory allocation failed\n");
+		return NULL;
+	}
+
+	if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+		goto out_free;
+
+	if (!next[0] || next[1]) {
+		fprintf(stderr, "fuse-subdir: exactly one next filesystem required\n");
+		goto out_free;
+	}
+
+	if (!d->base) {
+		fprintf(stderr, "fuse-subdir: missing 'subdir' option\n");
+		goto out_free;
+	}
+
+	if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+		char *tmp = realloc(d->base, strlen(d->base) + 2);
+		if (!tmp) {
+			fprintf(stderr, "fuse-subdir: memory allocation failed\n");
+			goto out_free;
+		}
+		d->base = tmp;
+		strcat(d->base, "/");
+	}
+	d->baselen = strlen(d->base);
+	d->next = next[0];
+	fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+	if (!fs)
+		goto out_free;
+	return fs;
+
+out_free:
+	free(d->base);
+	free(d);
+	return NULL;
+}
+
+FUSE_REGISTER_MODULE(subdir, subdir_new);
diff --git a/fuse/mount.c b/fuse/mount.c
new file mode 100644
index 0000000..8736856
--- /dev/null
+++ b/fuse/mount.c
@@ -0,0 +1,644 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_misc.h"
+#include "fuse_opt.h"
+#include "fuse_common_compat.h"
+#include "mount_util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+
+#ifdef __NetBSD__
+#include <perfuse.h>
+
+#define MS_RDONLY 	MNT_RDONLY
+#define MS_NOSUID 	MNT_NOSUID
+#define MS_NODEV 	MNT_NODEV
+#define MS_NOEXEC 	MNT_NOEXEC
+#define MS_SYNCHRONOUS 	MNT_SYNCHRONOUS
+#define MS_NOATIME 	MNT_NOATIME
+
+#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+#endif
+
+#define FUSERMOUNT_PROG		"fusermount"
+#define FUSE_COMMFD_ENV		"_FUSE_COMMFD"
+
+#if defined(__ANDROID__) && !defined(FUSERMOUNT_DIR)
+# define FUSERMOUNT_DIR "/system/xbin"
+#endif
+
+#ifndef HAVE_FORK
+#define fork() vfork()
+#endif
+
+#ifndef MS_DIRSYNC
+#define MS_DIRSYNC 128
+#endif
+
+enum {
+	KEY_KERN_FLAG,
+	KEY_KERN_OPT,
+	KEY_FUSERMOUNT_OPT,
+	KEY_SUBTYPE_OPT,
+	KEY_MTAB_OPT,
+	KEY_ALLOW_ROOT,
+	KEY_RO,
+	KEY_HELP,
+	KEY_VERSION,
+};
+
+struct mount_opts {
+	int allow_other;
+	int allow_root;
+	int ishelp;
+	int flags;
+	int nonempty;
+	int auto_unmount;
+	int blkdev;
+	char *fsname;
+	char *subtype;
+	char *subtype_opt;
+	char *mtab_opts;
+	char *fusermount_opts;
+	char *kernel_opts;
+};
+
+#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
+
+static const struct fuse_opt fuse_mount_opts[] = {
+	FUSE_MOUNT_OPT("allow_other",		allow_other),
+	FUSE_MOUNT_OPT("allow_root",		allow_root),
+	FUSE_MOUNT_OPT("nonempty",		nonempty),
+	FUSE_MOUNT_OPT("blkdev",		blkdev),
+	FUSE_MOUNT_OPT("auto_unmount",		auto_unmount),
+	FUSE_MOUNT_OPT("fsname=%s",		fsname),
+	FUSE_MOUNT_OPT("subtype=%s",		subtype),
+	FUSE_OPT_KEY("allow_other",		KEY_KERN_OPT),
+	FUSE_OPT_KEY("allow_root",		KEY_ALLOW_ROOT),
+	FUSE_OPT_KEY("nonempty",		KEY_FUSERMOUNT_OPT),
+	FUSE_OPT_KEY("auto_unmount",		KEY_FUSERMOUNT_OPT),
+	FUSE_OPT_KEY("blkdev",			KEY_FUSERMOUNT_OPT),
+	FUSE_OPT_KEY("fsname=",			KEY_FUSERMOUNT_OPT),
+	FUSE_OPT_KEY("subtype=",		KEY_SUBTYPE_OPT),
+	FUSE_OPT_KEY("large_read",		KEY_KERN_OPT),
+	FUSE_OPT_KEY("blksize=",		KEY_KERN_OPT),
+	FUSE_OPT_KEY("default_permissions",	KEY_KERN_OPT),
+	FUSE_OPT_KEY("max_read=",		KEY_KERN_OPT),
+	FUSE_OPT_KEY("max_read=",		FUSE_OPT_KEY_KEEP),
+	FUSE_OPT_KEY("user=",			KEY_MTAB_OPT),
+	FUSE_OPT_KEY("-r",			KEY_RO),
+	FUSE_OPT_KEY("ro",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("rw",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("suid",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("nosuid",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("dev",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("nodev",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("exec",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("noexec",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("async",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("sync",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("dirsync",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("atime",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("noatime",			KEY_KERN_FLAG),
+	FUSE_OPT_KEY("-h",			KEY_HELP),
+	FUSE_OPT_KEY("--help",			KEY_HELP),
+	FUSE_OPT_KEY("-V",			KEY_VERSION),
+	FUSE_OPT_KEY("--version",		KEY_VERSION),
+	FUSE_OPT_END
+};
+
+static void mount_help(void)
+{
+	fprintf(stderr,
+"    -o allow_other         allow access to other users\n"
+"    -o allow_root          allow access to root\n"
+"    -o auto_unmount        auto unmount on process termination\n"
+"    -o nonempty            allow mounts over non-empty file/dir\n"
+"    -o default_permissions enable permission checking by kernel\n"
+"    -o fsname=NAME         set filesystem name\n"
+"    -o subtype=NAME        set filesystem type\n"
+"    -o large_read          issue large read requests (2.4 only)\n"
+"    -o max_read=N          set maximum size of read requests\n"
+"\n");
+}
+
+static void exec_fusermount(const char *argv[])
+{
+	execv(FUSERMOUNT_DIR "/" FUSERMOUNT_PROG, (char **) argv);
+	execvp(FUSERMOUNT_PROG, (char **) argv);
+}
+
+static void mount_version(void)
+{
+	int pid = fork();
+	if (!pid) {
+		const char *argv[] = { FUSERMOUNT_PROG, "--version", NULL };
+		exec_fusermount(argv);
+		_exit(1);
+	} else if (pid != -1)
+		waitpid(pid, NULL, 0);
+}
+
+struct mount_flags {
+	const char *opt;
+	unsigned long flag;
+	int on;
+};
+
+static const struct mount_flags mount_flags[] = {
+	{"rw",	    MS_RDONLY,	    0},
+	{"ro",	    MS_RDONLY,	    1},
+	{"suid",    MS_NOSUID,	    0},
+	{"nosuid",  MS_NOSUID,	    1},
+	{"dev",	    MS_NODEV,	    0},
+	{"nodev",   MS_NODEV,	    1},
+	{"exec",    MS_NOEXEC,	    0},
+	{"noexec",  MS_NOEXEC,	    1},
+	{"async",   MS_SYNCHRONOUS, 0},
+	{"sync",    MS_SYNCHRONOUS, 1},
+	{"atime",   MS_NOATIME,	    0},
+	{"noatime", MS_NOATIME,	    1},
+#ifndef __NetBSD__
+	{"dirsync", MS_DIRSYNC,	    1},
+#endif
+	{NULL,	    0,		    0}
+};
+
+static void set_mount_flag(const char *s, int *flags)
+{
+	int i;
+
+	for (i = 0; mount_flags[i].opt != NULL; i++) {
+		const char *opt = mount_flags[i].opt;
+		if (strcmp(opt, s) == 0) {
+			if (mount_flags[i].on)
+				*flags |= mount_flags[i].flag;
+			else
+				*flags &= ~mount_flags[i].flag;
+			return;
+		}
+	}
+	fprintf(stderr, "fuse: internal error, can't find mount flag\n");
+	abort();
+}
+
+static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+			       struct fuse_args *outargs)
+{
+	struct mount_opts *mo = data;
+
+	switch (key) {
+	case KEY_ALLOW_ROOT:
+		if (fuse_opt_add_opt(&mo->kernel_opts, "allow_other") == -1 ||
+		    fuse_opt_add_arg(outargs, "-oallow_root") == -1)
+			return -1;
+		return 0;
+
+	case KEY_RO:
+		arg = "ro";
+		/* fall through */
+	case KEY_KERN_FLAG:
+		set_mount_flag(arg, &mo->flags);
+		return 0;
+
+	case KEY_KERN_OPT:
+		return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
+	case KEY_FUSERMOUNT_OPT:
+		return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
+
+	case KEY_SUBTYPE_OPT:
+		return fuse_opt_add_opt(&mo->subtype_opt, arg);
+
+	case KEY_MTAB_OPT:
+		return fuse_opt_add_opt(&mo->mtab_opts, arg);
+
+	case KEY_HELP:
+		mount_help();
+		mo->ishelp = 1;
+		break;
+
+	case KEY_VERSION:
+		mount_version();
+		mo->ishelp = 1;
+		break;
+	}
+	return 1;
+}
+
+/* return value:
+ * >= 0	 => fd
+ * -1	 => error
+ */
+static int receive_fd(int fd)
+{
+	struct msghdr msg;
+	struct iovec iov;
+	char buf[1];
+	int rv;
+	size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+	struct cmsghdr *cmsg;
+
+	iov.iov_base = buf;
+	iov.iov_len = 1;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.msg_name = 0;
+	msg.msg_namelen = 0;
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	/* old BSD implementations should use msg_accrights instead of
+	 * msg_control; the interface is different. */
+	msg.msg_control = ccmsg;
+	msg.msg_controllen = sizeof(ccmsg);
+
+	while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+	if (rv == -1) {
+		perror("recvmsg");
+		return -1;
+	}
+	if(!rv) {
+		/* EOF */
+		return -1;
+	}
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	if (cmsg->cmsg_type != SCM_RIGHTS) {
+		fprintf(stderr, "got control message of unknown type %d\n",
+			cmsg->cmsg_type);
+		return -1;
+	}
+	return *(int*)CMSG_DATA(cmsg);
+}
+
+void fuse_kern_unmount(const char *mountpoint, int fd)
+{
+	int res;
+	int pid;
+
+	if (!mountpoint)
+		return;
+
+	if (fd != -1) {
+		struct pollfd pfd;
+
+		pfd.fd = fd;
+		pfd.events = 0;
+		res = poll(&pfd, 1, 0);
+
+		/* Need to close file descriptor, otherwise synchronous umount
+		   would recurse into filesystem, and deadlock.
+
+		   Caller expects fuse_kern_unmount to close the fd, so close it
+		   anyway. */
+		close(fd);
+
+		/* If file poll returns POLLERR on the device file descriptor,
+		   then the filesystem is already unmounted */
+		if (res == 1 && (pfd.revents & POLLERR))
+			return;
+	}
+
+	if (geteuid() == 0) {
+		fuse_mnt_umount("fuse", mountpoint, mountpoint,  1);
+		return;
+	}
+
+	res = umount2(mountpoint, 2);
+	if (res == 0)
+		return;
+
+	pid = fork();
+	if(pid == -1)
+		return;
+
+	if(pid == 0) {
+		const char *argv[] = { FUSERMOUNT_PROG, "-u", "-q", "-z",
+				       "--", mountpoint, NULL };
+
+		exec_fusermount(argv);
+		_exit(1);
+	}
+	waitpid(pid, NULL, 0);
+}
+
+void fuse_unmount_compat22(const char *mountpoint)
+{
+	fuse_kern_unmount(mountpoint, -1);
+}
+
+static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+		const char *opts, int quiet)
+{
+	int fds[2], pid;
+	int res;
+	int rv;
+
+	if (!mountpoint) {
+		fprintf(stderr, "fuse: missing mountpoint parameter\n");
+		return -1;
+	}
+
+	res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+	if(res == -1) {
+		perror("fuse: socketpair() failed");
+		return -1;
+	}
+
+	pid = fork();
+	if(pid == -1) {
+		perror("fuse: fork() failed");
+		close(fds[0]);
+		close(fds[1]);
+		return -1;
+	}
+
+	if(pid == 0) {
+		char env[10];
+		const char *argv[32];
+		int a = 0;
+
+		if (quiet) {
+			int fd = open("/dev/null", O_RDONLY);
+			if (fd != -1) {
+				dup2(fd, 1);
+				dup2(fd, 2);
+			}
+		}
+
+		argv[a++] = FUSERMOUNT_PROG;
+		if (opts) {
+			argv[a++] = "-o";
+			argv[a++] = opts;
+		}
+		argv[a++] = "--";
+		argv[a++] = mountpoint;
+		argv[a++] = NULL;
+
+		close(fds[1]);
+		fcntl(fds[0], F_SETFD, 0);
+		snprintf(env, sizeof(env), "%i", fds[0]);
+		setenv(FUSE_COMMFD_ENV, env, 1);
+		exec_fusermount(argv);
+		perror("fuse: failed to exec fusermount");
+		_exit(1);
+	}
+
+	close(fds[0]);
+	rv = receive_fd(fds[1]);
+
+	if (!mo->auto_unmount) {
+		/* with auto_unmount option fusermount will not exit until 
+		   this socket is closed */
+		close(fds[1]);
+		waitpid(pid, NULL, 0); /* bury zombie */
+	}
+
+	return rv;
+}
+
+int fuse_mount_compat22(const char *mountpoint, const char *opts)
+{
+	struct mount_opts mo;
+	memset(&mo, 0, sizeof(mo));
+	mo.flags = MS_NOSUID | MS_NODEV;
+
+	return fuse_mount_fusermount(mountpoint, &mo, opts, 0);
+}
+
+static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+			  const char *mnt_opts)
+{
+	char tmp[128];
+	const char *devname = "/dev/fuse";
+	char *source = NULL;
+	char *type = NULL;
+	struct stat stbuf;
+	int fd;
+	int res;
+
+	if (!mnt) {
+		fprintf(stderr, "fuse: missing mountpoint parameter\n");
+		return -1;
+	}
+
+	res = stat(mnt, &stbuf);
+	if (res == -1) {
+		fprintf(stderr ,"fuse: failed to access mountpoint %s: %s\n",
+			mnt, strerror(errno));
+		return -1;
+	}
+
+	if (!mo->nonempty) {
+		res = fuse_mnt_check_empty("fuse", mnt, stbuf.st_mode,
+					   stbuf.st_size);
+		if (res == -1)
+			return -1;
+	}
+
+	if (mo->auto_unmount) {
+		/* Tell the caller to fallback to fusermount because
+		   auto-unmount does not work otherwise. */
+		return -2;
+	}
+
+	fd = open(devname, O_RDWR);
+	if (fd == -1) {
+		if (errno == ENODEV || errno == ENOENT)
+			fprintf(stderr, "fuse: device not found, try 'modprobe fuse' first\n");
+		else
+			fprintf(stderr, "fuse: failed to open %s: %s\n",
+				devname, strerror(errno));
+		return -1;
+	}
+
+	snprintf(tmp, sizeof(tmp),  "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+		 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
+
+	res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
+	if (res == -1)
+		goto out_close;
+
+	source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+			(mo->subtype ? strlen(mo->subtype) : 0) +
+			strlen(devname) + 32);
+
+	type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+	if (!type || !source) {
+		fprintf(stderr, "fuse: failed to allocate memory\n");
+		goto out_close;
+	}
+
+	strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+	if (mo->subtype) {
+		strcat(type, ".");
+		strcat(type, mo->subtype);
+	}
+	strcpy(source,
+	       mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
+	res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+	if (res == -1 && errno == ENODEV && mo->subtype) {
+		/* Probably missing subtype support */
+		strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+		if (mo->fsname) {
+			if (!mo->blkdev)
+				sprintf(source, "%s#%s", mo->subtype,
+					mo->fsname);
+		} else {
+			strcpy(source, type);
+		}
+		res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+	}
+	if (res == -1) {
+		/*
+		 * Maybe kernel doesn't support unprivileged mounts, in this
+		 * case try falling back to fusermount
+		 */
+		if (errno == EPERM) {
+			res = -2;
+		} else {
+			int errno_save = errno;
+			if (mo->blkdev && errno == ENODEV &&
+			    !fuse_mnt_check_fuseblk())
+				fprintf(stderr,
+					"fuse: 'fuseblk' support missing\n");
+			else
+				fprintf(stderr, "fuse: mount failed: %s\n",
+					strerror(errno_save));
+		}
+
+		goto out_close;
+	}
+
+#ifndef __NetBSD__
+#ifndef IGNORE_MTAB
+	if (geteuid() == 0) {
+		char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+		res = -1;
+		if (!newmnt)
+			goto out_umount;
+
+		res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+					 mnt_opts);
+		free(newmnt);
+		if (res == -1)
+			goto out_umount;
+	}
+#endif /* IGNORE_MTAB */
+#endif /* __NetBSD__ */
+	free(type);
+	free(source);
+
+	return fd;
+
+out_umount:
+	umount2(mnt, 2); /* lazy umount */
+out_close:
+	free(type);
+	free(source);
+	close(fd);
+	return res;
+}
+
+static int get_mnt_flag_opts(char **mnt_optsp, int flags)
+{
+	int i;
+
+	if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
+		return -1;
+
+	for (i = 0; mount_flags[i].opt != NULL; i++) {
+		if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+		    fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
+			return -1;
+	}
+	return 0;
+}
+
+int fuse_kern_mount(const char *mountpoint, struct fuse_args *args)
+{
+	struct mount_opts mo;
+	int res = -1;
+	char *mnt_opts = NULL;
+
+	memset(&mo, 0, sizeof(mo));
+	mo.flags = MS_NOSUID | MS_NODEV;
+
+	if (args &&
+	    fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+		return -1;
+
+	if (mo.allow_other && mo.allow_root) {
+		fprintf(stderr, "fuse: 'allow_other' and 'allow_root' options are mutually exclusive\n");
+		goto out;
+	}
+	res = 0;
+	if (mo.ishelp)
+		goto out;
+
+	res = -1;
+	if (get_mnt_flag_opts(&mnt_opts, mo.flags) == -1)
+		goto out;
+	if (mo.kernel_opts && fuse_opt_add_opt(&mnt_opts, mo.kernel_opts) == -1)
+		goto out;
+	if (mo.mtab_opts &&  fuse_opt_add_opt(&mnt_opts, mo.mtab_opts) == -1)
+		goto out;
+
+	res = fuse_mount_sys(mountpoint, &mo, mnt_opts);
+	if (res == -2) {
+		if (mo.fusermount_opts &&
+		    fuse_opt_add_opt(&mnt_opts, mo.fusermount_opts) == -1)
+			goto out;
+
+		if (mo.subtype) {
+			char *tmp_opts = NULL;
+
+			res = -1;
+			if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
+			    fuse_opt_add_opt(&tmp_opts, mo.subtype_opt) == -1) {
+				free(tmp_opts);
+				goto out;
+			}
+
+			res = fuse_mount_fusermount(mountpoint, &mo, tmp_opts, 1);
+			free(tmp_opts);
+			if (res == -1)
+				res = fuse_mount_fusermount(mountpoint, &mo,
+							    mnt_opts, 0);
+		} else {
+			res = fuse_mount_fusermount(mountpoint, &mo, mnt_opts, 0);
+		}
+	}
+out:
+	free(mnt_opts);
+	free(mo.fsname);
+	free(mo.subtype);
+	free(mo.fusermount_opts);
+	free(mo.subtype_opt);
+	free(mo.kernel_opts);
+	free(mo.mtab_opts);
+	return res;
+}
+
+FUSE_SYMVER(".symver fuse_mount_compat22,fuse_mount@FUSE_2.2");
+FUSE_SYMVER(".symver fuse_unmount_compat22,fuse_unmount@FUSE_2.2");
diff --git a/fuse/mount_bsd.c b/fuse/mount_bsd.c
new file mode 100644
index 0000000..3aec3e3
--- /dev/null
+++ b/fuse/mount_bsd.c
@@ -0,0 +1,391 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#include "fuse_i.h"
+#include "fuse_misc.h"
+#include "fuse_opt.h"
+
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <paths.h>
+#include <limits.h>
+
+#define FUSERMOUNT_PROG		"mount_fusefs"
+#define FUSE_DEV_TRUNK		"/dev/fuse"
+
+enum {
+	KEY_ALLOW_ROOT,
+	KEY_RO,
+	KEY_HELP,
+	KEY_VERSION,
+	KEY_KERN
+};
+
+struct mount_opts {
+	int allow_other;
+	int allow_root;
+	int ishelp;
+	char *kernel_opts;
+};
+
+#define FUSE_DUAL_OPT_KEY(templ, key) 				\
+	FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
+
+static const struct fuse_opt fuse_mount_opts[] = {
+	{ "allow_other", offsetof(struct mount_opts, allow_other), 1 },
+	{ "allow_root", offsetof(struct mount_opts, allow_root), 1 },
+	FUSE_OPT_KEY("allow_root",		KEY_ALLOW_ROOT),
+	FUSE_OPT_KEY("-r",			KEY_RO),
+	FUSE_OPT_KEY("-h",			KEY_HELP),
+	FUSE_OPT_KEY("--help",			KEY_HELP),
+	FUSE_OPT_KEY("-V",			KEY_VERSION),
+	FUSE_OPT_KEY("--version",		KEY_VERSION),
+	/* standard FreeBSD mount options */
+	FUSE_DUAL_OPT_KEY("dev",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("async",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("atime",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("dev",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("exec",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("suid",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("symfollow",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("rdonly",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("sync",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("union",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("userquota",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("groupquota",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("clusterr",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("clusterw",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("suiddir",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("snapshot",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("multilabel",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("acls",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("force",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("update",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("ro",			KEY_KERN),
+	FUSE_DUAL_OPT_KEY("rw",			KEY_KERN),
+	FUSE_DUAL_OPT_KEY("auto",		KEY_KERN),
+	/* options supported under both Linux and FBSD */
+	FUSE_DUAL_OPT_KEY("allow_other",	KEY_KERN),
+	FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
+	FUSE_OPT_KEY("max_read=",		KEY_KERN),
+	FUSE_OPT_KEY("subtype=",		KEY_KERN),
+	/* FBSD FUSE specific mount options */
+	FUSE_DUAL_OPT_KEY("private",		KEY_KERN),
+	FUSE_DUAL_OPT_KEY("neglect_shares",	KEY_KERN),
+	FUSE_DUAL_OPT_KEY("push_symlinks_in",	KEY_KERN),
+	FUSE_OPT_KEY("nosync_unmount",		KEY_KERN),
+	/* stock FBSD mountopt parsing routine lets anything be negated... */
+	/*
+	 * Linux specific mount options, but let just the mount util
+	 * handle them
+	 */
+	FUSE_OPT_KEY("fsname=",			KEY_KERN),
+	FUSE_OPT_KEY("nonempty",		KEY_KERN),
+	FUSE_OPT_KEY("large_read",		KEY_KERN),
+	FUSE_OPT_END
+};
+
+static void mount_help(void)
+{
+	fprintf(stderr,
+		"    -o allow_root          allow access to root\n"
+		);
+	system(FUSERMOUNT_PROG " --help");
+	fputc('\n', stderr);
+}
+
+static void mount_version(void)
+{
+	system(FUSERMOUNT_PROG " --version");
+}
+
+static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+			       struct fuse_args *outargs)
+{
+	struct mount_opts *mo = data;
+
+	switch (key) {
+	case KEY_ALLOW_ROOT:
+		if (fuse_opt_add_opt(&mo->kernel_opts, "allow_other") == -1 ||
+		    fuse_opt_add_arg(outargs, "-oallow_root") == -1)
+			return -1;
+		return 0;
+
+	case KEY_RO:
+		arg = "ro";
+		/* fall through */
+
+	case KEY_KERN:
+		return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
+	case KEY_HELP:
+		mount_help();
+		mo->ishelp = 1;
+		break;
+
+	case KEY_VERSION:
+		mount_version();
+		mo->ishelp = 1;
+		break;
+	}
+	return 1;
+}
+
+void fuse_unmount_compat22(const char *mountpoint)
+{
+	char dev[128];
+	char *ssc, *umount_cmd;
+	FILE *sf;
+	int rv;
+	char seekscript[] =
+		/* error message is annoying in help output */
+		"exec 2>/dev/null; "
+		"/usr/bin/fstat " FUSE_DEV_TRUNK "* | "
+		"/usr/bin/awk 'BEGIN{ getline; if (! ($3 == \"PID\" && $10 == \"NAME\")) exit 1; }; "
+		"              { if ($3 == %d) print $10; }' | "
+		"/usr/bin/sort | "
+		"/usr/bin/uniq | "
+		"/usr/bin/awk '{ i += 1; if (i > 1){ exit 1; }; printf; }; END{ if (i == 0) exit 1; }'";
+
+	(void) mountpoint;
+
+	/*
+	 * If we don't know the fd, we have to resort to the scripted
+	 * solution -- iterating over the fd-s is unpractical, as we
+	 * don't know how many of open files we have. (This could be
+	 * looked up in procfs -- however, that's optional on FBSD; or
+	 * read out from the kmem -- however, that's bound to
+	 * privileges (in fact, that's what happens when we call the
+	 * setgid kmem fstat(1) utility).
+	 */
+	if (asprintf(&ssc, seekscript, getpid()) == -1)
+		return;
+
+	errno = 0;
+	sf = popen(ssc, "r");
+	free(ssc);
+	if (! sf)
+		return;
+
+	fgets(dev, sizeof(dev), sf);
+	rv = pclose(sf);
+	if (rv)
+		return;
+
+	if (asprintf(&umount_cmd, "/sbin/umount %s", dev) == -1)
+		return;
+	system(umount_cmd);
+	free(umount_cmd);
+}
+
+static void do_unmount(char *dev, int fd)
+{
+	char device_path[SPECNAMELEN + 12];
+	const char *argv[4];
+	const char umount_cmd[] = "/sbin/umount";
+	pid_t pid;
+
+	snprintf(device_path, SPECNAMELEN + 12, _PATH_DEV "%s", dev);
+
+	argv[0] = umount_cmd;
+	argv[1] = "-f";
+	argv[2] = device_path;
+	argv[3] = NULL;
+
+	pid = fork();
+
+	if (pid == -1)
+		return;
+
+	if (pid == 0) {
+		close(fd);
+		execvp(umount_cmd, (char **)argv);
+		exit(1);
+	}
+
+	waitpid(pid, NULL, 0);
+}
+
+void fuse_kern_unmount(const char *mountpoint, int fd)
+{
+	char *ep, dev[128];
+	struct stat sbuf;
+
+	(void)mountpoint;
+
+	if (fstat(fd, &sbuf) == -1)
+		goto out;
+
+	devname_r(sbuf.st_rdev, S_IFCHR, dev, 128);
+
+	if (strncmp(dev, "fuse", 4))
+		goto out;
+
+	strtol(dev + 4, &ep, 10);
+	if (*ep != '\0')
+		goto out;
+
+	do_unmount(dev, fd);
+
+out:
+	close(fd);
+}
+
+/* Check if kernel is doing init in background */
+static int init_backgrounded(void)
+{
+	unsigned ibg, len;
+
+	len = sizeof(ibg);
+
+	if (sysctlbyname("vfs.fuse.init_backgrounded", &ibg, &len, NULL, 0))
+		return 0;
+
+	return ibg;
+}
+
+
+static int fuse_mount_core(const char *mountpoint, const char *opts)
+{
+	const char *mountprog = FUSERMOUNT_PROG;
+	int fd;
+	char *fdnam, *dev;
+	pid_t pid, cpid;
+	int status;
+
+	fdnam = getenv("FUSE_DEV_FD");
+
+	if (fdnam) {
+		char *ep;
+
+		fd = strtol(fdnam, &ep, 10);
+
+		if (*ep != '\0') {
+			fprintf(stderr, "invalid value given in FUSE_DEV_FD\n");
+			return -1;
+		}
+
+		if (fd < 0)
+			return -1;
+
+		goto mount;
+	}
+
+	dev = getenv("FUSE_DEV_NAME");
+
+	if (! dev)
+		dev = (char *)FUSE_DEV_TRUNK;
+
+	if ((fd = open(dev, O_RDWR)) < 0) {
+		perror("fuse: failed to open fuse device");
+		return -1;
+	}
+
+mount:
+	if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
+		goto out;
+
+	pid = fork();
+	cpid = pid;
+
+	if (pid == -1) {
+		perror("fuse: fork() failed");
+		close(fd);
+		return -1;
+	}
+
+	if (pid == 0) {
+		if (! init_backgrounded()) {
+			/*
+			 * If init is not backgrounded, we have to
+			 * call the mount util backgrounded, to avoid
+			 * deadlock.
+			 */
+
+			pid = fork();
+
+			if (pid == -1) {
+				perror("fuse: fork() failed");
+				close(fd);
+				exit(1);
+			}
+		}
+
+		if (pid == 0) {
+			const char *argv[32];
+			int a = 0;
+
+			if (! fdnam && asprintf(&fdnam, "%d", fd) == -1) {
+				perror("fuse: failed to assemble mount arguments");
+				exit(1);
+			}
+
+			argv[a++] = mountprog;
+			if (opts) {
+				argv[a++] = "-o";
+				argv[a++] = opts;
+			}
+			argv[a++] = fdnam;
+			argv[a++] = mountpoint;
+			argv[a++] = NULL;
+			execvp(mountprog, (char **) argv);
+			perror("fuse: failed to exec mount program");
+			exit(1);
+		}
+
+		exit(0);
+	}
+
+	if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
+		perror("fuse: failed to mount file system");
+		close(fd);
+		return -1;
+	}
+
+out:
+	return fd;
+}
+
+int fuse_kern_mount(const char *mountpoint, struct fuse_args *args)
+{
+	struct mount_opts mo;
+	int res = -1;
+
+	memset(&mo, 0, sizeof(mo));
+	/* mount util should not try to spawn the daemon */
+	setenv("MOUNT_FUSEFS_SAFE", "1", 1);
+	/* to notify the mount util it's called from lib */
+	setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
+
+	if (args &&
+	    fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+		return -1;
+
+	if (mo.allow_other && mo.allow_root) {
+		fprintf(stderr, "fuse: 'allow_other' and 'allow_root' options are mutually exclusive\n");
+		goto out;
+	}
+	if (mo.ishelp)
+		return 0;
+
+	res = fuse_mount_core(mountpoint, mo.kernel_opts);
+out:
+	free(mo.kernel_opts);
+	return res;
+}
+
+FUSE_SYMVER(".symver fuse_unmount_compat22,fuse_unmount@FUSE_2.2");
diff --git a/fuse/mount_util.c b/fuse/mount_util.c
new file mode 100644
index 0000000..020b223
--- /dev/null
+++ b/fuse/mount_util.c
@@ -0,0 +1,368 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#include "mount_util.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#ifndef __NetBSD__
+#include <mntent.h>
+#endif
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#if defined(__ANDROID__)
+#include <paths.h>
+#endif
+
+#ifdef __NetBSD__
+#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+#define mtab_needs_update(mnt) 0
+#elif defined(__ANDROID__)
+#define mtab_needs_update(mnt) 0
+#else
+static int mtab_needs_update(const char *mnt)
+{
+	int res;
+	struct stat stbuf;
+
+	/* If mtab is within new mount, don't touch it */
+	if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
+	    _PATH_MOUNTED[strlen(mnt)] == '/')
+		return 0;
+
+	/*
+	 * Skip mtab update if /etc/mtab:
+	 *
+	 *  - doesn't exist,
+	 *  - is a symlink,
+	 *  - is on a read-only filesystem.
+	 */
+	res = lstat(_PATH_MOUNTED, &stbuf);
+	if (res == -1) {
+		if (errno == ENOENT)
+			return 0;
+	} else {
+		uid_t ruid;
+		int err;
+
+		if (S_ISLNK(stbuf.st_mode))
+			return 0;
+
+		ruid = getuid();
+		if (ruid != 0)
+			setreuid(0, -1);
+
+		res = access(_PATH_MOUNTED, W_OK);
+		err = (res == -1) ? errno : 0;
+		if (ruid != 0)
+			setreuid(ruid, -1);
+
+		if (err == EROFS)
+			return 0;
+	}
+
+	return 1;
+}
+#endif /* __NetBSD__ */
+
+static int add_mount(const char *progname, const char *fsname,
+		       const char *mnt, const char *type, const char *opts)
+{
+	int res;
+	int status;
+	sigset_t blockmask;
+	sigset_t oldmask;
+
+	sigemptyset(&blockmask);
+	sigaddset(&blockmask, SIGCHLD);
+	res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+	if (res == -1) {
+		fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+		return -1;
+	}
+
+	res = fork();
+	if (res == -1) {
+		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+		goto out_restore;
+	}
+	if (res == 0) {
+		char *env = NULL;
+
+		sigprocmask(SIG_SETMASK, &oldmask, NULL);
+		setuid(geteuid());
+		execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
+		       "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
+		fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
+			progname, strerror(errno));
+		exit(1);
+	}
+	res = waitpid(res, &status, 0);
+	if (res == -1)
+		fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
+	if (status != 0)
+		res = -1;
+
+ out_restore:
+	sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+	return res;
+}
+
+int fuse_mnt_add_mount(const char *progname, const char *fsname,
+		       const char *mnt, const char *type, const char *opts)
+{
+	if (!mtab_needs_update(mnt))
+		return 0;
+
+	return add_mount(progname, fsname, mnt, type, opts);
+}
+
+static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
+{
+	int res;
+	int status;
+	sigset_t blockmask;
+	sigset_t oldmask;
+
+	sigemptyset(&blockmask);
+	sigaddset(&blockmask, SIGCHLD);
+	res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+	if (res == -1) {
+		fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+		return -1;
+	}
+
+	res = fork();
+	if (res == -1) {
+		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+		goto out_restore;
+	}
+	if (res == 0) {
+		char *env = NULL;
+
+		sigprocmask(SIG_SETMASK, &oldmask, NULL);
+		setuid(geteuid());
+		if (lazy) {
+			execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+			       "-l", NULL, &env);
+		} else {
+			execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+			       NULL, &env);
+		}
+		fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+			progname, strerror(errno));
+		exit(1);
+	}
+	res = waitpid(res, &status, 0);
+	if (res == -1)
+		fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
+	if (status != 0) {
+		res = -1;
+	}
+
+ out_restore:
+	sigprocmask(SIG_SETMASK, &oldmask, NULL);
+	return res;
+
+}
+
+int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+		    const char *rel_mnt, int lazy)
+{
+	int res;
+
+	if (!mtab_needs_update(abs_mnt)) {
+		res = umount2(rel_mnt, lazy ? 2 : 0);
+		if (res == -1)
+			fprintf(stderr, "%s: failed to unmount %s: %s\n",
+				progname, abs_mnt, strerror(errno));
+		return res;
+	}
+
+	return exec_umount(progname, rel_mnt, lazy);
+}
+
+static int remove_mount(const char *progname, const char *mnt)
+{
+	int res;
+	int status;
+	sigset_t blockmask;
+	sigset_t oldmask;
+
+	sigemptyset(&blockmask);
+	sigaddset(&blockmask, SIGCHLD);
+	res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+	if (res == -1) {
+		fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+		return -1;
+	}
+
+	res = fork();
+	if (res == -1) {
+		fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+		goto out_restore;
+	}
+	if (res == 0) {
+		char *env = NULL;
+
+		sigprocmask(SIG_SETMASK, &oldmask, NULL);
+		setuid(geteuid());
+		execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+		       "--fake", mnt, NULL, &env);
+		fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+			progname, strerror(errno));
+		exit(1);
+	}
+	res = waitpid(res, &status, 0);
+	if (res == -1)
+		fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
+	if (status != 0)
+		res = -1;
+
+ out_restore:
+	sigprocmask(SIG_SETMASK, &oldmask, NULL);
+	return res;
+}
+
+int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+{
+	if (!mtab_needs_update(mnt))
+		return 0;
+
+	return remove_mount(progname, mnt);
+}
+
+char *fuse_mnt_resolve_path(const char *progname, const char *orig)
+{
+	char buf[PATH_MAX];
+	char *copy;
+	char *dst;
+	char *end;
+	char *lastcomp;
+	const char *toresolv;
+
+	if (!orig[0]) {
+		fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
+			orig);
+		return NULL;
+	}
+
+	copy = strdup(orig);
+	if (copy == NULL) {
+		fprintf(stderr, "%s: failed to allocate memory\n", progname);
+		return NULL;
+	}
+
+	toresolv = copy;
+	lastcomp = NULL;
+	for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
+	if (end[0] != '/') {
+		char *tmp;
+		end[1] = '\0';
+		tmp = strrchr(copy, '/');
+		if (tmp == NULL) {
+			lastcomp = copy;
+			toresolv = ".";
+		} else {
+			lastcomp = tmp + 1;
+			if (tmp == copy)
+				toresolv = "/";
+		}
+		if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
+			lastcomp = NULL;
+			toresolv = copy;
+		}
+		else if (tmp)
+			tmp[0] = '\0';
+	}
+	if (realpath(toresolv, buf) == NULL) {
+		fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
+			strerror(errno));
+		free(copy);
+		return NULL;
+	}
+	if (lastcomp == NULL)
+		dst = strdup(buf);
+	else {
+		dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
+		if (dst) {
+			unsigned buflen = strlen(buf);
+			if (buflen && buf[buflen-1] == '/')
+				sprintf(dst, "%s%s", buf, lastcomp);
+			else
+				sprintf(dst, "%s/%s", buf, lastcomp);
+		}
+	}
+	free(copy);
+	if (dst == NULL)
+		fprintf(stderr, "%s: failed to allocate memory\n", progname);
+	return dst;
+}
+
+int fuse_mnt_check_empty(const char *progname, const char *mnt,
+			 mode_t rootmode, loff_t rootsize)
+{
+	int isempty = 1;
+
+	if (S_ISDIR(rootmode)) {
+		struct dirent *ent;
+		DIR *dp = opendir(mnt);
+		if (dp == NULL) {
+			fprintf(stderr,
+				"%s: failed to open mountpoint for reading: %s\n",
+				progname, strerror(errno));
+			return -1;
+		}
+		while ((ent = readdir(dp)) != NULL) {
+			if (strcmp(ent->d_name, ".") != 0 &&
+			    strcmp(ent->d_name, "..") != 0) {
+				isempty = 0;
+				break;
+			}
+		}
+		closedir(dp);
+	} else if (rootsize)
+		isempty = 0;
+
+	if (!isempty) {
+		fprintf(stderr, "%s: mountpoint is not empty\n", progname);
+		fprintf(stderr, "%s: if you are sure this is safe, use the 'nonempty' mount option\n", progname);
+		return -1;
+	}
+	return 0;
+}
+
+int fuse_mnt_check_fuseblk(void)
+{
+	char buf[256];
+	FILE *f = fopen("/proc/filesystems", "r");
+	if (!f)
+		return 1;
+
+	while (fgets(buf, sizeof(buf), f))
+		if (strstr(buf, "fuseblk\n")) {
+			fclose(f);
+			return 1;
+		}
+
+	fclose(f);
+	return 0;
+}
diff --git a/fuse/mount_util.h b/fuse/mount_util.h
new file mode 100644
index 0000000..0e0f564
--- /dev/null
+++ b/fuse/mount_util.h
@@ -0,0 +1,19 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB.
+*/
+
+#include <sys/types.h>
+
+int fuse_mnt_add_mount(const char *progname, const char *fsname,
+		       const char *mnt, const char *type, const char *opts);
+int fuse_mnt_remove_mount(const char *progname, const char *mnt);
+int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+		    const char *rel_mnt, int lazy);
+char *fuse_mnt_resolve_path(const char *progname, const char *orig);
+int fuse_mnt_check_empty(const char *progname, const char *mnt,
+			 mode_t rootmode, loff_t rootsize);
+int fuse_mnt_check_fuseblk(void);
diff --git a/fuse/ulockmgr.c b/fuse/ulockmgr.c
new file mode 100644
index 0000000..ebd68c6
--- /dev/null
+++ b/fuse/ulockmgr.c
@@ -0,0 +1,444 @@
+/*
+  libulockmgr: Userspace Lock Manager Library
+  Copyright (C) 2006  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU LGPLv2.
+  See the file COPYING.LIB
+*/
+
+/* #define DEBUG 1 */
+
+#include "ulockmgr.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <errno.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+struct message {
+	unsigned intr : 1;
+	unsigned nofd : 1;
+	pthread_t thr;
+	int cmd;
+	int fd;
+	struct flock lock;
+	int error;
+};
+
+struct fd_store {
+	struct fd_store *next;
+	int fd;
+	int inuse;
+};
+
+struct owner {
+	struct owner *next;
+	struct owner *prev;
+	struct fd_store *fds;
+	void *id;
+	size_t id_len;
+	int cfd;
+};
+
+static pthread_mutex_t ulockmgr_lock;
+static int ulockmgr_cfd = -1;
+static struct owner owner_list = { .next = &owner_list, .prev = &owner_list };
+
+#define MAX_SEND_FDS 2
+
+static void list_del_owner(struct owner *owner)
+{
+	struct owner *prev = owner->prev;
+	struct owner *next = owner->next;
+	prev->next = next;
+	next->prev = prev;
+}
+
+static void list_add_owner(struct owner *owner, struct owner *next)
+{
+	struct owner *prev = next->prev;
+	owner->next = next;
+	owner->prev = prev;
+	prev->next = owner;
+	next->prev = owner;
+}
+
+/*
+ * There's a bug in the linux kernel (< 2.6.22) recv() implementation
+ * on AF_UNIX, SOCK_STREAM sockets, that could cause it to return
+ * zero, even if data was available.  Retrying the recv will return
+ * the data in this case.
+ */
+static int do_recv(int sock, void *buf, size_t len, int flags)
+{
+	int res = recv(sock, buf, len, flags);
+	if (res == 0)
+		res = recv(sock, buf, len, flags);
+
+	return res;
+}
+
+static int ulockmgr_send_message(int sock, void *buf, size_t buflen,
+				 int *fdp, int numfds)
+{
+	struct msghdr msg;
+	struct cmsghdr *p_cmsg;
+	struct iovec vec;
+	size_t cmsgbuf[CMSG_SPACE(sizeof(int) * MAX_SEND_FDS) / sizeof(size_t)];
+	int res;
+
+	assert(numfds <= MAX_SEND_FDS);
+	msg.msg_control = cmsgbuf;
+	msg.msg_controllen = sizeof(cmsgbuf);
+	p_cmsg = CMSG_FIRSTHDR(&msg);
+	p_cmsg->cmsg_level = SOL_SOCKET;
+	p_cmsg->cmsg_type = SCM_RIGHTS;
+	p_cmsg->cmsg_len = CMSG_LEN(sizeof(int) * numfds);
+	memcpy(CMSG_DATA(p_cmsg), fdp, sizeof(int) * numfds);
+	msg.msg_controllen = p_cmsg->cmsg_len;
+	msg.msg_name = NULL;
+	msg.msg_namelen = 0;
+	msg.msg_iov = &vec;
+	msg.msg_iovlen = 1;
+	msg.msg_flags = 0;
+	vec.iov_base = buf;
+	vec.iov_len = buflen;
+	res = sendmsg(sock, &msg, MSG_NOSIGNAL);
+	if (res == -1) {
+		perror("libulockmgr: sendmsg");
+		return -1;
+	}
+	if ((size_t) res != buflen) {
+		fprintf(stderr, "libulockmgr: sendmsg short\n");
+		return -1;
+	}
+	return 0;
+}
+
+static int ulockmgr_start_daemon(void)
+{
+	int sv[2];
+	int res;
+	char tmp[64];
+
+	res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
+	if (res == -1) {
+		perror("libulockmgr: socketpair");
+		return -1;
+	}
+	snprintf(tmp, sizeof(tmp), "exec ulockmgr_server %i", sv[0]);
+	res = system(tmp);
+	close(sv[0]);
+	if (res == -1 || !WIFEXITED(res) || WEXITSTATUS(res) != 0) {
+		close(sv[1]);
+		return -1;
+	}
+	ulockmgr_cfd = sv[1];
+	return 0;
+}
+
+static struct owner *ulockmgr_new_owner(const void *id, size_t id_len)
+{
+	int sv[2];
+	int res;
+	char c = 'm';
+	struct owner *o;
+
+	if (ulockmgr_cfd == -1 && ulockmgr_start_daemon() == -1)
+		return NULL;
+
+	o = calloc(1, sizeof(struct owner) + id_len);
+	if (!o) {
+		fprintf(stderr, "libulockmgr: failed to allocate memory\n");
+		return NULL;
+	}
+	o->id = o + 1;
+	o->id_len = id_len;
+	res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
+	if (res == -1) {
+		perror("libulockmgr: socketpair");
+		goto out_free;
+	}
+	res = ulockmgr_send_message(ulockmgr_cfd, &c, sizeof(c), &sv[0], 1);
+	close(sv[0]);
+	if (res == -1) {
+		close(ulockmgr_cfd);
+		ulockmgr_cfd = -1;
+		goto out_close;
+	}
+
+	o->cfd = sv[1];
+	memcpy(o->id, id, id_len);
+	list_add_owner(o, &owner_list);
+
+	return o;
+
+out_close:
+	close(sv[1]);
+out_free:
+	free(o);
+	return NULL;
+}
+
+static int ulockmgr_send_request(struct message *msg, const void *id,
+				 size_t id_len)
+{
+	int sv[2];
+	int cfd;
+	struct owner *o;
+	struct fd_store *f = NULL;
+	struct fd_store *newf = NULL;
+	struct fd_store **fp;
+	int fd = msg->fd;
+	int cmd = msg->cmd;
+	int res;
+	int unlockall = (cmd == F_SETLK && msg->lock.l_type == F_UNLCK &&
+			 msg->lock.l_start == 0 && msg->lock.l_len == 0);
+
+	for (o = owner_list.next; o != &owner_list; o = o->next)
+		if (o->id_len == id_len && memcmp(o->id, id, id_len) == 0)
+			break;
+
+	if (o == &owner_list)
+		o = NULL;
+
+	if (!o && cmd != F_GETLK && msg->lock.l_type != F_UNLCK)
+		o = ulockmgr_new_owner(id, id_len);
+
+	if (!o) {
+		if (cmd == F_GETLK) {
+			res = fcntl(msg->fd, F_GETLK, &msg->lock);
+			return (res == -1) ? -errno : 0;
+		} else if (msg->lock.l_type == F_UNLCK)
+			return 0;
+		else
+			return -ENOLCK;
+	}
+
+	if (unlockall)
+		msg->nofd = 1;
+	else {
+		for (fp = &o->fds; *fp; fp = &(*fp)->next) {
+			f = *fp;
+			if (f->fd == fd) {
+				msg->nofd = 1;
+				break;
+			}
+		}
+	}
+
+	if (!msg->nofd) {
+		newf = f = calloc(1, sizeof(struct fd_store));
+		if (!f) {
+			fprintf(stderr, "libulockmgr: failed to allocate memory\n");
+			return -ENOLCK;
+		}
+	}
+
+	res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
+	if (res == -1) {
+		perror("libulockmgr: socketpair");
+		free(newf);
+		return -ENOLCK;
+	}
+
+	cfd = sv[1];
+	sv[1] = msg->fd;
+	res = ulockmgr_send_message(o->cfd, msg, sizeof(struct message), sv,
+				    msg->nofd ? 1 : 2);
+	close(sv[0]);
+	if (res == -1) {
+		free(newf);
+		close(cfd);
+		return -EIO;
+	}
+
+	if (newf) {
+		newf->fd = msg->fd;
+		newf->next = o->fds;
+		o->fds = newf;
+	}
+	if (f)
+		f->inuse++;
+
+	res = do_recv(cfd, msg, sizeof(struct message), MSG_WAITALL);
+	if (res == -1) {
+		perror("libulockmgr: recv");
+		msg->error = EIO;
+	} else if (res != sizeof(struct message)) {
+		fprintf(stderr, "libulockmgr: recv short\n");
+		msg->error = EIO;
+	} else if (cmd == F_SETLKW && msg->error == EAGAIN) {
+		pthread_mutex_unlock(&ulockmgr_lock);
+		while (1) {
+			sigset_t old;
+			sigset_t unblock;
+			int errno_save;
+
+			sigemptyset(&unblock);
+			sigaddset(&unblock, SIGUSR1);
+			pthread_sigmask(SIG_UNBLOCK, &unblock, &old);
+			res = do_recv(cfd, msg, sizeof(struct message),
+				      MSG_WAITALL);
+			errno_save = errno;
+			pthread_sigmask(SIG_SETMASK, &old, NULL);
+			if (res == sizeof(struct message))
+				break;
+			else if (res >= 0) {
+				fprintf(stderr, "libulockmgr: recv short\n");
+				msg->error = EIO;
+				break;
+			} else if (errno_save != EINTR) {
+				errno = errno_save;
+				perror("libulockmgr: recv");
+				msg->error = EIO;
+				break;
+			}
+			msg->intr = 1;
+			res = send(o->cfd, msg, sizeof(struct message),
+				   MSG_NOSIGNAL);
+			if (res == -1) {
+				perror("libulockmgr: send");
+				msg->error = EIO;
+				break;
+			}
+			if (res != sizeof(struct message)) {
+				fprintf(stderr, "libulockmgr: send short\n");
+				msg->error = EIO;
+				break;
+			}
+		}
+		pthread_mutex_lock(&ulockmgr_lock);
+
+	}
+	if (f)
+		f->inuse--;
+	close(cfd);
+	if (unlockall) {
+		for (fp = &o->fds; *fp;) {
+			f = *fp;
+			if (f->fd == fd && !f->inuse) {
+				*fp = f->next;
+				free(f);
+			} else
+				fp = &f->next;
+		}
+		if (!o->fds) {
+			list_del_owner(o);
+			close(o->cfd);
+			free(o);
+		}
+		/* Force OK on unlock-all, since it _will_ succeed once the
+		   owner is deleted */
+		msg->error = 0;
+	}
+
+	return -msg->error;
+}
+
+#ifdef DEBUG
+static uint32_t owner_hash(const unsigned char *id, size_t id_len)
+{
+	uint32_t h = 0;
+	size_t i;
+	for (i = 0; i < id_len; i++)
+		h = ((h << 8) | (h >> 24)) ^ id[i];
+
+	return h;
+}
+#endif
+
+static int ulockmgr_canonicalize(int fd, struct flock *lock)
+{
+	loff_t offset;
+	if (lock->l_whence == SEEK_CUR) {
+		offset = lseek(fd, 0, SEEK_CUR);
+		if (offset == (loff_t) -1)
+			return -errno;
+	} else if (lock->l_whence == SEEK_END) {
+		struct stat stbuf;
+		int res = fstat(fd, &stbuf);
+		if (res == -1)
+			return -errno;
+
+		offset = stbuf.st_size;
+	} else
+		offset = 0;
+
+	lock->l_whence = SEEK_SET;
+	lock->l_start += offset;
+
+	if (lock->l_start < 0)
+		return -EINVAL;
+
+	if (lock->l_len < 0) {
+		lock->l_start += lock->l_len;
+		if (lock->l_start < 0)
+			return -EINVAL;
+		lock->l_len = -lock->l_len;
+	}
+	if (lock->l_len && lock->l_start + lock->l_len - 1 < 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+int ulockmgr_op(int fd, int cmd, struct flock *lock, const void *owner,
+		size_t owner_len)
+{
+	int err;
+	struct message msg;
+	sigset_t old;
+	sigset_t block;
+
+	if (cmd != F_GETLK && cmd != F_SETLK && cmd != F_SETLKW)
+		return -EINVAL;
+
+	if (lock->l_type != F_RDLCK && lock->l_type != F_WRLCK &&
+	    lock->l_type != F_UNLCK)
+		return -EINVAL;
+
+	if (lock->l_whence != SEEK_SET && lock->l_whence != SEEK_CUR &&
+	    lock->l_whence != SEEK_END)
+		return -EINVAL;
+
+#ifdef DEBUG
+	fprintf(stderr, "libulockmgr: %i %i %i %lli %lli own: 0x%08x\n",
+		cmd, lock->l_type, lock->l_whence, lock->l_start, lock->l_len,
+		owner_hash(owner, owner_len));
+#endif
+
+	/* Unlock should never block anyway */
+	if (cmd == F_SETLKW && lock->l_type == F_UNLCK)
+		cmd = F_SETLK;
+
+	memset(&msg, 0, sizeof(struct message));
+	msg.cmd = cmd;
+	msg.fd = fd;
+	msg.lock = *lock;
+	err = ulockmgr_canonicalize(fd, &msg.lock);
+	if (err)
+		return err;
+
+	sigemptyset(&block);
+	sigaddset(&block, SIGUSR1);
+	pthread_sigmask(SIG_BLOCK, &block, &old);
+	pthread_mutex_lock(&ulockmgr_lock);
+	err = ulockmgr_send_request(&msg, owner, owner_len);
+	pthread_mutex_unlock(&ulockmgr_lock);
+	pthread_sigmask(SIG_SETMASK, &old, NULL);
+	if (!err && cmd == F_GETLK) {
+		if (msg.lock.l_type == F_UNLCK)
+			lock->l_type = F_UNLCK;
+		else
+			*lock = msg.lock;
+	}
+
+	return err;
+}
diff --git a/gpt/Android.mk b/gpt/Android.mk
new file mode 100644
index 0000000..1b694ea
--- /dev/null
+++ b/gpt/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH := $(call my-dir)
+
+# Build libgpt_twrp library
+
+include $(CLEAR_VARS)
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 26; echo $$?),0)
+LOCAL_CLANG := false
+endif
+LOCAL_MODULE := libgpt_twrp
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES = \
+    gpt.c \
+    gptcrc32.c
+
+LOCAL_CFLAGS := -Wno-format -Wno-format-security
+
+LOCAL_SHARED_LIBRARIES := libc
+include $(BUILD_SHARED_LIBRARY)
diff --git a/gpt/gpt.c b/gpt/gpt.c
new file mode 100644
index 0000000..068a244
--- /dev/null
+++ b/gpt/gpt.c
@@ -0,0 +1,675 @@
+/*
+    gpt.[ch]
+
+    Copyright (C) 2000-2001 Dell Computer Corporation <Matt_Domsch@dell.com> 
+
+    EFI GUID Partition Table handling
+    Per Intel EFI Specification v1.02
+    http://developer.intel.com/technology/efi/efi.htm
+
+    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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    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
+*/
+
+// For TWRP purposes, we'll be opting for version 3 of the GPL
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/utsname.h>
+#include <asm/byteorder.h>
+#include "gpt.h"
+#include "gptcrc32.h"
+
+#define BLKGETLASTSECT  _IO(0x12,108) /* get last sector of block device */
+#define BLKGETSIZE _IO(0x12,96)	/* return device size */
+#define BLKSSZGET  _IO(0x12,104)	/* get block device sector size */
+#define BLKGETSIZE64 _IOR(0x12,114,uint64_t)	/* return device size in bytes (u64 *arg) */
+
+struct blkdev_ioctl_param {
+        unsigned int block;
+        size_t content_length;
+        char * block_contents;
+};
+
+static inline int
+efi_guidcmp(efi_guid_t left, efi_guid_t right)
+{
+	return memcmp(&left, &right, sizeof (efi_guid_t));
+}
+
+static int
+get_sector_size(int filedes)
+{
+	int rc, sector_size = 512;
+
+	rc = ioctl(filedes, BLKSSZGET, &sector_size);
+	if (rc)
+		sector_size = 512;
+	return sector_size;
+}
+
+/**
+ * efi_crc32() - EFI version of crc32 function
+ * @buf: buffer to calculate crc32 of
+ * @len - length of buf
+ *
+ * Description: Returns EFI-style CRC32 value for @buf
+ * 
+ * This function uses the little endian Ethernet polynomial
+ * but seeds the function with ~0, and xor's with ~0 at the end.
+ * Note, the EFI Specification, v1.02, has a reference to
+ * Dr. Dobbs Journal, May 1994 (actually it's in May 1992).
+ */
+static inline uint32_t
+efi_crc32(const void *buf, unsigned long len)
+{
+	return (gptcrc32(buf, len, ~0L) ^ ~0L);
+}
+
+/**
+ * is_pmbr_valid(): test Protective MBR for validity
+ * @mbr: pointer to a legacy mbr structure
+ *
+ * Description: Returns 1 if PMBR is valid, 0 otherwise.
+ * Validity depends on two things:
+ *  1) MSDOS signature is in the last two bytes of the MBR
+ *  2) One partition of type 0xEE is found
+ */
+static int
+is_pmbr_valid(legacy_mbr *mbr)
+{
+	int i, found = 0, signature = 0;
+	if (!mbr)
+		return 0;
+	signature = (__le16_to_cpu(mbr->signature) == MSDOS_MBR_SIGNATURE);
+	for (i = 0; signature && i < 4; i++) {
+		if (mbr->partition[i].os_type ==
+                    EFI_PMBR_OSTYPE_EFI_GPT) {
+			found = 1;
+			break;
+		}
+	}
+	return (signature && found);
+}
+
+/**
+ * kernel_has_blkgetsize64()
+ *
+ * Returns: 0 on false, 1 on true
+ * True means kernel is 2.4.x, x>=18, or
+ *                   is 2.5.x, x>4, or
+ *                   is > 2.5
+ */
+static int
+kernel_has_blkgetsize64(void)
+{
+        int major=0, minor=0, patch=0, parsed;
+        int rc;
+        struct utsname u;
+
+        memset(&u, 0, sizeof(u));
+        rc = uname(&u);
+        if (rc) return 0;
+
+        parsed = sscanf(u.release, "%d.%d.%d", &major, &minor, &patch);
+        if (parsed < 3) return 0;
+        if (major > 2) return 1;
+        if (major == 2 && minor > 5) return 1;
+        if (major == 2 && minor == 5 && patch >= 4) return 1;
+        if (major == 2 && minor == 4 && patch >= 18) return 1;
+        return 0;
+}
+
+
+/************************************************************
+ * _get_num_sectors
+ * Requires:
+ *  - filedes is an open file descriptor, suitable for reading
+ * Modifies: nothing
+ * Returns:
+ *  Last LBA value on success 
+ *  0 on error
+ *
+ * Try getting BLKGETSIZE64 and BLKSSZGET first,
+ * then BLKGETSIZE if necessary.
+ *  Kernels 2.4.15-2.4.18 and 2.5.0-2.5.3 have a broken BLKGETSIZE64
+ *  which returns the number of 512-byte sectors, not the size of
+ *  the disk in bytes. Fixed in kernels 2.4.18-pre8 and 2.5.4-pre3.
+ ************************************************************/
+static uint64_t
+_get_num_sectors(int filedes)
+{
+	unsigned long sectors=0;
+        uint64_t bytes=0;
+	int rc;
+        if (kernel_has_blkgetsize64()) {
+                rc = ioctl(filedes, BLKGETSIZE64, &bytes);
+                if (!rc)
+                        return bytes / get_sector_size(filedes);
+        }
+
+        rc = ioctl(filedes, BLKGETSIZE, &sectors);
+        if (rc)
+                return 0;
+        
+	return sectors;
+}
+
+/************************************************************
+ * last_lba(): return number of last logical block of device
+ * 
+ * @fd
+ * 
+ * Description: returns Last LBA value on success, 0 on error.
+ * Notes: The value st_blocks gives the size of the file
+ *        in 512-byte blocks, which is OK if
+ *        EFI_BLOCK_SIZE_SHIFT == 9.
+ ************************************************************/
+
+static uint64_t
+last_lba(int filedes)
+{
+	int rc;
+	uint64_t sectors = 0;
+	struct stat s;
+	memset(&s, 0, sizeof (s));
+	rc = fstat(filedes, &s);
+	if (rc == -1) {
+		fprintf(stderr, "last_lba() could not stat: %s\n",
+			strerror(errno));
+		return 0;
+	}
+
+	if (S_ISBLK(s.st_mode)) {
+		sectors = _get_num_sectors(filedes);
+	} else {
+		fprintf(stderr,
+			"last_lba(): I don't know how to handle files with mode %x\n",
+			s.st_mode);
+		sectors = 1;
+	}
+
+	return sectors - 1;
+}
+
+
+static ssize_t
+read_lastoddsector(int fd, uint64_t lba __unused, void *buffer, size_t count)
+{
+        int rc;
+        struct blkdev_ioctl_param ioctl_param;
+
+        if (!buffer) return 0; 
+
+        ioctl_param.block = 0; /* read the last sector */
+        ioctl_param.content_length = count;
+        ioctl_param.block_contents = buffer;
+
+        rc = ioctl(fd, BLKGETLASTSECT, &ioctl_param);
+        if (rc == -1) perror("read failed");
+
+        return !rc;
+}
+
+static ssize_t
+read_lba(int fd, uint64_t lba, void *buffer, size_t bytes)
+{
+	int sector_size = get_sector_size(fd);
+	off_t offset = lba * sector_size;
+        ssize_t bytesread;
+        void *aligned;
+        void *unaligned;
+
+        if (bytes % sector_size)
+                return EINVAL;
+
+	unaligned = malloc(bytes+sector_size-1);
+	aligned = (void *)
+		(((unsigned long)unaligned + sector_size - 1) &
+		 ~(unsigned long)(sector_size-1));
+	memset(aligned, 0, bytes);
+
+
+	lseek(fd, offset, SEEK_SET);
+	bytesread = read(fd, aligned, bytes);
+        memcpy(buffer, aligned, bytesread);
+        free(unaligned);
+
+        /* Kludge.  This is necessary to read/write the last
+           block of an odd-sized disk, until Linux 2.5.x kernel fixes.
+           This is only used by gpt.c, and only to read
+           one sector, so we don't have to be fancy.
+        */
+        if (!bytesread && !(last_lba(fd) & 1) && lba == last_lba(fd)) {
+                bytesread = read_lastoddsector(fd, lba, buffer, bytes);
+        }
+        return bytesread;
+}
+
+/**
+ * alloc_read_gpt_entries(): reads partition entries from disk
+ * @fd  is an open file descriptor to the whole disk
+ * @gpt is a buffer into which the GPT will be put  
+ * Description: Returns ptes on success,  NULL on error.
+ * Allocates space for PTEs based on information found in @gpt.
+ * Notes: remember to free pte when you're done!
+ */
+static gpt_entry *
+alloc_read_gpt_entries(int fd, gpt_header * gpt)
+{
+	gpt_entry *pte;
+        size_t count = __le32_to_cpu(gpt->num_partition_entries) *
+                __le32_to_cpu(gpt->sizeof_partition_entry);
+
+        if (!count) return NULL;
+
+	pte = (gpt_entry *)malloc(count);
+	if (!pte)
+		return NULL;
+	memset(pte, 0, count);
+
+	if (!read_lba(fd, __le64_to_cpu(gpt->partition_entry_lba), pte,
+                      count)) {
+		free(pte);
+		return NULL;
+	}
+	return pte;
+}
+
+/**
+ * alloc_read_gpt_header(): Allocates GPT header, reads into it from disk
+ * @fd  is an open file descriptor to the whole disk
+ * @lba is the Logical Block Address of the partition table
+ * 
+ * Description: returns GPT header on success, NULL on error.   Allocates
+ * and fills a GPT header starting at @ from @bdev.
+ * Note: remember to free gpt when finished with it.
+ */
+static gpt_header *
+alloc_read_gpt_header(int fd, uint64_t lba)
+{
+	gpt_header *gpt;
+	gpt = (gpt_header *)
+	    malloc(sizeof (gpt_header));
+	if (!gpt)
+		return NULL;
+	memset(gpt, 0, sizeof (*gpt));
+	if (!read_lba(fd, lba, gpt, sizeof (gpt_header))) {
+		free(gpt);
+		return NULL;
+	}
+
+	return gpt;
+}
+
+/**
+ * is_gpt_valid() - tests one GPT header and PTEs for validity
+ * @fd  is an open file descriptor to the whole disk
+ * @lba is the logical block address of the GPT header to test
+ * @gpt is a GPT header ptr, filled on return.
+ * @ptes is a PTEs ptr, filled on return.
+ *
+ * Description: returns 1 if valid,  0 on error.
+ * If valid, returns pointers to newly allocated GPT header and PTEs.
+ */
+static int
+is_gpt_valid(int fd, uint64_t lba,
+             gpt_header ** gpt, gpt_entry ** ptes)
+{
+	int rc = 0;		/* default to not valid */
+	uint32_t crc, origcrc;
+
+	if (!gpt || !ptes)
+                return 0;
+	if (!(*gpt = alloc_read_gpt_header(fd, lba)))
+		return 0;
+
+	/* Check the GUID Partition Table signature */
+	if (__le64_to_cpu((*gpt)->signature) != GPT_HEADER_SIGNATURE) {
+		/* 
+		   printf("GUID Partition Table Header signature is wrong: %" PRIx64" != %" PRIx64 "\n",
+		   __le64_to_cpu((*gpt)->signature), GUID_PT_HEADER_SIGNATURE);
+		 */
+		free(*gpt);
+		*gpt = NULL;
+		return rc;
+	}
+
+	/* Check the GUID Partition Table Header CRC */
+	origcrc = __le32_to_cpu((*gpt)->header_crc32);
+	(*gpt)->header_crc32 = 0;
+	crc = efi_crc32(*gpt, __le32_to_cpu((*gpt)->header_size));
+	if (crc != origcrc) {
+		// printf( "GPTH CRC check failed, %x != %x.\n", origcrc, crc);
+		(*gpt)->header_crc32 = __cpu_to_le32(origcrc);
+		free(*gpt);
+		*gpt = NULL;
+		return 0;
+	}
+	(*gpt)->header_crc32 = __cpu_to_le32(origcrc);
+
+	/* Check that the my_lba entry points to the LBA
+	 * that contains the GPT we read */
+	if (__le64_to_cpu((*gpt)->my_lba) != lba) {
+		// printf( "my_lba % PRIx64 "x != lba %"PRIx64 "x.\n", __le64_to_cpu((*gpt)->my_lba), lba);
+		free(*gpt);
+		*gpt = NULL;
+		return 0;
+	}
+
+	if (!(*ptes = alloc_read_gpt_entries(fd, *gpt))) {
+		free(*gpt);
+		*gpt = NULL;
+		return 0;
+	}
+
+	/* Check the GUID Partition Entry Array CRC */
+	crc = efi_crc32(*ptes,
+                        __le32_to_cpu((*gpt)->num_partition_entries) *
+			__le32_to_cpu((*gpt)->sizeof_partition_entry));
+	if (crc != __le32_to_cpu((*gpt)->partition_entry_array_crc32)) {
+		// printf("GUID Partitition Entry Array CRC check failed.\n");
+		free(*gpt);
+		*gpt = NULL;
+		free(*ptes);
+		*ptes = NULL;
+		return 0;
+	}
+
+	/* We're done, all's well */
+	return 1;
+}
+/**
+ * compare_gpts() - Search disk for valid GPT headers and PTEs
+ * @pgpt is the primary GPT header
+ * @agpt is the alternate GPT header
+ * @lastlba is the last LBA number
+ * Description: Returns nothing.  Sanity checks pgpt and agpt fields
+ * and prints warnings on discrepancies.
+ * 
+ */
+static void
+compare_gpts(gpt_header *pgpt, gpt_header *agpt, uint64_t lastlba)
+{
+	int error_found = 0;
+	if (!pgpt || !agpt)
+		return;
+	if (__le64_to_cpu(pgpt->my_lba) != __le64_to_cpu(agpt->alternate_lba)) {
+		fprintf(stderr, 
+		       "GPT:Primary header LBA != Alt. header alternate_lba\n");
+		fprintf(stderr,  "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
+		       __le64_to_cpu(pgpt->my_lba),
+                       __le64_to_cpu(agpt->alternate_lba));
+		error_found++;
+	}
+	if (__le64_to_cpu(pgpt->alternate_lba) != __le64_to_cpu(agpt->my_lba)) {
+		fprintf(stderr, 
+		       "GPT:Primary header alternate_lba != Alt. header my_lba\n");
+		fprintf(stderr,  "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
+		       __le64_to_cpu(pgpt->alternate_lba),
+                       __le64_to_cpu(agpt->my_lba));
+		error_found++;
+	}
+	if (__le64_to_cpu(pgpt->first_usable_lba) !=
+            __le64_to_cpu(agpt->first_usable_lba)) {
+		fprintf(stderr,  "GPT:first_usable_lbas don't match.\n");
+		fprintf(stderr,  "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
+		       __le64_to_cpu(pgpt->first_usable_lba),
+                       __le64_to_cpu(agpt->first_usable_lba));
+		error_found++;
+	}
+	if (__le64_to_cpu(pgpt->last_usable_lba) !=
+            __le64_to_cpu(agpt->last_usable_lba)) {
+		fprintf(stderr,  "GPT:last_usable_lbas don't match.\n");
+		fprintf(stderr,  "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
+		       __le64_to_cpu(pgpt->last_usable_lba),
+                       __le64_to_cpu(agpt->last_usable_lba));
+		error_found++;
+	}
+	if (efi_guidcmp(pgpt->disk_guid, agpt->disk_guid)) {
+		fprintf(stderr,  "GPT:disk_guids don't match.\n");
+		error_found++;
+	}
+	if (__le32_to_cpu(pgpt->num_partition_entries) !=
+            __le32_to_cpu(agpt->num_partition_entries)) {
+		fprintf(stderr,  "GPT:num_partition_entries don't match: "
+		       "0x%x != 0x%x\n",
+		       __le32_to_cpu(pgpt->num_partition_entries),
+		       __le32_to_cpu(agpt->num_partition_entries));
+		error_found++;
+	}
+	if (__le32_to_cpu(pgpt->sizeof_partition_entry) !=
+            __le32_to_cpu(agpt->sizeof_partition_entry)) {
+		fprintf(stderr, 
+		       "GPT:sizeof_partition_entry values don't match: "
+		       "0x%x != 0x%x\n",
+                       __le32_to_cpu(pgpt->sizeof_partition_entry),
+		       __le32_to_cpu(agpt->sizeof_partition_entry));
+		error_found++;
+	}
+	if (__le32_to_cpu(pgpt->partition_entry_array_crc32) !=
+            __le32_to_cpu(agpt->partition_entry_array_crc32)) {
+		fprintf(stderr, 
+		       "GPT:partition_entry_array_crc32 values don't match: "
+		       "0x%x != 0x%x\n",
+                       __le32_to_cpu(pgpt->partition_entry_array_crc32),
+		       __le32_to_cpu(agpt->partition_entry_array_crc32));
+		error_found++;
+	}
+	if (__le64_to_cpu(pgpt->alternate_lba) != lastlba) {
+		fprintf(stderr, 
+		       "GPT:Primary header thinks Alt. header is not at the end of the disk.\n");
+		fprintf(stderr,  "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
+		       __le64_to_cpu(pgpt->alternate_lba), lastlba);
+		error_found++;
+	}
+
+	if (__le64_to_cpu(agpt->my_lba) != lastlba) {
+		fprintf(stderr, 
+		       "GPT:Alternate GPT header not at the end of the disk.\n");
+		fprintf(stderr,  "GPT:0x%" PRIx64 " != 0x%" PRIx64 "\n",
+		       __le64_to_cpu(agpt->my_lba), lastlba);
+		error_found++;
+	}
+
+	if (error_found)
+		fprintf(stderr, 
+		       "GPT: Use GNU Parted to correct GPT errors.\n");
+	return;
+}
+
+/**
+ * find_valid_gpt() - Search disk for valid GPT headers and PTEs
+ * @fd  is an open file descriptor to the whole disk
+ * @gpt is a GPT header ptr, filled on return.
+ * @ptes is a PTEs ptr, filled on return.
+ * Description: Returns 1 if valid, 0 on error.
+ * If valid, returns pointers to newly allocated GPT header and PTEs.
+ * Validity depends on finding either the Primary GPT header and PTEs valid,
+ * or the Alternate GPT header and PTEs valid, and the PMBR valid.
+ */
+static int
+find_valid_gpt(int fd, gpt_header ** gpt, gpt_entry ** ptes)
+{
+	int good_pgpt = 0, good_agpt = 0, good_pmbr = 0;
+	gpt_header *pgpt = NULL, *agpt = NULL;
+	gpt_entry *pptes = NULL, *aptes = NULL;
+	legacy_mbr *legacymbr = NULL;
+	uint64_t lastlba;
+	if (!gpt || !ptes)
+		return 0;
+
+	lastlba = last_lba(fd);
+	good_pgpt = is_gpt_valid(fd, GPT_PRIMARY_PARTITION_TABLE_LBA,
+				 &pgpt, &pptes);
+        if (good_pgpt) {
+		good_agpt = is_gpt_valid(fd,
+                                         __le64_to_cpu(pgpt->alternate_lba),
+					 &agpt, &aptes);
+                if (!good_agpt) {
+                        good_agpt = is_gpt_valid(fd, lastlba,
+                                                 &agpt, &aptes);
+                }
+        }
+        else {
+                good_agpt = is_gpt_valid(fd, lastlba,
+                                         &agpt, &aptes);
+        }
+
+        /* The obviously unsuccessful case */
+        if (!good_pgpt && !good_agpt) {
+                goto fail;
+        }
+
+	/* This will be added to the EFI Spec. per Intel after v1.02. */
+        legacymbr = malloc(sizeof (*legacymbr));
+        if (legacymbr) {
+                memset(legacymbr, 0, sizeof (*legacymbr));
+                read_lba(fd, 0, (uint8_t *) legacymbr,
+                         sizeof (*legacymbr));
+                good_pmbr = is_pmbr_valid(legacymbr);
+                free(legacymbr);
+                legacymbr=NULL;
+        }
+
+        /* Failure due to bad PMBR */
+        if ((good_pgpt || good_agpt) && !good_pmbr) {
+                fprintf(stderr,
+                       "  Warning: Disk has a valid GPT signature "
+                       "but invalid PMBR.\n"
+                       "  Assuming this disk is *not* a GPT disk anymore.\n"
+                       "  Use gpt kernel option to override.  "
+                       "Use GNU Parted to correct disk.\n");
+                goto fail;
+        }
+
+        /* Would fail due to bad PMBR, but force GPT anyhow */
+        if ((good_pgpt || good_agpt) && !good_pmbr) {
+                fprintf(stderr, 
+                       "  Warning: Disk has a valid GPT signature but "
+                       "invalid PMBR.\n"
+                       "  Use GNU Parted to correct disk.\n"
+                       "  gpt option taken, disk treated as GPT.\n");
+        }
+
+        compare_gpts(pgpt, agpt, lastlba);
+
+        /* The good cases */
+        if (good_pgpt && (good_pmbr)) {
+                *gpt  = pgpt;
+                *ptes = pptes;
+                if (agpt)  { free(agpt);   agpt = NULL; }
+                if (aptes) { free(aptes); aptes = NULL; }
+                if (!good_agpt) {
+                        fprintf(stderr, 
+			       "Alternate GPT is invalid, "
+                               "using primary GPT.\n");
+                }
+                return 1;
+        }
+        else if (good_agpt && (good_pmbr)) {
+                *gpt  = agpt;
+                *ptes = aptes;
+                if (pgpt)  { free(pgpt);   pgpt = NULL; }
+                if (pptes) { free(pptes); pptes = NULL; }
+                fprintf(stderr, 
+                       "Primary GPT is invalid, using alternate GPT.\n");
+                return 1;
+        }
+
+ fail:
+        if (pgpt)  { free(pgpt);   pgpt=NULL; }
+        if (agpt)  { free(agpt);   agpt=NULL; }
+        if (pptes) { free(pptes); pptes=NULL; }
+        if (aptes) { free(aptes); aptes=NULL; }
+        *gpt = NULL;
+        *ptes = NULL;
+        return 0;
+}
+
+void guid_to_ascii(const char *guid, char *s)
+{
+	uint32_t p1;
+	uint16_t p2;
+	uint16_t p3;
+	unsigned char p4[8];
+
+	memcpy(&p1, guid + 0, 4);
+	memcpy(&p2, guid + 4, 2);
+	memcpy(&p3, guid + 6, 2);
+	memcpy(p4, guid + 8, 8);
+
+	sprintf(s, "%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
+		p1, p2, p3, p4[0], p4[1],
+		p4[2], p4[3], p4[4], p4[5], p4[6], p4[7]);
+}
+
+
+/************************************************************
+ * gpt_disk_get_partition_info()
+ * Requires:
+ *  - open file descriptor fd
+ *  - start, size, signature, mbr_type, signature_type
+ * Modifies: all these
+ * Returns:
+ *  0 on success
+ *  non-zero on failure
+ *
+ ************************************************************/
+int
+gpt_disk_get_partition_info(int fd, uint32_t num,
+			    char *type, char *part)
+{
+	gpt_header *gpt = NULL;
+	gpt_entry *ptes = NULL, *p;
+
+	if (!find_valid_gpt(fd, &gpt, &ptes))
+		return 1;
+
+	if (num > 0 && num <= __le32_to_cpu(gpt->num_partition_entries)) {
+		p = &ptes[num - 1];
+		guid_to_ascii((char*)&p->partition_type_guid, type);
+		guid_to_ascii((char*)&p->unique_partition_guid, part);
+	} else {
+		fprintf (stderr,"partition %d is not valid\n", num);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4 
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: nil
+ * tab-width: 8
+ * End:
+ */
diff --git a/gpt/gpt.h b/gpt/gpt.h
new file mode 100644
index 0000000..9520c8d
--- /dev/null
+++ b/gpt/gpt.h
@@ -0,0 +1,182 @@
+/*
+    gpt.[ch]
+
+    Copyright (C) 2000-2001 Dell Computer Corporation <Matt_Domsch@dell.com> 
+
+    EFI GUID Partition Table handling
+    Per Intel EFI Specification v1.02
+    http://developer.intel.com/technology/efi/efi.htm
+
+    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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    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
+*/
+
+// For TWRP purposes, we'll be opting for version 3 of the GPL
+
+#ifndef _GPT_H
+#define _GPT_H
+
+
+#include <inttypes.h>
+//#include "efi.h"
+
+typedef struct {
+	uint8_t  b[16];
+} efi_guid_t;
+typedef uint16_t efi_char16_t;		/* UNICODE character */
+
+#define EFI_PMBR_OSTYPE_EFI 0xEF
+#define EFI_PMBR_OSTYPE_EFI_GPT 0xEE
+#define MSDOS_MBR_SIGNATURE 0xaa55
+#define GPT_BLOCK_SIZE 512
+
+static const char* TWGptAndroidExpand = "193d1ea4b3ca11e4b07510604b889dcf";
+
+#define GPT_HEADER_SIGNATURE ((uint64_t)(0x5452415020494645LL))
+#define GPT_HEADER_REVISION_V1_02 0x00010200
+#define GPT_HEADER_REVISION_V1_00 0x00010000
+#define GPT_HEADER_REVISION_V0_99 0x00009900
+#define GPT_PRIMARY_PARTITION_TABLE_LBA 1
+
+#define PARTITION_SYSTEM_GUID \
+    EFI_GUID( 0xC12A7328, 0xF81F, 0x11d2, \
+              0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B) 
+#define LEGACY_MBR_PARTITION_GUID \
+    EFI_GUID( 0x024DEE41, 0x33E7, 0x11d3, \
+              0x9D, 0x69, 0x00, 0x08, 0xC7, 0x81, 0xF3, 0x9F)
+#define PARTITION_MSFT_RESERVED_GUID \
+    EFI_GUID( 0xE3C9E316, 0x0B5C, 0x4DB8, \
+              0x81, 0x7D, 0xF9, 0x2D, 0xF0, 0x02, 0x15, 0xAE)
+#define PARTITION_BASIC_DATA_GUID \
+    EFI_GUID( 0xEBD0A0A2, 0xB9E5, 0x4433, \
+              0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7)
+#define PARTITION_LINUX_RAID_GUID \
+    EFI_GUID( 0xa19d880f, 0x05fc, 0x4d3b, \
+              0xa0, 0x06, 0x74, 0x3f, 0x0f, 0x84, 0x91, 0x1e)
+#define PARTITION_LINUX_SWAP_GUID \
+    EFI_GUID( 0x0657fd6d, 0xa4ab, 0x43c4, \
+              0x84, 0xe5, 0x09, 0x33, 0xc8, 0x4b, 0x4f, 0x4f)
+#define PARTITION_LINUX_LVM_GUID \
+    EFI_GUID( 0xe6d6d379, 0xf507, 0x44c2, \
+              0xa2, 0x3c, 0x23, 0x8f, 0x2a, 0x3d, 0xf9, 0x28)
+
+typedef struct _gpt_header {
+	uint64_t signature;
+	uint32_t revision;
+	uint32_t header_size;
+	uint32_t header_crc32;
+	uint32_t reserved1;
+	uint64_t my_lba;
+	uint64_t alternate_lba;
+	uint64_t first_usable_lba;
+	uint64_t last_usable_lba;
+	efi_guid_t disk_guid;
+	uint64_t partition_entry_lba;
+	uint32_t num_partition_entries;
+	uint32_t sizeof_partition_entry;
+	uint32_t partition_entry_array_crc32;
+	uint8_t reserved2[GPT_BLOCK_SIZE - 92];
+} __attribute__ ((packed)) gpt_header;
+
+typedef struct _gpt_entry_attributes {
+	uint64_t required_to_function:1;
+	uint64_t reserved:47;
+        uint64_t type_guid_specific:16;
+} __attribute__ ((packed)) gpt_entry_attributes;
+
+typedef struct _gpt_entry {
+	efi_guid_t partition_type_guid;
+	efi_guid_t unique_partition_guid;
+	uint64_t starting_lba;
+	uint64_t ending_lba;
+	gpt_entry_attributes attributes;
+	efi_char16_t partition_name[72 / sizeof(efi_char16_t)];
+} __attribute__ ((packed)) gpt_entry;
+
+
+/* 
+   These values are only defaults.  The actual on-disk structures
+   may define different sizes, so use those unless creating a new GPT disk!
+*/
+
+#define GPT_DEFAULT_RESERVED_PARTITION_ENTRY_ARRAY_SIZE 16384
+/* 
+   Number of actual partition entries should be calculated
+   as: 
+*/
+#define GPT_DEFAULT_RESERVED_PARTITION_ENTRIES \
+        (GPT_DEFAULT_RESERVED_PARTITION_ENTRY_ARRAY_SIZE / \
+         sizeof(gpt_entry))
+
+
+typedef struct _partition_record {
+	uint8_t boot_indicator;	/* Not used by EFI firmware. Set to 0x80 to indicate that this
+				   is the bootable legacy partition. */
+	uint8_t start_head;		/* Start of partition in CHS address, not used by EFI firmware. */
+	uint8_t start_sector;	/* Start of partition in CHS address, not used by EFI firmware. */
+	uint8_t start_track;	/* Start of partition in CHS address, not used by EFI firmware. */
+	uint8_t os_type;		/* OS type. A value of 0xEF defines an EFI system partition.
+				   Other values are reserved for legacy operating systems, and
+				   allocated independently of the EFI specification. */
+	uint8_t end_head;		/* End of partition in CHS address, not used by EFI firmware. */
+	uint8_t end_sector;		/* End of partition in CHS address, not used by EFI firmware. */
+	uint8_t end_track;		/* End of partition in CHS address, not used by EFI firmware. */
+	uint32_t starting_lba;	/* Starting LBA address of the partition on the disk. Used by
+				   EFI firmware to define the start of the partition. */
+	uint32_t size_in_lba;	/* Size of partition in LBA. Used by EFI firmware to determine
+				   the size of the partition. */
+} __attribute__ ((packed)) partition_record;
+
+
+/* Protected Master Boot Record  & Legacy MBR share same structure */
+/* Needs to be packed because the u16s force misalignment. */
+
+typedef struct _legacy_mbr {
+	uint8_t bootcode[440];
+	uint32_t unique_mbr_signature;
+	uint16_t unknown;
+	partition_record partition[4];
+	uint16_t signature;
+} __attribute__ ((packed)) legacy_mbr;
+
+
+
+
+#define EFI_GPT_PRIMARY_PARTITION_TABLE_LBA 1
+
+/* Functions */
+int gpt_disk_get_partition_info (int fd, uint32_t num,
+                                 char *type, char *part);
+
+
+#endif
+
+/*
+ * Overrides for Emacs so that we follow Linus's tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local variables:
+ * c-indent-level: 4 
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: nil
+ * tab-width: 8
+ * End:
+ */
diff --git a/gpt/gptcrc32.c b/gpt/gptcrc32.c
new file mode 100644
index 0000000..3157e93
--- /dev/null
+++ b/gpt/gptcrc32.c
@@ -0,0 +1,124 @@
+/* 
+ * Dec 5, 2000 Matt Domsch <Matt_Domsch@dell.com>
+ * - Copied crc32.c from the linux/drivers/net/cipe directory.
+ * - Now pass seed as an arg
+ * - changed len to be an unsigned long
+ * - changed crc32val to be a register
+ * - License remains unchanged!  It's still GPL-compatable!
+ */
+
+  /* ============================================================= */
+  /*  COPYRIGHT (C) 1986 Gary S. Brown.  You may use this program, or       */
+  /*  code or tables extracted from it, as desired without restriction.     */
+  /*                                                                        */
+  /*  First, the polynomial itself and its table of feedback terms.  The    */
+  /*  polynomial is                                                         */
+  /*  X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0   */
+  /*                                                                        */
+  /*  Note that we take it "backwards" and put the highest-order term in    */
+  /*  the lowest-order bit.  The X^32 term is "implied"; the LSB is the     */
+  /*  X^31 term, etc.  The X^0 term (usually shown as "+1") results in      */
+  /*  the MSB being 1.                                                      */
+  /*                                                                        */
+  /*  Note that the usual hardware shift register implementation, which     */
+  /*  is what we're using (we're merely optimizing it by doing eight-bit    */
+  /*  chunks at a time) shifts bits into the lowest-order term.  In our     */
+  /*  implementation, that means shifting towards the right.  Why do we     */
+  /*  do it this way?  Because the calculated CRC must be transmitted in    */
+  /*  order from highest-order term to lowest-order term.  UARTs transmit   */
+  /*  characters in order from LSB to MSB.  By storing the CRC this way,    */
+  /*  we hand it to the UART in the order low-byte to high-byte; the UART   */
+  /*  sends each low-bit to hight-bit; and the result is transmission bit   */
+  /*  by bit from highest- to lowest-order term without requiring any bit   */
+  /*  shuffling on our part.  Reception works similarly.                    */
+  /*                                                                        */
+  /*  The feedback terms table consists of 256, 32-bit entries.  Notes:     */
+  /*                                                                        */
+  /*      The table can be generated at runtime if desired; code to do so   */
+  /*      is shown later.  It might not be obvious, but the feedback        */
+  /*      terms simply represent the results of eight shift/xor opera-      */
+  /*      tions for all combinations of data and CRC register values.       */
+  /*                                                                        */
+  /*      The values must be right-shifted by eight bits by the "updcrc"    */
+  /*      logic; the shift must be unsigned (bring in zeroes).  On some     */
+  /*      hardware you could probably optimize the shift in assembler by    */
+  /*      using byte-swap instructions.                                     */
+  /*      polynomial $edb88320                                              */
+  /*                                                                        */
+  /*  --------------------------------------------------------------------  */
+
+#include <stdint.h>
+
+static uint32_t crc32_tab[] = {
+      0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
+      0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
+      0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
+      0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+      0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+      0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+      0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+      0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+      0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+      0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+      0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+      0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+      0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+      0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
+      0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
+      0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
+      0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+      0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+      0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+      0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+      0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+      0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+      0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+      0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+      0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+      0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+      0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
+      0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
+      0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
+      0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+      0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+      0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+      0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+      0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+      0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+      0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+      0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+      0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+      0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+      0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
+      0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
+      0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
+      0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+      0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+      0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+      0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+      0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+      0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+      0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+      0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+      0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+      0x2d02ef8dL
+   };
+
+/* Return a 32-bit CRC of the contents of the buffer. */
+
+uint32_t
+gptcrc32(const void *buf, unsigned long len, uint32_t seed)
+{
+  unsigned long i;
+  register uint32_t crc32val;
+  const unsigned char *s = buf;
+
+  crc32val = seed;
+  for (i = 0;  i < len;  i ++)
+    {
+      crc32val =
+	crc32_tab[(crc32val ^ s[i]) & 0xff] ^
+	  (crc32val >> 8);
+    }
+  return crc32val;
+}
diff --git a/gpt/gptcrc32.h b/gpt/gptcrc32.h
new file mode 100644
index 0000000..0631b7f
--- /dev/null
+++ b/gpt/gptcrc32.h
@@ -0,0 +1,36 @@
+/*
+    libparted - a library for manipulating disk partitions
+    Copyright (C) 1998-2000 Free Software Foundation, Inc.
+
+    crc32.h
+
+    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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    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
+*/
+
+// For TWRP purposes, we'll be opting for version 3 of the GPL
+
+#ifndef _GPTCRC32_H
+#define _GPTCRC32_H
+
+#include <stdint.h>
+
+/*
+ * This computes a 32 bit CRC of the data in the buffer, and returns the CRC.
+ * The polynomial used is 0xedb88320.
+ */
+
+extern uint32_t gptcrc32 (const void *buf, unsigned long len, uint32_t seed);
+
+#endif /* _GPTCRC32_H */
diff --git a/gui/Android.bp b/gui/Android.bp
new file mode 100644
index 0000000..2c4a33e
--- /dev/null
+++ b/gui/Android.bp
@@ -0,0 +1,91 @@
+bootstrap_go_package {
+    name: "soong-libguitwrp_defaults",
+    pkgPath: "bootable/recovery/gui",
+    deps: [
+        "soong",
+        "soong-android",
+        "soong-cc"
+    ],
+    srcs: [
+        "libguitwrp_defaults.go",
+        "../soong/copy.go",
+        "../soong/makevars.go"
+    ],
+    pluginFor: ["soong_build"]
+}
+
+libguitwrp_defaults {
+    name: "libguitwrp_defaults"
+}
+
+cc_library_static {
+    name: "libguitwrp",
+    defaults: ["libguitwrp_defaults"],
+    cflags: [
+        "-fno-strict-aliasing", 
+        "-Wno-implicit-fallthrough",
+        "-D_USE_SYSTEM_ZIPARCHIVE",
+        "-DTWRES=\"/twres/\""
+    ],
+    include_dirs: [
+        "bootable/recovery/crypto/scrypt/lib/util",
+        "bootable/recovery/otautil/include",
+        "bootable/recovery/install/include",
+        "system/libziparchive/include",
+        "bootable/recovery/recovery_ui/include",
+        "bootable/recovery/fuse_sideload/include",
+        "bootable/recovery/gui/include",
+        "bootable/recovery/twrpinstall",
+        "bootable/recovery/twrpinstall/include",
+        "bootable/recovery/libpixelflinger/include",
+        "bootable/recovery/minuitwrp/include",
+        "bionic",
+        "system/libbase/include",
+        "system/core/libcutils/include",
+        "system/core/include",
+        "external/freetype/include",
+        "external/libpng"
+
+    ],
+    srcs: [
+        "gui.cpp",
+        "resources.cpp",
+        "pages.cpp",
+        "text.cpp",
+        "image.cpp",
+        "action.cpp",
+        "console.cpp",
+        "fill.cpp",
+        "button.cpp",
+        "checkbox.cpp",
+        "fileselector.cpp",
+        "progressbar.cpp",
+        "animation.cpp",
+        "object.cpp",
+        "slider.cpp",
+        "slidervalue.cpp",
+        "listbox.cpp",
+        "keyboard.cpp",
+        "input.cpp",
+        "blanktimer.cpp",
+        "partitionlist.cpp",
+        "mousecursor.cpp",
+        "scrolllist.cpp",
+        "patternpassword.cpp",
+        "textbox.cpp",
+        "terminal.cpp",
+        "twmsg.cpp"
+    ],
+    shared_libs: [
+        "libminuitwrp",
+        "libc",
+        "libstdc++",
+        "libaosprecovery",
+        "libselinux",
+        "libziparchive"
+    ],
+    static_libs: [
+        "libotautil",
+        "libpng"
+    ]
+}
diff --git a/gui/action.cpp b/gui/action.cpp
new file mode 100755
index 0000000..61a3bed
--- /dev/null
+++ b/gui/action.cpp
@@ -0,0 +1,2361 @@
+/*
+	Copyright 2013 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <linux/input.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <private/android_filesystem_config.h>
+#include <android-base/properties.h>
+
+#include <string>
+#include <sstream>
+#include "../partitions.hpp"
+#include "../twrp-functions.hpp"
+#include "../twrpRepacker.hpp"
+#include "../openrecoveryscript.hpp"
+
+#include "twinstall/adb_install.h"
+
+#include "fuse_sideload.h"
+#include "blanktimer.hpp"
+#include "twinstall.h"
+
+extern "C" {
+#include "../twcommon.h"
+#include "../variables.h"
+#include "cutils/properties.h"
+#include "twinstall/adb_install.h"
+};
+#include "set_metadata.h"
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+#include "tw_atomic.hpp"
+
+GUIAction::mapFunc GUIAction::mf;
+std::set<string> GUIAction::setActionsRunningInCallerThread;
+static string zip_queue[10];
+static int zip_queue_index;
+pid_t sideload_child_pid;
+extern std::vector<users_struct> Users_List;
+extern GUITerminal* term;
+
+static void *ActionThread_work_wrapper(void *data);
+
+class ActionThread
+{
+public:
+	ActionThread();
+	~ActionThread();
+
+	void threadActions(GUIAction *act);
+	void run(void *data);
+private:
+	friend void *ActionThread_work_wrapper(void*);
+	struct ThreadData
+	{
+		ActionThread *this_;
+		GUIAction *act;
+		ThreadData(ActionThread *this_, GUIAction *act) : this_(this_), act(act) {}
+	};
+
+	pthread_t m_thread;
+	bool m_thread_running;
+	pthread_mutex_t m_act_lock;
+};
+
+static ActionThread action_thread;	// for all kinds of longer running actions
+static ActionThread cancel_thread;	// for longer running "cancel" actions
+
+static void *ActionThread_work_wrapper(void *data)
+{
+	static_cast<ActionThread::ThreadData*>(data)->this_->run(data);
+	return NULL;
+}
+
+ActionThread::ActionThread()
+{
+	m_thread_running = false;
+	pthread_mutex_init(&m_act_lock, NULL);
+}
+
+ActionThread::~ActionThread()
+{
+	pthread_mutex_lock(&m_act_lock);
+	if (m_thread_running) {
+		pthread_mutex_unlock(&m_act_lock);
+		pthread_join(m_thread, NULL);
+	} else {
+		pthread_mutex_unlock(&m_act_lock);
+	}
+	pthread_mutex_destroy(&m_act_lock);
+}
+
+void ActionThread::threadActions(GUIAction *act)
+{
+	pthread_mutex_lock(&m_act_lock);
+	if (m_thread_running) {
+		pthread_mutex_unlock(&m_act_lock);
+		LOGERR("Another threaded action is already running -- not running %u actions starting with '%s'\n",
+				act->mActions.size(), act->mActions[0].mFunction.c_str());
+	} else {
+		m_thread_running = true;
+		pthread_mutex_unlock(&m_act_lock);
+		ThreadData *d = new ThreadData(this, act);
+		pthread_create(&m_thread, NULL, &ActionThread_work_wrapper, d);
+	}
+}
+
+void ActionThread::run(void *data)
+{
+	ThreadData *d = (ThreadData*)data;
+	GUIAction* act = d->act;
+
+	std::vector<GUIAction::Action>::iterator it;
+	for (it = act->mActions.begin(); it != act->mActions.end(); ++it)
+		act->doAction(*it);
+
+	pthread_mutex_lock(&m_act_lock);
+	m_thread_running = false;
+	pthread_mutex_unlock(&m_act_lock);
+	delete d;
+}
+
+GUIAction::GUIAction(xml_node<>* node)
+	: GUIObject(node)
+{
+	xml_node<>* child;
+	xml_node<>* actions;
+	xml_attribute<>* attr;
+
+	if (!node)  return;
+
+	if (mf.empty()) {
+#define ADD_ACTION(n) mf[#n] = &GUIAction::n
+#define ADD_ACTION_EX(name, func) mf[name] = &GUIAction::func
+		// These actions will be run in the caller's thread
+		ADD_ACTION(reboot);
+		ADD_ACTION(home);
+		ADD_ACTION(key);
+		ADD_ACTION(page);
+		ADD_ACTION(reload);
+		ADD_ACTION(readBackup);
+		ADD_ACTION(set);
+		ADD_ACTION(clear);
+		ADD_ACTION(mount);
+		ADD_ACTION(unmount);
+		ADD_ACTION_EX("umount", unmount);
+		ADD_ACTION(restoredefaultsettings);
+		ADD_ACTION(copylog);
+		ADD_ACTION(compute);
+		ADD_ACTION_EX("addsubtract", compute);
+		ADD_ACTION(setguitimezone);
+		ADD_ACTION(overlay);
+		ADD_ACTION(queuezip);
+		ADD_ACTION(cancelzip);
+		ADD_ACTION(queueclear);
+		ADD_ACTION(sleep);
+		ADD_ACTION(sleepcounter);
+		ADD_ACTION(appenddatetobackupname);
+		ADD_ACTION(generatebackupname);
+		ADD_ACTION(checkpartitionlist);
+		ADD_ACTION(getpartitiondetails);
+		ADD_ACTION(screenshot);
+		ADD_ACTION(setbrightness);
+		ADD_ACTION(fileexists);
+		ADD_ACTION(killterminal);
+		ADD_ACTION(checkbackupname);
+		ADD_ACTION(adbsideloadcancel);
+		ADD_ACTION(fixsu);
+		ADD_ACTION(startmtp);
+		ADD_ACTION(stopmtp);
+		ADD_ACTION(cancelbackup);
+		ADD_ACTION(checkpartitionlifetimewrites);
+		ADD_ACTION(mountsystemtoggle);
+		ADD_ACTION(setlanguage);
+		ADD_ACTION(checkforapp);
+		ADD_ACTION(togglebacklight);
+		ADD_ACTION(enableadb);
+		ADD_ACTION(enablefastboot);
+		ADD_ACTION(changeterminal);
+		ADD_ACTION(unmapsuperdevices);
+
+		// remember actions that run in the caller thread
+		for (mapFunc::const_iterator it = mf.begin(); it != mf.end(); ++it)
+			setActionsRunningInCallerThread.insert(it->first);
+
+		// These actions will run in a separate thread
+		ADD_ACTION(flash);
+		ADD_ACTION(wipe);
+		ADD_ACTION(refreshsizes);
+		ADD_ACTION(nandroid);
+		ADD_ACTION(fixcontexts);
+		ADD_ACTION(fixpermissions);
+		ADD_ACTION(dd);
+		ADD_ACTION(partitionsd);
+		ADD_ACTION(cmd);
+		ADD_ACTION(terminalcommand);
+		ADD_ACTION(reinjecttwrp);
+		ADD_ACTION(decrypt);
+		ADD_ACTION(adbsideload);
+		ADD_ACTION(openrecoveryscript);
+		ADD_ACTION(installsu);
+		ADD_ACTION(decrypt_backup);
+		ADD_ACTION(repair);
+		ADD_ACTION(resize);
+		ADD_ACTION(changefilesystem);
+		ADD_ACTION(flashimage);
+		ADD_ACTION(twcmd);
+		ADD_ACTION(setbootslot);
+		ADD_ACTION(installapp);
+		ADD_ACTION(uninstalltwrpsystemapp);
+		ADD_ACTION(repackimage);
+		ADD_ACTION(reflashtwrp);
+		ADD_ACTION(fixabrecoverybootloop);
+		ADD_ACTION(applycustomtwrpfolder);
+#ifndef TW_EXCLUDE_NANO
+		ADD_ACTION(editfile);
+#endif
+		ADD_ACTION(mergesnapshots);
+	}
+
+	// First, get the action
+	actions = FindNode(node, "actions");
+	if (actions)	child = FindNode(actions, "action");
+	else			child = FindNode(node, "action");
+
+	if (!child) return;
+
+	while (child)
+	{
+		Action action;
+
+		attr = child->first_attribute("function");
+		if (!attr)  return;
+
+		action.mFunction = attr->value();
+		action.mArg = child->value();
+		mActions.push_back(action);
+
+		child = child->next_sibling("action");
+	}
+
+	// Now, let's get either the key or region
+	child = FindNode(node, "touch");
+	if (child)
+	{
+		attr = child->first_attribute("key");
+		if (attr)
+		{
+			std::vector<std::string> keys = TWFunc::Split_String(attr->value(), "+");
+			for (size_t i = 0; i < keys.size(); ++i)
+			{
+				const int key = getKeyByName(keys[i]);
+				mKeys[key] = false;
+			}
+		}
+		else
+		{
+			attr = child->first_attribute("x");
+			if (!attr)  return;
+			mActionX = atol(attr->value());
+			attr = child->first_attribute("y");
+			if (!attr)  return;
+			mActionY = atol(attr->value());
+			attr = child->first_attribute("w");
+			if (!attr)  return;
+			mActionW = atol(attr->value());
+			attr = child->first_attribute("h");
+			if (!attr)  return;
+			mActionH = atol(attr->value());
+		}
+	}
+}
+
+int GUIAction::NotifyTouch(TOUCH_STATE state, int x __unused, int y __unused)
+{
+	if (state == TOUCH_RELEASE)
+		doActions();
+
+	return 0;
+}
+
+int GUIAction::NotifyKey(int key, bool down)
+{
+	std::map<int, bool>::iterator itr = mKeys.find(key);
+	if (itr == mKeys.end())
+		return 1;
+
+	bool prevState = itr->second;
+	itr->second = down;
+
+	// If there is only one key for this action, wait for key up so it
+	// doesn't trigger with multi-key actions.
+	// Else, check if all buttons are pressed, then consume their release events
+	// so they don't trigger one-button actions and reset mKeys pressed status
+	if (mKeys.size() == 1) {
+		if (!down && prevState) {
+			doActions();
+			return 0;
+		}
+	} else if (down) {
+		for (itr = mKeys.begin(); itr != mKeys.end(); ++itr) {
+			if (!itr->second)
+				return 1;
+		}
+
+		// Passed, all req buttons are pressed, reset them and consume release events
+		HardwareKeyboard *kb = PageManager::GetHardwareKeyboard();
+		for (itr = mKeys.begin(); itr != mKeys.end(); ++itr) {
+			kb->ConsumeKeyRelease(itr->first);
+			itr->second = false;
+		}
+
+		doActions();
+		return 0;
+	}
+
+	return 1;
+}
+
+int GUIAction::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	GUIObject::NotifyVarChange(varName, value);
+
+	if (varName.empty() && !isConditionValid() && mKeys.empty() && !mActionW)
+		doActions();
+	else if ((varName.empty() || IsConditionVariable(varName)) && isConditionValid() && isConditionTrue())
+		doActions();
+
+	return 0;
+}
+
+void GUIAction::simulate_progress_bar(void)
+{
+	gui_msg("simulating=Simulating actions...");
+	for (int i = 0; i < 5; i++)
+	{
+		if (PartitionManager.stop_backup.get_value()) {
+			DataManager::SetValue("tw_cancel_backup", 1);
+			gui_msg("backup_cancel=Backup Cancelled");
+			DataManager::SetValue("ui_progress", 0);
+			PartitionManager.stop_backup.set_value(0);
+			return;
+		}
+		usleep(500000);
+		DataManager::SetValue("ui_progress", i * 20);
+	}
+}
+
+int GUIAction::flash_zip(std::string filename, int* wipe_cache)
+{
+	int ret_val = 0;
+
+	DataManager::SetValue("ui_progress", 0);
+	DataManager::SetValue("ui_portion_size", 0);
+	DataManager::SetValue("ui_portion_start", 0);
+
+	if (filename.empty())
+	{
+		LOGERR("No file specified.\n");
+		return -1;
+	}
+
+	if (!TWFunc::Path_Exists(filename)) {
+		if (!PartitionManager.Mount_By_Path(filename, true)) {
+			return -1;
+		}
+		if (!TWFunc::Path_Exists(filename)) {
+			gui_msg(Msg(msg::kError, "unable_to_locate=Unable to locate {1}.")(filename));
+			return -1;
+		}
+	}
+
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		char apex_enabled[PROPERTY_VALUE_MAX];
+		property_get("twrp.apex.flattened", apex_enabled, "");
+		if (strcmp(apex_enabled, "true") == 0) {
+			umount("/apex");
+		}
+		ret_val = TWinstall_zip(filename.c_str(), wipe_cache, (bool) !DataManager::GetIntValue(TW_SKIP_DIGEST_CHECK_ZIP_VAR));
+		PartitionManager.Unlock_Block_Partitions();
+		// Now, check if we need to ensure TWRP remains installed...
+		struct stat st;
+		if (stat("/system/bin/installTwrp", &st) == 0)
+		{
+			DataManager::SetValue("tw_operation", "Configuring TWRP");
+			DataManager::SetValue("tw_partition", "");
+			gui_msg("config_twrp=Configuring TWRP...");
+			if (TWFunc::Exec_Cmd("/system/bin/installTwrp reinstall") < 0)
+			{
+				gui_msg("config_twrp_err=Unable to configure TWRP with this kernel.");
+			}
+		}
+	}
+
+	// Done
+	DataManager::SetValue("ui_progress", 100);
+	DataManager::SetValue("ui_progress", 0);
+	DataManager::SetValue("ui_portion_size", 0);
+	DataManager::SetValue("ui_portion_start", 0);
+	return ret_val;
+}
+
+GUIAction::ThreadType GUIAction::getThreadType(const GUIAction::Action& action)
+{
+	string func = gui_parse_text(action.mFunction);
+	bool needsThread = setActionsRunningInCallerThread.find(func) == setActionsRunningInCallerThread.end();
+	if (needsThread) {
+		if (func == "cancelbackup")
+			return THREAD_CANCEL;
+		else
+			return THREAD_ACTION;
+	}
+	return THREAD_NONE;
+}
+
+int GUIAction::doActions()
+{
+	if (mActions.size() < 1)
+		return -1;
+
+	// Determine in which thread to run the actions.
+	// Do it for all actions at once before starting, so that we can cancel the whole batch if the thread is already busy.
+	ThreadType threadType = THREAD_NONE;
+	std::vector<Action>::iterator it;
+	for (it = mActions.begin(); it != mActions.end(); ++it) {
+		ThreadType tt = getThreadType(*it);
+		if (tt == THREAD_NONE)
+			continue;
+		if (threadType == THREAD_NONE)
+			threadType = tt;
+		else if (threadType != tt) {
+			LOGERR("Can't mix normal and cancel actions in the same list.\n"
+				"Running the whole batch in the cancel thread.\n");
+			threadType = THREAD_CANCEL;
+			break;
+		}
+	}
+
+	// Now run the actions in the desired thread.
+	switch (threadType) {
+		case THREAD_ACTION:
+			action_thread.threadActions(this);
+			break;
+
+		case THREAD_CANCEL:
+			cancel_thread.threadActions(this);
+			break;
+
+		default: {
+			// no iterators here because theme reloading might kill our object
+			const size_t cnt = mActions.size();
+			for (size_t i = 0; i < cnt; ++i)
+				doAction(mActions[i]);
+		}
+	}
+
+	return 0;
+}
+
+int GUIAction::doAction(Action action)
+{
+	DataManager::GetValue(TW_SIMULATE_ACTIONS, simulate);
+
+	std::string function = gui_parse_text(action.mFunction);
+	std::string arg = gui_parse_text(action.mArg);
+
+	// find function and execute it
+	mapFunc::const_iterator funcitr = mf.find(function);
+	if (funcitr != mf.end())
+		return (this->*funcitr->second)(arg);
+
+	LOGERR("Unknown action '%s'\n", function.c_str());
+	return -1;
+}
+
+void GUIAction::operation_start(const string operation_name)
+{
+	LOGINFO("operation_start: '%s'\n", operation_name.c_str());
+	time(&Start);
+	DataManager::SetValue(TW_ACTION_BUSY, 1);
+	DataManager::SetValue("ui_progress", 0);
+	DataManager::SetValue("ui_portion_size", 0);
+	DataManager::SetValue("ui_portion_start", 0);
+	DataManager::SetValue("tw_operation", operation_name);
+	DataManager::SetValue("tw_operation_state", 0);
+	DataManager::SetValue("tw_operation_status", 0);
+	bool tw_ab_device = TWFunc::get_log_dir() != CACHE_LOGS_DIR;
+	DataManager::SetValue("tw_ab_device", tw_ab_device);
+}
+
+void GUIAction::operation_end(const int operation_status)
+{
+	time_t Stop;
+	int simulate_fail;
+	DataManager::SetValue("ui_progress", 100);
+	if (simulate) {
+		DataManager::GetValue(TW_SIMULATE_FAIL, simulate_fail);
+		if (simulate_fail != 0)
+			DataManager::SetValue("tw_operation_status", 1);
+		else
+			DataManager::SetValue("tw_operation_status", 0);
+	} else {
+		if (operation_status != 0) {
+			DataManager::SetValue("tw_operation_status", 1);
+		}
+		else {
+			DataManager::SetValue("tw_operation_status", 0);
+		}
+	}
+	DataManager::SetValue("tw_operation_state", 1);
+	DataManager::SetValue(TW_ACTION_BUSY, 0);
+	blankTimer.resetTimerAndUnblank();
+	property_set("twrp.action_complete", "1");
+	time(&Stop);
+
+#ifndef TW_NO_HAPTICS
+	if ((int) difftime(Stop, Start) > 10)
+		DataManager::Vibrate("tw_action_vibrate");
+#endif
+
+	LOGINFO("operation_end - status=%d\n", operation_status);
+}
+
+int GUIAction::reboot(std::string arg)
+{
+	sync();
+	DataManager::SetValue("tw_gui_done", 1);
+	DataManager::SetValue("tw_reboot_arg", arg);
+
+	return 0;
+}
+
+int GUIAction::home(std::string arg __unused)
+{
+	gui_changePage("main");
+	return 0;
+}
+
+int GUIAction::key(std::string arg)
+{
+	const int key = getKeyByName(arg);
+	PageManager::NotifyKey(key, true);
+	PageManager::NotifyKey(key, false);
+	return 0;
+}
+
+int GUIAction::page(std::string arg)
+{
+	property_set("twrp.action_complete", "0");
+	std::string page_name = gui_parse_text(arg);
+	return gui_changePage(page_name);
+}
+
+int GUIAction::reload(std::string arg __unused)
+{
+	PageManager::RequestReload();
+	// The actual reload is handled in pages.cpp in PageManager::RunReload()
+	// The reload will occur on the next Update or Render call and will
+	// be performed in the rendoer thread instead of the action thread
+	// to prevent crashing which could occur when we start deleting
+	// GUI resources in the action thread while we attempt to render
+	// with those same resources in another thread.
+	return 0;
+}
+
+int GUIAction::readBackup(std::string arg __unused)
+{
+	string Restore_Name;
+
+	DataManager::GetValue("tw_restore", Restore_Name);
+	PartitionManager.Set_Restore_Files(Restore_Name);
+	return 0;
+}
+
+int GUIAction::set(std::string arg)
+{
+	if (arg.find('=') != string::npos)
+	{
+		string varName = arg.substr(0, arg.find('='));
+		string value = arg.substr(arg.find('=') + 1, string::npos);
+
+		DataManager::GetValue(value, value);
+		DataManager::SetValue(varName, value);
+	}
+	else
+		DataManager::SetValue(arg, "1");
+	return 0;
+}
+
+int GUIAction::clear(std::string arg)
+{
+	DataManager::SetValue(arg, "0");
+	return 0;
+}
+
+int GUIAction::mount(std::string arg)
+{
+	if (arg == "usb") {
+		DataManager::SetValue(TW_ACTION_BUSY, 1);
+		if (!simulate)
+			PartitionManager.usb_storage_enable();
+		else
+			gui_msg("simulating=Simulating actions...");
+	} else if (!simulate) {
+		PartitionManager.Mount_By_Path(arg, true);
+		PartitionManager.Add_MTP_Storage(arg);
+	} else
+		gui_msg("simulating=Simulating actions...");
+	return 0;
+}
+
+int GUIAction::unmount(std::string arg)
+{
+	if (arg == "usb") {
+		if (!simulate)
+			PartitionManager.usb_storage_disable();
+		else
+			gui_msg("simulating=Simulating actions...");
+		DataManager::SetValue(TW_ACTION_BUSY, 0);
+	} else if (!simulate) {
+		PartitionManager.UnMount_By_Path(arg, true);
+	} else
+		gui_msg("simulating=Simulating actions...");
+	return 0;
+}
+
+int GUIAction::restoredefaultsettings(std::string arg __unused)
+{
+	operation_start("Restore Defaults");
+	if (simulate) // Simulated so that people don't accidently wipe out the "simulation is on" setting
+		gui_msg("simulating=Simulating actions...");
+	else {
+		DataManager::ResetDefaults();
+		PartitionManager.Update_System_Details();
+		PartitionManager.Mount_Current_Storage(true);
+	}
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::copylog(std::string arg __unused)
+{
+	operation_start("Copy Log");
+	if (!simulate)
+	{
+		string dst, curr_storage;
+		int copy_kernel_log = 0;
+		int copy_logcat = 1;
+
+		DataManager::GetValue("tw_include_kernel_log", copy_kernel_log);
+		DataManager::GetValue("tw_include_logcat", copy_logcat);
+		PartitionManager.Mount_Current_Storage(true);
+		curr_storage = DataManager::GetCurrentStoragePath();
+		dst = curr_storage + "/recovery.log";
+		TWFunc::copy_file("/tmp/recovery.log", dst.c_str(), 0755);
+		tw_set_default_metadata(dst.c_str());
+		if (copy_kernel_log)
+			TWFunc::copy_kernel_log(curr_storage);
+		if (copy_logcat)
+			TWFunc::copy_logcat(curr_storage);
+		sync();
+		gui_msg(Msg("copy_log=Copied recovery log to {1}")(dst));
+	} else
+		simulate_progress_bar();
+	operation_end(0);
+	return 0;
+}
+
+
+int GUIAction::compute(std::string arg)
+{
+	if (arg.find("+") != string::npos)
+	{
+		string varName = arg.substr(0, arg.find('+'));
+		string string_to_add = arg.substr(arg.find('+') + 1, string::npos);
+		int amount_to_add = atoi(string_to_add.c_str());
+		int value;
+
+		DataManager::GetValue(varName, value);
+		DataManager::SetValue(varName, value + amount_to_add);
+		return 0;
+	}
+	if (arg.find("-") != string::npos)
+	{
+		string varName = arg.substr(0, arg.find('-'));
+		string string_to_subtract = arg.substr(arg.find('-') + 1, string::npos);
+		int amount_to_subtract = atoi(string_to_subtract.c_str());
+		int value;
+
+		DataManager::GetValue(varName, value);
+		value -= amount_to_subtract;
+		if (value <= 0)
+			value = 0;
+		DataManager::SetValue(varName, value);
+		return 0;
+	}
+	if (arg.find("*") != string::npos)
+	{
+		string varName = arg.substr(0, arg.find('*'));
+		string multiply_by_str = gui_parse_text(arg.substr(arg.find('*') + 1, string::npos));
+		int multiply_by = atoi(multiply_by_str.c_str());
+		int value;
+
+		DataManager::GetValue(varName, value);
+		DataManager::SetValue(varName, value*multiply_by);
+		return 0;
+	}
+	if (arg.find("/") != string::npos)
+	{
+		string varName = arg.substr(0, arg.find('/'));
+		string divide_by_str = gui_parse_text(arg.substr(arg.find('/') + 1, string::npos));
+		int divide_by = atoi(divide_by_str.c_str());
+		int value;
+
+		if (divide_by != 0)
+		{
+			DataManager::GetValue(varName, value);
+			DataManager::SetValue(varName, value/divide_by);
+		}
+		return 0;
+	}
+	LOGERR("Unable to perform compute '%s'\n", arg.c_str());
+	return -1;
+}
+
+int GUIAction::setguitimezone(std::string arg __unused)
+{
+	string SelectedZone;
+	DataManager::GetValue(TW_TIME_ZONE_GUISEL, SelectedZone); // read the selected time zone into SelectedZone
+	string Zone = SelectedZone.substr(0, SelectedZone.find(';')); // parse to get time zone
+	string DSTZone = SelectedZone.substr(SelectedZone.find(';') + 1, string::npos); // parse to get DST component
+
+	int dst;
+	DataManager::GetValue(TW_TIME_ZONE_GUIDST, dst); // check wether user chose to use DST
+
+	string offset;
+	DataManager::GetValue(TW_TIME_ZONE_GUIOFFSET, offset); // pull in offset
+
+	string NewTimeZone = Zone;
+	if (offset != "0")
+		NewTimeZone += ":" + offset;
+
+	if (dst != 0)
+		NewTimeZone += DSTZone;
+
+	DataManager::SetValue(TW_TIME_ZONE_VAR, NewTimeZone);
+	DataManager::update_tz_environment_variables();
+	return 0;
+}
+
+int GUIAction::overlay(std::string arg)
+{
+	return gui_changeOverlay(arg);
+}
+
+int GUIAction::queuezip(std::string arg __unused)
+{
+	if (zip_queue_index >= 10) {
+		gui_msg("max_queue=Maximum zip queue reached!");
+		return 0;
+	}
+	DataManager::GetValue("tw_filename", zip_queue[zip_queue_index]);
+	if (strlen(zip_queue[zip_queue_index].c_str()) > 0) {
+		zip_queue_index++;
+		DataManager::SetValue(TW_ZIP_QUEUE_COUNT, zip_queue_index);
+	}
+	return 0;
+}
+
+int GUIAction::cancelzip(std::string arg __unused)
+{
+	if (zip_queue_index <= 0) {
+		gui_msg("min_queue=Minimum zip queue reached!");
+		return 0;
+	} else {
+		zip_queue_index--;
+		DataManager::SetValue(TW_ZIP_QUEUE_COUNT, zip_queue_index);
+	}
+	return 0;
+}
+
+int GUIAction::queueclear(std::string arg __unused)
+{
+	zip_queue_index = 0;
+	DataManager::SetValue(TW_ZIP_QUEUE_COUNT, zip_queue_index);
+	return 0;
+}
+
+int GUIAction::sleep(std::string arg)
+{
+	operation_start("Sleep");
+	usleep(atoi(arg.c_str()));
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::sleepcounter(std::string arg)
+{
+	operation_start("SleepCounter");
+	// Ensure user notices countdown in case it needs to be cancelled
+	blankTimer.resetTimerAndUnblank();
+	int total = atoi(arg.c_str());
+	for (int t = total; t > 0; t--) {
+		int progress = (int)(((float)(total-t)/(float)total)*100.0);
+		DataManager::SetValue("ui_progress", progress);
+		::sleep(1);
+		DataManager::SetValue("tw_sleep", t-1);
+	}
+	DataManager::SetValue("ui_progress", 100);
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::appenddatetobackupname(std::string arg __unused)
+{
+	operation_start("AppendDateToBackupName");
+	string Backup_Name;
+	DataManager::GetValue(TW_BACKUP_NAME, Backup_Name);
+	Backup_Name += TWFunc::Get_Current_Date();
+	if (Backup_Name.size() > MAX_BACKUP_NAME_LEN)
+		Backup_Name.resize(MAX_BACKUP_NAME_LEN);
+	DataManager::SetValue(TW_BACKUP_NAME, Backup_Name);
+	PageManager::NotifyKey(KEY_END, true);
+	PageManager::NotifyKey(KEY_END, false);
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::generatebackupname(std::string arg __unused)
+{
+	operation_start("GenerateBackupName");
+	TWFunc::Auto_Generate_Backup_Name();
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::checkpartitionlist(std::string arg)
+{
+	string List, part_path;
+	int count = 0;
+
+	if (arg.empty())
+		arg = "tw_wipe_list";
+	DataManager::GetValue(arg, List);
+	LOGINFO("checkpartitionlist list '%s'\n", List.c_str());
+	if (!List.empty()) {
+		size_t start_pos = 0, end_pos = List.find(";", start_pos);
+		while (end_pos != string::npos && start_pos < List.size()) {
+			part_path = List.substr(start_pos, end_pos - start_pos);
+			LOGINFO("checkpartitionlist part_path '%s'\n", part_path.c_str());
+			if (part_path == "/and-sec" || part_path == "DALVIK" || part_path == "INTERNAL") {
+				// Do nothing
+			} else {
+				count++;
+			}
+			start_pos = end_pos + 1;
+			end_pos = List.find(";", start_pos);
+		}
+		DataManager::SetValue("tw_check_partition_list", count);
+	} else {
+		DataManager::SetValue("tw_check_partition_list", 0);
+	}
+	return 0;
+}
+
+int GUIAction::getpartitiondetails(std::string arg)
+{
+	string List, part_path;
+
+	if (arg.empty())
+		arg = "tw_wipe_list";
+	DataManager::GetValue(arg, List);
+	LOGINFO("getpartitiondetails list '%s'\n", List.c_str());
+	if (!List.empty()) {
+		size_t start_pos = 0, end_pos = List.find(";", start_pos);
+		part_path = List;
+		while (end_pos != string::npos && start_pos < List.size()) {
+			part_path = List.substr(start_pos, end_pos - start_pos);
+			LOGINFO("getpartitiondetails part_path '%s'\n", part_path.c_str());
+			if (part_path == "/and-sec" || part_path == "DALVIK" || part_path == "INTERNAL") {
+				// Do nothing
+			} else {
+				DataManager::SetValue("tw_partition_path", part_path);
+				break;
+			}
+			start_pos = end_pos + 1;
+			end_pos = List.find(";", start_pos);
+		}
+		if (!part_path.empty()) {
+			TWPartition* Part = PartitionManager.Find_Partition_By_Path(part_path);
+			if (Part) {
+				unsigned long long mb = 1048576;
+
+				DataManager::SetValue("tw_partition_name", Part->Display_Name);
+				DataManager::SetValue("tw_partition_mount_point", Part->Mount_Point);
+				DataManager::SetValue("tw_partition_file_system", Part->Current_File_System);
+				DataManager::SetValue("tw_partition_size", Part->Size / mb);
+				DataManager::SetValue("tw_partition_used", Part->Used / mb);
+				DataManager::SetValue("tw_partition_free", Part->Free / mb);
+				DataManager::SetValue("tw_partition_backup_size", Part->Backup_Size / mb);
+				DataManager::SetValue("tw_partition_removable", Part->Removable);
+				DataManager::SetValue("tw_partition_is_present", Part->Is_Present);
+
+				if (Part->Can_Repair())
+					DataManager::SetValue("tw_partition_can_repair", 1);
+				else
+					DataManager::SetValue("tw_partition_can_repair", 0);
+				if (Part->Can_Resize())
+					DataManager::SetValue("tw_partition_can_resize", 1);
+				else
+					DataManager::SetValue("tw_partition_can_resize", 0);
+				if (TWFunc::Path_Exists("/system/bin/mkfs.fat"))
+					DataManager::SetValue("tw_partition_vfat", 1);
+				else
+					DataManager::SetValue("tw_partition_vfat", 0);
+				if (TWFunc::Path_Exists("/system/bin/mkexfatfs"))
+					DataManager::SetValue("tw_partition_exfat", 1);
+				else
+					DataManager::SetValue("tw_partition_exfat", 0);
+				if (TWFunc::Path_Exists("/system/bin/mkfs.f2fs") || TWFunc::Path_Exists("/system/bin/make_f2fs"))
+					DataManager::SetValue("tw_partition_f2fs", 1);
+				else
+					DataManager::SetValue("tw_partition_f2fs", 0);
+				if (TWFunc::Path_Exists("/system/bin/mke2fs"))
+					DataManager::SetValue("tw_partition_ext", 1);
+				else
+					DataManager::SetValue("tw_partition_ext", 0);
+				return 0;
+			} else {
+				LOGERR("Unable to locate partition: '%s'\n", part_path.c_str());
+			}
+		}
+	}
+	DataManager::SetValue("tw_partition_name", "");
+	DataManager::SetValue("tw_partition_file_system", "");
+	// Set this to 0 to prevent trying to partition this device, just in case
+	DataManager::SetValue("tw_partition_removable", 0);
+	return 0;
+}
+
+int GUIAction::screenshot(std::string arg __unused)
+{
+	time_t tm;
+	char path[256];
+	int path_len;
+	uid_t uid = AID_MEDIA_RW;
+	gid_t gid = AID_MEDIA_RW;
+
+	const std::string storage = DataManager::GetCurrentStoragePath();
+	if (PartitionManager.Is_Mounted_By_Path(storage)) {
+		snprintf(path, sizeof(path), "%s/Pictures/Screenshots/", storage.c_str());
+	} else {
+		strcpy(path, "/tmp/");
+	}
+
+	if (!TWFunc::Create_Dir_Recursive(path, 0775, uid, gid))
+		return 0;
+
+	tm = time(NULL);
+	path_len = strlen(path);
+
+	// Screenshot_2014-01-01-18-21-38.png
+	strftime(path+path_len, sizeof(path)-path_len, "Screenshot_%Y-%m-%d-%H-%M-%S.png", localtime(&tm));
+
+	int res = gr_save_screenshot(path);
+	if (res == 0) {
+		chmod(path, 0666);
+		chown(path, uid, gid);
+
+		gui_msg(Msg("screenshot_saved=Screenshot was saved to {1}")(path));
+
+		// blink to notify that the screenshot was taken
+		gr_color(255, 255, 255, 255);
+		gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+		gr_flip();
+		gui_forceRender();
+	} else {
+		gui_err("screenshot_err=Failed to take a screenshot!");
+	}
+	return 0;
+}
+
+int GUIAction::setbrightness(std::string arg)
+{
+	return TWFunc::Set_Brightness(arg);
+}
+
+int GUIAction::fileexists(std::string arg)
+{
+	struct stat st;
+	string newpath = arg + "/.";
+
+	operation_start("FileExists");
+	if (stat(arg.c_str(), &st) == 0 || stat(newpath.c_str(), &st) == 0)
+		operation_end(0);
+	else
+		operation_end(1);
+	return 0;
+}
+
+void GUIAction::reinject_after_flash()
+{
+	if (DataManager::GetIntValue(TW_HAS_INJECTTWRP) == 1 && DataManager::GetIntValue(TW_INJECT_AFTER_ZIP) == 1) {
+		gui_msg("injecttwrp=Injecting TWRP into boot image...");
+		if (simulate) {
+			simulate_progress_bar();
+		} else {
+			TWPartition* Boot = PartitionManager.Find_Partition_By_Path("/boot");
+			if (Boot == NULL || Boot->Current_File_System != "emmc")
+				TWFunc::Exec_Cmd("injecttwrp --dump /tmp/backup_recovery_ramdisk.img /tmp/injected_boot.img --flash");
+			else {
+				string injectcmd = "injecttwrp --dump /tmp/backup_recovery_ramdisk.img /tmp/injected_boot.img --flash bd=" + Boot->Actual_Block_Device;
+				TWFunc::Exec_Cmd(injectcmd);
+			}
+			gui_msg("done=Done.");
+		}
+	}
+}
+
+int GUIAction::ozip_decrypt(string zip_path)
+{
+	if (!TWFunc::Path_Exists("/system/bin/ozip_decrypt")) {
+            return 1;
+        }
+    gui_msg("ozip_decrypt_decryption=Starting Ozip Decryption...");
+	TWFunc::Exec_Cmd("ozip_decrypt " + (string)TW_OZIP_DECRYPT_KEY + " '" + zip_path + "'");
+    gui_msg("ozip_decrypt_finish=Ozip Decryption Finished!");
+	return 0;
+}
+
+int GUIAction::flash(std::string arg)
+{
+	int i, ret_val = 0, wipe_cache = 0;
+	// We're going to jump to this page first, like a loading page
+	gui_changePage(arg);
+	for (i=0; i<zip_queue_index; i++) {
+		string zip_path = zip_queue[i];
+		size_t slashpos = zip_path.find_last_of('/');
+		string zip_filename = (slashpos == string::npos) ? zip_path : zip_path.substr(slashpos + 1);
+		operation_start("Flashing");
+		if((zip_path.substr(zip_path.size() - 4, 4)) == "ozip")
+		{
+			if((ozip_decrypt(zip_path)) != 0)
+			{
+		LOGERR("Unable to find ozip_decrypt!");
+				break;
+			}
+			zip_filename = (zip_filename.substr(0, zip_filename.size() - 4)).append("zip");
+			zip_path = (zip_path.substr(0, zip_path.size() - 4)).append("zip");
+			if (!TWFunc::Path_Exists(zip_path)) {
+				LOGERR("Unable to find decrypted zip");
+				break;
+			}
+		}
+		DataManager::SetValue("tw_filename", zip_path);
+		DataManager::SetValue("tw_file", zip_filename);
+		DataManager::SetValue(TW_ZIP_INDEX, (i + 1));
+
+		TWFunc::SetPerformanceMode(true);
+		ret_val = flash_zip(zip_path, &wipe_cache);
+		TWFunc::SetPerformanceMode(false);
+		if (ret_val != 0) {
+			gui_msg(Msg(msg::kError, "zip_err=Error installing zip file '{1}'")(zip_path));
+			ret_val = 1;
+			break;
+		}
+	}
+	zip_queue_index = 0;
+
+	if (wipe_cache) {
+		gui_msg("zip_wipe_cache=One or more zip requested a cache wipe -- Wiping cache now.");
+		PartitionManager.Wipe_By_Path("/cache");
+	}
+
+	reinject_after_flash();
+	PartitionManager.Update_System_Details();
+	operation_end(ret_val);
+	// This needs to be after the operation_end call so we change pages before we change variables that we display on the screen
+	DataManager::SetValue(TW_ZIP_QUEUE_COUNT, zip_queue_index);
+	return 0;
+}
+
+int GUIAction::wipe(std::string arg)
+{
+	operation_start("Format");
+	DataManager::SetValue("tw_partition", arg);
+	int ret_val = false;
+
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		if (arg == "data")
+			ret_val = PartitionManager.Factory_Reset();
+		else if (arg == "battery")
+			ret_val = PartitionManager.Wipe_Battery_Stats();
+		else if (arg == "rotate")
+			ret_val = PartitionManager.Wipe_Rotate_Data();
+		else if (arg == "dalvik")
+			ret_val = PartitionManager.Wipe_Dalvik_Cache();
+		else if (arg == "DATAMEDIA") {
+			ret_val = PartitionManager.Format_Data();
+		} else if (arg == "INTERNAL") {
+			int has_datamedia;
+
+			DataManager::GetValue(TW_HAS_DATA_MEDIA, has_datamedia);
+			if (has_datamedia) {
+				ret_val = PartitionManager.Wipe_Media_From_Data();
+			} else {
+				ret_val = PartitionManager.Wipe_By_Path(DataManager::GetSettingsStoragePath());
+			}
+		} else if (arg == "EXTERNAL") {
+			string External_Path;
+
+			DataManager::GetValue(TW_EXTERNAL_PATH, External_Path);
+			ret_val = PartitionManager.Wipe_By_Path(External_Path);
+		} else if (arg == "ANDROIDSECURE") {
+			ret_val = PartitionManager.Wipe_Android_Secure();
+		} else if (arg == "LIST") {
+			string Wipe_List, wipe_path;
+			bool skip = false;
+			ret_val = true;
+
+			DataManager::GetValue("tw_wipe_list", Wipe_List);
+			LOGINFO("wipe list '%s'\n", Wipe_List.c_str());
+			if (!Wipe_List.empty()) {
+				size_t start_pos = 0, end_pos = Wipe_List.find(";", start_pos);
+				while (end_pos != string::npos && start_pos < Wipe_List.size()) {
+					wipe_path = Wipe_List.substr(start_pos, end_pos - start_pos);
+					LOGINFO("wipe_path '%s'\n", wipe_path.c_str());
+					if (wipe_path == "/and-sec") {
+						if (!PartitionManager.Wipe_Android_Secure()) {
+							gui_msg("and_sec_wipe_err=Unable to wipe android secure");
+							ret_val = false;
+							break;
+						} else {
+							skip = true;
+						}
+					} else if (wipe_path == "DALVIK") {
+						if (!PartitionManager.Wipe_Dalvik_Cache()) {
+							gui_err("dalvik_wipe_err=Failed to wipe dalvik");
+							ret_val = false;
+							break;
+						} else {
+							skip = true;
+						}
+					} else if (wipe_path == "INTERNAL") {
+						if (!PartitionManager.Wipe_Media_From_Data()) {
+							ret_val = false;
+							break;
+						} else {
+							skip = true;
+						}
+					}
+					if (!skip) {
+						if (!PartitionManager.Wipe_By_Path(wipe_path)) {
+							gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(wipe_path));
+							ret_val = false;
+							break;
+						} else if (wipe_path == DataManager::GetSettingsStoragePath()) {
+							arg = wipe_path;
+						}
+					} else {
+						skip = false;
+					}
+					start_pos = end_pos + 1;
+					end_pos = Wipe_List.find(";", start_pos);
+				}
+			}
+		} else
+			ret_val = PartitionManager.Wipe_By_Path(arg);
+#ifndef TW_OEM_BUILD
+		if (arg == DataManager::GetSettingsStoragePath()) {
+			// If we wiped the settings storage path, recreate the TWRP folder and dump the settings
+			string Storage_Path = DataManager::GetSettingsStoragePath();
+
+			if (PartitionManager.Mount_By_Path(Storage_Path, true)) {
+				LOGINFO("Making TWRP folder and saving settings.\n");
+				Storage_Path += "/TWRP";
+				mkdir(Storage_Path.c_str(), 0777);
+				DataManager::Flush();
+			} else {
+				LOGERR("Unable to recreate TWRP folder and save settings.\n");
+			}
+		}
+#endif
+	}
+	PartitionManager.Update_System_Details();
+	if (ret_val)
+		ret_val = 0; // 0 is success
+	else
+		ret_val = 1; // 1 is failure
+	operation_end(ret_val);
+	return 0;
+}
+
+int GUIAction::refreshsizes(std::string arg __unused)
+{
+	operation_start("Refreshing Sizes");
+	if (simulate) {
+		simulate_progress_bar();
+	} else
+		PartitionManager.Update_System_Details();
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::nandroid(std::string arg)
+{
+	if (simulate) {
+		PartitionManager.stop_backup.set_value(0);
+		DataManager::SetValue("tw_partition", "Simulation");
+		simulate_progress_bar();
+		operation_end(0);
+	} else {
+		operation_start("Nandroid");
+		int ret = 0;
+
+		if (arg == "backup") {
+			string Backup_Name;
+			DataManager::GetValue(TW_BACKUP_NAME, Backup_Name);
+			string auto_gen = gui_lookup("auto_generate", "(Auto Generate)");
+			if (Backup_Name == auto_gen || Backup_Name == gui_lookup("curr_date", "(Current Date)") || Backup_Name == "0" || Backup_Name == "(" || PartitionManager.Check_Backup_Name(Backup_Name, true, true) == 0) {
+				ret = PartitionManager.Run_Backup(false);
+				DataManager::SetValue("tw_encrypt_backup", 0); // reset value so we don't encrypt every subsequent backup
+				if (!PartitionManager.stop_backup.get_value()) {
+					if (ret == false)
+						ret = 1; // 1 for failure
+					else
+						ret = 0; // 0 for success
+					DataManager::SetValue("tw_cancel_backup", 0);
+				} else {
+					DataManager::SetValue("tw_cancel_backup", 1);
+					gui_msg("backup_cancel=Backup Cancelled");
+					ret = 0;
+				}
+			} else {
+				operation_end(1);
+				return -1;
+			}
+			DataManager::SetValue(TW_BACKUP_NAME, auto_gen);
+		} else if (arg == "restore") {
+			string Restore_Name;
+			int gui_adb_backup;
+
+			DataManager::GetValue("tw_restore", Restore_Name);
+			DataManager::GetValue("tw_enable_adb_backup", gui_adb_backup);
+			if (gui_adb_backup) {
+				DataManager::SetValue("tw_operation_state", 1);
+				if (TWFunc::stream_adb_backup(Restore_Name) == 0)
+					ret = 0; // success
+				else
+					ret = 1; // failure
+				DataManager::SetValue("tw_enable_adb_backup", 0);
+				ret = 0; // assume success???
+			} else {
+				if (PartitionManager.Run_Restore(Restore_Name))
+					ret = 0; // success
+				else
+					ret = 1; // failure
+			}
+		} else {
+			operation_end(1); // invalid arg specified, fail
+			return -1;
+		}
+		operation_end(ret);
+		return ret;
+	}
+	return 0;
+}
+
+int GUIAction::cancelbackup(std::string arg __unused) {
+	if (simulate) {
+		PartitionManager.stop_backup.set_value(1);
+	}
+	else {
+		int op_status = PartitionManager.Cancel_Backup();
+		if (op_status != 0)
+			op_status = 1; // failure
+	}
+
+	return 0;
+}
+
+int GUIAction::fixcontexts(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Fix Contexts");
+	LOGINFO("fix contexts started!\n");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		op_status = PartitionManager.Fix_Contexts();
+		if (op_status != 0)
+			op_status = 1; // failure
+	}
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::fixpermissions(std::string arg)
+{
+	return fixcontexts(arg);
+}
+
+int GUIAction::dd(std::string arg)
+{
+	operation_start("imaging");
+
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		string cmd = "dd " + arg;
+		TWFunc::Exec_Cmd(cmd);
+	}
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::partitionsd(std::string arg __unused)
+{
+	operation_start("Partition SD Card");
+	int ret_val = 0;
+
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		int allow_partition;
+		DataManager::GetValue(TW_ALLOW_PARTITION_SDCARD, allow_partition);
+		if (allow_partition == 0) {
+			gui_err("no_real_sdcard=This device does not have a real SD Card! Aborting!");
+		} else {
+			if (!PartitionManager.Partition_SDCard())
+				ret_val = 1; // failed
+		}
+	}
+	operation_end(ret_val);
+	return 0;
+
+}
+
+int GUIAction::cmd(std::string arg)
+{
+	int op_status = 0;
+
+	operation_start("Command");
+	LOGINFO("Running command: '%s'\n", arg.c_str());
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		op_status = TWFunc::Exec_Cmd(arg);
+		if (op_status != 0)
+			op_status = 1;
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::terminalcommand(std::string arg)
+{
+	int op_status = 0;
+	string cmdpath, command;
+
+	DataManager::GetValue("tw_terminal_location", cmdpath);
+	operation_start("CommandOutput");
+	gui_print("%s # %s\n", cmdpath.c_str(), arg.c_str());
+	if (simulate) {
+		simulate_progress_bar();
+		operation_end(op_status);
+	} else if (arg == "exit") {
+		LOGINFO("Exiting terminal\n");
+		operation_end(op_status);
+		page("main");
+	} else {
+		command = "cd \"" + cmdpath + "\" && " + arg + " 2>&1";;
+		LOGINFO("Actual command is: '%s'\n", command.c_str());
+		DataManager::SetValue("tw_terminal_state", 1);
+		DataManager::SetValue("tw_background_thread_running", 1);
+		FILE* fp;
+		char line[512];
+
+		fp = popen(command.c_str(), "r");
+		if (fp == NULL) {
+			LOGERR("Error opening command to run (%s).\n", strerror(errno));
+		} else {
+			int fd = fileno(fp), has_data = 0, check = 0, keep_going = -1;
+			struct timeval timeout;
+			fd_set fdset;
+
+			while (keep_going)
+			{
+				FD_ZERO(&fdset);
+				FD_SET(fd, &fdset);
+				timeout.tv_sec = 0;
+				timeout.tv_usec = 400000;
+				has_data = select(fd+1, &fdset, NULL, NULL, &timeout);
+				if (has_data == 0) {
+					// Timeout reached
+					DataManager::GetValue("tw_terminal_state", check);
+					if (check == 0) {
+						keep_going = 0;
+					}
+				} else if (has_data < 0) {
+					// End of execution
+					keep_going = 0;
+				} else {
+					// Try to read output
+					if (fgets(line, sizeof(line), fp) != NULL)
+						gui_print("%s", line); // Display output
+					else
+						keep_going = 0; // Done executing
+				}
+			}
+			fclose(fp);
+		}
+		DataManager::SetValue("tw_operation_status", 0);
+		DataManager::SetValue("tw_operation_state", 1);
+		DataManager::SetValue("tw_terminal_state", 0);
+		DataManager::SetValue("tw_background_thread_running", 0);
+		DataManager::SetValue(TW_ACTION_BUSY, 0);
+	}
+	return 0;
+}
+
+int GUIAction::killterminal(std::string arg __unused)
+{
+	LOGINFO("Sending kill command...\n");
+	operation_start("KillCommand");
+	DataManager::SetValue("tw_operation_status", 0);
+	DataManager::SetValue("tw_operation_state", 1);
+	DataManager::SetValue("tw_terminal_state", 0);
+	DataManager::SetValue("tw_background_thread_running", 0);
+	DataManager::SetValue(TW_ACTION_BUSY, 0);
+	return 0;
+}
+
+int GUIAction::reinjecttwrp(std::string arg __unused)
+{
+	int op_status = 0;
+	operation_start("ReinjectTWRP");
+	gui_msg("injecttwrp=Injecting TWRP into boot image...");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		TWFunc::Exec_Cmd("injecttwrp --dump /tmp/backup_recovery_ramdisk.img /tmp/injected_boot.img --flash");
+		gui_msg("done=Done.");
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::checkbackupname(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("CheckBackupName");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		string Backup_Name;
+		DataManager::GetValue(TW_BACKUP_NAME, Backup_Name);
+		op_status = PartitionManager.Check_Backup_Name(Backup_Name, true, true);
+		if (op_status != 0)
+			op_status = 1;
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::decrypt(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Decrypt");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		string Password;
+		string userID;
+		DataManager::GetValue("tw_crypto_password", Password);
+
+		if (DataManager::GetIntValue(TW_IS_FBE)) {  // for FBE
+			DataManager::GetValue("tw_crypto_user_id", userID);
+			if (userID != "") {
+				op_status = PartitionManager.Decrypt_Device(Password, atoi(userID.c_str()));
+				if (userID != "0") {
+					if (op_status != 0)
+						op_status = 1;
+					operation_end(op_status);
+	          		return 0;
+				}
+			} else {
+				LOGINFO("User ID not found\n");
+				op_status = 1;
+			}
+		::sleep(1);
+		} else {  // for FDE
+			op_status = PartitionManager.Decrypt_Device(Password);
+		}
+
+		if (op_status != 0)
+			op_status = 1;
+		else {
+			DataManager::SetValue(TW_IS_ENCRYPTED, 0);
+
+			int has_datamedia;
+
+			// Check for a custom theme and load it if exists
+			DataManager::GetValue(TW_HAS_DATA_MEDIA, has_datamedia);
+			if (has_datamedia != 0) {
+				if (tw_get_default_metadata(DataManager::GetSettingsStoragePath().c_str()) != 0) {
+					LOGINFO("Failed to get default contexts and file mode for storage files.\n");
+				} else {
+					LOGINFO("Got default contexts and file mode for storage files.\n");
+				}
+			}
+			PartitionManager.Decrypt_Adopted();
+		}
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::adbsideload(std::string arg __unused)
+{
+	operation_start("Sideload");
+	if (simulate) {
+		simulate_progress_bar();
+		operation_end(0);
+	} else {
+		gui_msg("start_sideload=Starting ADB sideload feature...");
+		bool mtp_was_enabled = TWFunc::Toggle_MTP(false);
+
+		// wait for the adb connection
+		Device::BuiltinAction reboot_action = Device::REBOOT_BOOTLOADER;
+		int ret = twrp_sideload("/", &reboot_action);
+		sideload_child_pid = GetMiniAdbdPid();
+		DataManager::SetValue("tw_has_cancel", 0); // Remove cancel button from gui now that the zip install is going to start
+
+		if (ret != 0) {
+			if (ret == -2)
+				gui_msg("need_new_adb=You need adb 1.0.32 or newer to sideload to this device.");
+			ret = 1; // failure
+		} else {
+			int wipe_cache = 0;
+			int wipe_dalvik = 0;
+			DataManager::GetValue("tw_wipe_dalvik", wipe_dalvik);
+			if (wipe_cache || DataManager::GetIntValue("tw_wipe_cache"))
+				PartitionManager.Wipe_By_Path("/cache");
+			if (wipe_dalvik)
+				PartitionManager.Wipe_Dalvik_Cache();
+		}
+		TWFunc::Toggle_MTP(mtp_was_enabled);
+		reinject_after_flash();
+		operation_end(ret);
+	}
+	return 0;
+}
+
+int GUIAction::adbsideloadcancel(std::string arg __unused)
+{
+	struct stat st;
+	DataManager::SetValue("tw_has_cancel", 0); // Remove cancel button from gui
+	gui_msg("cancel_sideload=Cancelling ADB sideload...");
+	LOGINFO("Signaling child sideload process to exit.\n");
+	// Calling stat() on this magic filename signals the minadbd
+	// subprocess to shut down.
+	stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
+	sideload_child_pid = GetMiniAdbdPid();
+	if (!sideload_child_pid) {
+		LOGERR("Unable to get child ID\n");
+		return 0;
+	}
+	::sleep(1);
+	LOGINFO("Killing child sideload process.\n");
+	kill(sideload_child_pid, SIGTERM);
+	int status;
+	LOGINFO("Waiting for child sideload process to exit.\n");
+	waitpid(sideload_child_pid, &status, 0);
+	sideload_child_pid = 0;
+	DataManager::SetValue("tw_page_done", "1"); // For OpenRecoveryScript support
+	return 0;
+}
+
+int GUIAction::openrecoveryscript(std::string arg __unused)
+{
+	operation_start("OpenRecoveryScript");
+	if (simulate) {
+		simulate_progress_bar();
+		operation_end(0);
+	} else {
+		int op_status = OpenRecoveryScript::Run_OpenRecoveryScript_Action();
+		operation_end(op_status);
+	}
+	return 0;
+}
+
+int GUIAction::installsu(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Install SuperSU");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		LOGERR("Installing SuperSU was deprecated from TWRP.\n");
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::fixsu(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Fixing Superuser Permissions");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		LOGERR("Fixing su permissions was deprecated from TWRP.\n");
+		LOGERR("4.3+ ROMs with SELinux will always lose su perms.\n");
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::decrypt_backup(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Try Restore Decrypt");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		string Restore_Path, Filename, Password;
+		DataManager::GetValue("tw_restore", Restore_Path);
+		Restore_Path += "/";
+		DataManager::GetValue("tw_restore_password", Password);
+		TWFunc::SetPerformanceMode(true);
+		if (TWFunc::Try_Decrypting_Backup(Restore_Path, Password))
+			op_status = 0; // success
+		else
+			op_status = 1; // fail
+		TWFunc::SetPerformanceMode(false);
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::repair(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Repair Partition");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		string part_path;
+		DataManager::GetValue("tw_partition_mount_point", part_path);
+		if (PartitionManager.Repair_By_Path(part_path, true)) {
+			op_status = 0; // success
+		} else {
+			op_status = 1; // fail
+		}
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::resize(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Resize Partition");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		string part_path;
+		DataManager::GetValue("tw_partition_mount_point", part_path);
+		if (PartitionManager.Resize_By_Path(part_path, true)) {
+			op_status = 0; // success
+		} else {
+			op_status = 1; // fail
+		}
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::changefilesystem(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Change File System");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		string part_path, file_system;
+		DataManager::GetValue("tw_partition_mount_point", part_path);
+		DataManager::GetValue("tw_action_new_file_system", file_system);
+		if (PartitionManager.Wipe_By_Path(part_path, file_system)) {
+			op_status = 0; // success
+		} else {
+			gui_err("change_fs_err=Error changing file system.");
+			op_status = 1; // fail
+		}
+	}
+	PartitionManager.Update_System_Details();
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::startmtp(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Start MTP");
+	if (PartitionManager.Enable_MTP())
+		op_status = 0; // success
+	else
+		op_status = 1; // fail
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::stopmtp(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Stop MTP");
+	if (PartitionManager.Disable_MTP())
+		op_status = 0; // success
+	else
+		op_status = 1; // fail
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::flashimage(std::string arg __unused)
+{
+	int op_status = 0;
+	bool flag = true;
+
+	operation_start("Flash Image");
+	string path, filename;
+	DataManager::GetValue("tw_zip_location", path);
+	DataManager::GetValue("tw_file", filename);
+
+#ifdef AB_OTA_UPDATER
+	string target = DataManager::GetStrValue("tw_flash_partition");
+	unsigned int pos = target.find_last_of(';');
+	string mount_point = pos != string::npos ? target.substr(0, pos) : "";
+	TWPartition* t_part = PartitionManager.Find_Partition_By_Path(mount_point);
+	bool flash_in_both_slots = DataManager::GetIntValue("tw_flash_both_slots") ? true : false;
+
+	if (t_part != NULL && (flash_in_both_slots && t_part->SlotSelect)) 
+	{
+		string current_slot = PartitionManager.Get_Active_Slot_Display();
+		bool pre_op_status = PartitionManager.Flash_Image(path, filename);
+
+		PartitionManager.Override_Active_Slot(current_slot == "A" ? "B" : "A");
+		op_status = (int) !(pre_op_status && PartitionManager.Flash_Image(path, filename));
+		PartitionManager.Override_Active_Slot(current_slot);
+
+		DataManager::SetValue("tw_flash_both_slots", 0);
+		flag = false;
+	}
+#endif
+	if (flag)
+	{
+		if (PartitionManager.Flash_Image(path, filename))
+			op_status = 0; // success
+		else
+			op_status = 1; // fail
+	}
+	
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::twcmd(std::string arg)
+{
+	operation_start("TWRP CLI Command");
+	if (simulate)
+		simulate_progress_bar();
+	else
+		OpenRecoveryScript::Run_CLI_Command(arg.c_str());
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::getKeyByName(std::string key)
+{
+	if (key == "home")		return KEY_HOMEPAGE;  // note: KEY_HOME is cursor movement (like KEY_END)
+	else if (key == "menu")		return KEY_MENU;
+	else if (key == "back")	 	return KEY_BACK;
+	else if (key == "search")	return KEY_SEARCH;
+	else if (key == "voldown")	return KEY_VOLUMEDOWN;
+	else if (key == "volup")	return KEY_VOLUMEUP;
+	else if (key == "power") {
+		int ret_val;
+		DataManager::GetValue(TW_POWER_BUTTON, ret_val);
+		if (!ret_val)
+			return KEY_POWER;
+		else
+			return ret_val;
+	}
+
+	return atol(key.c_str());
+}
+
+int GUIAction::checkpartitionlifetimewrites(std::string arg)
+{
+	int op_status = 0;
+	TWPartition* sys = PartitionManager.Find_Partition_By_Path(arg);
+
+	operation_start("Check Partition Lifetime Writes");
+	if (sys) {
+		if (sys->Check_Lifetime_Writes() != 0)
+			DataManager::SetValue("tw_lifetime_writes", 1);
+		else
+			DataManager::SetValue("tw_lifetime_writes", 0);
+		op_status = 0; // success
+	} else {
+		DataManager::SetValue("tw_lifetime_writes", 1);
+		op_status = 1; // fail
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::mountsystemtoggle(std::string arg)
+{
+	int op_status = 0;
+	bool remount_system = PartitionManager.Is_Mounted_By_Path(PartitionManager.Get_Android_Root_Path());
+	bool remount_vendor = PartitionManager.Is_Mounted_By_Path("/vendor");
+
+	operation_start("Toggle System Mount");
+	if (!PartitionManager.UnMount_By_Path(PartitionManager.Get_Android_Root_Path(), true)) {
+		op_status = 1; // fail
+	} else {
+		TWPartition* Part = PartitionManager.Find_Partition_By_Path(PartitionManager.Get_Android_Root_Path());
+		if (Part) {
+			if (arg == "0") {
+				DataManager::SetValue("tw_mount_system_ro", 0);
+				Part->Change_Mount_Read_Only(false);
+			} else {
+				DataManager::SetValue("tw_mount_system_ro", 1);
+				Part->Change_Mount_Read_Only(true);
+			}
+			if (remount_system) {
+				Part->Mount(true);
+			}
+			op_status = 0; // success
+		} else {
+			op_status = 1; // fail
+		}
+		Part = PartitionManager.Find_Partition_By_Path("/vendor");
+		if (Part) {
+			if (arg == "0") {
+				Part->Change_Mount_Read_Only(false);
+			} else {
+				Part->Change_Mount_Read_Only(true);
+			}
+			if (remount_vendor) {
+				Part->Mount(true);
+			}
+			op_status = 0; // success
+		} else {
+			op_status = 1; // fail
+		}
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::setlanguage(std::string arg __unused)
+{
+	int op_status = 0;
+
+	operation_start("Set Language");
+	PageManager::LoadLanguage(DataManager::GetStrValue("tw_language"));
+	PageManager::RequestReload();
+	op_status = 0; // success
+
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::togglebacklight(std::string arg __unused)
+{
+	blankTimer.toggleBlank();
+	return 0;
+}
+
+int GUIAction::setbootslot(std::string arg)
+{
+	operation_start("Set Boot Slot");
+	if (!simulate) {
+		if (PartitionManager.Find_Partition_By_Path("/vendor")) {
+			if (!PartitionManager.UnMount_By_Path("/vendor", false)) {
+				// PartitionManager failed to unmount /vendor, this should not happen,
+				// but in case it does, do a lazy unmount
+				LOGINFO("WARNING: vendor partition could not be unmounted normally!\n");
+				umount2("/vendor", MNT_DETACH);
+			}
+		}
+		PartitionManager.Set_Active_Slot(arg);
+	} else {
+		simulate_progress_bar();
+	}
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::checkforapp(std::string arg __unused)
+{
+	operation_start("Check for TWRP App");
+	if (!simulate)
+	{
+		TWFunc::checkforapp();
+	} else
+		simulate_progress_bar();
+
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::installapp(std::string arg __unused)
+{
+	operation_start("Install TWRP App");
+	if (!simulate)
+	{
+		if (DataManager::GetIntValue("tw_mount_system_ro") > 0 || DataManager::GetIntValue("tw_app_install_system") == 0) {
+			if (PartitionManager.Mount_By_Path("/data", true)) {
+				string install_path = "/data/app";
+				string context = "u:object_r:apk_data_file:s0";
+				if (!TWFunc::Path_Exists(install_path)) {
+					if (mkdir(install_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
+						LOGERR("Error making %s directory: %s\n", install_path.c_str(), strerror(errno));
+						goto exit;
+					}
+					if (chown(install_path.c_str(), 1000, 1000)) {
+						LOGERR("chown %s error: %s\n", install_path.c_str(), strerror(errno));
+						goto exit;
+					}
+					if (setfilecon(install_path.c_str(), (char *)context.c_str()) < 0) {
+						LOGERR("setfilecon %s error: %s\n", install_path.c_str(), strerror(errno));
+						goto exit;
+					}
+				}
+				install_path += "/me.twrp.twrpapp-1";
+				if (mkdir(install_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
+					LOGERR("Error making %s directory: %s\n", install_path.c_str(), strerror(errno));
+					goto exit;
+				}
+				if (chown(install_path.c_str(), 1000, 1000)) {
+					LOGERR("chown %s error: %s\n", install_path.c_str(), strerror(errno));
+					goto exit;
+				}
+				if (setfilecon(install_path.c_str(), (char *)context.c_str()) < 0) {
+					LOGERR("setfilecon %s error: %s\n", install_path.c_str(), strerror(errno));
+					goto exit;
+				}
+				install_path += "/base.apk";
+				if (TWFunc::copy_file("/system/bin/me.twrp.twrpapp.apk", install_path, 0644)) {
+					LOGERR("Error copying apk file\n");
+					goto exit;
+				}
+				if (chown(install_path.c_str(), 1000, 1000)) {
+					LOGERR("chown %s error: %s\n", install_path.c_str(), strerror(errno));
+					goto exit;
+				}
+				if (setfilecon(install_path.c_str(), (char *)context.c_str()) < 0) {
+					LOGERR("setfilecon %s error: %s\n", install_path.c_str(), strerror(errno));
+					goto exit;
+				}
+				sync();
+				sync();
+			}
+		} else {
+			if (PartitionManager.Mount_By_Path(PartitionManager.Get_Android_Root_Path(), true)) {
+				string base_path = PartitionManager.Get_Android_Root_Path();
+				if (TWFunc::Path_Exists(PartitionManager.Get_Android_Root_Path() + "/system"))
+					base_path += "/system"; // For devices with system as a root file system (e.g. Pixel)
+				string install_path = base_path + "/priv-app";
+				string context = "u:object_r:system_file:s0";
+				if (!TWFunc::Path_Exists(install_path))
+					install_path = base_path + "/app";
+				if (TWFunc::Path_Exists(install_path)) {
+					install_path += "/twrpapp";
+					LOGINFO("Installing app to '%s'\n", install_path.c_str());
+					if (mkdir(install_path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0) {
+						if (setfilecon(install_path.c_str(), (char *)context.c_str()) < 0) {
+							LOGERR("setfilecon %s error: %s\n", install_path.c_str(), strerror(errno));
+							goto exit;
+						}
+						install_path += "/me.twrp.twrpapp.apk";
+						if (TWFunc::copy_file("/system/bin/me.twrp.twrpapp.apk", install_path, 0644)) {
+							LOGERR("Error copying apk file\n");
+							goto exit;
+						}
+						if (setfilecon(install_path.c_str(), (char *)context.c_str()) < 0) {
+							LOGERR("setfilecon %s error: %s\n", install_path.c_str(), strerror(errno));
+							goto exit;
+						}
+
+						// System apps require their permissions to be pre-set via an XML file in /etc/permissions
+						string permission_path = base_path + "/etc/permissions/privapp-permissions-twrpapp.xml";
+						if (TWFunc::copy_file("/system/bin/privapp-permissions-twrpapp.xml", permission_path, 0644)) {
+							LOGERR("Error copying permission file\n");
+							goto exit;
+						}
+						if (chown(permission_path.c_str(), 1000, 1000)) {
+							LOGERR("chown %s error: %s\n", permission_path.c_str(), strerror(errno));
+							goto exit;
+						}
+						if (setfilecon(permission_path.c_str(), (char *)context.c_str()) < 0) {
+							LOGERR("setfilecon %s error: %s\n", permission_path.c_str(), strerror(errno));
+							goto exit;
+						}
+
+						sync();
+						sync();
+						PartitionManager.UnMount_By_Path(PartitionManager.Get_Android_Root_Path(), true);
+					} else {
+						LOGERR("Error making app directory '%s': %s\n", strerror(errno));
+					}
+				}
+			}
+		}
+	} else
+		simulate_progress_bar();
+exit:
+	TWFunc::checkforapp();
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::uninstalltwrpsystemapp(std::string arg __unused)
+{
+	int op_status = 1;
+	operation_start("Uninstall TWRP System App");
+	if (!simulate)
+	{
+		int Mount_System_RO = DataManager::GetIntValue("tw_mount_system_ro");
+		TWPartition* Part = PartitionManager.Find_Partition_By_Path(PartitionManager.Get_Android_Root_Path());
+		if (!Part) {
+			LOGERR("Unabled to find system partition.\n");
+			goto exit;
+		}
+		if (!Part->UnMount(true)) {
+			goto exit;
+		}
+		if (Mount_System_RO > 0) {
+			DataManager::SetValue("tw_mount_system_ro", 0);
+			Part->Change_Mount_Read_Only(false);
+		}
+		if (Part->Mount(true)) {
+			string base_path = PartitionManager.Get_Android_Root_Path();
+			if (TWFunc::Path_Exists(PartitionManager.Get_Android_Root_Path() + "/system"))
+				base_path += "/system"; // For devices with system as a root file system (e.g. Pixel)
+			string uninstall_path = base_path + "/priv-app";
+			if (!TWFunc::Path_Exists(uninstall_path))
+				uninstall_path = base_path + "/app";
+			uninstall_path += "/twrpapp";
+			if (TWFunc::Path_Exists(uninstall_path)) {
+				LOGINFO("Uninstalling TWRP App from '%s'\n", uninstall_path.c_str());
+				if (TWFunc::removeDir(uninstall_path, false) == 0) {
+					sync();
+					op_status = 0;
+					DataManager::SetValue("tw_app_installed_in_system", 0);
+					DataManager::SetValue("tw_app_install_status", 0);
+				} else {
+					LOGERR("Unable to remove TWRP app from system.\n");
+				}
+			} else {
+				LOGINFO("didn't find TWRP app in '%s'\n", uninstall_path.c_str());
+			}
+		}
+		Part->UnMount(true);
+		if (Mount_System_RO > 0) {
+			DataManager::SetValue("tw_mount_system_ro", Mount_System_RO);
+			Part->Change_Mount_Read_Only(true);
+		}
+	} else
+		simulate_progress_bar();
+exit:
+	TWFunc::checkforapp();
+	operation_end(0);
+	return 0;
+}
+
+int GUIAction::repackimage(std::string arg __unused)
+{
+	int op_status = 1;
+	twrpRepacker repacker;
+
+	operation_start("Repack Image");
+	if (!simulate)
+	{
+		std::string path = DataManager::GetStrValue("tw_filename");
+		Repack_Options_struct Repack_Options;
+		Repack_Options.Disable_Verity = false;
+		Repack_Options.Disable_Force_Encrypt = false;
+		Repack_Options.Backup_First = DataManager::GetIntValue("tw_repack_backup_first") != 0;
+		if (DataManager::GetIntValue("tw_repack_kernel") == 1)
+			Repack_Options.Type = REPLACE_KERNEL;
+		else
+			Repack_Options.Type = REPLACE_RAMDISK;
+		if (!repacker.Repack_Image_And_Flash(path, Repack_Options))
+			goto exit;
+	} else
+		simulate_progress_bar();
+	op_status = 0;
+exit:
+	operation_end(op_status);
+	return 0;
+}
+
+int GUIAction::reflashtwrp(std::string arg __unused)
+{
+	int op_status = 1;
+	twrpRepacker repacker;
+
+	operation_start("Repack Image");
+	if (!simulate)
+	{
+		if (!repacker.Flash_Current_Twrp())
+		goto exit;
+	} else
+		simulate_progress_bar();
+	op_status = 0;
+exit:
+	operation_end(op_status);
+	return 0;
+}
+int GUIAction::fixabrecoverybootloop(std::string arg __unused)
+{
+	int op_status = 1;
+	twrpRepacker repacker;
+
+	operation_start("Repack Image");
+	if (!simulate)
+	{
+		if (!TWFunc::Path_Exists("/system/bin/magiskboot")) {
+			LOGERR("Image repacking tool not present in this TWRP build!");
+			goto exit;
+		}
+		DataManager::SetProgress(0);
+		TWPartition* part = PartitionManager.Find_Partition_By_Path("/boot");
+		if (part)
+			gui_msg(Msg("unpacking_image=Unpacking {1}...")(part->Display_Name));
+		else {
+			gui_msg(Msg(msg::kError, "unable_to_locate=Unable to locate {1}.")("/boot"));
+			goto exit;
+		}
+		if (!repacker.Backup_Image_For_Repack(part, REPACK_ORIG_DIR, DataManager::GetIntValue("tw_repack_backup_first") != 0, gui_lookup("repack", "Repack")))
+			goto exit;
+		DataManager::SetProgress(.25);
+		gui_msg("fixing_recovery_loop_patch=Patching kernel...");
+		std::string command = "cd " REPACK_ORIG_DIR " && /system/bin/magiskboot hexpatch kernel 77616E745F696E697472616D667300 736B69705F696E697472616D667300";
+		if (TWFunc::Exec_Cmd(command) != 0) {
+			gui_msg(Msg(msg::kError, "fix_recovery_loop_patch_error=Error patching kernel."));
+			goto exit;
+		}
+		std::string header_path = REPACK_ORIG_DIR;
+		header_path += "header";
+		if (TWFunc::Path_Exists(header_path)) {
+			command = "cd " REPACK_ORIG_DIR " && sed -i \"s|$(grep '^cmdline=' header | cut -d= -f2-)|$(grep '^cmdline=' header | cut -d= -f2- | sed -e 's/skip_override//' -e 's/  */ /g' -e 's/[ \t]*$//')|\" header";
+			if (TWFunc::Exec_Cmd(command) != 0) {
+				gui_msg(Msg(msg::kError, "fix_recovery_loop_patch_error=Error patching kernel."));
+				goto exit;
+			}
+		}
+		DataManager::SetProgress(.5);
+		gui_msg(Msg("repacking_image=Repacking {1}...")(part->Display_Name));
+		command = "cd " REPACK_ORIG_DIR " && /system/bin/magiskboot repack " REPACK_ORIG_DIR "boot.img";
+		if (TWFunc::Exec_Cmd(command) != 0) {
+			gui_msg(Msg(msg::kError, "repack_error=Error repacking image."));
+			goto exit;
+		}
+		DataManager::SetProgress(.75);
+		std::string path = REPACK_ORIG_DIR;
+		std::string file = "new-boot.img";
+		DataManager::SetValue("tw_flash_partition", "/boot;");
+		if (!PartitionManager.Flash_Image(path, file)) {
+			LOGINFO("Error flashing new image\n");
+			goto exit;
+		}
+		DataManager::SetProgress(1);
+		TWFunc::removeDir(REPACK_ORIG_DIR, false);
+	} else
+		simulate_progress_bar();
+	op_status = 0;
+exit:
+	operation_end(op_status);
+	return 0;
+}
+
+
+int GUIAction::enableadb(std::string arg __unused) {
+	android::base::SetProperty("sys.usb.config", "none");
+	android::base::SetProperty("sys.usb.config", "adb");
+	return 0;
+}
+
+int GUIAction::enablefastboot(std::string arg __unused) {
+	android::base::SetProperty("sys.usb.config", "none");
+	android::base::SetProperty("sys.usb.config", "fastboot");
+	return 0;
+}
+
+int GUIAction::changeterminal(std::string arg) {
+	bool res = true;
+	std::string resp, cmd = "cd " + arg;
+	DataManager::GetValue("tw_terminal_location", resp);
+	if (arg.empty() && !resp.empty()) {
+		cmd = "cd /";
+		for (uint8_t iter = 0; iter < cmd.size(); iter++)
+			term->NotifyCharInput(cmd.at(iter));
+		term->NotifyCharInput(13);
+		DataManager::SetValue("tw_terminal_location", "");
+		return 0;
+	}
+	if (term != NULL && !arg.empty()) {
+		DataManager::SetValue("tw_terminal_location", arg);
+		if (term->status()) {
+			for (uint8_t iter = 0; iter < cmd.size(); iter++)
+				term->NotifyCharInput(cmd.at(iter));
+			term->NotifyCharInput(13);
+		}
+		else if (chdir(arg.c_str()) != 0) {
+			LOGINFO("Unable to change dir to %s\n", arg.c_str());
+			res = false;
+		}
+	}
+	else {
+		res = false;
+		LOGINFO("Unable to switch to Terminal\n");
+	}
+	if (res)
+		gui_changePage("terminalcommand");
+	return 0;
+}
+
+int GUIAction::unmapsuperdevices(std::string arg __unused) {
+	int op_status = 1;
+
+	operation_start("Remove Super Devices");
+	if (simulate) {
+		simulate_progress_bar();
+	} else {
+		if (PartitionManager.Unmap_Super_Devices()) {
+			op_status = 0;
+		}
+	}
+
+	operation_end(op_status);
+	return 0;
+}
+
+#ifndef TW_EXCLUDE_NANO
+int GUIAction::editfile(std::string arg) {
+	if (term != NULL) {
+		for (uint8_t iter = 0; iter < arg.size(); iter++)
+			term->NotifyCharInput(arg.at(iter));
+		term->NotifyCharInput(13);
+	}
+	else
+		LOGINFO("Unable to switch to Terminal\n");
+	return 0;
+}
+#endif
+
+int GUIAction::applycustomtwrpfolder(string arg __unused)
+{
+	operation_start("ChangingTWRPFolder");
+	string storageFolder = DataManager::GetSettingsStoragePath();
+	string newFolder = storageFolder + '/' + arg;
+	string newBackupFolder = newFolder + "/BACKUPS/" + DataManager::GetStrValue("device_id");
+	string prevFolder = storageFolder + DataManager::GetStrValue(TW_RECOVERY_FOLDER_VAR);
+	bool ret = false;
+
+	if (TWFunc::Path_Exists(newFolder)) {
+		gui_msg(Msg(msg::kError, "tw_folder_exists=A folder with that name already exists!"));
+	} else {
+		ret = true;
+	}
+
+	if (newFolder != prevFolder && ret) {
+		ret = TWFunc::Exec_Cmd("mv -f \"" + prevFolder + "\" \"" + newFolder + '\"') != 0 ? false : true;
+	} else {
+		gui_msg(Msg(msg::kError, "tw_folder_exists=A folder with that name already exists!"));
+	}
+
+	if (ret) ret = TWFunc::Recursive_Mkdir(newBackupFolder) ? true : false;
+
+
+	if (ret) {
+		DataManager::SetValue(TW_RECOVERY_FOLDER_VAR, '/' + arg);
+		DataManager::SetValue(TW_BACKUPS_FOLDER_VAR, newBackupFolder);
+		DataManager::mBackingFile = newFolder + '/' + TW_SETTINGS_FILE;
+	}
+	operation_end((int)!ret);
+	return 0;
+}
+
+int GUIAction::mergesnapshots(string arg __unused) {
+	int op_status = 1;
+	if (PartitionManager.Check_Pending_Merges()) {
+		op_status = 0;
+	}
+	operation_end(op_status);
+	return 0;
+}
diff --git a/gui/animation.cpp b/gui/animation.cpp
new file mode 100644
index 0000000..ed32da4
--- /dev/null
+++ b/gui/animation.cpp
@@ -0,0 +1,147 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// animation.cpp - GUIAnimation object
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+
+GUIAnimation::GUIAnimation(xml_node<>* node) : GUIObject(node)
+{
+	xml_node<>* child;
+
+	mAnimation = NULL;
+	mFrame = 1;
+	mFPS = 1;
+	mLoop = -1;
+	mRender = 1;
+	mUpdateCount = 0;
+
+	if (!node)  return;
+
+	mAnimation = LoadAttrAnimation(FindNode(node, "resource"), "name");
+
+	// Load the placement
+	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, NULL, NULL, &mPlacement);
+
+	child = FindNode(node, "speed");
+	if (child)
+	{
+		mFPS = LoadAttrInt(child, "fps", mFPS);
+		mRender = LoadAttrInt(child, "render", mRender);
+	}
+	if (mFPS > 30)  mFPS = 30;
+
+	child = FindNode(node, "loop");
+	if (child)
+	{
+		xml_attribute<>* attr = child->first_attribute("frame");
+		if (attr)
+			mLoop = atoi(attr->value()) - 1;
+		mFrame = LoadAttrInt(child, "start", mFrame);
+	}
+
+	// Fetch the render sizes
+	if (mAnimation && mAnimation->GetResource())
+	{
+		mRenderW = mAnimation->GetWidth();
+		mRenderH = mAnimation->GetHeight();
+
+		// Adjust for placement
+		if (mPlacement != TOP_LEFT && mPlacement != BOTTOM_LEFT)
+		{
+			if (mPlacement == CENTER)
+				mRenderX -= (mRenderW / 2);
+			else
+				mRenderX -= mRenderW;
+		}
+		if (mPlacement != TOP_LEFT && mPlacement != TOP_RIGHT)
+		{
+			if (mPlacement == CENTER)
+				mRenderY -= (mRenderH / 2);
+			else
+				mRenderY -= mRenderH;
+		}
+		SetPlacement(TOP_LEFT);
+	}
+}
+
+int GUIAnimation::Render(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	if (!mAnimation || !mAnimation->GetResource(mFrame))	return -1;
+
+	gr_blit(mAnimation->GetResource(mFrame), 0, 0, mRenderW, mRenderH, mRenderX, mRenderY);
+	return 0;
+}
+
+int GUIAnimation::Update(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	if (!mAnimation)		return -1;
+
+	// Handle the "end-of-animation" state
+	if (mLoop == -2)		return 0;
+
+	// Determine if we need the next frame yet...
+	if (++mUpdateCount > 30 / mFPS)
+	{
+		mUpdateCount = 0;
+		if (++mFrame >= mAnimation->GetResourceCount())
+		{
+			if (mLoop < 0)
+			{
+				mFrame = mAnimation->GetResourceCount() - 1;
+				mLoop = -2;
+			}
+			else
+				mFrame = mLoop;
+		}
+		if (mRender == 2)	return 2;
+		return (Render() == 0 ? 1 : -1);
+	}
+	return 0;
+}
+
diff --git a/gui/blanktimer.cpp b/gui/blanktimer.cpp
new file mode 100755
index 0000000..61356d3
--- /dev/null
+++ b/gui/blanktimer.cpp
@@ -0,0 +1,151 @@
+/*
+        Copyright 2012 bigbiff/Dees_Troy TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string>
+#include <pthread.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+#include "pages.hpp"
+#include "blanktimer.hpp"
+#include "../data.hpp"
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+#include "../twrp-functions.hpp"
+#include "../variables.h"
+
+blanktimer::blanktimer(void) {
+	pthread_mutex_init(&mutex, NULL);
+	setTime(0); // no timeout
+	state = kOn;
+	orig_brightness = getBrightness();
+}
+
+bool blanktimer::isScreenOff() {
+	return state >= kOff;
+}
+
+void blanktimer::setTime(int newtime) {
+	pthread_mutex_lock(&mutex);
+	sleepTimer = newtime;
+	pthread_mutex_unlock(&mutex);
+}
+
+void blanktimer::setTimer(void) {
+	clock_gettime(CLOCK_MONOTONIC, &btimer);
+}
+
+void blanktimer::checkForTimeout() {
+#ifndef TW_NO_SCREEN_TIMEOUT
+	pthread_mutex_lock(&mutex);
+	timespec curTime, diff;
+	clock_gettime(CLOCK_MONOTONIC, &curTime);
+	diff = TWFunc::timespec_diff(btimer, curTime);
+	if (sleepTimer > 2 && diff.tv_sec > (sleepTimer - 2) && state == kOn) {
+		orig_brightness = getBrightness();
+		state = kDim;
+		TWFunc::Set_Brightness("5");
+	}
+	if (sleepTimer && diff.tv_sec > sleepTimer && state < kOff) {
+		state = kOff;
+		TWFunc::Set_Brightness("0");
+		TWFunc::check_and_run_script("/system/bin/postscreenblank.sh", "blank");
+		PageManager::ChangeOverlay("lock");
+	}
+#ifndef TW_NO_SCREEN_BLANK
+	if (state == kOff) {
+		gr_fb_blank(true);
+		state = kBlanked;
+	}
+#endif
+	pthread_mutex_unlock(&mutex);
+#endif
+}
+
+string blanktimer::getBrightness(void) {
+	string result;
+
+	if (DataManager::GetIntValue("tw_has_brightnesss_file")) {
+		DataManager::GetValue("tw_brightness", result);
+		if (result.empty())
+			result = "255";
+	}
+	return result;
+}
+
+void blanktimer::resetTimerAndUnblank(void) {
+#ifndef TW_NO_SCREEN_TIMEOUT
+	pthread_mutex_lock(&mutex);
+	setTimer();
+	switch (state) {
+		case kBlanked:
+#ifndef TW_NO_SCREEN_BLANK
+			gr_fb_blank(false);
+#endif
+			// TODO: this is asymmetric with postscreenblank.sh - shouldn't it be under the next case label?
+			TWFunc::check_and_run_script("/system/bin/postscreenunblank.sh", "unblank");
+			// No break here, we want to keep going
+		case kOff:
+			gui_forceRender();
+			// No break here, we want to keep going
+		case kDim:
+			if (!orig_brightness.empty())
+				TWFunc::Set_Brightness(orig_brightness);
+			state = kOn;
+		case kOn:
+			break;
+	}
+	pthread_mutex_unlock(&mutex);
+#endif
+}
+
+void blanktimer::blank(void) {
+/*  1) No need for timer handling since checkForTimeout() verifies
+ *     state of screen before performing screen-off
+ *  2) Assume screen-off causes issues for devices that set
+ *     TW_NO_SCREEN_TIMEOUT and do not blank screen here either
+ */
+
+#ifndef TW_NO_SCREEN_TIMEOUT
+	pthread_mutex_lock(&mutex);
+	if (state == kOn) {
+		orig_brightness = getBrightness();
+		state = kOff;
+		TWFunc::Set_Brightness("0");
+		TWFunc::check_and_run_script("/system/bin/postscreenblank.sh", "blank");
+	}
+#ifndef TW_NO_SCREEN_BLANK
+	if (state == kOff) {
+		gr_fb_blank(true);
+		state = kBlanked;
+	}
+#endif
+	pthread_mutex_unlock(&mutex);
+#endif
+}
+
+void blanktimer::toggleBlank(void) {
+	if (state == kOn) {
+		blank();
+		PageManager::ChangeOverlay("lock");
+	} else {
+		resetTimerAndUnblank();
+	}
+}
diff --git a/gui/blanktimer.hpp b/gui/blanktimer.hpp
new file mode 100644
index 0000000..6aca270
--- /dev/null
+++ b/gui/blanktimer.hpp
@@ -0,0 +1,61 @@
+/*
+        Copyright 2012 bigbiff/Dees_Troy TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __BLANKTIMER_HEADER_HPP
+#define __BLANKTIMER_HEADER_HPP
+
+#include <sys/time.h>
+
+using namespace std;
+
+class blanktimer
+{
+public:
+	blanktimer();
+
+	// set timeout in seconds
+	void setTime(int newtime);
+
+	// call this in regular intervals
+	void checkForTimeout();
+
+	// call this when an input event is received or when an operation is finished
+	void resetTimerAndUnblank();
+
+	// call this when power button is pressed
+	void toggleBlank(void);
+
+	bool isScreenOff();
+
+	void blank(void);
+
+private:
+	void setTimer(void);
+	string getBrightness(void);
+
+	pthread_mutex_t mutex;
+	enum State { kOn = 0, kDim = 1, kOff = 2, kBlanked = 3 };
+	State state;
+	timespec btimer;
+	long sleepTimer;
+	string orig_brightness;
+};
+
+extern blanktimer blankTimer;
+
+#endif // __BLANKTIMER_HEADER_HPP
diff --git a/gui/button.cpp b/gui/button.cpp
new file mode 100755
index 0000000..4ce595b
--- /dev/null
+++ b/gui/button.cpp
@@ -0,0 +1,266 @@
+/*
+	Copyright 2012 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "../data.hpp"
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUIButton::GUIButton(xml_node<>* node)
+	: GUIObject(node)
+{
+	mButtonImg = NULL;
+	mButtonIcon = NULL;
+	mButtonLabel = NULL;
+	mAction = NULL;
+	mRendered = false;
+	hasHighlightColor = false;
+	renderHighlight = false;
+	hasFill = false;
+
+	if (!node)  return;
+
+	// These can be loaded directly from the node
+	mButtonLabel = new GUIText(node);
+	mAction = new GUIAction(node);
+
+	mButtonImg = new GUIImage(node);
+	if (mButtonImg->Render() < 0)
+	{
+		delete mButtonImg;
+		mButtonImg = NULL;
+	}
+	if (mButtonLabel->Render() < 0)
+	{
+		delete mButtonLabel;
+		mButtonLabel = NULL;
+	}
+	// Load fill if it exists
+	mFillColor = LoadAttrColor(FindNode(node, "fill"), "color", &hasFill);
+	if (!hasFill && mButtonImg == NULL) {
+		LOGERR("No image resource or fill specified for button.\n");
+	}
+
+	// The icon is a special case
+	mButtonIcon = LoadAttrImage(FindNode(node, "icon"), "resource");
+
+	mHighlightColor = LoadAttrColor(FindNode(node, "highlight"), "color", &hasHighlightColor);
+
+	int x = 0, y = 0, w = 0, h = 0;
+	TextPlacement = TOP_LEFT;
+	if (mButtonImg) {
+		mButtonImg->GetRenderPos(x, y, w, h);
+	} else if (hasFill) {
+		LoadPlacement(FindNode(node, "placement"), &x, &y, &w, &h, &TextPlacement);
+	}
+	SetRenderPos(x, y, w, h);
+	if (mButtonLabel) {
+		TextPlacement = (Placement)LoadAttrInt(FindNode(node, "placement"), "textplacement", TOP_LEFT);
+		if (TextPlacement != TEXT_ONLY_RIGHT) {
+			mButtonLabel->scaleWidth = 1;
+			mButtonLabel->SetMaxWidth(w);
+			mButtonLabel->SetPlacement(CENTER);
+			mTextX = ((mRenderW / 2) + mRenderX);
+			mTextY = mRenderY + (mRenderH / 2);
+			mButtonLabel->SetRenderPos(mTextX, mTextY);
+		} else {
+			mTextX = mRenderW + mRenderX + 5;
+			mButtonLabel->GetCurrentBounds(mTextW, mTextH);
+			mRenderW += mTextW + 5;
+			mTextY = mRenderY + (mRenderH / 2) - (mTextH / 2);
+			mButtonLabel->SetRenderPos(mTextX, mTextY);
+			if (mAction)
+				mAction->SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+			SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+		}
+	}
+}
+
+GUIButton::~GUIButton()
+{
+	delete mButtonImg;
+	delete mButtonLabel;
+	delete mAction;
+}
+
+int GUIButton::Render(void)
+{
+	if (!isConditionTrue())
+	{
+		mRendered = false;
+		return 0;
+	}
+
+	int ret = 0;
+
+	if (mButtonImg)	 ret = mButtonImg->Render();
+	if (ret < 0)		return ret;
+	if (hasFill) {
+		gr_color(mFillColor.red, mFillColor.green, mFillColor.blue, mFillColor.alpha);
+		gr_fill(mRenderX, mRenderY, mRenderW, mRenderH);
+	}
+	if (mButtonIcon && mButtonIcon->GetResource())
+		gr_blit(mButtonIcon->GetResource(), 0, 0, mIconW, mIconH, mIconX, mIconY);
+	if (mButtonLabel) {
+		int w, h;
+		mButtonLabel->GetCurrentBounds(w, h);
+		if (w != mTextW) {
+			mTextW = w;
+		}
+		ret = mButtonLabel->Render();
+		if (ret < 0)		return ret;
+	}
+	if (renderHighlight && hasHighlightColor) {
+		gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha);
+		gr_fill(mRenderX, mRenderY, mRenderW, mRenderH);
+	}
+	mRendered = true;
+	return ret;
+}
+
+int GUIButton::Update(void)
+{
+	if (!isConditionTrue())	return (mRendered ? 2 : 0);
+	if (!mRendered)			return 2;
+
+	int ret = 0, ret2 = 0;
+
+	if (mButtonImg)			ret = mButtonImg->Update();
+	if (ret < 0)			return ret;
+
+	if (ret == 0)
+	{
+		if (mButtonLabel) {
+			ret2 = mButtonLabel->Update();
+			if (ret2 < 0)	return ret2;
+			if (ret2 > ret)	ret = ret2;
+		}
+	}
+	else if (ret == 1)
+	{
+		// The button re-rendered, so everyone else is a render
+		if (mButtonIcon && mButtonIcon->GetResource())
+			gr_blit(mButtonIcon->GetResource(), 0, 0, mIconW, mIconH, mIconX, mIconY);
+		if (mButtonLabel)   ret = mButtonLabel->Render();
+		if (ret < 0)		return ret;
+		ret = 1;
+	}
+	else
+	{
+		// Aparently, the button needs a background update
+		ret = 2;
+	}
+	return ret;
+}
+
+int GUIButton::SetRenderPos(int x, int y, int w, int h)
+{
+	mRenderX = x;
+	mRenderY = y;
+	if (w || h)
+	{
+		mRenderW = w;
+		mRenderH = h;
+	}
+	mIconW = mIconH = 0;
+
+	if (mButtonIcon && mButtonIcon->GetResource()) {
+		mIconW = mButtonIcon->GetWidth();
+		mIconH = mButtonIcon->GetHeight();
+	}
+
+	mTextH = 0;
+	mTextW = 0;
+	mIconX = mRenderX + ((mRenderW - mIconW) / 2);
+	if (mButtonLabel)   mButtonLabel->GetCurrentBounds(mTextW, mTextH);
+	if (mTextW && TextPlacement == TEXT_ONLY_RIGHT)
+	{
+		mRenderW += mTextW + 5;
+	}
+
+	if (mIconH == 0 || mTextH == 0 || mIconH + mTextH > mRenderH)
+	{
+		mIconY = mRenderY + (mRenderH / 2) - (mIconH / 2);
+	}
+	else
+	{
+		int divisor = mRenderH - (mIconH + mTextH);
+		mIconY = mRenderY + (divisor / 3);
+	}
+
+	if (mButtonLabel)   mButtonLabel->SetRenderPos(mTextX, mTextY);
+	if (mAction)		mAction->SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+	SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+	return 0;
+}
+
+int GUIButton::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	static int last_state = 0;
+
+	if (!isConditionTrue())	 return -1;
+	if (x < mRenderX || x - mRenderX > mRenderW || y < mRenderY || y - mRenderY > mRenderH || state == TOUCH_RELEASE) {
+		if (last_state == 1) {
+			last_state = 0;
+			if (mButtonLabel != NULL)
+				mButtonLabel->isHighlighted = false;
+			if (mButtonImg != NULL)
+				mButtonImg->isHighlighted = false;
+			renderHighlight = false;
+			mRendered = false;
+		}
+	} else {
+		if (last_state == 0) {
+			last_state = 1;
+
+#ifndef TW_NO_HAPTICS
+			DataManager::Vibrate("tw_button_vibrate");
+#endif
+			if (mButtonLabel != NULL)
+				mButtonLabel->isHighlighted = true;
+			if (mButtonImg != NULL)
+				mButtonImg->isHighlighted = true;
+			renderHighlight = true;
+			mRendered = false;
+		}
+	}
+	if (x < mRenderX || x - mRenderX > mRenderW || y < mRenderY || y - mRenderY > mRenderH)
+		return 0;
+	return (mAction ? mAction->NotifyTouch(state, x, y) : 1);
+}
diff --git a/gui/checkbox.cpp b/gui/checkbox.cpp
new file mode 100755
index 0000000..8bacd8f
--- /dev/null
+++ b/gui/checkbox.cpp
@@ -0,0 +1,194 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// checkbox.cpp - GUICheckbox object
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUICheckbox::GUICheckbox(xml_node<>* node)
+	: GUIObject(node)
+{
+	xml_attribute<>* attr;
+	xml_node<>* child;
+
+	mChecked = NULL;
+	mUnchecked = NULL;
+	mLabel = NULL;
+	mRendered = false;
+
+	mLastState = 0;
+
+	if (!node)
+		return;
+
+	// The label can be loaded directly
+	mLabel = new GUIText(node);
+
+	// Read the check states
+	child = FindNode(node, "image");
+	if (child)
+	{
+		mChecked = LoadAttrImage(child, "checked");
+		mUnchecked = LoadAttrImage(child, "unchecked");
+	}
+
+	// Get the variable data
+	child = FindNode(node, "data");
+	if (child)
+	{
+		attr = child->first_attribute("variable");
+		if (attr)
+			mVarName = attr->value();
+		attr = child->first_attribute("default");
+		if (attr) {
+			DataManager::SetValue(mVarName, attr->value());
+		} else {
+			int val;
+			if (DataManager::GetValue(mVarName, val) != 0)
+				DataManager::SetValue(mVarName, 0); // Prevents check boxes from having to be tapped twice the first time
+		}
+	}
+
+	mCheckW = mCheckH = 0;
+	if (mChecked && mChecked->GetResource()) {
+		mCheckW = mChecked->GetWidth();
+		mCheckH = mChecked->GetHeight();
+	} else if (mUnchecked && mUnchecked->GetResource()) {
+		mCheckW = mUnchecked->GetWidth();
+		mCheckH = mUnchecked->GetHeight();
+	}
+
+	int x, y, w, h;
+	mLabel->GetRenderPos(x, y, w, h);
+	SetRenderPos(x, y, 0, 0);
+}
+
+GUICheckbox::~GUICheckbox()
+{
+}
+
+int GUICheckbox::Render(void)
+{
+	if (!isConditionTrue())
+	{
+		mRendered = false;
+		return 0;
+	}
+
+	int ret = 0;
+	int lastState = 0;
+	DataManager::GetValue(mVarName, lastState);
+
+	if (lastState)
+	{
+		if (mChecked && mChecked->GetResource())
+			gr_blit(mChecked->GetResource(), 0, 0, mCheckW, mCheckH, mRenderX, mRenderY);
+	}
+	else
+	{
+		if (mUnchecked && mUnchecked->GetResource())
+			gr_blit(mUnchecked->GetResource(), 0, 0, mCheckW, mCheckH, mRenderX, mRenderY);
+	}
+	if (mLabel)
+		ret = mLabel->Render();
+	mLastState = lastState;
+	mRendered = true;
+	return ret;
+}
+
+int GUICheckbox::Update(void)
+{
+	if (!isConditionTrue())	return (mRendered ? 2 : 0);
+	if (!mRendered)			return 2;
+
+	int lastState = 0;
+	DataManager::GetValue(mVarName, lastState);
+
+	if (lastState != mLastState)
+		return 2;
+	return 0;
+}
+
+int GUICheckbox::SetRenderPos(int x, int y, int w, int h)
+{
+	mRenderX = x;
+	mRenderY = y;
+
+	if (w || h)
+		return -1;
+
+	int textW, textH;
+	mLabel->GetCurrentBounds(textW, textH);
+
+	w = textW + mCheckW + 5;
+	mRenderW = w;
+	mRenderH = mCheckH;
+
+	mTextX = mRenderX + mCheckW + 5;
+	mTextY = mRenderY + (mCheckH / 2);
+
+	mLabel->SetRenderPos(mTextX, mTextY, 0, 0);
+	mLabel->SetPlacement(TEXT_ONLY_RIGHT);
+	mLabel->SetMaxWidth(gr_fb_width() - mTextX);
+	SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+	return 0;
+}
+
+int GUICheckbox::NotifyTouch(TOUCH_STATE state, int x __unused, int y __unused)
+{
+	if (!isConditionTrue())
+		return -1;
+
+	if (state == TOUCH_RELEASE)
+	{
+		int lastState;
+		DataManager::GetValue(mVarName, lastState);
+		lastState = (lastState == 0) ? 1 : 0;
+		DataManager::SetValue(mVarName, lastState);
+
+#ifndef TW_NO_HAPTICS
+		DataManager::Vibrate("tw_button_vibrate");
+#endif
+
+	}
+	return 0;
+}
+
diff --git a/gui/console.cpp b/gui/console.cpp
new file mode 100755
index 0000000..7872344
--- /dev/null
+++ b/gui/console.cpp
@@ -0,0 +1,413 @@
+/*
+	Copyright 2012 - 2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// console.cpp - GUIConsole object
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+#include "gui.hpp"
+#include "twmsg.h"
+
+#define GUI_CONSOLE_BUFFER_SIZE 512
+
+static pthread_mutex_t console_lock;
+static size_t last_message_count = 0;
+static std::vector<Message> gMessages;
+
+static std::vector<std::string> gConsole;
+static std::vector<std::string> gConsoleColor;
+static FILE* ors_file = NULL;
+
+struct InitMutex
+{
+	InitMutex() { pthread_mutex_init(&console_lock, NULL); }
+} initMutex;
+
+static void internal_gui_print(const char *color, char *buf)
+{
+	// make sure to flush any outstanding messages first to preserve order of outputs
+	GUIConsole::Translate_Now();
+
+	fputs(buf, stdout);
+	if (ors_file) {
+		fprintf(ors_file, "%s", buf);
+		fflush(ors_file);
+	}
+
+	char *start, *next;
+
+	if (buf[0] == '\n' && strlen(buf) < 2) {
+		// This prevents the double lines bug seen in the console during zip installs
+		return;
+	}
+
+	pthread_mutex_lock(&console_lock);
+	for (start = next = buf; *next != '\0';)
+	{
+		if (*next == '\n')
+		{
+			*next = '\0';
+			gConsole.push_back(start);
+			gConsoleColor.push_back(color);
+
+			start = ++next;
+		}
+		else
+			++next;
+	}
+
+	// The text after last \n (or whole string if there is no \n)
+	if (*start) {
+		gConsole.push_back(start);
+		gConsoleColor.push_back(color);
+	}
+	pthread_mutex_unlock(&console_lock);
+}
+
+extern "C" void gui_print(const char *fmt, ...)
+{
+	char buf[GUI_CONSOLE_BUFFER_SIZE];		// We're going to limit a single request to 512 bytes
+
+	va_list ap;
+	va_start(ap, fmt);
+	vsnprintf(buf, GUI_CONSOLE_BUFFER_SIZE, fmt, ap);
+	va_end(ap);
+
+	internal_gui_print("normal", buf);
+}
+
+extern "C" void gui_print_color(const char *color, const char *fmt, ...)
+{
+	char buf[GUI_CONSOLE_BUFFER_SIZE];		// We're going to limit a single request to 512 bytes
+
+	va_list ap;
+	va_start(ap, fmt);
+	vsnprintf(buf, GUI_CONSOLE_BUFFER_SIZE, fmt, ap);
+	va_end(ap);
+
+	internal_gui_print(color, buf);
+}
+
+extern "C" void gui_set_FILE(FILE* f)
+{
+	ors_file = f;
+}
+
+void gui_msg(const char* text)
+{
+	if (text) {
+		Message msg = Msg(text);
+		gui_msg(msg);
+	}
+}
+
+void gui_warn(const char* text)
+{
+	if (text) {
+		Message msg = Msg(msg::kWarning, text);
+		gui_msg(msg);
+	}
+}
+
+void gui_err(const char* text)
+{
+	if (text) {
+		Message msg = Msg(msg::kError, text);
+		gui_msg(msg);
+	}
+}
+
+void gui_highlight(const char* text)
+{
+	if (text) {
+		Message msg = Msg(msg::kHighlight, text);
+		gui_msg(msg);
+	}
+}
+
+void gui_msg(Message msg)
+{
+	std::string output = msg;
+	output += "\n";
+	fputs(output.c_str(), stdout);
+	if (ors_file) {
+		fprintf(ors_file, "%s", output.c_str());
+		fflush(ors_file);
+	}
+	pthread_mutex_lock(&console_lock);
+	gMessages.push_back(msg);
+	pthread_mutex_unlock(&console_lock);
+}
+
+void GUIConsole::Translate_Now()
+{
+	pthread_mutex_lock(&console_lock);
+	size_t message_count = gMessages.size();
+	if (message_count <= last_message_count)
+	{
+		pthread_mutex_unlock(&console_lock);
+		return;
+	}
+
+	for (size_t m = last_message_count; m < message_count; m++) {
+		std::string message = gMessages[m];
+		std::string color = "normal";
+		if (gMessages[m].GetKind() == msg::kError)
+			color = "error";
+		else if (gMessages[m].GetKind() == msg::kHighlight)
+			color = "highlight";
+		else if (gMessages[m].GetKind() == msg::kWarning)
+			color = "warning";
+		gConsole.push_back(message);
+		gConsoleColor.push_back(color);
+	}
+	last_message_count = message_count;
+	pthread_mutex_unlock(&console_lock);
+}
+
+void GUIConsole::Clear_For_Retranslation()
+{
+	pthread_mutex_lock(&console_lock);
+	last_message_count = 0;
+	gConsole.clear();
+	gConsoleColor.clear();
+	pthread_mutex_unlock(&console_lock);
+}
+
+GUIConsole::GUIConsole(xml_node<>* node) : GUIScrollList(node)
+{
+	xml_node<>* child;
+
+	mLastCount = 0;
+	scrollToEnd = true;
+	mSlideoutX = mSlideoutY = mSlideoutW = mSlideoutH = 0;
+	mSlideout = 0;
+	mSlideoutState = visible;
+
+	allowSelection = false;	// console doesn't support list item selections
+
+	if (!node)
+	{
+		mRenderX = 0;
+		mRenderY = 0;
+		mRenderW = gr_fb_width();
+		mRenderH = gr_fb_height();
+	}
+	else
+	{
+		child = FindNode(node, "color");
+		if (child)
+		{
+			mFontColor = LoadAttrColor(child, "foreground", mFontColor);
+			mBackgroundColor = LoadAttrColor(child, "background", mBackgroundColor);
+			//mScrollColor = LoadAttrColor(child, "scroll", mScrollColor);
+		}
+
+		child = FindNode(node, "slideout");
+		if (child)
+		{
+			mSlideout = 1;
+			mSlideoutState = hidden;
+			LoadPlacement(child, &mSlideoutX, &mSlideoutY, &mSlideoutW, &mSlideoutH, &mPlacement);
+
+			mSlideoutImage = LoadAttrImage(child, "resource");
+
+			if (mSlideoutImage && mSlideoutImage->GetResource())
+			{
+				mSlideoutW = mSlideoutImage->GetWidth();
+				mSlideoutH = mSlideoutImage->GetHeight();
+				if (mPlacement == CENTER || mPlacement == CENTER_X_ONLY) {
+					mSlideoutX = mSlideoutX - (mSlideoutW / 2);
+					if (mPlacement == CENTER) {
+						mSlideoutY = mSlideoutY - (mSlideoutH / 2);
+					}
+				}
+			}
+		}
+	}
+}
+
+int GUIConsole::RenderSlideout(void)
+{
+	if (!mSlideoutImage || !mSlideoutImage->GetResource())
+		return -1;
+
+	gr_blit(mSlideoutImage->GetResource(), 0, 0, mSlideoutW, mSlideoutH, mSlideoutX, mSlideoutY);
+	return 0;
+}
+
+int GUIConsole::RenderConsole(void)
+{
+	Translate_Now();
+	pthread_mutex_lock(&console_lock);
+	AddLines(&gConsole, &gConsoleColor, &mLastCount, &rConsole, &rConsoleColor);
+	pthread_mutex_unlock(&console_lock);
+	GUIScrollList::Render();
+
+	// if last line is fully visible, keep tracking the last line when new lines are added
+	int bottom_offset = GetDisplayRemainder() - actualItemHeight;
+	bool isAtBottom = firstDisplayedItem == (int)GetItemCount() - GetDisplayItemCount() - (bottom_offset != 0) && y_offset == bottom_offset;
+	if (isAtBottom)
+		scrollToEnd = true;
+#if 0
+	// debug - show if we are tracking the last line
+	if (scrollToEnd) {
+		gr_color(0,255,0,255);
+		gr_fill(mRenderX+mRenderW-5, mRenderY+mRenderH-5, 5, 5);
+	} else {
+		gr_color(255,0,0,255);
+		gr_fill(mRenderX+mRenderW-5, mRenderY+mRenderH-5, 5, 5);
+	}
+#endif
+	return (mSlideout ? RenderSlideout() : 0);
+}
+
+int GUIConsole::Render(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	if (mSlideout && mSlideoutState == hidden)
+		return RenderSlideout();
+
+	return RenderConsole();
+}
+
+int GUIConsole::Update(void)
+{
+	if (mSlideout && mSlideoutState != visible)
+	{
+		if (mSlideoutState == hidden)
+			return 0;
+
+		if (mSlideoutState == request_hide)
+			mSlideoutState = hidden;
+
+		if (mSlideoutState == request_show)
+			mSlideoutState = visible;
+
+		// Any time we activate the console, we reset the position
+		SetVisibleListLocation(rConsole.size() - 1);
+		mUpdate = 1;
+		scrollToEnd = true;
+	}
+
+	pthread_mutex_lock(&console_lock);
+	bool addedNewText = AddLines(&gConsole, &gConsoleColor, &mLastCount, &rConsole, &rConsoleColor);
+	pthread_mutex_unlock(&console_lock);
+	if (addedNewText) {
+		// someone added new text
+		// at least the scrollbar must be updated, even if the new lines are currently not visible
+		mUpdate = 1;
+	}
+
+	if (scrollToEnd) {
+		// keep the last line in view
+		SetVisibleListLocation(rConsole.size() - 1);
+	}
+
+	GUIScrollList::Update();
+
+	if (mUpdate) {
+		mUpdate = 0;
+		if (Render() == 0)
+			return 2;
+	}
+	return 0;
+}
+
+// IsInRegion - Checks if the request is handled by this object
+//  Return 1 if this object handles the request, 0 if not
+int GUIConsole::IsInRegion(int x, int y)
+{
+	if (mSlideout) {
+		// Check if they tapped the slideout button
+		if (x >= mSlideoutX && x < mSlideoutX + mSlideoutW && y >= mSlideoutY && y < mSlideoutY + mSlideoutH)
+			return 1;
+
+		// If we're only rendering the slideout, bail now
+		if (mSlideoutState == hidden)
+			return 0;
+	}
+
+	return GUIScrollList::IsInRegion(x, y);
+}
+
+// NotifyTouch - Notify of a touch event
+//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
+int GUIConsole::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	if (!isConditionTrue())
+		return -1;
+
+	if (mSlideout && x >= mSlideoutX && x < mSlideoutX + mSlideoutW && y >= mSlideoutY && y < mSlideoutY + mSlideoutH) {
+		if (state == TOUCH_START) {
+			if (mSlideoutState == hidden)
+				mSlideoutState = request_show;
+			else if (mSlideoutState == visible)
+				mSlideoutState = request_hide;
+		}
+		return 1;
+	}
+	scrollToEnd = false;
+	return GUIScrollList::NotifyTouch(state, x, y);
+}
+
+size_t GUIConsole::GetItemCount()
+{
+	return rConsole.size();
+}
+
+void GUIConsole::RenderItem(size_t itemindex, int yPos, bool selected __unused)
+{
+	// Set the color for the font
+	if (rConsoleColor[itemindex] == "normal") {
+		gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
+	} else {
+		COLOR FontColor;
+		std::string color = rConsoleColor[itemindex];
+		ConvertStrToColor(color, &FontColor);
+		FontColor.alpha = 255;
+		gr_color(FontColor.red, FontColor.green, FontColor.blue, FontColor.alpha);
+	}
+
+	// render text
+	const char* text = rConsole[itemindex].c_str();
+	gr_textEx_scaleW(mRenderX, yPos, text, mFont->GetResource(), mRenderW, TOP_LEFT, 0);
+}
+
+void GUIConsole::NotifySelect(size_t item_selected __unused)
+{
+	// do nothing - console ignores selections
+}
diff --git a/gui/fileselector.cpp b/gui/fileselector.cpp
new file mode 100644
index 0000000..5835e90
--- /dev/null
+++ b/gui/fileselector.cpp
@@ -0,0 +1,451 @@
+/*
+	Copyright 2012 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <algorithm>
+#ifdef __ANDROID_API_M__
+#include <vector>
+#ifdef __ANDROID_API_N__
+#include <android-base/strings.h>
+#else
+#include <base/strings.h>
+#endif
+#else
+#endif
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+#include "../data.hpp"
+#include "../twrp-functions.hpp"
+#include "../adbbu/libtwadbbu.hpp"
+
+int GUIFileSelector::mSortOrder = 0;
+
+GUIFileSelector::GUIFileSelector(xml_node<>* node) : GUIScrollList(node)
+{
+	xml_attribute<>* attr;
+	xml_node<>* child;
+
+	mFolderIcon = mFileIcon = NULL;
+	mShowFolders = mShowFiles = mShowNavFolders = 1;
+	mUpdate = 0;
+	mPathVar = "cwd";
+	updateFileList = false;
+
+	// Load filter for filtering files (e.g. *.zip for only zips)
+	child = FindNode(node, "filter");
+	if (child) {
+		attr = child->first_attribute("extn");
+		if (attr)
+			mExtn = attr->value();
+		attr = child->first_attribute("folders");
+		if (attr)
+			mShowFolders = atoi(attr->value());
+		attr = child->first_attribute("files");
+		if (attr)
+			mShowFiles = atoi(attr->value());
+		attr = child->first_attribute("nav");
+		if (attr)
+			mShowNavFolders = atoi(attr->value());
+	}
+	child = FindNode(node, "prfxfilter");
+	if (child) {
+		attr = child->first_attribute("prfx");
+		if (attr)
+			mPrfx = attr->value();
+	}
+
+	// Handle the path variable
+	child = FindNode(node, "path");
+	if (child) {
+		attr = child->first_attribute("name");
+		if (attr)
+			mPathVar = attr->value();
+		attr = child->first_attribute("default");
+		if (attr) {
+			mPathDefault = attr->value();
+			DataManager::SetValue(mPathVar, attr->value());
+		}
+	}
+
+	// Handle the result variable
+	child = FindNode(node, "data");
+	if (child) {
+		attr = child->first_attribute("name");
+		if (attr)
+			mVariable = attr->value();
+		attr = child->first_attribute("default");
+		if (attr)
+			DataManager::SetValue(mVariable, attr->value());
+	}
+
+	// Handle the sort variable
+	child = FindNode(node, "sort");
+	if (child) {
+		attr = child->first_attribute("name");
+		if (attr)
+			mSortVariable = attr->value();
+		attr = child->first_attribute("default");
+		if (attr)
+			DataManager::SetValue(mSortVariable, attr->value());
+
+		DataManager::GetValue(mSortVariable, mSortOrder);
+	}
+
+	// Handle the selection variable
+	child = FindNode(node, "selection");
+	if (child && (attr = child->first_attribute("name")))
+		mSelection = attr->value();
+	else
+		mSelection = "0";
+
+	// Get folder and file icons if present
+	child = FindNode(node, "icon");
+	if (child) {
+		mFolderIcon = LoadAttrImage(child, "folder");
+		mFileIcon = LoadAttrImage(child, "file");
+	}
+	int iconWidth = 0, iconHeight = 0;
+	if (mFolderIcon && mFolderIcon->GetResource() && mFileIcon && mFileIcon->GetResource()) {
+		iconWidth = std::max(mFolderIcon->GetWidth(), mFileIcon->GetWidth());
+		iconHeight = std::max(mFolderIcon->GetHeight(), mFileIcon->GetHeight());
+	} else if (mFolderIcon && mFolderIcon->GetResource()) {
+		iconWidth = mFolderIcon->GetWidth();
+		iconHeight = mFolderIcon->GetHeight();
+	} else if (mFileIcon && mFileIcon->GetResource()) {
+		iconWidth = mFileIcon->GetWidth();
+		iconHeight = mFileIcon->GetHeight();
+	}
+	SetMaxIconSize(iconWidth, iconHeight);
+
+	// Fetch the file/folder list
+	std::string value;
+	DataManager::GetValue(mPathVar, value);
+	GetFileList(value);
+}
+
+GUIFileSelector::~GUIFileSelector()
+{
+}
+
+int GUIFileSelector::Update(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	GUIScrollList::Update();
+
+	// Update the file list if needed
+	if (updateFileList) {
+		string value;
+		DataManager::GetValue(mPathVar, value);
+		if (GetFileList(value) == 0) {
+			updateFileList = false;
+			mUpdate = 1;
+		} else
+			return 0;
+	}
+
+	if (mUpdate) {
+		mUpdate = 0;
+		if (Render() == 0)
+			return 2;
+	}
+	return 0;
+}
+
+int GUIFileSelector::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	GUIScrollList::NotifyVarChange(varName, value);
+
+	if (!isConditionTrue())
+		return 0;
+
+	if (varName.empty()) {
+		// Always clear the data variable so we know to use it
+		DataManager::SetValue(mVariable, "");
+	}
+	if (varName == mPathVar || varName == mSortVariable) {
+		if (varName == mSortVariable) {
+			DataManager::GetValue(mSortVariable, mSortOrder);
+		} else {
+			// Reset the list to the top
+			SetVisibleListLocation(0);
+			if (value.empty())
+				DataManager::SetValue(mPathVar, mPathDefault);
+		}
+		updateFileList = true;
+		mUpdate = 1;
+		return 0;
+	}
+	return 0;
+}
+
+bool GUIFileSelector::fileSort(FileData d1, FileData d2)
+{
+	if (d1.fileName == ".")
+		return -1;
+	if (d2.fileName == ".")
+		return 0;
+	if (d1.fileName == "..")
+		return -1;
+	if (d2.fileName == "..")
+		return 0;
+
+	switch (mSortOrder) {
+		case 3: // by size largest first
+			if (d1.fileSize == d2.fileSize || d1.fileType == DT_DIR) // some directories report a different size than others - but this is not the size of the files inside the directory, so we just sort by name on directories
+				return (strcasecmp(d1.fileName.c_str(), d2.fileName.c_str()) < 0);
+			return d1.fileSize < d2.fileSize;
+		case -3: // by size smallest first
+			if (d1.fileSize == d2.fileSize || d1.fileType == DT_DIR) // some directories report a different size than others - but this is not the size of the files inside the directory, so we just sort by name on directories
+				return (strcasecmp(d1.fileName.c_str(), d2.fileName.c_str()) > 0);
+			return d1.fileSize > d2.fileSize;
+		case 2: // by last modified date newest first
+			if (d1.lastModified == d2.lastModified)
+				return (strcasecmp(d1.fileName.c_str(), d2.fileName.c_str()) < 0);
+			return d1.lastModified < d2.lastModified;
+		case -2: // by date oldest first
+			if (d1.lastModified == d2.lastModified)
+				return (strcasecmp(d1.fileName.c_str(), d2.fileName.c_str()) > 0);
+			return d1.lastModified > d2.lastModified;
+		case -1: // by name descending
+			return (strcasecmp(d1.fileName.c_str(), d2.fileName.c_str()) > 0);
+		default: // should be a 1 - sort by name ascending
+			return (strcasecmp(d1.fileName.c_str(), d2.fileName.c_str()) < 0);
+	}
+	return 0;
+}
+
+int GUIFileSelector::GetFileList(const std::string folder)
+{
+	DIR* d;
+	struct dirent* de;
+	struct stat st;
+
+	// Clear all data
+	mFolderList.clear();
+	mFileList.clear();
+
+	d = opendir(folder.c_str());
+	if (d == NULL) {
+		LOGINFO("Unable to open '%s'\n", folder.c_str());
+		if (folder != "/" && (mShowNavFolders != 0 || mShowFiles != 0)) {
+			size_t found;
+			found = folder.find_last_of('/');
+			if (found != string::npos) {
+				string new_folder = folder.substr(0, found);
+
+				if (new_folder.length() < 2)
+					new_folder = "/";
+				DataManager::SetValue(mPathVar, new_folder);
+			}
+		}
+		return -1;
+	}
+
+	while ((de = readdir(d)) != NULL) {
+		FileData data;
+		bool match = false;
+
+		data.fileName = de->d_name;
+		if (data.fileName == ".")
+			continue;
+		if (data.fileName == ".." && folder == "/")
+			continue;
+
+		data.fileType = de->d_type;
+
+		std::string path = folder + "/" + data.fileName;
+		stat(path.c_str(), &st);
+		data.protection = st.st_mode;
+		data.userId = st.st_uid;
+		data.groupId = st.st_gid;
+		data.fileSize = st.st_size;
+		data.lastAccess = st.st_atime;
+		data.lastModified = st.st_mtime;
+		data.lastStatChange = st.st_ctime;
+
+		if (data.fileType == DT_UNKNOWN) {
+			data.fileType = TWFunc::Get_D_Type_From_Stat(path);
+		}
+		if (data.fileType == DT_DIR) {
+			if (mShowNavFolders || (data.fileName != "." && data.fileName != ".."))
+				mFolderList.push_back(data);
+		} else if (data.fileType == DT_REG || data.fileType == DT_LNK || data.fileType == DT_BLK) {
+#ifdef __ANDROID_API_M__
+			std::vector<std::string> mExtnResults = android::base::Split(mExtn, ";");
+			for (const std::string& mExtnElement : mExtnResults)
+			{
+				std::string mExtnName = android::base::Trim(mExtnElement);
+				if (mExtnName.empty() || (data.fileName.length() >= mExtnName.length() && data.fileName.substr(data.fileName.length() - mExtnName.length()) == mExtnName)) {
+					if (mExtnName == ".ab" && twadbbu::Check_ADB_Backup_File(path))
+						mFolderList.push_back(data);
+					else
+						mFileList.push_back(data);
+					match = true;
+					break;
+				}
+			}
+
+			if (!match) {
+				std::vector<std::string> mPrfxResults = android::base::Split(mPrfx, ";");
+				for (const std::string& mPrfxElement : mPrfxResults)
+				{
+					std::string mPrfxName = android::base::Trim(mPrfxElement);
+					if (!mPrfxName.empty() && data.fileName.length() >= mPrfxName.length() && data.fileName.substr(0, mPrfxName.length()) == mPrfxName) {
+						mFileList.push_back(data);
+					}
+#else //On android 5.1 we can't use android::base::Trim and Split so just use the first extension written in the list
+			std::size_t seppos = mExtn.find_first_of(";");
+			std::string mExtnf;
+			if (seppos!=std::string::npos){
+				mExtnf = mExtn.substr(0, seppos);
+			} else {
+				mExtnf = mExtn;
+			}
+			if (mExtnf.empty() || (data.fileName.length() >= mExtnf.length() && data.fileName.substr(data.fileName.length() - mExtnf.length()) == mExtnf)) {
+				if (mExtnf == ".ab" && twadbbu::Check_ADB_Backup_File(path))
+					mFolderList.push_back(data);
+				else
+					mFileList.push_back(data);
+				match = true;
+			}
+
+			if (!match) {
+				std::size_t seppos = mPrfx.find_first_of(";");
+				std::string mPrfxf;
+				if (seppos!=std::string::npos){
+					mPrfxf = mPrfx.substr(0, seppos);
+				} else {
+					mPrfxf = mPrfx;
+				}
+				if (!mPrfxf.empty() && data.fileName.length() >= mPrfxf.length() && data.fileName.substr(0, mPrfxf.length()) == mPrfxf) {
+					mFileList.push_back(data);
+#endif
+				}
+			}
+		}
+	}
+	closedir(d);
+
+	std::sort(mFolderList.begin(), mFolderList.end(), fileSort);
+	std::sort(mFileList.begin(), mFileList.end(), fileSort);
+
+	return 0;
+}
+
+void GUIFileSelector::SetPageFocus(int inFocus)
+{
+	GUIScrollList::SetPageFocus(inFocus);
+	if (inFocus) {
+		std::string value;
+		DataManager::GetValue(mPathVar, value);
+		if (value.empty())
+			DataManager::SetValue(mPathVar, mPathDefault);
+		updateFileList = true;
+		mUpdate = 1;
+	}
+}
+
+size_t GUIFileSelector::GetItemCount()
+{
+	size_t folderSize = mShowFolders ? mFolderList.size() : 0;
+	size_t fileSize = mShowFiles ? mFileList.size() : 0;
+	return folderSize + fileSize;
+}
+
+void GUIFileSelector::RenderItem(size_t itemindex, int yPos, bool selected)
+{
+	size_t folderSize = mShowFolders ? mFolderList.size() : 0;
+
+	ImageResource* icon;
+	std::string text;
+
+	if (itemindex < folderSize) {
+		text = mFolderList.at(itemindex).fileName;
+		icon = mFolderIcon;
+		if (text == "..")
+			text = gui_lookup("up_a_level", "(Up A Level)");
+	} else {
+		text = mFileList.at(itemindex - folderSize).fileName;
+		icon = mFileIcon;
+	}
+
+	RenderStdItem(yPos, selected, icon, text.c_str());
+}
+
+void GUIFileSelector::NotifySelect(size_t item_selected)
+{
+	size_t folderSize = mShowFolders ? mFolderList.size() : 0;
+	size_t fileSize = mShowFiles ? mFileList.size() : 0;
+
+	if (item_selected < folderSize + fileSize) {
+		// We've selected an item!
+		std::string str;
+		if (item_selected < folderSize) {
+			std::string cwd;
+
+			str = mFolderList.at(item_selected).fileName;
+			if (mSelection != "0")
+				DataManager::SetValue(mSelection, str);
+			DataManager::GetValue(mPathVar, cwd);
+
+			// Ignore requests to do nothing
+			if (str == ".")	 return;
+			if (str == "..") {
+				if (cwd != "/") {
+					size_t found;
+					found = cwd.find_last_of('/');
+					cwd = cwd.substr(0,found);
+
+					if (cwd.length() < 2)   cwd = "/";
+				}
+			} else {
+				// Add a slash if we're not the root folder
+				if (cwd != "/")	 cwd += "/";
+				cwd += str;
+			}
+
+			if (mShowNavFolders == 0 && (mShowFiles == 0 || mExtn == ".ab")) {
+				// this is probably the restore list and we need to save chosen location to mVariable instead of mPathVar
+				DataManager::SetValue(mVariable, cwd);
+			} else {
+				// We are changing paths, so we need to set mPathVar
+				DataManager::SetValue(mPathVar, cwd);
+			}
+		} else if (!mVariable.empty()) {
+			str = mFileList.at(item_selected - folderSize).fileName;
+			if (mSelection != "0")
+				DataManager::SetValue(mSelection, str);
+
+			std::string cwd;
+			DataManager::GetValue(mPathVar, cwd);
+			if (cwd != "/")
+				cwd += "/";
+			DataManager::SetValue(mVariable, cwd + str);
+		}
+	}
+	mUpdate = 1;
+}
diff --git a/gui/fill.cpp b/gui/fill.cpp
new file mode 100644
index 0000000..f813bf3
--- /dev/null
+++ b/gui/fill.cpp
@@ -0,0 +1,70 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// fill.cpp - GUIFill object
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUIFill::GUIFill(xml_node<>* node) : GUIObject(node)
+{
+	bool has_color = false;
+	mColor = LoadAttrColor(node, "color", &has_color);
+	if (!has_color) {
+		LOGERR("No color specified for fill\n");
+		return;
+	}
+
+	// Load the placement
+	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH);
+
+	return;
+}
+
+int GUIFill::Render(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	gr_color(mColor.red, mColor.green, mColor.blue, mColor.alpha);
+	gr_fill(mRenderX, mRenderY, mRenderW, mRenderH);
+	return 0;
+}
+
diff --git a/gui/gui.cpp b/gui/gui.cpp
new file mode 100755
index 0000000..00aab7f
--- /dev/null
+++ b/gui/gui.cpp
@@ -0,0 +1,955 @@
+/*
+        Copyright 2012 bigbiff/Dees_Troy TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <linux/input.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <time.h>
+#include <unistd.h>
+
+extern "C"
+{
+#include "../twcommon.h"
+#include <pixelflinger/pixelflinger.h>
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+#include "../data.hpp"
+#include "../variables.h"
+#include "../partitions.hpp"
+#include "../twrp-functions.hpp"
+#include "../openrecoveryscript.hpp"
+#include "../orscmd/orscmd.h"
+#include "blanktimer.hpp"
+#include "tw_atomic.hpp"
+
+// Enable to print render time of each frame to the log file
+//#define PRINT_RENDER_TIME 1
+
+#ifdef _EVENT_LOGGING
+#define LOGEVENT(...) LOGERR(__VA_ARGS__)
+#else
+#define LOGEVENT(...) do {} while (0)
+#endif
+
+using namespace rapidxml;
+
+// Global values
+static int gGuiInitialized = 0;
+static TWAtomicInt gForceRender;
+blanktimer blankTimer;
+int ors_read_fd = -1;
+static FILE* orsout = NULL;
+static float scale_theme_w = 1;
+static float scale_theme_h = 1;
+
+// Needed by pages.cpp too
+int gGuiRunning = 0;
+
+int g_pty_fd = -1;  // set by terminal on init
+void terminal_pty_read();
+
+int select_fd = 0;
+
+static int gRecorder = -1;
+
+extern "C" void gr_write_frame_to_file(int fd);
+
+static void flip(void)
+{
+	if (gRecorder != -1)
+	{
+		timespec time;
+		clock_gettime(CLOCK_MONOTONIC, &time);
+		write(gRecorder, &time, sizeof(timespec));
+		gr_write_frame_to_file(gRecorder);
+	}
+	gr_flip();
+}
+
+void rapidxml::parse_error_handler(const char *what, void *where)
+{
+	fprintf(stderr, "Parser error: %s\n", what);
+	fprintf(stderr, "  Start of string: %s\n",(char *) where);
+	LOGERR("Error parsing XML file.\n");
+	//abort();
+}
+
+class InputHandler
+{
+public:
+	void init()
+	{
+		// these might be read from DataManager in the future
+		touch_hold_ms = 500;
+		touch_repeat_ms = 100;
+		key_hold_ms = 500;
+		key_repeat_ms = 100;
+		touch_status = TS_NONE;
+		key_status = KS_NONE;
+		state = AS_NO_ACTION;
+		x = y = 0;
+
+#ifndef TW_NO_SCREEN_TIMEOUT
+		{
+			string seconds;
+			DataManager::GetValue("tw_screen_timeout_secs", seconds);
+			blankTimer.setTime(atoi(seconds.c_str()));
+			blankTimer.resetTimerAndUnblank();
+		}
+#else
+		LOGINFO("Skipping screen timeout: TW_NO_SCREEN_TIMEOUT is set\n");
+#endif
+	}
+
+	// process input events. returns true if any event was received.
+	bool processInput(int timeout_ms);
+
+	void handleDrag();
+
+private:
+	// timeouts for touch/key hold and repeat
+	int touch_hold_ms;
+	int touch_repeat_ms;
+	int key_hold_ms;
+	int key_repeat_ms;
+
+	enum touch_status_enum {
+		TS_NONE = 0,
+		TS_TOUCH_AND_HOLD = 1,
+		TS_TOUCH_REPEAT = 2,
+	};
+
+	enum key_status_enum {
+		KS_NONE = 0,
+		KS_KEY_PRESSED = 1,
+		KS_KEY_REPEAT = 2,
+	};
+
+	enum action_state_enum {
+		AS_IN_ACTION_AREA = 0, // we've touched a spot with an action
+		AS_NO_ACTION = 1,    // we've touched in an empty area (no action) and ignore remaining events until touch release
+	};
+	touch_status_enum touch_status;
+	key_status_enum key_status;
+	action_state_enum state;
+	int x, y; // x and y coordinates of last touch
+	struct timeval touchStart; // used to track time for long press / key repeat
+
+	void processHoldAndRepeat();
+	void process_EV_REL(input_event& ev);
+	void process_EV_ABS(input_event& ev);
+	void process_EV_KEY(input_event& ev);
+
+	void doTouchStart();
+};
+
+InputHandler input_handler;
+
+
+bool InputHandler::processInput(int timeout_ms)
+{
+	input_event ev;
+	int ret = ev_get(&ev, timeout_ms);
+
+	if (ret < 0)
+	{
+		// This path means that we did not get any new touch data, but
+		// we do not get new touch data if you press and hold on either
+		// the screen or on a keyboard key or mouse button
+		if (touch_status || key_status)
+			processHoldAndRepeat();
+		return (ret != -2);  // -2 means no more events in the queue
+	}
+
+	switch (ev.type)
+	{
+	case EV_ABS:
+		process_EV_ABS(ev);
+		break;
+
+	case EV_REL:
+		process_EV_REL(ev);
+		break;
+
+	case EV_KEY:
+		process_EV_KEY(ev);
+		break;
+	}
+
+	if (ev.code != KEY_POWER && ev.code > KEY_RESERVED)
+		blankTimer.resetTimerAndUnblank();
+
+	return true;  // we got an event, so there might be more in the queue
+}
+
+void InputHandler::processHoldAndRepeat()
+{
+	HardwareKeyboard *kb = PageManager::GetHardwareKeyboard();
+
+	// touch and key repeat section
+	struct timeval curTime;
+	gettimeofday(&curTime, NULL);
+	long seconds = curTime.tv_sec - touchStart.tv_sec;
+	long useconds = curTime.tv_usec - touchStart.tv_usec;
+	long mtime = ((seconds) * 1000 + useconds / 1000.0) + 0.5;
+
+	if (touch_status == TS_TOUCH_AND_HOLD && mtime > touch_hold_ms)
+	{
+		touch_status = TS_TOUCH_REPEAT;
+		gettimeofday(&touchStart, NULL);
+		LOGEVENT("TOUCH_HOLD: %d,%d\n", x, y);
+		PageManager::NotifyTouch(TOUCH_HOLD, x, y);
+	}
+	else if (touch_status == TS_TOUCH_REPEAT && mtime > touch_repeat_ms)
+	{
+		LOGEVENT("TOUCH_REPEAT: %d,%d\n", x, y);
+		gettimeofday(&touchStart, NULL);
+		PageManager::NotifyTouch(TOUCH_REPEAT, x, y);
+	}
+	else if (key_status == KS_KEY_PRESSED && mtime > key_hold_ms)
+	{
+		LOGEVENT("KEY_HOLD: %d,%d\n", x, y);
+		gettimeofday(&touchStart, NULL);
+		key_status = KS_KEY_REPEAT;
+		kb->KeyRepeat();
+	}
+	else if (key_status == KS_KEY_REPEAT && mtime > key_repeat_ms)
+	{
+		LOGEVENT("KEY_REPEAT: %d,%d\n", x, y);
+		gettimeofday(&touchStart, NULL);
+		kb->KeyRepeat();
+	}
+}
+
+void InputHandler::doTouchStart()
+{
+	LOGEVENT("TOUCH_START: %d,%d\n", x, y);
+	if (PageManager::NotifyTouch(TOUCH_START, x, y) > 0)
+		state = AS_NO_ACTION;
+	else
+		state = AS_IN_ACTION_AREA;
+	touch_status = TS_TOUCH_AND_HOLD;
+	gettimeofday(&touchStart, NULL);
+}
+
+void InputHandler::process_EV_ABS(input_event& ev)
+{
+	x = ev.value >> 16;
+	y = ev.value & 0xFFFF;
+
+	if (ev.code == 0)
+	{
+#ifndef TW_USE_KEY_CODE_TOUCH_SYNC
+		if (state == AS_IN_ACTION_AREA)
+		{
+			LOGEVENT("TOUCH_RELEASE: %d,%d\n", x, y);
+			PageManager::NotifyTouch(TOUCH_RELEASE, x, y);
+		}
+		touch_status = TS_NONE;
+#endif
+	}
+	else
+	{
+		if (!touch_status)
+		{
+#ifndef TW_USE_KEY_CODE_TOUCH_SYNC
+			doTouchStart();
+#endif
+		}
+		else
+		{
+			if (state == AS_IN_ACTION_AREA)
+			{
+				LOGEVENT("TOUCH_DRAG: %d,%d\n", x, y);
+			}
+		}
+	}
+}
+
+void InputHandler::process_EV_KEY(input_event& ev)
+{
+	HardwareKeyboard *kb = PageManager::GetHardwareKeyboard();
+
+	// Handle key-press here
+	LOGEVENT("TOUCH_KEY: %d\n", ev.code);
+	// Left mouse button is treated as a touch
+	if (ev.code == BTN_LEFT)
+	{
+		MouseCursor *cursor = PageManager::GetMouseCursor();
+		if (ev.value == 1)
+		{
+			cursor->GetPos(x, y);
+			doTouchStart();
+		}
+		else if (touch_status)
+		{
+			// Left mouse button was previously pressed and now is
+			// being released so send a TOUCH_RELEASE
+			if (state == AS_IN_ACTION_AREA)
+			{
+				cursor->GetPos(x, y);
+
+				LOGEVENT("Mouse TOUCH_RELEASE: %d,%d\n", x, y);
+				PageManager::NotifyTouch(TOUCH_RELEASE, x, y);
+			}
+			touch_status = TS_NONE;
+		}
+	}
+	// side mouse button, often used for "back" function
+	else if (ev.code == BTN_SIDE)
+	{
+		if (ev.value == 1)
+			kb->KeyDown(KEY_BACK);
+		else
+			kb->KeyUp(KEY_BACK);
+	} else if (ev.value != 0) {
+		// This is a key press
+#ifdef TW_USE_KEY_CODE_TOUCH_SYNC
+		if (ev.code == TW_USE_KEY_CODE_TOUCH_SYNC) {
+			LOGEVENT("key code %i key press == touch start %i %i\n", TW_USE_KEY_CODE_TOUCH_SYNC, x, y);
+			doTouchStart();
+			return;
+		}
+#endif
+		if (kb->KeyDown(ev.code)) {
+			// Key repeat is enabled for this key
+			key_status = KS_KEY_PRESSED;
+			touch_status = TS_NONE;
+			gettimeofday(&touchStart, NULL);
+		} else {
+			key_status = KS_NONE;
+			touch_status = TS_NONE;
+		}
+	} else {
+		// This is a key release
+		kb->KeyUp(ev.code);
+		key_status = KS_NONE;
+		touch_status = TS_NONE;
+#ifdef TW_USE_KEY_CODE_TOUCH_SYNC
+		if (ev.code == TW_USE_KEY_CODE_TOUCH_SYNC) {
+			LOGEVENT("key code %i key release == touch release %i %i\n", TW_USE_KEY_CODE_TOUCH_SYNC, x, y);
+			PageManager::NotifyTouch(TOUCH_RELEASE, x, y);
+		}
+#endif
+	}
+}
+
+void InputHandler::process_EV_REL(input_event& ev)
+{
+	// Mouse movement
+	MouseCursor *cursor = PageManager::GetMouseCursor();
+	LOGEVENT("EV_REL %d %d\n", ev.code, ev.value);
+	if (ev.code == REL_X)
+		cursor->Move(ev.value, 0);
+	else if (ev.code == REL_Y)
+		cursor->Move(0, ev.value);
+
+	if (touch_status) {
+		cursor->GetPos(x, y);
+		LOGEVENT("Mouse TOUCH_DRAG: %d, %d\n", x, y);
+		key_status = KS_NONE;
+	}
+}
+
+void InputHandler::handleDrag()
+{
+	// This allows us to only send one NotifyTouch event per render
+	// cycle to reduce overhead and perceived input latency.
+	static int prevx = 0, prevy = 0; // these track where the last drag notice was so that we don't send duplicate drag notices
+	if (touch_status && (x != prevx || y != prevy)) {
+		prevx = x;
+		prevy = y;
+		if (PageManager::NotifyTouch(TOUCH_DRAG, x, y) > 0)
+			state = AS_NO_ACTION;
+		else
+			state = AS_IN_ACTION_AREA;
+	}
+}
+
+void set_select_fd() {
+	select_fd = ors_read_fd + 1;
+	if (g_pty_fd >= select_fd)
+		select_fd = g_pty_fd + 1;
+	if (PartitionManager.uevent_pfd.fd >= select_fd)
+		select_fd = PartitionManager.uevent_pfd.fd + 1;
+}
+
+static void setup_ors_command()
+{
+	ors_read_fd = -1;
+	set_select_fd();
+
+	unlink(ORS_INPUT_FILE);
+	if (mkfifo(ORS_INPUT_FILE, 06660) != 0) {
+		LOGINFO("Unable to mkfifo %s\n", ORS_INPUT_FILE);
+		return;
+	}
+	unlink(ORS_OUTPUT_FILE);
+	if (mkfifo(ORS_OUTPUT_FILE, 06666) != 0) {
+		LOGINFO("Unable to mkfifo %s\n", ORS_OUTPUT_FILE);
+		unlink(ORS_INPUT_FILE);
+		return;
+	}
+
+	ors_read_fd = open(ORS_INPUT_FILE, O_RDONLY | O_NONBLOCK);
+	if (ors_read_fd < 0) {
+		LOGINFO("Unable to open %s\n", ORS_INPUT_FILE);
+		unlink(ORS_INPUT_FILE);
+		unlink(ORS_OUTPUT_FILE);
+	}
+	set_select_fd();
+}
+
+// callback called after a CLI command was executed
+static void ors_command_done()
+{
+	gui_set_FILE(NULL);
+	fclose(orsout);
+	orsout = NULL;
+
+	if (DataManager::GetIntValue("tw_page_done") == 0) {
+		// The select function will return ready to read and the
+		// read function will return errno 19 no such device unless
+		// we set everything up all over again.
+		close(ors_read_fd);
+		setup_ors_command();
+	}
+}
+
+static void ors_command_read()
+{
+	char command[1024];
+	int read_ret = read(ors_read_fd, &command, sizeof(command));
+
+	if (read_ret > 0) {
+		command[1022] = '\n';
+		command[1023] = '\0';
+		LOGINFO("Command '%s' received\n", command);
+		orsout = fopen(ORS_OUTPUT_FILE, "w");
+		if (!orsout) {
+			close(ors_read_fd);
+			ors_read_fd = -1;
+			set_select_fd();
+			LOGINFO("Unable to fopen %s\n", ORS_OUTPUT_FILE);
+			unlink(ORS_INPUT_FILE);
+			unlink(ORS_OUTPUT_FILE);
+			return;
+		}
+		if (DataManager::GetIntValue("tw_busy") != 0) {
+			fputs("Failed, operation in progress\n", orsout);
+			LOGINFO("Command cannot be performed, operation in progress.\n");
+			fclose(orsout);
+		} else {
+			if (strlen(command) == 11 && strncmp(command, "dumpstrings", 11) == 0) {
+				gui_set_FILE(orsout);
+				PageManager::GetResources()->DumpStrings();
+				ors_command_done();
+			} else if (strlen(command) == 11 && strncmp(command, "reloadtheme", 11) == 0) {
+				PageManager::RequestReload();
+				ors_command_done();
+			} else if (strlen(command) > 11 && strncmp(command, "changepage=", 11) == 0) {
+				char* pg = &command[11];
+				gui_changePage(pg);
+				ors_command_done();
+			} else {
+				// mirror output messages
+				gui_set_FILE(orsout);
+				// close orsout and restart listener after command is done
+				OpenRecoveryScript::Call_After_CLI_Command(ors_command_done);
+				// run the command in a threaded action...
+				DataManager::SetValue("tw_action", "twcmd");
+				DataManager::SetValue("tw_action_param", command);
+				// ...and switch back to the current page when finished
+				std::string currentPage = PageManager::GetCurrentPage();
+				DataManager::SetValue("tw_has_action2", "1");
+				DataManager::SetValue("tw_action2", "page");
+				DataManager::SetValue("tw_action2_param", currentPage);
+				DataManager::SetValue("tw_action_text1", gui_lookup("running_recovery_commands", "Running Recovery Commands"));
+				DataManager::SetValue("tw_action_text2", "");
+				gui_changePage("singleaction_page");
+				// now immediately return to the GUI main loop (the action runs in the background thread)
+				// put all things that need to be done after the command is finished into ors_command_done, not here
+			}
+		}
+	}
+}
+
+// Get and dispatch input events until it's time to draw the next frame
+// This special function will return immediately the first time, but then
+// always returns 1/30th of a second (or immediately if called later) from
+// the last time it was called
+static void loopTimer(int input_timeout_ms)
+{
+	static timespec lastCall;
+	static int initialized = 0;
+
+	if (!initialized)
+	{
+		clock_gettime(CLOCK_MONOTONIC, &lastCall);
+		initialized = 1;
+		return;
+	}
+
+	do
+	{
+		bool got_event = input_handler.processInput(input_timeout_ms); // get inputs but don't send drag notices
+		timespec curTime;
+		clock_gettime(CLOCK_MONOTONIC, &curTime);
+
+		timespec diff = TWFunc::timespec_diff(lastCall, curTime);
+
+		// This is really 2 or 30 times per second
+		// As long as we get events, increase the timeout so we can catch up with input
+		long timeout = got_event ? 500000000 : 33333333;
+
+		if (diff.tv_sec || diff.tv_nsec > timeout)
+		{
+			// int32_t input_time = TWFunc::timespec_diff_ms(lastCall, curTime);
+			// LOGINFO("loopTimer(): %u ms, count: %u\n", input_time, count);
+
+			lastCall = curTime;
+			input_handler.handleDrag(); // send only drag notices if needed
+			return;
+		}
+
+		// We need to sleep some period time microseconds
+		//unsigned int sleepTime = 33333 -(diff.tv_nsec / 1000);
+		//usleep(sleepTime); // removed so we can scan for input
+		input_timeout_ms = 0;
+	} while (1);
+}
+
+static int runPages(const char *page_name, const int stop_on_page_done)
+{
+	DataManager::SetValue("tw_page_done", 0);
+	DataManager::SetValue("tw_gui_done", 0);
+
+	if (page_name) {
+		PageManager::SetStartPage(page_name);
+		gui_changePage(page_name);
+	}
+
+	gGuiRunning = 1;
+
+	DataManager::SetValue("tw_loaded", 1);
+
+	struct timeval timeout;
+	fd_set fdset;
+	int has_data = 0;
+
+	int input_timeout_ms = 0;
+	int idle_frames = 0;
+
+	for (;;)
+	{
+		loopTimer(input_timeout_ms);
+		FD_ZERO(&fdset);
+		timeout.tv_sec = 0;
+		timeout.tv_usec = 1;
+		if (g_pty_fd > 0) {
+			FD_SET(g_pty_fd, &fdset);
+		}
+		if (PartitionManager.uevent_pfd.fd > 0) {
+			FD_SET(PartitionManager.uevent_pfd.fd, &fdset);
+		}
+#ifndef TW_OEM_BUILD
+		if (ors_read_fd > 0 && !orsout) { // orsout is non-NULL if a command is still running
+			FD_SET(ors_read_fd, &fdset);
+		}
+#endif
+		// TODO: combine this select with the poll done by input handling
+		has_data = select(select_fd, &fdset, NULL, NULL, &timeout);
+		if (has_data > 0) {
+			if (g_pty_fd > 0 && FD_ISSET(g_pty_fd, &fdset))
+				terminal_pty_read();
+			if (PartitionManager.uevent_pfd.fd > 0 && FD_ISSET(PartitionManager.uevent_pfd.fd, &fdset))
+				PartitionManager.read_uevent();
+			if (ors_read_fd > 0 && !orsout && FD_ISSET(ors_read_fd, &fdset))
+				ors_command_read();
+		}
+
+		if (!gForceRender.get_value())
+		{
+			int ret = PageManager::Update();
+			if (ret == 0)
+				++idle_frames;
+			else if (ret == -2)
+				break; // Theme reload failure
+			else
+				idle_frames = 0;
+			// due to possible animation objects, we need to delay activating the input timeout
+			input_timeout_ms = idle_frames > 15 ? 1000 : 0;
+
+#ifndef PRINT_RENDER_TIME
+			if (ret > 1)
+				PageManager::Render();
+
+			if (ret > 0)
+				flip();
+#else
+			if (ret > 1)
+			{
+				timespec start, end;
+				int32_t render_t, flip_t;
+				clock_gettime(CLOCK_MONOTONIC, &start);
+				PageManager::Render();
+				clock_gettime(CLOCK_MONOTONIC, &end);
+				render_t = TWFunc::timespec_diff_ms(start, end);
+
+				flip();
+				clock_gettime(CLOCK_MONOTONIC, &start);
+				flip_t = TWFunc::timespec_diff_ms(end, start);
+
+				LOGINFO("Render(): %u ms, flip(): %u ms, total: %u ms\n", render_t, flip_t, render_t+flip_t);
+			}
+			else if (ret > 0)
+				flip();
+#endif
+		}
+		else
+		{
+			gForceRender.set_value(0);
+			PageManager::Render();
+			flip();
+			input_timeout_ms = 0;
+		}
+
+		blankTimer.checkForTimeout();
+		if (stop_on_page_done && DataManager::GetIntValue("tw_page_done") != 0)
+		{
+			gui_changePage("main");
+			break;
+		}
+		if (DataManager::GetIntValue("tw_gui_done") != 0) {
+			break;
+		}
+	}
+	if (ors_read_fd > 0)
+		close(ors_read_fd);
+	ors_read_fd = -1;
+	set_select_fd();
+	gGuiRunning = 0;
+	return 0;
+}
+
+int gui_forceRender(void)
+{
+	gForceRender.set_value(1);
+	return 0;
+}
+
+int gui_changePage(std::string newPage)
+{
+	LOGINFO("Set page: '%s'\n", newPage.c_str());
+	PageManager::ChangePage(newPage);
+	gForceRender.set_value(1);
+	return 0;
+}
+
+int gui_changeOverlay(std::string overlay)
+{
+	LOGINFO("Set overlay: '%s'\n", overlay.c_str());
+	PageManager::ChangeOverlay(overlay);
+	gForceRender.set_value(1);
+	return 0;
+}
+
+std::string gui_parse_text(std::string str)
+{
+	// This function parses text for DataManager values encompassed by %value% in the XML
+	// and string resources (%@resource_name%)
+	size_t pos = 0, next, end;
+
+	while (1)
+	{
+		next = str.find("{@", pos);
+		if (next == std::string::npos)
+			break;
+
+		end = str.find('}', next + 1);
+		if (end == std::string::npos)
+			break;
+
+		std::string var = str.substr(next + 2, (end - next) - 2);
+		str.erase(next, (end - next) + 1);
+
+		size_t default_loc = var.find('=', 0);
+		std::string lookup;
+		if (default_loc == std::string::npos) {
+			str.insert(next, PageManager::GetResources()->FindString(var));
+		} else {
+			lookup = var.substr(0, default_loc);
+			std::string default_string = var.substr(default_loc + 1, var.size() - default_loc - 1);
+			str.insert(next, PageManager::GetResources()->FindString(lookup, default_string));
+		}
+	}
+	pos = 0;
+	while (1)
+	{
+		next = str.find('%', pos);
+		if (next == std::string::npos)
+			return str;
+
+		end = str.find('%', next + 1);
+		if (end == std::string::npos)
+			return str;
+
+		// We have a block of data
+		std::string var = str.substr(next + 1, (end - next) - 1);
+		str.erase(next, (end - next) + 1);
+
+		if (next + 1 == end)
+			str.insert(next, 1, '%');
+		else
+		{
+			std::string value;
+			if (var.size() > 0 && var[0] == '@') {
+				// this is a string resource ("%@string_name%")
+				value = PageManager::GetResources()->FindString(var.substr(1));
+				str.insert(next, value);
+			}
+			else if (DataManager::GetValue(var, value) == 0)
+				str.insert(next, value);
+		}
+
+		pos = next + 1;
+	}
+}
+
+std::string gui_lookup(const std::string& resource_name, const std::string& default_value) {
+	return PageManager::GetResources()->FindString(resource_name, default_value);
+}
+
+extern "C" int gui_init(void)
+{
+	gr_init();
+	TWFunc::Set_Brightness(DataManager::GetStrValue("tw_brightness"));
+
+#ifdef TW_SCREEN_BLANK_ON_BOOT
+        printf("TW_SCREEN_BLANK_ON_BOOT := true\n");
+        blankTimer.blank();
+        blankTimer.resetTimerAndUnblank();
+#endif
+
+	// load and show splash screen
+	if (PageManager::LoadPackage("splash", TWRES "splash.xml", "splash")) {
+		LOGERR("Failed to load splash screen XML.\n");
+	}
+	else {
+		PageManager::SelectPackage("splash");
+		PageManager::Render();
+		flip();
+		PageManager::ReleasePackage("splash");
+	}
+
+#ifdef TW_DELAY_TOUCH_INIT_MS
+	usleep(TW_DELAY_TOUCH_INIT_MS);
+#endif
+	ev_init();
+	return 0;
+}
+
+extern "C" int gui_loadResources(void)
+{
+#ifndef TW_OEM_BUILD
+	int check = 0;
+	DataManager::GetValue(TW_IS_ENCRYPTED, check);
+	if (check)
+	{
+		if (PageManager::LoadPackage("TWRP", TWRES "ui.xml", "decrypt"))
+		{
+			gui_err("base_pkg_err=Failed to load base packages.");
+			goto error;
+		}
+		else
+			check = 1;
+	}
+
+	if (check == 0)
+	{
+		std::string theme_path;
+
+		theme_path = DataManager::GetSettingsStoragePath();
+		if (!PartitionManager.Mount_Settings_Storage(false))
+		{
+			int retry_count = 5;
+			while (retry_count > 0 && !PartitionManager.Mount_Settings_Storage(false))
+			{
+				usleep(500000);
+				retry_count--;
+			}
+
+			if (!PartitionManager.Mount_Settings_Storage(true))
+			{
+				LOGINFO("Unable to mount %s during GUI startup.\n", theme_path.c_str());
+				check = 1;
+			}
+		}
+
+		theme_path += "/TWRP/theme/ui.zip";
+		if (check || PageManager::LoadPackage("TWRP", theme_path, "main"))
+		{
+#endif // ifndef TW_OEM_BUILD
+			if (PageManager::LoadPackage("TWRP", TWRES "ui.xml", "main"))
+			{
+				gui_err("base_pkg_err=Failed to load base packages.");
+				goto error;
+			}
+#ifndef TW_OEM_BUILD
+		}
+	}
+#endif // ifndef TW_OEM_BUILD
+	// Set the default package
+	PageManager::SelectPackage("TWRP");
+
+	gGuiInitialized = 1;
+	return 0;
+
+error:
+	LOGERR("An internal error has occurred: unable to load theme.\n");
+	gGuiInitialized = 0;
+	return -1;
+}
+
+extern "C" int gui_loadCustomResources(void)
+{
+#ifndef TW_OEM_BUILD
+	if (!PartitionManager.Mount_Settings_Storage(false)) {
+		LOGINFO("Unable to mount settings storage during GUI startup.\n");
+		return -1;
+	}
+
+	std::string theme_path = DataManager::GetSettingsStoragePath();
+	theme_path += "/TWRP/theme/ui.zip";
+	// Check for a custom theme
+	if (TWFunc::Path_Exists(theme_path)) {
+		// There is a custom theme, try to load it
+		if (PageManager::ReloadPackage("TWRP", theme_path)) {
+			// Custom theme failed to load, try to load stock theme
+			if (PageManager::ReloadPackage("TWRP", TWRES "ui.xml")) {
+				gui_err("base_pkg_err=Failed to load base packages.");
+				goto error;
+			}
+		}
+	}
+	// Set the default package
+	PageManager::SelectPackage("TWRP");
+#endif
+	return 0;
+
+#ifndef TW_OEM_BUILD
+error:
+	LOGERR("An internal error has occurred: unable to load theme.\n");
+	gGuiInitialized = 0;
+	return -1;
+#endif
+}
+
+extern "C" int gui_start(void)
+{
+	return gui_startPage("main", 1, 0);
+}
+
+extern "C" int gui_startPage(const char *page_name, __attribute__((unused)) const int allow_commands, int stop_on_page_done)
+{
+	if (!gGuiInitialized)
+		return -1;
+
+	// Set the default package
+	PageManager::SelectPackage("TWRP");
+
+	input_handler.init();
+#ifndef TW_OEM_BUILD
+	if (allow_commands)
+	{
+		if (ors_read_fd < 0)
+			setup_ors_command();
+	} else {
+		if (ors_read_fd >= 0) {
+			close(ors_read_fd);
+			ors_read_fd = -1;
+		}
+	}
+#endif
+	return runPages(page_name, stop_on_page_done);
+}
+
+
+extern "C" void set_scale_values(float w, float h)
+{
+	scale_theme_w = w;
+	scale_theme_h = h;
+}
+
+extern "C" int scale_theme_x(int initial_x)
+{
+	if (scale_theme_w != 1) {
+		int scaled = (float)initial_x * scale_theme_w;
+		if (scaled == 0 && initial_x > 0)
+			return 1;
+		return scaled;
+	}
+	return initial_x;
+}
+
+extern "C" int scale_theme_y(int initial_y)
+{
+	if (scale_theme_h != 1) {
+		int scaled = (float)initial_y * scale_theme_h;
+		if (scaled == 0 && initial_y > 0)
+			return 1;
+		return scaled;
+	}
+	return initial_y;
+}
+
+extern "C" int scale_theme_min(int initial_value)
+{
+	if (scale_theme_w != 1 || scale_theme_h != 1) {
+		if (scale_theme_w < scale_theme_h)
+			return scale_theme_x(initial_value);
+		else
+			return scale_theme_y(initial_value);
+	}
+	return initial_value;
+}
+
+extern "C" float get_scale_w()
+{
+	return scale_theme_w;
+}
+
+extern "C" float get_scale_h()
+{
+	return scale_theme_h;
+}
diff --git a/gui/gui.h b/gui/gui.h
new file mode 100644
index 0000000..034f1cd
--- /dev/null
+++ b/gui/gui.h
@@ -0,0 +1,41 @@
+/*
+        Copyright 2012 bigbiff/Dees_Troy TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _GUI_HEADER
+#define _GUI_HEADER
+
+#include <stdio.h>
+
+int gui_init();
+int gui_loadResources();
+int gui_loadCustomResources();
+int gui_start();
+int gui_startPage(const char* page_name, const int allow_comands, int stop_on_page_done);
+void gui_print(const char *fmt, ...);
+void gui_print_color(const char *color, const char *fmt, ...);
+void gui_set_FILE(FILE* f);
+
+void set_scale_values(float w, float h);
+int scale_theme_x(int initial_x);
+int scale_theme_y(int initial_y);
+int scale_theme_min(int initial_value);
+float get_scale_w();
+float get_scale_h();
+
+#endif  // _GUI_HEADER
+
diff --git a/gui/gui.hpp b/gui/gui.hpp
new file mode 100644
index 0000000..7e4ee4e
--- /dev/null
+++ b/gui/gui.hpp
@@ -0,0 +1,36 @@
+/*
+        Copyright 2015 bigbiff/Dees_Troy TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _GUI_HPP_HEADER
+#define _GUI_HPP_HEADER
+
+#include "twmsg.h"
+
+void set_select_fd();
+
+void gui_msg(const char* text);
+void gui_warn(const char* text);
+void gui_err(const char* text);
+void gui_highlight(const char* text);
+void gui_msg(Message msg);
+void gui_err(Message msg);
+
+std::string gui_parse_text(std::string inText);
+std::string gui_lookup(const std::string& resource_name, const std::string& default_value);
+
+#endif //_GUI_HPP_HEADER
diff --git a/gui/hardwarekeyboard.cpp b/gui/hardwarekeyboard.cpp
new file mode 100644
index 0000000..12c4737
--- /dev/null
+++ b/gui/hardwarekeyboard.cpp
@@ -0,0 +1,441 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// hardwarekeyboard.cpp - HardwareKeyboard object
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+
+extern "C" {
+#include "common.h"
+}
+
+#include "../twcommon.h"
+#include "objects.hpp"
+#include <linux/input.h>
+
+HardwareKeyboard::HardwareKeyboard()
+ : mLastKeyChar(0)
+{
+}
+
+HardwareKeyboard::~HardwareKeyboard()
+{
+}
+
+// Map keys to other keys.
+static int TranslateKeyCode(int key_code)
+{
+	switch (key_code) {
+		case KEY_SLEEP: // Lock key on Asus Transformer hardware keyboard
+			return KEY_POWER;
+	}
+	return key_code;
+}
+
+static int KeyCodeToChar(int key_code, bool shiftkey, bool ctrlkey)
+{
+	int keyboard = -1;
+
+	switch (key_code) {
+		case KEY_A:
+			if (shiftkey)
+				keyboard = 'A';
+			else
+				keyboard = 'a';
+			break;
+		case KEY_B:
+			if (shiftkey)
+				keyboard = 'B';
+			else
+				keyboard = 'b';
+			break;
+		case KEY_C:
+			if (shiftkey)
+				keyboard = 'C';
+			else
+				keyboard = 'c';
+			break;
+		case KEY_D:
+			if (shiftkey)
+				keyboard = 'D';
+			else
+				keyboard = 'd';
+			break;
+		case KEY_E:
+			if (shiftkey)
+				keyboard = 'E';
+			else
+				keyboard = 'e';
+			break;
+		case KEY_F:
+			if (shiftkey)
+				keyboard = 'F';
+			else
+				keyboard = 'f';
+			break;
+		case KEY_G:
+			if (shiftkey)
+				keyboard = 'G';
+			else
+				keyboard = 'g';
+			break;
+		case KEY_H:
+			if (shiftkey)
+				keyboard = 'H';
+			else
+				keyboard = 'h';
+			break;
+		case KEY_I:
+			if (shiftkey)
+				keyboard = 'I';
+			else
+				keyboard = 'i';
+			break;
+		case KEY_J:
+			if (shiftkey)
+				keyboard = 'J';
+			else
+				keyboard = 'j';
+			break;
+		case KEY_K:
+			if (shiftkey)
+				keyboard = 'K';
+			else
+				keyboard = 'k';
+			break;
+		case KEY_L:
+			if (shiftkey)
+				keyboard = 'L';
+			else
+				keyboard = 'l';
+			break;
+		case KEY_M:
+			if (shiftkey)
+				keyboard = 'M';
+			else
+				keyboard = 'm';
+			break;
+		case KEY_N:
+			if (shiftkey)
+				keyboard = 'N';
+			else
+				keyboard = 'n';
+			break;
+		case KEY_O:
+			if (shiftkey)
+				keyboard = 'O';
+			else
+				keyboard = 'o';
+			break;
+		case KEY_P:
+			if (shiftkey)
+				keyboard = 'P';
+			else
+				keyboard = 'p';
+			break;
+		case KEY_Q:
+			if (shiftkey)
+				keyboard = 'Q';
+			else
+				keyboard = 'q';
+			break;
+		case KEY_R:
+			if (shiftkey)
+				keyboard = 'R';
+			else
+				keyboard = 'r';
+			break;
+		case KEY_S:
+			if (shiftkey)
+				keyboard = 'S';
+			else
+				keyboard = 's';
+			break;
+		case KEY_T:
+			if (shiftkey)
+				keyboard = 'T';
+			else
+				keyboard = 't';
+			break;
+		case KEY_U:
+			if (shiftkey)
+				keyboard = 'U';
+			else
+				keyboard = 'u';
+			break;
+		case KEY_V:
+			if (shiftkey)
+				keyboard = 'V';
+			else
+				keyboard = 'v';
+			break;
+		case KEY_W:
+			if (shiftkey)
+				keyboard = 'W';
+			else
+				keyboard = 'w';
+			break;
+		case KEY_X:
+			if (shiftkey)
+				keyboard = 'X';
+			else
+				keyboard = 'x';
+			break;
+		case KEY_Y:
+			if (shiftkey)
+				keyboard = 'Y';
+			else
+				keyboard = 'y';
+			break;
+		case KEY_Z:
+			if (shiftkey)
+				keyboard = 'Z';
+			else
+				keyboard = 'z';
+			break;
+		case KEY_0:
+			if (shiftkey)
+				keyboard = ')';
+			else
+				keyboard = '0';
+			break;
+		case KEY_1:
+			if (shiftkey)
+				keyboard = '!';
+			else
+				keyboard = '1';
+			break;
+		case KEY_2:
+			if (shiftkey)
+				keyboard = '@';
+			else
+				keyboard = '2';
+			break;
+		case KEY_3:
+			if (shiftkey)
+				keyboard = '#';
+			else
+				keyboard = '3';
+			break;
+		case KEY_4:
+			if (shiftkey)
+				keyboard = '$';
+			else
+				keyboard = '4';
+			break;
+		case KEY_5:
+			if (shiftkey)
+				keyboard = '%';
+			else
+				keyboard = '5';
+			break;
+		case KEY_6:
+			if (shiftkey)
+				keyboard = '^';
+			else
+				keyboard = '6';
+			break;
+		case KEY_7:
+			if (shiftkey)
+				keyboard = '&';
+			else
+				keyboard = '7';
+			break;
+		case KEY_8:
+			if (shiftkey)
+				keyboard = '*';
+			else
+				keyboard = '8';
+			break;
+		case KEY_9:
+			if (shiftkey)
+				keyboard = '(';
+			else
+				keyboard = '9';
+			break;
+		case KEY_SPACE:
+			keyboard = ' ';
+			break;
+		case KEY_BACKSPACE:
+			keyboard = KEYBOARD_BACKSPACE;
+			break;
+		case KEY_TAB:
+			keyboard = KEYBOARD_TAB;
+			break;
+		case KEY_ENTER:
+			keyboard = KEYBOARD_ACTION;
+			break;
+		case KEY_SLASH:
+			if (shiftkey)
+				keyboard = '?';
+			else
+				keyboard = '/';
+			break;
+		case KEY_DOT:
+			if (shiftkey)
+				keyboard = '>';
+			else
+				keyboard = '.';
+			break;
+		case KEY_COMMA:
+			if (shiftkey)
+				keyboard = '<';
+			else
+				keyboard = ',';
+			break;
+		case KEY_MINUS:
+			if (shiftkey)
+				keyboard = '_';
+			else
+				keyboard = '-';
+			break;
+		case KEY_GRAVE:
+			if (shiftkey)
+				keyboard = '~';
+			else
+				keyboard = '`';
+			break;
+		case KEY_EQUAL:
+			if (shiftkey)
+				keyboard = '+';
+			else
+				keyboard = '=';
+			break;
+		case KEY_LEFTBRACE:
+			if (shiftkey)
+				keyboard = '{';
+			else
+				keyboard = '[';
+			break;
+		case KEY_RIGHTBRACE:
+			if (shiftkey)
+				keyboard = '}';
+			else
+				keyboard = ']';
+			break;
+		case KEY_BACKSLASH:
+			if (shiftkey)
+				keyboard = '|';
+			else
+				keyboard = '\\';
+			break;
+		case KEY_SEMICOLON:
+			if (shiftkey)
+				keyboard = ':';
+			else
+				keyboard = ';';
+			break;
+		case KEY_APOSTROPHE:
+			if (shiftkey)
+				keyboard = '\"';
+			else
+				keyboard = '\'';
+			break;
+
+#ifdef _EVENT_LOGGING
+		default:
+			LOGERR("Unmapped keycode: %i\n", key_code);
+			break;
+#endif
+	}
+	if (ctrlkey)
+	{
+		if (keyboard >= 96)
+			keyboard -= 96;
+		else
+			keyboard = -1;
+	}
+	return keyboard;
+}
+
+bool HardwareKeyboard::IsKeyDown(int key_code)
+{
+	std::set<int>::iterator it = mPressedKeys.find(key_code);
+	return (it != mPressedKeys.end());
+}
+
+int HardwareKeyboard::KeyDown(int key_code)
+{
+#ifdef _EVENT_LOGGING
+	LOGERR("HardwareKeyboard::KeyDown %i\n", key_code);
+#endif
+	key_code = TranslateKeyCode(key_code);
+	mPressedKeys.insert(key_code);
+
+	bool ctrlkey = IsKeyDown(KEY_LEFTCTRL) || IsKeyDown(KEY_RIGHTCTRL);
+	bool shiftkey = IsKeyDown(KEY_LEFTSHIFT) || IsKeyDown(KEY_RIGHTSHIFT);
+
+	int ch = KeyCodeToChar(key_code, shiftkey, ctrlkey);
+
+	if (ch != -1) {
+		mLastKeyChar = ch;
+		if (!PageManager::NotifyCharInput(ch))
+			return 1;  // Return 1 to enable key repeat
+	} else {
+		mLastKeyChar = 0;
+		mLastKey = key_code;
+		if (!PageManager::NotifyKey(key_code, true))
+			return 1;  // Return 1 to enable key repeat
+	}
+	return 0;
+}
+
+int HardwareKeyboard::KeyUp(int key_code)
+{
+#ifdef _EVENT_LOGGING
+	LOGERR("HardwareKeyboard::KeyUp %i\n", key_code);
+#endif
+	key_code = TranslateKeyCode(key_code);
+	std::set<int>::iterator itr = mPressedKeys.find(key_code);
+	if (itr != mPressedKeys.end()) {
+		mPressedKeys.erase(itr);
+		PageManager::NotifyKey(key_code, false);
+	}
+	return 0;
+}
+
+int HardwareKeyboard::KeyRepeat()
+{
+#ifdef _EVENT_LOGGING
+	LOGERR("HardwareKeyboard::KeyRepeat: %i\n", mLastKeyChar);
+#endif
+	if (mLastKeyChar)
+		PageManager::NotifyCharInput(mLastKeyChar);
+	else if (mLastKey)
+		PageManager::NotifyKey(mLastKey, true);
+	return 0;
+}
+
+void HardwareKeyboard::ConsumeKeyRelease(int key)
+{
+	// causes following KeyUp event to suppress notifications
+	mPressedKeys.erase(key);
+}
diff --git a/gui/image.cpp b/gui/image.cpp
new file mode 100644
index 0000000..84eac8c
--- /dev/null
+++ b/gui/image.cpp
@@ -0,0 +1,110 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// image.cpp - GUIImage object
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUIImage::GUIImage(xml_node<>* node) : GUIObject(node)
+{
+	mImage = NULL;
+	mHighlightImage = NULL;
+	isHighlighted = false;
+
+	if (!node)
+		return;
+
+	mImage = LoadAttrImage(FindNode(node, "image"), "resource");
+	mHighlightImage = LoadAttrImage(FindNode(node, "image"), "highlightresource");
+
+	// Load the placement
+	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, NULL, NULL, &mPlacement);
+
+	if (mImage && mImage->GetResource())
+	{
+		mRenderW = mImage->GetWidth();
+		mRenderH = mImage->GetHeight();
+
+		// Adjust for placement
+		if (mPlacement != TOP_LEFT && mPlacement != BOTTOM_LEFT)
+		{
+			if (mPlacement == CENTER)
+				mRenderX -= (mRenderW / 2);
+			else
+				mRenderX -= mRenderW;
+		}
+		if (mPlacement != TOP_LEFT && mPlacement != TOP_RIGHT)
+		{
+			if (mPlacement == CENTER)
+				mRenderY -= (mRenderH / 2);
+			else
+				mRenderY -= mRenderH;
+		}
+		SetPlacement(TOP_LEFT);
+	}
+}
+
+int GUIImage::Render(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	if (isHighlighted && mHighlightImage && mHighlightImage->GetResource()) {
+		gr_blit(mHighlightImage->GetResource(), 0, 0, mRenderW, mRenderH, mRenderX, mRenderY);
+		return 0;
+	}
+	else if (!mImage || !mImage->GetResource())
+		return -1;
+
+	gr_blit(mImage->GetResource(), 0, 0, mRenderW, mRenderH, mRenderX, mRenderY);
+	return 0;
+}
+
+int GUIImage::SetRenderPos(int x, int y, int w, int h)
+{
+	if (w || h)
+		return -1;
+
+	mRenderX = x;
+	mRenderY = y;
+	return 0;
+}
+
diff --git a/gui/include/gui/placement.h b/gui/include/gui/placement.h
new file mode 100644
index 0000000..02079e8
--- /dev/null
+++ b/gui/include/gui/placement.h
@@ -0,0 +1,32 @@
+/*
+	Copyright 2015 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __PLACEMENT_H
+#define __PLACEMENT_H
+
+enum Placement {
+	TOP_LEFT = 0,
+	TOP_RIGHT = 1,
+	BOTTOM_LEFT = 2,
+	BOTTOM_RIGHT = 3,
+	CENTER = 4,
+	CENTER_X_ONLY = 5,
+	TEXT_ONLY_RIGHT = 6,
+};
+
+#endif // __PLACEMENT_H
diff --git a/gui/input.cpp b/gui/input.cpp
new file mode 100755
index 0000000..a3c074a
--- /dev/null
+++ b/gui/input.cpp
@@ -0,0 +1,570 @@
+/*
+        Copyright 2012 to 2020 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// input.cpp - GUIInput object
+
+#include <linux/input.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+#include "minuitwrp/truetype.hpp"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+#include "../data.hpp"
+
+#define TW_INPUT_NO_UPDATE -1000 // Magic value for HandleTextLocation when no change in scrolling has occurred
+
+GUIInput::GUIInput(xml_node<>* node)
+	: GUIObject(node)
+{
+	xml_attribute<>* attr;
+	xml_node<>* child;
+
+	mInputText = NULL;
+	mAction = NULL;
+	mBackground = NULL;
+	mCursor = NULL;
+	mFont = NULL;
+	mRendered = false;
+	HasMask = false;
+	DrawCursor = false;
+	isLocalChange = true;
+	HasAllowed = false;
+	HasDisabled = false;
+	cursorX = textWidth = scrollingX = mFontHeight = mFontY = lastX = 0;
+	mBackgroundX = mBackgroundY = mBackgroundW = mBackgroundH = MinLen = MaxLen = 0;
+	mCursorLocation = -1; // -1 is always the end of the string
+	CursorWidth = 3;
+	ConvertStrToColor("black", &mBackgroundColor);
+	ConvertStrToColor("white", &mCursorColor);
+	mValue = "";
+	displayValue = "";
+
+	if (!node)
+		return;
+
+	// Load text directly from the node
+	mInputText = new GUIText(node);
+	// Load action directly from the node
+	mAction = new GUIAction(node);
+
+	if (mInputText->Render() < 0)
+	{
+		delete mInputText;
+		mInputText = NULL;
+	}
+
+	// Load the background
+	child = FindNode(node, "background");
+	if (child)
+	{
+		mBackground = LoadAttrImage(child, "resource");
+		mBackgroundColor = LoadAttrColor(child, "color", mBackgroundColor);
+	}
+	if (mBackground && mBackground->GetResource())
+	{
+		mBackgroundW = mBackground->GetWidth();
+		mBackgroundH = mBackground->GetHeight();
+	}
+
+	// Load the cursor color
+	child = FindNode(node, "cursor");
+	if (child)
+	{
+		mCursor = LoadAttrImage(child, "resource");
+		mCursorColor = LoadAttrColor(child, "color", mCursorColor);
+		attr = child->first_attribute("hasfocus");
+		if (attr)
+		{
+			std::string focus = attr->value();
+			SetInputFocus(atoi(focus.c_str()));
+		}
+		CursorWidth = LoadAttrIntScaleX(child, "width", CursorWidth);
+	}
+	DrawCursor = HasInputFocus;
+
+	// Load the font
+	child = FindNode(node, "font");
+	if (child)
+	{
+		mFont = LoadAttrFont(child, "resource");
+		if (mFont && mFont->GetResource())
+			mFontHeight = mFont->GetHeight();
+	}
+
+	child = FindNode(node, "data");
+	if (child)
+	{
+		attr = child->first_attribute("name");
+		if (attr)
+			mVariable = attr->value();
+		attr = child->first_attribute("default");
+		if (attr)
+			DataManager::SetValue(mVariable, attr->value());
+		mMask = LoadAttrString(child, "mask");
+		HasMask = !mMask.empty();
+	}
+
+	// Load input restrictions
+	child = FindNode(node, "restrict");
+	if (child)
+	{
+		MinLen = LoadAttrInt(child, "minlen", MinLen);
+		MaxLen = LoadAttrInt(child, "maxlen", MaxLen);
+		AllowedList = LoadAttrString(child, "allow");
+		HasAllowed = !AllowedList.empty();
+		DisabledList = LoadAttrString(child, "disable");
+		HasDisabled = !DisabledList.empty();
+	}
+
+	// Load the placement
+	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH);
+	SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+
+	if (mInputText && mFontHeight && mFontHeight < (unsigned)mRenderH) {
+		mFontY = ((mRenderH - mFontHeight) / 2) + mRenderY;
+		mInputText->SetRenderPos(mRenderX, mFontY);
+	} else
+		mFontY = mRenderY;
+
+	if (mInputText)
+		mInputText->SetMaxWidth(0);
+}
+
+GUIInput::~GUIInput()
+{
+	delete mInputText;
+	delete mAction;
+}
+
+void GUIInput::HandleTextLocation(int x) {
+	mRendered = false;
+	if (textWidth <= mRenderW) {
+		if (x != TW_INPUT_NO_UPDATE)
+			lastX = x;
+		scrollingX = 0;
+		return;
+	}
+	if (scrollingX + textWidth < mRenderW) {
+		scrollingX = mRenderW - textWidth;
+	}
+
+	if (x == TW_INPUT_NO_UPDATE)
+		return;
+
+	scrollingX += x - lastX;
+	if (scrollingX > 0)
+		scrollingX = 0;
+	else if (scrollingX + textWidth < mRenderW)
+		scrollingX = mRenderW - textWidth;
+	lastX = x;
+}
+
+void GUIInput::UpdateDisplayText() {
+	void* fontResource = NULL;
+
+	if (mFont) {
+		fontResource = mFont->GetResource();
+	} else {
+		textWidth = 0;
+		return;
+	}
+
+	DataManager::GetValue(mVariable, mValue);
+	if (HasMask) {
+		int index, string_size = mValue.size();
+		string maskedValue;
+		for (index=0; index<string_size; index++)
+			maskedValue += mMask;
+		displayValue = maskedValue;
+	} else {
+		displayValue = mValue;
+	}
+
+	textWidth = twrpTruetype::gr_ttf_measureEx(displayValue.c_str(), fontResource);
+}
+
+void GUIInput::HandleCursorByTouch(int x) {
+// Uses x to find mCursorLocation and cursorX
+	if (displayValue.size() == 0) {
+		mCursorLocation = -1;
+		cursorX = mRenderX;
+		return;
+	}
+
+	void* fontResource = NULL;
+	if (mFont) {
+		fontResource = mFont->GetResource();
+	} else {
+		return;
+	}
+
+	string cursorString;
+	unsigned index = 0, displaySize = displayValue.size();
+	int prevX = mRenderX + scrollingX;
+
+	for (index = 0; index <= displaySize; index++) {
+		cursorString = displayValue.substr(0, index);
+		cursorX = twrpTruetype::gr_ttf_measureEx(cursorString.c_str(), fontResource) + mRenderX + scrollingX;
+		if (cursorX > x) {
+			if (index > 0 && x <= cursorX - ((x - prevX) / 2) && prevX >= mRenderX) {
+				// This helps make sure that we can place the cursor before the very first char if the first char is
+				// is fully visible while also still letting us place the cursor after the last char if fully visible
+				mCursorLocation = index - 1;
+				cursorX = prevX;
+				return;
+			}
+			mCursorLocation = index;
+			if (cursorX > mRenderX + mRenderW) {
+				cursorX = prevX; // This makes sure that the cursor doesn't get placed after the end of the input box
+				mCursorLocation--;
+				return;
+			}
+			if (cursorX >= mRenderX) {
+				return; // This makes sure that the cursor doesn't get placed before the beginning of the input box
+			}
+		}
+		prevX = cursorX;
+	}
+	mCursorLocation = -1; // x is at or past the end of the string
+}
+
+void GUIInput::HandleCursorByText() {
+// Uses mCursorLocation to find cursorX
+	if (!DrawCursor)
+		return;
+
+	void* fontResource = NULL;
+	if (mFont) {
+		fontResource = mFont->GetResource();
+	} else {
+		return;
+	}
+
+	int cursorTextWidth = textWidth; // width of text to the left of the cursor
+
+	if (mCursorLocation != -1) {
+		string cursorDisplay = displayValue;
+		cursorDisplay.resize(mCursorLocation);
+		cursorTextWidth = twrpTruetype::gr_ttf_measureEx(cursorDisplay.c_str(), fontResource);
+	}
+	cursorX = mRenderX + cursorTextWidth + scrollingX;
+	if (cursorX >= mRenderX + mRenderW) {
+		scrollingX = mRenderW - cursorTextWidth;
+		cursorX = mRenderX + mRenderW - CursorWidth;
+	} else if (cursorX < mRenderX) {
+		scrollingX = cursorTextWidth * -1;
+		cursorX = mRenderX;
+	}
+}
+
+int GUIInput::Render(void)
+{
+	if (!isConditionTrue())
+	{
+		mRendered = false;
+		return 0;
+	}
+
+	// First step, fill background
+	gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, 255);
+	gr_fill(mRenderX, mRenderY, mRenderW, mRenderH);
+
+	// Next, render the background resource (if it exists)
+	if (mBackground && mBackground->GetResource())
+	{
+		mBackgroundX = mRenderX + ((mRenderW - mBackgroundW) / 2);
+		mBackgroundY = mRenderY + ((mRenderH - mBackgroundH) / 2);
+		gr_blit(mBackground->GetResource(), 0, 0, mBackgroundW, mBackgroundH, mBackgroundX, mBackgroundY);
+	}
+
+	int ret = 0;
+
+	// Render the text
+	if (mInputText) {
+		mInputText->SetRenderPos(mRenderX + scrollingX, mFontY);
+		mInputText->SetText(displayValue);
+		gr_clip(mRenderX, mRenderY, mRenderW, mRenderH);
+		ret = mInputText->Render();
+		gr_noclip();
+	}
+	if (ret < 0)
+		return ret;
+
+	if (HasInputFocus && DrawCursor) {
+		// Render the cursor
+		gr_color(mCursorColor.red, mCursorColor.green, mCursorColor.blue, 255);
+		gr_fill(cursorX, mFontY, CursorWidth, mFontHeight);
+	}
+
+	mRendered = true;
+	return ret;
+}
+
+int GUIInput::Update(void)
+{
+	if (!isConditionTrue())	 return (mRendered ? 2 : 0);
+	if (!mRendered)			 return 2;
+
+	int ret = 0;
+
+	if (mInputText)		 ret = mInputText->Update();
+	if (ret < 0)			return ret;
+
+	return ret;
+}
+
+int GUIInput::GetSelection(int x, int y)
+{
+	if (x < mRenderX || x - mRenderX > mRenderW || y < mRenderY || y - mRenderY > mRenderH) return -1;
+	return (x - mRenderX);
+}
+
+int GUIInput::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	static int startSelection = -1;
+
+	if (!isConditionTrue())
+		return -1;
+
+	if (!HasInputFocus) {
+		if (state != TOUCH_RELEASE)
+			return 0; // Only change focus if touch releases within the input box
+		if (GetSelection(x, y) >= 0) {
+			// When changing focus, we don't scroll or change the cursor location
+			PageManager::SetKeyBoardFocus(0);
+			PageManager::NotifyCharInput(0);
+			SetInputFocus(1);
+			DrawCursor = true;
+			mRendered = false;
+		}
+	} else {
+		switch (state) {
+		case TOUCH_HOLD:
+		case TOUCH_REPEAT:
+			break;
+		case TOUCH_START:
+			startSelection = GetSelection(x,y);
+			lastX = x;
+			DrawCursor = false;
+			mRendered = false;
+			break;
+
+		case TOUCH_DRAG:
+			// Check if we dragged out of the selection window
+			if (GetSelection(x, y) == -1) {
+				lastX = 0;
+				break;
+			}
+
+			DrawCursor = false;
+
+			// Provide some debounce on initial touches
+			if (startSelection != -1 && abs(x - lastX) < 6) {
+				break;
+			}
+
+			startSelection = -1;
+			if (lastX != x)
+				HandleTextLocation(x);
+			break;
+
+		case TOUCH_RELEASE:
+			// We've moved the cursor location
+			mRendered = false;
+			DrawCursor = true;
+			HandleCursorByTouch(x);
+			break;
+		}
+	}
+	return 0;
+}
+
+int GUIInput::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	GUIObject::NotifyVarChange(varName, value);
+
+	if (varName == mVariable) {
+		if (!isLocalChange) {
+			UpdateDisplayText();
+			HandleTextLocation(TW_INPUT_NO_UPDATE);
+		} else
+			isLocalChange = false;
+		return 0;
+	}
+	if (varName.empty()) {
+		UpdateDisplayText();
+		HandleTextLocation(TW_INPUT_NO_UPDATE);
+		HandleCursorByText();
+	}
+	return 0;
+}
+
+int GUIInput::NotifyKey(int key, bool down)
+{
+	if (!HasInputFocus || !down)
+		return 1;
+
+	switch (key)
+	{
+		case KEY_LEFT:
+			if (mCursorLocation == 0)
+				return 0; // we're already at the beginning
+			if (mCursorLocation == -1) {
+				if (displayValue.size() == 0) {
+					cursorX = mRenderX;
+					return 0;
+				}
+				mCursorLocation = displayValue.size() - 1;
+			} else {
+				mCursorLocation--;
+			}
+			mRendered = false;
+			HandleCursorByText();
+			return 0;
+
+		case KEY_RIGHT:
+			if (mCursorLocation == -1)
+				return 0; // we're already at the end
+			mCursorLocation++;
+			if ((int)displayValue.size() <= mCursorLocation)
+				mCursorLocation = -1;
+			HandleCursorByText();
+			mRendered = false;
+			return 0;
+
+		case KEY_HOME:
+		case KEY_UP:
+			if (displayValue.size() == 0)
+				return 0;
+			mCursorLocation = 0;
+			mRendered = false;
+			cursorX = mRenderX;
+			return 0;
+
+		case KEY_END:
+		case KEY_DOWN:
+			mCursorLocation = -1;
+			mRendered = false;
+			HandleCursorByText();
+			return 0;
+	}
+
+	return 1;
+}
+
+int GUIInput::NotifyCharInput(int key)
+{
+	if (HasInputFocus) {
+		if (key == KEYBOARD_BACKSPACE) {
+			//Backspace
+			if (mValue.size() > 0 && mCursorLocation != 0) {
+				if (mCursorLocation == -1) {
+					mValue.resize(mValue.size() - 1);
+				} else {
+					mValue.erase(mCursorLocation - 1, 1);
+					mCursorLocation--;
+				}
+				isLocalChange = true;
+				DataManager::SetValue(mVariable, mValue);
+				UpdateDisplayText();
+				HandleTextLocation(TW_INPUT_NO_UPDATE);
+				HandleCursorByText();
+			}
+		} else if (key == KEYBOARD_SWIPE_LEFT) {
+			// Delete all
+			isLocalChange = true;
+			if (mCursorLocation == -1) {
+				DataManager::SetValue (mVariable, "");
+				mValue = "";
+				textWidth = 0;
+				mCursorLocation = -1;
+			} else {
+				mValue.erase(0, mCursorLocation);
+				DataManager::SetValue(mVariable, mValue);
+				mCursorLocation = 0;
+			}
+			UpdateDisplayText();
+			cursorX = mRenderX;
+			scrollingX = 0;
+			mRendered = false;
+			return 0;
+		} else if (key >= 32) {
+			// Regular key
+			if (HasAllowed && AllowedList.find((char)key) == string::npos) {
+				return 0;
+			}
+			if (HasDisabled && DisabledList.find((char)key) != string::npos) {
+				return 0;
+			}
+			if (MaxLen != 0 && mValue.size() >= MaxLen) {
+				return 0;
+			}
+			if (mCursorLocation == -1) {
+				mValue += key;
+			} else {
+				mValue.insert(mCursorLocation, 1, key);
+				mCursorLocation++;
+			}
+			isLocalChange = true;
+			DataManager::SetValue(mVariable, mValue);
+			UpdateDisplayText();
+			HandleTextLocation(TW_INPUT_NO_UPDATE);
+			HandleCursorByText();
+		} else if (key == KEYBOARD_ACTION) {
+			// Action
+			if (mAction) {
+				unsigned inputLen = mValue.length();
+				if (inputLen < MinLen)
+					return 0;
+				else if (MaxLen != 0 && inputLen > MaxLen)
+					return 0;
+				else
+					return (mAction ? mAction->NotifyTouch(TOUCH_RELEASE, mRenderX, mRenderY) : 1);
+			}
+		}
+		return 0;
+	} else {
+		if (key == 0) {
+			// Somewhat ugly hack-ish way to tell the box to redraw after losing focus to remove the cursor
+			mRendered = false;
+			return 1;
+		}
+	}
+	return 1;
+}
diff --git a/gui/keyboard.cpp b/gui/keyboard.cpp
new file mode 100755
index 0000000..13fd779
--- /dev/null
+++ b/gui/keyboard.cpp
@@ -0,0 +1,642 @@
+/*
+        Copyright 2012 to 2020 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <linux/input.h>
+#include <stdlib.h>
+#include <string.h>
+#include "../data.hpp"
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+#include "gui.h"
+}
+#include "minuitwrp/minui.h"
+#include "minuitwrp/truetype.hpp"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+bool GUIKeyboard::CtrlActive = false;
+
+GUIKeyboard::GUIKeyboard(xml_node<>* node)
+	: GUIObject(node)
+{
+	int layoutindex, rowindex, keyindex, Xindex, Yindex, keyHeight = 0, keyWidth = 0;
+	currentKey = NULL;
+	highlightRenderCount = 0;
+	hasHighlight = hasCapsHighlight = hasCtrlHighlight = false;
+	char resource[10], layout[8], row[5], key[6], longpress[7];
+	xml_attribute<>* attr;
+	xml_node<>* child;
+	xml_node<>* keylayout;
+	xml_node<>* keyrow;
+
+	for (layoutindex=0; layoutindex<MAX_KEYBOARD_LAYOUTS; layoutindex++) {
+		layouts[layoutindex].keyboardImg = NULL;
+		memset(layouts[layoutindex].keys, 0, sizeof(Layout::keys));
+		memset(layouts[layoutindex].row_end_y, 0, sizeof(Layout::row_end_y));
+	}
+
+	mRendered = false;
+	currentLayout = 1;
+	CapsLockOn = false;
+
+	if (!node)  return;
+
+	mHighlightColor = LoadAttrColor(FindNode(node, "highlight"), "color", &hasHighlight);
+	mCapsHighlightColor = LoadAttrColor(FindNode(node, "capshighlight"), "color", &hasCapsHighlight);
+	mCtrlHighlightColor = LoadAttrColor(FindNode(node, "ctrlhighlight"), "color", &hasCtrlHighlight);
+
+	child = FindNode(node, "keymargin");
+	mKeyMarginX = LoadAttrIntScaleX(child, "x", 0);
+	mKeyMarginY = LoadAttrIntScaleY(child, "y", 0);
+
+	child = FindNode(node, "background");
+	mBackgroundColor = LoadAttrColor(child, "color", COLOR(32,32,32,255));
+
+	child = FindNode(node, "key-alphanumeric");
+	mFont = PageManager::GetResources()->FindFont(LoadAttrString(child, "font", "keylabel"));
+	mFontColor = LoadAttrColor(child, "textcolor", COLOR(255,255,255,255));
+	mKeyColorAlphanumeric = LoadAttrColor(child, "color", COLOR(0,0,0,0));
+
+	child = FindNode(node, "key-other");
+	mSmallFont = PageManager::GetResources()->FindFont(LoadAttrString(child, "font", "keylabel-small"));
+	mFontColorSmall = LoadAttrColor(child, "textcolor", COLOR(192,192,192,255));
+	mKeyColorOther = LoadAttrColor(child, "color", COLOR(0,0,0,0));
+
+	child = FindNode(node, "longpress");
+	mLongpressFont = PageManager::GetResources()->FindFont(LoadAttrString(child, "font", "keylabel-longpress"));
+	mLongpressFontColor = LoadAttrColor(child, "textcolor", COLOR(128,128,128,255));
+	LoadPlacement(child, &longpressOffsetX, &longpressOffsetY);
+
+	LoadKeyLabels(node, 0); // load global key labels
+
+	// compatibility ugliness: resources should be specified in the layouts themselves instead
+	// Load the images for the different layouts
+	child = FindNode(node, "layout");
+	if (child)
+	{
+		layoutindex = 1;
+		strcpy(resource, "resource1");
+		attr = child->first_attribute(resource);
+		while (attr && layoutindex < (MAX_KEYBOARD_LAYOUTS + 1)) {
+			layouts[layoutindex - 1].keyboardImg = LoadAttrImage(child, resource);
+
+			layoutindex++;
+			resource[8] = (char)(layoutindex + 48);
+			attr = child->first_attribute(resource);
+		}
+	}
+
+	// Check the first image to get height and width
+	if (layouts[0].keyboardImg && layouts[0].keyboardImg->GetResource())
+	{
+		mRenderW = layouts[0].keyboardImg->GetWidth();
+		mRenderH = layouts[0].keyboardImg->GetHeight();
+	}
+
+	// Load all of the layout maps
+	layoutindex = 1;
+	strcpy(layout, "layout1");
+	keylayout = FindNode(node, layout);
+	while (keylayout)
+	{
+		if (layoutindex > MAX_KEYBOARD_LAYOUTS) {
+			LOGERR("Too many layouts defined in keyboard.\n");
+			return;
+		}
+
+		LoadKeyLabels(keylayout, layoutindex); // load per-layout key labels
+
+		Layout& lay = layouts[layoutindex - 1];
+
+		child = keylayout->first_node("keysize");
+		keyHeight = LoadAttrIntScaleY(child, "height", 0);
+		keyWidth = LoadAttrIntScaleX(child, "width", 0);
+		// compatibility ugliness: capslock="0" means that this is the caps layout. Also it has nothing to do with keysize.
+		lay.is_caps = (LoadAttrInt(child, "capslock", 1) == 0);
+		// compatibility ugliness: revert_layout has nothing to do with keysize.
+		lay.revert_layout = LoadAttrInt(child, "revert_layout", -1);
+
+		rowindex = 1;
+		Yindex = 0;
+		strcpy(row, "row1");
+		keyrow = keylayout->first_node(row);
+		while (keyrow)
+		{
+			if (rowindex > MAX_KEYBOARD_ROWS) {
+				LOGERR("Too many rows defined in keyboard.\n");
+				return;
+			}
+
+			Yindex += keyHeight;
+			lay.row_end_y[rowindex - 1] = Yindex;
+
+			keyindex = 1;
+			Xindex = 0;
+			strcpy(key, "key01");
+			attr = keyrow->first_attribute(key);
+
+			while (attr) {
+				if (keyindex > MAX_KEYBOARD_KEYS) {
+					LOGERR("Too many keys defined in a keyboard row.\n");
+					return;
+				}
+
+				const char* keyinfo = attr->value();
+
+				if (strlen(keyinfo) == 0) {
+					LOGERR("No key info on layout%i, row%i, key%dd.\n", layoutindex, rowindex, keyindex);
+					return;
+				}
+
+				if (ParseKey(keyinfo, lay.keys[rowindex - 1][keyindex - 1], Xindex, keyWidth, false))
+					LOGERR("Invalid key info on layout%i, row%i, key%02i.\n", layoutindex, rowindex, keyindex);
+
+
+				// PROCESS LONG PRESS INFO IF EXISTS
+				sprintf(longpress, "long%02i", keyindex);
+				attr = keyrow->first_attribute(longpress);
+				if (attr) {
+					const char* keyinfo = attr->value();
+
+					if (strlen(keyinfo) == 0) {
+						LOGERR("No long press info on layout%i, row%i, long%dd.\n", layoutindex, rowindex, keyindex);
+						return;
+					}
+
+					if (ParseKey(keyinfo, lay.keys[rowindex - 1][keyindex - 1], Xindex, keyWidth, true))
+						LOGERR("Invalid long press key info on layout%i, row%i, long%02i.\n", layoutindex, rowindex, keyindex);
+				}
+				keyindex++;
+				sprintf(key, "key%02i", keyindex);
+				attr = keyrow->first_attribute(key);
+			}
+			rowindex++;
+			row[3] = (char)(rowindex + 48);
+			keyrow = keylayout->first_node(row);
+		}
+		layoutindex++;
+		layout[6] = (char)(layoutindex + 48);
+		keylayout = FindNode(node, layout);
+	}
+
+	int x, y;
+	// Load the placement
+	LoadPlacement(FindNode(node, "placement"), &x, &y, &mRenderW, &mRenderH);
+	SetRenderPos(x, y, mRenderW, mRenderH);
+	return;
+}
+
+GUIKeyboard::~GUIKeyboard()
+{
+}
+
+int GUIKeyboard::ParseKey(const char* keyinfo, Key& key, int& Xindex, int keyWidth, bool longpress)
+{
+	key.layout = 0;
+	int keychar = 0;
+	if (strlen(keyinfo) == 1) {
+		// This is a single key, simple definition
+		keychar = keyinfo[0];
+	} else {
+		// This key has extra data: {keywidth}:{what_the_key_does}
+		keyWidth = scale_theme_x(atoi(keyinfo));
+
+		const char* ptr = keyinfo;
+		while (*ptr > 32 && *ptr != ':')
+			ptr++;
+		if (*ptr != ':')
+			return -1;  // no colon is an error
+		ptr++;
+
+		if (*ptr == 0) {  // This is an empty area
+			keychar = 0;
+		} else if (strlen(ptr) == 1) {  // This is the character that this key uses
+			keychar = *ptr;
+		} else if (*ptr == 'c') {  // This is an ASCII character code: "c:{number}"
+			keychar = atoi(ptr + 2);
+		} else if (*ptr == 'k') {  // This is a Linux keycode from input.h: "k:{number}"
+			keychar = -atoi(ptr + 2);
+		} else if (*ptr == 'l') {  // This is a different layout: "layout{number}"
+			key.layout = atoi(ptr + 6);
+		} else if (*ptr == 'a') {  // This is an action: "action" (the Enter key)
+			keychar = KEYBOARD_ACTION;
+		} else
+			return -1;
+	}
+
+	if (longpress) {
+		key.longpresskey = keychar;
+	} else {
+		key.key = keychar;
+		Xindex += keyWidth;
+		key.end_x = Xindex - 1;
+	}
+
+	return 0;
+}
+
+void GUIKeyboard::LoadKeyLabels(xml_node<>* parent, int layout)
+{
+	for (xml_node<>* child = parent->first_node(); child; child = child->next_sibling()) {
+		std::string name = child->name();
+		if (name == "keylabel") {
+			std::string keydef = LoadAttrString(child, "key", "");
+			Key tempkey;
+			int dummyX;
+			if (ParseKey(keydef.c_str(), tempkey, dummyX, 0, false) == 0) {
+				KeyLabel keylabel;
+				keylabel.key = tempkey.key;
+				keylabel.layout_from = layout;
+				keylabel.layout_to = tempkey.layout;
+				keylabel.text = LoadAttrString(child, "text", "");
+				keylabel.image = LoadAttrImage(child, "resource");
+				mKeyLabels.push_back(keylabel);
+			} else {
+				LOGERR("Ignoring invalid keylabel in layout %d: '%s'.\n", layout, keydef.c_str());
+			}
+		}
+	}
+}
+
+void GUIKeyboard::DrawKey(Key& key, int keyX, int keyY, int keyW, int keyH)
+{
+	int keychar = key.key;
+	if (!keychar && !key.layout)
+		return;
+
+	// key background
+	COLOR& c = (keychar >= 32 && keychar < 127) ? mKeyColorAlphanumeric : mKeyColorOther;
+	gr_color(c.red, c.green, c.blue, c.alpha);
+	keyX += mKeyMarginX;
+	keyY += mKeyMarginY;
+	keyW -= mKeyMarginX * 2;
+	keyH -= mKeyMarginY * 2;
+	gr_fill(keyX, keyY, keyW, keyH);
+
+	// key label
+	FontResource* labelFont = mFont;
+	string labelText;
+	ImageResource* labelImage = NULL;
+	if (keychar > 32 && keychar < 127) {
+		// TODO: this will eventually need UTF-8 support
+		labelText = (char) keychar;
+		if (CtrlActive) {
+			int ctrlchar = KeyCharToCtrlChar(keychar);
+			if (ctrlchar != keychar)
+				labelText = std::string("^") + (char)(ctrlchar + 64);
+		}
+		gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
+	}
+	else {
+		// search for a special key label
+		for (std::vector<KeyLabel>::iterator it = mKeyLabels.begin(); it != mKeyLabels.end(); ++it) {
+			if (it->layout_from > 0 && it->layout_from != currentLayout)
+				continue; // this label is for another layout
+			if (it->key == key.key && it->layout_to == key.layout)
+			{
+				// found a label
+				labelText = it->text;
+				labelImage = it->image;
+				break;
+			}
+		}
+		labelFont = mSmallFont;
+		gr_color(mFontColorSmall.red, mFontColorSmall.green, mFontColorSmall.blue, mFontColorSmall.alpha);
+	}
+
+	if (labelImage && labelImage->GetResource())
+	{
+		int w = labelImage->GetWidth();
+		int h = labelImage->GetHeight();
+		int x = keyX + (keyW - w) / 2;
+		int y = keyY + (keyH - h) / 2;
+		gr_blit(labelImage->GetResource(), 0, 0, w, h, x, y);
+	}
+	else if (!labelText.empty() && labelFont && labelFont->GetResource())
+	{
+		void* fontResource = labelFont->GetResource();
+		int textW = twrpTruetype::gr_ttf_measureEx(labelText.c_str(), fontResource);
+		int textH = labelFont->GetHeight();
+		int textX = keyX + (keyW - textW) / 2;
+		int textY = keyY + (keyH - textH) / 2;
+		gr_textEx_scaleW(textX, textY, labelText.c_str(), fontResource, keyW, TOP_LEFT, 0);
+	}
+
+	// longpress key label (only if font is defined)
+	keychar = key.longpresskey;
+	if (keychar > 32 && keychar < 127 && mLongpressFont && mLongpressFont->GetResource()) {
+		void* fontResource = mLongpressFont->GetResource();
+		gr_color(mLongpressFontColor.red, mLongpressFontColor.green, mLongpressFontColor.blue, mLongpressFontColor.alpha);
+		string text(1, keychar);
+		int textW = twrpTruetype::gr_ttf_measureEx(text.c_str(), fontResource);
+		int textX = keyX + keyW - longpressOffsetX - textW;
+		int textY = keyY + longpressOffsetY;
+		gr_textEx_scaleW(textX, textY, text.c_str(), fontResource, keyW, TOP_LEFT, 0);
+	}
+}
+
+int GUIKeyboard::KeyCharToCtrlChar(int key)
+{
+	// convert upper and lower case to ctrl chars
+	// Ctrl+A to Ctrl+_ (we don't support entering null bytes)
+	if (key >= 65 && key <= 127 && key != 96)
+		return key & 0x1f;
+	return key; // pass on others (already ctrl chars, numbers, etc.) unchanged
+}
+
+int GUIKeyboard::Render(void)
+{
+	if (!isConditionTrue())
+	{
+		mRendered = false;
+		return 0;
+	}
+
+	Layout& lay = layouts[currentLayout - 1];
+
+	bool drawKeys = false;
+	if (lay.keyboardImg && lay.keyboardImg->GetResource())
+		// keyboard is image based
+		gr_blit(lay.keyboardImg->GetResource(), 0, 0, mRenderW, mRenderH, mRenderX, mRenderY);
+	else {
+		// keyboard is software drawn
+		// fill background
+		gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, mBackgroundColor.alpha);
+		gr_fill(mRenderX, mRenderY, mRenderW, mRenderH);
+		drawKeys = true;
+	}
+
+	// draw keys
+	int y1 = 0;
+	for (int row = 0; row < MAX_KEYBOARD_ROWS; ++row) {
+		int rowY = mRenderY + y1;
+		int rowH = lay.row_end_y[row] - y1;
+		y1 = lay.row_end_y[row];
+		int x1 = 0;
+		for (int col = 0; col < MAX_KEYBOARD_KEYS; ++col) {
+			Key& key = lay.keys[row][col];
+			int keyY = rowY;
+			int keyH = rowH;
+			int keyX = mRenderX + x1;
+			int keyW = key.end_x - x1;
+			x1 = key.end_x;
+
+			// Draw key for software drawn keyboard
+			if (drawKeys)
+				DrawKey(key, keyX, keyY, keyW, keyH);
+
+			// Draw highlight for capslock
+			if (hasCapsHighlight && lay.is_caps && CapsLockOn && key.layout > 0 && key.layout == lay.revert_layout) {
+				gr_color(mCapsHighlightColor.red, mCapsHighlightColor.green, mCapsHighlightColor.blue, mCapsHighlightColor.alpha);
+				gr_fill(keyX, keyY, keyW, keyH);
+			}
+
+			// Draw highlight for control
+			if (hasCtrlHighlight && key.key == -KEY_LEFTCTRL && CtrlActive) {
+				gr_color(mCtrlHighlightColor.red, mCtrlHighlightColor.green, mCtrlHighlightColor.blue, mCtrlHighlightColor.alpha);
+				gr_fill(keyX, keyY, keyW, keyH);
+			}
+
+			// Highlight current key
+			if (hasHighlight && &key == currentKey && highlightRenderCount != 0) {
+				gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha);
+				gr_fill(keyX, keyY, keyW, keyH);
+			}
+		}
+	}
+
+	if (!hasHighlight || highlightRenderCount == 0)
+		mRendered = true;
+	else if (highlightRenderCount > 0)
+		highlightRenderCount--;
+	return 0;
+}
+
+int GUIKeyboard::Update(void)
+{
+	if (!isConditionTrue())	 return (mRendered ? 2 : 0);
+	if (!mRendered)			 return 2;
+
+	return 0;
+}
+
+int GUIKeyboard::SetRenderPos(int x, int y, int w, int h)
+{
+	RenderObject::SetRenderPos(x, y, w, h);
+	SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+	return 0;
+}
+
+GUIKeyboard::Key* GUIKeyboard::HitTestKey(int x, int y)
+{
+	if (!IsInRegion(x, y))
+		return NULL;
+
+	int rely = y - mRenderY;
+	int relx = x - mRenderX;
+
+	Layout& lay = layouts[currentLayout - 1];
+
+	// Find the correct row
+	int row;
+	for (row = 0; row < MAX_KEYBOARD_ROWS; ++row) {
+		if (lay.row_end_y[row] > rely)
+			break;
+	}
+	if (row == MAX_KEYBOARD_ROWS)
+		return NULL;
+
+	// Find the correct key (column)
+	int col;
+	int x1 = 0;
+	for (col = 0; col < MAX_KEYBOARD_KEYS; ++col) {
+		Key& key = lay.keys[row][col];
+		if (x1 <= relx && relx < key.end_x && (key.key != 0 || key.layout != 0)) {
+			// This is the key that was pressed!
+			return &key;
+		}
+		x1 = key.end_x;
+	}
+	return NULL;
+}
+
+int GUIKeyboard::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	static int was_held = 0, startX = 0;
+
+	if (!isConditionTrue())	 return -1;
+
+	switch (state)
+	{
+	case TOUCH_START:
+		was_held = 0;
+		startX = x;
+		currentKey = HitTestKey(x, y);
+		if (currentKey)
+			highlightRenderCount = -1;
+		else
+			highlightRenderCount = 0;
+		mRendered = false;
+		break;
+
+	case TOUCH_DRAG:
+		break;
+
+	case TOUCH_RELEASE:
+		// TODO: we might want to notify of key releases here
+		if (x < startX - (mRenderW * 0.5)) {
+			if (highlightRenderCount != 0) {
+				highlightRenderCount = 0;
+				mRendered = false;
+			}
+			PageManager::NotifyCharInput(KEYBOARD_SWIPE_LEFT);
+			return 0;
+		} else if (x > startX + (mRenderW * 0.5)) {
+			if (highlightRenderCount != 0) {
+				highlightRenderCount = 0;
+				mRendered = false;
+			}
+			PageManager::NotifyCharInput(KEYBOARD_SWIPE_RIGHT);
+			return 0;
+		}
+		// fall through
+	case TOUCH_HOLD:
+	case TOUCH_REPEAT:
+		if (!currentKey) {
+			if (highlightRenderCount != 0) {
+				highlightRenderCount = 0;
+				mRendered = false;
+			}
+			return 0;
+		}
+
+		if (highlightRenderCount != 0) {
+			if (state == TOUCH_RELEASE)
+				highlightRenderCount = 2;
+			else
+				highlightRenderCount = -1;
+			mRendered = false;
+		}
+
+		if (HitTestKey(x, y) != currentKey) {
+			// We dragged off of the starting key
+			currentKey = NULL;
+			if (highlightRenderCount != 0) {
+				highlightRenderCount = 0;
+				mRendered = false;
+			}
+			return 0;
+		} else {
+			Key& key = *currentKey;
+			bool repeatKey = false;
+			Layout& lay = layouts[currentLayout - 1];
+			if (state == TOUCH_RELEASE && was_held == 0) {
+
+#ifndef TW_NO_HAPTICS
+				DataManager::Vibrate("tw_keyboard_vibrate");
+#endif
+
+				if (key.layout > 0) {
+					// Switch layouts
+					if (lay.is_caps && key.layout == lay.revert_layout && !CapsLockOn) {
+						CapsLockOn = true; // Set the caps lock
+					} else {
+						CapsLockOn = false; // Unset the caps lock and change layouts
+						currentLayout = key.layout;
+					}
+					mRendered = false;
+				} else if (key.key == KEYBOARD_ACTION) {
+					// Action
+					highlightRenderCount = 0;
+					// Send action notification
+					PageManager::NotifyCharInput(key.key);
+				} else if (key.key == -KEY_LEFTCTRL) {
+					CtrlActive = !CtrlActive; // toggle Control key state
+					mRendered = false; // render Ctrl key highlight
+				} else if (key.key != 0) {
+					// Regular key
+					if (key.key > 0) {
+						// ASCII code or character
+						int keycode = key.key;
+						if (CtrlActive) {
+							CtrlActive = false;
+							mRendered = false;
+							keycode = KeyCharToCtrlChar(key.key);
+						}
+						PageManager::NotifyCharInput(keycode);
+					} else {
+						// Linux key code
+						PageManager::NotifyKey(-key.key, true);
+						PageManager::NotifyKey(-key.key, false);
+					}
+					if (!CapsLockOn && lay.is_caps) {
+						// caps lock was not set, change layouts
+						currentLayout = lay.revert_layout;
+						mRendered = false;
+					}
+				}
+			} else if (state == TOUCH_HOLD) {
+				was_held = 1;
+				if (key.longpresskey > 0) {
+					// Long Press Key
+
+#ifndef TW_NO_HAPTICS
+					DataManager::Vibrate("tw_keyboard_vibrate");
+#endif
+
+					PageManager::NotifyCharInput(key.longpresskey);
+				}
+				else
+					repeatKey = true;
+			} else if (state == TOUCH_REPEAT) {
+				was_held = 1;
+				repeatKey = true;
+			}
+			if (repeatKey) {
+				if (key.key == KEYBOARD_BACKSPACE) {
+					// Repeat backspace
+					PageManager::NotifyCharInput(key.key);
+				}
+				switch (key.key)
+				{
+					// Repeat arrows
+					case -KEY_LEFT:
+					case -KEY_RIGHT:
+					case -KEY_UP:
+					case -KEY_DOWN:
+						PageManager::NotifyKey(-key.key, true);
+						PageManager::NotifyKey(-key.key, false);
+						break;
+				}
+			}
+		}
+		break;
+	}
+
+	return 0;
+}
+
+void GUIKeyboard::SetPageFocus(int inFocus)
+{
+	if (inFocus)
+		CtrlActive = false;
+}
diff --git a/gui/libguitwrp_defaults.go b/gui/libguitwrp_defaults.go
new file mode 100644
index 0000000..9d7dcef
--- /dev/null
+++ b/gui/libguitwrp_defaults.go
@@ -0,0 +1,384 @@
+package twrp
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+)
+
+func printThemeWarning(theme string) {
+	if theme == "" {
+		theme = "not set"
+	}
+	themeWarning := "***************************************************************************\n"
+	themeWarning += "Could not find ui.xml for TW_THEME: "
+	themeWarning += theme
+	themeWarning += "\nSet TARGET_SCREEN_WIDTH and TARGET_SCREEN_HEIGHT to automatically select\n"
+	themeWarning += "an appropriate theme, or set TW_THEME to one of the following:\n"
+	themeWarning += "landscape_hdpi landscape_mdpi portrait_hdpi portrait_mdpi watch_mdpi\n"
+	themeWarning += "****************************************************************************\n"
+	themeWarning += "(theme selection failed; exiting)\n"
+
+	fmt.Printf(themeWarning)
+}
+
+func printCustomThemeWarning(theme string, location string) {
+	customThemeWarning := "****************************************************************************\n"
+	customThemeWarning += "Could not find ui.xml for TW_CUSTOM_THEME: "
+	customThemeWarning += theme + "\n"
+	customThemeWarning += "Expected to find custom theme's ui.xml at: "
+	customThemeWarning += location
+	customThemeWarning += "Please fix this or set TW_THEME to one of the following:\n"
+	customThemeWarning += "landscape_hdpi landscape_mdpi portrait_hdpi portrait_mdpi watch_mdpi\n"
+	customThemeWarning += "****************************************************************************\n"
+	customThemeWarning += "(theme selection failed; exiting)\n"
+	fmt.Printf(customThemeWarning)
+}
+
+func copyThemeResources(ctx android.BaseContext, dirs []string, files []string) {
+	outDir := ctx.Config().Getenv("OUT")
+	twRes := outDir + "/recovery/root/twres/"
+	os.MkdirAll(twRes, os.ModePerm)
+	recoveryDir := getRecoveryAbsDir(ctx)
+	theme := determineTheme(ctx)
+	for idx, dir := range dirs {
+		_ = idx
+		dirToCopy := ""
+		destDir := twRes + path.Base(dir)
+		baseDir := path.Base(dir)
+		if baseDir == theme {
+			destDir = twRes
+			dirToCopy = recoveryDir + dir
+		} else {
+			dirToCopy = recoveryDir + dir
+		}
+		copyDir(dirToCopy, destDir)
+	}
+	for idx, file := range files {
+		_ = idx
+		fileToCopy := recoveryDir + file
+		fileDest := twRes + path.Base(file)
+		copyFile(fileToCopy, fileDest)
+	}
+	data, err := ioutil.ReadFile(recoveryDir + "variables.h")
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	version := "0"
+	for _, line := range strings.Split(string(data), "\n") {
+		if strings.Contains(line, "TW_THEME_VERSION") {
+			version = strings.Split(line, " ")[2]
+		}
+	}
+
+	_props := [3]string{"TW_CUSTOM_BATTERY_POS", "TW_CUSTOM_CPU_POS", "TW_CUSTOM_CLOCK_POS" }
+	props := [3]string{"0", "0", "0"}
+	for i, item := range _props {
+		if getMakeVars(ctx, item) != "" {
+			props[i] = strings.Trim(getMakeVars(ctx, item), "\"")
+		}
+	}
+
+	_files := [2]string{"splash.xml", "ui.xml"}
+	for _, i := range _files {
+		var fontsize int = 0
+		var width int = 0
+
+		data, err = ioutil.ReadFile(twRes + i)
+		if err != nil {
+			fmt.Println(err)
+			return
+		}
+
+		newFile := strings.Replace(string(data), "{themeversion}", version, -1)
+
+		// Custom position for status bar items - start
+		if i == "ui.xml" {
+
+			for _, line := range strings.Split(string(data), "\n") {
+		                if strings.Contains(line, "name=\"font_m\"") {
+		                      fontsize, err = strconv.Atoi(strings.Split(line, "\"")[5])
+		                       if err != nil {
+		                               fmt.Println(err)
+		                               return
+		                       }
+				}
+				if strings.Contains(line, "resolution") {
+					width, err = strconv.Atoi(strings.Split(line, "\"")[1])
+					if err != nil {
+						fmt.Println(err)
+						return
+					}
+				}
+		        }
+
+			var cpusize int = (fontsize * 5) + (width/100)
+			var clocksize int = (fontsize * 4) + (width/100)
+			var batterysize int = (fontsize * 6) - (width/100)
+			var pos_clock_24 string = props[2]
+			for j := 0; j < len(props); j++ {
+				if props[j] == "left" {
+					props[j] = strconv.Itoa(width/50)
+					if _props[j] == "TW_CUSTOM_CLOCK_POS" {
+						pos_clock_24 = props[j]
+					}
+				} else if props[j] == "center" {
+					if _props[j] == "TW_CUSTOM_BATTERY_POS" {
+						props[j] = strconv.Itoa( (width/2) - (batterysize*43/100) )
+					} else if _props[j] == "TW_CUSTOM_CLOCK_POS" {
+						pos := (width/2) - (clocksize*45/100)
+						props[j] = strconv.Itoa(pos)
+						pos_clock_24 = strconv.Itoa( pos * 31/30 )
+					} else if _props[j] == "TW_CUSTOM_CPU_POS" {
+						props[j] = strconv.Itoa( (width/2) - (cpusize*41/100) )
+					}
+				} else if props[j] == "right" {
+					if _props[j] == "TW_CUSTOM_BATTERY_POS" {
+						props[j] = strconv.Itoa( width - batterysize )
+					} else if _props[j] == "TW_CUSTOM_CLOCK_POS" {
+						props[j] = strconv.Itoa(width - clocksize)
+						pos_clock_24 = props[j]
+					} else if _props[j] == "TW_CUSTOM_CPU_POS" {
+						props[j] = strconv.Itoa( width - cpusize )
+					}
+				}
+			}
+
+			alignProp := "%status_topalign_header_y%"
+			if strings.Trim(getMakeVars(ctx, "TW_STATUS_ICONS_ALIGN"), "\"") == "center" || strings.Trim(getMakeVars(ctx, "TW_STATUS_ICONS_ALIGN"), "\"") == "2" {
+				alignProp = "%status_centeralign_header_y%"
+			} else if strings.Trim(getMakeVars(ctx, "TW_STATUS_ICONS_ALIGN"), "\"") == "bottom" || strings.Trim(getMakeVars(ctx, "TW_STATUS_ICONS_ALIGN"), "\"") == "3" {
+				alignProp = "%status_bottomalign_header_y%"
+			}
+			newFile = strings.Replace(newFile, "{battery_pos}", props[0], -1)
+			newFile = strings.Replace(newFile, "{cpu_pos}", props[1], -1)
+			newFile = strings.Replace(newFile, "{clock_12_pos}", props[2], -1)
+			newFile = strings.Replace(newFile, "{clock_24_pos}", pos_clock_24, -1)
+			newFile = strings.Replace(newFile, "{statusicons_align}", alignProp, -1)
+		}
+		// Custom position for status bar items - end
+
+		err = ioutil.WriteFile(twRes + i, []byte(newFile), 0)
+		if err != nil {
+			fmt.Println(err)
+			return
+		}
+	}
+}
+
+func copyCustomTheme(ctx android.BaseContext, customTheme string) {
+	outDir := ctx.Config().Getenv("OUT")
+	twRes := outDir + "/recovery/root/twres/"
+	os.MkdirAll(twRes, os.ModePerm)
+	fileDest := twRes + path.Base(customTheme)
+	fileToCopy := fmt.Sprintf("%s%s", getBuildAbsDir(ctx), customTheme)
+	copyFile(fileToCopy, fileDest)
+}
+
+func determineTheme(ctx android.BaseContext) string {
+	guiWidth := 0
+	guiHeight := 0
+	if getMakeVars(ctx, "TW_CUSTOM_THEME") == "" {
+		if getMakeVars(ctx, "TW_THEME") == "" {
+			if getMakeVars(ctx, "DEVICE_RESOLUTION") == "" {
+				width, err := strconv.Atoi(getMakeVars(ctx, "TARGET_SCREEN_WIDTH"))
+				if err == nil {
+					guiWidth = width
+				}
+				height, err := strconv.Atoi(getMakeVars(ctx, "TARGET_SCREEN_HEIGHT"))
+				if err == nil {
+					guiHeight = height
+				}
+			} else {
+				deviceRes := getMakeVars(ctx, "DEVICE_RESOLUTION")
+				width, err := strconv.Atoi(strings.Split(deviceRes, "x")[0])
+				if err == nil {
+					guiWidth = width
+				}
+				height, err := strconv.Atoi(strings.Split(deviceRes, "x")[1])
+				if err == nil {
+					guiHeight = height
+				}
+			}
+		}
+		if guiWidth > 100 {
+			if guiHeight > 100 {
+				if guiWidth > guiHeight {
+					if guiWidth > 1280 {
+						return "landscape_hdpi"
+					} else {
+						return "landscape_mdpi"
+					}
+				} else if guiWidth < guiHeight {
+					if guiWidth > 720 {
+						return "portrait_hdpi"
+					} else {
+						return "portrait_mdpi"
+					}
+				} else if guiWidth == guiHeight {
+					return "watch_mdpi"
+				}
+			}
+		}
+	}
+
+	return getMakeVars(ctx, "TW_THEME")
+}
+
+func copyTheme(ctx android.BaseContext) bool {
+	var directories []string
+	var files []string
+	var customThemeLoc string
+	localPath := ctx.ModuleDir()
+	directories = append(directories, "gui/theme/common/fonts/")
+	directories = append(directories, "gui/theme/common/languages/")
+	if getMakeVars(ctx, "TW_EXTRA_LANGUAGES") == "true" {
+		directories = append(directories, "gui/theme/extra-languages/fonts/")
+		directories = append(directories, "gui/theme/extra-languages/languages/")
+	}
+	var theme = determineTheme(ctx)
+	directories = append(directories, "gui/theme/"+theme)
+	themeXML := fmt.Sprintf("gui/theme/common/%s.xml", strings.Split(theme, "_")[0])
+	files = append(files, themeXML)
+	if getMakeVars(ctx, "TW_CUSTOM_THEME") == "" {
+		defaultTheme := fmt.Sprintf("%s/theme/%s/ui.xml", localPath, theme)
+		if android.ExistentPathForSource(ctx, defaultTheme).Valid() {
+			fullDefaultThemePath := fmt.Sprintf("gui/theme/%s/ui.xml", theme)
+			files = append(files, fullDefaultThemePath)
+		} else {
+			printThemeWarning(theme)
+			return false
+		}
+	} else {
+		customThemeLoc = getMakeVars(ctx, "TW_CUSTOM_THEME")
+		if android.ExistentPathForSource(ctx, customThemeLoc).Valid() {
+		} else {
+			printCustomThemeWarning(customThemeLoc, getMakeVars(ctx, "TW_CUSTOM_THEME"))
+			return false
+		}
+	}
+	copyThemeResources(ctx, directories, files)
+	if customThemeLoc != "" {
+		copyCustomTheme(ctx, customThemeLoc)
+	}
+	return true
+}
+
+func globalFlags(ctx android.BaseContext) []string {
+	var cflags []string
+
+	if getMakeVars(ctx, "TW_DELAY_TOUCH_INIT_MS") != "" {
+		cflags = append(cflags, "-DTW_DELAY_TOUCH_INIT_MS="+getMakeVars(ctx, "TW_DELAY_TOUCH_INIT_MS"))
+	}
+
+	if getMakeVars(ctx, "TW_EVENT_LOGGING") == "true" {
+		cflags = append(cflags, "-D_EVENT_LOGGING")
+	}
+
+	if getMakeVars(ctx, "TW_USE_KEY_CODE_TOUCH_SYNC") != "" {
+		cflags = append(cflags, "DTW_USE_KEY_CODE_TOUCH_SYNC="+getMakeVars(ctx, "TW_USE_KEY_CODE_TOUCH_SYNC"))
+	}
+
+	if getMakeVars(ctx, "TW_SCREEN_BLANK_ON_BOOT") != "" {
+		cflags = append(cflags, "-DTW_SCREEN_BLANK_ON_BOOT")
+	}
+
+	if getMakeVars(ctx, "TW_OZIP_DECRYPT_KEY") != "" {
+		cflags = append(cflags, "-DTW_OZIP_DECRYPT_KEY=\""+getMakeVars(ctx, "TW_OZIP_DECRYPT_KEY")+"\"")
+	} else {
+		cflags = append(cflags, "-DTW_OZIP_DECRYPT_KEY=0")
+	}
+
+	if getMakeVars(ctx, "TW_NO_SCREEN_BLANK") != "" {
+		cflags = append(cflags, "-DTW_NO_SCREEN_BLANK")
+	}
+
+	if getMakeVars(ctx, "TW_NO_SCREEN_TIMEOUT") != "" {
+		cflags = append(cflags, "-DTW_NO_SCREEN_TIMEOUT")
+	}
+
+	if getMakeVars(ctx, "TW_OEM_BUILD") != "" {
+		cflags = append(cflags, "-DTW_OEM_BUILD")
+	}
+
+	if getMakeVars(ctx, "TW_X_OFFSET") != "" {
+		cflags = append(cflags, "-DTW_X_OFFSET="+getMakeVars(ctx, "TW_X_OFFSET"))
+	}
+
+	if getMakeVars(ctx, "TW_Y_OFFSET") != "" {
+		cflags = append(cflags, "-DTW_Y_OFFSET="+getMakeVars(ctx, "TW_Y_OFFSET"))
+	}
+
+	if getMakeVars(ctx, "TW_W_OFFSET") != "" {
+		cflags = append(cflags, "-DTW_W_OFFSET="+getMakeVars(ctx, "TW_W_OFFSET"))
+	}
+
+	if getMakeVars(ctx, "TW_H_OFFSET") != "" {
+		cflags = append(cflags, "-DTW_H_OFFSET="+getMakeVars(ctx, "TW_H_OFFSET"))
+	}
+
+	if getMakeVars(ctx, "TW_ROUND_SCREEN") == "true" {
+		cflags = append(cflags, "-DTW_ROUND_SCREEN")
+	}
+
+	if getMakeVars(ctx, "TW_EXCLUDE_NANO") == "true" {
+		cflags = append(cflags, "-DTW_EXCLUDE_NANO")
+	}
+
+	if getMakeVars(ctx, "AB_OTA_UPDATER") == "true" {
+		cflags = append(cflags, "-DAB_OTA_UPDATER=1")
+	}
+
+	return cflags
+}
+
+func globalSrcs(ctx android.BaseContext) []string {
+	var srcs []string
+
+	if getMakeVars(ctx, "TWRP_CUSTOM_KEYBOARD") != "" {
+		srcs = append(srcs, getMakeVars(ctx, "TWRP_CUSTOM_KEYBOARD"))
+	} else {
+		srcs = append(srcs, "hardwarekeyboard.cpp")
+	}
+	return srcs
+}
+
+func libGuiDefaults(ctx android.LoadHookContext) {
+	type props struct {
+		Target struct {
+			Android struct {
+				Cflags  []string
+				Enabled *bool
+			}
+		}
+		Cflags       []string
+		Srcs         []string
+		Include_dirs []string
+	}
+
+	p := &props{}
+	p.Cflags = globalFlags(ctx)
+	s := globalSrcs(ctx)
+	p.Srcs = s
+	ctx.AppendProperties(p)
+	if copyTheme(ctx) == false {
+		os.Exit(-1)
+	}
+}
+
+func init() {
+	android.RegisterModuleType("libguitwrp_defaults", libGuiDefaultsFactory)
+}
+
+func libGuiDefaultsFactory() android.Module {
+	module := cc.DefaultsFactory()
+	android.AddLoadHook(module, libGuiDefaults)
+
+	return module
+}
diff --git a/gui/listbox.cpp b/gui/listbox.cpp
new file mode 100644
index 0000000..91cb7a7
--- /dev/null
+++ b/gui/listbox.cpp
@@ -0,0 +1,301 @@
+/*
+	Copyright 2013 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+#include "../data.hpp"
+#include "../partitions.hpp"
+#include "pages.hpp"
+
+extern std::vector<language_struct> Language_List;
+
+GUIListBox::GUIListBox(xml_node<>* node) : GUIScrollList(node)
+{
+	xml_attribute<>* attr;
+	xml_node<>* child;
+	mIconSelected = mIconUnselected = NULL;
+	mUpdate = 0;
+	isCheckList = isTextParsed = false;
+
+	// Get the icons, if any
+	child = FindNode(node, "icon");
+	if (child) {
+		mIconSelected = LoadAttrImage(child, "selected");
+		mIconUnselected = LoadAttrImage(child, "unselected");
+	}
+	int iconWidth = 0, iconHeight = 0;
+	if (mIconSelected && mIconSelected->GetResource() && mIconUnselected && mIconUnselected->GetResource()) {
+		iconWidth = std::max(mIconSelected->GetWidth(), mIconUnselected->GetWidth());
+		iconHeight = std::max(mIconSelected->GetHeight(), mIconUnselected->GetHeight());
+	} else if (mIconSelected && mIconSelected->GetResource()) {
+		iconWidth = mIconSelected->GetWidth();
+		iconHeight = mIconSelected->GetHeight();
+	} else if (mIconUnselected && mIconUnselected->GetResource()) {
+		iconWidth = mIconUnselected->GetWidth();
+		iconHeight = mIconUnselected->GetHeight();
+	}
+	SetMaxIconSize(iconWidth, iconHeight);
+
+	// Handle the result variable
+	child = FindNode(node, "data");
+	if (child) {
+		attr = child->first_attribute("name");
+		if (attr)
+			mVariable = attr->value();
+		attr = child->first_attribute("default");
+		if (attr)
+			DataManager::SetValue(mVariable, attr->value());
+		// Get the currently selected value for the list
+		DataManager::GetValue(mVariable, currentValue);
+		if (mVariable == "tw_language") {
+			std::vector<language_struct>::iterator iter;
+			for (iter = Language_List.begin(); iter != Language_List.end(); iter++) {
+				ListItem data;
+				data.displayName = (*iter).displayvalue;
+				data.variableValue = (*iter).filename;
+				data.action = NULL;
+				if (currentValue == (*iter).filename) {
+					data.selected = 1;
+					DataManager::SetValue("tw_language_display", (*iter).displayvalue);
+				} else
+					data.selected = 0;
+				mListItems.push_back(data);
+			}
+		} else if (mVariable == "tw_crypto_user_id") {
+			std::vector<users_struct>::iterator iter;
+			std::vector<users_struct>* Users_List = PartitionManager.Get_Users_List();
+			for (iter = Users_List->begin(); iter != Users_List->end(); iter++) {
+				if (!(*iter).isDecrypted) {
+					ListItem data;
+					data.displayName = (*iter).userName;
+					data.variableValue = (*iter).userId;
+					data.action = NULL;
+					DataManager::GetValue("tw_crypto_user_id", currentValue);
+					if (currentValue == (*iter).userId || currentValue == "") {
+						data.selected = 1;
+						DataManager::SetValue("tw_crypto_user_id", (*iter).userId);
+						DataManager::SetValue("tw_crypto_pwtype", (*iter).type);
+					} else
+						data.selected = 0;
+					mListItems.push_back(data);
+				}
+			}
+		}
+	} else
+		allowSelection = false;  // allows using listbox as a read-only list or menu
+
+	// Get the data for the list
+	child = FindNode(node, "listitem");
+	if (!child) return;
+	while (child) {
+		ListItem item;
+
+		attr = child->first_attribute("name");
+		if (!attr) continue;
+		// We will parse display names when we get page focus to ensure that translating takes place
+		item.displayName = attr->value();
+		item.variableValue = gui_parse_text(child->value());
+		item.selected = (child->value() == currentValue);
+		item.action = NULL;
+		xml_node<>* action = child->first_node("action");
+		if (!action) action = child->first_node("actions");
+		if (action) {
+			item.action = new GUIAction(child);
+			allowSelection = true;
+		}
+		xml_node<>* variable_name = child->first_node("data");
+		if (variable_name) {
+			attr = variable_name->first_attribute("variable");
+			if (attr) {
+				item.variableName = attr->value();
+				item.selected = (DataManager::GetIntValue(item.variableName) != 0);
+				allowSelection = true;
+				isCheckList = true;
+			}
+		}
+
+		LoadConditions(child, item.mConditions);
+
+		mListItems.push_back(item);
+		mVisibleItems.push_back(mListItems.size() - 1);
+
+		child = child->next_sibling("listitem");
+	}
+}
+
+GUIListBox::~GUIListBox()
+{
+}
+
+int GUIListBox::Update(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	if (mVariable == "tw_crypto_user_id") {
+		mListItems.clear();
+		std::vector<users_struct>::iterator iter;
+		std::vector<users_struct>* Users_List = PartitionManager.Get_Users_List();
+		for (iter = Users_List->begin(); iter != Users_List->end(); iter++) {
+			if (!(*iter).isDecrypted) {
+				ListItem data;
+				data.displayName = (*iter).userName;
+				data.variableValue = (*iter).userId;
+				data.action = NULL;
+				DataManager::GetValue("tw_crypto_user_id", currentValue);
+				if (currentValue == (*iter).userId || currentValue == "") {
+					data.selected = 1;
+					DataManager::SetValue("tw_crypto_user_id", (*iter).userId);
+					DataManager::SetValue("tw_crypto_pwtype", (*iter).type);
+				} else
+				data.selected = 0;
+				mListItems.push_back(data);
+			}
+		}
+		mVisibleItems.clear();
+		for (size_t i = 0; i < mListItems.size(); i++) {
+			mVisibleItems.push_back(i);
+		}
+		mUpdate = 1;
+	}
+
+	GUIScrollList::Update();
+
+	if (mUpdate) {
+		mUpdate = 0;
+		if (Render() == 0)
+			return 2;
+	}
+	return 0;
+}
+
+int GUIListBox::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	GUIScrollList::NotifyVarChange(varName, value);
+
+	if (!isConditionTrue())
+		return 0;
+
+	// Check to see if the variable that we are using to store the list selected value has been updated
+	if (varName == mVariable) {
+		currentValue = value;
+		mUpdate = 1;
+	}
+
+	std::vector<size_t> mVisibleItemsOld;
+	std::swap(mVisibleItemsOld, mVisibleItems);
+	for (size_t i = 0; i < mListItems.size(); i++) {
+		ListItem& item = mListItems[i];
+		// update per-item visibility condition
+		bool itemVisible = UpdateConditions(item.mConditions, varName);
+		if (itemVisible)
+			mVisibleItems.push_back(i);
+
+		if (isCheckList)
+		{
+			if (item.variableName == varName || varName.empty()) {
+				std::string val;
+				DataManager::GetValue(item.variableName, val);
+				item.selected = (val != "0");
+				mUpdate = 1;
+			}
+		}
+		else if (varName == mVariable) {
+			if (item.variableValue == currentValue) {
+				item.selected = 1;
+				SetVisibleListLocation(mVisibleItems.empty() ? 0 : mVisibleItems.size()-1);
+			} else {
+				item.selected = 0;
+			}
+		}
+	}
+
+	if (mVisibleItemsOld != mVisibleItems) {
+		mUpdate = 1; // some item's visibility has changed
+		if (firstDisplayedItem >= (int)mVisibleItems.size()) {
+			// all items in the view area were removed - make last item visible
+			SetVisibleListLocation(mVisibleItems.empty() ? 0 : mVisibleItems.size()-1);
+		}
+	}
+
+	return 0;
+}
+
+void GUIListBox::SetPageFocus(int inFocus)
+{
+	GUIScrollList::SetPageFocus(inFocus);
+	if (inFocus) {
+		if (!isTextParsed) {
+			isTextParsed = true;
+			for (size_t i = 0; i < mListItems.size(); i++) {
+				ListItem& item = mListItems[i];
+				item.displayName = gui_parse_text(item.displayName);
+			}
+		}
+		DataManager::GetValue(mVariable, currentValue);
+		NotifyVarChange(mVariable, currentValue);
+	}
+}
+
+size_t GUIListBox::GetItemCount()
+{
+	return mVisibleItems.size();
+}
+
+void GUIListBox::RenderItem(size_t itemindex, int yPos, bool selected)
+{
+	// note: the "selected" parameter above is for the currently touched item
+	// don't confuse it with the more persistent "selected" flag per list item used below
+	ListItem& item = mListItems[mVisibleItems[itemindex]];
+	ImageResource* icon = item.selected ? mIconSelected : mIconUnselected;
+	const std::string& text = item.displayName;
+
+	RenderStdItem(yPos, selected, icon, text.c_str());
+}
+
+void GUIListBox::NotifySelect(size_t item_selected)
+{
+	if (!isCheckList) {
+		// deselect all items, even invisible ones
+		for (size_t i = 0; i < mListItems.size(); i++) {
+			mListItems[i].selected = 0;
+		}
+	}
+
+	ListItem& item = mListItems[mVisibleItems[item_selected]];
+
+	if (isCheckList) {
+		int selected = 1 - item.selected;
+		item.selected = selected;
+		DataManager::SetValue(item.variableName, selected ? "1" : "0");
+	} else {
+		item.selected = 1;
+		string str = item.variableValue;	// [check] should this set currentValue instead?
+		DataManager::SetValue(mVariable, str);
+	}
+	if (item.action)
+		item.action->doActions();
+	mUpdate = 1;
+}
diff --git a/gui/mousecursor.cpp b/gui/mousecursor.cpp
new file mode 100644
index 0000000..8c0d438
--- /dev/null
+++ b/gui/mousecursor.cpp
@@ -0,0 +1,165 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+MouseCursor::MouseCursor(int resX, int resY)
+{
+	ResetData(resX, resY);
+}
+
+MouseCursor::~MouseCursor()
+{
+}
+
+void MouseCursor::ResetData(int resX, int resY)
+{
+	m_resX = resX;
+	m_resY = resY;
+	m_moved = false;
+	m_speedMultiplier = 2.5f;
+	m_image = NULL;
+	m_present = false;
+
+	ConvertStrToColor("red", &m_color);
+
+	SetRenderPos(resX/2, resY/2, 10, 10);
+}
+
+void MouseCursor::LoadData(xml_node<>* node)
+{
+	xml_attribute<>* attr;
+	xml_node<>* child;
+
+	child = FindNode(node, "placement");
+	if (child)
+		LoadPlacement(child, &mRenderX, &mRenderY, &mRenderW, &mRenderH);
+
+	child = FindNode(node, "background");
+	if (child)
+	{
+		m_color = LoadAttrColor(child, "color", m_color);
+		m_image = LoadAttrImage(child, "resource");
+		if (m_image && m_image->GetResource())
+		{
+			mRenderW = m_image->GetWidth();
+			mRenderH = m_image->GetHeight();
+		}
+	}
+
+	child = FindNode(node, "speed");
+	if (child)
+	{
+		attr = child->first_attribute("multiplier");
+		if (attr)
+			m_speedMultiplier = atof(attr->value());
+	}
+}
+
+int MouseCursor::Render(void)
+{
+	if (!m_present)
+		return 0;
+
+	if (m_image && m_image->GetResource())
+	{
+		gr_blit(m_image->GetResource(), 0, 0, mRenderW, mRenderH, mRenderX, mRenderY);
+	}
+	else
+	{
+		gr_color(m_color.red, m_color.green, m_color.blue, m_color.alpha);
+		gr_fill(mRenderX, mRenderY, mRenderW, mRenderH);
+	}
+	return 0;
+}
+
+int MouseCursor::Update(void)
+{
+	if (m_present != ev_has_mouse())
+	{
+		m_present = ev_has_mouse();
+		if (m_present)
+			SetRenderPos(m_resX/2, m_resY/2);
+		return 2;
+	}
+
+	if (m_present && m_moved)
+	{
+		m_moved = false;
+		return 2;
+	}
+	return 0;
+}
+
+int MouseCursor::SetRenderPos(int x, int y, int w, int h)
+{
+	if (x == mRenderX && y == mRenderY)
+		m_moved = true;
+
+	return RenderObject::SetRenderPos(x, y, w, h);
+}
+
+void MouseCursor::Move(int deltaX, int deltaY)
+{
+	if (deltaX != 0)
+	{
+		mRenderX += deltaX*m_speedMultiplier;
+		mRenderX = (std::min)(mRenderX, m_resX);
+		mRenderX = (std::max)(mRenderX, 0);
+
+		m_moved = true;
+	}
+
+	if (deltaY != 0)
+	{
+		mRenderY += deltaY*m_speedMultiplier;
+		mRenderY = (std::min)(mRenderY, m_resY);
+		mRenderY = (std::max)(mRenderY, 0);
+
+		m_moved = true;
+	}
+}
+
+void MouseCursor::GetPos(int& x, int& y)
+{
+	x = mRenderX;
+	y = mRenderY;
+}
diff --git a/gui/object.cpp b/gui/object.cpp
new file mode 100644
index 0000000..0cf2cce
--- /dev/null
+++ b/gui/object.cpp
@@ -0,0 +1,224 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// object.cpp - GUIObject base class
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+#include "../variables.h"
+}
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+#include "../data.hpp"
+
+GUIObject::GUIObject(xml_node<>* node)
+{
+	mConditionsResult = true;
+	if (node)
+		LoadConditions(node, mConditions);
+}
+
+void GUIObject::LoadConditions(xml_node<>* node, std::vector<Condition>& conditions)
+{
+	xml_node<>* condition = FindNode(node, "conditions");
+	if (condition)  condition = FindNode(condition, "condition");
+	else			condition = FindNode(node, "condition");
+
+	while (condition)
+	{
+		Condition cond;
+
+		cond.mCompareOp = "=";
+
+		xml_attribute<>* attr;
+		attr = condition->first_attribute("var1");
+		if (attr)   cond.mVar1 = attr->value();
+
+		attr = condition->first_attribute("op");
+		if (attr)   cond.mCompareOp = attr->value();
+
+		attr = condition->first_attribute("var2");
+		if (attr)   cond.mVar2 = attr->value();
+
+		conditions.push_back(cond);
+
+		condition = condition->next_sibling("condition");
+	}
+}
+
+GUIObject::~GUIObject()
+{
+}
+
+bool GUIObject::IsConditionVariable(std::string var)
+{
+	std::vector<Condition>::iterator iter;
+	for (iter = mConditions.begin(); iter != mConditions.end(); iter++)
+	{
+		if (iter->mVar1 == var)
+			return true;
+	}
+	return false;
+}
+
+bool GUIObject::isConditionTrue()
+{
+	return mConditionsResult;
+}
+
+bool GUIObject::isConditionTrue(Condition* condition)
+{
+	// This is used to hold the proper value of "true" based on the '!' NOT flag
+	bool bTrue = true;
+
+	if (condition->mVar1.empty())
+		return bTrue;
+
+	if (!condition->mCompareOp.empty() && condition->mCompareOp[0] == '!')
+		bTrue = false;
+
+	if (condition->mVar2.empty() && condition->mCompareOp != "modified")
+	{
+		if (!DataManager::GetStrValue(condition->mVar1).empty())
+			return bTrue;
+
+		return !bTrue;
+	}
+
+	string var1, var2;
+	if (DataManager::GetValue(condition->mVar1, var1))
+		var1 = condition->mVar1;
+	if (DataManager::GetValue(condition->mVar2, var2))
+		var2 = condition->mVar2;
+
+	if (var2.substr(0, 2) == "{@")
+		// translate resource string in value
+		var2 = gui_parse_text(var2);
+
+	// This is a special case, we stat the file and that determines our result
+	if (var1 == "fileexists")
+	{
+		struct stat st;
+		if (stat(var2.c_str(), &st) == 0)
+			var2 = var1;
+		else
+			var2 = "FAILED";
+	}
+	if (var1 == "mounted")
+	{
+		if (isMounted(condition->mVar2))
+			var2 = var1;
+		else
+			var2 = "FAILED";
+	}
+
+	if (condition->mCompareOp.find('=') != string::npos && var1 == var2)
+		return bTrue;
+
+	if (condition->mCompareOp.find('>') != string::npos && (atof(var1.c_str()) > atof(var2.c_str())))
+		return bTrue;
+
+	if (condition->mCompareOp.find('<') != string::npos && (atof(var1.c_str()) < atof(var2.c_str())))
+		return bTrue;
+
+	if (condition->mCompareOp == "modified")
+	{
+		// This is a hack to allow areas to reset the default value
+		if (var1.empty())
+		{
+			condition->mLastVal = var1;
+			return !bTrue;
+		}
+
+		if (var1 != condition->mLastVal)
+			return bTrue;
+	}
+
+	return !bTrue;
+}
+
+bool GUIObject::isConditionValid()
+{
+	return !mConditions.empty();
+}
+
+int GUIObject::NotifyVarChange(const std::string& varName, const std::string& value __unused)
+{
+	mConditionsResult = UpdateConditions(mConditions, varName);
+	return 0;
+}
+
+bool GUIObject::UpdateConditions(std::vector<Condition>& conditions, const std::string& varName)
+{
+	bool result = true;
+
+	const bool varNameEmpty = varName.empty();
+	std::vector<Condition>::iterator iter;
+	for (iter = conditions.begin(); iter != conditions.end(); ++iter)
+	{
+		if (varNameEmpty && iter->mCompareOp == "modified")
+		{
+			string val;
+
+			// If this fails, val will not be set, which is perfect
+			if (DataManager::GetValue(iter->mVar1, val))
+			{
+				DataManager::SetValue(iter->mVar1, "");
+				DataManager::GetValue(iter->mVar1, val);
+			}
+			iter->mLastVal = val;
+		}
+
+		if (varNameEmpty || iter->mVar1 == varName || iter->mVar2 == varName)
+			iter->mLastResult = isConditionTrue(&(*iter));
+
+		if (!iter->mLastResult)
+			result = false;
+	}
+	return result;
+}
+
+bool GUIObject::isMounted(string vol)
+{
+	FILE *fp;
+	char tmpOutput[255];
+
+	fp = fopen("/proc/mounts", "rt");
+	while (fgets(tmpOutput,255,fp) != NULL)
+	{
+		char* mnt = tmpOutput;
+		while (*mnt > 32)			mnt++;
+		while (*mnt > 0 && *mnt <= 32)	mnt++;
+		char* pos = mnt;
+		while (*pos > 32)   pos++;
+		*pos = 0;
+		if (vol == mnt)
+		{
+			fclose(fp);
+			return true;
+		}
+	}
+	fclose(fp);
+	return false;
+}
diff --git a/gui/objects.hpp b/gui/objects.hpp
new file mode 100755
index 0000000..ae1eeff
--- /dev/null
+++ b/gui/objects.hpp
@@ -0,0 +1,1260 @@
+/*
+	Copyright 2013 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// objects.hpp - Base classes for object manager of GUI
+
+#ifndef _OBJECTS_HEADER
+#define _OBJECTS_HEADER
+
+#include "rapidxml.hpp"
+#include <vector>
+#include <string>
+#include <map>
+#include <set>
+#include <time.h>
+
+using namespace rapidxml;
+
+#include "../data.hpp"
+#include "resources.hpp"
+#include "pages.hpp"
+#include "../partitions.hpp"
+#include "gui/placement.h"
+
+#ifndef TW_X_OFFSET
+#define TW_X_OFFSET 0
+#endif
+#ifndef TW_Y_OFFSET
+#define TW_Y_OFFSET 0
+#endif
+#ifndef TW_W_OFFSET
+#define TW_W_OFFSET 0
+#endif
+#ifndef TW_H_OFFSET
+#define TW_H_OFFSET 0
+#endif
+
+class RenderObject
+{
+public:
+	RenderObject() { mRenderX = 0; mRenderY = 0; mRenderW = 0; mRenderH = 0; mPlacement = TOP_LEFT; }
+	virtual ~RenderObject() {}
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void) = 0;
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void) { return 0; }
+
+	// GetRenderPos - Returns the current position of the object
+	virtual int GetRenderPos(int& x, int& y, int& w, int& h) { x = mRenderX; y = mRenderY; w = mRenderW; h = mRenderH; return 0; }
+
+	// SetRenderPos - Update the position of the object
+	//  Return 0 on success, <0 on error
+	virtual int SetRenderPos(int x, int y, int w = 0, int h = 0) { mRenderX = x; mRenderY = y; if (w || h) { mRenderW = w; mRenderH = h; } return 0; }
+
+	// GetPlacement - Returns the current placement
+	virtual int GetPlacement(Placement& placement) { placement = mPlacement; return 0; }
+
+	// SetPlacement - Update the current placement
+	virtual int SetPlacement(Placement placement) { mPlacement = placement; return 0; }
+
+	// SetPageFocus - Notify when a page gains or loses focus
+	// TODO: This should be named NotifyPageFocus for consistency
+	virtual void SetPageFocus(int inFocus __unused) { return; }
+
+protected:
+	int mRenderX, mRenderY, mRenderW, mRenderH;
+	Placement mPlacement;
+};
+
+class ActionObject
+{
+public:
+	ActionObject() { mActionX = 0; mActionY = 0; mActionW = 0; mActionH = 0; }
+	virtual ~ActionObject() {}
+
+public:
+	// NotifyTouch - Notify of a touch event
+	//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
+	virtual int NotifyTouch(TOUCH_STATE state __unused, int x __unused, int y __unused) { return 0; }
+
+	// NotifyKey - Notify of a key press
+	//  Return 0 on success (and consume key), >0 to pass key to next handler, and <0 on error
+	virtual int NotifyKey(int key __unused, bool down __unused) { return 1; }
+
+	virtual int GetActionPos(int& x, int& y, int& w, int& h) { x = mActionX; y = mActionY; w = mActionW; h = mActionH; return 0; }
+
+	//  Return 0 on success, <0 on error
+	virtual int SetActionPos(int x, int y, int w = 0, int h = 0);
+
+	// IsInRegion - Checks if the request is handled by this object
+	//  Return 1 if this object handles the request, 0 if not
+	virtual int IsInRegion(int x, int y) { return ((x < mActionX || x >= mActionX + mActionW || y < mActionY || y >= mActionY + mActionH) ? 0 : 1); }
+
+protected:
+	int mActionX, mActionY, mActionW, mActionH;
+};
+
+class GUIObject
+{
+public:
+	GUIObject(xml_node<>* node);
+	virtual ~GUIObject();
+
+public:
+	bool IsConditionVariable(std::string var);
+	bool isConditionTrue();
+	bool isConditionValid();
+
+	// NotifyVarChange - Notify of a variable change
+	//  Returns 0 on success, <0 on error
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+protected:
+	class Condition
+	{
+	public:
+		Condition() {
+			mLastResult = true;
+		}
+
+		std::string mVar1;
+		std::string mVar2;
+		std::string mCompareOp;
+		std::string mLastVal;
+		bool mLastResult;
+	};
+
+	std::vector<Condition> mConditions;
+
+protected:
+	static void LoadConditions(xml_node<>* node, std::vector<Condition>& conditions);
+	static bool isMounted(std::string vol);
+	static bool isConditionTrue(Condition* condition);
+	static bool UpdateConditions(std::vector<Condition>& conditions, const std::string& varName);
+
+	bool mConditionsResult;
+};
+
+class InputObject
+{
+public:
+	InputObject() { HasInputFocus = 0; }
+	virtual ~InputObject() {}
+
+public:
+	// NotifyCharInput - Notify of character input (usually from the onscreen or hardware keyboard)
+	//  Return 0 on success (and consume key), >0 to pass key to next handler, and <0 on error
+	virtual int NotifyCharInput(int ch __unused) { return 1; }
+
+	virtual int SetInputFocus(int focus) { HasInputFocus = focus; return 1; }
+
+protected:
+	int HasInputFocus;
+};
+
+// Derived Objects
+// GUIText - Used for static text
+class GUIText : public GUIObject, public RenderObject, public ActionObject
+{
+public:
+	// w and h may be ignored, in which case, no bounding box is applied
+	GUIText(xml_node<>* node);
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// Retrieve the size of the current string (dynamic strings may change per call)
+	virtual int GetCurrentBounds(int& w, int& h);
+
+	// Notify of a variable change
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+	// Set maximum width in pixels
+	virtual int SetMaxWidth(unsigned width);
+
+	void SetText(string newtext);
+
+public:
+	bool isHighlighted;
+	bool scaleWidth;
+	unsigned maxWidth;
+
+protected:
+	std::string mText;
+	std::string mLastValue;
+	COLOR mColor;
+	COLOR mHighlightColor;
+	FontResource* mFont;
+	int mIsStatic;
+	int mVarChanged;
+	int mFontHeight;
+};
+
+// GUIImage - Used for static image
+class GUIImage : public GUIObject, public RenderObject
+{
+public:
+	GUIImage(xml_node<>* node);
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// SetRenderPos - Update the position of the object
+	//  Return 0 on success, <0 on error
+	virtual int SetRenderPos(int x, int y, int w = 0, int h = 0);
+
+public:
+	bool isHighlighted;
+
+protected:
+	ImageResource* mImage;
+	ImageResource* mHighlightImage;
+};
+
+// GUIFill - Used for fill colors
+class GUIFill : public GUIObject, public RenderObject
+{
+public:
+	GUIFill(xml_node<>* node);
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+protected:
+	COLOR mColor;
+};
+
+// GUIAction - Used for standard actions
+class GUIAction : public GUIObject, public ActionObject
+{
+	friend class ActionThread;
+
+public:
+	GUIAction(xml_node<>* node);
+
+public:
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+	virtual int NotifyKey(int key, bool down);
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+	int doActions();
+
+protected:
+	class Action
+	{
+	public:
+		std::string mFunction;
+		std::string mArg;
+	};
+
+	std::vector<Action> mActions;
+	std::map<int, bool> mKeys;
+
+protected:
+	enum ThreadType { THREAD_NONE, THREAD_ACTION, THREAD_CANCEL };
+
+	int getKeyByName(std::string key);
+	int doAction(Action action);
+	ThreadType getThreadType(const Action& action);
+	void simulate_progress_bar(void);
+	int flash_zip(std::string filename, int* wipe_cache);
+	int ozip_decrypt(std::string zip_path);
+	void reinject_after_flash();
+	void operation_start(const string operation_name);
+	void operation_end(const int operation_status);
+	time_t Start;
+
+	// map action name to function pointer
+	typedef int (GUIAction::*execFunction)(std::string);
+	typedef std::map<std::string, execFunction> mapFunc;
+	static mapFunc mf;
+	static std::set<std::string> setActionsRunningInCallerThread;
+
+	// GUI actions
+	int reboot(std::string arg);
+	int home(std::string arg);
+	int key(std::string arg);
+	int page(std::string arg);
+	int reload(std::string arg);
+	int readBackup(std::string arg);
+	int set(std::string arg);
+	int clear(std::string arg);
+	int mount(std::string arg);
+	int unmount(std::string arg);
+	int restoredefaultsettings(std::string arg);
+	int copylog(std::string arg);
+	int compute(std::string arg);
+	int setguitimezone(std::string arg);
+	int overlay(std::string arg);
+	int queuezip(std::string arg);
+	int cancelzip(std::string arg);
+	int queueclear(std::string arg);
+	int sleep(std::string arg);
+	int sleepcounter(std::string arg);
+	int appenddatetobackupname(std::string arg);
+	int generatebackupname(std::string arg);
+	int checkpartitionlist(std::string arg);
+	int getpartitiondetails(std::string arg);
+	int screenshot(std::string arg);
+	int setbrightness(std::string arg);
+	int checkforapp(std::string arg);
+	int unmapsuperdevices(std::string arg);
+	int removedynamicgroups(std:: string arg);
+
+	// (originally) threaded actions
+	int fileexists(std::string arg);
+	int flash(std::string arg);
+	int wipe(std::string arg);
+	int refreshsizes(std::string arg);
+	int nandroid(std::string arg);
+	int fixcontexts(std::string arg);
+	int fixpermissions(std::string arg);
+	int dd(std::string arg);
+	int partitionsd(std::string arg);
+	int installhtcdumlock(std::string arg);
+	int htcdumlockrestoreboot(std::string arg);
+	int htcdumlockreflashrecovery(std::string arg);
+	int cmd(std::string arg);
+	int terminalcommand(std::string arg);
+	int killterminal(std::string arg);
+	int reinjecttwrp(std::string arg);
+	int checkbackupname(std::string arg);
+	int decrypt(std::string arg);
+	int adbsideload(std::string arg);
+	int adbsideloadcancel(std::string arg);
+	int openrecoveryscript(std::string arg);
+	int installsu(std::string arg);
+	int fixsu(std::string arg);
+	int decrypt_backup(std::string arg);
+	int repair(std::string arg);
+	int resize(std::string arg);
+	int changefilesystem(std::string arg);
+	int startmtp(std::string arg);
+	int stopmtp(std::string arg);
+	int flashimage(std::string arg);
+	int cancelbackup(std::string arg);
+	int checkpartitionlifetimewrites(std::string arg);
+	int mountsystemtoggle(std::string arg);
+	int setlanguage(std::string arg);
+	int togglebacklight(std::string arg);
+	int twcmd(std::string arg);
+	int setbootslot(std::string arg);
+	int installapp(std::string arg);
+	int uninstalltwrpsystemapp(std::string arg);
+	int repackimage(std::string arg);
+	int reflashtwrp(std::string arg);
+	int fixabrecoverybootloop(std::string arg);
+	int enableadb(std::string arg);
+	int enablefastboot(std::string arg);
+	int changeterminal(std::string arg);
+	int applycustomtwrpfolder(std::string arg);
+	int mergesnapshots(std::string arg);
+#ifndef TW_EXCLUDE_NANO
+	int editfile(std::string arg);
+#endif
+
+	int simulate;
+};
+
+class GUIButton : public GUIObject, public RenderObject, public ActionObject
+{
+public:
+	GUIButton(xml_node<>* node);
+	virtual ~GUIButton();
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// SetPos - Update the position of the render object
+	//  Return 0 on success, <0 on error
+	virtual int SetRenderPos(int x, int y, int w = 0, int h = 0);
+
+	// NotifyTouch - Notify of a touch event
+	//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+
+protected:
+	GUIImage* mButtonImg;
+	ImageResource* mButtonIcon;
+	GUIText* mButtonLabel;
+	GUIAction* mAction;
+	int mTextX, mTextY, mTextW, mTextH;
+	int mIconX, mIconY, mIconW, mIconH;
+	bool mRendered;
+	bool hasHighlightColor;
+	bool renderHighlight;
+	bool hasFill;
+	COLOR mFillColor;
+	COLOR mHighlightColor;
+	Placement TextPlacement;
+};
+
+class GUICheckbox: public GUIObject, public RenderObject, public ActionObject
+{
+public:
+	GUICheckbox(xml_node<>* node);
+	virtual ~GUICheckbox();
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// SetPos - Update the position of the render object
+	//  Return 0 on success, <0 on error
+	virtual int SetRenderPos(int x, int y, int w = 0, int h = 0);
+
+	// NotifyTouch - Notify of a touch event
+	//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+
+protected:
+	ImageResource* mChecked;
+	ImageResource* mUnchecked;
+	GUIText* mLabel;
+	int mTextX, mTextY;
+	int mCheckX, mCheckY, mCheckW, mCheckH;
+	int mLastState;
+	bool mRendered;
+	std::string mVarName;
+};
+
+class GUIScrollList : public GUIObject, public RenderObject, public ActionObject
+{
+public:
+	GUIScrollList(xml_node<>* node);
+	virtual ~GUIScrollList();
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// NotifyTouch - Notify of a touch event
+	//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+
+	// NotifyVarChange - Notify of a variable change
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+	// SetPos - Update the position of the render object
+	//  Return 0 on success, <0 on error
+	virtual int SetRenderPos(int x, int y, int w = 0, int h = 0);
+
+	// SetPageFocus - Notify when a page gains or loses focus
+	virtual void SetPageFocus(int inFocus);
+
+protected:
+	// derived classes need to implement these
+	// get number of items
+	virtual size_t GetItemCount() { return 0; }
+	// render a single item in rect (mRenderX, yPos, mRenderW, actualItemHeight)
+	virtual void RenderItem(size_t itemindex, int yPos, bool selected);
+	// an item was selected
+	virtual void NotifySelect(size_t item_selected __unused) {}
+
+	// render a standard-layout list item with optional icon and text
+	void RenderStdItem(int yPos, bool selected, ImageResource* icon, const char* text, int iconAndTextH = 0);
+
+	enum { NO_ITEM = (size_t)-1 };
+	// returns item index at coordinates or NO_ITEM if there is no item there
+	size_t HitTestItem(int x, int y);
+
+	// Called by the derived class to set the max icon size so we can calculate the proper actualItemHeight and center smaller icons if the icon size varies
+	void SetMaxIconSize(int w, int h);
+
+	// This will make sure that the item indicated by list_index is visible on the screen
+	void SetVisibleListLocation(size_t list_index);
+
+	// Handle scrolling changes for drags and kinetic scrolling
+	void HandleScrolling();
+
+	// Returns many full rows the list is capable of displaying
+	int GetDisplayItemCount();
+
+	// Returns the size in pixels of a partial item or row size
+	int GetDisplayRemainder();
+
+protected:
+	// Background
+	COLOR mBackgroundColor;
+	ImageResource* mBackground; // background image, if any, automatically centered
+
+	// Header
+	COLOR mHeaderBackgroundColor;
+	COLOR mHeaderFontColor;
+	std::string mHeaderText; // Original header text without parsing any variables
+	std::string mLastHeaderValue; // Header text after parsing variables
+	bool mHeaderIsStatic; // indicates if the header is static (no need to check for changes in NotifyVarChange)
+	int mHeaderH; // actual header height including font, icon, padding, and separator heights
+	ImageResource* mHeaderIcon;
+	int mHeaderIconHeight, mHeaderIconWidth; // width and height of the header icon if present
+	int mHeaderSeparatorH; // Height of the separator between header and list items
+	COLOR mHeaderSeparatorColor; // color of the header separator
+
+	// Per-item layout
+	FontResource* mFont;
+	COLOR mFontColor;
+	bool hasHighlightColor; // indicates if a highlight color was set
+	COLOR mHighlightColor; // background row highlight color
+	COLOR mFontHighlightColor;
+	int mFontHeight;
+	int actualItemHeight; // Actual height of each item in pixels including max icon size, font size, and padding
+	int maxIconWidth, maxIconHeight; // max icon width and height for the list, set by derived class in SetMaxIconSize
+	int mItemSpacing; // stores the spacing or padding on the y axis, part of the actualItemHeight
+	int mSeparatorH; // Height of the separator between items
+	COLOR mSeparatorColor; // color of the separator that is between items
+
+	// Scrollbar
+	int mFastScrollW; // width of the fastscroll area
+	int mFastScrollLineW; // width of the line for fastscroll rendering
+	int mFastScrollRectW; // width of the rectangle for fastscroll
+	int mFastScrollRectH; // minimum height of the rectangle for fastscroll
+	COLOR mFastScrollLineColor;
+	COLOR mFastScrollRectColor;
+
+	// Scrolling and dynamic state
+	int mFastScrollRectCurrentY; // current top of fastscroll rect relative to list top
+	int mFastScrollRectCurrentH; // current height of fastscroll rect
+	int mFastScrollRectTouchY; // offset from top of fastscroll rect where the user initially touched
+	bool hasScroll; // indicates that we have enough items in the list to scroll
+	int firstDisplayedItem; // this item goes at the top of the display list - may only be partially visible
+	int scrollingSpeed; // on a touch release, this is set based on the difference in the y-axis between the last 2 touches and indicates how fast the kinetic scrolling will go
+	int y_offset; // this is how many pixels offset in the y axis for per pixel scrolling, is always <= 0 and should never be < -actualItemHeight
+	bool allowSelection; // true if touched item can be selected, false for pure read-only lists and the console
+	size_t selectedItem; // selected item index after the initial touch, set to -1 if we are scrolling
+	int touchDebounce; // debounce for touches, minimum of 6 pixels but may be larger calculated based actualItemHeight / 3
+	int lastY, last2Y; // last 2 touch locations, used for tracking kinetic scroll speed
+	int fastScroll; // indicates that the inital touch was inside the fastscroll region - makes for easier fast scrolling as the touches don't have to stay within the fast scroll region and you drag your finger
+	int mUpdate; // indicates that a change took place and we need to re-render
+	bool AddLines(std::vector<std::string>* origText, std::vector<std::string>* origColor, size_t* lastCount, std::vector<std::string>* rText, std::vector<std::string>* rColor);
+};
+
+class GUIFileSelector : public GUIScrollList
+{
+public:
+	GUIFileSelector(xml_node<>* node);
+	virtual ~GUIFileSelector();
+
+public:
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// NotifyVarChange - Notify of a variable change
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+	// SetPageFocus - Notify when a page gains or loses focus
+	virtual void SetPageFocus(int inFocus);
+
+	virtual size_t GetItemCount();
+	virtual void RenderItem(size_t itemindex, int yPos, bool selected);
+	virtual void NotifySelect(size_t item_selected);
+
+protected:
+	struct FileData {
+		std::string fileName;
+		unsigned char fileType;	 // Uses d_type format from struct dirent
+		mode_t protection;		  // Uses mode_t format from stat
+		uid_t userId;
+		gid_t groupId;
+		off_t fileSize;
+		time_t lastAccess;		  // Uses time_t format from stat
+		time_t lastModified;		// Uses time_t format from stat
+		time_t lastStatChange;	  // Uses time_t format from stat
+	};
+
+protected:
+	virtual int GetFileList(const std::string folder);
+	static bool fileSort(FileData d1, FileData d2);
+
+protected:
+	std::vector<FileData> mFolderList;
+	std::vector<FileData> mFileList;
+	std::string mPathVar; // current path displayed, saved in the data manager
+	std::string mPathDefault; // default value for the path if none is set in mPathVar
+	std::string mExtn; // used for filtering the file list, for example, *.zip
+	std::string mPrfx; // used for filtering the file list, for example, Magisk-
+	std::string mVariable; // set when the user selects an item, pull path like /path/to/foo
+	std::string mSortVariable; // data manager variable used to change the sorting of files
+	std::string mSelection; // set when the user selects an item without the full path like selecting /path/to/foo would just be set to foo
+	int mShowFolders, mShowFiles; // indicates if the list should show folders and/or files
+	int mShowNavFolders; // indicates if the list should include the "up a level" item and allow you to traverse folders (nav folders are disabled for the restore list, for instance)
+	static int mSortOrder; // must be static because it is used by the static function fileSort
+	ImageResource* mFolderIcon;
+	ImageResource* mFileIcon;
+	bool updateFileList;
+};
+
+class GUIListBox : public GUIScrollList
+{
+public:
+	GUIListBox(xml_node<>* node);
+	virtual ~GUIListBox();
+
+public:
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// NotifyVarChange - Notify of a variable change
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+	// SetPageFocus - Notify when a page gains or loses focus
+	virtual void SetPageFocus(int inFocus);
+
+	virtual size_t GetItemCount();
+	virtual void RenderItem(size_t itemindex, int yPos, bool selected);
+	virtual void NotifySelect(size_t item_selected);
+
+protected:
+	struct ListItem {
+		std::string displayName;
+		std::string variableName;
+		std::string variableValue;
+		unsigned int selected;
+		GUIAction* action;
+		std::vector<Condition> mConditions;
+	};
+
+protected:
+	std::vector<ListItem> mListItems;
+	std::vector<size_t> mVisibleItems; // contains indexes in mListItems of visible items only
+	std::string mVariable;
+	std::string currentValue;
+	ImageResource* mIconSelected;
+	ImageResource* mIconUnselected;
+	bool isCheckList;
+	bool isTextParsed;
+};
+
+class GUIPartitionList : public GUIScrollList
+{
+public:
+	GUIPartitionList(xml_node<>* node);
+	virtual ~GUIPartitionList();
+
+public:
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update();
+
+	// NotifyVarChange - Notify of a variable change
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+	// SetPageFocus - Notify when a page gains or loses focus
+	virtual void SetPageFocus(int inFocus);
+
+	virtual size_t GetItemCount();
+	virtual void RenderItem(size_t itemindex, int yPos, bool selected);
+	virtual void NotifySelect(size_t item_selected);
+
+protected:
+	void MatchList();
+	void SetPosition();
+
+protected:
+	std::vector<PartitionList> mList;
+	std::string ListType;
+	std::string mVariable;
+	std::string selectedList;
+	std::string currentValue;
+	std::string mLastValue;
+	ImageResource* mIconSelected;
+	ImageResource* mIconUnselected;
+	bool updateList;
+};
+
+class GUITextBox : public GUIScrollList
+{
+public:
+	GUITextBox(xml_node<>* node);
+
+public:
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// NotifyVarChange - Notify of a variable change
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+	// ScrollList interface
+	virtual size_t GetItemCount();
+	virtual void RenderItem(size_t itemindex, int yPos, bool selected);
+	virtual void NotifySelect(size_t item_selected);
+protected:
+
+	size_t mLastCount;
+	bool mIsStatic;
+	std::vector<std::string> mLastValue; // Parsed text - parsed for variables but not word wrapped
+	std::vector<std::string> mText;      // Original text - not parsed for variables and not word wrapped
+	std::vector<std::string> rText;      // Rendered text - what we actually see
+
+};
+
+class GUIConsole : public GUIScrollList
+{
+public:
+	GUIConsole(xml_node<>* node);
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// IsInRegion - Checks if the request is handled by this object
+	//  Return 1 if this object handles the request, 0 if not
+	virtual int IsInRegion(int x, int y);
+
+	// NotifyTouch - Notify of a touch event
+	//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error (Return error to allow other handlers)
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+
+	// ScrollList interface
+	virtual size_t GetItemCount();
+	virtual void RenderItem(size_t itemindex, int yPos, bool selected);
+	virtual void NotifySelect(size_t item_selected);
+
+	static void Translate_Now();
+	static void Clear_For_Retranslation();
+protected:
+	enum SlideoutState
+	{
+		hidden = 0,
+		visible,
+		request_hide,
+		request_show
+	};
+
+	ImageResource* mSlideoutImage;
+	size_t mLastCount; // lines from gConsole that are already split and copied into rConsole
+	bool scrollToEnd; // true if we want to keep tracking the last line
+	int mSlideoutX, mSlideoutY, mSlideoutW, mSlideoutH;
+	int mSlideout;
+	SlideoutState mSlideoutState;
+	std::vector<std::string> rConsole;
+	std::vector<std::string> rConsoleColor;
+
+protected:
+	int RenderSlideout(void);
+	int RenderConsole(void);
+};
+
+class TerminalEngine;
+class GUITerminal : public GUIScrollList, public InputObject
+{
+public:
+	GUITerminal(xml_node<>* node);
+
+public:
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// NotifyTouch - Notify of a touch event
+	//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error (Return error to allow other handlers)
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+
+	// NotifyKey - Notify of a key press
+	//  Return 0 on success (and consume key), >0 to pass key to next handler, and <0 on error
+	virtual int NotifyKey(int key, bool down);
+
+	// character input
+	virtual int NotifyCharInput(int ch);
+
+	// SetPageFocus - Notify when a page gains or loses focus
+	virtual void SetPageFocus(int inFocus);
+
+	// ScrollList interface
+	virtual size_t GetItemCount();
+	virtual void RenderItem(size_t itemindex, int yPos, bool selected);
+	virtual void NotifySelect(size_t item_selected);
+	bool status();
+	void stop();
+protected:
+	void InitAndResize();
+
+	TerminalEngine* engine; // non-visual parts of the terminal (text buffer etc.), not owned
+	int updateCounter; // to track if anything changed in the back-end
+	bool lastCondition; // to track if the condition became true and we might need to resize the terminal engine
+};
+
+// GUIAnimation - Used for animations
+class GUIAnimation : public GUIObject, public RenderObject
+{
+public:
+	GUIAnimation(xml_node<>* node);
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+protected:
+	AnimationResource* mAnimation;
+	int mFrame;
+	int mFPS;
+	int mLoop;
+	int mRender;
+	int mUpdateCount;
+};
+
+class GUIProgressBar : public GUIObject, public RenderObject, public ActionObject
+{
+public:
+	GUIProgressBar(xml_node<>* node);
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// NotifyVarChange - Notify of a variable change
+	//  Returns 0 on success, <0 on error
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+protected:
+	ImageResource* mEmptyBar;
+	ImageResource* mFullBar;
+	std::string mMinValVar;
+	std::string mMaxValVar;
+	std::string mCurValVar;
+	float mSlide;
+	float mSlideInc;
+	int mSlideFrames;
+	int mLastPos;
+
+protected:
+	virtual int RenderInternal(void);	   // Does the actual render
+};
+
+class GUISlider : public GUIObject, public RenderObject, public ActionObject
+{
+public:
+	GUISlider(xml_node<>* node);
+	virtual ~GUISlider();
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// NotifyTouch - Notify of a touch event
+	//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+
+protected:
+	GUIAction* sAction;
+	GUIText* sSliderLabel;
+	ImageResource* sSlider;
+	ImageResource* sSliderUsed;
+	ImageResource* sTouch;
+	int sTouchW, sTouchH;
+	int sCurTouchX;
+	int sUpdate;
+};
+
+// these are ASCII codes reported via NotifyCharInput
+// other special keys (arrows etc.) are reported via NotifyKey
+#define KEYBOARD_ACTION 13	// CR
+#define KEYBOARD_BACKSPACE 8	// Backspace
+#define KEYBOARD_TAB 9		// Tab
+#define KEYBOARD_SWIPE_LEFT 21	// Ctrl+U to delete line, same as in readline (used by shell etc.)
+#define KEYBOARD_SWIPE_RIGHT 11	// Ctrl+K, same as in readline
+
+class GUIKeyboard : public GUIObject, public RenderObject, public ActionObject
+{
+public:
+	GUIKeyboard(xml_node<>* node);
+	virtual ~GUIKeyboard();
+
+public:
+	virtual int Render(void);
+	virtual int Update(void);
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+	virtual int SetRenderPos(int x, int y, int w = 0, int h = 0);
+	virtual void SetPageFocus(int inFocus);
+
+protected:
+	struct Key
+	{
+		int key; // positive: ASCII/Unicode code; negative: Linux key code (KEY_*)
+		int longpresskey;
+		int end_x;
+		int layout;
+	};
+	int ParseKey(const char* keyinfo, Key& key, int& Xindex, int keyWidth, bool longpress);
+	void LoadKeyLabels(xml_node<>* parent, int layout);
+	void DrawKey(Key& key, int keyX, int keyY, int keyW, int keyH);
+	int KeyCharToCtrlChar(int key);
+
+	enum {
+		MAX_KEYBOARD_LAYOUTS = 5,
+		MAX_KEYBOARD_ROWS = 9,
+		MAX_KEYBOARD_KEYS = 20
+	};
+	struct Layout
+	{
+		ImageResource* keyboardImg;
+		Key keys[MAX_KEYBOARD_ROWS][MAX_KEYBOARD_KEYS];
+		int row_end_y[MAX_KEYBOARD_ROWS];
+		bool is_caps;
+		int revert_layout;
+	};
+	Layout layouts[MAX_KEYBOARD_LAYOUTS];
+
+	struct KeyLabel
+	{
+		int key; // same as in struct Key
+		int layout_from; // 1-based; 0 for labels that apply to all layouts
+		int layout_to; // same as Key.layout
+		string text; // key label text
+		ImageResource* image; // image (overrides text if defined)
+	};
+	std::vector<KeyLabel> mKeyLabels;
+
+	// Find key at screen coordinates
+	Key* HitTestKey(int x, int y);
+
+	bool mRendered;
+	std::string mVariable;
+	int currentLayout;
+	bool CapsLockOn;
+	static bool CtrlActive; // all keyboards share a common Control key state so that the Control key can be on a separate keyboard instance
+	int highlightRenderCount;
+	Key* currentKey;
+	bool hasHighlight, hasCapsHighlight, hasCtrlHighlight;
+	COLOR mHighlightColor;
+	COLOR mCapsHighlightColor;
+	COLOR mCtrlHighlightColor;
+	COLOR mFontColor; // for centered key labels
+	COLOR mFontColorSmall; // for centered key labels
+	FontResource* mFont; // for main key labels
+	FontResource* mSmallFont; // for key labels like "?123"
+	FontResource* mLongpressFont; // for the small longpress label in the upper right corner
+	int longpressOffsetX, longpressOffsetY; // distance of the longpress label from the key corner
+	COLOR mLongpressFontColor;
+	COLOR mBackgroundColor; // keyboard background color
+	COLOR mKeyColorAlphanumeric; // key background color
+	COLOR mKeyColorOther; // key background color
+	int mKeyMarginX, mKeyMarginY; // space around key boxes - applied to left/right and top/bottom
+};
+
+// GUIInput - Used for keyboard input
+class GUIInput : public GUIObject, public RenderObject, public ActionObject, public InputObject
+{
+public:
+	GUIInput(xml_node<>* node);
+	virtual ~GUIInput();
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// Notify of a variable change
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+	// NotifyTouch - Notify of a touch event
+	//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+
+	virtual int NotifyKey(int key, bool down);
+	virtual int NotifyCharInput(int ch);
+
+protected:
+	virtual int GetSelection(int x, int y);
+
+	// Handles displaying the text properly when chars are added, deleted, or for scrolling
+	void HandleTextLocation(int x);
+	void UpdateDisplayText();
+	void HandleCursorByTouch(int x);
+	void HandleCursorByText();
+
+protected:
+	GUIText* mInputText;
+	GUIAction* mAction;
+	ImageResource* mBackground;
+	ImageResource* mCursor;
+	FontResource* mFont;
+	std::string mVariable;
+	std::string mMask;
+	std::string mValue;
+	std::string displayValue;
+	COLOR mBackgroundColor;
+	COLOR mCursorColor;
+	int scrollingX;
+	int cursorX;     // actual x axis location of the cursor
+	int lastX;
+	int mCursorLocation;
+	int mBackgroundX, mBackgroundY, mBackgroundW, mBackgroundH;
+	int mFontY;
+	int textWidth;
+	unsigned mFontHeight;
+	unsigned CursorWidth;
+	bool mRendered;
+	bool HasMask;
+	bool DrawCursor;
+	bool isLocalChange;
+	bool HasAllowed;
+	bool HasDisabled;
+	std::string AllowedList;
+	std::string DisabledList;
+	unsigned MinLen;
+	unsigned MaxLen;
+};
+
+class HardwareKeyboard
+{
+public:
+	HardwareKeyboard();
+	virtual ~HardwareKeyboard();
+
+public:
+	// called by the input handler for key events
+	int KeyDown(int key_code);
+	int KeyUp(int key_code);
+
+	// called by the input handler when holding a key down
+	int KeyRepeat();
+
+	// called by multi-key actions to suppress key-release notifications
+	void ConsumeKeyRelease(int key);
+
+	bool IsKeyDown(int key_code);
+private:
+	int mLastKey;
+	int mLastKeyChar;
+	std::set<int> mPressedKeys;
+};
+
+class GUISliderValue: public GUIObject, public RenderObject, public ActionObject
+{
+public:
+	GUISliderValue(xml_node<>* node);
+	virtual ~GUISliderValue();
+
+public:
+	// Render - Render the full object to the GL surface
+	//  Return 0 on success, <0 on error
+	virtual int Render(void);
+
+	// Update - Update any UI component animations (called <= 30 FPS)
+	//  Return 0 if nothing to update, 1 on success and contiue, >1 if full render required, and <0 on error
+	virtual int Update(void);
+
+	// SetPos - Update the position of the render object
+	//  Return 0 on success, <0 on error
+	virtual int SetRenderPos(int x, int y, int w = 0, int h = 0);
+
+	// NotifyTouch - Notify of a touch event
+	//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+
+	// Notify of a variable change
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+
+	// SetPageFocus - Notify when a page gains or loses focus
+	virtual void SetPageFocus(int inFocus);
+
+protected:
+	int measureText(const std::string& str);
+	int valueFromPct(float pct);
+	float pctFromValue(int value);
+	void loadValue(bool force = false);
+
+	std::string mVariable;
+	int mMax;
+	int mMin;
+	int mValue;
+	char *mValueStr;
+	float mValuePct;
+	std::string mMaxStr;
+	std::string mMinStr;
+	FontResource *mFont;
+	GUIText* mLabel;
+	int mLabelW;
+	COLOR mTextColor;
+	COLOR mLineColor;
+	COLOR mSliderColor;
+	bool mShowRange;
+	bool mShowCurr;
+	int mLineX;
+	int mLineY;
+	int mLineH;
+	int mLinePadding;
+	int mPadding;
+	int mSliderY;
+	int mSliderW;
+	int mSliderH;
+	bool mRendered;
+	int mFontHeight;
+	GUIAction *mAction;
+	bool mChangeOnDrag;
+	int mLineW;
+	bool mDragging;
+	ImageResource *mBackgroundImage;
+	ImageResource *mHandleImage;
+	ImageResource *mHandleHoverImage;
+};
+
+class MouseCursor : public RenderObject
+{
+public:
+	MouseCursor(int posX, int posY);
+	virtual ~MouseCursor();
+
+	virtual int Render(void);
+	virtual int Update(void);
+	virtual int SetRenderPos(int x, int y, int w = 0, int h = 0);
+
+	void Move(int deltaX, int deltaY);
+	void GetPos(int& x, int& y);
+	void LoadData(xml_node<>* node);
+	void ResetData(int resX, int resY);
+
+private:
+	int m_resX;
+	int m_resY;
+	bool m_moved;
+	float m_speedMultiplier;
+	COLOR m_color;
+	ImageResource *m_image;
+	bool m_present;
+};
+
+class GUIPatternPassword : public GUIObject, public RenderObject, public ActionObject
+{
+public:
+	GUIPatternPassword(xml_node<>* node);
+	virtual ~GUIPatternPassword();
+
+public:
+	virtual int Render(void);
+	virtual int Update(void);
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
+	virtual int SetRenderPos(int x, int y, int w = 0, int h = 0);
+
+protected:
+	void CalculateDotPositions();
+	void ResetActiveDots();
+	void ConnectDot(int dot_idx);
+	void ConnectIntermediateDots(int dot_idx);
+	void Resize(size_t size);
+	int InDot(int x, int y);
+	bool DotUsed(int dot_idx);
+	std::string GeneratePassphrase();
+	void PatternDrawn();
+
+	struct Dot {
+		int x;
+		int y;
+		bool active;
+	};
+
+	std::string mSizeVar;
+	size_t mGridSize;
+
+	Dot* mDots;
+	int* mConnectedDots;
+	size_t mConnectedDotsLen;
+	int mCurLineX;
+	int mCurLineY;
+	bool mTrackingTouch;
+	bool mNeedRender;
+
+	COLOR mDotColor;
+	COLOR mActiveDotColor;
+	COLOR mLineColor;
+	ImageResource *mDotImage;
+	ImageResource *mActiveDotImage;
+	gr_surface mDotCircle;
+	gr_surface mActiveDotCircle;
+	int mDotRadius;
+	int mLineWidth;
+
+	std::string mPassVar;
+	GUIAction *mAction;
+	int mUpdate;
+};
+
+
+// Helper APIs
+xml_node<>* FindNode(xml_node<>* parent, const char* nodename, int depth = 0);
+std::string LoadAttrString(xml_node<>* element, const char* attrname, const char* defaultvalue = "");
+int LoadAttrInt(xml_node<>* element, const char* attrname, int defaultvalue = 0);
+int LoadAttrIntScaleX(xml_node<>* element, const char* attrname, int defaultvalue = 0);
+int LoadAttrIntScaleY(xml_node<>* element, const char* attrname, int defaultvalue = 0);
+COLOR LoadAttrColor(xml_node<>* element, const char* attrname, bool* found_color, COLOR defaultvalue = COLOR(0,0,0,0));
+COLOR LoadAttrColor(xml_node<>* element, const char* attrname, COLOR defaultvalue = COLOR(0,0,0,0));
+FontResource* LoadAttrFont(xml_node<>* element, const char* attrname);
+ImageResource* LoadAttrImage(xml_node<>* element, const char* attrname);
+AnimationResource* LoadAttrAnimation(xml_node<>* element, const char* attrname);
+bool LoadPlacement(xml_node<>* node, int* x, int* y, int* w = NULL, int* h = NULL, Placement* placement = NULL);
+
+#endif  // _OBJECTS_HEADER
diff --git a/gui/pages.cpp b/gui/pages.cpp
new file mode 100755
index 0000000..8b9ce46
--- /dev/null
+++ b/gui/pages.cpp
@@ -0,0 +1,1691 @@
+/*
+	Copyright 2013 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// pages.cpp - Source to manage GUI base objects
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include "../twrp-functions.hpp"
+#include "../partitions.hpp"
+
+#include <string>
+#include <algorithm>
+
+
+#include <ziparchive/zip_archive.h>
+#include "ZipUtil.h"
+
+extern "C" {
+#include "../twcommon.h"
+#include "gui.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+#include "blanktimer.hpp"
+
+#include "../variables.h"
+
+#define TW_THEME_VER_ERR -2
+
+extern int gGuiRunning;
+GUITerminal* term = NULL;
+
+std::map<std::string, PageSet*> PageManager::mPageSets;
+PageSet* PageManager::mCurrentSet;
+MouseCursor *PageManager::mMouseCursor = NULL;
+HardwareKeyboard *PageManager::mHardwareKeyboard = NULL;
+bool PageManager::mReloadTheme = false;
+std::string PageManager::mStartPage = "main";
+std::vector<language_struct> Language_List;
+
+int tw_x_offset = 0;
+int tw_y_offset = 0;
+int tw_w_offset = 0;
+int tw_h_offset = 0;
+
+// Helper routine to convert a string to a color declaration
+int ConvertStrToColor(std::string str, COLOR* color)
+{
+	// Set the default, solid black
+	memset(color, 0, sizeof(COLOR));
+	color->alpha = 255;
+
+	// Translate variables
+	DataManager::GetValue(str, str);
+
+	// Look for some defaults
+	if (str == "black")		return 0;
+	else if (str == "white")	{ color->red = color->green = color->blue = 255; return 0; }
+	else if (str == "red")		{ color->red = 255; return 0; }
+	else if (str == "green")	{ color->green = 255; return 0; }
+	else if (str == "blue")		{ color->blue = 255; return 0; }
+
+	// At this point, we require an RGB(A) color
+	if (str[0] != '#')
+		return -1;
+
+	str.erase(0, 1);
+
+	int result;
+	if (str.size() >= 8) {
+		// We have alpha channel
+		string alpha = str.substr(6, 2);
+		result = strtol(alpha.c_str(), NULL, 16);
+		color->alpha = result & 0x000000FF;
+		str.resize(6);
+		result = strtol(str.c_str(), NULL, 16);
+		color->red = (result >> 16) & 0x000000FF;
+		color->green = (result >> 8) & 0x000000FF;
+		color->blue = result & 0x000000FF;
+	} else {
+		result = strtol(str.c_str(), NULL, 16);
+		color->red = (result >> 16) & 0x000000FF;
+		color->green = (result >> 8) & 0x000000FF;
+		color->blue = result & 0x000000FF;
+	}
+	return 0;
+}
+
+// Helper APIs
+xml_node<>* FindNode(xml_node<>* parent, const char* nodename, int depth /* = 0 */)
+{
+	if (!parent)
+		return NULL;
+
+	xml_node<>* child = parent->first_node(nodename);
+	if (child)
+		return child;
+
+	if (depth == 10) {
+		LOGERR("Too many style loops detected.\n");
+		return NULL;
+	}
+
+	xml_node<>* style = parent->first_node("style");
+	if (style) {
+		while (style) {
+			if (!style->first_attribute("name")) {
+				LOGERR("No name given for style.\n");
+				continue;
+			} else {
+				std::string name = style->first_attribute("name")->value();
+				xml_node<>* node = PageManager::FindStyle(name);
+
+				if (node) {
+					// We found the style that was named
+					xml_node<>* stylenode = FindNode(node, nodename, depth + 1);
+					if (stylenode)
+						return stylenode;
+				}
+			}
+			style = style->next_sibling("style");
+		}
+	} else {
+		// Search for stylename in the parent node <object type="foo" style="foo2">
+		xml_attribute<>* attr = parent->first_attribute("style");
+		// If no style is found anywhere else and the node wasn't found in the object itself
+		// as a special case we will search for a style that uses the same style name as the
+		// object type, so <object type="button"> would search for a style named "button"
+		if (!attr)
+			attr = parent->first_attribute("type");
+		// if there's no attribute type, the object type must be the element name
+		std::string stylename = attr ? attr->value() : parent->name();
+		xml_node<>* node = PageManager::FindStyle(stylename);
+		if (node) {
+			xml_node<>* stylenode = FindNode(node, nodename, depth + 1);
+			if (stylenode)
+				return stylenode;
+		}
+	}
+	return NULL;
+}
+
+std::string LoadAttrString(xml_node<>* element, const char* attrname, const char* defaultvalue)
+{
+	if (!element)
+		return defaultvalue;
+
+	xml_attribute<>* attr = element->first_attribute(attrname);
+	return attr ? attr->value() : defaultvalue;
+}
+
+int LoadAttrInt(xml_node<>* element, const char* attrname, int defaultvalue)
+{
+	string value = LoadAttrString(element, attrname);
+	// resolve variables
+	DataManager::GetValue(value, value);
+	return value.empty() ? defaultvalue : atoi(value.c_str());
+}
+
+int LoadAttrIntScaleX(xml_node<>* element, const char* attrname, int defaultvalue)
+{
+	return scale_theme_x(LoadAttrInt(element, attrname, defaultvalue));
+}
+
+int LoadAttrIntScaleY(xml_node<>* element, const char* attrname, int defaultvalue)
+{
+	return scale_theme_y(LoadAttrInt(element, attrname, defaultvalue));
+}
+
+COLOR LoadAttrColor(xml_node<>* element, const char* attrname, bool* found_color, COLOR defaultvalue)
+{
+	string value = LoadAttrString(element, attrname);
+	*found_color = !value.empty();
+	// resolve variables
+	DataManager::GetValue(value, value);
+	COLOR ret = defaultvalue;
+	if (ConvertStrToColor(value, &ret) == 0)
+		return ret;
+	else
+		return defaultvalue;
+}
+
+COLOR LoadAttrColor(xml_node<>* element, const char* attrname, COLOR defaultvalue)
+{
+	bool found_color = false;
+	return LoadAttrColor(element, attrname, &found_color, defaultvalue);
+}
+
+FontResource* LoadAttrFont(xml_node<>* element, const char* attrname)
+{
+	std::string name = LoadAttrString(element, attrname, "");
+	if (name.empty())
+		return NULL;
+	else
+		return PageManager::GetResources()->FindFont(name);
+}
+
+ImageResource* LoadAttrImage(xml_node<>* element, const char* attrname)
+{
+	std::string name = LoadAttrString(element, attrname, "");
+	if (name.empty())
+		return NULL;
+	else
+		return PageManager::GetResources()->FindImage(name);
+}
+
+AnimationResource* LoadAttrAnimation(xml_node<>* element, const char* attrname)
+{
+	std::string name = LoadAttrString(element, attrname, "");
+	if (name.empty())
+		return NULL;
+	else
+		return PageManager::GetResources()->FindAnimation(name);
+}
+
+bool LoadPlacement(xml_node<>* node, int* x, int* y, int* w /* = NULL */, int* h /* = NULL */, Placement* placement /* = NULL */)
+{
+	if (!node)
+		return false;
+
+	if (node->first_attribute("x"))
+		*x = LoadAttrIntScaleX(node, "x") + tw_x_offset;
+
+	if (node->first_attribute("y"))
+		*y = LoadAttrIntScaleY(node, "y") + tw_y_offset;
+
+	if (w && node->first_attribute("w"))
+		*w = LoadAttrIntScaleX(node, "w");
+
+	if (h && node->first_attribute("h"))
+		*h = LoadAttrIntScaleY(node, "h");
+
+	if (placement && node->first_attribute("placement"))
+		*placement = (Placement) LoadAttrInt(node, "placement");
+
+	return true;
+}
+
+int ActionObject::SetActionPos(int x, int y, int w, int h)
+{
+	if (x < 0 || y < 0)
+		return -1;
+
+	mActionX = x;
+	mActionY = y;
+	if (w || h)
+	{
+		mActionW = w;
+		mActionH = h;
+	}
+	return 0;
+}
+
+Page::Page(xml_node<>* page, std::vector<xml_node<>*> *templates)
+{
+	mTouchStart = NULL;
+
+	// We can memset the whole structure, because the alpha channel is ignored
+	memset(&mBackground, 0, sizeof(COLOR));
+
+	// With NULL, we make a console-only display
+	if (!page)
+	{
+		mName = "console";
+
+		GUIConsole* element = new GUIConsole(NULL);
+		mRenders.push_back(element);
+		mActions.push_back(element);
+		return;
+	}
+
+	if (page->first_attribute("name"))
+		mName = page->first_attribute("name")->value();
+	else
+	{
+		LOGERR("No page name attribute found!\n");
+		return;
+	}
+
+	LOGINFO("Loading page %s\n", mName.c_str());
+
+	// This is a recursive routine for template handling
+	ProcessNode(page, templates, 0);
+}
+
+Page::~Page()
+{
+	for (std::vector<GUIObject*>::iterator itr = mObjects.begin(); itr != mObjects.end(); ++itr)
+		delete *itr;
+}
+
+bool Page::ProcessNode(xml_node<>* page, std::vector<xml_node<>*> *templates, int depth)
+{
+	if (depth == 10)
+	{
+		LOGERR("Page processing depth has exceeded 10. Failing out. This is likely a recursive template.\n");
+		return false;
+	}
+
+	for (xml_node<>* child = page->first_node(); child; child = child->next_sibling())
+	{
+		std::string type = child->name();
+
+		if (type == "background") {
+			mBackground = LoadAttrColor(child, "color", COLOR(0,0,0,0));
+			continue;
+		}
+
+		if (type == "object") {
+			// legacy format : <object type="...">
+			xml_attribute<>* attr = child->first_attribute("type");
+			type = attr ? attr->value() : "*unspecified*";
+		}
+
+		if (type == "text")
+		{
+			GUIText* element = new GUIText(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "image")
+		{
+			GUIImage* element = new GUIImage(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+		}
+		else if (type == "fill")
+		{
+			GUIFill* element = new GUIFill(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+		}
+		else if (type == "action")
+		{
+			GUIAction* element = new GUIAction(child);
+			mObjects.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "console")
+		{
+			GUIConsole* element = new GUIConsole(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "terminal")
+		{
+			GUITerminal* element = new GUITerminal(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+			mInputs.push_back(element);
+			term = element;
+		}
+		else if (type == "button")
+		{
+			GUIButton* element = new GUIButton(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "checkbox")
+		{
+			GUICheckbox* element = new GUICheckbox(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "fileselector")
+		{
+			GUIFileSelector* element = new GUIFileSelector(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "animation")
+		{
+			GUIAnimation* element = new GUIAnimation(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+		}
+		else if (type == "progressbar")
+		{
+			GUIProgressBar* element = new GUIProgressBar(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "slider")
+		{
+			GUISlider* element = new GUISlider(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "slidervalue")
+		{
+			GUISliderValue *element = new GUISliderValue(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "listbox")
+		{
+			GUIListBox* element = new GUIListBox(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "keyboard")
+		{
+			GUIKeyboard* element = new GUIKeyboard(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "input")
+		{
+			GUIInput* element = new GUIInput(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+			mInputs.push_back(element);
+		}
+		else if (type == "partitionlist")
+		{
+			GUIPartitionList* element = new GUIPartitionList(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "patternpassword")
+		{
+			GUIPatternPassword* element = new GUIPatternPassword(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "textbox")
+		{
+			GUITextBox* element = new GUITextBox(child);
+			mObjects.push_back(element);
+			mRenders.push_back(element);
+			mActions.push_back(element);
+		}
+		else if (type == "template")
+		{
+			if (!templates || !child->first_attribute("name"))
+			{
+				LOGERR("Invalid template request.\n");
+			}
+			else
+			{
+				std::string name = child->first_attribute("name")->value();
+				xml_node<>* node;
+				bool node_found = false;
+
+				// We need to find the correct template
+				for (std::vector<xml_node<>*>::iterator itr = templates->begin(); itr != templates->end(); itr++) {
+					node = (*itr)->first_node("template");
+
+					while (node)
+					{
+						if (!node->first_attribute("name"))
+							continue;
+
+						if (name == node->first_attribute("name")->value())
+						{
+							if (!ProcessNode(node, templates, depth + 1))
+								return false;
+							else {
+								node_found = true;
+								break;
+							}
+						}
+						if (node_found)
+							break;
+						node = node->next_sibling("template");
+					}
+					// [check] why is there no if (node_found) here too?
+				}
+			}
+		}
+		else
+		{
+			LOGERR("Unknown object type: %s.\n", type.c_str());
+		}
+	}
+	return true;
+}
+
+int Page::Render(void)
+{
+	// Render background
+	gr_color(mBackground.red, mBackground.green, mBackground.blue, mBackground.alpha);
+	gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+
+	// Render remaining objects
+	std::vector<RenderObject*>::iterator iter;
+	for (iter = mRenders.begin(); iter != mRenders.end(); iter++)
+	{
+		if ((*iter)->Render())
+			LOGERR("A render request has failed.\n");
+	}
+	return 0;
+}
+
+int Page::Update(void)
+{
+	int retCode = 0;
+
+	std::vector<RenderObject*>::iterator iter;
+	for (iter = mRenders.begin(); iter != mRenders.end(); iter++)
+	{
+		int ret = (*iter)->Update();
+		if (ret < 0)
+			LOGERR("An update request has failed.\n");
+		else if (ret > retCode)
+			retCode = ret;
+	}
+
+	return retCode;
+}
+
+int Page::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	// By default, return 1 to ignore further touches if nobody is listening
+	int ret = 1;
+
+	// Don't try to handle a lack of handlers
+	if (mActions.size() == 0)
+		return ret;
+
+	// We record mTouchStart so we can pass all the touch stream to the same handler
+	if (state == TOUCH_START)
+	{
+		std::vector<ActionObject*>::reverse_iterator iter;
+		// We work backwards, from top-most element to bottom-most element
+		for (iter = mActions.rbegin(); iter != mActions.rend(); iter++)
+		{
+			if ((*iter)->IsInRegion(x, y))
+			{
+				mTouchStart = (*iter);
+				ret = mTouchStart->NotifyTouch(state, x, y);
+				if (ret >= 0)
+					break;
+				mTouchStart = NULL;
+			}
+		}
+	}
+	else if (state == TOUCH_RELEASE && mTouchStart != NULL)
+	{
+		ret = mTouchStart->NotifyTouch(state, x, y);
+		mTouchStart = NULL;
+	}
+	else if ((state == TOUCH_DRAG || state == TOUCH_HOLD || state == TOUCH_REPEAT) && mTouchStart != NULL)
+	{
+		ret = mTouchStart->NotifyTouch(state, x, y);
+	}
+	return ret;
+}
+
+int Page::NotifyKey(int key, bool down)
+{
+	std::vector<ActionObject*>::reverse_iterator iter;
+
+	int ret = 1;
+	// We work backwards, from top-most element to bottom-most element
+	for (iter = mActions.rbegin(); iter != mActions.rend(); iter++)
+	{
+		ret = (*iter)->NotifyKey(key, down);
+		if (ret == 0)
+			return 0;
+		if (ret < 0) {
+			LOGERR("An action handler has returned an error\n");
+			ret = 1;
+		}
+	}
+	return ret;
+}
+
+int Page::NotifyCharInput(int ch)
+{
+	std::vector<InputObject*>::reverse_iterator iter;
+
+	// We work backwards, from top-most element to bottom-most element
+	for (iter = mInputs.rbegin(); iter != mInputs.rend(); iter++)
+	{
+		int ret = (*iter)->NotifyCharInput(ch);
+		if (ret == 0)
+			return 0;
+		else if (ret < 0)
+			LOGERR("A char input handler has returned an error\n");
+	}
+	return 1;
+}
+
+int Page::SetKeyBoardFocus(int inFocus)
+{
+	std::vector<InputObject*>::reverse_iterator iter;
+
+	// We work backwards, from top-most element to bottom-most element
+	for (iter = mInputs.rbegin(); iter != mInputs.rend(); iter++)
+	{
+		int ret = (*iter)->SetInputFocus(inFocus);
+		if (ret == 0)
+			return 0;
+		else if (ret < 0)
+			LOGERR("An input focus handler has returned an error\n");
+	}
+	return 1;
+}
+
+void Page::SetPageFocus(int inFocus)
+{
+	// Render remaining objects
+	std::vector<RenderObject*>::iterator iter;
+	for (iter = mRenders.begin(); iter != mRenders.end(); iter++)
+		(*iter)->SetPageFocus(inFocus);
+
+	return;
+}
+
+int Page::NotifyVarChange(std::string varName, std::string value)
+{
+	std::vector<GUIObject*>::iterator iter;
+	for (iter = mObjects.begin(); iter != mObjects.end(); ++iter)
+	{
+		if ((*iter)->NotifyVarChange(varName, value))
+			LOGERR("An action handler errored on NotifyVarChange.\n");
+	}
+	return 0;
+}
+
+
+// transient data for loading themes
+struct LoadingContext
+{
+	ZipArchiveHandle zip; // zip to load theme from, or NULL for the stock theme
+	std::set<std::string> filenames; // to detect cyclic includes
+	std::string basepath; // if zip is NULL, base path to load includes from with trailing slash, otherwise empty
+	std::vector<xml_document<>*> xmldocs; // all loaded xml docs
+	std::vector<char*> xmlbuffers; // text buffers with xml content
+	std::vector<xml_node<>*> styles; // refer to <styles> nodes inside xmldocs
+	std::vector<xml_node<>*> templates; // refer to <templates> nodes inside xmldocs
+
+	LoadingContext()
+	{
+		zip = NULL;
+	}
+
+	~LoadingContext()
+	{
+		// free all xml buffers
+		for (std::vector<char*>::iterator it = xmlbuffers.begin(); it != xmlbuffers.end(); ++it)
+			free(*it);
+	}
+
+};
+
+// for FindStyle
+LoadingContext* PageManager::currentLoadingContext = NULL;
+
+
+PageSet::PageSet()
+{
+	mResources = new ResourceManager;
+	mCurrentPage = NULL;
+
+	set_scale_values(1, 1); // Reset any previous scaling values
+}
+
+PageSet::~PageSet()
+{
+	mOverlays.clear();
+	for (std::vector<Page*>::iterator itr = mPages.begin(); itr != mPages.end(); ++itr)
+		delete *itr;
+
+	delete mResources;
+}
+
+int PageSet::Load(LoadingContext& ctx, const std::string& filename)
+{
+	bool isMain = ctx.xmlbuffers.empty(); // if we have no files yet, remember that this is the main XML file
+
+	if (!ctx.filenames.insert(filename).second)
+		// ignore already loaded files to prevent crash with cyclic includes
+		return 0;
+
+	// load XML into buffer
+	char* xmlbuffer = PageManager::LoadFileToBuffer(filename, ctx.zip);
+	if (!xmlbuffer)
+		return -1; // error already displayed by LoadFileToBuffer
+	ctx.xmlbuffers.push_back(xmlbuffer);
+
+	// parse XML
+	xml_document<>* doc = new xml_document<>();
+	doc->parse<0>(xmlbuffer);
+	ctx.xmldocs.push_back(doc);
+
+	xml_node<>* root = doc->first_node("recovery");
+	if (!root)
+		root = doc->first_node("install");
+	if (!root) {
+		LOGERR("Unknown root element in %s\n", filename.c_str());
+		return -1;
+	}
+
+	if (isMain) {
+		int rc = LoadDetails(ctx, root);
+		if (rc != 0)
+			return rc;
+	}
+
+	LOGINFO("Loading resources...\n");
+	xml_node<>* child = root->first_node("resources");
+	if (child)
+		mResources->LoadResources(child, ctx.zip, "theme");
+
+	LOGINFO("Loading variables...\n");
+	child = root->first_node("variables");
+	if (child)
+		LoadVariables(child);
+
+	LOGINFO("Loading mouse cursor...\n");
+	child = root->first_node("mousecursor");
+	if (child)
+		PageManager::LoadCursorData(child);
+
+	LOGINFO("Loading pages...\n");
+	child = root->first_node("templates");
+	if (child)
+		ctx.templates.push_back(child);
+
+	child = root->first_node("styles");
+	if (child)
+		ctx.styles.push_back(child);
+
+	// Load pages
+	child = root->first_node("pages");
+	if (child) {
+		if (LoadPages(ctx, child)) {
+			LOGERR("PageSet::Load returning -1\n");
+			return -1;
+		}
+	}
+
+	// process includes recursively
+	child = root->first_node("include");
+	if (child) {
+		xml_node<>* include = child->first_node("xmlfile");
+		while (include != NULL) {
+			xml_attribute<>* attr = include->first_attribute("name");
+			if (!attr) {
+				LOGERR("Skipping include/xmlfile with no name\n");
+				continue;
+			}
+
+			string filename = ctx.basepath + attr->value();
+			LOGINFO("Including file: %s...\n", filename.c_str());
+			int rc = Load(ctx, filename);
+			if (rc != 0)
+				return rc;
+
+			include = include->next_sibling("xmlfile");
+		}
+	}
+
+	return 0;
+}
+
+void PageSet::MakeEmergencyConsoleIfNeeded()
+{
+	if (mPages.empty()) {
+		mCurrentPage = new Page(NULL, NULL); // fallback console page
+		// TODO: since removal of non-TTF fonts, the emergency console doesn't work without a font, which might be missing too
+		mPages.push_back(mCurrentPage);
+	}
+}
+
+int PageSet::LoadLanguage(char* languageFile, ZipArchiveHandle package)
+{
+	xml_document<> lang;
+	xml_node<>* parent;
+	xml_node<>* child;
+	std::string resource_source;
+	int ret = 0;
+
+	if (languageFile) {
+		printf("parsing languageFile\n");
+		lang.parse<0>(languageFile);
+		printf("parsing languageFile done\n");
+	} else {
+		return -1;
+	}
+
+	parent = lang.first_node("language");
+	if (!parent) {
+		LOGERR("Unable to locate language node in language file.\n");
+		lang.clear();
+		return -1;
+	}
+
+	child = parent->first_node("display");
+	if (child) {
+		DataManager::SetValue("tw_language_display", child->value());
+		resource_source = child->value();
+	} else {
+		LOGERR("language file does not have a display value set\n");
+		DataManager::SetValue("tw_language_display", "Not Set");
+		resource_source = languageFile;
+	}
+
+	child = parent->first_node("resources");
+	if (child)
+		mResources->LoadResources(child, package, resource_source);
+	else
+		ret = -1;
+	DataManager::SetValue("tw_backup_name", gui_lookup("auto_generate", "(Auto Generate)"));
+	lang.clear();
+	return ret;
+}
+
+int PageSet::LoadDetails(LoadingContext& ctx, xml_node<>* root)
+{
+	xml_node<>* child = root->first_node("details");
+	if (child) {
+		int theme_ver = 0;
+		xml_node<>* themeversion = child->first_node("themeversion");
+		if (themeversion && themeversion->value()) {
+			theme_ver = atoi(themeversion->value());
+		} else {
+			LOGINFO("No themeversion in theme.\n");
+		}
+		if (theme_ver != TW_THEME_VERSION) {
+			LOGINFO("theme version from xml: %i, expected %i\n", theme_ver, TW_THEME_VERSION);
+			if (ctx.zip) {
+				gui_err("theme_ver_err=Custom theme version does not match TWRP version. Using stock theme.");
+				return TW_THEME_VER_ERR;
+			} else {
+				gui_print_color("warning", "Stock theme version does not match TWRP version.\n");
+			}
+		}
+		xml_node<>* resolution = child->first_node("resolution");
+		if (resolution) {
+			LOGINFO("Checking resolution...\n");
+			xml_attribute<>* width_attr = resolution->first_attribute("width");
+			xml_attribute<>* height_attr = resolution->first_attribute("height");
+			xml_attribute<>* noscale_attr = resolution->first_attribute("noscaling");
+			if (width_attr && height_attr && !noscale_attr) {
+				int width = atoi(width_attr->value());
+				int height = atoi(height_attr->value());
+				int offx = 0, offy = 0;
+#ifdef TW_ROUND_SCREEN
+				xml_node<>* roundscreen = child->first_node("roundscreen");
+				if (roundscreen) {
+					LOGINFO("TW_ROUND_SCREEN := true, using round screen XML settings.\n");
+					xml_attribute<>* offx_attr = roundscreen->first_attribute("offset_x");
+					xml_attribute<>* offy_attr = roundscreen->first_attribute("offset_y");
+					if (offx_attr) {
+						offx = atoi(offx_attr->value());
+					}
+					if (offy_attr) {
+						offy = atoi(offy_attr->value());
+					}
+				}
+#endif
+				if (width != 0 && height != 0) {
+					float scale_w = (((float)gr_fb_width() + (float)tw_w_offset) - ((float)offx * 2.0)) / (float)width;
+					float scale_h = (((float)gr_fb_height() + (float)tw_h_offset) - ((float)offy * 2.0)) / (float)height;
+#ifdef TW_ROUND_SCREEN
+					float scale_off_w = ((float)gr_fb_width() + (float)tw_w_offset) / (float)width;
+					float scale_off_h = ((float)gr_fb_height() + (float)tw_h_offset) / (float)height;
+					tw_x_offset = offx * scale_off_w;
+					tw_y_offset = offy * scale_off_h;
+#endif
+					if (scale_w != 1 || scale_h != 1) {
+						LOGINFO("Scaling theme width %fx and height %fx, offsets x: %i y: %i w: %i h: %i\n",
+							scale_w, scale_h, tw_x_offset, tw_y_offset, tw_w_offset, tw_h_offset);
+						set_scale_values(scale_w, scale_h);
+					}
+				}
+			} else {
+				LOGINFO("XML does not contain width and height, no scaling will be applied\n");
+			}
+		} else {
+			LOGINFO("XML contains no resolution tag, no scaling will be applied.\n");
+		}
+	} else {
+		LOGINFO("XML contains no details tag, no scaling will be applied.\n");
+	}
+
+	return 0;
+}
+
+int PageSet::SetPage(std::string page)
+{
+	Page* tmp = FindPage(page);
+	if (tmp)
+	{
+		if (mCurrentPage)   mCurrentPage->SetPageFocus(0);
+		mCurrentPage = tmp;
+		mCurrentPage->SetPageFocus(1);
+		mCurrentPage->NotifyVarChange("", "");
+		return 0;
+	}
+	else
+	{
+		LOGERR("Unable to locate page (%s)\n", page.c_str());
+	}
+	return -1;
+}
+
+int PageSet::SetOverlay(Page* page)
+{
+	if (page) {
+		if (mOverlays.size() >= 10) {
+			LOGERR("Too many overlays requested, max is 10.\n");
+			return -1;
+		}
+
+		std::vector<Page*>::iterator iter;
+		for (iter = mOverlays.begin(); iter != mOverlays.end(); iter++) {
+			if ((*iter)->GetName() == page->GetName()) {
+				mOverlays.erase(iter);
+				// SetOverlay() is (and should stay) the only function which
+				// adds to mOverlays. Then, each page can appear at most once.
+				break;
+			}
+		}
+
+		page->SetPageFocus(1);
+		page->NotifyVarChange("", "");
+
+		if (!mOverlays.empty())
+			mOverlays.back()->SetPageFocus(0);
+
+		mOverlays.push_back(page);
+	} else {
+		if (!mOverlays.empty()) {
+			mOverlays.back()->SetPageFocus(0);
+			mOverlays.pop_back();
+			if (!mOverlays.empty())
+				mOverlays.back()->SetPageFocus(1);
+			else if (mCurrentPage)
+				mCurrentPage->SetPageFocus(1); // Just in case somehow the regular page lost focus, we'll set it again
+		}
+	}
+	return 0;
+}
+
+const ResourceManager* PageSet::GetResources()
+{
+	return mResources;
+}
+
+Page* PageSet::FindPage(std::string name)
+{
+	std::vector<Page*>::iterator iter;
+
+	for (iter = mPages.begin(); iter != mPages.end(); iter++)
+	{
+		if (name == (*iter)->GetName())
+			return (*iter);
+	}
+	return NULL;
+}
+
+int PageSet::LoadVariables(xml_node<>* vars)
+{
+	xml_node<>* child;
+	xml_attribute<> *name, *value, *persist;
+	int p;
+
+	child = vars->first_node("variable");
+	while (child)
+	{
+		name = child->first_attribute("name");
+		value = child->first_attribute("value");
+		persist = child->first_attribute("persist");
+		if (name && value)
+		{
+			if (strcmp(name->value(), "tw_x_offset") == 0) {
+				tw_x_offset = atoi(value->value());
+				child = child->next_sibling("variable");
+				continue;
+			}
+			if (strcmp(name->value(), "tw_y_offset") == 0) {
+				tw_y_offset = atoi(value->value());
+				child = child->next_sibling("variable");
+				continue;
+			}
+			if (strcmp(name->value(), "tw_w_offset") == 0) {
+				tw_w_offset = atoi(value->value());
+				child = child->next_sibling("variable");
+				continue;
+			}
+			if (strcmp(name->value(), "tw_h_offset") == 0) {
+				tw_h_offset = atoi(value->value());
+				child = child->next_sibling("variable");
+				continue;
+			}
+			p = persist ? atoi(persist->value()) : 0;
+			string temp = value->value();
+			string valstr = gui_parse_text(temp);
+
+			if (valstr.find("+") != string::npos) {
+				string val1str = valstr;
+				val1str = val1str.substr(0, val1str.find('+'));
+				string val2str = valstr;
+				val2str = val2str.substr(val2str.find('+') + 1, string::npos);
+				int val1 = atoi(val1str.c_str());
+				int val2 = atoi(val2str.c_str());
+				int val = val1 + val2;
+
+				DataManager::SetValue(name->value(), val, p);
+			} else if (valstr.find("-") != string::npos) {
+				string val1str = valstr;
+				val1str = val1str.substr(0, val1str.find('-'));
+				string val2str = valstr;
+				val2str = val2str.substr(val2str.find('-') + 1, string::npos);
+				int val1 = atoi(val1str.c_str());
+				int val2 = atoi(val2str.c_str());
+				int val = val1 - val2;
+
+				DataManager::SetValue(name->value(), val, p);
+			} else {
+				DataManager::SetValue(name->value(), valstr, p);
+			}
+		}
+
+		child = child->next_sibling("variable");
+	}
+	return 0;
+}
+
+int PageSet::LoadPages(LoadingContext& ctx, xml_node<>* pages)
+{
+	xml_node<>* child;
+
+	if (!pages)
+		return -1;
+
+	child = pages->first_node("page");
+	while (child != NULL)
+	{
+		Page* page = new Page(child, &ctx.templates);
+		if (page->GetName().empty())
+		{
+			LOGERR("Unable to process load page\n");
+			delete page;
+		}
+		else
+		{
+			mPages.push_back(page);
+		}
+		child = child->next_sibling("page");
+	}
+	if (mPages.size() > 0)
+		return 0;
+	return -1;
+}
+
+int PageSet::IsCurrentPage(Page* page)
+{
+	return ((mCurrentPage && mCurrentPage == page) ? 1 : 0);
+}
+
+std::string PageSet::GetCurrentPage() const
+{
+	return mCurrentPage ? mCurrentPage->GetName() : "";
+}
+
+int PageSet::Render(void)
+{
+	int ret;
+
+	ret = (mCurrentPage ? mCurrentPage->Render() : -1);
+	if (ret < 0)
+		return ret;
+
+	std::vector<Page*>::iterator iter;
+
+	for (iter = mOverlays.begin(); iter != mOverlays.end(); iter++) {
+		ret = ((*iter) ? (*iter)->Render() : -1);
+		if (ret < 0)
+			return ret;
+	}
+	return ret;
+}
+
+int PageSet::Update(void)
+{
+	int ret;
+
+	ret = (mCurrentPage ? mCurrentPage->Update() : -1);
+	if (ret < 0 || ret > 1)
+		return ret;
+
+	std::vector<Page*>::iterator iter;
+
+	for (iter = mOverlays.begin(); iter != mOverlays.end(); iter++) {
+		ret = ((*iter) ? (*iter)->Update() : -1);
+		if (ret < 0)
+			return ret;
+	}
+	return ret;
+}
+
+int PageSet::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	if (!mOverlays.empty())
+		return mOverlays.back()->NotifyTouch(state, x, y);
+
+	return (mCurrentPage ? mCurrentPage->NotifyTouch(state, x, y) : -1);
+}
+
+int PageSet::NotifyKey(int key, bool down)
+{
+	if (!mOverlays.empty())
+		return mOverlays.back()->NotifyKey(key, down);
+
+	return (mCurrentPage ? mCurrentPage->NotifyKey(key, down) : -1);
+}
+
+int PageSet::NotifyCharInput(int ch)
+{
+	if (!mOverlays.empty())
+		return mOverlays.back()->NotifyCharInput(ch);
+
+	return (mCurrentPage ? mCurrentPage->NotifyCharInput(ch) : -1);
+}
+
+int PageSet::SetKeyBoardFocus(int inFocus)
+{
+	if (!mOverlays.empty())
+		return mOverlays.back()->SetKeyBoardFocus(inFocus);
+
+	return (mCurrentPage ? mCurrentPage->SetKeyBoardFocus(inFocus) : -1);
+}
+
+int PageSet::NotifyVarChange(std::string varName, std::string value)
+{
+	std::vector<Page*>::iterator iter;
+
+	for (iter = mOverlays.begin(); iter != mOverlays.end(); iter++)
+		(*iter)->NotifyVarChange(varName, value);
+
+	return (mCurrentPage ? mCurrentPage->NotifyVarChange(varName, value) : -1);
+}
+
+void PageSet::AddStringResource(std::string resource_source, std::string resource_name, std::string value)
+{
+	mResources->AddStringResource(resource_source, resource_name, value);
+}
+
+char* PageManager::LoadFileToBuffer(std::string filename, ZipArchiveHandle package) {
+	size_t len;
+	char* buffer = NULL;
+
+	if (!package) {
+		// We can try to load the XML directly...
+		LOGINFO("PageManager::LoadFileToBuffer loading filename: '%s' directly\n", filename.c_str());
+		struct stat st;
+		if (stat(filename.c_str(),&st) != 0) {
+			// This isn't always an error, sometimes we request files that don't exist.
+			return NULL;
+		}
+
+		len = (size_t)st.st_size;
+
+		buffer = (char*) malloc(len + 1);
+		if (!buffer) {
+			LOGERR("PageManager::LoadFileToBuffer failed to malloc\n");
+			return NULL;
+		}
+
+		int fd = open(filename.c_str(), O_RDONLY);
+		if (fd == -1) {
+			LOGERR("PageManager::LoadFileToBuffer failed to open '%s' - (%s)\n", filename.c_str(), strerror(errno));
+			free(buffer);
+			return NULL;
+		}
+
+		if (read(fd, buffer, len) < 0) {
+			LOGERR("PageManager::LoadFileToBuffer failed to read '%s' - (%s)\n", filename.c_str(), strerror(errno));
+			free(buffer);
+			close(fd);
+			return NULL;
+		}
+		close(fd);
+	} else {
+		LOGINFO("PageManager::LoadFileToBuffer loading filename: '%s' from zip\n", filename.c_str());
+		ZipEntry binary_entry;
+		if (FindEntry(package, filename, &binary_entry) != 0) {
+			LOGERR("Unable to locate '%s' in zip file\n", filename.c_str());
+			return NULL;
+		}
+
+		// Allocate the buffer for the file
+		len = binary_entry.uncompressed_length;
+		buffer = (char*) malloc(len + 1);
+		if (!buffer)
+			return NULL;
+
+		int32_t err =
+			ExtractToMemory(package, &binary_entry, reinterpret_cast<uint8_t*>(buffer), len);
+		if (err != 0) {
+			LOGERR("Unable to extract '%s'\n", filename.c_str());
+			free(buffer);
+			return NULL;
+		}
+	}
+	// NULL-terminate the string
+	buffer[len] = 0x00;
+	return buffer;
+}
+
+void PageManager::LoadLanguageListDir(string dir) {
+	if (!TWFunc::Path_Exists(dir)) {
+		LOGERR("LoadLanguageListDir '%s' path not found\n", dir.c_str());
+		return;
+	}
+
+	DIR *d = opendir(dir.c_str());
+	struct dirent *p;
+
+	if (d == NULL) {
+		LOGERR("LoadLanguageListDir error opening dir: '%s', %s\n", dir.c_str(), strerror(errno));
+		return;
+	}
+
+	while ((p = readdir(d))) {
+		if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..") || strlen(p->d_name) < 5)
+			continue;
+
+		string file = p->d_name;
+		if (file.substr(strlen(p->d_name) - 4) != ".xml")
+			continue;
+		string path = dir + p->d_name;
+		string file_no_extn = file.substr(0, strlen(p->d_name) - 4);
+		struct language_struct language_entry;
+		language_entry.filename = file_no_extn;
+		char* xmlFile = PageManager::LoadFileToBuffer(dir + p->d_name, NULL);
+		if (xmlFile == NULL) {
+			LOGERR("LoadLanguageListDir unable to load '%s'\n", language_entry.filename.c_str());
+			continue;
+		}
+		xml_document<> *doc = new xml_document<>();
+		doc->parse<0>(xmlFile);
+
+		xml_node<>* parent = doc->first_node("language");
+		if (!parent) {
+			LOGERR("Invalid language XML file '%s'\n", language_entry.filename.c_str());
+		} else {
+			xml_node<>* child = parent->first_node("display");
+			if (child) {
+				language_entry.displayvalue = child->value();
+			} else {
+				LOGERR("No display value for '%s'\n", language_entry.filename.c_str());
+				language_entry.displayvalue = language_entry.filename;
+			}
+			Language_List.push_back(language_entry);
+		}
+		doc->clear();
+		delete doc;
+		free(xmlFile);
+	}
+	closedir(d);
+}
+
+void PageManager::LoadLanguageList(ZipArchiveHandle package) {
+	Language_List.clear();
+	if (TWFunc::Path_Exists(TWRES "customlanguages"))
+		TWFunc::removeDir(TWRES "customlanguages", true);
+	if (package) {
+		TWFunc::Recursive_Mkdir(TWRES "customlanguages");
+		ExtractPackageRecursive(package, "/", TWRES "customlanguages", nullptr, nullptr);
+
+		// package->ExtractRecursive("languages", TWRES "customlanguages/");
+		LoadLanguageListDir(TWRES "customlanguages/");
+	} else {
+		LoadLanguageListDir(TWRES "languages/");
+	}
+
+	std::sort(Language_List.begin(), Language_List.end());
+}
+
+void PageManager::LoadLanguage(string filename) {
+	string actual_filename;
+	if (TWFunc::Path_Exists(TWRES "customlanguages/" + filename + ".xml"))
+		actual_filename = TWRES "customlanguages/" + filename + ".xml";
+	else
+		actual_filename = TWRES "languages/" + filename + ".xml";
+	char* xmlFile = PageManager::LoadFileToBuffer(actual_filename, NULL);
+	if (xmlFile == NULL)
+		LOGERR("Unable to load '%s'\n", actual_filename.c_str());
+	else {
+		mCurrentSet->LoadLanguage(xmlFile, NULL);
+		free(xmlFile);
+	}
+	PartitionManager.Translate_Partition_Display_Names();
+}
+
+int PageManager::LoadPackage(std::string name, std::string package, std::string startpage)
+{
+	std::string mainxmlfilename = package;
+	char* languageFile = NULL;
+	char* baseLanguageFile = NULL;
+	PageSet* pageSet = NULL;
+	int ret;
+
+	mReloadTheme = false;
+	mStartPage = startpage;
+
+	// init the loading context
+	LoadingContext ctx;
+
+	// Open the XML file
+	LOGINFO("Loading package: %s (%s)\n", name.c_str(), package.c_str());
+	if (package.size() > 4 && package.substr(package.size() - 4) != ".zip")
+	{
+		LOGINFO("Load XML directly\n");
+		tw_x_offset = TW_X_OFFSET;
+		tw_y_offset = TW_Y_OFFSET;
+		tw_w_offset = TW_W_OFFSET;
+		tw_h_offset = TW_H_OFFSET;
+		if (name != "splash") {
+			LoadLanguageList(NULL);
+			languageFile = LoadFileToBuffer(TWRES "languages/en.xml", NULL);
+		}
+		ctx.basepath = TWRES;
+	}
+	else
+	{
+		LOGINFO("Loading zip theme\n");
+		tw_x_offset = 0;
+		tw_y_offset = 0;
+		tw_w_offset = 0;
+		tw_h_offset = 0;
+		if (!TWFunc::Path_Exists(package)) {
+			return -1;
+		}
+
+		ZipArchiveHandle Zip;
+		int err = OpenArchive(package.c_str(), &Zip);
+
+		if (err != 0)
+			return -1;
+
+		ctx.zip = Zip;
+		mainxmlfilename = "ui.xml";
+		LoadLanguageList(ctx.zip);
+		languageFile = LoadFileToBuffer("languages/en.xml", ctx.zip);
+		baseLanguageFile = LoadFileToBuffer(TWRES "languages/en.xml", NULL);
+	}
+
+	// Before loading, mCurrentSet must be the loading package so we can find resources
+	pageSet = mCurrentSet;
+	mCurrentSet = new PageSet();
+
+	if (baseLanguageFile) {
+		mCurrentSet->LoadLanguage(baseLanguageFile, NULL);
+		free(baseLanguageFile);
+	}
+
+	if (languageFile) {
+		mCurrentSet->LoadLanguage(languageFile, ctx.zip);
+		free(languageFile);
+	}
+
+	// Load and parse the XML and all includes
+	currentLoadingContext = &ctx; // required to find styles
+	ret = mCurrentSet->Load(ctx, mainxmlfilename);
+	currentLoadingContext = NULL;
+
+	if (ret == 0) {
+		mCurrentSet->SetPage(startpage);
+		mPageSets.insert(std::pair<std::string, PageSet*>(name, mCurrentSet));
+	} else {
+		if (ret != TW_THEME_VER_ERR)
+			LOGERR("Package %s failed to load.\n", name.c_str());
+	}
+
+	// reset to previous pageset
+	mCurrentSet = pageSet;
+
+	if (ctx.zip) {
+		CloseArchive(ctx.zip);
+	}
+	return ret;
+}
+
+PageSet* PageManager::FindPackage(std::string name)
+{
+	std::map<std::string, PageSet*>::iterator iter;
+
+	iter = mPageSets.find(name);
+	if (iter != mPageSets.end())
+		return (*iter).second;
+
+	LOGERR("Unable to locate package %s\n", name.c_str());
+	return NULL;
+}
+
+PageSet* PageManager::SelectPackage(std::string name)
+{
+	LOGINFO("Switching packages (%s)\n", name.c_str());
+	PageSet* tmp;
+
+	tmp = FindPackage(name);
+	if (tmp)
+	{
+		mCurrentSet = tmp;
+		mCurrentSet->MakeEmergencyConsoleIfNeeded();
+		mCurrentSet->NotifyVarChange("", "");
+	}
+	else
+		LOGERR("Unable to find package.\n");
+
+	return mCurrentSet;
+}
+
+int PageManager::ReloadPackage(std::string name, std::string package)
+{
+	std::map<std::string, PageSet*>::iterator iter;
+
+	mReloadTheme = false;
+
+	iter = mPageSets.find(name);
+	if (iter == mPageSets.end())
+		return -1;
+
+	if (mMouseCursor)
+		mMouseCursor->ResetData(gr_fb_width(), gr_fb_height());
+
+	PageSet* set = (*iter).second;
+	mPageSets.erase(iter);
+
+	if (LoadPackage(name, package, mStartPage) != 0)
+	{
+		LOGINFO("Failed to load package '%s'.\n", package.c_str());
+		mPageSets.insert(std::pair<std::string, PageSet*>(name, set));
+		return -1;
+	}
+	if (mCurrentSet == set)
+		SelectPackage(name);
+	delete set;
+	GUIConsole::Translate_Now();
+	return 0;
+}
+
+void PageManager::ReleasePackage(std::string name)
+{
+	std::map<std::string, PageSet*>::iterator iter;
+
+	iter = mPageSets.find(name);
+	if (iter == mPageSets.end())
+		return;
+
+	PageSet* set = (*iter).second;
+	mPageSets.erase(iter);
+	delete set;
+	if (set == mCurrentSet)
+		mCurrentSet = NULL;
+	return;
+}
+
+int PageManager::RunReload() {
+	int ret_val = 0;
+	std::string theme_path;
+
+	if (!mReloadTheme)
+		return 0;
+
+	mReloadTheme = false;
+	theme_path = DataManager::GetSettingsStoragePath();
+	if (PartitionManager.Mount_By_Path(theme_path.c_str(), 1) < 0) {
+		LOGERR("Unable to mount %s during gui_reload_theme function.\n", theme_path.c_str());
+		ret_val = 1;
+	}
+
+	theme_path += "/TWRP/theme/ui.zip";
+	if (ret_val != 0 || ReloadPackage("TWRP", theme_path) != 0)
+	{
+		// Loading the custom theme failed - try loading the stock theme
+		LOGINFO("Attempting to reload stock theme...\n");
+		if (ReloadPackage("TWRP", TWRES "ui.xml"))
+		{
+			LOGERR("Failed to load base packages.\n");
+			ret_val = 1;
+		}
+	}
+	if (ret_val == 0) {
+		if (DataManager::GetStrValue("tw_language") != "en.xml") {
+			LOGINFO("Loading language '%s'\n", DataManager::GetStrValue("tw_language").c_str());
+			LoadLanguage(DataManager::GetStrValue("tw_language"));
+		}
+	}
+
+	// This makes the console re-translate
+	GUIConsole::Clear_For_Retranslation();
+
+	return ret_val;
+}
+
+void PageManager::RequestReload() {
+	mReloadTheme = true;
+}
+
+void PageManager::SetStartPage(const std::string& page_name) {
+	mStartPage = page_name;
+}
+
+int PageManager::ChangePage(std::string name)
+{
+	DataManager::SetValue("tw_operation_state", 0);
+	int ret = (mCurrentSet ? mCurrentSet->SetPage(name) : -1);
+	return ret;
+}
+
+std::string PageManager::GetCurrentPage()
+{
+	return mCurrentSet ? mCurrentSet->GetCurrentPage() : "";
+}
+
+int PageManager::ChangeOverlay(std::string name)
+{
+	if (name.empty())
+		return mCurrentSet->SetOverlay(NULL);
+	else
+	{
+		Page* page = mCurrentSet ? mCurrentSet->FindPage(name) : NULL;
+		return mCurrentSet->SetOverlay(page);
+	}
+}
+
+const ResourceManager* PageManager::GetResources()
+{
+	return (mCurrentSet ? mCurrentSet->GetResources() : NULL);
+}
+
+int PageManager::IsCurrentPage(Page* page)
+{
+	return (mCurrentSet ? mCurrentSet->IsCurrentPage(page) : 0);
+}
+
+int PageManager::Render(void)
+{
+	if (blankTimer.isScreenOff())
+		return 0;
+
+	int res = (mCurrentSet ? mCurrentSet->Render() : -1);
+	if (mMouseCursor)
+		mMouseCursor->Render();
+	return res;
+}
+
+HardwareKeyboard *PageManager::GetHardwareKeyboard()
+{
+	if (!mHardwareKeyboard)
+		mHardwareKeyboard = new HardwareKeyboard();
+	return mHardwareKeyboard;
+}
+
+xml_node<>* PageManager::FindStyle(std::string name)
+{
+	if (!currentLoadingContext)
+	{
+		LOGERR("FindStyle works only while loading a theme.\n");
+		return NULL;
+	}
+
+	for (std::vector<xml_node<>*>::iterator itr = currentLoadingContext->styles.begin(); itr != currentLoadingContext->styles.end(); itr++) {
+		xml_node<>* node = (*itr)->first_node("style");
+
+		while (node) {
+			if (!node->first_attribute("name"))
+				continue;
+
+			if (name == node->first_attribute("name")->value())
+				return node;
+			node = node->next_sibling("style");
+		}
+	}
+	return NULL;
+}
+
+MouseCursor *PageManager::GetMouseCursor()
+{
+	if (!mMouseCursor)
+		mMouseCursor = new MouseCursor(gr_fb_width(), gr_fb_height());
+	return mMouseCursor;
+}
+
+void PageManager::LoadCursorData(xml_node<>* node)
+{
+	if (!mMouseCursor)
+		mMouseCursor = new MouseCursor(gr_fb_width(), gr_fb_height());
+
+	mMouseCursor->LoadData(node);
+}
+
+int PageManager::Update(void)
+{
+	if (blankTimer.isScreenOff())
+		return 0;
+
+	if (RunReload())
+		return -2;
+
+	int res = (mCurrentSet ? mCurrentSet->Update() : -1);
+
+	if (mMouseCursor)
+	{
+		int c_res = mMouseCursor->Update();
+		if (c_res > res)
+			res = c_res;
+	}
+	return res;
+}
+
+int PageManager::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	return (mCurrentSet ? mCurrentSet->NotifyTouch(state, x, y) : -1);
+}
+
+int PageManager::NotifyKey(int key, bool down)
+{
+	return (mCurrentSet ? mCurrentSet->NotifyKey(key, down) : -1);
+}
+
+int PageManager::NotifyCharInput(int ch)
+{
+	return (mCurrentSet ? mCurrentSet->NotifyCharInput(ch) : -1);
+}
+
+int PageManager::SetKeyBoardFocus(int inFocus)
+{
+	return (mCurrentSet ? mCurrentSet->SetKeyBoardFocus(inFocus) : -1);
+}
+
+int PageManager::NotifyVarChange(std::string varName, std::string value)
+{
+	return (mCurrentSet ? mCurrentSet->NotifyVarChange(varName, value) : -1);
+}
+
+void PageManager::AddStringResource(std::string resource_source, std::string resource_name, std::string value)
+{
+	if (mCurrentSet)
+		mCurrentSet->AddStringResource(resource_source, resource_name, value);
+}
+
+extern "C" void gui_notifyVarChange(const char *name, const char* value)
+{
+	if (!gGuiRunning)
+		return;
+
+	PageManager::NotifyVarChange(name, value);
+}
diff --git a/gui/pages.h b/gui/pages.h
new file mode 100644
index 0000000..d407162
--- /dev/null
+++ b/gui/pages.h
@@ -0,0 +1,7 @@
+#ifndef _PAGES_HEADER
+#define _PAGES_HEADER
+
+void gui_notifyVarChange(const char *name, const char* value);
+
+#endif  // _PAGES_HEADER
+
diff --git a/gui/pages.hpp b/gui/pages.hpp
new file mode 100755
index 0000000..7688604
--- /dev/null
+++ b/gui/pages.hpp
@@ -0,0 +1,212 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// pages.hpp - Base classes for page manager of GUI
+
+#ifndef _PAGES_HEADER_HPP
+#define _PAGES_HEADER_HPP
+
+#include <vector>
+#include <map>
+#include <string>
+#include "ziparchive/zip_archive.h"
+#include "rapidxml.hpp"
+#include "gui.hpp"
+using namespace rapidxml;
+
+enum TOUCH_STATE {
+	TOUCH_START = 0,
+	TOUCH_DRAG = 1,
+	TOUCH_RELEASE = 2,
+	TOUCH_HOLD = 3,
+	TOUCH_REPEAT = 4
+};
+
+struct COLOR {
+	unsigned char red;
+	unsigned char green;
+	unsigned char blue;
+	unsigned char alpha;
+	COLOR() : red(0), green(0), blue(0), alpha(0) {}
+	COLOR(unsigned char r, unsigned char g, unsigned char b, unsigned char a = 255)
+		: red(r), green(g), blue(b), alpha(a) {}
+};
+
+struct language_struct {
+	std::string filename;
+	std::string displayvalue;
+};
+
+inline bool operator < (const language_struct& language1, const language_struct& language2)
+{
+	return language1.displayvalue < language2.displayvalue;
+}
+
+extern std::vector<language_struct> Language_List;
+
+// Utility Functions
+int ConvertStrToColor(std::string str, COLOR* color);
+int gui_forceRender(void);
+int gui_changePage(std::string newPage);
+int gui_changeOverlay(std::string newPage);
+
+class Resource;
+class ResourceManager;
+class RenderObject;
+class ActionObject;
+class InputObject;
+class MouseCursor;
+class GUIObject;
+class HardwareKeyboard;
+
+class Page
+{
+public:
+	Page(xml_node<>* page, std::vector<xml_node<>*> *templates);
+	virtual ~Page();
+
+	std::string GetName(void)   { return mName; }
+
+public:
+	virtual int Render(void);
+	virtual int Update(void);
+	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
+	virtual int NotifyKey(int key, bool down);
+	virtual int NotifyCharInput(int ch);
+	virtual int SetKeyBoardFocus(int inFocus);
+	virtual int NotifyVarChange(std::string varName, std::string value);
+	virtual void SetPageFocus(int inFocus);
+
+protected:
+	std::string mName;
+	std::vector<GUIObject*> mObjects;
+	std::vector<RenderObject*> mRenders;
+	std::vector<ActionObject*> mActions;
+	std::vector<InputObject*> mInputs;
+
+	ActionObject* mTouchStart;
+	COLOR mBackground;
+
+protected:
+	bool ProcessNode(xml_node<>* page, std::vector<xml_node<>*> *templates, int depth);
+};
+
+struct LoadingContext;
+
+class PageSet
+{
+public:
+	PageSet();
+	virtual ~PageSet();
+
+public:
+	int Load(LoadingContext& ctx, const std::string& filename);
+	int LoadLanguage(char* languageFile, ZipArchiveHandle package);
+	void MakeEmergencyConsoleIfNeeded();
+
+	Page* FindPage(std::string name);
+	int SetPage(std::string page);
+	int SetOverlay(Page* page);
+	const ResourceManager* GetResources();
+
+	// Helper routine for identifing if we're the current page
+	int IsCurrentPage(Page* page);
+	std::string GetCurrentPage() const;
+
+	// These are routing routines
+	int Render(void);
+	int Update(void);
+	int NotifyTouch(TOUCH_STATE state, int x, int y);
+	int NotifyKey(int key, bool down);
+	int NotifyCharInput(int ch);
+	int SetKeyBoardFocus(int inFocus);
+	int NotifyVarChange(std::string varName, std::string value);
+
+	void AddStringResource(std::string resource_source, std::string resource_name, std::string value);
+
+protected:
+	int LoadDetails(LoadingContext& ctx, xml_node<>* root);
+	int LoadPages(LoadingContext& ctx, xml_node<>* pages);
+	int LoadVariables(xml_node<>* vars);
+
+protected:
+	ResourceManager* mResources;
+	std::vector<Page*> mPages;
+	Page* mCurrentPage;
+	std::vector<Page*> mOverlays; // Special case for popup dialogs and the lock screen
+};
+
+class PageManager
+{
+public:
+	// Used by GUI
+	static char* LoadFileToBuffer(std::string filename, ZipArchiveHandle package);
+	static void LoadLanguageList(ZipArchiveHandle package);
+	static void LoadLanguage(std::string filename);
+	static int LoadPackage(std::string name, std::string package, std::string startpage);
+	static PageSet* SelectPackage(std::string name);
+	static int ReloadPackage(std::string name, std::string package);
+	static void ReleasePackage(std::string name);
+	static int RunReload();
+	static void RequestReload();
+	static void SetStartPage(const std::string& page_name);
+
+	// Used for actions and pages
+	static int ChangePage(std::string name);
+	static int ChangeOverlay(std::string name);
+	static const ResourceManager* GetResources();
+	static std::string GetCurrentPage();
+
+	// Helper to identify if a particular page is the active page
+	static int IsCurrentPage(Page* page);
+
+	// These are routing routines
+	static int Render(void);
+	static int Update(void);
+	static int NotifyTouch(TOUCH_STATE state, int x, int y);
+	static int NotifyKey(int key, bool down);
+	static int NotifyCharInput(int ch);
+	static int SetKeyBoardFocus(int inFocus);
+	static int NotifyVarChange(std::string varName, std::string value);
+
+	static MouseCursor *GetMouseCursor();
+	static void LoadCursorData(xml_node<>* node);
+
+	static HardwareKeyboard *GetHardwareKeyboard();
+
+	static xml_node<>* FindStyle(std::string name);
+	static void AddStringResource(std::string resource_source, std::string resource_name, std::string value);
+
+protected:
+	static PageSet* FindPackage(std::string name);
+	static void LoadLanguageListDir(std::string dir);
+	static void Translate_Partition(const char* path, const char* resource_name, const char* default_value);
+	static void Translate_Partition(const char* path, const char* resource_name, const char* default_value, const char* storage_resource_name, const char* storage_default_value);
+	static void Translate_Partition_Display_Names();
+
+protected:
+	static std::map<std::string, PageSet*> mPageSets;
+	static PageSet* mCurrentSet;
+	static MouseCursor *mMouseCursor;
+	static HardwareKeyboard *mHardwareKeyboard;
+	static bool mReloadTheme;
+	static std::string mStartPage;
+	static LoadingContext* currentLoadingContext;
+};
+
+#endif  // _PAGES_HEADER_HPP
diff --git a/gui/partitionlist.cpp b/gui/partitionlist.cpp
new file mode 100755
index 0000000..406dbbb
--- /dev/null
+++ b/gui/partitionlist.cpp
@@ -0,0 +1,296 @@
+/*
+	Copyright 2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+#include "../data.hpp"
+#include "../partitions.hpp"
+
+GUIPartitionList::GUIPartitionList(xml_node<>* node) : GUIScrollList(node)
+{
+	xml_attribute<>* attr;
+	xml_node<>* child;
+
+	mIconSelected = mIconUnselected = NULL;
+	mUpdate = 0;
+	updateList = false;
+
+	child = FindNode(node, "icon");
+	if (child)
+	{
+		mIconSelected = LoadAttrImage(child, "selected");
+		mIconUnselected = LoadAttrImage(child, "unselected");
+	}
+
+	// Handle the result variable
+	child = FindNode(node, "data");
+	if (child)
+	{
+		attr = child->first_attribute("name");
+		if (attr)
+			mVariable = attr->value();
+		attr = child->first_attribute("selectedlist");
+		if (attr)
+			selectedList = attr->value();
+	}
+
+	int iconWidth = 0, iconHeight = 0;
+	if (mIconSelected && mIconSelected->GetResource() && mIconUnselected && mIconUnselected->GetResource()) {
+		iconWidth = std::max(mIconSelected->GetWidth(), mIconUnselected->GetWidth());
+		iconHeight = std::max(mIconSelected->GetHeight(), mIconUnselected->GetHeight());
+	} else if (mIconSelected && mIconSelected->GetResource()) {
+		iconWidth = mIconSelected->GetWidth();
+		iconHeight = mIconSelected->GetHeight();
+	} else if (mIconUnselected && mIconUnselected->GetResource()) {
+		iconWidth = mIconUnselected->GetWidth();
+		iconHeight = mIconUnselected->GetHeight();
+	}
+	SetMaxIconSize(iconWidth, iconHeight);
+
+	child = FindNode(node, "listtype");
+	if (child && (attr = child->first_attribute("name"))) {
+		ListType = attr->value();
+		updateList = true;
+	} else {
+		mList.clear();
+		LOGERR("No partition listtype specified for partitionlist GUI element\n");
+		return;
+	}
+}
+
+GUIPartitionList::~GUIPartitionList()
+{
+}
+
+int GUIPartitionList::Update(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	// Check for changes in mount points if the list type is mount and update the list and render if needed
+	if (ListType == "mount") {
+		int listSize = mList.size();
+		for (int i = 0; i < listSize; i++) {
+			if (PartitionManager.Is_Mounted_By_Path(mList.at(i).Mount_Point) && !mList.at(i).selected) {
+				mList.at(i).selected = 1;
+				mUpdate = 1;
+			} else if (!PartitionManager.Is_Mounted_By_Path(mList.at(i).Mount_Point) && mList.at(i).selected) {
+				mList.at(i).selected = 0;
+				mUpdate = 1;
+			}
+		}
+	}
+
+	GUIScrollList::Update();
+
+	if (updateList) {
+		// Completely update the list if needed -- Used primarily for
+		// restore as the list for restore will change depending on what
+		// partitions were backed up
+		mList.clear();
+		PartitionManager.Get_Partition_List(ListType, &mList);
+		SetVisibleListLocation(0);
+		updateList = false;
+		mUpdate = 1;
+		if (ListType == "backup" || ListType == "flashimg")
+			MatchList();
+	}
+
+	if (mUpdate) {
+		mUpdate = 0;
+		if (Render() == 0)
+			return 2;
+	}
+
+	return 0;
+}
+
+int GUIPartitionList::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	GUIScrollList::NotifyVarChange(varName, value);
+
+	if (!isConditionTrue())
+		return 0;
+
+	if (varName == mVariable && !mUpdate)
+	{
+		if (ListType == "storage") {
+			currentValue = value;
+			SetPosition();
+		} else if (ListType == "backup") {
+			updateList = true;
+			MatchList();
+		} else if (ListType == "restore") {
+			updateList = true;
+			SetVisibleListLocation(0);
+		}
+
+		mUpdate = 1;
+		return 0;
+	}
+	return 0;
+}
+
+void GUIPartitionList::SetPageFocus(int inFocus)
+{
+	GUIScrollList::SetPageFocus(inFocus);
+	if (inFocus) {
+		if (ListType == "storage" || ListType == "flashimg") {
+			DataManager::GetValue(mVariable, currentValue);
+			SetPosition();
+		}
+		updateList = true;
+		mUpdate = 1;
+	}
+}
+
+void GUIPartitionList::MatchList(void) {
+	int i, listSize = mList.size();
+	string variablelist, searchvalue;
+	size_t pos;
+
+	DataManager::GetValue(mVariable, variablelist);
+
+	for (i = 0; i < listSize; i++) {
+		searchvalue = mList.at(i).Mount_Point + ";";
+		pos = variablelist.find(searchvalue);
+		if (pos != string::npos) {
+			mList.at(i).selected = 1;
+			TWPartition* t_part = PartitionManager.Find_Partition_By_Path(mList.at(i).Mount_Point);
+			DataManager::SetValue("tw_is_slot_part", t_part != NULL ? (int) t_part->SlotSelect : 0);
+		} else {
+			mList.at(i).selected = 0;
+		}
+	}
+}
+
+void GUIPartitionList::SetPosition() {
+	int listSize = mList.size();
+
+	SetVisibleListLocation(0);
+	for (int i = 0; i < listSize; i++) {
+		if (mList.at(i).Mount_Point == currentValue) {
+			mList.at(i).selected = 1;
+			SetVisibleListLocation(i);
+		} else {
+			mList.at(i).selected = 0;
+		}
+	}
+}
+
+size_t GUIPartitionList::GetItemCount()
+{
+	return mList.size();
+}
+
+void GUIPartitionList::RenderItem(size_t itemindex, int yPos, bool selected)
+{
+	// note: the "selected" parameter above is for the currently touched item
+	// don't confuse it with the more persistent "selected" flag per list item used below
+	ImageResource* icon = mList.at(itemindex).selected ? mIconSelected : mIconUnselected;
+	const std::string& text = mList.at(itemindex).Display_Name;
+
+	RenderStdItem(yPos, selected, icon, text.c_str());
+}
+
+void GUIPartitionList::NotifySelect(size_t item_selected)
+{
+	if (item_selected < mList.size()) {
+		int listSize = mList.size();
+		if (ListType == "mount") {
+			if (!mList.at(item_selected).selected) {
+				if (PartitionManager.Mount_By_Path(mList.at(item_selected).Mount_Point, true)) {
+					mList.at(item_selected).selected = 1;
+					PartitionManager.Add_MTP_Storage(mList.at(item_selected).Mount_Point);
+					mUpdate = 1;
+				}
+			} else {
+				if (PartitionManager.UnMount_By_Path(mList.at(item_selected).Mount_Point, true)) {
+					mList.at(item_selected).selected = 0;
+					mUpdate = 1;
+				}
+			}
+		} else if (!mVariable.empty()) {
+			if (ListType == "storage") {
+				int i;
+				std::string str = mList.at(item_selected).Mount_Point;
+				bool update_size = false;
+				TWPartition* Part = PartitionManager.Find_Partition_By_Path(str);
+				if (Part == NULL) {
+					LOGERR("Unable to locate partition for '%s'\n", str.c_str());
+					return;
+				}
+				if (!Part->Is_Mounted() && Part->Removable)
+					update_size = true;
+				if (!Part->Mount(true)) {
+					// Do Nothing
+				} else if (update_size && !Part->Update_Size(true)) {
+					// Do Nothing
+				} else {
+					for (i=0; i<listSize; i++)
+						mList.at(i).selected = 0;
+
+					if (update_size) {
+						char free_space[255];
+						sprintf(free_space, "%llu", Part->Free / 1024 / 1024);
+						mList.at(item_selected).Display_Name = Part->Storage_Name + " (";
+						mList.at(item_selected).Display_Name += free_space;
+						mList.at(item_selected).Display_Name += "MB)";
+					}
+					mList.at(item_selected).selected = 1;
+					mUpdate = 1;
+					DataManager::SetValue(mVariable, str);
+				}
+			} else {
+				if (ListType == "flashimg") { // only one item can be selected for flashing images
+					for (int i=0; i<listSize; i++)
+						mList.at(i).selected = 0;
+				}
+				if (mList.at(item_selected).selected)
+					mList.at(item_selected).selected = 0;
+				else
+					mList.at(item_selected).selected = 1;
+					TWPartition* t_part = PartitionManager.Find_Partition_By_Path(mList.at(item_selected).Mount_Point);
+					DataManager::SetValue("tw_is_slot_part", t_part != NULL ? (int) t_part->SlotSelect : 0);
+
+				int i;
+				string variablelist;
+				for (i=0; i<listSize; i++) {
+					if (mList.at(i).selected) {
+						variablelist += mList.at(i).Mount_Point + ";";
+					}
+				}
+
+				mUpdate = 1;
+				if (selectedList.empty())
+					DataManager::SetValue(mVariable, variablelist);
+				else
+					DataManager::SetValue(selectedList, variablelist);
+			}
+		}
+	}
+}
diff --git a/gui/patternpassword.cpp b/gui/patternpassword.cpp
new file mode 100755
index 0000000..cedfda3
--- /dev/null
+++ b/gui/patternpassword.cpp
@@ -0,0 +1,494 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+#include <sstream>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+#include "../twrp-functions.hpp"
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUIPatternPassword::GUIPatternPassword(xml_node<>* node)
+	: GUIObject(node)
+{
+	xml_node<>* child;
+
+	// 3x3 is the default.
+	mGridSize = 3;
+	mDots = new Dot[mGridSize * mGridSize];
+	mConnectedDots = new int[mGridSize * mGridSize];
+
+	ResetActiveDots();
+	mTrackingTouch = false;
+	mNeedRender = true;
+
+	ConvertStrToColor("blue", &mDotColor);
+	ConvertStrToColor("white", &mActiveDotColor);
+	ConvertStrToColor("blue", &mLineColor);
+
+	mDotImage = mActiveDotImage = NULL;
+	mDotCircle = mActiveDotCircle = NULL;
+	mDotRadius = 50;
+	mLineWidth = 35;
+
+	mAction = NULL;
+	mUpdate = 0;
+
+	if (!node)
+		return;
+
+	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH, &mPlacement);
+
+	mAction = new GUIAction(node);
+
+	child = FindNode(node, "dot");
+	if (child)
+	{
+		mDotColor = LoadAttrColor(child, "color", mDotColor);
+		mActiveDotColor = LoadAttrColor(child, "activecolor", mActiveDotColor);
+		mDotRadius = LoadAttrIntScaleX(child, "radius", mDotRadius);
+
+		mDotImage = LoadAttrImage(child, "image");
+		mActiveDotImage = LoadAttrImage(child, "activeimage");
+	}
+
+	child = FindNode(node, "line");
+	if (child)
+	{
+		mLineColor = LoadAttrColor(child, "color", mLineColor);
+		mLineWidth = LoadAttrIntScaleX(child, "width", mLineWidth);
+	}
+
+	child = FindNode(node, "data");
+	if (child)
+		mPassVar = LoadAttrString(child, "name", "");
+
+	child = FindNode(node, "size");
+	if (child) {
+		mSizeVar = LoadAttrString(child, "name", "");
+
+		// Use the configured default, if set.
+		size_t size = LoadAttrInt(child, "default", mGridSize);
+		Resize(size);
+	}
+
+	if (!mDotImage || !mDotImage->GetResource() || !mActiveDotImage || !mActiveDotImage->GetResource())
+	{
+		mDotCircle = gr_render_circle(mDotRadius, mDotColor.red, mDotColor.green, mDotColor.blue, mDotColor.alpha);
+		mActiveDotCircle = gr_render_circle(mDotRadius/2, mActiveDotColor.red, mActiveDotColor.green, mActiveDotColor.blue, mActiveDotColor.alpha);
+	}
+	else if (mDotImage && mDotImage->GetResource())
+		mDotRadius = mDotImage->GetWidth()/2;
+
+	SetRenderPos(mRenderX, mRenderY, mRenderW, mRenderH);
+}
+
+GUIPatternPassword::~GUIPatternPassword()
+{
+	delete mDotImage;
+	delete mActiveDotImage;
+	delete mAction;
+
+	delete[] mDots;
+	delete[] mConnectedDots;
+
+	if (mDotCircle)
+		gr_free_surface(mDotCircle);
+
+	if (mActiveDotCircle)
+		gr_free_surface(mActiveDotCircle);
+}
+
+void GUIPatternPassword::ResetActiveDots()
+{
+	mConnectedDotsLen = 0;
+	mCurLineX = mCurLineY = -1;
+	for (size_t i = 0; i < mGridSize * mGridSize; ++i)
+		mDots[i].active = false;
+}
+
+int GUIPatternPassword::SetRenderPos(int x, int y, int w, int h)
+{
+	mRenderX = x;
+	mRenderY = y;
+
+	if (w || h)
+	{
+		mRenderW = w;
+		mRenderH = h;
+
+		mAction->SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+		SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+	}
+
+	CalculateDotPositions();
+	return 0;
+}
+
+void GUIPatternPassword::CalculateDotPositions(void)
+{
+	const int num_gaps = mGridSize - 1;
+	const int step_x = (mRenderW - mDotRadius*2) / num_gaps;
+	const int step_y = (mRenderH - mDotRadius*2) / num_gaps;
+	int x = mRenderX;
+	int y = mRenderY;
+
+	/* Order is important for keyphrase generation:
+	 *
+	 *   0    1    2    3 ...  n-1
+	 *   n  n+1  n+2  n+3 ... 2n-1
+	 *  2n 2n+1 2n+2 2n+3 ... 3n-1
+	 *  3n 3n+1 3n+2 3n+3 ... 4n-1
+	 *   :  :    :    :
+	 *                       n*n-1
+	 */
+
+	for (size_t r = 0; r < mGridSize; ++r)
+	{
+		for (size_t c = 0; c < mGridSize; ++c)
+		{
+			mDots[mGridSize*r + c].x = x;
+			mDots[mGridSize*r + c].y = y;
+			x += step_x;
+		}
+		x = mRenderX;
+		y += step_y;
+	}
+}
+
+int GUIPatternPassword::Render(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	gr_color(mLineColor.red, mLineColor.green, mLineColor.blue, mLineColor.alpha);
+	for (size_t i = 1; i < mConnectedDotsLen; ++i) {
+		const Dot& dp = mDots[mConnectedDots[i-1]];
+		const Dot& dc = mDots[mConnectedDots[i]];
+		gr_line(dp.x + mDotRadius, dp.y + mDotRadius, dc.x + mDotRadius, dc.y + mDotRadius, mLineWidth);
+	}
+
+	if (mConnectedDotsLen > 0 && mTrackingTouch) {
+		const Dot& dc = mDots[mConnectedDots[mConnectedDotsLen-1]];
+		gr_line(dc.x + mDotRadius, dc.y + mDotRadius, mCurLineX, mCurLineY, mLineWidth);
+	}
+
+	for (size_t i = 0; i < mGridSize * mGridSize; ++i) {
+		if (mDotCircle) {
+			gr_blit(mDotCircle, 0, 0, gr_get_width(mDotCircle), gr_get_height(mDotCircle), mDots[i].x, mDots[i].y);
+			if (mDots[i].active) {
+				gr_blit(mActiveDotCircle, 0, 0, gr_get_width(mActiveDotCircle), gr_get_height(mActiveDotCircle), mDots[i].x + mDotRadius/2, mDots[i].y + mDotRadius/2);
+			}
+		} else {
+			if (mDots[i].active && mActiveDotImage && mActiveDotImage->GetResource()) {
+				gr_blit(mActiveDotImage->GetResource(), 0, 0, mActiveDotImage->GetWidth(), mActiveDotImage->GetHeight(),
+						mDots[i].x + (mDotRadius - mActiveDotImage->GetWidth()/2), mDots[i].y + (mDotRadius - mActiveDotImage->GetHeight()/2));
+			} else if (mDotImage && mDotImage->GetResource()) {
+				gr_blit(mDotImage->GetResource(), 0, 0, mDotImage->GetWidth(), mDotImage->GetHeight(), mDots[i].x, mDots[i].y);
+			}
+		}
+	}
+	return 0;
+}
+
+int GUIPatternPassword::Update(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	int res = mNeedRender ? 2 : 1;
+	mNeedRender = false;
+	return res;
+}
+
+void GUIPatternPassword::Resize(size_t n) {
+	if (mGridSize == n)
+		return;
+
+	delete[] mDots;
+	delete[] mConnectedDots;
+
+	mGridSize = n;
+	mDots = new Dot[n*n];
+	mConnectedDots = new int[n*n];
+
+	ResetActiveDots();
+	CalculateDotPositions();
+	mTrackingTouch = false;
+	mNeedRender = true;
+}
+
+static int pow(int x, int i)
+{
+	int result = 1;
+	if (i<0)
+		return 0;
+	while(i-- > 0)
+		result *= x;
+	return result;
+}
+
+static bool IsInCircle(int x, int y, int ox, int oy, int r)
+{
+	return pow(x - ox, 2) + pow(y - oy, 2) <= pow(r, 2);
+}
+
+int GUIPatternPassword::InDot(int x, int y)
+{
+	for (size_t i = 0; i < mGridSize * mGridSize; ++i) {
+		if (IsInCircle(x, y, mDots[i].x + mDotRadius, mDots[i].y + mDotRadius, mDotRadius*3))
+			return i;
+	}
+	return -1;
+}
+
+bool GUIPatternPassword::DotUsed(int dot_idx)
+{
+	for (size_t i = 0; i < mConnectedDotsLen; ++i) {
+		if (mConnectedDots[i] == dot_idx)
+			return true;
+	}
+	return false;
+}
+
+void GUIPatternPassword::ConnectDot(int dot_idx)
+{
+	if (mConnectedDotsLen >= mGridSize * mGridSize)
+	{
+		LOGERR("mConnectedDots in GUIPatternPassword has overflown!\n");
+		return;
+	}
+
+	mConnectedDots[mConnectedDotsLen++] = dot_idx;
+	mDots[dot_idx].active = true;
+}
+
+void GUIPatternPassword::ConnectIntermediateDots(int next_dot_idx)
+{
+	if (mConnectedDotsLen == 0)
+		return;
+
+	const int prev_dot_idx = mConnectedDots[mConnectedDotsLen-1];
+
+	int px = prev_dot_idx % mGridSize;
+	int py = prev_dot_idx / mGridSize;
+
+	int nx = next_dot_idx % mGridSize;
+	int ny = next_dot_idx / mGridSize;
+
+	/*
+	 * We connect all dots that are in a straight line between the previous dot
+	 * and the next one. This is simple for 3x3, but is more complicated for
+	 * larger grids.
+	 *
+	 * Weirdly, Android doesn't do the logical thing when it comes to connecting
+	 * dots between two points. Rather than simply adding all points that lie
+	 * on the line between the start and end points, it instead only connects
+	 * dots that are adjacent in only three directions -- horizontal, vertical
+	 * and diagonal (45°).
+	 *
+	 * So we can just iterate over the correct axes, taking care to ensure that
+	 * the order in which the intermediate points are added to the pattern is
+	 * correct.
+	 */
+
+	int x = px;
+	int y = py;
+
+	int Dx = (nx > px) ? 1 : -1;
+	int Dy = (ny > py) ? 1 : -1;
+
+	// Vertical lines.
+	if (px == nx)
+		Dx = 0;
+
+	// Horizontal lines.
+	else if (py == ny)
+		Dy = 0;
+
+	// Diagonal lines (|∆x| = |∆y|).
+	else if (abs(px - nx) == abs(py - ny))
+		;
+
+	// No valid intermediate dots.
+	else
+		return;
+
+	// Iterate along axis, adding dots in the correct order.
+	while ((Dy == 0 || y != ny - Dy) && (Dx == 0 || x != nx - Dx)) {
+		x += Dx;
+		y += Dy;
+
+		int idx = mGridSize * y + x;
+		if (!DotUsed(idx))
+			ConnectDot(idx);
+	}
+}
+
+int GUIPatternPassword::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	if (!isConditionTrue())
+		return -1;
+
+	switch (state)
+	{
+		case TOUCH_START:
+		{
+			const int dot_idx = InDot(x, y);
+			if (dot_idx == -1)
+				break;
+
+			mTrackingTouch = true;
+			ResetActiveDots();
+			ConnectDot(dot_idx);
+
+#ifndef TW_NO_HAPTICS
+			DataManager::Vibrate("tw_button_vibrate");
+#endif
+
+			mCurLineX = x;
+			mCurLineY = y;
+			mNeedRender = true;
+			break;
+		}
+		case TOUCH_DRAG:
+		{
+			if (!mTrackingTouch)
+				break;
+
+			const int dot_idx = InDot(x, y);
+			if (dot_idx != -1 && !DotUsed(dot_idx))
+			{
+				ConnectIntermediateDots(dot_idx);
+				ConnectDot(dot_idx);
+
+#ifndef TW_NO_HAPTICS
+				DataManager::Vibrate("tw_button_vibrate");
+#endif
+
+			}
+
+			mCurLineX = x;
+			mCurLineY = y;
+			mNeedRender = true;
+			break;
+		}
+		case TOUCH_RELEASE:
+		{
+			if (!mTrackingTouch)
+				break;
+
+			mNeedRender = true;
+			mTrackingTouch = false;
+			PatternDrawn();
+			ResetActiveDots();
+			break;
+		}
+		default:
+			break;
+	}
+	return 0;
+}
+
+int GUIPatternPassword::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	if (varName == mSizeVar) {
+		Resize(atoi(value.c_str()));
+		mUpdate = true;
+	}
+	return 0;
+}
+
+static unsigned int getSDKVersion(void) {
+	unsigned int sdkver = 23;
+	string sdkverstr = TWFunc::System_Property_Get("ro.build.version.sdk");
+	if (!sdkverstr.empty()) {
+		sdkver = (unsigned int)strtoull(sdkverstr.c_str(), NULL, 10);
+		sdkver = (sdkver != 0) ? sdkver : 23;
+	}
+	LOGINFO("sdk version is %u\n", sdkver);
+	return sdkver;
+}
+
+std::string GUIPatternPassword::GeneratePassphrase()
+{
+	char pattern[mConnectedDotsLen];
+	for (size_t i = 0; i < mConnectedDotsLen; i++) {
+		pattern[i] = (char) mConnectedDots[i];
+	}
+
+	std::stringstream pass;
+	char buffer[3] = {0};
+
+	if ((mGridSize == 3) || (getSDKVersion() >= 23)) {
+		// Marshmallow uses a consistent method
+		for (size_t i = 0; i < mConnectedDotsLen; i++) {
+			buffer[0] = (pattern[i] & 0xff) + '1';
+			pass << std::string(buffer);
+		}
+	} else {
+		/*
+		 * Okay, rant time for pre-Marshmallow ROMs.
+		 * It turns out that Android and CyanogenMod have *two* separate methods
+		 * for generating passphrases from patterns. This is a legacy issue, as
+		 * Android only supports 3x3 grids, and so we need to support both.
+		 * Luckily, CyanogenMod is in the same boat as us and needs to support
+		 * Android's 3x3 encryption style.
+		 *
+		 * In order to generate a 3x3 passphrase, add 1 to each dot index
+		 * and concatenate the string representation of the integers. No
+		 * padding should be added.
+		 *
+		 * For *all* other NxN passphrases (until a 16x16 grid comes along),
+		 * they are generated by taking "%.2x" for each dot index and
+		 * concatenating the results (without adding 1).
+		 */
+		for (size_t i = 0; i < mConnectedDotsLen; i++) {
+			snprintf(buffer, 3, "%.2x", pattern[i] & 0xff);
+			pass << std::string(buffer);
+		}
+	}
+
+	return pass.str();
+}
+
+void GUIPatternPassword::PatternDrawn()
+{
+	if (!mPassVar.empty() && mConnectedDotsLen > 0)
+		DataManager::SetValue(mPassVar, GeneratePassphrase());
+
+	if (mAction)
+		mAction->doActions();
+}
diff --git a/gui/progressbar.cpp b/gui/progressbar.cpp
new file mode 100644
index 0000000..62ce7ce
--- /dev/null
+++ b/gui/progressbar.cpp
@@ -0,0 +1,247 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// progressbar.cpp - GUIProgressBar object
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUIProgressBar::GUIProgressBar(xml_node<>* node) : GUIObject(node)
+{
+	xml_node<>* child;
+
+	mEmptyBar = NULL;
+	mFullBar = NULL;
+	mLastPos = 0;
+	mSlide = 0.0;
+	mSlideInc = 0.0;
+
+	if (!node)
+	{
+		LOGERR("GUIProgressBar created without XML node\n");
+		return;
+	}
+
+	child = FindNode(node, "resource");
+	if (child)
+	{
+		mEmptyBar = LoadAttrImage(child, "empty");
+		mFullBar = LoadAttrImage(child, "full");
+	}
+
+	// Load the placement
+	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY);
+
+	// Load the data
+	child = FindNode(node, "data");
+	if (child)
+	{
+		mMinValVar = LoadAttrString(child, "min");
+		mMaxValVar = LoadAttrString(child, "max");
+		mCurValVar = LoadAttrString(child, "name");
+	}
+
+	if (mEmptyBar && mEmptyBar->GetResource()) {
+		mRenderW = mEmptyBar->GetWidth();
+		mRenderH = mEmptyBar->GetHeight();
+	} else {
+		mRenderW = mRenderH = 0;
+	}
+}
+
+int GUIProgressBar::Render(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	// This handles making sure timing updates occur
+	Update();
+	return RenderInternal();
+}
+
+int GUIProgressBar::RenderInternal(void)
+{
+	if (!mEmptyBar || !mEmptyBar->GetResource())
+		return -1;
+
+	if (!mFullBar || !mFullBar->GetResource())
+		return -1;
+
+	gr_blit(mEmptyBar->GetResource(), 0, 0, mRenderW, mRenderH, mRenderX, mRenderY);
+	gr_blit(mFullBar->GetResource(), 0, 0, mLastPos, mRenderH, mRenderX, mRenderY);
+	return 0;
+}
+
+int GUIProgressBar::Update(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	std::string str;
+	int min, max, cur, pos;
+
+	if (mMinValVar.empty())
+		min = 0;
+	else
+	{
+		str.clear();
+		if (atoi(mMinValVar.c_str()) != 0)
+			str = mMinValVar;
+		else
+			DataManager::GetValue(mMinValVar, str);
+		min = atoi(str.c_str());
+	}
+
+	if (mMaxValVar.empty())
+		max = 100;
+	else
+	{
+		str.clear();
+		if (atoi(mMaxValVar.c_str()) != 0)
+			str = mMaxValVar;
+		else
+			DataManager::GetValue(mMaxValVar, str);
+		max = atoi(str.c_str());
+	}
+
+	str.clear();
+	DataManager::GetValue(mCurValVar, str);
+	cur = atoi(str.c_str());
+
+	// Do slide, if needed
+	if (mSlideFrames)
+	{
+		mSlide += mSlideInc;
+		mSlideFrames--;
+		if (cur != (int) mSlide)
+		{
+			cur = (int) mSlide;
+			DataManager::SetValue(mCurValVar, cur);
+		}
+	}
+
+	// Normalize to 0
+	max -= min;
+	cur -= min;
+	min = 0;
+
+	if (cur < min)
+		cur = min;
+	if (cur > max)
+		cur = max;
+
+	if (max == 0)
+		pos = 0;
+	else
+		pos = (cur * mRenderW) / max;
+
+	if (pos == mLastPos)
+		return 0;
+
+	mLastPos = pos;
+
+	if (RenderInternal() != 0)
+		return -1;
+	return 2;
+}
+
+int GUIProgressBar::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	GUIObject::NotifyVarChange(varName, value);
+
+	if (!isConditionTrue())
+		return 0;
+
+	static int nextPush = 0;
+
+	if (varName.empty())
+	{
+		nextPush = 0;
+		mLastPos = 0;
+		mSlide = 0.0;
+		mSlideInc = 0.0;
+		return 0;
+	}
+
+	if (varName == "ui_progress_portion" || varName == "ui_progress_frames")
+	{
+		std::string str;
+		int cur;
+
+		if (mSlideFrames)
+		{
+			mSlide += (mSlideInc * mSlideFrames);
+			cur = (int) mSlide;
+			DataManager::SetValue(mCurValVar, cur);
+			mSlideFrames = 0;
+		}
+
+		if (nextPush)
+		{
+			mSlide += nextPush;
+			cur = (int) mSlide;
+			DataManager::SetValue(mCurValVar, cur);
+			nextPush = 0;
+		}
+
+		if (varName == "ui_progress_portion")   mSlide = atof(value.c_str());
+		else
+		{
+			mSlideFrames = atol(value.c_str());
+			if (mSlideFrames == 0)
+			{
+				// We're just holding this progress until the next push
+				nextPush = mSlide;
+			}
+		}
+
+		if (mSlide > 0 && mSlideFrames > 0)
+		{
+			// Get the current position
+			str.clear();
+			DataManager::GetValue(mCurValVar, str);
+			cur = atoi(str.c_str());
+
+			mSlideInc = (float) mSlide / (float) mSlideFrames;
+			mSlide = cur;
+		}
+	}
+	return 0;
+}
diff --git a/gui/rapidxml.hpp b/gui/rapidxml.hpp
new file mode 100644
index 0000000..f47807d
--- /dev/null
+++ b/gui/rapidxml.hpp
@@ -0,0 +1,2624 @@
+#ifndef RAPIDXML_HPP_INCLUDED
+#define RAPIDXML_HPP_INCLUDED
+
+#define RAPIDXML_NO_EXCEPTIONS
+
+// Copyright (C) 2006, 2009 Marcin Kalicinski
+// Version 1.13
+// Revision $DateTime: 2009/05/13 01:46:17 $
+//! \file rapidxml.hpp This file contains rapidxml parser and DOM implementation
+
+// If standard library is disabled, user must provide implementations of required functions and typedefs
+#if !defined(RAPIDXML_NO_STDLIB)
+    #include <cstdlib>      // For std::size_t
+    #include <cassert>      // For assert
+    #include <new>          // For placement new
+#endif
+
+// On MSVC, disable "conditional expression is constant" warning (level 4). 
+// This warning is almost impossible to avoid with certain types of templated code
+#ifdef _MSC_VER
+    #pragma warning(push)
+    #pragma warning(disable:4127)   // Conditional expression is constant
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+// RAPIDXML_PARSE_ERROR
+    
+#if defined(RAPIDXML_NO_EXCEPTIONS)
+
+#define RAPIDXML_PARSE_ERROR(what, where) { parse_error_handler(what, where); assert(0); }
+
+namespace rapidxml
+{
+    //! When exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, 
+    //! this function is called to notify user about the error.
+    //! It must be defined by the user.
+    //! <br><br>
+    //! This function cannot return. If it does, the results are undefined.
+    //! <br><br>
+    //! A very simple definition might look like that:
+    //! <pre>
+    //! void %rapidxml::%parse_error_handler(const char *what, void *where)
+    //! {
+    //!     std::cout << "Parse error: " << what << "\n";
+    //!     std::abort();
+    //! }
+    //! </pre>
+    //! \param what Human readable description of the error.
+    //! \param where Pointer to character data where error was detected.
+    void parse_error_handler(const char *what, void *where);
+}
+
+#else
+    
+#include <exception>    // For std::exception
+
+#define RAPIDXML_PARSE_ERROR(what, where) throw parse_error(what, where)
+
+namespace rapidxml
+{
+
+    //! Parse error exception. 
+    //! This exception is thrown by the parser when an error occurs. 
+    //! Use what() function to get human-readable error message. 
+    //! Use where() function to get a pointer to position within source text where error was detected.
+    //! <br><br>
+    //! If throwing exceptions by the parser is undesirable, 
+    //! it can be disabled by defining RAPIDXML_NO_EXCEPTIONS macro before rapidxml.hpp is included.
+    //! This will cause the parser to call rapidxml::parse_error_handler() function instead of throwing an exception.
+    //! This function must be defined by the user.
+    //! <br><br>
+    //! This class derives from <code>std::exception</code> class.
+    class parse_error: public std::exception
+    {
+    
+    public:
+    
+        //! Constructs parse error
+        parse_error(const char *what, void *where)
+            : m_what(what)
+            , m_where(where)
+        {
+        }
+
+        //! Gets human readable description of error.
+        //! \return Pointer to null terminated description of the error.
+        virtual const char *what() const throw()
+        {
+            return m_what;
+        }
+
+        //! Gets pointer to character data where error happened.
+        //! Ch should be the same as char type of xml_document that produced the error.
+        //! \return Pointer to location within the parsed string where error occured.
+        template<class Ch>
+        Ch *where() const
+        {
+            return reinterpret_cast<Ch *>(m_where);
+        }
+
+    private:  
+
+        const char *m_what;
+        void *m_where;
+
+    };
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+// Pool sizes
+
+#ifndef RAPIDXML_STATIC_POOL_SIZE
+    // Size of static memory block of memory_pool.
+    // Define RAPIDXML_STATIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.
+    // No dynamic memory allocations are performed by memory_pool until static memory is exhausted.
+    #define RAPIDXML_STATIC_POOL_SIZE (64 * 1024)
+#endif
+
+#ifndef RAPIDXML_DYNAMIC_POOL_SIZE
+    // Size of dynamic memory block of memory_pool.
+    // Define RAPIDXML_DYNAMIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.
+    // After the static block is exhausted, dynamic blocks with approximately this size are allocated by memory_pool.
+    #define RAPIDXML_DYNAMIC_POOL_SIZE (64 * 1024)
+#endif
+
+#ifndef RAPIDXML_ALIGNMENT
+    // Memory allocation alignment.
+    // Define RAPIDXML_ALIGNMENT before including rapidxml.hpp if you want to override the default value, which is the size of pointer.
+    // All memory allocations for nodes, attributes and strings will be aligned to this value.
+    // This must be a power of 2 and at least 1, otherwise memory_pool will not work.
+    #define RAPIDXML_ALIGNMENT sizeof(void *)
+#endif
+
+namespace rapidxml
+{
+    // Forward declarations
+    template<class Ch> class xml_node;
+    template<class Ch> class xml_attribute;
+    template<class Ch> class xml_document;
+    
+    //! Enumeration listing all node types produced by the parser.
+    //! Use xml_node::type() function to query node type.
+    enum node_type
+    {
+        node_document,      //!< A document node. Name and value are empty.
+        node_element,       //!< An element node. Name contains element name. Value contains text of first data node.
+        node_data,          //!< A data node. Name is empty. Value contains data text.
+        node_cdata,         //!< A CDATA node. Name is empty. Value contains data text.
+        node_comment,       //!< A comment node. Name is empty. Value contains comment text.
+        node_declaration,   //!< A declaration node. Name and value are empty. Declaration parameters (version, encoding and standalone) are in node attributes.
+        node_doctype,       //!< A DOCTYPE node. Name is empty. Value contains DOCTYPE text.
+        node_pi             //!< A PI node. Name contains target. Value contains instructions.
+    };
+
+    ///////////////////////////////////////////////////////////////////////
+    // Parsing flags
+
+    //! Parse flag instructing the parser to not create data nodes. 
+    //! Text of first data node will still be placed in value of parent element, unless rapidxml::parse_no_element_values flag is also specified.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_no_data_nodes = 0x1;            
+
+    //! Parse flag instructing the parser to not use text of first data node as a value of parent element.
+    //! Can be combined with other flags by use of | operator.
+    //! Note that child data nodes of element node take precendence over its value when printing. 
+    //! That is, if element has one or more child data nodes <em>and</em> a value, the value will be ignored.
+    //! Use rapidxml::parse_no_data_nodes flag to prevent creation of data nodes if you want to manipulate data using values of elements.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_no_element_values = 0x2;
+    
+    //! Parse flag instructing the parser to not place zero terminators after strings in the source text.
+    //! By default zero terminators are placed, modifying source text.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_no_string_terminators = 0x4;
+    
+    //! Parse flag instructing the parser to not translate entities in the source text.
+    //! By default entities are translated, modifying source text.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_no_entity_translation = 0x8;
+    
+    //! Parse flag instructing the parser to disable UTF-8 handling and assume plain 8 bit characters.
+    //! By default, UTF-8 handling is enabled.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_no_utf8 = 0x10;
+    
+    //! Parse flag instructing the parser to create XML declaration node.
+    //! By default, declaration node is not created.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_declaration_node = 0x20;
+    
+    //! Parse flag instructing the parser to create comments nodes.
+    //! By default, comment nodes are not created.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_comment_nodes = 0x40;
+    
+    //! Parse flag instructing the parser to create DOCTYPE node.
+    //! By default, doctype node is not created.
+    //! Although W3C specification allows at most one DOCTYPE node, RapidXml will silently accept documents with more than one.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_doctype_node = 0x80;
+    
+    //! Parse flag instructing the parser to create PI nodes.
+    //! By default, PI nodes are not created.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_pi_nodes = 0x100;
+    
+    //! Parse flag instructing the parser to validate closing tag names. 
+    //! If not set, name inside closing tag is irrelevant to the parser.
+    //! By default, closing tags are not validated.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_validate_closing_tags = 0x200;
+    
+    //! Parse flag instructing the parser to trim all leading and trailing whitespace of data nodes.
+    //! By default, whitespace is not trimmed. 
+    //! This flag does not cause the parser to modify source text.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_trim_whitespace = 0x400;
+
+    //! Parse flag instructing the parser to condense all whitespace runs of data nodes to a single space character.
+    //! Trimming of leading and trailing whitespace of data is controlled by rapidxml::parse_trim_whitespace flag.
+    //! By default, whitespace is not normalized. 
+    //! If this flag is specified, source text will be modified.
+    //! Can be combined with other flags by use of | operator.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_normalize_whitespace = 0x800;
+
+    // Compound flags
+    
+    //! Parse flags which represent default behaviour of the parser. 
+    //! This is always equal to 0, so that all other flags can be simply ored together.
+    //! Normally there is no need to inconveniently disable flags by anding with their negated (~) values.
+    //! This also means that meaning of each flag is a <i>negation</i> of the default setting. 
+    //! For example, if flag name is rapidxml::parse_no_utf8, it means that utf-8 is <i>enabled</i> by default,
+    //! and using the flag will disable it.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_default = 0;
+    
+    //! A combination of parse flags that forbids any modifications of the source text. 
+    //! This also results in faster parsing. However, note that the following will occur:
+    //! <ul>
+    //! <li>names and values of nodes will not be zero terminated, you have to use xml_base::name_size() and xml_base::value_size() functions to determine where name and value ends</li>
+    //! <li>entities will not be translated</li>
+    //! <li>whitespace will not be normalized</li>
+    //! </ul>
+    //! See xml_document::parse() function.
+    const int parse_non_destructive = parse_no_string_terminators | parse_no_entity_translation;
+    
+    //! A combination of parse flags resulting in fastest possible parsing, without sacrificing important data.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_fastest = parse_non_destructive | parse_no_data_nodes;
+    
+    //! A combination of parse flags resulting in largest amount of data being extracted. 
+    //! This usually results in slowest parsing.
+    //! <br><br>
+    //! See xml_document::parse() function.
+    const int parse_full = parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags;
+
+    ///////////////////////////////////////////////////////////////////////
+    // Internals
+
+    //! \cond internal
+    namespace internal
+    {
+
+        // Struct that contains lookup tables for the parser
+        // It must be a template to allow correct linking (because it has static data members, which are defined in a header file).
+        template<int Dummy>
+        struct lookup_tables
+        {
+            static const unsigned char lookup_whitespace[256];              // Whitespace table
+            static const unsigned char lookup_node_name[256];               // Node name table
+            static const unsigned char lookup_text[256];                    // Text table
+            static const unsigned char lookup_text_pure_no_ws[256];         // Text table
+            static const unsigned char lookup_text_pure_with_ws[256];       // Text table
+            static const unsigned char lookup_attribute_name[256];          // Attribute name table
+            static const unsigned char lookup_attribute_data_1[256];        // Attribute data table with single quote
+            static const unsigned char lookup_attribute_data_1_pure[256];   // Attribute data table with single quote
+            static const unsigned char lookup_attribute_data_2[256];        // Attribute data table with double quotes
+            static const unsigned char lookup_attribute_data_2_pure[256];   // Attribute data table with double quotes
+            static const unsigned char lookup_digits[256];                  // Digits
+            static const unsigned char lookup_upcase[256];                  // To uppercase conversion table for ASCII characters
+        };
+
+        // Find length of the string
+        template<class Ch>
+        inline std::size_t measure(const Ch *p)
+        {
+            const Ch *tmp = p;
+            while (*tmp) 
+                ++tmp;
+            return tmp - p;
+        }
+
+        // Compare strings for equality
+        template<class Ch>
+        inline bool compare(const Ch *p1, std::size_t size1, const Ch *p2, std::size_t size2, bool case_sensitive)
+        {
+            if (size1 != size2)
+                return false;
+            if (case_sensitive)
+            {
+                for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2)
+                    if (*p1 != *p2)
+                        return false;
+            }
+            else
+            {
+                for (const Ch *end = p1 + size1; p1 < end; ++p1, ++p2)
+                    if (lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p1)] != lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p2)])
+                        return false;
+            }
+            return true;
+        }
+    }
+    //! \endcond
+
+    ///////////////////////////////////////////////////////////////////////
+    // Memory pool
+    
+    //! This class is used by the parser to create new nodes and attributes, without overheads of dynamic memory allocation.
+    //! In most cases, you will not need to use this class directly. 
+    //! However, if you need to create nodes manually or modify names/values of nodes, 
+    //! you are encouraged to use memory_pool of relevant xml_document to allocate the memory. 
+    //! Not only is this faster than allocating them by using <code>new</code> operator, 
+    //! but also their lifetime will be tied to the lifetime of document, 
+    //! possibly simplyfing memory management. 
+    //! <br><br>
+    //! Call allocate_node() or allocate_attribute() functions to obtain new nodes or attributes from the pool. 
+    //! You can also call allocate_string() function to allocate strings.
+    //! Such strings can then be used as names or values of nodes without worrying about their lifetime.
+    //! Note that there is no <code>free()</code> function -- all allocations are freed at once when clear() function is called, 
+    //! or when the pool is destroyed.
+    //! <br><br>
+    //! It is also possible to create a standalone memory_pool, and use it 
+    //! to allocate nodes, whose lifetime will not be tied to any document.
+    //! <br><br>
+    //! Pool maintains <code>RAPIDXML_STATIC_POOL_SIZE</code> bytes of statically allocated memory. 
+    //! Until static memory is exhausted, no dynamic memory allocations are done.
+    //! When static memory is exhausted, pool allocates additional blocks of memory of size <code>RAPIDXML_DYNAMIC_POOL_SIZE</code> each,
+    //! by using global <code>new[]</code> and <code>delete[]</code> operators. 
+    //! This behaviour can be changed by setting custom allocation routines. 
+    //! Use set_allocator() function to set them.
+    //! <br><br>
+    //! Allocations for nodes, attributes and strings are aligned at <code>RAPIDXML_ALIGNMENT</code> bytes.
+    //! This value defaults to the size of pointer on target architecture.
+    //! <br><br>
+    //! To obtain absolutely top performance from the parser,
+    //! it is important that all nodes are allocated from a single, contiguous block of memory.
+    //! Otherwise, cache misses when jumping between two (or more) disjoint blocks of memory can slow down parsing quite considerably.
+    //! If required, you can tweak <code>RAPIDXML_STATIC_POOL_SIZE</code>, <code>RAPIDXML_DYNAMIC_POOL_SIZE</code> and <code>RAPIDXML_ALIGNMENT</code> 
+    //! to obtain best wasted memory to performance compromise.
+    //! To do it, define their values before rapidxml.hpp file is included.
+    //! \param Ch Character type of created nodes. 
+    template<class Ch = char>
+    class memory_pool
+    {
+        
+    public:
+
+        //! \cond internal
+        typedef void *(alloc_func)(std::size_t);       // Type of user-defined function used to allocate memory
+        typedef void (free_func)(void *);              // Type of user-defined function used to free memory
+        //! \endcond
+        
+        //! Constructs empty pool with default allocator functions.
+        memory_pool()
+            : m_alloc_func(0)
+            , m_free_func(0)
+        {
+            init();
+        }
+
+        //! Destroys pool and frees all the memory. 
+        //! This causes memory occupied by nodes allocated by the pool to be freed.
+        //! Nodes allocated from the pool are no longer valid.
+        ~memory_pool()
+        {
+            clear();
+        }
+
+        //! Allocates a new node from the pool, and optionally assigns name and value to it. 
+        //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+        //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+        //! will call rapidxml::parse_error_handler() function.
+        //! \param type Type of node to create.
+        //! \param name Name to assign to the node, or 0 to assign no name.
+        //! \param value Value to assign to the node, or 0 to assign no value.
+        //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string.
+        //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string.
+        //! \return Pointer to allocated node. This pointer will never be NULL.
+        xml_node<Ch> *allocate_node(node_type type, 
+                                    const Ch *name = 0, const Ch *value = 0, 
+                                    std::size_t name_size = 0, std::size_t value_size = 0)
+        {
+            void *memory = allocate_aligned(sizeof(xml_node<Ch>));
+            xml_node<Ch> *node = new(memory) xml_node<Ch>(type);
+            if (name)
+            {
+                if (name_size > 0)
+                    node->name(name, name_size);
+                else
+                    node->name(name);
+            }
+            if (value)
+            {
+                if (value_size > 0)
+                    node->value(value, value_size);
+                else
+                    node->value(value);
+            }
+            return node;
+        }
+
+        //! Allocates a new attribute from the pool, and optionally assigns name and value to it.
+        //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+        //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+        //! will call rapidxml::parse_error_handler() function.
+        //! \param name Name to assign to the attribute, or 0 to assign no name.
+        //! \param value Value to assign to the attribute, or 0 to assign no value.
+        //! \param name_size Size of name to assign, or 0 to automatically calculate size from name string.
+        //! \param value_size Size of value to assign, or 0 to automatically calculate size from value string.
+        //! \return Pointer to allocated attribute. This pointer will never be NULL.
+        xml_attribute<Ch> *allocate_attribute(const Ch *name = 0, const Ch *value = 0, 
+                                              std::size_t name_size = 0, std::size_t value_size = 0)
+        {
+            void *memory = allocate_aligned(sizeof(xml_attribute<Ch>));
+            xml_attribute<Ch> *attribute = new(memory) xml_attribute<Ch>;
+            if (name)
+            {
+                if (name_size > 0)
+                    attribute->name(name, name_size);
+                else
+                    attribute->name(name);
+            }
+            if (value)
+            {
+                if (value_size > 0)
+                    attribute->value(value, value_size);
+                else
+                    attribute->value(value);
+            }
+            return attribute;
+        }
+
+        //! Allocates a char array of given size from the pool, and optionally copies a given string to it.
+        //! If the allocation request cannot be accomodated, this function will throw <code>std::bad_alloc</code>.
+        //! If exceptions are disabled by defining RAPIDXML_NO_EXCEPTIONS, this function
+        //! will call rapidxml::parse_error_handler() function.
+        //! \param source String to initialize the allocated memory with, or 0 to not initialize it.
+        //! \param size Number of characters to allocate, or zero to calculate it automatically from source string length; if size is 0, source string must be specified and null terminated.
+        //! \return Pointer to allocated char array. This pointer will never be NULL.
+        Ch *allocate_string(const Ch *source = 0, std::size_t size = 0)
+        {
+            assert(source || size);     // Either source or size (or both) must be specified
+            if (size == 0)
+                size = internal::measure(source) + 1;
+            Ch *result = static_cast<Ch *>(allocate_aligned(size * sizeof(Ch)));
+            if (source)
+                for (std::size_t i = 0; i < size; ++i)
+                    result[i] = source[i];
+            return result;
+        }
+
+        //! Clones an xml_node and its hierarchy of child nodes and attributes.
+        //! Nodes and attributes are allocated from this memory pool.
+        //! Names and values are not cloned, they are shared between the clone and the source.
+        //! Result node can be optionally specified as a second parameter, 
+        //! in which case its contents will be replaced with cloned source node.
+        //! This is useful when you want to clone entire document.
+        //! \param source Node to clone.
+        //! \param result Node to put results in, or 0 to automatically allocate result node
+        //! \return Pointer to cloned node. This pointer will never be NULL.
+        xml_node<Ch> *clone_node(const xml_node<Ch> *source, xml_node<Ch> *result = 0)
+        {
+            // Prepare result node
+            if (result)
+            {
+                result->remove_all_attributes();
+                result->remove_all_nodes();
+                result->type(source->type());
+            }
+            else
+                result = allocate_node(source->type());
+
+            // Clone name and value
+            result->name(source->name(), source->name_size());
+            result->value(source->value(), source->value_size());
+
+            // Clone child nodes and attributes
+            for (xml_node<Ch> *child = source->first_node(); child; child = child->next_sibling())
+                result->append_node(clone_node(child));
+            for (xml_attribute<Ch> *attr = source->first_attribute(); attr; attr = attr->next_attribute())
+                result->append_attribute(allocate_attribute(attr->name(), attr->value(), attr->name_size(), attr->value_size()));
+
+            return result;
+        }
+
+        //! Clears the pool. 
+        //! This causes memory occupied by nodes allocated by the pool to be freed.
+        //! Any nodes or strings allocated from the pool will no longer be valid.
+        void clear()
+        {
+            while (m_begin != m_static_memory)
+            {
+                char *previous_begin = reinterpret_cast<header *>(align(m_begin))->previous_begin;
+                if (m_free_func)
+                    m_free_func(m_begin);
+                else
+                    delete[] m_begin;
+                m_begin = previous_begin;
+            }
+            init();
+        }
+
+        //! Sets or resets the user-defined memory allocation functions for the pool.
+        //! This can only be called when no memory is allocated from the pool yet, otherwise results are undefined.
+        //! Allocation function must not return invalid pointer on failure. It should either throw,
+        //! stop the program, or use <code>longjmp()</code> function to pass control to other place of program. 
+        //! If it returns invalid pointer, results are undefined.
+        //! <br><br>
+        //! User defined allocation functions must have the following forms:
+        //! <br><code>
+        //! <br>void *allocate(std::size_t size);
+        //! <br>void free(void *pointer);
+        //! </code><br>
+        //! \param af Allocation function, or 0 to restore default function
+        //! \param ff Free function, or 0 to restore default function
+        void set_allocator(alloc_func *af, free_func *ff)
+        {
+            assert(m_begin == m_static_memory && m_ptr == align(m_begin));    // Verify that no memory is allocated yet
+            m_alloc_func = af;
+            m_free_func = ff;
+        }
+
+    private:
+
+        struct header
+        {
+            char *previous_begin;
+        };
+
+        void init()
+        {
+            m_begin = m_static_memory;
+            m_ptr = align(m_begin);
+            m_end = m_static_memory + sizeof(m_static_memory);
+        }
+        
+        char *align(char *ptr)
+        {
+            std::size_t alignment = ((RAPIDXML_ALIGNMENT - (std::size_t(ptr) & (RAPIDXML_ALIGNMENT - 1))) & (RAPIDXML_ALIGNMENT - 1));
+            return ptr + alignment;
+        }
+        
+        char *allocate_raw(std::size_t size)
+        {
+            // Allocate
+            void *memory;   
+            if (m_alloc_func)   // Allocate memory using either user-specified allocation function or global operator new[]
+            {
+                memory = m_alloc_func(size);
+                assert(memory); // Allocator is not allowed to return 0, on failure it must either throw, stop the program or use longjmp
+            }
+            else
+            {
+                memory = new char[size];
+#ifdef RAPIDXML_NO_EXCEPTIONS
+                if (!memory)            // If exceptions are disabled, verify memory allocation, because new will not be able to throw bad_alloc
+                    RAPIDXML_PARSE_ERROR("out of memory", 0);
+#endif
+            }
+            return static_cast<char *>(memory);
+        }
+        
+        void *allocate_aligned(std::size_t size)
+        {
+            // Calculate aligned pointer
+            char *result = align(m_ptr);
+
+            // If not enough memory left in current pool, allocate a new pool
+            if (result + size > m_end)
+            {
+                // Calculate required pool size (may be bigger than RAPIDXML_DYNAMIC_POOL_SIZE)
+                std::size_t pool_size = RAPIDXML_DYNAMIC_POOL_SIZE;
+                if (pool_size < size)
+                    pool_size = size;
+                
+                // Allocate
+                std::size_t alloc_size = sizeof(header) + (2 * RAPIDXML_ALIGNMENT - 2) + pool_size;     // 2 alignments required in worst case: one for header, one for actual allocation
+                char *raw_memory = allocate_raw(alloc_size);
+                    
+                // Setup new pool in allocated memory
+                char *pool = align(raw_memory);
+                header *new_header = reinterpret_cast<header *>(pool);
+                new_header->previous_begin = m_begin;
+                m_begin = raw_memory;
+                m_ptr = pool + sizeof(header);
+                m_end = raw_memory + alloc_size;
+
+                // Calculate aligned pointer again using new pool
+                result = align(m_ptr);
+            }
+
+            // Update pool and return aligned pointer
+            m_ptr = result + size;
+            return result;
+        }
+
+        char *m_begin;                                      // Start of raw memory making up current pool
+        char *m_ptr;                                        // First free byte in current pool
+        char *m_end;                                        // One past last available byte in current pool
+        char m_static_memory[RAPIDXML_STATIC_POOL_SIZE];    // Static raw memory
+        alloc_func *m_alloc_func;                           // Allocator function, or 0 if default is to be used
+        free_func *m_free_func;                             // Free function, or 0 if default is to be used
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // XML base
+
+    //! Base class for xml_node and xml_attribute implementing common functions: 
+    //! name(), name_size(), value(), value_size() and parent().
+    //! \param Ch Character type to use
+    template<class Ch = char>
+    class xml_base
+    {
+
+    public:
+        
+        ///////////////////////////////////////////////////////////////////////////
+        // Construction & destruction
+    
+        // Construct a base with empty name, value and parent
+        xml_base()
+            : m_name(0)
+            , m_value(0)
+            , m_parent(0)
+        {
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Node data access
+    
+        //! Gets name of the node. 
+        //! Interpretation of name depends on type of node.
+        //! Note that name will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.
+        //! <br><br>
+        //! Use name_size() function to determine length of the name.
+        //! \return Name of node, or empty string if node has no name.
+        Ch *name() const
+        {
+            return m_name ? m_name : nullstr();
+        }
+
+        //! Gets size of node name, not including terminator character.
+        //! This function works correctly irrespective of whether name is or is not zero terminated.
+        //! \return Size of node name, in characters.
+        std::size_t name_size() const
+        {
+            return m_name ? m_name_size : 0;
+        }
+
+        //! Gets value of node. 
+        //! Interpretation of value depends on type of node.
+        //! Note that value will not be zero-terminated if rapidxml::parse_no_string_terminators option was selected during parse.
+        //! <br><br>
+        //! Use value_size() function to determine length of the value.
+        //! \return Value of node, or empty string if node has no value.
+        Ch *value() const
+        {
+            return m_value ? m_value : nullstr();
+        }
+
+        //! Gets size of node value, not including terminator character.
+        //! This function works correctly irrespective of whether value is or is not zero terminated.
+        //! \return Size of node value, in characters.
+        std::size_t value_size() const
+        {
+            return m_value ? m_value_size : 0;
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Node modification
+    
+        //! Sets name of node to a non zero-terminated string.
+        //! See \ref ownership_of_strings.
+        //! <br><br>
+        //! Note that node does not own its name or value, it only stores a pointer to it. 
+        //! It will not delete or otherwise free the pointer on destruction.
+        //! It is reponsibility of the user to properly manage lifetime of the string.
+        //! The easiest way to achieve it is to use memory_pool of the document to allocate the string -
+        //! on destruction of the document the string will be automatically freed.
+        //! <br><br>
+        //! Size of name must be specified separately, because name does not have to be zero terminated.
+        //! Use name(const Ch *) function to have the length automatically calculated (string must be zero terminated).
+        //! \param name Name of node to set. Does not have to be zero terminated.
+        //! \param size Size of name, in characters. This does not include zero terminator, if one is present.
+        void name(const Ch *name, std::size_t size)
+        {
+            m_name = const_cast<Ch *>(name);
+            m_name_size = size;
+        }
+
+        //! Sets name of node to a zero-terminated string.
+        //! See also \ref ownership_of_strings and xml_node::name(const Ch *, std::size_t).
+        //! \param name Name of node to set. Must be zero terminated.
+        void name(const Ch *name)
+        {
+            this->name(name, internal::measure(name));
+        }
+
+        //! Sets value of node to a non zero-terminated string.
+        //! See \ref ownership_of_strings.
+        //! <br><br>
+        //! Note that node does not own its name or value, it only stores a pointer to it. 
+        //! It will not delete or otherwise free the pointer on destruction.
+        //! It is reponsibility of the user to properly manage lifetime of the string.
+        //! The easiest way to achieve it is to use memory_pool of the document to allocate the string -
+        //! on destruction of the document the string will be automatically freed.
+        //! <br><br>
+        //! Size of value must be specified separately, because it does not have to be zero terminated.
+        //! Use value(const Ch *) function to have the length automatically calculated (string must be zero terminated).
+        //! <br><br>
+        //! If an element has a child node of type node_data, it will take precedence over element value when printing.
+        //! If you want to manipulate data of elements using values, use parser flag rapidxml::parse_no_data_nodes to prevent creation of data nodes by the parser.
+        //! \param value value of node to set. Does not have to be zero terminated.
+        //! \param size Size of value, in characters. This does not include zero terminator, if one is present.
+        void value(const Ch *value, std::size_t size)
+        {
+            m_value = const_cast<Ch *>(value);
+            m_value_size = size;
+        }
+
+        //! Sets value of node to a zero-terminated string.
+        //! See also \ref ownership_of_strings and xml_node::value(const Ch *, std::size_t).
+        //! \param value Vame of node to set. Must be zero terminated.
+        void value(const Ch *value)
+        {
+            this->value(value, internal::measure(value));
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Related nodes access
+    
+        //! Gets node parent.
+        //! \return Pointer to parent node, or 0 if there is no parent.
+        xml_node<Ch> *parent() const
+        {
+            return m_parent;
+        }
+
+    protected:
+
+        // Return empty string
+        static Ch *nullstr()
+        {
+            static Ch zero = Ch('\0');
+            return &zero;
+        }
+
+        Ch *m_name;                         // Name of node, or 0 if no name
+        Ch *m_value;                        // Value of node, or 0 if no value
+        std::size_t m_name_size;            // Length of node name, or undefined of no name
+        std::size_t m_value_size;           // Length of node value, or undefined if no value
+        xml_node<Ch> *m_parent;             // Pointer to parent node, or 0 if none
+
+    };
+
+    //! Class representing attribute node of XML document. 
+    //! Each attribute has name and value strings, which are available through name() and value() functions (inherited from xml_base).
+    //! Note that after parse, both name and value of attribute will point to interior of source text used for parsing. 
+    //! Thus, this text must persist in memory for the lifetime of attribute.
+    //! \param Ch Character type to use.
+    template<class Ch = char>
+    class xml_attribute: public xml_base<Ch>
+    {
+
+        friend class xml_node<Ch>;
+    
+    public:
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Construction & destruction
+    
+        //! Constructs an empty attribute with the specified type. 
+        //! Consider using memory_pool of appropriate xml_document if allocating attributes manually.
+        xml_attribute()
+        {
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Related nodes access
+    
+        //! Gets document of which attribute is a child.
+        //! \return Pointer to document that contains this attribute, or 0 if there is no parent document.
+        xml_document<Ch> *document() const
+        {
+            if (xml_node<Ch> *node = this->parent())
+            {
+                while (node->parent())
+                    node = node->parent();
+                return node->type() == node_document ? static_cast<xml_document<Ch> *>(node) : 0;
+            }
+            else
+                return 0;
+        }
+
+        //! Gets previous attribute, optionally matching attribute name. 
+        //! \param name Name of attribute to find, or 0 to return previous attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found attribute, or 0 if not found.
+        xml_attribute<Ch> *previous_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_attribute<Ch> *attribute = m_prev_attribute; attribute; attribute = attribute->m_prev_attribute)
+                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+                        return attribute;
+                return 0;
+            }
+            else
+                return this->m_parent ? m_prev_attribute : 0;
+        }
+
+        //! Gets next attribute, optionally matching attribute name. 
+        //! \param name Name of attribute to find, or 0 to return next attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found attribute, or 0 if not found.
+        xml_attribute<Ch> *next_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_attribute<Ch> *attribute = m_next_attribute; attribute; attribute = attribute->m_next_attribute)
+                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+                        return attribute;
+                return 0;
+            }
+            else
+                return this->m_parent ? m_next_attribute : 0;
+        }
+
+    private:
+
+        xml_attribute<Ch> *m_prev_attribute;        // Pointer to previous sibling of attribute, or 0 if none; only valid if parent is non-zero
+        xml_attribute<Ch> *m_next_attribute;        // Pointer to next sibling of attribute, or 0 if none; only valid if parent is non-zero
+    
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // XML node
+
+    //! Class representing a node of XML document. 
+    //! Each node may have associated name and value strings, which are available through name() and value() functions. 
+    //! Interpretation of name and value depends on type of the node.
+    //! Type of node can be determined by using type() function.
+    //! <br><br>
+    //! Note that after parse, both name and value of node, if any, will point interior of source text used for parsing. 
+    //! Thus, this text must persist in the memory for the lifetime of node.
+    //! \param Ch Character type to use.
+    template<class Ch = char>
+    class xml_node: public xml_base<Ch>
+    {
+
+    public:
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Construction & destruction
+    
+        //! Constructs an empty node with the specified type. 
+        //! Consider using memory_pool of appropriate document to allocate nodes manually.
+        //! \param type Type of node to construct.
+        xml_node(node_type type)
+            : m_type(type)
+            , m_first_node(0)
+            , m_first_attribute(0)
+        {
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Node data access
+    
+        //! Gets type of node.
+        //! \return Type of node.
+        node_type type() const
+        {
+            return m_type;
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Related nodes access
+    
+        //! Gets document of which node is a child.
+        //! \return Pointer to document that contains this node, or 0 if there is no parent document.
+        xml_document<Ch> *document() const
+        {
+            xml_node<Ch> *node = const_cast<xml_node<Ch> *>(this);
+            while (node->parent())
+                node = node->parent();
+            return node->type() == node_document ? static_cast<xml_document<Ch> *>(node) : 0;
+        }
+
+        //! Gets first child node, optionally matching node name.
+        //! \param name Name of child to find, or 0 to return first child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found child, or 0 if not found.
+        xml_node<Ch> *first_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_node<Ch> *child = m_first_node; child; child = child->next_sibling())
+                    if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive))
+                        return child;
+                return 0;
+            }
+            else
+                return m_first_node;
+        }
+
+        //! Gets last child node, optionally matching node name. 
+        //! Behaviour is undefined if node has no children.
+        //! Use first_node() to test if node has children.
+        //! \param name Name of child to find, or 0 to return last child regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found child, or 0 if not found.
+        xml_node<Ch> *last_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            assert(m_first_node);  // Cannot query for last child if node has no children
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_node<Ch> *child = m_last_node; child; child = child->previous_sibling())
+                    if (internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive))
+                        return child;
+                return 0;
+            }
+            else
+                return m_last_node;
+        }
+
+        //! Gets previous sibling node, optionally matching node name. 
+        //! Behaviour is undefined if node has no parent.
+        //! Use parent() to test if node has a parent.
+        //! \param name Name of sibling to find, or 0 to return previous sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found sibling, or 0 if not found.
+        xml_node<Ch> *previous_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            assert(this->m_parent);     // Cannot query for siblings if node has no parent
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_node<Ch> *sibling = m_prev_sibling; sibling; sibling = sibling->m_prev_sibling)
+                    if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))
+                        return sibling;
+                return 0;
+            }
+            else
+                return m_prev_sibling;
+        }
+
+        //! Gets next sibling node, optionally matching node name. 
+        //! Behaviour is undefined if node has no parent.
+        //! Use parent() to test if node has a parent.
+        //! \param name Name of sibling to find, or 0 to return next sibling regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found sibling, or 0 if not found.
+        xml_node<Ch> *next_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            assert(this->m_parent);     // Cannot query for siblings if node has no parent
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_node<Ch> *sibling = m_next_sibling; sibling; sibling = sibling->m_next_sibling)
+                    if (internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive))
+                        return sibling;
+                return 0;
+            }
+            else
+                return m_next_sibling;
+        }
+
+        //! Gets first attribute of node, optionally matching attribute name.
+        //! \param name Name of attribute to find, or 0 to return first attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found attribute, or 0 if not found.
+        xml_attribute<Ch> *first_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_attribute<Ch> *attribute = m_first_attribute; attribute; attribute = attribute->m_next_attribute)
+                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+                        return attribute;
+                return 0;
+            }
+            else
+                return m_first_attribute;
+        }
+
+        //! Gets last attribute of node, optionally matching attribute name.
+        //! \param name Name of attribute to find, or 0 to return last attribute regardless of its name; this string doesn't have to be zero-terminated if name_size is non-zero
+        //! \param name_size Size of name, in characters, or 0 to have size calculated automatically from string
+        //! \param case_sensitive Should name comparison be case-sensitive; non case-sensitive comparison works properly only for ASCII characters
+        //! \return Pointer to found attribute, or 0 if not found.
+        xml_attribute<Ch> *last_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const
+        {
+            if (name)
+            {
+                if (name_size == 0)
+                    name_size = internal::measure(name);
+                for (xml_attribute<Ch> *attribute = m_last_attribute; attribute; attribute = attribute->m_prev_attribute)
+                    if (internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive))
+                        return attribute;
+                return 0;
+            }
+            else
+                return m_first_attribute ? m_last_attribute : 0;
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Node modification
+    
+        //! Sets type of node.
+        //! \param type Type of node to set.
+        void type(node_type type)
+        {
+            m_type = type;
+        }
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Node manipulation
+
+        //! Prepends a new child node.
+        //! The prepended child becomes the first child, and all existing children are moved one position back.
+        //! \param child Node to prepend.
+        void prepend_node(xml_node<Ch> *child)
+        {
+            assert(child && !child->parent() && child->type() != node_document);
+            if (first_node())
+            {
+                child->m_next_sibling = m_first_node;
+                m_first_node->m_prev_sibling = child;
+            }
+            else
+            {
+                child->m_next_sibling = 0;
+                m_last_node = child;
+            }
+            m_first_node = child;
+            child->m_parent = this;
+            child->m_prev_sibling = 0;
+        }
+
+        //! Appends a new child node. 
+        //! The appended child becomes the last child.
+        //! \param child Node to append.
+        void append_node(xml_node<Ch> *child)
+        {
+            assert(child && !child->parent() && child->type() != node_document);
+            if (first_node())
+            {
+                child->m_prev_sibling = m_last_node;
+                m_last_node->m_next_sibling = child;
+            }
+            else
+            {
+                child->m_prev_sibling = 0;
+                m_first_node = child;
+            }
+            m_last_node = child;
+            child->m_parent = this;
+            child->m_next_sibling = 0;
+        }
+
+        //! Inserts a new child node at specified place inside the node. 
+        //! All children after and including the specified node are moved one position back.
+        //! \param where Place where to insert the child, or 0 to insert at the back.
+        //! \param child Node to insert.
+        void insert_node(xml_node<Ch> *where, xml_node<Ch> *child)
+        {
+            assert(!where || where->parent() == this);
+            assert(child && !child->parent() && child->type() != node_document);
+            if (where == m_first_node)
+                prepend_node(child);
+            else if (where == 0)
+                append_node(child);
+            else
+            {
+                child->m_prev_sibling = where->m_prev_sibling;
+                child->m_next_sibling = where;
+                where->m_prev_sibling->m_next_sibling = child;
+                where->m_prev_sibling = child;
+                child->m_parent = this;
+            }
+        }
+
+        //! Removes first child node. 
+        //! If node has no children, behaviour is undefined.
+        //! Use first_node() to test if node has children.
+        void remove_first_node()
+        {
+            assert(first_node());
+            xml_node<Ch> *child = m_first_node;
+            m_first_node = child->m_next_sibling;
+            if (child->m_next_sibling)
+                child->m_next_sibling->m_prev_sibling = 0;
+            else
+                m_last_node = 0;
+            child->m_parent = 0;
+        }
+
+        //! Removes last child of the node. 
+        //! If node has no children, behaviour is undefined.
+        //! Use first_node() to test if node has children.
+        void remove_last_node()
+        {
+            assert(first_node());
+            xml_node<Ch> *child = m_last_node;
+            if (child->m_prev_sibling)
+            {
+                m_last_node = child->m_prev_sibling;
+                child->m_prev_sibling->m_next_sibling = 0;
+            }
+            else
+                m_first_node = 0;
+            child->m_parent = 0;
+        }
+
+        //! Removes specified child from the node
+        // \param where Pointer to child to be removed.
+        void remove_node(xml_node<Ch> *where)
+        {
+            assert(where && where->parent() == this);
+            assert(first_node());
+            if (where == m_first_node)
+                remove_first_node();
+            else if (where == m_last_node)
+                remove_last_node();
+            else
+            {
+                where->m_prev_sibling->m_next_sibling = where->m_next_sibling;
+                where->m_next_sibling->m_prev_sibling = where->m_prev_sibling;
+                where->m_parent = 0;
+            }
+        }
+
+        //! Removes all child nodes (but not attributes).
+        void remove_all_nodes()
+        {
+            for (xml_node<Ch> *node = first_node(); node; node = node->m_next_sibling)
+                node->m_parent = 0;
+            m_first_node = 0;
+        }
+
+        //! Prepends a new attribute to the node.
+        //! \param attribute Attribute to prepend.
+        void prepend_attribute(xml_attribute<Ch> *attribute)
+        {
+            assert(attribute && !attribute->parent());
+            if (first_attribute())
+            {
+                attribute->m_next_attribute = m_first_attribute;
+                m_first_attribute->m_prev_attribute = attribute;
+            }
+            else
+            {
+                attribute->m_next_attribute = 0;
+                m_last_attribute = attribute;
+            }
+            m_first_attribute = attribute;
+            attribute->m_parent = this;
+            attribute->m_prev_attribute = 0;
+        }
+
+        //! Appends a new attribute to the node.
+        //! \param attribute Attribute to append.
+        void append_attribute(xml_attribute<Ch> *attribute)
+        {
+            assert(attribute && !attribute->parent());
+            if (first_attribute())
+            {
+                attribute->m_prev_attribute = m_last_attribute;
+                m_last_attribute->m_next_attribute = attribute;
+            }
+            else
+            {
+                attribute->m_prev_attribute = 0;
+                m_first_attribute = attribute;
+            }
+            m_last_attribute = attribute;
+            attribute->m_parent = this;
+            attribute->m_next_attribute = 0;
+        }
+
+        //! Inserts a new attribute at specified place inside the node. 
+        //! All attributes after and including the specified attribute are moved one position back.
+        //! \param where Place where to insert the attribute, or 0 to insert at the back.
+        //! \param attribute Attribute to insert.
+        void insert_attribute(xml_attribute<Ch> *where, xml_attribute<Ch> *attribute)
+        {
+            assert(!where || where->parent() == this);
+            assert(attribute && !attribute->parent());
+            if (where == m_first_attribute)
+                prepend_attribute(attribute);
+            else if (where == 0)
+                append_attribute(attribute);
+            else
+            {
+                attribute->m_prev_attribute = where->m_prev_attribute;
+                attribute->m_next_attribute = where;
+                where->m_prev_attribute->m_next_attribute = attribute;
+                where->m_prev_attribute = attribute;
+                attribute->m_parent = this;
+            }
+        }
+
+        //! Removes first attribute of the node. 
+        //! If node has no attributes, behaviour is undefined.
+        //! Use first_attribute() to test if node has attributes.
+        void remove_first_attribute()
+        {
+            assert(first_attribute());
+            xml_attribute<Ch> *attribute = m_first_attribute;
+            if (attribute->m_next_attribute)
+            {
+                attribute->m_next_attribute->m_prev_attribute = 0;
+            }
+            else
+                m_last_attribute = 0;
+            attribute->m_parent = 0;
+            m_first_attribute = attribute->m_next_attribute;
+        }
+
+        //! Removes last attribute of the node. 
+        //! If node has no attributes, behaviour is undefined.
+        //! Use first_attribute() to test if node has attributes.
+        void remove_last_attribute()
+        {
+            assert(first_attribute());
+            xml_attribute<Ch> *attribute = m_last_attribute;
+            if (attribute->m_prev_attribute)
+            {
+                attribute->m_prev_attribute->m_next_attribute = 0;
+                m_last_attribute = attribute->m_prev_attribute;
+            }
+            else
+                m_first_attribute = 0;
+            attribute->m_parent = 0;
+        }
+
+        //! Removes specified attribute from node.
+        //! \param where Pointer to attribute to be removed.
+        void remove_attribute(xml_attribute<Ch> *where)
+        {
+            assert(first_attribute() && where->parent() == this);
+            if (where == m_first_attribute)
+                remove_first_attribute();
+            else if (where == m_last_attribute)
+                remove_last_attribute();
+            else
+            {
+                where->m_prev_attribute->m_next_attribute = where->m_next_attribute;
+                where->m_next_attribute->m_prev_attribute = where->m_prev_attribute;
+                where->m_parent = 0;
+            }
+        }
+
+        //! Removes all attributes of node.
+        void remove_all_attributes()
+        {
+            for (xml_attribute<Ch> *attribute = first_attribute(); attribute; attribute = attribute->m_next_attribute)
+                attribute->m_parent = 0;
+            m_first_attribute = 0;
+        }
+        
+    private:
+
+        ///////////////////////////////////////////////////////////////////////////
+        // Restrictions
+
+        // No copying
+        xml_node(const xml_node &);
+        void operator =(const xml_node &);
+    
+        ///////////////////////////////////////////////////////////////////////////
+        // Data members
+    
+        // Note that some of the pointers below have UNDEFINED values if certain other pointers are 0.
+        // This is required for maximum performance, as it allows the parser to omit initialization of 
+        // unneded/redundant values.
+        //
+        // The rules are as follows:
+        // 1. first_node and first_attribute contain valid pointers, or 0 if node has no children/attributes respectively
+        // 2. last_node and last_attribute are valid only if node has at least one child/attribute respectively, otherwise they contain garbage
+        // 3. prev_sibling and next_sibling are valid only if node has a parent, otherwise they contain garbage
+
+        node_type m_type;                       // Type of node; always valid
+        xml_node<Ch> *m_first_node;             // Pointer to first child node, or 0 if none; always valid
+        xml_node<Ch> *m_last_node;              // Pointer to last child node, or 0 if none; this value is only valid if m_first_node is non-zero
+        xml_attribute<Ch> *m_first_attribute;   // Pointer to first attribute of node, or 0 if none; always valid
+        xml_attribute<Ch> *m_last_attribute;    // Pointer to last attribute of node, or 0 if none; this value is only valid if m_first_attribute is non-zero
+        xml_node<Ch> *m_prev_sibling;           // Pointer to previous sibling of node, or 0 if none; this value is only valid if m_parent is non-zero
+        xml_node<Ch> *m_next_sibling;           // Pointer to next sibling of node, or 0 if none; this value is only valid if m_parent is non-zero
+
+    };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // XML document
+    
+    //! This class represents root of the DOM hierarchy. 
+    //! It is also an xml_node and a memory_pool through public inheritance.
+    //! Use parse() function to build a DOM tree from a zero-terminated XML text string.
+    //! parse() function allocates memory for nodes and attributes by using functions of xml_document, 
+    //! which are inherited from memory_pool.
+    //! To access root node of the document, use the document itself, as if it was an xml_node.
+    //! \param Ch Character type to use.
+    template<class Ch = char>
+    class xml_document: public xml_node<Ch>, public memory_pool<Ch>
+    {
+    
+    public:
+
+        //! Constructs empty XML document
+        xml_document()
+            : xml_node<Ch>(node_document)
+        {
+        }
+
+        //! Parses zero-terminated XML string according to given flags.
+        //! Passed string will be modified by the parser, unless rapidxml::parse_non_destructive flag is used.
+        //! The string must persist for the lifetime of the document.
+        //! In case of error, rapidxml::parse_error exception will be thrown.
+        //! <br><br>
+        //! If you want to parse contents of a file, you must first load the file into the memory, and pass pointer to its beginning.
+        //! Make sure that data is zero-terminated.
+        //! <br><br>
+        //! Document can be parsed into multiple times. 
+        //! Each new call to parse removes previous nodes and attributes (if any), but does not clear memory pool.
+        //! \param text XML data to parse; pointer is non-const to denote fact that this data may be modified by the parser.
+        template<int Flags>
+        void parse(Ch *text)
+        {
+            assert(text);
+            
+            // Remove current contents
+            this->remove_all_nodes();
+            this->remove_all_attributes();
+            
+            // Parse BOM, if any
+            parse_bom<Flags>(text);
+
+            // Abort if it's ABX format
+            if (text[0] == Ch('A') &&
+                text[1] == Ch('B') &&
+                text[2] == Ch('X')) {
+                RAPIDXML_PARSE_ERROR("ABX Format Unsupported by RapidXML", text);
+                return;
+            }
+
+            // Parse children
+            while (1)
+            {
+                // Skip whitespace before node
+                skip<whitespace_pred, Flags>(text);
+                if (*text == 0)
+                    break;
+
+                // Parse and append new child
+                if (*text == Ch('<'))
+                {
+                    ++text;     // Skip '<'
+                    if (xml_node<Ch> *node = parse_node<Flags>(text))
+                        this->append_node(node);
+                }
+                else
+                    RAPIDXML_PARSE_ERROR("expected <", text);
+            }
+
+        }
+
+        //! Clears the document by deleting all nodes and clearing the memory pool.
+        //! All nodes owned by document pool are destroyed.
+        void clear()
+        {
+            this->remove_all_nodes();
+            this->remove_all_attributes();
+            memory_pool<Ch>::clear();
+        }
+        
+    private:
+
+        ///////////////////////////////////////////////////////////////////////
+        // Internal character utility functions
+        
+        // Detect whitespace character
+        struct whitespace_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_whitespace[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect node name character
+        struct node_name_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_node_name[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect attribute name character
+        struct attribute_name_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_attribute_name[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect text character (PCDATA)
+        struct text_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_text[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect text character (PCDATA) that does not require processing
+        struct text_pure_no_ws_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_text_pure_no_ws[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect text character (PCDATA) that does not require processing
+        struct text_pure_with_ws_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                return internal::lookup_tables<0>::lookup_text_pure_with_ws[static_cast<unsigned char>(ch)];
+            }
+        };
+
+        // Detect attribute value character
+        template<Ch Quote>
+        struct attribute_value_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                if (Quote == Ch('\''))
+                    return internal::lookup_tables<0>::lookup_attribute_data_1[static_cast<unsigned char>(ch)];
+                if (Quote == Ch('\"'))
+                    return internal::lookup_tables<0>::lookup_attribute_data_2[static_cast<unsigned char>(ch)];
+                return 0;       // Should never be executed, to avoid warnings on Comeau
+            }
+        };
+
+        // Detect attribute value character
+        template<Ch Quote>
+        struct attribute_value_pure_pred
+        {
+            static unsigned char test(Ch ch)
+            {
+                if (Quote == Ch('\''))
+                    return internal::lookup_tables<0>::lookup_attribute_data_1_pure[static_cast<unsigned char>(ch)];
+                if (Quote == Ch('\"'))
+                    return internal::lookup_tables<0>::lookup_attribute_data_2_pure[static_cast<unsigned char>(ch)];
+                return 0;       // Should never be executed, to avoid warnings on Comeau
+            }
+        };
+
+        // Insert coded character, using UTF8 or 8-bit ASCII
+        template<int Flags>
+        static void insert_coded_character(Ch *&text, unsigned long code)
+        {
+            if (Flags & parse_no_utf8)
+            {
+                // Insert 8-bit ASCII character
+                // Todo: possibly verify that code is less than 256 and use replacement char otherwise?
+                text[0] = static_cast<unsigned char>(code);
+                text += 1;
+            }
+            else
+            {
+                // Insert UTF8 sequence
+                if (code < 0x80)    // 1 byte sequence
+                {
+	                text[0] = static_cast<unsigned char>(code);
+                    text += 1;
+                }
+                else if (code < 0x800)  // 2 byte sequence
+                {
+	                text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[0] = static_cast<unsigned char>(code | 0xC0);
+                    text += 2;
+                }
+	            else if (code < 0x10000)    // 3 byte sequence
+                {
+	                text[2] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[0] = static_cast<unsigned char>(code | 0xE0);
+                    text += 3;
+                }
+	            else if (code < 0x110000)   // 4 byte sequence
+                {
+	                text[3] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[2] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); code >>= 6;
+	                text[0] = static_cast<unsigned char>(code | 0xF0);
+                    text += 4;
+                }
+                else    // Invalid, only codes up to 0x10FFFF are allowed in Unicode
+                {
+                    RAPIDXML_PARSE_ERROR("invalid numeric character entity", text);
+                }
+            }
+        }
+
+        // Skip characters until predicate evaluates to true
+        template<class StopPred, int Flags>
+        static void skip(Ch *&text)
+        {
+            Ch *tmp = text;
+            while (StopPred::test(*tmp))
+                ++tmp;
+            text = tmp;
+        }
+
+        // Skip characters until predicate evaluates to true while doing the following:
+        // - replacing XML character entity references with proper characters (&apos; &amp; &quot; &lt; &gt; &#...;)
+        // - condensing whitespace sequences to single space character
+        template<class StopPred, class StopPredPure, int Flags>
+        static Ch *skip_and_expand_character_refs(Ch *&text)
+        {
+            // If entity translation, whitespace condense and whitespace trimming is disabled, use plain skip
+            if (Flags & parse_no_entity_translation && 
+                !(Flags & parse_normalize_whitespace) &&
+                !(Flags & parse_trim_whitespace))
+            {
+                skip<StopPred, Flags>(text);
+                return text;
+            }
+            
+            // Use simple skip until first modification is detected
+            skip<StopPredPure, Flags>(text);
+
+            // Use translation skip
+            Ch *src = text;
+            Ch *dest = src;
+            while (StopPred::test(*src))
+            {
+                // If entity translation is enabled    
+                if (!(Flags & parse_no_entity_translation))
+                {
+                    // Test if replacement is needed
+                    if (src[0] == Ch('&'))
+                    {
+                        switch (src[1])
+                        {
+
+                        // &amp; &apos;
+                        case Ch('a'): 
+                            if (src[2] == Ch('m') && src[3] == Ch('p') && src[4] == Ch(';'))
+                            {
+                                *dest = Ch('&');
+                                ++dest;
+                                src += 5;
+                                continue;
+                            }
+                            if (src[2] == Ch('p') && src[3] == Ch('o') && src[4] == Ch('s') && src[5] == Ch(';'))
+                            {
+                                *dest = Ch('\'');
+                                ++dest;
+                                src += 6;
+                                continue;
+                            }
+                            break;
+
+                        // &quot;
+                        case Ch('q'): 
+                            if (src[2] == Ch('u') && src[3] == Ch('o') && src[4] == Ch('t') && src[5] == Ch(';'))
+                            {
+                                *dest = Ch('"');
+                                ++dest;
+                                src += 6;
+                                continue;
+                            }
+                            break;
+
+                        // &gt;
+                        case Ch('g'): 
+                            if (src[2] == Ch('t') && src[3] == Ch(';'))
+                            {
+                                *dest = Ch('>');
+                                ++dest;
+                                src += 4;
+                                continue;
+                            }
+                            break;
+
+                        // &lt;
+                        case Ch('l'): 
+                            if (src[2] == Ch('t') && src[3] == Ch(';'))
+                            {
+                                *dest = Ch('<');
+                                ++dest;
+                                src += 4;
+                                continue;
+                            }
+                            break;
+
+                        // &#...; - assumes ASCII
+                        case Ch('#'): 
+                            if (src[2] == Ch('x'))
+                            {
+                                unsigned long code = 0;
+                                src += 3;   // Skip &#x
+                                while (1)
+                                {
+                                    unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)];
+                                    if (digit == 0xFF)
+                                        break;
+                                    code = code * 16 + digit;
+                                    ++src;
+                                }
+                                insert_coded_character<Flags>(dest, code);    // Put character in output
+                            }
+                            else
+                            {
+                                unsigned long code = 0;
+                                src += 2;   // Skip &#
+                                while (1)
+                                {
+                                    unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)];
+                                    if (digit == 0xFF)
+                                        break;
+                                    code = code * 10 + digit;
+                                    ++src;
+                                }
+                                insert_coded_character<Flags>(dest, code);    // Put character in output
+                            }
+                            if (*src == Ch(';'))
+                                ++src;
+                            else
+                                RAPIDXML_PARSE_ERROR("expected ;", src);
+                            continue;
+
+                        // Something else
+                        default:
+                            // Ignore, just copy '&' verbatim
+                            break;
+
+                        }
+                    }
+                }
+                
+                // If whitespace condensing is enabled
+                if (Flags & parse_normalize_whitespace)
+                {
+                    // Test if condensing is needed                 
+                    if (whitespace_pred::test(*src))
+                    {
+                        *dest = Ch(' '); ++dest;    // Put single space in dest
+                        ++src;                      // Skip first whitespace char
+                        // Skip remaining whitespace chars
+                        while (whitespace_pred::test(*src))
+                            ++src;
+                        continue;
+                    }
+                }
+
+                // No replacement, only copy character
+                *dest++ = *src++;
+
+            }
+
+            // Return new end
+            text = src;
+            return dest;
+
+        }
+
+        ///////////////////////////////////////////////////////////////////////
+        // Internal parsing functions
+        
+        // Parse BOM, if any
+        template<int Flags>
+        void parse_bom(Ch *&text)
+        {
+            // UTF-8?
+            if (static_cast<unsigned char>(text[0]) == 0xEF && 
+                static_cast<unsigned char>(text[1]) == 0xBB && 
+                static_cast<unsigned char>(text[2]) == 0xBF)
+            {
+                text += 3;      // Skup utf-8 bom
+            }
+        }
+
+        // Parse XML declaration (<?xml...)
+        template<int Flags>
+        xml_node<Ch> *parse_xml_declaration(Ch *&text)
+        {
+            // If parsing of declaration is disabled
+            if (!(Flags & parse_declaration_node))
+            {
+                // Skip until end of declaration
+                while (text[0] != Ch('?') || text[1] != Ch('>'))
+                {
+                    if (!text[0]) {
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+						return 0;
+					}
+                    ++text;
+                }
+                text += 2;    // Skip '?>'
+                return 0;
+            }
+
+            // Create declaration
+            xml_node<Ch> *declaration = this->allocate_node(node_declaration);
+
+            // Skip whitespace before attributes or ?>
+            skip<whitespace_pred, Flags>(text);
+
+            // Parse declaration attributes
+            parse_node_attributes<Flags>(text, declaration);
+            
+            // Skip ?>
+            if (text[0] != Ch('?') || text[1] != Ch('>'))
+                RAPIDXML_PARSE_ERROR("expected ?>", text);
+            text += 2;
+            
+            return declaration;
+        }
+
+        // Parse XML comment (<!--...)
+        template<int Flags>
+        xml_node<Ch> *parse_comment(Ch *&text)
+        {
+            // If parsing of comments is disabled
+            if (!(Flags & parse_comment_nodes))
+            {
+                // Skip until end of comment
+                while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>'))
+                {
+                    if (!text[0]) {
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+						return 0;
+					}
+                    ++text;
+                }
+                text += 3;     // Skip '-->'
+                return 0;      // Do not produce comment node
+            }
+
+            // Remember value start
+            Ch *value = text;
+
+            // Skip until end of comment
+            while (text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>'))
+            {
+                if (!text[0]) {
+                    RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+					return 0;
+				}
+                ++text;
+            }
+
+            // Create comment node
+            xml_node<Ch> *comment = this->allocate_node(node_comment);
+            comment->value(value, text - value);
+            
+            // Place zero terminator after comment value
+            if (!(Flags & parse_no_string_terminators))
+                *text = Ch('\0');
+            
+            text += 3;     // Skip '-->'
+            return comment;
+        }
+
+        // Parse DOCTYPE
+        template<int Flags>
+        xml_node<Ch> *parse_doctype(Ch *&text)
+        {
+            // Remember value start
+            Ch *value = text;
+
+            // Skip to >
+            while (*text != Ch('>'))
+            {
+                // Determine character type
+                switch (*text)
+                {
+                
+                // If '[' encountered, scan for matching ending ']' using naive algorithm with depth
+                // This works for all W3C test files except for 2 most wicked
+                case Ch('['):
+                {
+                    ++text;     // Skip '['
+                    int depth = 1;
+                    while (depth > 0)
+                    {
+                        switch (*text)
+                        {
+                            case Ch('['): ++depth; break;
+                            case Ch(']'): --depth; break;
+                            case 0: RAPIDXML_PARSE_ERROR("unexpected end of data", text); return 0;
+                        }
+                        ++text;
+                    }
+                    break;
+                }
+                
+                // Error on end of text
+                case Ch('\0'):
+                    RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+					return 0;
+                
+                // Other character, skip it
+                default:
+                    ++text;
+
+                }
+            }
+            
+            // If DOCTYPE nodes enabled
+            if (Flags & parse_doctype_node)
+            {
+                // Create a new doctype node
+                xml_node<Ch> *doctype = this->allocate_node(node_doctype);
+                doctype->value(value, text - value);
+                
+                // Place zero terminator after value
+                if (!(Flags & parse_no_string_terminators))
+                    *text = Ch('\0');
+
+                text += 1;      // skip '>'
+                return doctype;
+            }
+            else
+            {
+                text += 1;      // skip '>'
+                return 0;
+            }
+
+        }
+
+        // Parse PI
+        template<int Flags>
+        xml_node<Ch> *parse_pi(Ch *&text)
+        {
+            // If creation of PI nodes is enabled
+            if (Flags & parse_pi_nodes)
+            {
+                // Create pi node
+                xml_node<Ch> *pi = this->allocate_node(node_pi);
+
+                // Extract PI target name
+                Ch *name = text;
+                skip<node_name_pred, Flags>(text);
+                if (text == name)
+                    RAPIDXML_PARSE_ERROR("expected PI target", text);
+                pi->name(name, text - name);
+                
+                // Skip whitespace between pi target and pi
+                skip<whitespace_pred, Flags>(text);
+
+                // Remember start of pi
+                Ch *value = text;
+                
+                // Skip to '?>'
+                while (text[0] != Ch('?') || text[1] != Ch('>'))
+                {
+                    if (*text == Ch('\0')) {
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+						return 0;
+					}
+                    ++text;
+                }
+
+                // Set pi value (verbatim, no entity expansion or whitespace normalization)
+                pi->value(value, text - value);     
+                
+                // Place zero terminator after name and value
+                if (!(Flags & parse_no_string_terminators))
+                {
+                    pi->name()[pi->name_size()] = Ch('\0');
+                    pi->value()[pi->value_size()] = Ch('\0');
+                }
+                
+                text += 2;                          // Skip '?>'
+                return pi;
+            }
+            else
+            {
+                // Skip to '?>'
+                while (text[0] != Ch('?') || text[1] != Ch('>'))
+                {
+                    if (*text == Ch('\0')) {
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+						return 0;
+					}
+                    ++text;
+                }
+                text += 2;    // Skip '?>'
+                return 0;
+            }
+        }
+
+        // Parse and append data
+        // Return character that ends data.
+        // This is necessary because this character might have been overwritten by a terminating 0
+        template<int Flags>
+        Ch parse_and_append_data(xml_node<Ch> *node, Ch *&text, Ch *contents_start)
+        {
+            // Backup to contents start if whitespace trimming is disabled
+            if (!(Flags & parse_trim_whitespace))
+                text = contents_start;     
+            
+            // Skip until end of data
+            Ch *value = text, *end;
+            if (Flags & parse_normalize_whitespace)
+                end = skip_and_expand_character_refs<text_pred, text_pure_with_ws_pred, Flags>(text);   
+            else
+                end = skip_and_expand_character_refs<text_pred, text_pure_no_ws_pred, Flags>(text);
+
+            // Trim trailing whitespace if flag is set; leading was already trimmed by whitespace skip after >
+            if (Flags & parse_trim_whitespace)
+            {
+                if (Flags & parse_normalize_whitespace)
+                {
+                    // Whitespace is already condensed to single space characters by skipping function, so just trim 1 char off the end
+                    if (*(end - 1) == Ch(' '))
+                        --end;
+                }
+                else
+                {
+                    // Backup until non-whitespace character is found
+                    while (whitespace_pred::test(*(end - 1)))
+                        --end;
+                }
+            }
+            
+            // If characters are still left between end and value (this test is only necessary if normalization is enabled)
+            // Create new data node
+            if (!(Flags & parse_no_data_nodes))
+            {
+                xml_node<Ch> *data = this->allocate_node(node_data);
+                data->value(value, end - value);
+                node->append_node(data);
+            }
+
+            // Add data to parent node if no data exists yet
+            if (!(Flags & parse_no_element_values)) 
+                if (*node->value() == Ch('\0'))
+                    node->value(value, end - value);
+
+            // Place zero terminator after value
+            if (!(Flags & parse_no_string_terminators))
+            {
+                Ch ch = *text;
+                *end = Ch('\0');
+                return ch;      // Return character that ends data; this is required because zero terminator overwritten it
+            }
+
+            // Return character that ends data
+            return *text;
+        }
+
+        // Parse CDATA
+        template<int Flags>
+        xml_node<Ch> *parse_cdata(Ch *&text)
+        {
+            // If CDATA is disabled
+            if (Flags & parse_no_data_nodes)
+            {
+                // Skip until end of cdata
+                while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>'))
+                {
+                    if (!text[0]) {
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+						return 0;
+					}
+                    ++text;
+                }
+                text += 3;      // Skip ]]>
+                return 0;       // Do not produce CDATA node
+            }
+
+            // Skip until end of cdata
+            Ch *value = text;
+            while (text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>'))
+            {
+                if (!text[0]) {
+                    RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+					return 0;
+				}
+                ++text;
+            }
+
+            // Create new cdata node
+            xml_node<Ch> *cdata = this->allocate_node(node_cdata);
+            cdata->value(value, text - value);
+
+            // Place zero terminator after value
+            if (!(Flags & parse_no_string_terminators))
+                *text = Ch('\0');
+
+            text += 3;      // Skip ]]>
+            return cdata;
+        }
+        
+        // Parse element node
+        template<int Flags>
+        xml_node<Ch> *parse_element(Ch *&text)
+        {
+            // Create element node
+            xml_node<Ch> *element = this->allocate_node(node_element);
+
+            // Extract element name
+            Ch *name = text;
+            skip<node_name_pred, Flags>(text);
+            if (text == name)
+                RAPIDXML_PARSE_ERROR("expected element name", text);
+            element->name(name, text - name);
+            
+            // Skip whitespace between element name and attributes or >
+            skip<whitespace_pred, Flags>(text);
+
+            // Parse attributes, if any
+            parse_node_attributes<Flags>(text, element);
+
+            // Determine ending type
+            if (*text == Ch('>'))
+            {
+                ++text;
+                parse_node_contents<Flags>(text, element);
+            }
+            else if (*text == Ch('/'))
+            {
+                ++text;
+                if (*text != Ch('>'))
+                    RAPIDXML_PARSE_ERROR("expected >", text);
+                ++text;
+            }
+            else
+                RAPIDXML_PARSE_ERROR("expected >", text);
+
+            // Place zero terminator after name
+            if (!(Flags & parse_no_string_terminators))
+                element->name()[element->name_size()] = Ch('\0');
+
+            // Return parsed element
+            return element;
+        }
+
+        // Determine node type, and parse it
+        template<int Flags>
+        xml_node<Ch> *parse_node(Ch *&text)
+        {
+            // Parse proper node type
+            switch (text[0])
+            {
+
+            // <...
+            default: 
+                // Parse and append element node
+                return parse_element<Flags>(text);
+
+            // <?...
+            case Ch('?'): 
+                ++text;     // Skip ?
+                if ((text[0] == Ch('x') || text[0] == Ch('X')) &&
+                    (text[1] == Ch('m') || text[1] == Ch('M')) && 
+                    (text[2] == Ch('l') || text[2] == Ch('L')) &&
+                    whitespace_pred::test(text[3]))
+                {
+                    // '<?xml ' - xml declaration
+                    text += 4;      // Skip 'xml '
+                    return parse_xml_declaration<Flags>(text);
+                }
+                else
+                {
+                    // Parse PI
+                    return parse_pi<Flags>(text);
+                }
+            
+            // <!...
+            case Ch('!'): 
+
+                // Parse proper subset of <! node
+                switch (text[1])    
+                {
+                
+                // <!-
+                case Ch('-'):
+                    if (text[2] == Ch('-'))
+                    {
+                        // '<!--' - xml comment
+                        text += 3;     // Skip '!--'
+                        return parse_comment<Flags>(text);
+                    }
+                    break;
+
+                // <![
+                case Ch('['):
+                    if (text[2] == Ch('C') && text[3] == Ch('D') && text[4] == Ch('A') && 
+                        text[5] == Ch('T') && text[6] == Ch('A') && text[7] == Ch('['))
+                    {
+                        // '<![CDATA[' - cdata
+                        text += 8;     // Skip '![CDATA['
+                        return parse_cdata<Flags>(text);
+                    }
+                    break;
+
+                // <!D
+                case Ch('D'):
+                    if (text[2] == Ch('O') && text[3] == Ch('C') && text[4] == Ch('T') && 
+                        text[5] == Ch('Y') && text[6] == Ch('P') && text[7] == Ch('E') && 
+                        whitespace_pred::test(text[8]))
+                    {
+                        // '<!DOCTYPE ' - doctype
+                        text += 9;      // skip '!DOCTYPE '
+                        return parse_doctype<Flags>(text);
+                    }
+
+                }   // switch
+
+                // Attempt to skip other, unrecognized node types starting with <!
+                ++text;     // Skip !
+                while (*text != Ch('>'))
+                {
+                    if (*text == 0) {
+                        RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+						return 0;
+					}
+                    ++text;
+                }
+                ++text;     // Skip '>'
+                return 0;   // No node recognized
+
+            }
+        }
+
+        // Parse contents of the node - children, data etc.
+        template<int Flags>
+        void parse_node_contents(Ch *&text, xml_node<Ch> *node)
+        {
+            // For all children and text
+            while (1)
+            {
+                // Skip whitespace between > and node contents
+                Ch *contents_start = text;      // Store start of node contents before whitespace is skipped
+                skip<whitespace_pred, Flags>(text);
+                Ch next_char = *text;
+
+            // After data nodes, instead of continuing the loop, control jumps here.
+            // This is because zero termination inside parse_and_append_data() function
+            // would wreak havoc with the above code.
+            // Also, skipping whitespace after data nodes is unnecessary.
+            after_data_node:    
+                
+                // Determine what comes next: node closing, child node, data node, or 0?
+                switch (next_char)
+                {
+                
+                // Node closing or child node
+                case Ch('<'):
+                    if (text[1] == Ch('/'))
+                    {
+                        // Node closing
+                        text += 2;      // Skip '</'
+                        if (Flags & parse_validate_closing_tags)
+                        {
+                            // Skip and validate closing tag name
+                            Ch *closing_name = text;
+                            skip<node_name_pred, Flags>(text);
+                            if (!internal::compare(node->name(), node->name_size(), closing_name, text - closing_name, true))
+                                RAPIDXML_PARSE_ERROR("invalid closing tag name", text);
+                        }
+                        else
+                        {
+                            // No validation, just skip name
+                            skip<node_name_pred, Flags>(text);
+                        }
+                        // Skip remaining whitespace after node name
+                        skip<whitespace_pred, Flags>(text);
+                        if (*text != Ch('>'))
+                            RAPIDXML_PARSE_ERROR("expected >", text);
+                        ++text;     // Skip '>'
+                        return;     // Node closed, finished parsing contents
+                    }
+                    else
+                    {
+                        // Child node
+                        ++text;     // Skip '<'
+                        if (xml_node<Ch> *child = parse_node<Flags>(text))
+                            node->append_node(child);
+                    }
+                    break;
+
+                // End of data - error
+                case Ch('\0'):
+                    RAPIDXML_PARSE_ERROR("unexpected end of data", text);
+					return;
+
+                // Data node
+                default:
+                    next_char = parse_and_append_data<Flags>(node, text, contents_start);
+                    goto after_data_node;   // Bypass regular processing after data nodes
+
+                }
+            }
+        }
+        
+        // Parse XML attributes of the node
+        template<int Flags>
+        void parse_node_attributes(Ch *&text, xml_node<Ch> *node)
+        {
+            // For all attributes 
+            while (attribute_name_pred::test(*text))
+            {
+                // Extract attribute name
+                Ch *name = text;
+                ++text;     // Skip first character of attribute name
+                skip<attribute_name_pred, Flags>(text);
+                if (text == name)
+                    RAPIDXML_PARSE_ERROR("expected attribute name", name);
+
+                // Create new attribute
+                xml_attribute<Ch> *attribute = this->allocate_attribute();
+                attribute->name(name, text - name);
+                node->append_attribute(attribute);
+
+                // Skip whitespace after attribute name
+                skip<whitespace_pred, Flags>(text);
+
+                // Skip =
+                if (*text != Ch('='))
+                    RAPIDXML_PARSE_ERROR("expected =", text);
+                ++text;
+
+                // Add terminating zero after name
+                if (!(Flags & parse_no_string_terminators))
+                    attribute->name()[attribute->name_size()] = 0;
+
+                // Skip whitespace after =
+                skip<whitespace_pred, Flags>(text);
+
+                // Skip quote and remember if it was ' or "
+                Ch quote = *text;
+                if (quote != Ch('\'') && quote != Ch('"'))
+                    RAPIDXML_PARSE_ERROR("expected ' or \"", text);
+                ++text;
+
+                // Extract attribute value and expand char refs in it
+                Ch *value = text, *end;
+                const int AttFlags = Flags & ~parse_normalize_whitespace;   // No whitespace normalization in attributes
+                if (quote == Ch('\''))
+                    end = skip_and_expand_character_refs<attribute_value_pred<Ch('\'')>, attribute_value_pure_pred<Ch('\'')>, AttFlags>(text);
+                else
+                    end = skip_and_expand_character_refs<attribute_value_pred<Ch('"')>, attribute_value_pure_pred<Ch('"')>, AttFlags>(text);
+                
+                // Set attribute value
+                attribute->value(value, end - value);
+                
+                // Make sure that end quote is present
+                if (*text != quote)
+                    RAPIDXML_PARSE_ERROR("expected ' or \"", text);
+                ++text;     // Skip quote
+
+                // Add terminating zero after value
+                if (!(Flags & parse_no_string_terminators))
+                    attribute->value()[attribute->value_size()] = 0;
+
+                // Skip whitespace after attribute value
+                skip<whitespace_pred, Flags>(text);
+            }
+        }
+
+    };
+
+    //! \cond internal
+    namespace internal
+    {
+
+        // Whitespace (space \n \r \t)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_whitespace[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  0,  0,  1,  0,  0,  // 0
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 1
+             1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 2
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 3
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 4
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 5
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 6
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 7
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 8
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 9
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // A
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // B
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // C
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // D
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // E
+             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0   // F
+        };
+
+        // Node name (anything but space \n \r \t / > ? \0)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_node_name[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  0,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Text (i.e. PCDATA) (anything but < \0)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_text[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Text (i.e. PCDATA) that does not require processing when ws normalization is disabled 
+        // (anything but < \0 &)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_text_pure_no_ws[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Text (i.e. PCDATA) that does not require processing when ws normalizationis is enabled
+        // (anything but < \0 & space \n \r \t)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_text_pure_with_ws[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  0,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             0,  1,  1,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Attribute name (anything but space \n \r \t / < > = ? ! \0)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_attribute_name[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  0,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Attribute data with single quote (anything but ' \0)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Attribute data with single quote that does not require processing (anything but ' \0 &)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1_pure[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  1,  1,  1,  1,  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Attribute data with double quote (anything but " \0)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Attribute data with double quote that does not require processing (anything but " \0 &)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2_pure[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+             0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 0
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 1
+             1,  1,  0,  1,  1,  1,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 2
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 3
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 4
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 5
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 6
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 7
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 8
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // 9
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // A
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // B
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // C
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // D
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  // E
+             1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1   // F
+        };
+
+        // Digits (dec and hex, 255 denotes end of numeric character reference)
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_digits[256] = 
+        {
+          // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 0
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 1
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 2
+             0,  1,  2,  3,  4,  5,  6,  7,  8,  9,255,255,255,255,255,255,  // 3
+           255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255,  // 4
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 5
+           255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255,  // 6
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 7
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 8
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // 9
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // A
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // B
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // C
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // D
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,  // E
+           255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255   // F
+        };
+    
+        // Upper case conversion
+        template<int Dummy>
+        const unsigned char lookup_tables<Dummy>::lookup_upcase[256] = 
+        {
+          // 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  A   B   C   D   E   F
+           0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,   // 0
+           16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,   // 1
+           32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,   // 2
+           48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,   // 3
+           64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,   // 4
+           80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,   // 5
+           96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,   // 6
+           80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123,124,125,126,127,  // 7
+           128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,  // 8
+           144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,  // 9
+           160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,  // A
+           176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,  // B
+           192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,  // C
+           208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,  // D
+           224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,  // E
+           240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255   // F
+        };
+    }
+    //! \endcond
+
+}
+
+// Undefine internal macros
+#undef RAPIDXML_PARSE_ERROR
+
+// On MSVC, restore warnings state
+#ifdef _MSC_VER
+    #pragma warning(pop)
+#endif
+
+#endif
diff --git a/gui/remaining.txt b/gui/remaining.txt
new file mode 100644
index 0000000..298ee90
--- /dev/null
+++ b/gui/remaining.txt
@@ -0,0 +1,4 @@
+* Confirmation of action
+* Multi-String Variables
+* String Box
+
diff --git a/gui/resources.cpp b/gui/resources.cpp
new file mode 100755
index 0000000..744a853
--- /dev/null
+++ b/gui/resources.cpp
@@ -0,0 +1,472 @@
+/*
+    Copyright 2012 to 2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// resource.cpp - Source to manage GUI resources
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include <fcntl.h>
+#include <ziparchive/zip_archive.h>
+#include <android-base/unique_fd.h>
+
+extern "C" {
+#include "../twcommon.h"
+#include "gui.h"
+}
+
+#include "minuitwrp/truetype.hpp"
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+#define TMP_RESOURCE_NAME   "/tmp/extract.bin"
+
+Resource::Resource(xml_node<>* node, ZipArchiveHandle pZip __unused)
+{
+	if (node && node->first_attribute("name"))
+		mName = node->first_attribute("name")->value();
+}
+
+int Resource::ExtractResource(ZipArchiveHandle pZip, std::string folderName, std::string fileName, std::string fileExtn, std::string destFile)
+{
+	if (!pZip)
+		return -1;
+
+	std::string src = folderName + "/" + fileName + fileExtn;
+	ZipEntry binary_entry;
+	if (FindEntry(pZip, src, &binary_entry) == 0) {
+		android::base::unique_fd fd(
+			open(destFile.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0666));
+		if (fd == -1) {
+			return -1;
+		}
+		int32_t err = ExtractEntryToFile(pZip, &binary_entry, fd);
+		if (err != 0)
+			return -1;
+	} else {
+		return -1;
+	}
+	return 0;
+}
+
+void Resource::LoadImage(ZipArchiveHandle pZip, std::string file, gr_surface* surface)
+{
+	int rc = 0;
+	if (ExtractResource(pZip, "images", file, ".png", TMP_RESOURCE_NAME) == 0)
+	{
+		rc = res_create_surface(TMP_RESOURCE_NAME, surface);
+		unlink(TMP_RESOURCE_NAME);
+	}
+	else if (ExtractResource(pZip, "images", file, "", TMP_RESOURCE_NAME) == 0)
+	{
+		// JPG includes the .jpg extension in the filename so extension should be blank
+		rc = res_create_surface(TMP_RESOURCE_NAME, surface);
+		unlink(TMP_RESOURCE_NAME);
+	}
+	else if (!pZip)
+	{
+		// File name in xml may have included .png so try without adding .png
+		rc = res_create_surface(file.c_str(), surface);
+	}
+	if (rc != 0)
+		LOGINFO("Failed to load image from %s%s, error %d\n", file.c_str(), pZip ? " (zip)" : "", rc);
+}
+
+void Resource::CheckAndScaleImage(gr_surface source, gr_surface* destination, int retain_aspect)
+{
+	if (!source) {
+		*destination = nullptr;
+		return;
+	}
+	if (get_scale_w() != 0 && get_scale_h() != 0) {
+		float scale_w = get_scale_w(), scale_h = get_scale_h();
+		if (retain_aspect) {
+			if (scale_w < scale_h)
+				scale_h = scale_w;
+			else
+				scale_w = scale_h;
+		}
+		if (res_scale_surface(source, destination, scale_w, scale_h)) {
+			LOGINFO("Error scaling image, using regular size.\n");
+			*destination = source;
+		}
+	} else {
+		*destination = source;
+	}
+}
+
+FontResource::FontResource(xml_node<>* node, ZipArchiveHandle pZip)
+ : Resource(node, pZip)
+{
+	origFontSize = 0;
+	origFont = NULL;
+	LoadFont(node, pZip);
+}
+
+void FontResource::LoadFont(xml_node<>* node, ZipArchiveHandle pZip)
+{
+	std::string file;
+	xml_attribute<>* attr;
+
+	mFont = NULL;
+	if (!node)
+		return;
+
+	attr = node->first_attribute("filename");
+	if (!attr)
+		return;
+
+	file = attr->value();
+
+	if (file.size() >= 4 && file.compare(file.size()-4, 4, ".ttf") == 0)
+	{
+		int font_size = 0;
+
+		if (origFontSize != 0) {
+			attr = node->first_attribute("scale");
+			if (attr == NULL)
+				return;
+			font_size = origFontSize * atoi(attr->value()) / 100;
+		} else {
+			attr = node->first_attribute("size");
+			if (attr == NULL)
+				return;
+			font_size = scale_theme_min(atoi(attr->value()));
+			origFontSize = font_size;
+		}
+
+		int dpi = 300;
+
+		attr = node->first_attribute("dpi");
+		if (attr)
+			dpi = atoi(attr->value());
+
+		// we can't use TMP_RESOURCE_NAME here because the ttf subsystem is caching the name and scaling needs to reload the font
+		std::string tmpname = "/tmp/" + file;
+		if (ExtractResource(pZip, "fonts", file, "", tmpname) == 0)
+		{
+			mFont = twrpTruetype::gr_ttf_loadFont(tmpname.c_str(), font_size, dpi);
+		}
+		else
+		{
+			file = std::string(TWRES "fonts/") + file;
+			mFont = twrpTruetype::gr_ttf_loadFont(file.c_str(), font_size, dpi);
+		}
+	}
+	else
+	{
+		LOGERR("Non-TTF fonts are no longer supported.\n");
+	}
+}
+
+void FontResource::DeleteFont() {
+	if (mFont) {
+		twrpTruetype::gr_ttf_freeFont(mFont);
+	}
+	mFont = NULL;
+	if (origFont) {
+		twrpTruetype::gr_ttf_freeFont(origFont);
+	}
+	origFont = NULL;
+}
+
+void FontResource::Override(xml_node<>* node, ZipArchiveHandle pZip) {
+	if (!origFont) {
+		origFont = mFont;
+	} else if (mFont) {
+		twrpTruetype::gr_ttf_freeFont(mFont);
+		mFont = NULL;
+	}
+	LoadFont(node, pZip);
+}
+
+FontResource::~FontResource()
+{
+	DeleteFont();
+}
+
+ImageResource::ImageResource(xml_node<>* node, ZipArchiveHandle pZip)
+ : Resource(node, pZip)
+{
+	std::string file;
+	gr_surface temp_surface = nullptr;
+
+	mSurface = NULL;
+	if (!node) {
+		LOGERR("ImageResource node is NULL\n");
+		return;
+	}
+
+	if (node->first_attribute("filename"))
+		file = node->first_attribute("filename")->value();
+	else {
+		LOGERR("No filename specified for image resource.\n");
+		return;
+	}
+
+	bool retain_aspect = (node->first_attribute("retainaspect") != NULL);
+	// the value does not matter, if retainaspect is present, we assume that we want to retain it
+	LoadImage(pZip, file, &temp_surface);
+	CheckAndScaleImage(temp_surface, &mSurface, retain_aspect);
+}
+
+ImageResource::~ImageResource()
+{
+	if (mSurface)
+		res_free_surface(mSurface);
+}
+
+AnimationResource::AnimationResource(xml_node<>* node, ZipArchiveHandle pZip)
+ : Resource(node, pZip)
+{
+	std::string file;
+	int fileNum = 1;
+
+	if (!node)
+		return;
+
+	if (node->first_attribute("filename"))
+		file = node->first_attribute("filename")->value();
+	else {
+		LOGERR("No filename specified for image resource.\n");
+		return;
+	}
+
+	bool retain_aspect = (node->first_attribute("retainaspect") != NULL);
+	// the value does not matter, if retainaspect is present, we assume that we want to retain it
+	for (;;)
+	{
+		std::ostringstream fileName;
+		fileName << file << std::setfill ('0') << std::setw (3) << fileNum;
+
+		gr_surface surface = nullptr;
+		gr_surface temp_surface = nullptr;
+		LoadImage(pZip, fileName.str(), &temp_surface);
+		CheckAndScaleImage(temp_surface, &surface, retain_aspect);
+		if (surface) {
+			mSurfaces.push_back(surface);
+			fileNum++;
+		} else
+			break; // Done loading animation images
+	}
+}
+
+AnimationResource::~AnimationResource()
+{
+	std::vector<gr_surface>::iterator it;
+
+	for (it = mSurfaces.begin(); it != mSurfaces.end(); ++it)
+		res_free_surface(*it);
+
+	mSurfaces.clear();
+}
+
+FontResource* ResourceManager::FindFont(const std::string& name) const
+{
+	for (std::vector<FontResource*>::const_iterator it = mFonts.begin(); it != mFonts.end(); ++it)
+		if (name == (*it)->GetName())
+			return *it;
+	return NULL;
+}
+
+ImageResource* ResourceManager::FindImage(const std::string& name) const
+{
+	for (std::vector<ImageResource*>::const_iterator it = mImages.begin(); it != mImages.end(); ++it)
+		if (name == (*it)->GetName())
+			return *it;
+	return NULL;
+}
+
+AnimationResource* ResourceManager::FindAnimation(const std::string& name) const
+{
+	for (std::vector<AnimationResource*>::const_iterator it = mAnimations.begin(); it != mAnimations.end(); ++it)
+		if (name == (*it)->GetName())
+			return *it;
+	return NULL;
+}
+
+std::string ResourceManager::FindString(const std::string& name) const
+{
+	//if (this != NULL) {
+		std::map<std::string, string_resource_struct>::const_iterator it = mStrings.find(name);
+		if (it != mStrings.end())
+			return it->second.value;
+		LOGERR("String resource '%s' not found. No default value.\n", name.c_str());
+		PageManager::AddStringResource("NO DEFAULT", name, "[" + name + ("]"));
+	/*} else {
+		LOGINFO("String resources not loaded when looking for '%s'. No default value.\n", name.c_str());
+	}*/
+	return "[" + name + ("]");
+}
+
+std::string ResourceManager::FindString(const std::string& name, const std::string& default_string) const
+{
+	//if (this != NULL) {
+		std::map<std::string, string_resource_struct>::const_iterator it = mStrings.find(name);
+		if (it != mStrings.end())
+			return it->second.value;
+		LOGERR("String resource '%s' not found. Using default value.\n", name.c_str());
+		PageManager::AddStringResource("DEFAULT", name, default_string);
+	/*} else {
+		LOGINFO("String resources not loaded when looking for '%s'. Using default value.\n", name.c_str());
+	}*/
+	return default_string;
+}
+
+void ResourceManager::DumpStrings() const
+{
+	/*if (this == NULL) {
+		gui_print("No string resources\n");
+		return;
+	}*/
+	std::map<std::string, string_resource_struct>::const_iterator it;
+	gui_print("Dumping all strings:\n");
+	for (it = mStrings.begin(); it != mStrings.end(); it++)
+		gui_print("source: %s: '%s' = '%s'\n", it->second.source.c_str(), it->first.c_str(), it->second.value.c_str());
+	gui_print("Done dumping strings\n");
+}
+
+ResourceManager::ResourceManager()
+{
+}
+
+void ResourceManager::AddStringResource(std::string resource_source, std::string resource_name, std::string value)
+{
+	string_resource_struct res;
+	res.source = resource_source;
+	res.value = value;
+	mStrings[resource_name] = res;
+}
+
+void ResourceManager::LoadResources(xml_node<>* resList, ZipArchiveHandle pZip, std::string resource_source)
+{
+	if (!resList)
+		return;
+
+	for (xml_node<>* child = resList->first_node(); child; child = child->next_sibling())
+	{
+		std::string type = child->name();
+		if (type == "resource") {
+			// legacy format : <resource type="...">
+			xml_attribute<>* attr = child->first_attribute("type");
+			type = attr ? attr->value() : "*unspecified*";
+		}
+
+		bool error = false;
+		if (type == "font")
+		{
+			FontResource* res = new FontResource(child, pZip);
+			if (res && res->GetResource())
+				mFonts.push_back(res);
+			else {
+				error = true;
+				delete res;
+			}
+		}
+		else if (type == "fontoverride")
+		{
+			if (mFonts.size() != 0 && child && child->first_attribute("name")) {
+				string FontName = child->first_attribute("name")->value();
+				size_t font_count = mFonts.size(), i;
+				bool found = false;
+
+				for (i = 0; i < font_count; i++) {
+					if (mFonts[i]->GetName() == FontName) {
+						mFonts[i]->Override(child, pZip);
+						found = true;
+						break;
+					}
+				}
+				if (!found) {
+					LOGERR("Unable to locate font '%s' for override.\n", FontName.c_str());
+				}
+			} else if (mFonts.size() != 0)
+				LOGERR("Unable to locate font name for type fontoverride.\n");
+		}
+		else if (type == "image")
+		{
+			ImageResource* res = new ImageResource(child, pZip);
+			if (res && res->GetResource())
+				mImages.push_back(res);
+			else {
+				error = true;
+				delete res;
+			}
+		}
+		else if (type == "animation")
+		{
+			AnimationResource* res = new AnimationResource(child, pZip);
+			if (res && res->GetResourceCount())
+				mAnimations.push_back(res);
+			else {
+				error = true;
+				delete res;
+			}
+		}
+		else if (type == "string")
+		{
+			if (xml_attribute<>* attr = child->first_attribute("name")) {
+				string_resource_struct res;
+				res.source = resource_source;
+				res.value = child->value();
+				mStrings[attr->value()] = res;
+			} else
+				error = true;
+		}
+		else
+		{
+			LOGERR("Resource type (%s) not supported.\n", type.c_str());
+			error = true;
+		}
+
+		if (error)
+		{
+			std::string res_name;
+			if (child->first_attribute("name"))
+				res_name = child->first_attribute("name")->value();
+			if (res_name.empty() && child->first_attribute("filename"))
+				res_name = child->first_attribute("filename")->value();
+
+			if (!res_name.empty()) {
+				LOGERR("Resource (%s)-(%s) failed to load\n", type.c_str(), res_name.c_str());
+			} else
+				LOGERR("Resource type (%s) failed to load\n", type.c_str());
+		}
+	}
+}
+
+ResourceManager::~ResourceManager()
+{
+	for (std::vector<FontResource*>::iterator it = mFonts.begin(); it != mFonts.end(); ++it)
+		delete *it;
+
+	for (std::vector<ImageResource*>::iterator it = mImages.begin(); it != mImages.end(); ++it)
+		delete *it;
+
+	for (std::vector<AnimationResource*>::iterator it = mAnimations.begin(); it != mAnimations.end(); ++it)
+		delete *it;
+}
diff --git a/gui/resources.hpp b/gui/resources.hpp
new file mode 100755
index 0000000..8c12276
--- /dev/null
+++ b/gui/resources.hpp
@@ -0,0 +1,136 @@
+/*
+    Copyright 2012 to 2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// resources.hpp - Base classes for resource management of GUI
+
+#ifndef _RESOURCE_HEADER
+#define _RESOURCE_HEADER
+
+#include <string>
+#include <vector>
+#include <map>
+#include "rapidxml.hpp"
+#include "ziparchive/zip_archive.h"
+#include "minuitwrp/truetype.hpp"
+
+extern "C" {
+#include "minuitwrp/minui.h"
+}
+
+// Base Objects
+class Resource
+{
+public:
+	Resource(xml_node<>* node, ZipArchiveHandle pZip);
+	virtual ~Resource() {}
+
+public:
+	std::string GetName() { return mName; }
+
+private:
+	std::string mName;
+
+protected:
+	static int ExtractResource(ZipArchiveHandle pZip, std::string folderName, std::string fileName, std::string fileExtn, std::string destFile);
+	static void LoadImage(ZipArchiveHandle pZip, std::string file, gr_surface* surface);
+	static void CheckAndScaleImage(gr_surface source, gr_surface* destination, int retain_aspect);
+};
+
+class FontResource : public Resource
+{
+public:
+	FontResource(xml_node<>* node, ZipArchiveHandle pZip);
+	virtual ~FontResource();
+
+public:
+	void* GetResource() { return mFont; }
+	int GetHeight() { return twrpTruetype::gr_ttf_getMaxFontHeight(mFont); }
+	void Override(xml_node<>* node, ZipArchiveHandle pZip);
+
+protected:
+	void* mFont;
+
+private:
+	void LoadFont(xml_node<>* node, ZipArchiveHandle pZip);
+	void DeleteFont();
+
+private:
+	int origFontSize;
+	void* origFont;
+};
+
+class ImageResource : public Resource
+{
+public:
+	ImageResource(xml_node<>* node, ZipArchiveHandle pZip);
+	virtual ~ImageResource();
+
+public:
+	gr_surface GetResource() { return mSurface; }
+	int GetWidth() { return gr_get_width(mSurface); }
+	int GetHeight() { return gr_get_height(mSurface); }
+
+protected:
+	gr_surface mSurface;
+};
+
+class AnimationResource : public Resource
+{
+public:
+	AnimationResource(xml_node<>* node, ZipArchiveHandle pZip);
+	virtual ~AnimationResource();
+
+public:
+	gr_surface GetResource() { return mSurfaces.empty() ? NULL : mSurfaces.at(0); }
+	gr_surface GetResource(int entry) { return mSurfaces.empty() ? NULL : mSurfaces.at(entry); }
+	int GetWidth() { return gr_get_width(GetResource()); }
+	int GetHeight() { return gr_get_height(GetResource()); }
+	int GetResourceCount() { return mSurfaces.size(); }
+
+protected:
+	std::vector<gr_surface> mSurfaces;
+};
+
+class ResourceManager
+{
+public:
+	ResourceManager();
+	virtual ~ResourceManager();
+	void AddStringResource(std::string resource_source, std::string resource_name, std::string value);
+	void LoadResources(xml_node<>* resList, ZipArchiveHandle pZip, std::string resource_source);
+
+public:
+	FontResource* FindFont(const std::string& name) const;
+	ImageResource* FindImage(const std::string& name) const;
+	AnimationResource* FindAnimation(const std::string& name) const;
+	std::string FindString(const std::string& name) const;
+	std::string FindString(const std::string& name, const std::string& default_string) const;
+	void DumpStrings() const;
+
+private:
+	struct string_resource_struct {
+		std::string value;
+		std::string source;
+	};
+	std::vector<FontResource*> mFonts;
+	std::vector<ImageResource*> mImages;
+	std::vector<AnimationResource*> mAnimations;
+	std::map<std::string, string_resource_struct> mStrings;
+};
+
+#endif  // _RESOURCE_HEADER
diff --git a/gui/scrolllist.cpp b/gui/scrolllist.cpp
new file mode 100755
index 0000000..cdc6c9e
--- /dev/null
+++ b/gui/scrolllist.cpp
@@ -0,0 +1,661 @@
+/*
+    Copyright 2012 to 2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+#include "minuitwrp/truetype.hpp"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+#include "../data.hpp"
+
+const float SCROLLING_SPEED_DECREMENT = 0.9; // friction
+const int SCROLLING_FLOOR = 2; // minimum pixels for scrolling to stop
+
+GUIScrollList::GUIScrollList(xml_node<>* node) : GUIObject(node)
+{
+	xml_node<>* child;
+
+	firstDisplayedItem = mItemSpacing = mFontHeight = mSeparatorH = y_offset = scrollingSpeed = 0;
+	maxIconWidth = maxIconHeight =  mHeaderIconHeight = mHeaderIconWidth = 0;
+	mHeaderSeparatorH = mHeaderH = actualItemHeight = 0;
+	mHeaderIsStatic = false;
+	mBackground = mHeaderIcon = NULL;
+	mFont = NULL;
+	mFastScrollW = mFastScrollLineW = mFastScrollRectW = mFastScrollRectH = 0;
+	mFastScrollRectCurrentY = mFastScrollRectCurrentH = mFastScrollRectTouchY = 0;
+	lastY = last2Y = fastScroll = 0;
+	mUpdate = 0;
+	touchDebounce = 6;
+	ConvertStrToColor("black", &mBackgroundColor);
+	ConvertStrToColor("black", &mHeaderBackgroundColor);
+	ConvertStrToColor("black", &mSeparatorColor);
+	ConvertStrToColor("black", &mHeaderSeparatorColor);
+	ConvertStrToColor("white", &mFontColor);
+	ConvertStrToColor("white", &mHeaderFontColor);
+	ConvertStrToColor("white", &mFastScrollLineColor);
+	ConvertStrToColor("white", &mFastScrollRectColor);
+	hasHighlightColor = false;
+	allowSelection = true;
+	selectedItem = NO_ITEM;
+
+	// Load header text
+	// note: node can be NULL for the emergency console
+	child = node ? node->first_node("text") : NULL;
+	if (child)  mHeaderText = child->value();
+	// Simple way to check for static state
+	mLastHeaderValue = gui_parse_text(mHeaderText);
+	mHeaderIsStatic = (mLastHeaderValue == mHeaderText);
+
+	mHighlightColor = LoadAttrColor(FindNode(node, "highlight"), "color", &hasHighlightColor);
+
+	child = FindNode(node, "background");
+	if (child)
+	{
+		mBackground = LoadAttrImage(child, "resource");
+		mBackgroundColor = LoadAttrColor(child, "color");
+	}
+
+	// Load the placement
+	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH);
+	SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+
+	// Load the font, and possibly override the color
+	child = FindNode(node, "font");
+	if (child)
+	{
+		mFont = LoadAttrFont(child, "resource");
+		mFontColor = LoadAttrColor(child, "color");
+		mFontHighlightColor = LoadAttrColor(child, "highlightcolor", mFontColor);
+		mItemSpacing = LoadAttrIntScaleY(child, "spacing");
+	}
+
+	// Load the separator if it exists
+	child = FindNode(node, "separator");
+	if (child)
+	{
+		mSeparatorColor = LoadAttrColor(child, "color");
+		mSeparatorH = LoadAttrIntScaleY(child, "height");
+	}
+
+	// Fast scroll
+	child = FindNode(node, "fastscroll");
+	if (child)
+	{
+		mFastScrollLineColor = LoadAttrColor(child, "linecolor");
+		mFastScrollRectColor = LoadAttrColor(child, "rectcolor");
+
+		mFastScrollW = LoadAttrIntScaleX(child, "w");
+		mFastScrollLineW = LoadAttrIntScaleX(child, "linew");
+		mFastScrollRectW = LoadAttrIntScaleX(child, "rectw");
+		mFastScrollRectH = LoadAttrIntScaleY(child, "recth");
+	}
+
+	// Retrieve the line height
+	mFontHeight = mFont->GetHeight();
+	actualItemHeight = mFontHeight + mItemSpacing + mSeparatorH;
+
+	// Load the header if it exists
+	child = FindNode(node, "header");
+	if (child)
+	{
+		mHeaderH = mFontHeight;
+		mHeaderIcon = LoadAttrImage(child, "icon");
+		mHeaderBackgroundColor = LoadAttrColor(child, "background", mBackgroundColor);
+		mHeaderFontColor = LoadAttrColor(child, "textcolor", mFontColor);
+		mHeaderSeparatorColor = LoadAttrColor(child, "separatorcolor", mSeparatorColor);
+		mHeaderSeparatorH = LoadAttrIntScaleY(child, "separatorheight", mSeparatorH);
+
+		if (mHeaderIcon && mHeaderIcon->GetResource())
+		{
+			mHeaderIconWidth = mHeaderIcon->GetWidth();
+			mHeaderIconHeight = mHeaderIcon->GetHeight();
+			if (mHeaderIconHeight > mHeaderH)
+				mHeaderH = mHeaderIconHeight;
+			if (mHeaderIconWidth > maxIconWidth)
+				maxIconWidth = mHeaderIconWidth;
+		}
+
+		mHeaderH += mItemSpacing + mHeaderSeparatorH;
+		if (mHeaderH < actualItemHeight)
+			mHeaderH = actualItemHeight;
+	}
+
+	if (actualItemHeight / 3 > 6)
+		touchDebounce = actualItemHeight / 3;
+}
+
+GUIScrollList::~GUIScrollList()
+{
+}
+
+void GUIScrollList::SetMaxIconSize(int w, int h)
+{
+	if (w > maxIconWidth)
+		maxIconWidth = w;
+	if (h > maxIconHeight)
+		maxIconHeight = h;
+	if (maxIconHeight > mFontHeight) {
+		actualItemHeight = maxIconHeight + mItemSpacing + mSeparatorH;
+		if (mHeaderH > 0 && actualItemHeight > mHeaderH)
+			mHeaderH = actualItemHeight;
+	}
+}
+
+void GUIScrollList::SetVisibleListLocation(size_t list_index)
+{
+	// This will make sure that the item indicated by list_index is visible on the screen
+	size_t lines = GetDisplayItemCount();
+
+	if (list_index <= (unsigned)firstDisplayedItem) {
+		// list_index is above the currently displayed items, put the selected item at the very top
+		firstDisplayedItem = list_index;
+		y_offset = 0;
+	} else if (list_index >= firstDisplayedItem + lines) {
+		// list_index is below the currently displayed items, put the selected item at the very bottom
+		firstDisplayedItem = list_index - lines + 1;
+		if (GetDisplayRemainder() != 0) {
+			// There's a partial row displayed, set the scrolling offset so that the selected item really is at the very bottom
+			firstDisplayedItem--;
+			y_offset = GetDisplayRemainder() - actualItemHeight;
+		} else {
+			// There's no partial row so zero out the offset
+			y_offset = 0;
+		}
+		if (firstDisplayedItem < 0)
+			firstDisplayedItem = 0;
+	}
+	scrollingSpeed = 0; // stop kinetic scrolling on setting visible location
+	mUpdate = 1;
+}
+
+int GUIScrollList::Render(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	// First step, fill background
+	gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, mBackgroundColor.alpha);
+	gr_fill(mRenderX, mRenderY + mHeaderH, mRenderW, mRenderH - mHeaderH);
+
+	// don't paint outside of the box
+	gr_clip(mRenderX, mRenderY, mRenderW, mRenderH);
+
+	// Next, render the background resource (if it exists)
+	if (mBackground && mBackground->GetResource())
+	{
+		int BackgroundW = mBackground->GetWidth();
+		int BackgroundH = mBackground->GetHeight();
+		int BackgroundX = mRenderX + ((mRenderW - BackgroundW) / 2);
+		int BackgroundY = mRenderY + ((mRenderH - BackgroundH) / 2);
+		gr_blit(mBackground->GetResource(), 0, 0, BackgroundW, BackgroundH, BackgroundX, BackgroundY);
+	}
+
+	// This tells us how many full lines we can actually render
+	size_t lines = GetDisplayItemCount();
+
+	size_t listSize = GetItemCount();
+	int listW = mRenderW; // this is only used for the separators - the list items are rendered in the full width of the list
+
+	if (listSize <= lines) {
+		hasScroll = false;
+		scrollingSpeed = 0;
+		lines = listSize;
+		y_offset = 0;
+	} else {
+		hasScroll = true;
+		listW -= mFastScrollW; // space for fast scroll
+		lines++;
+		if (lines < listSize)
+			lines++;
+	}
+
+	int yPos = mRenderY + mHeaderH + y_offset;
+
+	// render all visible items
+	for (size_t line = 0; line < lines; line++)
+	{
+		size_t itemindex = line + firstDisplayedItem;
+		if (itemindex >= listSize)
+			break;
+
+		RenderItem(itemindex, yPos, itemindex == selectedItem);
+
+		// Add the separator
+		gr_color(mSeparatorColor.red, mSeparatorColor.green, mSeparatorColor.blue, mSeparatorColor.alpha);
+		gr_fill(mRenderX, yPos + actualItemHeight - mSeparatorH, listW, mSeparatorH);
+
+		// Move the yPos
+		yPos += actualItemHeight;
+	}
+
+	// Render the Header (last so that it overwrites the top most row for per pixel scrolling)
+	yPos = mRenderY;
+	if (mHeaderH > 0) {
+		// First step, fill background
+		gr_color(mHeaderBackgroundColor.red, mHeaderBackgroundColor.green, mHeaderBackgroundColor.blue, mHeaderBackgroundColor.alpha);
+		gr_fill(mRenderX, mRenderY, mRenderW, mHeaderH);
+
+		int IconOffsetX = 0;
+
+		// render the icon if it exists
+		if (mHeaderIcon && mHeaderIcon->GetResource())
+		{
+			gr_blit(mHeaderIcon->GetResource(), 0, 0, mHeaderIconWidth, mHeaderIconHeight, mRenderX + ((mHeaderIconWidth - maxIconWidth) / 2), (yPos + (int)((mHeaderH - mHeaderIconHeight) / 2)));
+			IconOffsetX = maxIconWidth;
+		}
+
+		// render the text
+		if (mFont && mFont->GetResource()) {
+			gr_color(mHeaderFontColor.red, mHeaderFontColor.green, mHeaderFontColor.blue, mHeaderFontColor.alpha);
+			gr_textEx_scaleW(mRenderX + IconOffsetX + 5, yPos + (int)(mHeaderH / 2), mLastHeaderValue.c_str(), mFont->GetResource(), mRenderW, TEXT_ONLY_RIGHT, 0);
+		}
+
+		// Add the separator
+		gr_color(mHeaderSeparatorColor.red, mHeaderSeparatorColor.green, mHeaderSeparatorColor.blue, mHeaderSeparatorColor.alpha);
+		gr_fill(mRenderX, yPos + mHeaderH - mHeaderSeparatorH, mRenderW, mHeaderSeparatorH);
+	}
+
+	// reset clipping
+	gr_noclip();
+
+	// render fast scroll
+	if (hasScroll) {
+		int fWidth = mRenderW - listW;
+		int fHeight = mRenderH - mHeaderH;
+		int centerX = listW + mRenderX + fWidth / 2;
+
+		// first determine the total list height and where we are in the list
+		int totalHeight = GetItemCount() * actualItemHeight; // total height of the full list in pixels
+		int topPos = firstDisplayedItem * actualItemHeight - y_offset;
+
+		// now scale it proportionally to the scrollbar height
+		int boxH = fHeight * fHeight / totalHeight; // proportional height of the displayed portion
+		boxH = std::max(boxH, mFastScrollRectH); // but keep a minimum height
+		int boxY = (fHeight - boxH) * topPos / (totalHeight - fHeight); // pixels relative to top of list
+		int boxW = mFastScrollRectW;
+
+		int x = centerX - boxW / 2;
+		int y = mRenderY + mHeaderH + boxY;
+
+		// line above and below box (needs to be split because box can be transparent)
+		gr_color(mFastScrollLineColor.red, mFastScrollLineColor.green, mFastScrollLineColor.blue, mFastScrollLineColor.alpha);
+		gr_fill(centerX - mFastScrollLineW / 2, mRenderY + mHeaderH, mFastScrollLineW, boxY);
+		gr_fill(centerX - mFastScrollLineW / 2, y + boxH, mFastScrollLineW, fHeight - boxY - boxH);
+
+		// box
+		gr_color(mFastScrollRectColor.red, mFastScrollRectColor.green, mFastScrollRectColor.blue, mFastScrollRectColor.alpha);
+		gr_fill(x, y, boxW, boxH);
+
+		mFastScrollRectCurrentY = boxY;
+		mFastScrollRectCurrentH = boxH;
+	}
+	mUpdate = 0;
+	return 0;
+}
+
+void GUIScrollList::RenderItem(size_t itemindex __unused, int yPos, bool selected)
+{
+	RenderStdItem(yPos, selected, NULL, "implement RenderItem!");
+}
+
+void GUIScrollList::RenderStdItem(int yPos, bool selected, ImageResource* icon, const char* text, int iconAndTextH)
+{
+	if (hasHighlightColor && selected) {
+		// Highlight the item background of the selected item
+		gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha);
+		gr_fill(mRenderX, yPos, mRenderW, actualItemHeight);
+	}
+
+	if (selected) {
+		// Use the highlight color for the font
+		gr_color(mFontHighlightColor.red, mFontHighlightColor.green, mFontHighlightColor.blue, mFontHighlightColor.alpha);
+	} else {
+		// Set the color for the font
+		gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
+	}
+
+	if (!iconAndTextH)
+		iconAndTextH = actualItemHeight;
+
+	// render icon
+	if (icon && icon->GetResource()) {
+		int iconH = icon->GetHeight();
+		int iconW = icon->GetWidth();
+		int iconY = yPos + (iconAndTextH - iconH) / 2;
+		int iconX = mRenderX + (maxIconWidth - iconW) / 2;
+		gr_blit(icon->GetResource(), 0, 0, iconW, iconH, iconX, iconY);
+	}
+
+	// render label text
+	if (mFont && mFont->GetResource()) {
+		int textX = mRenderX + maxIconWidth + 5;
+		int textY = yPos + (iconAndTextH / 2);
+		gr_textEx_scaleW(textX, textY, text, mFont->GetResource(), mRenderW, TEXT_ONLY_RIGHT, 0);
+	}
+}
+
+int GUIScrollList::Update(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	if (!mHeaderIsStatic) {
+		std::string newValue = gui_parse_text(mHeaderText);
+		if (mLastHeaderValue != newValue) {
+			mLastHeaderValue = newValue;
+			mUpdate = 1;
+		}
+	}
+
+	// Handle kinetic scrolling
+	// maximum number of items to scroll per update
+	float maxItemsScrolledPerFrame = std::max(2.5, float(GetDisplayItemCount() / 4) + 0.5);
+
+	int maxScrollDistance = actualItemHeight * maxItemsScrolledPerFrame;
+	int oldScrollingSpeed = scrollingSpeed;
+	if (scrollingSpeed == 0) {
+		// Do nothing
+		return 0;
+	} else if (scrollingSpeed > 0) {
+		if (scrollingSpeed < maxScrollDistance)
+			y_offset += scrollingSpeed;
+		else
+			y_offset += maxScrollDistance;
+		scrollingSpeed *= SCROLLING_SPEED_DECREMENT;
+		if (scrollingSpeed == oldScrollingSpeed)
+			--scrollingSpeed;
+	} else if (scrollingSpeed < 0) {
+		if (abs(scrollingSpeed) < maxScrollDistance)
+			y_offset += scrollingSpeed;
+		else
+			y_offset -= maxScrollDistance;
+		scrollingSpeed *= SCROLLING_SPEED_DECREMENT;
+		if (scrollingSpeed == oldScrollingSpeed)
+			++scrollingSpeed;
+	}
+	if (abs(scrollingSpeed) < SCROLLING_FLOOR)
+		scrollingSpeed = 0;
+	HandleScrolling();
+	mUpdate = 1;
+
+	return 0;
+}
+
+size_t GUIScrollList::HitTestItem(int x __unused, int y)
+{
+	// We only care about y position
+	if (y < mRenderY || y - mRenderY <= mHeaderH || y - mRenderY > mRenderH)
+		return NO_ITEM;
+
+	int startSelection = (y - mRenderY - mHeaderH);
+
+	// Locate the correct item
+	size_t actualSelection = firstDisplayedItem;
+	int selectY = y_offset;
+	while (selectY + actualItemHeight < startSelection) {
+		selectY += actualItemHeight;
+		actualSelection++;
+	}
+
+	if (actualSelection < GetItemCount())
+		return actualSelection;
+
+	return NO_ITEM;
+}
+
+int GUIScrollList::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	if (!isConditionTrue())
+		return -1;
+
+	switch (state)
+	{
+	case TOUCH_START:
+		if (hasScroll && x >= mRenderX + mRenderW - mFastScrollW) {
+			fastScroll = 1; // Initial touch is in the fast scroll region
+			int fastScrollBoxTop = mFastScrollRectCurrentY + mRenderY + mHeaderH;
+			int fastScrollBoxBottom = fastScrollBoxTop + mFastScrollRectCurrentH;
+			if (y >= fastScrollBoxTop && y < fastScrollBoxBottom)
+				// user grabbed the fastscroll bar
+				// try to keep the initially touched part of the scrollbar under the finger
+				mFastScrollRectTouchY = y - fastScrollBoxTop;
+			else
+				// user tapped outside the fastscroll bar
+				// center fastscroll rect on the initial touch position
+				mFastScrollRectTouchY = mFastScrollRectCurrentH / 2;
+		}
+
+		if (scrollingSpeed != 0) {
+			selectedItem = NO_ITEM; // this allows the user to tap the list to stop the scrolling without selecting the item they tap
+			scrollingSpeed = 0; // stop scrolling on a new touch
+		} else if (!fastScroll && allowSelection) {
+			// find out which item the user touched
+			selectedItem = HitTestItem(x, y);
+		}
+		if (selectedItem != NO_ITEM)
+			mUpdate = 1;
+		lastY = last2Y = y;
+		break;
+
+	case TOUCH_DRAG:
+		if (fastScroll)
+		{
+			int relY = y - mRenderY - mHeaderH; // touch position relative to window
+			int windowH = mRenderH - mHeaderH;
+			int totalHeight = GetItemCount() * actualItemHeight; // total height of the full list in pixels
+
+			// calculate new top position of the fastscroll bar relative to window
+			int newY = relY - mFastScrollRectTouchY;
+			// keep it fully inside the list
+			newY = std::min(std::max(newY, 0), windowH - mFastScrollRectCurrentH);
+
+			// now compute the new scroll position for the list
+			int newTopPos = newY * (totalHeight - windowH) / (windowH - mFastScrollRectCurrentH); // new top pixel of list
+			newTopPos = std::min(newTopPos, totalHeight - windowH); // account for rounding errors
+			firstDisplayedItem = newTopPos / actualItemHeight;
+			y_offset = - newTopPos % actualItemHeight;
+
+			selectedItem = NO_ITEM;
+			mUpdate = 1;
+			scrollingSpeed = 0; // prevent kinetic scrolling when using fast scroll
+			break;
+		}
+
+		// Provide some debounce on initial touches
+		if (selectedItem != NO_ITEM && abs(y - lastY) < touchDebounce) {
+			mUpdate = 1;
+			break;
+		}
+
+		selectedItem = NO_ITEM; // nothing is selected because we dragged too far
+		// Handle scrolling
+		if (hasScroll) {
+			y_offset += y - lastY; // adjust the scrolling offset based on the difference between the starting touch and the current touch
+			last2Y = lastY; // keep track of previous y locations so that we can tell how fast to scroll for kinetic scrolling
+			lastY = y; // update last touch to the current touch so we can tell how far and what direction we scroll for the next touch event
+
+			HandleScrolling();
+		} else
+			y_offset = 0;
+		mUpdate = 1;
+		break;
+
+	case TOUCH_RELEASE:
+		if (fastScroll)
+			mUpdate = 1; // get rid of touch effects on the fastscroll bar
+		fastScroll = 0;
+		if (selectedItem != NO_ITEM) {
+			// We've selected an item!
+			NotifySelect(selectedItem);
+			mUpdate = 1;
+
+#ifndef TW_NO_HAPTICS
+			DataManager::Vibrate("tw_button_vibrate");
+#endif
+
+			selectedItem = NO_ITEM;
+		} else {
+			// Start kinetic scrolling
+			scrollingSpeed = lastY - last2Y;
+			if (abs(scrollingSpeed) < touchDebounce)
+				scrollingSpeed = 0;
+		}
+	case TOUCH_REPEAT:
+	case TOUCH_HOLD:
+		break;
+	}
+	return 0;
+}
+
+void GUIScrollList::HandleScrolling()
+{
+	// handle dragging downward, scrolling upward
+	// the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
+	while (firstDisplayedItem && y_offset > 0) {
+		firstDisplayedItem--;
+		y_offset -= actualItemHeight;
+	}
+	if (firstDisplayedItem == 0 && y_offset > 0) {
+		y_offset = 0; // user kept dragging downward past the top of the list, so always reset the offset to 0 since we can't scroll any further in this direction
+		scrollingSpeed = 0; // stop kinetic scrolling
+	}
+
+	// handle dragging upward, scrolling downward
+	int totalSize = GetItemCount();
+	int lines = GetDisplayItemCount(); // number of full lines our list can display at once
+	int bottom_offset = GetDisplayRemainder() - actualItemHeight; // extra display area that can display a partial line for per pixel scrolling
+
+	// the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
+	while (firstDisplayedItem + lines + (bottom_offset ? 1 : 0) < totalSize && abs(y_offset) > actualItemHeight) {
+		firstDisplayedItem++;
+		y_offset += actualItemHeight;
+	}
+	// Check if we dragged too far, set the list at the bottom and adjust offset as needed
+	if (bottom_offset != 0 && firstDisplayedItem + lines + 1 >= totalSize && y_offset <= bottom_offset) {
+		firstDisplayedItem = totalSize - lines - 1;
+		y_offset = bottom_offset;
+		scrollingSpeed = 0; // stop kinetic scrolling
+	} else if (firstDisplayedItem + lines >= totalSize && y_offset < 0) {
+		firstDisplayedItem = totalSize - lines;
+		y_offset = 0;
+		scrollingSpeed = 0; // stop kinetic scrolling
+	}
+}
+
+int GUIScrollList::GetDisplayItemCount()
+{
+	return (mRenderH - mHeaderH) / (actualItemHeight);
+}
+
+int GUIScrollList::GetDisplayRemainder()
+{
+	return (mRenderH - mHeaderH) % actualItemHeight;
+}
+
+int GUIScrollList::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	GUIObject::NotifyVarChange(varName, value);
+
+	if (!isConditionTrue())
+		return 0;
+
+	if (!mHeaderIsStatic) {
+		std::string newValue = gui_parse_text(mHeaderText);
+		if (mLastHeaderValue != newValue) {
+			mLastHeaderValue = newValue;
+			firstDisplayedItem = 0;
+			y_offset = 0;
+			scrollingSpeed = 0; // stop kinetic scrolling on variable changes
+			mUpdate = 1;
+		}
+	}
+	return 0;
+}
+
+int GUIScrollList::SetRenderPos(int x, int y, int w /* = 0 */, int h /* = 0 */)
+{
+	mRenderX = x;
+	mRenderY = y;
+	if (w || h)
+	{
+		mRenderW = w;
+		mRenderH = h;
+	}
+	SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
+	mUpdate = 1;
+	return 0;
+}
+
+void GUIScrollList::SetPageFocus(int inFocus)
+{
+	if (inFocus) {
+		NotifyVarChange("", ""); // This forces a check for the header text
+		scrollingSpeed = 0; // stop kinetic scrolling on page changes
+		mUpdate = 1;
+	}
+}
+
+bool GUIScrollList::AddLines(std::vector<std::string>* origText, std::vector<std::string>* origColor, size_t* lastCount, std::vector<std::string>* rText, std::vector<std::string>* rColor)
+{
+	if (!mFont || !mFont->GetResource())
+		return false;
+	if (*lastCount == origText->size())
+		return false; // nothing to add
+
+	size_t prevCount = *lastCount;
+	*lastCount = origText->size();
+
+	// Due to word wrap, figure out what / how the newly added text needs to be added to the render vector that is word wrapped
+	// Note, that multiple consoles on different GUI pages may be different widths or use different fonts, so the word wrapping
+	// may different in different console windows
+	for (size_t i = prevCount; i < *lastCount; i++) {
+		string curr_line = origText->at(i);
+		string curr_color;
+		if (origColor)
+			curr_color = origColor->at(i);
+		for (;;) {
+			size_t line_char_width = twrpTruetype::gr_ttf_maxExW(curr_line.c_str(), mFont->GetResource(), mRenderW);
+			if (line_char_width < curr_line.size()) {
+				//string left = curr_line.substr(0, line_char_width);
+				size_t wrap_pos = curr_line.find_last_of(" ,./:-_;", line_char_width - 1);
+				if (wrap_pos == string::npos)
+					wrap_pos = line_char_width;
+				else if (wrap_pos < line_char_width - 1)
+					wrap_pos++;
+				rText->push_back(curr_line.substr(0, wrap_pos));
+				if (origColor)
+					rColor->push_back(curr_color);
+				curr_line = curr_line.substr(wrap_pos);
+				/* After word wrapping, delete any leading spaces. Note that the word wrapping is not smart enough to know not
+				 * to wrap in the middle of something like ... so some of the ... could appear on the following line. */
+				curr_line.erase(0, curr_line.find_first_not_of(" "));
+			} else {
+				rText->push_back(curr_line);
+				if (origColor)
+					rColor->push_back(curr_color);
+				break;
+			}
+		}
+	}
+	return true;
+}
diff --git a/gui/slider.cpp b/gui/slider.cpp
new file mode 100755
index 0000000..a183d28
--- /dev/null
+++ b/gui/slider.cpp
@@ -0,0 +1,227 @@
+/*
+	Copyright 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// slider.cpp - GUISlider object
+// Pulled & ported from https://raw.github.com/agrabren/RecoverWin/master/gui/slider.cpp
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "../data.hpp"
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUISlider::GUISlider(xml_node<>* node) : GUIObject(node)
+{
+	xml_node<>* child;
+
+	sAction = NULL;
+	sSliderLabel = NULL;
+	sSlider = NULL;
+	sSliderUsed = NULL;
+	sTouch = NULL;
+	sTouchW = 20;
+
+	if (!node)
+	{
+		LOGERR("GUISlider created without XML node\n");
+		return;
+	}
+
+	// Load the resources
+	child = FindNode(node, "resource");
+	if (child)
+	{
+		sSlider = LoadAttrImage(child, "base");
+		sSliderUsed = LoadAttrImage(child, "used");
+		sTouch = LoadAttrImage(child, "touch");
+	}
+
+	// Load the text label
+	sSliderLabel = new GUIText(node);
+	if (sSliderLabel->Render() < 0)
+	{
+		delete sSliderLabel;
+		sSliderLabel = NULL;
+	}
+
+	// Load the placement
+	Placement TextPlacement = CENTER;
+	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH, &TextPlacement);
+
+	if (sSlider && sSlider->GetResource()) {
+		mRenderW = sSlider->GetWidth();
+		mRenderH = sSlider->GetHeight();
+	} else {
+		mRenderW = mRenderH = 0;
+	}
+	if (TextPlacement == CENTER || TextPlacement == CENTER_X_ONLY) {
+		mRenderX = mRenderX - (mRenderW / 2);
+		if (TextPlacement == CENTER) {
+			mRenderY = mRenderY - (mRenderH / 2);
+		}
+	}
+	if (sSliderLabel) {
+		int sTextX = mRenderX + (mRenderW / 2);
+		int w, h;
+		sSliderLabel->GetCurrentBounds(w, h);
+		int sTextY = mRenderY + ((mRenderH - h) / 2);
+		sSliderLabel->SetRenderPos(sTextX, sTextY);
+		sSliderLabel->SetMaxWidth(mRenderW);
+	}
+	if (sTouch && sTouch->GetResource())
+	{
+		sTouchW = sTouch->GetWidth();  // Width of the "touch image" that follows the touch (arrow)
+		sTouchH = sTouch->GetHeight(); // Height of the "touch image" that follows the touch (arrow)
+	}
+
+	//LOGINFO("mRenderW: %i mTouchW: %i\n", mRenderW, mTouchW);
+	mActionX = mRenderX;
+	mActionY = mRenderY;
+	mActionW = mRenderW;
+	mActionH = mRenderH;
+
+	sAction = new GUIAction(node);
+
+	sCurTouchX = mRenderX;
+	sUpdate = 1;
+}
+
+GUISlider::~GUISlider()
+{
+	delete sAction;
+	delete sSliderLabel;
+}
+
+int GUISlider::Render(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	if (!sSlider || !sSlider->GetResource())
+		return -1;
+
+	// Draw the slider
+	gr_blit(sSlider->GetResource(), 0, 0, mRenderW, mRenderH, mRenderX, mRenderY);
+
+	// Draw the used
+	if (sSliderUsed && sSliderUsed->GetResource() && sCurTouchX > mRenderX)
+		gr_blit(sSliderUsed->GetResource(), 0, 0, sCurTouchX - mRenderX, mRenderH, mRenderX, mRenderY);
+
+	// Draw the touch icon
+	if (sTouch && sTouch->GetResource())
+		gr_blit(sTouch->GetResource(), 0, 0, sTouchW, sTouchH, sCurTouchX, (mRenderY + ((mRenderH - sTouchH) / 2)));
+
+	if (sSliderLabel) {
+		int ret = sSliderLabel->Render();
+		if (ret < 0)		return ret;
+	}
+
+	sUpdate = 0;
+	return 0;
+}
+
+int GUISlider::Update(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	if (sUpdate)
+		return 2;
+	return 0;
+}
+
+int GUISlider::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	if (!isConditionTrue())
+		return -1;
+
+	static bool dragging = false;
+
+	switch (state)
+	{
+	case TOUCH_START:
+		if (x >= mRenderX && x <= mRenderX + sTouchW &&
+			y >= mRenderY && y <= mRenderY + mRenderH)
+		{
+			sCurTouchX = x - (sTouchW / 2);
+			if (sCurTouchX < mRenderX)
+				sCurTouchX = mRenderX;
+			dragging = true;
+		}
+		break;
+
+	case TOUCH_DRAG:
+		if (!dragging)
+			return 0;
+		if (y < mRenderY - sTouchH || y > mRenderY + (sTouchH * 2))
+		{
+			sCurTouchX = mRenderX;
+			dragging = false;
+			sUpdate = 1;
+			break;
+		}
+		sCurTouchX = x - (sTouchW / 2);
+		if (sCurTouchX < mRenderX)
+			sCurTouchX = mRenderX;
+		if (sCurTouchX > mRenderX + mRenderW - sTouchW)
+			sCurTouchX = mRenderX + mRenderW - sTouchW;
+		sUpdate = 1;
+		break;
+
+	case TOUCH_RELEASE:
+		if (!dragging)
+			return 0;
+
+		if (sCurTouchX >= mRenderX + mRenderW - sTouchW) {
+
+#ifndef TW_NO_HAPTICS
+			DataManager::Vibrate("tw_button_vibrate");
+#endif
+
+			sAction->doActions();
+		}
+
+		sCurTouchX = mRenderX;
+		dragging = false;
+		sUpdate = 1;
+	case TOUCH_REPEAT:
+	case TOUCH_HOLD:
+		break;
+	}
+	return 0;
+}
diff --git a/gui/slidervalue.cpp b/gui/slidervalue.cpp
new file mode 100755
index 0000000..8deb9e0
--- /dev/null
+++ b/gui/slidervalue.cpp
@@ -0,0 +1,444 @@
+/*
+    Copyright 2012 to 2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// slidervalue.cpp - GUISliderValue object
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+#include "minuitwrp/truetype.hpp"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUISliderValue::GUISliderValue(xml_node<>* node) : GUIObject(node)
+{
+	xml_attribute<>* attr;
+	xml_node<>* child;
+
+	mMin = 0;
+	mMax = 100;
+	mValue = 0;
+	mLineH = 2;
+	mLinePadding = 10;
+	mSliderW = 5;
+	mSliderH = 30;
+	mLineX = 0;
+	mLineY = 0;
+	mValueStr = NULL;
+	mAction = NULL;
+	mShowCurr = true;
+	mShowRange = false;
+	mChangeOnDrag = false;
+	mRendered = false;
+	mBackgroundImage = NULL;
+	mHandleImage = NULL;
+	mHandleHoverImage = NULL;
+	mDragging = false;
+
+	mLabel = NULL;
+	ConvertStrToColor("white", &mTextColor);
+	ConvertStrToColor("white", &mLineColor);
+	ConvertStrToColor("blue", &mSliderColor);
+
+	if (!node)
+	{
+		LOGERR("GUISliderValue created without XML node\n");
+		return;
+	}
+
+	mLabel = new GUIText(node);
+	if (mLabel->Render() < 0)
+	{
+		delete mLabel;
+		mLabel = NULL;
+	}
+
+	mAction = new GUIAction(node);
+
+	child = FindNode(node, "font");
+	if (child)
+	{
+		mFont = LoadAttrFont(child, "resource");
+		mTextColor = LoadAttrColor(child, "color", mTextColor);
+	}
+
+	// Load the placement
+	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW);
+
+	child = FindNode(node, "colors");
+	if (child)
+	{
+		mLineColor = LoadAttrColor(child, "line");
+		mSliderColor = LoadAttrColor(child, "slider");
+	}
+
+	child = FindNode(node, "resource");
+	if (child)
+	{
+		mBackgroundImage = LoadAttrImage(child, "background");
+		mHandleImage = LoadAttrImage(child, "handle");
+		mHandleHoverImage = LoadAttrImage(child, "handlehover");
+	}
+
+	child = FindNode(node, "data");
+	if (child)
+	{
+		attr = child->first_attribute("variable");
+		if (attr)
+			mVariable = attr->value();
+
+		attr = child->first_attribute("min");
+		if (attr)
+		{
+			mMinStr = gui_parse_text(attr->value());
+			mMin = atoi(mMinStr.c_str());
+		}
+
+		attr = child->first_attribute("max");
+		if (attr)
+		{
+			mMaxStr = gui_parse_text(attr->value());
+			mMax = atoi(mMaxStr.c_str());
+		}
+
+		if (mMin > mMax)
+			mMin = mMax;
+
+		attr = child->first_attribute("default");
+		if (attr)
+		{
+			string parsevalue = gui_parse_text(attr->value());
+			int def = atoi(parsevalue.c_str());
+
+			if (def < mMin)
+				def = mMin;
+			else if (def > mMax)
+				def = mMax;
+			DataManager::SetValue(mVariable, def);
+		}
+
+		attr = child->first_attribute("showrange");
+		if (attr)
+			mShowRange = atoi(attr->value());
+
+		attr = child->first_attribute("showcurr");
+		if (attr)
+			mShowCurr = atoi(attr->value());
+
+		attr = child->first_attribute("changeondrag");
+		if (attr)
+			mChangeOnDrag = atoi(attr->value());
+	}
+
+	child = FindNode(node, "dimensions");
+	if (child)
+	{
+		mLineH = LoadAttrIntScaleY(child, "lineh", mLineH);
+		mLinePadding = LoadAttrIntScaleX(child, "linepadding", mLinePadding);
+		mSliderW = LoadAttrIntScaleX(child, "sliderw", mSliderW);
+		mSliderH = LoadAttrIntScaleY(child, "sliderh", mSliderH);
+	}
+
+	if (mFont && mFont->GetResource())
+		mFontHeight = mFont->GetHeight();
+	else
+		mFontHeight = 0;
+
+	if (mShowCurr)
+	{
+		int maxLen = std::max(strlen(mMinStr.c_str()), strlen(mMaxStr.c_str()));
+		mValueStr = new char[maxLen+1];
+	}
+
+	loadValue(true);
+
+	if (mShowRange)
+	{
+		int textW = std::max(measureText(mMaxStr), measureText(mMinStr));
+		mLinePadding += textW;
+	}
+
+	SetRenderPos(mRenderX, mRenderY, mRenderW);
+}
+
+GUISliderValue::~GUISliderValue()
+{
+	delete mLabel;
+	delete mAction;
+	delete[] mValueStr;
+}
+
+void GUISliderValue::loadValue(bool force)
+{
+	if (!mVariable.empty())
+	{
+		int value = DataManager::GetIntValue(mVariable);
+		if (mValue == value && !force)
+			return;
+
+		mValue = value;
+	}
+
+	mValue = std::max(mValue, mMin);
+	mValue = std::min(mValue, mMax);
+	mValuePct = pctFromValue(mValue);
+	mRendered = false;
+}
+
+int GUISliderValue::SetRenderPos(int x, int y, int w, int h)
+{
+	mRenderX = x;
+	mRenderY = y;
+	if (w || h)
+	{
+		mRenderW = w;
+		mRenderH = h;
+	}
+
+	mRenderH = mSliderH;
+	if (mShowCurr)
+		mRenderH += mFontHeight;
+
+	if (mLabel)
+	{
+		int lw, lh;
+		mLabel->GetCurrentBounds(lw, lh);
+		int textX = mRenderX + (mRenderW/2 - lw/2);
+
+		mLabel->SetRenderPos(textX, mRenderY);
+
+		y += lh;
+		mRenderH += lh;
+	}
+
+	mSliderY = y;
+
+	mActionX = mRenderX;
+	mActionY = mRenderY;
+	mActionW = mRenderW;
+	mActionH = mRenderH;
+
+	if (mBackgroundImage && mBackgroundImage->GetResource())
+	{
+		mLineW = mBackgroundImage->GetWidth();
+		mLineH = mBackgroundImage->GetHeight();
+	}
+	else
+		mLineW = mRenderW - (mLinePadding * 2);
+
+	mLineY = y + (mSliderH/2 - mLineH/2);
+	mLineX = mRenderX + (mRenderW/2 - mLineW/2);
+
+	return 0;
+}
+
+int GUISliderValue::measureText(const std::string& str)
+{
+	void* fontResource = NULL;
+	if (mFont)  fontResource = mFont->GetResource();
+
+	return twrpTruetype::gr_ttf_measureEx(str.c_str(), fontResource);
+}
+
+int GUISliderValue::Render(void)
+{
+	if (!isConditionTrue())
+	{
+		mRendered = false;
+		return 0;
+	}
+
+	if (mLabel)
+	{
+		int w, h;
+		mLabel->GetCurrentBounds(w, h);
+		if (w != mLabelW) {
+			mLabelW = w;
+			int textX = mRenderX + (mRenderW/2 - mLabelW/2);
+			mLabel->SetRenderPos(textX, mRenderY);
+		}
+		int res = mLabel->Render();
+		if (res < 0)
+			return res;
+	}
+
+	// line
+	if (mBackgroundImage && mBackgroundImage->GetResource())
+	{
+		gr_blit(mBackgroundImage->GetResource(), 0, 0, mLineW, mLineH, mLineX, mLineY);
+	}
+	else
+	{
+		gr_color(mLineColor.red, mLineColor.green, mLineColor.blue, mLineColor.alpha);
+		gr_fill(mLineX, mLineY, mLineW, mLineH);
+	}
+
+	// slider
+	uint32_t sliderX = mLineX + (mValuePct*(mLineW - mSliderW))/100;
+
+	if (mHandleImage && mHandleImage->GetResource())
+	{
+		gr_surface s = mHandleImage->GetResource();
+		if (mDragging && mHandleHoverImage && mHandleHoverImage->GetResource())
+			s = mHandleHoverImage->GetResource();
+		gr_blit(s, 0, 0, mSliderW, mSliderH, sliderX, mLineY + (mLineH/2 - mSliderH/2));
+	}
+	else
+	{
+		gr_color(mSliderColor.red, mSliderColor.green, mSliderColor.blue, mSliderColor.alpha);
+		gr_fill(sliderX, mSliderY, mSliderW, mSliderH);
+	}
+
+	void *fontResource = NULL;
+	if (mFont) fontResource = mFont->GetResource();
+	gr_color(mTextColor.red, mTextColor.green, mTextColor.blue, mTextColor.alpha);
+	if (mShowRange)
+	{
+		int rangeY = (mLineY - mLineH/2) - mFontHeight/2;
+		gr_textEx_scaleW(mRenderX + mPadding/2, rangeY, mMinStr.c_str(), fontResource, mRenderW, TOP_LEFT, 0);
+		gr_textEx_scaleW(mLineX + mLineW + mPadding/2, rangeY, mMaxStr.c_str(), fontResource, mRenderW, TOP_LEFT, 0);
+	}
+
+	if (mValueStr && mShowCurr)
+	{
+		sprintf(mValueStr, "%d", mValue);
+		int textW = measureText(mValueStr);
+		gr_textEx_scaleW(mRenderX + (mRenderW/2 - textW/2), mSliderY+mSliderH, mValueStr, fontResource, mRenderW, TOP_LEFT, 0);
+	}
+
+	mRendered = true;
+	return 0;
+}
+
+int GUISliderValue::Update(void)
+{
+	if (!isConditionTrue())
+		return mRendered ? 2 : 0;
+	if (!mRendered)
+		return 2;
+
+	if (mLabel)
+		return mLabel->Update();
+	return 0;
+}
+
+int GUISliderValue::valueFromPct(float pct)
+{
+	int range = abs(mMax - mMin);
+	return mMin + (pct * range) / 100;
+}
+
+float GUISliderValue::pctFromValue(int value)
+{
+	return float((value - mMin) * 100) / abs(mMax - mMin);
+}
+
+int GUISliderValue::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	if (!isConditionTrue())
+		return -1;
+
+	switch (state)
+	{
+	case TOUCH_START:
+		if (x >= mLineX && x <= mLineX + mLineW &&
+			y >= mRenderY && y <= mRenderY + mRenderH)
+		{
+			mDragging = true;
+		}
+		// no break
+	case TOUCH_DRAG:
+	{
+		if (!mDragging)  return 0;
+
+		x = std::max(mLineX + mSliderW/2, x);
+		x = std::min(mLineX + mLineW - mSliderW/2, x);
+
+		mValuePct = float(((x - (mLineX + mSliderW/2)) * 100) / (mLineW - mSliderW));
+		int newVal = valueFromPct(mValuePct);
+		if (newVal != mValue) {
+			mRendered = false;
+			mValue = newVal;
+			if (mChangeOnDrag) {
+				if (!mVariable.empty())
+					DataManager::SetValue(mVariable, mValue);
+				if (mAction)
+					mAction->doActions();
+			}
+		}
+		break;
+	}
+	case TOUCH_RELEASE:
+	{
+		if (!mDragging)  return 0;
+		mDragging = false;
+
+		if (!mVariable.empty())
+			DataManager::SetValue(mVariable, mValue);
+		if (mAction)
+			mAction->doActions();
+		break;
+	}
+	case TOUCH_REPEAT:
+	case TOUCH_HOLD:
+		break;
+	}
+	return 0;
+}
+
+int GUISliderValue::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	GUIObject::NotifyVarChange(varName, value);
+
+	if (mLabel)
+		mLabel->NotifyVarChange(varName, value);
+	if (varName == mVariable) {
+		int newVal = atoi(value.c_str());
+		if (newVal != mValue) {
+			mValue = newVal;
+			mValuePct = pctFromValue(mValue);
+			mRendered = false;
+		}
+	}
+	return 0;
+}
+
+void GUISliderValue::SetPageFocus(int inFocus)
+{
+	if (inFocus)
+		loadValue();
+}
diff --git a/gui/terminal.cpp b/gui/terminal.cpp
new file mode 100755
index 0000000..7ce8154
--- /dev/null
+++ b/gui/terminal.cpp
@@ -0,0 +1,941 @@
+/*
+	Copyright 2016 _that/TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// terminal.cpp - GUITerminal object
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termio.h>
+
+#include <string>
+#include <cctype>
+#include <linux/input.h>
+#include <sys/wait.h>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+#include "minuitwrp/truetype.hpp"
+
+#include "gui.hpp"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+#if 0
+#define debug_printf printf
+#else
+#define debug_printf(...)
+#endif
+
+extern int g_pty_fd; // in gui.cpp where the select is
+/*
+Pseudoterminal handler.
+*/
+class Pseudoterminal
+{
+public:
+	Pseudoterminal() : fdMaster(0), pid(0)
+	{
+	}
+
+	bool started() const { return pid > 0; }
+
+	bool start()
+	{
+		fdMaster = getpt();
+		if (fdMaster < 0) {
+			LOGERR("Error %d on getpt()\n", errno);
+			return false;
+		}
+
+		if (unlockpt(fdMaster) != 0) {
+			LOGERR("Error %d on unlockpt()\n", errno);
+			return false;
+		}
+
+		pid = fork();
+		if (pid < 0) {
+			LOGERR("fork failed for pty, error %d\n", errno);
+			close(fdMaster);
+			pid = 0;
+			return false;
+		}
+		else if (pid) {
+			// child started, now someone needs to periodically read from fdMaster
+			// and write it to the terminal
+			// this currently works through gui.cpp calling terminal_pty_read below
+			g_pty_fd = fdMaster;
+			set_select_fd();
+			return true;
+		}
+		else {
+			int fdSlave = open(ptsname(fdMaster), O_RDWR);
+			close(fdMaster);
+			runSlave(fdSlave);
+		}
+		// we can't get here
+		LOGERR("impossible error in pty\n");
+		return false;
+	}
+
+	void runSlave(int fdSlave)
+	{
+		dup2(fdSlave, 0); // PTY becomes standard input (0)
+		dup2(fdSlave, 1); // PTY becomes standard output (1)
+		dup2(fdSlave, 2); // PTY becomes standard error (2)
+
+		// Now the original file descriptor is useless
+		close(fdSlave);
+
+		// Make the current process a new session leader
+		if (setsid() == (pid_t)-1)
+			LOGERR("setsid failed: %d\n", errno);
+
+		// As the child is a session leader, set the controlling terminal to be the slave side of the PTY
+		// (Mandatory for programs like the shell to make them manage correctly their outputs)
+		ioctl(0, TIOCSCTTY, 1);
+
+		execl("/system/bin/sh", "sh", NULL);
+		_exit(127);
+	}
+
+	int read(char* buffer, size_t size)
+	{
+		if (!started()) {
+			LOGERR("someone tried to read from pty, but it was not started\n");
+			return -1;
+		}
+		int rc = ::read(fdMaster, buffer, size);
+		debug_printf("pty read: %d bytes\n", rc);
+		if (rc < 0) {
+			// assume child has died (usual errno when shell exits seems to be EIO == 5)
+			if (errno != EIO)
+				LOGERR("pty read failed: %d\n", errno);
+			stop();
+		}
+		return rc;
+	}
+
+	int write(const char* buffer, size_t size)
+	{
+		if (!started()) {
+			LOGERR("someone tried to write to pty, but it was not started\n");
+			return -1;
+		}
+		int rc = ::write(fdMaster, buffer, size);
+		debug_printf("pty write: %zu bytes -> %d\n", size, rc);
+		if (rc < 0) {
+			LOGERR("pty write failed: %d\n", errno);
+			// assume child has died
+			stop();
+		}
+		return rc;
+	}
+
+	template<size_t n>
+	inline int write(const char (&literal)[n])
+	{
+		return write(literal, n-1);
+	}
+
+	void resize(int xChars, int yChars, int w, int h)
+	{
+		struct winsize ws;
+		ws.ws_row = yChars;
+		ws.ws_col = xChars;
+		ws.ws_xpixel = w;
+		ws.ws_ypixel = h;
+		if (ioctl(fdMaster, TIOCSWINSZ, &ws) < 0)
+			LOGERR("failed to set window size, error %d\n", errno);
+	}
+
+	void stop()
+	{
+		if (!started()) {
+			LOGERR("someone tried to stop pty, but it was not started\n");
+			return;
+		}
+		close(fdMaster);
+		g_pty_fd = fdMaster = -1;
+		set_select_fd();
+		int status;
+		waitpid(pid, &status, WNOHANG); // avoid zombies but don't hang if the child is still alive and we got here due to some error
+		pid = 0;
+	}
+
+private:
+	int fdMaster;
+	pid_t pid;
+};
+
+// UTF-8 decoder
+// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
+
+const uint32_t UTF8_ACCEPT = 0;
+const uint32_t UTF8_REJECT = 1;
+
+static const uint8_t utf8d[] = {
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
+	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
+	7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
+	8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
+	0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
+	0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
+	0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
+	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
+	1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
+	1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
+	1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
+};
+
+uint32_t inline utf8decode(uint32_t* state, uint32_t* codep, uint32_t byte)
+{
+	uint32_t type = utf8d[byte];
+
+	*codep = (*state != UTF8_ACCEPT) ?
+		(byte & 0x3fu) | (*codep << 6) :
+		(0xff >> type) & (byte);
+
+	*state = utf8d[256 + *state*16 + type];
+	return *state;
+}
+// end of UTF-8 decoder
+
+// Append a UTF-8 codepoint to string s
+size_t utf8add(std::string& s, uint32_t cp)
+{
+	if (cp < 0x7f) {
+		s += cp;
+		return 1;
+	}
+	else if (cp < 0x7ff) {
+		s += (0xc0 | (cp >> 6));
+		s += (0x80 | (cp & 0x3f));
+		return 2;
+	}
+	else if (cp < 0xffff) {
+		s += (0xe0 | (cp >> 12));
+		s += (0x80 | ((cp >> 6) & 0x3f));
+		s += (0x80 | (cp & 0x3f));
+		return 3;
+	}
+	else if (cp < 0x1fffff) {
+		s += (0xf0 | (cp >> 18));
+		s += (0x80 | ((cp >> 12) & 0x3f));
+		s += (0x80 | ((cp >> 6) & 0x3f));
+		s += (0x80 | (cp & 0x3f));
+		return 4;
+	}
+	return 0;
+}
+
+/*
+TerminalEngine is the terminal back-end, dealing with the text buffer and attributes
+and with communicating with the pty.
+It does not care about visual things like rendering, fonts, windows etc.
+The idea is that 0 to n GUITerminal instances (e.g. on different pages) can connect
+to one TerminalEngine to interact with the terminal, and that the TerminalEngine
+survives things like page changes or even theme reloads.
+*/
+class TerminalEngine
+{
+public:
+#if 0 // later
+	struct Attributes
+	{
+		COLOR fgcolor; // TODO: what about palette?
+		COLOR bgcolor;
+		// could add bold, underline, blink, etc.
+	};
+
+	struct AttributeRange
+	{
+		size_t start; // start position inside text (in bytes)
+		Attributes a;
+	};
+#endif
+	typedef uint32_t CodePoint; // Unicode code point
+
+	// A line of text, optimized for rendering and storage in the buffer
+	struct Line
+	{
+		std::string text; // in UTF-8 format
+//		std::vector<AttributeRange> attrs;
+		Line() {}
+		size_t utf8forward(size_t start) const
+		{
+			if (start >= text.size())
+				return start;
+			uint32_t u8state = 0, u8cp = 0;
+			size_t i = start;
+			uint32_t rc;
+			do {
+				rc = utf8decode(&u8state, &u8cp, (unsigned char)text[i]);
+				++i;
+			} while (rc != UTF8_ACCEPT && rc != UTF8_REJECT && i < text.size());
+			return i;
+		}
+
+		std::string substr(size_t start, size_t n) const
+		{
+			size_t i = 0;
+			for (; start && i < text.size(); i = utf8forward(i))
+				--start;
+			size_t s = i;
+			for (; n && i < text.size(); i = utf8forward(i))
+				--n;
+			return text.substr(s, i - s);
+		}
+		size_t length() const
+		{
+			size_t n = 0;
+			for (size_t i = 0; i < text.size(); i = utf8forward(i))
+				++n;
+			return n;
+		}
+	};
+
+	// A single character cell with a Unicode code point
+	struct Cell
+	{
+		Cell() : cp(' ') {}
+		Cell(CodePoint cp) : cp(cp) {}
+		CodePoint cp;
+//		Attributes a;
+	};
+
+	// A line of text, optimized for editing single characters
+	struct UnpackedLine
+	{
+		std::vector<Cell> cells;
+		void eraseFrom(size_t x)
+		{
+			if (cells.size() > x)
+				cells.erase(cells.begin() + x, cells.end());
+		}
+
+		void eraseTo(size_t x)
+		{
+			if (x > 0)
+				cells.erase(cells.begin(), cells.begin() + x);
+		}
+	};
+
+	TerminalEngine()
+	{
+		// the default size will be overwritten by the GUI window when the size is known
+		width = 40;
+		height = 10;
+
+		clear();
+		updateCounter = 0;
+		state = kStateGround;
+		utf8state = utf8codepoint = 0;
+	}
+
+	void setSize(int xChars, int yChars, int w, int h)
+	{
+		width = xChars;
+		height = yChars;
+		if (pty.started())
+			pty.resize(width, height, w, h);
+		debug_printf("setSize: %d*%d chars, %d*%d pixels\n", xChars, yChars, w, h);
+	}
+
+	void initPty()
+	{
+		if (!pty.started())
+		{
+			pty.start();
+			pty.resize(width, height, 0, 0);
+		}
+	}
+
+	void readPty()
+	{
+		char buffer[1024];
+		int rc = pty.read(buffer, sizeof(buffer));
+		debug_printf("readPty: %d bytes\n", rc);
+		if (rc < 0)
+			output("\r\nChild process exited.\r\n");	// TODO: maybe exit terminal here
+		else
+			for (int i = 0; i < rc; ++i)
+				output(buffer[i]);
+	}
+
+	bool status() {
+		return pty.started();
+	}
+
+	void stop() {
+		pty.stop();
+	}
+
+	void clear()
+	{
+		cursorX = cursorY = 0;
+		lines.clear();
+		setY(0);
+		unpackLine(0);
+		linewrap = false;
+		++updateCounter;
+	}
+
+	void output(const char *buf)
+	{
+		for (const char* p = buf; *p; ++p)
+			output(*p);
+	}
+
+	void output(const char ch)
+	{
+		char debug[2]; debug[0] = ch; debug[1] = 0;
+		debug_printf("output: %d %s\n", (int)ch, (ch >= ' ' && ch < 127) ? debug : ch == 27 ? "esc" : "");
+		if (ch < 32) {
+			// always process control chars, even after incomplete UTF-8 fragments
+			processC0(ch);
+			if (utf8state != UTF8_ACCEPT)
+			{
+				debug_printf("Terminal: incomplete UTF-8 fragment before control char ignored, codepoint=%u ch=%d\n", utf8codepoint, (int)ch);
+				utf8state = UTF8_ACCEPT;
+			}
+			return;
+		}
+		uint32_t rc = utf8decode(&utf8state, &utf8codepoint, (unsigned char)ch);
+		if (rc == UTF8_ACCEPT)
+			processCodePoint(utf8codepoint);
+		else if (rc == UTF8_REJECT) {
+			debug_printf("Terminal: invalid UTF-8 sequence ignored, codepoint=%u ch=%d\n", utf8codepoint, (int)ch);
+			utf8state = UTF8_ACCEPT;
+		}
+		// else we need to read more bytes to assemble a codepoint
+	}
+
+	bool inputChar(int ch)
+	{
+		debug_printf("inputChar: %d\n", ch);
+		initPty();	// reinit just in case it died before
+		// encode the char as UTF-8 and send it to the pty
+		std::string c;
+		utf8add(c, (uint32_t)ch);
+		pty.write(c.c_str(), c.size());
+		return true;
+	}
+
+	bool inputKey(int key)
+	{
+		debug_printf("inputKey: %d\n", key);
+		switch (key)
+		{
+			case KEY_UP: pty.write("\e[A"); break;
+			case KEY_DOWN: pty.write("\e[B"); break;
+			case KEY_RIGHT: pty.write("\e[C"); break;
+			case KEY_LEFT: pty.write("\e[D"); break;
+			case KEY_HOME: pty.write("\eOH"); break;
+			case KEY_END: pty.write("\eOF"); break;
+			case KEY_INSERT: pty.write("\e[2~"); break;
+			case KEY_DELETE: pty.write("\e[3~"); break;
+			case KEY_PAGEUP: pty.write("\e[5~"); break;
+			case KEY_PAGEDOWN: pty.write("\e[6~"); break;
+			// TODO: other keys
+			default:
+				return false;
+		}
+		return true;
+	}
+
+	size_t getLinesCount() const { return lines.size(); }
+	const Line& getLine(size_t n) { if (unpackedY == n) packLine(); return lines[n]; }
+	int getCursorX() const { return cursorX; }
+	int getCursorY() const { return cursorY; }
+	int getUpdateCounter() const { return updateCounter; }
+
+	void setX(int x)
+	{
+		x = min(width, max(x, 0));
+		cursorX = x;
+		linewrap = false;
+		++updateCounter;
+	}
+
+	void setY(int y)
+	{
+		//y = min(height, max(y, 0));
+		y = max(y, 0);
+		cursorY = y;
+		linewrap = false;
+		while (lines.size() <= (size_t) y)
+			lines.push_back(Line());
+		++updateCounter;
+	}
+
+	void up(int n = 1) { setY(cursorY - n); }
+	void down(int n = 1) { setY(cursorY + n); }
+	void left(int n = 1) { setX(cursorX - n); }
+	void right(int n = 1) { setX(cursorX + n); }
+
+private:
+	void packLine()
+	{
+		std::string& s = lines[unpackedY].text;
+		s.clear();
+		for (size_t i = 0; i < unpackedLine.cells.size(); ++i) {
+			Cell& c = unpackedLine.cells[i];
+			utf8add(s, c.cp);
+			// later: if attributes changed, add attributes
+		}
+	}
+
+	void unpackLine(size_t y)
+	{
+		uint32_t u8state = 0, u8cp = 0;
+		std::string& s = lines[y].text;
+		unpackedLine.cells.clear();
+		for (size_t i = 0; i < s.size(); ++i) {
+			uint32_t rc = utf8decode(&u8state, &u8cp, (unsigned char)s[i]);
+			if (rc == UTF8_ACCEPT)
+				unpackedLine.cells.push_back(Cell(u8cp));
+		}
+		if (unpackedLine.cells.size() < (size_t)width)
+			unpackedLine.cells.resize(width);
+		unpackedY = y;
+	}
+
+	void ensureUnpacked(size_t y)
+	{
+		if (unpackedY != y)
+		{
+			packLine();
+			unpackLine(y);
+		}
+	}
+
+	void processC0(char ch)
+	{
+		switch (ch)
+		{
+			case 7: // BEL
+
+#ifndef TW_NO_HAPTICS
+				DataManager::Vibrate("tw_button_vibrate");
+#endif
+
+				break;
+			case 8: // BS
+				left();
+				break;
+			case 9: // HT
+				// TODO: this might be totally wrong
+				right();
+				while (cursorX % 8 != 0 && cursorX < width)
+					right();
+				break;
+			case 10: // LF
+			case 11: // VT
+			case 12: // FF
+				down();
+				break;
+			case 13: // CR
+				setX(0);
+				break;
+			case 24: // CAN
+			case 26: // SUB
+				state = kStateGround;
+				ctlseq.clear();
+				break;
+			case 27: // ESC
+				state = kStateEsc;
+				ctlseq.clear();
+				break;
+		}
+	}
+
+	void processCodePoint(CodePoint cp)
+	{
+		++updateCounter;
+		debug_printf("codepoint: %u\n", cp);
+		if (cp == 0x9b) // CSI
+		{
+			state = kStateCsi;
+			ctlseq.clear();
+			return;
+		}
+		switch (state)
+		{
+			case kStateGround:
+				processChar(cp);
+				break;
+			case kStateEsc:
+				processEsc(cp);
+				break;
+			case kStateCsi:
+				processControlSequence(cp);
+				break;
+		}
+	}
+
+	void processChar(CodePoint cp)
+	{
+		if (linewrap) {
+			down();
+			setX(0);
+		}
+		ensureUnpacked(cursorY);
+		// extend unpackedLine if needed, write ch into cell
+		if (unpackedLine.cells.size() <= (size_t)cursorX)
+			unpackedLine.cells.resize(cursorX+1);
+		unpackedLine.cells[cursorX].cp = cp;
+
+		right(); // also bumps updateCounter
+
+		if (cursorX >= width)
+			linewrap = true;
+	}
+
+	void processEsc(CodePoint cp)
+	{
+		switch (cp) {
+			case 'c': // TODO: Reset
+				break;
+			case 'D': // Line feed
+				down();
+				break;
+			case 'E': // Newline
+				setX(0);
+				down();
+				break;
+			case '[': // CSI
+				state = kStateCsi;
+				ctlseq.clear();
+				break;
+			case ']': // TODO: OSC state
+			default:
+				state = kStateGround;
+		}
+	}
+
+	void processControlSequence(CodePoint cp)
+	{
+		if (cp >= 0x40 && cp <= 0x7e) {
+			ctlseq += cp;
+			execControlSequence(ctlseq);
+			ctlseq.clear();
+			state = kStateGround;
+			return;
+		}
+		if (isdigit(cp) || cp == ';' /* || (ch >= 0x3c && ch <= 0x3f) */) {
+			ctlseq += cp;
+			// state = kStateCsiParam;
+			return;
+		}
+	}
+
+	static int parseArg(std::string& s, int defaultvalue)
+	{
+		if (s.empty() || !isdigit(s[0]))
+			return defaultvalue;
+		int value = atoi(s.c_str());
+		size_t pos = s.find(';');
+		s.erase(0, pos != std::string::npos ? pos+1 : std::string::npos);
+		return value;
+	}
+
+	void execControlSequence(std::string ctlseq)
+	{
+		// assert(!ctlseq.empty());
+		if (ctlseq == "6n") {
+			// CPR - cursor position report
+			char answer[20];
+			sprintf(answer, "\e[%d;%dR", cursorY, cursorX);
+			pty.write(answer, strlen(answer));
+			return;
+		}
+		char f = *ctlseq.rbegin();
+		// if (f == '?') ... private mode
+		switch (f)
+		{
+			// case '@': // ICH - insert character
+			case 'A': // CUU - cursor up
+				up(parseArg(ctlseq, 1));
+				break;
+			case 'B': // CUD - cursor down
+			case 'e': // VPR - line position forward
+				down(parseArg(ctlseq, 1));
+				break;
+			case 'C': // CUF - cursor right
+			case 'a': // HPR - character position forward
+				right(parseArg(ctlseq, 1));
+				break;
+			case 'D': // CUB - cursor left
+				left(parseArg(ctlseq, 1));
+				break;
+			case 'E': // CNL - cursor next line
+				down(parseArg(ctlseq, 1));
+				setX(0);
+				break;
+			case 'F': // CPL - cursor preceding line
+				up(parseArg(ctlseq, 1));
+				setX(0);
+				break;
+			case 'G': // CHA - cursor character absolute
+				setX(parseArg(ctlseq, 1)-1);
+				break;
+			case 'H': // CUP - cursor position
+				// TODO: consider scrollback area
+				setY(parseArg(ctlseq, 1)-1);
+				setX(parseArg(ctlseq, 1)-1);
+				break;
+			case 'J': // ED - erase in page
+				{
+					int param = parseArg(ctlseq, 0);
+					ensureUnpacked(cursorY);
+					switch (param) {
+						default:
+						case 0:
+							unpackedLine.eraseFrom(cursorX);
+							if (lines.size() > (size_t)cursorY+1)
+								lines.erase(lines.begin() + cursorY+1, lines.end());
+							break;
+						case 1:
+							unpackedLine.eraseTo(cursorX);
+							if (cursorY > 0) {
+								lines.erase(lines.begin(), lines.begin() + cursorY-1);
+								cursorY = 0;
+							}
+							break;
+						case 2: // clear
+						case 3:	// clear incl scrollback
+							clear();
+							break;
+					}
+				}
+				break;
+			case 'K': // EL - erase in line
+				{
+					int param = parseArg(ctlseq, 0);
+					ensureUnpacked(cursorY);
+					switch (param) {
+						default:
+						case 0:
+							unpackedLine.eraseFrom(cursorX);
+							break;
+						case 1:
+							unpackedLine.eraseTo(cursorX);
+							break;
+						case 2:
+							unpackedLine.cells.clear();
+							break;
+					}
+				}
+				break;
+			// case 'L': // IL - insert line
+
+			default:
+				debug_printf("unknown ctlseq: '%s'\n", ctlseq.c_str());
+				break;
+		}
+	}
+
+private:
+	int cursorX, cursorY; // 0-based, char based. TODO: decide how to handle scrollback
+	bool linewrap; // true to put next character into next line
+	int width, height; // window size in chars
+	std::vector<Line> lines; // the text buffer
+	UnpackedLine unpackedLine; // current line for editing
+	size_t unpackedY; // number of current line
+	int updateCounter; // changes whenever terminal could require redraw
+
+	Pseudoterminal pty;
+	enum { kStateGround, kStateEsc, kStateCsi } state;
+
+	// for accumulating a full UTF-8 character from individual bytes
+	uint32_t utf8state;
+	uint32_t utf8codepoint;
+
+	// for accumulating a control sequence after receiving CSI
+	std::string ctlseq;
+};
+
+// The one and only terminal engine for now
+TerminalEngine gEngine;
+
+void terminal_pty_read()
+{
+	gEngine.readPty();
+}
+
+
+GUITerminal::GUITerminal(xml_node<>* node) : GUIScrollList(node)
+{
+	allowSelection = false; // terminal doesn't support list item selections
+	lastCondition = false;
+
+	if (!node) {
+		mRenderX = 0;
+		mRenderY = 0;
+		mRenderW = gr_fb_width();
+		mRenderH = gr_fb_height();
+	}
+
+	engine = &gEngine;
+	updateCounter = 0;
+}
+
+int GUITerminal::Update(void)
+{
+	if (!isConditionTrue()) {
+		lastCondition = false;
+		return 0;
+	}
+
+	if (lastCondition == false) {
+		lastCondition = true;
+		// we're becoming visible, so we might need to resize the terminal content
+		InitAndResize();
+	}
+
+	if (updateCounter != engine->getUpdateCounter()) {
+		// try to keep the cursor in view
+		SetVisibleListLocation(engine->getCursorY());
+		updateCounter = engine->getUpdateCounter();
+	}
+
+	GUIScrollList::Update();
+
+	if (mUpdate) {
+		mUpdate = 0;
+		if (Render() == 0)
+			return 2;
+	}
+	return 0;
+}
+
+// NotifyTouch - Notify of a touch event
+//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
+int GUITerminal::NotifyTouch(TOUCH_STATE state, int x, int y)
+{
+	if (!isConditionTrue())
+		return -1;
+
+	// TODO: grab focus correctly
+	// TODO: fix focus handling in PageManager and GUIInput
+	SetInputFocus(1);
+	debug_printf("Terminal: SetInputFocus\n");
+	return GUIScrollList::NotifyTouch(state, x, y);
+	// TODO later: allow cursor positioning by touch (simulate mouse click?)
+	// http://stackoverflow.com/questions/5966903/how-to-get-mousemove-and-mouseclick-in-bash
+	// will likely not work with Busybox anyway
+}
+
+int GUITerminal::NotifyKey(int key, bool down)
+{
+	if (!HasInputFocus)
+		return 1;
+	if (down)
+		if (engine->inputKey(key))
+			mUpdate = 1;
+	return 0;
+}
+
+// character input
+int GUITerminal::NotifyCharInput(int ch)
+{
+	if (engine->inputChar(ch))
+		mUpdate = 1;
+	return 0;
+}
+
+size_t GUITerminal::GetItemCount()
+{
+	return engine->getLinesCount();
+}
+
+void GUITerminal::RenderItem(size_t itemindex, int yPos, bool selected __unused)
+{
+	const TerminalEngine::Line& line = engine->getLine(itemindex);
+	
+	if (!mFont || !mFont->GetResource())
+		return;
+
+	gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
+	// later: handle attributes here
+
+	// render text
+	const char* text = line.text.c_str();
+	gr_textEx_scaleW(mRenderX, yPos, text, mFont->GetResource(), mRenderW, TOP_LEFT, 0);
+
+	if (itemindex == (size_t) engine->getCursorY()) {
+		// render cursor
+		int cursorX = engine->getCursorX();
+		std::string leftOfCursor = line.substr(0, cursorX);
+		int x = twrpTruetype::gr_ttf_measureEx(leftOfCursor.c_str(), mFont->GetResource());
+		// note that this single character can be a UTF-8 sequence
+		std::string atCursor = (size_t)cursorX < line.length() ? line.substr(cursorX, 1) : " ";
+		int w = twrpTruetype::gr_ttf_measureEx(atCursor.c_str(), mFont->GetResource());
+		gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
+		gr_fill(mRenderX + x, yPos, w, actualItemHeight);
+		gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, mBackgroundColor.alpha);
+		gr_textEx_scaleW(mRenderX + x, yPos, atCursor.c_str(), mFont->GetResource(), mRenderW, TOP_LEFT, 0);
+	}
+}
+
+void GUITerminal::NotifySelect(size_t item_selected __unused)
+{
+	// do nothing - terminal ignores selections
+}
+
+bool GUITerminal::status()
+{
+	return engine->status();
+}
+
+void GUITerminal::stop()
+{
+	engine->stop();
+	engine->clear();
+}
+
+void GUITerminal::InitAndResize()
+{
+	// make sure the shell is started
+	engine->initPty();
+	// send window resize
+	if (mFont && mFont->GetResource()) {
+		int charWidth = twrpTruetype::gr_ttf_measureEx("N", mFont->GetResource());
+		engine->setSize(mRenderW / charWidth, GetDisplayItemCount(), mRenderW, mRenderH);
+	}
+}
+
+void GUITerminal::SetPageFocus(int inFocus)
+{
+	if (inFocus && isConditionTrue()) {
+		// TODO: grab focus correctly, this hack grabs focus and insists that the terminal be the focus regardless of other elements
+		// It's highly unlikely that there will be any other visible input elements on the page anyway...
+		SetInputFocus(1);
+		InitAndResize();
+	}
+}
diff --git a/gui/text.cpp b/gui/text.cpp
new file mode 100755
index 0000000..d081e0d
--- /dev/null
+++ b/gui/text.cpp
@@ -0,0 +1,187 @@
+/*
+        Copyright 2012 to 2020 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// text.cpp - GUIText object
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+#include "minuitwrp/truetype.hpp"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUIText::GUIText(xml_node<>* node)
+	: GUIObject(node)
+{
+	mFont = NULL;
+	mIsStatic = 1;
+	mVarChanged = 0;
+	mFontHeight = 0;
+	maxWidth = 0;
+	scaleWidth = true;
+	isHighlighted = false;
+	mText = "";
+
+	if (!node)
+		return;
+
+	// Load colors
+	mColor = LoadAttrColor(node, "color", COLOR(0,0,0,255));
+	mHighlightColor = LoadAttrColor(node, "highlightcolor", mColor);
+
+	// Load the font, and possibly override the color
+	mFont = LoadAttrFont(FindNode(node, "font"), "resource");
+	if (!mFont || !mFont->GetResource())
+		return;
+	mColor = LoadAttrColor(FindNode(node, "font"), "color", mColor);
+	mHighlightColor = LoadAttrColor(FindNode(node, "font"), "highlightcolor", mColor);
+
+	// Load the placement
+	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH, &mPlacement);
+
+	xml_node<>* child = FindNode(node, "text");
+	if (child)  mText = child->value();
+
+	child = FindNode(node, "noscaling");
+	if (child) {
+		scaleWidth = false;
+	} else {
+		if (mPlacement == TOP_LEFT || mPlacement == BOTTOM_LEFT) {
+			maxWidth = gr_fb_width() - mRenderX;
+		} else if (mPlacement == TOP_RIGHT || mPlacement == BOTTOM_RIGHT) {
+			maxWidth = mRenderX;
+		} else if (mPlacement == CENTER || mPlacement == CENTER_X_ONLY) {
+			if (mRenderX < gr_fb_width() / 2) {
+				maxWidth = mRenderX * 2;
+			} else {
+				maxWidth = (gr_fb_width() - mRenderX) * 2;
+			}
+		}
+	}
+
+	// Simple way to check for static state
+	mLastValue = gui_parse_text(mText);
+	if (mLastValue != mText)   mIsStatic = 0;
+
+	mFontHeight = mFont->GetHeight();
+}
+
+int GUIText::Render(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	void* fontResource = NULL;
+	if (mFont)
+		fontResource = mFont->GetResource();
+	else
+		return -1;
+
+	mLastValue = gui_parse_text(mText);
+
+	mVarChanged = 0;
+
+	if (isHighlighted)
+		gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha);
+	else
+		gr_color(mColor.red, mColor.green, mColor.blue, mColor.alpha);
+
+	gr_textEx_scaleW(mRenderX, mRenderY, mLastValue.c_str(), fontResource, maxWidth, mPlacement, scaleWidth);
+
+	return 0;
+}
+
+int GUIText::Update(void)
+{
+	if (!isConditionTrue())
+		return 0;
+
+	static int updateCounter = 3;
+
+	// This hack just makes sure we update at least once a minute for things like clock and battery
+	if (updateCounter)  updateCounter--;
+	else
+	{
+		mVarChanged = 1;
+		updateCounter = 3;
+	}
+
+	if (mIsStatic || !mVarChanged)
+		return 0;
+
+	std::string newValue = gui_parse_text(mText);
+	if (mLastValue == newValue)
+		return 0;
+	else
+		mLastValue = newValue;
+	return 2;
+}
+
+int GUIText::GetCurrentBounds(int& w, int& h)
+{
+	void* fontResource = NULL;
+
+	if (mFont)
+		fontResource = mFont->GetResource();
+
+	h = mFontHeight;
+	mLastValue = gui_parse_text(mText);
+	w = twrpTruetype::gr_ttf_measureEx(mLastValue.c_str(), fontResource);
+	return 0;
+}
+
+int GUIText::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	GUIObject::NotifyVarChange(varName, value);
+
+	mVarChanged = 1;
+	return 0;
+}
+
+int GUIText::SetMaxWidth(unsigned width)
+{
+	maxWidth = width;
+	if (!maxWidth)
+		scaleWidth = false;
+	mVarChanged = 1;
+	return 0;
+}
+
+void GUIText::SetText(string newtext)
+{
+	mText = newtext;
+}
diff --git a/gui/textbox.cpp b/gui/textbox.cpp
new file mode 100644
index 0000000..c0620ee
--- /dev/null
+++ b/gui/textbox.cpp
@@ -0,0 +1,123 @@
+/*
+        Copyright 2015 bigbiff/Dees_Troy/_that TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// textbox.cpp - GUITextBox object
+
+#include <string>
+
+extern "C" {
+#include "../twcommon.h"
+}
+#include "minuitwrp/minui.h"
+
+#include "rapidxml.hpp"
+#include "objects.hpp"
+
+GUITextBox::GUITextBox(xml_node<>* node) : GUIScrollList(node)
+{
+	xml_node<>* child;
+
+	mLastCount = 0;
+	mIsStatic = true;
+
+	allowSelection = false;	// textbox doesn't support list item selections
+
+	child = FindNode(node, "color");
+	if (child)
+	{
+		mFontColor = LoadAttrColor(child, "foreground", mFontColor);
+		mBackgroundColor = LoadAttrColor(child, "background", mBackgroundColor);
+		//mScrollColor = LoadAttrColor(child, "scroll", mScrollColor);
+	}
+	child = FindNode(node, "text");
+	while (child) {
+		string txt = child->value();
+		mText.push_back(txt);
+		string lookup = gui_parse_text(txt);
+		if (lookup != txt)
+			mIsStatic = false;
+		mLastValue.push_back(lookup);
+		child = child->next_sibling("text");
+	}
+}
+
+int GUITextBox::Update(void)
+{
+	if (AddLines(&mLastValue, NULL, &mLastCount, &rText, NULL)) {
+		// someone added new text
+		// at least the scrollbar must be updated, even if the new lines are currently not visible
+		mUpdate = 1;
+	}
+
+	GUIScrollList::Update();
+
+	if (mUpdate) {
+		mUpdate = 0;
+		if (Render() == 0)
+			return 2;
+	}
+	return 0;
+}
+
+size_t GUITextBox::GetItemCount()
+{
+	return rText.size();
+}
+
+void GUITextBox::RenderItem(size_t itemindex, int yPos, bool selected __unused)
+{
+	if (!mFont || !mFont->GetResource())
+		return;
+
+	// Set the color for the font
+	gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
+
+	// render text
+	const char* text = rText[itemindex].c_str();
+	gr_textEx_scaleW(mRenderX, yPos, text, mFont->GetResource(), mRenderW, TOP_LEFT, 0);
+}
+
+void GUITextBox::NotifySelect(size_t item_selected __unused)
+{
+	// do nothing - textbox ignores selections
+}
+
+int GUITextBox::NotifyVarChange(const std::string& varName, const std::string& value)
+{
+	GUIScrollList::NotifyVarChange(varName, value);
+
+	if (!isConditionTrue() || mIsStatic)
+		return 0;
+
+	// Check to see if the variable exists in mText
+	for (size_t i = 0; i < mText.size(); i++) {
+		string lookup = gui_parse_text(mText.at(i));
+		if (lookup != mText.at(i)) {
+			mLastValue.at(i) = lookup;
+			mUpdate = 1;
+			// There are ways to improve efficiency here, but I am not
+			// sure if we will even use this feature in the stock theme
+			// at all except for language translation. If we start using
+			// variables in textboxes in the stock theme, we can circle
+			// back and make improvements here.
+			mLastCount = 0;
+			rText.clear();
+		}
+	}
+	return 0;
+}
diff --git a/gui/theme/common/fonts/DroidSansMono.ttf b/gui/theme/common/fonts/DroidSansMono.ttf
new file mode 100644
index 0000000..4085cee
--- /dev/null
+++ b/gui/theme/common/fonts/DroidSansMono.ttf
Binary files differ
diff --git a/gui/theme/common/fonts/RobotoCondensed-Regular.ttf b/gui/theme/common/fonts/RobotoCondensed-Regular.ttf
new file mode 100644
index 0000000..b9fc49c
--- /dev/null
+++ b/gui/theme/common/fonts/RobotoCondensed-Regular.ttf
Binary files differ
diff --git a/gui/theme/common/images/progress_empty.png b/gui/theme/common/images/progress_empty.png
new file mode 100644
index 0000000..1a7471e
--- /dev/null
+++ b/gui/theme/common/images/progress_empty.png
Binary files differ
diff --git a/gui/theme/common/images/progress_fill.png b/gui/theme/common/images/progress_fill.png
new file mode 100644
index 0000000..7f4121f
--- /dev/null
+++ b/gui/theme/common/images/progress_fill.png
Binary files differ
diff --git a/gui/theme/common/landscape.xml b/gui/theme/common/landscape.xml
new file mode 100755
index 0000000..9fb8570
--- /dev/null
+++ b/gui/theme/common/landscape.xml
@@ -0,0 +1,6180 @@
+<?xml version="1.0"?>
+<recovery>
+	<styles>
+		<style name="text_l">
+			<font resource="font_l" color="%text_color%"/>
+		</style>
+
+		<style name="text_m">
+			<font resource="font_m" color="%text_color%"/>
+		</style>
+
+		<style name="text_m_accent">
+			<font resource="font_m" color="%accent_color%"/>
+		</style>
+
+		<style name="text_m_fail">
+			<font resource="font_m" color="%text_fail_color%"/>
+		</style>
+
+		<style name="text_s">
+			<font resource="font_s" color="%text_color%"/>
+		</style>
+
+		<style name="input">
+			<background color="%background_color%"/>
+			<cursor color="%accent_color%" hasfocus="1" width="%input_line_width%"/>
+			<font resource="font_m" color="%text_color%"/>
+		</style>
+
+		<style name="checkbox">
+			<font resource="font_m" color="%text_color%"/>
+			<image checked="checkbox_true" unchecked="checkbox_false"/>
+		</style>
+
+		<style name="radiobutton">
+			<font resource="font_m" color="%text_color%"/>
+			<image checked="radio_true" unchecked="radio_false"/>
+		</style>
+
+		<style name="main_button">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_l" color="%text_button_color%"/>
+			<image resource="main_button"/>
+		</style>
+
+		<style name="main_button_m">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="main_button"/>
+		</style>
+
+		<style name="main_button_half_width">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="main_button_half_width"/>
+		</style>
+
+		<style name="main_button_half_width_low">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="main_button_half_width_low"/>
+		</style>
+
+		<style name="button_third_width">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="tab_3"/>
+		</style>
+
+		<style name="button_quarter_width">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="tab_4"/>
+		</style>
+
+		<style name="sort_asc">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="sort_asc"/>
+		</style>
+
+		<style name="sort_desc">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="sort_desc"/>
+		</style>
+
+		<style name="sort_empty">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="sort_empty"/>
+		</style>
+
+		<style name="fab">
+			<highlight color="%highlight_color%"/>
+			<placement x="%indent_right%" y="%row21a_y%" placement="1"/>
+			<font resource="font_m" color="%button_text_color%"/>
+		</style>
+
+		<style name="tab">
+			<highlight color="%highlight_color%"/>
+			<fill color="%accent_color%"/>
+			<font resource="font_s" color="%text_button_color%"/>
+		</style>
+
+		<style name="console">
+			<fastscroll rectcolor="%accent_color%" w="%fastscroll_w%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<color foreground="%text_color%" background="%background_color%" scroll="%background_color%"/>
+			<font resource="fixed"/>
+		</style>
+
+		<style name="terminal">
+			<fastscroll linecolor="%transparent%" rectcolor="%accent_color%" w="%fastscroll_w%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<background color="%background_color%"/>
+			<font resource="fixed" spacing="3" color="%text_color%"/>
+		</style>
+
+		<style name="fileselector">
+			<highlight color="%fileselector_highlight_color%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<sort name="tw_gui_sort_order"/>
+			<icon folder="folder" file="file"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="listbox">
+			<highlight color="%fileselector_highlight_color%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<icon selected="radio_true" unselected="radio_false"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="scrolllist">
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+		</style>
+
+		<style name="partitionlist">
+			<highlight color="%fileselector_highlight_color%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<icon selected="checkbox_true" unselected="checkbox_false"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%partitionlist_spacing%" color="%text_color%" highlightcolor="%fileselector_highlight_font_color%"/>
+		</style>
+
+		<style name="partitionlist_storage">
+			<highlight color="%fileselector_highlight_color%"/>
+			<placement x="%dialog_content_x%" y="%row4_y%" w="%content_overlay_width%" h="%partitionlist_storage_height%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<icon selected="radio_true" unselected="radio_false"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%partitionlist_spacing%" color="%text_color%" highlightcolor="%fileselector_highlight_font_color%"/>
+		</style>
+
+		<style name="advanced_listbox">
+			<highlight color="%fileselector_highlight_color%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<icon selected="handle" unselected="handle"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="options_listbox">
+			<highlight color="%fileselector_highlight_color%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%background_color%" height="%fileselector_separatorheight%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="slider">
+			<placement x="%col2_x_right%" y="%row17a_y%"/>
+			<font resource="font_m" color="%text_color%"/>
+			<resource base="slider" used="slider_used" touch="slider_touch"/>
+		</style>
+
+		<style name="slider_centered">
+			<placement x="%center_x%" y="%row17a_y%"/>
+			<font resource="font_m" color="%text_color%"/>
+			<resource base="slider" used="slider_used" touch="slider_touch"/>
+		</style>
+
+		<style name="slidervalue">
+			<resource handle="handle"/>
+			<font resource="font_m" color="%text_color%"/>
+			<colors line="%fileselector_linecolor%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+		</style>
+
+		<style name="patternpassword">
+			<size name="tw_gui_pattern_grid_size" default="3"/>
+		</style>
+	</styles>
+
+	<pages>
+		<page name="main">
+			<action>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="main2">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@twrp_header=Team Win Recovery Project}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_version%</text>
+			</text>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@install_btn=Install}</text>
+				<actions>
+					<action function="queueclear"/>
+					<action function="set">tw_selectimage=0</action>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<placement x="%center_x%" y="%row2_y%"/>
+				<text>{@wipe_btn=Wipe}</text>
+				<action function="page">wipe</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row6a_y%"/>
+				<text>{@backup_btn=Backup}</text>
+				<action function="page">backup</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%center_x%" y="%row6a_y%"/>
+				<text>{@restore_btn=Restore}</text>
+				<action function="page">restore</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@mount_btn=Mount}</text>
+				<action function="page">mount</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%center_x%" y="%row11_y%"/>
+				<text>{@settings_btn=Settings}</text>
+				<action function="page">settings</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@advanced_btn=Advanced}</text>
+				<action function="page">advanced</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>{@reboot_btn=Reboot}</text>
+				<action function="page">reboot</action>
+			</button>
+		</page>
+
+		<page name="install">
+			<template name="page"/>
+
+			<text style="text_l">
+				<condition var1="tw_selectimage" var2="0"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip}</text>
+			</text>
+
+			<text style="text_l">
+				<condition var1="tw_selectimage" var2="1"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_image_hdr=Install Image}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@select_file_from_storage=Select File from %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%col1_x_left%" y="%row1a_y%" w="%fileselector_install_folder_width%" h="%fileselector_install_height%"/>
+				<text>{@file_selector_folders_hdr=Folders}</text>
+				<filter folders="1" files="0"/>
+				<path name="tw_zip_location" default="/sdcard"/>
+				<data name="select"/>
+			</fileselector>
+
+			<fileselector>
+				<condition var1="tw_selectimage" var2="0"/>
+				<placement x="%col2_x_left%" y="%row1a_y%" w="%fileselector_install_width%" h="%fileselector_install_height%"/>
+				<text>%tw_zip_location%</text>
+				<filter extn=".zip;.ozip;.ZIP;.OZIP" folders="0" files="1"/>
+				<prfxfilter prfx="Magisk-;Magisk.apk;app-release.apk;app-debug.apk" folders="1" files="1"/>
+				<path name="tw_zip_location" default="/sdcard"/>
+				<data name="tw_filename"/>
+				<selection name="tw_file"/>
+			</fileselector>
+
+			<fileselector>
+				<condition var1="tw_selectimage" var2="1"/>
+				<placement x="%col2_x_left%" y="%row1a_y%" w="%fileselector_install_width%" h="%fileselector_install_height%"/>
+				<text>%tw_zip_location%</text>
+				<filter extn=".img" folders="0" files="1"/>
+				<path name="tw_zip_location" default="/sdcard"/>
+				<data name="tw_filename"/>
+				<selection name="tw_file"/>
+			</fileselector>
+
+			<button style="main_button_half_width_low">
+				<condition var1="tw_selectimage" var2="0"/>
+				<placement x="%col_button_right%" y="%row13a_y%"/>
+				<text>{@install_image_btn=Install Image}</text>
+				<actions>
+					<action function="set">tw_selectimage=1</action>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width_low">
+				<condition var1="tw_selectimage" var2="1"/>
+				<placement x="%col_button_right%" y="%row13a_y%"/>
+				<text>{@install_zip_btn=Install Zip}</text>
+				<actions>
+					<action function="set">tw_selectimage=0</action>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row16a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=install</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<action>
+				<conditions>
+					<condition var1="tw_selectimage" var2="0"/>
+					<condition var1="tw_filename" op="modified"/>
+				</conditions>
+				<actions>
+					<action function="queuezip"/>
+					<action function="page">flash_confirm</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_selectimage" var2="1"/>
+					<condition var1="tw_filename" op="modified"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_is_slot_part=0</action>
+					<action function="page">flashimage_confirm</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="flash_confirm">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@zip_queue_count=%tw_zip_queue_count% of max of 10 Files queued}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@zip_warn1=This operation may install incompatible}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@zip_warn2=software and render your device unusable.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@zip_back_cancel=Press back to cancel adding this zip.}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row8_y%"/>
+				<text>{@folder=Folder:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row9_y%"/>
+				<text>%tw_zip_location%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@file=File:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row12_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row8_y%"/>
+				<text>{@options=Options:}</text>
+			</text>
+
+			<listbox style="options_listbox">
+				<placement x="%col1_x_right%" y="%row9_y%" w="%content_half_width%" h="%listbox_options_height%"/>
+				<icon selected="checkbox_true" unselected="checkbox_false"/>
+				<listitem name="{@zip_sig_chk=Zip signature verification}">
+					<data variable="tw_signed_zip_verify"/>
+				</listitem>
+				<listitem name="{@skip_digest_zip_chk=Skip Digest check before installing zip}">
+					<data variable="tw_skip_digest_check_zip"/>
+				</listitem>
+				<listitem name="{@auto_reflashtwrp_chk=Automatically Reflash TWRP after flashing a ROM}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<data variable="tw_auto_reflashtwrp"/>
+				</listitem>
+				<listitem name="{@install_reboot_chk=Reboot after installation is complete}">
+					<data variable="tw_install_reboot"/>
+				</listitem>
+				<listitem name="{@inject_twrp_chk=Inject TWRP after install}">
+					<condition var1="tw_has_injecttwrp" var2="1"/>
+					<data variable="tw_inject_after_zip"/>
+				</listitem>
+			</listbox>
+
+			<button style="main_button_half_width">
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@zip_add_btn=Add more Zips}</text>
+				<action function="page">install</action>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@zip_clear_btn=Clear Zip Queue}</text>
+				<actions>
+					<action function="queueclear"/>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_flash=Swipe to confirm Flash}</text>
+				<action function="flash">flash_zip</action>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="cancelzip"/>
+					<action function="page">install</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="flash_zip">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_zip_count_hdr=Install Zip %tw_zip_index% of %tw_zip_queue_count%}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<action function="page">flash_done</action>
+			</action>
+		</page>
+
+		<page name="flash_done">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_operation_status" op="!=" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@failed=Failed}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_operation_status" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@successful=Successful}</text>
+			</text>
+
+			<template name="console"/>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_ab_device" var2="0"/>
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@wipe_cache_dalvik_btn=Wipe Cache/Dalvik}</text>
+				<actions>
+					<action function="set">tw_back=flash_done</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=/cache</action>
+					<action function="set">tw_has_action2=1</action>
+					<action function="set">tw_action2=wipe</action>
+					<action function="set">tw_action2_param=dalvik</action>
+					<action function="set">tw_text1={@wipe_cache_dalvik_confirm=Wipe Cache &amp; Dalvik?}</action>
+					<action function="set">tw_action_text1={@wiping_cache_dalvik=Wiping Cache &amp; Dalvik...}</action>
+					<action function="set">tw_complete_text1={@wipe_cache_dalvik_complete=Cache &amp; Dalvik Wipe Complete}</action>
+					<action function="set">tw_slider_text={@swipe_wipe=Swipe to Wipe}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_ab_device" var2="1"/>
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@wipe_dalvik_btn=Wipe Dalvik}</text>
+				<actions>
+					<action function="set">tw_back=flash_done</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=dalvik</action>
+					<action function="set">tw_text1={@wipe_dalvik_confirm=Wipe Dalvik?}</action>
+					<action function="set">tw_action_text1={@wiping_cache_dalvik=Wiping Dalvik...}</action>
+					<action function="set">tw_complete_text1={@wipe_dalvik_complete=Dalvik Wipe Complete}</action>
+					<action function="set">tw_slider_text={@swipe_wipe=Swipe to Wipe}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_ab_device" var2="0"/>
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>{@reboot_system_btn=Reboot System}</text>
+				<actions>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot_system_routine</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_ab_device" var2="1"/>
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>{@reboot_btn=Reboot}</text>
+				<actions>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot</action>
+				</actions>
+			</button>
+
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=install</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_install_reboot" var2="1"/>
+					<condition var1="tw_operation_status" var2="0"/>
+					<condition var1="tw_reboot_system" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_sleep=%tw_sleep_total%</action>
+					<action function="page">flash_sleep_and_reboot</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="flash_sleep_and_reboot">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@install_reboot=Rebooting in %tw_sleep% second(s)}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_sleep" op="&gt;" var2="0"/>
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_install_reboot=0</action>
+					<action function="page">flash_done</action>
+				</actions>
+			</button>
+
+			<action>
+				<conditions>
+					<condition var1="tw_sleep" var2="tw_sleep_total"/>
+					<condition var1="tw_install_reboot" var2="1"/>
+				</conditions>
+				<action function="sleepcounter">%tw_sleep_total%</action>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_sleep" var2="0"/>
+					<condition var1="tw_install_reboot" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="sleep">50000</action>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot_system_routine</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="flashimage_confirm">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_image_hdr=Install Image}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@install_sel_target=Select Target Partition}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row8_y%"/>
+				<text>{@folder=Folder:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row9_y%"/>
+				<text>%tw_zip_location%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@file=File:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row12_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<partitionlist>
+				<placement x="%col1_x_right%" y="%row7a_y%" w="%content_half_width%" h="%partitionlist_flashimage_height%"/>
+				<icon selected="radio_true" unselected="radio_false"/>
+				<text>{@flash_image_select=Select Partition to Flash Image:}</text>
+				<data name="tw_flash_partition"/>
+				<listtype name="flashimg"/>
+			</partitionlist>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_is_slot_part" op="=" var2="1"/>
+					<condition var1="tw_flash_both_slots" op="=" var2="0"/>
+					<condition var1="tw_has_boot_slots" var2="1"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row14a_y%" textplacement="6"/>
+				<text>{@flash_ab_both_slots=Flash to both slots}</text>
+				<image resource="checkbox_false"/>
+				<action function="set">tw_flash_both_slots=1</action>
+			</button>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_is_slot_part" op="=" var2="1"/>
+					<condition var1="tw_flash_both_slots" op="=" var2="1"/>
+					<condition var1="tw_has_boot_slots" var2="1"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row14a_y%" textplacement="6"/>
+				<text>{@flash_ab_both_slots=Flash to both slots}</text>
+				<image resource="checkbox_true"/>
+				<action function="set">tw_flash_both_slots=0</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_flash=Swipe to confirm Flash}</text>
+				<actions>
+					<action function="set">tw_back=flashimage_confirm</action>
+					<action function="set">tw_action=flashimage</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_action_text1={@flashing_image=Flashing Image...}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@image_flashed=Image Flashed}</action>
+					<action function="page">action_page</action>
+				</actions>
+				<action function="flashimage"/>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=install</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="clear_vars">
+			<action>
+				<action function="set">tw_operation_state=0</action>
+				<action function="set">tw_text1=</action>
+				<action function="set">tw_text2=</action>
+				<action function="set">tw_text3=</action>
+				<action function="set">tw_text4=</action>
+				<action function="set">tw_action_text1=</action>
+				<action function="set">tw_action_text2=</action>
+				<action function="set">tw_action_param=</action>
+				<action function="set">tw_has_action2=0</action>
+				<action function="set">tw_action2=</action>
+				<action function="set">tw_action2_param=</action>
+				<action function="set">tw_has_cancel=0</action>
+				<action function="set">tw_cancel_action=</action>
+				<action function="set">tw_cancel_param=</action>
+				<action function="set">tw_show_exclamation=0</action>
+				<action function="set">tw_show_reboot=0</action>
+				<action function="set">tw_crypto_user_id=</action>
+				<action function="set">tw_multiuser_warning_accepted=</action>
+				<action function="set">tw_multiuser_warning_destination=</action>
+				<action function="page">%tw_clear_destination%</action>
+			</action>
+		</page>
+
+		<page name="reboot_system_routine">
+			<action>
+				<action function="set">tw_action=reboot</action>
+				<action function="set">tw_action_param=system</action>
+				<action function="set">tw_has_action2=0</action>
+				<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+				<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+				<action function="set">tw_text3=</action>
+				<action function="set">tw_text4=</action>
+				<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+				<action function="set">tw_action_text2=</action>
+				<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+				<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+				<action function="page">rebootcheck</action>
+			</action>
+		</page>
+
+		<page name="confirm_action">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@confirm_action=Confirm Action}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text/>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>%tw_text1%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>%tw_text2%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>%tw_text3%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>%tw_text4%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@back_cancel=Press back button to cancel.}</text>
+			</text>
+
+			<slider style="slider_centered">
+				<text>%tw_slider_text%</text>
+				<action function="page">action_page</action>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="action_page">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>%tw_action_text1%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_action_text2%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_has_cancel" var2="1"/>
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="%tw_cancel_action%">%tw_cancel_param%</action>
+			</button>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<actions>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="0"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="1"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+					<action function="%tw_action2%">%tw_action2_param%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="singleaction_page">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>%tw_action_text1%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_action_text2%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="0"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="1"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+					<action function="%tw_action2%">%tw_action2_param%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="action_complete">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>%tw_complete_text1%</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_operation_status" op="!=" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@failed=Failed}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_operation_status" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@successful=Successful}</text>
+			</text>
+
+			<template name="console"/>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@back_btn=Back}</text>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>{@reboot_system_btn=Reboot System}</text>
+				<actions>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot_system_routine</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="filecheck">
+			<action>
+				<action function="fileexists">%tw_filecheck%</action>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_fileexists=1</action>
+					<action function="page">%tw_existpage%</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_fileexists=0</action>
+					<action function="page">%tw_notexistpage%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="rebootcheck">
+			<action>
+				<condition var1="tw_backup_system_size" op="&gt;=" var2="%tw_min_system%"/>
+				<action function="reboot">%tw_reboot_param%</action>
+			</action>
+
+			<action>
+				<condition var1="tw_backup_system_size" op="&lt;" var2="%tw_min_system%"/>
+				<action function="page">confirm_action</action>
+			</action>
+		</page>
+
+		<page name="wipe">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@wipe_hdr=Wipe}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@factory_reset_hdr=Factory Reset}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@factory_reset1=Wipes Data, Cache, and Dalvik}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@factory_reset5=(not including users/lockscreen)}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_has_data_media" var2="1"/>
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@factory_reset2=(not including internal storage)}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<conditions>
+					<condition var1="tw_has_android_secure" var2="1"/>
+					<condition var1="fileexists" var2="/and-sec"/>
+				</conditions>
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@android_secure=Android Secure}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_has_sdext_partition" var2="1"/>
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@sdext=SD-EXT}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@factory_reset3=Most of the time this is}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row8_y%" placement="5"/>
+				<text>{@factory_reset4=the only wipe that you need.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row10_y%" placement="5"/>
+				<text>{@back_cancel=Press back button to cancel.}</text>
+			</text>
+
+			<button style="main_button_half_width">
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@advanced_wipe_btn=Advanced Wipe}</text>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="page">advancedwipe</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_has_data_media" var2="1"/>
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@format_data_btn=Format Data}</text>
+				<action function="page">formatdata</action>
+			</button>
+
+			<button style="main_button_half_width">
+				<conditions>
+					<condition var1="tw_is_encrypted" var2="1"/>
+					<condition var1="tw_has_data_media" var2="0"/>
+				</conditions>
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@wipe_enc_btn=Wipe Encryption}</text>
+				<actions>
+					<action function="set">tw_back=wipe</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=DATAMEDIA</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@wipe_enc_confirm=Wipe Encryption from Data?}</action>
+					<action function="set">tw_text2=</action>
+					<action function="set">tw_action_text1={@formatting_data=Formatting Data...}</action>
+					<action function="set">tw_complete_text1={@format_data_complete=Data Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_format_data=Swipe to Format Data}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_factory_reset=Swipe to Factory Reset}</text>
+				<actions>
+					<action function="set">tw_back=wipe</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=data</action>
+					<action function="set">tw_action_text1={@factory_resetting=Factory Reset...}</action>
+					<action function="set">tw_complete_text1={@factory_reset_complete=Factory Reset Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="advancedwipe">
+			<template name="page"/>
+
+			<action>
+				<action function="set">tw_wipe_list=</action>
+			</action>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@wipe_hdr=Wipe}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@advanced_wipe_hdr=Advanced Wipe}</text>
+			</text>
+
+			<partitionlist>
+				<placement x="%col1_x_left%" y="%row1a_y%" w="%content_width%" h="%partitionlist_wipe_height%"/>
+				<text>{@sel_part_wipe=Select Partitions to Wipe:}</text>
+				<data name="tw_wipe_list"/>
+				<listtype name="wipe"/>
+			</partitionlist>
+
+			<text style="text_m_fail">
+				<condition var1="partitionlisterror" var2="1"/>
+				<placement x="%col2_x_left%" y="%row14a_y%" placement="5"/>
+				<text>{@invalid_part_sel=Invalid partition selection}</text>
+			</text>
+
+			<button style="main_button_m">
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@repair_change_btn=Repair or Change File System}</text>
+				<actions>
+					<action function="checkpartitionlist">tw_wipe_list</action>
+					<action function="page">checkpartitionlist</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_wipe=Swipe to Wipe}</text>
+				<actions>
+					<action function="set">tw_back=advancedwipe</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=LIST</action>
+					<action function="set">tw_text1={@wipe_sel_confirm=Wipe Selected Partition(s)?}</action>
+					<action function="set">tw_action_text1={@wiping_part=Wiping Partition(s)...}</action>
+					<action function="set">tw_complete_text1={@wipe_complete=Wipe Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">wipe</action>
+			</action>
+		</page>
+
+		<page name="formatdata">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@wipe_hdr=Wipe}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@format_data_hdr=Format Data}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@format_data_lcp1=Format Data will wipe all of your apps, backups, pictures, videos, media, and}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@format_data_lcp2=removes encryption on internal storage.}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_has_adopted_storage" var2="1"/>
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@format_data_adopted=Including Adopted Storage}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@format_data_undo=This cannot be undone.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@yes_continue=Type yes to continue.  Press back to cancel.}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row8_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_confirm_formatdata%</text>
+				<data name="tw_confirm_formatdata"/>
+				<restrict minlen="3" maxlen="3" allow="yes"/>
+				<action function="page">formatdata_confirm</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row9_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">wipe</action>
+			</action>
+		</page>
+
+		<page name="formatdata_confirm">
+			<action>
+				<condition var1="tw_confirm_formatdata" op="=" var2="yes"/>
+				<actions>
+					<action function="set">tw_back=formatdata</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=DATAMEDIA</action>
+					<action function="set">tw_action_text1={@formatting_data=Formatting Data...}</action>
+					<action function="set">tw_complete_text1={@format_data_complete=Data Format Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_confirm_formatdata" op="!=" var2="yes"/>
+				<action function="page">formatdata</action>
+			</action>
+		</page>
+
+		<page name="checkpartitionlist">
+			<action>
+				<condition var1="tw_check_partition_list" op="=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="getpartitiondetails">tw_wipe_list</action>
+					<action function="page">partitionoptions</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_check_partition_list" op="!=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=1</action>
+					<action function="page">advancedwipe</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="partitionoptions">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@wipe_hdr=Wipe}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@part_opt_hdr=Partition Options for: %tw_partition_name%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@part_mount_point=Mount Point: %tw_partition_mount_point%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@part_curr_fs=File system: %tw_partition_file_system%}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_is_present" var2="1"/>
+				<placement x="%col2_x_left%" y="%row5_y%"/>
+				<text>{@part_present_yes=Present: Yes}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_is_present" var2="0"/>
+				<placement x="%col2_x_left%" y="%row5_y%"/>
+				<text>{@part_present_no=Present: No}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_removable" var2="1"/>
+				<placement x="%col1_x_right%" y="%row5_y%"/>
+				<text>{@part_removable_yes=Removable: Yes}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_removable" var2="0"/>
+				<placement x="%col1_x_right%" y="%row5_y%"/>
+				<text>{@part_removable_no=Removable: No}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col2_x_left%" y="%row7_y%"/>
+				<text>{@part_size=Size: %tw_partition_size%MB}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_right%" y="%row7_y%"/>
+				<text>{@part_used=Used: %tw_partition_used%MB}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col2_x_left%" y="%row9_y%"/>
+				<text>{@part_free=Free: %tw_partition_free%MB}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_right%" y="%row9_y%"/>
+				<text>{@part_backup_size=Backup Size: %tw_partition_backup_size%MB}</text>
+			</text>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_partition_can_resize" op="=" var2="1"/>
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@resize_btn=Resize File System}</text>
+				<actions>
+					<action function="set">tw_back=partitionoptions</action>
+					<action function="set">tw_action=resize</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_has_action2=1</action>
+					<action function="set">tw_action2=getpartitiondetails</action>
+					<action function="set">tw_action2_param=tw_wipe_list</action>
+					<action function="set">tw_text1={@resize_confirm=Resize %tw_partition_name%?}</action>
+					<action function="set">tw_text2=</action>
+					<action function="set">tw_action_text1={@resizing=Resizing...}</action>
+					<action function="set">tw_complete_text1={@resize_complete=Resize Complete}</action>
+					<action function="set">tw_slider_text={@swipe_resize=Swipe to Resize}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_partition_can_repair" op="=" var2="1"/>
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@repair_btn=Repair File System}</text>
+				<actions>
+					<action function="set">tw_back=partitionoptions</action>
+					<action function="set">tw_action=repair</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@repair_confirm=Repair %tw_partition_name%?}</action>
+					<action function="set">tw_text2=</action>
+					<action function="set">tw_action_text1={@repairing=Repairing...}</action>
+					<action function="set">tw_complete_text1={@repair_complete=Repair Complete}</action>
+					<action function="set">tw_slider_text={@swipe_repair=Swipe to Repair}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>{@change_fs_btn=Change File System}</text>
+				<action function="page">selectfilesystem</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advancedwipe</action>
+			</action>
+		</page>
+
+		<page name="refreshfilesystem">
+			<action>
+				<condition var1="tw_check_partition_list" op="=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="getpartitiondetails">tw_wipe_list</action>
+					<action function="page">selectfilesystem</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_check_partition_list" op="!=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=1</action>
+					<action function="set">tw_wipe_list=</action>
+					<action function="page">advancedwipe</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="selectfilesystem">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@wipe_hdr=Wipe}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@change_fs_for_hdr=Change File System for: %tw_partition_name%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@part_mount_point=Mount Point: %tw_partition_mount_point%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@part_curr_fs=File system: %tw_partition_file_system%}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@change_fs_warn1=Some ROMs or kernels may not support some}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@change_fs_warn2=file systems. Proceed with caution!}</text>
+			</text>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_partition_ext" op="=" var2="1"/>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>EXT2</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=ext2</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=EXT2</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_partition_ext" op="=" var2="1"/>
+				<placement x="%col2_x_left%" y="%row11_y%"/>
+				<text>EXT3</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=ext3</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=EXT3</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_partition_ext" op="=" var2="1"/>
+				<placement x="%center_x%" y="%row11_y%"/>
+				<text>EXT4</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=ext4</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=EXT4</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_partition_vfat" op="=" var2="1"/>
+				<placement x="%col2_x_right%" y="%row11_y%"/>
+				<text>FAT</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=vfat</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=FAT</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_partition_exfat" op="=" var2="1"/>
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>exFAT</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=exfat</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=exFAT</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_partition_f2fs" op="=" var2="1"/>
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>F2FS</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=f2fs</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=F2FS</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">partitionoptions</action>
+			</action>
+		</page>
+
+		<page name="backup">
+			<template name="page"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_is_fbe" var2="1"/>
+					<condition var1="tw_all_users_decrypted" var2="0"/>
+					<condition var1="tw_multiuser_warning_accepted" op="!=" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_multiuser_warning_destination=backup</action>
+					<action function="page">multiuser_warning</action>
+				</actions>
+			</action>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@backup_hdr=Backup}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row3_y%"/>
+				<text>%tw_backup_name%</text>
+			</text>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_left%" y="row4_y" w="%content_half_width%" h="input_line_width"/>
+			</fill>
+
+			<button>
+				<placement x="col1_x_left" y="%row2_y%" w="%content_half_width%" h="%navbar_height%"/>
+				<fill color="%transparent%"/>
+				<actions>
+					<action function="set">tw_fileexists=0</action>
+					<action function="page">backupname1</action>
+				</actions>
+			</button>
+
+			<partitionlist>
+				<placement x="%col1_x_left%" y="%row4a_y%" w="%content_half_width%" h="%partitionlist_backup_height%"/>
+				<text>{@sel_part_backup=Select Partitions to Backup:}</text>
+				<data name="tw_backup_list"/>
+				<listtype name="backup"/>
+			</partitionlist>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row2_y%"/>
+				<text>{@encryption=Encryption:}</text>
+			</text>
+
+			<text style="text_m">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="0"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%col1_x_right%" y="%row3_y%"/>
+				<text>{@enc_disabled=disabled - set a password to enable}</text>
+			</text>
+
+			<text style="text_m">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="1"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%col1_x_right%" y="%row3_y%"/>
+				<text>{@enc_enabled=enabled}</text>
+			</text>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_right%" y="row4_y" w="%content_half_width%" h="input_line_width"/>
+			</fill>
+
+			<button>
+				<placement x="col1_x_right" y="%row2_y%" w="%content_half_width%" h="%navbar_height%"/>
+				<fill color="%transparent%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row5_y%"/>
+				<text>{@options=Options:}</text>
+			</text>
+
+			<checkbox>
+				<placement x="%col1_x_right%" y="%row6_y%"/>
+				<text>{@enable_backup_comp_chk=Enable compression}</text>
+				<data variable="tw_use_compression"/>
+			</checkbox>
+
+			<checkbox>
+				<placement x="%col1_x_right%" y="%row7a_y%"/>
+				<text>{@skip_digest_backup_chk=Skip Digest generation during backup}</text>
+				<data variable="tw_skip_digest_generate"/>
+			</checkbox>
+
+			<checkbox>
+				<placement x="%col1_x_right%" y="%row9_y%"/>
+				<text>{@disable_backup_space_chk=Disable free space check before backup}</text>
+				<data variable="tw_disable_free_space"/>
+			</checkbox>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=backup</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@refresh_sizes_btn=Refresh Sizes}</text>
+				<actions>
+					<action function="refreshsizes"/>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_backup=Swipe to Backup}</text>
+				<action function="page">backup_run</action>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="multiuser_warning">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@multiuser_warning_hdr=Multiuser Warning}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@multiuser_warning1=Not all users decrypted!}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row8_y%" placement="5"/>
+				<text>{@multiuser_warning2=Backup/restore operations may fail!}</text>
+			</text>
+
+			<button style="main_button_m">
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@decrypt_users=Decrypt Users}</text>
+				<action function="page">decrypt_users</action>
+			</button>
+
+			<slider>
+				<text>{@multiuser_warning_accept=Continue Anyway}</text>
+				<actions>
+					<action function="set">tw_multiuser_warning_accepted=1</action>
+					<action function="page">%tw_multiuser_warning_destination%</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="backupname1">
+			<action>
+				<condition var1="tw_backup_name" op="=" var2="{@auto_generate=(Auto Generate)}"/>
+				<action function="generatebackupname"/>
+			</action>
+
+			<action>
+				<action function="page">backupname2</action>
+			</action>
+		</page>
+
+		<page name="backupname2">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@backup_hdr=Backup}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_backup_name%</text>
+				<data name="tw_backup_name"/>
+				<restrict minlen="1" maxlen="64" allow=" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_.{}[]"/>
+				<actions>
+					<action function="set">tw_filecheck=%tw_backups_folder%/%tw_backup_name%</action>
+					<action function="set">tw_existpage=backupname2</action>
+					<action function="set">tw_notexistpage=backup</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row4_y" w="%content_half_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="row4_y" w="%content_half_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@backup_name_exists=A backup with that name already exists!}</text>
+			</text>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_backup_name={@auto_generate=(Auto Generate)}</action>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width_low">
+				<placement x="%date_button_x%" y="%row5_y%"/>
+				<text>{@append_date_btn=Append Date}</text>
+				<action function="appenddatetobackupname"/>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_backup_name={@auto_generate=(Auto Generate)}</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_backup_name={@auto_generate=(Auto Generate)}</action>
+					<action function="page">backup</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="backupencryption">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@backup_hdr=Backup}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row2_y%"/>
+				<text>{@enter_pass=Enter Password:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_right%" y="%row2_input_y%" w="%content_half_width%" h="%input_height%"/>
+				<text>%tw_backup_encrypt_display%</text>
+				<data name="tw_backup_password" mask="*" maskvariable="tw_backup_encrypt_display"/>
+				<restrict minlen="1" maxlen="32" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"/>
+				<action function="page">backupencryption2</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_right%" y="row4_y" w="%content_half_width%" h="input_line_width"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_not_match" var2="1"/>
+				<placement x="%col1_x_right%" y="row4_y" w="%content_half_width%" h="input_line_width"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_not_match" var2="1"/>
+				<placement x="%col1_x_right%" y="%row4_y%"/>
+				<text>{@pass_not_match=Passwords do not match!}</text>
+			</text>
+
+			<button style="main_button_half_width_low">
+				<placement x="%center_x%" y="%row5_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_encrypt_backup=0</action>
+					<action function="set">tw_backup_password=</action>
+					<action function="set">tw_backup_password2=</action>
+					<action function="set">tw_backup_encrypt_display=</action>
+					<action function="set">tw_backup_encrypt_display2=</action>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">backup</action>
+			</action>
+		</page>
+
+		<page name="backupencryption2">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@backup_hdr=Backup}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row2_y%"/>
+				<text>{@enter_pass=Enter Password:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_right%" y="%row2_input_y%" w="%content_half_width%" h="%input_height%"/>
+				<text>%tw_backup_encrypt_display2%</text>
+				<data name="tw_backup_password2" mask="*" maskvariable="tw_backup_encrypt_display2"/>
+				<restrict minlen="1" maxlen="32" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"/>
+				<action function="page">checkbackuppassword</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_right%" y="row4_y" w="%content_half_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m">
+				<placement x="%col1_x_right%" y="%row4_y%"/>
+				<text>{@enter_pass2=Enter Password again:}</text>
+			</text>
+
+			<button style="main_button_half_width_low">
+				<placement x="%center_x%" y="%row5_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_encrypt_backup=0</action>
+					<action function="set">tw_backup_password=</action>
+					<action function="set">tw_backup_password2=</action>
+					<action function="set">tw_backup_encrypt_display=</action>
+					<action function="set">tw_backup_encrypt_display2=</action>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">backup</action>
+			</action>
+		</page>
+
+		<page name="checkbackuppassword">
+			<action>
+				<condition var1="tw_backup_password2" var2="tw_backup_password"/>
+				<actions>
+					<action function="set">tw_encrypt_backup=1</action>
+					<action function="page">backup</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_backup_password2" op="!=" var2="tw_backup_password"/>
+				<actions>
+					<action function="set">tw_encrypt_backup=0</action>
+					<action function="set">tw_password_not_match=1</action>
+					<action function="set">tw_backup_password=</action>
+					<action function="set">tw_backup_password2=</action>
+					<action function="set">tw_backup_encrypt_display=</action>
+					<action function="set">tw_backup_encrypt_display2=</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="backup_run">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>%tw_operation%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_partition% Partition</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<text style="text_m_accent">
+				<placement x="%progress_text_x%" y="%progress_text_y%"/>
+				<text>{@progress=Progress:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%progress_text_x%" y="%row17_y%"/>
+				<text>%tw_file_progress%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%progress_text_x%" y="%row18_y%"/>
+				<text>%tw_size_progress%</text>
+			</text>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="cancelbackup"/>
+			</button>
+
+			<action>
+				<action function="nandroid">backup</action>
+			</action>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<condition var1="tw_cancel_backup" var2="0"/>
+				<actions>
+					<action function="set">tw_back=backup</action>
+					<action function="set">tw_complete_text1={@backup_complete=Backup Complete}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<condition var1="tw_cancel_backup" var2="1"/>
+				<actions>
+					<action function="set">tw_back=backup</action>
+					<action function="set">tw_complete_text1={@backup_cancel=Backup Cancelled}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore">
+			<template name="page"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_is_fbe" var2="1"/>
+					<condition var1="tw_all_users_decrypted" var2="0"/>
+					<condition var1="tw_multiuser_warning_accepted" op="!=" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_multiuser_warning_destination=restore</action>
+					<action function="page">multiuser_warning</action>
+				</actions>
+			</action>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_sel_store_hdr=Select Backup from %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%col1_x_left%" y="%row1a_y%" w="%content_quarter_width%" h="%fileselector_install_height%"/>
+				<text>{@restore_sel_pack_fs=Select Package to Restore:}</text>
+				<filter folders="1" files="1" nav="0" extn=".ab"/>
+				<path name="tw_backups_folder"/>
+				<data name="tw_restore" default=""/>
+				<selection name="tw_restore_name"/>
+			</fileselector>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row16a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+			<action>
+				<condition var1="tw_crypto_pwtype" op="!=" var2="0"/>
+				<actions>
+					<action function="page">restore_keymaster</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_restore" op="modified"/>
+				<actions>
+					<action function="readBackup"/>
+					<action function="page">restore_read</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+
+		<page name="restore_force">
+			<template name="page"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_is_fbe" var2="1"/>
+					<condition var1="tw_all_users_decrypted" var2="0"/>
+					<condition var1="tw_multiuser_warning_accepted" op="!=" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_multiuser_warning_destination=restore_force</action>
+					<action function="page">multiuser_warning</action>
+				</actions>
+			</action>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_sel_store_hdr=Select Backup from %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%col1_x_left%" y="%row1a_y%" w="%content_quarter_width%" h="%fileselector_install_height%"/>
+				<text>{@restore_sel_pack_fs=Select Package to Restore:}</text>
+				<filter folders="1" files="1" nav="0" extn=".ab"/>
+				<path name="tw_backups_folder"/>
+				<data name="tw_restore" default=""/>
+				<selection name="tw_restore_name"/>
+			</fileselector>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row16a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<action>
+				<condition var1="tw_restore" op="modified"/>
+				<actions>
+					<action function="readBackup"/>
+					<action function="page">restore_read</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="restore_read">
+			<action>
+				<condition var1="tw_restore_encrypted" var2="1"/>
+				<actions>
+					<action function="set">tw_password_fail=0</action>
+					<action function="page">restore_decrypt</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_restore_encrypted" var2="0"/>
+				<actions>
+					<action function="page">restore_select</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore_decrypt">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_enc_backup_hdr=Encrypted Backup}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@enter_pass=Enter Password:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_restore_display%</text>
+				<data name="tw_restore_password" mask="*" maskvariable="tw_restore_display"/>
+				<restrict minlen="1" maxlen="32" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"/>
+				<action function="page">try_restore_decrypt</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row5_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="row5_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="%row5_y%"/>
+				<text>{@restore_dec_fail=Password failed, please try again!}</text>
+			</text>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">restore</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row6_y%"/>
+				<text>{@del_backup_btn=Delete Backup}</text>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="set">tw_action=cmd</action>
+					<action function="set">tw_action_param=cd %tw_backups_folder% &amp;&amp; rm -rf "%tw_restore_name%"</action>
+					<action function="set">tw_text1={@del_backup_confirm=Delete Backup?}</action>
+					<action function="set">tw_text2=%tw_restore_name%</action>
+					<action function="set">tw_text4={@del_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@deleting_backup=Deleting Backup...}</action>
+					<action function="set">tw_complete_text1={@backup_deleted=Backup Delete Complete}</action>
+					<action function="set">tw_slider_text={@swipe_delete=Swipe to Delete}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">restore</action>
+			</action>
+		</page>
+
+		<page name="try_restore_decrypt">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_try_decrypt=Encrypted Backup - Trying Decryption}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<action function="decrypt_backup"/>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="!=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_password_fail=1</action>
+					<action function="page">restore_decrypt</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">restore_select</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore_select">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_backup_date=Backup made on %tw_restore_file_date%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row3_y%"/>
+				<text>%tw_restore_name%</text>
+			</text>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_left%" y="row4_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<button>
+				<placement x="col1_x_left" y="%row2_y%" w="%content_width%" h="%navbar_height%"/>
+				<fill color="%transparent%"/>
+				<actions>
+					<action function="set">tw_backup_rename=%tw_restore_name%</action>
+					<action function="set">tw_fileexists=0</action>
+					<action function="page">renamebackup</action>
+				</actions>
+			</button>
+
+			<partitionlist>
+				<placement x="%col1_x_left%" y="%row4a_y%" w="%content_half_width%" h="%partitionlist_backup_height%"/>
+				<text>{@restore_sel_part=Select Partitions to Restore:}</text>
+				<data name="tw_restore_list" selectedlist="tw_restore_selected"/>
+				<listtype name="restore"/>
+			</partitionlist>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row5_y%"/>
+				<text>{@options=Options:}</text>
+			</text>
+
+			<checkbox>
+				<placement x="%col1_x_right%" y="%row6_y%"/>
+				<condition var1="tw_enable_adb_backup" op="=" var2="0"/>
+				<text>{@restore_enable_digest_chk=Enable Digest Verification of Backup Files}</text>
+				<data variable="tw_skip_digest_check"/>
+			</checkbox>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@del_backup_btn=Delete Backup}</text>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="set">tw_action=cmd</action>
+					<action function="set">tw_action_param=cd %tw_backups_folder% &amp;&amp; rm -rf "%tw_restore_name%"</action>
+					<action function="set">tw_text1={@del_backup_confirm=Delete Backup?}</action>
+					<action function="set">tw_text2=%tw_restore_name%</action>
+					<action function="set">tw_text4={@del_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@deleting_backup=Deleting Backup...}</action>
+					<action function="set">tw_complete_text1={@backup_deleted=Backup Delete Complete}</action>
+					<action function="set">tw_slider_text={@swipe_delete=Swipe to Delete}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_restore=Swipe to Restore}</text>
+				<action function="page">restore_run</action>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">restore</action>
+			</action>
+		</page>
+
+		<page name="renamebackup">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@rename_backup_hdr=Rename Backup}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_backup_rename%</text>
+				<data name="tw_backup_rename"/>
+				<restrict minlen="1" maxlen="64" allow=" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_.{}[]"/>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="set">tw_action=cmd</action>
+					<action function="set">tw_action_param=cd %tw_backups_folder% &amp;&amp; mv "%tw_restore_name%" "%tw_backup_rename%"</action>
+					<action function="set">tw_text1={@rename_backup_confirm=Rename Backup?}</action>
+					<action function="set">tw_text2={@rename_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@renaming_backup=Renaming Backup...}</action>
+					<action function="set">tw_complete_text1={@rename_backup_complete=Backup Rename Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_rename=Swipe to Rename}</action>
+					<action function="set">tw_filecheck=%tw_backups_folder%/%tw_backup_rename%</action>
+					<action function="set">tw_existpage=renamebackup</action>
+					<action function="set">tw_notexistpage=confirm_action</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@backup_name_exists=A backup with that name already exists!}</text>
+			</text>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">restore_select</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">restore_select</action>
+			</action>
+		</page>
+
+		<page name="restore_run">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_operation% %tw_partition%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<text style="text_m_accent">
+				<placement x="%progress_text_x%" y="%progress_text_y%"/>
+				<text>{@progress=Progress:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%progress_text_x%" y="%row18_y%"/>
+				<text>%tw_size_progress%</text>
+			</text>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<actions>
+					<action function="set">tw_back=restore_select</action>
+					<action function="set">tw_complete_text1={@restore_complete=Restore Complete}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<action function="nandroid">restore</action>
+			</action>
+		</page>
+
+		<page name="mount">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<partitionlist>
+				<placement x="%col1_x_left%" y="%row1a_y%" w="%content_width%" h="%partitionlist_mount_height%"/>
+				<text>{@mount_sel_part=Select Partitions to Mount:}</text>
+				<listtype name="mount"/>
+			</partitionlist>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_mount_system_ro" op="=" var2="0"/>
+					<condition var1="tw_is_super" op="=" var2="0"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row14_y%" textplacement="6"/>
+				<text>{@mount_sys_ro_chk=Mount system partition read-only}</text>
+				<image resource="checkbox_false"/>
+				<action function="mountsystemtoggle">1</action>
+			</button>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_mount_system_ro" op="!=" var2="0"/>
+					<condition var1="tw_is_super" op="=" var2="0"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row14_y%" textplacement="6"/>
+				<text>{@mount_sys_ro_chk=Mount system partition read-only}</text>
+				<image resource="checkbox_true"/>
+				<actions>
+					<action function="set">tw_lifetime_writes=2</action>
+					<action function="page">system_readonly_check</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_has_usb_storage" var2="1"/>
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@mount_usb_storage_btn=Mount USB Storage}</text>
+				<action function="page">usb_mount</action>
+			</button>
+
+			<button style="main_button_half_width">
+				<conditions>
+					<condition var1="tw_is_encrypted" var2="1"/>
+					<condition var1="tw_is_decrypted" var2="0"/>
+				</conditions>
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@decrypt_data_btn=Decrypt Data}</text>
+				<action function="set">tw_crypto_user_id=0</action>
+				<action function="set">tw_crypto_password=</action>
+				<action function="set">tw_password_fail=0</action>
+				<action function="set">tw_crypto_pwtype=%tw_crypto_pwtype_0%</action>
+				<action function="page">decrypt</action>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=mount</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<conditions>
+					<condition var1="tw_has_mtp" var2="1"/>
+					<condition var1="tw_mtp_enabled" var2="0"/>
+				</conditions>
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<text>{@enable_mtp_btn=Enable MTP}</text>
+				<action function="startmtp"/>
+			</button>
+
+			<button style="main_button_half_width">
+				<conditions>
+					<condition var1="tw_has_mtp" var2="1"/>
+					<condition var1="tw_mtp_enabled" var2="1"/>
+				</conditions>
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<text>{@disable_mtp_btn=Disable MTP}</text>
+				<action function="stopmtp"/>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="usb_mount">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@usb_storage_hdr=USB Storage}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@usb_stor_mnt1=USB Storage Mounted}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@usb_stor_mnt2=Be sure to safely remove your device}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@usb_stor_mnt3=from your computer before unmounting!}</text>
+			</text>
+
+			<button style="main_button_half_width">
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@unmount_btn=Unmount}</text>
+				<action function="page">usb_umount</action>
+			</button>
+
+			<action>
+				<action function="mount">usb</action>
+				<action function="set">tw_busy=1</action>
+			</action>
+		</page>
+
+		<page name="usb_umount">
+			<action>
+				<action function="unmount">usb</action>
+			</action>
+
+			<action>
+				<action function="page">mount</action>
+				<action function="set">tw_busy=0</action>
+			</action>
+		</page>
+
+		<page name="system_readonly_check">
+			<action>
+				<action function="checkpartitionlifetimewrites">/system</action>
+			</action>
+
+			<action>
+				<condition var1="tw_lifetime_writes" var2="1"/>
+				<actions>
+					<action function="mountsystemtoggle">0</action>
+					<action function="page">mount</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_lifetime_writes" var2="0"/>
+				<actions>
+					<action function="set">tw_back=mount</action>
+					<action function="page">system_readonly</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="fastbootreboot">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@reboot_hdr=Reboot}</text>
+			</text>
+
+			<listbox style="advanced_listbox">
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%fileselector_install_height%"/>
+
+				<listitem name="{@rb_system_btn=System}">
+					<condition var1="tw_reboot_system" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=system</action>
+						<action function="set">tw_reboot_param=system</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_poweroff_btn=Power Off}">
+					<condition var1="tw_reboot_poweroff" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=poweroff</action>
+						<action function="set">tw_reboot_param=poweroff</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@turning_off=Turning Off...}</action>
+						<action function="set">tw_complete_text1={@turning_off=Turning Off...}</action>
+						<action function="set">tw_slider_text={@swipe_power_off=Swipe to Power Off}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_recovery_btn=Recovery}">
+					<condition var1="tw_reboot_recovery" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=recovery</action>
+						<action function="set">tw_reboot_param=recovery</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_bootloader_btn=Bootloader}">
+					<condition var1="tw_reboot_bootloader" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=bootloader</action>
+						<action function="set">tw_reboot_param=bootloader</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_download_btn=Download}">
+					<condition var1="tw_download_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=download</action>
+						<action function="set">tw_reboot_param=download</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@fastboot_button=Fastboot}">
+					<condition var1="tw_fastboot_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=fastboot</action>
+						<action function="set">tw_reboot_param=fastboot</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_edl_btn=EDL}">
+					<condition var1="tw_edl_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=edl</action>
+						<action function="set">tw_reboot_param=edl</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+			</listbox>
+			<action>
+				<touch key="home"/>
+				<action function="page">fastboot</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">fastboot</action>
+			</action>
+		</page>
+
+		<page name="reboot">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@reboot_hdr=Reboot}</text>
+			</text>
+
+			<listbox style="advanced_listbox">
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%fileselector_install_height%"/>
+
+				<listitem name="{@rb_system_btn=System}">
+					<condition var1="tw_reboot_system" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="page">reboot_system_routine</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_poweroff_btn=Power Off}">
+					<condition var1="tw_reboot_poweroff" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=poweroff</action>
+						<action function="set">tw_reboot_param=poweroff</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_ospo=sure you wish to power off?}</action>
+						<action function="set">tw_action_text1={@turning_off=Turning Off...}</action>
+						<action function="set">tw_complete_text1={@turning_off=Turning Off...}</action>
+						<action function="set">tw_slider_text={@swipe_power_off=Swipe to Power Off}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_recovery_btn=Recovery}">
+					<condition var1="tw_reboot_recovery" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=recovery</action>
+						<action function="set">tw_reboot_param=recovery</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_bootloader_btn=Bootloader}">
+					<condition var1="tw_reboot_bootloader" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=bootloader</action>
+						<action function="set">tw_reboot_param=bootloader</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_download_btn=Download}">
+					<condition var1="tw_download_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=download</action>
+						<action function="set">tw_reboot_param=download</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+
+
+				<listitem name="{@fastboot_button=Fastboot}">
+					<condition var1="tw_fastboot_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=fastboot</action>
+						<action function="set">tw_reboot_param=fastboot</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_edl_btn=EDL}">
+					<condition var1="tw_edl_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=edl</action>
+						<action function="set">tw_reboot_param=edl</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+			</listbox>
+
+			<text style="text_m">
+				<condition var1="tw_has_boot_slots" var2="1"/>
+				<placement x="%col1_x_header%" y="%row14_y%"/>
+				<text>{@current_boot_slot=Current Slot: %tw_active_slot%}</text>
+			</text>
+
+			<button style="main_button">
+				<condition var1="tw_has_boot_slots" var2="1"/>
+				<placement x="%indent%" y="%row16_y%"/>
+				<text>{@boot_slot_a=Slot A}</text>
+				<actions>
+					<action function="set">tw_back=reboot</action>
+					<action function="set">tw_action=setbootslot</action>
+					<action function="set">tw_action_param=A</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_action_text1={@changing_boot_slot=Changing Boot Slot}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@changing_boot_slot_complete=Change Boot Slot Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_has_boot_slots" var2="1"/>
+				<placement x="%center_x%" y="%row16_y%"/>
+				<text>{@boot_slot_b=Slot B}</text>
+				<actions>
+					<action function="set">tw_back=reboot</action>
+					<action function="set">tw_action=setbootslot</action>
+					<action function="set">tw_action_param=B</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_action_text1={@changing_boot_slot=Changing Boot Slot}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@changing_boot_slot_complete=Change Boot Slot Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="installapp">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>%tw_appinstall_title%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@reboot_install_app_hdr=Install TWRP App}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@reboot_install_app1=Would you like to install the Official TWRP App?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@reboot_install_app2=The app can check for new TWRP versions.}</text>
+			</text>
+
+			<checkbox>
+				<condition var1="tw_mount_system_ro" var2="0"/>
+				<placement x="%indent%" y="%row7_y%"/>
+				<text>{@reboot_install_app_system=Install as a System App}</text>
+				<data variable="tw_app_install_system"/>
+			</checkbox>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@install_cancel=Do not Install}</text>
+				<action function="page">%tw_back%</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_to_install_app=Swipe to Install TWRP App}</text>
+				<actions>
+					<action function="set">tw_action=installapp</action>
+					<action function="set">tw_action_text1={@reboot_installing_app=Installing App...}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@successful=Successful}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_app_install_status=0</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_app_install_status=0</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="system_readonly">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@sys_ro_hdr=Unmodified System Partition}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@sys_ro_keep=Keep System Read only?}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@sys_rol1=TWRP can leave your system partition unmodified to make it easier for you to take official updates.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@sys_rol2=TWRP will be unable to prevent the stock ROM from replacing TWRP and will not offer to root your device.}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@sys_rol3=Installing zips or performing adb operations may still modify the system partition.}</text>
+			</text>
+
+			<checkbox>
+				<condition var1="tw_is_encrypted" var2="0"/>
+				<placement x="%col1_x_left%" y="%row14_y%"/>
+				<text>{@sys_ro_never_show_chk=Never show this screen during boot again}</text>
+				<data variable="tw_never_show_system_ro_page"/>
+			</checkbox>
+
+			<button style="main_button_half_width">
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@sys_ro_keep_ro_btn=Keep Read Only}</text>
+				<actions>
+					<action function="mountsystemtoggle">1</action>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="overlay">select_language</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_allow_mod=Swipe to Allow Modifications}</text>
+				<actions>
+					<action function="mountsystemtoggle">0</action>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</slider>
+		</page>
+
+		<page name="settings">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@settings_gen_hdr=General Settings}</text>
+			</text>
+
+			<template name="tabs_settings"/>
+
+			<fill color="%text_color%">
+				<placement x="0" y="%row_tab_y%" w="%tab5_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<listbox style="scrolllist">
+				<placement x="%col1_x_left%" y="%row3a_y%" w="%content_half_width%" h="%listbox_settings_height%"/>
+				<icon selected="checkbox_true" unselected="checkbox_false"/>
+				<listitem name="{@zip_sig_chk=Zip signature verification}">
+					<data variable="tw_signed_zip_verify"/>
+				</listitem>
+				<listitem name="{@install_reboot_chk=Reboot after installation is complete}">
+					<data variable="tw_install_reboot"/>
+				</listitem>
+				<listitem name="{@use_rmrf_chk=Use rm -rf instead of formatting}">
+					<data variable="tw_rm_rf"/>
+				</listitem>
+				<listitem name="{@disable_backup_space_chk=Disable free space check before backup}">
+					<data variable="tw_disable_free_space"/>
+				</listitem>
+				<listitem name="{@skip_digest_backup_chk=Skip Digest generation during backup}">
+					<data variable="tw_skip_digest_generate"/>
+				</listitem>
+				<listitem name="{@restore_enable_digest_chk=Enable Digest verification of backup files}">
+					<data variable="tw_skip_digest_check"/>
+				</listitem>
+				<listitem name="{@skip_digest_zip_chk=Skip Digest check before installing zip}">
+					<data variable="tw_skip_digest_check_zip"/>
+				</listitem>
+				<listitem name="{@use24clock_chk=Use 24-hour clock}">
+					<data variable="tw_military_time"/>
+				</listitem>
+				<listitem name="{@simact_chk=Simulate actions for theme testing}">
+					<data variable="tw_simulate_actions"/>
+				</listitem>
+				<listitem name="{@simfail_chk=Simulate failure for actions}">
+					<condition var1="tw_simulate_actions" var2="1"/>
+					<data variable="tw_simulate_fail"/>
+				</listitem>
+				<listitem name="{@sha2_chk=Use SHA2 for hashing}">
+					<condition var1="tw_no_sha2" var2="0"/>
+					<data variable="tw_use_sha2"/>
+				</listitem>
+				<listitem name="{@unmount_sys_install=Unmount System before installing a ZIP}">
+					<data variable="tw_unmount_system"/>
+				</listitem>
+				<listitem name="{@auto_reflashtwrp_chk=Automatically Reflash TWRP after flashing a ROM}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<data variable="tw_auto_reflashtwrp"/>
+				</listitem>
+			</listbox>
+
+			<checkbox>
+				<placement x="%col1_x_right%" y="%row3a_y%"/>
+				<text>{@rev_navbar_chk=Reversed navbar layout}</text>
+				<data variable="tw_samsung_navbar"/>
+			</checkbox>
+
+			<button style="radiobutton">
+				<condition var1="tw_navbar_button_position" op="!=" var2="0"/>
+				<placement x="%col1_x_right%" y="%row5_y%" textplacement="6"/>
+				<text>{@ctr_navbar_rdo=Center navbar buttons}</text>
+				<image resource="radio_false"/>
+				<action function="set">tw_navbar_button_position=0</action>
+			</button>
+
+			<button style="radiobutton">
+				<condition var1="tw_navbar_button_position" var2="0"/>
+				<placement x="%col1_x_right%" y="%row5_y%" textplacement="6"/>
+				<text>{@ctr_navbar_rdo=Center navbar buttons}</text>
+				<image resource="radio_true"/>
+				<action function="set">tw_navbar_button_position=0</action>
+			</button>
+
+			<button style="radiobutton">
+				<condition var1="tw_navbar_button_position" op="!=" var2="1"/>
+				<placement x="%col1_x_right%" y="%row6a_y%" textplacement="6"/>
+				<text>{@lft_navbar_rdo=Left align navbar buttons}</text>
+				<image resource="radio_false"/>
+				<action function="set">tw_navbar_button_position=1</action>
+			</button>
+
+			<button style="radiobutton">
+				<condition var1="tw_navbar_button_position" var2="1"/>
+				<placement x="%col1_x_right%" y="%row6a_y%" textplacement="6"/>
+				<text>{@lft_navbar_rdo=Left align navbar buttons}</text>
+				<image resource="radio_true"/>
+				<action function="set">tw_navbar_button_position=1</action>
+			</button>
+
+			<button style="radiobutton">
+				<condition var1="tw_navbar_button_position" op="!=" var2="2"/>
+				<placement x="%col1_x_right%" y="%row8_y%" textplacement="6"/>
+				<text>{@rht_navbar_rdo=Right align navbar buttons}</text>
+				<image resource="radio_false"/>
+				<action function="set">tw_navbar_button_position=2</action>
+			</button>
+
+			<button style="radiobutton">
+				<condition var1="tw_navbar_button_position" var2="2"/>
+				<placement x="%col1_x_right%" y="%row8_y%" textplacement="6"/>
+				<text>{@rht_navbar_rdo=Right align navbar buttons}</text>
+				<image resource="radio_true"/>
+				<action function="set">tw_navbar_button_position=2</action>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<text>{@restore_defaults_btn=Restore Defaults}</text>
+				<action function="restoredefaultsettings"/>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="settings_timezone">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@time_zone_hdr=Time Zone}</text>
+			</text>
+
+			<template name="tabs_settings"/>
+
+			<fill color="%text_color%">
+				<placement x="%tab5_col2_x%" y="%row_tab_y%" w="%tab5_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<listbox>
+				<placement x="%col1_x_left%" y="%row3_y%" w="%content_half_width%" h="%listbox_timezone_height%"/>
+				<text>{@sel_tz_list=Select Time Zone:}</text>
+				<data name="tw_time_zone_guisel"/>
+				<listitem name="{@utcm11=(UTC -11) Samoa, Midway Island}">BST11;BDT</listitem>
+				<listitem name="{@utcm10=(UTC -10) Hawaii}">HST10;HDT</listitem>
+				<listitem name="{@utcm9=(UTC -9) Alaska}">AST9;ADT</listitem>
+				<listitem name="{@utcm8=(UTC -8) Pacific Time}">PST8;PDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm7=(UTC -7) Mountain Time}">MST7;MDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm6=(UTC -6) Central Time}">CST6;CDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm5=(UTC -5) Eastern Time}">EST5;EDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm4=(UTC -4) Atlantic Time}">AST4;ADT</listitem>
+				<listitem name="{@utcm3=(UTC -3) Brazil, Buenos Aires}">GRNLNDST3;GRNLNDDT</listitem>
+				<listitem name="{@utcm2=(UTC -2) Mid-Atlantic}">FALKST2;FALKDT</listitem>
+				<listitem name="{@utcm1=(UTC -1) Azores, Cape Verde}">AZOREST1;AZOREDT</listitem>
+				<listitem name="{@utc0=(UTC  0) London, Dublin, Lisbon}">GMT0;BST,M3.5.0,M10.5.0</listitem>
+				<listitem name="{@utcp1=(UTC +1) Berlin, Brussels, Paris}">CET-1;CEST,M3.5.0,M10.5.0</listitem>
+				<listitem name="{@utcp2=(UTC +2) Athens, Istanbul, South Africa}">WET-2;WET,M3.5.0,M10.5.0</listitem>
+				<listitem name="{@utcp3=(UTC +3) Moscow, Baghdad}">SAUST-3;SAUDT</listitem>
+				<listitem name="{@utcp4=(UTC +4) Abu Dhabi, Tbilisi, Muscat}">WST-4;WDT</listitem>
+				<listitem name="{@utcp5=(UTC +5) Yekaterinburg, Islamabad}">PAKST-5;PAKDT</listitem>
+				<listitem name="{@utcp6=(UTC +6) Almaty, Dhaka, Colombo}">TASHST-6;TASHDT</listitem>
+				<listitem name="{@utcp7=(UTC +7) Bangkok, Hanoi, Jakarta}">THAIST-7;THAIDT</listitem>
+				<listitem name="{@utcp8=(UTC +8) Beijing, Singapore, Hong Kong}">TAIST-8;TAIDT</listitem>
+				<listitem name="{@utcp9=(UTC +9) Tokyo, Seoul, Yakutsk}">JST-9;JSTDT</listitem>
+				<listitem name="{@utcp10=(UTC +10) Eastern Australia, Guam}">EET-10;EETDT</listitem>
+				<listitem name="{@utcp11=(UTC +11) Vladivostok, Solomon Islands}">MET-11;METDT</listitem>
+				<listitem name="{@utcp12=(UTC +12) Auckland, Wellington, Fiji}">NZST-12;NZDT</listitem>
+			</listbox>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row3a_y%"/>
+				<text>{@sel_tz_offset=Select Offset (usually 0): %tw_time_zone_guioffset%}</text>
+			</text>
+
+			<button style="button_quarter_width">
+				<placement x="%col1_x_right%" y="%row5_y%"/>
+				<text>{@tz_offset_none=None}</text>
+				<action function="set">tw_time_zone_guioffset=0</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col2_x_right%" y="%row5_y%"/>
+				<text>{@tz_offset_15=15}</text>
+				<action function="set">tw_time_zone_guioffset=15</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col3_x_right%" y="%row5_y%"/>
+				<text>{@tz_offset_30=30}</text>
+				<action function="set">tw_time_zone_guioffset=30</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col4_x_right%" y="%row5_y%"/>
+				<text>{@tz_offset_45=45}</text>
+				<action function="set">tw_time_zone_guioffset=45</action>
+			</button>
+
+			<checkbox>
+				<placement x="%col1_x_right%" y="%row7_y%"/>
+				<font resource="font_m" color="%text_color%"/>
+				<text>{@use_dst_chk=Use daylight savings time (DST)}</text>
+				<data variable="tw_time_zone_guidst"/>
+				<image checked="checkbox_true" unchecked="checkbox_false"/>
+			</checkbox>
+
+			<text color="%text_color%">
+				<font resource="font_m"/>
+				<placement x="%col1_x_right%" y="%row9_y%"/>
+				<text>{@curr_tz=Current Time Zone: %tw_time_zone%}</text>
+			</text>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<font resource="font_m" color="%text_button_color%"/>
+				<text>{@set_tz_btn=Set Time Zone}</text>
+				<image resource="main_button_half_width"/>
+				<action function="setguitimezone"/>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="settings_screen">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@settings_screen_hdr=Screen Settings}</text>
+			</text>
+
+			<template name="tabs_settings"/>
+
+			<fill color="%text_color%">
+				<placement x="%tab5_col3_x%" y="%row_tab_y%" w="%tab5_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row3a_y%" textplacement="6"/>
+				<font resource="font_m" color="%text_color%"/>
+				<condition var1="tw_screen_timeout_secs" op="=" var2="0"/>
+				<condition var1="tw_no_screen_timeout" op="!=" var2="1"/>
+				<text>{@enable_timeout_chk=Enable screen timeout}</text>
+				<image resource="checkbox_false"/>
+				<action function="set">tw_screen_timeout_secs=60</action>
+			</button>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row3a_y%" textplacement="6"/>
+				<font resource="font_m" color="%text_color%"/>
+				<condition var1="tw_screen_timeout_secs" op="!=" var2="0"/>
+				<condition var1="tw_no_screen_timeout" op="!=" var2="1"/>
+				<text>{@enable_timeout_chk=Enable screen timeout}</text>
+				<image resource="checkbox_true"/>
+				<action function="set">tw_screen_timeout_secs=0</action>
+			</button>
+
+			<slidervalue>
+				<condition var1="tw_screen_timeout_secs" op="!=" var2="0"/>
+				<condition var1="tw_no_screen_timeout" op="!=" var2="1"/>
+				<placement x="col1_x_left" y="%row5_y%" w="%content_width%"/>
+				<text>{@screen_to_slider=Screen timeout in seconds:}</text>
+				<data variable="tw_screen_timeout_secs" min="15" max="300"/>
+			</slidervalue>
+
+			<slidervalue>
+				<condition var1="tw_has_brightnesss_file" var2="1"/>
+				<placement x="col1_x_left" y="%row10_y%" w="%content_width%"/>
+				<text>{@screen_bright_slider=Brightness: %tw_brightness_pct%%}</text>
+				<data variable="tw_brightness_pct" min="10" max="100"/>
+				<actions>
+					<action function="set">tw_brightness=%tw_brightness_max%</action>
+					<action function="compute">tw_brightness*%tw_brightness_pct%</action>
+					<action function="compute">tw_brightness/100</action>
+					<action function="setbrightness">%tw_brightness%</action>
+				</actions>
+			</slidervalue>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="settings_vibration">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@vibration_hdr=Vibration}</text>
+			</text>
+
+			<template name="tabs_settings"/>
+
+			<fill color="%text_color%">
+				<placement x="%tab5_col4_x%" y="%row_tab_y%" w="%tab5_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<text style="text_m">
+				<condition var1="tw_disable_haptics" var2="1"/>
+				<placement x="col1_x_left" y="%row4_y%" w="%content_width%"/>
+				<text>{@vibration_disabled=Vibration Disabled for Device}</text>
+			</text>
+
+			<slidervalue>
+				<condition var1="tw_disable_haptics" var2="0"/>
+				<placement x="col1_x_left" y="%row4_y%" w="%content_width%"/>
+				<text>{@button_vibration=Button Vibration:}</text>
+				<data variable="tw_button_vibrate" min="0" max="300"/>
+			</slidervalue>
+
+			<slidervalue>
+				<condition var1="tw_disable_haptics" var2="0"/>
+				<placement x="col1_x_left" y="%row9_y%" w="%content_width%"/>
+				<text>{@kb_vibration=Keyboard Vibration:}</text>
+				<data variable="tw_keyboard_vibrate" min="0" max="300"/>
+			</slidervalue>
+
+			<slidervalue>
+				<condition var1="tw_disable_haptics" var2="0"/>
+				<placement x="col1_x_left" y="%row14_y%" w="%content_width%"/>
+				<text>{@act_vibration=Action Vibration:}</text>
+				<data variable="tw_action_vibrate" min="0" max="500"/>
+			</slidervalue>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="settings_language">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@language_hdr=Language - %tw_language_display%}</text>
+			</text>
+
+			<template name="tabs_settings"/>
+
+			<fill color="%text_color%">
+				<placement x="%tab5_col5_x%" y="%row_tab_y%" w="%tab5_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<listbox>
+				<placement x="%col1_x_left%" y="%row3_y%" w="%content_half_width%" h="%listbox_timezone_height%"/>
+				<text>{@select_language=Select Language:}</text>
+				<icon selected="radio_true" unselected="radio_false" />
+				<data name="tw_language" />
+			</listbox>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<font resource="font_m" color="%text_button_color%"/>
+				<text>{@set_language_btn=Set Language}</text>
+				<image resource="main_button_half_width"/>
+				<action function="setlanguage"></action>
+			</button>
+
+			<action>
+				<touch key="home" />
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back" />
+				<action function="page">settings</action>
+			</action>
+		</page>
+
+		<page name="copylog">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@copying_log=Copy Logs to SD Card}</text>
+			</text>
+
+			<checkbox>
+				<condition var1="tw_logcat_exists" var2="1"/>
+				<placement x="%indent%" y="%row4_y%"/>
+				<text>{@include_logcat=Include Logcat}</text>
+				<data variable="tw_include_logcat" value="1"/>
+			</checkbox>
+
+			<checkbox>
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>{@include_kernel_log=Include Kernel Log}</text>
+				<data variable="tw_include_kernel_log" value="1"/>
+			</checkbox>
+
+			<slider>
+				<text>{@copying_log=Copy Logs to SD Card}</text>
+				<actions>
+					<action function="set">tw_back=advanced</action>
+					<action function="set">tw_action=copylog</action>
+					<action function="set">tw_action_text1={@copying_log=Copy Logs to SD Card}</action>
+					<action function="set">tw_complete_text1={@copy_log_complete=Logs Copy Completed}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="page">advanced</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="advanced">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@copy_log_btn=Copy Log}</text>
+				<action function="page">copylog</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row6a_y%"/>
+				<text>{@file_manager_btn=File Manager}</text>
+				<action function="page">filemanagerlist</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@terminal_btn=Terminal}</text>
+				<action function="page">terminalcommand</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@adb_sideload_btn=ADB Sideload}</text>
+				<action function="page">sideload</action>
+			</button>
+
+			<listbox style="advanced_listbox">
+				<placement x="%center_x%" y="%row2_y%" w="%content_half_width%" h="%fileselector_install_height%"/>
+				<listitem name="{@change_twrp_folder_btn=Change TWRP folder}">
+					<condition var1="tw_is_decrypted" var2="1"/>
+					<action function="page">changeTwrpFolder</action>
+				</listitem>
+				<listitem name="{@decrypt_users=Decrypt Users}">
+					<conditions>
+						<condition var1="tw_is_fbe" var2="1"/>
+						<condition var1="tw_all_users_decrypted" var2="0"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_crypto_user_id=</action>
+						<action function="page">decrypt_users</action>
+					</actions>
+				</listitem>
+				<listitem name="{@reload_theme_btn=Reload Theme}">
+					<action function="reload"/>
+				</listitem>
+				<listitem name="{@part_sd_btn=Partition SD Card}">
+					<condition var1="tw_allow_partition_sdcard" var2="1"/>
+					<actions>
+						<action function="set">partitionlisterror=0</action>
+						<action function="page">partsdcardsel</action>
+					</actions>
+				</listitem>
+				<listitem name="{@fix_context_btn=Fix Contexts}">
+					<condition var1="tw_has_data_media" var2="1"/>
+					<action function="page">fixcontexts</action>
+				</listitem>
+				<listitem name="{@dumlock_btn=HTC Dumlock}">
+					<condition var1="tw_show_dumlock" var2="1"/>
+					<action function="page">htcdumlock</action>
+				</listitem>
+				<listitem name="{@inject_twrp_btn=Inject TWRP}">
+					<condition var1="tw_has_injecttwrp" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=reinjecttwrp</action>
+						<action function="set">tw_text1={@inject_twrp_confirm=Re-Inject TWRP?}</action>
+						<action function="set">tw_action_text1={@injecting_twrp=Re-Injecting TWRP...}</action>
+						<action function="set">tw_complete_text1={@inject_twrp_complete=TWRP Injection Complete}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@install_twrp_ramdisk=Install Recovery Ramdisk}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_repack_kernel=0</action>
+						<action function="page">repackselect</action>
+					</actions>
+				</listitem>
+				<listitem name="{@reflash_twrp=Flash Current TWRP}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_repack_kernel=0</action>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=reflashtwrp</action>
+						<action function="set">tw_text1={@reflash_twrp_confirm=Flash Current TWRP?}</action>
+						<action function="set">tw_action_text1={@reflashing_twrp=Flashing TWRP...}</action>
+						<action function="set">tw_complete_text1={@reflash_twrp_complete=Done flashing TWRP}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@install_kernel=Install Kernel}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_repack_kernel=1</action>
+						<action function="page">repackselect</action>
+					</actions>
+				</listitem>
+				<listitem name="{@fix_recovery_loop=Fix Recovery Bootloop}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+						<condition var1="tw_uses_initramfs" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=fixabrecoverybootloop</action>
+						<action function="set">tw_text1={@fix_recovery_loop_confirm=Fix Recovery Bootloop?}</action>
+						<action function="set">tw_action_text1={@fixing_recovery_loop=Fixing Recovery Bootloop...}</action>
+						<action function="set">tw_complete_text1={@fix_recovery_loop_complete=Fix Recovery Bootloop Complete}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@reboot_install_app_hdr=Install TWRP App}">
+					<condition var1="tw_app_install_status" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_appinstall_title={@advanced_hdr=Advanced}</action>
+						<action function="page">installapp</action>
+					</actions>
+				</listitem>
+				<listitem name="{@uninstall_twrp_system_app=Uninstall TWRP App from System}">
+					<condition var1="tw_app_installed_in_system" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=uninstalltwrpsystemapp</action>
+						<action function="set">tw_text1={@uninstall_twrp_system_app_confirm=Uninstall TWRP App from System?}</action>
+						<action function="set">tw_action_text1={@uninstalling_twrp_system_app=Uninstalling TWRP App from System...}</action>
+						<action function="set">tw_complete_text1={@uninstall_twrp_system_app_complete=Uninstall TWRP App from System Complete}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@unmap_super_devices=Unmap Super Devices}">
+					<condition var1="tw_virtual_ab.enabled" op="=" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=unmapsuperdevices</action>
+						<action function="set">tw_text1={@unmap_super_devices_confirm=Unmap all Super Devices?}</action>
+						<action function="set">tw_action_text1={@unmapping_super_devices=Unmapping Super Devices...}</action>
+						<action function="set">tw_complete_text1={@unmap_super_devices_complete=Unmapped all Super Devices}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@merges_snapshots=Merge Snapshots}">
+					<condition var1="tw_virtual_ab.enabled" op="=" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=mergesnapshots</action>
+						<action function="set">tw_text1={@merge_snapshots_confirm=Merge Snapshots?}</action>
+						<action function="set">tw_action_text1={@merging_snapshots=Merging Snapshots...}</action>
+						<action function="set">tw_complete_text1={@merging_snapshots_complete=Merged Snapshots}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+			</listbox>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="partsdcardsel">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@part_sd_hdr=Partition SD Card}</text>
+			</text>
+
+			<partitionlist>
+				<placement x="%col1_x_left%" y="%row1a_y%" w="%content_width%" h="%partitionlist_mount_height%"/>
+				<text>{@sel_storage_list=Select Storage}</text>
+				<data name="tw_storage_path"/>
+				<listtype name="storage"/>
+			</partitionlist>
+
+			<text style="text_m_fail">
+				<condition var1="partitionlisterror" var2="1"/>
+				<placement x="%center_x%" y="%row14_y%" placement="5"/>
+				<text>{@invalid_partsd_sel=You must select a removable device}</text>
+			</text>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<text>{@ok_btn=OK}</text>
+				<actions>
+					<action function="getpartitiondetails">tw_storage_path</action>
+					<action function="page">partsdcardcheck</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="partsdcardcheck">
+			<action>
+				<condition var1="tw_partition_removable" op="=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="page">partsdcard</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_partition_removable" op="!=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=1</action>
+					<action function="page">partsdcardsel</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="partsdcard">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@part_sd_hdr=Partition SD Card}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@part_sd_lose=You will lose all files on your SD card!}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@part_sd_undo=This action cannot be undone!}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@part_sd_ext_sz=EXT Size:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>%tw_sdext_size%</text>
+			</text>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col3_x_left%" y="%row5_y%"/>
+				<text>{@part_sd_m=-}</text>
+				<action function="addsubtract">tw_sdext_size-256</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col2_x_right%" y="%row5_y%"/>
+				<text>{@part_sd_p=+}</text>
+				<action function="addsubtract">tw_sdext_size+256</action>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row9_y%" placement="5"/>
+				<text>{@part_sd_swap_sz=Swap Size:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row10_y%" placement="5"/>
+				<text>%tw_swap_size%</text>
+			</text>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col3_x_left%" y="%row9_y%"/>
+				<text>{@part_sd_m=-}</text>
+				<action function="addsubtract">tw_swap_size-64</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col2_x_right%" y="%row9_y%"/>
+				<text>{@part_sd_p=+}</text>
+				<action function="addsubtract">tw_swap_size+64</action>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row13_y%" placement="5"/>
+				<text>{@file_system=File System:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row14_y%" placement="5"/>
+				<text>%tw_sdpart_file_system%</text>
+			</text>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col3_x_left%" y="%row13_y%"/>
+				<text>EXT3</text>
+				<action function="set">tw_sdpart_file_system=ext3</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col2_x_right%" y="%row13_y%"/>
+				<text>EXT4</text>
+				<action function="set">tw_sdpart_file_system=ext4</action>
+			</button>
+
+			<slider style="slider_centered">
+				<text>{@swipe_part_sd=Swipe to Partition}</text>
+				<action function="page">partsdcardaction</action>
+				<actions>
+					<action function="set">tw_back=partsdcard</action>
+					<action function="set">tw_action=partitionsd</action>
+					<action function="set">tw_has_action2=1</action>
+					<action function="set">tw_action2=set</action>
+					<action function="set">tw_action2_param=tw_zip_location=/sdcard</action>
+					<action function="set">tw_action_text1={@partitioning_sd=Partitioning SD Card...}</action>
+					<action function="set">tw_action_text2={@partitioning_sd2=This will take a few minutes.}</action>
+					<action function="set">tw_complete_text1={@part_sd_complete=Partitioning Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">partsdcardsel</action>
+			</action>
+		</page>
+
+		<page name="htcdumlock">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@dumlock_hdr=HTC Dumlock}</text>
+			</text>
+
+			<button style="main_button_m">
+				<placement x="%col2_x_left%" y="%row2_y%"/>
+				<text>{@dumlock_restore_btn=Restore Original Boot}</text>
+				<actions>
+					<action function="set">tw_back=htcdumlock</action>
+					<action function="set">tw_action=htcdumlockrestoreboot</action>
+					<action function="set">tw_text1={@dumlock_restore_confirm=Restore original boot image?}</action>
+					<action function="set">tw_action_text1={@dumlock_restoring=Restoring Original Boot...}</action>
+					<action function="set">tw_complete_text1={@dumlock_restore_complete=Restore Original Boot Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_m">
+				<placement x="%col2_x_left%" y="%row6a_y%"/>
+				<text>{@dumlock_reflash_btn=Reflash Recovery}</text>
+				<actions>
+					<action function="set">tw_back=htcdumlock</action>
+					<action function="set">tw_action=htcdumlockreflashrecovery</action>
+					<action function="set">tw_text1={@dumlock_reflash_confirm=Reflash recovery to boot?}</action>
+					<action function="set">tw_action_text1={@dumlock_reflashing=Flashing recovery to boot...}</action>
+					<action function="set">tw_complete_text1={@dumlock_reflash_complete=Recovery Flash to Boot Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_m">
+				<placement x="%col2_x_left%" y="%row11_y%"/>
+				<text>{@dumlock_install_btn=Install HTC Dumlock}</text>
+				<actions>
+					<action function="set">tw_back=htcdumlock</action>
+					<action function="set">tw_action=installhtcdumlock</action>
+					<action function="set">tw_text1={@dumlock_install_confirm=Install HTC dumlock files to ROM?}</action>
+					<action function="set">tw_action_text1={@dumlock_installing=Installing HTC Dumlock...}</action>
+					<action function="set">tw_complete_text1={@dumlock_install_complete=HTC Dumlock Install Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="repackselect">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@repack_image_hdr=Select Image}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@select_file_from_storage=Select File from %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%col1_x_left%" y="%row1a_y%" w="%content_quarter_width%" h="%fileselector_filemanager_height%"/>
+				<text>%tw_zip_location%</text>
+				<filter extn=".img" folders="1" files="1"/>
+				<path name="tw_zip_location" default="/sdcard"/>
+				<data name="tw_filename"/>
+				<selection name="tw_file"/>
+			</fileselector>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row16a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=install</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<action>
+				<conditions>
+					<condition var1="tw_filename" op="modified"/>
+				</conditions>
+				<action function="page">repackconfirm</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="repackconfirm">
+			<template name="page"/>
+
+			<text style="text_l">
+				<condition var1="tw_repack_kernel" var2="1"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@repack_kernel_confirm_hdr=Install Kernel}</text>
+			</text>
+
+			<text style="text_l">
+				<condition var1="tw_repack_kernel" var2="0"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@repack_ramdisk_confirm_hdr=Install Recovery}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_repack_kernel" var2="1"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@repack_kernel_confirm=Install Kernel?}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_repack_kernel" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@repack_ramdisk_confirm=Install Recovery?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@folder=Folder:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row3_y%"/>
+				<text>%tw_zip_location%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row4_y%"/>
+				<text>{@file=File:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<checkbox>
+				<placement x="%indent%" y="%row7_y%"/>
+				<text>{@repack_backup_first=Back up existing image first}</text>
+				<data variable="tw_repack_backup_first"/>
+			</checkbox>
+
+			<button style="main_button_half_width">
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@install_cancel=Do not Install}</text>
+				<action function="page">repackselect</action>
+			</button>
+
+			<slider style="slider_centered">
+				<text>{@swipe_to_install=Swipe to Install}</text>
+				<actions>
+					<action function="set">tw_back=advanced</action>
+					<action function="set">tw_action=repackimage</action>
+					<action function="set">tw_action_param=/boot</action>
+					<action function="set">tw_action_text1={@installing=Installing...}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@install_complete=Install Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">repackselect</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="lock">
+			<background color="%semi_transparent%"/>
+
+			<image>
+				<image resource="unlock_icon"/>
+				<placement x="%center_x%" y="%row8_y%" placement="4"/>
+			</image>
+
+			<slider style="slider_centered">
+				<text>{@swipe_to_unlock=Swipe to Unlock}</text>
+				<action function="overlay"/>
+			</slider>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+		</page>
+
+		<page name="filemanagerlist">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_sel_file=Select a File or Folder}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%col1_x_left%" y="%row1a_y%" w="%content_quarter_width%" h="%fileselector_filemanager_height%"/>
+				<text>%tw_file_location1%</text>
+				<filter folders="1" files="1"/>
+				<path name="tw_file_location1" default="/"/>
+				<data name="tw_filename1"/>
+				<selection name="tw_selection1"/>
+			</fileselector>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row16a_y%"/>
+				<text>{@fm_sel_curr_folder=Select Current Folder}</text>
+				<actions>
+					<action function="set">tw_filename1=tw_file_location1</action>
+					<action function="set">tw_fm_isfolder=1</action>
+					<action function="set">tw_fm_type={@fm_type_folder=Folder}</action>
+					<action function="page">filemanageroptions</action>
+				</actions>
+			</button>
+
+			<action>
+				<actions>
+					<action function="set">tw_fm_type={@fm_type_file=File}</action>
+					<action function="set">tw_fm_isfolder=0</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+
+			<action>
+				<condition var1="tw_filename1" op="modified"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanageroptions">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_choose_act=Choose Action}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@fm_selected=%tw_fm_type% selected:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row3_y%"/>
+				<text>%tw_filename1%</text>
+			</text>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_fm_isfolder" var2="0"/>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@fm_copy_file_btn=Copy File}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=cp</action>
+					<action function="set">tw_fm_text1={@fm_copying=Copying}</action>
+					<action function="page">choosedestinationfolder</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_fm_isfolder" var2="1"/>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@fm_copy_folder_btn=Copy Folder}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=cd "%tw_file_location1%" &amp;&amp; cd .. &amp;&amp; cp -R</action>
+					<action function="set">tw_fm_text1={@fm_copying=Copying}</action>
+					<action function="page">choosedestinationfolder</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+edi				<conditions>
+					<condition var1="tw_include_nano" var2="1"/>
+					<condition var1="tw_fm_isfolder" var2="0"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@fm_edit_file=Edit File}</text>
+				<actions>
+					<action function="editfile">nano "%tw_filename1%"</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_left%" y="%row11_y%"/>
+				<text>{@fm_move_btn=Move}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=mv</action>
+					<action function="set">tw_fm_text1={@fm_moving=Moving}</action>
+					<action function="page">choosedestinationfolder</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_left%" y="%row15a_y%"/>
+				<text>{@fm_chmod755_btn=chmod 755}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=chmod 755</action>
+					<action function="set">tw_fm_text1={@fm_chmod755ing=chmod 755}</action>
+					<action function="set">tw_fm_text2=</action>
+					<action function="set">tw_fm_text3=</action>
+					<action function="set">tw_include_text3=0</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>{@fm_chmod_btn=chmod}</text>
+				<actions>
+					<action function="set">tw_filemanager_rename=0000</action>
+					<action function="set">tw_fm_text2=</action>
+					<action function="set">tw_fm_text3=</action>
+					<action function="set">tw_include_text3=0</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerchmod</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_fm_isfolder" var2="1"/>
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<text>{@fm_open_terminal_btn=Open Terminal Here}</text>
+				<action function="changeterminal">%tw_filename1%</action>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_right%" y="%row11_y%"/>
+				<text>{@fm_delete_btn=Delete}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=rm -rf</action>
+					<action function="set">tw_fm_text1={@fm_deleting=Deleting}</action>
+					<action function="set">tw_fm_text2=</action>
+					<action function="set">tw_fm_text3=</action>
+					<action function="set">tw_include_text3=0</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_fm_isfolder" var2="0"/>
+				<placement x="%center_x%" y="%row11_y%"/>
+				<text>{@fm_rename_file_btn=Rename File}</text>
+				<actions>
+					<action function="set">tw_filemanager_rename=tw_selection1</action>
+					<action function="set">tw_fm_text1={@fm_renaming=Renaming}</action>
+					<action function="set">tw_filemanager_command=mv</action>
+					<action function="page">filemanagerrenamefile</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_fm_isfolder" var2="1"/>
+				<placement x="%center_x%" y="%row11_y%"/>
+				<text>{@fm_rename_folder_btn=Rename Folder}</text>
+				<actions>
+					<action function="set">tw_filemanager_rename=tw_selection1</action>
+					<action function="set">tw_fm_text1={@fm_renaming=Renaming}</action>
+					<action function="set">tw_filemanager_command=cd "%tw_file_location1%" &amp;&amp; cd .. &amp;&amp; mv</action>
+					<action function="page">filemanagerrenamefolder</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanagerlist</action>
+			</action>
+		</page>
+
+		<page name="choosedestinationfolder">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_sel_dest=Select Destination Folder}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%col1_x_left%" y="%row1a_y%" w="%content_quarter_width%" h="%fileselector_filemanager_height%"/>
+				<text>%tw_file_location2%</text>
+				<filter folders="1" files="0"/>
+				<path name="tw_file_location2" default="/"/>
+				<data name="tw_filename2"/>
+				<selection name="tw_selection2"/>
+			</fileselector>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row16a_y%"/>
+				<text>{@fm_sel_curr_folder=Select Current Folder}</text>
+				<actions>
+					<action function="set">tw_fm_text2=to</action>
+					<action function="set">tw_fm_text3=%tw_file_location2%</action>
+					<action function="set">tw_include_text3=1</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerrenamefile">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_rename_hdr=Rename} %tw_fm_type%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_filemanager_rename%</text>
+				<data name="tw_filemanager_rename"/>
+				<restrict minlen="1" maxlen="128"/>
+				<actions>
+					<action function="set">tw_fm_text2=to</action>
+					<action function="set">tw_fm_text3="%tw_file_location1%/%tw_filemanager_rename%"</action>
+					<action function="set">tw_include_text3=1</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">filemanageroptions</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerrenamefolder">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_rename_hdr=Rename} %tw_fm_type%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_filemanager_rename%</text>
+				<data name="tw_filemanager_rename"/>
+				<restrict minlen="1" maxlen="128"/>
+				<actions>
+					<action function="set">tw_fm_text2=to</action>
+					<action function="set">tw_fm_text3=%tw_filemanager_rename%</action>
+					<action function="set">tw_include_text3=1</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">filemanageroptions</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerchmod">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_set_perms_hdr=Set Permissions}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@fm_perms=Permissions:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_filemanager_rename%</text>
+				<data name="tw_filemanager_rename"/>
+				<restrict minlen="3" maxlen="4" allow="0123456789"/>
+				<actions>
+					<action function="set">tw_filemanager_command=chmod %tw_filemanager_rename%</action>
+					<action function="set">tw_fm_text1=chmod %tw_filemanager_rename%</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">filemanageroptions</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerconfirm">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@confirm_action=Confirm Action}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>%tw_fm_text1%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>%tw_filename1%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>%tw_fm_text2%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>%tw_fm_text3%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row8_y%" placement="5"/>
+				<text>{@back_cancel=Press back button to cancel.}</text>
+			</text>
+
+			<slider style="slider_centered">
+				<text>{@swipe_to_confirm=Swipe to Confirm}</text>
+				<action function="page">filemanageraction</action>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">%tw_back%</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="filemanageraction">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_fm_text1%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_back=filemanagerlist</action>
+					<action function="set">tw_complete_text1={@fm_complete=File Operation Complete}</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="!=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_complete_text1={@fm_complete=File Operation Complete}</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_include_text3" var2="0"/>
+				<action function="cmd">%tw_filemanager_command% "%tw_filename1%"</action>
+			</action>
+
+			<action>
+				<condition var1="tw_include_text3" var2="1"/>
+				<action function="cmd">%tw_filemanager_command% "%tw_filename1%" "%tw_fm_text3%"</action>
+			</action>
+		</page>
+
+		<page name="decrypt">
+			<template name="page"/>
+
+			<action>
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<action function="page">decrypt_pattern</action>
+			</action>
+
+			<action>
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<action function="page">decrypt_pin</action>
+			</action>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@decrypt_data_hdr=Decrypt Data}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" op="!=" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pass=Enter Password:}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pass_fbe=Enter Password for User [%tw_crypto_user_id%]}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_crypto_display%</text>
+				<data name="tw_crypto_password" mask="*" maskvariable="tw_crypto_display"/>
+				<restrict minlen="1" maxlen="254"/>
+				<action function="page">trydecrypt</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row5_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="row5_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="%row5_y%"/>
+				<text>{@decrypt_data_failed=Password failed, please try again!}</text>
+			</text>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">canceldecrypt</action>
+			</button>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent_right%" y="%row6_y%" placement="1"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="overlay">select_language</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+		</page>
+
+		<page name="decrypt_pattern">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@decrypt_data_hdr=Decrypt Data}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col2_x_left%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pattern=Enter Pattern.}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col2_x_left%" y="%row3_y%" placement="5"/>
+				<text>{@decrypt_data_failed_pattern=Pattern failed, please try again!}</text>
+			</text>
+
+			<patternpassword>
+				<placement x="%pattern_x%" y="%row4a_y%" w="%pattern_size%" h="%pattern_size%"/>
+				<dot color="%fileselector_linecolor%" activecolor="%accent_color%" radius="%pattern_dot_dia%"/>
+				<line color="%fileselector_linecolor%" width="%pattern_line_w%"/>
+				<data name="tw_crypto_password"/>
+				<action function="page">trydecrypt</action>
+			</patternpassword>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row1a_y%"/>
+				<text>3x3</text>
+				<action function="set">tw_gui_pattern_grid_size=3</action>
+			</button>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row4a_y%"/>
+				<text>4x4</text>
+				<action function="set">tw_gui_pattern_grid_size=4</action>
+			</button>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row7a_y%"/>
+				<text>5x5</text>
+				<action function="set">tw_gui_pattern_grid_size=5</action>
+			</button>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col_button_right%" y="%row10a_y%"/>
+				<text>6x6</text>
+				<action function="set">tw_gui_pattern_grid_size=6</action>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%col1_x_right%" y="%row15a_y%"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="overlay">select_language</action>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">canceldecrypt</action>
+			</button>
+		</page>
+
+		<page name="decrypt_pin">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@decrypt_data_hdr=Decrypt Data}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" op="!=" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pass=Enter PIN:}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pass_fbe=Enter PIN for User [%tw_crypto_user_id%]}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_crypto_display%</text>
+				<data name="tw_crypto_password" mask="*" maskvariable="tw_crypto_display"/>
+				<restrict minlen="1" maxlen="254"/>
+				<action function="page">trydecrypt</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row5_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="row5_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="%row5_y%"/>
+				<text>{@decrypt_data_failed=PIN failed, please try again!}</text>
+			</text>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">canceldecrypt</action>
+			</button>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent_right%" y="%row6_y%" placement="1"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="overlay">select_language</action>
+			</button>
+
+			<template name="keyboardnum"/>
+		</page>
+
+		<page name="trydecrypt">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@decrypt_data_trying=Trying Decryption}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<action function="decrypt"/>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="!=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_password_fail=1</action>
+					<action function="page">decrypt</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="canceldecrypt">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@refresh_sizes_btn=Refresh Sizes}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<action function="refreshsizes"/>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="terminalcommand">
+			<template name="page"/>
+
+			<fill color="%background_color%">
+				<placement x="0" y="%status_height%" w="%screen_width%" h="%header_height%"/>
+			</fill>
+
+			<terminal>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%row3_header_y%" w="%screen_width%" h="%terminal_s_height%"/>
+			</terminal>
+
+			<terminal>
+				<condition var1="tw_hide_kb" var2="1"/>
+				<placement x="0" y="%row3_header_y%" w="%screen_width%" h="%terminal_l_height%"/>
+			</terminal>
+
+			<template name="keyboardterminaltemplate"/>
+
+			<object type="fill" color="#000000">
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%" />
+			</object>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">advanced</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="0" />
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_hide" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=1</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="1" />
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_show" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="key">home</action>
+					<action function="changeterminal"/>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="0" />
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_hide" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=1</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="1" />
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_show" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">advanced</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">advanced</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="0" />
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_hide" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=1</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="1" />
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_show" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<placement x="%home_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="key">home</action>
+					<action function="changeterminal"/>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="0" />
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_hide" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=1</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="1" />
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_show" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">advanced</action>
+					<action function="changeterminal"/>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">advanced</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="0" />
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_hide" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=1</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="1" />
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_show" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<placement x="%home_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="key">home</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="0" />
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_hide" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=1</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="1" />
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_show" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">advanced</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="page">main</action>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="changeterminal"/>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="page">advanced</action>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="changeterminal"/>
+				</actions>
+			</action>
+
+			<object type="action">
+				<touch key="power+voldown" />
+				<action function="screenshot" />
+			</object>
+		</page>
+
+		<page name="fastboot">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fastboot_button=Fastboot}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_enable_fastboot" op="=" var2="1" />
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@enable_adb=Enable ADB}</text>
+				<actions>
+					<action function="enableadb"/>
+					<action function="set">tw_enable_adb=1</action>
+					<action function="set">tw_enable_fastboot=0</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<condition var1="tw_enable_adb" op="=" var2="1" />
+				<placement x="%col1_x_left%" y="%row15a_y%"/>
+				<text>{@enable_fastboot=Enable Fastboot}</text>
+				<actions>
+					<action function="enablefastboot"/>
+					<action function="set">tw_enable_fastboot=1</action>
+					<action function="set">tw_enable_adb=0</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<text>{@reboot_btn=Reboot}</text>
+				<action function="page">fastbootreboot</action>
+			</button>
+		</page>
+
+		<page name="sideload">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@adb_sideload_hdr=ADB Sideload}</text>
+			</text>
+
+			<checkbox>
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@sideload_wipe_dalvik_chk=Wipe Dalvik Cache}</text>
+				<data variable="tw_wipe_dalvik"/>
+			</checkbox>
+
+			<checkbox>
+				<placement x="%col1_x_left%" y="%row3a_y%"/>
+				<text>{@sideload_wipe_cache_chk=Wipe Cache}</text>
+				<data variable="tw_wipe_cache"/>
+			</checkbox>
+
+			<slider style="slider_centered">
+				<text>{@swipe_to_sideload=Swipe to Start Sideload}</text>
+				<actions>
+					<action function="set">tw_back=advanced</action>
+					<action function="set">tw_action=adbsideload</action>
+					<action function="set">tw_action_text1={@sideload_confirm=ADB Sideload}</action>
+					<action function="set">tw_action_text2={@sideload_usage=Usage: adb sideload filename.zip}</action>
+					<action function="set">tw_complete_text1={@sideload_complete=ADB Sideload Complete}</action>
+					<action function="set">tw_has_cancel=1</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="set">tw_cancel_action=adbsideloadcancel</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="fixcontexts">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fix_contexts_hdr=Fix Contexts}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@fix_contexts_note1=Note: Fixing contexts is rarely needed.}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@fix_contexts_note2=Fixing SELinux Contexts may cause}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@fix_contexts_note3=your device to not boot properly.}</text>
+			</text>
+
+			<slider style="slider_centered">
+				<text>{@swipe_to_fix_contexts=Swipe to Fix Contexts}</text>
+				<actions>
+					<action function="set">tw_back=advanced</action>
+					<action function="set">tw_action=fixcontexts</action>
+					<action function="set">tw_action_text1={@fixing_contexts=Fixing Contexts...}</action>
+					<action function="set">tw_complete_text1={@fix_contexts_complete=Fix Contexts Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="slideout">
+			<fill color="%background_color%">
+				<placement x="0" y="%row2_header_y%" w="%screen_width%" h="%slideout_bg_height%"/>
+			</fill>
+
+			<console>
+				<placement x="%col1_x_left%" y="%slideout_y%" w="%content_width%" h="%slideout_height%"/>
+			</console>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%"/>
+				<action function="overlay"/>
+			</button>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="select_storage">
+			<fill color="%semi_transparent%">
+				<placement x="0" y="0" w="%screen_width%" h="%screen_height%"/>
+			</fill>
+
+			<fill color="%background_color%">
+				<placement x="%col2_x_left%" y="row4_y" w="%dialog_width%" h="%dialog_height%"/>
+			</fill>
+
+			<partitionlist style="partitionlist_storage">
+				<text>{@sel_storage_list=Select Storage}</text>
+				<data name="tw_storage_path"/>
+				<listtype name="storage"/>
+			</partitionlist>
+
+			<button style="button_third_width">
+				<placement x="%dialog_button_x%" y="%row13_y%"/>
+				<text>{@ok_btn=OK}</text>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="overlay"/>
+					<action function="page">clear_vars</action>
+				</actions>
+			</button>
+
+			<fill color="#000000">
+				<placement x="0" y="navbar_y" w="%screen_width%" h="%navbar_height"/>
+			</fill>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">home</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<placement x="%home_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">home</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<placement x="%home_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">home</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="select_language">
+			<fill color="%semi_transparent%">
+				<placement x="0" y="0" w="%screen_width%" h="%screen_height%"/>
+			</fill>
+
+			<fill color="%background_color%">
+				<placement x="%col2_x_left%" y="row4_y" w="%dialog_width%" h="%dialog_height%"/>
+			</fill>
+
+			<listbox>
+				<placement x="%dialog_content_x%" y="%row4_y%" w="%content_overlay_width%" h="%partitionlist_storage_height%"/>
+				<text>{@select_language=Select Language:}</text>
+				<icon selected="radio_true" unselected="radio_false" />
+				<data name="tw_language" />
+			</listbox>
+
+			<button style="button_third_width">
+				<placement x="%dialog_button_x%" y="%row13_y%"/>
+				<text>{@ok_btn=OK}</text>
+				<actions>
+					<action function="overlay"/>
+					<action function="setlanguage"></action>
+				</actions>
+			</button>
+
+			<fill color="#000000">
+				<placement x="0" y="navbar_y" w="%screen_width%" h="%navbar_height"/>
+			</fill>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">home</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<placement x="%home_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">home</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<placement x="%home_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">home</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="console" />
+				<condition var1="tw_busy" var2="0" />
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2" />
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="decrypt_users">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@decrypt_users=Decrypt Users}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_users_selection=Select a user ID to decrypt}</text>
+			</text>
+
+			<listbox>
+				<placement x="%col1_x_left%" y="%row1a_y%" w="%content_width%" h="%partitionlist_mount_height%"/>
+				<text>{@select_user=Select User}</text>
+				<icon selected="radio_true" unselected="radio_false" />
+				<data name="tw_crypto_user_id" />
+			</listbox>
+
+			<button style="main_button_half_width">
+				<placement x="%col2_x_right%" y="%row15a_y%"/>
+				<text>{@decrypt_users=Decrypt Users}</text>
+				<action function="set">tw_crypto_password=</action>
+				<action function="set">tw_password_fail=0</action>
+				<action function="page">decrypt</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="changeTwrpFolder">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@change_twrp_folder_btn=Change TWRP folder}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_custom_folder%</text>
+				<data name="tw_custom_folder"/>
+				<restrict minlen="1" maxlen="64" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"/>
+				<actions>
+					<action function="set">tw_back=changeTwrpFolder</action>
+					<action function="set">tw_action=applycustomtwrpfolder</action>
+					<action function="set">tw_action_param=%tw_custom_folder%</action>
+					<action function="set">tw_text1={@confirm_action=Confirm}</action>
+					<action function="set">tw_text2={@rename_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@change_twrp_folder_on_process=Changing TWRP folder}</action>
+					<action function="set">tw_complete_text1={@change_twrp_folder_after_process=TWRP folder changed to} %tw_custom_folder%</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="set">tw_filecheck=/sdcard/%tw_custom_folder%</action>
+					<action function="set">tw_existpage=changeTwrpFolder</action>
+					<action function="set">tw_notexistpage=confirm_action</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@tw_folder_exists=A folder with that name already exists!}</text>
+			</text>
+
+			<button style="main_button_half_width_low">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">advanced</action>
+			</button>
+
+			<button style="main_button_half_width_low">
+				<condition var1="tw_recovery_folder" op="!=" var2="/TWRP"/>
+				<placement x="%date_button_x%" y="%row5_y%"/>
+				<text>{@restore_defaults_btn=Restore Defaults}</text>
+				<actions>
+					<action function="set">tw_back=changeTwrpFolder</action>
+					<action function="set">tw_action=applycustomtwrpfolder</action>
+					<action function="set">tw_action_param=TWRP</action>
+					<action function="set">tw_text1={@confirm_action=Confirm}</action>
+					<action function="set">tw_text2={@rename_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@change_twrp_folder_on_process=Changing TWRP folder}</action>
+					<action function="set">tw_complete_text1={@change_twrp_folder_after_process=TWRP folder changed to} TWRP</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="set">tw_filecheck=/sdcard/TWRP</action>
+					<action function="set">tw_existpage=changeTwrpFolder</action>
+					<action function="set">tw_notexistpage=confirm_action</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="page">advanced</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore_keymaster">
+			<template name="page"/>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="1"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_with_pin_password1=PIN/Password is enabled}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="1"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_with_pin_password2=PIN/Password should be disabled before restore}</text>
+			</text>
+			<text style="text_l">
+				<condition var1="tw_crypto_pwtype" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@restore_pin_password=Restore While PIN/Password Enabled?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_with_pattern1=Pattern is enabled}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_with_pattern2=Pattern should be disabled before restore}</text>
+			</text>
+
+			<text style="text_l">
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@restore_pattern=Restore While Pattern Enabled?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_with_pin1=PIN is enabled}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_with_pin2=PIN should be disabled before restore}</text>
+			</text>
+			<text style="text_l">
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@restore_pin=Restore While PIN Enabled?}</text>
+			</text>
+
+			<slider>
+				<text>{@continue_restore_encrypted=Continue Restore?}</text>
+				<actions>
+					<action function="page">restore_force</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="page">main</action>
+				</actions>
+			</action>
+		</page>
+	</pages>
+</recovery>
diff --git a/gui/theme/common/languages/cz.xml b/gui/theme/common/languages/cz.xml
new file mode 100644
index 0000000..e15b2a7
--- /dev/null
+++ b/gui/theme/common/languages/cz.xml
@@ -0,0 +1,634 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<language>
+	<display>Czech</display>
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<string name="system">Systém</string>
+		<string name="system_image">Obraz systému</string>
+		<string name="vendor">Dodavatel</string>
+		<string name="vendor_image">Obrázek dodavatela</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="sdcard">SD karta</string>
+		<string name="internal">Interní úložiště</string>
+		<string name="microsd">Mikro SD karta</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Přijaté data</string>
+		<string name="adopted_storage">Přijaté úložiště</string>
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% °C</string>
+		<string name="battery_pct">Baterie: %tw_battery%</string>
+		<string name="sort_by_name">Seřadit podle názvu</string>
+		<string name="sort_by_date">Seřadit podle datumu</string>
+		<string name="sort_by_size">Seřadit podle velikosti</string>
+		<string name="sort_by_name_only">Název</string>
+		<string name="sort_by_date_only">Datum</string>
+		<string name="sort_by_size_only">Velikost</string>
+		<string name="tab_general">VŠEOBECNÉ</string>
+		<string name="tab_options">MOŽNOSTI</string>
+		<string name="tab_backup">ZÁLOHOVÁNÍ</string>
+		<string name="tab_time_zone">ČASOVÉ PÁSMO</string>
+		<string name="tab_screen">OBRAZOVKA</string>
+		<string name="tab_vibration">VIBRACE</string>
+		<string name="tab_language">JAZYK</string>
+		<string name="install_btn">Instalovat</string>
+		<string name="wipe_btn">Vymazat</string>
+		<string name="backup_btn">Zálohovat</string>
+		<string name="restore_btn">Obnovit</string>
+		<string name="mount_btn">Připojit</string>
+		<string name="settings_btn">Nastavení</string>
+		<string name="advanced_btn">Rozšířené</string>
+		<string name="reboot_btn">Restartovat</string>
+		<string name="files_btn">Soubory</string>
+		<string name="copy_log_btn">Kopírovat log</string>
+		<string name="select_type_hdr">Vyberte typ</string>
+		<string name="install_zip_hdr">Instalovat Zip</string>
+		<string name="install_zip_btn">Instalovat Zip</string>
+		<string name="install_image_hdr">Instalovat obraz</string>
+		<string name="install_image_btn">Instalovat obraz</string>
+		<string name="install_select_file_hdr">Vybrat soubor</string>
+		<string name="file_selector_folders_hdr">Složky</string>
+		<string name="select_file_from_storage">Vyberte soubor z: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Instalovat</string>
+		<string name="select_storage_hdr">Vyberte úložiště</string>
+		<string name="select_storage_btn">Vyberte úložiště</string>
+		<string name="queue_hdr">Řada</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% / max 10 souborů v řadě</string>
+		<string name="zip_queue_count_s">Soubor %tw_zip_queue_count% z 10:</string>
+		<string name="zip_warn1">Tato operace může nainstalovat nekompatibilní</string>
+		<string name="zip_warn2">software a znefunkčnit zařízení.</string>
+		<string name="zip_back_cancel">Stlačte Nazpět pro zrušení přidání zip.</string>
+		<string name="zip_back_clear">Stlačte Nazpět pro vyčištění seznamu.</string>
+		<string name="folder">Adresář:</string>
+		<string name="file">Soubor:</string>
+		<string name="zip_sig_chk">Verifikace podpisu zip</string>
+		<string name="inject_twrp_chk">Vložit TWRP po instalaci</string>
+		<string name="options_hdr">Nastavení</string>
+		<string name="confirm_flash_hdr">Potvrdit flash</string>
+		<string name="zip_queue">Řada:</string>
+		<string name="options">Nastavení:</string>
+		<string name="swipe_confirm">   Potvrdit</string>
+		<string name="zip_add_btn">Přidat víc Zip</string>
+		<string name="zip_clear_btn">Vyčistit seznam Zip</string>
+		<string name="install_zip_count_hdr">Nainstaluj Zip %tw_zip_index% z %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Instalace Zip: %tw_file%</string>
+		<string name="failed">Neúspěšné</string>
+		<string name="successful">Úspěšné</string>
+		<string name="install_failed">Instalace selhala</string>
+		<string name="install_successful">Instalace se podařila</string>
+		<string name="wipe_cache_dalvik_btn">Vymazat cache/dalvik</string>
+		<string name="reboot_system_btn">Restartovat systém</string>
+		<string name="install_sel_target">Vyberte cílový oddíl</string>
+		<string name="flash_image_select">Vyberte cílový oddíl pro zápis obrazu:</string>
+		<string name="target_partition">Cílový oddíl:</string>
+		<string name="flashing_image">Zapisování obrazu...</string>
+		<string name="image_flashed">Obraz zapsaný</string>
+		<string name="wipe_cache_dalvik_confirm">Smazat cache &amp; dalvik?</string>
+		<string name="wiping_cache_dalvik">Mazání cache &amp; dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Vymazání cache &amp; dalvik dokončeno</string>
+		<string name="swipe_wipe">Potáhnout pro vymazání</string>
+		<string name="swipe_wipe_s">   Vymazání</string>
+		<string name="no_os1">Není nainstalovaný OS! Jste si</string>
+		<string name="no_osrb">si jistý, že chcete restart?</string>
+		<string name="no_ospo">si jistý, že chcete vypnout zařízení?</string>
+		<string name="rebooting">Restartování...</string>
+		<string name="swipe_reboot">Potáhnout pro restart</string>
+		<string name="swipe_reboot_s">   Restart</string>
+		<string name="swipe_flash">Potáhnout pro potvrzení zápisu</string>
+		<string name="confirm_action">Potvrdit akci</string>
+		<string name="back_cancel">Pro zrušení stlačte tlačítko nazpět.</string>
+		<string name="cancel_btn">Zrušit</string>
+		<string name="wipe_hdr">Vymazat</string>
+		<string name="factory_reset_hdr">Tovární nastavení</string>
+		<string name="factory_reset_btn">Tovární nastavení</string>
+		<string name="factory_reset1">Vymazat Data, Cache a Dalvik</string>
+		<string name="factory_reset2">(nezahrnuje vnitřní paměť)</string>
+		<string name="factory_reset3">Většinou je</string>
+		<string name="factory_reset4">potřebné jen vymazaní.</string>
+		<string name="factory_resetting">Tovární nastavení...</string>
+		<string name="advanced_wipe_hdr">Rozšířené vymazání</string>
+		<string name="advanced_wipe_btn">Rozšířené vymazání</string>
+		<string name="wipe_enc_confirm">Vymazat šifrovaní z Data?</string>
+		<string name="formatting_data">Formátuji Data...</string>
+		<string name="swipe_format_data">Swipe pro volbu Formátovat Data</string>
+		<string name="swipe_format_data_s">   Formátovat Data</string>
+		<string name="factory_reset_complete">Tovární nastavení ukončené</string>
+		<string name="sel_part_hdr">Vyberte oddíly</string>
+		<string name="wipe_sel_confirm">Vymazat vybraný(é) oddíl(y)?</string>
+		<string name="wiping_part">Vymazávání oddílu(ů)...</string>
+		<string name="wipe_complete">Vymazávání dokončeno</string>
+		<string name="sel_part_wipe">Vyberte oddíly na vymazání:</string>
+		<string name="invalid_part_sel">Neplatný výběr oddílu</string>
+		<string name="format_data_hdr">Formátovat Data</string>
+		<string name="format_data_btn">Formátovat Data</string>
+		<string name="format_data_ptr1">Formátování Data vymaže všechny aplikace,</string>
+		<string name="format_data_ptr2">zálohy, fotky, videa, hudbu</string>
+		<string name="format_data_ptr3">a vymaže enkrypci vnitřní paměťi.</string>
+		<string name="format_data_adopted">Zahrnuje adoptovanou paměť</string>
+		<string name="format_data_lcp1">Formátování Data vymaže všechny aplikace, zálohy, fotografie, videa, hudbu</string>
+		<string name="format_data_lcp2">a vymaže enkrypci vnitřní paměťi.</string>
+		<string name="format_data_wtc1">Formátování Data vymaže všechny aplikace,</string>
+		<string name="format_data_wtc2">zálohy a hudbu. Tento proces je nevratný.</string>
+		<string name="format_data_undo">Toto je nevratné.</string>
+		<string name="format_data_complete">Formátování Data je hotové</string>
+		<string name="yes_continue">Napiš \"yes\" pro pokračování. Stlač nazpět pro zrušení.</string>
+		<string name="part_opt_hdr">Možnosti pro: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Vyber akci</string>
+		<string name="file_sys_opt">Nastavení souborového systému</string>
+		<string name="partition">Oddíly: %tw_partition_name%</string>
+		<string name="part_mount_point">Mount point: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Souborový systém: %tw_partition_file_system%</string>
+		<string name="part_prosent_yes">Přítomný: Ano</string>
+		<string name="part_prosent_no">Přítomný: Ne</string>
+		<string name="part_removable_yes">Vyměnitelný: Ano</string>
+		<string name="part_removable_no">Vyměnitelný: Ne</string>
+		<string name="part_size">Velikost: %tw_partition_size%MB</string>
+		<string name="part_used">Použité: %tw_partition_used%MB</string>
+		<string name="part_free">Volné: %tw_partition_free%MB</string>
+		<string name="part_backup_size">Velikost zálohy: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">Natáhnout souborový systém</string>
+		<string name="resize_btn_s">Natáhnout</string>
+		<string name="resize_confirm">Natáhnout %tw_partition_name%?</string>
+		<string name="resizing">Natahuji...</string>
+		<string name="resize_complete">Natažení dokončeno</string>
+		<string name="swipe_resize">Potáhni pro natažení</string>
+		<string name="swipe_resize_s">   Natáhnout</string>
+		<string name="repair_btn">Opravit souborový systém</string>
+		<string name="repair_btn_s">Oprava</string>
+		<string name="repair_confirm">Opravit %tw_partition_name%?</string>
+		<string name="repairing">Opravování...</string>
+		<string name="repair_complete">Oprava ukončená</string>
+		<string name="swipe_repair">Potáhnout pro opravu</string>
+		<string name="swipe_repair_s">   Oprava</string>
+		<string name="change_fs_btn">Změnit souborový systém</string>
+		<string name="change_fs_btn_s">Změnit</string>
+		<string name="change_fs_for_hdr">Změnit souborový systém pro: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Oddíl: %tw_partition_name% &gt; Zvolte souborový systém</string>
+		<string name="change_fs_warn1">Některé ROM nebo jádra nemusí podporovat některé</string>
+		<string name="change_fs_warn2">souborové systémy. Pokračujte opatrně!</string>
+		<string name="change_fs_confirm">Změnit %tw_partition_name%?</string>
+		<string name="formatting">Formátovaní...</string>
+		<string name="format_complete">Formátovaní dokončeno</string>
+		<string name="swipe_change_fs">Potáhnout pro změnu</string>
+		<string name="swipe_change_s">   Změna</string>
+		<string name="back_btn">Nazpět</string>
+		<string name="wipe_enc_btn">Vymazat šifrování</string>
+		<string name="swipe_factory_reset">Potáhnout pro tovární nastavení</string>
+		<string name="repair_change_btn">Opravit nebo změnit souborový systém</string>
+		<string name="storage_hdr">Úložiště: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Záloha</string>
+		<string name="backup_confirm_hdr">Potvrdit zálohu</string>
+		<string name="encryption_tab">ŠIFROVÁNÍ</string>
+		<string name="encryption">Šifrování:</string>
+		<string name="name">Název:</string>
+		<string name="sel_part_backup">Vybrat oddíly do zálohy:</string>
+		<string name="storage">Paměť:</string>
+		<string name="enc_disabled">zakázané - pro povolení zadejte heslo</string>
+		<string name="enc_enabled">povolené</string>
+		<string name="enable_backup_comp_chk">Povolit kompresi</string>
+		<string name="skip_digest_backup_chk" version="2">Vynechat generovaní Digest během zálohy</string>
+		<string name="disable_backup_space_chk">Nekontrolovat volné místo</string>
+		<string name="refresh_sizes_btn">Obnovit velikosti</string>
+		<string name="swipe_backup">Potáhnout pro zálohu</string>
+		<string name="append_date_btn">Připojit datum</string>
+		<string name="backup_name_exists">Záloha se stejným názvem už existuje!</string>
+		<string name="encrypt_backup">Šifrovat zálohu?</string>
+		<string name="enter_pass">Zadajte heslo:</string>
+		<string name="enter_pass2">Zopakujte heslo:</string>
+		<string name="pass_not_match">Hesla se nezhodují!</string>
+		<string name="partitions">Oddíly:</string>
+		<string name="disabled">Zakázané</string>
+		<string name="enabled">Povolené</string>
+		<string name="backup_name_hdr">Pojmenování zálohy</string>
+		<string name="progress">Příběh:</string>
+		<string name="backup_complete">Zálohovaní je dokončeno</string>
+		<string name="restore_hdr">Obnova</string>
+		<string name="sel_backup_hdr">Vyberte zálohu</string>
+		<string name="restore_sel_store_hdr">Vyberte zálohu z %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Vyberte balíček pro obnovu:</string>
+		<string name="restore_enc_backup_hdr">Šifrovaná záloha</string>
+		<string name="restore_dec_fail">Heslo je nesprávne, zkuste to znova!</string>
+		<string name="del_backup_btn">Smazat zálohu</string>
+		<string name="del_backup_confirm">Smazat zálohu?</string>
+		<string name="del_backup_confirm2">Toto je nevratné!</string>
+		<string name="deleting_backup">Vymazávaní zálohy...</string>
+		<string name="backup_deleted">Vymazání zálohy dokončeno</string>
+		<string name="swipe_delete">Potáhnout pro smazání</string>
+		<string name="swipe_delete_s">   Vymazat</string>
+		<string name="restore_try_decrypt">Šifrovaná záloha - pokus o dešifrovaní</string>
+		<string name="restore_try_decrypt_s">Pokus o dešifrovaní</string>
+		<string name="restore_backup_date">Záloha hotová %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Vybrat oddíl pro obnovu:</string>
+		<string name="restore_enable_digest_chk" version="2">Povolit kontrolu Digest na zálohovaných souborech</string>
+		<string name="restore_complete">Obnova dokočená</string>
+		<string name="swipe_restore">Potáhnout pro obnovu</string>
+		<string name="swipe_restore_s">   Obnovit</string>
+		<string name="rename_backup_hdr">Přejmenovat zálohu</string>
+		<string name="rename_backup_confirm">Přejmenovat zálohu?</string>
+		<string name="rename_backup_confirm2">Toto je nevratné!</string>
+		<string name="renaming_backup">Přejmenovávaní zálohy...</string>
+		<string name="rename_backup_complete">Přejmenování zálohy dokončeno</string>
+		<string name="swipe_to_rename">Potáhnutím přejmenovat</string>
+		<string name="swipe_rename">   Přejmenovat</string>
+		<string name="confirm_hdr">Potvrdit</string>
+		<string name="mount_hdr">Připojit</string>
+		<string name="mount_sel_part">Vybrat oddíly pro připojení:</string>
+		<string name="mount_sys_ro_chk">Připojit oddíly v režimu čtení</string>
+		<string name="mount_sys_ro_s_chk">Připojit systém v režimu na čtení</string>
+		<string name="decrypt_data_btn">Dekryptovat Data</string>
+		<string name="disable_mtp_btn">Zakázat MTP</string>
+		<string name="enable_mtp_btn">Povolit MTP</string>
+		<string name="mount_usb_storage_btn">Připoj. ukl. prostor USB</string>
+		<string name="usb_storage_hdr">Ukládací prostor USB</string>
+		<string name="usb_stor_mnt1">Ukládací prostor USB připojený</string>
+		<string name="usb_stor_mnt2">Ujistěte se, že jste zařízení odebrali</string>
+		<string name="usb_stor_mnt3">z počítače před odpojením!</string>
+		<string name="unmount_btn">Odpojit</string>
+		<string name="rb_system_btn">Systém</string>
+		<string name="rb_poweroff_btn">Vypnout</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Stáhnout</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Vypínání...</string>
+		<string name="swipe_power_off">Potáhnout pro vypnutí</string>
+		<string name="swipe_power_off_s">Vypnout</string>
+		<string name="sys_ro_hdr">Nezměněný systémový oddíl</string>
+		<string name="sys_ro_keep">Ponechat Systém v režimu na čtení?</string>
+		<string name="sys_rop1">TWRP může ponechat váš systémový oddíl nezměněný</string>
+		<string name="sys_rop2">což zjednoduší instalaci oficiálních aktualizací.</string>
+		<string name="sys_rop3">TWRP nezabrání tuctové ROMky</string>
+		<string name="sys_rop4">přepsat TWRP a nebude nabízet rootování zařízení.</string>
+		<string name="sys_rop5">Instalovaní Zip nebo operace přes adb můžu stále</string>
+		<string name="sys_rop6">modifikovat systémový oddíl.</string>
+		<string name="sys_rol1">TWRP může ponechat váš systémový oddíl nezměněný což zjednoduší instalaci oficiálních aktualizací.</string>
+		<string name="sys_rol2">TWRP nezabrání tuctové ROMce přepsat TWRP a nebude nabízet rootování zařízení.</string>
+		<string name="sys_rol3">Instalování Zip nebo operace přes adb můžou stále modifikovat systémový oddíl.</string>
+		<string name="sys_ro_never_show_chk">Nikdy víc nezobrazovat tuto obrazovku po startu</string>
+		<string name="sys_ro_keep_ro_btn">Ponechat v režimu na čtení</string>
+		<string name="swipe_allow_mod">Potáhnout pro povolení změn</string>
+		<string name="swipe_allow_mod_s">Povolit změny</string>
+		<string name="settings_hdr">Nastavení</string>
+		<string name="settings_gen_hdr">Všeobecné</string>
+		<string name="settings_gen_s_hdr">Všeobecné</string>
+		<string name="settings_gen_btn">Všeobecné</string>
+		<string name="use_rmrf_chk">Použít rm -rf namísto naformátování</string>
+		<string name="use24clock_chk">Používat 24-hodin</string>
+		<string name="rev_navbar_chk">Převrácené uspořádaní lišty</string>
+		<string name="simact_chk">Simulovat akce pro testovaní téma</string>
+		<string name="simfail_chk">Simulovat neúspěchy akcí</string>
+		<string name="ctr_navbar_rdo">Centrovat tlačítka navigace</string>
+		<string name="lft_navbar_rdo">Zarovnat tlačítka navigace vlevo</string>
+		<string name="rht_navbar_rdo">Zarovnat tlačítka navigace vpravo</string>
+		<string name="restore_defaults_btn">Obnovit předvolené</string>
+		<string name="settings_tz_btn">Časové pásmo</string>
+		<string name="settings_screen_btn">Obrazovka</string>
+		<string name="settings_screen_bright_btn">Jas obrazovky</string>
+		<string name="settings_vibration_btn">Vibrovat</string>
+		<string name="settings_language_btn">Jazyk</string>
+		<string name="time_zone_hdr">Časové pásmo</string>
+		<string name="sel_tz_list">Vyberte časové pásmo:</string>
+		<string name="utcm11">(UTC -11) Samoa, Midwayské ostrovy</string>
+		<string name="utcm10">(UTC -10) Havaj</string>
+		<string name="utcm9">(UTC -9) Aljaška</string>
+		<string name="utcm8">(UTC -8) Tichomořský čas</string>
+		<string name="utcm7">(UTC -7) Západní (Horský) čas USA, Arizona</string>
+		<string name="utcm6">(UTC -6) Centrální čas USA, Mexiko</string>
+		<string name="utcm5">(UTC -5) Východní čas USA, Indiana</string>
+		<string name="utcm4">(UTC -4) Atlantický čas, Caracas</string>
+		<string name="utcm3">(UTC -3) Brazílie, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Středoatlantický čas</string>
+		<string name="utcm1">(UTC -1) Azory, Cape Verde</string>
+		<string name="utc0">(UTC  0) Greenwichský čas, Londýn, Dublin, Lisabon</string>
+		<string name="utcp1">(UTC +1) Stredoevropský čas, Praha, Berlín, Brusel, Paříž</string>
+		<string name="utcp2">(UTC +2) Atény, Istanbul, Minsk, Káhira</string>
+		<string name="utcp3">(UTC +3) Moskva, Bagdád, Teherán</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC +5) Islamabád, Karáčí, Jekaterinsburg</string>
+		<string name="utcp6">(UTC +6) Astana, Colombo, Dhakar</string>
+		<string name="utcp7">(UTC +7) Bangkok, Džakarta, Hanoj</string>
+		<string name="utcp8">(UTC +8) Peking, Hong Kong, Singapur</string>
+		<string name="utcp9">(UTC +9) Jakutsk, Tokyo, Soul</string>
+		<string name="utcp10">(UTC +10) Vladivostok, Sydney, Guam</string>
+		<string name="utcp11">(UTC +11) Magadan, Šalamounovy ostrovy</string>
+		<string name="utcp12">(UTC +12) Wellington, Fidži, Kamčatka</string>
+		<string name="sel_tz_offset">Zvolte odchylku (obyčejně 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Žádná</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Použít letní čas (DST)</string>
+		<string name="curr_tz">Aktuální časové pásmo: %tw_time_zone%</string>
+		<string name="curr_tz_s">Aktuální časové pásmo:</string>
+		<string name="set_tz_btn">Nastav časové pásmo</string>
+		<string name="settings_screen_hdr">Obrazovka</string>
+		<string name="settings_screen_timeout_hdr">Vypnutí obrazovky</string>
+		<string name="enable_timeout_chk">Povolit vypnutí obrazovky</string>
+		<string name="screen_to_slider">Vypnutí obrazovky v sekundách:</string>
+		<string name="screen_to_slider_s">Vypnutí obrazovky v sekundách (0=zakázané): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Nastavení vypnutí obrazovky nedostupné</string>
+		<string name="screen_bright_slider">Jas: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Nastavení jasu nedostupné</string>
+		<string name="vibration_hdr">Vibrovat</string>
+		<string name="button_vibration_hdr">Vibrovat při stláčení</string>
+		<string name="kb_vibration_hdr">Vibrovat při psaní</string>
+		<string name="act_vibration_hdr">Vibrovat při akci</string>
+		<string name="button_vibration">Vibrovat při stláčení:</string>
+		<string name="kb_vibration">Vibrovat při psaní:</string>
+		<string name="act_vibration">Vibrovat při akci:</string>
+		<string name="select_language">Výběr jazyka:</string>
+		<string name="sel_lang_btn">Výběr jazyka</string>
+		<string name="set_language_btn">Nastavit jazyk</string>
+		<string name="advanced_hdr">Rozšířené</string>
+		<string name="copy_log_confirm">Kopírovat log na SD kartu?</string>
+		<string name="copying_log">Kopírování logu na SD kartu...</string>
+		<string name="copy_log_complete">Kopírování logu dokončeno</string>
+		<string name="part_sd_btn">Rozdělit SD kartu</string>
+		<string name="part_sd_s_btn">SD karta</string>
+		<string name="file_manager_btn">Souborový manažer</string>
+		<string name="language_hdr">Jazyk</string>
+		<string name="terminal_btn">Terminál</string>
+		<string name="reload_theme_btn">Načítat téma</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Vložit TWRP</string>
+		<string name="inject_twrp_confirm">Znova vložit TWRP?</string>
+		<string name="injecting_twrp">Znova vkladám TWRP...</string>
+		<string name="inject_twrp_complete">Vložení TWRP dokončeno</string>
+		<string name="swipe_to_confirm">Potáhnout pro potvrzení</string>
+		<string name="part_sd_hdr">Rozdělit SD kartu</string>
+		<string name="invalid_partsd_sel">Musíte zvolit odstranitelné zařízení</string>
+		<string name="part_sd_lose">Přijdete o všechny soubory na vložené SD kartě!</string>
+		<string name="part_sd_undo">Toto je nevratná akce!</string>
+		<string name="part_sd_ext_sz">Velikost EXT:</string>
+		<string name="part_sd_swap_sz">Velikost Swap:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Souborový systém:</string>
+		<string name="swipe_part_sd">Potáhnutím rozdělit</string>
+		<string name="swipe_part_sd_s">Rozdělení</string>
+		<string name="partitioning_sd">Dělím SD kartu...</string>
+		<string name="partitioning_sd2">Toto potrvá několik minut.</string>
+		<string name="part_sd_complete">Rozdělení kompletní</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Onbovit původní boot</string>
+		<string name="dumlock_restore_confirm">Obnovit původní obraz boot?</string>
+		<string name="dumlock_restoring">Obnovování původního bootu...</string>
+		<string name="dumlock_restore_complete">Obnova původního bootu dokončená</string>
+		<string name="dumlock_reflash_btn">Přepsání recovery</string>
+		<string name="dumlock_reflash_confirm">Přepsat recovery při startu?</string>
+		<string name="dumlock_reflashing">Přepisování recovery při startu...</string>
+		<string name="dumlock_reflash_complete">Přepsání recovery při startu dokončeno</string>
+		<string name="dumlock_install_btn">Instalovat HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Instalovat soubory HTC Dumlock do ROM?</string>
+		<string name="dumlock_installing">Instalace HTC Dumlock...</string>
+		<string name="dumlock_install_complete">Instalace HTC Dumlock dokončená</string>
+		<string name="swipe_to_unlock">Potáhnutím odemknout</string>
+		<string name="swipe_unlock">   Odemknout</string>
+		<string name="fm_hdr">Souborový manažer</string>
+		<string name="fm_sel_file">Vyberte soubor nebo adresář</string>
+		<string name="fm_type_folder">Adresář</string>
+		<string name="fm_type_file">Soubor</string>
+		<string name="fm_choose_act">Zvolte akci</string>
+		<string name="fm_selected">Zvolené %tw_fm_type%:</string>
+		<string name="fm_copy_btn">Kopíruj</string>
+		<string name="fm_copy_file_btn">Kopírovat soubor</string>
+		<string name="fm_copy_folder_btn">Kopírovat adresář</string>
+		<string name="fm_copying">Kopírování</string>
+		<string name="fm_move_btn">Přesunout</string>
+		<string name="fm_moving">Přesouvání</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Vymazat</string>
+		<string name="fm_deleting">Vymazávání</string>
+		<string name="fm_rename_btn">Přejmenovat</string>
+		<string name="fm_rename_file_btn">Přejmenovat soubor</string>
+		<string name="fm_rename_folder_btn">Přejmenovat adresář</string>
+		<string name="fm_renaming">Přejmenovávání</string>
+		<string name="fm_sel_dest">Vyberte cílový adresář</string>
+		<string name="fm_sel_curr_folder">Vyberte současný adresář</string>
+		<string name="fm_rename_hdr">Přejmenovat</string>
+		<string name="fm_set_perms_hdr">Nastavit práva</string>
+		<string name="fm_perms">Práva:</string>
+		<string name="fm_complete">Operace na souboru dokončená</string>
+		<string name="decrypt_data_hdr">Dešifrovat data</string>
+		<string name="decrypt_data_enter_pass">Zadejte heslo.</string>
+		<string name="decrypt_data_enter_pattern">Zadejte vzor.</string>
+		<string name="decrypt_data_trying">Pokus o dešifrování</string>
+		<string name="term_hdr">Příkaz terminálu</string>
+		<string name="term_s_hdr">Terminál</string>
+		<string name="term_kill_btn">ZABÍT</string>
+		<string name="term_sel_folder_hdr">Navigovat do počátečního adresáře</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Vymazat dalvik cache</string>
+		<string name="sideload_wipe_cache_chk">Vymazat cache</string>
+		<string name="swipe_to_sideload">Potáhnout pro spuštění sideload</string>
+		<string name="swipe_sideload">   Start</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Použití: adb sideload název.zip</string>
+		<string name="sideload_complete">ADB Sideload dokončený</string>
+		<string name="reboot_hdr">Restart</string>
+		<string name="install_cancel">Neinstalovat</string>
+		<string name="sel_storage_list">Vyberte úložiště</string>
+		<string name="ok_btn">OK</string>
+		<string name="no_kernel_selinux">Kernel nemá podporu pro čtení obsahu SELinux.</string>
+		<string name="full_selinux">Plná podpora SELinux.</string>
+		<string name="no_selinux">Bez podpory SELinux (chybí libselinux).</string>
+		<string name="mtp_enabled">MTP povolené</string>
+		<string name="mtp_crash">MTP selhalo, MTP nebude spuštěné při startu.</string>
+		<string name="decrypt_success">Úspešně dešifrované s přednastaveným heslem.</string>
+		<string name="unable_to_decrypt">Nelze dešifrovat s přednastaveným heslem. Možná budete muset naformátovat Data.</string>
+		<string name="generating_digest1" version="2">Generuje se Digest</string>
+		<string name="generating_digest2" version="2"> *Generuje se Digest...</string>
+		<string name="digest_created" version="2"> *Digest vygenerované.</string>
+		<string name="digest_error" version="2"> * chyba Digest!</string>
+		<string name="digest_compute_error" version="2"> * chyba při výpočtu Digest.</string>
+		<string name="current_date">(Aktuální datum)</string>
+		<string name="auto_generate">(Generovat automaticky)</string>
+		<string name="unable_to_locate_partition">Nelze najít oddíl \'{1}\' pro výpočet zálohy.</string>
+		<string name="no_partition_selected">Žádné oddíly vybrané na zálohování.</string>
+		<string name="total_partitions_backup"> * Počet oddílů označených na zálohování: {1}</string>
+		<string name="total_backup_size"> * Konečná velikost zálohy: {1}MB</string>
+		<string name="available_space"> * Zůstatek místa: {1}MB</string>
+		<string name="unable_locate_storage">Nelze najít úložiště.</string>
+		<string name="no_space">Nedostatek místa na úložišti.</string>
+		<string name="backup_started">[ZÁLOHOVÁNÍ ZAČALO]</string>
+		<string name="backup_folder"> * Adresář zálohy: {1}</string>
+		<string name="fail_backup_folder">Nelze vytvořit adresář zálohy.</string>
+		<string name="avg_backup_fs">Průměrná míra zálohování souborových systémů: {1} MB/sek</string>
+		<string name="avg_backup_img">Průměrná míra zálohování disků: {1} MB/sek</string>
+		<string name="total_backed_size">[{1} MB ZÁLOH DOHROMADY]</string>
+		<string name="backup_completed">[ZÁLOHA DOKONČENÁ ZA {1} SEKUND]</string>
+		<string name="restore_started">[ZAČÁTEK OBNOVY]</string>
+		<string name="restore_folder">Obnovit adresář: \'{1}\'</string>
+		<string name="verifying_digest" version="2">Kontrolování Digest</string>
+		<string name="skip_digest" version="2">Vynechání kontroly Digest na základě uživatelského nastavení.</string>
+		<string name="calc_restore">Počítání detailů obnovy...</string>
+		<string name="restore_read_only">Nelze obnovit {1} -- připojené jen na čtení.</string>
+		<string name="restore_unable_locate">Nelze najít oddíl \'{1}\' pro obnovu.</string>
+		<string name="no_part_restore">Žádné oddíly nebyly vybrané pro obnovu.</string>
+		<string name="restore_part_count">Obnova {1} oddílů...</string>
+		<string name="total_restore_size">Velikost obnovy je spolu {1}MB</string>
+		<string name="updating_system_details">Obnovování detailů systému</string>
+		<string name="restore_completed">[OBNOVA DOKONČENÁ ZA {1} SEKUND]</string>
+		<string name="error_opening_strerr">Chyba při otvírání: \'{1}\' ({2})</string>
+		<string name="unable_locate_part_backup_name">Nelze najít oddíl podle názvu zálohy: \'{1}\'</string>
+		<string name="unable_find_part_path">Nelze najít oddíl na cestě \'{1}\'</string>
+		<string name="update_part_details">Obnova detailů oddílů...</string>
+		<string name="update_part_details_done">...hotovo</string>
+		<string name="wiping_dalvik">Vymazávání adresářů dalvik cache...</string>
+		<string name="cleaned">Vyčištěné: {1}...</string>
+		<string name="dalvik_done">-- Vyčištění adresáře Dalvik cache bylo dokončeno!</string>
+		<string name="no_andsec">Žádné zabezpečené android oddíly nebyly nalezeny.</string>
+		<string name="unable_to_locate">{1} nelze najít.</string>
+		<string name="wiping_datamedia">Vymazávání interního úložiště -- /data/media...</string>
+		<string name="unable_to_mount">{1} nelze připojit</string>
+		<string name="unable_to_mount_internal">Nelze připojit interní úložiště</string>
+		<string name="unable_to_mount_storage">Nelze připojit úložiště</string>
+		<string name="fail_decrypt">Nelze dešifrovat data.</string>
+		<string name="no_crypto_support">Tato verze byla zkompilovaná bez podpory šifrování.</string>
+		<string name="decrypt_success_dev">Data byly úspěšne dešifrované, nový block device: \'{1}\'</string>
+		<string name="done">Hotovo.</string>
+		<string name="start_partition_sd">Dělení SD karty...</string>
+		<string name="partition_sd_locate">Nelze najít zařízení na rozdělení.</string>
+		<string name="ext_swap_size">Velikost EXT + Swap je větší jako velikost SD karty.</string>
+		<string name="remove_part_table">Vymazávání tabulky rozdělení...</string>
+		<string name="unable_rm_part">Nelze vymazat tabulku rozdělení.</string>
+		<string name="create_part">Vytváření oddílu {1}...</string>
+		<string name="unable_to_create_part">oddíl {1} nelze vytvořit.</string>
+		<string name="format_sdext_as">Formátování sd-ext jako {1}...</string>
+		<string name="part_complete">Rozdělení dokončeno.</string>
+		<string name="unable_to_open">Nelze otevřít \'{1}\'.</string>
+		<string name="mtp_already_enabled">MTP je už povolené</string>
+		<string name="mtp_fail">Nelze povolit MTP</string>
+		<string name="no_mtp">Podpora MTP není zahrnutá</string>
+		<string name="image_flash_start">[ZÁPIS OBRAZU SPUŠTĚNÝ]</string>
+		<string name="img_to_flash">Obraz na zápis: \'{1}\'</string>
+		<string name="flash_unable_locate">Nelze najít oddíl \'{1}\' pro zápis.</string>
+		<string name="no_part_flash">Na zápis nebyly zvolené žádné oddíly.</string>
+		<string name="too_many_flash">Na zápis je zvolených příliš mnoho oddílů.</string>
+		<string name="invalid_flash">Zvolený oddíl je nesprávný pro zápis.</string>
+		<string name="flash_done">[ZÁPIS OBRAZU DOKONČENÝ]</string>
+		<string name="wiping">Mazání {1}</string>
+		<string name="repair_not_exist">{1} neexistuje! Oprava nemožná!</string>
+		<string name="repairing_using">Oprava {1} pomocí {2}...</string>
+		<string name="unable_repair">Nelze opravit {1}.</string>
+		<string name="mount_data_footer">Nelze připojit /data a nelze najít parametry šifrování.</string>
+		<string name="create_folder_strerr">Adresář \'{1}\' nelze vytvořit ({2}).</string>
+		<string name="fail_mount">Nelze připojit \'{1}\' ({2})</string>
+		<string name="fail_unmount">Nelze odpojit \'{1}\' ({2})</string>
+		<string name="cannot_resize">Nelze natáhnout {1}.</string>
+		<string name="repair_resize">Opravování {1} před natáhnutím.</string>
+		<string name="unable_resize">Nelze natáhnout {1}.</string>
+		<string name="no_digest_found" version="2">Soubor Digest se nenašel pro \'{1}\'. Prosím zvolte Vypnout kontrolu Digest během obnovy.</string>
+		<string name="digest_fail_match" version="2">Digest neodpovídá souboru \'{1}\'.</string>
+		<string name="digest_matched" version="2">Digest matched for '{1}'.</string>
+		<string name="fail_decrypt_tar">Nelze dešifrovat soubor tar \'{1}\'</string>
+		<string name="format_data_msg">Měli by jste restartovat recovery aby jste mohli znova používat /data.</string>
+		<string name="format_data_err">Nelze formátovat pro zrušení šifrování.</string>
+		<string name="formatting_using">Formátování {1} použitím {2}...</string>
+		<string name="unable_to_wipe">Nelze vymazat {1}.</string>
+		<string name="cannot_wipe">Oddíl {1} se nedá vymazat.</string>
+		<string name="remove_all">Vymazání všech souborů v \'{1}\'</string>
+		<string name="wiping_data">Vymazaní Data bez vymazání /data/media...</string>
+		<string name="backing_up">Zálohování {1}...</string>
+		<string name="backing">Zálohování</string>
+		<string name="backup_size">Velikost zálohy pro \'{1}\' je 0 bajtů.</string>
+		<string name="datamedia_fs_restore">UPOZORNĚNÍ: Tato záloha /data byla vytvořená pomocí systému souborů {1}! Záloha nebude bootovat pokud se nezmění nazpět na {1}.</string>
+		<string name="restoring">Obnovování {1}...</string>
+		<string name="restoring_hdr">Obnovování</string>
+		<string name="recreate_folder_err">Nelze vytvořit adresář {1}.</string>
+		<string name="img_size_err">Velikost obrazu je větší než cílové zařízení</string>
+		<string name="flashing">Zapisování {1}...</string>
+		<string name="backup_folder_set">Adresář záloh nastavený na: \'{1}\'</string>
+		<string name="locate_backup_err">Zálohu \'{1}\' nelze najít</string>
+		<string name="set_restore_opt">Nastavení možností obnovy: \'{1}\':</string>
+		<string name="digest_check_skip" version="2">Vynechání kontroly Digest zapnuté</string>
+		<string name="ors_encrypt_restore_err">Nelze použít OpenRecoveryScript na obnovu zašifrované zálohy.</string>
+		<string name="mounting">Připojení</string>
+		<string name="unmounting">Odpojení</string>
+		<string name="mounted">\'{1}\' připojený</string>
+		<string name="unmounted">\'{1}\' odpojený</string>
+		<string name="setting">Nastavení \'{1}\' na \'{2}\'</string>
+		<string name="setting_empty">Nastavení \'{1}\' na prázdné</string>
+		<string name="making_dir1">Vytváření adresáře</string>
+		<string name="making_dir2">Vytváření adresáře: \'{1}\'</string>
+		<string name="running_command">Spouštění příkazu</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">Spouštění funkce ADB sideload...</string>
+		<string name="need_new_adb">pro použití sideload na toto zařízení potřebujete ADB 1.0.32 nebo novší.</string>
+		<string name="no_pwd">Heslo nebylo poskytnuté.</string>
+		<string name="done_ors">Vykonávání skriptu dokončeno</string>
+		<string name="injecttwrp">Vkládání TWRP do obrazu boot...</string>
+		<string name="zip_err">Chyba při instalaci zip souboru \'{1}\'</string>
+		<string name="installing_zip">Instalace zip souboru \'{1}\'</string>
+		<string name="select_backup_opt">Nastavení možností zálohy:</string>
+		<string name="comprossion_on">Komprese zapnutá</string>
+		<string name="digest_off" version="2">Generovaní Digest vypnuté</string>
+		<string name="backup_fail">Zálohování selhalo</string>
+		<string name="backup_clean">Zálohování selhalo. Čistění adresáře záloh.</string>
+		<string name="running_recovery_commands">Spouštění příkazů recovery</string>
+		<string name="recovery_commands_complete">Příkazy recovery dokončeno</string>
+		<string name="running_ors">Spouštění OpenRecoveryScript</string>
+		<string name="ors_complete">OpenRecoveryScript dokončený</string>
+		<string name="no_updater_binary">Nelze nájít \'{1}\' v souboru zip.</string>
+		<string name="check_for_digest" version="2">Kontrola Digest souboru...</string>
+		<string name="fail_sysmap">Nelze namapovat soubor \'{1}\'</string>
+		<string name="verify_zip_sig">Ověřování podpisu zip souboru...</string>
+		<string name="verify_zip_fail">Ověřování podpisu zip souboru selhalo!</string>
+		<string name="verify_zip_done">Podpis souboru zip úspešně ověřený.</string>
+		<string name="zip_corrupt">Soubor zip je poškozený!</string>
+		<string name="no_digest" version="2">Vynechání kontroly Digest: soubor s Digest nenalezen</string>
+		<string name="digest_fail" version="2">Digest nesedí</string>
+		<string name="digest_match" version="2">Digest sedí</string>
+		<string name="pid_signal">Proces {1} skončil se signálem: {2}</string>
+		<string name="pid_error">Proces {1} skončil s CHYBOU: {2}</string>
+		<string name="install_dumlock">Instalování HTC Dumlock do systému...</string>
+		<string name="dumlock_restore">Obnovování původního bootu...</string>
+		<string name="dumlock_reflash">Zapisování recovery do bootu...</string>
+		<string name="run_script">Běh skriptu {1}...</string>
+		<string name="rename_stock">Přejmenovaný tuctový recovery soubor v /systém pro zabránění přepsání TWRP tuctovou ROM.</string>
+		<string name="split_backup">Rozdělení zálohy do více archívů...</string>
+		<string name="backup_error">Chyba při vytváření zálohy.</string>
+		<string name="restore_error">Chyba během procesu obnovy.</string>
+		<string name="split_thread">Rozdělení ID vlákna {1} do archívu {2}</string>
+		<string name="file_progress">%llu z %llu souborů, %i%%</string>
+		<string name="size_progress">%lluMB z %lluMB, %i%%</string>
+		<string name="decrypt_cmd">Pokus o dešifrování datového oddílu přez příkazový řádek.</string>
+		<string name="base_pkg_err">Nepodařilo se načíst základní balíčky.</string>
+		<string name="simulating">Simulování akcí...</string>
+		<string name="backup_cancel">Zálohování přerušeno</string>
+		<string name="config_twrp">Konfigurování TWRP...</string>
+		<string name="config_twrp_err">Nelze konfigurovat TWRP s tímto jádrem.</string>
+		<string name="copy_log">Recovery log zkopírovaný do {1}.</string>
+		<string name="max_queue">Dosažený maximální počet zip v seznamu!</string>
+		<string name="min_queue">Dosažený minimální počet zip v seznamu!</string>
+		<string name="screenshot_saved">Snímek obrazovky byl uložen do {1}</string>
+		<string name="screenshot_err">Nelze udělat snímek obrazovky!</string>
+		<string name="zip_wipe_cache">Jeden nebo víc zip souborů vyžádalo smazaní cache -- Vymazání cache nyní.</string>
+		<string name="and_sec_wipe_err">Nelze vymazat android secure</string>
+		<string name="dalvik_wipe_err">Nelze vymazat dalvik</string>
+		<string name="auto_gen">(Generovat automaticky)</string>
+		<string name="curr_date">(Aktuální datum)</string>
+		<string name="backup_name_len">Název zálohy je příliš dlouhý.</string>
+		<string name="backup_name_invalid">Název zálohy \'{1}\' obsahuje neplatný znak: \'{1}\'</string>
+		<string name="no_real_sdcard">Toto zařízení nemá skutečnou SD kartu! Přerušuji!</string>
+		<string name="cancel_sideload">Ruším ADB sideload...</string>
+		<string name="change_fs_err">Chyba při změně souborového systému.</string>
+		<string name="theme_ver_err">Téma vlastní verze nesedí s verzí TWRP. Použije se tuctové téma.</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/de.xml b/gui/theme/common/languages/de.xml
new file mode 100644
index 0000000..92cf03c
--- /dev/null
+++ b/gui/theme/common/languages/de.xml
@@ -0,0 +1,740 @@
+<?xml version="1.0"?>
+
+<language>
+	<display>Deutsch</display>
+
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<!-- Partition display names -->
+		<string name="system">System</string>
+		<string name="system_image">System-Image</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Vendor-Image</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="data_backup">Data (ohne /data/media)</string>
+		<string name="sdcard">SD-Karte</string>
+		<string name="internal">Interner Speicher</string>
+		<string name="microsd">Micro SD-Karte</string>
+		<string name="usbotg">USB-OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Eingegliederte Daten-Partition</string>
+		<string name="adopted_storage">Eingegliederte Storage-Partition</string>
+		<string name="autostorage">Storage-Partition</string>
+
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% &#xB0;C</string>
+		<string name="battery_pct">Akku: %tw_battery%</string>
+		<string name="sort_by_name">Nach Namen sortieren</string>
+		<string name="sort_by_date">Nach Datum sortieren</string>
+		<string name="sort_by_size">Nach Größe sortieren</string>
+		<string name="sort_by_name_only">Name</string>
+		<string name="sort_by_date_only">Datum</string>
+		<string name="sort_by_size_only">Größe</string>
+		<string name="tab_general">ALLGEMEIN</string>
+		<string name="tab_options">OPTIONEN</string>
+		<string name="tab_backup">SICHERUNG</string>
+		<string name="tab_time_zone">ZEITZONE</string>
+		<string name="tab_screen">BILDSCHIRM</string>
+		<string name="tab_vibration">VIBRATION</string>
+		<string name="tab_language">SPRACHE</string>
+
+		<string name="install_btn">Installieren</string>
+		<string name="wipe_btn">Löschen</string>
+		<string name="backup_btn">Sichern</string>
+		<string name="restore_btn">Wiederherstellen</string>
+		<string name="mount_btn">Einhängen</string>
+		<string name="settings_btn">Einstellungen</string>
+		<string name="advanced_btn">Erweitert</string>
+		<string name="reboot_btn">Neustart</string>
+		<string name="files_btn">Dateien</string>
+		<string name="copy_log_btn">Log kopieren</string>
+		<string name="select_type_hdr">Typ wählen</string>
+		<string name="install_zip_hdr">ZIP installieren</string>
+		<string name="install_zip_btn">ZIP installieren</string>
+		<string name="install_image_hdr">Image installieren</string>
+		<string name="install_image_btn">Image installieren</string>
+		<string name="install_select_file_hdr">Datei auswählen</string>
+		<string name="file_selector_folders_hdr">Ordner</string>
+		<string name="select_file_from_storage">Speicher: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Installieren</string>
+		<string name="select_storage_hdr">Speicher auswählen</string>
+		<string name="select_storage_btn">Speicher auswählen</string>
+		<string name="queue_hdr">Einreihen</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% von 10 Dateien eingereiht</string>
+		<string name="zip_queue_count_s">File %tw_zip_queue_count% von 10:</string>
+		<string name="zip_warn1">Dieser Vorgang kann möglicherweise inkompatible</string>
+		<string name="zip_warn2">Software installieren und das Gerät unbrauchbar machen.</string>
+		<string name="zip_back_cancel">Zurück-Taste drücken, um den Vorgang abzubrechen.</string>
+		<string name="zip_back_clear">Zurück-Taste drücken, um Warteschlange zu löschen.</string>
+		<string name="folder">Ordner:</string>
+		<string name="file">Datei:</string>
+		<string name="zip_sig_chk">ZIP-Signaturprüfung</string>
+		<string name="inject_twrp_chk">TWRP nach der Installation injizieren</string>
+		<string name="install_reboot_chk">Automatischer Neustart nach Installation</string>
+		<string name="options_hdr">Einstellungen</string>
+		<string name="confirm_flash_hdr">Flashvorgang bestätigen</string>
+		<string name="zip_queue">Warteschlange:</string>
+		<string name="options">Einstellungen:</string>
+		<string name="swipe_confirm">Bestätigen</string>
+		<string name="zip_add_btn">Mehr ZIPs hinzufügen</string>
+		<string name="zip_clear_btn">ZIP-Stapel löschen</string>
+		<string name="install_zip_count_hdr">Installiere ZIP %tw_zip_index% von %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Installiere ZIP: %tw_file%</string>
+		<string name="failed">Fehlgeschlagen</string>
+		<string name="successful">Erfolgreich</string>
+		<string name="install_failed">Installation fehlgeschlagen</string>
+		<string name="install_successful">Installation erfolgreich</string>
+		<string name="wipe_cache_dalvik_btn">Cache/Dalvik löschen</string>
+		<string name="wipe_dalvik_btn">Dalvik löschen</string>
+		<string name="reboot_system_btn">System neustarten</string>
+		<string name="install_sel_target">Ziel-Partition auswählen</string>
+		<string name="flash_image_select">Partition wählen, um das Image zu einzuspielen:</string>
+		<string name="target_partition">Ziel-Partition:</string>
+		<string name="flashing_image">Spiele Image ein...</string>
+		<string name="image_flashed">Image eingespielt</string>
+		<string name="wipe_cache_dalvik_confirm">Cache &amp; Dalvik löschen?</string>
+		<string name="wipe_dalvik_confirm">Dalvik löschen?</string>
+		<string name="wiping_cache_dalvik">Lösche Cache &amp; Dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Cache &amp; Dalvik gelöscht</string>
+		<string name="wipe_dalvik_complete">Dalvik gelöscht</string>
+		<string name="swipe_wipe">Löschen bestätigen</string>
+		<string name="swipe_wipe_s">Löschen</string>
+		<string name="no_os1">Kein OS installiert! Sind Sie</string>
+		<string name="no_osrb">sicher, dass Sie neu starten möchten?</string>
+		<string name="no_ospo">sicher, dass Sie ausschalten möchten?</string>
+		<string name="rebooting">Neustart...</string>
+		<string name="swipe_reboot">Neustart bestätigen</string>
+		<string name="swipe_reboot_s">Neustart</string>
+		<string name="reboot_install_app_hdr">TWRP App installieren?</string>
+		<string name="reboot_install_app1">Soll die offizielle TWRP App installiert werden?</string>
+		<string name="reboot_install_app2">Die App kann nach neuen TWRP-Versionen suchen.</string>
+		<string name="reboot_install_app_prompt_install">Fragen, ob TWRP App installiert werden soll</string>
+		<string name="reboot_install_app_system">Als System-App installieren</string>
+		<string name="reboot_installing_app">Installiere App...</string>
+		<string name="swipe_to_install_app">TWRP App installieren</string>
+		<string name="uninstall_twrp_system_app">Deinstalliere TWRP App vom System</string>
+		<string name="uninstall_twrp_system_app_confirm">TWRP App vom System deinstallieren?</string>
+		<string name="uninstalling_twrp_system_app">Deinstalliere TWRP App vom System...</string>
+		<string name="uninstall_twrp_system_app_complete">Deinstallationder TWRP App vom System abgeschlossen</string>
+		<string name="swipe_flash">Installation bestätigen</string>
+		<string name="confirm_action">Aktion bestätigen</string>
+		<string name="back_cancel">Zurück-Taste drücken, um den Vorgang abzubrechen.</string>
+		<string name="cancel_btn">Abbruch</string>
+		<string name="wipe_hdr">Löschen</string>
+		<string name="factory_reset_hdr">Werkseinstellungen herstellen</string>
+		<string name="factory_reset_btn">Werkseinstellungen</string>
+		<string name="factory_reset1">Löscht Daten, Cache und Dalvik</string>
+		<string name="factory_reset2">(ohne internen Speicher)</string>
+		<string name="factory_reset3">Diese Lösch-Aktion ist in der Regel</string>
+		<string name="factory_reset4">die einzige die benötigt wird.</string>
+		<string name="factory_reset5">(beinhaltet nicht die Benutzer und den Sperrbildschirm)</string>
+		<string name="factory_resetting">Werkseinstellungen herstellen...</string>
+		<string name="advanced_wipe_hdr">Erweitertes Löschen</string>
+		<string name="advanced_wipe_btn">Erweitertes Löschen</string>
+		<string name="wipe_enc_confirm">Verschlüsselung der Daten-Partition enfernen?</string>
+		<string name="formatting_data">Data wird formatiert...</string>
+		<string name="swipe_format_data">Daten-Partition formatieren</string>
+		<string name="swipe_format_data_s">Formatieren</string>
+		<string name="factory_reset_complete">Werkseinstellungen wiederhergestellt</string>
+		<string name="sel_part_hdr">Partitionen auswählen</string>
+		<string name="wipe_sel_confirm">Ausgewählte Partition(en) löschen?</string>
+		<string name="wiping_part">Lösche Partition(en)...</string>
+		<string name="wipe_complete">Löschen abgeschlossen </string>
+		<string name="sel_part_wipe">Partitionen zum Löschen auswählen:</string>
+		<string name="invalid_part_sel">Ungültige Partitionsauswahl</string>
+		<string name="format_data_hdr">Daten-Partition formatieren</string>
+		<string name="format_data_btn">Daten formatieren</string>
+		<string name="format_data_ptr1">Formatieren der Daten-Partition löscht alle Apps,</string>
+		<string name="format_data_ptr2">Sicherungen, Bilder, Videos, sowie Medien und</string>
+		<string name="format_data_ptr3">entfernt die Verschlüsselung des internen Speichers.</string>
+		<string name="format_data_adopted">Betrifft auch die eingegliederte Storage-Partition.</string>
+		<string name="format_data_lcp1">Formatieren der Daten-Partition löscht alle Apps, Sicherungen, Bilder, Videos, sowie Medien und</string>
+		<string name="format_data_lcp2">entfernt die Verschlüsselung des internen Speichers.</string>
+		<string name="format_data_wtc1">Formatieren der Daten-Partition löscht alle Apps,</string>
+		<string name="format_data_wtc2">Sicherungen und Medien. Endgültig!</string>
+		<string name="format_data_undo">Dies kann nicht rückgängig gemacht werden.</string>
+		<string name="format_data_complete">Formatierung der Daten-Partition abgeschlossen</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">"yes" eingeben um fortzufahren. Zurück drücken um abzubrechen.</string>
+		<string name="part_opt_hdr">Optionen für %tw_partition_name%-Partition</string>
+		<string name="sel_act_hdr">Aktion auswählen</string>
+		<string name="file_sys_opt">Dateisystem-Optionen</string>
+		<string name="partition">Partition: %tw_partition_name%</string>
+		<string name="part_mount_point">Einhängepunkt: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Dateisystem: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Vorhanden: Ja</string>
+		<string name="part_present_no">Vorhanden: Nein</string>
+		<string name="part_removable_yes">Wechselbar: Ja</string>
+		<string name="part_removable_no">Wechselbar: Nein</string>
+		<string name="part_size">Größe: %tw_partition_size%MB</string>
+		<string name="part_used">Verwendet: %tw_partition_used%MB</string>
+		<string name="part_free">Frei: %tw_partition_free%MB</string>
+		<string name="part_backup_size">Backup-Größe: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">Größe ändern</string>
+		<string name="resize_btn_s">Größe ändern</string>
+		<string name="resize_confirm">Größe von %tw_partition_name% ändern?</string>
+		<string name="resizing">Ändere Größe...</string>
+		<string name="resize_complete">Größenänderung abgeschlossen</string>
+		<string name="swipe_resize">Größenänderung bestätigen</string>
+		<string name="swipe_resize_s">Größe ändern</string>
+		<string name="repair_btn">Dateisystem reparieren</string>
+		<string name="repair_btn_s">Reparatur</string>
+		<string name="repair_confirm">Repariere %tw_partition_name%?</string>
+		<string name="repairing">Reparieren...</string>
+		<string name="repair_complete">Reparatur abgeschlossen</string>
+		<string name="swipe_repair">Partition reparieren</string>
+		<string name="swipe_repair_s">Reparieren</string>
+		<string name="change_fs_btn">Dateisystem ändern</string>
+		<string name="change_fs_btn_s">Ändern</string>
+		<string name="change_fs_for_hdr">Dateisystem für %tw_partition_name% ändern</string>
+		<string name="change_fs_for_hdr_s">Partition: %tw_partition_name% &gt; Dateisystem auswählen</string>
+		<string name="change_fs_warn1">Einige ROMs oder Kernel unterstützen möglicherweise manche</string>
+		<string name="change_fs_warn2">Dateisysteme nicht. Überlegt handeln!</string>
+		<string name="change_fs_confirm">%tw_partition_name% ändern?</string>
+		<string name="formatting">Formatierung...</string>
+		<string name="format_complete">Formatieren abgeschlossen</string>
+		<string name="swipe_change_fs">Dateisystem ändern</string>
+		<string name="swipe_change_s">Ändern</string>
+		<string name="back_btn">Zurück</string>
+		<string name="wipe_enc_btn">Verschlüsselung entfernen</string>
+		<string name="swipe_factory_reset">Werkseinstellungen herstellen</string>
+		<string name="repair_change_btn">Dateisystem reparieren oder ändern</string>
+		<string name="storage_hdr">Speicher: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Sicherung</string>
+		<string name="backup_confirm_hdr">Sicherung bestätigen</string>
+		<string name="encryption_tab">VERSCHLÜSSELUNG</string>
+		<string name="encryption">Verschlüsselung:</string>
+		<string name="name">Name:</string>
+		<string name="sel_part_backup">Zu sichernde Partitionen auswählen:</string>
+		<string name="storage">Speicherort:</string>
+		<string name="enc_disabled">deaktiviert - Passwort festlegen um zu aktivieren</string>
+		<string name="enc_enabled">aktiviert</string>
+		<string name="enable_backup_comp_chk">Komprimierung aktivieren</string>
+		<string name="skip_digest_backup_chk" version="2">Digest-Erstellung für die Sicherung überspringen</string>
+		<string name="disable_backup_space_chk">Prüfung auf freien Speicher deaktivieren</string>
+		<string name="current_boot_slot">Aktiver Slot: %tw_active_slot%</string>
+		<string name="boot_slot_a">Slot A</string>
+		<string name="boot_slot_b">Slot B</string>
+		<string name="changing_boot_slot">Wechsle Boot Slot</string>
+		<string name="changing_boot_slot_complete">Boot Slot gewechselt</string>
+		<string name="refresh_sizes_btn">Größen aktualisieren</string>
+		<string name="swipe_backup">Sicherung erstellen</string>
+		<string name="append_date_btn">Datum anhängen</string>
+		<string name="backup_name_exists">Eine Sicherung mit diesem Namen existiert bereits!</string>
+		<string name="encrypt_backup">Sicherung verschlüsseln?</string>
+		<string name="enter_pass">Passwort eingeben:</string>
+		<string name="enter_pass2">Passwort erneut eingeben:</string>
+		<string name="pass_not_match">Die Passwörter stimmen nicht überein!</string>
+		<string name="partitions">Partitionen:</string>
+		<string name="disabled">Deaktiviert</string>
+		<string name="enabled">Aktiviert</string>
+		<string name="backup_name_hdr">Sicherung benennen</string>
+		<string name="progress">Fortschritt:</string>
+		<string name="backup_complete">Sicherung abgeschlossen</string>
+		<string name="restore_hdr">Wiederherstellen</string>
+		<string name="sel_backup_hdr">Sicherung auswählen</string>
+		<string name="restore_sel_store_hdr">Speicher: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Wiederherzustellende Sicherung:</string>
+		<string name="restore_enc_backup_hdr">Verschlüsselte Sicherung</string>
+		<string name="restore_dec_fail">Passwort falsch, bitte erneut versuchen!</string>
+		<string name="del_backup_btn">Sicherung löschen</string>
+		<string name="del_backup_confirm">Sicherung löschen?</string>
+		<string name="del_backup_confirm2">Dies kann nicht rückgängig gemacht werden!</string>
+		<string name="deleting_backup">Sicherung wird gelöscht...</string>
+		<string name="backup_deleted">Sicherung gelöscht</string>
+		<string name="swipe_delete">Löschen bestätigen</string>
+		<string name="swipe_delete_s">Löschen</string>
+		<string name="restore_try_decrypt">Verschlüsselte Sicherung - Versuche Entschlüsselung</string>
+		<string name="restore_try_decrypt_s">Versuche Entschlüsselung</string>
+		<string name="restore_backup_date">Sicherung am %tw_restore_file_date% erstellt</string>
+		<string name="restore_sel_part">Wiederherzustellende Partitionen:</string>
+		<string name="restore_enable_digest_chk" version="2">Digest-Prüfung von Sicherungs-Dateien aktivieren</string>
+		<string name="restore_complete">Wiederherstellen abgeschlossen</string>
+		<string name="swipe_restore">Sicherung wiederherstellen</string>
+		<string name="swipe_restore_s">Wiederherstellen</string>
+		<string name="rename_backup_hdr">Sicherung umbenennen</string>
+		<string name="rename_backup_confirm">Sicherung umbenennen?</string>
+		<string name="rename_backup_confirm2">Dies kann nicht rückgängig gemacht werden!</string>
+		<string name="renaming_backup">Sicherung wird umbenannt...</string>
+		<string name="rename_backup_complete">Sicherung umbenannt</string>
+		<string name="swipe_to_rename">Name ändern</string>
+		<string name="swipe_rename">Umbenennen</string>
+		<string name="confirm_hdr">Bestätigen</string>
+		<string name="mount_hdr">Einhängen</string>
+		<string name="mount_sel_part">Einzuhängende Partitionen:</string>
+		<string name="mount_sys_ro_chk">System-Partition schreibgeschützt einhängen</string>
+		<string name="mount_sys_ro_s_chk">System nur lesend</string>
+		<string name="decrypt_data_btn">Daten-Partition entschlüsseln</string>
+		<string name="disable_mtp_btn">MTP deaktivieren</string>
+		<string name="enable_mtp_btn">MTP aktivieren</string>
+		<string name="mount_usb_storage_btn">USB-Speicher einhängen</string>
+		<string name="usb_storage_hdr">USB-Speicher</string>
+		<string name="usb_stor_mnt1">USB-Speicher eingehängt</string>
+		<string name="usb_stor_mnt2">Funktion "USB-Speicher auswerfen" auf Computer</string>
+		<string name="usb_stor_mnt3">ausführen, bevor die Verbindung getrennt wird!</string>
+		<string name="unmount_btn">Auswerfen</string>
+		<string name="rb_system_btn">System</string>
+		<string name="rb_poweroff_btn">Ausschalten</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Download</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Ausschalten...</string>
+		<string name="swipe_power_off">Gerät ausschalten</string>
+		<string name="swipe_power_off_s">Ausschalten</string>
+		<string name="sys_ro_hdr">Unveränderte System-Partition</string>
+		<string name="sys_ro_keep">System-Partition schreibgeschützt einhängen?</string>
+		<string name="sys_rop1">TWRP kann die System-Partition unverändert lassen,</string>
+		<string name="sys_rop2">um das Aufspielen offizieller Updates zu erleichtern.</string>
+		<string name="sys_rop3">Jedoch kann dann das Hersteller-ROM TWRP wieder</string>
+		<string name="sys_rop4">überschreiben und TWRP kann keinen Root-Zugriff einrichten.</string>
+		<string name="sys_rop5">Das Installieren von ZIPs oder ADB-Vorgänge könnten jedoch</string>
+		<string name="sys_rop6">trotzdem die System-Partition verändern.</string>
+		<string name="sys_rol1">TWRP kann die System-Partition unverändert lassen, um das Aufspielen offizieller Updates zu erleichtern.</string>
+		<string name="sys_rol2">Jedoch kann dann das Hersteller-ROM TWRP wieder überschreiben und TWRP kann keinen Root-Zugriff einrichten.</string>
+		<string name="sys_rol3">Das Installieren von ZIPs oder ADB-Vorgänge könnten jedoch trotzdem die System-Partition verändern.</string>
+		<string name="sys_ro_never_show_chk">Diesen Bildschirm nicht mehr anzeigen</string>
+		<string name="sys_ro_keep_ro_btn">Nur-Lesend belassen</string>
+		<string name="swipe_allow_mod">Veränderungen zulassen</string>
+		<string name="swipe_allow_mod_s">Zulassen</string>
+		<string name="settings_hdr">Einstellungen</string>
+		<string name="settings_gen_hdr">Allgemein</string>
+		<string name="settings_gen_s_hdr">Allgemein</string>
+		<string name="settings_gen_btn">Allgemein</string>
+		<string name="use_rmrf_chk">Verwende rm -rf anstatt zu formatieren</string>
+		<string name="use24clock_chk">24-Stunden-Format</string>
+		<string name="rev_navbar_chk">Navigationsleiste umkehren</string>
+		<string name="simact_chk">Benutzeraktionen für Design-Test simulieren</string>
+		<string name="simfail_chk">Fehlschlag von Benutzeraktionen simulieren</string>
+		<string name="ctr_navbar_rdo">Navigationsleisten-Schaltflächen zentrieren</string>
+		<string name="lft_navbar_rdo">Navigationsleisten-Schaltflächen linksbündig</string>
+		<string name="rht_navbar_rdo">Navigationsleisten-Schaltflächen rechtsbündig</string>
+		<string name="restore_defaults_btn">Standard herstellen</string>
+		<string name="settings_tz_btn">Zeitzone</string>
+		<string name="settings_screen_btn">Bildschirm</string>
+		<string name="settings_screen_bright_btn">Helligkeit</string>
+		<string name="vibration_disabled">Vibration ist auf diesen Gerät ausgeschalten</string>
+		<string name="settings_vibration_btn">Vibration</string>
+		<string name="settings_language_btn">Sprache</string>
+		<string name="time_zone_hdr">Zeitzone</string>
+		<string name="sel_tz_list">Zeitzone auswählen:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) Samoa, Midwayinseln</string>
+		<string name="utcm10">(UTC -10) Hawaii</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm8">(UTC -8) Pacific Time</string>
+		<string name="utcm7">(UTC -7) Mountain Time (USA)</string>
+		<string name="utcm6">(UTC -6) Central Time (USA)</string>
+		<string name="utcm5">(UTC -5) Eastern Time (USA)</string>
+		<string name="utcm4">(UTC -4) Atlantic Time (USA)</string>
+		<string name="utcm3">(UTC -3) Brasilien, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Mittelatlantik</string>
+		<string name="utcm1">(UTC -1) Azores, Cape Verde</string>
+		<string name="utc0">(UTC 0) London, Dublin, Lissabon</string>
+		<string name="utcp1">(UTC +1) Berlin, Brüssel, Paris</string>
+		<string name="utcp2">(UTC +2) Athen, Istanbul, Südafrika</string>
+		<string name="utcp3">(UTC +3) Moskau, Bagdad</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tiflis, Muscat</string>
+		<string name="utcp5">(UTC +5) Ekaterinburg, Islamabad</string>
+		<string name="utcp6">(UTC +6) Almaty, Dhaka, Colombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp8">(UTC +8) Peking, Singapur, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokio, Seoul, Yakutsk</string>
+		<string name="utcp10">(UTC +10) Ostaustralien, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Salomonen</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fidschi</string>
+		<string name="sel_tz_offset">Zeitverschiebung: %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Keine</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Sommerzeit verwenden</string>
+		<string name="curr_tz">Aktuelle Zeitzone: %tw_time_zone%</string>
+		<string name="curr_tz_s">Aktuelle Zeitzone:</string>
+		<string name="set_tz_btn">Zeitzone einstellen</string>
+		<string name="settings_screen_hdr">Bildschirm</string>
+		<string name="settings_screen_timeout_hdr">Bildschirm-Timeout</string>
+		<string name="enable_timeout_chk">Bildschirm automatisch ausschalten</string>
+		<string name="screen_to_slider">Zeit bis zum Ausschalten (in Sekunden):</string>
+		<string name="screen_to_slider_s">Ausschalten nach %tw_screen_timeout_secs% Sekunden (0 = deaktiviert): </string>
+		<string name="screen_to_na">Bildschirm-Abschaltung nicht verfügbar</string>
+		<string name="screen_bright_slider">Helligkeit: %tw_brightness_pct% %</string>
+		<string name="screen_bright_na">Helligkeit nicht verfügbar</string>
+		<string name="vibration_hdr">Vibration</string>
+		<string name="button_vibration_hdr">Tasten-Vibration</string>
+		<string name="kb_vibration_hdr">Tastatur-Vibration</string>
+		<string name="act_vibration_hdr">Aktion-Vibration</string>
+		<string name="button_vibration">Tasten-Vibration:</string>
+		<string name="kb_vibration">Tastatur-Vibration:</string>
+		<string name="act_vibration">Aktion-Vibration:</string>
+		<string name="select_language">Sprache auswählen:</string>
+		<string name="sel_lang_btn">Sprache auswählen</string>
+		<string name="set_language_btn">Sprache einstellen</string>
+		<string name="advanced_hdr">Erweitert</string>
+		<string name="copy_log_confirm">Log auf SD-Karte kopieren?</string>
+		<string name="copying_log">Kopiere Log auf SD-Karte...</string>
+		<string name="copy_log_complete">Log erfolgreich kopiert</string>
+		<string name="fix_context_btn">SELinux Kontexte</string>
+		<string name="part_sd_btn">SD-Karte partitionieren</string>
+		<string name="part_sd_s_btn">SD-Karte</string>
+		<string name="file_manager_btn">Dateimanager</string>
+		<string name="language_hdr">Sprache</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="reload_theme_btn">Theme neu laden</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">TWRP injizieren</string>
+		<string name="inject_twrp_confirm">TWRP wieder injizieren?</string>
+		<string name="injecting_twrp">TWRP wird wieder injiziert...</string>
+		<string name="inject_twrp_complete">TWRP-Injektion abgeschlossen</string>
+		<string name="swipe_to_confirm">Aktion bestätigen</string>
+		<string name="part_sd_hdr">SD-Karte partitionieren</string>
+		<string name="invalid_partsd_sel">Es muss ein Wechseldatenträger ausgewählt sein!</string>
+		<string name="part_sd_lose">Es werden alle Dateien von der SD-Karte gelöscht!</string>
+		<string name="part_sd_undo">Diese Aktion kann nicht rückgängig gemacht werden!</string>
+		<string name="part_sd_ext_sz">EXT Grösse:</string>
+		<string name="part_sd_swap_sz">Grösse der Auslagerungsdatei:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Dateisystem:</string>
+		<string name="swipe_part_sd">SD-Karte partitionieren</string>
+		<string name="swipe_part_sd_s">Partitionieren</string>
+		<string name="partitioning_sd">Partitioniere SD-Karte...</string>
+		<string name="partitioning_sd2">Dies wird einige Minuten dauern.</string>
+		<string name="part_sd_complete">Partitionierung abgeschlossen</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Orginale Boot-Partition wiederherstellen</string>
+		<string name="dumlock_restore_confirm">Orginales Image der Boot-Partition wiederherstellen?</string>
+		<string name="dumlock_restoring">Stelle originale Boot-Partition wieder her...</string>
+		<string name="dumlock_restore_complete">Wiederherstellen der orginalen Boot-Partition abgeschlossen</string>
+		<string name="dumlock_reflash_btn">Recovery erneut einspielen</string>
+		<string name="dumlock_reflash_confirm">Recovery in Boot-Partition einspielen?</string>
+		<string name="dumlock_reflashing">Spiele Recovery in Boot-Partition ein...</string>
+		<string name="dumlock_reflash_complete">Einspielen der Recovery in die Boot-Partition abgeschlossen</string>
+		<string name="dumlock_install_btn">HTC Dumlock installieren</string>
+		<string name="dumlock_install_confirm">HTC Dumlock Dateien ins ROM installieren?</string>
+		<string name="dumlock_installing">Installiere HTC Dumlock...</string>
+		<string name="dumlock_install_complete">HTC Dumlock Installation abgeschlossen</string>
+		<string name="swipe_to_unlock">Bildschirm entsperren</string>
+		<string name="swipe_unlock">Entsperren</string>
+		<string name="fm_hdr">Dateimanager</string>
+		<string name="fm_sel_file">Datei oder Ordner auswählen</string>
+		<string name="fm_type_folder">Ordner</string>
+		<string name="fm_type_file">Datei</string>
+		<string name="fm_choose_act">Aktion auswählen</string>
+		<string name="fm_selected">%tw_fm_type%:</string>
+		<string name="fm_copy_btn">Kopieren</string>
+		<string name="fm_copy_file_btn">Datei kopieren</string>
+		<string name="fm_copy_folder_btn">Ordner kopieren</string>
+		<string name="fm_copying">Kopiere</string>
+		<string name="fm_move_btn">Verschieben</string>
+		<string name="fm_moving">Verschiebe</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Löschen</string>
+		<string name="fm_deleting">Wird gelöscht</string>
+		<string name="fm_rename_btn">Umbenennen</string>
+		<string name="fm_rename_file_btn">Datei umbennen</string>
+		<string name="fm_rename_folder_btn">Ordner umbenennen</string>
+		<string name="fm_renaming">Umbenennen</string>
+		<string name="fm_sel_dest">Zielordner auswählen</string>
+		<string name="fm_sel_curr_folder">Aktuellen Ordner auswählen</string>
+		<string name="fm_rename_hdr">Umbenennen</string>
+		<string name="fm_set_perms_hdr">Berechtigungen setzen</string>
+		<string name="fm_perms">Berechtigungen:</string>
+		<string name="fm_complete">Dateivorgang beendet</string>
+		<string name="decrypt_data_hdr">Daten-Partition entschlüsseln</string>
+		<string name="decrypt_data_enter_pass">Passwort eingeben:</string>
+		<string name="decrypt_data_failed">Passwort falsch, bitte erneut versuchen!</string>
+		<string name="decrypt_data_failed_pattern">Muster falsch, bitte erneut versuchen!</string>
+		<string name="decrypt_data_enter_pattern">Muster eingeben.</string>
+		<string name="decrypt_data_trying">Versuche Entschlüsselung</string>
+		<string name="decrypt_data_vold_os_missing">Fehlende Dateien für vold Entschlüsselung werden benötigt: {1}</string>
+		<string name="term_hdr">Terminal</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">BEENDEN</string>
+		<string name="term_sel_folder_hdr">Zum Stamm-Ordner navigieren</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Dalvik Cache löschen</string>
+		<string name="sideload_wipe_cache_chk">Cache löschen</string>
+		<string name="swipe_to_sideload">ADB Sideload starten</string>
+		<string name="swipe_sideload">Start</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Syntax: adb sideload dateiname.zip</string>
+		<string name="sideload_complete">ADB Sideload abgeschlossen</string>
+		<string name="fix_contexts_hdr">SELinux Kontexte</string>
+		<string name="fix_contexts_note1">Das Korrigieren der Kontexte wird selten benötigt!</string>
+		<string name="fix_contexts_note2">Das Korrigieren der SELinux-Kontexte kann</string>
+		<string name="fix_contexts_note3">Startprobleme verursachen.</string>
+		<string name="swipe_to_fix_contexts">SELinux-Kontexte korrigieren</string>
+		<string name="swipe_fix_contexts">Korrigieren</string>
+		<string name="fixing_contexts">Korrigiere Kontexte...</string>
+		<string name="fix_contexts_complete">Korrektur der Kontexte abgeschlossen</string>
+		<string name="reboot_hdr">Neustart</string>
+		<string name="install_cancel">Nicht installieren</string>
+		<string name="sel_storage_list">Speicher auswählen</string>
+		<string name="ok_btn">OK</string>
+		<string name="install_twrp_ramdisk">Installiere Recovery Ramdisk</string>
+		<string name="install_kernel">Kernel installieren</string>
+		<string name="repack_kernel_confirm_hdr">Kernel installieren</string>
+		<string name="repack_ramdisk_confirm_hdr">Recovery installieren</string>
+		<string name="repack_kernel_confirm">Kernel installieren?</string>
+		<string name="repack_ramdisk_confirm">Recovery installieren?</string>
+		<string name="repack_backup_first">Sichern des existierenden Images zu Beginn</string>
+		<string name="repack">Packen</string>
+		<string name="swipe_to_install">Installieren</string>
+		<string name="installing">Installiere...</string>
+		<string name="install_complete">Installation abgeschlossen</string>
+		<string name="unpack_error">Fehler beim Entpacken des Images.</string>
+		<string name="repack_error">Fehler beim Packen des Images.</string>
+		<string name="unpacking_image">Entpacke {1}...</string>
+		<string name="repacking_image">Packen {1}...</string>
+		<string name="repack_image_hdr">Wähle ein Image</string>
+		<string name="fix_recovery_loop">Recovery Bootloop reparieren</string>
+		<string name="fix_recovery_loop_confirm">Recovery Bootloop reparieren?</string>
+		<string name="fixing_recovery_loop">Recovery Bootloop wird repariert...</string>
+		<string name="fix_recovery_loop_complete">Recovery Bootloop Reparatur abgeschlossen</string>
+		<string name="fixing_recovery_loop_patch">Patche Kernel...</string>
+		<string name="fix_recovery_loop_patch_error">Fehler beim Patchen des Kernels.</string>
+		<string name="decrypt_users">Benutzer entschlüsseln</string>
+		<string name="decrypt_users_selection">Wähle eine Benutzer ID zum entschlüsseln</string>
+		<string name="select_user">Benutzer wählen</string>
+		<string name="backup_storage_undecrypt_warning">Das Backup enthält manche Dateien des Benutzers {1} nicht, da dieser nicht verschlüsselt ist</string>
+		<string name="decrypting_user_fbe">Entschlüsselung des FBE von Benutzer {1}...</string>
+		<string name="decrypt_user_success_fbe">Entschlüsselung des Benutzers {1} erfolgreich</string>
+		<string name="decrypt_user_fail_fbe">Entschlüsselung des Benutzers {1} fehlgeschlagen</string>
+		<string name="decrypt_data_enter_pass_fbe">Passwort für Benutzer [%tw_crypto_user_id%] eingeben</string>
+		<string name="decrypt_data_enter_pattern_fbe">Muster für Benutzer [%tw_crypto_user_id%] eingeben</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">Dieser Kernel hat keine Leseunterstützung für SELinux-Kontexte.</string>
+		<string name="full_selinux">Volle SELinux-Unterstützung ist vorhanden.</string>
+		<string name="no_selinux">Keine SELinux-Unterstützung vorhanden (kein libselinux).</string>
+		<string name="mtp_enabled">MTP aktiviert</string>
+		<string name="mtp_crash">MTP abgestürzt, MTP wird künftig während Bootvorgang nicht gestartet.</string>
+		<string name="decrypt_success">Erfolgreich mit dem Standardpasswort entschlüsselt.</string>
+		<string name="unable_to_decrypt">Entschlüsselung mit Standardpasswort nicht möglich. Eventuell muss die Daten-Partition formatiert werden.</string>
+		<string name="generating_digest1" version="2">Generiere Digest</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * generiere Digest...</string>
+		<string name="digest_created" version="2"> * Digest erstellt.</string>
+		<string name="digest_error" version="2"> * Digest-Fehler!</string>
+		<string name="digest_compute_error" version="2"> * Fehler bei Digest-Berechnung.</string>
+		<string name="current_date">(Aktuelles Datum)</string>
+		<string name="auto_generate">(Automatisch generiert)</string>
+		<string name="unable_to_locate_partition">Kann '{1}' Partition für Sicherungsberechnungen nicht finden.</string>
+		<string name="no_partition_selected">Keine Partitionen für die Sicherung ausgewählt.</string>
+		<string name="total_partitions_backup"> * Anzahl der zu sichernden Partitionen: {1}</string>
+		<string name="total_backup_size"> * Gesamtgröße aller Daten: {1}MB</string>
+		<string name="available_space"> * Verfügbare Speicherplatz: {1}MB</string>
+		<string name="unable_locate_storage">Kann Sicherungsgerät nicht finden.</string>
+		<string name="no_space">Zu wenig freier Speicherplatz.</string>
+		<string name="backup_started">[SICHERUNG GESTARTET]</string>
+		<string name="backup_folder"> * Ordner für Sicherung: {1}</string>
+		<string name="fail_backup_folder">Ordner für Sicherung konnte nicht erstellt werden.</string>
+		<string name="avg_backup_fs">Durchschnittliche Sicherungsgeschwindigkeit für Dateisysteme: {1} MB/sek</string>
+		<string name="avg_backup_img">Durchschnittliche Sicherungsgeschwindigkeit für abgebildete Laufwerke: {1} MB/sek</string>
+		<string name="total_backed_size">[INSGESAMT {1} MB GESICHERT]</string>
+		<string name="backup_completed">[SICHERUNG ABGESCHLOSSEN IN {1} SEKUNDEN]</string>
+		<string name="restore_started">[WIEDERHERSTELLEN GESTARTET]</string>
+		<string name="restore_folder">Wiederherstellen aus Ordner: '{1}'</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} fertiggestellt ({2} Sekunden)]</string>
+		<string name="verifying_digest" version="2">Überprüfe Digest</string>
+		<string name="skip_digest" version="2">Überspringe Digest-Prüfung aufgrund Benutzereinstellungen.</string>
+		<string name="calc_restore">Berechne Wiederherstellungsinformationen...</string>
+		<string name="restore_read_only">Kann {1} nicht wiederherstellen -- Partition schreibgeschützt.</string>
+		<string name="restore_unable_locate">Kann wiederherzustellende '{1}'-Partition nicht finden.</string>
+		<string name="no_part_restore">Keine Partitionen zur Wiederherstellung ausgewählt.</string>
+		<string name="restore_part_count">{1} Partitionen wiederherstellen...</string>
+		<string name="total_restore_size">Insgesamt wiederherzustellen: {1}MB</string>
+		<string name="updating_system_details">Aktualisieren der System-Details</string>
+		<string name="restore_completed">[WIEDERHERSTELLEN ABGESCHLOSSEN IN {1} SEKUNDEN]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">Fehler beim Öffnen: '{1}' ({2})</string>
+		<string name="unable_locate_part_backup_name">Partition nicht anhand des Sicherungsnamens ermittelbar: '{1}'</string>
+		<string name="unable_find_part_path">Partition für Pfad '{1}' nicht gefunden</string>
+		<string name="update_part_details">Partitions-Informationen werden aktualisiert...</string>
+		<string name="update_part_details_done">...Fertig</string>
+		<string name="wiping_dalvik">Dalvik Cache-Verzeichnisse bereinigen...</string>
+		<string name="cleaned">Bereinigt: {1}...</string>
+		<string name="cache_dalvik_done">-- Dalvik Cache-Verzeichnisse bereinigt!</string>
+		<string name="dalvik_done">-- Dalvik-Verzeichnis bereinigt!</string>
+		<string name="no_andsec">Keine "Android Secure"-Partitionen gefunden.</string>
+		<string name="unable_to_locate">{1} nicht gefunden.</string>
+		<string name="wiping_datamedia">Lösche internen Speicher -- /data/media...</string>
+		<string name="unable_to_mount">Kann {1} nicht einhängen</string>
+		<string name="unable_to_mount_internal">Kann internen Speicher nicht einhängen</string>
+		<string name="unable_to_mount_storage">Kann Speicher nicht einhängen</string>
+		<string name="fail_decrypt">Entschlüsselung der Daten-Partition fehlgeschlagen.</string>
+		<string name="no_crypto_support">In diese Version wurde keine Krypto-Unterstützung eingebunden.</string>
+		<string name="decrypt_success_dev">Erfolgreich entschlüsselt, neues Blockgerät: '{1}'</string>
+		<string name="decrypt_success_nodev">Erfolgreich entschlüsseltd</string>
+		<string name="done">Abgeschlossen.</string>
+		<string name="start_partition_sd">Partitioniere SD-Karte...</string>
+		<string name="partition_sd_locate">Zu partitionierendes Gerät nicht gefunden.</string>
+		<string name="ext_swap_size">EXT + Auslagerung ist grösser als die SD-Karte.</string>
+		<string name="remove_part_table">Entferne Partitionstabelle...</string>
+		<string name="unable_rm_part">Partitionstabelle kann nicht entfernt werden.</string>
+		<string name="create_part">Erstelle {1}-Partition...</string>
+		<string name="unable_to_create_part">{1}-Partition kann nicht erstellt werden.</string>
+		<string name="format_sdext_as">Formatiere sd-ext als {1}...</string>
+		<string name="part_complete">Partitionierung abgeschlossen.</string>
+		<string name="unable_to_open">'{1}' kann nicht geöffnet werden.</string>
+		<string name="mtp_already_enabled">MTP bereits aktiviert</string>
+		<string name="mtp_fail">Fehler beim Aktivieren von MTP</string>
+		<string name="no_mtp">MTP-Unterstützung nicht integriert</string>
+		<string name="image_flash_start">[IMAGE-DATEI WIRD AUFGESPIELT]</string>
+		<string name="img_to_flash">Aufzuspielendes Image: '{1}'</string>
+		<string name="flash_unable_locate">'{1}'-Partition wurde nicht gefunden.</string>
+		<string name="no_part_flash">Keine Partitionen ausgewählt.</string>
+		<string name="too_many_flash">Zu viele Partitionen ausgewählt.</string>
+		<string name="invalid_flash">Ungültige Partitions-Auswahl.</string>
+		<string name="flash_done">[IMAGE EINSPIELEN ABGESCHLOSSEN]</string>
+		<string name="wiping">Lösche {1}</string>
+		<string name="repair_not_exist">{1} existiert nicht! Reparieren nicht möglich!</string>
+		<string name="repairing_using">Reparatur von {1} mit {2}...</string>
+		<string name="unable_repair">{1} kann nicht repariert werden.</string>
+		<string name="mount_data_footer">/data konnte nicht eingehängt werden und Krypto-Signatur wurde nicht gefunden.</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">Ordner '{1}' konnte nicht angelegt werden ({2}).</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">'{1}' konnte nicht eingehängt werden ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">'{1}' konnte nicht eingehängt werden ({2})</string>
+		<string name="cannot_resize">Größe von {1} kann nicht geändert werden.</string>
+		<string name="repair_resize">{1} wird repariert. Danach wird die Größe geändert.</string>
+		<string name="unable_resize">Größe von {1} kann nicht geändert werden.</string>
+		<string name="no_digest_found" version="2">Keine Digest-Datei für '{1}' gefunden. Bitte Digest-Prüfung für Wiederherstellung deaktivieren.</string>
+		<string name="digest_fail_match" version="2">Digest-Prüfung für '{1}' fehlgeschlagen.</string>
+		<string name="digest_matched" version="2">Digest matched for '{1}'.</string>
+		<string name="fail_decrypt_tar">TAR-Datei '{1}' konnte nicht entschlüsselt werden.</string>
+		<string name="format_data_msg">Ein Neustart von TWRP kann notwendig sein, damit /data wieder verwendet werden kann.</string>
+		<string name="format_data_err">Formatierung zum Entfernen der Verschlüsselung kann nicht durchgeführt werden.</string>
+		<string name="formatting_using">Formatiere {1} mit {2}...</string>
+		<string name="unable_to_wipe">{1} kann nicht gelöscht werden.</string>
+		<string name="cannot_wipe">Partition {1} kann nicht gelöscht werden.</string>
+		<string name="remove_all">Entferne alle Dateien unter '{1}'</string>
+		<string name="wiping_data">Lösche Daten, aber verschone internen Speicher...</string>
+		<string name="backing_up">Sichere {1}...</string>
+		<string name="backup_storage_warning">Backups of {1} do not include any files in internal storage such as pictures or downloads.</string>
+		<string name="backing">Sichere</string>
+		<string name="backup_size">Sicherung von '{1}' hat 0 Byte.</string>
+		<string name="datamedia_fs_restore">WARNUNG: Diese Sicherung wurde mit dem Dateisystem {1} erstellt! Es kann sein, dass wieder zu {1} gewechselt werden muss, damit das Gerät nach der Wiederherstellung auch startet.</string>
+		<string name="restoring">Wiederherstellung läuft</string>
+		<string name="restoring_hdr">Wiederherstellung läuft</string>
+		<string name="recreate_folder_err">Ordner {1} kann nicht wiederhergestellt werden.</string>
+		<string name="img_size_err">Image ist zu groß für das Gerät</string>
+		<string name="flashing">Einspielen von {1}...</string>
+		<string name="backup_folder_set"> * Ordner für Sicherung: {1}</string>
+		<string name="locate_backup_err">Sicherung '{1}' nicht gefunden</string>
+		<string name="set_restore_opt">Setze Wiederherstellungs-Optionen: '{1}':</string>
+		<string name="digest_check_skip" version="2">Digest-Prüfung ist deaktiviert</string>
+		<string name="ors_encrypt_restore_err">Eine verschlüsselte Sicherung kann nicht per OpenRecoveryScript wiederhergestellt werden.</string>
+		<string name="mounting">Einhängen</string>
+		<string name="unmounting">Auswerfen</string>
+		<string name="mounted">'{1}' eingehängt</string>
+		<string name="unmounted">'{1}' ausgeworfen</string>
+		<string name="setting">Setze '{1}' auf '{2}'</string>
+		<string name="setting_empty">Setze '{1}' auf leer</string>
+		<string name="making_dir1">Erstelle Verzeichnis</string>
+		<string name="making_dir2">Erstelle Verzeichnis: '{1}'</string>
+		<string name="running_command">Führe Befehl aus</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">Starte ADB Sideload...</string>
+		<string name="need_new_adb">Für dieses Gerät wird ADB 1.0.32 oder neuer benötigt.</string>
+		<string name="no_pwd">Kein Passwort angegeben.</string>
+		<string name="done_ors">Skript wurde verarbeitet</string>
+		<string name="injecttwrp">Injiziere TWRP in das Boot-Image...</string>
+		<string name="zip_err">Fehler beim Installieren von ZIP '{1}'</string>
+		<string name="installing_zip">Installiere ZIP '{1}'</string>
+		<string name="select_backup_opt">Setze Sicherungs-Optionen:</string>
+		<string name="compression_on">Komprimierung ist aktiviert</string>
+		<string name="digest_off" version="2">Digest-Generierung ist deaktiviert</string>
+		<string name="backup_fail">Sicherung fehlgeschlagen</string>
+		<string name="backup_clean">Sicherung fehlgeschlagen, bereinige Sicherungs-Verzeichnis</string>
+		<string name="running_recovery_commands">Führe Recovery-Befehle aus</string>
+		<string name="recovery_commands_complete">Recovery-Befehle ausgeführt</string>
+		<string name="running_ors">Führe OpenRecoveryScript aus</string>
+		<string name="ors_complete">OpenRecoveryScript ausgeführt</string>
+		<string name="invalid_zip_format">Invalides ZIP-Datei Format!</string>
+		<string name="check_for_digest" version="2">Suche nach Digest-Datei...</string>
+		<string name="fail_sysmap">'{1}' kann nicht zugeordnet werden</string>
+		<string name="verify_zip_sig">Überprüfe ZIP-Signatur...</string>
+		<string name="verify_zip_fail">Prüfung der ZIP-Signatur fehlgeschlagen!</string>
+		<string name="verify_zip_done">Prüfung der ZIP-Signatur erfolgreich.</string>
+		<string name="zip_corrupt">ZIP-Datei ist beschädigt!</string>
+		<string name="no_digest" version="2">Digest-Prüfung übersprungen: keine Digest-Datei gefunden</string>
+		<string name="digest_fail" version="2">Digest stimmt nicht überein</string>
+		<string name="digest_match" version="2">Digest stimmt überein</string>
+		<string name="pid_signal">Prozess {1} endete mit Meldung: {2}</string>
+		<string name="pid_error">Prozess {1} endete mit FEHLER: {2}</string>
+		<string name="install_dumlock">Installiere HTC Dumlock in System-Partition...</string>
+		<string name="dumlock_restore">Stelle originale Boot-Partition wieder her...</string>
+		<string name="dumlock_reflash">Recovery auf Boot-Partition einspielen...</string>
+		<string name="run_script">Führe {1}-Skript aus...</string>
+		<string name="rename_stock">Hersteller-Recovery in "/system" wurde umbenannt, damit das Hersteller-ROM TWRP nicht überschreibt.</string>
+		<string name="split_backup">Sicherungs-Datei wird in mehrere Archive aufgeteilt...</string>
+		<string name="backup_error">Fehler beim Erstellen der Sicherung.</string>
+		<string name="restore_error">Fehler während der Wiederherstellung.</string>
+		<string name="split_thread">Teile Thread-ID {1} in Archiv {2}</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">%llu von %llu Dateien, %i%%</string>
+		<string name="size_progress">%lluMB von %lluMB, %i%%</string>
+		<string name="decrypt_cmd">Versuche die Daten-Partition per Kommandozeile zu entschlüsseln.</string>
+		<string name="base_pkg_err">Basis-Pakete konnten nicht geladen werden.</string>
+		<string name="simulating">Simuliere Aktionen...</string>
+		<string name="backup_cancel">Sicherung abgebrochen</string>
+		<string name="config_twrp">Konfiguriere TWRP...</string>
+		<string name="config_twrp_err">Konfigurieren von TWRP mit diesem Kernel nicht möglich.</string>
+		<string name="copy_log">Recovery-Log wurde nach {1} kopiert.</string>
+		<string name="max_queue">Maximale Anzahl an ZIP-Dateien erreicht!</string>
+		<string name="min_queue">Minimale Anzahl an ZIP-Dateien erreicht!</string>
+		<string name="screenshot_saved">Bildschirmfoto gespeichert unter {1}</string>
+		<string name="screenshot_err">Bildschirmfoto konnte nicht erstellt werden!</string>
+		<string name="zip_wipe_cache">Eine oder mehrere ZIP-Dateien wollen den Cache löschen -- Lösche Cache jetzt.</string>
+		<string name="and_sec_wipe_err">Android Secure kann nicht gelöscht werden</string>
+		<string name="dalvik_wipe_err">Löschen von Dalvik fehlgeschlagen</string>
+		<string name="auto_gen">(automatisch erstellt)</string>
+		<string name="curr_date">(aktuelles Datum)</string>
+		<string name="backup_name_len">Der Name der Sicherung ist zu lang.</string>
+		<string name="backup_name_invalid">Sicherungs-Name '{1}' enthält ungültige Zeichen: '{1}'</string>
+		<string name="no_real_sdcard">Dieses Gerät verwendet keine SD-Karte! Abbruch!</string>
+		<string name="cancel_sideload">ADB Sideload wird abgebrochen...</string>
+		<string name="change_fs_err">Fehler beim Ändern des Dateisystems.</string>
+		<string name="theme_ver_err">Theme-Version inkompatibel zu TWRP-Version. Standard-Theme wird verwendet.</string>
+		<string name="up_a_level">(Übergeordneter Ordner)</string>
+		<string name="install_reboot"version="2">Neustart in %tw_sleep% Sekunden</string>
+		<string name="adbbackup_error">Fehler während ADB Backup. Abbruch..."</string>
+		<string name="adbbackup_control_error">Kein Zugriff auf ADB Control Channel</string>
+		<string name="twrp_adbbu_option">--twrp option wird für ADB Backup benötigt</string>
+		<string name="partition_not_found">Pfad: {1} wurde nicht in Partitionsliste gefunden</string>
+		<string name="copy_kernel_log">Kernel-Log wurde nach {1} kopiert</string>
+		<string name="include_kernel_log">Zusätzlich Kernel-Log kopieren</string>
+		<string name="sha2_chk">SHA2 als Hash-Algorithmus verwenden</string>
+		<string name="unable_set_boot_slot">Fehler während Wechsel zu Slot {1}</string>
+		<string name="unmount_sys_install">Aushängen der System-Partition vor der Installation einer ZIP-Datei.</string>
+		<string name="unmount_system">Aushängen der System-Partiton...</string>
+		<string name="unmount_system_err">Aushängen der System-Partition fehlgeschlagen</string>
+		<string name="flash_ab_inactive">Einspielen des A/B ZIPs auf den inaktiven Slot: {1}</string>
+		<string name="flash_ab_reboot">Um weitere ZIP-Dateien einzuspielen bitte das Recovery neustartet um auf den geänderten Slot zu wechseln.</string>
+		<string name="ozip_decrypt_decryption">Starte Ozip Entschlüsselung...</string>
+		<string name="ozip_decrypt_finish">Ozip Entschlüsselung abgeschlossen!</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/el.xml b/gui/theme/common/languages/el.xml
new file mode 100644
index 0000000..688299a
--- /dev/null
+++ b/gui/theme/common/languages/el.xml
@@ -0,0 +1,668 @@
+<?xml version="1.0"?>
+<language>
+    <display>Ελληνικά</display>
+    <resources>
+        <resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+        <resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+        <resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+        <resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+        <string name="system">Σύστημα</string>
+        <string name="system_image">Εικόνα Συστήματος</string>
+        <string name="vendor">Προμηθευτής</string>
+        <string name="vendor_image">Εικόνα Προμηθευτή</string>
+        <string name="boot">Boot</string>
+        <string name="recovery">Recovery</string>
+        <string name="cache">Μνήμη cache</string>
+        <string name="data">Data</string>
+        <string name="data_backup">Data (εκτός αποθ. χώρου)</string>
+        <string name="sdcard">Κάρτα SD</string>
+        <string name="internal">Εσωτερικός χώρος αποθήκευσης</string>
+        <string name="microsd">Κάρτα Micro SD</string>
+        <string name="usbotg">USB OTG</string>
+        <string name="android_secure">Android secure</string>
+        <string name="dalvik">Dalvik / ART Cache</string>
+        <string name="sdext">SD-EXT</string>
+        <string name="adopted_data">Εγκεκριμένα δεδομένα</string>
+        <string name="adopted_storage">Εγκεκριμένος Αποθ. Χώρος</string>
+        <string name="twrp_header">Team Win Recovery Project</string>
+        <string name="twrp_watch_header">TWRP %tw_version%</string>
+        <string name="cpu_temp">CPU: %tw_cpu_temp% &#xB0;C</string>
+        <string name="battery_pct">Μπαταρία: %tw_battery%</string>
+        <string name="sort_by_name">Ταξινόμηση κατά όνομα</string>
+        <string name="sort_by_date">Ταξινόμηση κατά ημερομηνία</string>
+        <string name="sort_by_size">Ταξινόμηση κατά μέγεθος</string>
+        <string name="sort_by_name_only">Όνομα</string>
+        <string name="sort_by_date_only">Ημ/νία</string>
+        <string name="sort_by_size_only">Μέγεθος</string>
+        <string name="tab_general">ΓΕΝΙΚΑ</string>
+        <string name="tab_options">ΡΥΘΜΙΣΕΙΣ</string>
+        <string name="tab_backup">ΑΝΤΙΓΡ. ΑΣΦΑΛ.</string>
+        <string name="tab_time_zone">ΖΩΝΗ ΩΡΑΣ</string>
+        <string name="tab_screen">ΟΘΟΝΗ</string>
+        <string name="tab_vibration">ΔΟΝΗΣΗ</string>
+        <string name="tab_language">ΓΛΩΣΣΑ</string>
+        <string name="install_btn">Εγκατάσταση</string>
+        <string name="wipe_btn">Εκκαθάριση</string>
+        <string name="backup_btn">Αντίγρ. ασφαλ.</string>
+        <string name="restore_btn">Επαναφορά</string>
+        <string name="mount_btn">Προσάρτηση</string>
+        <string name="settings_btn">Ρυθμίσεις</string>
+        <string name="advanced_btn">Προηγμένες</string>
+        <string name="reboot_btn">Επανεκκίνηση</string>
+        <string name="files_btn">Αρχεία</string>
+        <string name="copy_log_btn">Αντιγρ. αρχ. καταγραφής</string>
+        <string name="select_type_hdr">Επιλέξτε τύπο</string>
+        <string name="install_zip_hdr">Εγκατάσταση Zip</string>
+        <string name="install_zip_btn">Εγκατάσταση Zip</string>
+        <string name="install_image_hdr">Εγκατάσταση εικόνας</string>
+        <string name="install_image_btn">Εγκατάσταση εικόνας</string>
+        <string name="install_select_file_hdr">Επιλέξτε Αρχείο</string>
+        <string name="file_selector_folders_hdr">Φάκελοι</string>
+        <string name="select_file_from_storage">Επιλέξτε αρχείο από %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+        <string name="adb_sideload_btn">ADB Sideload</string>
+        <string name="install_hdr">Εγκατάσταση</string>
+        <string name="select_storage_hdr">Επιλέξτε Αποθ. χώρο</string>
+        <string name="select_storage_btn">Επιλέξτε Αποθ. χώρο</string>
+        <string name="queue_hdr">Ουρά</string>
+        <string name="zip_queue_count">%tw_zip_queue_count% το μέγιστο 10 αρχεία στην ουρά</string>
+        <string name="zip_queue_count_s">Αρχεία %tw_zip_queue_count% από 10:</string>
+        <string name="zip_warn1">Αυτή η λειτουργία μπορεί να εγκαταστήσει μη συμβατό</string>
+        <string name="zip_warn2">λογισμικό και να αχρηστεύσει τη συσκευή σας.</string>
+        <string name="zip_back_cancel">Πιέστε το πλήκτρο πίσω για να ακυρώσετε την προσθήκη του zip.</string>
+        <string name="zip_back_clear">Πατήστε το κουμπί πίσω για να καθαρίσετε την ουρά.</string>
+        <string name="folder">Φάκελος:</string>
+        <string name="file">Αρχείο:</string>
+        <string name="zip_sig_chk">Επαλήθευση της υπογραφής Zip</string>
+        <string name="inject_twrp_chk">Εισαγωγή TWRP μετά την εγκατάσταση</string>
+        <string name="install_reboot_chk">Επανεκκίνηση μετά την εγκατάσταση</string>
+        <string name="options_hdr">Ρυθμίσεις</string>
+        <string name="confirm_flash_hdr">Επιβεβαίωση Flash</string>
+        <string name="zip_queue">Ουρά:</string>
+        <string name="options">Ρυθμίσεις:</string>
+        <string name="swipe_confirm">   Επιβεβαιώστε</string>
+        <string name="zip_add_btn">Προσθ. περισσοτέρων zip</string>
+        <string name="zip_clear_btn">Εκκαθάριση ουράς Zip</string>
+        <string name="install_zip_count_hdr">Εγκαταστήστε Zip %tw_zip_index% από %tw_zip_queue_count%</string>
+        <string name="installing_zip_xml">Εγκατάσταση Zip: %tw_file%</string>
+        <string name="failed">Απέτυχε</string>
+        <string name="successful">Επιτυχής</string>
+        <string name="install_failed">Η εγκατάσταση απέτυχε</string>
+        <string name="install_successful">Επιτυχής εγκατάσταση</string>
+        <string name="wipe_cache_dalvik_btn">Εκκαθάριση cache/dalvik</string>
+        <string name="reboot_system_btn">Επανεκ. συστήματος</string>
+        <string name="install_sel_target">Επιλέξτε Διαμέρισμα</string>
+        <string name="flash_image_select">Επιλέξτε Διαμέρισμα για Flash εικόνας:</string>
+        <string name="target_partition">Διαμέρισμα:</string>
+        <string name="flashing_image">Εγγραφή εικόνας...</string>
+        <string name="image_flashed">Η εικόνα εγγράφηκε</string>
+        <string name="wipe_cache_dalvik_confirm">Εκκαθάριση Cache &amp; Dalvik;</string>
+        <string name="wiping_cache_dalvik">Εκκαθάριση Cache &amp; Dalvik...</string>
+        <string name="wipe_cache_dalvik_complete">Η Εκκαθάριση Cache &amp; Dalvik ολοκληρώθηκε</string>
+        <string name="swipe_wipe">Σύρετε για εκκαθάριση</string>
+        <string name="swipe_wipe_s">   Εκαθάριση</string>
+        <string name="no_os1">Κανένα λειτουργικό σύστημα εγκατεστημένο! Είστε</string>
+        <string name="no_osrb">σίγουροι ότι θέλετε να κάνετε επανεκκίνηση;</string>
+        <string name="no_ospo">σίγουροι ότι θέλετε να κάνετε απενεργοποίηση;</string>
+        <string name="rebooting">Επανεκκίνηση...</string>
+        <string name="swipe_reboot">Σύρετε για Επανεκκίνηση</string>
+        <string name="swipe_reboot_s">   Επανεκκίνηση</string>
+        <string name="reboot_install_app_hdr">Εγκατάσταση της εφαρμογής TWRP;</string>
+        <string name="reboot_install_app1">Θέλετε να εγκαταστήσετε την Επίσημη Εφαρμογή TWRP;</string>
+        <string name="reboot_install_app2">Η εφαρμογή μπορεί να ελέγχει για νεότερες εκδόσεις του TWRP.</string>
+        <string name="reboot_install_app_prompt_install">Προτροπή για εγκατάσταση της εφαρμογής TWRP εαν δεν είναι εγκατεστημένη</string>
+        <string name="reboot_install_app_system">Εγκατάσταση ως Εφαρμογή Συστήματος</string>
+        <string name="reboot_installing_app">Εγκατάσταση εφαρμογής...</string>
+        <string name="swipe_to_install_app">Σύρετε για εγκατάσταση του TWRP</string>
+        <string name="swipe_flash">Σύρετε για Εγκατάσταση</string>
+        <string name="confirm_action">Επιβεβαίωση ενέργειας</string>
+        <string name="back_cancel">Πατήστε το κουμπί πίσω για να ακυρώσετε.</string>
+        <string name="cancel_btn">Άκυρο</string>
+        <string name="wipe_hdr">Εκκαθάριση</string>
+        <string name="factory_reset_hdr">Επαναφορά εργοστασιακών ρυθμίσεων</string>
+        <string name="factory_reset_btn">Επαναφορά εργοστασιακών ρυθμίσεων</string>
+        <string name="factory_reset1">Εκκαθάριση Data, Cache και Dalvik</string>
+        <string name="factory_reset2">(εξαιρείται η εσωτερική αποθήκευση)</string>
+        <string name="factory_reset3">Στις περισσότερες περιπτώσεις αυτή είναι</string>
+        <string name="factory_reset4">η μόνη εκκαθάριση που χρειάζεστε.</string>
+        <string name="factory_resetting">Επαναφορά εργοστασιακών ρυθμίσεων...</string>
+        <string name="advanced_wipe_hdr">Σύνθετη Εκκαθάριση</string>
+        <string name="advanced_wipe_btn">Σύνθετη Εκκαθάριση</string>
+        <string name="wipe_enc_confirm">Εκκαθάριση κρυπτογράφηση από τα Δεδομένα;</string>
+        <string name="formatting_data">Διαμόρφωση Δεδομένων...</string>
+        <string name="swipe_format_data">Σύρετε για Διαμόρφ. Δεδομ.</string>
+        <string name="swipe_format_data_s">   Διαμόρφ Δεδομ.</string>
+        <string name="factory_reset_complete">Επαναφορά εργοστασιακών ρυθμίσεων ολοκληρώθηκε</string>
+        <string name="sel_part_hdr">Επιλέξτε Τομέα(είς)</string>
+        <string name="wipe_sel_confirm">Εκκαθάριση επιλεγμένου(ων) τομέα(ων);</string>
+        <string name="wiping_part">Εκκαθάριση Τομέα(ων)...</string>
+        <string name="wipe_complete">Η εκκαθάριση ολοκληρώθηκε</string>
+        <string name="sel_part_wipe">Επιλέξτε Τομείς για Εκκαθάριση:</string>
+        <string name="invalid_part_sel">Μη έγκυρη επιλογή Τομέα</string>
+        <string name="format_data_hdr">Διαμόρφωση Δεδομένων</string>
+        <string name="format_data_btn">Διαμόρφωση Δεδομένων</string>
+        <string name="format_data_ptr1">Η Διαμόρφωση Δεδομένων θα διαγράψει όλες τις εφαρμογές,</string>
+        <string name="format_data_ptr2">αντίγραφα ασφαλείας, φωτογραφίες, βίντεο, πολυμέσα και</string>
+        <string name="format_data_ptr3">αφαιρεί την κρυπτογράφηση στον εσωτερικό χώρο αποθήκευσης.</string>
+        <string name="format_data_adopted">Περιλαμβάνει Adopted Storage</string>
+        <string name="format_data_lcp1">Η Διαμόρφωση Δεδομένων θα διαγράψει όλες τις εφαρμογές, αντίγραφα ασφαλείας, φωτογραφίες, βίντεο, πολυμέσα και</string>
+        <string name="format_data_lcp2">αφαιρεί την κρυπτογράφηση στον εσωτερικό χώρο αποθήκευσης.</string>
+        <string name="format_data_wtc1">Η Διαμόρφωση Δεδομένων θα διαγράψει όλες τις εφαρμογές,</string>
+        <string name="format_data_wtc2">αντίγραφα ασφαλείας και πολυμέσα. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.</string>
+        <string name="format_data_undo">Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.</string>
+        <string name="format_data_complete">Η Διαμόρφωση Δεδομένων ολοκληρώθηκε</string>
+        <string name="yes_continue">Πληκτρολογήστε Yes για να συνεχίσετε.  Πιέστε το πλήκτρο πίσω για να ακυρώσετε.</string>
+        <string name="part_opt_hdr">Ρυθμίσεις Τομέα: %tw_partition_name%</string>
+        <string name="sel_act_hdr">Επιλέξτε ενέργεια</string>
+        <string name="file_sys_opt">Επιλογές των αρχείων συστήματος</string>
+        <string name="partition">Τομέας: %tw_partition_name%</string>
+        <string name="part_mount_point">Σημείο προσάρτησης: %tw_partition_mount_point%</string>
+        <string name="part_curr_fs">Αρχεία Σύστηματος: %tw_partition_file_system%</string>
+        <string name="part_present_yes">Παρόν: Ναι</string>
+        <string name="part_present_no">Παρόν: Όχι</string>
+        <string name="part_removable_yes">Αφαιρούμενο: Ναι</string>
+        <string name="part_removable_no">Αφαιρούμενο: Όχι</string>
+        <string name="part_size">Μέγεθος: %tw_partition_size%MB</string>
+        <string name="part_used">Χρησιμοποιούνται: %tw_partition_used%MB</string>
+        <string name="part_free">Ελεύθερα: %tw_partition_free%MB</string>
+        <string name="part_backup_size">Μέγεθος αντιγράφου ασφαλείας: %tw_partition_backup_size% MB</string>
+        <string name="resize_btn">Αλλαγή μεγέθους του Αρχείου Συστήματος</string>
+        <string name="resize_btn_s">Αλλαγή μεγέθους</string>
+        <string name="resize_confirm">Αλλαγή μεγέθους %tw_partition_name%;</string>
+        <string name="resizing">Αλλαγή μεγέθους...</string>
+        <string name="resize_complete">Η αλλαγή μεγέθους ολοκληρώθηκε</string>
+        <string name="swipe_resize">Σύρετε για αλλαγή μεγέθους</string>
+        <string name="swipe_resize_s">   Αλλαγή μεγέθους</string>
+        <string name="repair_btn">Επιδιόρθωση Αρχ. Συστημ.</string>
+        <string name="repair_btn_s">Επιδιόρθωση</string>
+        <string name="repair_confirm">Επιδιόρθωση %tw_partition_name%;</string>
+        <string name="repairing">Επιδιόρθωση...</string>
+        <string name="repair_complete">Η επιδιόρθωση ολοκληρώθηκε</string>
+        <string name="swipe_repair">Σύρετε για Επιδιόρθωση</string>
+        <string name="swipe_repair_s">   Επιδιόρθωση</string>
+        <string name="change_fs_btn">Αλλαγή Αρχ. Συστήματος</string>
+        <string name="change_fs_btn_s">Αλλαγή</string>
+        <string name="change_fs_for_hdr">Αλλαγή Αρχείου Συστήματος για: %tw_partition_name%</string>
+        <string name="change_fs_for_hdr_s">Τομέας: %tw_partition_name% &gt; επιλέξετε Αρχεία Συστήματος</string>
+        <string name="change_fs_warn1">Κάποια ROM ή kernels μπορεί να μην υποστηρίζουν κάποια</string>
+        <string name="change_fs_warn2">αρχεία συστήματος. Συνεχίστε με προσοχή!</string>
+        <string name="change_fs_confirm">Αλλαγή %tw_partition_name%;</string>
+        <string name="formatting">Διαμόρφωση...</string>
+        <string name="format_complete">Η Διαμόρφωση ολοκληρώθηκε</string>
+        <string name="swipe_change_fs">Σύρετε για Αλλαγή</string>
+        <string name="swipe_change_s">   Αλλαγή</string>
+        <string name="back_btn">Πίσω</string>
+        <string name="wipe_enc_btn">Εκκαθάριση Κρυπτογράφησης</string>
+        <string name="swipe_factory_reset">Σύρετε για Επαναφ. εργοστ. ρυθμ.</string>
+        <string name="repair_change_btn">Επιδιόρθωση ή Αλλαγή Αρχείων Συστήματος</string>
+        <string name="storage_hdr">Αποθ. χώρος: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+        <string name="backup_hdr">Αντίγραφα ασφαλείας</string>
+        <string name="backup_confirm_hdr">Επιβεβαίωση Αντιγράφων Ασφαλείας</string>
+        <string name="encryption_tab">ΚΡΥΠΤΟΓΡΑΦΗΣΗ</string>
+        <string name="encryption">Κρυπτογράφηση:</string>
+        <string name="name">Όνομα:</string>
+        <string name="sel_part_backup">Επιλέξτε Τομείς για αντίγραφα ασφαλείας:</string>
+        <string name="storage">Αποθ. χώρος:</string>
+        <string name="enc_disabled">απενεργοποιημένο - ορίσετε έναν κωδικό για να ενεργοποιήσετε</string>
+        <string name="enc_enabled">ενεργοποιημένο</string>
+        <string name="enable_backup_comp_chk">Ενεργοποιήστε τη συμπίεση</string>
+        <string name="skip_digest_backup_chk" version="2">Παράλ. δημιουργ. Digest κατά την δημ. αντιγρ. ασφ.</string>
+        <string name="disable_backup_space_chk" version="2">Απενεργοποίηση ελέγχου ελεύθ. χώρου πριν την αντ. ασφ.</string>
+        <string name="current_boot_slot">Τωρινή Θέση: %tw_active_slot%</string>
+        <string name="boot_slot_a">Θέση A</string>
+        <string name="boot_slot_b">Θέση B</string>
+        <string name="changing_boot_slot">Αλλαγή Θέσης Boot</string>
+        <string name="changing_boot_slot_complete">Η Αλλαγή Θέσης Boot Ολοκληρώθηκε</string>
+        <string name="refresh_sizes_btn">Ανανέωση Μεγεθών</string>
+        <string name="swipe_backup">Σύρετε για δημιουργ. Αντιγρ. Ασφαλ.</string>
+        <string name="append_date_btn">Προσαρτημένη Ημ/νία</string>
+        <string name="backup_name_exists">Ένα αντίγραφο ασφαλείας με αυτό το όνομα υπάρχει ήδη!</string>
+        <string name="encrypt_backup">Κρυπτογράφηση αντιγράφων ασφαλείας;</string>
+        <string name="enter_pass">Εισάγετε κωδικό:</string>
+        <string name="enter_pass2">Εισάγετε τον κωδικό ξανά:</string>
+        <string name="pass_not_match">Οι κωδικοί πρόσβασης δεν ταιριάζουν!</string>
+        <string name="partitions">Τομείς:</string>
+        <string name="disabled">Απενεργοποιημένο</string>
+        <string name="enabled">Ενεργοποιημένο</string>
+        <string name="backup_name_hdr">Ορίστε όνομα Αντίγραφου Ασφαλείας</string>
+        <string name="progress">Πρόοδος:</string>
+        <string name="backup_complete">Αντίγραφο Ασφαλείας Δημιουργήθηκε</string>
+        <string name="restore_hdr">Επαναφορά</string>
+        <string name="sel_backup_hdr">Επιλέξτε αντίγραφα ασφαλείας</string>
+        <string name="restore_sel_store_hdr">Επιλέξτε αρχείο από %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+        <string name="restore_sel_pack_fs">Επιλέξτε πακέτο Επαναφοράς:</string>
+        <string name="restore_enc_backup_hdr">Κρυπτογραφημένο αντίγραφο ασφαλείας</string>
+        <string name="restore_dec_fail">Κωδικός απέτυχε, παρακαλώ προσπαθήστε ξανά!</string>
+        <string name="del_backup_btn">Διαγραφή αντιγράφου ασφαλείας</string>
+        <string name="del_backup_confirm">Διαγραφή αντιγράφου ασφαλείας;</string>
+        <string name="del_backup_confirm2">Αυτή η ενέργεια δεν μπορεί να αναιρεθεί!</string>
+        <string name="deleting_backup">Διαγραφή αντιγράφων ασφαλείας...</string>
+        <string name="backup_deleted">Το αντίγραφο ασφαλείας διαγράφηκε</string>
+        <string name="swipe_delete">Σύρετε για διαγραφή</string>
+        <string name="swipe_delete_s">   Διαγραφή</string>
+        <string name="restore_try_decrypt">Κρυπτογραφημένο Αντίγραφο Ασφαλείας - Προσπάθεια Αποκρυπτογράφησης</string>
+        <string name="restore_try_decrypt_s">Προσπάθεια αποκρυπτογράφησης</string>
+        <string name="restore_backup_date">Το Αντίγραφο ασφαλείας δημιουργήθηκε στο %tw_restore_file_date%</string>
+        <string name="restore_sel_part">Επιλέξτε Τομείς για Επαναφορά:</string>
+        <string name="restore_enable_digest_chk" version="2">Ενεργ. την επαλήθευση Digest στα αντίγρ. ασφαλ.</string>
+        <string name="restore_complete">Η Eπαναφορά ολοκληρώθηκε</string>
+        <string name="swipe_restore">Σύρετε για Eπαναφορά</string>
+        <string name="swipe_restore_s">   Επαναφορά</string>
+        <string name="rename_backup_hdr">Μετονομασία αντιγράφου ασφαλείας</string>
+        <string name="rename_backup_confirm">Μετονομασία αντιγράφου ασφαλείας;</string>
+        <string name="rename_backup_confirm2">Αυτή η ενέργεια δεν μπορεί να αναιρεθεί!</string>
+        <string name="renaming_backup">Μετονομασία αντίγραφου ασφαλείας...</string>
+        <string name="rename_backup_complete">Η Μετονομασία Αντ. Ασφαλ. Ολοκληρώθηκε</string>
+        <string name="swipe_to_rename">Σύρετε για Μετονομασία</string>
+        <string name="swipe_rename">   Μετονομασία</string>
+        <string name="confirm_hdr">Επιβεβαίωση</string>
+        <string name="mount_hdr">Προσάρτηση</string>
+        <string name="mount_sel_part">Επιλέξτε Τομείς για Προσάρτηση:</string>
+        <string name="mount_sys_ro_chk">Ορισμός του τομέα συστήματος μόνο για ανάγνωση</string>
+        <string name="mount_sys_ro_s_chk">Τοποθέτηση Συστήματος σε RO</string>
+        <string name="decrypt_data_btn">Αποκρυπτογράφηση δεδομένων</string>
+        <string name="disable_mtp_btn">Απενεργοποίηση MTP</string>
+        <string name="enable_mtp_btn">Ενεργοποίηση MTP</string>
+        <string name="mount_usb_storage_btn">Προσάρτ. αποθ. χώρου USB</string>
+        <string name="usb_storage_hdr">Εξ. χώρος USB</string>
+        <string name="usb_stor_mnt1">Εξ. χώρος USB προσαρτήθηκε</string>
+        <string name="usb_stor_mnt2">Βεβαιωθείτε ότι θα αφαιρέσετε με ασφάλεια τη συσκευή σας</string>
+        <string name="usb_stor_mnt3">από τον υπολογιστή σας πριν από την αποπροσάρτηση!</string>
+        <string name="unmount_btn">Αποπροσάρτηση</string>
+        <string name="rb_system_btn">Σύστημα</string>
+        <string name="rb_poweroff_btn">Απενεργοποίηση</string>
+        <string name="rb_recovery_btn">Recovery</string>
+        <string name="rb_bootloader_btn">Bootloader</string>
+        <string name="rb_download_btn">Λήψη</string>
+		<string name="rb_edl_btn">EDL</string>
+        <string name="turning_off">Απενεργοποίηση...</string>
+        <string name="swipe_power_off">Σύρετε για Απενεργοποίηση</string>
+        <string name="swipe_power_off_s">Απενεργοποίηση</string>
+        <string name="sys_ro_hdr">Τομέας συστήματος χωρίς τροποποιήσεις</string>
+        <string name="sys_ro_keep">Κρατήστε το σύστημα για ανάγνωση μόνο;</string>
+        <string name="sys_rop1">Το TWRP μπορεί να αφήσει τον τομέα συστήματος μη τροποποιημένο</string>
+        <string name="sys_rop2">ώστε να κάνει ευκολότερο για σας να λάβετε επίσημες ενημερώσεις.</string>
+        <string name="sys_rop3">Το TWRP δεν θα είναι σε θέση να αποτρέψει το λειτουργικό</string>
+        <string name="sys_rop4">να αντικαταστήσει το TWRP και δεν θα προσφέρει τη δυνατότητα root στη συσκευή σας.</string>
+        <string name="sys_rop5">Η εγκατάσταση zip ή η εκτέλεση εντολών adb μπορούν να</string>
+        <string name="sys_rop6">τροποποιήσουν το διαμέρισμα του συστήματος.</string>
+        <string name="sys_rol1">Το TWRP μπορεί να αφήσει τον τομέα συστήματος μη τροποποιημένο ώστε να κάνει ευκολότερο για σας να λάβετε επίσημες ενημερώσεις.</string>
+        <string name="sys_rol2">Το TWRP δεν θα είναι σε θέση να αποτρέψει το λειτουργικό να αντικαταστήσει το TWRP και δεν θα προσφέρει τη δυνατότητα root στη συσκευή σας.</string>
+        <string name="sys_rol3">Η Εγκατάσταση zip ή η εκτέλεση εργασιών adb μπορούν να τροποποιήσουν το διαμέρισμα του συστήματος.</string>
+        <string name="sys_ro_never_show_chk">Να μην εμφανιστεί ξανά αυτή η οθόνη κατά την εκκίνηση</string>
+        <string name="sys_ro_keep_ro_btn">Διατήρηση Ανάγνωσης Μόνο</string>
+        <string name="swipe_allow_mod">Σύρετε για να Επιτρέψετε τις Τροποποιήσεις</string>
+        <string name="swipe_allow_mod_s">Επιτρέψτε τροποποιήσεις</string>
+        <string name="settings_hdr">Ρυθμίσεις</string>
+        <string name="settings_gen_hdr">Γενικές Ρυθμίσεις</string>
+        <string name="settings_gen_s_hdr">Γενικά</string>
+        <string name="settings_gen_btn">Γενικά</string>
+        <string name="use_rmrf_chk">Χρήση rm -rf αντί για διαμόρφωση</string>
+        <string name="use24clock_chk">Χρήση 24-ωρης μορφής ώρας</string>
+        <string name="rev_navbar_chk">Αντεστραμμένη navbar διάταξης</string>
+        <string name="simact_chk">Προσομοιώση ενεργειών για τη δοκιμή θέματος</string>
+        <string name="simfail_chk">Προσομοίωση αποτυχίας για δράσεις</string>
+        <string name="ctr_navbar_rdo">Κεντρική στοίχιση navbar κουμπιών</string>
+        <string name="lft_navbar_rdo">Αριστερή στοίχιση navbar κουμπιών</string>
+        <string name="rht_navbar_rdo">Δεξιά στοίχιση navbar κουμπιών</string>
+        <string name="restore_defaults_btn">Επαναφ. Προεπιλεγμ.</string>
+        <string name="settings_tz_btn">Ζώνη Ώρας</string>
+        <string name="settings_screen_btn">Οθόνη</string>
+        <string name="settings_screen_bright_btn">Φωτεινότητα οθόνης</string>
+        <string name="settings_vibration_btn">Δόνηση</string>
+        <string name="settings_language_btn">Γλώσσα</string>
+        <string name="time_zone_hdr">Ζώνη Ώρας</string>
+        <string name="sel_tz_list">Επιλέξτε ζώνη ώρας:</string>
+        <string name="utcm11">(UTC -11) Σαμόα, Midway Island</string>
+        <string name="utcm10">(UTC -10) Χαβάη</string>
+        <string name="utcm9">(UTC -9) Αλάσκα</string>
+        <string name="utcm8">(UTC -8) Ώρα Ειρηνικού</string>
+        <string name="utcm7">(UTC -7) Mountain Time</string>
+        <string name="utcm6">(UTC -6) Κεντρική ώρα</string>
+        <string name="utcm5">(UTC -5) Ανατολική ώρα</string>
+        <string name="utcm4">(UTC -4) Ώρα Ατλαντικού</string>
+        <string name="utcm3">(UTC -3) Βραζιλία, Μπουένος Άϊρες</string>
+        <string name="utcm2">(UTC -2) Mid-Atlantic</string>
+        <string name="utcm1">(UTC -1) Αζόρες, Νήσοι Πράσινου Ακρωτηρίου</string>
+        <string name="utc0">(UTC 0) Λονδίνο, Δουβλίνο, Λισαβόνα</string>
+        <string name="utcp1">(UTC + 1) Βερολίνο, Βρυξέλλες, Παρίσι</string>
+        <string name="utcp2">(UTC + 2) Αθήνα, Κωνσταντινούπολη, Νότια Αφρική</string>
+        <string name="utcp3">(UTC + 3) Μόσχα, Βαγδάτη</string>
+        <string name="utcp4">(UTC + 4) Αμπού Ντάμπι, Τιφλίδα, Μοσχάτο</string>
+        <string name="utcp5">(UTC + 5) Αικατερινούπολη, Ισλαμαμπάντ</string>
+        <string name="utcp6">(UTC + 6) Αλμάτι, Ντάκα, Κολόμπο</string>
+        <string name="utcp7">(UTC + 7) Μπανγκόκ, Ανόι, Τζακάρτα</string>
+        <string name="utcp8">(UTC + 8) Πεκίνο, Σιγκαπούρη, Hong Kong</string>
+        <string name="utcp9">(UTC + 9) Τόκιο, Σεούλ, Γιακούτσκ</string>
+        <string name="utcp10">(UTC + 10) Ανατολική Αυστραλία, Γκουάμ</string>
+        <string name="utcp11">(UTC + 11) Βλαδιβοστόκ, Νησιά Σολομώντα</string>
+        <string name="utcp12">(UTC + 12) Όκλαντ, Γουέλινγκτον, Φίτζι</string>
+        <string name="sel_tz_offset">Επιλέξτε μετατόπιση (συνήθως 0): %tw_time_zone_guioffset%</string>
+        <string name="tz_offset_none">Κανένα</string>
+        <string name="tz_offset_0">0</string>
+        <string name="tz_offset_15">15</string>
+        <string name="tz_offset_30">30</string>
+        <string name="tz_offset_45">45</string>
+        <string name="use_dst_chk">Χρησιμοποιήστε τη θερινή ώρα (DST)</string>
+        <string name="curr_tz">Τρέχουσα ζώνης ώρας: %tw_time_zone%</string>
+        <string name="curr_tz_s">Τρέχουσα ζώνης ώρας:</string>
+        <string name="set_tz_btn">Ορισμός ζώνης ώρας</string>
+        <string name="settings_screen_hdr">Ρυθμίσεις οθόνης</string>
+        <string name="settings_screen_timeout_hdr">Χρονικό όριο οθόνης</string>
+        <string name="enable_timeout_chk">Ενεργοποίηση χρονικού ορίου οθόνης</string>
+        <string name="screen_to_slider">Χρονικό όριο οθόνης σε δευτερόλεπτα:</string>
+        <string name="screen_to_slider_s">Χρονικό όριο οθόνης σε δευτερόλεπτα (0 = απενεργοποιημένο): %tw_screen_timeout_secs%</string>
+        <string name="screen_to_na">Η ρύθμιση χρονικού ορίου οθόνης δεν είναι διαθέσιμη</string>
+        <string name="screen_bright_slider">Φωτεινότητα: %tw_brightness_pct%%</string>
+        <string name="screen_bright_na">Η ρύθμιση φωτεινότητας δεν είναι διαθέσιμη</string>
+        <string name="vibration_hdr">Δόνηση</string>
+        <string name="button_vibration_hdr">Δόνηση Κουμπιού</string>
+        <string name="kb_vibration_hdr">Δόνηση Πληκτρολογίου</string>
+        <string name="act_vibration_hdr">Δόνηση Ενέργειας</string>
+        <string name="button_vibration">Δόνηση Κουμπιού:</string>
+        <string name="kb_vibration">Δόνηση Πληκτρολογίου:</string>
+        <string name="act_vibration">Δόνηση Ενέργειας:</string>
+        <string name="select_language">Επιλέξτε γλώσσα:</string>
+        <string name="sel_lang_btn">Ορισμός γλώσσας</string>
+        <string name="set_language_btn">Ρύθμιση γλώσσας</string>
+        <string name="advanced_hdr">Προηγμένες</string>
+        <string name="copy_log_confirm">Αντιγραφή Log στην κάρτα SD;</string>
+        <string name="copying_log" version="2">Αντιγραφή των Logs στην κάρτα SD...</string>
+        <string name="copy_log_complete" version="2">Η Αντιγραφή των Logs Ολοκληρώθηκε</string>
+        <string name="fix_context_btn">Διορθ. δικαιωμ.</string>
+        <string name="part_sd_btn">Τομέας κάρτας SD</string>
+        <string name="part_sd_s_btn">Κάρτα SD</string>
+        <string name="file_manager_btn">Διαχείρ. αρχείων</string>
+        <string name="language_hdr">Γλώσσα</string>
+        <string name="terminal_btn">Τερματικό</string>
+        <string name="reload_theme_btn">Επαναφόρτ. θέματ.</string>
+        <string name="dumlock_btn">HTC Dumlock</string>
+        <string name="inject_twrp_btn">Εισαγωγή TWRP</string>
+        <string name="inject_twrp_confirm">Επανεισαγωγή TWRP;</string>
+        <string name="injecting_twrp">Επανεισαγωγή TWRP...</string>
+        <string name="inject_twrp_complete">Εισαγωγή TWRP ολοκληρώθηκε</string>
+        <string name="swipe_to_confirm">Σύρετε για επιβεβαιώσετε</string>
+        <string name="part_sd_hdr">Τομέας κάρτας SD</string>
+        <string name="invalid_partsd_sel">Πρέπει να επιλέξετε μία αφαιρ. συσκευή</string>
+        <string name="part_sd_lose">Θα χάσετε όλα τα αρχεία στην κάρτα SD!</string>
+        <string name="part_sd_undo">Αυτή η ενέργεια δεν μπορεί να αναιρεθεί!</string>
+        <string name="part_sd_ext_sz">Μέγεθος EXT:</string>
+        <string name="part_sd_swap_sz">Αλλαγή Μεγέθους:</string>
+        <string name="part_sd_m">-</string>
+        <string name="part_sd_p">+</string>
+        <string name="file_system">Σύστημα αρχείων:</string>
+        <string name="swipe_part_sd">Σύρετε για Διαχωρισμό</string>
+        <string name="swipe_part_sd_s">Τομέας</string>
+        <string name="partitioning_sd">Διαχώριση κάρτας SD...</string>
+        <string name="partitioning_sd2">Αυτό θα διαρκέσει μερικά λεπτά.</string>
+        <string name="part_sd_complete">Η Διαχώριση ολοκληρώθηκε</string>
+        <string name="dumlock_hdr">HTC Dumlock</string>
+        <string name="dumlock_restore_btn">Επαναφέρετε το Αρχικό Boot</string>
+        <string name="dumlock_restore_confirm">Επαναφορά την αρχικής εικόνας boot;</string>
+        <string name="dumlock_restoring">Επαναφορά του Αρχικού Boot...</string>
+        <string name="dumlock_restore_complete">Επαναφορά του Αρχικού Boot Ολοκληρώθηκε</string>
+        <string name="dumlock_reflash_btn">Επανεγγραφή Recovery</string>
+        <string name="dumlock_reflash_confirm">Επανεγγραφή Recovery στο boot;</string>
+        <string name="dumlock_reflashing">Εγγραφή recovery στο boot...</string>
+        <string name="dumlock_reflash_complete">Η Εγγραφή Recovery στο Boot Ολοκληρώθηκε</string>
+        <string name="dumlock_install_btn">Εγκατάσταση του HTC Dumlock</string>
+        <string name="dumlock_install_confirm">Εγκατάσταση αρχείων HTC dumlock στο λειτουργικό;</string>
+        <string name="dumlock_installing">Εγκατάσταση του HTC Dumlock...</string>
+        <string name="dumlock_install_complete">Η εγκατάσταση HTC Dumlock Ολοκληρώθηκε</string>
+        <string name="swipe_to_unlock">Σύρετε για ξεκλείδωμα</string>
+        <string name="swipe_unlock">   Ξεκλείδωμα</string>
+        <string name="fm_hdr">Διαχείριση αρχείων</string>
+        <string name="fm_sel_file">Επιλέξτε ένα αρχείο ή φάκελο</string>
+        <string name="fm_type_folder">Φάκελος</string>
+        <string name="fm_type_file">Αρχείο</string>
+        <string name="fm_choose_act">Επιλέξτε ενέργεια</string>
+        <string name="fm_selected">%tw_fm_type% επιλέχθηκεί:</string>
+        <string name="fm_copy_btn">Αντιγραφή</string>
+        <string name="fm_copy_file_btn">Αντιγραφή αρχείου</string>
+        <string name="fm_copy_folder_btn">Αντιγραφή φακέλου</string>
+        <string name="fm_copying">Αντιγραφή</string>
+        <string name="fm_move_btn">Μετακίνηση</string>
+        <string name="fm_moving">Μετακίνηση</string>
+        <string name="fm_chmod755_btn">chmod 755</string>
+        <string name="fm_chmod755ing">chmod 755</string>
+        <string name="fm_chmod_btn">chmod</string>
+        <string name="fm_delete_btn">Διαγραφή</string>
+        <string name="fm_deleting">Διαγραφή</string>
+        <string name="fm_rename_btn">Μετονομασία</string>
+        <string name="fm_rename_file_btn">Μετονοσία αρχείου</string>
+        <string name="fm_rename_folder_btn">Μετονομασία φακέλου</string>
+        <string name="fm_renaming">Μετονομασία</string>
+        <string name="fm_sel_dest">Επιλέξτε φάκελο προορισμού</string>
+        <string name="fm_sel_curr_folder">Επιλέξτε τρέχοντα φάκελο</string>
+        <string name="fm_rename_hdr">Μετονομασία</string>
+        <string name="fm_set_perms_hdr">Ορισμός δικαιωμάτων</string>
+        <string name="fm_perms">Δικαιώματα:</string>
+        <string name="fm_complete">Η Λειτουργία Αρχείου Ολοκληρώθηκε</string>
+        <string name="decrypt_data_hdr">Αποκρυπτογράφηση δεδομένων</string>
+        <string name="decrypt_data_enter_pass">Εισάγετε Κωδικό</string>
+        <string name="decrypt_data_failed">Κωδικός απέτυχε, παρακαλώ προσπαθήστε ξανά!</string>
+        <string name="decrypt_data_failed_pattern">Το Μοτίβο απέτυχε, παρακαλώ προσπαθήστε ξανά!</string>
+        <string name="decrypt_data_enter_pattern">Εισαγάγετε το μοτίβο.</string>
+        <string name="decrypt_data_trying">Προσπάθεια αποκρυπτογράφησης</string>
+        <string name="decrypt_data_vold_os_missing">Αρχεία που λείπουν χρειάζονται για την αποκρυπτογράφηση: {1}</string>
+        <string name="term_hdr">Εντολή τερματικού</string>
+        <string name="term_s_hdr">Τερματικό</string>
+        <string name="term_kill_btn">ΤΕΡΜΑΤΙΣΕ</string>
+        <string name="term_sel_folder_hdr">Περιηγηθείτε στον αρχικό φάκελο</string>
+        <string name="adb_sideload_hdr">ADB Sideload</string>
+        <string name="sideload_wipe_dalvik_chk">Εκκαθάριση Dalvik Cache</string>
+        <string name="sideload_wipe_cache_chk">Εκκαθάριση Cache</string>
+        <string name="swipe_to_sideload">Σύρετε για Sideload</string>
+        <string name="swipe_sideload">   Έναρξη</string>
+        <string name="sideload_confirm">ADB Sideload</string>
+        <string name="sideload_usage">Χρήση: adb sideload filename.zip</string>
+        <string name="sideload_complete">ADB Sideload ολοκληρώθηκε</string>
+        <string name="fix_contexts_hdr">Διόρθωση δικαιωμάτων</string>
+        <string name="fix_contexts_note1">Σημείωση: Η διόρθωση δικαιωμάτων, σπάνια χρειάζεται.</string>
+        <string name="fix_contexts_note2">Η διόρθωση δικαιωμάτων SELinux μπορεί να</string>
+        <string name="fix_contexts_note3">κάνει την συσκευή σας να μην ξεκινάει σωστά.</string>
+        <string name="swipe_to_fix_contexts">Σύρετε για Διόρθωση Δικαιωμάτων</string>
+        <string name="swipe_fix_contexts">   Διορθ. δικαιωμ.</string>
+        <string name="fixing_contexts">Διόρθωση δικαιωμάτων...</string>
+        <string name="fix_contexts_complete">Η Διόρθωση δικαιωμάτων ολοκληρώθηκε</string>
+        <string name="reboot_hdr">Επανεκκίνηση</string>
+        <string name="install_cancel">Μην εγκαταστήσετε</string>
+        <string name="sel_storage_list">Επιλέξτε Αποθ. χώρο</string>
+        <string name="ok_btn">OΚ</string>
+        <string name="no_kernel_selinux">Το Kernel δεν υποστηρίζει την ανάγνωση περιεχομένων SELinux.</string>
+        <string name="full_selinux">Υπάρχει πλήρης υποστήριξη SELinux.</string>
+        <string name="no_selinux">Δεν υπάρχει υποστήριξη SELinux (libselinux).</string>
+        <string name="mtp_enabled">MTP ενεργοποιημένο</string>
+        <string name="mtp_crash">MTP συνετρίβη, δεν αρχίζει το MTP κατά την εκκίνηση.</string>
+        <string name="decrypt_success">Αποκρυπτογραφήθηκε με επιτυχία με τον προεπιλεγμένο κωδικό.</string>
+        <string name="unable_to_decrypt">Δεν είναι δυνατή η αποκρυπτογράφηση με τον προεπιλεγμένο κωδικός. Ίσως χρειαστεί να εκτελέσετε εκκαθάριση δεδομένων.</string>
+        <string name="generating_digest1" version="2">Δημιουργία Digest</string>
+        <string name="generating_digest2" version="2"> * Δημιουργία Digest...</string>
+        <string name="digest_created" version="2"> * Digest δημιουργήθηκε.</string>
+        <string name="digest_error" version="2"> * Σφάλμα Digest!</string>
+        <string name="digest_compute_error" version="2"> * Σφάλμα υπολογισμού Digest.</string>
+        <string name="current_date">(Τρέχουσα ημερομηνία)</string>
+        <string name="auto_generate">(Αυτόματη δημιουργία)</string>
+        <string name="unable_to_locate_partition">Δεν μπορεί να εντοπιστεί ο '{1}' τομέας για υπολογισμούς αντ. ασφαλ.</string>
+        <string name="no_partition_selected">Δεν επιλέχθηκαν τομείς για αντίγραφο ασφαλείας.</string>
+        <string name="total_partitions_backup"> * Συνολικός αριθμός διαμερισμάτων για να δημιουργήσετε αντίγραφα ασφαλείας: {1}</string>
+        <string name="total_backup_size"> * Συνολικό μέγεθος όλων των δεδομένων: {1}MB</string>
+        <string name="available_space"> * Διαθέσιμος χώρος: {1}MB</string>
+        <string name="unable_locate_storage">Δεν μπορεί να εντοπιστεί ο αποθ. χώρος.</string>
+        <string name="no_space">Ανεπαρκής χώρος στον αποθ. χώρο.</string>
+        <string name="backup_started">[ΔΗΜΙΟΥΡΓΙΑ ΑΝΤΙΓΡ. ΑΣΦΑΛ.]</string>
+        <string name="backup_folder"> * Κάντε φάκελο αντιγρ. ασφ.: {1}</string>
+        <string name="fail_backup_folder">Απέτυχε η δημιουργία φακέλου αντιγρ. ασφ.</string>
+        <string name="avg_backup_fs">Μέσος ρυθμός αντιγρ. ασφ. αρχείων συστήματος: {1} MB/δευτερ.</string>
+        <string name="avg_backup_img">Μέσος ρυθμός αντιγρ. ασφ. εικονικών μονάδων: {1} MB/δευτερ.</string>
+        <string name="total_backed_size">[{1} MB ΣΥΝΟΛΙΚΑ ΑΝΤΙΓΡΑΦΗΚΑΝ]</string>
+        <string name="backup_completed">[Η ΔΗΜΙΟΥΡΓΙΑ ΑΝΤΙΓΡ. ΑΣΦ. ΟΛΟΚΛΗΡΩΘΗΚΕ ΣΕ {1} ΔΕΥΤΕΡΟΛΕΠΤΑ]</string>
+        <string name="restore_started">[ΕΠΑΝΑΦΟΡΑ ΞΕΚΙΝΗΣΕ]</string>
+        <string name="restore_folder">Επαναφορά φακέλου: '{1}'</string>
+        <string name="restore_part_done">[{1} ολοκληρώθηκαν ({2} δευτερόλεπτα)]</string>
+        <string name="verifying_digest" version="2">Επαλήθευση Digest</string>
+        <string name="skip_digest" version="2">Παράλειψη ελέγχου Digest βάσει τις ρυθμίσεις χρήστη.</string>
+        <string name="calc_restore">Υπολογισμός λεπτομερειών επαναφοράς...</string>
+        <string name="restore_read_only">Δεν είναι δυνατό να επαναφέρετε {1} -- τοποθέτηση μόνο για ανάγνωση.</string>
+        <string name="restore_unable_locate">Δεν εντοπίστηκε το διαμέρισμα «{1}» για αποκατάσταση.</string>
+        <string name="no_part_restore">Δεν επιλέχθηκαν τομείς για επαναφορά.</string>
+        <string name="restore_part_count">Επαναφορά {1} τομέων...</string>
+        <string name="total_restore_size">Το συνολικό μέγεθος επαναφοράς είναι {1}MB</string>
+        <string name="updating_system_details">Ενημέρωση λεπτομερειών συστήματος</string>
+        <string name="restore_completed">[ΕΠΑΝΑΦΟΡΑ ΟΛΟΚΛΗΡΩΝΕΤΑΙ ΣΕ {1} ΔΕΥΤΕΡΟΛΕΠΤΑ]</string>
+        <string name="error_opening_strerr">Σφάλμα κατά το άνοιγμα: '{1}' ({2})</string>
+        <string name="unable_locate_part_backup_name">Δεν εντοπίστηκε διαμέρισμα από το όνομα του αντίγραφου ασφαλείας: '{1}'</string>
+        <string name="unable_find_part_path">Δεν μπόρεσε να βρεθεί ο τομέας για τη διαδρομή '{1}'</string>
+        <string name="update_part_details">Ενημέρωση λεπτομερειών Τομέα...</string>
+        <string name="update_part_details_done">...ολοκληρώθηκε</string>
+        <string name="wiping_dalvik">Εκκαθάριση Καταλόγων Dalvik Cache...</string>
+        <string name="cleaned">Καθαρίστηκαν: {1}...</string>
+        <string name="dalvik_done">-- Οι κατάλογοι Dalvik Cache, Διαγράφηκαν!</string>
+        <string name="no_andsec">Δεν βρέθηκαν τομείς android secure.</string>
+        <string name="unable_to_locate">Δεν μπορεί να εντοπιστεί {1}.</string>
+        <string name="wiping_datamedia">Εκκαθάριση εσωτ. χώρου αποθ. -- /data/media...</string>
+        <string name="unable_to_mount">Αδύνατη η προσάρτηση {1}</string>
+        <string name="unable_to_mount_internal">Αδύνατη η προσάρτηση του internal_storage</string>
+        <string name="unable_to_mount_storage">Αδύνατη η προσάρτηση του χώρου αποθ.</string>
+        <string name="fail_decrypt">Απέτυχε η αποκρυπτογράφηση δεδομένων.</string>
+        <string name="no_crypto_support">Δεν έχει μεταγλωττιστεί υποστήριξη κρυπτογράφησης σε αυτό το build.</string>
+        <string name="decrypt_success_dev">Τα δεδομένα αποκρυπτογραφήθηκαν, νέο μπλοκάρισμα συσκευής: '{1}'</string>
+        <string name="decrypt_success_nodev">Τα δεδομένα αποκρυπτογραφήθηκαν με επιτυχία</string>
+        <string name="done">Έγινε.</string>
+        <string name="start_partition_sd">Διαχώριση κάρτας SD...</string>
+        <string name="partition_sd_locate">Δεν μπορεί να εντοπιστεί η συσκευή για διαχωρισμό.</string>
+        <string name="ext_swap_size">EXT + Η αλλαγή μεγέθους είναι μεγαλύτερη από το μέγεθος της κάρτας sd.</string>
+        <string name="remove_part_table">Κατάργηση πίνακα τομέων...</string>
+        <string name="unable_rm_part">Δεν είναι δυνατή η κατάργηση του πίνακα τομέων.</string>
+        <string name="create_part">Δημιουργία {1} τομέα...</string>
+        <string name="unable_to_create_part">Δεν είναι δυνατή η δημιουργία τομέα {1}.</string>
+        <string name="format_sdext_as">Διαμόρφωση sd-ext ως {1}...</string>
+        <string name="part_complete">Η Διαχώριση ολοκληρώθηκε.</string>
+        <string name="unable_to_open">Δεν είναι δυνατό το άνοιγμα '{1}'.</string>
+        <string name="mtp_already_enabled">Το MTP ενεργοποιήθηκε ήδη</string>
+        <string name="mtp_fail">Αποτυχία ενεργοποιήσης MTP</string>
+        <string name="no_mtp">Δεν περιλαμβάνεται η υποστήριξη MTP</string>
+        <string name="image_flash_start">[ΕΓΓΡΑΦΗ ΕΙΚΟΝΑΣ ΞΕΚΙΝΗΣΕ]</string>
+        <string name="img_to_flash">Εικόνα για εγγραφή: '{1}'</string>
+        <string name="flash_unable_locate">Δεν μπορεί να εντοπιστεί ο '{1}' τομέας για εγγραφή.</string>
+        <string name="no_part_flash">Δεν επιλέχθηκαν τομείς για εγγραφή.</string>
+        <string name="too_many_flash">Έχουν επιλεγεί πολλοί τομείς για εγγραφή.</string>
+        <string name="invalid_flash">Μη έγκυρη εγγραφή τομέα που καθορίζεται.</string>
+        <string name="flash_done">[ΕΓΓΡΑΦΗ ΕΙΚΟΝΑΣ ΟΛΟΚΛΗΡΩΘΗΚΕ]</string>
+        <string name="wiping">Εκκαθάριση {1}</string>
+        <string name="repair_not_exist">{1} δεν υπάρχει! Δεν μπορεί να επιδιορθωθεί!</string>
+        <string name="repairing_using">Επιδιόρθωση {1} χρήση {2}...</string>
+        <string name="unable_repair">Δεν είναι δυνατή η επιδιόρθωση {1}.</string>
+        <string name="mount_data_footer">Δεν μπόρεσε να προσαρτηθεί το /data και είναι αδύνατη η εύρεση του κρύπτο υποσέλιδου.</string>
+        <string name="create_folder_strerr">Δεν μπόρεσε να δημιουργήσει '{1}' το φάκελο ({2}).</string>
+        <string name="fail_mount">Αποτυχία προσάρτησης '{1}' ({2})</string>
+        <string name="fail_unmount">Αποτυχία αποπροσάρτησης '{1}' ({2})</string>
+        <string name="cannot_resize">Δεν μπορεί να αλλαχθεί το μέγεθος {1}.</string>
+        <string name="repair_resize">Επιδιόρθωση {1} πριν την αλλαγή μεγέθους.</string>
+        <string name="unable_resize">Δεν είναι δυνατή η αλλαγή μεγέθους {1}.</string>
+        <string name="no_digest_found" version="2">Δεν βρέθηκε Digest αρχείο για το '{1}'. Παρακαλώ αποεπιλέξτε την επαλήθευση MD5 για επαναφορά.</string>
+        <string name="digest_fail_match" version="2">Το Digest απέτυχε να ταυτιστεί με το '{1}'.</string>
+        <string name="digest_matched" version="2">Το Digest ταυτίζετε με το '{1}'.</string>
+        <string name="fail_decrypt_tar">Αποτυχία αποκρυπτογράφησης του αρχείου tar '{1}'</string>
+        <string name="format_data_msg">Ίσως χρειαστεί να επανεκκινήσετε το recovery για να ξαναχρησιμοποιήσετε το /data.</string>
+        <string name="format_data_err">Αδύνατη η διαμόρφωση για να καταργήσετε την κρυπτογράφηση.</string>
+        <string name="formatting_using">Διαμόρφωση {1} χρησιμοποιόντας {2}...</string>
+        <string name="unable_to_wipe">Αδυναμία εκκαθάρισης {1}.</string>
+        <string name="cannot_wipe">Ο Τομέας {1} δεν μπορεί να εκκαθαριστεί.</string>
+        <string name="remove_all">Διαγραφή όλων των αρχείων στο '{1}'</string>
+        <string name="wiping_data">Εκκαθάριση δεδομένων χωρίς την εκκαθάριση του /data/media ...</string>
+        <string name="backing_up">Δημιουργία Αντιγρ. Ασφαλ. {1}...</string>
+        <string name="backup_storage_warning">Τα αντιγρ. ασφ. του {1} δεν περιλαμβάνουν κανένα αρχείο του εσωτερικού χώρου αποθήκευσης όπως φωτογραφίες ή λήψεις.</string>
+        <string name="backing">Δημιουργία Αντιγρ. Ασφαλ.</string>
+        <string name="backup_size">Μέγεθος αρχείου αντιγρ. ασφ. για το «{1}» είναι 0 bytes.</string>
+        <string name="datamedia_fs_restore">Προειδοποίηση: Αυτό το αντίγραφο ασφαλείας /data έγινε με το σύστημα αρχείων {1}! Η δημιουργία αντιγράφων ασφαλείας ενδέχεται να μην εκκινηθεί, εκτός και αν επιστρέψετε στο {1}.</string>
+        <string name="restoring">Επαναφορά {1}...</string>
+        <string name="restoring_hdr">Επαναφορά</string>
+        <string name="recreate_folder_err">Δεν είναι δυνατή η ανασύνθεση του φακέλου {1}.</string>
+        <string name="img_size_err">Το μέγεθος της εικόνας είναι μεγαλύτερο από την συσκευή προορισμού</string>
+        <string name="flashing">Εγγραφή {1}...</string>
+        <string name="backup_folder_set">Φάκελος αντιγρ. ασφ. ορίστηκε το '{1}'</string>
+        <string name="locate_backup_err">Δεν μπόρεσε να εντοπιστεί το αντιγρ. ασφ. '{1}'</string>
+        <string name="set_restore_opt">Ρύθμιση επιλογών επαναφοράς: '{1}':</string>
+        <string name="digest_check_skip" version="2">Η παράλειψη ελέγχου Digest είναι ενεργή</string>
+        <string name="ors_encrypt_restore_err">Δεν μπορεί να χρησιμοποιηθεί το OpenRecoveryScript για να επαναφέρετε ένα κρυπτογραφημένο αντίγραφο ασφαλείας.</string>
+        <string name="mounting">Προσάρτηση</string>
+        <string name="unmounting">Αποπροσάρτηση</string>
+        <string name="mounted">Προσαρτήθηκε '{1}'</string>
+        <string name="unmounted">Αποπροσαρτήθηκε '{1}'</string>
+        <string name="setting">Ρύθμιση '{1}' για '{2}'</string>
+        <string name="setting_empty">Ρύθμιση '{1}' για άδειασμα</string>
+        <string name="making_dir1">Δημιουργία φακέλου</string>
+        <string name="making_dir2">Δημιουργία φακέλου: '{1}'</string>
+        <string name="running_command">Εκτέλεση Εντολής</string>
+        <string name="sideload">ADB Sideload</string>
+        <string name="start_sideload">Εκτέλεση ADB sideload...</string>
+        <string name="need_new_adb">Χρειάζεστε adb 1.0.32 ή νεότερο για sideload σε αυτήν τη συσκευή.</string>
+        <string name="no_pwd">Δεν παρέχεται κωδικός.</string>
+        <string name="done_ors">Έγινε επεξεργασία του αρχείου script</string>
+        <string name="injecttwrp">Εισαγωγή TWRP στην εικόνα εκκίνησης...</string>
+        <string name="zip_err">Σφάλμα κατά την εγκατάσταση του zip '{1}'</string>
+        <string name="installing_zip">Εγκατάσταση αρχείου zip '{1}'</string>
+        <string name="select_backup_opt">Ρύθμιση της δημιουργίας αντιγράφων ασφαλείας:</string>
+        <string name="compression_on">Η συμπίεση είναι ενεργή</string>
+        <string name="digest_off" version="2">Η δημιουργία Digest είναι απενεργοποιημένη</string>
+        <string name="backup_fail">Η δημιουργία Αντίγραφου Ασφαλείας απέτυχε</string>
+        <string name="backup_clean">Η δημιουργία Αντίγρ. Ασφαλ. απέτυχε. Εκκαθάριση φακέλου.</string>
+        <string name="running_recovery_commands">Εκτέλεση Eντολών Recovery</string>
+        <string name="recovery_commands_complete">Οι Εντολές Recovery Ολοκληρώθηκαν</string>
+        <string name="running_ors">Εκτέλεση OpenRecoveryScript</string>
+        <string name="ors_complete">OpenRecoveryScript Ολοκληρώθηκε</string>
+        <string name="invalid_zip_format">Μη έγκυρη μορφή αρχείου zip!</string>
+        <string name="check_for_digest" version="2">Έλεγχος για Digest αρχείο...</string>
+        <string name="fail_sysmap">Αποτυχία αντιστ. αρχείου '{1}'</string>
+        <string name="verify_zip_sig">Επαλήθευση υπογραφής zip...</string>
+        <string name="verify_zip_fail">Η επαλήθευση της υπογραφής zip απέτυχε!</string>
+        <string name="verify_zip_done">Η επαλήθευση της υπογραφής zip ολοκληρώθηκε.</string>
+        <string name="zip_corrupt">Το αρχείο zip είναι κατεστραμμένο!</string>
+        <string name="no_digest" version="2">Παράλειψη ελέγχου Digest: δεν βρέθηκε αρχείο Digest</string>
+        <string name="digest_fail" version="2">Τα Digest δεν ταυτίζονται</string>
+        <string name="digest_match" version="2">Τα Digest ταυτίζονται</string>
+        <string name="pid_signal">{1} διαδικασία τερματίστηκε με σήμα: {2}</string>
+        <string name="pid_error">{1} διαδικασία τερματίστηκε με ΣΦΑΛΜΑ: {2}</string>
+        <string name="install_dumlock">Εγκατάσταση HTC Dumlock στο σύστημα...</string>
+        <string name="dumlock_restore">Επαναφορά του Αρχικού Boot...</string>
+        <string name="dumlock_reflash">Επανεγγραφή recovery στο boot...</string>
+        <string name="run_script">Εκτέλεση {1} script...</string>
+        <string name="rename_stock">Μετονομασία αρχικού recovery στο /system για να αποτρέψετε την αντικατάσταση του TWRP από το λειτουργικό.</string>
+        <string name="split_backup">Διάσπαση αντίγραφου ασφαλείας σε πολλαπλά αρχεία...</string>
+        <string name="backup_error">Σφάλμα κατά τη δημιουργία αντιγράφων ασφαλείας.</string>
+        <string name="restore_error">Σφάλμα κατά τη διαδικασία επαναφοράς.</string>
+        <string name="split_thread">Διάσπαση thread ID {1} στο αρχείο {2}</string>
+        <string name="file_progress">%llu από %llu αρχεία, %i%%</string>
+        <string name="size_progress">%lluMB από %lluMB, %i%%</string>
+        <string name="decrypt_cmd">Προσπάθεια αποκρυπτογράφησης τομέα δεδομένων μέσω της γραμμής εντολών.</string>
+        <string name="base_pkg_err">Απέτυχε η φόρτωση των βασικών πακέτων.</string>
+        <string name="simulating">Προσομοίωση ενεργειών...</string>
+        <string name="backup_cancel">Δημιουργία αντιγρ. ασφ. ακυρώθηκε</string>
+        <string name="config_twrp">Ρύθμιση παραμέτρων TWRP...</string>
+        <string name="config_twrp_err">Αδύνατη η ρύθμιση TWRP με αυτό το kernel.</string>
+        <string name="copy_log">Αντιγραφή καταγραφής αποκατάστασης στο {1}.</string>
+        <string name="max_queue">Μέγιστος αριθμός zip!</string>
+        <string name="min_queue">Ελάχιστος αριθμός zip!</string>
+        <string name="screenshot_saved">Το στιγμιότυπο αποθηκεύτηκε στο {1}</string>
+        <string name="screenshot_err">Απέτυχε η λήψη στιγμιότυπου οθόνης!</string>
+        <string name="zip_wipe_cache">Ένα ή περισσότερα zip ζήτησε εκκαθάριση cache -- Εκκαθάριση cache τώρα.</string>
+        <string name="and_sec_wipe_err">Αποτυχία εκκαθάρισης android secure</string>
+        <string name="dalvik_wipe_err">Αποτυχία εκκαθάρισης dalvik</string>
+        <string name="auto_gen">(Αυτόματη δημιουργία)</string>
+        <string name="curr_date">(Τρέχουσα ημερομηνία)</string>
+        <string name="backup_name_len">Το όνομα του αντίγραφου ασφαλείας είναι πολύ μεγάλο.</string>
+        <string name="backup_name_invalid">Το όνομα αντ. ασφ. '{1}' περιέχει μη έγκυρους χαρακτήρες: '{1}'</string>
+        <string name="no_real_sdcard">Αυτή η συσκευή δεν έχει πραγματική κάρτα SD! Ματαίωση!</string>
+        <string name="cancel_sideload">Ακύρωση ADB sideload...</string>
+        <string name="change_fs_err">Σφάλμα στην αλλαγή αρχείων συστήματος.</string>
+        <string name="theme_ver_err">Η έκδοση του θέματος δεν ταιριάζει με αυτή του TWRP. Χρήση προεπιλεγμένου θέματος.</string>
+        <string name="up_a_level">(Προηγ. Επίπεδο)</string>
+        <string name="install_reboot" version="2">Επανεκκίνηση σε %tw_sleep% δευτ.</string>
+        <string name="adbbackup_error">Σφάλμα με την αντιγρ. ασφ. με το ADB. Ακύρωση..."</string>
+        <string name="adbbackup_control_error">Δεν είναι δυνατή η εγγραφή στο κανάλι ελέγχου του adb</string>
+        <string name="twrp_adbbu_option">--η επιλογή twrp απαιτείτε για την ενεργοποίηση των αντ. ασφ. twrp adb</string>
+        <string name="partition_not_found">διαδρομή: {1} δεν βρέθηκε στην λίστα τομέων</string>
+        <string name="copy_kernel_log">Το log του kernel αντιγράφηκε στο {1}</string>
+        <string name="include_kernel_log">Εισαγωγή Kernel Log</string>
+    </resources>
+</language>
diff --git a/gui/theme/common/languages/en.xml b/gui/theme/common/languages/en.xml
new file mode 100755
index 0000000..1f3e494
--- /dev/null
+++ b/gui/theme/common/languages/en.xml
@@ -0,0 +1,797 @@
+<?xml version="1.0"?>
+
+<language>
+	<display>English</display>
+
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<!-- Partition display names -->
+		<string name="system">System</string>
+		<string name="system_image">System Image</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Vendor Image</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="data_backup">Data (excl. storage)</string>
+		<string name="sdcard">SD card</string>
+		<string name="internal">Internal Storage</string>
+		<string name="microsd">Micro SD card</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Adopted Data</string>
+		<string name="adopted_storage">Adopted Storage</string>
+		<string name="autostorage">Storage</string>
+
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% &#xB0;C</string>
+		<string name="battery_pct">Battery: %tw_battery%</string>
+		<string name="sort_by_name">Sort by Name</string>
+		<string name="sort_by_date">Sort by Date</string>
+		<string name="sort_by_size">Sort by Size</string>
+		<string name="sort_by_name_only">Name</string>
+		<string name="sort_by_date_only">Date</string>
+		<string name="sort_by_size_only">Size</string>
+		<string name="tab_general">GENERAL</string>
+		<string name="tab_options">OPTIONS</string>
+		<string name="tab_backup">BACKUP</string>
+		<string name="tab_time_zone">TIME ZONE</string>
+		<string name="tab_screen">SCREEN</string>
+		<string name="tab_vibration">VIBRATION</string>
+		<string name="tab_language">LANGUAGE</string>
+
+		<string name="install_btn">Install</string>
+		<string name="wipe_btn">Wipe</string>
+		<string name="backup_btn">Backup</string>
+		<string name="restore_btn">Restore</string>
+		<string name="mount_btn">Mount</string>
+		<string name="settings_btn">Settings</string>
+		<string name="advanced_btn">Advanced</string>
+		<string name="reboot_btn">Reboot</string>
+		<string name="files_btn">Files</string>
+		<string name="copy_log_btn">Copy Log</string>
+		<string name="select_type_hdr">Select Type</string>
+		<string name="install_zip_hdr">Install Zip</string>
+		<string name="install_zip_btn">Install Zip</string>
+		<string name="install_image_hdr">Install Image</string>
+		<string name="install_image_btn">Install Image</string>
+		<string name="install_select_file_hdr">Select File</string>
+		<string name="file_selector_folders_hdr">Folders</string>
+		<string name="select_file_from_storage">Select File from %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Install</string>
+		<string name="select_storage_hdr">Select Storage</string>
+		<string name="select_storage_btn">Select Storage</string>
+		<string name="queue_hdr">Queue</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% of max of 10 Files queued</string>
+		<string name="zip_queue_count_s">File %tw_zip_queue_count% of 10:</string>
+		<string name="zip_warn1">This operation may install incompatible</string>
+		<string name="zip_warn2">software and render your device unusable.</string>
+		<string name="zip_back_cancel">Press back to cancel adding this zip.</string>
+		<string name="zip_back_clear">Press back button to clear the queue.</string>
+		<string name="folder">Folder:</string>
+		<string name="file">File:</string>
+		<string name="zip_sig_chk">Zip signature verification</string>
+		<string name="inject_twrp_chk">Inject TWRP after install</string>
+		<string name="install_reboot_chk">Reboot after installation is complete</string>
+		<string name="options_hdr">Options</string>
+		<string name="confirm_flash_hdr">Confirm Flash</string>
+		<string name="zip_queue">Queue:</string>
+		<string name="options">Options:</string>
+		<string name="swipe_confirm">   Confirm</string>
+		<string name="zip_add_btn">Add more Zips</string>
+		<string name="zip_clear_btn">Clear Zip Queue</string>
+		<string name="install_zip_count_hdr">Install Zip %tw_zip_index% of %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Installing Zip: %tw_file%</string>
+		<string name="failed">Failed</string>
+		<string name="successful">Successful</string>
+		<string name="install_failed">Installation Failed</string>
+		<string name="install_successful">Installation Successful</string>
+		<string name="wipe_cache_dalvik_btn">Wipe Cache/Dalvik</string>
+		<string name="wipe_dalvik_btn">Wipe Dalvik</string>
+		<string name="reboot_system_btn">Reboot System</string>
+		<string name="install_sel_target">Select Target Partition</string>
+		<string name="flash_image_select">Select Partition to Flash Image:</string>
+		<string name="target_partition">Target Partition:</string>
+		<string name="flashing_image">Flashing Image...</string>
+		<string name="image_flashed">Image Flashed</string>
+		<string name="wipe_cache_dalvik_confirm">Wipe Cache &amp; Dalvik?</string>
+		<string name="wipe_dalvik_confirm">Wipe Dalvik?</string>
+		<string name="wiping_cache_dalvik">Wiping Cache &amp; Dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Cache &amp; Dalvik Wipe Complete</string>
+		<string name="wipe_dalvik_complete">Dalvik Wipe Complete</string>
+		<string name="swipe_wipe">Swipe to Wipe</string>
+		<string name="swipe_wipe_s">   Wipe</string>
+		<string name="no_os1">No OS Installed! Are you</string>
+		<string name="no_osrb">sure you wish to reboot?</string>
+		<string name="no_ospo">sure you wish to power off?</string>
+		<string name="rebooting">Rebooting...</string>
+		<string name="swipe_reboot">Swipe to Reboot</string>
+		<string name="swipe_reboot_s">   Reboot</string>
+		<string name="reboot_install_app_hdr">Install TWRP App</string>
+		<string name="reboot_install_app1">Would you like to install the Official TWRP App?</string>
+		<string name="reboot_install_app2">The app can check for new TWRP versions.</string>
+		<string name="reboot_install_app_prompt_install">Prompt to install TWRP app if not installed</string>
+		<string name="reboot_install_app_system">Install as a System App</string>
+		<string name="reboot_installing_app">Installing App...</string>
+		<string name="swipe_to_install_app">Swipe to Install TWRP App</string>
+		<string name="uninstall_twrp_system_app">Uninstall TWRP App from System</string>
+		<string name="uninstall_twrp_system_app_confirm">Uninstall TWRP App from System?</string>
+		<string name="uninstalling_twrp_system_app">Uninstalling TWRP App from System...</string>
+		<string name="uninstall_twrp_system_app_complete">Uninstall TWRP App from System Complete</string>
+		<string name="swipe_flash">Swipe to confirm Flash</string>
+		<string name="confirm_action">Confirm Action</string>
+		<string name="back_cancel">Press back button to cancel.</string>
+		<string name="cancel_btn">Cancel</string>
+		<string name="wipe_hdr">Wipe</string>
+		<string name="factory_reset_hdr">Factory Reset</string>
+		<string name="factory_reset_btn">Factory Reset</string>
+		<string name="factory_reset1">Wipes Data, Cache, and Dalvik</string>
+		<string name="factory_reset2">(not including internal storage)</string>
+		<string name="factory_reset3">Most of the time this is</string>
+		<string name="factory_reset4">the only wipe that you need.</string>
+		<string name="factory_reset5">(not including users/lockscreen)</string>
+		<string name="factory_resetting">Factory Reset...</string>
+		<string name="advanced_wipe_hdr">Advanced Wipe</string>
+		<string name="advanced_wipe_btn">Advanced Wipe</string>
+		<string name="wipe_enc_confirm">Wipe Encryption from Data?</string>
+		<string name="formatting_data">Formatting Data...</string>
+		<string name="swipe_format_data">Swipe to Format Data</string>
+		<string name="swipe_format_data_s">   Format Data</string>
+		<string name="factory_reset_complete">Factory Reset Complete</string>
+		<string name="sel_part_hdr">Select Partitions</string>
+		<string name="wipe_sel_confirm">Wipe Selected Partition(s)?</string>
+		<string name="wiping_part">Wiping Partition(s)...</string>
+		<string name="wipe_complete">Wipe Complete</string>
+		<string name="sel_part_wipe">Select Partitions to Wipe:</string>
+		<string name="invalid_part_sel">Invalid partition selection</string>
+		<string name="format_data_hdr">Format Data</string>
+		<string name="format_data_btn">Format Data</string>
+		<string name="format_data_ptr1">Format Data will wipe all of your apps,</string>
+		<string name="format_data_ptr2">backups, pictures, videos, media, and</string>
+		<string name="format_data_ptr3">removes encryption on internal storage.</string>
+		<string name="format_data_adopted">Including Adopted Storage</string>
+		<string name="format_data_lcp1">Format Data will wipe all of your apps, backups, pictures, videos, media, and</string>
+		<string name="format_data_lcp2">removes encryption on internal storage.</string>
+		<string name="format_data_wtc1">Format Data will wipe all of your apps,</string>
+		<string name="format_data_wtc2">backups and media. This cannot be undone.</string>
+		<string name="format_data_undo">This cannot be undone.</string>
+		<string name="format_data_complete">Data Format Complete</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">Type yes to continue.  Press back to cancel.</string>
+		<string name="part_opt_hdr">Partition Options for: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Select Action</string>
+		<string name="file_sys_opt">File System Options</string>
+		<string name="partition">Partition: %tw_partition_name%</string>
+		<string name="part_mount_point">Mount Point: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">File system: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Present: Yes</string>
+		<string name="part_present_no">Present: No</string>
+		<string name="part_removable_yes">Removable: Yes</string>
+		<string name="part_removable_no">Removable: No</string>
+		<string name="part_size">Size: %tw_partition_size%MB</string>
+		<string name="part_used">Used: %tw_partition_used%MB</string>
+		<string name="part_free">Free: %tw_partition_free%MB</string>
+		<string name="part_backup_size">Backup Size: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">Resize File System</string>
+		<string name="resize_btn_s">Resize</string>
+		<string name="resize_confirm">Resize %tw_partition_name%?</string>
+		<string name="resizing">Resizing...</string>
+		<string name="resize_complete">Resize Complete</string>
+		<string name="swipe_resize">Swipe to Resize</string>
+		<string name="swipe_resize_s">   Resize</string>
+		<string name="repair_btn">Repair File System</string>
+		<string name="repair_btn_s">Repair</string>
+		<string name="repair_confirm">Repair %tw_partition_name%?</string>
+		<string name="repairing">Repairing...</string>
+		<string name="repair_complete">Repair Complete</string>
+		<string name="swipe_repair">Swipe to Repair</string>
+		<string name="swipe_repair_s">   Repair</string>
+		<string name="change_fs_btn">Change File System</string>
+		<string name="change_fs_btn_s">Change</string>
+		<string name="change_fs_for_hdr">Change File System for: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Partition: %tw_partition_name% &gt; Select File System</string>
+		<string name="change_fs_warn1">Some ROMs or kernels may not support some</string>
+		<string name="change_fs_warn2">file systems. Proceed with caution!</string>
+		<string name="change_fs_confirm">Change %tw_partition_name%?</string>
+		<string name="formatting">Formatting...</string>
+		<string name="format_complete">Format Complete</string>
+		<string name="swipe_change_fs">Swipe to Change</string>
+		<string name="swipe_change_s">   Change</string>
+		<string name="back_btn">Back</string>
+		<string name="wipe_enc_btn">Wipe Encryption</string>
+		<string name="swipe_factory_reset">Swipe to Factory Reset</string>
+		<string name="repair_change_btn">Repair or Change File System</string>
+		<string name="storage_hdr">Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Backup</string>
+		<string name="backup_confirm_hdr">Confirm Backup</string>
+		<string name="encryption_tab">ENCRYPTION</string>
+		<string name="encryption">Encryption:</string>
+		<string name="name">Name:</string>
+		<string name="sel_part_backup">Select Partitions to Backup:</string>
+		<string name="storage">Storage:</string>
+		<string name="enc_disabled">disabled - set a password to enable</string>
+		<string name="enc_enabled">enabled</string>
+		<string name="enable_backup_comp_chk">Enable compression</string>
+		<string name="skip_digest_backup_chk" version="2">Skip Digest generation during backup</string>
+		<string name="disable_backup_space_chk" version="2">Disable free space check before backup</string>
+		<string name="skip_digest_zip_chk">Skip Digest check before installing zip</string>
+		<string name="current_boot_slot">Current Slot: %tw_active_slot%</string>
+		<string name="boot_slot_a">Slot A</string>
+		<string name="boot_slot_b">Slot B</string>
+		<string name="changing_boot_slot">Changing Boot Slot</string>
+		<string name="changing_boot_slot_complete">Changing Boot Slot Complete</string>
+		<string name="refresh_sizes_btn">Refresh Sizes</string>
+		<string name="swipe_backup">Swipe to Backup</string>
+		<string name="append_date_btn">Append Date</string>
+		<string name="backup_name_exists">A backup with that name already exists!</string>
+		<string name="encrypt_backup">Encrypt your Backup?</string>
+		<string name="enter_pass">Enter Password:</string>
+		<string name="enter_pass2">Enter Password Again:</string>
+		<string name="pass_not_match">Passwords do not match!</string>
+		<string name="partitions">Partitions:</string>
+		<string name="disabled">Disabled</string>
+		<string name="enabled">Enabled</string>
+		<string name="backup_name_hdr">Set Backup Name</string>
+		<string name="progress">Progress:</string>
+		<string name="backup_complete">Backup Complete</string>
+		<string name="restore_hdr">Restore</string>
+		<string name="sel_backup_hdr">Select Backup</string>
+		<string name="restore_sel_store_hdr">Select Backup from %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Select Package to Restore:</string>
+		<string name="restore_enc_backup_hdr">Encrypted Backup</string>
+		<string name="restore_dec_fail">Password failed, please try again!</string>
+		<string name="del_backup_btn">Delete Backup</string>
+		<string name="del_backup_confirm">Delete Backup?</string>
+		<string name="del_backup_confirm2">This cannot be undone!</string>
+		<string name="deleting_backup">Deleting Backup...</string>
+		<string name="backup_deleted">Backup Delete Complete</string>
+		<string name="swipe_delete">Swipe to Delete</string>
+		<string name="swipe_delete_s">   Delete</string>
+		<string name="restore_try_decrypt">Encrypted Backup - Trying Decryption</string>
+		<string name="restore_try_decrypt_s">Trying Decryption</string>
+		<string name="restore_backup_date">Backup made on %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Select Partitions to Restore:</string>
+		<string name="restore_enable_digest_chk" version="2">Enable Digest Verification of Backup Files</string>
+		<string name="restore_complete">Restore Complete</string>
+		<string name="swipe_restore">Swipe to Restore</string>
+		<string name="swipe_restore_s">   Restore</string>
+		<string name="rename_backup_hdr">Rename Backup</string>
+		<string name="rename_backup_confirm">Rename Backup?</string>
+		<string name="rename_backup_confirm2">This cannot be undone!</string>
+		<string name="renaming_backup">Renaming Backup...</string>
+		<string name="rename_backup_complete">Backup Rename Complete</string>
+		<string name="swipe_to_rename">Swipe to Rename</string>
+		<string name="swipe_rename">   Rename</string>
+		<string name="confirm_hdr">Confirm</string>
+		<string name="mount_hdr">Mount</string>
+		<string name="mount_sel_part">Select Partitions to Mount:</string>
+		<string name="mount_sys_ro_chk">Mount system partition read-only</string>
+		<string name="mount_sys_ro_s_chk">Mount System RO</string>
+		<string name="decrypt_data_btn">Decrypt Data</string>
+		<string name="disable_mtp_btn">Disable MTP</string>
+		<string name="enable_mtp_btn">Enable MTP</string>
+		<string name="mount_usb_storage_btn">Mount USB Storage</string>
+		<string name="usb_storage_hdr">USB Storage</string>
+		<string name="usb_stor_mnt1">USB Storage Mounted</string>
+		<string name="usb_stor_mnt2">Be sure to safely remove your device</string>
+		<string name="usb_stor_mnt3">from your computer before unmounting!</string>
+		<string name="unmount_btn">Unmount</string>
+		<string name="rb_system_btn">System</string>
+		<string name="rb_poweroff_btn">Power Off</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Download</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Turning Off...</string>
+		<string name="swipe_power_off">Swipe to Power Off</string>
+		<string name="swipe_power_off_s">Power Off</string>
+		<string name="sys_ro_hdr">Unmodified System Partition</string>
+		<string name="sys_ro_keep">Keep System Read only?</string>
+		<string name="sys_rop1">TWRP can leave your system partition unmodified</string>
+		<string name="sys_rop2">to make it easier for you to take official updates.</string>
+		<string name="sys_rop3">TWRP will be unable to prevent the stock ROM from</string>
+		<string name="sys_rop4">replacing TWRP and will not offer to root your device.</string>
+		<string name="sys_rop5">Installing zips or performing adb operations may still</string>
+		<string name="sys_rop6">modify the system partition.</string>
+		<string name="sys_rol1">TWRP can leave your system partition unmodified to make it easier for you to take official updates.</string>
+		<string name="sys_rol2">TWRP will be unable to prevent the stock ROM from replacing TWRP and will not offer to root your device.</string>
+		<string name="sys_rol3">Installing zips or performing adb operations may still modify the system partition.</string>
+		<string name="sys_ro_never_show_chk">Never show this screen during boot again</string>
+		<string name="sys_ro_keep_ro_btn">Keep Read Only</string>
+		<string name="swipe_allow_mod">Swipe to Allow Modifications</string>
+		<string name="swipe_allow_mod_s">Allow Modifications</string>
+		<string name="settings_hdr">Settings</string>
+		<string name="settings_gen_hdr">General</string>
+		<string name="settings_gen_s_hdr">General</string>
+		<string name="settings_gen_btn">General</string>
+		<string name="use_rmrf_chk">Use rm -rf instead of formatting</string>
+		<string name="auto_reflashtwrp_chk">Automatically Reflash TWRP after flashing a ROM</string>
+		<string name="use24clock_chk">Use 24-hour clock</string>
+		<string name="rev_navbar_chk">Reversed navbar layout</string>
+		<string name="simact_chk">Simulate actions for theme testing</string>
+		<string name="simfail_chk">Simulate failure for actions</string>
+		<string name="ctr_navbar_rdo">Center navbar buttons</string>
+		<string name="lft_navbar_rdo">Left align navbar buttons</string>
+		<string name="rht_navbar_rdo">Right align navbar buttons</string>
+		<string name="restore_defaults_btn">Restore Defaults</string>
+		<string name="settings_tz_btn">Time Zone</string>
+		<string name="settings_screen_btn">Screen</string>
+		<string name="settings_screen_bright_btn">Screen Brightness</string>
+		<string name="vibration_disabled">Vibration is disabled for this device</string>
+		<string name="settings_vibration_btn">Vibration</string>
+		<string name="settings_language_btn">Language</string>
+		<string name="time_zone_hdr">Time Zone</string>
+		<string name="sel_tz_list">Select Time Zone:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) Samoa, Midway Island</string>
+		<string name="utcm10">(UTC -10) Hawaii</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm8">(UTC -8) Pacific Time</string>
+		<string name="utcm7">(UTC -7) Mountain Time</string>
+		<string name="utcm6">(UTC -6) Central Time</string>
+		<string name="utcm5">(UTC -5) Eastern Time</string>
+		<string name="utcm4">(UTC -4) Atlantic Time</string>
+		<string name="utcm3">(UTC -3) Brazil, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Mid-Atlantic</string>
+		<string name="utcm1">(UTC -1) Azores, Cape Verde</string>
+		<string name="utc0">(UTC  0) London, Dublin, Lisbon</string>
+		<string name="utcp1">(UTC +1) Berlin, Brussels, Paris</string>
+		<string name="utcp2">(UTC +2) Athens, Istanbul, South Africa</string>
+		<string name="utcp3">(UTC +3) Moscow, Baghdad</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC +5) Yekaterinburg, Islamabad</string>
+		<string name="utcp6">(UTC +6) Almaty, Dhaka, Colombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp8">(UTC +8) Beijing, Singapore, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokyo, Seoul, Yakutsk</string>
+		<string name="utcp10">(UTC +10) Eastern Australia, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Solomon Islands</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fiji</string>
+		<string name="sel_tz_offset">Select Offset (usually 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">None</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Use daylight savings time (DST)</string>
+		<string name="curr_tz">Current Time Zone: %tw_time_zone%</string>
+		<string name="curr_tz_s">Current Time Zone:</string>
+		<string name="set_tz_btn">Set Time Zone</string>
+		<string name="settings_screen_hdr">Screen</string>
+		<string name="settings_screen_timeout_hdr">Screen Timeout</string>
+		<string name="enable_timeout_chk">Enable screen timeout</string>
+		<string name="screen_to_slider">Screen timeout in seconds:</string>
+		<string name="screen_to_slider_s">Screen timeout in seconds (0=disabled): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Screen timeout setting unavailable</string>
+		<string name="screen_bright_slider">Brightness: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Brightness setting unavailable</string>
+		<string name="vibration_hdr">Vibration</string>
+		<string name="button_vibration_hdr">Button Vibration</string>
+		<string name="kb_vibration_hdr">Keyboard Vibration</string>
+		<string name="act_vibration_hdr">Action Vibration</string>
+		<string name="button_vibration">Button Vibration:</string>
+		<string name="kb_vibration">Keyboard Vibration:</string>
+		<string name="act_vibration">Action Vibration:</string>
+		<string name="select_language">Select Language:</string>
+		<string name="sel_lang_btn">Select Language</string>
+		<string name="set_language_btn">Set Language</string>
+		<string name="advanced_hdr">Advanced</string>
+		<string name="copy_log_confirm">Copy Log to SD card?</string>
+		<string name="copying_log" version="2">Copying Logs to SD card...</string>
+		<string name="copy_log_complete" version="2">Logs Copy Complete</string>
+		<string name="fix_context_btn">Fix Contexts</string>
+		<string name="part_sd_btn">Partition SD card</string>
+		<string name="part_sd_s_btn">SD card</string>
+		<string name="file_manager_btn">File Manager</string>
+		<string name="language_hdr">Language</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="reload_theme_btn">Reload Theme</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Inject TWRP</string>
+		<string name="inject_twrp_confirm">Re-Inject TWRP?</string>
+		<string name="injecting_twrp">Re-Injecting TWRP...</string>
+		<string name="inject_twrp_complete">TWRP Injection Complete</string>
+		<string name="swipe_to_confirm">Swipe to Confirm</string>
+		<string name="part_sd_hdr">Partition SD card</string>
+		<string name="invalid_partsd_sel">You must select a removable device</string>
+		<string name="part_sd_lose">You will lose all files on your SD card!</string>
+		<string name="part_sd_undo">This action cannot be undone!</string>
+		<string name="part_sd_ext_sz">EXT Size:</string>
+		<string name="part_sd_swap_sz">Swap Size:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">File System:</string>
+		<string name="swipe_part_sd">Swipe to Partition</string>
+		<string name="swipe_part_sd_s">Partition</string>
+		<string name="partitioning_sd">Partitioning SD card...</string>
+		<string name="partitioning_sd2">This will take a few minutes.</string>
+		<string name="part_sd_complete">Partitioning Complete</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Restore Original Boot</string>
+		<string name="dumlock_restore_confirm">Restore original boot image?</string>
+		<string name="dumlock_restoring">Restoring Original Boot...</string>
+		<string name="dumlock_restore_complete">Restore Original Boot Complete</string>
+		<string name="dumlock_reflash_btn">Reflash Recovery</string>
+		<string name="dumlock_reflash_confirm">Reflash recovery to boot?</string>
+		<string name="dumlock_reflashing">Flashing recovery to boot...</string>
+		<string name="dumlock_reflash_complete">Recovery Flash to Boot Complete</string>
+		<string name="dumlock_install_btn">Install HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Install HTC dumlock files to ROM?</string>
+		<string name="dumlock_installing">Installing HTC Dumlock...</string>
+		<string name="dumlock_install_complete">HTC Dumlock Install Complete</string>
+		<string name="swipe_to_unlock">Swipe to Unlock</string>
+		<string name="swipe_unlock">   Unlock</string>
+		<string name="fm_hdr">File Manager</string>
+		<string name="fm_sel_file">Select a File or Folder</string>
+		<string name="fm_type_folder">Folder</string>
+		<string name="fm_type_file">File</string>
+		<string name="fm_choose_act">Choose Action</string>
+		<string name="fm_selected">%tw_fm_type% selected:</string>
+		<string name="fm_copy_btn">Copy</string>
+		<string name="fm_copy_file_btn">Copy File</string>
+		<string name="fm_copy_folder_btn">Copy Folder</string>
+		<string name="fm_copying">Copying</string>
+		<string name="fm_move_btn">Move</string>
+		<string name="fm_moving">Moving</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Delete</string>
+		<string name="fm_deleting">Deleting</string>
+		<string name="fm_rename_btn">Rename</string>
+		<string name="fm_rename_file_btn">Rename File</string>
+		<string name="fm_rename_folder_btn">Rename Folder</string>
+		<string name="fm_renaming">Renaming</string>
+		<string name="fm_sel_dest">Select Destination Folder</string>
+		<string name="fm_sel_curr_folder">Select Current Folder</string>
+		<string name="fm_rename_hdr">Rename</string>
+		<string name="fm_set_perms_hdr">Set Permissions</string>
+		<string name="fm_perms">Permissions:</string>
+		<string name="fm_complete">File Operation Complete</string>
+		<string name="fm_open_terminal_btn">Open Terminal Here</string>
+		<string name="fm_edit_file">Edit File</string>
+		<string name="decrypt_data_hdr">Decrypt Data</string>
+		<string name="decrypt_data_enter_pass">Enter Password.</string>
+		<string name="decrypt_data_failed">Password failed, please try again!</string>
+		<string name="decrypt_data_failed_pattern">Pattern failed, please try again!</string>
+		<string name="decrypt_data_enter_pattern">Enter Pattern.</string>
+		<string name="decrypt_data_trying">Trying Decryption</string>
+		<string name="decrypt_data_vold_os_missing">Missing files needed for vold decrypt: {1}</string>
+		<string name="term_hdr">Terminal Command</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">KILL</string>
+		<string name="term_sel_folder_hdr">Browse to Starting Folder</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Wipe Dalvik Cache</string>
+		<string name="sideload_wipe_cache_chk">Wipe Cache</string>
+		<string name="swipe_to_sideload">Swipe to Start Sideload</string>
+		<string name="swipe_sideload">   Start</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Usage: adb sideload filename.zip</string>
+		<string name="sideload_complete">ADB Sideload Complete</string>
+		<string name="fix_contexts_hdr">Fix Contexts</string>
+		<string name="fix_contexts_note1">Note: Fixing contexts is rarely needed.</string>
+		<string name="fix_contexts_note2">Fixing SELinux Contexts may cause</string>
+		<string name="fix_contexts_note3">your device to not boot properly.</string>
+		<string name="swipe_to_fix_contexts">Swipe to Fix Contexts</string>
+		<string name="swipe_fix_contexts">   Fix Contexts</string>
+		<string name="fixing_contexts">Fixing Contexts...</string>
+		<string name="fix_contexts_complete">Fix Contexts Complete</string>
+		<string name="reboot_hdr">Reboot</string>
+		<string name="install_cancel">Do Not Install</string>
+		<string name="sel_storage_list">Select Storage</string>
+		<string name="ok_btn">OK</string>
+		<string name="install_twrp_ramdisk">Install Recovery Ramdisk</string>
+		<string name="install_kernel">Install Kernel</string>
+		<string name="repack_kernel_confirm_hdr">Install Kernel</string>
+		<string name="repack_ramdisk_confirm_hdr">Install Recovery</string>
+		<string name="repack_kernel_confirm">Install Kernel?</string>
+		<string name="repack_ramdisk_confirm">Install Recovery?</string>
+		<string name="repack_backup_first">Back up existing image first</string>
+		<string name="repack">Repack</string>
+		<string name="swipe_to_install">Swipe to Install</string>
+		<string name="installing">Installing...</string>
+		<string name="install_complete">Install Complete</string>
+		<string name="unpack_error">Error unpacking image.</string>
+		<string name="repack_error">Error repacking image.</string>
+		<string name="modified_ramdisk_error">ramdisk files have been modified, unable to create ramdisk to flash, fastboot boot twrp and try this option again or use the Install Recovery Ramdisk option.</string>
+		<string name="create_ramdisk_error">failed to create ramdisk to flash.</string>
+		<string name="unpacking_image">Unpacking {1}...</string>
+		<string name="repacking_image">Repacking {1}...</string>
+		<string name="repack_image_hdr">Select Image</string>
+		<string name="repack_overwrite_warning">If device was previously rooted, then root has been overwritten and will need to be reinstalled.</string>
+		<string name="reflash_twrp">Flash Current TWRP</string>
+		<string name="reflash_twrp_confirm">Flash Current TWRP?</string>
+		<string name="reflashing_twrp">Flashing TWRP...</string>
+		<string name="reflash_twrp_complete">Done flashing TWRP</string>
+		<string name="fix_recovery_loop">Fix Recovery Bootloop</string>
+		<string name="fix_recovery_loop_confirm">Fix Recovery Bootloop?</string>
+		<string name="fixing_recovery_loop">Fixing Recovery Bootloop...</string>
+		<string name="fix_recovery_loop_complete">Fix Recovery Bootloop Complete</string>
+		<string name="fixing_recovery_loop_patch">Patching kernel...</string>
+		<string name="fix_recovery_loop_patch_error">Error patching kernel.</string>
+		<string name="decrypt_users">Decrypt Users</string>
+		<string name="decrypt_users_selection">Select a user ID to decrypt</string>
+		<string name="select_user">Select User</string>
+		<string name="backup_storage_undecrypt_warning">Backup will not include some files of user {1}, because the user is not decrypted.</string>
+		<string name="decrypting_user_fbe">Attempting to decrypt FBE for user {1}...</string>
+		<string name="decrypt_user_success_fbe">User {1} Decrypted Successfully</string>
+		<string name="decrypt_user_fail_fbe">Failed to decrypt user {1}</string>
+		<string name="decrypt_data_enter_pass_fbe">Enter Password for User [%tw_crypto_user_id%]</string>
+		<string name="decrypt_data_enter_pattern_fbe">Enter Pattern for User [%tw_crypto_user_id%]</string>
+		<string name="multiuser_warning1">Not all users decrypted!</string>
+		<string name="multiuser_warning2">Backup/restore operations may fail!</string>
+		<string name="multiuser_warning_accept">Continue Anyway</string>
+		<string name="multiuser_warning_hdr">Multiuser Warning</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">Kernel does not have support for reading SELinux contexts.</string>
+		<string name="full_selinux">Full SELinux support is present.</string>
+		<string name="no_selinux">No SELinux support (no libselinux).</string>
+		<string name="mtp_enabled">MTP Enabled</string>
+		<string name="mtp_crash">MTP Crashed, not starting MTP on boot.</string>
+		<string name="decrypt_success">Successfully decrypted with default password.</string>
+		<string name="unable_to_decrypt">Unable to decrypt with default password. You may need to perform a Format Data.</string>
+		<string name="generating_digest1" version="2">Generating Digest</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * Generating digest...</string>
+		<string name="digest_created" version="2"> * Digest Created.</string>
+		<string name="digest_error" version="2"> * Digest Error!</string>
+		<string name="digest_compute_error" version="2"> * Error computing Digest.</string>
+		<string name="current_date">(Current Date)</string>
+		<string name="auto_generate">(Auto Generate)</string>
+		<string name="unable_to_locate_partition">Unable to locate '{1}' partition for backup calculations.</string>
+		<string name="no_partition_selected">No partitions selected for backup.</string>
+		<string name="total_partitions_backup"> * Total number of partitions to back up: {1}</string>
+		<string name="total_backup_size"> * Total size of all data: {1}MB</string>
+		<string name="available_space"> * Available space: {1}MB</string>
+		<string name="unable_locate_storage">Unable to locate storage device.</string>
+		<string name="no_space">Not enough free space on storage.</string>
+		<string name="backup_started">[BACKUP STARTED]</string>
+		<string name="backup_folder"> * Backup Folder: {1}</string>
+		<string name="fail_backup_folder">Failed to make backup folder.</string>
+		<string name="avg_backup_fs">Average backup rate for file systems: {1} MB/sec</string>
+		<string name="avg_backup_img">Average backup rate for imaged drives: {1} MB/sec</string>
+		<string name="total_backed_size">[{1} MB TOTAL BACKED UP]</string>
+		<string name="backup_completed">[BACKUP COMPLETED IN {1} SECONDS]</string>
+		<string name="restore_started">[RESTORE STARTED]</string>
+		<string name="restore_folder">Restore folder: '{1}'</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} done ({2} seconds)]</string>
+		<string name="verifying_digest" version="2">Verifying Digest</string>
+		<string name="skip_digest" version="2">Skipping Digest check based on user setting.</string>
+		<string name="calc_restore">Calculating restore details...</string>
+		<string name="restore_read_only">Cannot restore {1} -- mounted read only.</string>
+		<string name="restore_unable_locate">Unable to locate '{1}' partition for restoring.</string>
+		<string name="no_part_restore">No partitions selected for restore.</string>
+		<string name="restore_part_count">Restoring {1} partitions...</string>
+		<string name="total_restore_size">Total restore size is {1}MB</string>
+		<string name="updating_system_details">Updating System Details</string>
+		<string name="restore_completed">[RESTORE COMPLETED IN {1} SECONDS]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">Error opening: '{1}' ({2})</string>
+		<string name="unable_locate_part_backup_name">Unable to locate partition by backup name: '{1}'</string>
+		<string name="unable_find_part_path">Unable to find partition for path '{1}'</string>
+		<string name="update_part_details">Updating partition details...</string>
+		<string name="update_part_details_done">...done</string>
+		<string name="wiping_dalvik">Wiping Dalvik Directories...</string>
+		<string name="cleaned">Cleaned: {1}...</string>
+		<string name="cache_dalvik_done">-- Dalvik Cache Directories Wipe Complete!</string>
+		<string name="dalvik_done">-- Dalvik Directory Wipe Complete!</string>
+		<string name="no_andsec">No android secure partitions found.</string>
+		<string name="unable_to_locate">Unable to locate {1}.</string>
+		<string name="wiping_datamedia">Wiping internal storage -- /data/media...</string>
+		<string name="unable_to_mount">Unable to mount {1}</string>
+		<string name="unable_to_mount_internal">Unable to mount internal_storage</string>
+		<string name="unable_to_mount_storage">Unable to mount storage</string>
+		<string name="fail_decrypt">Failed to decrypt data.</string>
+		<string name="no_crypto_support">No crypto support was compiled into this build.</string>
+		<string name="decrypt_success_dev">Data successfully decrypted, new block device: '{1}'</string>
+		<string name="decrypt_success_nodev">Data successfully decrypted</string>
+		<string name="done">Done.</string>
+		<string name="start_partition_sd">Partitioning SD card...</string>
+		<string name="partition_sd_locate">Unable to locate device to partition.</string>
+		<string name="ext_swap_size">EXT + Swap size is larger than sdcard size.</string>
+		<string name="remove_part_table">Removing partition table...</string>
+		<string name="unable_rm_part">Unable to remove partition table.</string>
+		<string name="create_part">Creating {1} partition...</string>
+		<string name="unable_to_create_part">Unable to create {1} partition.</string>
+		<string name="format_sdext_as">Formatting sd-ext as {1}...</string>
+		<string name="part_complete">Partitioning complete.</string>
+		<string name="unable_to_open">Unable to open '{1}'.</string>
+		<string name="mtp_already_enabled">MTP already enabled</string>
+		<string name="mtp_fail">Failed to enable MTP</string>
+		<string name="no_mtp">MTP support not included</string>
+		<string name="image_flash_start">[IMAGE FLASH STARTED]</string>
+		<string name="img_to_flash">Image to flash: '{1}'</string>
+		<string name="flash_unable_locate">Unable to locate '{1}' partition for flashing.</string>
+		<string name="no_part_flash">No partitions selected for flashing.</string>
+		<string name="too_many_flash">Too many partitions selected for flashing.</string>
+		<string name="invalid_flash">Invalid flash partition specified.</string>
+		<string name="flash_done">[IMAGE FLASH COMPLETED]</string>
+		<string name="wiping">Wiping {1}</string>
+		<string name="repair_not_exist">{1} does not exist! Cannot repair!</string>
+		<string name="repairing_using">Repairing {1} using {2}...</string>
+		<string name="unable_repair">Unable to repair {1}.</string>
+		<string name="mount_data_footer">Could not mount /data and unable to find crypto footer.</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">Can not create '{1}' folder ({2}).</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">Failed to mount '{1}' ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">Failed to unmount '{1}' ({2})</string>
+		<string name="cannot_resize">Cannot resize {1}.</string>
+		<string name="repair_resize">Repairing {1} before resizing.</string>
+		<string name="unable_resize">Unable to resize {1}.</string>
+		<string name="no_digest_found" version="2">No digest file found for '{1}'. Please unselect Enable Digest verification to restore.</string>
+		<string name="digest_fail_match" version="2">Digest failed to match on '{1}'.</string>
+		<string name="digest_matched" version="2">Digest matched for '{1}'.</string>
+		<string name="fail_decrypt_tar">Failed to decrypt tar file '{1}'</string>
+		<string name="format_data_msg">You may need to reboot recovery to be able to use /data again.</string>
+		<string name="format_data_err">Unable to format to remove encryption.</string>
+		<string name="formatting_using">Formatting {1} using {2}...</string>
+		<string name="unable_to_wipe">Unable to wipe {1}.</string>
+		<string name="cannot_wipe">Partition {1} cannot be wiped.</string>
+		<string name="remove_all">Removing all files under '{1}'</string>
+		<string name="wiping_data">Wiping data without wiping /data/media ...</string>
+		<string name="backing_up">Backing up {1}...</string>
+		<string name="backup_storage_warning">Backups of {1} do not include any files in internal storage such as pictures or downloads.</string>
+		<string name="backing">Backing Up</string>
+		<string name="backup_size">Backup file size for '{1}' is 0 bytes.</string>
+		<string name="datamedia_fs_restore">WARNING: This /data backup was made with {1} file system! The backup may not boot unless you change back to {1}.</string>
+		<string name="restoring">Restoring {1}...</string>
+		<string name="restoring_hdr">Restoring</string>
+		<string name="recreate_folder_err">Unable to recreate {1} folder.</string>
+		<string name="img_size_err">Size of image is larger than target device</string>
+		<string name="flashing">Flashing {1}...</string>
+		<string name="backup_folder_set">Backup folder set to '{1}'</string>
+		<string name="locate_backup_err">Unable to locate backup '{1}'</string>
+		<string name="set_restore_opt">Setting restore options: '{1}':</string>
+		<string name="digest_check_skip" version="2">Digest check skip is on</string>
+		<string name="ors_encrypt_restore_err">Unable to use OpenRecoveryScript to restore an encrypted backup.</string>
+		<string name="mounting">Mounting</string>
+		<string name="unmounting">Unmounting</string>
+		<string name="mounted">Mounted '{1}'</string>
+		<string name="unmounted">Unmounted '{1}'</string>
+		<string name="setting">Setting '{1}' to '{2}'</string>
+		<string name="setting_empty">Setting '{1}' to empty</string>
+		<string name="making_dir1">Making Directory</string>
+		<string name="making_dir2">Making directory: '{1}'</string>
+		<string name="running_command">Running Command</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">Starting ADB sideload feature...</string>
+		<string name="need_new_adb">You need adb 1.0.32 or newer to sideload to this device.</string>
+		<string name="no_pwd">No password provided.</string>
+		<string name="done_ors">Done processing script file</string>
+		<string name="injecttwrp">Injecting TWRP into boot image...</string>
+		<string name="zip_err">Error installing zip file '{1}'</string>
+		<string name="installing_zip">Installing zip file '{1}'</string>
+		<string name="select_backup_opt">Setting backup options:</string>
+		<string name="compression_on">Compression is on</string>
+		<string name="digest_off" version="2">Digest Generation is off</string>
+		<string name="backup_fail">Backup Failed</string>
+		<string name="backup_clean">Backup Failed. Cleaning Backup Folder.</string>
+		<string name="running_recovery_commands">Running Recovery Commands</string>
+		<string name="recovery_commands_complete">Recovery Commands Complete</string>
+		<string name="running_ors">Running OpenRecoveryScript</string>
+		<string name="ors_complete">OpenRecoveryScript Complete</string>
+		<string name="check_for_digest" version="2">Checking for Digest file...</string>
+		<string name="invalid_zip_format">Invalid zip file format!</string>
+		<string name="fail_sysmap">Failed to map file '{1}'</string>
+		<string name="verify_zip_sig">Verifying zip signature...</string>
+		<string name="verify_zip_fail">Zip signature verification failed!</string>
+		<string name="verify_zip_done">Zip signature verified successfully.</string>
+		<string name="zip_corrupt">Zip file is corrupt!</string>
+		<string name="no_digest" version="2">Skipping Digest check: no Digest file found</string>
+		<string name="digest_fail" version="2">Digest does not match</string>
+		<string name="digest_match" version="2">Digest matched</string>
+		<string name="pid_signal">{1} process ended with signal: {2}</string>
+		<string name="pid_error">{1} process ended with ERROR: {2}</string>
+		<string name="install_dumlock">Installing HTC Dumlock to system...</string>
+		<string name="dumlock_restore">Restoring original boot...</string>
+		<string name="dumlock_reflash">Reflashing recovery to boot...</string>
+		<string name="run_script">Running {1} script...</string>
+		<string name="rename_stock">Renamed stock recovery file in /system to prevent the stock ROM from replacing TWRP.</string>
+		<string name="split_backup">Breaking backup file into multiple archives...</string>
+		<string name="backup_error">Error creating backup.</string>
+		<string name="restore_error">Error during restore process.</string>
+		<string name="split_thread">Splitting thread ID {1} into archive {2}</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">%llu of %llu files, %i%%</string>
+		<string name="size_progress">%lluMB of %lluMB, %i%%</string>
+		<string name="decrypt_cmd" version="2">Attempting to decrypt data partition or user data via command line.</string>
+		<string name="base_pkg_err">Failed to load base packages.</string>
+		<string name="simulating">Simulating actions...</string>
+		<string name="backup_cancel">Backup Cancelled</string>
+		<string name="config_twrp">Configuring TWRP...</string>
+		<string name="config_twrp_err">Unable to configure TWRP with this kernel.</string>
+		<string name="copy_log">Copied recovery log to {1}.</string>
+		<string name="max_queue">Maximum zip queue reached!</string>
+		<string name="min_queue">Minimum zip queue reached!</string>
+		<string name="screenshot_saved">Screenshot was saved to {1}</string>
+		<string name="screenshot_err">Failed to take a screenshot!</string>
+		<string name="zip_wipe_cache">One or more zip requested a cache wipe -- Wiping cache now.</string>
+		<string name="and_sec_wipe_err">Unable to wipe android secure</string>
+		<string name="dalvik_wipe_err">Failed to wipe dalvik</string>
+		<string name="auto_gen">(Auto Generate)</string>
+		<string name="curr_date">(Current Date)</string>
+		<string name="backup_name_len">Backup name is too long.</string>
+		<string name="backup_name_invalid">Backup name '{1}' contains invalid character: '{1}'</string>
+		<string name="no_real_sdcard">This device does not have a real SD card! Aborting!</string>
+		<string name="cancel_sideload">Cancelling ADB sideload...</string>
+		<string name="change_fs_err">Error changing file system.</string>
+		<string name="theme_ver_err">Custom theme version does not match TWRP version. Using stock theme.</string>
+		<string name="up_a_level">(Up A Level)</string>
+		<string name="install_reboot" version="2">Rebooting in %tw_sleep% second(s)</string>
+		<string name="adbbackup_error">Error with ADB Backup. Quitting..."</string>
+		<string name="adbbackup_control_error">Cannot write to adb control channel</string>
+		<string name="twrp_adbbu_option">--twrp option is required to enable twrp adb backup</string>
+		<string name="partition_not_found">path: {1} not found in partititon list</string>
+		<string name="copy_kernel_log">Copied kernel log to {1}</string>
+		<string name="copy_logcat">Copied logcat to {1}</string>
+		<string name="include_kernel_log">Include Kernel Log</string>
+		<string name="include_logcat">Include Logcat</string>
+		<string name="sha2_chk">Use SHA2 for hashing</string>
+		<string name="unable_set_boot_slot">Error changing bootloader boot slot to {1}</string>
+		<string name="unmount_sys_install">Unmount System before installing a ZIP</string>
+		<string name="unmount_system">Unmounting System...</string>
+		<string name="unmount_system_err">Failed unmounting System</string>
+		<string name="flash_ab_inactive">Flashing A/B zip to inactive slot: {1}</string>
+		<string name="flash_ab_reboot">To flash additional zips, please reboot recovery to switch to the updated slot.</string>
+		<string name="flash_ab_both_slots">Flash to both slots</string>
+		<string name="ozip_decrypt_decryption">Starting Ozip Decryption...</string>
+		<string name="ozip_decrypt_finish">Ozip Decryption Finished!</string>
+		<string name="fastboot_button">Fastboot</string>
+		<string name="enable_adb">Enable ADB</string>
+		<string name="enable_fastboot">Enable Fastboot</string>
+		<string name="fastboot_console_msg">Entered Fastboot mode...</string>
+		<string name="data_media_fbe_msg">TWRP will not recreate /data/media on an FBE device. Please reboot into your rom to create /data/media</string>
+		<!-- Android Rescue Party trigger -->
+		<string name="rescue_party0">Android Rescue Party trigger! Possible solutions? Either:</string>
+		<string name="rescue_party1"> 1. Wipe caches, and/or</string>
+		<string name="rescue_party2"> 2. Format data, and/or</string>
+		<string name="rescue_party3"> 3. Clean-flash your ROM.</string>
+		<string name="rescue_party4">The reported problem is: </string>
+		<string name="restore_system_context">Unable to get default context for {1} -- Android may not boot.</string>
+		<string name="change_twrp_folder_btn">Change TWRP folder</string>
+		<string name="change_twrp_folder_on_process">Changing TWRP folder</string>
+		<string name="change_twrp_folder_after_process">TWRP folder changed to</string>
+		<string name="tw_folder_exists">A folder with that name already exists!</string>
+		<string name="unmap_super_devices">Unmap Super Devices</string>
+		<string name="unmap_super_devices_confirm">Unmap all Super Devices?</string>
+		<string name="unmapping_super_devices">Unmapping Super Devices...</string>
+		<string name="unmap_super_devices_complete">Unmapped all Super Devices</string>
+		<string name="remove_dynamic_groups_confirm">Unmap all Super Devices?</string>
+		<string name="remove_dynamic_groups">Unmapping Super Devices...</string>
+		<string name="remove_dynamic_groups_complete">Unmapped all Super Devices</string>
+		<string name="mount_vab_partitions">Devices on super may not mount until rebooting recovery.</string>
+		<string name="merges_snapshots">Merge Snapshots</string>
+		<string name="merge_snapshots_confirm">Merge Snapshots?</string>
+		<string name="merging_snapshots">Merging Snapshots...</string>
+		<string name="merging_snapshots_complete">Merged Snapshots</string>
+		<string name="restore_pin_password">Restore While PIN/Password Enabled?</string>
+		<string name="restore_pattern">Restore While Pattern Enabled?</string>
+		<string name="restore_pin">Restore While PIN Enabled?</string>
+		<string name="continue_restore_encrypted">Continue Restore?</string>
+		<string name="restore_with_pin_password1">PIN/Password is enabled</string>
+		<string name="restore_with_pin_password2">PIN/Password should be disabled before restore</string>
+		<string name="restore_with_pin1">PIN is enabled</string>
+		<string name="restore_with_pin2">PIN should be disabled before restore</string>
+		<string name="restore_with_pattern1">Pattern is enabled</string>
+		<string name="restore_with_pattern2">Pattern should be disabled before restore</string>
+		<string name="reboot_after_restore">It is recommended to reboot Android once after first boot.</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/es.xml b/gui/theme/common/languages/es.xml
new file mode 100644
index 0000000..bdf9b24
--- /dev/null
+++ b/gui/theme/common/languages/es.xml
@@ -0,0 +1,649 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<language>
+	<display>Spanish</display>
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<string name="system">Sistema</string>
+		<string name="system_image">Imagen del Sistema</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Imagen del Vendor</string>
+		<string name="boot">Inicio</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Caché</string>
+		<string name="data">Datos</string>
+		<string name="sdcard">Tarjeta SD</string>
+		<string name="internal">Memoria Interna</string>
+		<string name="microsd">Tarjeta Micro SD</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Caché</string>
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Datos adoptados</string>
+		<string name="adopted_storage">Memoria adoptada</string>
+		<string name="twrp_header">Proyecto Team Win Recovery</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% °C</string>
+		<string name="battery_pct">Batería: %tw_battery%</string>
+		<string name="sort_by_name">Ordenar por Nombre</string>
+		<string name="sort_by_date">Ordenar por Fecha</string>
+		<string name="sort_by_size">Ordenar por Tamaño</string>
+		<string name="sort_by_name_only">Nombre</string>
+		<string name="sort_by_date_only">Fecha</string>
+		<string name="sort_by_size_only">Tamaño</string>
+		<string name="tab_general">GENERAL</string>
+		<string name="tab_options">OPCIONES</string>
+		<string name="tab_backup">COPIA DE SEGURIDAD</string>
+		<string name="tab_time_zone">ZONA HORARIA</string>
+		<string name="tab_screen">PANTALLA</string>
+		<string name="tab_vibration">VIBRACIÓN</string>
+		<string name="tab_language">LENGUAJE</string>
+		<string name="install_btn">Instalar</string>
+		<string name="wipe_btn">Limpiar</string>
+		<string name="backup_btn">Copia de seguridad</string>
+		<string name="restore_btn">Restaurar</string>
+		<string name="mount_btn">Montar</string>
+		<string name="settings_btn">Ajustes</string>
+		<string name="advanced_btn">Avanzado</string>
+		<string name="reboot_btn">Reinicio</string>
+		<string name="files_btn">Archivos</string>
+		<string name="copy_log_btn">Copiar Log</string>
+		<string name="select_type_hdr">Seleccionar Tipo</string>
+		<string name="install_zip_hdr">Instalar Zip</string>
+		<string name="install_zip_btn">Instalar Zip</string>
+		<string name="install_image_hdr">Instalar Imagen</string>
+		<string name="install_image_btn">Instalar Imagen</string>
+		<string name="install_select_file_hdr">Seleccionar Archivo</string>
+		<string name="file_selector_folders_hdr">Carpetas</string>
+		<string name="select_file_from_storage">Seleccionar Archivo desde %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">Carga archivo por ADB</string>
+		<string name="install_hdr">Instalar</string>
+		<string name="select_storage_hdr">Seleccionar Almacenamiento</string>
+		<string name="select_storage_btn">Seleccionar Almacenamiento</string>
+		<string name="queue_hdr">Cola</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% de un máximo de 10 archivos en cola</string>
+		<string name="zip_queue_count_s">Archivo %tw_zip_queue_count% de 10:</string>
+		<string name="zip_warn1">Esta operación podría instalar software</string>
+		<string name="zip_warn2">incompatible y dejar tu dispositivo inutilizable.</string>
+		<string name="zip_back_cancel">Pulsa el botón de atrás para cancelar el añadir este zip.</string>
+		<string name="zip_back_clear">Pulsa el botón de atrás para limpiar la cola.</string>
+		<string name="folder">Carpeta:</string>
+		<string name="file">Archivo:</string>
+		<string name="zip_sig_chk">Verificar la firma del archivo</string>
+		<string name="inject_twrp_chk">Injectar TWRP después de instalar</string>
+		<string name="install_reboot_chk">Reiniciar después de completar la instalación</string>
+		<string name="options_hdr">Opciones</string>
+		<string name="confirm_flash_hdr">Confirmar el Flasheo</string>
+		<string name="zip_queue">Cola:</string>
+		<string name="options">Opciones:</string>
+		<string name="swipe_confirm">   Confirmar</string>
+		<string name="zip_add_btn">Añadir más Zips</string>
+		<string name="zip_clear_btn">Limpiar cola de Zips</string>
+		<string name="install_zip_count_hdr">Instalar Zip %tw_zip_index% de %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Instalando Zip: %tw_file%</string>
+		<string name="failed">Fallido</string>
+		<string name="successful">Éxito</string>
+		<string name="install_failed">Instalación Fallida</string>
+		<string name="install_successful">Instalación Exitosa</string>
+		<string name="wipe_cache_dalvik_btn">Limpiar caché/dalvik</string>
+		<string name="reboot_system_btn">Reiniciar Sistema</string>
+		<string name="install_sel_target">Seleccionar Partición de Destino</string>
+		<string name="flash_image_select">Seleccionar Partición para Flashear Imagen:</string>
+		<string name="target_partition">Partición de Destino:</string>
+		<string name="flashing_image">Flasheando Imagen...</string>
+		<string name="image_flashed">Image Flashed</string>
+		<string name="wipe_cache_dalvik_confirm">Limpiar Caché &amp; Dalvik?</string>
+		<string name="wiping_cache_dalvik">Limpiando Caché &amp; Dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Caché &amp; Dalvik Limpieza Completa</string>
+		<string name="swipe_wipe">Desliza para Limpiar</string>
+		<string name="swipe_wipe_s">   Limpiar</string>
+		<string name="no_os1">¡No hay SO Instalado!</string>
+		<string name="no_osrb">¿Estás seguro de que quieres reiniciar?</string>
+		<string name="no_ospo">¿Estás seguro de que quieres apagar?</string>
+		<string name="rebooting">Reiniciando…</string>
+		<string name="swipe_reboot">Desliza para Reiniciar</string>
+		<string name="swipe_reboot_s">   Reiniciar</string>
+		<string name="swipe_flash">Desliza para confirmar el Flasheo</string>
+		<string name="confirm_action">Confirmar Acción</string>
+		<string name="back_cancel">Pulsa el botón atrás para cancelar.</string>
+		<string name="cancel_btn">Cancelar</string>
+		<string name="wipe_hdr">Limpiar</string>
+		<string name="factory_reset_hdr">Restablecimiento de Fábrica</string>
+		<string name="factory_reset_btn">Restablecimiento de Fábrica</string>
+		<string name="factory_reset1">Limpiar Datos, Caché y Dalvik</string>
+		<string name="factory_reset2">(no incluye almacenamiento interno)</string>
+		<string name="factory_reset3">La mayoría de las veces esta es</string>
+		<string name="factory_reset4">la única limpieza que necesitas.</string>
+		<string name="factory_resetting">Restablecimiento de Fábrica...</string>
+		<string name="advanced_wipe_hdr">Limpieza Avanzada</string>
+		<string name="advanced_wipe_btn">Limpieza Avanzada</string>
+		<string name="wipe_enc_confirm">¿Quitar la encriptación de Datos?</string>
+		<string name="formatting_data">Formateando Datos...</string>
+		<string name="swipe_format_data">Desliza para Formatear Datos</string>
+		<string name="swipe_format_data_s">   Formatear Datos</string>
+		<string name="factory_reset_complete">Restablecimiento de Fábrica Completo</string>
+		<string name="sel_part_hdr">Seleccionar Partición(es)</string>
+		<string name="wipe_sel_confirm">Limpiar Partición(es) Seleccionadas?</string>
+		<string name="wiping_part">Limpiando Partición(es)...</string>
+		<string name="wipe_complete">Limpieza Completa</string>
+		<string name="sel_part_wipe">Seleccionar Partición(es) a Limpiar:</string>
+		<string name="invalid_part_sel">Selección de partición inválida</string>
+		<string name="format_data_hdr">Formatear Datos</string>
+		<string name="format_data_btn">Formatear Datos</string>
+		<string name="format_data_ptr1">Formatear Datos eliminará todas tus aplicaciones,</string>
+		<string name="format_data_ptr2">copias de seguridad, imágenes, vídeos, media y</string>
+		<string name="format_data_ptr3">quita la encriptación del almacenamiento interno.</string>
+		<string name="format_data_adopted">Incluyendo Almacenamiento Adoptado</string>
+		<string name="format_data_lcp1">Formatear Datos elimina todas tus aplicaciones, copias de seguridad, imágenes, vídeos, media, y</string>
+		<string name="format_data_lcp2">quita la encriptación del almacenamiento interno.</string>
+		<string name="format_data_wtc1">Formatear Datos limpiará todas tus aplicaciones,</string>
+		<string name="format_data_wtc2">copias de seguridad y media. Esto no se puede deshacer.</string>
+		<string name="format_data_undo">Esto no se puede deshacer.</string>
+		<string name="format_data_complete">Formateo de Datos Completo</string>
+		<string name="yes_continue">Escribe sí para continuar.  Pulsa atrás para cancelar.</string>
+		<string name="part_opt_hdr">Opciones de Partición para: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Seleccionar Acción</string>
+		<string name="file_sys_opt">Opciones de Sistema de Archivos</string>
+		<string name="partition">Partición: %tw_partition_name%</string>
+		<string name="part_mount_point">Opción de Montaje: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Sistema de archivo: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Presente: Sí</string>
+		<string name="part_present_no">Presente: No</string>
+		<string name="part_removable_yes">Extraíble: Sí</string>
+		<string name="part_removable_no">Extraíble: No</string>
+		<string name="part_size">Tamaño: %tw_partition_size%MB</string>
+		<string name="part_used">En uso: %tw_partition_used%MB</string>
+		<string name="part_free">Libre: %tw_partition_free%MB</string>
+		<string name="part_backup_size">Tamaño copia de seguridad: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">Cambiar el tamaño del Sistema de Archivos</string>
+		<string name="resize_btn_s">Cambiar tamaño</string>
+		<string name="resize_confirm">Cambiar tamaño %tw_partition_name%?</string>
+		<string name="resizing">Cambiando el tamaño...</string>
+		<string name="resize_complete">Cambio de tamaño completo</string>
+		<string name="swipe_resize">Desliza para Cambiar el tamaño</string>
+		<string name="swipe_resize_s">   Cambiar tamaño</string>
+		<string name="repair_btn">Reparar Sistema de Archivos</string>
+		<string name="repair_btn_s">Reparar</string>
+		<string name="repair_confirm">Reparar %tw_partition_name%?</string>
+		<string name="repairing">Reparando...</string>
+		<string name="repair_complete">Reparación Completa</string>
+		<string name="swipe_repair">Desliza para Reparar</string>
+		<string name="swipe_repair_s">   Reparar</string>
+		<string name="change_fs_btn">Cambiar el Sistema de Archivos</string>
+		<string name="change_fs_btn_s">Cambio</string>
+		<string name="change_fs_for_hdr">Cambiar Sistema de Archivos a: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Partición: %tw_partition_name% &gt; Seleccionar Sistema de Archivos</string>
+		<string name="change_fs_warn1">Algunas ROMs o kernels quizás no soporten algunos</string>
+		<string name="change_fs_warn2">sistemas de archivos. Procede con cautela!</string>
+		<string name="change_fs_confirm">Cambiar %tw_partition_name%?</string>
+		<string name="formatting">Formateando...</string>
+		<string name="format_complete">Formateo Completo</string>
+		<string name="swipe_change_fs">Desliza para Cambiar</string>
+		<string name="swipe_change_s">   Cambiar</string>
+		<string name="back_btn">Atrás</string>
+		<string name="wipe_enc_btn">Limpiar Encriptación</string>
+		<string name="swipe_factory_reset">Desliza para Restablecer de Fábrica</string>
+		<string name="repair_change_btn">Reparar ó Cambiar Sistema de Archivos</string>
+		<string name="storage_hdr">Almacenamiento: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Copia de Seguridad</string>
+		<string name="backup_confirm_hdr">Confirmar Copia de Seguridad</string>
+		<string name="encryption_tab">ENCRIPTACIÓN</string>
+		<string name="encryption">Encriptación:</string>
+		<string name="name">Nombre:</string>
+		<string name="sel_part_backup">Seleccionar Partición(es) para Copia de Seguridad:</string>
+		<string name="storage">Almacenamiento:</string>
+		<string name="enc_disabled">desactivado - establezca una contraseña para activar</string>
+		<string name="enc_enabled">activado</string>
+		<string name="enable_backup_comp_chk">Habilitar compresión</string>
+		<string name="skip_digest_backup_chk" version="2">Saltar generación Digest durante respaldo</string>
+		<string name="disable_backup_space_chk">Desactivar comprobación de Espacio Libre</string>
+		<string name="refresh_sizes_btn">Refrescar Tamaños</string>
+		<string name="swipe_backup">Desliza para Respaldar</string>
+		<string name="append_date_btn">Ajustar Fecha</string>
+		<string name="backup_name_exists">Ya existe una copia de seguridad con este nombre.</string>
+		<string name="encrypt_backup">Cifrar tú Copia de seguridad?</string>
+		<string name="enter_pass">Introducir tú contraseña:</string>
+		<string name="enter_pass2">Introducir tú contraseña de nuevo:</string>
+		<string name="pass_not_match">Las Contraseñas no coinciden!</string>
+		<string name="partitions">Particiones:</string>
+		<string name="disabled">Desactivado</string>
+		<string name="enabled">Activado</string>
+		<string name="backup_name_hdr">Establecer nombre de copia de seguridad</string>
+		<string name="progress">Progreso:</string>
+		<string name="backup_complete">¡Copia de seguridad completa!</string>
+		<string name="restore_hdr">Restaurar</string>
+		<string name="sel_backup_hdr">Selecciona la Copia de Seguridad</string>
+		<string name="restore_sel_store_hdr">Seleccionar Copia de Seguridad de %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Selecciona el Paquete para Restaurar:</string>
+		<string name="restore_enc_backup_hdr">Copia de Seguridad Cifrada</string>
+		<string name="restore_dec_fail">¡Contraseña incorrecta, inténtelo de nuevo!</string>
+		<string name="del_backup_btn">Eliminar Copia de seguridad</string>
+		<string name="del_backup_confirm">Eliminar Copia de Seguridad?</string>
+		<string name="del_backup_confirm2">Esto no se puede deshacer!</string>
+		<string name="deleting_backup">Eliminando Copia de Seguridad...</string>
+		<string name="backup_deleted">Eliminar Copia de Seguridad Completado</string>
+		<string name="swipe_delete">Desliza para Eliminar</string>
+		<string name="swipe_delete_s">   Eliminar</string>
+		<string name="restore_try_decrypt">Copia de seguridad cifrada - Intentando Desencriptarla</string>
+		<string name="restore_try_decrypt_s">Intentando Desencriptarla</string>
+		<string name="restore_backup_date">Copia de seguridad creada en %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Selecciona las Particiones para Restaurar:</string>
+		<string name="restore_enable_digest_chk" version="2">Activar Verificación Digest de la copia de seguridad</string>
+		<string name="restore_complete">Restauración Completa</string>
+		<string name="swipe_restore">Desliza para Restaurar</string>
+		<string name="swipe_restore_s">   Restaurar</string>
+		<string name="rename_backup_hdr">Renombrar Copia de Seguridad</string>
+		<string name="rename_backup_confirm">Renombrar Copia de Seguridad?</string>
+		<string name="rename_backup_confirm2">Esto no se puede Deshacer!</string>
+		<string name="renaming_backup">Renombrando Copia de Seguridad...</string>
+		<string name="rename_backup_complete">Renombrar Copia de Seguridad Completado</string>
+		<string name="swipe_to_rename">Desliza para Renombrar</string>
+		<string name="swipe_rename">   Renombrar</string>
+		<string name="confirm_hdr">Confirmar</string>
+		<string name="mount_hdr">Montar</string>
+		<string name="mount_sel_part">Selecciona las Particiones para Montar:</string>
+		<string name="mount_sys_ro_chk">Montar partición de sistema en modo lectura</string>
+		<string name="mount_sys_ro_s_chk">Montar System RO</string>
+		<string name="decrypt_data_btn">Desencritar Data</string>
+		<string name="disable_mtp_btn">Desactivar MTP</string>
+		<string name="enable_mtp_btn">Activar MTP</string>
+		<string name="mount_usb_storage_btn">Montar Almacenamiento USB</string>
+		<string name="usb_storage_hdr">Almacenamiento USB</string>
+		<string name="usb_stor_mnt1">Almacenamiento USB Montado</string>
+		<string name="usb_stor_mnt2">¡Asegúrese de retirar de forma segura el dispositivo</string>
+		<string name="usb_stor_mnt3">de el ordenador antes de desmontar!</string>
+		<string name="unmount_btn">Desmontar</string>
+		<string name="rb_system_btn">Sistema</string>
+		<string name="rb_poweroff_btn">Apagar</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Descarga</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Apagando...</string>
+		<string name="swipe_power_off">Deslice para Apagar</string>
+		<string name="swipe_power_off_s">Apagar</string>
+		<string name="sys_ro_hdr">Particion de sistema sin modificar</string>
+		<string name="sys_ro_keep">Mantener Sitema solo lectura</string>
+		<string name="sys_rop1">TWRP puede dejar sin modificar la partición del sistema</string>
+		<string name="sys_rop2">para que sea más fácil para usted instalar actualizaciones oficiales.</string>
+		<string name="sys_rop3">TWRP será incapaz de impedir que la stock ROM</string>
+		<string name="sys_rop4">sustituya TWRP y no ofrecerá rootear su dispositivo.</string>
+		<string name="sys_rop5">Instalación de zips o la realización de operaciones adb aún puede</string>
+		<string name="sys_rop6">modificar la partición del sistema.</string>
+		<string name="sys_rol1">TWRP puede dejar sin modificar la partición del sistema para que sea más fácil para usted instalar actualizaciones oficiales.</string>
+		<string name="sys_rol2">TWRP será incapaz de impedir que la stock ROM sustituya TWRP y no ofrecerá rootear su dispositivo.</string>
+		<string name="sys_rol3">Instalación de zips o la realización de operaciones adb aún puede modificar la partición del sistema.</string>
+		<string name="sys_ro_never_show_chk">No mostrar nunca más esta pantalla durante el inicio</string>
+		<string name="sys_ro_keep_ro_btn">Mantener modo lectura</string>
+		<string name="swipe_allow_mod">Deslice para Permitir Modificaciones</string>
+		<string name="swipe_allow_mod_s">Permitir Modificaciones</string>
+		<string name="settings_hdr">Ajustes</string>
+		<string name="settings_gen_hdr">General</string>
+		<string name="settings_gen_s_hdr">General</string>
+		<string name="settings_gen_btn">General</string>
+		<string name="use_rmrf_chk">Usar rm -rf en lugar de formateo</string>
+		<string name="use24clock_chk">Usar reloj 24-horas </string>
+		<string name="rev_navbar_chk">Invertir posición de la barra de navegacion</string>
+		<string name="simact_chk">Simulate actions for theme testing</string>
+		<string name="simfail_chk">Simular fallos de acciones</string>
+		<string name="ctr_navbar_rdo">Centrar Botones de la barra de navegacion</string>
+		<string name="lft_navbar_rdo">Diseño de barra de navegación Invertida izquirda</string>
+		<string name="rht_navbar_rdo">Diseño de barra de navegación Invertida derecha</string>
+		<string name="restore_defaults_btn">Restore Defaults</string>
+		<string name="settings_tz_btn">Zona Horaria</string>
+		<string name="settings_screen_btn">Pantalla</string>
+		<string name="settings_screen_bright_btn">Brillo de Pantalla</string>
+		<string name="settings_vibration_btn">Vibración</string>
+		<string name="settings_language_btn">Idioma</string>
+		<string name="time_zone_hdr">Zona Horaria</string>
+		<string name="sel_tz_list">Seleccione Zona Horaria:</string>
+		<string name="utcm11">(UTC -11) Samoa, Midway Island</string>
+		<string name="utcm10">(UTC -10) Hawaii</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm8">(UTC -8) Pacific Time</string>
+		<string name="utcm7">(UTC -7) Mountain Time</string>
+		<string name="utcm6">(UTC -6) Central Time</string>
+		<string name="utcm5">(UTC -5) Eastern Time</string>
+		<string name="utcm4">(UTC -4) Atlantic Time</string>
+		<string name="utcm3">(UTC -3) Brazil, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Mid-Atlantic</string>
+		<string name="utcm1">(UTC -1) Azores, Cape Verde</string>
+		<string name="utc0">(UTC  0) London, Dublin, Lisbon</string>
+		<string name="utcp1">(UTC +1) Berlin, Brussels, Paris</string>
+		<string name="utcp2">(UTC +2) Athens, Istanbul, South Africa</string>
+		<string name="utcp3">(UTC +3) Moscow, Baghdad</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC +5) Yekaterinburg, Islamabad</string>
+		<string name="utcp6">(UTC +6) Almaty, Dhaka, Colombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp8">(UTC +8) Beijing, Singapore, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokyo, Seoul, Yakutsk</string>
+		<string name="utcp10">(UTC +10) Eastern Australia, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Solomon Islands</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fiji</string>
+		<string name="sel_tz_offset">Selecciona Variación (usualmente 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Ninguno</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Utilizar el horario de verano (DST)</string>
+		<string name="curr_tz">Zona Horaria Actual: %tw_time_zone%</string>
+		<string name="curr_tz_s">Zona Horaria Actual:</string>
+		<string name="set_tz_btn">Establecer Zona Horaria</string>
+		<string name="settings_screen_hdr">Pantalla</string>
+		<string name="settings_screen_timeout_hdr">Tiempo de espera de pantalla</string>
+		<string name="enable_timeout_chk">Activar tiempo de espera de pantalla</string>
+		<string name="screen_to_slider">Tiempo de espera de pantalla en segundos:</string>
+		<string name="screen_to_slider_s">Tiempo de espera de pantalla en segundos (0=disabled): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Ajustes de tiempo de espera de la pantalla no disponibles</string>
+		<string name="screen_bright_slider">Brillo: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Configuración de brillo no diponible</string>
+		<string name="vibration_hdr">Vibración</string>
+		<string name="button_vibration_hdr">Vibración de los Botones</string>
+		<string name="kb_vibration_hdr">Vibración del Teclado</string>
+		<string name="act_vibration_hdr">Vibración de Acciones</string>
+		<string name="button_vibration">Vibración de Botón:</string>
+		<string name="kb_vibration">Vibración de Teclado:</string>
+		<string name="act_vibration">Vibración de Acciones:</string>
+		<string name="select_language">Selecciona Idioma:</string>
+		<string name="sel_lang_btn">Seleccionar Idioma</string>
+		<string name="set_language_btn">Establecer Idioma</string>
+		<string name="advanced_hdr">Avanzado</string>
+		<string name="copy_log_confirm">Copiar Log a Tarjeta SD?</string>
+		<string name="copying_log">Copiando Log a Tarjeta SD...</string>
+		<string name="copy_log_complete">Copia del Log Completada</string>
+		<string name="fix_context_btn">Reparar Contextos</string>
+		<string name="part_sd_btn">Particionar Tarjeta SD</string>
+		<string name="part_sd_s_btn">Tarjeta SD</string>
+		<string name="file_manager_btn">Administrador de Archivos</string>
+		<string name="language_hdr">Idioma</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="reload_theme_btn">Recargar Tema</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Injectar TWRP</string>
+		<string name="inject_twrp_confirm">Re-Injectar TWRP?</string>
+		<string name="injecting_twrp">Re-Injectando TWRP...</string>
+		<string name="inject_twrp_complete">TWRP Injección Completa</string>
+		<string name="swipe_to_confirm">Desliza para Confirmar</string>
+		<string name="part_sd_hdr">Particionar Tarjeta SD</string>
+		<string name="invalid_partsd_sel">Debes seleccionar un dispositivo extraíble</string>
+		<string name="part_sd_lose">Perderá todos los archivos de la tarjeta SD!</string>
+		<string name="part_sd_undo">Esta Acción no se puede Deshacer!</string>
+		<string name="part_sd_ext_sz">Tamaño de EXT:</string>
+		<string name="part_sd_swap_sz">Tamaño de Intercambio:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Sistema de Archivos:</string>
+		<string name="swipe_part_sd">Desliza para Particionar</string>
+		<string name="swipe_part_sd_s">Particionar</string>
+		<string name="partitioning_sd">Particionando Tarjeta SD...</string>
+		<string name="partitioning_sd2">Esto puede tomar unos Minutos.</string>
+		<string name="part_sd_complete">Particionar Completado</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Restaurar Boot Original</string>
+		<string name="dumlock_restore_confirm">¿Restaurar imagen de boot original?</string>
+		<string name="dumlock_restoring">Restaurando boot Original...</string>
+		<string name="dumlock_restore_complete">Restauración del Boot Original Completado</string>
+		<string name="dumlock_reflash_btn">Reflashear Recovery</string>
+		<string name="dumlock_reflash_confirm">¿Reflashear recovery a boot?</string>
+		<string name="dumlock_reflashing">Flasheando recovery a boot...</string>
+		<string name="dumlock_reflash_complete">Flasheo del Recovery a Boot Completado</string>
+		<string name="dumlock_install_btn">Instalar HTC Dumlock</string>
+		<string name="dumlock_install_confirm">¿Instalar los archivos de HTC dumlock en la ROM?</string>
+		<string name="dumlock_installing">Instalando HTC Dumlock...</string>
+		<string name="dumlock_install_complete">Instalación de HTC Dumlock Completada</string>
+		<string name="swipe_to_unlock">Desliza para Desbloquear</string>
+		<string name="swipe_unlock">   Desbloquear</string>
+		<string name="fm_hdr">Administrador de Archivos </string>
+		<string name="fm_sel_file">Selecciona un Archivo ó Carpeta</string>
+		<string name="fm_type_folder">Carpeta</string>
+		<string name="fm_type_file">Archivo</string>
+		<string name="fm_choose_act">Elija una Acción</string>
+		<string name="fm_selected">%tw_fm_type% seleccionado:</string>
+		<string name="fm_copy_btn">Copiar</string>
+		<string name="fm_copy_file_btn">Copiar Archivo</string>
+		<string name="fm_copy_folder_btn">Copiar Carpeta</string>
+		<string name="fm_copying">Copiando</string>
+		<string name="fm_move_btn">Mover</string>
+		<string name="fm_moving">Moviendo</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Eliminar</string>
+		<string name="fm_deleting">Eliminando</string>
+		<string name="fm_rename_btn">Renombrar</string>
+		<string name="fm_rename_file_btn">Renombrar Archivo</string>
+		<string name="fm_rename_folder_btn">Renombrar Carpeta</string>
+		<string name="fm_renaming">Renombrando</string>
+		<string name="fm_sel_dest">Selecciona la carpeta de destino</string>
+		<string name="fm_sel_curr_folder">Selecciona la carpeta actual</string>
+		<string name="fm_rename_hdr">Renombrar</string>
+		<string name="fm_set_perms_hdr">Establecer Permisos</string>
+		<string name="fm_perms">Permisos:</string>
+		<string name="fm_complete">Operación de Archivo Completado</string>
+		<string name="decrypt_data_hdr">Desencriptar Datos</string>
+		<string name="decrypt_data_enter_pass">Introducir Contraseña.</string>
+		<string name="decrypt_data_failed">Contraseña incorrecta, intente de nuevo!</string>
+		<string name="decrypt_data_failed_pattern">Patrón incorrecto, intentelo de nuevo!</string>
+		<string name="decrypt_data_enter_pattern">Introduzca un patrón.</string>
+		<string name="decrypt_data_trying">Tratando de Desencriptar</string>
+		<string name="term_hdr">Comando de Terminal</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">MATAR</string>
+		<string name="term_sel_folder_hdr">Navegar hasta la Carpeta de Inicio</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Limpiar Dalvik Cache</string>
+		<string name="sideload_wipe_cache_chk">Limpiar Cache</string>
+		<string name="swipe_to_sideload">Desliza para iniciar Sideload</string>
+		<string name="swipe_sideload">   Comenzar</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Uso: adb sideload nombredearchivo.zip</string>
+		<string name="sideload_complete">ADB Sideload Completado</string>
+		<string name="fix_contexts_hdr">Fijar Contextos</string>
+		<string name="fix_contexts_note1">Nota: La fijación de los contextos rara vez es necesaria.</string>
+		<string name="fix_contexts_note2">La fijación de Contextos de SELinux puede causar</string>
+		<string name="fix_contexts_note3">que tú dispositivo no arranque correctamente.</string>
+		<string name="swipe_to_fix_contexts">Desliza para fijar Contextos</string>
+		<string name="swipe_fix_contexts">   Fijar Contextos</string>
+		<string name="fixing_contexts">Fijando Contextos...</string>
+		<string name="fix_contexts_complete">Fijar Contextos Completado</string>
+		<string name="reboot_hdr">Reiniciar</string>
+		<string name="install_cancel">No instalar</string>
+		<string name="sel_storage_list">Seleccionar Almacenamiento</string>
+		<string name="ok_btn">OK</string>
+		<string name="no_kernel_selinux">Kernel no tiene soporte para la lectura de los contextos de SELinux.</string>
+		<string name="full_selinux">Soporte completo de SELinux está presente.</string>
+		<string name="no_selinux">No hay soporte SELinux (no libselinux).</string>
+		<string name="mtp_enabled">MTP Activado</string>
+		<string name="mtp_crash">MTP falló, no se iniciará MTP en el arranque.</string>
+		<string name="decrypt_success">Datos descifrados exitosamente, nuevo dispositivo de bloque: \'{1}\'</string>
+		<string name="unable_to_decrypt">Incapaz de descifrar con la contraseña actual. Quizás necesites realizar un Formateo de Datos.</string>
+		<string name="generating_digest1" version="2">Generando Digest</string>
+		<string name="generating_digest2" version="2"> * Generando Digest...</string>
+		<string name="digest_created" version="2"> * Digest Creado.</string>
+		<string name="digest_error" version="2"> * ¡Digest Error!</string>
+		<string name="digest_compute_error" version="2"> * Error creando Digest.</string>
+		<string name="current_date">(Fecha Actual)</string>
+		<string name="auto_generate">(Autogenerar)</string>
+		<string name="unable_to_locate_partition">Incapaz de localizar la partición’{1}’ para los cálculos de la copia de seguridad.</string>
+		<string name="no_partition_selected">No se han seleccionado particiones para copia de seguridad.</string>
+		<string name="total_partitions_backup"> * Número total de particiones para copia de seguridad: {1}</string>
+		<string name="total_backup_size"> * Tamaño total de todos los datos: {1}MB</string>
+		<string name="available_space"> * Espacio disponible: {1}MB</string>
+		<string name="unable_locate_storage">Incapaz de localizar el dispositivo de almacenamiento.</string>
+		<string name="no_space">No hay suficiente espacio en el almacenamiento.</string>
+		<string name="backup_started">[COPIA DE SEGURIDAD COMENZADA]</string>
+		<string name="backup_folder">Carpeta de copia seguridad establecida en \'{1}\'</string>
+		<string name="fail_backup_folder">Error al crear carpeta de copia de seguridad.</string>
+		<string name="avg_backup_fs">Tasa media de copia de seguridad para sistemas de archivo: {1} MB/seg</string>
+		<string name="avg_backup_img">Tasa media de copia de seguridad para imágenes de unidades: {1} MB/seg</string>
+		<string name="total_backed_size">[{1} MB TOTALES DE COPIA DE SEGURIDAD]</string>
+		<string name="backup_completed">[COPIA DE SEGURIDAD COMPLETADA EN {1} SEGUNDOS	]</string>
+		<string name="restore_started">[RESTAURACIÓN COMENZADA]</string>
+		<string name="restore_folder">Restaurar carpeta: \'{1}\'</string>
+		<string name="restore_part_done">[{1} completado ({2} segundos)]</string>
+		<string name="verifying_digest" version="2">Verificando Digest</string>
+		<string name="skip_digest" version="2">Saltando checkeo de Digest basado en ajustes del usuario.</string>
+		<string name="calc_restore">Calculando detalles de la restauración...</string>
+		<string name="restore_read_only">No se puede restaurar {1} -- montado en modo leer.</string>
+		<string name="restore_unable_locate">Incapaz de localizar la partición \'{1}\' para la restauración.</string>
+		<string name="no_part_restore">No se han seleccionado particiones para restaurar.</string>
+		<string name="restore_part_count">Restaurando {1} particiones...</string>
+		<string name="total_restore_size">El tamaño total de restauración es de {1}MB</string>
+		<string name="updating_system_details">Actualizando Detalles de Sistema</string>
+		<string name="restore_completed">[RESTAURACIÓN COMPLETADA EN {1} SEGUNDOS]</string>
+		<string name="error_opening_strerr">Error abriendo: \'{1}\' ({2})</string>
+		<string name="unable_locate_part_backup_name">Incapaz de localizar partición con el nombre de copia de seguridad: \'{1}\'</string>
+		<string name="unable_find_part_path">Incapaz de encontrar la partición para la dirección \'{1}\'</string>
+		<string name="update_part_details">Actualizando detalles de la partición…</string>
+		<string name="update_part_details_done">…hecho</string>
+		<string name="wiping_dalvik">Limpiando Directorios de Caché de Dalvik...</string>
+		<string name="cleaned">Limpio: {1}...</string>
+		<string name="dalvik_done">-- ¡Limpieza de Directorios de Caché de Dalvik Completa!</string>
+		<string name="no_andsec">No se encontró ninguna partición de android secure.</string>
+		<string name="unable_to_locate">Incapaz de localizar {1}.</string>
+		<string name="wiping_datamedia">Limpiando almacenamiento interno -- /data/media...</string>
+		<string name="unable_to_mount">Incapaz de montar {1}</string>
+		<string name="unable_to_mount_internal">Incapaz de montar almacenamiento_interno</string>
+		<string name="unable_to_mount_storage">Incapaz de montar almacenamiento</string>
+		<string name="fail_decrypt">Error al descifrar datos.</string>
+		<string name="no_crypto_support">No hay ningún soporte de cifrado compilado en esta compilación.</string>
+		<string name="decrypt_success_dev">Datos desencriptados satisfactoriamente, nuevo bloque de dispositivo: '{1}'</string>
+		<string name="done">Hecho.</string>
+		<string name="start_partition_sd">Particionando tarjeta SD...</string>
+		<string name="partition_sd_locate">Incapaz de localizar el dispositivo a particionar.</string>
+		<string name="ext_swap_size">El tamaño de EXT + Swap es más grande que el tamaño de la tarjeta SD.</string>
+		<string name="remove_part_table">Eliminando tabla de partición...</string>
+		<string name="unable_rm_part">Incapaz de eliminar la tabla de partición.</string>
+		<string name="create_part">Creando partición {1}...</string>
+		<string name="unable_to_create_part">Incapaz de crear partición {1}.</string>
+		<string name="format_sdext_as">Formateando sd-ext como {1}...</string>
+		<string name="part_complete">Particionamiento completo.</string>
+		<string name="unable_to_open">Incapaz de abrir \'{1}\'.</string>
+		<string name="mtp_already_enabled">MTP ya está habilitado</string>
+		<string name="mtp_fail">Error al activar MTP</string>
+		<string name="no_mtp">Soporte a MTP no incluido</string>
+		<string name="image_flash_start">[FLASHEO DE IMAGEN EMPEZADA]</string>
+		<string name="img_to_flash">Imagen a flashear: \'{1}\'</string>
+		<string name="flash_unable_locate">Incapaz de localizar la partición \'{1}\' para flashear.</string>
+		<string name="no_part_flash">No se ha seleccionado partición para flashear.</string>
+		<string name="too_many_flash">Demasiadas particiones seleccionadas para flashear.</string>
+		<string name="invalid_flash">La partición de flasheo especificada es inválida.</string>
+		<string name="flash_done">[FLASHEO DE IMAGEN COMPLETO]</string>
+		<string name="wiping">Limpiando {1}</string>
+		<string name="repair_not_exist">¡{1} no existe! ¡No se puede reparar!</string>
+		<string name="repairing_using">Reparando {1} usando {2}...</string>
+		<string name="unable_repair">Incapaz de reparar {1}.</string>
+		<string name="mount_data_footer">No se pudo montar /data e incapaz de encontrar pie de página de crypto.</string>
+		<string name="create_folder_strerr">No se pudo crear \'{1}\' en carpeta ({2}).</string>
+		<string name="fail_mount">Error al desmontar \'{1}\' ({2})</string>
+		<string name="fail_unmount">Fallo al desmontar '{1}' ({2})</string>
+		<string name="cannot_resize">No se puede cambiar el tamaño de {1}.</string>
+		<string name="repair_resize">Reparando {1} antes de cambiar de tamaño.</string>
+		<string name="unable_resize">Incapaz de cambiar el tamaño de archivo de {1}.</string>
+		<string name="no_digest_found" version="2">No se encuentra archivo Digest para \'{1}\'. Por favor, deselecciona Activar verificación de Digest para restaurar.</string>
+		<string name="digest_fail_match" version="2">Digest no coincide en \'{1}\'.</string>
+		<string name="digest_matched" version="2">Digest matched for '{1}'.</string>
+		<string name="fail_decrypt_tar">Fallo al desencriptar el archivo tar '{1}'</string>
+		<string name="format_data_msg">Quizás necesites reiniciar el recovery para ser capaz de usar /data de nuevo.</string>
+		<string name="format_data_err">Incapaz de formatear para remover encriptación.</string>
+		<string name="formatting_using">Formateando {1} usando {2}...</string>
+		<string name="unable_to_wipe">Incapaz de limpiar {1}.</string>
+		<string name="cannot_wipe">Partición {1} no se limpiar.</string>
+		<string name="remove_all">Eliminando todos los archivos dentro de \'{1}\'</string>
+		<string name="wiping_data">Limpiando datos sin limpiar /data/media ...</string>
+		<string name="backing_up">Realizando copia de seguridad {1}...</string>
+		<string name="backing">Realizando copia de seguridad</string>
+		<string name="backup_size">El tamaño del archivo \'{1}\' de la copia seguridad es de 0 bytes.</string>
+		<string name="datamedia_fs_restore">CUIDADO: !Esta copia de seguridad de /data se hizo con el sistema de archivos {1}! La copia de seguridad puede no arrancar a no ser que cambies a {1}.</string>
+		<string name="restoring">Restaurando {1}...</string>
+		<string name="restoring_hdr">Restaurando</string>
+		<string name="recreate_folder_err">Incapaz de recrear la carpeta {1}.</string>
+		<string name="img_size_err">El tamaño de la imagen es mayor que el tamaño del dispositivo de destino.</string>
+		<string name="flashing">Flasheando {1}...</string>
+		<string name="backup_folder_set">Copia de Seguridad establecida en '{1}'</string>
+		<string name="locate_backup_err">Incapaz de localizar copia de seguridad \'{1}\'</string>
+		<string name="set_restore_opt">Ajustando opciones de restauración: \'{1}\':</string>
+		<string name="digest_check_skip" version="2">El salto de checkeo de Digest está activado</string>
+		<string name="ors_encrypt_restore_err">Incapaz de usar OpenRecoveryScript para restaurar una copia de seguridad encriptada.</string>
+		<string name="mounting">Montando</string>
+		<string name="unmounting">Desmontando</string>
+		<string name="mounted">Montado \'{1}\'</string>
+		<string name="unmounted">Desmontado \'{1}\'</string>
+		<string name="setting">Ajustando \'{1}\' a \'{2}\'</string>
+		<string name="setting_empty">Ajustando \'{1}\' a vacío</string>
+		<string name="making_dir1">Creando Directorio</string>
+		<string name="making_dir2">Creando directorio: \'{1}\'</string>
+		<string name="running_command">Corriendo Comando</string>
+		<string name="sideload">Carga lateral de ADB</string>
+		<string name="start_sideload">Empezando carga lateral de ADB...</string>
+		<string name="need_new_adb">Necesitas de adb 1.0.32 o más nuevo para poder realizar la carga lateral en este dispositivo.</string>
+		<string name="no_pwd">No se ha proporcionado contraseña.</string>
+		<string name="done_ors">Procesamiento de comando de archivos hecho</string>
+		<string name="injecttwrp">Inyectando TWRP en imagen boot...</string>
+		<string name="zip_err">Error instalando archivo zip \'{1}\'</string>
+		<string name="installing_zip">Instalando archivo zip '{1}'</string>
+		<string name="select_backup_opt">Ajustando opciones de copia de seguridad:</string>
+		<string name="compression_on">La compresión está activada</string>
+		<string name="digest_off" version="2">La generación de Digest está desactivada</string>
+		<string name="backup_fail">Copia de seguridad fallida</string>
+		<string name="backup_clean">La copia de seguridad falló. Limpiando carpeta de la copia de seguridad.</string>
+		<string name="running_recovery_commands">Corriendo Comandos de Recovery</string>
+		<string name="recovery_commands_complete">Comandos de Recovery Completos</string>
+		<string name="running_ors">Corriendo OpenRecoveryScript</string>
+		<string name="ors_complete">OpenRecoveryScript Completo</string>
+		<string name="no_updater_binary">No fue posible encontrar \'{1}\' en el archivo zip.</string>
+		<string name="check_for_digest" version="2">Buscando archivo Digest...</string>
+		<string name="fail_sysmap">Error al mapear archivo \'{1}\'</string>
+		<string name="verify_zip_sig">Verificando firma de zip…</string>
+		<string name="verify_zip_fail">¡Verificación de firma de Zip fallida!</string>
+		<string name="verify_zip_done">Firma de Zip verificada exitosamente.</string>
+		<string name="zip_corrupt">¡Este archivo Zip es corrupto!</string>
+		<string name="no_digest" version="2">Saltando verificación de Digest: no se ha encontrado archivo Digest</string>
+		<string name="digest_fail" version="2">Digest no coincide</string>
+		<string name="digest_match" version="2">Digest coincide</string>
+		<string name="pid_signal">Proceso {1} terminó con señal: {2}</string>
+		<string name="pid_error">Proceso {1} terminó con ERROR: {2}</string>
+		<string name="install_dumlock">Instalando HTC Dumlock en el sistema…</string>
+		<string name="dumlock_restore">Restaurando el boot original...</string>
+		<string name="dumlock_reflash">Reflasheando el recovery en boot...</string>
+		<string name="run_script">Ejecutando script {1} ...</string>
+		<string name="rename_stock">Renombrado stock recovery en /system para prevenir que la ROM stock remplace TWRP.</string>
+		<string name="split_backup">Dividiendo copia de seguridad en múltiples archivos...</string>
+		<string name="backup_error">Error creando copia de seguridad.</string>
+		<string name="restore_error">Error durante el proceso de restauración.</string>
+		<string name="split_thread">Dividiendo hilo ID {1} en archivo {2}</string>
+		<string name="file_progress">%llu de %llu archivos, %i%%</string>
+		<string name="size_progress">%lluMB de %lluMB, %i%%</string>
+		<string name="decrypt_cmd">Intentando decriptar partición de datos via línea de comandos.</string>
+		<string name="base_pkg_err">Carga de paquetes básicos fallido.</string>
+		<string name="simulating">Simulando acciones...</string>
+		<string name="backup_cancel">Copia de seguridad cancelada.</string>
+		<string name="config_twrp">Configurando TWRP...</string>
+		<string name="config_twrp_err">Incapaz de configurar TWRP con este kernel.</string>
+		<string name="copy_log">Log del recovery copiado en {1}.</string>
+		<string name="max_queue">¡Máximo de archivos zip en cola alcanzado!</string>
+		<string name="min_queue">¡Mínimo de archivos zip en cola alcanzado!</string>
+		<string name="screenshot_saved">Captura de pantalla guardada en {1}</string>
+		<string name="screenshot_err">¡La captura de pantalla falló!</string>
+		<string name="zip_wipe_cache">Uno o más archivos zip han pedido una limpieza de caché -- Limpiando caché ahora.</string>
+		<string name="and_sec_wipe_err">Incapaz de limpiar android secure</string>
+		<string name="dalvik_wipe_err">¡La limpieza de dalvik falló!</string>
+		<string name="auto_gen">(Autogenerar)</string>
+		<string name="curr_date">(Fecha Actual)</string>
+		<string name="backup_name_len">El nombre de esta copia de seguridad es demasiado largo.</string>
+		<string name="backup_name_invalid">El nombre de la copia de seguridad \'{1}\' contiene un carácter inválido: \'{1}\'</string>
+		<string name="no_real_sdcard">¡Este dispositivo no tiene una tarjeta SD real! ¡Abortando!</string>
+		<string name="cancel_sideload">Cancelando carga lateral ADB...</string>
+		<string name="change_fs_err">Error cambiando sistema de archivos.</string>
+		<string name="theme_ver_err">Tema personalizado no coincide con la versión de TWRP. Usando tema stock.</string>
+		<string name="up_a_level">(Subir Un Nivel)</string>
+		<string name="install_reboot">Reiniciando en 5 segundos</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/fr.xml b/gui/theme/common/languages/fr.xml
new file mode 100644
index 0000000..6e824af
--- /dev/null
+++ b/gui/theme/common/languages/fr.xml
@@ -0,0 +1,645 @@
+<?xml version="1.0"?>
+<language>
+    <display>Français</display>
+    <resources>
+        <resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+        <resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+        <resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+        <resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+        <string name="system">Système</string>
+        <string name="system_image">Image Système</string>
+        <string name="vendor">Fabricant</string>
+        <string name="vendor_image">Image Fabricant</string>
+        <string name="boot">Amorçage</string>
+        <string name="recovery">Récupération</string>
+        <string name="cache">Cache</string>
+        <string name="data">Données</string>
+        <string name="sdcard">Carte SD</string>
+        <string name="internal">Mémoire interne</string>
+        <string name="microsd">Carte micro SD</string>
+        <string name="usbotg">USB OTG</string>
+        <string name="android_secure">Android Sécurisé</string>
+        <string name="dalvik">Cache Dalvik / ART</string>
+        <string name="sdext">SD-EXT</string>
+        <string name="adopted_data">Données adoptées</string>
+        <string name="adopted_storage">Stockage adopté</string>
+        <string name="twrp_header">Team Win Recovery Project</string>
+        <string name="twrp_watch_header">TWRP %tw_version%</string>
+        <string name="cpu_temp">CPU : %tw_cpu_temp% &#xB0;C</string>
+        <string name="battery_pct">Batterie : %tw_battery%</string>
+        <string name="sort_by_name">Trier par nom</string>
+        <string name="sort_by_date">Trier par Date</string>
+        <string name="sort_by_size">Trier par taille</string>
+        <string name="sort_by_name_only">Nom</string>
+        <string name="sort_by_date_only">Date</string>
+        <string name="sort_by_size_only">Taile</string>
+        <string name="tab_general">GÉNÉRAL</string>
+        <string name="tab_options">OPTIONS</string>
+        <string name="tab_backup">SAUVEGARDE</string>
+        <string name="tab_time_zone">FUSEAU HORAIRE</string>
+        <string name="tab_screen">ÉCRAN</string>
+        <string name="tab_vibration">VIBRATION</string>
+        <string name="tab_language">LANGUE</string>
+        <string name="install_btn">Installer</string>
+        <string name="wipe_btn">Formater</string>
+        <string name="backup_btn">Sauvergarder</string>
+        <string name="restore_btn">Restaurer</string>
+        <string name="mount_btn">Montage partitions</string>
+        <string name="settings_btn">Paramètres</string>
+        <string name="advanced_btn">Paramètres avancé</string>
+        <string name="reboot_btn">Redémarrer</string>
+        <string name="files_btn">Fichiers</string>
+        <string name="copy_log_btn">Copier le journal</string>
+        <string name="select_type_hdr">Selctionner le type</string>
+        <string name="install_zip_hdr">Installer le Zip</string>
+        <string name="install_zip_btn">Installer le Zip</string>
+        <string name="install_image_hdr">Installer l'Image</string>
+        <string name="install_image_btn">Installer l'Image</string>
+        <string name="install_select_file_hdr">Sélectionnez un fichier</string>
+        <string name="file_selector_folders_hdr">Dossier</string>
+        <string name="select_file_from_storage">Sélectionnez un fichier dans %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+        <string name="adb_sideload_btn">Transfert via ADB</string>
+        <string name="install_hdr">Installer</string>
+        <string name="select_storage_hdr">Sélectionner l'emplacement</string>
+        <string name="select_storage_btn">Sélect. l'emplacement</string>
+        <string name="queue_hdr">Mettre en file d'attente</string>
+        <string name="zip_queue_count">%tw_zip_queue_count% de max de 10 fichiers en file d'attente</string>
+        <string name="zip_queue_count_s">Fichier %tw_zip_queue_count% sur 10 :</string>
+        <string name="zip_warn1">Cette opération pourrait intaller un logiciel</string>
+        <string name="zip_warn2">incompatible et rendre votre appareil inutilisable.</string>
+        <string name="zip_back_cancel">Appuyer sur retour pour annuler l'ajout de ce zip.</string>
+        <string name="zip_back_clear">Appuyer sur retour pour effacer la liste d'attente.</string>
+        <string name="folder">Dossier :</string>
+        <string name="file">Fichier :</string>
+        <string name="zip_sig_chk">Vérification de la signature du Zip</string>
+        <string name="inject_twrp_chk">Injecter TWRP après l'installation</string>
+        <string name="options_hdr">Options</string>
+        <string name="confirm_flash_hdr">Confirmer le Flash</string>
+        <string name="zip_queue">Mettre en file d'attente :</string>
+        <string name="options">Options :</string>
+        <string name="swipe_confirm">Confirmer</string>
+        <string name="zip_add_btn">Ajouter plus de Zips</string>
+        <string name="zip_clear_btn">Effacer la liste d'attente des Zips</string>
+        <string name="install_zip_count_hdr">Installe le Zip %tw_zip_index% sur %tw_zip_queue_count%</string>
+        <string name="installing_zip_xml">Installe le Zip : %tw_file%</string>
+        <string name="failed">Échec</string>
+        <string name="successful">Réussie</string>
+        <string name="install_failed">Installation échouée</string>
+        <string name="install_successful">Installation réussie</string>
+        <string name="wipe_cache_dalvik_btn">Effacer cache/dalvik</string>
+        <string name="reboot_system_btn">Redémarrer le système</string>
+        <string name="install_sel_target">Sélectionner la Partition d'installation</string>
+        <string name="flash_image_select">Sélectionner la partition où écrire l'image :</string>
+        <string name="target_partition">Partition de destination :</string>
+        <string name="flashing_image">Image en cours de Flash...</string>
+        <string name="image_flashed">Image Flashée</string>
+        <string name="wipe_cache_dalvik_confirm">Effacer Cache &amp; Dalvik?</string>
+        <string name="wiping_cache_dalvik">Effacement du Cache &amp; Dalvik...</string>
+        <string name="wipe_cache_dalvik_complete">Cache &amp; Dalvik Effacés</string>
+        <string name="swipe_wipe">Glisser pour formater</string>
+        <string name="swipe_wipe_s">Formater</string>
+        <string name="no_os1">Aucun système d'exploitation installé ! Êtes-vous</string>
+        <string name="no_osrb">certain de vouloir redémarrer ?</string>
+        <string name="no_ospo">certain de vouloir arrêter le système ?</string>
+        <string name="rebooting">En cours de redémarrage...</string>
+        <string name="swipe_reboot_s">Redémarrer</string>
+        <string name="swipe_flash">Glisser pour confirmer le flash</string>
+        <string name="confirm_action">Confirmer l'action</string>
+        <string name="back_cancel">Presser retour pour annuler.</string>
+        <string name="cancel_btn">Annuler</string>
+        <string name="wipe_hdr">Effacer</string>
+        <string name="factory_reset_hdr">Retour aux réglages d'usine</string>
+        <string name="factory_reset_btn">Retour aux réglages d'usine</string>
+        <string name="factory_reset1">Formater les données, le Cache et Dalvik</string>
+        <string name="factory_reset2">(ne comprend pas le stockage interne)</string>
+        <string name="factory_reset3">La plupart du temps il s'agit</string>
+        <string name="factory_reset4">de la seule chose que vous avez besoin de formater.</string>
+        <string name="factory_resetting">Réinitialisation d'usine...</string>
+        <string name="advanced_wipe_hdr">Formatage avancé</string>
+        <string name="advanced_wipe_btn">Formatage avancé</string>
+        <string name="wipe_enc_confirm">Effacer le chiffrement des données ?</string>
+        <string name="formatting_data">Formatage en cours ...</string>
+        <string name="swipe_format_data">Glisser pour formater les données</string>
+        <string name="swipe_format_data_s">Formater les données</string>
+        <string name="factory_reset_complete">Réinitialisation complète</string>
+        <string name="sel_part_hdr">Sélectionner les Partitions</string>
+        <string name="wipe_sel_confirm">Formater la (les) partition(s) Sélectionnée(s) ?</string>
+        <string name="wiping_part">Formatage de la (des) partition(s) en cours...</string>
+        <string name="wipe_complete">Formatage Terminé</string>
+        <string name="sel_part_wipe">Sélectionner les Partitions a formater :</string>
+        <string name="invalid_part_sel">Sélection de partition non valide</string>
+        <string name="format_data_hdr">Formater les données</string>
+        <string name="format_data_btn">Formater les données</string>
+        <string name="format_data_ptr1">Formater les données effacera toutes vos applications,</string>
+        <string name="format_data_ptr2">sauvegardes, photos, vidéos, media, et</string>
+        <string name="format_data_ptr3">retirera l'encryption sur le stockage interne.</string>
+        <string name="format_data_lcp1">Formater les données effacera toutes vos applications, sauvergardes, photos, videos, media, et</string>
+        <string name="format_data_lcp2">retirera le chiffrement du stockage interne.</string>
+        <string name="format_data_wtc1">Formater les données effacera toutes vos applications,</string>
+        <string name="format_data_wtc2">les sauvegardes et les médias. Cette action ne peut pas être annulée.</string>
+        <string name="format_data_undo">La suppression est irréversible.</string>
+        <string name="format_data_complete">Formatage des données terminé</string>
+        <string name="yes_continue">Entrer "yes" pour continuer.  Appuyez sur retour pour annuler.</string>
+        <string name="part_opt_hdr">Options de la partition : %tw_partition_name%</string>
+        <string name="sel_act_hdr">Sélectionner une action</string>
+        <string name="file_sys_opt">Options de système de fichiers</string>
+        <string name="partition">Partition : %tw_partition_name%</string>
+        <string name="part_mount_point">Point de montage : %tw_partition_mount_point%</string>
+        <string name="part_curr_fs">Système de fichiers : %tw_partition_file_system%</string>
+        <string name="part_present_yes">Présent : Oui</string>
+        <string name="part_present_no">Présent : non</string>
+        <string name="part_removable_yes">Amovible : Oui</string>
+        <string name="part_removable_no">Amovible : non</string>
+        <string name="part_size">Taille : % tw_partition_size % MB</string>
+        <string name="part_used">Utilisé : % tw_partition_used % MB</string>
+        <string name="part_free">Libre: % tw_partition_free % MO</string>
+        <string name="part_backup_size">Taille de la sauvegarde : % tw_partition_backup_size % MB</string>
+        <string name="resize_btn">Redimensionner le système de fichiers</string>
+        <string name="resize_btn_s">Redimensionner</string>
+        <string name="resize_confirm">Redimensionner %tw_partition_name% ?</string>
+        <string name="resizing">Redimensionnement en cours...</string>
+        <string name="resize_complete">Redimensionnenent Terminé</string>
+        <string name="swipe_resize">Glisser pour redimensionner</string>
+        <string name="swipe_resize_s">Redimensionner</string>
+        <string name="repair_btn">Réparation du Système de fichier</string>
+        <string name="repair_btn_s">Réparer</string>
+        <string name="repair_confirm">Réparer %tw_partition_name% ?</string>
+        <string name="repairing">Réparation en cours...</string>
+        <string name="repair_complete">Réparation Terminée</string>
+        <string name="swipe_repair">Glisser pour réparer</string>
+        <string name="swipe_repair_s">Réparer</string>
+        <string name="change_fs_btn">Changer le Système de fichiers</string>
+        <string name="change_fs_btn_s">Changer</string>
+        <string name="change_fs_for_hdr">Changer le Système de fichiers pour : %tw_partition_name%</string>
+        <string name="change_fs_for_hdr_s">Partition : %tw_partition_name% &gt; sélectionnez le Système de fichiers</string>
+        <string name="change_fs_warn1">Certains ROMs ou kernels peuvent ne pas supporter certains</string>
+        <string name="change_fs_warn2">systèmes de fichiers. Procéder avec prudence !</string>
+        <string name="change_fs_confirm">Changer %tw_partition_name%?</string>
+        <string name="formatting">Formatage en cours...</string>
+        <string name="format_complete">Formatage Terminé</string>
+        <string name="swipe_change_fs">Glisser pour changer</string>
+        <string name="swipe_change_s">Changer</string>
+        <string name="back_btn">Retour</string>
+        <string name="wipe_enc_btn">Effacer le chiffrement</string>
+        <string name="swipe_factory_reset">Glisser pour restaurer les Paramètres d'usine</string>
+        <string name="repair_change_btn">Réparer ou Changer le Système de fichiers</string>
+        <string name="storage_hdr">Stockage : %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+        <string name="backup_hdr">Sauvergarder</string>
+        <string name="backup_confirm_hdr">Confirmer la sauvegarde</string>
+        <string name="encryption_tab">CHIFFREMENT</string>
+        <string name="encryption">Chiffrement :</string>
+        <string name="name">Nom:</string>
+        <string name="sel_part_backup">Sélectionner les Partitions à sauvegarder :</string>
+        <string name="storage">Stockage :</string>
+        <string name="enc_disabled">désactivé - définir un mot de passe pour activer</string>
+        <string name="enc_enabled">activé</string>
+        <string name="enable_backup_comp_chk">Activer la compression</string>
+        <string name="skip_digest_backup_chk" version="2">Ne pas générer de Digest pendant la sauvegarde</string>
+        <string name="disable_backup_space_chk">Désactiver la vérification de l'espace libre</string>
+        <string name="refresh_sizes_btn">Actualiser l'espace disponible</string>
+        <string name="swipe_backup">Glisser pour Sauvegarder</string>
+        <string name="append_date_btn">Ajouter Date</string>
+        <string name="backup_name_exists">Une sauvegarde avec ce nom existe déjà !</string>
+        <string name="encrypt_backup">Chiffrer votre sauvegarde ?</string>
+        <string name="enter_pass">Entrez un mot de passe :</string>
+        <string name="enter_pass2">Entrez le mot de passe à nouveau :</string>
+        <string name="pass_not_match">Les Mots de passe ne correspondent pas !</string>
+        <string name="partitions">Partitions :</string>
+        <string name="disabled">Désactivé</string>
+        <string name="enabled">Activé</string>
+        <string name="backup_name_hdr">Nom de la sauvegarde</string>
+        <string name="progress">Progression :</string>
+        <string name="backup_complete">Sauvegarde terminée</string>
+        <string name="backup_cancel">Sauvegarde annulée</string>
+        <string name="restore_hdr">Restaurer</string>
+        <string name="sel_backup_hdr">Choisir la sauvegarde</string>
+        <string name="restore_sel_store_hdr">Sélectionner la Sauvegarde dans %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+        <string name="restore_sel_pack_fs">Sélectionner le Package à restaurer :</string>
+        <string name="restore_enc_backup_hdr">Sauvegarde chiffrée</string>
+        <string name="restore_dec_fail">Mot de passe incorrect, Veuillez réessayer !</string>
+        <string name="del_backup_btn">Supprimer la sauvegarde</string>
+        <string name="del_backup_confirm">Supprimer la sauvegarde ?</string>
+        <string name="del_backup_confirm2">Cette action ne peut pas être annulée !</string>
+        <string name="deleting_backup">Suppression de sauvegarde en cours...</string>
+        <string name="backup_deleted">Suppression de la Sauvegarde Terminée</string>
+        <string name="swipe_delete">Glisser pour supprimer</string>
+        <string name="swipe_delete_s">Supprimer</string>
+        <string name="restore_try_decrypt">Sauvegarde chiffrée - Tentative de décryptage</string>
+        <string name="restore_try_decrypt_s">Tentative de décryptage</string>
+        <string name="restore_backup_date">Sauvegarde effectuée sur %tw_restore_file_date%</string>
+        <string name="restore_sel_part">Sélectionner les Partitions à restaurer :</string>
+        <string name="restore_enable_digest_chk" version="2">Activer la vérif. Digest des fichiers de sauvegarde</string>
+        <string name="restore_completed">Restauration terminée</string>
+        <string name="swipe_restore">Glisser pour Restaurer</string>
+        <string name="swipe_restore_s">Restaurer</string>
+        <string name="rename_backup_hdr">Renommer la sauvegarde</string>
+        <string name="rename_backup_confirm">Renommer la sauvegarde ?</string>
+        <string name="rename_backup_confirm2">Cette action ne peut pas être annulée !</string>
+        <string name="renaming_backup">Changement du nom de la sauvegarde en cours...</string>
+        <string name="rename_backup_complete">Sauvegarde Renommée</string>
+        <string name="swipe_to_rename">Glisser pour Renommer</string>
+        <string name="swipe_rename">Renommer</string>
+        <string name="confirm_hdr">Confirmer</string>
+        <string name="mount_hdr">Monter</string>
+        <string name="mount_sel_part"></string>
+        <string name="mount_sel_part">Sélectionner les Partitions à monter :</string>
+        <string name="mount_sys_ro_chk">Montez la partition système en lecture seule</string>
+        <string name="mount_sys_ro_s_chk">Monter le Système RO</string>
+        <string name="decrypt_data_btn">Chiffrer les données</string>
+        <string name="disable_mtp_btn">Désactiver MTP</string>
+        <string name="enable_mtp_btn">Activez MTP</string>
+        <string name="mount_usb_storage_btn">Monter Stockage USB</string>
+        <string name="usb_storage_hdr">Stockage USB</string>
+        <string name="usb_stor_mnt1">Stockage USB monté</string>
+        <string name="usb_stor_mnt2">Veillez à déconnecter votre appareil</string>
+        <string name="usb_stor_mnt3">de votre ordinateur avant de le libérer !</string>
+        <string name="unmount_btn">Libérer</string>
+        <string name="reboot_hdr">Redémarrer</string>
+        <string name="rb_system_btn">Système</string>
+        <string name="rb_poweroff_btn">Eteindre</string>
+        <string name="rb_recovery_btn">Récupération</string>
+        <string name="rb_bootloader_btn">Amorçage</string>
+        <string name="rb_download_btn">Télécharger</string>
+		<string name="rb_edl_btn">EDL</string>
+        <string name="turning_off">Arrêt en cours...</string>
+        <string name="swipe_power_off">Glisser pour éteindre</string>
+        <string name="swipe_power_off_s">Éteindre</string>
+        <string name="sys_ro_hdr">Partition système non modifiée</string>
+        <string name="sys_ro_keep">Conserver le système de fichier en lecture seule ?</string>
+        <string name="sys_rop1">TWRP peut laisser votre partition système non modifiée</string>
+        <string name="sys_rop2">pour rendre plus facile les mises à jour officielles.</string>
+        <string name="sys_rop3">TWRP sera incapable d'empêcher la ROM d'origine</string>
+        <string name="sys_rop4">de remplacer TWRP et ne pourra pas vous proposer de rooter votre appareil.</string>
+        <string name="sys_rop5">L'installation de zips ou les opérations via adb peuvent</string>
+        <string name="sys_rop6">peut-être modifier la partition système.</string>
+        <string name="sys_rol1">TWRP peut laisser votre partition système non modifiée pour rendre plus facile les mises à jour officielles.</string>
+        <string name="sys_rol2">TWRP sera incapable d'empêcher la ROM d'origine de remplacer TWRP et ne pourra pas vous proposer de rooter votre appareil.</string>
+        <string name="sys_rol3">L'installation de zips ou les opérations via adb peuvent peut-être modifier la partition système.</string>
+        <string name="sys_ro_never_show_chk">Ne plus afficher cet écran lors des prochains démarrages</string>
+        <string name="sys_ro_keep_ro_btn">Conserver la lecture seule</string>
+        <string name="swipe_allow_mod">Glisser pour autoriser les modifications</string>
+        <string name="swipe_allow_mod_s">Autoriser les modifications</string>
+        <string name="settings_hdr">Paramètres</string>
+        <string name="settings_gen_hdr">Paramètre généraux</string>
+        <string name="settings_gen_s_hdr">Général</string>
+        <string name="settings_gen_btn">Général</string>
+        <string name="use_rmrf_chk">Utiliser rm -rf au lieu de formater</string>
+        <string name="use24clock_chk">Utiliser le format d'horloge sur 24 heures</string>
+        <string name="rev_navbar_chk">Disposition de la barre de navigation inversée</string>
+        <string name="simact_chk">Simuler des actions pour tester un thème</string>
+        <string name="simfail_chk">Simuler l'échec des actions</string>
+        <string name="ctr_navbar_rdo">Centrer les Boutons de la barre de navigation</string>
+        <string name="lft_navbar_rdo">Aligner les boutons de la barre de navigation à gauche</string>
+        <string name="rht_navbar_rdo">Aligner les boutons de barre de navigation à droite</string>
+        <string name="restore_defaults_btn">Rétablir les param. par défaut</string>
+        <string name="settings_tz_btn">Fuseau horaire</string>
+        <string name="settings_screen_btn">Écran</string>
+        <string name="settings_screen_bright_btn">Luminosité</string>
+        <string name="settings_vibration_btn">Vibration</string>
+        <string name="settings_language_btn">Langue</string>
+        <string name="time_zone_hdr">Fuseau horaire</string>
+        <string name="sel_tz_list">Sélectionnez le fuseau horaire :</string>
+        <string name="utcm11">(UTC -11) Samoa, Îles samoanes</string>
+        <string name="utcm10">(UTC -10) Hawaï</string>
+        <string name="utcm9">(UTC -9) Alaska</string>
+        <string name="utcm8">(UTC -8) Heure du Pacifique</string>
+        <string name="utcm7">(UTC -7) Heure des Rocheuses</string>
+        <string name="utcm6">(UTC -6) Heure du Centre</string>
+        <string name="utcm5">(UTC -5) Heure de l'Est</string>
+        <string name="utcm4">(UTC -4) Heure de l'Atlantique</string>
+        <string name="utcm3">(UTC -3) Brésil, Buenos Aires</string>
+        <string name="utcm2">(UTC -2) Brésil, São Paulo</string>
+        <string name="utcm1">(UTC -1) Açores, Le Cap-Vert</string>
+        <string name="utc0">(UTC 0) Londres, Dublin, Lisbonne</string>
+        <string name="utcp1">(UTC +1) Berlin, Bruxelles, Paris</string>
+        <string name="utcp2">(UTC +2) Athènes, Istanbul, Afrique du Sud</string>
+        <string name="utcp3">(UTC +3) Moscou, Bagdad</string>
+        <string name="utcp4">(UTC +4) Abu Dhabi, Tbilissi, Muscat</string>
+        <string name="utcp5">(UTC +5) Ekaterinbourg, Islamabad</string>
+        <string name="utcp6">(UTC +6) Almaty, Dhaka, Colombo</string>
+        <string name="utcp7">(UTC +7) Bangkok, Hanoï, Djakarta</string>
+        <string name="utcp8">(UTC +8) Pékin, Singapour, Hong Kong</string>
+        <string name="utcp9">(UTC +9) Tokyo, Séoul, Iakoutsk</string>
+        <string name="utcp10">(UTC +10) Australie orientale, Guam</string>
+        <string name="utcp11">(UTC +11) Vladivostok, îles Salomon</string>
+        <string name="utcp12">(UTC +12) Auckland, Wellington, Îles Fidji</string>
+        <string name="sel_tz_offset">Sélectionner le Décalage (habituellement 0) : %tw_time_zone_guioffset%</string>
+        <string name="tz_offset_none">Aucun</string>
+        <string name="tz_offset_0">0</string>
+        <string name="tz_offset_15">15</string>
+        <string name="tz_offset_30">30</string>
+        <string name="tz_offset_45">45</string>
+        <string name="use_dst_chk">Utiliser l'heure d'été/hiver (DST)</string>
+        <string name="curr_tz">Fuseau horaire : %tw_time_zone%</string>
+        <string name="curr_tz_s">Fuseau horaire :</string>
+        <string name="set_tz_btn">Régler le fuseau horaire</string>
+        <string name="settings_screen_hdr">Paramètres d'écran</string>
+        <string name="settings_screen_timeout_hdr">Mise en veille de l'écan</string>
+        <string name="enable_timeout_chk">Activer la mise en veille</string>
+        <string name="screen_to_slider">Délai avant mise en veille :</string>
+        <string name="screen_to_slider_s">Délai avant mise en veille en secondes (0 = désactivé): %tw_screen_timeout_secs%</string>
+        <string name="screen_to_na">Paramètre de mise en veille non disponible</string>
+        <string name="screen_bright_slider">Luminosité : %tw_brightness_pct% %</string>
+        <string name="screen_bright_na">Paramètre de luminosité non disponible</string>
+        <string name="vibration_hdr">Vibration</string>
+        <string name="button_vibration_hdr">Vibration des boutons</string>
+        <string name="kb_vibration_hdr">Vibration du clavier</string>
+        <string name="act_vibration_hdr">Vibration d'action</string>
+        <string name="button_vibration">Vibration des boutons :</string>
+        <string name="kb_vibration">Vibration du clavier :</string>
+        <string name="act_vibration">Vibration d'action :</string>
+        <string name="select_language">Choisir la langue :</string>
+        <string name="set_language_btn">Définir la langue</string>
+        <string name="advanced_hdr">Paramètres avancés</string>
+        <string name="copy_log_confirm">Copier les journaux sur carte SD ?</string>
+        <string name="copying_log">Copie des journaux sur la carte SD en cours...</string>
+        <string name="copy_log_complete">Copie des journaux Effectuée</string>
+        <string name="fix_perm_btn">Corriger les autorisations</string>
+        <string name="fix_perm_s_btn">Corriger les autorisations</string>
+        <string name="part_sd_btn">Partitionner SD Card</string>
+        <string name="part_sd_s_btn">Carte SD</string>
+        <string name="file_manager_btn">Gestionnaire de fichiers</string>
+        <string name="language_hdr">Langue</string>
+        <string name="terminal_btn">Terminal</string>
+        <string name="reload_theme_btn">Recharger le thème</string>
+        <string name="dumlock_btn">HTC Dumlock</string>
+        <string name="inject_twrp_btn">Injecter TWRP</string>
+        <string name="inject_twrp_confirm">Réinjecter TWRP ?</string>
+        <string name="injecting_twrp">Réinjection de TWRP en cours...</string>
+        <string name="inject_twrp_complete">Injection de TWRP Terminée</string>
+        <string name="swipe_to_confirm">Glisser pour confirmer</string>
+        <string name="part_sd_hdr">Partitionner la carte SD</string>
+        <string name="part_sd_lose">Vous allez perdre tous les fichiers sur votre carte SD !</string>
+        <string name="part_sd_undo">Cette action ne peut pas être annulée !</string>
+        <string name="part_sd_ext_sz">Taille EXT :</string>
+        <string name="part_sd_swap_sz">Taille du swap :</string>
+        <string name="part_sd_m">-</string>
+        <string name="part_sd_p">+</string>
+        <string name="file_system">Système de Fichiers :</string>
+        <string name="swipe_part_sd">Glisser pour Partitionner</string>
+        <string name="swipe_part_sd_s">Partitionner</string>
+        <string name="partitioning_sd">Partitionnement de la carte SD en cours...</string>
+        <string name="partitioning_sd2">Cela va durer quelques minutes.</string>
+        <string name="part_sd_complete">Partitionnement Terminé</string>
+        <string name="dumlock_hdr">HTC Dumlock</string>
+        <string name="dumlock_restore_btn">Restaurer l'image de démarrage originale</string>
+        <string name="dumlock_restore_confirm">Restaurer l'image de démarrage originale ?</string>
+        <string name="dumlock_restoring">Restauration de l'image de démarrage originale en cours...</string>
+        <string name="dumlock_restore_complete">Restauration de l'image de démarrage originale effectué</string>
+        <string name="dumlock_reflash_btn">Réinstaller le mode de Récupération</string>
+        <string name="dumlock_reflash_confirm">Réinstaller le mode de Récupération sur le Démarrage ?</string>
+        <string name="dumlock_reflashing">Réinstallation du mode de Récupération sur Démarrage...</string>
+        <string name="dumlock_reflash_complete">Réinstallation du mode de Récupération sur Démarrage effectuée</string>
+        <string name="dumlock_install_btn">Installer HTC Dumlock</string>
+        <string name="dumlock_install_confirm">Installer les fichiers HTC Dumlock sur la ROM?</string>
+        <string name="dumlock_installing">Installation de HTC Dumlock...</string>
+        <string name="dumlock_install_complete">Installation de HTC Dumlock complétée</string>
+        <string name="swipe_to_unlock">Glisser pour déverrouiller</string>
+        <string name="swipe_unlock">Dévérrouiller</string>
+        <string name="fm_hdr">Gestionnaire de fichiers</string>
+        <string name="fm_sel_file">Sélectionnez un fichier ou un dossier</string>
+        <string name="fm_type_folder">Dossier</string>
+        <string name="fm_type_file">Fichier </string>
+        <string name="fm_choose_act">Choisir une action</string>
+        <string name="fm_selected">%tw_fm_type% sélectionné:</string>
+        <string name="fm_copy_btn">Copier</string>
+        <string name="fm_copy_file_btn">Copier le fichier</string>
+        <string name="fm_copy_folder_btn">Copier le dossier</string>
+        <string name="fm_copying">Copie</string>
+        <string name="fm_move_btn">Déplacer</string>
+        <string name="fm_moving">Déplacement</string>
+        <string name="fm_chmod755_btn">chmod 755</string>
+        <string name="fm_chmod755ing">chmod 755</string>
+        <string name="fm_chmod_btn">chmod</string>
+        <string name="fm_delete_btn">Supprimer</string>
+        <string name="fm_deleting">Suppression en cours</string>
+        <string name="fm_rename_btn">Renommer</string>
+        <string name="fm_rename_file_btn">Renommer le fichier</string>
+        <string name="fm_rename_folder_btn">Renommer le dossier</string>
+        <string name="fm_renaming">Renommage en cours</string>
+        <string name="fm_sel_dest">Sélectionnez le dossier de destination</string>
+        <string name="fm_sel_curr_folder">Sélectionner le dossier actuel</string>
+        <string name="fm_rename_hdr">Renommer</string>
+        <string name="fm_set_perms_hdr">Définir les permissions</string>
+        <string name="fm_perms">Permissions:</string>
+        <string name="fm_complete">Opération de fichier complétée</string>
+        <string name="decrypt_data_hdr">Décrypter les données</string>
+        <string name="decrypt_data_enter_pass"></string>
+        <string name="decrypt_data_failed">Mot de passe incorrect, veuillez réessayer!</string>
+        <string name="decrypt_data_failed_pattern">Échec du schéma, veuillez réessayer!</string>
+        <string name="decrypt_data_enter_pattern">Entrez le schéma.</string>
+        <string name="decrypt_data_trying">Tentative de décryptage</string>
+        <string name="term_hdr">Commande de terminal</string>
+        <string name="term_s_hdr">Terminal</string>
+        <string name="term_kill_btn">TUER</string>
+        <string name="term_sel_folder_hdr">Naviguez jusqu'au dossier de démarrage</string>
+        <string name="adb_sideload_hdr">Transfert via ADB</string>
+        <string name="sideload_wipe_dalvik_chk">Effacer le cache Dalvik</string>
+        <string name="sideload_wipe_cache_chk">Effacer le Cache</string>
+        <string name="swipe_to_sideload">Glisser pour débuter le transfert</string>
+        <string name="swipe_sideload">Démarrer</string>
+        <string name="sideload_confirm">Transfert via ADB</string>
+        <string name="sideload_usage">Utilisation: adb sideload (nom de fichier.zip)</string>
+        <string name="sideload_complete">Transfert par ADB complété</string>
+        <string name="fix_perms_hdr">Réparer les permissions</string>
+        <string name="fix_perms_note">Note: La réparation des permissions est rarement nécessaire.</string>
+        <string name="fix_perms_selinux_chk">Réparer aussi les contextes SELinux</string>
+        <string name="fix_perms_sel_note1">La réparation des contextes SELinux pourrait</string>
+        <string name="fix_perms_sel_note2">empêcher l'appareil de démarrer correctement.</string>
+        <string name="swipe_to_fix_perms">Glisser pour réparer les permissions</string>
+        <string name="swipe_fix_perms">Réparer les permissions</string>
+        <string name="fixing_perms">Réparation des permissions...</string>
+        <string name="fix_perms_complete">Réparation des permissions complétée</string>
+        <string name="reboot_hdr">Redémarrer</string>
+        <string name="install_cancel">Ne pas installer</string>
+        <string name="sel_storage_list">Sélection du stockage</string>
+        <string name="ok_btn">OK</string>
+        <string name="no_kernel_selinux">Le noyau ne supporte pas la lecture des contextes SELinux.</string>
+        <string name="full_selinux">Le support complet de SELinux est présent.</string>
+        <string name="no_selinux">Aucun support SELinux (pas de libselinux).</string>
+        <string name="mtp_enabled">MTP activé</string>
+        <string name="mtp_crash">MTP a planté, pas de démarrage de MTP à l'allumage.</string>
+        <string name="rebooting">Redémarrage...</string>
+        <string name="decrypt_success">Décryption réussie avec le mot de passe par défaut.</string>
+        <string name="unable_to_decrypt">Incapable de décrypter avec le mot de passe par défaut. Vous devrez peut-être formater la partition Données.</string>
+        <string name="generating_digest1" version="2">Génération du Digest</string>
+        <string name="generating_digest2" version="2">* Génération du Digest...</string>
+        <string name="digest_created" version="2">* Digest créé.</string>
+        <string name="digest_error" version="2">* Erreur Digest !</string>
+        <string name="digest_compute_error" version="2">* Erreur dans le calcul du Digest.</string>
+        <string name="digest_created" version="2">* Digest créé.</string>
+        <string name="current_date">(Date du jour)</string>
+        <string name="auto_generate">(Génération automatisée)</string>
+        <string name="unable_to_locate_partition">Impossible de localiser la partition '{1}' pour le calcul de la sauvegarde.</string>
+        <string name="no_partition_selected">Aucune partition sélectionnée pour la sauvegarde.</string>
+        <string name="total_partitions_backup">* Nombre de partitions à sauvegarder : {1}</string>
+        <string name="total_backup_size">* Taille totale des données: {1}MB</string>
+        <string name="available_space">* Espace disponible: {1}MB</string>
+        <string name="unable_locate_storage">Impossible de trouver le périphérique de stockage.</string>
+        <string name="no_space">Pas assez d'espace libre.</string>
+        <string name="backup_started">[SAUVEGARDE COMMENCÉE]</string>
+        <string name="backup_folder">* Dossier de sauvegarde: {1}</string>
+        <string name="fail_backup_folder">Échec de la création du dossier de sauvegarde.</string>
+        <string name="avg_backup_fs">Vitesse moyenne pour les systèmes de fichiers: {1} MB/sec</string>
+        <string name="avg_backup_img">Vitesse moyenne pour les images de disque: {1} MB/sec</string>
+        <string name="total_backed_size">[{1} MB TOTAL SAUVEGARDÉES]</string>
+        <string name="backup_completed">[SAUVEGARDE TERMINÉE EN {1} SECONDES]</string>
+        <string name="restore_started">[RESTAURATION COMMENCÉE]</string>
+        <string name="restore_folder">Restauration du dossier: '{1}'</string>
+        <string name="restore_part_done">[{1} complété ({2} secondes)]</string>
+        <string name="verifying_digest" version="2">Vérification du Digest</string>
+        <string name="skip_digest" version="2">Omission de la vérification des Digest selon le réglage utilisateur.</string>
+        <string name="calc_restore">Calcul des détails de la restauration...</string>
+        <string name="restore_read_only">Impossible de restaurer {1} -- monté en lecture seule.</string>
+        <string name="restore_unable_locate">Impossible de localiser la partition '{1}' pour la restauration.</string>
+        <string name="no_part_restore">Aucune partition sélectionnée pour la restauration.</string>
+        <string name="restore_part_count">Restauration de {1} partitions...</string>
+        <string name="total_restore_size">Taille totale de la restauration: {1}MB</string>
+        <string name="updating_system_details">Mise à jour des détails du système</string>
+        <string name="restore_complete">[RESTAURATION COMPLÉTÉE EN {1} SECONDES]</string>
+        <string name="error_opening_strerr">Erreur d'ouverture: '{1}' ({2})</string>
+        <string name="unable_locate_part_backup_name">Impossible de localiser la partition nommée: '{1}'</string>
+        <string name="unable_find_part_path">Impossible de trouver la partition pour le chemin '{1}'</string>
+        <string name="update_part_details">Mise à jour des informations de partition...</string>
+        <string name="update_part_details_done">...terminé</string>
+        <string name="wiping_dalvik">Effacement des dossiers du cache Dalvik...</string>
+        <string name="cleaned">Nettoyé: {1}...</string>
+        <string name="dalvik_done">-- Effacement des dossiers du cache Dalvik complété!</string>
+        <string name="no_andsec">Pas de partitions Android sécurisé trouvées.</string>
+        <string name="unable_to_locate">Impossible de trouver {1}.</string>
+        <string name="wiping_datamedia">Effacement du stockage interne -- /data/media...</string>
+        <string name="unable_to_mount">Incapable de monter {1}</string>
+        <string name="unable_to_mount_internal">Incapable de monter internal_storage</string>
+        <string name="unable_to_mount_storage">Incapable de monter le stockage</string>
+        <string name="fail_decrypt">Échec de la décryption des données.</string>
+        <string name="no_crypto_support">Le support cryptographique n'a pas été inclus dans cette compilation.</string>
+        <string name="decrypt_success">Données décryptées correctement, nouvel appareil bloc: '{1}'</string>
+        <string name="done">Terminé.</string>
+        <string name="start_partition_sd">Partitionnement de la carte SD en cours...</string>
+        <string name="partition_sd_locate">Incapable de localiser le bloc à partitioner.</string>
+        <string name="ext_swap_size">EXT + Swap est plus grand que la taille de sdcard.</string>
+        <string name="remove_part_table">Suppression de table de partition...</string>
+        <string name="unable_rm_part">Impossible de supprimer la table de partition.</string>
+        <string name="create_part">Création de la partition {1}...</string>
+        <string name="unable_to_create_part">Impossible de créer la partition {1}.</string>
+        <string name="format_sdext_as">Formatage sd-ext en {1}...</string>
+        <string name="part_complete">Partitionnement terminé.</string>
+        <string name="unable_to_open">Impossible d'ouvrir '{1}'.</string>
+        <string name="mtp_already_enabled">MTP déjà activé</string>
+        <string name="mtp_fail">Échec de l'activation du MTP</string>
+        <string name="no_mtp">Support MTP non inclus</string>
+        <string name="image_flash_start">[ÉCRITURE IMAGE COMMENCÉE]</string>
+        <string name="img_to_flash">Image à écrire: '{1}'</string>
+        <string name="flash_unable_locate">Impossible de localiser la partition '{1}' pour l'écriture.</string>
+        <string name="no_part_flash">Pas de partition sélectionnée pour l'écriture.</string>
+        <string name="too_many_flash">Trop de partitions sélectionnées pour l'écriture.</string>
+        <string name="invalid_flash">Partition invalide pour l'écriture.</string>
+        <string name="flash_done">[ÉCRITURE IMAGE COMPLÉTÉE]</string>
+        <string name="wiping">Effacement {1}</string>
+        <string name="repair_not_exist">{1} n'existe pas! Incapable de réparer!</string>
+        <string name="repairing_using">Réparation de {1} à l'aide de {2}...</string>
+        <string name="unable_repair">Incapable de réparer {1}.</string>
+        <string name="mount_data_footer">Échec du montage de /data et incapable de trouver la fin du bloc crypto.</string>
+        <string name="create_folder_strerr">Incapable de créer le dossier '{1}' ({2}).</string>
+        <string name="fail_mount">Échec du montage '{1}' ({2})</string>
+        <string name="fail_mount">Échec du démontage '{1}' ({2})</string>
+        <string name="cannot_resize">Incapable de redimensionner {1}.</string>
+        <string name="repair_resize">Réparation de {1} avant de redimensionner.</string>
+        <string name="unable_resize">Incapable de redimensionner {1}.</string>
+        <string name="no_digest_found" version="2">Pas de fichier Digest trouvé pour '{1}'. Veuillez désactiver la vérification Digest pour effectuer la restauration.</string>
+        <string name="digest_fail_match" version="2">Le Digest ne corresponds pas pour '{1}'.</string>
+        <string name="digest_matched" version="2">Le Digest correspond pour '{1}'.</string>
+        <string name="restoring">Restauration en cours</string>
+        <string name="format_data_msg">Vous aurez peut-être à redémarrer pour pouvoir utiliser /data à nouveau.</string>
+        <string name="format_data_err">Incapable de formater pour enlever l'encryption.</string>
+        <string name="formatting_using">Formatage {1} en utilisant {2}...</string>
+        <string name="unable_to_wipe">Incapable d'effacer {1}.</string>
+        <string name="remove_all">Supression de tout les fichiers sous '{1}'</string>
+        <string name="wiping_data">Effacement de /data sauf /data/media...</string>
+        <string name="backing_up">Sauvegarde {1}...</string>
+        <string name="backing">Sauvegarde en cours</string>
+        <string name="backup_size">La taille de la sauvegarde de '{1}' est de 0 octets.</string>
+        <string name="datamedia_fs_restore">AVERTISSEMENT: Cette sauvegarde de /data a été faite avec un système de fichier {1}! Il est possible que cette restauration ne fonctionne pas correctement sans que vous retourniez au format {1}.</string>
+        <string name="restoring">Restauration de {1}...</string>
+        <string name="restoring_hdr">Restauration en cours</string>
+        <string name="recreate_folder_err">Incapable de recréer le dossier {1}.</string>
+        <string name="img_size_err">La taille de l'image est plus grande que la cible</string>
+        <string name="flashing">Écriture de {1}...</string>
+        <string name="backup_folder">Dossier de sauvegarde à '{1}'</string>
+        <string name="locate_backup_err">Impossible de localiser la sauvegarde '{1}'</string>
+        <string name="set_restore_opt">Définition des options de restauration: '{1}':</string>
+        <string name="digest_check_skip" version="2">La vérification Digest est omise</string>
+        <string name="ors_encrypt_restore_err">Impossible d'utiliser OpenRecoveryScript pour restaurer une sauvegarde chiffrée.</string>
+        <string name="mounting">Montage</string>
+        <string name="unmounting">Démontage</string>
+        <string name="mounted">Monté la partition '{1}'</string>
+        <string name="unmounted">Démonté la partition '{1}'</string>
+        <string name="setting">Réglage de '{1}' à '{2}'</string>
+        <string name="setting_empty">Réglage de '{1}' à vide</string>
+        <string name="making_dir1">Création du répertoire</string>
+        <string name="making_dir2">Création du répertoire: '{1}'</string>
+        <string name="running_command">Éxécution de la commande</string>
+        <string name="sideload">Transfert via ADB</string>
+        <string name="start_sideload">Démarrage de la fonctionalité de transfert par ADB...</string>
+        <string name="need_new_adb">Vous avez besoin d'adb 1.0.32 ou plus récent pour utiliser le transfert (sideload) sur cet appareil.</string>
+        <string name="no_pwd">Aucun mot de passe fourni.</string>
+        <string name="done_ors">Traitement du fichier de script terminé</string>
+        <string name="injecttwrp">Injection de TWRP dans l'image de démarrage...</string>
+        <string name="zip_err">Erreur lors de l'installation du fichier zip '{1}'</string>
+        <string name="installing_zip">Installation du fichier zip '{1}'</string>
+        <string name="select_backup_opt">Définition des options de sauvegarde:</string>
+        <string name="compression_on">Compression activée</string>
+        <string name="digest_off" version="2">La génération de Digest est désactivée</string>
+        <string name="backup_fail">Échec de la sauvegarde</string>
+        <string name="backup_complete">Sauvegarde terminée!</string>
+        <string name="running_recovery_commands">Exécution des commandes de récupération</string>
+        <string name="recovery_commands_complete">Commandes de récupération complétées</string>
+        <string name="running_ors">OpenRecoveryScript en cours d'exécution</string>
+        <string name="ors_complete">OpenRecoveryScript complété</string>
+        <string name="no_updater_binary">'{1}' introuvable dans le fichier zip.</string>
+        <string name="check_for_digest" version="2">Vérification des fichiers Digest...</string>
+        <string name="fail_sysmap">Impossible de mapper le fichier '{1}'</string>
+        <string name="verify_zip_sig">Vérification de la signature du zip...</string>
+        <string name="verify_zip_fail">Échec de la vérification des signatures du zip!</string>
+        <string name="verify_zip_done">Signature du zip vérifiée correctement.</string>
+        <string name="zip_corrupt">Le fichier zip est corrompu!</string>
+        <string name="no_digest" version="2">Pas de vérification Digest: aucun fichier Digest trouvé</string>
+        <string name="digest_fail" version="2">Le Digest ne correspond pas</string>
+        <string name="digest_match" version="2">Le Digest correspond</string>
+        <string name="pid_signal">Le processus {1} s'est terminé avec le signal: {2}</string>
+        <string name="pid_error">Le processus {1} s'est terminé avec ERREUR: {2}</string>
+        <string name="install_dumlock">Installation de HTC Dumlock...</string>
+        <string name="dumlock_restore">Restauration de l'image de démarrage originale en cours...</string>
+        <string name="dumlock_reflash">Réécriture de l'image de récupération sur celle de démarrage...</string>
+        <string name="run_script">Exécution du script {1}...</string>
+        <string name="rename_stock">Renommé le fichier de récupération original dans /system pour empêcher le système d'exploitation original de remplacer TWRP.</string>
+        <string name="split_backup">Morcellement des fichiers de sauvegarde en plusieurs archives...</string>
+        <string name="backup_error">Erreur dans la création de la sauvegarde.</string>
+        <string name="restore_error">Erreur durant le procédé de restauration.</string>
+        <string name="split_thread">Division du thread ID {1} en archive {2}</string>
+        <string name="file_progress">%llu de %llu fichiers, %i%%</string>
+        <string name="size_progress">%lluMO de %lluMO, %i%%</string>
+        <string name="decrypt_cmd">Tentative de décryption de la partition de données via la ligne de commande.</string>
+        <string name="base_pkg_err">Incapable de charger les paquets de base.</string>
+        <string name="simulating">Simulation des actions...</string>
+        <string name="backup_cancel">Sauvegarde annulée.</string>
+        <string name="config_twrp">Configuration de TWRP...</string>
+        <string name="config_twrp_err">Impossible de configurer TWRP avec ce noyau.</string>
+        <string name="copy_log">Copié le journal de récupération sur {1}.</string>
+        <string name="max_queue">File d'attente pour fichiers zip maximale atteinte!</string>
+        <string name="min_queue">File d'attente pour fichiers zip minimale atteinte!</string>
+        <string name="screenshot_saved">La capture d'écran a été sauvegardée à {1}</string>
+        <string name="screenshot_err">Échec de la capture d'écran!</string>
+        <string name="zip_wipe_cache">Un ou plusieurs zip ont demandé un effacement du cache -- E/n cours maintenant.</string>
+        <string name="and_sec_wipe_err">Impossible d'effacer "Android Secure"</string>
+        <string name="dalvik_wipe_err">Échec de l'effacement de Dalvik</string>
+        <string name="auto_gen">(Génération automatisée)</string>
+        <string name="curr_date">(Date du jour)</string>
+        <string name="backup_name_len">Le nom de la sauvegarde est trop long.</string>
+        <string name="backup_name_invalid">Le nom de la sauvegarde '{1}' contient le caractère non valide: '{1}'</string>
+        <string name="backup_name_exists">Une sauvegarde avec ce nom existe déjà.</string>
+        <string name="no_real_sdcard">Cet appareil n'a pas une vraie carte SD! Abandon!</string>
+        <string name="cancel_sideload">Annulation du transfert par ADB...</string>
+        <string name="change_fs_err">Erreur lors du changement du système de fichier.</string>
+    </resources>
+</language>
diff --git a/gui/theme/common/languages/hu.xml b/gui/theme/common/languages/hu.xml
new file mode 100644
index 0000000..2030889
--- /dev/null
+++ b/gui/theme/common/languages/hu.xml
@@ -0,0 +1,689 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<language>
+	<display>Magyar</display>
+
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<!-- Partition display names -->
+		<string name="system">Rendszer</string>
+		<string name="system_image">Rendszerkép</string>
+		<string name="vendor">Gyártó</string>
+		<string name="vendor_image">Gyártói kép</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Gyorsítótár</string>
+		<string name="data">Adat</string>
+		<string name="data_backup">Adat (data/media nélkül)</string>
+		<string name="sdcard">SD-kártya</string>
+		<string name="internal">Belső tárhely</string>
+		<string name="microsd">Mikro SD-kártya</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART-gyorsítótár</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Adoptált adatok</string>
+		<string name="adopted_storage">Adoptált tároló</string>
+
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% °C</string>
+		<string name="battery_pct">Akkumulátor: %tw_battery%</string>
+		<string name="sort_by_name">Rendezés név szerint</string>
+		<string name="sort_by_date">Rendezés dátum szerint</string>
+		<string name="sort_by_size">Rendezés méret szerint</string>
+		<string name="sort_by_name_only">Név</string>
+		<string name="sort_by_date_only">Dátum</string>
+		<string name="sort_by_size_only">Méret</string>
+		<string name="tab_general">ÁLTALÁNOS</string>
+		<string name="tab_options">BEÁLLÍTÁSOK</string>
+		<string name="tab_backup">BIZTONSÁGI MENTÉS</string>
+		<string name="tab_time_zone">IDŐZÓNA</string>
+		<string name="tab_screen">KÉPERNYŐ</string>
+		<string name="tab_vibration">REZGÉS</string>
+		<string name="tab_language">NYELV</string>
+
+		<string name="install_btn">Telepítés</string>
+		<string name="wipe_btn">Törlés</string>
+		<string name="backup_btn">Biztonsági mentés</string>
+		<string name="restore_btn">Visszaállítás</string>
+		<string name="mount_btn">Csatolás</string>
+		<string name="settings_btn">Beállítások</string>
+		<string name="advanced_btn">Haladó</string>
+		<string name="reboot_btn">Újraindítás</string>
+		<string name="files_btn">Fájlok</string>
+		<string name="copy_log_btn">Napló másolása</string>
+		<string name="select_type_hdr">Válasszon típust</string>
+		<string name="install_zip_hdr">Zip telepítése</string>
+		<string name="install_zip_btn">Zip telepítése</string>
+		<string name="install_image_hdr">Képfájl telepítése</string>
+		<string name="install_image_btn">Képfájl telepítése</string>
+		<string name="install_select_file_hdr">Fájl kiválasztása</string>
+		<string name="file_selector_folders_hdr">Mappák</string>
+		<string name="select_file_from_storage">Fájl kiválasztása innen: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Telepítés</string>
+		<string name="select_storage_hdr">Válasszon tárolót</string>
+		<string name="select_storage_btn">Válasszon tárolót</string>
+		<string name="queue_hdr">Várólista</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% / max 10 fájl a várólistán</string>
+		<string name="zip_queue_count_s">%tw_zip_queue_count% / 10 fájl:</string>
+		<string name="zip_warn1">Ez a művelet nem kompatibilis szoftvert telepíthet,</string>
+		<string name="zip_warn2">és a készüléket használhatatlanná teheti.</string>
+		<string name="zip_back_cancel">Nyomja meg a vissza gombot a zip hozzáadásának visszavonásához.</string>
+		<string name="zip_back_clear">Nyomja meg a vissza gombot a várólista törléséhez.</string>
+		<string name="folder">Mappa:</string>
+		<string name="file">Fájl:</string>
+		<string name="zip_sig_chk">Zip aláírás ellenőrzése</string>
+		<string name="inject_twrp_chk">TWRP injektálása telepítés után</string>
+		<string name="install_reboot_chk">Újraindítás telepítés vége után</string>
+		<string name="options_hdr">Beállítások</string>
+		<string name="confirm_flash_hdr">Flashelés megerősítése</string>
+		<string name="zip_queue">Várólista:</string>
+		<string name="options">Beállítások:</string>
+		<string name="swipe_confirm">   Megerősítés</string>
+		<string name="zip_add_btn">Több Zip hozzáadása</string>
+		<string name="zip_clear_btn">Zip várólista törlése</string>
+		<string name="install_zip_count_hdr">%tw_zip_index% / %tw_zip_queue_count% Zip telepítése</string>
+		<string name="installing_zip_xml">Zip telepítése: %tw_file%</string>
+		<string name="failed">Sikertelen</string>
+		<string name="successful">Sikeres</string>
+		<string name="install_failed">A telepítés sikertelen</string>
+		<string name="install_successful">A telepítés sikeres</string>
+		<string name="wipe_cache_dalvik_btn">Gyorsítótár/dalvik törlése</string>
+		<string name="reboot_system_btn">Rendszer újraindítása</string>
+		<string name="install_sel_target">Válassza ki a célpartíciót</string>
+		<string name="flash_image_select">Kép flasheléséhez vállaszon partíciót:</string>
+		<string name="target_partition">Célpartíció:</string>
+		<string name="flashing_image">Kép flashelése...</string>
+		<string name="image_flashed">Kép flashelve</string>
+		<string name="wipe_cache_dalvik_confirm">Gyorsítótár &amp; Dalvik törlése?</string>
+		<string name="wiping_cache_dalvik">Gyorsítótár &amp; Dalvik törlése...</string>
+		<string name="wipe_cache_dalvik_complete">Gyorsítótár &amp; Dalvik törlése kész</string>
+		<string name="swipe_wipe">Csúsztasson a törléshez</string>
+		<string name="swipe_wipe_s">   Törlés</string>
+		<string name="no_os1">Nincs telepített operációs rendszer!</string>
+		<string name="no_osrb">Biztosan újra kívánja indítani?</string>
+		<string name="no_ospo">Biztosan ki szeretné kapcsolni?</string>
+		<string name="rebooting">Újraindítás...</string>
+		<string name="swipe_reboot">Csúsztasson az újraindításhoz</string>
+		<string name="swipe_reboot_s">   Újraindítás</string>
+		<string name="reboot_install_app_hdr">TWRP alkalmazás telepítése?</string>
+		<string name="reboot_install_app1">Szeretné telepíteni a hivatalos TWRP alkalmazást?</string>
+		<string name="reboot_install_app2">Az alkalmazás képes az új TWRP verziók ellenőrzésére.</string>
+		<string name="reboot_install_app_prompt_install">Azonnal telepítse a TWRP alkalmazást, ha nincs telepítve</string>
+		<string name="reboot_install_app_system">Telepítés rendszeralkalmazásként</string>
+		<string name="reboot_installing_app">Alkalmazás telepítése...</string>
+		<string name="swipe_to_install_app">Csúsztasson a TWRP alkalmazás telepítéséhez</string>
+		<string name="swipe_flash">Csúsztasson a flashelés megerősítéséhez</string>
+		<string name="confirm_action">Művelet megerősítése</string>
+		<string name="back_cancel">Nyomja meg a vissza gombot a megszakításhoz.</string>
+		<string name="cancel_btn">Mégsem</string>
+		<string name="wipe_hdr">Törlés</string>
+		<string name="factory_reset_hdr">Gyári beállítások visszaállítása</string>
+		<string name="factory_reset_btn">Gyári beállítások visszaállítása</string>
+		<string name="factory_reset1">Törli az Adatot, Gyorsítótárat, és Dalvikot</string>
+		<string name="factory_reset2">(nem érinti a belső tárolót)</string>
+		<string name="factory_reset3">Leggyakrabban ez az</string>
+		<string name="factory_reset4">amire szüksége van.</string>
+		<string name="factory_resetting">Gyári beállítások visszaállítása...</string>
+		<string name="advanced_wipe_hdr">Speciális törlés</string>
+		<string name="advanced_wipe_btn">Speciális törlés</string>
+		<string name="wipe_enc_confirm">Törli az adatok titkosítását?</string>
+		<string name="formatting_data">Adatok formázása...</string>
+		<string name="swipe_format_data">Csúsztasson az Adatok formázásához</string>
+		<string name="swipe_format_data_s">   Adatok formázása</string>
+		<string name="factory_reset_complete">Gyári beállítások visszaállítása kész</string>
+		<string name="sel_part_hdr">Válassza ki a partíciókat</string>
+		<string name="wipe_sel_confirm">Törli a kiválasztott partíció(ka)t?</string>
+		<string name="wiping_part">Partíció(k) törlése...</string>
+		<string name="wipe_complete">Törlés kész</string>
+		<string name="sel_part_wipe">Válassza ki a törlendő partíciókat:</string>
+		<string name="invalid_part_sel">Érvénytelen partíciós kiválasztás</string>
+		<string name="format_data_hdr">Adatok formázása</string>
+		<string name="format_data_btn">Adatok formázása</string>
+		<string name="format_data_ptr1">Adatok formázása törli az összes alkalmazását,</string>
+		<string name="format_data_ptr2">mentését, képét, videóját, médiáját és</string>
+		<string name="format_data_ptr3">eltávolítja a titkosítást a belső tárolón.</string>
+		<string name="format_data_adopted">Beleértve az adoptált tárolót</string>
+		<string name="format_data_lcp1">Adatok formázása törli az összes alkalmazását, mentését, képét, videóját, médiáját és</string>
+		<string name="format_data_lcp2">eltávolítja a titkosítást a belső tárolón.</string>
+		<string name="format_data_wtc1">Adatok formázása törli az összes alkalmazását,</string>
+		<string name="format_data_wtc2">mentését és médiáját. Nem lehet visszavonni.</string>
+		<string name="format_data_undo">Ezt nem lehet visszavonni.</string>
+		<string name="format_data_complete">Adatok formázása kész</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">Folytatáshoz gépelje be: yes. Nyomja meg a vissza gombot a megszakításhoz.</string>
+		<string name="part_opt_hdr">Partíció lehetőségei: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Válasszon műveletet</string>
+		<string name="file_sys_opt">Fájlrendszer beállítások</string>
+		<string name="partition">Partíció: %tw_partition_name%</string>
+		<string name="part_mount_point">Csatlakoztatási pont: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Fájlrendszer: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Jelen: igen</string>
+		<string name="part_present_no">Jelen: nem</string>
+		<string name="part_removable_yes">Cserélhető: igen</string>
+		<string name="part_removable_no">Cserélhető: nem</string>
+		<string name="part_size">Méret: %tw_partition_size%MB</string>
+		<string name="part_used">Használt: %tw_partition_used%MB</string>
+		<string name="part_free">Szabad: %tw_partition_free%MB</string>
+		<string name="part_backup_size">Biztonsági mentés mérete: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">Fájlrendszer átméretezése</string>
+		<string name="resize_btn_s">Átméretezés</string>
+		<string name="resize_confirm">Átméretezi: %tw_partition_name%?</string>
+		<string name="resizing">Átméretezés...</string>
+		<string name="resize_complete">Átméretezés kész</string>
+		<string name="swipe_resize">Csúsztasson az átméretezéshez</string>
+		<string name="swipe_resize_s">   Átméretezés</string>
+		<string name="repair_btn">Fájlrendszer javítása</string>
+		<string name="repair_btn_s">Javítás</string>
+		<string name="repair_confirm">Javítja: %tw_partition_name%?</string>
+		<string name="repairing">Javítás...</string>
+		<string name="repair_complete">Javítás kész</string>
+		<string name="swipe_repair">Csúsztasson a javításhoz</string>
+		<string name="swipe_repair_s">   Javítás</string>
+		<string name="change_fs_btn">Fájlrendszer módosítása</string>
+		<string name="change_fs_btn_s">Módosítás</string>
+		<string name="change_fs_for_hdr">Fájlrendszer módosítása: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Partíció: %tw_partition_name% &gt; Válasszon fájlrendszert</string>
+		<string name="change_fs_warn1">Néhány ROM vagy a kernel nem támogat bizonyos</string>
+		<string name="change_fs_warn2">fájlrendszert. Óvatosan folytassa!</string>
+		<string name="change_fs_confirm">Módosítja: %tw_partition_name%?</string>
+		<string name="formatting">Formázás...</string>
+		<string name="format_complete">Formázás kész</string>
+		<string name="swipe_change_fs">Csúsztasson a módosításhoz</string>
+		<string name="swipe_change_s">   Módosítás</string>
+		<string name="back_btn">Vissza</string>
+		<string name="wipe_enc_btn">Titkosítás törlése</string>
+		<string name="swipe_factory_reset">Csúsztasson a gyári beállítások visszaállításához</string>
+		<string name="repair_change_btn">Fájlrendszer javítása vagy módosítása</string>
+		<string name="storage_hdr">Tároló: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Biztonsági mentés</string>
+		<string name="backup_confirm_hdr">Biztonsági mentés megerősítése</string>
+		<string name="encryption_tab">TITKOSÍTÁS</string>
+		<string name="encryption">Titkosítás:</string>
+		<string name="name">Név:</string>
+		<string name="sel_part_backup">Válassza ki a menteni kívánt partíciókat:</string>
+		<string name="storage">Tárhely:</string>
+		<string name="enc_disabled">letiltva - állítson be jelszót az engedélyezéshez</string>
+		<string name="enc_enabled">engedélyezve</string>
+		<string name="enable_backup_comp_chk">Tömörítés engedélyezése</string>
+		<string name="skip_digest_backup_chk" version="2">Mentés közbeni Digest generálás kihagyása</string>
+		<string name="disable_backup_space_chk" version="2">Szabad hely ellenőrzésének letiltása</string>
+		<string name="current_boot_slot">Jelenlegi Slot: %tw_active_slot%</string>
+		<string name="boot_slot_a">Slot A</string>
+		<string name="boot_slot_b">Slot B</string>
+		<string name="changing_boot_slot">Boot Slot váltása</string>
+		<string name="changing_boot_slot_complete">Boot Slot váltása befejezve</string>
+		<string name="refresh_sizes_btn">Méretek frissítése</string>
+		<string name="swipe_backup">Csúsztasson a biztonsági mentéshez</string>
+		<string name="append_date_btn">Hozzáadás dátuma</string>
+		<string name="backup_name_exists">Egy biztonsági mentés az adott névvel már létezik!</string>
+		<string name="encrypt_backup">Biztonsági mentését titkosítani szeretné?</string>
+		<string name="enter_pass">Adja meg a jelszót:</string>
+		<string name="enter_pass2">Adja meg a jelszót újra:</string>
+		<string name="pass_not_match">A jelszavak nem egyeznek!</string>
+		<string name="partitions">Partíciók:</string>
+		<string name="disabled">Letiltva</string>
+		<string name="enabled">Engedélyezve</string>
+		<string name="backup_name_hdr">Biztonsági mentés nevének beállítása</string>
+		<string name="progress">Folyamat:</string>
+		<string name="backup_complete">Biztonsági mentés kész</string>
+		<string name="restore_hdr">Visszaállítás</string>
+		<string name="sel_backup_hdr">Biztonsági mentés kiválasztása</string>
+		<string name="restore_sel_store_hdr">Mentés kiválasztása innen: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Válassza ki a visszaállítandó csomagot:</string>
+		<string name="restore_enc_backup_hdr">Titkosított biztonsági mentés</string>
+		<string name="restore_dec_fail">Jelszó hibás, próbálja meg újra!</string>
+		<string name="del_backup_btn">Biztonsági mentés törlése</string>
+		<string name="del_backup_confirm">Biztonsági mentés törlése?</string>
+		<string name="del_backup_confirm2">Ezt nem lehet visszavonni!</string>
+		<string name="deleting_backup">Biztonsági mentés törlése...</string>
+		<string name="backup_deleted">Biztonsági mentés törlése kész</string>
+		<string name="swipe_delete">Csúsztasson a törléshez</string>
+		<string name="swipe_delete_s">   Törlés</string>
+		<string name="restore_try_decrypt">Titkosított biztonsági mentés - Dekódolás megpróbálása</string>
+		<string name="restore_try_decrypt_s">Dekódolás megpróbálása</string>
+		<string name="restore_backup_date">Biztonsági mentés ideje: %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Válassza ki a visszaállítandó partíciókat:</string>
+		<string name="restore_enable_digest_chk" version="3">A biztonsági mentési fájlok Digest ellenőrzésének engedélyezése</string>
+		<string name="restore_complete">Mentés helyreállítása kész</string>
+		<string name="swipe_restore">Csúsztasson a visszaállításhoz</string>
+		<string name="swipe_restore_s">   Visszaállítás</string>
+		<string name="rename_backup_hdr">Biztonsági mentés átnevezése</string>
+		<string name="rename_backup_confirm">Biztonsági mentés átnevezése?</string>
+		<string name="rename_backup_confirm2">Ezt nem lehet visszavonni!</string>
+		<string name="renaming_backup">Biztonsági mentés átnevezése...</string>
+		<string name="rename_backup_complete">Biztonsági mentés átnevezése kész</string>
+		<string name="swipe_to_rename">Csúsztasson az átnevezéshez</string>
+		<string name="swipe_rename">   Átnevezés</string>
+		<string name="confirm_hdr">Megerösítés</string>
+		<string name="mount_hdr">Csatolás</string>
+		<string name="mount_sel_part">Válassza ki a csatolandó partíciókat:</string>
+		<string name="mount_sys_ro_chk">Rendszerpartíció csatolása csak olvashatóként</string>
+		<string name="mount_sys_ro_s_chk">Rendszer csatolása RO</string>
+		<string name="decrypt_data_btn">Adat dekódolása</string>
+		<string name="disable_mtp_btn">MTP letiltása</string>
+		<string name="enable_mtp_btn">MTP engedélyezése</string>
+		<string name="mount_usb_storage_btn">USB-tár csatolása</string>
+		<string name="usb_storage_hdr">USB-tár</string>
+		<string name="usb_stor_mnt1">USB-tár csatolva</string>
+		<string name="usb_stor_mnt2">Győződjön meg róla, hogy biztonságosan távolítja el az eszközt</string>
+		<string name="usb_stor_mnt3">a számítógépről való leválasztás előtt!</string>
+		<string name="unmount_btn">Leválasztás</string>
+		<string name="rb_system_btn">Rendszer</string>
+		<string name="rb_poweroff_btn">Kikapcsolás</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Download mód</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Kikapcsolás...</string>
+		<string name="swipe_power_off">Csúsztasson a kikapcsoláshoz</string>
+		<string name="swipe_power_off_s">Kikapcsolás</string>
+		<string name="sys_ro_hdr">Változatlan rendszerpartíció</string>
+		<string name="sys_ro_keep">A rendszert továbbra is csak olvashatónak hagyja?</string>
+		<string name="sys_rop1">TWRP a rendszerpartícióját változatlanul hagyhatja,</string>
+		<string name="sys_rop2">hogy megkönnyítse a hivatalos frissítéseket.</string>
+		<string name="sys_rop3">TWRP nem lesz képes megakadályozni a gyári ROM-ot abban,</string>
+		<string name="sys_rop4">hogy lecserélje a TWRP-t és nem ajánlja fel a rootolást eszközéhez.</string>
+		<string name="sys_rop5">Zip-ek telepítése, vagy az adb műveletek továbbra is</string>
+		<string name="sys_rop6">módosítják a rendszer partícióit.</string>
+		<string name="sys_rol1">TWRP a rendszerpartícióját változatlanul hagyhatja, hogy megkönnyítse a hivatalos frissítéseket.</string>
+		<string name="sys_rol2">TWRP nem lesz képes megakadályozni a gyári ROM-ot abban, hogy lecserélje a TWRP-t és nem ajánlja fel a rootolást eszközéhez.</string>
+		<string name="sys_rol3">Zip-ek telepítése, vagy az adb műveletek továbbra is módosítják a rendszer partícióit.</string>
+		<string name="sys_ro_never_show_chk">Többé ne mutassa ezt a képernyőt</string>
+		<string name="sys_ro_keep_ro_btn">Csak olvasható megtartása</string>
+		<string name="swipe_allow_mod">Csúsztasson a módosítások engedélyezéséhez</string>
+		<string name="swipe_allow_mod_s">Módosítások engedélyezése</string>
+		<string name="settings_hdr">Beállítások</string>
+		<string name="settings_gen_hdr">Általános</string>
+		<string name="settings_gen_s_hdr">Általános</string>
+		<string name="settings_gen_btn">Általános</string>
+		<string name="use_rmrf_chk">Az rm -rf használata a formázás helyett</string>
+		<string name="use24clock_chk">24-órás idő használata</string>
+		<string name="rev_navbar_chk">Fordított navigációs sáv elrendezés</string>
+		<string name="simact_chk">Műveletek szimulálása téma teszteléséhez</string>
+		<string name="simfail_chk">Sikertelen műveletek szimulálása</string>
+		<string name="ctr_navbar_rdo">Navigációs sáv gombjai középre</string>
+		<string name="lft_navbar_rdo">Navigációs sáv gombjai balra igazítva</string>
+		<string name="rht_navbar_rdo">Navigációs sáv gombjai jobbra igazítva</string>
+		<string name="restore_defaults_btn">Alapértelmezés visszaállítása</string>
+		<string name="settings_tz_btn">Időzóna</string>
+		<string name="settings_screen_btn">Képernyő</string>
+		<string name="settings_screen_bright_btn">Képernyő fényereje</string>
+		<string name="settings_vibration_btn">Rezgés</string>
+		<string name="settings_language_btn">Nyelv</string>
+		<string name="time_zone_hdr">Időzóna</string>
+		<string name="sel_tz_list">Időzóna választása:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC-11) Szamoa, Midway-szigetek</string>
+		<string name="utcm10">(UTC-10) Hawaii</string>
+		<string name="utcm9">(UTC-9) Alaszka</string>
+		<string name="utcm8">(UTC -8) Csendes-óceáni idő</string>
+		<string name="utcm7">(UTC-7) Hegyvidéki idő</string>
+		<string name="utcm6">(UTC -6) Közép idő</string>
+		<string name="utcm5">(UTC -5) Keleti idő</string>
+		<string name="utcm4">(UTC -4) Atlanti idő</string>
+		<string name="utcm3">(UTC -3) Brazília, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Közép-Atlanti</string>
+		<string name="utcm1">(UTC -1) Azori-szigetek, Zöld-foki Köztársaság</string>
+		<string name="utc0">(UTC 0) London, Dublin, Lisszabon</string>
+		<string name="utcp1">(UTC +1) Berlin, Brüsszel, Párizs</string>
+		<string name="utcp2">(UTC +2) Athén, Isztambul, Dél-afrikai Köztársaság</string>
+		<string name="utcp3">(UTC +3) Moszkva, Bagdad</string>
+		<string name="utcp4">(UTC +4) Abu-Dzabi, Tbiliszi, Maszkat</string>
+		<string name="utcp5">(UTC +5) Jekatyerinburg, Iszlámábád</string>
+		<string name="utcp6">(UTC +6) Almati, Dakka, Colombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp8">(UTC +8) Peking, Szingapúr, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokió, Szöul, Jakutszk</string>
+		<string name="utcp10">(UTC +10) Kelet-Ausztrália, Guam</string>
+		<string name="utcp11">(UTC +11) Vlagyivosztok, Salamon-szigetek</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fidzsi-szigetek</string>
+		<string name="sel_tz_offset">Válasszon eltolást (általában 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Nincs</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Nyári időszámítás használata (DST)</string>
+		<string name="curr_tz">Jelenlegi időzóna: %tw_time_zone%</string>
+		<string name="curr_tz_s">Jelenlegi időzóna:</string>
+		<string name="set_tz_btn">Időzóna beállítása</string>
+		<string name="settings_screen_hdr">Képernyő</string>
+		<string name="settings_screen_timeout_hdr">Képernyő időkorlátja</string>
+		<string name="enable_timeout_chk">Képernyő időkorlát engedélyezése</string>
+		<string name="screen_to_slider">Képernyő időkorlát másodpercben:</string>
+		<string name="screen_to_slider_s">Képernyő időkorlát másodpercben (0=letiltva): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Képernyő időkorlát beállítás nem érhető el</string>
+		<string name="screen_bright_slider">Fényerő: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Fényerő beállítás nem érhető el</string>
+		<string name="vibration_hdr">Rezgés</string>
+		<string name="button_vibration_hdr">Gombok rezgése</string>
+		<string name="kb_vibration_hdr">Billentyűzet rezgése</string>
+		<string name="act_vibration_hdr">Művelet rezgése</string>
+		<string name="button_vibration">Gomb rezgés:</string>
+		<string name="kb_vibration">Billentyűzet rezgés:</string>
+		<string name="act_vibration">Művelet rezgés:</string>
+		<string name="select_language">Nyelv kiválasztása:</string>
+		<string name="sel_lang_btn">Nyelv kiválasztása</string>
+		<string name="set_language_btn">Nyelv beállítása</string>
+		<string name="advanced_hdr">Haladó</string>
+		<string name="copy_log_confirm">Napló másolása SD-kártyára?</string>
+		<string name="copying_log" version="2">Napló másolása az SD-kártyára...</string>
+		<string name="copy_log_complete" version="2">Napló másolása kész</string>
+		<string name="fix_context_btn">Kontextus javítása</string>
+		<string name="part_sd_btn">SD-kártya particionálása</string>
+		<string name="part_sd_s_btn">SD-kártya</string>
+		<string name="file_manager_btn">Fájlkezelő</string>
+		<string name="language_hdr">Nyelv</string>
+		<string name="terminal_btn">Terminál</string>
+		<string name="reload_theme_btn">Téma újratöltése</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">TWRP injektálása</string>
+		<string name="inject_twrp_confirm">TWRP újrainjektálása?</string>
+		<string name="injecting_twrp">TWRP újrainjektálása...</string>
+		<string name="inject_twrp_complete">TWRP injektálása kész</string>
+		<string name="swipe_to_confirm">Csúsztasson a megerősítéshez</string>
+		<string name="part_sd_hdr">SD-kártya particionálása</string>
+		<string name="invalid_partsd_sel">Ki kell választania egy eltávolítható eszközt</string>
+		<string name="part_sd_lose">Minden fájl el fog veszni az SD-kártyán!</string>
+		<string name="part_sd_undo">Ez a művelet nem vonható vissza!</string>
+		<string name="part_sd_ext_sz">EXT méret:</string>
+		<string name="part_sd_swap_sz">Swap méret:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Fájlrendszer:</string>
+		<string name="swipe_part_sd">Csúsztasson a particionáláshoz</string>
+		<string name="swipe_part_sd_s">Particionálás</string>
+		<string name="partitioning_sd">SD-kártya particionálása...</string>
+		<string name="partitioning_sd2">Ez néhány percet igénybe vesz.</string>
+		<string name="part_sd_complete">Particionálás kész</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Eredeti boot visszaállítása</string>
+		<string name="dumlock_restore_confirm">Visszaállítja az eredeti boot lemezképet?</string>
+		<string name="dumlock_restoring">Eredeti boot visszaállítása...</string>
+		<string name="dumlock_restore_complete">Eredeti boot visszaállítása kész</string>
+		<string name="dumlock_reflash_btn">Recovery újraflashelése</string>
+		<string name="dumlock_reflash_confirm">Recovery újraflashelése bootba?</string>
+		<string name="dumlock_reflashing">Recovery újraflashelése bootba...</string>
+		<string name="dumlock_reflash_complete">Recovery flashelése bootba kész</string>
+		<string name="dumlock_install_btn">HTC Dumlock telepítése</string>
+		<string name="dumlock_install_confirm">HTC Dumlock telepítése a ROM-hoz?</string>
+		<string name="dumlock_installing">HTC Dumlock telepítése...</string>
+		<string name="dumlock_install_complete">HTC Dumlock telepítése kész</string>
+		<string name="swipe_to_unlock">Csúsztasson a feloldáshoz</string>
+		<string name="swipe_unlock">   Feloldás</string>
+		<string name="fm_hdr">Fájlkezelő</string>
+		<string name="fm_sel_file">Válasszon fájlt vagy mappát</string>
+		<string name="fm_type_folder">Mappa</string>
+		<string name="fm_type_file">Fájl</string>
+		<string name="fm_choose_act">Válasszon műveletet</string>
+		<string name="fm_selected">%tw_fm_type% kiválasztva:</string>
+		<string name="fm_copy_btn">Másolás</string>
+		<string name="fm_copy_file_btn">Fájl másolása</string>
+		<string name="fm_copy_folder_btn">Mappa másolása</string>
+		<string name="fm_copying">Másolás</string>
+		<string name="fm_move_btn">Áthelyezés</string>
+		<string name="fm_moving">Áthelyezés</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Törlés</string>
+		<string name="fm_deleting">Törlés</string>
+		<string name="fm_rename_btn">Átnevezés</string>
+		<string name="fm_rename_file_btn">Fájl átnevezése</string>
+		<string name="fm_rename_folder_btn">Mappa átnevezése</string>
+		<string name="fm_renaming">Átnevezés</string>
+		<string name="fm_sel_dest">Válassza ki a célmappát</string>
+		<string name="fm_sel_curr_folder">Válassza ki az aktuális mappát</string>
+		<string name="fm_rename_hdr">Átnevezés</string>
+		<string name="fm_set_perms_hdr">Engedélyek beállítása</string>
+		<string name="fm_perms">Engedélyek:</string>
+		<string name="fm_complete">Fájlművelet kész</string>
+		<string name="decrypt_data_hdr">Adat dekódolása</string>
+		<string name="decrypt_data_enter_pass">Adja meg a jelszót.</string>
+		<string name="decrypt_data_enter_pattern">Adja meg a mintát.</string>
+		<string name="decrypt_data_failed">Helytelen jelszó, próbálja újra!</string>
+		<string name="decrypt_data_failed_pattern">Helytelen minta, próbálja újra!</string>
+		<string name="decrypt_data_trying">Dekódolás megpróbálása</string>
+		<string name="term_hdr">Terminál parancs</string>
+		<string name="term_s_hdr">Terminál</string>
+		<string name="term_kill_btn">MEGÖL</string>
+		<string name="term_sel_folder_hdr">Navigáljon a kezdőmappához</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Dalvik gyorsítótár törlése</string>
+		<string name="sideload_wipe_cache_chk">Gyorsítótár törlése</string>
+		<string name="swipe_to_sideload">Csúsztasson a Sideload indításához</string>
+		<string name="swipe_sideload">   Indítás</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Használat: adb sideload fájlnév.zip</string>
+		<string name="sideload_complete">ADB Sideload kész</string>
+		<string name="fix_contexts_hdr">Kontextus javítása</string>
+		<string name="fix_contexts_note1">Megjegyzés: kontextus javítására ritkán van szükség.</string>
+		<string name="fix_contexts_note2">SELinux kontextus javítása a készülék</string>
+		<string name="fix_contexts_note3">nem megfelelő elindulását okozhatja.</string>
+		<string name="swipe_to_fix_contexts">Csúsztasson a kontextus javításához</string>
+		<string name="swipe_fix_contexts">   Kontextus javítása</string>
+		<string name="fixing_contexts">Kontextus javítása...</string>
+		<string name="fix_contexts_complete">Kontextus javítása kész</string>
+		<string name="reboot_hdr">Újraindítás</string>
+		<string name="install_cancel">Ne telepítse</string>
+		<string name="sel_storage_list">Válasszon tárolót</string>
+		<string name="ok_btn">OK</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">Kernel nem támogatja a SELinux összefüggések olvasását.</string>
+		<string name="full_selinux">Teljes SELinux támogatás jelen van.</string>
+		<string name="no_selinux">Nincs SELinux támogatás (nincs libselinux).</string>
+		<string name="mtp_enabled">MTP engedélyezve</string>
+		<string name="mtp_crash">MTP összeomlott, MTP nem indul bootoláskor.</string>
+		<string name="decrypt_success">Sikeresen visszafejtve az alapértelmezett jelszóval.</string>
+		<string name="unable_to_decrypt">Visszafejtés sikertelen az alapértelmezett jelszóval. Szükség lehet az adatok formázására.</string>
+		<string name="generating_digest1" version="3">Digest generálása</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="3"> * Digest generálása...</string>
+		<string name="digest_created" version="3"> * Digest létrehozva.</string>
+		<string name="digest_error" version="3"> * Digest hiba!</string>
+		<string name="digest_compute_error" version="3"> * Digest számítási hiba.</string>
+		<string name="current_date">(Aktuális dátum)</string>
+		<string name="auto_generate">(Automatikus létrehozás)</string>
+		<string name="unable_to_locate_partition">'{1}' partíció nem található a biztonsági mentési számításokhoz.</string>
+		<string name="no_partition_selected">Nincs biztonsági mentésre kijelölt partíció.</string>
+		<string name="total_partitions_backup"> * A biztonsági mentésre jelölt partíciók teljes száma: {1}</string>
+		<string name="total_backup_size"> * Az összes adat teljes mérete: {1}MB</string>
+		<string name="available_space"> * A rendelkezésre álló terület: {1}MB</string>
+		<string name="unable_locate_storage">Nem található tárolóeszköz.</string>
+		<string name="no_space">Nincs elég szabad hely a tárolón.</string>
+		<string name="backup_started">[BIZTONSÁGI MENTÉS ELINDÍTVA]</string>
+		<string name="backup_folder"> * Biztonsági mentés mappa: {1}</string>
+		<string name="fail_backup_folder">Nem sikerült biztonsági mentés mappát létrehozni.</string>
+		<string name="avg_backup_fs">Fájlrendszer átlagos mentési sebessége: {1} MB/mp</string>
+		<string name="avg_backup_img">Lemezképpel rendelkező meghajtók átlagos mentési sebessége: {1} MB/mp</string>
+		<string name="total_backed_size">[TELJES MENTÉSI MÉRET: {1} MB]</string>
+		<string name="backup_completed">[BIZTONSÁGI MENTÉS KÉSZ {1} MÁSODPERC ALATT]</string>
+		<string name="restore_started">[VISSZAÁLLÍTÁS ELINDÍTVA]</string>
+		<string name="restore_folder">Mappa visszaállítása: '{1}'</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} kész ({2} másodperc)]</string>
+		<string name="verifying_digest" version="3">Digest ellenőrzése</string>
+		<string name="skip_digest" version="3">Digest ellenőrzés átugrása felhasználói beállítás alapján.</string>
+		<string name="calc_restore">Visszaállítási részletek számítása...</string>
+		<string name="restore_read_only">{1} nem állítható vissza -- csak olvashatóként csatolva.</string>
+		<string name="restore_unable_locate">Nem található a(z) '{1}' partíció a visszaállításhoz.</string>
+		<string name="no_part_restore">Nincs partíció kijelölve a visszaállításhoz.</string>
+		<string name="restore_part_count">{1} partíciók visszaállítása...</string>
+		<string name="total_restore_size">Teljes visszaállítás mérete: {1}MB</string>
+		<string name="updating_system_details">Rendszer részleteinek frissítése</string>
+		<string name="restore_completed">[VISSZAÁLLÍTÁS BEFEJEZŐDÖTT {1} MÁSODPERC ALATT]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">Megnyitási hiba: '{1}' ({2})</string>
+		<string name="unable_locate_part_backup_name">Nem található partíció a biztonsági mentés neve alapján: '{1}'</string>
+		<string name="unable_find_part_path">Nem található partíció a(z) '{1}' elérési útnak</string>
+		<string name="update_part_details">Partíciós adatok frissítése...</string>
+		<string name="update_part_details_done">...kész</string>
+		<string name="wiping_dalvik">Dalvik gyorsítótár mappák törlése...</string>
+		<string name="cleaned">Törölve: {1}...</string>
+		<string name="dalvik_done">-- Dalvik gyorsítótár mappák törlése kész!</string>
+		<string name="no_andsec">Nem találhatóak android secure partíciók.</string>
+		<string name="unable_to_locate">{1} nem található.</string>
+		<string name="wiping_datamedia">Belső tároló törlése -- /data/media...</string>
+		<string name="unable_to_mount">{1} nem csatolható</string>
+		<string name="unable_to_mount_internal">Belső tároló csatolása sikertelen</string>
+		<string name="unable_to_mount_storage">Tároló csatolása sikertelen</string>
+		<string name="fail_decrypt">Nem sikerült visszafejteni az adatokat.</string>
+		<string name="no_crypto_support">Nincs titkosítási támogatás beleépítve ebbe a változatba.</string>
+		<string name="decrypt_success_dev">Adatok sikeresen visszafejtve, új blokk eszköz: '{1}'</string>
+		<string name="decrypt_success_nodev">Adatok sikeresen visszafejtve</string>
+		<string name="done">Kész.</string>
+		<string name="start_partition_sd">SD-kártya particionálása...</string>
+		<string name="partition_sd_locate">Nem található eszköz a particionálásra.</string>
+		<string name="ext_swap_size">EXT + Swap mérete nagyobb az SD-kártya méreténél.</string>
+		<string name="remove_part_table">Partíciós tábla eltávolítása...</string>
+		<string name="unable_rm_part">A partíciós tábla eltávolítása sikertelen.</string>
+		<string name="create_part">{1} partíció létrehozása...</string>
+		<string name="unable_to_create_part">{1} partíció létrehozása sikertelen.</string>
+		<string name="format_sdext_as">Sd-ext formázása, mint {1}...</string>
+		<string name="part_complete">Particionálás kész.</string>
+		<string name="unable_to_open">'{1}' megnyitása sikertelen.</string>
+		<string name="mtp_already_enabled">MTP már engedélyezve van</string>
+		<string name="mtp_fail">MTP engedélyezése sikertelen</string>
+		<string name="no_mtp">Nem tartalmaz MTP támogatást</string>
+		<string name="image_flash_start">[KÉP FLASHELÉSE ELINDULT]</string>
+		<string name="img_to_flash">Kép a flasheléshez: '{1}'</string>
+		<string name="flash_unable_locate">Nem található a(z) '{1}' partíció a flasheléshez.</string>
+		<string name="no_part_flash">Nincs kijelölve partíció a flasheléshez.</string>
+		<string name="too_many_flash">Túl sok partíció kijelölve a flasheléshez.</string>
+		<string name="invalid_flash">Érvénytelen partíció megjelölve a flasheléshez.</string>
+		<string name="flash_done">[KÉP FLASHELÉSE KÉSZ]</string>
+		<string name="wiping">{1} törlése</string>
+		<string name="repair_not_exist">{1} nem létezik! Nem javítható!</string>
+		<string name="repairing_using">{1} javítása {2} segítségével...</string>
+		<string name="unable_repair">{1} nem javítható.</string>
+		<string name="mount_data_footer">Nem sikerült csatolni az /adat partíciót és a titkosítási lábléc sem található.</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">'{1}' mappa létrehozása sikertelen ({2}).</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">'{1}' csatolása sikertlelen ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">'{1}' leválasztása sikertlelen ({2})</string>
+		<string name="cannot_resize">{1} átméretezése sikertelen.</string>
+		<string name="repair_resize">{1} javítása az átméretezés előtt.</string>
+		<string name="unable_resize">Nem lehet átméretezni: {1}.</string>
+		<string name="no_digest_found" version="2">Nem található Digest fájl ehhez: '{1}'. Kérjük, a visszaállításhoz szüntesse meg a kijelölést: \"MD5 ellenőrzés engedélyezése\".</string>
+		<string name="digest_fail_match" version="2">Digest nem egyezik ezzel: '{1}'.</string>
+		<string name="digest_matched" version="2">Digest egyezik ezzel: '{1}'.</string>
+		<string name="fail_decrypt_tar">Nem sikerült visszafejteni a(z) '{1}' tar fájlt</string>
+		<string name="format_data_msg">Lehet, hogy a recovery-be kell újraindítania ahhoz, hogy képes legyen használni az /adat partíciót.</string>
+		<string name="format_data_err">Nem sikerült formázni a titkosítás eltávolításához.</string>
+		<string name="formatting_using">{1} formázása {2} segítségével...</string>
+		<string name="unable_to_wipe">{1} törlése sikertelen.</string>
+		<string name="cannot_wipe">Nem lehet törölni a(z) {1} partíciót.</string>
+		<string name="remove_all">'{1}' alatt található összes fájl eltávolítása</string>
+		<string name="wiping_data">Adatok törlése a /data/media kivételével ...</string>
+		<string name="backing_up">{1} biztonsági mentése...</string>
+		<string name="backup_storage_warning">{1} biztonsági mentése nem tartalmaz belső tárhely fájlokat, mint pl. képek vagy letöltések.</string>
+		<string name="backing">Biztonsági mentés</string>
+		<string name="backup_size">Az '{1}' biztonsági mentés fájl mérete 0 bájt.</string>
+		<string name="datamedia_fs_restore">FIGYELMEZTETÉS: ez az /adat biztonsági mentés {1} fájlrendszerrel készült! A biztonsági mentés nem fog bootolni, hacsak nem tér vissza erre: {1}.</string>
+		<string name="restoring">{1} visszaállítása...</string>
+		<string name="restoring_hdr">Visszaállítás</string>
+		<string name="recreate_folder_err">{1} mappa újra létrehozása sikertelen.</string>
+		<string name="img_size_err">Kép mérete nagyobb, mint a céleszköz</string>
+		<string name="flashing">{1} flashelése...</string>
+		<string name="backup_folder_set">Biztonsági mentés mappája beállítva erre: '{1}'</string>
+		<string name="locate_backup_err">Nem található a(z) '{1}' biztonsági mentés</string>
+		<string name="set_restore_opt">Visszaállítási beállítások beállítása: '{1}':</string>
+		<string name="digest_check_skip" version="2">Digest ellenőrzés kihagyása bekapcsolva</string>
+		<string name="ors_encrypt_restore_err">Nem sikerült az OpenRecoveryScript futtatása egy titkosított biztonsági mentés visszaállításához.</string>
+		<string name="mounting">Csatolás</string>
+		<string name="unmounting">Leválasztás</string>
+		<string name="mounted">'{1}' csatolva</string>
+		<string name="unmounted">'{1}' leválasztva</string>
+		<string name="setting">'{1}' beállítása erre: '{2}'</string>
+		<string name="setting_empty">'{1}' beállítása üres értékre</string>
+		<string name="making_dir1">Mappa létrehozása</string>
+		<string name="making_dir2">Mappa létrehozása: '{1}'</string>
+		<string name="running_command">Parancs futtatása</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">ADB sideload funkció indítása...</string>
+		<string name="need_new_adb">Adb 1.0.32, vagy frissebbre van szüksége ahhoz, hogy a sideload funkciót használhassa ehhez a készülékhez.</string>
+		<string name="no_pwd">Nincs megadott jelszó.</string>
+		<string name="done_ors">Kész feldolgozó script fájl</string>
+		<string name="injecttwrp">TWRP injektálása a boot lemezképbe...</string>
+		<string name="zip_err">'{1}' zip fájl telepítési hiba</string>
+		<string name="installing_zip">'{1}' zip fájl telepítése</string>
+		<string name="select_backup_opt">Biztonsági mentési beállítások:</string>
+		<string name="compression_on">Tömörítés bekapcsolva</string>
+		<string name="digest_off" version="3">Digest generálás kikapcsolva</string>
+		<string name="backup_fail">Biztonsági mentés készítése sikertelen</string>
+		<string name="backup_clean">Biztonsági mentés nem sikerült. Mentési mappa tisztítása.</string>
+		<string name="running_recovery_commands">Recovery parancsok futtatása</string>
+		<string name="recovery_commands_complete">Recovery parancsok készen vannak</string>
+		<string name="running_ors">OpenRecoveryScript futtatása</string>
+		<string name="ors_complete">OpenRecoveryScript kész</string>
+		<string name="invalid_zip_format">Érvénytelen zip fájlformátum!</string>
+		<string name="check_for_digest" version="2">Digest fájl ellenőrzése...</string>
+		<string name="fail_sysmap">'{1}' fájl feltérképezése sikertelen</string>
+		<string name="verify_zip_sig">Zip aláírás ellenőrzése...</string>
+		<string name="verify_zip_fail">Zip aláírás ellenőrzése nem sikerült!</string>
+		<string name="verify_zip_done">Zip aláírás ellenőrzése sikeres.</string>
+		<string name="zip_corrupt">Zip fájl sérült!</string>
+		<string name="no_digest" version="3">Digest ellenőrzés kihagyása: nem található Digest fájl</string>
+		<string name="digest_fail" version="3">Digest nem egyezik meg</string>
+		<string name="digest_match" version="3">Digest egyezik</string>
+		<string name="pid_signal">{1} folyamat véget ért a következő jellel: {2}</string>
+		<string name="pid_error">{1} folyamat véget ért a következő HIBÁVAL: {2}</string>
+		<string name="install_dumlock">HTC Dumlock telepítése a rendszerbe...</string>
+		<string name="dumlock_restore">Eredeti boot visszaállítása...</string>
+		<string name="dumlock_reflash">Recovery újraflashelése a bootba...</string>
+		<string name="run_script">{1} script futtatása...</string>
+		<string name="rename_stock">Gyári recovery fájl a /rendszer partíción átnevezve, annak megakadályozására, hogy a gyári ROM lecserélje TWRP-t.</string>
+		<string name="split_backup">Biztonsági mentés fájljának tördelése több archívumba...</string>
+		<string name="backup_error">Hiba a biztonsági mentés létrehozásakor.</string>
+		<string name="restore_error">Hiba a visszaállítási folyamat közben.</string>
+		<string name="split_thread">{1} szálazonosító tördelése {2} archívumba</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">%llu / %llu fájl, %i%%</string>
+		<string name="size_progress">%lluMB / %lluMB, %i%%</string>
+		<string name="decrypt_cmd">Adatpartíció visszafejtésének megpróbálása parancssor segítségével.</string>
+		<string name="base_pkg_err">Nem sikerült betölteni az alap csomagokat.</string>
+		<string name="simulating">Műveletek szimulálása...</string>
+		<string name="backup_cancel">Biztonsági mentés megszakítva</string>
+		<string name="config_twrp">TWRP konfigurálása...</string>
+		<string name="config_twrp_err">TWRP konfigurálása ezzel a kernellel sikertelen.</string>
+		<string name="copy_log">Recovery napló másolva ide: {1}.</string>
+		<string name="max_queue">Elérte a Zip várólista maximumát!</string>
+		<string name="min_queue">Elérte a Zip várólista minimumát!</string>
+		<string name="screenshot_saved">Képernyőmentés elmentve ide: {1}</string>
+		<string name="screenshot_err">Képernyőmentés készítése sikertelen!</string>
+		<string name="zip_wipe_cache">Egy vagy több zip kérte a gyorsítótár törlését - Gyorsítótár törlése most.</string>
+		<string name="and_sec_wipe_err">Android secure törlése sikertelen</string>
+		<string name="dalvik_wipe_err">Dalvik törlése nem sikerült</string>
+		<string name="auto_gen">(Automatikus létrehozás)</string>
+		<string name="curr_date">(Aktuális dátum)</string>
+		<string name="backup_name_len">Biztonsági mentés neve túl hosszú.</string>
+		<string name="backup_name_invalid">'{1}' biztonsági mentés név érvénytelen karaktert tartalmaz: '{1}'</string>
+		<string name="no_real_sdcard">Ez az eszköz nem rendelkezik igazi SD-kártyával! Megszakítás!</string>
+		<string name="cancel_sideload">ADB sideload megszakítva...</string>
+		<string name="change_fs_err">Fájlrendszer módosítási hiba.</string>
+		<string name="theme_ver_err">Egyéni téma verziója nem egyezik a TWRP verziójával. Gyári téma használata.</string>
+		<string name="up_a_level">(Egy szinttel feljebb)</string>
+		<string name="install_reboot" version="2">Újraindítás %tw_sleep% mp múlva</string>
+		<string name="adbbackup_error">Hiba az ADB Backup során. Kilépés..."</string>
+		<string name="adbbackup_control_error">Nincs hozzáférés: adb control channel</string>
+		<string name="twrp_adbbu_option">--twrp opció szükséges az ADB Backup-hoz </string>
+		<string name="partition_not_found">útvonal: {1} nem található a partíciós listában</string>
+		<string name="copy_kernel_log">Kernel log másolva: {1}</string>
+		<string name="include_kernel_log">Emellett Kernel Log másolása</string>
+		<string name="unable_set_boot_slot">Hiba a boot slot váltása során erre: {1}</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/id.xml b/gui/theme/common/languages/id.xml
new file mode 100755
index 0000000..5ace582
--- /dev/null
+++ b/gui/theme/common/languages/id.xml
@@ -0,0 +1,762 @@
+<?xml version="1.0"?>
+
+<language>
+	<display>Indonesia</display>
+
+	<resources>
+		<!-- Penggantian font - hanya ubah ini jika bahasa Anda memerlukan karakter khusus -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<!-- Nama tampilan partisi -->
+		<string name="system">Sistem</string>
+		<string name="system_image">Image Sistem</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Image Vendor</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="data_backup">Data (excl. Penyimpanan)</string>
+		<string name="sdcard">Kartu SD</string>
+		<string name="internal">Penyimpanan Internal</string>
+		<string name="microsd">Micro SD</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Keamanan Android</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<!-- Ini adalah partisi yang jarang digunakan pada Micro SD untuk sistem app2sd yang sangat lama -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Data teradopsi</string>
+		<string name="adopted_storage">Penyimpanan teradopsi</string>
+		<string name="autostorage">Penyimpanan</string>
+
+		<!-- String XML GUI -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% &#xB0;C</string>
+		<string name="battery_pct">Baterai: %tw_battery%</string>
+		<string name="sort_by_name">Urut Sesuai Nama</string>
+		<string name="sort_by_date">Urut Sesuai Tanggal</string>
+		<string name="sort_by_size">Urut Sesuai Ukuran</string>
+		<string name="sort_by_name_only">Nama</string>
+		<string name="sort_by_date_only">Tanggal</string>
+		<string name="sort_by_size_only">Ukuran</string>
+		<string name="tab_general">UMUM</string>
+		<string name="tab_options">PILIHAN</string>
+		<string name="tab_backup">CADANGAN</string>
+		<string name="tab_time_zone">ZONA WAKTU</string>
+		<string name="tab_screen">TAMPILAN</string>
+		<string name="tab_vibration">GETAR</string>
+		<string name="tab_language">BAHASA</string>
+
+		<string name="install_btn">Instalasi</string>
+		<string name="wipe_btn">Format</string>
+		<string name="backup_btn">Cadangkan</string>
+		<string name="restore_btn">Pulihkan</string>
+		<string name="mount_btn">Mount</string>
+		<string name="settings_btn">Pengaturan</string>
+		<string name="advanced_btn">Lainnya</string>
+		<string name="reboot_btn">Reboot</string>
+		<string name="files_btn">File</string>
+		<string name="copy_log_btn">Salin log</string>
+		<string name="select_type_hdr">Pilih jenis</string>
+		<string name="install_zip_hdr">Instal zip</string>
+		<string name="install_zip_btn">Instal zip</string>
+		<string name="install_image_hdr">Instal Image</string>
+		<string name="install_image_btn">Instal Image</string>
+		<string name="install_select_file_hdr">Pilih File</string>
+		<string name="file_selector_folders_hdr">Folder</string>
+		<string name="select_file_from_storage">Pilih dari %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Instal</string>
+		<string name="select_storage_hdr">Pilih Penyimpanan</string>
+		<string name="select_storage_btn">Pilih Penyimpanan</string>
+		<string name="queue_hdr">Antrian</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% dari maks 10 antrian file</string>
+		<string name="zip_queue_count_s">File %tw_zip_queue_count% dari 10:</string>
+		<string name="zip_warn1">Jika program yang Anda instal tidak kompatibel</string>
+		<string name="zip_warn2">dapat menyebabkan perangkat tidak dapat digunakan.</string>
+		<string name="zip_back_cancel">Tekan tombol kembali untuk membatalkan.</string>
+		<string name="zip_back_clear">Tekan tombol kembali untuk hapus antrian.</string>
+		<string name="folder">Folder:</string>
+		<string name="file">File:</string>
+		<string name="zip_sig_chk">Verifikasi tanda tangan zip</string>
+		<string name="inject_twrp_chk">Suntikan TWRP setelah menginstal</string>
+		<string name="install_reboot_chk">Reboot setelah instalasi selesai</string>
+		<string name="options_hdr">Pilihan</string>
+		<string name="confirm_flash_hdr">Konfirmasi Flash</string>
+		<string name="zip_queue">Antrian:</string>
+		<string name="options">Pilihan:</string>
+		<string name="swipe_confirm">   Konfirmasi</string>
+		<string name="zip_add_btn">Tambah zip lainnya</string>
+		<string name="zip_clear_btn">Hapus antrian zip</string>
+		<string name="install_zip_count_hdr">Instal zip %tw_zip_index% dari %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Menginstal Zip: %tw_file%</string>
+		<string name="failed">Gagal.</string>
+		<string name="successful">Berhasil.</string>
+		<string name="install_failed">Instalasi gagal.</string>
+		<string name="install_successful">Instalasi berhasil.</string>
+		<string name="wipe_cache_dalvik_btn">Hapus Cache/Dalvik</string>
+		<string name="wipe_dalvik_btn">Hapus Dalvik</string>
+		<string name="reboot_system_btn">Reboot Sistem</string>
+		<string name="install_sel_target">Pilih partisi tujuan</string>
+		<string name="flash_image_select">Pilih partisi untuk flash image:</string>
+		<string name="target_partition">Partisi tujuan:</string>
+		<string name="flashing_image">Memflash image...</string>
+		<string name="image_flashed">Flash image selesai.</string>
+		<string name="wipe_cache_dalvik_confirm">Hapus Cache &amp; Dalvik?</string>
+		<string name="wipe_dalvik_confirm">Hapus Dalvik?</string>
+		<string name="wiping_cache_dalvik">Menghapus Cache &amp; Dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Cache &amp; Dalvik berhasil dihapus.</string>
+		<string name="wipe_dalvik_complete">Dalvik berhasil dihapus.</string>
+		<string name="swipe_wipe">Geser untuk Hapus</string>
+		<string name="swipe_wipe_s">   Hapus</string>
+		<string name="no_os1">Tidak ada OS terinstal! Apakah Anda</string>
+		<string name="no_osrb">yakin untuk reboot?</string>
+		<string name="no_ospo">yakin untuk matikan perangkat?</string>
+		<string name="rebooting">Mereboot...</string>
+		<string name="swipe_reboot">Geser untuk Reboot</string>
+		<string name="swipe_reboot_s">   Reboot</string>
+		<string name="reboot_install_app_hdr">Instal aplikasi TWRP</string>
+		<string name="reboot_install_app1">Apakah Anda ingin memasang Aplikasi TWRP resmi?</string>
+		<string name="reboot_install_app2">Aplikasi dapat memeriksa versi TWRP terbaru.</string>
+		<string name="reboot_install_app_prompt_install">Instal aplikasi TWRP jika belum tersedia</string>
+		<string name="reboot_install_app_system">Instal sebagai aplikasi sistem</string>
+		<string name="reboot_installing_app">Menginstal aplikasi...</string>
+		<string name="swipe_to_install_app">Geser untuk Instal</string>
+		<string name="uninstall_twrp_system_app">Hapus aplikasi TWRP dari sistem</string>
+		<string name="uninstall_twrp_system_app_confirm">Hapus aplikasi TWRP dari sistem?</string>
+		<string name="uninstalling_twrp_system_app">Menghapus aplikasi TWRP dari sistem ...</string>
+		<string name="uninstall_twrp_system_app_complete">Aplikasi TWRP berhasil dihapus</string>
+		<string name="swipe_flash">Geser untuk Flash</string>
+		<string name="confirm_action">Konfirmasi Tindakan</string>
+		<string name="back_cancel">Tekan tombol kembali untuk membatalkan.</string>
+		<string name="cancel_btn">Batal</string>
+		<string name="wipe_hdr">Hapus</string>
+		<string name="factory_reset_hdr">Reset Pabrik</string>
+		<string name="factory_reset_btn">Reset Pabrik</string>
+		<string name="factory_reset1">Hapus Data, Cache, dan Dalvik</string>
+		<string name="factory_reset2">(tidak termasuk penyimpanan internal)</string>
+		<string name="factory_reset3">Ini adalah satu-satunya fitur format</string>
+		<string name="factory_reset4">yang paling Anda perlukan.</string>
+		<string name="factory_reset5">(tidak termasuk pengguna/layar kunci)</string>
+		<string name="factory_resetting">Mereset Pabrik...</string>
+		<string name="advanced_wipe_hdr">Hapus Lainnya</string>
+		<string name="advanced_wipe_btn">Hapus Lainnya</string>
+		<string name="wipe_enc_confirm">Hapus enkripsi dari data?</string>
+		<string name="formatting_data">Memformat Data...</string>
+		<string name="swipe_format_data">Geser untuk Format</string>
+		<string name="swipe_format_data_s">   Format Data</string>
+		<string name="factory_reset_complete">Reset pabrik selesai.</string>
+		<string name="sel_part_hdr">Pilih partisi</string>
+		<string name="wipe_sel_confirm">Hapus partisi terpilih(s)?</string>
+		<string name="wiping_part">Menghapus partisi(s)...</string>
+		<string name="wipe_complete">Berhasil dihapus.</string>
+		<string name="sel_part_wipe">Hapus partisi:</string>
+		<string name="invalid_part_sel">Partisi yang dipilih tidak valid</string>
+		<string name="format_data_hdr">Format Data</string>
+		<string name="format_data_btn">Format Data</string>
+		<string name="format_data_ptr1">Format Data akan menghapus semua aplikasi,</string>
+		<string name="format_data_ptr2">cadangan, gambar, video, media, dan</string>
+		<string name="format_data_ptr3">menghapus enkripsi pada penyimpanan internal Anda.</string>
+		<string name="format_data_adopted">Tidak termasuk penyimpanan teradopsi</string>
+		<string name="format_data_lcp1">Format Data akan menghapus semua aplikasi, cadangan, gambar, video, media, dan</string>
+		<string name="format_data_lcp2">menghapus enkripsi pada penyimpanan internal Anda.</string>
+		<string name="format_data_wtc1">Format Data akan menghapus semua aplikasi,</string>
+		<string name="format_data_wtc2">cadangan dan media. Ini tidak dapat dibatalkan.</string>
+		<string name="format_data_undo">Ini tidak dapat dibatalkan.</string>
+		<string name="format_data_complete">Format data berhasil.</string>
+		<!-- Catatan Terjemahan: kata "yes" harus tetap Inggris! -->
+		<string name="yes_continue">Ketik "yes" untuk melanjutkan.</string>
+		<string name="part_opt_hdr">Pilihan partisi untuk: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Pilih Tindakan</string>
+		<string name="file_sys_opt">Pilihan File Sistem</string>
+		<string name="partition">Partisi: %tw_partition_name%</string>
+		<string name="part_mount_point">Titik Mount: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">File sistem: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Ditampilkan: Ya</string>
+		<string name="part_present_no">Ditampilkan: Tidak</string>
+		<string name="part_removable_yes">Dapat Dilepas: Yes</string>
+		<string name="part_removable_no">Dapat Dilepas: Tidak</string>
+		<string name="part_size">Ukuran: %tw_partition_size%MB</string>
+		<string name="part_used">Terpakai: %tw_partition_used%MB</string>
+		<string name="part_free">Tersedia: %tw_partition_free%MB</string>
+		<string name="part_backup_size">Ukuran cadangan: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">Ubah Ukuran File</string>
+		<string name="resize_btn_s">Ubah Ukuran</string>
+		<string name="resize_confirm">Ubah ukuran %tw_partition_name%?</string>
+		<string name="resizing">Mengubah ukuran...</string>
+		<string name="resize_complete">Ubah ukuran selesai.</string>
+		<string name="swipe_resize">Geser untuk Ubah</string>
+		<string name="swipe_resize_s">   Ubah Ukuran</string>
+		<string name="repair_btn">Perbaiki File Sistem</string>
+		<string name="repair_btn_s">Perbaiki</string>
+		<string name="repair_confirm">Perbaiki %tw_partition_name%?</string>
+		<string name="repairing">Memperbaiki...</string>
+		<string name="repair_complete">Berhasil Memperbaiki</string>
+		<string name="swipe_repair">Geser untuk Perbaiki</string>
+		<string name="swipe_repair_s">   Perbaiki</string>
+		<string name="change_fs_btn">Ubah File Sistem</string>
+		<string name="change_fs_btn_s">Ubah</string>
+		<string name="change_fs_for_hdr">Ubah file sistem untuk: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Partisi: %tw_partition_name% &gt; Pilih file sistem</string>
+		<string name="change_fs_warn1">Beberapa ROM atau Kernel mungkin tidak</string>
+		<string name="change_fs_warn2">mendukung file sistem. Lakukan dengan hati-hati!</string>
+		<string name="change_fs_confirm">Ubah %tw_partition_name%?</string>
+		<string name="formatting">Memformat...</string>
+		<string name="format_complete">Format berhasil.</string>
+		<string name="swipe_change_fs">Geser untuk Mengubah</string>
+		<string name="swipe_change_s">   Ubah</string>
+		<string name="back_btn">Kembali</string>
+		<string name="wipe_enc_btn">Hapus enkripsi</string>
+		<string name="swipe_factory_reset">Geser untuk Reset Pabrik</string>
+		<string name="repair_change_btn">Perbaiki atau ubah file sistem</string>
+		<string name="storage_hdr">Penyimpanan: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Cadangan</string>
+		<string name="backup_confirm_hdr">Konfirmasi pencadangan</string>
+		<string name="encryption_tab">ENKRIPSI</string>
+		<string name="encryption">Enkripsi:</string>
+		<string name="name">Nama:</string>
+		<string name="sel_part_backup">Pilih partisi untuk dicadangkan:</string>
+		<string name="storage">Penyimpanan:</string>
+		<string name="enc_disabled">nonaktif - atur sandi untuk mengaktifkan</string>
+		<string name="enc_enabled">aktif</string>
+		<string name="enable_backup_comp_chk">Aktifkan kompresi</string>
+		<string name="skip_digest_backup_chk" version="2">Lewati pembuatan digest selama mencadangkan</string>
+		<string name="disable_backup_space_chk" version="2">Matikan cek sisa ruang sebelum mencadangkan</string>
+		<string name="skip_digest_zip_chk">Lewati pemeriksa digest sebelum menginstal zip</string>
+		<string name="current_boot_slot">Slot Saat Ini: %tw_active_slot%</string>
+		<string name="boot_slot_a">Slot A</string>
+		<string name="boot_slot_b">Slot B</string>
+		<string name="changing_boot_slot">Mengubah slot boot</string>
+		<string name="changing_boot_slot_complete">Mengubah slot boot selesai.</string>
+		<string name="refresh_sizes_btn">Refresh Ukuran</string>
+		<string name="swipe_backup">Geser untuk Cadangkan</string>
+		<string name="append_date_btn">Lampirkan Tanggal</string>
+		<string name="backup_name_exists">Nama cadangan sudah ada!</string>
+		<string name="encrypt_backup">Enkripsi cadangan Anda?</string>
+		<string name="enter_pass">Masukkan Sandi:</string>
+		<string name="enter_pass2">Masukkan Sandi Lagi:</string>
+		<string name="pass_not_match">Sandi tidak cocok!</string>
+		<string name="partitions">Partisi:</string>
+		<string name="disabled">Nonaktif</string>
+		<string name="enabled">Aktif</string>
+		<string name="backup_name_hdr">Atur nama cadangan</string>
+		<string name="progress">Memproses:</string>
+		<string name="backup_complete">Pencadangan berhasil.</string>
+		<string name="restore_hdr">Pulihkan</string>
+		<string name="sel_backup_hdr">Pulihkan Cadangan</string>
+		<string name="restore_sel_store_hdr">Pilih dari %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Pilih paket untuk dipulihkan:</string>
+		<string name="restore_enc_backup_hdr">Cadangan terenkripsi</string>
+		<string name="restore_dec_fail">Sandi salah, coba lagi!</string>
+		<string name="del_backup_btn">Hapus Cadangan</string>
+		<string name="del_backup_confirm">Hapus cadangan?</string>
+		<string name="del_backup_confirm2">Ini tidak dapat dibatalkan!</string>
+		<string name="deleting_backup">Menghapus cadangan...</string>
+		<string name="backup_deleted">Cadangan berhasil dihapus.</string>
+		<string name="swipe_delete">Geser untuk Hapus</string>
+		<string name="swipe_delete_s">   Hapus</string>
+		<string name="restore_try_decrypt">Cadangan terenkripsi - Mencoba mendeskripsikan</string>
+		<string name="restore_try_decrypt_s">Mencoba mendeskripsikan</string>
+		<string name="restore_backup_date">Cadangan dibuat pada %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Pilih partisi untuk dipulihkan:</string>
+		<string name="restore_enable_digest_chk" version="2">Aktifkan verifikasi digest pada file cadangan</string>
+		<string name="restore_complete">Pemulihan selesai.</string>
+		<string name="swipe_restore">Geser untuk Memulihkan</string>
+		<string name="swipe_restore_s">   Pulihkan</string>
+		<string name="rename_backup_hdr">Ubah nama cadangan</string>
+		<string name="rename_backup_confirm">Ubah nama cadangan?</string>
+		<string name="rename_backup_confirm2">Ini tidak dapat dibatalkan!</string>
+		<string name="renaming_backup">Mengubah nama cadangan...</string>
+		<string name="rename_backup_complete">Nama cadangan berhasil diubah.</string>
+		<string name="swipe_to_rename">Geser untuk Ubah Nama</string>
+		<string name="swipe_rename">   Ubah Nama</string>
+		<string name="confirm_hdr">Konfirmasi</string>
+		<string name="mount_hdr">Mount</string>
+		<string name="mount_sel_part">Pilih partisi untuk mount:</string>
+		<string name="mount_sys_ro_chk">Mount partisi sistem baca-saja</string>
+		<string name="mount_sys_ro_s_chk">Mount sistem RO</string>
+		<string name="decrypt_data_btn">Deskripsi data</string>
+		<string name="disable_mtp_btn">Nonaktifkan MTP</string>
+		<string name="enable_mtp_btn">Aktifkan MTP</string>
+		<string name="mount_usb_storage_btn">Mount USB</string>
+		<string name="usb_storage_hdr">Penyimpanan USB</string>
+		<string name="usb_stor_mnt1">Penyimpanan USB terpasang</string>
+		<string name="usb_stor_mnt2">Hati-hati saat melepaskan</string>
+		<string name="usb_stor_mnt3">perangkat dari komputer Anda!</string>
+		<string name="unmount_btn">Dilepas</string>
+		<string name="rb_system_btn">Sistem</string>
+		<string name="rb_poweroff_btn">Matikan Perangkat</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Download</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Mematikan...</string>
+		<string name="swipe_power_off">Geser untuk Matikan</string>
+		<string name="swipe_power_off_s">Matikan Perangkat</string>
+		<string name="sys_ro_hdr">Partisi sistem tidak dimodifikasi</string>
+		<string name="sys_ro_keep">Tetapkan sistem Baca-Saja?</string>
+		<string name="sys_rop1">TWRP dapat membiarkan partisi sistem tidak dimodifikasi</string>
+		<string name="sys_rop2">untuk memudahkan Anda melakukan pembaruan resmi.</string>
+		<string name="sys_rop3">TWRP tidak dapat mencegah stok ROM mengubah TWRP dan</string>
+		<string name="sys_rop4">tidak akan menawarkan untuk melakukan root pada perangkat Anda.</string>
+		<string name="sys_rop5">Menginstal zip atau menjalankan operasi adb</string>
+		<string name="sys_rop6">akan selalu mengubah partisi sistem.</string>
+		<string name="sys_rol1">TWRP dapat membiarkan partisi sistem Anda tidak dimodifikasi untuk memudahkan Anda melakukan pembaruan resmi.</string>
+		<string name="sys_rol2">TWRP tidak dapat mencegah stok ROM mengubah TWRP dan tidak akan menawarkan untuk melakukan root pada perangkat Anda.</string>
+		<string name="sys_rol3">Menginstal zip atau menjalankan operasi adb akan selalu mengubah partisi sistem.</string>
+		<string name="sys_ro_never_show_chk">Jangan pernah tampilkan ini lagi saat boot</string>
+		<string name="sys_ro_keep_ro_btn">Tetap Baca Saja</string>
+		<string name="swipe_allow_mod">Geser untuk Izinkan</string>
+		<string name="swipe_allow_mod_s">Izinkan modifikasi</string>
+		<string name="settings_hdr">Pengaturan</string>
+		<string name="settings_gen_hdr">Umum</string>
+		<string name="settings_gen_s_hdr">Umum</string>
+		<string name="settings_gen_btn">Umum</string>
+		<string name="use_rmrf_chk">Gunakan rm -rf sebagai ganti pemformatan</string>
+		<string name="use24clock_chk">Gunakan format waktu 24 jam</string>
+		<string name="rev_navbar_chk">Tukar tata letak tombol navigasi</string>
+		<string name="simact_chk">Simulasikan tindakan untuk pengujian tema</string>
+		<string name="simfail_chk">Simulasikan kegagalan untuk tindakan</string>
+		<string name="ctr_navbar_rdo">Tombol navigasi tengah</string>
+		<string name="lft_navbar_rdo">Tombol navigasi rata kiri</string>
+		<string name="rht_navbar_rdo">Tombol navigasi rata kanan</string>
+		<string name="restore_defaults_btn">Reset</string>
+		<string name="settings_tz_btn">Zona Waktu</string>
+		<string name="settings_screen_btn">Tampilan</string>
+		<string name="settings_screen_bright_btn">Kecerahan</string>
+		<string name="vibration_disabled">Getar di nonaktifkan pada perangkat ini</string>
+		<string name="settings_vibration_btn">Getar</string>
+		<string name="settings_language_btn">Bahasa</string>
+		<string name="time_zone_hdr">Zona Waktu</string>
+		<string name="sel_tz_list">Pilih zona waktu:</string>
+		<!-- Catatan terjemahan: jangan ragu untuk mengubah lokasi yang terdaftar atau menghapus lokasi seluruhnya dan sebut saja UTC -6 -->
+		<string name="utcm11">(UTC -11) Samoa, Midway Island</string>
+		<string name="utcm10">(UTC -10) Hawaii</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm8">(UTC -8) Waktu Pasifik</string>
+		<string name="utcm7">(UTC -7) Waktu Pegunungan</string>
+		<string name="utcm6">(UTC -6) Waktu Tengah</string>
+		<string name="utcm5">(UTC -5) Waktu Timur</string>
+		<string name="utcm4">(UTC -4) Waktu Atlantik</string>
+		<string name="utcm3">(UTC -3) Brazil, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Atlantik-Tengah</string>
+		<string name="utcm1">(UTC -1) Azores, Cape Verde</string>
+		<string name="utc0">(UTC  0) London, Dublin, Lisbon</string>
+		<string name="utcp1">(UTC +1) Berlin, Brussels, Paris</string>
+		<string name="utcp2">(UTC +2) Athens, Istanbul, South Africa</string>
+		<string name="utcp3">(UTC +3) Moscow, Baghdad</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC +5) Yekaterinburg, Islamabad</string>
+		<string name="utcp6">(UTC +6) Almaty, Dhaka, Colombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp8">(UTC +8) Beijing, Singapore, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokyo, Seoul, Yakutsk</string>
+		<string name="utcp10">(UTC +10) Australia Timur, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Solomon Islands</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fiji</string>
+		<string name="sel_tz_offset">Pilih Penyeimbang (biasanya 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Tidak ada</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Gunakan waktu musim panas (DST)</string>
+		<string name="curr_tz">Zona waktu saat ini: %tw_time_zone%</string>
+		<string name="curr_tz_s">Zona waktu saat Ini:</string>
+		<string name="set_tz_btn">Atur zona waktu</string>
+		<string name="settings_screen_hdr">Layar</string>
+		<string name="settings_screen_timeout_hdr">Waktu Redup Layar</string>
+		<string name="enable_timeout_chk">Aktifkan Waktu Redup Layar</string>
+		<string name="screen_to_slider">Layar redup dalam (detik):</string>
+		<string name="screen_to_slider_s">Layar redup dalam detik (0=disabled): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Pengaturan waktu redup layar tidak tersedia</string>
+		<string name="screen_bright_slider">Kecerahan: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Pengaturan kecerahan tidak tersedia</string>
+		<string name="vibration_hdr">Getar</string>
+		<string name="button_vibration_hdr">Getaran tombol</string>
+		<string name="kb_vibration_hdr">Getaran papan ketik</string>
+		<string name="act_vibration_hdr">Getaran ketika disentuh</string>
+		<string name="button_vibration">Getaran tombol:</string>
+		<string name="kb_vibration">Getaran papan ketik:</string>
+		<string name="act_vibration">Getaran ketika disentuh:</string>
+		<string name="select_language">Pilih Bahasa:</string>
+		<string name="sel_lang_btn">Pilih Bahasa</string>
+		<string name="set_language_btn">Atur Bahasa</string>
+		<string name="advanced_hdr">Lainnya</string>
+		<string name="copy_log_confirm">Salin log ke kartu SD?</string>
+		<string name="copying_log" version="2">Menyalin log ke kartu SD...</string>
+		<string name="copy_log_complete" version="2">Log Berhasil disalin.</string>
+		<string name="fix_context_btn">Perbaiki Konteks</string>
+		<string name="part_sd_btn">Partisi Kartu SD</string>
+		<string name="part_sd_s_btn">Kartu SD</string>
+		<string name="file_manager_btn">File Manager</string>
+		<string name="language_hdr">Bahasa</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="reload_theme_btn">Reset Tema</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Suntik TWRP</string>
+		<string name="inject_twrp_confirm">Suntik ulang TWRP?</string>
+		<string name="injecting_twrp">Menyuntik ulang TWRP...</string>
+		<string name="inject_twrp_complete">Menyuntik TWRP Selesai</string>
+		<string name="swipe_to_confirm">Geser untuk Konfirmasi</string>
+		<string name="part_sd_hdr">Partisi Kartu SD</string>
+		<string name="invalid_partsd_sel">Anda harus memilih perangkat yang dapat dilepas</string>
+		<string name="part_sd_lose">Anda akan kehilangan semua file pada Kartu SD!</string>
+		<string name="part_sd_undo">Tindakan ini tidak dapat dibatalkan!</string>
+		<string name="part_sd_ext_sz">Ukuran EXT:</string>
+		<string name="part_sd_swap_sz">Ukuran Swap:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">File Sistem:</string>
+		<string name="swipe_part_sd">Geser untuk Mempartisi</string>
+		<string name="swipe_part_sd_s">Partisi</string>
+		<string name="partitioning_sd">Mempartisi kartu SD...</string>
+		<string name="partitioning_sd2">Ini membutuhkan waktu beberapa menit.</string>
+		<string name="part_sd_complete">Mempartisi selesai.</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Pulihkan boot original</string>
+		<string name="dumlock_restore_confirm">Pulihkan image boot original?</string>
+		<string name="dumlock_restoring">Memulihkan boot original...</string>
+		<string name="dumlock_restore_complete">Memulihkan boot original selesai</string>
+		<string name="dumlock_reflash_btn">Flash ulang recovery</string>
+		<string name="dumlock_reflash_confirm">Flash ulang recovery ke boot?</string>
+		<string name="dumlock_reflashing">Memflash recovery ke boot...</string>
+		<string name="dumlock_reflash_complete">Flash recovery ke boot selesai.</string>
+		<string name="dumlock_install_btn">Instal HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Instal file HTC dumlock ke ROM?</string>
+		<string name="dumlock_installing">Menginstal HTC Dumlock...</string>
+		<string name="dumlock_install_complete">Instal HTC Dumlock selesai.</string>
+		<string name="swipe_to_unlock">Geser untuk Membuka</string>
+		<string name="swipe_unlock">   Buka</string>
+		<string name="fm_hdr">File Manager</string>
+		<string name="fm_sel_file">Pilih File atau Folder</string>
+		<string name="fm_type_folder">Folder</string>
+		<string name="fm_type_file">File</string>
+		<string name="fm_choose_act">Pilih Tindakan</string>
+		<string name="fm_selected">%tw_fm_type% dipilih:</string>
+		<string name="fm_copy_btn">Salin</string>
+		<string name="fm_copy_file_btn">Salin File</string>
+		<string name="fm_copy_folder_btn">Salin Folder</string>
+		<string name="fm_copying">Salin</string>
+		<string name="fm_move_btn">Pindah</string>
+		<string name="fm_moving">Pindahkan</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Hapus</string>
+		<string name="fm_deleting">Menghapus</string>
+		<string name="fm_rename_btn">Ubah Nama</string>
+		<string name="fm_rename_file_btn">Ubah Nama File</string>
+		<string name="fm_rename_folder_btn">Ubah Nama Folder</string>
+		<string name="fm_renaming">Mengubah Nama</string>
+		<string name="fm_sel_dest">Pilih Folder Tujuan</string>
+		<string name="fm_sel_curr_folder">Pilih folder tujuan</string>
+		<string name="fm_rename_hdr">Ubah Nama</string>
+		<string name="fm_set_perms_hdr">Atur perizinan</string>
+		<string name="fm_perms">Perizinan:</string>
+		<string name="fm_complete">Operasi Berhasil</string>
+		<string name="fm_open_terminal_btn">Buka terminal disini</string>
+		<string name="fm_edit_file">Ubah File</string>
+		<string name="decrypt_data_hdr">Deskripsi Data</string>
+		<string name="decrypt_data_enter_pass">Masukkan sandi.</string>
+		<string name="decrypt_data_failed">Sandi salah, coba lagi!</string>
+		<string name="decrypt_data_failed_pattern">Pola salah, coba lagi!</string>
+		<string name="decrypt_data_enter_pattern">Masukkan pola.</string>
+		<string name="decrypt_data_trying">Mencoba mendeskripsikan</string>
+		<string name="decrypt_data_vold_os_missing">File yang hilang diperlukan untuk deskripsi vold: {1}</string>
+		<string name="term_hdr">Perintah terminal</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">MATIKAN</string>
+		<string name="term_sel_folder_hdr">Telusuri ke folder awal</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Hapus Dalvik Cache</string>
+		<string name="sideload_wipe_cache_chk">Hapus Cache</string>
+		<string name="swipe_to_sideload">Geser untuk Mulai Sideload</string>
+		<string name="swipe_sideload">   Mulai</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Terpakai: adb sideload filename.zip</string>
+		<string name="sideload_complete">ADB Sideload Selesai</string>
+		<string name="fix_contexts_hdr">Perbaiki Konteks</string>
+		<string name="fix_contexts_note1">Peringatan: Memperbaiki konteks sangat jarang diperlukan.</string>
+		<string name="fix_contexts_note2">Memperbaiki konteks SELinux dapat menyebabkannya</string>
+		<string name="fix_contexts_note3">perangkat tidak dapat boot dengan benar.</string>
+		<string name="swipe_to_fix_contexts">Geser untuk Perbaiki</string>
+		<string name="swipe_fix_contexts">   Perbaiki Konteks</string>
+		<string name="fixing_contexts">Memperbaiki Konteks...</string>
+		<string name="fix_contexts_complete">Perbaiki konteks selesai.</string>
+		<string name="reboot_hdr">Reboot</string>
+		<string name="install_cancel">Jangan Instal</string>
+		<string name="sel_storage_list">Pilih Penyimpanan</string>
+		<string name="ok_btn">OK</string>
+		<string name="install_twrp_ramdisk">Instal recovery ramdisk</string>
+		<string name="install_kernel">Instal kernel</string>
+		<string name="repack_kernel_confirm_hdr">Instal kernel</string>
+		<string name="repack_ramdisk_confirm_hdr">Instal recovery</string>
+		<string name="repack_kernel_confirm">Instal kernel?</string>
+		<string name="repack_ramdisk_confirm">Instal recovery?</string>
+		<string name="repack_backup_first">Buat cadangan Image yang ada terlebih dahulu</string>
+		<string name="repack">Kemas ulang</string>
+		<string name="swipe_to_install">Geser untuk Instal</string>
+		<string name="installing">Menginstal...</string>
+		<string name="install_complete">Instal selesai.</string>
+		<string name="unpack_error">Kesalahan membongkar image.</string>
+		<string name="repack_error">Kesalahan mengemas image.</string>
+		<string name="unpacking_image">Membongkar {1}...</string>
+		<string name="repacking_image">Mengemas {1}...</string>
+		<string name="repack_image_hdr">Pilih Image</string>
+		<string name="fix_recovery_loop">Perbaiki recovery bootloop</string>
+		<string name="fix_recovery_loop_confirm">Perbaiki recovery bootloop?</string>
+		<string name="fixing_recovery_loop">Memperbaiki recovery bootloop...</string>
+		<string name="fix_recovery_loop_complete">Perbaiki recovery bootloop selesai.</string>
+		<string name="fixing_recovery_loop_patch">Menambal kernel ...</string>
+		<string name="fix_recovery_loop_patch_error">Kesalahan menambal kernel.</string>
+		<string name="decrypt_users">Deskripsi pengguna</string>
+		<string name="decrypt_users_selection">Pilih ID pengguna untuk mendeskripsi</string>
+		<string name="select_user">Pilih Pengguna</string>
+		<string name="backup_storage_undecrypt_warning">Cadangan tidak akan menyertakan beberapa file pengguna {1}, karena tidak di deskripsikan.</string>
+		<string name="decrypting_user_fbe">Mencoba mendekripsi FBE untuk pengguna {1}...</string>
+		<string name="decrypt_user_success_fbe">Pengguna {1} berhasil dideskripsi.</string>
+		<string name="decrypt_user_fail_fbe">Gagal mendekripsi pengguna {1}</string>
+		<string name="decrypt_data_enter_pass_fbe">Masukkan sandi pengguna [%tw_crypto_user_id%]</string>
+		<string name="decrypt_data_enter_pattern_fbe">Masukkan sandi pengguna [%tw_crypto_user_id%]</string>
+		<string name="multiuser_warning1">Tidak semua pengguna didekripsi!</string>
+		<string name="multiuser_warning2">Operasi pencadangan /recovery mungkin gagal!</string>
+		<string name="multiuser_warning_accept">Lanjutkan saja</string>
+		<string name="multiuser_warning_hdr">Peringatan multipengguna</string>
+
+		<!-- Berbagai pesan konsol - ini terdiri dari pesan yang ditampilkan pengguna, biasanya kesalahan -->
+		<string name="no_kernel_selinux">Kernel tidak memiliki dukungan untuk membaca konteks SELinux.</string>
+		<string name="full_selinux">Dukungan penuh SELinux aktif.</string>
+		<string name="no_selinux">Tidak ada dukungan SELinux (tidak ada libselinux).</string>
+		<string name="mtp_enabled">MTP Diaktifkan</string>
+		<string name="mtp_crash">MTP Macet, tidak dimulai saat boot.</string>
+		<string name="decrypt_success">Berhasil didekripsi dengan sandi default.</string>
+		<string name="unable_to_decrypt">Tidak dapat mendekripsi dengan sandi default. Anda mungkin perlu melakukan Format Data.</string>
+		<string name="generating_digest1" version="2">Membuat digest</string>
+		<!-- Pesan yang ditampilkan selama pencadangan jika kita membuat Digest, idealnya, biarkan awalan "*" untuk membantu menyelaraskan dan memisahkan teks ini dari teks konsol lain -->
+		<string name="generating_digest2" version="2"> * Membuat digest...</string>
+		<string name="digest_created" version="2"> * Digest dibuat.</string>
+		<string name="digest_error" version="2"> * Kesalahan digest!</string>
+		<string name="digest_compute_error" version="2"> * Kesalahan kalkulasi digest.</string>
+		<string name="current_date">(Waktu saat ini)</string>
+		<string name="auto_generate">(Buat otomatis)</string>
+		<string name="unable_to_locate_partition">Tidak dapat menemukan partisi '{1}' untuk kalkulasi cadangan.</string>
+		<string name="no_partition_selected">Tidak ada partisi yang dipilih untuk cadangan.</string>
+		<string name="total_partitions_backup"> * Jumlah total partisi yang akan dicadangkan: {1}</string>
+		<string name="total_backup_size"> * Ukuran total semua data: {1}MB</string>
+		<string name="available_space"> * Ruang tersedia: {1}MB</string>
+		<string name="unable_locate_storage">Tidak dapat menemukan perangkat penyimpanan.</string>
+		<string name="no_space">Ruang penyimpanan tidak cukup.</string>
+		<string name="backup_started">[PENCADANGAN DIMULAI]</string>
+		<string name="backup_folder"> * Folder cadangan: {1}</string>
+		<string name="fail_backup_folder">Gagal membuat folder cadangan.</string>
+		<string name="avg_backup_fs">Kecepatan rata-rata mencadangkan file: {1} MB/det</string>
+		<string name="avg_backup_img">Kecepatan rata-rata mencadangkan Image: {1} MB/det</string>
+		<string name="total_backed_size">[{1} MB TOTAL DICADANGKAN]</string>
+		<string name="backup_completed">[MENCADANGKAN SELESAI DALAM {1} DETIK]</string>
+		<string name="restore_started">[PEMULIHAN DIMULAI]</string>
+		<string name="restore_folder">Pulihkan folder: '{1}'</string>
+		<!-- {1} adalah nama tampilan partisi dan {2} adalah nilai detik -->
+		<string name="restore_part_done">[{1} berhasil ({2} detik)]</string>
+		<string name="verifying_digest" version="2">Verifikasi digest</string>
+		<string name="skip_digest" version="2">Melewati pemeriksaan digest berdasarkan pengaturan.</string>
+		<string name="calc_restore">Menghitung rincian pemulihan ...</string>
+		<string name="restore_read_only">Tidak dapat memulihkan {1} -- diatur hanya Baca-Saja.</string>
+		<string name="restore_unable_locate">Tidak dapat menemukan partisi '{1}' untuk dipulihkan.</string>
+		<string name="no_part_restore">Tidak ada partisi yang dipilih untuk dipulihkan.</string>
+		<string name="restore_part_count">Memulihkan partisi {1}...</string>
+		<string name="total_restore_size">Ukuran total pemulihan {1}MB</string>
+		<string name="updating_system_details">Memperbarui rincian sistem</string>
+		<string name="restore_completed">[PEMULIHAN SELESAI DALAM {1} DETIK]</string>
+		<!-- {1} adalah lokasi yang tidak bisa kami buka, {2} adalah keluaran strerror -->
+		<string name="error_opening_strerr">Kesalahan membuka: '{1}' ({2})</string>
+		<string name="unable_locate_part_backup_name">Tidak dapat menemukan partisi cadangan dengan nama: '{1}'</string>
+		<string name="unable_find_part_path">Tidak dapat menemukan partisi pada '{1}'</string>
+		<string name="update_part_details">Memperbarui rincian partisi...</string>
+		<string name="update_part_details_done">... berhasil.</string>
+		<string name="wiping_dalvik">Menghapus direktori Dalvik...</string>
+		<string name="cleaned">Dibersihkan: {1}...</string>
+		<string name="cache_dalvik_done">-- Menghapus direktori dalvik cache berhasil!</string>
+		<string name="dalvik_done">-- Menghapus direktori dalvik berhasil!</string>
+		<string name="no_andsec">Tidak ada partisi keamanan Android yang ditemukan.</string>
+		<string name="unable_to_locate">Tidak dapat menemukan {1}.</string>
+		<string name="wiping_datamedia">Menghapus penyimpanan internal -- /data/media...</string>
+		<string name="unable_to_mount">Tidak dapat mount {1}</string>
+		<string name="unable_to_mount_internal">Tidak dapat mount penyimpanan_internal</string>
+		<string name="unable_to_mount_storage">Tidak dapat mount penyimpanan</string>
+		<string name="fail_decrypt">Gagal mendeskripsikan data.</string>
+		<string name="no_crypto_support">Tidak ada dukungan crypto yang dikompilasi ke dalam build ini.</string>
+		<string name="decrypt_success_dev">Data berhasil didekripsi, blok baru perangkat: '{1}'</string>
+		<string name="decrypt_success_nodev">Data berhasil dideskripsi</string>
+		<string name="done">Berhasil.</string>
+		<string name="start_partition_sd">Mempartisi kartu SD...</string>
+		<string name="partition_sd_locate">Tidak dapat menemukan perangkat untuk partisi.</string>
+		<string name="ext_swap_size">EXT + Ukuran swap lebih besar dari ukuran kartu SD.</string>
+		<string name="remove_part_table">Menghapus tabel partisi...</string>
+		<string name="unable_rm_part">Tidak dapat menghapus tabel partisi.</string>
+		<string name="create_part">Membuat partisi {1}...</string>
+		<string name="unable_to_create_part">Tidak dapat membuat partisi {1}.</string>
+		<string name="format_sdext_as">Memformat sd-ext sebagai {1}...</string>
+		<string name="part_complete">Mempartisi selesai.</string>
+		<string name="unable_to_open">Tidak dapat membuka '{1}'.</string>
+		<string name="mtp_already_enabled">MTP telah diaktifkan</string>
+		<string name="mtp_fail">Gagal mengaktifkan MTP</string>
+		<string name="no_mtp">Dukungan MTP tidak tersedia</string>
+		<string name="image_flash_start">[FLASH IMAGE DIMULAI]</string>
+		<string name="img_to_flash">Image untuk flash: '{1}'</string>
+		<string name="flash_unable_locate">Tidak dapat menemukan partisi '{1}'.</string>
+		<string name="no_part_flash">Tidak ada partisi yang dipilih.</string>
+		<string name="too_many_flash">Terlalu banyak partisi yang dipilih.</string>
+		<string name="invalid_flash">Partisi flash yang dipilih tidak valid.</string>
+		<string name="flash_done">[FLASH IMAGE BERHASIL]</string>
+		<string name="wiping">Menghapus {1}</string>
+		<string name="repair_not_exist">{1} tidak ada! Tidak dapat memperbaiki!</string>
+		<string name="repairing_using">Memperbaiki {1} menggunakan {2}...</string>
+		<string name="unable_repair">Tidak dapat memperbaiki {1}.</string>
+		<string name="mount_data_footer">Tidak dapat mount /data dan tidak dapat menemukan footer kripto.</string>
+		<!-- {1} adalah nama folder yang tidak dapat dibuat, {2} adalah keluaran strerror -->
+		<string name="create_folder_strerr">Tidak dapat membuat folder '{1}' ({2}).</string>
+		<!-- {1} adalah nama folder yang tidak dapat kami pasang, {2} adalah keluaran strerror -->
+		<string name="fail_mount">Gagal mount '{1}' ({2})</string>
+		<!-- {1} adalah nama folder yang tidak dapat dilepas, {2} adalah keluaran strerror -->
+		<string name="fail_unmount">Gagal unmount '{1}' ({2})</string>
+		<string name="cannot_resize">Tidak dapat mengubah ukuran {1}.</string>
+		<string name="repair_resize">Memperbaiki {1} sebelum mengubah ukuran.</string>
+		<string name="unable_resize">Tidak dapat mengubah ukuran {1}.</string>
+		<string name="no_digest_found" version="2">Tidak ada file digest pada '{1}'. Batalkan pilihan aktifkan verifikasi digest untuk memulihkan.</string>
+		<string name="digest_fail_match" version="2">Digest tidak cocok untuk '{1}'.</string>
+		<string name="digest_matched" version="2">Digest cocok untuk '{1}'.</string>
+		<string name="fail_decrypt_tar">Gagal mendeskripsikan file tar '{1}'</string>
+		<string name="format_data_msg">Anda mungkin perlu mereboot recovery agar dapat menggunakan /data lagi.</string>
+		<string name="format_data_err">Tidak dapat melakukan format untuk hapus enkripsi.</string>
+		<string name="formatting_using">Memformat {1} menggunakan {2}...</string>
+		<string name="unable_to_wipe">Tidak dapat menghapus {1}.</string>
+		<string name="cannot_wipe">Partisi {1} tidak dapat dihapus.</string>
+		<string name="remove_all">Menghapus semua file di didalam '{1}'</string>
+		<string name="wiping_data">Menghapus data tanpa menghapus /data/media ...</string>
+		<string name="backing_up">Mencadangkan {1}...</string>
+		<string name="backup_storage_warning">Cadangan {1} tidak menyertakan berkas apa pun di penyimpanan internal seperti gambar atau unduhan.</string>
+		<string name="backing">Mencadangkan</string>
+		<string name="backup_size">Ukuran file cadangan untuk '{1}' adalah 0 bytes.</string>
+		<string name="datamedia_fs_restore">PERINGATAN: Pencadangan /data ini dibuat dengan file sistem {1} ! Cadangan mungkin tidak bisa boot kecuali Anda mengubah kembali ke {1}.</string>
+		<string name="restoring">Memulihkan {1}...</string>
+		<string name="restoring_hdr">Memulihkan</string>
+		<string name="recreate_folder_err">Tidak dapat membuat folder {1}.</string>
+		<string name="img_size_err">Ukuran Image lebih besar dari perangkat</string>
+		<string name="flashing">Memflash {1}...</string>
+		<string name="backup_folder_set">Folder cadangan disetel ke '{1}'</string>
+		<string name="locate_backup_err">Tidak dapat menemukan cadangan '{1}'</string>
+		<string name="set_restore_opt">Pengaturan opsi pemulihan: '{1}':</string>
+		<string name="digest_check_skip" version="2">Pengabaian pemeriksaan digest aktif</string>
+		<string name="ors_encrypt_restore_err">Tidak dapat menggunakan OpenRecoveryScript untuk memulihkan cadangan terenkripsi.</string>
+		<string name="mounting">Memasang</string>
+		<string name="unmounting">Melepas</string>
+		<string name="mounted">'{1}' Terpasang</string>
+		<string name="unmounted">'{1}' Dilepaskan</string>
+		<string name="setting">Pengaturan '{1}' ke '{2}'</string>
+		<string name="setting_empty">Pengaturan '{1}' tidak ada</string>
+		<string name="making_dir1">Membuat direktori</string>
+		<string name="making_dir2">Membuat direktori: '{1}'</string>
+		<string name="running_command">Menjalankan perintah</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">Memulai fitur ADB sideload...</string>
+		<string name="need_new_adb">Anda memerlukan adb 1.0.32 atau lebih baru pada perangkat ini.</string>
+		<string name="no_pwd">Sandi tidak tersedia.</string>
+		<string name="done_ors">Memproses file skripsi selesai.</string>
+		<string name="injecttwrp">Menyuntik TWRP kedalam image boot...</string>
+		<string name="zip_err">Kesalahan Menginstal file zip '{1}'</string>
+		<string name="installing_zip">Menginstal file zip '{1}'</string>
+		<string name="select_backup_opt">Pengaturan opsi pencadangan:</string>
+		<string name="compression_on">Kompresi aktif</string>
+		<string name="digest_off" version="2">Penghasil digest dinonaktifkan</string>
+		<string name="backup_fail">Pencadangan gagal</string>
+		<string name="backup_clean">Pencadangan gagal. Membersihkan folder cadangan.</string>
+		<string name="running_recovery_commands">Menjalankan perintah recovery</string>
+		<string name="recovery_commands_complete">Perintah recovery selesai.</string>
+		<string name="running_ors">Menjalankan OpenRecoveryScript</string>
+		<string name="ors_complete">OpenRecoveryScript selesai.</string>
+		<string name="check_for_digest" version="2">Memeriksa file digest...</string>
+		<string name="invalid_zip_format">Format file zip tidak valid!</string>
+		<string name="fail_sysmap">Gagal memetakan file '{1}'</string>
+		<string name="verify_zip_sig">Memverifikasi tanda tangan zip ...</string>
+		<string name="verify_zip_fail">Verifikasi gagal!</string>
+		<string name="verify_zip_done">Verifikasi berhasil.</string>
+		<string name="zip_corrupt">File zip rusak!</string>
+		<string name="no_digest" version="2">Melewati pemeriksa digest: tidak ada file digest yang ditemukan</string>
+		<string name="digest_fail" version="2">Digest tidak cocok</string>
+		<string name="digest_match" version="2">Digest cocok</string>
+		<string name="pid_signal">{1} proses diakhiri dengan sinyal: {2}</string>
+		<string name="pid_error">{1} proses diakhiri dengan kesalahan: {2}</string>
+		<string name="install_dumlock">Menginstal HTC Dumlock ke sistem...</string>
+		<string name="dumlock_restore">Memulihkan boot original...</string>
+		<string name="dumlock_reflash">Memlash ulang recovery ke boot...</string>
+		<string name="run_script">Menjalankan skripsi {1}...</string>
+		<string name="rename_stock">Ubah nama file stok recovery dalam /sistem untuk mencegah stok ROM mengubah TWRP.</string>
+		<string name="split_backup">Memecah file cadangan menjadi beberapa arsip ...</string>
+		<string name="backup_error">Kesalahan membuat cadangan.</string>
+		<string name="restore_error">Kesalahan saat proses pemulihan.</string>
+		<string name="split_thread">Memisah ID untaian {1} menjadi arsip {2}</string>
+		<!-- 2 item ini disimpan di pengelola data bukan pengelola sumber daya, jadi %llu, dll sesuai daripada {1} -->
+		<string name="file_progress">%llu dari file %llu, %i%%</string>
+		<string name="size_progress">%lluMB of %lluMB, %i%%</string>
+		<string name="decrypt_cmd" version="2">Mencoba mendekripsi partisi data atau pengguna melalui baris perintah.</string>
+		<string name="base_pkg_err">Gagal memuat paket dasar.</string>
+		<string name="simulating">Mensimulasikan tindakan...</string>
+		<string name="backup_cancel">Pencadangan dibatalkan.</string>
+		<string name="config_twrp">Mengkonfigurasi TWRP...</string>
+		<string name="config_twrp_err">Tidak dapat mengkonfigurasi TWRP dengan kernel ini.</string>
+		<string name="copy_log">Log pemulihan disalin ke {1}.</string>
+		<string name="max_queue">Antrian maksimum zip tercapai!</string>
+		<string name="min_queue">Antrian minimum zip tercapai!</string>
+		<string name="screenshot_saved">Screenshot disimpan ke {1}</string>
+		<string name="screenshot_err">Gagal mengambil screenshot!</string>
+		<string name="zip_wipe_cache">Satu atau lebih zip meminta penghapusan cache -- Hapus cache sekarang.</string>
+		<string name="and_sec_wipe_err">Tidak dapat menghapus keamanan Android</string>
+		<string name="dalvik_wipe_err">Gagal menghapus dalvik</string>
+		<string name="auto_gen">(Buat otomatis)</string>
+		<string name="curr_date">(Waktu saat ini)</string>
+		<string name="backup_name_len">Nama cadangan terlalu panjang.</string>
+		<string name="backup_name_invalid">Nama cadangan '{1}' terdapat karakter yang tidak valid: '{1}'</string>
+		<string name="no_real_sdcard">Perangkat ini tidak memiliki Kartu SD asli! Membatalkan!</string>
+		<string name="cancel_sideload">Membatalkan ADB sideload...</string>
+		<string name="change_fs_err">Kesalahan mengubah file sistem.</string>
+		<string name="theme_ver_err">Versi tema kustom tidak cocok dengan versi TWRP. Gunakan tema stok.</string>
+		<string name="up_a_level">(Kembali)</string>
+		<string name="install_reboot" version="2">Mereboot dalam %tw_sleep% detik(s)</string>
+		<string name="adbbackup_error">Kesalahan dengan pencadangan ADB. Mengeluarkan..."</string>
+		<string name="adbbackup_control_error">Tidak dapat menulis ke saluran kontrol adb</string>
+		<string name="twrp_adbbu_option">--opsi twrp diperlukan untuk mengaktifkan twrp adb backup</string>
+		<string name="partition_not_found">lokasi: {1} tidak ditemukan dalam daftar partisi</string>
+		<string name="copy_kernel_log">Salin log kernel ke {1}</string>
+		<string name="include_kernel_log">Termasuk log kernel</string>
+		<string name="sha2_chk">Gunakan SHA2 untuk mencirikan</string>
+		<string name="unable_set_boot_slot">Kesalahan mengubah boot bootloader ke {1}</string>
+		<string name="unmount_sys_install">Unmount sistem sebelum menginstal zip</string>
+		<string name="unmount_system">Unmount sistem...</string>
+		<string name="unmount_system_err">Gagal unmount sistem</string>
+		<string name="flash_ab_inactive">Memflash zip A / B ke slot tidak aktif: {1}</string>
+		<string name="flash_ab_reboot">Untuk memflash zip tambahan, silakan reboot recovery untuk beralih ke slot yang diperbarui.</string>
+		<string name="flash_ab_both_slots">Flash ke kedua slot</string>
+		<string name="ozip_decrypt_decryption">Mulai mendekripsi Ozip ...</string>
+		<string name="ozip_decrypt_finish">Mendeskripsi Ozip selesai!</string>
+		<string name="fastboot_button">Fastboot</string>
+		<string name="enable_adb">Aktifkan ADB</string>
+		<string name="enable_fastboot">Aktifkan Fastboot</string>
+		<string name="fastboot_console_msg">Memasuki mode Fastboot...</string>
+		<!-- Android Rescue Party trigger -->
+		<string name="rescue_party0">Memicu Pihak Penyelamat Android! Solusi memungkinkan? Antara:</string>
+		<string name="rescue_party1"> 1. Hapus cache, dan/atau</string>
+		<string name="rescue_party2"> 2. Format data, dan/atau</string>
+		<string name="rescue_party3"> 3. Bersihkan-flash ROM Anda.</string>
+		<string name="rescue_party4">Masalah yang dilaporkan adalah: </string>
+		<string name="restore_system_context">Tidak dapat memperoleh konteks default untuk {1} -- Android mungkin tidak bisa booting.</string>
+		<string name="change_twrp_folder_btn">Ubah folder TWRP</string>
+		<string name="change_twrp_folder_on_process">Mengubah folder TWRP</string>
+		<string name="change_twrp_folder_after_process">Folder TWRP diubah ke</string>
+		<string name="tw_folder_exists">Nama folder sudah ada!</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/it.xml b/gui/theme/common/languages/it.xml
new file mode 100644
index 0000000..3427710
--- /dev/null
+++ b/gui/theme/common/languages/it.xml
@@ -0,0 +1,689 @@
+<?xml version="1.0"?>
+
+<language>
+	<display>Italiano</display>
+
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<!-- Partition display names -->
+		<string name="system">System</string>
+		<string name="system_image">Immagine System</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Immagine Vendor</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="data_backup">Data (escl. archivio)</string>
+		<string name="sdcard">Scheda SD</string>
+		<string name="internal">Archivio interno</string>
+		<string name="microsd">Scheda Micro SD</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Data annessa</string>
+		<string name="adopted_storage">Archivio annesso</string>
+
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% &#xB0;C</string>
+		<string name="battery_pct">Batteria: %tw_battery%</string>
+		<string name="sort_by_name">Ordina per Nome</string>
+		<string name="sort_by_date">Ordina per Data</string>
+		<string name="sort_by_size">Ordina per Dimensione</string>
+		<string name="sort_by_name_only">Nome</string>
+		<string name="sort_by_date_only">Data</string>
+		<string name="sort_by_size_only">Dimensione</string>
+		<string name="tab_general">GENERALE</string>
+		<string name="tab_options">OPZIONI</string>
+		<string name="tab_backup">BACKUP</string>
+		<string name="tab_time_zone">FUSO ORARIO</string>
+		<string name="tab_screen">SCHERMO</string>
+		<string name="tab_vibration">VIBRAZIONE</string>
+		<string name="tab_language">LINGUA</string>
+
+		<string name="install_btn">Installa</string>
+		<string name="wipe_btn">Pulisci</string>
+		<string name="backup_btn">Backup</string>
+		<string name="restore_btn">Ripristina</string>
+		<string name="mount_btn">Monta</string>
+		<string name="settings_btn">Impostazioni</string>
+		<string name="advanced_btn">Avanzate</string>
+		<string name="reboot_btn">Riavvia</string>
+		<string name="files_btn">File</string>
+		<string name="copy_log_btn">Copia Log</string>
+		<string name="select_type_hdr">Scegli tipologia</string>
+		<string name="install_zip_hdr">Installa Zip</string>
+		<string name="install_zip_btn">Installa Zip</string>
+		<string name="install_image_hdr">Installa Immagine</string>
+		<string name="install_image_btn">Installa Immagine</string>
+		<string name="install_select_file_hdr">Seleziona il file</string>
+		<string name="file_selector_folders_hdr">Cartelle</string>
+		<string name="select_file_from_storage">Seleziona il file da %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Installa</string>
+		<string name="select_storage_hdr">Scegli archivio</string>
+		<string name="select_storage_btn">Scegli archivio</string>
+		<string name="queue_hdr">Coda</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% file accodati su un max di 10</string>
+		<string name="zip_queue_count_s">File %tw_zip_queue_count% di 10:</string>
+		<string name="zip_warn1">Questa operazione potrebbe installare software</string>
+		<string name="zip_warn2">incompatibile e rendere il tuo dispositivo inutilizzabile.</string>
+		<string name="zip_back_cancel">Premi indietro per annullare la selezione di questo file.</string>
+		<string name="zip_back_clear">Premi indietro per eliminare la coda di Zip.</string>
+		<string name="folder">Cartella:</string>
+		<string name="file">File:</string>
+		<string name="zip_sig_chk">Verifica della firma del file Zip</string>
+		<string name="inject_twrp_chk">Esegui TWRP Inject dopo l'installazione</string>
+		<string name="install_reboot_chk">Riavvia dopo che l'installazione è completata</string>
+		<string name="options_hdr">Opzioni</string>
+		<string name="confirm_flash_hdr">Conferma installazione</string>
+		<string name="zip_queue">Coda:</string>
+		<string name="options">Opzioni:</string>
+		<string name="swipe_confirm">   Conferma</string>
+		<string name="zip_add_btn">Aggiungi altri Zip</string>
+		<string name="zip_clear_btn">Elimina la coda di Zip</string>
+		<string name="install_zip_count_hdr">Installazione Zip %tw_zip_index% di %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Installazione Zip %tw_file% in corso...</string>
+		<string name="failed">Operazione fallita</string>
+		<string name="successful">Operazione riuscita</string>
+		<string name="install_failed">Installazione fallita</string>
+		<string name="install_successful">Installazione riuscita</string>
+		<string name="wipe_cache_dalvik_btn">Pulisci Cache/Dalvik</string>
+		<string name="reboot_system_btn">Riavvia sistema</string>
+		<string name="install_sel_target">Scegli partizione</string>
+		<string name="flash_image_select">Scegli la partizione su cui installare:</string>
+		<string name="target_partition">Partizione scelta:</string>
+		<string name="flashing_image">Installazione Immagine...</string>
+		<string name="image_flashed">Immagine installata</string>
+		<string name="wipe_cache_dalvik_confirm">Pulire Cache &amp; Dalvik?</string>
+		<string name="wiping_cache_dalvik">Pulizia Cache &amp; Dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Pulizia Cache &amp; Dalvik completata</string>
+		<string name="swipe_wipe">Scorri per pulire</string>
+		<string name="swipe_wipe_s">   Pulisci</string>
+		<string name="no_os1">Nessun SO installato. Sei</string>
+		<string name="no_osrb">certo di voler riavviare?</string>
+		<string name="no_ospo">certo di voler spegnere?</string>
+		<string name="rebooting">Riavvio...</string>
+		<string name="swipe_reboot">Scorri per riavviare</string>
+		<string name="swipe_reboot_s">   Riavvia</string>
+		<string name="reboot_install_app_hdr">Installazione app di TWRP</string>
+		<string name="reboot_install_app1">Ti piacerebbe installare l'app ufficiale di TWRP?</string>
+		<string name="reboot_install_app2">L'app può verificare nuove versioni di TWRP.</string>
+		<string name="reboot_install_app_prompt_install">Chiedi di installare l'app TWRP se non è installata</string>
+		<string name="reboot_install_app_system">Installa come app di Sistema</string>
+		<string name="reboot_installing_app">Installazione dell'app...</string>
+		<string name="swipe_to_install_app">Scorri per installare l'app</string>
+		<string name="swipe_flash">Scorri per installare</string>
+		<string name="confirm_action">Conferma l'azione</string>
+		<string name="back_cancel">Premi indietro per annullare.</string>
+		<string name="cancel_btn">Annulla</string>
+		<string name="wipe_hdr">Pulizia</string>
+		<string name="factory_reset_hdr">Reset di fabbrica</string>
+		<string name="factory_reset_btn">Reset di fabbrica</string>
+		<string name="factory_reset1">Data, Cache e Dalvik saranno puliti</string>
+		<string name="factory_reset2">(l'archivio interno sarà escluso)</string>
+		<string name="factory_reset3">La maggior parte delle volte, questo</string>
+		<string name="factory_reset4">sarà l'unico tipo di pulizia necessario.</string>
+		<string name="factory_resetting">Reset in corso...</string>
+		<string name="advanced_wipe_hdr">Pulizia avanzata</string>
+		<string name="advanced_wipe_btn">Pulizia avanzata</string>
+		<string name="wipe_enc_confirm">Eliminare la crittografia da Data?</string>
+		<string name="formatting_data">Formattazione di Data in corso...</string>
+		<string name="swipe_format_data">Scorri per formattare Data...</string>
+		<string name="swipe_format_data_s">   Formattazione Data</string>
+		<string name="factory_reset_complete">Reset di fabbrica completato</string>
+		<string name="sel_part_hdr">Selezione partizioni</string>
+		<string name="wipe_sel_confirm">Pulire questa(e) partizione(i)?</string>
+		<string name="wiping_part">Pulizia partizione(i) in corso...</string>
+		<string name="wipe_complete">Pulizia completata</string>
+		<string name="sel_part_wipe">Scegli le partizioni da pulire:</string>
+		<string name="invalid_part_sel">Scelta non valida</string>
+		<string name="format_data_hdr">Formattazione Data</string>
+		<string name="format_data_btn">Formatta Data</string>
+		<string name="format_data_ptr1">Formattare Data eliminerà tutte le tue app,</string>
+		<string name="format_data_ptr2">backup, immagini, video e file. Inoltre, la</string>
+		<string name="format_data_ptr3">crittografia dell'archivio interno sarà rimossa.</string>
+		<string name="format_data_adopted">Includi Archivio annesso</string>
+		<string name="format_data_lcp1">Formattare Data eliminerà tutte le app, backup, immagini, video e file.</string>
+		<string name="format_data_lcp2">Inoltre, la crittografia dell'archivio interno sarà rimossa.</string>
+		<string name="format_data_wtc1">Formattare Data eliminerà tutte le tue app,</string>
+		<string name="format_data_wtc2">backup e file. L'operazione è irreversibile.</string>
+		<string name="format_data_undo">L'operazione è irreversibile.</string>
+		<string name="format_data_complete">Formattazione di Data completata</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">Scrivi yes per continuare.  Premi indietro per annullare.</string>
+		<string name="part_opt_hdr">Opzioni partizione per: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Scegli azione</string>
+		<string name="file_sys_opt">Opzioni filesystem</string>
+		<string name="partition">Partizione: %tw_partition_name%</string>
+		<string name="part_mount_point">Punto di montaggio: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Filesystem: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Presente: Sì</string>
+		<string name="part_present_no">Presente: No</string>
+		<string name="part_removable_yes">Rimovibile: Sì</string>
+		<string name="part_removable_no">Rimovibile: No</string>
+		<string name="part_size">Dimensione: %tw_partition_size%MB</string>
+		<string name="part_used">Utilizzati: %tw_partition_used%MB</string>
+		<string name="part_free">Liberi: %tw_partition_free%MB</string>
+		<string name="part_backup_size">Dimensione backup: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">Ridimensiona filesystem</string>
+		<string name="resize_btn_s">Ridimensiona</string>
+		<string name="resize_confirm">Ridimensionare %tw_partition_name%?</string>
+		<string name="resizing">Ridimensionamento in corso...</string>
+		<string name="resize_complete">Ridimensionamento completato</string>
+		<string name="swipe_resize">Scorri per ridimensionare</string>
+		<string name="swipe_resize_s">   Ridimensiona</string>
+		<string name="repair_btn">Ripara filesystem</string>
+		<string name="repair_btn_s">Ripara</string>
+		<string name="repair_confirm">Riparare %tw_partition_name%?</string>
+		<string name="repairing">Riparazione in corso...</string>
+		<string name="repair_complete">Riparazione completata</string>
+		<string name="swipe_repair">Scorri per riparare</string>
+		<string name="swipe_repair_s">   Ripara</string>
+		<string name="change_fs_btn">Cambia filesystem</string>
+		<string name="change_fs_btn_s">Cambia</string>
+		<string name="change_fs_for_hdr">Cambia il filesystem di: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Partizione: %tw_partition_name% &gt; Scegli filesystem</string>
+		<string name="change_fs_warn1">Alcune ROM o kernel non supportano determinati</string>
+		<string name="change_fs_warn2">filesystem. Procedi con cautela!</string>
+		<string name="change_fs_confirm">Modificare %tw_partition_name%?</string>
+		<string name="formatting">Formattazione in corso...</string>
+		<string name="format_complete">Formattazione completata</string>
+		<string name="swipe_change_fs">Scorri per modificare</string>
+		<string name="swipe_change_s">   Modifica</string>
+		<string name="back_btn">Indietro</string>
+		<string name="wipe_enc_btn">Elimina crittografia</string>
+		<string name="swipe_factory_reset">Scorri per eseguire reset di fabbrica</string>
+		<string name="repair_change_btn">Ripara o Modifica filesystem</string>
+		<string name="storage_hdr">Archivio: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Backup</string>
+		<string name="backup_confirm_hdr">Conferma Backup</string>
+		<string name="encryption_tab">CRITTOGRAFIA</string>
+		<string name="encryption">Crittografia:</string>
+		<string name="name">Nome:</string>
+		<string name="sel_part_backup">Seleziona le partizioni di cui eseguire un Backup:</string>
+		<string name="storage">Archivio:</string>
+		<string name="enc_disabled">disabilitata - imposta una password per abilitarla</string>
+		<string name="enc_enabled">abilitata</string>
+		<string name="enable_backup_comp_chk">Abilita compressione</string>
+		<string name="skip_digest_backup_chk" version="2">Salta la generazione di Digest durante il backup</string>
+		<string name="disable_backup_space_chk" version="2">Disabilita controllo dello spazio libero prima del backup</string>
+		<string name="current_boot_slot">Slot attuale: %tw_active_slot%</string>
+		<string name="boot_slot_a">Slot A</string>
+		<string name="boot_slot_b">Slot B</string>
+		<string name="changing_boot_slot">Cambiamento Slot d'avvio</string>
+		<string name="changing_boot_slot_complete">Cambiamento Slot d'avvio completato</string>
+		<string name="refresh_sizes_btn">Aggiorna dimensioni</string>
+		<string name="swipe_backup">Scorri per eseguire il backup</string>
+		<string name="append_date_btn">Includi la data</string>
+		<string name="backup_name_exists">Esiste già un backup con lo stesso nome!</string>
+		<string name="encrypt_backup">Crittografare il backup?</string>
+		<string name="enter_pass">Inserisci una password:</string>
+		<string name="enter_pass2">Inserisci nuovamente la password:</string>
+		<string name="pass_not_match">Le password non corrispondono!</string>
+		<string name="partitions">Partizioni:</string>
+		<string name="disabled">Disabilitato</string>
+		<string name="enabled">Abilitato</string>
+		<string name="backup_name_hdr">Imposta nome backup</string>
+		<string name="progress">Avanzamento:</string>
+		<string name="backup_complete">Backup completato</string>
+		<string name="restore_hdr">Ripristina backup</string>
+		<string name="sel_backup_hdr">Scegli backup</string>
+		<string name="restore_sel_store_hdr">Seleziona un backup da %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Scegli i contenuti da ripristinare:</string>
+		<string name="restore_enc_backup_hdr">Backup crittografato</string>
+		<string name="restore_dec_fail">Password errata! Riprova.</string>
+		<string name="del_backup_btn">Elimina backup</string>
+		<string name="del_backup_confirm">Eliminare il backup?</string>
+		<string name="del_backup_confirm2">L'operazione è irreversibile!</string>
+		<string name="deleting_backup">Eliminazione backup in corso...</string>
+		<string name="backup_deleted">Eliminazione backup completata</string>
+		<string name="swipe_delete">Scorri per eliminare</string>
+		<string name="swipe_delete_s">   Elimina</string>
+		<string name="restore_try_decrypt">Backup crittografato - tentativo di decriptazione</string>
+		<string name="restore_try_decrypt_s">Tentativo di decriptazione</string>
+		<string name="restore_backup_date">Backup creato il %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Seleziona le partizioni da ripristinare:</string>
+		<string name="restore_enable_digest_chk" version="2">Abilita verifica Digest</string>
+		<string name="restore_complete">Ripristino completato</string>
+		<string name="swipe_restore">Scorri per ripristinare</string>
+		<string name="swipe_restore_s">   Ripristina</string>
+		<string name="rename_backup_hdr">Rinomina backup</string>
+		<string name="rename_backup_confirm">Rinominare il backup?</string>
+		<string name="rename_backup_confirm2">L'operazione è irreversibile!</string>
+		<string name="renaming_backup">Rinominazione del backup in corso...</string>
+		<string name="rename_backup_complete">Rinominazione del backup completata</string>
+		<string name="swipe_to_rename">Scorri per rinominare</string>
+		<string name="swipe_rename">   Rinomina</string>
+		<string name="confirm_hdr">Conferma</string>
+		<string name="mount_hdr">Monta</string>
+		<string name="mount_sel_part">Seleziona le partizioni da montare:</string>
+		<string name="mount_sys_ro_chk">Monta la partizione System in sola lettura</string>
+		<string name="mount_sys_ro_s_chk">Monta System in sola lettura</string>
+		<string name="decrypt_data_btn">Decripta Data</string>
+		<string name="disable_mtp_btn">Disabilita MTP</string>
+		<string name="enable_mtp_btn">Abilita MTP</string>
+		<string name="mount_usb_storage_btn">Monta archivio USB</string>
+		<string name="usb_storage_hdr">Archivio USB</string>
+		<string name="usb_stor_mnt1">Archivio USB montato</string>
+		<string name="usb_stor_mnt2">Accertati di aver eseguito la rimozione sicura</string>
+		<string name="usb_stor_mnt3">dal tuo computer prima di smontare l'archivio!</string>
+		<string name="unmount_btn">Smonta</string>
+		<string name="rb_system_btn">Sistema</string>
+		<string name="rb_poweroff_btn">Spegni</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Download</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Spegnimento in corso...</string>
+		<string name="swipe_power_off">Scorri per spegnere</string>
+		<string name="swipe_power_off_s">Spegni</string>
+		<string name="sys_ro_hdr">Partizione System non modificata</string>
+		<string name="sys_ro_keep">Mantenere System in sola lettura?</string>
+		<string name="sys_rop1">TWRP può preservare la partizione System dalle modifiche</string>
+		<string name="sys_rop2">per facilitarti l'installazione di aggiornamenti ufficiali.</string>
+		<string name="sys_rop3">TWRP non potrà prevenire la sostituzione della Recovery da parte</string>
+		<string name="sys_rop4">della ROM, e non ti chiederà se desideri ottenere accesso root.</string>
+		<string name="sys_rop5">Installare Zip o eseguire operazioni mediante adb</string>
+		<string name="sys_rop6">potrà ugualmente operare modifiche su System.</string>
+		<string name="sys_rol1">TWRP può preservare la partizione System dalle modifiche per facilitarti l'installazione di aggiornamenti ufficiali.</string>
+		<string name="sys_rol2">TWRP non potrà prevenire la sostituzione della Recovery da parte della ROM, e non ti chiederà se desideri ottenere accesso root.</string>
+		<string name="sys_rol3">Installare Zip o eseguire operazioni mediante adb potrà ugualmente operare modifiche su System.</string>
+		<string name="sys_ro_never_show_chk">Non mostrare più questa schermata all'avvio</string>
+		<string name="sys_ro_keep_ro_btn">Mantieni sola lettura</string>
+		<string name="swipe_allow_mod">Scorri per permettere le modifiche</string>
+		<string name="swipe_allow_mod_s">Permetti modifiche</string>
+		<string name="settings_hdr">Impostazioni</string>
+		<string name="settings_gen_hdr">Generale</string>
+		<string name="settings_gen_s_hdr">Generale</string>
+		<string name="settings_gen_btn">Generale</string>
+		<string name="use_rmrf_chk">Usa rm -rf invece di formattare</string>
+		<string name="use24clock_chk">Usa orologio a 24 ore</string>
+		<string name="rev_navbar_chk">Rovescia la navbar</string>
+		<string name="simact_chk">Simula le azioni per il testing dei temi</string>
+		<string name="simfail_chk">Simula fallimenti per le azioni</string>
+		<string name="ctr_navbar_rdo">Centra pulsanti della navbar</string>
+		<string name="lft_navbar_rdo">Allinea pulsanti della navbar a SX</string>
+		<string name="rht_navbar_rdo">Allinea pulsanti della navbar a DX</string>
+		<string name="restore_defaults_btn">Ripristina predefinite</string>
+		<string name="settings_tz_btn">Fuso orario</string>
+		<string name="settings_screen_btn">Schermo</string>
+		<string name="settings_screen_bright_btn">Luminosità</string>
+		<string name="settings_vibration_btn">Vibrazione</string>
+		<string name="settings_language_btn">Lingua</string>
+		<string name="time_zone_hdr">Fuso orario</string>
+		<string name="sel_tz_list">Scegli fuso orario:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) Samoa, Midway Island</string>
+		<string name="utcm10">(UTC -10) Hawaii</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm8">(UTC -8) Pacific Time</string>
+		<string name="utcm7">(UTC -7) Mountain Time</string>
+		<string name="utcm6">(UTC -6) Central Time</string>
+		<string name="utcm5">(UTC -5) Eastern Time</string>
+		<string name="utcm4">(UTC -4) Atlantic Time</string>
+		<string name="utcm3">(UTC -3) Brazil, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Mid-Atlantic</string>
+		<string name="utcm1">(UTC -1) Azores, Cape Verde</string>
+		<string name="utc0">(UTC  0) London, Dublin, Lisbon</string>
+		<string name="utcp1">(UTC +1) Berlin, Brussels, Paris</string>
+		<string name="utcp2">(UTC +2) Athens, Istanbul, South Africa</string>
+		<string name="utcp3">(UTC +3) Moscow, Baghdad</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC +5) Yekaterinburg, Islamabad</string>
+		<string name="utcp6">(UTC +6) Almaty, Dhaka, Colombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp8">(UTC +8) Beijing, Singapore, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokyo, Seoul, Yakutsk</string>
+		<string name="utcp10">(UTC +10) Eastern Australia, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Solomon Islands</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fiji</string>
+		<string name="sel_tz_offset">Scegli offset (solitamente 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Nessuno</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Usa ora legale</string>
+		<string name="curr_tz">Fuso orario attuale: %tw_time_zone%</string>
+		<string name="curr_tz_s">Fuso orario attuale:</string>
+		<string name="set_tz_btn">Imposta fuso orario</string>
+		<string name="settings_screen_hdr">Schermo</string>
+		<string name="settings_screen_timeout_hdr">Timeout schermo</string>
+		<string name="enable_timeout_chk">Abilita timeout schermo</string>
+		<string name="screen_to_slider">Timeout in secondi:</string>
+		<string name="screen_to_slider_s">Timeout in secondi (0=disabilitato): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Impostazione del timeout non disponibile</string>
+		<string name="screen_bright_slider">Luminosità: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Impostazione luminosità non disponibile</string>
+		<string name="vibration_hdr">Vibrazione</string>
+		<string name="button_vibration_hdr">Vibrazione pulsanti</string>
+		<string name="kb_vibration_hdr">Vibrazione tastiera</string>
+		<string name="act_vibration_hdr">Vibrazione azioni</string>
+		<string name="button_vibration">Vibrazione pulsanti:</string>
+		<string name="kb_vibration">Vibrazione tastiera:</string>
+		<string name="act_vibration">Vibrazione azioni:</string>
+		<string name="select_language">Scegli lingua:</string>
+		<string name="sel_lang_btn">Scegli lingua</string>
+		<string name="set_language_btn">Imposta lingua</string>
+		<string name="advanced_hdr">Avanzate</string>
+		<string name="copy_log_confirm">Copiare il log su scheda SD?</string>
+		<string name="copying_log" version="2">Copia i log nella scheda SD...</string>
+		<string name="copy_log_complete" version="2">Copia dei log completata</string>
+		<string name="fix_context_btn">Ripara contesti</string>
+		<string name="part_sd_btn">Partiziona SD</string>
+		<string name="part_sd_s_btn">Scheda SD</string>
+		<string name="file_manager_btn">File manager</string>
+		<string name="language_hdr">Lingua</string>
+		<string name="terminal_btn">Terminale</string>
+		<string name="reload_theme_btn">Ricarica tema</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">TWRP Inject</string>
+		<string name="inject_twrp_confirm">Eseguire nuovamente TWRP Inject?</string>
+		<string name="injecting_twrp">Esecuzione di TWRP Inject in corso...</string>
+		<string name="inject_twrp_complete">TWRP Inject completato</string>
+		<string name="swipe_to_confirm">Scorri per confermare</string>
+		<string name="part_sd_hdr">Partiziona scheda SD</string>
+		<string name="invalid_partsd_sel">Devi selezionare un dispositivo rimovibile</string>
+		<string name="part_sd_lose">Tutti i file sulla scheda SD andranno perduti!</string>
+		<string name="part_sd_undo">L'operazione è irreversibile!</string>
+		<string name="part_sd_ext_sz">Dimensione EXT:</string>
+		<string name="part_sd_swap_sz">Dimensione Swap:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Filesystem:</string>
+		<string name="swipe_part_sd">Scorri per partizionare</string>
+		<string name="swipe_part_sd_s">Partiziona</string>
+		<string name="partitioning_sd">Partizionamento scheda SD in corso...</string>
+		<string name="partitioning_sd2">Ci vorranno un paio di minuti.</string>
+		<string name="part_sd_complete">Partizionamento completato</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Ripristina Boot originale</string>
+		<string name="dumlock_restore_confirm">Ripristinare l'immagine di Boot originale?</string>
+		<string name="dumlock_restoring">Ripristino del Boot originale in corso...</string>
+		<string name="dumlock_restore_complete">Ripristino del Boot originale completato</string>
+		<string name="dumlock_reflash_btn">Reinstalla Recovery</string>
+		<string name="dumlock_reflash_confirm">Reinstallare la Recovery in Boot?</string>
+		<string name="dumlock_reflashing">Installazione Recovery in Boot in corso...</string>
+		<string name="dumlock_reflash_complete">Installazione della Recovery in Boot completata</string>
+		<string name="dumlock_install_btn">Installa HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Installare i file di HTC Dumlock nella ROM?</string>
+		<string name="dumlock_installing">Installazione di HTC Dumlock in corso...</string>
+		<string name="dumlock_install_complete">Installazione di HTC Dumlock Install completata</string>
+		<string name="swipe_to_unlock">Scorri per sbloccare</string>
+		<string name="swipe_unlock">   Sblocca</string>
+		<string name="fm_hdr">File manager</string>
+		<string name="fm_sel_file">Seleziona un file o una cartella</string>
+		<string name="fm_type_folder">Cartella</string>
+		<string name="fm_type_file">File</string>
+		<string name="fm_choose_act">Scegli azione</string>
+		<string name="fm_selected">%tw_fm_type% selezionato(a):</string>
+		<string name="fm_copy_btn">Copia</string>
+		<string name="fm_copy_file_btn">Copia</string>
+		<string name="fm_copy_folder_btn">Copia</string>
+		<string name="fm_copying">Copia di</string>
+		<string name="fm_move_btn">Sposta</string>
+		<string name="fm_moving">Spostamento di</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755 di</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Elimina</string>
+		<string name="fm_deleting">Eliminazione di</string>
+		<string name="fm_rename_btn">Rinomina</string>
+		<string name="fm_rename_file_btn">Rinomina</string>
+		<string name="fm_rename_folder_btn">Rinomina</string>
+		<string name="fm_renaming">Rinominazione di</string>
+		<string name="fm_sel_dest">Scegli cartella di destinazione</string>
+		<string name="fm_sel_curr_folder">Scegli cartella attuale</string>
+		<string name="fm_rename_hdr">Rinomina</string>
+		<string name="fm_set_perms_hdr">Imposta permessi</string>
+		<string name="fm_perms">Permessi:</string>
+		<string name="fm_complete">Operazione completata</string>
+		<string name="decrypt_data_hdr">Decripta Data</string>
+		<string name="decrypt_data_enter_pass">Inserisci la password.</string>
+		<string name="decrypt_data_failed">Password errata! Riprova.</string>
+		<string name="decrypt_data_failed_pattern">Sequenza errata! Riprova.</string>
+		<string name="decrypt_data_enter_pattern">Inserisci sequenza di sblocco.</string>
+		<string name="decrypt_data_trying">Tentativo di decriptazione in corso...</string>
+		<string name="term_hdr">Terminale</string>
+		<string name="term_s_hdr">Terminale</string>
+		<string name="term_kill_btn">TERMINA</string>
+		<string name="term_sel_folder_hdr">Scegli la cartella d'inizio</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Pulisci la cache Dalvik</string>
+		<string name="sideload_wipe_cache_chk">Pulisci Cache</string>
+		<string name="swipe_to_sideload">Scorri per avviare Sideload</string>
+		<string name="swipe_sideload">   Avvia</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Utilizzo: adb sideload nomefile.zip</string>
+		<string name="sideload_complete">ADB Sideload completato</string>
+		<string name="fix_contexts_hdr">Ripristina contesti</string>
+		<string name="fix_contexts_note1">Nota: il ripristino dei contesti è raramente necessario.</string>
+		<string name="fix_contexts_note2">Il ripristino dei contesti SELinux può impedire</string>
+		<string name="fix_contexts_note3">il corretto avvio del tuo dispositivo.</string>
+		<string name="swipe_to_fix_contexts">Scorri per ripristinare i contesti</string>
+		<string name="swipe_fix_contexts">   Ripristina contesti</string>
+		<string name="fixing_contexts">Ripristino dei contesti in corso...</string>
+		<string name="fix_contexts_complete">Ripristino dei contesti completato</string>
+		<string name="reboot_hdr">Riavvio</string>
+		<string name="install_cancel">Non installare</string>
+		<string name="sel_storage_list">Scegli archivio</string>
+		<string name="ok_btn">OK</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">Il kernel non supporta la lettura dei contesti SELinux.</string>
+		<string name="full_selinux">Supporto per SELinux completo presente.</string>
+		<string name="no_selinux">Nessun supporto per SELinux (libselinux assente).</string>
+		<string name="mtp_enabled">MTP abilitato</string>
+		<string name="mtp_crash">MTP si è arrestato. Non sarà più inizializzato all'avvio.</string>
+		<string name="decrypt_success">Decriptazione con password predefinita avvenuta con successo.</string>
+		<string name="unable_to_decrypt">Impossibile decriptare con password predefinita. Potresti dover formattare Data.</string>
+		<string name="generating_digest1" version="2">Generazione Digest in corso...</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * Generazione Digest in corso...</string>
+		<string name="digest_created" version="2"> * Digest creato.</string>
+		<string name="digest_error" version="2"> * Errore nell'Digest!</string>
+		<string name="digest_compute_error" version="2"> * Errore nel calcolo dell'Digest.</string>
+		<string name="current_date">(Data attuale)</string>
+		<string name="auto_generate">(Genera automaticamente)</string>
+		<string name="unable_to_locate_partition">Impossibile localizzare la partizione '{1}' per il calcolo delle dimensioni del backup.</string>
+		<string name="no_partition_selected">Nessuna partizione selezionata.</string>
+		<string name="total_partitions_backup"> * Numero di partizioni di cui eseguire il backup: {1}</string>
+		<string name="total_backup_size"> * Dimensione complessiva: {1}MB</string>
+		<string name="available_space"> * Spazio disponibile: {1}MB</string>
+		<string name="unable_locate_storage">Impossibile localizzare l'archivio.</string>
+		<string name="no_space">Lo spazio libero dell'archivio scelto è insufficiente.</string>
+		<string name="backup_started">[INIZIO DEL BACKUP]</string>
+		<string name="backup_folder"> * Cartella dei backup: {1}</string>
+		<string name="fail_backup_folder">Creazione della cartella dei backup fallita.</string>
+		<string name="avg_backup_fs">Velocità media di backup del filesystem: {1} MB/sec</string>
+		<string name="avg_backup_img">Velocità media di backup del drive: {1} MB/sec</string>
+		<string name="total_backed_size">[DIMENSIONE TOTALE SALVATA: {1} MB]</string>
+		<string name="backup_completed">[BACKUP COMPLETATO IN {1} SECONDI]</string>
+		<string name="restore_started">[INIZIO DEL RIPRISTINO]</string>
+		<string name="restore_folder">Cartella da cui ripristinare: '{1}'</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[Partizione {1} ripristinata ({2} secondi)]</string>
+		<string name="verifying_digest" version="2">Verifica Digest in corso...</string>
+		<string name="skip_digest" version="2">Saltando la verifica del Digest in base alle impostazioni stabilite.</string>
+		<string name="calc_restore">Calcolo dei dettagli del ripristino in corso...</string>
+		<string name="restore_read_only">Impossibile ripristinare {1} -- partizione montata in sola lettura.</string>
+		<string name="restore_unable_locate">Impossibile localizzare la partizione '{1}' per il ripristino.</string>
+		<string name="no_part_restore">Nessuna partizione selezionata per il ripristino.</string>
+		<string name="restore_part_count">Ripristino di {1} partizioni in corso...</string>
+		<string name="total_restore_size">Dimensione complessiva da ripristinare: {1}MB</string>
+		<string name="updating_system_details">Aggiornamento dei dettagli di System...</string>
+		<string name="restore_completed">[RIPRISTINO COMPLETATO IN {1} SECONDI]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">Errore durante l'apertura di: '{1}' ({2})</string>
+		<string name="unable_locate_part_backup_name">Impossibile localizzare la partizione dal nome di backup: '{1}'</string>
+		<string name="unable_find_part_path">Impossibile localizzare la partizione associata al percorso '{1}'</string>
+		<string name="update_part_details">Aggiornamento dei dettagli della partizione...</string>
+		<string name="update_part_details_done">...fatto</string>
+		<string name="wiping_dalvik">Pulizia delle cartelle della cache Dalvik...</string>
+		<string name="cleaned">Cartella pulita: {1}...</string>
+		<string name="dalvik_done">-- pulizia delle cartelle della cache Dalvik completata!</string>
+		<string name="no_andsec">Nessuna partizione Android protetta trovata.</string>
+		<string name="unable_to_locate">Impossibile localizzare {1}.</string>
+		<string name="wiping_datamedia">Pulizia dell'archivio interno -- /data/media...</string>
+		<string name="unable_to_mount">Impossibile montare {1}</string>
+		<string name="unable_to_mount_internal">Impossibile montare l'archivio interno</string>
+		<string name="unable_to_mount_storage">Impossibile montare l'archivio</string>
+		<string name="fail_decrypt">Decriptazione dei dati fallita.</string>
+		<string name="no_crypto_support">In questa build non è stato integrato alcun supporto per la crittografia.</string>
+		<string name="decrypt_success_dev">Dati decriptati con successo. Nuovo dispositivo di memoria: '{1}'</string>
+		<string name="decrypt_success_nodev">Dati decriptati con successo</string>
+		<string name="done">Fatto.</string>
+		<string name="start_partition_sd">Partizionamento scheda SD in corso...</string>
+		<string name="partition_sd_locate">Impossibile localizzare il dispositivo da partizionare.</string>
+		<string name="ext_swap_size">La dimensione complessiva di EXT e Swap eccede la capacità della scheda SD.</string>
+		<string name="remove_part_table">Rimozione della tabella delle partizioni in corso...</string>
+		<string name="unable_rm_part">Impossibile rimuovere la tabella delle partizioni.</string>
+		<string name="create_part">Creazione della partizione {1} in corso...</string>
+		<string name="unable_to_create_part">Impossibile creare la partizione {1}.</string>
+		<string name="format_sdext_as">Formattazione di sd-ext come {1}...</string>
+		<string name="part_complete">Partizionamento completato.</string>
+		<string name="unable_to_open">Impossibile aprire '{1}'.</string>
+		<string name="mtp_already_enabled">MTP è stato già abilitato</string>
+		<string name="mtp_fail">Impossibile abilitare MTP</string>
+		<string name="no_mtp">Supporto per MTP non incluso</string>
+		<string name="image_flash_start">[INIZIO INSTALLAZIONE DELL'IMMAGINE]</string>
+		<string name="img_to_flash">Immagine da installare: '{1}'</string>
+		<string name="flash_unable_locate">Impossibile localizzare la partizione '{1}' per l'installazione.</string>
+		<string name="no_part_flash">Nessuna partizione scelta per l'installazione.</string>
+		<string name="too_many_flash">Troppe partizioni scelte per l'installazione.</string>
+		<string name="invalid_flash">La partizione specificata non è valida.</string>
+		<string name="flash_done">[INSTALLAZIONE COMPLETATA]</string>
+		<string name="wiping">Pulizia di {1} in corso...</string>
+		<string name="repair_not_exist">{1} non esiste! Impossibile riparare!</string>
+		<string name="repairing_using">Riparazione di {1} utilizzando {2}...</string>
+		<string name="unable_repair">Impossibile riparare {1}.</string>
+		<string name="mount_data_footer">Impossibile montare la partizione Data. Footer criptato non trovato.</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">Impossibile creare la cartella '{1}' ({2}).</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">Impossibile montare '{1}' ({2}).</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">Impossibile smontare '{1}' ({2}).</string>
+		<string name="cannot_resize">Impossibile ridimensionare {1}.</string>
+		<string name="repair_resize">Riparazione di {1} prima del ridimensionamento.</string>
+		<string name="unable_resize">Impossibile ridimensionare {1}.</string>
+		<string name="no_digest_found" version="2">Nessun Digest trovato per '{1}'. Deseleziona "Abilita verifica Digest" per procedere con il ripristino.</string>
+		<string name="digest_fail_match" version="2">Il valore Digest non corrisponde per '{1}'.</string>
+		<string name="digest_matched" version="2">Digest trovato per '{1}'.</string>
+		<string name="fail_decrypt_tar">Decriptazione fallita per il file Tar '{1}'</string>
+		<string name="format_data_msg">Potresti dover riavviare la Recovery per accedere nuovamente a Data.</string>
+		<string name="format_data_err">Impossibile formattare al fine di rimuovere la crittografia.</string>
+		<string name="formatting_using">Formattazione di {1} tramite {2}...</string>
+		<string name="unable_to_wipe">Impossibile pulire {1}.</string>
+		<string name="cannot_wipe">La partizione {1} non può essere pulita.</string>
+		<string name="remove_all">Eliminazione di tutti i file in '{1}'...</string>
+		<string name="wiping_data">Pulizia di Data tralasciando /data/media...</string>
+		<string name="backing_up">Backup di {1} in corso...</string>
+		<string name="backup_storage_warning">Backup di {1} esso non include nessun file dell'archivio interno come foto o download.</string>
+		<string name="backing">Backup in corso...</string>
+		<string name="backup_size">La dimensione del backup di '{1}' è 0 byte.</string>
+		<string name="datamedia_fs_restore">ATTENZIONE: Il backup di Data è stato fatto mentre il filesystem era {1}! Il dispositivo potrebbe non avviarsi finché non reimposti il filesystem {1}.</string>
+		<string name="restoring">Ripristino di {1} in corso...</string>
+		<string name="restoring_hdr">Ripristino in corso...</string>
+		<string name="recreate_folder_err">Impossibile ripristinare la cartella {1}.</string>
+		<string name="img_size_err">La dimensione dell'immagine eccede la capacità del dispositivo</string>
+		<string name="flashing">Installazione di {1}...</string>
+		<string name="backup_folder_set">Cartella dei backup impostata in '{1}'</string>
+		<string name="locate_backup_err">Impossibile localizzare il backup '{1}'</string>
+		<string name="set_restore_opt">Impostazione opzioni di ripristino: '{1}':</string>
+		<string name="digest_check_skip" version="2">Tralascia controllo Digest: attivo</string>
+		<string name="ors_encrypt_restore_err">Impossibile utilizzare OpenRecoveryScript per ripristinare un backup criptato.</string>
+		<string name="mounting">Montaggio</string>
+		<string name="unmounting">Smontaggio</string>
+		<string name="mounted">'{1}' montata</string>
+		<string name="unmounted">'{1}' smontata</string>
+		<string name="setting">Impostazione di '{1}' a '{2}'</string>
+		<string name="setting_empty">Impostazione di '{1}' a vuoto</string>
+		<string name="making_dir1">Creazione cartella</string>
+		<string name="making_dir2">Creazione cartella: '{1}'</string>
+		<string name="running_command">Esecuzione comando</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">Avvio di ADB Sideload...</string>
+		<string name="need_new_adb">Necessiti di adb 1.0.32 o più recente, per inviare dati tramite Sideload.</string>
+		<string name="no_pwd">Nessuna password fornita.</string>
+		<string name="done_ors">Elaborazione script terminata</string>
+		<string name="injecttwrp">Installazione di TWRP in Boot tramite TWRP Inject...</string>
+		<string name="zip_err">Errore durante l'installazione dello Zip '{1}'</string>
+		<string name="installing_zip">Installazione dello Zip '{1}' in corso...</string>
+		<string name="select_backup_opt">Impostazione opzioni di backup:</string>
+		<string name="compression_on">La compressione è attiva</string>
+		<string name="digest_off" version="2">La generazione di Digest è disattiva</string>
+		<string name="backup_fail">Backup non riuscito</string>
+		<string name="backup_clean">Backup non riuscito. Pulizia della cartella dei backup in corso...</string>
+		<string name="running_recovery_commands">Esecuzione dei comandi di ripristino...</string>
+		<string name="recovery_commands_complete">Esecuzione dei comandi completata</string>
+		<string name="running_ors">Esecuzione di OpenRecoveryScript...</string>
+		<string name="ors_complete">Esecuzione di OpenRecoveryScript completata</string>
+		<string name="invalid_zip_format">File zip non valido!</string>
+		<string name="check_for_digest" version="2">Controllo sull'Digest in corso...</string>
+		<string name="fail_sysmap">Impossibile mappare '{1}'</string>
+		<string name="verify_zip_sig">Verifica della firma dello Zip in corso...</string>
+		<string name="verify_zip_fail">Verifica della firma non riuscita!</string>
+		<string name="verify_zip_done">Verifica della firma completata con successo.</string>
+		<string name="zip_corrupt">Il file Zip è corrotto!</string>
+		<string name="no_digest" version="2">Nessun Digest trovato: la verifica non sarà eseguita</string>
+		<string name="digest_fail" version="2">L'Digest non corrisponde</string>
+		<string name="digest_match" version="2">L'Digest corrisponde</string>
+		<string name="pid_signal">Il processo {1} è terminato con segnale: {2}</string>
+		<string name="pid_error">Il processo {1} è terminato con ERRORE: {2}</string>
+		<string name="install_dumlock">Installazione di HTC Dumlock in System...</string>
+		<string name="dumlock_restore">Ripristino del Boot originale...</string>
+		<string name="dumlock_reflash">Reinstallazione della Recovery in Boot...</string>
+		<string name="run_script">Esecuzione dello script {1}...</string>
+		<string name="rename_stock">Il file della Recovery stock, in System, è stato rinominato per prevenire la sostituzione di TWRP da paete della ROM.</string>
+		<string name="split_backup">Divisione del backup in archivi multipli...</string>
+		<string name="backup_error">Errore nella creazione del backup.</string>
+		<string name="restore_error">Errore durante il ripristino.</string>
+		<string name="split_thread">Divisione del thread {1} nell'archivio {2}</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">File %llu di %llu, %i%%</string>
+		<string name="size_progress">%lluMB di %lluMB, %i%%</string>
+		<string name="decrypt_cmd">Tentativo di decriptare Data da riga di comando.</string>
+		<string name="base_pkg_err">Errore nel caricamento dei pacchetti base.</string>
+		<string name="simulating">Simulazione azioni in corso...</string>
+		<string name="backup_cancel">Backup annullato</string>
+		<string name="config_twrp">Configurazione di TWRP in corso...</string>
+		<string name="config_twrp_err">Impossibile configurare TWRP con questo kernel.</string>
+		<string name="copy_log">Log della Recovery copiato in {1}.</string>
+		<string name="max_queue">È stata raggiunta la dimensione massima della coda di Zip!</string>
+		<string name="min_queue">È stata raggiunta la dimensione minima della coda di Zip!</string>
+		<string name="screenshot_saved">Lo screenshot è stato salvato in {1}</string>
+		<string name="screenshot_err">Cattura dello screenshot non riuscita!</string>
+		<string name="zip_wipe_cache">Uno o più Zip ha richiesto la pulizia della Cache -- La Cache sarà pulita immediatamente.</string>
+		<string name="and_sec_wipe_err">Impossibile pulire android_secure</string>
+		<string name="dalvik_wipe_err">Pulizia della cache Dalvik fallita</string>
+		<string name="auto_gen">(Genera automaticamente)</string>
+		<string name="curr_date">(Data attuale)</string>
+		<string name="backup_name_len">Il nome del backup eccede la lunghezza massima.</string>
+		<string name="backup_name_invalid">Il nome del backup '{1}' contiene uno o più caratteri non validi: '{1}'</string>
+		<string name="no_real_sdcard">Questo dispositivo non possiede una scheda SD reale! Interruzione immediata!</string>
+		<string name="cancel_sideload">Annullamento di ADB sideload...</string>
+		<string name="change_fs_err">Errore nella modifica del filesystem.</string>
+		<string name="theme_ver_err">La versione del tema personalizzato non corrisponde alla versione di TWRP. Sarà utilizzato il tema predefinito.</string>
+		<string name="up_a_level">(Livello superiore)</string>
+		<string name="install_reboot" version="2">Riavvio in %tw_sleep% secondo(i)</string>
+		<string name="adbbackup_error">Errore con ADB Backup. Abbandono..."</string>
+		<string name="adbbackup_control_error">Impossibile scrivere nel canale di controllo adb</string>
+		<string name="twrp_adbbu_option">--twrp opzione richiesta per abilitare twrp adb backup</string>
+		<string name="partition_not_found">percorso: {1} non trovato nella lista delle partizioni</string>
+		<string name="copy_kernel_log">Log del kernel copiato in {1}</string>
+		<string name="include_kernel_log">Includi Log del Kernel</string>
+		<string name="unable_set_boot_slot">Errore nel cambio slot d'avvio del bootloader in {1}</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/nl.xml b/gui/theme/common/languages/nl.xml
new file mode 100644
index 0000000..37e712a
--- /dev/null
+++ b/gui/theme/common/languages/nl.xml
@@ -0,0 +1,756 @@
+<?xml version="1.0" encoding="utf-8"?>
+<language>
+	<display>Nederlands</display>
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource filename="RobotoCondensed-Regular.ttf" name="font_l" scale="100" type="fontoverride"/>
+		<resource filename="RobotoCondensed-Regular.ttf" name="font_m" scale="100" type="fontoverride"/>
+		<resource filename="RobotoCondensed-Regular.ttf" name="font_s" scale="100" type="fontoverride"/>
+		<resource filename="DroidSansMono.ttf" name="fixed" scale="100" type="fontoverride"/>
+		<!-- Partition display names -->
+		<string name="system">System</string>
+		<string name="system_image">System-image</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Vendor-image</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="data_backup">Data (exclusief /data/media)</string>
+		<string name="sdcard">SD-kaart</string>
+		<string name="internal">Interne opslag</string>
+		<string name="microsd">Micro SD-kaart</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik- / ART-cache</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Geadopteerde Data</string>
+		<string name="adopted_storage">Geadopteerde opslag</string>
+		<string name="autostorage">Opslag</string>
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% ° C</string>
+		<string name="battery_pct">Accu: %tw_battery%</string>
+		<string name="sort_by_name">Op naam sorteren</string>
+		<string name="sort_by_date">Op datum sorteren</string>
+		<string name="sort_by_size">Op grootte sorteren</string>
+		<string name="sort_by_name_only">Naam</string>
+		<string name="sort_by_date_only">Datum</string>
+		<string name="sort_by_size_only">Grootte</string>
+		<string name="tab_general">ALGEMEEN</string>
+		<string name="tab_options">OPTIES</string>
+		<string name="tab_backup">BACK-UP</string>
+		<string name="tab_time_zone">TIJDZONE</string>
+		<string name="tab_screen">SCHERM</string>
+		<string name="tab_vibration">TRILLEN</string>
+		<string name="tab_language">TAAL</string>
+		<string name="install_btn">Installeren</string>
+		<string name="wipe_btn">Wissen</string>
+		<string name="backup_btn">Back-uppen</string>
+		<string name="restore_btn">Herstellen</string>
+		<string name="mount_btn">Koppelen</string>
+		<string name="settings_btn">Instellingen</string>
+		<string name="advanced_btn">Geavanceerd</string>
+		<string name="reboot_btn">Herstarten</string>
+		<string name="files_btn">Bestanden</string>
+		<string name="copy_log_btn">Log kopiëren</string>
+		<string name="select_type_hdr">Type selecteren</string>
+		<string name="install_zip_hdr">Zipbestand installeren</string>
+		<string name="install_zip_btn">Zipbestand installeren</string>
+		<string name="install_image_hdr">Image installeren</string>
+		<string name="install_image_btn">Image installeren</string>
+		<string name="install_select_file_hdr">Bestand selecteren</string>
+		<string name="file_selector_folders_hdr">Mappen</string>
+		<string name="select_file_from_storage">Selecteer bestand in %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB-sideload</string>
+		<string name="install_hdr">Installeren</string>
+		<string name="select_storage_hdr">Opslag selecteren</string>
+		<string name="select_storage_btn">Opslag selecteren</string>
+		<string name="queue_hdr">Wachtrij</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% van max. 10 bestanden in de wachtrij</string>
+		<string name="zip_queue_count_s">Bestand %tw_zip_queue_count% van 10:</string>
+		<string name="zip_warn1">Deze toepassing kan onverenigbare software</string>
+		<string name="zip_warn2">installeren die het apparaat onbruikbaar maakt.</string>
+		<string name="zip_back_cancel">Klik op Terug om het toevoegen van het zipbestand te annuleren.</string>
+		<string name="zip_back_clear">Klik op Terug om de wachtrij leeg te maken.</string>
+		<string name="folder">Map:</string>
+		<string name="file">Bestand:</string>
+		<string name="zip_sig_chk">Ondertekening zipbestand verifiëren</string>
+		<string name="inject_twrp_chk">TWRP injecteren na installatie</string>
+		<string name="install_reboot_chk">Apparaat herstarten na installatie</string>
+		<string name="options_hdr">Opties</string>
+		<string name="confirm_flash_hdr">Flashen bevestigen</string>
+		<string name="zip_queue">Wachtrij:</string>
+		<string name="options">Instellingen:</string>
+		<string name="swipe_confirm">   Bevestigen</string>
+		<string name="zip_add_btn">Zipbestanden toevoegen</string>
+		<string name="zip_clear_btn">Wachtrij leegmaken</string>
+		<string name="install_zip_count_hdr">Zipbestand %tw_zip_index% van %tw_zip_queue_count% installeren</string>
+		<string name="installing_zip_xml">Zipbestand wordt geïnstalleerd: %tw_file%</string>
+		<string name="failed">Mislukt</string>
+		<string name="successful">Geslaagd</string>
+		<string name="install_failed">Installatie mislukt</string>
+		<string name="install_successful">Installatie geslaagd</string>
+		<string name="wipe_cache_dalvik_btn">Cache &amp; Dalvik wissen</string>
+		<string name="wipe_dalvik_btn">Dalvik wissen</string>
+		<string name="reboot_system_btn">Systeem herstarten</string>
+		<string name="install_sel_target">Doelpartitie selecteren</string>
+		<string name="flash_image_select">Partitie selecteren om op te installeren:</string>
+		<string name="target_partition">Doelpartitie:</string>
+		<string name="flashing_image">Image wordt geïnstalleerd...</string>
+		<string name="image_flashed">Image geïnstalleerd</string>
+		<string name="wipe_cache_dalvik_confirm">Cache &amp; Dalvik wissen?</string>
+		<string name="wipe_dalvik_confirm">Dalvik wissen?</string>
+		<string name="wiping_cache_dalvik">Cache &amp; Dalvik worden gewist...</string>
+		<string name="wipe_cache_dalvik_complete">Cache &amp; Dalvik gewist</string>
+		<string name="wipe_dalvik_complete">Dalvik gewist</string>
+		<string name="swipe_wipe">Veeg om te wissen</string>
+		<string name="swipe_wipe_s">   Wissen</string>
+		<string name="no_os1">Geen besturingssysteem geïnstalleerd! Weet u</string>
+		<string name="no_osrb">zeker dat u wilt herstarten?</string>
+		<string name="no_ospo">zeker dat u uw apparaat wilt uitschakelen?</string>
+		<string name="rebooting">Herstarten...</string>
+		<string name="swipe_reboot">Veeg om te herstarten</string>
+		<string name="swipe_reboot_s">   Herstarten</string>
+		<string name="reboot_install_app_hdr">TWRP-app installeren?</string>
+		<string name="reboot_install_app1">Wilt u de officiële TWRP-app installeren?</string>
+		<string name="reboot_install_app2">De app kan controleren of er een nieuwere versie van TWRP beschikaar is.</string>
+		<string name="reboot_install_app_prompt_install">Installatie TWRP-app aanbieden</string>
+		<string name="reboot_install_app_system">Installeren als System-app</string>
+		<string name="reboot_installing_app">App wordt geïnstalleerd...</string>
+		<string name="swipe_to_install_app">Veeg om TWRP-app te installeren</string>
+		<string name="uninstall_twrp_system_app">TWRP-app verwijderen van System</string>
+		<string name="uninstall_twrp_system_app_confirm">TWRP-app verwijderen van System?</string>
+		<string name="uninstalling_twrp_system_app">TWRP-app wordt verwijderd van System...</string>
+		<string name="uninstall_twrp_system_app_complete">TWRP-app verwijderd</string>
+		<string name="swipe_flash">Veeg om te installeren</string>
+		<string name="confirm_action">Actie bevestigen</string>
+		<string name="back_cancel">Klik op Terug om te annuleren.</string>
+		<string name="cancel_btn">Annuleren</string>
+		<string name="wipe_hdr">Wissen</string>
+		<string name="factory_reset_hdr">Fabrieksinstellingen herstellen</string>
+		<string name="factory_reset_btn">Fabrieksinstellingen herstellen</string>
+		<string name="factory_reset1">Wist Data, Cache en Dalvik</string>
+		<string name="factory_reset2">(exclusief interne opslag)</string>
+		<string name="factory_reset3">Meestal kan worden volstaan</string>
+		<string name="factory_reset4">met dit type opschoning.</string>
+		<string name="factory_reset5">(exclusief gebruikers/vergrendelscherm)</string>
+		<string name="factory_resetting">Fabrieksinstellingen worden hersteld...</string>
+		<string name="advanced_wipe_hdr">Geavanceerd wissen</string>
+		<string name="advanced_wipe_btn">Geavanceerd wissen</string>
+		<string name="wipe_enc_confirm">Versleuteling van Data verwijderen?</string>
+		<string name="formatting_data">Data wordt geformatteerd...</string>
+		<string name="swipe_format_data">Veeg om Data te formatteren</string>
+		<string name="swipe_format_data_s">   Data formatteren</string>
+		<string name="factory_reset_complete">Fabrieksinstellingen hersteld</string>
+		<string name="sel_part_hdr">Partitie(s) selecteren</string>
+		<string name="wipe_sel_confirm">Geselecteerde partities wissen?</string>
+		<string name="wiping_part">Partities worden gewist...</string>
+		<string name="wipe_complete">Wissen voltooid</string>
+		<string name="sel_part_wipe">Partitie(s) selecteren om te wissen:</string>
+		<string name="invalid_part_sel">Ongeldige partitie geselecteerd</string>
+		<string name="format_data_hdr">Data formatteren</string>
+		<string name="format_data_btn">Data formatteren</string>
+		<string name="format_data_ptr1">Het formatteren van Data verwijdert al uw apps,</string>
+		<string name="format_data_ptr2">back-ups, foto's, video's en andere media, en</string>
+		<string name="format_data_ptr3">verwijdert de versleuteling van de interne opslag.</string>
+		<string name="format_data_adopted">De geadopteerde opslag inbegrepen</string>
+		<string name="format_data_lcp1">Data formatteren wist alle apps, back-ups, foto's, videos en andere media,</string>
+		<string name="format_data_lcp2">en verwijdert de versleuteling van de interne opslag.</string>
+		<string name="format_data_wtc1">Data formatteren verwijdert al uw apps,</string>
+		<string name="format_data_wtc2">back-ups en media. Dit kan niet ongedaan worden gemaakt.</string>
+		<string name="format_data_undo">Dit kan niet ongedaan worden gemaakt.</string>
+		<string name="format_data_complete">Formatteren van Data voltooid</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">Typ 'yes' om verder te gaan. Klik op Terug om te annuleren.</string>
+		<string name="part_opt_hdr">Opties voor partitioneren: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Actie selecteren</string>
+		<string name="file_sys_opt">Opties voor bestandssysteem</string>
+		<string name="partition">Partitie: %tw_partition_name%</string>
+		<string name="part_mount_point">Koppelpunt: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Bestandssysteem: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Aanwezig: Ja</string>
+		<string name="part_present_no">Aanwezig: Nee</string>
+		<string name="part_removable_yes">Verwijderbaar: Ja</string>
+		<string name="part_removable_no">Verwijderbaar: Nee</string>
+		<string name="part_size">Grootte: % tw_partition_size % MB</string>
+		<string name="part_used">Gebruikt: % tw_partition_used % MB</string>
+		<string name="part_free">Vrij: % tw_partition_free % MB</string>
+		<string name="part_backup_size">Back-upgrootte: % tw_partition_backup_size % MB</string>
+		<string name="resize_btn">Grootte bestandssysteem aanpassen</string>
+		<string name="resize_btn_s">Grootte aanpassen</string>
+		<string name="resize_confirm">Grootte van %tw_partition_name% aanpassen?</string>
+		<string name="resizing">Grootte wordt aangepast...</string>
+		<string name="resize_complete">Aanpassen voltooid</string>
+		<string name="swipe_resize">Veeg om de grootte aan te passen</string>
+		<string name="swipe_resize_s">   Grootte aanpassen</string>
+		<string name="repair_btn">Bestandssysteem repareren</string>
+		<string name="repair_btn_s">Repareren</string>
+		<string name="repair_confirm">%tw_partition_name% repareren?</string>
+		<string name="repairing">Bestandssysteem wordt gerepareerd...</string>
+		<string name="repair_complete">Reparatie voltooid</string>
+		<string name="swipe_repair">Veeg om te repareren</string>
+		<string name="swipe_repair_s">   Repareren</string>
+		<string name="change_fs_btn">Type bestandssysteem wijzigen</string>
+		<string name="change_fs_btn_s">Wijzigen</string>
+		<string name="change_fs_for_hdr">Type bestandssysteem wijzigen voor: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Partitie: %tw_partition_name% &gt; selecteer bestandssysteem</string>
+		<string name="change_fs_warn1">Sommige ROMs of kernels missen mogelijk ondersteuning voor sommige</string>
+		<string name="change_fs_warn2">soorten bestandssystemen. Voorzichtigheid betrachten!</string>
+		<string name="change_fs_confirm">%tw_partition_name% wijzigen?</string>
+		<string name="formatting">Wordt geformatteerd...</string>
+		<string name="format_complete">Formatteren voltooid</string>
+		<string name="swipe_change_fs">Veeg om te wijzigen</string>
+		<string name="swipe_change_s">   Wijzigen</string>
+		<string name="back_btn">Terug</string>
+		<string name="wipe_enc_btn">Versleuteling verwijderen</string>
+		<string name="swipe_factory_reset">Veeg om herstel fabrieksinst.</string>
+		<string name="repair_change_btn">Bestandssysteem repareren of type ervan veranderen</string>
+		<string name="storage_hdr">Opslag: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Back-uppen</string>
+		<string name="backup_confirm_hdr">Back-up bevestigen</string>
+		<string name="encryption_tab">VERSLEUTELING</string>
+		<string name="encryption">Versleuteling:</string>
+		<string name="name">Naam:</string>
+		<string name="sel_part_backup">Partitie(s) selecteren om te back-uppen:</string>
+		<string name="storage">Opslag:</string>
+		<string name="enc_disabled">uitgeschakeld - stel een wachtwoord in om in te schakelen</string>
+		<string name="enc_enabled">ingeschakeld</string>
+		<string name="enable_backup_comp_chk">Compressie inschakelen</string>
+		<string name="skip_digest_backup_chk" version="2">Genereren van digest tijdens back-up overslaan</string>
+		<string name="disable_backup_space_chk" version="2">Controle vrije ruimte uitschakelen</string>
+		<string name="current_boot_slot">Huidige slot: %tw_active_slot%</string>
+		<string name="boot_slot_a">Slot A</string>
+		<string name="boot_slot_b">Slot B</string>
+		<string name="changing_boot_slot">Opstartslot wordt gewijzigd</string>
+		<string name="changing_boot_slot_complete">Wijzigen opstartslot voltooid</string>
+		<string name="refresh_sizes_btn">Grootte verversen</string>
+		<string name="swipe_backup">Veeg om te back-uppen</string>
+		<string name="append_date_btn">Datum toevoegen</string>
+		<string name="backup_name_exists">Een back-up met die naam bestaat al!</string>
+		<string name="encrypt_backup">Back-up versleutelen?</string>
+		<string name="enter_pass">Wachtwoord invoeren:</string>
+		<string name="enter_pass2">Wachtwoord opnieuw invoeren:</string>
+		<string name="pass_not_match">Wachtwoorden komen niet overeen!</string>
+		<string name="partitions">Partities:</string>
+		<string name="disabled">Uitgeschakeld</string>
+		<string name="enabled">Ingeschakeld</string>
+		<string name="backup_name_hdr">Naam back-up</string>
+		<string name="progress">Voortgang:</string>
+		<string name="backup_complete">Back-up voltooid</string>
+		<string name="restore_hdr">Herstellen</string>
+		<string name="sel_backup_hdr">Back-up selecteren</string>
+		<string name="restore_sel_store_hdr">Back-up selecteren van %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Pakket selecteren om te herstellen:</string>
+		<string name="restore_enc_backup_hdr">Versleutelde back-up</string>
+		<string name="restore_dec_fail">Wachtwoord mislukt. Probeer opnieuw!</string>
+		<string name="del_backup_btn">Back-up wissen</string>
+		<string name="del_backup_confirm">Back-up wissen?</string>
+		<string name="del_backup_confirm2">Dit kan niet ongedaan worden gemaakt!</string>
+		<string name="deleting_backup">Back-up wordt gewist...</string>
+		<string name="backup_deleted">Back-up gewist</string>
+		<string name="swipe_delete">Veeg om te wissen</string>
+		<string name="swipe_delete_s">   Wissen</string>
+		<string name="restore_try_decrypt">Versleutelde back-up - Wordt ontsleuteld</string>
+		<string name="restore_try_decrypt_s">Poging tot ontsleutelen</string>
+		<string name="restore_backup_date">Back-up gemaakt op %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Partitie(s) selecteren om te herstellen:</string>
+		<string name="restore_enable_digest_chk" version="2">Digestcontrole back-upbestanden inschakelen</string>
+		<string name="restore_complete">Herstellen voltooid</string>
+		<string name="swipe_restore">Veeg om te herstellen</string>
+		<string name="swipe_restore_s">   Herstellen</string>
+		<string name="rename_backup_hdr">Back-up hernoemen</string>
+		<string name="rename_backup_confirm">Back-up hernoemen?</string>
+		<string name="rename_backup_confirm2">Dit kan niet ongedaan worden gemaakt!</string>
+		<string name="renaming_backup">Back-up wordt hernoemd...</string>
+		<string name="rename_backup_complete">Back-up hernoemd</string>
+		<string name="swipe_to_rename">Veeg om te hernoemen</string>
+		<string name="swipe_rename">   Hernoemen</string>
+		<string name="confirm_hdr">Bevestigen</string>
+		<string name="mount_hdr">Koppelen</string>
+		<string name="mount_sel_part">Partitie(s) selecteren om te koppelen:</string>
+		<string name="mount_sys_ro_chk">System-partitie in onbeschrijfbaar-modus koppelen</string>
+		<string name="mount_sys_ro_s_chk">System-partitie onbeschrijfbaar</string>
+		<string name="decrypt_data_btn">Data ontsleutelen</string>
+		<string name="disable_mtp_btn">MTP uitschakelen</string>
+		<string name="enable_mtp_btn">MTP inschakelen</string>
+		<string name="mount_usb_storage_btn">USB-opslag koppelen</string>
+		<string name="usb_storage_hdr">USB-opslag</string>
+		<string name="usb_stor_mnt1">USB-opslag gekoppeld</string>
+		<string name="usb_stor_mnt2">Het apparaat dient eerst veilig te worden</string>
+		<string name="usb_stor_mnt3">verwijderd van de computer alvorens te ontkoppelen!</string>
+		<string name="unmount_btn">Ontkoppelen</string>
+		<string name="rb_system_btn">Systeem</string>
+		<string name="rb_poweroff_btn">Uitzetten</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Download</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Wordt uitgeschakeld...</string>
+		<string name="swipe_power_off">Veeg om uit te schakelen</string>
+		<string name="swipe_power_off_s">Uitschakelen</string>
+		<string name="sys_ro_hdr">Ongewijzigde System-partitie</string>
+		<string name="sys_ro_keep">System-partitie onbeschijfbaar laten?</string>
+		<string name="sys_rop1">TWRP kan de System-partitie ongewijzigd laten</string>
+		<string name="sys_rop2">om het ontvangen van officiële updates makkelijker te maken.</string>
+		<string name="sys_rop3">TWRP kan het overschrijven van zichzelf door de oorspronkelijke</string>
+		<string name="sys_rop4">ROM niet voorkomen en zal ook niet aanbieden het apparaat te rooten.</string>
+		<string name="sys_rop5">Het installeren van zipbestanden en het gebruik van adb</string>
+		<string name="sys_rop6">kunnen de System-partitie alsnog doen veranderen.</string>
+		<string name="sys_rol1">TWRP kan de System-partitie ongewijzigd laten om het ontvangen van officiële updates makkelijker te maken.</string>
+		<string name="sys_rol2">TWRP kan het overschrijven van zichzelf door de oorspronkelijke ROM niet voorkomen en zal ook niet aanbieden het apparaat te rooten.</string>
+		<string name="sys_rol3">Het installeren van zipbestanden en het gebruik van adb kunnen de System-partitie alsnog doen veranderen.</string>
+		<string name="sys_ro_never_show_chk">Dit scherm niet meer weergeven tijdens het opstarten</string>
+		<string name="sys_ro_keep_ro_btn">Onbeschrijfbaar laten</string>
+		<string name="swipe_allow_mod">Veeg om wijzigingen toe te staan</string>
+		<string name="swipe_allow_mod_s">Wijzigingen toestaan</string>
+		<string name="settings_hdr">Instellingen</string>
+		<string name="settings_gen_hdr">Algemeen</string>
+		<string name="settings_gen_s_hdr">Algemeen</string>
+		<string name="settings_gen_btn">Algemeen</string>
+		<string name="use_rmrf_chk">'rm -rf' uitvoeren i.p.v. formatteren</string>
+		<string name="use24clock_chk">24-uurs klok</string>
+		<string name="rev_navbar_chk">Omgekeerde volgorde navbar-knoppen</string>
+		<string name="simact_chk">Simuleren acties t.b.v. testen thema</string>
+		<string name="simfail_chk">Mislukken van acties simuleren</string>
+		<string name="ctr_navbar_rdo">Navbar-knoppen centreren</string>
+		<string name="lft_navbar_rdo">Navbar-knoppen links uitlijnen</string>
+		<string name="rht_navbar_rdo">Navbar-knoppen rechts uitlijnen</string>
+		<string name="restore_defaults_btn">Standaardwaarden herst.</string>
+		<string name="settings_tz_btn">Tijdzone</string>
+		<string name="settings_screen_btn">Scherm</string>
+		<string name="settings_screen_bright_btn">Schermhelderheid</string>
+		<string name="vibration_disabled">Trillen is uitgeschakeld voor dit apparaat</string>
+		<string name="settings_vibration_btn">Trillen</string>
+		<string name="settings_language_btn">Taal</string>
+		<string name="time_zone_hdr">Tijdzone</string>
+		<string name="sel_tz_list">Tijdzone selecteren:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) Samoa, Midway-eilanden</string>
+		<string name="utcm10">(UTC -10) Hawaii</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm8">(UTC -8) Pacific-tijd</string>
+		<string name="utcm7">(UTC -7) Mountain-tijd</string>
+		<string name="utcm6">(UTC -6) Central-tijd, Mexico</string>
+		<string name="utcm5">(UTC -5) Eastern-tijd</string>
+		<string name="utcm4">(UTC -4) Atlantische tijd</string>
+		<string name="utcm3">(UTC -3) Brazilië, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Mid-Atlantisch</string>
+		<string name="utcm1">(UTC -1) Azoren, Kaapverdië</string>
+		<string name="utc0">(UTC  0) Londen, Dublin, Lissabon</string>
+		<string name="utcp1">(UTC +1) Berlijn, Brussel, Parijs</string>
+		<string name="utcp2">(UTC +2) Athene, Istanboel, Zuid-Afrika</string>
+		<string name="utcp3">(UTC +3) Moskou, Bagdad</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC +5) Jekaterinenburg, Islamabad</string>
+		<string name="utcp6">(UTC +6) Alma-Ata, Dhaka, Colombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp8">(UTC +8) Beijing, Singapore, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokio, Seoul, Yakutsk</string>
+		<string name="utcp10">(UTC +10) Oost-Australië, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Salomonseilanden</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fiji</string>
+		<string name="sel_tz_offset">Compensatie selecteren (meestal 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Geen</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Zomertijd (DST) gebruiken</string>
+		<string name="curr_tz">Huidige tijdzone: %tw_time_zone%</string>
+		<string name="curr_tz_s">Huidige tijdzone:</string>
+		<string name="set_tz_btn">Tijdzone instellen</string>
+		<string name="settings_screen_hdr">Scherm</string>
+		<string name="settings_screen_timeout_hdr">Schermverlooptijd</string>
+		<string name="enable_timeout_chk">Schermverloop inschakelen</string>
+		<string name="screen_to_slider">Schermverlooptijd in seconden:</string>
+		<string name="screen_to_slider_s">Schermverlooptijd in seconden (0 = uitgeschakeld): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Instelling schermverlooptijd niet beschikbaar</string>
+		<string name="screen_bright_slider">Helderheid: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Instelling helderheid niet beschikbaar</string>
+		<string name="vibration_hdr">Trillen</string>
+		<string name="button_vibration_hdr">Trillen knoppen</string>
+		<string name="kb_vibration_hdr">Trillen toetsenbord</string>
+		<string name="act_vibration_hdr">Trillen acties</string>
+		<string name="button_vibration">Trillen knoppen:</string>
+		<string name="kb_vibration">Trillen toetsenbord:</string>
+		<string name="act_vibration">Trillen acties:</string>
+		<string name="select_language">Taal selecteren:</string>
+		<string name="sel_lang_btn">Taal selecteren</string>
+		<string name="set_language_btn">Taal instellen</string>
+		<string name="advanced_hdr">Geavanceerd</string>
+		<string name="copy_log_confirm">Logbestand naar SD-kaart kopiëren?</string>
+		<string name="copying_log" version="2">Log naar SD-kaart kopiëren...</string>
+		<string name="copy_log_complete" version="2">Logbestand gekopieerd</string>
+		<string name="fix_context_btn">Contexten repareren</string>
+		<string name="part_sd_btn">SD-kaart partitioneren</string>
+		<string name="part_sd_s_btn">SD-kaart</string>
+		<string name="file_manager_btn">Bestandsbeheer</string>
+		<string name="language_hdr">Taal</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="reload_theme_btn">Thema herladen</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">TWRP injecteren</string>
+		<string name="inject_twrp_confirm">TWRP opnieuw injecteren?</string>
+		<string name="injecting_twrp">TWRP opnieuw injecteren...</string>
+		<string name="inject_twrp_complete">TWRP geïnjecteerd</string>
+		<string name="swipe_to_confirm">Veeg om te bevestigen</string>
+		<string name="part_sd_hdr">SD-kaart partitioneren</string>
+		<string name="invalid_partsd_sel">U moet een verwisselbaar apparaat selecteren</string>
+		<string name="part_sd_lose">U raakt alle bestanden op uw SD-kaart kwijt!</string>
+		<string name="part_sd_undo">Deze actie kan niet ongedaan gemaakt worden!</string>
+		<string name="part_sd_ext_sz">EXT-grootte:</string>
+		<string name="part_sd_swap_sz">Swapgrootte:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Bestandssysteem:</string>
+		<string name="swipe_part_sd">Veeg om te partitioneren</string>
+		<string name="swipe_part_sd_s">Partitie</string>
+		<string name="partitioning_sd">SD-kaart wordt gepartitioneerd...</string>
+		<string name="partitioning_sd2">Dit duurt een paar minuten.</string>
+		<string name="part_sd_complete">Partitioneren voltooid</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Oorspronkelijk bootimage herstellen</string>
+		<string name="dumlock_restore_confirm">Oorspronkelijk bootimage herstellen?</string>
+		<string name="dumlock_restoring">Oorspronkelijk bootimage wordt hersteld...</string>
+		<string name="dumlock_restore_complete">Oorspronkelijk bootimage hersteld</string>
+		<string name="dumlock_reflash_btn">Recovery opnieuw flashen</string>
+		<string name="dumlock_reflash_confirm">Recovery opnieuw naar boot flashen?</string>
+		<string name="dumlock_reflashing">Recovery wordt naar boot geflasht...</string>
+		<string name="dumlock_reflash_complete">Flashen recovery naar boot voltooid</string>
+		<string name="dumlock_install_btn">HTC Dumlock installeren</string>
+		<string name="dumlock_install_confirm">HTC Dumlock-bestanden in ROM installeren?</string>
+		<string name="dumlock_installing">HTC Dumlock wordt geïnstalleerd...</string>
+		<string name="dumlock_install_complete">Installatie HTC Dumlock voltooid</string>
+		<string name="swipe_to_unlock">Veeg om te ontgrendelen</string>
+		<string name="swipe_unlock">   Ontgrendelen</string>
+		<string name="fm_hdr">Bestandsbeheer</string>
+		<string name="fm_sel_file">Een bestand of map selecteren</string>
+		<string name="fm_type_folder">Map</string>
+		<string name="fm_type_file">Bestand</string>
+		<string name="fm_choose_act">Actie selecteren</string>
+		<string name="fm_selected">%tw_fm_type% geselecteerd:</string>
+		<string name="fm_copy_btn">Kopiëren</string>
+		<string name="fm_copy_file_btn">Bestand kopiëren</string>
+		<string name="fm_copy_folder_btn">Map kopiëren</string>
+		<string name="fm_copying">Wordt gekopieerd</string>
+		<string name="fm_move_btn">Verplaatsen</string>
+		<string name="fm_moving">Wordt verplaatst</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Verwijderen</string>
+		<string name="fm_deleting">Wordt verwijderd</string>
+		<string name="fm_rename_btn">Hernoemen</string>
+		<string name="fm_rename_file_btn">Bestand hernoemen</string>
+		<string name="fm_rename_folder_btn">Map hernoemen</string>
+		<string name="fm_renaming">Wordt hernoemd</string>
+		<string name="fm_sel_dest">Doelmap selecteren</string>
+		<string name="fm_sel_curr_folder">Huidige map selecteren</string>
+		<string name="fm_rename_hdr">Hernoemen</string>
+		<string name="fm_set_perms_hdr">Machtigingen instellen</string>
+		<string name="fm_perms">Machtigingen:</string>
+		<string name="fm_complete">Bestandsbewerking voltooid</string>
+		<string name="fm_open_terminal_btn">Terminal hier openen</string>
+		<string name="fm_edit_file">Bestand bewerken</string>
+		<string name="decrypt_data_hdr">Data-partitie ontsleutelen</string>
+		<string name="decrypt_data_enter_pass">Wachtwoord invoeren.</string>
+		<string name="decrypt_data_failed">Wachtwoord mislukt. Probeer opnieuw!</string>
+		<string name="decrypt_data_failed_pattern">Patroon mislukt. Probeer opnieuw!</string>
+		<string name="decrypt_data_enter_pattern">Patroon invoeren.</string>
+		<string name="decrypt_data_trying">Poging tot ontsleutelen</string>
+		<string name="decrypt_data_vold_os_missing">Vereiste bestanden ontbreken voor ontsleutelen vold: {1}</string>
+		<string name="term_hdr">Terminal-commando's</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">KILL</string>
+		<string name="term_sel_folder_hdr">Naar beginmap gaan</string>
+		<string name="adb_sideload_hdr">ADB-sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Dalvik-cache legen</string>
+		<string name="sideload_wipe_cache_chk">Cache legen</string>
+		<string name="swipe_to_sideload">Veeg om sideload te starten</string>
+		<string name="swipe_sideload">   Start</string>
+		<string name="sideload_confirm">ADB-sideload</string>
+		<string name="sideload_usage">Gebruik: adb sideload bestandsnaam.zip</string>
+		<string name="sideload_complete">ADB-sideload voltooid</string>
+		<string name="fix_contexts_hdr">Contexten repareren</string>
+		<string name="fix_contexts_note1">N.B.: Het repareren van de contexten is zelden nodig.</string>
+		<string name="fix_contexts_note2">Het repareren van SELinux-contexten kan tot gevolg</string>
+		<string name="fix_contexts_note3">hebben dat het apparaat niet meer naar behoren opstart.</string>
+		<string name="swipe_to_fix_contexts">Veeg om contexten te repareren</string>
+		<string name="swipe_fix_contexts">   Contexten repareren</string>
+		<string name="fixing_contexts">Contexten worden gerepareerd...</string>
+		<string name="fix_contexts_complete">Reparatie contexten voltooid</string>
+		<string name="reboot_hdr">Herstarten</string>
+		<string name="install_cancel">Niet installeren</string>
+		<string name="sel_storage_list">Opslag selecteren</string>
+		<string name="ok_btn">OK</string>
+		<string name="install_twrp_ramdisk">Recovery-ramdisk installeren</string>
+		<string name="install_kernel">Kernel installeren</string>
+		<string name="repack_kernel_confirm_hdr">Kernel installeren</string>
+		<string name="repack_ramdisk_confirm_hdr">Recovery installeren</string>
+		<string name="repack_kernel_confirm">Kernel installeren?</string>
+		<string name="repack_ramdisk_confirm">Recovery installeren?</string>
+		<string name="repack_backup_first">Bestaande image eerst back-uppen</string>
+		<string name="repack">Opnieuw inpakken</string>
+		<string name="swipe_to_install">Veeg om te installeren</string>
+		<string name="installing">Wordt geïnstalleerd...</string>
+		<string name="install_complete">Installatie voltooid</string>
+		<string name="unpack_error">Fout tijdens uitpakken image.</string>
+		<string name="repack_error">Fout tijdens opnieuw inpakken image.</string>
+		<string name="unpacking_image">{1} wordt uitgepakt...</string>
+		<string name="repacking_image">{1} wordt opnieuw ingepakt...</string>
+		<string name="repack_image_hdr">Image selecteren</string>
+		<string name="fix_recovery_loop">Herstellen van oneindige lus tijdens opstarten recovery</string>
+		<string name="fix_recovery_loop_confirm">Herstellen van oneindige recoverylus?</string>
+		<string name="fixing_recovery_loop">Oneindige recoverylus wordt doorbroken...</string>
+		<string name="fix_recovery_loop_complete">Oneindige lus opgelost</string>
+		<string name="fixing_recovery_loop_patch">Kernel wordt gepatcht...</string>
+		<string name="fix_recovery_loop_patch_error">Fout tijdens patchen kernel.</string>
+		<string name="decrypt_users">Gebruikers ontsleutelen</string>
+		<string name="decrypt_users_selection">Selecteer een gebruiker ID om te ontsleutelen</string>
+		<string name="select_user">Selecteer gebruiker</string>
+		<string name="backup_storage_undecrypt_warning">Back-up zonder sommige bestanden van gebruiker {1}: gebruiker niet ontsleuteld.</string>
+		<string name="decrypting_user_fbe">Ontsleutelen van FBE voor gebruiker {1}...</string>
+		<string name="decrypt_user_success_fbe">FBE gebruiker {1} ontsleuteld</string>
+		<string name="decrypt_user_fail_fbe">Ontsleuteling FBE gebruiker {1} mislukt</string>
+		<string name="decrypt_data_enter_pass_fbe">Voer wachtwoord in voor gebruiker [%tw_crypto_user_id%]</string>
+		<string name="decrypt_data_enter_pattern_fbe">Voer patroon in voor gebruiker [%tw_crypto_user_id%]</string>
+		<string name="multiuser_warning1">Niet alle gebruikers ontsleuteld!</string>
+		<string name="multiuser_warning2">Back-up/Herstelbewerkingen kunnen mislukken!</string>
+		<string name="multiuser_warning_accept">Toch doorgaan</string>
+		<string name="multiuser_warning_hdr">Multigebruiker Waarschuwing</string>
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">Kernel ondersteunt het lezen van SELinux-contexten niet.</string>
+		<string name="full_selinux">Volledige SELinux-ondersteuning is aanwezig.</string>
+		<string name="no_selinux">Geen SELinux-ondersteuning aanwezig (geen libselinux).</string>
+		<string name="mtp_enabled">MTP ingeschakeld</string>
+		<string name="mtp_crash">MTP gefaald en voortaan uitgeschakeld tijdens boot.</string>
+		<string name="decrypt_success">Ontsleuteling met standaardwachtwoord geslaagd.</string>
+		<string name="unable_to_decrypt">Ontsleuteling met standaardwachtwoord mislukt. Mogelijk moet de Data-partitie worden geformatteerd.</string>
+		<string name="generating_digest1" version="2">Digest wordt gegenereerd</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * Digest wordt gegenereerd...</string>
+		<string name="digest_created" version="2"> * Digest gecreëerd.</string>
+		<string name="digest_error" version="2"> * Digestfout!</string>
+		<string name="digest_compute_error" version="2"> * Fout tijdens het genereren van de digest.</string>
+		<string name="current_date">(Huidige datum)</string>
+		<string name="auto_generate">(Automatisch genereren)</string>
+		<string name="unable_to_locate_partition">Kan partitie '{1}' niet vinden voor back-upberekeningen.</string>
+		<string name="no_partition_selected">Geen partities geselecteerd om te back-uppen.</string>
+		<string name="total_partitions_backup"> * Totaal aantal partities om te back-uppen: {1}</string>
+		<string name="total_backup_size"> * Totale grootte van alle gegevens: {1} MB</string>
+		<string name="available_space"> * Beschikbare ruimte: {1} MB</string>
+		<string name="unable_locate_storage">Kan opslagapparaat niet vinden.</string>
+		<string name="no_space">Er bestaat onvoldoende vrije ruimte op het opslagmedium.</string>
+		<string name="backup_started">[BACK-UP GESTART]</string>
+		<string name="backup_folder"> * Back-upmap: {1}</string>
+		<string name="fail_backup_folder">Het maken van de back-upmap is mislukt.</string>
+		<string name="avg_backup_fs">Gem. back-upsnelheid voor bestandssystemen: {1} MB/sec</string>
+		<string name="avg_backup_img">Gem. back-upsnelheid voor images: {1} MB/sec</string>
+		<string name="total_backed_size">[{1} MB IN TOTAAL GEBACK-UPT]</string>
+		<string name="backup_completed">[BACK-UP VOLTOOID IN {1} SECONDEN]</string>
+		<string name="restore_started">[HERSTELLEN GESTART]</string>
+		<string name="restore_folder">Herstelmap: {1}</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} VOLTOOID IN {2} seconden]</string>
+		<string name="verifying_digest" version="2">Digest controleren</string>
+		<string name="skip_digest" version="2">Digestcontrole al dan niet overslaan op basis van gebruikersinstelling.</string>
+		<string name="calc_restore">Details herstelbewerking worden berekend...</string>
+		<string name="restore_read_only">Kan {1} niet herstellen -- In onbeschrijfbaar-modus gekoppeld.</string>
+		<string name="restore_unable_locate">Kan {1}-partitie niet vinden om te herstellen.</string>
+		<string name="no_part_restore">Geen partities geselecteerd om te herstellen.</string>
+		<string name="restore_part_count">Bezig {1} partitie(s) te herstellen...</string>
+		<string name="total_restore_size">[{1} MB IN TOTAAL HERSTELD]</string>
+		<string name="updating_system_details">System-gegevens worden bijgewerkt</string>
+		<string name="restore_completed">[HERSTELLEN VOLTOOID IN {1} SECONDEN]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">Fout bij het openen: {1} ({2})</string>
+		<string name="unable_locate_part_backup_name">Partitie kan niet worden gevonden aan de hand van de back-upnaam: {1}</string>
+		<string name="unable_find_part_path">Kan geen partitie vinden voor pad '{1}'</string>
+		<string name="update_part_details">Partitiegegevens worden bijgewerkt...</string>
+		<string name="update_part_details_done">...gereed</string>
+		<string name="wiping_dalvik">Dalvik-cachemappen worden geleegd...</string>
+		<string name="cleaned">Schoongemaakt: {1}...</string>
+		<string name="cache_dalvik_done">-- Wissen Dalvik-cachemappen voltooid!</string>
+		<string name="dalvik_done">-- Legen Dalvik-mappen voltooid!</string>
+		<string name="no_andsec">Geen 'Android Secure'-partities gevonden.</string>
+		<string name="unable_to_locate">Kan {1} niet vinden.</string>
+		<string name="wiping_datamedia">Interne opslag wordt gewist -- /data/media...</string>
+		<string name="unable_to_mount">Kan {1} niet koppelen</string>
+		<string name="unable_to_mount_internal">Kan interne opslag niet koppelen</string>
+		<string name="unable_to_mount_storage">Kan opslag niet koppelen</string>
+		<string name="fail_decrypt">Ontsleutelen gegevens mislukt.</string>
+		<string name="no_crypto_support">Geen ondersteuning voor crypto aanwezig.</string>
+		<string name="decrypt_success_dev">Data ontsleuteld. Nieuw blok-device: {1}</string>
+		<string name="decrypt_success_nodev">Data ontsleuteld</string>
+		<string name="done">Gereed.</string>
+		<string name="start_partition_sd">SD-kaart wordt gepartitioneerd...</string>
+		<string name="partition_sd_locate">Kan te partitioneren device niet vinden.</string>
+		<string name="ext_swap_size">Grootte EXT + Swap groter dan SD-kaart.</string>
+		<string name="remove_part_table">Partitietabel wordt verwijderd...</string>
+		<string name="unable_rm_part">Kan partitietabel niet verwijderen.</string>
+		<string name="create_part">Partitie {1} aanmaken...</string>
+		<string name="unable_to_create_part">Kan partitie {1} niet aanmaken.</string>
+		<string name="format_sdext_as">Externe SD-kaart wordt geformatteerd als {1}...</string>
+		<string name="part_complete">Partitioneren voltooid.</string>
+		<string name="unable_to_open">Kan '{1}' niet openen.</string>
+		<string name="mtp_already_enabled">MTP reeds ingeschakeld</string>
+		<string name="mtp_fail">Kan MTP niet inschakelen</string>
+		<string name="no_mtp">MTP-ondersteuning niet aanwezig</string>
+		<string name="image_flash_start">[IMAGEFLASH GESTART]</string>
+		<string name="img_to_flash">Image om te flashen: {1}</string>
+		<string name="flash_unable_locate">Kan partitie '{1}' niet vinden om te flashen.</string>
+		<string name="no_part_flash">Geen partities geselecteerd voor het flashen.</string>
+		<string name="too_many_flash">Te veel partities geselecteerd voor het flashen.</string>
+		<string name="invalid_flash">Ongeldige flashpartitie opgegeven.</string>
+		<string name="flash_done">[IMAGEFLASH VOLTOOID]</string>
+		<string name="wiping">{1} wordt gewist</string>
+		<string name="repair_not_exist">{1} bestaat niet! Reparatie onmogelijk!</string>
+		<string name="repairing_using">{1} wordt gerepareerd met behulp van {2}...</string>
+		<string name="unable_repair">Kan {1} niet repareren.</string>
+		<string name="mount_data_footer">Kan /data niet koppelen en cryptovoettekst ook niet vinden.</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">Kan map '{1}' niet maken ({2}).</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">Fout bij het koppelen van '{1}' ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">Fout bij het ontkoppelen van '{1}' ({2})</string>
+		<string name="cannot_resize">Kan de grootte van {1} niet wijzigen.</string>
+		<string name="repair_resize">{1} wordt gerepareerd alvorens te vergroten/verkleinen.</string>
+		<string name="unable_resize">Wijzigen grootte {1} niet mogelijk.</string>
+		<string name="no_digest_found" version="2">Geen digestbestand gevonden voor '{1}'. Gelieve digestcontrole uit te schakelen.</string>
+		<string name="digest_fail_match" version="2">Digest voor '{1}' klopt niet.</string>
+		<string name="digest_matched" version="2">Digest voor '{1}' klopt.</string>
+		<string name="fail_decrypt_tar">Ontsleutelen van tarbestand '{1}' mislukt</string>
+		<string name="format_data_msg">U moet mogelijk in recovery herstarten voordat u /data weer kunt gebruiken.</string>
+		<string name="format_data_err">Kan niet formatteren t.b.v. verwijderen versleuteling.</string>
+		<string name="formatting_using">{1} wordt geformatteerd met behulp van {2}...</string>
+		<string name="unable_to_wipe">Kan {1} niet wissen.</string>
+		<string name="cannot_wipe">Partitie {1} kan niet worden gewist.</string>
+		<string name="remove_all">Alle bestanden onder '{1}' worden verwijderd</string>
+		<string name="wiping_data">Data-partitie wordt gewist m.u.v. /data/media...</string>
+		<string name="backing_up">{1} wordt geback-upt...</string>
+		<string name="backup_storage_warning">Van back-ups van {1} zijn uitgezonderd de bestanden in interne opslag, zoals plaatjes en downloads.</string>
+		<string name="backing">Wordt geback-upt</string>
+		<string name="backup_size">Back-upgrootte van '{1}' bedraagt 0 bytes.</string>
+		<string name="datamedia_fs_restore">WAARSCHUWING: Deze back-up van /data is gemaakt met het {1}-bestandssysteem! Mogelijk start de back-up pas weer op wanneer er naar {1} wordt teruggeschakeld.</string>
+		<string name="restoring">{1} wordt hersteld...</string>
+		<string name="restoring_hdr">Wordt hersteld</string>
+		<string name="recreate_folder_err">Kan map {1} niet opnieuw maken.</string>
+		<string name="img_size_err">Imagegrootte is groter dan doelapparaat</string>
+		<string name="flashing">{1} wordt geflasht...</string>
+		<string name="backup_folder_set">Back-upmap ingesteld op '{1}'</string>
+		<string name="locate_backup_err">Kan back-up '{1}' niet vinden</string>
+		<string name="set_restore_opt">Instellen van herstelopties: {1}:</string>
+		<string name="digest_check_skip" version="2">Digestcontrole overslaan staat aan</string>
+		<string name="ors_encrypt_restore_err">Kan OpenRecoveryScript niet gebruiken om een versleutelde back-up te herstellen.</string>
+		<string name="mounting">Wordt gekoppeld</string>
+		<string name="unmounting">Wordt ontkoppeld</string>
+		<string name="mounted">'{1}' gekoppeld</string>
+		<string name="unmounted">'{1}' ontkoppeld</string>
+		<string name="setting">Instellen van '{1}' op '{2}'</string>
+		<string name="setting_empty">Instellen van '{1}' naar leeg</string>
+		<string name="making_dir1">Map maken</string>
+		<string name="making_dir2">Map: '{1}' maken</string>
+		<string name="running_command">Commando uitvoeren</string>
+		<string name="sideload">ADB-sideload</string>
+		<string name="start_sideload">ADB-sideloadfunctie wordt gestart...</string>
+		<string name="need_new_adb">adb 1.0.32 of nieuwer is vereist om te sideloaden naar dit apparaat.</string>
+		<string name="no_pwd">Geen wachtwoord opgegeven.</string>
+		<string name="done_ors">Uitvoeren scriptbestand voltooid</string>
+		<string name="injecttwrp">TWRP wordt in het bootimage ingelast...</string>
+		<string name="zip_err">Fout bij het installeren van zipbestand '{1}'</string>
+		<string name="installing_zip">Zipbestand '{1}' wordt geïnstalleerd</string>
+		<string name="select_backup_opt">Instellen van back-upopties:</string>
+		<string name="compression_on">Compressie staat aan</string>
+		<string name="digest_off" version="2">Het genereren van een digest staat uit</string>
+		<string name="backup_fail">Back-up mislukt</string>
+		<string name="backup_clean">Back-up mislukt. Back-upmap wordt geleegd.</string>
+		<string name="running_recovery_commands">Recoverycommando's worden uitgevoerd</string>
+		<string name="recovery_commands_complete">Recoverycommando's voltooid</string>
+		<string name="running_ors">OpenRecoveryScript wordt uitgevoerd</string>
+		<string name="ors_complete">OpenRecoveryScript voltooid</string>
+		<string name="check_for_digest" version="2">Digestbestand wordt gecontroleerd...</string>
+		<string name="invalid_zip_format">Beschadigd zipbestand!</string>
+		<string name="fail_sysmap">Toewijzen van bestand '{1}' mislukt</string>
+		<string name="verify_zip_sig">Handtekening zipbestand wordt gecontroleerd...</string>
+		<string name="verify_zip_fail">Verificatie handtekening zipbestand mislukt!</string>
+		<string name="verify_zip_done">Handtekening zipbestand geverifieerd.</string>
+		<string name="zip_corrupt">Zipbestand beschadigd!</string>
+		<string name="no_digest" version="2">Controle digest wordt overgeslagen: Geen digestbestand gevonden</string>
+		<string name="digest_fail" version="2">Digest klopt niet</string>
+		<string name="digest_match" version="2">Digest klopt</string>
+		<string name="pid_signal">{1} proces geëindigd met signaal: {2}</string>
+		<string name="pid_error">{1} proces geëindigd met fout: {2}</string>
+		<string name="install_dumlock">HTC Dumlock wordt geïninstalleerd...</string>
+		<string name="dumlock_restore">Oorspronkelijke boot wordt hersteld...</string>
+		<string name="dumlock_reflash">Recovery wordt opnieuw naar boot geflasht...</string>
+		<string name="run_script">Script {1} wordt uitgevoerd...</string>
+		<string name="rename_stock">Oorspronkelijke recoverybestand hernoemd in /system om te voorkomen dat de ROM TWRP vervangt.</string>
+		<string name="split_backup">Back-upbestand wordt in meerdere archiefbestanden opgesplitst...</string>
+		<string name="backup_error">Fout bij het maken van de back-up.</string>
+		<string name="restore_error">Fout tijdens het herstellen.</string>
+		<string name="split_thread">Thread-ID {1} wordt gesplitst in archief {2}</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">%llu van %llu bestanden, %i%%</string>
+		<string name="size_progress">%llu MB van %llu MB, %i%%</string>
+		<string name="decrypt_cmd" version="2">Er wordt geprobeerd de Data-partitie te ontsleutelen via de commandoregel.</string>
+		<string name="base_pkg_err">Fout bij het laden van de basispakketten.</string>
+		<string name="simulating">Acties worden gesimuleerd...</string>
+		<string name="backup_cancel">Back-up geannuleerd</string>
+		<string name="config_twrp">TWRP wordt geconfigureerd...</string>
+		<string name="config_twrp_err">Met deze kernel kan TWRP niet worden geconfigureerd.</string>
+		<string name="copy_log">Recoverylogbestand gekopieerd naar {1}.</string>
+		<string name="max_queue">Maximale lengte zipwachtrij bereikt!</string>
+		<string name="min_queue">Minimale lengte zipwachtrij bereikt!</string>
+		<string name="screenshot_saved">Schermafdruk opgeslagen in {1}</string>
+		<string name="screenshot_err">Maken schermafdruk mislukt!</string>
+		<string name="zip_wipe_cache">Een of meer zipbestanden vroegen om het wissen van de cache -- Cache wordt nu verwijderd.</string>
+		<string name="and_sec_wipe_err">Kan Android Secure niet wissen</string>
+		<string name="dalvik_wipe_err">Wissen van Dalvik mislukt</string>
+		<string name="auto_gen">(Automatisch genereren)</string>
+		<string name="curr_date">(Huidige datum)</string>
+		<string name="backup_name_len">Back-upnaam is te lang.</string>
+		<string name="backup_name_invalid">Back-upnaam '{1}' bevat een ongeldig teken: '{1}'</string>
+		<string name="no_real_sdcard">Dit apparaat beschikt niet over een echte SD-kaart! Afgebroken!</string>
+		<string name="cancel_sideload">ADB-sideload wordt geannuleerd...</string>
+		<string name="change_fs_err">Fout tijdens het wijzigen van het bestandssysteem.</string>
+		<string name="theme_ver_err">Versie maatwerkthema past niet bij deze versie van TWRP. Standaardthema wordt gebruikt.</string>
+		<string name="up_a_level">(Niveau omhoog)</string>
+		<string name="install_reboot" version="2">Herstarten over %tw_sleep% seconden</string>
+		<string name="adbbackup_error">Fout bij ADB-back-up. Afgebroken...&quot;</string>
+		<string name="adbbackup_control_error">Kan niet schrijven naar adb-beheerskanaal</string>
+		<string name="twrp_adbbu_option">--twrp optie vereist voor inschakelen twrp adb-backup</string>
+		<string name="partition_not_found">pad: {1} niet gevonden in partitielijst</string>
+		<string name="copy_kernel_log">Kernellogbestand gekopieerd naar {1}</string>
+		<string name="include_kernel_log">Kernellog bijvoegen</string>
+		<string name="sha2_chk">SHA2 gebruiken voor hashen</string>
+		<string name="unable_set_boot_slot">Fout tijdens aanpassen opstartslot bootloader in {1}</string>
+		<string name="unmount_sys_install">Ontkoppel System vóór installatie zipbestand</string>
+		<string name="unmount_system">System wordt ontkoppeld...</string>
+		<string name="unmount_system_err">Ontkoppelen System mislukt</string>
+		<string name="flash_ab_inactive">A/B zipbestand wordt geflasht naar inactieve slot: {1}</string>
+		<string name="flash_ab_reboot">Om meer zipbestanden te flashen dient u eerst te herstarten in Recovery om naar de bijgewerkte slot te schakelen.</string>
+		<string name="flash_ab_both_slots">Beide slots flashen</string>
+		<string name="ozip_decrypt_decryption">Ozip-ontsleutelen...</string>
+		<string name="ozip_decrypt_finish">Ozip-ontsleutelen gereed!</string>
+		<string name="fastboot_button">Fastboot</string>
+		<string name="enable_adb">ADB inschakelen</string>
+		<string name="enable_fastboot">Fastboot inschakelen</string>
+		<string name="fastboot_console_msg">In Fastboot modus...</string>
+		<!-- Android Rescue Party trigger -->
+		<string name="rescue_party0">Android Rescue Party trigger! Mogelijke oplossingen? Of:</string>
+		<string name="rescue_party1"> 1. Cachemappen wissen, en/of</string>
+		<string name="rescue_party2"> 2. Data formatteren, en/of</string>
+		<string name="rescue_party3"> 3. Uw ROM schoon flashen.</string>
+		<string name="rescue_party4">Het gemelde probleem is: </string>
+		<string name="restore_system_context">Kan standaardcontext voor {1} niet achterhalen -- Android start mogelijk niet op.</string>
+		<string name="change_twrp_folder_btn">TWRP-map wijzigen</string>
+		<string name="change_twrp_folder_on_process">TWRP-map wordt gewijzigd</string>
+		<string name="change_twrp_folder_after_process">TWRP-map gewijzigd in</string>
+		<string name="tw_folder_exists">Er bestaat al een map met die naam!</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/pl.xml b/gui/theme/common/languages/pl.xml
new file mode 100644
index 0000000..469b037
--- /dev/null
+++ b/gui/theme/common/languages/pl.xml
@@ -0,0 +1,798 @@
+<?xml version="1.0"?>
+
+<language>
+	<display>Polish</display>
+
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<!-- Partition display names -->
+		<string name="system">System</string>
+		<string name="system_image">Obraz Systemu</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Obraz Vendor</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="data_backup">Data (bez pamięci wewn.)</string>
+		<string name="sdcard">Karta SD</string>
+		<string name="internal">Pamięć Wewnętrzna</string>
+		<string name="microsd">Karta Micro SD</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Adopted Data</string>
+		<string name="adopted_storage">Adopted Storage</string>
+		<string name="autostorage">Pamięć</string>
+
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Projekt Team Win Recovery</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% &#xB0;C</string>
+		<string name="battery_pct">Bateria: %tw_battery%</string>
+		<string name="sort_by_name">Sortuj wg nazwy</string>
+		<string name="sort_by_date">Sortuj wg daty</string>
+		<string name="sort_by_size">Sortuj wg rozmiaru</string>
+		<string name="sort_by_name_only">Nazwa</string>
+		<string name="sort_by_date_only">Data</string>
+		<string name="sort_by_size_only">Rozmiar</string>
+		<string name="tab_general">GŁÓWNE</string>
+		<string name="tab_options">OPCJE</string>
+		<string name="tab_backup">KOPIA ZAPASOWA</string>
+		<string name="tab_time_zone">STREFA CZASOWA</string>
+		<string name="tab_screen">EKRAN</string>
+		<string name="tab_vibration">WIBRACJE</string>
+		<string name="tab_language">JĘZYK</string>
+
+		<string name="install_btn">Instaluj</string>
+		<string name="wipe_btn">Wyczyść</string>
+		<string name="backup_btn">Kopia</string>
+		<string name="restore_btn">Przywróć</string>
+		<string name="mount_btn">Zamontuj</string>
+		<string name="settings_btn">Ustawienia</string>
+		<string name="advanced_btn">Zaawansowane</string>
+		<string name="reboot_btn">Restart</string>
+		<string name="files_btn">Pliki</string>
+		<string name="copy_log_btn">Kopiuj Log</string>
+		<string name="select_type_hdr">Wybierz Typ</string>
+		<string name="install_zip_hdr">Instaluj Zip</string>
+		<string name="install_zip_btn">Instaluj Zip</string>
+		<string name="install_image_hdr">Instaluj Obraz</string>
+		<string name="install_image_btn">Instaluj Obraz</string>
+		<string name="install_select_file_hdr">Wybierz Plik</string>
+		<string name="file_selector_folders_hdr">Foldery</string>
+		<string name="select_file_from_storage">Wybierz plik z %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Instaluj</string>
+		<string name="select_storage_hdr">Wybierz Pamięć</string>
+		<string name="select_storage_btn">Wybierz Pamięć</string>
+		<string name="queue_hdr">Kolejka</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% z max. 10 plików w kolejce</string>
+		<string name="zip_queue_count_s">Plik %tw_zip_queue_count% z 10:</string>
+		<string name="zip_warn1">Ta operacja może zainstalować niekompatybilne oprogramowanie</string>
+		<string name="zip_warn2">i uczynić urządzenie nie nadającym się do użytku.</string>
+		<string name="zip_back_cancel">Naciśnij wstecz, aby anulować dodawanie tego pliku.</string>
+		<string name="zip_back_clear">Naciśnij wstecz, aby wyczyścić kolejkę.</string>
+		<string name="folder">Folder:</string>
+		<string name="file">Plik:</string>
+		<string name="zip_sig_chk">Weryfikacja sygnatury pliku zip</string>
+		<string name="inject_twrp_chk">Zaszczep TWRP po instalacji</string>
+		<string name="install_reboot_chk">Uruchom ponownie po instalacji</string>
+		<string name="options_hdr">Opcje</string>
+		<string name="confirm_flash_hdr">Potwierdź instalację</string>
+		<string name="zip_queue">Kolejka:</string>
+		<string name="options">Opcje:</string>
+		<string name="swipe_confirm">   Potwierdź</string>
+		<string name="zip_add_btn">Dodaj więcej plików</string>
+		<string name="zip_clear_btn">Wyczyść kolejkę plików</string>
+		<string name="install_zip_count_hdr">Zainstaluj plik %tw_zip_index% z %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Instalowanie plików: %tw_file%</string>
+		<string name="failed">Niepowodzenie</string>
+		<string name="successful">Sukces</string>
+		<string name="install_failed">Instalacja nieudana</string>
+		<string name="install_successful">Instalacja zakończona pomyślnie</string>
+		<string name="wipe_cache_dalvik_btn">Wyczyść Cache/Dalvik</string>
+		<string name="wipe_dalvik_btn">Wyczyść Dalvik</string>
+		<string name="reboot_system_btn">Uruchom System</string>
+		<string name="install_sel_target">Wybierz partycję docelową</string>
+		<string name="flash_image_select">Wybierz partycję do zainstalowania obrazu:</string>
+		<string name="target_partition">Partycja docelowa:</string>
+		<string name="flashing_image">Instalowanie obrazu...</string>
+		<string name="image_flashed">Obraz zainstalowany</string>
+		<string name="wipe_cache_dalvik_confirm">Wyczyścić Cache &amp; Dalvik?</string>
+		<string name="wipe_dalvik_confirm">Wyczyścić Dalvik?</string>
+		<string name="wiping_cache_dalvik">Czyszczenie Cache &amp; Dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Czyszczenie Cache &amp; Dalvik zakończone</string>
+		<string name="wipe_dalvik_complete">Czyszczenie Dalvik zakończone</string>
+		<string name="swipe_wipe">Przesuń, aby wyczyścić</string>
+		<string name="swipe_wipe_s">   Wyczyść</string>
+		<string name="no_os1">Brak zainstalowanego systemu! Jesteś</string>
+		<string name="no_osrb">pewien, że chcesz uruchomić urządzenie?</string>
+		<string name="no_ospo">pewien, że chcesz wyłączyć?</string>
+		<string name="rebooting">Restartowanie...</string>
+		<string name="swipe_reboot">Przesuń, aby zrestartować</string>
+		<string name="swipe_reboot_s">   Zrestartuj</string>
+		<string name="reboot_install_app_hdr">Zainstaluj aplikację TWRP</string>
+		<string name="reboot_install_app1">Czy chcesz zainstalować oficjalną aplikację TWRP?</string>
+		<string name="reboot_install_app2">Aplikacja może sprawdzać dostępność nowych wydań TWRP.</string>
+		<string name="reboot_install_app_prompt_install">Informuj o możliwości instalacji aplikacji TWRP, jeśli jej nie zainstalowano</string>
+		<string name="reboot_install_app_system">Zainstaluj jako systemową</string>
+		<string name="reboot_installing_app">Instalowanie aplikacji...</string>
+		<string name="swipe_to_install_app">Przesuń, aby zainstalować aplikację TWRP</string>
+		<string name="uninstall_twrp_system_app">Odinstaluj aplikację TWRP z systemu</string>
+		<string name="uninstall_twrp_system_app_confirm">Odinstalować aplikację TWRP z systemu?</string>
+		<string name="uninstalling_twrp_system_app">Dezinstalacja aplikacji TWRP z systemu...</string>
+		<string name="uninstall_twrp_system_app_complete">Dezinstalacja aplikacji TWRP ukończona</string>
+		<string name="swipe_flash">Przesuń, aby zainstalować</string>
+		<string name="confirm_action">Potwierdź czynność</string>
+		<string name="back_cancel">Naciśnij wstecz, aby anulować.</string>
+		<string name="cancel_btn">Anuluj</string>
+		<string name="wipe_hdr">Wyczyść</string>
+		<string name="factory_reset_hdr">Przywrócenie ustawień fabrycznych</string>
+		<string name="factory_reset_btn">Przywrócenie ustawień fabrycznych</string>
+		<string name="factory_reset1">Czyści Data, Cache i Dalvik</string>
+		<string name="factory_reset2">(nie czyści pamięci wewnętrznej)</string>
+		<string name="factory_reset3">W większości przypadków jest to</string>
+		<string name="factory_reset4">jedyne czyszczenie jakiego potrzebujesz.</string>
+		<string name="factory_reset5">(nie czyści użytkowników/ekranu blokady)</string>
+		<string name="factory_resetting">Przywracanie ustawień fabrycznych...</string>
+		<string name="advanced_wipe_hdr">Zaawansowane czyszczenie</string>
+		<string name="advanced_wipe_btn">Zaaw. czyszczenie</string>
+		<string name="wipe_enc_confirm">Usunąć szyfrowanie Data?</string>
+		<string name="formatting_data">Formatowanie Data...</string>
+		<string name="swipe_format_data">Przesuń, aby sformatować Data</string>
+		<string name="swipe_format_data_s">   Sformatuj Data</string>
+		<string name="factory_reset_complete">Przywracanie ustawień fabrycznych zakończone</string>
+		<string name="sel_part_hdr">Wybierz partycje</string>
+		<string name="wipe_sel_confirm">Sformatować wybrane partycje?</string>
+		<string name="wiping_part">Formatowanie partycji...</string>
+		<string name="wipe_complete">Formatowanie zakończone</string>
+		<string name="sel_part_wipe">Partycje do sformatowania:</string>
+		<string name="invalid_part_sel">Nieprawidłowy wybór partycji</string>
+		<string name="format_data_hdr">Formatuj Data</string>
+		<string name="format_data_btn">Formatuj Data</string>
+		<string name="format_data_ptr1">Formatowanie Data usunie wszystkie aplikacje,</string>
+		<string name="format_data_ptr2">kopie zapasowe, zdjęcia, filmy, media, oraz</string>
+		<string name="format_data_ptr3">szyfrowanie pamięci wewnętrznej.</string>
+		<string name="format_data_adopted">Łącznie z Adopted Storage</string>
+		<string name="format_data_lcp1">Formatowanie Data usunie wszystkie aplikacje, kopie zapasowe, zdjęcia, filmy, media, oraz</string>
+		<string name="format_data_lcp2">usuwa szyfrowanie pamięci wewnętrznej.</string>
+		<string name="format_data_wtc1">Format Data usunie wszystkie aplikacje,</string>
+		<string name="format_data_wtc2">kopie zapasowe i media. To nieodwracalne.</string>
+		<string name="format_data_undo">To nieodwracalne.</string>
+		<string name="format_data_complete">Formatowanie Data zakończone</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">Wpisz "yes", by kontynuować.  Naciśnij wstecz, aby anulować.</string>
+		<string name="part_opt_hdr">Opcje partycji dla: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Wybierz działanie</string>
+		<string name="file_sys_opt">Opcje systemu plików</string>
+		<string name="partition">Partycja: %tw_partition_name%</string>
+		<string name="part_mount_point">Punkt montowania: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">System plików: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Obecna: Tak</string>
+		<string name="part_present_no">Obecna: Nie</string>
+		<string name="part_removable_yes">Przenośna: Tak</string>
+		<string name="part_removable_no">Przenośna: Nie</string>
+		<string name="part_size">Rozmiar: %tw_partition_size%MB</string>
+		<string name="part_used">Użyte: %tw_partition_used%MB</string>
+		<string name="part_free">Wolne: %tw_partition_free%MB</string>
+		<string name="part_backup_size">Rozmiar kopii zapasowej: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">Zmiana rozmiaru systemu plików</string>
+		<string name="resize_btn_s">Zmień rozmiar</string>
+		<string name="resize_confirm">Zmienić rozmiar %tw_partition_name%?</string>
+		<string name="resizing">Zmienianie rozmiaru...</string>
+		<string name="resize_complete">Zmienianie rozmiaru zakończone</string>
+		<string name="swipe_resize">Przesuń, aby zmienić rozmiar</string>
+		<string name="swipe_resize_s">   Zmień rozmiar</string>
+		<string name="repair_btn">Napraw system plików</string>
+		<string name="repair_btn_s">Napraw</string>
+		<string name="repair_confirm">Naprawić %tw_partition_name%?</string>
+		<string name="repairing">Naprawianie...</string>
+		<string name="repair_complete">Naprawianie zakończone</string>
+		<string name="swipe_repair">Przesuń, aby naprawić</string>
+		<string name="swipe_repair_s">   Napraw</string>
+		<string name="change_fs_btn">Zmień system plików</string>
+		<string name="change_fs_btn_s">Zmień</string>
+		<string name="change_fs_for_hdr">Zmień system plików na: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Partycja: %tw_partition_name% &gt; Wybierz system plików</string>
+		<string name="change_fs_warn1">Niektore ROMy i kernele mogą nie obsługiwac niektórych</string>
+		<string name="change_fs_warn2">systemów plików. Postępuj ostrożnie!</string>
+		<string name="change_fs_confirm">Zmienić %tw_partition_name%?</string>
+		<string name="formatting">Formatowanie...</string>
+		<string name="format_complete">Formatowanie zakończone</string>
+		<string name="swipe_change_fs">Przesuń, aby zmienić</string>
+		<string name="swipe_change_s">   Zmień</string>
+		<string name="back_btn">Wstecz</string>
+		<string name="wipe_enc_btn">Usuń szyfrowanie</string>
+		<string name="swipe_factory_reset">Przesuń, aby wykonać</string>
+		<string name="repair_change_btn">Napraw lub zmień system plików</string>
+		<string name="storage_hdr">Pamięć: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Kopia zapasowa</string>
+		<string name="backup_confirm_hdr">Potwierdź kopię zapasową</string>
+		<string name="encryption_tab">SZYFROWANIE</string>
+		<string name="encryption">Szyfrowanie:</string>
+		<string name="name">Nazwa:</string>
+		<string name="sel_part_backup">Wybierz partycje do skopiowania:</string>
+		<string name="storage">Pamięć:</string>
+		<string name="enc_disabled">wyłączone - ustaw hasło, aby włączyć</string>
+		<string name="enc_enabled">włączone</string>
+		<string name="enable_backup_comp_chk">Włącz kompresję</string>
+		<string name="skip_digest_backup_chk" version="2">Pomiń generowanie Digest kopii zapasowej</string>
+		<string name="disable_backup_space_chk" version="2">Wyłącz sprawdzanie wolnego miejsca</string>
+		<string name="skip_digest_zip_chk">Pomiń sprawdzanie Digest przed instalacją pliku zip</string>
+		<string name="current_boot_slot">Obecny slot: %tw_active_slot%</string>
+		<string name="boot_slot_a">Slot A</string>
+		<string name="boot_slot_b">Slot B</string>
+		<string name="changing_boot_slot">Zmiana slotu uruchamiania</string>
+		<string name="changing_boot_slot_complete">Zmiana slotu uruchamiania ukończona</string>
+		<string name="refresh_sizes_btn">Odśwież rozmiary</string>
+		<string name="swipe_backup">Przesuń, aby wykonać kopię</string>
+		<string name="append_date_btn">Dodaj datę</string>
+		<string name="backup_name_exists">Kopia zapasowa o takiej nazwie już istnieje!</string>
+		<string name="encrypt_backup">Zaszyfrować kopię zapasową?</string>
+		<string name="enter_pass">Wpisz hasło:</string>
+		<string name="enter_pass2">Wpisz hasło ponownie:</string>
+		<string name="pass_not_match">Hasła się nie zgadzają!</string>
+		<string name="partitions">Partycje:</string>
+		<string name="disabled">Wyłączone</string>
+		<string name="enabled">Włączone</string>
+		<string name="backup_name_hdr">Ustaw nazwę kopii zapasowej</string>
+		<string name="progress">Postęp:</string>
+		<string name="backup_complete">Kopia zapasowa wykonana</string>
+		<string name="restore_hdr">Przywróć</string>
+		<string name="sel_backup_hdr">Wybierz kopię zapasową</string>
+		<string name="restore_sel_store_hdr">Wybierz kopię zapasową z %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Wybierz zestaw do przywrócenia:</string>
+		<string name="restore_enc_backup_hdr">Szyfrowana kopia</string>
+		<string name="restore_dec_fail">Hasło nieprawidłowe, spróbuj ponownie!</string>
+		<string name="del_backup_btn">Usuń kopię zapasową</string>
+		<string name="del_backup_confirm">Usunąć kopię zapasową?</string>
+		<string name="del_backup_confirm2">To nieodwracalne!</string>
+		<string name="deleting_backup">Usuwanie kopii zapasowej...</string>
+		<string name="backup_deleted">Usuwanie kopii zapasowej zakończone</string>
+		<string name="swipe_delete">Przesuń, aby usunąć</string>
+		<string name="swipe_delete_s">   Usuń</string>
+		<string name="restore_try_decrypt">Kopia zapasowa zaszyfrowana - próbuję rozszyfrować</string>
+		<string name="restore_try_decrypt_s">Spróbuj rozszyfrować</string>
+		<string name="restore_backup_date">Kopia zapasowa utworzona %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Wybierz partycje do przywrócenia:</string>
+		<string name="restore_enable_digest_chk" version="2">Włącz weryfikację Digest kopii zapasowych</string>
+		<string name="restore_complete">Przywracanie zakończone</string>
+		<string name="swipe_restore">Przesuń, aby przywrócić</string>
+		<string name="swipe_restore_s">   Przywróć</string>
+		<string name="rename_backup_hdr">Zmień nazwę kopii zapasowej</string>
+		<string name="rename_backup_confirm">Zmienić nazwę kopii zapasowej?</string>
+		<string name="rename_backup_confirm2">To nieodwracalne!</string>
+		<string name="renaming_backup">Zmienianie nazwy kopii zapasowej...</string>
+		<string name="rename_backup_complete">Zmienianie nazwy kopii zapasowej zakończone</string>
+		<string name="swipe_to_rename">Przesuń, aby zmienić nazwę</string>
+		<string name="swipe_rename">   Zmień nazwę</string>
+		<string name="confirm_hdr">Potwierdź</string>
+		<string name="mount_hdr">Zamontuj</string>
+		<string name="mount_sel_part">Wybierz partycje do zamontowania:</string>
+		<string name="mount_sys_ro_chk">Zamontuj partycję systemową tylko do odczytu</string>
+		<string name="mount_sys_ro_s_chk">Zamontuj system tylko do odczytu</string>
+		<string name="decrypt_data_btn">Rozszyfruj Data</string>
+		<string name="disable_mtp_btn">Wyłącz MTP</string>
+		<string name="enable_mtp_btn">Włącz MTP</string>
+		<string name="mount_usb_storage_btn">Zamontuj pamięć USB</string>
+		<string name="usb_storage_hdr">Pamięć USB</string>
+		<string name="usb_stor_mnt1">Pamięć USB zamontowana</string>
+		<string name="usb_stor_mnt2">Pamiętaj, aby bezpiecznie usunąć urządzenie</string>
+		<string name="usb_stor_mnt3">z komputera przed odmontowaniem!</string>
+		<string name="unmount_btn">Odmontuj</string>
+		<string name="rb_system_btn">System</string>
+		<string name="rb_poweroff_btn">Wyłącz</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Download</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Wyłączanie...</string>
+		<string name="swipe_power_off">Przesuń, aby wyłączyć</string>
+		<string name="swipe_power_off_s">Wyłącz</string>
+		<string name="sys_ro_hdr">Niezmodyfikowana partycja systemowa</string>
+		<string name="sys_ro_keep">Zachować system tylko do odczytu?</string>
+		<string name="sys_rop1">TWRP możę pozostawić system niezmodyfikowany,</string>
+		<string name="sys_rop2">aby ułatwić ci otrzymywanie oficjalnych aktualizacji.</string>
+		<string name="sys_rop3">TWRP nie może zapobiec przed podmianą recovery przez</string>
+		<string name="sys_rop4">oficjalny system i nie będzie oferowało rootowania urządzenia.</string>
+		<string name="sys_rop5">Instalowanie plików i operacje przez ADB mogą wciąż</string>
+		<string name="sys_rop6">modyfikować partycję systemową.</string>
+		<string name="sys_rol1">TWRP może pozostawić system niezmodyfikowany, aby ułatwić ci otrzymywanie oficjalnych aktualizacji.</string>
+		<string name="sys_rol2">TWRP nie może zapobiec przed podmianą recovery przez oficjalny system i nie będzie oferowało rootowania urządzenia.</string>
+		<string name="sys_rol3">Instalowanie plików i operacje przez ADB mogą wciąż modyfikować partycję systemową.</string>
+		<string name="sys_ro_never_show_chk">Nie pokazuj tego ekranu ponownie po uruchomieniu</string>
+		<string name="sys_ro_keep_ro_btn">Tylko do odczytu</string>
+		<string name="swipe_allow_mod">Przesuń, aby zmodyfikować</string>
+		<string name="swipe_allow_mod_s">Zezwól na modyfikacje</string>
+		<string name="settings_hdr">Ustawienia</string>
+		<string name="settings_gen_hdr">Główne</string>
+		<string name="settings_gen_s_hdr">Główne</string>
+		<string name="settings_gen_btn">Główne</string>
+		<string name="use_rmrf_chk">Używaj rm -rf zamiast formatowania</string>
+		<string name="auto_reflashtwrp_chk">Automatycznie wgrywaj TWRP po wgraniu ROM-u</string>
+		<string name="use24clock_chk">Użyj 24-godzinnego zegara</string>
+		<string name="rev_navbar_chk">Odwrócony układ paska nawigacji</string>
+		<string name="simact_chk">Symuluj akcje do testowania motywów</string>
+		<string name="simfail_chk">Symuluj niepowodzenia do testów</string>
+		<string name="ctr_navbar_rdo">Wyśrodkuj przyciski paska nawigacji</string>
+		<string name="lft_navbar_rdo">Wyrównaj do lewej przyciski paska nawigacyji</string>
+		<string name="rht_navbar_rdo">Wyrównaj do prawej przyciski paska nawigacyji</string>
+		<string name="restore_defaults_btn">Przywróć domyślne</string>
+		<string name="settings_tz_btn">Strefa czasowa</string>
+		<string name="settings_screen_btn">Ekran</string>
+		<string name="settings_screen_bright_btn">Jasność ekranu</string>
+		<string name="vibration_disabled">Wibracje są wyłączone na tym urządzeniu</string>
+		<string name="settings_vibration_btn">Wibracje</string>
+		<string name="settings_language_btn">Język</string>
+		<string name="time_zone_hdr">Strefa czasowa</string>
+		<string name="sel_tz_list">Wybierz strefę czasową:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) Samoa, Wyspy Midway</string>
+		<string name="utcm10">(UTC -10) Hawaje</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm8">(UTC -8) Czas pacyficzny</string>
+		<string name="utcm7">(UTC -7) Czas górski</string>
+		<string name="utcm6">(UTC -6) Czas centralny</string>
+		<string name="utcm5">(UTC -5) Czas wschodni</string>
+		<string name="utcm4">(UTC -4) Czas atlantycki</string>
+		<string name="utcm3">(UTC -3) Brazylia, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Wybrzeże Atlantyku</string>
+		<string name="utcm1">(UTC -1) Azory, Przylądek Zielony</string>
+		<string name="utc0">(UTC  0) Londyn, Dublin, Lizbona</string>
+		<string name="utcp1">(UTC +1) Berlin, Bruksela, Paryż, Warszawa</string>
+		<string name="utcp2">(UTC +2) Ateny, Stambuł, Afryka Południowa</string>
+		<string name="utcp3">(UTC +3) Moskwa, Bagdad</string>
+		<string name="utcp4">(UTC +4) Abu Zabi, Tbilisi, Maskat</string>
+		<string name="utcp5">(UTC +5) Jekaterynburg, Islamabad</string>
+		<string name="utcp6">(UTC +6) Ałmaty, Dhaka, Kolombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Dżakarta</string>
+		<string name="utcp8">(UTC +8) Pekin, Singapur, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokio, Seul, Jakuck</string>
+		<string name="utcp10">(UTC +10) Australia Wschodnia, Guam</string>
+		<string name="utcp11">(UTC +11) Władywostok, Wyspy Salomona</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fidżi</string>
+		<string name="sel_tz_offset">Wyrównianie czasu (najczęściej 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Brak</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Użyj czasu letniego (DST)</string>
+		<string name="curr_tz">Obecna strefa czasowa: %tw_time_zone%</string>
+		<string name="curr_tz_s">Obecna strefa czasowa:</string>
+		<string name="set_tz_btn">Ustaw strefę czasową</string>
+		<string name="settings_screen_hdr">Ekran</string>
+		<string name="settings_screen_timeout_hdr">Wygaszanie ekranu</string>
+		<string name="enable_timeout_chk">Włącz wygaszanie ekranu</string>
+		<string name="screen_to_slider">Wygaszanie ekranu w sekundach:</string>
+		<string name="screen_to_slider_s">Wygaszanie ekranu w sekundach (0=wyłączone): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Ustawienia wygaszania ekranu niedostępne</string>
+		<string name="screen_bright_slider">Jasność: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Ustawienia jasności ekranu niedostępne</string>
+		<string name="vibration_hdr">Wibracje</string>
+		<string name="button_vibration_hdr">Wibracje przycisków</string>
+		<string name="kb_vibration_hdr">Wibracje klawiatury</string>
+		<string name="act_vibration_hdr">Wibracje działań</string>
+		<string name="button_vibration">Wibracje przycisków:</string>
+		<string name="kb_vibration">Wibracje klawiatury:</string>
+		<string name="act_vibration">Wibracje działań:</string>
+		<string name="select_language">Wybierz język:</string>
+		<string name="sel_lang_btn">Wybierz język</string>
+		<string name="set_language_btn">Wybierz język</string>
+		<string name="advanced_hdr">Zaawansowane</string>
+		<string name="copy_log_confirm">Skopiować log na kartę SD?</string>
+		<string name="copying_log" version="2">Kopiowanie pliku log na kartę SD...</string>
+		<string name="copy_log_complete" version="2">Kopiowanie pliku log zakończone</string>
+		<string name="fix_context_btn">Uprawnienia</string>
+		<string name="part_sd_btn">Partycjonowanie</string>
+		<string name="part_sd_s_btn">Karta SD</string>
+		<string name="file_manager_btn">Menedżer Plików</string>
+		<string name="language_hdr">Język</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="reload_theme_btn">Odśwież Motyw</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Zaszczep TWRP</string>
+		<string name="inject_twrp_confirm">Ponownie zaszczepić TWRP?</string>
+		<string name="injecting_twrp">Szczepienie TWRP...</string>
+		<string name="inject_twrp_complete">Szczepienie TWRP zakończone</string>
+		<string name="swipe_to_confirm">Przesuń, aby potwierdzić</string>
+		<string name="part_sd_hdr">Partycjonowanie Karty SD</string>
+		<string name="invalid_partsd_sel">Musisz wybrać urządzenie przenośne</string>
+		<string name="part_sd_lose">Stracisz wszystkie pliki na Karcie SD!</string>
+		<string name="part_sd_undo">To nieodwracalne!</string>
+		<string name="part_sd_ext_sz">Rozmiar EXT:</string>
+		<string name="part_sd_swap_sz">Rozmiar Swap:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">System plików:</string>
+		<string name="swipe_part_sd">Przesuń, aby partycjonować</string>
+		<string name="swipe_part_sd_s">Partycjonuj</string>
+		<string name="partitioning_sd">Partycjonowanie Karty SD...</string>
+		<string name="partitioning_sd2">To potrwa parę minut.</string>
+		<string name="part_sd_complete">Partycjonowanie zakończone</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Przywróć oryginalny Boot</string>
+		<string name="dumlock_restore_confirm">Przywrócić oryginalny obraz Boot?</string>
+		<string name="dumlock_restoring">Przywracanie oryginalnego obrazu Boot...</string>
+		<string name="dumlock_restore_complete">Przywracanie oryginalnego obrazu Boot zakończone</string>
+		<string name="dumlock_reflash_btn">Reinstaluj Recovery</string>
+		<string name="dumlock_reflash_confirm">Instalować recovery do boot?</string>
+		<string name="dumlock_reflashing">Instalowanie recovery do boot...</string>
+		<string name="dumlock_reflash_complete">Instalowanie recovery do boot zakończone</string>
+		<string name="dumlock_install_btn">Instaluj HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Instalować pliki HTC Dumlock do ROMu?</string>
+		<string name="dumlock_installing">Instalowanie HTC Dumlock...</string>
+		<string name="dumlock_install_complete">Instalowanie HTC Dumlock zakończone</string>
+		<string name="swipe_to_unlock">Przesuń, aby odblokować</string>
+		<string name="swipe_unlock">   Odblokuj</string>
+		<string name="fm_hdr">Menedżer Plików</string>
+		<string name="fm_sel_file">Wybierz plik lub folder</string>
+		<string name="fm_type_folder">Folder</string>
+		<string name="fm_type_file">Plik</string>
+		<string name="fm_choose_act">Wybierz działanie</string>
+		<string name="fm_selected">%tw_fm_type% wybrane:</string>
+		<string name="fm_copy_btn">Kopiuj</string>
+		<string name="fm_copy_file_btn">Kopiuj plik</string>
+		<string name="fm_copy_folder_btn">Kopiuj folder</string>
+		<string name="fm_copying">Kopiowanie</string>
+		<string name="fm_move_btn">Przenieś</string>
+		<string name="fm_moving">Przenoszenie</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Usuń</string>
+		<string name="fm_deleting">Usuwanie</string>
+		<string name="fm_rename_btn">Zmień nazwę</string>
+		<string name="fm_rename_file_btn">Zmień nazwę pliku</string>
+		<string name="fm_rename_folder_btn">Zmień nazwę folderu</string>
+		<string name="fm_renaming">Zmienianie nazwy</string>
+		<string name="fm_sel_dest">Wybierz folder docelowy</string>
+		<string name="fm_sel_curr_folder">Wybierz bierzący folder</string>
+		<string name="fm_rename_hdr">Zmień nazwę</string>
+		<string name="fm_set_perms_hdr">Ustaw uprawnienia</string>
+		<string name="fm_perms">Uprawnienia:</string>
+		<string name="fm_complete">Działanie na plikach zakończone</string>
+		<string name="fm_open_terminal_btn">Otwórz tu terminal</string>
+		<string name="fm_edit_file">Edytuj plik</string>
+		<string name="decrypt_data_hdr">Deszyfrowanie danych</string>
+		<string name="decrypt_data_enter_pass">Wprowadź hasło.</string>
+		<string name="decrypt_data_failed">Hasło nieprawidłowe, spróbuj ponownie!</string>
+		<string name="decrypt_data_failed_pattern">Wzór nieprawidłowy, spróbuj ponownie!</string>
+		<string name="decrypt_data_enter_pattern">Wprowadź wzór.</string>
+		<string name="decrypt_data_trying">Próbuję rozszyfrować</string>
+		<string name="decrypt_data_vold_os_missing">Brakuje plików niezbędnych do zdeszyfrowania vold: {1}</string>
+		<string name="term_hdr">Polecenia Terminala</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">ZABIJ</string>
+		<string name="term_sel_folder_hdr">Przejdź do folderu startowego</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Wyczyść Dalvik Cache</string>
+		<string name="sideload_wipe_cache_chk">Wyczyść Cache</string>
+		<string name="swipe_to_sideload">Przesuń, aby włączyć Sideload</string>
+		<string name="swipe_sideload">   Włącz</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Użycie: adb sideload nazwapliku.zip</string>
+		<string name="sideload_complete">ADB Sideload zakończone</string>
+		<string name="fix_contexts_hdr">Naprawa uprawnień</string>
+		<string name="fix_contexts_note1">Uwaga: Naprawa uprawnień jest rzadko potrzebna.</string>
+		<string name="fix_contexts_note2">Naprawa uprawnień SELinux może spowodować</string>
+		<string name="fix_contexts_note3">problemy z włączeniem urządzenia.</string>
+		<string name="swipe_to_fix_contexts">Przesuń, aby naprawić</string>
+		<string name="swipe_fix_contexts">   Napraw uprawnienia</string>
+		<string name="fixing_contexts">Naprawianie uprawnień...</string>
+		<string name="fix_contexts_complete">Naprawianie uprawnień zakończone</string>
+		<string name="reboot_hdr">Restart</string>
+		<string name="install_cancel">Nie instaluj</string>
+		<string name="sel_storage_list">Wybierz pamięć</string>
+		<string name="ok_btn">OK</string>
+		<string name="install_twrp_ramdisk">Instaluj Ramdisk Recovery</string>
+		<string name="install_kernel">Instaluj Kernel</string>
+		<string name="repack_kernel_confirm_hdr">Instalacja Kernel</string>
+		<string name="repack_ramdisk_confirm_hdr">Instalacja Recovery</string>
+		<string name="repack_kernel_confirm">Zainstalować Kernel?</string>
+		<string name="repack_ramdisk_confirm">Zainstalować Recovery?</string>
+		<string name="repack_backup_first">Najpierw wykonaj kopię obecnego obrazu</string>
+		<string name="repack">Przepakuj</string>
+		<string name="swipe_to_install">Przesuń, aby zainstalować</string>
+		<string name="installing">Instalacja...</string>
+		<string name="install_complete">Instalacja ukończona</string>
+		<string name="unpack_error">Błąd przy rozpakowywaniu obrazu.</string>
+		<string name="repack_error">Błąd przy przepakowywaniu obrazu.</string>
+		<string name="modified_ramdisk_error">pliki ramdisku zostały zmodyfikowane, nie można utworzyć ramdisku do wgrania, uruchom twrp w trybie fastboot i ponów tę operację lub użyj opcji Instaluj Ramdisk Recovery.</string>
+		<string name="create_ramdisk_error">nie udało się utworzyć ramdisku do wgrania.</string>
+		<string name="unpacking_image">Rozpakowywanie {1}...</string>
+		<string name="repacking_image">Przepakowywanie {1}...</string>
+		<string name="repack_image_hdr">Wybierz obraz</string>
+		<string name="repack_overwrite_warning">Jeśli urządzenie zostało przedtem zrootowane, root zniknie i trzeba będzie zainstalować go ponownie.</string>
+		<string name="reflash_twrp">Wgraj Obecne TWRP</string>
+		<string name="reflash_twrp_confirm">Wgrać obecne TWRP?</string>
+		<string name="reflashing_twrp">Wgrywanie TWRP...</string>
+		<string name="reflash_twrp_complete">Ukończono wgrywanie TWRP</string>
+		<string name="fix_recovery_loop">Napraw Bootloop Recovery</string>
+		<string name="fix_recovery_loop_confirm">Naprawić bootloop recovery?</string>
+		<string name="fixing_recovery_loop">Naprawianie bootloopa Recovery...</string>
+		<string name="fix_recovery_loop_complete">Ukończono naprawianie bootloopa Recovery</string>
+		<string name="fixing_recovery_loop_patch">Patchowanie kernela...</string>
+		<string name="fix_recovery_loop_patch_error">Błąd przy patchowaniu kernela.</string>
+		<string name="decrypt_users">Deszyfruj użytkowników</string>
+		<string name="decrypt_users_selection">Wybierz ID użytkownika do zdeszyfrowania</string>
+		<string name="select_user">Wybierz użytkownika</string>
+		<string name="backup_storage_undecrypt_warning">Kopia zapasowa nie będzie zawierać pewnych plików użytkownika {1}, ponieważ nie jest on odszyfrowany.</string>
+		<string name="decrypting_user_fbe">Próba zdeszyfrowania FBE dla użytkownika {1}...</string>
+		<string name="decrypt_user_success_fbe">Pomyślnie zdeszyfrowano użytkownika {1}</string>
+		<string name="decrypt_user_fail_fbe">Nie udało się zdeszyfrować użytkownika {1}</string>
+		<string name="decrypt_data_enter_pass_fbe">Wprowadź hasło dla użytkownika [%tw_crypto_user_id%]</string>
+		<string name="decrypt_data_enter_pattern_fbe">Wprowadź wzór dla użytkownika [%tw_crypto_user_id%]</string>
+		<string name="multiuser_warning1">Nie wszyscy użytkownicy są zdeszyfrowani!</string>
+		<string name="multiuser_warning2">Operacje tworzenia kopii zapasowej lub jej przywrócenia mogą się nie powieść!</string>
+		<string name="multiuser_warning_accept">Kontynuuj mimo to</string>
+		<string name="multiuser_warning_hdr">Ostrzeżenie dot. wielu użytkowników</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">Kernel nie posiada obsługi odczytu uprawnień SELinux.</string>
+		<string name="full_selinux">Pełna obsługa SELinux jest dostępna.</string>
+		<string name="no_selinux">Brak obsługi SELinux (brak libselinux).</string>
+		<string name="mtp_enabled">MTP Włączone</string>
+		<string name="mtp_crash">MTP uległo awarii, wyłączczam MTP przy starcie.</string>
+		<string name="decrypt_success">Pomyślnie rozszyfrowano z domyślnym hasłem.</string>
+		<string name="unable_to_decrypt">Nie można odszyfrować z domyślnym hasłem. Może trzeba będzie wykonać Format Data.</string>
+		<string name="generating_digest1" version="2">Generowanie Digest</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * Generowanie Digest...</string>
+		<string name="digest_created" version="2"> * Digest utworzone.</string>
+		<string name="digest_error" version="2"> * Błąd Digest!</string>
+		<string name="digest_compute_error" version="2"> * Błąd przetwarzania Digest.</string>
+		<string name="current_date">(Obecna data)</string>
+		<string name="auto_generate">(Generuj automatycznie)</string>
+		<string name="unable_to_locate_partition">Nie można zlokalizować partycji '{1}' do obliczenia kopii zapasowej.</string>
+		<string name="no_partition_selected">Nie wybrano partycji do utworzenia kopii zapasowych.</string>
+		<string name="total_partitions_backup"> * Łączna liczba partycji do tworzenia kopii zapasowych: {1}</string>
+		<string name="total_backup_size"> * Łączna wielkość wszystkich danych: {1}MB</string>
+		<string name="available_space"> * Dostępne miejsce: {1}MB</string>
+		<string name="unable_locate_storage">Nie można zlokalizować urządzenia magazynującego.</string>
+		<string name="no_space">Zbyt mało wolnego miejsca w pamięci urządzenia.</string>
+		<string name="backup_started">[KOPIA ROZPOCZĘTA]</string>
+		<string name="backup_folder"> * Folder kopii zapasowej: {1}</string>
+		<string name="fail_backup_folder">Nie udało się utworzyć folderu kopii zapasowej.</string>
+		<string name="avg_backup_fs">Średnie tempo tworzenia kopii zapasowych systemu plików: {1} MB/sek</string>
+		<string name="avg_backup_img">Średnie tempo tworzenia kopii zapasowych obrazu dysku: {1} MB/sek</string>
+		<string name="total_backed_size">[UTWORZONO KOPIĘ {1} MB]</string>
+		<string name="backup_completed">[KOPIA ZAKOŃCZONA W {1} SEKUND]</string>
+		<string name="restore_started">[PRZYWRACANIE ROZPOCZĘTE]</string>
+		<string name="restore_folder">Folder przywracania: '{1}'</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} zakończono ({2} sekund)]</string>
+		<string name="verifying_digest" version="2">Weryfikowanie Digest</string>
+		<string name="skip_digest" version="2">Pomijanie sprawdzania Digest włączone przez użytkownika.</string>
+		<string name="calc_restore">Obliczanie danych przywracania...</string>
+		<string name="restore_read_only">Nie można przywrócić {1} -- zamontowane tylko do odczytu.</string>
+		<string name="restore_unable_locate">Nie można zlokalizować partycji '{1}' do przywrócenia.</string>
+		<string name="no_part_restore">Nie wybrano partycji do przywrócenia.</string>
+		<string name="restore_part_count">Przywracanie {1} partycji...</string>
+		<string name="total_restore_size">Całkowity rozmiar przywracania {1}MB</string>
+		<string name="updating_system_details">Aktualizowanie szczegółów systemu</string>
+		<string name="restore_completed">[PRZYWRACANIE ZAKOŃCZONE W {1} SEKUND]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">Błąd otwierania: '{1}' ({2})</string>
+		<string name="unable_locate_part_backup_name">Nie można zlokalizować partycji wg nazwy kopii zapasowej: '{1}'</string>
+		<string name="unable_find_part_path">Nie można zlokalizować partycji dla ścieżki '{1}'</string>
+		<string name="update_part_details">Aktualizowanie szczegółów partycji...</string>
+		<string name="update_part_details_done">...ukończono</string>
+		<string name="wiping_dalvik">Czyszczenie katalogów Dalvik Cache...</string>
+		<string name="cleaned">Wyczyszczono: {1}...</string>
+		<string name="cache_dalvik_done">-- Czyszczenie katalogów Dalvik Cache zakończone!</string>
+		<string name="dalvik_done">-- Czyszczenie Dalvik Cache zakończone!</string>
+		<string name="no_andsec">Nie znaleziono partycji android_secure.</string>
+		<string name="unable_to_locate">Nie można zlokalizować {1}.</string>
+		<string name="wiping_datamedia">Czyszczenie Pamięci Wewnętrznej -- /data/media...</string>
+		<string name="unable_to_mount">Nie można zamontować {1}</string>
+		<string name="unable_to_mount_internal">Nie można zamontować pamięci wewnętrznej</string>
+		<string name="unable_to_mount_storage">Nie można zamontować pamięci</string>
+		<string name="fail_decrypt">Niepowodzenie rozszyfrowywania danych.</string>
+		<string name="no_crypto_support">Brak obsługi crypto w tej kompilacji.</string>
+		<string name="decrypt_success_dev">Dane pomyślnie rozszyfrowane, nowe urządzenie blokowe: '{1}'</string>
+		<string name="decrypt_success_nodev">Dane pomyślnie rozszyfrowane</string>
+		<string name="done">Ukończono.</string>
+		<string name="start_partition_sd">Partycjonowanie Karty SD...</string>
+		<string name="partition_sd_locate">Nie można zlokalizować urządzenia do partycjonowania.</string>
+		<string name="ext_swap_size">Rozmiar EXT + Swap jest większy niż rozmiar Karty SD.</string>
+		<string name="remove_part_table">Usuwanie tablicy partycji...</string>
+		<string name="unable_rm_part">Unable to remove partition table.</string>
+		<string name="create_part">Tworzenie {1} partycji...</string>
+		<string name="unable_to_create_part">Nie można utworzyć {1} partycji.</string>
+		<string name="format_sdext_as">Formatowanie SD-EXT jako {1}...</string>
+		<string name="part_complete">Partycjonowanie zakończone.</string>
+		<string name="unable_to_open">Nie można otworzyć '{1}'.</string>
+		<string name="mtp_already_enabled">MTP jest już włączone</string>
+		<string name="mtp_fail">Nie udało się włączyć MTP</string>
+		<string name="no_mtp">Wsparcie MTP nieuwzględnione</string>
+		<string name="image_flash_start">[FLASHOWANIE OBRAZU ROZPOCZĘTE]</string>
+		<string name="img_to_flash">Obraz do flashowania: '{1}'</string>
+		<string name="flash_unable_locate">Nie można zlokalizować partycji '{1}' do flashowania.</string>
+		<string name="no_part_flash">Nie wybrano partycji do flashowania.</string>
+		<string name="too_many_flash">Zbyt wiele partycji wybranych do flashowania.</string>
+		<string name="invalid_flash">Wybrano nieprawidłową partycję do flashowania.</string>
+		<string name="flash_done">[FLASHOWANIE OBRAZU ZAKOŃCZONE]</string>
+		<string name="wiping">Czyszczenie {1}</string>
+		<string name="repair_not_exist">{1} nie istnieje! Nie można naprawić!</string>
+		<string name="repairing_using">Naprawianie {1} przy użyciu {2}...</string>
+		<string name="unable_repair">Nie udało się naprawić {1}.</string>
+		<string name="mount_data_footer">Nie można zamontować /data i odnaleźć stopki crypto.</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">Nie można utworzyć folderu '{1}' {2}).</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">Nie można zamontować '{1}' ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">Nie można odmontować '{1}' ({2})</string>
+		<string name="cannot_resize">Nie można zmienić rozmiaru {1}.</string>
+		<string name="repair_resize">Naprawianie {1} przed zmianą rozmiaru.</string>
+		<string name="unable_resize">Nie można zmienić rozmiaru {1}.</string>
+		<string name="no_digest_found" version="2">Nie znaleziono pliku Digest '{1}'. Proszę wyłączyć weryfikację Digest, aby przywrócić.</string>
+		<string name="digest_fail_match" version="2">Digest nie pasuje do '{1}'.</string>
+		<string name="digest_matched" version="2">Digest matched for '{1}'.</string>
+		<string name="fail_decrypt_tar">Nie udało się odszyfrować pliku tar '{1}'</string>
+		<string name="format_data_msg">Może być konieczne ponowne uruchomienie recovery, aby móc ponownie użyć /data.</string>
+		<string name="format_data_err">Nie można sformatować, aby usunąć szyfrowanie.</string>
+		<string name="formatting_using">Formatowanie {1} przy użyciu {2}...</string>
+		<string name="unable_to_wipe">Nie można wyczyścić {1}.</string>
+		<string name="cannot_wipe">Partycja {1} nie może zostać wyczyszczona.</string>
+		<string name="remove_all">Usuwanie wszystkich plików w '{1}'</string>
+		<string name="wiping_data">Czyszczenie Data bez /data/media ...</string>
+		<string name="backing_up">Tworzenie kopii zapasowej {1}...</string>
+		<string name="backup_storage_warning">Kopie zapasowe {1} nie zawierają plików z pamięci wewnętrznej, takich jak zdjęcia czy pobrane elementy.</string>
+		<string name="backing">Tworzenie kopii zapasowej</string>
+		<string name="backup_size">Rozmiar kopii zapasowej '{1}' to 0 bajtów.</string>
+		<string name="datamedia_fs_restore">UWAGA: Ta kopia /data została wykonana w sytemie plików {1}! Kopia może się nie uruchomić jeśli nie wrócisz do {1}.</string>
+		<string name="restoring">Przywracanie {1}...</string>
+		<string name="restoring_hdr">Przywracanie</string>
+		<string name="recreate_folder_err">Nie można odtworzyć {1} folderu.</string>
+		<string name="img_size_err">Rozmiar obrazu jest większy niż urządzenia docelowego</string>
+		<string name="flashing">Instalowanie {1}...</string>
+		<string name="backup_folder_set">'{1}' ustawiony jako folder kopii zapasowych</string>
+		<string name="locate_backup_err">Nie udało się zlokalizować kopii '{1}'</string>
+		<string name="set_restore_opt">Ustawienia przywracania: '{1}':</string>
+		<string name="digest_check_skip" version="2">Sprawdzanie Digest jest włączone</string>
+		<string name="ors_encrypt_restore_err">Nie można użyć OpenRecoveryScript do przywrócenia zaszyfrowanej kopii zapasowej.</string>
+		<string name="mounting">Montowanie</string>
+		<string name="unmounting">Odmontowywanie</string>
+		<string name="mounted">Zamontowano '{1}'</string>
+		<string name="unmounted">Odmontowano '{1}'</string>
+		<string name="setting">Ustawianie '{1}' do '{2}'</string>
+		<string name="setting_empty">Ustawianie '{1}' jako pusty</string>
+		<string name="making_dir1">Tworzenie Folderu</string>
+		<string name="making_dir2">Tworzenie folderu: '{1}'</string>
+		<string name="running_command">Uruchamianie poleceń</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">Uruchomiono funkcję ADB Sideload...</string>
+		<string name="need_new_adb">Wymagane ADB 1.0.32 lub nowsze, aby użyć sideload na tym urządzeniu.</string>
+		<string name="no_pwd">Hasło nie zostało podane.</string>
+		<string name="done_ors">Zakończono przetwarzanie skryptu</string>
+		<string name="injecttwrp">Zaszczepianie TWRP w obrazie boot...</string>
+		<string name="zip_err">Błąd instalowania pliku zip '{1}'</string>
+		<string name="installing_zip">Instalowanie pliku zip '{1}'</string>
+		<string name="select_backup_opt">Ustawienia kopii zapasowych:</string>
+		<string name="compression_on">Kompresja jest włączona</string>
+		<string name="digest_off" version="2">Generowanie Digest jest wyłączone</string>
+		<string name="backup_fail">Niepowodzenie wykonywania kopii zapasowej</string>
+		<string name="backup_clean">Niepowodzenie wykonywania kopii zapasowej. Oczyszczanie folderu kopii zapasowej.</string>
+		<string name="running_recovery_commands">Wykonywanie komend trybu odzyskiwania</string>
+		<string name="recovery_commands_complete">Ukończono komendy trybu odzyskiwania</string>
+		<string name="running_ors">Wykonywanie Skryptu OpenRecovery</string>
+		<string name="ors_complete">Ukończono Skrypt OpenRecovery</string>
+		<string name="no_updater_binary">Nie można znaleźć '{1}' w pliku zip.</string>
+		<string name="check_for_digest" version="2">Sprawdzanie pliku Digest...</string>
+		<string name="invalid_zip_format">Nieprawidłowy format pliku zip!</string>
+		<string name="fail_sysmap">Nieudane mapowanie pliku '{1}'</string>
+		<string name="verify_zip_sig">Weryfikowanie sygnatury pliku zip...</string>
+		<string name="verify_zip_fail">Niewłaściwa sygnatura pliku zip!</string>
+		<string name="verify_zip_done">Sygnatura pliku zip zweryfikowana pomyślnie.</string>
+		<string name="zip_corrupt">Plik zip uszkodzony!</string>
+		<string name="no_digest" version="2">Pomijanie sprawdzania Digest: brak pliku Digest</string>
+		<string name="digest_fail" version="2">Digest niezgodne</string>
+		<string name="digest_match" version="2">Digest zgodne</string>
+		<string name="pid_signal">Proces {1} zakończony z sygnałem: {2}</string>
+		<string name="pid_error">Proces {1} zakończony z błędem: {2}</string>
+		<string name="install_dumlock">Instalowanie HTC Dumlock do systemu...</string>
+		<string name="dumlock_restore">Przywracanie oryginalnego obrazu boot...</string>
+		<string name="dumlock_reflash">Reinstalowanie recovery do boot...</string>
+		<string name="run_script">Uruchomiono skrypt {1}...</string>
+		<string name="rename_stock">Zmieniono nazwę stock recovery w /system w celu uniknięcia podmiany TWRP przez stock ROM.</string>
+		<string name="split_backup">Dzielenie pliku kopii zapasowej na wiele plików...</string>
+		<string name="backup_error">Błąd podczas tworzenia kopii zapasowej.</string>
+		<string name="restore_error">Błąd podczas procesu przywracania.</string>
+		<string name="split_thread">Dzielenie {1} na {2} archiwa</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">%llu z %llu plików, %i%%</string>
+		<string name="size_progress">%lluMB z %lluMB, %i%%</string>
+		<string name="decrypt_cmd" version="2">Próba odszyfrowania partycji danych poprzez linię poleceń.</string>
+		<string name="base_pkg_err">Nie udało się załadować pakietów podstawowych.</string>
+		<string name="simulating">Symulowanie działań...</string>
+		<string name="backup_cancel">Anulowano wykonywanie kopii zapasowej</string>
+		<string name="config_twrp">Konfigurowanie TWRP...</string>
+		<string name="config_twrp_err">Nie można skonfigurować TWRP z tym kernelem.</string>
+		<string name="copy_log">Skopiowano log recovery do {1}.</string>
+		<string name="max_queue">Maksymalna kolejka plików osiągnięta!</string>
+		<string name="min_queue">Minimalna kolejka plików osiągnięta!</string>
+		<string name="screenshot_saved">Zrzut ekranu zapisany do {1}</string>
+		<string name="screenshot_err">Niepowodzenie wykonywania zrzutu ekranu!</string>
+		<string name="zip_wipe_cache">Jeden lub więcej plików wymaga czyszczenia Cache -- Czyszczę Cache.</string>
+		<string name="and_sec_wipe_err">Nie można wyczyścić android secure</string>
+		<string name="dalvik_wipe_err">Błąd czyszczenia Dalvik</string>
+		<string name="auto_gen">(Generuj automatycznie)</string>
+		<string name="curr_date">(Aktualna data)</string>
+		<string name="backup_name_len">Nazwa kopii zapasowej jest zbyt długa.</string>
+		<string name="backup_name_invalid">Nazwa kopii zapasowej '{1}' zawiera niedozwolony znak: '{1}'</string>
+		<string name="no_real_sdcard">To urządzenie nie ma karty SD! Przerywanie...</string>
+		<string name="cancel_sideload">Anulowanie ADB sideload...</string>
+		<string name="change_fs_err">Niepowodzenie zmiany systemu plików.</string>
+		<string name="theme_ver_err">Wersja niestandardowego motywu nie zgadza się z wersją TWRP. Użyto domyślnego motywu.</string>
+		<string name="up_a_level">(Cofnij)</string>
+		<string name="install_reboot" version="2">Ponowne uruchomienie za %tw_sleep% sekund</string>
+		<string name="adbbackup_error">Błąd z ADB Backup. Kończenie..."</string>
+		<string name="adbbackup_control_error">Nie można zapisać do kanału kontroli adb</string>
+		<string name="twrp_adbbu_option">parametr --twrp jest wymagany, aby włączyć kopię zapasową adb twrp</string>
+		<string name="partition_not_found">ścieżka: {1} nieznaleziona na liście partycji</string>
+		<string name="copy_kernel_log">Skopiowano log kernela do {1}</string>
+		<string name="copy_logcat">Skopiowano logcat do {1}</string>
+		<string name="include_kernel_log">Skopiuj log kernela</string>
+		<string name="include_logcat">Skopiuj logcat</string>
+		<string name="sha2_chk">Użyj SHA2 do haszowania</string>
+		<string name="unable_set_boot_slot">Błąd przy zmienianiu slotu bootowania bootloadera na {1}</string>
+		<string name="unmount_sys_install">Odmontuj /system przed instalacją pliku ZIP</string>
+		<string name="unmount_system">Odmontowywanie /system...</string>
+		<string name="unmount_system_err">Błąd przy odmontowywaniu /system</string>
+		<string name="flash_ab_inactive">Wgrywanie pliku zip A/B do nieaktywnego slotu: {1}</string>
+		<string name="flash_ab_reboot">Aby wgrać dodatkowe pliki zip, uruchom ponownie recovery w celu przejścia na zaktualizowany slot.</string>
+		<string name="flash_ab_both_slots">Wgraj na oba sloty</string>
+		<string name="ozip_decrypt_decryption">Rozpoczynanie deszyfrowania Ozip...</string>
+		<string name="ozip_decrypt_finish">Deszyfrowanie Ozip zakończone!</string>
+		<string name="fastboot_button">Fastboot</string>
+		<string name="enable_adb">Włącz ADB</string>
+		<string name="enable_fastboot">Włącz Fastboot</string>
+		<string name="fastboot_console_msg">Jesteś w trybie Fastboot...</string>
+		<string name="data_media_fbe_msg">TWRP nie utworzy ponownie katalogu /data/media na urządzeniu z FBE. Uruchom swój ROM, aby utworzyć /data/media</string>
+		<!-- Android Rescue Party trigger -->
+		<string name="rescue_party0">Wywołano Android Rescue Party! Co może pomóc? Spróbuj:</string>
+		<string name="rescue_party1"> 1. wyczyścić cache i/lub</string>
+		<string name="rescue_party2"> 2. sformatować partycję /data i/lub</string>
+		<string name="rescue_party3"> 3. wgrać ROM z czyszczeniem danych.</string>
+		<string name="rescue_party4">Zgłoszony problem: </string>
+		<string name="restore_system_context">Nie można uzyskać domyślnego kontekstu dla {1} -- Android może się nie uruchomić.</string>
+		<string name="change_twrp_folder_btn">Zmień folder TWRP</string>
+		<string name="change_twrp_folder_on_process">Zmienianie folderu TWRP</string>
+		<string name="change_twrp_folder_after_process">Folder TWRP zmieniony na</string>
+		<string name="tw_folder_exists">Folder o tej nazwie już istnieje!</string>
+		<string name="unmap_super_devices">Odmapuj urządzenia Super</string>
+		<string name="unmap_super_devices_confirm">Odmapować wszystkie urządzenia Super?</string>
+		<string name="unmapping_super_devices">Odmapowanie urządzeń Super...</string>
+		<string name="unmap_super_devices_complete">Odmapowano wszystkie urządzenia Super</string>
+		<string name="remove_dynamic_groups_confirm">Odmapować wszystkie urządzenia Super?</string>
+		<string name="remove_dynamic_groups">Odmapowywanie urządzeń Super...</string>
+		<string name="remove_dynamic_groups_complete">Odmapowano wszystkie urządzenia Super</string>
+		<string name="mount_vab_partitions">Urządzenia na partycji /super mogą nie zostać zamontowane do momentu ponownego uruchomienia recovery.</string>
+		<string name="merges_snapshots">Połącz migawki</string>
+		<string name="merge_snapshots_confirm">Połączyć migawki?</string>
+		<string name="merging_snapshots">Łączenie migawek...</string>
+		<string name="merging_snapshots_complete">Złączono migawki</string>
+		<string name="restore_pin_password">Przywrócić z włączonym PIN-em/hasłem?</string>
+		<string name="restore_pattern">Przywrócić z włączonym wzorem?</string>
+		<string name="restore_pin">Przywrócić z włączonym PIN-em?</string>
+		<string name="continue_restore_encrypted">Kontynuować przywracanie?</string>
+		<string name="restore_with_pin_password1">Włączony PIN/hasło</string>
+		<string name="restore_with_pin_password2">PIN/hasło powinny być wyłączone przed rozpoczęciem przywracania</string>
+		<string name="restore_with_pin1">PIN jest włączony</string>
+		<string name="restore_with_pin2">PIN powinien być wyłączony przed rozpoczęciem przywracania</string>
+		<string name="restore_with_pattern1">Wzór jest włączony</string>
+		<string name="restore_with_pattern2">Wzór powinien być wyłączony przed rozpoczęciem przywracania</string>
+		<string name="reboot_after_restore">Po pierwszym uruchomieniu Androida zaleca się jednorazowo ponownie uruchomić urządzenie.</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/pt_BR.xml b/gui/theme/common/languages/pt_BR.xml
new file mode 100644
index 0000000..9f717b5
--- /dev/null
+++ b/gui/theme/common/languages/pt_BR.xml
@@ -0,0 +1,634 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<language>
+	<display>Portuguese (Brazilian)</display>
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<string name="system">Sistema</string>
+		<string name="system_image">Imagem do sistema</string>
+		<string name="vendor">Fornecedor</string>
+		<string name="vendor_image">Imagem do fornecedor</string>
+		<string name="boot">Inicio</string>
+		<string name="recovery">Recuperação</string>
+		<string name="cache">Cache</string>
+		<string name="data">Dados</string>
+		<string name="sdcard">Cartão SD</string>
+		<string name="internal">Armazenamento interno</string>
+		<string name="microsd">Cartão Micro SD</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Seguro</string>
+		<string name="dalvik">Dalvik / Cache de arte</string>
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Dados adotados</string>
+		<string name="adopted_storage">Armazenamento adotado</string>
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% ° C</string>
+		<string name="battery_pct">Bateria: %tw_battery%</string>
+		<string name="sort_by_name">Ordenar por nome</string>
+		<string name="sort_by_date">Ordenar por data</string>
+		<string name="sort_by_size">Ordenar por tamanho</string>
+		<string name="sort_by_name_only">Nome</string>
+		<string name="sort_by_date_only">Data</string>
+		<string name="sort_by_size_only">Tamanho</string>
+		<string name="tab_general">GERAL</string>
+		<string name="tab_options">OPÇÕES</string>
+		<string name="tab_backup">CÓPIA DE SEGURANÇA</string>
+		<string name="tab_time_zone">FUSO HORÁRIO</string>
+		<string name="tab_screen">TELA</string>
+		<string name="tab_vibration">VIBRAÇÃO</string>
+		<string name="tab_language">LÍNGUA</string>
+		<string name="install_btn">Instalar</string>
+		<string name="wipe_btn">Apagar a memória</string>
+		<string name="backup_btn">Cópia de segurança</string>
+		<string name="restore_btn">Restaurar</string>
+		<string name="mount_btn">Monte</string>
+		<string name="settings_btn">Configurações</string>
+		<string name="advanced_btn">Avançado</string>
+		<string name="reboot_btn">Reiniciar</string>
+		<string name="files_btn">Arquivos</string>
+		<string name="copy_log_btn">Log da cópia</string>
+		<string name="select_type_hdr">Selecione o tipo</string>
+		<string name="install_zip_hdr">Instalar o Zip</string>
+		<string name="install_zip_btn">Instalar o Zip</string>
+		<string name="install_image_hdr">Imagem de instalação</string>
+		<string name="install_image_btn">Imagem de instalação</string>
+		<string name="install_select_file_hdr">Selecione o arquivo</string>
+		<string name="file_selector_folders_hdr">Pastas</string>
+		<string name="select_file_from_storage">Selecione o arquivo de %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Instalar</string>
+		<string name="select_storage_hdr">Selecionar armazenamento</string>
+		<string name="select_storage_btn">Selecionar armazenamento</string>
+		<string name="queue_hdr">Lista</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% de máximo de 10 arquivos na fila</string>
+		<string name="zip_queue_count_s">Arquivo %tw_zip_queue_count% de 10:</string>
+		<string name="zip_warn1">Esta operação pode instalar incompatível</string>
+		<string name="zip_warn2">software e tornar seu aparelho inutilizável.</string>
+		<string name="zip_back_cancel">Pressione para cancelar a adicionar este postal.</string>
+		<string name="zip_back_clear">Pressione o botão voltar para limpar a fila.</string>
+		<string name="folder">Pasta:</string>
+		<string name="file">Arquivo:</string>
+		<string name="zip_sig_chk">Verificação de assinatura do arquivo zipado</string>
+		<string name="inject_twrp_chk">Injetar TWRP após a instalação</string>
+		<string name="options_hdr">Opções</string>
+		<string name="confirm_flash_hdr">Confirmar o Flash</string>
+		<string name="zip_queue">Lista:</string>
+		<string name="options">Opcões:</string>
+		<string name="swipe_confirm">   Confirmar</string>
+		<string name="zip_add_btn">Adicionar mais Zips</string>
+		<string name="zip_clear_btn">Limpar fila de Zip</string>
+		<string name="install_zip_count_hdr">Instalar o Zip %tw_zip_index% de %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Instalar o Zip: %tw_file%</string>
+		<string name="failed">Falhou</string>
+		<string name="successful">Sucesso</string>
+		<string name="install_failed">Instalação falhou</string>
+		<string name="install_successful">Instalação bem sucedida</string>
+		<string name="wipe_cache_dalvik_btn">Limpe o cache/dalvik</string>
+		<string name="reboot_system_btn">Reiniciar o sistema</string>
+		<string name="install_sel_target">Selecione a partição de destino</string>
+		<string name="flash_image_select">Selecione a partição para Flash imagem:</string>
+		<string name="target_partition">Partição de destino:</string>
+		<string name="flashing_image">Piscando a imagem...</string>
+		<string name="image_flashed">Um flash de imagem</string>
+		<string name="wipe_cache_dalvik_confirm">Limpe o Cache &amp; Dalvik?</string>
+		<string name="wiping_cache_dalvik">Limpando o Cache &amp; Dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Cache de &amp; Dalvik limpar completas</string>
+		<string name="swipe_wipe">Deslize para limpar</string>
+		<string name="swipe_wipe_s">   Limpe</string>
+		<string name="no_os1">Sem OS instalado! Você é</string>
+		<string name="no_osrb">tem certeza que deseja reiniciar?</string>
+		<string name="no_ospo">claro que gostaria de poder fora?</string>
+		<string name="rebooting">Reiniciando...</string>
+		<string name="swipe_reboot">Deslize para reiniciar</string>
+		<string name="swipe_reboot_s">   Reiniciar</string>
+		<string name="swipe_flash">Deslize para confirmar Flash</string>
+		<string name="confirm_action">Confirmar a ação</string>
+		<string name="back_cancel">Pressione o botão voltar para cancelar.</string>
+		<string name="cancel_btn">Cancelar</string>
+		<string name="wipe_hdr">Apagar a memória</string>
+		<string name="factory_reset_hdr">Reset de fábrica</string>
+		<string name="factory_reset_btn">Reset de fábrica</string>
+		<string name="factory_reset1">Apaga dados, Cache e Dalvik</string>
+		<string name="factory_reset2">(não incluindo armazenamento interno)</string>
+		<string name="factory_reset3">Na maioria das vezes isto é</string>
+		<string name="factory_reset4">a única limpeza que você precisa.</string>
+		<string name="factory_resetting">Reset de fábrica...</string>
+		<string name="advanced_wipe_hdr">Limpeza avançada</string>
+		<string name="advanced_wipe_btn">Limpeza avançada</string>
+		<string name="wipe_enc_confirm">Limpe a criptografia de dados?</string>
+		<string name="formatting_data">Formatação de dados...</string>
+		<string name="swipe_format_data">Deslize para formatar dados</string>
+		<string name="swipe_format_data_s">   Formato de dados</string>
+		<string name="factory_reset_complete">Reset de fábrica completa</string>
+		<string name="sel_part_hdr">Selecione as partições</string>
+		<string name="wipe_sel_confirm">Limpar partição (s) selecionada (s)?</string>
+		<string name="wiping_part">Limpar partição (s)...</string>
+		<string name="wipe_complete">Limpeza completa</string>
+		<string name="sel_part_wipe">Selecione as partições para limpeza:</string>
+		<string name="invalid_part_sel">Seleção de partição inválida</string>
+		<string name="format_data_hdr">Formatação de dados</string>
+		<string name="format_data_btn">Formatação de dados</string>
+		<string name="format_data_ptr1">Formatar dados limpará todos seus aplicativos</string>
+		<string name="format_data_ptr2">os backups, mídia, fotos, vídeos e</string>
+		<string name="format_data_ptr3">remove a criptografia no armazenamento interno.</string>
+		<string name="format_data_adopted">Incluindo o armazenamento adotado</string>
+		<string name="format_data_lcp1">Formatar dados limpará todos seus apps, backups, fotos, vídeos, mídia, e</string>
+		<string name="format_data_lcp2">remove a criptografia no armazenamento interno.</string>
+		<string name="format_data_wtc1">Formatar dados limpará todos seus aplicativos,</string>
+		<string name="format_data_wtc2">cópia de segurança e mídia. Isto não pode ser desfeito.</string>
+		<string name="format_data_undo">Isto não pode ser desfeito.</string>
+		<string name="format_data_complete">Formatação de dados completa</string>
+		<string name="yes_continue">Digite Sim para continuar.  Pressione para cancelar.</string>
+		<string name="part_opt_hdr">Partição de opções para: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Selecionar Ação</string>
+		<string name="file_sys_opt">Opções de sistema de arquivo</string>
+		<string name="partition">Partição: %tw_partition_name%</string>
+		<string name="part_mount_point">Ponto de montagem: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Sistema de arquivos: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Presente: Sim</string>
+		<string name="part_present_no">Presente: não</string>
+		<string name="part_removable_yes">Removível: Sim</string>
+		<string name="part_removable_no">Removível: não</string>
+		<string name="part_size">Tamanho: % tw_partition_size % MB</string>
+		<string name="part_used">Usado: % tw_partition_used % MB</string>
+		<string name="part_free">Livre: % tw_partition_free % MB</string>
+		<string name="part_backup_size">Tamanho do backup: % tw_partition_backup_size % MB</string>
+		<string name="resize_btn">Redimensionar o sistema de arquivos</string>
+		<string name="resize_btn_s">Redimensionar</string>
+		<string name="resize_confirm">Redimensionar o %tw_partition_name%?</string>
+		<string name="resizing">Redimensionamento...</string>
+		<string name="resize_complete">Redimensionamento completo</string>
+		<string name="swipe_resize">Deslize para redimensionar</string>
+		<string name="swipe_resize_s">   Redimensionar</string>
+		<string name="repair_btn">Reparação de sistema de arquivos</string>
+		<string name="repair_btn_s">Reparar</string>
+		<string name="repair_confirm">%tw_partition_name% de reparação?</string>
+		<string name="repairing">Reparando...</string>
+		<string name="repair_complete">Reparo completo</string>
+		<string name="swipe_repair">Deslize para reparar</string>
+		<string name="swipe_repair_s">   Reparação</string>
+		<string name="change_fs_btn">Mudança de sistema de arquivos</string>
+		<string name="change_fs_btn_s">Modificar</string>
+		<string name="change_fs_for_hdr">Alterar o sistema de arquivos para: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Partição: %tw_partition_name% &gt; selecione sistema de arquivos</string>
+		<string name="change_fs_warn1">Algumas ROMs ou kernel pode não oferecer suporte a alguns</string>
+		<string name="change_fs_warn2">sistemas de arquivos. Proceda com cautela!</string>
+		<string name="change_fs_confirm">Mudar %tw_partition_name%?</string>
+		<string name="formatting">Formatação...</string>
+		<string name="format_complete">Formatação completa</string>
+		<string name="swipe_change_fs">Deslize para a mudança</string>
+		<string name="swipe_change_s">   Mudança</string>
+		<string name="back_btn">Retornar</string>
+		<string name="wipe_enc_btn">Limpe a criptografia</string>
+		<string name="swipe_factory_reset">Deslize para o Reset de fábrica</string>
+		<string name="repair_change_btn">Reparação ou mudança de sistema de arquivos</string>
+		<string name="storage_hdr">Armazenamento: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Cópia de segurança</string>
+		<string name="backup_confirm_hdr">Confirmar a Cópia de Segurança</string>
+		<string name="encryption_tab">CRIPTOGRAFIA</string>
+		<string name="encryption">Criptografia:</string>
+		<string name="name">Nome:</string>
+		<string name="sel_part_backup">Selecione as partições de Cópia de Segurança:</string>
+		<string name="storage">Armazenamento:</string>
+		<string name="enc_disabled">desativado - definir uma senha para habilitar</string>
+		<string name="enc_enabled">ativado</string>
+		<string name="enable_backup_comp_chk">Habilitar a compactação</string>
+		<string name="skip_digest_backup_chk" version="2">Geração de Skip Digest durante a cópia de segurança</string>
+		<string name="disable_backup_space_chk">Desativar a verificação de espaço livre</string>
+		<string name="refresh_sizes_btn">Atualizar os tamanhos</string>
+		<string name="swipe_backup">Deslize para a Cópia de Segurança</string>
+		<string name="append_date_btn">Acrescentar a data</string>
+		<string name="backup_name_exists">Uma Cópia de Segurança com esse nome já existe!</string>
+		<string name="encrypt_backup">Criptografar sua cópia de segurança?</string>
+		<string name="enter_pass">Digite a senha:</string>
+		<string name="enter_pass2">Digite a senha novamente:</string>
+		<string name="pass_not_match">As senhas digitadas não conferem!</string>
+		<string name="partitions">Partições:</string>
+		<string name="disabled">Desabilitar</string>
+		<string name="enabled">Ativado</string>
+		<string name="backup_name_hdr">Nome do conjunto de Backup</string>
+		<string name="progress">Progresso:</string>
+		<string name="backup_complete">Cópia de segurança concluído</string>
+		<string name="restore_hdr">Restaurar</string>
+		<string name="sel_backup_hdr">Selecione a cópia de segurança</string>
+		<string name="restore_sel_store_hdr">Selecione a cópia de segurança de %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Selecione o pacote para restaurar:</string>
+		<string name="restore_enc_backup_hdr">Cópia de segurança criptografada</string>
+		<string name="restore_dec_fail">Senha falhou, por favor tente novamente!</string>
+		<string name="del_backup_btn">Excluir Cópia de Segurança</string>
+		<string name="del_backup_confirm">Excluir Cópia de Segurança?</string>
+		<string name="del_backup_confirm2">Isto não pode ser desfeito!</string>
+		<string name="deleting_backup">Exclusão de Cópia de Segurança...</string>
+		<string name="backup_deleted">Exclusão de Cópia de Segurança completa</string>
+		<string name="swipe_delete">Deslize para excluir</string>
+		<string name="swipe_delete_s">   Excluir</string>
+		<string name="restore_try_decrypt">Cópia de Segurança criptografada - tentando descriptografia</string>
+		<string name="restore_try_decrypt_s">Tentando a descriptografia</string>
+		<string name="restore_backup_date">Cópia de Segurança feita em %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Selecione partições a restaurar:</string>
+		<string name="restore_enable_digest_chk" version="2">Habilitar verificação de Digest dos arquivos de Cópia de Segurança</string>
+		<string name="restore_complete">Restauração completa</string>
+		<string name="swipe_restore">Deslize para restaurar</string>
+		<string name="swipe_restore_s">   Restauração</string>
+		<string name="rename_backup_hdr">Renomear a Cópia de Segurança</string>
+		<string name="rename_backup_confirm">Renomear a Cópia de Segurança?</string>
+		<string name="rename_backup_confirm2">Isto não pode ser desfeito!</string>
+		<string name="renaming_backup">Renomear a Cópia de Segurança...</string>
+		<string name="rename_backup_complete">Renomear a Cópia de Segurança completa</string>
+		<string name="swipe_to_rename">Deslize para renomear</string>
+		<string name="swipe_rename">   Renomear</string>
+		<string name="confirm_hdr">Confirme</string>
+		<string name="mount_hdr">Monte</string>
+		<string name="mount_sel_part">Selecione as partições para montagem:</string>
+		<string name="mount_sys_ro_chk">Montar a partição de sistema somente leitura</string>
+		<string name="mount_sys_ro_s_chk">Sistema de montagem RO</string>
+		<string name="decrypt_data_btn">Descriptografar dados</string>
+		<string name="disable_mtp_btn">Desabilitar o MTP</string>
+		<string name="enable_mtp_btn">Habilitar o MTP</string>
+		<string name="mount_usb_storage_btn">Montar armazenamento USB</string>
+		<string name="usb_storage_hdr">Armazenamento USB</string>
+		<string name="usb_stor_mnt1">Armazenamento USB montado</string>
+		<string name="usb_stor_mnt2">Não se esqueça de remover com segurança seu dispositivo</string>
+		<string name="usb_stor_mnt3">do seu computador antes de desmontar!</string>
+		<string name="unmount_btn">Desmonte</string>
+		<string name="rb_system_btn">Sistema</string>
+		<string name="rb_poweroff_btn">Desligar</string>
+		<string name="rb_recovery_btn">Recuperação</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Baixar</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Desligar...</string>
+		<string name="swipe_power_off">Deslize para desligar</string>
+		<string name="swipe_power_off_s">Desligar</string>
+		<string name="sys_ro_hdr">Partição do sistema não modificado</string>
+		<string name="sys_ro_keep">Manter o sistema apenas leitura?</string>
+		<string name="sys_rop1">TWRP pode deixar sua partição do sistema não modificada</string>
+		<string name="sys_rop2">para tornar mais fácil para você tomar atualizações oficiais.</string>
+		<string name="sys_rop3">TWRP será incapaz de impedir o stock ROM de</string>
+		<string name="sys_rop4">substituindo TWRP e não oferecerá a raiz do seu dispositivo.</string>
+		<string name="sys_rop5">Instalar zips ou executar operações adb pode ainda</string>
+		<string name="sys_rop6">modifica a partição do sistema.</string>
+		<string name="sys_rol1">TWRP pode deixar sua partição do sistema não-modificada para torná-lo mais fácil para você tomar atualizações oficiais.</string>
+		<string name="sys_rol2">TWRP será incapaz de impedir que o stock ROM substituindo TWRP e não oferecerá a raiz do seu dispositivo.</string>
+		<string name="sys_rol3">Instalar zips ou executar operações adb ainda pode modificar a partição do sistema.</string>
+		<string name="sys_ro_never_show_chk">Nunca mostrar esta tela durante a inicialização de novo</string>
+		<string name="sys_ro_keep_ro_btn">Manter apenas leitura</string>
+		<string name="swipe_allow_mod">Deslize para permitir modificações</string>
+		<string name="swipe_allow_mod_s">Permitir modificações</string>
+		<string name="settings_hdr">Configurações</string>
+		<string name="settings_gen_hdr">Geral</string>
+		<string name="settings_gen_s_hdr">Geral</string>
+		<string name="settings_gen_btn">Geral</string>
+		<string name="use_rmrf_chk">Usar o rm -rf em vez de formatação</string>
+		<string name="use24clock_chk">Relógio 24 horas de uso</string>
+		<string name="rev_navbar_chk">Layout de barra invertida</string>
+		<string name="simact_chk">Simular ações para tema de teste</string>
+		<string name="simfail_chk">Simular falha para ações</string>
+		<string name="ctr_navbar_rdo">Botões de barra de navegação do centro</string>
+		<string name="lft_navbar_rdo">Alinhar à esquerda os botões de navegação</string>
+		<string name="rht_navbar_rdo">Bem, alinhar botões da barra de navegação</string>
+		<string name="restore_defaults_btn">Restaurar padrões</string>
+		<string name="settings_tz_btn">Fuso Horário</string>
+		<string name="settings_screen_btn">Tela</string>
+		<string name="settings_screen_bright_btn">Brilho da tela</string>
+		<string name="settings_vibration_btn">Vibração</string>
+		<string name="settings_language_btn">Idioma</string>
+		<string name="time_zone_hdr">Fuso Horário</string>
+		<string name="sel_tz_list">Selecione o Fuso Horário:</string>
+		<string name="utcm11">(UTC -11) Samoa, Midway Island</string>
+		<string name="utcm10">(UTC -10) Havaí</string>
+		<string name="utcm9">(UTC -9) Alasca</string>
+		<string name="utcm8">(UTC -8) Hora do Pacífico</string>
+		<string name="utcm7">(UTC -7) Tempo de Mountain</string>
+		<string name="utcm6">(UTC -6) Horário central</string>
+		<string name="utcm5">(UTC -5) Hora do leste</string>
+		<string name="utcm4">(UTC -4) Horário do Atlântico</string>
+		<string name="utcm3">(UTC -3) Brasil, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Mid-Atlantic</string>
+		<string name="utcm1">(UTC -1) Açores, Cabo Verde</string>
+		<string name="utc0">(UTC 0) Londres, Dublin, Lisboa</string>
+		<string name="utcp1">(UTC + 1) Berlim, Bruxelas, Paris</string>
+		<string name="utcp2">(UTC + 2) Atenas, Istambul, África do Sul</string>
+		<string name="utcp3">(UTC + 3) Moscou, Bagdá</string>
+		<string name="utcp4">(UTC + 4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC + 5) Ecaterimburgo, Islamabad</string>
+		<string name="utcp6">(UTC + 6) Almaty, Dhaka, Colombo</string>
+		<string name="utcp7">(UTC + 7) Bangcoc, Hanói, Jacarta</string>
+		<string name="utcp8">(UTC + 8) Beijing, Singapura, Hong Kong</string>
+		<string name="utcp9">(UTC + 9) Tokyo, Seoul, Yakutsk</string>
+		<string name="utcp10">(UTC + 10) Austrália oriental, Guam</string>
+		<string name="utcp11">(UTC + 11) Vladivostok, Ilhas Salomão</string>
+		<string name="utcp12">(UTC + 12) Auckland, Wellington, Fiji</string>
+		<string name="sel_tz_offset">Selecione o deslocamento (geralmente 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Nenhum</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Use o horário de Verão (DST)</string>
+		<string name="curr_tz">Fuso horário atual: %tw_time_zone%</string>
+		<string name="curr_tz_s">Fuso horário atual:</string>
+		<string name="set_tz_btn">Definir fuso horário</string>
+		<string name="settings_screen_hdr">Tela</string>
+		<string name="settings_screen_timeout_hdr">Tempo limite da Tela</string>
+		<string name="enable_timeout_chk">Habilitar tempo de tela</string>
+		<string name="screen_to_slider">Tempo limite de tela em segundos:</string>
+		<string name="screen_to_slider_s">Tempo limite de tela em segundos (0 = desativado): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Tempo limite de tela configuração indisponível</string>
+		<string name="screen_bright_slider">Brilho: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Configuração de brilho não está disponível</string>
+		<string name="vibration_hdr">Vibração</string>
+		<string name="button_vibration_hdr">Botão de vibração</string>
+		<string name="kb_vibration_hdr">Vibração de teclado</string>
+		<string name="act_vibration_hdr">Ação de vibração</string>
+		<string name="button_vibration">Botão de vibração:</string>
+		<string name="kb_vibration">Vibração de teclado:</string>
+		<string name="act_vibration">Ação de vibração:</string>
+		<string name="select_language">Selecionar idioma:</string>
+		<string name="sel_lang_btn">Selecionar idioma</string>
+		<string name="set_language_btn">Definir idioma</string>
+		<string name="advanced_hdr">Avançado</string>
+		<string name="copy_log_confirm">Copiar o Log para o cartão SD?</string>
+		<string name="copying_log">Copiar o Log para o cartão SD...</string>
+		<string name="copy_log_complete">Cópia de registro completo</string>
+		<string name="part_sd_btn">Cartão SD de partição</string>
+		<string name="part_sd_s_btn">Cartão SD</string>
+		<string name="file_manager_btn">Gerenciador de Arquivos</string>
+		<string name="language_hdr">Idioma</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="reload_theme_btn">Tema carregado</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Injetar TWRP</string>
+		<string name="inject_twrp_confirm">Re-injetar TWRP?</string>
+		<string name="injecting_twrp">Re-injetar TWRP...</string>
+		<string name="inject_twrp_complete">TWRP injeção completa</string>
+		<string name="swipe_to_confirm">Deslize para confirmar</string>
+		<string name="part_sd_hdr">Cartão SD de partição</string>
+		<string name="invalid_partsd_sel">Você deve selecionar um dispositivo removível</string>
+		<string name="part_sd_lose">Você vai perder todos os arquivos no cartão SD!</string>
+		<string name="part_sd_undo">Esta ação não pode ser desfeita!</string>
+		<string name="part_sd_ext_sz">Tamanho EXT:</string>
+		<string name="part_sd_swap_sz">Tamanho de swap:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Sistema de arquivos:</string>
+		<string name="swipe_part_sd">Deslize para particionar</string>
+		<string name="swipe_part_sd_s">Partição</string>
+		<string name="partitioning_sd">Particionando o cartão SD...</string>
+		<string name="partitioning_sd2">Isso levará alguns minutos.</string>
+		<string name="part_sd_complete">Particionamento completo</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Restaurar a Inicialização Original</string>
+		<string name="dumlock_restore_confirm">Restaurar a imagem de inicialização original?</string>
+		<string name="dumlock_restoring">Restauração de inicialização Original...</string>
+		<string name="dumlock_restore_complete">Restauração completa de inicialização Original</string>
+		<string name="dumlock_reflash_btn">Reflash recuperação</string>
+		<string name="dumlock_reflash_confirm">Reflash recuperação para arrancar?</string>
+		<string name="dumlock_reflashing">Reflash recuperação para arrancar...</string>
+		<string name="dumlock_reflash_complete">Flash de recuperação de inicialização completa</string>
+		<string name="dumlock_install_btn">Instalar o HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Instalar HTC dumlock arquivos de ROM?</string>
+		<string name="dumlock_installing">Instalando o HTC Dumlock...</string>
+		<string name="dumlock_install_complete">HTC Dumlock instalação completa</string>
+		<string name="swipe_to_unlock">Deslize para desbloquear</string>
+		<string name="swipe_unlock">   Desbloquear</string>
+		<string name="fm_hdr">Gerenciador de arquivos</string>
+		<string name="fm_sel_file">Selecione um arquivo ou pasta</string>
+		<string name="fm_type_folder">Pasta</string>
+		<string name="fm_type_file">Arquivo</string>
+		<string name="fm_choose_act">Escolha uma Ação</string>
+		<string name="fm_selected">%tw_fm_type% selecionado:</string>
+		<string name="fm_copy_btn">Cópia</string>
+		<string name="fm_copy_file_btn">Copiar arquivo</string>
+		<string name="fm_copy_folder_btn">Copiar pasta</string>
+		<string name="fm_copying">Copiando</string>
+		<string name="fm_move_btn">Mover</string>
+		<string name="fm_moving">Movendo</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Excluir</string>
+		<string name="fm_deleting">Excluíndo</string>
+		<string name="fm_rename_btn">Renomear</string>
+		<string name="fm_rename_file_btn">Renomear arquivo</string>
+		<string name="fm_rename_folder_btn">Renomear Pasta</string>
+		<string name="fm_renaming">Renomeando</string>
+		<string name="fm_sel_dest">Selecione a pasta de destino</string>
+		<string name="fm_sel_curr_folder">Selecione a pasta atual</string>
+		<string name="fm_rename_hdr">Renomear</string>
+		<string name="fm_set_perms_hdr">Definir permissões</string>
+		<string name="fm_perms">Permissões:</string>
+		<string name="fm_complete">Operação de arquivo completo</string>
+		<string name="decrypt_data_hdr">Descriptografar dados</string>
+		<string name="decrypt_data_enter_pass">Digite a senha.</string>
+		<string name="decrypt_data_enter_pattern">Digite padrão.</string>
+		<string name="decrypt_data_trying">Tentando a descriptografia</string>
+		<string name="term_hdr">Comando no terminal</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">MATAR</string>
+		<string name="term_sel_folder_hdr">Navegue até a pasta a começar</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Limpe Dalvik Cache</string>
+		<string name="sideload_wipe_cache_chk">Limpe o Cache</string>
+		<string name="swipe_to_sideload">Deslize para começar Sideload</string>
+		<string name="swipe_sideload">   Início</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Uso: adb sideload filename.zip</string>
+		<string name="sideload_complete">ADB Sideload completa</string>
+		<string name="reboot_hdr">Reiniciar</string>
+		<string name="install_cancel">Não instale</string>
+		<string name="sel_storage_list">Selecionar armazenamento</string>
+		<string name="ok_btn">OK</string>
+		<string name="no_kernel_selinux">Kernel não tem suporte para leitura de contextos do SELinux.</string>
+		<string name="full_selinux">Suporte completo do SELinux está presente.</string>
+		<string name="no_selinux">Não há suporte SELinux (não libselinux).</string>
+		<string name="mtp_enabled">MTP-habilitado</string>
+		<string name="mtp_crash">MTP caiu, não começando MTP na inicialização.</string>
+		<string name="decrypt_success">Com êxito descriptografada com a senha padrão.</string>
+		<string name="unable_to_decrypt">Não é possível descriptografar com senha padrão. Você pode precisar executar um formato de dados.</string>
+		<string name="generating_digest1" version="2">Gerar o Digest</string>
+		<string name="generating_digest2" version="2"> * Gerar o Digest...</string>
+		<string name="digest_created" version="2"> * Digest criado.</string>
+		<string name="digest_error" version="2"> * Digest erro!</string>
+		<string name="digest_compute_error" version="2"> * Erro Digest de computação.</string>
+		<string name="current_date">(Data atual)</string>
+		<string name="auto_generate">(Auto gerar)</string>
+		<string name="unable_to_locate_partition">Não é possível localizar a partição \'{1}\' para cálculos de cópia de segurança.</string>
+		<string name="no_partition_selected">Sem partições selecionadas para a cópia de segurança.</string>
+		<string name="total_partitions_backup"> * Total número de partições de cópia de segurança: {1}</string>
+		<string name="total_backup_size"> * O tamanho total de todos os dados: {1}MB</string>
+		<string name="available_space"> * Espaço disponível: {1}MB</string>
+		<string name="unable_locate_storage">Não é possível localizar o dispositivo de armazenamento.</string>
+		<string name="no_space">Não há suficiente espaço livre no armazenamento.</string>
+		<string name="backup_started">[CÓPIA DE SEGURANÇA COMEÇADO]</string>
+		<string name="backup_folder"> * Cópia de segurança da pasta: {1}</string>
+		<string name="fail_backup_folder">Não conseguiu fazer a pasta de cópia de segurança.</string>
+		<string name="avg_backup_fs">Taxa média da cópia de segurança para sistemas de arquivo: {1} MB/seg</string>
+		<string name="avg_backup_img">Taxa média de cópia de segurança para drives de imagens: {1} MB/seg</string>
+		<string name="total_backed_size">[{1} MB TOTAL CÓPIA DE SEGURANÇA]</string>
+		<string name="backup_completed">[CÓPIA DE SEGURANÇA CONCLUÍDO EM {1} SEGUNDOS]</string>
+		<string name="restore_started">[RESTAURAÇÃO COMEÇADA]</string>
+		<string name="restore_folder">Restaurar a pasta: \'{1}\'</string>
+		<string name="verifying_digest" version="2">Verificando o Digest</string>
+		<string name="skip_digest" version="2">Saltando de verificação Digest com base na configuração de usuário.</string>
+		<string name="calc_restore">Cálculo de restauração de detalhes...</string>
+		<string name="restore_read_only">Não é possível restaurar {1}..--montada somente leitura.</string>
+		<string name="restore_unable_locate">Não é possível localizar a partição \'{1}\' para restaurar.</string>
+		<string name="no_part_restore">Sem partições selecionadas para restauração.</string>
+		<string name="restore_part_count">Restaurando partições {1}...</string>
+		<string name="total_restore_size">Tamanho total da restauração é {1}MB</string>
+		<string name="updating_system_details">Detalhes do sistema de atualização</string>
+		<string name="restore_completed">[RESTAURAÇÃO COMPLETADA EM {1} SEGUNDOS]</string>
+		<string name="error_opening_strerr">Erro ao abrir: \'{1}\' ({2})</string>
+		<string name="unable_locate_part_backup_name">Não é possível localizar a partição pelo nome da cópia de segurança: \'{1}\'</string>
+		<string name="unable_find_part_path">Não é possível localizar a partição para o caminho \'{1}\'</string>
+		<string name="update_part_details">Atualização de partição de detalhes...</string>
+		<string name="update_part_details_done">... feito</string>
+		<string name="wiping_dalvik">Limpando diretórios Dalvik Cache...</string>
+		<string name="cleaned">Limpeza: {1}...</string>
+		<string name="dalvik_done">-Limpado completamente diretórios Dalvik Cache!</string>
+		<string name="no_andsec">Não encontradas partições seguras android.</string>
+		<string name="unable_to_locate">Não é possível localizar o {1}.</string>
+		<string name="wiping_datamedia">Limpando o armazenamento interno... /data/media...</string>
+		<string name="unable_to_mount">Incapaz de montar {1}</string>
+		<string name="unable_to_mount_internal">Incapaz de montar memória interna</string>
+		<string name="unable_to_mount_storage">Incapaz de montar o armazenamento</string>
+		<string name="fail_decrypt">Falha ao descriptografar os dados.</string>
+		<string name="no_crypto_support">Não há suporte para criptografia compilada para esta compilação.</string>
+		<string name="decrypt_success_dev">Dados descriptografados com sucesso, o novo dispositivo de bloco: \'{1}\'</string>
+		<string name="done">Concluído.</string>
+		<string name="start_partition_sd">Particionando o cartão SD...</string>
+		<string name="partition_sd_locate">Não é possível localizar o dispositivo para a partição.</string>
+		<string name="ext_swap_size">EXT + Swap tamanho é maior do que o tamanho do sdcard.</string>
+		<string name="remove_part_table">Remoção de tabela de partição...</string>
+		<string name="unable_rm_part">Não é possível remover a tabela de partição.</string>
+		<string name="create_part">Criando a partição de {1}...</string>
+		<string name="unable_to_create_part">Não é possível criar partição de {1}.</string>
+		<string name="format_sdext_as">Formatando sd-ext como {1}...</string>
+		<string name="part_complete">Particionamento completo.</string>
+		<string name="unable_to_open">Não é possível abrir \'{1}\'.</string>
+		<string name="mtp_already_enabled">MTP já habilitado</string>
+		<string name="mtp_fail">Falha ao ativar o MTP</string>
+		<string name="no_mtp">Suporte MTP não incluído</string>
+		<string name="image_flash_start">[IMAGEM FLASH COMEÇADO]</string>
+		<string name="img_to_flash">Imagem a piscar: \'{1}\'</string>
+		<string name="flash_unable_locate">Não é possível localizar a partição \'{1}\' para piscar.</string>
+		<string name="no_part_flash">Sem partições seleccionadas para piscar.</string>
+		<string name="too_many_flash">Muitas partições selecionadas para piscar.</string>
+		<string name="invalid_flash">Partição especificada inválida para flash.</string>
+		<string name="flash_done">[IMAGEM FLASH CONCLUÍDO]</string>
+		<string name="wiping">Limpando {1}</string>
+		<string name="repair_not_exist">{1} não existe! Não pode reparar!</string>
+		<string name="repairing_using">Reparação de {1} usando {2}...</string>
+		<string name="unable_repair">Não é possível reparar {1}.</string>
+		<string name="mount_data_footer">Não poderia montar /data e incapaz de encontrar o rodapé de criptografia.</string>
+		<string name="create_folder_strerr">Não foi possível criar a pasta \'{1}\' ({2}).</string>
+		<string name="fail_mount">Falha ao montar \'{1}\' ({2})</string>
+		<string name="fail_unmount">Falha ao montar \'{1}\' ({2})</string>
+		<string name="cannot_resize">Não é possível redimensionar {1}.</string>
+		<string name="repair_resize">Reparação de {1} antes de redimensionamento.</string>
+		<string name="unable_resize">Não é possível redimensionar {1}.</string>
+		<string name="no_digest_found" version="2">Nenhum arquivo Digest foi encontrado para \'{1}\'. Por favor, cancelar a seleção de verificação Digest habilitar para restaurar.</string>
+		<string name="digest_fail_match" version="2">Digest não conseguiram corresponder na \'{1}\'.</string>
+		<string name="digest_matched" version="2">Digest matched for '{1}'.</string>
+		<string name="fail_decrypt_tar">Falha ao descriptografar arquivo tar \'{1}\'</string>
+		<string name="format_data_msg">Você pode precisar reiniciar recuperação para ser capaz de usar /data novamente.</string>
+		<string name="format_data_err">Não é possível formatar para remover a criptografia.</string>
+		<string name="formatting_using">Formatação {1} usando {2}...</string>
+		<string name="unable_to_wipe">Não é possível limpar {1}.</string>
+		<string name="cannot_wipe">Partição {1} não pode ser apagada.</string>
+		<string name="remove_all">Remover todos os arquivos em \'{1}\'</string>
+		<string name="wiping_data">Limpando os dados sem limpar /data/media...</string>
+		<string name="backing_up">Cópia de Segurança de {1}...</string>
+		<string name="backing">Fazendo cópia de segurança</string>
+		<string name="backup_size">Tamanho do arquivo de cópia de segurança para \'{1}\' é 0 bytes.</string>
+		<string name="datamedia_fs_restore">Aviso: Essa cópia de segurança /data foi feita com o sistema de arquivos de {1}! A cópia de segurança não pode arrancar a menos que você muda de volta para {1}.</string>
+		<string name="restoring">Restauração de {1}...</string>
+		<string name="restoring_hdr">Restaurando</string>
+		<string name="recreate_folder_err">Não é possível recriar a pasta de {1}.</string>
+		<string name="img_size_err">Tamanho da imagem é maior do que o dispositivo de destino</string>
+		<string name="flashing">Piscando {1}...</string>
+		<string name="backup_folder_set">Pasta da cópia de segurança definida como \'{1}\'</string>
+		<string name="locate_backup_err">Não é possível localizar a cópia de segurança \'{1}\'</string>
+		<string name="set_restore_opt">Definir opções de restauração: \'{1}\':</string>
+		<string name="digest_check_skip" version="2">Ignorar verificação de Digest é na</string>
+		<string name="ors_encrypt_restore_err">Não é possível usar OpenRecoveryScript para restaurar uma cópia de segurança criptografada.</string>
+		<string name="mounting">Montando</string>
+		<string name="unmounting">Desmontando</string>
+		<string name="mounted">Montada \'{1}\'</string>
+		<string name="unmounted">Desmontado \'{1}\'</string>
+		<string name="setting">Configuração \'{1}\' para \'{2}\'</string>
+		<string name="setting_empty">Configuração \'{1}\' para o vazio</string>
+		<string name="making_dir1">Fazendo o diretório</string>
+		<string name="making_dir2">Fazendo o diretório: \'{1}\'</string>
+		<string name="running_command">Executando o comando</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">Iniciado sideload ADB característica...</string>
+		<string name="need_new_adb">Você precisa adb 1.0.32 ou mais recente para sideload para este dispositivo.</string>
+		<string name="no_pwd">Nenhuma senha fornecida.</string>
+		<string name="done_ors">Arquivo de script de processamento feito</string>
+		<string name="injecttwrp">Injetando TWRP na imagem de inicialização...</string>
+		<string name="zip_err">Erro ao instalar o arquivo zip \'{1}\'</string>
+		<string name="installing_zip">Instalando o arquivo zip \'{1}\'</string>
+		<string name="select_backup_opt">Definir opções da cópia de segurança:</string>
+		<string name="compression_on">Compressão é na</string>
+		<string name="digest_off" version="2">Geração de Digest está fora</string>
+		<string name="backup_fail">Cópia de segurança falhou</string>
+		<string name="backup_clean">Falha de backup. Limpar a pasta de Backup.</string>
+		<string name="running_recovery_commands">Executando comandos de recuperação</string>
+		<string name="recovery_commands_complete">Comandos de recuperação completa</string>
+		<string name="running_ors">OpenRecoveryScript em execução</string>
+		<string name="ors_complete">OpenRecoveryScript completa</string>
+		<string name="no_updater_binary">Não foi possível encontrar \'{1}\' no arquivo zip.</string>
+		<string name="check_for_digest" version="2">Verificação de arquivo Digest...</string>
+		<string name="fail_sysmap">Falha ao mapear o arquivo \'{1}\'</string>
+		<string name="verify_zip_sig">Verificar a assinatura do zip...</string>
+		<string name="verify_zip_fail">Falha de verificação de assinatura do arquivo zip!</string>
+		<string name="verify_zip_done">Assinatura zip é verificada com êxito.</string>
+		<string name="zip_corrupt">O arquivo zip está corrompido!</string>
+		<string name="no_digest" version="2">Saltando de verificação Digest: nenhum arquivo Digest encontrado</string>
+		<string name="digest_fail" version="2">Não coincide com Digest</string>
+		<string name="digest_match" version="2">Correspondência de Digest</string>
+		<string name="pid_signal">{1} processo terminou com sinal: {2}</string>
+		<string name="pid_error">{1} processo terminou com erro: {2}</string>
+		<string name="install_dumlock">Instalação HTC Dumlock sistema...</string>
+		<string name="dumlock_restore">Restauração de inicialização Original...</string>
+		<string name="dumlock_reflash">Regravando recuperação para arrancar...</string>
+		<string name="run_script">Executando o script {1}...</string>
+		<string name="rename_stock">Renomeado o arquivo de recuperação das ações em / / sistema para impedir que o stock ROM substituindo TWRP.</string>
+		<string name="split_backup">Quebrando o arquivo de cópia de segurança em vários arquivos...</string>
+		<string name="backup_error">Erro ao criar a cópia de segurança.</string>
+		<string name="restore_error">Erro durante o processo de restauração.</string>
+		<string name="split_thread">Dividindo o segmento ID {1} em {2} de arquivo</string>
+		<string name="file_progress">%llu de arquivos %llu, %i%%</string>
+		<string name="size_progress">%lluMB de %lluMB, %i%%</string>
+		<string name="decrypt_cmd">A tentativa de decifrar a partição de dados via linha de comando.</string>
+		<string name="base_pkg_err">Falha ao carregar pacotes base.</string>
+		<string name="simulating">Simulando ações...</string>
+		<string name="backup_cancel">Cópia de segurança cancelado</string>
+		<string name="config_twrp">Configurando TWRP...</string>
+		<string name="config_twrp_err">Não é possível configurar TWRP com este kernel.</string>
+		<string name="copy_log">Copiado o registro de recuperação para {1}.</string>
+		<string name="max_queue">Arquivo zip máximo alcançado!</string>
+		<string name="min_queue">Arquivo zip mínimo alcançado!</string>
+		<string name="screenshot_saved">Captura de tela foi salvo em {1}</string>
+		<string name="screenshot_err">Não foi possível tirar uma cópia de tela!</string>
+		<string name="zip_wipe_cache">Um ou mais zip solicitado uma limpeza de cache..--limpando cache agora.</string>
+		<string name="and_sec_wipe_err">Não é possível limpar o andróide seguro</string>
+		<string name="dalvik_wipe_err">Falha ao limpar dalvik</string>
+		<string name="auto_gen">(Auto gerar)</string>
+		<string name="curr_date">(Data atual)</string>
+		<string name="backup_name_len">Nome da cópia de segurança é muito longo.</string>
+		<string name="backup_name_invalid">\'{1}\' nome da cópia de segurança contém o caractere inválido: \'{1}\'</string>
+		<string name="no_real_sdcard">Este dispositivo não tem um cartão SD real! Abortando!</string>
+		<string name="cancel_sideload">Cancelamento de sideload ADB...</string>
+		<string name="change_fs_err">Sistema de arquivo erro em mudança.</string>
+		<string name="theme_ver_err">Versão personalizada do tema não coincide com a versão TWRP. Usando o tema conservado em estoque.</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/pt_PT.xml b/gui/theme/common/languages/pt_PT.xml
new file mode 100644
index 0000000..ee589eb
--- /dev/null
+++ b/gui/theme/common/languages/pt_PT.xml
@@ -0,0 +1,723 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<language>
+	<display>Português</display>
+
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<!-- Partition display names -->
+		<string name="system">Sistema</string>
+		<string name="system_image">Imagem do sistema</string>
+		<string name="vendor">Fornecedor</string>
+		<string name="vendor_image">Imagem do fornecedor</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Dados</string>
+		<string name="data_backup">Dados (excl. armazenamento)</string>
+		<string name="sdcard">Cartão SD</string>
+		<string name="internal">Armazenamento interno</string>
+		<string name="microsd">Cartão SD</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android seguro</string>
+		<string name="dalvik">Cache Dalvik / ART</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Dados adoptados</string>
+		<string name="adopted_storage">Armazenamento adoptado</string>
+		<string name="autostorage">Armazenamento</string>
+
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% &#xB0;C</string>
+		<string name="battery_pct">Bateria: %tw_battery%</string>
+		<string name="sort_by_name">Ordenar por nome</string>
+		<string name="sort_by_date">Ordenar por data</string>
+		<string name="sort_by_size">Ordenar por tamanho</string>
+		<string name="sort_by_name_only">Nome</string>
+		<string name="sort_by_date_only">Data</string>
+		<string name="sort_by_size_only">Tamanho</string>
+		<string name="tab_general">GERAL</string>
+		<string name="tab_options">OPÇÕES</string>
+		<string name="tab_backup">BACKUP</string>
+		<string name="tab_time_zone">FUSO HORÁRIO</string>
+		<string name="tab_screen">ECRÃ</string>
+		<string name="tab_vibration">VIBRAÇÃO</string>
+		<string name="tab_language">IDIOMA</string>
+
+		<string name="install_btn">Instalar</string>
+		<string name="wipe_btn">Apagar</string>
+		<string name="backup_btn">Backup</string>
+		<string name="restore_btn">Restaurar</string>
+		<string name="mount_btn">Montar</string>
+		<string name="settings_btn">Definições</string>
+		<string name="advanced_btn">Avançadas</string>
+		<string name="reboot_btn">Reiniciar</string>
+		<string name="files_btn">Ficheiros</string>
+		<string name="copy_log_btn">Copiar registo</string>
+		<string name="select_type_hdr">Selecione o tipo</string>
+		<string name="install_zip_hdr">Instalar Zip</string>
+		<string name="install_zip_btn">Instalar Zip</string>
+		<string name="install_image_hdr">Instalar Imagem</string>
+		<string name="install_image_btn">Instalar Imagem</string>
+		<string name="install_select_file_hdr">Selecione o ficheiro</string>
+		<string name="file_selector_folders_hdr">Pastas</string>
+		<string name="select_file_from_storage">Selecione o ficheiro do %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Instalar</string>
+		<string name="select_storage_hdr">Escolher armazenamento</string>
+		<string name="select_storage_btn">Armazenamento</string>
+		<string name="queue_hdr">Lista</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% do máximo de 10 ficheiros na fila</string>
+		<string name="zip_queue_count_s">ficheiro %tw_zip_queue_count% de 10:</string>
+		<string name="zip_warn1">Esta operação poderá instalar software</string>
+		<string name="zip_warn2">incompatível e tornar o seu aparelho inutilizável.</string>
+		<string name="zip_back_cancel">Pressione o botão voltar para cancelar a instalação.</string>
+		<string name="zip_back_clear">Pressione o botão voltar para limpar a fila.</string>
+		<string name="folder">Origem:</string>
+		<string name="file">Ficheiro:</string>
+		<string name="zip_sig_chk">Verificação de assinatura do ficheiro zip</string>
+		<string name="inject_twrp_chk">Injetar a TWRP após a instalação</string>
+		<string name="install_reboot_chk">Reiniciar após concluir a instalação</string>
+		<string name="options_hdr">Opções</string>
+		<string name="confirm_flash_hdr">Confirmar a instalação</string>
+		<string name="zip_queue">Lista:</string>
+		<string name="options">Opcões:</string>
+		<string name="swipe_confirm">   Confirmar</string>
+		<string name="zip_add_btn">Adicionar Zips</string>
+		<string name="zip_clear_btn">Limpar fila</string>
+		<string name="install_zip_count_hdr">A instalar o Zip %tw_zip_index% de %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">A instalar o Zip: %tw_file%</string>
+		<string name="failed">Falhou</string>
+		<string name="successful">Sucesso</string>
+		<string name="install_failed">A instalação falhou</string>
+		<string name="install_successful">A instalação foi bem sucedida</string>
+		<string name="wipe_cache_dalvik_btn">Limpar cache/dalvik</string>
+		<string name="wipe_dalvik_btn">Limpar Dalvik</string>
+		<string name="reboot_system_btn">Reiniciar</string>
+		<string name="install_sel_target">Selecione a partição de destino</string>
+		<string name="flash_image_select">Selecione a partição para onde instalar a imagem:</string>
+		<string name="target_partition">Partição de destino:</string>
+		<string name="flashing_image">A instalar a imagem...</string>
+		<string name="image_flashed">Imagem instalada</string>
+		<string name="wipe_cache_dalvik_confirm">Apagar a Cache &amp; Dalvik?</string>
+		<string name="wipe_dalvik_confirm">Apagar Dalvik?</string>
+		<string name="wiping_cache_dalvik">A apagar a Cache &amp; Dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">A cache de &amp; Dalvik foi apagada</string>
+		<string name="wipe_dalvik_complete">A cache Dalvik foi apagada</string>
+		<string name="swipe_wipe">        Deslize para apagar</string>
+		<string name="swipe_wipe_s">   Apagar</string>
+		<string name="no_os1">Nenhum SO instalado! Tem a</string>
+		<string name="no_osrb">certeza que pretende reiniciar?</string>
+		<string name="no_ospo">certeza que pretende desligar?</string>
+		<string name="rebooting">A reiniciar...</string>
+		<string name="swipe_reboot">     Deslize para reiniciar</string>
+		<string name="swipe_reboot_s">   Reiniciar</string>
+		<string name="reboot_install_app_hdr">Instalar a App TWRP?</string>
+		<string name="reboot_install_app1">Pretende instalar a App TWRP Oficial?</string>
+		<string name="reboot_install_app2">A mesma pesquisa por novas versões da TWRP.</string>
+		<string name="reboot_install_app_prompt_install">Perguntar se pretende instalar a App TWRP caso não esteja</string>
+		<string name="reboot_install_app_system">Instalar como App de Sistema</string>
+		<string name="reboot_installing_app">A instalar a App...</string>
+		<string name="swipe_to_install_app">      Deslize para instalar</string>
+		<string name="uninstall_twrp_system_app">Desinstalar a App TWRP do System</string>
+		<string name="uninstall_twrp_system_app_confirm">Desinstalar a App TWRP?</string>
+		<string name="uninstalling_twrp_system_app">A desinstalar a App...</string>
+		<string name="uninstall_twrp_system_app_complete">A desinstalação foi concluída</string>
+		<string name="swipe_flash">      Deslize para instalar</string>
+		<string name="confirm_action">Confirmar a ação</string>
+		<string name="back_cancel">Pressione o botão voltar para cancelar.</string>
+		<string name="cancel_btn">Cancelar</string>
+		<string name="wipe_hdr">Apagar a memória</string>
+		<string name="factory_reset_hdr">Reset de fábrica</string>
+		<string name="factory_reset_btn">Reset</string>
+		<string name="factory_reset1">Apaga os dados, a cache e a cache Dalvik/ART</string>
+		<string name="factory_reset2">(não apaga o armazenamento interno)</string>
+		<string name="factory_reset3">Por norma, esta é a limpeza</string>
+		<string name="factory_reset4">que se costuma querer fazer.</string>
+		<string name="factory_resetting">Reset de fábrica...</string>
+		<string name="advanced_wipe_hdr">Limpeza personalizada</string>
+		<string name="advanced_wipe_btn">Personalizar</string>
+		<string name="wipe_enc_confirm">Apagar a encriptação dos dados?</string>
+		<string name="formatting_data">A formatar os dados...</string>
+		<string name="swipe_format_data">Deslize para formatar os dados</string>
+		<string name="swipe_format_data_s">   Formatar os dados</string>
+		<string name="factory_reset_complete">Reset de fábrica completo</string>
+		<string name="sel_part_hdr">Selecione as partições</string>
+		<string name="wipe_sel_confirm">Limpar a(s) partição(s) selecionada(s)?</string>
+		<string name="wiping_part">A limpar partição(s)...</string>
+		<string name="wipe_complete">Limpeza completa</string>
+		<string name="sel_part_wipe">Selecione as partições a limpar:</string>
+		<string name="invalid_part_sel">Seleção de partição inválida</string>
+		<string name="format_data_hdr">Formatar dados</string>
+		<string name="format_data_btn">Formatar</string>
+		<string name="format_data_ptr1">O formatar dos dados eliminará todos os seus</string>
+		<string name="format_data_ptr2">aplicativos, backups, mídia, fotos, vídeos e</string>
+		<string name="format_data_ptr3">remove a encriptação do armazenamento interno.</string>
+		<string name="format_data_adopted">Incluindo o armazenamento adoptado</string>
+		<string name="format_data_lcp1">O formatar dos dados eliminará todos as suas apps, os backups, as fotos, os vídeos, a mídia, e</string>
+		<string name="format_data_lcp2">remove a encriptação do armazenamento interno.</string>
+		<string name="format_data_wtc1">O formatar dos dados eliminará todos os seus aplicativos,</string>
+		<string name="format_data_wtc2">backups e mídia. Isto não pode ser desfeito.</string>
+		<string name="format_data_undo">Isto não pode ser desfeito.</string>
+		<string name="format_data_complete">Concluída a formatação dos dados</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">Escreva yes para continuar.  Cancele com o botão voltar.</string>
+		<string name="part_opt_hdr">Opções de partição para: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Escolher ação</string>
+		<string name="file_sys_opt">Opções de sistema de ficheiros</string>
+		<string name="partition">Partição: %tw_partition_name%</string>
+		<string name="part_mount_point">Ponto de montagem: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Sistema de ficheiros: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Presente: Sim</string>
+		<string name="part_present_no">Presente: Não</string>
+		<string name="part_removable_yes">Removível: Sim</string>
+		<string name="part_removable_no">Removível: Não</string>
+		<string name="part_size">Tamanho: % tw_partition_size % MB</string>
+		<string name="part_used">Ocupado: % tw_partition_used % MB</string>
+		<string name="part_free">Livre: % tw_partition_free % MB</string>
+		<string name="part_backup_size">Tamanho do backup: % tw_partition_backup_size % MB</string>
+		<string name="resize_btn">Redimensionar sistema de ficheiros</string>
+		<string name="resize_btn_s">Redimensionar</string>
+		<string name="resize_confirm">Redimensionar %tw_partition_name%?</string>
+		<string name="resizing">A redimensionar...</string>
+		<string name="resize_complete">Redimensionamento completo</string>
+		<string name="swipe_resize">Deslize para redimensionar</string>
+		<string name="swipe_resize_s">   Redimensionar</string>
+		<string name="repair_btn">Reparar sistema de ficheiros</string>
+		<string name="repair_btn_s">Reparar</string>
+		<string name="repair_confirm">%tw_partition_name% de reparação?</string>
+		<string name="repairing">A reparar...</string>
+		<string name="repair_complete">Reparação concluída</string>
+		<string name="swipe_repair">       Deslize para reparar</string>
+		<string name="swipe_repair_s">   Reparar</string>
+		<string name="change_fs_btn">Alterar sistema de ficheiros</string>
+		<string name="change_fs_btn_s">Alterar</string>
+		<string name="change_fs_for_hdr">Alterar o sistema de ficheiros para: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Partição: %tw_partition_name% &gt; Selecione o sistema de ficheiros</string>
+		<string name="change_fs_warn1">Algumas ROMs, ou kernels, podem não suportar</string>
+		<string name="change_fs_warn2">certos sistemas de ficheiros. Proceda com cautela!</string>
+		<string name="change_fs_confirm">Alterar %tw_partition_name%?</string>
+		<string name="formatting">A formatar...</string>
+		<string name="format_complete">Formatação concluída</string>
+		<string name="swipe_change_fs">       Deslize para alterar</string>
+		<string name="swipe_change_s">   Alterar</string>
+		<string name="back_btn">Voltar</string>
+		<string name="wipe_enc_btn">Apagar encriptação</string>
+		<string name="swipe_factory_reset">   Deslize para fazer reset</string>
+		<string name="repair_change_btn">Reparar ou alterar sistema de ficheiros</string>
+		<string name="storage_hdr">Armazenamento: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Backup</string>
+		<string name="backup_confirm_hdr">Confirmar o Backup</string>
+		<string name="encryption_tab">ENCRIPTAÇÃO</string>
+		<string name="encryption">Encriptação:</string>
+		<string name="name">Nome:</string>
+		<string name="sel_part_backup">Selecione as partições para o Backup:</string>
+		<string name="storage">Armazenamento:</string>
+		<string name="enc_disabled">desativado - definir uma palavra-passe para ativar</string>
+		<string name="enc_enabled">ativado</string>
+		<string name="enable_backup_comp_chk">Ativar a compactação</string>
+		<string name="skip_digest_backup_chk" version="2">Não gerar MD5 durante o Backup</string>
+		<string name="disable_backup_space_chk" version="2">Desativar a verificação do espaço livre</string>
+		<string name="current_boot_slot">Slot atual: %tw_active_slot%</string>
+		<string name="boot_slot_a">Slot A</string>
+		<string name="boot_slot_b">Slot B</string>
+		<string name="changing_boot_slot">A alterar o Slot de Arranque</string>
+		<string name="changing_boot_slot_complete">Alteração efetuada com sucesso</string>
+		<string name="refresh_sizes_btn">Atualizar ocupação</string>
+		<string name="swipe_backup"> Deslize para fazer Backup</string>
+		<string name="append_date_btn">Acrescentar data</string>
+		<string name="backup_name_exists">Já existe um Backup com esse nome!</string>
+		<string name="encrypt_backup">Encriptar o Backup?</string>
+		<string name="enter_pass">Defina uma palavra-passe:</string>
+		<string name="enter_pass2">Digite a palavra-passe novamente:</string>
+		<string name="pass_not_match">As palavra-passes não coincidem!</string>
+		<string name="partitions">Partições:</string>
+		<string name="disabled">Desativado</string>
+		<string name="enabled">Ativado</string>
+		<string name="backup_name_hdr">Definir um nome para o Backup</string>
+		<string name="progress">Progresso:</string>
+		<string name="backup_complete">Backup concluído</string>
+		<string name="restore_hdr">Restaurar</string>
+		<string name="sel_backup_hdr">Selecione o Backup</string>
+		<string name="restore_sel_store_hdr">Selecione o Backup do %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Selecione o Backup a restaurar:</string>
+		<string name="restore_enc_backup_hdr">Backup encriptado</string>
+		<string name="restore_dec_fail">Palavra-passe incorrecta, por favor tente novamente!</string>
+		<string name="del_backup_btn">Apagar Backup</string>
+		<string name="del_backup_confirm">Apagar o Backup?</string>
+		<string name="del_backup_confirm2">Não será possível reverter!</string>
+		<string name="deleting_backup">A apagar o Backup...</string>
+		<string name="backup_deleted">Backup apagado</string>
+		<string name="swipe_delete">        Deslize para apagar</string>
+		<string name="swipe_delete_s">   Apagar</string>
+		<string name="restore_try_decrypt">Backup encriptado - a tentar desencriptar</string>
+		<string name="restore_try_decrypt_s">A tentar desencriptar</string>
+		<string name="restore_backup_date">Backup executado a %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Selecione as partições a restaurar:</string>
+		<string name="restore_enable_digest_chk" version="2">Ativar a verificação de MD5 dos ficheiros de Backup</string>
+		<string name="restore_complete">Restauro completo</string>
+		<string name="swipe_restore">     Deslize para restaurar</string>
+		<string name="swipe_restore_s">   Restaurar</string>
+		<string name="rename_backup_hdr">Renomear o Backup</string>
+		<string name="rename_backup_confirm">Renomear o Backup?</string>
+		<string name="rename_backup_confirm2">Não será possível reverter!</string>
+		<string name="renaming_backup">A renomear o Backup...</string>
+		<string name="rename_backup_complete">Backup renomeado</string>
+		<string name="swipe_to_rename">      Deslize para renomear</string>
+		<string name="swipe_rename">   Renomear</string>
+		<string name="confirm_hdr">Confirme</string>
+		<string name="mount_hdr">Montar</string>
+		<string name="mount_sel_part">Selecione as partições a montar:</string>
+		<string name="mount_sys_ro_chk">Montar a partição de sistema apenas para leitura</string>
+		<string name="mount_sys_ro_s_chk">Sistema de montagem RO</string>
+		<string name="decrypt_data_btn">Desencriptar dados</string>
+		<string name="disable_mtp_btn">Desativar MTP</string>
+		<string name="enable_mtp_btn">Ativar MTP</string>
+		<string name="mount_usb_storage_btn">Montar Cartão SD</string>
+		<string name="usb_storage_hdr">Cartão SD</string>
+		<string name="usb_stor_mnt1">O Cartão SD está montado</string>
+		<string name="usb_stor_mnt2">Não se esqueça de remover o seu dispositivo com</string>
+		<string name="usb_stor_mnt3">segurança do computador antes de o desmontar!</string>
+		<string name="unmount_btn">Desmontar</string>
+		<string name="rb_system_btn">Sistema</string>
+		<string name="rb_poweroff_btn">Desligar</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Descarregar</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">A desligar...</string>
+		<string name="swipe_power_off">      Deslize para desligar</string>
+		<string name="swipe_power_off_s">Desligar</string>
+		<string name="sys_ro_hdr">Partição de sistema não modificada</string>
+		<string name="sys_ro_keep">Pretende manter o sistema apenas para leitura?</string>
+		<string name="sys_rop1">A TWRP pode manter a partição de sistema como não modificada</string>
+		<string name="sys_rop2">contudo, se assim for, a TWRP será incapaz de impedir</string>
+		<string name="sys_rop3">que a ROM oficial substitua a Recovery novamente e</string>
+		<string name="sys_rop4">não irá disponibilizar a opção de dar root ao seu dispositivo.</string>
+		<string name="sys_rop5">Ainda assim, a instalação de Zips, ou a execução de</string>
+		<string name="sys_rop6">operações adb, pode modificar a partição de sistema.</string>
+		<string name="sys_rol1">A TWRP pode manter a partição de sistema como não modificada contudo, se assim for, a TWRP será incapaz de impedir</string>
+		<string name="sys_rol2">que a ROM oficial substitua a Recovery novamente e não irá disponibilizar a opção de dar root ao seu dispositivo.</string>
+		<string name="sys_rol3">Ainda assim, a instalação de Zips, ou a execução de operações adb, pode modificar a partição de sistema.</string>
+		<string name="sys_ro_never_show_chk">Não voltar a mostrar este aviso</string>
+		<string name="sys_ro_keep_ro_btn">Apenas para leitura</string>
+		<string name="swipe_allow_mod">      Deslize para permitir</string>
+		<string name="swipe_allow_mod_s">Permitir modificações</string>
+		<string name="settings_hdr">Definições</string>
+		<string name="settings_gen_hdr">Geral</string>
+		<string name="settings_gen_s_hdr">Geral</string>
+		<string name="settings_gen_btn">Geral</string>
+		<string name="use_rmrf_chk">Utilizar rm -rf ao invés de formatar</string>
+		<string name="use24clock_chk">Relógio de 24 horas</string>
+		<string name="rev_navbar_chk">Barra de navegação invertida</string>
+		<string name="simact_chk">Simular ações para teste de temas</string>
+		<string name="simfail_chk">Simular falhas para ações</string>
+		<string name="ctr_navbar_rdo">Botões de navegação centrados</string>
+		<string name="lft_navbar_rdo">Botões de navegação alinhados à esquerda</string>
+		<string name="rht_navbar_rdo">Botões de navegação alinhados à direita</string>
+		<string name="restore_defaults_btn">Restaurar predefinições</string>
+		<string name="settings_tz_btn">Fuso Horário</string>
+		<string name="settings_screen_btn">Ecrã</string>
+		<string name="settings_screen_bright_btn">Brilho do ecrã</string>
+		<string name="vibration_disabled">A vibração está desativada</string>
+		<string name="settings_vibration_btn">Vibração</string>
+		<string name="settings_language_btn">Idioma</string>
+		<string name="time_zone_hdr">Fuso Horário</string>
+		<string name="sel_tz_list">Selecione o Fuso Horário:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) Samoa, Midway Island</string>
+		<string name="utcm10">(UTC -10) Hawaii</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm8">(UTC -8) Pacific Time</string>
+		<string name="utcm7">(UTC -7) Mountain Time</string>
+		<string name="utcm6">(UTC -6) Central Time</string>
+		<string name="utcm5">(UTC -5) Eastern Time</string>
+		<string name="utcm4">(UTC -4) Atlantic Time</string>
+		<string name="utcm3">(UTC -3) Brazil, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Mid-Atlantic</string>
+		<string name="utcm1">(UTC -1) Azores, Cape Verde</string>
+		<string name="utc0">(UTC  0) London, Dublin, Lisbon</string>
+		<string name="utcp1">(UTC +1) Berlin, Brussels, Paris</string>
+		<string name="utcp2">(UTC +2) Athens, Istanbul, South Africa</string>
+		<string name="utcp3">(UTC +3) Moscow, Baghdad</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC +5) Yekaterinburg, Islamabad</string>
+		<string name="utcp6">(UTC +6) Almaty, Dhaka, Colombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp8">(UTC +8) Beijing, Singapore, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokyo, Seoul, Yakutsk</string>
+		<string name="utcp10">(UTC +10) Eastern Australia, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Solomon Islands</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fiji</string>
+		<string name="sel_tz_offset">Selecione o deslocamento (geralmente 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Nenhum</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Utilizar horário de Verão (DST)</string>
+		<string name="curr_tz">Fuso horário atual: %tw_time_zone%</string>
+		<string name="curr_tz_s">Fuso horário atual:</string>
+		<string name="set_tz_btn">Definir fuso horário</string>
+		<string name="settings_screen_hdr">Ecrã</string>
+		<string name="settings_screen_timeout_hdr">Tempo limite do ecrã</string>
+		<string name="enable_timeout_chk">Ativar tempo do ecrã</string>
+		<string name="screen_to_slider">Tempo limite do ecrã em segundos:</string>
+		<string name="screen_to_slider_s">Tempo limite do ecrã em segundos (0 = desativado): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Configuração de tempo limite do ecrã indisponível</string>
+		<string name="screen_bright_slider">Brilho: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Configuração de brilho indisponível</string>
+		<string name="vibration_hdr">Vibração</string>
+		<string name="button_vibration_hdr">Vibração dos botões</string>
+		<string name="kb_vibration_hdr">Vibração do teclado</string>
+		<string name="act_vibration_hdr">Vibração de ações</string>
+		<string name="button_vibration">Vibração dos botões:</string>
+		<string name="kb_vibration">Vibração do teclado:</string>
+		<string name="act_vibration">Vibração de ações:</string>
+		<string name="select_language">Selecionar idioma:</string>
+		<string name="sel_lang_btn">Selecionar idioma</string>
+		<string name="set_language_btn">Definir idioma</string>
+		<string name="advanced_hdr">Avançadas</string>
+		<string name="copy_log_confirm">Copiar o registo para o cartão SD?</string>
+		<string name="copying_log" version="2">A copiar o registo para o cartão SD...</string>
+		<string name="copy_log_complete" version="2">Cópia do registo efetuada</string>
+		<string name="fix_context_btn">Corrigir SELinux</string>
+		<string name="part_sd_btn">Particionar SD</string>
+		<string name="part_sd_s_btn">Cartão SD</string>
+		<string name="file_manager_btn">Gestor de ficheiros</string>
+		<string name="language_hdr">Idioma</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="reload_theme_btn">Recarregar Tema</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Injetar TWRP</string>
+		<string name="inject_twrp_confirm">Pretende injetar a TWRP?</string>
+		<string name="injecting_twrp">A injetar a TWRP...</string>
+		<string name="inject_twrp_complete">Injeção da TWRP concluída</string>
+		<string name="swipe_to_confirm">     Deslize para confirmar</string>
+		<string name="part_sd_hdr">Particionar o Cartão SD</string>
+		<string name="invalid_partsd_sel">Tem de selecionar um dispositivo removível</string>
+		<string name="part_sd_lose">Irá perder todos os dados do cartão SD!</string>
+		<string name="part_sd_undo">Esta ação não pode ser desfeita!</string>
+		<string name="part_sd_ext_sz">Tamanho EXT:</string>
+		<string name="part_sd_swap_sz">Tamanho de swap:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Sistema de ficheiros:</string>
+		<string name="swipe_part_sd">    Deslize para particionar</string>
+		<string name="swipe_part_sd_s">Particionar</string>
+		<string name="partitioning_sd">A particionar o cartão SD...</string>
+		<string name="partitioning_sd2">Esta operação demorará algum tempo.</string>
+		<string name="part_sd_complete">Particionamento concluído</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Restaurar a imagem de Boot Original</string>
+		<string name="dumlock_restore_confirm">Pretende restaurar a imagem de Boot original?</string>
+		<string name="dumlock_restoring">A restaurar a imagem de Boot Original...</string>
+		<string name="dumlock_restore_complete">Imagem de Boot restaurada</string>
+		<string name="dumlock_reflash_btn">Instalar Recovery</string>
+		<string name="dumlock_reflash_confirm">Pretende instalar a Recovery para a partição Boot?</string>
+		<string name="dumlock_reflashing">A instalar a Recovery para a partição Boot...</string>
+		<string name="dumlock_reflash_complete">Instalação da Recovery para a partição Boot concluída</string>
+		<string name="dumlock_install_btn">Instalar o HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Pretende instalar os ficheiros HTC dumlock para a ROM?</string>
+		<string name="dumlock_installing">A instalar o HTC Dumlock...</string>
+		<string name="dumlock_install_complete">Instalção do HTC Dumlock concluída</string>
+		<string name="swipe_to_unlock">    Deslize para desbloquear</string>
+		<string name="swipe_unlock">   Desbloquear</string>
+		<string name="fm_hdr">Gestor de ficheiros</string>
+		<string name="fm_sel_file">Selecione um ficheiro ou pasta</string>
+		<string name="fm_type_folder">Pasta</string>
+		<string name="fm_type_file">Ficheiro</string>
+		<string name="fm_choose_act">Escolha uma Ação</string>
+		<string name="fm_selected">%tw_fm_type% selecionado:</string>
+		<string name="fm_copy_btn">Cópia</string>
+		<string name="fm_copy_file_btn">Copiar ficheiro</string>
+		<string name="fm_copy_folder_btn">Copiar pasta</string>
+		<string name="fm_copying">A copiar</string>
+		<string name="fm_move_btn">Mover</string>
+		<string name="fm_moving">A mover</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Eliminar</string>
+		<string name="fm_deleting">A eliminar</string>
+		<string name="fm_rename_btn">Renomear</string>
+		<string name="fm_rename_file_btn">Renomear ficheiro</string>
+		<string name="fm_rename_folder_btn">Renomear Pasta</string>
+		<string name="fm_renaming">A renomear</string>
+		<string name="fm_sel_dest">Selecione a pasta de destino</string>
+		<string name="fm_sel_curr_folder">Selecione a pasta atual</string>
+		<string name="fm_rename_hdr">Renomear</string>
+		<string name="fm_set_perms_hdr">Definir permissões</string>
+		<string name="fm_perms">Permissões:</string>
+		<string name="fm_complete">Operação concluída</string>
+		<string name="decrypt_data_hdr">Desencriptar dados</string>
+		<string name="decrypt_data_enter_pass">Insira a sua palavra-passe.</string>
+		<string name="decrypt_data_failed">Palavra-passe incorrecta, tente novamente por favor!</string>
+		<string name="decrypt_data_failed_pattern">Padrão incorrecto, tente novamente por favor!</string>
+		<string name="decrypt_data_enter_pattern">Desenhe o seu padrão.</string>
+		<string name="decrypt_data_trying">A tentar a desencriptação</string>
+		<string name="decrypt_data_vold_os_missing">Ficheiros em falta necessários para a desencriptação vold: {1}</string>
+		<string name="term_hdr">Linha de comandos do Terminal</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">KILL</string>
+		<string name="term_sel_folder_hdr">Voltar à pasta inicial</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Limpar a cache Dalvik</string>
+		<string name="sideload_wipe_cache_chk">Limpar a cache</string>
+		<string name="swipe_to_sideload">Deslize para iniciar Sideload</string>
+		<string name="swipe_sideload">   Início</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Modo de usar: adb sideload filename.zip</string>
+		<string name="sideload_complete">ADB Sideload concluído</string>
+		<string name="fix_contexts_hdr">Corrigir SELinux</string>
+		<string name="fix_contexts_note1">Nota: Corrigir contextos SELinux raramente é necessário.</string>
+		<string name="fix_contexts_note2">A correção de contextos SELinux poderá causar</string>
+		<string name="fix_contexts_note3">falhas que impeçam o arranque do seu dispositivo.</string>
+		<string name="swipe_to_fix_contexts">      Deslize para corrigir</string>
+		<string name="swipe_fix_contexts">   Corrigir</string>
+		<string name="fixing_contexts">A corrigir contextos...</string>
+		<string name="fix_contexts_complete">Correção de contextos concluída</string>
+		<string name="reboot_hdr">Reiniciar</string>
+		<string name="install_cancel">Não instalar</string>
+		<string name="sel_storage_list">Selecionar armazenamento</string>
+		<string name="ok_btn">OK</string>
+		<string name="install_twrp_ramdisk">Instalar a Ramdisk da Recovery</string>
+		<string name="install_kernel">Instalar o Kernel</string>
+		<string name="repack_kernel_confirm_hdr">Instalar Kernel</string>
+		<string name="repack_ramdisk_confirm_hdr">Instalar Recovery</string>
+		<string name="repack_kernel_confirm">Instalar o Kernel?</string>
+		<string name="repack_ramdisk_confirm">Instalar a Recovery?</string>
+		<string name="repack_backup_first">Fazer Backup da imagem existente primeiro</string>
+		<string name="repack">Repack</string>
+		<string name="swipe_to_install">Deslize para instalar</string>
+		<string name="installing">A instalar...</string>
+		<string name="install_complete">Instalação concluída</string>
+		<string name="unpack_error">Erro ao fazer unpack da imagem.</string>
+		<string name="repack_error">Erro ao fazer repack da imagem.</string>
+		<string name="unpacking_image">A fazer Unpack de {1}...</string>
+		<string name="repacking_image">A fazer Repack de {1}...</string>
+		<string name="repack_image_hdr">Selecionar Imagem</string>
+		<string name="fix_recovery_loop">Resolver Bootloop da Recovery</string>
+		<string name="fix_recovery_loop_confirm">Resolver Bootloop?</string>
+		<string name="fixing_recovery_loop"> A Resolver Bootloop...</string>
+		<string name="fix_recovery_loop_complete">Resolução do Bootloop da Recovery concluída</string>
+		<string name="fixing_recovery_loop_patch">A aplicar o patch ao kernel...</string>
+		<string name="fix_recovery_loop_patch_error">Erro ao aplicar o patch ao kernel.</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">O Kernel não tem suporte para leitura de contextos do SELinux.</string>
+		<string name="full_selinux">Suporte completo ao SELinux.</string>
+		<string name="no_selinux">Não há suporte ao SELinux (não existe libselinux).</string>
+		<string name="mtp_enabled">MTP ativado</string>
+		<string name="mtp_crash">MTP falhou, o mesmo não será iniciado no arranque.</string>
+		<string name="decrypt_success">Desencriptação realizada com êxito recorrendo à palavra-passe padrão.</string>
+		<string name="unable_to_decrypt">Não é possível desencriptar com a palavra-passe padrão. É possível que seja necessário executar uma formatação dos dados.</string>
+		<string name="generating_digest1" version="2">A gerar o MD5</string>
+		<!-- Message displayed during a backup if we are generating an MD5, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * A gerar o md5...</string>
+		<string name="digest_created" version="2"> * MD5 criado.</string>
+		<string name="digest_error" version="2"> * Erro MD5!</string>
+		<string name="digest_compute_error" version="2"> * Erro ao computar o MD5.</string>
+		<string name="current_date">(Data atual)</string>
+		<string name="auto_generate">(Criar automaticamente)</string>
+		<string name="unable_to_locate_partition">Não é possível localizar a partição '{1}' para os cálculos do Backup.</string>
+		<string name="no_partition_selected">Não estão selecionadas partições para Backup.</string>
+		<string name="total_partitions_backup"> * Número total de partições de Backup: {1}</string>
+		<string name="total_backup_size"> * Tamanho total dos dados: {1}MB</string>
+		<string name="available_space"> * Espaço disponível: {1}MB</string>
+		<string name="unable_locate_storage">Não é possível localizar o dispositivo de armazenamento.</string>
+		<string name="no_space">Não existe espaço suficiente no armazenamento.</string>
+		<string name="backup_started">[BACKUP INICIADO]</string>
+		<string name="backup_folder"> * Pasta de Backup: {1}</string>
+		<string name="fail_backup_folder">Não foi possível criar a pasta de Backup.</string>
+		<string name="avg_backup_fs">Taxa média de Backup para sistemas de ficheiro: {1} MB/seg</string>
+		<string name="avg_backup_img">Taxa média de Backup para drives de imagens: {1} MB/seg</string>
+		<string name="total_backed_size">[BACKUP CONCLUÍDO COM UM TOTAL DE {1} MB]</string>
+		<string name="backup_completed">[BACKUP REALIZADO EM {1} SEGUNDOS]</string>
+		<string name="restore_started">[RESTAURO INICIADO]</string>
+		<string name="restore_folder">Restaurar a pasta: '{1}'</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} concluído(a) ({2} seconds)]</string>
+		<string name="verifying_digest" version="2">A verificar o MD5</string>
+		<string name="skip_digest" version="2">A ignorar a verificação MD5 por escolha do utilizador.</string>
+		<string name="calc_restore">A calcular detalhes da restauração...</string>
+		<string name="restore_read_only">Impossível restaurar a partição {1} -- está montada apenas para leitura.</string>
+		<string name="restore_unable_locate">Impossível localizar a partição '{1}' para restaurar.</string>
+		<string name="no_part_restore">Não estão selecionadas partições para restaurar.</string>
+		<string name="restore_part_count">A restaurar {1} partições...</string>
+		<string name="total_restore_size">{1}MB de tamanho total de restauro</string>
+		<string name="updating_system_details">A atualizar detalhes do sistema</string>
+		<string name="restore_completed">[RESTAURO CONCLUÍDO EM {1} SEGUNDOS]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">Erro ao abrir: '{1}' ({2})</string>
+		<string name="unable_locate_part_backup_name">Não é possível localizar a partição pelo nome do Backup: '{1}'</string>
+		<string name="unable_find_part_path">Não é possível localizar a partição para o caminho '{1}'</string>
+		<string name="update_part_details">A atualizar detalhes da partição...</string>
+		<string name="update_part_details_done">... concluído</string>
+		<string name="wiping_dalvik">A limpar os diretórios da cache Dalvik...</string>
+		<string name="cleaned">Limpos: {1}...</string>
+		<string name="cache_dalvik_done">-- Diretórios da cache Dalvik limpos!</string>
+		<string name="dalvik_done">-- Diretório Dalvik limpo!</string>
+		<string name="no_andsec">Não foram encontradas partições android secure.</string>
+		<string name="unable_to_locate">Não é possível localizar {1}.</string>
+		<string name="wiping_datamedia">A limpar o armazenamento interno  -- /data/media...</string>
+		<string name="unable_to_mount">Foi impossível montar {1}</string>
+		<string name="unable_to_mount_internal">Foi impossível montar a memória interna</string>
+		<string name="unable_to_mount_storage">Foi impossível montar o armazenamento</string>
+		<string name="fail_decrypt">Falha ao desencriptar os dados.</string>
+		<string name="no_crypto_support">Não existe crypto support nesta compilação.</string>
+		<string name="decrypt_success_dev">Dados desencriptados com sucesso, novo dispositivo de bloco: '{1}'</string>
+		<string name="decrypt_success_nodev">Dados desencriptados com sucesso</string>
+		<string name="done">Concluído.</string>
+		<string name="start_partition_sd">A particionar o cartão SD...</string>
+		<string name="partition_sd_locate">Não foi possível localizar o dispositivo a particionar.</string>
+		<string name="ext_swap_size">O tamanho EXT + Swap excede a capacidade do cartão SD.</string>
+		<string name="remove_part_table">A remover a tabela de partição...</string>
+		<string name="unable_rm_part">Não foi possível remover a tabela de partição.</string>
+		<string name="create_part">A criar a partição {1}...</string>
+		<string name="unable_to_create_part">Não foi possível criar a partição {1}.</string>
+		<string name="format_sdext_as">A formatar sd-ext como {1}...</string>
+		<string name="part_complete">Particionamento concluído.</string>
+		<string name="unable_to_open">Não é possível abrir '{1}'.</string>
+		<string name="mtp_already_enabled">O MTP já está ativado</string>
+		<string name="mtp_fail">Falha ao ativar o MTP</string>
+		<string name="no_mtp">Não existe suporte ao MTP</string>
+		<string name="image_flash_start">[INSTALAÇÃO DA IMAGEM INICIADA]</string>
+		<string name="img_to_flash">Imagem a instalar: '{1}'</string>
+		<string name="flash_unable_locate">Não é possível localizar a partição '{1}' para instalar.</string>
+		<string name="no_part_flash">Não foi selecionada nenhuma partição para onde instalar.</string>
+		<string name="too_many_flash">Demasiadas partições selecionadas para onde instalar.</string>
+		<string name="invalid_flash">Selecionou uma partição inválida para onde instalar.</string>
+		<string name="flash_done">[INSTALAÇÃO DA IMAGEM CONCLUÍDA]</string>
+		<string name="wiping">A limpar {1}</string>
+		<string name="repair_not_exist">{1} não existe! Não é possível reparar!</string>
+		<string name="repairing_using">Reparação de {1} utilizando {2}...</string>
+		<string name="unable_repair">Não é possível reparar {1}.</string>
+		<string name="mount_data_footer">Não foi possível montar a /data nem localizar o crypto footer.</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">Não foi possível criar a pasta '{1}' ({2}).</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_mount">Falha ao montar '{1}' ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">Falha ao montar '{1}' ({2})</string>
+		<string name="cannot_resize">Não é possível redimensionar {1}.</string>
+		<string name="repair_resize">Reparação de {1} antes de redimensionamento.</string>
+		<string name="unable_resize">Não é possível redimensionar {1}.</string>
+		<string name="no_digest_found" version="2">'{1}' não dsipõe de md5. Por favor, desative a opção de verificação de MD5.</string>
+		<string name="digest_fail_match" version="2">MD5 de '{1}' não coincide.</string>
+		<string name="digest_matched" version="2">MD5 de '{1}' ok.</string>
+		<string name="fail_decrypt_tar">Falha ao desencriptar o ficheiro tar '{1}'</string>
+		<string name="format_data_msg">É necessário reiniciar a Recovery para a /data seja novamente capaz de ser utilizada.</string>
+		<string name="format_data_err">Não é possível formatar para remover a encriptação.</string>
+		<string name="formatting_using">A formatar {1} utilizando {2}...</string>
+		<string name="unable_to_wipe">Não foi possível limpar {1}.</string>
+		<string name="cannot_wipe">A partição {1} não pode ser apagada.</string>
+		<string name="remove_all">Remover todos os ficheiros em '{1}'</string>
+		<string name="wiping_data">A limpar os dados sem limpar /data/media...</string>
+		<string name="backing_up">A fazer backup de {1}...</string>
+		<string name="backup_storage_warning">O backup de {1} não inclui quaisquer ficheiros do armazenamento interno tais como imagens ou downloads.</string>
+		<string name="backing">A fazer backup</string>
+		<string name="backup_size">O tamanho do ficheiro de Backup para '{1}' é de 0 bytes.</string>
+		<string name="datamedia_fs_restore">AVISO: Este Backup de /data foi feito com o sistema de ficheiros de {1}! Ao restaurar, o dispositivo poderá não arrancar a não ser que mude para {1}.</string>
+		<string name="restoring">A restaurar {1}...</string>
+		<string name="restoring_hdr">A restaurar</string>
+		<string name="recreate_folder_err">Não é possível recriar a pasta {1}.</string>
+		<string name="img_size_err">O tamanho da imagem excede a capacidade do dispositivo de destino</string>
+		<string name="flashing">A instalar {1}...</string>
+		<string name="backup_folder_set">Pasta de Backup definida para '{1}'</string>
+		<string name="locate_backup_err">Não é possível localizar o Backup '{1}'</string>
+		<string name="set_restore_opt">A definir opções de restauro: '{1}':</string>
+		<string name="digest_check_skip" version="2">O ignorar da verificação MD5 está ativo</string>
+		<string name="ors_encrypt_restore_err">Não foi possível utilizar o OpenRecoveryScript para restaurar um Backup encriptado.</string>
+		<string name="mounting">A montar</string>
+		<string name="unmounting">A desmontar</string>
+		<string name="mounted">'{1}' Montado</string>
+		<string name="unmounted">'{1}' Desmontado</string>
+		<string name="setting">A definir '{1}' para '{2}'</string>
+		<string name="setting_empty">A definir '{1}' como vazio</string>
+		<string name="making_dir1">A criar o diretório</string>
+		<string name="making_dir2">A criar o diretório: '{1}'</string>
+		<string name="running_command">A executar o comando</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">A iniciar a funcionalidade ADB sideload...</string>
+		<string name="need_new_adb">É necessário o adb 1.0.32, ou mais recente, para fazer sideload a este dispositivo.</string>
+		<string name="no_pwd">Não foi fornecida uma palavra-passe.</string>
+		<string name="done_ors">O ficheiro de script foi processado</string>
+		<string name="injecttwrp">A injetar a TWRP na imagem de Boot...</string>
+		<string name="zip_err">Erro ao instalar o ficheiro zip '{1}'</string>
+		<string name="installing_zip">A instalar o ficheiro zip '{1}'</string>
+		<string name="select_backup_opt">A definir opções para o Backup:</string>
+		<string name="compression_on">A compactação está ativa</string>
+		<string name="digest_off" version="2">A geração do MD5 está desativada</string>
+		<string name="backup_fail">O backup falhou</string>
+		<string name="backup_clean">O backup falhou. A limpar a pasta de Backup.</string>
+		<string name="running_recovery_commands">A executar os comandos de Recovery</string>
+		<string name="recovery_commands_complete">Comandos de Recovery concluídos</string>
+		<string name="running_ors">OpenRecoveryScript em execução</string>
+		<string name="ors_complete">OpenRecoveryScript concluído</string>
+		<string name="check_for_digest" version="2">A pesquisar pelo ficheiro MD5...</string>
+		<string name="invalid_zip_format">Formato de zip inválido!</string>
+		<string name="fail_sysmap">Falha ao mapear o ficheiro '{1}'</string>
+		<string name="verify_zip_sig">A verificar a assinatura do zip...</string>
+		<string name="verify_zip_fail">A verificação da assinatura do ficheiro zip falhou!</string>
+		<string name="verify_zip_done">Assinatura do ficheiro zip verificada com sucesso.</string>
+		<string name="zip_corrupt">O ficheiro zip está corrompido!</string>
+		<string name="no_digest" version="2">A ignorar a verificação MD5: não foi encontrado nenhum ficheiro MD5</string>
+		<string name="digest_fail" version="2">O MD5 não coincide</string>
+		<string name="digest_match" version="2">O MD5 coincide</string>
+		<string name="pid_signal">O processo {1} terminou com o sinal: {2}</string>
+		<string name="pid_error">O processo {1} terminou com o erro: {2}</string>
+		<string name="install_dumlock">A instalar o HTC Dumlock para o sistema...</string>
+		<string name="dumlock_restore">A restaurar a imagem de Boot original...</string>
+		<string name="dumlock_reflash">A instalar a Recovery para o Boot...</string>
+		<string name="run_script">A executar o script {1}...</string>
+		<string name="rename_stock">O ficheiro da Recovery em /system foi renomeado de modo a impedir que a stock ROM substitua a TWRP.</string>
+		<string name="split_backup">A dividir o ficheiro de Backup em vários arquivos...</string>
+		<string name="backup_error">Erro ao criar o Backup.</string>
+		<string name="restore_error">Erro durante o processo de restauro.</string>
+		<string name="split_thread">A dividir o segmento com o ID {1} para o arquivo {2}</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">%llu de %llu ficheiros, %i%%</string>
+		<string name="size_progress">%lluMB de %lluMB, %i%%</string>
+		<string name="decrypt_cmd">A tentar desencriptar a partição de dados via linha de comandos.</string>
+		<string name="base_pkg_err">Falha ao carregar os pacotes base.</string>
+		<string name="simulating">A simular ações...</string>
+		<string name="backup_cancel">O backup foi cancelado</string>
+		<string name="config_twrp">A confiurar a TWRP...</string>
+		<string name="config_twrp_err">Não é possível configurar a TWRP com este kernel.</string>
+		<string name="copy_log">O registo da Recovery foi copiado para {1}.</string>
+		<string name="max_queue">A fila de ficheiros zip atingiu o seu máximo!</string>
+		<string name="min_queue">A fila de ficheiros zip atingiu o seu mínimo!</string>
+		<string name="screenshot_saved">A captura de ecrã foi guardada em {1}</string>
+		<string name="screenshot_err">Não foi possível capturar o ecrã!</string>
+		<string name="zip_wipe_cache">Pelo menos, um dos zip solicitou uma limpeza da cache -- a limpar a cache.</string>
+		<string name="and_sec_wipe_err">Não foi possível limpar o android secure</string>
+		<string name="dalvik_wipe_err">Falha ao limpar a cache dalvik</string>
+		<string name="auto_gen">(Criar automaticamente)</string>
+		<string name="curr_date">(Data atual)</string>
+		<string name="backup_name_len">O nome do Backup é muito extenso.</string>
+		<string name="backup_name_invalid">O nome '{1}' para o Backup contém um caráter inválido: '{1}'</string>
+		<string name="no_real_sdcard">Este dispositivo não tem um cartão SD externo! A abortar!</string>
+		<string name="cancel_sideload">A cancelar o ADB sideload...</string>
+		<string name="change_fs_err">Erro ao alterar o sistema de ficheiros.</string>
+		<string name="theme_ver_err">A versão do tema personalizado não é compatível com a versão da TWRP. A o usar o tema padrão.</string>
+		<string name="up_a_level">(Nível acima)</string>
+		<string name="install_reboot" version="2">A reiniciar daqui a %tw_sleep% segundo(s)</string>
+		<string name="adbbackup_error">Erro no Backup ADB. A sair..."</string>
+		<string name="adbbackup_control_error">Não é possível escrever no canal de controlo adb</string>
+		<string name="twrp_adbbu_option">A opção --twrp é requerida para ativar o twrp adb backup</string>
+		<string name="partition_not_found">caminho: {1} não encontrado na lista de partições</string>
+		<string name="copy_kernel_log">Log do Kernel copiado para {1}</string>
+		<string name="include_kernel_log">Incluir log do Kernel</string>
+		<string name="sha2_chk">Usar SHA2 para fazer hash</string>
+		<string name="unable_set_boot_slot">Erro ao alterar o slot do bootloader para {1}</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/ru.xml b/gui/theme/common/languages/ru.xml
new file mode 100644
index 0000000..7c0f5ea
--- /dev/null
+++ b/gui/theme/common/languages/ru.xml
@@ -0,0 +1,766 @@
+<?xml version="1.0" encoding="utf-8"?>
+<language>
+	<display>Русский</display>
+	<!-- Translated by Jemmini; modified by agur4ik, SevenMaxs, S-trace 2017, f2065 -->
+
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource filename="RobotoCondensed-Regular.ttf" name="font_l" scale="90" type="fontoverride"/>
+		<resource filename="RobotoCondensed-Regular.ttf" name="font_m" scale="90" type="fontoverride"/>
+		<resource filename="RobotoCondensed-Regular.ttf" name="font_s" scale="90" type="fontoverride"/>
+		<resource filename="DroidSansMono.ttf" name="fixed" scale="100" type="fontoverride"/>
+
+		<!-- Partition display names -->
+		<string name="system">System</string>
+		<string name="system_image">System Image</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Vendor Image</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="data_backup">Data (искл. внутр.память)</string>
+		<string name="sdcard">SD-карта</string>
+		<string name="internal">Внутренняя память</string>
+		<string name="microsd">MicroSD-карта</string>
+		<string name="usbotg">USB-накопитель</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik/ART Cache</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Adopted Data</string>
+		<string name="adopted_storage">Adopted Storage</string>
+		<string name="autostorage">Storage</string>
+
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">ЦП: %tw_cpu_temp% °C</string>
+		<string name="battery_pct">Батарея: %tw_battery%</string>
+		<string name="sort_by_name">Сорт.по имени</string>
+		<string name="sort_by_date">Сорт.по дате</string>
+		<string name="sort_by_size">Сорт.по размеру</string>
+		<string name="sort_by_name_only">Имя</string>
+		<string name="sort_by_date_only">Дата</string>
+		<string name="sort_by_size_only">Размер</string>
+		<string name="tab_general">ОСНОВНЫЕ</string>
+		<string name="tab_options">ОПЦИИ</string>
+		<string name="tab_backup">КОПИИ</string>
+		<string name="tab_time_zone">ЧАСОВОЙ ПОЯС</string>
+		<string name="tab_screen">ЭКРАН</string>
+		<string name="tab_vibration">ВИБРАЦИЯ</string>
+		<string name="tab_language">ЯЗЫК</string>
+
+		<string name="install_btn">Установка</string>
+		<string name="wipe_btn">Очистка</string>
+		<string name="backup_btn">Резервное коп-ние</string>
+		<string name="restore_btn">Восстановление</string>
+		<string name="mount_btn">Монтирование</string>
+		<string name="settings_btn">Настройки</string>
+		<string name="advanced_btn">Дополнительно</string>
+		<string name="reboot_btn">Перезагрузка</string>
+		<string name="files_btn">Файлы</string>
+		<string name="copy_log_btn">Копировать лог</string>
+		<string name="select_type_hdr">Выбор типа</string>
+		<string name="install_zip_hdr">Установка ZIP</string>
+		<string name="install_zip_btn">Установка ZIP</string>
+		<string name="install_image_hdr">Установка IMG-образа</string>
+		<string name="install_image_btn">Установка IMG</string>
+		<string name="install_select_file_hdr">Выбор файла</string>
+		<string name="file_selector_folders_hdr">Папки</string>
+		<string name="select_file_from_storage">Текущий накопитель: %tw_storage_display_name% (%tw_storage_free_size% МБ)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Установка</string>
+		<string name="select_storage_hdr">Выберите накопитель</string>
+		<string name="select_storage_btn">Выбор накопителя</string>
+		<string name="queue_hdr">Очередь</string>
+		<string name="zip_queue_count">Файлов в очереди: %tw_zip_queue_count% из 10 возможных</string>
+		<string name="zip_queue_count_s">Файл %tw_zip_queue_count% из 10:</string>
+		<string name="zip_warn1">Эта операция может установить несовместимое ПО на</string>
+		<string name="zip_warn2">ваше устройство и сделать загрузку системы невозможной.</string>
+		<string name="zip_back_cancel">Нажмите назад для отмены установки этого ZIP.</string>
+		<string name="zip_back_clear">Нажмите назад для очистки очереди.</string>
+		<string name="folder">Папка:</string>
+		<string name="file">Файл:</string>
+		<string name="zip_sig_chk">Проверка подписи ZIP</string>
+		<string name="inject_twrp_chk">Интегрировать TWRP после установки</string>
+		<string name="install_reboot_chk">Автоматическая перезагрузка после установки</string>
+		<string name="options_hdr">Опции</string>
+		<string name="confirm_flash_hdr">Подтвердите установку</string>
+		<string name="zip_queue">Очередь:</string>
+		<string name="options">Опции:</string>
+		<string name="swipe_confirm">   Подтвердить</string>
+		<string name="zip_add_btn">Добавить ещё ZIP</string>
+		<string name="zip_clear_btn">Очистить очередь ZIP</string>
+		<string name="install_zip_count_hdr">Установка ZIP %tw_zip_index% из %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Установка ZIP: %tw_file%</string>
+		<string name="failed">Неудача</string>
+		<string name="successful">Успешно</string>
+		<string name="install_failed">Установка не удалась</string>
+		<string name="install_successful">Установка выполнена успешно</string>
+		<string name="wipe_cache_dalvik_btn">Очистка cache/dalvik</string>
+		<string name="wipe_dalvik_btn">Очистка dalvik</string>
+		<string name="reboot_system_btn">Перезагрузка в ОС</string>
+		<string name="install_sel_target">Выбор раздела</string>
+		<string name="flash_image_select">Выберите раздел для прошивки образа:</string>
+		<string name="target_partition">Целевой раздел:</string>
+		<string name="flashing_image">Прошивка образа...</string>
+		<string name="image_flashed">Прошивка образа завершена</string>
+		<string name="wipe_cache_dalvik_confirm">Очистить Cache &amp; Dalvik?</string>
+		<string name="wipe_dalvik_confirm">Очистить Dalvik?</string>
+		<string name="wiping_cache_dalvik">Очистка Cache &amp; Dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Очистка Cache &amp; Dalvik завершена</string>
+		<string name="wipe_dalvik_complete">Очистка Dalvik завершена</string>
+		<string name="swipe_wipe">Свайп для очистки</string>
+		<string name="swipe_wipe_s">   Очистка</string>
+		<string name="no_os1">Система не установлена! Вы уверены,</string>
+		<string name="no_osrb">что хотите перезагрузить?</string>
+		<string name="no_ospo">что хотите выключить?</string>
+		<string name="rebooting">Перезагрузка...</string>
+		<string name="swipe_reboot">Свайп для перезагрузки</string>
+		<string name="swipe_reboot_s">   Перезагрузка</string>
+		<string name="reboot_install_app_hdr">Установить TWRP App</string>
+		<string name="reboot_install_app1">Вы хотите установить официальное приложение TWRP?</string>
+		<string name="reboot_install_app2">Оно позволит вам проверять наличие новой версии TWRP.</string>
+		<string name="reboot_install_app_prompt_install">Предлагать установку TWRP App при его отсутствии</string>
+		<string name="reboot_install_app_system">Установить как системное приложение</string>
+		<string name="reboot_installing_app">Установка TWRP приложения...</string>
+		<string name="swipe_to_install_app">Свайп для установки TWRP App</string>
+		<string name="uninstall_twrp_system_app">Удаление TWRP App из системы</string>
+		<string name="uninstall_twrp_system_app_confirm">Удалить TWRP App из системы?</string>
+		<string name="uninstalling_twrp_system_app">Удаление TWRP App из системы...</string>
+		<string name="uninstall_twrp_system_app_complete">Удаление TWRP App из системы завершено</string>
+		<string name="swipe_flash">Свайп для прошивки</string>
+		<string name="confirm_action">Подтвердить действие</string>
+		<string name="back_cancel">Нажмите назад для отмены.</string>
+		<string name="cancel_btn">Отмена</string>
+		<string name="wipe_hdr">Очистка</string>
+		<string name="factory_reset_hdr">Сброс до заводского состояния</string>
+		<string name="factory_reset_btn">Сброс на фабричные</string>
+		<string name="factory_reset1">Очистка Data, Cache, и Dalvik</string>
+		<string name="factory_reset2">(исключая внутреннее хранилище)</string>
+		<string name="factory_reset3">В большинстве случаев очистка</string>
+		<string name="factory_reset4">этих разделов достаточна.</string>
+		<string name="factory_reset5">(исключая аккаунты и экран блокировки)</string>
+		<string name="factory_resetting">Сброс до заводского состояния...</string>
+		<string name="advanced_wipe_hdr">Выборочная очистка</string>
+		<string name="advanced_wipe_btn">Выборочная очистка</string>
+		<string name="wipe_enc_confirm">Очистить шифрованные данные?</string>
+		<string name="formatting_data">Форматирование Data...</string>
+		<string name="swipe_format_data">Свайп для форматирования</string>
+		<string name="swipe_format_data_s">   Форматировать Data</string>
+		<string name="factory_reset_complete">Сброс до заводского состояния завершён</string>
+		<string name="sel_part_hdr">Выбор разделов</string>
+		<string name="wipe_sel_confirm">Очистить выбранные разделы?</string>
+		<string name="wiping_part">Очистка разделов...</string>
+		<string name="wipe_complete">Очистка завершена</string>
+		<string name="sel_part_wipe">Выберите разделы для очистки:</string>
+		<string name="invalid_part_sel">Выбран неверный раздел</string>
+		<string name="format_data_hdr">Форматировать Data</string>
+		<string name="format_data_btn">Форматировать Data</string>
+		<string name="format_data_ptr1">Форматирование Data удалит все приложения,</string>
+		<string name="format_data_ptr2">резервные копии, картинки, видео, медиафайлы</string>
+		<string name="format_data_ptr3">и зашифрованные файлы во внутренней памяти,</string>
+		<string name="format_data_adopted">включая расширенное хранилище.</string>
+		<string name="format_data_lcp1">Форматирование данных удалит все приложения, резервные копии, картинки, видео, медиафайлы</string>
+		<string name="format_data_lcp2">и зашифрованные файлы во внутренней памяти.</string>
+		<string name="format_data_wtc1">Форматирование данных удалит все приложения,</string>
+		<string name="format_data_wtc2">резервные копии и медиа. Это не может быть отменено.</string>
+		<string name="format_data_undo">Это не может быть отменено.</string>
+		<string name="format_data_complete">Форматирование данных завершено</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">Введите yes для продолжения.  Нажмите назад для отмены.</string>
+		<string name="part_opt_hdr">Опции для раздела %tw_partition_name%</string>
+		<string name="sel_act_hdr">Выбор действия</string>
+		<string name="file_sys_opt">Опции файловой системы</string>
+		<string name="partition">Раздел: %tw_partition_name%</string>
+		<string name="part_mount_point">Точка монтирования: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Файловая система: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Присутствует: да</string>
+		<string name="part_present_no">Присутствует: нет</string>
+		<string name="part_removable_yes">Съёмное: да</string>
+		<string name="part_removable_no">Съёмное: нет</string>
+		<string name="part_size">Размер: %tw_partition_size% МБ</string>
+		<string name="part_used">Использовано: %tw_partition_used% МБ</string>
+		<string name="part_free">Свободно: %tw_partition_free% МБ</string>
+		<string name="part_backup_size">Размер резервных копий: %tw_partition_backup_size% МБ</string>
+		<string name="resize_btn">Измен. размер</string>
+		<string name="resize_btn_s">Изм. размер</string>
+		<string name="resize_confirm">Изменить размер %tw_partition_name%?</string>
+		<string name="resizing">Изменение размера...</string>
+		<string name="resize_complete">Размер раздела изменён</string>
+		<string name="swipe_resize">Свайп для изменения размера</string>
+		<string name="swipe_resize_s">   Измен. размер</string>
+		<string name="repair_btn">Восст-ть файл. систему</string>
+		<string name="repair_btn_s">Восстановление</string>
+		<string name="repair_confirm">Восстановить %tw_partition_name%?</string>
+		<string name="repairing">Восстановление...</string>
+		<string name="repair_complete">Восстановление завершено</string>
+		<string name="swipe_repair">Свайп для восстановления</string>
+		<string name="swipe_repair_s">   Восстановление</string>
+		<string name="change_fs_btn">Изм. файловую систему</string>
+		<string name="change_fs_btn_s">Измен. ФС</string>
+		<string name="change_fs_for_hdr">Изменить файловую систему для %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Раздел: %tw_partition_name% &gt; Select File System</string>
+		<string name="change_fs_warn1">Некоторые прошивки или ядра могут не поддерживать</string>
+		<string name="change_fs_warn2">некоторые файловые системы. Действуйте с осторожностью!</string>
+		<string name="change_fs_confirm">Изменить %tw_partition_name%?</string>
+		<string name="formatting">Форматирование...</string>
+		<string name="format_complete">Форматирование завершено</string>
+		<string name="swipe_change_fs">Свайп для изменения</string>
+		<string name="swipe_change_s">   Изменить</string>
+		<string name="back_btn">Назад</string>
+		<string name="wipe_enc_btn">Стереть шифрование</string>
+		<string name="swipe_factory_reset">Свайп для подтверждения</string>
+		<string name="repair_change_btn">Восстановить или изменить файловую систему</string>
+		<string name="storage_hdr">Текущий накопитель: %tw_storage_display_name% (%tw_storage_free_size% МБ)</string>
+		<string name="backup_hdr">Резервное копирование</string>
+		<string name="backup_confirm_hdr">Подтверждение резервного копирования</string>
+		<string name="encryption_tab">ШИФРОВАНИЕ</string>
+		<string name="encryption">Шифрование:</string>
+		<string name="name">Имя:</string>
+		<string name="sel_part_backup">Выберите разделы для резервного копирования:</string>
+		<string name="storage">Хранилище:</string>
+		<string name="enc_disabled">выключено - установите пароль для включения</string>
+		<string name="enc_enabled">включено</string>
+		<string name="enable_backup_comp_chk">Использовать сжатие</string>
+		<string name="skip_digest_backup_chk" version="2">Не создавать контрольные суммы</string>
+		<string name="disable_backup_space_chk" version="2">Отключить проверку свободного места</string>
+		<string name="skip_digest_zip_chk">Не проверять контрольные суммы ZIP</string>
+		<string name="current_boot_slot">Текущий слот: %tw_active_slot%</string>
+		<string name="boot_slot_a">Слот A</string>
+		<string name="boot_slot_b">Слот B</string>
+		<string name="changing_boot_slot">Изменение слота загрузки</string>
+		<string name="changing_boot_slot_complete">Изменение слота загрузки завершено</string>
+		<string name="refresh_sizes_btn">Обновить размеры</string>
+		<string name="swipe_backup">Свайп для начала</string>
+		<string name="append_date_btn">Добавить дату</string>
+		<string name="backup_name_exists">Резервная копия с таким именем уже существует!</string>
+		<string name="encrypt_backup">Зашифровать вашу резервную копию?</string>
+		<string name="enter_pass">Введите пароль:</string>
+		<string name="enter_pass2">Введите пароль ещё раз:</string>
+		<string name="pass_not_match">Пароли не совпадают!</string>
+		<string name="partitions">Разделы:</string>
+		<string name="disabled">Выключено</string>
+		<string name="enabled">Включено</string>
+		<string name="backup_name_hdr">Установка имени резервной копии</string>
+		<string name="progress">Выполнение:</string>
+		<string name="backup_complete">Резервное копирование завершено</string>
+		<string name="restore_hdr">Восстановление</string>
+		<string name="sel_backup_hdr">Выбор резервной копии</string>
+		<string name="restore_sel_store_hdr">Текущий накопитель: %tw_storage_display_name% (%tw_storage_free_size% МБ)</string>
+		<string name="restore_sel_pack_fs">Выбор резервной копии для восстановления:</string>
+		<string name="restore_enc_backup_hdr">Зашифрованная резервная копия</string>
+		<string name="restore_dec_fail">Пароль неверный, попробуйте снова!</string>
+		<string name="del_backup_btn">Удаление рез.копии</string>
+		<string name="del_backup_confirm">Удалить резервную копию?</string>
+		<string name="del_backup_confirm2">Это не может быть отменено!</string>
+		<string name="deleting_backup">Удаление резервной копии...</string>
+		<string name="backup_deleted">Удаление завершено</string>
+		<string name="swipe_delete">Свайп для удаления</string>
+		<string name="swipe_delete_s">   Удаление</string>
+		<string name="restore_try_decrypt">Зашифрованная резервная копия - попытка расшифровки</string>
+		<string name="restore_try_decrypt_s">Попытка расшифровки</string>
+		<string name="restore_backup_date">Резервная копия создана: %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Выбор раздела для восстановления:</string>
+		<string name="restore_enable_digest_chk" version="2">Включить проверку контрольных сумм резервной копии</string>
+		<string name="restore_complete">Восстановление завершено</string>
+		<string name="swipe_restore">Свайп для восстановления</string>
+		<string name="swipe_restore_s">   Восстановление</string>
+		<string name="rename_backup_hdr">Переименование резервной копии</string>
+		<string name="rename_backup_confirm">Переименовать резервную копию?</string>
+		<string name="rename_backup_confirm2">Это не может быть отменено!</string>
+		<string name="renaming_backup">Переименование резервной копии...</string>
+		<string name="rename_backup_complete">Переименование завершено</string>
+		<string name="swipe_to_rename">Свайп для переименования</string>
+		<string name="swipe_rename">   Переименовать</string>
+		<string name="confirm_hdr">Подтвердить</string>
+		<string name="mount_hdr">Монтирование</string>
+		<string name="mount_sel_part">Выбор разделов для монтирования:</string>
+		<string name="mount_sys_ro_chk">Системный раздел только для чтения</string>
+		<string name="mount_sys_ro_s_chk"> Система RO</string>
+		<string name="decrypt_data_btn">Расшифровать данные</string>
+		<string name="disable_mtp_btn">Выключить MTP</string>
+		<string name="enable_mtp_btn">Включить MTP</string>
+		<string name="mount_usb_storage_btn">Включить UMS</string>
+		<string name="usb_storage_hdr">USB накопитель</string>
+		<string name="usb_stor_mnt1">Режим USB накопителя включён</string>
+		<string name="usb_stor_mnt2">Перед отключением вашего устройства от компьютера,</string>
+		<string name="usb_stor_mnt3">выполните размонтирование (выключите UMS)!</string>
+		<string name="unmount_btn">Выключить UMS</string>
+		<string name="rb_system_btn">Система</string>
+		<string name="rb_poweroff_btn">Выключение</string>
+		<string name="rb_recovery_btn">Рекавери</string>
+		<string name="rb_bootloader_btn">Загрузчик</string>
+		<string name="rb_download_btn">Режим загрузки</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Выключение...</string>
+		<string name="swipe_power_off">Свайп для выключения</string>
+		<string name="swipe_power_off_s">Выключение</string>
+		<string name="sys_ro_hdr">Модификация системного раздела</string>
+		<string name="sys_ro_keep">Оставить системный раздел только для чтения?</string>
+		<string name="sys_rop1">TWRP может оставить системный раздел неизменным,</string>
+		<string name="sys_rop2">чтобы сохранить возможность официальных обновлений.</string>
+		<string name="sys_rop3">При этом система сможет вернуть обратно стоковый</string>
+		<string name="sys_rop4">рекавери, а TWRP не будет предлагать установку root.</string>
+		<string name="sys_rop5">Установка ZIP или работа ADB всё равно смогут изменять</string>
+		<string name="sys_rop6">системный раздел.</string>
+		<string name="sys_rol1">TWRP может оставить системный раздел неизменным, чтобы сохранить возможность официальных обновлений.</string>
+		<string name="sys_rol2">При этом система сможет вернуть обратно стоковый рекавери, а TWRP не будет предлагать установку root.</string>
+		<string name="sys_rol3">Установка ZIP или работа ADB всё равно смогут изменять системный раздел.</string>
+		<string name="sys_ro_never_show_chk">Больше не показывать это при загрузке</string>
+		<string name="sys_ro_keep_ro_btn">Только для чтения</string>
+		<string name="swipe_allow_mod">Разрешить изменения</string>
+		<string name="swipe_allow_mod_s">Разрешить изменения</string>
+		<string name="settings_hdr">Настройки</string>
+		<string name="settings_gen_hdr">Основные</string>
+		<string name="settings_gen_s_hdr">Основные</string>
+		<string name="settings_gen_btn">Основные</string>
+		<string name="use_rmrf_chk">Использовать rm -rf вместо форматирования</string>
+		<string name="use24clock_chk">Использовать 24-часовой формат времени</string>
+		<string name="rev_navbar_chk">Инвертировать порядок навигационных кнопок</string>
+		<string name="simact_chk">Имитация действий для тестирования темы</string>
+		<string name="simfail_chk">Имитация отказа для действий</string>
+		<string name="ctr_navbar_rdo">Кнопки нав.панели по центру</string>
+		<string name="lft_navbar_rdo">Выровнять кнопки нав.панели слева</string>
+		<string name="rht_navbar_rdo">Выровнять кнопки нав.панели справа</string>
+		<string name="restore_defaults_btn">Сброс настроек</string>
+		<string name="settings_tz_btn">Часовой пояс</string>
+		<string name="settings_screen_btn">Экран</string>
+		<string name="settings_screen_bright_btn">Яркость</string>
+		<string name="vibration_disabled">Вибрация отключена для этого устройства</string>
+		<string name="settings_vibration_btn">Вибрация</string>
+		<string name="settings_language_btn">Язык</string>
+		<string name="time_zone_hdr">Часовой пояс</string>
+		<string name="sel_tz_list">Выбор часового пояса:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+			 feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC-11, SST) о.Мидуэй, Самоа</string>
+		<string name="utcm10">(UTC-10, HAST) Гавайи</string>
+		<string name="utcm9">(UTC-9, AKST) Аляска</string>
+		<string name="utcm8">(UTC-8, PST) Тихоокеанское время (США и Канада), Тихуана</string>
+		<string name="utcm7">(UTC-7, MST) Горное время (США и Канада), Аризона</string>
+		<string name="utcm6">(UTC-6, CST) Центральное время (США и Канада), Мехико</string>
+		<string name="utcm5">(UTC-5, EST) Восточное время (США и Канада), Богота, Лима, Кито</string>
+		<string name="utcm4">(UTC-4, AST) Атлантическое время (Канада), Каракас, Ла Пас, Сантьяго</string>
+		<string name="utcm3">(UTC-3) Бразилия, Буэнос-Айрес, Джорджтаун, Гренландия</string>
+		<string name="utcm2">(UTC-2) Среднеатлантическое время</string>
+		<string name="utcm1">(UTC-1) Азорские о-ва, Кабо-Верде (о-ва Зелёного мыса)</string>
+		<string name="utc0">(UTC 0) Лондон, Касабланка, Дублин, Лиссабон</string>
+		<string name="utcp1">(UTC+1, CET) Амстердам, Берлин, Брюссель, Мадрид, Париж, Рим</string>
+		<string name="utcp2">(UTC+2, KALT, EET) Калининград, Киев, Хельсинки, Варшава</string>
+		<string name="utcp3">(UTC+3, MSK) Московское время, Москва, Минск, Эр-Рияд</string>
+		<string name="utcp4">(UTC+4, SAMT) Самарское время, Волгоград, Баку, Тбилиси</string>
+		<string name="utcp5">(UTC+5, YEKT) Екатеринбургское время, Уфа, Челябинск, Ташкент</string>
+		<string name="utcp6">(UTC+6, OMST) Омское время, Алма-Ата, Бишкек</string>
+		<string name="utcp7">(UTC+7, KRAT) Красноярское время, Новосибирск, Томск</string>
+		<string name="utcp8">(UTC+8, IRKT) Иркутское время, Пекин, Гонконг, Сингапур</string>
+		<string name="utcp9">(UTC+9, YAKT) Якутское время, Чита, Токио, Сеул</string>
+		<string name="utcp10">(UTC+10, VLAT) Владивостокское время, Хабаровск, Сидней</string>
+		<string name="utcp11">(UTC+11, MAGT) Магаданское время, Южно-Сахалинск</string>
+		<string name="utcp12">(UTC+12, PETT, WAKT) Камчатское время, Анадырь</string>
+		<string name="sel_tz_offset">Выбор смещения (как правило 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Нет</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Использовать летнее время (DST)</string>
+		<string name="curr_tz">Текущий часовой пояс: %tw_time_zone%</string>
+		<string name="curr_tz_s">Текущий часовой пояс:</string>
+		<string name="set_tz_btn">Установ.часовой пояс</string>
+		<string name="settings_screen_hdr">Экран</string>
+		<string name="settings_screen_timeout_hdr">Таймаут экрана</string>
+		<string name="enable_timeout_chk">Включить таймаут экрана</string>
+		<string name="screen_to_slider">Выключение экрана через n сек:</string>
+		<string name="screen_to_slider_s">Выключение экрана через n сек (0=выключен): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Установка таймаута экрана недоступна</string>
+		<string name="screen_bright_slider">Яркость: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Установка яркости недоступна</string>
+		<string name="vibration_hdr">Вибрация</string>
+		<string name="button_vibration_hdr">Вибрация при нажатии кнопок</string>
+		<string name="kb_vibration_hdr">Вибрация при работе с клавиатурой</string>
+		<string name="act_vibration_hdr">Вибрация при выборе действия</string>
+		<string name="button_vibration">Вибрация при нажатии кнопок:</string>
+		<string name="kb_vibration">Вибрация при работе с клавиатурой:</string>
+		<string name="act_vibration">Вибрация при завершении действия:</string>
+		<string name="select_language">Выбор языка:</string>
+		<string name="sel_lang_btn">Выбор языка</string>
+		<string name="set_language_btn">Установить язык</string>
+		<string name="advanced_hdr">Дополнительно</string>
+		<string name="copy_log_confirm">Копировать лог на SD?</string>
+		<string name="copying_log" version="2">Копирование лога на SD...</string>
+		<string name="copy_log_complete" version="2">Копирование лога завершено.</string>
+		<string name="fix_context_btn">Исправить контекст</string>
+		<string name="part_sd_btn">Разметка SD-карты</string>
+		<string name="part_sd_s_btn">Разметка SD</string>
+		<string name="file_manager_btn">Проводник</string>
+		<string name="language_hdr">Язык</string>
+		<string name="terminal_btn">Терминал</string>
+		<string name="reload_theme_btn">Перезагрузить тему</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Интегрировать TWRP</string>
+		<string name="inject_twrp_confirm">Интегрировать TWRP заново?</string>
+		<string name="injecting_twrp">Интегрирование TWRP...</string>
+		<string name="inject_twrp_complete">Интегрирование TWRP завершено</string>
+		<string name="swipe_to_confirm">Свайп для подтверждения</string>
+		<string name="part_sd_hdr">Разметка SD-карты</string>
+		<string name="invalid_partsd_sel">Вы должны выбрать съёмное устройство</string>
+		<string name="part_sd_lose">Все файлы на вашей SD-карте будут стёрты!</string>
+		<string name="part_sd_undo">Это действие не может быть отменено!</string>
+		<string name="part_sd_ext_sz">Размер EXT-раздела:</string>
+		<string name="part_sd_swap_sz">Размер swap-раздела:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Файловая система:</string>
+		<string name="swipe_part_sd">Свайп для разметки</string>
+		<string name="swipe_part_sd_s">Разметка</string>
+		<string name="partitioning_sd">Разметка SD-карты...</string>
+		<string name="partitioning_sd2">Это может занять несколько минут.</string>
+		<string name="part_sd_complete">Разметка завершена</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Восстановление оригинального Boot</string>
+		<string name="dumlock_restore_confirm">Восстановить оригинальный образ boot?</string>
+		<string name="dumlock_restoring">Восстановление оригинального Boot...</string>
+		<string name="dumlock_restore_complete">Восстановление оригинального Boot завершено</string>
+		<string name="dumlock_reflash_btn">Перепрошивка рекавери</string>
+		<string name="dumlock_reflash_confirm">Перепрошить recovery в boot?</string>
+		<string name="dumlock_reflashing">Прошивка recovery в boot...</string>
+		<string name="dumlock_reflash_complete">Прошивка recovery в boot завершена</string>
+		<string name="dumlock_install_btn">Установка HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Установить HTC dumlock файлы в прошивку?</string>
+		<string name="dumlock_installing">Установка HTC Dumlock...</string>
+		<string name="dumlock_install_complete">Установка HTC Dumlock завершена</string>
+		<string name="swipe_to_unlock">Свайп для разблокировки</string>
+		<string name="swipe_unlock">   Разблокировать</string>
+		<string name="fm_hdr">Файловый менеджер</string>
+		<string name="fm_sel_file">Выбор файла или папки</string>
+		<string name="fm_type_folder">Папка</string>
+		<string name="fm_type_file">Файл</string>
+		<string name="fm_choose_act">Выбор действия</string>
+		<string name="fm_selected">%tw_fm_type% :</string>
+		<string name="fm_copy_btn">Копировать</string>
+		<string name="fm_copy_file_btn">Копировать файл</string>
+		<string name="fm_copy_folder_btn">Копировать папку</string>
+		<string name="fm_copying">Копирование</string>
+		<string name="fm_move_btn">Переместить</string>
+		<string name="fm_moving">Перемещение</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Удалить</string>
+		<string name="fm_deleting">Удаление</string>
+		<string name="fm_rename_btn">Переименовать</string>
+		<string name="fm_rename_file_btn">Переименовать файл</string>
+		<string name="fm_rename_folder_btn">Переименовать папку</string>
+		<string name="fm_renaming">Переименование</string>
+		<string name="fm_sel_dest">Выбор целевой папки</string>
+		<string name="fm_sel_curr_folder">Выбор текущей папки</string>
+		<string name="fm_rename_hdr">Переименовать</string>
+		<string name="fm_set_perms_hdr">Установить права</string>
+		<string name="fm_perms">Права:</string>
+		<string name="fm_complete">Операция завершена</string>
+		<string name="fm_open_terminal_btn">Открыть терминал здесь</string>
+		<string name="fm_edit_file">Изменить файл</string>
+		<string name="decrypt_data_hdr">Расшифровать данные</string>
+		<string name="decrypt_data_enter_pass">Введите пароль.</string>
+		<string name="decrypt_data_failed">Пароль неверный, попробуйте снова!</string>
+		<string name="decrypt_data_failed_pattern">Шаблон неверный, попробуйте снова!</string>
+		<string name="decrypt_data_enter_pattern">Введите шаблон.</string>
+		<string name="decrypt_data_trying">Попытка расшифровки</string>
+		<string name="decrypt_data_vold_os_missing">Отсутствуют файлы необходимые для расшифровки vold: {1}</string>
+		<string name="term_hdr">Команда терминала</string>
+		<string name="term_s_hdr">Терминал</string>
+		<string name="term_kill_btn">Остановить</string>
+		<string name="term_sel_folder_hdr">Перейдите к начальной папке</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Очистка Dalvik Cache</string>
+		<string name="sideload_wipe_cache_chk">Очистка Cache</string>
+		<string name="swipe_to_sideload">Свайп для начала sideload</string>
+		<string name="swipe_sideload">   Старт</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Пример: adb sideload filename.zip</string>
+		<string name="sideload_complete">ADB Sideload завершён</string>
+		<string name="fix_contexts_hdr">Исправление контекста SELinux</string>
+		<string name="fix_contexts_note1">Примечание: исправление контекста SELinux требуется редко.</string>
+		<string name="fix_contexts_note2">Исправление SELinux-контекста может привести</string>
+		<string name="fix_contexts_note3">к тому, что система не будет запускаться.</string>
+		<string name="swipe_to_fix_contexts">Свайп для исправлен. контекста</string>
+		<string name="swipe_fix_contexts">   Исправить контекст</string>
+		<string name="fixing_contexts">Исправление контекста...</string>
+		<string name="fix_contexts_complete">Исправление контекста завершено</string>
+		<string name="reboot_hdr">Перезагрузка</string>
+		<string name="install_cancel">Не устанавливать</string>
+		<string name="sel_storage_list">Выберите накопитель</string>
+		<string name="ok_btn">OK</string>
+		<string name="install_twrp_ramdisk">Установка рекавери в ramdisk</string>
+		<string name="install_kernel">Установка ядра</string>
+		<string name="repack_kernel_confirm_hdr">Установка ярда</string>
+		<string name="repack_ramdisk_confirm_hdr">Установка рекавери</string>
+		<string name="repack_kernel_confirm">Установить ядро?</string>
+		<string name="repack_ramdisk_confirm">Установить рекавери?</string>
+		<string name="repack_backup_first">Сначала сделайте резервную копию текущего образа</string>
+		<string name="repack">Перепаковка</string>
+		<string name="swipe_to_install">Свайп для установки</string>
+		<string name="installing">Установка...</string>
+		<string name="install_complete">Установка завершена</string>
+		<string name="unpack_error">Ошибка распаковки образа.</string>
+		<string name="repack_error">Ошибка перепаковки образа.</string>
+		<string name="modified_ramdisk_error">файлы рамдиска были изменены, не удалось создать рамдиск для установки, используйте fastboot boot twrp и попробуйте эту функцию снова или воспользуйтесь Установка рекавери в ramdisk.</string>
+		<string name="create_ramdisk_error">не удалось создать рамдиск для установки.</string>
+		<string name="unpacking_image">Распаковка {1}...</string>
+		<string name="repacking_image">Перепаковка {1}...</string>
+		<string name="repack_image_hdr">Выбор образа</string>
+		<string name="fix_recovery_loop">Исправить циклическую перезагрузку рекавери</string>
+		<string name="fix_recovery_loop_confirm">Исправить циклическую перезагрузку рекавери?</string>
+		<string name="fixing_recovery_loop">Исправление циклической перезагрузки рекавери...</string>
+		<string name="fix_recovery_loop_complete">Исправление циклической перезагрузки рекавери завершено</string>
+		<string name="fixing_recovery_loop_patch">Патчинг ядра...</string>
+		<string name="fix_recovery_loop_patch_error">Ошибка патчинга ядра.</string>
+		<string name="decrypt_users">Расшифровка пользователей</string>
+		<string name="decrypt_users_selection">Выберите ID пользователя для расшифровки</string>
+		<string name="select_user">Выбор пользователя</string>
+        <string name="backup_storage_undecrypt_warning">В резервную копию не включены некоторые файлы пользователя {1}, потому что пользователь не расшифрован.</string>
+        <string name="decrypting_user_fbe">Попытка расшифровать FBE пользователя {1}...</string>
+		<string name="decrypt_user_success_fbe">Пользователь {1} расшифрован успешно</string>
+		<string name="decrypt_user_fail_fbe">Ошибка расшифровки пользователя {1}</string>
+		<string name="decrypt_data_enter_pass_fbe">Введите пароль пользователя [%tw_crypto_user_id%]</string>
+		<string name="decrypt_data_enter_pattern_fbe">Введите шаблон пользователя [%tw_crypto_user_id%]</string>
+		<string name="multiuser_warning1">Не все пользователи расшифрованы!</string>
+        <string name="multiuser_warning2">Резервное копирование или восстановление могут завершиться неудачно!</string>
+		<string name="multiuser_warning_accept">Всё равно продолжить</string>
+        <string name="multiuser_warning_hdr">Предупреждение о нескольких пользователях</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">Чтение SELinux-контекста не поддерживается ядром.</string>
+		<string name="full_selinux">Присутствует полная поддержка SELinux.</string>
+		<string name="no_selinux">Отсутствует поддержка SELinux (нет libselinux).</string>
+		<string name="mtp_enabled">MTP Включено</string>
+		<string name="mtp_crash">Сбой MTP: MTP не запущен при загрузке.</string>
+		<string name="decrypt_success">Расшифровка выполнена успешно с паролём по умолчанию.</string>
+		<string name="unable_to_decrypt">Невозможно расшифровать с паролём по умолчанию. Возможно потребуется выполнить форматирование Data.</string>
+		<string name="generating_digest1" version="2">Вычисление контрольной суммы</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * Вычисление контрольной суммы...</string>
+		<string name="digest_created" version="2"> * Контрольная сумма создана.</string>
+		<string name="digest_error" version="2"> * Ошибка контрольной суммы!</string>
+		<string name="digest_compute_error" version="2"> * Ошибка вычисления контрольной суммы.</string>
+		<string name="current_date">(Текущая дата)</string>
+		<string name="auto_generate">(Создать автоматически)</string>
+		<string name="unable_to_locate_partition">Не удаётся найти '{1}' раздел для расчёта копирования.</string>
+		<string name="no_partition_selected">Не выбраны разделы для резервного копирования.</string>
+		<string name="total_partitions_backup"> * Общее количество разделов для резервного копирования: {1}</string>
+		<string name="total_backup_size"> * Общий объём данных: {1} МБ</string>
+		<string name="available_space"> * Доступный объём: {1} МБ</string>
+		<string name="unable_locate_storage">Не удаётся найти накопитель для хранения.</string>
+		<string name="no_space">В накопителе для хранения недостаточно свободного места.</string>
+		<string name="backup_started">[РЕЗЕРВНОЕ КОПИРОВАНИЕ НАЧАТО]</string>
+		<string name="backup_folder"> * Папка для резервной копии: {1}</string>
+		<string name="fail_backup_folder">Не удаётся создать папку для резервной копии.</string>
+		<string name="avg_backup_fs">Средняя скорость копирования для файлов: {1} МБ/сек</string>
+		<string name="avg_backup_img">Средняя скорость копирования для образов: {1} МБ/сек</string>
+		<string name="total_backed_size">[ВСЕГО СКОПИРОВАНО {1} МБ]</string>
+		<string name="backup_completed">[КОПИРОВАНИЕ ЗАВЕРШЕНО ЗА {1} СЕКУНД(Ы)]</string>
+		<string name="restore_started">[ВОССТАНОВЛЕНИЕ НАЧАТО]</string>
+		<string name="restore_folder">Папка для восстановления: '{1}'</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} выполнено за {2} секунд(ы)]</string>
+		<string name="verifying_digest" version="2">Проверка контрольной суммы</string>
+		<string name="skip_digest" version="2">Пропуск проверки контрольной суммы согласно настройкам.</string>
+		<string name="calc_restore">Вычисление информации о восстановлении...</string>
+		<string name="restore_read_only">Невозможно восстановить {1} -- смонтировано только для чтения.</string>
+		<string name="restore_unable_locate">Не удаётся найти '{1}' раздел для восстановления.</string>
+		<string name="no_part_restore">Не выбраны разделы для восстановления.</string>
+		<string name="restore_part_count">Восстановление {1} разделов...</string>
+		<string name="total_restore_size">Общий размер для восстановления: {1} МБ</string>
+		<string name="updating_system_details">Обновление информации о системе</string>
+		<string name="restore_completed">[ВОССТАНОВЛЕНИЕ ЗАВЕРШЕНО ЗА {1} СЕКУНД(Ы)]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">Ошибка открытия: '{1}' ({2})</string>
+		<string name="unable_locate_part_backup_name">Не удаётся найти раздел для копирования: '{1}'</string>
+		<string name="unable_find_part_path">Не удаётся найти раздел по пути '{1}'</string>
+		<string name="update_part_details">Обновление информации о разделах...</string>
+		<string name="update_part_details_done">...готово</string>
+		<string name="wiping_dalvik">Очистка Dalvik...</string>
+		<string name="cleaned">Очищены: {1}...</string>
+		<string name="cache_dalvik_done">-- Очистка директорий Dalvik Cache завершена!</string>
+		<string name="dalvik_done">-- Очистка директорий Dalvik завершена!</string>
+		<string name="no_andsec">Не найдены разделы android secure.</string>
+		<string name="unable_to_locate">{1} не найден.</string>
+		<string name="wiping_datamedia">Очистка внутренней памяти -- /data/media...</string>
+		<string name="unable_to_mount">Не удаётся смонтировать {1}</string>
+		<string name="unable_to_mount_internal">Не удаётся смонтировать internal_storage</string>
+		<string name="unable_to_mount_storage">Не удаётся смонтировать накопитель</string>
+		<string name="fail_decrypt">Не удаётся расшифровать data.</string>
+		<string name="no_crypto_support">Шифрование не добавлено в эту сборку, пинайте разработчика.</string>
+		<string name="decrypt_success_dev">Раздел Data успешно расшифрован, новое блочное устройство: '{1}'</string>
+		<string name="decrypt_success_nodev">Раздел Data успешно расшифрован</string>
+		<string name="done">Готово.</string>
+		<string name="start_partition_sd">Разметка SD-карты...</string>
+		<string name="partition_sd_locate">Не удалось найти устройство для разметки.</string>
+		<string name="ext_swap_size">Размер EXT + Swap превышает размер sdcard.</string>
+		<string name="remove_part_table">Удаление таблицы разделов...</string>
+		<string name="unable_rm_part">Не удаётся удалить таблицу разделов.</string>
+		<string name="create_part">Создание раздела {1}...</string>
+		<string name="unable_to_create_part">Не удаётся создать раздел {1}.</string>
+		<string name="format_sdext_as">Форматирование sd-ext в {1}...</string>
+		<string name="part_complete">Разметка завершена.</string>
+		<string name="unable_to_open">Не удаётся открыть '{1}'.</string>
+		<string name="mtp_already_enabled">MTP уже включён</string>
+		<string name="mtp_fail">Не удалось включить MTP</string>
+		<string name="no_mtp">Поддержка MTP отсутствует</string>
+		<string name="image_flash_start">[ПРОШИВКА ОБРАЗА НАЧАТА]</string>
+		<string name="img_to_flash">Образ для прошивки: '{1}'</string>
+		<string name="flash_unable_locate">Не удаётся найти раздел '{1}'.</string>
+		<string name="no_part_flash">Не выбраны разделы для прошивки.</string>
+		<string name="too_many_flash">Выбрано слишком много разделов для прошивки.</string>
+		<string name="invalid_flash">Указан неверный раздел для прошивки.</string>
+		<string name="flash_done">[ПРОШИВКА ОБРАЗА ЗАВЕРШЕНА]</string>
+		<string name="wiping">Очистка {1}</string>
+		<string name="repair_not_exist">{1} не существует! Невозможно восстановить!</string>
+		<string name="repairing_using">Восстановление {1} с помощью {2}...</string>
+		<string name="unable_repair">Невозможно восстановить {1}.</string>
+		<string name="mount_data_footer">Не удалось смонтировать /data, не найден крипто-ключ.</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">Невозможно создать папку '{1}' ({2}).</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">Не удаётся смонтировать '{1}' ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">Не удаётся размонтировать '{1}' ({2})</string>
+		<string name="cannot_resize">Невозможно изменить размер {1}.</string>
+		<string name="repair_resize">Восстановление {1} перед изменением размера.</string>
+		<string name="unable_resize">Не удаётся изменить размер {1}.</string>
+		<string name="no_digest_found" version="2">Отсутствует файл контрольной суммы для '{1}'. Пожалуйста отмените выбор проверки контрольных сумм при восстановлении.</string>
+		<string name="digest_fail_match" version="2">Контрольная сумма не соответствует '{1}'.</string>
+		<string name="digest_matched" version="2">Контрольная сумма соответствует '{1}'.</string>
+		<string name="fail_decrypt_tar">Не удаётся расшифровать tar-файл '{1}'</string>
+		<string name="format_data_msg">Вам, возможно, потребуется перезагрузить рекавери, чтобы иметь возможность использовать /data снова.</string>
+		<string name="format_data_err">Невозможно отформатировать и удалить шифрование.</string>
+		<string name="formatting_using">Форматирование {1} с помощью {2}...</string>
+		<string name="unable_to_wipe">Невозможно очистить {1}.</string>
+		<string name="cannot_wipe">Раздел {1} не может быть очищен.</string>
+		<string name="remove_all">Удаление всех файлов в '{1}'</string>
+		<string name="wiping_data">Очистка /data без очистки /data/media ...</string>
+		<string name="backing_up">Резервное копирование {1}...</string>
+		<string name="backup_storage_warning">Резервное копирование {1}, за исключением файлов во внутренней памяти, таких как фотографии или загрузки.</string>
+		<string name="backing">Резервное копирование</string>
+		<string name="backup_size">Размер файла резервной копии для '{1}' 0 байт.</string>
+		<string name="datamedia_fs_restore">ВНИМАНИЕ: Эта резервная копия /data была создана с файловой системы {1}! Резервная копия может не загрузиться, если вы не измените обратно на {1}.</string>
+		<string name="restoring">Восстановление {1}...</string>
+		<string name="restoring_hdr">Восстановление</string>
+		<string name="recreate_folder_err">Невозможно заново создать папку {1}.</string>
+		<string name="img_size_err">Размер образа больше размера целевого устройства</string>
+		<string name="flashing">Прошивка {1}...</string>
+		<string name="backup_folder_set">Папка для резервной копии установлена в '{1}'</string>
+		<string name="locate_backup_err">Не удаётся найти резервную копию '{1}'</string>
+		<string name="set_restore_opt">Установки опций восстановления: '{1}':</string>
+		<string name="digest_check_skip" version="2">Пропуск проверки контрольных сумм включён</string>
+		<string name="ors_encrypt_restore_err">Не удаётся использовать OpenRecoveryScript для восстановления зашифрованной резервной копии.</string>
+		<string name="mounting">Монтирование</string>
+		<string name="unmounting">Размонтирование</string>
+		<string name="mounted">'{1}' смонтирован</string>
+		<string name="unmounted">'{1}' размонтирован</string>
+		<string name="setting">Установка '{1}' в '{2}'</string>
+		<string name="setting_empty">Установка значения '{1}' в пустое</string>
+		<string name="making_dir1">Создание директории</string>
+		<string name="making_dir2">Создание директории '{1}'</string>
+		<string name="running_command">Запуск команды</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">Запуск ADB Sideload...</string>
+		<string name="need_new_adb">Вам необходим ADB 1.0.32 или новее для выполнения sideload на этом устр-ве.</string>
+		<string name="no_pwd">Не предоставлен пароль.</string>
+		<string name="done_ors">Завершено выполнение файла сценария</string>
+		<string name="injecttwrp">Введение TWRP в образ boot...</string>
+		<string name="zip_err">Ошибка при установке ZIP-файла '{1}'</string>
+		<string name="installing_zip">Установка ZIP-файла '{1}'</string>
+		<string name="select_backup_opt">Установка опций резервного копирования:</string>
+		<string name="compression_on">Сжатие включено</string>
+		<string name="digest_off" version="2">Вычисление контрольных сумм выключено</string>
+		<string name="backup_fail">Ошибка резервного копирования</string>
+		<string name="backup_clean">Ошибка резервного копирования. Очистка папки с резервной копией.</string>
+		<string name="running_recovery_commands">Выполнение команд рекавери</string>
+		<string name="recovery_commands_complete">Команды рекавери завершены</string>
+		<string name="running_ors">Выполнение OpenRecoveryScript</string>
+		<string name="ors_complete">OpenRecoveryScript выполнен</string>
+		<string name="check_for_digest" version="2">Проверка файлов контрольных сумм...</string>
+		<string name="invalid_zip_format">Неверный формат ZIP-файла!</string>
+		<string name="fail_sysmap">Не удалось отобразить в память файл '{1}'</string>
+		<string name="verify_zip_sig">Проверка подписи ZIP...</string>
+		<string name="verify_zip_fail">Подпись ZIP не прошла проверку!</string>
+		<string name="verify_zip_done">Подпись ZIP проверена успешно.</string>
+		<string name="zip_corrupt">ZIP-файл повреждён!</string>
+		<string name="no_digest" version="2">Пропуск проверки контрольной суммы: не найден файл контрольной суммы</string>
+		<string name="digest_fail" version="2">Контрольная сумма не соответствует</string>
+		<string name="digest_match" version="2">Контрольная сумма соответствует</string>
+		<string name="pid_signal">Процесс {1} завершился с кодом {2}</string>
+		<string name="pid_error">Процесс {1} завершился с ОШИБКОЙ: {2}</string>
+		<string name="install_dumlock">Установка HTC Dumlock в систему...</string>
+		<string name="dumlock_restore">Восстановление оригинального boot...</string>
+		<string name="dumlock_reflash">Перепрошивка recovery в boot...</string>
+		<string name="run_script">Запуск сценария {1}...</string>
+		<string name="rename_stock">Переименование файла стокового рекавери в /system для предотвращения возврата на стоковое рекавери вместо TWRP.</string>
+		<string name="split_backup">Разделение резервной копии на несколько архивов...</string>
+		<string name="backup_error">Ошибка создания резервной копии.</string>
+		<string name="restore_error">Ошибка восстановления резервной копии.</string>
+		<string name="split_thread">Разделение идентификатора потока {1} в архив {2}</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">%llu из %llu файлов, %i%%</string>
+		<string name="size_progress">%llu МБ из %llu МБ, %i%%</string>
+		<string name="decrypt_cmd" version="2">Попытка расшифровать раздел данных или пользовательских данных с помощью командной строки.</string>
+		<string name="base_pkg_err">Неудача при загрузке базовых пакетов.</string>
+		<string name="simulating">Имитация действий...</string>
+		<string name="backup_cancel">Резервное копирование отменено</string>
+		<string name="config_twrp">Конфигурирование TWRP...</string>
+		<string name="config_twrp_err">Невозможно сконфигурировать TWRP с этим ядром.</string>
+		<string name="copy_log">recovery.log скопирован в {1}.</string>
+		<string name="max_queue">Максимум ZIP в очереди достигнут!</string>
+		<string name="min_queue">Минимум ZIP в очереди достигнут!</string>
+		<string name="screenshot_saved">Скриншот сохранён в {1}</string>
+		<string name="screenshot_err">Не удалось сделать скриншот!</string>
+		<string name="zip_wipe_cache">Один или более ZIP запрашивают очистку cache -- Очистка cache...</string>
+		<string name="and_sec_wipe_err">Невозможно очистить android secure</string>
+		<string name="dalvik_wipe_err">Не удалось очистить dalvik</string>
+		<string name="auto_gen">(Создать автоматически)</string>
+		<string name="curr_date">(Текущая дата)</string>
+		<string name="backup_name_len">Имя резервной копии слишком длинное.</string>
+		<string name="backup_name_invalid">Имя резервной копии '{1}' содержит недопустимые символы: '{1}'</string>
+		<string name="no_real_sdcard">Отмена, т.к. в устройстве отсутствует реальная SD-карта!</string>
+		<string name="cancel_sideload">Отмена ADB Sideload...</string>
+		<string name="change_fs_err">Ошибка при изменении файловой системы.</string>
+		<string name="theme_ver_err">Версия темы не соответствует версии TWRP. Используется тема по умолчанию.</string>
+		<string name="up_a_level">(в родительский каталог)</string>
+		<string name="install_reboot" version="2">Устройство будет перезагружено через %tw_sleep% сек.</string>
+		<string name="adbbackup_error">Ошибка резервного копирования по ADB. Выход...</string>
+		<string name="adbbackup_control_error">Невозможно записать в канал управления ADB.</string>
+		<string name="twrp_adbbu_option">-- эта опция позволяет делать резервное копирование по ADB</string>
+		<string name="partition_not_found">путь: {1} отсутствует в списке разделов.</string>
+		<string name="copy_kernel_log">Kernel Log скопирован в {1}</string>
+		<string name="include_kernel_log">Добавить Kernel Log</string>
+		<string name="sha2_chk">Использовать SHA2 для контрольных сумм (иначе - MD5)</string>
+		<string name="unable_set_boot_slot">Ошибка смены слота загрузки bootloader на {1}</string>
+		<string name="unmount_sys_install">Размонтировать System перед установкой ZIP</string>
+		<string name="unmount_system">Размонтирование System...</string>
+		<string name="unmount_system_err">Ошибка размонтирования System</string>
+		<string name="flash_ab_inactive">Прошивка A/B ZIP в неактивный слот: {1}</string>
+		<string name="flash_ab_reboot">Для прошивки дополнительных ZIP перезагрузите рекавери для переключения на обновлённый слот.</string>
+		<string name="flash_ab_both_slots">Прошить в оба слота</string>
+		<string name="ozip_decrypt_decryption">Начало расшифровки Ozip...</string>
+		<string name="ozip_decrypt_finish">Расшифровка Ozip завершена!</string>
+		<string name="fbe_wipe_msg">ВНИМАНИЕ: {1} очищен. Устройство необходимо перезагрузить в ОС (не в рекавери) для установки начальной политики FBE после очистки.</string>
+		<string name="fastboot_button">Fastboot</string>
+		<string name="enable_adb">Включить ADB</string>
+		<string name="enable_fastboot">Включить Fastboot</string>
+		<string name="fastboot_console_msg">Переход в режим Fastboot...</string>
+		<!-- Android Rescue Party trigger -->
+		<string name="rescue_party0">Обнаружен сбой Android Rescue Party! Возможные решения:</string>
+		<string name="rescue_party1"> 1. Очистить Cache и/или</string>
+		<string name="rescue_party2"> 2. Форматировать Data и/или</string>
+		<string name="rescue_party3"> 3. Заново установить прошивку.</string>
+		<string name="rescue_party4">Проблема: </string>
+		<string name="restore_system_context">Невозможно получить контекст по умолчанию для {1} -- Android может не загрузиться.</string>
+		<string name="change_twrp_folder_btn">Сменить папку TWRP</string>
+		<string name="change_twrp_folder_on_process">Смена папки TWRP</string>
+		<string name="change_twrp_folder_after_process">Папка TWRP изменена на</string>
+		<string name="tw_folder_exists">Папка с таким именем уже существует!</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/sk.xml b/gui/theme/common/languages/sk.xml
new file mode 100644
index 0000000..e212f7d
--- /dev/null
+++ b/gui/theme/common/languages/sk.xml
@@ -0,0 +1,634 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<language>
+	<display>Slovak</display>
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<string name="system">Systém</string>
+		<string name="system_image">Image systému</string>
+		<string name="vendor">Dodávateľ</string>
+		<string name="vendor_image">Logo dodávateľa</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="sdcard">SD karta</string>
+		<string name="internal">Interné úložisko</string>
+		<string name="microsd">Mikro SD karta</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Prijaté dáta</string>
+		<string name="adopted_storage">Prijaté úložisko</string>
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% °C</string>
+		<string name="battery_pct">Batéria: %tw_battery%</string>
+		<string name="sort_by_name">Zoradiť podľa názvu</string>
+		<string name="sort_by_date">Zoradiť podľa dátumu</string>
+		<string name="sort_by_size">Zoradiť podľa veľkosti</string>
+		<string name="sort_by_name_only">Názov</string>
+		<string name="sort_by_date_only">Dátum</string>
+		<string name="sort_by_size_only">Veľkosť</string>
+		<string name="tab_general">VŠEOBECNÉ</string>
+		<string name="tab_options">MOŽNOSTI</string>
+		<string name="tab_backup">ZÁLOHOVANIE</string>
+		<string name="tab_time_zone">ČASOVÉ PÁSMO</string>
+		<string name="tab_screen">OBRAZOVKA</string>
+		<string name="tab_vibration">VIBRÁCIE</string>
+		<string name="tab_language">JAZYK</string>
+		<string name="install_btn">Inštalovať</string>
+		<string name="wipe_btn">Vymazať</string>
+		<string name="backup_btn">Zálohovať</string>
+		<string name="restore_btn">Obnoviť</string>
+		<string name="mount_btn">Pripojiť</string>
+		<string name="settings_btn">Nastavenia</string>
+		<string name="advanced_btn">Rozšírené</string>
+		<string name="reboot_btn">Reštartovať</string>
+		<string name="files_btn">Súbory</string>
+		<string name="copy_log_btn">Kopírovať log</string>
+		<string name="select_type_hdr">Vyberte typ</string>
+		<string name="install_zip_hdr">Inštalovať Zip</string>
+		<string name="install_zip_btn">Inštalovať Zip</string>
+		<string name="install_image_hdr">Inštalovať image</string>
+		<string name="install_image_btn">Inštalovať image</string>
+		<string name="install_select_file_hdr">Vybrať súbor</string>
+		<string name="file_selector_folders_hdr">Priečinky</string>
+		<string name="select_file_from_storage">Vyberte súbor z: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Inštalovať</string>
+		<string name="select_storage_hdr">Vyberte úložisko</string>
+		<string name="select_storage_btn">Vyberte úložisko</string>
+		<string name="queue_hdr">Poradie</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% / max 10 súborov v poradí</string>
+		<string name="zip_queue_count_s">Súbor %tw_zip_queue_count% z 10:</string>
+		<string name="zip_warn1">Táto operácia môže nainštalovať nekompatibilný</string>
+		<string name="zip_warn2">softvér a znefunkčniť zariadenie.</string>
+		<string name="zip_back_cancel">Stlačte Naspäť pre zrušenie pridania zip.</string>
+		<string name="zip_back_clear">Stlačte Naspäť pre vyčistenie zoznamu.</string>
+		<string name="folder">Zložka:</string>
+		<string name="file">Súbor:</string>
+		<string name="zip_sig_chk">Overenie podpisu zip</string>
+		<string name="inject_twrp_chk">Nainštalovať TWRP po inštalácií</string>
+		<string name="options_hdr">Nastavenia</string>
+		<string name="confirm_flash_hdr">Potvrdiť inštaláciu</string>
+		<string name="zip_queue">Poradie:</string>
+		<string name="options">Nastavenia:</string>
+		<string name="swipe_confirm">   Potvrdiť</string>
+		<string name="zip_add_btn">Pridať viac Zip</string>
+		<string name="zip_clear_btn">Vyčistiť zoznam Zip</string>
+		<string name="install_zip_count_hdr">Nainštalovať Zip %tw_zip_index% z %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Inštalácia Zip: %tw_file%</string>
+		<string name="failed">Neúspešné</string>
+		<string name="successful">Úspešné</string>
+		<string name="install_failed">Inštalácia zlyhala</string>
+		<string name="install_successful">Inštalácia dokončená</string>
+		<string name="wipe_cache_dalvik_btn">Vymazať cache/dalvik</string>
+		<string name="reboot_system_btn">Reštartovať systém</string>
+		<string name="install_sel_target">Vyberte cieľovú partíciu</string>
+		<string name="flash_image_select">Vyberte cieľovú partíciu pre zápis image:</string>
+		<string name="target_partition">Cieľová partícia:</string>
+		<string name="flashing_image">Zapisovanie image...</string>
+		<string name="image_flashed">Image nainštalovaný</string>
+		<string name="wipe_cache_dalvik_confirm">Vymazať cache &amp; dalvik?</string>
+		<string name="wiping_cache_dalvik">Mazanie cache &amp; dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Vymazanie cache &amp; dalvik dokončené</string>
+		<string name="swipe_wipe">Potiahnite pre vymazanie</string>
+		<string name="swipe_wipe_s">   Vymazanie</string>
+		<string name="no_os1">Nie je nainštalovaný OS! Ste</string>
+		<string name="no_osrb">si istý, že chcete reštartovať?</string>
+		<string name="no_ospo">si istý, že chcete vypnúť zariadenie?</string>
+		<string name="rebooting">Reštartovanie...</string>
+		<string name="swipe_reboot">Potiahnite pre reštart</string>
+		<string name="swipe_reboot_s">   Reštart</string>
+		<string name="swipe_flash">Potiahnite pre potvrdenie</string>
+		<string name="confirm_action">Potvrdiť akciu</string>
+		<string name="back_cancel">Pre zrušenie stlačte tlačidlo naspäť.</string>
+		<string name="cancel_btn">Zrušiť</string>
+		<string name="wipe_hdr">Vymazať</string>
+		<string name="factory_reset_hdr">Továrne nastavenia</string>
+		<string name="factory_reset_btn">Továrne nastavenia</string>
+		<string name="factory_reset1">Vymazať Dáta, Cache a Dalvik</string>
+		<string name="factory_reset2">(nezahŕňa vnútornú pamäť)</string>
+		<string name="factory_reset3">Väčšinou je</string>
+		<string name="factory_reset4">potrebné len vymazanie.</string>
+		<string name="factory_resetting">Továrne nastavenia...</string>
+		<string name="advanced_wipe_hdr">Rozšírené vymazanie</string>
+		<string name="advanced_wipe_btn">Rozšírené vymazanie</string>
+		<string name="wipe_enc_confirm">Vymazať šifrovanie z Data?</string>
+		<string name="formatting_data">Formátovanie Data partície...</string>
+		<string name="swipe_format_data">Potiahnite pre voľbu Formátovať Data</string>
+		<string name="swipe_format_data_s">   Formátovať Data</string>
+		<string name="factory_reset_complete">Továrne nastavenia ukončené</string>
+		<string name="sel_part_hdr">Vyberte partície</string>
+		<string name="wipe_sel_confirm">Vymazať vybrané partície?</string>
+		<string name="wiping_part">Mazanie partícií...</string>
+		<string name="wipe_complete">Mazanie dokončené</string>
+		<string name="sel_part_wipe">Vyberte partície na vymazanie:</string>
+		<string name="invalid_part_sel">Neplatný výber partície</string>
+		<string name="format_data_hdr">Formátovať Data</string>
+		<string name="format_data_btn">Formátovať Data</string>
+		<string name="format_data_ptr1">Formátovanie Data vymaže všetky aplikácie,</string>
+		<string name="format_data_ptr2">zálohy, fotky, videá, hudbu</string>
+		<string name="format_data_ptr3">a vymaže šifrovanie interného úložiska.</string>
+		<string name="format_data_adopted">Zahŕňa adoptovanú pamäť</string>
+		<string name="format_data_lcp1">Formátovanie Data vymaže všetky aplikácie, zálohy, fotografie, videá, hudbu</string>
+		<string name="format_data_lcp2">a vymaže šifrovanie interného úložiska.</string>
+		<string name="format_data_wtc1">Formátovanie Data vymaže všetky aplikácie,</string>
+		<string name="format_data_wtc2">zálohy a hudbu. Tento proces je nezvratný.</string>
+		<string name="format_data_undo">Toto je nezvratné.</string>
+		<string name="format_data_complete">Formátovanie Data je hotové</string>
+		<string name="yes_continue">Napíšte \"yes\" pre pokračovanie. Stlačte naspäť pre zrušenie.</string>
+		<string name="part_opt_hdr">Možnosti pre: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Vyberte akciu</string>
+		<string name="file_sys_opt">Nastavenia súborového systému</string>
+		<string name="partition">Partície: %tw_partition_name%</string>
+		<string name="part_mount_point">Mount point: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Súborový systém: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Prítomný: Áno</string>
+		<string name="part_present_no">Prítomný: Nie</string>
+		<string name="part_removable_yes">Vymeniteľný: Áno</string>
+		<string name="part_removable_no">Vymeniteľný: Nie</string>
+		<string name="part_size">Veľkosť: %tw_partition_size%MB</string>
+		<string name="part_used">Použité: %tw_partition_used%MB</string>
+		<string name="part_free">Voľné: %tw_partition_free%MB</string>
+		<string name="part_backup_size">Veľkosť zálohy: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">Zväčšiť súborový systém</string>
+		<string name="resize_btn_s">Zväčšiť</string>
+		<string name="resize_confirm">Zväčšiť %tw_partition_name%?</string>
+		<string name="resizing">Zväčšenie...</string>
+		<string name="resize_complete">Zväčšenie dokončené</string>
+		<string name="swipe_resize">Potiahnite pre zväčšenie</string>
+		<string name="swipe_resize_s">   Zväčšenie</string>
+		<string name="repair_btn">Opraviť súborový systém</string>
+		<string name="repair_btn_s">Oprava</string>
+		<string name="repair_confirm">Opraviť %tw_partition_name%?</string>
+		<string name="repairing">Opravovanie...</string>
+		<string name="repair_complete">Oprava dokončená</string>
+		<string name="swipe_repair">Potiahnite pre opravu</string>
+		<string name="swipe_repair_s">   Oprava</string>
+		<string name="change_fs_btn">Zmeniť súborový systém</string>
+		<string name="change_fs_btn_s">Zmeniť</string>
+		<string name="change_fs_for_hdr">Zmeniť súborový systém pre: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Partícia: %tw_partition_name% &gt; Zvoľte súborový systém</string>
+		<string name="change_fs_warn1">Niektoré ROM alebo kernely nemusia podporovať niektoré</string>
+		<string name="change_fs_warn2">súborové systémy. Pokračujte opatrne!</string>
+		<string name="change_fs_confirm">Zmeniť %tw_partition_name%?</string>
+		<string name="formatting">Formátovanie...</string>
+		<string name="format_complete">Formátovanie dokončené</string>
+		<string name="swipe_change_fs">Potiahnite pre zmenu</string>
+		<string name="swipe_change_s">   Zmena</string>
+		<string name="back_btn">Naspäť</string>
+		<string name="wipe_enc_btn">Vymazať šifrovanie</string>
+		<string name="swipe_factory_reset">Potiahnite pre továrne nastavenia</string>
+		<string name="repair_change_btn">Opraviť alebo zmeniť súborový systém</string>
+		<string name="storage_hdr">Úložisko: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Záloha</string>
+		<string name="backup_confirm_hdr">Potvrdiť zálohu</string>
+		<string name="encryption_tab">ŠIFROVANIE</string>
+		<string name="encryption">Šifrovanie:</string>
+		<string name="name">Názov:</string>
+		<string name="sel_part_backup">Vybrať partície pre zálohovanie:</string>
+		<string name="storage">Pamäť:</string>
+		<string name="enc_disabled">zakázané - pre povolenie zadajte heslo</string>
+		<string name="enc_enabled">povolené</string>
+		<string name="enable_backup_comp_chk">Povoliť kompresiu</string>
+		<string name="skip_digest_backup_chk" version="2">Vynechať generovanie Digest počas zálohy</string>
+		<string name="disable_backup_space_chk">Nekontrolovať voľné miesto</string>
+		<string name="refresh_sizes_btn">Obnoviť veľkosti</string>
+		<string name="swipe_backup">Potiahnite pre zálohu</string>
+		<string name="append_date_btn">Pripojiť dátum</string>
+		<string name="backup_name_exists">Záloha s rovnakým názvom už existuje!</string>
+		<string name="encrypt_backup">Šifrovať zálohu?</string>
+		<string name="enter_pass">Zadajte heslo:</string>
+		<string name="enter_pass2">Zopakujte heslo:</string>
+		<string name="pass_not_match">Heslá sa nezhodujú!</string>
+		<string name="partitions">Partície:</string>
+		<string name="disabled">Zakázané</string>
+		<string name="enabled">Povolené</string>
+		<string name="backup_name_hdr">Názov zálohy</string>
+		<string name="progress">Priebeh:</string>
+		<string name="backup_complete">Zálohovanie je dokončené</string>
+		<string name="restore_hdr">Obnovenie</string>
+		<string name="sel_backup_hdr">Vyberte zálohu</string>
+		<string name="restore_sel_store_hdr">Vyberte zálohu z %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Vyberte balíček pre obnovu:</string>
+		<string name="restore_enc_backup_hdr">Šifrovaná záloha</string>
+		<string name="restore_dec_fail">Heslo je nesprávne, skúste to znova!</string>
+		<string name="del_backup_btn">Vymazať zálohu</string>
+		<string name="del_backup_confirm">Vymazať zálohu?</string>
+		<string name="del_backup_confirm2">Toto je nezvratné!</string>
+		<string name="deleting_backup">Vymazávanie zálohy...</string>
+		<string name="backup_deleted">Vymazanie zálohy dokončené</string>
+		<string name="swipe_delete">Potiahnite pre vymazanie</string>
+		<string name="swipe_delete_s">   Vymazať</string>
+		<string name="restore_try_decrypt">Šifrovaná záloha - pokus o dešifrovanie</string>
+		<string name="restore_try_decrypt_s">Pokus o dešifrovanie</string>
+		<string name="restore_backup_date">Záloha vytvorená %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Vybrať partície pre obnovu:</string>
+		<string name="restore_enable_digest_chk" version="2">Povoliť kontrolu Digest na zálohovaných súboroch</string>
+		<string name="restore_complete">Obnova dokončená</string>
+		<string name="swipe_restore">Potiahnite pre obnovu</string>
+		<string name="swipe_restore_s">   Obnoviť</string>
+		<string name="rename_backup_hdr">Premenovať zálohu</string>
+		<string name="rename_backup_confirm">Premenovať zálohu?</string>
+		<string name="rename_backup_confirm2">Toto je nezvratné!</string>
+		<string name="renaming_backup">Premenovanie zálohy...</string>
+		<string name="rename_backup_complete">Premenovanie zálohy dokončené</string>
+		<string name="swipe_to_rename">Potiahnutím premenujete</string>
+		<string name="swipe_rename">   Premenovať</string>
+		<string name="confirm_hdr">Potvrdiť</string>
+		<string name="mount_hdr">Pripojiť</string>
+		<string name="mount_sel_part">Vybrať partície pre pripojenie:</string>
+		<string name="mount_sys_ro_chk">Pripojiť partície v režime na čítanie</string>
+		<string name="mount_sys_ro_s_chk">Pripojiť systém v režime na čítanie</string>
+		<string name="decrypt_data_btn">Dešifrovať Data</string>
+		<string name="disable_mtp_btn">Zakázať MTP</string>
+		<string name="enable_mtp_btn">Povoliť MTP</string>
+		<string name="mount_usb_storage_btn">Pripoj. ukl. priestor USB</string>
+		<string name="usb_storage_hdr">Ukladací priestor USB</string>
+		<string name="usb_stor_mnt1">Ukladací priestor USB pripojený</string>
+		<string name="usb_stor_mnt2">Uistite sa, ze ste zariadenie odobrali</string>
+		<string name="usb_stor_mnt3">z počítača pred odpojením!</string>
+		<string name="unmount_btn">Odpojiť</string>
+		<string name="rb_system_btn">Systém</string>
+		<string name="rb_poweroff_btn">Vypnúť</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Stiahnuť</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Vypínanie...</string>
+		<string name="swipe_power_off">Potiahnite pre vypnutie</string>
+		<string name="swipe_power_off_s">Vypnúť</string>
+		<string name="sys_ro_hdr">Nezmenená systémová partícia</string>
+		<string name="sys_ro_keep">Ponechať System v režime na čítanie?</string>
+		<string name="sys_rop1">TWRP môže ponechať systémovú partíciu nezmenenú</string>
+		<string name="sys_rop2">čo zjednoduší inštaláciu oficiálnych aktualizácií.</string>
+		<string name="sys_rop3">TWRP sa nezabráni oficiálnej ROM</string>
+		<string name="sys_rop4">prepísať TWRP a nebude ponúkať rootovanie zariadenia.</string>
+		<string name="sys_rop5">Inštalovanie Zip alebo operácie cez adb môžu stále</string>
+		<string name="sys_rop6">modifikovať systémovú partíciu.</string>
+		<string name="sys_rol1">TWRP môže ponechať systémovú partíciu nezmenenú čo zjednoduší inštaláciu oficiálnych aktualizácií.</string>
+		<string name="sys_rol2">TWRP nezabráni oficiálnej ROM prepísať TWRP a nebude ponúkať rootovanie zariadenia.</string>
+		<string name="sys_rol3">Inštalovanie Zip alebo operácie cez adb môžu stále modifikovať systémovú partíciu.</string>
+		<string name="sys_ro_never_show_chk">Viac nezobrazovať túto obrazovku pri štarte</string>
+		<string name="sys_ro_keep_ro_btn">Ponechať v režime na čítanie</string>
+		<string name="swipe_allow_mod">Potiahnuť pre povolenie zmien</string>
+		<string name="swipe_allow_mod_s">Povoliť zmeny</string>
+		<string name="settings_hdr">Nastavenia</string>
+		<string name="settings_gen_hdr">Všeobecné</string>
+		<string name="settings_gen_s_hdr">Všeobecné</string>
+		<string name="settings_gen_btn">Všeobecné</string>
+		<string name="use_rmrf_chk">Použiť rm -rf namiesto formátovania</string>
+		<string name="use24clock_chk">Používať 24h formát času</string>
+		<string name="rev_navbar_chk">Prevrátené usporiadanie lišty</string>
+		<string name="simact_chk">Simulovať akcie pre testovanie témy</string>
+		<string name="simfail_chk">Simulovať neúspechy akcií</string>
+		<string name="ctr_navbar_rdo">Centrovať tlačidlá navigácie</string>
+		<string name="lft_navbar_rdo">Zarovnať tlačidlá navigácie vľavo</string>
+		<string name="rht_navbar_rdo">Zarovnať tlačidlá navigácie vpravo</string>
+		<string name="restore_defaults_btn">Obnoviť predvolené</string>
+		<string name="settings_tz_btn">Časové pásmo</string>
+		<string name="settings_screen_btn">Obrazovka</string>
+		<string name="settings_screen_bright_btn">Jas obrazovky</string>
+		<string name="settings_vibration_btn">Vibrovať</string>
+		<string name="settings_language_btn">Jazyk</string>
+		<string name="time_zone_hdr">Časové pásmo</string>
+		<string name="sel_tz_list">Vyberte časové pásmo:</string>
+		<string name="utcm11">(UTC -11) Samoa, Midwayské ostrovy</string>
+		<string name="utcm10">(UTC -10) Havaj</string>
+		<string name="utcm9">(UTC -9) Aljaška</string>
+		<string name="utcm8">(UTC -8) Tichomorský čas</string>
+		<string name="utcm7">(UTC -7) Západný (Horský) čas USA, Arizona</string>
+		<string name="utcm6">(UTC -6) Centrálny čas USA, Mexiko</string>
+		<string name="utcm5">(UTC -5) Východný čas USA, Indiana</string>
+		<string name="utcm4">(UTC -4) Atlantický čas, Caracas</string>
+		<string name="utcm3">(UTC -3) Brazília, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Stredoatlantický čas</string>
+		<string name="utcm1">(UTC -1) Azory, Cape Verde</string>
+		<string name="utc0">(UTC  0) Greenwichský čas, Londýn, Dublin, Lisbon</string>
+		<string name="utcp1">(UTC +1) Stredoeurópsky čas, Bratislava, Berlín, Brusel, Paríž</string>
+		<string name="utcp2">(UTC +2) Atény, Istanbul, Minsk, Káhira</string>
+		<string name="utcp3">(UTC +3) Moskva, Bagdad, Teherán</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC +5) Islamabád, Karáčí, Jekaterinsburg</string>
+		<string name="utcp6">(UTC +6) Astana, Colombo, Dhakar</string>
+		<string name="utcp7">(UTC +7) Bangkok, Džakarta, Hanoj</string>
+		<string name="utcp8">(UTC +8) Peking, Hong Kong, Singapur</string>
+		<string name="utcp9">(UTC +9) Jakutsk, Tokyo, Soul</string>
+		<string name="utcp10">(UTC +10) Vladivostok, Sydney, Guam</string>
+		<string name="utcp11">(UTC +11) Magadan, Šalamúnové ostrovy</string>
+		<string name="utcp12">(UTC +12) Wellington, Fidži, Kamčatka</string>
+		<string name="sel_tz_offset">Zvoľte odchýlku (zvyčajne 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Žiadna</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Použiť letný čas (DST)</string>
+		<string name="curr_tz">Aktuálne časové pásmo: %tw_time_zone%</string>
+		<string name="curr_tz_s">Aktuálne časové pásmo:</string>
+		<string name="set_tz_btn">Nastav časové pásmo</string>
+		<string name="settings_screen_hdr">Obrazovka</string>
+		<string name="settings_screen_timeout_hdr">Vypnutie obrazovky</string>
+		<string name="enable_timeout_chk">Povoliť vypnutie obrazovky</string>
+		<string name="screen_to_slider">Vypnutie obrazovky v sekundách:</string>
+		<string name="screen_to_slider_s">Vypnutie obrazovky v sekundách (0=zakázané): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Nastavenie vypnutia obrazovky je nedostupné</string>
+		<string name="screen_bright_slider">Jas: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Nastavenie jasu je nedostupné</string>
+		<string name="vibration_hdr">Vibrovať</string>
+		<string name="button_vibration_hdr">Vibrovať pri dotyku</string>
+		<string name="kb_vibration_hdr">Vibrovať pri písaní</string>
+		<string name="act_vibration_hdr">Vibrovať pri akcii</string>
+		<string name="button_vibration">Vibrovať pri dotyku:</string>
+		<string name="kb_vibration">Vibrovať pri písaní:</string>
+		<string name="act_vibration">Vibrovať pri akcii:</string>
+		<string name="select_language">Výber jazyka:</string>
+		<string name="sel_lang_btn">Výber jazyka</string>
+		<string name="set_language_btn">Nastaviť jazyk</string>
+		<string name="advanced_hdr">Rozšírené</string>
+		<string name="copy_log_confirm">Skopírovať log na SD kartu?</string>
+		<string name="copying_log">Kopírovanie logu na SD kartu...</string>
+		<string name="copy_log_complete">Kopírovanie logu dokončené</string>
+		<string name="part_sd_btn">Rozdeliť SD kartu</string>
+		<string name="part_sd_s_btn">SD karta</string>
+		<string name="file_manager_btn">Súborový manažér</string>
+		<string name="language_hdr">Jazyk</string>
+		<string name="terminal_btn">Terminál</string>
+		<string name="reload_theme_btn">Načítať tému</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Vložiť TWRP</string>
+		<string name="inject_twrp_confirm">Znova vložiť TWRP?</string>
+		<string name="injecting_twrp">Vkladanie TWRP...</string>
+		<string name="inject_twrp_complete">Vloženie TWRP dokončené</string>
+		<string name="swipe_to_confirm">Potiahnite pre potvrdenie</string>
+		<string name="part_sd_hdr">Rozdeliť SD kartu</string>
+		<string name="invalid_partsd_sel">Musíte zvoliť odpojiteľné zariadenie</string>
+		<string name="part_sd_lose">Prídete o všetky súbory na vloženej SD karte!</string>
+		<string name="part_sd_undo">Toto je nezvratná akcia!</string>
+		<string name="part_sd_ext_sz">Veľkosť EXT:</string>
+		<string name="part_sd_swap_sz">Veľkosť Swap:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Súborový systém:</string>
+		<string name="swipe_part_sd">Potiahnutím rozdelíte</string>
+		<string name="swipe_part_sd_s">Rozdelenie</string>
+		<string name="partitioning_sd">Delenie SD karty...</string>
+		<string name="partitioning_sd2">Môže to trvať niekoľko minút.</string>
+		<string name="part_sd_complete">Rozdelenie kompletné</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Obnoviť pôvodný boot.img</string>
+		<string name="dumlock_restore_confirm">Obnoviť pôvodný boot.img?</string>
+		<string name="dumlock_restoring">Obnovenie pôvodného boot.img...</string>
+		<string name="dumlock_restore_complete">Obnova pôvodného boot.img dokončená</string>
+		<string name="dumlock_reflash_btn">Prepísanie recovery</string>
+		<string name="dumlock_reflash_confirm">Prepísať recovery pri štarte?</string>
+		<string name="dumlock_reflashing">Prepísanie recovery pri štarte...</string>
+		<string name="dumlock_reflash_complete">Prepísanie recovery pri štarte dokončené</string>
+		<string name="dumlock_install_btn">Inštalovať HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Inštalovať súbory HTC Dumlock do ROM?</string>
+		<string name="dumlock_installing">Inštalácia HTC Dumlock...</string>
+		<string name="dumlock_install_complete">Inštalácia HTC Dumlock dokončená</string>
+		<string name="swipe_to_unlock">Potiahnutím odomknúť</string>
+		<string name="swipe_unlock">   Odomknúť</string>
+		<string name="fm_hdr">Súborový manažér</string>
+		<string name="fm_sel_file">Vyberte súbor alebo adresár</string>
+		<string name="fm_type_folder">Adresár</string>
+		<string name="fm_type_file">Súbor</string>
+		<string name="fm_choose_act">Zvoľte akciu</string>
+		<string name="fm_selected">Zvolené %tw_fm_type%:</string>
+		<string name="fm_copy_btn">Kopírovať</string>
+		<string name="fm_copy_file_btn">Kopírovať súbor</string>
+		<string name="fm_copy_folder_btn">Kopírovať adresár</string>
+		<string name="fm_copying">Kopírovanie</string>
+		<string name="fm_move_btn">Presunúť</string>
+		<string name="fm_moving">Presúvanie</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Vymazať</string>
+		<string name="fm_deleting">Vymazávanie</string>
+		<string name="fm_rename_btn">Premenovať</string>
+		<string name="fm_rename_file_btn">Premenovať súbor</string>
+		<string name="fm_rename_folder_btn">Premenovať adresár</string>
+		<string name="fm_renaming">Premenovávanie</string>
+		<string name="fm_sel_dest">Vyberte cieľový adresár</string>
+		<string name="fm_sel_curr_folder">Vyberte súčasný adresár</string>
+		<string name="fm_rename_hdr">Premenovať</string>
+		<string name="fm_set_perms_hdr">Nastaviť práva</string>
+		<string name="fm_perms">Práva:</string>
+		<string name="fm_complete">Operácia na súbore dokončená</string>
+		<string name="decrypt_data_hdr">Dešifrovať dáta</string>
+		<string name="decrypt_data_enter_pass">Zadajte heslo.</string>
+		<string name="decrypt_data_enter_pattern">Zadajte vzor.</string>
+		<string name="decrypt_data_trying">Pokus o dešifrovanie</string>
+		<string name="term_hdr">Príkaz terminálu</string>
+		<string name="term_s_hdr">Terminál</string>
+		<string name="term_kill_btn">ZABI</string>
+		<string name="term_sel_folder_hdr">Navigovať do počiatočného adresára</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Vymazať dalvik cache</string>
+		<string name="sideload_wipe_cache_chk">Vymazať cache</string>
+		<string name="swipe_to_sideload">Potiahnite pre spustenie sideload</string>
+		<string name="swipe_sideload">   Štart</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Použitie: adb sideload názov.zip</string>
+		<string name="sideload_complete">ADB Sideload dokončený</string>
+		<string name="reboot_hdr">Reštart</string>
+		<string name="install_cancel">Neinštalovať</string>
+		<string name="sel_storage_list">Vyberte úložisko</string>
+		<string name="ok_btn">OK</string>
+		<string name="no_kernel_selinux">Kernel nemá podporu pre čítanie obsahu SELinux.</string>
+		<string name="full_selinux">Plná podpora SELinux.</string>
+		<string name="no_selinux">Bez podpory SELinux (chýba libselinux).</string>
+		<string name="mtp_enabled">MTP povolené</string>
+		<string name="mtp_crash">MTP zlyhalo, MTP nebude spustené pri štarte.</string>
+		<string name="decrypt_success">Úspešne dešifrované s prednastaveným heslom.</string>
+		<string name="unable_to_decrypt">Nemožno dešifrovať s prednastaveným heslom. Možno budete musieť naformátovať partíciu Data.</string>
+		<string name="generating_digest1" version="2">Generuje sa Digest</string>
+		<string name="generating_digest2" version="2"> *Generuje sa Md5...</string>
+		<string name="digest_created" version="2"> *Digest vygenerované.</string>
+		<string name="digest_error" version="2"> * chyba Digest!</string>
+		<string name="digest_compute_error" version="2"> * chyba pri výpočte Digest.</string>
+		<string name="current_date">(Aktuálny dátum)</string>
+		<string name="auto_generate">(Generovať automaticky)</string>
+		<string name="unable_to_locate_partition">Nemožno nájsť partíciu \'{1}\' pre výpočet zálohy.</string>
+		<string name="no_partition_selected">Žiadne partície vybrané na zálohovanie.</string>
+		<string name="total_partitions_backup"> * Počet partícií označených na zálohovanie: {1}</string>
+		<string name="total_backup_size"> * Konečná veľkosť zálohy: {1}MB</string>
+		<string name="available_space"> * Voľné miesto na úložisku: {1}MB</string>
+		<string name="unable_locate_storage">Nie je možné nájsť uložisko.</string>
+		<string name="no_space">Nedostatok miesta na úložisku.</string>
+		<string name="backup_started">[ZÁLOHOVANIE ZAČALO]</string>
+		<string name="backup_folder"> * Adresár zálohy: {1}</string>
+		<string name="fail_backup_folder">Nie je možné vytvoriť adresár zálohy.</string>
+		<string name="avg_backup_fs">Priemerná miera zálohovania súborových systémov: {1} MB/sek</string>
+		<string name="avg_backup_img">Priemerná miera zálohovania diskov: {1} MB/sek</string>
+		<string name="total_backed_size">[{1} MB ZÁLOH SPOLU]</string>
+		<string name="backup_completed">[ZÁLOHA DOKONČENÁ ZA {1} SEKÚND]</string>
+		<string name="restore_started">[ZAČIATOK OBNOVY]</string>
+		<string name="restore_folder">Obnoviť adresár: \'{1}\'</string>
+		<string name="verifying_digest" version="2">Kontrolovanie Digest</string>
+		<string name="skip_digest" version="2">Vynechanie kontroly Digest na základe užívateľského nastavenia.</string>
+		<string name="calc_restore">Výpočet detailov obnovy...</string>
+		<string name="restore_read_only">Nie je možné obnoviť {1} -- pripojené len na čítanie.</string>
+		<string name="restore_unable_locate">Nie je možné nájsť partíciu \'{1}\' pre obnovu.</string>
+		<string name="no_part_restore">Žiadne partície neboli vybrané pre obnovu.</string>
+		<string name="restore_part_count">Obnova {1} partiícií...</string>
+		<string name="total_restore_size">Veľkosť obnovy je spolu {1}MB</string>
+		<string name="updating_system_details">Obnovovanie detailov systému</string>
+		<string name="restore_completed">[OBNOVA DOKONČENÁ ZA {1} SEKÚND]</string>
+		<string name="error_opening_strerr">Chyba pri otváraní: \'{1}\' ({2})</string>
+		<string name="unable_locate_part_backup_name">Nie je možné nájsť partíciu podľa názvu zálohy: \'{1}\'</string>
+		<string name="unable_find_part_path">Nie je možné nájsť partíciu na adrese \'{1}\'</string>
+		<string name="update_part_details">Obnova detailov partície...</string>
+		<string name="update_part_details_done">...hotovo</string>
+		<string name="wiping_dalvik">Vymazávanie adresárov dalvik cache...</string>
+		<string name="cleaned">Vyčistené: {1}...</string>
+		<string name="dalvik_done">-- Vyčistenie adresára Dalvik cache bolo dokončené!</string>
+		<string name="no_andsec">Žiadne zabezpečené android partície neboli nájdené.</string>
+		<string name="unable_to_locate">{1} nie je možné nájsť.</string>
+		<string name="wiping_datamedia">Vymazávanie interného úložiska -- /data/media...</string>
+		<string name="unable_to_mount">{1} nie je možné pripojiť</string>
+		<string name="unable_to_mount_internal">Nie je možné pripojiť interné úložisko</string>
+		<string name="unable_to_mount_storage">Nie je možné pripojiť úložisko</string>
+		<string name="fail_decrypt">Nie je možné dešifrovať data.</string>
+		<string name="no_crypto_support">Táto verzia bola skompilovaná bez podpory šifrovania.</string>
+		<string name="decrypt_success_dev">Dáta boli úspešne dešifrované, nový block device: \'{1}\'</string>
+		<string name="done">Hotovo.</string>
+		<string name="start_partition_sd">Delenie SD karty...</string>
+		<string name="partition_sd_locate">Nie je možné nájsť zariadenie na rozdelenie.</string>
+		<string name="ext_swap_size">Veľkosť EXT + Swap je väčšia ako veľkosť SD-karty.</string>
+		<string name="remove_part_table">Vymazávanie tabuľky rozdelenia...</string>
+		<string name="unable_rm_part">Nie je možné vymazať tabuľku rozdelenia.</string>
+		<string name="create_part">Vytváranie partície {1}...</string>
+		<string name="unable_to_create_part">Partíciu {1} nie je možné vytvoriť.</string>
+		<string name="format_sdext_as">Formátovanie sd-ext ako {1}...</string>
+		<string name="part_complete">Rozdelenie dokončené.</string>
+		<string name="unable_to_open">Nie je možné otvoriť \'{1}\'.</string>
+		<string name="mtp_already_enabled">MTP je už povolené</string>
+		<string name="mtp_fail">Nie je možné povoliť MTP</string>
+		<string name="no_mtp">Podpora MTP nie je zahrnutá</string>
+		<string name="image_flash_start">[ZÁPIS IMAGE SPUSTENÝ]</string>
+		<string name="img_to_flash">Obraz na zápis: \'{1}\'</string>
+		<string name="flash_unable_locate">Nie je možné nájsť partíciu \'{1}\' pre zápis.</string>
+		<string name="no_part_flash">Ná zápis neboli zvolené žiadne partície.</string>
+		<string name="too_many_flash">Na zápis je zvolených príliš veľa partícií.</string>
+		<string name="invalid_flash">Zvolená partícia nie je správna pre zápis.</string>
+		<string name="flash_done">[ZÁPIS IMAGE DOKONČENÝ]</string>
+		<string name="wiping">Mazanie {1}</string>
+		<string name="repair_not_exist">{1} neexistuje! Oprava nie je možná!</string>
+		<string name="repairing_using">Oprava {1} pomocou {2}...</string>
+		<string name="unable_repair">Nie je možné opraviť {1}.</string>
+		<string name="mount_data_footer">Nie je možné pripojiť /data a nájsť parametre šifrovania.</string>
+		<string name="create_folder_strerr">Adresár \'{1}\' nie je možné vytvoriť ({2}).</string>
+		<string name="fail_mount">Nie je možné pripojiť \'{1}\' ({2})</string>
+		<string name="fail_unmount">Nie je možné odpojiť \'{1}\' ({2})</string>
+		<string name="cannot_resize">Nie je možné zväčšiť {1}.</string>
+		<string name="repair_resize">Opravovanie {1} pred zväčšením.</string>
+		<string name="unable_resize">Nie je možné zväčšiť {1}.</string>
+		<string name="no_digest_found" version="2">Súbor Digest sa nenašiel pre \'{1}\'. Prosím zvoľte Vypnúť kontrolu MD5 počas obnovy.</string>
+		<string name="digest_fail_match" version="2">Digest nezodpovedá súboru \'{1}\'.</string>
+		<string name="digest_matched" version="2">Digest sa zhoduje pre '{1}'.</string>
+		<string name="fail_decrypt_tar">Nie je možné dešifrovať súbor tar \'{1}\'</string>
+		<string name="format_data_msg">Mali by ste reštartovať recovery aby ste mohli znova používať /data.</string>
+		<string name="format_data_err">Nie je možné formátovať pre zrušenie šifrovania.</string>
+		<string name="formatting_using">Formátovanie {1} použitím {2}...</string>
+		<string name="unable_to_wipe">Nie je možné vymazať {1}.</string>
+		<string name="cannot_wipe">Partíciu {1} nie je možné vymazať.</string>
+		<string name="remove_all">Vymazanie všetkých súborov v \'{1}\'</string>
+		<string name="wiping_data">Vymazanie Data bez vymazania zložky /data/media...</string>
+		<string name="backing_up">Zálohovanie {1}...</string>
+		<string name="backing">Zálohovanie</string>
+		<string name="backup_size">Veľkosť zálohy pre \'{1}\' je 0 bajtov.</string>
+		<string name="datamedia_fs_restore">UPOZORNENIE: Táto záloha /data bola vytvorená pomocou systému súborov {1}! Záloha nebude bootovať pokiaľ sa nezmení naspäť na {1}.</string>
+		<string name="restoring">Obnovenie {1}...</string>
+		<string name="restoring_hdr">Obnovenie</string>
+		<string name="recreate_folder_err">Nie je možné vytvoriť adresár {1}.</string>
+		<string name="img_size_err">Veľkosť image je väčšia ako cieľová partícia</string>
+		<string name="flashing">Zapisovanie {1}...</string>
+		<string name="backup_folder_set">Adresár záloh nastavený na: \'{1}\'</string>
+		<string name="locate_backup_err">Zálohu \'{1}\' nie je možné nájsť</string>
+		<string name="set_restore_opt">Nastavenie možností obnovy: \'{1}\':</string>
+		<string name="digest_check_skip" version="2">Vynechanie kontroly Digest zapnuté</string>
+		<string name="ors_encrypt_restore_err">Nie je možné použiť OpenRecoveryScript pre obnovu zašifrovanej zálohy.</string>
+		<string name="mounting">Pripájanie</string>
+		<string name="unmounting">Odpájanie</string>
+		<string name="mounted">\'{1}\' pripojený</string>
+		<string name="unmounted">\'{1}\' odpojený</string>
+		<string name="setting">Nastavenie \'{1}\' na \'{2}\'</string>
+		<string name="setting_empty">Nastavenie \'{1}\' na prázdne</string>
+		<string name="making_dir1">Vytváranie adresára</string>
+		<string name="making_dir2">Vytváranie adresára: \'{1}\'</string>
+		<string name="running_command">Spúšťanie príkazu</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">Spúšťanie funkcie ADB sideload...</string>
+		<string name="need_new_adb">Pre použitie sideload na toto zariadenie potrebujete ADB 1.0.32 alebo novšie.</string>
+		<string name="no_pwd">Heslo nebolo zadané.</string>
+		<string name="done_ors">Vykonávanie skriptu dokončené</string>
+		<string name="injecttwrp">Vkladanie TWRP do boot.img...</string>
+		<string name="zip_err">Chyba pri inštalácií zip súboru \'{1}\'</string>
+		<string name="installing_zip">Inštalácia zip súboru \'{1}\'</string>
+		<string name="select_backup_opt">Nastavenie možností zálohy:</string>
+		<string name="compression_on">Kompresia zapnutá</string>
+		<string name="digest_off" version="2">Generovanie Digest vypnuté</string>
+		<string name="backup_fail">Zálohovanie zlyhalo</string>
+		<string name="backup_clean">Zálohovanie zlyhalo. Čistenie adresára záloh.</string>
+		<string name="running_recovery_commands">Spúšťanie príkazov recovery</string>
+		<string name="recovery_commands_complete">Príkazy recovery dokončené</string>
+		<string name="running_ors">Spúšťanie OpenRecoveryScript</string>
+		<string name="ors_complete">OpenRecoveryScript dokončený</string>
+		<string name="no_updater_binary">Nie je možné nájsť \'{1}\' v súbore zip.</string>
+		<string name="check_for_digest" version="2">Kontrola Digest súboru...</string>
+		<string name="fail_sysmap">Nie je možné namapovať súbor \'{1}\'</string>
+		<string name="verify_zip_sig">Overovanie podpisu zip súboru...</string>
+		<string name="verify_zip_fail">Overenie podpisu zip súboru zlyhalo!</string>
+		<string name="verify_zip_done">Podpis súboru zip úspešne overený.</string>
+		<string name="zip_corrupt">Súbor zip je poškodený!</string>
+		<string name="no_digest" version="2">Vynechanie kontroly Digest: súbor s MD5 nenájdený</string>
+		<string name="digest_fail" version="2">Digest sa nezhoduje</string>
+		<string name="digest_match" version="2">Digest sa zhoduje</string>
+		<string name="pid_signal">Proces {1} skončil so signálom: {2}</string>
+		<string name="pid_error">Proces {1} skončil s CHYBOU: {2}</string>
+		<string name="install_dumlock">Inštalovanie HTC Dumlock do systému...</string>
+		<string name="dumlock_restore">Obnovenie pôvodného boot.img...</string>
+		<string name="dumlock_reflash">Zapisovanie recovery do boot.img...</string>
+		<string name="run_script">Beh skriptu {1}...</string>
+		<string name="rename_stock">Premenovaný originál recovery súbor v /system pre zabránenie prepísania TWRP originál ROM.</string>
+		<string name="split_backup">Rozdelenie zálohy do viacerých archívov...</string>
+		<string name="backup_error">Chyba pri vytváraní zálohy.</string>
+		<string name="restore_error">Chyba počas procesu obnovy.</string>
+		<string name="split_thread">Rozdelenie ID vlákna {1} do archívu {2}</string>
+		<string name="file_progress">%llu z %llu súborov, %i%%</string>
+		<string name="size_progress">%lluMB z %lluMB, %i%%</string>
+		<string name="decrypt_cmd">Pokus o dešifrovanie data partície cez príkazový riadok.</string>
+		<string name="base_pkg_err">Nie je možné načítať základné balíčky.</string>
+		<string name="simulating">Simulovanie akcií...</string>
+		<string name="backup_cancel">Zálohovanie prerušené</string>
+		<string name="config_twrp">Konfigurovanie TWRP...</string>
+		<string name="config_twrp_err">Nie je možné konfigurovať TWRP s týmto kernelom.</string>
+		<string name="copy_log">Recovery log skopírovaný do {1}.</string>
+		<string name="max_queue">Dosiahnutý maximálny počet zip v zozname!</string>
+		<string name="min_queue">Dosiahnutý minimálny počet zip v zozname!</string>
+		<string name="screenshot_saved">Screenshot bol uložený do {1}</string>
+		<string name="screenshot_err">Nie je možné vytvoriť screenshot!</string>
+		<string name="zip_wipe_cache">Jeden alebo viac zip súborov vyžiadalo vymazanie cache -- Vymazávanie cache.</string>
+		<string name="and_sec_wipe_err">Nie je možné vymazať android secure</string>
+		<string name="dalvik_wipe_err">Nie je možné vymazať dalvik</string>
+		<string name="auto_gen">(Generovať automaticky)</string>
+		<string name="curr_date">(Aktuálny dátum)</string>
+		<string name="backup_name_len">Názov zálohy je príliš dlhý.</string>
+		<string name="backup_name_invalid">Názov zálohy \'{1}\' obsahuje neplatný znak: \'{1}\'</string>
+		<string name="no_real_sdcard">Toto zariadenie nemá skutočnú SD kartu! Prerušujem!</string>
+		<string name="cancel_sideload">Rušenie ADB sideload...</string>
+		<string name="change_fs_err">Chyba pri zmene súborového systému.</string>
+		<string name="theme_ver_err">Vlastná téma sa nezhoduje s verziou TWRP. Používa sa originál téma.</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/sl.xml b/gui/theme/common/languages/sl.xml
new file mode 100644
index 0000000..ef0a692
--- /dev/null
+++ b/gui/theme/common/languages/sl.xml
@@ -0,0 +1,634 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<language>
+	<display>Slovenian</display>
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<string name="system">Sistem</string>
+		<string name="system_image">Sistemski odtis</string>
+		<string name="vendor">Prodajalec</string>
+		<string name="vendor_image">Odtis prodajalca</string>
+		<string name="boot">Zagon</string>
+		<string name="recovery">Obnovitev</string>
+		<string name="cache">Predpomnilnik</string>
+		<string name="data">Podatki</string>
+		<string name="sdcard">Kartica SD</string>
+		<string name="internal">Notranja pomnilniška naprava</string>
+		<string name="microsd">Kartica mikro SD</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Varni Android</string>
+		<string name="dalvik">Predpomnilnik Dalvik/ART</string>
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Prevzeti podatki</string>
+		<string name="adopted_storage">Prevzeta pomnilniška naprava</string>
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPE: %tw_cpu_temp% °C</string>
+		<string name="battery_pct">Baterija: %tw_battery%</string>
+		<string name="sort_by_name">Razvrsti po imenu</string>
+		<string name="sort_by_date">Razvrsti po datumu</string>
+		<string name="sort_by_size">Razvrsti po velikosti</string>
+		<string name="sort_by_name_only">Ime</string>
+		<string name="sort_by_date_only">Datum</string>
+		<string name="sort_by_size_only">Velikost</string>
+		<string name="tab_general">SPLOŠNO</string>
+		<string name="tab_options">MOŽNOSTI</string>
+		<string name="tab_backup">VAR. KOP.</string>
+		<string name="tab_time_zone">ČASOVNI PAS</string>
+		<string name="tab_screen">ZASLON</string>
+		<string name="tab_vibration">VIBRIRANJE</string>
+		<string name="tab_language">JEZIK</string>
+		<string name="install_btn">Namesti</string>
+		<string name="wipe_btn">Počisti</string>
+		<string name="backup_btn">Var. kopiraj</string>
+		<string name="restore_btn">Obnovi</string>
+		<string name="mount_btn">Priklopi</string>
+		<string name="settings_btn">Nastavitve</string>
+		<string name="advanced_btn">Napredno</string>
+		<string name="reboot_btn">Pon. zaženi</string>
+		<string name="files_btn">Datoteke</string>
+		<string name="copy_log_btn">Kopiraj dnevnik</string>
+		<string name="select_type_hdr">Izberite vrsto</string>
+		<string name="install_zip_hdr">Namesti ZIP</string>
+		<string name="install_zip_btn">Namesti ZIP</string>
+		<string name="install_image_hdr">Namesti odtis</string>
+		<string name="install_image_btn">Namesti odtis</string>
+		<string name="install_select_file_hdr">Izberite datoteko</string>
+		<string name="file_selector_folders_hdr">Mape</string>
+		<string name="select_file_from_storage">Izberite datoteko iz %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">Stransko nalaganje ADB</string>
+		<string name="install_hdr">Namesti</string>
+		<string name="select_storage_hdr">Izberite pomnilniško napravo</string>
+		<string name="select_storage_btn">Izberite pomnilniško napravo</string>
+		<string name="queue_hdr">Čakalna vrsta</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% od največ 10 datotek v čakalni vrsti</string>
+		<string name="zip_queue_count_s">Datoteka %tw_zip_queue_count% od 10:</string>
+		<string name="zip_warn1">To opravilo lahko namesti nezdružljivo</string>
+		<string name="zip_warn2">programsko opremo in naredi napravo neuporabno.</string>
+		<string name="zip_back_cancel">Pritisnite Nazaj za preklic dodajanja tega ZIP-a.</string>
+		<string name="zip_back_clear">Pritisnite Nazaj za čiščenje čakalne vrste.</string>
+		<string name="folder">Mapa:</string>
+		<string name="file">Datoteka:</string>
+		<string name="zip_sig_chk">Preverjanje podpisa ZIP</string>
+		<string name="inject_twrp_chk">Vstavi TWRP po namestitvi</string>
+		<string name="options_hdr">Možnosti</string>
+		<string name="confirm_flash_hdr">Potrdite namestitev</string>
+		<string name="zip_queue">Čakalna vrsta:</string>
+		<string name="options">Možnosti:</string>
+		<string name="swipe_confirm">   Potrdi</string>
+		<string name="zip_add_btn">Dodaj več ZIP-ov</string>
+		<string name="zip_clear_btn">Počisti čakalno vrsto ZIP-ov</string>
+		<string name="install_zip_count_hdr">Namesti ZIP %tw_zip_index% od %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Nameščanje ZIP-a: %tw_file%</string>
+		<string name="failed">Spodletelo</string>
+		<string name="successful">Uspelo</string>
+		<string name="install_failed">Namestitev je spodletela</string>
+		<string name="install_successful">Namestitev je uspela</string>
+		<string name="wipe_cache_dalvik_btn">Počisti predpomnilnik/Dalvik</string>
+		<string name="reboot_system_btn">Ponovno zaženi sistem</string>
+		<string name="install_sel_target">Izberite ciljni razdelek</string>
+		<string name="flash_image_select">Izberite razdelek za namestitev odtisa:</string>
+		<string name="target_partition">Ciljni razdelek:</string>
+		<string name="flashing_image">Nameščanje odtisa …</string>
+		<string name="image_flashed">Odtis nameščen</string>
+		<string name="wipe_cache_dalvik_confirm">Počisti predpomnilnik in Dalvik?</string>
+		<string name="wiping_cache_dalvik">Čiščenje predpomnilnika in Dalvika …</string>
+		<string name="wipe_cache_dalvik_complete">Čiščenje predpomnilnika in Dalvika končano</string>
+		<string name="swipe_wipe">Povlecite za čiščenje</string>
+		<string name="swipe_wipe_s">   Počisti</string>
+		<string name="no_os1">OS-a ni nameščenega! Ali res</string>
+		<string name="no_osrb">želite ponovno zagnati?</string>
+		<string name="no_ospo">želite izklopiti?</string>
+		<string name="rebooting">Ponovno zaganjanje …</string>
+		<string name="swipe_reboot">Povlecite za ponovni zagon</string>
+		<string name="swipe_reboot_s">   Ponovno zaženi</string>
+		<string name="swipe_flash">Povlecite za potrditev namestive</string>
+		<string name="confirm_action">Potrdite dejanje</string>
+		<string name="back_cancel">Pritisnite Nazaj za preklic.</string>
+		<string name="cancel_btn">Prekliči</string>
+		<string name="wipe_hdr">Počisti</string>
+		<string name="factory_reset_hdr">Ponastavitev na tov. vred.</string>
+		<string name="factory_reset_btn">Ponastavitev na tov. vred.</string>
+		<string name="factory_reset1">Počisti podatke, predpomnilnik in Dalvik</string>
+		<string name="factory_reset2">(brez notranje pomnilniške naprave)</string>
+		<string name="factory_reset3">Večinoma je to</string>
+		<string name="factory_reset4">edino čiščenje, ki ga potrebujete.</string>
+		<string name="factory_resetting">Ponastavitev na tov. vred. …</string>
+		<string name="advanced_wipe_hdr">Napredno čiščenje</string>
+		<string name="advanced_wipe_btn">Napredno čiščenje</string>
+		<string name="wipe_enc_confirm">Počisti šifriranje podatkov?</string>
+		<string name="formatting_data">Formatiranje podatkov …</string>
+		<string name="swipe_format_data">Povlecite za formatiranje podatkov</string>
+		<string name="swipe_format_data_s">   Formatiraj podatke</string>
+		<string name="factory_reset_complete">Ponastavitev na tovarniške vrednosti končana</string>
+		<string name="sel_part_hdr">Izberite razdelke</string>
+		<string name="wipe_sel_confirm">Počisti izbrane razdelke?</string>
+		<string name="wiping_part">Čiščenje razdelkov …</string>
+		<string name="wipe_complete">Čiščenje končano</string>
+		<string name="sel_part_wipe">Izberite razdelke za čiščenje:</string>
+		<string name="invalid_part_sel">Neveljavna izbira razdelkov</string>
+		<string name="format_data_hdr">Formatiraj podatke</string>
+		<string name="format_data_btn">Formatiraj podatke</string>
+		<string name="format_data_ptr1">Formatiranje podatkov bo počistilo vse programe,</string>
+		<string name="format_data_ptr2">varnostne kopije, slike, videoposnetke, predstavnost in</string>
+		<string name="format_data_ptr3">odstranilo šifriranje na notranji pomnilniški napravi.</string>
+		<string name="format_data_adopted">Vključno s prevzeto pomnilniško napravo</string>
+		<string name="format_data_lcp1">Formatiranje podatkov bo počistilo vse vaše programe, varnostne kopije, slike, videoposnetke, predstavnost in</string>
+		<string name="format_data_lcp2">odstranilo šifriranje na notranji pomnilniški napravi.</string>
+		<string name="format_data_wtc1">Formatiranje podatkov bo počistilo vse programe,</string>
+		<string name="format_data_wtc2">varnostne kopije in predstavnost.  Tega ni mogoče razveljaviti.</string>
+		<string name="format_data_undo">Tega ni mogoče razveljaviti.</string>
+		<string name="format_data_complete">Formatiranje podatkov končano</string>
+		<string name="yes_continue">Vtipkajte \'yes\' za nadaljevanje.  Pritisnite Nazaj za preklic.</string>
+		<string name="part_opt_hdr">Možnosti razdelkov za: %tw_partition_name%</string>
+		<string name="sel_act_hdr">Izberite dejanje</string>
+		<string name="file_sys_opt">Možnosti datotečnega sistema</string>
+		<string name="partition">Razdelek: %tw_partition_name%</string>
+		<string name="part_mount_point">Priklopna točka: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Datotečni sistem: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Prisoten: da</string>
+		<string name="part_present_no">Prisoten: ne</string>
+		<string name="part_removable_yes">Odstranljiv: da</string>
+		<string name="part_removable_no">Odstranljiv: ne</string>
+		<string name="part_size">Velikost: %tw_partition_size% MB</string>
+		<string name="part_used">Uporabljeno: %tw_partition_used% MB</string>
+		<string name="part_free">Nezasedeno: %tw_partition_free% MB</string>
+		<string name="part_backup_size">Velikost varnostne kopije: %tw_partition_backup_size% MB</string>
+		<string name="resize_btn">Spremeni velikost dat. sistema</string>
+		<string name="resize_btn_s">Spr. velik.</string>
+		<string name="resize_confirm">Spremeni velikost %tw_partition_name%?</string>
+		<string name="resizing">Spreminjanje velikosti …</string>
+		<string name="resize_complete">Spreminjanje velikosti končano</string>
+		<string name="swipe_resize">Povlecite za spr. velikosti</string>
+		<string name="swipe_resize_s">   Spr. velikost</string>
+		<string name="repair_btn">Popravi datotečni sistem</string>
+		<string name="repair_btn_s">Popravi</string>
+		<string name="repair_confirm">Popravi %tw_partition_name%?</string>
+		<string name="repairing">Popravljanje …</string>
+		<string name="repair_complete">Popravljanje končano</string>
+		<string name="swipe_repair">Povlecite za popravilo</string>
+		<string name="swipe_repair_s">   Popravi</string>
+		<string name="change_fs_btn">Spremeni datotečni sistem</string>
+		<string name="change_fs_btn_s">Spremeni</string>
+		<string name="change_fs_for_hdr">Spremeni datotečni sistem za: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Razdelek: %tw_partition_name% &gt; izberite datotečni sistem</string>
+		<string name="change_fs_warn1">Nekateri ROM-i ali jedra ne podpirajo vseh</string>
+		<string name="change_fs_warn2">datotečnih sistemov.  Nadaljujte previdno!</string>
+		<string name="change_fs_confirm">Spremeni %tw_partition_name%?</string>
+		<string name="formatting">Formatiranje …</string>
+		<string name="format_complete">Formatiranje končano</string>
+		<string name="swipe_change_fs">Povlecite za spremembo</string>
+		<string name="swipe_change_s">   Spremeni</string>
+		<string name="back_btn">Nazaj</string>
+		<string name="wipe_enc_btn">Počisti šifriranje</string>
+		<string name="swipe_factory_reset">Povlecite za ponastavitev na tov. vrednosti</string>
+		<string name="repair_change_btn">Popravi ali Spremeni datotečni sistem</string>
+		<string name="storage_hdr">Pomnilniška naprava: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Var. kopiraj</string>
+		<string name="backup_confirm_hdr">Potrdite varnostno kopiranje</string>
+		<string name="encryption_tab">ŠIFRIRANJE</string>
+		<string name="encryption">Šifriranje:</string>
+		<string name="name">Ime:</string>
+		<string name="sel_part_backup">Izberite razdelke za varnostno kopiranje:</string>
+		<string name="storage">Pom. naprava:</string>
+		<string name="enc_disabled">onemogočeno - nastavite geslo za omogočanje</string>
+		<string name="enc_enabled">omogočeno</string>
+		<string name="enable_backup_comp_chk">Omogoči stiskanje</string>
+		<string name="skip_digest_backup_chk" version="2">Preskoči ustvarjanje razpršila Digest med varnostnim kopiranjem</string>
+		<string name="disable_backup_space_chk">Onemogoči preverjanje nezasedenega prostora</string>
+		<string name="refresh_sizes_btn">Osveži velikosti</string>
+		<string name="swipe_backup">Povlecite za var. kopiranje</string>
+		<string name="append_date_btn">Pripni datum</string>
+		<string name="backup_name_exists">Varnostna kopija z enakim imenom že obstaja!</string>
+		<string name="encrypt_backup">Šifriraj varnostno kopijo?</string>
+		<string name="enter_pass">Vnesite geslo:</string>
+		<string name="enter_pass2">Ponovno vnesite geslo:</string>
+		<string name="pass_not_match">Gesli se ne ujemata!</string>
+		<string name="partitions">Razdelki:</string>
+		<string name="disabled">Onemogočeno</string>
+		<string name="enabled">Omogočeno</string>
+		<string name="backup_name_hdr">Nastavite ime varnostne kopije</string>
+		<string name="progress">Napredek:</string>
+		<string name="backup_complete">Varnostno kopiranje končano</string>
+		<string name="restore_hdr">Obnovi</string>
+		<string name="sel_backup_hdr">Izberite varnostno kopijo</string>
+		<string name="restore_sel_store_hdr">Izberite varnostno kopijo iz %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Izberite paket za obnovitev:</string>
+		<string name="restore_enc_backup_hdr">Šifrirana varnostna kopija</string>
+		<string name="restore_dec_fail">Geslo je spodletelo, poizkusite znova!</string>
+		<string name="del_backup_btn">Izbriši varnostno kopijo</string>
+		<string name="del_backup_confirm">Izbriši varnostno kopijo?</string>
+		<string name="del_backup_confirm2">Tega ni mogoče razveljaviti!</string>
+		<string name="deleting_backup">Brisanje varnostne kopije …</string>
+		<string name="backup_deleted">Brisanje varnostne kopije končano</string>
+		<string name="swipe_delete">Povlecite za izbris</string>
+		<string name="swipe_delete_s">   Izbriši</string>
+		<string name="restore_try_decrypt">Šifrirana varnostna kopija - poizkušanje odšifriranja</string>
+		<string name="restore_try_decrypt_s">Poizkušanje odšifriranja</string>
+		<string name="restore_backup_date">Varnostna kopija ustvarjena %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Izberite razdelke za obnovitev:</string>
+		<string name="restore_enable_digest_chk" version="2">Omogoči preverjanje razpršila Digest varnostnih kopij</string>
+		<string name="restore_complete">Obnavljanje končano</string>
+		<string name="swipe_restore">Povlecite za obnovitev</string>
+		<string name="swipe_restore_s">   Obnovi</string>
+		<string name="rename_backup_hdr">Preimenuj varnostno kopijo</string>
+		<string name="rename_backup_confirm">Preimenuj varnostno kopijo?</string>
+		<string name="rename_backup_confirm2">Tega ni mogoče razveljaviti!</string>
+		<string name="renaming_backup">Preimenovanje varnostne kopije …</string>
+		<string name="rename_backup_complete">Preimenovanje varnostne kopije končano</string>
+		<string name="swipe_to_rename">Povlecite za preimeovanje</string>
+		<string name="swipe_rename">   Preimenuj</string>
+		<string name="confirm_hdr">Potrdi</string>
+		<string name="mount_hdr">Priklopi</string>
+		<string name="mount_sel_part">Izberite razdelke za priklop:</string>
+		<string name="mount_sys_ro_chk">Priklopi sistemski razdelek samo za branje</string>
+		<string name="mount_sys_ro_s_chk">Priklopi sistem samo za branje</string>
+		<string name="decrypt_data_btn">Odšifriraj podatke</string>
+		<string name="disable_mtp_btn">Onemogoči MTP</string>
+		<string name="enable_mtp_btn">Omogoči MTP</string>
+		<string name="mount_usb_storage_btn">Priklopi pomnilniško napravo USB</string>
+		<string name="usb_storage_hdr">Pom. naprava USB</string>
+		<string name="usb_stor_mnt1">Pomnilniška naprava USB priklopljena</string>
+		<string name="usb_stor_mnt2">Prepričajte se, da varno odstranite napravo</string>
+		<string name="usb_stor_mnt3">iz računalnika pred odklopom!</string>
+		<string name="unmount_btn">Odklopi</string>
+		<string name="rb_system_btn">Sistem</string>
+		<string name="rb_poweroff_btn">Izklopi</string>
+		<string name="rb_recovery_btn">Obnovitev</string>
+		<string name="rb_bootloader_btn">Zagonski nalagalnik</string>
+		<string name="rb_download_btn">Prejmi</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Izklapljanje …</string>
+		<string name="swipe_power_off">Povlecite za izklop</string>
+		<string name="swipe_power_off_s">Izklopi</string>
+		<string name="sys_ro_hdr">Nespremenjeni sistemski razdelek</string>
+		<string name="sys_ro_keep">Ohrani sistem samo za branje?</string>
+		<string name="sys_rop1">TWRP lahko pusti sistemski razdelek nespremenjen</string>
+		<string name="sys_rop2">za lažje uradno posodabljanje.</string>
+		<string name="sys_rop3">TWRP tovarniškemu ROM-u ne bo mogel preprečiti</string>
+		<string name="sys_rop4">nadomestitev TWRP-ja in ne bo ponudil skrbniškega dostopa do naprave.</string>
+		<string name="sys_rop5">Nameščenje ZIP-ov ali izvajanje opravil ADB lahko še vedno</string>
+		<string name="sys_rop6">spremeni sistemski razdelek.</string>
+		<string name="sys_rol1">TWRP lahko pusti sistemski razdelek nespremenjen za lažje uradno posodabljanje.</string>
+		<string name="sys_rol2">TWRP tovarniškemu ROM-u ne bo mogel preprečiti nadomestitev TWRP-ja in ne bo ponudil skrbniškega dostopa do naprave.</string>
+		<string name="sys_rol3">Nameščenje ZIP-ov ali izvajanje opravil ADB lahko še vedno spremeni sistemski razdelek.</string>
+		<string name="sys_ro_never_show_chk">Nikoli več ne prikaži tega zaslona med zagonom</string>
+		<string name="sys_ro_keep_ro_btn">Ohrani samo za branje</string>
+		<string name="swipe_allow_mod">Povlecite, da dovolite spremembe</string>
+		<string name="swipe_allow_mod_s">Dovoli spremembe</string>
+		<string name="settings_hdr">Nastavitve</string>
+		<string name="settings_gen_hdr">Splošno</string>
+		<string name="settings_gen_s_hdr">Splošno</string>
+		<string name="settings_gen_btn">Splošno</string>
+		<string name="use_rmrf_chk">Namesto formatiranja uporabite rm -rf</string>
+		<string name="use24clock_chk">Uporabi 24-urno obliko</string>
+		<string name="rev_navbar_chk">Obratna razporeditev vrstice za krmarjenje</string>
+		<string name="simact_chk">Simuliraj dejanja za preizkušanje tem</string>
+		<string name="simfail_chk">Simuliraj spodletela dejanja</string>
+		<string name="ctr_navbar_rdo">Gumbi vrstice za krmarjenje na sredini</string>
+		<string name="lft_navbar_rdo">Gumbi vrstice za krmarjenje na levi</string>
+		<string name="rht_navbar_rdo">Gumbi vrstice za krmarjenje na desni</string>
+		<string name="restore_defaults_btn">Obnovi privzete</string>
+		<string name="settings_tz_btn">Časovni pas</string>
+		<string name="settings_screen_btn">Zaslon</string>
+		<string name="settings_screen_bright_btn">Svetlost zaslona</string>
+		<string name="settings_vibration_btn">Vibriranje</string>
+		<string name="settings_language_btn">Jezik</string>
+		<string name="time_zone_hdr">Časovni pas</string>
+		<string name="sel_tz_list">Izberite časovni pas:</string>
+		<string name="utcm11">(UTC -11) Samoa, Otoki Midway</string>
+		<string name="utcm10">(UTC -10) Havaji</string>
+		<string name="utcm9">(UTC -9) Aljaska</string>
+		<string name="utcm8">(UTC -8) Tihooceanski čas</string>
+		<string name="utcm7">(UTC -7) Ameriški gorski čas</string>
+		<string name="utcm6">(UTC -6) Ameriški osrednji čas</string>
+		<string name="utcm5">(UTC -5) Ameriški vzhodni čas</string>
+		<string name="utcm4">(UTC -4) Atlantski čas</string>
+		<string name="utcm3">(UTC -3) Brazilija, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Srednji Atlantik</string>
+		<string name="utcm1">(UTC -1) Azori, Zelenortski otoki</string>
+		<string name="utc0">(UTC  0) London, Dublin, Lisbona</string>
+		<string name="utcp1">(UTC +1) Berlin, Bruselj, Pariz</string>
+		<string name="utcp2">(UTC +2) Atene, Istanbul, Južna Afrika</string>
+		<string name="utcp3">(UTC +3) Moskva, Bagdad</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC +5) Ekaterinburg, Islamabad</string>
+		<string name="utcp6">(UTC +6) Almaty, Dhaka, Colombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Džakarta</string>
+		<string name="utcp8">(UTC +8) Peking, Singapur, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokio, Seul, Jakutsk</string>
+		<string name="utcp10">(UTC +10) Vzhodna Avstralija, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Salomonovi otoki</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fidži</string>
+		<string name="sel_tz_offset">Izberite zamik (običajno 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Brez</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Uporabi poletni čas (DST)</string>
+		<string name="curr_tz">Trenutni časovni pas: %tw_time_zone%</string>
+		<string name="curr_tz_s">Trenutni časovni pas:</string>
+		<string name="set_tz_btn">Nastavi časovni pas</string>
+		<string name="settings_screen_hdr">Zaslon</string>
+		<string name="settings_screen_timeout_hdr">Časovna omejitev zaslona</string>
+		<string name="enable_timeout_chk">Omogoči časovno omejitev zaslona</string>
+		<string name="screen_to_slider">Časovna omejitev zaslona v sekundah:</string>
+		<string name="screen_to_slider_s">Časovna omejitev zaslona v sekundah: (0 = onemogočeno): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Nastavitev časovne omejitve zaslona ni na voljo</string>
+		<string name="screen_bright_slider">Svetlost: %tw_brightness_pct% %</string>
+		<string name="screen_bright_na">Nastavitev svetlosti ni na voljo</string>
+		<string name="vibration_hdr">Vibriranje</string>
+		<string name="button_vibration_hdr">Vibriranje gumbov</string>
+		<string name="kb_vibration_hdr">Vibriranje tipkovnice</string>
+		<string name="act_vibration_hdr">Vibriranje dejanj</string>
+		<string name="button_vibration">Vibriranje gumbov:</string>
+		<string name="kb_vibration">Vibriranje tipkovnice:</string>
+		<string name="act_vibration">Vibriranje dejanj:</string>
+		<string name="select_language">Izberite jezik:</string>
+		<string name="sel_lang_btn">Izberite jezik</string>
+		<string name="set_language_btn">Nastavi jezik</string>
+		<string name="advanced_hdr">Napredno</string>
+		<string name="copy_log_confirm">Kopiraj dnevnik na kartico SD?</string>
+		<string name="copying_log">Kopiranje dnevnika na kartico SD …</string>
+		<string name="copy_log_complete">Kopiranje dnevnika končano</string>
+		<string name="part_sd_btn">Razdeli kartico SD</string>
+		<string name="part_sd_s_btn">Kartica SD</string>
+		<string name="file_manager_btn">Upravljalnik datotek</string>
+		<string name="language_hdr">Jezik</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="reload_theme_btn">Ponovno naloži temo</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Vstavi TWRP</string>
+		<string name="inject_twrp_confirm">Ponovno vstavi TWRP?</string>
+		<string name="injecting_twrp">Ponovno vstavljanje TWRP-ja …</string>
+		<string name="inject_twrp_complete">Vstavljanje TWRP-ja končano</string>
+		<string name="swipe_to_confirm">Povlecite za potrditev</string>
+		<string name="part_sd_hdr">Razdeli kartico SD</string>
+		<string name="invalid_partsd_sel">Izbrati morate odstranljivo napravo</string>
+		<string name="part_sd_lose">Izgubili boste vse datoteke na kartici SD!</string>
+		<string name="part_sd_undo">Tega dejanja ni mogoče razveljaviti!</string>
+		<string name="part_sd_ext_sz">Velikost EXT:</string>
+		<string name="part_sd_swap_sz">Izmen. velikost:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Datotečni sistem:</string>
+		<string name="swipe_part_sd">Povlecite za razdelitev</string>
+		<string name="swipe_part_sd_s">Razdelek</string>
+		<string name="partitioning_sd">Razdeljevanje kartice SD …</string>
+		<string name="partitioning_sd2">To bo trajalo nekaj minut.</string>
+		<string name="part_sd_complete">Razdeljevanje končano</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Obnovi izvirni zagon</string>
+		<string name="dumlock_restore_confirm">Obnovi izvirni zagonski odtis?</string>
+		<string name="dumlock_restoring">Obnavljanje izvirnega zagona …</string>
+		<string name="dumlock_restore_complete">Obnavljanje izvirnega zagona končano</string>
+		<string name="dumlock_reflash_btn">Ponovno namesti obnovitev</string>
+		<string name="dumlock_reflash_confirm">Ponovno namesti obnovitev v zagon?</string>
+		<string name="dumlock_reflashing">Nameščanje obnovitve v zagon …</string>
+		<string name="dumlock_reflash_complete">Namestite v obnovitve v zagon končana</string>
+		<string name="dumlock_install_btn">Namesti HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Namesti datoteke HTC Dumlock v ROM?</string>
+		<string name="dumlock_installing">Nameščanje HTC Dumlocka …</string>
+		<string name="dumlock_install_complete">Nameščanje HTC Dumlocka končano</string>
+		<string name="swipe_to_unlock">Povlecite za odklepanje</string>
+		<string name="swipe_unlock">   Odkleni</string>
+		<string name="fm_hdr">Upravljalnik datotek</string>
+		<string name="fm_sel_file">Izberite datoteko ali mapo</string>
+		<string name="fm_type_folder">Mapa</string>
+		<string name="fm_type_file">Datoteka</string>
+		<string name="fm_choose_act">Izberite dejanje</string>
+		<string name="fm_selected">Izbrano %tw_fm_type%:</string>
+		<string name="fm_copy_btn">Kopiraj</string>
+		<string name="fm_copy_file_btn">Kopiraj datoteko</string>
+		<string name="fm_copy_folder_btn">Kopiraj mapo</string>
+		<string name="fm_copying">Kopiranje …</string>
+		<string name="fm_move_btn">Premakni</string>
+		<string name="fm_moving">Premikanje</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Izbriši</string>
+		<string name="fm_deleting">Brisanje …</string>
+		<string name="fm_rename_btn">Preimenuj</string>
+		<string name="fm_rename_file_btn">Preimenuj datoteko</string>
+		<string name="fm_rename_folder_btn">Preimenuj mapo</string>
+		<string name="fm_renaming">Preimenovanje</string>
+		<string name="fm_sel_dest">Izberite ciljno mapo</string>
+		<string name="fm_sel_curr_folder">Izberite trenutno mapo</string>
+		<string name="fm_rename_hdr">Preimenuj</string>
+		<string name="fm_set_perms_hdr">Nastavi dovoljenja</string>
+		<string name="fm_perms">Dovoljenja:</string>
+		<string name="fm_complete">Opravilo na datotekah končano</string>
+		<string name="decrypt_data_hdr">Odšifriraj podatke</string>
+		<string name="decrypt_data_enter_pass">Vnesite geslo.</string>
+		<string name="decrypt_data_enter_pattern">Vnesite vzorec.</string>
+		<string name="decrypt_data_trying">Poizkušanje odšifriranja</string>
+		<string name="term_hdr">Ukaz za terminal</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">UNIČI</string>
+		<string name="term_sel_folder_hdr">Brskajte do začetne mape</string>
+		<string name="adb_sideload_hdr">Stransko nalaganje ADB</string>
+		<string name="sideload_wipe_dalvik_chk">Počisti predpomnilnik Dalvik</string>
+		<string name="sideload_wipe_cache_chk">Počisti predpom.</string>
+		<string name="swipe_to_sideload">Povlecite za začetek stranskega nalaganja</string>
+		<string name="swipe_sideload">   Začni</string>
+		<string name="sideload_confirm">Stransko nalaganje ADB</string>
+		<string name="sideload_usage">Uporaba: adb sideload filename.zip</string>
+		<string name="sideload_complete">Stransko nalaganje ADB končano</string>
+		<string name="reboot_hdr">Pon. zaženi</string>
+		<string name="install_cancel">Ne namesti</string>
+		<string name="sel_storage_list">Izberite pomnilniško napravo</string>
+		<string name="ok_btn">V redu</string>
+		<string name="no_kernel_selinux">Jedro nima podpore za branje vsebin SELinux.</string>
+		<string name="full_selinux">Prisotna je polna podpora za SELinux.</string>
+		<string name="no_selinux">Ni podpore za SELinux (libselinux manjka).</string>
+		<string name="mtp_enabled">MTP omogočen</string>
+		<string name="mtp_crash">MTP se je sesul, MTP se ob zagonu ne bo začel.</string>
+		<string name="decrypt_success">Uspešno odšifrirano s privzetim geslom.</string>
+		<string name="unable_to_decrypt">Odšifriranje s privzetim geslom ni mogoče.  Morda boste moral sformatirati podatke.</string>
+		<string name="generating_digest1" version="2">Ustvarjanje razpršila Digest</string>
+		<string name="generating_digest2" version="2"> * Ustvarjanje razpršila Digest …</string>
+		<string name="digest_created" version="2"> * Razpršilo Digest ustvarjeno.</string>
+		<string name="digest_error" version="2"> * Napaka razpršila Digest!</string>
+		<string name="digest_compute_error" version="2"> * Napaka pri računanju razpršila Digest.</string>
+		<string name="current_date">(trenutni datum)</string>
+		<string name="auto_generate">(samodejno ustvari)</string>
+		<string name="unable_to_locate_partition">Razdelka \'{1}\' za računanje varnostnih kopij ni mogoče najti.</string>
+		<string name="no_partition_selected">Ni izbranih razdelkov za varnostno kopiranje.</string>
+		<string name="total_partitions_backup"> * Skupno število razdelkov za varnostno kopiranje: {1}</string>
+		<string name="total_backup_size"> * Skupna velikost vseh podatkov: {1} MB</string>
+		<string name="available_space"> * Nezaseden prostor: {1} MB</string>
+		<string name="unable_locate_storage">Pomnilniške naprave ni mogoče najti.</string>
+		<string name="no_space">Ni dovolj prostora na pomnilniški napravi.</string>
+		<string name="backup_started">[VAR. KOPIRANJE SE JE ZAČELO]</string>
+		<string name="backup_folder"> * Mapa varnostnega kopiranja: {1}</string>
+		<string name="fail_backup_folder">Ustvarjanje mape varnostnega kopiranja je spodletelo.</string>
+		<string name="avg_backup_fs">Povprečna hitrost varnostnega kopiranja za datotečne sisteme: {1} MB/s</string>
+		<string name="avg_backup_img">Povprečna hitrost varnostnega kopiranja za odtise pogonov: {1} MB/s</string>
+		<string name="total_backed_size">[VARNOSTNO KOPIRANO SKUPAJ {1} MB]</string>
+		<string name="backup_completed">[VARNOSTNO KOPIRANJE KONČANO V {1} S]</string>
+		<string name="restore_started">[OBNOVITEV SE JE ZAČELA]</string>
+		<string name="restore_folder">Mapa obnovitve: \'{1}\'</string>
+		<string name="verifying_digest" version="2">Preverjanje razpršila Digest</string>
+		<string name="skip_digest" version="2">Preskakovanje preverjanja razpršila Digest glede na uporabniško nastavitev.</string>
+		<string name="calc_restore">Računanje podrobnosti obnovitve …</string>
+		<string name="restore_read_only">{1} ni mogoče obnoviti -- priklopljen je samo za branje.</string>
+		<string name="restore_unable_locate">Razdelka \'{1}\' za obnovitev ni mogoče najti.</string>
+		<string name="no_part_restore">Ni izbranih razdelkov za obnovitev.</string>
+		<string name="restore_part_count">Obnavljanje {1} razdelkov …</string>
+		<string name="total_restore_size">Skupna velikost obnovitve je {1} MB</string>
+		<string name="updating_system_details">Posodabljanje sistemskih podrobnosti</string>
+		<string name="restore_completed">[OBNOVITEV KONČANA V {1} S]</string>
+		<string name="error_opening_strerr">Napaka odpiranja: \'{1}\' ({2})</string>
+		<string name="unable_locate_part_backup_name">Razdelka po imenu varnostne kopije ni mogoče najti: \'{1}\'</string>
+		<string name="unable_find_part_path">Razdelka za pot \'{1}\' ni mogoče najti</string>
+		<string name="update_part_details">Posodabljanje podrobnosti razdelka …</string>
+		<string name="update_part_details_done">… končano</string>
+		<string name="wiping_dalvik">Čiščenje map predpomnilnika Dalvik …</string>
+		<string name="cleaned">Počiščeno: {1} …</string>
+		<string name="dalvik_done">-- Čiščenje map predpomnilnika Dalvik končano!</string>
+		<string name="no_andsec">Varnih razdelkov Android ni bilo najdenih.</string>
+		<string name="unable_to_locate">{1} ni mogoče najti.</string>
+		<string name="wiping_datamedia">Čiščenje notranje pomnilniške naprave -- /data/media …</string>
+		<string name="unable_to_mount">{1} ni mogoče priklopiti.</string>
+		<string name="unable_to_mount_internal">Notranje pomnilniške naprave ni mogoče priklopiti</string>
+		<string name="unable_to_mount_storage">Pomnilniške naprave ni mogoče priklopiti</string>
+		<string name="fail_decrypt">Odšifriranje podatkov je spodletelo.</string>
+		<string name="no_crypto_support">Podpora za šifriranje ni bila vgrajena v to izgradnjo.</string>
+		<string name="decrypt_success_dev">Podatki so bili uspešno odšifrirani, nova bločna naprava: \'{1}\'</string>
+		<string name="done">Končano.</string>
+		<string name="start_partition_sd">Razdeljevanje kartice SD …</string>
+		<string name="partition_sd_locate">Naprave za razdeljevanje ni mogoče najti.</string>
+		<string name="ext_swap_size">EXT + izmenjevalna velikost sta večja od velikosti kartice SD.</string>
+		<string name="remove_part_table">Odstranjevanje preglednice razdelka …</string>
+		<string name="unable_rm_part">Preglednice razdelka ni mogoče odstraniti.</string>
+		<string name="create_part">Ustvarjanje razdelka {1} …</string>
+		<string name="unable_to_create_part">Razdelka {1} ni mogoče ustvariti.</string>
+		<string name="format_sdext_as">Formatiranje SD-EXT kot {1} …</string>
+		<string name="part_complete">Razdeljevanje končano.</string>
+		<string name="unable_to_open">\'{1}\' ni mogoče odpreti.</string>
+		<string name="mtp_already_enabled">MTP je že omogočen</string>
+		<string name="mtp_fail">Omogočanje MTP-ja je spodletelo</string>
+		<string name="no_mtp">Podpora za MTP ni vključena</string>
+		<string name="image_flash_start">[NAMESTITEV ODTISA SE JE ZAČELA]</string>
+		<string name="img_to_flash">Odtis za namestitev: \'{1}\'</string>
+		<string name="flash_unable_locate">Razdelka \'{1}\' za namestitev ni mogoče najti.</string>
+		<string name="no_part_flash">Ni izbranih razdelkov za namestitev.</string>
+		<string name="too_many_flash">Izbranih preveč razdelkov za namestitev.</string>
+		<string name="invalid_flash">Naveden neveljaven razdelek za namestitev.</string>
+		<string name="flash_done">[NAMESTITEV ODTISA KONČANA]</string>
+		<string name="wiping">Čiščenje {1}</string>
+		<string name="repair_not_exist">{1} ne obstaja!  Popravilo ni mogoče!</string>
+		<string name="repairing_using">Popravljanje {1} s/z {2} …</string>
+		<string name="unable_repair">Popravilo {1} ni mogoče.</string>
+		<string name="mount_data_footer">/data ni bilo mogoče priklopiti in vnožja za šifriranje ni mogoče najti.</string>
+		<string name="create_folder_strerr">Mape \'{1}\' ni mogoče ustvariti ({2}).</string>
+		<string name="fail_mount">Priklop \'{1}\' je spodletel ({2})</string>
+		<string name="fail_unmount">Odklop \'{1}\' je spodletel ({2})</string>
+		<string name="cannot_resize">Velikosti {1} ni mogoče spremeniti.</string>
+		<string name="repair_resize">Popravljanje {1} pred spremembo velikosti.</string>
+		<string name="unable_resize">Velikosti {1} ni mogoče spremeniti.</string>
+		<string name="no_digest_found" version="2">Za \'{1}\' ni bilo najdene datoteke Digest.  Odstranite izbiro \'Omogoči preverjanje razpršila Digest\' za obnovitev.</string>
+		<string name="digest_fail_match" version="2">Ujemanje razpršila Digest na \'{1}\' je spodletelo.</string>
+		<string name="digest_matched" version="2">Digest matched for '{1}'.</string>
+		<string name="fail_decrypt_tar">Odšifriranje datoteke TAR \'{1}\' je spodletelo</string>
+		<string name="format_data_msg">Za ponovno uporabo /data boste morda morali ponovno zagnati obnovitev.</string>
+		<string name="format_data_err">Formatiranje za odstranitev šifriranja ni mogoče.</string>
+		<string name="formatting_using">Formatiranje {1} s/z {2} …</string>
+		<string name="unable_to_wipe">Čiščenje {1} ni mogoče.</string>
+		<string name="cannot_wipe">Razdelka {1} ni mogoče počistiti.</string>
+		<string name="remove_all">Odstranjevanje vseh datotek v \'{1}\'</string>
+		<string name="wiping_data">Čiščenje podatkov brez čiščenja /data/media …</string>
+		<string name="backing_up">Varnostno kopiranje {1} …</string>
+		<string name="backing">Varnostno kopiranje</string>
+		<string name="backup_size">Velikost varnostne kopije za \'{1}\' je 0 B.</string>
+		<string name="datamedia_fs_restore">OPOZORILO: ta varnostna kopija /data je bila ustvarjena z datotečnim sistemom {1}!  Varnostna kopija se morda ne bo zagnala, če je ne spremenite nazaj na {1}.</string>
+		<string name="restoring">Obnavljanje {1} …</string>
+		<string name="restoring_hdr">Obnavljanje</string>
+		<string name="recreate_folder_err">Mape {1} ni mogoče ponovno ustvariti.</string>
+		<string name="img_size_err">Velikost odtisa je večja od ciljne naprave</string>
+		<string name="flashing">Nameščanje {1} …</string>
+		<string name="backup_folder_set">Mapa varnostnega kopiranja nastavljena na \'{1}\'</string>
+		<string name="locate_backup_err">Varnostne kopije \'{1}\' ni mogoče najti</string>
+		<string name="set_restore_opt">Nastavljanje možnosti za varnostno kopiranje: \'{1}\':</string>
+		<string name="digest_check_skip" version="2">Preskak. preverjanja raz. Digest je vkl.</string>
+		<string name="ors_encrypt_restore_err">OpenRecoveryScript ni mogoče uporabiti za obnavljanje šifriranih varnostnih kopij.</string>
+		<string name="mounting">Priklapljanje</string>
+		<string name="unmounting">Odklapljanje</string>
+		<string name="mounted">\'{1}\' priklopljen</string>
+		<string name="unmounted">\'{1}\' odklopljen</string>
+		<string name="setting">Nastavljanje \'{1}\' na \'{2}\'</string>
+		<string name="setting_empty">Nastavljanje \'{1}\' na prazno</string>
+		<string name="making_dir1">Ustvarjanje mape</string>
+		<string name="making_dir2">Ustvarjanje mape: \'{1}\'</string>
+		<string name="running_command">Izvajanje ukaza</string>
+		<string name="sideload">Stransko nalaganje ADB</string>
+		<string name="start_sideload">Začenjanje značilnosti stranskega nalaganja ADB …</string>
+		<string name="need_new_adb">Za stransko nalaganje na to napravo potrebujete ADB 1.0.32 ali novejši</string>
+		<string name="no_pwd">Geslo ni bilo navedeno.</string>
+		<string name="done_ors">Obdelava datoteke skripta je končana</string>
+		<string name="injecttwrp">Vstavljanje TWRP-ja v zagonski odtis …</string>
+		<string name="zip_err">Napaka pri nameščanju datoteke ZIP \'{1}\'</string>
+		<string name="installing_zip">Nameščanje datoteke ZIP \'{1}\'</string>
+		<string name="select_backup_opt">Nastavljanje možnosti varnostnega kopiranja:</string>
+		<string name="compression_on">Stiskanje je vklopljeno</string>
+		<string name="digest_off" version="2">Ustvarjanje razpršila Digest je izklopljeno</string>
+		<string name="backup_fail">Var. kop. je spodletelo</string>
+		<string name="backup_clean">Varnostno kopiranje je spodletelo. Čiščenje mape varnostnega kopiranja.</string>
+		<string name="running_recovery_commands">Izvajanje ukazov za obnovitev</string>
+		<string name="recovery_commands_complete">Ukazi za obnovitev kočani</string>
+		<string name="running_ors">Izvajanje OpenRecoveryScript</string>
+		<string name="ors_complete">OpenRecoveryScript končan</string>
+		<string name="no_updater_binary">V datoteki ZIP ni bilo mogoče najti \'{1}\'</string>
+		<string name="check_for_digest" version="2">Preverjanje za datoteko razpršila Digest …</string>
+		<string name="fail_sysmap">Preslikava datoteke \'{1}\' je spodletela</string>
+		<string name="verify_zip_sig">Preverjanje podpisa ZIP</string>
+		<string name="verify_zip_fail">Preverjanje podpisa ZIP je spodletelo!</string>
+		<string name="verify_zip_done">Preverjanje podpisa ZIP je bilo uspešno!</string>
+		<string name="zip_corrupt">Datoteka ZIP je poškodovana!</string>
+		<string name="no_digest" version="2">Preskakovanje preverjanja razpršila Digest: datoteka Digest ni bila najdena</string>
+		<string name="digest_fail" version="2">Digest se ne ujema</string>
+		<string name="digest_match" version="2">Digest se ujema</string>
+		<string name="pid_signal">{1} opravilo se je končalo s signalom: {2}</string>
+		<string name="pid_error">{1} opravilo se je končalo z NAPAKO: {2}</string>
+		<string name="install_dumlock">Nameščanje HTC Dumlocka na sistem …</string>
+		<string name="dumlock_restore">Obnavljanje izvirnega zagona …</string>
+		<string name="dumlock_reflash">Ponovno nameščanje obnovitve v zagon …</string>
+		<string name="run_script">Izvajanje skripta {1} …</string>
+		<string name="rename_stock">Tovarniška datoteka obnovitve v /system je bila preimenovana, da tovarniškemu ROM-u prepreči nadomestitev TWRP-ja.</string>
+		<string name="split_backup">Razcepljanje varnostne kopije v več arhivov …</string>
+		<string name="backup_error">Napaka pri ustvarjanju varnostne kopije.</string>
+		<string name="restore_error">Napaka med postopkom obnovitve.</string>
+		<string name="split_thread">Razcepljanje ID-ja niti {1} v arhiv {2}</string>
+		<string name="file_progress">%llu od %llu datotek, %i %%</string>
+		<string name="size_progress">%llu MB od %llu MB, %i %%</string>
+		<string name="decrypt_cmd">Poizkušanje odšifriranja podatkovnega razdelka preko ukazne vrstice.</string>
+		<string name="base_pkg_err">Nalaganje osnovnih paketov je spodletelo.</string>
+		<string name="simulating">Simuliranje dejanj …</string>
+		<string name="backup_cancel">Varnostno kopiranje preklicano</string>
+		<string name="config_twrp">Nastavljanje TWRP-ja …</string>
+		<string name="config_twrp_err">TWRP-ja ni mogoče nastaviti s tem jedrom.</string>
+		<string name="copy_log">Dnevnik obnovitve kopiran v {1}.</string>
+		<string name="max_queue">Dosežena največja velikost čakalne vrste ZIP!</string>
+		<string name="min_queue">Dosežena najmanjša velikost čakalne vrste ZIP!</string>
+		<string name="screenshot_saved">Zaslonska slika je bila shranjena v {1}</string>
+		<string name="screenshot_err">Zajem zaslonske slike je spodletel!</string>
+		<string name="zip_wipe_cache">Eden ali več ZIP-ov je zahteval čiščenje predpomnilnika -- čiščenje.</string>
+		<string name="and_sec_wipe_err">Varnega razdelka Android ni mogoče počistiti</string>
+		<string name="dalvik_wipe_err">Čiščenje Dalvika je spodletelo</string>
+		<string name="auto_gen">(samodejno ustvari)</string>
+		<string name="curr_date">(trenutni datum)</string>
+		<string name="backup_name_len">Ime varnostne kopije je predolgo.</string>
+		<string name="backup_name_invalid">Ime varnostne kopije \'{1}\' vsebuje neveljavni znak: \'{1}\'</string>
+		<string name="no_real_sdcard">Ta naprava nima resnične kartice SD!  Prekinjanje!</string>
+		<string name="cancel_sideload">Preklicevanje stranskega nalaganja ADB …</string>
+		<string name="change_fs_err">Napaka pri spreminjanju datotečnega sistema.</string>
+		<string name="theme_ver_err">Različica teme po meri se ne ujema z različico TWRP-ja.  Uporabljena bo tovarniška tema.</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/sv.xml b/gui/theme/common/languages/sv.xml
new file mode 100644
index 0000000..1f4368c
--- /dev/null
+++ b/gui/theme/common/languages/sv.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+<language>
+	<display>Swedish</display>
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<string name="system">System</string>
+		<string name="vendor">Leverantör</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="sdcard">SD-kort</string>
+		<string name="internal">Intern lagring</string>
+		<string name="microsd">Micro SD-kort</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<string name="sdext">SD-EXT</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="sort_by_name">Sortera efter namn</string>
+		<string name="sort_by_date">Sortera efter datum</string>
+		<string name="sort_by_size">Sortera efter storlek</string>
+		<string name="sort_by_name_only">Namn</string>
+		<string name="sort_by_date_only">Datum</string>
+		<string name="sort_by_size_only">Storlek</string>
+		<string name="tab_general">ALLMÄNT</string>
+		<string name="tab_options">ALTERNATIV</string>
+		<string name="tab_time_zone">TIDSZON</string>
+		<string name="tab_screen">SKÄRM</string>
+		<string name="tab_vibration">VIBRATION</string>
+		<string name="tab_language">SPRÅK</string>
+		<string name="install_btn">Installera</string>
+		<string name="restore_btn">Återställ</string>
+		<string name="settings_btn">Inställningar</string>
+		<string name="advanced_btn">Avancerad</string>
+		<string name="reboot_btn">Starta om</string>
+		<string name="files_btn">Filer</string>
+		<string name="copy_log_btn">Kopiera logg</string>
+		<string name="select_type_hdr">Välj typ</string>
+		<string name="install_zip_hdr">Installera Zip</string>
+		<string name="install_zip_btn">Installera Zip</string>
+		<string name="install_select_file_hdr">Välj fil</string>
+		<string name="file_selector_folders_hdr">Mappar</string>
+		<string name="install_hdr">Installera</string>
+		<string name="select_storage_hdr">Välj lagring</string>
+		<string name="select_storage_btn">Välj lagring</string>
+		<string name="queue_hdr">Kö</string>
+		<string name="folder">Mapp:</string>
+		<string name="file">Fil:</string>
+		<string name="options_hdr">Alternativ</string>
+		<string name="confirm_flash_hdr">Bekräfta Flash</string>
+		<string name="zip_queue">Kö:</string>
+		<string name="options">Alternativ:</string>
+		<string name="swipe_confirm">   Bekräfta</string>
+		<string name="failed">Misslyckades</string>
+		<string name="install_failed">Installationen misslyckades</string>
+		<string name="install_sel_target">Välj målpartition</string>
+		<string name="swipe_reboot_s">   Starta om</string>
+		<string name="confirm_action">Bekräfta åtgärd</string>
+		<string name="cancel_btn">Avbryt</string>
+		<string name="factory_reset_hdr">Fabriksåterställning</string>
+		<string name="factory_reset_btn">Fabriksåterställning</string>
+		<string name="factory_resetting">Fabriksåterställning...</string>
+		<string name="swipe_format_data_s">   Formattera data</string>
+		<string name="sel_part_hdr">Välj partitioner</string>
+		<string name="format_data_hdr">Formattera data</string>
+		<string name="format_data_btn">Formattera data</string>
+		<string name="sel_act_hdr">Välj åtgärd</string>
+		<string name="repair_btn_s">Reparera</string>
+		<string name="repairing">Reparerar...</string>
+		<string name="swipe_repair_s">   Reparera</string>
+		<string name="change_fs_btn_s">Ändra</string>
+		<string name="formatting">Formatterar...</string>
+		<string name="swipe_change_s">   Ändra</string>
+		<string name="back_btn">Tillbaka</string>
+		<string name="encryption_tab">KRYPTERING</string>
+		<string name="encryption">Kryptering:</string>
+		<string name="name">Namn:</string>
+		<string name="storage">Lagring:</string>
+		<string name="enc_enabled">aktiverad</string>
+		<string name="append_date_btn">Lägg till datum</string>
+		<string name="enter_pass">Ange lösenord:</string>
+		<string name="enter_pass2">Ange lösenordet igen:</string>
+		<string name="partitions">Partitioner:</string>
+		<string name="disabled">Inaktiverad</string>
+		<string name="enabled">Aktiverad</string>
+		<string name="progress">Förlopp:</string>
+		<string name="restore_hdr">Återställ</string>
+		<string name="swipe_delete_s">   Ta bort</string>
+		<string name="restore_sel_part">Välj partitioner att återställa:</string>
+		<string name="swipe_restore_s">   Återställ</string>
+		<string name="swipe_rename">   Byt namn</string>
+		<string name="confirm_hdr">Bekräfta</string>
+		<string name="decrypt_data_btn">Dekryptera data</string>
+		<string name="disable_mtp_btn">Inaktivera MTP</string>
+		<string name="enable_mtp_btn">Aktivera MTP</string>
+		<string name="usb_storage_hdr">USB-lagring</string>
+		<string name="rb_system_btn">System</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Ladda ner</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Stänger av...</string>
+		<string name="settings_hdr">Inställningar</string>
+		<string name="settings_gen_s_hdr">Allmänt</string>
+		<string name="settings_gen_btn">Allmänt</string>
+		<string name="settings_tz_btn">Tidszon</string>
+		<string name="settings_screen_btn">Skärm</string>
+		<string name="settings_vibration_btn">Vibration</string>
+		<string name="settings_language_btn">Språk</string>
+		<string name="time_zone_hdr">Tidszon</string>
+		<string name="sel_tz_list">Välj tidszon:</string>
+		<string name="utcm10">(UTC -10) Hawaii</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm3">(UTC -3) Brasilien, Buenos Aires</string>
+		<string name="utcm1">(UTC -1) Azorerna, Kap Verde</string>
+		<string name="utc0">(UTC 0) London, Dublin, Lissabon</string>
+		<string name="utcp1">(UTC +1) Berlin, Bryssel, Paris</string>
+		<string name="utcp2">(UTC +2) Aten, Istanbul, Sydafrika</string>
+		<string name="utcp3">(UTC +3) Moskva, Bagdad</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp9">(UTC +9) Tokyo, Seoul, Yakutsk</string>
+		<string name="utcp10">(UTC +10) Östra Australien, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Salomonöarna</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fiji</string>
+		<string name="tz_offset_none">Ingen</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="curr_tz_s">Aktuell tidszon:</string>
+		<string name="set_tz_btn">Ställ in tidszon</string>
+		<string name="vibration_hdr">Vibration</string>
+		<string name="select_language">Välj språk:</string>
+		<string name="set_language_btn">Ställ in språk</string>
+		<string name="advanced_hdr">Avancerad</string>
+		<string name="part_sd_s_btn">SD-kort</string>
+		<string name="file_manager_btn">Filhanterare</string>
+		<string name="language_hdr">Språk</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Injicera TWRP</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Filsystem:</string>
+		<string name="swipe_part_sd_s">Partition</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_install_btn">Installera HTC Dumlock</string>
+		<string name="dumlock_installing">Installerar HTC Dumlock...</string>
+		<string name="swipe_unlock">   Lås upp</string>
+		<string name="fm_hdr">Filhanterare</string>
+		<string name="fm_type_folder">Mapp</string>
+		<string name="fm_type_file">Fil</string>
+		<string name="fm_choose_act">Välj åtgärd</string>
+		<string name="fm_copy_btn">Kopiera</string>
+		<string name="fm_copy_file_btn">Kopiera fil</string>
+		<string name="fm_copy_folder_btn">Kopiera mapp</string>
+		<string name="fm_copying">Kopierar</string>
+		<string name="fm_move_btn">Flytta</string>
+		<string name="fm_moving">Flyttar</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Ta bort</string>
+		<string name="fm_deleting">Tar bort</string>
+		<string name="fm_rename_btn">Byt namn</string>
+		<string name="fm_renaming">Byter namn på</string>
+		<string name="fm_sel_dest">Välj målmapp</string>
+		<string name="fm_sel_curr_folder">Välj aktuell mapp</string>
+		<string name="fm_rename_hdr">Byt namn</string>
+		<string name="decrypt_data_hdr">Dekryptera data</string>
+		<string name="decrypt_data_enter_pattern">Ange mönster.</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">DÖDA</string>
+		<string name="swipe_sideload">   Start</string>
+		<string name="reboot_hdr">Starta om</string>
+		<string name="install_cancel">Installera inte</string>
+		<string name="sel_storage_list">Välj lagring</string>
+		<string name="ok_btn">OK</string>
+		<string name="generating_digest1" version="2">Genererar Digest</string>
+		<string name="generating_digest2" version="2"> * Genererar Digest...</string>
+		<string name="digest_created" version="2"> * Digest skapad.</string>
+		<string name="digest_error" version="2"> * Digest-fel!</string>
+		<string name="current_date">(Dagens datum)</string>
+		<string name="auto_generate">(Auto-generera)</string>
+		<string name="available_space"> * Tillgängligt utrymme: {1}MB</string>
+		<string name="verifying_digest" version="2">Verifierar Digest</string>
+		<string name="update_part_details_done">...klar</string>
+		<string name="done">Klar.</string>
+		<string name="create_part">Skapar {1} partition...</string>
+		<string name="mtp_already_enabled">MTP redan aktiverat</string>
+		<string name="flashing">Blinkande {1}...</string>
+		<string name="installing_zip">Installerar zipfil \"{1}\"</string>
+		<string name="compression_on">Komprimering är på</string>
+		<string name="verify_zip_sig">Verifierar zip-signatur...</string>
+		<string name="config_twrp">Konfigurerar TWRP...</string>
+		<string name="auto_gen">(Auto-generera)</string>
+		<string name="curr_date">(Dagens datum)</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/tr.xml b/gui/theme/common/languages/tr.xml
new file mode 100644
index 0000000..3e040de
--- /dev/null
+++ b/gui/theme/common/languages/tr.xml
@@ -0,0 +1,691 @@
+<?xml version="1.0"?>
+
+<language>
+	<display>Turkish</display>
+
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+
+		<!-- Partition display names -->
+		<string name="system">Sistem</string>
+		<string name="system_image">Sistem İmajı</string>
+		<string name="vendor">Satıcı</string>
+		<string name="vendor_image">Satıcı İmajı</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Önbellek</string>
+		<string name="data">Veri</string>
+		<string name="data_backup">Veri (depolama hariç)</string>
+		<string name="sdcard">SD Kart</string>
+		<string name="internal">Dahili Depolama</string>
+		<string name="microsd">Micro SD Kart</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Önbelleği</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">Harici SD</string>
+		<string name="adopted_data">Kabul Edilen Veri</string>
+		<string name="adopted_storage">Kabul Edilen Depolama</string>
+		<string name="autostorage">Depolama</string>
+
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">İşlemci: %tw_cpu_temp% °C</string>
+		<string name="battery_pct">Pil: %tw_battery%</string>
+		<string name="sort_by_name">Ada göre sırala</string>
+		<string name="sort_by_date">Tarihe göre sırala</string>
+		<string name="sort_by_size">Boyuta göre sırala</string>
+		<string name="sort_by_name_only">Ad</string>
+		<string name="sort_by_date_only">Tarih</string>
+		<string name="sort_by_size_only">Boyut</string>
+		<string name="tab_general">GENEL</string>
+		<string name="tab_options">SEÇENEKLER</string>
+		<string name="tab_backup">YEDEKLE</string>
+		<string name="tab_time_zone">ZAMAN DİLİMİ</string>
+		<string name="tab_screen">EKRAN</string>
+		<string name="tab_vibration">TİTREŞİM</string>
+		<string name="tab_language">DİL</string>
+
+		<string name="install_btn">Yükle</string>
+		<string name="wipe_btn">Temizle</string>
+		<string name="backup_btn">Yedekle</string>
+		<string name="restore_btn">Geri Yükle</string>
+		<string name="mount_btn">Bağla</string>
+		<string name="settings_btn">Ayarlar</string>
+		<string name="advanced_btn">Gelişmiş</string>
+		<string name="reboot_btn">Yeniden Başlat</string>
+		<string name="files_btn">Dosyalar</string>
+		<string name="copy_log_btn">Kaydı Kopyala</string>
+		<string name="select_type_hdr">Tür Seç</string>
+		<string name="install_zip_hdr">Zip Yükle</string>
+		<string name="install_zip_btn">Zip Yükle</string>
+		<string name="install_image_hdr">İmaj Yükle</string>
+		<string name="install_image_btn">İmaj Yükle</string>
+		<string name="install_select_file_hdr">Dosya Seç</string>
+		<string name="file_selector_folders_hdr">Klasörler</string>
+		<string name="select_file_from_storage">%tw_storage_display_name% içinden dosya seç (%tw_storage_free_size% MB)</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">Yükle</string>
+		<string name="select_storage_hdr">Depolama Seç</string>
+		<string name="select_storage_btn">Depolama Seç</string>
+		<string name="queue_hdr">Sıra</string>
+		<string name="zip_queue_count">%tw_zip_queue_count%/10 dosya sıraya alındı</string>
+		<string name="zip_queue_count_s">%tw_zip_queue_count%/10 dosya:</string>
+		<string name="zip_warn1">Bu işlem uyumsuz yazılım yükleyebilir</string>
+		<string name="zip_warn2">ve cihazınızı kullanılamaz hale getirebilir.</string>
+		<string name="zip_back_cancel">Bu zip'i eklememek için geriye basın.</string>
+		<string name="zip_back_clear">Sırayı temizlemek için geri düğmesine basın.</string>
+		<string name="folder">Klasör:</string>
+		<string name="file">Dosya:</string>
+		<string name="zip_sig_chk">Zip imza doğrulaması</string>
+		<string name="inject_twrp_chk">Yükledikten sonra TWRP ekle</string>
+		<string name="install_reboot_chk">Yükleme tamamlandıktan sonra yeniden başlat</string>
+		<string name="options_hdr">Seçenekler</string>
+		<string name="confirm_flash_hdr">Flashlamayı Onayla</string>
+		<string name="zip_queue">Sıra:</string>
+		<string name="options">Seçenekler:</string>
+		<string name="swipe_confirm">   Onayla</string>
+		<string name="zip_add_btn">Daha fazla zip ekle</string>
+		<string name="zip_clear_btn">Zip sırasını temizle</string>
+		<string name="install_zip_count_hdr">Zip'i yükle %tw_zip_index%/%tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Zip yükleniyor: %tw_file%</string>
+		<string name="failed">Başarısız</string>
+		<string name="successful">Başarılı</string>
+		<string name="install_failed">Yükleme Başarısız</string>
+		<string name="install_successful">Yükleme Başarılı</string>
+		<string name="wipe_cache_dalvik_btn">Cache/dalvik temizle</string>
+		<string name="reboot_system_btn">Sistemi Yeniden Başlat</string>
+		<string name="install_sel_target">Hedef Bölümü Seç</string>
+		<string name="flash_image_select">İmajı Flashlamak için Bölüm Seç:</string>
+		<string name="target_partition">Hedef Bölüm:</string>
+		<string name="flashing_image">İmaj Flashlanıyor...</string>
+		<string name="image_flashed">İmaj Flashlandı</string>
+		<string name="wipe_cache_dalvik_confirm">Önbellek ve Dalvik temizlensin mi?</string>
+		<string name="wiping_cache_dalvik">Önbellek ve Dalvik temizleniyor...</string>
+		<string name="wipe_cache_dalvik_complete">Önbellek ve Dalvik temizliği tamamlandı</string>
+		<string name="swipe_wipe">Temizlemek için Kaydır</string>
+		<string name="swipe_wipe_s">   Temizle</string>
+		<string name="no_os1">İşletim Sistemi yüklü değil!</string>
+		<string name="no_osrb">Yeniden başlatmak istediğinize emin misiniz?</string>
+		<string name="no_ospo">Kapatmak istediğinize emin misiniz?</string>
+		<string name="rebooting">Yeniden Başlatılıyor...</string>
+		<string name="swipe_reboot">Yeniden Başlatmak için Kaydır</string>
+		<string name="swipe_reboot_s">   Yeniden başlat</string>
+		<string name="reboot_install_app_hdr">TWRP Uygulaması yüklensin mi?</string>
+		<string name="reboot_install_app1">Resmi TWRP Uygulamasını yüklemek ister misiniz?</string>
+		<string name="reboot_install_app2">Uygulama, yeni TWRP sürümlerini kontrol edebilir.</string>
+		<string name="reboot_install_app_prompt_install">Yüklü değilse, TWRP uygulamasını yüklemek için sor</string>
+		<string name="reboot_install_app_system">Sistem Uygulaması olarak yükle</string>
+		<string name="reboot_installing_app">Uygulama yükleniyor...</string>
+		<string name="swipe_to_install_app">TWRP Uygulamasını yüklemek için kaydır</string>
+		<string name="swipe_flash">Flashlamayı onaylamak için kaydır</string>
+		<string name="confirm_action">Eylemi Onayla</string>
+		<string name="back_cancel">İptal etmek için geri düğmesine basın.</string>
+		<string name="cancel_btn">İptal</string>
+		<string name="wipe_hdr">Temizle</string>
+		<string name="factory_reset_hdr">Sıfırla</string>
+		<string name="factory_reset_btn">Sıfırla</string>
+		<string name="factory_reset1">Veriyi, Önbelleği, ve Dalvik'i temizler</string>
+		<string name="factory_reset2">(dahili depolama hariç)</string>
+		<string name="factory_reset3">Çoğu zaman ihtiyacınız olan</string>
+		<string name="factory_reset4">tek temizlik budur.</string>
+		<string name="factory_resetting">Sıfırla...</string>
+		<string name="advanced_wipe_hdr">Gelişmiş Temizlik</string>
+		<string name="advanced_wipe_btn">Gelişmiş Temizlik</string>
+		<string name="wipe_enc_confirm">Şifreleme Verilerden Silinsin mi?</string>
+		<string name="formatting_data">Veri Biçimlendiriliyor...</string>
+		<string name="swipe_format_data">Veriyi Biçimlendirmek için Kaydır</string>
+		<string name="swipe_format_data_s">   Veriyi Biçimlendir</string>
+		<string name="factory_reset_complete">Sıfırlama Tamamlandı</string>
+		<string name="sel_part_hdr">Bölümleri Seç</string>
+		<string name="wipe_sel_confirm">Seçili Bölüm(ler) Temizlensin mi?</string>
+		<string name="wiping_part">Bölüm(ler) Temizleniyor...</string>
+		<string name="wipe_complete">Temizlik Tamamlandı</string>
+		<string name="sel_part_wipe">Temizlenecek Bölümleri Seçin:</string>
+		<string name="invalid_part_sel">Geçersiz bölüm seçimi</string>
+		<string name="format_data_hdr">Veriyi Biçimlendir</string>
+		<string name="format_data_btn">Veriyi Biçimlendir</string>
+		<string name="format_data_ptr1">Veriyi biçimlendirmek tüm uygulamalarınızı,</string>
+		<string name="format_data_ptr2">yedeklerinizi, resimlerinizi, videolarınızı, medyanızı, temizleyecek ve</string>
+		<string name="format_data_ptr3">dahili depolama üzerindeki şifrelemeyi kaldıracak.</string>
+		<string name="format_data_adopted">Kabul Edilen Depolama Dahil</string>
+		<string name="format_data_lcp1">Veriyi biçimlendirmek tüm uygulamalarınızı, yedeklerinizi, resimlerinizi, videolarınızı, medyanızı, temizleyecek ve</string>
+		<string name="format_data_lcp2">dahili depolama üzerindeki şifrelemeyi kaldıracak.</string>
+		<string name="format_data_wtc1">Veriyi biçimlendirmek tüm uygulamalarınızı,</string>
+		<string name="format_data_wtc2">yedeklerinizi ve medyanızı temizleyecek. Bu işlem geri alınamaz.</string>
+		<string name="format_data_undo">Bu işlem geri alınamaz.</string>
+		<string name="format_data_complete">Veriyi Biçimlendirme Tamamlandı</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">Devam etmek için yes yazın.  İptal etmek için geriye basın.</string>
+		<string name="part_opt_hdr">%tw_partition_name% bölümü için bölümleme seçenekleri</string>
+		<string name="sel_act_hdr">Eylem Seç</string>
+		<string name="file_sys_opt">Dosya Sistemi Seçenekleri</string>
+		<string name="partition">Bölüm: %tw_partition_name%</string>
+		<string name="part_mount_point">Bağlama Noktası: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Dosya sistemi: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Mevcut: Evet</string>
+		<string name="part_present_no">Mevcut: Hayır</string>
+		<string name="part_removable_yes">Çıkarılabilir: Evet</string>
+		<string name="part_removable_no">Çıkarılabilir: Hayır</string>
+		<string name="part_size">Boyut: %tw_partition_size%MB</string>
+		<string name="part_used">Kullanılan: %tw_partition_used%MB</string>
+		<string name="part_free">Boş: %tw_partition_free%MB</string>
+		<string name="part_backup_size">Yedek Boyutu: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">Dosya Sistemini Yeniden Boyutlandır</string>
+		<string name="resize_btn_s">Yeniden Boyutlandır</string>
+		<string name="resize_confirm">%tw_partition_name% yeniden boyutlandırılsın mı?</string>
+		<string name="resizing">Yeniden boyutlandırılıyor...</string>
+		<string name="resize_complete">Yeniden boyutlandırma tamamlandı</string>
+		<string name="swipe_resize">Yeniden Boyutlandırmak için Kaydır</string>
+		<string name="swipe_resize_s">   Yeniden Boyutlandır</string>
+		<string name="repair_btn">Dosya Sistemini Onar</string>
+		<string name="repair_btn_s">Onar</string>
+		<string name="repair_confirm">%tw_partition_name% onarılsın mı?</string>
+		<string name="repairing">Onarılıyor...</string>
+		<string name="repair_complete">Onarma tamamlandı</string>
+		<string name="swipe_repair">Onarmak için Kaydır</string>
+		<string name="swipe_repair_s">   Onar</string>
+		<string name="change_fs_btn">Dosya Sistemini Değiştir</string>
+		<string name="change_fs_btn_s">Değiştir</string>
+		<string name="change_fs_for_hdr">Dosya Sistemini Değiştir: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Bölüm: %tw_partition_name% &gt; Dosya Sistemini Seç</string>
+		<string name="change_fs_warn1">Bazı ROM'lar veya kerneller bazı dosya sistemlerini</string>
+		<string name="change_fs_warn2">desteklemeyebilir. Dikkatli olun!</string>
+		<string name="change_fs_confirm">%tw_partition_name% değiştirilsin mi?</string>
+		<string name="formatting">Biçimlendiriliyor...</string>
+		<string name="format_complete">Biçimlendirme tamamlandı</string>
+		<string name="swipe_change_fs">Değiştirmek için Kaydır</string>
+		<string name="swipe_change_s">   Değiştir</string>
+		<string name="back_btn">Geri</string>
+		<string name="wipe_enc_btn">Şifrelemeyi Temizle</string>
+		<string name="swipe_factory_reset">Sıfırlamak için Kaydır</string>
+		<string name="repair_change_btn">Dosya Sistemini Onar veya Değiştir</string>
+		<string name="storage_hdr">Depolama: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">Yedekle</string>
+		<string name="backup_confirm_hdr">Yedeklemeyi Onayla</string>
+		<string name="encryption_tab">ŞİFRELEME</string>
+		<string name="encryption">Şifreleme:</string>
+		<string name="name">Ad:</string>
+		<string name="sel_part_backup">Yedeklenecek Bölümleri Seç:</string>
+		<string name="storage">Depolama:</string>
+		<string name="enc_disabled">devre dışı - etkinleştirmek için bir şifre ayarlayın</string>
+		<string name="enc_enabled">etkin</string>
+		<string name="enable_backup_comp_chk">Sıkıştırmayı etkinleştir</string>
+		<string name="skip_digest_backup_chk" version="2">Yedekleme sırasında Digest oluşturmayı geç</string>
+		<string name="disable_backup_space_chk" version="2">Yedeklemeden önce boş alan denetimini devre dışı bırak</string>
+		<string name="current_boot_slot">Geçerli Yuva: %tw_active_slot%</string>
+		<string name="boot_slot_a">A Yuvası</string>
+		<string name="boot_slot_b">B Yuvası</string>
+		<string name="changing_boot_slot">Önyükleme Yuvasını Değiştir</string>
+		<string name="changing_boot_slot_complete">Önyükleme Yuvasını Değiştirme Tamamlandı</string>
+		<string name="refresh_sizes_btn">Boyutları Yenile</string>
+		<string name="swipe_backup">Yedeklemek için Kaydır</string>
+		<string name="append_date_btn">Tarih Ekle</string>
+		<string name="backup_name_exists">Bu adda bir yedek zaten var!</string>
+		<string name="encrypt_backup">Yedeklemeniz şifrelensin mi?</string>
+		<string name="enter_pass">Şifre Gir:</string>
+		<string name="enter_pass2">Şifreyi Tekrar Girin:</string>
+		<string name="pass_not_match">Şifreler eşleşmiyor!</string>
+		<string name="partitions">Bölümler:</string>
+		<string name="disabled">Devre dışı</string>
+		<string name="enabled">Etkin</string>
+		<string name="backup_name_hdr">Yedek Adı</string>
+		<string name="progress">İlerleme:</string>
+		<string name="backup_complete">Yedekleme Tamamlandı</string>
+		<string name="restore_hdr">Geri Yükle</string>
+		<string name="sel_backup_hdr">Yedek Seç</string>
+		<string name="restore_sel_store_hdr">%tw_storage_display_name% içinden yedek seç (%tw_storage_free_size% MB)</string>
+		<string name="restore_sel_pack_fs">Geri Yüklenecek Paketi Seçin:</string>
+		<string name="restore_enc_backup_hdr">Şifreli Yedek</string>
+		<string name="restore_dec_fail">Şifre yanlış, lütfen tekrar deneyin!</string>
+		<string name="del_backup_btn">Yedeği Sil</string>
+		<string name="del_backup_confirm">Yedek Silinsin mi?</string>
+		<string name="del_backup_confirm2">Bu işlem geri alınamaz!</string>
+		<string name="deleting_backup">Yedek Siliniyor...</string>
+		<string name="backup_deleted">Yedek Silme Tamamlandı</string>
+		<string name="swipe_delete">Silmek için Kaydır</string>
+		<string name="swipe_delete_s">   Sil</string>
+		<string name="restore_try_decrypt">Şifreli Yedek - Çözülmeye Çalışılıyor</string>
+		<string name="restore_try_decrypt_s">Çözülmeye Çalışılıyor</string>
+		<string name="restore_backup_date">Yedek tarihi: %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Geri Yüklenecek Bölümleri Seç:</string>
+		<string name="restore_enable_digest_chk" version="2">Yedek Dosyalarının Digest Doğrulamasını etkinleştir</string>
+		<string name="restore_complete">Geri Yükleme Tamamlandı</string>
+		<string name="swipe_restore">Geri Yüklemek için Kaydır</string>
+		<string name="swipe_restore_s">   Geri Yükle</string>
+		<string name="rename_backup_hdr">Yedeği Yeniden Adlandır</string>
+		<string name="rename_backup_confirm">Yedek Yeniden Adlandırılsın mı?</string>
+		<string name="rename_backup_confirm2">Bu işlem geri alınamaz!</string>
+		<string name="renaming_backup">Yedek Yeniden Adlandırılıyor...</string>
+		<string name="rename_backup_complete">Yedek Yeniden Adlandırıldı</string>
+		<string name="swipe_to_rename">Yeniden Adlandırmak için Kaydır</string>
+		<string name="swipe_rename">   Yeniden Adlandır</string>
+		<string name="confirm_hdr">Onayla</string>
+		<string name="mount_hdr">Bağla</string>
+		<string name="mount_sel_part">Bağlanacak Bölümleri Seç:</string>
+		<string name="mount_sys_ro_chk">Sistem bölümünü salt okunur bağla</string>
+		<string name="mount_sys_ro_s_chk">Sistemi RO Bağla</string>
+		<string name="decrypt_data_btn">Veri Şifresini Çöz</string>
+		<string name="disable_mtp_btn">MTP'yi Devre Dışı Bırak</string>
+		<string name="enable_mtp_btn">MTP'yi Etkinleştir</string>
+		<string name="mount_usb_storage_btn">USB Depolamayı Bağla</string>
+		<string name="usb_storage_hdr">USB Depolama</string>
+		<string name="usb_stor_mnt1">USB Depolama Bağlandı</string>
+		<string name="usb_stor_mnt2">Çıkarmadan önce cihazınızı bilgisayarınızdan</string>
+		<string name="usb_stor_mnt3">güvenle çıkardığınızdan emin olun!</string>
+		<string name="unmount_btn">Çıkar</string>
+		<string name="rb_system_btn">Sistem</string>
+		<string name="rb_poweroff_btn">Kapat</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Download</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Kapatılıyor...</string>
+		<string name="swipe_power_off">Kapatmak için Kaydır</string>
+		<string name="swipe_power_off_s">Kapat</string>
+		<string name="sys_ro_hdr">Değiştirilmemiş Sistem Bölümü</string>
+		<string name="sys_ro_keep">Sistem bölümü salt okunur kalsın mı?</string>
+		<string name="sys_rop1">TWRP resmi güncellemeleri almanızı kolaylaştırmak</string>
+		<string name="sys_rop2">için sistem bölümünüzü değiştirmeyebilir.</string>
+		<string name="sys_rop3">TWRP stock ROM'un kendisini değiştirmesini</string>
+		<string name="sys_rop4">engelleyemeyecek ve cihazınızı rootlamanızı istemeyecek.</string>
+		<string name="sys_rop5">Zip yüklemek veya adb işlemleri gerçekleştirmek</string>
+		<string name="sys_rop6">sistem bölümünü değiştirebilir.</string>
+		<string name="sys_rol1">TWRP resmi güncellemeleri almanızı kolaylaştırmak için sistem bölümünüzü değiştirmeyebilir.</string>
+		<string name="sys_rol2">TWRP stock ROM'un kendisini değiştirmesini engelleyemeyecek ve cihazınızı rootlamanızı istemeyecek.</string>
+		<string name="sys_rol3">Zip yüklemek veya adb işlemleri gerçekleştirmek sistem bölümünü değiştirebilir.</string>
+		<string name="sys_ro_never_show_chk">Bu ekranı açılış sırasında tekrar gösterme</string>
+		<string name="sys_ro_keep_ro_btn">Salt Okunur Bırak</string>
+		<string name="swipe_allow_mod">Değişikliklere İzin Vermek için Kaydır</string>
+		<string name="swipe_allow_mod_s">Değişikliklere İzin Ver</string>
+		<string name="settings_hdr">Ayarlar</string>
+		<string name="settings_gen_hdr">Genel</string>
+		<string name="settings_gen_s_hdr">Genel</string>
+		<string name="settings_gen_btn">Genel</string>
+		<string name="use_rmrf_chk">Biçimlendirme yerine rm -rf kullan</string>
+		<string name="use24clock_chk">24-saat biçimini kullan</string>
+		<string name="rev_navbar_chk">Tersine çevrilmiş gezinti çubuğu düzeni</string>
+		<string name="simact_chk">Tema testi için eylemleri taklit et</string>
+		<string name="simfail_chk">Eylemlerin başarısızlığını taklit et</string>
+		<string name="ctr_navbar_rdo">Merkezi gezinti çubuğu düğmeleri</string>
+		<string name="lft_navbar_rdo">Gezinti çubuğu düğmelerini sola hizala</string>
+		<string name="rht_navbar_rdo">Gezinti çubuğu düğmelerini sağa hizala</string>
+		<string name="restore_defaults_btn">Varsayılanları Geri Yükle</string>
+		<string name="settings_tz_btn">Zaman Dilimi</string>
+		<string name="settings_screen_btn">Ekran</string>
+		<string name="settings_screen_bright_btn">Ekran Parlaklığı</string>
+		<string name="settings_vibration_btn">Titreşim</string>
+		<string name="settings_language_btn">Dil</string>
+		<string name="time_zone_hdr">Zaman Dilimi</string>
+		<string name="sel_tz_list">Zaman Dilimi Seç:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) Samoa, Midway Adası</string>
+		<string name="utcm10">(UTC -10) Hawaii</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm8">(UTC -8) Pasifik Saati</string>
+		<string name="utcm7">(UTC -7) Dağ Saati</string>
+		<string name="utcm6">(UTC -6) Merkezi Saat</string>
+		<string name="utcm5">(UTC -5) Doğu Saati</string>
+		<string name="utcm4">(UTC -4) Atlantik Saati</string>
+		<string name="utcm3">(UTC -3) Brezilya, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Orta Atlantik</string>
+		<string name="utcm1">(UTC -1) Azorlar, Cape Verde</string>
+		<string name="utc0">(UTC  0) Londra, Dublin, Lizbon</string>
+		<string name="utcp1">(UTC +1) Berlin, Brüksel, Paris</string>
+		<string name="utcp2">(UTC +2) Atina, İstanbul, Güney Afrika</string>
+		<string name="utcp3">(UTC +3) Moskova, Bağdat</string>
+		<string name="utcp4">(UTC +4) Abu Dabi, Tiflis, Maskat</string>
+		<string name="utcp5">(UTC +5) Yekaterinburg, İslamabad</string>
+		<string name="utcp6">(UTC +6) Almatı, Dakka, Kolombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp8">(UTC +8) Pekin, Singapur, Hong Kong</string>
+		<string name="utcp9">(UTC +9) Tokyo, Seul, Yakutsk</string>
+		<string name="utcp10">(UTC +10) Doğu Avustralya, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Solomon Adaları</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fiji</string>
+		<string name="sel_tz_offset">Ofset Seçin (Genellikle 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Hiçbiri</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Gün ışığından yararlanma saati (DST) kullan</string>
+		<string name="curr_tz">Geçerli Zaman Dilimi: %tw_time_zone%</string>
+		<string name="curr_tz_s">Geçerli Zaman Dilimi:</string>
+		<string name="set_tz_btn">Zaman Dilimini Ayarla</string>
+		<string name="settings_screen_hdr">Ekran</string>
+		<string name="settings_screen_timeout_hdr">Ekran Zaman Aşımı</string>
+		<string name="enable_timeout_chk">Ekran zaman aşımını etkinleştir</string>
+		<string name="screen_to_slider">Ekran zaman aşımı:</string>
+		<string name="screen_to_slider_s">Ekran zaman aşımı (0=devre dışı): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Ekran zaman aşımı ayarı kullanılamıyor</string>
+		<string name="screen_bright_slider">Parlaklık: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Parlaklık ayarı kullanılamıyor</string>
+		<string name="vibration_hdr">Titreşim</string>
+		<string name="button_vibration_hdr">Düğme Titreşimi</string>
+		<string name="kb_vibration_hdr">Klavye Titreşimi</string>
+		<string name="act_vibration_hdr">Eylem Titreşimi</string>
+		<string name="button_vibration">Düğme Titreşimi:</string>
+		<string name="kb_vibration">Klavye Titreşimi:</string>
+		<string name="act_vibration">Eylem Titreşimi:</string>
+		<string name="select_language">Dil Seç:</string>
+		<string name="sel_lang_btn">Dil Seç</string>
+		<string name="set_language_btn">Dili Ayarla</string>
+		<string name="advanced_hdr">Gelişmiş</string>
+		<string name="copy_log_confirm">Kayıt SD Karta Kopyalansın mı?</string>
+		<string name="copying_log" version="2">Kayıtları SD Karta Kopyala...</string>
+		<string name="copy_log_complete" version="2">Kayıtları Kopyalama Tamamlandı</string>
+		<string name="fix_context_btn">Bağlamları Düzelt</string>
+		<string name="part_sd_btn">SD Kartı Bölümle</string>
+		<string name="part_sd_s_btn">SD Kart</string>
+		<string name="file_manager_btn">Dosya Yöneticisi</string>
+		<string name="language_hdr">Dil</string>
+		<string name="terminal_btn">Terminal</string>
+		<string name="reload_theme_btn">Temayı Yeniden Yükle</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">TWRP Ekle</string>
+		<string name="inject_twrp_confirm">TWRP yeniden eklensin mi?</string>
+		<string name="injecting_twrp">TWRP yeniden ekleniyor...</string>
+		<string name="inject_twrp_complete">TWRP Ekleme Tamamlandı</string>
+		<string name="swipe_to_confirm">Onaylamak için Kaydır</string>
+		<string name="part_sd_hdr">SD Kartı Bölümle</string>
+		<string name="invalid_partsd_sel">Çıkarılabilir bir cihaz seçmelisiniz</string>
+		<string name="part_sd_lose">SD kartınız üzerindeki tüm dosyaları kaybedeceksiniz!</string>
+		<string name="part_sd_undo">Bu işlem geri alınamaz!</string>
+		<string name="part_sd_ext_sz">EXT Boyutu:</string>
+		<string name="part_sd_swap_sz">Swap Boyutu:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">Dosya Sistemi:</string>
+		<string name="swipe_part_sd">Bölümlemek için Kaydır</string>
+		<string name="swipe_part_sd_s">Bölümle</string>
+		<string name="partitioning_sd">SD Kart Bölümleniyor...</string>
+		<string name="partitioning_sd2">Bu işlem birkaç dakika sürecek.</string>
+		<string name="part_sd_complete">Bölümleme Tamamlandı</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Orijinal Boot'u Geri Yükle</string>
+		<string name="dumlock_restore_confirm">Orijinal boot imajı geri yüklensin mi?</string>
+		<string name="dumlock_restoring">Orijinal Boot Geri Yükleniyor...</string>
+		<string name="dumlock_restore_complete">Orijinal Boot Geri Yüklemesi Tamamlandı</string>
+		<string name="dumlock_reflash_btn">Recovery'yi Yeniden Flashla</string>
+		<string name="dumlock_reflash_confirm">Cihazın açılması için kurtarma yazılımı tekrar yüklensin mi?</string>
+		<string name="dumlock_reflashing">Cihazın açılması için kurtarma yazılımı tekrar yükleniyor...</string>
+		<string name="dumlock_reflash_complete">Cihazın açılması için kurtarma yazılımını tekrar yükleme tamamlandı</string>
+		<string name="dumlock_install_btn">HTC Dumlock Yükle</string>
+		<string name="dumlock_install_confirm">HTC dumlock dosyaları ROM'a yüklensin mi?</string>
+		<string name="dumlock_installing">HTC Dumlock Yükleniyor...</string>
+		<string name="dumlock_install_complete">HTC Dumlock Yüklemesi Tamamlandı</string>
+		<string name="swipe_to_unlock">Kilidi Açmak için Kaydır</string>
+		<string name="swipe_unlock">   Kilidi Aç</string>
+		<string name="fm_hdr">Dosya Yöneticisi</string>
+		<string name="fm_sel_file">Bir Dosya veya Klasör Seçin</string>
+		<string name="fm_type_folder">Klasör</string>
+		<string name="fm_type_file">Dosya</string>
+		<string name="fm_choose_act">Eylem Seçin</string>
+		<string name="fm_selected">%tw_fm_type% seçili:</string>
+		<string name="fm_copy_btn">Kopyala</string>
+		<string name="fm_copy_file_btn">Dosyayı Kopyala</string>
+		<string name="fm_copy_folder_btn">Klasörü Kopyala</string>
+		<string name="fm_copying">Kopyala</string>
+		<string name="fm_move_btn">Taşı</string>
+		<string name="fm_moving">Taşı</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Sil</string>
+		<string name="fm_deleting">Sil</string>
+		<string name="fm_rename_btn">Yeniden Adlandır</string>
+		<string name="fm_rename_file_btn">Dosyayı Yeniden Adlandır</string>
+		<string name="fm_rename_folder_btn">Klasörü Yeniden Adlandır</string>
+		<string name="fm_renaming">Yeniden Adlandır</string>
+		<string name="fm_sel_dest">Hedef Klasörü Seçin</string>
+		<string name="fm_sel_curr_folder">Geçerli Klasörü Seç</string>
+		<string name="fm_rename_hdr">Yeniden Adlandır</string>
+		<string name="fm_set_perms_hdr">İzinleri Ayarla</string>
+		<string name="fm_perms">İzinler:</string>
+		<string name="fm_complete">Dosya İşlemi Tamamlandı</string>
+		<string name="decrypt_data_hdr">Veri Şifresini Çöz</string>
+		<string name="decrypt_data_enter_pass">Şifre Girin.</string>
+		<string name="decrypt_data_failed">Şifre başarısız oldu, lütfen tekrar deneyin!</string>
+		<string name="decrypt_data_failed_pattern">Desen başarısız oldu, lütfen tekrar deneyin!</string>
+		<string name="decrypt_data_enter_pattern">Desen Girin.</string>
+		<string name="decrypt_data_trying">Şifre Çözülmeye Çalışılıyor</string>
+		<string name="decrypt_data_vold_os_missing">Vold şifresini çözmek için gerekli dosyalar eksik: {1}</string>
+		<string name="term_hdr">Terminal Komutu</string>
+		<string name="term_s_hdr">Terminal</string>
+		<string name="term_kill_btn">SONLANDIR</string>
+		<string name="term_sel_folder_hdr">Başlangıç Klasörüne Gözat</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Dalvik Önbelleğini Temizle</string>
+		<string name="sideload_wipe_cache_chk">Önbelleği Temizle</string>
+		<string name="swipe_to_sideload">Sideload'u Başlatmak için Kaydır</string>
+		<string name="swipe_sideload">   Başlat</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">Kullanım: adb sideload dosyaadı.zip</string>
+		<string name="sideload_complete">ADB Sideload Tamamlandı</string>
+		<string name="fix_contexts_hdr">Bağlamları Düzelt</string>
+		<string name="fix_contexts_note1">Not: Bağlamları düzeltme nadiren gereklidir.</string>
+		<string name="fix_contexts_note2">SELinux Bağlamlarını düzeltmek,</string>
+		<string name="fix_contexts_note3">cihazınızın düzgün olarak önyükleme yapmamasına neden olabilir.</string>
+		<string name="swipe_to_fix_contexts">Bağlamları Düzeltmek için Kaydır</string>
+		<string name="swipe_fix_contexts">   Bağlamları Düzelt</string>
+		<string name="fixing_contexts">Bağlamlar Düzeltiliyor...</string>
+		<string name="fix_contexts_complete">Bağlamları Düzeltme Tamamlandı</string>
+		<string name="reboot_hdr">Yeniden Başlat</string>
+		<string name="install_cancel">Yükleme</string>
+		<string name="sel_storage_list">Depolama Seç</string>
+		<string name="ok_btn">Tamam</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">Kernel SELinux içeriklerini okuma desteğine sahip değil.</string>
+		<string name="full_selinux">Tam SELinux desteği mevcut.</string>
+		<string name="no_selinux">SELinux desteği yok (libselinux yok).</string>
+		<string name="mtp_enabled">MTP Etkin</string>
+		<string name="mtp_crash">MTP Çöktü, açılışta başlatılmıyor.</string>
+		<string name="decrypt_success">Varsayılan şifre ile çözüldü.</string>
+		<string name="unable_to_decrypt">Varsayılan şifre ile çözülemedi. Veri Biçimlendirmesi yapmanız gerekebilir.</string>
+		<string name="generating_digest1" version="2">Digest Oluşturuluyor</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * Digest Oluşturuluyor</string>
+		<string name="generating_digest2" version="2"> * Digest oluşturuluyor...</string>
+		<string name="digest_created" version="2"> * Digest Oluşturuldu.</string>
+		<string name="digest_error" version="2"> * Digest Hatası!</string>
+		<string name="digest_compute_error" version="2"> * Digest hesaplama hatası.</string>
+		<string name="current_date">(Geçerli Tarih)</string>
+		<string name="auto_generate">(Otomatik Oluştur)</string>
+		<string name="unable_to_locate_partition">Yedekleme hesaplamaları için \'{1}\' bölümü bulunamıyor.</string>
+		<string name="no_partition_selected">Yedekleme için bölüm seçilmedi.</string>
+		<string name="total_partitions_backup"> * Toplam yedeklenecek bölüm sayısı: {1}</string>
+		<string name="total_backup_size"> * Toplam veri boyutu: {1}MB</string>
+		<string name="available_space"> * Kullanılabilir alan: {1}MB</string>
+		<string name="unable_locate_storage">Depolama aygıtı bulunamıyor.</string>
+		<string name="no_space">Yeteri kadar boş alan yok.</string>
+		<string name="backup_started">[YEDEKLEME BAŞLATILDI]</string>
+		<string name="backup_folder"> * Yedekleme Klasörü: {1}</string>
+		<string name="fail_backup_folder">Yedekleme klasörü oluşturulamadı.</string>
+		<string name="avg_backup_fs">Dosya sistemleri için ortalama yedekleme hızı: {1} MB/sn</string>
+		<string name="avg_backup_img">Görüntülü sürücüler için ortalama yedekleme hızı: {1} MB/sn</string>
+		<string name="total_backed_size">[{1} MB YEDEKLENDİ]</string>
+		<string name="backup_completed">[YEDEKLEME {1} SANİYEDE TAMAMLANDI]</string>
+		<string name="restore_started">[GERİ YÜKLEME BAŞLATILDI]</string>
+		<string name="restore_folder">Geri yükleme klasörü: \'{1}\'</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} bitti ({2} saniye)]</string>
+		<string name="verifying_digest" version="2">Digest Doğrulanıyor</string>
+		<string name="skip_digest" version="2">Kullanıcı ayarına göre Digest denetimi geçiliyor.</string>
+		<string name="calc_restore">Geri yükleme ayrıntıları hesaplanıyor...</string>
+		<string name="restore_read_only">{1} geri yüklenemiyor -- salt okunur bağlanmış.</string>
+		<string name="restore_unable_locate">Geri yüklemek için \'{1}\' bölümü bulunamıyor.</string>
+		<string name="no_part_restore">Geri yüklemek için bölüm seçilmedi.</string>
+		<string name="restore_part_count">{1} bölüm geri yükleniyor...</string>
+		<string name="total_restore_size">Toplam geri yükleme boyutu {1}MB</string>
+		<string name="updating_system_details">Sistem Ayrıntıları Güncelleniyor</string>
+		<string name="restore_completed">[GERİ YÜKLEME {1} SANİYEDE TAMAMLANDI]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">Açma hatası: \'{1}\' ({2})</string>
+		<string name="unable_locate_part_backup_name">Bölüm yedek adına göre bulunamadı: \'{1}\'</string>
+		<string name="unable_find_part_path">\'{1}\' yolu için bölüm bulunamıyor</string>
+		<string name="update_part_details">Bölüm ayrıntıları güncelleniyor...</string>
+		<string name="update_part_details_done">...bitti</string>
+		<string name="wiping_dalvik">Dalvik Önbelleği Dizinleri Temizleniyor...</string>
+		<string name="cleaned">Temizlendi: {1}...</string>
+		<string name="dalvik_done">-- Dalvik Önbelleği Dizinleri Temizleme Tamamlandı!</string>
+		<string name="no_andsec">Android secure bölümü yok.</string>
+		<string name="unable_to_locate">{1} bulunamıyor.</string>
+		<string name="wiping_datamedia">Dahili depolama temizleniyor -- /data/media...</string>
+		<string name="unable_to_mount">{1} bağlanamadı</string>
+		<string name="unable_to_mount_internal">Dahili depolama bağlanamadı</string>
+		<string name="unable_to_mount_storage">Depolama bağlanamadı</string>
+		<string name="fail_decrypt">Verilerin şifresi çözülemedi.</string>
+		<string name="no_crypto_support">Şifreleme desteği bu yapı içine derlenmemiştir.</string>
+		<string name="decrypt_success_dev">Veri başarıyla çözüldü, yeni blok aygıtı: '{1}'</string>
+		<string name="decrypt_success_nodev">Veri başarıyla çözüldü</string>
+		<string name="done">Bitti.</string>
+		<string name="start_partition_sd">SD Kart Bölümleniyor...</string>
+		<string name="partition_sd_locate">Bölümlenecek cihaz bulunamadı.</string>
+		<string name="ext_swap_size">EXT + Swap boyutu sd kart boyutundan daha büyük.</string>
+		<string name="remove_part_table">Bölüm tablosu kaldırılıyor...</string>
+		<string name="unable_rm_part">Bölüm tablosu kaldırılamadı.</string>
+		<string name="create_part">{1} bölümü oluşturuluyor...</string>
+		<string name="unable_to_create_part">{1} bölümü oluşturulamadı.</string>
+		<string name="format_sdext_as">Harici SD, {1} olarak biçimlendiriliyor...</string>
+		<string name="part_complete">Bölümleme tamamlandı.</string>
+		<string name="unable_to_open">\'{1}\' açılamadı.</string>
+		<string name="mtp_already_enabled">MTP zaten etkin</string>
+		<string name="mtp_fail">MTP etkinleştirilemedi</string>
+		<string name="no_mtp">MTP desteği dahil değil</string>
+		<string name="image_flash_start">[İMAJ FLASHLAMA BAŞLATILDI]</string>
+		<string name="img_to_flash">Flashlanacak imaj: \'{1}\'</string>
+		<string name="flash_unable_locate">Flashlama için \'{1}\' bölümü bulunamıyor.</string>
+		<string name="no_part_flash">Flashlama için bölüm seçilmedi.</string>
+		<string name="too_many_flash">Flashlama için çok fazla bölüm seçildi.</string>
+		<string name="invalid_flash">Geçersiz flash bölümü belirtildi.</string>
+		<string name="flash_done">[İMAJ FLASHLAMA TAMAMLANDI]</string>
+		<string name="wiping">{1} temizleniyor</string>
+		<string name="repair_not_exist">{1} yok! Onarılamıyor!</string>
+		<string name="repairing_using">{2} kullanılarak {1} onarılıyor...</string>
+		<string name="unable_repair">{1} onarılamıyor.</string>
+		<string name="mount_data_footer">/data bağlanamadı ve kripto altbilgisi bulunamadı.</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">\'{1}\' klasörü oluşturulamıyor ({2}).</string>
+		<string name="fail_mount">\'{1}\' bağlanamıyor ({2})</string>
+		<string name="fail_unmount">\'{1}\' çıkarılamıyor ({2})</string>
+		<string name="cannot_resize">{1} yeniden boyutlandırılamıyor.</string>
+		<string name="repair_resize">Yeniden boyutlandırmadan önce {1} onarılıyor.</string>
+		<string name="unable_resize">{1} yeniden boyutlandırılamıyor.</string>
+		<string name="no_digest_found" version="2">'{1}' için hiçbir digest dosyası bulunamadı. Geri yüklemek için Digest doğrulamasını etkinleştir seçeneğinin işaretini kaldırın.</string>
+		<string name="digest_fail_match" version="2">'{1}' ile digest eşleşmedi.</string>
+		<string name="digest_matched" version="2">'{1}' için digest eşleşti.</string>
+		<string name="fail_decrypt_tar">'{1}' tar dosyasının şifresini çözmek başarısız oldu</string>
+		<string name="format_data_msg">/data'yı tekrar kullanabilmek için recovery'yi yeniden başlatmanız gerekebilir.</string>
+		<string name="format_data_err">Şifrelemeyi kaldırmak için biçimlendirme yapılamıyor.</string>
+		<string name="formatting_using">{2} kullanılarak {1} biçimlendiriliyor...</string>
+		<string name="unable_to_wipe">{1} temizlenemiyor.</string>
+		<string name="cannot_wipe">{1} bölümü temizlenemiyor.</string>
+		<string name="remove_all">\'{1}\' altındaki tüm dosyalar kaldırılıyor</string>
+		<string name="wiping_data">/data/media temizlenmeden data temizleniyor ...</string>
+		<string name="backing_up">{1} yedekleniyor...</string>
+		<string name="backup_storage_warning">{1} yedekleri dahili depolama biriminde resim veya indirme gibi herhangi bir dosya içermiyor.</string>
+		<string name="backing">Yedekleme</string>
+		<string name="backup_size">'{1}' için yedek dosyası boyutu 0 bayt.</string>
+		<string name="datamedia_fs_restore">UYARI: Bu /data yedekleme {1} dosya sistemi ile yapıldı! {1} olarak değiştirilmedikçe yedekleme önyükleme yapmayabilir.</string>
+		<string name="restoring">{1} geri yükleniyor...</string>
+		<string name="restoring_hdr">Geri yükleniyor</string>
+		<string name="recreate_folder_err">{1} klasörü yeniden oluşturulamadı.</string>
+		<string name="img_size_err">İmaj boyutu hedef cihazdan daha büyük</string>
+		<string name="flashing">{1} flashlanıyor...</string>
+		<string name="backup_folder_set">Yedekleme klasörü '{1}' olarak ayarlandı</string>
+		<string name="locate_backup_err">'{1}' yedeği bulunamadı</string>
+		<string name="set_restore_opt">Geri yükleme seçeneklerini ayarlama: '{1}':</string>
+		<string name="digest_check_skip" version="2">Digest denetimini atlama açık</string>
+		<string name="ors_encrypt_restore_err">Şifrelenmiş bir yedeklemeyi geri yüklemek için OpenRecoveryScript kullanılamıyor.</string>
+		<string name="mounting">Bağlanıyor</string>
+		<string name="unmounting">Kaldırılıyor</string>
+		<string name="mounted">'{1}' bağlandı</string>
+		<string name="unmounted">'{1}' kaldırıldı</string>
+		<string name="setting">'{2}' '{1}' olarak ayarlı</string>
+		<string name="setting_empty">'{1}' boş olarak ayarlı</string>
+		<string name="making_dir1">Dizin Oluşturuluyor</string>
+		<string name="making_dir2">Dizin oluşturuluyor: '{1}'</string>
+		<string name="running_command">Komut Çalıştırılıyor</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">ADB sideload  özelliği başlatılıyor...</string>
+		<string name="need_new_adb">Bu cihaza sideload eklemek için adb 1.0.32 veya daha yenisine ihtiyacınız var.</string>
+		<string name="no_pwd">Parola sağlanmadı.</string>
+		<string name="done_ors">Komut dosyası işleme tamamlandı</string>
+		<string name="injecttwrp">TWRP boot imajına ekleniyor...</string>
+		<string name="zip_err">'{1}' zip dosyası yüklenemiyor</string>
+		<string name="installing_zip">'{1}' zip dosyası yükleniyor</string>
+		<string name="select_backup_opt">Yedekleme seçeneklerini ayarlama:</string>
+		<string name="compression_on">Sıkıştırma açık</string>
+		<string name="digest_off" version="2">Digest Oluşturma kapalı</string>
+		<string name="backup_fail">Yedekleme Başarısız</string>
+		<string name="backup_clean">Yedekleme Başarısız Oldu. Yedekleme Klasörü Temizleniyor.</string>
+		<string name="running_recovery_commands">Recovery Komutları Çalıştırılıyor</string>
+		<string name="recovery_commands_complete">Recovery Komutları Tamamlandı</string>
+		<string name="running_ors">OpenRecoveryScript Çalıştırılıyor</string>
+		<string name="ors_complete">OpenRecoveryScript Tamamlandı</string>
+		<string name="check_for_digest" version="2">Digest dosyası denetleniyor...</string>
+		<string name="invalid_zip_format">Geçersiz zip dosyası biçimi!</string>
+		<string name="fail_sysmap">'{1}' dosyası eşleştirilemedi</string>
+		<string name="verify_zip_sig">Zip imzası doğrulanıyor....</string>
+		<string name="verify_zip_fail">Zip imzası doğrulama başarısız oldu!</string>
+		<string name="verify_zip_done">Zip imzası başarıyla doğrulandı.</string>
+		<string name="zip_corrupt">Zip dosyası bozuk!</string>
+		<string name="no_digest" version="2">Digest denetimi geçiliyor: Digest dosyası bulunamadı</string>
+		<string name="digest_fail" version="2">Digest eşleşmiyor</string>
+		<string name="digest_match" version="2">Digest eşleşti</string>
+		<string name="pid_signal">{1} process ended with signal: {2}</string>
+		<string name="pid_error">{1} process ended with ERROR: {2}</string>
+		<string name="install_dumlock">HTC Dumlock sisteme yükleniyor...</string>
+		<string name="dumlock_restore">Boot geri yükleniyor...</string>
+		<string name="dumlock_reflash">Boot için recovery yeniden flashlanıyor...</string>
+		<string name="run_script">{1} komut dosyası çalıştırılıyor...</string>
+		<string name="rename_stock">Stok ROM'un TWRP'yi değiştirmesini önlemek için /systemde stok kurtarma dosyası yeniden adlandırıldı.</string>
+		<string name="split_backup">Yedek dosyayı birden fazla arşiv haline getirme...</string>
+		<string name="backup_error">Yedekleme hatası oluştu.</string>
+		<string name="restore_error">Geri yükleme işlemi sırasında hata oluştu.</string>
+		<string name="split_thread">{1} İş parçacığı kimliğini, {2} arşiv olarak bölüyor.</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">%llu / %llu dosya, %i%%</string>
+		<string name="size_progress">%lluMB / %lluMB, %i%%</string>
+		<string name="decrypt_cmd">Komut satırıyla veri bölümünün şifresi çözmeye çalışılıyor.</string>
+		<string name="base_pkg_err">Temel paketler yüklenemedi.</string>
+		<string name="simulating">Eylemler taklit ediliyor...</string>
+		<string name="backup_cancel">Yedekleme İptal Edildi</string>
+		<string name="config_twrp">TWRP'yi yapılandırma...</string>
+		<string name="config_twrp_err">Bu kernel ile TWRP yapılandırılamadı.</string>
+		<string name="copy_log">Recovery kaydı şuraya kopyalandı: {1}.</string>
+		<string name="max_queue">Maksimum zip kuyruğuna ulaşıldı!</string>
+		<string name="min_queue">Minimum zip kuyruğuna ulaşıldı!</string>
+		<string name="screenshot_saved">Ekran görüntüsü şuraya kaydedildi: {1}</string>
+		<string name="screenshot_err">Ekran görüntüsü alınamadı!</string>
+		<string name="zip_wipe_cache">Bir veya daha fazla zip, bir önbellek silme isteğinde bulundu - Şimdi önbellek siliniyor.</string>
+		<string name="and_sec_wipe_err">Android secure silinemez</string>
+		<string name="dalvik_wipe_err">Dalvik silinemedi</string>
+		<string name="auto_gen">(Otomatik Oluştur)</string>
+		<string name="curr_date">(Geçerli Tarih)</string>
+		<string name="backup_name_len">Yedekleme adı çok uzun.</string>
+		<string name="backup_name_invalid">'{1}' yedek adı geçersiz karakter içeriyor: '{1}'</string>
+		<string name="no_real_sdcard">Bu cihazın gerçek bir SD kartı yok! İptal ediliyor!</string>
+		<string name="cancel_sideload">ADB sideload iptal ediliyor...</string>
+		<string name="change_fs_err">Dosya sistemi değiştirilemiyor.</string>
+		<string name="theme_ver_err">Özel tema sürümü TWRP sürümü ile eşleşmiyor. Stok tema kullanılıyor.</string>
+		<string name="up_a_level">(Üst Dizin)</string>
+		<string name="install_reboot" version="2">%tw_sleep% saniye içinde yeniden başlatılıyor</string>
+		<string name="adbbackup_error">ADB Yedekleme Hatası. Çıkılıyor..."</string>
+		<string name="adbbackup_control_error">Adb kontrol kanalına yazılamıyor</string>
+		<string name="twrp_adbbu_option">--twrp adb yedeklemeyi etkinleştirmek için twrp seçeneği gerekiyor</string>
+		<string name="partition_not_found">yol: {1} bölüm listesinde bulunamadı</string>
+		<string name="copy_kernel_log">Kernel kaydı şuraya kopyalandı: {1}</string>
+		<string name="include_kernel_log">Kernel Kaydını Dahil Et</string>
+		<string name="sha2_chk">Hash için SHA2 kullan</string>
+		<string name="unable_set_boot_slot">Önyükleyici önyükleme yuvasını {1} olarak değiştiremiyor</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/languages/uk.xml b/gui/theme/common/languages/uk.xml
new file mode 100644
index 0000000..d48310b
--- /dev/null
+++ b/gui/theme/common/languages/uk.xml
@@ -0,0 +1,664 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--Generated by crowdin.com-->
+
+<language>
+	<display>Українська</display>
+	<!-- Translated by Bukwaezhka and Jemmini, 2016 -->
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_m" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="font_s" type="fontoverride" filename="RobotoCondensed-Regular.ttf" scale="90" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansMono.ttf" scale="100" />
+		<!-- Partition display names -->
+		<string name="system">System</string>
+		<string name="system_image">System Image</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Vendor Image</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="sdcard">SDCard</string>
+		<string name="internal">Internal Storage</string>
+		<string name="microsd">Micro SDCard</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Adopted Data</string>
+		<string name="adopted_storage">Adopted Storage</string>
+		<!-- GUI XML strings -->
+		<string name="cpu_temp">Процесор: %tw_cpu_temp% &#xB0;C</string>
+		<string name="battery_pct">Батарея: %tw_battery%</string>
+		<string name="sort_by_name">Сортувати за назвою</string>
+		<string name="sort_by_date">Сортувати по даті</string>
+		<string name="sort_by_size">Сортувати за розміром</string>
+		<string name="sort_by_name_only">Назва</string>
+		<string name="sort_by_date_only">Дата</string>
+		<string name="sort_by_size_only">Розмір</string>
+		<string name="tab_general">ЗАГАЛЬНІ</string>
+		<string name="tab_options">ПАРАМЕТРИ</string>
+		<string name="tab_backup">РЕЗЕРВУВАННЯ</string>
+		<string name="tab_time_zone">ЧАСОВИЙ ПОЯС</string>
+		<string name="tab_screen">ЕКРАН</string>
+		<string name="tab_vibration">ВІБРАЦІЯ</string>
+		<string name="tab_language">МОВА</string>
+		<string name="install_btn">Встановити</string>
+		<string name="wipe_btn">Витерти</string>
+		<string name="backup_btn">Резервувати</string>
+		<string name="restore_btn">Відновити</string>
+		<string name="mount_btn">Монтувати</string>
+		<string name="settings_btn">Налаштування</string>
+		<string name="advanced_btn">Додатково</string>
+		<string name="reboot_btn">Перезавантажити</string>
+		<string name="files_btn">Файли</string>
+		<string name="copy_log_btn">Копіювати Log</string>
+		<string name="select_type_hdr">Вибрати тип</string>
+		<string name="install_zip_hdr">Встановити Zip</string>
+		<string name="install_zip_btn">Встановити Zip</string>
+		<string name="install_image_hdr">Встановити Img-образ</string>
+		<string name="install_image_btn">Встановити Img</string>
+		<string name="install_select_file_hdr">Вибрати файл</string>
+		<string name="file_selector_folders_hdr">Папки</string>
+		<string name="select_file_from_storage">Виберіть файл з %tw_storage_display_name% (%tw_storage_free_size% МБ)</string>
+		<string name="adb_sideload_btn">ADB завантаження</string>
+		<string name="install_hdr">Встановлення</string>
+		<string name="select_storage_hdr">Обрати сховище</string>
+		<string name="select_storage_btn">Обрати сховище</string>
+		<string name="queue_hdr">Черга</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% з максимум 10 файлів у черзі</string>
+		<string name="zip_queue_count_s">Файл %tw_zip_queue_count% з 10:</string>
+		<string name="zip_warn1">Ця операція може встановити несумісні</string>
+		<string name="zip_warn2">файли і зламати прошивку.</string>
+		<string name="zip_back_cancel">Натисніть Назад, щоб скасувати додавання цього zip.</string>
+		<string name="zip_back_clear">Натисніть Назад, щоб очистити чергу.</string>
+		<string name="folder">Папка:</string>
+		<string name="file">Файл:</string>
+		<string name="zip_sig_chk">Перевірка підпису Zip</string>
+		<string name="install_reboot_chk">Автоматичне перезавантаження після встановлення</string>
+		<string name="inject_twrp_chk">Інтегрувати TWRP після встановлення</string>
+		<string name="options_hdr">Параметри</string>
+		<string name="confirm_flash_hdr">Підтвердити Прошивку</string>
+		<string name="zip_queue">Черга:</string>
+		<string name="options">Параметри:</string>
+		<string name="swipe_confirm">   Підтвердити</string>
+		<string name="zip_add_btn">Додати ще Zip</string>
+		<string name="zip_clear_btn">Очистити чергу Zip</string>
+		<string name="install_zip_count_hdr">Встановлення Zip %tw_zip_index% з %tw_zip_queue_count%</string>
+		<string name="installing_zip_xml">Встановлення Zip: %tw_file%</string>
+		<string name="failed">Не вдалося</string>
+		<string name="successful">Успішно</string>
+		<string name="install_failed">Помилка встановлення</string>
+		<string name="install_successful">Успішно встановлено</string>
+		<string name="wipe_cache_dalvik_btn">Очистка cache/dalvik</string>
+		<string name="reboot_system_btn">Завантажити систему</string>
+		<string name="install_sel_target">Вибір розділу</string>
+		<string name="flash_image_select">Виберіть разділ для прошивки образу:</string>
+		<string name="target_partition">Цільовий розділ:</string>
+		<string name="flashing_image">Прошивка образу...</string>
+		<string name="image_flashed">Прошивка образу завершена</string>
+		<string name="wipe_cache_dalvik_confirm">Очистити Cache &amp; Dalvik?</string>
+		<string name="wiping_cache_dalvik">Очищення Cache &amp; Dalvik...</string>
+		<string name="wipe_cache_dalvik_complete">Очищення Cache &amp; Dalvik завершено</string>
+		<string name="swipe_wipe">Очистити</string>
+		<string name="swipe_wipe_s">   Очистка</string>
+		<string name="no_os1">Система не встановлена! Ви впевнені,</string>
+		<string name="no_osrb">що хочете перезавантажити?</string>
+		<string name="no_ospo">що хочете вимкнути?</string>
+		<string name="rebooting">Перезавантаження...</string>
+		<string name="swipe_reboot">Перезавантажити</string>
+		<string name="swipe_reboot_s">   Перезавантаження</string>
+		<string name="swipe_flash">Прошити</string>
+		<string name="confirm_action">Підтвердити</string>
+		<string name="back_cancel">Натисніть Назад щоб скасувати.</string>
+		<string name="cancel_btn">Скасувати</string>
+		<string name="wipe_hdr">Очистка</string>
+		<string name="factory_reset_hdr">Скинути до заводських налаштувань</string>
+		<string name="factory_reset_btn">Скинути налаштування</string>
+		<string name="factory_reset1">Очистка Data, Cache та Dalvik</string>
+		<string name="factory_reset2">(не включаючи внутрішню пам'ять)</string>
+		<string name="factory_reset3">У більшості випадків достатньо</string>
+		<string name="factory_reset4">очистити ці розділи.</string>
+		<string name="factory_resetting">Скидання до заводських налаштувань...</string>
+		<string name="advanced_wipe_hdr">Вибіркове очищення</string>
+		<string name="advanced_wipe_btn">Вибіркове очищення</string>
+		<string name="wipe_enc_confirm">Скинути Шифрування Data?</string>
+		<string name="formatting_data">Форматування Data...</string>
+		<string name="swipe_format_data">Форматувати Data</string>
+		<string name="swipe_format_data_s">   Форматування Data</string>
+		<string name="factory_reset_complete">Скидання до заводських налаштувань завершено</string>
+		<string name="sel_part_hdr">Вибір розділів</string>
+		<string name="wipe_sel_confirm">Очистити вибрані розділи?</string>
+		<string name="wiping_part">Очищення розділів...</string>
+		<string name="wipe_complete">Очищення завершено</string>
+		<string name="sel_part_wipe">Виберіть розділи для очищення:</string>
+		<string name="invalid_part_sel">Вибрано невірний розділ</string>
+		<string name="format_data_hdr">Форматувати Data</string>
+		<string name="format_data_btn">Форматувати Data</string>
+		<string name="format_data_ptr1">Форматування Data очистить всі ваші додатки,</string>
+		<string name="format_data_ptr2">резервні копії, фотографії, відео, медіафайли</string>
+		<string name="format_data_ptr3">та видалить шифровані файли з внутрішньої пам'яті.</string>
+		<string name="format_data_adopted">Включаючи пристосовану пам'ять</string>
+		<string name="format_data_lcp1">Форматування Data очистить всі ваші додатки, резервні копії, відео, медіафайли</string>
+		<string name="format_data_lcp2">та видалить шифровані файли з внутрішньої пам'яті.</string>
+		<string name="format_data_wtc1">Форматування Data очистить всі ваші додатки,</string>
+		<string name="format_data_wtc2">резервні копії та медіафайли. Скасування цього неможливе.</string>
+		<string name="format_data_undo">Скасування цього неможливе.</string>
+		<string name="format_data_complete">Форматування Data завершено</string>
+				<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">Введіть yes щоб продовжити. Натисніть Назад щоб скасувати.</string>
+		<string name="part_opt_hdr">Параметри розділу %tw_partition_name%</string>
+		<string name="sel_act_hdr">Оберіть дію</string>
+		<string name="file_sys_opt">Параметри файлової системи</string>
+		<string name="partition">Розділ: %tw_partition_name%</string>
+		<string name="part_mount_point">Точка монтування: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">Файлова система: %tw_partition_file_system%</string>
+		<string name="part_present_yes">Присутній: Так</string>
+		<string name="part_present_no">Присутній: Ні</string>
+		<string name="part_removable_yes">Знімний: так</string>
+		<string name="part_removable_no">Знімний: ні</string>
+		<string name="part_size">Розмір: %tw_partition_size%MБ</string>
+		<string name="part_used">Зайнято: %tw_partition_used%МБ</string>
+		<string name="part_free">Вільно: %tw_partition_free%МБ</string>
+		<string name="part_backup_size">Розмір РК: %tw_partition_backup_size%МБ</string>
+		<string name="resize_btn">Змінити розмір файлової системи</string>
+		<string name="resize_btn_s">Змінити розмір</string>
+		<string name="resize_confirm">Змінити розмір %tw_partition_name%?</string>
+		<string name="resizing">Зміна розміру...</string>
+		<string name="resize_complete">Зміна розміру завершена</string>
+		<string name="swipe_resize">Змінити</string>
+		<string name="swipe_resize_s">   Зміна розміру</string>
+		<string name="repair_btn">Відновити файлову систему</string>
+		<string name="repair_btn_s">Відновити</string>
+		<string name="repair_confirm">Відновити %tw_partition_name%?</string>
+		<string name="repairing">Відновлення...</string>
+		<string name="repair_complete">Відновлення завершено</string>
+		<string name="swipe_repair">Відновити</string>
+		<string name="swipe_repair_s">   Відновлення</string>
+		<string name="change_fs_btn">Змінити файлову систему</string>
+		<string name="change_fs_btn_s">Змінити</string>
+		<string name="change_fs_for_hdr">Змінити файлову систему для: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">Розділ: %tw_partition_name% &gt; виберіть файлову систему</string>
+		<string name="change_fs_warn1">Деякі прошивки або ядра можуть не підтримувати деякі</string>
+		<string name="change_fs_warn2">файлові системи. Користуйтесь обережно!</string>
+		<string name="change_fs_confirm">Змінити %tw_partition_name%?</string>
+		<string name="formatting">Форматування...</string>
+		<string name="format_complete">Форматування Завершено</string>
+		<string name="swipe_change_fs">Змінити</string>
+		<string name="swipe_change_s">   Зміна</string>
+		<string name="back_btn">Назад</string>
+		<string name="wipe_enc_btn">Стерти Шифрування</string>
+		<string name="swipe_factory_reset">Скинути</string>
+		<string name="repair_change_btn">Відновити або змінити Файлову Систему</string>
+		<string name="storage_hdr">Сховище: (%tw_storage_display_name% МБ)</string>
+		<string name="backup_hdr">Резервне копіювання</string>
+		<string name="backup_confirm_hdr">Підтвердження Резервування</string>
+		<string name="encryption_tab">ШИФРУВАННЯ</string>
+		<string name="encryption">Шифрування:</string>
+		<string name="name">Ім’я:</string>
+		<string name="sel_part_backup">Вибрати Розділ для Резервування:</string>
+		<string name="storage">Сховище:</string>
+		<string name="enc_disabled">вимкнено - встановіть пароль</string>
+		<string name="enc_enabled">увімкнено</string>
+		<string name="enable_backup_comp_chk">Увімкнути стискання</string>
+		<string name="skip_digest_backup_chk" version="2">Пропустити генерацію Digest під час Резервного Копіювання</string>
+		<string name="disable_backup_space_chk">Вимкнути перевірку вільного місця</string>
+		<string name="refresh_sizes_btn">Оновити розміри</string>
+		<string name="swipe_backup">Резервувати</string>
+		<string name="append_date_btn">Додати дату</string>
+		<string name="backup_name_exists">Резервна копія з такою назвою вже існує!</string>
+		<string name="encrypt_backup">Шифрувати резервну копію?</string>
+		<string name="enter_pass">Введіть пароль:</string>
+		<string name="enter_pass2">Введіть пароль ще раз:</string>
+		<string name="pass_not_match">Паролі не співпадають!</string>
+		<string name="partitions">Розділи:</string>
+		<string name="disabled">Вимкнено</string>
+		<string name="enabled">Увімкнено</string>
+		<string name="backup_name_hdr">Введіть назву для Резервної копії</string>
+		<string name="progress">Прогрес:</string>
+		<string name="backup_complete">Резервне копіювання завершено</string>
+		<string name="restore_hdr">Відновити</string>
+		<string name="sel_backup_hdr">Виберіть резервну копію</string>
+		<string name="restore_sel_store_hdr">Оберіть резервну копію з %tw_storage_display_name% (%tw_storage_free_size% МБ)</string>
+		<string name="restore_sel_pack_fs">Виберіть пакунок для відновлення:</string>
+		<string name="restore_enc_backup_hdr">Зашифрована Резервна копія</string>
+		<string name="restore_dec_fail">Неправильний пароль, будь ласка, спробуйте ще раз!</string>
+		<string name="del_backup_btn">Видалити резервну копію</string>
+		<string name="del_backup_confirm">Видалити резервну копію?</string>
+		<string name="del_backup_confirm2">Це неможливо скасувати!</string>
+		<string name="deleting_backup">Видалення резервної копії...</string>
+		<string name="backup_deleted">Видалення резервної копії завершено</string>
+		<string name="swipe_delete">Видалити</string>
+		<string name="swipe_delete_s">   Видалення</string>
+		<string name="restore_try_decrypt">Шифрована Резервна копія - спроба дешифрувати</string>
+		<string name="restore_try_decrypt_s">Спроба дешифрувати</string>
+		<string name="restore_backup_date">Резервна копія створена: %tw_restore_file_date%</string>
+		<string name="restore_sel_part">Виберіть розділ для відновлення:</string>
+		<string name="restore_enable_digest_chk" version="2">Увімкнути перевірку Digest для файлів резервних копій</string>
+		<string name="restore_complete">Відновлення завершено</string>
+		<string name="swipe_restore">Відновити</string>
+		<string name="swipe_restore_s">   Відновлення</string>
+		<string name="rename_backup_hdr">Перейменування резервної копії</string>
+		<string name="rename_backup_confirm">Перейменувати резервну копію?</string>
+		<string name="rename_backup_confirm2">Це неможливо скасувати!</string>
+		<string name="renaming_backup">Перейменування резервної копії...</string>
+		<string name="rename_backup_complete">Перейменування резервної копії завершено</string>
+		<string name="swipe_to_rename">Перейменувати</string>
+		<string name="swipe_rename">   Перейменування</string>
+		<string name="confirm_hdr">Підтвердити</string>
+		<string name="mount_hdr">Монтувати</string>
+		<string name="mount_sel_part">Виберіть розділи для монтування:</string>
+		<string name="mount_sys_ro_chk">Системний розділ тільки для читання</string>
+		<string name="mount_sys_ro_s_chk"> Система RO</string>
+		<string name="decrypt_data_btn">Розшифрувати Data</string>
+		<string name="disable_mtp_btn">Вимкнути MTP</string>
+		<string name="enable_mtp_btn">Увімкнути MTP</string>
+		<string name="mount_usb_storage_btn">Змонтувати USB</string>
+		<string name="usb_storage_hdr">USB накопичувач</string>
+		<string name="usb_stor_mnt1">USB накопичувач змонтований</string>
+		<string name="usb_stor_mnt2">Для безпечного від'єднання вашого пристрою</string>
+		<string name="usb_stor_mnt3">від компьютера виконайте розмонтування!</string>
+		<string name="unmount_btn">Розмонтувати</string>
+		<string name="rb_system_btn">Система</string>
+		<string name="rb_poweroff_btn">Вимкнення</string>
+		<string name="rb_recovery_btn">Рекавері</string>
+		<string name="rb_bootloader_btn">Завантажувач</string>
+		<string name="rb_download_btn">Режим завантаження</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">Вимкнення...</string>
+		<string name="swipe_power_off">Вимкнути</string>
+		<string name="swipe_power_off_s">Вимкнення</string>
+		<string name="sys_ro_hdr">Модифікація системного розділу</string>
+		<string name="sys_ro_keep">Залишити System тільки для читання?</string>
+		<string name="sys_rop1">TWRP може залишити системний розділ незмінним,</string>
+		<string name="sys_rop2">щоб зберегти можливість отримання офіційних оновлень.</string>
+		<string name="sys_rop3">TWRP не зможе попередити перезапис рекавері офіційною прошивкою </string>
+		<string name="sys_rop4">та не буде пропонувати отримати привілеї суперкористувача.</string>
+		<string name="sys_rop5">Встановлення zip та робота через adb зможе</string>
+		<string name="sys_rop6">модифікувати системний розділ.</string>
+		<string name="sys_rol1">TWRP може залишити системний розділ незмінним, щоб зберегти можливість отримання офіційних оновлень.</string>
+		<string name="sys_rol2">TWRP не зможе попредити перезапис рекавері офіційною прошивкою та не буде пропонувати отримати привілеї суперкористувача.</string>
+		<string name="sys_rol3">Встановлення zip та робота через adb зможе модифікувати системний розділ.</string>
+		<string name="sys_ro_never_show_chk">Більше не показувати при завантаженні</string>
+		<string name="sys_ro_keep_ro_btn">Тільки читання</string>
+		<string name="swipe_allow_mod">Дозволити зміни</string>
+		<string name="swipe_allow_mod_s">Дозволити зміни</string>
+		<string name="settings_hdr">Налаштування</string>
+		<string name="settings_gen_hdr">Загальні</string>
+		<string name="settings_gen_s_hdr">Загальні</string>
+		<string name="settings_gen_btn">Загальні</string>
+		<string name="use_rmrf_chk">Використовувати rm -rf замість форматування</string>
+		<string name="use24clock_chk">Використовувати 24-часовий годинник</string>
+		<string name="rev_navbar_chk">Віддзеркалити навігаційні клавіші</string>
+		<string name="simact_chk">Імітувати дії для тестування теми</string>
+		<string name="simfail_chk">Імітація відмови для дій</string>
+		<string name="ctr_navbar_rdo">Навігаційні кнопки по центру</string>
+		<string name="lft_navbar_rdo">Навігаційні кнопки зліва</string>
+		<string name="rht_navbar_rdo">Навігаційні кнопки зправа</string>
+		<string name="restore_defaults_btn">За замовчуванням</string>
+		<string name="settings_tz_btn">Часовий пояс</string>
+		<string name="settings_screen_btn">Екран</string>
+		<string name="settings_screen_bright_btn">Яскравість екрану</string>
+		<string name="settings_vibration_btn">Вібрація</string>
+		<string name="settings_language_btn">Мова</string>
+		<string name="time_zone_hdr">Часовий пояс</string>
+		<string name="sel_tz_list">Виберіть часовий пояс:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) о. Мидуей, Самоа</string>
+		<string name="utcm10">(UTC -10) Гаваї</string>
+		<string name="utcm9">(UTC -9) Аляска</string>
+		<string name="utcm8">(UTC -8) Тихоокеанський часовий пояс</string>
+		<string name="utcm7">(UTC -7) Гірський час (США і Канада), Аризона</string>
+		<string name="utcm6">(UTC -6) Центральний час (США і Канада), Центральна Америка</string>
+		<string name="utcm5">(UTC -5) Східний час (США і Канада), Індіана (схід)</string>
+		<string name="utcm4">(UTC -4) Атлантичний час (Канада), Каракас</string>
+		<string name="utcm3">(UTC -3) Бразилія, Буенос-Айрес</string>
+		<string name="utcm2">(UTC -2) Середньоатлантичний час</string>
+		<string name="utcm1">(UTC -1) Азорські о-ва, о-ва Зеленого мису</string>
+		<string name="utc0">(UTC  0) Лондон, Дублін, Лісабон</string>
+		<string name="utcp1">(UTC +1) Берлін, Брюсель, Париж</string>
+		<string name="utcp2">(UTC +2) Київ, Мінськ, Рига, Талін, Стамбул</string>
+		<string name="utcp3">(UTC +3) Москва, Санкт-Петербург, Волгоград, Багдад</string>
+		<string name="utcp4">(UTC +4) Баку, Ереван, Тбілісі, Абу-Дабі, Мускат</string>
+		<string name="utcp5">(UTC +5) Ташкент, Ісламабад, Карачі</string>
+		<string name="utcp6">(UTC +6) Алмати, Астана, Дхака, Катеринбург</string>
+		<string name="utcp7">(UTC +7) Омськ, Новосибірськ, Бангкок, Джакарта, Ханой</string>
+		<string name="utcp8">(UTC +8) Красноярськ, Пекін, Улан-Батор, Іркутськ</string>
+		<string name="utcp9">(UTC +9) Осака, Саппоро, Токіо, Сеул, Якутськ</string>
+		<string name="utcp10">(UTC +10) Гуам, Канберра, Мельбурн, Сідней</string>
+		<string name="utcp11">(UTC +11) Владивосток, Сахалін, Соломоновы о-ва</string>
+		<string name="utcp12">(UTC +12) Магадан, Камчатка, Фіджи, Маршалови о-ва</string>
+		<string name="sel_tz_offset">Вибір зміщення (як правило 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">Немає</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">Використовувати літній час (DST)</string>
+		<string name="curr_tz">Часовий пояс: %tw_time_zone%</string>
+		<string name="curr_tz_s">Часовий пояс:</string>
+		<string name="set_tz_btn">Встановити часовий пояс</string>
+		<string name="settings_screen_hdr">Екран</string>
+		<string name="settings_screen_timeout_hdr">Таймаут екрану</string>
+		<string name="enable_timeout_chk">Увімкнути таймаут екрану</string>
+		<string name="screen_to_slider">Таймаут екрану, сек:</string>
+		<string name="screen_to_slider_s">Таймаут екрану через, сек(0=вимкнений): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">Налаштування таймаута екрану недоступне</string>
+		<string name="screen_bright_slider">Яскравість: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">Налаштування яскравості недоступні</string>
+		<string name="vibration_hdr">Вібрація</string>
+		<string name="button_vibration_hdr">Натискання клавіш</string>
+		<string name="kb_vibration_hdr">Клавіатури</string>
+		<string name="act_vibration_hdr">При виборі дій</string>
+		<string name="button_vibration">Натискання клавіш:</string>
+		<string name="kb_vibration">Клавіатури:</string>
+		<string name="act_vibration">При виборі дій:</string>
+		<string name="select_language">Вибір мови:</string>
+		<string name="sel_lang_btn">Вибір мови</string>
+		<string name="set_language_btn">Встановити мову</string>
+		<string name="advanced_hdr">Додатково</string>
+		<string name="copy_log_confirm">Копіювати лог SD?</string>
+		<string name="copying_log">Копіювання логу на SD...</string>
+		<string name="copy_log_complete">Копіювання логу завершено</string>
+		<string name="fix_context_btn">Виправити контексти</string>
+		<string name="part_sd_btn">Розмітка SD Card</string>
+		<string name="part_sd_s_btn">SD Card</string>
+		<string name="file_manager_btn">Файл менеджер</string>
+		<string name="language_hdr">Мова</string>
+		<string name="terminal_btn">Термінал</string>
+		<string name="reload_theme_btn">Завантажити тему</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">Інтегрувати TWRP</string>
+		<string name="inject_twrp_confirm">Інтегрувати TWRP наново?</string>
+		<string name="injecting_twrp">Інтегрування TWRP...</string>
+		<string name="inject_twrp_complete">Інтегрування TWRP завершено</string>
+		<string name="swipe_to_confirm">Підтвердити</string>
+		<string name="part_sd_hdr">Розмітка SD Card</string>
+		<string name="invalid_partsd_sel">Виберіть знімний пристрій</string>
+		<string name="part_sd_lose">Всі ваші файли на SD карті будуть стерті!</string>
+		<string name="part_sd_undo">Це буде неможливо скасувати!</string>
+		<string name="part_sd_ext_sz">EXT розмір:</string>
+		<string name="part_sd_swap_sz">Swap розмір:</string>
+		<string name="file_system">Файлова система:</string>
+		<string name="swipe_part_sd">Розмітити</string>
+		<string name="swipe_part_sd_s">Розмітка</string>
+		<string name="partitioning_sd">Розмітка SD карти...</string>
+		<string name="partitioning_sd2">Це може зайняти кілька хвилин.</string>
+		<string name="part_sd_complete">Розмітка завершена</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">Відновлення оригінального Boot</string>
+		<string name="dumlock_restore_confirm">Відновити оригінальний образ Boot?</string>
+		<string name="dumlock_restoring">Відновлення оригінального Boot...</string>
+		<string name="dumlock_restore_complete">Відновлення оригінального Boot завершено</string>
+		<string name="dumlock_reflash_btn">Перепрошивка Recovery</string>
+		<string name="dumlock_reflash_confirm">Прошити recovery в boot?</string>
+		<string name="dumlock_reflashing">Прошивка recovery в boot...</string>
+		<string name="dumlock_reflash_complete">Прошивка recovery в boot завершена</string>
+		<string name="dumlock_install_btn">Встановлення HTC Dumlock</string>
+		<string name="dumlock_install_confirm">Встановити HTC dumlock до прошивки?</string>
+		<string name="dumlock_installing">Встановлення HTC Dumlock...</string>
+		<string name="dumlock_install_complete">Встановлення HTC Dumlock завершено</string>
+		<string name="swipe_to_unlock">Розблокувати</string>
+		<string name="swipe_unlock">   Разблокування</string>
+		<string name="fm_hdr">Файловий менеджер</string>
+		<string name="fm_sel_file">Вибір Файла або Папки</string>
+		<string name="fm_type_folder">Папка</string>
+		<string name="fm_type_file">Файл</string>
+		<string name="fm_choose_act">Вибір дії</string>
+		<string name="fm_selected">%tw_fm_type% обрано:</string>
+		<string name="fm_copy_btn">Копіювати</string>
+		<string name="fm_copy_file_btn">Копіювати Файл</string>
+		<string name="fm_copy_folder_btn">Копіювати Папку</string>
+		<string name="fm_copying">Копіювання</string>
+		<string name="fm_move_btn">Перемістити</string>
+		<string name="fm_moving">Переміщення</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">Видалити</string>
+		<string name="fm_deleting">Видалення</string>
+		<string name="fm_rename_btn">Перейменувати</string>
+		<string name="fm_rename_file_btn">Перейменувати Файл</string>
+		<string name="fm_rename_folder_btn">Перейменувати Папку</string>
+		<string name="fm_renaming">Перейменування</string>
+		<string name="fm_sel_dest">Обрати цільову Папку</string>
+		<string name="fm_sel_curr_folder">Обрати поточну Папку</string>
+		<string name="fm_rename_hdr">Перейменувати</string>
+		<string name="fm_set_perms_hdr">Встановити дозволи</string>
+		<string name="fm_perms">Дозволи:</string>
+		<string name="fm_complete">Операція завершена</string>
+		<string name="decrypt_data_hdr">Розшифрувати Data</string>
+		<string name="decrypt_data_enter_pass">Введіть пароль.</string>
+		<string name="decrypt_data_failed">Пароль неправильний, спробуйте ще!</string>
+		<string name="decrypt_data_failed_pattern">Шаблон неправильний, спробуйте ще!</string>
+		<string name="decrypt_data_enter_pattern">Введіть шаблон</string>
+		<string name="decrypt_data_trying">Спроба розшифровки</string>
+		<string name="term_hdr">Команди терміналу</string>
+		<string name="term_s_hdr">Термінал</string>
+		<string name="term_kill_btn">ВБИТИ</string>
+		<string name="term_sel_folder_hdr">Перейти до початкової папки</string>
+		<string name="adb_sideload_hdr">ADB завантаження</string>
+		<string name="sideload_wipe_dalvik_chk">Очистка Dalvik Cache</string>
+		<string name="sideload_wipe_cache_chk">Очистка Cache</string>
+		<string name="swipe_to_sideload">Почати завантаження</string>
+		<string name="swipe_sideload">   Почати</string>
+		<string name="sideload_confirm">ADB завантаження</string>
+		<string name="sideload_usage">Приклад: adb sideload filename.zip</string>
+		<string name="sideload_complete">ADB завантаження завершено</string>
+		<string name="fix_contexts_hdr">Виправлення контекста SELinux</string>
+		<string name="fix_contexts_note1">Примітка: Виправлення контексту рідко потребується.</string>
+		<string name="fix_contexts_note2">Виправлення SELinux контексту може призвести</string>
+		<string name="fix_contexts_note3">до того, що система не запуститься.</string>
+		<string name="swipe_to_fix_contexts">Виправити контексти</string>
+		<string name="swipe_fix_contexts">   Виправлення контексту</string>
+		<string name="fixing_contexts">Виправлення контексту...</string>
+		<string name="fix_contexts_complete">Виправлення контексту завершено</string>
+		<string name="reboot_hdr">Перезавантаження</string>
+		<string name="install_cancel">Не встановлювати</string>
+		<string name="sel_storage_list">Вибір сховища</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">Ядро не підтримує читання SELinux контексту.</string>
+		<string name="full_selinux">Присутня повна підтримка SELinux.</string>
+		<string name="no_selinux">Відсутня підтримка SELinux (немає libselinux).</string>
+		<string name="mtp_enabled">MTP Увімкнено</string>
+		<string name="mtp_crash">Збій MTP: MTP не запущений при завантаженні.</string>
+		<string name="decrypt_success">Розшифровка виконана успішно з паролем за замовчуванням.</string>
+		<string name="unable_to_decrypt">Неможливо розшифрувати з паролем за замовчуванням. Можливо буде потрібно форматувати Data.</string>
+		<string name="generating_digest1" version="2">Генерація Digest</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * Генерація Digest...</string>
+		<string name="digest_created" version="2"> * Digest сгенеровано.</string>
+		<string name="digest_error" version="2"> * Digest Помилка!</string>
+		<string name="digest_compute_error" version="2"> * Помилка розрахунку Digest.</string>
+		<string name="current_date">(Поточна дата)</string>
+		<string name="auto_generate">(автогенерація)</string>
+		<string name="unable_to_locate_partition">Не вдається знайти '{1}' розділ для розрахунку копіювання.</string>
+		<string name="no_partition_selected">Не обрані розділи для резервного копіювання.</string>
+		<string name="total_partitions_backup"> * Загальна кількість розділів для резервного копіювання: {1}</string>
+		<string name="total_backup_size"> * Загальний об'єм даних: {1}MБ</string>
+		<string name="available_space"> * Доступний об'єм: {1}MБ</string>
+		<string name="unable_locate_storage">Не вдається знайти сховище для зберігання.</string>
+		<string name="no_space">У сховищі недостатньо місця.</string>
+		<string name="backup_started">[РОЗПОЧАТО РЕЗЕРВНЕ КОПІЮВАННЯ]</string>
+		<string name="backup_folder"> * Папка для резервної копії: {1}</string>
+		<string name="fail_backup_folder">Невдається створити папку для резервної копії.</string>
+		<string name="avg_backup_fs">Середня швидкість копіювання файлів: {1} МБ/сек</string>
+		<string name="avg_backup_img">Середня швидкість копіювання образів: {1} МБ/сек</string>
+		<string name="total_backed_size">[{1} MБ ВСЬОГО ЗАРЕЗЕРВОВАНО]</string>
+		<string name="backup_completed">[КОПІЮВАННЯ ВИКОНАНО ЗА {1} СЕКУНД(И)]</string>
+		<string name="restore_started">[ВІДНОВЛЕННЯ РОЗПОЧАТО]</string>
+		<string name="restore_folder">Папка відновлення: '{1}'</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} завершено за {2} секунд(и)]</string>
+		<string name="verifying_digest" version="2">Перевірка Digest</string>
+		<string name="skip_digest" version="2">Пропуск перевірки Digest згідно системним налаштуванням.</string>
+		<string name="calc_restore">Розрахування інформації о відновленні...</string>
+		<string name="restore_read_only">Неможливо відновити {1} -- змонтовано тільки для читання.</string>
+		<string name="restore_unable_locate">Невдається знайти розділ '{1}' для відновлення.</string>
+		<string name="no_part_restore">Не обрані розділи для відновлення.</string>
+		<string name="restore_part_count">Відновлення розділу {1} ...</string>
+		<string name="total_restore_size">Загальний обсяг відновлення {1}MБ</string>
+		<string name="updating_system_details">Оновлення інформації о системі</string>
+		<string name="restore_completed">[ВІДНОВЛЕННЯ ЗАВЕРШЕНО ЗА {1} СЕКУНД(И)]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">Помилка відкриття: '{1}' ({2})</string>
+		<string name="unable_locate_part_backup_name">Невдається знайти розділ для копії: '{1}'</string>
+		<string name="unable_find_part_path">Невдається знайти розділ за шляхом '{1}'</string>
+		<string name="update_part_details">Оновлення інформації о розділах...</string>
+		<string name="update_part_details_done">...готово</string>
+		<string name="wiping_dalvik">Очищення директорій Dalvik Cache...</string>
+		<string name="cleaned">Очищено: {1}...</string>
+		<string name="dalvik_done">-- Очищення директорій Dalvik Cache завершено!</string>
+		<string name="no_andsec">Розділи android secure не знайдені.</string>
+		<string name="unable_to_locate">Не знайдено {1}.</string>
+		<string name="wiping_datamedia">Очищення внутрішньої пам'яті -- /data/media...</string>
+		<string name="unable_to_mount">Не вдається змонтувати {1}</string>
+		<string name="unable_to_mount_internal">Не вдається змонтувати internal_storage</string>
+		<string name="unable_to_mount_storage">Не вдається змонтувати storage</string>
+		<string name="fail_decrypt">Не вдається розшифрувати data.</string>
+		<string name="no_crypto_support">Шифрування не підтримується у цій версії.</string>
+		<string name="decrypt_success_dev">Data успішно розшифрований, новий блочний пристрій: '{1}'</string>
+		<string name="done">Готово.</string>
+		<string name="start_partition_sd">Розмітка SD Card...</string>
+		<string name="partition_sd_locate">Не вдалось знайти пристрій для розмітки.</string>
+		<string name="ext_swap_size">Розмір EXT + Swap перевищує розмір sdcard.</string>
+		<string name="remove_part_table">Видалення таблиці розділів...</string>
+		<string name="unable_rm_part">Не вдалось видалити таблицю розділів.</string>
+		<string name="create_part">Створення розділу {1} ...</string>
+		<string name="unable_to_create_part">Не вдалось створити розділ {1} .</string>
+		<string name="format_sdext_as">Форматування sd-ext у {1}...</string>
+		<string name="part_complete">Розмітка завершена.</string>
+		<string name="unable_to_open">Не вдалось відкрити '{1}'.</string>
+		<string name="mtp_already_enabled">MTP вже увімкнуто</string>
+		<string name="mtp_fail">Не вдалося увімкнути MTP</string>
+		<string name="no_mtp">Підтримка MTP відсутня</string>
+		<string name="image_flash_start">[РОЗПОЧАТО ПРОШИВКУ ОБРАЗУ]</string>
+		<string name="img_to_flash">Образ для прошивки: '{1}'</string>
+		<string name="flash_unable_locate">Не вдається знайти розділ '{1}' для прошивки.</string>
+		<string name="no_part_flash">Не обрані розділи для прошивки.</string>
+		<string name="too_many_flash">Забагато розділів для прошивки.</string>
+		<string name="invalid_flash">Вказан хибний розділ для прошивки.</string>
+		<string name="flash_done">[ПРОШИВКУ РОЗДІЛУ ЗАВЕРШЕНО]</string>
+		<string name="wiping">Очистка {1}</string>
+		<string name="repair_not_exist">{1} не існує! Неможливо відновити!</string>
+		<string name="repairing_using">Відновлення {1} з використанням {2}...</string>
+		<string name="unable_repair">Неможливо відновити {1}.</string>
+		<string name="mount_data_footer">Не вдалось змонтувати /data, не знайдено крипто-ключ.</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">Не вдалось створити папку '{1}' ({2}).</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">Не вдалось змонтувати '{1}' ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">Не вдалось розмонтувати '{1}' ({2})</string>
+		<string name="cannot_resize">Не вдалось розмітити {1}.</string>
+		<string name="repair_resize">Відновлення {1} перед розміткою.</string>
+		<string name="unable_resize">Не вдалось розмітити {1}.</string>
+		<string name="no_digest_found" version="2">Відсутній файл md5 для '{1}'. Будь ласка зніміть вибір з перевірки Digest при відновленні.</string>
+		<string name="digest_fail_match" version="2">Digest не співпадає для '{1}'.</string>
+		<string name="fail_decrypt_tar">Не вдалось розшифрувати tar-файл '{1}'</string>
+		<string name="format_data_msg">Можливо вам доведеться перезавантажити рекавері, щоб мати можливість доступу до /data знову.</string>
+		<string name="format_data_err">Неможливо відформатувати, щоб видалити шифрування.</string>
+		<string name="formatting_using">Форматування {1} з використанням {2}...</string>
+		<string name="unable_to_wipe">Неможливо очистити {1}.</string>
+		<string name="cannot_wipe">Розділ {1} не може бути очищений.</string>
+		<string name="remove_all">Видалення всіх файлів з '{1}'</string>
+		<string name="wiping_data">Очистка data без очистки /data/media ...</string>
+		<string name="backing_up">Резервування {1}...</string>
+		<string name="backing">Резервне копіювання</string>
+		<string name="backup_size">Розмір файлу резервної копії для '{1}' 0 байт.</string>
+		<string name="datamedia_fs_restore">УВАГА: Ця резервна копія /data була зроблена з файловою системою {1}! Резервна копія може не завантажитись, якщо ви не повернетесь на {1}.</string>
+		<string name="restoring">Відновлення {1}...</string>
+		<string name="restoring_hdr">Відновлення</string>
+		<string name="recreate_folder_err">Неможливо створити папку {1} наново.</string>
+		<string name="img_size_err">Розмір образу більший за цільовий пристрій</string>
+		<string name="flashing">Прошивка {1}...</string>
+		<string name="backup_folder_set">Для резервного копіювавння встановлена папка '{1}'</string>
+		<string name="locate_backup_err">Не вдається знайти резервну копію '{1}'</string>
+		<string name="set_restore_opt">Установки опцій відновлення: '{1}':</string>
+		<string name="digest_check_skip" version="2">Пропуск перевірки Digest увімкнено</string>
+		<string name="ors_encrypt_restore_err">Не вдалось використати OpenRecoveryScript для відновлення зашифрованої резервної копії.</string>
+		<string name="mounting">Монтування</string>
+		<string name="unmounting">Розмонтування</string>
+		<string name="mounted">Змонтовано '{1}'</string>
+		<string name="unmounted">Розмонтоване '{1}'</string>
+		<string name="setting">Установка '{1}' у '{2}'</string>
+		<string name="setting_empty">Установка '{1}' в нікуди</string>
+		<string name="making_dir1">Створення директорії</string>
+		<string name="making_dir2">Створення директорії: '{1}'</string>
+		<string name="running_command">Запуск команди</string>
+		<string name="sideload">ADB завантаження</string>
+		<string name="start_sideload">Запуск ADB завантаження...</string>
+		<string name="need_new_adb">Вам необхідний adb 1.0.32 чи новіший для завантаження цього пристрою.</string>
+		<string name="no_pwd">Пароль не надано.</string>
+		<string name="done_ors">Завершено виконання файлу сценарію</string>
+		<string name="injecttwrp">Введення TWRP до образу boot...</string>
+		<string name="zip_err">Помилка встановлення zip файлу '{1}'</string>
+		<string name="installing_zip">Встановлення zip файлу '{1}'</string>
+		<string name="select_backup_opt">Налаштування резервного копіювання:</string>
+		<string name="compression_on">Стискання увімкнено</string>
+		<string name="digest_off" version="2">Генерація Digest вимкнена</string>
+		<string name="backup_fail">Помилка резервного копіювання</string>
+		<string name="backup_clean">Помилка резервного копіювання. Очистка папки резервного копіювання.</string>
+		<string name="running_recovery_commands">Запуск команд Recovery</string>
+		<string name="recovery_commands_complete">Команди Recovery виконані</string>
+		<string name="running_ors">Виконання OpenRecoveryScript</string>
+		<string name="ors_complete">OpenRecoveryScript виконано</string>
+		<string name="no_updater_binary">Не вдається знайти '{1}' у zip файлі.</string>
+		<string name="check_for_digest" version="2">Перевіка Digest файлу...</string>
+		<string name="verify_zip_sig">Перевірка підпису zip...</string>
+		<string name="verify_zip_fail">Підпис Zip не пройшов перевірку!</string>
+		<string name="verify_zip_done">Підпис Zip успішно перевірено.</string>
+		<string name="zip_corrupt">Zip файл пошкоджено!</string>
+		<string name="no_digest" version="2">Пропуск перевірки Digest: не знайдено MD5 файл</string>
+		<string name="digest_fail" version="2">Digest не співпадає</string>
+		<string name="digest_match" version="2">Digest співпадає</string>
+		<string name="pid_signal">{1} процес завершився з кодом: {2}</string>
+		<string name="pid_error">{1} процес завершився з ПОМИЛКОЮ: {2}</string>
+		<string name="install_dumlock">Встановлення HTC Dumlock у систему...</string>
+		<string name="dumlock_restore">Відновлення оригінального boot...</string>
+		<string name="dumlock_reflash">Прошивка recovery до boot...</string>
+		<string name="run_script">Виконання сценарію {1}...</string>
+		<string name="rename_stock">Перейменування файлу стокового рекавері у /system для попередження відновлення стокового замість TWRP.</string>
+		<string name="split_backup">Розбиття резервної копії на частини...</string>
+		<string name="backup_error">Помилка створення резервної копії.</string>
+		<string name="restore_error">Помилка під час відновлення.</string>
+		<string name="split_thread">Розщеплення ID {1} в архіві {2}</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">%llu з %llu файлів, %i%%</string>
+		<string name="size_progress">%llu МБ з %llu МБ, %i%%</string>
+		<string name="decrypt_cmd">Спроба розшифрувати розділ data за допомогою командного рядка.</string>
+		<string name="base_pkg_err">Невдача при завантаженні базових пакетів.</string>
+		<string name="simulating">Імітування дій...</string>
+		<string name="backup_cancel">Резервне копіювання перервано</string>
+		<string name="config_twrp">Конфігурування TWRP...</string>
+		<string name="config_twrp_err">Неможливо зконфігурувати TWRP з цим ядром.</string>
+		<string name="copy_log">recovery.log скопійовано до {1}.</string>
+		<string name="max_queue">Досягнута максимальна черга zip!</string>
+		<string name="min_queue">Досягнута мінімальна черга zip!</string>
+		<string name="screenshot_saved">Знімок екрану збережено до {1}</string>
+		<string name="screenshot_err">Не вдалось зробити знімок екрану!</string>
+		<string name="zip_wipe_cache">Один чи декілька zip запросили очистку cache -- Очистка cache.</string>
+		<string name="and_sec_wipe_err">Не вдалось очисти android secure</string>
+		<string name="dalvik_wipe_err">Не вдалось очистити dalvik</string>
+		<string name="auto_gen">(Автогенерація)</string>
+		<string name="curr_date">(Поточна дата)</string>
+		<string name="backup_name_len">Ім'я резервної копії надто довге.</string>
+		<string name="backup_name_invalid">Ім'я резервної копії '{1}' має недопустимі символи: '{1}'</string>
+		<string name="no_real_sdcard">Цей пристрій не має реальної SD карти! Відміна!</string>
+		<string name="cancel_sideload">Відміна ADB завантаження...</string>
+		<string name="change_fs_err">Помилка зміни файлової системи.</string>
+		<string name="theme_ver_err">Версія теми не збігається з версією TWRP. Використовується тема за замовченням.</string>
+		<string name="install_reboot">Пристрій буде перезавантажений через 5 секунд.</string>
+		<string name="up_a_level">(на рівень уверх)</string>
+		<string name="adbbackup_error">Помилка ADB копіювання. Вихід..."</string>
+		<string name="adbbackup_control_error">Не вдається запис у adb канал контролю</string>
+		<string name="twrp_adbbu_option">--опція twrp потребує увімкнення adb копіювання</string>
+		<string name="partition_not_found">шлях: {1} не знайдено у списку розділів</string>
+		<string name="copy_kernel_log">Копіювання kernel log у {1}</string>
+		<string name="include_kernel_log">Увімкнути Kernel Log</string>
+	</resources>
+</language>
diff --git a/gui/theme/common/portrait.xml b/gui/theme/common/portrait.xml
new file mode 100755
index 0000000..db8ea38
--- /dev/null
+++ b/gui/theme/common/portrait.xml
@@ -0,0 +1,5912 @@
+<?xml version="1.0"?>
+<recovery>
+	<styles>
+		<style name="text_l">
+			<font resource="font_l" color="%text_color%"/>
+		</style>
+
+		<style name="text_m">
+			<font resource="font_m" color="%text_color%"/>
+		</style>
+
+		<style name="text_m_accent">
+			<font resource="font_m" color="%accent_color%"/>
+		</style>
+
+		<style name="text_m_fail">
+			<font resource="font_m" color="%text_fail_color%"/>
+		</style>
+
+		<style name="text_s">
+			<font resource="font_s" color="%text_color%"/>
+		</style>
+
+		<style name="input">
+			<background color="%background_color%"/>
+			<cursor color="%accent_color%" hasfocus="1" width="%input_line_width%"/>
+			<font resource="font_m" color="%text_color%"/>
+		</style>
+
+		<style name="checkbox">
+			<font resource="font_m" color="%text_color%"/>
+			<image checked="checkbox_true" unchecked="checkbox_false"/>
+		</style>
+
+		<style name="main_button">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_l" color="%text_button_color%"/>
+			<image resource="main_button"/>
+		</style>
+
+		<style name="main_button_half_height">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="main_button_half_height"/>
+		</style>
+
+		<style name="main_button_half_height_full_width">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="main_button_half_height_full_width"/>
+		</style>
+
+		<style name="button_third_width">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="tab_3"/>
+		</style>
+
+		<style name="button_quarter_width">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="tab_4"/>
+		</style>
+
+		<style name="fab">
+			<highlight color="%highlight_color%"/>
+			<placement x="%indent_right%" y="%row21a_y%" placement="1"/>
+			<font resource="font_m" color="%button_text_color%"/>
+		</style>
+
+		<style name="tab">
+			<highlight color="%highlight_color%"/>
+			<fill color="%transparent%"/>
+			<font resource="font_s" color="%text_button_color%"/>
+		</style>
+
+		<style name="console">
+			<fastscroll rectcolor="%accent_color%" w="%fastscroll_w%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<color foreground="%text_color%" background="%background_color%" scroll="%background_color%"/>
+			<font resource="fixed"/>
+		</style>
+
+		<style name="terminal">
+			<fastscroll linecolor="%transparent%" rectcolor="%accent_color%" w="%fastscroll_w%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<background color="%background_color%"/>
+			<font resource="fixed" spacing="3" color="%text_color%"/>
+		</style>
+
+		<style name="fileselector">
+			<highlight color="%fileselector_highlight_color%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<sort name="tw_gui_sort_order"/>
+			<icon folder="folder" file="file"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="listbox">
+			<highlight color="%fileselector_highlight_color%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<icon selected="radio_true" unselected="radio_false"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="scrolllist">
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+		</style>
+
+		<style name="partitionlist">
+			<highlight color="%fileselector_highlight_color%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<icon selected="checkbox_true" unselected="checkbox_false"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%partitionlist_spacing%" color="%text_color%" highlightcolor="%fileselector_highlight_font_color%"/>
+		</style>
+
+		<style name="partitionlist_storage">
+			<highlight color="%fileselector_highlight_color%"/>
+			<placement x="%col1_x_left%" y="%row5_y%" w="%content_overlay_width%" h="%partitionlist_storage_height%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<icon selected="radio_true" unselected="radio_false"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%partitionlist_spacing%" color="%text_color%" highlightcolor="%fileselector_highlight_font_color%"/>
+		</style>
+
+		<style name="advanced_listbox">
+			<highlight color="%fileselector_highlight_color%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<icon selected="handle" unselected="handle"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="options_listbox">
+			<highlight color="%fileselector_highlight_color%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%background_color%" height="%fileselector_separatorheight%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="slider">
+			<placement x="%center_x%" y="%row23_y%"/>
+			<font resource="font_s" color="%text_color%"/>
+			<resource base="slider" used="slider_used" touch="slider_touch"/>
+		</style>
+
+		<style name="slidervalue">
+			<resource handle="handle"/>
+			<font resource="font_m" color="%text_color%"/>
+			<colors line="%fileselector_linecolor%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+		</style>
+
+		<style name="patternpassword">
+			<size name="tw_gui_pattern_grid_size" default="3"/>
+		</style>
+	</styles>
+
+	<pages>
+		<page name="main">
+			<action>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="main2">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@twrp_header=Team Win Recovery Project}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_version%</text>
+			</text>
+
+			<button style="main_button">
+				<placement x="%indent%" y="%row2a_y%"/>
+				<text>{@install_btn=Install}</text>
+				<actions>
+					<action function="queueclear"/>
+					<action function="set">tw_selectimage=0</action>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<placement x="%center_x%" y="%row2a_y%"/>
+				<text>{@wipe_btn=Wipe}</text>
+				<action function="page">wipe</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%indent%" y="%row8_y%"/>
+				<text>{@backup_btn=Backup}</text>
+				<action function="page">backup</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%center_x%" y="%row8_y%"/>
+				<text>{@restore_btn=Restore}</text>
+				<action function="page">restore</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%indent%" y="%row13a_y%"/>
+				<text>{@mount_btn=Mount}</text>
+				<action function="page">mount</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%center_x%" y="%row13a_y%"/>
+				<text>{@settings_btn=Settings}</text>
+				<action function="page">settings</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%indent%" y="%row19_y%"/>
+				<text>{@advanced_btn=Advanced}</text>
+				<action function="page">advanced</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%center_x%" y="%row19_y%"/>
+				<text>{@reboot_btn=Reboot}</text>
+				<action function="page">reboot</action>
+			</button>
+		</page>
+
+		<page name="install">
+			<template name="page"/>
+
+			<text style="text_l">
+				<condition var1="tw_selectimage" var2="0"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip}</text>
+			</text>
+
+			<text style="text_l">
+				<condition var1="tw_selectimage" var2="1"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_image_hdr=Install Image}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@select_file_from_storage=Select File from %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<condition var1="tw_selectimage" var2="0"/>
+				<placement x="%indent%" y="%row3_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>%tw_zip_location%</text>
+				<filter extn=".zip;.ozip;.ZIP;.OZIP" folders="1" files="1"/>
+				<prfxfilter prfx="Magisk-;Magisk.apk;app-release.apk;app-debug.apk" folders="1" files="1"/>
+				<path name="tw_zip_location" default="/sdcard"/>
+				<data name="tw_filename"/>
+				<selection name="tw_file"/>
+			</fileselector>
+
+			<fileselector>
+				<condition var1="tw_selectimage" var2="1"/>
+				<placement x="%indent%" y="%row3_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>%tw_zip_location%</text>
+				<filter extn=".img" folders="1" files="1"/>
+				<path name="tw_zip_location" default="/sdcard"/>
+				<data name="tw_filename"/>
+				<selection name="tw_file"/>
+			</fileselector>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=install</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_selectimage" var2="0"/>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@install_image_btn=Install Image}</text>
+				<actions>
+					<action function="set">tw_selectimage=1</action>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_selectimage" var2="1"/>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@install_zip_btn=Install Zip}</text>
+				<actions>
+					<action function="set">tw_selectimage=0</action>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<action>
+				<conditions>
+					<condition var1="tw_selectimage" var2="0"/>
+					<condition var1="tw_filename" op="modified"/>
+				</conditions>
+				<actions>
+					<action function="queuezip"/>
+					<action function="page">flash_confirm</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_selectimage" var2="1"/>
+					<condition var1="tw_filename" op="modified"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_is_slot_part=0</action>
+					<action function="page">flashimage_confirm</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="flash_confirm">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@zip_queue_count=%tw_zip_queue_count% of max of 10 Files queued}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@zip_warn1=This operation may install incompatible}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@zip_warn2=software and render your device unusable.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@zip_back_cancel=Press back to cancel adding this zip.}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row7_y%"/>
+				<text>{@folder=Folder:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row8_y%"/>
+				<text>%tw_zip_location%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row9a_y%"/>
+				<text>{@file=File:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row10a_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<listbox style="options_listbox">
+				<placement x="%indent%" y="%row12_y%" w="%content_width%" h="%listbox_options_height%"/>
+				<icon selected="checkbox_true" unselected="checkbox_false"/>
+				<listitem name="{@zip_sig_chk=Zip signature verification}">
+					<data variable="tw_signed_zip_verify"/>
+				</listitem>
+				<listitem name="{@skip_digest_zip_chk=Skip Digest check before installing zip}">
+					<data variable="tw_skip_digest_check_zip"/>
+				</listitem>
+				<listitem name="{@auto_reflashtwrp_chk=Automatically Reflash TWRP after flashing a ROM}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<data variable="tw_auto_reflashtwrp"/>
+				</listitem>
+				<listitem name="{@install_reboot_chk=Reboot after installation is complete}">
+					<data variable="tw_install_reboot"/>
+				</listitem>
+				<listitem name="{@inject_twrp_chk=Inject TWRP after install}">
+					<condition var1="tw_has_injecttwrp" var2="1"/>
+					<data variable="tw_inject_after_zip"/>
+				</listitem>
+			</listbox>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@zip_add_btn=Add more Zips}</text>
+				<action function="page">install</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row18a_y%"/>
+				<text>{@zip_clear_btn=Clear Zip Queue}</text>
+				<actions>
+					<action function="queueclear"/>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_flash=Swipe to confirm Flash}</text>
+				<action function="flash">flash_zip</action>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="cancelzip"/>
+					<action function="page">install</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="flash_zip">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_zip_count_hdr=Install Zip %tw_zip_index% of %tw_zip_queue_count%}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<action function="page">flash_done</action>
+			</action>
+		</page>
+
+		<page name="flash_done">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_operation_status" op="!=" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@failed=Failed}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_operation_status" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@successful=Successful}</text>
+			</text>
+
+			<template name="console"/>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_ab_device" var2="0"/>
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@wipe_cache_dalvik_btn=Wipe Cache/Dalvik}</text>
+				<actions>
+					<action function="set">tw_back=flash_done</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=/cache</action>
+					<action function="set">tw_has_action2=1</action>
+					<action function="set">tw_action2=wipe</action>
+					<action function="set">tw_action2_param=dalvik</action>
+					<action function="set">tw_text1={@wipe_cache_dalvik_confirm=Wipe Cache &amp; Dalvik?}</action>
+					<action function="set">tw_action_text1={@wiping_cache_dalvik=Wiping Cache &amp; Dalvik...}</action>
+					<action function="set">tw_complete_text1={@wipe_cache_dalvik_complete=Cache &amp; Dalvik Wipe Complete}</action>
+					<action function="set">tw_slider_text={@swipe_wipe=Swipe to Wipe}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_ab_device" var2="1"/>
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@wipe_dalvik_btn=Wipe Dalvik}</text>
+				<actions>
+					<action function="set">tw_back=flash_done</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=dalvik</action>
+					<action function="set">tw_text1={@wipe_dalvik_confirm=Wipe Dalvik?}</action>
+					<action function="set">tw_action_text1={@wiping_cache_dalvik=Wiping Dalvik...}</action>
+					<action function="set">tw_complete_text1={@wipe_dalvik_complete=Dalvik Wipe Complete}</action>
+					<action function="set">tw_slider_text={@swipe_wipe=Swipe to Wipe}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_ab_device" var2="0"/>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@reboot_system_btn=Reboot System}</text>
+				<actions>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot_system_routine</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_ab_device" var2="1"/>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@reboot_btn=Reboot}</text>
+				<actions>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=install</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_install_reboot" var2="1"/>
+					<condition var1="tw_operation_status" var2="0"/>
+					<condition var1="tw_reboot_system" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_sleep=%tw_sleep_total%</action>
+					<action function="page">flash_sleep_and_reboot</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="flash_sleep_and_reboot">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@install_reboot=Rebooting in %tw_sleep% second(s)}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_sleep" op="&gt;" var2="0"/>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_install_reboot=0</action>
+					<action function="page">flash_done</action>
+				</actions>
+			</button>
+
+			<action>
+				<conditions>
+					<condition var1="tw_sleep" var2="tw_sleep_total"/>
+					<condition var1="tw_install_reboot" var2="1"/>
+				</conditions>
+				<action function="sleepcounter">%tw_sleep_total%</action>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_sleep" var2="0"/>
+					<condition var1="tw_install_reboot" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="sleep">50000</action>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot_system_routine</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="flashimage_confirm">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@install_image_hdr=Install Image}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@install_sel_target=Select Target Partition}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@folder=Folder:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row3_y%"/>
+				<text>%tw_zip_location%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row4a_y%"/>
+				<text>{@file=File:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row5a_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<partitionlist>
+				<placement x="%indent%" y="%row7_y%" w="%content_width%" h="%partitionlist_flashimage_height%"/>
+				<icon selected="radio_true" unselected="radio_false"/>
+				<text>{@flash_image_select=Select Partition to Flash Image:}</text>
+				<data name="tw_flash_partition"/>
+				<listtype name="flashimg"/>
+			</partitionlist>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_is_slot_part" op="=" var2="1"/>
+					<condition var1="tw_flash_both_slots" op="=" var2="0"/>
+					<condition var1="tw_has_boot_slots" var2="1"/>
+				</conditions>
+				<placement x="%indent%" y="%row19a_y%" textplacement="6"/>
+				<text>{@flash_ab_both_slots=Flash to both slots}</text>
+				<image resource="checkbox_false"/>
+				<action function="set">tw_flash_both_slots=1</action>
+			</button>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_is_slot_part" op="=" var2="1"/>
+					<condition var1="tw_flash_both_slots" op="=" var2="1"/>
+					<condition var1="tw_has_boot_slots" var2="1"/>
+				</conditions>
+				<placement x="%indent%" y="%row19a_y%" textplacement="6"/>
+				<text>{@flash_ab_both_slots=Flash to both slots}</text>
+				<image resource="checkbox_true"/>
+				<action function="set">tw_flash_both_slots=0</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_flash=Swipe to confirm Flash}</text>
+				<actions>
+					<action function="set">tw_back=flashimage_confirm</action>
+					<action function="set">tw_action=flashimage</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_action_text1={@flashing_image=Flashing Image...}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@image_flashed=Image Flashed}</action>
+					<action function="page">action_page</action>
+				</actions>
+				<action function="flashimage"/>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=install</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="clear_vars">
+			<action>
+				<action function="set">tw_operation_state=0</action>
+				<action function="set">tw_text1=</action>
+				<action function="set">tw_text2=</action>
+				<action function="set">tw_text3=</action>
+				<action function="set">tw_text4=</action>
+				<action function="set">tw_action_text1=</action>
+				<action function="set">tw_action_text2=</action>
+				<action function="set">tw_action_param=</action>
+				<action function="set">tw_has_action2=0</action>
+				<action function="set">tw_action2=</action>
+				<action function="set">tw_action2_param=</action>
+				<action function="set">tw_has_cancel=0</action>
+				<action function="set">tw_cancel_action=</action>
+				<action function="set">tw_cancel_param=</action>
+				<action function="set">tw_show_exclamation=0</action>
+				<action function="set">tw_show_reboot=0</action>
+				<action function="set">tw_crypto_user_id=</action>
+				<action function="set">tw_multiuser_warning_accepted=</action>
+				<action function="set">tw_multiuser_warning_destination=</action>
+				<action function="page">%tw_clear_destination%</action>
+			</action>
+		</page>
+
+		<page name="reboot_system_routine">
+			<action>
+				<action function="set">tw_action=reboot</action>
+				<action function="set">tw_action_param=system</action>
+				<action function="set">tw_has_action2=0</action>
+				<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+				<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+				<action function="set">tw_text3=</action>
+				<action function="set">tw_text4=</action>
+				<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+				<action function="set">tw_action_text2=</action>
+				<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+				<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+				<action function="page">rebootcheck</action>
+			</action>
+		</page>
+
+		<page name="confirm_action">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@confirm_action=Confirm Action}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text/>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>%tw_text1%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>%tw_text2%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>%tw_text3%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>%tw_text4%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@back_cancel=Press back button to cancel.}</text>
+			</text>
+
+			<slider>
+				<text>%tw_slider_text%</text>
+				<action function="page">action_page</action>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="action_page">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>%tw_action_text1%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_action_text2%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_has_cancel" var2="1"/>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="%tw_cancel_action%">%tw_cancel_param%</action>
+			</button>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<actions>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="0"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="1"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+					<action function="%tw_action2%">%tw_action2_param%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="singleaction_page">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>%tw_action_text1%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_action_text2%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="0"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="1"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+					<action function="%tw_action2%">%tw_action2_param%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="action_complete">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>%tw_complete_text1%</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_operation_status" op="!=" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@failed=Failed}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_operation_status" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@successful=Successful}</text>
+			</text>
+
+			<template name="console"/>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@back_btn=Back}</text>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@reboot_system_btn=Reboot System}</text>
+				<actions>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot_system_routine</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="filecheck">
+			<action>
+				<action function="fileexists">%tw_filecheck%</action>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_fileexists=1</action>
+					<action function="page">%tw_existpage%</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_fileexists=0</action>
+					<action function="page">%tw_notexistpage%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="rebootcheck">
+			<action>
+				<condition var1="tw_backup_system_size" op="&gt;=" var2="%tw_min_system%"/>
+				<action function="reboot">%tw_reboot_param%</action>
+			</action>
+
+			<action>
+				<condition var1="tw_backup_system_size" op="&lt;" var2="%tw_min_system%"/>
+				<action function="page">confirm_action</action>
+			</action>
+		</page>
+
+		<page name="wipe">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@wipe_hdr=Wipe}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@factory_reset_hdr=Factory Reset}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@factory_reset1=Wipes Data, Cache, and Dalvik}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@factory_reset5=(not including users/lockscreen)}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_has_data_media" var2="1"/>
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@factory_reset2=(not including internal storage)}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<conditions>
+					<condition var1="tw_has_android_secure" var2="1"/>
+					<condition var1="fileexists" var2="/and-sec"/>
+				</conditions>
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@android_secure=Android Secure}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_has_sdext_partition" var2="1"/>
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@sdext=SD-EXT}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@factory_reset3=Most of the time this is}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row8_y%" placement="5"/>
+				<text>{@factory_reset4=the only wipe that you need.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row10_y%" placement="5"/>
+				<text>{@back_cancel=Press back button to cancel.}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@advanced_wipe_btn=Advanced Wipe}</text>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="page">advancedwipe</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_has_data_media" var2="1"/>
+				<placement x="%center_x%" y="%row18a_y%"/>
+				<text>{@format_data_btn=Format Data}</text>
+				<action function="page">formatdata</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<conditions>
+					<condition var1="tw_is_encrypted" var2="1"/>
+					<condition var1="tw_has_data_media" var2="0"/>
+				</conditions>
+				<placement x="%center_x%" y="%row18a_y%"/>
+				<text>{@wipe_enc_btn=Wipe Encryption}</text>
+				<actions>
+					<action function="set">tw_back=wipe</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=DATAMEDIA</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@wipe_enc_confirm=Wipe Encryption from Data?}</action>
+					<action function="set">tw_text2=</action>
+					<action function="set">tw_action_text1={@formatting_data=Formatting Data...}</action>
+					<action function="set">tw_complete_text1={@format_data_complete=Data Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_format_data=Swipe to Format Data}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_factory_reset=Swipe to Factory Reset}</text>
+				<actions>
+					<action function="set">tw_back=wipe</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=data</action>
+					<action function="set">tw_action_text1={@factory_resetting=Factory Reset...}</action>
+					<action function="set">tw_complete_text1={@factory_reset_complete=Factory Reset Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="advancedwipe">
+			<template name="page"/>
+
+			<action>
+				<action function="set">tw_wipe_list=</action>
+			</action>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@wipe_hdr=Wipe}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@advanced_wipe_hdr=Advanced Wipe}</text>
+			</text>
+
+			<partitionlist>
+				<placement x="%indent%" y="%row1a_y%" w="%content_width%" h="%partitionlist_wipe_height%"/>
+				<text>{@sel_part_wipe=Select Partitions to Wipe:}</text>
+				<data name="tw_wipe_list"/>
+				<listtype name="wipe"/>
+			</partitionlist>
+
+			<text style="text_m_fail">
+				<condition var1="partitionlisterror" var2="1"/>
+				<placement x="%center_x%" y="%row17_y%" placement="5"/>
+				<text>{@invalid_part_sel=Invalid partition selection}</text>
+			</text>
+
+			<button style="main_button_half_height_full_width">
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@repair_change_btn=Repair or Change File System}</text>
+				<actions>
+					<action function="checkpartitionlist">tw_wipe_list</action>
+					<action function="page">checkpartitionlist</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_wipe=Swipe to Wipe}</text>
+				<actions>
+					<action function="set">tw_back=advancedwipe</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=LIST</action>
+					<action function="set">tw_text1={@wipe_sel_confirm=Wipe Selected Partition(s)?}</action>
+					<action function="set">tw_action_text1={@wiping_part=Wiping Partition(s)...}</action>
+					<action function="set">tw_complete_text1={@wipe_complete=Wipe Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">wipe</action>
+			</action>
+		</page>
+
+		<page name="formatdata">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@wipe_hdr=Wipe}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@format_data_hdr=Format Data}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@format_data_ptr1=Format Data will wipe all of your apps,}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@format_data_ptr2=backups, pictures, videos, media, and}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@format_data_ptr3=removes encryption on internal storage.}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_has_adopted_storage" var2="1"/>
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@format_data_adopted=Including Adopted Storage}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@format_data_undo=This cannot be undone.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row8_y%" placement="5"/>
+				<text>{@yes_continue=Type yes to continue.  Press back to cancel.}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row10_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_confirm_formatdata%</text>
+				<data name="tw_confirm_formatdata"/>
+				<restrict minlen="3" maxlen="3" allow="yes"/>
+				<action function="page">formatdata_confirm</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row11_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">wipe</action>
+			</action>
+		</page>
+
+		<page name="formatdata_confirm">
+			<action>
+				<condition var1="tw_confirm_formatdata" op="=" var2="yes"/>
+				<actions>
+					<action function="set">tw_back=formatdata</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=DATAMEDIA</action>
+					<action function="set">tw_action_text1={@formatting_data=Formatting Data...}</action>
+					<action function="set">tw_complete_text1={@format_data_complete=Data Format Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_confirm_formatdata" op="!=" var2="yes"/>
+				<action function="page">formatdata</action>
+			</action>
+		</page>
+
+		<page name="checkpartitionlist">
+			<action>
+				<condition var1="tw_check_partition_list" op="=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="getpartitiondetails">tw_wipe_list</action>
+					<action function="page">partitionoptions</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_check_partition_list" op="!=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=1</action>
+					<action function="page">advancedwipe</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="partitionoptions">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@wipe_hdr=Wipe}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@part_opt_hdr=Partition Options for: %tw_partition_name%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@part_mount_point=Mount Point: %tw_partition_mount_point%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@part_curr_fs=File system: %tw_partition_file_system%}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_is_present" var2="1"/>
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@part_present_yes=Present: Yes}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_is_present" var2="0"/>
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@part_present_no=Present: No}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_removable" var2="1"/>
+				<placement x="%center_x%" y="%row5_y%"/>
+				<text>{@part_removable_yes=Removable: Yes}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_removable" var2="0"/>
+				<placement x="%center_x%" y="%row5_y%"/>
+				<text>{@part_removable_no=Removable: No}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row7_y%"/>
+				<text>{@part_size=Size: %tw_partition_size%MB}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row7_y%"/>
+				<text>{@part_used=Used: %tw_partition_used%MB}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row9_y%"/>
+				<text>{@part_free=Free: %tw_partition_free%MB}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row9_y%"/>
+				<text>{@part_backup_size=Backup Size: %tw_partition_backup_size%MB}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_partition_can_resize" op="=" var2="1"/>
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@resize_btn=Resize File System}</text>
+				<actions>
+					<action function="set">tw_back=partitionoptions</action>
+					<action function="set">tw_action=resize</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_has_action2=1</action>
+					<action function="set">tw_action2=getpartitiondetails</action>
+					<action function="set">tw_action2_param=tw_wipe_list</action>
+					<action function="set">tw_text1={@resize_confirm=Resize %tw_partition_name%?}</action>
+					<action function="set">tw_text2=</action>
+					<action function="set">tw_action_text1={@resizing=Resizing...}</action>
+					<action function="set">tw_complete_text1={@resize_complete=Resize Complete}</action>
+					<action function="set">tw_slider_text={@swipe_resize=Swipe to Resize}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_partition_can_repair" op="=" var2="1"/>
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@repair_btn=Repair File System}</text>
+				<actions>
+					<action function="set">tw_back=partitionoptions</action>
+					<action function="set">tw_action=repair</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@repair_confirm=Repair %tw_partition_name%?}</action>
+					<action function="set">tw_text2=</action>
+					<action function="set">tw_action_text1={@repairing=Repairing...}</action>
+					<action function="set">tw_complete_text1={@repair_complete=Repair Complete}</action>
+					<action function="set">tw_slider_text={@swipe_repair=Swipe to Repair}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@change_fs_btn=Change File System}</text>
+				<action function="page">selectfilesystem</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advancedwipe</action>
+			</action>
+		</page>
+
+		<page name="refreshfilesystem">
+			<action>
+				<condition var1="tw_check_partition_list" op="=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="getpartitiondetails">tw_wipe_list</action>
+					<action function="page">selectfilesystem</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_check_partition_list" op="!=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=1</action>
+					<action function="set">tw_wipe_list=</action>
+					<action function="page">advancedwipe</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="selectfilesystem">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@wipe_hdr=Wipe}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@change_fs_for_hdr=Change File System for: %tw_partition_name%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@part_mount_point=Mount Point: %tw_partition_mount_point%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@part_curr_fs=File system: %tw_partition_file_system%}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@change_fs_warn1=Some ROMs or kernels may not support some}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@change_fs_warn2=file systems. Proceed with caution!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_partition_ext" op="=" var2="1"/>
+				<placement x="%indent%" y="%row15a_y%"/>
+				<text>EXT2</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=ext2</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=EXT2</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_partition_ext" op="=" var2="1"/>
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>EXT3</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=ext3</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=EXT3</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_partition_ext" op="=" var2="1"/>
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>EXT4</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=ext4</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=EXT4</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_partition_vfat" op="=" var2="1"/>
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>FAT</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=vfat</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=FAT</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_partition_exfat" op="=" var2="1"/>
+				<placement x="%center_x%" y="%row18a_y%"/>
+				<text>exFAT</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=exfat</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=exFAT</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_partition_f2fs" op="=" var2="1"/>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>F2FS</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=f2fs</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=F2FS</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_fs=Swipe to Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">partitionoptions</action>
+			</action>
+		</page>
+
+		<page name="backup">
+			<template name="page"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_is_fbe" var2="1"/>
+					<condition var1="tw_all_users_decrypted" var2="0"/>
+					<condition var1="tw_multiuser_warning_accepted" op="!=" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_multiuser_warning_destination=backup</action>
+					<action function="page">multiuser_warning</action>
+				</actions>
+			</action>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@backup_hdr=Backup}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="tabs_backup"/>
+
+			<fill color="%text_color%">
+				<placement x="0" y="%row_tab_y%" w="%tab3_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<button style="tab">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="0"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%tab3_col3_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@encryption_tab=ENCRYPTION}</text>
+				<font resource="font_s" color="%text_color%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<button style="tab">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="1"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%tab3_col3_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@encryption_tab=ENCRYPTION}</text>
+				<font resource="font_s" color="%text_success_color%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row3a_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row4a_y%"/>
+				<text>%tw_backup_name%</text>
+			</text>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row5a_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<button>
+				<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				<placement x="indent" y="%row3a_y%" w="%content_width%" h="%navbar_height%"/>
+				<fill color="%transparent%"/>
+				<actions>
+					<action function="set">tw_fileexists=0</action>
+					<action function="page">backupname1</action>
+				</actions>
+			</button>
+
+			<partitionlist>
+				<placement x="%indent%" y="%row6_y%" w="%content_width%" h="%partitionlist_backup_height%"/>
+				<text>{@sel_part_backup=Select Partitions to Backup:}</text>
+				<data name="tw_backup_list"/>
+				<listtype name="backup"/>
+			</partitionlist>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=backup</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row18a_y%"/>
+				<text>{@refresh_sizes_btn=Refresh Sizes}</text>
+				<actions>
+					<action function="refreshsizes"/>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_backup=Swipe to Backup}</text>
+				<action function="page">backup_run</action>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="multiuser_warning">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@multiuser_warning_hdr=Multiuser Warning}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@multiuser_warning1=Not all users decrypted!}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row8_y%" placement="5"/>
+				<text>{@multiuser_warning2=Backup/restore operations may fail!}</text>
+			</text>
+
+			<button style="main_button_half_height_full_width">
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@decrypt_users=Decrypt Users}</text>
+				<action function="page">decrypt_users</action>
+			</button>
+
+			<slider>>
+				<text>{@multiuser_warning_accept=Continue Anyway}</text>
+				<actions>
+					<action function="set">tw_multiuser_warning_accepted=1</action>
+					<action function="page">%tw_multiuser_warning_destination%</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="backup_options">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@backup_hdr=Backup}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="tabs_backup"/>
+
+			<fill color="%text_color%">
+				<placement x="%tab3_col2_x%" y="%row_tab_y%" w="%tab3_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<button style="tab">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="0"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%tab3_col3_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@encryption_tab=ENCRYPTION}</text>
+				<font resource="font_s" color="%text_color%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<button style="tab">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="1"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%tab3_col3_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@encryption_tab=ENCRYPTION}</text>
+				<font resource="font_s" color="%text_success_color%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<checkbox>
+				<placement x="%indent%" y="%row3a_y%"/>
+				<text>{@enable_backup_comp_chk=Enable compression}</text>
+				<data variable="tw_use_compression"/>
+			</checkbox>
+
+			<checkbox>
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@skip_digest_backup_chk=Skip Digest generation during backup}</text>
+				<data variable="tw_skip_digest_generate"/>
+			</checkbox>
+
+			<checkbox>
+				<placement x="%indent%" y="%row6a_y%"/>
+				<text>{@disable_backup_space_chk=Disable free space check before backup}</text>
+				<data variable="tw_disable_free_space"/>
+			</checkbox>
+
+			<text style="text_m">
+				<condition var1="tw_has_boot_slots" var2="1"/>
+				<placement x="%center_x%" y="%row18_y%" placement="5"/>
+				<text>{@current_boot_slot=Current Slot: %tw_active_slot%}</text>
+			</text>
+
+			<button style="main_button">
+				<condition var1="tw_has_boot_slots" var2="1"/>
+				<placement x="%indent%" y="%row19_y%"/>
+				<text>{@boot_slot_a=Slot A}</text>
+				<actions>
+					<action function="set">tw_back=backup_options</action>
+					<action function="set">tw_action=setbootslot</action>
+					<action function="set">tw_action_param=A</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_action_text1={@changing_boot_slot=Changing Boot Slot}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@changing_boot_slot_complete=Change Boot Slot Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_has_boot_slots" var2="1"/>
+				<placement x="%center_x%" y="%row19_y%"/>
+				<text>{@boot_slot_b=Slot B}</text>
+				<actions>
+					<action function="set">tw_back=backup_options</action>
+					<action function="set">tw_action=setbootslot</action>
+					<action function="set">tw_action_param=B</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_action_text1={@changing_boot_slot=Changing Boot Slot}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@changing_boot_slot_complete=Change Boot Slot Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">backup</action>
+			</action>
+		</page>
+
+		<page name="backupname1">
+			<action>
+				<condition var1="tw_backup_name" op="=" var2="{@auto_generate=(Auto Generate)}"/>
+				<action function="generatebackupname"/>
+			</action>
+
+			<action>
+				<action function="page">backupname2</action>
+			</action>
+		</page>
+
+		<page name="backupname2">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@backup_hdr=Backup}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="tabs_backup"/>
+
+			<fill color="%text_color%">
+				<placement x="0" y="%row_tab_y%" w="%tab3_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<button style="tab">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="0"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%tab3_col3_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@encryption_tab=ENCRYPTION}</text>
+				<font resource="font_s" color="%text_color%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<button style="tab">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="1"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%tab3_col3_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@encryption_tab=ENCRYPTION}</text>
+				<font resource="font_s" color="%text_success_color%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row3a_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row4_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_backup_name%</text>
+				<data name="tw_backup_name"/>
+				<restrict minlen="1" maxlen="64" allow=" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_.{}[]"/>
+				<actions>
+					<action function="set">tw_filecheck=%tw_backups_folder%/%tw_backup_name%</action>
+					<action function="set">tw_existpage=backupname2</action>
+					<action function="set">tw_notexistpage=backup</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row5a_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%indent%" y="row5a_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>{@backup_name_exists=A backup with that name already exists!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_backup_name={@auto_generate=(Auto Generate)}</action>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row10_y%"/>
+				<text>{@append_date_btn=Append Date}</text>
+				<action function="appenddatetobackupname"/>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_backup_name={@auto_generate=(Auto Generate)}</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_backup_name={@auto_generate=(Auto Generate)}</action>
+					<action function="page">backup</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="backupencryption">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@backup_hdr=Backup}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="tabs_backup"/>
+
+			<button style="tab">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="0"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%tab3_col3_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@encryption_tab=ENCRYPTION}</text>
+				<font resource="font_s" color="%text_color%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<button style="tab">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="1"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%tab3_col3_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@encryption_tab=ENCRYPTION}</text>
+				<font resource="font_s" color="%text_success_color%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<fill color="%text_color%">
+				<placement x="%tab3_col3_x%" y="%row_tab_y%" w="%tab3_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3a_y%" placement="5"/>
+				<text>{@encrypt_backup=Encrypt your Backup?}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@enter_pass=Enter Password:}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row7_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_backup_encrypt_display%</text>
+				<data name="tw_backup_password" mask="*" maskvariable="tw_backup_encrypt_display"/>
+				<restrict minlen="1" maxlen="32" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"/>
+				<action function="page">backupencryption2</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row8_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_not_match" var2="1"/>
+				<placement x="%indent%" y="row8_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_not_match" var2="1"/>
+				<placement x="%indent%" y="%row8a_y%"/>
+				<text>{@pass_not_match=Passwords do not match!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_encrypt_backup=0</action>
+					<action function="set">tw_backup_password=</action>
+					<action function="set">tw_backup_password2=</action>
+					<action function="set">tw_backup_encrypt_display=</action>
+					<action function="set">tw_backup_encrypt_display2=</action>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">backup</action>
+			</action>
+		</page>
+
+		<page name="backupencryption2">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@backup_hdr=Backup}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="tabs_backup"/>
+
+			<button style="tab">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="0"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%tab3_col3_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@encryption_tab=ENCRYPTION}</text>
+				<font resource="font_s" color="%text_color%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<button style="tab">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="1"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%tab3_col3_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@encryption_tab=ENCRYPTION}</text>
+				<font resource="font_s" color="%text_success_color%"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<fill color="%text_color%">
+				<placement x="%tab3_col3_x%" y="%row_tab_y%" w="%tab3_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3a_y%" placement="5"/>
+				<text>{@encrypt_backup=Encrypt your Backup?}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@enter_pass2=Enter Password again:}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row7_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_backup_encrypt_display2%</text>
+				<data name="tw_backup_password2" mask="*" maskvariable="tw_backup_encrypt_display2"/>
+				<restrict minlen="1" maxlen="32" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"/>
+				<action function="page">checkbackuppassword</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row8_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_encrypt_backup=0</action>
+					<action function="set">tw_backup_password=</action>
+					<action function="set">tw_backup_password2=</action>
+					<action function="set">tw_backup_encrypt_display=</action>
+					<action function="set">tw_backup_encrypt_display2=</action>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">backup</action>
+			</action>
+		</page>
+
+		<page name="checkbackuppassword">
+			<action>
+				<condition var1="tw_backup_password2" var2="tw_backup_password"/>
+				<actions>
+					<action function="set">tw_encrypt_backup=1</action>
+					<action function="page">backup</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_backup_password2" op="!=" var2="tw_backup_password"/>
+				<actions>
+					<action function="set">tw_encrypt_backup=0</action>
+					<action function="set">tw_password_not_match=1</action>
+					<action function="set">tw_backup_password=</action>
+					<action function="set">tw_backup_password2=</action>
+					<action function="set">tw_backup_encrypt_display=</action>
+					<action function="set">tw_backup_encrypt_display2=</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="backup_run">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>%tw_operation%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_partition% Partition</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row19_y%"/>
+				<text>{@progress=Progress:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row20_y%"/>
+				<text>%tw_file_progress%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row21_y%"/>
+				<text>%tw_size_progress%</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="cancelbackup"/>
+			</button>
+
+			<action>
+				<action function="nandroid">backup</action>
+			</action>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<condition var1="tw_cancel_backup" var2="0"/>
+				<actions>
+					<action function="set">tw_back=backup</action>
+					<action function="set">tw_complete_text1={@backup_complete=Backup Complete}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<condition var1="tw_cancel_backup" var2="1"/>
+				<actions>
+					<action function="set">tw_back=backup</action>
+					<action function="set">tw_complete_text1={@backup_cancel=Backup Cancelled}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore">
+			<template name="page"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_is_fbe" var2="1"/>
+					<condition var1="tw_all_users_decrypted" var2="0"/>
+					<condition var1="tw_multiuser_warning_accepted" op="!=" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_multiuser_warning_destination=restore</action>
+					<action function="page">multiuser_warning</action>
+				</actions>
+			</action>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_sel_store_hdr=Select Backup from %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%indent%" y="%row3_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>{@restore_sel_pack_fs=Select Package to Restore:}</text>
+				<filter folders="1" files="1" nav="0" extn=".ab"/>
+				<path name="tw_backups_folder"/>
+				<data name="tw_restore" default=""/>
+				<selection name="tw_restore_name"/>
+			</fileselector>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<action>
+				<condition var1="tw_crypto_pwtype" op="!=" var2="0"/>
+				<actions>
+					<action function="page">restore_keymaster</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_restore" op="modified"/>
+				<actions>
+					<action function="readBackup"/>
+					<action function="page">restore_read</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+
+		<page name="restore_force">
+			<template name="page"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_is_fbe" var2="1"/>
+					<condition var1="tw_all_users_decrypted" var2="0"/>
+					<condition var1="tw_multiuser_warning_accepted" op="!=" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_multiuser_warning_destination=restore_force</action>
+					<action function="page">multiuser_warning</action>
+				</actions>
+			</action>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_sel_store_hdr=Select Backup from %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%indent%" y="%row3_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>{@restore_sel_pack_fs=Select Package to Restore:}</text>
+				<filter folders="1" files="1" nav="0" extn=".ab"/>
+				<path name="tw_backups_folder"/>
+				<data name="tw_restore" default=""/>
+				<selection name="tw_restore_name"/>
+			</fileselector>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<action>
+				<condition var1="tw_restore" op="modified"/>
+				<actions>
+					<action function="readBackup"/>
+					<action function="page">restore_read</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="restore_read">
+			<action>
+				<condition var1="tw_restore_encrypted" var2="1"/>
+				<actions>
+					<action function="set">tw_password_fail=0</action>
+					<action function="page">restore_decrypt</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_restore_encrypted" var2="0"/>
+				<actions>
+					<action function="page">restore_select</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore_decrypt">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_enc_backup_hdr=Encrypted Backup}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@enter_pass=Enter Password:}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row3_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_restore_display%</text>
+				<data name="tw_restore_password" mask="*" maskvariable="tw_restore_display"/>
+				<restrict minlen="1" maxlen="32" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"/>
+				<action function="page">try_restore_decrypt</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row4a_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%indent%" y="row4a_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@restore_dec_fail=Password failed, please try again!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">restore</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row10_y%"/>
+				<text>{@del_backup_btn=Delete Backup}</text>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="set">tw_action=cmd</action>
+					<action function="set">tw_action_param=cd %tw_backups_folder% &amp;&amp; rm -rf "%tw_restore_name%"</action>
+					<action function="set">tw_text1={@del_backup_confirm=Delete Backup?}</action>
+					<action function="set">tw_text2=%tw_restore_name%</action>
+					<action function="set">tw_text4={@del_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@deleting_backup=Deleting Backup...}</action>
+					<action function="set">tw_complete_text1={@backup_deleted=Backup Delete Complete}</action>
+					<action function="set">tw_slider_text={@swipe_delete=Swipe to Delete}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">restore</action>
+			</action>
+		</page>
+
+		<page name="try_restore_decrypt">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_try_decrypt=Encrypted Backup - Trying Decryption}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<action function="decrypt_backup"/>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="!=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_password_fail=1</action>
+					<action function="page">restore_decrypt</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">restore_select</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore_select">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@restore_backup_date=Backup made on %tw_restore_file_date%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row3_y%"/>
+				<text>%tw_restore_name%</text>
+			</text>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row4_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<button>
+				<placement x="indent" y="%row2_y%" w="%content_width%" h="%navbar_height%"/>
+				<fill color="%transparent%"/>
+				<actions>
+					<action function="set">tw_backup_rename=%tw_restore_name%</action>
+					<action function="set">tw_fileexists=0</action>
+					<action function="page">renamebackup</action>
+				</actions>
+			</button>
+
+			<partitionlist>
+				<placement x="%indent%" y="%row4a_y%" w="%content_width%" h="%partitionlist_backup_height%"/>
+				<text>{@restore_sel_part=Select Partitions to Restore:}</text>
+				<data name="tw_restore_list" selectedlist="tw_restore_selected"/>
+				<listtype name="restore"/>
+			</partitionlist>
+
+			<checkbox>
+				<condition var1="tw_enable_adb_backup" op="=" var2="0"/>
+				<placement x="%indent%" y="%row17_y%"/>
+				<text>{@restore_enable_digest_chk=Enable Digest Verification of Backup Files}</text>
+				<data variable="tw_skip_digest_check"/>
+			</checkbox>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@del_backup_btn=Delete Backup}</text>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="set">tw_action=cmd</action>
+					<action function="set">tw_action_param=cd %tw_backups_folder% &amp;&amp; rm -rf "%tw_restore_name%"</action>
+					<action function="set">tw_text1={@del_backup_confirm=Delete Backup?}</action>
+					<action function="set">tw_text2=%tw_restore_name%</action>
+					<action function="set">tw_text4={@del_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@deleting_backup=Deleting Backup...}</action>
+					<action function="set">tw_complete_text1={@backup_deleted=Backup Delete Complete}</action>
+					<action function="set">tw_slider_text={@swipe_delete=Swipe to Delete}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_restore=Swipe to Restore}</text>
+				<action function="page">restore_run</action>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">restore</action>
+			</action>
+		</page>
+
+		<page name="renamebackup">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@rename_backup_hdr=Rename Backup}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_backup_rename%</text>
+				<data name="tw_backup_rename"/>
+				<restrict minlen="1" maxlen="64" allow=" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_.{}[]"/>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="set">tw_action=cmd</action>
+					<action function="set">tw_action_param=cd %tw_backups_folder% &amp;&amp; mv "%tw_restore_name%" "%tw_backup_rename%"</action>
+					<action function="set">tw_text1={@rename_backup_confirm=Rename Backup?}</action>
+					<action function="set">tw_text2={@rename_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@renaming_backup=Renaming Backup...}</action>
+					<action function="set">tw_complete_text1={@rename_backup_complete=Backup Rename Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_rename=Swipe to Rename}</action>
+					<action function="set">tw_filecheck=%tw_backups_folder%/%tw_backup_rename%</action>
+					<action function="set">tw_existpage=renamebackup</action>
+					<action function="set">tw_notexistpage=confirm_action</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%indent%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%indent%" y="%row4a_y%"/>
+				<text>{@backup_name_exists=A backup with that name already exists!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">restore_select</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">restore_select</action>
+			</action>
+		</page>
+
+		<page name="restore_run">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_hdr=Restore}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_operation% %tw_partition%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row19_y%"/>
+				<text>{@progress=Progress:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row20_y%"/>
+				<text>%tw_size_progress%</text>
+			</text>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<actions>
+					<action function="set">tw_back=restore_select</action>
+					<action function="set">tw_complete_text1={@restore_complete=Restore Complete}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<action function="nandroid">restore</action>
+			</action>
+		</page>
+
+		<page name="mount">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@storage_hdr=Storage: %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<partitionlist>
+				<placement x="%indent%" y="%row1a_y%" w="%content_width%" h="%partitionlist_mount_height%"/>
+				<text>{@mount_sel_part=Select Partitions to Mount:}</text>
+				<listtype name="mount"/>
+			</partitionlist>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_mount_system_ro" op="=" var2="0"/>
+					<condition var1="tw_is_super" op="=" var2="0"/>
+				</conditions>
+				<placement x="%indent%" y="%row15a_y%" textplacement="6"/>
+				<text>{@mount_sys_ro_chk=Mount system partition read-only}</text>
+				<image resource="checkbox_false"/>
+				<action function="mountsystemtoggle">1</action>
+			</button>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_mount_system_ro" op="!=" var2="0"/>
+					<condition var1="tw_is_super" op="=" var2="0"/>
+				</conditions>
+				<placement x="%indent%" y="%row15a_y%" textplacement="6"/>
+				<text>{@mount_sys_ro_chk=Mount system partition read-only}</text>
+				<image resource="checkbox_true"/>
+				<actions>
+					<action function="set">tw_lifetime_writes=2</action>
+					<action function="page">system_readonly_check</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_has_usb_storage" var2="1"/>
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@mount_usb_storage_btn=Mount USB Storage}</text>
+				<action function="page">usb_mount</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<conditions>
+					<condition var1="tw_is_encrypted" var2="1"/>
+					<condition var1="tw_is_decrypted" var2="0"/>
+				</conditions>
+				<placement x="%center_x%" y="%row18a_y%"/>
+				<text>{@decrypt_data_btn=Decrypt Data}</text>
+				<action function="set">tw_crypto_user_id=0</action>
+				<action function="set">tw_crypto_password=</action>
+				<action function="set">tw_password_fail=0</action>
+				<action function="set">tw_crypto_pwtype=%tw_crypto_pwtype_0%</action>
+				<action function="page">decrypt</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=mount</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<conditions>
+					<condition var1="tw_has_mtp" var2="1"/>
+					<condition var1="tw_mtp_enabled" var2="0"/>
+				</conditions>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@enable_mtp_btn=Enable MTP}</text>
+				<action function="startmtp"/>
+			</button>
+
+			<button style="main_button_half_height">
+				<conditions>
+					<condition var1="tw_has_mtp" var2="1"/>
+					<condition var1="tw_mtp_enabled" var2="1"/>
+				</conditions>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@disable_mtp_btn=Disable MTP}</text>
+				<action function="stopmtp"/>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="usb_mount">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@usb_storage_hdr=USB Storage}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@usb_stor_mnt1=USB Storage Mounted}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@usb_stor_mnt2=Be sure to safely remove your device}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@usb_stor_mnt3=from your computer before unmounting!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@unmount_btn=Unmount}</text>
+				<action function="page">usb_umount</action>
+			</button>
+
+			<action>
+				<action function="mount">usb</action>
+				<action function="set">tw_busy=1</action>
+			</action>
+		</page>
+
+		<page name="usb_umount">
+			<action>
+				<action function="unmount">usb</action>
+			</action>
+
+			<action>
+				<action function="page">mount</action>
+				<action function="set">tw_busy=0</action>
+			</action>
+		</page>
+
+		<page name="system_readonly_check">
+			<action>
+				<action function="checkpartitionlifetimewrites">/system</action>
+			</action>
+
+			<action>
+				<condition var1="tw_lifetime_writes" var2="1"/>
+				<actions>
+					<action function="mountsystemtoggle">0</action>
+					<action function="page">mount</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_lifetime_writes" var2="0"/>
+				<actions>
+					<action function="set">tw_back=mount</action>
+					<action function="page">system_readonly</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="fastbootreboot">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@reboot_hdr=Reboot}</text>
+			</text>
+
+			<listbox style="advanced_listbox">
+				<placement x="%indent%" y="%row2a_y%" w="%content_width%" h="%listbox_advanced_height%"/>
+
+				<listitem name="{@rb_system_btn=System}">
+					<condition var1="tw_reboot_system" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=system</action>
+						<action function="set">tw_reboot_param=system</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_poweroff_btn=Power Off}">
+					<condition var1="tw_reboot_poweroff" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=poweroff</action>
+						<action function="set">tw_reboot_param=poweroff</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@turning_off=Turning Off...}</action>
+						<action function="set">tw_complete_text1={@turning_off=Turning Off...}</action>
+						<action function="set">tw_slider_text={@swipe_power_off=Swipe to Power Off}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_recovery_btn=Recovery}">
+					<condition var1="tw_reboot_recovery" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=recovery</action>
+						<action function="set">tw_reboot_param=recovery</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_bootloader_btn=Bootloader}">
+					<condition var1="tw_reboot_bootloader" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=bootloader</action>
+						<action function="set">tw_reboot_param=bootloader</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_download_btn=Download}">
+					<condition var1="tw_download_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=download</action>
+						<action function="set">tw_reboot_param=download</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@fastboot_button=Fastboot}">
+					<condition var1="tw_fastboot_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=fastboot</action>
+						<action function="set">tw_reboot_param=fastboot</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_edl_btn=EDL}">
+					<condition var1="tw_edl_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=fastbootreboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=edl</action>
+						<action function="set">tw_reboot_param=edl</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+			</listbox>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">fastboot</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">fastboot</action>
+			</action>
+		</page>
+
+		<page name="reboot">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@reboot_hdr=Reboot}</text>
+			</text>
+
+			<listbox style="advanced_listbox">
+				<placement x="%indent%" y="%row2a_y%" w="%content_width%" h="%listbox_advanced_height%"/>
+
+				<listitem name="{@rb_system_btn=System}">
+					<condition var1="tw_reboot_system" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="page">reboot_system_routine</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_poweroff_btn=Power Off}">
+					<condition var1="tw_reboot_poweroff" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=poweroff</action>
+						<action function="set">tw_reboot_param=poweroff</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_ospo=sure you wish to power off?}</action>
+						<action function="set">tw_action_text1={@turning_off=Turning Off...}</action>
+						<action function="set">tw_complete_text1={@turning_off=Turning Off...}</action>
+						<action function="set">tw_slider_text={@swipe_power_off=Swipe to Power Off}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_recovery_btn=Recovery}">
+					<condition var1="tw_reboot_recovery" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=recovery</action>
+						<action function="set">tw_reboot_param=recovery</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_bootloader_btn=Bootloader}">
+					<condition var1="tw_reboot_bootloader" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=bootloader</action>
+						<action function="set">tw_reboot_param=bootloader</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_download_btn=Download}">
+					<condition var1="tw_download_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=download</action>
+						<action function="set">tw_reboot_param=download</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@fastboot_button=Fastboot}">
+					<condition var1="tw_fastboot_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=fastboot</action>
+						<action function="set">tw_reboot_param=fastboot</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+
+				<listitem name="{@rb_edl_btn=EDL}">
+					<condition var1="tw_edl_mode" var2="1"/>
+					<actions>
+						<action function="set">tw_back=reboot</action>
+						<action function="set">tw_action=reboot</action>
+						<action function="set">tw_action_param=edl</action>
+						<action function="set">tw_reboot_param=edl</action>
+						<action function="set">tw_has_action2=0</action>
+						<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+						<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+						<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+						<action function="set">tw_slider_text={@swipe_reboot=Swipe to Reboot}</action>
+						<action function="page">rebootcheck</action>
+					</actions>
+				</listitem>
+			</listbox>
+
+			<text style="text_m">
+				<condition var1="tw_has_boot_slots" var2="1"/>
+				<placement x="%center_x%" y="%row17_y%" placement="5"/>
+				<text>{@current_boot_slot=Current Slot: %tw_active_slot%}</text>
+			</text>
+
+			<button style="main_button">
+				<condition var1="tw_has_boot_slots" var2="1"/>
+				<placement x="%indent%" y="%row19_y%"/>
+				<text>{@boot_slot_a=Slot A}</text>
+				<actions>
+					<action function="set">tw_back=reboot</action>
+					<action function="set">tw_action=setbootslot</action>
+					<action function="set">tw_action_param=A</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_action_text1={@changing_boot_slot=Changing Boot Slot}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@changing_boot_slot_complete=Change Boot Slot Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_has_boot_slots" var2="1"/>
+				<placement x="%center_x%" y="%row19_y%"/>
+				<text>{@boot_slot_b=Slot B}</text>
+				<actions>
+					<action function="set">tw_back=reboot</action>
+					<action function="set">tw_action=setbootslot</action>
+					<action function="set">tw_action_param=B</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_action_text1={@changing_boot_slot=Changing Boot Slot}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@changing_boot_slot_complete=Change Boot Slot Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="installapp">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>%tw_appinstall_title%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@reboot_install_app_hdr=Install TWRP App}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@reboot_install_app1=Would you like to install the Official TWRP App?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@reboot_install_app2=The app can check for new TWRP versions.}</text>
+			</text>
+
+			<checkbox>
+				<condition var1="tw_mount_system_ro" var2="0"/>
+				<placement x="%indent%" y="%row7_y%"/>
+				<text>{@reboot_install_app_system=Install as a System App}</text>
+				<data variable="tw_app_install_system"/>
+			</checkbox>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row16_y%"/>
+				<text>{@install_cancel=Do not Install}</text>
+				<action function="page">%tw_back%</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_to_install_app=Swipe to Install TWRP App}</text>
+				<actions>
+					<action function="set">tw_action=installapp</action>
+					<action function="set">tw_action_text1={@reboot_installing_app=Installing App...}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@successful=Successful}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_app_install_status=0</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_app_install_status=0</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="system_readonly">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@sys_ro_hdr=Unmodified System Partition}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@sys_ro_keep=Keep System Read only?}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@sys_rop1=TWRP can leave your system partition unmodified}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@sys_rop2=to make it easier for you to take official updates.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@sys_rop3=TWRP will be unable to prevent the stock ROM from}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@sys_rop4=replacing TWRP and will not offer to root your device.}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row9_y%" placement="5"/>
+				<text>{@sys_rop5=Installing zips or performing adb operations may still}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row10_y%" placement="5"/>
+				<text>{@sys_rop6=modify the system partition.}</text>
+			</text>
+
+			<checkbox>
+				<condition var1="tw_is_encrypted" var2="0"/>
+				<placement x="%indent%" y="%row15_y%"/>
+				<text>{@sys_ro_never_show_chk=Never show this screen during boot again}</text>
+				<data variable="tw_never_show_system_ro_page"/>
+			</checkbox>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@sys_ro_keep_ro_btn=Keep Read Only}</text>
+				<actions>
+					<action function="mountsystemtoggle">1</action>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row18a_y%"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="overlay">select_language</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_allow_mod=Swipe to Allow Modifications}</text>
+				<actions>
+					<action function="mountsystemtoggle">0</action>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</slider>
+		</page>
+
+		<page name="settings">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@settings_gen_hdr=General Settings}</text>
+			</text>
+
+			<template name="tabs_settings"/>
+
+			<fill color="%text_color%">
+				<placement x="0" y="%row_tab_y%" w="%tab5_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<listbox style="scrolllist">
+				<placement x="%indent%" y="%row3a_y%" w="%content_width%" h="%listbox_settings_height%"/>
+				<icon selected="checkbox_true" unselected="checkbox_false"/>
+				<listitem name="{@zip_sig_chk=Zip signature verification}">
+					<data variable="tw_signed_zip_verify"/>
+				</listitem>
+				<listitem name="{@install_reboot_chk=Reboot after installation is complete}">
+					<data variable="tw_install_reboot"/>
+				</listitem>
+				<listitem name="{@use_rmrf_chk=Use rm -rf instead of formatting}">
+					<data variable="tw_rm_rf"/>
+				</listitem>
+				<listitem name="{@disable_backup_space_chk=Disable free space check before backup}">
+					<data variable="tw_disable_free_space"/>
+				</listitem>
+				<listitem name="{@skip_digest_backup_chk=Skip Digest generation during backup}">
+					<data variable="tw_skip_digest_generate"/>
+				</listitem>
+				<listitem name="{@restore_enable_digest_chk=Enable Digest verification of backup files}">
+					<data variable="tw_skip_digest_check"/>
+				</listitem>
+				<listitem name="{@skip_digest_zip_chk=Skip Digest check before installing zip}">
+					<data variable="tw_skip_digest_check_zip"/>
+				</listitem>
+				<listitem name="{@use24clock_chk=Use 24-hour clock}">
+					<data variable="tw_military_time"/>
+				</listitem>
+				<listitem name="{@rev_navbar_chk=Reversed navbar layout}">
+					<data variable="tw_samsung_navbar"/>
+				</listitem>
+				<listitem name="{@simact_chk=Simulate actions for theme testing}">
+					<data variable="tw_simulate_actions"/>
+				</listitem>
+				<listitem name="{@simfail_chk=Simulate failure for actions}">
+					<condition var1="tw_simulate_actions" var2="1"/>
+					<data variable="tw_simulate_fail"/>
+				</listitem>
+				<listitem name="{@sha2_chk=Use SHA2 for hashing}">
+					<condition var1="tw_no_sha2" var2="0"/>
+					<data variable="tw_use_sha2"/>
+				</listitem>
+				<listitem name="{@unmount_sys_install=Unmount System before installing a ZIP}">
+					<data variable="tw_unmount_system"/>
+				</listitem>
+				<listitem name="{@auto_reflashtwrp_chk=Automatically Reflash TWRP after flashing a ROM}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<data variable="tw_auto_reflashtwrp"/>
+				</listitem>
+			</listbox>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@restore_defaults_btn=Restore Defaults}</text>
+				<actions>
+					<action function="set">tw_samsung_navbar=0</action>
+					<action function="set">tw_tab_icons=0</action>
+					<action function="restoredefaultsettings"/>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="settings_timezone">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@time_zone_hdr=Time Zone}</text>
+			</text>
+
+			<template name="tabs_settings"/>
+
+			<fill color="%text_color%">
+				<placement x="%tab5_col2_x%" y="%row_tab_y%" w="%tab5_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<listbox>
+				<placement x="%indent%" y="%row3_y%" w="%content_width%" h="%listbox_timezone_height%"/>
+				<text>{@sel_tz_list=Select Time Zone:}</text>
+				<data name="tw_time_zone_guisel"/>
+				<listitem name="{@utcm11=(UTC -11) Samoa, Midway Island}">BST11;BDT</listitem>
+				<listitem name="{@utcm10=(UTC -10) Hawaii}">HST10;HDT</listitem>
+				<listitem name="{@utcm9=(UTC -9) Alaska}">AST9;ADT</listitem>
+				<listitem name="{@utcm8=(UTC -8) Pacific Time}">PST8;PDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm7=(UTC -7) Mountain Time}">MST7;MDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm6=(UTC -6) Central Time}">CST6;CDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm5=(UTC -5) Eastern Time}">EST5;EDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm4=(UTC -4) Atlantic Time}">AST4;ADT</listitem>
+				<listitem name="{@utcm3=(UTC -3) Brazil, Buenos Aires}">GRNLNDST3;GRNLNDDT</listitem>
+				<listitem name="{@utcm2=(UTC -2) Mid-Atlantic}">FALKST2;FALKDT</listitem>
+				<listitem name="{@utcm1=(UTC -1) Azores, Cape Verde}">AZOREST1;AZOREDT</listitem>
+				<listitem name="{@utc0=(UTC  0) London, Dublin, Lisbon}">GMT0;BST,M3.5.0,M10.5.0</listitem>
+				<listitem name="{@utcp1=(UTC +1) Berlin, Brussels, Paris}">CET-1;CEST,M3.5.0,M10.5.0</listitem>
+				<listitem name="{@utcp2=(UTC +2) Athens, Istanbul, South Africa}">WET-2;WET,M3.5.0,M10.5.0</listitem>
+				<listitem name="{@utcp3=(UTC +3) Moscow, Baghdad}">SAUST-3;SAUDT</listitem>
+				<listitem name="{@utcp4=(UTC +4) Abu Dhabi, Tbilisi, Muscat}">WST-4;WDT</listitem>
+				<listitem name="{@utcp5=(UTC +5) Yekaterinburg, Islamabad}">PAKST-5;PAKDT</listitem>
+				<listitem name="{@utcp6=(UTC +6) Almaty, Dhaka, Colombo}">TASHST-6;TASHDT</listitem>
+				<listitem name="{@utcp7=(UTC +7) Bangkok, Hanoi, Jakarta}">THAIST-7;THAIDT</listitem>
+				<listitem name="{@utcp8=(UTC +8) Beijing, Singapore, Hong Kong}">TAIST-8;TAIDT</listitem>
+				<listitem name="{@utcp9=(UTC +9) Tokyo, Seoul, Yakutsk}">JST-9;JSTDT</listitem>
+				<listitem name="{@utcp10=(UTC +10) Eastern Australia, Guam}">EET-10;EETDT</listitem>
+				<listitem name="{@utcp11=(UTC +11) Vladivostok, Solomon Islands}">MET-11;METDT</listitem>
+				<listitem name="{@utcp12=(UTC +12) Auckland, Wellington, Fiji}">NZST-12;NZDT</listitem>
+			</listbox>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row15a_y%"/>
+				<text>{@sel_tz_offset=Select Offset (usually 0): %tw_time_zone_guioffset%}</text>
+			</text>
+
+			<button style="button_quarter_width">
+				<placement x="%indent%" y="%row16a_y%"/>
+				<text>{@tz_offset_none=None}</text>
+				<action function="set">tw_time_zone_guioffset=0</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col2_x%" y="%row16a_y%"/>
+				<text>{@tz_offset_15=15}</text>
+				<action function="set">tw_time_zone_guioffset=15</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col3_x%" y="%row16a_y%"/>
+				<text>{@tz_offset_30=30}</text>
+				<action function="set">tw_time_zone_guioffset=30</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col4_x%" y="%row16a_y%"/>
+				<text>{@tz_offset_45=45}</text>
+				<action function="set">tw_time_zone_guioffset=45</action>
+			</button>
+
+			<checkbox>
+				<placement x="%indent%" y="%row18a_y%"/>
+				<font resource="font_m" color="%text_color%"/>
+				<text>{@use_dst_chk=Use daylight savings time (DST)}</text>
+				<data variable="tw_time_zone_guidst"/>
+				<image checked="checkbox_true" unchecked="checkbox_false"/>
+			</checkbox>
+
+			<text color="%text_color%">
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row20_y%"/>
+				<text>{@curr_tz=Current Time Zone: %tw_time_zone%}</text>
+			</text>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<font resource="font_m" color="%text_button_color%"/>
+				<text>{@set_tz_btn=Set Time Zone}</text>
+				<image resource="main_button_half_height"/>
+				<action function="setguitimezone"/>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="settings_screen">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@settings_screen_hdr=Screen Settings}</text>
+			</text>
+
+			<template name="tabs_settings"/>
+
+			<fill color="%text_color%">
+				<placement x="%tab5_col3_x%" y="%row_tab_y%" w="%tab5_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<button>
+				<placement x="%indent%" y="%row3a_y%" textplacement="6"/>
+				<font resource="font_m" color="%text_color%"/>
+				<condition var1="tw_screen_timeout_secs" op="=" var2="0"/>
+				<condition var1="tw_no_screen_timeout" op="!=" var2="1"/>
+				<text>{@enable_timeout_chk=Enable screen timeout}</text>
+				<image resource="checkbox_false"/>
+				<action function="set">tw_screen_timeout_secs=60</action>
+			</button>
+
+			<button>
+				<placement x="%indent%" y="%row3a_y%" textplacement="6"/>
+				<font resource="font_m" color="%text_color%"/>
+				<condition var1="tw_screen_timeout_secs" op="!=" var2="0"/>
+				<condition var1="tw_no_screen_timeout" op="!=" var2="1"/>
+				<text>{@enable_timeout_chk=Enable screen timeout}</text>
+				<image resource="checkbox_true"/>
+				<action function="set">tw_screen_timeout_secs=0</action>
+			</button>
+
+			<slidervalue>
+				<condition var1="tw_screen_timeout_secs" op="!=" var2="0"/>
+				<condition var1="tw_no_screen_timeout" op="!=" var2="1"/>
+				<placement x="indent" y="%row5_y%" w="%content_width%"/>
+				<text>{@screen_to_slider=Screen timeout in seconds:}</text>
+				<data variable="tw_screen_timeout_secs" min="15" max="300"/>
+			</slidervalue>
+
+			<slidervalue>
+				<condition var1="tw_has_brightnesss_file" var2="1"/>
+				<placement x="indent" y="%row10_y%" w="%content_width%"/>
+				<text>{@screen_bright_slider=Brightness: %tw_brightness_pct%%}</text>
+				<data variable="tw_brightness_pct" min="10" max="100"/>
+				<actions>
+					<action function="set">tw_brightness=%tw_brightness_max%</action>
+					<action function="compute">tw_brightness*%tw_brightness_pct%</action>
+					<action function="compute">tw_brightness/100</action>
+					<action function="setbrightness">%tw_brightness%</action>
+				</actions>
+			</slidervalue>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="settings_vibration">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@vibration_hdr=Vibration}</text>
+			</text>
+
+			<template name="tabs_settings"/>
+
+			<fill color="%text_color%">
+				<placement x="%tab5_col4_x%" y="%row_tab_y%" w="%tab5_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<text style="text_m">
+				<condition var1="tw_disable_haptics" var2="1"/>
+				<placement x="%indent%" y="%row4_y%"/>
+				<text>{@vibration_disabled=Vibration Disabled for Device}</text>
+			</text>
+
+			<slidervalue>
+				<condition var1="tw_disable_haptics" var2="0"/>
+				<placement x="indent" y="%row4_y%" w="%content_width%"/>
+				<text>{@button_vibration=Button Vibration:}</text>
+				<data variable="tw_button_vibrate" min="0" max="300"/>
+			</slidervalue>
+
+			<slidervalue>
+				<condition var1="tw_disable_haptics" var2="0"/>
+				<placement x="indent" y="%row9_y%" w="%content_width%"/>
+				<text>{@kb_vibration=Keyboard Vibration:}</text>
+				<data variable="tw_keyboard_vibrate" min="0" max="300"/>
+			</slidervalue>
+
+			<slidervalue>
+				<condition var1="tw_disable_haptics" var2="0"/>
+				<placement x="indent" y="%row14_y%" w="%content_width%"/>
+				<text>{@act_vibration=Action Vibration:}</text>
+				<data variable="tw_action_vibrate" min="0" max="500"/>
+			</slidervalue>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="settings_language">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@language_hdr=Language - %tw_language_display%}</text>
+			</text>
+
+			<template name="tabs_settings"/>
+
+			<fill color="%text_color%">
+				<placement x="%tab5_col5_x%" y="%row_tab_y%" w="%tab5_width%" h="%tab_indicator_height%"/>
+			</fill>
+
+			<listbox>
+				<placement x="%indent%" y="%row3_y%" w="%content_width%" h="%listbox_settings_height%"/>
+				<text>{@select_language=Select Language:}</text>
+				<icon selected="radio_true" unselected="radio_false" />
+				<data name="tw_language" />
+			</listbox>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@set_language_btn=Set Language}</text>
+				<action function="setlanguage"></action>
+			</button>
+
+			<action>
+				<touch key="home" />
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back" />
+				<action function="page">settings</action>
+			</action>
+		</page>
+
+		<page name="copylog">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@copying_log=Copy Logs to SD Card}</text>
+			</text>
+
+			<checkbox>
+				<condition var1="tw_logcat_exists" var2="1"/>
+				<placement x="%indent%" y="%row4_y%"/>
+				<text>{@include_logcat=Include Logcat}</text>
+				<data variable="tw_include_logcat" value="1"/>
+			</checkbox>
+
+			<checkbox>
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>{@include_kernel_log=Include Kernel Log}</text>
+				<data variable="tw_include_kernel_log" value="1"/>
+			</checkbox>
+
+			<slider>
+				<text>{@copying_log=Copy Logs to SD Card}</text>
+				<actions>
+					<action function="set">tw_back=advanced</action>
+					<action function="set">tw_action=copylog</action>
+					<action function="set">tw_action_text1={@copying_log=Copy Logs to SD Card}</action>
+					<action function="set">tw_complete_text1={@copy_log_complete=Logs Copy Completed}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="page">advanced</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="advanced">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<button style="main_button">
+				<placement x="%indent%" y="%row2a_y%"/>
+				<text>{@copy_log_btn=Copy Log}</text>
+				<action function="page">copylog</action>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_has_data_media" var2="1"/>
+				<placement x="%center_x%" y="%row2a_y%"/>
+				<text>{@adb_sideload_btn=ADB Sideload}</text>
+				<action function="page">sideload</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%indent%" y="%row8_y%"/>
+				<text>{@terminal_btn=Terminal}</text>
+				<action function="page">terminalcommand</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%center_x%" y="%row8_y%"/>
+				<text>{@file_manager_btn=File Manager}</text>
+				<action function="page">filemanagerlist</action>
+			</button>
+
+			<listbox style="advanced_listbox">
+				<placement x="%indent%" y="%row13a_y%" w="%content_width%" h="%listbox_advanced_height%"/>
+				<listitem name="{@change_twrp_folder_btn=Change TWRP folder}">
+					<condition var1="tw_is_decrypted" var2="1"/>
+					<action function="page">changeTwrpFolder</action>
+				</listitem>
+				<listitem name="{@decrypt_users=Decrypt Users}">
+					<conditions>
+						<condition var1="tw_is_fbe" var2="1"/>
+						<condition var1="tw_all_users_decrypted" var2="0"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_crypto_user_id=</action>
+						<action function="page">decrypt_users</action>
+					</actions>
+				</listitem>
+				<listitem name="{@reload_theme_btn=Reload Theme}">
+					<action function="reload"/>
+				</listitem>
+				<listitem name="{@part_sd_btn=Partition SD Card}">
+					<condition var1="tw_allow_partition_sdcard" var2="1"/>
+					<actions>
+						<action function="set">partitionlisterror=0</action>
+						<action function="page">partsdcardsel</action>
+					</actions>
+				</listitem>
+				<listitem name="{@fix_context_btn=Fix Contexts}">
+					<condition var1="tw_has_data_media" var2="1"/>
+					<action function="page">fixcontexts</action>
+				</listitem>
+				<listitem name="{@dumlock_btn=HTC Dumlock}">
+					<condition var1="tw_show_dumlock" var2="1"/>
+					<action function="page">htcdumlock</action>
+				</listitem>
+				<listitem name="{@inject_twrp_btn=Inject TWRP}">
+					<condition var1="tw_has_injecttwrp" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=reinjecttwrp</action>
+						<action function="set">tw_text1={@inject_twrp_confirm=Re-Inject TWRP?}</action>
+						<action function="set">tw_action_text1={@injecting_twrp=Re-Injecting TWRP...}</action>
+						<action function="set">tw_complete_text1={@inject_twrp_complete=TWRP Injection Complete}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@install_twrp_ramdisk=Install Recovery Ramdisk}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_repack_kernel=0</action>
+						<action function="page">repackselect</action>
+					</actions>
+				</listitem>
+				<listitem name="{@reflash_twrp=Flash Current TWRP}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_repack_kernel=0</action>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=reflashtwrp</action>
+						<action function="set">tw_text1={@reflash_twrp_confirm=Flash Current TWRP?}</action>
+						<action function="set">tw_action_text1={@reflashing_twrp=Flashing TWRP...}</action>
+						<action function="set">tw_complete_text1={@reflash_twrp_complete=Done Flashing TWRP}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+						</actions>
+				</listitem>
+				<listitem name="{@install_kernel=Install Kernel}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_repack_kernel=1</action>
+						<action function="page">repackselect</action>
+					</actions>
+				</listitem>
+				<listitem name="{@fix_recovery_loop=Fix Recovery Bootloop}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+						<condition var1="tw_uses_initramfs" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=fixabrecoverybootloop</action>
+						<action function="set">tw_text1={@fix_recovery_loop_confirm=Fix Recovery Bootloop?}</action>
+						<action function="set">tw_action_text1={@fixing_recovery_loop=Fixing Recovery Bootloop...}</action>
+						<action function="set">tw_complete_text1={@fix_recovery_loop_complete=Fix Recovery Bootloop Complete}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@reboot_install_app_hdr=Install TWRP App}">
+					<condition var1="tw_app_install_status" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_appinstall_title={@advanced_hdr=Advanced}</action>
+						<action function="page">installapp</action>
+					</actions>
+				</listitem>
+				<listitem name="{@uninstall_twrp_system_app=Uninstall TWRP App from System}">
+					<condition var1="tw_app_installed_in_system" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=uninstalltwrpsystemapp</action>
+						<action function="set">tw_text1={@uninstall_twrp_system_app_confirm=Uninstall TWRP App from System?}</action>
+						<action function="set">tw_action_text1={@uninstalling_twrp_system_app=Uninstalling TWRP App from System...}</action>
+						<action function="set">tw_complete_text1={@uninstall_twrp_system_app_complete=Uninstall TWRP App from System Complete}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@unmap_super_devices=Unmap Super Devices}">
+					<condition var1="tw_virtual_ab.enabled" op="=" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=unmapsuperdevices</action>
+						<action function="set">tw_text1={@unmap_super_devices_confirm=Unmap all Super Devices?}</action>
+						<action function="set">tw_action_text1={@unmapping_super_devices=Unmapping Super Devices...}</action>
+						<action function="set">tw_complete_text1={@unmap_super_devices_complete=Unmapped all Super Devices}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@merges_snapshots=Merge Snapshots}">
+					<condition var1="tw_virtual_ab.enabled" op="=" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=mergesnapshots</action>
+						<action function="set">tw_text1={@merge_snapshots_confirm=Merge Snapshots?}</action>
+						<action function="set">tw_action_text1={@merging_snapshots=Merging Snapshots...}</action>
+						<action function="set">tw_complete_text1={@merging_snapshots_complete=Merged Snapshots}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+			</listbox>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="partsdcardsel">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@part_sd_hdr=Partition SD Card}</text>
+			</text>
+
+			<partitionlist>
+				<placement x="%indent%" y="%row1a_y%" w="%content_width%" h="%partitionlist_storage_height%"/>
+				<text>{@sel_storage_list=Select Storage}</text>
+				<data name="tw_storage_path"/>
+				<listtype name="storage"/>
+			</partitionlist>
+
+			<text style="text_m_fail">
+				<condition var1="partitionlisterror" var2="1"/>
+				<placement x="%center_x%" y="%row20_y%" placement="5"/>
+				<text>{@invalid_partsd_sel=You must select a removable device}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@ok_btn=OK}</text>
+				<actions>
+					<action function="getpartitiondetails">tw_storage_path</action>
+					<action function="page">partsdcardcheck</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="partsdcardcheck">
+			<action>
+				<condition var1="tw_partition_removable" op="=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="page">partsdcard</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_partition_removable" op="!=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=1</action>
+					<action function="page">partsdcardsel</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="partsdcard">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@part_sd_hdr=Partition SD Card}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@part_sd_lose=You will lose all files on your SD card!}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@part_sd_undo=This action cannot be undone!}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@part_sd_ext_sz=EXT Size:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6a_y%" placement="5"/>
+				<text>%tw_sdext_size%</text>
+			</text>
+
+			<button style="button_quarter_width">
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>{@part_sd_m=-}</text>
+				<action function="addsubtract">tw_sdext_size-256</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col4_x%" y="%row6_y%"/>
+				<text>{@part_sd_p=+}</text>
+				<action function="addsubtract">tw_sdext_size+256</action>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row9_y%" placement="5"/>
+				<text>{@part_sd_swap_sz=Swap Size:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row10a_y%" placement="5"/>
+				<text>%tw_swap_size%</text>
+			</text>
+
+			<button style="button_quarter_width">
+				<placement x="%indent%" y="%row10_y%"/>
+				<text>{@part_sd_m=-}</text>
+				<action function="addsubtract">tw_swap_size-64</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col4_x%" y="%row10_y%"/>
+				<text>{@part_sd_p=+}</text>
+				<action function="addsubtract">tw_swap_size+64</action>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row13_y%" placement="5"/>
+				<text>{@file_system=File System:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row14a_y%" placement="5"/>
+				<text>%tw_sdpart_file_system%</text>
+			</text>
+
+			<button style="button_quarter_width">
+				<placement x="%indent%" y="%row14_y%"/>
+				<text>EXT3</text>
+				<action function="set">tw_sdpart_file_system=ext3</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col4_x%" y="%row14_y%"/>
+				<text>EXT4</text>
+				<action function="set">tw_sdpart_file_system=ext4</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_part_sd=Swipe to Partition}</text>
+				<action function="page">partsdcardaction</action>
+				<actions>
+					<action function="set">tw_back=partsdcard</action>
+					<action function="set">tw_action=partitionsd</action>
+					<action function="set">tw_has_action2=1</action>
+					<action function="set">tw_action2=set</action>
+					<action function="set">tw_action2_param=tw_zip_location=/sdcard</action>
+					<action function="set">tw_action_text1={@partitioning_sd=Partitioning SD Card...}</action>
+					<action function="set">tw_action_text2={@partitioning_sd2=This will take a few minutes.}</action>
+					<action function="set">tw_complete_text1={@part_sd_complete=Partitioning Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="page">partsdcardsel</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="htcdumlock">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@dumlock_hdr=HTC Dumlock}</text>
+			</text>
+
+			<button style="main_button_half_height_full_width">
+				<placement x="%indent%" y="%row15a_y%"/>
+				<text>{@dumlock_restore_btn=Restore Original Boot}</text>
+				<actions>
+					<action function="set">tw_back=htcdumlock</action>
+					<action function="set">tw_action=htcdumlockrestoreboot</action>
+					<action function="set">tw_text1={@dumlock_restore_confirm=Restore original boot image?}</action>
+					<action function="set">tw_action_text1={@dumlock_restoring=Restoring Original Boot...}</action>
+					<action function="set">tw_complete_text1={@dumlock_restore_complete=Restore Original Boot Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height_full_width">
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@dumlock_reflash_btn=Reflash Recovery}</text>
+				<actions>
+					<action function="set">tw_back=htcdumlock</action>
+					<action function="set">tw_action=htcdumlockreflashrecovery</action>
+					<action function="set">tw_text1={@dumlock_reflash_confirm=Reflash recovery to boot?}</action>
+					<action function="set">tw_action_text1={@dumlock_reflashing=Flashing recovery to boot...}</action>
+					<action function="set">tw_complete_text1={@dumlock_reflash_complete=Recovery Flash to Boot Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height_full_width">
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@dumlock_install_btn=Install HTC Dumlock}</text>
+				<actions>
+					<action function="set">tw_back=htcdumlock</action>
+					<action function="set">tw_action=installhtcdumlock</action>
+					<action function="set">tw_text1={@dumlock_install_confirm=Install HTC dumlock files to ROM?}</action>
+					<action function="set">tw_action_text1={@dumlock_installing=Installing HTC Dumlock...}</action>
+					<action function="set">tw_complete_text1={@dumlock_install_complete=HTC Dumlock Install Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="repackselect">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@repack_image_hdr=Select Image}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@select_file_from_storage=Select File from %tw_storage_display_name% (%tw_storage_free_size% MB)}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%indent%" y="%row3_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>%tw_zip_location%</text>
+				<filter extn=".img" folders="1" files="1"/>
+				<path name="tw_zip_location" default="/sdcard"/>
+				<data name="tw_filename"/>
+				<selection name="tw_file"/>
+			</fileselector>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@select_storage_btn=Select Storage}</text>
+				<actions>
+					<action function="set">tw_back=repackselect</action>
+					<action function="overlay">select_storage</action>
+				</actions>
+			</button>
+
+			<action>
+				<conditions>
+					<condition var1="tw_filename" op="modified"/>
+				</conditions>
+				<action function="page">repackconfirm</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="repackconfirm">
+			<template name="page"/>
+
+			<text style="text_l">
+				<condition var1="tw_repack_kernel" var2="1"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@repack_kernel_confirm_hdr=Install Kernel}</text>
+			</text>
+
+			<text style="text_l">
+				<condition var1="tw_repack_kernel" var2="0"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@repack_ramdisk_confirm_hdr=Install Recovery}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_repack_kernel" var2="1"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@repack_kernel_confirm=Install Kernel?}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_repack_kernel" var2="0"/>
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@repack_ramdisk_confirm=Install Recovery?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@folder=Folder:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row3_y%"/>
+				<text>%tw_zip_location%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row4_y%"/>
+				<text>{@file=File:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<checkbox>
+				<placement x="%indent%" y="%row7_y%"/>
+				<text>{@repack_backup_first=Back up existing image first}</text>
+				<data variable="tw_repack_backup_first"/>
+			</checkbox>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row16_y%"/>
+				<text>{@install_cancel=Do not Install}</text>
+				<action function="page">repackselect</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_to_install=Swipe to Install}</text>
+				<actions>
+					<action function="set">tw_back=advanced</action>
+					<action function="set">tw_action=repackimage</action>
+					<action function="set">tw_action_param=/boot</action>
+					<action function="set">tw_action_text1={@installing=Installing...}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@install_complete=Install Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">repackselect</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="lock">
+			<background color="%semi_transparent%"/>
+
+			<image>
+				<image resource="unlock_icon"/>
+				<placement x="%center_x%" y="%row10_y%" placement="4"/>
+			</image>
+
+			<slider>
+				<text>{@swipe_to_unlock=Swipe to Unlock}</text>
+				<action function="overlay"/>
+			</slider>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+		</page>
+
+		<page name="filemanagerlist">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_sel_file=Select a File or Folder}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%indent%" y="%row3_y%" w="%content_width%" h="%fileselector_filemanager_height%"/>
+				<text>%tw_file_location1%</text>
+				<filter folders="1" files="1"/>
+				<path name="tw_file_location1" default="/"/>
+				<data name="tw_filename1"/>
+				<selection name="tw_selection1"/>
+			</fileselector>
+
+			<button style="fab">
+				<image resource="fab_selectfolder"/>
+				<actions>
+					<action function="set">tw_filename1=tw_file_location1</action>
+					<action function="set">tw_fm_isfolder=1</action>
+					<action function="set">tw_fm_type={@fm_type_folder=Folder}</action>
+					<action function="page">filemanageroptions</action>
+				</actions>
+			</button>
+
+			<action>
+				<actions>
+					<action function="set">tw_fm_type={@fm_type_file=File}</action>
+					<action function="set">tw_fm_isfolder=0</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+
+			<action>
+				<condition var1="tw_filename1" op="modified"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanageroptions">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_choose_act=Choose Action}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@fm_selected=%tw_fm_type% selected:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row3_y%"/>
+				<text>%tw_filename1%</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<conditions>
+					<condition var1="tw_include_nano" var2="1"/>
+					<condition var1="tw_fm_isfolder" var2="0"/>
+				</conditions>
+				<placement x="%indent%" y="%row12a_y%"/>
+				<text>{@fm_edit_file=Edit File}</text>
+				<actions>
+					<action function="editfile">nano "%tw_filename1%"</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_fm_isfolder" var2="1"/>
+				<placement x="%center_x%" y="%row12a_y%"/>
+				<text>{@fm_open_terminal_btn=Open Terminal Here}</text>
+				<action function="changeterminal">%tw_filename1%</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_fm_isfolder" var2="0"/>
+				<placement x="%indent%" y="%row15a_y%"/>
+				<text>{@fm_copy_file_btn=Copy File}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=cp</action>
+					<action function="set">tw_fm_text1={@fm_copying=Copying}</action>
+					<action function="page">choosedestinationfolder</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_fm_isfolder" var2="1"/>
+				<placement x="%indent%" y="%row15a_y%"/>
+				<text>{@fm_copy_folder_btn=Copy Folder}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=cd "%tw_file_location1%" &amp;&amp; cd .. &amp;&amp; cp -R</action>
+					<action function="set">tw_fm_text1={@fm_copying=Copying}</action>
+					<action function="page">choosedestinationfolder</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row15a_y%"/>
+				<text>{@fm_move_btn=Move}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=mv</action>
+					<action function="set">tw_fm_text1={@fm_moving=Moving}</action>
+					<action function="page">choosedestinationfolder</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row18a_y%"/>
+				<text>{@fm_chmod755_btn=chmod 755}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=chmod 755</action>
+					<action function="set">tw_fm_text1={@fm_chmod755ing=chmod 755}</action>
+					<action function="set">tw_fm_text2=</action>
+					<action function="set">tw_fm_text3=</action>
+					<action function="set">tw_include_text3=0</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row18a_y%"/>
+				<text>{@fm_chmod_btn=chmod}</text>
+				<actions>
+					<action function="set">tw_filemanager_rename=0000</action>
+					<action function="set">tw_fm_text2=</action>
+					<action function="set">tw_fm_text3=</action>
+					<action function="set">tw_include_text3=0</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerchmod</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@fm_delete_btn=Delete}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=rm -rf</action>
+					<action function="set">tw_fm_text1={@fm_deleting=Deleting}</action>
+					<action function="set">tw_fm_text2=</action>
+					<action function="set">tw_fm_text3=</action>
+					<action function="set">tw_include_text3=0</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_fm_isfolder" var2="0"/>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@fm_rename_file_btn=Rename File}</text>
+				<actions>
+					<action function="set">tw_filemanager_rename=tw_selection1</action>
+					<action function="set">tw_fm_text1={@fm_renaming=Renaming}</action>
+					<action function="set">tw_filemanager_command=mv</action>
+					<action function="page">filemanagerrenamefile</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_fm_isfolder" var2="1"/>
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@fm_rename_folder_btn=Rename Folder}</text>
+				<actions>
+					<action function="set">tw_filemanager_rename=tw_selection1</action>
+					<action function="set">tw_fm_text1={@fm_renaming=Renaming}</action>
+					<action function="set">tw_filemanager_command=cd "%tw_file_location1%" &amp;&amp; cd .. &amp;&amp; mv</action>
+					<action function="page">filemanagerrenamefolder</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanagerlist</action>
+			</action>
+		</page>
+
+		<page name="choosedestinationfolder">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_sel_dest=Select Destination Folder}</text>
+			</text>
+
+			<template name="sort_options"/>
+
+			<fileselector>
+				<placement x="%indent%" y="%row3_y%" w="%content_width%" h="%fileselector_filemanager_height%"/>
+				<text>%tw_file_location2%</text>
+				<filter folders="1" files="0"/>
+				<path name="tw_file_location2" default="/"/>
+				<data name="tw_filename2"/>
+				<selection name="tw_selection2"/>
+			</fileselector>
+
+			<button style="fab">
+				<image resource="fab_selectfolder"/>
+				<actions>
+					<action function="set">tw_fm_text2=to</action>
+					<action function="set">tw_fm_text3=%tw_file_location2%</action>
+					<action function="set">tw_include_text3=1</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerrenamefile">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_rename_hdr=Rename} %tw_fm_type%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_filemanager_rename%</text>
+				<data name="tw_filemanager_rename"/>
+				<restrict minlen="1" maxlen="128"/>
+				<actions>
+					<action function="set">tw_fm_text2=to</action>
+					<action function="set">tw_fm_text3="%tw_file_location1%/%tw_filemanager_rename%"</action>
+					<action function="set">tw_include_text3=1</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">filemanageroptions</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerrenamefolder">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_rename_hdr=Rename} %tw_fm_type%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_filemanager_rename%</text>
+				<data name="tw_filemanager_rename"/>
+				<restrict minlen="1" maxlen="128"/>
+				<actions>
+					<action function="set">tw_fm_text2=to</action>
+					<action function="set">tw_fm_text3=%tw_filemanager_rename%</action>
+					<action function="set">tw_include_text3=1</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">filemanageroptions</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerchmod">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fm_set_perms_hdr=Set Permissions}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@fm_perms=Permissions:}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_filemanager_rename%</text>
+				<data name="tw_filemanager_rename"/>
+				<restrict minlen="3" maxlen="4" allow="0123456789"/>
+				<actions>
+					<action function="set">tw_filemanager_command=chmod %tw_filemanager_rename%</action>
+					<action function="set">tw_fm_text1=chmod %tw_filemanager_rename%</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">filemanageroptions</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerconfirm">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@confirm_action=Confirm Action}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>%tw_fm_text1%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>%tw_filename1%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>%tw_fm_text2%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>%tw_fm_text3%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row8_y%" placement="5"/>
+				<text>{@back_cancel=Press back button to cancel.}</text>
+			</text>
+
+			<slider>
+				<text>{@swipe_to_confirm=Swipe to Confirm}</text>
+				<action function="page">filemanageraction</action>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">%tw_back%</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="filemanageraction">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fm_hdr=File Manager}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>%tw_fm_text1%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_back=filemanagerlist</action>
+					<action function="set">tw_complete_text1={@fm_complete=File Operation Complete}</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="!=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_complete_text1={@fm_complete=File Operation Complete}</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_include_text3" var2="0"/>
+				<action function="cmd">%tw_filemanager_command% "%tw_filename1%"</action>
+			</action>
+
+			<action>
+				<condition var1="tw_include_text3" var2="1"/>
+				<action function="cmd">%tw_filemanager_command% "%tw_filename1%" "%tw_fm_text3%"</action>
+			</action>
+		</page>
+
+		<page name="decrypt">
+			<template name="page"/>
+
+			<action>
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<action function="page">decrypt_pattern</action>
+			</action>
+
+			<action>
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<action function="page">decrypt_pin</action>
+			</action>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@decrypt_data_hdr=Decrypt Data}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" op="!=" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pass=Enter Password:}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pass_fbe=Enter Password for User [%tw_crypto_user_id%]}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row3_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_crypto_display%</text>
+				<data name="tw_crypto_password" mask="*" maskvariable="tw_crypto_display"/>
+				<restrict minlen="1" maxlen="254"/>
+				<action function="page">trydecrypt</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row4a_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%indent%" y="row4a_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@decrypt_data_failed=Password failed, please try again!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">canceldecrypt</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row10_y%"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="overlay">select_language</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+		</page>
+
+		<page name="decrypt_pattern">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@decrypt_data_hdr=Decrypt Data}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" op="!=" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pattern=Enter Pattern.}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pattern_fbe=Enter Pattern for User [%tw_crypto_user_id%]}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@decrypt_data_failed_pattern=Pattern failed, please try again!}</text>
+			</text>
+
+			<patternpassword>
+				<placement x="%pattern_x%" y="%row5_y%" w="%pattern_size%" h="%pattern_size%"/>
+				<dot color="%fileselector_linecolor%" activecolor="%accent_color%" radius="%pattern_dot_dia%"/>
+				<line color="%fileselector_linecolor%" width="%pattern_line_w%"/>
+				<data name="tw_crypto_password"/>
+				<action function="page">trydecrypt</action>
+			</patternpassword>
+
+			<button style="button_quarter_width">
+				<placement x="%indent%" y="%row19a_y%"/>
+				<text>3x3</text>
+				<action function="set">tw_gui_pattern_grid_size=3</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col2_x%" y="%row19a_y%"/>
+				<text>4x4</text>
+				<action function="set">tw_gui_pattern_grid_size=4</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col3_x%" y="%row19a_y%"/>
+				<text>5x5</text>
+				<action function="set">tw_gui_pattern_grid_size=5</action>
+			</button>
+
+			<button style="button_quarter_width">
+				<placement x="%btn4_col4_x%" y="%row19a_y%"/>
+				<text>6x6</text>
+				<action function="set">tw_gui_pattern_grid_size=6</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">canceldecrypt</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="overlay">select_language</action>
+			</button>
+		</page>
+
+		<page name="decrypt_pin">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@decrypt_data_hdr=Decrypt Data}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" op="!=" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pass=Enter PIN:}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pass_fbe=Enter PIN for User [%tw_crypto_user_id%]}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row3_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_crypto_display%</text>
+				<data name="tw_crypto_password" mask="*" maskvariable="tw_crypto_display"/>
+				<restrict minlen="1" maxlen="254"/>
+				<action function="page">trydecrypt</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row4a_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%indent%" y="row4a_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@decrypt_data_failed=PIN failed, please try again!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">canceldecrypt</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row10_y%"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="overlay">select_language</action>
+			</button>
+
+			<template name="keyboardnum"/>
+		</page>
+
+		<page name="trydecrypt">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@mount_hdr=Mount}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@decrypt_data_trying=Trying Decryption}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<action function="decrypt"/>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="!=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_password_fail=1</action>
+					<action function="page">decrypt</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="canceldecrypt">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@refresh_sizes_btn=Refresh Sizes}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<action function="refreshsizes"/>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="terminalcommand">
+			<template name="page"/>
+
+			<fill color="%background_color%">
+				<placement x="0" y="%row2_header_y%" w="%screen_width%" h="%slideout_bg_height%"/>
+			</fill>
+
+			<terminal>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%row3_header_y%" w="%screen_width%" h="%console_terminal_height%"/>
+			</terminal>
+
+			<terminal>
+				<condition var1="tw_hide_kb" var2="1"/>
+				<placement x="0" y="%row3_header_y%" w="%screen_width%" h="%slideout_height%"/>
+			</terminal>
+
+			<template name="keyboardterminaltemplate"/>
+
+			<template name="keyboardtemplate"/>
+
+			<fill color="#000000">
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%" />
+			</fill>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="key">back</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="0" />
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_hide" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=1</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="1" />
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_show" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="key">home</action>
+					<action function="changeterminal"/>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="0" />
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_hide" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=1</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1" />
+				<condition var1="tw_hide_kb" op="=" var2="1" />
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_show" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1" />
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="key">back</action>
+					<action function="changeterminal"/>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">main</action>
+					<action function="changeterminal"/>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">advanced</action>
+					<action function="changeterminal"/>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="power+voldown" />
+				<action function="screenshot" />
+			</action>
+		</page>
+
+		<page name="fastboot">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@fastboot_button=Fastboot}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_enable_fastboot" op="=" var2="1" />
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@enable_adb=Enable ADB}</text>
+				<actions>
+					<action function="enableadb"/>
+					<action function="set">tw_enable_adb=1</action>
+					<action function="set">tw_enable_fastboot=0</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_enable_adb" op="=" var2="1" />
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@enable_fastboot=Enable Fastboot}</text>
+				<actions>
+					<action function="enablefastboot"/>
+					<action function="set">tw_enable_fastboot=1</action>
+					<action function="set">tw_enable_adb=0</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@reboot_btn=Reboot}</text>
+				<action function="page">fastbootreboot</action>
+			</button>
+		</page>
+
+		<page name="sideload">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@adb_sideload_hdr=ADB Sideload}</text>
+			</text>
+
+			<checkbox>
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@sideload_wipe_dalvik_chk=Wipe Dalvik Cache}</text>
+				<data variable="tw_wipe_dalvik"/>
+			</checkbox>
+
+			<checkbox>
+				<placement x="%indent%" y="%row3a_y%"/>
+				<text>{@sideload_wipe_cache_chk=Wipe Cache}</text>
+				<data variable="tw_wipe_cache"/>
+			</checkbox>
+
+			<slider>
+				<text>{@swipe_to_sideload=Swipe to Start Sideload}</text>
+				<actions>
+					<action function="set">tw_back=advanced</action>
+					<action function="set">tw_action=adbsideload</action>
+					<action function="set">tw_action_text1={@sideload_confirm=ADB Sideload}</action>
+					<action function="set">tw_action_text2={@sideload_usage=Usage: adb sideload filename.zip}</action>
+					<action function="set">tw_complete_text1={@sideload_complete=ADB Sideload Complete}</action>
+					<action function="set">tw_has_cancel=1</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="set">tw_cancel_action=adbsideloadcancel</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="fixcontexts">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@fix_contexts_hdr=Fix Contexts}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@fix_contexts_note1=Note: Fixing contexts is rarely needed.}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@fix_contexts_note2=Fixing SELinux Contexts may cause}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@fix_contexts_note3=your device to not boot properly.}</text>
+			</text>
+
+			<slider>
+				<text>{@swipe_to_fix_contexts=Swipe to Fix Contexts}</text>
+				<actions>
+					<action function="set">tw_back=advanced</action>
+					<action function="set">tw_action=fixcontexts</action>
+					<action function="set">tw_action_text1={@fixing_contexts=Fixing Contexts...}</action>
+					<action function="set">tw_complete_text1={@fix_contexts_complete=Fix Contexts Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="slideout">
+			<fill color="%background_color%">
+				<placement x="0" y="%row2_header_y%" w="%screen_width%" h="%slideout_bg_height%"/>
+			</fill>
+
+			<console>
+				<placement x="%indent%" y="%row3_header_y%" w="%content_width%" h="%slideout_height%"/>
+			</console>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%"/>
+				<action function="overlay"/>
+			</button>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="select_storage">
+			<fill color="%semi_transparent%">
+				<placement x="0" y="0" w="%screen_width%" h="%screen_height%"/>
+			</fill>
+
+			<fill color="%background_color%">
+				<placement x="%indent%" y="row5_y" w="%content_width%" h="%dialog_height%"/>
+			</fill>
+
+			<partitionlist style="partitionlist_storage">
+				<text>{@sel_storage_list=Select Storage}</text>
+				<data name="tw_storage_path"/>
+				<listtype name="storage"/>
+			</partitionlist>
+
+			<button style="button_third_width">
+				<placement x="%dialog_button_x%" y="%row14_y%"/>
+				<text>{@ok_btn=OK}</text>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="overlay"/>
+					<action function="page">clear_vars</action>
+				</actions>
+			</button>
+
+			<fill color="#000000">
+				<placement x="0" y="row25_y" w="%screen_width%" h="%navbar_height"/>
+			</fill>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<actions>
+					<action function="overlay"/>
+					<action function="key">home</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="select_language">
+			<fill color="%semi_transparent%">
+				<placement x="0" y="0" w="%screen_width%" h="%screen_height%"/>
+			</fill>
+
+			<fill color="%background_color%">
+				<placement x="%indent%" y="row5_y" w="%content_width%" h="%dialog_height%"/>
+			</fill>
+
+			<listbox>
+				<placement x="%col1_x_left%" y="%row5_y%" w="%content_overlay_width%" h="%partitionlist_storage_height%"/>
+				<text>{@select_language=Select Language:}</text>
+				<icon selected="radio_true" unselected="radio_false" />
+				<data name="tw_language" />
+			</listbox>
+
+			<button style="button_third_width">
+				<placement x="%dialog_button_x%" y="%row14_y%"/>
+				<text>{@ok_btn=OK}</text>
+				<actions>
+					<action function="overlay"/>
+					<action function="setlanguage"></action>
+				</actions>
+			</button>
+
+			<fill color="#000000">
+				<placement x="0" y="row25_y" w="%screen_width%" h="%navbar_height"/>
+			</fill>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<actions>
+					<action function="overlay"/>
+					<action function="key">home</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<actions>
+					<action function="overlay"/>
+					<action function="key">%tw_back%</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="decrypt_users">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@decrypt_users=Decrypt Users}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_users_selection=Select a user ID to decrypt}</text>
+			</text>
+
+			<listbox>
+				<placement x="%indent%" y="%row3_y%" w="%content_width%" h="%listbox_advanced_height%"/>
+				<text>{@select_user=Select User}</text>
+				<icon selected="radio_true" unselected="radio_false" />
+				<data name="tw_crypto_user_id" />
+			</listbox>
+
+			<button style="main_button_half_height">
+				<placement x="%center_x%" y="%row21a_y%"/>
+				<text>{@decrypt_users=Decrypt Users}</text>
+				<action function="set">tw_crypto_password=</action>
+				<action function="set">tw_password_fail=0</action>
+				<action function="page">decrypt</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="changeTwrpFolder">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_header%" y="%row4_header_y%"/>
+				<text>{@change_twrp_folder_btn=Change TWRP folder}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%indent%" y="%row2_input_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_custom_folder%</text>
+				<data name="tw_custom_folder"/>
+				<restrict minlen="1" maxlen="64" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"/>
+				<actions>
+					<action function="set">tw_back=changeTwrpFolder</action>
+					<action function="set">tw_action=applycustomtwrpfolder</action>
+					<action function="set">tw_action_param=%tw_custom_folder%</action>
+					<action function="set">tw_text1={@confirm_action=Confirm}</action>
+					<action function="set">tw_text2={@rename_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@change_twrp_folder_on_process=Changing TWRP folder}</action>
+					<action function="set">tw_complete_text1={@change_twrp_folder_after_process=TWRP folder changed to} %tw_custom_folder%</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="set">tw_filecheck=/sdcard/%tw_custom_folder%</action>
+					<action function="set">tw_existpage=changeTwrpFolder</action>
+					<action function="set">tw_notexistpage=confirm_action</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%indent%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%indent%" y="%row4a_y%"/>
+				<text>{@tw_folder_exists=A folder with that name already exists!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row10_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="page">advanced</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_recovery_folder" op="!=" var2="/TWRP"/>
+				<placement x="%center_x%" y="%row10_y%"/>
+				<text>{@restore_defaults_btn=Restore Defaults}</text>
+				<actions>
+					<action function="set">tw_back=changeTwrpFolder</action>
+					<action function="set">tw_action=applycustomtwrpfolder</action>
+					<action function="set">tw_action_param=TWRP</action>
+					<action function="set">tw_text1={@confirm_action=Confirm}</action>
+					<action function="set">tw_text2={@rename_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@change_twrp_folder_on_process=Changing TWRP folder}</action>
+					<action function="set">tw_complete_text1={@change_twrp_folder_after_process=TWRP folder changed to} TWRP</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="set">tw_filecheck=/sdcard/TWRP</action>
+					<action function="set">tw_existpage=changeTwrpFolder</action>
+					<action function="set">tw_notexistpage=confirm_action</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="page">advanced</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore_keymaster">
+			<template name="page"/>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@restore_with_pin_password1=PIN/Password is enabled}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="1"/>
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@restore_with_pin_password2=PIN/Password should be disabled before restore}</text>
+			</text>
+			<text style="text_l">
+				<condition var1="tw_crypto_pwtype" var2="1"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_pin_password=Restore While PIN/Password Enabled?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@restore_with_pattern1=Pattern is enabled}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@restore_with_pattern2=Pattern should be disabled before restore}</text>
+			</text>
+
+			<text style="text_l">
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_pattern=Restore While Pattern Enabled?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@restore_with_pin1=PIN is enabled}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@restore_with_pin2=PIN should be disabled before restore}</text>
+			</text>
+
+			<text style="text_l">
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@restore_pin=Restore While PIN Enabled?}</text>
+			</text>
+
+			<slider>
+				<text>{@continue_restore_encrypted=Continue Restore?}</text>
+				<actions>
+					<action function="page">restore_force</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="page">main</action>
+				</actions>
+			</action>
+		</page>
+	</pages>
+</recovery>
diff --git a/gui/theme/common/watch.xml b/gui/theme/common/watch.xml
new file mode 100755
index 0000000..f82c2d8
--- /dev/null
+++ b/gui/theme/common/watch.xml
@@ -0,0 +1,6278 @@
+<?xml version="1.0"?>
+<recovery>
+	<styles>
+		<style name="text_l">
+			<font resource="font_l" color="%text_color%"/>
+		</style>
+
+		<style name="text_m">
+			<font resource="font_m" color="%text_color%"/>
+		</style>
+
+		<style name="text_m_accent">
+			<font resource="font_m" color="%accent_color%"/>
+		</style>
+
+		<style name="text_m_fail">
+			<font resource="font_m" color="%text_fail_color%"/>
+		</style>
+
+		<style name="text_s">
+			<font resource="font_s" color="%text_color%"/>
+		</style>
+
+		<style name="input">
+			<background color="%background_color%"/>
+			<cursor color="%accent_color%" hasfocus="1" width="%input_line_width%"/>
+			<font resource="font_m" color="%text_color%"/>
+		</style>
+
+		<style name="checkbox">
+			<font resource="font_m" color="%text_color%"/>
+			<image checked="checkbox_true" unchecked="checkbox_false"/>
+		</style>
+
+		<style name="main_button">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_l" color="%text_button_color%"/>
+			<image resource="main_button"/>
+		</style>
+
+		<style name="main_button_full_width">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_l" color="%text_button_color%"/>
+			<image resource="main_button_full_width"/>
+		</style>
+
+		<style name="main_button_quarter_width">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_l" color="%text_button_color%"/>
+			<image resource="main_button_quarter_width"/>
+		</style>
+
+		<style name="main_button_half_height">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="main_button_half_height"/>
+		</style>
+
+		<style name="main_button_half_height_full_width">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="main_button_half_height_full_width"/>
+		</style>
+
+		<style name="button_third_width">
+			<highlight color="%highlight_color%"/>
+			<font resource="font_m" color="%text_button_color%"/>
+			<image resource="tab_3"/>
+		</style>
+
+		<style name="button_navbar">
+			<font resource="font_m" color="%text_button_color%"/>
+		</style>
+
+		<style name="console">
+			<color foreground="%text_color%" background="%background_color%" scroll="%background_color%"/>
+			<font resource="fixed"/>
+		</style>
+
+		<style name="terminal">
+			<fastscroll linecolor="%transparent%" rectcolor="%accent_color%" w="%fastscroll_w%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<background color="%background_color%"/>
+			<font resource="fixed" spacing="2" color="%text_color%"/>
+		</style>
+
+		<style name="fileselector">
+			<highlight color="%fileselector_highlight_color%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%fileselector_linecolor%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<sort name="tw_gui_sort_order"/>
+			<icon folder="folder" file="file"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="listbox">
+			<highlight color="%fileselector_highlight_color%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<icon selected="radio_true" unselected="radio_false"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="listbox_headerless">
+			<highlight color="%fileselector_highlight_color%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<icon selected="radio_true" unselected="radio_false"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="listbox_options">
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+			<icon selected="checkbox_true_small" unselected="checkbox_false_small"/>
+		</style>
+
+		<style name="scrolllist">
+			<highlight color="%fileselector_highlight_color%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<icon selected="checkbox_true" unselected="checkbox_false"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+		</style>
+
+		<style name="partitionlist">
+			<highlight color="%fileselector_highlight_color%"/>
+			<header background="%background_color%" textcolor="%accent_color%" separatorcolor="%accent_color%" separatorheight="%fileselector_separatorheight%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<icon selected="checkbox_true" unselected="checkbox_false"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%partitionlist_spacing%" color="%text_color%" highlightcolor="%fileselector_highlight_font_color%"/>
+		</style>
+
+		<style name="partitionlist_summary">
+			<highlight color="%fileselector_highlight_color%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%partitionlist_spacing%" color="%text_color%" highlightcolor="%fileselector_highlight_font_color%"/>
+			<icon selected="checkbox_true_small" unselected="checkbox_false_small"/>
+		</style>
+
+		<style name="partitionlist_headerless_rb">
+			<highlight color="%fileselector_highlight_color%"/>
+			<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%partitionlist_storage_height%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%fileselector_linecolor%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<icon selected="radio_true" unselected="radio_false"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%partitionlist_spacing%" color="%text_color%" highlightcolor="%fileselector_highlight_font_color%"/>
+		</style>
+
+		<style name="partitionlist_headerless_cb">
+			<highlight color="%fileselector_highlight_color%"/>
+			<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%partitionlist_storage_height%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%fileselector_linecolor%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<icon selected="checkbox_true" unselected="checkbox_false"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%partitionlist_spacing%" color="%text_color%" highlightcolor="%fileselector_highlight_font_color%"/>
+		</style>
+
+		<style name="advanced_listbox">
+			<highlight color="%fileselector_highlight_color%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%fileselector_linecolor%" height="%fileselector_separatorheight%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<icon selected="handle" unselected="handle"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="options_listbox">
+			<highlight color="%fileselector_highlight_color%"/>
+			<fastscroll linecolor="%fileselector_linecolor%" rectcolor="%accent_color%" w="%fastscroll_w%" linew="%fastscroll_linew%" rectw="%fastscroll_rectw%" recth="%fastscroll_recth%"/>
+			<separator color="%background_color%" height="%fileselector_separatorheight%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+			<background color="%background_color%"/>
+			<font resource="font_m" spacing="%fileselector_spacing%" color="%text_color%" highlightcolor="%accent_color%"/>
+		</style>
+
+		<style name="slider">
+			<placement x="%center_x%" y="%slider_y%"/>
+			<font resource="font_l" color="%text_color%"/>
+			<resource base="slider" used="slider_used" touch="slider_touch"/>
+		</style>
+
+		<style name="slidervalue">
+			<resource handle="handle"/>
+			<font resource="font_m" color="%text_color%"/>
+			<colors line="%fileselector_linecolor%"/>
+			<dimensions lineh="%slidervalue_lineh%" linepadding="%slidervalue_padding%" sliderw="%slidervalue_sliderw%" sliderh="%slidervalue_sliderh%"/>
+		</style>
+
+		<style name="patternpassword">
+			<size name="tw_gui_pattern_grid_size" default="3"/>
+		</style>
+	</styles>
+
+	<pages>
+		<page name="main">
+			<action>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="main2">
+			<template name="page"/>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@install_btn=Install}</text>
+				<action function="page">install_type</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_right%" y="%row1_y%"/>
+				<text>{@wipe_btn=Wipe}</text>
+				<action function="page">wipe_type1</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@backup_btn=Backup}</text>
+				<actions>
+					<action function="set">tw_back=main</action>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_right%" y="%row6_y%"/>
+				<text>{@restore_btn=Restore}</text>
+				<action function="page">restore</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@reboot_btn=Reboot}</text>
+				<action function="page">reboot</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">main3</action>
+			</button>
+		</page>
+
+		<page name="copylog">
+			<template name="page"/>
+
+			<checkbox>
+				<placement x="%indent%" y="%row1_y%"/>
+				<text>{@include_kernel_log=Include Kernel Log}</text>
+				<data variable="tw_include_kernel_log" value="1"/>
+			</checkbox>
+
+			<checkbox>
+				<condition var1="tw_logcat_exists" var2="1"/>
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@include_logcat=Include Logcat}</text>
+				<data variable="tw_include_logcat" value="1"/>
+			</checkbox>
+
+			<slider>
+				<text>{@swipe_confirm=   Confirm}</text>
+				<actions>
+					<action function="set">tw_back=main3</action>
+					<action function="set">tw_action=copylog</action>
+					<action function="set">tw_action_text1={@copying_log=Copy Logs to SD Card}</action>
+					<action function="set">tw_complete_text1={@copy_log_complete=Logs Copy Completed}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main2</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="main3">
+			<template name="page"/>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@mount_btn=Mount}</text>
+				<action function="page">mount</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_right%" y="%row1_y%"/>
+				<text>{@settings_btn=Settings}</text>
+				<action function="page">settings</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@files_btn=Files}</text>
+				<action function="page">filemanagerlist</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_right%" y="%row6_y%"/>
+				<text>{@copy_log_btn=Copy Log}</text>
+				<action function="page">copylog</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_right%" y="%row11_y%"/>
+				<text>{@advanced_btn=Advanced}</text>
+				<action function="page">advanced</action>
+			</button>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">main2</action>
+			</button>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main2</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="install_type">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_hdr=Install} &gt; {@select_type_hdr=Select Type}</text>
+			</text>
+
+			<button style="main_button_full_width">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@install_zip_btn=Install Zip}</text>
+				<actions>
+					<action function="queueclear"/>
+					<action function="set">tw_selectimage=0</action>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<button style="main_button_full_width">
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@install_image_btn=Install Image}</text>
+				<actions>
+					<action function="queueclear"/>
+					<action function="set">tw_selectimage=1</action>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<button style="main_button_full_width">
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@adb_sideload_btn=ADB Sideload}</text>
+				<action function="page">sideload</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="install">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<condition var1="tw_selectimage" var2="0"/>
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip} &gt; {@install_select_file_hdr=Select File}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_selectimage" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_image_hdr=Install Image} &gt; {@install_select_file_hdr=Select File}</text>
+			</text>
+
+			<fileselector>
+				<condition var1="tw_selectimage" var2="0"/>
+				<placement x="%indent%" y="%row2_header_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>%tw_zip_location%</text>
+				<filter extn=".zip;.ozip;.ZIP;.OZIP" folders="1" files="1"/>
+				<prfxfilter prfx="Magisk-;Magisk.apk;app-release.apk;app-debug.apk" folders="1" files="1"/>
+				<path name="tw_zip_location" default="/sdcard"/>
+				<data name="tw_filename"/>
+				<selection name="tw_file"/>
+			</fileselector>
+
+			<fileselector>
+				<condition var1="tw_selectimage" var2="1"/>
+				<placement x="%indent%" y="%row2_header_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>%tw_zip_location%</text>
+				<filter extn=".img" folders="1" files="1"/>
+				<path name="tw_zip_location" default="/sdcard"/>
+				<data name="tw_filename"/>
+				<selection name="tw_file"/>
+			</fileselector>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_storage"/>
+				<actions>
+					<action function="set">tw_storagetext={@install_hdr=Install} &gt; {@select_storage_hdr=Select Storage}</action>
+					<action function="set">tw_back=install</action>
+					<action function="page">select_storage</action>
+				</actions>
+			</button>
+
+			<action>
+				<conditions>
+					<condition var1="tw_selectimage" var2="0"/>
+					<condition var1="tw_filename" op="modified"/>
+				</conditions>
+				<actions>
+					<action function="queuezip"/>
+					<action function="page">flash_queue</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_selectimage" var2="1"/>
+					<condition var1="tw_filename" op="modified"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_is_slot_part=0</action>
+					<action function="page">flashimage_type</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">install_type</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="flash_queue">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip} &gt; {@queue_hdr=Queue}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row1_y%"/>
+				<text>{@zip_queue_count_s=File %tw_zip_queue_count% of 10:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row4_y%"/>
+				<text>{@folder=Folder:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>%tw_zip_location%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row8_y%"/>
+				<text>{@zip_back_clear=Press back button to clear the queue.}</text>
+			</text>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<actions>
+					<action function="queueclear"/>
+					<action function="page">install</action>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%btn4_col2_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_addzip"/>
+				<action function="page">install</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col3_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept"/>
+				<action function="page">flash_confirm</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">flash_options</action>
+			</button>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="queueclear"/>
+					<action function="page">install</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="flash_options">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<condition var1="tw_selectimage" var2="0"/>
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip} &gt; {@options_hdr=Options}</text>
+			</text>
+
+			<listbox style="options_listbox">
+				<placement x="%indent%" y="%row1_y%" w="%content_width%" h="%listbox_options_height%"/>
+				<icon selected="checkbox_true" unselected="checkbox_false"/>
+				<listitem name="{@zip_sig_chk=Zip signature verification}">
+					<data variable="tw_signed_zip_verify"/>
+				</listitem>
+				<listitem name="{@skip_digest_zip_chk=Skip Digest check before installing zip}">
+					<data variable="tw_skip_digest_check_zip"/>
+				</listitem>
+				<listitem name="{@auto_reflashtwrp_chk=Automatically Reflash TWRP after flashing a ROM}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<data variable="tw_auto_reflashtwrp"/>
+				</listitem>
+				<listitem name="{@install_reboot_chk=Reboot after installation is complete}">
+					<data variable="tw_install_reboot"/>
+				</listitem>
+				<listitem name="{@inject_twrp_chk=Inject TWRP after install}">
+					<condition var1="tw_has_injecttwrp" var2="1"/>
+					<data variable="tw_inject_after_zip"/>
+				</listitem>
+			</listbox>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">flash_queue</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept_transp"/>
+				<action function="page">flash_confirm</action>
+			</button>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">flash_queue</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="flash_confirm">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_zip_hdr=Install Zip} &gt; {@confirm_flash_hdr=Confirm Flash}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row1_y%"/>
+				<text>{@file=File:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%row2_header_y%" w="%screen_width%" h="48"/>
+				<action function="page">flash_queue</action>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row4_y%"/>
+				<text>{@zip_queue=Queue:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@zip_queue_count_s=File %tw_zip_queue_count% of 10:}</text>
+			</text>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%row3a_y%" w="%screen_width%" h="48"/>
+				<action function="page">flash_queue</action>
+			</button>
+
+			<listbox style="listbox_options">
+				<placement x="%col1_x_left%" y="%row8_y%" w="%content_width%" h="80"/>
+				<listitem name="{@zip_sig_chk=Zip signature verification}">
+					<data variable="tw_signed_zip_verify"/>
+				</listitem>
+			</listbox>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row7_y%"/>
+				<text>{@options=Options:}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_has_injecttwrp" var2="1"/>
+				<condition var1="tw_inject_after_zip" var2="1"/>
+				<placement x="%indent%" y="%row9_y%"/>
+				<text>{@inject_twrp_chk=Inject TWRP after install}</text>
+			</text>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%row6a_y%" w="%screen_width%" h="64"/>
+				<action function="page">flash_options</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_confirm=   Confirm}</text>
+				<action function="flash">flash_zip</action>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="cancelzip"/>
+					<action function="page">install</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="flash_zip">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@installing_zip_xml=Installing Zip: %tw_file%}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<action function="page">flash_done</action>
+			</action>
+		</page>
+
+		<page name="flash_done">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m_fail">
+				<condition var1="tw_operation_status" op="!=" var2="0"/>
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_failed=Installation Failed}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_operation_status" var2="0"/>
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_successful=Installation Successful}</text>
+			</text>
+
+			<template name="console"/>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_ab_device" var2="0"/>
+				<placement x="%col1_x_left%" y="%row13_y%"/>
+				<text>{@wipe_cache_dalvik_btn=Wipe cache/dalvik}</text>
+				<actions>
+					<action function="set">tw_back=flash_done</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=/cache</action>
+					<action function="set">tw_has_action2=1</action>
+					<action function="set">tw_action2=wipe</action>
+					<action function="set">tw_action2_param=dalvik</action>
+					<action function="set">tw_text1={@wipe_cache_dalvik_confirm=Wipe Cache &amp; Dalvik?}</action>
+					<action function="set">tw_action_text1={@wiping_cache_dalvik=Wiping Cache &amp; Dalvik...}</action>
+					<action function="set">tw_complete_text1={@wipe_cache_dalvik_complete=Cache &amp; Dalvik Wipe Complete}</action>
+					<action function="set">tw_slider_text={@swipe_wipe=Swipe to Wipe}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_ab_device" var2="1"/>
+				<placement x="%indent%" y="%row21a_y%"/>
+				<text>{@wipe_dalvik_btn=Wipe Dalvik}</text>
+				<actions>
+					<action function="set">tw_back=flash_done</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=dalvik</action>
+					<action function="set">tw_text1={@wipe_dalvik_confirm=Wipe Dalvik?}</action>
+					<action function="set">tw_action_text1={@wiping_cache_dalvik=Wiping Dalvik...}</action>
+					<action function="set">tw_complete_text1={@wipe_dalvik_complete=Dalvik Wipe Complete}</action>
+					<action function="set">tw_slider_text={@swipe_wipe=Swipe to Wipe}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_ab_device" var2="0"/>
+				<placement x="%col1_x_right%" y="%row13_y%"/>
+				<text>{@reboot_system_btn=Reboot System}</text>
+				<actions>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot_system_routine</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_ab_device" var2="1"/>
+				<placement x="%col1_x_right%" y="%row13_y%"/>
+				<text>{@reboot_btn=Reboot}</text>
+				<actions>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=install_type</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_install_reboot" var2="1"/>
+					<condition var1="tw_operation_status" var2="0"/>
+					<condition var1="tw_reboot_system" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_sleep=%tw_sleep_total%</action>
+					<action function="page">flash_sleep_and_reboot</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="flash_sleep_and_reboot">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_reboot=Rebooting in %tw_sleep% second(s)}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_sleep" op="&gt;" var2="0"/>
+				<placement x="%col1_x_right%" y="%row13_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_install_reboot=0</action>
+					<action function="page">flash_done</action>
+				</actions>
+			</button>
+
+			<action>
+				<conditions>
+					<condition var1="tw_sleep" var2="tw_sleep_total"/>
+					<condition var1="tw_install_reboot" var2="1"/>
+				</conditions>
+				<action function="sleepcounter">%tw_sleep_total%</action>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_sleep" var2="0"/>
+					<condition var1="tw_install_reboot" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="sleep">50000</action>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot_system_routine</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="flashimage_type">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_image_hdr=Install Image} &gt; {@install_sel_target=Select Target Partition}</text>
+			</text>
+
+			<partitionlist style="partitionlist_headerless_rb">
+				<data name="tw_flash_partition"/>
+				<listtype name="flashimg"/>
+			</partitionlist>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_is_slot_part" op="=" var2="1"/>
+					<condition var1="tw_flash_both_slots" op="=" var2="0"/>
+					<condition var1="tw_has_boot_slots" var2="1"/>
+				</conditions>
+				<placement x="%col2_x_left%" y="%row11_y%" textplacement="6"/>
+				<text>{@flash_ab_both_slots=Flash to both slots}</text>
+				<image resource="checkbox_false"/>
+				<action function="set">tw_flash_both_slots=1</action>
+			</button>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_is_slot_part" op="=" var2="1"/>
+					<condition var1="tw_flash_both_slots" op="=" var2="1"/>
+					<condition var1="tw_has_boot_slots" var2="1"/>
+				</conditions>
+				<placement x="%col2_x_left%" y="%row11_y%" textplacement="6"/>
+				<text>{@flash_ab_both_slots=Flash to both slots}</text>
+				<image resource="checkbox_true"/>
+				<action function="set">tw_flash_both_slots=0</action>
+			</button>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">install</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept_transp"/>
+				<action function="page">flashimage_confirm</action>
+			</button>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">install</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="flashimage_confirm">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_image_hdr=Install Image} &gt; {@confirm_flash_hdr=Confirm Flash}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row1_y%"/>
+				<text>{@file=File:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%row2_header_y%" w="%screen_width%" h="48"/>
+				<action function="page">install</action>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row4_y%"/>
+				<text>{@target_partition=Target Partition:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>%tw_flash_partition%</text>
+			</text>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%row3a_y%" w="%screen_width%" h="48"/>
+				<action function="page">flashimage_type</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_confirm=   Confirm}</text>
+				<actions>
+					<action function="set">tw_back=flashimage_confirm</action>
+					<action function="set">tw_action=flashimage</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_action_text1={@flashing_image=Flashing Image...}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@image_flashed=Image Flashed}</action>
+					<action function="page">action_page</action>
+				</actions>
+				<action function="flashimage"/>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=install</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="clear_vars">
+			<action>
+				<action function="set">tw_operation_state=0</action>
+				<action function="set">tw_text1=</action>
+				<action function="set">tw_text2=</action>
+				<action function="set">tw_text3=</action>
+				<action function="set">tw_text4=</action>
+				<action function="set">tw_action_text1=</action>
+				<action function="set">tw_action_text2=</action>
+				<action function="set">tw_action_param=</action>
+				<action function="set">tw_has_action2=0</action>
+				<action function="set">tw_action2=</action>
+				<action function="set">tw_action2_param=</action>
+				<action function="set">tw_has_cancel=0</action>
+				<action function="set">tw_cancel_action=</action>
+				<action function="set">tw_cancel_param=</action>
+				<action function="set">tw_show_exclamation=0</action>
+				<action function="set">tw_show_reboot=0</action>
+				<action function="set">tw_crypto_user_id=</action>
+				<action function="set">tw_multiuser_warning_accepted=</action>
+				<action function="set">tw_multiuser_warning_destination=</action>
+				<action function="page">%tw_clear_destination%</action>
+			</action>
+		</page>
+
+		<page name="reboot_system_routine">
+			<action>
+				<action function="set">tw_back=main2</action>
+				<action function="set">tw_action=reboot</action>
+				<action function="set">tw_action_param=system</action>
+				<action function="set">tw_has_action2=0</action>
+				<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+				<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+				<action function="set">tw_text3=</action>
+				<action function="set">tw_text4=</action>
+				<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+				<action function="set">tw_action_text2=</action>
+				<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+				<action function="set">tw_slider_text={@swipe_reboot_s=   Reboot}</action>
+				<action function="page">rebootcheck</action>
+			</action>
+		</page>
+
+		<page name="confirm_action">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@confirm_action=Confirm Action}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>%tw_text1%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>%tw_text2%</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_action" op="!=" var2="changefilesystem"/>
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>%tw_text3%</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_action" op="!=" var2="changefilesystem"/>
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>%tw_text4%</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_action" var2="changefilesystem"/>
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>%tw_text3%</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_action" var2="changefilesystem"/>
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>%tw_text4%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row8_y%" placement="5"/>
+				<text>{@back_cancel=Press back button to cancel.}</text>
+			</text>
+
+			<slider>
+				<text>%tw_slider_text%</text>
+				<action function="page">action_page</action>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="action_page">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>%tw_action_text1% %tw_action_text2%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_has_cancel" var2="1"/>
+				<placement x="%col1_x_right%" y="%row13_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="%tw_cancel_action%">%tw_cancel_param%</action>
+			</button>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<actions>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="0"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="1"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+					<action function="%tw_action2%">%tw_action2_param%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="singleaction_page">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>%tw_action_text1% %tw_action_text2%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="0"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_has_action2" var2="1"/>
+				<actions>
+					<action function="%tw_action%">%tw_action_param%</action>
+					<action function="%tw_action2%">%tw_action2_param%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="action_complete">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m_fail">
+				<condition var1="tw_operation_status" op="!=" var2="0"/>
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>%tw_complete_text1% {@failed=Failed}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_operation_status" var2="0"/>
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>%tw_complete_text1% {@successful=Successful}</text>
+			</text>
+
+			<template name="console"/>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row13_y%"/>
+				<text>{@back_btn=Back}</text>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_right%" y="%row13_y%"/>
+				<text>{@reboot_system_btn=Reboot System}</text>
+				<actions>
+					<action function="set">tw_back=main2</action>
+					<action function="page">reboot_system_routine</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_clear_destination=main2</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="filecheck">
+			<action>
+				<action function="fileexists">%tw_filecheck%</action>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_fileexists=1</action>
+					<action function="page">%tw_existpage%</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_fileexists=0</action>
+					<action function="page">%tw_notexistpage%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="rebootcheck">
+			<action>
+				<condition var1="tw_backup_system_size" op="&gt;=" var2="%tw_min_system%"/>
+				<action function="reboot">%tw_reboot_param%</action>
+			</action>
+
+			<action>
+				<condition var1="tw_backup_system_size" op="&lt;" var2="%tw_min_system%"/>
+				<action function="page">confirm_action</action>
+			</action>
+		</page>
+
+		<page name="wipe_type1">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@wipe_hdr=Wipe} &gt; {@select_type_hdr=Select Type}</text>
+			</text>
+
+			<button style="main_button_full_width">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@factory_reset_btn=Factory Reset}</text>
+				<action function="page">wipe</action>
+			</button>
+
+			<button style="main_button_full_width">
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@advanced_wipe_btn=Advanced Wipe}</text>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="page">advancedwipe</action>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">wipe_type2</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="wipe_type2">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@wipe_hdr=Wipe} &gt; {@select_type_hdr=Select Type}</text>
+			</text>
+
+			<button style="main_button_full_width">
+				<condition var1="tw_has_data_media" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@format_data_btn=Format Data}</text>
+				<action function="page">formatdata</action>
+			</button>
+
+			<button style="main_button_full_width">
+				<conditions>
+					<condition var1="tw_is_encrypted" var2="1"/>
+					<condition var1="tw_has_data_media" var2="0"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@wipe_enc_btn=Wipe Encryption}</text>
+				<actions>
+					<action function="set">tw_back=wipe</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=DATAMEDIA</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@wipe_enc_confirm=Wipe Encryption from Data?}</action>
+					<action function="set">tw_text2=</action>
+					<action function="set">tw_action_text1={@formatting_data=Formatting Data...}</action>
+					<action function="set">tw_complete_text1={@format_data_complete=Data Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_format_data_s=   Format Data}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_full_width">
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@file_sys_opt=File System Options}</text><actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="page">partitionoptions_select</action>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">wipe_type1</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="wipe">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@wipe_hdr=Wipe} &gt; {@factory_reset_hdr=Factory Reset}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@factory_reset1=Wipes Data, Cache, and Dalvik}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@factory_reset5=(not including users/lockscreen)}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_has_data_media" var2="1"/>
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@factory_reset2=(not including internal storage)}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<conditions>
+					<condition var1="tw_has_android_secure" var2="1"/>
+					<condition var1="fileexists" var2="/and-sec"/>
+				</conditions>
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@android_secure=Android Secure}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_has_sdext_partition" var2="1"/>
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@sdext=SD-EXT}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@factory_reset3=Most of the time this is}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@factory_reset4=the only wipe that you need.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row9_y%" placement="5"/>
+				<text>{@back_cancel=Press back button to cancel.}</text>
+			</text>
+
+			<slider>
+				<text>{@swipe_wipe_s=   Wipe}</text>
+				<actions>
+					<action function="set">tw_back=wipe</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=data</action>
+					<action function="set">tw_action_text1={@factory_resetting=Factory Reset...}</action>
+					<action function="set">tw_complete_text1={@factory_reset_complete=Factory Reset Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">wipe_type1</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="advancedwipe">
+			<action>
+				<action function="set">tw_wipe_list=</action>
+			</action>
+
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@wipe_hdr=Wipe} &gt; {@advanced_wipe_hdr=Advanced Wipe} &gt; {@sel_part_hdr=Select Partitions}</text>
+			</text>
+
+			<partitionlist style="partitionlist_headerless_cb">
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%partitionlist_wipe_height%"/>
+				<data name="tw_wipe_list"/>
+				<listtype name="wipe"/>
+			</partitionlist>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row10a_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<slider>
+				<text>{@swipe_wipe_s=   Wipe}</text>
+				<actions>
+					<action function="set">tw_back=advancedwipe</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=LIST</action>
+					<action function="set">tw_text1={@wipe_sel_confirm=Wipe Selected Partition(s)?}</action>
+					<action function="set">tw_action_text1={@wiping_part=Wiping Partition(s)...}</action>
+					<action function="set">tw_complete_text1={@wipe_complete=Wipe Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">wipe_type1</action>
+			</action>
+		</page>
+
+		<page name="formatdata">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@wipe_hdr=Wipe} &gt; {@format_data_hdr=Format Data}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@format_data_wtc1=Format Data will wipe all of your apps,}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@format_data_wtc2=backups and media. This cannot be undone.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@yes_continue=Type yes to continue. Press back to cancel.}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row5_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_confirm_formatdata%</text>
+				<data name="tw_confirm_formatdata"/>
+				<restrict minlen="3" maxlen="3" allow="yes"/>
+				<action function="page">formatdata_confirm</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row6_input_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">wipe_type2</action>
+			</action>
+		</page>
+
+		<page name="formatdata_confirm">
+			<action>
+				<condition var1="tw_confirm_formatdata" op="=" var2="yes"/>
+				<actions>
+					<action function="set">tw_back=formatdata</action>
+					<action function="set">tw_action=wipe</action>
+					<action function="set">tw_action_param=DATAMEDIA</action>
+					<action function="set">tw_action_text1={@formatting_data=Formatting Data...}</action>
+					<action function="set">tw_complete_text1={@format_data_complete=Data Format Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_confirm_formatdata" op="!=" var2="yes"/>
+				<action function="page">formatdata</action>
+			</action>
+		</page>
+
+		<page name="checkpartitionlist">
+			<action>
+				<condition var1="tw_check_partition_list" op="=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="getpartitiondetails">tw_wipe_list</action>
+					<action function="page">partitionoptions</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_check_partition_list" op="!=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=1</action>
+					<action function="page">partitionoptions_select</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="partitionoptions_select">
+			<action>
+				<action function="set">tw_wipe_list=</action>
+			</action>
+
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@wipe_hdr=Wipe} &gt; {@file_sys_opt=File System Options} &gt; {@sel_part_hdr=Select Partitions}</text>
+			</text>
+
+			<partitionlist style="partitionlist_headerless_cb">
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%partitionlist_wipe_height%"/>
+				<data name="tw_wipe_list"/>
+				<listtype name="wipe"/>
+			</partitionlist>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row10a_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="partitionlisterror" var2="1"/>
+				<placement x="%col1_x_left%" y="%row12_y%"/>
+				<text>{@invalid_part_sel=Invalid partition selection}</text>
+			</text>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/><actions>
+					<action function="checkpartitionlist">tw_wipe_list</action>
+					<action function="page">checkpartitionlist</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">wipe_type2</action>
+			</action>
+		</page>
+
+		<page name="partitionoptions">
+
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@wipe_hdr=Wipe} &gt; {@file_sys_opt=File System Options} &gt; {@sel_act_hdr=Select Action}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@partition=Partition: %tw_partition_name%}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row1_y%"/>
+				<text>{@part_curr_fs=File system: %tw_partition_file_system%}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_is_present" var2="1"/>
+				<placement x="%col1_x_left%" y="%row2a_y%"/>
+				<text>{@part_present_yes=Present: Yes}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_is_present" var2="0"/>
+				<placement x="%col1_x_left%" y="%row2a_y%"/>
+				<text>{@part_present_no=Present: No}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_removable" var2="1"/>
+				<placement x="%col1_x_right%" y="%row2a_y%"/>
+				<text>{@part_removable_yes=Removable: Yes}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_partition_removable" var2="0"/>
+				<placement x="%col1_x_right%" y="%row2a_y%"/>
+				<text>{@part_removable_no=Removable: No}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row3a_y%"/>
+				<text>{@part_size=Size: %tw_partition_size%MB}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_right%" y="%row3a_y%"/>
+				<text>{@part_used=Used: %tw_partition_used%MB}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row4a_y%"/>
+				<text>{@part_free=Free: %tw_partition_free%MB}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_right%" y="%row4a_y%"/>
+				<text>{@part_backup_size=Backup Size: %tw_partition_backup_size%MB}</text>
+			</text>
+
+			<button style="main_button">
+				<condition var1="tw_partition_can_resize" op="=" var2="1"/>
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@resize_btn_s=Resize}</text>
+				<actions>
+					<action function="set">tw_back=partitionoptions</action>
+					<action function="set">tw_action=resize</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_has_action2=1</action>
+					<action function="set">tw_action2=getpartitiondetails</action>
+					<action function="set">tw_action2_param=tw_wipe_list</action>
+					<action function="set">tw_text1={@resize_confirm=Resize %tw_partition_name%?}</action>
+					<action function="set">tw_text2=</action>
+					<action function="set">tw_action_text1={@resizing=Resizing...}</action>
+					<action function="set">tw_complete_text1={@resize_complete=Resize Complete}</action>
+					<action function="set">tw_slider_text={@swipe_resize_s=   Resize}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_partition_can_repair" op="=" var2="1"/>
+				<placement x="%col1_x_right%" y="%row6_y%"/>
+				<text>{@repair_btn_s=Repair}</text>
+				<actions>
+					<action function="set">tw_back=partitionoptions</action>
+					<action function="set">tw_action=repair</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@repair_confirm=Repair %tw_partition_name%?}</action>
+					<action function="set">tw_text2=</action>
+					<action function="set">tw_action_text1={@repairing=Repairing...}</action>
+					<action function="set">tw_complete_text1={@repair_complete=Repair Complete}</action>
+					<action function="set">tw_slider_text={@swipe_repair_s=   Repair}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">partitionoptions_select</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_right%" y="%row11_y%"/>
+				<text>{@change_fs_btn_s=Change}</text>
+				<action function="page">selectfilesystem</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">partitionoptions_select</action>
+			</action>
+		</page>
+
+		<page name="refreshfilesystem">
+			<action>
+				<condition var1="tw_check_partition_list" op="=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="getpartitiondetails">tw_wipe_list</action>
+					<action function="page">selectfilesystem</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_check_partition_list" op="!=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=1</action>
+					<action function="set">tw_wipe_list=</action>
+					<action function="page">advancedwipe</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="selectfilesystem">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@change_fs_for_hdr_s=Partition: %tw_partition_name% &gt; Select File System}</text>
+			</text>
+
+			<button style="main_button">
+				<condition var1="tw_partition_ext" op="=" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>EXT2</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=ext2</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=EXT2</action>
+					<action function="set">tw_text3={@change_fs_warn1=Some ROMs or kernels may not support some}</action>
+					<action function="set">tw_text4={@change_fs_warn2=file systems. Proceed with caution!}</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_s=   Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_partition_ext" op="=" var2="1"/>
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>EXT3</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=ext3</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=EXT3</action>
+					<action function="set">tw_text3={@change_fs_warn1=Some ROMs or kernels may not support some}</action>
+					<action function="set">tw_text4={@change_fs_warn2=file systems. Proceed with caution!}</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_s=   Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_partition_ext" op="=" var2="1"/>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>EXT4</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=ext4</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=EXT4</action>
+					<action function="set">tw_text3={@change_fs_warn1=Some ROMs or kernels may not support some}</action>
+					<action function="set">tw_text4={@change_fs_warn2=file systems. Proceed with caution!}</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_s=   Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_partition_vfat" op="=" var2="1"/>
+				<placement x="%col1_x_right%" y="%row1_y%"/>
+				<text>FAT</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=vfat</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=FAT</action>
+					<action function="set">tw_text3={@change_fs_warn1=Some ROMs or kernels may not support some}</action>
+					<action function="set">tw_text4={@change_fs_warn2=file systems. Proceed with caution!}</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_s=   Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_partition_exfat" op="=" var2="1"/>
+				<placement x="%col1_x_right%" y="%row6_y%"/>
+				<text>exFAT</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=exfat</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=exFAT</action>
+					<action function="set">tw_text3={@change_fs_warn1=Some ROMs or kernels may not support some}</action>
+					<action function="set">tw_text4={@change_fs_warn2=file systems. Proceed with caution!}</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_s=   Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_partition_f2fs" op="=" var2="1"/>
+				<placement x="%col1_x_right%" y="%row11_y%"/>
+				<text>F2FS</text>
+				<actions>
+					<action function="set">tw_back=refreshfilesystem</action>
+					<action function="set">tw_action=changefilesystem</action>
+					<action function="set">tw_action_param=%tw_partition_mount_point%</action>
+					<action function="set">tw_action_new_file_system=f2fs</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@change_fs_confirm=Change %tw_partition_name%?}</action>
+					<action function="set">tw_text2=F2FS</action>
+					<action function="set">tw_text3={@change_fs_warn1=Some ROMs or kernels may not support some}</action>
+					<action function="set">tw_text4={@change_fs_warn2=file systems. Proceed with caution!}</action>
+					<action function="set">tw_action_text1={@formatting=Formatting...}</action>
+					<action function="set">tw_complete_text1={@format_complete=Format Complete}</action>
+					<action function="set">tw_slider_text={@swipe_change_s=   Change}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">partitionoptions</action>
+			</action>
+		</page>
+
+		<page name="backup">
+			<template name="page"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_is_fbe" var2="1"/>
+					<condition var1="tw_all_users_decrypted" var2="0"/>
+					<condition var1="tw_multiuser_warning_accepted" op="!=" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_multiuser_warning_destination=backup</action>
+					<action function="page">multiuser_warning</action>
+				</actions>
+			</action>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@backup_hdr=Backup} &gt; {@select_storage_hdr=Select Storage}</text>
+			</text>
+
+			<partitionlist style="partitionlist_headerless_rb">
+				<data name="tw_storage_path"/>
+				<listtype name="storage"/>
+			</partitionlist>
+
+			<button>
+				<placement x="%btn4_col2_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_refresh"/>
+				<actions>
+					<action function="refreshsizes"/>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_back" var2="backup_confirm"/>
+				<placement x="%btn4_col3_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept"/>
+				<action function="page">backup_confirm</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<actions>
+					<action function="set">tw_back=backup</action>
+					<action function="page">backup_selectpartitions</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">%tw_back%</action>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="backup_selectpartitions">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@backup_hdr=Backup} &gt; {@sel_part_hdr=Select Partitions}</text>
+			</text>
+
+			<partitionlist style="partitionlist_headerless_cb">
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%partitionlist_wipe_height%"/>
+				<data name="tw_backup_list"/>
+				<listtype name="backup"/>
+			</partitionlist>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row10a_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">backup</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col2_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_refresh"/>
+				<action function="page">backup_selectpartitions</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col3_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept"/>
+				<action function="page">backup_confirm</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<actions>
+					<action function="set">tw_back=backup_selectpartitions</action>
+					<action function="page">backup_options</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">%tw_back%</action>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="multiuser_warning">
+			<template name="page"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@multiuser_warning_hdr=Multiuser Warning}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@multiuser_warning1=Not all users decrypted!}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@multiuser_warning2=Backup/restore operations may fail!}</text>
+			</text>
+
+			<button style="main_button_full_width">
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@decrypt_users=Decrypt Users}</text>
+				<action function="page">decrypt_users</action>
+			</button>
+
+			<slider>
+				<text>{@multiuser_warning_accept=Continue Anyway}</text>
+				<actions>
+					<action function="set">tw_multiuser_warning_accepted=1</action>
+					<action function="page">%tw_multiuser_warning_destination%</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="backup_options">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@backup_hdr=Backup} &gt; {@options_hdr=Options}</text>
+			</text>
+
+			<listbox style="scrolllist">
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%partitionlist_wipe_height%"/>
+				<listitem name="{@enable_backup_comp_chk=Enable compression}">
+					<data variable="tw_use_compression"/>
+				</listitem>
+				<listitem name="{@skip_digest_backup_chk=Skip Digest generation during backup}">
+					<data variable="tw_skip_digest_generate"/>
+				</listitem>
+				<listitem name="{@disable_backup_space_chk=Disable free space check before backup}">
+					<data variable="tw_disable_free_space"/>
+				</listitem>
+			</listbox>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">backup_selectpartitions</action>
+			</button>
+
+			<button>
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="0"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%btn4_col2_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_encryption_dis"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="set">tw_back=backup_options</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<button>
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="1"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%btn4_col2_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_encryption_en"/>
+				<actions>
+					<action function="set">tw_password_not_match=0</action>
+					<action function="set">tw_back=backup_options</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept_transp"/>
+				<actions>
+					<action function="set">tw_back=backup_options</action>
+					<action function="page">backup_confirm</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">%tw_back%</action>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="backup_confirm">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@backup_hdr=Backup} &gt; {@backup_confirm_hdr=Confirm Backup}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row2_y%"/>
+				<text>%tw_backup_name%</text>
+			</text>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_half_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%row2_header_y%" w="%screen_half_width%" h="48"/>
+				<actions>
+					<action function="set">tw_back=backup_confirm</action>
+					<action function="set">tw_fileexists=0</action>
+					<action function="page">backup_name1</action>
+				</actions>
+			</button>
+
+			<fill color="%background_color%">
+				<placement x="%col1_x_left_negative%" y="%row2_header_y%" w="%screen_half_width%" h="48"/>
+			</fill>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row3a_y%"/>
+				<text>{@partitions=Partitions:}</text>
+			</text>
+
+			<partitionlist style="partitionlist_summary">
+				<placement x="%col1_x_left%" y="%row4a_y%" w="%content_half_width%" h="96"/>
+				<data name="tw_backup_list"/>
+				<listtype name="backup"/>
+			</partitionlist>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%row3a_y%" w="%screen_half_width%" h="112"/>
+				<actions>
+					<action function="set">tw_back=backup_confirm</action>
+					<action function="page">backup_selectpartitions</action>
+				</actions>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row1_y%"/>
+				<text>{@storage=Storage:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%col1_x_right%" y="%row2_y%"/>
+				<text>%tw_storage_display_name%</text>
+			</text>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="%center_x%" y="%row2_header_y%" w="%screen_half_width%" h="48"/>
+				<actions>
+					<action function="set">tw_back=backup_confirm</action>
+					<action function="page">backup</action>
+				</actions>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row3a_y%"/>
+				<text>{@encryption=Encryption:}</text>
+			</text>
+
+			<text style="text_m">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="0"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%col1_x_right%" y="%row4a_y%"/>
+				<text>{@disabled=Disabled}</text>
+			</text>
+
+			<text style="text_m">
+				<conditions>
+					<condition var1="tw_include_encrypted_backup" var2="1"/>
+					<condition var1="tw_encrypt_backup" var2="1"/>
+					<condition var1="tw_enable_adb_backup" op="!=" var2="1"/>
+				</conditions>
+				<placement x="%col1_x_right%" y="%row4a_y%"/>
+				<text>{@enabled=Enabled}</text>
+			</text>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="%center_x%" y="%row3a_y%" w="%screen_half_width%" h="48"/>
+				<actions>
+					<action function="set">tw_back=backup_confirm</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</button>
+
+			<listbox style="listbox_options">
+				<placement x="%col1_x_right%" y="%row7_y%" w="%content_half_width%" h="80"/>
+				<listitem name="{@enable_backup_comp_chk=Enable compression}">
+					<data variable="tw_use_compression"/>
+				</listitem>
+				<listitem name="{@skip_digest_backup_chk=Skip Digest generation during backup}">
+					<data variable="tw_skip_digest_generate"/>
+				</listitem>
+				<listitem name="{@disable_backup_space_chk=Disable free space check before backup}">
+					<data variable="tw_disable_free_space"/>
+				</listitem>
+			</listbox>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row6_y%"/>
+				<text>{@options=Options:}</text>
+			</text>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="%center_x%" y="%row6_y%" w="%screen_half_width%" h="64"/>
+				<actions>
+					<action function="set">tw_back=backup_confirm</action>
+					<action function="page">backup_options</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_confirm=   Confirm}</text>
+				<action function="page">backup_run</action>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">backup</action>
+			</action>
+		</page>
+
+		<page name="backup_name1">
+			<action>
+				<condition var1="tw_backup_name" op="=" var2="{@auto_generate=(Auto Generate)}"/>
+				<action function="generatebackupname"/>
+			</action>
+
+			<action>
+				<action function="page">backup_name2</action>
+			</action>
+		</page>
+
+		<page name="backup_name2">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@backup_hdr=Backup} &gt; {@backup_name_hdr=Set Backup Name}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_backup_name%</text>
+				<data name="tw_backup_name"/>
+				<restrict minlen="1" maxlen="64" allow=" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_.{}[]"/>
+				<actions>
+					<action function="set">tw_filecheck=%tw_backups_folder%/%tw_backup_name%</action>
+					<action function="set">tw_existpage=backup_name2</action>
+					<action function="set">tw_notexistpage=backup_confirm</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_line_width%" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_line_width%" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="%row3_input_y%"/>
+				<text>{@backup_name_exists=A backup with that name already exists!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_backup_name={@auto_generate=(Auto Generate)}</action>
+					<action function="page">backup_confirm</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_right%" y="%row4_y%"/>
+				<text>{@append_date_btn=Append Date}</text>
+				<action function="appenddatetobackupname"/>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_backup_name={@auto_generate=(Auto Generate)}</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_backup_name={@auto_generate=(Auto Generate)}</action>
+					<action function="page">backup_confirm</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="backupencryption">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@backup_hdr=Backup} &gt; {@encrypt_backup=Encrypt your Backup?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@enter_pass=Enter Password:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_backup_encrypt_display%</text>
+				<data name="tw_backup_password" mask="*" maskvariable="tw_backup_encrypt_display"/>
+				<restrict minlen="1" maxlen="32" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"/>
+				<action function="page">backupencryption2</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_line_width%" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_not_match" var2="1"/>
+				<placement x="%col1_x_left%" y="row3_input_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_not_match" var2="1"/>
+				<placement x="%col1_x_left%" y="%row3_input_y%"/>
+				<text>{@pass_not_match=Passwords do not match!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_right%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_encrypt_backup=0</action>
+					<action function="set">tw_backup_password=</action>
+					<action function="set">tw_backup_password2=</action>
+					<action function="set">tw_backup_encrypt_display=</action>
+					<action function="set">tw_backup_encrypt_display2=</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">%tw_back%</action>
+			</action>
+		</page>
+
+		<page name="backupencryption2">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@backup_hdr=Backup} &gt; {@encrypt_backup=Encrypt your Backup?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@enter_pass2=Enter Password again:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_backup_encrypt_display2%</text>
+				<data name="tw_backup_password2" mask="*" maskvariable="tw_backup_encrypt_display2"/>
+				<restrict minlen="1" maxlen="32" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"/>
+				<action function="page">checkbackuppassword</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_line_width%" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_right%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_encrypt_backup=0</action>
+					<action function="set">tw_backup_password=</action>
+					<action function="set">tw_backup_password2=</action>
+					<action function="set">tw_backup_encrypt_display=</action>
+					<action function="set">tw_backup_encrypt_display2=</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">%tw_back%</action>
+			</action>
+		</page>
+
+		<page name="checkbackuppassword">
+			<action>
+				<condition var1="tw_backup_password2" var2="tw_backup_password"/>
+				<actions>
+					<action function="set">tw_encrypt_backup=1</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_backup_password2" op="!=" var2="tw_backup_password"/>
+				<actions>
+					<action function="set">tw_encrypt_backup=0</action>
+					<action function="set">tw_password_not_match=1</action>
+					<action function="set">tw_backup_password=</action>
+					<action function="set">tw_backup_password2=</action>
+					<action function="set">tw_backup_encrypt_display=</action>
+					<action function="set">tw_backup_encrypt_display2=</action>
+					<action function="page">backupencryption</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="backup_run">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>%tw_operation% %tw_partition%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row13a_y%"/>
+				<text>%tw_file_progress%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row14a_y%"/>
+				<text>%tw_size_progress%</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_right%" y="%row13_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="cancelbackup"/>
+			</button>
+
+			<action>
+				<action function="nandroid">backup</action>
+			</action>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<condition var1="tw_cancel_backup" var2="0"/>
+				<actions>
+					<action function="set">tw_back=backup_selectpartitions</action>
+					<action function="set">tw_complete_text1={@backup_complete=Backup Complete}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<condition var1="tw_cancel_backup" var2="1"/>
+				<actions>
+					<action function="set">tw_back=backup_selectpartitions</action>
+					<action function="set">tw_complete_text1={@backup_cancel=Backup Cancelled}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore">
+			<template name="page"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_is_fbe" var2="1"/>
+					<condition var1="tw_all_users_decrypted" var2="0"/>
+					<condition var1="tw_multiuser_warning_accepted" op="!=" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_multiuser_warning_destination=restore</action>
+					<action function="page">multiuser_warning</action>
+				</actions>
+			</action>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@restore_hdr=Restore} &gt; {@sel_backup_hdr=Select Backup}</text>
+			</text>
+
+			<fileselector>
+				<placement x="%indent%" y="%row2_header_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>%tw_storage_display_name%</text>
+				<filter folders="1" files="1" nav="0" extn=".ab"/>
+				<path name="tw_backups_folder"/>
+				<data name="tw_restore" default=""/>
+				<selection name="tw_restore_name"/>
+			</fileselector>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_storage"/>
+				<actions>
+					<action function="set">tw_storagetext={@restore_btn=Restore} &gt; {@select_storage_btn=Select Storage}</action>
+					<action function="set">tw_back=restore</action>
+					<action function="page">select_storage</action>
+				</actions>
+			</button>
+
+			<action>
+				<condition var1="tw_crypto_pwtype" op="!=" var2="0"/>
+				<actions>
+					<action function="page">restore_keymaster</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_restore" op="modified"/>
+				<actions>
+					<action function="readBackup"/>
+					<action function="set">tw_back=restore</action>
+					<action function="page">restore_read</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="restore_force">
+			<template name="page"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_is_fbe" var2="1"/>
+					<condition var1="tw_all_users_decrypted" var2="0"/>
+					<condition var1="tw_multiuser_warning_accepted" op="!=" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_multiuser_warning_destination=restore_force</action>
+					<action function="page">multiuser_warning</action>
+				</actions>
+			</action>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@restore_hdr=Restore} &gt; {@sel_backup_hdr=Select Backup}</text>
+			</text>
+
+			<fileselector>
+				<placement x="%indent%" y="%row2_header_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>%tw_storage_display_name%</text>
+				<filter folders="1" files="1" nav="0" extn=".ab"/>
+				<path name="tw_backups_folder"/>
+				<data name="tw_restore" default=""/>
+				<selection name="tw_restore_name"/>
+			</fileselector>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_storage"/>
+				<actions>
+					<action function="set">tw_storagetext={@restore_btn=Restore} &gt; {@select_storage_btn=Select Storage}</action>
+					<action function="set">tw_back=restore</action>
+					<action function="page">select_storage</action>
+				</actions>
+			</button>
+
+			<action>
+				<condition var1="tw_restore" op="modified"/>
+				<actions>
+					<action function="readBackup"/>
+					<action function="set">tw_back=restore</action>
+					<action function="page">restore_read</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="restore_read">
+			<action>
+				<condition var1="tw_restore_encrypted" var2="1"/>
+				<actions>
+					<action function="set">tw_password_fail=0</action>
+					<action function="page">restore_decrypt</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_restore_encrypted" var2="0"/>
+				<actions>
+					<action function="page">restore_select</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore_decrypt">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@restore_hdr=Restore} &gt; {@restore_enc_backup_hdr=Encrypted Backup}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@enter_pass=Enter Password:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_restore_display%</text>
+				<data name="tw_restore_password" mask="*" maskvariable="tw_restore_display"/>
+				<restrict minlen="1" maxlen="32" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"/>
+				<action function="page">try_restore_decrypt</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_line_width%" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="row3_input_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="%row3_input_y%"/>
+				<text>{@restore_dec_fail=Password failed, please try again!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">restore</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_right%" y="%row4_y%"/>
+				<text>{@del_backup_btn=Delete Backup}</text>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="set">tw_action=cmd</action>
+					<action function="set">tw_action_param=cd %tw_backups_folder% &amp;&amp; rm -rf "%tw_restore_name%"</action>
+					<action function="set">tw_text1={@del_backup_confirm=Delete Backup?}</action>
+					<action function="set">tw_text2=%tw_restore_name%</action>
+					<action function="set">tw_text4={@del_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@deleting_backup=Deleting Backup...}</action>
+					<action function="set">tw_complete_text1={@backup_deleted=Backup Delete Complete}</action>
+					<action function="set">tw_slider_text={@swipe_delete_s=   Delete}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">restore</action>
+			</action>
+		</page>
+
+		<page name="try_restore_decrypt">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@restore_hdr=Restore} &gt; {@restore_try_decrypt_s=Trying Decryption}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<action function="decrypt_backup"/>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="!=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_password_fail=1</action>
+					<action function="page">restore_decrypt</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">restore_select</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore_select">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@restore_hdr=Restore} &gt; {@sel_part_hdr=Select Partitions}</text>
+			</text>
+
+			<partitionlist style="partitionlist_headerless_cb">
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%partitionlist_wipe_height%"/>
+				<data name="tw_restore_list" selectedlist="tw_restore_selected"/>
+				<listtype name="restore"/>
+			</partitionlist>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row10a_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">restore</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col2_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_delete"/>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="set">tw_action=cmd</action>
+					<action function="set">tw_action_param=cd %tw_backups_folder% &amp;&amp; rm -rf "%tw_restore_name%"</action>
+					<action function="set">tw_text1={@del_backup_confirm=Delete Backup?}</action>
+					<action function="set">tw_text2=%tw_restore_name%</action>
+					<action function="set">tw_text4={@del_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@deleting_backup=Deleting Backup...}</action>
+					<action function="set">tw_complete_text1={@backup_deleted=Backup Delete Complete}</action>
+					<action function="set">tw_slider_text={@swipe_delete_s=   Delete}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%btn4_col3_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept"/>
+				<action function="page">restore_confirm</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<actions>
+					<action function="set">tw_back=restore_select</action>
+					<action function="page">restore_options</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">%tw_back%</action>
+			</action>
+		</page>
+
+		<page name="restore_options">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@restore_hdr=Restore} &gt; {@options_hdr=Options}</text>
+			</text>
+
+			<listbox style="scrolllist">
+				<condition var1="tw_enable_adb_backup" op="=" var2="0"/>
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%partitionlist_wipe_height%"/>
+				<listitem name="{@restore_enable_digest_chk=Enable Digest Verification of Backup Files}">
+					<data variable="tw_skip_digest_check"/>
+				</listitem>
+			</listbox>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">restore_select</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept_transp"/>
+				<actions>
+					<action function="set">tw_back=restore_options</action>
+					<action function="page">restore_confirm</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">%tw_back%</action>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="renamebackup">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@restore_hdr=Restore} &gt; {@rename_backup_hdr=Rename Backup}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_backup_rename%</text>
+				<data name="tw_backup_rename"/>
+				<restrict minlen="1" maxlen="64" allow=" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_.{}[]"/>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="set">tw_action=cmd</action>
+					<action function="set">tw_action_param=cd %tw_backups_folder% &amp;&amp; mv "%tw_restore_name%" "%tw_backup_rename%"</action>
+					<action function="set">tw_text1={@rename_backup_confirm=Rename Backup?}</action>
+					<action function="set">tw_text2={@rename_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@renaming_backup=Renaming Backup...}</action>
+					<action function="set">tw_complete_text1={@rename_backup_complete=Backup Rename Complete}</action>
+					<action function="set">tw_slider_text={@swipe_rename=   Rename}</action>
+					<action function="set">tw_filecheck=%tw_backups_folder%/%tw_backup_rename%</action>
+					<action function="set">tw_existpage=renamebackup</action>
+					<action function="set">tw_notexistpage=confirm_action</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_line_width%" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_line_width%" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="%row3_input_y%"/>
+				<text>{@backup_name_exists=A backup with that name already exists!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">restore_confirm</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">restore_confirm</action>
+			</action>
+		</page>
+
+		<page name="restore_confirm">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@restore_hdr=Restore} &gt; {@confirm_hdr=Confirm}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row1_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>%tw_restore_name%</text>
+			</text>
+
+			<fill color="%accent_color%">
+				<placement x="%indent%" y="64" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%row2_header_y%" w="%screen_width%" h="48"/>
+				<actions>
+					<action function="set">tw_back=restore_confirm</action>
+					<action function="set">tw_backup_rename=%tw_restore_name%</action>
+					<action function="set">tw_fileexists=0</action>
+					<action function="page">renamebackup</action>
+				</actions>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row3a_y%"/>
+				<text>{@partitions=Partitions:}</text>
+			</text>
+
+			<partitionlist style="partitionlist_summary">
+				<placement x="%col1_x_left%" y="%row4a_y%" w="%content_half_width%" h="96"/>
+				<data name="tw_restore_list" selectedlist="tw_restore_selected"/>
+				<listtype name="restore"/>
+			</partitionlist>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%row3a_y%" w="%screen_half_width%" h="112"/>
+				<actions>
+					<action function="set">tw_back=restore_confirm</action>
+					<action function="page">restore_select</action>
+				</actions>
+			</button>
+
+			<listbox style="listbox_options">
+				<placement x="%col1_x_right%" y="%row4a_y%" w="%content_half_width%" h="80"/>
+				<listitem name="{@restore_enable_digest_chk=Enable Digest Verification of Backup Files}">
+					<data variable="tw_skip_digest_check"/>
+				</listitem>
+			</listbox>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_right%" y="%row3a_y%"/>
+				<text>{@options=Options:}</text>
+			</text>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="%center_x%" y="%row3a_y%" w="%screen_half_width%" h="64"/>
+				<actions>
+					<action function="set">tw_back=restore_confirm</action>
+					<action function="page">restore_options</action>
+				</actions>
+			</button>
+
+			<slider>
+				<text>{@swipe_restore_s=   Restore}</text>
+				<action function="page">restore_run</action>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">restore_options</action>
+			</action>
+		</page>
+
+		<page name="restore_run">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>%tw_operation% %tw_partition%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row14a_y%"/>
+				<text>%tw_size_progress%</text>
+			</text>
+
+			<action>
+				<condition var1="tw_operation_state" var2="1"/>
+				<actions>
+					<action function="set">tw_back=restore</action>
+					<action function="set">tw_complete_text1={@restore_complete=Restore Complete}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<action function="nandroid">restore</action>
+			</action>
+		</page>
+
+		<page name="mount">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@mount_hdr=Mount} &gt; %tw_storage_display_name%</text>
+			</text>
+
+			<partitionlist style="partitionlist_headerless_cb">
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%partitionlist_wipe_height%"/>
+				<listtype name="mount"/>
+			</partitionlist>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row10a_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_mount_system_ro" op="=" var2="0"/>
+					<condition var1="tw_is_super" op="=" var2="0"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row11_y%" textplacement="6"/>
+				<text>{@mount_sys_ro_s_chk=Mount System RO}</text>
+				<image resource="checkbox_false"/>
+				<action function="mountsystemtoggle">1</action>
+			</button>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_mount_system_ro" op="!=" var2="0"/>
+					<condition var1="tw_is_super" op="=" var2="0"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row11_y%" textplacement="6"/>
+				<text>{@mount_sys_ro_s_chk=Mount System RO}</text>
+				<image resource="checkbox_true"/>
+				<actions>
+					<action function="set">tw_lifetime_writes=2</action>
+					<action function="page">system_readonly_check</action>
+				</actions>
+			</button>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_has_mtp" var2="1"/>
+					<condition var1="tw_mtp_enabled" var2="0"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row13_y%" textplacement="6"/>
+				<image resource="checkbox_false"/>
+				<text>{@enable_mtp_btn=Enable MTP}</text>
+				<action function="startmtp"/>
+			</button>
+
+			<button style="checkbox">
+				<conditions>
+					<condition var1="tw_has_mtp" var2="1"/>
+					<condition var1="tw_mtp_enabled" var2="1"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row13_y%" textplacement="6"/>
+				<image resource="checkbox_true"/>
+				<text>{@disable_mtp_btn=Disable MTP}</text>
+				<action function="stopmtp"/>
+			</button>
+
+			<button>
+				<placement x="%btn4_col3_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_storage"/>
+				<actions>
+					<action function="set">tw_storagetext={@mount_hdr=Mount} &gt; {@select_storage_hdr=Select Storage}</action>
+					<action function="set">tw_back=mount</action>
+					<action function="page">select_storage</action>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">mount_options</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="mount_options">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@mount_hdr=Mount} &gt; %tw_storage_display_name%</text>
+			</text>
+
+			<button style="main_button_full_width">
+				<condition var1="tw_has_usb_storage" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@mount_usb_storage_btn=Mount USB Storage}</text>
+				<action function="page">usb_mount</action>
+			</button>
+
+			<button style="main_button_full_width">
+				<conditions>
+					<condition var1="tw_is_encrypted" var2="1"/>
+					<condition var1="tw_is_decrypted" var2="0"/>
+				</conditions>
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@decrypt_data_btn=Decrypt Data}</text>
+				<action function="set">tw_crypto_user_id=0</action>
+				<action function="set">tw_crypto_password=</action>
+				<action function="set">tw_password_fail=0</action>
+				<action function="set">tw_crypto_pwtype=%tw_crypto_pwtype_0%</action>
+				<action function="page">decrypt</action>
+			</button>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">mount</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">mount</action>
+			</action>
+		</page>
+
+		<page name="usb_mount">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@mount_hdr=Mount} &gt; {@usb_storage_hdr=USB Storage}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@usb_stor_mnt1=USB Storage Mounted}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@usb_stor_mnt2=Be sure to safely remove your device}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@usb_stor_mnt3=from your computer before unmounting!}</text>
+			</text>
+
+			<button style="main_button">
+				<placement x="%btn4_col2_x%" y="%row11_y%"/>
+				<text>{@unmount_btn=Unmount}</text>
+				<action function="page">usb_umount</action>
+			</button>
+
+			<action>
+				<action function="mount">usb</action>
+				<action function="set">tw_busy=1</action>
+			</action>
+		</page>
+
+		<page name="usb_umount">
+			<action>
+				<action function="unmount">usb</action>
+			</action>
+
+			<action>
+				<action function="page">mount_options</action>
+				<action function="set">tw_busy=0</action>
+			</action>
+		</page>
+
+		<page name="system_readonly_check">
+			<action>
+				<action function="checkpartitionlifetimewrites">/system</action>
+			</action>
+
+			<action>
+				<condition var1="tw_lifetime_writes" var2="1"/>
+				<actions>
+					<action function="mountsystemtoggle">0</action>
+					<action function="page">mount</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_lifetime_writes" var2="0"/>
+				<actions>
+					<action function="set">tw_back=mount</action>
+					<action function="page">system_readonly</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="reboot">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@reboot_hdr=Reboot}</text>
+			</text>
+
+			<button style="main_button">
+				<condition var1="tw_reboot_system" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@rb_system_btn=System}</text>
+				<actions>
+					<action function="set">tw_back=reboot</action>
+					<action function="page">reboot_system_routine</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_reboot_poweroff" var2="1"/>
+				<placement x="%col1_x_right%" y="%row1_y%"/>
+				<text>{@rb_poweroff_btn=Power Off}</text>
+				<actions>
+					<action function="set">tw_back=reboot</action>
+					<action function="set">tw_action=reboot</action>
+					<action function="set">tw_action_param=poweroff</action>
+					<action function="set">tw_reboot_param=poweroff</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+					<action function="set">tw_text2={@no_ospo=sure you wish to power off?}</action>
+					<action function="set">tw_action_text1={@turning_off=Turning Off...}</action>
+					<action function="set">tw_complete_text1={@turning_off=Turning Off...}</action>
+					<action function="set">tw_slider_text={@swipe_power_off_s=Power Off}</action>
+					<action function="page">rebootcheck</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_reboot_recovery" var2="1"/>
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@rb_recovery_btn=Recovery}</text>
+				<actions>
+					<action function="set">tw_back=reboot</action>
+					<action function="set">tw_action=reboot</action>
+					<action function="set">tw_action_param=recovery</action>
+					<action function="set">tw_reboot_param=recovery</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+					<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+					<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+					<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+					<action function="set">tw_slider_text={@swipe_reboot_s=   Reboot}</action>
+					<action function="page">rebootcheck</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_reboot_bootloader" var2="1"/>
+				<placement x="%col1_x_right%" y="%row6_y%"/>
+				<text>{@rb_bootloader_btn=Bootloader}</text>
+				<actions>
+					<action function="set">tw_back=reboot</action>
+					<action function="set">tw_action=reboot</action>
+					<action function="set">tw_action_param=bootloader</action>
+					<action function="set">tw_reboot_param=bootloader</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+					<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+					<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+					<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+					<action function="set">tw_slider_text={@swipe_reboot_s=   Reboot}</action>
+					<action function="page">rebootcheck</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_download_mode" var2="1"/>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@rb_download_btn=Download}</text>
+				<actions>
+					<action function="set">tw_back=reboot</action>
+					<action function="set">tw_action=reboot</action>
+					<action function="set">tw_action_param=download</action>
+					<action function="set">tw_reboot_param=download</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+					<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+					<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+					<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+					<action function="set">tw_slider_text={@swipe_reboot_s=   Reboot}</action>
+					<action function="page">rebootcheck</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_edl_mode" var2="1"/>
+				<placement x="%col1_x_right%" y="%row11_y%"/>
+				<text>{@rb_edl_btn=EDL}</text>
+				<actions>
+					<action function="set">tw_back=reboot</action>
+					<action function="set">tw_action=reboot</action>
+					<action function="set">tw_action_param=edl</action>
+					<action function="set">tw_reboot_param=edl</action>
+					<action function="set">tw_has_action2=0</action>
+					<action function="set">tw_text1={@no_os1=No OS Installed! Are you}</action>
+					<action function="set">tw_text2={@no_osrb=sure you wish to reboot?}</action>
+					<action function="set">tw_action_text1={@rebooting=Rebooting...}</action>
+					<action function="set">tw_complete_text1={@rebooting=Rebooting...}</action>
+					<action function="set">tw_slider_text={@swipe_reboot_s=   Reboot}</action>
+					<action function="page">rebootcheck</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="installapp">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>%tw_appinstall_title% &gt; {@reboot_install_app_hdr=Install TWRP App}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@reboot_install_app1=Would you like to install the Official TWRP App?}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@reboot_install_app2=The app can check for new TWRP versions.}</text>
+			</text>
+
+			<checkbox>
+				<condition var1="tw_mount_system_ro" var2="0"/>
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>{@reboot_install_app_system=Install as a System App}</text>
+				<data variable="tw_app_install_system"/>
+			</checkbox>
+
+			<button style="main_button_half_height">
+				<placement x="%indent%" y="%row7_y%"/>
+				<text>{@install_cancel=Do not Install}</text>
+				<action function="page">%tw_back%</action>
+			</button>
+
+			<slider>
+				<text>{@swipe_confirm=   Confirm}</text>
+				<actions>
+					<action function="set">tw_action=installapp</action>
+					<action function="set">tw_action_text1={@reboot_installing_app=Installing App...}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@successful=Successful}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_app_install_status=0</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_app_install_status=0</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="system_readonly">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@sys_ro_hdr=Unmodified System Partition}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@sys_ro_keep=Keep System Read only?}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@sys_rop1=TWRP can leave your system partition unmodified}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@sys_rop2=to make it easier for you to take official updates.}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@sys_rop3=TWRP will be unable to prevent the stock ROM from}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@sys_rop4=replacing TWRP and will not offer to root your device.}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@sys_rop5=Installing zips or performing adb operations may still}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row7_y%" placement="5"/>
+				<text>{@sys_rop6=modify the system partition.}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_right%" y="%row8a_y%"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="page">settings_language</action>
+			</button>
+
+			<checkbox>
+				<condition var1="tw_is_encrypted" var2="0"/>
+				<placement x="%indent%" y="%row11_y%"/>
+				<text>{@sys_ro_never_show_chk=Never show this screen during boot again}</text>
+				<data variable="tw_never_show_system_ro_page"/>
+			</checkbox>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row13_y%"/>
+				<text>{@sys_ro_keep_ro_btn=Keep Read Only}</text>
+				<actions>
+					<action function="mountsystemtoggle">1</action>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_right%" y="%row13_y%"/>
+				<text>{@swipe_allow_mod_s=Allow Modifications}</text>
+				<actions>
+					<action function="mountsystemtoggle">0</action>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">%tw_back%</action>
+				</actions>
+			</button>
+		</page>
+
+		<page name="settings">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@settings_hdr=Settings}</text>
+			</text>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@settings_gen_btn=General}</text>
+				<action function="page">settings_general</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_right%" y="%row1_y%"/>
+				<text>{@settings_tz_btn=Time Zone}</text>
+				<action function="page">settings_timezone</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@settings_screen_btn=Screen}</text>
+				<action function="page">settings_screen</action>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_disable_haptics" var2="0"/>
+				<placement x="%col1_x_right%" y="%row6_y%"/>
+				<text>{@settings_vibration_btn=Vibration}</text>
+				<action function="page">settings_vibration</action>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@settings_language_btn=Language}</text>
+				<action function="page">settings_language</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_reset"/>
+				<action function="restoredefaultsettings"/>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main3</action>
+			</action>
+		</page>
+
+		<page name="settings_general">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@settings_hdr=Settings} &gt; {@settings_gen_s_hdr=General}</text>
+			</text>
+
+			<listbox style="scrolllist">
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%listbox_settings_height%"/>
+				<listitem name="{@zip_sig_chk=Zip signature verification}">
+					<data variable="tw_signed_zip_verify"/>
+				</listitem>
+				<listitem name="{@install_reboot_chk=Reboot after installation is complete}">
+					<data variable="tw_install_reboot"/>
+				</listitem>
+				<listitem name="{@use_rmrf_chk=Use rm -rf instead of formatting}">
+					<data variable="tw_rm_rf"/>
+				</listitem>
+				<listitem name="{@disable_backup_space_chk=Disable free space check before backup}">
+					<data variable="tw_disable_free_space"/>
+				</listitem>
+				<listitem name="{@skip_digest_backup_chk=Skip Digest generation during backup}">
+					<data variable="tw_skip_digest_generate"/>
+				</listitem>
+				<listitem name="{@restore_enable_digest_chk=Enable Digest verification of backup files}">
+					<data variable="tw_skip_digest_check"/>
+				</listitem>
+				<listitem name="{@skip_digest_zip_chk=Skip Digest check before installing zip}">
+					<data variable="tw_skip_digest_check_zip"/>
+				</listitem>
+				<listitem name="{@use24clock_chk=Use 24-hour clock}">
+					<data variable="tw_military_time"/>
+				</listitem>
+				<listitem name="{@simact_chk=Simulate actions for theme testing}">
+					<data variable="tw_simulate_actions"/>
+				</listitem>
+				<listitem name="{@simfail_chk=Simulate failure for actions}">
+					<data variable="tw_simulate_fail"/>
+					<condition var1="tw_simulate_actions" var2="1"/>
+				</listitem>
+				<listitem name="{@sha2_chk=Use SHA2 for hashing}">
+					<condition var1="tw_no_sha2" var2="0"/>
+					<data variable="tw_use_sha2"/>
+				</listitem>
+				<listitem name="{@unmount_sys_install=Unmount System before installing a ZIP}">
+					<data variable="tw_unmount_system"/>
+				</listitem>
+				<listitem name="{@auto_reflashtwrp_chk=Automatically Reflash TWRP after flashing a ROM}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<data variable="tw_auto_reflashtwrp"/>
+				</listitem>
+			</listbox>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">settings</action>
+			</action>
+		</page>
+
+		<page name="settings_timezone">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@settings_hdr=Settings} &gt; {@time_zone_hdr=Time Zone}</text>
+			</text>
+
+			<text color="%text_color%">
+				<font resource="font_m"/>
+				<placement x="%indent_right%" y="%row1_header_y%" placement="1"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<listbox style="listbox_headerless">
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%listbox_timezone_height%"/>
+				<data name="tw_time_zone_guisel"/>
+				<listitem name="{@utcm11=(UTC -11) Samoa, Midway Island}">BST11;BDT</listitem>
+				<listitem name="{@utcm10=(UTC -10) Hawaii}">HST10;HDT</listitem>
+				<listitem name="{@utcm9=(UTC -9) Alaska}">AST9;ADT</listitem>
+				<listitem name="{@utcm8=(UTC -8) Pacific Time}">PST8;PDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm7=(UTC -7) Mountain Time}">MST7;MDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm6=(UTC -6) Central Time}">CST6;CDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm5=(UTC -5) Eastern Time}">EST5;EDT,M3.2.0,M11.1.0</listitem>
+				<listitem name="{@utcm4=(UTC -4) Atlantic Time}">AST4;ADT</listitem>
+				<listitem name="{@utcm3=(UTC -3) Brazil, Buenos Aires}">GRNLNDST3;GRNLNDDT</listitem>
+				<listitem name="{@utcm2=(UTC -2) Mid-Atlantic}">FALKST2;FALKDT</listitem>
+				<listitem name="{@utcm1=(UTC -1) Azores, Cape Verde}">AZOREST1;AZOREDT</listitem>
+				<listitem name="{@utc0=(UTC  0) London, Dublin, Lisbon}">GMT0;BST,M3.5.0,M10.5.0</listitem>
+				<listitem name="{@utcp1=(UTC +1) Berlin, Brussels, Paris}">CET-1;CEST,M3.5.0,M10.5.0</listitem>
+				<listitem name="{@utcp2=(UTC +2) Athens, Istanbul, South Africa}">WET-2;WET,M3.5.0,M10.5.0</listitem>
+				<listitem name="{@utcp3=(UTC +3) Moscow, Baghdad}">SAUST-3;SAUDT</listitem>
+				<listitem name="{@utcp4=(UTC +4) Abu Dhabi, Tbilisi, Muscat}">WST-4;WDT</listitem>
+				<listitem name="{@utcp5=(UTC +5) Yekaterinburg, Islamabad}">PAKST-5;PAKDT</listitem>
+				<listitem name="{@utcp6=(UTC +6) Almaty, Dhaka, Colombo}">TASHST-6;TASHDT</listitem>
+				<listitem name="{@utcp7=(UTC +7) Bangkok, Hanoi, Jakarta}">THAIST-7;THAIDT</listitem>
+				<listitem name="{@utcp8=(UTC +8) Beijing, Singapore, Hong Kong}">TAIST-8;TAIDT</listitem>
+				<listitem name="{@utcp9=(UTC +9) Tokyo, Seoul, Yakutsk}">JST-9;JSTDT</listitem>
+				<listitem name="{@utcp10=(UTC +10) Eastern Australia, Guam}">EET-10;EETDT</listitem>
+				<listitem name="{@utcp11=(UTC +11) Vladivostok, Solomon Islands}">MET-11;METDT</listitem>
+				<listitem name="{@utcp12=(UTC +12) Auckland, Wellington, Fiji}">NZST-12;NZDT</listitem>
+			</listbox>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<actions>
+					<action function="setguitimezone"/>
+					<action function="page">settings_timezone2</action>
+				</actions>
+			</button>
+
+			<action>
+				<condition var1="tw_time_zone_guisel" op="modified"/>
+				<action function="setguitimezone"/>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="page">main</action>
+					<action function="setguitimezone"/>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="page">settings</action>
+					<action function="setguitimezone"/>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="settings_timezone2">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@settings_hdr=Settings} &gt; {@time_zone_hdr=Time Zone}</text>
+			</text>
+
+			<text color="%text_color%">
+				<font resource="font_m"/>
+				<placement x="%indent_right%" y="%row1_header_y%" placement="1"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<checkbox>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<font resource="font_m" color="%text_color%"/>
+				<text>{@use_dst_chk=Use daylight savings time (DST)}</text>
+				<data variable="tw_time_zone_guidst"/>
+				<image checked="checkbox_true" unchecked="checkbox_false"/>
+			</checkbox>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@sel_tz_offset=Select Offset (usually 0): %tw_time_zone_guioffset%}</text>
+			</text>
+
+			<button style="main_button_quarter_width">
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@tz_offset_0=0}</text>
+				<actions>
+					<action function="set">tw_time_zone_guioffset=0</action>
+					<action function="setguitimezone"/>
+				</actions>
+			</button>
+
+			<button style="main_button_quarter_width">
+				<placement x="%btn4_col2_x%" y="%row6_y%"/>
+				<text>{@tz_offset_15=15}</text>
+				<actions>
+					<action function="set">tw_time_zone_guioffset=15</action>
+					<action function="setguitimezone"/>
+				</actions>
+			</button>
+
+			<button style="main_button_quarter_width">
+				<placement x="%btn4_col3_x%" y="%row6_y%"/>
+				<text>{@tz_offset_30=30}</text>
+				<actions>
+					<action function="set">tw_time_zone_guioffset=30</action>
+					<action function="setguitimezone"/>
+				</actions>
+			</button>
+
+			<button style="main_button_quarter_width">
+				<placement x="%btn4_col4_x%" y="%row6_y%"/>
+				<text>{@tz_offset_45=45}</text>
+				<actions>
+					<action function="set">tw_time_zone_guioffset=45</action>
+					<action function="setguitimezone"/>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<actions>
+					<action function="setguitimezone"/>
+					<action function="page">settings_timezone</action>
+				</actions>
+			</button>
+
+			<text style="text_m_accent">
+				<placement x="%btn4_col2_x%" y="%row11_y%"/>
+				<text>{@curr_tz_s=Current Time Zone:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%btn4_col2_x%" y="%row12a_y%"/>
+				<text>%tw_time_zone%</text>
+			</text>
+
+			<action>
+				<condition var1="tw_time_zone_guidst" op="modified"/>
+				<action function="setguitimezone"/>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="setguitimezone"/>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="setguitimezone"/>
+					<action function="page">settings_timezone</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="settings_screen">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@settings_hdr=Settings} &gt; {@settings_screen_timeout_hdr=Screen Timeout}</text>
+			</text>
+
+			<button style="checkbox">
+				<condition var1="tw_screen_timeout_secs" op="=" var2="0"/>
+				<condition var1="tw_no_screen_timeout" op="!=" var2="1"/>
+				<placement x="%indent%" y="%row1_y%" textplacement="6"/>
+				<text>{@enable_timeout_chk=Enable screen timeout}</text>
+				<image resource="checkbox_false"/>
+				<action function="set">tw_screen_timeout_secs=60</action>
+			</button>
+
+			<button style="checkbox">
+				<condition var1="tw_screen_timeout_secs" op="!=" var2="0"/>
+				<condition var1="tw_no_screen_timeout" op="!=" var2="1"/>
+				<placement x="%indent%" y="%row1_y%" textplacement="6"/>
+				<text>{@enable_timeout_chk=Enable screen timeout}</text>
+				<image resource="checkbox_true"/>
+				<action function="set">tw_screen_timeout_secs=0</action>
+			</button>
+
+			<slidervalue>
+				<condition var1="tw_no_screen_timeout" op="!=" var2="1"/>
+				<placement x="col1_x_left" y="%row2a_y%" w="%content_width%"/>
+				<text>{@screen_to_slider_s=Screen timeout in seconds (0=disabled): %tw_screen_timeout_secs%}</text>
+				<data variable="tw_screen_timeout_secs" min="15" max="300"/>
+			</slidervalue>
+
+			<text style="text_m_fail">
+				<condition var1="tw_no_screen_timeout" op="=" var2="1"/>
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@screen_to_na=Screen timeout setting unavailable}</text>
+			</text>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">settings_screen2</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">settings</action>
+			</action>
+		</page>
+
+		<page name="settings_screen2">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@settings_hdr=Settings} &gt; {@settings_screen_bright_btn=Screen Brightness}</text>
+			</text>
+
+			<slidervalue>
+				<condition var1="tw_has_brightnesss_file" var2="1"/>
+				<placement x="col1_x_left" y="%row1_y%" w="%content_width%"/>
+				<text>{@screen_bright_slider=Brightness: %tw_brightness_pct%%}</text>
+				<data variable="tw_brightness_pct" min="10" max="100"/>
+				<actions>
+					<action function="set">tw_brightness=%tw_brightness_max%</action>
+					<action function="compute">tw_brightness*%tw_brightness_pct%</action>
+					<action function="compute">tw_brightness/100</action>
+					<action function="setbrightness">%tw_brightness%</action>
+				</actions>
+			</slidervalue>
+
+			<text style="text_m_fail">
+				<condition var1="tw_has_brightnesss_file" var2="0"/>
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@screen_bright_na=Brightness setting unavailable}</text>
+			</text>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">settings_screen</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">settings</action>
+			</action>
+		</page>
+
+		<page name="settings_vibration">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@settings_hdr=Settings} &gt; {@vibration_hdr=Vibration} - {@button_vibration_hdr=Button Vibration}</text>
+			</text>
+
+			<slidervalue>
+				<placement x="col1_x_left" y="%row1_y%" w="%content_width%"/>
+				<text>{@button_vibration=Button Vibration:}</text>
+				<data variable="tw_button_vibrate" min="0" max="300"/>
+			</slidervalue>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">settings_vibration2</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">settings</action>
+			</action>
+		</page>
+
+		<page name="settings_vibration2">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@settings_hdr=Settings} &gt; {@vibration_hdr=Vibration} - {@kb_vibration_hdr=Keyboard Vibration}</text>
+			</text>
+
+			<slidervalue>
+				<placement x="col1_x_left" y="%row1_y%" w="%content_width%"/>
+				<text>{@kb_vibration=Keyboard Vibration:}</text>
+				<data variable="tw_keyboard_vibrate" min="0" max="300"/>
+			</slidervalue>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">settings_vibration</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">settings_vibration3</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">settings</action>
+			</action>
+		</page>
+
+		<page name="settings_vibration3">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@settings_hdr=Settings} &gt; {@vibration_hdr=Vibration} - {@act_vibration_hdr=Action Vibration}</text>
+			</text>
+
+			<slidervalue>
+				<placement x="col1_x_left" y="%row1_y%" w="%content_width%"/>
+				<text>{@act_vibration=Action Vibration:}</text>
+				<data variable="tw_action_vibrate" min="0" max="500"/>
+			</slidervalue>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">settings_vibration2</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">settings</action>
+			</action>
+		</page>
+
+		<page name="settings_language">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@settings_hdr=Settings} &gt; {@language_hdr=Language} - %tw_language_display%</text>
+			</text>
+
+			<listbox style="scrolllist">
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%fileselector_filemanager_height%"/>
+				<icon selected="radio_true" unselected="radio_false" />
+				<data name="tw_language" />
+			</listbox>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept_transp"/>
+				<action function="setlanguage"></action>
+			</button>
+
+			<action>
+				<touch key="home" />
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back" />
+				<action function="page">settings</action>
+			</action>
+		</page>
+
+		<page name="advanced">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@terminal_btn=Terminal}</text>
+				<action function="page">terminalcommand</action>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_has_data_media" var2="1"/>
+				<placement x="%col1_x_right%" y="%row1_y%"/>
+				<text>{@fix_context_btn=Fix Contexts}</text>
+				<action function="page">fixcontexts</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">advanced2</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main3</action>
+			</action>
+		</page>
+
+		<page name="advanced2">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@advanced_hdr=Advanced}</text>
+			</text>
+
+			<listbox style="advanced_listbox">
+				<placement x="%indent%" y="%row2_header_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<listitem name="{@change_twrp_folder_btn=Change TWRP folder}">
+					<condition var1="tw_is_decrypted" var2="1"/>
+					<action function="page">changeTwrpFolder</action>
+				</listitem>
+				<listitem name="{@decrypt_users=Decrypt Users}">
+					<conditions>
+						<condition var1="tw_is_fbe" var2="1"/>
+						<condition var1="tw_all_users_decrypted" var2="0"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_crypto_user_id=</action>
+						<action function="page">decrypt_users</action>
+					</actions>
+				</listitem>
+				<listitem name="{@reload_theme_btn=Reload Theme}">
+					<action function="reload"/>
+				</listitem>
+				<listitem name="{@part_sd_btn=Partition SD Card}">
+					<condition var1="tw_allow_partition_sdcard" var2="1"/>
+					<actions>
+						<action function="set">partitionlisterror=0</action>
+						<action function="page">partsdcardsel</action>
+					</actions>
+				</listitem>
+				<listitem name="{@dumlock_btn=HTC Dumlock}">
+					<condition var1="tw_show_dumlock" var2="1"/>
+					<action function="page">htcdumlock</action>
+				</listitem>
+				<listitem name="{@inject_twrp_btn=Inject TWRP}">
+					<condition var1="tw_has_injecttwrp" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=reinjecttwrp</action>
+						<action function="set">tw_text1={@inject_twrp_confirm=Re-Inject TWRP?}</action>
+						<action function="set">tw_action_text1={@injecting_twrp=Re-Injecting TWRP...}</action>
+						<action function="set">tw_complete_text1={@inject_twrp_complete=TWRP Injection Complete}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@install_twrp_ramdisk=Install Recovery Ramdisk}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_repack_kernel=0</action>
+						<action function="page">repackselect</action>
+					</actions>
+				</listitem>
+				<listitem name="{@reflash_twrp=Flash Current TWRP}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_repack_kernel=0</action>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=reflashtwrp</action>
+						<action function="set">tw_text1={@reflash_twrp_confirm=Flash Current TWRP?}</action>
+						<action function="set">tw_action_text1={@reflashing_twrp=Flashing TWRP...}</action>
+						<action function="set">tw_complete_text1={@reflash_twrp_complete=Done Flashing TWRP}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+						</actions>
+				</listitem>
+				<listitem name="{@install_kernel=Install Kernel}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_repack_kernel=1</action>
+						<action function="page">repackselect</action>
+					</actions>
+				</listitem>
+				<listitem name="{@fix_recovery_loop=Fix Recovery Bootloop}">
+					<conditions>
+						<condition var1="tw_has_boot_slots" var2="1"/>
+						<condition var1="tw_has_repack_tools" var2="1"/>
+						<condition var1="tw_uses_initramfs" var2="1"/>
+					</conditions>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=fixabrecoverybootloop</action>
+						<action function="set">tw_text1={@fix_recovery_loop_confirm=Fix Recovery Bootloop?}</action>
+						<action function="set">tw_action_text1={@fixing_recovery_loop=Fixing Recovery Bootloop...}</action>
+						<action function="set">tw_complete_text1={@fix_recovery_loop_complete=Fix Recovery Bootloop Complete}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@reboot_install_app_hdr=Install TWRP App}">
+					<condition var1="tw_app_install_status" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced2</action>
+						<action function="set">tw_appinstall_title={@advanced_hdr=Advanced}</action>
+						<action function="page">installapp</action>
+					</actions>
+				</listitem>
+				<listitem name="{@uninstall_twrp_system_app=Uninstall TWRP App from System}">
+					<condition var1="tw_app_installed_in_system" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=uninstalltwrpsystemapp</action>
+						<action function="set">tw_text1={@uninstall_twrp_system_app_confirm=Uninstall TWRP App from System?}</action>
+						<action function="set">tw_action_text1={@uninstalling_twrp_system_app=Uninstalling TWRP App from System...}</action>
+						<action function="set">tw_complete_text1={@uninstall_twrp_system_app_complete=Uninstall TWRP App from System Complete}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@unmap_super_devices=Unmap Super Devices}">
+					<condition var1="tw_virtual_ab.enabled" op="=" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=unmapsuperdevices</action>
+						<action function="set">tw_text1={@unmap_super_devices_confirm=Unmap all Super Devices?}</action>
+						<action function="set">tw_action_text1={@unmapping_super_devices=Unmapping Super Devices...}</action>
+						<action function="set">tw_complete_text1={@unmap_super_devices_complete=Unmapped all Super Devices}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+				<listitem name="{@merges_snapshots=Merge Snapshots}">
+					<condition var1="tw_virtual_ab.enabled" op="=" var2="1"/>
+					<actions>
+						<action function="set">tw_back=advanced</action>
+						<action function="set">tw_action=mergesnapshots</action>
+						<action function="set">tw_text1={@merge_snapshots_confirm=Merge Snapshots?}</action>
+						<action function="set">tw_action_text1={@merging_snapshots=Merging Snapshots...}</action>
+						<action function="set">tw_complete_text1={@merging_snapshots_complete=Merged Snapshots}</action>
+						<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+						<action function="page">confirm_action</action>
+					</actions>
+				</listitem>
+			</listbox>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">advanced</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main3</action>
+			</action>
+		</page>
+
+		<page name="partsdcardsel">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@advanced_hdr=Advanced} &gt; {@part_sd_hdr=Partition SD Card}</text>
+			</text>
+
+			<partitionlist style="partitionlist_headerless_rb">
+				<data name="tw_storage_path"/>
+				<listtype name="storage"/>
+			</partitionlist>
+
+			<text style="text_m_fail">
+				<condition var1="partitionlisterror" var2="1"/>
+				<placement x="%center_x%" y="%row9_y%" placement="5"/>
+				<text>{@invalid_partsd_sel=You must select a removable device}</text>
+			</text>
+
+			<button>
+				<placement x="%btn4_col2_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_refresh"/>
+				<action function="refreshsizes"/>
+			</button>
+
+			<button>
+				<placement x="%btn4_col3_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept"/>
+				<actions>
+					<action function="getpartitiondetails">tw_storage_path</action>
+					<action function="page">partsdcardcheck</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="partsdcardcheck">
+			<action>
+				<condition var1="tw_partition_removable" op="=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=0</action>
+					<action function="page">partsdcard</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_partition_removable" op="!=" var2="1"/>
+				<actions>
+					<action function="set">partitionlisterror=1</action>
+					<action function="page">partsdcardsel</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="partsdcard">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@advanced_hdr=Advanced} &gt; {@part_sd_hdr=Partition SD Card}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@part_sd_lose=You will lose all files on your SD card!}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@part_sd_undo=This action cannot be undone!}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@part_sd_ext_sz=EXT Size:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6a_y%" placement="5"/>
+				<text>%tw_sdext_size%</text>
+			</text>
+
+			<button style="main_button_quarter_width">
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>{@part_sd_m=-}</text>
+				<action function="addsubtract">tw_sdext_size-256</action>
+			</button>
+
+			<button style="main_button_quarter_width">
+				<placement x="%btn4_col4_x%" y="%row6_y%"/>
+				<text>{@part_sd_p=+}</text>
+				<action function="addsubtract">tw_sdext_size+256</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">partsdcard2</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">partsdcardsel</action>
+			</action>
+		</page>
+
+		<page name="partsdcard2">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@advanced_hdr=Advanced} &gt; {@part_sd_hdr=Partition SD Card}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@part_sd_lose=You will lose all files on your SD card!}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@part_sd_undo=This action cannot be undone!}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@part_sd_swap_sz=Swap Size:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6a_y%" placement="5"/>
+				<text>%tw_swap_size%</text>
+			</text>
+
+			<button style="main_button_quarter_width">
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>{@part_sd_m=-}</text>
+				<action function="addsubtract">tw_swap_size-64</action>
+			</button>
+
+			<button style="main_button_quarter_width">
+				<placement x="%btn4_col4_x%" y="%row6_y%"/>
+				<text>{@part_sd_p=+}</text>
+				<action function="addsubtract">tw_swap_size+64</action>
+			</button>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">partsdcard</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">partsdcard3</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="partsdcard3">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@advanced_hdr=Advanced} &gt; {@part_sd_hdr=Partition SD Card}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@part_sd_lose=You will lose all files on your SD card!}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@part_sd_undo=This action cannot be undone!}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@file_system=File System:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6a_y%" placement="5"/>
+				<text>%tw_sdpart_file_system%</text>
+			</text>
+
+			<button style="main_button_quarter_width">
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>EXT3</text>
+				<action function="set">tw_sdpart_file_system=ext3</action>
+			</button>
+
+			<button style="main_button_quarter_width">
+				<placement x="%btn4_col4_x%" y="%row6_y%"/>
+				<text>EXT4</text>
+				<action function="set">tw_sdpart_file_system=ext4</action>
+			</button>
+
+			<button>
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_left"/>
+				<action function="page">partsdcard2</action>
+			</button>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_arrow_right"/>
+				<action function="page">partsdcard_confirm</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="partsdcard_confirm">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@advanced_hdr=Advanced} &gt; {@part_sd_hdr=Partition SD Card}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@part_sd_lose=You will lose all files on your SD card!}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@part_sd_undo=This action cannot be undone!}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row4_y%" placement="5"/>
+				<text>{@part_sd_ext_sz=EXT Size:} %tw_sdext_size%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>{@part_sd_swap_sz=Swap Size:} %tw_swap_size%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row8_y%" placement="5"/>
+				<text>{@file_system=File System:} %tw_sdpart_file_system%</text>
+			</text>
+
+			<slider>
+				<text>{@swipe_part_sd_s=Partition}</text>
+				<action function="page">partsdcardaction</action>
+				<actions>
+					<action function="set">tw_back=partsdcard</action>
+					<action function="set">tw_action=partitionsd</action>
+					<action function="set">tw_has_action2=1</action>
+					<action function="set">tw_action2=set</action>
+					<action function="set">tw_action2_param=tw_zip_location=/sdcard</action>
+					<action function="set">tw_action_text1={@partitioning_sd=Partitioning SD Card...}</action>
+					<action function="set">tw_action_text2={@partitioning_sd2=This will take a few minutes.}</action>
+					<action function="set">tw_complete_text1={@part_sd_complete=Partitioning Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="htcdumlock">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@advanced_hdr=Advanced} &gt; {@dumlock_hdr=HTC Dumlock}</text>
+			</text>
+
+			<button style="main_button_full_width">
+				<placement x="%indent%" y="%row1_y%"/>
+				<text>{@dumlock_restore_btn=Restore Original Boot}</text>
+				<actions>
+					<action function="set">tw_back=htcdumlock</action>
+					<action function="set">tw_action=htcdumlockrestoreboot</action>
+					<action function="set">tw_text1={@dumlock_restore_confirm=Restore original boot image?}</action>
+					<action function="set">tw_action_text1={@dumlock_restoring=Restoring Original Boot...}</action>
+					<action function="set">tw_complete_text1={@dumlock_restore_complete=Restore Original Boot Complete}</action>
+					<action function="set">tw_slider_text={@swipe_confirm=   Confirm}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_full_width">
+				<placement x="%indent%" y="%row6_y%"/>
+				<text>{@dumlock_reflash_btn=Reflash Recovery}</text>
+				<actions>
+					<action function="set">tw_back=htcdumlock</action>
+					<action function="set">tw_action=htcdumlockreflashrecovery</action>
+					<action function="set">tw_text1={@dumlock_reflash_confirm=Reflash recovery to boot?}</action>
+					<action function="set">tw_action_text1={@dumlock_reflashing=Flashing recovery to boot...}</action>
+					<action function="set">tw_complete_text1={@dumlock_reflash_complete=Recovery Flash to Boot Complete}</action>
+					<action function="set">tw_slider_text={@swipe_confirm=   Confirm}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<button style="main_button_full_width">
+				<placement x="%indent%" y="%row11_y%"/>
+				<text>{@dumlock_install_btn=Install HTC Dumlock}</text>
+				<actions>
+					<action function="set">tw_back=htcdumlock</action>
+					<action function="set">tw_action=installhtcdumlock</action>
+					<action function="set">tw_action_text1={@dumlock_installing=Installing HTC Dumlock...}</action>
+					<action function="set">tw_complete_text1={@dumlock_install_complete=HTC Dumlock Install Complete}</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="set">tw_slider_text={@swipe_confirm=   Confirm}</action>
+					<action function="page">confirm_action</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="repackselect">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@repack_image_hdr=Select Image}</text>
+			</text>
+
+			<fileselector>
+				<placement x="%indent%" y="%row2_header_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>%tw_zip_location%</text>
+				<filter extn=".img" folders="1" files="1"/>
+				<path name="tw_zip_location" default="/sdcard"/>
+				<data name="tw_filename"/>
+				<selection name="tw_file"/>
+			</fileselector>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_storage"/>
+				<actions>
+					<action function="set">tw_storagetext={@install_hdr=Install} &gt; {@select_storage_hdr=Select Storage}</action>
+					<action function="set">tw_back=install</action>
+					<action function="page">select_storage</action>
+				</actions>
+			</button>
+
+			<action>
+				<conditions>
+					<condition var1="tw_filename" op="modified"/>
+				</conditions>
+				<action function="page">repackconfirm</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="repackconfirm">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<condition var1="tw_repack_kernel" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@repack_kernel_confirm=Install Kernel?}</text>
+			</text>
+
+			<text style="text_m">
+				<condition var1="tw_repack_kernel" var2="0"/>
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@repack_ramdisk_confirm=Install Recovery?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@folder=Folder:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row3_y%"/>
+				<text>%tw_zip_location%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row4_y%"/>
+				<text>{@file=File:}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%indent%" y="%row5_y%"/>
+				<text>%tw_file%</text>
+			</text>
+
+			<checkbox>
+				<placement x="%indent%" y="%row7_y%"/>
+				<text>{@repack_backup_first=Back up existing image first}</text>
+				<data variable="tw_repack_backup_first"/>
+			</checkbox>
+
+			<slider>
+				<text>{@swipe_to_install=Swipe to Install}</text>
+				<actions>
+					<action function="set">tw_back=advanced</action>
+					<action function="set">tw_action=repackimage</action>
+					<action function="set">tw_action_param=/boot</action>
+					<action function="set">tw_action_text1={@installing=Installing...}</action>
+					<action function="set">tw_action_text2=</action>
+					<action function="set">tw_complete_text1={@install_complete=Install Complete}</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">repackselect</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="lock">
+			<background color="%semi_transparent%"/>
+
+			<image>
+				<image resource="unlock_icon"/>
+				<placement x="%center_x%" y="%row6_y%" placement="4"/>
+			</image>
+
+			<slider>
+				<text>{@swipe_unlock=   Unlock}</text>
+				<action function="overlay"/>
+			</slider>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+		</page>
+
+		<page name="filemanagerlist">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@fm_hdr=File Manager} &gt; {@fm_sel_file=Select a File or Folder}</text>
+			</text>
+
+			<fileselector>
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%fileselector_filemanager_height%"/>
+				<text>%tw_file_location1%</text>
+				<filter folders="1" files="1"/>
+				<path name="tw_file_location1" default="/"/>
+				<data name="tw_filename1"/>
+				<selection name="tw_selection1"/>
+			</fileselector>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_folder"/>
+				<actions>
+					<action function="set">tw_filename1=tw_file_location1</action>
+					<action function="set">tw_fm_isfolder=1</action>
+					<action function="set">tw_fm_type={@fm_type_folder=Folder}</action>
+					<action function="page">filemanageroptions</action>
+				</actions>
+			</button>
+
+			<action>
+				<actions>
+					<action function="set">tw_fm_type={@fm_type_file=File}</action>
+					<action function="set">tw_fm_isfolder=0</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">main3</action>
+			</action>
+
+			<action>
+				<condition var1="tw_filename1" op="modified"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanageroptions">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>%tw_fm_type% &gt; %tw_filename1%</text>
+			</text>
+
+			<button style="main_button">
+				<condition var1="tw_fm_isfolder" var2="0"/>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@fm_copy_btn=Copy}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=cp</action>
+					<action function="set">tw_fm_text1={@fm_copying=Copying}</action>
+					<action function="page">choosedestinationfolder</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_fm_isfolder" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@fm_copy_btn=Copy}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=cd "%tw_file_location1%" &amp;&amp; cd .. &amp;&amp; cp -R</action>
+					<action function="set">tw_fm_text1={@fm_copying=Copying}</action>
+					<action function="page">choosedestinationfolder</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_right%" y="%row1_y%"/>
+				<text>{@fm_move_btn=Move}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=mv</action>
+					<action function="set">tw_fm_text1={@fm_moving=Moving}</action>
+					<action function="page">choosedestinationfolder</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row6_y%"/>
+				<text>{@fm_chmod755_btn=chmod 755}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=chmod 755</action>
+					<action function="set">tw_fm_text1={@fm_chmod755ing=chmod 755}</action>
+					<action function="set">tw_fm_text2=</action>
+					<action function="set">tw_fm_text3=</action>
+					<action function="set">tw_include_text3=0</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_right%" y="%row6_y%"/>
+				<text>{@fm_chmod_btn=chmod}</text>
+				<actions>
+					<action function="set">tw_filemanager_rename=0000</action>
+					<action function="set">tw_fm_text2=</action>
+					<action function="set">tw_fm_text3=</action>
+					<action function="set">tw_include_text3=0</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerchmod</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<placement x="%col1_x_left%" y="%row11_y%"/>
+				<text>{@fm_delete_btn=Delete}</text>
+				<actions>
+					<action function="set">tw_filemanager_command=rm -rf</action>
+					<action function="set">tw_fm_text1={@fm_deleting=Deleting}</action>
+					<action function="set">tw_fm_text2=</action>
+					<action function="set">tw_fm_text3=</action>
+					<action function="set">tw_include_text3=0</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_fm_isfolder" var2="0"/>
+				<placement x="%col1_x_right%" y="%row11_y%"/>
+				<text>{@fm_rename_btn=Rename}</text>
+				<actions>
+					<action function="set">tw_filemanager_rename=tw_selection1</action>
+					<action function="set">tw_fm_text1={@fm_renaming=Renaming}</action>
+					<action function="set">tw_filemanager_command=mv</action>
+					<action function="page">filemanagerrenamefile</action>
+				</actions>
+			</button>
+
+			<button style="main_button">
+				<condition var1="tw_fm_isfolder" var2="1"/>
+				<placement x="%col1_x_right%" y="%row11_y%"/>
+				<text>{@fm_rename_btn=Rename}</text>
+				<actions>
+					<action function="set">tw_filemanager_rename=tw_selection1</action>
+					<action function="set">tw_fm_text1={@fm_renaming=Renaming}</action>
+					<action function="set">tw_filemanager_command=cd "%tw_file_location1%" &amp;&amp; cd .. &amp;&amp; mv</action>
+					<action function="page">filemanagerrenamefolder</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanagerlist</action>
+			</action>
+		</page>
+
+		<page name="choosedestinationfolder">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@fm_hdr=File Manager} &gt; {@fm_sel_dest=Select Destination Folder}</text>
+			</text>
+
+			<fileselector>
+				<placement x="%col1_x_left%" y="%row2_header_y%" w="%content_width%" h="%fileselector_filemanager_height%"/>
+				<text>%tw_file_location2%</text>
+				<filter folders="1" files="0"/>
+				<path name="tw_file_location2" default="/"/>
+				<data name="tw_filename2"/>
+				<selection name="tw_selection2"/>
+			</fileselector>
+
+			<button>
+				<placement x="%btn4_col4_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_folder"/>
+				<actions>
+					<action function="set">tw_fm_text2=to</action>
+					<action function="set">tw_fm_text3=%tw_file_location2%</action>
+					<action function="set">tw_include_text3=1</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerrenamefile">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@fm_hdr=File Manager} &gt; {@fm_rename_hdr=Rename} %tw_fm_type%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_filemanager_rename%</text>
+				<data name="tw_filemanager_rename"/>
+				<restrict minlen="1" maxlen="128"/>
+				<actions>
+					<action function="set">tw_fm_text2=to</action>
+					<action function="set">tw_fm_text3="%tw_file_location1%/%tw_filemanager_rename%"</action>
+					<action function="set">tw_include_text3=1</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_line_width%" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">filemanageroptions</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerrenamefolder">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@fm_hdr=File Manager} &gt; {@fm_rename_hdr=Rename} %tw_fm_type%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_filemanager_rename%</text>
+				<data name="tw_filemanager_rename"/>
+				<restrict minlen="1" maxlen="128"/>
+				<actions>
+					<action function="set">tw_fm_text2=to</action>
+					<action function="set">tw_fm_text3=%tw_filemanager_rename%</action>
+					<action function="set">tw_include_text3=1</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">filemanageroptions</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerchmod">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@fm_hdr=File Manager} &gt; {@fm_set_perms_hdr=Set Permissions}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@fm_perms=Permissions:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_filemanager_rename%</text>
+				<data name="tw_filemanager_rename"/>
+				<restrict minlen="3" maxlen="4" allow="0123456789"/>
+				<actions>
+					<action function="set">tw_filemanager_command=chmod %tw_filemanager_rename%</action>
+					<action function="set">tw_fm_text1=chmod %tw_filemanager_rename%</action>
+					<action function="set">tw_back=filemanageroptions</action>
+					<action function="page">filemanagerconfirm</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row4_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">filemanageroptions</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">filemanageroptions</action>
+			</action>
+		</page>
+
+		<page name="filemanagerconfirm">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@fm_hdr=File Manager} &gt; {@confirm_action=Confirm Action}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>%tw_fm_text1%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>%tw_filename1%</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>%tw_fm_text2%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row6_y%" placement="5"/>
+				<text>%tw_fm_text3%</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row8_y%" placement="5"/>
+				<text>{@back_cancel=Press back button to cancel.}</text>
+			</text>
+
+			<slider>
+				<text>{@swipe_confirm=   Confirm}</text>
+				<action function="page">filemanageraction</action>
+			</slider>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">%tw_back%</action>
+			</action>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+		</page>
+
+		<page name="filemanageraction">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@fm_hdr=File Manager} &gt; %tw_fm_text1%</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_back=filemanagerlist</action>
+					<action function="set">tw_complete_text1={@fm_complete=File Operation Complete}</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="!=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_complete_text1={@fm_complete=File Operation Complete}</action>
+					<action function="page">action_complete</action>
+				</actions>
+			</action>
+
+			<action>
+				<condition var1="tw_include_text3" var2="0"/>
+				<action function="cmd">%tw_filemanager_command% "%tw_filename1%"</action>
+			</action>
+
+			<action>
+				<condition var1="tw_include_text3" var2="1"/>
+				<action function="cmd">%tw_filemanager_command% "%tw_filename1%" "%tw_fm_text3%"</action>
+			</action>
+		</page>
+
+		<page name="decrypt">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<action>
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<action function="page">decrypt_pattern</action>
+			</action>
+
+			<action>
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<action function="page">decrypt_pin</action>
+			</action>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@mount_hdr=Mount} &gt; {@decrypt_data_hdr=Decrypt Data}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" op="!=" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@decrypt_data_enter_pass=Enter Password:}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@decrypt_data_enter_pass_fbe=Enter Password for User [%tw_crypto_user_id%]}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_crypto_display%</text>
+				<data name="tw_crypto_password" mask="*" maskvariable="tw_crypto_display"/>
+				<restrict minlen="1" maxlen="254"/>
+				<action function="page">trydecrypt</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row3_input_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="row3_input_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="%row3_input_y%"/>
+				<text>{@decrypt_data_failed=Password failed, please try again!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="page">settings_language</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_right%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">canceldecrypt</action>
+			</button>
+
+			<template name="keyboardtemplate"/>
+		</page>
+
+		<page name="decrypt_pattern">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<action>
+				<action function="set">tw_gui_pattern_grid_size=3</action>
+			</action>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@mount_hdr=Mount} &gt; {@decrypt_data_hdr=Decrypt Data}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" op="!=" var2="1"/>
+				<condition var1="tw_password_fail" op="!=" var2="1"/>
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pattern=Enter Pattern.}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" var2="1"/>
+				<condition var1="tw_password_fail" op="!=" var2="1"/>
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@decrypt_data_enter_pattern_fbe=Enter Pattern for User [%tw_crypto_user_id%]}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%center_x%" y="%row5_y%" placement="5"/>
+				<text>{@decrypt_data_failed_pattern=Pattern failed, please try again!}</text>
+			</text>
+
+			<text style="text_m">
+				<placement x="%center_x%" y="%row10_y%" placement="5"/>
+				<text>{@back_cancel=Press back to cancel.}</text>
+			</text>
+
+			<patternpassword>
+				<placement x="%pattern_x%" y="%row2_y%" w="%pattern_size%" h="%pattern_size%"/>
+				<dot color="%fileselector_linecolor%" activecolor="%accent_color%" radius="%pattern_dot_dia%"/>
+				<line color="%fileselector_linecolor%" width="%pattern_line_w%"/>
+				<data name="tw_crypto_password"/>
+				<action function="page">trydecrypt</action>
+			</patternpassword>
+
+			<fill color="#000000">
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%"/>
+			</fill>
+
+			<button>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="page">canceldecrypt</action>
+			</button>
+
+			<button>
+				<condition var1="tw_gui_pattern_grid_size" var2="3"/>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="grid_less"/>
+				<action function="set">tw_gui_pattern_grid_size=3</action>
+			</button>
+
+			<button>
+				<condition var1="tw_gui_pattern_grid_size" var2="4"/>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="grid_less"/>
+				<action function="set">tw_gui_pattern_grid_size=3</action>
+			</button>
+
+			<button>
+				<condition var1="tw_gui_pattern_grid_size" var2="5"/>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="grid_less"/>
+				<action function="set">tw_gui_pattern_grid_size=4</action>
+			</button>
+
+			<button>
+				<condition var1="tw_gui_pattern_grid_size" var2="6"/>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="grid_less"/>
+				<action function="set">tw_gui_pattern_grid_size=5</action>
+			</button>
+
+			<button>
+				<condition var1="tw_gui_pattern_grid_size" var2="3"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="grid_more"/>
+				<action function="set">tw_gui_pattern_grid_size=4</action>
+			</button>
+
+			<button>
+				<condition var1="tw_gui_pattern_grid_size" var2="4"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="grid_more"/>
+				<action function="set">tw_gui_pattern_grid_size=5</action>
+			</button>
+
+			<button>
+				<condition var1="tw_gui_pattern_grid_size" var2="5"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="grid_more"/>
+				<action function="set">tw_gui_pattern_grid_size=6</action>
+			</button>
+
+			<button>
+				<condition var1="tw_gui_pattern_grid_size" var2="6"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="grid_more"/>
+				<action function="set">tw_gui_pattern_grid_size=6</action>
+			</button>
+		</page>
+
+		<page name="decrypt_pin">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@mount_hdr=Mount} &gt; {@decrypt_data_hdr=Decrypt Data}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" op="!=" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@decrypt_data_enter_pass=Enter PIN:}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_is_fbe" var2="1"/>
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@decrypt_data_enter_pass_fbe=Enter PIN for User [%tw_crypto_user_id%]}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_crypto_display%</text>
+				<data name="tw_crypto_password" mask="*" maskvariable="tw_crypto_display"/>
+				<restrict minlen="1" maxlen="254"/>
+				<action function="page">trydecrypt</action>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="row3_input_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="row3_input_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_password_fail" var2="1"/>
+				<placement x="%col1_x_left%" y="%row3_input_y%"/>
+				<text>{@decrypt_data_failed=PIN failed, please try again!}</text>
+			</text>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@sel_lang_btn=Select Language}</text>
+				<action function="page">settings_language</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_right%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">canceldecrypt</action>
+			</button>
+
+			<template name="keyboardnum"/>
+		</page>
+
+		<page name="trydecrypt">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@mount_hdr=Mount} &gt; {@decrypt_data_trying=Trying Decryption}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<action function="decrypt"/>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="!=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_password_fail=1</action>
+					<action function="page">decrypt</action>
+				</actions>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+					<condition var1="tw_operation_status" op="=" var2="0"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="canceldecrypt">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@cancel_btn=Cancel} &gt; {@refresh_sizes_btn=Refresh Sizes}</text>
+			</text>
+
+			<template name="console"/>
+
+			<template name="progress_bar"/>
+
+			<action>
+				<action function="refreshsizes"/>
+			</action>
+
+			<action>
+				<conditions>
+					<condition var1="tw_operation_state" var2="1"/>
+				</conditions>
+				<actions>
+					<action function="set">tw_page_done=1</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="terminalcommand">
+			<terminal>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="0" w="%screen_width%" h="%console_terminal_s_height%"/>
+			</terminal>
+
+			<terminal>
+				<condition var1="tw_hide_kb" var2="1"/>
+				<placement x="0" y="0" w="%screen_width%" h="%console_terminal_l_height%"/>
+			</terminal>
+
+			<template name="keyboardterminaltemplate"/>
+
+			<template name="keyboardtemplate"/>
+
+			<fill color="#000000">
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%" />
+			</fill>
+
+			<button>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="back" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="key">back</action>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="home" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="key">home</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_hide" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=1</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<button>
+				<condition var1="tw_hide_kb" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4" />
+				<image resource="kb_show" />
+				<condition var1="tw_busy" var2="0" />
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">terminalcommand</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="set">tw_hide_kb=0</action>
+					<action function="page">advanced</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="power+voldown" />
+				<action function="screenshot" />
+			</action>
+		</page>
+
+		<page name="sideload">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@install_hdr=Install} &gt; {@adb_sideload_hdr=ADB Sideload}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%indent%" y="%row1_y%"/>
+				<text>{@options=Options:}</text>
+			</text>
+
+			<checkbox>
+				<placement x="%indent%" y="%row2_y%"/>
+				<text>{@sideload_wipe_dalvik_chk=Wipe Dalvik Cache}</text>
+				<data variable="tw_wipe_dalvik"/>
+			</checkbox>
+
+			<checkbox>
+				<placement x="%indent%" y="%row4a_y%"/>
+				<text>{@sideload_wipe_cache_chk=Wipe Cache}</text>
+				<data variable="tw_wipe_cache"/>
+			</checkbox>
+
+			<slider>
+				<text>{@swipe_sideload=   Start}</text>
+				<actions>
+					<action function="set">tw_back=install_type</action>
+					<action function="set">tw_action=adbsideload</action>
+					<action function="set">tw_action_text1={@sideload_confirm=ADB Sideload}</action>
+					<action function="set">tw_action_text2={@sideload_usage=Usage: adb sideload filename.zip}</action>
+					<action function="set">tw_complete_text1={@sideload_complete=ADB Sideload Complete}</action>
+					<action function="set">tw_has_cancel=1</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="set">tw_cancel_action=adbsideloadcancel</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">install_type</action>
+			</action>
+		</page>
+
+		<page name="fixcontexts">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@advanced_hdr=Advanced} &gt; {@fix_contexts_hdr=Fix Contexts}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@fix_contexts_note1=Note: Fixing contexts is rarely needed.}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row6a_y%" placement="5"/>
+				<text>{@fix_contexts_note2=Fixing SELinux Contexts may cause}</text>
+			</text>
+
+			<text style="text_m_fail">
+				<placement x="%center_x%" y="%row7a_y%" placement="5"/>
+				<text>{@fix_contexts_note3=your device to not boot properly.}</text>
+			</text>
+
+			<slider>
+				<text>{@swipe_fix_contexts=   Fix Contexts}</text>
+				<actions>
+					<action function="set">tw_back=advanced</action>
+					<action function="set">tw_action=fixcontexts</action>
+					<action function="set">tw_action_text1={@fixing_contexts=Fixing Contexts...}</action>
+					<action function="set">tw_complete_text1={@fix_contexts_complete=Fix Contexts Complete}</action>
+					<action function="set">tw_slider_text={@swipe_confirm=   Confirm}</action>
+					<action function="set">tw_show_reboot=1</action>
+					<action function="page">action_page</action>
+				</actions>
+			</slider>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="slideout">
+			<fill color="%background_color%">
+				<placement x="0" y="%row2_header_y%" w="%screen_width%" h="%slideout_bg_height%"/>
+			</fill>
+
+			<console>
+				<placement x="%indent%" y="%row1_y%" w="%content_width%" h="%slideout_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row1_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row15a_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+
+			<button>
+				<fill color="%transparent%"/>
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%"/>
+				<action function="overlay"/>
+			</button>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="select_storage">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>%tw_storagetext%</text>
+			</text>
+
+			<partitionlist style="partitionlist_headerless_rb">
+				<data name="tw_storage_path"/>
+				<listtype name="storage"/>
+			</partitionlist>
+
+			<button>
+				<placement x="%btn4_col2_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_refresh"/>
+				<actions>
+					<action function="refreshsizes"/>
+					<action function="page">select_storage</action>
+				</actions>
+			</button>
+
+			<button>
+				<placement x="%btn4_col3_x%" y="%row11_y%"/>
+				<highlight color="%highlight_color%"/>
+				<image resource="q_btn_accept"/>
+				<actions>
+					<action function="set">tw_clear_destination=%tw_back%</action>
+					<action function="page">clear_vars</action>
+				</actions>
+			</button>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</page>
+
+		<page name="decrypt_users">
+			<template name="page"/>
+
+			<text style="text_l">
+				<placement x="%col1_x_header%" y="%row3_header_y%"/>
+				<text>{@decrypt_users=Decrypt Users}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@decrypt_users_selection=Select a user ID to decrypt}</text>
+			</text>
+
+			<listbox>
+				<placement x="%indent%" y="%row2_header_y%" w="%content_width%" h="%fileselector_install_height%"/>
+				<text>{@select_user=Select User}</text>
+				<icon selected="radio_true" unselected="radio_false" />
+				<data name="tw_crypto_user_id" />
+			</listbox>
+
+			<button style="main_button_half_height">
+				<placement x="%col1_x_left%" y="%row13_y%" textplacement="6"/>
+				<text>{@decrypt_users=Decrypt Users}</text>
+				<action function="set">tw_crypto_password=</action>
+				<action function="set">tw_password_fail=0</action>
+				<action function="page">decrypt</action>
+			</button>
+
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<action function="page">advanced</action>
+			</action>
+		</page>
+
+		<page name="changeTwrpFolder">
+			<template name="page"/>
+
+			<template name="statusbar"/>
+
+			<text style="text_m">
+				<placement x="%col1_x_left%" y="%row1_header_y%"/>
+				<text>{@change_twrp_folder_btn=Change TWRP folder}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<placement x="%col1_x_left%" y="%row1_y%"/>
+				<text>{@name=Name:}</text>
+			</text>
+
+			<input>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%input_height%"/>
+				<text>%tw_custom_folder%</text>
+				<data name="tw_custom_folder"/>
+				<restrict minlen="1" maxlen="64" allow="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"/>
+				<actions>
+					<action function="set">tw_back=changeTwrpFolder</action>
+					<action function="set">tw_action=applycustomtwrpfolder</action>
+					<action function="set">tw_action_param=%tw_custom_folder%</action>
+					<action function="set">tw_text1={@confirm_action=Confirm}</action>
+					<action function="set">tw_text2={@rename_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@change_twrp_folder_on_process=Changing TWRP folder}</action>
+					<action function="set">tw_complete_text1={@change_twrp_folder_after_process=TWRP folder changed to} %tw_custom_folder%</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="set">tw_filecheck=/sdcard/%tw_custom_folder%</action>
+					<action function="set">tw_existpage=changeTwrpFolder</action>
+					<action function="set">tw_notexistpage=confirm_action</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</input>
+
+			<fill color="%accent_color%">
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_line_width%" placement="1"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="%row3_input_y%" w="%content_width%" h="%input_line_width%" placement="1"/>
+			</fill>
+
+			<text style="text_m_fail">
+				<condition var1="tw_fileexists" var2="1"/>
+				<placement x="%col1_x_left%" y="%row3_input_y%"/>
+				<text>{@tw_folder_exists=A folder with that name already exists!}</text>
+			</text>
+
+			<button style="main_button_half_width_low">
+				<placement x="%col1_x_left%" y="%row4_y%"/>
+				<text>{@cancel_btn=Cancel}</text>
+				<action function="page">advanced2</action>
+			</button>
+
+			<button style="main_button_half_height">
+				<condition var1="tw_recovery_folder" op="!=" var2="/TWRP"/>
+				<placement x="%col1_x_right%" y="%row4_y%"/>
+				<text>{@restore_defaults_btn=Restore Defaults}</text>
+				<actions>
+					<action function="set">tw_back=changeTwrpFolder</action>
+					<action function="set">tw_action=applycustomtwrpfolder</action>
+					<action function="set">tw_action_param=TWRP</action>
+					<action function="set">tw_text1={@confirm_action=Confirm}</action>
+					<action function="set">tw_text2={@rename_backup_confirm2=This cannot be undone!}</action>
+					<action function="set">tw_action_text1={@change_twrp_folder_on_process=Changing TWRP folder}</action>
+					<action function="set">tw_complete_text1={@change_twrp_folder_after_process=TWRP folder changed to} TWRP</action>
+					<action function="set">tw_slider_text={@swipe_to_confirm=Swipe to Confirm}</action>
+					<action function="set">tw_filecheck=/sdcard/TWRP</action>
+					<action function="set">tw_existpage=changeTwrpFolder</action>
+					<action function="set">tw_notexistpage=confirm_action</action>
+					<action function="page">filecheck</action>
+				</actions>
+			</button>
+
+			<template name="keyboardtemplate"/>
+
+			<action>
+				<touch key="home"/>
+				<actions>
+					<action function="page">main</action>
+				</actions>
+			</action>
+
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="page">advanced2</action>
+				</actions>
+			</action>
+		</page>
+
+		<page name="restore_keymaster">
+			<template name="page"/>
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="1"/>
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@restore_with_pin_password1=PIN/Password is enabled}</text>
+			</text>
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="1"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@restore_with_pin_password2=PIN/Password should be disabled before restore}</text>
+			</text>
+			<text style="text_l">
+				<condition var1="tw_crypto_pwtype" var2="1"/>
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@restore_pin_password=Restore While PIN/Password Enabled?}</text>
+			</text>
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<placement x="%center_x%" y="%row1_y%" placement="5"/>
+				<text>{@restore_with_pattern1=Pattern is enabled}</text>
+			</text>
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@restore_with_pattern2=Pattern should be disabled before restore}</text>
+			</text>
+			<text style="text_l">
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@restore_pattern=Restore While Pattern Enabled?}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="2"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@restore_with_pin1=PIN is enabled}</text>
+			</text>
+
+			<text style="text_m_accent">
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<placement x="%center_x%" y="%row2_y%" placement="5"/>
+				<text>{@restore_with_pin2=PIN should be disabled before restore}</text>
+			</text>
+
+			<text style="text_l">
+				<condition var1="tw_crypto_pwtype" var2="3"/>
+				<placement x="%center_x%" y="%row3_y%" placement="5"/>
+				<text>{@restore_pin=Restore While PIN Enabled?}</text>
+			</text>
+
+			<slider>
+				<text>{@continue_restore_encrypted=Continue Restore?}</text>
+				<actions>
+					<action function="page">restore_force</action>
+				</actions>
+			</slider>
+			<action>
+				<touch key="home"/>
+				<action function="page">main</action>
+			</action>
+			<action>
+				<touch key="back"/>
+				<actions>
+					<action function="page">main</action>
+				</actions>
+			</action>
+		</page>
+	</pages>
+</recovery>
diff --git a/gui/theme/extra-languages/fonts/DroidSansFallback.ttf b/gui/theme/extra-languages/fonts/DroidSansFallback.ttf
new file mode 100644
index 0000000..ba9d76f
--- /dev/null
+++ b/gui/theme/extra-languages/fonts/DroidSansFallback.ttf
Binary files differ
diff --git a/gui/theme/extra-languages/fonts/NotoSansCJKjp-Regular.ttf b/gui/theme/extra-languages/fonts/NotoSansCJKjp-Regular.ttf
new file mode 100644
index 0000000..bb71b63
--- /dev/null
+++ b/gui/theme/extra-languages/fonts/NotoSansCJKjp-Regular.ttf
Binary files differ
diff --git a/gui/theme/extra-languages/fonts/OFL.txt b/gui/theme/extra-languages/fonts/OFL.txt
new file mode 100644
index 0000000..b6d9425
--- /dev/null
+++ b/gui/theme/extra-languages/fonts/OFL.txt
@@ -0,0 +1,93 @@
+Copyright (c) 2011, Cyreal (www.cyreal.org),

+with Reserved Font Name "Junge".

+This Font Software is licensed under the SIL Open Font License, Version 1.1.

+This license is copied below, and is also available with a FAQ at:

+http://scripts.sil.org/OFL

+

+

+-----------------------------------------------------------

+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007

+-----------------------------------------------------------

+

+PREAMBLE

+The goals of the Open Font License (OFL) are to stimulate worldwide

+development of collaborative font projects, to support the font creation

+efforts of academic and linguistic communities, and to provide a free and

+open framework in which fonts may be shared and improved in partnership

+with others.

+

+The OFL allows the licensed fonts to be used, studied, modified and

+redistributed freely as long as they are not sold by themselves. The

+fonts, including any derivative works, can be bundled, embedded, 

+redistributed and/or sold with any software provided that any reserved

+names are not used by derivative works. The fonts and derivatives,

+however, cannot be released under any other type of license. The

+requirement for fonts to remain under this license does not apply

+to any document created using the fonts or their derivatives.

+

+DEFINITIONS

+"Font Software" refers to the set of files released by the Copyright

+Holder(s) under this license and clearly marked as such. This may

+include source files, build scripts and documentation.

+

+"Reserved Font Name" refers to any names specified as such after the

+copyright statement(s).

+

+"Original Version" refers to the collection of Font Software components as

+distributed by the Copyright Holder(s).

+

+"Modified Version" refers to any derivative made by adding to, deleting,

+or substituting -- in part or in whole -- any of the components of the

+Original Version, by changing formats or by porting the Font Software to a

+new environment.

+

+"Author" refers to any designer, engineer, programmer, technical

+writer or other person who contributed to the Font Software.

+

+PERMISSION & CONDITIONS

+Permission is hereby granted, free of charge, to any person obtaining

+a copy of the Font Software, to use, study, copy, merge, embed, modify,

+redistribute, and sell modified and unmodified copies of the Font

+Software, subject to the following conditions:

+

+1) Neither the Font Software nor any of its individual components,

+in Original or Modified Versions, may be sold by itself.

+

+2) Original or Modified Versions of the Font Software may be bundled,

+redistributed and/or sold with any software, provided that each copy

+contains the above copyright notice and this license. These can be

+included either as stand-alone text files, human-readable headers or

+in the appropriate machine-readable metadata fields within text or

+binary files as long as those fields can be easily viewed by the user.

+

+3) No Modified Version of the Font Software may use the Reserved Font

+Name(s) unless explicit written permission is granted by the corresponding

+Copyright Holder. This restriction only applies to the primary font name as

+presented to the users.

+

+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font

+Software shall not be used to promote, endorse or advertise any

+Modified Version, except to acknowledge the contribution(s) of the

+Copyright Holder(s) and the Author(s) or with their explicit written

+permission.

+

+5) The Font Software, modified or unmodified, in part or in whole,

+must be distributed entirely under this license, and must not be

+distributed under any other license. The requirement for fonts to

+remain under this license does not apply to any document created

+using the Font Software.

+

+TERMINATION

+This license becomes null and void if any of the above conditions are

+not met.

+

+DISCLAIMER

+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF

+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT

+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE

+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,

+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL

+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM

+OTHER DEALINGS IN THE FONT SOFTWARE.

diff --git a/gui/theme/extra-languages/languages/ja.xml b/gui/theme/extra-languages/languages/ja.xml
new file mode 100644
index 0000000..9fe767b
--- /dev/null
+++ b/gui/theme/extra-languages/languages/ja.xml
@@ -0,0 +1,661 @@
+<?xml version="1.0" encoding="utf-8"?>
+<language>
+	<display>Japanese</display>
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource filename="NotoSansCJKjp-Regular.ttf" name="font_l" scale="80" type="fontoverride"/>
+		<resource filename="NotoSansCJKjp-Regular.ttf" name="font_m" scale="85" type="fontoverride"/>
+		<resource filename="NotoSansCJKjp-Regular.ttf" name="font_s" scale="90" type="fontoverride"/>
+		<resource filename="NotoSansCJKjp-Regular.ttf" name="fixed" scale="90" type="fontoverride"/>
+		<!-- Partition display names -->
+		<string name="system">System</string>
+		<string name="system_image">Systemイメージ</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Vendorイメージ</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="sdcard">SDカード</string>
+		<string name="internal">内部ストレージ</string>
+		<string name="microsd">microSD カード</string>
+		<string name="usbotg">USB OTG</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Adopted Data</string>
+		<string name="adopted_storage">Adopted Storage</string>
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU: %tw_cpu_temp% °C</string>
+		<string name="battery_pct">バッテリー: %tw_battery%</string>
+		<string name="sort_by_name">名前順で並び替え</string>
+		<string name="sort_by_date">日付順で並び替え</string>
+		<string name="sort_by_size">サイズ順で並び替え</string>
+		<string name="sort_by_name_only">名前</string>
+		<string name="sort_by_date_only">日付</string>
+		<string name="sort_by_size_only">サイズ</string>
+		<string name="tab_general">基本</string>
+		<string name="tab_options">オプション</string>
+		<string name="tab_backup">バックアップ</string>
+		<string name="tab_time_zone">タイムゾーン</string>
+		<string name="tab_screen">画面</string>
+		<string name="tab_vibration">バイブ</string>
+		<string name="tab_language">言語</string>
+		<string name="install_btn">インストール</string>
+		<string name="wipe_btn">消去</string>
+		<string name="backup_btn">バックアップ</string>
+		<string name="restore_btn">リストア</string>
+		<string name="mount_btn">マウント</string>
+		<string name="settings_btn">設定</string>
+		<string name="advanced_btn">高度な機能</string>
+		<string name="reboot_btn">再起動</string>
+		<string name="files_btn">ファイル</string>
+		<string name="copy_log_btn">ログのコピー</string>
+		<string name="select_type_hdr">形式の選択</string>
+		<string name="install_zip_hdr">ZIPのインストール</string>
+		<string name="install_zip_btn">ZIPのインストール</string>
+		<string name="install_image_hdr">イメージのインストール</string>
+		<string name="install_image_btn">イメージのインストール</string>
+		<string name="install_select_file_hdr">ファイルの選択</string>
+		<string name="file_selector_folders_hdr">フォルダ</string>
+		<string name="select_file_from_storage">%tw_storage_display_name% (%tw_storage_free_size% MB) からファイルを選択</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">インストール</string>
+		<string name="select_storage_hdr">ストレージの選択</string>
+		<string name="select_storage_btn">ストレージの選択</string>
+		<string name="queue_hdr">キュー</string>
+		<string name="zip_queue_count">%tw_zip_queue_count% / 10ファイルキューに追加済み</string>
+		<string name="zip_queue_count_s">ファイル %tw_zip_queue_count% / 10:</string>
+		<string name="zip_warn1">この操作では互換性のないソフトウェアを</string>
+		<string name="zip_warn2">インストールして文鎮化する可能性もあります。</string>
+		<string name="zip_back_cancel">バックキーで ZIP の追加をキャンセルします。</string>
+		<string name="zip_back_clear">バックキーでキューを消去します。</string>
+		<string name="folder">フォルダ:</string>
+		<string name="file">ファイル:</string>
+		<string name="zip_sig_chk">ZIPの署名確認</string>
+		<string name="inject_twrp_chk">インストール後にTWRPを組み込む</string>
+		<string name="install_reboot_chk">インストール後に再起動する</string>
+		<string name="options_hdr">オプション</string>
+		<string name="confirm_flash_hdr">書き込みの確認</string>
+		<string name="zip_queue">キュー:</string>
+		<string name="options">オプション:</string>
+		<string name="swipe_confirm">   確認</string>
+		<string name="zip_add_btn">ZIPを追加する</string>
+		<string name="zip_clear_btn">ZIPキューを消去する</string>
+		<string name="install_zip_count_hdr">ZIP %tw_zip_index% / %tw_zip_queue_count% のインストール</string>
+		<string name="installing_zip_xml">ZIP のインストール中: %tw_file%</string>
+		<string name="failed">失敗</string>
+		<string name="successful">成功</string>
+		<string name="install_failed">インストールに失敗しました</string>
+		<string name="install_successful">インストールしました</string>
+		<string name="wipe_cache_dalvik_btn">Cache/dalvik の消去</string>
+		<string name="reboot_system_btn">再起動</string>
+		<string name="install_sel_target">ターゲットパーティションの選択</string>
+		<string name="flash_image_select">イメージを書き込むパーティションの選択:</string>
+		<string name="target_partition">ターゲットパーティション:</string>
+		<string name="flashing_image">イメージの書き込み中...</string>
+		<string name="image_flashed">イメージを書き込みました</string>
+		<string name="wipe_cache_dalvik_confirm">Cache &amp; Dalvik を消去しますか?</string>
+		<string name="wiping_cache_dalvik">Cache &amp; Dalvik を消去中...</string>
+		<string name="wipe_cache_dalvik_complete">Cache &amp; Dalvik を消去しました</string>
+		<string name="swipe_wipe">スワイプで消去</string>
+		<string name="swipe_wipe_s">   消去</string>
+		<string name="no_os1">OS が見つかりません!</string>
+		<string name="no_osrb">本当に再起動しますか?</string>
+		<string name="no_ospo">本当に電源を切りますか?</string>
+		<string name="rebooting">再起動中...</string>
+		<string name="swipe_reboot">スワイプで再起動</string>
+		<string name="swipe_reboot_s">   再起動</string>
+		<string name="swipe_flash">スワイプで書き込みの確認</string>
+		<string name="confirm_action">アクションの確認</string>
+		<string name="back_cancel">バックボタンでキャンセルします。</string>
+		<string name="cancel_btn">キャンセル</string>
+		<string name="wipe_hdr">消去</string>
+		<string name="factory_reset_hdr">データの初期化</string>
+		<string name="factory_reset_btn">データの初期化</string>
+		<string name="factory_reset1">Data、Cache、Dalvikを消去します</string>
+		<string name="factory_reset2">(内部ストレージは除外されます)</string>
+		<string name="factory_reset3">ほとんどの場合、</string>
+		<string name="factory_reset4">これが最適です。</string>
+		<string name="factory_resetting">データを初期化する...</string>
+		<string name="advanced_wipe_hdr">高度な消去</string>
+		<string name="advanced_wipe_btn">高度な消去</string>
+		<string name="wipe_enc_confirm">Data の暗号化を解除しますか?</string>
+		<string name="formatting_data">Data の初期化中...</string>
+		<string name="swipe_format_data">スワイプで Data を初期化する</string>
+		<string name="swipe_format_data_s">   Data の初期化</string>
+		<string name="factory_reset_complete">データの初期化が完了しました</string>
+		<string name="sel_part_hdr">パーティションの選択</string>
+		<string name="wipe_sel_confirm">選択したパーティションを消去しますか?</string>
+		<string name="wiping_part">パーティションを消去中...</string>
+		<string name="wipe_complete">消去しました</string>
+		<string name="sel_part_wipe">消去するパーティションの選択:</string>
+		<string name="invalid_part_sel">パーティションの選択が間違っています</string>
+		<string name="format_data_hdr">Dataの初期化</string>
+		<string name="format_data_btn">Dataの初期化</string>
+		<string name="format_data_ptr1">Dataを初期化するとすべてのアプリ、</string>
+		<string name="format_data_ptr2">バックアップ、写真、動画</string>
+		<string name="format_data_ptr3">や暗号化が内部ストレージから消去されます。</string>
+		<string name="format_data_adopted">Adopted Storage を含める</string>
+		<string name="format_data_lcp1">Data を初期化するとすべてのアプリ、バックアップ、写真、動画</string>
+		<string name="format_data_lcp2">や暗号化が内部ストレージから消去されます。</string>
+		<string name="format_data_wtc1">Data を初期化するとすべてのアプリ、</string>
+		<string name="format_data_wtc2">バックアップやメディアが完全に消去されます。</string>
+		<string name="format_data_undo">この操作は元に戻せません。</string>
+		<string name="format_data_complete">Data を初期化しました</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">yes と入力すると続行します。バックキーでキャンセルします。</string>
+		<string name="part_opt_hdr">パーティションオプション: %tw_partition_name%</string>
+		<string name="sel_act_hdr">アクションの選択</string>
+		<string name="file_sys_opt">ファイルシステムオプション</string>
+		<string name="partition">パーティション: %tw_partition_name%</string>
+		<string name="part_mount_point">マウントポイント: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">ファイルシステム: %tw_partition_file_system%</string>
+		<string name="part_present_yes">現在利用可能: はい</string>
+		<string name="part_present_no">現在利用可能: いいえ</string>
+		<string name="part_removable_yes">取り外し可能: はい</string>
+		<string name="part_removable_no">取り外し可能: いいえ</string>
+		<string name="part_size">サイズ: %tw_partition_size%MB</string>
+		<string name="part_used">使用中: %tw_partition_used%MB</string>
+		<string name="part_free">空き: %tw_partition_free%MB</string>
+		<string name="part_backup_size">バックアップサイズ: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">ファイルシステムのリサイズ</string>
+		<string name="resize_btn_s">リサイズ</string>
+		<string name="resize_confirm">%tw_partition_name% をリサイズしますか?</string>
+		<string name="resizing">リサイズ中...</string>
+		<string name="resize_complete">リサイズしました</string>
+		<string name="swipe_resize">スワイプでリストア</string>
+		<string name="swipe_resize_s">   リサイズ</string>
+		<string name="repair_btn">ファイルシステムの修復</string>
+		<string name="repair_btn_s">修復</string>
+		<string name="repair_confirm">%tw_partition_name% を修復しますか?</string>
+		<string name="repairing">修復中...</string>
+		<string name="repair_complete">修復しました</string>
+		<string name="swipe_repair">スワイプで修復</string>
+		<string name="swipe_repair_s">   修復</string>
+		<string name="change_fs_btn">ファイルシステムの変更</string>
+		<string name="change_fs_btn_s">変更</string>
+		<string name="change_fs_for_hdr">ファイルシステムの変更: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">パーティション: %tw_partition_name% &gt; ファイルシステムの選択</string>
+		<string name="change_fs_warn1">ROM やカーネルによっては一部のファイルシステムに</string>
+		<string name="change_fs_warn2">対応していません。注意してください!</string>
+		<string name="change_fs_confirm">%tw_partition_name% を変更しますか?</string>
+		<string name="formatting">初期化中...</string>
+		<string name="format_complete">初期化しました</string>
+		<string name="swipe_change_fs">スワイプで変更</string>
+		<string name="swipe_change_s">   変更</string>
+		<string name="back_btn">戻る</string>
+		<string name="wipe_enc_btn">暗号化の消去</string>
+		<string name="swipe_factory_reset">スワイプでデータの初期化</string>
+		<string name="repair_change_btn">ファイルシステムの修復と変更</string>
+		<string name="storage_hdr">ストレージ: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">バックアップ</string>
+		<string name="backup_confirm_hdr">バックアップの確認</string>
+		<string name="encryption_tab">暗号化</string>
+		<string name="encryption">暗号化:</string>
+		<string name="name">名前:</string>
+		<string name="sel_part_backup">バックアップするパーティション:</string>
+		<string name="storage">ストレージ:</string>
+		<string name="enc_disabled">無効 - パスワード設定で有効化します</string>
+		<string name="enc_enabled">有効</string>
+		<string name="enable_backup_comp_chk">圧縮する</string>
+		<string name="skip_digest_backup_chk">バックアップ時に Digest を生成しない</string>
+		<string name="disable_backup_space_chk">空き容量のチェックを無効化する</string>
+		<string name="refresh_sizes_btn">サイズの更新</string>
+		<string name="swipe_backup">スワイプでバックアップ</string>
+		<string name="append_date_btn">日付の追加</string>
+		<string name="backup_name_exists">同じ名前のバックアップが既に存在します!</string>
+		<string name="encrypt_backup">バックアップを暗号化しますか?</string>
+		<string name="enter_pass">パスワードを入力:</string>
+		<string name="enter_pass2">パスワードを再入力:</string>
+		<string name="pass_not_match">パスワードが一致しません!</string>
+		<string name="partitions">パーティション:</string>
+		<string name="disabled">無効</string>
+		<string name="enabled">有効</string>
+		<string name="backup_name_hdr">バックアップ名の設定</string>
+		<string name="progress">進捗:</string>
+		<string name="backup_complete">バックアップしました</string>
+		<string name="restore_hdr">リストア</string>
+		<string name="sel_backup_hdr">バックアップの選択</string>
+		<string name="restore_sel_store_hdr">%tw_storage_display_name% (%tw_storage_free_size% MB) からバックアップを選択</string>
+		<string name="restore_sel_pack_fs">リストアするパッケージの選択:</string>
+		<string name="restore_enc_backup_hdr">暗号化されたバックアップ</string>
+		<string name="restore_dec_fail">パスワードが間違っているので、入力し直してください!</string>
+		<string name="del_backup_btn">バックアップの削除</string>
+		<string name="del_backup_confirm">バックアップを削除しますか?</string>
+		<string name="del_backup_confirm2">この操作は元に戻せません!</string>
+		<string name="deleting_backup">バックアップの削除中...</string>
+		<string name="backup_deleted">バックアップを削除しました</string>
+		<string name="swipe_delete">スワイプで削除</string>
+		<string name="swipe_delete_s">   削除</string>
+		<string name="restore_try_decrypt">暗号化されたバックアップ - 復号の試行中</string>
+		<string name="restore_try_decrypt_s">復号の試行中</string>
+		<string name="restore_backup_date">%tw_restore_file_date% に作成されたバックアップ</string>
+		<string name="restore_sel_part">リストアするパーティションの選択:</string>
+		<string name="restore_enable_digest_chk">バックアップファイルの Digest 検証を有効化する</string>
+		<string name="restore_complete">リストアしました</string>
+		<string name="swipe_restore">スワイプでリストア</string>
+		<string name="swipe_restore_s">   リストア</string>
+		<string name="rename_backup_hdr">バックアップの名前を変更</string>
+		<string name="rename_backup_confirm">バックアップの名前を変更しますか?</string>
+		<string name="rename_backup_confirm2">この操作は元に戻せません!</string>
+		<string name="renaming_backup">バックアップの名前を変更中...</string>
+		<string name="rename_backup_complete">バックアップの名前を変更しました</string>
+		<string name="swipe_to_rename">スワイプで名前の変更</string>
+		<string name="swipe_rename">   名前の変更</string>
+		<string name="confirm_hdr">確認</string>
+		<string name="mount_hdr">マウント</string>
+		<string name="mount_sel_part">マウントするパーティションの選択:</string>
+		<string name="mount_sys_ro_chk"> System を読み取り専用でマウントする</string>
+		<string name="mount_sys_ro_s_chk">System を RO でマウント</string>
+		<string name="decrypt_data_btn">Data の復号</string>
+		<string name="disable_mtp_btn">MTP の無効化</string>
+		<string name="enable_mtp_btn">MTP の有効化</string>
+		<string name="mount_usb_storage_btn">USB ストレージのマウント</string>
+		<string name="usb_storage_hdr">USB ストレージ</string>
+		<string name="usb_stor_mnt1">USB ストレージをマウントしました</string>
+		<string name="usb_stor_mnt2">機器を安全に取り外すよう</string>
+		<string name="usb_stor_mnt3">マウント解除時は注意してください!</string>
+		<string name="unmount_btn">マウント解除</string>
+		<string name="rb_system_btn">システム</string>
+		<string name="rb_poweroff_btn">電源を切る</string>
+		<string name="rb_recovery_btn">リカバリ</string>
+		<string name="rb_bootloader_btn">ブートローダー</string>
+		<string name="rb_download_btn">ダウンロード</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">電源を切っています...</string>
+		<string name="swipe_power_off">スワイプで電源を切る</string>
+		<string name="swipe_power_off_s">電源を切る</string>
+		<string name="sys_ro_hdr">未変更の system パーティション</string>
+		<string name="sys_ro_keep">system を読み取り専用のままにしますか?</string>
+		<string name="sys_rop1">TWRP でsystemを編集しないようにして</string>
+		<string name="sys_rop2">公式なソフトウェア更新を適用しやすくできます。</string>
+		<string name="sys_rop3">TWRP は stock ROM が TWRP を取り除くのを防げず</string>
+		<string name="sys_rop4">root化することもできません。</string>
+		<string name="sys_rop5">ZIPをインストールしたりadbで操作したりすると</string>
+		<string name="sys_rop6">system パーティションが編集される可能性があります。</string>
+		<string name="sys_rol1">公式なソフトウェア更新を適用しやすくするため、TWRP は system パーティションを編集しないようにできます。</string>
+		<string name="sys_rol2">TWRP は stock ROM が TWRP を取り除くことを防げず、root化する事もできません。</string>
+		<string name="sys_rol3">ZIPのインストールや adb での操作で system が編集される可能性があります。</string>
+		<string name="sys_ro_never_show_chk">起動時にこの画面を二度と表示しない</string>
+		<string name="sys_ro_keep_ro_btn">読み取り専用のままにする</string>
+		<string name="swipe_allow_mod">スワイプで編集を許可</string>
+		<string name="swipe_allow_mod_s">編集の許可</string>
+		<string name="settings_hdr">設定</string>
+		<string name="settings_gen_hdr">基本</string>
+		<string name="settings_gen_s_hdr">基本</string>
+		<string name="settings_gen_btn">基本</string>
+		<string name="use_rmrf_chk">初期化に rm -rf コマンドを使う</string>
+		<string name="use24clock_chk">時計を24時間表示にする</string>
+		<string name="rev_navbar_chk">ナビバーの並びを逆にする</string>
+		<string name="simact_chk">テーマのテストのため動作をシミュレートする</string>
+		<string name="simfail_chk">シミュレートに失敗しました</string>
+		<string name="ctr_navbar_rdo">ナビバーのボタンを中央寄せにする</string>
+		<string name="lft_navbar_rdo">ナビバーのボタンを左寄せにする</string>
+		<string name="rht_navbar_rdo">ナビバーのボタンを右寄せにする</string>
+		<string name="restore_defaults_btn">初期設定に戻す</string>
+		<string name="settings_tz_btn">タイムゾーン</string>
+		<string name="settings_screen_btn">画面</string>
+		<string name="settings_screen_bright_btn">画面の明るさ</string>
+		<string name="settings_vibration_btn">バイブレーション</string>
+		<string name="settings_language_btn">言語</string>
+		<string name="time_zone_hdr">タイムゾーン</string>
+		<string name="sel_tz_list">タイム ゾーンの選択:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) Samoa, Midway Island</string>
+		<string name="utcm10">(UTC -10) Hawaii</string>
+		<string name="utcm9">(UTC -9) Alaska</string>
+		<string name="utcm8">(UTC -8) Pacific Time</string>
+		<string name="utcm7">(UTC -7) Mountain Time</string>
+		<string name="utcm6">(UTC -6) Central Time</string>
+		<string name="utcm5">(UTC -5) Eastern Time</string>
+		<string name="utcm4">(UTC -4) Atlantic Time</string>
+		<string name="utcm3">(UTC -3) Brazil, Buenos Aires</string>
+		<string name="utcm2">(UTC -2) Mid-Atlantic</string>
+		<string name="utcm1">(UTC -1) Azores, Cape Verde</string>
+		<string name="utc0">(UTC  0) London, Dublin, Lisbon</string>
+		<string name="utcp1">(UTC +1) Berlin, Brussels, Paris</string>
+		<string name="utcp2">(UTC +2) Athens, Istanbul, South Africa</string>
+		<string name="utcp3">(UTC +3) Moscow, Baghdad</string>
+		<string name="utcp4">(UTC +4) Abu Dhabi, Tbilisi, Muscat</string>
+		<string name="utcp5">(UTC +5) Yekaterinburg, Islamabad</string>
+		<string name="utcp6">(UTC +6) Almaty, Dhaka, Colombo</string>
+		<string name="utcp7">(UTC +7) Bangkok, Hanoi, Jakarta</string>
+		<string name="utcp8">(UTC +8) Beijing, Singapore, Hong Kong</string>
+		<string name="utcp9">(UTC +9) 日本標準時</string>
+		<string name="utcp10">(UTC +10) Eastern Australia, Guam</string>
+		<string name="utcp11">(UTC +11) Vladivostok, Solomon Islands</string>
+		<string name="utcp12">(UTC +12) Auckland, Wellington, Fiji</string>
+		<string name="sel_tz_offset">オフセットの選択 (通常は 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">なし</string>
+		<string name="tz_offset_0">0</string>
+		<string name="tz_offset_15">15</string>
+		<string name="tz_offset_30">30</string>
+		<string name="tz_offset_45">45</string>
+		<string name="use_dst_chk">夏時間 (DST) を使用する</string>
+		<string name="curr_tz">現在のタイムゾーン: %tw_time_zone%</string>
+		<string name="curr_tz_s">現在のタイムゾーン:</string>
+		<string name="set_tz_btn">タイムゾーンの設定</string>
+		<string name="settings_screen_hdr">画面</string>
+		<string name="settings_screen_timeout_hdr">消灯までの時間</string>
+		<string name="enable_timeout_chk">画面の自動消灯を有効化する</string>
+		<string name="screen_to_slider">消灯までの時間 (秒):</string>
+		<string name="screen_to_slider_s">消灯までの時間 (秒、0=無効): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">画面の自動消灯設定は利用できません</string>
+		<string name="screen_bright_slider">明るさ: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">明るさ設定は利用できません</string>
+		<string name="vibration_hdr">バイブレーション</string>
+		<string name="button_vibration_hdr">ボタンバイブ</string>
+		<string name="kb_vibration_hdr">キーボードバイブ</string>
+		<string name="act_vibration_hdr">アクションバイブ</string>
+		<string name="button_vibration">ボタンバイブ:</string>
+		<string name="kb_vibration">キーボードバイブ:</string>
+		<string name="act_vibration">アクションバイブ:</string>
+		<string name="select_language">言語の選択:</string>
+		<string name="sel_lang_btn">言語の選択</string>
+		<string name="set_language_btn">言語の設定</string>
+		<string name="advanced_hdr">高度な機能</string>
+		<string name="copy_log_confirm">ログを SD カードにコピーしますか?</string>
+		<string name="copying_log">ログを SD カードにコピー中...</string>
+		<string name="copy_log_complete">ログをコピーしました</string>
+		<string name="fix_context_btn">コンテキストの修正</string>
+		<string name="part_sd_btn">SDカードの分割</string>
+		<string name="part_sd_s_btn">SD カード</string>
+		<string name="file_manager_btn">ファイルマネージャ</string>
+		<string name="language_hdr">言語</string>
+		<string name="terminal_btn">ターミナル</string>
+		<string name="reload_theme_btn">テーマの再読み込み</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">TWRP の組み込み</string>
+		<string name="inject_twrp_confirm">TWRP を再度組み込みますか?</string>
+		<string name="injecting_twrp">TWRP を組み込み中...</string>
+		<string name="inject_twrp_complete">TWRP を組み込みました</string>
+		<string name="swipe_to_confirm">スワイプで確認</string>
+		<string name="part_sd_hdr">SDカードの分割</string>
+		<string name="invalid_partsd_sel">リムーバブルデバイスを選択してください</string>
+		<string name="part_sd_lose">SD カード上のすべてのファイルを消去します!</string>
+		<string name="part_sd_undo">このアクションは取り消せません!</string>
+		<string name="part_sd_ext_sz">EXT サイズ:</string>
+		<string name="part_sd_swap_sz">Swap サイズ:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">ファイルシステム:</string>
+		<string name="swipe_part_sd">スワイプで分割</string>
+		<string name="swipe_part_sd_s">分割</string>
+		<string name="partitioning_sd">SD カードの分割中...</string>
+		<string name="partitioning_sd2">完了まで数分かかります。</string>
+		<string name="part_sd_complete">分割しました</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">元の Boot をリストアする</string>
+		<string name="dumlock_restore_confirm">元の Boot イメージをリストアしますか?</string>
+		<string name="dumlock_restoring">元の Boot をリストア中...</string>
+		<string name="dumlock_restore_complete">元の Boot をリストアしました</string>
+		<string name="dumlock_reflash_btn">リカバリの再書き込み</string>
+		<string name="dumlock_reflash_confirm">boot にリカバリを再書き込みしますか?</string>
+		<string name="dumlock_reflashing">boot にリカバリを書き込み中...</string>
+		<string name="dumlock_reflash_complete">リカバリを書き込みました</string>
+		<string name="dumlock_install_btn">HTC Dumlock のインストール</string>
+		<string name="dumlock_install_confirm">ROM に HTC dumlock をインストールしますか?</string>
+		<string name="dumlock_installing">HTC Dumlock のインストール中...</string>
+		<string name="dumlock_install_complete">HTC Dumlock をインストールしました</string>
+		<string name="swipe_to_unlock">スワイプでロック解除</string>
+		<string name="swipe_unlock">   ロック解除</string>
+		<string name="fm_hdr">ファイルマネージャ</string>
+		<string name="fm_sel_file">ファイルかフォルダの選択</string>
+		<string name="fm_type_folder">フォルダ</string>
+		<string name="fm_type_file">ファイル</string>
+		<string name="fm_choose_act">操作の選択</string>
+		<string name="fm_selected">選択中の%tw_fm_type%:</string>
+		<string name="fm_copy_btn">コピー</string>
+		<string name="fm_copy_file_btn">ファイルのコピー</string>
+		<string name="fm_copy_folder_btn">フォルダのコピー</string>
+		<string name="fm_copying">コピー中</string>
+		<string name="fm_move_btn">移動</string>
+		<string name="fm_moving">移動中</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">削除</string>
+		<string name="fm_deleting">削除中</string>
+		<string name="fm_rename_btn">名前の変更</string>
+		<string name="fm_rename_file_btn">ファイル名の変更</string>
+		<string name="fm_rename_folder_btn">フォルダ名の変更</string>
+		<string name="fm_renaming">名前の変更中</string>
+		<string name="fm_sel_dest">宛先のフォルダの選択</string>
+		<string name="fm_sel_curr_folder">現在のフォルダの選択</string>
+		<string name="fm_rename_hdr">名前の変更</string>
+		<string name="fm_set_perms_hdr">権限の設定</string>
+		<string name="fm_perms">権限:</string>
+		<string name="fm_complete">ファイル操作を完了しました</string>
+		<string name="decrypt_data_hdr">Data の復号</string>
+		<string name="decrypt_data_enter_pass">パスワードを入力してください。</string>
+		<string name="decrypt_data_failed">パスワード認証失敗、入力し直してください!</string>
+		<string name="decrypt_data_failed_pattern">パターン認証失敗、入力し直してください!</string>
+		<string name="decrypt_data_enter_pattern">パターンを入力してください。</string>
+		<string name="decrypt_data_trying">復号の試行中</string>
+		<string name="term_hdr">ターミナルコマンド</string>
+		<string name="term_s_hdr">ターミナル</string>
+		<string name="term_kill_btn">キル</string>
+		<string name="term_sel_folder_hdr">開始時のフォルダを参照</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">Dalvik Cache の消去</string>
+		<string name="sideload_wipe_cache_chk">Cache の消去</string>
+		<string name="swipe_to_sideload">スワイプで Sideload の開始</string>
+		<string name="swipe_sideload">   開始</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">使用方法: adb sideload filename.zip</string>
+		<string name="sideload_complete">ADB Sideload が完了しました</string>
+		<string name="fix_contexts_hdr">コンテキストの修正</string>
+		<string name="fix_contexts_note1">注意:コンテキストの修正は滅多に必要ありません。</string>
+		<string name="fix_contexts_note2">SELinux コンテキストの修正で</string>
+		<string name="fix_contexts_note3">機器が起動しなくなる場合があります。</string>
+		<string name="swipe_to_fix_contexts">スワイプでコンテキストの修正</string>
+		<string name="swipe_fix_contexts">   コンテキストの修正</string>
+		<string name="fixing_contexts">コンテキストの修正中...</string>
+		<string name="fix_contexts_complete">コンテキストを修正しました</string>
+		<string name="reboot_hdr">再起動</string>
+		<string name="install_cancel">インストールしない</string>
+		<string name="sel_storage_list">ストレージの選択</string>
+		<string name="ok_btn">OK</string>
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">カーネルが SELinux コンテキストの読み取りに対応していません。</string>
+		<string name="full_selinux">SELinux に完全に対応しています。</string>
+		<string name="no_selinux">SELinux に対応していません。(libselinux なし)</string>
+		<string name="mtp_enabled">MTP 有効</string>
+		<string name="mtp_crash">MTP がクラッシュしました。起動時に開始しません。</string>
+		<string name="decrypt_success">デフォルトのパスワードで復号しました。</string>
+		<string name="unable_to_decrypt">デフォルトのパスワードで復号できません。data の初期化が必要です。</string>
+		<string name="generating_digest1">Digest の生成中</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2"> * Digest の生成中...</string>
+		<string name="digest_created"> * Digest を生成しました。</string>
+		<string name="digest_error"> * Digest エラー!</string>
+		<string name="digest_compute_error"> * Digestの計算エラーが起きました。</string>
+		<string name="current_date">(現在の日付)</string>
+		<string name="auto_generate">(自動生成)</string>
+		<string name="unable_to_locate_partition">バックアップの計算で \'{1}\' パーティションが見つかりません。</string>
+		<string name="no_partition_selected">バックアップするパーティションが選択されていません。</string>
+		<string name="total_partitions_backup"> * バックアップするパーティションの総数: {1}</string>
+		<string name="total_backup_size"> * すべてのデータの合計サイズ: {1}MB</string>
+		<string name="available_space"> * 空き容量: {1}MB</string>
+		<string name="unable_locate_storage">ストレージデバイスを見つけられません。</string>
+		<string name="no_space">十分な空き容量がありません。</string>
+		<string name="backup_started">[バックアップ開始]</string>
+		<string name="backup_folder"> * バックアップフォルダ: {1}</string>
+		<string name="fail_backup_folder">バックアップフォルダの作成に失敗しました。</string>
+		<string name="avg_backup_fs">ファイルシステムの平均バックアップ速度: {1} MB/秒</string>
+		<string name="avg_backup_img">イメージドライブの平均バックアップ速度: {1} MB/秒</string>
+		<string name="total_backed_size">[合計 {1} MB バックアップ]</string>
+		<string name="backup_completed">[{1} 秒でバックアップしました]</string>
+		<string name="restore_started">[リストア開始]</string>
+		<string name="restore_folder">リストアフォルダ: \'{1}\'</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} 完了 ({2} 秒)]</string>
+		<string name="verifying_digest">Digest の検証中</string>
+		<string name="skip_digest">ユーザー設定に基づき Digest の検証をスキップします。</string>
+		<string name="calc_restore">リストアの詳細を計算中...</string>
+		<string name="restore_read_only">{1} をリストアできません -- 読み取り専用でマウントされています。</string>
+		<string name="restore_unable_locate">リストアする \'{1}\' パーティションが見つかりません。</string>
+		<string name="no_part_restore">リストアするパーティションが選択されていません。</string>
+		<string name="restore_part_count">{1} パーティションをリストア中...</string>
+		<string name="total_restore_size">合計リストアサイズ: {1}MB</string>
+		<string name="updating_system_details">system の詳細を更新中</string>
+		<string name="restore_completed">[{1} 秒でリストアしました]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">開けません: \'{1}\' ({2})</string>
+		<string name="unable_locate_part_backup_name">バックアップ名からパーティションを見つけられません: \'{1}\'</string>
+		<string name="unable_find_part_path">パス \'{1}\' のパーティションを見つけられません</string>
+		<string name="update_part_details">パーティションの詳細を更新中...</string>
+		<string name="update_part_details_done">...完了</string>
+		<string name="wiping_dalvik">Dalvik Cache ディレクトリを消去中...</string>
+		<string name="cleaned">掃除しました: {1}...</string>
+		<string name="dalvik_done">-- Dalvik Cache ディレクトリを消去しました!</string>
+		<string name="no_andsec">android secure パーティションが見つかりません。</string>
+		<string name="unable_to_locate">{1} が見つかりません。</string>
+		<string name="wiping_datamedia">内部ストレージを消去中 -- /data/media...</string>
+		<string name="unable_to_mount">{1} をマウントできません</string>
+		<string name="unable_to_mount_internal">内部ストレージをマウントできません</string>
+		<string name="unable_to_mount_storage">ストレージをマウントできません</string>
+		<string name="fail_decrypt">Data の復号に失敗しました。</string>
+		<string name="no_crypto_support">このビルドでは暗号化に非対応です。</string>
+		<string name="decrypt_success_dev">Data を復号しました。新しいブロックデバイス: '{1}'</string>
+		<string name="done">完了しました。</string>
+		<string name="start_partition_sd">SD カードの分割中...</string>
+		<string name="partition_sd_locate">パーティションが見つかりません。</string>
+		<string name="ext_swap_size">EXT + Swap のサイズが SD カードのサイズを超えています。</string>
+		<string name="remove_part_table">パーティションテーブルを削除中...</string>
+		<string name="unable_rm_part">パーティションテーブルを削除きません。</string>
+		<string name="create_part">{1} パーティションを作成中...</string>
+		<string name="unable_to_create_part">{1} パーティションを作成できません。</string>
+		<string name="format_sdext_as">{1} として sd-ext を初期化しています...</string>
+		<string name="part_complete">分割しました。</string>
+		<string name="unable_to_open">\'{1}\' を開けません。</string>
+		<string name="mtp_already_enabled">MTP はすでに有効です。</string>
+		<string name="mtp_fail">MTP を有効化できませんでした。</string>
+		<string name="no_mtp">MTP に対応していません。</string>
+		<string name="image_flash_start">[イメージ書き込み開始]</string>
+		<string name="img_to_flash">書き込むイメージ: \'{1}\'</string>
+		<string name="flash_unable_locate">書き込み先の \'{1}\' パーティションが見つかりません。</string>
+		<string name="no_part_flash">書き込むパーティションが選択されていません。</string>
+		<string name="too_many_flash">書き込み先として選択されたパーティションが多すぎます。</string>
+		<string name="invalid_flash">パーティションの指定が間違っています。</string>
+		<string name="flash_done">[イメージ書き込み完了]</string>
+		<string name="wiping">{1} の消去中</string>
+		<string name="repair_not_exist">{1} は存在しません! 修復できません!</string>
+		<string name="repairing_using">{2} を使用して {1} を修復中...</string>
+		<string name="unable_repair">{1} を修復できません。</string>
+		<string name="mount_data_footer">/data をマウントできず、暗号フッターを見つけられませんでした。</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">\'{1}\' フォルダを作成できません。 ({2})</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">\'{1}\' をマウントできませんでした ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">'{1}' をアンマウントできませんでした ({2})</string>
+		<string name="cannot_resize">{1} をリサイズできません。</string>
+		<string name="repair_resize">リサイズの前に {1} を修復しています。</string>
+		<string name="unable_resize">{1} をリサイズできません。</string>
+		<string name="no_digest_found">\'{1}\' の Digest ファイルが見つかりません。リストアするには Digest の検証を無効化してください。</string>
+		<string name="digest_fail_match">\'{1}\' で Digest が一致しません。</string>
+		<string name="digest_matched">Digest matched for '{1}'.</string>
+		<string name="fail_decrypt_tar">tar ファイル '{1}' を復号できませんでした</string>
+		<string name="format_data_msg">/data を再度使用するにはリカバリを再起動してください。</string>
+		<string name="format_data_err">暗号化を解除するための初期化ができません。</string>
+		<string name="formatting_using">{2} を使用して {1} をフォーマット中...</string>
+		<string name="unable_to_wipe">{1} を消去できません。</string>
+		<string name="cannot_wipe">パーティション {1} は消去できません。</string>
+		<string name="remove_all">\'{1}\' のすべてのファイルを削除中</string>
+		<string name="wiping_data">/data/media 以外のデータを消去中...</string>
+		<string name="backing_up">{1} をバックアップ中...</string>
+		<string name="backing">バックアップ中</string>
+		<string name="backup_size">\'{1}\' のバックアップファイルサイズは0バイトです。</string>
+		<string name="datamedia_fs_restore">警告: この /data バックアップは {1} ファイルシステムでされています! {1} に戻さないとバックアップは起動できません。</string>
+		<string name="restoring">{1} をリストア中...</string>
+		<string name="restoring_hdr">リストア中</string>
+		<string name="recreate_folder_err">{1} フォルダを再作成できません。</string>
+		<string name="img_size_err">イメージのサイズがターゲットデバイスのサイズを超えています。</string>
+		<string name="flashing">{1} を書き込み中...</string>
+		<string name="backup_folder_set">バックアップフォルダを '{1}' に設定しました</string>
+		<string name="locate_backup_err">バックアップ \'{1}\' が見つかりません</string>
+		<string name="set_restore_opt">リストアオプションの設定中: \'{1}\':</string>
+		<string name="digest_check_skip">Digest チェックのスキップが有効です</string>
+		<string name="ors_encrypt_restore_err">OpenRecoveryScript を使用して暗号化されたバックアップをリストアできません。</string>
+		<string name="mounting">マウント中</string>
+		<string name="unmounting">マウント解除中</string>
+		<string name="mounted">\'{1}\' をマウントしました</string>
+		<string name="unmounted">\'{1}\' のマウントを解除しました</string>
+		<string name="setting">\'{1}\' を \'{2}\' に設定中</string>
+		<string name="setting_empty">\'{1}\' を空に設定中</string>
+		<string name="making_dir1">ディレクトリを作成中</string>
+		<string name="making_dir2">ディレクトリを作成中: \'{1}\'</string>
+		<string name="running_command">コマンドの実行中</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">ADB sideload を開始中...</string>
+		<string name="need_new_adb">Sideload 機能を使うには、adb 1.0.32 以降が必要です。</string>
+		<string name="no_pwd">パスワードが指定されていません。</string>
+		<string name="done_ors">スクリプトファイルの処理が完了しました</string>
+		<string name="injecttwrp">boot イメージに TWRP を組み込み中...</string>
+		<string name="zip_err">ZIPファイル \'{1}\' のインストール中にエラーが発生しました</string>
+		<string name="installing_zip">ZIP ファイル \'{1}\' をインストール中</string>
+		<string name="select_backup_opt">バックアップオプションの設定中:</string>
+		<string name="compression_on">圧縮は有効です</string>
+		<string name="digest_off">Digest 生成は無効です</string>
+		<string name="backup_fail">バックアップに失敗しました</string>
+		<string name="backup_clean">バックアップに失敗しました。バックアップフォルダを掃除中です。</string>
+		<string name="running_recovery_commands">リカバリコマンドの実行中</string>
+		<string name="recovery_commands_complete">リカバリコマンドを実行しました</string>
+		<string name="running_ors">OpenRecoveryScript の実行中</string>
+		<string name="ors_complete">OpenRecoveryScript を実行しました</string>
+		<string name="no_updater_binary">ZIP ファイルの中で \'{1}\' が見つかりませんでした。</string>
+		<string name="check_for_digest">Digest ファイルをチェック中...</string>
+		<string name="fail_sysmap">ファイル \'{1}\' のマップに失敗しました</string>
+		<string name="verify_zip_sig">ZIP の署名を検証中...</string>
+		<string name="verify_zip_fail">ZIP の署名の検証に失敗しました!</string>
+		<string name="verify_zip_done">ZIP の署名の検証に成功しました。</string>
+		<string name="zip_corrupt">ZIP ファイルが破損しています!</string>
+		<string name="no_digest">Digest チェックをスキップ中: Digest ファイルが見つかりません</string>
+		<string name="digest_fail">Digest が一致しません</string>
+		<string name="digest_match">Digest が一致しました</string>
+		<string name="pid_signal">{1} プロセスはこのシグナルで終了しました: {2}</string>
+		<string name="pid_error">{1} プロセスはこのエラーで終了しました: {2}</string>
+		<string name="install_dumlock">HTC Dumlock を system にインストール中...</string>
+		<string name="dumlock_restore">元の Boot をリストア中...</string>
+		<string name="dumlock_reflash">boot にリカバリを書き込み中...</string>
+		<string name="run_script">{1} スクリプトを実行しています...</string>
+		<string name="rename_stock">stock ROM が TWRP を置き換えるのを防ぐため、 /system の stock リカバリファイルの名前を変更しました。</string>
+		<string name="split_backup">バックアップファイルを複数の書庫に分割中...</string>
+		<string name="backup_error">バックアップの作成中にエラーが発生しました。</string>
+		<string name="restore_error">リストア処理中にエラーが発生しました。</string>
+		<string name="split_thread">スレッド ID {1} を書庫 {2} に分割中</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">%llu / %llu ファイル、%i%%</string>
+		<string name="size_progress">%lluMB / %lluMB、%i%%</string>
+		<string name="decrypt_cmd">コマンドラインで data パーティションの復号を試行中です。</string>
+		<string name="base_pkg_err">基本パッケージの読み込みに失敗しました。</string>
+		<string name="simulating">アクションのシミュレート中...</string>
+		<string name="backup_cancel">バックアップをキャンセルしました</string>
+		<string name="config_twrp">TWRP を設定中...</string>
+		<string name="config_twrp_err">このカーネルで TWRP を設定できません。</string>
+		<string name="copy_log">リカバリログを {1} にコピーしました。</string>
+		<string name="max_queue">ZIP キューの上限に達しました!</string>
+		<string name="min_queue">ZIP キューの下限に達しました!</string>
+		<string name="screenshot_saved">スクリーンショットは {1} に保存されました。</string>
+		<string name="screenshot_err">スクリーンショットの撮影に失敗しました!</string>
+		<string name="zip_wipe_cache">一つ以上の ZIP が cache の消去を要求しました -- Cache を消去中です。</string>
+		<string name="and_sec_wipe_err">android secure を消去できません</string>
+		<string name="dalvik_wipe_err">Dalvik の消去に失敗しました</string>
+		<string name="auto_gen">(自動生成)</string>
+		<string name="curr_date">(現在の日付)</string>
+		<string name="backup_name_len">バックアップ名が長すぎます。</string>
+		<string name="backup_name_invalid">バックアップ名 \'{1}\' に使用できない文字が含まれています: \'{1}\'</string>
+		<string name="no_real_sdcard">この機器には実際の SD カードがありません! 中止します!</string>
+		<string name="cancel_sideload">ADB sideload を中止中...</string>
+		<string name="change_fs_err">ファイルシステムの変更中にエラーが発生しました。</string>
+		<string name="theme_ver_err">カスタムテーマのバージョンが TWRP のバージョンと一致しません。標準のテーマを使用します。</string>
+		<string name="up_a_level">(上の階層へ)</string>
+		<string name="install_reboot">5秒後に再起動します</string>
+	</resources>
+</language>
diff --git a/gui/theme/extra-languages/languages/zh_CN.xml b/gui/theme/extra-languages/languages/zh_CN.xml
new file mode 100644
index 0000000..a998be3
--- /dev/null
+++ b/gui/theme/extra-languages/languages/zh_CN.xml
@@ -0,0 +1,791 @@
+<?xml version="1.0"?>
+
+<language>
+	<display>Chinese (Simplified)</display>
+
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="DroidSansFallback.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="DroidSansFallback.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="DroidSansFallback.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansFallback.ttf" scale="100" />
+
+		<!-- Partition display names -->
+		<string name="system">System</string>
+		<string name="system_image">System 镜像</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Vendor 镜像</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="data_backup">Data(不包含存储)</string>
+		<string name="sdcard">SDCard</string>
+		<string name="internal">内置存储</string>
+		<string name="microsd">SD 卡</string>
+		<string name="usbotg">外置 U 盘</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Adopted Data</string>
+		<string name="adopted_storage">Adopted 存储</string>
+		<string name="autostorage">存储</string>
+
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">CPU温度: %tw_cpu_temp% &#xB0;C</string>
+		<string name="battery_pct">电量: %tw_battery%</string>
+		<string name="sort_by_name">按名称排序</string>
+		<string name="sort_by_date">按日期排序</string>
+		<string name="sort_by_size">按大小排序</string>
+		<string name="sort_by_name_only">名称</string>
+		<string name="sort_by_date_only">日期</string>
+		<string name="sort_by_size_only">大小</string>
+		<string name="tab_general">常规</string>
+		<string name="tab_options">选项</string>
+		<string name="tab_backup">备份</string>
+		<string name="tab_time_zone">时区</string>
+		<string name="tab_screen">屏幕</string>
+		<string name="tab_vibration">振动</string>
+		<string name="tab_language">语言</string>
+
+		<string name="install_btn">安装</string>
+		<string name="wipe_btn">清除</string>
+		<string name="backup_btn">备份</string>
+		<string name="restore_btn">恢复</string>
+		<string name="mount_btn">挂载</string>
+		<string name="settings_btn">设置</string>
+		<string name="advanced_btn">高级</string>
+		<string name="reboot_btn">重启</string>
+		<string name="files_btn">文件</string>
+		<string name="copy_log_btn">导出日志</string>
+		<string name="select_type_hdr">选择类型</string>
+		<string name="install_zip_hdr">安装 Zip 刷机包</string>
+		<string name="install_zip_btn">安装刷机包</string>
+		<string name="install_image_hdr">刷入 Image 镜像</string>
+		<string name="install_image_btn">刷入镜像</string>
+		<string name="install_select_file_hdr">选择文件</string>
+		<string name="file_selector_folders_hdr">目录</string>
+		<string name="select_file_from_storage">从 %tw_storage_display_name% (%tw_storage_free_size% MB) 中选择文件</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">安装</string>
+		<string name="select_storage_hdr">存储位置</string>
+		<string name="select_storage_btn">存储位置</string>
+		<string name="queue_hdr">队列</string>
+		<string name="zip_queue_count">已选 %tw_zip_queue_count% 个文件(最多 10 个)</string>
+		<string name="zip_queue_count_s">已选 %tw_zip_queue_count% 个,最多 10 个:</string>
+		<string name="zip_warn1">此操作可能会刷入不兼容的固件,</string>
+		<string name="zip_warn2">致使您的设备变砖。</string>
+		<string name="zip_back_cancel">按返回按钮取消添加此刷机包。</string>
+		<string name="zip_back_clear">按返回按钮清除刷机包刷入队列。</string>
+		<string name="folder">目录:</string>
+		<string name="file">文件:</string>
+		<string name="zip_sig_chk">Zip 刷机包签名校验</string>
+		<string name="inject_twrp_chk">安装完成后注入 TWRP</string>
+		<string name="install_reboot_chk">安装完成后重启</string>
+		<string name="options_hdr">选项</string>
+		<string name="confirm_flash_hdr">确认刷入</string>
+		<string name="zip_queue">队列:</string>
+		<string name="options">选项:</string>
+		<string name="swipe_confirm">   确认</string>
+		<string name="zip_add_btn">添加更多刷机包</string>
+		<string name="zip_clear_btn">清除刷机队列</string>
+		<string name="install_zip_count_hdr">正在刷入第 %tw_zip_index% 个,共 %tw_zip_queue_count% 个</string>
+		<string name="installing_zip_xml">正在安装: %tw_file%</string>
+		<string name="failed">失败</string>
+		<string name="successful">成功</string>
+		<string name="install_failed">安装失败</string>
+		<string name="install_successful">安装成功</string>
+		<string name="wipe_cache_dalvik_btn">清除 Cache/Dalvik</string>
+		<string name="wipe_dalvik_btn">清除 Dalvik</string>
+		<string name="reboot_system_btn">重启系统</string>
+		<string name="install_sel_target">选择目标分区</string>
+		<string name="flash_image_select">选择要刷入镜像的分区:</string>
+		<string name="target_partition">目标分区:</string>
+		<string name="flashing_image">正在刷入镜像…</string>
+		<string name="image_flashed">镜像已刷入。</string>
+		<string name="wipe_cache_dalvik_confirm">是否清除 Cache 和 Dalvik?</string>
+		<string name="wipe_dalvik_confirm">是否清除 Dalvik?</string>
+		<string name="wiping_cache_dalvik">正在清除 Cache 和 Dalvik…</string>
+		<string name="wipe_cache_dalvik_complete">Cache 和 Dalvik 清除完成</string>
+		<string name="wipe_dalvik_complete">Dalvik 清除完成</string>
+		<string name="swipe_wipe">滑动滑块确认清除</string>
+		<string name="swipe_wipe_s">   清除</string>
+		<string name="no_os1">没有安装任何系统!</string>
+		<string name="no_osrb">您确定要重启?</string>
+		<string name="no_ospo">您确定要关机?</string>
+		<string name="rebooting">正在重启…</string>
+		<string name="swipe_reboot">滑动滑块确认重启</string>
+		<string name="swipe_reboot_s">   重启</string>
+		<string name="reboot_install_app_hdr">安装 TWRP 应用?</string>
+		<string name="reboot_install_app1">您希望安装官方 TWRP 应用吗?</string>
+		<string name="reboot_install_app2">该应用可帮您检查 TWRP 的最新版本。</string>
+		<string name="reboot_install_app_prompt_install">如果 TWRP 应用未安装时提示我</string>
+		<string name="reboot_install_app_system">安装为系统应用</string>
+		<string name="reboot_installing_app">正在安装应用…</string>
+		<string name="swipe_to_install_app">滑动滑块确认安装</string>
+		<string name="uninstall_twrp_system_app">卸载 TWRP 应用</string>
+		<string name="uninstall_twrp_system_app_confirm">确认卸载 TWRP 应用?</string>
+		<string name="uninstalling_twrp_system_app">正在卸载 TWRP 应用…</string>
+		<string name="uninstall_twrp_system_app_complete">卸载 TWRP 应用完成</string>
+		<string name="swipe_flash">滑动滑块确认刷入</string>
+		<string name="confirm_action">确认操作</string>
+		<string name="back_cancel">按返回按钮取消。</string>
+		<string name="cancel_btn">取消</string>
+		<string name="wipe_hdr">清除</string>
+		<string name="factory_reset_hdr">恢复出厂设置(双清)</string>
+		<string name="factory_reset_btn">恢复出厂</string>
+		<string name="factory_reset1">这将会清空 Data、Cache 分区和 Dalvik 缓存</string>
+		<string name="factory_reset2">(不会清除内置存储/sdcard)</string>
+		<string name="factory_reset3">在大多数情况下,您只需要</string>
+		<string name="factory_reset4">执行此操作即可。</string>
+		<string name="factory_reset5">(不包括用户/锁屏)</string>
+		<string name="factory_resetting">正在恢复出厂设置…</string>
+		<string name="advanced_wipe_hdr">高级清除选项</string>
+		<string name="advanced_wipe_btn">高级清除</string>
+		<string name="wipe_enc_confirm">是否清除 Data 分区加密?</string>
+		<string name="formatting_data">正在格式化 Data 分区…</string>
+		<string name="swipe_format_data">滑动滑块确认格式化</string>
+		<string name="swipe_format_data_s">   格式化 Data 分区</string>
+		<string name="factory_reset_complete">恢复出厂完成</string>
+		<string name="sel_part_hdr">选择分区</string>
+		<string name="wipe_sel_confirm">是否清除所选的分区?</string>
+		<string name="wiping_part">正在清除分区…</string>
+		<string name="wipe_complete">清除完成</string>
+		<string name="sel_part_wipe">选择要清除的分区:</string>
+		<string name="invalid_part_sel">无效的分区</string>
+		<string name="format_data_hdr">格式化 Data 分区</string>
+		<string name="format_data_btn">格式化 Data 分区</string>
+		<string name="format_data_ptr1">格式化 Data 分区将会清空您安装的所有</string>
+		<string name="format_data_ptr2">软件、备份、图片、媒体,</string>
+		<string name="format_data_ptr3">以及对内置存储所做的加密等。</string>
+		<string name="format_data_adopted">也包括 Adopted 存储</string>
+		<string name="format_data_lcp1">格式化 Data 分区将会清空您安装的所有软件、备份、图片、媒体,</string>
+		<string name="format_data_lcp2">以及对内置存储所做的加密等。</string>
+		<string name="format_data_wtc1">格式化 Data 分区会清除您的所有软件</string>
+		<string name="format_data_wtc2">备份和媒体数据,无法撤销。</string>
+		<string name="format_data_undo">执行此操作后将无法恢复。</string>
+		<string name="format_data_complete">格式化 Data 分区完成</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">输入 yes 继续,按返回按钮取消。</string>
+		<string name="part_opt_hdr">分区名: %tw_partition_name%</string>
+		<string name="sel_act_hdr">选择操作</string>
+		<string name="file_sys_opt">文件系统选项</string>
+		<string name="partition">分区: %tw_partition_name%</string>
+		<string name="part_mount_point">挂载位置: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">文件系统: %tw_partition_file_system%</string>
+		<string name="part_present_yes">当前:是</string>
+		<string name="part_present_no">当前:否</string>
+		<string name="part_removable_yes">可移除:是</string>
+		<string name="part_removable_no">可移除:否</string>
+		<string name="part_size">大小: %tw_partition_size%MB</string>
+		<string name="part_used">已用: %tw_partition_used%MB</string>
+		<string name="part_free">空闲: %tw_partition_free%MB</string>
+		<string name="part_backup_size">备份大小: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">调整文件系统</string>
+		<string name="resize_btn_s">调整</string>
+		<string name="resize_confirm">是否调整 %tw_partition_name%?</string>
+		<string name="resizing">正在调整…</string>
+		<string name="resize_complete">调整完成</string>
+		<string name="swipe_resize">滑动滑块确认调整</string>
+		<string name="swipe_resize_s">   调整</string>
+		<string name="repair_btn">修复文件系统</string>
+		<string name="repair_btn_s">修复</string>
+		<string name="repair_confirm">是否修复 %tw_partition_name%?</string>
+		<string name="repairing">正在修复…</string>
+		<string name="repair_complete">修复完成</string>
+		<string name="swipe_repair">滑动滑块确认修复</string>
+		<string name="swipe_repair_s">   修复</string>
+		<string name="change_fs_btn">更改文件系统</string>
+		<string name="change_fs_btn_s">更改</string>
+		<string name="change_fs_for_hdr">要更改的分区: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">分区: %tw_partition_name%&gt;选择文件系统</string>
+		<string name="change_fs_warn1">一些固件或内核可能不支持</string>
+		<string name="change_fs_warn2">某些文件系统,请谨慎!</string>
+		<string name="change_fs_confirm">是否更改 %tw_partition_name% 为:</string>
+		<string name="formatting">正在格式化…</string>
+		<string name="format_complete">格式化完成</string>
+		<string name="swipe_change_fs">滑动滑块确认更改</string>
+		<string name="swipe_change_s">   更改</string>
+		<string name="back_btn">返回</string>
+		<string name="wipe_enc_btn">清除加密</string>
+		<string name="swipe_factory_reset">滑动滑块确认恢复出厂</string>
+		<string name="repair_change_btn">修复或更改文件系统</string>
+		<string name="storage_hdr">存储位置: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">备份</string>
+		<string name="backup_confirm_hdr">确认备份</string>
+		<string name="encryption_tab">加密</string>
+		<string name="encryption">加密:</string>
+		<string name="name">名称:</string>
+		<string name="sel_part_backup">选择要备份的分区:</string>
+		<string name="storage">储存位置:</string>
+		<string name="enc_disabled">已禁用 - 设置密码以启用</string>
+		<string name="enc_enabled">已启用</string>
+		<string name="enable_backup_comp_chk">启用压缩</string>
+		<string name="skip_digest_backup_chk" version="2">当备份时跳过生成 Digest</string>
+		<string name="disable_backup_space_chk" version="2">在备份之前禁用剩余空间检查</string>
+		<string name="skip_digest_zip_chk">安装 ZIP 之前跳过 Digest 校验</string>
+		<string name="current_boot_slot">当前槽位: %tw_active_slot%</string>
+		<string name="boot_slot_a">槽位 A</string>
+		<string name="boot_slot_b">槽位 B</string>
+		<string name="changing_boot_slot">更改启动槽位</string>
+		<string name="changing_boot_slot_complete">更改启动槽位完成</string>
+		<string name="refresh_sizes_btn">刷新大小</string>
+		<string name="swipe_backup">滑动滑块确认备份</string>
+		<string name="append_date_btn">附加日期</string>
+		<string name="backup_name_exists">使用该名称的备份已经存在!</string>
+		<string name="encrypt_backup">是否加密备份?</string>
+		<string name="enter_pass">请输入密码:</string>
+		<string name="enter_pass2">请再输入一遍:</string>
+		<string name="pass_not_match">密码不匹配!</string>
+		<string name="partitions">分区:</string>
+		<string name="disabled">已禁用</string>
+		<string name="enabled">已启用</string>
+		<string name="backup_name_hdr">设置备份名称</string>
+		<string name="progress">进度:</string>
+		<string name="backup_complete">备份完成</string>
+		<string name="restore_hdr">恢复</string>
+		<string name="sel_backup_hdr">选择备份</string>
+		<string name="restore_sel_store_hdr">从 %tw_storage_display_name% (%tw_storage_free_size% MB) 中选择备份</string>
+		<string name="restore_sel_pack_fs">选择要恢复的备份包:</string>
+		<string name="restore_enc_backup_hdr">备份已被加密</string>
+		<string name="restore_dec_fail">密码错误,请重试</string>
+		<string name="del_backup_btn">删除备份</string>
+		<string name="del_backup_confirm">是否删除备份:</string>
+		<string name="del_backup_confirm2">此操作无法恢复!</string>
+		<string name="deleting_backup">正在删除备份…</string>
+		<string name="backup_deleted">备份删除完成</string>
+		<string name="swipe_delete">滑动滑块确认删除</string>
+		<string name="swipe_delete_s">   删除</string>
+		<string name="restore_try_decrypt">加密的备份 - 尝试解密</string>
+		<string name="restore_try_decrypt_s">正在尝试解密</string>
+		<string name="restore_backup_date">备份于 %tw_restore_file_date%</string>
+		<string name="restore_sel_part">选择要恢复的分区:</string>
+		<string name="restore_enable_digest_chk" version="2">启用 Digest 校验备份文件</string>
+		<string name="restore_complete">恢复完成</string>
+		<string name="swipe_restore">滑动滑块确认恢复</string>
+		<string name="swipe_restore_s">   恢复</string>
+		<string name="rename_backup_hdr">重命名备份</string>
+		<string name="rename_backup_confirm">是否重命名备份文件?</string>
+		<string name="rename_backup_confirm2">此操作无法恢复!</string>
+		<string name="renaming_backup">正在重命名…</string>
+		<string name="rename_backup_complete">重命名完成</string>
+		<string name="swipe_to_rename">滑动滑块确认重命名</string>
+		<string name="swipe_rename">   重命名</string>
+		<string name="confirm_hdr">确认</string>
+		<string name="mount_hdr">挂载</string>
+		<string name="mount_sel_part">选择要挂载的分区:</string>
+		<string name="mount_sys_ro_chk">挂载 System 分区为只读</string>
+		<string name="mount_sys_ro_s_chk">挂载 System 为只读</string>
+		<string name="decrypt_data_btn">解密 Data 分区</string>
+		<string name="disable_mtp_btn">停用 MTP 模式</string>
+		<string name="enable_mtp_btn">启用 MTP 模式</string>
+		<string name="mount_usb_storage_btn">挂载 USB 存储</string>
+		<string name="usb_storage_hdr">USB 存储</string>
+		<string name="usb_stor_mnt1">已挂载 USB 存储</string>
+		<string name="usb_stor_mnt2">请确认解除挂载前先从</string>
+		<string name="usb_stor_mnt3">电脑上安全地移除设备!</string>
+		<string name="unmount_btn">卸载</string>
+		<string name="rb_system_btn">系统</string>
+		<string name="rb_poweroff_btn">关机</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Download</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">正在关机…</string>
+		<string name="swipe_power_off">滑动滑块确认关机</string>
+		<string name="swipe_power_off_s">关机</string>
+		<string name="sys_ro_hdr">未修改的 System 分区</string>
+		<string name="sys_ro_keep">是否保持系统分区为只读?</string>
+		<string name="sys_rop1">TWRP 可以保持系统分区未修改状态</string>
+		<string name="sys_rop2">使您更容易获得官方的更新。</string>
+		<string name="sys_rop3">TWRP 将无法禁止原厂固件可能</string>
+		<string name="sys_rop4">替换 TWRP 并且不能 Root 您的设备。</string>
+		<string name="sys_rop5">安装刷机包或者通过 adb 操作依然可以</string>
+		<string name="sys_rop6">修改系统分区。</string>
+		<string name="sys_rol1">TWRP 可以保持系统分区为未修改状态,使您更容易获得官方的更新。</string>
+		<string name="sys_rol2">TWRP 将无法禁止原厂固件替换 TWRP,并且不能 Root 您的设备。</string>
+		<string name="sys_rol3">安装刷机包或者通过 adb 操作依然可以修改系统分区。</string>
+		<string name="sys_ro_never_show_chk">下次启动时不再显示此界面</string>
+		<string name="sys_ro_keep_ro_btn">保持只读</string>
+		<string name="swipe_allow_mod">滑动滑块允许修改</string>
+		<string name="swipe_allow_mod_s">允许修改</string>
+		<string name="settings_hdr">设置</string>
+		<string name="settings_gen_hdr">常规</string>
+		<string name="settings_gen_s_hdr">常规</string>
+		<string name="settings_gen_btn">常规</string>
+		<string name="use_rmrf_chk">使用 rm -rf 命令代替格式化</string>
+		<string name="auto_reflashtwrp_chk">刷入 ROM 后自动重装 TWRP</string>
+		<string name="use24clock_chk">使用 24 小时制</string>
+		<string name="rev_navbar_chk">反转底部按钮布局</string>
+		<string name="simact_chk">为测试主题使用模拟操作</string>
+		<string name="simfail_chk">模拟失败操作</string>
+		<string name="ctr_navbar_rdo">导航栏按钮居中</string>
+		<string name="lft_navbar_rdo">导航栏按钮左对齐</string>
+		<string name="rht_navbar_rdo">导航栏按钮右对齐</string>
+		<string name="restore_defaults_btn">恢复默认设置</string>
+		<string name="settings_tz_btn">时区</string>
+		<string name="settings_screen_btn">屏幕</string>
+		<string name="settings_screen_bright_btn">屏幕亮度</string>
+		<string name="vibration_disabled">振动已在本设备上停用</string>
+		<string name="settings_vibration_btn">振动</string>
+		<string name="settings_language_btn">语言</string>
+		<string name="time_zone_hdr">时区</string>
+		<string name="sel_tz_list">选择时区:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) 美属萨摩亚、中途岛</string>
+		<string name="utcm10">(UTC -10) 夏威夷</string>
+		<string name="utcm9">(UTC -9) 阿拉斯加</string>
+		<string name="utcm8">(UTC -8) 太平洋标准时间</string>
+		<string name="utcm7">(UTC -7) 北美山区标准时间</string>
+		<string name="utcm6">(UTC -6) 北美中部标准时间</string>
+		<string name="utcm5">(UTC -5) 北美东部标准时间</string>
+		<string name="utcm4">(UTC -4) 大西洋标准时间</string>
+		<string name="utcm3">(UTC -3) 巴西、阿根廷(布宜诺斯)</string>
+		<string name="utcm2">(UTC -2) 大西洋中部</string>
+		<string name="utcm1">(UTC -1) 葡萄牙(亚速尔群岛),佛得角</string>
+		<string name="utc0">(UTC  0) 伦敦、都柏林、里斯本</string>
+		<string name="utcp1">(UTC +1) 柏林、布鲁塞尔、巴黎</string>
+		<string name="utcp2">(UTC +2) 雅典、伊斯坦布尔、南非</string>
+		<string name="utcp3">(UTC +3) 莫斯科、巴格达</string>
+		<string name="utcp4">(UTC +4) 阿布扎比、第比利斯、马斯喀特</string>
+		<string name="utcp5">(UTC +5) 叶卡捷琳堡、伊斯兰堡</string>
+		<string name="utcp6">(UTC +6) 阿拉木图、达卡、科伦坡</string>
+		<string name="utcp7">(UTC +7) 曼谷、河内、雅加达</string>
+		<string name="utcp8">(UTC +8) 北京、新加坡、香港</string>
+		<string name="utcp9">(UTC +9) 东京、首尔、雅库茨克</string>
+		<string name="utcp10">(UTC +10) 澳大利亚东部、关岛</string>
+		<string name="utcp11">(UTC +11) 符拉迪沃斯托克、索罗门群岛</string>
+		<string name="utcp12">(UTC +12) 奥克兰、惠灵顿、斐济</string>
+		<string name="sel_tz_offset">时间偏差(通常为 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">无</string>
+		<string name="tz_offset_0">0 分钟</string>
+		<string name="tz_offset_15">15 分钟</string>
+		<string name="tz_offset_30">30 分钟</string>
+		<string name="tz_offset_45">45 分钟</string>
+		<string name="use_dst_chk">使用夏令时 (DST)</string>
+		<string name="curr_tz">当前时区: %tw_time_zone%</string>
+		<string name="curr_tz_s">当前时区:</string>
+		<string name="set_tz_btn">设置时区</string>
+		<string name="settings_screen_hdr">屏幕</string>
+		<string name="settings_screen_timeout_hdr">屏幕超时</string>
+		<string name="enable_timeout_chk">启用屏幕超时</string>
+		<string name="screen_to_slider">屏幕超时时间(秒):</string>
+		<string name="screen_to_slider_s">屏幕超时(0=禁用): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">屏幕超时设置不可用</string>
+		<string name="screen_bright_slider">屏幕亮度: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">屏幕亮度设置不可用</string>
+		<string name="vibration_hdr">振动</string>
+		<string name="button_vibration_hdr">按钮振动</string>
+		<string name="kb_vibration_hdr">键盘振动</string>
+		<string name="act_vibration_hdr">操作振动</string>
+		<string name="button_vibration">按钮振动强度:</string>
+		<string name="kb_vibration">键盘振动强度:</string>
+		<string name="act_vibration">操作振动强度:</string>
+		<string name="select_language">选择语言:</string>
+		<string name="sel_lang_btn">选择语言</string>
+		<string name="set_language_btn">设置语言</string>
+		<string name="advanced_hdr">高级选项</string>
+		<string name="copy_log_confirm">是否导出日志到内置存储?</string>
+		<string name="copying_log" version="2">正在复制…</string>
+		<string name="copy_log_complete" version="2">日志导出完成</string>
+		<string name="fix_context_btn">修复 Context</string>
+		<string name="part_sd_btn">分区 SD 卡</string>
+		<string name="part_sd_s_btn">SD 卡</string>
+		<string name="file_manager_btn">文件管理</string>
+		<string name="language_hdr">语言</string>
+		<string name="terminal_btn">终端命令</string>
+		<string name="reload_theme_btn">重载主题</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">注入 TWRP</string>
+		<string name="inject_twrp_confirm">是否重新注入 TWRP?</string>
+		<string name="injecting_twrp">正在重新注入 TWRP…</string>
+		<string name="inject_twrp_complete">TWRP 注入完成</string>
+		<string name="swipe_to_confirm">滑动滑块确认</string>
+		<string name="part_sd_hdr">SD 卡分区</string>
+		<string name="invalid_partsd_sel">您必须选择一个可移动设备</string>
+		<string name="part_sd_lose">您将会丢失 SD 卡上的所有文件!</string>
+		<string name="part_sd_undo">此操作无法恢复!</string>
+		<string name="part_sd_ext_sz">EXT 大小:</string>
+		<string name="part_sd_swap_sz">Swap 大小:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">文件系统:</string>
+		<string name="swipe_part_sd">滑动滑块确认分区</string>
+		<string name="swipe_part_sd_s">分区</string>
+		<string name="partitioning_sd">正在对 SD 卡分区…</string>
+		<string name="partitioning_sd2">这将需要几分钟的时间。</string>
+		<string name="part_sd_complete">分区完成</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">恢复原版 Boot 镜像</string>
+		<string name="dumlock_restore_confirm">是否恢复原版 Boot 镜像?</string>
+		<string name="dumlock_restoring">正在恢复原版 Boot 镜像…</string>
+		<string name="dumlock_restore_complete">恢复原版 Boot 镜像完成</string>
+		<string name="dumlock_reflash_btn">重新刷入 Recovery</string>
+		<string name="dumlock_reflash_confirm">是否重新刷入 Recovery 至 Boot 分区?</string>
+		<string name="dumlock_reflashing">正在刷入 Recovery 至 Boot 分区…</string>
+		<string name="dumlock_reflash_complete">Recovery 刷入完成</string>
+		<string name="dumlock_install_btn">安装 HTC Dumlock</string>
+		<string name="dumlock_install_confirm">是否安装 HTC Dumlock 文件到系统中?</string>
+		<string name="dumlock_installing">正在安装 HTC Dumlock…</string>
+		<string name="dumlock_install_complete">HTC Dumlock 安装完成</string>
+		<string name="swipe_to_unlock">滑动滑块解锁</string>
+		<string name="swipe_unlock">   解锁</string>
+		<string name="fm_hdr">文件管理</string>
+		<string name="fm_sel_file">选择文件或文件夹</string>
+		<string name="fm_type_folder">文件夹</string>
+		<string name="fm_type_file">文件</string>
+		<string name="fm_choose_act">选择操作</string>
+		<string name="fm_selected">已选择: %tw_fm_type%</string>
+		<string name="fm_copy_btn">复制</string>
+		<string name="fm_copy_file_btn">复制文件</string>
+		<string name="fm_copy_folder_btn">复制文件夹</string>
+		<string name="fm_copying">正在复制</string>
+		<string name="fm_move_btn">移动</string>
+		<string name="fm_moving">正在移动</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">删除</string>
+		<string name="fm_deleting">正在删除</string>
+		<string name="fm_rename_btn">重命名</string>
+		<string name="fm_rename_file_btn">重命名文件</string>
+		<string name="fm_rename_folder_btn">重命名文件夹</string>
+		<string name="fm_renaming">正在重命名</string>
+		<string name="fm_sel_dest">选择目标目录</string>
+		<string name="fm_sel_curr_folder">选择当前目录</string>
+		<string name="fm_rename_hdr">重命名</string>
+		<string name="fm_set_perms_hdr">设置权限</string>
+		<string name="fm_perms">权限:</string>
+		<string name="fm_complete">操作完成</string>
+		<string name="fm_open_terminal_btn">在此处打开终端</string>
+		<string name="fm_edit_file">编辑文件</string>
+		<string name="decrypt_data_hdr">解密 Data 分区</string>
+		<string name="decrypt_data_enter_pass">请输入密码。</string>
+		<string name="decrypt_data_failed">密码错误,请重试!</string>
+		<string name="decrypt_data_failed_pattern">图案错误,请重试!</string>
+		<string name="decrypt_data_enter_pattern">绘制解锁图案</string>
+		<string name="decrypt_data_trying">正在尝试解密</string>
+		<string name="decrypt_data_vold_os_missing">缺少解密 vold 所需的文件: {1}</string>
+		<string name="term_hdr">终端命令</string>
+		<string name="term_s_hdr">终端</string>
+		<string name="term_kill_btn">终止</string>
+		<string name="term_sel_folder_hdr">选择运行目录</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">清除 Dalvik Cache</string>
+		<string name="sideload_wipe_cache_chk">清除 Cache</string>
+		<string name="swipe_to_sideload">滑动滑块开始 Sideload</string>
+		<string name="swipe_sideload">   开始</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">使用方式:adb sideload filename.zip</string>
+		<string name="sideload_complete">ADB Sideload 完成</string>
+		<string name="fix_contexts_hdr">修复 Context</string>
+		<string name="fix_contexts_note1">注意:很少情况下需要修复 Context。</string>
+		<string name="fix_contexts_note2">修复 SELinux Context 可能会导致</string>
+		<string name="fix_contexts_note3">您的设备无法正常启动。</string>
+		<string name="swipe_to_fix_contexts">滑动滑块确认修复</string>
+		<string name="swipe_fix_contexts">   修复 Context</string>
+		<string name="fixing_contexts">正在修复 Context…</string>
+		<string name="fix_contexts_complete">修复 Context 完成</string>
+		<string name="reboot_hdr">重启选项</string>
+		<string name="install_cancel">不,以后再说</string>
+		<string name="sel_storage_list">选择存储位置</string>
+		<string name="ok_btn">确定</string>
+		<string name="install_twrp_ramdisk">安装 Recovery Ramdisk</string>
+		<string name="install_kernel">安装内核</string>
+		<string name="repack_kernel_confirm_hdr">安装内核</string>
+		<string name="repack_ramdisk_confirm_hdr">安装 Recovery</string>
+		<string name="repack_kernel_confirm">是否安装内核?</string>
+		<string name="repack_ramdisk_confirm">是否安装 Recovery?</string>
+		<string name="repack_backup_first">请先备份现有的镜像</string>
+		<string name="repack">重新打包</string>
+		<string name="swipe_to_install">滑动滑块确认安装</string>
+		<string name="installing">正在安装…</string>
+		<string name="install_complete">安装完成</string>
+		<string name="unpack_error">解包镜像错误。</string>
+		<string name="repack_error">重新打包镜像错误。</string>
+		<string name="unpacking_image">正在解包 {1}…</string>
+		<string name="repacking_image">正在打包 {1}…</string>
+		<string name="repack_image_hdr">选择镜像</string>
+		<string name="repack_overwrite_warning">如果设备之前有root权限,现在可能被覆盖,请重新获取root权限。</string>
+		<string name="reflash_twrp">刷入当前的 TWRP</string>
+		<string name="reflash_twrp_confirm">是否刷入当前的 TWRP?</string>
+		<string name="reflashing_twrp">正在刷入 TWRP...</string>
+		<string name="reflash_twrp_complete">TWRP 刷入完成!</string>
+		<string name="fix_recovery_loop">修复 Recovery 无限重启</string>
+		<string name="fix_recovery_loop_confirm">是否修复 Recovery 无限重启?</string>
+		<string name="fixing_recovery_loop">正在修复 Recovery 无限重启…</string>
+		<string name="fix_recovery_loop_complete">Recovery 无限重启修复完成</string>
+		<string name="fixing_recovery_loop_patch">正在对内核打补丁…</string>
+		<string name="fix_recovery_loop_patch_error">对内核打补丁时出错。</string>
+		<string name="decrypt_users">解密用户</string>
+		<string name="decrypt_users_selection">选择一位要解密的用户 ID</string>
+		<string name="select_user">选择用户</string>
+		<string name="backup_storage_undecrypt_warning">备份不包含一些属于用户 {1} 的文件,因为该用户没有解密</string>
+		<string name="decrypting_user_fbe">尝试为用户 {1} 解密 FBE…</string>
+		<string name="decrypt_user_success_fbe">用户 {1} 解密成功</string>
+		<string name="decrypt_user_fail_fbe">用户 {1} 解密失败</string>
+		<string name="decrypt_data_enter_pass_fbe">请输入用户 [%tw_crypto_user_id%] 的密码</string>
+		<string name="decrypt_data_enter_pattern_fbe">请绘制用户 [%tw_crypto_user_id%] 的解锁图案</string>
+		<string name="multiuser_warning1">并非所有用户都被解密!</string>
+		<string name="multiuser_warning2">备份/还原操作可能会失败!</string>
+		<string name="multiuser_warning_accept">仍然继续</string>
+		<string name="multiuser_warning_hdr">多用户警告</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux">内核不支持读取 SELinux Context。</string>
+		<string name="full_selinux">完整 SELinux 支持。</string>
+		<string name="no_selinux">无 SELinux 支持(无 libselinux)。</string>
+		<string name="mtp_enabled">MTP 已启用</string>
+		<string name="mtp_crash">MTP 已崩溃。启动时不再加载 MTP。</string>
+		<string name="decrypt_success">已使用默认密码解密成功。</string>
+		<string name="unable_to_decrypt">无法使用默认密码来解密,您可能需要格式化 Data 分区。</string>
+		<string name="generating_digest1" version="2">正在生成 Digest</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * 正在生成 Digest…</string>
+		<string name="digest_created" version="2"> * Digest 已生成。</string>
+		<string name="digest_error" version="2"> * Digest 错误!</string>
+		<string name="digest_compute_error" version="2"> * 计算 Digest 错误。</string>
+		<string name="current_date">(当前日期)</string>
+		<string name="auto_generate">(自动生成)</string>
+		<string name="unable_to_locate_partition">未找到“{1}”分区。</string>
+		<string name="no_partition_selected">未选择备份分区。</string>
+		<string name="total_partitions_backup"> * 备份分区总数: {1}</string>
+		<string name="total_backup_size"> * 备份数据总计: {1}MB</string>
+		<string name="available_space"> * 可用空间: {1}MB</string>
+		<string name="unable_locate_storage">未找到存储设备。</string>
+		<string name="no_space">存储器上没有足够的空间。</string>
+		<string name="backup_started">[已开始备份]</string>
+		<string name="backup_folder"> * 备份文件夹: {1}</string>
+		<string name="fail_backup_folder">建立备份文件夹失败。</string>
+		<string name="avg_backup_fs">文件平均备份速度: {1}MB/秒</string>
+		<string name="avg_backup_img">镜像平均备份速度: {1}MB/秒</string>
+		<string name="total_backed_size">[总计备份 {1}MB]</string>
+		<string name="backup_completed">[备份已完成,耗时 {1} 秒]</string>
+		<string name="restore_started">[已开始还原]</string>
+		<string name="restore_folder">恢复文件夹:“{1}”</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} 恢复完成({2} 秒)]</string>
+		<string name="verifying_digest" version="2">正在校验 Digest</string>
+		<string name="skip_digest" version="2">基于用户设置,已跳过 Digest 检查。</string>
+		<string name="calc_restore">正在计算恢复详情…</string>
+		<string name="restore_read_only">无法恢复 {1} -- 已挂载为只读。</string>
+		<string name="restore_unable_locate">未找到“{1}”分区。</string>
+		<string name="no_part_restore">未选择要恢复的分区。</string>
+		<string name="restore_part_count">正在恢复 {1} 个分区…</string>
+		<string name="total_restore_size">恢复文件总计 {1}MB</string>
+		<string name="updating_system_details">更新系统详细信息</string>
+		<string name="restore_completed">[恢复完成,耗时 {1} 秒]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">打开出错:“{1}” ({2})</string>
+		<string name="unable_locate_part_backup_name">无法通过备份名称来查找分区:“{1}”</string>
+		<string name="unable_find_part_path">找不到分区路径“{1}”</string>
+		<string name="update_part_details">正在更新分区详情…</string>
+		<string name="update_part_details_done">…完成</string>
+		<string name="wiping_dalvik">正在清除 Dalvik 目录…</string>
+		<string name="cleaned">已清除:{1}…</string>
+		<string name="cache_dalvik_done">-- Dalvik Cache 目录清除完成!</string>
+		<string name="dalvik_done">-- Dalvik 目录清除完成!</string>
+		<string name="no_andsec">未发现 android secure 分区。</string>
+		<string name="unable_to_locate">未找到 {1}。</string>
+		<string name="wiping_datamedia">清除内置存储 -- /data/media…</string>
+		<string name="unable_to_mount">无法挂载 {1}</string>
+		<string name="unable_to_mount_internal">无法挂载内置存储</string>
+		<string name="unable_to_mount_storage">无法挂载存储</string>
+		<string name="fail_decrypt">解密数据失败。</string>
+		<string name="no_crypto_support">此版本不支持加密。</string>
+		<string name="decrypt_success_dev">Data 分区成功解密,新增块设备:“{1}”</string>
+		<string name="decrypt_success_nodev">Data 分区已成功解密</string>
+		<string name="done">完成。</string>
+		<string name="start_partition_sd">正在给 SD 卡分区…</string>
+		<string name="partition_sd_locate">未找到需要分区的设备。</string>
+		<string name="ext_swap_size">EXT+Swap 大小超过 SD 卡容量。</string>
+		<string name="remove_part_table">正在删除分区表…</string>
+		<string name="unable_rm_part">无法删除分区表。</string>
+		<string name="create_part">正在创建 {1} 分区…</string>
+		<string name="unable_to_create_part">无法创建 {1} 分区。</string>
+		<string name="format_sdext_as">正在将 sd-ext 格式化为 {1}…</string>
+		<string name="part_complete">分区完成。</string>
+		<string name="unable_to_open">无法打开“{1}”。</string>
+		<string name="mtp_already_enabled">MTP 已经启用</string>
+		<string name="mtp_fail">启用 MTP 失败</string>
+		<string name="no_mtp">不支持 MTP</string>
+		<string name="image_flash_start">[开始刷入镜像]</string>
+		<string name="img_to_flash">刷入镜像:“{1}”</string>
+		<string name="flash_unable_locate">未找到“{1}”分区。</string>
+		<string name="no_part_flash">未选择刷入分区。</string>
+		<string name="too_many_flash">选择刷入的分区太多。</string>
+		<string name="invalid_flash">指定的刷入分区无效。</string>
+		<string name="flash_done">[镜像刷入完成]</string>
+		<string name="wiping">正在清除 {1}</string>
+		<string name="repair_not_exist">{1} 不存在!无法修复!</string>
+		<string name="repairing_using">正在使用 {2} 修复 {1}…</string>
+		<string name="unable_repair">无法修复 {1}。</string>
+		<string name="mount_data_footer">无法挂载/data,未找到加密 footer。</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">无法创建“{1}”文件夹 ({2})。</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">挂载“{1}”失败 ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">卸载“{1}”失败 ({2})</string>
+		<string name="cannot_resize">无法更改 {1} 的大小。</string>
+		<string name="repair_resize">在调整大小之前修复 {1}。</string>
+		<string name="unable_resize">无法调整 {1} 的大小。</string>
+		<string name="no_digest_found" version="2">未找到“{1}”的 Digest 校验文件。请取消选中“启用 Digest 校验备份文件”复选框。</string>
+		<string name="digest_fail_match" version="2">“{1}” Digest 校验失败。</string>
+		<string name="digest_matched" version="2">Digest 匹配“{1}”。</string>
+		<string name="fail_decrypt_tar">无法解密 Tar 文件“{1}”</string>
+		<string name="format_data_msg">您可能需要重启 Recovery 才能使用/data。</string>
+		<string name="format_data_err">无法格式化并删除加密。</string>
+		<string name="formatting_using">使用 {2} 格式化 {1}…</string>
+		<string name="unable_to_wipe">无法清除 {1}。</string>
+		<string name="cannot_wipe">无法清除 {1} 分区。</string>
+		<string name="remove_all">移除“{1}”下的所有文件</string>
+		<string name="wiping_data">正在清除 Data 分区,跳过清除/data/media…</string>
+		<string name="backing_up">正在备份 {1}…</string>
+		<string name="backup_storage_warning">备份的 {1} 分区不包含内置存储上的任何文件,例如照片或下载的文件等等。</string>
+		<string name="backing">正在备份</string>
+		<string name="backup_size">“{1}”的备份文件大小为 0 字节。</string>
+		<string name="datamedia_fs_restore">警告:此/data 备份文件系统为 {1}! 除非把文件系统格式设置为 {1} 否则可能无法启动。</string>
+		<string name="restoring">正在恢复 {1}…</string>
+		<string name="restoring_hdr">正在恢复</string>
+		<string name="recreate_folder_err">无法重新创建 {1} 文件夹。</string>
+		<string name="img_size_err">镜像大小大于目标设备</string>
+		<string name="flashing">正在刷入 {1}…</string>
+		<string name="backup_folder_set">备份文件夹设置为:“{1}”</string>
+		<string name="locate_backup_err">未找到备份文件“{1}”</string>
+		<string name="set_restore_opt">设置恢复选项:“{1}”:</string>
+		<string name="digest_check_skip" version="2">跳过校验 Digest 已开启</string>
+		<string name="ors_encrypt_restore_err">无法使用 OpenRecoveryScript 脚本恢复加密备份。</string>
+		<string name="mounting">正在挂载</string>
+		<string name="unmounting">正在卸载</string>
+		<string name="mounted">“{1}”已挂载</string>
+		<string name="unmounted">“{1}”已卸载</string>
+		<string name="setting">设置“{1}”为“{2}”</string>
+		<string name="setting_empty">设置“{1}”为空</string>
+		<string name="making_dir1">创建目录</string>
+		<string name="making_dir2">创建目录:“{1}”</string>
+		<string name="running_command">运行命令</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">正在开始 ADB sideload…</string>
+		<string name="need_new_adb">您需要 1.0.32 或更高版本的 adb 才可以使用。</string>
+		<string name="no_pwd">未提供密码。</string>
+		<string name="done_ors">脚本执行完毕</string>
+		<string name="injecttwrp">正在注入 TWRP 到 Boot 镜像…</string>
+		<string name="zip_err">安装 Zip 刷机包“{1}”时出错</string>
+		<string name="installing_zip">正在安装 Zip 刷机包“{1}”</string>
+		<string name="select_backup_opt">设置备份选项:</string>
+		<string name="compression_on">压缩选项已开启</string>
+		<string name="digest_off" version="2">Digest 生成已关闭</string>
+		<string name="backup_fail">备份失败</string>
+		<string name="backup_clean">备份失败。正在清理备份文件夹。</string>
+		<string name="running_recovery_commands">运行 Recovery 命令</string>
+		<string name="recovery_commands_complete">Recovery 命令执行完成</string>
+		<string name="running_ors">运行 OpenRecoveryScript 脚本</string>
+		<string name="ors_complete">运行 OpenRecoveryScript 脚本完成</string>
+		<string name="check_for_digest" version="2">正在检测 Digest…</string>
+		<string name="invalid_zip_format">无效的 Zip 文件格式!</string>
+		<string name="fail_sysmap">映射文件“{1}”失败</string>
+		<string name="verify_zip_sig">正在校验 Zip 刷机包签名…</string>
+		<string name="verify_zip_fail">刷机包签名校验失败!</string>
+		<string name="verify_zip_done">刷机包签名校验成功。</string>
+		<string name="zip_corrupt">刷机包已损坏!</string>
+		<string name="no_digest" version="2">跳过 Digest 校验:未找到 Digest 文件</string>
+		<string name="digest_fail" version="2">Digest 不匹配</string>
+		<string name="digest_match" version="2">Digest 校验成功</string>
+		<string name="pid_signal">{1} 过程结束,标志: {2}</string>
+		<string name="pid_error">{1} 过程结束,错误: {2}</string>
+		<string name="install_dumlock">安装 HTC Dumlock 到系统…</string>
+		<string name="dumlock_restore">正在恢复原版 Boot…</string>
+		<string name="dumlock_reflash">正在重新刷入 Recovery 到 Boot…</string>
+		<string name="run_script">正在运行 {1} 脚本…</string>
+		<string name="rename_stock">重命名/system 下原版 Recovery 的补丁文件,避免原厂固件替换 TWRP。</string>
+		<string name="split_backup">分解备份文件为多个压缩文件…</string>
+		<string name="backup_error">建立备份失败。</string>
+		<string name="restore_error">在恢复过程中发生错误。</string>
+		<string name="split_thread">拆分线程 ID {1} 到压缩文件 {2}</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">已完成 %llu 个,共 %llu 个文件,进度 %i%%</string>
+		<string name="size_progress">已完成 %lluMB,共 %lluMB,进度 %i%%</string>
+		<string name="decrypt_cmd" version="2">正在尝试通过命令行解密 Data 分区或用户数据。</string>
+		<string name="base_pkg_err">无法加载基本包。</string>
+		<string name="simulating">正在模拟操作…</string>
+		<string name="backup_cancel">备份已取消。</string>
+		<string name="config_twrp">正在配置 TWRP…</string>
+		<string name="config_twrp_err">此内核无法配置 TWRP。</string>
+		<string name="copy_log">已经导出 Recovery 日志到 {1}。</string>
+		<string name="max_queue">已达到最大刷机包刷入队列!</string>
+		<string name="min_queue">已达到最大刷机包刷入队列!</string>
+		<string name="screenshot_saved">截图已保存到:{1}</string>
+		<string name="screenshot_err">截图失败!</string>
+		<string name="zip_wipe_cache">一个或多个刷机包请求清除 Cache -- 正在清除 Cache。</string>
+		<string name="and_sec_wipe_err">无法清除 android secure</string>
+		<string name="dalvik_wipe_err">清除 Dalvik 失败</string>
+		<string name="auto_gen">(Auto Generate)</string>
+		<string name="curr_date">(Current Date)</string>
+		<string name="backup_name_len">备份名称太长。</string>
+		<string name="backup_name_invalid">备份名称“{1}”中包含无效字符:“{1}”</string>
+		<string name="no_real_sdcard">此设备没有真实的 SD 卡!正在中止!</string>
+		<string name="cancel_sideload">取消 ADB Sideload…</string>
+		<string name="change_fs_err">更换文件系统时出错。</string>
+		<string name="theme_ver_err">自定义主题和 TWRP 版本不匹配,正在使用原版主题。</string>
+		<string name="up_a_level">(上级目录)</string>
+		<string name="install_reboot" version="2">将会在 %tw_sleep% 秒内重启</string>
+		<string name="adbbackup_error">ADB 备份错误。正在退出…"</string>
+		<string name="adbbackup_control_error">无法写入 adb 控制通道</string>
+		<string name="twrp_adbbu_option">启用 TWRP ADB 备份需使用 --twrp 选项</string>
+		<string name="partition_not_found">分区列表中未发现路径: {1}</string>
+		<string name="copy_kernel_log">已导出 kernel 日志到 {1}</string>
+		<string name="copy_logcat">已导出 logcat 日志到 {1}</string>
+		<string name="include_kernel_log">包含 Kernel 日志</string>
+		<string name="include_logcat">包含 Logcat 日志</string>
+		<string name="sha2_chk">使用 SHA2 进行散列校验</string>
+		<string name="unable_set_boot_slot">更改 Bootloader 启动槽位至 {1} 错误</string>
+		<string name="unmount_sys_install">在安装 Zip 刷机包之前先解除挂载 System</string>
+		<string name="unmount_system">正在解除挂载 System…</string>
+		<string name="unmount_system_err">解除挂载 System 失败</string>
+		<string name="flash_ab_inactive">正在刷入 A/B Zip 刷机包至非活动槽位: {1}</string>
+		<string name="flash_ab_reboot">请重启 TWRP 切换到新槽位后,再刷入其它的 ZIP。</string>
+		<string name="flash_ab_both_slots">刷入到2个槽位</string>
+		<string name="ozip_decrypt_decryption">正在解密 Ozip…</string>
+		<string name="ozip_decrypt_finish">Ozip 解密完成!</string>
+		<string name="fastboot_button">Fastboot</string>
+		<string name="enable_adb">启用 ADB</string>
+		<string name="enable_fastboot">启用 Fastboot</string>
+		<string name="fastboot_console_msg">进入 Fastboot 模式…</string>
+		<!-- Android Rescue Party trigger -->
+		<string name="rescue_party0">Android 救援程序已触发!您可以尝试下面的操作:</string>
+		<string name="rescue_party1"> 1. 清除 Cache;</string>
+		<string name="rescue_party2"> 2. 格式化 Data 分区;</string>
+		<string name="rescue_party3"> 3. 重新刷入您的 ROM。</string>
+		<string name="rescue_party4">报告的问题为:</string>
+		<string name="restore_system_context">无法获取 {1} 的默认 context -- Android 可能无法启动。</string>
+		<string name="change_twrp_folder_btn">更改 TWRP 文件夹</string>
+		<string name="change_twrp_folder_on_process">正在更改 TWRP 文件夹</string>
+		<string name="change_twrp_folder_after_process">TWRP文件夹已改为</string>
+		<string name="tw_folder_exists">已存在同名文件夹!</string>
+		<string name="unmap_super_devices">取消 Super Devices 映射</string>
+		<string name="unmap_super_devices_confirm">是否取消所有 Super Devices 映射?</string>
+		<string name="unmapping_super_devices">正在取消映射 Super Devices...</string>
+		<string name="unmap_super_devices_complete">取消所有 Super Devices 映射完成!</string>
+		<string name="merges_snapshots">合并快照</string>
+		<string name="merge_snapshots_confirm">是否合并快照?</string>
+		<string name="merging_snapshots">正在合并快照...</string>
+		<string name="merging_snapshots_complete">合并快照完成!</string>
+		<string name="mount_vab_partitions">在重新启动 TWRP 之前,Super Devices 上的分区可能不会挂载。</string>
+		<string name="restore_pin_password">是否在 PIN码/混合密码 启用时进行恢复?</string>
+		<string name="restore_pattern">是否在 图案密码 启用时恢复?</string>
+		<string name="restore_pin">是否在 PIN码 启用时恢复?</string>
+		<string name="continue_restore_encrypted">是否继续恢复?</string>
+		<string name="restore_with_pin_password1">ROM中的 PIN码/混合密码 已启用</string>
+		<string name="restore_with_pin_password2">恢复前应该在 ROM 中禁用 PIN码/混合密码</string>
+		<string name="restore_with_pin1">ROM中的 PIN码 已启用</string>
+		<string name="restore_with_pin2">恢复前应该在 ROM 中禁用 PIN码</string>
+		<string name="restore_with_pattern1">ROM 中的 图案密码 已启用</string>
+		<string name="restore_with_pattern2">恢复前应该在 ROM 中禁用 图案密码</string>
+		<string name="reboot_after_restore">建议在 Android 首次启动后再重启一次。</string>
+	</resources>
+</language>
diff --git a/gui/theme/extra-languages/languages/zh_TW.xml b/gui/theme/extra-languages/languages/zh_TW.xml
new file mode 100644
index 0000000..3d211b5
--- /dev/null
+++ b/gui/theme/extra-languages/languages/zh_TW.xml
@@ -0,0 +1,754 @@
+<?xml version="1.0"?>
+
+<language>
+	<display>Chinese (Traditional)</display>
+
+	<resources>
+		<!-- Font overrides - only change these if your language requires special characters -->
+		<resource name="font_l" type="fontoverride" filename="DroidSansFallback.ttf" scale="100" />
+		<resource name="font_m" type="fontoverride" filename="DroidSansFallback.ttf" scale="100" />
+		<resource name="font_s" type="fontoverride" filename="DroidSansFallback.ttf" scale="100" />
+		<resource name="fixed" type="fontoverride" filename="DroidSansFallback.ttf" scale="100" />
+
+		<!-- Partition display names -->
+		<string name="system">System</string>
+		<string name="system_image">System 映像</string>
+		<string name="vendor">Vendor</string>
+		<string name="vendor_image">Vendor 映像</string>
+		<string name="boot">Boot</string>
+		<string name="recovery">Recovery</string>
+		<string name="cache">Cache</string>
+		<string name="data">Data</string>
+		<string name="data_backup">Data(不包含儲存空間)</string>
+		<string name="sdcard">SDCard</string>
+		<string name="internal">內建儲存空間</string>
+		<string name="microsd">SD 卡</string>
+		<string name="usbotg">外接式隨身碟</string>
+		<string name="android_secure">Android Secure</string>
+		<string name="dalvik">Dalvik / ART Cache</string>
+		<!-- This is a rarely used partition on a Micro SD card for a very old app2sd system -->
+		<string name="sdext">SD-EXT</string>
+		<string name="adopted_data">Adopted Data</string>
+		<string name="adopted_storage">內部儲存(延伸)空間</string>
+		<string name="autostorage">儲存空間</string>
+
+		<!-- GUI XML strings -->
+		<string name="twrp_header">Team Win Recovery Project</string>
+		<string name="twrp_watch_header">TWRP %tw_version%</string>
+		<string name="cpu_temp">溫度: %tw_cpu_temp% &#xB0;C</string>
+		<string name="battery_pct">電量: %tw_battery%</string>
+		<string name="sort_by_name">按名稱排序</string>
+		<string name="sort_by_date">按日期排序</string>
+		<string name="sort_by_size">按大小排序</string>
+		<string name="sort_by_name_only">名稱</string>
+		<string name="sort_by_date_only">日期</string>
+		<string name="sort_by_size_only">大小</string>
+		<string name="tab_general">一般</string>
+		<string name="tab_options">選項</string>
+		<string name="tab_backup">備份</string>
+		<string name="tab_time_zone">時區</string>
+		<string name="tab_screen">螢幕</string>
+		<string name="tab_vibration">震動</string>
+		<string name="tab_language">語言</string>
+
+		<string name="install_btn">安裝</string>
+		<string name="wipe_btn">清除</string>
+		<string name="backup_btn">備份</string>
+		<string name="restore_btn">恢復</string>
+		<string name="mount_btn">掛載</string>
+		<string name="settings_btn">設定</string>
+		<string name="advanced_btn">進階</string>
+		<string name="reboot_btn">重新啟動</string>
+		<string name="files_btn">檔案</string>
+		<string name="copy_log_btn">匯出日誌</string>
+		<string name="select_type_hdr">選擇類型</string>
+		<string name="install_zip_hdr">安裝 ZIP 刷機包</string>
+		<string name="install_zip_btn">安裝刷機包</string>
+		<string name="install_image_hdr">刷入 Image 映像</string>
+		<string name="install_image_btn">刷入映像</string>
+		<string name="install_select_file_hdr">選擇檔案</string>
+		<string name="file_selector_folders_hdr">目錄</string>
+		<string name="select_file_from_storage">從 %tw_storage_display_name% (%tw_storage_free_size% MB) 中選擇檔案</string>
+		<string name="adb_sideload_btn">ADB Sideload</string>
+		<string name="install_hdr">安裝</string>
+		<string name="select_storage_hdr">儲存位置</string>
+		<string name="select_storage_btn">儲存位置</string>
+		<string name="queue_hdr">佇列</string>
+		<string name="zip_queue_count">已選 %tw_zip_queue_count% 個檔案(最多 10 個)</string>
+		<string name="zip_queue_count_s">已選 %tw_zip_queue_count% 個,最多 10 個:</string>
+		<string name="zip_warn1">此操作可能會刷入不相容的韌體,</string>
+		<string name="zip_warn2">導致您的設備變磚。</string>
+		<string name="zip_back_cancel">按返回按鈕取消增加此刷機包。</string>
+		<string name="zip_back_clear">按返回按鈕清除刷機包刷入佇列。</string>
+		<string name="folder">目錄:</string>
+		<string name="file">檔案:</string>
+		<string name="zip_sig_chk">Zip 刷機包簽名校驗</string>
+		<string name="inject_twrp_chk">安裝完成後注入 TWRP</string>
+		<string name="install_reboot_chk">安裝完成後重新開機</string>
+		<string name="options_hdr">選項</string>
+		<string name="confirm_flash_hdr">確認刷入</string>
+		<string name="zip_queue">佇列:</string>
+		<string name="options">選項:</string>
+		<string name="swipe_confirm">   確認</string>
+		<string name="zip_add_btn">增加更多刷機包</string>
+		<string name="zip_clear_btn">清除刷機佇列</string>
+		<string name="install_zip_count_hdr">正在刷入第 %tw_zip_index% 個,共 %tw_zip_queue_count% 個</string>
+		<string name="installing_zip_xml">正在安裝: %tw_file%</string>
+		<string name="failed">失敗</string>
+		<string name="successful">成功</string>
+		<string name="install_failed">安裝失敗</string>
+		<string name="install_successful">安裝成功</string>
+		<string name="wipe_cache_dalvik_btn">清除 Cache/Dalvik</string>
+		<string name="wipe_dalvik_btn">清除 Dalvik</string>
+		<string name="reboot_system_btn">重新開機至系統</string>
+		<string name="install_sel_target">選擇目標分割區</string>
+		<string name="flash_image_select">選擇要刷入映像的分割區:</string>
+		<string name="target_partition">目標分割區:</string>
+		<string name="flashing_image">正在刷入映像…</string>
+		<string name="image_flashed">映像已刷入。</string>
+		<string name="wipe_cache_dalvik_confirm">是否清除 Cache 和 Dalvik?</string>
+		<string name="wipe_dalvik_confirm">是否清除 Dalvik?</string>
+		<string name="wiping_cache_dalvik">正在清除 Cache 和 Dalvik…</string>
+		<string name="wipe_cache_dalvik_complete">Cache 和 Dalvik 清除完成</string>
+		<string name="wipe_dalvik_complete">Dalvik 清除完成</string>
+		<string name="swipe_wipe">滑動滑塊確認清除</string>
+		<string name="swipe_wipe_s">   清除</string>
+		<string name="no_os1">沒有安裝任何系統!</string>
+		<string name="no_osrb">您確定要重新開機?</string>
+		<string name="no_ospo">您確定要關機?</string>
+		<string name="rebooting">正在重新開機…</string>
+		<string name="swipe_reboot">滑動滑塊確認重新開機</string>
+		<string name="swipe_reboot_s">   重新開機</string>
+		<string name="reboot_install_app_hdr">安裝 TWRP 應用程式?</string>
+		<string name="reboot_install_app1">您希望安裝官方 TWRP 應用程式嗎?</string>
+		<string name="reboot_install_app2">該應用可幫您檢查 TWRP 的最新版本。</string>
+		<string name="reboot_install_app_prompt_install">如果 TWRP 應用程式未安裝時提示我</string>
+		<string name="reboot_install_app_system">安裝為系統應用程式</string>
+		<string name="reboot_installing_app">正在安裝應用程式…</string>
+		<string name="swipe_to_install_app">滑動滑塊以確認安裝</string>
+		<string name="uninstall_twrp_system_app">解除安裝 TWRP 應用程式</string>
+		<string name="uninstall_twrp_system_app_confirm">確認解除安裝 TWRP 應用程式?</string>
+		<string name="uninstalling_twrp_system_app">正在解除安裝 TWRP 應用程式…</string>
+		<string name="uninstall_twrp_system_app_complete">解除安裝 TWRP 應用程式完成</string>
+		<string name="swipe_flash">滑動滑塊以確認刷入</string>
+		<string name="confirm_action">確認操作</string>
+		<string name="back_cancel">按返回按鈕取消。</string>
+		<string name="cancel_btn">取消</string>
+		<string name="wipe_hdr">清除</string>
+		<string name="factory_reset_hdr">恢復出廠設定(雙清)</string>
+		<string name="factory_reset_btn">恢復出廠</string>
+		<string name="factory_reset1">這將會清空 Data、Cache 分割區和 Dalvik 快取</string>
+		<string name="factory_reset2">(不會清除內建儲存空間 /sdcard)</string>
+		<string name="factory_reset3">在大多數情況下,您只需要</string>
+		<string name="factory_reset4">執行此操作即可。</string>
+		<string name="factory_reset5">(不包括使用者/鎖定螢幕)</string>
+		<string name="factory_resetting">正在恢復出廠設定…</string>
+		<string name="advanced_wipe_hdr">進階清除選項</string>
+		<string name="advanced_wipe_btn">進階清除</string>
+		<string name="wipe_enc_confirm">是否清除 Data 分割區加密?</string>
+		<string name="formatting_data">正在格式化 Data 分割區…</string>
+		<string name="swipe_format_data">滑動滑塊確認格式化</string>
+		<string name="swipe_format_data_s">   格式化 Data 分割區</string>
+		<string name="factory_reset_complete">恢復出廠完成</string>
+		<string name="sel_part_hdr">選擇分割區</string>
+		<string name="wipe_sel_confirm">是否清除所選的分割區?</string>
+		<string name="wiping_part">正在清除分割區…</string>
+		<string name="wipe_complete">清除完成</string>
+		<string name="sel_part_wipe">選擇要清除的分割區:</string>
+		<string name="invalid_part_sel">無效的分割區</string>
+		<string name="format_data_hdr">格式化 Data 分割區</string>
+		<string name="format_data_btn">格式化 Data 分割區</string>
+		<string name="format_data_ptr1">格式化 Data 分割區將會清空您安裝的所有</string>
+		<string name="format_data_ptr2">軟體、備份、圖片、媒體,</string>
+		<string name="format_data_ptr3">以及對內建儲存空間所做的加密等。</string>
+		<string name="format_data_adopted">也包括 內部儲存(延伸)空間</string>
+		<string name="format_data_lcp1">格式化 Data 分割區將會清空您安裝的所有軟體、備份、圖片、媒體,</string>
+		<string name="format_data_lcp2">以及對內建儲存空間所做的加密等。</string>
+		<string name="format_data_wtc1">格式化 Data 分割區會清除您的所有軟體</string>
+		<string name="format_data_wtc2">備份和媒體數據,無法取消。</string>
+		<string name="format_data_undo">執行此操作後將無法恢復。</string>
+		<string name="format_data_complete">格式化 Data 分割區完成</string>
+		<!-- Translator note: the word "yes" must remain English! -->
+		<string name="yes_continue">輸入 yes 繼續,按返回按鈕取消。</string>
+		<string name="part_opt_hdr">分割區名: %tw_partition_name%</string>
+		<string name="sel_act_hdr">選擇操作</string>
+		<string name="file_sys_opt">檔案系統選項</string>
+		<string name="partition">分割區: %tw_partition_name%</string>
+		<string name="part_mount_point">掛載位置: %tw_partition_mount_point%</string>
+		<string name="part_curr_fs">檔案系統: %tw_partition_file_system%</string>
+		<string name="part_present_yes">當前:是</string>
+		<string name="part_present_no">當前:否</string>
+		<string name="part_removable_yes">可移除:是</string>
+		<string name="part_removable_no">可移除:否</string>
+		<string name="part_size">大小: %tw_partition_size%MB</string>
+		<string name="part_used">已用: %tw_partition_used%MB</string>
+		<string name="part_free">可用: %tw_partition_free%MB</string>
+		<string name="part_backup_size">備份大小: %tw_partition_backup_size%MB</string>
+		<string name="resize_btn">調整檔案系統</string>
+		<string name="resize_btn_s">調整</string>
+		<string name="resize_confirm">是否調整 %tw_partition_name%?</string>
+		<string name="resizing">正在調整…</string>
+		<string name="resize_complete">調整完成</string>
+		<string name="swipe_resize">滑動滑塊確認調整</string>
+		<string name="swipe_resize_s">   調整</string>
+		<string name="repair_btn">修復檔案系統</string>
+		<string name="repair_btn_s">修復</string>
+		<string name="repair_confirm">是否修復 %tw_partition_name%?</string>
+		<string name="repairing">正在修復…</string>
+		<string name="repair_complete">修復完成</string>
+		<string name="swipe_repair">滑動滑塊確認修復</string>
+		<string name="swipe_repair_s">   修復</string>
+		<string name="change_fs_btn">更改檔案系統</string>
+		<string name="change_fs_btn_s">更改</string>
+		<string name="change_fs_for_hdr">要更改的分割區: %tw_partition_name%</string>
+		<string name="change_fs_for_hdr_s">分割區: %tw_partition_name%&gt;選擇檔案系統</string>
+		<string name="change_fs_warn1">一些韌體或 Kernel 可能不支援</string>
+		<string name="change_fs_warn2">某些檔案系統,請謹慎!</string>
+		<string name="change_fs_confirm">是否更改 %tw_partition_name% 為:</string>
+		<string name="formatting">正在格式化…</string>
+		<string name="format_complete">格式化完成</string>
+		<string name="swipe_change_fs">滑動滑塊確認更改</string>
+		<string name="swipe_change_s">   更改</string>
+		<string name="back_btn">返回</string>
+		<string name="wipe_enc_btn">清除加密</string>
+		<string name="swipe_factory_reset">滑動滑塊確認恢復出廠</string>
+		<string name="repair_change_btn">修復或更改檔案系統</string>
+		<string name="storage_hdr">儲存位置: %tw_storage_display_name% (%tw_storage_free_size% MB)</string>
+		<string name="backup_hdr">備份</string>
+		<string name="backup_confirm_hdr">確認備份</string>
+		<string name="encryption_tab">加密</string>
+		<string name="encryption">加密:</string>
+		<string name="name">名稱:</string>
+		<string name="sel_part_backup">選擇要備份的分割區:</string>
+		<string name="storage">儲存位置:</string>
+		<string name="enc_disabled">已停用 - 設定密碼以啟用</string>
+		<string name="enc_enabled">已啟用</string>
+		<string name="enable_backup_comp_chk">啟用壓縮</string>
+		<string name="skip_digest_backup_chk" version="2">備份時跳過生成 Digest</string>
+		<string name="disable_backup_space_chk" version="2">在備份之前停用剩餘空間檢查</string>
+		<string name="current_boot_slot">當前槽位: %tw_active_slot%</string>
+		<string name="boot_slot_a">槽位 A</string>
+		<string name="boot_slot_b">槽位 B</string>
+		<string name="changing_boot_slot">更改啟動槽位</string>
+		<string name="changing_boot_slot_complete">更改啟動槽位完成</string>
+		<string name="refresh_sizes_btn">刷新大小</string>
+		<string name="swipe_backup">滑動滑塊確認備份</string>
+		<string name="append_date_btn">附加日期</string>
+		<string name="backup_name_exists">使用該名稱的備份已經存在!</string>
+		<string name="encrypt_backup">是否加密備份?</string>
+		<string name="enter_pass">請輸入密碼:</string>
+		<string name="enter_pass2">請再輸入一遍:</string>
+		<string name="pass_not_match">密碼不匹配!</string>
+		<string name="partitions">分割區:</string>
+		<string name="disabled">已停用</string>
+		<string name="enabled">已啟用</string>
+		<string name="backup_name_hdr">設定備份名稱</string>
+		<string name="progress">進度:</string>
+		<string name="backup_complete">備份完成</string>
+		<string name="restore_hdr">恢復</string>
+		<string name="sel_backup_hdr">選擇備份</string>
+		<string name="restore_sel_store_hdr">從 %tw_storage_display_name% (%tw_storage_free_size% MB) 中選擇備份</string>
+		<string name="restore_sel_pack_fs">選擇要恢復的備份包:</string>
+		<string name="restore_enc_backup_hdr">備份已被加密</string>
+		<string name="restore_dec_fail">密碼錯誤,請重試</string>
+		<string name="del_backup_btn">刪除備份</string>
+		<string name="del_backup_confirm">是否刪除備份:</string>
+		<string name="del_backup_confirm2">此操作無法恢復!</string>
+		<string name="deleting_backup">正在刪除備份…</string>
+		<string name="backup_deleted">備份刪除完成</string>
+		<string name="swipe_delete">滑動滑塊確認刪除</string>
+		<string name="swipe_delete_s">   刪除</string>
+		<string name="restore_try_decrypt">加密的備份 - 嘗試解密</string>
+		<string name="restore_try_decrypt_s">正在嘗試解密</string>
+		<string name="restore_backup_date">備份於 %tw_restore_file_date%</string>
+		<string name="restore_sel_part">選擇要恢復的分割區:</string>
+		<string name="restore_enable_digest_chk" version="2">啟用 Digest 校驗備份檔案</string>
+		<string name="restore_complete">恢復完成</string>
+		<string name="swipe_restore">滑動滑塊確認恢復</string>
+		<string name="swipe_restore_s">   恢復</string>
+		<string name="rename_backup_hdr">重新命名備份</string>
+		<string name="rename_backup_confirm">是否重新命名備份檔案?</string>
+		<string name="rename_backup_confirm2">此操作無法恢復!</string>
+		<string name="renaming_backup">正在重新命名…</string>
+		<string name="rename_backup_complete">重新命名完成</string>
+		<string name="swipe_to_rename">滑動滑塊確認重新命名</string>
+		<string name="swipe_rename">   重新命名</string>
+		<string name="confirm_hdr">確認</string>
+		<string name="mount_hdr">掛載</string>
+		<string name="mount_sel_part">選擇要掛載的分割區:</string>
+		<string name="mount_sys_ro_chk">掛載 System 分割區為只讀</string>
+		<string name="mount_sys_ro_s_chk">掛載 System 為只讀</string>
+		<string name="decrypt_data_btn">解密 Data 分割區</string>
+		<string name="disable_mtp_btn">啟用 MTP 模式</string>
+		<string name="enable_mtp_btn">停用 MTP 模式</string>
+		<string name="mount_usb_storage_btn">掛載 USB 儲存空間</string>
+		<string name="usb_storage_hdr">USB 儲存空間</string>
+		<string name="usb_stor_mnt1">已掛載 USB 儲存空間</string>
+		<string name="usb_stor_mnt2">請確認解除掛載前先從</string>
+		<string name="usb_stor_mnt3">電腦上安全地移除設備!</string>
+		<string name="unmount_btn">解除掛載</string>
+		<string name="rb_system_btn">系統</string>
+		<string name="rb_poweroff_btn">關機</string>
+		<string name="rb_recovery_btn">Recovery</string>
+		<string name="rb_bootloader_btn">Bootloader</string>
+		<string name="rb_download_btn">Download</string>
+		<string name="rb_edl_btn">EDL</string>
+		<string name="turning_off">正在關機…</string>
+		<string name="swipe_power_off">滑動滑塊確認關機</string>
+		<string name="swipe_power_off_s">關機</string>
+		<string name="sys_ro_hdr">未修改的 System 分割區</string>
+		<string name="sys_ro_keep">是否保持系統分割區為只讀?</string>
+		<string name="sys_rop1">TWRP 可以保持系統分割區未修改狀態</string>
+		<string name="sys_rop2">使您更容易獲得官方的更新。</string>
+		<string name="sys_rop3">TWRP 將無法禁止原廠韌體可能</string>
+		<string name="sys_rop4">替換 TWRP 並且不能 Root 您的設備。</string>
+		<string name="sys_rop5">安裝刷機包或者通過 adb 操作依然可以</string>
+		<string name="sys_rop6">修改系統分割區。</string>
+		<string name="sys_rol1">TWRP 可以保持系統分割區為未修改狀態,使您更容易獲得官方的更新。</string>
+		<string name="sys_rol2">TWRP 將無法禁止原廠韌體替換 TWRP,並且不能 Root 您的設備。</string>
+		<string name="sys_rol3">安裝刷機包或者通過 adb 操作依然可以修改系統分割區。</string>
+		<string name="sys_ro_never_show_chk">下次啟動時不再顯示此介面</string>
+		<string name="sys_ro_keep_ro_btn">保持只讀</string>
+		<string name="swipe_allow_mod">滑動滑塊允許修改</string>
+		<string name="swipe_allow_mod_s">允許修改</string>
+		<string name="settings_hdr">設定</string>
+		<string name="settings_gen_hdr">一般</string>
+		<string name="settings_gen_s_hdr">一般</string>
+		<string name="settings_gen_btn">一般</string>
+		<string name="use_rmrf_chk">使用 rm -rf 指令代替格式化</string>
+		<string name="use24clock_chk">使用 24 小時制</string>
+		<string name="rev_navbar_chk">反轉底部按鈕佈局</string>
+		<string name="simact_chk">為測試主題使用模擬操作</string>
+		<string name="simfail_chk">模擬失敗操作</string>
+		<string name="ctr_navbar_rdo">導航欄按鈕居中</string>
+		<string name="lft_navbar_rdo">導航欄按鈕左對齊</string>
+		<string name="rht_navbar_rdo">導航欄按鈕右對齊</string>
+		<string name="restore_defaults_btn">恢復預設設定</string>
+		<string name="settings_tz_btn">時區</string>
+		<string name="settings_screen_btn">螢幕</string>
+		<string name="settings_screen_bright_btn">螢幕亮度</string>
+		<string name="vibration_disabled">震動已在本設備上停用</string>
+		<string name="settings_vibration_btn">震動</string>
+		<string name="settings_language_btn">語言</string>
+		<string name="time_zone_hdr">時區</string>
+		<string name="sel_tz_list">選擇時區:</string>
+		<!-- Translator note: if it does not make sense to translate the locations or if it makes more sense,
+				feel free to change the location listed or drop the location entirely and just call it UTC -6 -->
+		<string name="utcm11">(UTC -11) 美屬薩摩亞、中途島</string>
+		<string name="utcm10">(UTC -10) 夏威夷</string>
+		<string name="utcm9">(UTC -9) 阿拉斯加</string>
+		<string name="utcm8">(UTC -8) 太平洋標準時間</string>
+		<string name="utcm7">(UTC -7) 北美山區標準時間</string>
+		<string name="utcm6">(UTC -6) 北美中部標準時間</string>
+		<string name="utcm5">(UTC -5) 北美東部標準時間</string>
+		<string name="utcm4">(UTC -4) 大西洋標準時間</string>
+		<string name="utcm3">(UTC -3) 巴西、阿根廷(布宜諾斯)</string>
+		<string name="utcm2">(UTC -2) 大西洋中部</string>
+		<string name="utcm1">(UTC -1) 葡萄牙(亞速群島),維德角</string>
+		<string name="utc0">(UTC  0) 倫敦、都柏林、里斯本</string>
+		<string name="utcp1">(UTC +1) 柏林、布魯塞爾、巴黎</string>
+		<string name="utcp2">(UTC +2) 雅典、伊斯坦堡、南非</string>
+		<string name="utcp3">(UTC +3) 莫斯科、巴格達</string>
+		<string name="utcp4">(UTC +4) 阿布達比、提比里西、馬斯喀特</string>
+		<string name="utcp5">(UTC +5) 葉卡捷琳堡、伊斯蘭瑪巴德</string>
+		<string name="utcp6">(UTC +6) 阿拉木圖、達卡、可倫坡</string>
+		<string name="utcp7">(UTC +7) 曼谷、河內、雅加達</string>
+		<string name="utcp8">(UTC +8) 台北、北京、新加坡、香港</string>
+		<string name="utcp9">(UTC +9) 東京、首爾、雅庫茨克</string>
+		<string name="utcp10">(UTC +10) 澳洲東部、關島</string>
+		<string name="utcp11">(UTC +11) 符拉迪沃斯托克、索羅門群島</string>
+		<string name="utcp12">(UTC +12) 奧克蘭、惠靈頓、斐濟</string>
+		<string name="sel_tz_offset">時間偏差(通常為 0): %tw_time_zone_guioffset%</string>
+		<string name="tz_offset_none">無</string>
+		<string name="tz_offset_0">0 分鐘</string>
+		<string name="tz_offset_15">15 分鐘</string>
+		<string name="tz_offset_30">30 分鐘</string>
+		<string name="tz_offset_45">45 分鐘</string>
+		<string name="use_dst_chk">使用夏令時 (DST)</string>
+		<string name="curr_tz">當前時區: %tw_time_zone%</string>
+		<string name="curr_tz_s">當前時區:</string>
+		<string name="set_tz_btn">設定時區</string>
+		<string name="settings_screen_hdr">螢幕</string>
+		<string name="settings_screen_timeout_hdr">螢幕超時</string>
+		<string name="enable_timeout_chk">啟用螢幕超時</string>
+		<string name="screen_to_slider">螢幕超時時間(秒):</string>
+		<string name="screen_to_slider_s">螢幕超時(0=停用): %tw_screen_timeout_secs%</string>
+		<string name="screen_to_na">螢幕超時設定不可用</string>
+		<string name="screen_bright_slider">螢幕亮度: %tw_brightness_pct%%</string>
+		<string name="screen_bright_na">螢幕亮度設定不可用</string>
+		<string name="vibration_hdr">震動</string>
+		<string name="button_vibration_hdr">按鈕震動</string>
+		<string name="kb_vibration_hdr">鍵盤震動</string>
+		<string name="act_vibration_hdr">操作震動</string>
+		<string name="button_vibration">按鈕震動強度:</string>
+		<string name="kb_vibration">鍵盤震動強度:</string>
+		<string name="act_vibration">操作震動強度:</string>
+		<string name="select_language">選擇語言:</string>
+		<string name="sel_lang_btn">選擇語言</string>
+		<string name="set_language_btn">設定語言</string>
+		<string name="advanced_hdr">進階選項</string>
+		<string name="copy_log_confirm">是否匯出日誌到內建儲存空間?</string>
+		<string name="copying_log" version="2">正在複製…</string>
+		<string name="copy_log_complete" version="2">日誌匯出完成</string>
+		<string name="fix_context_btn">修復 Context</string>
+		<string name="part_sd_btn">分割區 SD 卡</string>
+		<string name="part_sd_s_btn">SD 卡</string>
+		<string name="file_manager_btn">檔案管理</string>
+		<string name="language_hdr">語言</string>
+		<string name="terminal_btn">終端指令</string>
+		<string name="reload_theme_btn">重載主題</string>
+		<string name="dumlock_btn">HTC Dumlock</string>
+		<string name="inject_twrp_btn">注入 TWRP</string>
+		<string name="inject_twrp_confirm">是否重新注入 TWRP?</string>
+		<string name="injecting_twrp">正在重新注入 TWRP…</string>
+		<string name="inject_twrp_complete">TWRP 注入完成</string>
+		<string name="swipe_to_confirm">滑動滑塊確認</string>
+		<string name="part_sd_hdr">SD 卡分割區</string>
+		<string name="invalid_partsd_sel">您必須選擇一個可移動設備</string>
+		<string name="part_sd_lose">您將會遺失 SD 卡上的所有檔案!</string>
+		<string name="part_sd_undo">此操作無法恢復!</string>
+		<string name="part_sd_ext_sz">EXT 大小:</string>
+		<string name="part_sd_swap_sz">Swap 大小:</string>
+		<string name="part_sd_m">-</string>
+		<string name="part_sd_p">+</string>
+		<string name="file_system">檔案系統:</string>
+		<string name="swipe_part_sd">滑動滑塊確認分割區</string>
+		<string name="swipe_part_sd_s">分割區</string>
+		<string name="partitioning_sd">正在對 SD 卡分割區…</string>
+		<string name="partitioning_sd2">這將需要幾分鐘的時間。</string>
+		<string name="part_sd_complete">分割區完成</string>
+		<string name="dumlock_hdr">HTC Dumlock</string>
+		<string name="dumlock_restore_btn">恢復原廠 Boot 映像</string>
+		<string name="dumlock_restore_confirm">是否恢復原廠 Boot 映像?</string>
+		<string name="dumlock_restoring">正在恢復原廠 Boot 映像…</string>
+		<string name="dumlock_restore_complete">恢復原廠 Boot 映像完成。</string>
+		<string name="dumlock_reflash_btn">重新刷入 Recovery</string>
+		<string name="dumlock_reflash_confirm">是否重新刷入 Recovery 至 Boot 分割區?</string>
+		<string name="dumlock_reflashing">正在刷入 Recovery 至 Boot 分割區…</string>
+		<string name="dumlock_reflash_complete">Recovery 刷入完成</string>
+		<string name="dumlock_install_btn">安裝 HTC Dumlock</string>
+		<string name="dumlock_install_confirm">是否安裝 HTC Dumlock 檔案到系統中?</string>
+		<string name="dumlock_installing">正在安裝 HTC Dumlock…</string>
+		<string name="dumlock_install_complete">HTC Dumlock 安裝完成</string>
+		<string name="swipe_to_unlock">滑動滑塊解鎖</string>
+		<string name="swipe_unlock">   解鎖</string>
+		<string name="fm_hdr">檔案管理</string>
+		<string name="fm_sel_file">選擇檔案或檔案夾</string>
+		<string name="fm_type_folder">檔案夾</string>
+		<string name="fm_type_file">檔案</string>
+		<string name="fm_choose_act">選擇操作</string>
+		<string name="fm_selected">已選擇: %tw_fm_type%</string>
+		<string name="fm_copy_btn">複製</string>
+		<string name="fm_copy_file_btn">複製檔案</string>
+		<string name="fm_copy_folder_btn">複製檔案夾</string>
+		<string name="fm_copying">正在複製</string>
+		<string name="fm_move_btn">移動</string>
+		<string name="fm_moving">正在移動</string>
+		<string name="fm_chmod755_btn">chmod 755</string>
+		<string name="fm_chmod755ing">chmod 755</string>
+		<string name="fm_chmod_btn">chmod</string>
+		<string name="fm_delete_btn">刪除</string>
+		<string name="fm_deleting">正在刪除</string>
+		<string name="fm_rename_btn">重新命名</string>
+		<string name="fm_rename_file_btn">重新命名檔案</string>
+		<string name="fm_rename_folder_btn">重新命名檔案夾</string>
+		<string name="fm_renaming">正在重新命名</string>
+		<string name="fm_sel_dest">選擇目標目錄</string>
+		<string name="fm_sel_curr_folder">選擇當前目錄</string>
+		<string name="fm_rename_hdr">重新命名</string>
+		<string name="fm_set_perms_hdr">設定許可權</string>
+		<string name="fm_perms">許可權:</string>
+		<string name="fm_complete">操作完成</string>
+		<string name="decrypt_data_hdr">解密 Data 分割區</string>
+		<string name="decrypt_data_enter_pass">請輸入密碼。</string>
+		<string name="decrypt_data_failed">密碼錯誤,請重試!</string>
+		<string name="decrypt_data_failed_pattern">圖案錯誤,請重試!</string>
+		<string name="decrypt_data_enter_pattern">繪製解鎖圖案</string>
+		<string name="decrypt_data_trying">正在嘗試解密</string>
+		<string name="decrypt_data_vold_os_missing">缺少解密 vold 所需的檔案: {1}</string>
+		<string name="term_hdr">終端機指令</string>
+		<string name="term_s_hdr">終端機</string>
+		<string name="term_kill_btn">終止</string>
+		<string name="term_sel_folder_hdr">選擇執行目錄</string>
+		<string name="adb_sideload_hdr">ADB Sideload</string>
+		<string name="sideload_wipe_dalvik_chk">清除 Dalvik Cache</string>
+		<string name="sideload_wipe_cache_chk">清除 Cache</string>
+		<string name="swipe_to_sideload">滑動滑塊開始 Sideload</string>
+		<string name="swipe_sideload">   開始</string>
+		<string name="sideload_confirm">ADB Sideload</string>
+		<string name="sideload_usage">使用方式:adb sideload filename.zip</string>
+		<string name="sideload_complete">ADB Sideload 完成</string>
+		<string name="fix_contexts_hdr">修復 Context</string>
+		<string name="fix_contexts_note1">注意:很少情況下需要修復 Context。</string>
+		<string name="fix_contexts_note2">修復 SELinux Context 可能會導致</string>
+		<string name="fix_contexts_note3">您的設備無法正常啟動。</string>
+		<string name="swipe_to_fix_contexts">滑動滑塊確認修復</string>
+		<string name="swipe_fix_contexts">   修復 Context</string>
+		<string name="fixing_contexts">正在修復 Context…</string>
+		<string name="fix_contexts_complete">修復 Context 完成</string>
+		<string name="reboot_hdr">重新開機選項</string>
+		<string name="install_cancel">不,以後再說</string>
+		<string name="sel_storage_list">選擇儲存位置</string>
+		<string name="ok_btn">確定</string>
+		<string name="install_twrp_ramdisk">安裝 Recovery Ramdisk</string>
+		<string name="install_kernel">安裝 Kernel </string>
+		<string name="repack_kernel_confirm_hdr">安裝 Kernel </string>
+		<string name="repack_ramdisk_confirm_hdr">安裝 Recovery</string>
+		<string name="repack_kernel_confirm">是否安裝 Kernel ?</string>
+		<string name="repack_ramdisk_confirm">是否安裝 Recovery?</string>
+		<string name="repack_backup_first">請先備份現有的映像</string>
+		<string name="repack">重新打包</string>
+		<string name="swipe_to_install">滑動滑塊確認安裝</string>
+		<string name="installing">正在安裝…</string>
+		<string name="install_complete">安裝完成</string>
+		<string name="unpack_error">解包映像錯誤。</string>
+		<string name="repack_error">重新打包映像錯誤。</string>
+		<string name="unpacking_image">正在解包 {1}…</string>
+		<string name="repacking_image">正在打包 {1}…</string>
+		<string name="repack_image_hdr">選擇映像</string>
+		<string name="fix_recovery_loop">修復 Recovery 無限重新開機</string>
+		<string name="fix_recovery_loop_confirm">是否修復 Recovery 無限重新開機?</string>
+		<string name="fixing_recovery_loop">正在修復 Recovery 無限重新開機…</string>
+		<string name="fix_recovery_loop_complete">Recovery 無限重新開機修復完成</string>
+		<string name="fixing_recovery_loop_patch">正在對 Kernel 打補丁…</string>
+		<string name="fix_recovery_loop_patch_error">對 Kernel 打補丁時出錯。</string>
+		<string name="decrypt_users">解密使用者</string>
+		<string name="decrypt_users_selection">選擇一位要解密的使用者 ID</string>
+		<string name="select_user">選擇使用者</string>
+		<string name="backup_storage_undecrypt_warning">備份不包含一些屬於使用者 {1} 的文件,因為該使用者沒有解密</string>
+		<string name="decrypting_user_fbe">嘗試為使用者 {1} 解密 FBE…</string>
+		<string name="decrypt_user_success_fbe">使用者 {1} 解密成功</string>
+		<string name="decrypt_user_fail_fbe">使用者 {1} 解密失敗</string>
+		<string name="decrypt_data_enter_pass_fbe">請輸入使用者 [%tw_crypto_user_id%] 的密碼</string>
+		<string name="decrypt_data_enter_pattern_fbe">請繪製使用者 [%tw_crypto_user_id%] 的解鎖圖案</string>
+		<string name="multiuser_warning1">並非所有使用者都被解密!</string>
+		<string name="multiuser_warning2">備份/還原操作可能會失敗!</string>
+		<string name="multiuser_warning_accept">仍然繼續</string>
+		<string name="multiuser_warning_hdr">多使用者警告</string>
+
+		<!-- Various console messages - these consist of user displayed messages, oftentimes errors -->
+		<string name="no_kernel_selinux"> Kernel 不支援讀取 SELinux Context。</string>
+		<string name="full_selinux">完整 SELinux 支援。</string>
+		<string name="no_selinux">無 SELinux 支援(無 libselinux)。</string>
+		<string name="mtp_enabled">MTP 已啟用</string>
+		<string name="mtp_crash">MTP 已崩潰。啟動時不再載入 MTP。</string>
+		<string name="decrypt_success">已使用預設密碼解密成功。</string>
+		<string name="unable_to_decrypt">無法使用預設密碼來解密,您可能需要格式化 Data 分割區。</string>
+		<string name="generating_digest1" version="2">正在生成 Digest</string>
+		<!-- Message displayed during a backup if we are generating an Digest, ideally, leave the leading " * " to help align and separate this text from other console text -->
+		<string name="generating_digest2" version="2"> * 正在生成 Digest…</string>
+		<string name="digest_created" version="2"> * Digest 已生成。</string>
+		<string name="digest_error" version="2"> * Digest 錯誤!</string>
+		<string name="digest_compute_error" version="2"> * 計算 Digest 錯誤。</string>
+		<string name="current_date">(當前日期)</string>
+		<string name="auto_generate">(自動生成)</string>
+		<string name="unable_to_locate_partition">未找到“{1}”分割區。</string>
+		<string name="no_partition_selected">未選擇備份分割區。</string>
+		<string name="total_partitions_backup"> * 備份分割區總數: {1}</string>
+		<string name="total_backup_size"> * 備份數據總計: {1}MB</string>
+		<string name="available_space"> * 可用空間: {1}MB</string>
+		<string name="unable_locate_storage">未找到儲存設備。</string>
+		<string name="no_space">記憶體上沒有足夠的空間。</string>
+		<string name="backup_started">[已開始備份]</string>
+		<string name="backup_folder"> * 備份檔案夾: {1}</string>
+		<string name="fail_backup_folder">建立備份檔案夾失敗。</string>
+		<string name="avg_backup_fs">檔案平均備份速度: {1}MB/秒</string>
+		<string name="avg_backup_img">映像平均備份速度: {1}MB/秒</string>
+		<string name="total_backed_size">[總計備份 {1}MB]</string>
+		<string name="backup_completed">[備份已完成,耗時 {1} 秒]</string>
+		<string name="restore_started">[已開始恢復]</string>
+		<string name="restore_folder">恢復檔案夾:“{1}”</string>
+		<!-- {1} is the partition display name and {2} is the number of seconds -->
+		<string name="restore_part_done">[{1} 恢復完成({2} 秒)]</string>
+		<string name="verifying_digest" version="2">正在校驗 Digest</string>
+		<string name="skip_digest" version="2">基於使用者設定,已跳過 Digest 檢查。</string>
+		<string name="calc_restore">正在計算恢復資訊…</string>
+		<string name="restore_read_only">無法恢復 {1} -- 已掛載為只讀。</string>
+		<string name="restore_unable_locate">未找到“{1}”分割區。</string>
+		<string name="no_part_restore">未選擇要恢復的分割區。</string>
+		<string name="restore_part_count">正在恢復 {1} 個分割區…</string>
+		<string name="total_restore_size">恢復檔案總計 {1}MB</string>
+		<string name="updating_system_details">更新系統詳細資訊</string>
+		<string name="restore_completed">[恢復完成,耗時 {1} 秒]</string>
+		<!-- {1} is the path we could not open, {2} is strerror output -->
+		<string name="error_opening_strerr">打開出錯:“{1}” ({2})</string>
+		<string name="unable_locate_part_backup_name">無法透過備份名稱來尋找分割區:“{1}”</string>
+		<string name="unable_find_part_path">找不到分割區路徑“{1}”</string>
+		<string name="update_part_details">正在更新分割區資訊…</string>
+		<string name="update_part_details_done">…完成</string>
+		<string name="wiping_dalvik">正在清除 Dalvik 目錄…</string>
+		<string name="cleaned">已清除:{1}…</string>
+		<string name="cache_dalvik_done">-- Dalvik Cache 目錄清除完成!</string>
+		<string name="dalvik_done">-- Dalvik 目錄清除完成!</string>
+		<string name="no_andsec">未發現 android secure 分割區。</string>
+		<string name="unable_to_locate">未找到 {1}。</string>
+		<string name="wiping_datamedia">清除內建儲存空間 -- /data/media…</string>
+		<string name="unable_to_mount">無法掛載 {1}</string>
+		<string name="unable_to_mount_internal">無法掛載內建儲存空間</string>
+		<string name="unable_to_mount_storage">無法掛載儲存</string>
+		<string name="fail_decrypt">解密數據失敗。</string>
+		<string name="no_crypto_support">此版本不支援加密。</string>
+		<string name="decrypt_success_dev">Data 分割區成功解密,新增塊設備:“{1}”</string>
+		<string name="decrypt_success_nodev">Data 分割區已成功解密</string>
+		<string name="done">完成。</string>
+		<string name="start_partition_sd">正在給 SD 卡分割區…</string>
+		<string name="partition_sd_locate">未找到需要分割區的設備。</string>
+		<string name="ext_swap_size">EXT+Swap 大小超過 SD 卡容量。</string>
+		<string name="remove_part_table">正在刪除分割區表…</string>
+		<string name="unable_rm_part">無法刪除分割區表。</string>
+		<string name="create_part">正在建立 {1} 分割區…</string>
+		<string name="unable_to_create_part">無法建立 {1} 分割區。</string>
+		<string name="format_sdext_as">正在將 sd-ext 格式化為 {1}…</string>
+		<string name="part_complete">分割區完成。</string>
+		<string name="unable_to_open">無法打開“{1}”。</string>
+		<string name="mtp_already_enabled">MTP 已經啟用</string>
+		<string name="mtp_fail">啟用 MTP 失敗</string>
+		<string name="no_mtp">不支援 MTP</string>
+		<string name="image_flash_start">[開始刷入映像]</string>
+		<string name="img_to_flash">刷入映像:“{1}”</string>
+		<string name="flash_unable_locate">未找到“{1}”分割區。</string>
+		<string name="no_part_flash">未選擇刷入分割區。</string>
+		<string name="too_many_flash">選擇刷入的分割區太多。</string>
+		<string name="invalid_flash">指定的刷入分割區無效。</string>
+		<string name="flash_done">[映像刷入完成]</string>
+		<string name="wiping">正在清除 {1}</string>
+		<string name="repair_not_exist">{1} 不存在!無法修復!</string>
+		<string name="repairing_using">正在使用 {2} 修復 {1}…</string>
+		<string name="unable_repair">無法修復 {1}。</string>
+		<string name="mount_data_footer">無法掛載/data,未找到加密 footer。</string>
+		<!-- {1} is the folder name that we could not create, {2} is strerror output -->
+		<string name="create_folder_strerr">無法建立“{1}”檔案夾 ({2})。</string>
+		<!-- {1} is the folder name that we could not mount, {2} is strerror output -->
+		<string name="fail_mount">掛載“{1}”失敗 ({2})</string>
+		<!-- {1} is the folder name that we could not unmount, {2} is strerror output -->
+		<string name="fail_unmount">解除掛載“{1}”失敗 ({2})</string>
+		<string name="cannot_resize">無法更改 {1} 的大小。</string>
+		<string name="repair_resize">在調整大小之前修復 {1}。</string>
+		<string name="unable_resize">無法調整 {1} 的大小。</string>
+		<string name="no_digest_found" version="2">未找到“{1}”的 Digest 校驗檔案。請取消選中“啟用 Digest 校驗備份檔案”複選框。</string>
+		<string name="digest_fail_match" version="2">“{1}” Digest 校驗失敗。</string>
+		<string name="digest_matched" version="2">Digest 匹配“{1}”。</string>
+		<string name="fail_decrypt_tar">無法解密 Tar 檔案“{1}”</string>
+		<string name="format_data_msg">您可能需要重新開機 Recovery 才能使用/data。</string>
+		<string name="format_data_err">無法格式化並刪除加密。</string>
+		<string name="formatting_using">使用 {2} 格式化 {1}…</string>
+		<string name="unable_to_wipe">無法清除 {1}。</string>
+		<string name="cannot_wipe">無法清除 {1} 分割區。</string>
+		<string name="remove_all">移除“{1}”下的所有檔案</string>
+		<string name="wiping_data">正在清除 Data 分割區,跳過清除/data/media…</string>
+		<string name="backing_up">正在備份 {1}…</string>
+		<string name="backup_storage_warning">備份的 {1} 分割區不包含內建儲存空間上的任何檔案,例如照片或下載的檔案等等。</string>
+		<string name="backing">正在備份</string>
+		<string name="backup_size">“{1}”的備份檔案大小為 0 位元組。</string>
+		<string name="datamedia_fs_restore">警告:此/data備份檔案系統為 {1}!除非把檔案系統格式設定為 {1} 否則可能無法啟動。</string>
+		<string name="restoring">正在恢復 {1}…</string>
+		<string name="restoring_hdr">正在恢復</string>
+		<string name="recreate_folder_err">無法重新建立 {1} 檔案夾。</string>
+		<string name="img_size_err">映像大小大於目標設備</string>
+		<string name="flashing">正在刷入 {1}…</string>
+		<string name="backup_folder_set">備份檔案夾設定為:“{1}”</string>
+		<string name="locate_backup_err">未找到備份檔案“{1}”</string>
+		<string name="set_restore_opt">設定恢復選項:“{1}”:</string>
+		<string name="digest_check_skip" version="2">跳過校驗 Digest 已開啟</string>
+		<string name="ors_encrypt_restore_err">無法使用 OpenRecoveryScript 腳本恢復加密備份。</string>
+		<string name="mounting">正在掛載</string>
+		<string name="unmounting">正在解除掛載</string>
+		<string name="mounted">“{1}”已掛載</string>
+		<string name="unmounted">“{1}”已解除掛載</string>
+		<string name="setting">設定“{1}”為“{2}”</string>
+		<string name="setting_empty">設定“{1}”為空</string>
+		<string name="making_dir1">建立目錄</string>
+		<string name="making_dir2">建立目錄:“{1}”</string>
+		<string name="running_command">執行指令</string>
+		<string name="sideload">ADB Sideload</string>
+		<string name="start_sideload">正在開始 ADB sideload…</string>
+		<string name="need_new_adb">您需要 1.0.32 或更高版本的 adb 才可以使用。</string>
+		<string name="no_pwd">未提供密碼。</string>
+		<string name="done_ors">腳本執行完畢</string>
+		<string name="injecttwrp">正在注入 TWRP 到 Boot 映像…</string>
+		<string name="zip_err">安裝 Zip 刷機包“{1}”時出錯</string>
+		<string name="installing_zip">正在安裝 Zip 刷機包“{1}”</string>
+		<string name="select_backup_opt">設定備份選項:</string>
+		<string name="compression_on">壓縮選項已開啟</string>
+		<string name="digest_off" version="2">Digest 生成已關閉</string>
+		<string name="backup_fail">備份失敗</string>
+		<string name="backup_clean">備份失敗。正在清理備份檔案夾。</string>
+		<string name="running_recovery_commands">執行 Recovery 指令</string>
+		<string name="recovery_commands_complete">Recovery 指令執行完成</string>
+		<string name="running_ors">執行 OpenRecoveryScript 腳本</string>
+		<string name="ors_complete">執行 OpenRecoveryScript 腳本完成</string>
+		<string name="check_for_digest" version="2">正在檢測 Digest…</string>
+		<string name="invalid_zip_format">Zip 檔案格式無效!</string>
+		<string name="fail_sysmap">映射檔案“{1}”失敗</string>
+		<string name="verify_zip_sig">正在校驗 Zip 刷機包簽名…</string>
+		<string name="verify_zip_fail">刷機包簽名校驗失敗!</string>
+		<string name="verify_zip_done">刷機包簽名校驗成功。</string>
+		<string name="zip_corrupt">刷機包已損壞!</string>
+		<string name="no_digest" version="2">跳過 Digest 校驗:未找到 Digest 檔案</string>
+		<string name="digest_fail" version="2">Digest 不匹配</string>
+		<string name="digest_match" version="2">Digest 校驗成功</string>
+		<string name="pid_signal">{1} 過程結束,標誌: {2}</string>
+		<string name="pid_error">{1} 過程結束,錯誤: {2}</string>
+		<string name="install_dumlock">安裝 HTC Dumlock 到系統…</string>
+		<string name="dumlock_restore">正在恢復原廠 Boot…</string>
+		<string name="dumlock_reflash">正在重新刷入 Recovery 到 Boot…</string>
+		<string name="run_script">正在執行 {1} 腳本…</string>
+		<string name="rename_stock">重新命名/system 下原廠 Recovery 的補丁,避免原廠韌體替換 TWRP。</string>
+		<string name="split_backup">拆分備份檔案為多個壓縮檔…</string>
+		<string name="backup_error">建立備份失敗。</string>
+		<string name="restore_error">在恢復過程中發生錯誤。</string>
+		<string name="split_thread">拆分執行緒 ID {1} 到壓縮檔 {2}</string>
+		<!-- These 2 items are saved in the data manager instead of resource manager, so %llu, etc is correct instead of {1} -->
+		<string name="file_progress">已完成 %llu 個,共 %llu 個檔案,進度 %i%%</string>
+		<string name="size_progress">已完成 %lluMB,共 %lluMB,進度 %i%%</string>
+		<string name="decrypt_cmd" version="2">正在嘗試通過指令解密 Data 分割區或使用者數據。</string>
+		<string name="base_pkg_err">無法載入基本包。</string>
+		<string name="simulating">正在模擬操作…</string>
+		<string name="backup_cancel">備份已取消。</string>
+		<string name="config_twrp">正在配置 TWRP…</string>
+		<string name="config_twrp_err">此 Kernel 無法配置 TWRP。</string>
+		<string name="copy_log">已經匯出 Recovery 日誌到 {1}。</string>
+		<string name="max_queue">已達到最大刷機包刷入佇列!</string>
+		<string name="min_queue">已達到最大刷機包刷入佇列!</string>
+		<string name="screenshot_saved">截圖已儲存到:{1}</string>
+		<string name="screenshot_err">截圖失敗!</string>
+		<string name="zip_wipe_cache">一個或多個刷機包請求清除 Cache -- 正在清除 Cache。</string>
+		<string name="and_sec_wipe_err">無法清除 android secure</string>
+		<string name="dalvik_wipe_err">清除 Dalvik 失敗</string>
+		<string name="auto_gen">(Auto Generate)</string>
+		<string name="curr_date">(Current Date)</string>
+		<string name="backup_name_len">備份名稱太長。</string>
+		<string name="backup_name_invalid">備份名稱“{1}”中包含無效字元:“{1}”</string>
+		<string name="no_real_sdcard">此設備沒有真實的 SD 卡!正在中止!</string>
+		<string name="cancel_sideload">取消 ADB Sideload…</string>
+		<string name="change_fs_err">更換檔案系統時出錯。</string>
+		<string name="theme_ver_err">自訂主題和 TWRP 版本不匹配,正使用預設主題。</string>
+		<string name="up_a_level">(上級目錄)</string>
+		<string name="install_reboot" version="2">將會在 %tw_sleep% 秒內重新開機</string>
+		<string name="adbbackup_error">ADB 備份錯誤。正在退出…"</string>
+		<string name="adbbackup_control_error">無法寫入 adb 控制通道</string>
+		<string name="twrp_adbbu_option">啟用 TWRP ADB 備份需使用 --twrp 選項</string>
+		<string name="partition_not_found">分割區列表中未發現路徑: {1}</string>
+		<string name="copy_kernel_log">已匯出 Kernel 日誌至 {1}</string>
+		<string name="include_kernel_log">包含 Kernel 日誌</string>
+		<string name="sha2_chk">使用 SHA2 進行散列校驗</string>
+		<string name="unable_set_boot_slot">更改 Bootloader 啟動槽位至 {1} 錯誤</string>
+		<string name="unmount_sys_install">在安裝 Zip 刷機包之前先解除掛載 System</string>
+		<string name="unmount_system">正在解除掛載 System…</string>
+		<string name="unmount_system_err">解除掛載 System 失敗</string>
+		<string name="flash_ab_inactive">正在刷入 A/B Zip 刷機包至非活動槽位: {1}</string>
+		<string name="flash_ab_reboot">如需刷入其它的 Zip 刷機包,請重新啟動 Recovery 切換到新槽位。</string>
+		<string name="ozip_decrypt_decryption">正在解密 Ozip…</string>
+		<string name="ozip_decrypt_finish">Ozip 解密完成!</string>
+		<string name="fastboot_button">Fastboot</string>
+		<string name="enable_adb">啟用 ADB</string>
+		<string name="enable_fastboot">啟用 Fastboot</string>
+		<string name="fastboot_console_msg">進入 Fastboot 模式…</string>
+		<!-- Android Rescue Party trigger -->
+		<string name="rescue_party0">Android 救援模式已觸發!您可以嘗試下面的操作:</string>
+		<string name="rescue_party1"> 1. 清除 Cache;</string>
+		<string name="rescue_party2"> 2. 格式化 Data 分割區;</string>
+		<string name="rescue_party3"> 3. 重新刷入您的 ROM。</string>
+		<string name="rescue_party4">報告的問題為:</string>
+	</resources>
+</language>
diff --git a/gui/theme/landscape_hdpi/images/back.png b/gui/theme/landscape_hdpi/images/back.png
new file mode 100644
index 0000000..e67a88b
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/back.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/backspace.png b/gui/theme/landscape_hdpi/images/backspace.png
new file mode 100644
index 0000000..1d78402
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/backspace.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/backspace_terminal.png b/gui/theme/landscape_hdpi/images/backspace_terminal.png
new file mode 100644
index 0000000..8686f83
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/backspace_terminal.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/checkbox_false.png b/gui/theme/landscape_hdpi/images/checkbox_false.png
new file mode 100644
index 0000000..5e72143
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/checkbox_false.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/checkbox_true.png b/gui/theme/landscape_hdpi/images/checkbox_true.png
new file mode 100644
index 0000000..aacb591
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/checkbox_true.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/console.png b/gui/theme/landscape_hdpi/images/console.png
new file mode 100644
index 0000000..861a7a7
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/console.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/cursor.png b/gui/theme/landscape_hdpi/images/cursor.png
new file mode 100644
index 0000000..3513d06
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/cursor.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/enter.png b/gui/theme/landscape_hdpi/images/enter.png
new file mode 100644
index 0000000..b7154ca
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/enter.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/enter_terminal.png b/gui/theme/landscape_hdpi/images/enter_terminal.png
new file mode 100644
index 0000000..0e1bcf1
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/enter_terminal.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/file.png b/gui/theme/landscape_hdpi/images/file.png
new file mode 100644
index 0000000..f69d35f
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/file.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/folder.png b/gui/theme/landscape_hdpi/images/folder.png
new file mode 100644
index 0000000..656d845
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/folder.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/handle.png b/gui/theme/landscape_hdpi/images/handle.png
new file mode 100644
index 0000000..996426d
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/handle.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/home.png b/gui/theme/landscape_hdpi/images/home.png
new file mode 100644
index 0000000..de2eea8
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/home.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate001.png b/gui/theme/landscape_hdpi/images/indeterminate001.png
new file mode 100644
index 0000000..7582e03
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate001.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate002.png b/gui/theme/landscape_hdpi/images/indeterminate002.png
new file mode 100644
index 0000000..e205c90
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate002.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate003.png b/gui/theme/landscape_hdpi/images/indeterminate003.png
new file mode 100644
index 0000000..65c3a13
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate003.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate004.png b/gui/theme/landscape_hdpi/images/indeterminate004.png
new file mode 100644
index 0000000..7aeb1fd
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate004.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate005.png b/gui/theme/landscape_hdpi/images/indeterminate005.png
new file mode 100644
index 0000000..58cd2a4
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate005.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate006.png b/gui/theme/landscape_hdpi/images/indeterminate006.png
new file mode 100644
index 0000000..53cedf4
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate006.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate007.png b/gui/theme/landscape_hdpi/images/indeterminate007.png
new file mode 100644
index 0000000..a11e910
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate007.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate008.png b/gui/theme/landscape_hdpi/images/indeterminate008.png
new file mode 100644
index 0000000..da5b768
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate008.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate009.png b/gui/theme/landscape_hdpi/images/indeterminate009.png
new file mode 100644
index 0000000..302674a
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate009.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate010.png b/gui/theme/landscape_hdpi/images/indeterminate010.png
new file mode 100644
index 0000000..17b8531
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate010.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate011.png b/gui/theme/landscape_hdpi/images/indeterminate011.png
new file mode 100644
index 0000000..1ed0531
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate011.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/indeterminate012.png b/gui/theme/landscape_hdpi/images/indeterminate012.png
new file mode 100644
index 0000000..977da99
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/indeterminate012.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/kb_arrow_down.png b/gui/theme/landscape_hdpi/images/kb_arrow_down.png
new file mode 100644
index 0000000..65b3846
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/kb_arrow_down.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/kb_arrow_left.png b/gui/theme/landscape_hdpi/images/kb_arrow_left.png
new file mode 100644
index 0000000..a46e519
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/kb_arrow_left.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/kb_arrow_right.png b/gui/theme/landscape_hdpi/images/kb_arrow_right.png
new file mode 100644
index 0000000..3c1a59f
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/kb_arrow_right.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/kb_arrow_up.png b/gui/theme/landscape_hdpi/images/kb_arrow_up.png
new file mode 100644
index 0000000..e5228c3
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/kb_arrow_up.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/kb_hide.png b/gui/theme/landscape_hdpi/images/kb_hide.png
new file mode 100644
index 0000000..88974cc
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/kb_hide.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/kb_show.png b/gui/theme/landscape_hdpi/images/kb_show.png
new file mode 100644
index 0000000..3c09214
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/kb_show.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/logo.png b/gui/theme/landscape_hdpi/images/logo.png
new file mode 100644
index 0000000..a8f2254
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/logo.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/main_button.png b/gui/theme/landscape_hdpi/images/main_button.png
new file mode 100644
index 0000000..1b491ae
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/main_button.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/main_button_half_width.png b/gui/theme/landscape_hdpi/images/main_button_half_width.png
new file mode 100644
index 0000000..e9a47ce
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/main_button_half_width.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/main_button_half_width_low.png b/gui/theme/landscape_hdpi/images/main_button_half_width_low.png
new file mode 100644
index 0000000..cb7e1df
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/main_button_half_width_low.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/progress_empty.png b/gui/theme/landscape_hdpi/images/progress_empty.png
new file mode 100644
index 0000000..4e35f3d
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/progress_empty.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/progress_fill.png b/gui/theme/landscape_hdpi/images/progress_fill.png
new file mode 100644
index 0000000..1046598
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/progress_fill.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/radio_false.png b/gui/theme/landscape_hdpi/images/radio_false.png
new file mode 100644
index 0000000..cc7c270
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/radio_false.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/radio_true.png b/gui/theme/landscape_hdpi/images/radio_true.png
new file mode 100644
index 0000000..3e041c3
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/radio_true.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/shift.png b/gui/theme/landscape_hdpi/images/shift.png
new file mode 100644
index 0000000..5b3e908
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/shift.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/shift_fill.png b/gui/theme/landscape_hdpi/images/shift_fill.png
new file mode 100644
index 0000000..5c10629
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/shift_fill.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/shift_terminal.png b/gui/theme/landscape_hdpi/images/shift_terminal.png
new file mode 100644
index 0000000..49ebdb3
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/shift_terminal.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/shift_terminal_fill.png b/gui/theme/landscape_hdpi/images/shift_terminal_fill.png
new file mode 100644
index 0000000..4f98aaa
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/shift_terminal_fill.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/slider.png b/gui/theme/landscape_hdpi/images/slider.png
new file mode 100644
index 0000000..2921679
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/slider.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/slider_touch.png b/gui/theme/landscape_hdpi/images/slider_touch.png
new file mode 100644
index 0000000..63dd52f
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/slider_touch.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/slider_used.png b/gui/theme/landscape_hdpi/images/slider_used.png
new file mode 100644
index 0000000..355c580
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/slider_used.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/sort_asc.png b/gui/theme/landscape_hdpi/images/sort_asc.png
new file mode 100644
index 0000000..28e0f9d
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/sort_asc.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/sort_desc.png b/gui/theme/landscape_hdpi/images/sort_desc.png
new file mode 100644
index 0000000..71d8695
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/sort_desc.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/sort_empty.png b/gui/theme/landscape_hdpi/images/sort_empty.png
new file mode 100644
index 0000000..bf6e0a5
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/sort_empty.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/space.png b/gui/theme/landscape_hdpi/images/space.png
new file mode 100644
index 0000000..dabbfec
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/space.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/space_terminal.png b/gui/theme/landscape_hdpi/images/space_terminal.png
new file mode 100644
index 0000000..6772563
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/space_terminal.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/splashlogo.png b/gui/theme/landscape_hdpi/images/splashlogo.png
new file mode 100644
index 0000000..42f516c
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/splashlogo.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/splashteamwin.png b/gui/theme/landscape_hdpi/images/splashteamwin.png
new file mode 100644
index 0000000..16a6d59
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/splashteamwin.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/tab_3.png b/gui/theme/landscape_hdpi/images/tab_3.png
new file mode 100644
index 0000000..25997fc
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/tab_3.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/tab_4.png b/gui/theme/landscape_hdpi/images/tab_4.png
new file mode 100644
index 0000000..fd2eee4
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/tab_4.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/images/unlock_icon.png b/gui/theme/landscape_hdpi/images/unlock_icon.png
new file mode 100644
index 0000000..5865e93
--- /dev/null
+++ b/gui/theme/landscape_hdpi/images/unlock_icon.png
Binary files differ
diff --git a/gui/theme/landscape_hdpi/splash.xml b/gui/theme/landscape_hdpi/splash.xml
new file mode 100644
index 0000000..56391bd
--- /dev/null
+++ b/gui/theme/landscape_hdpi/splash.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<recovery>
+	<details>
+		<resolution width="1920" height="1200"/>
+		<author>TeamWin</author>
+		<title>TWRP</title>
+		<description>splash screen</description>
+		<themeversion>{themeversion}</themeversion>
+	</details>
+
+	<resources>
+		<font name="font_l" filename="RobotoCondensed-Regular.ttf" size="52"/>
+		<image name="splashlogo" filename="splashlogo" retainaspect="1"/>
+		<image name="splashteamwin" filename="splashteamwin" retainaspect="1"/>
+	</resources>
+
+	<variables>
+		<variable name="screen_width" value="1920"/>
+		<variable name="screen_height" value="1200"/>
+		<variable name="background_color" value="#222222"/>
+		<variable name="header_color" value="#555555"/>
+		<variable name="accent_color" value="#0090CA"/>
+	</variables>
+
+	<pages>
+		<page name="splash">
+			<background color="%background_color%"/>
+
+			<fill color="%header_color%">
+				<placement x="0" y="0" w="%screen_width%" h="320"/>
+			</fill>
+
+			<image>
+				<image resource="splashlogo"/>
+				<placement x="960" y="320" placement="4"/>
+			</image>
+
+			<image>
+				<image resource="splashteamwin"/>
+				<placement x="960" y="920" placement="4"/>
+			</image>
+
+			<text color="%header_color%">
+				<font resource="font_l"/>
+				<placement x="960" y="970" placement="5"/>
+				<text>Recovery Project %tw_version%</text>
+			</text>
+		</page>
+	</pages>
+</recovery>
+
diff --git a/gui/theme/landscape_hdpi/ui.xml b/gui/theme/landscape_hdpi/ui.xml
new file mode 100755
index 0000000..a71387f
--- /dev/null
+++ b/gui/theme/landscape_hdpi/ui.xml
@@ -0,0 +1,807 @@
+<?xml version="1.0"?>
+<recovery>
+	<details>
+		<resolution width="1920" height="1200"/>
+		<author>TeamWin</author>
+		<title>Backup Naowz</title>
+		<description>Default basic theme</description>
+		<preview>preview.png</preview>
+		<themeversion>{themeversion}</themeversion>
+	</details>
+
+	<include>
+		<xmlfile name="landscape.xml"/>
+	</include>
+
+	<resources>
+		<font name="font_l" filename="RobotoCondensed-Regular.ttf" size="42"/>
+		<font name="font_m" filename="RobotoCondensed-Regular.ttf" size="32"/>
+		<font name="font_s" filename="RobotoCondensed-Regular.ttf" size="28"/>
+		<font name="fixed" filename="DroidSansMono.ttf" size="26"/>
+
+		<font name="keylabel" filename="RobotoCondensed-Regular.ttf" size="50"/>
+		<font name="keylabel-small" filename="RobotoCondensed-Regular.ttf" size="42"/>
+		<font name="keylabel-longpress" filename="RobotoCondensed-Regular.ttf" size="26"/>
+
+		<image name="logo" filename="logo" retainaspect="1"/>
+		<image name="main_button" filename="main_button"/>
+		<image name="main_button_half_width" filename="main_button_half_width"/>
+		<image name="main_button_half_width_low" filename="main_button_half_width_low"/>
+		<image name="tab_3" filename="tab_3"/>
+		<image name="tab_4" filename="tab_4"/>
+		<image name="file" filename="file" retainaspect="1"/>
+		<image name="folder" filename="folder" retainaspect="1"/>
+		<image name="unlock_icon" filename="unlock_icon" retainaspect="1"/>
+		<image name="home" filename="home" retainaspect="1"/>
+		<image name="back" filename="back" retainaspect="1"/>
+		<image name="console" filename="console" retainaspect="1"/>
+		<image name="kb_hide" filename="kb_hide" retainaspect="1"/>
+		<image name="kb_show" filename="kb_show" retainaspect="1"/>
+		<image name="checkbox_false" filename="checkbox_false" retainaspect="1"/>
+		<image name="checkbox_true" filename="checkbox_true" retainaspect="1"/>
+		<image name="radio_false" filename="radio_false" retainaspect="1"/>
+		<image name="radio_true" filename="radio_true" retainaspect="1"/>
+		<image name="sort_asc" filename="sort_asc"/>
+		<image name="sort_desc" filename="sort_desc"/>
+		<image name="sort_empty" filename="sort_empty"/>
+		<animation name="progress" filename="indeterminate"/>
+		<image name="progress_empty" filename="progress_empty"/>
+		<image name="progress_full" filename="progress_fill"/>
+		<image name="slider" filename="slider"/>
+		<image name="slider_used" filename="slider_used"/>
+		<image name="slider_touch" filename="slider_touch"/>
+		<image name="handle" filename="handle"/>
+		<image name="cursor" filename="cursor" retainaspect="1"/>
+
+		<image name="backspace" filename="backspace" retainaspect="1"/>
+		<image name="backspace_terminal" filename="backspace_terminal" retainaspect="1"/>
+		<image name="enter" filename="enter" retainaspect="1"/>
+		<image name="enter_terminal" filename="enter_terminal" retainaspect="1"/>
+		<image name="shift" filename="shift" retainaspect="1"/>
+		<image name="shift_terminal" filename="shift_terminal" retainaspect="1"/>
+		<image name="shift_fill" filename="shift_fill" retainaspect="1"/>
+		<image name="shift_terminal_fill" filename="shift_terminal_fill" retainaspect="1"/>
+		<image name="space" filename="space" retainaspect="1"/>
+		<image name="space_terminal" filename="space_terminal" retainaspect="1"/>
+		<image name="kb_arrow_left" filename="kb_arrow_left" retainaspect="1"/>
+		<image name="kb_arrow_right" filename="kb_arrow_right" retainaspect="1"/>
+		<image name="kb_arrow_up" filename="kb_arrow_up" retainaspect="1"/>
+		<image name="kb_arrow_down" filename="kb_arrow_down" retainaspect="1"/>
+	</resources>
+
+	<variables>
+		<variable name="tw_samsung_navbar" value="0" persist="1"/>
+		<variable name="tw_navbar_button_position" value="0" persist="1"/>
+
+		<variable name="tw_hide_kb" value="0"/>
+		<variable name="screen_width" value="1920"/>
+		<variable name="screen_height" value="1200"/>
+		<variable name="status_height" value="48"/>
+		<variable name="header_height" value="192"/>
+		<variable name="navbar_height" value="96"/>
+		<variable name="content_width" value="1824"/>
+		<variable name="content_quarter_width" value="1416"/>
+		<variable name="content_half_width" value="888"/>
+		<variable name="content_overlay_width" value="912"/>
+		<variable name="dialog_height" value="528"/>
+		<variable name="dialog_width" value="960"/>
+		<variable name="dialog_content_x" value="528"/>
+		<variable name="back_button_x" value="633"/>
+		<variable name="console_button_x" value="1287"/>
+		<variable name="back_button_x_1" value="120"/>
+		<variable name="home_button_x_1" value="320"/>
+		<variable name="console_button_x_1" value="520"/>
+		<variable name="back_button_x_2" value="1400"/>
+		<variable name="home_button_x_2" value="1600"/>
+		<variable name="console_button_x_2" value="1800"/>
+		<variable name="col1_x_header" value="144"/>
+		<variable name="indent" value="24"/>
+		<variable name="col1_x_left" value="48"/>
+		<variable name="col2_x_left" value="504"/>
+		<variable name="center_x" value="960"/>
+		<variable name="col1_x_right" value="984"/>
+		<variable name="col2_x_right" value="1416"/>
+		<variable name="col_button_right" value="1488"/>
+		<variable name="indent_right" value="1896"/>
+		<variable name="tab_height" value="72"/>
+		<variable name="tab_indicator_height" value="4"/>
+		<variable name="tab4_width" value="480"/>
+		<variable name="tab5_width" value="384"/>
+		<variable name="tab4_col2_x" value="480"/>
+		<variable name="tab4_col4_x" value="1440"/>
+		<variable name="tab5_col2_x" value="384"/>
+		<variable name="tab5_col3_x" value="768"/>
+		<variable name="tab5_col4_x" value="1152"/>
+		<variable name="tab5_col5_x" value="1536"/>
+		<variable name="btn4_col2_x_left" value="282"/>
+		<variable name="btn4_col3_x_left" value="516"/>
+		<variable name="btn4_col4_x_left" value="750"/>
+		<variable name="btn4_col2_x_right" value="1218"/>
+		<variable name="btn4_col3_x_right" value="1452"/>
+		<variable name="btn4_col4_x_right" value="1686"/>
+		<variable name="status_topalign_header_y" value="0"/>
+		<variable name="status_centeralign_header_y" value="7"/>
+		<variable name="status_bottomalign_header_y" value="14"/>
+		<variable name="row1_header_y" value="{statusicons_align}"/>
+		<variable name="row2_header_y" value="48"/>
+		<variable name="row3_header_y" value="60"/>
+		<variable name="row4_header_y" value="126"/>
+		<variable name="row1_y" value="192"/>
+		<variable name="row1a_y" value="216"/>
+		<variable name="row2_y" value="240"/>
+		<variable name="row2a_y" value="264"/>
+		<variable name="row_tab_y" value="260"/>
+		<variable name="row2_input_y" value="286"/>
+		<variable name="row3_y" value="288"/>
+		<variable name="row3a_y" value="312"/>
+		<variable name="row3_input_y" value="334"/>
+		<variable name="row4_y" value="336"/>
+		<variable name="row4a_y" value="360"/>
+		<variable name="row4_input_y" value="382"/>
+		<variable name="row5_y" value="384"/>
+		<variable name="row5a_y" value="408"/>
+		<variable name="row6_y" value="432"/>
+		<variable name="row6a_y" value="456"/>
+		<variable name="row7_y" value="480"/>
+		<variable name="row7a_y" value="504"/>
+		<variable name="row8_y" value="528"/>
+		<variable name="row8a_y" value="552"/>
+		<variable name="row9_y" value="576"/>
+		<variable name="row9a_y" value="600"/>
+		<variable name="row10_y" value="624"/>
+		<variable name="row10a_y" value="648"/>
+		<variable name="row11_y" value="672"/>
+		<variable name="row11a_y" value="696"/>
+		<variable name="row12_y" value="720"/>
+		<variable name="row12a_y" value="744"/>
+		<variable name="row13_y" value="768"/>
+		<variable name="row13a_y" value="792"/>
+		<variable name="row14_y" value="816"/>
+		<variable name="row14a_y" value="840"/>
+		<variable name="row15_y" value="864"/>
+		<variable name="row15a_y" value="888"/>
+		<variable name="row16_y" value="912"/>
+		<variable name="row16a_y" value="936"/>
+		<variable name="row17_y" value="960"/>
+		<variable name="row17a_y" value="984"/>
+		<variable name="row18_y" value="1008"/>
+		<variable name="row18a_y" value="1032"/>
+		<variable name="row19_y" value="1056"/>
+		<variable name="row19a_y" value="1080"/>
+		<variable name="row20_y" value="1104"/>
+		<variable name="navbar_y" value="1104"/>
+		<variable name="navbar_btn_y" value="1152"/>
+		<variable name="keyboard_y" value="592"/>
+		<variable name="background_color" value="#1A1A1A"/>
+		<variable name="accent_color" value="#0090CA"/>
+		<variable name="text_color" value="#EEEEEE"/>
+		<variable name="text_button_color" value="#EEEEEE"/>
+		<variable name="text_success_color" value="#76FF03"/>
+		<variable name="text_fail_color" value="#FF0101"/>
+		<variable name="highlight_color" value="#1A1A1A80"/>
+		<variable name="caps_highlight_color" value="#22222280"/>
+		<variable name="transparent" value="#00000000"/>
+		<variable name="semi_transparent" value="#00000099"/>
+		<variable name="warning" value="#F8F8A0"/>
+		<variable name="error" value="#FF0101"/>
+		<variable name="highlight" value="#0090CA"/>
+		<variable name="fileselector_linecolor" value="#555555"/>
+		<variable name="fileselector_highlight_color" value="#555555"/>
+		<variable name="fileselector_separatorheight" value="2"/>
+		<variable name="fileselector_spacing" value="0"/>
+		<variable name="fileselector_install_height" value="840"/>
+		<variable name="fileselector_install_width" value="960"/>
+		<variable name="fileselector_install_folder_width" value="408"/>
+		<variable name="fileselector_filemanager_height" value="840"/>
+		<variable name="partitionlist_spacing" value="0"/>
+		<variable name="partitionlist_storage_height" value="400"/>
+		<variable name="partitionlist_flashimage_height" value="448"/>
+		<variable name="partitionlist_wipe_height" value="600"/>
+		<variable name="partitionlist_mount_height" value="552"/>
+		<variable name="partitionlist_backup_height" value="528"/>
+		<variable name="listbox_timezone_height" value="768"/>
+		<variable name="listbox_settings_height" value="648"/>
+		<variable name="listbox_advanced_height" value="700"/>
+		<variable name="listbox_options_height" value="304"/>
+		<variable name="fastscroll_w" value="18"/>
+		<variable name="fastscroll_linew" value="2"/>
+		<variable name="fastscroll_rectw" value="18"/>
+		<variable name="fastscroll_recth" value="96"/>
+		<variable name="slidervalue_lineh" value="3"/>
+		<variable name="slidervalue_padding" value="0"/>
+		<variable name="slidervalue_sliderw" value="72"/>
+		<variable name="slidervalue_sliderh" value="96"/>
+		<variable name="slideout_y" value="96"/>
+		<variable name="slideout_height" value="960"/>
+		<variable name="slideout_bg_height" value="1056"/>
+		<variable name="input_height" value="48"/>
+		<variable name="input_line_width" value="3"/>
+		<variable name="console_height" value="624"/>
+		<variable name="console_terminal_height" value="420"/>
+		<variable name="terminal_s_height" value="520"/>
+		<variable name="terminal_l_height" value="1032"/>
+		<variable name="dialog_button_x" value="1176"/>
+		<variable name="date_button_x" value="552"/>
+		<variable name="progress_x" value="552"/>
+		<variable name="progress_text_x" value="96"/>
+		<variable name="progress_text_y" value="900"/>
+		<variable name="pattern_x" value="180"/>
+		<variable name="pattern_dot_dia" value="32"/>
+		<variable name="pattern_line_w" value="12"/>
+		<variable name="pattern_size" value="648"/>
+		<variable name="tw_clock_12_pos_x" value="{clock_12_pos}"/>
+		<variable name="tw_clock_24_pos_x" value="{clock_24_pos}"/>
+		<variable name="tw_cpu_pos_x" value="{cpu_pos}"/>
+		<variable name="tw_battery_pos_x" value="{battery_pos}"/>
+	</variables>
+
+	<mousecursor>
+		<placement w="15" h="15"/>
+		<background color="#FFFF00FF" resource="cursor"/>
+		<speed multiplier="2.5"/>
+	</mousecursor>
+
+	<templates>
+		<template name="page">
+			<background color="%background_color%"/>
+
+			<fill color="%accent_color%">
+				<placement x="0" y="0" w="%screen_width%" h="%header_height%"/>
+			</fill>
+
+			<image>
+				<condition var1="tw_busy" var2="1"/>
+				<image resource="logo"/>
+				<placement x="0" y="0"/>
+			</image>
+
+			<button>
+				<condition var1="tw_busy" var2="0"/>
+				<placement x="0" y="0"/>
+				<image resource="logo"/>
+				<action function="key">home</action>
+			</button>
+
+			<fill color="#00000030">
+				<condition var1="tw_simulate_actions" var2="0"/>
+				<placement x="0" y="0" w="%screen_width%" h="%status_height%"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_simulate_actions" var2="1"/>
+				<placement x="0" y="0" w="%screen_width%" h="%status_height%"/>
+			</fill>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="1"/>
+				<condition var1="tw_cpu_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row1_header_y%"/>
+				<text>%tw_version%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<condition var1="tw_cpu_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row1_header_y%"/>
+				<text>{@cpu_temp=CPU: %tw_cpu_temp% °C}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_12_pos_x" var2="0"/>
+				<condition var1="tw_clock_24_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%center_x%" y="%row1_header_y%" placement="5"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<conditions>
+					<condition var1="tw_no_battery_percent" var2="0"/>
+					<condition var1="tw_battery" op="&gt;" var2="0"/>
+					<condition var1="tw_battery" op="&lt;" var2="101"/>
+					<condition var1="tw_battery_pos_x" var2="0"/>
+				</conditions>
+				<font resource="font_m"/>
+				<placement x="%indent_right%" y="%row1_header_y%" placement="1"/>
+				<text>{@battery_pct=Battery: %tw_battery%}</text>
+			</text>
+
+			<!-- Custom position for status bar items if defined START -->
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="1"/>
+				<condition var1="tw_cpu_pos_x" op="!=" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_cpu_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_version%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<condition var1="tw_cpu_pos_x" op="!=" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_cpu_pos_x%" y="%row1_header_y%"/>
+				<text>{@cpu_temp=CPU: %tw_cpu_temp% °C}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_12_pos_x" op="!=" var2="0"/>
+				<condition var1="tw_military_time" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_clock_12_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_24_pos_x" op="!=" var2="0"/>
+				<condition var1="tw_military_time" var2="1"/>
+				<font resource="font_m"/>
+				<placement x="%tw_clock_24_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<conditions>
+					<condition var1="tw_no_battery_percent" var2="0"/>
+					<condition var1="tw_battery" op="&gt;" var2="0"/>
+					<condition var1="tw_battery" op="&lt;" var2="101"/>
+					<condition var1="tw_battery_pos_x" op="!=" var2="0"/>
+				</conditions>
+				<font resource="font_m"/>
+				<placement x="%tw_battery_pos_x%" y="%row1_header_y%"/>
+				<text>{@battery_pct=Battery: %tw_battery%}</text>
+			</text>
+
+			<!-- Custom position for status bar items if defined END -->
+
+			<fill color="#000000">
+				<condition var1="tw_busy" var2="0"/>
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%"/>
+			</fill>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0"/>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">home</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1"/>
+				<placement x="%home_button_x_1%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">home</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2"/>
+				<placement x="%home_button_x_2%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">home</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</template>
+
+		<template name="progress_bar">
+			<progressbar>
+				<placement x="%progress_x%" y="%row16_y%"/>
+				<resource empty="progress_empty" full="progress_full"/>
+				<data name="ui_progress"/>
+			</progressbar>
+
+			<animation>
+				<placement x="%progress_x%" y="%row16_y%"/>
+				<resource name="progress"/>
+				<speed fps="24" render="2"/>
+				<loop frame="1"/>
+			</animation>
+		</template>
+
+		<template name="sort_options">
+			<image>
+				<placement x="%col_button_right%" y="%row1a_y%"/>
+				<image resource="main_button_half_width_low"/>
+			</image>
+
+			<image>
+				<placement x="%col_button_right%" y="%row4a_y%"/>
+				<image resource="main_button_half_width_low"/>
+			</image>
+
+			<image>
+				<placement x="%col_button_right%" y="%row7a_y%"/>
+				<image resource="main_button_half_width_low"/>
+			</image>
+
+			<button style="sort_asc">
+				<condition var1="tw_gui_sort_order" op="=" var2="1"/>
+				<placement x="%col_button_right%" y="%row1a_y%"/>
+				<text>{@sort_by_name=Sort by Name}</text>
+				<action function="set">tw_gui_sort_order=-1</action>
+			</button>
+
+			<button style="sort_desc">
+				<condition var1="tw_gui_sort_order" op="=" var2="-1"/>
+				<placement x="%col_button_right%" y="%row1a_y%"/>
+				<text>{@sort_by_name=Sort by Name}</text>
+				<action function="set">tw_gui_sort_order=1</action>
+			</button>
+
+			<button style="sort_empty">
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="1"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-1"/>
+				</conditions>
+				<placement x="%col_button_right%" y="%row1a_y%"/>
+				<text>{@sort_by_name=Sort by Name}</text>
+				<action function="set">tw_gui_sort_order=1</action>
+			</button>
+
+			<button style="sort_asc">
+				<condition var1="tw_gui_sort_order" op="=" var2="2"/>
+				<placement x="%col_button_right%" y="%row4a_y%"/>
+				<text>{@sort_by_date=Sort by Date}</text>
+				<action function="set">tw_gui_sort_order=-2</action>
+			</button>
+
+			<button style="sort_desc">
+				<condition var1="tw_gui_sort_order" op="=" var2="-2"/>
+				<placement x="%col_button_right%" y="%row4a_y%"/>
+				<text>{@sort_by_date=Sort by Date}</text>
+				<action function="set">tw_gui_sort_order=2</action>
+			</button>
+
+			<button style="sort_empty">
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="2"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-2"/>
+				</conditions>
+				<placement x="%col_button_right%" y="%row4a_y%"/>
+				<text>{@sort_by_date=Sort by Date}</text>
+				<action function="set">tw_gui_sort_order=2</action>
+			</button>
+
+			<button style="sort_asc">
+				<condition var1="tw_gui_sort_order" op="=" var2="3"/>
+				<placement x="%col_button_right%" y="%row7a_y%"/>
+				<text>{@sort_by_size=Sort by Size}</text>
+				<action function="set">tw_gui_sort_order=-3</action>
+			</button>
+
+			<button style="sort_desc">
+				<condition var1="tw_gui_sort_order" op="=" var2="-3"/>
+				<placement x="%col_button_right%" y="%row7a_y%"/>
+				<text>{@sort_by_size=Sort by Size}</text>
+				<action function="set">tw_gui_sort_order=3</action>
+			</button>
+
+			<button style="sort_empty">
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="3"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-3"/>
+				</conditions>
+				<placement x="%col_button_right%" y="%row7a_y%"/>
+				<text>{@sort_by_size=Sort by Size}</text>
+				<action function="set">tw_gui_sort_order=3</action>
+			</button>
+		</template>
+
+		<template name="tabs_settings">
+			<button style="tab">
+				<placement x="0" y="%row1_y%" w="%tab5_width%" h="%tab_height%"/>
+				<text>{@tab_general=GENERAL}</text>
+				<action function="page">settings</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_col2_x%" y="%row1_y%" w="%tab5_width%" h="%tab_height%"/>
+				<text>{@tab_time_zone=TIME ZONE}</text>
+				<action function="page">settings_timezone</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_col3_x%" y="%row1_y%" w="%tab5_width%" h="%tab_height%"/>
+				<text>{@tab_screen=SCREEN}</text>
+				<action function="page">settings_screen</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_col4_x%" y="%row1_y%" w="%tab5_width%" h="%tab_height%"/>
+				<text>{@tab_vibration=VIBRATION}</text>
+				<action function="page">settings_vibration</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_col5_x%" y="%row1_y%" w="%tab5_width%" h="%tab_height%"/>
+				<text>{@tab_language=LANGUAGE}</text>
+				<action function="page">settings_language</action>
+			</button>
+		</template>
+
+		<template name="console">
+			<console>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%console_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_left%" y="row2_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_left%" y="row15_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+		</template>
+
+		<template name="console_terminal">
+			<console>
+				<placement x="%col1_x_left%" y="%row3_header_y%" w="content_width" h="%console_terminal_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_left%" y="%row3_header_y%" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_left%" y="row7_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+		</template>
+
+		<template name="keyboardterminaltemplate">
+			<keyboard>
+				<condition var1="tw_hide_kb" op="=" var2="0"/>
+				<placement x="0" y="%keyboard_y%" w="1920" h="512"/>
+				<keymargin x="6" y="6"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="40" y="4"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace_terminal"/>
+				<keylabel key="0:action" text="Enter" resource="enter_terminal"/>
+				<keylabel key=" " text="Space" resource="space_terminal"/>
+				<keylabel key="0:k:29" text="Ctrl"/>
+				<keylabel key="0:c:27" text="Esc"/>
+				<keylabel key="0:c:9" text="Tab"/>
+				<keylabel key="0:k:105" text="&lt;" resource="kb_arrow_left"/>
+				<keylabel key="0:k:108" text="v" resource="kb_arrow_down"/>
+				<keylabel key="0:k:103" text="^" resource="kb_arrow_up"/>
+				<keylabel key="0:k:106" text="&gt;" resource="kb_arrow_right"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<ctrlhighlight color="#0090CA80"/>
+				<layout1>
+					<keysize height="128" width="160"/>
+					<row1 key01="160:c:27" long01=":c:3" key02="q" long02="1" key03="w" long03="2" key04="e" long04="3" key05="r" long05="4" key06="t" long06="5" key07="y" long07="6" key08="u" long08="7" key09="i" long09="8" key10="o" long10="9" key11="p" long11="0" key12="160:k:103"/>
+					<row2 key01="160:c:9" key02="80:" key03="a" long03="@" key04="s" long04="#" key05="d" long05="$" key06="f" long06="%" key07="g" long07="&amp;" key08="h" long08="*" key09="j" long09="-" key10="k" long10="+" key11="l" long11="_" key12="80:" key13="160:k:105"/>
+					<row3 key01="160:" key02="240:layout2" key03="z" long03="!" key04="x" long04="&quot;" key05="c" long05="'" key06="v" long06=":" key07="b" long07=";" key08="n" long08="/" key09="m" long09="?" key10="240:c:8" key11="160:k:106"/>
+					<row4 key01="160:k:29" key02="240:layout3" key03="/" key04="800: " key05="." key06="240:action" key07="160:k:108"/>
+					<keylabel key="0:layout2" text="Shift" resource="shift_terminal"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout1>
+				<layout2>
+					<keysize height="128" width="160" capslock="0" revert_layout="1"/>
+					<row1 key01="160:c:27" long01=":c:3" key02="Q" long02="1" key03="W" long03="2" key04="E" long04="3" key05="R" long05="4" key06="T" long06="5" key07="Y" long07="6" key08="U" long08="7" key09="I" long09="8" key10="O" long10="9" key11="P" long11="0" key12="160:k:103"/>
+					<row2 key01="160:c:9" key02="80:" key03="A" long03="@" key04="S" long04="#" key05="D" long05="$" key06="F" long06="%" key07="G" long07="&amp;" key08="H" long08="*" key09="J" long09="-" key10="K" long10="+" key11="L" long11="_" key12="80:" key13="160:k:105"/>
+					<row3 key01="160:" key02="240:layout1" key03="Z" long03="!" key04="X" long04="&quot;" key05="C" long05="'" key06="V" long06=":" key07="B" long07=";" key08="N" long08="/" key09="M" long09="?" key10="240:c:8" key11="160:k:106"/>
+					<row4 key01="160:k:29" key02="240:layout3" key03="/" key04="800: " key05="." key06="240:action" key07="160:k:108"/>
+					<keylabel key="0:layout1" text="Shift" resource="shift_terminal_fill"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout2>
+				<layout3>
+					<keysize height="128" width="160"/>
+					<row1 key01="160:c:27" long01=":c:3" key02="1" key03="2" key04="3" key05="4" key06="5" key07="6" key08="7" key09="8" key10="9" key11="0" key12="160:k:103"/>
+					<row2 key01="160:c:9" key02="@" key03="#" key04="$" key05="%" key06="&amp;" key07="*" key08="-" key09="+" key10="(" key11=")" key12="160:k:105"/>
+					<row3 key01="160:" key02="240:layout4" key03="!" key04="160:c:34" key05="'" key06=":" key07=";" key08="/" key09="?" key10="240:c:8" key11="160:k:106"/>
+					<row4 key01="160:k:29" key02="240:layout1" key03="," key04="800: " key05="." key06="240:action" key07="160:k:108"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout4" text="~\{"/>
+				</layout3>
+				<layout4>
+					<keysize height="128" width="160"/>
+					<row1 key01="160:c:27" long01=":c:3" key02="~" key03="`" key04="|" key05="160:" key06="160:" key07="160:" key08="%" key09="160:" key10="{" key11="}" key12="160:k:103"/>
+					<row2 key01="160:c:9" key02="160:" key03="160:" key04="160:" key05="160:" key06="160:" key07="^" key08="_" key09="=" key10="[" key11="]" key12="160:k:105"/>
+					<row3 key01="160:" key02="240:layout3" key03="160:" key04="160:" key05="160:" key06="160:" key07="\" key08="&lt;" key09="&gt;" key10="240:c:8" key11="160:k:106"/>
+					<row4 key01="160:k:29" key02="240:layout1" key03="160:c:34" key04="800: " key05="." key06="240:action" key07="160:k:108"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout4>
+			</keyboard>
+		</template>
+
+		<template name="keyboardtemplate">
+			<keyboard>
+				<placement x="0" y="%keyboard_y%" w="1920" h="512"/>
+				<keymargin x="6" y="6"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="40" y="4"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace"/>
+				<keylabel key="0:action" text="Enter" resource="enter"/>
+				<keylabel key=" " text="Space" resource="space"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<layout1>
+					<keysize height="128" width="192"/>
+					<row1 key01="q" long01="1" key02="w" long02="2" key03="e" long03="3" key04="r" long04="4" key05="t" long05="5" key06="y" long06="6" key07="u" long07="7" key08="i" long08="8" key09="o" long09="9" key10="p" long10="0"/>
+					<row2 key01="96:" key02="a" long02="@" key03="s" long03="#" key04="d" long04="$" key05="f" long05="%" key06="g" long06="&amp;" key07="h" long07="*" key08="j" long08="-" key09="k" long09="+" key10="l" long10="_" key11="96:"/>
+					<row3 key01="288:layout2" key02="z" long02="!" key03="x" long03="&quot;" key04="c" long04="'" key05="v" long05=":" key06="b" long06=";" key07="n" long07="/" key08="m" long08="?" key09="288:c:8"/>
+					<row4 key01="288:layout3" key02="/" key03="960: " key04="." key05="288:action"/>
+					<keylabel key="0:layout2" text="Shift" resource="shift"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout1>
+				<layout2>
+					<keysize height="128" width="192" capslock="0" revert_layout="1"/>
+					<row1 key01="Q" long01="1" key02="W" long02="2" key03="E" long03="3" key04="R" long04="4" key05="T" long05="5" key06="Y" long06="6" key07="U" long07="7" key08="I" long08="8" key09="O" long09="9" key10="P" long10="0"/>
+					<row2 key01="96:" key02="A" long02="@" key03="S" long03="#" key04="D" long04="$" key05="F" long05="%" key06="G" long06="&amp;" key07="H" long07="*" key08="J" long08="-" key09="K" long09="+" key10="L" long10="_" key11="96:"/>
+					<row3 key01="288:layout1" key02="Z" long02="!" key03="X" long03="&quot;" key04="C" long04="'" key05="V" long05=":" key06="B" long06=";" key07="N" long07="/" key08="M" long08="?" key09="288:c:8"/>
+					<row4 key01="288:layout3" key02="/" key03="960: " key04="." key05="288:action"/>
+					<keylabel key="0:layout1" text="Shift" resource="shift_fill"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout2>
+				<layout3>
+					<keysize height="128" width="192"/>
+					<row1 key01="1" key02="2" key03="3" key04="4" key05="5" key06="6" key07="7" key08="8" key09="9" key10="0"/>
+					<row2 key01="@" key02="#" key03="$" key04="%" key05="&amp;" key06="*" key07="-" key08="+" key09="(" key10=")"/>
+					<row3 key01="288:layout4" key02="!" key03="192:c:34" key04="'" key05=":" key06=";" key07="/" key08="?" key09="288:c:8"/>
+					<row4 key01="288:layout1" key02="," key03="960: " key04="." key05="288:action"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout4" text="~\{"/>
+				</layout3>
+				<layout4>
+					<keysize height="128" width="192"/>
+					<row1 key01="~" key02="`" key03="|" key04="192:" key05="192:" key06="192:" key07="%" key08="192:" key09="{" key10="}"/>
+					<row2 key01="192:" key02="192:" key03="192:" key04="192:" key05="192:" key06="^" key07="_" key08="=" key09="[" key10="]"/>
+					<row3 key01="288:layout3" key02="192:" key03="192:" key04="192:" key05="192:" key06="\" key07="&lt;" key08="&gt;" key09="288:c:8"/>
+					<row4 key01="288:layout1" key02="192:c:34" key03="960: " key04="." key05="288:action"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout4>
+			</keyboard>
+		</template>
+
+		<template name="keyboardnum">
+			<keyboard>
+				<placement x="0" y="%keyboard_y%" w="1920" h="512"/>
+				<keymargin x="6" y="6"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="40" y="4"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace"/>
+				<keylabel key="0:action" text="Enter" resource="enter"/>
+				<keylabel key=" " text="Space" resource="space"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<layout1>
+					<keysize height="127" width="376"/>
+					<row1 key01="396:" key02="1" key03="2" key04="3" key05="396:"/>
+					<row2 key01="396:" key02="4" key03="5" key04="6" key05="396:"/>
+					<row3 key01="396:" key02="7" key03="8" key04="9" key05="396:"/>
+					<row4 key01="396:" key02="376:c:8" key03="0" key04="378:action" key05="396:"/>
+				</layout1>
+			</keyboard>
+		</template>
+	</templates>
+</recovery>
diff --git a/gui/theme/landscape_mdpi/images/back.png b/gui/theme/landscape_mdpi/images/back.png
new file mode 100644
index 0000000..a431034
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/back.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/backspace.png b/gui/theme/landscape_mdpi/images/backspace.png
new file mode 100644
index 0000000..a516547
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/backspace.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/backspace_terminal.png b/gui/theme/landscape_mdpi/images/backspace_terminal.png
new file mode 100644
index 0000000..53db5aa
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/backspace_terminal.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/checkbox_false.png b/gui/theme/landscape_mdpi/images/checkbox_false.png
new file mode 100644
index 0000000..a4ed41e
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/checkbox_false.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/checkbox_true.png b/gui/theme/landscape_mdpi/images/checkbox_true.png
new file mode 100644
index 0000000..f7adcf5
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/checkbox_true.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/console.png b/gui/theme/landscape_mdpi/images/console.png
new file mode 100644
index 0000000..cc9bbf5
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/console.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/cursor.png b/gui/theme/landscape_mdpi/images/cursor.png
new file mode 100644
index 0000000..d5319ac
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/cursor.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/enter.png b/gui/theme/landscape_mdpi/images/enter.png
new file mode 100644
index 0000000..209a1be
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/enter.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/enter_terminal.png b/gui/theme/landscape_mdpi/images/enter_terminal.png
new file mode 100644
index 0000000..8bc0e89
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/enter_terminal.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/file.png b/gui/theme/landscape_mdpi/images/file.png
new file mode 100644
index 0000000..bb68fb7
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/file.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/folder.png b/gui/theme/landscape_mdpi/images/folder.png
new file mode 100644
index 0000000..72b76a9
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/folder.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/handle.png b/gui/theme/landscape_mdpi/images/handle.png
new file mode 100644
index 0000000..25e5355
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/handle.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/home.png b/gui/theme/landscape_mdpi/images/home.png
new file mode 100644
index 0000000..71b37e0
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/home.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate001.png b/gui/theme/landscape_mdpi/images/indeterminate001.png
new file mode 100644
index 0000000..930d7eb
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate001.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate002.png b/gui/theme/landscape_mdpi/images/indeterminate002.png
new file mode 100644
index 0000000..c4d207e
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate002.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate003.png b/gui/theme/landscape_mdpi/images/indeterminate003.png
new file mode 100644
index 0000000..376de52
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate003.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate004.png b/gui/theme/landscape_mdpi/images/indeterminate004.png
new file mode 100644
index 0000000..dbc1375
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate004.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate005.png b/gui/theme/landscape_mdpi/images/indeterminate005.png
new file mode 100644
index 0000000..cae5c3b
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate005.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate006.png b/gui/theme/landscape_mdpi/images/indeterminate006.png
new file mode 100644
index 0000000..3267998
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate006.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate007.png b/gui/theme/landscape_mdpi/images/indeterminate007.png
new file mode 100644
index 0000000..dee4840
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate007.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate008.png b/gui/theme/landscape_mdpi/images/indeterminate008.png
new file mode 100644
index 0000000..470f080
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate008.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate009.png b/gui/theme/landscape_mdpi/images/indeterminate009.png
new file mode 100644
index 0000000..08ac625
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate009.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate010.png b/gui/theme/landscape_mdpi/images/indeterminate010.png
new file mode 100644
index 0000000..a7ec133
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate010.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate011.png b/gui/theme/landscape_mdpi/images/indeterminate011.png
new file mode 100644
index 0000000..6e3bf96
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate011.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/indeterminate012.png b/gui/theme/landscape_mdpi/images/indeterminate012.png
new file mode 100644
index 0000000..d2f8c6a
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/indeterminate012.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/kb_arrow_down.png b/gui/theme/landscape_mdpi/images/kb_arrow_down.png
new file mode 100644
index 0000000..16042c1
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/kb_arrow_down.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/kb_arrow_left.png b/gui/theme/landscape_mdpi/images/kb_arrow_left.png
new file mode 100644
index 0000000..e5d6eaf
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/kb_arrow_left.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/kb_arrow_right.png b/gui/theme/landscape_mdpi/images/kb_arrow_right.png
new file mode 100644
index 0000000..3be128e
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/kb_arrow_right.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/kb_arrow_up.png b/gui/theme/landscape_mdpi/images/kb_arrow_up.png
new file mode 100644
index 0000000..c131e06
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/kb_arrow_up.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/kb_hide.png b/gui/theme/landscape_mdpi/images/kb_hide.png
new file mode 100644
index 0000000..3350f00
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/kb_hide.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/kb_show.png b/gui/theme/landscape_mdpi/images/kb_show.png
new file mode 100644
index 0000000..a01d75a
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/kb_show.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/logo.png b/gui/theme/landscape_mdpi/images/logo.png
new file mode 100644
index 0000000..c08babc
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/logo.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/main_button.png b/gui/theme/landscape_mdpi/images/main_button.png
new file mode 100644
index 0000000..65b778e
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/main_button.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/main_button_half_width.png b/gui/theme/landscape_mdpi/images/main_button_half_width.png
new file mode 100644
index 0000000..7e644fe
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/main_button_half_width.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/main_button_half_width_low.png b/gui/theme/landscape_mdpi/images/main_button_half_width_low.png
new file mode 100644
index 0000000..72c52f7
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/main_button_half_width_low.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/progress_empty.png b/gui/theme/landscape_mdpi/images/progress_empty.png
new file mode 100644
index 0000000..0eecb8b
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/progress_empty.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/progress_fill.png b/gui/theme/landscape_mdpi/images/progress_fill.png
new file mode 100644
index 0000000..87b5124
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/progress_fill.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/radio_false.png b/gui/theme/landscape_mdpi/images/radio_false.png
new file mode 100644
index 0000000..e753021
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/radio_false.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/radio_true.png b/gui/theme/landscape_mdpi/images/radio_true.png
new file mode 100644
index 0000000..5ab1b0f
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/radio_true.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/shift.png b/gui/theme/landscape_mdpi/images/shift.png
new file mode 100644
index 0000000..200e4c5
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/shift.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/shift_fill.png b/gui/theme/landscape_mdpi/images/shift_fill.png
new file mode 100644
index 0000000..cf46c1a
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/shift_fill.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/shift_terminal.png b/gui/theme/landscape_mdpi/images/shift_terminal.png
new file mode 100644
index 0000000..bc2cc7d
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/shift_terminal.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/shift_terminal_fill.png b/gui/theme/landscape_mdpi/images/shift_terminal_fill.png
new file mode 100644
index 0000000..02190b1
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/shift_terminal_fill.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/slider.png b/gui/theme/landscape_mdpi/images/slider.png
new file mode 100644
index 0000000..1473c80
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/slider.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/slider_touch.png b/gui/theme/landscape_mdpi/images/slider_touch.png
new file mode 100644
index 0000000..a701f53
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/slider_touch.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/slider_used.png b/gui/theme/landscape_mdpi/images/slider_used.png
new file mode 100644
index 0000000..56cff27
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/slider_used.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/sort_asc.png b/gui/theme/landscape_mdpi/images/sort_asc.png
new file mode 100644
index 0000000..d37dbfe
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/sort_asc.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/sort_desc.png b/gui/theme/landscape_mdpi/images/sort_desc.png
new file mode 100644
index 0000000..8c55158
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/sort_desc.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/sort_empty.png b/gui/theme/landscape_mdpi/images/sort_empty.png
new file mode 100644
index 0000000..0483546
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/sort_empty.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/space.png b/gui/theme/landscape_mdpi/images/space.png
new file mode 100644
index 0000000..06ff8df
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/space.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/space_terminal.png b/gui/theme/landscape_mdpi/images/space_terminal.png
new file mode 100644
index 0000000..d401333
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/space_terminal.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/splashlogo.png b/gui/theme/landscape_mdpi/images/splashlogo.png
new file mode 100644
index 0000000..daac294
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/splashlogo.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/splashteamwin.png b/gui/theme/landscape_mdpi/images/splashteamwin.png
new file mode 100644
index 0000000..d2fad9c
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/splashteamwin.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/tab_3.png b/gui/theme/landscape_mdpi/images/tab_3.png
new file mode 100644
index 0000000..1c37f98
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/tab_3.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/tab_4.png b/gui/theme/landscape_mdpi/images/tab_4.png
new file mode 100644
index 0000000..41f606c
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/tab_4.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/images/unlock_icon.png b/gui/theme/landscape_mdpi/images/unlock_icon.png
new file mode 100644
index 0000000..890c38c
--- /dev/null
+++ b/gui/theme/landscape_mdpi/images/unlock_icon.png
Binary files differ
diff --git a/gui/theme/landscape_mdpi/splash.xml b/gui/theme/landscape_mdpi/splash.xml
new file mode 100644
index 0000000..ea87310
--- /dev/null
+++ b/gui/theme/landscape_mdpi/splash.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<recovery>
+	<details>
+		<resolution width="800" height="480"/>
+		<author>TeamWin</author>
+		<title>TWRP</title>
+		<description>splash screen</description>
+		<themeversion>{themeversion}</themeversion>
+	</details>
+
+	<resources>
+		<font name="font_l" filename="RobotoCondensed-Regular.ttf" size="24"/>
+		<image name="splashlogo" filename="splashlogo" retainaspect="1"/>
+		<image name="splashteamwin" filename="splashteamwin" retainaspect="1"/>
+	</resources>
+
+	<variables>
+		<variable name="screen_width" value="800"/>
+		<variable name="screen_height" value="480"/>
+		<variable name="background_color" value="#222222"/>
+		<variable name="header_color" value="#555555"/>
+		<variable name="accent_color" value="#0090CA"/>
+	</variables>
+
+	<pages>
+		<page name="splash">
+			<background color="%background_color%"/>
+
+			<fill color="%header_color%">
+				<placement x="0" y="0" w="%screen_width%" h="140"/>
+			</fill>
+
+			<image>
+				<image resource="splashlogo"/>
+				<placement x="400" y="140" placement="4"/>
+			</image>
+
+			<image>
+				<image resource="splashteamwin"/>
+				<placement x="400" y="366" placement="4"/>
+			</image>
+
+			<text color="%header_color%">
+				<font resource="font_l"/>
+				<placement x="400" y="386" placement="5"/>
+				<text>Recovery Project %tw_version%</text>
+			</text>
+		</page>
+	</pages>
+</recovery>
+
diff --git a/gui/theme/landscape_mdpi/ui.xml b/gui/theme/landscape_mdpi/ui.xml
new file mode 100755
index 0000000..dadfbb9
--- /dev/null
+++ b/gui/theme/landscape_mdpi/ui.xml
@@ -0,0 +1,807 @@
+<?xml version="1.0"?>
+<recovery>
+	<details>
+		<resolution width="800" height="480"/>
+		<author>TeamWin</author>
+		<title>Backup Naowz</title>
+		<description>Default basic theme</description>
+		<preview>preview.png</preview>
+		<themeversion>{themeversion}</themeversion>
+	</details>
+
+	<include>
+		<xmlfile name="landscape.xml"/>
+	</include>
+
+	<resources>
+		<font name="font_l" filename="RobotoCondensed-Regular.ttf" size="18"/>
+		<font name="font_m" filename="RobotoCondensed-Regular.ttf" size="14"/>
+		<font name="font_s" filename="RobotoCondensed-Regular.ttf" size="12"/>
+		<font name="fixed" filename="DroidSansMono.ttf" size="12"/>
+
+		<font name="keylabel" filename="RobotoCondensed-Regular.ttf" size="21"/>
+		<font name="keylabel-small" filename="RobotoCondensed-Regular.ttf" size="18"/>
+		<font name="keylabel-longpress" filename="RobotoCondensed-Regular.ttf" size="11"/>
+
+		<image name="logo" filename="logo" retainaspect="1"/>
+		<image name="main_button" filename="main_button"/>
+		<image name="main_button_half_width" filename="main_button_half_width"/>
+		<image name="main_button_half_width_low" filename="main_button_half_width_low"/>
+		<image name="tab_3" filename="tab_3"/>
+		<image name="tab_4" filename="tab_4"/>
+		<image name="file" filename="file" retainaspect="1"/>
+		<image name="folder" filename="folder" retainaspect="1"/>
+		<image name="unlock_icon" filename="unlock_icon" retainaspect="1"/>
+		<image name="home" filename="home" retainaspect="1"/>
+		<image name="back" filename="back" retainaspect="1"/>
+		<image name="console" filename="console" retainaspect="1"/>
+		<image name="kb_hide" filename="kb_hide" retainaspect="1"/>
+		<image name="kb_show" filename="kb_show" retainaspect="1"/>
+		<image name="checkbox_false" filename="checkbox_false" retainaspect="1"/>
+		<image name="checkbox_true" filename="checkbox_true" retainaspect="1"/>
+		<image name="radio_false" filename="radio_false" retainaspect="1"/>
+		<image name="radio_true" filename="radio_true" retainaspect="1"/>
+		<image name="sort_asc" filename="sort_asc"/>
+		<image name="sort_desc" filename="sort_desc"/>
+		<image name="sort_empty" filename="sort_empty"/>
+		<animation name="progress" filename="indeterminate"/>
+		<image name="progress_empty" filename="progress_empty"/>
+		<image name="progress_full" filename="progress_fill"/>
+		<image name="slider" filename="slider"/>
+		<image name="slider_used" filename="slider_used"/>
+		<image name="slider_touch" filename="slider_touch"/>
+		<image name="handle" filename="handle"/>
+		<image name="cursor" filename="cursor" retainaspect="1"/>
+
+		<image name="backspace" filename="backspace" retainaspect="1"/>
+		<image name="backspace_terminal" filename="backspace_terminal" retainaspect="1"/>
+		<image name="enter" filename="enter" retainaspect="1"/>
+		<image name="enter_terminal" filename="enter_terminal" retainaspect="1"/>
+		<image name="shift" filename="shift" retainaspect="1"/>
+		<image name="shift_terminal" filename="shift_terminal" retainaspect="1"/>
+		<image name="shift_fill" filename="shift_fill" retainaspect="1"/>
+		<image name="shift_terminal_fill" filename="shift_terminal_fill" retainaspect="1"/>
+		<image name="space" filename="space" retainaspect="1"/>
+		<image name="space_terminal" filename="space_terminal" retainaspect="1"/>
+		<image name="kb_arrow_left" filename="kb_arrow_left" retainaspect="1"/>
+		<image name="kb_arrow_right" filename="kb_arrow_right" retainaspect="1"/>
+		<image name="kb_arrow_up" filename="kb_arrow_up" retainaspect="1"/>
+		<image name="kb_arrow_down" filename="kb_arrow_down" retainaspect="1"/>
+	</resources>
+
+	<variables>
+		<variable name="tw_samsung_navbar" value="0" persist="1"/>
+		<variable name="tw_navbar_button_position" value="0" persist="1"/>
+
+		<variable name="tw_hide_kb" value="0"/>
+		<variable name="screen_width" value="800"/>
+		<variable name="screen_height" value="480"/>
+		<variable name="status_height" value="20"/>
+		<variable name="header_height" value="80"/>
+		<variable name="navbar_height" value="40"/>
+		<variable name="content_width" value="760"/>
+		<variable name="content_quarter_width" value="590"/>
+		<variable name="content_half_width" value="370"/>
+		<variable name="content_overlay_width" value="380"/>
+		<variable name="dialog_height" value="207"/>
+		<variable name="dialog_width" value="400"/>
+		<variable name="dialog_content_x" value="220"/>
+		<variable name="back_button_x" value="264"/>
+		<variable name="console_button_x" value="536"/>
+		<variable name="back_button_x_1" value="50"/>
+		<variable name="home_button_x_1" value="130"/>
+		<variable name="console_button_x_1" value="210"/>
+		<variable name="back_button_x_2" value="590"/>
+		<variable name="home_button_x_2" value="670"/>
+		<variable name="console_button_x_2" value="750"/>
+		<variable name="col1_x_header" value="60"/>
+		<variable name="indent" value="10"/>
+		<variable name="col1_x_left" value="20"/>
+		<variable name="col2_x_left" value="210"/>
+		<variable name="center_x" value="400"/>
+		<variable name="col1_x_right" value="410"/>
+		<variable name="col2_x_right" value="590"/>
+		<variable name="col_button_right" value="620"/>
+		<variable name="indent_right" value="790"/>
+		<variable name="tab_height" value="30"/>
+		<variable name="tab_indicator_height" value="2"/>
+		<variable name="tab4_width" value="200"/>
+		<variable name="tab5_width" value="160"/>
+		<variable name="tab4_col2_x" value="200"/>
+		<variable name="tab4_col4_x" value="600"/>
+		<variable name="tab5_col2_x" value="160"/>
+		<variable name="tab5_col3_x" value="320"/>
+		<variable name="tab5_col4_x" value="480"/>
+		<variable name="tab5_col5_x" value="640"/>
+		<variable name="btn4_col2_x_left" value="117"/>
+		<variable name="btn4_col3_x_left" value="216"/>
+		<variable name="btn4_col4_x_left" value="313"/>
+		<variable name="btn4_col2_x_right" value="507"/>
+		<variable name="btn4_col3_x_right" value="606"/>
+		<variable name="btn4_col4_x_right" value="703"/>
+		<variable name="status_topalign_header_y" value="0"/>
+		<variable name="status_centeralign_header_y" value="2"/>
+		<variable name="status_bottomalign_header_y" value="4"/>
+		<variable name="row1_header_y" value="{statusicons_align}"/>
+		<variable name="row2_header_y" value="20"/>
+		<variable name="row3_header_y" value="24"/>
+		<variable name="row4_header_y" value="52"/>
+		<variable name="row1_y" value="80"/>
+		<variable name="row1a_y" value="89"/>
+		<variable name="row2_y" value="98"/>
+		<variable name="row2a_y" value="107"/>
+		<variable name="row_tab_y" value="108"/>
+		<variable name="row2_input_y" value="116"/>
+		<variable name="row3_y" value="116"/>
+		<variable name="row3a_y" value="125"/>
+		<variable name="row3_input_y" value="125"/>
+		<variable name="row4_y" value="134"/>
+		<variable name="row4a_y" value="143"/>
+		<variable name="row4_input_y" value="143"/>
+		<variable name="row5_y" value="152"/>
+		<variable name="row5a_y" value="161"/>
+		<variable name="row6_y" value="170"/>
+		<variable name="row6a_y" value="179"/>
+		<variable name="row7_y" value="188"/>
+		<variable name="row7a_y" value="197"/>
+		<variable name="row8_y" value="206"/>
+		<variable name="row8a_y" value="215"/>
+		<variable name="row9_y" value="224"/>
+		<variable name="row9a_y" value="233"/>
+		<variable name="row10_y" value="242"/>
+		<variable name="row10a_y" value="251"/>
+		<variable name="row11_y" value="260"/>
+		<variable name="row11a_y" value="269"/>
+		<variable name="row12_y" value="278"/>
+		<variable name="row12a_y" value="288"/>
+		<variable name="row13_y" value="296"/>
+		<variable name="row13a_y" value="305"/>
+		<variable name="row14_y" value="314"/>
+		<variable name="row14a_y" value="323"/>
+		<variable name="row15_y" value="332"/>
+		<variable name="row15a_y" value="341"/>
+		<variable name="row16_y" value="350"/>
+		<variable name="row16a_y" value="359"/>
+		<variable name="row17_y" value="368"/>
+		<variable name="row17a_y" value="377"/>
+		<variable name="row18_y" value="386"/>
+		<variable name="row18a_y" value="395"/>
+		<variable name="row19_y" value="404"/>
+		<variable name="row19a_y" value="413"/>
+		<variable name="row20_y" value="422"/>
+		<variable name="navbar_y" value="440"/>
+		<variable name="navbar_btn_y" value="460"/>
+		<variable name="keyboard_y" value="248"/>
+		<variable name="background_color" value="#1A1A1A"/>
+		<variable name="accent_color" value="#0090CA"/>
+		<variable name="text_color" value="#EEEEEE"/>
+		<variable name="text_button_color" value="#EEEEEE"/>
+		<variable name="text_success_color" value="#76FF03"/>
+		<variable name="text_fail_color" value="#FF0101"/>
+		<variable name="highlight_color" value="#1A1A1A80"/>
+		<variable name="caps_highlight_color" value="#22222280"/>
+		<variable name="transparent" value="#00000000"/>
+		<variable name="semi_transparent" value="#00000099"/>
+		<variable name="warning" value="#F8F8A0"/>
+		<variable name="error" value="#FF0101"/>
+		<variable name="highlight" value="#0090CA"/>
+		<variable name="fileselector_linecolor" value="#555555"/>
+		<variable name="fileselector_highlight_color" value="#555555"/>
+		<variable name="fileselector_separatorheight" value="1"/>
+		<variable name="fileselector_spacing" value="0"/>
+		<variable name="fileselector_install_height" value="333"/>
+		<variable name="fileselector_install_width" value="400"/>
+		<variable name="fileselector_install_folder_width" value="170"/>
+		<variable name="fileselector_filemanager_height" value="333"/>
+		<variable name="partitionlist_spacing" value="0"/>
+		<variable name="partitionlist_storage_height" value="160"/>
+		<variable name="partitionlist_flashimage_height" value="144"/>
+		<variable name="partitionlist_wipe_height" value="243"/>
+		<variable name="partitionlist_mount_height" value="221"/>
+		<variable name="partitionlist_backup_height" value="198"/>
+		<variable name="listbox_timezone_height" value="306"/>
+		<variable name="listbox_settings_height" value="243"/>
+		<variable name="listbox_advanced_height" value="300"/>
+		<variable name="listbox_options_height" value="110"/>
+		<variable name="fastscroll_w" value="7"/>
+		<variable name="fastscroll_linew" value="1"/>
+		<variable name="fastscroll_rectw" value="7"/>
+		<variable name="fastscroll_recth" value="45"/>
+		<variable name="slidervalue_lineh" value="2"/>
+		<variable name="slidervalue_padding" value="0"/>
+		<variable name="slidervalue_sliderw" value="24"/>
+		<variable name="slidervalue_sliderh" value="36"/>
+		<variable name="slideout_y" value="40"/>
+		<variable name="slideout_height" value="380"/>
+		<variable name="slideout_bg_height" value="420"/>
+		<variable name="input_height" value="18"/>
+		<variable name="input_line_width" value="1"/>
+		<variable name="console_height" value="234"/>
+		<variable name="console_terminal_height" value="164"/>
+		<variable name="terminal_s_height" value="220"/>
+		<variable name="terminal_l_height" value="412"/>
+		<variable name="dialog_button_x" value="480"/>
+		<variable name="date_button_x" value="230"/>
+		<variable name="progress_x" value="230"/>
+		<variable name="progress_text_x" value="40"/>
+		<variable name="progress_text_y" value="348"/>
+		<variable name="pattern_x" value="84"/>
+		<variable name="pattern_dot_dia" value="12"/>
+		<variable name="pattern_line_w" value="4"/>
+		<variable name="pattern_size" value="252"/>
+		<variable name="tw_clock_12_pos_x" value="{clock_12_pos}"/>
+		<variable name="tw_clock_24_pos_x" value="{clock_24_pos}"/>
+		<variable name="tw_cpu_pos_x" value="{cpu_pos}"/>
+		<variable name="tw_battery_pos_x" value="{battery_pos}"/>
+	</variables>
+
+	<mousecursor>
+		<placement w="15" h="15"/>
+		<background color="#FFFF00FF" resource="cursor"/>
+		<speed multiplier="2.5"/>
+	</mousecursor>
+
+	<templates>
+		<template name="page">
+			<background color="%background_color%"/>
+
+			<fill color="%accent_color%">
+				<placement x="0" y="0" w="%screen_width%" h="%header_height%"/>
+			</fill>
+
+			<image>
+				<condition var1="tw_busy" var2="1"/>
+				<image resource="logo"/>
+				<placement x="0" y="0"/>
+			</image>
+
+			<button>
+				<condition var1="tw_busy" var2="0"/>
+				<placement x="0" y="0"/>
+				<image resource="logo"/>
+				<action function="key">home</action>
+			</button>
+
+			<fill color="#00000030">
+				<condition var1="tw_simulate_actions" var2="0"/>
+				<placement x="0" y="0" w="%screen_width%" h="%status_height%"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_simulate_actions" var2="1"/>
+				<placement x="0" y="0" w="%screen_width%" h="%status_height%"/>
+			</fill>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="1"/>
+				<condition var1="tw_cpu_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row1_header_y%"/>
+				<text>%tw_version%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<condition var1="tw_cpu_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row1_header_y%"/>
+				<text>{@cpu_temp=CPU: %tw_cpu_temp% °C}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_12_pos_x" var2="0"/>
+				<condition var1="tw_clock_24_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%center_x%" y="%row1_header_y%" placement="5"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<conditions>
+					<condition var1="tw_no_battery_percent" var2="0"/>
+					<condition var1="tw_battery" op="&gt;" var2="0"/>
+					<condition var1="tw_battery" op="&lt;" var2="101"/>
+					<condition var1="tw_battery_pos_x" var2="0"/>
+				</conditions>
+				<font resource="font_m"/>
+				<placement x="%indent_right%" y="%row1_header_y%" placement="1"/>
+				<text>{@battery_pct=Battery: %tw_battery%}</text>
+			</text>
+
+			<!-- Custom position for status bar items if defined START -->
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="1"/>
+				<condition var1="tw_cpu_pos_x" op="!=" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_cpu_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_version%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<condition var1="tw_cpu_pos_x" op="!=" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_cpu_pos_x%" y="%row1_header_y%"/>
+				<text>{@cpu_temp=CPU: %tw_cpu_temp% °C}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_12_pos_x" op="!=" var2="0"/>
+				<condition var1="tw_military_time" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_clock_12_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_24_pos_x" op="!=" var2="0"/>
+				<condition var1="tw_military_time" var2="1"/>
+				<font resource="font_m"/>
+				<placement x="%tw_clock_24_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<conditions>
+					<condition var1="tw_no_battery_percent" var2="0"/>
+					<condition var1="tw_battery" op="&gt;" var2="0"/>
+					<condition var1="tw_battery" op="&lt;" var2="101"/>
+					<condition var1="tw_battery_pos_x" op="!=" var2="0"/>
+				</conditions>
+				<font resource="font_m"/>
+				<placement x="%tw_battery_pos_x%" y="%row1_header_y%"/>
+				<text>{@battery_pct=Battery: %tw_battery%}</text>
+			</text>
+
+			<!-- Custom position for status bar items if defined END -->
+
+			<fill color="#000000">
+				<condition var1="tw_busy" var2="0"/>
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%"/>
+			</fill>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0"/>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">home</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="0"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%back_button_x_1%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1"/>
+				<placement x="%home_button_x_1%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">home</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="1"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%console_button_x_1%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%back_button_x_2%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2"/>
+				<placement x="%home_button_x_2%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">home</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2"/>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_navbar_button_position" op="=" var2="2"/>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%console_button_x_2%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</template>
+
+		<template name="progress_bar">
+			<progressbar>
+				<placement x="%progress_x%" y="%row16_y%"/>
+				<resource empty="progress_empty" full="progress_full"/>
+				<data name="ui_progress"/>
+			</progressbar>
+
+			<animation>
+				<placement x="%progress_x%" y="%row16_y%"/>
+				<resource name="progress"/>
+				<speed fps="24" render="2"/>
+				<loop frame="1"/>
+			</animation>
+		</template>
+
+		<template name="sort_options">
+			<image>
+				<placement x="%col_button_right%" y="%row1a_y%"/>
+				<image resource="main_button_half_width_low"/>
+			</image>
+
+			<image>
+				<placement x="%col_button_right%" y="%row4a_y%"/>
+				<image resource="main_button_half_width_low"/>
+			</image>
+
+			<image>
+				<placement x="%col_button_right%" y="%row7a_y%"/>
+				<image resource="main_button_half_width_low"/>
+			</image>
+
+			<button style="sort_asc">
+				<condition var1="tw_gui_sort_order" op="=" var2="1"/>
+				<placement x="%col_button_right%" y="%row1a_y%"/>
+				<text>{@sort_by_name=Sort by Name}</text>
+				<action function="set">tw_gui_sort_order=-1</action>
+			</button>
+
+			<button style="sort_desc">
+				<condition var1="tw_gui_sort_order" op="=" var2="-1"/>
+				<placement x="%col_button_right%" y="%row1a_y%"/>
+				<text>{@sort_by_name=Sort by Name}</text>
+				<action function="set">tw_gui_sort_order=1</action>
+			</button>
+
+			<button style="sort_empty">
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="1"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-1"/>
+				</conditions>
+				<placement x="%col_button_right%" y="%row1a_y%"/>
+				<text>{@sort_by_name=Sort by Name}</text>
+				<action function="set">tw_gui_sort_order=1</action>
+			</button>
+
+			<button style="sort_asc">
+				<condition var1="tw_gui_sort_order" op="=" var2="2"/>
+				<placement x="%col_button_right%" y="%row4a_y%"/>
+				<text>{@sort_by_date=Sort by Date}</text>
+				<action function="set">tw_gui_sort_order=-2</action>
+			</button>
+
+			<button style="sort_desc">
+				<condition var1="tw_gui_sort_order" op="=" var2="-2"/>
+				<placement x="%col_button_right%" y="%row4a_y%"/>
+				<text>{@sort_by_date=Sort by Date}</text>
+				<action function="set">tw_gui_sort_order=2</action>
+			</button>
+
+			<button style="sort_empty">
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="2"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-2"/>
+				</conditions>
+				<placement x="%col_button_right%" y="%row4a_y%"/>
+				<text>{@sort_by_date=Sort by Date}</text>
+				<action function="set">tw_gui_sort_order=2</action>
+			</button>
+
+			<button style="sort_asc">
+				<condition var1="tw_gui_sort_order" op="=" var2="3"/>
+				<placement x="%col_button_right%" y="%row7a_y%"/>
+				<text>{@sort_by_size=Sort by Size}</text>
+				<action function="set">tw_gui_sort_order=-3</action>
+			</button>
+
+			<button style="sort_desc">
+				<condition var1="tw_gui_sort_order" op="=" var2="-3"/>
+				<placement x="%col_button_right%" y="%row7a_y%"/>
+				<text>{@sort_by_size=Sort by Size}</text>
+				<action function="set">tw_gui_sort_order=3</action>
+			</button>
+
+			<button style="sort_empty">
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="3"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-3"/>
+				</conditions>
+				<placement x="%col_button_right%" y="%row7a_y%"/>
+				<text>{@sort_by_size=Sort by Size}</text>
+				<action function="set">tw_gui_sort_order=3</action>
+			</button>
+		</template>
+
+		<template name="tabs_settings">
+			<button style="tab">
+				<placement x="0" y="%row1_y%" w="%tab5_width%" h="%tab_height%"/>
+				<text>{@tab_general=GENERAL}</text>
+				<action function="page">settings</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_col2_x%" y="%row1_y%" w="%tab5_width%" h="%tab_height%"/>
+				<text>{@tab_time_zone=TIME ZONE}</text>
+				<action function="page">settings_timezone</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_col3_x%" y="%row1_y%" w="%tab5_width%" h="%tab_height%"/>
+				<text>{@tab_screen=SCREEN}</text>
+				<action function="page">settings_screen</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_col4_x%" y="%row1_y%" w="%tab5_width%" h="%tab_height%"/>
+				<text>{@tab_vibration=VIBRATION}</text>
+				<action function="page">settings_vibration</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_col5_x%" y="%row1_y%" w="%tab5_width%" h="%tab_height%"/>
+				<text>{@tab_language=LANGUAGE}</text>
+				<action function="page">settings_language</action>
+			</button>
+		</template>
+
+		<template name="console">
+			<console>
+				<placement x="%col1_x_left%" y="%row2_y%" w="%content_width%" h="%console_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_left%" y="row2_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_left%" y="row15_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+		</template>
+
+		<template name="console_terminal">
+			<console>
+				<placement x="%col1_x_left%" y="%row3_header_y%" w="content_width" h="%console_terminal_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_left%" y="%row3_header_y%" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%col1_x_left%" y="row7_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+		</template>
+
+		<template name="keyboardterminaltemplate">
+			<keyboard>
+				<condition var1="tw_hide_kb" op="=" var2="0"/>
+				<placement x="0" y="%keyboard_y%" w="800" h="192"/>
+				<keymargin x="4" y="4"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="14" y="2"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace_terminal"/>
+				<keylabel key="0:action" text="Enter" resource="enter_terminal"/>
+				<keylabel key=" " text="Space" resource="space_terminal"/>
+				<keylabel key="0:k:29" text="Ctrl"/>
+				<keylabel key="0:c:27" text="Esc"/>
+				<keylabel key="0:c:9" text="Tab"/>
+				<keylabel key="0:k:105" text="&lt;" resource="kb_arrow_left"/>
+				<keylabel key="0:k:108" text="v" resource="kb_arrow_down"/>
+				<keylabel key="0:k:103" text="^" resource="kb_arrow_up"/>
+				<keylabel key="0:k:106" text="&gt;" resource="kb_arrow_right"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<ctrlhighlight color="#0090CA80"/>
+				<layout1>
+					<keysize height="48" width="68"/>
+					<row1 key01="60:c:27" long01=":c:3" key02="q" long02="1" key03="w" long03="2" key04="e" long04="3" key05="r" long05="4" key06="t" long06="5" key07="y" long07="6" key08="u" long08="7" key09="i" long09="8" key10="o" long10="9" key11="p" long11="0" key12="60:k:103"/>
+					<row2 key01="60:c:9" key02="34:" key03="a" long03="@" key04="s" long04="#" key05="d" long05="$" key06="f" long06="%" key07="g" long07="&amp;" key08="h" long08="*" key09="j" long09="-" key10="k" long10="+" key11="l" long11="_" key12="34:" key13="60:k:105"/>
+					<row3 key01="60:" key02="102:layout2" key03="z" long03="!" key04="x" long04="&quot;" key05="c" long05="'" key06="v" long06=":" key07="b" long07=";" key08="n" long08="/" key09="m" long09="?" key10="102:c:8" key11="60:k:106"/>
+					<row4 key01="60:k:29" key02="102:layout3" key03="/" key04="340: " key05="." key06="102:action" key07="60:k:108"/>
+					<keylabel key="0:layout2" text="Shift" resource="shift_terminal"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout1>
+				<layout2>
+					<keysize height="48" width="68" capslock="0" revert_layout="1"/>
+					<row1 key01="60:c:27" long01=":c:3" key02="Q" long02="1" key03="W" long03="2" key04="E" long04="3" key05="R" long05="4" key06="T" long06="5" key07="Y" long07="6" key08="U" long08="7" key09="I" long09="8" key10="O" long10="9" key11="P" long11="0" key12="60:k:103"/>
+					<row2 key01="60:c:9" key02="34:" key03="A" long03="@" key04="S" long04="#" key05="D" long05="$" key06="F" long06="%" key07="G" long07="&amp;" key08="H" long08="*" key09="J" long09="-" key10="K" long10="+" key11="L" long11="_" key12="34:" key13="60:k:105"/>
+					<row3 key01="60:" key02="102:layout1" key03="Z" long03="!" key04="X" long04="&quot;" key05="C" long05="'" key06="V" long06=":" key07="B" long07=";" key08="N" long08="/" key09="M" long09="?" key10="102:c:8" key11="60:k:106"/>
+					<row4 key01="60:k:29" key02="102:layout3" key03="/" key04="340: " key05="." key06="102:action" key07="60:k:108"/>
+					<keylabel key="0:layout1" text="Shift" resource="shift_terminal_fill"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout2>
+				<layout3>
+					<keysize height="48" width="68"/>
+					<row1 key01="60:c:27" long01=":c:3" key02="1" key03="2" key04="3" key05="4" key06="5" key07="6" key08="7" key09="8" key10="9" key11="0" key12="60:k:103"/>
+					<row2 key01="60:c:9" key02="@" key03="#" key04="$" key05="%" key06="&amp;" key07="*" key08="-" key09="+" key10="(" key11=")" key12="60:k:105"/>
+					<row3 key01="60:" key02="102:layout4" key03="!" key04="68:c:34" key05="'" key06=":" key07=";" key08="/" key09="?" key10="102:c:8" key11="60:k:106"/>
+					<row4 key01="60:k:29" key02="102:layout1" key03="," key04="340: " key05="." key06="102:action" key07="60:k:108"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout4" text="~\{"/>
+				</layout3>
+				<layout4>
+					<keysize height="48" width="68"/>
+					<row1 key01="60:c:27" long01=":c:3" key02="~" key03="`" key04="|" key05="160:" key06="160:" key07="160:" key08="%" key09="160:" key10="{" key11="}" key12="60:k:103"/>
+					<row2 key01="60:c:9" key02="68:" key03="68:" key04="68:" key05="68:" key06="68:" key07="^" key08="_" key09="=" key10="[" key11="]" key12="60:k:105"/>
+					<row3 key01="60:" key02="102:layout3" key03="68:" key04="68:" key05="68:" key06="68:" key07="\" key08="&lt;" key09="&gt;" key10="102:c:8" key11="60:k:106"/>
+					<row4 key01="60:k:29" key02="102:layout1" key03="68:c:34" key04="340: " key05="." key06="102:action" key07="60:k:108"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout4>
+			</keyboard>
+		</template>
+
+		<template name="keyboardtemplate">
+			<keyboard>
+				<placement x="0" y="%keyboard_y%" w="800" h="192"/>
+				<keymargin x="4" y="4"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="14" y="2"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace"/>
+				<keylabel key="0:action" text="Enter" resource="enter"/>
+				<keylabel key=" " text="Space" resource="space"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<layout1>
+					<keysize height="48" width="80"/>
+					<row1 key01="q" long01="1" key02="w" long02="2" key03="e" long03="3" key04="r" long04="4" key05="t" long05="5" key06="y" long06="6" key07="u" long07="7" key08="i" long08="8" key09="o" long09="9" key10="p" long10="0"/>
+					<row2 key01="40:" key02="a" long02="@" key03="s" long03="#" key04="d" long04="$" key05="f" long05="%" key06="g" long06="&amp;" key07="h" long07="*" key08="j" long08="-" key09="k" long09="+" key10="l" long10="_" key11="40:"/>
+					<row3 key01="120:layout2" key02="z" long02="!" key03="x" long03="&quot;" key04="c" long04="'" key05="v" long05=":" key06="b" long06=";" key07="n" long07="/" key08="m" long08="?" key09="120:c:8"/>
+					<row4 key01="120:layout3" key02="/" key03="400: " key04="." key05="120:action"/>
+					<keylabel key="0:layout2" text="Shift" resource="shift"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout1>
+				<layout2>
+					<keysize height="48" width="80" capslock="0" revert_layout="1"/>
+					<row1 key01="Q" long01="1" key02="W" long02="2" key03="E" long03="3" key04="R" long04="4" key05="T" long05="5" key06="Y" long06="6" key07="U" long07="7" key08="I" long08="8" key09="O" long09="9" key10="P" long10="0"/>
+					<row2 key01="40:" key02="A" long02="@" key03="S" long03="#" key04="D" long04="$" key05="F" long05="%" key06="G" long06="&amp;" key07="H" long07="*" key08="J" long08="-" key09="K" long09="+" key10="L" long10="_" key11="40:"/>
+					<row3 key01="120:layout1" key02="Z" long02="!" key03="X" long03="&quot;" key04="C" long04="'" key05="V" long05=":" key06="B" long06=";" key07="N" long07="/" key08="M" long08="?" key09="120:c:8"/>
+					<row4 key01="120:layout3" key02="/" key03="400: " key04="." key05="120:action"/>
+					<keylabel key="0:layout1" text="Shift" resource="shift_fill"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout2>
+				<layout3>
+					<keysize height="48" width="80"/>
+					<row1 key01="1" key02="2" key03="3" key04="4" key05="5" key06="6" key07="7" key08="8" key09="9" key10="0"/>
+					<row2 key01="@" key02="#" key03="$" key04="%" key05="&amp;" key06="*" key07="-" key08="+" key09="(" key10=")"/>
+					<row3 key01="120:layout4" key02="!" key03="80:c:34" key04="'" key05=":" key06=";" key07="/" key08="?" key09="120:c:8"/>
+					<row4 key01="120:layout1" key02="," key03="400: " key04="." key05="120:action"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout4" text="~\{"/>
+				</layout3>
+				<layout4>
+					<keysize height="48" width="80"/>
+					<row1 key01="~" key02="`" key03="|" key04="80:" key05="80:" key06="80:" key07="%" key08="80:" key09="{" key10="}"/>
+					<row2 key01="80:" key02="80:" key03="80:" key04="80:" key05="80:" key06="^" key07="_" key08="=" key09="[" key10="]"/>
+					<row3 key01="120:layout3" key02="80:" key03="80:" key04="80:" key05="80:" key06="\" key07="&lt;" key08="&gt;" key09="120:c:8"/>
+					<row4 key01="120:layout1" key02="80:c:34" key03="400: " key04="." key05="120:action"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout4>
+			</keyboard>
+		</template>
+
+		<template name="keyboardnum">
+			<keyboard>
+				<placement x="0" y="%keyboard_y%" w="800" h="192"/>
+				<keymargin x="4" y="4"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="14" y="2"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace"/>
+				<keylabel key="0:action" text="Enter" resource="enter"/>
+				<keylabel key=" " text="Space" resource="space"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<layout1>
+					<keysize height="47" width="158"/>
+					<row1 key01="164:" key02="1" key03="2" key04="3" key05="164:"/>
+					<row2 key01="164:" key02="4" key03="5" key04="6" key05="164:"/>
+					<row3 key01="164:" key02="7" key03="8" key04="9" key05="164:"/>
+					<row4 key01="164:" key02="158:c:8" key03="0" key04="159:action" key05="164:"/>
+				</layout1>
+			</keyboard>
+		</template>
+	</templates>
+</recovery>
diff --git a/gui/theme/portrait_hdpi/images/back.png b/gui/theme/portrait_hdpi/images/back.png
new file mode 100644
index 0000000..7369e2b
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/back.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/backspace.png b/gui/theme/portrait_hdpi/images/backspace.png
new file mode 100644
index 0000000..cc84f7a
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/backspace.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/checkbox_false.png b/gui/theme/portrait_hdpi/images/checkbox_false.png
new file mode 100644
index 0000000..b7c8013
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/checkbox_false.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/checkbox_true.png b/gui/theme/portrait_hdpi/images/checkbox_true.png
new file mode 100644
index 0000000..d8a7b5b
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/checkbox_true.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/console.png b/gui/theme/portrait_hdpi/images/console.png
new file mode 100644
index 0000000..13fce5c
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/console.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/cursor.png b/gui/theme/portrait_hdpi/images/cursor.png
new file mode 100644
index 0000000..3513d06
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/cursor.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/enter.png b/gui/theme/portrait_hdpi/images/enter.png
new file mode 100644
index 0000000..365f99e
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/enter.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/fab_selectfolder.png b/gui/theme/portrait_hdpi/images/fab_selectfolder.png
new file mode 100644
index 0000000..3be39ba
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/fab_selectfolder.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/file.png b/gui/theme/portrait_hdpi/images/file.png
new file mode 100644
index 0000000..7f027da
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/file.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/folder.png b/gui/theme/portrait_hdpi/images/folder.png
new file mode 100644
index 0000000..ebdced2
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/folder.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/handle.png b/gui/theme/portrait_hdpi/images/handle.png
new file mode 100644
index 0000000..cdd20a0
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/handle.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/home.png b/gui/theme/portrait_hdpi/images/home.png
new file mode 100644
index 0000000..d2d56a5
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/home.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate001.png b/gui/theme/portrait_hdpi/images/indeterminate001.png
new file mode 100644
index 0000000..151c8ac
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate001.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate002.png b/gui/theme/portrait_hdpi/images/indeterminate002.png
new file mode 100644
index 0000000..7d66373
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate002.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate003.png b/gui/theme/portrait_hdpi/images/indeterminate003.png
new file mode 100644
index 0000000..b235c01
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate003.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate004.png b/gui/theme/portrait_hdpi/images/indeterminate004.png
new file mode 100644
index 0000000..0ac66d5
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate004.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate005.png b/gui/theme/portrait_hdpi/images/indeterminate005.png
new file mode 100644
index 0000000..86992eb
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate005.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate006.png b/gui/theme/portrait_hdpi/images/indeterminate006.png
new file mode 100644
index 0000000..e03772c
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate006.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate007.png b/gui/theme/portrait_hdpi/images/indeterminate007.png
new file mode 100644
index 0000000..4ff1aa4
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate007.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate008.png b/gui/theme/portrait_hdpi/images/indeterminate008.png
new file mode 100644
index 0000000..ff757ee
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate008.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate009.png b/gui/theme/portrait_hdpi/images/indeterminate009.png
new file mode 100644
index 0000000..9e337f9
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate009.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate010.png b/gui/theme/portrait_hdpi/images/indeterminate010.png
new file mode 100644
index 0000000..7dbcd79
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate010.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate011.png b/gui/theme/portrait_hdpi/images/indeterminate011.png
new file mode 100644
index 0000000..ab67d38
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate011.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/indeterminate012.png b/gui/theme/portrait_hdpi/images/indeterminate012.png
new file mode 100644
index 0000000..ceadbb0
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/indeterminate012.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/kb_arrow_down.png b/gui/theme/portrait_hdpi/images/kb_arrow_down.png
new file mode 100644
index 0000000..ba3b90a
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/kb_arrow_down.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/kb_arrow_left.png b/gui/theme/portrait_hdpi/images/kb_arrow_left.png
new file mode 100644
index 0000000..c6d1fd6
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/kb_arrow_left.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/kb_arrow_right.png b/gui/theme/portrait_hdpi/images/kb_arrow_right.png
new file mode 100644
index 0000000..be0f124
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/kb_arrow_right.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/kb_arrow_up.png b/gui/theme/portrait_hdpi/images/kb_arrow_up.png
new file mode 100644
index 0000000..16d7765
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/kb_arrow_up.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/kb_hide.png b/gui/theme/portrait_hdpi/images/kb_hide.png
new file mode 100644
index 0000000..6fa9c6d
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/kb_hide.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/kb_show.png b/gui/theme/portrait_hdpi/images/kb_show.png
new file mode 100644
index 0000000..69a0eed
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/kb_show.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/logo.png b/gui/theme/portrait_hdpi/images/logo.png
new file mode 100644
index 0000000..d311a2d
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/logo.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/main_button.png b/gui/theme/portrait_hdpi/images/main_button.png
new file mode 100644
index 0000000..b6ca36b
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/main_button.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/main_button_half_height.png b/gui/theme/portrait_hdpi/images/main_button_half_height.png
new file mode 100644
index 0000000..f8a5263
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/main_button_half_height.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/main_button_half_height_full_width.png b/gui/theme/portrait_hdpi/images/main_button_half_height_full_width.png
new file mode 100644
index 0000000..33a2a1a
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/main_button_half_height_full_width.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/progress_empty.png b/gui/theme/portrait_hdpi/images/progress_empty.png
new file mode 100644
index 0000000..6f7d652
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/progress_empty.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/progress_fill.png b/gui/theme/portrait_hdpi/images/progress_fill.png
new file mode 100644
index 0000000..41ca321
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/progress_fill.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/radio_false.png b/gui/theme/portrait_hdpi/images/radio_false.png
new file mode 100644
index 0000000..213e2be
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/radio_false.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/radio_true.png b/gui/theme/portrait_hdpi/images/radio_true.png
new file mode 100644
index 0000000..c8499b4
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/radio_true.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/shift.png b/gui/theme/portrait_hdpi/images/shift.png
new file mode 100644
index 0000000..53de2e2
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/shift.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/shift_fill.png b/gui/theme/portrait_hdpi/images/shift_fill.png
new file mode 100644
index 0000000..bc81247
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/shift_fill.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/slider.png b/gui/theme/portrait_hdpi/images/slider.png
new file mode 100644
index 0000000..f2d6799
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/slider.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/slider_touch.png b/gui/theme/portrait_hdpi/images/slider_touch.png
new file mode 100644
index 0000000..1157c9b
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/slider_touch.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/slider_used.png b/gui/theme/portrait_hdpi/images/slider_used.png
new file mode 100644
index 0000000..3501ad1
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/slider_used.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/sort_asc.png b/gui/theme/portrait_hdpi/images/sort_asc.png
new file mode 100644
index 0000000..19f8483
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/sort_asc.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/sort_desc.png b/gui/theme/portrait_hdpi/images/sort_desc.png
new file mode 100644
index 0000000..160af06
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/sort_desc.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/sort_empty.png b/gui/theme/portrait_hdpi/images/sort_empty.png
new file mode 100644
index 0000000..4e2c538
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/sort_empty.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/space.png b/gui/theme/portrait_hdpi/images/space.png
new file mode 100644
index 0000000..be39629
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/space.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/splashlogo.png b/gui/theme/portrait_hdpi/images/splashlogo.png
new file mode 100644
index 0000000..42f516c
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/splashlogo.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/splashteamwin.png b/gui/theme/portrait_hdpi/images/splashteamwin.png
new file mode 100644
index 0000000..16a6d59
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/splashteamwin.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/tab_3.png b/gui/theme/portrait_hdpi/images/tab_3.png
new file mode 100644
index 0000000..0061b6b
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/tab_3.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/tab_4.png b/gui/theme/portrait_hdpi/images/tab_4.png
new file mode 100644
index 0000000..27a3e71
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/tab_4.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/tab_display.png b/gui/theme/portrait_hdpi/images/tab_display.png
new file mode 100644
index 0000000..91e2014
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/tab_display.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/tab_general.png b/gui/theme/portrait_hdpi/images/tab_general.png
new file mode 100644
index 0000000..08a8f27
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/tab_general.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/tab_language.png b/gui/theme/portrait_hdpi/images/tab_language.png
new file mode 100644
index 0000000..a664e97
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/tab_language.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/tab_timezone.png b/gui/theme/portrait_hdpi/images/tab_timezone.png
new file mode 100644
index 0000000..d270ebd
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/tab_timezone.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/tab_vibration.png b/gui/theme/portrait_hdpi/images/tab_vibration.png
new file mode 100644
index 0000000..a227fc9
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/tab_vibration.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/images/unlock_icon.png b/gui/theme/portrait_hdpi/images/unlock_icon.png
new file mode 100644
index 0000000..5865e93
--- /dev/null
+++ b/gui/theme/portrait_hdpi/images/unlock_icon.png
Binary files differ
diff --git a/gui/theme/portrait_hdpi/splash.xml b/gui/theme/portrait_hdpi/splash.xml
new file mode 100644
index 0000000..f793afa
--- /dev/null
+++ b/gui/theme/portrait_hdpi/splash.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<recovery>
+	<details>
+		<resolution width="1080" height="1920"/>
+		<author>TeamWin</author>
+		<title>TWRP</title>
+		<description>splash screen</description>
+		<themeversion>{themeversion}</themeversion>
+	</details>
+
+	<resources>
+		<font name="font_l" filename="RobotoCondensed-Regular.ttf" size="52"/>
+		<image name="splashlogo" filename="splashlogo" retainaspect="1"/>
+		<image name="splashteamwin" filename="splashteamwin" retainaspect="1"/>
+	</resources>
+
+	<variables>
+		<variable name="screen_width" value="1080"/>
+		<variable name="screen_height" value="1920"/>
+		<variable name="background_color" value="#222222"/>
+		<variable name="header_color" value="#555555"/>
+		<variable name="accent_color" value="#0090CA"/>
+	</variables>
+
+	<pages>
+		<page name="splash">
+			<background color="%background_color%"/>
+
+			<fill color="%header_color%">
+				<placement x="0" y="0" w="%screen_width%" h="456"/>
+			</fill>
+
+			<image>
+				<image resource="splashlogo"/>
+				<placement x="540" y="456" placement="4"/>
+			</image>
+
+			<image>
+				<image resource="splashteamwin"/>
+				<placement x="540" y="1540" placement="4"/>
+			</image>
+
+			<text color="%header_color%">
+				<font resource="font_l"/>
+				<placement x="540" y="1590" placement="5"/>
+				<text>Recovery Project %tw_version%</text>
+			</text>
+		</page>
+	</pages>
+</recovery>
+
diff --git a/gui/theme/portrait_hdpi/ui.xml b/gui/theme/portrait_hdpi/ui.xml
new file mode 100755
index 0000000..104c3f6
--- /dev/null
+++ b/gui/theme/portrait_hdpi/ui.xml
@@ -0,0 +1,726 @@
+<?xml version="1.0"?>
+<recovery>
+	<details>
+		<resolution width="1080" height="1920"/>
+		<author>TeamWin</author>
+		<title>Backup Naowz</title>
+		<description>Default basic theme</description>
+		<preview>preview.png</preview>
+		<themeversion>{themeversion}</themeversion>
+	</details>
+
+	<include>
+		<xmlfile name="portrait.xml"/>
+	</include>
+
+	<resources>
+		<font name="font_l" filename="RobotoCondensed-Regular.ttf" size="54"/>
+		<font name="font_m" filename="RobotoCondensed-Regular.ttf" size="42"/>
+		<font name="font_s" filename="RobotoCondensed-Regular.ttf" size="36"/>
+		<font name="fixed" filename="DroidSansMono.ttf" size="30"/>
+
+		<font name="keylabel" filename="RobotoCondensed-Regular.ttf" size="64"/>
+		<font name="keylabel-small" filename="RobotoCondensed-Regular.ttf" size="46"/>
+		<font name="keylabel-longpress" filename="RobotoCondensed-Regular.ttf" size="32"/>
+
+		<image name="logo" filename="logo" retainaspect="1"/>
+		<image name="main_button" filename="main_button"/>
+		<image name="main_button_half_height" filename="main_button_half_height"/>
+		<image name="main_button_half_height_full_width" filename="main_button_half_height_full_width"/>
+		<image name="fab_selectfolder" filename="fab_selectfolder" retainaspect="1"/>
+		<image name="tab_3" filename="tab_3"/>
+		<image name="tab_4" filename="tab_4"/>
+		<image name="file" filename="file" retainaspect="1"/>
+		<image name="folder" filename="folder" retainaspect="1"/>
+		<image name="unlock_icon" filename="unlock_icon" retainaspect="1"/>
+		<image name="home" filename="home" retainaspect="1"/>
+		<image name="back" filename="back" retainaspect="1"/>
+		<image name="console" filename="console" retainaspect="1"/>
+		<image name="kb_hide" filename="kb_hide" retainaspect="1"/>
+		<image name="kb_show" filename="kb_show" retainaspect="1"/>
+		<image name="checkbox_false" filename="checkbox_false" retainaspect="1"/>
+		<image name="checkbox_true" filename="checkbox_true" retainaspect="1"/>
+		<image name="radio_false" filename="radio_false" retainaspect="1"/>
+		<image name="radio_true" filename="radio_true" retainaspect="1"/>
+		<image name="sort_asc" filename="sort_asc"/>
+		<image name="sort_desc" filename="sort_desc"/>
+		<image name="sort_empty" filename="sort_empty"/>
+		<animation name="progress" filename="indeterminate"/>
+		<image name="progress_empty" filename="progress_empty"/>
+		<image name="progress_full" filename="progress_fill"/>
+		<image name="slider" filename="slider"/>
+		<image name="slider_used" filename="slider_used"/>
+		<image name="slider_touch" filename="slider_touch"/>
+		<image name="handle" filename="handle"/>
+		<image name="cursor" filename="cursor" retainaspect="1"/>
+		<image name="tab_general" filename="tab_general" retainaspect="1"/>
+		<image name="tab_timezone" filename="tab_timezone" retainaspect="1"/>
+		<image name="tab_display" filename="tab_display" retainaspect="1"/>
+		<image name="tab_vibration" filename="tab_vibration" retainaspect="1"/>
+		<image name="tab_language" filename="tab_language" retainaspect="1"/>
+
+		<image name="backspace" filename="backspace" retainaspect="1"/>
+		<image name="enter" filename="enter" retainaspect="1"/>
+		<image name="shift" filename="shift" retainaspect="1"/>
+		<image name="shift_fill" filename="shift_fill" retainaspect="1"/>
+		<image name="space" filename="space" retainaspect="1"/>
+		<image name="kb_arrow_left" filename="kb_arrow_left" retainaspect="1"/>
+		<image name="kb_arrow_right" filename="kb_arrow_right" retainaspect="1"/>
+		<image name="kb_arrow_up" filename="kb_arrow_up" retainaspect="1"/>
+		<image name="kb_arrow_down" filename="kb_arrow_down" retainaspect="1"/>
+	</resources>
+
+	<variables>
+		<variable name="tw_samsung_navbar" value="0" persist="1"/>
+
+		<variable name="tw_hide_kb" value="0"/>
+		<variable name="screen_width" value="1080"/>
+		<variable name="screen_height" value="1920"/>
+		<variable name="status_height" value="72"/>
+		<variable name="header_height" value="256"/>
+		<variable name="navbar_height" value="130"/>
+		<variable name="content_width" value="1008"/>
+		<variable name="content_overlay_width" value="936"/>
+		<variable name="dialog_height" value="704"/>
+		<variable name="back_button_x" value="240"/>
+		<variable name="console_button_x" value="840"/>
+		<variable name="indent" value="36"/>
+		<variable name="col1_x_left" value="72"/>
+		<variable name="col1_x_header" value="184"/>
+		<variable name="center_x" value="540"/>
+		<variable name="col1_x_right" value="576"/>
+		<variable name="indent_right" value="1044"/>
+		<variable name="tab_height" value="96"/>
+		<variable name="tab_indicator_height" value="6"/>
+		<variable name="tab_y" value="304"/>
+		<variable name="tab3_width" value="360"/>
+		<variable name="tab4_width" value="270"/>
+		<variable name="tab5_width" value="216"/>
+		<variable name="tab3_col2_x" value="360"/>
+		<variable name="tab3_col3_x" value="720"/>
+		<variable name="tab4_col2_x" value="270"/>
+		<variable name="tab4_col4_x" value="810"/>
+		<variable name="tab5_col2_x" value="216"/>
+		<variable name="tab5_col3_x" value="432"/>
+		<variable name="tab5_col4_x" value="648"/>
+		<variable name="tab5_col5_x" value="864"/>
+		<variable name="tab5_centered_col1_x" value="108"/>
+		<variable name="tab5_centered_col2_x" value="324"/>
+		<variable name="tab5_centered_col4_x" value="756"/>
+		<variable name="tab5_centered_col5_x" value="972"/>
+		<variable name="btn3_col2_x" value="384"/>
+		<variable name="btn3_col3_x" value="732"/>
+		<variable name="btn4_col2_x" value="297"/>
+		<variable name="btn4_col3_x" value="558"/>
+		<variable name="btn4_col4_x" value="819"/>
+		<variable name="status_topalign_header_y" value="4"/>
+		<variable name="status_centeralign_header_y" value="15"/>
+		<variable name="status_bottomalign_header_y" value="30"/>
+		<variable name="row1_header_y" value="{statusicons_align}"/>
+		<variable name="row2_header_y" value="72"/>
+		<variable name="row3_header_y" value="90"/>
+		<variable name="row4_header_y" value="168"/>
+		<variable name="row1_y" value="256"/>
+		<variable name="row1a_y" value="288"/>
+		<variable name="row2_y" value="320"/>
+		<variable name="row2a_y" value="352"/>
+		<variable name="row_tab_y" value="346"/>
+		<variable name="row2_input_y" value="381"/>
+		<variable name="row3_y" value="384"/>
+		<variable name="row3_input_y" value="413"/>
+		<variable name="row3a_y" value="416"/>
+		<variable name="row4_y" value="448"/>
+		<variable name="row4_input_y" value="477"/>
+		<variable name="row4a_y" value="480"/>
+		<variable name="row5_y" value="512"/>
+		<variable name="row5a_y" value="544"/>
+		<variable name="row6_y" value="576"/>
+		<variable name="row6a_y" value="608"/>
+		<variable name="row7_y" value="640"/>
+		<variable name="row7a_y" value="672"/>
+		<variable name="row8_y" value="704"/>
+		<variable name="row8a_y" value="736"/>
+		<variable name="row9_y" value="768"/>
+		<variable name="row9a_y" value="800"/>
+		<variable name="row10_y" value="832"/>
+		<variable name="row10a_y" value="864"/>
+		<variable name="row11_y" value="896"/>
+		<variable name="row11a_y" value="928"/>
+		<variable name="row12_y" value="960"/>
+		<variable name="row12a_y" value="992"/>
+		<variable name="row13_y" value="1024"/>
+		<variable name="row13a_y" value="1056"/>
+		<variable name="row14_y" value="1088"/>
+		<variable name="row14a_y" value="1120"/>
+		<variable name="row15_y" value="1152"/>
+		<variable name="row15a_y" value="1184"/>
+		<variable name="row16_y" value="1216"/>
+		<variable name="row16a_y" value="1248"/>
+		<variable name="row17_y" value="1280"/>
+		<variable name="row17a_y" value="1312"/>
+		<variable name="row18_y" value="1344"/>
+		<variable name="row18a_y" value="1376"/>
+		<variable name="row19_y" value="1408"/>
+		<variable name="row19a_y" value="1440"/>
+		<variable name="row20_y" value="1472"/>
+		<variable name="row20a_y" value="1504"/>
+		<variable name="row21_y" value="1536"/>
+		<variable name="row21a_y" value="1568"/>
+		<variable name="row22_y" value="1600"/>
+		<variable name="row22a_y" value="1632"/>
+		<variable name="row23_y" value="1664"/>
+		<variable name="row23a_y" value="1696"/>
+		<variable name="row24_y" value="1728"/>
+		<variable name="row24a_y" value="1760"/>
+		<variable name="row25_y" value="1792"/>
+		<variable name="navbar_y" value="1792"/>
+		<variable name="navbar_btn_y" value="1856"/>
+		<variable name="keyboard_y" value="1149"/>
+		<variable name="keyboard_terminal_y" value="1053"/>
+		<variable name="background_color" value="#1A1A1A"/>
+		<variable name="accent_color" value="#0090CA"/>
+		<variable name="accent_color_semitransparent" value="#0090CA30"/>
+		<variable name="text_color" value="#EEEEEE"/>
+		<variable name="text_button_color" value="#EEEEEE"/>
+		<variable name="text_success_color" value="#76FF03"/>
+		<variable name="text_fail_color" value="#FF0101"/>
+		<variable name="highlight_color" value="#1A1A1A80"/>
+		<variable name="caps_highlight_color" value="#22222280"/>
+		<variable name="transparent" value="#00000000"/>
+		<variable name="semi_transparent" value="#00000099"/>
+		<variable name="warning" value="#F8F8A0"/>
+		<variable name="error" value="#FF0101"/>
+		<variable name="highlight" value="#0090CA"/>
+		<variable name="fileselector_linecolor" value="#555555"/>
+		<variable name="fileselector_highlight_color" value="#555555"/>
+		<variable name="fileselector_separatorheight" value="2"/>
+		<variable name="fileselector_spacing" value="0"/>
+		<variable name="fileselector_install_height" value="1152"/>
+		<variable name="fileselector_filemanager_height" value="1376"/>
+		<variable name="partitionlist_spacing" value="0"/>
+		<variable name="partitionlist_storage_height" value="544"/>
+		<variable name="partitionlist_flashimage_height" value="544"/>
+		<variable name="partitionlist_wipe_height" value="1088"/>
+		<variable name="partitionlist_mount_height" value="832"/>
+		<variable name="partitionlist_backup_height" value="768"/>
+		<variable name="listbox_timezone_height" value="756"/>
+		<variable name="listbox_settings_height" value="960"/>
+		<variable name="listbox_advanced_height" value="700"/>
+		<variable name="listbox_options_height" value="384"/>
+		<variable name="fastscroll_w" value="24"/>
+		<variable name="fastscroll_linew" value="2"/>
+		<variable name="fastscroll_rectw" value="24"/>
+		<variable name="fastscroll_recth" value="108"/>
+		<variable name="slidervalue_lineh" value="3"/>
+		<variable name="slidervalue_padding" value="0"/>
+		<variable name="slidervalue_sliderw" value="72"/>
+		<variable name="slidervalue_sliderh" value="108"/>
+		<variable name="slideout_height" value="1686"/>
+		<variable name="slideout_bg_height" value="1720"/>
+		<variable name="input_height" value="64"/>
+		<variable name="input_line_width" value="3"/>
+		<variable name="console_height" value="960"/>
+		<variable name="console_terminal_height" value="950"/>
+		<variable name="dialog_button_x" value="696"/>
+		<variable name="pattern_x" value="126"/>
+		<variable name="pattern_dot_dia" value="32"/>
+		<variable name="pattern_line_w" value="12"/>
+		<variable name="pattern_size" value="828"/>
+		<variable name="tw_clock_12_pos_x" value="{clock_12_pos}"/>
+		<variable name="tw_clock_24_pos_x" value="{clock_24_pos}"/>
+		<variable name="tw_cpu_pos_x" value="{cpu_pos}"/>
+		<variable name="tw_battery_pos_x" value="{battery_pos}"/>
+	</variables>
+
+	<mousecursor>
+		<placement w="15" h="15"/>
+		<background color="#FFFF00FF" resource="cursor"/>
+		<speed multiplier="2.5"/>
+	</mousecursor>
+
+	<templates>
+		<template name="page">
+			<background color="%background_color%"/>
+
+			<fill color="%accent_color%">
+				<placement x="0" y="0" w="%screen_width%" h="%header_height%"/>
+			</fill>
+
+			<image>
+				<condition var1="tw_busy" var2="1"/>
+				<image resource="logo"/>
+				<placement x="0" y="0"/>
+			</image>
+
+			<button>
+				<condition var1="tw_busy" var2="0"/>
+				<placement x="0" y="0"/>
+				<image resource="logo"/>
+				<action function="key">home</action>
+			</button>
+
+			<fill color="#00000030">
+				<condition var1="tw_simulate_actions" var2="0"/>
+				<placement x="0" y="0" w="%screen_width%" h="%status_height%"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_simulate_actions" var2="1"/>
+				<placement x="0" y="0" w="%screen_width%" h="%status_height%"/>
+			</fill>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="1"/>
+				<condition var1="tw_cpu_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row1_header_y%"/>
+				<text>%tw_version%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<condition var1="tw_cpu_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row1_header_y%"/>
+				<text>{@cpu_temp=CPU: %tw_cpu_temp% °C}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_12_pos_x" var2="0"/>
+				<condition var1="tw_clock_24_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%center_x%" y="%row1_header_y%" placement="5"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<conditions>
+					<condition var1="tw_no_battery_percent" var2="0"/>
+					<condition var1="tw_battery" op="&gt;" var2="0"/>
+					<condition var1="tw_battery" op="&lt;" var2="101"/>
+					<condition var1="tw_battery_pos_x" var2="0"/>
+				</conditions>
+				<font resource="font_m"/>
+				<placement x="%indent_right%" y="%row1_header_y%" placement="1"/>
+				<text>{@battery_pct=Battery: %tw_battery%}</text>
+			</text>
+
+			<!-- Custom position for status bar items if defined START -->
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="1"/>
+				<condition var1="tw_cpu_pos_x" op="!=" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_cpu_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_version%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<condition var1="tw_cpu_pos_x" op="!=" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_cpu_pos_x%" y="%row1_header_y%"/>
+				<text>{@cpu_temp=CPU: %tw_cpu_temp% °C}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_12_pos_x" op="!=" var2="0"/>
+				<condition var1="tw_military_time" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_clock_12_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_24_pos_x" op="!=" var2="0"/>
+				<condition var1="tw_military_time" var2="1"/>
+				<font resource="font_m"/>
+				<placement x="%tw_clock_24_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<conditions>
+					<condition var1="tw_no_battery_percent" var2="0"/>
+					<condition var1="tw_battery" op="&gt;" var2="0"/>
+					<condition var1="tw_battery" op="&lt;" var2="101"/>
+					<condition var1="tw_battery_pos_x" op="!=" var2="0"/>
+				</conditions>
+				<font resource="font_m"/>
+				<placement x="%tw_battery_pos_x%" y="%row1_header_y%"/>
+				<text>{@battery_pct=Battery: %tw_battery%}</text>
+			</text>
+
+			<!-- Custom position for status bar items if defined END -->
+
+			<fill color="#000000">
+				<condition var1="tw_busy" var2="0"/>
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%"/>
+			</fill>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">home</action>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</template>
+
+		<template name="progress_bar">
+			<progressbar>
+				<placement x="%indent%" y="%row17a_y%"/>
+				<resource empty="progress_empty" full="progress_full"/>
+				<data name="ui_progress"/>
+			</progressbar>
+
+			<animation>
+				<placement x="%indent%" y="%row17a_y%"/>
+				<resource name="progress"/>
+				<speed fps="24" render="2"/>
+				<loop frame="1"/>
+			</animation>
+		</template>
+
+		<template name="sort_options">
+			<image>
+				<placement x="%indent%" y="%row1a_y%"/>
+				<image resource="tab_3"/>
+			</image>
+
+			<image>
+				<placement x="%btn3_col2_x%" y="%row1a_y%"/>
+				<image resource="tab_3"/>
+			</image>
+
+			<image>
+				<placement x="%btn3_col3_x%" y="%row1a_y%"/>
+				<image resource="tab_3"/>
+			</image>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="1"/>
+				<placement x="%indent%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_name_only=Name}</text>
+				<image resource="sort_asc"/>
+				<action function="set">tw_gui_sort_order=-1</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="-1"/>
+				<placement x="%indent%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_name_only=Name}</text>
+				<image resource="sort_desc"/>
+				<action function="set">tw_gui_sort_order=1</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="1"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-1"/>
+				</conditions>
+				<placement x="%indent%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_name_only=Name}</text>
+				<image resource="sort_empty"/>
+				<action function="set">tw_gui_sort_order=1</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="2"/>
+				<placement x="%btn3_col2_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_date_only=Date}</text>
+				<image resource="sort_asc"/>
+				<action function="set">tw_gui_sort_order=-2</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="-2"/>
+				<placement x="%btn3_col2_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_date_only=Date}</text>
+				<image resource="sort_desc"/>
+				<action function="set">tw_gui_sort_order=2</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="2"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-2"/>
+				</conditions>
+				<placement x="%btn3_col2_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_date_only=Date}</text>
+				<image resource="sort_empty"/>
+				<action function="set">tw_gui_sort_order=2</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="3"/>
+				<placement x="%btn3_col3_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_size_only=Size}</text>
+				<image resource="sort_asc"/>
+				<action function="set">tw_gui_sort_order=-3</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="-3"/>
+				<placement x="%btn3_col3_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_size_only=Size}</text>
+				<image resource="sort_desc"/>
+				<action function="set">tw_gui_sort_order=3</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="3"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-3"/>
+				</conditions>
+				<placement x="%btn3_col3_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_size_only=Size}</text>
+				<image resource="sort_empty"/>
+				<action function="set">tw_gui_sort_order=3</action>
+			</button>
+		</template>
+
+		<template name="tabs_backup">
+			<fill color="%accent_color%">
+				<placement x="0" y="row1_y" w="%screen_width%" h="tab_height"/>
+			</fill>
+
+			<button style="tab">
+				<placement x="0" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@tab_backup=BACKUP}</text>
+				<action function="page">backup</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab3_col2_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@tab_options=OPTIONS}</text>
+				<action function="page">backup_options</action>
+			</button>
+		</template>
+
+		<template name="tabs_settings">
+			<fill color="%accent_color%">
+				<placement x="0" y="row1_y" w="%screen_width%" h="tab_height"/>
+			</fill>
+
+			<button style="tab">
+				<placement x="%tab5_centered_col1_x%" y="%tab_y%" placement="4"/>
+				<image resource="tab_general"/>
+				<action function="page">settings</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_centered_col2_x%" y="%tab_y%" placement="4"/>
+				<image resource="tab_timezone"/>
+				<action function="page">settings_timezone</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%center_x%" y="%tab_y%" placement="4"/>
+				<image resource="tab_display"/>
+				<action function="page">settings_screen</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_centered_col4_x%" y="%tab_y%" placement="4"/>
+				<image resource="tab_vibration"/>
+				<action function="page">settings_vibration</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_centered_col5_x%" y="%tab_y%" placement="4"/>
+				<image resource="tab_language"/>
+				<action function="page">settings_language</action>
+			</button>
+		</template>
+
+		<template name="console">
+			<console>
+				<placement x="%indent%" y="%row2_y%" w="%content_width%" h="%console_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row2_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row17_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+		</template>
+
+		<template name="console_terminal">
+			<console>
+				<placement x="%indent%" y="%row2_y%" w="content_width" h="%console_terminal_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row2_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row11_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+		</template>
+
+		<template name="keyboardterminaltemplate">
+			<keyboard>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%keyboard_terminal_y%" w="%screen_width%" h="96"/>
+				<keymargin x="0" y="0"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="12" y="0"/>
+				<keylabel key="0:k:29" text="Ctrl"/>
+				<keylabel key="0:c:27" text="Esc"/>
+				<keylabel key="0:c:9" text="Tab"/>
+				<keylabel key="0:k:105" text="&lt;" resource="kb_arrow_left"/>
+				<keylabel key="0:k:108" text="v" resource="kb_arrow_down"/>
+				<keylabel key="0:k:103" text="^" resource="kb_arrow_up"/>
+				<keylabel key="0:k:106" text="&gt;" resource="kb_arrow_right"/>
+				<highlight color="%highlight_color%"/>
+				<ctrlhighlight color="#0090CA80"/>
+				<layout1>
+					<keysize height="96" width="154"/>
+					<row1 key01="155:k:29" key02="155:c:27" long02=":c:3" key03="154:c:9" key04="154:k:105" key05="154:k:108" key06="154:k:103" key07="154:k:106"/>
+				</layout1>
+			</keyboard>
+		</template>
+
+		<template name="keyboardtemplate">
+			<keyboard>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%keyboard_y%" w="1080" h="644"/>
+				<keymargin x="8" y="8"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="12" y="0"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace"/>
+				<keylabel key="0:action" text="Enter" resource="enter"/>
+				<keylabel key=" " text="Space" resource="space"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<layout1>
+					<keysize height="161" width="108"/>
+					<row1 key01="q" long01="1" key02="w" long02="2" key03="e" long03="3" key04="r" long04="4" key05="t" long05="5" key06="y" long06="6" key07="u" long07="7" key08="i" long08="8" key09="o" long09="9" key10="p" long10="0"/>
+					<row2 key01="54:" key02="a" long02="@" key03="s" long03="#" key04="d" long04="$" key05="f" long05="%" key06="g" long06="&amp;" key07="h" long07="*" key08="j" long08="-" key09="k" long09="+" key10="l" long10="_" key11="54:"/>
+					<row3 key01="162:layout2" key02="z" long02="!" key03="x" long03="&quot;" key04="c" long04="'" key05="v" long05=":" key06="b" long06=";" key07="n" long07="/" key08="m" long08="?" key09="162:c:8"/>
+					<row4 key01="162:layout3" key02="/" key03="540: " key04="." key05="162:action"/>
+					<keylabel key="0:layout2" text="Shift" resource="shift"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout1>
+				<layout2>
+					<keysize height="161" width="108" capslock="0" revert_layout="1"/>
+					<row1 key01="Q" long01="1" key02="W" long02="2" key03="E" long03="3" key04="R" long04="4" key05="T" long05="5" key06="Y" long06="6" key07="U" long07="7" key08="I" long08="8" key09="O" long09="9" key10="P" long10="0"/>
+					<row2 key01="54:" key02="A" long02="@" key03="S" long03="#" key04="D" long04="$" key05="F" long05="%" key06="G" long06="&amp;" key07="H" long07="*" key08="J" long08="-" key09="K" long09="+" key10="L" long10="_" key11="54:"/>
+					<row3 key01="162:layout1" key02="Z" long02="!" key03="X" long03="&quot;" key04="C" long04="'" key05="V" long05=":" key06="B" long06=";" key07="N" long07="/" key08="M" long08="?" key09="162:c:8"/>
+					<row4 key01="162:layout3" key02="/" key03="540: " key04="." key05="162:action"/>
+					<keylabel key="0:layout1" text="Shift" resource="shift_fill"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout2>
+				<layout3>
+					<keysize height="161" width="108"/>
+					<row1 key01="1" key02="2" key03="3" key04="4" key05="5" key06="6" key07="7" key08="8" key09="9" key10="0"/>
+					<row2 key01="@" key02="#" key03="$" key04="%" key05="&amp;" key06="*" key07="-" key08="+" key09="(" key10=")"/>
+					<row3 key01="162:layout4" key02="!" key03="108:c:34" key04="'" key05=":" key06=";" key07="/" key08="?" key09="162:c:8"/>
+					<row4 key01="162:layout1" key02="," key03="540: " key04="." key05="162:action"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout4" text="~\{"/>
+				</layout3>
+				<layout4>
+					<keysize height="161" width="108"/>
+					<row1 key01="~" key02="`" key03="|" key04="108:" key05="108:" key06="108:" key07="%" key08="108:" key09="{" key10="}"/>
+					<row2 key01="108:" key02="108:" key03="108:" key04="108:" key05="108:" key06="^" key07="_" key08="=" key09="[" key10="]"/>
+					<row3 key01="162:layout3" key02="108:" key03="108:" key04="108:" key05="108:" key06="\" key07="&lt;" key08="&gt;" key09="162:c:8"/>
+					<row4 key01="162:layout1" key02="108:c:34" key03="540: " key04="." key05="162:action"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout4>
+			</keyboard>
+		</template>
+
+		<template name="keyboardnum">
+			<keyboard>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%keyboard_y%" w="1080" h="644"/>
+				<keymargin x="8" y="8"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="12" y="0"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace"/>
+				<keylabel key="0:action" text="Enter" resource="enter"/>
+				<keylabel key=" " text="Space" resource="space"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<layout1>
+					<keysize height="160" width="212"/>
+					<row1 key01="225:" key02="1" key03="2" key04="3" key05="225:"/>
+					<row2 key01="225:" key02="4" key03="5" key04="6" key05="225:"/>
+					<row3 key01="225:" key02="7" key03="8" key04="9" key05="225:"/>
+					<row4 key01="225:" key02="212:c:8" key03="0" key04="214:action" key05="225:"/>
+				</layout1>
+			</keyboard>
+		</template>
+	</templates>
+</recovery>
diff --git a/gui/theme/portrait_mdpi/images/back.png b/gui/theme/portrait_mdpi/images/back.png
new file mode 100644
index 0000000..67182bf
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/back.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/backspace.png b/gui/theme/portrait_mdpi/images/backspace.png
new file mode 100644
index 0000000..f7d75db
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/backspace.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/checkbox_false.png b/gui/theme/portrait_mdpi/images/checkbox_false.png
new file mode 100644
index 0000000..0fd57c1
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/checkbox_false.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/checkbox_true.png b/gui/theme/portrait_mdpi/images/checkbox_true.png
new file mode 100644
index 0000000..5a286f3
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/checkbox_true.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/console.png b/gui/theme/portrait_mdpi/images/console.png
new file mode 100644
index 0000000..7223548
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/console.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/cursor.png b/gui/theme/portrait_mdpi/images/cursor.png
new file mode 100644
index 0000000..0c9ae5c
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/cursor.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/enter.png b/gui/theme/portrait_mdpi/images/enter.png
new file mode 100644
index 0000000..1132d64
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/enter.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/fab_selectfolder.png b/gui/theme/portrait_mdpi/images/fab_selectfolder.png
new file mode 100644
index 0000000..c9470eb
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/fab_selectfolder.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/file.png b/gui/theme/portrait_mdpi/images/file.png
new file mode 100644
index 0000000..3d17f15
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/file.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/folder.png b/gui/theme/portrait_mdpi/images/folder.png
new file mode 100644
index 0000000..d9d67f3
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/folder.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/handle.png b/gui/theme/portrait_mdpi/images/handle.png
new file mode 100644
index 0000000..e376523
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/handle.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/home.png b/gui/theme/portrait_mdpi/images/home.png
new file mode 100644
index 0000000..199602b
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/home.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate001.png b/gui/theme/portrait_mdpi/images/indeterminate001.png
new file mode 100644
index 0000000..d3a3e1f
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate001.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate002.png b/gui/theme/portrait_mdpi/images/indeterminate002.png
new file mode 100644
index 0000000..975d4f3
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate002.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate003.png b/gui/theme/portrait_mdpi/images/indeterminate003.png
new file mode 100644
index 0000000..12e165c
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate003.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate004.png b/gui/theme/portrait_mdpi/images/indeterminate004.png
new file mode 100644
index 0000000..3aa6062
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate004.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate005.png b/gui/theme/portrait_mdpi/images/indeterminate005.png
new file mode 100644
index 0000000..8b31bbe
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate005.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate006.png b/gui/theme/portrait_mdpi/images/indeterminate006.png
new file mode 100644
index 0000000..a7dc4e1
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate006.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate007.png b/gui/theme/portrait_mdpi/images/indeterminate007.png
new file mode 100644
index 0000000..213160f
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate007.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate008.png b/gui/theme/portrait_mdpi/images/indeterminate008.png
new file mode 100644
index 0000000..8c63de2
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate008.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate009.png b/gui/theme/portrait_mdpi/images/indeterminate009.png
new file mode 100644
index 0000000..c90e3e0
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate009.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate010.png b/gui/theme/portrait_mdpi/images/indeterminate010.png
new file mode 100644
index 0000000..43fd271
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate010.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate011.png b/gui/theme/portrait_mdpi/images/indeterminate011.png
new file mode 100644
index 0000000..34b13f4
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate011.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/indeterminate012.png b/gui/theme/portrait_mdpi/images/indeterminate012.png
new file mode 100644
index 0000000..ee93701
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/indeterminate012.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/kb_arrow_down.png b/gui/theme/portrait_mdpi/images/kb_arrow_down.png
new file mode 100644
index 0000000..79071b1
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/kb_arrow_down.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/kb_arrow_left.png b/gui/theme/portrait_mdpi/images/kb_arrow_left.png
new file mode 100644
index 0000000..c44a873
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/kb_arrow_left.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/kb_arrow_right.png b/gui/theme/portrait_mdpi/images/kb_arrow_right.png
new file mode 100644
index 0000000..9b4a918
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/kb_arrow_right.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/kb_arrow_up.png b/gui/theme/portrait_mdpi/images/kb_arrow_up.png
new file mode 100644
index 0000000..c55d21e
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/kb_arrow_up.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/kb_hide.png b/gui/theme/portrait_mdpi/images/kb_hide.png
new file mode 100644
index 0000000..4c1a3bf
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/kb_hide.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/kb_show.png b/gui/theme/portrait_mdpi/images/kb_show.png
new file mode 100644
index 0000000..3ec3fe7
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/kb_show.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/logo.png b/gui/theme/portrait_mdpi/images/logo.png
new file mode 100644
index 0000000..6a8be5f
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/logo.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/main_button.png b/gui/theme/portrait_mdpi/images/main_button.png
new file mode 100644
index 0000000..a4b5b61
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/main_button.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/main_button_half_height.png b/gui/theme/portrait_mdpi/images/main_button_half_height.png
new file mode 100644
index 0000000..964c33b
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/main_button_half_height.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/main_button_half_height_full_width.png b/gui/theme/portrait_mdpi/images/main_button_half_height_full_width.png
new file mode 100644
index 0000000..70b8f27
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/main_button_half_height_full_width.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/progress_empty.png b/gui/theme/portrait_mdpi/images/progress_empty.png
new file mode 100644
index 0000000..6f04b81
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/progress_empty.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/progress_fill.png b/gui/theme/portrait_mdpi/images/progress_fill.png
new file mode 100644
index 0000000..6744d3e
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/progress_fill.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/radio_false.png b/gui/theme/portrait_mdpi/images/radio_false.png
new file mode 100644
index 0000000..f7167a3
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/radio_false.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/radio_true.png b/gui/theme/portrait_mdpi/images/radio_true.png
new file mode 100644
index 0000000..cd12eb4
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/radio_true.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/shift.png b/gui/theme/portrait_mdpi/images/shift.png
new file mode 100644
index 0000000..0d99d5a
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/shift.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/shift_fill.png b/gui/theme/portrait_mdpi/images/shift_fill.png
new file mode 100644
index 0000000..88ed853
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/shift_fill.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/slider.png b/gui/theme/portrait_mdpi/images/slider.png
new file mode 100644
index 0000000..0a407d9
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/slider.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/slider_touch.png b/gui/theme/portrait_mdpi/images/slider_touch.png
new file mode 100644
index 0000000..e22d414
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/slider_touch.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/slider_used.png b/gui/theme/portrait_mdpi/images/slider_used.png
new file mode 100644
index 0000000..e4f5293
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/slider_used.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/sort_asc.png b/gui/theme/portrait_mdpi/images/sort_asc.png
new file mode 100644
index 0000000..2be68c8
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/sort_asc.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/sort_desc.png b/gui/theme/portrait_mdpi/images/sort_desc.png
new file mode 100644
index 0000000..26186c5
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/sort_desc.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/sort_empty.png b/gui/theme/portrait_mdpi/images/sort_empty.png
new file mode 100644
index 0000000..b9dc56c
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/sort_empty.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/space.png b/gui/theme/portrait_mdpi/images/space.png
new file mode 100644
index 0000000..51599af
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/space.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/splashlogo.png b/gui/theme/portrait_mdpi/images/splashlogo.png
new file mode 100644
index 0000000..daac294
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/splashlogo.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/splashteamwin.png b/gui/theme/portrait_mdpi/images/splashteamwin.png
new file mode 100644
index 0000000..d2fad9c
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/splashteamwin.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/tab_3.png b/gui/theme/portrait_mdpi/images/tab_3.png
new file mode 100644
index 0000000..65d2ae6
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/tab_3.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/tab_4.png b/gui/theme/portrait_mdpi/images/tab_4.png
new file mode 100644
index 0000000..65d2ae6
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/tab_4.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/tab_display.png b/gui/theme/portrait_mdpi/images/tab_display.png
new file mode 100644
index 0000000..cbd3314
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/tab_display.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/tab_general.png b/gui/theme/portrait_mdpi/images/tab_general.png
new file mode 100644
index 0000000..c953926
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/tab_general.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/tab_language.png b/gui/theme/portrait_mdpi/images/tab_language.png
new file mode 100644
index 0000000..ba01efa
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/tab_language.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/tab_timezone.png b/gui/theme/portrait_mdpi/images/tab_timezone.png
new file mode 100644
index 0000000..298993d
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/tab_timezone.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/tab_vibration.png b/gui/theme/portrait_mdpi/images/tab_vibration.png
new file mode 100644
index 0000000..3117f3b
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/tab_vibration.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/images/unlock_icon.png b/gui/theme/portrait_mdpi/images/unlock_icon.png
new file mode 100644
index 0000000..d19b8e4
--- /dev/null
+++ b/gui/theme/portrait_mdpi/images/unlock_icon.png
Binary files differ
diff --git a/gui/theme/portrait_mdpi/splash.xml b/gui/theme/portrait_mdpi/splash.xml
new file mode 100644
index 0000000..b0b97ee
--- /dev/null
+++ b/gui/theme/portrait_mdpi/splash.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<recovery>
+	<details>
+		<resolution width="480" height="800"/>
+		<author>TeamWin</author>
+		<title>TWRP</title>
+		<description>splash screen</description>
+		<themeversion>{themeversion}</themeversion>
+	</details>
+
+	<resources>
+		<font name="font_l" filename="RobotoCondensed-Regular.ttf" size="24"/>
+		<image name="splashlogo" filename="splashlogo" retainaspect="1"/>
+		<image name="splashteamwin" filename="splashteamwin" retainaspect="1"/>
+	</resources>
+
+	<variables>
+		<variable name="screen_width" value="480"/>
+		<variable name="screen_height" value="800"/>
+		<variable name="background_color" value="#222222"/>
+		<variable name="header_color" value="#555555"/>
+		<variable name="accent_color" value="#0090CA"/>
+	</variables>
+
+	<pages>
+		<page name="splash">
+			<background color="%background_color%"/>
+
+			<fill color="%header_color%">
+				<placement x="0" y="0" w="%screen_width%" h="200"/>
+			</fill>
+
+			<image>
+				<image resource="splashlogo"/>
+				<placement x="240" y="200" placement="4"/>
+			</image>
+
+			<image>
+				<image resource="splashteamwin"/>
+				<placement x="240" y="660" placement="4"/>
+			</image>
+
+			<text color="%header_color%">
+				<font resource="font_l"/>
+				<placement x="240" y="680" placement="5"/>
+				<text>Recovery Project %tw_version%</text>
+			</text>
+		</page>
+	</pages>
+</recovery>
+
diff --git a/gui/theme/portrait_mdpi/ui.xml b/gui/theme/portrait_mdpi/ui.xml
new file mode 100755
index 0000000..d81a9a9
--- /dev/null
+++ b/gui/theme/portrait_mdpi/ui.xml
@@ -0,0 +1,726 @@
+<?xml version="1.0"?>
+<recovery>
+	<details>
+		<resolution width="480" height="800"/>
+		<author>TeamWin</author>
+		<title>Backup Naowz</title>
+		<description>Default basic theme</description>
+		<preview>preview.png</preview>
+		<themeversion>{themeversion}</themeversion>
+	</details>
+
+	<include>
+		<xmlfile name="portrait.xml"/>
+	</include>
+
+	<resources>
+		<font name="font_l" filename="RobotoCondensed-Regular.ttf" size="24"/>
+		<font name="font_m" filename="RobotoCondensed-Regular.ttf" size="18"/>
+		<font name="font_s" filename="RobotoCondensed-Regular.ttf" size="14"/>
+		<font name="fixed" filename="DroidSansMono.ttf" size="12"/>
+
+		<font name="keylabel" filename="RobotoCondensed-Regular.ttf" size="30"/>
+		<font name="keylabel-small" filename="RobotoCondensed-Regular.ttf" size="20"/>
+		<font name="keylabel-longpress" filename="RobotoCondensed-Regular.ttf" size="14"/>
+
+		<image name="logo" filename="logo" retainaspect="1"/>
+		<image name="main_button" filename="main_button"/>
+		<image name="main_button_half_height" filename="main_button_half_height"/>
+		<image name="main_button_half_height_full_width" filename="main_button_half_height_full_width"/>
+		<image name="fab_selectfolder" filename="fab_selectfolder" retainaspect="1"/>
+		<image name="tab_3" filename="tab_3"/>
+		<image name="tab_4" filename="tab_4"/>
+		<image name="file" filename="file" retainaspect="1"/>
+		<image name="folder" filename="folder" retainaspect="1"/>
+		<image name="unlock_icon" filename="unlock_icon" retainaspect="1"/>
+		<image name="home" filename="home" retainaspect="1"/>
+		<image name="back" filename="back" retainaspect="1"/>
+		<image name="console" filename="console" retainaspect="1"/>
+		<image name="kb_hide" filename="kb_hide" retainaspect="1"/>
+		<image name="kb_show" filename="kb_show" retainaspect="1"/>
+		<image name="checkbox_false" filename="checkbox_false" retainaspect="1"/>
+		<image name="checkbox_true" filename="checkbox_true" retainaspect="1"/>
+		<image name="radio_false" filename="radio_false" retainaspect="1"/>
+		<image name="radio_true" filename="radio_true" retainaspect="1"/>
+		<image name="sort_asc" filename="sort_asc"/>
+		<image name="sort_desc" filename="sort_desc"/>
+		<image name="sort_empty" filename="sort_empty"/>
+		<animation name="progress" filename="indeterminate"/>
+		<image name="progress_empty" filename="progress_empty"/>
+		<image name="progress_full" filename="progress_fill"/>
+		<image name="slider" filename="slider"/>
+		<image name="slider_used" filename="slider_used"/>
+		<image name="slider_touch" filename="slider_touch"/>
+		<image name="handle" filename="handle"/>
+		<image name="cursor" filename="cursor" retainaspect="1"/>
+		<image name="tab_general" filename="tab_general" retainaspect="1"/>
+		<image name="tab_timezone" filename="tab_timezone" retainaspect="1"/>
+		<image name="tab_display" filename="tab_display" retainaspect="1"/>
+		<image name="tab_vibration" filename="tab_vibration" retainaspect="1"/>
+		<image name="tab_language" filename="tab_language" retainaspect="1"/>
+
+		<image name="backspace" filename="backspace" retainaspect="1"/>
+		<image name="enter" filename="enter" retainaspect="1"/>
+		<image name="shift" filename="shift" retainaspect="1"/>
+		<image name="shift_fill" filename="shift_fill" retainaspect="1"/>
+		<image name="space" filename="space" retainaspect="1"/>
+		<image name="kb_arrow_left" filename="kb_arrow_left" retainaspect="1"/>
+		<image name="kb_arrow_right" filename="kb_arrow_right" retainaspect="1"/>
+		<image name="kb_arrow_up" filename="kb_arrow_up" retainaspect="1"/>
+		<image name="kb_arrow_down" filename="kb_arrow_down" retainaspect="1"/>
+	</resources>
+
+	<variables>
+		<variable name="tw_samsung_navbar" value="0" persist="1"/>
+
+		<variable name="tw_hide_kb" value="0"/>
+		<variable name="screen_width" value="480"/>
+		<variable name="screen_height" value="800"/>
+		<variable name="status_height" value="30"/>
+		<variable name="header_height" value="110"/>
+		<variable name="navbar_height" value="52"/>
+		<variable name="content_width" value="450"/>
+		<variable name="content_overlay_width" value="420"/>
+		<variable name="dialog_height" value="286"/>
+		<variable name="back_button_x" value="105"/>
+		<variable name="console_button_x" value="375"/>
+		<variable name="indent" value="15"/>
+		<variable name="col1_x_left" value="30"/>
+		<variable name="col1_x_header" value="82"/>
+		<variable name="center_x" value="240"/>
+		<variable name="col1_x_right" value="255"/>
+		<variable name="indent_right" value="465"/>
+		<variable name="tab_height" value="40"/>
+		<variable name="tab_indicator_height" value="3"/>
+		<variable name="tab_y" value="130"/>
+		<variable name="tab3_width" value="160"/>
+		<variable name="tab4_width" value="120"/>
+		<variable name="tab5_width" value="96"/>
+		<variable name="tab3_col2_x" value="160"/>
+		<variable name="tab3_col3_x" value="320"/>
+		<variable name="tab4_col2_x" value="120"/>
+		<variable name="tab4_col4_x" value="360"/>
+		<variable name="tab5_col2_x" value="96"/>
+		<variable name="tab5_col3_x" value="192"/>
+		<variable name="tab5_col4_x" value="288"/>
+		<variable name="tab5_col5_x" value="384"/>
+		<variable name="tab5_centered_col1_x" value="48"/>
+		<variable name="tab5_centered_col2_x" value="144"/>
+		<variable name="tab5_centered_col4_x" value="336"/>
+		<variable name="tab5_centered_col5_x" value="432"/>
+		<variable name="btn3_col2_x" value="170"/>
+		<variable name="btn3_col3_x" value="325"/>
+		<variable name="btn4_col2_x" value="131"/>
+		<variable name="btn4_col3_x" value="247"/>
+		<variable name="btn4_col4_x" value="363"/>
+		<variable name="status_topalign_header_y" value="2"/>
+		<variable name="status_centeralign_header_y" value="6"/>
+		<variable name="status_bottomalign_header_y" value="12"/>
+		<variable name="row1_header_y" value="{statusicons_align}"/>
+		<variable name="row2_header_y" value="30"/>
+		<variable name="row3_header_y" value="40"/>
+		<variable name="row4_header_y" value="74"/>
+		<variable name="row1_y" value="110"/>
+		<variable name="row1a_y" value="123"/>
+		<variable name="row2_y" value="136"/>
+		<variable name="row2a_y" value="149"/>
+		<variable name="row_tab_y" value="147"/>
+		<variable name="row2_input_y" value="162"/>
+		<variable name="row3_y" value="162"/>
+		<variable name="row3_input_y" value="175"/>
+		<variable name="row3a_y" value="175"/>
+		<variable name="row4_y" value="188"/>
+		<variable name="row4_input_y" value="201"/>
+		<variable name="row4a_y" value="201"/>
+		<variable name="row5_y" value="214"/>
+		<variable name="row5a_y" value="227"/>
+		<variable name="row6_y" value="240"/>
+		<variable name="row6a_y" value="253"/>
+		<variable name="row7_y" value="266"/>
+		<variable name="row7a_y" value="279"/>
+		<variable name="row8_y" value="292"/>
+		<variable name="row8a_y" value="305"/>
+		<variable name="row9_y" value="318"/>
+		<variable name="row9a_y" value="331"/>
+		<variable name="row10_y" value="344"/>
+		<variable name="row10a_y" value="357"/>
+		<variable name="row11_y" value="370"/>
+		<variable name="row11a_y" value="383"/>
+		<variable name="row12_y" value="396"/>
+		<variable name="row12a_y" value="409"/>
+		<variable name="row13_y" value="422"/>
+		<variable name="row13a_y" value="435"/>
+		<variable name="row14_y" value="448"/>
+		<variable name="row14a_y" value="461"/>
+		<variable name="row15_y" value="474"/>
+		<variable name="row15a_y" value="487"/>
+		<variable name="row16_y" value="500"/>
+		<variable name="row16a_y" value="513"/>
+		<variable name="row17_y" value="526"/>
+		<variable name="row17a_y" value="539"/>
+		<variable name="row18_y" value="552"/>
+		<variable name="row18a_y" value="565"/>
+		<variable name="row19_y" value="578"/>
+		<variable name="row19a_y" value="591"/>
+		<variable name="row20_y" value="604"/>
+		<variable name="row20a_y" value="617"/>
+		<variable name="row21_y" value="630"/>
+		<variable name="row21a_y" value="643"/>
+		<variable name="row22_y" value="656"/>
+		<variable name="row22a_y" value="669"/>
+		<variable name="row23_y" value="682"/>
+		<variable name="row23a_y" value="695"/>
+		<variable name="row24_y" value="708"/>
+		<variable name="row24a_y" value="721"/>
+		<variable name="row25_y" value="734"/>
+		<variable name="navbar_y" value="748"/>
+		<variable name="navbar_btn_y" value="774"/>
+		<variable name="keyboard_y" value="464"/>
+		<variable name="keyboard_terminal_y" value="422"/>
+		<variable name="background_color" value="#1A1A1A"/>
+		<variable name="accent_color" value="#0090CA"/>
+		<variable name="accent_color_semitransparent" value="#0090CA30"/>
+		<variable name="text_color" value="#EEEEEE"/>
+		<variable name="text_button_color" value="#EEEEEE"/>
+		<variable name="text_success_color" value="#76FF03"/>
+		<variable name="text_fail_color" value="#FF0101"/>
+		<variable name="highlight_color" value="#1A1A1A80"/>
+		<variable name="caps_highlight_color" value="#22222280"/>
+		<variable name="transparent" value="#00000000"/>
+		<variable name="semi_transparent" value="#00000099"/>
+		<variable name="warning" value="#F8F8A0"/>
+		<variable name="error" value="#FF0101"/>
+		<variable name="highlight" value="#0090CA"/>
+		<variable name="fileselector_linecolor" value="#555555"/>
+		<variable name="fileselector_highlight_color" value="#555555"/>
+		<variable name="fileselector_separatorheight" value="1"/>
+		<variable name="fileselector_spacing" value="0"/>
+		<variable name="fileselector_install_height" value="468"/>
+		<variable name="fileselector_filemanager_height" value="559"/>
+		<variable name="partitionlist_spacing" value="0"/>
+		<variable name="partitionlist_storage_height" value="221"/>
+		<variable name="partitionlist_flashimage_height" value="221"/>
+		<variable name="partitionlist_wipe_height" value="442"/>
+		<variable name="partitionlist_mount_height" value="338"/>
+		<variable name="partitionlist_backup_height" value="312"/>
+		<variable name="listbox_timezone_height" value="306"/>
+		<variable name="listbox_settings_height" value="400"/>
+		<variable name="listbox_advanced_height" value="290"/>
+		<variable name="listbox_options_height" value="160"/>
+		<variable name="fastscroll_w" value="10"/>
+		<variable name="fastscroll_linew" value="2"/>
+		<variable name="fastscroll_rectw" value="10"/>
+		<variable name="fastscroll_recth" value="52"/>
+		<variable name="slidervalue_lineh" value="2"/>
+		<variable name="slidervalue_padding" value="0"/>
+		<variable name="slidervalue_sliderw" value="32"/>
+		<variable name="slidervalue_sliderh" value="48"/>
+		<variable name="slideout_height" value="700"/>
+		<variable name="slideout_bg_height" value="718"/>
+		<variable name="input_height" value="26"/>
+		<variable name="input_line_width" value="2"/>
+		<variable name="console_height" value="390"/>
+		<variable name="console_terminal_height" value="369"/>
+		<variable name="dialog_button_x" value="310"/>
+		<variable name="pattern_x" value="60"/>
+		<variable name="pattern_dot_dia" value="12"/>
+		<variable name="pattern_line_w" value="4"/>
+		<variable name="pattern_size" value="360"/>
+		<variable name="tw_clock_12_pos_x" value="{clock_12_pos}"/>
+		<variable name="tw_clock_24_pos_x" value="{clock_24_pos}"/>
+		<variable name="tw_cpu_pos_x" value="{cpu_pos}"/>
+		<variable name="tw_battery_pos_x" value="{battery_pos}"/>
+	</variables>
+
+	<mousecursor>
+		<placement w="15" h="15"/>
+		<background color="#FFFF00FF" resource="cursor"/>
+		<speed multiplier="2.5"/>
+	</mousecursor>
+
+	<templates>
+		<template name="page">
+			<background color="%background_color%"/>
+
+			<fill color="%accent_color%">
+				<placement x="0" y="0" w="%screen_width%" h="%header_height%"/>
+			</fill>
+
+			<image>
+				<condition var1="tw_busy" var2="1"/>
+				<image resource="logo"/>
+				<placement x="0" y="0"/>
+			</image>
+
+			<button>
+				<condition var1="tw_busy" var2="0"/>
+				<placement x="0" y="0"/>
+				<image resource="logo"/>
+				<action function="key">home</action>
+			</button>
+
+			<fill color="#00000030">
+				<condition var1="tw_simulate_actions" var2="0"/>
+				<placement x="0" y="0" w="%screen_width%" h="%status_height%"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_simulate_actions" var2="1"/>
+				<placement x="0" y="0" w="%screen_width%" h="%status_height%"/>
+			</fill>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="1"/>
+				<condition var1="tw_cpu_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row1_header_y%"/>
+				<text>%tw_version%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<condition var1="tw_cpu_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row1_header_y%"/>
+				<text>{@cpu_temp=CPU: %tw_cpu_temp% °C}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_12_pos_x" var2="0"/>
+				<condition var1="tw_clock_24_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%center_x%" y="%row1_header_y%" placement="5"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<conditions>
+					<condition var1="tw_no_battery_percent" var2="0"/>
+					<condition var1="tw_battery" op="&gt;" var2="0"/>
+					<condition var1="tw_battery" op="&lt;" var2="101"/>
+					<condition var1="tw_battery_pos_x" var2="0"/>
+				</conditions>
+				<font resource="font_m"/>
+				<placement x="%indent_right%" y="%row1_header_y%" placement="1"/>
+				<text>{@battery_pct=Battery: %tw_battery%}</text>
+			</text>
+
+			<!-- Custom position for status bar items if defined START -->
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="1"/>
+				<condition var1="tw_cpu_pos_x" op="!=" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_cpu_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_version%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<condition var1="tw_cpu_pos_x" op="!=" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_cpu_pos_x%" y="%row1_header_y%"/>
+				<text>{@cpu_temp=CPU: %tw_cpu_temp% °C}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_12_pos_x" op="!=" var2="0"/>
+				<condition var1="tw_military_time" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_clock_12_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_24_pos_x" op="!=" var2="0"/>
+				<condition var1="tw_military_time" var2="1"/>
+				<font resource="font_m"/>
+				<placement x="%tw_clock_24_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<conditions>
+					<condition var1="tw_no_battery_percent" var2="0"/>
+					<condition var1="tw_battery" op="&gt;" var2="0"/>
+					<condition var1="tw_battery" op="&lt;" var2="101"/>
+					<condition var1="tw_battery_pos_x" op="!=" var2="0"/>
+				</conditions>
+				<font resource="font_m"/>
+				<placement x="%tw_battery_pos_x%" y="%row1_header_y%"/>
+				<text>{@battery_pct=Battery: %tw_battery%}</text>
+			</text>
+
+			<!-- Custom position for status bar items if defined END -->
+
+			<fill color="#000000">
+				<condition var1="tw_busy" var2="0"/>
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%"/>
+			</fill>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">home</action>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="!=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<button>
+				<condition var1="tw_samsung_navbar" op="=" var2="1"/>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</template>
+
+		<template name="progress_bar">
+			<progressbar>
+				<placement x="%indent%" y="%row17a_y%"/>
+				<resource empty="progress_empty" full="progress_full"/>
+				<data name="ui_progress"/>
+			</progressbar>
+
+			<animation>
+				<placement x="%indent%" y="%row17a_y%"/>
+				<resource name="progress"/>
+				<speed fps="24" render="2"/>
+				<loop frame="1"/>
+			</animation>
+		</template>
+
+		<template name="sort_options">
+			<image>
+				<placement x="%indent%" y="%row1a_y%"/>
+				<image resource="tab_3"/>
+			</image>
+
+			<image>
+				<placement x="%btn3_col2_x%" y="%row1a_y%"/>
+				<image resource="tab_3"/>
+			</image>
+
+			<image>
+				<placement x="%btn3_col3_x%" y="%row1a_y%"/>
+				<image resource="tab_3"/>
+			</image>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="1"/>
+				<placement x="%indent%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_name_only=Name}</text>
+				<image resource="sort_asc"/>
+				<action function="set">tw_gui_sort_order=-1</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="-1"/>
+				<placement x="%indent%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_name_only=Name}</text>
+				<image resource="sort_desc"/>
+				<action function="set">tw_gui_sort_order=1</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="1"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-1"/>
+				</conditions>
+				<placement x="%indent%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_name_only=Name}</text>
+				<image resource="sort_empty"/>
+				<action function="set">tw_gui_sort_order=1</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="2"/>
+				<placement x="%btn3_col2_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_date_only=Date}</text>
+				<image resource="sort_asc"/>
+				<action function="set">tw_gui_sort_order=-2</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="-2"/>
+				<placement x="%btn3_col2_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_date_only=Date}</text>
+				<image resource="sort_desc"/>
+				<action function="set">tw_gui_sort_order=2</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="2"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-2"/>
+				</conditions>
+				<placement x="%btn3_col2_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_date_only=Date}</text>
+				<image resource="sort_empty"/>
+				<action function="set">tw_gui_sort_order=2</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="3"/>
+				<placement x="%btn3_col3_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_size_only=Size}</text>
+				<image resource="sort_asc"/>
+				<action function="set">tw_gui_sort_order=-3</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<condition var1="tw_gui_sort_order" op="=" var2="-3"/>
+				<placement x="%btn3_col3_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_size_only=Size}</text>
+				<image resource="sort_desc"/>
+				<action function="set">tw_gui_sort_order=3</action>
+			</button>
+
+			<button>
+				<highlight color="%highlight_color%"/>
+				<conditions>
+					<condition var1="tw_gui_sort_order" op="!=" var2="3"/>
+					<condition var1="tw_gui_sort_order" op="!=" var2="-3"/>
+				</conditions>
+				<placement x="%btn3_col3_x%" y="%row1a_y%"/>
+				<font resource="font_s" color="%text_button_color%"/>
+				<text>{@sort_by_size_only=Size}</text>
+				<image resource="sort_empty"/>
+				<action function="set">tw_gui_sort_order=3</action>
+			</button>
+		</template>
+
+		<template name="tabs_backup">
+			<fill color="%accent_color%">
+				<placement x="0" y="row1_y" w="%screen_width%" h="tab_height"/>
+			</fill>
+
+			<button style="tab">
+				<placement x="0" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@tab_backup=BACKUP}</text>
+				<action function="page">backup</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab3_col2_x%" y="%row1_y%" w="%tab3_width%" h="%tab_height%"/>
+				<text>{@tab_options=OPTIONS}</text>
+				<action function="page">backup_options</action>
+			</button>
+		</template>
+
+		<template name="tabs_settings">
+			<fill color="%accent_color%">
+				<placement x="0" y="row1_y" w="%screen_width%" h="tab_height"/>
+			</fill>
+
+			<button style="tab">
+				<placement x="%tab5_centered_col1_x%" y="%tab_y%" placement="4"/>
+				<image resource="tab_general"/>
+				<action function="page">settings</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_centered_col2_x%" y="%tab_y%" placement="4"/>
+				<image resource="tab_timezone"/>
+				<action function="page">settings_timezone</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%center_x%" y="%tab_y%" placement="4"/>
+				<image resource="tab_display"/>
+				<action function="page">settings_screen</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_centered_col4_x%" y="%tab_y%" placement="4"/>
+				<image resource="tab_vibration"/>
+				<action function="page">settings_vibration</action>
+			</button>
+
+			<button style="tab">
+				<placement x="%tab5_centered_col5_x%" y="%tab_y%" placement="4"/>
+				<image resource="tab_language"/>
+				<action function="page">settings_language</action>
+			</button>
+		</template>
+
+		<template name="console">
+			<console>
+				<placement x="%indent%" y="%row2_y%" w="%content_width%" h="%console_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row2_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row17_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+		</template>
+
+		<template name="console_terminal">
+			<console>
+				<placement x="%indent%" y="%row2_y%" w="content_width" h="%console_terminal_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row2_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row11_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+		</template>
+
+		<template name="keyboardterminaltemplate">
+			<keyboard>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%keyboard_terminal_y%" w="%screen_width%" h="42"/>
+				<keymargin x="0" y="0"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="12" y="0"/>
+				<keylabel key="0:k:29" text="Ctrl"/>
+				<keylabel key="0:c:27" text="Esc"/>
+				<keylabel key="0:c:9" text="Tab"/>
+				<keylabel key="0:k:105" text="&lt;" resource="kb_arrow_left"/>
+				<keylabel key="0:k:108" text="v" resource="kb_arrow_down"/>
+				<keylabel key="0:k:103" text="^" resource="kb_arrow_up"/>
+				<keylabel key="0:k:106" text="&gt;" resource="kb_arrow_right"/>
+				<highlight color="%highlight_color%"/>
+				<ctrlhighlight color="#0090CA80"/>
+				<layout1>
+					<keysize height="42" width="69"/>
+					<row1 key01="68:k:29" key02="68:c:27" long02=":c:3" key03="68:c:9" key04="69:k:105" key05="69:k:108" key06="69:k:103" key07="69:k:106"/>
+				</layout1>
+			</keyboard>
+		</template>
+
+		<template name="keyboardtemplate">
+			<keyboard>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%keyboard_y%" w="480" h="284"/>
+				<keymargin x="4" y="4"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="6" y="0"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace"/>
+				<keylabel key="0:action" text="Enter" resource="enter"/>
+				<keylabel key=" " text="Space" resource="space"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<layout1>
+					<keysize height="71" width="48"/>
+					<row1 key01="q" long01="1" key02="w" long02="2" key03="e" long03="3" key04="r" long04="4" key05="t" long05="5" key06="y" long06="6" key07="u" long07="7" key08="i" long08="8" key09="o" long09="9" key10="p" long10="0"/>
+					<row2 key01="24:" key02="a" long02="@" key03="s" long03="#" key04="d" long04="$" key05="f" long05="%" key06="g" long06="&amp;" key07="h" long07="*" key08="j" long08="-" key09="k" long09="+" key10="l" long10="_" key11="24:"/>
+					<row3 key01="72:layout2" key02="z" long02="!" key03="x" long03="&quot;" key04="c" long04="'" key05="v" long05=":" key06="b" long06=";" key07="n" long07="/" key08="m" long08="?" key09="72:c:8"/>
+					<row4 key01="72:layout3" key02="/" key03="240: " key04="." key05="72:action"/>
+					<keylabel key="0:layout2" text="Shift" resource="shift"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout1>
+				<layout2>
+					<keysize height="71" width="48" capslock="0" revert_layout="1"/>
+					<row1 key01="Q" long01="1" key02="W" long02="2" key03="E" long03="3" key04="R" long04="4" key05="T" long05="5" key06="Y" long06="6" key07="U" long07="7" key08="I" long08="8" key09="O" long09="9" key10="P" long10="0"/>
+					<row2 key01="24:" key02="A" long02="@" key03="S" long03="#" key04="D" long04="$" key05="F" long05="%" key06="G" long06="&amp;" key07="H" long07="*" key08="J" long08="-" key09="K" long09="+" key10="L" long10="_" key11="24:"/>
+					<row3 key01="72:layout1" key02="Z" long02="!" key03="X" long03="&quot;" key04="C" long04="'" key05="V" long05=":" key06="B" long06=";" key07="N" long07="/" key08="M" long08="?" key09="72:c:8"/>
+					<row4 key01="72:layout3" key02="/" key03="240: " key04="." key05="72:action"/>
+					<keylabel key="0:layout1" text="Shift" resource="shift_fill"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout2>
+				<layout3>
+					<keysize height="71" width="48"/>
+					<row1 key01="1" key02="2" key03="3" key04="4" key05="5" key06="6" key07="7" key08="8" key09="9" key10="0"/>
+					<row2 key01="@" key02="#" key03="$" key04="%" key05="&amp;" key06="*" key07="-" key08="+" key09="(" key10=")"/>
+					<row3 key01="72:layout4" key02="!" key03="48:c:34" key04="'" key05=":" key06=";" key07="/" key08="?" key09="72:c:8"/>
+					<row4 key01="72:layout1" key02="," key03="240: " key04="." key05="72:action"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout4" text="~\{"/>
+				</layout3>
+				<layout4>
+					<keysize height="71" width="48"/>
+					<row1 key01="~" key02="`" key03="|" key04="48:" key05="48:" key06="48:" key07="%" key08="48:" key09="{" key10="}"/>
+					<row2 key01="48:" key02="48:" key03="48:" key04="48:" key05="48:" key06="^" key07="_" key08="=" key09="[" key10="]"/>
+					<row3 key01="72:layout3" key02="48:" key03="48:" key04="48:" key05="48:" key06="\" key07="&lt;" key08="&gt;" key09="72:c:8"/>
+					<row4 key01="72:layout1" key02="48:c:34" key03="240: " key04="." key05="72:action"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout4>
+			</keyboard>
+		</template>
+
+		<template name="keyboardnum">
+			<keyboard>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%keyboard_y%" w="480" h="284"/>
+				<keymargin x="4" y="4"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="6" y="0"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace"/>
+				<keylabel key="0:action" text="Enter" resource="enter"/>
+				<keylabel key=" " text="Space" resource="space"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<layout1>
+					<keysize height="70" width="94"/>
+					<row1 key01="100:" key02="1" key03="2" key04="3" key05="100:"/>
+					<row2 key01="100:" key02="4" key03="5" key04="6" key05="100:"/>
+					<row3 key01="100:" key02="7" key03="8" key04="9" key05="100:"/>
+					<row4 key01="100:" key02="94:c:8" key03="0" key04="95:action" key05="100:"/>
+				</layout1>
+			</keyboard>
+		</template>
+	</templates>
+</recovery>
diff --git a/gui/theme/watch_mdpi/images/back.png b/gui/theme/watch_mdpi/images/back.png
new file mode 100644
index 0000000..b4bcda7
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/back.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/backspace.png b/gui/theme/watch_mdpi/images/backspace.png
new file mode 100644
index 0000000..cf22b65
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/backspace.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/checkbox_false.png b/gui/theme/watch_mdpi/images/checkbox_false.png
new file mode 100644
index 0000000..4439e45
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/checkbox_false.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/checkbox_false_small.png b/gui/theme/watch_mdpi/images/checkbox_false_small.png
new file mode 100644
index 0000000..e6b0b44
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/checkbox_false_small.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/checkbox_true.png b/gui/theme/watch_mdpi/images/checkbox_true.png
new file mode 100644
index 0000000..b0b7709
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/checkbox_true.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/checkbox_true_small.png b/gui/theme/watch_mdpi/images/checkbox_true_small.png
new file mode 100644
index 0000000..ae8a080
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/checkbox_true_small.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/close.png b/gui/theme/watch_mdpi/images/close.png
new file mode 100644
index 0000000..95c0cf4
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/close.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/console.png b/gui/theme/watch_mdpi/images/console.png
new file mode 100644
index 0000000..cf94aa8
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/console.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/enter.png b/gui/theme/watch_mdpi/images/enter.png
new file mode 100644
index 0000000..e831bed
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/enter.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/file.png b/gui/theme/watch_mdpi/images/file.png
new file mode 100644
index 0000000..ea2cedf
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/file.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/folder.png b/gui/theme/watch_mdpi/images/folder.png
new file mode 100644
index 0000000..3d5be9a
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/folder.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/grid_less.png b/gui/theme/watch_mdpi/images/grid_less.png
new file mode 100644
index 0000000..d71867f
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/grid_less.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/grid_more.png b/gui/theme/watch_mdpi/images/grid_more.png
new file mode 100644
index 0000000..4f03d93
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/grid_more.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/handle.png b/gui/theme/watch_mdpi/images/handle.png
new file mode 100644
index 0000000..f1c9e32
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/handle.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/home.png b/gui/theme/watch_mdpi/images/home.png
new file mode 100644
index 0000000..42955ba
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/home.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate001.png b/gui/theme/watch_mdpi/images/indeterminate001.png
new file mode 100644
index 0000000..e1431ed
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate001.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate002.png b/gui/theme/watch_mdpi/images/indeterminate002.png
new file mode 100644
index 0000000..be06bd6
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate002.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate003.png b/gui/theme/watch_mdpi/images/indeterminate003.png
new file mode 100644
index 0000000..e47fae3
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate003.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate004.png b/gui/theme/watch_mdpi/images/indeterminate004.png
new file mode 100644
index 0000000..1931b60
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate004.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate005.png b/gui/theme/watch_mdpi/images/indeterminate005.png
new file mode 100644
index 0000000..f372669
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate005.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate006.png b/gui/theme/watch_mdpi/images/indeterminate006.png
new file mode 100644
index 0000000..2a1f1da
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate006.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate007.png b/gui/theme/watch_mdpi/images/indeterminate007.png
new file mode 100644
index 0000000..5244e53
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate007.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate008.png b/gui/theme/watch_mdpi/images/indeterminate008.png
new file mode 100644
index 0000000..cf20bb1
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate008.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate009.png b/gui/theme/watch_mdpi/images/indeterminate009.png
new file mode 100644
index 0000000..f8a7547
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate009.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate010.png b/gui/theme/watch_mdpi/images/indeterminate010.png
new file mode 100644
index 0000000..bfd0a6d
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate010.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate011.png b/gui/theme/watch_mdpi/images/indeterminate011.png
new file mode 100644
index 0000000..004b77f
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate011.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/indeterminate012.png b/gui/theme/watch_mdpi/images/indeterminate012.png
new file mode 100644
index 0000000..a677077
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/indeterminate012.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/kb_arrow_down.png b/gui/theme/watch_mdpi/images/kb_arrow_down.png
new file mode 100644
index 0000000..1439b59
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/kb_arrow_down.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/kb_arrow_left.png b/gui/theme/watch_mdpi/images/kb_arrow_left.png
new file mode 100644
index 0000000..2afb82c
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/kb_arrow_left.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/kb_arrow_right.png b/gui/theme/watch_mdpi/images/kb_arrow_right.png
new file mode 100644
index 0000000..fb565b9
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/kb_arrow_right.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/kb_arrow_up.png b/gui/theme/watch_mdpi/images/kb_arrow_up.png
new file mode 100644
index 0000000..af00e75
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/kb_arrow_up.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/kb_hide.png b/gui/theme/watch_mdpi/images/kb_hide.png
new file mode 100644
index 0000000..170cf33
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/kb_hide.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/kb_show.png b/gui/theme/watch_mdpi/images/kb_show.png
new file mode 100644
index 0000000..4115c63
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/kb_show.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/main_button.png b/gui/theme/watch_mdpi/images/main_button.png
new file mode 100644
index 0000000..769a1b3
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/main_button.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/main_button_full_width.png b/gui/theme/watch_mdpi/images/main_button_full_width.png
new file mode 100644
index 0000000..19012c4
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/main_button_full_width.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/main_button_half_height.png b/gui/theme/watch_mdpi/images/main_button_half_height.png
new file mode 100644
index 0000000..2206048
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/main_button_half_height.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/main_button_quarter_width.png b/gui/theme/watch_mdpi/images/main_button_quarter_width.png
new file mode 100644
index 0000000..f068f5f
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/main_button_quarter_width.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/progress_empty.png b/gui/theme/watch_mdpi/images/progress_empty.png
new file mode 100644
index 0000000..561d49c
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/progress_empty.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/progress_fill.png b/gui/theme/watch_mdpi/images/progress_fill.png
new file mode 100644
index 0000000..54c0e1b
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/progress_fill.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_accept.png b/gui/theme/watch_mdpi/images/q_btn_accept.png
new file mode 100644
index 0000000..4c04f22
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_accept.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_accept_transp.png b/gui/theme/watch_mdpi/images/q_btn_accept_transp.png
new file mode 100644
index 0000000..9d1c177
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_accept_transp.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_addzip.png b/gui/theme/watch_mdpi/images/q_btn_addzip.png
new file mode 100644
index 0000000..6ca0135
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_addzip.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_arrow_left.png b/gui/theme/watch_mdpi/images/q_btn_arrow_left.png
new file mode 100644
index 0000000..b07d2d0
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_arrow_left.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_arrow_right.png b/gui/theme/watch_mdpi/images/q_btn_arrow_right.png
new file mode 100644
index 0000000..fb0c9ea
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_arrow_right.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_delete.png b/gui/theme/watch_mdpi/images/q_btn_delete.png
new file mode 100644
index 0000000..8922498
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_delete.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_encryption_dis.png b/gui/theme/watch_mdpi/images/q_btn_encryption_dis.png
new file mode 100644
index 0000000..8480075
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_encryption_dis.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_encryption_en.png b/gui/theme/watch_mdpi/images/q_btn_encryption_en.png
new file mode 100644
index 0000000..6ac2a91
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_encryption_en.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_folder.png b/gui/theme/watch_mdpi/images/q_btn_folder.png
new file mode 100644
index 0000000..eccba87
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_folder.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_refresh.png b/gui/theme/watch_mdpi/images/q_btn_refresh.png
new file mode 100644
index 0000000..a7762d8
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_refresh.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_reset.png b/gui/theme/watch_mdpi/images/q_btn_reset.png
new file mode 100644
index 0000000..f05f43c
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_reset.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/q_btn_storage.png b/gui/theme/watch_mdpi/images/q_btn_storage.png
new file mode 100644
index 0000000..ddfddb4
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/q_btn_storage.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/radio_false.png b/gui/theme/watch_mdpi/images/radio_false.png
new file mode 100644
index 0000000..c517286
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/radio_false.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/radio_true.png b/gui/theme/watch_mdpi/images/radio_true.png
new file mode 100644
index 0000000..523cecc
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/radio_true.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/shift.png b/gui/theme/watch_mdpi/images/shift.png
new file mode 100644
index 0000000..0770972
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/shift.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/shift_fill.png b/gui/theme/watch_mdpi/images/shift_fill.png
new file mode 100644
index 0000000..0941aa6
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/shift_fill.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/slider.png b/gui/theme/watch_mdpi/images/slider.png
new file mode 100644
index 0000000..a2083c5
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/slider.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/slider_touch.png b/gui/theme/watch_mdpi/images/slider_touch.png
new file mode 100644
index 0000000..9fcb729
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/slider_touch.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/slider_used.png b/gui/theme/watch_mdpi/images/slider_used.png
new file mode 100644
index 0000000..d1df845
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/slider_used.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/space.png b/gui/theme/watch_mdpi/images/space.png
new file mode 100644
index 0000000..44d7121
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/space.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/splashlogo.png b/gui/theme/watch_mdpi/images/splashlogo.png
new file mode 100644
index 0000000..24fc4e7
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/splashlogo.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/splashteamwin.png b/gui/theme/watch_mdpi/images/splashteamwin.png
new file mode 100644
index 0000000..47885cc
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/splashteamwin.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/images/unlock_icon.png b/gui/theme/watch_mdpi/images/unlock_icon.png
new file mode 100644
index 0000000..b2b5f8b
--- /dev/null
+++ b/gui/theme/watch_mdpi/images/unlock_icon.png
Binary files differ
diff --git a/gui/theme/watch_mdpi/splash.xml b/gui/theme/watch_mdpi/splash.xml
new file mode 100644
index 0000000..2ce71b3
--- /dev/null
+++ b/gui/theme/watch_mdpi/splash.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<recovery>
+	<details>
+		<resolution width="320" height="320"/>
+		<author>TeamWin</author>
+		<title>TWRP</title>
+		<description>splash screen</description>
+		<themeversion>{themeversion}</themeversion>
+	</details>
+
+	<resources>
+		<font name="font_l" filename="RobotoCondensed-Regular.ttf" size="24"/>
+		<image name="splashlogo" filename="splashlogo" retainaspect="1"/>
+		<image name="splashteamwin" filename="splashteamwin" retainaspect="1"/>
+	</resources>
+
+	<variables>
+		<variable name="screen_width" value="320"/>
+		<variable name="screen_height" value="320"/>
+		<variable name="background_color" value="#222222"/>
+		<variable name="header_color" value="#555555"/>
+		<variable name="accent_color" value="#0090CA"/>
+	</variables>
+
+	<pages>
+		<page name="splash">
+			<background color="%background_color%"/>
+
+			<fill color="%header_color%">
+				<placement x="0" y="0" w="%screen_width%" h="120"/>
+			</fill>
+
+			<image>
+				<image resource="splashlogo"/>
+				<placement x="160" y="120" placement="4"/>
+			</image>
+
+			<image>
+				<image resource="splashteamwin"/>
+				<placement x="160" y="270" placement="4"/>
+			</image>
+
+			<text color="%header_color%">
+				<font resource="font_l"/>
+				<placement x="160" y="290" placement="5"/>
+				<text>Recovery Project %tw_version%</text>
+			</text>
+		</page>
+	</pages>
+</recovery>
+
diff --git a/gui/theme/watch_mdpi/ui.xml b/gui/theme/watch_mdpi/ui.xml
new file mode 100644
index 0000000..8396639
--- /dev/null
+++ b/gui/theme/watch_mdpi/ui.xml
@@ -0,0 +1,476 @@
+<?xml version="1.0"?>
+<recovery>
+	<details>
+		<resolution width="320" height="320"/>
+		<roundscreen offset_x="40" offset_y="40"/>
+		<author>TeamWin</author>
+		<title>Backup Naowz</title>
+		<description>Default basic theme</description>
+		<preview>preview.png</preview>
+		<themeversion>{themeversion}</themeversion>
+	</details>
+
+	<include>
+		<xmlfile name="watch.xml"/>
+	</include>
+
+	<resources>
+		<font name="font_l" filename="RobotoCondensed-Regular.ttf" size="24"/>
+		<font name="font_m" filename="RobotoCondensed-Regular.ttf" size="14"/>
+		<font name="font_s" filename="RobotoCondensed-Regular.ttf" size="12"/>
+		<font name="fixed" filename="DroidSansMono.ttf" size="14"/>
+
+		<font name="keylabel" filename="RobotoCondensed-Regular.ttf" size="18"/>
+		<font name="keylabel-small" filename="RobotoCondensed-Regular.ttf" size="14"/>
+		<font name="keylabel-longpress" filename="RobotoCondensed-Regular.ttf" size="12"/>
+
+		<image name="main_button" filename="main_button"/>
+		<image name="main_button_half_height" filename="main_button_half_height"/>
+		<image name="main_button_full_width" filename="main_button_full_width"/>
+		<image name="main_button_quarter_width" filename="main_button_quarter_width"/>
+		<image name="q_btn_arrow_right" filename="q_btn_arrow_right" retainaspect="1"/>
+		<image name="q_btn_arrow_left" filename="q_btn_arrow_left" retainaspect="1"/>
+		<image name="q_btn_storage" filename="q_btn_storage" retainaspect="1"/>
+		<image name="q_btn_refresh" filename="q_btn_refresh" retainaspect="1"/>
+		<image name="q_btn_accept" filename="q_btn_accept" retainaspect="1"/>
+		<image name="q_btn_accept_transp" filename="q_btn_accept_transp" retainaspect="1"/>
+		<image name="q_btn_addzip" filename="q_btn_addzip" retainaspect="1"/>
+		<image name="q_btn_encryption_dis" filename="q_btn_encryption_dis" retainaspect="1"/>
+		<image name="q_btn_encryption_en" filename="q_btn_encryption_en" retainaspect="1"/>
+		<image name="q_btn_delete" filename="q_btn_delete" retainaspect="1"/>
+		<image name="q_btn_reset" filename="q_btn_reset" retainaspect="1"/>
+		<image name="q_btn_folder" filename="q_btn_folder" retainaspect="1"/>
+		<image name="file" filename="file" retainaspect="1"/>
+		<image name="folder" filename="folder" retainaspect="1"/>
+		<image name="unlock_icon" filename="unlock_icon" retainaspect="1"/>
+		<image name="home" filename="home" retainaspect="1"/>
+		<image name="back" filename="back" retainaspect="1"/>
+		<image name="console" filename="console" retainaspect="1"/>
+		<image name="kb_hide" filename="kb_hide" retainaspect="1"/>
+		<image name="kb_show" filename="kb_show" retainaspect="1"/>
+		<image name="grid_less" filename="grid_less" retainaspect="1"/>
+		<image name="grid_more" filename="grid_more" retainaspect="1"/>
+		<image name="checkbox_false" filename="checkbox_false" retainaspect="1"/>
+		<image name="checkbox_false_small" filename="checkbox_false_small" retainaspect="1"/>
+		<image name="checkbox_true" filename="checkbox_true" retainaspect="1"/>
+		<image name="checkbox_true_small" filename="checkbox_true_small" retainaspect="1"/>
+		<image name="radio_false" filename="radio_false" retainaspect="1"/>
+		<image name="radio_true" filename="radio_true" retainaspect="1"/>
+		<animation name="progress" filename="indeterminate"/>
+		<image name="progress_empty" filename="progress_empty"/>
+		<image name="progress_full" filename="progress_fill"/>
+		<image name="slider" filename="slider"/>
+		<image name="slider_used" filename="slider_used"/>
+		<image name="slider_touch" filename="slider_touch"/>
+		<image name="handle" filename="handle"/>
+		<image name="backspace" filename="backspace" retainaspect="1"/>
+		<image name="enter" filename="enter" retainaspect="1"/>
+		<image name="shift" filename="shift" retainaspect="1"/>
+		<image name="shift_fill" filename="shift_fill" retainaspect="1"/>
+		<image name="space" filename="space" retainaspect="1"/>
+		<image name="close" filename="close" retainaspect="1"/>
+		<image name="kb_arrow_left" filename="kb_arrow_left" retainaspect="1"/>
+		<image name="kb_arrow_right" filename="kb_arrow_right" retainaspect="1"/>
+		<image name="kb_arrow_up" filename="kb_arrow_up" retainaspect="1"/>
+		<image name="kb_arrow_down" filename="kb_arrow_down" retainaspect="1"/>
+	</resources>
+
+	<variables>
+		<variable name="tw_hide_kb" value="0"/>
+		<variable name="screen_width" value="320"/>
+		<variable name="screen_half_width" value="160"/>
+		<variable name="screen_height" value="320"/>
+		<variable name="status_height" value="20"/>
+		<variable name="header_height" value="20"/>
+		<variable name="navbar_height" value="52"/>
+		<variable name="content_width" value="304"/>
+		<variable name="content_half_width" value="148"/>
+		<variable name="back_button_x" value="70"/>
+		<variable name="console_button_x" value="250"/>
+		<variable name="indent" value="8"/>
+		<variable name="col1_x_left" value="8"/>
+		<variable name="col1_x_left_negative" value="156"/>
+		<variable name="center_x" value="160"/>
+		<variable name="col1_x_right" value="164"/>
+		<variable name="indent_right" value="312"/>
+		<variable name="btn4_col2_x" value="86"/>
+		<variable name="btn4_col3_x" value="164"/>
+		<variable name="btn4_col4_x" value="242"/>
+		<variable name="status_topalign_header_y" value="0"/>
+		<variable name="status_centeralign_header_y" value="3"/>
+		<variable name="status_bottomalign_header_y" value="6"/>
+		<variable name="row1_header_y" value="{statusicons_align}"/>
+		<variable name="row2_header_y" value="20"/>
+		<variable name="row1_y" value="28"/>
+		<variable name="row1a_y" value="36"/>
+		<variable name="row2_y" value="44"/>
+		<variable name="row2a_y" value="52"/>
+		<variable name="row3_y" value="60"/>
+		<variable name="row3_input_y" value="64"/>
+		<variable name="row3a_y" value="68"/>
+		<variable name="row4_y" value="76"/>
+		<variable name="row4a_y" value="84"/>
+		<variable name="row5_y" value="92"/>
+		<variable name="row5a_y" value="100"/>
+		<variable name="row6_y" value="108"/>
+		<variable name="row6_input_y" value="112"/>
+		<variable name="row6a_y" value="116"/>
+		<variable name="row6a_input_y" value="120"/>
+		<variable name="row7_y" value="124"/>
+		<variable name="row7a_y" value="132"/>
+		<variable name="row8_y" value="140"/>
+		<variable name="row8a_y" value="148"/>
+		<variable name="row9_y" value="156"/>
+		<variable name="row9a_y" value="164"/>
+		<variable name="row10_y" value="172"/>
+		<variable name="row10a_y" value="180"/>
+		<variable name="row11_y" value="188"/>
+		<variable name="row11a_y" value="196"/>
+		<variable name="row12_y" value="204"/>
+		<variable name="row12a_y" value="212"/>
+		<variable name="row13_y" value="220"/>
+		<variable name="slider_y" value="224"/>
+		<variable name="row13a_y" value="228"/>
+		<variable name="row14_y" value="236"/>
+		<variable name="row14a_y" value="244"/>
+		<variable name="row15_y" value="252"/>
+		<variable name="row15a_y" value="260"/>
+		<variable name="navbar_y" value="268"/>
+		<variable name="navbar_btn_y" value="294"/>
+		<variable name="keyboard_y" value="124"/>
+		<variable name="keyboard_terminal_y" value="100"/>
+		<variable name="background_color" value="#1A1A1A"/>
+		<variable name="accent_color" value="#0090CA"/>
+		<variable name="text_color" value="#EEEEEE"/>
+		<variable name="text_button_color" value="#EEEEEE"/>
+		<variable name="text_success_color" value="#76FF03"/>
+		<variable name="text_fail_color" value="#FF0101"/>
+		<variable name="highlight_color" value="#1A1A1A80"/>
+		<variable name="caps_highlight_color" value="#22222280"/>
+		<variable name="transparent" value="#00000000"/>
+		<variable name="semi_transparent" value="#00000099"/>
+		<variable name="warning" value="#F8F8A0"/>
+		<variable name="error" value="#FF0101"/>
+		<variable name="highlight" value="#0090CA"/>
+		<variable name="fileselector_linecolor" value="#555555"/>
+		<variable name="fileselector_highlight_color" value="#555555"/>
+		<variable name="fileselector_separatorheight" value="1"/>
+		<variable name="fileselector_spacing" value="0"/>
+		<variable name="fileselector_install_height" value="248"/>
+		<variable name="fileselector_filemanager_height" value="248"/>
+		<variable name="partitionlist_spacing" value="0"/>
+		<variable name="partitionlist_storage_height" value="160"/>
+		<variable name="partitionlist_wipe_height" value="160"/>
+		<variable name="listbox_timezone_height" value="248"/>
+		<variable name="listbox_settings_height" value="248"/>
+		<variable name="listbox_options_height" value="160"/>
+		<variable name="fastscroll_w" value="10"/>
+		<variable name="fastscroll_linew" value="1"/>
+		<variable name="fastscroll_rectw" value="10"/>
+		<variable name="fastscroll_recth" value="18"/>
+		<variable name="slidervalue_lineh" value="4"/>
+		<variable name="slidervalue_padding" value="0"/>
+		<variable name="slidervalue_sliderw" value="70"/>
+		<variable name="slidervalue_sliderh" value="72"/>
+		<variable name="slideout_height" value="232"/>
+		<variable name="slideout_bg_height" value="248"/>
+		<variable name="input_height" value="16"/>
+		<variable name="input_line_width" value="1"/>
+		<variable name="input_line_console" value="272"/>
+		<variable name="console_height" value="152"/>
+		<variable name="console_terminal_s_height" value="100"/>
+		<variable name="console_terminal_l_height" value="268"/>
+		<variable name="close_btn_x" value="280"/>
+		<variable name="pattern_x" value="60"/>
+		<variable name="pattern_dot_dia" value="12"/>
+		<variable name="pattern_line_w" value="4"/>
+		<variable name="pattern_size" value="200"/>
+		<variable name="tw_clock_12_pos_x" value="{clock_12_pos}"/>
+		<variable name="tw_clock_24_pos_x" value="{clock_24_pos}"/>
+		<variable name="tw_cpu_pos_x" value="{cpu_pos}"/>
+		<variable name="tw_battery_pos_x" value="{battery_pos}"/>
+	</variables>
+
+	<templates>
+		<template name="statusbar">
+			<fill color="%accent_color%">
+				<placement x="0" y="0" w="%screen_width%" h="%header_height%"/>
+			</fill>
+
+			<fill color="%text_fail_color%">
+				<condition var1="tw_simulate_actions" var2="1"/>
+				<placement x="0" y="0" w="%screen_width%" h="%status_height%"/>
+			</fill>  
+		</template>
+
+		<template name="page">
+			<background color="%background_color%"/>
+
+			<template name="statusbar"/>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="1"/>
+				<condition var1="tw_cpu_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row1_header_y%"/>
+				<text>{@twrp_watch_header=TWRP %tw_version%}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<condition var1="tw_cpu_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%indent%" y="%row1_header_y%"/>
+				<text>{@cpu_temp=CPU: %tw_cpu_temp% °C}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_12_pos_x" var2="0"/>
+				<condition var1="tw_clock_24_pos_x" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%center_x%" y="%row1_header_y%" placement="5"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<conditions>
+					<condition var1="tw_no_battery_percent" var2="0"/>
+					<condition var1="tw_battery" op="&gt;" var2="0"/>
+					<condition var1="tw_battery" op="&lt;" var2="101"/>
+					<condition var1="tw_battery_pos_x" var2="0"/>
+				</conditions>
+				<font resource="font_m"/>
+				<placement x="%indent_right%" y="%row1_header_y%" placement="1"/>
+				<text>{@battery_pct=Battery: %tw_battery%}</text>
+			</text>
+
+			<!-- Custom position for status bar items if defined START -->
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="1"/>
+				<condition var1="tw_cpu_pos_x" op="!=" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_cpu_pos_x%" y="%row1_header_y%"/>
+				<text>{@twrp_watch_header=TWRP %tw_version%}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_no_cpu_temp" var2="0"/>
+				<condition var1="tw_cpu_pos_x" op="!=" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_cpu_pos_x%" y="%row1_header_y%"/>
+				<text>{@cpu_temp=CPU: %tw_cpu_temp% °C}</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_12_pos_x" op="!=" var2="0"/>
+				<condition var1="tw_military_time" var2="0"/>
+				<font resource="font_m"/>
+				<placement x="%tw_clock_12_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<condition var1="tw_clock_24_pos_x" op="!=" var2="0"/>
+				<condition var1="tw_military_time" var2="1"/>
+				<font resource="font_m"/>
+				<placement x="%tw_clock_24_pos_x%" y="%row1_header_y%"/>
+				<text>%tw_time%</text>
+			</text>
+
+			<text color="%text_color%">
+				<conditions>
+					<condition var1="tw_no_battery_percent" var2="0"/>
+					<condition var1="tw_battery" op="&gt;" var2="0"/>
+					<condition var1="tw_battery" op="&lt;" var2="101"/>
+					<condition var1="tw_battery_pos_x" op="!=" var2="0"/>
+				</conditions>
+				<font resource="font_m"/>
+				<placement x="%tw_battery_pos_x%" y="%row1_header_y%"/>
+				<text>{@battery_pct=Battery: %tw_battery%}</text>
+			</text>
+
+			<!-- Custom position for status bar items if defined END -->
+
+			<fill color="#000000">
+				<placement x="0" y="%navbar_y%" w="%screen_width%" h="%navbar_height%"/>
+			</fill>
+
+			<button>
+				<placement x="%back_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="back"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">back</action>
+			</button>
+
+			<button>
+				<placement x="%center_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="home"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="key">home</action>
+			</button>
+
+			<button>
+				<placement x="%console_button_x%" y="%navbar_btn_y%" placement="4"/>
+				<image resource="console"/>
+				<condition var1="tw_busy" var2="0"/>
+				<action function="overlay">slideout</action>
+			</button>
+
+			<action>
+				<touch key="power"/>
+				<action function="togglebacklight"/>
+			</action>
+
+			<action>
+				<touch key="power+voldown"/>
+				<action function="screenshot"/>
+			</action>
+		</template>
+
+		<template name="progress_bar">
+			<progressbar>
+				<placement x="%indent%" y="%row11_y%"/>
+				<resource empty="progress_empty" full="progress_full"/>
+				<data name="ui_progress"/>
+			</progressbar>
+
+			<animation>
+				<placement x="%indent%" y="%row11_y%"/>
+				<resource name="progress"/>
+				<speed fps="24" render="2"/>
+				<loop frame="1"/>
+			</animation>
+		</template>
+
+		<template name="console">
+			<console>
+				<placement x="%indent%" y="%row1_y%" w="%content_width%" h="%console_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row1_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row10a_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+		</template>
+
+		<template name="console_terminal">
+			<console>
+				<placement x="%indent%" y="%row1_y%" w="content_width" h="%console_terminal_height%"/>
+			</console>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row1_y" w="%content_width%" h="input_line_width" placement="1"/>
+			</fill>
+
+			<fill color="%fileselector_linecolor%">
+				<placement x="%indent%" y="row5_y" w="%content_width%" h="input_line_width"/>
+			</fill>
+		</template>
+
+		<template name="keyboardterminaltemplate">
+			<keyboard>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%keyboard_terminal_y%" w="%screen_width%" h="24"/>
+				<keymargin x="0" y="0"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="0" y="0"/>
+				<keylabel key="0:k:29" text="Ctrl"/>
+				<keylabel key="0:c:27" text="Esc"/>
+				<keylabel key="0:c:9" text="Tab"/>
+				<keylabel key="0:k:105" text="&lt;" resource="kb_arrow_left"/>
+				<keylabel key="0:k:108" text="v" resource="kb_arrow_down"/>
+				<keylabel key="0:k:103" text="^" resource="kb_arrow_up"/>
+				<keylabel key="0:k:106" text="&gt;" resource="kb_arrow_right"/>
+				<highlight color="%highlight_color%"/>
+				<ctrlhighlight color="#0090CA80"/>
+				<layout1>
+					<keysize height="24" width="44"/>
+					<row1 key01="48:k:29" key02="48:c:27" long02=":c:3" key03="48:c:9" key04="44:k:105" key05="44:k:108" key06="44:k:103" key07="44:k:106"/>
+				</layout1>
+			</keyboard>
+		</template>
+
+		<template name="keyboardtemplate">
+			<keyboard>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%keyboard_y%" w="320" h="144"/>
+				<keymargin x="2" y="2"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="0" y="0"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace"/>
+				<keylabel key="0:action" text="Enter" resource="enter"/>
+				<keylabel key=" " text="Space" resource="space"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<layout1>
+					<keysize height="36" width="32"/>
+					<row1 key01="q" long01="1" key02="w" long02="2" key03="e" long03="3" key04="r" long04="4" key05="t" long05="5" key06="y" long06="6" key07="u" long07="7" key08="i" long08="8" key09="o" long09="9" key10="p" long10="0"/>
+					<row2 key01="16:" key02="a" long02="@" key03="s" long03="#" key04="d" long04="$" key05="f" long05="%" key06="g" long06="&amp;" key07="h" long07="*" key08="j" long08="-" key09="k" long09="+" key10="l" long10="_" key11="16:"/>
+					<row3 key01="48:layout2" key02="z" long02="!" key03="x" long03="&quot;" key04="c" long04="'" key05="v" long05=":" key06="b" long06=";" key07="n" long07="/" key08="m" long08="?" key09="48:c:8"/>
+					<row4 key01="48:layout3" key02="/" key03="160: " key04="." key05="48:action"/>
+					<keylabel key="0:layout2" text="Shift" resource="shift"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout1>
+				<layout2>
+					<keysize height="36" width="32" capslock="0" revert_layout="1"/>
+					<row1 key01="Q" long01="1" key02="W" long02="2" key03="E" long03="3" key04="R" long04="4" key05="T" long05="5" key06="Y" long06="6" key07="U" long07="7" key08="I" long08="8" key09="O" long09="9" key10="P" long10="0"/>
+					<row2 key01="16:" key02="A" long02="@" key03="S" long03="#" key04="D" long04="$" key05="F" long05="%" key06="G" long06="&amp;" key07="H" long07="*" key08="J" long08="-" key09="K" long09="+" key10="L" long10="_" key11="16:"/>
+					<row3 key01="48:layout1" key02="Z" long02="!" key03="X" long03="&quot;" key04="C" long04="'" key05="V" long05=":" key06="B" long06=";" key07="N" long07="/" key08="M" long08="?" key09="48:c:8"/>
+					<row4 key01="48:layout3" key02="/" key03="160: " key04="." key05="48:action"/>
+					<keylabel key="0:layout1" text="Shift" resource="shift_fill"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout2>
+				<layout3>
+					<keysize height="36" width="32"/>
+					<row1 key01="1" key02="2" key03="3" key04="4" key05="5" key06="6" key07="7" key08="8" key09="9" key10="0"/>
+					<row2 key01="@" key02="#" key03="$" key04="%" key05="&amp;" key06="*" key07="-" key08="+" key09="(" key10=")"/>
+					<row3 key01="48:layout4" key02="!" key03="32:c:34" key04="'" key05=":" key06=";" key07="/" key08="?" key09="48:c:8"/>
+					<row4 key01="48:layout1" key02="," key03="160: " key04="." key05="48:action"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout4" text="~\{"/>
+				</layout3>
+				<layout4>
+					<keysize height="36" width="32"/>
+					<row1 key01="~" key02="`" key03="|" key04="32:" key05="32:" key06="32:" key07="%" key08="32:" key09="{" key10="}"/>
+					<row2 key01="32:" key02="32:" key03="32:" key04="32:" key05="32:" key06="^" key07="_" key08="=" key09="[" key10="]"/>
+					<row3 key01="48:layout3" key02="32:" key03="32:" key04="32:" key05="32:" key06="\" key07="&lt;" key08="&gt;" key09="48:c:8"/>
+					<row4 key01="48:layout1" key02="32:c:34" key03="160: " key04="." key05="48:action"/>
+					<keylabel key="0:layout1" text="ABC"/>
+					<keylabel key="0:layout3" text="?123"/>
+				</layout4>
+			</keyboard>
+		</template>
+
+		<template name="keyboardnum">
+			<keyboard>
+				<condition var1="tw_hide_kb" var2="0"/>
+				<placement x="0" y="%keyboard_y%" w="320" h="144"/>
+				<keymargin x="2" y="2"/>
+				<background color="#111111"/>
+				<key-alphanumeric color="#111111" font="keylabel" textcolor="#EEEEEE"/>
+				<key-other color="#111111" font="keylabel-small" textcolor="#5b5b5bff"/>
+				<longpress font="keylabel-longpress" textcolor="#5b5b5bff" x="0" y="0"/>
+				<keylabel key="0:c:8" text="Bksp" resource="backspace"/>
+				<keylabel key="0:action" text="Enter" resource="enter"/>
+				<keylabel key=" " text="Space" resource="space"/>
+				<highlight color="%highlight_color%"/>
+				<capshighlight color="%highlight_color%"/>
+				<layout1>
+					<keysize height="35" width="62"/>
+					<row1 key01="67:" key02="1" key03="2" key04="3" key05="67:"/>
+					<row2 key01="67:" key02="4" key03="5" key04="6" key05="67:"/>
+					<row3 key01="67:" key02="7" key03="8" key04="9" key05="67:"/>
+					<row4 key01="67:" key02="62:c:8" key03="0" key04="63:action" key05="67:"/>
+				</layout1>
+			</keyboard>
+		</template>
+	</templates>
+</recovery>
diff --git a/gui/twmsg.cpp b/gui/twmsg.cpp
new file mode 100644
index 0000000..ebd6fc3
--- /dev/null
+++ b/gui/twmsg.cpp
@@ -0,0 +1,139 @@
+/*
+	Copyright 2015 _that/TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "../data.hpp"
+#include "pages.hpp"
+#include "resources.hpp"
+
+#include "twmsg.h"
+#include <cctype>
+
+std::string Message::GetFormatString(const std::string& name) const
+{
+	return resourceLookup(name);
+}
+
+// Look up in local replacement vars first, if not found then use outer lookup object
+class LocalLookup : public StringLookup
+{
+	const std::vector<std::string>& vars;
+	const StringLookup& next;
+public:
+	LocalLookup(const std::vector<std::string>& vars, const StringLookup& next) : vars(vars), next(next) {}
+	virtual std::string operator()(const std::string& name) const
+	{
+		if (!name.empty() && isdigit(name[0])) { // {1}..{9}
+			int i = atoi(name.c_str());
+			if (i > 0 && i <= (int)vars.size())
+				return vars[i - 1];
+		}
+		return next(name);
+	}
+};
+
+// conversion to final string
+Message::operator std::string() const
+{
+	// do resource lookup
+	std::string str = GetFormatString(name);
+
+	LocalLookup lookup(variables, varLookup);
+
+	// now insert stuff into curly braces
+
+	size_t pos = 0;
+	while ((pos = str.find('{', pos)) < std::string::npos) {
+		size_t end = str.find('}', pos);
+		if (end == std::string::npos)
+			break;
+
+		std::string varname = str.substr(pos + 1, end - pos - 1);
+		std::string vartext = lookup(varname);
+
+		str.replace(pos, end - pos + 1, vartext);
+	}
+	// TODO: complain about too many or too few numbered replacement variables
+	return str;
+}
+
+/*
+Resource manager lookup
+*/
+class ResourceLookup : public StringLookup
+{
+public:
+	virtual std::string operator()(const std::string& name) const
+	{
+		std::string resname;
+		std::string default_value;
+
+		size_t pos = name.find('=');
+		if (pos == std::string::npos) {
+			resname = name;
+		} else {
+			resname = name.substr(0, pos);
+			default_value = name.substr(pos + 1);
+		}
+#ifndef BUILD_TWRPTAR_MAIN
+		const ResourceManager* res = PageManager::GetResources();
+		if (res) {
+			if (default_value.empty())
+				return res->FindString(resname);
+			else
+				return res->FindString(resname, default_value);
+		}
+#endif
+		if (!default_value.empty()) {
+			return default_value;
+		}
+		return name;
+	}
+};
+ResourceLookup resourceLookup;
+
+
+/*
+DataManager lookup
+*/
+class DataLookup : public StringLookup
+{
+public:
+	virtual std::string operator()(const std::string& name) const
+	{
+#ifndef BUILD_TWRPTAR_MAIN
+		std::string value;
+		if (DataManager::GetValue(name, value) == 0)
+			return value;
+		else
+#endif
+			return "";
+	}
+};
+DataLookup dataLookup;
+
+
+// Utility functions to create messages. Short names to make usage convenient.
+Message Msg(const char* name)
+{
+	return Message(msg::kNormal, name, resourceLookup, dataLookup);
+}
+
+Message Msg(msg::Kind kind, const char* name)
+{
+	return Message(kind, name, resourceLookup, dataLookup);
+}
diff --git a/gui/twmsg.h b/gui/twmsg.h
new file mode 100644
index 0000000..cd035d1
--- /dev/null
+++ b/gui/twmsg.h
@@ -0,0 +1,97 @@
+/*
+	Copyright 2015 _that/TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef twmsg_h
+#define twmsg_h
+
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <errno.h>
+
+/*
+Abstract interface for something that can look up strings by name.
+*/
+class StringLookup
+{
+public:
+	virtual std::string operator()(const std::string& name) const = 0;
+	virtual ~StringLookup() {};
+};
+
+
+namespace msg
+{
+	// These get translated to colors in the GUI console
+	enum Kind
+	{
+		kNormal,
+		kHighlight,
+		kWarning,
+		kError
+	};
+
+
+	template<typename T> std::string to_string(const T& v)
+	{
+		std::ostringstream ss;
+		ss << v;
+		return ss.str();
+	}
+}
+
+
+/*
+Generic message formatting class.
+Designed to decouple message generation from actual resource string lookup and variable insertion,
+so that messages can be re-translated at any later time.
+*/
+class Message
+{
+	msg::Kind kind; // severity or similar message kind
+	std::string name; // the resource string name. may be of format "name=default value".
+	std::vector<std::string> variables; // collected insertion variables to replace {1}, {2}, ...
+	const StringLookup& resourceLookup; // object to resolve resource string name into a final format string
+	const StringLookup& varLookup; // object to resolve any non-numeric insertion strings
+
+	std::string GetFormatString(const std::string& name) const;
+
+public:
+	Message(msg::Kind kind, const char* name, const StringLookup& resourceLookup, const StringLookup& varLookup)
+		: kind(kind), name(name), resourceLookup(resourceLookup), varLookup(varLookup) {}
+
+	// Variable insertion.
+	template<typename T>
+	Message& operator()(const T& v) { variables.push_back(msg::to_string(v)); return *this; }
+
+	// conversion to final string
+	operator std::string() const;
+
+	// Get Message Kind
+	msg::Kind GetKind() {return kind;};
+};
+
+
+// Utility functions to create messages with standard resource and data manager lookups.
+// Short names to make usage convenient.
+Message Msg(const char* name);
+Message Msg(msg::Kind kind, const char* name);
+
+#endif
diff --git a/gui/twrp b/gui/twrp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gui/twrp
diff --git a/htcdumlock/Android.mk b/htcdumlock/Android.mk
new file mode 100755
index 0000000..ffc1601
--- /dev/null
+++ b/htcdumlock/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+ifeq ($(TW_INCLUDE_DUMLOCK), true)
+	LOCAL_SRC_FILES:= \
+		htcdumlock.c
+	LOCAL_CFLAGS:= -g -c -W
+	LOCAL_MODULE:=htcdumlock
+	LOCAL_MODULE_TAGS:= optional
+	LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+	include $(BUILD_EXECUTABLE)
+endif
diff --git a/htcdumlock/htcdumlock.c b/htcdumlock/htcdumlock.c
new file mode 100644
index 0000000..9119612
--- /dev/null
+++ b/htcdumlock/htcdumlock.c
@@ -0,0 +1,364 @@
+/*
+ * This binary is a workaround for HTC's unlock method that doesn't let
+ * you flash boot while booted to recovery.  It is designed to dump
+ * recovery and boot to the sdcard then flash recovery to boot. When
+ * used with a supported recovery, you can reflash the dumped copy of
+ * boot once you enter the recovery.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * The code was written from scratch by Dees_Troy dees_troy at
+ * yahoo
+ *
+ * Copyright (c) 2012
+ *
+ * Note that this all could probably be done as a shell script, but
+ * I am much better at C than I am at scripting. :)
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+// Number of bytes in the ramdisks to compare
+#define SCAN_SIZE 60
+
+#define DEVID_MAX 64
+
+#define CMDLINE_SERIALNO        "androidboot.serialno="
+#define CMDLINE_SERIALNO_LEN    (strlen(CMDLINE_SERIALNO))
+#define CPUINFO_SERIALNO        "Serial"
+#define CPUINFO_SERIALNO_LEN    (strlen(CPUINFO_SERIALNO))
+#define CPUINFO_HARDWARE        "Hardware"
+#define CPUINFO_HARDWARE_LEN    (strlen(CPUINFO_HARDWARE))
+
+char device_id[DEVID_MAX] = { 0 };
+int verbose = 0, java = 0;
+
+void sanitize_device_id(void) {
+	const char* whitelist ="-._";
+	char str[DEVID_MAX];
+	char* c = str;
+
+	snprintf(str, DEVID_MAX, "%s", device_id);
+	memset(device_id, 0, strlen(device_id));
+	while (*c) {
+		if (isalnum(*c) || strchr(whitelist, *c))
+			strncat(device_id, c, 1);
+		c++;
+	}
+	return;
+}
+
+/* Recent HTC devices that still take advantage of dumlock
+   can safely rely on cmdline device_id retrieval */
+void get_device_id(void)
+{
+	FILE *fp;
+	char line[2048];
+	char* token;
+
+	// Check the cmdline to see if the serial number was supplied
+	fp = fopen("/proc/cmdline", "rt");
+	if (fp != NULL) {
+		fgets(line, sizeof(line), fp);
+		fclose(fp); // cmdline is only one line long
+
+		token = strtok(line, " ");
+		while (token) {
+			if (memcmp(token, CMDLINE_SERIALNO, CMDLINE_SERIALNO_LEN) == 0) {
+				token += CMDLINE_SERIALNO_LEN;
+				snprintf(device_id, DEVID_MAX, "%s", token);
+				sanitize_device_id(); // also removes newlines
+				return;
+			}
+			token = strtok(NULL, " ");
+		}
+	}
+
+	strcpy(device_id, "serialno");
+	if (verbose)
+		printf("device id not found, using '%s'.", device_id);
+	return;
+}
+
+void reboot_device() {
+	// Reboot
+	printf("Rebooting!\n");
+	system("reboot system");
+}
+
+void scan_for_ramdisk_data(char *filename, char *ramdisk) {
+	FILE *pFile;
+	unsigned long lSize;
+	unsigned char *buffer;
+	size_t result;
+	int i;
+
+	pFile = fopen(filename, "rb");
+	if(pFile==NULL){
+		printf("Unabled to open image.\nFailed\n");
+		exit(1);
+	}
+
+	fseek (pFile , 0 , SEEK_END);
+	lSize = ftell(pFile);
+	rewind(pFile);
+
+	//printf("\n\nFile is %ld bytes big\n\n", lSize);
+
+	buffer = (unsigned char*)malloc(sizeof(unsigned char) * lSize);
+	if(buffer == NULL){
+		printf("File read error!\nFailed\n");
+		exit(2);
+	}
+
+	result = fread (buffer, 1, lSize, pFile);
+	if (result != lSize) {
+		printf("Error reading file '%s'\nFailed\n", filename);
+		exit(3);
+	}
+
+	unsigned char needle[6] = {0x00, 0x00, 0x00, 0x00, 0x1f, 0x8b};
+	unsigned char *last_needle = NULL;
+	//char *p = memmem(needle, lSize, buffer, sizeof(needle));
+	unsigned char *p = memmem(buffer + 2048, lSize - 2048, needle, sizeof(needle));
+	if (!p) {
+		fclose(pFile);
+		printf("Ramdisk not found in '%s', error!\nFailed\n", filename);
+		exit(4);
+	} else {
+		//printf("Ramdisk found in '%s'!\n", filename);
+	}
+
+	memcpy(ramdisk, p, sizeof(char) * SCAN_SIZE);
+	fclose(pFile);
+	free(buffer);
+}
+
+int compare_ramdisks(char *boot_path, char *recovery_path) {
+	char boot_data[SCAN_SIZE], recovery_data[SCAN_SIZE];
+
+	scan_for_ramdisk_data(boot_path, (char*)&boot_data);
+	scan_for_ramdisk_data(recovery_path, (char*)&recovery_data);
+	if (memcmp(boot_data, recovery_data, sizeof(boot_data)) == 0) {
+		if (verbose)
+			printf("Boot and recovery are the same.\n");
+		return 0;
+	} else {
+		if (verbose)
+			printf("Boot and recovery are NOT the same.\n");
+		return 1;
+	}
+}
+
+void flash_recovery_to_boot(int no_flash, int no_reboot) {
+	char twrp_device_path[255], recovery_path[255], boot_path[255],
+		exec[255], md5recovery[255], md5boot[255],
+		recoveryimg[255], bootimg[255], tempimg[255];
+	int ret_val = 0;
+	FILE *fp;
+	char* token;
+
+	// Create folders
+	if (verbose)
+		printf("Making '/sdcard/TWRP'\n");
+	mkdir("/sdcard/TWRP", 0777);
+	if (verbose)
+		printf("Making folder '/sdcard/TWRP/htcdumlock'\n");
+	mkdir("/sdcard/TWRP/htcdumlock", 0777);
+	strcpy(twrp_device_path, "/sdcard/TWRP/htcdumlock/");
+	strcat(twrp_device_path, device_id);
+	if (verbose)
+		printf("Making folder '%s'\n", twrp_device_path);
+	mkdir(twrp_device_path, 0777);
+	// Make folder for recovery
+	strcpy(recovery_path, twrp_device_path);
+	strcat(recovery_path, "/recovery");
+	if (verbose)
+		printf("Making folder '%s'\n", recovery_path);
+	mkdir(recovery_path, 0777);
+	strcat(recovery_path, "/");
+	// Make folder for boot
+	strcpy(boot_path, twrp_device_path);
+	strcat(boot_path, "/boot");
+	if (verbose)
+		printf("Making folder '%s'\n", boot_path);
+	mkdir(boot_path, 0777);
+	strcat(boot_path, "/");
+
+	// Set up file locations
+	strcpy(recoveryimg, recovery_path);
+	strcat(recoveryimg, "recovery.img");
+	strcpy(bootimg, boot_path);
+	strcat(bootimg, "boot.img");
+	strcpy(tempimg, twrp_device_path);
+	strcat(tempimg, "/temp.img");
+
+	// Dump recovery
+	strcpy(exec, "dump_image recovery ");
+	strcat(exec, recoveryimg);
+	if (verbose)
+		printf("Running command: '%s'\n", exec);
+	ret_val = system(exec);
+	if (ret_val != 0) {
+		printf("Unable to dump recovery.\nFailed\n");
+		return;
+	}
+
+	// Dump boot (kernel)
+	strcpy(exec, "dump_image boot ");
+	strcat(exec, tempimg);
+	if (verbose)
+		printf("Running command: '%s'\n", exec);
+	ret_val = system(exec);
+	if (ret_val != 0) {
+		printf("Unable to dump recovery.\nFailed\n");
+		return;
+	}
+
+	// Compare the ramdisks of the images from boot and recovery to make sure they are different
+	// If they are the same, then recovery is already flashed to boot and we don't want to wipe
+	// out our existing backup of boot
+	if (compare_ramdisks(tempimg, recoveryimg) != 0) {
+		if (verbose)
+			printf("Boot and recovery do not match so recovery is not flashed to boot yet...\n");
+		strcpy(exec, "mv ");
+		strcat(exec, tempimg);
+		strcat(exec, " ");
+		strcat(exec, bootimg);
+		if (verbose)
+			printf("Moving temporary boot.img: '%s'\n", exec);
+		ret_val = system(exec);
+		if (ret_val != 0) {
+			printf("Unable to move temporary boot image.\nFailed\n");
+			return;
+		}
+	} else {
+		if (!java)
+			printf("Ramdisk recovery and boot matches! Recovery is already flashed to boot!\n");
+		if (!no_reboot)
+			reboot_device();
+		return;
+	}
+
+	// Flash recovery to boot
+	strcpy(exec, "flash_image boot ");
+	strcat(exec, recoveryimg);
+	if (no_flash) {
+		if (verbose)
+			printf("NOT flashing recovery to boot due to argument 'noflash', command is '%s'\n", exec);
+	} else {
+		if (verbose)
+			printf("Running command: '%s'\n", exec);
+		ret_val = system(exec);
+		if (ret_val != 0) {
+			printf("Unable to flash recovery to boot.\nFailed\n");
+			return;
+		}
+	}
+
+	if (!no_reboot && !ret_val)
+		reboot_device();
+}
+
+void restore_original_boot(int no_flash) {
+	char boot_path[255], exec[255];
+
+	// Restore original boot partition
+	strcpy(boot_path, "/sdcard/TWRP/htcdumlock/");
+	strcat(boot_path, device_id);
+	strcat(boot_path, "/boot/");
+	strcpy(exec, "flash_image boot ");
+	strcat(exec, boot_path);
+	strcat(exec, "boot.img");
+	if (no_flash) {
+		if (verbose)
+			printf("NOT restoring boot due to argument 'noflash', command is '%s'\n", exec);
+	} else {
+		if (verbose)
+			printf("Running command: '%s'\n", exec);
+		system(exec);
+	}
+}
+
+int main(int argc, char** argv)
+{
+	int recovery = 0, no_flash = 0, restore_boot = 0, arg_error = 0,
+		no_reboot = 0, i;
+
+	// Parse the arguments
+	if (argc < 2)
+		arg_error = 1;
+	else {
+		for (i=1; i<argc; i++) {
+			if (strcmp(argv[i], "recovery") == 0) {
+				// Check to see if restore option is already set
+				// Do not allow user to do recovery and restore at the same time
+				if (restore_boot)
+					arg_error = 1;
+				recovery = 1;
+			} else if (strcmp(argv[i], "restore") == 0) {
+				// Check to see if recovery option is already set
+				// Do not allow user to do recovery and restore at the same time
+				if (recovery)
+					arg_error = 1;
+				restore_boot = 1;
+			} else if (strcmp(argv[i], "noflash") == 0)
+				no_flash = 1;
+			else if (strcmp(argv[i], "noreboot") == 0)
+				no_reboot = 1;
+			else if (strcmp(argv[i], "verbose") == 0)
+				verbose = 1;
+			else if (strcmp(argv[i], "java") == 0)
+				java = 1;
+			else
+				arg_error = 1;
+		}
+	}
+	if (arg_error) {
+		printf("Invalid argument given.\n");
+		printf("Valid arguments are:\n");
+		printf("  recovery -- backs up boot and recovery and flashes recovery to boot\n");
+		printf("  restore  -- restores the most recent backup of boot made by this utility\n");
+		printf("  noflash  -- same as 'recovery' but does not flash boot or reboot at the end\n");
+		printf("  noreboot -- does not reboot after flashing boot during 'recovery'\n");
+		printf("  verbose  -- show extra debug information\n");
+		printf("\nNOTE: You cannot do 'recovery' and 'restore' in the same operation.\nFailed\n");
+		return 0;
+	}
+
+	get_device_id();
+	if (verbose)
+		printf("Device ID is: '%s'\n", device_id);
+	if (strcmp(device_id, "serialno") == 0) {
+		printf("Error, dummy device ID detected!\n");
+		printf("Did you 'su' first? HTC Dumlock requires root access.\nFailed\n");
+		return 0;
+	}
+
+	if (recovery) {
+		if (!java)
+			printf("Flashing recovery to boot, this may take a few minutes . . .\n");
+		flash_recovery_to_boot(no_flash, no_reboot);
+	}
+	if (restore_boot) {
+		printf("Restoring boot, this may take a few minutes . . .\n");
+		restore_original_boot(no_flash);
+	}
+
+	return 0;
+}
diff --git a/infomanager.cpp b/infomanager.cpp
new file mode 100644
index 0000000..275c70c
--- /dev/null
+++ b/infomanager.cpp
@@ -0,0 +1,232 @@
+/*
+	Copyright 2012 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string>
+#include <map>
+#include <fstream>
+#include <sstream>
+
+#include "infomanager.hpp"
+#include "twcommon.h"
+#include "partitions.hpp"
+#include "set_metadata.h"
+
+using namespace std;
+
+InfoManager::InfoManager() {
+	file_version = 0;
+	is_const = false;
+}
+
+InfoManager::InfoManager(const string& filename) {
+	file_version = 0;
+	is_const = false;
+	SetFile(filename);
+}
+
+InfoManager::~InfoManager(void) {
+	Clear();
+}
+
+void InfoManager::SetFile(const string& filename) {
+	File = filename;
+}
+
+void InfoManager::SetFileVersion(int version) {
+	file_version = version;
+}
+
+void InfoManager::SetConst(void) {
+	is_const = true;
+}
+
+void InfoManager::Clear(void) {
+	mValues.clear();
+}
+
+int InfoManager::LoadValues(void) {
+	string str;
+
+	// Read in the file, if possible
+	FILE* in = fopen(File.c_str(), "rb");
+	if (!in) {
+		LOGINFO("InfoManager file '%s' not found.\n", File.c_str());
+		return -1;
+	} else {
+		LOGINFO("InfoManager loading from '%s'.\n", File.c_str());
+	}
+
+	if (file_version) {
+		int read_file_version;
+		if (fread(&read_file_version, 1, sizeof(int), in) != sizeof(int))
+			goto error;
+		if (read_file_version != file_version) {
+			LOGINFO("InfoManager file version has changed, not reading file\n");
+			goto error;
+		}
+	}
+
+	while (!feof(in)) {
+		string Name;
+		string Value;
+		unsigned short length;
+		char array[513];
+
+		if (fread(&length, 1, sizeof(unsigned short), in) != sizeof(unsigned short))	goto error;
+		if (length >= 512)																goto error;
+		if (fread(array, 1, length, in) != length)										goto error;
+		array[length+1] = '\0';
+		Name = array;
+
+		if (fread(&length, 1, sizeof(unsigned short), in) != sizeof(unsigned short))	goto error;
+		if (length >= 512)																goto error;
+		if (fread(array, 1, length, in) != length)										goto error;
+		array[length+1] = '\0';
+		Value = array;
+
+		map<string, string>::iterator pos;
+
+		pos = mValues.find(Name);
+		if (pos != mValues.end()) {
+			pos->second = Value;
+		} else {
+			mValues.insert(make_pair(Name, Value));
+		}
+	}
+error:
+	fclose(in);
+	return 0;
+}
+
+int InfoManager::SaveValues(void) {
+	if (File.empty())
+		return -1;
+
+	PartitionManager.Mount_By_Path(File, true);
+	LOGINFO("InfoManager saving '%s'\n", File.c_str());
+	FILE* out = fopen(File.c_str(), "wb");
+	if (!out)
+		return -1;
+
+	if (file_version) {
+		fwrite(&file_version, 1, sizeof(int), out);
+	}
+
+	map<string, string>::iterator iter;
+	for (iter = mValues.begin(); iter != mValues.end(); ++iter) {
+		unsigned short length = (unsigned short) iter->first.length() + 1;
+		fwrite(&length, 1, sizeof(unsigned short), out);
+		fwrite(iter->first.c_str(), 1, length, out);
+		length = (unsigned short) iter->second.length() + 1;
+		fwrite(&length, 1, sizeof(unsigned short), out);
+		fwrite(iter->second.c_str(), 1, length, out);
+	}
+	fclose(out);
+	tw_set_default_metadata(File.c_str());
+	return 0;
+}
+
+int InfoManager::GetValue(const string& varName, string& value) {
+	string localStr = varName;
+
+	map<string, string>::iterator pos;
+	pos = mValues.find(localStr);
+	if (pos == mValues.end())
+		return -1;
+
+	value = pos->second;
+	return 0;
+}
+
+int InfoManager::GetValue(const string& varName, int& value) {
+	string data;
+
+	if (GetValue(varName,data) != 0)
+		return -1;
+
+	value = atoi(data.c_str());
+	return 0;
+}
+
+int InfoManager::GetValue(const string& varName, float& value) {
+	string data;
+
+	if (GetValue(varName,data) != 0)
+		return -1;
+
+	value = atof(data.c_str());
+	return 0;
+}
+
+unsigned long long InfoManager::GetValue(const string& varName, unsigned long long& value) {
+	string data;
+
+	if (GetValue(varName,data) != 0)
+		return -1;
+
+	value = strtoull(data.c_str(), NULL, 10);
+	return 0;
+}
+
+// This function will return an empty string if the value doesn't exist
+string InfoManager::GetStrValue(const string& varName) {
+	string retVal;
+
+	GetValue(varName, retVal);
+	return retVal;
+}
+
+// This function will return 0 if the value doesn't exist
+int InfoManager::GetIntValue(const string& varName) {
+	string retVal;
+	GetValue(varName, retVal);
+	return atoi(retVal.c_str());
+}
+
+int InfoManager::SetValue(const string& varName, const string& value) {
+	// Don't allow empty names or numerical starting values
+	if (varName.empty() || (varName[0] >= '0' && varName[0] <= '9'))
+		return -1;
+
+	map<string, string>::iterator pos;
+	pos = mValues.find(varName);
+	if (pos == mValues.end())
+		mValues.insert(make_pair(varName, value));
+	else if (!is_const)
+		pos->second = value;
+
+	return 0;
+}
+
+int InfoManager::SetValue(const string& varName, const int value) {
+	ostringstream valStr;
+	valStr << value;
+	return SetValue(varName, valStr.str());
+}
+
+int InfoManager::SetValue(const string& varName, const float value) {
+	ostringstream valStr;
+	valStr << value;
+	return SetValue(varName, valStr.str());
+}
+
+int InfoManager::SetValue(const string& varName, const unsigned long long& value) {
+	ostringstream valStr;
+	valStr << value;
+	return SetValue(varName, valStr.str());
+}
diff --git a/infomanager.hpp b/infomanager.hpp
new file mode 100644
index 0000000..4ce67aa
--- /dev/null
+++ b/infomanager.hpp
@@ -0,0 +1,65 @@
+/*
+	Copyright 2012 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _INFOMANAGER_HPP_HEADER
+#define _INFOMANAGER_HPP_HEADER
+
+#include <string>
+#include <utility>
+#include <map>
+
+using namespace std;
+
+class InfoManager
+{
+public:
+	InfoManager();
+	explicit InfoManager(const string& filename);
+	virtual ~InfoManager();
+	void SetFile(const string& filename);
+	void SetFileVersion(int version);
+	void SetConst();
+	void Clear();
+	int LoadValues();
+	int SaveValues();
+
+	// Core get routines
+	int GetValue(const string& varName, string& value);
+	int GetValue(const string& varName, int& value);
+	int GetValue(const string& varName, float& value);
+	unsigned long long GetValue(const string& varName, unsigned long long& value);
+
+	string GetStrValue(const string& varName);
+	int GetIntValue(const string& varName);
+
+	// Core set routines
+	int SetValue(const string& varName, const string& value);
+	int SetValue(const string& varName, const int value);
+	int SetValue(const string& varName, const float value);
+	int SetValue(const string& varName, const unsigned long long& value);
+
+private:
+	string File;
+	map<string, string> mValues;
+	int file_version;
+	bool is_const;
+
+};
+
+#endif // _DATAMANAGER_HPP_HEADER
+
diff --git a/injecttwrp/Android.mk b/injecttwrp/Android.mk
new file mode 100755
index 0000000..51040f5
--- /dev/null
+++ b/injecttwrp/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+ifeq ($(TW_INCLUDE_INJECTTWRP), true)
+	LOCAL_SRC_FILES:= \
+		injecttwrp.c
+	LOCAL_CFLAGS:= -g -c -W
+	LOCAL_MODULE:=injecttwrp
+	LOCAL_MODULE_TAGS:= optional
+	LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+	include $(BUILD_EXECUTABLE)
+endif
diff --git a/injecttwrp/injecttwrp.c b/injecttwrp/injecttwrp.c
new file mode 100644
index 0000000..2d52ae0
--- /dev/null
+++ b/injecttwrp/injecttwrp.c
@@ -0,0 +1,433 @@
+/*
+ * This binary allows you to back up the second (recovery) ramdisk on
+ * typical Samsung boot images and to inject a new second ramdisk into
+ * an existing boot image.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * The code was written from scratch by Dees_Troy dees_troy at
+ * yahoo
+ *
+ * Copyright (c) 2012
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#define INJECT_USE_TMP 1
+
+int scan_file_for_data(char *filename, unsigned char *data, int data_size, unsigned long start_location, unsigned long *data_address) {
+	FILE *pFile;
+	unsigned long lSize;
+	unsigned char *buffer, *last_needle = NULL;
+	unsigned char needle[data_size];
+	size_t result;
+	int i, return_val = 0;
+
+	pFile = fopen(filename, "rb");
+	if(pFile==NULL){
+		printf("Unabled to open file '%s'.\nFailed\n", filename);
+		return -1;
+	}
+
+	fseek (pFile , 0 , SEEK_END);
+	lSize = ftell(pFile);
+	rewind(pFile);
+
+	buffer = (unsigned char*)malloc(sizeof(unsigned char) * lSize);
+	if(buffer == NULL){
+		printf("Memory allocation error on '%s'!\nFailed\n", filename);
+		return -1;
+	}
+
+	result = fread(buffer, 1, lSize, pFile);
+	if (result != lSize) {
+		printf("Error reading file '%s'\nFailed\n", filename);
+		return -1;
+	}
+
+	for (i=0; i<data_size; i++) {
+		needle[i] = *data;
+		data++;
+	}
+
+	unsigned char *p = memmem(buffer + start_location, lSize - start_location, needle, data_size);
+
+	if (!p) {
+		return_val = -1;
+	} else {
+		*data_address = p - buffer + data_size;
+	}
+
+	fclose(pFile);
+	free(buffer);
+	return return_val;
+}
+
+int write_new_ramdisk(char *bootimage, char *newramdisk, unsigned long start_location, char *outputimage) {
+	FILE *bFile; // bootimage
+	FILE *rFile; // ramdisk
+	FILE *oFile; // output file
+	unsigned long blSize, rlSize, offset_table, ramdisk_len;
+	unsigned char *bbuffer, *rbuffer;
+	size_t result;
+	int return_val;
+
+	// Read the original boot image
+	printf("Reading the original boot image...\n");
+	bFile = fopen(bootimage, "rb");
+	if(bFile==NULL){
+		printf("Unabled to open original boot image '%s'.\nFailed\n", bootimage);
+		exit(0);
+	}
+
+	fseek (bFile , 0 , SEEK_END);
+	blSize = ftell(bFile);
+	rewind(bFile);
+	printf("Size of original boot is %lu\n", blSize);
+
+	bbuffer = (unsigned char*)malloc(sizeof(unsigned char) * blSize);
+	if(bbuffer == NULL){
+		printf("File read error on original boot image '%s'!\nFailed\n", bootimage);
+		exit(0);
+	}
+
+	result = fread(bbuffer, 1, blSize, bFile);
+	if (result != blSize) {
+		printf("Error reading original boot image '%s'\nFailed\n", bootimage);
+		exit(0);
+	}
+
+	// Find the ramdisk offset table
+	unsigned char needle[13] = "recovery_len=";
+	return_val = scan_file_for_data(bootimage, &needle, 13, 0, &offset_table);
+	if (return_val < 0) {
+		fclose(bFile);
+		printf("Ramdisk offset table not found in %s!\nFailed\n", bootimage);
+		exit(0);
+	}
+	printf("Ramdisk offset table found at 0x%08x\n", offset_table);
+
+	// Read the ramdisk to insert into the boot image
+	printf("Reading the ramdisk...\n");
+	rFile = fopen(newramdisk, "rb");
+	if(rFile==NULL){
+		printf("Unabled to open ramdisk image '%s'.\nFailed\n", newramdisk);
+		exit(0);
+	}
+
+	fseek (rFile , 0 , SEEK_END);
+	rlSize = ftell(rFile);
+	rewind(rFile);
+	printf("Size of new ramdisk is %lu\n", rlSize);
+	ramdisk_len = rlSize / 512;
+	if ((rlSize % 512) != 0)
+		ramdisk_len++;
+	printf("Ramdisk length for offset table: %lu\n", ramdisk_len);
+
+	rbuffer = (unsigned char*)malloc(sizeof(unsigned char) * blSize);
+	if(rbuffer == NULL){
+		printf("File read error on ramdisk image '%s'!\nFailed\n", newramdisk);
+		exit(0);
+	}
+
+	result = fread(rbuffer, 1, rlSize, rFile);
+	if (result != rlSize) {
+		printf("Error reading ramdisk image '%s'\nFailed\n", newramdisk);
+		exit(0);
+	}
+
+	// Open the output image for writing
+	printf("Opening the output image for writing...\n");
+	oFile = fopen(outputimage, "wb");
+	if(oFile==NULL){
+		printf("Unabled to open output image '%s'.\nFailed\n", outputimage);
+		exit(0);
+	}
+
+	printf("Writing kernel and first ramdisk...\n");
+	result = fwrite(bbuffer, 1, start_location - 1, oFile);
+	if (result != start_location - 1) {
+		printf("Write count does not match! (1)\n");
+	}
+	fseek(oFile, start_location, SEEK_SET);
+	printf("Writing new second ramdisk...\n");
+	result = fwrite(rbuffer, 1, rlSize, oFile);
+	if (result != rlSize) {
+		printf("Write count does not match! (2)\n");
+	} else {
+		printf("Finished writing new boot image '%s'\n", outputimage);
+	}
+
+	// Write new ramdisk_len to offset table
+	printf("Writing new ramdisk length to offset table...\n");
+	fseek(oFile, offset_table, SEEK_SET);
+	char ramdisk_lens[20];
+	sprintf(ramdisk_lens, "%lu;\n\n", ramdisk_len);
+	fwrite(ramdisk_lens, 1, strlen(ramdisk_lens), oFile);
+
+	fclose(bFile);
+	fclose(rFile);
+	fclose(oFile);
+	free(bbuffer);
+	free(rbuffer);
+
+	printf("All done writing new image: '%s'\n", outputimage);
+	return 1;
+}
+
+int backup_recovery_ramdisk(char *bootimage, unsigned long start_location, char *outputimage) {
+	FILE *bFile; // bootimage
+	FILE *rFile; // ramdisk
+	FILE *oFile; // output file
+	unsigned long blSize, offset_table, ramdisk_len;
+	unsigned char *bbuffer;
+	size_t result;
+	unsigned char ramdisk_lens[4], *p;
+	int return_val, i;
+
+	// Read the original boot image
+	printf("Reading the original boot image...\n");
+	bFile = fopen(bootimage, "rb");
+	if(bFile==NULL){
+		printf("Unabled to open original boot image '%s'.\nFailed\n", bootimage);
+		exit(0);
+	}
+
+	fseek (bFile , 0 , SEEK_END);
+	blSize = ftell(bFile);
+	rewind(bFile);
+	printf("Size of original boot is %lu\n", blSize);
+
+	bbuffer = (unsigned char*)malloc(sizeof(unsigned char) * blSize);
+	if(bbuffer == NULL){
+		printf("File read error on original boot image '%s'!\nFailed\n", bootimage);
+		exit(0);
+	}
+
+	result = fread(bbuffer, 1, blSize, bFile);
+	if (result != blSize) {
+		printf("Error reading original boot image '%s'\nFailed\n", bootimage);
+		exit(0);
+	}
+
+	// Find the ramdisk offset table
+	unsigned char needle[13] = "recovery_len=";
+	return_val = scan_file_for_data(bootimage, &needle, 13, 0, &offset_table);
+	if (return_val < 0) {
+		fclose(bFile);
+		printf("Ramdisk offset table not found in %s!\nFailed\n", bootimage);
+		exit(0);
+	}
+	printf("Ramdisk offset table found at 0x%08x\n", offset_table);
+	for (i=0; i<4; i++) {
+		p = bbuffer + offset_table + i;
+		if (*p == ';') {
+			ramdisk_lens[i] = 0;
+		} else {
+			ramdisk_lens[i] = *p;
+		}
+	}
+	ramdisk_len = atoi(ramdisk_lens);
+	ramdisk_len *= 512;
+	printf("Ramdisk length: %lu\n", ramdisk_len);
+
+	// Open the output image for writing
+	printf("Opening the output image for writing...\n");
+	oFile = fopen(outputimage, "wb");
+	if(oFile==NULL){
+		printf("Unabled to open output image '%s'.\nFailed\n", outputimage);
+		exit(0);
+	}
+
+	printf("Writing backup ramdisk...\n");
+	result = fwrite(bbuffer + start_location, 1, ramdisk_len, oFile);
+	if (result != ramdisk_len) {
+		printf("Write count does not match! (1)\n");
+	} else {
+		printf("Finished backing up ramdisk image '%s'\n", outputimage);
+	}
+
+	fclose(bFile);
+	fclose(oFile);
+	free(bbuffer);
+	return 1;
+}
+
+int find_gzip_recovery_ramdisk(char *boot_image, unsigned long *ramdisk_address) {
+	unsigned char gzip_ramdisk[6] = {0x00, 0x00, 0x00, 0x00, 0x1f, 0x8b};
+	unsigned long address1, address2;
+	int return_val;
+
+	// Find the first ramdisk
+	return_val = scan_file_for_data(boot_image, &gzip_ramdisk, 6, 0, &address1);
+	if (return_val < 0) {
+		printf("No ramdisk found in '%s'\nFailed\n", boot_image);
+		printf("This boot image may not be using gzip compression.\n");
+		return -1;
+	}
+	address1 -= 2;
+	printf("Ramdisk found in '%s' at offset 0x%08x\n", boot_image, address1);
+
+	// Find the second (recovery) ramdisk
+	return_val = scan_file_for_data(boot_image, &gzip_ramdisk, 6, address1 + 50, &address2);
+	if (return_val < 0) {
+		printf("No recovery ramdisk found in '%s'\nFailed\n", boot_image, address2);
+		return -1;
+	}
+	address2 -= 2;
+	printf("Recovery ramdisk found in '%s' at offset 0x%08x\n", boot_image, address2);
+
+	*ramdisk_address = address2;
+	return 0;
+}
+
+int main(int argc, char** argv) {
+	int arg_error = 0, delete_ind = 0, return_val, index, len;
+	unsigned long address2;
+	unsigned char regular_check[8] = "ANDROID!";
+	char boot_image[512], backup_image[512], boot_block_device[512], command[512];
+
+	printf("-- InjectTWRP Recovery Ramdisk Injection Tool for Samsung devices. --\n");
+	printf("--                  by Dees_Troy and Team Win                      --\n");
+	printf("--                       http://teamw.in                           --\n");
+	printf("--                Bringing some win to Samsung!                    --\n");
+	printf("--        This tool comes with no warranties whatsoever!           --\n");
+	printf("--        Use at your own risk and always keep a backup!           --\n\n");
+	printf("Version 0.2 beta\n\n");
+
+	// Parse the arguments
+	if (argc < 2 || argc > 6)
+		arg_error = 1;
+	else {
+		strcpy(boot_block_device, "boot");
+		for (index = 1; index < argc; index++) {
+			len = strlen(argv[index]);
+			if (len > 3 && strncmp(argv[index], "bd=", 3) == 0) {
+				strcpy(boot_block_device, argv[index] + 3);
+				index = argc;
+			}
+		}
+		if ((argc >= 2 && argc <= 4) && (strcmp(argv[1], "-b") == 0 || strcmp(argv[1], "--backup") == 0)) {
+			// Backup existing boot image
+			printf("Dumping boot image...\n");
+#ifdef INJECT_USE_TMP
+			sprintf(command, "dump_image %s /tmp/original_boot.img", boot_block_device);
+			system(command);
+			strcpy(boot_image, "/tmp/original_boot.img");
+
+			if (argc == 2)
+				strcpy(backup_image, "/tmp/recovery_ramdisk.img");
+			else
+				strcpy(backup_image, argv[2]);
+#else
+			system("mount /cache");
+			sprintf(command, "dump_image %s /cache/original_boot.img", boot_block_device);
+			system(command);
+			strcpy(boot_image, "/cache/original_boot.img");
+
+			if (argc == 2)
+				strcpy(backup_image, "/cache/recovery_ramdisk.img");
+			else
+				strcpy(backup_image, argv[2]);
+#endif
+
+			// Check if this is a normal Android image or a Samsung image
+			return_val = scan_file_for_data(boot_image, &regular_check, 8, 0, &address2);
+			if (return_val >= 0) {
+				printf("This is not a properly formatted Samsung boot image!\nFailed\n");
+				return 1;
+			}
+
+			// Find the ramdisk
+			return_val = find_gzip_recovery_ramdisk(boot_image, &address2);
+			if (return_val < 0) {
+				return 1;
+			}
+
+			backup_recovery_ramdisk(boot_image, address2, backup_image);
+			return 0;
+		} else {
+			// Inject new ramdisk
+			if (strcmp(argv[1], "-d") == 0 || strcmp(argv[1], "--dump") == 0) {
+				printf("Dumping boot image...\n");
+#ifdef INJECT_USE_TMP
+				sprintf(command, "dump_image %s /tmp/original_boot.img", boot_block_device);
+				system(command);
+				strcpy(boot_image, "/tmp/original_boot.img");
+#else
+				system("mount /cache");
+				sprintf(command, "dump_image %s /cache/original_boot.img", boot_block_device);
+				system(command);
+				strcpy(boot_image, "/cache/original_boot.img");
+				delete_ind = -1;
+#endif
+			} else
+				strcpy(boot_image, argv[1]);
+
+			// Check if this is a normal Android image or a Samsung image
+			return_val = scan_file_for_data(boot_image, &regular_check, 8, 0, &address2);
+			if (return_val >= 0) {
+				printf("This is not a properly formatted Samsung boot image!\nFailed\n");
+				return 1;
+			}
+
+			// Find the ramdisk
+			return_val = find_gzip_recovery_ramdisk(boot_image, &address2);
+			if (return_val < 0) {
+				return 1;
+			}
+
+			// Write the new image
+			write_new_ramdisk(boot_image, argv[2], address2, argv[3]);
+
+			// Delete --dump image if needed
+			if (delete_ind) {
+				printf("Deleting dumped boot image from /cache\n");
+				system("rm /cache/original_boot.img");
+			}
+
+			if (argc >= 5 && (strcmp(argv[4], "-f") == 0 || strcmp(argv[4], "--flash") == 0)) {
+				printf("Flashing new image...\n");
+				sprintf(command, "erase_image %s", boot_block_device);
+				system(command);
+				sprintf(command, "flash_image %s %s", boot_block_device, argv[3]);
+				system(command);
+				printf("Flash complete.\n");
+			}
+			return 0;
+		}
+	}
+
+	if (arg_error) {
+		printf("Invalid arguments supplied.\n");
+		printf("Usage:\n\n");
+		printf("Backup existing recovery ramdisk (requires dump_image):\n");
+		printf("injecttwrp --backup [optionalbackuplocation.img]\n\n");
+		printf("Inject new recovery ramdisk:\n");
+		printf("injecttwrp originalboot.img ramdisk-recovery.img outputboot.img\n");
+		printf("injecttwrp --dump ramdisk-recovery.img outputboot.img [--flash]\n");
+		printf("--dump will use dump_image to dump your existing boot image\n");
+		printf("--flash will use flash_image to flash the new boot image\n\n");
+		printf("NOTE: dump_image, erase_image, and flash_image must already be installed!\n\n");
+		printf("If needed you can add bd=/dev/block/mmcblk0p5 to indicate the location\n");
+		printf("of the boot partition on emmc devices as the final parameter.\n");
+		return 0;
+	}
+
+	return 0;
+}
diff --git a/install/Android.bp b/install/Android.bp
index c591714..22d3fec 100644
--- a/install/Android.bp
+++ b/install/Android.bp
@@ -29,6 +29,10 @@
         "libspl_check_defaults",
     ],
 
+    cflags: [
+        //"-DRECOVERY_API_VERSION=3"
+    ],
+
     shared_libs: [
         "libbase",
         "libbootloader_message",
@@ -51,6 +55,7 @@
         "libotautil",
         "libsnapshot_nobinder",
         "ota_metadata_proto_cc",
+	"update_metadata-protos",
 
         // external dependencies
         "libvintf",
@@ -79,6 +84,7 @@
         "libziparchive",
         "libz",
         "libprotobuf-cpp-lite",
+	"update_metadata-protos",
     ],
 }
 
diff --git a/install/ZipUtil.cpp b/install/ZipUtil.cpp
new file mode 100755
index 0000000..690942a
--- /dev/null
+++ b/install/ZipUtil.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ZipUtil.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <utime.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+#include <ziparchive/zip_archive.h>
+
+#include "otautil/dirutil.h"
+
+static constexpr mode_t UNZIP_DIRMODE = 0755;
+static constexpr mode_t UNZIP_FILEMODE = 0644;
+
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
+                             const std::string& dest_path, const struct utimbuf* timestamp,
+                             struct selabel_handle* sehnd) {
+    if (!zip_path.empty() && zip_path[0] == '/') {
+        LOG(ERROR) << "ExtractPackageRecursive(): zip_path must be a relative path " << zip_path;
+        return false;
+    }
+    if (dest_path.empty() || dest_path[0] != '/') {
+        LOG(ERROR) << "ExtractPackageRecursive(): dest_path must be an absolute path " << dest_path;
+        return false;
+    }
+
+    void* cookie;
+    std::string target_dir(dest_path);
+    if (dest_path.back() != '/') {
+        target_dir += '/';
+    }
+    std::string prefix_path(zip_path);
+    if (!zip_path.empty() && zip_path.back() != '/') {
+        prefix_path += '/';
+    }
+
+    const std::string zip_prefix(prefix_path.c_str());
+    int ret = StartIteration(zip, &cookie, zip_prefix);
+
+    if (ret != 0) {
+        LOG(ERROR) << "failed to start iterating zip entries.";
+        return false;
+    }
+
+    std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
+    ZipEntry entry;
+    std::string name;
+    int extractCount = 0;
+    while (Next(cookie, &entry, &name) == 0) {
+        std::string entry_name(name.c_str(), name.c_str() + name.size());
+        CHECK_LE(prefix_path.size(), entry_name.size());
+        std::string path = target_dir + entry_name.substr(prefix_path.size());
+        // Skip dir.
+        if (path.back() == '/') {
+            continue;
+        }
+        //TODO(b/31917448) handle the symlink.
+
+        if (dirCreateHierarchy(path.c_str(), UNZIP_DIRMODE, timestamp, true, sehnd) != 0) {
+            LOG(ERROR) << "failed to create dir for " << path;
+            return false;
+        }
+
+        char *secontext = NULL;
+        if (sehnd) {
+            selabel_lookup(sehnd, &secontext, path.c_str(), UNZIP_FILEMODE);
+            setfscreatecon(secontext);
+        }
+        android::base::unique_fd fd(open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, UNZIP_FILEMODE));
+        if (fd == -1) {
+            PLOG(ERROR) << "Can't create target file \"" << path << "\"";
+            return false;
+        }
+        if (secontext) {
+            freecon(secontext);
+            setfscreatecon(NULL);
+        }
+
+        int err = ExtractEntryToFile(zip, &entry, fd);
+        if (err != 0) {
+            LOG(ERROR) << "Error extracting \"" << path << "\" : " << ErrorCodeString(err);
+            return false;
+        }
+
+        if (fsync(fd) != 0) {
+            PLOG(ERROR) << "Error syncing file descriptor when extracting \"" << path << "\"";
+            return false;
+        }
+
+        if (timestamp != nullptr && utime(path.c_str(), timestamp)) {
+            PLOG(ERROR) << "Error touching \"" << path << "\"";
+            return false;
+        }
+
+        LOG(INFO) << "Extracted file \"" << path << "\"";
+        ++extractCount;
+    }
+
+    LOG(INFO) << "Extracted " << extractCount << " file(s)";
+    return true;
+}
diff --git a/install/ZipUtil.h b/install/ZipUtil.h
new file mode 100644
index 0000000..cda405c
--- /dev/null
+++ b/install/ZipUtil.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _OTAUTIL_ZIPUTIL_H
+#define _OTAUTIL_ZIPUTIL_H
+
+#include <utime.h>
+
+#include <string>
+
+#include <selinux/label.h>
+#include <ziparchive/zip_archive.h>
+
+/*
+ * Inflate all files under zip_path to the directory specified by
+ * dest_path, which must exist and be a writable directory. The zip_path
+ * is allowed to be an empty string, in which case the whole package
+ * will be extracted.
+ *
+ * Directory entries are not extracted.
+ *
+ * The immediate children of zip_path will become the immediate
+ * children of dest_path; e.g., if the archive contains the entries
+ *
+ *     a/b/c/one
+ *     a/b/c/two
+ *     a/b/c/d/three
+ *
+ * and ExtractPackageRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting
+ * files will be
+ *
+ *     /tmp/one
+ *     /tmp/two
+ *     /tmp/d/three
+ *
+ * If timestamp is non-NULL, file timestamps will be set accordingly.
+ *
+ * Returns true on success, false on failure.
+ */
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
+                             const std::string& dest_path, const struct utimbuf* timestamp,
+                             struct selabel_handle* sehnd);
+
+#endif // _OTAUTIL_ZIPUTIL_H
diff --git a/install/adb_install.cpp b/install/adb_install.cpp
old mode 100644
new mode 100755
index b12e529..b9dac0f
--- a/install/adb_install.cpp
+++ b/install/adb_install.cpp
@@ -90,12 +90,12 @@
 
 // Installs the package from FUSE. Returns the installation result and whether it should continue
 // waiting for new commands.
-static auto AdbInstallPackageHandler(Device* device, InstallResult* result) {
+static auto AdbInstallPackageHandler(InstallResult* result) {
   // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long
   // because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME
   // will start to exist once the host connects and starts serving a package. Poll for its
   // appearance. (Note that inotify doesn't work with FUSE.)
-  auto ui = device->GetUI();
+  //auto ui = device->GetUI();
   constexpr int ADB_INSTALL_TIMEOUT = 15;
   bool should_continue = true;
   *result = INSTALL_ERROR;
@@ -107,15 +107,14 @@
         continue;
       } else {
         should_continue = false;
-        ui->Print("\nTimed out waiting for fuse to be ready.\n\n");
+        // ui->Print("\nTimed out waiting for fuse to be ready.\n\n");
         break;
       }
     }
 
     auto package =
-        Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME,
-                                   std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
-    *result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0, device);
+        Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME, nullptr);
+    *result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0);
     break;
   }
 
@@ -187,7 +186,7 @@
 
 // TODO(xunchang) add a wrapper function and kill the minadbd service there.
 static void ListenAndExecuteMinadbdCommands(
-    RecoveryUI* ui, pid_t minadbd_pid, android::base::unique_fd&& socket_fd,
+    pid_t minadbd_pid, android::base::unique_fd&& socket_fd,
     const std::map<MinadbdCommand, CommandFunction>& command_map) {
   android::base::unique_fd epoll_fd(epoll_create1(O_CLOEXEC));
   if (epoll_fd == -1) {
@@ -211,8 +210,8 @@
   constexpr int TIMEOUT_MILLIS = 300 * 1000;
   while (true) {
     // Reset the progress bar and the background image before each command.
-    ui->SetProgressType(RecoveryUI::EMPTY);
-    ui->SetBackground(RecoveryUI::NO_COMMAND);
+    // ui->SetProgressType(RecoveryUI::EMPTY);
+    // ui->SetBackground(RecoveryUI::NO_COMMAND);
 
     // Poll for the status change of the socket_fd, and handle the message if the fd is ready to
     // read.
@@ -277,8 +276,8 @@
 //                               b11. exit the listening loop
 //
 static void CreateMinadbdServiceAndExecuteCommands(
-    RecoveryUI* ui, const std::map<MinadbdCommand, CommandFunction>& command_map,
-    bool rescue_mode) {
+    const std::map<MinadbdCommand, CommandFunction>& command_map,
+    bool rescue_mode, std::string install_file) {
   signal(SIGPIPE, SIG_IGN);
 
   android::base::unique_fd recovery_socket;
@@ -296,8 +295,10 @@
   if (child == 0) {
     recovery_socket.reset();
     std::vector<std::string> minadbd_commands = {
-      "/system/bin/minadbd",
-      "--socket_fd",
+      "/system/bin/recovery",
+      "recovery",
+      "--adbd",
+      install_file,
       std::to_string(minadbd_socket.release()),
     };
     if (rescue_mode) {
@@ -317,7 +318,7 @@
     return;
   }
 
-  std::thread listener_thread(ListenAndExecuteMinadbdCommands, ui, child,
+  std::thread listener_thread(ListenAndExecuteMinadbdCommands, child,
                               std::move(recovery_socket), std::ref(command_map));
   if (listener_thread.joinable()) {
     listener_thread.join();
@@ -336,7 +337,8 @@
   signal(SIGPIPE, SIG_DFL);
 }
 
-InstallResult ApplyFromAdb(Device* device, bool rescue_mode, Device::BuiltinAction* reboot_action) {
+  int ApplyFromAdb(const char* install_file, Device::BuiltinAction* reboot_action) {
+
   // Save the usb state to restore after the sideload operation.
   std::string usb_state = android::base::GetProperty("sys.usb.state", "none");
   // Clean up state and stop adbd.
@@ -345,24 +347,18 @@
     return INSTALL_ERROR;
   }
 
-  RecoveryUI* ui = device->GetUI();
+  // RecoveryUI* ui = device->GetUI();
 
   InstallResult install_result = INSTALL_ERROR;
   std::map<MinadbdCommand, CommandFunction> command_map{
-    { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, device, &install_result) },
+    { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, &install_result) },
     { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid,
                                                 &install_result, reboot_action) },
-    { MinadbdCommand::kRebootBootloader,
-      std::bind(&AdbRebootHandler, MinadbdCommand::kRebootBootloader, &install_result,
-                reboot_action) },
-    { MinadbdCommand::kRebootFastboot, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootFastboot,
-                                                 &install_result, reboot_action) },
-    { MinadbdCommand::kRebootRecovery, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRecovery,
-                                                 &install_result, reboot_action) },
-    { MinadbdCommand::kRebootRescue,
-      std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) },
-  };
+  { MinadbdCommand::kRebootRescue,
+    std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) },
+};
 
+/*
   if (!rescue_mode) {
     ui->Print(
         "\n\nNow send the package you want to apply\n"
@@ -376,8 +372,8 @@
 
     ui->Print("\n\nWaiting for rescue commands...\n");
   }
-
-  CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode);
+*/
+  CreateMinadbdServiceAndExecuteCommands(command_map, false, install_file);
 
   // Clean up before switching to the older state, for example setting the state
   // to none sets sys/class/android_usb/android0/enable to 0.
diff --git a/install/fuse_install.cpp b/install/fuse_install.cpp
old mode 100644
new mode 100755
index 197e1de..5ae5382
--- a/install/fuse_install.cpp
+++ b/install/fuse_install.cpp
@@ -146,7 +146,7 @@
   return run_fuse_sideload(std::move(fuse_data_provider)) == 0;
 }
 
-InstallResult InstallWithFuseFromPath(std::string_view path, Device* device) {
+InstallResult InstallWithFuseFromPath(std::string_view path, __attribute__((unused)) RecoveryUI* ui) {
   // We used to use fuse in a thread as opposed to a process. Since accessing
   // through fuse involves going from kernel to userspace to kernel, it leads
   // to deadlock when a page fault occurs. (Bug: 26313124)
@@ -182,10 +182,9 @@
       }
     }
     auto package =
-        Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME,
-                                   std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
-    result = InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0 /* retry_count */,
-                            device);
+        Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME, nullptr);
+    result =
+        InstallPackage(package.get(), FUSE_SIDELOAD_HOST_PATHNAME, false, 0 /* retry_count */);
     break;
   }
 
diff --git a/install/get_args.cpp b/install/get_args.cpp
new file mode 100755
index 0000000..92f6b10
--- /dev/null
+++ b/install/get_args.cpp
@@ -0,0 +1,74 @@
+#include "twinstall/get_args.h"
+
+std::string stage;
+
+// command line args come from, in decreasing precedence:
+//   - the actual command line
+//   - the bootloader control block (one per line, after "recovery")
+//   - the contents of COMMAND_FILE (one per line)
+std::vector<std::string> args::get_args(const int *argc, char*** const argv) {
+  CHECK_GT(*argc, 0);
+
+  bootloader_message boot = {};
+
+  std::string err;
+  if (!read_bootloader_message(&boot, &err)) {
+    LOG(ERROR) << err;
+    // If fails, leave a zeroed bootloader_message.
+    boot = {};
+  }
+  stage = std::string(boot.stage);
+
+  std::string boot_command;
+  if (boot.command[0] != 0) {
+    if (memchr(boot.command, '\0', sizeof(boot.command))) {
+      boot_command = std::string(boot.command);
+    } else {
+      boot_command = std::string(boot.command, sizeof(boot.command));
+    }
+    LOG(INFO) << "Boot command: " << boot_command;
+    printf("boot command: %s\n", boot_command.c_str());
+  }
+
+  if (boot.status[0] != 0) {
+    std::string boot_status = std::string(boot.status, sizeof(boot.status));
+    LOG(INFO) << "Boot status: " << boot_status;
+  }
+
+  std::vector<std::string> args(*argv, *argv + *argc);
+
+  // --- if arguments weren't supplied, look in the bootloader control block
+  if (args.size() == 1) {
+    boot.recovery[sizeof(boot.recovery) - 1] = '\0';  // Ensure termination
+    std::string boot_recovery(boot.recovery);
+    std::vector<std::string> tokens = android::base::Split(boot_recovery, "\n");
+    if (!tokens.empty() && tokens[0] == "recovery") {
+      for (auto it = tokens.begin() + 1; it != tokens.end(); it++) {
+        // Skip empty and '\0'-filled tokens.
+        if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it));
+      }
+      LOG(INFO) << "Got " << args.size() << " arguments from boot message";
+    } else if (boot.recovery[0] != 0) {
+      LOG(ERROR) << "Bad boot message: \"" << boot_recovery << "\"";
+    }
+  }
+
+  // Write the arguments (excluding the filename in args[0]) back into the
+  // bootloader control block. So the device will always boot into recovery to
+  // finish the pending work, until finish_recovery() is called.
+  std::vector<std::string> options(args.cbegin() + 1, args.cend());
+  if (!update_bootloader_message(options, &err)) {
+    LOG(ERROR) << "Failed to set BCB message: " << err;
+  }
+
+  // Finally, if no arguments were specified, check whether we should boot
+  // into fastboot or rescue mode.
+  if (args.size() == 1 && boot_command == "boot-fastboot") {
+    printf("fastbootd needed\n");
+    args.emplace_back("--fastboot");
+  } else if (args.size() == 1 && boot_command == "boot-rescue") {
+    args.emplace_back("--rescue");
+  }
+
+  return args;
+}
diff --git a/install/include/install/adb_install.h b/install/include/install/adb_install.h
old mode 100644
new mode 100755
diff --git a/install/include/install/install.h b/install/include/install/install.h
old mode 100644
new mode 100755
index 0f5102f..b2d9ca9
--- a/install/include/install/install.h
+++ b/install/include/install/install.h
@@ -50,12 +50,11 @@
 // cache partition after a successful installation if |should_wipe_cache| is true or an updater
 // command asks to wipe the cache.
 InstallResult InstallPackage(Package* package, const std::string_view package_id,
-                             bool should_wipe_cache, int retry_count,
-                             Device* ui);
+                             bool should_wipe_cache, int retry_count);
 
 // Verifies the package by ota keys. Returns true if the package is verified successfully,
 // otherwise returns false.
-bool verify_package(Package* package, RecoveryUI* ui);
+bool verify_package(Package* package);
 
 // Reads meta data file of the package; parses each line in the format "key=value"; and writes the
 // result to |metadata|. Return true if succeed, otherwise return false.
diff --git a/install/include/install/wipe_data.h b/install/include/install/wipe_data.h
old mode 100644
new mode 100755
index 42cad87..0505fc9
--- a/install/include/install/wipe_data.h
+++ b/install/include/install/wipe_data.h
@@ -24,7 +24,7 @@
 struct selabel_handle;
 
 // Returns true on success.
-bool WipeCache(RecoveryUI* ui, const std::function<bool()>& confirm);
+bool WipeCache(const std::function<bool()>& confirm);
 
 // Returns true on success.
-bool WipeData(Device* device);
+bool WipeData();
diff --git a/install/include/install/wipe_device.h b/install/include/install/wipe_device.h
old mode 100644
new mode 100755
diff --git a/install/include/set_metadata.h b/install/include/set_metadata.h
new file mode 100644
index 0000000..9a46be9
--- /dev/null
+++ b/install/include/set_metadata.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Team Win Recovery Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The purpose of these functions is to try to get and set the proper
+ * file permissions, SELinux contexts, owner, and group so that these
+ * files are accessible when we boot up to normal Android via MTP and to
+ * file manager apps. During early boot we try to read the contexts and
+ * owner / group info from /data/media or from /data/media/0 and store
+ * them in static variables. From there, we'll try to set the same
+ * contexts, owner, and group information on most files we create during
+ * operations like backups, copying the log, and MTP operations.
+ */
+
+#ifndef _RECOVERY_SET_CONTEXTS_H
+#define _RECOVERY_SET_CONTEXTS_H
+
+#include <sys/stat.h>
+#include "selinux/selinux.h"
+
+int tw_get_default_metadata(const char* filename);
+int tw_set_default_metadata(const char* filename);
+
+#endif //_RECOVERY_SET_CONTEXTS_H
diff --git a/install/install.cpp b/install/install.cpp
old mode 100644
new mode 100755
index 83f3cad..8c92817
--- a/install/install.cpp
+++ b/install/install.cpp
@@ -66,8 +66,8 @@
 static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions.");
 
 // Default allocation of progress bar segments to operations
-static constexpr int VERIFICATION_PROGRESS_TIME = 60;
-static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
+// static constexpr int VERIFICATION_PROGRESS_TIME = 60;
+// static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
 // The charater used to separate dynamic fingerprints. e.x. sargo|aosp-sargo
 static const char* FINGERPRING_SEPARATOR = "|";
 static std::condition_variable finish_log_temperature;
@@ -343,11 +343,11 @@
   }
 }
 
-static bool PerformPowerwashIfRequired(ZipArchiveHandle zip, Device *device) {
+static bool PerformPowerwashIfRequired(ZipArchiveHandle zip) {
   const auto payload_properties = ExtractPayloadProperties(zip);
   if (payload_properties.find("POWERWASH=1") != std::string::npos) {
     LOG(INFO) << "Payload properties has POWERWASH=1, wiping userdata...";
-    return WipeData(device);
+    return WipeData();
   }
   return true;
 }
@@ -355,8 +355,7 @@
 // If the package contains an update binary, extract it and run it.
 static InstallResult TryUpdateBinary(Package* package, bool* wipe_cache,
                                      std::vector<std::string>* log_buffer, int retry_count,
-                                     int* max_temperature, Device* device) {
-  auto ui = device->GetUI();
+                                     int* max_temperature) {
   std::map<std::string, std::string> metadata;
   auto zip = package->GetZipArchiveHandle();
   if (!ReadMetadataFromPackage(zip, &metadata)) {
@@ -491,7 +490,7 @@
       int seconds;
       if (tokens.size() == 2 && android::base::ParseDouble(tokens[0].c_str(), &fraction) &&
           android::base::ParseInt(tokens[1], &seconds)) {
-        ui->ShowProgress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), seconds);
+        // ui->ShowProgress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), seconds);
       } else {
         LOG(ERROR) << "invalid \"progress\" parameters: " << line;
       }
@@ -499,22 +498,22 @@
       std::vector<std::string> tokens = android::base::Split(args, " ");
       double fraction;
       if (tokens.size() == 1 && android::base::ParseDouble(tokens[0].c_str(), &fraction)) {
-        ui->SetProgress(fraction);
+        // ui->SetProgress(fraction);
       } else {
         LOG(ERROR) << "invalid \"set_progress\" parameters: " << line;
       }
     } else if (command == "ui_print") {
-      ui->PrintOnScreenOnly("%s\n", args.c_str());
+      // ui->PrintOnScreenOnly("%s\n", args.c_str());
       fflush(stdout);
     } else if (command == "wipe_cache") {
       *wipe_cache = true;
     } else if (command == "clear_display") {
-      ui->SetBackground(RecoveryUI::NONE);
+      // ui->SetBackground(RecoveryUI::NONE);
     } else if (command == "enable_reboot") {
       // packages can explicitly request that they want the user
       // to be able to reboot during installation (useful for
       // debugging packages that don't exit).
-      ui->SetEnableReboot(true);
+      // ui->SetEnableReboot(true);
     } else if (command == "retry_update") {
       retry_update = true;
     } else if (command == "log") {
@@ -551,43 +550,40 @@
   } else {
     LOG(FATAL) << "Invalid status code " << status;
   }
-  PerformPowerwashIfRequired(zip, device);
+  PerformPowerwashIfRequired(zip);
 
   return INSTALL_SUCCESS;
 }
 
 static InstallResult VerifyAndInstallPackage(Package* package, bool* wipe_cache,
                                              std::vector<std::string>* log_buffer, int retry_count,
-                                             int* max_temperature, Device* device) {
-  auto ui = device->GetUI();
-  ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
+                                             int* max_temperature) {
+  // ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
   // Give verification half the progress bar...
-  ui->SetProgressType(RecoveryUI::DETERMINATE);
-  ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
+  // ui->SetProgressType(RecoveryUI::DETERMINATE);
+  // ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
 
   // Verify package.
-  if (!verify_package(package, ui)) {
+  if (!verify_package(package)) {
     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
     return INSTALL_CORRUPT;
   }
 
   // Verify and install the contents of the package.
-  ui->Print("Installing update...\n");
-  if (retry_count > 0) {
-    ui->Print("Retry attempt: %d\n", retry_count);
-  }
-  ui->SetEnableReboot(false);
-  auto result =
-      TryUpdateBinary(package, wipe_cache, log_buffer, retry_count, max_temperature, device);
-  ui->SetEnableReboot(true);
-  ui->Print("\n");
+  // ui->Print("Installing update...\n");
+  // if (retry_count > 0) {
+    // ui->Print("Retry attempt: %d\n", retry_count);
+  // }
+  // ui->SetEnableReboot(false);
+  auto result = TryUpdateBinary(package, wipe_cache, log_buffer, retry_count, max_temperature);
+  // ui->SetEnableReboot(true);
+  // ui->Print("\n");
 
   return result;
 }
 
 InstallResult InstallPackage(Package* package, const std::string_view package_id,
-                             bool should_wipe_cache, int retry_count, Device* device) {
-  auto ui = device->GetUI();
+                             bool should_wipe_cache, int retry_count) {
   auto start = std::chrono::system_clock::now();
 
   int start_temperature = GetMaxValueFromThermalZone();
@@ -596,9 +592,9 @@
   InstallResult result;
   std::vector<std::string> log_buffer;
 
-  ui->Print("Supported API: %d\n", kRecoveryApiVersion);
+  // ui->Print("Supported API: %d\n", kRecoveryApiVersion);
 
-  ui->Print("Finding update package...\n");
+  // ui->Print("Finding update package...\n");
   LOG(INFO) << "Update package id: " << package_id;
   if (!package) {
     log_buffer.push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
@@ -609,7 +605,7 @@
   } else {
     bool updater_wipe_cache = false;
     result = VerifyAndInstallPackage(package, &updater_wipe_cache, &log_buffer, retry_count,
-                                     &max_temperature, device);
+                                     &max_temperature);
     should_wipe_cache = should_wipe_cache || updater_wipe_cache;
   }
 
@@ -666,7 +662,7 @@
   LOG(INFO) << log_content;
 
   if (result == INSTALL_SUCCESS && should_wipe_cache) {
-    if (!WipeCache(ui, nullptr)) {
+    if (!WipeCache(nullptr)) {
       result = INSTALL_ERROR;
     }
   }
@@ -674,7 +670,7 @@
   return result;
 }
 
-bool verify_package(Package* package, RecoveryUI* ui) {
+bool verify_package(Package* package) {
   static constexpr const char* CERTIFICATE_ZIP_FILE = "/system/etc/security/otacerts.zip";
   std::vector<Certificate> loaded_keys = LoadKeysFromZipfile(CERTIFICATE_ZIP_FILE);
   if (loaded_keys.empty()) {
@@ -684,11 +680,11 @@
   LOG(INFO) << loaded_keys.size() << " key(s) loaded from " << CERTIFICATE_ZIP_FILE;
 
   // Verify package.
-  ui->Print("Verifying update package...\n");
-  auto t0 = std::chrono::system_clock::now();
+  // ui->Print("Verifying update package...\n");
+  // auto t0 = std::chrono::system_clock::now();
   int err = verify_file(package, loaded_keys);
-  std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
-  ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
+  // std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
+  // ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
   if (err != VERIFY_SUCCESS) {
     LOG(ERROR) << "Signature verification failed";
     LOG(ERROR) << "error: " << kZipVerificationFailure;
diff --git a/install/set_metadata.cpp b/install/set_metadata.cpp
new file mode 100644
index 0000000..9eb20bf
--- /dev/null
+++ b/install/set_metadata.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 The Team Win Recovery Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The purpose of these functions is to try to get and set the proper
+ * file permissions, SELinux contexts, owner, and group so that these
+ * files are accessible when we boot up to normal Android via MTP and to
+ * file manager apps. During early boot we try to read the contexts and
+ * owner / group info from /data/media or from /data/media/0 and store
+ * them in static variables. From there, we'll try to set the same
+ * contexts, owner, and group information on most files we create during
+ * operations like backups, copying the log, and MTP operations.
+ */
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "selinux/selinux.h"
+
+static char* selinux_context;
+struct stat s;
+static int has_stat = 0;
+
+int tw_get_context(const char* filename) {
+	if (lgetfilecon(filename, &selinux_context) >= 0) {
+		printf("tw_get_context got selinux context: %s, file: %s\n", selinux_context, filename);
+		return 0;
+	} else {
+		printf("tw_get_context failed to get selinux context, file %s\n", filename);
+		selinux_context = NULL;
+	}
+	return -1;
+}
+
+int tw_get_stat(const char* filename) {
+	if (lstat(filename, &s) == 0) {
+		has_stat = 1;
+		return 0;
+	}
+	printf("tw_get_stat failed to lstat '%s'\n", filename);
+	return -1;
+}
+
+int tw_get_default_metadata(const char* filename) {
+	if (tw_get_context(filename) == 0 && tw_get_stat(filename) == 0)
+		return 0;
+	return -1;
+}
+
+// Most of this logging is disabled to prevent log spam if we are trying
+// to set contexts and permissions on file systems that do not support
+// these types of things (e.g. vfat / FAT / FAT32).
+int tw_set_default_metadata(const char* filename) {
+	int ret = 0;
+	struct stat st;
+
+	if (selinux_context == NULL) {
+		//printf("selinux_context was null, '%s'\n", filename);
+		ret = -1;
+	} else if (lsetfilecon(filename, selinux_context) < 0) {
+		//printf("Failed to set default contexts on '%s'.\n", filename);
+		ret = -1;
+	}
+
+	if (lstat(filename, &st) == 0 && st.st_mode & S_IFREG && chmod(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH) < 0) {
+		//printf("Failed to chmod '%s'\n", filename);
+		ret = -1;
+	}
+
+	if (has_stat && chown(filename, s.st_uid, s.st_gid) < 0) {
+		//printf("Failed to lchown '%s'.\n", filename);
+		ret = -1;
+	}
+	//printf("Done trying to set defaults on '%s'\n");
+	return ret;
+}
diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp
old mode 100644
new mode 100755
index 024c1e1..6b7cc25
--- a/install/wipe_data.cpp
+++ b/install/wipe_data.cpp
@@ -32,12 +32,15 @@
 #include "recovery_utils/roots.h"
 
 constexpr const char* CACHE_ROOT = "/cache";
-constexpr const char* DATA_ROOT = "/data";
-constexpr const char* METADATA_ROOT = "/metadata";
+//constexpr const char* DATA_ROOT = "/data";
+//constexpr const char* METADATA_ROOT = "/metadata";
 
-static bool EraseVolume(const char* volume, RecoveryUI* ui) {
+static bool EraseVolume(const char* volume) {
   bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
 
+  // ui->SetBackground(RecoveryUI::ERASING);
+  // ui->SetProgressType(RecoveryUI::INDETERMINATE);
+
   std::vector<saved_log_file> log_files;
   if (is_cache) {
     // If we're reformatting /cache, we load any past logs (i.e. "/cache/recovery/last_*") and the
@@ -45,7 +48,7 @@
     log_files = ReadLogFilesToMemory();
   }
 
-  ui->Print("Formatting %s...\n", volume);
+  // ui->Print("Formatting %s...\n", volume);
 
   ensure_path_unmounted(volume);
 
@@ -58,10 +61,10 @@
   return (result == 0);
 }
 
-bool WipeCache(RecoveryUI* ui, const std::function<bool()>& confirm_func) {
+bool WipeCache(const std::function<bool()>& confirm_func) {
   bool has_cache = volume_for_mount_point("/cache") != nullptr;
   if (!has_cache) {
-    ui->Print("No /cache partition found.\n");
+    // ui->Print("No /cache partition found.\n");
     return false;
   }
 
@@ -69,40 +72,41 @@
     return false;
   }
 
-  ui->Print("\n-- Wiping cache...\n");
-  ui->SetBackground(RecoveryUI::ERASING);
-  ui->SetProgressType(RecoveryUI::INDETERMINATE);
+  // ui->Print("\n-- Wiping cache...\n");
+  // ui->SetBackground(RecoveryUI::ERASING);
+  // ui->SetProgressType(RecoveryUI::INDETERMINATE);
 
-  bool success = EraseVolume("/cache", ui);
-  ui->Print("Cache wipe %s.\n", success ? "complete" : "failed");
+  bool success = EraseVolume("/cache");
+  //ui->Print("Cache wipe %s.\n", success ? "complete" : "failed");
   return success;
 }
 
-bool WipeData(Device* device) {
-  RecoveryUI* ui = device->GetUI();
-  ui->Print("\n-- Wiping data...\n");
-  ui->SetBackground(RecoveryUI::ERASING);
-  ui->SetProgressType(RecoveryUI::INDETERMINATE);
+bool WipeData() {
+  // RecoveryUI* ui = device->GetUI();
+  // ui->Print("\n-- Wiping data...\n");
+  // ui->SetBackground(RecoveryUI::ERASING);
+  // ui->SetProgressType(RecoveryUI::INDETERMINATE);
 
-  if (!FinishPendingSnapshotMerges(device)) {
-    ui->Print("Unable to check update status or complete merge, cannot wipe partitions.\n");
-    return false;
-  }
+  // if (!FinishPendingSnapshotMerges(device)) {
+  //   ui->Print("Unable to check update status or complete merge, cannot wipe partitions.\n");
+  //   return false;
+  // }
 
-  bool success = device->PreWipeData();
-  if (success) {
-    success &= EraseVolume(DATA_ROOT, ui);
-    bool has_cache = volume_for_mount_point("/cache") != nullptr;
-    if (has_cache) {
-      success &= EraseVolume(CACHE_ROOT, ui);
-    }
-    if (volume_for_mount_point(METADATA_ROOT) != nullptr) {
-      success &= EraseVolume(METADATA_ROOT, ui);
-    }
-  }
-  if (success) {
-    success &= device->PostWipeData();
-  }
-  ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
+  //bool success = device->PreWipeData();
+  bool success = true;
+  //if (success) {
+    //success &= EraseVolume(DATA_ROOT);
+    //bool has_cache = volume_for_mount_point("/cache") != nullptr;
+    //if (has_cache) {
+    //  success &= EraseVolume(CACHE_ROOT);
+   // }
+    //if (volume_for_mount_point(METADATA_ROOT) != nullptr) {
+      //success &= EraseVolume(METADATA_ROOT);
+    //}
+  //}
+  //if (success) {
+    //success &= device->PostWipeData();
+  //}
+  // ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
   return success;
 }
diff --git a/install/wipe_device.cpp b/install/wipe_device.cpp
index 0a525fa..fe7e78e 100644
--- a/install/wipe_device.cpp
+++ b/install/wipe_device.cpp
@@ -144,15 +144,15 @@
   }
 
   return Package::CreateMemoryPackage(
-      std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr);
+      std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()));
 }
 
 // Checks if the wipe package matches expectation. If the check passes, reads the list of
 // partitions to wipe from the package. Checks include
 // 1. verify the package.
 // 2. check metadata (ota-type, pre-device and serial number if having one).
-static bool CheckWipePackage(Package* wipe_package, RecoveryUI* ui) {
-  if (!verify_package(wipe_package, ui)) {
+static bool CheckWipePackage(Package* wipe_package, __attribute__((unused)) RecoveryUI* ui) {
+  if (!verify_package(wipe_package)) {
     LOG(ERROR) << "Failed to verify package";
     return false;
   }
diff --git a/kernel_module_loader.cpp b/kernel_module_loader.cpp
new file mode 100644
index 0000000..db5dee8
--- /dev/null
+++ b/kernel_module_loader.cpp
@@ -0,0 +1,184 @@
+#include "kernel_module_loader.hpp"
+#include "common.h"
+
+const std::vector<std::string> kernel_modules_requested = TWFunc::split_string(EXPAND(TW_LOAD_VENDOR_MODULES), ' ', true);
+
+bool KernelModuleLoader::Load_Vendor_Modules() {
+	// check /lib/modules (ramdisk vendor_boot)
+	// check /lib/modules/N.N (ramdisk vendor_boot)
+	// check /lib/modules/N.N-gki (ramdisk vendor_boot)
+	// check /vendor/lib/modules (ramdisk)
+	// check /vendor/lib/modules/1.1 (ramdisk prebuilt modules)
+	// check /vendor/lib/modules/N.N (vendor mounted)
+	// check /vendor/lib/modules/N.N-gki (vendor mounted)
+	// check /vendor_dlkm/lib/modules (vendor_dlkm mounted)
+	int modules_loaded = 0;
+
+	LOGINFO("Attempting to load modules\n");
+	std::string vendor_base_dir(VENDOR_MODULE_DIR);
+	std::string base_dir(VENDOR_BOOT_MODULE_DIR);
+	std::string vendor_dlkm_base_dir(VENDOR_DLKM_MODULE_DIR);
+	std::vector<std::string> module_dirs;
+	std::vector<std::string> vendor_module_dirs;
+
+	TWPartition* ven = PartitionManager.Find_Partition_By_Path("/vendor");
+	TWPartition* ven_dlkm = PartitionManager.Find_Partition_By_Path("/vendor_dlkm");
+	vendor_module_dirs.push_back(VENDOR_MODULE_DIR);
+	vendor_module_dirs.push_back(vendor_base_dir + "/1.1");
+
+	module_dirs.push_back(base_dir);
+
+	struct utsname uts;
+	if (uname(&uts)) {
+		LOGERR("Unable to query kernel for version info\n");
+	}
+
+	std::string rls(uts.release);
+	std::vector<std::string> release = TWFunc::split_string(rls, '.', true);
+	int expected_module_count = kernel_modules_requested.size();
+	module_dirs.push_back(base_dir + "/" + release[0] + "." + release[1]);
+	std::string gki = "/" + release[0] + "." + release[1] + "-gki";
+	module_dirs.push_back(base_dir + gki);
+	vendor_module_dirs.push_back(vendor_base_dir + gki);
+
+	for (auto&& module_dir:module_dirs) {
+		modules_loaded += Try_And_Load_Modules(module_dir, false);
+		if (modules_loaded >= expected_module_count) goto exit;
+	}
+
+	for (auto&& module_dir:vendor_module_dirs) {
+		modules_loaded += Try_And_Load_Modules(module_dir, false);
+		if (modules_loaded >= expected_module_count) goto exit;
+	}
+
+	if (ven) {
+		LOGINFO("Checking mounted /vendor\n");
+		ven->Mount(true);
+	}
+	if (ven_dlkm) {
+		LOGINFO("Checking mounted /vendor_dlkm\n");
+		ven_dlkm->Mount(true);
+	}
+
+	for (auto&& module_dir:vendor_module_dirs) {
+		modules_loaded += Try_And_Load_Modules(module_dir, true);
+		if (modules_loaded >= expected_module_count) goto exit;
+	}
+
+	modules_loaded += Try_And_Load_Modules(vendor_dlkm_base_dir, true);
+	if (modules_loaded >= expected_module_count) goto exit;
+
+exit:
+	if (ven)
+		ven->UnMount(false);
+	if (ven_dlkm)
+		ven_dlkm->UnMount(false);
+
+	android::base::SetProperty("twrp.modules.loaded", "true");
+
+#ifdef TW_BATTERY_SYSFS_WAIT_SECONDS
+	TWFunc::Wait_For_Battery(std::chrono::seconds(TW_BATTERY_SYSFS_WAIT_SECONDS));
+#endif
+
+	return true;
+}
+
+int KernelModuleLoader::Try_And_Load_Modules(std::string module_dir, bool vendor_is_mounted) {
+		LOGINFO("Checking directory: %s\n", module_dir.c_str());
+		int modules_loaded = 0;
+		std::string dest_module_dir;
+		dest_module_dir = "/tmp" + module_dir;
+		TWFunc::Recursive_Mkdir(dest_module_dir);
+		Copy_Modules_To_Tmpfs(module_dir);
+		if (!Write_Module_List(dest_module_dir))
+			return kernel_modules_requested.size();
+		if (!vendor_is_mounted && module_dir == "/vendor/lib/modules") {
+			module_dir = "/lib/modules";
+		}
+		LOGINFO("mounting %s on %s\n", dest_module_dir.c_str(), module_dir.c_str());
+		if (mount(dest_module_dir.c_str(), module_dir.c_str(), "", MS_BIND, NULL) == 0) {
+			Modprobe m({module_dir}, "modules.load.twrp", false);
+			m.LoadListedModules(false);
+			modules_loaded = m.GetModuleCount();
+			umount2(module_dir.c_str(), MNT_DETACH);
+			LOGINFO("Modules Loaded: %d\n", modules_loaded);
+		}
+		return modules_loaded;
+}
+
+std::vector<string> KernelModuleLoader::Skip_Loaded_Kernel_Modules() {
+	std::vector<string> kernel_modules = kernel_modules_requested;
+	std::vector<string> loaded_modules;
+	std::string kernel_module_file = "/proc/modules";
+	if (TWFunc::read_file(kernel_module_file, loaded_modules) < 0)
+		LOGINFO("failed to get loaded kernel modules\n");
+	LOGINFO("number of modules loaded by init: %zu\n", loaded_modules.size());
+	if (loaded_modules.size() == 0)
+		return kernel_modules;
+	for (auto&& module_line:loaded_modules) {
+		auto module = TWFunc::Split_String(module_line, " ")[0];
+		std::string full_module_name = module + ".ko";
+		auto found = std::find(kernel_modules.begin(), kernel_modules.end(), full_module_name);
+		if (found != kernel_modules.end()) {
+			LOGINFO("found module to dedupe: %s\n", (*found).c_str());
+			kernel_modules.erase(found);
+		}
+	}
+	return kernel_modules;
+}
+
+bool KernelModuleLoader::Write_Module_List(std::string module_dir) {
+	DIR* d;
+	struct dirent* de;
+	std::vector<std::string> kernel_modules;
+	d = opendir(module_dir.c_str());
+	auto deduped_modules = Skip_Loaded_Kernel_Modules();
+	if (deduped_modules.size() == 0) {
+		LOGINFO("Requested modules are loaded\n");
+		return false;
+	}
+	if (d != nullptr) {
+		while ((de = readdir(d)) != nullptr) {
+			std::string kernel_module = de->d_name;
+			if (de->d_type == DT_REG) {
+				if (android::base::EndsWith(kernel_module, ".ko")) {
+					for (auto&& requested:kernel_modules_requested) {
+						if (kernel_module == requested) {
+							kernel_modules.push_back(kernel_module);
+							continue;
+						}
+					}
+					continue;
+				}
+			}
+		}
+		std::string module_file = module_dir + "/modules.load.twrp";
+		TWFunc::write_to_file(module_file, kernel_modules);
+		closedir(d);
+	}
+	return true;
+}
+
+bool KernelModuleLoader::Copy_Modules_To_Tmpfs(std::string module_dir) {
+	std::string ramdisk_dir = "/tmp" + module_dir;
+	DIR* d;
+	struct dirent* de;
+	d = opendir(module_dir.c_str());
+	if (d != nullptr) {
+		while ((de = readdir(d)) != nullptr) {
+			std::string kernel_module = de->d_name;
+			if (de->d_type == DT_REG) {
+				std::string src =  module_dir + "/" + de->d_name;
+				std::string dest = ramdisk_dir + "/" + de->d_name;
+				if (TWFunc::copy_file(src, dest, 0700, false) != 0) {
+					return false;
+				}
+			}
+		}
+		closedir(d);
+	} else {
+		LOGINFO("Unable to open module directory: %s. Skipping\n", module_dir.c_str());
+		return false;
+	}
+	return true;
+}
diff --git a/kernel_module_loader.hpp b/kernel_module_loader.hpp
new file mode 100644
index 0000000..afdfd8f
--- /dev/null
+++ b/kernel_module_loader.hpp
@@ -0,0 +1,36 @@
+#ifndef _KERNELMODULELOADER_HPP
+#define _KERNELMODULELOADER_HPP
+
+#include <dirent.h>
+#include <string>
+#include <vector>
+#include <android-base/strings.h>
+#include <modprobe/modprobe.h>
+#include <sys/mount.h>
+#include <sys/utsname.h>
+
+#include "twcommon.h"
+#include "twrp-functions.hpp"
+
+#define VENDOR_MODULE_DIR "/vendor/lib/modules"           // Base path for vendor kernel modules to check by TWRP
+#define VENDOR_BOOT_MODULE_DIR "/lib/modules"             // vendor_boot ramdisk GKI modules to check by TWRP
+#define VENDOR_DLKM_MODULE_DIR "/vendor_dlkm/lib/modules" // vendor_dlkm placed modules to check by TWRP
+typedef enum {
+	RECOVERY_FASTBOOT_MODE = 0,
+	RECOVERY_IN_BOOT_MODE,
+	FASTBOOTD_MODE
+} BOOT_MODE;
+
+class KernelModuleLoader
+{
+public:
+    static bool Load_Vendor_Modules(); // Load specific maintainer defined kernel modules in TWRP
+
+private:
+	static int Try_And_Load_Modules(std::string module_dir, bool vendor_is_mounted); // Use libmodprobe to attempt loading kernel modules
+	static bool Write_Module_List(std::string module_dir); // Write list of modules to load from TW_LOAD_VENDOR_MODULES
+    static bool Copy_Modules_To_Tmpfs(std::string module_dir); // Copy modules to ramdisk for loading
+	static std::vector<string> Skip_Loaded_Kernel_Modules(); // return list of loaded kernel modules already done by init
+};
+
+#endif // _KERNELMODULELOADER_HPP
\ No newline at end of file
diff --git a/legacy_properties.h b/legacy_properties.h
new file mode 100644
index 0000000..44cdd95
--- /dev/null
+++ b/legacy_properties.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _INCLUDE_LEGACY_PROPERTIES_H
+#define _INCLUDE_LEGACY_PROPERTIES_H
+
+#include <sys/system_properties.h>
+
+typedef struct prop_area prop_area;
+typedef struct prop_msg prop_msg;
+
+#define PROP_AREA_MAGIC   0x504f5250
+#define PROP_AREA_VERSION 0x45434f76
+
+#define PROP_SERVICE_NAME "property_service"
+
+/* #define PROP_MAX_ENTRIES 247 */
+/* 247 -> 32620 bytes (<32768) */
+
+#define TOC_NAME_LEN(toc)       ((toc) >> 24)
+#define TOC_TO_INFO(area, toc)  ((prop_info*) (((char*) area) + ((toc) & 0xFFFFFF)))
+
+struct prop_area {
+    unsigned volatile count;
+    unsigned volatile serial;
+    unsigned magic;
+    unsigned version;
+    unsigned reserved[4];
+    unsigned toc[1];
+};
+
+#define SERIAL_VALUE_LEN(serial) ((serial) >> 24)
+#define SERIAL_DIRTY(serial) ((serial) & 1)
+
+struct prop_info {
+    char name[PROP_NAME_MAX];
+    unsigned volatile serial;
+    char value[PROP_VALUE_MAX];
+};
+
+struct prop_msg
+{
+    unsigned cmd;
+    char name[PROP_NAME_MAX];
+    char value[PROP_VALUE_MAX];
+};
+
+#define PROP_MSG_SETPROP 1
+
+/*
+** Rules:
+**
+** - there is only one writer, but many readers
+** - prop_area.count will never decrease in value
+** - once allocated, a prop_info's name will not change
+** - once allocated, a prop_info's offset will not change
+** - reading a value requires the following steps
+**   1. serial = pi->serial
+**   2. if SERIAL_DIRTY(serial), wait*, then goto 1
+**   3. memcpy(local, pi->value, SERIAL_VALUE_LEN(serial) + 1)
+**   4. if pi->serial != serial, goto 2
+**
+** - writing a value requires the following steps
+**   1. pi->serial = pi->serial | 1
+**   2. memcpy(pi->value, local_value, value_len)
+**   3. pi->serial = (value_len << 24) | ((pi->serial + 1) & 0xffffff)
+**
+** Improvements:
+** - maintain the toc sorted by pi->name to allow lookup
+**   by binary search
+**
+*/
+
+#define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"
+#define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"
+#define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"
+#define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"
+
+#endif
diff --git a/libaosprecovery_defaults.go b/libaosprecovery_defaults.go
new file mode 100644
index 0000000..1289ffa
--- /dev/null
+++ b/libaosprecovery_defaults.go
@@ -0,0 +1,62 @@
+package twrp
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+)
+
+func globalFlags(ctx android.BaseContext) []string {
+	var cflags []string
+
+	if getMakeVars(ctx, "AB_OTA_UPDATER") == "true" {
+		cflags = append(cflags, "-DAB_OTA_UPDATER=1")
+	}
+
+	if getMakeVars(ctx, "TW_USE_FSCRYPT_POLICY") == "1" {
+		cflags = append(cflags, "-DUSE_FSCRYPT_POLICY_V1")
+	} else {
+		cflags = append(cflags, "-DUSE_FSCRYPT_POLICY_V2")
+	}
+	return cflags
+}
+
+func globalSrcs(ctx android.BaseContext) []string {
+	var srcs []string
+
+	if getMakeVars(ctx, "TWRP_CUSTOM_KEYBOARD") != "" {
+		srcs = append(srcs, getMakeVars(ctx, "TWRP_CUSTOM_KEYBOARD"))
+	}
+
+	return srcs
+}
+
+func libAospRecoveryDefaults(ctx android.LoadHookContext) {
+	type props struct {
+		Target struct {
+			Android struct {
+				Cflags  []string
+				Enabled *bool
+			}
+		}
+		Cflags       []string
+		Srcs         []string
+		Include_dirs []string
+	}
+
+	p := &props{}
+	p.Cflags = globalFlags(ctx)
+	s := globalSrcs(ctx)
+	p.Srcs = s
+	ctx.AppendProperties(p)
+}
+
+func init() {
+	android.RegisterModuleType("libaosprecovery_defaults", libAospRecoveryDefaultsFactory)
+}
+
+func libAospRecoveryDefaultsFactory() android.Module {
+	module := cc.DefaultsFactory()
+	android.AddLoadHook(module, libAospRecoveryDefaults)
+
+	return module
+}
diff --git a/libblkid/Android.mk b/libblkid/Android.mk
new file mode 100755
index 0000000..bc797a2
--- /dev/null
+++ b/libblkid/Android.mk
@@ -0,0 +1,199 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libutil-linux
+LOCAL_MODULE_TAGS := optional
+#LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+LOCAL_CFLAGS := -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS += -Wno-missing-field-initializers -Wno-sign-compare -Wno-unused-parameter -Wno-format -Wno-pointer-arith
+LOCAL_SRC_FILES = 	lib/at.c \
+			lib/blkdev.c \
+			lib/canonicalize.c \
+			lib/colors.c \
+			lib/crc32.c \
+			lib/crc64.c \
+			lib/env.c \
+			lib/exec_shell.c \
+			lib/fileutils.c \
+			lib/ismounted.c \
+			lib/langinfo.c \
+			lib/linux_version.c \
+			lib/loopdev.c \
+			lib/mangle.c \
+			lib/match.c \
+			lib/mbsalign.c \
+			lib/md5.c \
+			lib/pager.c \
+			lib/path.c \
+			lib/procutils.c \
+			lib/randutils.c \
+			lib/setproctitle.c \
+			lib/strutils.c \
+			lib/sysfs.c \
+
+LOCAL_C_INCLUDES += \
+			$(LOCAL_PATH)/include \
+		    $(LOCAL_PATH)/src
+
+LOCAL_SHARED_LIBRARIES += libc
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libuuid
+LOCAL_MODULE_TAGS := optional
+#LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+LOCAL_CFLAGS := -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC -DHAVE_SYS_FILE_H
+LOCAL_CFLAGS += -Wno-missing-field-initializers -Wno-sign-compare -Wno-unused-parameter -Wno-format -Wno-pointer-arith
+LOCAL_SRC_FILES =	libuuid/src/clear.c \
+			libuuid/src/copy.c \
+			libuuid/src/isnull.c \
+			libuuid/src/parse.c \
+			libuuid/src/unpack.c \
+			libuuid/src/uuid_time.c \
+			libuuid/src/compare.c \
+			libuuid/src/gen_uuid.c \
+			libuuid/src/pack.c \
+			libuuid/src/test_uuid.c \
+			libuuid/src/unparse.c
+
+LOCAL_C_INCLUDES += 	$(LOCAL_PATH)/libuuid/src \
+			$(LOCAL_PATH)/include \
+			$(LOCAL_PATH)/src
+
+LOCAL_SHARED_LIBRARIES += libc libutil-linux
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libfdisk
+LOCAL_MODULE_TAGS := optional
+#LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+LOCAL_CFLAGS := -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS += -Wno-missing-field-initializers -Wno-sign-compare -Wno-unused-parameter -Wno-format -Wno-pointer-arith
+LOCAL_SRC_FILES = 	libfdisk/src/alignment.c \
+			libfdisk/src/context.c  \
+			libfdisk/src/init.c   \
+			libfdisk/src/partition.c \
+			libfdisk/src/sgi.c \
+			libfdisk/src/test.c \
+			libfdisk/src/ask.c \
+			libfdisk/src/dos.c \
+			libfdisk/src/iter.c \
+			libfdisk/src/parttype.c \
+			libfdisk/src/sun.c \
+			libfdisk/src/utils.c \
+			libfdisk/src/bsd.c \
+			libfdisk/src/gpt.c \
+			libfdisk/src/label.c \
+			libfdisk/src/script.c \
+			libfdisk/src/table.c
+
+LOCAL_C_INCLUDES += 	$(LOCAL_PATH)/libfdisk/src \
+			$(LOCAL_PATH)/include \
+			$(LOCAL_PATH)/libuuid/src \
+			$(LOCAL_PATH)/src
+
+LOCAL_SHARED_LIBRARIES += libc libutil-linux libuuid
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libblkid
+LOCAL_MODULE_TAGS := optional
+#LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+LOCAL_CFLAGS := -D_FILE_OFFSET_BITS=64 -DHAVE_LOFF_T -DHAVE_ERR_H -DHAVE_MEMPCPY -DHAVE_FSYNC
+LOCAL_CFLAGS += -Wno-missing-field-initializers -Wno-sign-compare -Wno-unused-parameter -Wno-format -Wno-pointer-arith
+LOCAL_SRC_FILES = 	src/cache.c \
+			src/config.c \
+			src/dev.c \
+			src/devname.c \
+			src/devno.c \
+			src/encode.c \
+			src/evaluate.c \
+			src/getsize.c \
+			src/init.c \
+			src/llseek.c \
+			src/probe.c \
+			src/read.c \
+			src/resolve.c \
+			src/save.c \
+			src/tag.c \
+			src/verify.c \
+			src/version.c \
+			src/partitions/aix.c \
+			src/partitions/bsd.c \
+			src/partitions/dos.c \
+			src/partitions/gpt.c \
+			src/partitions/mac.c \
+			src/partitions/minix.c \
+			src/partitions/partitions.c \
+			src/partitions/sgi.c \
+			src/partitions/solaris_x86.c \
+			src/partitions/sun.c \
+			src/partitions/ultrix.c \
+			src/partitions/unixware.c \
+			src/superblocks/adaptec_raid.c  \
+			src/superblocks/bcache.c  \
+			src/superblocks/befs.c  \
+			src/superblocks/bfs.c \
+			src/superblocks/btrfs.c \
+			src/superblocks/cramfs.c \
+			src/superblocks/ddf_raid.c \
+			src/superblocks/drbd.c \
+			src/superblocks/drbdproxy_datalog.c \
+			src/superblocks/exfat.c \
+			src/superblocks/ext.c \
+			src/superblocks/erofs.c \
+			src/superblocks/f2fs.c \
+			src/superblocks/gfs.c \
+			src/superblocks/hfs.c \
+			src/superblocks/highpoint_raid.c \
+			src/superblocks/hpfs.c \
+			src/superblocks/iso9660.c \
+			src/superblocks/isw_raid.c \
+			src/superblocks/jfs.c \
+			src/superblocks/jmicron_raid.c \
+			src/superblocks/linux_raid.c \
+			src/superblocks/lsi_raid.c \
+			src/superblocks/luks.c \
+			src/superblocks/lvm.c \
+			src/superblocks/minix.c \
+			src/superblocks/netware.c \
+			src/superblocks/nilfs.c \
+			src/superblocks/ntfs.c \
+			src/superblocks/nvidia_raid.c \
+			src/superblocks/ocfs.c \
+			src/superblocks/promise_raid.c \
+			src/superblocks/refs.c \
+			src/superblocks/reiserfs.c \
+			src/superblocks/romfs.c \
+			src/superblocks/silicon_raid.c \
+			src/superblocks/squashfs.c \
+			src/superblocks/superblocks.c \
+			src/superblocks/swap.c \
+			src/superblocks/sysv.c \
+			src/superblocks/ubifs.c \
+			src/superblocks/udf.c \
+			src/superblocks/ufs.c \
+			src/superblocks/vfat.c \
+			src/superblocks/via_raid.c \
+			src/superblocks/vmfs.c \
+			src/superblocks/vxfs.c \
+			src/superblocks/xfs.c \
+			src/superblocks/zfs.c \
+			src/topology/dm.c \
+			src/topology/evms.c \
+			src/topology/ioctl.c \
+			src/topology/lvm.c \
+			src/topology/md.c \
+			src/topology/sysfs.c \
+			src/topology/topology.c \
+
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/include \
+			$(LOCAL_PATH)/src
+
+LOCAL_SHARED_LIBRARIES += libc libutil-linux
+include $(BUILD_SHARED_LIBRARY)
diff --git a/libblkid/COPYING b/libblkid/COPYING
new file mode 100644
index 0000000..be1a5b3
--- /dev/null
+++ b/libblkid/COPYING
@@ -0,0 +1,8 @@
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later
+version.
+
+The complete text of the license is available in the
+../Documentation/licenses/COPYING.LGPLv2.1 file.
diff --git a/libblkid/Makemodule.am b/libblkid/Makemodule.am
new file mode 100644
index 0000000..b4f6f9c
--- /dev/null
+++ b/libblkid/Makemodule.am
@@ -0,0 +1,16 @@
+if BUILD_LIBBLKID
+
+include libblkid/src/Makemodule.am
+include libblkid/samples/Makemodule.am
+
+if ENABLE_GTK_DOC
+# Docs uses separate Makefiles
+SUBDIRS += libblkid/docs
+endif
+
+pkgconfig_DATA += libblkid/blkid.pc
+PATHFILES      += libblkid/blkid.pc
+dist_man_MANS  += libblkid/libblkid.3
+EXTRA_DIST     += libblkid/libblkid.3 libblkid/COPYING
+
+endif # BUILD_LIBBLKID
diff --git a/libblkid/blkid.pc.in b/libblkid/blkid.pc.in
new file mode 100644
index 0000000..40ec8a9
--- /dev/null
+++ b/libblkid/blkid.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@usrlib_execdir@
+includedir=@includedir@
+
+Name: blkid
+Description: Block device id library
+Version: @LIBBLKID_VERSION@
+Requires.private: uuid
+Cflags: -I${includedir}/blkid
+Libs: -L${libdir} -lblkid
diff --git a/libblkid/docs/.gitignore b/libblkid/docs/.gitignore
new file mode 100644
index 0000000..f91f93d
--- /dev/null
+++ b/libblkid/docs/.gitignore
@@ -0,0 +1,18 @@
+*-decl-list.txt
+*-decl.txt
+*-overrides.txt
+*-undeclared.txt
+*-undocumented.txt
+*-unused.txt
+*.args
+*.bak
+*.hierarchy
+*.interfaces
+*.prerequisites
+*.signals
+*.stamp
+*.types
+html/*
+tmpl/*
+version.xml
+xml/*
diff --git a/libblkid/docs/Makefile.am b/libblkid/docs/Makefile.am
new file mode 100644
index 0000000..dc6dd3e
--- /dev/null
+++ b/libblkid/docs/Makefile.am
@@ -0,0 +1,95 @@
+## Process this file with automake to produce Makefile.in
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=libblkid
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=../src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=--deprecated-guards="BLKID_DISABLE_DEPRECATED"
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space blkid
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS=
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=$(top_builddir)/libblkid/src/blkid.h
+CFILE_GLOB=$(top_srcdir)/libblkid/src/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES=
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES=blkidP.h partitions.h superblocks.h \
+	      topology.h aix.h dos.h iso9660.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = $(builddir)/version.xml $(srcdir)/libblkid-config.xml
+
+# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS=
+GTKDOC_LIBS=
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/config/gtk-doc.make
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST += version.xml.in $(srcdir)/libblkid-config.xml
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+DISTCLEANFILES += version.xml
+
diff --git a/libblkid/docs/libblkid-config.xml b/libblkid/docs/libblkid-config.xml
new file mode 100644
index 0000000..89fbb7f
--- /dev/null
+++ b/libblkid/docs/libblkid-config.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
+[
+  <!ENTITY version SYSTEM "version.xml">
+]>
+<refentry id="libblkid-config">
+<refmeta>
+<refentrytitle role="top_of_page" id="libblkid-config.top_of_page">Config file</refentrytitle>
+<manvolnum>3</manvolnum>
+<refmiscinfo>LIBBLKID Library</refmiscinfo>
+</refmeta>
+
+<refnamediv>
+<refname>Config file</refname>
+<refpurpose>config file to control paths and basic library behavior</refpurpose>
+</refnamediv>
+
+<refsect1 id="libblkid-config.description" role="desc">
+<title role="desc.title">Description</title>
+<para>
+The standard location of the
+/etc/blkid.conf config file can be overridden by the environment variable
+BLKID_CONF.  The following options control the libblkid library:
+</para>
+
+<variablelist role="params">
+ <varlistentry>
+  <term>SEND_UEVENT=<parameter>yes|not</parameter></term>
+  <listitem><simpara>
+   Sends uevent when /dev/disk/by-{label,uuid}/
+   symlink does not match with LABEL or UUID on the device. Default is "yes".
+  </simpara></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>CACHE_FILE=<parameter>path</parameter></term>
+  <listitem><simpara>
+   Overrides the standard location of the cache file. This
+   setting can be overridden by the environment variable BLKID_FILE. Default is
+   /etc/blkid.tab.
+  </simpara></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>EVALUATE=<parameter>method</parameter></term>
+  <listitem><simpara>
+   Defines LABEL and UUID evaluation method(s). Currently,
+   the libblkid library supports "udev" and "scan" methods. More than one methods
+   may be specified in a comma separated list. Default is "udev,scan". The "udev"
+   method uses udev /dev/disk/by-* symlinks and the "scan" method scans all
+   block devices from the /proc/partitions file.
+  </simpara></listitem>
+ </varlistentry>
+</variablelist>
+
+</refsect1>
+
+</refentry>
diff --git a/libblkid/docs/libblkid-docs.xml b/libblkid/docs/libblkid-docs.xml
new file mode 100644
index 0000000..1f412c1
--- /dev/null
+++ b/libblkid/docs/libblkid-docs.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
+[
+  <!ENTITY version SYSTEM "version.xml">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+  <bookinfo>
+    <title>libblkid Reference Manual</title>
+    <releaseinfo>for libblkid version &version;</releaseinfo>
+    <copyright>
+      <year>2009-2013</year>
+      <holder>Karel Zak &lt;kzak@redhat.com&gt;</holder>
+    </copyright>
+  </bookinfo>
+
+  <part id="gtk">
+    <title>libblkid Overview</title>
+    <partintro>
+    <para>
+The libblkid library is used to identify block devices (disks) as to their
+content (e.g.  filesystem type, partitions) as well as extracting additional
+information such as filesystem labels/volume names, partitions, unique
+identifiers/serial numbers, etc.  A common use is to allow use of LABEL= and
+UUID= tags instead of hard-coding specific block device names into
+configuration files.
+    </para>
+    <para>
+The libblkid librray
+was written by Andreas Dilger for the ext2 filesystem utilties, with input
+from Ted Ts'o.  The library was subsequently heavily modified by Ted Ts'o.
+    </para>
+    <para>
+The low-level probing code, topology and partitions support was written
+by Karel Zak. Currently, the library is mainatned by Karel Zak.
+    </para>
+    <para>
+The library is part of the util-linux package since version 2.15 and is
+available from ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+    </para>
+  </partintro>
+  <xi:include href="xml/libblkid-config.xml"/>
+ </part>
+
+  <part>
+    <title>High-level</title>
+    <xi:include href="xml/evaluate.xml"/>
+    <xi:include href="xml/cache.xml"/>
+    <xi:include href="xml/search.xml"/>
+  </part>
+  <part>
+    <title>Low-level</title>
+    <xi:include href="xml/init.xml"/>
+    <xi:include href="xml/lowprobe.xml"/>
+    <xi:include href="xml/lowprobe-tags.xml"/>
+    <xi:include href="xml/superblocks.xml"/>
+    <xi:include href="xml/partitions.xml"/>
+    <xi:include href="xml/topology.xml"/>
+  </part>
+  <part>
+    <title>Common utils</title>
+    <xi:include href="xml/encode.xml"/>
+    <xi:include href="xml/misc.xml"/>
+  </part>
+
+  <index id="api-index-full">
+    <title>API Index</title>
+    <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
+  </index>
+</book>
diff --git a/libblkid/docs/libblkid-sections.txt b/libblkid/docs/libblkid-sections.txt
new file mode 100644
index 0000000..53cf84b
--- /dev/null
+++ b/libblkid/docs/libblkid-sections.txt
@@ -0,0 +1,201 @@
+<SECTION>
+<FILE>evaluate</FILE>
+blkid_evaluate_tag
+blkid_evaluate_spec
+</SECTION>
+
+<SECTION>
+<FILE>init</FILE>
+blkid_init_debug
+</SECTION>
+
+<SECTION>
+<FILE>cache</FILE>
+blkid_cache
+blkid_gc_cache
+blkid_get_cache
+blkid_put_cache
+blkid_probe_all
+blkid_probe_all_removable
+blkid_probe_all_new
+blkid_verify
+</SECTION>
+
+<SECTION>
+<FILE>search</FILE>
+blkid_dev
+blkid_dev_devname
+blkid_dev_has_tag
+blkid_dev_iterate
+blkid_dev_iterate_begin
+blkid_dev_iterate_end
+blkid_dev_next
+blkid_dev_set_search
+blkid_find_dev_with_tag
+blkid_get_dev
+blkid_get_devname
+blkid_get_tag_value
+blkid_tag_iterate
+blkid_tag_iterate_begin
+blkid_tag_iterate_end
+blkid_tag_next
+</SECTION>
+
+<SECTION>
+<FILE>lowprobe</FILE>
+blkid_probe
+blkid_free_probe
+blkid_new_probe
+blkid_new_probe_from_filename
+blkid_probe_get_devno
+blkid_probe_get_fd
+blkid_probe_get_offset
+blkid_probe_get_sectors
+blkid_probe_get_sectorsize
+blkid_probe_get_size
+blkid_probe_get_wholedisk_devno
+blkid_probe_is_wholedisk
+blkid_probe_set_device
+blkid_probe_step_back
+blkid_reset_probe
+</SECTION>
+
+<SECTION>
+<FILE>lowprobe-tags</FILE>
+blkid_do_fullprobe
+blkid_do_wipe
+blkid_do_probe
+blkid_do_safeprobe
+<SUBSECTION>
+blkid_probe_get_value
+blkid_probe_has_value
+blkid_probe_lookup_value
+blkid_probe_numof_values
+</SECTION>
+
+<SECTION>
+<FILE>partitions</FILE>
+blkid_partlist
+blkid_partition
+blkid_parttable
+blkid_probe_enable_partitions
+blkid_probe_set_partitions_flags
+blkid_probe_filter_partitions_type
+blkid_probe_invert_partitions_filter
+blkid_probe_reset_partitions_filter
+<SUBSECTION>
+blkid_known_pttype
+<SUBSECTION>
+blkid_partition_get_name
+blkid_partition_get_flags
+blkid_partition_get_partno
+blkid_partition_get_size
+blkid_partition_get_start
+blkid_partition_get_table
+blkid_partition_get_type
+blkid_partition_get_type_string
+blkid_partition_get_uuid
+blkid_partition_is_extended
+blkid_partition_is_logical
+blkid_partition_is_primary
+<SUBSECTION>
+blkid_partlist_get_partition
+blkid_partlist_get_partition_by_partno
+blkid_partlist_numof_partitions
+blkid_partlist_devno_to_partition
+blkid_partlist_get_table
+<SUBSECTION>
+blkid_parttable_get_id
+blkid_parttable_get_offset
+blkid_parttable_get_parent
+blkid_parttable_get_type
+<SUBSECTION>
+blkid_probe_get_partitions
+</SECTION>
+
+<SECTION>
+<FILE>superblocks</FILE>
+blkid_probe_enable_superblocks
+<SUBSECTION>
+blkid_known_fstype
+blkid_superblocks_get_name
+<SUBSECTION>
+blkid_probe_filter_superblocks_type
+blkid_probe_filter_superblocks_usage
+blkid_probe_invert_superblocks_filter
+blkid_probe_reset_superblocks_filter
+blkid_probe_set_superblocks_flags
+<SUBSECTION>
+blkid_probe_reset_filter
+blkid_probe_filter_types
+blkid_probe_filter_usage
+blkid_probe_invert_filter
+blkid_probe_set_request
+</SECTION>
+
+<SECTION>
+<FILE>topology</FILE>
+blkid_topology
+blkid_probe_enable_topology
+<SUBSECTION>
+blkid_probe_get_topology
+blkid_topology_get_alignment_offset
+blkid_topology_get_logical_sector_size
+blkid_topology_get_minimum_io_size
+blkid_topology_get_optimal_io_size
+blkid_topology_get_physical_sector_size
+</SECTION>
+
+<SECTION>
+<FILE>encode</FILE>
+blkid_encode_string
+blkid_safe_string
+</SECTION>
+
+<SECTION>
+<FILE>misc</FILE>
+blkid_loff_t
+blkid_devno_to_devname
+blkid_devno_to_wholedisk
+blkid_get_dev_size
+blkid_get_library_version
+blkid_parse_tag_string
+blkid_parse_version_string
+blkid_send_uevent
+BLKID_VERSION
+BLKID_DATE
+BLKID_FLTR_NOTIN
+BLKID_FLTR_ONLYIN
+BLKID_DEV_CREATE
+BLKID_DEV_FIND
+BLKID_DEV_NORMAL
+BLKID_DEV_VERIFY
+BLKID_PARTS_ENTRY_DETAILS
+BLKID_PARTS_FORCE_GPT
+BLKID_PARTS_MAGIC
+BLKID_PROBREQ_LABEL
+BLKID_PROBREQ_LABELRAW
+BLKID_PROBREQ_SECTYPE
+BLKID_PROBREQ_TYPE
+BLKID_PROBREQ_USAGE
+BLKID_PROBREQ_UUID
+BLKID_PROBREQ_UUIDRAW
+BLKID_PROBREQ_VERSION
+BLKID_SUBLKS_BADCSUM
+BLKID_SUBLKS_DEFAULT
+BLKID_SUBLKS_LABEL
+BLKID_SUBLKS_LABELRAW
+BLKID_SUBLKS_MAGIC
+BLKID_SUBLKS_SECTYPE
+BLKID_SUBLKS_TYPE
+BLKID_SUBLKS_USAGE
+BLKID_SUBLKS_UUID
+BLKID_SUBLKS_UUIDRAW
+BLKID_SUBLKS_VERSION
+BLKID_USAGE_CRYPTO
+BLKID_USAGE_FILESYSTEM
+BLKID_USAGE_OTHER
+BLKID_USAGE_RAID
+</SECTION>
+
+
diff --git a/libblkid/docs/version.xml.in b/libblkid/docs/version.xml.in
new file mode 100644
index 0000000..d78bda9
--- /dev/null
+++ b/libblkid/docs/version.xml.in
@@ -0,0 +1 @@
+@VERSION@
diff --git a/libblkid/include/Makemodule.am b/libblkid/include/Makemodule.am
new file mode 100644
index 0000000..c4a52e4
--- /dev/null
+++ b/libblkid/include/Makemodule.am
@@ -0,0 +1,57 @@
+
+dist_noinst_HEADERS += \
+	include/all-io.h \
+	include/at.h \
+	include/bitops.h \
+	include/blkdev.h \
+	include/monotonic.h \
+	include/c.h \
+	include/canonicalize.h \
+	include/carefulputc.h \
+	include/closestream.h \
+	include/colors.h \
+	include/cpuset.h \
+	include/crc32.h \
+	include/crc64.h \
+	include/debug.h \
+	include/env.h \
+	include/exec_shell.h \
+	include/exitcodes.h \
+	include/fileutils.h \
+	include/ismounted.h \
+	include/linux_reboot.h \
+	include/linux_version.h \
+	include/list.h \
+	include/loopdev.h \
+	include/mangle.h \
+	include/match.h \
+	include/mbsalign.h \
+	include/md5.h \
+	include/minix.h \
+	include/namespace.h \
+	include/nls.h \
+	include/optutils.h \
+	include/pager.h \
+	include/pamfail.h \
+	include/path.h \
+	include/pathnames.h \
+	include/procutils.h \
+	include/randutils.h \
+	include/readutmp.h \
+	include/rpmatch.h \
+	include/setproctitle.h \
+	include/strutils.h \
+	include/swapprober.h \
+	include/swapheader.h \
+	include/sysfs.h \
+	include/timer.h \
+	include/timeutils.h \
+	include/ttyutils.h \
+	include/widechar.h \
+	include/xalloc.h \
+	include/pt-sgi.h \
+	include/pt-bsd.h \
+	include/pt-mbr.h \
+	include/pt-mbr-partnames.h \
+	include/pt-sun.h \
+	include/statfs_magic.h
diff --git a/libblkid/include/all-io.h b/libblkid/include/all-io.h
new file mode 100644
index 0000000..9a4aeba
--- /dev/null
+++ b/libblkid/include/all-io.h
@@ -0,0 +1,84 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ *            Petr Uzel <petr.uzel@suse.cz>
+ */
+
+#ifndef UTIL_LINUX_ALL_IO_H
+#define UTIL_LINUX_ALL_IO_H
+
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "c.h"
+
+static inline int write_all(int fd, const void *buf, size_t count)
+{
+	while (count) {
+		ssize_t tmp;
+
+		errno = 0;
+		tmp = write(fd, buf, count);
+		if (tmp > 0) {
+			count -= tmp;
+			if (count)
+				buf = (void *) ((char *) buf + tmp);
+		} else if (errno != EINTR && errno != EAGAIN)
+			return -1;
+		if (errno == EAGAIN)	/* Try later, *sigh* */
+			usleep(250000);
+	}
+	return 0;
+}
+
+static inline int fwrite_all(const void *ptr, size_t size,
+			     size_t nmemb, FILE *stream)
+{
+	while (nmemb) {
+		size_t tmp;
+
+		errno = 0;
+		tmp = fwrite(ptr, size, nmemb, stream);
+		if (tmp > 0) {
+			nmemb -= tmp;
+			if (nmemb)
+				ptr = (void *) ((char *) ptr + (tmp * size));
+		} else if (errno != EINTR && errno != EAGAIN)
+			return -1;
+		if (errno == EAGAIN)	/* Try later, *sigh* */
+			usleep(250000);
+	}
+	return 0;
+}
+
+static inline ssize_t read_all(int fd, char *buf, size_t count)
+{
+	ssize_t ret;
+	ssize_t c = 0;
+	int tries = 0;
+
+	memset(buf, 0, count);
+	while (count > 0) {
+		ret = read(fd, buf, count);
+		if (ret <= 0) {
+			if ((errno == EAGAIN || errno == EINTR || ret == 0) &&
+			    (tries++ < 5)) {
+				usleep(250000);
+				continue;
+			}
+			return c ? c : -1;
+		}
+		if (ret > 0)
+			tries = 0;
+		count -= ret;
+		buf += ret;
+		c += ret;
+	}
+	return c;
+}
+
+
+#endif /* UTIL_LINUX_ALL_IO_H */
diff --git a/libblkid/include/at.h b/libblkid/include/at.h
new file mode 100644
index 0000000..63a80f0
--- /dev/null
+++ b/libblkid/include/at.h
@@ -0,0 +1,32 @@
+/*
+ * wrappers for "at" functions.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef UTIL_LINUX_AT_H
+#define UTIL_LINUX_AT_H
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "c.h"
+
+extern int fstat_at(int dir, const char *dirname,
+			const char *filename, struct stat *st, int nofollow);
+
+extern int open_at(int dir, const char *dirname,
+			const char *filename, int flags);
+
+extern FILE *fopen_at(int dir, const char *dirname, const char *filename,
+			int flags, const char *mode);
+
+extern ssize_t readlink_at(int dir, const char *dirname, const char *pathname,
+                    char *buf, size_t bufsiz);
+
+
+#endif /* UTIL_LINUX_AT_H */
diff --git a/libblkid/include/bitops.h b/libblkid/include/bitops.h
new file mode 100644
index 0000000..498ec63
--- /dev/null
+++ b/libblkid/include/bitops.h
@@ -0,0 +1,124 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#ifndef BITOPS_H
+#define BITOPS_H
+
+#include <stdint.h>
+#include <sys/param.h>
+
+#if defined(HAVE_BYTESWAP_H)
+# include <byteswap.h>
+#endif
+
+#if defined(HAVE_ENDIAN_H)
+#  include <endian.h>
+#elif defined(HAVE_SYS_ENDIAN_H)	/* BSDs have them here */
+#  include <sys/endian.h>
+#endif
+
+#if defined(__OpenBSD__)
+# include <sys/types.h>
+# define be16toh(x) betoh16(x)
+# define be32toh(x) betoh32(x)
+# define be64toh(x) betoh64(x)
+#endif
+
+/*
+ * Fallbacks
+ */
+#ifndef bswap_16
+# define bswap_16(x)   ((((x) & 0x00FF) << 8) | \
+			(((x) & 0xFF00) >> 8))
+#endif
+
+#ifndef bswap_32
+# define bswap_32(x)   ((((x) & 0x000000FF) << 24) | \
+			(((x) & 0x0000FF00) << 8)  | \
+			(((x) & 0x00FF0000) >> 8)  | \
+			(((x) & 0xFF000000) >> 24))
+#endif
+
+#ifndef bswap_64
+# define bswap_64(x) ((((x) & 0x00000000000000FFULL) << 56) | \
+                      (((x) & 0x000000000000FF00ULL) << 40) | \
+                      (((x) & 0x0000000000FF0000ULL) << 24) | \
+                      (((x) & 0x00000000FF000000ULL) << 8)  | \
+                      (((x) & 0x000000FF00000000ULL) >> 8)  | \
+                      (((x) & 0x0000FF0000000000ULL) >> 24) | \
+                      (((x) & 0x00FF000000000000ULL) >> 40) | \
+                      (((x) & 0xFF00000000000000ULL) >> 56))
+#endif
+
+#ifndef htobe16
+# if !defined(WORDS_BIGENDIAN)
+#  define htobe16(x) bswap_16 (x)
+#  define htole16(x) (x)
+#  define be16toh(x) bswap_16 (x)
+#  define le16toh(x) (x)
+#  define htobe32(x) bswap_32 (x)
+#  define htole32(x) (x)
+#  define be32toh(x) bswap_32 (x)
+#  define le32toh(x) (x)
+#  define htobe64(x) bswap_64 (x)
+#  define htole64(x) (x)
+#  define be64toh(x) bswap_64 (x)
+#  define le64toh(x) (x)
+# else
+#  define htobe16(x) (x)
+#  define htole16(x) bswap_16 (x)
+#  define be16toh(x) (x)
+#  define le16toh(x) bswap_16 (x)
+#  define htobe32(x) (x)
+#  define htole32(x) bswap_32 (x)
+#  define be32toh(x) (x)
+#  define le32toh(x) bswap_32 (x)
+#  define htobe64(x) (x)
+#  define htole64(x) bswap_64 (x)
+#  define be64toh(x) (x)
+#  define le64toh(x) bswap_64 (x)
+# endif
+#endif
+
+/*
+ * Byte swab macros (based on linux/byteorder/swab.h)
+ */
+#define swab16(x) bswap_16(x)
+#define swab32(x) bswap_32(x)
+#define swab64(x) bswap_64(x)
+
+#define cpu_to_le16(x) ((uint16_t) htole16(x))
+#define cpu_to_le32(x) ((uint32_t) htole32(x))
+#define cpu_to_le64(x) ((uint64_t) htole64(x))
+
+#define cpu_to_be16(x) ((uint16_t) htobe16(x))
+#define cpu_to_be32(x) ((uint32_t) htobe32(x))
+#define cpu_to_be64(x) ((uint64_t) htobe64(x))
+
+#define le16_to_cpu(x) ((uint16_t) le16toh(x))
+#define le32_to_cpu(x) ((uint32_t) le32toh(x))
+#define le64_to_cpu(x) ((uint64_t) le64toh(x))
+
+#define be16_to_cpu(x) ((uint16_t) be16toh(x))
+#define be32_to_cpu(x) ((uint32_t) be32toh(x))
+#define be64_to_cpu(x) ((uint64_t) be64toh(x))
+
+/*
+ * Bit map related macros. Usually provided by libc.
+ */
+#ifndef NBBY
+# define NBBY            CHAR_BIT
+#endif
+
+#ifndef setbit
+# define setbit(a,i)	((a)[(i)/NBBY] |= 1<<((i)%NBBY))
+# define clrbit(a,i)	((a)[(i)/NBBY] &= ~(1<<((i)%NBBY)))
+# define isset(a,i)	((a)[(i)/NBBY] & (1<<((i)%NBBY)))
+# define isclr(a,i)	(((a)[(i)/NBBY] & (1<<((i)%NBBY))) == 0)
+#endif
+
+#endif /* BITOPS_H */
+
diff --git a/libblkid/include/blkdev.h b/libblkid/include/blkdev.h
new file mode 100644
index 0000000..c994795
--- /dev/null
+++ b/libblkid/include/blkdev.h
@@ -0,0 +1,146 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#ifndef BLKDEV_H
+#define BLKDEV_H
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_IOCCOM_H
+# include <sys/ioccom.h> /* for _IO macro on e.g. Solaris */
+#endif
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef HAVE_SYS_MKDEV_H
+# include <sys/mkdev.h>		/* major and minor on Solaris */
+#endif
+
+#define DEFAULT_SECTOR_SIZE       512
+
+#ifdef __linux__
+/* very basic ioctls, should be available everywhere */
+# ifndef BLKROSET
+#  define BLKROSET   _IO(0x12,93)	/* set device read-only (0 = read-write) */
+#  define BLKROGET   _IO(0x12,94)	/* get read-only status (0 = read_write) */
+#  define BLKRRPART  _IO(0x12,95)	/* re-read partition table */
+#  define BLKGETSIZE _IO(0x12,96)	/* return device size /512 (long *arg) */
+#  define BLKFLSBUF  _IO(0x12,97)	/* flush buffer cache */
+#  define BLKRASET   _IO(0x12,98)	/* set read ahead for block device */
+#  define BLKRAGET   _IO(0x12,99)	/* get current read ahead setting */
+#  define BLKFRASET  _IO(0x12,100)	/* set filesystem (mm/filemap.c) read-ahead */
+#  define BLKFRAGET  _IO(0x12,101)	/* get filesystem (mm/filemap.c) read-ahead */
+#  define BLKSECTSET _IO(0x12,102)	/* set max sectors per request (ll_rw_blk.c) */
+#  define BLKSECTGET _IO(0x12,103)	/* get max sectors per request (ll_rw_blk.c) */
+#  define BLKSSZGET  _IO(0x12,104)	/* get block device sector size */
+
+/* ioctls introduced in 2.2.16, removed in 2.5.58 */
+#  define BLKELVGET  _IOR(0x12,106,size_t) /* elevator get */
+#  define BLKELVSET  _IOW(0x12,107,size_t) /* elevator set */
+
+#  define BLKBSZGET  _IOR(0x12,112,size_t)
+#  define BLKBSZSET  _IOW(0x12,113,size_t)
+# endif /* !BLKROSET */
+
+# ifndef BLKGETSIZE64
+#  define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size in bytes (u64 *arg) */
+# endif
+
+/* block device topology ioctls, introduced in 2.6.32 (commit ac481c20) */
+# ifndef BLKIOMIN
+#  define BLKIOMIN   _IO(0x12,120)
+#  define BLKIOOPT   _IO(0x12,121)
+#  define BLKALIGNOFF _IO(0x12,122)
+#  define BLKPBSZGET _IO(0x12,123)
+# endif
+
+/* discard zeroes support, introduced in 2.6.33 (commit 98262f27) */
+# ifndef BLKDISCARDZEROES
+#  define BLKDISCARDZEROES _IO(0x12,124)
+# endif
+
+/* filesystem freeze, introduced in 2.6.29 (commit fcccf502) */
+# ifndef FIFREEZE
+#  define FIFREEZE   _IOWR('X', 119, int)    /* Freeze */
+#  define FITHAW     _IOWR('X', 120, int)    /* Thaw */
+# endif
+
+/* uniform CD-ROM information */
+# ifndef CDROM_GET_CAPABILITY
+#  define CDROM_GET_CAPABILITY 0x5331
+# endif
+
+#endif /* __linux */
+
+
+#ifdef APPLE_DARWIN
+# define BLKGETSIZE DKIOCGETBLOCKCOUNT32
+#endif
+
+#ifndef HDIO_GETGEO
+# ifdef __linux__
+#  define HDIO_GETGEO 0x0301
+# endif
+
+struct hd_geometry {
+	unsigned char heads;
+	unsigned char sectors;
+	unsigned short cylinders;	/* truncated */
+	unsigned long start;
+};
+#endif /* HDIO_GETGEO */
+
+
+/* are we working with block device? */
+int is_blkdev(int fd);
+
+/* Determine size in bytes */
+off_t blkdev_find_size (int fd);
+
+/* get size in bytes */
+int blkdev_get_size(int fd, unsigned long long *bytes);
+
+/* get 512-byte sector count */
+int blkdev_get_sectors(int fd, unsigned long long *sectors);
+
+/* get hardware sector size */
+int blkdev_get_sector_size(int fd, int *sector_size);
+
+/* specifies whether or not the device is misaligned */
+int blkdev_is_misaligned(int fd);
+
+/* get physical block device size */
+int blkdev_get_physector_size(int fd, int *sector_size);
+
+/* is the device cdrom capable? */
+int blkdev_is_cdrom(int fd);
+
+/* get device's geometry - legacy */
+int blkdev_get_geometry(int fd, unsigned int *h, unsigned int *s);
+
+/* SCSI device types.  Copied almost as-is from kernel header.
+ * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/scsi/scsi.h */
+#define SCSI_TYPE_DISK			0x00
+#define SCSI_TYPE_TAPE			0x01
+#define SCSI_TYPE_PRINTER		0x02
+#define SCSI_TYPE_PROCESSOR		0x03	/* HP scanners use this */
+#define SCSI_TYPE_WORM			0x04	/* Treated as ROM by our system */
+#define SCSI_TYPE_ROM			0x05
+#define SCSI_TYPE_SCANNER		0x06
+#define SCSI_TYPE_MOD			0x07	/* Magneto-optical disk - treated as SCSI_TYPE_DISK */
+#define SCSI_TYPE_MEDIUM_CHANGER	0x08
+#define SCSI_TYPE_COMM			0x09	/* Communications device */
+#define SCSI_TYPE_RAID			0x0c
+#define SCSI_TYPE_ENCLOSURE		0x0d	/* Enclosure Services Device */
+#define SCSI_TYPE_RBC			0x0e
+#define SCSI_TYPE_OSD			0x11
+#define SCSI_TYPE_NO_LUN		0x7f
+
+/* convert scsi type code to name */
+const char *blkdev_scsi_type_to_name(int type);
+
+
+#endif /* BLKDEV_H */
diff --git a/libblkid/include/blkid.h b/libblkid/include/blkid.h
new file mode 100644
index 0000000..4d43130
--- /dev/null
+++ b/libblkid/include/blkid.h
@@ -0,0 +1,414 @@
+/*
+ * blkid.h - Interface for libblkid, a library to identify block devices
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _BLKID_BLKID_H
+#define _BLKID_BLKID_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LIBBLKID_VERSION   "2.25.0"
+#define LIBBLKID_DATE      "22-Jul-2014"
+
+/**
+ * blkid_dev:
+ *
+ * The device object keeps information about one device
+ */
+typedef struct blkid_struct_dev *blkid_dev;
+
+/**
+ * blkid_cache:
+ *
+ * information about all system devices
+ */
+typedef struct blkid_struct_cache *blkid_cache;
+
+/**
+ * blkid_probe:
+ *
+ * low-level probing setting
+ */
+typedef struct blkid_struct_probe *blkid_probe;
+
+/**
+ * blkid_topology:
+ *
+ * device topology information
+ */
+typedef struct blkid_struct_topology *blkid_topology;
+
+/**
+ * blkid_partlist
+ *
+ * list of all detected partitions and partitions tables
+ */
+typedef struct blkid_struct_partlist *blkid_partlist;
+
+/**
+ * blkid_partition:
+ *
+ * information about a partition
+ */
+typedef struct blkid_struct_partition *blkid_partition;
+
+/**
+ * blkid_parttable:
+ *
+ * information about a partition table
+ */
+typedef struct blkid_struct_parttable *blkid_parttable;
+
+/**
+ * blkid_loff_t:
+ *
+ * 64-bit signed number for offsets and sizes
+ */
+typedef int64_t blkid_loff_t;
+
+/**
+ * blkid_tag_iterate:
+ *
+ * tags iterator for high-level (blkid_cache) API
+ */
+typedef struct blkid_struct_tag_iterate *blkid_tag_iterate;
+
+/**
+ * blkid_dev_iterate:
+ *
+ * devices iterator for high-level (blkid_cache) API
+ */
+typedef struct blkid_struct_dev_iterate *blkid_dev_iterate;
+
+/*
+ * Flags for blkid_get_dev
+ *
+ * BLKID_DEV_CREATE	Create an empty device structure if not found
+ *			in the cache.
+ * BLKID_DEV_VERIFY	Make sure the device structure corresponds
+ *			with reality.
+ * BLKID_DEV_FIND	Just look up a device entry, and return NULL
+ *			if it is not found.
+ * BLKID_DEV_NORMAL	Get a valid device structure, either from the
+ *			cache or by probing the device.
+ */
+#define BLKID_DEV_FIND		0x0000
+#define BLKID_DEV_CREATE	0x0001
+#define BLKID_DEV_VERIFY	0x0002
+#define BLKID_DEV_NORMAL	(BLKID_DEV_CREATE | BLKID_DEV_VERIFY)
+
+
+#ifndef __GNUC_PREREQ
+# if defined __GNUC__ && defined __GNUC_MINOR__
+#  define __GNUC_PREREQ(maj, min)  ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+# else
+#  define __GNUC_PREREQ(maj, min) 0
+# endif
+#endif
+
+#ifndef __ul_attribute__
+# if __GNUC_PREREQ (3, 4)
+#  define __ul_attribute__(_a_) __attribute__(_a_)
+# else
+#  define __ul_attribute__(_a_)
+# endif
+#endif
+
+/* cache.c */
+extern void blkid_init_debug(int mask);
+extern void blkid_put_cache(blkid_cache cache);
+extern int blkid_get_cache(blkid_cache *cache, const char *filename);
+extern void blkid_gc_cache(blkid_cache cache);
+
+/* dev.c */
+extern const char *blkid_dev_devname(blkid_dev dev)
+			__ul_attribute__((warn_unused_result));
+
+extern blkid_dev_iterate blkid_dev_iterate_begin(blkid_cache cache);
+extern int blkid_dev_set_search(blkid_dev_iterate iter,
+				char *search_type, char *search_value);
+extern int blkid_dev_next(blkid_dev_iterate iterate, blkid_dev *dev);
+extern void blkid_dev_iterate_end(blkid_dev_iterate iterate);
+
+/* devno.c */
+extern char *blkid_devno_to_devname(dev_t devno)
+			__ul_attribute__((warn_unused_result));
+extern int blkid_devno_to_wholedisk(dev_t dev, char *diskname,
+                        size_t len, dev_t *diskdevno)
+			__ul_attribute__((warn_unused_result));
+
+/* devname.c */
+extern int blkid_probe_all(blkid_cache cache);
+extern int blkid_probe_all_new(blkid_cache cache);
+extern int blkid_probe_all_removable(blkid_cache cache);
+
+extern blkid_dev blkid_get_dev(blkid_cache cache, const char *devname, int flags);
+
+/* getsize.c */
+extern blkid_loff_t blkid_get_dev_size(int fd);
+
+/* verify.c */
+extern blkid_dev blkid_verify(blkid_cache cache, blkid_dev dev);
+
+/* read.c */
+
+/* resolve.c */
+extern char *blkid_get_tag_value(blkid_cache cache, const char *tagname,
+				       const char *devname)
+			__ul_attribute__((warn_unused_result));
+extern char *blkid_get_devname(blkid_cache cache, const char *token,
+			       const char *value)
+			__ul_attribute__((warn_unused_result));
+
+/* tag.c */
+extern blkid_tag_iterate blkid_tag_iterate_begin(blkid_dev dev);
+extern int blkid_tag_next(blkid_tag_iterate iterate,
+			      const char **type, const char **value);
+extern void blkid_tag_iterate_end(blkid_tag_iterate iterate);
+extern int blkid_dev_has_tag(blkid_dev dev, const char *type, const char *value);
+
+extern blkid_dev blkid_find_dev_with_tag(blkid_cache cache,
+					 const char *type,
+					 const char *value);
+
+extern int blkid_parse_tag_string(const char *token, char **ret_type, char **ret_val);
+
+/* version.c */
+extern int blkid_parse_version_string(const char *ver_string)
+			__ul_attribute__((nonnull));
+extern int blkid_get_library_version(const char **ver_string,
+				     const char **date_string);
+
+/* encode.c */
+extern int blkid_encode_string(const char *str, char *str_enc, size_t len);
+extern int blkid_safe_string(const char *str, char *str_safe, size_t len);
+
+/* evaluate.c */
+extern int blkid_send_uevent(const char *devname, const char *action);
+extern char *blkid_evaluate_tag(const char *token, const char *value,
+				blkid_cache *cache)
+			__ul_attribute__((warn_unused_result));
+extern char *blkid_evaluate_spec(const char *spec, blkid_cache *cache)
+			__ul_attribute__((warn_unused_result));
+
+/* probe.c */
+extern blkid_probe blkid_new_probe(void)
+			__ul_attribute__((warn_unused_result));
+extern blkid_probe blkid_new_probe_from_filename(const char *filename)
+			__ul_attribute__((warn_unused_result));
+extern void blkid_free_probe(blkid_probe pr);
+
+extern void blkid_reset_probe(blkid_probe pr);
+
+extern int blkid_probe_set_device(blkid_probe pr, int fd,
+	                blkid_loff_t off, blkid_loff_t size);
+
+extern dev_t blkid_probe_get_devno(blkid_probe pr)
+			__ul_attribute__((nonnull));
+
+extern dev_t blkid_probe_get_wholedisk_devno(blkid_probe pr)
+			__ul_attribute__((nonnull));
+
+extern int blkid_probe_is_wholedisk(blkid_probe pr)
+			__ul_attribute__((nonnull));
+
+extern blkid_loff_t blkid_probe_get_size(blkid_probe pr);
+extern blkid_loff_t blkid_probe_get_offset(blkid_probe pr);
+extern unsigned int blkid_probe_get_sectorsize(blkid_probe pr);
+extern blkid_loff_t blkid_probe_get_sectors(blkid_probe pr);
+
+extern int blkid_probe_get_fd(blkid_probe pr);
+
+/*
+ * superblocks probing
+ */
+extern int blkid_known_fstype(const char *fstype);
+
+extern int blkid_superblocks_get_name(size_t idx, const char **name, int *usage);
+
+extern int blkid_probe_enable_superblocks(blkid_probe pr, int enable);
+
+#define BLKID_SUBLKS_LABEL	(1 << 1) /* read LABEL from superblock */
+#define BLKID_SUBLKS_LABELRAW	(1 << 2) /* read and define LABEL_RAW result value*/
+#define BLKID_SUBLKS_UUID	(1 << 3) /* read UUID from superblock */
+#define BLKID_SUBLKS_UUIDRAW	(1 << 4) /* read and define UUID_RAW result value */
+#define BLKID_SUBLKS_TYPE	(1 << 5) /* define TYPE result value */
+#define BLKID_SUBLKS_SECTYPE	(1 << 6) /* define compatible fs type (second type) */
+#define BLKID_SUBLKS_USAGE	(1 << 7) /* define USAGE result value */
+#define BLKID_SUBLKS_VERSION	(1 << 8) /* read FS type from superblock */
+#define BLKID_SUBLKS_MAGIC	(1 << 9) /* define SBMAGIC and SBMAGIC_OFFSET */
+#define BLKID_SUBLKS_BADCSUM	(1 << 10) /* allow a bad checksum */
+
+#define BLKID_SUBLKS_DEFAULT	(BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | \
+				 BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE)
+
+extern int blkid_probe_set_superblocks_flags(blkid_probe pr, int flags);
+extern int blkid_probe_reset_superblocks_filter(blkid_probe pr);
+extern int blkid_probe_invert_superblocks_filter(blkid_probe pr);
+
+/**
+ * BLKID_FLTR_NOTIN
+ */
+#define BLKID_FLTR_NOTIN		1
+/**
+ * BLKID_FLTR_ONLYIN
+ */
+#define BLKID_FLTR_ONLYIN		2
+extern int blkid_probe_filter_superblocks_type(blkid_probe pr, int flag, char *names[]);
+
+#define BLKID_USAGE_FILESYSTEM		(1 << 1)
+#define BLKID_USAGE_RAID		(1 << 2)
+#define BLKID_USAGE_CRYPTO		(1 << 3)
+#define BLKID_USAGE_OTHER		(1 << 4)
+extern int blkid_probe_filter_superblocks_usage(blkid_probe pr, int flag, int usage);
+
+/*
+ * topology probing
+ */
+extern int blkid_probe_enable_topology(blkid_probe pr, int enable);
+
+/* binary interface */
+extern blkid_topology blkid_probe_get_topology(blkid_probe pr);
+
+extern unsigned long blkid_topology_get_alignment_offset(blkid_topology tp)
+			__ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_minimum_io_size(blkid_topology tp)
+			__ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_optimal_io_size(blkid_topology tp)
+			__ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_logical_sector_size(blkid_topology tp)
+			__ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_physical_sector_size(blkid_topology tp)
+			__ul_attribute__((nonnull));
+
+/*
+ * partitions probing
+ */
+extern int blkid_known_pttype(const char *pttype);
+
+extern int blkid_probe_enable_partitions(blkid_probe pr, int enable);
+
+extern int blkid_probe_reset_partitions_filter(blkid_probe pr);
+extern int blkid_probe_invert_partitions_filter(blkid_probe pr);
+extern int blkid_probe_filter_partitions_type(blkid_probe pr, int flag, char *names[]);
+
+/* partitions probing flags */
+#define BLKID_PARTS_FORCE_GPT		(1 << 1)
+#define BLKID_PARTS_ENTRY_DETAILS	(1 << 2)
+#define BLKID_PARTS_MAGIC		(1 << 3)
+extern int blkid_probe_set_partitions_flags(blkid_probe pr, int flags);
+
+/* binary interface */
+extern blkid_partlist blkid_probe_get_partitions(blkid_probe pr);
+
+extern int blkid_partlist_numof_partitions(blkid_partlist ls);
+extern blkid_parttable blkid_partlist_get_table(blkid_partlist ls);
+extern blkid_partition blkid_partlist_get_partition(blkid_partlist ls, int n);
+extern blkid_partition blkid_partlist_get_partition_by_partno(blkid_partlist ls, int n);
+extern blkid_partition blkid_partlist_devno_to_partition(blkid_partlist ls, dev_t devno);
+extern blkid_parttable blkid_partition_get_table(blkid_partition par);
+
+extern const char *blkid_partition_get_name(blkid_partition par);
+extern const char *blkid_partition_get_uuid(blkid_partition par);
+extern int blkid_partition_get_partno(blkid_partition par);
+extern blkid_loff_t blkid_partition_get_start(blkid_partition par);
+extern blkid_loff_t blkid_partition_get_size(blkid_partition par);
+
+extern int blkid_partition_get_type(blkid_partition par)
+			__ul_attribute__((nonnull));
+
+extern const char *blkid_partition_get_type_string(blkid_partition par);
+
+extern unsigned long long blkid_partition_get_flags(blkid_partition par)
+			__ul_attribute__((nonnull));
+
+extern int blkid_partition_is_logical(blkid_partition par)
+			__ul_attribute__((nonnull));
+extern int blkid_partition_is_extended(blkid_partition par)
+			__ul_attribute__((nonnull));
+extern int blkid_partition_is_primary(blkid_partition par)
+			__ul_attribute__((nonnull));
+
+extern const char *blkid_parttable_get_type(blkid_parttable tab);
+extern const char *blkid_parttable_get_id(blkid_parttable tab);
+
+extern blkid_loff_t blkid_parttable_get_offset(blkid_parttable tab);
+extern blkid_partition blkid_parttable_get_parent(blkid_parttable tab);
+
+/*
+ * NAME=value low-level interface
+ */
+extern int blkid_do_probe(blkid_probe pr);
+extern int blkid_do_safeprobe(blkid_probe pr);
+extern int blkid_do_fullprobe(blkid_probe pr);
+
+extern int blkid_probe_numof_values(blkid_probe pr);
+extern int blkid_probe_get_value(blkid_probe pr, int num, const char **name,
+                        const char **data, size_t *len);
+extern int blkid_probe_lookup_value(blkid_probe pr, const char *name,
+                        const char **data, size_t *len);
+extern int blkid_probe_has_value(blkid_probe pr, const char *name)
+			__ul_attribute__((nonnull));
+
+extern int blkid_do_wipe(blkid_probe pr, int dryrun);
+extern int blkid_probe_step_back(blkid_probe pr);
+
+/*
+ * Deprecated functions/macros
+ */
+#ifndef BLKID_DISABLE_DEPRECATED
+
+#define BLKID_PROBREQ_LABEL     BLKID_SUBLKS_LABEL
+#define BLKID_PROBREQ_LABELRAW  BLKID_SUBLKS_LABELRAW
+#define BLKID_PROBREQ_UUID      BLKID_SUBLKS_UUID
+#define BLKID_PROBREQ_UUIDRAW   BLKID_SUBLKS_UUIDRAW
+#define BLKID_PROBREQ_TYPE      BLKID_SUBLKS_TYPE
+#define BLKID_PROBREQ_SECTYPE   BLKID_SUBLKS_SECTYPE
+#define BLKID_PROBREQ_USAGE     BLKID_SUBLKS_USAGE
+#define BLKID_PROBREQ_VERSION   BLKID_SUBLKS_VERSION
+
+extern int blkid_probe_set_request(blkid_probe pr, int flags)
+			__ul_attribute__((deprecated));
+
+extern int blkid_probe_filter_usage(blkid_probe pr, int flag, int usage)
+			__ul_attribute__((deprecated));
+
+extern int blkid_probe_filter_types(blkid_probe pr, int flag, char *names[])
+			__ul_attribute__((deprecated));
+
+extern int blkid_probe_invert_filter(blkid_probe pr)
+			__ul_attribute__((deprecated));
+
+extern int blkid_probe_reset_filter(blkid_probe pr)
+			__ul_attribute__((deprecated));
+
+#endif /* BLKID_DISABLE_DEPRECATED */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _BLKID_BLKID_H */
diff --git a/libblkid/include/canonicalize.h b/libblkid/include/canonicalize.h
new file mode 100644
index 0000000..7a18aca
--- /dev/null
+++ b/libblkid/include/canonicalize.h
@@ -0,0 +1,21 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library Public License for more details.
+ */
+#ifndef CANONICALIZE_H
+#define CANONICALIZE_H
+
+#include "c.h"	/* for PATH_MAX */
+
+extern char *canonicalize_path(const char *path);
+extern char *canonicalize_path_restricted(const char *path);
+extern char *canonicalize_dm_name(const char *ptname);
+
+#endif /* CANONICALIZE_H */
diff --git a/libblkid/include/carefulputc.h b/libblkid/include/carefulputc.h
new file mode 100644
index 0000000..3a0ec5b
--- /dev/null
+++ b/libblkid/include/carefulputc.h
@@ -0,0 +1,67 @@
+#ifndef UTIL_LINUX_CAREFUULPUTC_H
+#define UTIL_LINUX_CAREFUULPUTC_H
+
+/*
+ * A putc() for use in write and wall (that sometimes are sgid tty).
+ * It avoids control characters in our locale, and also ASCII control
+ * characters.   Note that the locale of the recipient is unknown.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+static inline int fputc_careful(int c, FILE *fp, const char fail)
+{
+	int ret;
+
+	if (isprint(c) || c == '\a' || c == '\t' || c == '\r' || c == '\n')
+		ret = putc(c, fp);
+	else if (!isascii(c))
+		ret = fprintf(fp, "\\%3o", (unsigned char)c);
+	else {
+		ret = putc(fail, fp);
+		if (ret != EOF)
+			ret = putc(c ^ 0x40, fp);
+	}
+	return (ret < 0) ? EOF : 0;
+}
+
+static inline void fputs_quoted(const char *data, FILE *out)
+{
+	const char *p;
+
+	fputc('"', out);
+	for (p = data; p && *p; p++) {
+		if ((unsigned char) *p == 0x22 ||		/* " */
+		    (unsigned char) *p == 0x5c ||		/* \ */
+		    (unsigned char) *p == 0x60 ||		/* ` */
+		    (unsigned char) *p == 0x24 ||		/* $ */
+		    !isprint((unsigned char) *p) ||
+		    iscntrl((unsigned char) *p)) {
+
+			fprintf(out, "\\x%02x", (unsigned char) *p);
+		} else
+			fputc(*p, out);
+	}
+	fputc('"', out);
+}
+
+static inline void fputs_nonblank(const char *data, FILE *out)
+{
+	const char *p;
+
+	for (p = data; p && *p; p++) {
+		if (isblank((unsigned char) *p) ||
+		    (unsigned char) *p == 0x5c ||		/* \ */
+		    !isprint((unsigned char) *p) ||
+		    iscntrl((unsigned char) *p)) {
+
+			fprintf(out, "\\x%02x", (unsigned char) *p);
+
+		} else
+			fputc(*p, out);
+	}
+}
+
+
+#endif  /*  _CAREFUULPUTC_H  */
diff --git a/libblkid/include/closestream.h b/libblkid/include/closestream.h
new file mode 100644
index 0000000..7842456
--- /dev/null
+++ b/libblkid/include/closestream.h
@@ -0,0 +1,71 @@
+#ifndef UTIL_LINUX_CLOSESTREAM_H
+#define UTIL_LINUX_CLOSESTREAM_H
+
+#include <stdio.h>
+#ifdef HAVE_STDIO_EXT_H
+#include <stdio_ext.h>
+#endif
+#include <unistd.h>
+
+#include "c.h"
+#include "nls.h"
+
+#ifndef HAVE___FPENDING
+static inline int
+__fpending(FILE *stream __attribute__((__unused__)))
+{
+	return 0;
+}
+#endif
+
+static inline int
+close_stream(FILE * stream)
+{
+	const int some_pending = (__fpending(stream) != 0);
+	const int prev_fail = (ferror(stream) != 0);
+	const int fclose_fail = (fclose(stream) != 0);
+
+	if (prev_fail || (fclose_fail && (some_pending || errno != EBADF))) {
+		if (!fclose_fail && !(errno == EPIPE))
+			errno = 0;
+		return EOF;
+	}
+	return 0;
+}
+
+/* Meant to be used atexit(close_stdout); */
+static inline void
+close_stdout(void)
+{
+	if (close_stream(stdout) != 0 && !(errno == EPIPE)) {
+		if (errno)
+			warn(_("write error"));
+		else
+			warnx(_("write error"));
+		_exit(EXIT_FAILURE);
+	}
+
+	if (close_stream(stderr) != 0)
+		_exit(EXIT_FAILURE);
+}
+
+#ifndef HAVE_FSYNC
+static inline int
+fsync(int fd __attribute__((__unused__)))
+{
+	return 0;
+}
+#endif
+
+static inline int
+close_fd(int fd)
+{
+	const int fsync_fail = (fsync(fd) != 0);
+	const int close_fail = (close(fd) != 0);
+
+	if (fsync_fail || close_fail)
+		return EOF;
+	return 0;
+}
+
+#endif /* UTIL_LINUX_CLOSESTREAM_H */
diff --git a/libblkid/include/colors.h b/libblkid/include/colors.h
new file mode 100644
index 0000000..97efc48
--- /dev/null
+++ b/libblkid/include/colors.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef UTIL_LINUX_COLORS_H
+#define UTIL_LINUX_COLORS_H
+
+#include <stdio.h>
+#include <unistd.h>
+
+#define UL_COLOR_RESET		"\033[0m"
+#define UL_COLOR_BOLD		"\033[1m"
+#define UL_COLOR_HALFBRIGHT	"\033[2m"
+#define UL_COLOR_UNDERSCORE	"\033[4m"
+#define UL_COLOR_BLINK		"\033[5m"
+#define UL_COLOR_REVERSE	"\033[7m"
+
+/* Standard colors */
+#define UL_COLOR_BLACK		"\033[30m"
+#define UL_COLOR_RED		"\033[31m"
+#define UL_COLOR_GREEN		"\033[32m"
+#define UL_COLOR_BROWN		"\033[33m"	/* well, brown */
+#define UL_COLOR_BLUE		"\033[34m"
+#define UL_COLOR_MAGENTA	"\033[35m"
+#define UL_COLOR_CYAN		"\033[36m"
+#define UL_COLOR_GRAY		"\033[37m"
+
+/* Bold variants */
+#define UL_COLOR_DARK_GRAY	"\033[1;30m"
+#define UL_COLOR_BOLD_RED	"\033[1;31m"
+#define UL_COLOR_BOLD_GREEN	"\033[1;32m"
+#define UL_COLOR_BOLD_YELLOW	"\033[1;33m"
+#define UL_COLOR_BOLD_BLUE	"\033[1;34m"
+#define UL_COLOR_BOLD_MAGENTA	"\033[1;35m"
+#define UL_COLOR_BOLD_CYAN	"\033[1;36m"
+
+#define UL_COLOR_WHITE		"\033[1;37m"
+
+/* --color[=WHEN] */
+enum colortmode {
+	UL_COLORMODE_AUTO = 0,
+	UL_COLORMODE_NEVER,
+	UL_COLORMODE_ALWAYS,
+	UL_COLORMODE_UNDEF,
+
+	__UL_NCOLORMODES	/* last */
+};
+
+extern int colormode_from_string(const char *str);
+extern int colormode_or_err(const char *str, const char *errmsg);
+
+/* Initialize the global variable UL_COLOR_TERM_OK */
+extern int colors_init(int mode, const char *util_name);
+
+/* Returns 1 or 0 */
+extern int colors_wanted(void);
+
+/* temporary enable/disable colors */
+extern void colors_off(void);
+extern void colors_on(void);
+
+
+/* Set the color */
+extern void color_fenable(const char *seq, FILE *f);
+
+extern void color_scheme_fenable(const char *name, const char *dflt, FILE *f);
+extern const char *color_scheme_get_sequence(const char *name, const char *dflt);
+
+static inline void color_enable(const char *seq)
+{
+	color_fenable(seq, stdout);
+}
+
+static inline void color_scheme_enable(const char *name, const char *dflt)
+{
+	color_scheme_fenable(name, dflt, stdout);
+}
+
+/* Reset colors to default */
+extern void color_fdisable(FILE *f);
+
+static inline void color_disable(void)
+{
+	color_fdisable(stdout);
+}
+
+/* converts "red" to UL_COLOR_RED, etc. */
+extern const char *color_sequence_from_colorname(const char *str);
+
+
+#endif /* UTIL_LINUX_COLORS_H */
diff --git a/libblkid/include/config.h b/libblkid/include/config.h
new file mode 100644
index 0000000..0a71fcf
--- /dev/null
+++ b/libblkid/include/config.h
@@ -0,0 +1,687 @@
+/* config.h.  Generated from config.h.in by configure.  */
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define if building universal (internal helper macro) */
+/* #undef AC_APPLE_UNIVERSAL_BUILD */
+
+/* Enable agetty --reload feature */
+#define AGETTY_RELOAD 1
+
+/* Should chfn and chsh require the user to enter the password? */
+#define CHFN_CHSH_PASSWORD 1
+
+/* Path to hwclock adjtime file */
+#define CONFIG_ADJTIME_PATH "/etc/adjtime"
+
+/* Define to 1 if translation of program messages to the user's native
+   language is requested. */
+#define ENABLE_NLS 1
+
+/* search path for fs helpers */
+#define FS_SEARCH_PATH "/sbin:/sbin/fs.d:/sbin/fs"
+
+/* Define to 1 if you have the <asm/io.h> header file. */
+/* #undef HAVE_ASM_IO_H */
+
+/* Define to 1 if you have the <byteswap.h> header file. */
+#define HAVE_BYTESWAP_H 1
+
+/* Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in the
+   CoreFoundation framework. */
+/* #undef HAVE_CFLOCALECOPYCURRENT */
+
+/* Define to 1 if you have the Mac OS X function CFPreferencesCopyAppValue in
+   the CoreFoundation framework. */
+/* #undef HAVE_CFPREFERENCESCOPYAPPVALUE */
+
+/* Define to 1 if you have the `clock_gettime' function. */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 if the system has the type `cpu_set_t'. */
+#define HAVE_CPU_SET_T 1
+
+/* Define to 1 if you have the <crypt.h> header file. */
+#define HAVE_CRYPT_H 1
+
+/* Define if the GNU dcgettext() function is already present or preinstalled.
+   */
+#define HAVE_DCGETTEXT 1
+
+/* Define to 1 if you have the declaration of `CPU_ALLOC', and to 0 if you
+   don't. */
+#define HAVE_DECL_CPU_ALLOC 1
+
+/* Define to 1 if you have the declaration of `dirfd', and to 0 if you don't.
+   */
+/* #undef HAVE_DECL_DIRFD */
+
+/* Define to 1 if you have the declaration of `_NL_TIME_WEEK_1STDAY', and to 0
+   if you don't. */
+#define HAVE_DECL__NL_TIME_WEEK_1STDAY 1
+
+/* Define to 1 if you have the `dirfd' function. */
+#define HAVE_DIRFD 1
+
+/* Define to 1 if `dd_fd' is a member of `DIR'. */
+/* #undef HAVE_DIR_DD_FD */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <endian.h> header file. */
+#define HAVE_ENDIAN_H 1
+
+/* Define to 1 if have **environ prototype */
+#define HAVE_ENVIRON_DECL 1
+
+/* Define to 1 if you have the `err' function. */
+#define HAVE_ERR 1
+
+/* Define to 1 if you have the <errno.h> header file. */
+#define HAVE_ERRNO_H 1
+
+/* Define to 1 if you have the `errx' function. */
+#define HAVE_ERRX 1
+
+/* Define to 1 if you have the <err.h> header file. */
+#define HAVE_ERR_H 1
+
+/* Have valid fallocate() function */
+#define HAVE_FALLOCATE 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */
+#define HAVE_FSEEKO 1
+
+/* Define to 1 if you have the `fstatat' function. */
+#define HAVE_FSTATAT 1
+
+/* Define to 1 if you have the `fsync' function. */
+#define HAVE_FSYNC 1
+
+/* Define to 1 if you have the `futimens' function. */
+#define HAVE_FUTIMENS 1
+
+/* Define to 1 if you have the `getdomainname' function. */
+#define HAVE_GETDOMAINNAME 1
+
+/* Define to 1 if you have the `getdtablesize' function. */
+#define HAVE_GETDTABLESIZE 1
+
+/* Define to 1 if you have the `getexecname' function. */
+/* #undef HAVE_GETEXECNAME */
+
+/* Define to 1 if you have the `getmntinfo' function. */
+/* #undef HAVE_GETMNTINFO */
+
+/* Define to 1 if you have the <getopt.h> header file. */
+#define HAVE_GETOPT_H 1
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define to 1 if you have the `getsgnam' function. */
+#define HAVE_GETSGNAM 1
+
+/* Define if the GNU gettext() function is already present or preinstalled. */
+#define HAVE_GETTEXT 1
+
+/* Define if you have the iconv() function and it works. */
+/* #undef HAVE_ICONV */
+
+/* Define to 1 if you have the `inotify_init' function. */
+#define HAVE_INOTIFY_INIT 1
+
+/* Define to 1 if you have the `inotify_init1' function. */
+#define HAVE_INOTIFY_INIT1 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `ioperm' function. */
+#define HAVE_IOPERM 1
+
+/* Define to 1 if you have the `iopl' function. */
+#define HAVE_IOPL 1
+
+/* Define to 1 if you have the `jrand48' function. */
+#define HAVE_JRAND48 1
+
+/* Define to 1 if you have the <langinfo.h> header file. */
+#define HAVE_LANGINFO_H 1
+
+/* Define to 1 if you have the `lchown' function. */
+#define HAVE_LCHOWN 1
+
+/* Define to 1 if you have the `audit' library (-laudit). */
+/* #undef HAVE_LIBAUDIT */
+
+/* Define to 1 if you have the -lblkid. */
+#define HAVE_LIBBLKID 1
+
+/* Define to 1 if you have the `cap-ng' library (-lcap-ng). */
+/* #undef HAVE_LIBCAP_NG */
+
+/* Do we need -lcrypt? */
+#define HAVE_LIBCRYPT 1
+
+/* Define to 1 if you have the `ncurses' library (-lncurses). */
+#define HAVE_LIBNCURSES 1
+
+/* Define to 1 if you have the `ncursesw' library (-lncursesw). */
+/* #undef HAVE_LIBNCURSESW */
+
+/* Define if SELinux is available */
+/* #undef HAVE_LIBSELINUX */
+
+/* Define if libsystemd is available */
+/* #undef HAVE_LIBSYSTEMD */
+
+/* Define to 1 if you have the `termcap' library (-ltermcap). */
+#define HAVE_LIBTERMCAP 1
+
+/* Define to 1 if you have the `udev' library (-ludev). */
+/* #undef HAVE_LIBUDEV */
+
+/* Define if libuser is available */
+/* #undef HAVE_LIBUSER */
+
+/* Define to 1 if you have the `utempter' library (-lutempter). */
+/* #undef HAVE_LIBUTEMPTER */
+
+/* Define to 1 if you have the `util' library (-lutil). */
+#define HAVE_LIBUTIL 1
+
+/* Define to 1 if you have the -luuid. */
+#define HAVE_LIBUUID 1
+
+/* Define to 1 if you have the <linux/blkpg.h> header file. */
+#define HAVE_LINUX_BLKPG_H 1
+
+/* Define to 1 if you have the <linux/cdrom.h> header file. */
+#define HAVE_LINUX_CDROM_H 1
+
+/* Define to 1 if you have the <linux/compiler.h> header file. */
+/* #undef HAVE_LINUX_COMPILER_H */
+
+/* Define to 1 if you have the <linux/falloc.h> header file. */
+#define HAVE_LINUX_FALLOC_H 1
+
+/* Define to 1 if you have the <linux/fd.h> header file. */
+#define HAVE_LINUX_FD_H 1
+
+/* Define to 1 if you have the <linux/gsmmux.h> header file. */
+/* #undef HAVE_LINUX_GSMMUX_H */
+
+/* Define to 1 if you have the <linux/major.h> header file. */
+#define HAVE_LINUX_MAJOR_H 1
+
+/* Define to 1 if you have the <linux/raw.h> header file. */
+#define HAVE_LINUX_RAW_H 1
+
+/* Define to 1 if you have the <linux/securebits.h> header file. */
+#define HAVE_LINUX_SECUREBITS_H 1
+
+/* Define to 1 if you have the <linux/tiocl.h> header file. */
+#define HAVE_LINUX_TIOCL_H 1
+
+/* Define to 1 if you have the <linux/version.h> header file. */
+#define HAVE_LINUX_VERSION_H 1
+
+/* Define to 1 if you have the <linux/watchdog.h> header file. */
+#define HAVE_LINUX_WATCHDOG_H 1
+
+/* Define to 1 if you have the `llseek' function. */
+#define HAVE_LLSEEK 1
+
+/* Define to 1 if have llseek prototype */
+/* #undef HAVE_LLSEEK_PROTOTYPE */
+
+/* Define to 1 if you have the <locale.h> header file. */
+#define HAVE_LOCALE_H 1
+
+/* Define to 1 if the system has the type `loff_t'. */
+#define HAVE_LOFF_T 1
+
+/* Define to 1 if you have the `lseek64' function. */
+#define HAVE_LSEEK64 1
+
+/* Define to 1 if have lseek64 prototype */
+#define HAVE_LSEEK64_PROTOTYPE 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mempcpy' function. */
+#define HAVE_MEMPCPY 1
+
+/* Define to 1 if you have the <mntent.h> header file. */
+#define HAVE_MNTENT_H 1
+
+/* Define to 1 if you have the `nanosleep' function. */
+#define HAVE_NANOSLEEP 1
+
+/* Define to 1 if you have the <ncursesw/ncurses.h> header file. */
+/* #undef HAVE_NCURSESW_NCURSES_H */
+
+/* Define to 1 if you have the <ncurses.h> header file. */
+#define HAVE_NCURSES_H 1
+
+/* Define to 1 if you have the <ncurses/ncurses.h> header file. */
+/* #undef HAVE_NCURSES_NCURSES_H */
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <net/if_dl.h> header file. */
+/* #undef HAVE_NET_IF_DL_H */
+
+/* Define to 1 if you have the <net/if.h> header file. */
+#define HAVE_NET_IF_H 1
+
+/* Define to 1 if you have the `openat' function. */
+#define HAVE_OPENAT 1
+
+/* Define to 1 if you have the `open_memstream' function. */
+#define HAVE_OPEN_MEMSTREAM 1
+
+/* Define to 1 if you have the <paths.h> header file. */
+#define HAVE_PATHS_H 1
+
+/* Define to 1 if you have the `personality' function. */
+#define HAVE_PERSONALITY 1
+
+/* Define to 1 if you have the `posix_fadvise' function. */
+#define HAVE_POSIX_FADVISE 1
+
+/* Define to 1 if you have the `prctl' function. */
+#define HAVE_PRCTL 1
+
+/* Define to 1 if you have the `prlimit' function. */
+#define HAVE_PRLIMIT 1
+
+/* Define if program_invocation_short_name is defined */
+#define HAVE_PROGRAM_INVOCATION_SHORT_NAME 1
+
+/* Define to 1 if you have the <pty.h> header file. */
+#define HAVE_PTY_H 1
+
+/* Define to 1 if you have the `qsort_r' function. */
+#define HAVE_QSORT_R 1
+
+/* Define if curses library has the resizeterm(). */
+/* #undef HAVE_RESIZETERM */
+
+/* Define to 1 if you have the `rpmatch' function. */
+#define HAVE_RPMATCH 1
+
+/* Define if struct sockaddr contains sa_len */
+/* #undef HAVE_SA_LEN */
+
+/* Define to 1 if you have the `scandirat' function. */
+#define HAVE_SCANDIRAT 1
+
+/* scanf %as modifier */
+/* #undef HAVE_SCANF_AS_MODIFIER */
+
+/* scanf %ms modifier */
+#define HAVE_SCANF_MS_MODIFIER 1
+
+/* Define to 1 if you have the `secure_getenv' function. */
+#define HAVE_SECURE_GETENV 1
+
+/* Define to 1 if you have the `security_get_initial_context' function. */
+/* #undef HAVE_SECURITY_GET_INITIAL_CONTEXT */
+
+/* Define to 1 if you have the <security/openpam.h> header file. */
+/* #undef HAVE_SECURITY_OPENPAM_H */
+
+/* Define to 1 if you have the <security/pam_appl.h> header file. */
+/* #undef HAVE_SECURITY_PAM_APPL_H */
+
+/* Define to 1 if you have the <security/pam_misc.h> header file. */
+/* #undef HAVE_SECURITY_PAM_MISC_H */
+
+/* Define to 1 if you have the `setns' function. */
+#define HAVE_SETNS 1
+
+/* Define to 1 if you have the `setresgid' function. */
+#define HAVE_SETRESGID 1
+
+/* Define to 1 if you have the `setresuid' function. */
+#define HAVE_SETRESUID 1
+
+/* Define to 1 if the system has the type `sighandler_t'. */
+#define HAVE_SIGHANDLER_T 1
+
+/* Define to 1 if you have the `sigqueue' function. */
+#define HAVE_SIGQUEUE 1
+
+/* Define to 1 if you have the <slang.h> header file. */
+/* #undef HAVE_SLANG_H */
+
+/* Define to 1 if you have the <slang/slang.h> header file. */
+/* #undef HAVE_SLANG_SLANG_H */
+
+/* Define to 1 if you have the <slang/slcurses.h> header file. */
+/* #undef HAVE_SLANG_SLCURSES_H */
+
+/* Define to 1 if you have the <slcurses.h> header file. */
+/* #undef HAVE_SLCURSES_H */
+
+/* Add SMACK support */
+/* #undef HAVE_SMACK */
+
+/* Define to 1 if you have the `srandom' function. */
+#define HAVE_SRANDOM 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdio_ext.h> header file. */
+#define HAVE_STDIO_EXT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strnchr' function. */
+/* #undef HAVE_STRNCHR */
+
+/* Define to 1 if you have the `strndup' function. */
+#define HAVE_STRNDUP 1
+
+/* Define to 1 if you have the `strnlen' function. */
+#define HAVE_STRNLEN 1
+
+/* Define to 1 if have strsignal function prototype */
+#define HAVE_STRSIGNAL_DECL 1
+
+/* Define to 1 if you have the `strtoull' function. */
+#define HAVE_STRTOULL 1
+
+/* Define to 1 if `st_mtim.tv_nsec' is a member of `struct stat'. */
+#define HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 1
+
+/* Define to 1 if `c_line' is a member of `struct termios'. */
+#define HAVE_STRUCT_TERMIOS_C_LINE 1
+
+/* Define to 1 if you have the `sysconf' function. */
+#define HAVE_SYSCONF 1
+
+/* Define to 1 if you have the `sysinfo' function. */
+#define HAVE_SYSINFO 1
+
+/* Define to 1 if you have the <sys/disklabel.h> header file. */
+/* #undef HAVE_SYS_DISKLABEL_H */
+
+/* Define to 1 if you have the <sys/disk.h> header file. */
+/* #undef HAVE_SYS_DISK_H */
+
+/* Define to 1 if you have the <sys/endian.h> header file. */
+/* #undef HAVE_SYS_ENDIAN_H */
+
+/* Define to 1 if you have the <sys/file.h> header file. */
+#define HAVE_SYS_FILE_H 1
+
+/* Define to 1 if you have the <sys/ioccom.h> header file. */
+/* #undef HAVE_SYS_IOCCOM_H */
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/io.h> header file. */
+#define HAVE_SYS_IO_H 1
+
+/* Define to 1 if you have the <sys/mkdev.h> header file. */
+/* #undef HAVE_SYS_MKDEV_H */
+
+/* Define to 1 if you have the <sys/prctl.h> header file. */
+#define HAVE_SYS_PRCTL_H 1
+
+/* Define to 1 if you have the <sys/queue.h> header file. */
+#define HAVE_SYS_QUEUE_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/sockio.h> header file. */
+/* #undef HAVE_SYS_SOCKIO_H */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/swap.h> header file. */
+#define HAVE_SYS_SWAP_H 1
+
+/* Define to 1 if you have the <sys/syscall.h> header file. */
+#define HAVE_SYS_SYSCALL_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/ttydefaults.h> header file. */
+#define HAVE_SYS_TTYDEFAULTS_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/un.h> header file. */
+#define HAVE_SYS_UN_H 1
+
+/* Define to 1 if the target supports thread-local storage. */
+#define HAVE_TLS 1
+
+/* Does struct tm have a field tm_gmtoff? */
+#define HAVE_TM_GMTOFF 1
+
+/* Define to 1 if the system has the type `union semun'. */
+/* #undef HAVE_UNION_SEMUN */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `unlinkat' function. */
+#define HAVE_UNLINKAT 1
+
+/* Define to 1 if you have the `unshare' function. */
+#define HAVE_UNSHARE 1
+
+/* Define to 1 if you have the `updwtmp' function. */
+#define HAVE_UPDWTMP 1
+
+/* Define if curses library has the use_default_colors(). */
+/* #undef HAVE_USE_DEFAULT_COLORS */
+
+/* Define to 1 if you have the `usleep' function. */
+#define HAVE_USLEEP 1
+
+/* Define to 1 if you have the `utimensat' function. */
+#define HAVE_UTIMENSAT 1
+
+/* Define to 1 if you want to use uuid daemon. */
+#define HAVE_UUIDD 1
+
+/* Define to 1 if you have the `warn' function. */
+#define HAVE_WARN 1
+
+/* Define to 1 if you have the `warnx' function. */
+#define HAVE_WARNX 1
+
+/* Do we have wide character support? */
+#define HAVE_WIDECHAR 1
+
+/* Define to 1 if you have the `__fpending' function. */
+#define HAVE___FPENDING 1
+
+/* Define if __progname is defined */
+#define HAVE___PROGNAME 1
+
+/* Define to 1 if you have the `__secure_getenv' function. */
+/* #undef HAVE___SECURE_GETENV */
+
+/* libblkid date string */
+#define LIBBLKID_DATE "22-Jul-2014"
+
+/* libblkid version string */
+#define LIBBLKID_VERSION "2.25.0"
+
+/* libfdisk version string */
+#define LIBFDISK_VERSION "2.25.0"
+
+/* libmount version string */
+#define LIBMOUNT_VERSION "2.25.0"
+
+/* libsmartcols version string */
+#define LIBSMARTCOLS_VERSION "2.25.0"
+
+/* Should login chown /dev/vcsN? */
+/* #undef LOGIN_CHOWN_VCS */
+
+/* Should login stat() the mailbox? */
+/* #undef LOGIN_STAT_MAIL */
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+   */
+#define LT_OBJDIR ".libs/"
+
+/* Should chsh allow only shells in /etc/shells? */
+#define ONLY_LISTED_SHELLS 1
+
+/* Name of package */
+#define PACKAGE "util-linux"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "kzak@redhat.com"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "util-linux"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "util-linux 2.25.590-bf6c"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "util-linux"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "http://www.kernel.org/pub/linux/utils/util-linux/"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "2.25.590-bf6c"
+
+/* Should pg ring the bell on invalid keys? */
+#define PG_BELL 1
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Is swapon() declared with two parameters? */
+#define SWAPON_HAS_TWO_ARGS 1
+
+/* Fallback syscall number for fallocate */
+/* #undef SYS_fallocate */
+
+/* Fallback syscall number for ioprio_get */
+/* #undef SYS_ioprio_get */
+
+/* Fallback syscall number for ioprio_set */
+/* #undef SYS_ioprio_set */
+
+/* Fallback syscall number for pivot_root */
+/* #undef SYS_pivot_root */
+
+/* Fallback syscall number for prlimit64 */
+/* #undef SYS_prlimit64 */
+
+/* Fallback syscall number for sched_getaffinity */
+/* #undef SYS_sched_getaffinity */
+
+/* Fallback syscall number for setns */
+/* #undef SYS_setns */
+
+/* Fallback syscall number for unshare */
+/* #undef SYS_unshare */
+
+/* Should sulogin use a emergency mount of /dev and /proc? */
+/* #undef USE_SULOGIN_EMERGENCY_MOUNT */
+
+/* Enable extensions on AIX 3, Interix.  */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them.  */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris.  */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop.  */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris.  */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* Should wall and write be installed setgid tty? */
+#define USE_TTY_GROUP 1
+
+/* Version number of package */
+#define VERSION "2.25.590-bf6c"
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+   significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+#  define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+/* #  undef WORDS_BIGENDIAN */
+# endif
+#endif
+
+/* Enable large inode numbers on Mac OS X 10.5.  */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
+/* #undef _LARGEFILE_SOURCE */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+   this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to empty if the keyword `volatile' does not work. Warning: valid
+   code using `volatile' can become incorrect without. Disable with care. */
+/* #undef volatile */
diff --git a/libblkid/include/cpuset.h b/libblkid/include/cpuset.h
new file mode 100644
index 0000000..f8948a9
--- /dev/null
+++ b/libblkid/include/cpuset.h
@@ -0,0 +1,99 @@
+/*
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef UTIL_LINUX_CPUSET_H
+#define UTIL_LINUX_CPUSET_H
+
+#include <sched.h>
+
+/*
+ * Fallback for old or obscure libcs without dynamically allocated cpusets
+ *
+ * The following macros are based on code from glibc.
+ *
+ * The GNU C Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+#if !HAVE_DECL_CPU_ALLOC
+
+# define CPU_ZERO_S(setsize, cpusetp) \
+  do {									      \
+    size_t __i;								      \
+    size_t __imax = (setsize) / sizeof (__cpu_mask);			      \
+    __cpu_mask *__bits = (cpusetp)->__bits;				      \
+    for (__i = 0; __i < __imax; ++__i)					      \
+      __bits[__i] = 0;							      \
+  } while (0)
+
+# define CPU_SET_S(cpu, setsize, cpusetp) \
+   ({ size_t __cpu = (cpu);						      \
+      __cpu < 8 * (setsize)						      \
+      ? (((__cpu_mask *) ((cpusetp)->__bits))[__CPUELT (__cpu)]		      \
+	 |= __CPUMASK (__cpu))						      \
+      : 0; })
+
+# define CPU_ISSET_S(cpu, setsize, cpusetp) \
+   ({ size_t __cpu = (cpu);						      \
+      __cpu < 8 * (setsize)						      \
+      ? ((((__cpu_mask *) ((cpusetp)->__bits))[__CPUELT (__cpu)]	      \
+	  & __CPUMASK (__cpu))) != 0					      \
+      : 0; })
+
+# define CPU_EQUAL_S(setsize, cpusetp1, cpusetp2) \
+   ({ __cpu_mask *__arr1 = (cpusetp1)->__bits;				      \
+      __cpu_mask *__arr2 = (cpusetp2)->__bits;				      \
+      size_t __imax = (setsize) / sizeof (__cpu_mask);			      \
+      size_t __i;							      \
+      for (__i = 0; __i < __imax; ++__i)				      \
+	if (__arr1[__i] != __arr2[__i])					      \
+	  break;							      \
+      __i == __imax; })
+
+extern int __cpuset_count_s(size_t setsize, const cpu_set_t *set);
+# define CPU_COUNT_S(setsize, cpusetp)	__cpuset_count_s(setsize, cpusetp)
+
+# define CPU_ALLOC_SIZE(count) \
+	  ((((count) + __NCPUBITS - 1) / __NCPUBITS) * sizeof (__cpu_mask))
+# define CPU_ALLOC(count)	(malloc(CPU_ALLOC_SIZE(count)))
+# define CPU_FREE(cpuset)	(free(cpuset))
+
+#endif /* !HAVE_DECL_CPU_ALLOC */
+
+
+#define cpuset_nbits(setsize)	(8 * (setsize))
+
+/*
+ * The @idx parametr returns an index of the first mask from @ary array where
+ * the @cpu is set.
+ *
+ * Returns: 0 if found, otherwise 1.
+ */
+static inline int cpuset_ary_isset(size_t cpu, cpu_set_t **ary, size_t nmemb,
+				   size_t setsize, size_t *idx)
+{
+	size_t i;
+
+	for (i = 0; i < nmemb; i++) {
+		if (CPU_ISSET_S(cpu, setsize, ary[i])) {
+			*idx = i;
+			return 0;
+		}
+	}
+	return 1;
+}
+
+extern int get_max_number_of_cpus(void);
+
+extern cpu_set_t *cpuset_alloc(int ncpus, size_t *setsize, size_t *nbits);
+extern void cpuset_free(cpu_set_t *set);
+
+extern char *cpulist_create(char *str, size_t len, cpu_set_t *set, size_t setsize);
+extern int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail);
+
+extern char *cpumask_create(char *str, size_t len, cpu_set_t *set, size_t setsize);
+extern int cpumask_parse(const char *str, cpu_set_t *set, size_t setsize);
+
+#endif /* UTIL_LINUX_CPUSET_H */
diff --git a/libblkid/include/crc32.h b/libblkid/include/crc32.h
new file mode 100644
index 0000000..2616910
--- /dev/null
+++ b/libblkid/include/crc32.h
@@ -0,0 +1,10 @@
+#ifndef UL_NG_CRC32_H
+#define UL_NG_CRC32_H
+
+#include <sys/types.h>
+#include <stdint.h>
+
+extern uint32_t crc32(uint32_t seed, const unsigned char *buf, size_t len);
+
+#endif
+
diff --git a/libblkid/include/crc64.h b/libblkid/include/crc64.h
new file mode 100644
index 0000000..40475d5
--- /dev/null
+++ b/libblkid/include/crc64.h
@@ -0,0 +1,9 @@
+#ifndef UTIL_LINUX_CRC64_H
+#define UTIL_LINUX_CRC64_H
+
+#include <sys/types.h>
+#include <stdint.h>
+
+extern uint64_t crc64(uint64_t seed, const unsigned char *data, size_t len);
+
+#endif
diff --git a/libblkid/include/debug.h b/libblkid/include/debug.h
new file mode 100644
index 0000000..0229ab3
--- /dev/null
+++ b/libblkid/include/debug.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef UTIL_LINUX_DEBUG_H
+#define UTIL_LINUX_DEBUG_H
+
+
+/*
+ * util-linux debug macros
+ *
+ * The debug stuff is based on <name>_debug_mask that controls what outputs is
+ * expected. The mask is usually initialized by <NAME>_DEBUG= env.variable
+ *
+ * After successful initialization the flag <PREFIX>_DEBUG_INIT is always set
+ * to the mask (this flag is required). The <PREFIX> is usually library API
+ * prefix (e.g. MNT_) or program name (e.g. CFDISK_)
+ *
+ * In the code is possible to use
+ *
+ *	DBG(FOO, ul_debug("this is output for foo"));
+ *
+ * where for the FOO has to be defined <PREFIX>_DEBUG_FOO.
+ *
+ * It's possible to initialize the mask by comma delimited strings with
+ * subsystem names (e.g. "LIBMOUNT_DEBUG=options,tab"). In this case is
+ * necessary to define mask names array. This functionality is optional.
+ *
+ * It's stringly recommended to use UL_* macros to define/declare/use
+ * the debug stuff.
+ *
+ * See disk-utils/cfdisk.c: cfdisk_init_debug()  for programs debug
+ *  or libmount/src/init.c: mnt_init_debug()     for library debug
+ *
+ */
+
+#include <stdarg.h>
+#include <string.h>
+
+struct ul_debug_maskname {
+	const char *name;
+	int mask;
+	const char *help;
+};
+#define UL_DEBUG_EMPTY_MASKNAMES {{ NULL, 0, NULL }}
+#define UL_DEBUG_DEFINE_MASKNAMES(m) static const struct ul_debug_maskname m ## _masknames[]
+#define UL_DEBUG_MASKNAMES(m)	m ## _masknames
+
+#define UL_DEBUG_DEFINE_MASK(m) int m ## _debug_mask
+#define UL_DEBUG_DECLARE_MASK(m) extern UL_DEBUG_DEFINE_MASK(m)
+
+/* p - flag prefix, m - flag postfix */
+#define UL_DEBUG_DEFINE_FLAG(p, m) p ## m
+
+/* l - library name, p - flag prefix, m - flag postfix, x - function */
+#define __UL_DBG(l, p, m, x) \
+	do { \
+		if ((p ## m) & l ## _debug_mask) { \
+			fprintf(stderr, "%d: %s: %8s: ", getpid(), # l, # m); \
+			x; \
+		} \
+	} while (0)
+
+#define __UL_DBG_CALL(l, p, m, x) \
+	do { \
+		if ((p ## m) & l ## _debug_mask) { \
+			x; \
+		} \
+	} while (0)
+
+#define __UL_DBG_FLUSH(l, p) \
+	do { \
+		if (l ## _debug_mask && \
+		    l ## _debug_mask != p ## INIT) { \
+			fflush(stderr); \
+		} \
+	} while (0)
+
+
+#define __UL_INIT_DEBUG(lib, pref, mask, env) \
+	do { \
+		if (lib ## _debug_mask & pref ## INIT) \
+		; \
+		else if (!mask) { \
+			char *str = getenv(# env); \
+			if (str) \
+				lib ## _debug_mask = ul_debug_parse_envmask(lib ## _masknames, str); \
+		} else \
+			lib ## _debug_mask = mask; \
+		lib ## _debug_mask |= pref ## INIT; \
+	} while (0)
+
+
+static inline void __attribute__ ((__format__ (__printf__, 1, 2)))
+ul_debug(const char *mesg, ...)
+{
+	va_list ap;
+	va_start(ap, mesg);
+	vfprintf(stderr, mesg, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+}
+
+static inline void __attribute__ ((__format__ (__printf__, 2, 3)))
+ul_debugobj(void *handler, const char *mesg, ...)
+{
+	va_list ap;
+
+	if (handler)
+		fprintf(stderr, "[%p]: ", handler);
+	va_start(ap, mesg);
+	vfprintf(stderr, mesg, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+}
+
+static inline int ul_debug_parse_envmask(
+			const struct ul_debug_maskname flagnames[],
+			const char *mask)
+{
+	int res;
+	char *ptr;
+
+	/* let's check for a numeric mask first */
+	res = strtoul(mask, &ptr, 0);
+
+	/* perhaps it's a comma-separated string? */
+	if (ptr && *ptr && flagnames && flagnames[0].name) {
+		char *msbuf, *ms, *name;
+		res = 0;
+
+		ms = msbuf = strdup(mask);
+		if (!ms)
+			return res;
+
+		while ((name = strtok_r(ms, ",", &ptr))) {
+			const struct ul_debug_maskname *d;
+			ms = ptr;
+
+			for (d = flagnames; d && d->name; d++) {
+				if (strcmp(name, d->name) == 0) {
+					res |= d->mask;
+					break;
+				}
+			}
+			/* nothing else we can do by OR-ing the mask */
+			if (res == 0xffff)
+				break;
+		}
+		free(msbuf);
+	} else if (ptr && strcmp(ptr, "all") == 0)
+		res = 0xffff;
+
+	return res;
+}
+
+static inline void ul_debug_print_masks(
+			const char *env,
+			const struct ul_debug_maskname flagnames[])
+{
+	const struct ul_debug_maskname *d;
+
+	if (!flagnames)
+		return;
+
+	fprintf(stderr, "Available \"%s=<name>[,...]|<mask>\" debug masks:\n",
+			env);
+	for (d = flagnames; d && d->name; d++) {
+		if (!d->help)
+			continue;
+		fprintf(stderr, "   %-8s [0x%04x] : %s\n",
+				d->name, d->mask, d->help);
+	}
+}
+
+#endif /* UTIL_LINUX_DEBUG_H */
diff --git a/libblkid/include/env.h b/libblkid/include/env.h
new file mode 100644
index 0000000..a53d310
--- /dev/null
+++ b/libblkid/include/env.h
@@ -0,0 +1,16 @@
+#ifndef UTIL_LINUX_ENV_H
+#define UTIL_LINUX_ENV_H
+
+#include "c.h"
+
+extern void sanitize_env(void);
+extern char *safe_getenv(const char *arg);
+
+static inline void xsetenv(char const *name, char const *val, int overwrite)
+{
+	if (setenv(name, val, overwrite) != 0)
+		err(EXIT_FAILURE, "failed to set the %s environment variable", name);
+}
+
+#endif /* UTIL_LINUX_ENV_H */
+
diff --git a/libblkid/include/exec_shell.h b/libblkid/include/exec_shell.h
new file mode 100644
index 0000000..a2aa757
--- /dev/null
+++ b/libblkid/include/exec_shell.h
@@ -0,0 +1 @@
+extern void __attribute__((__noreturn__)) exec_shell(void);
diff --git a/libblkid/include/exitcodes.h b/libblkid/include/exitcodes.h
new file mode 100644
index 0000000..24ee123
--- /dev/null
+++ b/libblkid/include/exitcodes.h
@@ -0,0 +1,35 @@
+#ifndef UTIL_LINUX_EXITCODES_H
+#define UTIL_LINUX_EXITCODES_H
+/*
+ * BE CAREFUL
+ *
+ * These exit codes are part of the official interface for mount,
+ * fsck, mkfs, etc. wrappers.
+ */
+
+/* Exit codes used by mkfs-type programs */
+#define MKFS_EX_OK	0	/* No errors */
+#define MKFS_EX_ERROR	8	/* Operational error */
+#define MKFS_EX_USAGE	16	/* Usage or syntax error */
+
+/* Exit codes used by fsck-type programs */
+#define FSCK_EX_OK		0	/* No errors */
+#define FSCK_EX_NONDESTRUCT	1	/* File system errors corrected */
+#define FSCK_EX_REBOOT		2	/* System should be rebooted */
+#define FSCK_EX_DESTRUCT	FSCK_EX_REBOOT	/* Alias */
+#define FSCK_EX_UNCORRECTED	4	/* File system errors left uncorrected */
+#define FSCK_EX_ERROR		8	/* Operational error */
+#define FSCK_EX_USAGE		16	/* Usage or syntax error */
+#define FSCK_EX_LIBRARY		128	/* Shared library error */
+
+/* Exit codes used by mount-line programs */
+#define MOUNT_EX_SUCCESS	0	/* No errors */
+#define MOUNT_EX_USAGE		1	/* incorrect invocation or permission */
+#define MOUNT_EX_SYSERR		2	/* out of memory, cannot fork, ... */
+#define MOUNT_EX_SOFTWARE	4	/* internal mount bug or wrong version */
+#define MOUNT_EX_USER		8	/* user interrupt */
+#define MOUNT_EX_FILEIO		16	/* problems writing, locking, ... mtab/fstab */
+#define MOUNT_EX_FAIL		32	/* mount failure */
+#define MOUNT_EX_SOMEOK		64	/* some mount succeeded */
+
+#endif	/* UTIL_LINUX_EXITCODES_H */
diff --git a/libblkid/include/fileutils.h b/libblkid/include/fileutils.h
new file mode 100644
index 0000000..b4e0a4a
--- /dev/null
+++ b/libblkid/include/fileutils.h
@@ -0,0 +1,33 @@
+#ifndef UTIL_LINUX_FILEUTILS
+#define UTIL_LINUX_FILEUTILS
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "c.h"
+
+extern int xmkstemp(char **tmpname, char *dir);
+
+static inline FILE *xfmkstemp(char **tmpname, char *dir)
+{
+	int fd;
+	FILE *ret;
+
+	fd = xmkstemp(tmpname, dir);
+	if (fd == -1)
+		return NULL;
+
+	if (!(ret = fdopen(fd, "w+"))) {
+		close(fd);
+		return NULL;
+	}
+	return ret;
+}
+
+extern int get_fd_tabsize(void);
+
+extern int mkdir_p(const char *path, mode_t mode);
+extern char *stripoff_last_component(char *path);
+
+#endif /* UTIL_LINUX_FILEUTILS */
diff --git a/libblkid/include/ismounted.h b/libblkid/include/ismounted.h
new file mode 100644
index 0000000..57918cb
--- /dev/null
+++ b/libblkid/include/ismounted.h
@@ -0,0 +1,14 @@
+#ifndef IS_MOUNTED_H
+#define IS_MOUNTED_H
+
+#define MF_MOUNTED	1
+#define MF_ISROOT	2
+#define MF_READONLY	4
+#define MF_SWAP		8
+#define MF_BUSY		16
+
+extern int is_mounted(const char *file);
+extern int check_mount_point(const char *device, int *mount_flags,
+				 char *mtpt, int mtlen);
+
+#endif /* IS_MOUNTED_H */
diff --git a/libblkid/include/linux_reboot.h b/libblkid/include/linux_reboot.h
new file mode 100644
index 0000000..9cebc67
--- /dev/null
+++ b/libblkid/include/linux_reboot.h
@@ -0,0 +1,72 @@
+#ifndef _LINUX_REBOOT_H
+#define _LINUX_REBOOT_H
+
+/*
+ * Magic values required to use _reboot() system call.
+ */
+
+#define	LINUX_REBOOT_MAGIC1	0xfee1dead
+#define	LINUX_REBOOT_MAGIC2	672274793
+#define	LINUX_REBOOT_MAGIC2A	85072278
+#define	LINUX_REBOOT_MAGIC2B	369367448
+
+
+/*
+ * Commands accepted by the _reboot() system call.
+ *
+ * RESTART     Restart system using default command and mode.
+ * HALT        Stop OS and give system control to ROM monitor, if any.
+ * CAD_ON      Ctrl-Alt-Del sequence causes RESTART command.
+ * CAD_OFF     Ctrl-Alt-Del sequence sends SIGINT to init task.
+ * POWER_OFF   Stop OS and remove all power from system, if possible.
+ * RESTART2    Restart system using given command string.
+ */
+
+#define	LINUX_REBOOT_CMD_RESTART	0x01234567
+#define	LINUX_REBOOT_CMD_HALT		0xCDEF0123
+#define	LINUX_REBOOT_CMD_CAD_ON		0x89ABCDEF
+#define	LINUX_REBOOT_CMD_CAD_OFF	0x00000000
+#define	LINUX_REBOOT_CMD_POWER_OFF	0x4321FEDC
+#define	LINUX_REBOOT_CMD_RESTART2	0xA1B2C3D4
+
+/* Including <unistd.h> makes sure that on a glibc system
+   <features.h> is included, which again defines __GLIBC__ */
+#include <unistd.h>
+#include "linux_reboot.h"
+
+#define USE_LIBC
+
+#ifdef USE_LIBC
+
+/* libc version */
+#if defined __GLIBC__ && __GLIBC__ >= 2
+#  include <sys/reboot.h>
+#  define REBOOT(cmd) reboot(cmd)
+#else
+extern int reboot(int, int, int);
+#  define REBOOT(cmd) reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,(cmd))
+#endif
+static inline int my_reboot(int cmd) {
+	return REBOOT(cmd);
+}
+
+#else /* no USE_LIBC */
+
+/* direct syscall version */
+#include <linux/unistd.h>
+
+#ifdef _syscall3
+_syscall3(int,  reboot,  int,  magic, int, magic_too, int, cmd);
+#else
+/* Let us hope we have a 3-argument reboot here */
+extern int reboot(int, int, int);
+#endif
+
+static inline int my_reboot(int cmd) {
+	return reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd);
+}
+
+#endif
+
+
+#endif /*  _LINUX_REBOOT_H  */
diff --git a/libblkid/include/linux_version.h b/libblkid/include/linux_version.h
new file mode 100644
index 0000000..a6a1e99
--- /dev/null
+++ b/libblkid/include/linux_version.h
@@ -0,0 +1,14 @@
+#ifndef LINUX_VERSION_H
+#define LINUX_VERSION_H
+
+#ifdef HAVE_LINUX_VERSION_H
+# include <linux/version.h>
+#endif
+
+#ifndef KERNEL_VERSION
+# define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+#endif
+
+int get_linux_version(void);
+
+#endif /* LINUX_VERSION_H */
diff --git a/libblkid/include/list.h b/libblkid/include/list.h
new file mode 100644
index 0000000..3c08aa5
--- /dev/null
+++ b/libblkid/include/list.h
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 1999-2008 by Theodore Ts'o
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * (based on list.h from e2fsprogs)
+ * Merge sort based on kernel's implementation.
+ */
+
+#ifndef UTIL_LINUX_LIST_H
+#define UTIL_LINUX_LIST_H
+
+/* TODO: use AC_C_INLINE */
+#ifdef __GNUC__
+#define _INLINE_ static __inline__
+#else                         /* For Watcom C */
+#define _INLINE_ static inline
+#endif
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+	struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+	struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+_INLINE_ void __list_add(struct list_head * add,
+	struct list_head * prev,
+	struct list_head * next)
+{
+	next->prev = add;
+	add->next = next;
+	add->prev = prev;
+	prev->next = add;
+}
+
+/**
+ * list_add - add a new entry
+ * @add:	new entry to be added
+ * @head:	list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+_INLINE_ void list_add(struct list_head *add, struct list_head *head)
+{
+	__list_add(add, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @add:	new entry to be added
+ * @head:	list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+_INLINE_ void list_add_tail(struct list_head *add, struct list_head *head)
+{
+	__list_add(add, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+_INLINE_ void __list_del(struct list_head * prev,
+				  struct list_head * next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry:	the element to delete from the list.
+ *
+ * list_empty() on @entry does not return true after this, @entry is
+ * in an undefined state.
+ */
+_INLINE_ void list_del(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry:	the element to delete from the list.
+ */
+_INLINE_ void list_del_init(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head:	the list to test.
+ */
+_INLINE_ int list_empty(struct list_head *head)
+{
+	return head->next == head;
+}
+
+/**
+ * list_entry_is_last - tests whether is entry last in the list
+ * @entry:	the entry to test.
+ * @head:	the list to test.
+ */
+_INLINE_ int list_entry_is_last(struct list_head *entry, struct list_head *head)
+{
+	return head->prev == entry;
+}
+
+/**
+ * list_splice - join two lists
+ * @list:	the new list to add.
+ * @head:	the place to add it in the first list.
+ */
+_INLINE_ void list_splice(struct list_head *list, struct list_head *head)
+{
+	struct list_head *first = list->next;
+
+	if (first != list) {
+		struct list_head *last = list->prev;
+		struct list_head *at = head->next;
+
+		first->prev = head;
+		head->next = first;
+
+		last->next = at;
+		at->prev = last;
+	}
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:	the &struct list_head pointer.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) __extension__ ({              \
+	const typeof( ((type *)0)->member ) *__mptr = (ptr);   \
+	(type *)( (char *)__mptr - offsetof(type,member) );})
+
+
+#define list_first_entry(head, type, member) \
+	((head) && (head)->next != (head) ? list_entry((head)->next, type, member) : NULL)
+
+#define list_last_entry(head, type, member) \
+	((head) && (head)->prev != (head) ? list_entry((head)->prev, type, member) : NULL)
+
+/**
+ * list_for_each - iterate over elements in a list
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+#define list_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_backwardly - iterate over elements in a list in reverse
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+#define list_for_each_backwardly(pos, head) \
+	for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over elements in a list, but don't dereference
+ *                      pos after the body is done (in case it is freed)
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @pnext:	the &struct list_head to use as a pointer to the next item.
+ * @head:	the head for your list (not included in iteration).
+ */
+#define list_for_each_safe(pos, pnext, head) \
+	for (pos = (head)->next, pnext = pos->next; pos != (head); \
+	     pos = pnext, pnext = pos->next)
+
+#define MAX_LIST_LENGTH_BITS 20
+
+/*
+ * Returns a list organized in an intermediate format suited
+ * to chaining of merge() calls: null-terminated, no reserved or
+ * sentinel head node, "prev" links not maintained.
+ */
+_INLINE_ struct list_head *merge(int (*cmp)(struct list_head *a,
+					  struct list_head *b,
+					  void *data),
+			       void *data,
+			       struct list_head *a, struct list_head *b)
+{
+	struct list_head head, *tail = &head;
+
+	while (a && b) {
+		/* if equal, take 'a' -- important for sort stability */
+		if ((*cmp)(a, b, data) <= 0) {
+			tail->next = a;
+			a = a->next;
+		} else {
+			tail->next = b;
+			b = b->next;
+		}
+		tail = tail->next;
+	}
+	tail->next = a ? a : b;
+	return head.next;
+}
+
+/*
+ * Combine final list merge with restoration of standard doubly-linked
+ * list structure.  This approach duplicates code from merge(), but
+ * runs faster than the tidier alternatives of either a separate final
+ * prev-link restoration pass, or maintaining the prev links
+ * throughout.
+ */
+_INLINE_ void merge_and_restore_back_links(int (*cmp)(struct list_head *a,
+						    struct list_head *b,
+						    void *data),
+					 void *data,
+					 struct list_head *head,
+					 struct list_head *a, struct list_head *b)
+{
+	struct list_head *tail = head;
+
+	while (a && b) {
+		/* if equal, take 'a' -- important for sort stability */
+		if ((*cmp)(a, b, data) <= 0) {
+			tail->next = a;
+			a->prev = tail;
+			a = a->next;
+		} else {
+			tail->next = b;
+			b->prev = tail;
+			b = b->next;
+		}
+		tail = tail->next;
+	}
+	tail->next = a ? a : b;
+
+	do {
+		/*
+		 * In worst cases this loop may run many iterations.
+		 * Continue callbacks to the client even though no
+		 * element comparison is needed, so the client's cmp()
+		 * routine can invoke cond_resched() periodically.
+		 */
+		(*cmp)(tail->next, tail->next, data);
+
+		tail->next->prev = tail;
+		tail = tail->next;
+	} while (tail->next);
+
+	tail->next = head;
+	head->prev = tail;
+}
+
+
+/**
+ * list_sort - sort a list
+ * @head: the list to sort
+ * @cmp: the elements comparison function
+ *
+ * This function implements "merge sort", which has O(nlog(n))
+ * complexity.
+ *
+ * The comparison function @cmp must return a negative value if @a
+ * should sort before @b, and a positive value if @a should sort after
+ * @b. If @a and @b are equivalent, and their original relative
+ * ordering is to be preserved, @cmp must return 0.
+ */
+_INLINE_ void list_sort(struct list_head *head,
+			int (*cmp)(struct list_head *a,
+				   struct list_head *b,
+				   void *data),
+			void *data)
+{
+	struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists
+							   -- last slot is a sentinel */
+	size_t lev;  /* index into part[] */
+	size_t max_lev = 0;
+	struct list_head *list;
+
+	if (list_empty(head))
+		return;
+
+	memset(part, 0, sizeof(part));
+
+	head->prev->next = NULL;
+	list = head->next;
+
+	while (list) {
+		struct list_head *cur = list;
+		list = list->next;
+		cur->next = NULL;
+
+		for (lev = 0; part[lev]; lev++) {
+			cur = merge(cmp, data, part[lev], cur);
+			part[lev] = NULL;
+		}
+		if (lev > max_lev) {
+			/* list passed to list_sort() too long for efficiency */
+			if (lev >= ARRAY_SIZE(part) - 1)
+				lev--;
+			max_lev = lev;
+		}
+		part[lev] = cur;
+	}
+
+	for (lev = 0; lev < max_lev; lev++)
+		if (part[lev])
+			list = merge(cmp, data, part[lev], list);
+
+	merge_and_restore_back_links(cmp, data, head, part[max_lev], list);
+}
+
+#undef _INLINE_
+
+#endif /* UTIL_LINUX_LIST_H */
diff --git a/libblkid/include/loopdev.h b/libblkid/include/loopdev.h
new file mode 100644
index 0000000..573a569
--- /dev/null
+++ b/libblkid/include/loopdev.h
@@ -0,0 +1,193 @@
+#ifndef UTIL_LINUX_LOOPDEV_H
+#define UTIL_LINUX_LOOPDEV_H
+
+#include "sysfs.h"
+
+/*
+ * loop_info.lo_encrypt_type
+ */
+#define LO_CRYPT_NONE	0
+#define LO_CRYPT_XOR	1
+#define LO_CRYPT_DES	2
+#define LO_CRYPT_CRYPTOAPI 18
+
+#define LOOP_SET_FD		0x4C00
+#define LOOP_CLR_FD		0x4C01
+/*
+ * Obsolete (kernel < 2.6)
+ *
+ * #define LOOP_SET_STATUS	0x4C02
+ * #define LOOP_GET_STATUS	0x4C03
+ */
+#define LOOP_SET_STATUS64	0x4C04
+#define LOOP_GET_STATUS64	0x4C05
+/* #define LOOP_CHANGE_FD	0x4C06 */
+#define LOOP_SET_CAPACITY	0x4C07
+
+/* /dev/loop-control interface */
+#ifndef LOOP_CTL_ADD
+# define LOOP_CTL_ADD		0x4C80
+# define LOOP_CTL_REMOVE	0x4C81
+# define LOOP_CTL_GET_FREE	0x4C82
+#endif
+
+/*
+ * loop_info.lo_flags
+ */
+enum {
+	LO_FLAGS_READ_ONLY  = 1,
+	LO_FLAGS_USE_AOPS   = 2,
+	LO_FLAGS_AUTOCLEAR  = 4,	/* kernel >= 2.6.25 */
+	LO_FLAGS_PARTSCAN   = 8,	/* kernel >= 3.2 */
+};
+
+#define LO_NAME_SIZE	64
+#define LO_KEY_SIZE	32
+
+/*
+ * Linux LOOP_{SET,GET}_STATUS64 ioctl struct
+ */
+struct loop_info64 {
+	uint64_t	lo_device;
+	uint64_t	lo_inode;
+	uint64_t	lo_rdevice;
+	uint64_t	lo_offset;
+	uint64_t	lo_sizelimit; /* bytes, 0 == max available */
+	uint32_t	lo_number;
+	uint32_t	lo_encrypt_type;
+	uint32_t	lo_encrypt_key_size;
+	uint32_t	lo_flags;
+	uint8_t		lo_file_name[LO_NAME_SIZE];
+	uint8_t		lo_crypt_name[LO_NAME_SIZE];
+	uint8_t		lo_encrypt_key[LO_KEY_SIZE];
+	uint64_t	lo_init[2];
+};
+
+#define LOOPDEV_MAJOR		7	/* loop major number */
+#define LOOPDEV_DEFAULT_NNODES	8	/* default number of loop devices */
+
+struct loopdev_iter {
+	FILE		*proc;		/* /proc/partitions */
+	DIR		*sysblock;	/* /sys/block */
+	int		ncur;		/* current position */
+	int		*minors;	/* ary of minor numbers (when scan whole /dev) */
+	int		nminors;	/* number of items in *minors */
+	int		ct_perm;	/* count permission problems */
+	int		ct_succ;	/* count number of detected devices */
+
+	unsigned int	done:1;		/* scanning done */
+	unsigned int	default_check:1;/* check first LOOPDEV_NLOOPS */
+	int		flags;		/* LOOPITER_FL_* flags */
+};
+
+enum {
+	LOOPITER_FL_FREE	= (1 << 0),
+	LOOPITER_FL_USED	= (1 << 1)
+};
+
+/*
+ * handler for work with loop devices
+ */
+struct loopdev_cxt {
+	char		device[128];	/* device path (e.g. /dev/loop<N>) */
+	char		*filename;	/* backing file for loopcxt_set_... */
+	int		fd;		/* open(/dev/looo<N>) */
+	int		mode;		/* fd mode O_{RDONLY,RDWR} */
+
+	int		flags;		/* LOOPDEV_FL_* flags */
+	unsigned int	has_info:1;	/* .info contains data */
+	unsigned int	extra_check:1;	/* unusual stuff for iterator */
+	unsigned int	info_failed:1;	/* LOOP_GET_STATUS ioctl failed */
+	unsigned int    control_ok:1;	/* /dev/loop-control success */
+
+	struct sysfs_cxt	sysfs;	/* pointer to /sys/dev/block/<maj:min>/ */
+	struct loop_info64	info;	/* for GET/SET ioctl */
+	struct loopdev_iter	iter;	/* scans /sys or /dev for used/free devices */
+};
+
+#define UL_LOOPDEVCXT_EMPTY { .fd = -1, .sysfs = UL_SYSFSCXT_EMPTY }
+
+/*
+ * loopdev_cxt.flags
+ */
+enum {
+	LOOPDEV_FL_RDONLY	= (1 << 0),	/* open(/dev/loop) mode; default */
+	LOOPDEV_FL_RDWR		= (1 << 1),	/* necessary for loop setup only */
+	LOOPDEV_FL_OFFSET	= (1 << 4),
+	LOOPDEV_FL_NOSYSFS	= (1 << 5),
+	LOOPDEV_FL_NOIOCTL	= (1 << 6),
+	LOOPDEV_FL_DEVSUBDIR	= (1 << 7),
+	LOOPDEV_FL_CONTROL	= (1 << 8),	/* system with /dev/loop-control */
+	LOOPDEV_FL_SIZELIMIT	= (1 << 9)
+};
+
+/*
+ * High-level
+ */
+extern int loopmod_supports_partscan(void);
+
+extern int is_loopdev(const char *device);
+extern int loopdev_is_autoclear(const char *device);
+
+extern char *loopdev_get_backing_file(const char *device);
+extern int loopdev_is_used(const char *device, const char *filename,
+			   uint64_t offset, int flags);
+extern char *loopdev_find_by_backing_file(const char *filename,
+					  uint64_t offset, int flags);
+extern int loopcxt_find_unused(struct loopdev_cxt *lc);
+extern int loopdev_delete(const char *device);
+extern int loopdev_count_by_backing_file(const char *filename, char **loopdev);
+
+/*
+ * Low-level
+ */
+extern int loopcxt_init(struct loopdev_cxt *lc, int flags)
+				__attribute__ ((warn_unused_result));
+extern void loopcxt_deinit(struct loopdev_cxt *lc);
+
+extern int loopcxt_set_device(struct loopdev_cxt *lc, const char *device)
+				__attribute__ ((warn_unused_result));
+extern int loopcxt_has_device(struct loopdev_cxt *lc);
+extern int loopcxt_add_device(struct loopdev_cxt *lc);
+extern char *loopcxt_strdup_device(struct loopdev_cxt *lc);
+extern const char *loopcxt_get_device(struct loopdev_cxt *lc);
+extern struct sysfs_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc);
+extern struct loop_info64 *loopcxt_get_info(struct loopdev_cxt *lc);
+
+extern int loopcxt_get_fd(struct loopdev_cxt *lc);
+extern int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode);
+
+extern int loopcxt_init_iterator(struct loopdev_cxt *lc, int flags);
+extern int loopcxt_deinit_iterator(struct loopdev_cxt *lc);
+extern int loopcxt_next(struct loopdev_cxt *lc);
+
+extern int loopcxt_setup_device(struct loopdev_cxt *lc);
+extern int loopcxt_delete_device(struct loopdev_cxt *lc);
+extern int loopcxt_set_capacity(struct loopdev_cxt *lc);
+
+int loopcxt_set_offset(struct loopdev_cxt *lc, uint64_t offset);
+int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit);
+int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags);
+int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename);
+
+extern char *loopcxt_get_backing_file(struct loopdev_cxt *lc);
+extern int loopcxt_get_backing_devno(struct loopdev_cxt *lc, dev_t *devno);
+extern int loopcxt_get_backing_inode(struct loopdev_cxt *lc, ino_t *ino);
+extern int loopcxt_get_offset(struct loopdev_cxt *lc, uint64_t *offset);
+extern int loopcxt_get_sizelimit(struct loopdev_cxt *lc, uint64_t *size);
+extern int loopcxt_get_encrypt_type(struct loopdev_cxt *lc, uint32_t *type);
+extern const char *loopcxt_get_crypt_name(struct loopdev_cxt *lc);
+extern int loopcxt_is_autoclear(struct loopdev_cxt *lc);
+extern int loopcxt_is_readonly(struct loopdev_cxt *lc);
+extern int loopcxt_is_partscan(struct loopdev_cxt *lc);
+extern int loopcxt_find_by_backing_file(struct loopdev_cxt *lc,
+				const char *filename,
+                                uint64_t offset, int flags);
+
+extern int loopcxt_is_used(struct loopdev_cxt *lc,
+                    struct stat *st,
+                    const char *backing_file,
+                    uint64_t offset,
+                    int flags);
+
+#endif /* UTIL_LINUX_LOOPDEV_H */
diff --git a/libblkid/include/mangle.h b/libblkid/include/mangle.h
new file mode 100644
index 0000000..ec492b5
--- /dev/null
+++ b/libblkid/include/mangle.h
@@ -0,0 +1,26 @@
+#ifndef UTIL_LINUX_MANGLE_H
+#define UTIL_LINUX_MANGLE_H
+
+/*
+ * Functions for \oct encoding used in mtab/fstab/swaps/etc.
+ */
+
+extern char *mangle(const char *s);
+
+extern void unmangle_to_buffer(const char *s, char *buf, size_t len);
+void unhexmangle_to_buffer(const char *s, char *buf, size_t len);
+
+extern char *unmangle(const char *s, char **end);
+
+static inline void unmangle_string(char *s)
+{
+	unmangle_to_buffer(s, s, strlen(s) + 1);
+}
+
+static inline void unhexmangle_string(char *s)
+{
+	unhexmangle_to_buffer(s, s, strlen(s) + 1);
+}
+
+#endif /* UTIL_LINUX_MANGLE_H */
+
diff --git a/libblkid/include/match.h b/libblkid/include/match.h
new file mode 100644
index 0000000..94440c2
--- /dev/null
+++ b/libblkid/include/match.h
@@ -0,0 +1,12 @@
+/*
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef UTIL_LINUX_MATCH_H
+#define UTIL_LINUX_MATCH_H
+
+extern int match_fstype(const char *type, const char *pattern);
+
+#endif /* UTIL_LINUX_MATCH_H */
diff --git a/libblkid/include/mbsalign.h b/libblkid/include/mbsalign.h
new file mode 100644
index 0000000..5eaf606
--- /dev/null
+++ b/libblkid/include/mbsalign.h
@@ -0,0 +1,56 @@
+/* Align/Truncate a string in a given screen width
+   Copyright (C) 2009-2010 Free Software Foundation, Inc.
+   Copyright (C) 2010-2013 Karel Zak <kzak@redhat.com>
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation, either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.  */
+#ifndef UTIL_LINUX_MBSALIGN_H
+# define UTIL_LINUX_MBSALIGN_H
+# include <stddef.h>
+
+typedef enum { MBS_ALIGN_LEFT, MBS_ALIGN_RIGHT, MBS_ALIGN_CENTER } mbs_align_t;
+
+enum {
+  /* Use unibyte mode for invalid multibyte strings or
+     or when heap memory is exhausted.  */
+  MBA_UNIBYTE_FALLBACK = 0x0001,
+
+#if 0 /* Other possible options.  */
+  /* Skip invalid multibyte chars rather than failing  */
+  MBA_IGNORE_INVALID   = 0x0002,
+
+  /* Align multibyte strings using "figure space" (\u2007)  */
+  MBA_USE_FIGURE_SPACE = 0x0004,
+
+  /* Don't add any padding  */
+  MBA_TRUNCATE_ONLY    = 0x0008,
+
+  /* Don't truncate  */
+  MBA_PAD_ONLY         = 0x0010,
+#endif
+};
+
+extern size_t mbs_truncate(char *str, size_t *width);
+
+extern size_t mbsalign (const char *src, char *dest,
+			size_t dest_size,  size_t *width,
+			mbs_align_t align, int flags);
+
+extern size_t mbs_safe_nwidth(const char *buf, size_t bufsz, size_t *sz);
+extern size_t mbs_safe_width(const char *s);
+
+extern char *mbs_safe_encode(const char *s, size_t *width);
+extern char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf);
+extern size_t mbs_safe_encode_size(size_t bytes);
+
+#endif /* UTIL_LINUX_MBSALIGN_H */
diff --git a/libblkid/include/md5.h b/libblkid/include/md5.h
new file mode 100644
index 0000000..d997e37
--- /dev/null
+++ b/libblkid/include/md5.h
@@ -0,0 +1,29 @@
+#ifndef MD5_H
+#define MD5_H
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#else
+typedef unsigned int uint32_t;
+#endif
+
+#define MD5LENGTH 16
+
+struct MD5Context {
+	uint32_t buf[4];
+	uint32_t bits[2];
+	unsigned char in[64];
+};
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, unsigned char const *buf,
+	       unsigned len);
+void MD5Final(unsigned char digest[MD5LENGTH], struct MD5Context *context);
+void MD5Transform(uint32_t buf[4], uint32_t const in[16]);
+
+/*
+ * This is needed to make RSAREF happy on some MS-DOS compilers.
+ */
+typedef struct MD5Context MD5_CTX;
+
+#endif /* !MD5_H */
diff --git a/libblkid/include/minix.h b/libblkid/include/minix.h
new file mode 100644
index 0000000..f28991c
--- /dev/null
+++ b/libblkid/include/minix.h
@@ -0,0 +1,85 @@
+#ifndef UTIL_LINUX_MINIX_H
+#define UTIL_LINUX_MINIX_H
+
+#include <stdint.h>
+
+struct minix_inode {
+	uint16_t i_mode;
+	uint16_t i_uid;
+	uint32_t i_size;
+	uint32_t i_time;
+	uint8_t  i_gid;
+	uint8_t  i_nlinks;
+	uint16_t i_zone[9];
+};
+
+struct minix2_inode {
+	uint16_t i_mode;
+	uint16_t i_nlinks;
+	uint16_t i_uid;
+	uint16_t i_gid;
+	uint32_t i_size;
+	uint32_t i_atime;
+	uint32_t i_mtime;
+	uint32_t i_ctime;
+	uint32_t i_zone[10];
+};
+
+struct minix_super_block {
+	uint16_t s_ninodes;
+	uint16_t s_nzones;
+	uint16_t s_imap_blocks;
+	uint16_t s_zmap_blocks;
+	uint16_t s_firstdatazone;
+	uint16_t s_log_zone_size;
+	uint32_t s_max_size;
+	uint16_t s_magic;
+	uint16_t s_state;
+	uint32_t s_zones;
+};
+
+/* V3 minix super-block data on disk */
+struct minix3_super_block {
+	uint32_t s_ninodes;
+	uint16_t s_pad0;
+	uint16_t s_imap_blocks;
+	uint16_t s_zmap_blocks;
+	uint16_t s_firstdatazone;
+	uint16_t s_log_zone_size;
+	uint16_t s_pad1;
+	uint32_t s_max_size;
+	uint32_t s_zones;
+	uint16_t s_magic;
+	uint16_t s_pad2;
+	uint16_t s_blocksize;
+	uint8_t  s_disk_version;
+};
+
+/*
+ * Minix subpartitions are always within primary dos partition.
+ */
+#define MINIX_MAXPARTITIONS  4
+
+#define MINIX_BLOCK_SIZE_BITS 10
+#define MINIX_BLOCK_SIZE     (1 << MINIX_BLOCK_SIZE_BITS)
+
+#define MINIX_NAME_MAX       255             /* # chars in a file name */
+#define MINIX_MAX_INODES     65535
+
+#define MINIX_INODES_PER_BLOCK ((MINIX_BLOCK_SIZE)/(sizeof (struct minix_inode)))
+#define MINIX2_INODES_PER_BLOCK ((MINIX_BLOCK_SIZE)/(sizeof (struct minix2_inode)))
+
+/* minix_super_block.s_state */
+#define MINIX_VALID_FS       0x0001          /* Clean fs. */
+#define MINIX_ERROR_FS       0x0002          /* fs has errors. */
+
+
+#define MINIX_SUPER_MAGIC    0x137F          /* minix V1 fs, 14 char names */
+#define MINIX_SUPER_MAGIC2   0x138F          /* minix V1 fs, 30 char names */
+
+#define MINIX2_SUPER_MAGIC   0x2468	     /* minix V2 fs, 14 char names */
+#define MINIX2_SUPER_MAGIC2  0x2478	     /* minix V2 fs, 30 char names */
+
+#define MINIX3_SUPER_MAGIC   0x4d5a          /* minix V3 fs (60 char names) */
+
+#endif /* UTIL_LINUX_MINIX_H */
diff --git a/libblkid/include/monotonic.h b/libblkid/include/monotonic.h
new file mode 100644
index 0000000..f3b03d3
--- /dev/null
+++ b/libblkid/include/monotonic.h
@@ -0,0 +1,11 @@
+#ifndef UTIL_LINUX_BOOTTIME_H
+#define UTIL_LINUX_BOOTTIME_H
+
+/*
+ * Uses clock_gettime() that requires $CLOCKGETTIME_LIBS
+ */
+extern int get_boot_time(struct timeval *boot_time);
+
+extern int gettime_monotonic(struct timeval *tv);
+
+#endif /* UTIL_LINUX_BOOTTIME_H */
diff --git a/libblkid/include/namespace.h b/libblkid/include/namespace.h
new file mode 100644
index 0000000..ea231ca
--- /dev/null
+++ b/libblkid/include/namespace.h
@@ -0,0 +1,44 @@
+/* Compat code so unshare and setns can be used with older libcs */
+#ifndef UTIL_LINUX_NAMESPACE_H
+# define UTIL_LINUX_NAMESPACE_H
+
+# include <sched.h>
+
+# ifndef CLONE_NEWNS
+#  define CLONE_NEWNS 0x00020000
+# endif
+# ifndef CLONE_NEWUTS
+#  define CLONE_NEWUTS 0x04000000
+# endif
+# ifndef CLONE_NEWIPC
+#  define CLONE_NEWIPC 0x08000000
+# endif
+# ifndef CLONE_NEWNET
+#  define CLONE_NEWNET 0x40000000
+# endif
+# ifndef CLONE_NEWUSER
+#  define CLONE_NEWUSER 0x10000000
+# endif
+# ifndef CLONE_NEWPID
+#  define CLONE_NEWPID 0x20000000
+# endif
+
+# if !defined(HAVE_UNSHARE) || !defined(HAVE_SETNS)
+#  include <sys/syscall.h>
+# endif
+
+# if !defined(HAVE_UNSHARE) && defined(SYS_unshare)
+static inline int unshare(int flags)
+{
+	return syscall(SYS_unshare, flags);
+}
+# endif
+
+# if !defined(HAVE_SETNS) && defined(SYS_setns)
+static inline int setns(int fd, int nstype)
+{
+	return syscall(SYS_setns, fd, nstype);
+}
+# endif
+
+#endif	/* UTIL_LINUX_NAMESPACE_H */
diff --git a/libblkid/include/nls.h b/libblkid/include/nls.h
new file mode 100644
index 0000000..3eabfe6
--- /dev/null
+++ b/libblkid/include/nls.h
@@ -0,0 +1,115 @@
+#ifndef UTIL_LINUX_NLS_H
+#define UTIL_LINUX_NLS_H
+
+int main(int argc, char *argv[]);
+
+#ifndef LOCALEDIR
+#define LOCALEDIR "/usr/share/locale"
+#endif
+
+#ifdef HAVE_LOCALE_H
+# include <locale.h>
+#else
+# undef setlocale
+# define setlocale(Category, Locale) /* empty */
+struct lconv
+{
+	char *decimal_point;
+};
+# undef localeconv
+# define localeconv() NULL
+#endif
+
+#ifdef ENABLE_NLS
+# include <libintl.h>
+# define _(Text) gettext (Text)
+# ifdef gettext_noop
+#  define N_(String) gettext_noop (String)
+# else
+#  define N_(String) (String)
+# endif
+# define P_(Singular, Plural, n) ngettext (Singular, Plural, n)
+#else
+# undef bindtextdomain
+# define bindtextdomain(Domain, Directory) /* empty */
+# undef textdomain
+# define textdomain(Domain) /* empty */
+# define _(Text) (Text)
+# define N_(Text) (Text)
+# define P_(Singular, Plural, n) ((n) == 1 ? (Singular) : (Plural))
+#endif
+
+#ifdef HAVE_LANGINFO_H
+# include <langinfo.h>
+#else
+
+typedef int nl_item;
+extern char *langinfo_fallback(nl_item item);
+
+# define nl_langinfo	langinfo_fallback
+
+enum {
+	CODESET = 1,
+	RADIXCHAR,
+	THOUSEP,
+	D_T_FMT,
+	D_FMT,
+	T_FMT,
+	T_FMT_AMPM,
+	AM_STR,
+	PM_STR,
+
+	DAY_1,
+	DAY_2,
+	DAY_3,
+	DAY_4,
+	DAY_5,
+	DAY_6,
+	DAY_7,
+
+	ABDAY_1,
+	ABDAY_2,
+	ABDAY_3,
+	ABDAY_4,
+	ABDAY_5,
+	ABDAY_6,
+	ABDAY_7,
+
+	MON_1,
+	MON_2,
+	MON_3,
+	MON_4,
+	MON_5,
+	MON_6,
+	MON_7,
+	MON_8,
+	MON_9,
+	MON_10,
+	MON_11,
+	MON_12,
+
+	ABMON_1,
+	ABMON_2,
+	ABMON_3,
+	ABMON_4,
+	ABMON_5,
+	ABMON_6,
+	ABMON_7,
+	ABMON_8,
+	ABMON_9,
+	ABMON_10,
+	ABMON_11,
+	ABMON_12,
+
+	ERA_D_FMT,
+	ERA_D_T_FMT,
+	ERA_T_FMT,
+	ALT_DIGITS,
+	CRNCYSTR,
+	YESEXPR,
+	NOEXPR
+};
+
+#endif /* !HAVE_LANGINFO_H */
+
+#endif /* UTIL_LINUX_NLS_H */
diff --git a/libblkid/include/optutils.h b/libblkid/include/optutils.h
new file mode 100644
index 0000000..99ad7e4
--- /dev/null
+++ b/libblkid/include/optutils.h
@@ -0,0 +1,103 @@
+#ifndef UTIL_LINUX_OPTUTILS_H
+#define UTIL_LINUX_OPTUTILS_H
+
+#include "c.h"
+#include "nls.h"
+
+static inline const char *option_to_longopt(int c, const struct option *opts)
+{
+	const struct option *o;
+
+	for (o = opts; o->name; o++)
+		if (o->val == c)
+			return o->name;
+	return NULL;
+}
+
+#ifndef OPTUTILS_EXIT_CODE
+# define OPTUTILS_EXIT_CODE EXIT_FAILURE
+#endif
+
+/*
+ * Check collisions between options.
+ *
+ * The conflicts between options are described in ul_excl_t array. The
+ * array contains groups of mutually exclusive options. For example
+ *
+ *	static const ul_excl_t excl[] = {
+ *		{ 'Z','b','c' },		// first group
+ *		{ 'b','x' },			// second group
+ *		{ 0 }
+ *	};
+ *
+ *	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+ *
+ *	while ((c = getopt_long(argc, argv, "Zbcx", longopts, NULL)) != -1) {
+ *
+ *		err_exclusive_options(c, longopts, excl, excl_st);
+ *
+ *		switch (c) {
+ *		case 'Z':
+ *		   ....
+ *		}
+ *	}
+ *
+ * The array excl[] defines two groups of the mutually exclusive options. The
+ * option '-b' is in the both groups.
+ *
+ * Note that the options in the group have to be in ASCII order (ABC..abc..) and
+ * groups have to be also in ASCII order.
+ *
+ * The maximal number of the options in the group is 15 (size of the array is
+ * 16, last is zero).
+ *
+ * The current status of options is stored in excl_st array. The size of the array
+ * must be the same as number of the groups in the ul_excl_t array.
+ *
+ * If you're unsure then see sys-utils/mount.c or misc-utils/findmnt.c.
+ */
+#define UL_EXCL_STATUS_INIT	{ 0 }
+typedef int ul_excl_t[16];
+
+static inline void err_exclusive_options(
+			int c,
+			const struct option *opts,
+			const ul_excl_t *excl,
+			int *status)
+{
+	int e;
+
+	for (e = 0; excl[e][0] && excl[e][0] <= c; e++) {
+		const int *op = excl[e];
+
+		for (; *op && *op <= c; op++) {
+			if (*op != c)
+				continue;
+			if (status[e] == 0)
+				status[e] = c;
+			else if (status[e] != c) {
+				size_t ct = 0;
+
+				fprintf(stderr, _("%s: these options are "
+						  "mutually exclusive:"),
+						program_invocation_short_name);
+
+				for (op = excl[e];
+				     ct + 1 < ARRAY_SIZE(excl[0]) && *op;
+				     op++, ct++) {
+					const char *n = option_to_longopt(*op, opts);
+					if (n)
+						fprintf(stderr, " --%s", n);
+					else
+						fprintf(stderr, " -%c", *op);
+				}
+				fputc('\n', stderr);
+				exit(OPTUTILS_EXIT_CODE);
+			}
+			break;
+		}
+	}
+}
+
+#endif
+
diff --git a/libblkid/include/pager.h b/libblkid/include/pager.h
new file mode 100644
index 0000000..9ca42eb
--- /dev/null
+++ b/libblkid/include/pager.h
@@ -0,0 +1,6 @@
+#ifndef UTIL_LINUX_PAGER
+#define UTIL_LINUX_PAGER
+
+void setup_pager(void);
+
+#endif
diff --git a/libblkid/include/pamfail.h b/libblkid/include/pamfail.h
new file mode 100644
index 0000000..bb83b94
--- /dev/null
+++ b/libblkid/include/pamfail.h
@@ -0,0 +1,26 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#ifndef UTIL_LINUX_PAMFAIL_H
+#include <security/pam_appl.h>
+#ifdef HAVE_SECURITY_PAM_MISC_H
+# include <security/pam_misc.h>
+#elif defined(HAVE_SECURITY_OPENPAM_H)
+# include <security/openpam.h>
+#endif
+#include "c.h"
+
+static inline int
+pam_fail_check(pam_handle_t *pamh, int retcode)
+{
+	if (retcode == PAM_SUCCESS)
+		return 0;
+	warnx("%s", pam_strerror(pamh, retcode));
+	pam_end(pamh, retcode);
+	return 1;
+}
+
+#endif /* UTIL_LINUX_PAMFAIL_H */
diff --git a/libblkid/include/path.h b/libblkid/include/path.h
new file mode 100644
index 0000000..45da692
--- /dev/null
+++ b/libblkid/include/path.h
@@ -0,0 +1,33 @@
+#ifndef UTIL_LINUX_PATH_H
+#define UTIL_LINUX_PATH_H
+
+#include <stdio.h>
+#include <stdint.h>
+
+extern char *path_strdup(const char *path, ...)
+			__attribute__ ((__format__ (__printf__, 1, 2)));
+extern FILE *path_fopen(const char *mode, int exit_on_err, const char *path, ...)
+			__attribute__ ((__format__ (__printf__, 3, 4)));
+extern void path_read_str(char *result, size_t len, const char *path, ...)
+			__attribute__ ((__format__ (__printf__, 3, 4)));
+extern int path_write_str(const char *str, const char *path, ...)
+			 __attribute__ ((__format__ (__printf__, 2, 3)));
+extern int path_read_s32(const char *path, ...)
+			__attribute__ ((__format__ (__printf__, 1, 2)));
+extern uint64_t path_read_u64(const char *path, ...)
+			__attribute__ ((__format__ (__printf__, 1, 2)));
+
+extern int path_exist(const char *path, ...)
+		      __attribute__ ((__format__ (__printf__, 1, 2)));
+
+#ifdef HAVE_CPU_SET_T
+# include "cpuset.h"
+
+extern cpu_set_t *path_read_cpuset(int, const char *path, ...)
+			      __attribute__ ((__format__ (__printf__, 2, 3)));
+extern cpu_set_t *path_read_cpulist(int, const char *path, ...)
+			       __attribute__ ((__format__ (__printf__, 2, 3)));
+extern void path_set_prefix(const char *);
+#endif /* HAVE_CPU_SET_T */
+
+#endif /* UTIL_LINUX_PATH_H */
diff --git a/libblkid/include/pathnames.h b/libblkid/include/pathnames.h
new file mode 100644
index 0000000..0d21b98
--- /dev/null
+++ b/libblkid/include/pathnames.h
@@ -0,0 +1,196 @@
+/*
+ * Vaguely based on
+ *	@(#)pathnames.h	5.3 (Berkeley) 5/9/89
+ * This code is in the public domain.
+ */
+#ifndef PATHNAMES_H
+#define PATHNAMES_H
+
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+
+#ifndef __STDC__
+# error "we need an ANSI compiler"
+#endif
+
+/* used by kernel in /proc (e.g. /proc/swaps) for deleted files */
+#define PATH_DELETED_SUFFIX	"\\040(deleted)"
+#define PATH_DELETED_SUFFIX_SZ	(sizeof(PATH_DELETED_SUFFIX) - 1)
+
+/* DEFPATHs from <paths.h> don't include /usr/local */
+#undef _PATH_DEFPATH
+#define	_PATH_DEFPATH	        "/usr/local/bin:/bin:/usr/bin"
+
+#undef _PATH_DEFPATH_ROOT
+#define	_PATH_DEFPATH_ROOT	"/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
+
+#define _PATH_SECURETTY		"/etc/securetty"
+#define _PATH_WTMPLOCK		"/etc/wtmplock"
+
+#define	_PATH_HUSHLOGIN		".hushlogin"
+#define	_PATH_HUSHLOGINS	"/etc/hushlogins"
+
+#define _PATH_NOLOGIN_TXT	"/etc/nologin.txt"
+
+#ifndef _PATH_MAILDIR
+#define	_PATH_MAILDIR		"/var/spool/mail"
+#endif
+#define	_PATH_MOTDFILE		"/etc/motd"
+#define	_PATH_NOLOGIN		"/etc/nologin"
+#define	_PATH_VAR_NOLOGIN	"/var/run/nologin"
+
+#define _PATH_LOGIN		"/bin/login"
+#define _PATH_INITTAB		"/etc/inittab"
+#define _PATH_RC		"/etc/rc"
+#define _PATH_REBOOT		"/sbin/reboot"
+#define _PATH_SHUTDOWN		"/sbin/shutdown"
+#define _PATH_SINGLE		"/etc/singleboot"
+#define _PATH_SHUTDOWN_CONF	"/etc/shutdown.conf"
+
+#define _PATH_SECURE		"/etc/securesingle"
+#define _PATH_USERTTY           "/etc/usertty"
+
+#define _PATH_TERMCOLORS_DIRNAME "terminal-colors.d"
+#define _PATH_TERMCOLORS_DIR	"/etc/" _PATH_TERMCOLORS_DIRNAME
+
+/* used in login-utils/shutdown.c */
+
+/* used in login-utils/setpwnam.h and login-utils/islocal.c */
+#define _PATH_PASSWD		"/etc/passwd"
+
+/* used in login-utils/newgrp and login-utils/setpwnam.h*/
+#define _PATH_GSHADOW		"/etc/gshadow"
+
+/* used in login-utils/setpwnam.h */
+#define _PATH_GROUP		"/etc/group"
+#define _PATH_SHADOW_PASSWD	"/etc/shadow"
+#define _PATH_SHELLS		"/etc/shells"
+
+/* used in term-utils/agetty.c */
+#define _PATH_ISSUE		"/etc/issue"
+#define _PATH_OS_RELEASE	"/etc/os-release"
+#define _PATH_NUMLOCK_ON	_PATH_LOCALSTATEDIR "/numlock-on"
+
+#define _PATH_LOGINDEFS		"/etc/login.defs"
+
+/* used in misc-utils/look.c */
+#define _PATH_WORDS             "/usr/share/dict/words"
+#define _PATH_WORDS_ALT         "/usr/share/dict/web2"
+
+/* mount paths */
+#define _PATH_UMOUNT		"/bin/umount"
+
+#define _PATH_FILESYSTEMS	"/etc/filesystems"
+#define _PATH_PROC_SWAPS	"/proc/swaps"
+#define _PATH_PROC_FILESYSTEMS	"/proc/filesystems"
+#define _PATH_PROC_MOUNTS	"/proc/mounts"
+#define _PATH_PROC_PARTITIONS	"/proc/partitions"
+#define _PATH_PROC_DEVICES	"/proc/devices"
+#define _PATH_PROC_MOUNTINFO	"/proc/self/mountinfo"
+#define _PATH_PROC_LOCKS        "/proc/locks"
+#define _PATH_PROC_CDROMINFO	"/proc/sys/dev/cdrom/info"
+
+#define _PATH_PROC_UIDMAP	"/proc/self/uid_map"
+#define _PATH_PROC_GIDMAP	"/proc/self/gid_map"
+
+#define _PATH_PROC_ATTR_CURRENT	"/proc/self/attr/current"
+#define _PATH_PROC_ATTR_EXEC	"/proc/self/attr/exec"
+#define _PATH_PROC_CAPLASTCAP	"/proc/sys/kernel/cap_last_cap"
+
+
+#define _PATH_SYS_BLOCK		"/sys/block"
+#define _PATH_SYS_DEVBLOCK	"/sys/dev/block"
+#define _PATH_SYS_CLASS		"/sys/class"
+#define _PATH_SYS_SCSI		"/sys/bus/scsi"
+
+#define _PATH_SYS_SELINUX	"/sys/fs/selinux"
+#define _PATH_SYS_APPARMOR	"/sys/kernel/security/apparmor"
+
+#ifndef _PATH_MOUNTED
+# ifdef MOUNTED					/* deprecated */
+#  define _PATH_MOUNTED		MOUNTED
+# else
+#  define _PATH_MOUNTED		"/etc/mtab"
+# endif
+#endif
+
+#ifndef _PATH_MNTTAB
+# ifdef MNTTAB					/* deprecated */
+#  define _PATH_MNTTAB		MNTTAB
+# else
+#  define _PATH_MNTTAB		"/etc/fstab"
+# endif
+#endif
+
+#define _PATH_MNTTAB_DIR	_PATH_MNTTAB ".d"
+
+#define _PATH_MOUNTED_LOCK	_PATH_MOUNTED "~"
+#define _PATH_MOUNTED_TMP	_PATH_MOUNTED ".tmp"
+
+#ifndef _PATH_DEV
+  /*
+   * The tailing '/' in _PATH_DEV is there for compatibility with libc.
+   */
+# define _PATH_DEV		"/dev/"
+#endif
+
+#define _PATH_DEV_MEM		"/dev/mem"
+
+#define _PATH_DEV_LOOP		"/dev/loop"
+#define _PATH_DEV_LOOPCTL	"/dev/loop-control"
+#define _PATH_DEV_TTY		"/dev/tty"
+
+
+/* udev paths */
+#define _PATH_DEV_BYLABEL	"/dev/disk/by-label"
+#define _PATH_DEV_BYUUID	"/dev/disk/by-uuid"
+#define _PATH_DEV_BYID		"/dev/disk/by-id"
+#define _PATH_DEV_BYPATH	"/dev/disk/by-path"
+#define _PATH_DEV_BYPARTLABEL	"/dev/disk/by-partlabel"
+#define _PATH_DEV_BYPARTUUID	"/dev/disk/by-partuuid"
+
+/* hwclock paths */
+#ifdef CONFIG_ADJTIME_PATH
+# define _PATH_ADJTIME		CONFIG_ADJTIME_PATH
+#else
+# define _PATH_ADJTIME		"/etc/adjtime"
+#endif
+
+#define _PATH_LASTDATE		"/var/lib/lastdate"
+#ifdef __ia64__
+# define _PATH_RTC_DEV		"/dev/efirtc"
+#else
+# define _PATH_RTC_DEV		"/dev/rtc"
+#endif
+
+#ifndef _PATH_BTMP
+#define _PATH_BTMP		"/var/log/btmp"
+#endif
+
+/* raw paths*/
+#define _PATH_RAWDEVDIR		"/dev/raw/"
+#define _PATH_RAWDEVCTL		_PATH_RAWDEVDIR "rawctl"
+/* deprecated */
+#define _PATH_RAWDEVCTL_OLD	"/dev/rawctl"
+
+/* wdctl path */
+#define _PATH_WATCHDOG_DEV	"/dev/watchdog"
+
+/* ipc paths */
+#define _PATH_PROC_SYSV_MSG	"/proc/sysvipc/msg"
+#define _PATH_PROC_SYSV_SEM	"/proc/sysvipc/sem"
+#define _PATH_PROC_SYSV_SHM	"/proc/sysvipc/shm"
+#define _PATH_PROC_IPC_MSGMAX	"/proc/sys/kernel/msgmax"
+#define _PATH_PROC_IPC_MSGMNB	"/proc/sys/kernel/msgmnb"
+#define _PATH_PROC_IPC_MSGMNI	"/proc/sys/kernel/msgmni"
+#define _PATH_PROC_IPC_SEM	"/proc/sys/kernel/sem"
+#define _PATH_PROC_IPC_SHMALL	"/proc/sys/kernel/shmall"
+#define _PATH_PROC_IPC_SHMMAX	"/proc/sys/kernel/shmmax"
+#define _PATH_PROC_IPC_SHMMNI	"/proc/sys/kernel/shmmni"
+
+/* kernel command line */
+#define _PATH_PROC_CMDLINE	"/proc/cmdline"
+
+#endif /* PATHNAMES_H */
+
diff --git a/libblkid/include/procutils.h b/libblkid/include/procutils.h
new file mode 100644
index 0000000..14b766c
--- /dev/null
+++ b/libblkid/include/procutils.h
@@ -0,0 +1,32 @@
+#ifndef UTIL_LINUX_PROCUTILS
+#define UTIL_LINUX_PROCUTILS
+
+#include <dirent.h>
+
+struct proc_tasks {
+	DIR *dir;
+};
+
+extern struct proc_tasks *proc_open_tasks(pid_t pid);
+extern void proc_close_tasks(struct proc_tasks *tasks);
+extern int proc_next_tid(struct proc_tasks *tasks, pid_t *tid);
+
+struct proc_processes {
+	DIR *dir;
+
+	const char *fltr_name;
+	uid_t fltr_uid;
+
+	unsigned int has_fltr_name : 1,
+		     has_fltr_uid : 1;
+};
+
+extern struct proc_processes *proc_open_processes(void);
+extern void proc_close_processes(struct proc_processes *ps);
+
+extern void proc_processes_filter_by_name(struct proc_processes *ps, const char *name);
+extern void proc_processes_filter_by_uid(struct proc_processes *ps, uid_t uid);
+extern int proc_next_pid(struct proc_processes *ps, pid_t *pid);
+
+
+#endif /* UTIL_LINUX_PROCUTILS */
diff --git a/libblkid/include/pt-bsd.h b/libblkid/include/pt-bsd.h
new file mode 100644
index 0000000..9bf47a5
--- /dev/null
+++ b/libblkid/include/pt-bsd.h
@@ -0,0 +1,156 @@
+#ifndef UTIL_LINUX_PT_BSD_H
+#define UTIL_LINUX_PT_BSD_H
+
+#define BSD_MAXPARTITIONS	16
+#define BSD_FS_UNUSED		0
+
+#ifndef BSD_DISKMAGIC
+# define BSD_DISKMAGIC     ((uint32_t) 0x82564557)
+#endif
+
+#define BSD_LINUX_BOOTDIR "/usr/ucb/mdec"
+
+#if defined (__alpha__) || defined (__powerpc__) || \
+    defined (__ia64__) || defined (__hppa__)
+# define BSD_LABELSECTOR   0
+# define BSD_LABELOFFSET   64
+#else
+# define BSD_LABELSECTOR   1
+# define BSD_LABELOFFSET   0
+#endif
+
+#define	BSD_BBSIZE        8192		/* size of boot area, with label */
+#define	BSD_SBSIZE        8192		/* max size of fs superblock */
+
+struct bsd_disklabel {
+	uint32_t	d_magic;		/* the magic number */
+	int16_t		d_type;			/* drive type */
+	int16_t		d_subtype;		/* controller/d_type specific */
+	char		d_typename[16];		/* type name, e.g. "eagle" */
+	char		d_packname[16];		/* pack identifier */
+
+			/* disk geometry: */
+	uint32_t	d_secsize;		/* # of bytes per sector */
+	uint32_t	d_nsectors;		/* # of data sectors per track */
+	uint32_t	d_ntracks;		/* # of tracks per cylinder */
+	uint32_t	d_ncylinders;		/* # of data cylinders per unit */
+	uint32_t	d_secpercyl;		/* # of data sectors per cylinder */
+	uint32_t	d_secperunit;		/* # of data sectors per unit */
+
+	/*
+	 * Spares (bad sector replacements) below
+	 * are not counted in d_nsectors or d_secpercyl.
+	 * Spare sectors are assumed to be physical sectors
+	 * which occupy space at the end of each track and/or cylinder.
+	 */
+	uint16_t	d_sparespertrack;	/* # of spare sectors per track */
+	uint16_t	d_sparespercyl;		/* # of spare sectors per cylinder */
+
+	/*
+	 * Alternate cylinders include maintenance, replacement,
+	 * configuration description areas, etc.
+	 */
+	uint32_t	d_acylinders;		/* # of alt. cylinders per unit */
+
+			/* hardware characteristics: */
+	/*
+	 * d_interleave, d_trackskew and d_cylskew describe perturbations
+	 * in the media format used to compensate for a slow controller.
+	 * Interleave is physical sector interleave, set up by the formatter
+	 * or controller when formatting.  When interleaving is in use,
+	 * logically adjacent sectors are not physically contiguous,
+	 * but instead are separated by some number of sectors.
+	 * It is specified as the ratio of physical sectors traversed
+	 * per logical sector.  Thus an interleave of 1:1 implies contiguous
+	 * layout, while 2:1 implies that logical sector 0 is separated
+	 * by one sector from logical sector 1.
+	 * d_trackskew is the offset of sector 0 on track N
+	 * relative to sector 0 on track N-1 on the same cylinder.
+	 * Finally, d_cylskew is the offset of sector 0 on cylinder N
+	 * relative to sector 0 on cylinder N-1.
+	 */
+	uint16_t	d_rpm;			/* rotational speed */
+	uint16_t	d_interleave;		/* hardware sector interleave */
+	uint16_t	d_trackskew;		/* sector 0 skew, per track */
+	uint16_t	d_cylskew;		/* sector 0 skew, per cylinder */
+	uint32_t	d_headswitch;		/* head switch time, usec */
+	uint32_t	d_trkseek;		/* track-to-track seek, usec */
+	uint32_t	d_flags;		/* generic flags */
+	uint32_t	d_drivedata[5];		/* drive-type specific information */
+	uint32_t	d_spare[5];		/* reserved for future use */
+	uint32_t	d_magic2;		/* the magic number (again) */
+	uint16_t	d_checksum;		/* xor of data incl. partitions */
+
+			/* filesystem and partition information: */
+	uint16_t	d_npartitions;	        /* number of partitions in following */
+	uint32_t	d_bbsize;	        /* size of boot area at sn0, bytes */
+	uint32_t	d_sbsize;	        /* max size of fs superblock, bytes */
+
+	struct bsd_partition	 {		/* the partition table */
+		uint32_t	p_size;	        /* number of sectors in partition */
+		uint32_t	p_offset;       /* starting sector */
+		uint32_t	p_fsize;        /* filesystem basic fragment size */
+		uint8_t		p_fstype;	/* filesystem type, see below */
+		uint8_t		p_frag;		/* filesystem fragments per block */
+		uint16_t	p_cpg;	        /* filesystem cylinders per group */
+	} __attribute__((packed)) d_partitions[BSD_MAXPARTITIONS];	/* actually may be more */
+} __attribute__((packed));
+
+
+/* d_type values: */
+#define	BSD_DTYPE_SMD		1		/* SMD, XSMD; VAX hp/up */
+#define	BSD_DTYPE_MSCP		2		/* MSCP */
+#define	BSD_DTYPE_DEC		3		/* other DEC (rk, rl) */
+#define	BSD_DTYPE_SCSI		4		/* SCSI */
+#define	BSD_DTYPE_ESDI		5		/* ESDI interface */
+#define	BSD_DTYPE_ST506		6		/* ST506 etc. */
+#define	BSD_DTYPE_HPIB		7		/* CS/80 on HP-IB */
+#define BSD_DTYPE_HPFL		8		/* HP Fiber-link */
+#define	BSD_DTYPE_FLOPPY	10		/* floppy */
+
+/* d_subtype values: */
+#define BSD_DSTYPE_INDOSPART	0x8		/* is inside dos partition */
+#define BSD_DSTYPE_DOSPART(s)	((s) & 3)	/* dos partition number */
+#define BSD_DSTYPE_GEOMETRY	0x10		/* drive params in label */
+
+/*
+ * Filesystem type and version.
+ * Used to interpret other filesystem-specific
+ * per-partition information.
+ */
+#define	BSD_FS_UNUSED	0		/* unused */
+#define	BSD_FS_SWAP    	1		/* swap */
+#define	BSD_FS_V6      	2		/* Sixth Edition */
+#define	BSD_FS_V7      	3		/* Seventh Edition */
+#define	BSD_FS_SYSV    	4		/* System V */
+#define	BSD_FS_V71K    	5		/* V7 with 1K blocks (4.1, 2.9) */
+#define	BSD_FS_V8      	6		/* Eighth Edition, 4K blocks */
+#define	BSD_FS_BSDFFS	7		/* 4.2BSD fast file system */
+#define	BSD_FS_BSDLFS	9		/* 4.4BSD log-structured file system */
+#define	BSD_FS_OTHER	10		/* in use, but unknown/unsupported */
+#define	BSD_FS_HPFS	11		/* OS/2 high-performance file system */
+#define	BSD_FS_ISO9660	12		/* ISO-9660 filesystem (cdrom) */
+#define BSD_FS_ISOFS	BSD_FS_ISO9660
+#define	BSD_FS_BOOT	13		/* partition contains bootstrap */
+#define BSD_FS_ADOS	14		/* AmigaDOS fast file system */
+#define BSD_FS_HFS	15		/* Macintosh HFS */
+#define BSD_FS_ADVFS	16		/* Digital Unix AdvFS */
+
+/* this is annoying, but it's also the way it is :-( */
+#ifdef __alpha__
+#define	BSD_FS_EXT2	8		/* ext2 file system */
+#else
+#define	BSD_FS_MSDOS	8		/* MS-DOS file system */
+#endif
+
+/*
+ * flags shared by various drives:
+ */
+#define	BSD_D_REMOVABLE	0x01		/* removable media */
+#define	BSD_D_ECC      	0x02		/* supports ECC */
+#define	BSD_D_BADSECT	0x04		/* supports bad sector forw. */
+#define	BSD_D_RAMDISK	0x08		/* disk emulator */
+#define	BSD_D_CHAIN    	0x10		/* can do back-back transfers */
+#define	BSD_D_DOSPART	0x20		/* within MSDOS partition */
+
+#endif /* UTIL_LINUX_PT_BSD_H */
diff --git a/libblkid/include/pt-mbr-partnames.h b/libblkid/include/pt-mbr-partnames.h
new file mode 100644
index 0000000..282adba
--- /dev/null
+++ b/libblkid/include/pt-mbr-partnames.h
@@ -0,0 +1,105 @@
+	{0x00, N_("Empty")},
+	{0x01, N_("FAT12")},
+	{0x02, N_("XENIX root")},
+	{0x03, N_("XENIX usr")},
+	{0x04, N_("FAT16 <32M")},
+	{0x05, N_("Extended")},		/* DOS 3.3+ extended partition */
+	{0x06, N_("FAT16")},		/* DOS 16-bit >=32M */
+	{0x07, N_("HPFS/NTFS/exFAT")},	/* OS/2 IFS, eg, HPFS or NTFS or QNX or exFAT */
+	{0x08, N_("AIX")},		/* AIX boot (AIX -- PS/2 port) or SplitDrive */
+	{0x09, N_("AIX bootable")},	/* AIX data or Coherent */
+	{0x0a, N_("OS/2 Boot Manager")},/* OS/2 Boot Manager */
+	{0x0b, N_("W95 FAT32")},
+	{0x0c, N_("W95 FAT32 (LBA)")},/* LBA really is `Extended Int 13h' */
+	{0x0e, N_("W95 FAT16 (LBA)")},
+	{0x0f, N_("W95 Ext'd (LBA)")},
+	{0x10, N_("OPUS")},
+	{0x11, N_("Hidden FAT12")},
+	{0x12, N_("Compaq diagnostics")},
+	{0x14, N_("Hidden FAT16 <32M")},
+	{0x16, N_("Hidden FAT16")},
+	{0x17, N_("Hidden HPFS/NTFS")},
+	{0x18, N_("AST SmartSleep")},
+	{0x1b, N_("Hidden W95 FAT32")},
+	{0x1c, N_("Hidden W95 FAT32 (LBA)")},
+	{0x1e, N_("Hidden W95 FAT16 (LBA)")},
+	{0x24, N_("NEC DOS")},
+	{0x27, N_("Hidden NTFS WinRE")},
+	{0x39, N_("Plan 9")},
+	{0x3c, N_("PartitionMagic recovery")},
+	{0x40, N_("Venix 80286")},
+	{0x41, N_("PPC PReP Boot")},
+	{0x42, N_("SFS")},
+	{0x4d, N_("QNX4.x")},
+	{0x4e, N_("QNX4.x 2nd part")},
+	{0x4f, N_("QNX4.x 3rd part")},
+	{0x50, N_("OnTrack DM")},
+	{0x51, N_("OnTrack DM6 Aux1")},	/* (or Novell) */
+	{0x52, N_("CP/M")},		/* CP/M or Microport SysV/AT */
+	{0x53, N_("OnTrack DM6 Aux3")},
+	{0x54, N_("OnTrackDM6")},
+	{0x55, N_("EZ-Drive")},
+	{0x56, N_("Golden Bow")},
+	{0x5c, N_("Priam Edisk")},
+	{0x61, N_("SpeedStor")},
+	{0x63, N_("GNU HURD or SysV")},	/* GNU HURD or Mach or Sys V/386 (such as ISC UNIX) */
+	{0x64, N_("Novell Netware 286")},
+	{0x65, N_("Novell Netware 386")},
+	{0x70, N_("DiskSecure Multi-Boot")},
+	{0x75, N_("PC/IX")},
+	{0x80, N_("Old Minix")},	/* Minix 1.4a and earlier */
+	{0x81, N_("Minix / old Linux")},/* Minix 1.4b and later */
+	{0x82, N_("Linux swap / Solaris")},
+	{0x83, N_("Linux")},
+	{0x84, N_("OS/2 hidden C: drive")},
+	{0x85, N_("Linux extended")},
+	{0x86, N_("NTFS volume set")},
+	{0x87, N_("NTFS volume set")},
+	{0x88, N_("Linux plaintext")},
+	{0x8e, N_("Linux LVM")},
+	{0x93, N_("Amoeba")},
+	{0x94, N_("Amoeba BBT")},	/* (bad block table) */
+	{0x9f, N_("BSD/OS")},		/* BSDI */
+	{0xa0, N_("IBM Thinkpad hibernation")},
+	{0xa5, N_("FreeBSD")},		/* various BSD flavours */
+	{0xa6, N_("OpenBSD")},
+	{0xa7, N_("NeXTSTEP")},
+	{0xa8, N_("Darwin UFS")},
+	{0xa9, N_("NetBSD")},
+	{0xab, N_("Darwin boot")},
+	{0xaf, N_("HFS / HFS+")},
+	{0xb7, N_("BSDI fs")},
+	{0xb8, N_("BSDI swap")},
+	{0xbb, N_("Boot Wizard hidden")},
+	{0xbe, N_("Solaris boot")},
+	{0xbf, N_("Solaris")},
+	{0xc1, N_("DRDOS/sec (FAT-12)")},
+	{0xc4, N_("DRDOS/sec (FAT-16 < 32M)")},
+	{0xc6, N_("DRDOS/sec (FAT-16)")},
+	{0xc7, N_("Syrinx")},
+	{0xda, N_("Non-FS data")},
+	{0xdb, N_("CP/M / CTOS / ...")},/* CP/M or Concurrent CP/M or
+					   Concurrent DOS or CTOS */
+	{0xde, N_("Dell Utility")},	/* Dell PowerEdge Server utilities */
+	{0xdf, N_("BootIt")},		/* BootIt EMBRM */
+	{0xe1, N_("DOS access")},	/* DOS access or SpeedStor 12-bit FAT
+					   extended partition */
+	{0xe3, N_("DOS R/O")},		/* DOS R/O or SpeedStor */
+	{0xe4, N_("SpeedStor")},	/* SpeedStor 16-bit FAT extended
+					   partition < 1024 cyl. */
+	{0xeb, N_("BeOS fs")},
+	{0xee, N_("GPT")},		/* Intel EFI GUID Partition Table */
+	{0xef, N_("EFI (FAT-12/16/32)")},/* Intel EFI System Partition */
+	{0xf0, N_("Linux/PA-RISC boot")},/* Linux/PA-RISC boot loader */
+	{0xf1, N_("SpeedStor")},
+	{0xf4, N_("SpeedStor")},	/* SpeedStor large partition */
+	{0xf2, N_("DOS secondary")},	/* DOS 3.3+ secondary */
+	{0xfb, N_("VMware VMFS")},
+	{0xfc, N_("VMware VMKCORE")},	/* VMware kernel dump partition */
+	{0xfd, N_("Linux raid autodetect")},/* New (2.2.x) raid partition with
+					       autodetect using persistent
+					       superblock */
+	{0xfe, N_("LANstep")},		/* SpeedStor >1024 cyl. or LANstep */
+	{0xff, N_("BBT")},		/* Xenix Bad Block Table */
+
+	{ 0, 0 }
diff --git a/libblkid/include/pt-mbr.h b/libblkid/include/pt-mbr.h
new file mode 100644
index 0000000..1279e3c
--- /dev/null
+++ b/libblkid/include/pt-mbr.h
@@ -0,0 +1,178 @@
+#ifndef UTIL_LINUX_PT_MBR_H
+#define UTIL_LINUX_PT_MBR_H
+
+struct dos_partition {
+	unsigned char boot_ind;		/* 0x80 - active */
+	unsigned char bh, bs, bc;	/* begin CHS */
+	unsigned char sys_ind;
+	unsigned char eh, es, ec;	/* end CHS */
+	unsigned char start_sect[4];
+	unsigned char nr_sects[4];
+} __attribute__((packed));
+
+#define MBR_PT_OFFSET		0x1be
+
+static inline struct dos_partition *mbr_get_partition(unsigned char *mbr, int i)
+{
+	return (struct dos_partition *)
+		(mbr + MBR_PT_OFFSET + (i * sizeof(struct dos_partition)));
+}
+
+/* assemble badly aligned little endian integer */
+static inline unsigned int __dos_assemble_4le(const unsigned char *p)
+{
+	return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+}
+
+static inline void __dos_store_4le(unsigned char *p, unsigned int val)
+{
+	p[0] = (val & 0xff);
+	p[1] = ((val >> 8) & 0xff);
+	p[2] = ((val >> 16) & 0xff);
+	p[3] = ((val >> 24) & 0xff);
+}
+
+static inline unsigned int dos_partition_get_start(struct dos_partition *p)
+{
+	return __dos_assemble_4le(&(p->start_sect[0]));
+}
+
+static inline void dos_partition_set_start(struct dos_partition *p, unsigned int n)
+{
+	__dos_store_4le(p->start_sect, n);
+}
+
+static inline unsigned int dos_partition_get_size(struct dos_partition *p)
+{
+	return __dos_assemble_4le(&(p->nr_sects[0]));
+}
+
+static inline void dos_partition_set_size(struct dos_partition *p, unsigned int n)
+{
+	__dos_store_4le(p->nr_sects, n);
+}
+
+static inline int mbr_is_valid_magic(const unsigned char *mbr)
+{
+	return mbr[510] == 0x55 && mbr[511] == 0xaa ? 1 : 0;
+}
+
+static inline void mbr_set_magic(unsigned char *b)
+{
+	b[510] = 0x55;
+	b[511] = 0xaa;
+}
+
+static inline unsigned int mbr_get_id(const unsigned char *mbr)
+{
+	return __dos_assemble_4le(&mbr[440]);
+}
+
+static inline void mbr_set_id(unsigned char *b, unsigned int id)
+{
+	__dos_store_4le(&b[440], id);
+}
+
+enum {
+	MBR_EMPTY_PARTITION		= 0x00,
+	MBR_FAT12_PARTITION		= 0x01,
+	MBR_XENIX_ROOT_PARTITION	= 0x02,
+	MBR_XENIX_USR_PARTITION		= 0x03,
+	MBR_FAT16_LESS32M_PARTITION	= 0x04,
+	MBR_DOS_EXTENDED_PARTITION	= 0x05,
+	MBR_FAT16_PARTITION		= 0x06, /* DOS 16-bit >=32M */
+	MBR_HPFS_NTFS_PARTITION		= 0x07, /* OS/2 IFS, eg, HPFS or NTFS or QNX */
+	MBR_AIX_PARTITION		= 0x08, /* AIX boot (AIX -- PS/2 port) or SplitDrive */
+	MBR_AIX_BOOTABLE_PARTITION	= 0x09, /* AIX data or Coherent */
+	MBR_OS2_BOOTMNGR_PARTITION	= 0x0a, /* OS/2 Boot Manager */
+	MBR_W95_FAT32_PARTITION		= 0x0b,
+	MBR_W95_FAT32_LBA_PARTITION	= 0x0c, /* LBA really is `Extended Int 13h' */
+	MBR_W95_FAT16_LBA_PARTITION	= 0x0e,
+	MBR_W95_EXTENDED_PARTITION	= 0x0f,
+	MBR_OPUS_PARTITION		= 0x10,
+	MBR_HIDDEN_FAT12_PARTITION	= 0x11,
+	MBR_COMPAQ_DIAGNOSTICS_PARTITION = 0x12,
+	MBR_HIDDEN_FAT16_L32M_PARTITION	= 0x14,
+	MBR_HIDDEN_FAT16_PARTITION	= 0x16,
+	MBR_HIDDEN_HPFS_NTFS_PARTITION	= 0x17,
+	MBR_AST_SMARTSLEEP_PARTITION	= 0x18,
+	MBR_HIDDEN_W95_FAT32_PARTITION	= 0x1b,
+	MBR_HIDDEN_W95_FAT32LBA_PARTITION = 0x1c,
+	MBR_HIDDEN_W95_FAT16LBA_PARTITION = 0x1e,
+	MBR_NEC_DOS_PARTITION		= 0x24,
+	MBR_PLAN9_PARTITION		= 0x39,
+	MBR_PARTITIONMAGIC_PARTITION	= 0x3c,
+	MBR_VENIX80286_PARTITION	= 0x40,
+	MBR_PPC_PREP_BOOT_PARTITION	= 0x41,
+	MBR_SFS_PARTITION		= 0x42,
+	MBR_QNX_4X_PARTITION		= 0x4d,
+	MBR_QNX_4X_2ND_PARTITION	= 0x4e,
+	MBR_QNX_4X_3RD_PARTITION	= 0x4f,
+	MBR_DM_PARTITION		= 0x50,
+	MBR_DM6_AUX1_PARTITION		= 0x51, /* (or Novell) */
+	MBR_CPM_PARTITION		= 0x52, /* CP/M or Microport SysV/AT */
+	MBR_DM6_AUX3_PARTITION		= 0x53,
+	MBR_DM6_PARTITION		= 0x54,
+	MBR_EZ_DRIVE_PARTITION		= 0x55,
+	MBR_GOLDEN_BOW_PARTITION	= 0x56,
+	MBR_PRIAM_EDISK_PARTITION	= 0x5c,
+	MBR_SPEEDSTOR_PARTITION		= 0x61,
+	MBR_GNU_HURD_PARTITION		= 0x63, /* GNU HURD or Mach or Sys V/386 (such as ISC UNIX) */
+	MBR_UNIXWARE_PARTITION		= MBR_GNU_HURD_PARTITION,
+	MBR_NETWARE_286_PARTITION	= 0x64,
+	MBR_NETWARE_386_PARTITION	= 0x65,
+	MBR_DISKSECURE_MULTIBOOT_PARTITION = 0x70,
+	MBR_PC_IX_PARTITION		= 0x75,
+	MBR_OLD_MINIX_PARTITION		= 0x80, /* Minix 1.4a and earlier */
+	MBR_MINIX_PARTITION		= 0x81, /* Minix 1.4b and later */
+	MBR_LINUX_SWAP_PARTITION	= 0x82,
+	MBR_SOLARIS_X86_PARTITION	= MBR_LINUX_SWAP_PARTITION,
+	MBR_LINUX_DATA_PARTITION	= 0x83,
+	MBR_OS2_HIDDEN_DRIVE_PARTITION	= 0x84,
+	MBR_LINUX_EXTENDED_PARTITION	= 0x85,
+	MBR_NTFS_VOL_SET1_PARTITION	= 0x86,
+	MBR_NTFS_VOL_SET2_PARTITION	= 0x87,
+	MBR_LINUX_PLAINTEXT_PARTITION	= 0x88,
+	MBR_LINUX_LVM_PARTITION		= 0x8e,
+	MBR_AMOEBA_PARTITION		= 0x93,
+	MBR_AMOEBA_BBT_PARTITION	= 0x94, /* (bad block table) */
+	MBR_BSD_OS_PARTITION		= 0x9f, /* BSDI */
+	MBR_THINKPAD_HIBERNATION_PARTITION = 0xa0,
+	MBR_FREEBSD_PARTITION		= 0xa5, /* various BSD flavours */
+	MBR_OPENBSD_PARTITION		= 0xa6,
+	MBR_NEXTSTEP_PARTITION		= 0xa7,
+	MBR_DARWIN_UFS_PARTITION	= 0xa8,
+	MBR_NETBSD_PARTITION		= 0xa9,
+	MBR_DARWIN_BOOT_PARTITION	= 0xab,
+	MBR_HFS_HFS_PARTITION		= 0xaf,
+	MBR_BSDI_FS_PARTITION		= 0xb7,
+	MBR_BSDI_SWAP_PARTITION		= 0xb8,
+	MBR_BOOTWIZARD_HIDDEN_PARTITION	= 0xbb,
+	MBR_SOLARIS_BOOT_PARTITION	= 0xbe,
+	MBR_SOLARIS_PARTITION		= 0xbf,
+	MBR_DRDOS_FAT12_PARTITION	= 0xc1,
+	MBR_DRDOS_FAT16_L32M_PARTITION	= 0xc4,
+	MBR_DRDOS_FAT16_PARTITION	= 0xc6,
+	MBR_SYRINX_PARTITION		= 0xc7,
+	MBR_NONFS_DATA_PARTITION	= 0xda,
+	MBR_CPM_CTOS_PARTITION		= 0xdb, /* CP/M or Concurrent CP/M or Concurrent DOS or CTOS */
+	MBR_DELL_UTILITY_PARTITION	= 0xde, /* Dell PowerEdge Server utilities */
+	MBR_BOOTIT_PARTITION		= 0xdf, /* BootIt EMBRM */
+	MBR_DOS_ACCESS_PARTITION	= 0xe1, /* DOS access or SpeedStor 12-bit FAT extended partition */
+	MBR_DOS_RO_PARTITION		= 0xe3, /* DOS R/O or SpeedStor */
+	MBR_SPEEDSTOR_EXTENDED_PARTITION = 0xe4, /* SpeedStor 16-bit FAT extended partition < 1024 cyl. */
+	MBR_BEOS_FS_PARTITION		= 0xeb,
+	MBR_GPT_PARTITION		= 0xee, /* Intel EFI GUID Partition Table */
+	MBR_EFI_SYSTEM_PARTITION	= 0xef, /* Intel EFI System Partition */
+	MBR_LINUX_PARISC_BOOT_PARTITION	= 0xf0, /* Linux/PA-RISC boot loader */
+	MBR_SPEEDSTOR1_PARTITION	= 0xf1,
+	MBR_SPEEDSTOR2_PARTITION	= 0xf4, /* SpeedStor large partition */
+	MBR_DOS_SECONDARY_PARTITION	= 0xf2, /* DOS 3.3+ secondary */
+	MBR_VMWARE_VMFS_PARTITION	= 0xfb,
+	MBR_VMWARE_VMKCORE_PARTITION	= 0xfc, /* VMware kernel dump partition */
+	MBR_LINUX_RAID_PARTITION	= 0xfd, /* New (2.2.x) raid partition with autodetect using persistent superblock */
+	MBR_LANSTEP_PARTITION		= 0xfe, /* SpeedStor >1024 cyl. or LANstep */
+	MBR_XENIX_BBT_PARTITION		= 0xff, /* Xenix Bad Block Table */
+};
+
+#endif /* UTIL_LINUX_PT_MBR_H */
diff --git a/libblkid/include/pt-sgi.h b/libblkid/include/pt-sgi.h
new file mode 100644
index 0000000..547b37a
--- /dev/null
+++ b/libblkid/include/pt-sgi.h
@@ -0,0 +1,110 @@
+#ifndef UTIL_LINUX_PT_SUN_H
+#define UTIL_LINUX_PT_SUN_H
+
+#include <stdint.h>
+
+#define	SGI_LABEL_MAGIC		0x0be5a941
+
+#define SGI_MAXPARTITIONS	16
+#define SGI_MAXVOLUMES		15
+
+/* partition types */
+enum {
+	SGI_TYPE_VOLHDR		= 0x00,
+	SGI_TYPE_TRKREPL	= 0x01,
+	SGI_TYPE_SECREPL	= 0x02,
+	SGI_TYPE_SWAP		= 0x03,
+	SGI_TYPE_BSD		= 0x04,
+	SGI_TYPE_SYSV		= 0x05,
+	SGI_TYPE_ENTIRE_DISK	= 0x06,
+	SGI_TYPE_EFS		= 0x07,
+	SGI_TYPE_LVOL		= 0x08,
+	SGI_TYPE_RLVOL		= 0x09,
+	SGI_TYPE_XFS		= 0x0a,
+	SGI_TYPE_XFSLOG		= 0x0b,
+	SGI_TYPE_XLV		= 0x0c,
+	SGI_TYPE_XVM		= 0x0d
+};
+
+struct sgi_device_parameter {
+	unsigned char skew;
+	unsigned char gap1;
+	unsigned char gap2;
+	unsigned char sparecyl;
+
+	uint16_t pcylcount;
+	uint16_t head_vol0;
+	uint16_t ntrks;		/* tracks in cyl 0 or vol 0 */
+
+	unsigned char cmd_tag_queue_depth;
+	unsigned char unused0;
+
+	uint16_t unused1;
+	uint16_t nsect;		/* sectors/tracks in cyl 0 or vol 0 */
+	uint16_t bytes;
+	uint16_t ilfact;
+	uint32_t flags;		/* SGI_DEVPARAM_* controller flags */
+	uint32_t datarate;
+	uint32_t retries_on_error;
+	uint32_t ms_per_word;
+	uint16_t xylogics_gap1;
+	uint16_t xylogics_syncdelay;
+	uint16_t xylogics_readdelay;
+	uint16_t xylogics_gap2;
+	uint16_t xylogics_readgate;
+	uint16_t xylogics_writecont;
+} __attribute__((packed));
+
+enum {
+	SGI_DEVPARAM_SECTOR_SLIP	= 0x01,
+	SGI_DEVPARAM_SECTOR_FWD		= 0x02,
+	SGI_DEVPARAM_TRACK_FWD		= 0x04,
+	SGI_DEVPARAM_TRACK_MULTIVOL	= 0x08,
+	SGI_DEVPARAM_IGNORE_ERRORS	= 0x10,
+	SGI_DEVPARAM_RESEEK		= 0x20,
+	SGI_DEVPARAM_CMDTAGQ_ENABLE	= 0x40
+};
+
+
+struct sgi_disklabel {
+	uint32_t magic;			/* magic number */
+	uint16_t root_part_num;		/* # root partition */
+	uint16_t swap_part_num;		/* # swap partition */
+	unsigned char boot_file[16];	/* name of boot file */
+
+	struct sgi_device_parameter	devparam;	/* not used now */
+
+	struct sgi_volume {
+		unsigned char name[8];	/* name of volume */
+		uint32_t block_num;	/* logical block number */
+		uint32_t num_bytes;	/* how big, in bytes */
+	} __attribute__((packed)) volume[SGI_MAXVOLUMES];
+
+	struct sgi_partition {
+		uint32_t num_blocks;	/* size in logical blocks */
+		uint32_t first_block;	/* first logical block */
+		uint32_t type;		/* type of this partition */
+	} __attribute__((packed)) partitions[SGI_MAXPARTITIONS];
+
+	/* checksum is the 32bit 2's complement sum of the disklabel */
+	uint32_t csum;			/* disk label checksum */
+	uint32_t padding;		/* padding */
+} __attribute__((packed));
+
+static inline uint32_t sgi_pt_checksum(struct sgi_disklabel *label)
+{
+	int i;
+	uint32_t *ptr = (uint32_t *) label;
+	uint32_t sum = 0;
+
+	i = sizeof(*label) / sizeof(*ptr);
+
+	while (i) {
+		i--;
+		sum -= be32_to_cpu(ptr[i]);
+	}
+
+	return sum;
+}
+
+#endif /* UTIL_LINUX_PT_SUN_H */
diff --git a/libblkid/include/pt-sun.h b/libblkid/include/pt-sun.h
new file mode 100644
index 0000000..b085268
--- /dev/null
+++ b/libblkid/include/pt-sun.h
@@ -0,0 +1,90 @@
+#ifndef UTIL_LINUX_PT_SUN_H
+#define UTIL_LINUX_PT_SUN_H
+
+#include <stdint.h>
+
+#define SUN_LABEL_MAGIC		0xDABE
+
+/* Supported VTOC setting */
+#define SUN_VTOC_SANITY		0x600DDEEE	/* magic number */
+#define SUN_VTOC_VERSION	1
+#define SUN_MAXPARTITIONS	8
+
+struct sun_disklabel {
+	unsigned char label_id[128];   /* Informative text string */
+
+	struct sun_vtoc {
+		uint32_t version;     /* version */
+		char	 volume_id[8];/* volume name */
+		uint16_t nparts;      /* num of partitions */
+
+		struct sun_info {        /* partition information */
+			uint16_t id;     /* SUN_TAG_*  */
+			uint16_t flags;  /* SUN_FLAG_* */
+		} __attribute__ ((packed)) infos[8];
+
+		uint16_t padding;      /* padding */
+		uint32_t bootinfo[3];  /* info needed by mboot */
+		uint32_t sanity;       /* magic number */
+		uint32_t reserved[10]; /* padding */
+		uint32_t timestamp[8]; /* partition timestamp */
+	} __attribute__ ((packed)) vtoc;
+
+	uint32_t write_reinstruct;     /* sectors to skip, writes */
+	uint32_t read_reinstruct;      /* sectors to skip, reads */
+	unsigned char spare[148];      /* padding */
+	uint16_t rpm;                  /* disk rotational speed */
+	uint16_t pcyl;                 /* physical cylinder count */
+	uint16_t apc;                  /* extra sects per cylinder */
+	uint16_t obs1;
+	uint16_t obs2;
+	uint16_t intrlv;               /* interleave factor */
+	uint16_t ncyl;                 /* data cylinder count */
+	uint16_t acyl;                 /* alt. cylinder count */
+	uint16_t nhead;                /* tracks per cylinder   <---- */
+	uint16_t nsect;                /* sectors per track     <---- */
+	uint16_t obs3;
+	uint16_t obs4;
+
+	struct sun_partition {         /* partitions */
+		uint32_t start_cylinder;
+		uint32_t num_sectors;
+	} __attribute__ ((packed)) partitions[8];
+
+	uint16_t magic;                /* magic number */
+	uint16_t csum;                 /* label xor'd checksum */
+} __attribute__ ((packed));
+
+
+#define SUN_TAG_UNASSIGNED	0x00	/* Unassigned partition */
+#define SUN_TAG_BOOT		0x01	/* Boot partition	*/
+#define SUN_TAG_ROOT		0x02	/* Root filesystem	*/
+#define SUN_TAG_SWAP		0x03	/* Swap partition	*/
+#define SUN_TAG_USR		0x04	/* /usr filesystem	*/
+#define SUN_TAG_WHOLEDISK	0x05	/* Full-disk slice	*/
+#define SUN_TAG_STAND		0x06	/* Stand partition	*/
+#define SUN_TAG_VAR		0x07	/* /var filesystem	*/
+#define SUN_TAG_HOME		0x08	/* /home filesystem	*/
+#define SUN_TAG_ALTSCTR		0x09	/* Alt sector partition	*/
+#define SUN_TAG_CACHE		0x0a	/* Cachefs partition	*/
+#define SUN_TAG_RESERVED	0x0b	/* SMI reserved data	*/
+#define SUN_TAG_LINUX_SWAP	0x82	/* Linux SWAP		*/
+#define SUN_TAG_LINUX_NATIVE	0x83	/* Linux filesystem	*/
+#define SUN_TAG_LINUX_LVM	0x8e	/* Linux LVM		*/
+#define SUN_TAG_LINUX_RAID	0xfd	/* LInux RAID		*/
+
+#define SUN_FLAG_UNMNT		0x01	/* Unmountable partition*/
+#define SUN_FLAG_RONLY		0x10	/* Read only		*/
+
+static inline uint16_t sun_pt_checksum(struct sun_disklabel *label)
+{
+	uint16_t *ptr = ((uint16_t *) (label + 1)) - 1;
+	uint16_t sum;
+
+	for (sum = 0; ptr >= ((uint16_t *) label);)
+		sum ^= *ptr--;
+
+	return sum;
+}
+
+#endif /* UTIL_LINUX_PT_SUN_H */
diff --git a/libblkid/include/randutils.h b/libblkid/include/randutils.h
new file mode 100644
index 0000000..17e2a02
--- /dev/null
+++ b/libblkid/include/randutils.h
@@ -0,0 +1,13 @@
+#ifndef UTIL_LINUX_RANDUTILS
+#define UTIL_LINUX_RANDUTILS
+
+#ifdef HAVE_SRANDOM
+#define srand(x)	srandom(x)
+#define rand()		random()
+#endif
+
+extern int random_get_fd(void);
+extern void random_get_bytes(void *buf, size_t nbytes);
+extern const char *random_tell_source(void);
+
+#endif
diff --git a/libblkid/include/readutmp.h b/libblkid/include/readutmp.h
new file mode 100644
index 0000000..93251ea
--- /dev/null
+++ b/libblkid/include/readutmp.h
@@ -0,0 +1,28 @@
+/* Declarations for GNU's read utmp module.
+
+   Copyright (C) 1992-2007, 2009-2014 Free Software Foundation, Inc.
+
+   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
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by jla; revised by djm */
+
+#ifndef READUTMP_H
+#define READUTMP_H
+
+#include <sys/types.h>
+#include <utmp.h>
+
+int read_utmp (char const *file, size_t *n_entries, struct utmp **utmp_buf);
+
+#endif /* READUTMP_H */
diff --git a/libblkid/include/rpmatch.h b/libblkid/include/rpmatch.h
new file mode 100644
index 0000000..d62634b
--- /dev/null
+++ b/libblkid/include/rpmatch.h
@@ -0,0 +1,9 @@
+#ifndef UTIL_LINUX_RPMATCH_H
+#define UTIL_LINUX_RPMATCH_H
+
+#ifndef HAVE_RPMATCH
+#define rpmatch(r) \
+	(*r == 'y' || *r == 'Y' ? 1 : *r == 'n' || *r == 'N' ? 0 : -1)
+#endif
+
+#endif /* UTIL_LINUX_RPMATCH_H */
diff --git a/libblkid/include/setproctitle.h b/libblkid/include/setproctitle.h
new file mode 100644
index 0000000..70a9efa
--- /dev/null
+++ b/libblkid/include/setproctitle.h
@@ -0,0 +1,7 @@
+#ifndef UTIL_LINUX_SETPROCTITLE_H
+#define UTIL_LINUX_SETPROCTITLE_H
+
+extern void initproctitle (int argc, char **argv);
+extern void setproctitle (const char *prog, const char *txt);
+
+#endif
diff --git a/libblkid/include/statfs_magic.h b/libblkid/include/statfs_magic.h
new file mode 100644
index 0000000..7397a4e
--- /dev/null
+++ b/libblkid/include/statfs_magic.h
@@ -0,0 +1,99 @@
+#ifndef UTIL_LINUX_STATFS_MAGIC_H
+#define UTIL_LINUX_STATFS_MAGIC_H
+
+#include <sys/statfs.h>
+
+/*
+ * If possible then don't depend on internal libc __SWORD_TYPE type.
+ */
+#ifdef __GNUC__
+#define F_TYPE_EQUAL(a, b) (a == (__typeof__(a)) b)
+#else
+#define F_TYPE_EQUAL(a, b) (a == (__SWORD_TYPE) b)
+#endif
+
+/*
+ *  Unfortunately, Linux kernel hedeader file <linux/magic.h> is incomplete
+ *  mess and kernel returns by statfs f_type many numbers that are nowhere
+ *  specified (in API).
+ *
+ *  This is collection of the magic numbers.
+ */
+#define STATFS_ADFS_MAGIC	0xadf5
+#define STATFS_AFFS_MAGIC	0xadff
+#define STATFS_AFS_MAGIC	0x5346414F
+#define STATFS_AUTOFS_MAGIC	0x0187
+#define STATFS_BDEVFS_MAGIC	0x62646576
+#define STATFS_BEFS_MAGIC	0x42465331
+#define STATFS_BFS_MAGIC	0x1BADFACE
+#define STATFS_BINFMTFS_MAGIC	0x42494e4d
+#define STATFS_BTRFS_MAGIC	0x9123683E
+#define STATFS_CEPH_MAGIC	0x00c36400
+#define STATFS_CGROUP_MAGIC	0x27e0eb
+#define STATFS_CIFS_MAGIC	0xff534d42
+#define STATFS_CODA_MAGIC	0x73757245
+#define STATFS_CONFIGFS_MAGIC	0x62656570
+#define STATFS_CRAMFS_MAGIC	0x28cd3d45
+#define STATFS_DEBUGFS_MAGIC	0x64626720
+#define STATFS_DEVPTS_MAGIC	0x1cd1
+#define STATFS_ECRYPTFS_MAGIC	0xf15f
+#define STATFS_EFIVARFS_MAGIC	0xde5e81e4
+#define STATFS_EFS_MAGIC	0x414A53
+#define STATFS_EXOFS_MAGIC	0x5DF5
+#define STATFS_EXT2_MAGIC	0xEF53
+#define STATFS_EXT3_MAGIC	0xEF53
+#define STATFS_EXT4_MAGIC	0xEF53
+#define STATFS_F2FS_MAGIC	0xF2F52010
+#define STATFS_FUSE_MAGIC	0x65735546
+#define STATFS_FUTEXFS_MAGIC	0xBAD1DEA
+#define STATFS_GFS2_MAGIC	0x01161970
+#define STATFS_HFSPLUS_MAGIC	0x482b
+#define STATFS_HOSTFS_MAGIC	0x00c0ffee
+#define STATFS_HPFS_MAGIC	0xf995e849
+#define STATFS_HPPFS_MAGIC	0xb00000ee
+#define STATFS_HUGETLBFS_MAGIC	0x958458f6
+#define STATFS_ISOFS_MAGIC	0x9660
+#define STATFS_JFFS2_MAGIC	0x72b6
+#define STATFS_JFS_MAGIC	0x3153464a
+#define STATFS_LOGFS_MAGIC	0xc97e8168
+#define STATFS_MINIX2_MAGIC	0x2468
+#define STATFS_MINIX2_MAGIC2	0x2478
+#define STATFS_MINIX3_MAGIC	0x4d5a
+#define STATFS_MINIX_MAGIC	0x137F
+#define STATFS_MINIX_MAGIC2	0x138F
+#define STATFS_MQUEUE_MAGIC	0x19800202
+#define STATFS_MSDOS_MAGIC	0x4d44
+#define STATFS_NCP_MAGIC	0x564c
+#define STATFS_NFS_MAGIC	0x6969
+#define STATFS_NILFS_MAGIC	0x3434
+#define STATFS_NTFS_MAGIC	0x5346544e
+#define STATFS_OCFS2_MAGIC	0x7461636f
+#define STATFS_OMFS_MAGIC	0xC2993D87
+#define STATFS_OPENPROMFS_MAGIC	0x9fa1
+#define STATFS_PIPEFS_MAGIC	0x50495045
+#define STATFS_PROC_MAGIC	0x9fa0
+#define STATFS_PSTOREFS_MAGIC	0x6165676C
+#define STATFS_QNX4_MAGIC	0x002f
+#define STATFS_QNX6_MAGIC	0x68191122
+#define STATFS_RAMFS_MAGIC	0x858458f6
+#define STATFS_REISERFS_MAGIC	0x52654973
+#define STATFS_ROMFS_MAGIC	0x7275
+#define STATFS_SECURITYFS_MAGIC	0x73636673
+#define STATFS_SELINUXFS_MAGIC	0xf97cff8c
+#define STATFS_SMACKFS_MAGIC	0x43415d53
+#define STATFS_SMB_MAGIC	0x517B
+#define STATFS_SOCKFS_MAGIC	0x534F434B
+#define STATFS_SQUASHFS_MAGIC	0x73717368
+#define STATFS_SYSFS_MAGIC	0x62656572
+#define STATFS_TMPFS_MAGIC	0x01021994
+#define STATFS_UBIFS_MAGIC	0x24051905
+#define STATFS_UDF_MAGIC	0x15013346
+#define STATFS_UFS2_MAGIC	0x19540119
+#define STATFS_UFS_MAGIC	0x00011954
+#define STATFS_V9FS_MAGIC	0x01021997
+#define STATFS_VXFS_MAGIC	0xa501FCF5
+#define STATFS_XENFS_MAGIC	0xabba1974
+#define STATFS_XFS_MAGIC	0x58465342
+
+#endif /* UTIL_LINUX_STATFS_MAGIC_H */
+
diff --git a/libblkid/include/strutils.h b/libblkid/include/strutils.h
new file mode 100644
index 0000000..4d8463a
--- /dev/null
+++ b/libblkid/include/strutils.h
@@ -0,0 +1,204 @@
+#ifndef UTIL_LINUX_STRUTILS
+#define UTIL_LINUX_STRUTILS
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+/* default strtoxx_or_err() exit code */
+#ifndef STRTOXX_EXIT_CODE
+# define STRTOXX_EXIT_CODE EXIT_FAILURE
+#endif
+
+
+extern int parse_size(const char *str, uintmax_t *res, int *power);
+extern int strtosize(const char *str, uintmax_t *res);
+extern uintmax_t strtosize_or_err(const char *str, const char *errmesg);
+
+extern int16_t strtos16_or_err(const char *str, const char *errmesg);
+extern uint16_t strtou16_or_err(const char *str, const char *errmesg);
+
+extern int32_t strtos32_or_err(const char *str, const char *errmesg);
+extern uint32_t strtou32_or_err(const char *str, const char *errmesg);
+
+extern int64_t strtos64_or_err(const char *str, const char *errmesg);
+extern uint64_t strtou64_or_err(const char *str, const char *errmesg);
+
+extern double strtod_or_err(const char *str, const char *errmesg);
+
+extern long strtol_or_err(const char *str, const char *errmesg);
+extern unsigned long strtoul_or_err(const char *str, const char *errmesg);
+
+extern void strtotimeval_or_err(const char *str, struct timeval *tv,
+		const char *errmesg);
+
+extern int isdigit_string(const char *str);
+
+#ifndef HAVE_MEMPCPY
+extern void *mempcpy(void *restrict dest, const void *restrict src, size_t n);
+#endif
+#ifndef HAVE_STRNLEN
+extern size_t strnlen(const char *s, size_t maxlen);
+#endif
+#ifndef HAVE_STRNDUP
+extern char *strndup(const char *s, size_t n);
+#endif
+#ifndef HAVE_STRNCHR
+extern char *strnchr(const char *s, size_t maxlen, int c);
+#endif
+
+/* caller guarantees n > 0 */
+static inline void xstrncpy(char *dest, const char *src, size_t n)
+{
+	strncpy(dest, src, n-1);
+	dest[n-1] = 0;
+}
+
+static inline char *strdup_to_offset(void *stru, size_t offset, const char *str)
+{
+	char *n = NULL;
+	char **o = (char **) ((char *) stru + offset);
+
+	if (str) {
+		n = strdup(str);
+		if (!n)
+			return NULL;
+	}
+
+	free(*o);
+	*o = n;
+	return n;
+}
+
+#define strdup_to_struct_member(_s, _m, _str) \
+		strdup_to_offset((void *) _s, offsetof(__typeof__(*(_s)), _m), _str)
+
+extern void strmode(mode_t mode, char *str);
+
+/* Options for size_to_human_string() */
+enum
+{
+        SIZE_SUFFIX_1LETTER = 0,
+        SIZE_SUFFIX_3LETTER = 1,
+        SIZE_SUFFIX_SPACE   = 2
+};
+
+extern char *size_to_human_string(int options, uint64_t bytes);
+
+extern int string_to_idarray(const char *list, int ary[], size_t arysz,
+			   int (name2id)(const char *, size_t));
+extern int string_add_to_idarray(const char *list, int ary[],
+				 size_t arysz, int *ary_pos,
+				 int (name2id)(const char *, size_t));
+
+extern int string_to_bitarray(const char *list, char *ary,
+			    int (*name2bit)(const char *, size_t));
+
+extern int string_to_bitmask(const char *list,
+			     unsigned long *mask,
+			     long (*name2flag)(const char *, size_t));
+extern int parse_range(const char *str, int *lower, int *upper, int def);
+
+extern int streq_except_trailing_slash(const char *s1, const char *s2);
+
+/*
+ * Match string beginning.
+ */
+static inline const char *startswith(const char *s, const char *prefix)
+{
+	size_t sz = prefix ? strlen(prefix) : 0;
+
+        if (s && sz && strncmp(s, prefix, sz) == 0)
+                return s + sz;
+	return NULL;
+}
+
+/*
+ * Case insensitive match string beginning.
+ */
+static inline const char *startswith_no_case(const char *s, const char *prefix)
+{
+	size_t sz = prefix ? strlen(prefix) : 0;
+
+        if (s && sz && strncasecmp(s, prefix, sz) == 0)
+                return s + sz;
+	return NULL;
+}
+
+/*
+ * Match string ending.
+ */
+static inline const char *endswith(const char *s, const char *postfix)
+{
+	size_t sl = s ? strlen(s) : 0;
+	size_t pl = postfix ? strlen(postfix) : 0;
+
+	if (pl == 0)
+		return (char *)s + sl;
+	if (sl < pl)
+		return NULL;
+	if (memcmp(s + sl - pl, postfix, pl) != 0)
+		return NULL;
+	return (char *)s + sl - pl;
+}
+
+/*
+ * Skip leading white space.
+ */
+static inline const char *skip_space(const char *p)
+{
+	while (isspace(*p))
+		++p;
+	return p;
+}
+
+static inline const char *skip_blank(const char *p)
+{
+	while (isblank(*p))
+		++p;
+	return p;
+}
+
+
+/* Removes whitespace from the right-hand side of a string (trailing
+ * whitespace).
+ *
+ * Returns size of the new string (without \0).
+ */
+static inline size_t rtrim_whitespace(unsigned char *str)
+{
+	size_t i = strlen((char *) str);
+
+	while (i) {
+		i--;
+		if (!isspace(str[i])) {
+			i++;
+			break;
+		}
+	}
+	str[i] = '\0';
+	return i;
+}
+
+/* Removes whitespace from the left-hand side of a string.
+ *
+ * Returns size of the new string (without \0).
+ */
+static inline size_t ltrim_whitespace(unsigned char *str)
+{
+	size_t len;
+	unsigned char *p;
+
+	for (p = str; p && isspace(*p); p++);
+
+	len = strlen((char *) p);
+
+	if (len && p > str)
+		memmove(str, p, len + 1);
+
+	return len;
+}
+
+#endif
diff --git a/libblkid/include/swapheader.h b/libblkid/include/swapheader.h
new file mode 100644
index 0000000..3fce0d0
--- /dev/null
+++ b/libblkid/include/swapheader.h
@@ -0,0 +1,23 @@
+#ifndef _SWAPHEADER_H
+#define _SWAPHEADER_H
+
+#define SWAP_VERSION 1
+#define SWAP_UUID_LENGTH 16
+#define SWAP_LABEL_LENGTH 16
+#define SWAP_SIGNATURE "SWAPSPACE2"
+#define SWAP_SIGNATURE_SZ (sizeof(SWAP_SIGNATURE) - 1)
+
+#include <stdint.h>
+
+struct swap_header_v1_2 {
+	char	      bootbits[1024];    /* Space for disklabel etc. */
+	uint32_t      version;
+	uint32_t      last_page;
+	uint32_t      nr_badpages;
+	unsigned char uuid[SWAP_UUID_LENGTH];
+	char	      volume_name[SWAP_LABEL_LENGTH];
+	uint32_t      padding[117];
+	uint32_t      badpages[1];
+};
+
+#endif /* _SWAPHEADER_H */
diff --git a/libblkid/include/swapprober.h b/libblkid/include/swapprober.h
new file mode 100644
index 0000000..5107700
--- /dev/null
+++ b/libblkid/include/swapprober.h
@@ -0,0 +1,9 @@
+#ifndef UTIL_LINUX_SWAP_PROBER_H
+#define UTIL_LINUX_SWAP_PROBER_H
+
+#include <blkid.h>
+
+blkid_probe get_swap_prober(const char *devname);
+
+#endif /* UTIL_LINUX_SWAP_PROBER_H */
+
diff --git a/libblkid/include/sysfs.h b/libblkid/include/sysfs.h
new file mode 100644
index 0000000..1de624a
--- /dev/null
+++ b/libblkid/include/sysfs.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ */
+#ifndef UTIL_LINUX_SYSFS_H
+#define UTIL_LINUX_SYSFS_H
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <inttypes.h>
+#include <dirent.h>
+
+struct sysfs_cxt {
+	dev_t	devno;
+	int	dir_fd;		/* /sys/block/<name> */
+	char	*dir_path;
+	struct sysfs_cxt *parent;
+
+	unsigned int	scsi_host,
+			scsi_channel,
+			scsi_target,
+			scsi_lun;
+
+	unsigned int	has_hctl : 1;
+};
+
+#define UL_SYSFSCXT_EMPTY { 0, -1, NULL, NULL, 0, 0, 0, 0, 0 }
+
+extern char *sysfs_devno_attribute_path(dev_t devno, char *buf,
+                                 size_t bufsiz, const char *attr);
+extern int sysfs_devno_has_attribute(dev_t devno, const char *attr);
+extern char *sysfs_devno_path(dev_t devno, char *buf, size_t bufsiz);
+extern char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz);
+extern dev_t sysfs_devname_to_devno(const char *name, const char *parent);
+
+extern int sysfs_init(struct sysfs_cxt *cxt, dev_t devno, struct sysfs_cxt *parent)
+					__attribute__ ((warn_unused_result));
+extern void sysfs_deinit(struct sysfs_cxt *cxt);
+
+extern DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr);
+
+extern int sysfs_stat(struct sysfs_cxt *cxt, const char *attr, struct stat *st);
+extern ssize_t sysfs_readlink(struct sysfs_cxt *cxt, const char *attr,
+	                   char *buf, size_t bufsiz);
+extern int sysfs_has_attribute(struct sysfs_cxt *cxt, const char *attr);
+
+extern int sysfs_scanf(struct sysfs_cxt *cxt,  const char *attr,
+		       const char *fmt, ...)
+		        __attribute__ ((format (scanf, 3, 4)));
+
+extern int sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr, int64_t *res);
+extern int sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t *res);
+extern int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res);
+
+extern int sysfs_write_string(struct sysfs_cxt *cxt, const char *attr, const char *str);
+extern int sysfs_write_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t num);
+
+extern char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz);
+
+extern char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr);
+
+extern int sysfs_count_dirents(struct sysfs_cxt *cxt, const char *attr);
+extern int sysfs_count_partitions(struct sysfs_cxt *cxt, const char *devname);
+extern dev_t sysfs_partno_to_devno(struct sysfs_cxt *cxt, int partno);
+extern char *sysfs_get_slave(struct sysfs_cxt *cxt);
+
+extern char *sysfs_get_devchain(struct sysfs_cxt *cxt, char *buf, size_t bufsz);
+extern int sysfs_next_subsystem(struct sysfs_cxt *cxt, char *devchain, char **subsys);
+extern int sysfs_is_hotpluggable(struct sysfs_cxt *cxt);
+
+extern int sysfs_is_partition_dirent(DIR *dir, struct dirent *d,
+			const char *parent_name);
+
+extern int sysfs_devno_to_wholedisk(dev_t dev, char *diskname,
+            size_t len, dev_t *diskdevno);
+
+extern int sysfs_devno_is_lvm_private(dev_t devno);
+extern int sysfs_devno_is_wholedisk(dev_t devno);
+
+extern int sysfs_scsi_get_hctl(struct sysfs_cxt *cxt, int *h,
+			       int *c, int *t, int *l);
+extern char *sysfs_scsi_host_strdup_attribute(struct sysfs_cxt *cxt,
+                const char *type, const char *attr);
+extern int sysfs_scsi_host_is(struct sysfs_cxt *cxt, const char *type);
+extern int sysfs_scsi_has_attribute(struct sysfs_cxt *cxt, const char *attr);
+extern int sysfs_scsi_path_contains(struct sysfs_cxt *cxt, const char *pattern);
+
+#endif /* UTIL_LINUX_SYSFS_H */
diff --git a/libblkid/include/timer.h b/libblkid/include/timer.h
new file mode 100644
index 0000000..79ef649
--- /dev/null
+++ b/libblkid/include/timer.h
@@ -0,0 +1,31 @@
+#ifndef UTIL_LINUX_TIMER_H
+#define UTIL_LINUX_TIMER_H
+
+#include <signal.h>
+#include <sys/time.h>
+
+static inline int setup_timer(
+			struct itimerval *timer,
+			struct itimerval *old_timer,
+			struct sigaction *old_sa,
+			void (*timeout_handler)(int))
+{
+	struct sigaction sa;
+
+	memset(&sa, 0, sizeof sa);
+	sa.sa_handler = timeout_handler;
+	sa.sa_flags = SA_RESETHAND;
+	sigaction(SIGALRM, &sa, old_sa);
+
+	return setitimer(ITIMER_REAL, timer, old_timer);
+}
+
+static inline void cancel_timer(
+			struct itimerval *old_timer,
+			struct sigaction *old_sa)
+{
+	setitimer(ITIMER_REAL, old_timer, NULL);
+	sigaction(SIGALRM, old_sa, NULL);
+}
+
+#endif
diff --git a/libblkid/include/timeutils.h b/libblkid/include/timeutils.h
new file mode 100644
index 0000000..8ed501b
--- /dev/null
+++ b/libblkid/include/timeutils.h
@@ -0,0 +1,56 @@
+/***
+  First set of functions in this file are part of systemd, and were
+  copied to util-linux at August 2013.
+
+  Copyright 2010 Lennart Poettering
+  Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+#ifndef UTIL_LINUX_TIME_UTIL_H
+#define UTIL_LINUX_TIME_UTIL_H
+
+#include <stdio.h>
+#include <inttypes.h>
+
+typedef uint64_t usec_t;
+typedef uint64_t nsec_t;
+
+#define MSEC_PER_SEC  1000ULL
+#define USEC_PER_SEC  1000000ULL
+#define USEC_PER_MSEC 1000ULL
+#define NSEC_PER_SEC  1000000000ULL
+#define NSEC_PER_MSEC 1000000ULL
+#define NSEC_PER_USEC 1000ULL
+
+#define USEC_PER_MINUTE	(60ULL*USEC_PER_SEC)
+#define NSEC_PER_MINUTE	(60ULL*NSEC_PER_SEC)
+#define USEC_PER_HOUR	(60ULL*USEC_PER_MINUTE)
+#define NSEC_PER_HOUR	(60ULL*NSEC_PER_MINUTE)
+#define USEC_PER_DAY	(24ULL*USEC_PER_HOUR)
+#define NSEC_PER_DAY	(24ULL*NSEC_PER_HOUR)
+#define USEC_PER_WEEK	(7ULL*USEC_PER_DAY)
+#define NSEC_PER_WEEK	(7ULL*NSEC_PER_DAY)
+#define USEC_PER_MONTH	(2629800ULL*USEC_PER_SEC)
+#define NSEC_PER_MONTH	(2629800ULL*NSEC_PER_SEC)
+#define USEC_PER_YEAR	(31557600ULL*USEC_PER_SEC)
+#define NSEC_PER_YEAR	(31557600ULL*NSEC_PER_SEC)
+
+#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1)	/* weekdays can be unicode */
+#define FORMAT_TIMESTAMP_RELATIVE_MAX 256
+#define FORMAT_TIMESPAN_MAX 64
+
+int parse_timestamp(const char *t, usec_t *usec);
+
+#endif /* UTIL_LINUX_TIME_UTIL_H */
diff --git a/libblkid/include/ttyutils.h b/libblkid/include/ttyutils.h
new file mode 100644
index 0000000..e842f9f
--- /dev/null
+++ b/libblkid/include/ttyutils.h
@@ -0,0 +1,168 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#ifndef UTIL_LINUX_TTYUTILS_H
+#define UTIL_LINUX_TTYUTILS_H
+
+#include <stdlib.h>
+#include <termios.h>
+#include <limits.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_TTYDEFAULTS_H
+#include <sys/ttydefaults.h>
+#endif
+
+/* Some shorthands for control characters. */
+#define CTL(x)		((x) ^ 0100)	/* Assumes ASCII dialect */
+#define CR		CTL('M')	/* carriage return */
+#define NL		CTL('J')	/* line feed */
+#define BS		CTL('H')	/* back space */
+#define DEL		CTL('?')	/* delete */
+
+/* Defaults for line-editing etc. characters; you may want to change these. */
+#define DEF_ERASE	DEL		/* default erase character */
+#define DEF_INTR	CTL('C')	/* default interrupt character */
+#define DEF_QUIT	CTL('\\')	/* default quit char */
+#define DEF_KILL	CTL('U')	/* default kill char */
+#define DEF_EOF		CTL('D')	/* default EOF char */
+#define DEF_EOL		0
+#define DEF_SWITCH	0		/* default switch char */
+
+/* Storage for things detected while the login name was read. */
+struct chardata {
+	int erase;		/* erase character */
+	int kill;		/* kill character */
+	int eol;		/* end-of-line character */
+	int parity;		/* what parity did we see */
+	int capslock;		/* upper case without lower case */
+};
+
+#define INIT_CHARDATA(ptr) do {              \
+		(ptr)->erase    = DEF_ERASE; \
+		(ptr)->kill     = DEF_KILL;  \
+		(ptr)->eol      = CTRL('r'); \
+	        (ptr)->parity   = 0;         \
+	        (ptr)->capslock = 0;         \
+	} while (0)
+
+extern int get_terminal_width(void);
+extern int get_terminal_name(int fd, const char **path, const char **name,
+			     const char **number);
+
+#define UL_TTY_KEEPCFLAGS	(1 << 1)
+#define UL_TTY_UTF8		(1 << 2)
+
+static inline void reset_virtual_console(struct termios *tp, int flags)
+{
+	/* Use defaults of <sys/ttydefaults.h> for base settings */
+	tp->c_iflag |= TTYDEF_IFLAG;
+	tp->c_oflag |= TTYDEF_OFLAG;
+	tp->c_lflag |= TTYDEF_LFLAG;
+
+	if ((flags & UL_TTY_KEEPCFLAGS) == 0) {
+#ifdef CBAUD
+		tp->c_lflag &= ~CBAUD;
+#endif
+		tp->c_cflag |= (B38400 | TTYDEF_CFLAG);
+	}
+
+	/* Sane setting, allow eight bit characters, no carriage return delay
+	 * the same result as `stty sane cr0 pass8'
+	 */
+#ifndef IUCLC
+# define IUCLC 0
+#endif
+#ifndef NL0
+# define NL0 0
+#endif
+#ifndef CR0
+# define CR0 0
+#endif
+#ifndef BS0
+# define BS0 0
+#endif
+#ifndef VT0
+# define VT0 0
+#endif
+#ifndef FF0
+# define FF0 0
+#endif
+#ifndef OLCUC
+# define OLCUC 0
+#endif
+#ifndef OFILL
+# define OFILL 0
+#endif
+#ifndef NLDLY
+# define NLDLY 0
+#endif
+#ifndef CRDLY
+# define CRDLY 0
+#endif
+#ifndef BSDLY
+# define BSDLY 0
+#endif
+#ifndef VTDLY
+# define VTDLY 0
+#endif
+#ifndef FFDLY
+# define FFDLY 0
+#endif
+
+	tp->c_iflag |=  (BRKINT | ICRNL | IMAXBEL);
+	tp->c_iflag &= ~(IGNBRK | INLCR | IGNCR | IXOFF | IUCLC | IXANY | ISTRIP);
+	tp->c_oflag |=  (OPOST | ONLCR | NL0 | CR0 | TAB0 | BS0 | VT0 | FF0);
+	tp->c_oflag &= ~(OLCUC | OCRNL | ONOCR | ONLRET | OFILL | \
+			    NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
+	tp->c_lflag |=  (ISIG | ICANON | IEXTEN | ECHO|ECHOE|ECHOK|ECHOKE|ECHOCTL);
+	tp->c_lflag &= ~(ECHONL|ECHOPRT | NOFLSH | TOSTOP);
+
+	if ((flags & UL_TTY_KEEPCFLAGS) == 0) {
+		tp->c_cflag |=  (CREAD | CS8 | HUPCL);
+		tp->c_cflag &= ~(PARODD | PARENB);
+	}
+#ifdef OFDEL
+	tp->c_oflag &= ~OFDEL;
+#endif
+#ifdef XCASE
+	tp->c_lflag &= ~XCASE;
+#endif
+#ifdef IUTF8
+	if (flags & UL_TTY_UTF8)
+		tp->c_iflag |= IUTF8;	    /* Set UTF-8 input flag */
+	else
+		tp->c_iflag &= ~IUTF8;
+#endif
+	/* VTIME and VMIN can overlap with VEOF and VEOL since they are
+	 * only used for non-canonical mode. We just set the at the
+	 * beginning, so nothing bad should happen.
+	 */
+	tp->c_cc[VTIME]    = 0;
+	tp->c_cc[VMIN]     = 1;
+	tp->c_cc[VINTR]    = CINTR;
+	tp->c_cc[VQUIT]    = CQUIT;
+	tp->c_cc[VERASE]   = CERASE; /* ASCII DEL (0177) */
+	tp->c_cc[VKILL]    = CKILL;
+	tp->c_cc[VEOF]     = CEOF;
+#ifdef VSWTC
+	tp->c_cc[VSWTC]    = _POSIX_VDISABLE;
+#elif defined(VSWTCH)
+	tp->c_cc[VSWTCH]   = _POSIX_VDISABLE;
+#endif
+	tp->c_cc[VSTART]   = CSTART;
+	tp->c_cc[VSTOP]    = CSTOP;
+	tp->c_cc[VSUSP]    = CSUSP;
+	tp->c_cc[VEOL]     = _POSIX_VDISABLE;
+	tp->c_cc[VREPRINT] = CREPRINT;
+	tp->c_cc[VDISCARD] = CDISCARD;
+	tp->c_cc[VWERASE]  = CWERASE;
+	tp->c_cc[VLNEXT]   = CLNEXT;
+	tp->c_cc[VEOL2]    = _POSIX_VDISABLE;
+}
+
+#endif /* UTIL_LINUX_TTYUTILS_H */
diff --git a/libblkid/include/widechar.h b/libblkid/include/widechar.h
new file mode 100644
index 0000000..b023b5f
--- /dev/null
+++ b/libblkid/include/widechar.h
@@ -0,0 +1,38 @@
+/* Declarations for wide characters */
+/* This file must be included last because the redefinition of wchar_t may
+   cause conflicts when system include files were included after it. */
+
+#ifdef HAVE_WIDECHAR
+
+# include <wchar.h>
+# include <wctype.h>
+
+#else /* !HAVE_WIDECHAR */
+
+# include <ctype.h>
+  /* Fallback for types */
+# define wchar_t char
+# define wint_t int
+# define WEOF EOF
+  /* Fallback for input operations */
+# define fgetwc fgetc
+# define getwc getc
+# define getwchar getchar
+# define fgetws fgets
+  /* Fallback for output operations */
+# define fputwc fputc
+# define putwc putc
+# define putwchar putchar
+# define fputws fputs
+  /* Fallback for character classification */
+# define iswgraph isgraph
+# define iswprint isprint
+# define iswspace isspace
+  /* Fallback for string functions */
+# define wcschr strchr
+# define wcsdup strdup
+# define wcslen strlen
+
+# define wcwidth(c) 1
+
+#endif /* HAVE_WIDECHAR */
diff --git a/libblkid/include/xalloc.h b/libblkid/include/xalloc.h
new file mode 100644
index 0000000..f012fb2
--- /dev/null
+++ b/libblkid/include/xalloc.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * General memory allocation wrappers for malloc, realloc, calloc and strdup
+ */
+
+#ifndef UTIL_LINUX_XALLOC_H
+#define UTIL_LINUX_XALLOC_H
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "c.h"
+
+#ifndef XALLOC_EXIT_CODE
+# define XALLOC_EXIT_CODE EXIT_FAILURE
+#endif
+
+static inline __ul_alloc_size(1)
+void *xmalloc(const size_t size)
+{
+        void *ret = malloc(size);
+
+        if (!ret && size)
+                err(XALLOC_EXIT_CODE, "cannot allocate %zu bytes", size);
+        return ret;
+}
+
+static inline __ul_alloc_size(2)
+void *xrealloc(void *ptr, const size_t size)
+{
+        void *ret = realloc(ptr, size);
+
+        if (!ret && size)
+                err(XALLOC_EXIT_CODE, "cannot allocate %zu bytes", size);
+        return ret;
+}
+
+static inline __ul_calloc_size(1, 2)
+void *xcalloc(const size_t nelems, const size_t size)
+{
+        void *ret = calloc(nelems, size);
+
+        if (!ret && size && nelems)
+                err(XALLOC_EXIT_CODE, "cannot allocate %zu bytes", size);
+        return ret;
+}
+
+static inline char __attribute__((warn_unused_result)) *xstrdup(const char *str)
+{
+        char *ret;
+
+        if (!str)
+                return NULL;
+
+        ret = strdup(str);
+
+        if (!ret)
+                err(XALLOC_EXIT_CODE, "cannot duplicate string");
+        return ret;
+}
+
+static inline char * __attribute__((warn_unused_result)) xstrndup(const char *str, size_t size)
+{
+        char *ret;
+
+        if (!str)
+                return NULL;
+
+        ret = strndup(str, size);
+
+        if (!ret)
+                err(XALLOC_EXIT_CODE, "cannot duplicate string");
+        return ret;
+}
+
+
+static inline int __attribute__ ((__format__(printf, 2, 3)))
+    xasprintf(char **strp, const char *fmt, ...)
+{
+	int ret;
+	va_list args;
+	va_start(args, fmt);
+	ret = vasprintf(&(*strp), fmt, args);
+	va_end(args);
+	if (ret < 0)
+		err(XALLOC_EXIT_CODE, "cannot allocate string");
+	return ret;
+}
+
+static inline int xvasprintf(char **strp, const char *fmt, va_list ap)
+{
+	int ret = vasprintf(&(*strp), fmt, ap);
+	if (ret < 0)
+		err(XALLOC_EXIT_CODE, "cannot allocate string");
+	return ret;
+}
+
+
+static inline char * __attribute__((warn_unused_result)) xgethostname(void)
+{
+	char *name;
+	size_t sz = get_hostname_max() + 1;
+
+	name = xmalloc(sizeof(char) * sz);
+
+	if (gethostname(name, sz) != 0) {
+		free(name);
+		return NULL;
+	}
+	name[sz - 1] = '\0';
+	return name;
+}
+
+#endif
diff --git a/libblkid/lib/Makemodule.am b/libblkid/lib/Makemodule.am
new file mode 100644
index 0000000..565294e
--- /dev/null
+++ b/libblkid/lib/Makemodule.am
@@ -0,0 +1,115 @@
+
+noinst_LTLIBRARIES += libcommon.la
+libcommon_la_CFLAGS = $(AM_CFLAGS)
+libcommon_la_SOURCES = \
+	lib/at.c \
+	lib/blkdev.c \
+	lib/canonicalize.c \
+	lib/colors.c \
+	lib/crc32.c \
+	lib/crc64.c \
+	lib/env.c \
+	lib/fileutils.c \
+	lib/ismounted.c \
+	lib/mangle.c \
+	lib/match.c \
+	lib/mbsalign.c \
+	lib/md5.c \
+	lib/pager.c \
+	lib/path.c \
+	lib/procutils.c \
+	lib/randutils.c \
+	lib/setproctitle.c \
+	lib/strutils.c \
+	lib/sysfs.c \
+	lib/timeutils.c \
+	lib/ttyutils.c \
+	lib/exec_shell.c \
+	lib/readutmp.c
+
+if LINUX
+libcommon_la_SOURCES += \
+	lib/linux_version.c \
+	lib/loopdev.c
+endif
+
+if !HAVE_LANGINFO
+libcommon_la_SOURCES += lib/langinfo.c
+endif
+
+if HAVE_CPU_SET_T
+libcommon_la_SOURCES += lib/cpuset.c
+endif
+
+dist_man_MANS += lib/terminal-colors.d.5
+
+check_PROGRAMS += \
+	test_at \
+	test_blkdev \
+	test_canonicalize \
+	test_colors \
+	test_fileutils \
+	test_ismounted \
+	test_mangle \
+	test_procutils \
+	test_randutils \
+	test_strutils \
+	test_ttyutils
+
+if LINUX
+if HAVE_CPU_SET_T
+check_PROGRAMS += test_cpuset
+endif
+check_PROGRAMS += \
+	test_sysfs \
+	test_pager
+endif
+
+test_ttyutils_SOURCES = lib/ttyutils.c
+test_ttyutils_CFLAGS = -DTEST_PROGRAM
+test_ttyutils_LDADD = libcommon.la
+
+test_blkdev_SOURCES = lib/blkdev.c
+test_blkdev_CFLAGS = -DTEST_PROGRAM_BLKDEV
+test_blkdev_LDADD = libcommon.la
+
+test_ismounted_SOURCES = lib/ismounted.c
+test_ismounted_CFLAGS = -DTEST_PROGRAM
+test_ismounted_LDADD = libcommon.la
+
+test_mangle_SOURCES = lib/mangle.c
+test_mangle_CFLAGS = -DTEST_PROGRAM
+
+test_at_SOURCES = lib/at.c
+test_at_CFLAGS = -DTEST_PROGRAM_AT
+
+test_strutils_SOURCES = lib/strutils.c
+test_strutils_CFLAGS = -DTEST_PROGRAM
+
+test_colors_SOURCES = lib/colors.c
+test_colors_CFLAGS = -DTEST_PROGRAM
+
+test_randutils_SOURCES = lib/randutils.c
+test_randutils_CFLAGS = -DTEST_PROGRAM
+
+test_procutils_SOURCES = lib/procutils.c lib/at.c
+test_procutils_CFLAGS = -DTEST_PROGRAM
+
+if LINUX
+test_cpuset_SOURCES = lib/cpuset.c
+test_cpuset_CFLAGS = -DTEST_PROGRAM
+
+test_sysfs_SOURCES = lib/sysfs.c
+test_sysfs_CFLAGS = -DTEST_PROGRAM_SYSFS
+test_sysfs_LDADD = libcommon.la
+
+test_pager_SOURCES = lib/pager.c
+test_pager_CFLAGS = -DTEST_PROGRAM
+endif
+
+test_fileutils_SOURCES = lib/fileutils.c
+test_fileutils_CFLAGS = -DTEST_PROGRAM
+
+test_canonicalize_SOURCES = lib/canonicalize.c
+test_canonicalize_CFLAGS = -DTEST_PROGRAM_CANONICALIZE
+
diff --git a/libblkid/lib/at.c b/libblkid/lib/at.c
new file mode 100644
index 0000000..f8bfe13
--- /dev/null
+++ b/libblkid/lib/at.c
@@ -0,0 +1,143 @@
+/*
+ * Portable xxxat() functions.
+ *
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "at.h"
+#include "c.h"
+
+#ifdef HAVE_FSTATAT
+int fstat_at(int dir, const char *dirname __attribute__ ((__unused__)),
+	     const char *filename, struct stat *st, int nofollow)
+{
+	return fstatat(dir, filename, st,
+			nofollow ? AT_SYMLINK_NOFOLLOW : 0);
+}
+#else
+int fstat_at(int dir, const char *dirname, const char *filename,
+				struct stat *st, int nofollow)
+{
+
+	if (*filename != '/') {
+		char path[PATH_MAX];
+		int len;
+
+		len = snprintf(path, sizeof(path), "%s/%s", dirname, filename);
+		if (len < 0 || len + 1 > sizeof(path))
+			return -1;
+
+		return nofollow ? lstat(path, st) : stat(path, st);
+	}
+
+	return nofollow ? lstat(filename, st) : stat(filename, st);
+}
+#endif
+
+#ifdef HAVE_FSTATAT
+int open_at(int dir, const char *dirname __attribute__ ((__unused__)),
+	    const char *filename, int flags)
+{
+	return openat(dir, filename, flags);
+}
+#else
+int open_at(int dir, const char *dirname, const char *filename, int flags)
+{
+	if (*filename != '/') {
+		char path[PATH_MAX];
+		int len;
+
+		len = snprintf(path, sizeof(path), "%s/%s", dirname, filename);
+		if (len < 0 || len + 1 > sizeof(path))
+			return -1;
+
+		return open(path, flags);
+	}
+	return open(filename, flags);
+}
+#endif
+
+FILE *fopen_at(int dir, const char *dirname, const char *filename, int flags,
+			const char *mode)
+{
+	int fd = open_at(dir, dirname, filename, flags);
+
+	if (fd < 0)
+		return NULL;
+
+	return fdopen(fd, mode);
+}
+
+#ifdef HAVE_FSTATAT
+ssize_t readlink_at(int dir, const char *dirname __attribute__ ((__unused__)),
+		    const char *pathname, char *buf, size_t bufsiz)
+{
+	return readlinkat(dir, pathname, buf, bufsiz);
+}
+#else
+ssize_t readlink_at(int dir, const char *dirname, const char *pathname,
+		    char *buf, size_t bufsiz)
+{
+	if (*pathname != '/') {
+		char path[PATH_MAX];
+		int len;
+
+		len = snprintf(path, sizeof(path), "%s/%s", dirname, pathname);
+		if (len < 0 || len + 1 > sizeof(path))
+			return -1;
+
+		return readlink(path, buf, bufsiz);
+	}
+	return readlink(pathname, buf, bufsiz);
+}
+#endif
+
+#ifdef TEST_PROGRAM_AT
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+
+int main(int argc, char *argv[])
+{
+	DIR *dir;
+	struct dirent *d;
+	char *dirname;
+
+	if (argc != 2) {
+		fprintf(stderr, "usage: %s <directory>\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+	dirname = argv[1];
+
+	dir = opendir(dirname);
+	if (!dir)
+		err(EXIT_FAILURE, "cannot open %s", dirname);
+
+	while ((d = readdir(dir))) {
+		struct stat st;
+		FILE *f;
+
+		printf("%32s ", d->d_name);
+
+		if (fstat_at(dirfd(dir), dirname, d->d_name, &st, 0) == 0)
+			printf("%16jd bytes ", st.st_size);
+		else
+			printf("%16s bytes ", "???");
+
+		f = fopen_at(dirfd(dir), dirname, d->d_name, O_RDONLY, "r");
+		printf("   %s\n", f ? "OK" : strerror(errno));
+		if (f)
+			fclose(f);
+	}
+	closedir(dir);
+	return EXIT_SUCCESS;
+}
+#endif
diff --git a/libblkid/lib/blkdev.c b/libblkid/lib/blkdev.c
new file mode 100644
index 0000000..a293529
--- /dev/null
+++ b/libblkid/lib/blkdev.c
@@ -0,0 +1,378 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#ifdef HAVE_LINUX_FD_H
+#include <linux/fd.h>
+#endif
+
+#ifdef HAVE_SYS_DISKLABEL_H
+#include <sys/disklabel.h>
+#endif
+
+#ifdef HAVE_SYS_DISK_H
+#ifdef HAVE_SYS_QUEUE_H
+#include <sys/queue.h> /* for LIST_HEAD */
+#endif
+#include <sys/disk.h>
+#endif
+
+#ifdef __FreeBSD_kernel__
+#include <sys/disk.h>
+#endif
+
+#include "blkdev.h"
+#include "c.h"
+#include "linux_version.h"
+
+static long
+blkdev_valid_offset (int fd, off_t offset) {
+	char ch;
+
+	if (lseek (fd, offset, 0) < 0)
+		return 0;
+	if (read (fd, &ch, 1) < 1)
+		return 0;
+	return 1;
+}
+
+int is_blkdev(int fd)
+{
+	struct stat st;
+	return (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode));
+}
+
+off_t
+blkdev_find_size (int fd) {
+	uintmax_t high, low = 0;
+
+	for (high = 1024; blkdev_valid_offset (fd, high); ) {
+		if (high == UINTMAX_MAX)
+			return -1;
+
+		low = high;
+
+		if (high >= UINTMAX_MAX/2)
+			high = UINTMAX_MAX;
+		else
+			high *= 2;
+	}
+
+	while (low < high - 1)
+	{
+		uintmax_t mid = (low + high) / 2;
+
+		if (blkdev_valid_offset (fd, mid))
+			low = mid;
+		else
+			high = mid;
+	}
+	blkdev_valid_offset (fd, 0);
+	return (low + 1);
+}
+
+/* get size in bytes */
+int
+blkdev_get_size(int fd, unsigned long long *bytes)
+{
+#ifdef DKIOCGETBLOCKCOUNT
+	/* Apple Darwin */
+	if (ioctl(fd, DKIOCGETBLOCKCOUNT, bytes) >= 0) {
+		*bytes <<= 9;
+		return 0;
+	}
+#endif
+
+#ifdef BLKGETSIZE64
+	{
+#ifdef __linux__
+		int ver = get_linux_version();
+
+		/* kernels 2.4.15-2.4.17, had a broken BLKGETSIZE64 */
+		if (ver >= KERNEL_VERSION (2,6,0) ||
+		   (ver >= KERNEL_VERSION (2,4,18) && ver < KERNEL_VERSION (2,5,0)))
+#endif
+			if (ioctl(fd, BLKGETSIZE64, bytes) >= 0)
+				return 0;
+	}
+#endif /* BLKGETSIZE64 */
+
+#ifdef BLKGETSIZE
+	{
+		unsigned long size;
+
+		if (ioctl(fd, BLKGETSIZE, &size) >= 0) {
+			*bytes = ((unsigned long long)size << 9);
+			return 0;
+		}
+	}
+
+#endif /* BLKGETSIZE */
+
+#ifdef DIOCGMEDIASIZE
+	/* FreeBSD */
+	if (ioctl(fd, DIOCGMEDIASIZE, bytes) >= 0)
+		return 0;
+#endif
+
+#ifdef FDGETPRM
+	{
+		struct floppy_struct this_floppy;
+
+		if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) {
+			*bytes = this_floppy.size << 9;
+			return 0;
+		}
+	}
+#endif /* FDGETPRM */
+
+#ifdef HAVE_SYS_DISKLABEL_H
+	{
+		/*
+		 * This code works for FreeBSD 4.11 i386, except for the full device
+		 * (such as /dev/ad0). It doesn't work properly for newer FreeBSD
+		 * though. FreeBSD >= 5.0 should be covered by the DIOCGMEDIASIZE
+		 * above however.
+		 *
+		 * Note that FreeBSD >= 4.0 has disk devices as unbuffered (raw,
+		 * character) devices, so we need to check for S_ISCHR, too.
+		 */
+		int part = -1;
+		struct disklabel lab;
+		struct partition *pp;
+		char ch;
+		struct stat st;
+
+		if ((fstat(fd, &st) >= 0) &&
+		    (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)))
+			part = st.st_rdev & 7;
+
+		if (part >= 0 && (ioctl(fd, DIOCGDINFO, (char *)&lab) >= 0)) {
+			pp = &lab.d_partitions[part];
+			if (pp->p_size) {
+				 *bytes = pp->p_size << 9;
+				 return 0;
+			}
+		}
+	}
+#endif /* HAVE_SYS_DISKLABEL_H */
+
+	{
+		struct stat st;
+
+		if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) {
+			*bytes = st.st_size;
+			return 0;
+		}
+		if (!S_ISBLK(st.st_mode))
+			return -1;
+	}
+
+	*bytes = blkdev_find_size(fd);
+	return 0;
+}
+
+/* get 512-byte sector count */
+int
+blkdev_get_sectors(int fd, unsigned long long *sectors)
+{
+	unsigned long long bytes;
+
+	if (blkdev_get_size(fd, &bytes) == 0) {
+		*sectors = (bytes >> 9);
+		return 0;
+	}
+
+	return -1;
+}
+
+/*
+ * Get logical sector size.
+ *
+ * This is the smallest unit the storage device can
+ * address. It is typically 512 bytes.
+ */
+int blkdev_get_sector_size(int fd, int *sector_size)
+{
+#ifdef BLKSSZGET
+	if (ioctl(fd, BLKSSZGET, sector_size) >= 0)
+		return 0;
+	return -1;
+#else
+	*sector_size = DEFAULT_SECTOR_SIZE;
+	return 0;
+#endif
+}
+
+/*
+ * Get physical block device size. The BLKPBSZGET is supported since Linux
+ * 2.6.32. For old kernels is probably the best to assume that physical sector
+ * size is the same as logical sector size.
+ *
+ * Example:
+ *
+ * rc = blkdev_get_physector_size(fd, &physec);
+ * if (rc || physec == 0) {
+ *	rc = blkdev_get_sector_size(fd, &physec);
+ *	if (rc)
+ *		physec = DEFAULT_SECTOR_SIZE;
+ * }
+ */
+int blkdev_get_physector_size(int fd, int *sector_size)
+{
+#ifdef BLKPBSZGET
+	if (ioctl(fd, BLKPBSZGET, &sector_size) >= 0)
+		return 0;
+	return -1;
+#else
+	*sector_size = DEFAULT_SECTOR_SIZE;
+	return 0;
+#endif
+}
+
+/*
+ * Return the alignment status of a device
+ */
+int blkdev_is_misaligned(int fd)
+{
+#ifdef BLKALIGNOFF
+	int aligned;
+
+	if (ioctl(fd, BLKALIGNOFF, &aligned) < 0)
+		return 0;			/* probably kernel < 2.6.32 */
+	/*
+	 * Note that kernel returns -1 as alignement offset if no compatible
+	 * sizes and alignments exist for stacked devices
+	 */
+	return aligned != 0 ? 1 : 0;
+#else
+	return 0;
+#endif
+}
+
+int blkdev_is_cdrom(int fd)
+{
+#ifdef CDROM_GET_CAPABILITY
+	int ret;
+
+	if ((ret = ioctl(fd, CDROM_GET_CAPABILITY, NULL)) < 0)
+		return 0;
+	else
+		return ret;
+#else
+	return 0;
+#endif
+}
+
+/*
+ * Get kernel's interpretation of the device's geometry.
+ *
+ * Returns the heads and sectors - but not cylinders
+ * as it's truncated for disks with more than 65535 tracks.
+ *
+ * Note that this is deprecated in favor of LBA addressing.
+ */
+int blkdev_get_geometry(int fd, unsigned int *h, unsigned int *s)
+{
+#ifdef HDIO_GETGEO
+	struct hd_geometry geometry;
+
+	if (ioctl(fd, HDIO_GETGEO, &geometry) == 0) {
+		*h = geometry.heads;
+		*s = geometry.sectors;
+		return 0;
+	}
+#else
+	*h = 0;
+	*s = 0;
+#endif
+	return -1;
+}
+
+/*
+ * Convert scsi type to human readable string.
+ */
+const char *blkdev_scsi_type_to_name(int type)
+{
+	switch (type) {
+	case SCSI_TYPE_DISK:
+		return "disk";
+	case SCSI_TYPE_TAPE:
+		return "tape";
+	case SCSI_TYPE_PRINTER:
+		return "printer";
+	case SCSI_TYPE_PROCESSOR:
+		return "processor";
+	case SCSI_TYPE_WORM:
+		return "worm";
+	case SCSI_TYPE_ROM:
+		return "rom";
+	case SCSI_TYPE_SCANNER:
+		return "scanner";
+	case SCSI_TYPE_MOD:
+		return "mo-disk";
+	case SCSI_TYPE_MEDIUM_CHANGER:
+		return "changer";
+	case SCSI_TYPE_COMM:
+		return "comm";
+	case SCSI_TYPE_RAID:
+		return "raid";
+	case SCSI_TYPE_ENCLOSURE:
+		return "enclosure";
+	case SCSI_TYPE_RBC:
+		return "rbc";
+	case SCSI_TYPE_OSD:
+		return "osd";
+	case SCSI_TYPE_NO_LUN:
+		return "no-lun";
+	default:
+		break;
+	}
+	return NULL;
+}
+
+#ifdef TEST_PROGRAM_BLKDEV
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+int
+main(int argc, char **argv)
+{
+	unsigned long long bytes;
+	unsigned long long sectors;
+	int sector_size, phy_sector_size;
+	int fd;
+
+	if (argc != 2) {
+		fprintf(stderr, "usage: %s device\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	if ((fd = open(argv[1], O_RDONLY|O_CLOEXEC)) < 0)
+		err(EXIT_FAILURE, "open %s failed", argv[1]);
+
+	if (blkdev_get_size(fd, &bytes) < 0)
+		err(EXIT_FAILURE, "blkdev_get_size() failed");
+	if (blkdev_get_sectors(fd, &sectors) < 0)
+		err(EXIT_FAILURE, "blkdev_get_sectors() failed");
+	if (blkdev_get_sector_size(fd, &sector_size) < 0)
+		err(EXIT_FAILURE, "blkdev_get_sector_size() failed");
+	if (blkdev_get_physector_size(fd, &phy_sector_size) < 0)
+		err(EXIT_FAILURE, "blkdev_get_physector_size() failed");
+
+	printf("          bytes: %llu\n", bytes);
+	printf("        sectors: %llu\n", sectors);
+	printf("    sector size: %d\n", sector_size);
+	printf("phy-sector size: %d\n", phy_sector_size);
+
+	return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/libblkid/lib/canonicalize.c b/libblkid/lib/canonicalize.c
new file mode 100644
index 0000000..303703b
--- /dev/null
+++ b/libblkid/lib/canonicalize.c
@@ -0,0 +1,145 @@
+/*
+ * canonicalize.c -- canonicalize pathname by removing symlinks
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2009-2013 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "canonicalize.h"
+
+/*
+ * Converts private "dm-N" names to "/dev/mapper/<name>"
+ *
+ * Since 2.6.29 (patch 784aae735d9b0bba3f8b9faef4c8b30df3bf0128) kernel sysfs
+ * provides the real DM device names in /sys/block/<ptname>/dm/name
+ */
+char *canonicalize_dm_name(const char *ptname)
+{
+	FILE	*f;
+	size_t	sz;
+	char	path[256], name[256], *res = NULL;
+
+	if (!ptname || !*ptname)
+		return NULL;
+
+	snprintf(path, sizeof(path), "/sys/block/%s/dm/name", ptname);
+	if (!(f = fopen(path, "r")))
+		return NULL;
+
+	/* read "<name>\n" from sysfs */
+	if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) {
+		name[sz - 1] = '\0';
+		snprintf(path, sizeof(path), "/dev/mapper/%s", name);
+
+		if (access(path, F_OK) == 0)
+			res = strdup(path);
+	}
+	fclose(f);
+	return res;
+}
+
+static int is_dm_devname(char *canonical, char **name)
+{
+	struct stat sb;
+	char *p = strrchr(canonical, '/');
+
+	*name = NULL;
+
+	if (!p
+	    || strncmp(p, "/dm-", 4) != 0
+	    || !isdigit(*(p + 4))
+	    || stat(canonical, &sb) != 0
+	    || !S_ISBLK(sb.st_mode))
+		return 0;
+
+	*name = p + 1;
+	return 1;
+}
+
+char *canonicalize_path(const char *path)
+{
+	char *canonical, *dmname;
+
+	if (!path || !*path)
+		return NULL;
+
+	canonical = realpath(path, NULL);
+	if (!canonical)
+		return strdup(path);
+
+	if (is_dm_devname(canonical, &dmname)) {
+		char *dm = canonicalize_dm_name(dmname);
+		if (dm) {
+			free(canonical);
+			return dm;
+		}
+	}
+
+	return canonical;
+}
+
+char *canonicalize_path_restricted(const char *path)
+{
+	char *canonical, *dmname;
+	int errsv;
+	uid_t euid;
+	gid_t egid;
+
+	if (!path || !*path)
+		return NULL;
+
+	euid = geteuid();
+	egid = getegid();
+
+	/* drop permissions */
+	if (setegid(getgid()) < 0 || seteuid(getuid()) < 0)
+		return NULL;
+
+	errsv = errno = 0;
+
+	canonical = realpath(path, NULL);
+	if (!canonical)
+		errsv = errno;
+	else if (is_dm_devname(canonical, &dmname)) {
+		char *dm = canonicalize_dm_name(dmname);
+		if (dm) {
+			free(canonical);
+			canonical = dm;
+		}
+	}
+
+	/* restore */
+	if (setegid(egid) < 0 || seteuid(euid) < 0) {
+		free(canonical);
+		return NULL;
+	}
+
+	errno = errsv;
+	return canonical;
+}
+
+
+#ifdef TEST_PROGRAM_CANONICALIZE
+int main(int argc, char **argv)
+{
+	if (argc < 2) {
+		fprintf(stderr, "usage: %s <device>\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	fprintf(stdout, "orig: %s\n", argv[1]);
+	fprintf(stdout, "real: %s\n", canonicalize_path(argv[1]));
+
+	exit(EXIT_SUCCESS);
+}
+#endif
diff --git a/libblkid/lib/colors.c b/libblkid/lib/colors.c
new file mode 100644
index 0000000..6f79ac4
--- /dev/null
+++ b/libblkid/lib/colors.c
@@ -0,0 +1,877 @@
+/*
+ * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <assert.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#include "c.h"
+#include "colors.h"
+#include "pathnames.h"
+#include "strutils.h"
+
+#include "debug.h"
+
+/*
+ * terminal-colors.d debug stuff
+ */
+UL_DEBUG_DEFINE_MASK(termcolors);
+UL_DEBUG_DEFINE_MASKNAMES(termcolors) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define TERMCOLORS_DEBUG_INIT	(1 << 1)
+#define TERMCOLORS_DEBUG_CONF	(1 << 2)
+#define TERMCOLORS_DEBUG_SCHEME	(1 << 3)
+#define TERMCOLORS_DEBUG_ALL	0xFFFF
+
+#define DBG(m, x)       __UL_DBG(termcolors, TERMCOLORS_DEBUG_, m, x)
+#define ON_DBG(m, x)    __UL_DBG_CALL(termcolors, TERMCOLORS_DEBUG_, m, x)
+
+/*
+ * terminal-colors.d file types
+ */
+enum {
+	UL_COLORFILE_DISABLE,		/* .disable */
+	UL_COLORFILE_ENABLE,		/* .enable */
+	UL_COLORFILE_SCHEME,		/* .scheme */
+
+	__UL_COLORFILE_COUNT
+};
+
+struct ul_color_scheme {
+	char *name;
+	char *seq;
+};
+
+/*
+ * Global colors control struct
+ *
+ * The terminal-colors.d/ evaluation is based on "scores":
+ *
+ *  filename                    score
+ *  ---------------------------------------
+ *  type			1
+ *  @termname.type		10 + 1
+ *  utilname.type		20 + 1
+ *  utilname@termname.type	20 + 10 + 1
+ *
+ * the match with higher score wins. The score is per type.
+ */
+struct ul_color_ctl {
+	const char	*utilname;	/* util name */
+	const char	*termname;	/* terminal name ($TERM) */
+
+	char		*sfile;	/* path to scheme */
+
+	struct ul_color_scheme	*schemes;	/* array with color schemes */
+	size_t			nschemes;	/* number of the items */
+	size_t			schemes_sz;	/* number of the allocated items */
+
+	int		mode;		/* UL_COLORMODE_* */
+	unsigned int	has_colors : 1,	/* based on mode and scores[] */
+			disabled   : 1, /* disable colors */
+			cs_configured : 1, /* color schemes read */
+			configured : 1; /* terminal-colors.d parsed */
+
+	int		scores[__UL_COLORFILE_COUNT];	/* the best match */
+};
+
+/*
+ * Control struct, globally shared.
+ */
+static struct ul_color_ctl ul_colors;
+
+static void colors_free_schemes(struct ul_color_ctl *cc);
+static int colors_read_schemes(struct ul_color_ctl *cc);
+
+/*
+ * qsort/bsearch buddy
+ */
+static int cmp_scheme_name(const void *a0, const void *b0)
+{
+	struct ul_color_scheme	*a = (struct ul_color_scheme *) a0,
+				*b = (struct ul_color_scheme *) b0;
+	return strcmp(a->name, b->name);
+}
+
+/*
+ * Maintains human readable color names
+ */
+const char *color_sequence_from_colorname(const char *str)
+{
+	static const struct ul_color_scheme basic_schemes[] = {
+		{ "black",	UL_COLOR_BLACK           },
+		{ "blue",	UL_COLOR_BLUE            },
+		{ "brown",	UL_COLOR_BROWN           },
+		{ "cyan",	UL_COLOR_CYAN            },
+		{ "darkgray",	UL_COLOR_DARK_GRAY       },
+		{ "gray",	UL_COLOR_GRAY            },
+		{ "green",	UL_COLOR_GREEN           },
+		{ "lightblue",	UL_COLOR_BOLD_BLUE       },
+		{ "lightcyan",	UL_COLOR_BOLD_CYAN       },
+		{ "lightgray,",	UL_COLOR_GRAY            },
+		{ "lightgreen", UL_COLOR_BOLD_GREEN      },
+		{ "lightmagenta", UL_COLOR_BOLD_MAGENTA  },
+		{ "lightred",	UL_COLOR_BOLD_RED        },
+		{ "magenta",	UL_COLOR_MAGENTA         },
+		{ "red",	UL_COLOR_RED             },
+		{ "yellow",	UL_COLOR_BOLD_YELLOW     },
+	};
+	struct ul_color_scheme key = { .name = (char *) str }, *res;
+
+	if (!str)
+		return NULL;
+
+	res = bsearch(&key, basic_schemes, ARRAY_SIZE(basic_schemes),
+				sizeof(struct ul_color_scheme),
+				cmp_scheme_name);
+	return res ? res->seq : NULL;
+}
+
+
+/*
+ * Resets control struct (note that we don't allocate the struct)
+ */
+static void colors_reset(struct ul_color_ctl *cc)
+{
+	if (!cc)
+		return;
+
+	colors_free_schemes(cc);
+
+	free(cc->sfile);
+
+	cc->sfile = NULL;
+	cc->utilname = NULL;
+	cc->termname = NULL;
+	cc->mode = UL_COLORMODE_UNDEF;
+
+	memset(cc->scores, 0, sizeof(cc->scores));
+}
+
+static void colors_debug(struct ul_color_ctl *cc)
+{
+	size_t i;
+
+	if (!cc)
+		return;
+
+	printf("Colors:\n");
+	printf("\tutilname = '%s'\n", cc->utilname);
+	printf("\ttermname = '%s'\n", cc->termname);
+	printf("\tscheme file = '%s'\n", cc->sfile);
+	printf("\tmode = %s\n",
+			cc->mode == UL_COLORMODE_UNDEF ? "undefined" :
+			cc->mode == UL_COLORMODE_AUTO ?  "auto" :
+			cc->mode == UL_COLORMODE_NEVER ? "never" :
+			cc->mode == UL_COLORMODE_ALWAYS ? "always" : "???");
+	printf("\thas_colors = %d\n", cc->has_colors);
+	printf("\tdisabled = %d\n", cc->disabled);
+	printf("\tconfigured = %d\n", cc->configured);
+	printf("\tcs configured = %d\n", cc->cs_configured);
+
+	fputc('\n', stdout);
+
+	for (i = 0; i < ARRAY_SIZE(cc->scores); i++)
+		printf("\tscore %s = %d\n",
+				i == UL_COLORFILE_DISABLE ? "disable" :
+				i == UL_COLORFILE_ENABLE ? "enable" :
+				i == UL_COLORFILE_SCHEME ? "scheme" : "???",
+				cc->scores[i]);
+
+	fputc('\n', stdout);
+
+	for (i = 0; i < cc->nschemes; i++) {
+		printf("\tscheme #%02zu ", i);
+		color_scheme_enable(cc->schemes[i].name, NULL);
+		fputs(cc->schemes[i].name, stdout);
+		color_disable();
+		fputc('\n', stdout);
+	}
+	fputc('\n', stdout);
+}
+
+/*
+ * Parses [[<utilname>][@<termname>].]<type>
+ */
+static int filename_to_tokens(const char *str,
+			      const char **name, size_t *namesz,
+			      const char **term, size_t *termsz,
+			      int  *filetype)
+{
+	const char *type_start, *term_start, *p;
+
+	if (!str || !*str || *str == '.' || strlen(str) > PATH_MAX)
+		return -EINVAL;
+
+	/* parse .type */
+	p = strrchr(str, '.');
+	type_start = p ? p + 1 : str;
+
+	if (strcmp(type_start, "disable") == 0)
+		*filetype = UL_COLORFILE_DISABLE;
+	else if (strcmp(type_start, "enable") == 0)
+		*filetype = UL_COLORFILE_ENABLE;
+	else if (strcmp(type_start, "scheme") == 0)
+		*filetype = UL_COLORFILE_SCHEME;
+	else {
+		DBG(CONF, ul_debug("unknown type '%s'", type_start));
+		return 1;				/* unknown type */
+	}
+
+	if (type_start == str)
+		return 0;				/* "type" only */
+
+	/* parse @termname */
+	p = strchr(str, '@');
+	term_start = p ? p + 1 : NULL;
+	if (term_start) {
+		*term = term_start;
+		*termsz = type_start - term_start - 1;
+		if (term_start - 1 == str)
+			return 0;			/* "@termname.type" */
+	}
+
+	/* parse utilname */
+	p = term_start ? term_start : type_start;
+	*name =  str;
+	*namesz	= p - str - 1;
+
+	return 0;
+}
+
+/*
+ * Scans @dirname and select the best matches for UL_COLORFILE_* types.
+ * The result is stored to cc->scores. The path to the best "scheme"
+ * file is stored to cc->scheme.
+ */
+static int colors_readdir(struct ul_color_ctl *cc, const char *dirname)
+{
+	DIR *dir;
+	int rc = 0;
+	struct dirent *d;
+	char sfile[PATH_MAX] = { '\0' };
+	size_t namesz, termsz;
+
+	if (!dirname || !cc || !cc->utilname || !*cc->utilname)
+		return -EINVAL;
+
+	DBG(CONF, ul_debug("reading dir: '%s'", dirname));
+
+	dir = opendir(dirname);
+	if (!dir)
+		return -errno;
+
+	namesz = strlen(cc->utilname);
+	termsz = cc->termname ? strlen(cc->termname) : 0;
+
+	while ((d = readdir(dir))) {
+		int type, score = 1;
+		const char *tk_name = NULL, *tk_term = NULL;
+		size_t tk_namesz = 0, tk_termsz = 0;
+
+		if (*d->d_name == '.')
+			continue;
+#ifdef _DIRENT_HAVE_D_TYPE
+		if (d->d_type != DT_UNKNOWN && d->d_type != DT_LNK &&
+		    d->d_type != DT_REG)
+			continue;
+#endif
+		if (filename_to_tokens(d->d_name,
+				       &tk_name, &tk_namesz,
+				       &tk_term, &tk_termsz, &type) != 0)
+			continue;
+
+		/* count teoretical score before we check names to avoid
+		 * unnecessary strcmp() */
+		if (tk_name)
+			score += 20;
+		if (tk_term)
+			score += 10;
+
+		DBG(CONF, ul_debug("item '%s': score=%d "
+			"[cur: %d, name(%zu): %s, term(%zu): %s]",
+			d->d_name, score, cc->scores[type],
+			tk_namesz, tk_name,
+			tk_termsz, tk_term));
+
+
+		if (score < cc->scores[type])
+			continue;
+
+		/* filter out by names */
+		if (tk_namesz && (tk_namesz != namesz ||
+				 strncmp(tk_name, cc->utilname, namesz) != 0))
+			continue;
+
+		if (tk_termsz && (termsz == 0 || tk_termsz != termsz ||
+				  strncmp(tk_term, cc->termname, termsz) != 0))
+			continue;
+
+		DBG(CONF, ul_debug("setting '%s' from %d -to-> %d",
+					type == UL_COLORFILE_SCHEME ? "scheme" :
+					type == UL_COLORFILE_DISABLE ? "disable" :
+					type == UL_COLORFILE_ENABLE ? "enable" : "???",
+					cc->scores[type], score));
+		cc->scores[type] = score;
+		if (type == UL_COLORFILE_SCHEME)
+			strncpy(sfile, d->d_name, sizeof(sfile));
+	}
+
+	if (*sfile) {
+		sfile[sizeof(sfile) - 1] = '\0';
+		if (asprintf(&cc->sfile, "%s/%s", dirname, sfile) <= 0)
+			rc = -ENOMEM;
+	}
+
+	closedir(dir);
+	return rc;
+}
+
+/* atexit() wrapper */
+static void colors_deinit(void)
+{
+	colors_reset(&ul_colors);
+}
+
+/*
+ * Returns path to $XDG_CONFIG_HOME/terminal-colors.d
+ */
+static char *colors_get_homedir(char *buf, size_t bufsz)
+{
+	char *p = getenv("XDG_CONFIG_HOME");
+
+	if (p) {
+		snprintf(buf, bufsz, "%s/" _PATH_TERMCOLORS_DIRNAME, p);
+		return buf;
+	}
+
+	p = getenv("HOME");
+	if (p) {
+		snprintf(buf, bufsz, "%s/.config/" _PATH_TERMCOLORS_DIRNAME, p);
+		return buf;
+	}
+
+	return NULL;
+}
+
+/* canonicalize sequence */
+static int cn_sequence(const char *str, char **seq)
+{
+	char *in, *out;
+
+	if (!str)
+		return -EINVAL;
+
+	*seq = NULL;
+
+	/* convert logical names like "red" to the real sequence */
+	if (*str != '\\' && isalpha(*str)) {
+		const char *s = color_sequence_from_colorname(str);
+		*seq = strdup(s ? s : str);
+
+		return *seq ? 0 : -ENOMEM;
+	}
+
+	/* convert xx;yy sequences to "\033[xx;yy" */
+	if (asprintf(seq, "\033[%sm", str) < 1)
+		return -ENOMEM;
+
+	for (in = *seq, out = *seq; in && *in; in++) {
+		if (*in != '\\') {
+			*out++ = *in;
+			continue;
+		}
+		switch(*(in + 1)) {
+		case 'a':
+			*out++ = '\a';	/* Bell */
+			break;
+		case 'b':
+			*out++ = '\b';	/* Backspace */
+			break;
+		case 'e':
+			*out++ = '\033';	/* Escape */
+			break;
+		case 'f':
+			*out++ = '\f';	/* Form Feed */
+			break;
+		case 'n':
+			*out++ = '\n';	/* Newline */
+			break;
+		case 'r':
+			*out++ = '\r';	/* Carriage Return */
+			break;
+		case 't':
+			*out++ = '\t';	/* Tab */
+			break;
+		case 'v':
+			*out++ = '\v';	/* Vertical Tab */
+			break;
+		case '\\':
+			*out++ = '\\';	/* Backslash */
+			break;
+		case '_':
+			*out++ = ' ';	/* Space */
+			break;
+		case '#':
+			*out++ = '#';	/* Hash mark */
+			break;
+		case '?':
+			*out++ = '?';	/* Qestion mark */
+			break;
+		default:
+			*out++ = *in;
+			*out++ = *(in + 1);
+			break;
+		}
+		in++;
+	}
+	*out = '\0';
+
+	return 0;
+}
+
+
+/*
+ * Adds one color sequence to array with color scheme.
+ * When returning success (0) this function takes ownership of
+ * @seq and @name, which have to be allocated strings.
+ */
+static int colors_add_scheme(struct ul_color_ctl *cc,
+			     char *name,
+			     char *seq0)
+{
+	struct ul_color_scheme *cs = NULL;
+	char *seq = NULL;
+	int rc;
+
+	if (!cc || !name || !*name || !seq0 || !*seq0)
+		return -EINVAL;
+
+	DBG(SCHEME, ul_debug("add '%s'", name));
+
+	rc = cn_sequence(seq0, &seq);
+	if (rc)
+		return rc;
+
+	rc = -ENOMEM;
+
+	/* convert logical name (e.g. "red") to real ESC code */
+	if (isalpha(*seq)) {
+		const char *s = color_sequence_from_colorname(seq);
+		char *p;
+
+		if (!s) {
+			DBG(SCHEME, ul_debug("unknown logical name: %s", seq));
+			rc = -EINVAL;
+			goto err;
+		}
+
+		p = strdup(s);
+		if (!p)
+			goto err;
+		free(seq);
+		seq = p;
+	}
+
+	/* enlarge the array */
+	if (cc->nschemes == cc->schemes_sz) {
+		void *tmp = realloc(cc->schemes, (cc->nschemes + 10)
+					* sizeof(struct ul_color_scheme));
+		if (!tmp)
+			goto err;
+		cc->schemes = tmp;
+		cc->schemes_sz = cc->nschemes + 10;
+	}
+
+	/* add a new item */
+	cs = &cc->schemes[cc->nschemes];
+	cs->seq = seq;
+	cs->name = strdup(name);
+	if (!cs->name)
+		goto err;
+
+	cc->nschemes++;
+	return 0;
+err:
+	if (cs) {
+		free(cs->seq);
+		free(cs->name);
+		cs->seq = cs->name = NULL;
+	} else
+		free(seq);
+	return rc;
+}
+
+/*
+ * Deallocates all regards to color schemes
+ */
+static void colors_free_schemes(struct ul_color_ctl *cc)
+{
+	size_t i;
+
+	DBG(SCHEME, ul_debug("free scheme"));
+
+	for (i = 0; i < cc->nschemes; i++) {
+		free(cc->schemes[i].name);
+		free(cc->schemes[i].seq);
+	}
+
+	free(cc->schemes);
+	cc->schemes = NULL;
+	cc->nschemes = 0;
+	cc->schemes_sz = 0;
+}
+
+/*
+ * The scheme configuration has to be sorted for bsearch
+ */
+static void colors_sort_schemes(struct ul_color_ctl *cc)
+{
+	if (!cc->nschemes)
+		return;
+
+	DBG(SCHEME, ul_debug("sort scheme"));
+
+	qsort(cc->schemes, cc->nschemes,
+	      sizeof(struct ul_color_scheme), cmp_scheme_name);
+}
+
+/*
+ * Returns just one color scheme
+ */
+static struct ul_color_scheme *colors_get_scheme(struct ul_color_ctl *cc,
+						 const char *name)
+{
+	struct ul_color_scheme key = { .name = (char *) name}, *res;
+
+	if (!cc || !name || !*name)
+		return NULL;
+
+	if (!cc->cs_configured) {
+		int rc = colors_read_schemes(cc);
+		if (rc)
+			return NULL;
+	}
+	if (!cc->nschemes)
+		return NULL;
+
+	DBG(SCHEME, ul_debug("search '%s'", name));
+
+	res = bsearch(&key, cc->schemes, cc->nschemes,
+				sizeof(struct ul_color_scheme),
+				cmp_scheme_name);
+
+	return res && res->seq ? res  : NULL;
+}
+
+/*
+ * Parses filenames in terminal-colors.d
+ */
+static int colors_read_configuration(struct ul_color_ctl *cc)
+{
+	int rc = -ENOENT;
+	char *dirname, buf[PATH_MAX];
+
+	cc->termname = getenv("TERM");
+
+	dirname = colors_get_homedir(buf, sizeof(buf));
+	if (dirname)
+		rc = colors_readdir(cc, dirname);		/* ~/.config */
+	if (rc == -EPERM || rc == -EACCES || rc == -ENOENT)
+		rc = colors_readdir(cc, _PATH_TERMCOLORS_DIR);	/* /etc */
+
+	cc->configured = 1;
+	return rc;
+}
+
+/*
+ * Reads terminal-colors.d/ scheme file into array schemes
+ */
+static int colors_read_schemes(struct ul_color_ctl *cc)
+{
+	int rc = 0;
+	FILE *f = NULL;
+	char buf[BUFSIZ],
+	     cn[129], seq[129];
+
+	if (!cc->configured)
+		rc = colors_read_configuration(cc);
+
+	cc->cs_configured = 1;
+
+	if (rc || !cc->sfile)
+		goto done;
+
+	DBG(SCHEME, ul_debug("reading file '%s'", cc->sfile));
+
+	f = fopen(cc->sfile, "r");
+	if (!f) {
+		rc = -errno;
+		goto done;
+	}
+
+	while (fgets(buf, sizeof(buf), f)) {
+		char *p = strchr(buf, '\n');
+
+		if (!p) {
+			if (feof(f))
+				p = strchr(buf, '\0');
+			else {
+				rc = -errno;
+				goto done;
+			}
+		}
+		*p = '\0';
+		p = (char *) skip_blank(buf);
+		if (*p == '\0' || *p == '#')
+			continue;
+
+		rc = sscanf(p, "%128[^ ] %128[^\n ]", cn, seq);
+		if (rc == 2 && *cn && *seq) {
+			rc = colors_add_scheme(cc, cn, seq);	/* set rc=0 on success */
+			if (rc)
+				goto done;
+		}
+	}
+	rc = 0;
+
+done:
+	if (f)
+		fclose(f);
+	colors_sort_schemes(cc);
+
+	return rc;
+}
+
+
+static void termcolors_init_debug(void)
+{
+	__UL_INIT_DEBUG(termcolors, TERMCOLORS_DEBUG_, 0, TERMINAL_COLORS_DEBUG);
+}
+
+/**
+ * colors_init:
+ * @mode: UL_COLORMODE_*
+ * @name: util argv[0]
+ *
+ * Initialize private color control struct and initialize the colors
+ * status. The color schemes are parsed on demand by colors_get_scheme().
+ *
+ * Returns: >0 on success.
+ */
+int colors_init(int mode, const char *name)
+{
+	int atty = -1;
+	struct ul_color_ctl *cc = &ul_colors;
+
+	cc->utilname = name;
+	cc->mode = mode;
+
+	termcolors_init_debug();
+
+	if (mode == UL_COLORMODE_UNDEF && (atty = isatty(STDOUT_FILENO))) {
+		int rc = colors_read_configuration(cc);
+		if (rc)
+			cc->mode = UL_COLORMODE_AUTO;
+		else {
+
+			/* evaluate scores */
+			if (cc->scores[UL_COLORFILE_DISABLE] >
+			    cc->scores[UL_COLORFILE_ENABLE])
+				cc->mode = UL_COLORMODE_NEVER;
+			else
+				cc->mode = UL_COLORMODE_AUTO;
+
+			atexit(colors_deinit);
+		}
+	}
+
+	switch (cc->mode) {
+	case UL_COLORMODE_AUTO:
+		cc->has_colors = atty == -1 ? isatty(STDOUT_FILENO) : atty;
+		break;
+	case UL_COLORMODE_ALWAYS:
+		cc->has_colors = 1;
+		break;
+	case UL_COLORMODE_NEVER:
+	default:
+		cc->has_colors = 0;
+	}
+
+	ON_DBG(CONF, colors_debug(cc));
+
+	return cc->has_colors;
+}
+
+/*
+ * Temporary disable colors (this setting is independent on terminal-colors.d/)
+ */
+void colors_off(void)
+{
+	ul_colors.disabled = 1;
+}
+
+/*
+ * Enable colors
+ */
+void colors_on(void)
+{
+	ul_colors.disabled = 0;
+}
+
+/*
+ * Is terminal-colors.d/ configured to use colors?
+ */
+int colors_wanted(void)
+{
+	return ul_colors.has_colors;
+}
+
+/*
+ * Enable @seq color
+ */
+void color_fenable(const char *seq, FILE *f)
+{
+	if (!ul_colors.disabled && ul_colors.has_colors && seq)
+		fputs(seq, f);
+}
+
+/*
+ * Returns escape sequence by logical @name, if undefined then returns @dflt.
+ */
+const char *color_scheme_get_sequence(const char *name, const char *dflt)
+{
+	struct ul_color_scheme *cs;
+
+	if (ul_colors.disabled || !ul_colors.has_colors)
+		return NULL;
+
+	cs = colors_get_scheme(&ul_colors, name);
+	return cs && cs->seq ? cs->seq : dflt;
+}
+
+/*
+ * Enable color by logical @name, if undefined enable @dflt.
+ */
+void color_scheme_fenable(const char *name, const char *dflt, FILE *f)
+{
+	const char *seq = color_scheme_get_sequence(name, dflt);
+
+	if (!seq)
+		return;
+	color_fenable(seq, f);
+}
+
+
+/*
+ * Disable previously enabled color
+ */
+void color_fdisable(FILE *f)
+{
+	if (!ul_colors.disabled && ul_colors.has_colors)
+		fputs(UL_COLOR_RESET, f);
+}
+
+/*
+ * Parses @str to return UL_COLORMODE_*
+ */
+int colormode_from_string(const char *str)
+{
+	size_t i;
+	static const char *modes[] = {
+		[UL_COLORMODE_AUTO]   = "auto",
+		[UL_COLORMODE_NEVER]  = "never",
+		[UL_COLORMODE_ALWAYS] = "always",
+		[UL_COLORMODE_UNDEF] = ""
+	};
+
+	if (!str || !*str)
+		return -EINVAL;
+
+	assert(ARRAY_SIZE(modes) == __UL_NCOLORMODES);
+
+	for (i = 0; i < ARRAY_SIZE(modes); i++) {
+		if (strcasecmp(str, modes[i]) == 0)
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+/*
+ * Parses @str and exit(EXIT_FAILURE) on error
+ */
+int colormode_or_err(const char *str, const char *errmsg)
+{
+	const char *p = str && *str == '=' ? str + 1 : str;
+	int colormode;
+
+	colormode = colormode_from_string(p);
+	if (colormode < 0)
+		errx(EXIT_FAILURE, "%s: '%s'", errmsg, p);
+
+	return colormode;
+}
+
+#ifdef TEST_PROGRAM
+# include <getopt.h>
+int main(int argc, char *argv[])
+{
+	static const struct option longopts[] = {
+		{ "mode",	required_argument, 0, 'm' },
+		{ "color",	required_argument, 0, 'c' },
+		{ "color-scheme", required_argument, 0, 'C' },
+		{ "name",	required_argument, 0, 'n' },
+		{ NULL, 0, 0, 0 }
+	};
+	int c, mode = UL_COLORMODE_UNDEF;	/* default */
+	const char *color = "red", *name = NULL, *color_scheme = NULL;
+	const char *seq = NULL;
+
+	while ((c = getopt_long(argc, argv, "C:c:m:n:", longopts, NULL)) != -1) {
+		switch (c) {
+		case 'c':
+			color = optarg;
+			break;
+		case 'C':
+			color_scheme = optarg;
+			break;
+		case 'm':
+			mode = colormode_or_err(optarg, "unsupported color mode");
+			break;
+		case 'n':
+			name = optarg;
+			break;
+		default:
+			fprintf(stderr, "usage: %s [options]\n"
+			" -m, --mode <auto|never|always>  default is undefined\n"
+			" -c, --color <red|blue|...>      color for the test message\n"
+			" -C, --color-scheme <name>       color for the test message\n"
+			" -n, --name <utilname>           util name\n",
+			program_invocation_short_name);
+			return EXIT_FAILURE;
+		}
+	}
+
+	colors_init(mode, name ? name : program_invocation_short_name);
+
+	seq = color_sequence_from_colorname(color);
+
+	if (color_scheme)
+		color_scheme_enable(color_scheme, seq);
+	else
+		color_enable(seq);
+	printf("Hello World!");
+	color_disable();
+	fputc('\n', stdout);
+
+	return EXIT_SUCCESS;
+}
+#endif
+
diff --git a/libblkid/lib/cpuset.c b/libblkid/lib/cpuset.c
new file mode 100644
index 0000000..d715720
--- /dev/null
+++ b/libblkid/lib/cpuset.c
@@ -0,0 +1,403 @@
+/*
+ * Terminology:
+ *
+ *	cpuset	- (libc) cpu_set_t data structure represents set of CPUs
+ *	cpumask	- string with hex mask (e.g. "0x00000001")
+ *	cpulist - string with CPU ranges (e.g. "0-3,5,7,8")
+ *
+ * Based on code from taskset.c and Linux kernel.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sched.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/syscall.h>
+
+#include "cpuset.h"
+#include "c.h"
+
+static inline int val_to_char(int v)
+{
+	if (v >= 0 && v < 10)
+		return '0' + v;
+	else if (v >= 10 && v < 16)
+		return ('a' - 10) + v;
+	else
+		return -1;
+}
+
+static inline int char_to_val(int c)
+{
+	int cl;
+
+	cl = tolower(c);
+	if (c >= '0' && c <= '9')
+		return c - '0';
+	else if (cl >= 'a' && cl <= 'f')
+		return cl + (10 - 'a');
+	else
+		return -1;
+}
+
+static const char *nexttoken(const char *q,  int sep)
+{
+	if (q)
+		q = strchr(q, sep);
+	if (q)
+		q++;
+	return q;
+}
+
+/*
+ * Number of bits in a CPU bitmask on current system
+ */
+int get_max_number_of_cpus(void)
+{
+#ifdef SYS_sched_getaffinity
+	int n, cpus = 2048;
+	size_t setsize;
+	cpu_set_t *set = cpuset_alloc(cpus, &setsize, NULL);
+
+	if (!set)
+		return -1;	/* error */
+
+	for (;;) {
+		CPU_ZERO_S(setsize, set);
+
+		/* the library version does not return size of cpumask_t */
+		n = syscall(SYS_sched_getaffinity, 0, setsize, set);
+
+		if (n < 0 && errno == EINVAL && cpus < 1024 * 1024) {
+			cpuset_free(set);
+			cpus *= 2;
+			set = cpuset_alloc(cpus, &setsize, NULL);
+			if (!set)
+				return -1;	/* error */
+			continue;
+		}
+		cpuset_free(set);
+		return n * 8;
+	}
+#endif
+	return -1;
+}
+
+/*
+ * Allocates a new set for ncpus and returns size in bytes and size in bits
+ */
+cpu_set_t *cpuset_alloc(int ncpus, size_t *setsize, size_t *nbits)
+{
+	cpu_set_t *set = CPU_ALLOC(ncpus);
+
+	if (!set)
+		return NULL;
+	if (setsize)
+		*setsize = CPU_ALLOC_SIZE(ncpus);
+	if (nbits)
+		*nbits = cpuset_nbits(CPU_ALLOC_SIZE(ncpus));
+	return set;
+}
+
+void cpuset_free(cpu_set_t *set)
+{
+	CPU_FREE(set);
+}
+
+#if !HAVE_DECL_CPU_ALLOC
+/* Please, use CPU_COUNT_S() macro. This is fallback */
+int __cpuset_count_s(size_t setsize, const cpu_set_t *set)
+{
+	int s = 0;
+	const __cpu_mask *p = set->__bits;
+	const __cpu_mask *end = &set->__bits[setsize / sizeof (__cpu_mask)];
+
+	while (p < end) {
+		__cpu_mask l = *p++;
+
+		if (l == 0)
+			continue;
+# if LONG_BIT > 32
+		l = (l & 0x5555555555555555ul) + ((l >> 1) & 0x5555555555555555ul);
+		l = (l & 0x3333333333333333ul) + ((l >> 2) & 0x3333333333333333ul);
+		l = (l & 0x0f0f0f0f0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0f0f0f0f0ful);
+		l = (l & 0x00ff00ff00ff00fful) + ((l >> 8) & 0x00ff00ff00ff00fful);
+		l = (l & 0x0000ffff0000fffful) + ((l >> 16) & 0x0000ffff0000fffful);
+		l = (l & 0x00000000fffffffful) + ((l >> 32) & 0x00000000fffffffful);
+# else
+		l = (l & 0x55555555ul) + ((l >> 1) & 0x55555555ul);
+		l = (l & 0x33333333ul) + ((l >> 2) & 0x33333333ul);
+		l = (l & 0x0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0ful);
+		l = (l & 0x00ff00fful) + ((l >> 8) & 0x00ff00fful);
+		l = (l & 0x0000fffful) + ((l >> 16) & 0x0000fffful);
+# endif
+		s += l;
+	}
+	return s;
+}
+#endif
+
+/*
+ * Returns human readable representation of the cpuset. The output format is
+ * a list of CPUs with ranges (for example, "0,1,3-9").
+ */
+char *cpulist_create(char *str, size_t len,
+			cpu_set_t *set, size_t setsize)
+{
+	size_t i;
+	char *ptr = str;
+	int entry_made = 0;
+	size_t max = cpuset_nbits(setsize);
+
+	for (i = 0; i < max; i++) {
+		if (CPU_ISSET_S(i, setsize, set)) {
+			int rlen;
+			size_t j, run = 0;
+			entry_made = 1;
+			for (j = i + 1; j < max; j++) {
+				if (CPU_ISSET_S(j, setsize, set))
+					run++;
+				else
+					break;
+			}
+			if (!run)
+				rlen = snprintf(ptr, len, "%zd,", i);
+			else if (run == 1) {
+				rlen = snprintf(ptr, len, "%zd,%zd,", i, i + 1);
+				i++;
+			} else {
+				rlen = snprintf(ptr, len, "%zd-%zd,", i, i + run);
+				i += run;
+			}
+			if (rlen < 0 || (size_t) rlen + 1 > len)
+				return NULL;
+			ptr += rlen;
+			if (rlen > 0 && len > (size_t) rlen)
+				len -= rlen;
+			else
+				len = 0;
+		}
+	}
+	ptr -= entry_made;
+	*ptr = '\0';
+
+	return str;
+}
+
+/*
+ * Returns string with CPU mask.
+ */
+char *cpumask_create(char *str, size_t len,
+			cpu_set_t *set, size_t setsize)
+{
+	char *ptr = str;
+	char *ret = NULL;
+	int cpu;
+
+	for (cpu = cpuset_nbits(setsize) - 4; cpu >= 0; cpu -= 4) {
+		char val = 0;
+
+		if (len == (size_t) (ptr - str))
+			break;
+
+		if (CPU_ISSET_S(cpu, setsize, set))
+			val |= 1;
+		if (CPU_ISSET_S(cpu + 1, setsize, set))
+			val |= 2;
+		if (CPU_ISSET_S(cpu + 2, setsize, set))
+			val |= 4;
+		if (CPU_ISSET_S(cpu + 3, setsize, set))
+			val |= 8;
+
+		if (!ret && val)
+			ret = ptr;
+		*ptr++ = val_to_char(val);
+	}
+	*ptr = '\0';
+	return ret ? ret : ptr - 1;
+}
+
+/*
+ * Parses string with CPUs mask.
+ */
+int cpumask_parse(const char *str, cpu_set_t *set, size_t setsize)
+{
+	int len = strlen(str);
+	const char *ptr = str + len - 1;
+	int cpu = 0;
+
+	/* skip 0x, it's all hex anyway */
+	if (len > 1 && !memcmp(str, "0x", 2L))
+		str += 2;
+
+	CPU_ZERO_S(setsize, set);
+
+	while (ptr >= str) {
+		char val;
+
+		/* cpu masks in /sys uses comma as a separator */
+		if (*ptr == ',')
+			ptr--;
+
+		val = char_to_val(*ptr);
+		if (val == (char) -1)
+			return -1;
+		if (val & 1)
+			CPU_SET_S(cpu, setsize, set);
+		if (val & 2)
+			CPU_SET_S(cpu + 1, setsize, set);
+		if (val & 4)
+			CPU_SET_S(cpu + 2, setsize, set);
+		if (val & 8)
+			CPU_SET_S(cpu + 3, setsize, set);
+		len--;
+		ptr--;
+		cpu += 4;
+	}
+
+	return 0;
+}
+
+/*
+ * Parses string with list of CPU ranges.
+ * Returns 0 on success.
+ * Returns 1 on error.
+ * Returns 2 if fail is set and a cpu number passed in the list doesn't fit
+ * into the cpu_set. If fail is not set cpu numbers that do not fit are
+ * ignored and 0 is returned instead.
+ */
+int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize, int fail)
+{
+	size_t max = cpuset_nbits(setsize);
+	const char *p, *q;
+	int r = 0;
+
+	q = str;
+	CPU_ZERO_S(setsize, set);
+
+	while (p = q, q = nexttoken(q, ','), p) {
+		unsigned int a;	/* beginning of range */
+		unsigned int b;	/* end of range */
+		unsigned int s;	/* stride */
+		const char *c1, *c2;
+		char c;
+
+		if ((r = sscanf(p, "%u%c", &a, &c)) < 1)
+			return 1;
+		b = a;
+		s = 1;
+
+		c1 = nexttoken(p, '-');
+		c2 = nexttoken(p, ',');
+		if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+			if ((r = sscanf(c1, "%u%c", &b, &c)) < 1)
+				return 1;
+			c1 = nexttoken(c1, ':');
+			if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+				if ((r = sscanf(c1, "%u%c", &s, &c)) < 1)
+					return 1;
+				if (s == 0)
+					return 1;
+			}
+		}
+
+		if (!(a <= b))
+			return 1;
+		while (a <= b) {
+			if (fail && (a >= max))
+				return 2;
+			CPU_SET_S(a, setsize, set);
+			a += s;
+		}
+	}
+
+	if (r == 2)
+		return 1;
+	return 0;
+}
+
+#ifdef TEST_PROGRAM
+
+#include <getopt.h>
+
+int main(int argc, char *argv[])
+{
+	cpu_set_t *set;
+	size_t setsize, buflen, nbits;
+	char *buf, *mask = NULL, *range = NULL;
+	int ncpus = 2048, rc, c;
+
+	static const struct option longopts[] = {
+	    { "ncpus", 1, 0, 'n' },
+	    { "mask",  1, 0, 'm' },
+	    { "range", 1, 0, 'r' },
+	    { NULL,    0, 0, 0 }
+	};
+
+	while ((c = getopt_long(argc, argv, "n:m:r:", longopts, NULL)) != -1) {
+		switch(c) {
+		case 'n':
+			ncpus = atoi(optarg);
+			break;
+		case 'm':
+			mask = strdup(optarg);
+			break;
+		case 'r':
+			range = strdup(optarg);
+			break;
+		default:
+			goto usage_err;
+		}
+	}
+
+	if (!mask && !range)
+		goto usage_err;
+
+	set = cpuset_alloc(ncpus, &setsize, &nbits);
+	if (!set)
+		err(EXIT_FAILURE, "failed to allocate cpu set");
+
+	/*
+	fprintf(stderr, "ncpus: %d, cpuset bits: %zd, cpuset bytes: %zd\n",
+			ncpus, nbits, setsize);
+	*/
+
+	buflen = 7 * nbits;
+	buf = malloc(buflen);
+	if (!buf)
+		err(EXIT_FAILURE, "failed to allocate cpu set buffer");
+
+	if (mask)
+		rc = cpumask_parse(mask, set, setsize);
+	else
+		rc = cpulist_parse(range, set, setsize, 0);
+
+	if (rc)
+		errx(EXIT_FAILURE, "failed to parse string: %s", mask ? : range);
+
+	printf("%-15s = %15s ", mask ? : range,
+				cpumask_create(buf, buflen, set, setsize));
+	printf("[%s]\n", cpulist_create(buf, buflen, set, setsize));
+
+	free(buf);
+	free(mask);
+	free(range);
+	cpuset_free(set);
+
+	return EXIT_SUCCESS;
+
+usage_err:
+	fprintf(stderr,
+		"usage: %s [--ncpus <num>] --mask <mask> | --range <list>",
+		program_invocation_short_name);
+	exit(EXIT_FAILURE);
+}
+#endif
diff --git a/libblkid/lib/crc32.c b/libblkid/lib/crc32.c
new file mode 100644
index 0000000..be98f1a
--- /dev/null
+++ b/libblkid/lib/crc32.c
@@ -0,0 +1,118 @@
+/*
+ *  COPYRIGHT (C) 1986 Gary S. Brown.  You may use this program, or
+ *  code or tables extracted from it, as desired without restriction.
+ *
+ *  First, the polynomial itself and its table of feedback terms.  The
+ *  polynomial is
+ *  X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ *  Note that we take it "backwards" and put the highest-order term in
+ *  the lowest-order bit.  The X^32 term is "implied"; the LSB is the
+ *  X^31 term, etc.  The X^0 term (usually shown as "+1") results in
+ *  the MSB being 1.
+ *
+ *  Note that the usual hardware shift register implementation, which
+ *  is what we're using (we're merely optimizing it by doing eight-bit
+ *  chunks at a time) shifts bits into the lowest-order term.  In our
+ *  implementation, that means shifting towards the right.  Why do we
+ *  do it this way?  Because the calculated CRC must be transmitted in
+ *  order from highest-order term to lowest-order term.  UARTs transmit
+ *  characters in order from LSB to MSB.  By storing the CRC this way,
+ *  we hand it to the UART in the order low-byte to high-byte; the UART
+ *  sends each low-bit to hight-bit; and the result is transmission bit
+ *  by bit from highest- to lowest-order term without requiring any bit
+ *  shuffling on our part.  Reception works similarly.
+ *
+ *  The feedback terms table consists of 256, 32-bit entries.  Notes
+ *
+ *      The table can be generated at runtime if desired; code to do so
+ *      is shown later.  It might not be obvious, but the feedback
+ *      terms simply represent the results of eight shift/xor opera-
+ *      tions for all combinations of data and CRC register values.
+ *
+ *      The values must be right-shifted by eight bits by the "updcrc"
+ *      logic; the shift must be unsigned (bring in zeroes).  On some
+ *      hardware you could probably optimize the shift in assembler by
+ *      using byte-swap instructions.
+ *      polynomial $edb88320
+ *
+ */
+
+#include <stdio.h>
+
+#include "crc32.h"
+
+
+static const uint32_t crc32_tab[] = {
+	0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
+	0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
+	0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
+	0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+	0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+	0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+	0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+	0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+	0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+	0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+	0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+	0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+	0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+	0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
+	0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
+	0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
+	0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+	0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+	0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+	0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+	0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+	0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+	0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+	0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+	0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+	0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+	0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
+	0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
+	0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
+	0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+	0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+	0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+	0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+	0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+	0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+	0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+	0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+	0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+	0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+	0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
+	0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
+	0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
+	0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+	0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+	0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+	0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+	0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+	0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+	0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+	0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+	0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+	0x2d02ef8dL
+};
+
+/*
+ * This a generic crc32() function, it takes seed as an argument,
+ * and does __not__ xor at the end. Then individual users can do
+ * whatever they need.
+ */
+uint32_t crc32(uint32_t seed, const unsigned char *buf, size_t len)
+{
+	uint32_t crc = seed;
+	const unsigned char *p = buf;
+
+	while (len) {
+		crc = crc32_tab[(crc ^ *p++) & 0xff] ^ (crc >> 8);
+		len--;
+	}
+
+	return crc;
+}
+
diff --git a/libblkid/lib/crc64.c b/libblkid/lib/crc64.c
new file mode 100644
index 0000000..0be78e6
--- /dev/null
+++ b/libblkid/lib/crc64.c
@@ -0,0 +1,109 @@
+#include "crc64.h"
+
+static const uint64_t crc64_tab[256] = {
+	0x0000000000000000ULL, 0x42F0E1EBA9EA3693ULL, 0x85E1C3D753D46D26ULL,
+	0xC711223CFA3E5BB5ULL, 0x493366450E42ECDFULL, 0x0BC387AEA7A8DA4CULL,
+	0xCCD2A5925D9681F9ULL, 0x8E224479F47CB76AULL, 0x9266CC8A1C85D9BEULL,
+	0xD0962D61B56FEF2DULL, 0x17870F5D4F51B498ULL, 0x5577EEB6E6BB820BULL,
+	0xDB55AACF12C73561ULL, 0x99A54B24BB2D03F2ULL, 0x5EB4691841135847ULL,
+	0x1C4488F3E8F96ED4ULL, 0x663D78FF90E185EFULL, 0x24CD9914390BB37CULL,
+	0xE3DCBB28C335E8C9ULL, 0xA12C5AC36ADFDE5AULL, 0x2F0E1EBA9EA36930ULL,
+	0x6DFEFF5137495FA3ULL, 0xAAEFDD6DCD770416ULL, 0xE81F3C86649D3285ULL,
+	0xF45BB4758C645C51ULL, 0xB6AB559E258E6AC2ULL, 0x71BA77A2DFB03177ULL,
+	0x334A9649765A07E4ULL, 0xBD68D2308226B08EULL, 0xFF9833DB2BCC861DULL,
+	0x388911E7D1F2DDA8ULL, 0x7A79F00C7818EB3BULL, 0xCC7AF1FF21C30BDEULL,
+	0x8E8A101488293D4DULL, 0x499B3228721766F8ULL, 0x0B6BD3C3DBFD506BULL,
+	0x854997BA2F81E701ULL, 0xC7B97651866BD192ULL, 0x00A8546D7C558A27ULL,
+	0x4258B586D5BFBCB4ULL, 0x5E1C3D753D46D260ULL, 0x1CECDC9E94ACE4F3ULL,
+	0xDBFDFEA26E92BF46ULL, 0x990D1F49C77889D5ULL, 0x172F5B3033043EBFULL,
+	0x55DFBADB9AEE082CULL, 0x92CE98E760D05399ULL, 0xD03E790CC93A650AULL,
+	0xAA478900B1228E31ULL, 0xE8B768EB18C8B8A2ULL, 0x2FA64AD7E2F6E317ULL,
+	0x6D56AB3C4B1CD584ULL, 0xE374EF45BF6062EEULL, 0xA1840EAE168A547DULL,
+	0x66952C92ECB40FC8ULL, 0x2465CD79455E395BULL, 0x3821458AADA7578FULL,
+	0x7AD1A461044D611CULL, 0xBDC0865DFE733AA9ULL, 0xFF3067B657990C3AULL,
+	0x711223CFA3E5BB50ULL, 0x33E2C2240A0F8DC3ULL, 0xF4F3E018F031D676ULL,
+	0xB60301F359DBE0E5ULL, 0xDA050215EA6C212FULL, 0x98F5E3FE438617BCULL,
+	0x5FE4C1C2B9B84C09ULL, 0x1D14202910527A9AULL, 0x93366450E42ECDF0ULL,
+	0xD1C685BB4DC4FB63ULL, 0x16D7A787B7FAA0D6ULL, 0x5427466C1E109645ULL,
+	0x4863CE9FF6E9F891ULL, 0x0A932F745F03CE02ULL, 0xCD820D48A53D95B7ULL,
+	0x8F72ECA30CD7A324ULL, 0x0150A8DAF8AB144EULL, 0x43A04931514122DDULL,
+	0x84B16B0DAB7F7968ULL, 0xC6418AE602954FFBULL, 0xBC387AEA7A8DA4C0ULL,
+	0xFEC89B01D3679253ULL, 0x39D9B93D2959C9E6ULL, 0x7B2958D680B3FF75ULL,
+	0xF50B1CAF74CF481FULL, 0xB7FBFD44DD257E8CULL, 0x70EADF78271B2539ULL,
+	0x321A3E938EF113AAULL, 0x2E5EB66066087D7EULL, 0x6CAE578BCFE24BEDULL,
+	0xABBF75B735DC1058ULL, 0xE94F945C9C3626CBULL, 0x676DD025684A91A1ULL,
+	0x259D31CEC1A0A732ULL, 0xE28C13F23B9EFC87ULL, 0xA07CF2199274CA14ULL,
+	0x167FF3EACBAF2AF1ULL, 0x548F120162451C62ULL, 0x939E303D987B47D7ULL,
+	0xD16ED1D631917144ULL, 0x5F4C95AFC5EDC62EULL, 0x1DBC74446C07F0BDULL,
+	0xDAAD56789639AB08ULL, 0x985DB7933FD39D9BULL, 0x84193F60D72AF34FULL,
+	0xC6E9DE8B7EC0C5DCULL, 0x01F8FCB784FE9E69ULL, 0x43081D5C2D14A8FAULL,
+	0xCD2A5925D9681F90ULL, 0x8FDAB8CE70822903ULL, 0x48CB9AF28ABC72B6ULL,
+	0x0A3B7B1923564425ULL, 0x70428B155B4EAF1EULL, 0x32B26AFEF2A4998DULL,
+	0xF5A348C2089AC238ULL, 0xB753A929A170F4ABULL, 0x3971ED50550C43C1ULL,
+	0x7B810CBBFCE67552ULL, 0xBC902E8706D82EE7ULL, 0xFE60CF6CAF321874ULL,
+	0xE224479F47CB76A0ULL, 0xA0D4A674EE214033ULL, 0x67C58448141F1B86ULL,
+	0x253565A3BDF52D15ULL, 0xAB1721DA49899A7FULL, 0xE9E7C031E063ACECULL,
+	0x2EF6E20D1A5DF759ULL, 0x6C0603E6B3B7C1CAULL, 0xF6FAE5C07D3274CDULL,
+	0xB40A042BD4D8425EULL, 0x731B26172EE619EBULL, 0x31EBC7FC870C2F78ULL,
+	0xBFC9838573709812ULL, 0xFD39626EDA9AAE81ULL, 0x3A28405220A4F534ULL,
+	0x78D8A1B9894EC3A7ULL, 0x649C294A61B7AD73ULL, 0x266CC8A1C85D9BE0ULL,
+	0xE17DEA9D3263C055ULL, 0xA38D0B769B89F6C6ULL, 0x2DAF4F0F6FF541ACULL,
+	0x6F5FAEE4C61F773FULL, 0xA84E8CD83C212C8AULL, 0xEABE6D3395CB1A19ULL,
+	0x90C79D3FEDD3F122ULL, 0xD2377CD44439C7B1ULL, 0x15265EE8BE079C04ULL,
+	0x57D6BF0317EDAA97ULL, 0xD9F4FB7AE3911DFDULL, 0x9B041A914A7B2B6EULL,
+	0x5C1538ADB04570DBULL, 0x1EE5D94619AF4648ULL, 0x02A151B5F156289CULL,
+	0x4051B05E58BC1E0FULL, 0x87409262A28245BAULL, 0xC5B073890B687329ULL,
+	0x4B9237F0FF14C443ULL, 0x0962D61B56FEF2D0ULL, 0xCE73F427ACC0A965ULL,
+	0x8C8315CC052A9FF6ULL, 0x3A80143F5CF17F13ULL, 0x7870F5D4F51B4980ULL,
+	0xBF61D7E80F251235ULL, 0xFD913603A6CF24A6ULL, 0x73B3727A52B393CCULL,
+	0x31439391FB59A55FULL, 0xF652B1AD0167FEEAULL, 0xB4A25046A88DC879ULL,
+	0xA8E6D8B54074A6ADULL, 0xEA16395EE99E903EULL, 0x2D071B6213A0CB8BULL,
+	0x6FF7FA89BA4AFD18ULL, 0xE1D5BEF04E364A72ULL, 0xA3255F1BE7DC7CE1ULL,
+	0x64347D271DE22754ULL, 0x26C49CCCB40811C7ULL, 0x5CBD6CC0CC10FAFCULL,
+	0x1E4D8D2B65FACC6FULL, 0xD95CAF179FC497DAULL, 0x9BAC4EFC362EA149ULL,
+	0x158E0A85C2521623ULL, 0x577EEB6E6BB820B0ULL, 0x906FC95291867B05ULL,
+	0xD29F28B9386C4D96ULL, 0xCEDBA04AD0952342ULL, 0x8C2B41A1797F15D1ULL,
+	0x4B3A639D83414E64ULL, 0x09CA82762AAB78F7ULL, 0x87E8C60FDED7CF9DULL,
+	0xC51827E4773DF90EULL, 0x020905D88D03A2BBULL, 0x40F9E43324E99428ULL,
+	0x2CFFE7D5975E55E2ULL, 0x6E0F063E3EB46371ULL, 0xA91E2402C48A38C4ULL,
+	0xEBEEC5E96D600E57ULL, 0x65CC8190991CB93DULL, 0x273C607B30F68FAEULL,
+	0xE02D4247CAC8D41BULL, 0xA2DDA3AC6322E288ULL, 0xBE992B5F8BDB8C5CULL,
+	0xFC69CAB42231BACFULL, 0x3B78E888D80FE17AULL, 0x7988096371E5D7E9ULL,
+	0xF7AA4D1A85996083ULL, 0xB55AACF12C735610ULL, 0x724B8ECDD64D0DA5ULL,
+	0x30BB6F267FA73B36ULL, 0x4AC29F2A07BFD00DULL, 0x08327EC1AE55E69EULL,
+	0xCF235CFD546BBD2BULL, 0x8DD3BD16FD818BB8ULL, 0x03F1F96F09FD3CD2ULL,
+	0x41011884A0170A41ULL, 0x86103AB85A2951F4ULL, 0xC4E0DB53F3C36767ULL,
+	0xD8A453A01B3A09B3ULL, 0x9A54B24BB2D03F20ULL, 0x5D45907748EE6495ULL,
+	0x1FB5719CE1045206ULL, 0x919735E51578E56CULL, 0xD367D40EBC92D3FFULL,
+	0x1476F63246AC884AULL, 0x568617D9EF46BED9ULL, 0xE085162AB69D5E3CULL,
+	0xA275F7C11F7768AFULL, 0x6564D5FDE549331AULL, 0x279434164CA30589ULL,
+	0xA9B6706FB8DFB2E3ULL, 0xEB46918411358470ULL, 0x2C57B3B8EB0BDFC5ULL,
+	0x6EA7525342E1E956ULL, 0x72E3DAA0AA188782ULL, 0x30133B4B03F2B111ULL,
+	0xF7021977F9CCEAA4ULL, 0xB5F2F89C5026DC37ULL, 0x3BD0BCE5A45A6B5DULL,
+	0x79205D0E0DB05DCEULL, 0xBE317F32F78E067BULL, 0xFCC19ED95E6430E8ULL,
+	0x86B86ED5267CDBD3ULL, 0xC4488F3E8F96ED40ULL, 0x0359AD0275A8B6F5ULL,
+	0x41A94CE9DC428066ULL, 0xCF8B0890283E370CULL, 0x8D7BE97B81D4019FULL,
+	0x4A6ACB477BEA5A2AULL, 0x089A2AACD2006CB9ULL, 0x14DEA25F3AF9026DULL,
+	0x562E43B4931334FEULL, 0x913F6188692D6F4BULL, 0xD3CF8063C0C759D8ULL,
+	0x5DEDC41A34BBEEB2ULL, 0x1F1D25F19D51D821ULL, 0xD80C07CD676F8394ULL,
+	0x9AFCE626CE85B507ULL
+};
+
+/*
+ * This a generic crc64() function, it takes seed as an argument,
+ * and does __not__ xor at the end. Then individual users can do
+ * whatever they need.
+ */
+uint64_t crc64(uint64_t seed, const unsigned char *data, size_t len)
+{
+	uint64_t crc = seed;
+
+	while (len) {
+		int i = ((int) (crc >> 56) ^ *data++) & 0xFF;
+		crc = crc64_tab[i] ^ (crc << 8);
+		len--;
+	}
+
+	return crc;
+}
+
diff --git a/libblkid/lib/env.c b/libblkid/lib/env.c
new file mode 100644
index 0000000..c79e0e0
--- /dev/null
+++ b/libblkid/lib/env.c
@@ -0,0 +1,110 @@
+/*
+ * Security checks of environment
+ * Added from shadow-utils package
+ * by Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#else
+#define PR_GET_DUMPABLE 3
+#endif
+#if (!defined(HAVE_PRCTL) && defined(linux))
+#include <sys/syscall.h>
+#endif
+#include <unistd.h>
+#include <sys/types.h>
+
+#include "env.h"
+
+#ifndef HAVE_ENVIRON_DECL
+extern char **environ;
+#endif
+
+static char * const forbid[] = {
+        "_RLD_=",
+        "BASH_ENV=",    /* GNU creeping featurism strikes again... */
+        "ENV=",
+        "HOME=",
+        "IFS=",
+        "KRB_CONF=",
+        "LD_",          /* anything with the LD_ prefix */
+        "LIBPATH=",
+        "MAIL=",
+        "NLSPATH=",
+        "PATH=",
+        "SHELL=",
+        "SHLIB_PATH=",
+        (char *) 0
+};
+
+/* these are allowed, but with no slashes inside
+   (to work around security problems in GNU gettext) */
+static char * const noslash[] = {
+        "LANG=",
+        "LANGUAGE=",
+        "LC_",          /* anything with the LC_ prefix */
+        (char *) 0
+};
+
+void
+sanitize_env(void)
+{
+        char **envp = environ;
+        char * const *bad;
+        char **cur;
+        char **move;
+
+        for (cur = envp; *cur; cur++) {
+                for (bad = forbid; *bad; bad++) {
+                        if (strncmp(*cur, *bad, strlen(*bad)) == 0) {
+                                for (move = cur; *move; move++)
+                                        *move = *(move + 1);
+                                cur--;
+                                break;
+                        }
+                }
+        }
+
+        for (cur = envp; *cur; cur++) {
+                for (bad = noslash; *bad; bad++) {
+                        if (strncmp(*cur, *bad, strlen(*bad)) != 0)
+                                continue;
+                        if (!strchr(*cur, '/'))
+                                continue;  /* OK */
+                        for (move = cur; *move; move++)
+                                *move = *(move + 1);
+                        cur--;
+                        break;
+                }
+        }
+}
+
+
+char *safe_getenv(const char *arg)
+{
+	uid_t ruid = getuid();
+
+	if (ruid != 0 || (ruid != geteuid()) || (getgid() != getegid()))
+		return NULL;
+#ifdef HAVE_PRCTL
+	if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 0)
+		return NULL;
+#else
+#if (defined(linux) && defined(SYS_prctl))
+	if (syscall(SYS_prctl, PR_GET_DUMPABLE, 0, 0, 0, 0) == 0)
+		return NULL;
+#endif
+#endif
+#ifdef HAVE_SECURE_GETENV
+return secure_getenv(arg);
+#elif HAVE___SECURE_GETENV
+	return __secure_getenv(arg);
+#else
+	return getenv(arg);
+#endif
+}
diff --git a/libblkid/lib/exec_shell.c b/libblkid/lib/exec_shell.c
new file mode 100644
index 0000000..2b723ac
--- /dev/null
+++ b/libblkid/lib/exec_shell.c
@@ -0,0 +1,47 @@
+/*
+ * exec_shell() - launch a shell, else exit!
+ *
+ * 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 the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <libgen.h>
+
+#include "nls.h"
+#include "c.h"
+#include "xalloc.h"
+
+#include "exec_shell.h"
+
+#define DEFAULT_SHELL "/bin/sh"
+
+void exec_shell(void)
+{
+	const char *shell = getenv("SHELL"), *shell_basename;
+	char *arg0;
+	if (!shell)
+		shell = DEFAULT_SHELL;
+
+	shell_basename = basename(shell);
+	arg0 = xmalloc(strlen(shell_basename) + 2);
+	arg0[0] = '-';
+	strcpy(arg0 + 1, shell_basename);
+
+	execl(shell, arg0, NULL);
+	err(EXIT_FAILURE, _("failed to execute %s"), shell);
+}
diff --git a/libblkid/lib/fileutils.c b/libblkid/lib/fileutils.c
new file mode 100644
index 0000000..92a391d
--- /dev/null
+++ b/libblkid/lib/fileutils.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2012 Sami Kerola <kerolasa@iki.fi>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <paths.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/limits.h>
+
+#include "c.h"
+#include "fileutils.h"
+#include "pathnames.h"
+
+#ifndef _PATH_TMP
+#define _PATH_TMP   "/tmp/"
+#endif
+
+#ifndef OPEN_MAX
+#define OPEN_MAX 256
+#endif
+
+/* Create open temporary file in safe way.  Please notice that the
+ * file permissions are -rw------- by default. */
+int xmkstemp(char **tmpname, char *dir)
+{
+	char *localtmp;
+	char *tmpenv;
+	mode_t old_mode;
+	int fd, rc;
+
+	/* Some use cases must be capable of being moved atomically
+	 * with rename(2), which is the reason why dir is here.  */
+	if (dir != NULL)
+		tmpenv = dir;
+	else
+		tmpenv = getenv("TMPDIR");
+
+	if (tmpenv)
+		rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv,
+			  program_invocation_short_name);
+	else
+		rc = asprintf(&localtmp, "%s/%s.XXXXXX", _PATH_TMP,
+			  program_invocation_short_name);
+
+	if (rc < 0)
+		return -1;
+
+	old_mode = umask(077);
+	fd = mkstemp(localtmp);
+	umask(old_mode);
+	if (fd == -1) {
+		free(localtmp);
+		localtmp = NULL;
+	}
+	*tmpname = localtmp;
+	return fd;
+}
+
+/*
+ * portable getdtablesize()
+ */
+int get_fd_tabsize(void)
+{
+	int m;
+
+#if defined(HAVE_GETDTABLESIZE)
+	m = getdtablesize();
+#elif defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
+	struct rlimit rl;
+
+	getrlimit(RLIMIT_NOFILE, &rl);
+	m = rl.rlim_cur;
+#elif defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX)
+	m = sysconf(_SC_OPEN_MAX);
+#else
+	m = OPEN_MAX;
+#endif
+	return m;
+}
+
+#ifdef TEST_PROGRAM
+int main(void)
+{
+	FILE *f;
+	char *tmpname;
+	f = xfmkstemp(&tmpname, NULL);
+	unlink(tmpname);
+	free(tmpname);
+	fclose(f);
+	return EXIT_FAILURE;
+}
+#endif
+
+
+int mkdir_p(const char *path, mode_t mode)
+{
+	char *p, *dir;
+	int rc = 0;
+
+	if (!path || !*path)
+		return -EINVAL;
+
+	dir = p = strdup(path);
+	if (!dir)
+		return -ENOMEM;
+
+	if (*p == '/')
+		p++;
+
+	while (p && *p) {
+		char *e = strchr(p, '/');
+		if (e)
+			*e = '\0';
+		if (*p) {
+			rc = mkdir(dir, mode);
+			if (rc && errno != EEXIST)
+				break;
+			rc = 0;
+		}
+		if (!e)
+			break;
+		*e = '/';
+		p = e + 1;
+	}
+
+	free(dir);
+	return rc;
+}
+
+/* returns basename and keeps dirname in the @path, if @path is "/" (root)
+ * then returns empty string */
+char *stripoff_last_component(char *path)
+{
+	char *p = path ? strrchr(path, '/') : NULL;
+
+	if (!p)
+		return NULL;
+	*p = '\0';
+	return p + 1;
+}
diff --git a/libblkid/lib/ismounted.c b/libblkid/lib/ismounted.c
new file mode 100644
index 0000000..00b575c
--- /dev/null
+++ b/libblkid/lib/ismounted.c
@@ -0,0 +1,388 @@
+/*
+ * ismounted.c --- Check to see if the filesystem was mounted
+ *
+ * Copyright (C) 1995,1996,1997,1998,1999,2000,2008 Theodore Ts'o.
+ *
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_MNTENT_H
+#include <mntent.h>
+#endif
+#include <string.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <sys/param.h>
+#ifdef __APPLE__
+#include <sys/ucred.h>
+#include <sys/mount.h>
+#endif
+
+#include "pathnames.h"
+#include "ismounted.h"
+#include "c.h"
+#ifdef __linux__
+# include "loopdev.h"
+#endif
+
+
+
+#ifdef HAVE_MNTENT_H
+/*
+ * Helper function which checks a file in /etc/mtab format to see if a
+ * filesystem is mounted.  Returns an error if the file doesn't exist
+ * or can't be opened.
+ */
+static int check_mntent_file(const char *mtab_file, const char *file,
+				   int *mount_flags, char *mtpt, int mtlen)
+{
+	struct mntent	*mnt;
+	struct stat	st_buf;
+	int		retval = 0;
+	dev_t		file_dev=0, file_rdev=0;
+	ino_t		file_ino=0;
+	FILE		*f;
+	int		fd;
+
+	*mount_flags = 0;
+	if ((f = setmntent (mtab_file, "r")) == NULL)
+		return errno;
+
+	if (stat(file, &st_buf) == 0) {
+		if (S_ISBLK(st_buf.st_mode)) {
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+			file_rdev = st_buf.st_rdev;
+#endif	/* __GNU__ */
+		} else {
+			file_dev = st_buf.st_dev;
+			file_ino = st_buf.st_ino;
+		}
+	}
+
+	while ((mnt = getmntent (f)) != NULL) {
+		if (mnt->mnt_fsname[0] != '/')
+			continue;
+		if (strcmp(file, mnt->mnt_fsname) == 0)
+			break;
+		if (stat(mnt->mnt_fsname, &st_buf) != 0)
+			continue;
+
+		if (S_ISBLK(st_buf.st_mode)) {
+#ifndef __GNU__
+			if (file_rdev && file_rdev == st_buf.st_rdev)
+				break;
+#ifdef __linux__
+			/* maybe the file is loopdev backing file */
+			if (file_dev
+			    && major(st_buf.st_rdev) == LOOPDEV_MAJOR
+			    && loopdev_is_used(mnt->mnt_fsname, file, 0, 0))
+				break;
+#endif /* __linux__ */
+#endif	/* __GNU__ */
+		} else {
+			if (file_dev && ((file_dev == st_buf.st_dev) &&
+					 (file_ino == st_buf.st_ino)))
+				break;
+		}
+	}
+
+	if (mnt == NULL) {
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+		/*
+		 * Do an extra check to see if this is the root device.  We
+		 * can't trust /etc/mtab, and /proc/mounts will only list
+		 * /dev/root for the root filesystem.  Argh.  Instead we
+		 * check if the given device has the same major/minor number
+		 * as the device that the root directory is on.
+		 */
+		if (file_rdev && stat("/", &st_buf) == 0 &&
+		    st_buf.st_dev == file_rdev) {
+			*mount_flags = MF_MOUNTED;
+			if (mtpt)
+				strncpy(mtpt, "/", mtlen);
+			goto is_root;
+		}
+#endif	/* __GNU__ */
+		goto errout;
+	}
+#ifndef __GNU__ /* The GNU hurd is deficient; what else is new? */
+	/* Validate the entry in case /etc/mtab is out of date */
+	/*
+	 * We need to be paranoid, because some broken distributions
+	 * (read: Slackware) don't initialize /etc/mtab before checking
+	 * all of the non-root filesystems on the disk.
+	 */
+	if (stat(mnt->mnt_dir, &st_buf) < 0) {
+		retval = errno;
+		if (retval == ENOENT) {
+#ifdef DEBUG
+			printf("Bogus entry in %s!  (%s does not exist)\n",
+			       mtab_file, mnt->mnt_dir);
+#endif /* DEBUG */
+			retval = 0;
+		}
+		goto errout;
+	}
+	if (file_rdev && (st_buf.st_dev != file_rdev)) {
+#ifdef DEBUG
+		printf("Bogus entry in %s!  (%s not mounted on %s)\n",
+		       mtab_file, file, mnt->mnt_dir);
+#endif /* DEBUG */
+		goto errout;
+	}
+#endif /* __GNU__ */
+	*mount_flags = MF_MOUNTED;
+
+#ifdef MNTOPT_RO
+	/* Check to see if the ro option is set */
+	if (hasmntopt(mnt, MNTOPT_RO))
+		*mount_flags |= MF_READONLY;
+#endif
+
+	if (mtpt)
+		strncpy(mtpt, mnt->mnt_dir, mtlen);
+	/*
+	 * Check to see if we're referring to the root filesystem.
+	 * If so, do a manual check to see if we can open /etc/mtab
+	 * read/write, since if the root is mounted read/only, the
+	 * contents of /etc/mtab may not be accurate.
+	 */
+	if (!strcmp(mnt->mnt_dir, "/")) {
+is_root:
+#define TEST_FILE "/.ismount-test-file"
+		*mount_flags |= MF_ISROOT;
+		fd = open(TEST_FILE, O_RDWR|O_CREAT|O_CLOEXEC, 0600);
+		if (fd < 0) {
+			if (errno == EROFS)
+				*mount_flags |= MF_READONLY;
+		} else
+			close(fd);
+		(void) unlink(TEST_FILE);
+	}
+	retval = 0;
+errout:
+	endmntent (f);
+	return retval;
+}
+
+static int check_mntent(const char *file, int *mount_flags,
+			      char *mtpt, int mtlen)
+{
+	int	retval;
+
+#ifdef DEBUG
+	retval = check_mntent_file("/tmp/mtab", file, mount_flags,
+				   mtpt, mtlen);
+	if (retval == 0)
+		return 0;
+#endif /* DEBUG */
+#ifdef __linux__
+	retval = check_mntent_file("/proc/mounts", file, mount_flags,
+				   mtpt, mtlen);
+	if (retval == 0 && (*mount_flags != 0))
+		return 0;
+	if (access("/proc/mounts", R_OK) == 0) {
+		*mount_flags = 0;
+		return retval;
+	}
+#endif /* __linux__ */
+#if defined(MOUNTED) || defined(_PATH_MOUNTED)
+#ifndef MOUNTED
+#define MOUNTED _PATH_MOUNTED
+#endif /* MOUNTED */
+	retval = check_mntent_file(MOUNTED, file, mount_flags, mtpt, mtlen);
+	return retval;
+#else
+	*mount_flags = 0;
+	return 0;
+#endif /* defined(MOUNTED) || defined(_PATH_MOUNTED) */
+}
+
+#else
+#if defined(HAVE_GETMNTINFO)
+
+static int check_getmntinfo(const char *file, int *mount_flags,
+				  char *mtpt, int mtlen)
+{
+	struct statfs *mp;
+        int    len, n;
+        const  char   *s1;
+	char	*s2;
+
+        n = getmntinfo(&mp, MNT_NOWAIT);
+        if (n == 0)
+		return errno;
+
+        len = sizeof(_PATH_DEV) - 1;
+        s1 = file;
+        if (strncmp(_PATH_DEV, s1, len) == 0)
+                s1 += len;
+
+	*mount_flags = 0;
+        while (--n >= 0) {
+                s2 = mp->f_mntfromname;
+                if (strncmp(_PATH_DEV, s2, len) == 0) {
+                        s2 += len - 1;
+                        *s2 = 'r';
+                }
+                if (strcmp(s1, s2) == 0 || strcmp(s1, &s2[1]) == 0) {
+			*mount_flags = MF_MOUNTED;
+			break;
+		}
+                ++mp;
+	}
+	if (mtpt)
+		strncpy(mtpt, mp->f_mntonname, mtlen);
+	return 0;
+}
+#endif /* HAVE_GETMNTINFO */
+#endif /* HAVE_MNTENT_H */
+
+/*
+ * Check to see if we're dealing with the swap device.
+ */
+static int is_swap_device(const char *file)
+{
+	FILE		*f;
+	char		buf[1024], *cp;
+	dev_t		file_dev;
+	struct stat	st_buf;
+	int		ret = 0;
+
+	file_dev = 0;
+#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */
+	if ((stat(file, &st_buf) == 0) &&
+	    S_ISBLK(st_buf.st_mode))
+		file_dev = st_buf.st_rdev;
+#endif	/* __GNU__ */
+
+	if (!(f = fopen("/proc/swaps", "r")))
+		return 0;
+	/* Skip the first line */
+	if (!fgets(buf, sizeof(buf), f))
+		goto leave;
+	if (*buf && strncmp(buf, "Filename\t", 9))
+		/* Linux <=2.6.19 contained a bug in the /proc/swaps
+		 * code where the header would not be displayed
+		 */
+		goto valid_first_line;
+
+	while (fgets(buf, sizeof(buf), f)) {
+valid_first_line:
+		if ((cp = strchr(buf, ' ')) != NULL)
+			*cp = 0;
+		if ((cp = strchr(buf, '\t')) != NULL)
+			*cp = 0;
+		if (strcmp(buf, file) == 0) {
+			ret++;
+			break;
+		}
+#ifndef __GNU__
+		if (file_dev && (stat(buf, &st_buf) == 0) &&
+		    S_ISBLK(st_buf.st_mode) &&
+		    file_dev == st_buf.st_rdev) {
+			ret++;
+			break;
+		}
+#endif	/* __GNU__ */
+	}
+
+leave:
+	fclose(f);
+	return ret;
+}
+
+
+/*
+ * check_mount_point() fills determines if the device is mounted or otherwise
+ * busy, and fills in mount_flags with one or more of the following flags:
+ * MF_MOUNTED, MF_ISROOT, MF_READONLY, MF_SWAP, and MF_BUSY.  If mtpt is
+ * non-NULL, the directory where the device is mounted is copied to where mtpt
+ * is pointing, up to mtlen characters.
+ */
+#ifdef __TURBOC__
+ #pragma argsused
+#endif
+int check_mount_point(const char *device, int *mount_flags,
+				  char *mtpt, int mtlen)
+{
+	struct stat	st_buf;
+	int	retval = 0;
+	int		fd;
+
+	if (is_swap_device(device)) {
+		*mount_flags = MF_MOUNTED | MF_SWAP;
+		if (mtpt && mtlen)
+			strncpy(mtpt, "[SWAP]", mtlen);
+	} else {
+#ifdef HAVE_MNTENT_H
+		retval = check_mntent(device, mount_flags, mtpt, mtlen);
+#else
+#ifdef HAVE_GETMNTINFO
+		retval = check_getmntinfo(device, mount_flags, mtpt, mtlen);
+#else
+#ifdef __GNUC__
+ //#warning "Can't use getmntent or getmntinfo to check for mounted filesystems!"
+#endif
+		*mount_flags = 0;
+#endif /* HAVE_GETMNTINFO */
+#endif /* HAVE_MNTENT_H */
+	}
+	if (retval)
+		return retval;
+
+#ifdef __linux__ /* This only works on Linux 2.6+ systems */
+	if ((stat(device, &st_buf) != 0) ||
+	    !S_ISBLK(st_buf.st_mode))
+		return 0;
+	fd = open(device, O_RDONLY|O_EXCL|O_CLOEXEC);
+	if (fd < 0) {
+		if (errno == EBUSY)
+			*mount_flags |= MF_BUSY;
+	} else
+		close(fd);
+#endif
+
+	return 0;
+}
+
+int is_mounted(const char *file)
+{
+	int	retval;
+	int	mount_flags = 0;
+
+	retval = check_mount_point(file, &mount_flags, NULL, 0);
+	if (retval)
+		return 0;
+	return mount_flags & MF_MOUNTED;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+	int flags = 0;
+	char devname[PATH_MAX];
+
+	if (argc < 2) {
+		fprintf(stderr, "Usage: %s device\n", argv[0]);
+		return EXIT_FAILURE;
+	}
+
+	if (check_mount_point(argv[1], &flags, devname, sizeof(devname)) == 0 &&
+	    (flags & MF_MOUNTED)) {
+		if (flags & MF_SWAP)
+			printf("used swap device\n");
+		else
+			printf("mounted on %s\n", devname);
+		return EXIT_SUCCESS;
+	}
+
+	printf("not mounted\n");
+	return EXIT_FAILURE;
+}
+#endif /* DEBUG */
diff --git a/libblkid/lib/langinfo.c b/libblkid/lib/langinfo.c
new file mode 100644
index 0000000..deeab9b
--- /dev/null
+++ b/libblkid/lib/langinfo.c
@@ -0,0 +1,121 @@
+/*
+ * This is callback solution for systems without nl_langinfo(), this function
+ * returns hardcoded and on locale setting independed value.
+ *
+ * See langinfo.h man page for more details.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include "nls.h"
+
+char *langinfo_fallback(nl_item item)
+{
+	switch (item) {
+	case CODESET:
+		return "ISO-8859-1";
+	case THOUSEP:
+		return ",";
+	case D_T_FMT:
+	case ERA_D_T_FMT:
+		return "%a %b %e %H:%M:%S %Y";
+	case D_FMT:
+	case ERA_D_FMT:
+		return "%m/%d/%y";
+	case T_FMT:
+	case ERA_T_FMT:
+		return "%H:%M:%S";
+	case T_FMT_AMPM:
+		return "%I:%M:%S %p";
+	case AM_STR:
+		return "AM";
+	case PM_STR:
+		return "PM";
+	case DAY_1:
+		return "Sunday";
+	case DAY_2:
+		return "Monday";
+	case DAY_3:
+		return "Tuesday";
+	case DAY_4:
+		return "Wednesday";
+	case DAY_5:
+		return "Thursday";
+	case DAY_6:
+		return "Friday";
+	case DAY_7:
+		return "Saturday";
+	case ABDAY_1:
+		return "Sun";
+	case ABDAY_2:
+		return "Mon";
+	case ABDAY_3:
+		return "Tue";
+	case ABDAY_4:
+		return "Wed";
+	case ABDAY_5:
+		return "Thu";
+	case ABDAY_6:
+		return "Fri";
+	case ABDAY_7:
+		return "Sat";
+	case MON_1:
+		return "January";
+	case MON_2:
+		return "February";
+	case MON_3:
+		return "March";
+	case MON_4:
+		return "April";
+	case MON_5:
+		return "May";
+	case MON_6:
+		return "June";
+	case MON_7:
+		return "July";
+	case MON_8:
+		return "August";
+	case MON_9:
+		return "September";
+	case MON_10:
+		return "October";
+	case MON_11:
+		return "November";
+	case MON_12:
+		return "December";
+	case ABMON_1:
+		return "Jan";
+	case ABMON_2:
+		return "Feb";
+	case ABMON_3:
+		return "Mar";
+	case ABMON_4:
+		return "Apr";
+	case ABMON_5:
+		return "May";
+	case ABMON_6:
+		return "Jun";
+	case ABMON_7:
+		return "Jul";
+	case ABMON_8:
+		return "Aug";
+	case ABMON_9:
+		return "Sep";
+	case ABMON_10:
+		return "Oct";
+	case ABMON_11:
+		return "Nov";
+	case ABMON_12:
+		return "Dec";
+	case ALT_DIGITS:
+		return "\0\0\0\0\0\0\0\0\0\0";
+	case CRNCYSTR:
+		return "-";
+	case YESEXPR:
+		return "^[yY]";
+	case NOEXPR:
+		return "^[nN]";
+	default:
+		return "";
+	}
+}
+
diff --git a/libblkid/lib/linux_version.c b/libblkid/lib/linux_version.c
new file mode 100644
index 0000000..2bcc2cc
--- /dev/null
+++ b/libblkid/lib/linux_version.c
@@ -0,0 +1,25 @@
+#include <stdio.h>
+#include <sys/utsname.h>
+
+#include "linux_version.h"
+
+int get_linux_version (void)
+{
+	static int kver = -1;
+	struct utsname uts;
+	int major = 0;
+	int minor = 0;
+	int teeny = 0;
+	int n;
+
+	if (kver != -1)
+		return kver;
+	if (uname (&uts))
+		return kver = 0;
+
+	n = sscanf(uts.release, "%d.%d.%d", &major, &minor, &teeny);
+	if (n < 1 || n > 3)
+		return kver = 0;
+
+	return kver = KERNEL_VERSION(major, minor, teeny);
+}
diff --git a/libblkid/lib/loopdev.c b/libblkid/lib/loopdev.c
new file mode 100644
index 0000000..09b9bbf
--- /dev/null
+++ b/libblkid/lib/loopdev.c
@@ -0,0 +1,1572 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ *
+ * -- based on mount/losetup.c
+ *
+ * Simple library for work with loop devices.
+ *
+ *  - requires kernel 2.6.x
+ *  - reads info from /sys/block/loop<N>/loop/<attr> (new kernels)
+ *  - reads info by ioctl
+ *  - supports *unlimited* number of loop devices
+ *  - supports /dev/loop<N> as well as /dev/loop/<N>
+ *  - minimize overhead (fd, loopinfo, ... are shared for all operations)
+ *  - setup (associate device and backing file)
+ *  - delete (dis-associate file)
+ *  - old LOOP_{SET,GET}_STATUS (32bit) ioctls are unsupported
+ *  - extendible
+ */
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/sysmacros.h>
+#include <inttypes.h>
+#include <dirent.h>
+#include <linux/posix_types.h>
+
+#include "linux_version.h"
+#include "c.h"
+#include "sysfs.h"
+#include "pathnames.h"
+#include "loopdev.h"
+#include "canonicalize.h"
+#include "at.h"
+#include "blkdev.h"
+#include "debug.h"
+
+/*
+ * Debug stuff (based on include/debug.h)
+ */
+UL_DEBUG_DEFINE_MASK(loopdev);
+UL_DEBUG_DEFINE_MASKNAMES(loopdev) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define LOOPDEV_DEBUG_INIT	(1 << 1)
+#define LOOPDEV_DEBUG_CXT	(1 << 2)
+#define LOOPDEV_DEBUG_ITER	(1 << 3)
+#define LOOPDEV_DEBUG_SETUP	(1 << 4)
+#define SFDISKPROG_DEBUG_ALL	0xFFFF
+
+#define DBG(m, x)       __UL_DBG(loopdev, LOOPDEV_DEBUG_, m, x)
+#define ON_DBG(m, x)    __UL_DBG_CALL(loopdev, LOOPDEV_DEBUG_, m, x)
+
+static void loopdev_init_debug(void)
+{
+	if (loopdev_debug_mask)
+		return;
+	__UL_INIT_DEBUG(loopdev, LOOPDEV_DEBUG_, 0, LOOPDEV_DEBUG);
+}
+
+/*
+ * see loopcxt_init()
+ */
+#define loopcxt_ioctl_enabled(_lc)	(!((_lc)->flags & LOOPDEV_FL_NOIOCTL))
+#define loopcxt_sysfs_available(_lc)	(!((_lc)->flags & LOOPDEV_FL_NOSYSFS)) \
+					 && !loopcxt_ioctl_enabled(_lc)
+
+/*
+ * @lc: context
+ * @device: device name, absolute device path or NULL to reset the current setting
+ *
+ * Sets device, absolute paths (e.g. "/dev/loop<N>") are unchanged, device
+ * names ("loop<N>") are converted to the path (/dev/loop<N> or to
+ * /dev/loop/<N>)
+ *
+ * This sets the device name, but does not check if the device exists!
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_set_device(struct loopdev_cxt *lc, const char *device)
+{
+	if (!lc)
+		return -EINVAL;
+
+	if (lc->fd >= 0) {
+		close(lc->fd);
+		DBG(CXT, ul_debugobj(lc, "closing old open fd"));
+	}
+	lc->fd = -1;
+	lc->mode = 0;
+	lc->has_info = 0;
+	lc->info_failed = 0;
+	*lc->device = '\0';
+	memset(&lc->info, 0, sizeof(lc->info));
+
+	/* set new */
+	if (device) {
+		if (*device != '/') {
+			const char *dir = _PATH_DEV;
+
+			/* compose device name for /dev/loop<n> or /dev/loop/<n> */
+			if (lc->flags & LOOPDEV_FL_DEVSUBDIR) {
+				if (strlen(device) < 5)
+					return -1;
+				device += 4;
+				dir = _PATH_DEV_LOOP "/";	/* _PATH_DEV uses tailing slash */
+			}
+			snprintf(lc->device, sizeof(lc->device), "%s%s",
+				dir, device);
+		} else {
+			strncpy(lc->device, device, sizeof(lc->device));
+			lc->device[sizeof(lc->device) - 1] = '\0';
+		}
+		DBG(CXT, ul_debugobj(lc, "%s name assigned", device));
+	}
+
+	sysfs_deinit(&lc->sysfs);
+	return 0;
+}
+
+int loopcxt_has_device(struct loopdev_cxt *lc)
+{
+	return lc && *lc->device;
+}
+
+/*
+ * @lc: context
+ * @flags: LOOPDEV_FL_* flags
+ *
+ * Initilize loop handler.
+ *
+ * We have two sets of the flags:
+ *
+ *	* LOOPDEV_FL_* flags control loopcxt_* API behavior
+ *
+ *	* LO_FLAGS_* are kernel flags used for LOOP_{SET,GET}_STAT64 ioctls
+ *
+ * Note about LOOPDEV_FL_{RDONLY,RDWR} flags. These flags are used for open(2)
+ * syscall to open loop device. By default is the device open read-only.
+ *
+ * The expection is loopcxt_setup_device(), where the device is open read-write
+ * if LO_FLAGS_READ_ONLY flags is not set (see loopcxt_set_flags()).
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int loopcxt_init(struct loopdev_cxt *lc, int flags)
+{
+	int rc;
+	struct stat st;
+	struct loopdev_cxt dummy = UL_LOOPDEVCXT_EMPTY;
+
+	if (!lc)
+		return -EINVAL;
+
+	loopdev_init_debug();
+	DBG(CXT, ul_debugobj(lc, "initialize context"));
+
+	memcpy(lc, &dummy, sizeof(dummy));
+	lc->flags = flags;
+
+	rc = loopcxt_set_device(lc, NULL);
+	if (rc)
+		return rc;
+
+	if (stat(_PATH_SYS_BLOCK, &st) || !S_ISDIR(st.st_mode)) {
+		lc->flags |= LOOPDEV_FL_NOSYSFS;
+		lc->flags &= ~LOOPDEV_FL_NOIOCTL;
+		DBG(CXT, ul_debugobj(lc, "init: disable /sys usage"));
+	}
+
+	if (!(lc->flags & LOOPDEV_FL_NOSYSFS) &&
+	    get_linux_version() >= KERNEL_VERSION(2,6,37)) {
+		/*
+		 * Use only sysfs for basic information about loop devices
+		 */
+		lc->flags |= LOOPDEV_FL_NOIOCTL;
+		DBG(CXT, ul_debugobj(lc, "init: ignore ioctls"));
+	}
+
+	if (!(lc->flags & LOOPDEV_FL_CONTROL) && !stat(_PATH_DEV_LOOPCTL, &st)) {
+		lc->flags |= LOOPDEV_FL_CONTROL;
+		DBG(CXT, ul_debugobj(lc, "init: loop-control detected "));
+	}
+
+	return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Deinitialize loop context
+ */
+void loopcxt_deinit(struct loopdev_cxt *lc)
+{
+	int errsv = errno;
+
+	if (!lc)
+		return;
+
+	DBG(CXT, ul_debugobj(lc, "de-initialize"));
+
+	free(lc->filename);
+	lc->filename = NULL;
+
+	ignore_result( loopcxt_set_device(lc, NULL) );
+	loopcxt_deinit_iterator(lc);
+
+	errno = errsv;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns newly allocated device path.
+ */
+char *loopcxt_strdup_device(struct loopdev_cxt *lc)
+{
+	if (!lc || !*lc->device)
+		return NULL;
+	return strdup(lc->device);
+}
+
+/*
+ * @lc: context
+ *
+ * Returns pointer device name in the @lc struct.
+ */
+const char *loopcxt_get_device(struct loopdev_cxt *lc)
+{
+	return lc && *lc->device ? lc->device : NULL;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns pointer to the sysfs context (see lib/sysfs.c)
+ */
+struct sysfs_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc)
+{
+	if (!lc || !*lc->device || (lc->flags & LOOPDEV_FL_NOSYSFS))
+		return NULL;
+
+	if (!lc->sysfs.devno) {
+		dev_t devno = sysfs_devname_to_devno(lc->device, NULL);
+		if (!devno) {
+			DBG(CXT, ul_debugobj(lc, "sysfs: failed devname to devno"));
+			return NULL;
+		}
+		if (sysfs_init(&lc->sysfs, devno, NULL)) {
+			DBG(CXT, ul_debugobj(lc, "sysfs: init failed"));
+			return NULL;
+		}
+	}
+
+	return &lc->sysfs;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: file descriptor to the open loop device or <0 on error. The mode
+ *          depends on LOOPDEV_FL_{RDWR,RDONLY} context flags. Default is
+ *          read-only.
+ */
+int loopcxt_get_fd(struct loopdev_cxt *lc)
+{
+	if (!lc || !*lc->device)
+		return -EINVAL;
+
+	if (lc->fd < 0) {
+		lc->mode = lc->flags & LOOPDEV_FL_RDWR ? O_RDWR : O_RDONLY;
+		lc->fd = open(lc->device, lc->mode | O_CLOEXEC);
+		DBG(CXT, ul_debugobj(lc, "open %s [%s]: %m", lc->device,
+				lc->flags & LOOPDEV_FL_RDWR ? "rw" : "ro"));
+	}
+	return lc->fd;
+}
+
+int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode)
+{
+	if (!lc)
+		return -EINVAL;
+
+	lc->fd = fd;
+	lc->mode = mode;
+	return 0;
+}
+
+/*
+ * @lc: context
+ * @flags: LOOPITER_FL_* flags
+ *
+ * Iterator allows to scan list of the free or used loop devices.
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_init_iterator(struct loopdev_cxt *lc, int flags)
+{
+	struct loopdev_iter *iter;
+	struct stat st;
+
+	if (!lc)
+		return -EINVAL;
+
+
+	iter = &lc->iter;
+	DBG(ITER, ul_debugobj(iter, "initialize"));
+
+	/* always zeroize
+	 */
+	memset(iter, 0, sizeof(*iter));
+	iter->ncur = -1;
+	iter->flags = flags;
+	iter->default_check = 1;
+
+	if (!lc->extra_check) {
+		/*
+		 * Check for /dev/loop/<N> subdirectory
+		 */
+		if (!(lc->flags & LOOPDEV_FL_DEVSUBDIR) &&
+		    stat(_PATH_DEV_LOOP, &st) == 0 && S_ISDIR(st.st_mode))
+			lc->flags |= LOOPDEV_FL_DEVSUBDIR;
+
+		lc->extra_check = 1;
+	}
+	return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_deinit_iterator(struct loopdev_cxt *lc)
+{
+	struct loopdev_iter *iter;
+
+	if (!lc)
+		return -EINVAL;
+
+	iter = &lc->iter;
+	DBG(ITER, ul_debugobj(iter, "de-initialize"));
+
+	free(iter->minors);
+	if (iter->proc)
+		fclose(iter->proc);
+	if (iter->sysblock)
+		closedir(iter->sysblock);
+	iter->minors = NULL;
+	iter->proc = NULL;
+	iter->sysblock = NULL;
+	iter->done = 1;
+	return 0;
+}
+
+/*
+ * Same as loopcxt_set_device, but also checks if the device is
+ * associeted with any file.
+ *
+ * Returns: <0 on error, 0 on success, 1 device does not match with
+ *         LOOPITER_FL_{USED,FREE} flags.
+ */
+static int loopiter_set_device(struct loopdev_cxt *lc, const char *device)
+{
+	int rc = loopcxt_set_device(lc, device);
+	int used;
+
+	if (rc)
+		return rc;
+
+	if (!(lc->iter.flags & LOOPITER_FL_USED) &&
+	    !(lc->iter.flags & LOOPITER_FL_FREE))
+		return 0;	/* caller does not care about device status */
+
+	if (!is_loopdev(lc->device)) {
+		DBG(ITER, ul_debugobj(&lc->iter, "%s does not exist", lc->device));
+		return -errno;
+	}
+
+	DBG(ITER, ul_debugobj(&lc->iter, "%s exist", lc->device));
+
+	used = loopcxt_get_offset(lc, NULL) == 0;
+
+	if ((lc->iter.flags & LOOPITER_FL_USED) && used)
+		return 0;
+
+	if ((lc->iter.flags & LOOPITER_FL_FREE) && !used)
+		return 0;
+
+	DBG(ITER, ul_debugobj(&lc->iter, "failed to use %s device", lc->device));
+
+	ignore_result( loopcxt_set_device(lc, NULL) );
+	return 1;
+}
+
+static int cmpnum(const void *p1, const void *p2)
+{
+	return (((* (int *) p1) > (* (int *) p2)) -
+			((* (int *) p1) < (* (int *) p2)));
+}
+
+/*
+ * The classic scandir() is more expensive and less portable.
+ * We needn't full loop device names -- loop numbers (loop<N>)
+ * are enough.
+ */
+static int loop_scandir(const char *dirname, int **ary, int hasprefix)
+{
+	DIR *dir;
+	struct dirent *d;
+	unsigned int n, count = 0, arylen = 0;
+
+	if (!dirname || !ary)
+		return 0;
+
+	DBG(ITER, ul_debug("scan dir: %s", dirname));
+
+	dir = opendir(dirname);
+	if (!dir)
+		return 0;
+	free(*ary);
+	*ary = NULL;
+
+	while((d = readdir(dir))) {
+#ifdef _DIRENT_HAVE_D_TYPE
+		if (d->d_type != DT_BLK && d->d_type != DT_UNKNOWN &&
+		    d->d_type != DT_LNK)
+			continue;
+#endif
+		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			continue;
+
+		if (hasprefix) {
+			/* /dev/loop<N> */
+			if (sscanf(d->d_name, "loop%u", &n) != 1)
+				continue;
+		} else {
+			/* /dev/loop/<N> */
+			char *end = NULL;
+
+			errno = 0;
+			n = strtol(d->d_name, &end, 10);
+			if (d->d_name == end || (end && *end) || errno)
+				continue;
+		}
+		if (n < LOOPDEV_DEFAULT_NNODES)
+			continue;			/* ignore loop<0..7> */
+
+		if (count + 1 > arylen) {
+			int *tmp;
+
+			arylen += 1;
+
+			tmp = realloc(*ary, arylen * sizeof(int));
+			if (!tmp) {
+				free(*ary);
+				closedir(dir);
+				return -1;
+			}
+			*ary = tmp;
+		}
+		if (*ary)
+			(*ary)[count++] = n;
+	}
+	if (count && *ary)
+		qsort(*ary, count, sizeof(int), cmpnum);
+
+	closedir(dir);
+	return count;
+}
+
+/*
+ * Set the next *used* loop device according to /proc/partitions.
+ *
+ * Loop devices smaller than 512 bytes are invisible for this function.
+ */
+static int loopcxt_next_from_proc(struct loopdev_cxt *lc)
+{
+	struct loopdev_iter *iter = &lc->iter;
+	char buf[BUFSIZ];
+
+	DBG(ITER, ul_debugobj(iter, "scan /proc/partitions"));
+
+	if (!iter->proc)
+		iter->proc = fopen(_PATH_PROC_PARTITIONS, "r");
+	if (!iter->proc)
+		return 1;
+
+	while (fgets(buf, sizeof(buf), iter->proc)) {
+		unsigned int m;
+		char name[128 + 1];
+
+
+		if (sscanf(buf, " %u %*s %*s %128[^\n ]",
+			   &m, name) != 2 || m != LOOPDEV_MAJOR)
+			continue;
+
+		DBG(ITER, ul_debugobj(iter, "checking %s", name));
+
+		if (loopiter_set_device(lc, name) == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
+/*
+ * Set the next *used* loop device according to
+ * /sys/block/loopN/loop/backing_file (kernel >= 2.6.37 is required).
+ *
+ * This is preferred method.
+ */
+static int loopcxt_next_from_sysfs(struct loopdev_cxt *lc)
+{
+	struct loopdev_iter *iter = &lc->iter;
+	struct dirent *d;
+	int fd;
+
+	DBG(ITER, ul_debugobj(iter, "scanning /sys/block"));
+
+	if (!iter->sysblock)
+		iter->sysblock = opendir(_PATH_SYS_BLOCK);
+
+	if (!iter->sysblock)
+		return 1;
+
+	fd = dirfd(iter->sysblock);
+
+	while ((d = readdir(iter->sysblock))) {
+		char name[256];
+		struct stat st;
+
+		DBG(ITER, ul_debugobj(iter, "check %s", d->d_name));
+
+		if (strcmp(d->d_name, ".") == 0
+		    || strcmp(d->d_name, "..") == 0
+		    || strncmp(d->d_name, "loop", 4) != 0)
+			continue;
+
+		snprintf(name, sizeof(name), "%s/loop/backing_file", d->d_name);
+		if (fstat_at(fd, _PATH_SYS_BLOCK, name, &st, 0) != 0)
+			continue;
+
+		if (loopiter_set_device(lc, d->d_name) == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
+/*
+ * @lc: context, has to initialized by loopcxt_init_iterator()
+ *
+ * Returns: 0 on success, -1 on error, 1 at the end of scanning. The details
+ *          about the current loop device are available by
+ *          loopcxt_get_{fd,backing_file,device,offset, ...} functions.
+ */
+int loopcxt_next(struct loopdev_cxt *lc)
+{
+	struct loopdev_iter *iter;
+
+	if (!lc)
+		return -EINVAL;
+
+
+	iter = &lc->iter;
+	if (iter->done)
+		return 1;
+
+	DBG(ITER, ul_debugobj(iter, "next"));
+
+	/* A) Look for used loop devices in /proc/partitions ("losetup -a" only)
+	 */
+	if (iter->flags & LOOPITER_FL_USED) {
+		int rc;
+
+		if (loopcxt_sysfs_available(lc))
+			rc = loopcxt_next_from_sysfs(lc);
+		else
+			rc = loopcxt_next_from_proc(lc);
+		if (rc == 0)
+			return 0;
+		goto done;
+	}
+
+	/* B) Classic way, try first eight loop devices (default number
+	 *    of loop devices). This is enough for 99% of all cases.
+	 */
+	if (iter->default_check) {
+		DBG(ITER, ul_debugobj(iter, "next: default check"));
+		for (++iter->ncur; iter->ncur < LOOPDEV_DEFAULT_NNODES;
+							iter->ncur++) {
+			char name[16];
+			snprintf(name, sizeof(name), "loop%d", iter->ncur);
+
+			if (loopiter_set_device(lc, name) == 0)
+				return 0;
+		}
+		iter->default_check = 0;
+	}
+
+	/* C) the worst possibility, scan whole /dev or /dev/loop/<N>
+	 */
+	if (!iter->minors) {
+		DBG(ITER, ul_debugobj(iter, "next: scanning /dev"));
+		iter->nminors = (lc->flags & LOOPDEV_FL_DEVSUBDIR) ?
+			loop_scandir(_PATH_DEV_LOOP, &iter->minors, 0) :
+			loop_scandir(_PATH_DEV, &iter->minors, 1);
+		iter->ncur = -1;
+	}
+	for (++iter->ncur; iter->ncur < iter->nminors; iter->ncur++) {
+		char name[16];
+		snprintf(name, sizeof(name), "loop%d", iter->minors[iter->ncur]);
+
+		if (loopiter_set_device(lc, name) == 0)
+			return 0;
+	}
+done:
+	loopcxt_deinit_iterator(lc);
+	return 1;
+}
+
+/*
+ * @device: path to device
+ */
+int is_loopdev(const char *device)
+{
+	struct stat st;
+
+	if (!device)
+		return 0;
+
+	return (stat(device, &st) == 0 &&
+		S_ISBLK(st.st_mode) &&
+		major(st.st_rdev) == LOOPDEV_MAJOR);
+}
+
+/*
+ * @lc: context
+ *
+ * Returns result from LOOP_GET_STAT64 ioctl or NULL on error.
+ */
+struct loop_info64 *loopcxt_get_info(struct loopdev_cxt *lc)
+{
+	int fd;
+
+	if (!lc || lc->info_failed) {
+		errno = EINVAL;
+		return NULL;
+	}
+	errno = 0;
+	if (lc->has_info)
+		return &lc->info;
+
+	fd = loopcxt_get_fd(lc);
+	if (fd < 0)
+		return NULL;
+
+	if (ioctl(fd, LOOP_GET_STATUS64, &lc->info) == 0) {
+		lc->has_info = 1;
+		lc->info_failed = 0;
+		DBG(CXT, ul_debugobj(lc, "reading loop_info64 OK"));
+		return &lc->info;
+	}
+
+	lc->info_failed = 1;
+	DBG(CXT, ul_debugobj(lc, "reading loop_info64 FAILED"));
+
+	return NULL;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns (allocated) string with path to the file assicieted
+ * with the current loop device.
+ */
+char *loopcxt_get_backing_file(struct loopdev_cxt *lc)
+{
+	struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+	char *res = NULL;
+
+	if (sysfs)
+		/*
+		 * This is always preffered, the loop_info64
+		 * has too small buffer for the filename.
+		 */
+		res = sysfs_strdup(sysfs, "loop/backing_file");
+
+	if (!res && loopcxt_ioctl_enabled(lc)) {
+		struct loop_info64 *lo = loopcxt_get_info(lc);
+
+		if (lo) {
+			lo->lo_file_name[LO_NAME_SIZE - 2] = '*';
+			lo->lo_file_name[LO_NAME_SIZE - 1] = '\0';
+			res = strdup((char *) lo->lo_file_name);
+		}
+	}
+
+	DBG(CXT, ul_debugobj(lc, "get_backing_file [%s]", res));
+	return res;
+}
+
+/*
+ * @lc: context
+ * @offset: returns offset number for the given device
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_offset(struct loopdev_cxt *lc, uint64_t *offset)
+{
+	struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+	int rc = -EINVAL;
+
+	if (sysfs)
+		rc = sysfs_read_u64(sysfs, "loop/offset", offset);
+
+	if (rc && loopcxt_ioctl_enabled(lc)) {
+		struct loop_info64 *lo = loopcxt_get_info(lc);
+		if (lo) {
+			if (offset)
+				*offset = lo->lo_offset;
+			rc = 0;
+		} else
+			rc = -errno;
+	}
+
+	DBG(CXT, ul_debugobj(lc, "get_offset [rc=%d]", rc));
+	return rc;
+}
+
+/*
+ * @lc: context
+ * @sizelimit: returns size limit for the given device
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_sizelimit(struct loopdev_cxt *lc, uint64_t *size)
+{
+	struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+	int rc = -EINVAL;
+
+	if (sysfs)
+		rc = sysfs_read_u64(sysfs, "loop/sizelimit", size);
+
+	if (rc && loopcxt_ioctl_enabled(lc)) {
+		struct loop_info64 *lo = loopcxt_get_info(lc);
+		if (lo) {
+			if (size)
+				*size = lo->lo_sizelimit;
+			rc = 0;
+		} else
+			rc = -errno;
+	}
+
+	DBG(CXT, ul_debugobj(lc, "get_sizelimit [rc=%d]", rc));
+	return rc;
+}
+
+/*
+ * @lc: context
+ * @devno: returns encryption type
+ *
+ * Cryptoloop is DEPRECATED!
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_encrypt_type(struct loopdev_cxt *lc, uint32_t *type)
+{
+	struct loop_info64 *lo = loopcxt_get_info(lc);
+	int rc;
+
+	/* not provided by sysfs */
+	if (lo) {
+		if (type)
+			*type = lo->lo_encrypt_type;
+		rc = 0;
+	} else
+		rc = -errno;
+
+	DBG(CXT, ul_debugobj(lc, "get_encrypt_type [rc=%d]", rc));
+	return rc;
+}
+
+/*
+ * @lc: context
+ * @devno: returns crypt name
+ *
+ * Cryptoloop is DEPRECATED!
+ *
+ * Returns: <0 on error, 0 on success
+ */
+const char *loopcxt_get_crypt_name(struct loopdev_cxt *lc)
+{
+	struct loop_info64 *lo = loopcxt_get_info(lc);
+
+	if (lo)
+		return (char *) lo->lo_crypt_name;
+
+	DBG(CXT, ul_debugobj(lc, "get_crypt_name failed"));
+	return NULL;
+}
+
+/*
+ * @lc: context
+ * @devno: returns backing file devno
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_backing_devno(struct loopdev_cxt *lc, dev_t *devno)
+{
+	struct loop_info64 *lo = loopcxt_get_info(lc);
+	int rc;
+
+	if (lo) {
+		if (devno)
+			*devno = lo->lo_device;
+		rc = 0;
+	} else
+		rc = -errno;
+
+	DBG(CXT, ul_debugobj(lc, "get_backing_devno [rc=%d]", rc));
+	return rc;
+}
+
+/*
+ * @lc: context
+ * @ino: returns backing file inode
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_backing_inode(struct loopdev_cxt *lc, ino_t *ino)
+{
+	struct loop_info64 *lo = loopcxt_get_info(lc);
+	int rc;
+
+	if (lo) {
+		if (ino)
+			*ino = lo->lo_inode;
+		rc = 0;
+	} else
+		rc = -errno;
+
+	DBG(CXT, ul_debugobj(lc, "get_backing_inode [rc=%d]", rc));
+	return rc;
+}
+
+/*
+ * Check if the kernel supports partitioned loop devices.
+ *
+ * Notes:
+ *   - kernels < 3.2 support partitioned loop devices and PT scanning
+ *     only if max_part= module paremeter is non-zero
+ *
+ *   - kernels >= 3.2 always support partitioned loop devices
+ *
+ *   - kernels >= 3.2 always support BLKPG_{ADD,DEL}_PARTITION ioctls
+ *
+ *   - kernels >= 3.2 enable PT scanner only if max_part= is non-zero or if the
+ *     LO_FLAGS_PARTSCAN flag is set for the device. The PT scanner is disabled
+ *     by default.
+ *
+ *  See kernel commit e03c8dd14915fabc101aa495828d58598dc5af98.
+ */
+int loopmod_supports_partscan(void)
+{
+	int rc, ret = 0;
+	FILE *f;
+
+	if (get_linux_version() >= KERNEL_VERSION(3,2,0))
+		return 1;
+
+	f = fopen("/sys/module/loop/parameters/max_part", "r");
+	if (!f)
+		return 0;
+	rc = fscanf(f, "%d", &ret);
+	fclose(f);
+	return rc == 1 ? ret : 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the partscan flags is set *or* (for old kernels) partitions
+ * scannig is enabled for all loop devices.
+ */
+int loopcxt_is_partscan(struct loopdev_cxt *lc)
+{
+	struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+	if (sysfs) {
+		/* kernel >= 3.2 */
+		int fl;
+		if (sysfs_read_int(sysfs, "loop/partscan", &fl) == 0)
+			return fl;
+	}
+
+	/* old kernels (including kernels without loopN/loop/<flags> directory */
+	return loopmod_supports_partscan();
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the autoclear flags is set.
+ */
+int loopcxt_is_autoclear(struct loopdev_cxt *lc)
+{
+	struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+	if (sysfs) {
+		int fl;
+		if (sysfs_read_int(sysfs, "loop/autoclear", &fl) == 0)
+			return fl;
+	}
+
+	if (loopcxt_ioctl_enabled(lc)) {
+		struct loop_info64 *lo = loopcxt_get_info(lc);
+		if (lo)
+			return lo->lo_flags & LO_FLAGS_AUTOCLEAR;
+	}
+	return 0;
+}
+
+/*
+ * @lc: context
+ *
+ * Returns: 1 if the readonly flags is set.
+ */
+int loopcxt_is_readonly(struct loopdev_cxt *lc)
+{
+	struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+
+	if (sysfs) {
+		int fl;
+		if (sysfs_read_int(sysfs, "ro", &fl) == 0)
+			return fl;
+	}
+
+	if (loopcxt_ioctl_enabled(lc)) {
+		struct loop_info64 *lo = loopcxt_get_info(lc);
+		if (lo)
+			return lo->lo_flags & LO_FLAGS_READ_ONLY;
+	}
+	return 0;
+}
+
+/*
+ * @lc: context
+ * @st: backing file stat or NULL
+ * @backing_file: filename
+ * @offset: offset
+ * @flags: LOOPDEV_FL_OFFSET if @offset should not be ignored
+ *
+ * Returns 1 if the current @lc loopdev is associated with the given backing
+ * file. Note that the preferred way is to use devno and inode number rather
+ * than filename. The @backing_file filename is poor solution usable in case
+ * that you don't have rights to call stat().
+ *
+ * Don't forget that old kernels provide very restricted (in size) backing
+ * filename by LOOP_GET_STAT64 ioctl only.
+ */
+int loopcxt_is_used(struct loopdev_cxt *lc,
+		    struct stat *st,
+		    const char *backing_file,
+		    uint64_t offset,
+		    int flags)
+{
+	ino_t ino;
+	dev_t dev;
+
+	if (!lc)
+		return 0;
+
+	DBG(CXT, ul_debugobj(lc, "checking %s vs. %s",
+				loopcxt_get_device(lc),
+				backing_file));
+
+	if (st && loopcxt_get_backing_inode(lc, &ino) == 0 &&
+		  loopcxt_get_backing_devno(lc, &dev) == 0) {
+
+		if (ino == st->st_ino && dev == st->st_dev)
+			goto found;
+
+		/* don't use filename if we have devno and inode */
+		return 0;
+	}
+
+	/* poor man's solution */
+	if (backing_file) {
+		char *name = loopcxt_get_backing_file(lc);
+		int rc = name && strcmp(name, backing_file) == 0;
+
+		free(name);
+		if (rc)
+			goto found;
+	}
+
+	return 0;
+found:
+	if (flags & LOOPDEV_FL_OFFSET) {
+		uint64_t off;
+
+		return loopcxt_get_offset(lc, &off) == 0 && off == offset;
+	}
+	return 1;
+}
+
+/*
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ */
+int loopcxt_set_offset(struct loopdev_cxt *lc, uint64_t offset)
+{
+	if (!lc)
+		return -EINVAL;
+	lc->info.lo_offset = offset;
+
+	DBG(CXT, ul_debugobj(lc, "set offset=%jd", offset));
+	return 0;
+}
+
+/*
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ */
+int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit)
+{
+	if (!lc)
+		return -EINVAL;
+	lc->info.lo_sizelimit = sizelimit;
+
+	DBG(CXT, ul_debugobj(lc, "set sizelimit=%jd", sizelimit));
+	return 0;
+}
+
+/*
+ * @lc: context
+ * @flags: kernel LO_FLAGS_{READ_ONLY,USE_AOPS,AUTOCLEAR} flags
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags)
+{
+	if (!lc)
+		return -EINVAL;
+	lc->info.lo_flags = flags;
+
+	DBG(CXT, ul_debugobj(lc, "set flags=%u", (unsigned) flags));
+	return 0;
+}
+
+/*
+ * @lc: context
+ * @filename: backing file path (the path will be canonicalized)
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename)
+{
+	if (!lc)
+		return -EINVAL;
+
+	lc->filename = canonicalize_path(filename);
+	if (!lc->filename)
+		return -errno;
+
+	strncpy((char *)lc->info.lo_file_name, lc->filename, LO_NAME_SIZE);
+	lc->info.lo_file_name[LO_NAME_SIZE- 1] = '\0';
+
+	DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->info.lo_file_name));
+	return 0;
+}
+
+/*
+ * In kernels prior to v3.9, if the offset or sizelimit options
+ * are used, the block device's size won't be synced automatically.
+ * blockdev --getsize64 and filesystems will use the backing
+ * file size until the block device has been re-opened or the
+ * LOOP_SET_CAPACITY ioctl is called to sync the sizes.
+ *
+ * Since mount -oloop uses the LO_FLAGS_AUTOCLEAR option and passes
+ * the open file descriptor to the mount system call, we need to use
+ * the ioctl. Calling losetup directly doesn't have this problem since
+ * it closes the device when it exits and whatever consumes the device
+ * next will re-open it, causing the resync.
+ */
+static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd)
+{
+	uint64_t size, expected_size;
+	int dev_fd;
+	struct stat st;
+
+	if (!lc->info.lo_offset && !lc->info.lo_sizelimit)
+		return 0;
+
+	if (fstat(file_fd, &st)) {
+		DBG(CXT, ul_debugobj(lc, "failed to fstat backing file"));
+		return -errno;
+	}
+	if (S_ISBLK(st.st_mode)) {
+		if (blkdev_get_size(file_fd,
+				(unsigned long long *) &expected_size)) {
+			DBG(CXT, ul_debugobj(lc, "failed to determine device size"));
+			return -errno;
+		}
+	} else
+		expected_size = st.st_size;
+
+	if (expected_size == 0 || expected_size <= lc->info.lo_offset) {
+		DBG(CXT, ul_debugobj(lc, "failed to determine expected size"));
+		return 0;	/* ignore this error */
+	}
+
+	if (lc->info.lo_offset > 0)
+		expected_size -= lc->info.lo_offset;
+
+	if (lc->info.lo_sizelimit > 0 && lc->info.lo_sizelimit < expected_size)
+		expected_size = lc->info.lo_sizelimit;
+
+	dev_fd = loopcxt_get_fd(lc);
+	if (dev_fd < 0) {
+		DBG(CXT, ul_debugobj(lc, "failed to get loop FD"));
+		return -errno;
+	}
+
+	if (blkdev_get_size(dev_fd, (unsigned long long *) &size)) {
+		DBG(CXT, ul_debugobj(lc, "failed to determine loopdev size"));
+		return -errno;
+	}
+
+	/* It's block device, so, align to 512-byte sectors */
+	if (expected_size % 512) {
+		DBG(CXT, ul_debugobj(lc, "expected size misaligned to 512-byte sectors"));
+		expected_size = (expected_size >> 9) << 9;
+	}
+
+	if (expected_size != size) {
+		DBG(CXT, ul_debugobj(lc, "warning: loopdev and expected "
+				      "size dismatch (%ju/%ju)",
+				      size, expected_size));
+
+		if (loopcxt_set_capacity(lc)) {
+			/* ioctl not available */
+			if (errno == ENOTTY || errno == EINVAL)
+				errno = ERANGE;
+			return -errno;
+		}
+
+		if (blkdev_get_size(dev_fd, (unsigned long long *) &size))
+			return -errno;
+
+		if (expected_size != size) {
+			errno = ERANGE;
+			DBG(CXT, ul_debugobj(lc, "failed to set loopdev size, "
+					"size: %ju, expected: %ju",
+					size, expected_size));
+			return -errno;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * @cl: context
+ *
+ * Associate the current device (see loopcxt_{set,get}_device()) with
+ * a file (see loopcxt_set_backing_file()).
+ *
+ * The device is initialized read-write by default. If you want read-only
+ * device then set LO_FLAGS_READ_ONLY by loopcxt_set_flags(). The LOOPDEV_FL_*
+ * flags are ignored and modified according to LO_FLAGS_*.
+ *
+ * If the device is already open by loopcxt_get_fd() then this setup device
+ * function will re-open the device to fix read/write mode.
+ *
+ * The device is also initialized read-only if the backing file is not
+ * possible to open read-write (e.g. read-only FS).
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int loopcxt_setup_device(struct loopdev_cxt *lc)
+{
+	int file_fd, dev_fd, mode = O_RDWR, rc = -1, cnt = 0;
+
+	if (!lc || !*lc->device || !lc->filename)
+		return -EINVAL;
+
+	DBG(SETUP, ul_debugobj(lc, "device setup requested"));
+
+	/*
+	 * Open backing file and device
+	 */
+	if (lc->info.lo_flags & LO_FLAGS_READ_ONLY)
+		mode = O_RDONLY;
+
+	if ((file_fd = open(lc->filename, mode | O_CLOEXEC)) < 0) {
+		if (mode != O_RDONLY && (errno == EROFS || errno == EACCES))
+			file_fd = open(lc->filename, mode = O_RDONLY);
+
+		if (file_fd < 0) {
+			DBG(SETUP, ul_debugobj(lc, "open backing file failed: %m"));
+			return -errno;
+		}
+	}
+	DBG(SETUP, ul_debugobj(lc, "backing file open: OK"));
+
+	if (lc->fd != -1 && lc->mode != mode) {
+		DBG(SETUP, ul_debugobj(lc, "closing already open device (mode mismatch)"));
+		close(lc->fd);
+		lc->fd = -1;
+		lc->mode = 0;
+	}
+
+	if (mode == O_RDONLY) {
+		lc->flags |= LOOPDEV_FL_RDONLY;			/* open() mode */
+		lc->info.lo_flags |= LO_FLAGS_READ_ONLY;	/* kernel loopdev mode */
+	} else {
+		lc->flags |= LOOPDEV_FL_RDWR;			/* open() mode */
+		lc->info.lo_flags &= ~LO_FLAGS_READ_ONLY;
+		lc->flags &= ~LOOPDEV_FL_RDONLY;
+	}
+
+	do {
+		errno = 0;
+		dev_fd = loopcxt_get_fd(lc);
+		if (dev_fd >= 0 || lc->control_ok == 0)
+			break;
+		if (errno != EACCES && errno != ENOENT)
+			break;
+		/* We have permissions to open /dev/loop-control, but open
+		 * /dev/loopN failed with EACCES, it's probably because udevd
+		 * does not applied chown yet. Let's wait a moment. */
+		usleep(25000);
+	} while (cnt++ < 16);
+
+	if (dev_fd < 0) {
+		rc = -errno;
+		goto err;
+	}
+
+	DBG(SETUP, ul_debugobj(lc, "device open: OK"));
+
+	/*
+	 * Set FD
+	 */
+	if (ioctl(dev_fd, LOOP_SET_FD, file_fd) < 0) {
+		rc = -errno;
+		DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD failed: %m"));
+		goto err;
+	}
+
+	DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD: OK"));
+
+	if (ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info)) {
+		DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m"));
+		goto err;
+	}
+
+	DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK"));
+
+	if ((rc = loopcxt_check_size(lc, file_fd)))
+		goto err;
+
+	close(file_fd);
+
+	memset(&lc->info, 0, sizeof(lc->info));
+	lc->has_info = 0;
+	lc->info_failed = 0;
+
+	DBG(SETUP, ul_debugobj(lc, "success [rc=0]"));
+	return 0;
+err:
+	if (file_fd >= 0)
+		close(file_fd);
+	if (dev_fd >= 0 && rc != -EBUSY)
+		ioctl(dev_fd, LOOP_CLR_FD, 0);
+
+	DBG(SETUP, ul_debugobj(lc, "failed [rc=%d]", rc));
+	return rc;
+}
+
+int loopcxt_set_capacity(struct loopdev_cxt *lc)
+{
+	int fd = loopcxt_get_fd(lc);
+
+	if (fd < 0)
+		return -EINVAL;
+
+	/* Kernels prior to v2.6.30 don't support this ioctl */
+	if (ioctl(fd, LOOP_SET_CAPACITY, 0) < 0) {
+		int rc = -errno;
+		DBG(CXT, ul_debugobj(lc, "LOOP_SET_CAPACITY failed: %m"));
+		return rc;
+	}
+
+	DBG(CXT, ul_debugobj(lc, "capacity set"));
+	return 0;
+}
+
+int loopcxt_delete_device(struct loopdev_cxt *lc)
+{
+	int fd = loopcxt_get_fd(lc);
+
+	if (fd < 0)
+		return -EINVAL;
+
+	if (ioctl(fd, LOOP_CLR_FD, 0) < 0) {
+		DBG(CXT, ul_debugobj(lc, "LOOP_CLR_FD failed: %m"));
+		return -errno;
+	}
+
+	DBG(CXT, ul_debugobj(lc, "device removed"));
+	return 0;
+}
+
+int loopcxt_add_device(struct loopdev_cxt *lc)
+{
+	int rc = -EINVAL;
+	int ctl, nr = -1;
+	const char *p, *dev = loopcxt_get_device(lc);
+
+	if (!dev)
+		goto done;
+
+	if (!(lc->flags & LOOPDEV_FL_CONTROL)) {
+		rc = -ENOSYS;
+		goto done;
+	}
+
+	p = strrchr(dev, '/');
+	if (!p || (sscanf(p, "/loop%d", &nr) != 1 && sscanf(p, "/%d", &nr) != 1)
+	       || nr < 0)
+		goto done;
+
+	ctl = open(_PATH_DEV_LOOPCTL, O_RDWR|O_CLOEXEC);
+	if (ctl >= 0) {
+		DBG(CXT, ul_debugobj(lc, "add_device %d", nr));
+		rc = ioctl(ctl, LOOP_CTL_ADD, nr);
+		close(ctl);
+	}
+	lc->control_ok = rc >= 0 ? 1 : 0;
+done:
+	DBG(CXT, ul_debugobj(lc, "add_device done [rc=%d]", rc));
+	return rc;
+}
+
+/*
+ * Note that LOOP_CTL_GET_FREE ioctl is supported since kernel 3.1. In older
+ * kernels we have to check all loop devices to found unused one.
+ *
+ * See kernel commit 770fe30a46a12b6fb6b63fbe1737654d28e8484.
+ */
+int loopcxt_find_unused(struct loopdev_cxt *lc)
+{
+	int rc = -1;
+
+	DBG(CXT, ul_debugobj(lc, "find_unused requested"));
+
+	if (lc->flags & LOOPDEV_FL_CONTROL) {
+		int ctl = open(_PATH_DEV_LOOPCTL, O_RDWR|O_CLOEXEC);
+
+		if (ctl >= 0)
+			rc = ioctl(ctl, LOOP_CTL_GET_FREE);
+		if (rc >= 0) {
+			char name[16];
+			snprintf(name, sizeof(name), "loop%d", rc);
+
+			rc = loopiter_set_device(lc, name);
+		}
+		lc->control_ok = ctl >= 0 && rc == 0 ? 1 : 0;
+		if (ctl >= 0)
+			close(ctl);
+		DBG(CXT, ul_debugobj(lc, "find_unused by loop-control [rc=%d]", rc));
+	}
+
+	if (rc < 0) {
+		rc = loopcxt_init_iterator(lc, LOOPITER_FL_FREE);
+		if (rc)
+			return rc;
+
+		rc = loopcxt_next(lc);
+		loopcxt_deinit_iterator(lc);
+		DBG(CXT, ul_debugobj(lc, "find_unused by scan [rc=%d]", rc));
+	}
+	return rc;
+}
+
+
+
+/*
+ * Return: TRUE/FALSE
+ */
+int loopdev_is_autoclear(const char *device)
+{
+	struct loopdev_cxt lc;
+	int rc;
+
+	if (!device)
+		return 0;
+
+	rc = loopcxt_init(&lc, 0);
+	if (!rc)
+		rc = loopcxt_set_device(&lc, device);
+	if (!rc)
+		rc = loopcxt_is_autoclear(&lc);
+
+	loopcxt_deinit(&lc);
+	return rc;
+}
+
+char *loopdev_get_backing_file(const char *device)
+{
+	struct loopdev_cxt lc;
+	char *res = NULL;
+
+	if (!device)
+		return NULL;
+	if (loopcxt_init(&lc, 0))
+		return NULL;
+	if (loopcxt_set_device(&lc, device) == 0)
+		res = loopcxt_get_backing_file(&lc);
+
+	loopcxt_deinit(&lc);
+	return res;
+}
+
+/*
+ * Returns: TRUE/FALSE
+ */
+int loopdev_is_used(const char *device, const char *filename,
+		    uint64_t offset, int flags)
+{
+	struct loopdev_cxt lc;
+	struct stat st;
+	int rc = 0;
+
+	if (!device || !filename)
+		return 0;
+
+	rc = loopcxt_init(&lc, 0);
+	if (!rc)
+		rc = loopcxt_set_device(&lc, device);
+	if (rc)
+		return rc;
+
+	rc = !stat(filename, &st);
+	rc = loopcxt_is_used(&lc, rc ? &st : NULL, filename, offset, flags);
+
+	loopcxt_deinit(&lc);
+	return rc;
+}
+
+int loopdev_delete(const char *device)
+{
+	struct loopdev_cxt lc;
+	int rc;
+
+	if (!device)
+		return -EINVAL;
+
+	rc = loopcxt_init(&lc, 0);
+	if (!rc)
+		rc = loopcxt_set_device(&lc, device);
+	if (!rc)
+		rc = loopcxt_delete_device(&lc);
+	loopcxt_deinit(&lc);
+	return rc;
+}
+
+/*
+ * Returns: 0 = success, < 0 error, 1 not found
+ */
+int loopcxt_find_by_backing_file(struct loopdev_cxt *lc, const char *filename,
+				 uint64_t offset, int flags)
+{
+	int rc, hasst;
+	struct stat st;
+
+	if (!filename)
+		return -EINVAL;
+
+	hasst = !stat(filename, &st);
+
+	rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED);
+	if (rc)
+		return rc;
+
+	while ((rc = loopcxt_next(lc)) == 0) {
+
+		if (loopcxt_is_used(lc, hasst ? &st : NULL,
+					filename, offset, flags))
+			break;
+	}
+
+	loopcxt_deinit_iterator(lc);
+	return rc;
+}
+
+/*
+ * Returns allocated string with device name
+ */
+char *loopdev_find_by_backing_file(const char *filename, uint64_t offset, int flags)
+{
+	struct loopdev_cxt lc;
+	char *res = NULL;
+
+	if (!filename)
+		return NULL;
+
+	if (loopcxt_init(&lc, 0))
+		return NULL;
+	if (loopcxt_find_by_backing_file(&lc, filename, offset, flags) == 0)
+		res = loopcxt_strdup_device(&lc);
+	loopcxt_deinit(&lc);
+
+	return res;
+}
+
+/*
+ * Returns number of loop devices associated with @file, if only one loop
+ * device is associeted with the given @filename and @loopdev is not NULL then
+ * @loopdev returns name of the device.
+ */
+int loopdev_count_by_backing_file(const char *filename, char **loopdev)
+{
+	struct loopdev_cxt lc;
+	int count = 0, rc;
+
+	if (!filename)
+		return -1;
+
+	rc = loopcxt_init(&lc, 0);
+	if (rc)
+		return rc;
+	if (loopcxt_init_iterator(&lc, LOOPITER_FL_USED))
+		return -1;
+
+	while(loopcxt_next(&lc) == 0) {
+		char *backing = loopcxt_get_backing_file(&lc);
+
+		if (!backing || strcmp(backing, filename)) {
+			free(backing);
+			continue;
+		}
+
+		free(backing);
+		if (loopdev && count == 0)
+			*loopdev = loopcxt_strdup_device(&lc);
+		count++;
+	}
+
+	loopcxt_deinit(&lc);
+
+	if (loopdev && count > 1) {
+		free(*loopdev);
+		*loopdev = NULL;
+	}
+	return count;
+}
+
diff --git a/libblkid/lib/mangle.c b/libblkid/lib/mangle.c
new file mode 100644
index 0000000..5236e97
--- /dev/null
+++ b/libblkid/lib/mangle.c
@@ -0,0 +1,166 @@
+/*
+ * Functions for \oct encoding used in mtab/fstab/swaps/etc.
+ *
+ * Based on code from mount(8).
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "mangle.h"
+#include "c.h"
+
+#define isoctal(a)		(((a) & ~7) == '0')
+
+#define from_hex(c)		(isdigit(c) ? c - '0' : tolower(c) - 'a' + 10)
+
+#define is_unwanted_char(x)	(strchr(" \t\n\\", (unsigned int) x) != NULL)
+
+
+char *mangle(const char *s)
+{
+	char *ss, *sp;
+
+	if (!s)
+		return NULL;
+
+	ss = sp = malloc(4 * strlen(s) + 1);
+	if (!sp)
+		return NULL;
+	while(1) {
+		if (!*s) {
+			*sp = '\0';
+			break;
+		}
+		if (is_unwanted_char(*s)) {
+			*sp++ = '\\';
+			*sp++ = '0' + ((*s & 0300) >> 6);
+			*sp++ = '0' + ((*s & 070) >> 3);
+			*sp++ = '0' + (*s & 07);
+		} else
+			*sp++ = *s;
+		s++;
+	}
+	return ss;
+}
+
+
+void unmangle_to_buffer(const char *s, char *buf, size_t len)
+{
+	size_t sz = 0;
+
+	if (!s)
+		return;
+
+	while(*s && sz < len - 1) {
+		if (*s == '\\' && sz + 3 < len - 1 && isoctal(s[1]) &&
+		    isoctal(s[2]) && isoctal(s[3])) {
+
+			*buf++ = 64*(s[1] & 7) + 8*(s[2] & 7) + (s[3] & 7);
+			s += 4;
+			sz += 4;
+		} else {
+			*buf++ = *s++;
+			sz++;
+		}
+	}
+	*buf = '\0';
+}
+
+void unhexmangle_to_buffer(const char *s, char *buf, size_t len)
+{
+	size_t sz = 0;
+
+	if (!s)
+		return;
+
+	while(*s && sz < len - 1) {
+		if (*s == '\\' && sz + 3 < len - 1 && s[1] == 'x' &&
+		    isxdigit(s[2]) && isxdigit(s[3])) {
+
+			*buf++ = from_hex(s[2]) << 4 | from_hex(s[3]);
+			s += 4;
+			sz += 4;
+		} else {
+			*buf++ = *s++;
+			sz++;
+		}
+	}
+	*buf = '\0';
+}
+
+static inline char *skip_nonspaces(const char *s)
+{
+	while (*s && !(*s == ' ' || *s == '\t'))
+		s++;
+	return (char *) s;
+}
+
+/*
+ * Returns mallocated buffer or NULL in case of error.
+ */
+char *unmangle(const char *s, char **end)
+{
+	char *buf;
+	char *e;
+	size_t sz;
+
+	if (!s)
+		return NULL;
+
+	e = skip_nonspaces(s);
+	sz = e - s + 1;
+
+	if (end)
+		*end = e;
+	if (e == s)
+		return NULL;	/* empty string */
+
+	buf = malloc(sz);
+	if (!buf)
+		return NULL;
+
+	unmangle_to_buffer(s, buf, sz);
+	return buf;
+}
+
+#ifdef TEST_PROGRAM
+#include <errno.h>
+int main(int argc, char *argv[])
+{
+	char *p = NULL;
+	if (argc < 3) {
+		fprintf(stderr, "usage: %s --mangle|unmangle <string>\n",
+						program_invocation_short_name);
+		return EXIT_FAILURE;
+	}
+
+	if (!strcmp(argv[1], "--mangle")) {
+		p = mangle(argv[2]);
+		printf("mangled: '%s'\n", p);
+		free(p);
+	}
+
+	else if (!strcmp(argv[1], "--unmangle")) {
+		char *x = unmangle(argv[2], NULL);
+
+		if (x) {
+			printf("unmangled: '%s'\n", x);
+			free(x);
+		}
+
+		x = strdup(argv[2]);
+		unmangle_to_buffer(x, x, strlen(x) + 1);
+
+		if (x) {
+			printf("self-unmangled: '%s'\n", x);
+			free(x);
+		}
+	}
+
+	return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/libblkid/lib/match.c b/libblkid/lib/match.c
new file mode 100644
index 0000000..9be82b0
--- /dev/null
+++ b/libblkid/lib/match.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <string.h>
+
+#include "match.h"
+
+/*
+ * match_fstype:
+ * @type: filesystem type
+ * @pattern: filesystem name or comma delimited list of names
+ *
+ * The @pattern list of filesystem can be prefixed with a global
+ * "no" prefix to invert matching of the whole list. The "no" could
+ * also be used for individual items in the @pattern list. So,
+ * "nofoo,bar" has the same meaning as "nofoo,nobar".
+ */
+int match_fstype(const char *type, const char *pattern)
+{
+	int no = 0;		/* negated types list */
+	int len;
+	const char *p;
+
+	if (!pattern && !type)
+		return 1;
+	if (!pattern)
+		return 0;
+
+	if (!strncmp(pattern, "no", 2)) {
+		no = 1;
+		pattern += 2;
+	}
+
+	/* Does type occur in types, separated by commas? */
+	len = strlen(type);
+	p = pattern;
+	while(1) {
+		if (!strncmp(p, "no", 2) && !strncmp(p+2, type, len) &&
+		    (p[len+2] == 0 || p[len+2] == ','))
+			return 0;
+		if (strncmp(p, type, len) == 0 && (p[len] == 0 || p[len] == ','))
+			return !no;
+		p = strchr(p,',');
+		if (!p)
+			break;
+		p++;
+	}
+	return no;
+}
diff --git a/libblkid/lib/mbsalign.c b/libblkid/lib/mbsalign.c
new file mode 100644
index 0000000..5e52e8f
--- /dev/null
+++ b/libblkid/lib/mbsalign.c
@@ -0,0 +1,466 @@
+/* Align/Truncate a string in a given screen width
+   Copyright (C) 2009-2010 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation, either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Pádraig Brady.  */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "c.h"
+#include "mbsalign.h"
+#include "widechar.h"
+
+#ifdef HAVE_WIDECHAR
+/* Replace non printable chars.
+   Note \t and \n etc. are non printable.
+   Return 1 if replacement made, 0 otherwise.  */
+
+/*
+ * Counts number of cells in multibyte string. For all control and
+ * non-printable chars is the result width enlarged to store \x?? hex
+ * sequence. See mbs_safe_encode().
+ *
+ * Returns: number of cells, @sz returns number of bytes.
+ */
+size_t mbs_safe_nwidth(const char *buf, size_t bufsz, size_t *sz)
+{
+	mbstate_t st;
+	const char *p = buf, *last = buf;
+	size_t width = 0, bytes = 0;
+
+	memset(&st, 0, sizeof(st));
+
+	if (p && *p && bufsz)
+		last = p + (bufsz - 1);
+
+	while (p && *p && p <= last) {
+		if (iscntrl((unsigned char) *p)) {
+			width += 4, bytes += 4;		/* *p encoded to \x?? */
+			p++;
+		}
+#ifdef HAVE_WIDECHAR
+		else {
+			wchar_t wc;
+			size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+
+			if (len == 0)
+				break;
+
+			if (len == (size_t) -1 || len == (size_t) -2) {
+				len = 1;
+				if (isprint((unsigned char) *p))
+					width += 1, bytes += 1;
+				else
+					width += 4, bytes += 4;
+
+			} else if (!iswprint(wc)) {
+				width += len * 4;	/* hex encode whole sequence */
+				bytes += len * 4;
+			} else {
+				width += wcwidth(wc);	/* number of cells */
+				bytes += len;		/* number of bytes */
+			}
+			p += len;
+		}
+#else
+		else if (!isprint((unsigned char) *p)) {
+			width += 4, bytes += 4;		/* *p encoded to \x?? */
+			p++;
+		} else {
+			width++, bytes++;
+			p++;
+		}
+#endif
+	}
+
+	if (sz)
+		*sz = bytes;
+	return width;
+}
+
+size_t mbs_safe_width(const char *s)
+{
+	if (!s || !*s)
+		return 0;
+	return mbs_safe_nwidth(s, strlen(s), NULL);
+}
+
+/*
+ * Copy @s to @buf and replace control and non-printable chars with
+ * \x?? hex sequence. The @width returns number of cells.
+ *
+ * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s)))
+ * bytes.
+ */
+char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf)
+{
+	mbstate_t st;
+	const char *p = s;
+	char *r;
+	size_t sz = s ? strlen(s) : 0;
+
+	if (!sz || !buf)
+		return NULL;
+
+	memset(&st, 0, sizeof(st));
+
+	r = buf;
+	*width = 0;
+
+	while (p && *p) {
+		if (iscntrl((unsigned char) *p)) {
+			sprintf(r, "\\x%02x", (unsigned char) *p);
+			r += 4;
+			*width += 4;
+			p++;
+		}
+#ifdef HAVE_WIDECHAR
+		else {
+			wchar_t wc;
+			size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+
+			if (len == 0)
+				break;		/* end of string */
+
+			if (len == (size_t) -1 || len == (size_t) -2) {
+				len = 1;
+				/*
+				 * Not valid multibyte sequence -- maybe it's
+				 * printable char according to the current locales.
+				 */
+				if (!isprint((unsigned char) *p)) {
+					sprintf(r, "\\x%02x", (unsigned char) *p);
+					r += 4;
+					*width += 4;
+				} else {
+					width++;
+					*r++ = *p;
+				}
+			} else if (!iswprint(wc)) {
+				size_t i;
+				for (i = 0; i < len; i++) {
+					sprintf(r, "\\x%02x", (unsigned char) *p);
+					r += 4;
+					*width += 4;
+				}
+			} else {
+				memcpy(r, p, len);
+				r += len;
+				*width += wcwidth(wc);
+			}
+			p += len;
+		}
+#else
+		else if (!isprint((unsigned char) *p)) {
+			sprintf(r, "\\x%02x", (unsigned char) *p);
+			p++;
+			r += 4;
+			*width += 4;
+		} else {
+			*r++ = *p++;
+			*width++;
+		}
+#endif
+	}
+
+	*r = '\0';
+
+	return buf;
+}
+
+size_t mbs_safe_encode_size(size_t bytes)
+{
+	return (bytes * 4) + 1;
+}
+
+/*
+ * Returns allocated string where all control and non-printable chars are
+ * replaced with \x?? hex sequence.
+ */
+char *mbs_safe_encode(const char *s, size_t *width)
+{
+	size_t sz = s ? strlen(s) : 0;
+	char *buf;
+
+	if (!sz)
+		return NULL;
+	buf = malloc(mbs_safe_encode_size(sz));
+	if (!buf)
+		return NULL;
+
+	return mbs_safe_encode_to_buffer(s, width, buf);
+}
+
+static bool
+wc_ensure_printable (wchar_t *wchars)
+{
+  bool replaced = false;
+  wchar_t *wc = wchars;
+  while (*wc)
+    {
+      if (!iswprint ((wint_t) *wc))
+        {
+          *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+          replaced = true;
+        }
+      wc++;
+    }
+  return replaced;
+}
+
+/* Truncate wchar string to width cells.
+ * Returns number of cells used.  */
+
+static size_t
+wc_truncate (wchar_t *wc, size_t width)
+{
+  size_t cells = 0;
+  int next_cells = 0;
+
+  while (*wc)
+    {
+      next_cells = wcwidth (*wc);
+      if (next_cells == -1) /* non printable */
+        {
+          *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+          next_cells = 1;
+        }
+      if (cells + next_cells > width)
+        break;
+      cells += next_cells;
+      wc++;
+    }
+  *wc = L'\0';
+  return cells;
+}
+
+/* FIXME: move this function to gnulib as it's missing on:
+   OpenBSD 3.8, IRIX 5.3, Solaris 2.5.1, mingw, BeOS  */
+
+static int
+rpl_wcswidth (const wchar_t *s, size_t n)
+{
+  int ret = 0;
+
+  while (n-- > 0 && *s != L'\0')
+    {
+      int nwidth = wcwidth (*s++);
+      if (nwidth == -1)             /* non printable */
+        return -1;
+      if (ret > (INT_MAX - nwidth)) /* overflow */
+        return -1;
+      ret += nwidth;
+    }
+
+  return ret;
+}
+#endif
+
+/* Truncate multi-byte string to @width and returns number of
+ * bytes of the new string @str, and in @width returns number
+ * of cells.
+ */
+size_t
+mbs_truncate(char *str, size_t *width)
+{
+	ssize_t bytes = strlen(str);
+#ifdef HAVE_WIDECHAR
+	ssize_t sz = mbstowcs(NULL, str, 0);
+	wchar_t *wcs = NULL;
+
+	if (sz == (ssize_t) -1)
+		goto done;
+
+	wcs = malloc((sz + 1) * sizeof(wchar_t));
+	if (!wcs)
+		goto done;
+
+	if (!mbstowcs(wcs, str, sz))
+		goto done;
+	*width = wc_truncate(wcs, *width);
+	bytes = wcstombs(str, wcs, bytes);
+done:
+	free(wcs);
+#else
+	if (*width < bytes)
+		bytes = *width;
+#endif
+	if (bytes >= 0)
+		str[bytes] = '\0';
+	return bytes;
+}
+
+/* Write N_SPACES space characters to DEST while ensuring
+   nothing is written beyond DEST_END. A terminating NUL
+   is always added to DEST.
+   A pointer to the terminating NUL is returned.  */
+
+static char*
+mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces)
+{
+  /* FIXME: Should we pad with "figure space" (\u2007)
+     if non ascii data present?  */
+  for (/* nothing */; n_spaces && (dest < dest_end); n_spaces--)
+    *dest++ = ' ';
+  *dest = '\0';
+  return dest;
+}
+
+/* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte
+   characters; write the result into the DEST_SIZE-byte buffer, DEST.
+   ALIGNMENT specifies whether to left- or right-justify or to center.
+   If SRC requires more than *WIDTH columns, truncate it to fit.
+   When centering, the number of trailing spaces may be one less than the
+   number of leading spaces. The FLAGS parameter is unused at present.
+   Return the length in bytes required for the final result, not counting
+   the trailing NUL.  A return value of DEST_SIZE or larger means there
+   wasn't enough space.  DEST will be NUL terminated in any case.
+   Return (size_t) -1 upon error (invalid multi-byte sequence in SRC,
+   or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified.
+   Update *WIDTH to indicate how many columns were used before padding.  */
+
+size_t
+mbsalign (const char *src, char *dest, size_t dest_size,
+          size_t *width, mbs_align_t align, int flags)
+{
+  size_t ret = -1;
+  size_t src_size = strlen (src) + 1;
+  char *newstr = NULL;
+  wchar_t *str_wc = NULL;
+  const char *str_to_print = src;
+  size_t n_cols = src_size - 1;
+  size_t n_used_bytes = n_cols; /* Not including NUL */
+  size_t n_spaces = 0, space_left;
+  bool conversion = false;
+  bool wc_enabled = false;
+
+#ifdef HAVE_WIDECHAR
+  /* In multi-byte locales convert to wide characters
+     to allow easy truncation. Also determine number
+     of screen columns used.  */
+  if (MB_CUR_MAX > 1)
+    {
+      size_t src_chars = mbstowcs (NULL, src, 0);
+      if (src_chars == (size_t) -1)
+        {
+          if (flags & MBA_UNIBYTE_FALLBACK)
+            goto mbsalign_unibyte;
+          else
+            goto mbsalign_cleanup;
+        }
+      src_chars += 1; /* make space for NUL */
+      str_wc = malloc (src_chars * sizeof (wchar_t));
+      if (str_wc == NULL)
+        {
+          if (flags & MBA_UNIBYTE_FALLBACK)
+            goto mbsalign_unibyte;
+          else
+            goto mbsalign_cleanup;
+        }
+      if (mbstowcs (str_wc, src, src_chars) != 0)
+        {
+          str_wc[src_chars - 1] = L'\0';
+          wc_enabled = true;
+          conversion = wc_ensure_printable (str_wc);
+          n_cols = rpl_wcswidth (str_wc, src_chars);
+        }
+    }
+
+  /* If we transformed or need to truncate the source string
+     then create a modified copy of it.  */
+  if (wc_enabled && (conversion || (n_cols > *width)))
+    {
+        if (conversion)
+          {
+             /* May have increased the size by converting
+                \t to \uFFFD for example.  */
+            src_size = wcstombs(NULL, str_wc, 0) + 1;
+          }
+        newstr = malloc (src_size);
+        if (newstr == NULL)
+        {
+          if (flags & MBA_UNIBYTE_FALLBACK)
+            goto mbsalign_unibyte;
+          else
+            goto mbsalign_cleanup;
+        }
+        str_to_print = newstr;
+        n_cols = wc_truncate (str_wc, *width);
+        n_used_bytes = wcstombs (newstr, str_wc, src_size);
+    }
+#endif
+
+mbsalign_unibyte:
+
+  if (n_cols > *width) /* Unibyte truncation required.  */
+    {
+      n_cols = *width;
+      n_used_bytes = n_cols;
+    }
+
+  if (*width > n_cols) /* Padding required.  */
+    n_spaces = *width - n_cols;
+
+  /* indicate to caller how many cells needed (not including padding).  */
+  *width = n_cols;
+
+  /* indicate to caller how many bytes needed (not including NUL).  */
+  ret = n_used_bytes + (n_spaces * 1);
+
+  /* Write as much NUL terminated output to DEST as possible.  */
+  if (dest_size != 0)
+    {
+      char *dest_end = dest + dest_size - 1;
+      size_t start_spaces;
+      size_t end_spaces;
+
+      switch (align)
+        {
+        case MBS_ALIGN_CENTER:
+          start_spaces = n_spaces / 2 + n_spaces % 2;
+          end_spaces = n_spaces / 2;
+          break;
+        case MBS_ALIGN_LEFT:
+          start_spaces = 0;
+          end_spaces = n_spaces;
+          break;
+        case MBS_ALIGN_RIGHT:
+          start_spaces = n_spaces;
+          end_spaces = 0;
+          break;
+	default:
+	  abort();
+        }
+
+      dest = mbs_align_pad (dest, dest_end, start_spaces);
+      space_left = dest_end - dest;
+      dest = memcpy (dest, str_to_print, min (n_used_bytes, space_left));
+      mbs_align_pad (dest, dest_end, end_spaces);
+    }
+
+mbsalign_cleanup:
+
+  free (str_wc);
+  free (newstr);
+
+  return ret;
+}
diff --git a/libblkid/lib/md5.c b/libblkid/lib/md5.c
new file mode 100644
index 0000000..488d16e
--- /dev/null
+++ b/libblkid/lib/md5.c
@@ -0,0 +1,257 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+#include <string.h>		/* for memcpy() */
+
+#include "md5.h"
+
+#if !defined(WORDS_BIGENDIAN)
+#define byteReverse(buf, len)	/* Nothing */
+#else
+void byteReverse(unsigned char *buf, unsigned longs);
+
+#ifndef ASM_MD5
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+void byteReverse(unsigned char *buf, unsigned longs)
+{
+    uint32_t t;
+    do {
+	t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+	    ((unsigned) buf[1] << 8 | buf[0]);
+	*(uint32_t *) buf = t;
+	buf += 4;
+    } while (--longs);
+}
+#endif
+#endif
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void MD5Init(struct MD5Context *ctx)
+{
+    ctx->buf[0] = 0x67452301;
+    ctx->buf[1] = 0xefcdab89;
+    ctx->buf[2] = 0x98badcfe;
+    ctx->buf[3] = 0x10325476;
+
+    ctx->bits[0] = 0;
+    ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len)
+{
+    uint32_t t;
+
+    /* Update bitcount */
+
+    t = ctx->bits[0];
+    if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+	ctx->bits[1]++;		/* Carry from low to high */
+    ctx->bits[1] += len >> 29;
+
+    t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */
+
+    /* Handle any leading odd-sized chunks */
+
+    if (t) {
+	unsigned char *p = (unsigned char *) ctx->in + t;
+
+	t = 64 - t;
+	if (len < t) {
+	    memcpy(p, buf, len);
+	    return;
+	}
+	memcpy(p, buf, t);
+	byteReverse(ctx->in, 16);
+	MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+	buf += t;
+	len -= t;
+    }
+    /* Process data in 64-byte chunks */
+
+    while (len >= 64) {
+	memcpy(ctx->in, buf, 64);
+	byteReverse(ctx->in, 16);
+	MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+	buf += 64;
+	len -= 64;
+    }
+
+    /* Handle any remaining bytes of data. */
+
+    memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern 
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void MD5Final(unsigned char digest[MD5LENGTH], struct MD5Context *ctx)
+{
+    unsigned count;
+    unsigned char *p;
+
+    /* Compute number of bytes mod 64 */
+    count = (ctx->bits[0] >> 3) & 0x3F;
+
+    /* Set the first char of padding to 0x80.  This is safe since there is
+       always at least one byte free */
+    p = ctx->in + count;
+    *p++ = 0x80;
+
+    /* Bytes of padding needed to make 64 bytes */
+    count = 64 - 1 - count;
+
+    /* Pad out to 56 mod 64 */
+    if (count < 8) {
+	/* Two lots of padding:  Pad the first block to 64 bytes */
+	memset(p, 0, count);
+	byteReverse(ctx->in, 16);
+	MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+
+	/* Now fill the next block with 56 bytes */
+	memset(ctx->in, 0, 56);
+    } else {
+	/* Pad block to 56 bytes */
+	memset(p, 0, count - 8);
+    }
+    byteReverse(ctx->in, 14);
+
+    /* Append length in bits and transform.
+     * Use memcpy to avoid aliasing problems.  On most systems,
+     * this will be optimized away to the same code.
+     */
+    memcpy(&ctx->in[14 * sizeof(uint32_t)], &ctx->bits[0], 4);
+    memcpy(&ctx->in[15 * sizeof(uint32_t)], &ctx->bits[1], 4);
+
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    byteReverse((unsigned char *) ctx->buf, 4);
+    memcpy(digest, ctx->buf, MD5LENGTH);
+    memset(ctx, 0, sizeof(*ctx));	/* In case it's sensitive */
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+	( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void MD5Transform(uint32_t buf[4], uint32_t const in[16])
+{
+    register uint32_t a, b, c, d;
+
+    a = buf[0];
+    b = buf[1];
+    c = buf[2];
+    d = buf[3];
+
+    MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+    MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+    MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+    MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+    MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+    MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+    MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+    MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+    MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+    MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+    MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+    MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+    MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+    MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+    MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+    MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+    MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+    MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+    MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+    MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+    MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+    MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+    MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+    MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+    MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+    MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+    MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+    MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+    MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+    MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+    MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+    MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+    MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+    MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+    MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+    MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+    MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+    MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+    MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+    MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+    MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+    MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+    MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+    MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+    MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+    MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+    MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+    MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+    MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+    MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+    MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+    MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+    MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+    MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+    MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+    MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+    MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+    MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+    MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+    MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+    MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+    MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+    MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+    MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+    buf[0] += a;
+    buf[1] += b;
+    buf[2] += c;
+    buf[3] += d;
+}
+
+#endif
+
diff --git a/libblkid/lib/monotonic.c b/libblkid/lib/monotonic.c
new file mode 100644
index 0000000..3d4a443
--- /dev/null
+++ b/libblkid/lib/monotonic.c
@@ -0,0 +1,68 @@
+/*
+ * Please, don't add this file to libcommon because clock_gettime() requires
+ * -lrt on systems with old libc.
+ */
+#include <time.h>
+#include <sys/sysinfo.h>
+#include <sys/time.h>
+
+#include "c.h"
+#include "nls.h"
+#include "monotonic.h"
+
+int get_boot_time(struct timeval *boot_time)
+{
+#ifdef CLOCK_BOOTTIME
+	struct timespec hires_uptime;
+	struct timeval lores_uptime;
+#endif
+	struct timeval now;
+#ifdef HAVE_SYSINFO
+	struct sysinfo info;
+#endif
+
+	if (gettimeofday(&now, NULL) != 0) {
+		warn(_("gettimeofday failed"));
+		return -errno;
+	}
+#ifdef CLOCK_BOOTTIME
+	if (clock_gettime(CLOCK_BOOTTIME, &hires_uptime) == 0) {
+		TIMESPEC_TO_TIMEVAL(&lores_uptime, &hires_uptime);
+		timersub(&now, &lores_uptime, boot_time);
+		return 0;
+	}
+#endif
+#ifdef HAVE_SYSINFO
+	/* fallback */
+	if (sysinfo(&info) != 0)
+		warn(_("sysinfo failed"));
+
+	boot_time->tv_sec = now.tv_sec - info.uptime;
+	boot_time->tv_usec = 0;
+	return 0;
+#else
+	return -ENOSYS;
+#endif
+}
+
+int gettime_monotonic(struct timeval *tv)
+{
+#ifdef CLOCK_MONOTONIC
+	/* Can slew only by ntp and adjtime */
+	int ret;
+	struct timespec ts;
+
+# ifdef CLOCK_MONOTONIC_RAW
+	/* Linux specific, cant slew */
+	if (!(ret = clock_gettime(CLOCK_MONOTONIC_RAW, &ts))) {
+# else
+	if (!(ret = clock_gettime(CLOCK_MONOTONIC, &ts))) {
+# endif
+		tv->tv_sec = ts.tv_sec;
+		tv->tv_usec = ts.tv_nsec / 1000;
+	}
+	return ret;
+#else
+	return gettimeofday(tv, NULL);
+#endif
+}
diff --git a/libblkid/lib/pager.c b/libblkid/lib/pager.c
new file mode 100644
index 0000000..9e09cd5
--- /dev/null
+++ b/libblkid/lib/pager.c
@@ -0,0 +1,210 @@
+/*
+ * Based on linux-perf/git scm
+ *
+ * Some modifications and simplifications for util-linux
+ * by Davidlohr Bueso <dave@xxxxxxx> - March 2012.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "c.h"
+#include "xalloc.h"
+#include "nls.h"
+
+#define NULL_DEVICE	"/dev/null"
+
+void setup_pager(void);
+
+static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
+
+struct child_process {
+	const char **argv;
+	pid_t pid;
+	int in;
+	int out;
+	int err;
+	unsigned no_stdin:1;
+	void (*preexec_cb)(void);
+};
+static struct child_process pager_process;
+
+static inline void close_pair(int fd[2])
+{
+	close(fd[0]);
+	close(fd[1]);
+}
+
+static int start_command(struct child_process *cmd)
+{
+	int need_in;
+	int fdin[2];
+
+	/*
+	 * In case of errors we must keep the promise to close FDs
+	 * that have been passed in via ->in and ->out.
+	 */
+	need_in = !cmd->no_stdin && cmd->in < 0;
+	if (need_in) {
+		if (pipe(fdin) < 0) {
+			if (cmd->out > 0)
+				close(cmd->out);
+			return -1;
+		}
+		cmd->in = fdin[1];
+	}
+
+	fflush(NULL);
+	cmd->pid = fork();
+	if (!cmd->pid) {
+		if (need_in) {
+			dup2(fdin[0], STDIN_FILENO);
+			close_pair(fdin);
+		} else if (cmd->in > 0) {
+			dup2(cmd->in, STDIN_FILENO);
+			close(cmd->in);
+		}
+
+		cmd->preexec_cb();
+		execvp(cmd->argv[0], (char *const*) cmd->argv);
+		exit(127); /* cmd not found */
+	}
+
+	if (cmd->pid < 0) {
+		if (need_in)
+			close_pair(fdin);
+		else if (cmd->in)
+			close(cmd->in);
+		return -1;
+	}
+
+	if (need_in)
+		close(fdin[0]);
+	else if (cmd->in)
+		close(cmd->in);
+	return 0;
+}
+
+static int wait_or_whine(pid_t pid)
+{
+	for (;;) {
+		int status, code;
+		pid_t waiting = waitpid(pid, &status, 0);
+
+		if (waiting < 0) {
+			if (errno == EINTR)
+				continue;
+			err(EXIT_FAILURE, _("waitpid failed (%s)"), strerror(errno));
+		}
+		if (waiting != pid)
+			return -1;
+		if (WIFSIGNALED(status))
+			return -1;
+
+		if (!WIFEXITED(status))
+			return -1;
+		code = WEXITSTATUS(status);
+		switch (code) {
+		case 127:
+			return -1;
+		case 0:
+			return 0;
+		default:
+			return -1;
+		}
+	}
+}
+
+static int finish_command(struct child_process *cmd)
+{
+	return wait_or_whine(cmd->pid);
+}
+
+static void pager_preexec(void)
+{
+	/*
+	 * Work around bug in "less" by not starting it until we
+	 * have real input
+	 */
+	fd_set in;
+
+	FD_ZERO(&in);
+	FD_SET(STDIN_FILENO, &in);
+	select(1, &in, NULL, &in, NULL);
+
+	setenv("LESS", "FRSX", 0);
+}
+
+static void wait_for_pager(void)
+{
+	fflush(stdout);
+	fflush(stderr);
+	/* signal EOF to pager */
+	close(STDOUT_FILENO);
+	close(STDERR_FILENO);
+	finish_command(&pager_process);
+}
+
+static void wait_for_pager_signal(int signo)
+{
+	wait_for_pager();
+	raise(signo);
+}
+
+void setup_pager(void)
+{
+	const char *pager = getenv("PAGER");
+
+	if (!isatty(STDOUT_FILENO))
+		return;
+
+	if (!pager)
+		pager = "less";
+	else if (!*pager || !strcmp(pager, "cat"))
+		return;
+
+	/* spawn the pager */
+	pager_argv[2] = pager;
+	pager_process.argv = pager_argv;
+	pager_process.in = -1;
+	pager_process.preexec_cb = pager_preexec;
+
+	if (start_command(&pager_process))
+		return;
+
+	/* original process continues, but writes to the pipe */
+	dup2(pager_process.in, STDOUT_FILENO);
+	if (isatty(STDERR_FILENO))
+		dup2(pager_process.in, STDERR_FILENO);
+	close(pager_process.in);
+
+	/* this makes sure that the parent terminates after the pager */
+	signal(SIGINT, wait_for_pager_signal);
+	signal(SIGHUP, wait_for_pager_signal);
+	signal(SIGTERM, wait_for_pager_signal);
+	signal(SIGQUIT, wait_for_pager_signal);
+	signal(SIGPIPE, wait_for_pager_signal);
+
+	atexit(wait_for_pager);
+}
+
+#ifdef TEST_PROGRAM
+
+#define MAX 255
+
+int main(int argc __attribute__ ((__unused__)),
+	 char *argv[] __attribute__ ((__unused__)))
+{
+	int i;
+
+	setup_pager();
+	for (i = 0; i < MAX; i++)
+		printf("%d\n", i);
+	return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/libblkid/lib/path.c b/libblkid/lib/path.c
new file mode 100644
index 0000000..fc90c0a
--- /dev/null
+++ b/libblkid/lib/path.c
@@ -0,0 +1,258 @@
+/*
+ * Simple functions to access files, paths maybe be globally prefixed by a
+ * global prefix to read data from alternative destination (e.g. /proc dump for
+ * regression tests).
+ *
+ * Taken from lscpu.c
+ *
+ * Copyright (C) 2008 Cai Qian <qcai@redhat.com>
+ * Copyright (C) 2008-2012 Karel Zak <kzak@redhat.com>
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include "all-io.h"
+#include "path.h"
+#include "nls.h"
+#include "c.h"
+
+static size_t prefixlen;
+static char pathbuf[PATH_MAX];
+
+static const char *
+path_vcreate(const char *path, va_list ap)
+{
+	if (prefixlen)
+		vsnprintf(pathbuf + prefixlen,
+			  sizeof(pathbuf) - prefixlen, path, ap);
+	else
+		vsnprintf(pathbuf, sizeof(pathbuf), path, ap);
+	return pathbuf;
+}
+
+char *
+path_strdup(const char *path, ...)
+{
+	const char *p;
+	va_list ap;
+
+	va_start(ap, path);
+	p = path_vcreate(path, ap);
+	va_end(ap);
+
+	return p ? strdup(p) : NULL;
+}
+
+static FILE *
+path_vfopen(const char *mode, int exit_on_error, const char *path, va_list ap)
+{
+	FILE *f;
+	const char *p = path_vcreate(path, ap);
+
+	f = fopen(p, mode);
+	if (!f && exit_on_error)
+		err(EXIT_FAILURE, _("cannot open %s"), p);
+	return f;
+}
+
+static int
+path_vopen(int flags, const char *path, va_list ap)
+{
+	int fd;
+	const char *p = path_vcreate(path, ap);
+
+	fd = open(p, flags);
+	if (fd == -1)
+		err(EXIT_FAILURE, _("cannot open %s"), p);
+	return fd;
+}
+
+FILE *
+path_fopen(const char *mode, int exit_on_error, const char *path, ...)
+{
+	FILE *fd;
+	va_list ap;
+
+	va_start(ap, path);
+	fd = path_vfopen(mode, exit_on_error, path, ap);
+	va_end(ap);
+
+	return fd;
+}
+
+void
+path_read_str(char *result, size_t len, const char *path, ...)
+{
+	FILE *fd;
+	va_list ap;
+
+	va_start(ap, path);
+	fd = path_vfopen("r", 1, path, ap);
+	va_end(ap);
+
+	if (!fgets(result, len, fd))
+		err(EXIT_FAILURE, _("cannot read %s"), pathbuf);
+	fclose(fd);
+
+	len = strlen(result);
+	if (result[len - 1] == '\n')
+		result[len - 1] = '\0';
+}
+
+int
+path_read_s32(const char *path, ...)
+{
+	FILE *fd;
+	va_list ap;
+	int result;
+
+	va_start(ap, path);
+	fd = path_vfopen("r", 1, path, ap);
+	va_end(ap);
+
+	if (fscanf(fd, "%d", &result) != 1) {
+		if (ferror(fd))
+			err(EXIT_FAILURE, _("cannot read %s"), pathbuf);
+		else
+			errx(EXIT_FAILURE, _("parse error: %s"), pathbuf);
+	}
+	fclose(fd);
+	return result;
+}
+
+uint64_t
+path_read_u64(const char *path, ...)
+{
+	FILE *fd;
+	va_list ap;
+	uint64_t result;
+
+	va_start(ap, path);
+	fd = path_vfopen("r", 1, path, ap);
+	va_end(ap);
+
+	if (fscanf(fd, "%"SCNu64, &result) != 1) {
+		if (ferror(fd))
+			err(EXIT_FAILURE, _("cannot read %s"), pathbuf);
+		else
+			errx(EXIT_FAILURE, _("parse error: %s"), pathbuf);
+	}
+	fclose(fd);
+	return result;
+}
+
+int
+path_write_str(const char *str, const char *path, ...)
+{
+	int fd, result;
+	va_list ap;
+
+	va_start(ap, path);
+	fd = path_vopen(O_WRONLY|O_CLOEXEC, path, ap);
+	va_end(ap);
+	result = write_all(fd, str, strlen(str));
+	close(fd);
+	return result;
+}
+
+int
+path_exist(const char *path, ...)
+{
+	va_list ap;
+	const char *p;
+
+	va_start(ap, path);
+	p = path_vcreate(path, ap);
+	va_end(ap);
+
+	return access(p, F_OK) == 0;
+}
+
+#ifdef HAVE_CPU_SET_T
+
+static cpu_set_t *
+path_cpuparse(int maxcpus, int islist, const char *path, va_list ap)
+{
+	FILE *fd;
+	cpu_set_t *set;
+	size_t setsize, len = maxcpus * 7;
+	char buf[len];
+
+	fd = path_vfopen("r", 1, path, ap);
+
+	if (!fgets(buf, len, fd))
+		err(EXIT_FAILURE, _("cannot read %s"), pathbuf);
+	fclose(fd);
+
+	len = strlen(buf);
+	if (buf[len - 1] == '\n')
+		buf[len - 1] = '\0';
+
+	set = cpuset_alloc(maxcpus, &setsize, NULL);
+	if (!set)
+		err(EXIT_FAILURE, _("failed to callocate cpu set"));
+
+	if (islist) {
+		if (cpulist_parse(buf, set, setsize, 0))
+			errx(EXIT_FAILURE, _("failed to parse CPU list %s"), buf);
+	} else {
+		if (cpumask_parse(buf, set, setsize))
+			errx(EXIT_FAILURE, _("failed to parse CPU mask %s"), buf);
+	}
+	return set;
+}
+
+cpu_set_t *
+path_read_cpuset(int maxcpus, const char *path, ...)
+{
+	va_list ap;
+	cpu_set_t *set;
+
+	va_start(ap, path);
+	set = path_cpuparse(maxcpus, 0, path, ap);
+	va_end(ap);
+
+	return set;
+}
+
+cpu_set_t *
+path_read_cpulist(int maxcpus, const char *path, ...)
+{
+	va_list ap;
+	cpu_set_t *set;
+
+	va_start(ap, path);
+	set = path_cpuparse(maxcpus, 1, path, ap);
+	va_end(ap);
+
+	return set;
+}
+
+#endif /* HAVE_CPU_SET_T */
+
+void
+path_set_prefix(const char *prefix)
+{
+	prefixlen = strlen(prefix);
+	strncpy(pathbuf, prefix, sizeof(pathbuf));
+	pathbuf[sizeof(pathbuf) - 1] = '\0';
+}
diff --git a/libblkid/lib/procutils.c b/libblkid/lib/procutils.c
new file mode 100644
index 0000000..ef96941
--- /dev/null
+++ b/libblkid/lib/procutils.c
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2011 Davidlohr Bueso <dave@gnu.org>
+ *
+ * procutils.c: General purpose procfs parsing utilities
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library Public License for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#include "procutils.h"
+#include "at.h"
+#include "c.h"
+
+/*
+ * @pid: process ID for which we want to obtain the threads group
+ *
+ * Returns: newly allocated tasks structure
+ */
+struct proc_tasks *proc_open_tasks(pid_t pid)
+{
+	struct proc_tasks *tasks;
+	char path[PATH_MAX];
+
+	sprintf(path, "/proc/%d/task/", pid);
+
+	tasks = malloc(sizeof(struct proc_tasks));
+	if (tasks) {
+		tasks->dir = opendir(path);
+		if (tasks->dir)
+			return tasks;
+	}
+
+	free(tasks);
+	return NULL;
+}
+
+/*
+ * @tasks: allocated tasks structure
+ *
+ * Returns: nothing
+ */
+void proc_close_tasks(struct proc_tasks *tasks)
+{
+	if (tasks && tasks->dir)
+		closedir(tasks->dir);
+	free(tasks);
+}
+
+/*
+ * @tasks: allocated task structure
+ * @tid: [output] one of the thread IDs belonging to the thread group
+ *        If when an error occurs, it is set to 0.
+ *
+ * Returns: 0 on success, 1 on end, -1 on failure or no more threads
+ */
+int proc_next_tid(struct proc_tasks *tasks, pid_t *tid)
+{
+	struct dirent *d;
+	char *end;
+
+	if (!tasks || !tid)
+		return -EINVAL;
+
+	*tid = 0;
+	errno = 0;
+
+	do {
+		d = readdir(tasks->dir);
+		if (!d)
+			return errno ? -1 : 1;		/* error or end-of-dir */
+
+		if (!isdigit((unsigned char) *d->d_name))
+			continue;
+		errno = 0;
+		*tid = (pid_t) strtol(d->d_name, &end, 10);
+		if (errno || d->d_name == end || (end && *end))
+			return -1;
+
+	} while (!*tid);
+
+	return 0;
+}
+
+struct proc_processes *proc_open_processes(void)
+{
+	struct proc_processes *ps;
+
+	ps = calloc(1, sizeof(struct proc_processes));
+	if (ps) {
+		ps->dir = opendir("/proc");
+		if (ps->dir)
+			return ps;
+	}
+
+	free(ps);
+	return NULL;
+}
+
+void proc_close_processes(struct proc_processes *ps)
+{
+	if (ps && ps->dir)
+		closedir(ps->dir);
+	free(ps);
+}
+
+void proc_processes_filter_by_name(struct proc_processes *ps, const char *name)
+{
+	ps->fltr_name = name;
+	ps->has_fltr_name = name ? 1 : 0;
+}
+
+void proc_processes_filter_by_uid(struct proc_processes *ps, uid_t uid)
+{
+	ps->fltr_uid = uid;
+	ps->has_fltr_uid = 1;
+}
+
+int proc_next_pid(struct proc_processes *ps, pid_t *pid)
+{
+	struct dirent *d;
+
+	if (!ps || !pid)
+		return -EINVAL;
+
+	*pid = 0;
+	errno = 0;
+
+	do {
+		char buf[BUFSIZ], *p;
+
+		d = readdir(ps->dir);
+		if (!d)
+			return errno ? -1 : 1;		/* error or end-of-dir */
+
+
+		if (!isdigit((unsigned char) *d->d_name))
+			continue;
+
+		/* filter out by UID */
+		if (ps->has_fltr_uid) {
+			struct stat st;
+
+			if (fstat_at(dirfd(ps->dir), "/proc", d->d_name, &st, 0))
+				continue;
+			if (ps->fltr_uid != st.st_uid)
+				continue;
+		}
+
+		/* filter out by NAME */
+		if (ps->has_fltr_name) {
+			char procname[256];
+			FILE *f;
+
+			snprintf(buf, sizeof(buf), "%s/stat", d->d_name);
+			f = fopen_at(dirfd(ps->dir), "/proc", buf,
+						O_CLOEXEC|O_RDONLY, "r");
+			if (!f)
+				continue;
+
+			p = fgets(buf, sizeof(buf), f);
+			fclose(f);
+			if (!p)
+				continue;
+
+			if (sscanf(buf, "%*d (%255[^)])", procname) != 1)
+				continue;
+
+			/* ok, we got the process name. */
+			if (strcmp(procname, ps->fltr_name) != 0)
+				continue;
+		}
+
+		p = NULL;
+		errno = 0;
+		*pid = (pid_t) strtol(d->d_name, &p, 10);
+		if (errno || d->d_name == p || (p && *p))
+			return errno ? -errno : -1;
+
+		return 0;
+	} while (1);
+
+	return 0;
+}
+
+#ifdef TEST_PROGRAM
+
+static int test_tasks(int argc, char *argv[])
+{
+	pid_t tid, pid;
+	struct proc_tasks *ts;
+
+	if (argc != 2)
+		return EXIT_FAILURE;
+
+	pid = strtol(argv[1], (char **) NULL, 10);
+	printf("PID=%d, TIDs:", pid);
+
+	ts = proc_open_tasks(pid);
+	if (!ts)
+		err(EXIT_FAILURE, "open list of tasks failed");
+
+	while (proc_next_tid(ts, &tid) == 0)
+		printf(" %d", tid);
+
+	printf("\n");
+        proc_close_tasks(ts);
+	return EXIT_SUCCESS;
+}
+
+static int test_processes(int argc, char *argv[])
+{
+	pid_t pid;
+	struct proc_processes *ps;
+
+	ps = proc_open_processes();
+	if (!ps)
+		err(EXIT_FAILURE, "open list of processes failed");
+
+	if (argc >= 3 && strcmp(argv[1], "--name") == 0)
+		proc_processes_filter_by_name(ps, argv[2]);
+
+	if (argc >= 3 && strcmp(argv[1], "--uid") == 0)
+		proc_processes_filter_by_uid(ps, (uid_t) atol(argv[2]));
+
+	while (proc_next_pid(ps, &pid) == 0)
+		printf(" %d", pid);
+
+	printf("\n");
+        proc_close_processes(ps);
+	return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+	if (argc < 2) {
+		fprintf(stderr, "usage: %1$s --tasks <pid>\n"
+				"       %1$s --processes [---name <name>] [--uid <uid>]\n",
+				program_invocation_short_name);
+		return EXIT_FAILURE;
+	}
+
+	if (strcmp(argv[1], "--tasks") == 0)
+		return test_tasks(argc - 1, argv + 1);
+	if (strcmp(argv[1], "--processes") == 0)
+		return test_processes(argc - 1, argv + 1);
+
+	return EXIT_FAILURE;
+}
+#endif /* TEST_PROGRAM */
diff --git a/libblkid/lib/randutils.c b/libblkid/lib/randutils.c
new file mode 100644
index 0000000..684ac0a
--- /dev/null
+++ b/libblkid/lib/randutils.c
@@ -0,0 +1,147 @@
+/*
+ * General purpose random utilities
+ *
+ * Based on libuuid code.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include <sys/syscall.h>
+
+#include "c.h"
+#include "randutils.h"
+#include "nls.h"
+
+#ifdef HAVE_TLS
+#define THREAD_LOCAL static __thread
+#else
+#define THREAD_LOCAL static
+#endif
+
+#if defined(__linux__) && defined(__NR_gettid) && defined(HAVE_JRAND48)
+#define DO_JRAND_MIX
+THREAD_LOCAL unsigned short ul_jrand_seed[3];
+#endif
+
+int random_get_fd(void)
+{
+	int i, fd;
+	struct timeval	tv;
+
+	gettimeofday(&tv, 0);
+	fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
+	if (fd == -1)
+		fd = open("/dev/random", O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+	if (fd >= 0) {
+		i = fcntl(fd, F_GETFD);
+		if (i >= 0)
+			fcntl(fd, F_SETFD, i | FD_CLOEXEC);
+	}
+	srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
+
+#ifdef DO_JRAND_MIX
+	ul_jrand_seed[0] = getpid() ^ (tv.tv_sec & 0xFFFF);
+	ul_jrand_seed[1] = getppid() ^ (tv.tv_usec & 0xFFFF);
+	ul_jrand_seed[2] = (tv.tv_sec ^ tv.tv_usec) >> 16;
+#endif
+	/* Crank the random number generator a few times */
+	gettimeofday(&tv, 0);
+	for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--)
+		rand();
+	return fd;
+}
+
+
+/*
+ * Generate a stream of random nbytes into buf.
+ * Use /dev/urandom if possible, and if not,
+ * use glibc pseudo-random functions.
+ */
+void random_get_bytes(void *buf, size_t nbytes)
+{
+	size_t i, n = nbytes;
+	int fd = random_get_fd();
+	int lose_counter = 0;
+	unsigned char *cp = (unsigned char *) buf;
+
+	if (fd >= 0) {
+		while (n > 0) {
+			ssize_t x = read(fd, cp, n);
+			if (x <= 0) {
+				if (lose_counter++ > 16)
+					break;
+				continue;
+			}
+			n -= x;
+			cp += x;
+			lose_counter = 0;
+		}
+
+		close(fd);
+	}
+
+	/*
+	 * We do this all the time, but this is the only source of
+	 * randomness if /dev/random/urandom is out to lunch.
+	 */
+	for (cp = buf, i = 0; i < nbytes; i++)
+		*cp++ ^= (rand() >> 7) & 0xFF;
+
+#ifdef DO_JRAND_MIX
+	{
+		unsigned short tmp_seed[3];
+
+		memcpy(tmp_seed, ul_jrand_seed, sizeof(tmp_seed));
+		ul_jrand_seed[2] = ul_jrand_seed[2] ^ syscall(__NR_gettid);
+		for (cp = buf, i = 0; i < nbytes; i++)
+			*cp++ ^= (jrand48(tmp_seed) >> 7) & 0xFF;
+		memcpy(ul_jrand_seed, tmp_seed,
+		       sizeof(ul_jrand_seed)-sizeof(unsigned short));
+	}
+#endif
+
+	return;
+}
+
+
+/*
+ * Tell source of randomness.
+ */
+const char *random_tell_source(void)
+{
+	size_t i;
+	static const char *random_sources[] = {
+		"/dev/urandom",
+		"/dev/random"
+	};
+
+	for (i = 0; i < ARRAY_SIZE(random_sources); i++) {
+		if (!access(random_sources[i], R_OK))
+			return random_sources[i];
+	}
+
+	return _("libc pseudo-random functions");
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc __attribute__ ((__unused__)),
+         char *argv[] __attribute__ ((__unused__)))
+{
+	unsigned int v, i;
+
+	/* generate and print 10 random numbers */
+	for (i = 0; i < 10; i++) {
+		random_get_bytes(&v, sizeof(v));
+		printf("%d\n", v);
+	}
+
+	return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/libblkid/lib/readutmp.c b/libblkid/lib/readutmp.c
new file mode 100644
index 0000000..b11e9a4
--- /dev/null
+++ b/libblkid/lib/readutmp.c
@@ -0,0 +1,78 @@
+/* GNU's read utmp module.
+
+	 Copyright (C) 1992-2001, 2003-2006, 2009-2014 Free Software Foundation, Inc.
+
+	 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
+	 the Free Software Foundation; either version 3 of the License, or
+	 (at your option) any later version.
+
+	 This program is distributed in the hope that it will be useful,
+	 but WITHOUT ANY WARRANTY; without even the implied warranty of
+	 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, see <http://www.gnu.org/licenses/>.	*/
+
+/* Written by jla; revised by djm */
+/* extracted for util-linux by ooprala */
+
+#include <errno.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "xalloc.h"
+#include "readutmp.h"
+
+/* Read the utmp entries corresponding to file FILE into freshly-
+	 malloc'd storage, set *UTMP_BUF to that pointer, set *N_ENTRIES to
+	 the number of entries, and return zero.	If there is any error,
+	 return -1, setting errno, and don't modify the parameters.
+	 If OPTIONS & READ_UTMP_CHECK_PIDS is nonzero, omit entries whose
+	 process-IDs do not currently exist.	*/
+int
+read_utmp (char const *file, size_t *n_entries, struct utmp **utmp_buf)
+{
+	size_t n_read = 0;
+	size_t n_alloc = 0;
+	struct utmp *utmp = NULL;
+	struct utmp *u;
+
+	/* Ignore the return value for now.
+		 Solaris' utmpname returns 1 upon success -- which is contrary
+		 to what the GNU libc version does.	In addition, older GNU libc
+		 versions are actually void.	 */
+	utmpname(file);
+
+	setutent();
+
+	errno = 0;
+	while ((u = getutent()) != NULL) {
+		if (n_read == n_alloc) {
+			n_alloc += 32;
+			utmp = xrealloc(utmp, n_alloc * sizeof (struct utmp));
+			if (!utmp)
+				return -1;
+		}
+		utmp[n_read++] = *u;
+	}
+	if (!u && errno) {
+		free(utmp);
+		return -1;
+	}
+
+	endutent();
+
+	*n_entries = n_read;
+	*utmp_buf = utmp;
+
+	return 0;
+}
diff --git a/libblkid/lib/setproctitle.c b/libblkid/lib/setproctitle.c
new file mode 100644
index 0000000..4bcf8c8
--- /dev/null
+++ b/libblkid/lib/setproctitle.c
@@ -0,0 +1,74 @@
+/*
+ *  set process title for ps (from sendmail)
+ *
+ *  Clobbers argv of our main procedure so ps(1) will display the title.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "setproctitle.h"
+
+#ifndef SPT_BUFSIZE
+# define SPT_BUFSIZE     2048
+#endif
+
+extern char **environ;
+
+static char **argv0;
+static int argv_lth;
+
+void initproctitle (int argc, char **argv)
+{
+	int i;
+	char **envp = environ;
+
+	/*
+	 * Move the environment so we can reuse the memory.
+	 * (Code borrowed from sendmail.)
+	 * WARNING: ugly assumptions on memory layout here;
+	 *          if this ever causes problems, #undef DO_PS_FIDDLING
+	 */
+	for (i = 0; envp[i] != NULL; i++)
+		continue;
+
+	environ = (char **) malloc(sizeof(char *) * (i + 1));
+	if (environ == NULL)
+		return;
+
+	for (i = 0; envp[i] != NULL; i++)
+		if ((environ[i] = strdup(envp[i])) == NULL)
+			return;
+	environ[i] = NULL;
+
+	argv0 = argv;
+	if (i > 0)
+		argv_lth = envp[i-1] + strlen(envp[i-1]) - argv0[0];
+	else
+		argv_lth = argv0[argc-1] + strlen(argv0[argc-1]) - argv0[0];
+}
+
+void setproctitle (const char *prog, const char *txt)
+{
+        int i;
+        char buf[SPT_BUFSIZE];
+
+        if (!argv0)
+                return;
+
+	if (strlen(prog) + strlen(txt) + 5 > SPT_BUFSIZE)
+		return;
+
+	sprintf(buf, "%s -- %s", prog, txt);
+
+        i = strlen(buf);
+        if (i > argv_lth - 2) {
+                i = argv_lth - 2;
+                buf[i] = '\0';
+        }
+	memset(argv0[0], '\0', argv_lth);       /* clear the memory area */
+        strcpy(argv0[0], buf);
+
+        argv0[1] = NULL;
+}
diff --git a/libblkid/lib/strutils.c b/libblkid/lib/strutils.c
new file mode 100644
index 0000000..9fe9481
--- /dev/null
+++ b/libblkid/lib/strutils.c
@@ -0,0 +1,763 @@
+/*
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <assert.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "bitops.h"
+
+static int do_scale_by_power (uintmax_t *x, int base, int power)
+{
+	while (power--) {
+		if (UINTMAX_MAX / base < *x)
+			return -ERANGE;
+		*x *= base;
+	}
+	return 0;
+}
+
+/*
+ * strtosize() - convert string to size (uintmax_t).
+ *
+ * Supported suffixes:
+ *
+ * XiB or X for 2^N
+ *     where X = {K,M,G,T,P,E,Z,Y}
+ *        or X = {k,m,g,t,p,e}  (undocumented for backward compatibility only)
+ * for example:
+ *		10KiB	= 10240
+ *		10K	= 10240
+ *
+ * XB for 10^N
+ *     where X = {K,M,G,T,P,E,Z,Y}
+ * for example:
+ *		10KB	= 10000
+ *
+ * The optinal 'power' variable returns number associated with used suffix
+ * {K,M,G,T,P,E,Z,Y}  = {1,2,3,4,5,6,7,8}.
+ *
+ * The function also supports decimal point, for example:
+ *              0.5MB   = 500000
+ *              0.5MiB  = 512000
+ *
+ * Note that the function does not accept numbers with '-' (negative sign)
+ * prefix.
+ */
+int parse_size(const char *str, uintmax_t *res, int *power)
+{
+	char *p;
+	uintmax_t x, frac = 0;
+	int base = 1024, rc = 0, pwr = 0, frac_zeros = 0;
+
+	static const char *suf  = "KMGTPEYZ";
+	static const char *suf2 = "kmgtpeyz";
+	const char *sp;
+
+	*res = 0;
+
+	if (!str || !*str) {
+		rc = -EINVAL;
+		goto err;
+	}
+
+	/* Only positive numbers are acceptable
+	 *
+	 * Note that this check is not perfect, it would be better to
+	 * use lconv->negative_sign. But coreutils use the same solution,
+	 * so it's probably good enough...
+	 */
+	p = (char *) str;
+	while (isspace((unsigned char) *p))
+		p++;
+	if (*p == '-') {
+		rc = -EINVAL;
+		goto err;
+	}
+	p = NULL;
+
+	errno = 0;
+	x = strtoumax(str, &p, 0);
+
+	if (p == str ||
+	    (errno != 0 && (x == UINTMAX_MAX || x == 0))) {
+		rc = errno ? -errno : -1;
+		goto err;
+	}
+	if (!p || !*p)
+		goto done;			/* without suffix */
+
+	/*
+	 * Check size suffixes
+	 */
+check_suffix:
+	if (*(p + 1) == 'i' && *(p + 2) == 'B' && !*(p + 3))
+		base = 1024;			/* XiB, 2^N */
+	else if (*(p + 1) == 'B' && !*(p + 2))
+		base = 1000;			/* XB, 10^N */
+	else if (*(p + 1)) {
+		struct lconv const *l = localeconv();
+		char *dp = l ? l->decimal_point : NULL;
+		size_t dpsz = dp ? strlen(dp) : 0;
+
+		if (frac == 0 && *p && dp && strncmp(dp, p, dpsz) == 0) {
+			char *fstr = p + dpsz;
+
+			for (p = fstr; *p && *p == '0'; p++)
+				frac_zeros++;
+			errno = 0, p = NULL;
+			frac = strtoumax(fstr, &p, 0);
+			if (p == fstr ||
+			    (errno != 0 && (frac == UINTMAX_MAX || frac == 0))) {
+				rc = errno ? -errno : -1;
+				goto err;
+			}
+			if (frac && (!p  || !*p)) {
+				rc = -EINVAL;
+				goto err;		/* without suffix, but with frac */
+			}
+			goto check_suffix;
+		}
+		rc = -EINVAL;
+		goto err;			/* unexpected suffix */
+	}
+
+	sp = strchr(suf, *p);
+	if (sp)
+		pwr = (sp - suf) + 1;
+	else {
+		sp = strchr(suf2, *p);
+		if (sp)
+			pwr = (sp - suf2) + 1;
+		else {
+			rc = -EINVAL;
+			goto err;
+		}
+	}
+
+	rc = do_scale_by_power(&x, base, pwr);
+	if (power)
+		*power = pwr;
+	if (frac && pwr) {
+		int zeros_in_pwr = frac_zeros % 3;
+		int frac_pwr = pwr - (frac_zeros / 3) - 1;
+		uintmax_t y = frac * (zeros_in_pwr == 0 ? 100 :
+				      zeros_in_pwr == 1 ?  10 : 1);
+
+		if (frac_pwr < 0) {
+			rc = -EINVAL;
+			goto err;
+		}
+		do_scale_by_power(&y, base, frac_pwr);
+		x += y;
+	}
+done:
+	*res = x;
+err:
+	return rc;
+}
+
+int strtosize(const char *str, uintmax_t *res)
+{
+	return parse_size(str, res, NULL);
+}
+
+int isdigit_string(const char *str)
+{
+	const char *p;
+
+	for (p = str; p && *p && isdigit((unsigned char) *p); p++);
+
+	return p && p > str && !*p;
+}
+
+
+#ifndef HAVE_MEMPCPY
+void *mempcpy(void *restrict dest, const void *restrict src, size_t n)
+{
+    return ((char *)memcpy(dest, src, n)) + n;
+}
+#endif
+
+#ifndef HAVE_STRNLEN
+size_t strnlen(const char *s, size_t maxlen)
+{
+        int i;
+
+        for (i = 0; i < maxlen; i++) {
+                if (s[i] == '\0')
+                        return i + 1;
+        }
+        return maxlen;
+}
+#endif
+
+#ifndef HAVE_STRNCHR
+char *strnchr(const char *s, size_t maxlen, int c)
+{
+	for (; maxlen-- && *s != '\0'; ++s)
+		if (*s == (char)c)
+			return (char *)s;
+	return NULL;
+}
+#endif
+
+#ifndef HAVE_STRNDUP
+char *strndup(const char *s, size_t n)
+{
+	size_t len = strnlen(s, n);
+	char *new = (char *) malloc((len + 1) * sizeof(char));
+	if (!new)
+		return NULL;
+	new[len] = '\0';
+	return (char *) memcpy(new, s, len);
+}
+#endif
+
+int16_t strtos16_or_err(const char *str, const char *errmesg)
+{
+	int32_t num = strtos32_or_err(str, errmesg);
+
+	if (num < INT16_MIN || num > INT16_MAX)
+		errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+	return num;
+}
+
+uint16_t strtou16_or_err(const char *str, const char *errmesg)
+{
+	uint32_t num = strtou32_or_err(str, errmesg);
+
+	if (num > UINT16_MAX)
+		errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+	return num;
+}
+
+int32_t strtos32_or_err(const char *str, const char *errmesg)
+{
+	int64_t num = strtos64_or_err(str, errmesg);
+
+	if (num < INT32_MIN || num > INT32_MAX)
+		errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+	return num;
+}
+
+uint32_t strtou32_or_err(const char *str, const char *errmesg)
+{
+	uint64_t num = strtou64_or_err(str, errmesg);
+
+	if (num > UINT32_MAX)
+		errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+	return num;
+}
+
+int64_t strtos64_or_err(const char *str, const char *errmesg)
+{
+	int64_t num;
+	char *end = NULL;
+
+	if (str == NULL || *str == '\0')
+		goto err;
+	errno = 0;
+	num = strtoimax(str, &end, 10);
+
+	if (errno || str == end || (end && *end))
+		goto err;
+
+	return num;
+err:
+	if (errno)
+		err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+	errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+uint64_t strtou64_or_err(const char *str, const char *errmesg)
+{
+	uintmax_t num;
+	char *end = NULL;
+
+	if (str == NULL || *str == '\0')
+		goto err;
+	errno = 0;
+	num = strtoumax(str, &end, 10);
+
+	if (errno || str == end || (end && *end))
+		goto err;
+
+	return num;
+err:
+	if (errno)
+		err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+	errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+
+double strtod_or_err(const char *str, const char *errmesg)
+{
+	double num;
+	char *end = NULL;
+
+	if (str == NULL || *str == '\0')
+		goto err;
+	errno = 0;
+	num = strtod(str, &end);
+
+	if (errno || str == end || (end && *end))
+		goto err;
+
+	return num;
+err:
+	if (errno)
+		err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+	errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+long strtol_or_err(const char *str, const char *errmesg)
+{
+	long num;
+	char *end = NULL;
+
+	if (str == NULL || *str == '\0')
+		goto err;
+	errno = 0;
+	num = strtol(str, &end, 10);
+
+	if (errno || str == end || (end && *end))
+		goto err;
+
+	return num;
+err:
+	if (errno)
+		err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+	errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+unsigned long strtoul_or_err(const char *str, const char *errmesg)
+{
+	unsigned long num;
+	char *end = NULL;
+
+	if (str == NULL || *str == '\0')
+		goto err;
+	errno = 0;
+	num = strtoul(str, &end, 10);
+
+	if (errno || str == end || (end && *end))
+		goto err;
+
+	return num;
+err:
+	if (errno)
+		err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+	errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+uintmax_t strtosize_or_err(const char *str, const char *errmesg)
+{
+	uintmax_t num;
+
+	if (strtosize(str, &num) == 0)
+		return num;
+
+	if (errno)
+		err(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+
+	errx(STRTOXX_EXIT_CODE, "%s: '%s'", errmesg, str);
+}
+
+
+void strtotimeval_or_err(const char *str, struct timeval *tv, const char *errmesg)
+{
+	double user_input;
+
+	user_input = strtod_or_err(str, errmesg);
+	tv->tv_sec = (time_t) user_input;
+	tv->tv_usec = (long)((user_input - tv->tv_sec) * 1000000);
+}
+
+/*
+ * Converts stat->st_mode to ls(1)-like mode string. The size of "str" must
+ * be 11 bytes.
+ */
+void strmode(mode_t mode, char *str)
+{
+	if (S_ISDIR(mode))
+		str[0] = 'd';
+	else if (S_ISLNK(mode))
+		str[0] = 'l';
+	else if (S_ISCHR(mode))
+		str[0] = 'c';
+	else if (S_ISBLK(mode))
+		str[0] = 'b';
+	else if (S_ISSOCK(mode))
+		str[0] = 's';
+	else if (S_ISFIFO(mode))
+		str[0] = 'p';
+	else if (S_ISREG(mode))
+		str[0] = '-';
+
+	str[1] = mode & S_IRUSR ? 'r' : '-';
+	str[2] = mode & S_IWUSR ? 'w' : '-';
+	str[3] = (mode & S_ISUID
+		? (mode & S_IXUSR ? 's' : 'S')
+		: (mode & S_IXUSR ? 'x' : '-'));
+	str[4] = mode & S_IRGRP ? 'r' : '-';
+	str[5] = mode & S_IWGRP ? 'w' : '-';
+	str[6] = (mode & S_ISGID
+		? (mode & S_IXGRP ? 's' : 'S')
+		: (mode & S_IXGRP ? 'x' : '-'));
+	str[7] = mode & S_IROTH ? 'r' : '-';
+	str[8] = mode & S_IWOTH ? 'w' : '-';
+	str[9] = (mode & S_ISVTX
+		? (mode & S_IXOTH ? 't' : 'T')
+		: (mode & S_IXOTH ? 'x' : '-'));
+	str[10] = '\0';
+}
+
+/*
+ * returns exponent (2^x=n) in range KiB..PiB
+ */
+static int get_exp(uint64_t n)
+{
+	int shft;
+
+	for (shft = 10; shft <= 60; shft += 10) {
+		if (n < (1ULL << shft))
+			break;
+	}
+	return shft - 10;
+}
+
+char *size_to_human_string(int options, uint64_t bytes)
+{
+	char buf[32];
+	int dec, exp;
+	uint64_t frac;
+	const char *letters = "BKMGTPE";
+	char suffix[sizeof(" KiB")], *psuf = suffix;
+	char c;
+
+	if (options & SIZE_SUFFIX_SPACE)
+		*psuf++ = ' ';
+
+	exp  = get_exp(bytes);
+	c    = *(letters + (exp ? exp / 10 : 0));
+	dec  = exp ? bytes / (1ULL << exp) : bytes;
+	frac = exp ? bytes % (1ULL << exp) : 0;
+
+	*psuf++ = c;
+
+	if ((options & SIZE_SUFFIX_3LETTER) && (c != 'B')) {
+		*psuf++ = 'i';
+		*psuf++ = 'B';
+	}
+
+	*psuf = '\0';
+
+	/* fprintf(stderr, "exp: %d, unit: %c, dec: %d, frac: %jd\n",
+	 *                 exp, suffix[0], dec, frac);
+	 */
+
+	if (frac) {
+		/* round */
+		frac = (frac / (1ULL << (exp - 10)) + 50) / 100;
+		if (frac == 10)
+			dec++, frac = 0;
+	}
+
+	if (frac) {
+		struct lconv const *l = localeconv();
+		char *dp = l ? l->decimal_point : NULL;
+
+		if (!dp || !*dp)
+			dp = ".";
+		snprintf(buf, sizeof(buf), "%d%s%jd%s", dec, dp, frac, suffix);
+	} else
+		snprintf(buf, sizeof(buf), "%d%s", dec, suffix);
+
+	return strdup(buf);
+}
+
+/*
+ * Parses comma delimited list to array with IDs, for example:
+ *
+ * "aaa,bbb,ccc" --> ary[0] = FOO_AAA;
+ *                   ary[1] = FOO_BBB;
+ *                   ary[3] = FOO_CCC;
+ *
+ * The function name2id() provides conversion from string to ID.
+ *
+ * Returns: >= 0  : number of items added to ary[]
+ *            -1  : parse error or unknown item
+ *            -2  : arysz reached
+ */
+int string_to_idarray(const char *list, int ary[], size_t arysz,
+			int (name2id)(const char *, size_t))
+{
+	const char *begin = NULL, *p;
+	size_t n = 0;
+
+	if (!list || !*list || !ary || !arysz || !name2id)
+		return -1;
+
+	for (p = list; p && *p; p++) {
+		const char *end = NULL;
+		int id;
+
+		if (n >= arysz)
+			return -2;
+		if (!begin)
+			begin = p;		/* begin of the column name */
+		if (*p == ',')
+			end = p;		/* terminate the name */
+		if (*(p + 1) == '\0')
+			end = p + 1;		/* end of string */
+		if (!begin || !end)
+			continue;
+		if (end <= begin)
+			return -1;
+
+		id = name2id(begin, end - begin);
+		if (id == -1)
+			return -1;
+		ary[ n++ ] = id;
+		begin = NULL;
+		if (end && !*end)
+			break;
+	}
+	return n;
+}
+
+/*
+ * Parses the array like string_to_idarray but if format is "+aaa,bbb"
+ * it adds fields to array instead of replacing them.
+ */
+int string_add_to_idarray(const char *list, int ary[], size_t arysz,
+			int *ary_pos, int (name2id)(const char *, size_t))
+{
+	const char *list_add;
+	int r;
+
+	if (!list || !*list || !ary_pos ||
+	    *ary_pos < 0 || (size_t) *ary_pos > arysz)
+		return -1;
+
+	if (list[0] == '+')
+		list_add = &list[1];
+	else {
+		list_add = list;
+		*ary_pos = 0;
+	}
+
+	r = string_to_idarray(list_add, &ary[*ary_pos], arysz - *ary_pos, name2id);
+	if (r > 0)
+		*ary_pos += r;
+	return r;
+}
+
+/*
+ * LIST ::= <item> [, <item>]
+ *
+ * The <item> is translated to 'id' by name2id() function and the 'id' is used
+ * as a position in the 'ary' bit array. It means that the 'id' has to be in
+ * range <0..N> where N < sizeof(ary) * NBBY.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int string_to_bitarray(const char *list,
+		     char *ary,
+		     int (*name2bit)(const char *, size_t))
+{
+	const char *begin = NULL, *p;
+
+	if (!list || !name2bit || !ary)
+		return -EINVAL;
+
+	for (p = list; p && *p; p++) {
+		const char *end = NULL;
+		int bit;
+
+		if (!begin)
+			begin = p;		/* begin of the level name */
+		if (*p == ',')
+			end = p;		/* terminate the name */
+		if (*(p + 1) == '\0')
+			end = p + 1;		/* end of string */
+		if (!begin || !end)
+			continue;
+		if (end <= begin)
+			return -1;
+
+		bit = name2bit(begin, end - begin);
+		if (bit < 0)
+			return bit;
+		setbit(ary, bit);
+		begin = NULL;
+		if (end && !*end)
+			break;
+	}
+	return 0;
+}
+
+/*
+ * LIST ::= <item> [, <item>]
+ *
+ * The <item> is translated to 'id' by name2flag() function and the flags is
+ * set to the 'mask'
+*
+ * Returns: 0 on success, <0 on error.
+ */
+int string_to_bitmask(const char *list,
+		     unsigned long *mask,
+		     long (*name2flag)(const char *, size_t))
+{
+	const char *begin = NULL, *p;
+
+	if (!list || !name2flag || !mask)
+		return -EINVAL;
+
+	for (p = list; p && *p; p++) {
+		const char *end = NULL;
+		long flag;
+
+		if (!begin)
+			begin = p;		/* begin of the level name */
+		if (*p == ',')
+			end = p;		/* terminate the name */
+		if (*(p + 1) == '\0')
+			end = p + 1;		/* end of string */
+		if (!begin || !end)
+			continue;
+		if (end <= begin)
+			return -1;
+
+		flag = name2flag(begin, end - begin);
+		if (flag < 0)
+			return flag;	/* error */
+		*mask |= flag;
+		begin = NULL;
+		if (end && !*end)
+			break;
+	}
+	return 0;
+}
+
+/*
+ * Parse the lower and higher values in a string containing
+ * "lower:higher" or "lower-higher" format. Note that either
+ * the lower or the higher values may be missing, and the def
+ * value will be assigned to it by default.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int parse_range(const char *str, int *lower, int *upper, int def)
+{
+	char *end = NULL;
+
+	if (!str)
+		return 0;
+
+	*upper = *lower = def;
+	errno = 0;
+
+	if (*str == ':') {				/* <:N> */
+		str++;
+		*upper = strtol(str, &end, 10);
+		if (errno || !end || *end || end == str)
+			return -1;
+	} else {
+		*upper = *lower = strtol(str, &end, 10);
+		if (errno || !end || end == str)
+			return -1;
+
+		if (*end == ':' && !*(end + 1))		/* <M:> */
+			*upper = 0;
+		else if (*end == '-' || *end == ':') {	/* <M:N> <M-N> */
+			str = end + 1;
+			end = NULL;
+			errno = 0;
+			*upper = strtol(str, &end, 10);
+
+			if (errno || !end || *end || end == str)
+				return -1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Compare two strings for equality, ignoring at most one trailing
+ * slash.
+ */
+int streq_except_trailing_slash(const char *s1, const char *s2)
+{
+	int equal;
+
+	if (!s1 && !s2)
+		return 1;
+	if (!s1 || !s2)
+		return 0;
+
+	equal = !strcmp(s1, s2);
+
+	if (!equal) {
+		size_t len1 = strlen(s1);
+		size_t len2 = strlen(s2);
+
+		if (len1 && *(s1 + len1 - 1) == '/')
+			len1--;
+		if (len2 && *(s2 + len2 - 1) == '/')
+			len2--;
+		if (len1 != len2)
+			return 0;
+
+		equal = !strncmp(s1, s2, len1);
+	}
+
+	return equal;
+}
+
+
+#ifdef TEST_PROGRAM
+
+int main(int argc, char *argv[])
+{
+	uintmax_t size = 0;
+	char *hum, *hum2;
+
+	if (argc < 2) {
+		fprintf(stderr, "usage: %s <number>[suffix]\n",	argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	if (strtosize(argv[1], &size))
+		errx(EXIT_FAILURE, "invalid size '%s' value", argv[1]);
+
+	hum = size_to_human_string(SIZE_SUFFIX_1LETTER, size);
+	hum2 = size_to_human_string(SIZE_SUFFIX_3LETTER |
+				    SIZE_SUFFIX_SPACE, size);
+
+	printf("%25s : %20ju : %8s : %12s\n", argv[1], size, hum, hum2);
+	free(hum);
+	free(hum2);
+
+	return EXIT_SUCCESS;
+}
+#endif /* TEST_PROGRAM */
diff --git a/libblkid/lib/swapprober.c b/libblkid/lib/swapprober.c
new file mode 100644
index 0000000..5a4b112
--- /dev/null
+++ b/libblkid/lib/swapprober.c
@@ -0,0 +1,49 @@
+
+#include "c.h"
+#include "nls.h"
+
+#include "swapheader.h"
+#include "swapprober.h"
+
+blkid_probe get_swap_prober(const char *devname)
+{
+	blkid_probe pr;
+	int rc;
+	const char *version = NULL;
+	char *swap_filter[] = { "swap", NULL };
+
+	pr = blkid_new_probe_from_filename(devname);
+	if (!pr) {
+		warn(_("%s: unable to probe device"), devname);
+		return NULL;
+	}
+
+	blkid_probe_enable_superblocks(pr, TRUE);
+	blkid_probe_set_superblocks_flags(pr,
+			BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
+			BLKID_SUBLKS_VERSION);
+
+	blkid_probe_filter_superblocks_type(pr, BLKID_FLTR_ONLYIN, swap_filter);
+
+	rc = blkid_do_safeprobe(pr);
+	if (rc == -1)
+		warn(_("%s: unable to probe device"), devname);
+	else if (rc == -2)
+		warnx(_("%s: ambiguous probing result; use wipefs(8)"), devname);
+	else if (rc == 1)
+		warnx(_("%s: not a valid swap partition"), devname);
+
+	if (rc == 0) {
+		/* Only the SWAPSPACE2 is supported. */
+		if (blkid_probe_lookup_value(pr, "VERSION", &version, NULL) == 0
+		    && version
+		    && strcmp(version, stringify_value(SWAP_VERSION)))
+			warnx(_("%s: unsupported swap version '%s'"),
+						devname, version);
+		else
+			return pr;
+	}
+
+	blkid_free_probe(pr);
+	return NULL;
+}
diff --git a/libblkid/lib/sysfs.c b/libblkid/lib/sysfs.c
new file mode 100644
index 0000000..71cf1b4
--- /dev/null
+++ b/libblkid/lib/sysfs.c
@@ -0,0 +1,1075 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <ctype.h>
+#include <string.h>
+#include <libgen.h>
+#include <sys/sysmacros.h>
+
+#include "c.h"
+#include "at.h"
+#include "pathnames.h"
+#include "sysfs.h"
+#include "fileutils.h"
+#include "all-io.h"
+
+#define STRINGIFY(x) #x
+#define EXPAND(x) STRINGIFY(x)
+
+char *sysfs_devno_attribute_path(dev_t devno, char *buf,
+				 size_t bufsiz, const char *attr)
+{
+	int len;
+
+	if (attr)
+		len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d/%s",
+			major(devno), minor(devno), attr);
+	else
+		len = snprintf(buf, bufsiz, _PATH_SYS_DEVBLOCK "/%d:%d",
+			major(devno), minor(devno));
+
+	return (len < 0 || (size_t) len + 1 > bufsiz) ? NULL : buf;
+}
+
+int sysfs_devno_has_attribute(dev_t devno, const char *attr)
+{
+	char path[PATH_MAX];
+	struct stat info;
+
+	if (!sysfs_devno_attribute_path(devno, path, sizeof(path), attr))
+		return 0;
+	if (stat(path, &info) == 0)
+		return 1;
+	return 0;
+}
+
+char *sysfs_devno_path(dev_t devno, char *buf, size_t bufsiz)
+{
+	return sysfs_devno_attribute_path(devno, buf, bufsiz, NULL);
+}
+
+dev_t sysfs_devname_to_devno(const char *name, const char *parent)
+{
+	char buf[PATH_MAX], *path = NULL;
+	dev_t dev = 0;
+
+	if (strncmp("/dev/", name, 5) == 0) {
+		/*
+		 * Read from /dev
+		 */
+		struct stat st;
+
+		if (stat(name, &st) == 0)
+			dev = st.st_rdev;
+		else
+			name += 5;	/* unaccesible, or not node in /dev */
+	}
+
+	if (!dev && parent && strncmp("dm-", name, 3)) {
+		/*
+		 * Create path to /sys/block/<parent>/<name>/dev
+		 */
+		int len = snprintf(buf, sizeof(buf),
+				_PATH_SYS_BLOCK "/%s/%s/dev", parent, name);
+		if (len < 0 || (size_t) len + 1 > sizeof(buf))
+			return 0;
+		path = buf;
+
+	} else if (!dev) {
+		/*
+		 * Create path to /sys/block/<name>/dev
+		 */
+		int len = snprintf(buf, sizeof(buf),
+				_PATH_SYS_BLOCK "/%s/dev", name);
+		if (len < 0 || (size_t) len + 1 > sizeof(buf))
+			return 0;
+		path = buf;
+	}
+
+	if (path) {
+		/*
+		 * read devno from sysfs
+		 */
+		FILE *f;
+		int maj = 0, min = 0;
+
+		f = fopen(path, "r" UL_CLOEXECSTR);
+		if (!f)
+			return 0;
+
+		if (fscanf(f, "%d:%d", &maj, &min) == 2)
+			dev = makedev(maj, min);
+		fclose(f);
+	}
+	return dev;
+}
+
+/*
+ * Returns devname (e.g. "/dev/sda1") for the given devno.
+ *
+ * Note that the @buf has to be large enough to store /sys/dev/block/<maj:min>
+ * symlinks.
+ *
+ * Please, use more robust blkid_devno_to_devname() in your applications.
+ */
+char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz)
+{
+	struct sysfs_cxt cxt;
+	char *name;
+	size_t sz;
+	struct stat st;
+
+	if (sysfs_init(&cxt, devno, NULL))
+		return NULL;
+
+	name = sysfs_get_devname(&cxt, buf, bufsiz);
+	sysfs_deinit(&cxt);
+
+	if (!name)
+		return NULL;
+
+	sz = strlen(name);
+
+	if (sz + sizeof("/dev/") > bufsiz)
+		return NULL;
+
+	/* create the final "/dev/<name>" string */
+	memmove(buf + 5, name, sz + 1);
+	memcpy(buf, "/dev/", 5);
+
+	if (!stat(buf, &st) && S_ISBLK(st.st_mode) && st.st_rdev == devno)
+		return buf;
+
+	return NULL;
+}
+
+int sysfs_init(struct sysfs_cxt *cxt, dev_t devno, struct sysfs_cxt *parent)
+{
+	char path[PATH_MAX];
+	int fd, rc;
+
+	memset(cxt, 0, sizeof(*cxt));
+	cxt->dir_fd = -1;
+
+	if (!sysfs_devno_path(devno, path, sizeof(path)))
+		goto err;
+
+	fd = open(path, O_RDONLY|O_CLOEXEC);
+	if (fd < 0)
+		goto err;
+	cxt->dir_fd = fd;
+
+	cxt->dir_path = strdup(path);
+	if (!cxt->dir_path)
+		goto err;
+	cxt->devno = devno;
+	cxt->parent = parent;
+	return 0;
+err:
+	rc = errno > 0 ? -errno : -1;
+	sysfs_deinit(cxt);
+	return rc;
+}
+
+void sysfs_deinit(struct sysfs_cxt *cxt)
+{
+	if (!cxt)
+		return;
+
+	if (cxt->dir_fd >= 0)
+	       close(cxt->dir_fd);
+	free(cxt->dir_path);
+
+	memset(cxt, 0, sizeof(*cxt));
+
+	cxt->dir_fd = -1;
+}
+
+int sysfs_stat(struct sysfs_cxt *cxt, const char *attr, struct stat *st)
+{
+	int rc = fstat_at(cxt->dir_fd, cxt->dir_path, attr, st, 0);
+
+	if (rc != 0 && errno == ENOENT &&
+	    strncmp(attr, "queue/", 6) == 0 && cxt->parent) {
+
+		/* Exception for "queue/<attr>". These attributes are available
+		 * for parental devices only
+		 */
+		return fstat_at(cxt->parent->dir_fd,
+				cxt->parent->dir_path, attr, st, 0);
+	}
+	return rc;
+}
+
+int sysfs_has_attribute(struct sysfs_cxt *cxt, const char *attr)
+{
+	struct stat st;
+
+	return sysfs_stat(cxt, attr, &st) == 0;
+}
+
+static int sysfs_open(struct sysfs_cxt *cxt, const char *attr, int flags)
+{
+	int fd = open_at(cxt->dir_fd, cxt->dir_path, attr, flags);
+
+	if (fd == -1 && errno == ENOENT &&
+	    strncmp(attr, "queue/", 6) == 0 && cxt->parent) {
+
+		/* Exception for "queue/<attr>". These attributes are available
+		 * for parental devices only
+		 */
+		fd = open_at(cxt->parent->dir_fd, cxt->dir_path, attr, flags);
+	}
+	return fd;
+}
+
+ssize_t sysfs_readlink(struct sysfs_cxt *cxt, const char *attr,
+		   char *buf, size_t bufsiz)
+{
+	if (!cxt->dir_path)
+		return -1;
+
+	if (attr)
+		return readlink_at(cxt->dir_fd, cxt->dir_path, attr, buf, bufsiz);
+
+	/* read /sys/dev/block/<maj:min> link */
+	return readlink(cxt->dir_path, buf, bufsiz);
+}
+
+DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr)
+{
+	DIR *dir;
+	int fd = -1;
+
+	if (attr)
+		fd = sysfs_open(cxt, attr, O_RDONLY|O_CLOEXEC);
+
+	else if (cxt->dir_fd >= 0)
+		/* request to open root of device in sysfs (/sys/block/<dev>)
+		 * -- we cannot use cxt->sysfs_fd directly, because closedir()
+		 * will close this our persistent file descriptor.
+		 */
+		fd = dup(cxt->dir_fd);
+
+	if (fd < 0)
+		return NULL;
+
+	dir = fdopendir(fd);
+	if (!dir) {
+		close(fd);
+		return NULL;
+	}
+	if (!attr)
+		 rewinddir(dir);
+	return dir;
+}
+
+
+static FILE *sysfs_fopen(struct sysfs_cxt *cxt, const char *attr)
+{
+	int fd = sysfs_open(cxt, attr, O_RDONLY|O_CLOEXEC);
+
+	return fd < 0 ? NULL : fdopen(fd, "r" UL_CLOEXECSTR);
+}
+
+
+static struct dirent *xreaddir(DIR *dp)
+{
+	struct dirent *d;
+
+	while ((d = readdir(dp))) {
+		if (!strcmp(d->d_name, ".") ||
+		    !strcmp(d->d_name, ".."))
+			continue;
+
+		/* blacklist here? */
+		break;
+	}
+	return d;
+}
+
+int sysfs_is_partition_dirent(DIR *dir, struct dirent *d, const char *parent_name)
+{
+	char path[256];
+
+#ifdef _DIRENT_HAVE_D_TYPE
+	if (d->d_type != DT_DIR &&
+	    d->d_type != DT_LNK &&
+	    d->d_type != DT_UNKNOWN)
+		return 0;
+#endif
+	if (parent_name) {
+		const char *p = parent_name;
+		size_t len;
+
+		/* /dev/sda --> "sda" */
+		if (*parent_name == '/') {
+			p = strrchr(parent_name, '/');
+			if (!p)
+				return 0;
+			p++;
+		}
+
+		len = strlen(p);
+		if (strlen(d->d_name) <= len)
+			return 0;
+
+		/* partitions subdir name is
+		 *	"<parent>[:digit:]" or "<parent>p[:digit:]"
+		 */
+		return strncmp(p, d->d_name, len) == 0 &&
+		       ((*(d->d_name + len) == 'p' && isdigit(*(d->d_name + len + 1)))
+			|| isdigit(*(d->d_name + len)));
+	}
+
+	/* Cannot use /partition file, not supported on old sysfs */
+	snprintf(path, sizeof(path), "%s/start", d->d_name);
+
+	return faccessat(dirfd(dir), path, R_OK, 0) == 0;
+}
+
+/*
+ * Converts @partno (partition number) to devno of the partition.
+ * The @cxt handles wholedisk device.
+ *
+ * Note that this code does not expect any special format of the
+ * partitions devnames.
+ */
+dev_t sysfs_partno_to_devno(struct sysfs_cxt *cxt, int partno)
+{
+	DIR *dir;
+	struct dirent *d;
+	char path[256];
+	dev_t devno = 0;
+
+	dir = sysfs_opendir(cxt, NULL);
+	if (!dir)
+		return 0;
+
+	while ((d = xreaddir(dir))) {
+		int n, maj, min;
+
+		if (!sysfs_is_partition_dirent(dir, d, NULL))
+			continue;
+
+		snprintf(path, sizeof(path), "%s/partition", d->d_name);
+		if (sysfs_read_int(cxt, path, &n))
+			continue;
+
+		if (n == partno) {
+			snprintf(path, sizeof(path), "%s/dev", d->d_name);
+			if (sysfs_scanf(cxt, path, "%d:%d", &maj, &min) == 2)
+				devno = makedev(maj, min);
+			break;
+		}
+	}
+
+	closedir(dir);
+	return devno;
+}
+
+
+int sysfs_scanf(struct sysfs_cxt *cxt,  const char *attr, const char *fmt, ...)
+{
+	FILE *f = sysfs_fopen(cxt, attr);
+	va_list ap;
+	int rc;
+
+	if (!f)
+		return -EINVAL;
+	va_start(ap, fmt);
+	rc = vfscanf(f, fmt, ap);
+	va_end(ap);
+
+	fclose(f);
+	return rc;
+}
+
+
+int sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr, int64_t *res)
+{
+	int64_t x = 0;
+
+	if (sysfs_scanf(cxt, attr, "%"SCNd64, &x) == 1) {
+		if (res)
+			*res = x;
+		return 0;
+	}
+	return -1;
+}
+
+int sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t *res)
+{
+	uint64_t x = 0;
+
+	if (sysfs_scanf(cxt, attr, "%"SCNu64, &x) == 1) {
+		if (res)
+			*res = x;
+		return 0;
+	}
+	return -1;
+}
+
+int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res)
+{
+	int x = 0;
+
+	if (sysfs_scanf(cxt, attr, "%d", &x) == 1) {
+		if (res)
+			*res = x;
+		return 0;
+	}
+	return -1;
+}
+
+int sysfs_write_string(struct sysfs_cxt *cxt, const char *attr, const char *str)
+{
+	int fd = sysfs_open(cxt, attr, O_WRONLY|O_CLOEXEC);
+	int rc, errsv;
+
+	if (fd < 0)
+		return -errno;
+	rc = write_all(fd, str, strlen(str));
+
+	errsv = errno;
+	close(fd);
+	errno = errsv;
+	return rc;
+}
+
+int sysfs_write_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t num)
+{
+	char buf[sizeof(STRINGIFY(ULLONG_MAX))];
+	int fd, rc = 0, len, errsv;
+
+	fd = sysfs_open(cxt, attr, O_WRONLY|O_CLOEXEC);
+	if (fd < 0)
+		return -errno;
+
+	len = snprintf(buf, sizeof(buf), "%ju", num);
+	if (len < 0 || (size_t) len + 1 > sizeof(buf))
+		rc = -errno;
+	else
+		rc = write_all(fd, buf, len);
+
+	errsv = errno;
+	close(fd);
+	errno = errsv;
+	return rc;
+}
+
+char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr)
+{
+	char buf[1024];
+	return sysfs_scanf(cxt, attr, "%1023[^\n]", buf) == 1 ?
+						strdup(buf) : NULL;
+}
+
+int sysfs_count_dirents(struct sysfs_cxt *cxt, const char *attr)
+{
+	DIR *dir;
+	int r = 0;
+
+	if (!(dir = sysfs_opendir(cxt, attr)))
+		return 0;
+
+	while (xreaddir(dir)) r++;
+
+	closedir(dir);
+	return r;
+}
+
+int sysfs_count_partitions(struct sysfs_cxt *cxt, const char *devname)
+{
+	DIR *dir;
+	struct dirent *d;
+	int r = 0;
+
+	if (!(dir = sysfs_opendir(cxt, NULL)))
+		return 0;
+
+	while ((d = xreaddir(dir))) {
+		if (sysfs_is_partition_dirent(dir, d, devname))
+			r++;
+	}
+
+	closedir(dir);
+	return r;
+}
+
+/*
+ * Returns slave name if there is only one slave, otherwise returns NULL.
+ * The result should be deallocated by free().
+ */
+char *sysfs_get_slave(struct sysfs_cxt *cxt)
+{
+	DIR *dir;
+	struct dirent *d;
+	char *name = NULL;
+
+	if (!(dir = sysfs_opendir(cxt, "slaves")))
+		return NULL;
+
+	while ((d = xreaddir(dir))) {
+		if (name)
+			goto err;	/* more slaves */
+
+		name = strdup(d->d_name);
+	}
+
+	closedir(dir);
+	return name;
+err:
+	free(name);
+	closedir(dir);
+	return NULL;
+}
+
+/*
+ * Note that the @buf has to be large enough to store /sys/dev/block/<maj:min>
+ * symlinks.
+ */
+char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz)
+{
+	char *name = NULL;
+	ssize_t sz;
+
+	sz = sysfs_readlink(cxt, NULL, buf, bufsiz - 1);
+	if (sz < 0)
+		return NULL;
+
+	buf[sz] = '\0';
+	name = strrchr(buf, '/');
+	if (!name)
+		return NULL;
+
+	name++;
+	sz = strlen(name);
+
+	memmove(buf, name, sz + 1);
+	return buf;
+}
+
+#define SUBSYSTEM_LINKNAME	"/subsystem"
+
+/*
+ * For example:
+ *
+ * chain: /sys/dev/block/../../devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/ \
+ *                           1-1.2:1.0/host65/target65:0:0/65:0:0:0/block/sdb
+ *
+ * The function check if <chain>/subsystem symlink exists, if yes then returns
+ * basename of the readlink result, and remove the last subdirectory from the
+ * <chain> path.
+ */
+static char *get_subsystem(char *chain, char *buf, size_t bufsz)
+{
+	size_t len;
+	char *p;
+
+	if (!chain || !*chain)
+		return NULL;
+
+	len = strlen(chain);
+	if (len + sizeof(SUBSYSTEM_LINKNAME) > PATH_MAX)
+		return NULL;
+
+	do {
+		ssize_t sz;
+
+		/* append "/subsystem" to the path */
+		memcpy(chain + len, SUBSYSTEM_LINKNAME, sizeof(SUBSYSTEM_LINKNAME));
+
+		/* try if subsystem symlink exists */
+		sz = readlink(chain, buf, bufsz - 1);
+
+		/* remove last subsystem from chain */
+		chain[len] = '\0';
+		p = strrchr(chain, '/');
+		if (p) {
+			*p = '\0';
+			len = p - chain;
+		}
+
+		if (sz > 0) {
+			/* we found symlink to subsystem, return basename */
+			buf[sz] = '\0';
+			return basename(buf);
+		}
+
+	} while (p);
+
+	return NULL;
+}
+
+/*
+ * Returns complete path to the device, the patch contains all all sybsystems
+ * used for the device.
+ */
+char *sysfs_get_devchain(struct sysfs_cxt *cxt, char *buf, size_t bufsz)
+{
+	/* read /sys/dev/block/<maj>:<min> symlink */
+	size_t sz = sysfs_readlink(cxt, NULL, buf, bufsz);
+	if (sz <= 0 || sz + sizeof(_PATH_SYS_DEVBLOCK "/") > bufsz)
+		return NULL;
+
+	buf[sz++] = '\0';
+
+	/* create absolute patch from the link */
+	memmove(buf + sizeof(_PATH_SYS_DEVBLOCK "/") - 1, buf, sz);
+	memcpy(buf, _PATH_SYS_DEVBLOCK "/", sizeof(_PATH_SYS_DEVBLOCK "/") - 1);
+
+	return buf;
+}
+
+/*
+ * The @subsys returns the next subsystem in the chain. Function modifies
+ * @devchain string.
+ *
+ * Returns: 0 in success, <0 on error, 1 on end of chain
+ */
+int sysfs_next_subsystem(struct sysfs_cxt *cxt __attribute__((unused)),
+			 char *devchain, char **subsys)
+{
+	char subbuf[PATH_MAX];
+	char *sub;
+
+	if (!subsys || !devchain)
+		return -EINVAL;
+
+	while ((sub = get_subsystem(devchain, subbuf, sizeof(subbuf)))) {
+		*subsys = strdup(sub);
+		if (!*subsys)
+			return -ENOMEM;
+		return 0;
+	}
+
+	return 1;
+}
+
+
+static int is_hotpluggable_subsystem(const char *name)
+{
+	static const char * const hotplug_subsystems[] = {
+		"usb",
+		"ieee1394",
+		"pcmcia",
+		"mmc",
+		"ccw"
+	};
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(hotplug_subsystems); i++)
+		if (strcmp(name, hotplug_subsystems[i]) == 0)
+			return 1;
+
+	return 0;
+}
+
+int sysfs_is_hotpluggable(struct sysfs_cxt *cxt)
+{
+	char buf[PATH_MAX], *chain, *sub;
+	int rc = 0;
+
+
+	/* check /sys/dev/block/<maj>:<min>/removable attribute */
+	if (sysfs_read_int(cxt, "removable", &rc) == 0 && rc == 1)
+		return 1;
+
+	chain = sysfs_get_devchain(cxt, buf, sizeof(buf));
+
+	while (chain && sysfs_next_subsystem(cxt, chain, &sub) == 0) {
+		rc = is_hotpluggable_subsystem(sub);
+		if (rc) {
+			free(sub);
+			break;
+		}
+		free(sub);
+	}
+
+	return rc;
+}
+
+static int get_dm_wholedisk(struct sysfs_cxt *cxt, char *diskname,
+                size_t len, dev_t *diskdevno)
+{
+    int rc = 0;
+    char *name;
+
+    /* Note, sysfs_get_slave() returns the first slave only,
+     * if there is more slaves, then return NULL
+     */
+    name = sysfs_get_slave(cxt);
+    if (!name)
+        return -1;
+
+    if (diskname && len) {
+        strncpy(diskname, name, len);
+        diskname[len - 1] = '\0';
+    }
+
+    if (diskdevno) {
+        *diskdevno = sysfs_devname_to_devno(name, NULL);
+        if (!*diskdevno)
+            rc = -1;
+    }
+
+    free(name);
+    return rc;
+}
+
+/*
+ * Returns by @diskdevno whole disk device devno and (optionaly) by
+ * @diskname the whole disk device name.
+ */
+int sysfs_devno_to_wholedisk(dev_t dev, char *diskname,
+            size_t len, dev_t *diskdevno)
+{
+    struct sysfs_cxt cxt;
+    int is_part = 0;
+
+    if (!dev || sysfs_init(&cxt, dev, NULL) != 0)
+        return -1;
+
+    is_part = sysfs_has_attribute(&cxt, "partition");
+    if (!is_part) {
+        /*
+         * Extra case for partitions mapped by device-mapper.
+         *
+         * All regualar partitions (added by BLKPG ioctl or kernel PT
+         * parser) have the /sys/.../partition file. The partitions
+         * mapped by DM don't have such file, but they have "part"
+         * prefix in DM UUID.
+         */
+        char *uuid = sysfs_strdup(&cxt, "dm/uuid");
+        char *tmp = uuid;
+        char *prefix = uuid ? strsep(&tmp, "-") : NULL;
+
+        if (prefix && strncasecmp(prefix, "part", 4) == 0)
+            is_part = 1;
+        free(uuid);
+
+        if (is_part &&
+            get_dm_wholedisk(&cxt, diskname, len, diskdevno) == 0)
+            /*
+             * partitioned device, mapped by DM
+             */
+            goto done;
+
+        is_part = 0;
+    }
+
+    if (!is_part) {
+        /*
+         * unpartitioned device
+         */
+        if (diskname && len) {
+            if (!sysfs_get_devname(&cxt, diskname, len))
+                goto err;
+        }
+        if (diskdevno)
+            *diskdevno = dev;
+
+    } else {
+        /*
+         * partitioned device
+         *  - readlink /sys/dev/block/8:1   = ../../block/sda/sda1
+         *  - dirname  ../../block/sda/sda1 = ../../block/sda
+         *  - basename ../../block/sda      = sda
+         */
+        char linkpath[PATH_MAX];
+        char *name;
+        int linklen;
+
+        linklen = sysfs_readlink(&cxt, NULL,
+                linkpath, sizeof(linkpath) - 1);
+        if (linklen < 0)
+            goto err;
+        linkpath[linklen] = '\0';
+
+        stripoff_last_component(linkpath);      /* dirname */
+        name = stripoff_last_component(linkpath);   /* basename */
+        if (!name)
+            goto err;
+
+        if (diskname && len) {
+            strncpy(diskname, name, len);
+            diskname[len - 1] = '\0';
+        }
+
+        if (diskdevno) {
+            *diskdevno = sysfs_devname_to_devno(name, NULL);
+            if (!*diskdevno)
+                goto err;
+        }
+    }
+
+done:
+    sysfs_deinit(&cxt);
+    return 0;
+err:
+    sysfs_deinit(&cxt);
+    return -1;
+}
+
+/*
+ * Returns 1 if the device is private LVM device.
+ */
+int sysfs_devno_is_lvm_private(dev_t devno)
+{
+	struct sysfs_cxt cxt = UL_SYSFSCXT_EMPTY;
+	char *uuid = NULL;
+	int rc = 0;
+
+	if (sysfs_init(&cxt, devno, NULL) != 0)
+		return 0;
+
+	uuid = sysfs_strdup(&cxt, "dm/uuid");
+
+	/* Private LVM devices use "LVM-<uuid>-<name>" uuid format (important
+	 * is the "LVM" prefix and "-<name>" postfix).
+	 */
+	if (uuid && strncmp(uuid, "LVM-", 4) == 0) {
+		char *p = strrchr(uuid + 4, '-');
+
+		if (p && *(p + 1))
+			rc = 1;
+	}
+
+	sysfs_deinit(&cxt);
+	free(uuid);
+	return rc;
+}
+
+/*
+ * Return 0 or 1, or < 0 in case of error
+ */
+int sysfs_devno_is_wholedisk(dev_t devno)
+{
+	dev_t disk;
+
+	if (sysfs_devno_to_wholedisk(devno, NULL, 0, &disk) != 0)
+		return -1;
+
+	return devno == disk;
+}
+
+
+int sysfs_scsi_get_hctl(struct sysfs_cxt *cxt, int *h, int *c, int *t, int *l)
+{
+	char buf[PATH_MAX], *hctl;
+	ssize_t len;
+
+	if (!cxt)
+		return -EINVAL;
+	if (cxt->has_hctl)
+		goto done;
+
+	len = sysfs_readlink(cxt, "device", buf, sizeof(buf) - 1);
+	if (len < 0)
+		return len;
+
+	buf[len] = '\0';
+	hctl = strrchr(buf, '/');
+	if (!hctl)
+		return -1;
+	hctl++;
+
+	if (sscanf(hctl, "%u:%u:%u:%u", &cxt->scsi_host, &cxt->scsi_channel,
+				&cxt->scsi_target, &cxt->scsi_lun) != 4)
+		return -1;
+
+	cxt->has_hctl = 1;
+done:
+	if (h)
+		*h = cxt->scsi_host;
+	if (c)
+		*c = cxt->scsi_channel;
+	if (t)
+		*t = cxt->scsi_target;
+	if (l)
+		*l = cxt->scsi_lun;
+	return 0;
+}
+
+
+static char *sysfs_scsi_host_attribute_path(struct sysfs_cxt *cxt,
+		const char *type, char *buf, size_t bufsz, const char *attr)
+{
+	int len;
+	int host;
+
+	if (sysfs_scsi_get_hctl(cxt, &host, NULL, NULL, NULL))
+		return NULL;
+
+	if (attr)
+		len = snprintf(buf, bufsz, _PATH_SYS_CLASS "/%s_host/host%d/%s",
+				type, host, attr);
+	else
+		len = snprintf(buf, bufsz, _PATH_SYS_CLASS "/%s_host/host%d",
+				type, host);
+
+	return (len < 0 || (size_t) len + 1 > bufsz) ? NULL : buf;
+}
+
+char *sysfs_scsi_host_strdup_attribute(struct sysfs_cxt *cxt,
+		const char *type, const char *attr)
+{
+	char buf[1024];
+	int rc;
+	FILE *f;
+
+	if (!attr || !type ||
+	    !sysfs_scsi_host_attribute_path(cxt, type, buf, sizeof(buf), attr))
+		return NULL;
+
+	if (!(f = fopen(buf, "r" UL_CLOEXECSTR)))
+                return NULL;
+
+	rc = fscanf(f, "%1023[^\n]", buf);
+	fclose(f);
+
+	return rc == 1 ? strdup(buf) : NULL;
+}
+
+int sysfs_scsi_host_is(struct sysfs_cxt *cxt, const char *type)
+{
+	char buf[PATH_MAX];
+	struct stat st;
+
+	if (!type || !sysfs_scsi_host_attribute_path(cxt, type,
+				buf, sizeof(buf), NULL))
+		return 0;
+
+	return stat(buf, &st) == 0 && S_ISDIR(st.st_mode);
+}
+
+static char *sysfs_scsi_attribute_path(struct sysfs_cxt *cxt,
+		char *buf, size_t bufsz, const char *attr)
+{
+	int len, h, c, t, l;
+
+	if (sysfs_scsi_get_hctl(cxt, &h, &c, &t, &l) != 0)
+		return NULL;
+
+	if (attr)
+		len = snprintf(buf, bufsz, _PATH_SYS_SCSI "/devices/%d:%d:%d:%d/%s",
+				h,c,t,l, attr);
+	else
+		len = snprintf(buf, bufsz, _PATH_SYS_SCSI "/devices/%d:%d:%d:%d",
+				h,c,t,l);
+	return (len < 0 || (size_t) len + 1 > bufsz) ? NULL : buf;
+}
+
+int sysfs_scsi_has_attribute(struct sysfs_cxt *cxt, const char *attr)
+{
+	char path[PATH_MAX];
+	struct stat st;
+
+	if (!sysfs_scsi_attribute_path(cxt, path, sizeof(path), attr))
+		return 0;
+
+	return stat(path, &st) == 0;
+}
+
+int sysfs_scsi_path_contains(struct sysfs_cxt *cxt, const char *pattern)
+{
+	char path[PATH_MAX], linkc[PATH_MAX];
+	struct stat st;
+	ssize_t len;
+
+	if (!sysfs_scsi_attribute_path(cxt, path, sizeof(path), NULL))
+		return 0;
+
+	if (stat(path, &st) != 0)
+		return 0;
+
+	len = readlink(path, linkc, sizeof(linkc) - 1);
+	if (len < 0)
+		return 0;
+
+	linkc[len] = '\0';
+	return strstr(linkc, pattern) != NULL;
+}
+
+#ifdef TEST_PROGRAM_SYSFS
+#include <errno.h>
+#include <err.h>
+#include <stdlib.h>
+
+int main(int argc, char *argv[])
+{
+	struct sysfs_cxt cxt = UL_SYSFSCXT_EMPTY;
+	char *devname;
+	dev_t devno;
+	char path[PATH_MAX], *sub, *chain;
+	int i, is_part;
+	uint64_t u64;
+	ssize_t len;
+
+	if (argc != 2)
+		errx(EXIT_FAILURE, "usage: %s <devname>", argv[0]);
+
+	devname = argv[1];
+	devno = sysfs_devname_to_devno(devname, NULL);
+
+	if (!devno)
+		err(EXIT_FAILURE, "failed to read devno");
+
+	is_part = sysfs_devno_has_attribute(devno, "partition");
+
+	printf("NAME: %s\n", devname);
+	printf("DEVNO: %u (%d:%d)\n", (unsigned int) devno, major(devno), minor(devno));
+	printf("DEVNOPATH: %s\n", sysfs_devno_path(devno, path, sizeof(path)));
+	printf("DEVPATH: %s\n", sysfs_devno_to_devpath(devno, path, sizeof(path)));
+	printf("PARTITION: %s\n", is_part ? "YES" : "NOT");
+
+	if (sysfs_init(&cxt, devno, NULL))
+		return EXIT_FAILURE;
+
+	len = sysfs_readlink(&cxt, NULL, path, sizeof(path) - 1);
+	if (len > 0) {
+		path[len] = '\0';
+		printf("DEVNOLINK: %s\n", path);
+	}
+
+	if (!is_part) {
+		printf("First 5 partitions:\n");
+		for (i = 1; i <= 5; i++) {
+			dev_t dev = sysfs_partno_to_devno(&cxt, i);
+			if (dev)
+				printf("\t#%d %d:%d\n", i, major(dev), minor(dev));
+		}
+	}
+
+	printf("SLAVES: %d\n", sysfs_count_dirents(&cxt, "slaves"));
+
+	if (sysfs_read_u64(&cxt, "size", &u64))
+		printf("read SIZE failed\n");
+	else
+		printf("SIZE: %jd\n", u64);
+
+	if (sysfs_read_int(&cxt, "queue/hw_sector_size", &i))
+		printf("read SECTOR failed\n");
+	else
+		printf("SECTOR: %d\n", i);
+
+	printf("DEVNAME: %s\n", sysfs_get_devname(&cxt, path, sizeof(path)));
+	printf("HOTPLUG: %s\n", sysfs_is_hotpluggable(&cxt) ? "yes" : "no");
+
+	chain = sysfs_get_devchain(&cxt, path, sizeof(path));
+	printf("SUBSUSTEMS:\n");
+
+	while (chain && sysfs_next_subsystem(&cxt, chain, &sub) == 0) {
+		printf("\t%s\n", sub);
+		free(sub);
+	}
+
+
+	sysfs_deinit(&cxt);
+	return EXIT_SUCCESS;
+}
+#endif
diff --git a/libblkid/lib/terminal-colors.d.5 b/libblkid/lib/terminal-colors.d.5
new file mode 100644
index 0000000..66ecf2c
--- /dev/null
+++ b/libblkid/lib/terminal-colors.d.5
@@ -0,0 +1,190 @@
+.\" terminal-colors.d.5 --
+.\" Copyright 2014 Ondrej Oprala <ooprala@redhat.com>
+.\" Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+.\" Copyright 2014 Red Hat, Inc.
+.\" May be distributed under the GNU General Public License
+.TH "TERMINAL_COLORS.D" "5" "January 2014" "util-linux" "terminal-colors.d"
+.SH "NAME"
+terminal-colors.d \- Configure output colorization for various utilities
+.SH "SYNOPSIS"
+/etc/terminal-colors\&.d/[[\fIname\fR][@\fIterm\fR]\&.][\fItype\fR]
+.SH "DESCRIPTION"
+Files in this directory determine the default behavior for utilities
+when coloring output.
+
+The
+.I name
+is a utility name.  The name is optional and when none is specified then the
+file is used for all unspecified utilities.
+
+The
+.I term
+is a terminal identifier (the TERM environment variable).
+The terminal identifier is optional and when none is specified then the file
+is used for all unspecified terminals.
+
+The
+.I type
+is a file type.  Supported file types are:
+.TP
+.B disable
+Turns off output colorization for all compatible utilities.
+.TP
+.B enable
+Turns on output colorization; any matching
+.B disable
+files are ignored.
+.TP
+.B scheme
+Specifies colors used for output.  The file format may be specific to the utility,
+the default format is described below.
+.PP
+If there are more files that match for a utility, then the file with the more
+specific filename wins.  For example, the filename "@xterm.scheme" has less
+priority than "dmesg@xterm.scheme".  The lowest priority are those files without a
+utility name and terminal identifier (e.g. "disable").
+
+The user-specific
+.I $XDG_CONFIG_HOME/terminal-colors.d
+or
+.I $HOME/.config/terminal-colors.d
+overrides the global setting.
+
+.SH EXAMPLES
+Disable colors for all compatible utilities:
+.RS
+.br
+.B "touch /etc/terminal-colors.d/disable"
+.br
+.RE
+
+Disable colors for all compatible utils on a vt100 terminal:
+.RS
+.br
+.B "touch /etc/terminal-colors.d/@vt100.disable"
+.br
+.RE
+
+Disable colors for all compatible utils except dmesg(1):
+.RS
+.br
+.B "touch /etc/terminal-colors.d/disable"
+.sp
+.B "touch /etc/terminal-colors.d/dmesg.enable"
+.br
+.RE
+
+.SH DEFAULT SCHEME FILES FORMAT
+The following statement is recognized:
+
+.RS
+.br
+.B "name color-sequence"
+.br
+.RE
+
+The
+.B name
+is a logical name of color sequence (for example "error").  The names are
+specific to the utilities.  For more details always see the COLORS section
+in the man page for the utility.
+
+The
+.B color-sequence
+is a color name, ASCII color sequences or escape sequences.
+
+.SS Color names
+black, blue, brown, cyan, darkgray, gray, green, lightblue, lightcyan
+lightgray, lightgreen, lightmagenta, lightred, magenta, red and yellow
+.SS ANSI color sequences
+The color sequences are composed of sequences of numbers
+separated by semicolons.  The most common codes are:
+.sp
+.RS
+.TS
+l l.
+ 0	to restore default color
+ 1	for brighter colors
+ 4	for underlined text
+ 5	for flashing text
+30	for black foreground
+31	for red foreground
+32	for green foreground
+33	for yellow (or brown) foreground
+34	for blue foreground
+35	for purple foreground
+36	for cyan foreground
+37	for white (or gray) foreground
+40	for black background
+41	for red background
+42	for green background
+43	for yellow (or brown) background
+44	for blue background
+45	for purple background
+46	for cyan background
+47	for white (or gray) background
+.TE
+.RE
+.SS Escape sequences
+To specify control or blank characters in the color sequences,
+C-style \e-escaped notation can be used:
+.sp
+.RS
+.TS
+lb l.
+\ea	Bell (ASCII 7)
+\eb	Backspace (ASCII 8)
+\ee	Escape (ASCII 27)
+\ef	Form feed (ASCII 12)
+\en	Newline (ASCII 10)
+\er	Carriage Return (ASCII 13)
+\et	Tab (ASCII 9)
+\ev	Vertical Tab (ASCII 11)
+\e?	Delete (ASCII 127)
+\e_	Space
+\e\e	Backslash (\e)
+\e^	Caret (^)
+\e#	Hash mark (#)
+.TE
+.RE
+.sp
+Please note that escapes are necessary to enter a space, backslash,
+caret, or any control character anywhere in the string, as well as a
+hash mark as the first character.
+
+For example, to use a red background for alert messages in the output of
+.BR dmesg (1),
+use:
+
+.RS
+.br
+.B "echo 'alert 37;41' >> /etc/terminal-colors.d/dmesg.scheme"
+.br
+.RE
+
+.SS Comments
+Lines where the first non-blank character is a # (hash) are ignored.
+Any other use of the hash character is not interpreted as introducing
+a comment.
+
+.SH FILES
+.B $XDG_CONFIG_HOME/terminal-colors.d
+.br
+.B $HOME/.config/terminal-colors.d
+.br
+.B /etc/terminal-colors.d
+
+.SH ENVIRONMENT
+.IP TERMINAL_COLORS_DEBUG=all
+enables debug output.
+
+.SH COMPATIBILITY
+The terminal-colors.d functionality is currently supported by all util-linux
+utilities which provides colorized output.  For more details always see the
+COLORS section in the man page for the utility.
+
+.SH AVAILABILITY
+terminal-colors.d is part of the util-linux package and is available from
+.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/libblkid/lib/timeutils.c b/libblkid/lib/timeutils.c
new file mode 100644
index 0000000..b811041
--- /dev/null
+++ b/libblkid/lib/timeutils.c
@@ -0,0 +1,342 @@
+/***
+  First set of functions in this file are part of systemd, and were
+  copied to util-linux at August 2013.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with util-linux; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/sysinfo.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "timeutils.h"
+
+#define WHITESPACE " \t\n\r"
+
+#define streq(a,b) (strcmp((a),(b)) == 0)
+
+static int parse_sec(const char *t, usec_t *usec)
+{
+	 static const struct {
+		const char *suffix;
+		usec_t usec;
+	 } table[] = {
+		{ "seconds",	USEC_PER_SEC },
+		{ "second",	USEC_PER_SEC },
+		{ "sec",	USEC_PER_SEC },
+		{ "s",		USEC_PER_SEC },
+		{ "minutes",	USEC_PER_MINUTE },
+		{ "minute",	USEC_PER_MINUTE },
+		{ "min",	USEC_PER_MINUTE },
+		{ "months",	USEC_PER_MONTH },
+		{ "month",	USEC_PER_MONTH },
+		{ "msec",	USEC_PER_MSEC },
+		{ "ms",		USEC_PER_MSEC },
+		{ "m",		USEC_PER_MINUTE },
+		{ "hours",	USEC_PER_HOUR },
+		{ "hour",	USEC_PER_HOUR },
+		{ "hr",		USEC_PER_HOUR },
+		{ "h",		USEC_PER_HOUR },
+		{ "days",	USEC_PER_DAY },
+		{ "day",	USEC_PER_DAY },
+		{ "d",		USEC_PER_DAY },
+		{ "weeks",	USEC_PER_WEEK },
+		{ "week",	USEC_PER_WEEK },
+		{ "w",		USEC_PER_WEEK },
+		{ "years",	USEC_PER_YEAR },
+		{ "year",	USEC_PER_YEAR },
+		{ "y",		USEC_PER_YEAR },
+		{ "usec",	1ULL },
+		{ "us",		1ULL },
+		{ "",		USEC_PER_SEC },	/* default is sec */
+	 };
+
+	const char *p;
+	usec_t r = 0;
+	int something = FALSE;
+
+	assert(t);
+	assert(usec);
+
+	p = t;
+	for (;;) {
+		long long l, z = 0;
+		char *e;
+		unsigned i, n = 0;
+
+		p += strspn(p, WHITESPACE);
+
+		if (*p == 0) {
+			if (!something)
+				return -EINVAL;
+
+			break;
+		}
+
+		errno = 0;
+		l = strtoll(p, &e, 10);
+
+		if (errno > 0)
+			return -errno;
+
+		if (l < 0)
+			return -ERANGE;
+
+		if (*e == '.') {
+			char *b = e + 1;
+
+			errno = 0;
+			z = strtoll(b, &e, 10);
+			if (errno > 0)
+				return -errno;
+
+			if (z < 0)
+				return -ERANGE;
+
+			if (e == b)
+				return -EINVAL;
+
+			n = e - b;
+
+		} else if (e == p)
+			return -EINVAL;
+
+		e += strspn(e, WHITESPACE);
+
+		for (i = 0; i < ARRAY_SIZE(table); i++)
+			if (startswith(e, table[i].suffix)) {
+				usec_t k = (usec_t) z * table[i].usec;
+
+				for (; n > 0; n--)
+					k /= 10;
+
+				r += (usec_t) l *table[i].usec + k;
+				p = e + strlen(table[i].suffix);
+
+				something = TRUE;
+				break;
+			}
+
+		if (i >= ARRAY_SIZE(table))
+			return -EINVAL;
+
+	}
+
+	*usec = r;
+
+	return 0;
+}
+
+int parse_timestamp(const char *t, usec_t *usec)
+{
+	 static const struct {
+		const char *name;
+		const int nr;
+	 } day_nr[] = {
+		{ "Sunday",	0 },
+		{ "Sun",	0 },
+		{ "Monday",	1 },
+		{ "Mon",	1 },
+		{ "Tuesday",	2 },
+		{ "Tue",	2 },
+		{ "Wednesday",	3 },
+		{ "Wed",	3 },
+		{ "Thursday",	4 },
+		{ "Thu",	4 },
+		{ "Friday",	5 },
+		{ "Fri",	5 },
+		{ "Saturday",	6 },
+		{ "Sat",	6 },
+	 };
+
+	const char *k;
+	struct tm tm, copy;
+	time_t x;
+	usec_t plus = 0, minus = 0, ret;
+	int r, weekday = -1;
+	unsigned i;
+
+	/*
+	 * Allowed syntaxes:
+	 *
+	 *   2012-09-22 16:34:22
+	 *   2012-09-22 16:34	  (seconds will be set to 0)
+	 *   2012-09-22		  (time will be set to 00:00:00)
+	 *   16:34:22		  (date will be set to today)
+	 *   16:34		  (date will be set to today, seconds to 0)
+	 *   now
+	 *   yesterday		  (time is set to 00:00:00)
+	 *   today		  (time is set to 00:00:00)
+	 *   tomorrow		  (time is set to 00:00:00)
+	 *   +5min
+	 *   -5days
+	 *
+	 */
+
+	assert(t);
+	assert(usec);
+
+	x = time(NULL);
+	localtime_r(&x, &tm);
+	tm.tm_isdst = -1;
+
+	if (streq(t, "now"))
+		goto finish;
+
+	else if (streq(t, "today")) {
+		tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+		goto finish;
+
+	} else if (streq(t, "yesterday")) {
+		tm.tm_mday--;
+		tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+		goto finish;
+
+	} else if (streq(t, "tomorrow")) {
+		tm.tm_mday++;
+		tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+		goto finish;
+
+	} else if (t[0] == '+') {
+
+		r = parse_sec(t + 1, &plus);
+		if (r < 0)
+			return r;
+
+		goto finish;
+	} else if (t[0] == '-') {
+
+		r = parse_sec(t + 1, &minus);
+		if (r < 0)
+			return r;
+
+		goto finish;
+
+	} else if (endswith(t, " ago")) {
+		char *z;
+
+		z = strndup(t, strlen(t) - 4);
+		if (!z)
+			return -ENOMEM;
+
+		r = parse_sec(z, &minus);
+		free(z);
+		if (r < 0)
+			return r;
+
+		goto finish;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(day_nr); i++) {
+		size_t skip;
+
+		if (!startswith_no_case(t, day_nr[i].name))
+			continue;
+
+		skip = strlen(day_nr[i].name);
+		if (t[skip] != ' ')
+			continue;
+
+		weekday = day_nr[i].nr;
+		t += skip + 1;
+		break;
+	}
+
+	copy = tm;
+	k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
+	if (k && *k == 0)
+		goto finish;
+
+	tm = copy;
+	k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
+	if (k && *k == 0)
+		goto finish;
+
+	tm = copy;
+	k = strptime(t, "%y-%m-%d %H:%M", &tm);
+	if (k && *k == 0) {
+		tm.tm_sec = 0;
+		goto finish;
+	}
+
+	tm = copy;
+	k = strptime(t, "%Y-%m-%d %H:%M", &tm);
+	if (k && *k == 0) {
+		tm.tm_sec = 0;
+		goto finish;
+	}
+
+	tm = copy;
+	k = strptime(t, "%y-%m-%d", &tm);
+	if (k && *k == 0) {
+		tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+		goto finish;
+	}
+
+	tm = copy;
+	k = strptime(t, "%Y-%m-%d", &tm);
+	if (k && *k == 0) {
+		tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+		goto finish;
+	}
+
+	tm = copy;
+	k = strptime(t, "%H:%M:%S", &tm);
+	if (k && *k == 0)
+		goto finish;
+
+	tm = copy;
+	k = strptime(t, "%H:%M", &tm);
+	if (k && *k == 0) {
+		tm.tm_sec = 0;
+		goto finish;
+	}
+
+	tm = copy;
+	k = strptime(t, "%Y%m%d%H%M%S", &tm);
+	if (k && *k == 0) {
+		tm.tm_sec = 0;
+		goto finish;
+	}
+
+	return -EINVAL;
+
+ finish:
+	x = mktime(&tm);
+	if (x == (time_t)-1)
+		return -EINVAL;
+
+	if (weekday >= 0 && tm.tm_wday != weekday)
+		return -EINVAL;
+
+	ret = (usec_t) x *USEC_PER_SEC;
+
+	ret += plus;
+	if (ret > minus)
+		ret -= minus;
+	else
+		ret = 0;
+
+	*usec = ret;
+
+	return 0;
+}
diff --git a/libblkid/lib/ttyutils.c b/libblkid/lib/ttyutils.c
new file mode 100644
index 0000000..ea551e2
--- /dev/null
+++ b/libblkid/lib/ttyutils.c
@@ -0,0 +1,95 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#include <ctype.h>
+
+#include "c.h"
+#include "ttyutils.h"
+
+int get_terminal_width(void)
+{
+#ifdef TIOCGSIZE
+	struct ttysize	t_win;
+#endif
+#ifdef TIOCGWINSZ
+	struct winsize	w_win;
+#endif
+        const char	*cp;
+
+#ifdef TIOCGSIZE
+	if (ioctl (STDIN_FILENO, TIOCGSIZE, &t_win) == 0)
+		return t_win.ts_cols;
+#endif
+#ifdef TIOCGWINSZ
+	if (ioctl (STDIN_FILENO, TIOCGWINSZ, &w_win) == 0)
+		return w_win.ws_col;
+#endif
+        cp = getenv("COLUMNS");
+	if (cp) {
+		char *end = NULL;
+		long c;
+
+		errno = 0;
+		c = strtol(cp, &end, 10);
+
+		if (errno == 0 && end && *end == '\0' && end > cp &&
+		    c > 0 && c <= INT_MAX)
+			return c;
+	}
+	return 0;
+}
+
+int get_terminal_name(int fd,
+		      const char **path,
+		      const char **name,
+		      const char **number)
+{
+	const char *tty;
+	const char *p;
+
+	if (name)
+		*name = NULL;
+	if (path)
+		*path = NULL;
+	if (number)
+		*number = NULL;
+
+	tty = ttyname(fd);
+	if (!tty)
+		return -1;
+	if (path)
+		*path = tty;
+	tty = strncmp(tty, "/dev/", 5) == 0 ? tty + 5 : tty;
+	if (name)
+		*name = tty;
+	if (number) {
+		for (p = tty; p && *p; p++) {
+			if (isdigit(*p)) {
+				*number = p;
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+
+#ifdef TEST_PROGRAM
+# include <stdlib.h>
+int main(void)
+{
+	const char *path, *name, *num;
+
+	if (get_terminal_name(STDERR_FILENO, &path, &name, &num) == 0) {
+		fprintf(stderr, "tty path:   %s\n", path);
+		fprintf(stderr, "tty name:   %s\n", name);
+		fprintf(stderr, "tty number: %s\n", num);
+	}
+	fprintf(stderr,         "tty width:  %d\n", get_terminal_width());
+
+	return EXIT_SUCCESS;
+}
+#endif
diff --git a/libblkid/libblkid.3 b/libblkid/libblkid.3
new file mode 100644
index 0000000..58ca91c
--- /dev/null
+++ b/libblkid/libblkid.3
@@ -0,0 +1,79 @@
+.\" Copyright 2001 Andreas Dilger (adilger@turbolinux.com)
+.\"
+.\" This man page was created for libblkid.so.1.0 from e2fsprogs-1.24.
+.\"
+.\" This file may be copied under the terms of the GNU Lesser General Public
+.\" License.
+.\"
+.\" Created  Wed Sep 14 12:02:12 2001, Andreas Dilger
+.TH LIBBLKID 3 "May 2009" "util-linux" "Programmer's Manual"
+.SH NAME
+libblkid \- block device identification library
+.SH SYNOPSIS
+.B #include <blkid.h>
+.sp
+.B cc
+.I file.c
+.B \-lblkid
+.SH DESCRIPTION
+The
+.B libblkid
+library is used to identify block devices (disks) as to their content (e.g.
+filesystem type) as well as extracting additional information such as
+filesystem labels/volume names, unique identifiers/serial numbers.
+A common use is to allow use of LABEL= and UUID= tags instead of hard-coding
+specific block device names into configuration files.
+.P
+The low-level part of the library also allows to extract information about
+partitions and block device topology.
+.P
+The high-level part of the library keeps information about block devices in a
+cache file and is verified to still be valid before being returned to the user
+(if the user has read permission on the raw block device, otherwise not).
+The cache file also allows unprivileged users (normally anyone other
+than root, or those not in the "disk" group) to locate devices by label/id.
+The standard location of the cache file can be overridden by the
+environment variable BLKID_FILE.
+.P
+In situations where one is getting information about a single known device, it
+does not impact performance whether the cache is used or not (unless you are
+not able to read the block device directly).
+.P
+The high-level part of the library supports two methods to evaluate LABEL/UUID.
+It reads information directly from a block device or read information from
+/dev/disk/by-* udev symlinks.  The udev is preferred method by default.
+.P
+If you are dealing with
+multiple devices, use of the cache is highly recommended (even if empty) as
+devices will be scanned at most one time and the on-disk cache will be
+updated if possible.
+.P
+In some cases (modular kernels), block devices are not even visible until
+after they are accessed the first time, so it is critical that there is
+some way to locate these devices without enumerating only visible devices,
+so the use of the cache file is
+.B required
+in this situation.
+.SH CONFIGURATION FILE
+The standard location of the
+.I /etc/blkid.conf
+config file can be overridden by the environment variable BLKID_CONF.  For more
+details about the config file see
+.BR blkid (8)
+man page.
+.SH AUTHOR
+.B libblkid
+was written by Andreas Dilger for the ext2 filesystem utilties, with input
+from Ted Ts'o.  The library was subsequently heavily modified by Ted Ts'o.
+
+The low-level probing code was rewritten by Karel Zak.
+.SH COPYING
+.B libblkid
+is available under the terms of the GNU Library General Public License (LGPL),
+version 2 (or at your discretion any later version).
+.SH "SEE ALSO"
+.BR blkid (8),
+.BR findfs (8)
+.SH AVAILABILITY
+libblkid is part of the util-linux package since version 2.15 and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
diff --git a/libblkid/libfdisk/COPYING b/libblkid/libfdisk/COPYING
new file mode 100644
index 0000000..be1a5b3
--- /dev/null
+++ b/libblkid/libfdisk/COPYING
@@ -0,0 +1,8 @@
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later
+version.
+
+The complete text of the license is available in the
+../Documentation/licenses/COPYING.LGPLv2.1 file.
diff --git a/libblkid/libfdisk/Makemodule.am b/libblkid/libfdisk/Makemodule.am
new file mode 100644
index 0000000..5d83341
--- /dev/null
+++ b/libblkid/libfdisk/Makemodule.am
@@ -0,0 +1,14 @@
+if BUILD_LIBFDISK
+
+include libfdisk/src/Makemodule.am
+
+if ENABLE_GTK_DOC
+# Docs uses separate Makefiles
+SUBDIRS += libfdisk/docs
+endif
+
+pkgconfig_DATA += libfdisk/fdisk.pc
+PATHFILES      += libfdisk/fdisk.pc
+EXTRA_DIST     += libfdisk/COPYING
+
+endif # BUILD_LIBFDISK
diff --git a/libblkid/libfdisk/docs/.gitignore b/libblkid/libfdisk/docs/.gitignore
new file mode 100644
index 0000000..f91f93d
--- /dev/null
+++ b/libblkid/libfdisk/docs/.gitignore
@@ -0,0 +1,18 @@
+*-decl-list.txt
+*-decl.txt
+*-overrides.txt
+*-undeclared.txt
+*-undocumented.txt
+*-unused.txt
+*.args
+*.bak
+*.hierarchy
+*.interfaces
+*.prerequisites
+*.signals
+*.stamp
+*.types
+html/*
+tmpl/*
+version.xml
+xml/*
diff --git a/libblkid/libfdisk/docs/Makefile.am b/libblkid/libfdisk/docs/Makefile.am
new file mode 100644
index 0000000..dc70979
--- /dev/null
+++ b/libblkid/libfdisk/docs/Makefile.am
@@ -0,0 +1,93 @@
+## Process this file with automake to produce Makefile.in
+
+# We require automake 1.10 at least.
+AUTOMAKE_OPTIONS = 1.10
+
+# This is a blank Makefile.am for using gtk-doc.
+# Copy this to your project's API docs directory and modify the variables to
+# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples
+# of using the various options.
+
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=libfdisk
+
+# Uncomment for versioned docs and specify the version of the module, e.g. '2'.
+#DOC_MODULE_VERSION=2
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.xml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=../src
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkdb.
+# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml
+MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space fdisk
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkhtml
+MKHTML_OPTIONS=
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h
+# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c
+HFILE_GLOB=$(top_builddir)/libfdisk/src/libfdisk.h
+CFILE_GLOB=$(top_srcdir)/libfdisk/src/*.c
+
+# Extra header to include when scanning, which are not under DOC_SOURCE_DIR
+# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h
+EXTRA_HFILES=
+
+# Header files to ignore when scanning. Use base file name, no paths
+# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
+IGNORE_HFILES=fdiskP.h
+
+# Images to copy into HTML directory.
+# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+# e.g. content_files=running.sgml building.sgml changes-2.0.sgml
+content_files = $(builddir)/version.xml
+
+# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+# e.g. expand_content_files=running.sgml
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS)
+# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
+GTKDOC_CFLAGS=
+GTKDOC_LIBS=
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/config/gtk-doc.make
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST += version.xml.in
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+DISTCLEANFILES += version.xml
diff --git a/libblkid/libfdisk/docs/libfdisk-docs.xml b/libblkid/libfdisk/docs/libfdisk-docs.xml
new file mode 100644
index 0000000..546d007
--- /dev/null
+++ b/libblkid/libfdisk/docs/libfdisk-docs.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
+[
+  <!ENTITY version SYSTEM "version.xml">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+  <bookinfo>
+    <title>libfdisk Reference Manual</title>
+    <releaseinfo>for libfdisk version &version;</releaseinfo>
+    <copyright>
+      <year>2014</year>
+      <holder>Karel Zak &lt;kzak@redhat.com&gt;</holder>
+    </copyright>
+  </bookinfo>
+
+  <part id="over">
+    <title>libfdisk Overview</title>
+    <partintro>
+    <para>
+The libfdisk library is used for manipulating with partition tables.
+    </para>
+    <para>
+The library is part of the util-linux package since version 2.26 and is
+available from ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+    </para>
+  </partintro>
+ </part>
+
+  <part>
+    <title>Basic handlers and setting</title>
+    <xi:include href="xml/context.xml"/>
+    <xi:include href="xml/ask.xml"/>
+    <xi:include href="xml/alignment.xml"/>
+    <xi:include href="xml/script.xml"/>
+  </part>
+   <part>
+    <title>Partitining</title>
+    <xi:include href="xml/label.xml"/>
+    <xi:include href="xml/partition.xml"/>
+    <xi:include href="xml/table.xml"/>
+    <xi:include href="xml/parttype.xml"/>
+  </part>
+  <part>
+    <title>Label specific functions</title>
+    <xi:include href="xml/dos.xml"/>
+    <xi:include href="xml/gpt.xml"/>
+    <xi:include href="xml/sun.xml"/>
+    <xi:include href="xml/sgi.xml"/>
+    <xi:include href="xml/bsd.xml"/>
+  </part>
+  <part>
+    <title>Misc</title>
+    <xi:include href="xml/iter.xml"/>
+    <xi:include href="xml/utils.xml"/>
+    <xi:include href="xml/init.xml"/>
+  </part>
+  <index id="api-index-full">
+    <title>API Index</title>
+    <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
+  </index>
+</book>
diff --git a/libblkid/libfdisk/docs/libfdisk-sections.txt b/libblkid/libfdisk/docs/libfdisk-sections.txt
new file mode 100644
index 0000000..c0aaeae
--- /dev/null
+++ b/libblkid/libfdisk/docs/libfdisk-sections.txt
@@ -0,0 +1,303 @@
+<SECTION>
+<FILE>init</FILE>
+fdisk_init_debug
+</SECTION>
+
+<SECTION>
+<FILE>ask</FILE>
+fdisk_info
+fdisk_warn
+fdisk_warnx
+fdisk_set_ask
+<SUBSECTION>
+fdisk_ask
+fdisk_ask_get_query
+fdisk_ask_get_type
+fdisk_ask_menu_get_default
+fdisk_ask_menu_get_item
+fdisk_ask_menu_get_nitems
+fdisk_ask_menu_get_result
+fdisk_ask_menu_set_result
+fdisk_ask_number
+fdisk_ask_number_get_base
+fdisk_ask_number_get_default
+fdisk_ask_number_get_high
+fdisk_ask_number_get_low
+fdisk_ask_number_get_range
+fdisk_ask_number_get_result
+fdisk_ask_number_get_unit
+fdisk_ask_number_inchars
+fdisk_ask_number_set_relative
+fdisk_ask_number_set_result
+fdisk_ask_partnum
+fdisk_ask_print_get_errno
+fdisk_ask_print_get_mesg
+fdisk_ask_string
+fdisk_ask_string_get_result
+fdisk_ask_string_set_result
+fdisk_ask_yesno
+fdisk_ask_yesno_get_result
+fdisk_ask_yesno_set_result
+fdisk_ref_ask
+fdisk_unref_ask
+</SECTION>
+
+<SECTION>
+<FILE>alignment</FILE>
+fdisk_align_lba
+fdisk_align_lba_in_range
+fdisk_has_user_device_properties
+fdisk_lba_is_phy_aligned
+fdisk_override_geometry
+fdisk_reread_partition_table
+fdisk_reset_alignment
+fdisk_reset_device_properties
+fdisk_save_user_geometry
+fdisk_save_user_sector_size
+</SECTION>
+
+<SECTION>
+<FILE>label</FILE>
+fdisk_create_disklabel
+fdisk_list_disklabel
+fdisk_locate_disklabel
+fdisk_reorder_partitions
+fdisk_set_disklabel_id
+fdisk_set_partition_type
+fdisk_toggle_partition_flag
+fdisk_verify_disklabel
+fdisk_write_disklabel
+<SUBSECTION>
+fdisk_get_disklabel_id
+fdisk_get_label
+fdisk_get_nlabels
+fdisk_next_label
+fdisk_get_npartitions
+<SUBSECTION>
+fdisk_field
+fdisk_field_get_id
+fdisk_field_get_name
+fdisk_field_get_width
+fdisk_field_is_number
+<SUBSECTION>
+fdisk_label
+fdisk_label_get_field
+fdisk_label_get_field_by_name
+fdisk_label_get_fields_ids
+fdisk_label_get_name
+fdisk_label_get_nparttypes
+fdisk_label_get_parttype
+fdisk_label_get_parttype_from_code
+fdisk_label_get_parttype_from_string
+fdisk_label_get_type
+fdisk_label_has_code_parttypes
+fdisk_label_is_changed
+fdisk_label_is_disabled
+fdisk_label_parse_parttype
+fdisk_label_require_geometry
+fdisk_label_set_changed
+fdisk_label_set_disabled
+</SECTION>
+
+<SECTION>
+<FILE>script</FILE>
+fdisk_set_script
+fdisk_get_script
+<SUBSECTION>
+fdisk_apply_script
+fdisk_apply_script_headers
+<SUBSECTION>
+fdisk_script
+fdisk_new_script
+fdisk_new_script_from_file
+fdisk_ref_script
+fdisk_script_get_header
+fdisk_script_get_nlines
+fdisk_script_get_table
+fdisk_script_read_context
+fdisk_script_read_file
+fdisk_script_read_line
+fdisk_script_set_header
+fdisk_script_write_file
+fdisk_unref_script
+</SECTION>
+
+<SECTION>
+<FILE>bsd</FILE>
+fdisk_bsd_edit_disklabel
+fdisk_bsd_link_partition
+fdisk_bsd_write_bootstrap
+</SECTION>
+
+<SECTION>
+<FILE>partition</FILE>
+fdisk_add_partition
+fdisk_delete_all_partitions
+fdisk_delete_partition
+fdisk_get_partition
+fdisk_is_partition_used
+fdisk_set_partition
+<SUBSECTION>
+fdisk_partition
+fdisk_new_partition
+fdisk_partition_cmp_partno
+fdisk_partition_cmp_start
+fdisk_partition_end_follow_default
+fdisk_partition_end_is_default
+fdisk_partition_get_attrs
+fdisk_partition_get_end
+fdisk_partition_get_name
+fdisk_partition_get_parent
+fdisk_partition_get_partno
+fdisk_partition_get_size
+fdisk_partition_get_start
+fdisk_partition_get_type
+fdisk_partition_get_uuid
+fdisk_partition_has_end
+fdisk_partition_has_partno
+fdisk_partition_has_size
+fdisk_partition_has_start
+fdisk_partition_is_bootable
+fdisk_partition_is_container
+fdisk_partition_is_freespace
+fdisk_partition_is_nested
+fdisk_partition_is_used
+fdisk_partition_next_partno
+fdisk_partition_partno_follow_default
+fdisk_partition_set_attrs
+fdisk_partition_set_name
+fdisk_partition_set_partno
+fdisk_partition_set_size
+fdisk_partition_set_start
+fdisk_partition_set_type
+fdisk_partition_set_uuid
+fdisk_partition_size_explicit
+fdisk_partition_start_follow_default
+fdisk_partition_start_is_default
+fdisk_partition_to_string
+fdisk_partition_unset_partno
+fdisk_partition_unset_size
+fdisk_partition_unset_start
+fdisk_ref_partition
+fdisk_reset_partition
+fdisk_unref_partition
+</SECTION>
+
+<SECTION>
+<FILE>dos</FILE>
+fdisk_dos_enable_compatible
+fdisk_dos_is_compatible
+fdisk_dos_move_begin
+</SECTION>
+
+<SECTION>
+<FILE>sgi</FILE>
+fdisk_sgi_create_info
+fdisk_sgi_set_bootfile
+</SECTION>
+
+<SECTION>
+<FILE>gpt</FILE>
+fdisk_gpt_is_hybrid
+</SECTION>
+
+<SECTION>
+<FILE>sun</FILE>
+fdisk_sun_set_alt_cyl
+fdisk_sun_set_ilfact
+fdisk_sun_set_pcylcount
+fdisk_sun_set_rspeed
+fdisk_sun_set_xcyl
+</SECTION>
+
+<SECTION>
+<FILE>parttype</FILE>
+fdisk_parttype
+fdisk_copy_parttype
+fdisk_new_parttype
+fdisk_new_unknown_parttype
+fdisk_parttype_get_code
+fdisk_parttype_get_name
+fdisk_parttype_get_string
+fdisk_parttype_is_unknown
+fdisk_parttype_set_code
+fdisk_parttype_set_name
+fdisk_parttype_set_typestr
+fdisk_ref_parttype
+fdisk_unref_parttype
+</SECTION>
+
+<SECTION>
+<FILE>table</FILE>
+fdisk_get_freespaces
+fdisk_get_partitions
+<SUBSECTION>
+fdisk_table
+fdisk_apply_table
+fdisk_new_table
+fdisk_ref_table
+fdisk_reset_table
+fdisk_table_add_partition
+fdisk_table_get_nents
+fdisk_table_get_partition
+fdisk_table_is_empty
+fdisk_table_next_partition
+fdisk_table_remove_partition
+fdisk_table_sort_partitions
+fdisk_table_wrong_order
+fdisk_unref_table
+</SECTION>
+
+
+<SECTION>
+<FILE>context</FILE>
+fdisk_context
+fdisk_assign_device
+fdisk_deassign_device
+fdisk_enable_details
+fdisk_enable_listonly
+fdisk_get_alignment_offset
+fdisk_get_devfd
+fdisk_get_devname
+fdisk_get_first_lba
+fdisk_get_geom_cylinders
+fdisk_get_geom_heads
+fdisk_get_geom_sectors
+fdisk_get_grain_size
+fdisk_get_last_lba
+fdisk_get_minimal_iosize
+fdisk_get_nsectors
+fdisk_get_optimal_iosize
+fdisk_get_parent
+fdisk_get_physector_size
+fdisk_get_sector_size
+fdisk_get_unit
+fdisk_get_units_per_sector
+fdisk_has_label
+fdisk_has_user_device_properties
+fdisk_is_details
+fdisk_is_labeltype
+fdisk_is_listonly
+fdisk_is_readonly
+fdisk_new_context
+fdisk_new_nested_context
+fdisk_ref_context
+fdisk_set_first_lba
+fdisk_set_last_lba
+fdisk_set_unit
+fdisk_unref_context
+fdisk_use_cylinders
+</SECTION>
+
+<SECTION>
+<FILE>utils</FILE>
+fdisk_partname
+</SECTION>
+
+<SECTION>
+<FILE>iter</FILE>
+fdisk_free_iter
+fdisk_iter_get_direction
+fdisk_new_iter
+fdisk_reset_iter
+</SECTION>
diff --git a/libblkid/libfdisk/docs/version.xml.in b/libblkid/libfdisk/docs/version.xml.in
new file mode 100644
index 0000000..d78bda9
--- /dev/null
+++ b/libblkid/libfdisk/docs/version.xml.in
@@ -0,0 +1 @@
+@VERSION@
diff --git a/libblkid/libfdisk/fdisk.pc.in b/libblkid/libfdisk/fdisk.pc.in
new file mode 100644
index 0000000..bf81df0
--- /dev/null
+++ b/libblkid/libfdisk/fdisk.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@usrlib_execdir@
+includedir=@includedir@
+
+Name: fdisk
+Description: fdisk library
+Version: @LIBFDISK_VERSION@
+Requires.private: @LIBFDISK_PC_REQUIRES@
+Cflags: -I${includedir}/libfdisk
+Libs: -L${libdir} -lfdisk
diff --git a/libblkid/libfdisk/src/.gitignore b/libblkid/libfdisk/src/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libblkid/libfdisk/src/.gitignore
diff --git a/libblkid/libfdisk/src/Makemodule.am b/libblkid/libfdisk/src/Makemodule.am
new file mode 100644
index 0000000..18ddec7
--- /dev/null
+++ b/libblkid/libfdisk/src/Makemodule.am
@@ -0,0 +1,112 @@
+
+# libfdisk.h is generated, so it's stored in builddir!
+fdiskincdir = $(includedir)/libfdisk
+nodist_fdiskinc_HEADERS = $(top_builddir)/libfdisk/src/libfdisk.h
+
+usrlib_exec_LTLIBRARIES += libfdisk.la
+libfdisk_la_SOURCES = \
+	include/list.h \
+	\
+	libfdisk/src/fdiskP.h \
+	libfdisk/src/init.c \
+	libfdisk/src/test.c \
+	libfdisk/src/ask.c \
+	libfdisk/src/alignment.c \
+	libfdisk/src/label.c \
+	libfdisk/src/utils.c \
+	libfdisk/src/context.c \
+	libfdisk/src/parttype.c \
+	libfdisk/src/partition.c \
+	libfdisk/src/table.c \
+	libfdisk/src/iter.c \
+	libfdisk/src/script.c \
+	\
+	libfdisk/src/sun.c \
+	libfdisk/src/sgi.c \
+	libfdisk/src/dos.c \
+	libfdisk/src/bsd.c \
+	libfdisk/src/gpt.c \
+	$(nodist_fdiskinc_HEADERS)
+
+
+nodist_libfdisk_la_SOURCES = libfdisk/src/fdiskP.h
+
+libfdisk_la_LIBADD = libcommon.la libuuid.la
+
+libfdisk_la_CFLAGS = \
+	$(SOLIB_CFLAGS) \
+	-I$(ul_libuuid_incdir) \
+	-I$(ul_libfdisk_incdir) \
+	-I$(top_srcdir)/libfdisk/src
+
+libfdisk_la_DEPENDENCIES = \
+	libcommon.la \
+	libuuid.la \
+	libfdisk/src/libfdisk.sym \
+	libfdisk/src/libfdisk.h.in
+
+libfdisk_la_LDFLAGS = \
+	$(SOLIB_LDFLAGS) \
+	-Wl,--version-script=$(top_srcdir)/libfdisk/src/libfdisk.sym \
+	-version-info $(LIBFDISK_VERSION_INFO)
+
+if BUILD_LIBBLKID
+libfdisk_la_LIBADD += libblkid.la
+libfdisk_la_DEPENDENCIES += libblkid.la
+libfdisk_la_CFLAGS += -I$(ul_libblkid_incdir)
+endif
+
+EXTRA_DIST += \
+	libfdisk/src/libfdisk.sym \
+	libfdisk/src/libfdisk.h.in
+
+if BUILD_LIBFDISK_TESTS
+check_PROGRAMS += \
+	test_fdisk_ask \
+	test_fdisk_script \
+	test_fdisk_utils
+
+libfdisk_tests_cflags  = -DTEST_PROGRAM $(libfdisk_la_CFLAGS)
+libfdisk_tests_ldflags = libuuid.la -static
+libfdisk_tests_ldadd   = libfdisk.la $(UUID_LIBS)
+
+if BUILD_LIBBLKID
+libfdisk_tests_ldflags += libblkid.la
+endif
+
+test_fdisk_ask_SOURCES = libfdisk/src/ask.c
+test_fdisk_ask_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_ask_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_ask_LDADD = $(libfdisk_tests_ldadd)
+
+test_fdisk_utils_SOURCES = libfdisk/src/utils.c
+test_fdisk_utils_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_utils_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_utils_LDADD = $(libfdisk_tests_ldadd)
+
+test_fdisk_script_SOURCES = libfdisk/src/script.c
+test_fdisk_script_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_script_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_script_LDADD = $(libfdisk_tests_ldadd)
+
+endif # BUILD_LIBFDISK_TESTS
+
+
+# move lib from $(usrlib_execdir) to $(libdir) if needed
+install-exec-hook-libfdisk:
+	if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libfdisk.so"; then \
+		mkdir -p $(DESTDIR)$(libdir); \
+		mv $(DESTDIR)$(usrlib_execdir)/libfdisk.so.* $(DESTDIR)$(libdir); \
+		so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libfdisk.so); \
+		so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \
+		(cd $(DESTDIR)$(usrlib_execdir) && \
+			rm -f libfdisk.so && \
+			$(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libfdisk.so); \
+	fi
+
+uninstall-hook-libfdisk:
+	rm -f $(DESTDIR)$(libdir)/libfdisk.so*
+
+INSTALL_EXEC_HOOKS += install-exec-hook-libfdisk
+UNINSTALL_HOOKS += uninstall-hook-libfdisk
+
diff --git a/libblkid/libfdisk/src/alignment.c b/libblkid/libfdisk/src/alignment.c
new file mode 100644
index 0000000..67f1ddd
--- /dev/null
+++ b/libblkid/libfdisk/src/alignment.c
@@ -0,0 +1,654 @@
+
+#ifdef HAVE_LIBBLKID
+#include <blkid.h>
+#endif
+#include "blkdev.h"
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: alignment
+ * @title: Alignment
+ * @short_description: functions to align partitions and work with disk topology and geometry
+ *
+ * The libfdisk aligns the end of the partitions to make it possible to align
+ * the next partition to the "grain" (see fdisk_get_grain()). The grain is
+ * usually 1MiB (or more for devices where optimal I/O is greater than 1MiB).
+ *
+ * It means that the library does not align strictly to physical sector size
+ * (or minimal or optimal I/O), but it uses greater granularity. It makes
+ * partition tables more portable. If you copy disk layout from 512-sector to
+ * 4K-sector device, all partitions are still aligned to physical sectors.
+ *
+ * This unified concept also makes partition tables more user friendly, all
+ * tables look same, LBA of the first partition is 2048 sectors everywhere, etc.
+ *
+ * It's recommended to not change any alignment or device properties. All is
+ * initialized by default by fdisk_assign_device().
+ *
+ * Note that terminology used by libfdisk is: 
+ *   - device properties: I/O limits (topology), geometry, sector size, ...
+ *   - alignment: first, last LBA, grain, ...
+ *
+ * The alignment setting may be modified by disk label driver.
+ */
+
+/*
+ * Alignment according to logical granularity (usually 1MiB)
+ */
+static int lba_is_aligned(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+	unsigned long granularity = max(cxt->phy_sector_size, cxt->min_io_size);
+	uintmax_t offset;
+
+	if (cxt->grain > granularity)
+		granularity = cxt->grain;
+	offset = (lba * cxt->sector_size) & (granularity - 1);
+
+	return !((granularity + cxt->alignment_offset - offset) & (granularity - 1));
+}
+
+/*
+ * Alignment according to physical device topology (usually minimal i/o size)
+ */
+static int lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+	unsigned long granularity = max(cxt->phy_sector_size, cxt->min_io_size);
+	uintmax_t offset = (lba * cxt->sector_size) & (granularity - 1);
+
+	return !((granularity + cxt->alignment_offset - offset) & (granularity - 1));
+}
+
+/**
+ * fdisk_align_lba:
+ * @cxt: context
+ * @lba: address to align
+ * @direction: FDISK_ALIGN_{UP,DOWN,NEAREST}
+ *
+ * This function aligns @lba to the "grain" (see fdisk_get_grain()). If the
+ * device uses alignment offset then the result is moved according the offset
+ * to be on the physical boundary.
+ *
+ * Returns: alignment LBA.
+ */
+fdisk_sector_t fdisk_align_lba(struct fdisk_context *cxt, fdisk_sector_t lba, int direction)
+{
+	fdisk_sector_t res;
+
+	if (lba_is_aligned(cxt, lba))
+		res = lba;
+	else {
+		fdisk_sector_t sects_in_phy = cxt->grain / cxt->sector_size;
+
+		if (lba < cxt->first_lba)
+			res = cxt->first_lba;
+
+		else if (direction == FDISK_ALIGN_UP)
+			res = ((lba + sects_in_phy) / sects_in_phy) * sects_in_phy;
+
+		else if (direction == FDISK_ALIGN_DOWN)
+			res = (lba / sects_in_phy) * sects_in_phy;
+
+		else /* FDISK_ALIGN_NEAREST */
+			res = ((lba + sects_in_phy / 2) / sects_in_phy) * sects_in_phy;
+
+		if (cxt->alignment_offset && !lba_is_aligned(cxt, res) &&
+		    res > cxt->alignment_offset / cxt->sector_size) {
+			/*
+			 * apply alignment_offset
+			 *
+			 * On disk with alignment compensation physical blocks starts
+			 * at LBA < 0 (usually LBA -1). It means we have to move LBA
+			 * according the offset to be on the physical boundary.
+			 */
+			/* fprintf(stderr, "LBA: %llu apply alignment_offset\n", res); */
+			res -= (max(cxt->phy_sector_size, cxt->min_io_size) -
+					cxt->alignment_offset) / cxt->sector_size;
+
+			if (direction == FDISK_ALIGN_UP && res < lba)
+				res += sects_in_phy;
+		}
+	}
+
+	if (lba != res)
+		DBG(CXT, ul_debugobj(cxt, "LBA %ju -aligned-to-> %ju",
+				(uintmax_t) lba,
+				(uintmax_t) res));
+	return res;
+}
+
+/**
+ * fdisk_align_lba_in_range:
+ * @cxt: context
+ * @lba: LBA
+ * @start: range start
+ * @stop: range stop
+ *
+ * Align @lba, the result has to be between @start and @stop
+ *
+ * Returns: aligned LBA
+ */
+fdisk_sector_t fdisk_align_lba_in_range(struct fdisk_context *cxt,
+				  fdisk_sector_t lba, fdisk_sector_t start, fdisk_sector_t stop)
+{
+	fdisk_sector_t res;
+
+	start = fdisk_align_lba(cxt, start, FDISK_ALIGN_UP);
+	stop = fdisk_align_lba(cxt, stop, FDISK_ALIGN_DOWN);
+	lba = fdisk_align_lba(cxt, lba, FDISK_ALIGN_NEAREST);
+
+	if (lba < start)
+		res = start;
+	else if (lba > stop)
+		res = stop;
+	else
+		res = lba;
+
+	DBG(CXT, ul_debugobj(cxt, "LBA %ju range:<%ju..%ju>, result: %ju",
+				(uintmax_t) lba,
+				(uintmax_t) start,
+				(uintmax_t) stop,
+				(uintmax_t) res));
+	return res;
+}
+
+/**
+ * fdisk_lba_is_phy_aligned:
+ * @cxt: context
+ * @lba: LBA to check
+ *
+ * Check if the @lba is aligned to physical sector boundary.
+ *
+ * Returns: 1 if aligned.
+ */
+int fdisk_lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+	return lba_is_phy_aligned(cxt, lba);
+}
+
+static unsigned long get_sector_size(int fd)
+{
+	int sect_sz;
+
+	if (!blkdev_get_sector_size(fd, &sect_sz))
+		return (unsigned long) sect_sz;
+	return DEFAULT_SECTOR_SIZE;
+}
+
+static void recount_geometry(struct fdisk_context *cxt)
+{
+	if (!cxt->geom.heads)
+		cxt->geom.heads = 255;
+	if (!cxt->geom.sectors)
+		cxt->geom.sectors = 63;
+
+	cxt->geom.cylinders = cxt->total_sectors /
+		(cxt->geom.heads * cxt->geom.sectors);
+}
+
+/**
+ * fdisk_override_geometry:
+ * @cxt: fdisk context
+ * @cylinders: user specified cylinders
+ * @heads: user specified heads
+ * @sectors: user specified sectors
+ *
+ * Overrides auto-discovery. The function fdisk_reset_device_properties()
+ * restores the original setting.
+ *
+ * The difference between fdisk_override_geometry() and fdisk_save_user_geometry()
+ * is that saved user geometry is persistent setting and it's applied always
+ * when device is assigned to the context or device properties are reseted.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_override_geometry(struct fdisk_context *cxt,
+			    unsigned int cylinders,
+			    unsigned int heads,
+			    unsigned int sectors)
+{
+	if (!cxt)
+		return -EINVAL;
+	if (heads)
+		cxt->geom.heads = heads;
+	if (sectors)
+		cxt->geom.sectors = sectors;
+
+	if (cylinders)
+		cxt->geom.cylinders = cylinders;
+	else
+		recount_geometry(cxt);
+
+	fdisk_reset_alignment(cxt);
+
+	DBG(CXT, ul_debugobj(cxt, "override C/H/S: %u/%u/%u",
+		(unsigned) cxt->geom.cylinders,
+		(unsigned) cxt->geom.heads,
+		(unsigned) cxt->geom.sectors));
+
+	return 0;
+}
+
+/**
+ * fdisk_save_user_geometry:
+ * @cxt: context
+ * @cylinders: C
+ * @heads: H
+ * @sectors: S
+ *
+ * Save user defined geometry to use it for partitioning.
+ *
+ * The user properties are applied by fdisk_assign_device() or
+ * fdisk_reset_device_properties().
+
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_save_user_geometry(struct fdisk_context *cxt,
+			    unsigned int cylinders,
+			    unsigned int heads,
+			    unsigned int sectors)
+{
+	if (!cxt)
+		return -EINVAL;
+
+	if (heads)
+		cxt->user_geom.heads = heads > 256 ? 0 : heads;
+	if (sectors)
+		cxt->user_geom.sectors = sectors >= 64 ? 0 : sectors;
+	if (cylinders)
+		cxt->user_geom.cylinders = cylinders;
+
+	DBG(CXT, ul_debugobj(cxt, "user C/H/S: %u/%u/%u",
+				(unsigned) cxt->user_geom.cylinders,
+				(unsigned) cxt->user_geom.heads,
+				(unsigned) cxt->user_geom.sectors));
+
+	return 0;
+}
+
+/**
+ * fdisk_save_user_sector_size:
+ * @cxt: context
+ * @phy: physical sector size
+ * @log: logicla sector size
+ *
+ * Save user defined sector sizes to use it for partitioning.
+ *
+ * The user properties are applied by fdisk_assign_device() or 
+ * fdisk_reset_device_properties().
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_save_user_sector_size(struct fdisk_context *cxt,
+				unsigned int phy,
+				unsigned int log)
+{
+	if (!cxt)
+		return -EINVAL;
+
+	DBG(CXT, ul_debugobj(cxt, "user phy/log sector size: %u/%u", phy, log));
+
+	cxt->user_pyh_sector = phy;
+	cxt->user_log_sector = log;
+
+	return 0;
+}
+
+/**
+ * fdisk_has_user_device_properties:
+ * @cxt: context
+ *
+ * Returns: 1 if user specified any properties
+ */
+int fdisk_has_user_device_properties(struct fdisk_context *cxt)
+{
+	return (cxt->user_pyh_sector
+		    || cxt->user_log_sector
+		    || cxt->user_geom.heads
+		    || cxt->user_geom.sectors
+		    || cxt->user_geom.cylinders);
+}
+
+int fdisk_apply_user_device_properties(struct fdisk_context *cxt)
+{
+	if (!cxt)
+		return -EINVAL;
+
+	DBG(CXT, ul_debugobj(cxt, "appling user device properties"));
+
+	if (cxt->user_pyh_sector)
+		cxt->phy_sector_size = cxt->user_pyh_sector;
+	if (cxt->user_log_sector)
+		cxt->sector_size = cxt->min_io_size =
+			cxt->io_size = cxt->user_log_sector;
+
+	if (cxt->user_geom.heads)
+		cxt->geom.heads = cxt->user_geom.heads;
+	if (cxt->user_geom.sectors)
+		cxt->geom.sectors = cxt->user_geom.sectors;
+
+	if (cxt->user_geom.cylinders)
+		cxt->geom.cylinders = cxt->user_geom.cylinders;
+	else if (cxt->user_geom.heads || cxt->user_geom.sectors)
+		recount_geometry(cxt);
+
+	fdisk_reset_alignment(cxt);
+	if (cxt->firstsector_bufsz != cxt->sector_size)
+		fdisk_read_firstsector(cxt);
+
+	DBG(CXT, ul_debugobj(cxt, "new C/H/S: %u/%u/%u",
+		(unsigned) cxt->geom.cylinders,
+		(unsigned) cxt->geom.heads,
+		(unsigned) cxt->geom.sectors));
+	DBG(CXT, ul_debugobj(cxt, "new log/phy sector size: %u/%u",
+		(unsigned) cxt->sector_size,
+		(unsigned) cxt->phy_sector_size));
+
+	return 0;
+}
+
+void fdisk_zeroize_device_properties(struct fdisk_context *cxt)
+{
+	assert(cxt);
+
+	cxt->io_size = 0;
+	cxt->optimal_io_size = 0;
+	cxt->min_io_size = 0;
+	cxt->phy_sector_size = 0;
+	cxt->sector_size = 0;
+	cxt->alignment_offset = 0;
+	cxt->grain = 0;
+	cxt->first_lba = 0;
+	cxt->last_lba = 0;
+	cxt->total_sectors = 0;
+
+	memset(&cxt->geom, 0, sizeof(struct fdisk_geometry));
+}
+
+/**
+ * fdisk_reset_device_properties:
+ * @cxt: context
+ *
+ * Resets and discovery topology (I/O limits), geometry, re-read the first
+ * rector on the device if necessary and apply user device setting (geometry
+ * and sector size), then initialize alignment according to label driver (see
+ * fdisk_reset_alignment()).
+ *
+ * You don't have to use this function by default, fdisk_assign_device() is
+ * smart enough to initialize all necessary setting.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_reset_device_properties(struct fdisk_context *cxt)
+{
+	int rc;
+
+	if (!cxt)
+		return -EINVAL;
+
+	DBG(CXT, ul_debugobj(cxt, "*** reseting device properties"));
+
+	fdisk_zeroize_device_properties(cxt);
+	fdisk_discover_topology(cxt);
+	fdisk_discover_geometry(cxt);
+
+	rc = fdisk_read_firstsector(cxt);
+	if (rc)
+		return rc;
+
+	fdisk_apply_user_device_properties(cxt);
+	return 0;
+}
+
+/*
+ * Generic (label independent) geometry
+ */
+int fdisk_discover_geometry(struct fdisk_context *cxt)
+{
+	fdisk_sector_t nsects;
+
+	assert(cxt);
+	assert(cxt->geom.heads == 0);
+
+	DBG(CXT, ul_debugobj(cxt, "%s: discovering geometry...", cxt->dev_path));
+
+	/* get number of 512-byte sectors, and convert it the real sectors */
+	if (!blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &nsects))
+		cxt->total_sectors = (nsects / (cxt->sector_size >> 9));
+
+	DBG(CXT, ul_debugobj(cxt, "total sectors: %ju (ioctl=%ju)",
+				(uintmax_t) cxt->total_sectors,
+				(uintmax_t) nsects));
+
+	/* what the kernel/bios thinks the geometry is */
+	blkdev_get_geometry(cxt->dev_fd, &cxt->geom.heads, (unsigned int *) &cxt->geom.sectors);
+
+	/* obtained heads and sectors */
+	recount_geometry(cxt);
+
+	DBG(CXT, ul_debugobj(cxt, "result: C/H/S: %u/%u/%u",
+			       (unsigned) cxt->geom.cylinders,
+			       (unsigned) cxt->geom.heads,
+			       (unsigned) cxt->geom.sectors));
+	return 0;
+}
+
+int fdisk_discover_topology(struct fdisk_context *cxt)
+{
+#ifdef HAVE_LIBBLKID
+	blkid_probe pr;
+#endif
+	assert(cxt);
+	assert(cxt->sector_size == 0);
+
+	DBG(CXT, ul_debugobj(cxt, "%s: discovering topology...", cxt->dev_path));
+#ifdef HAVE_LIBBLKID
+	DBG(CXT, ul_debugobj(cxt, "initialize libblkid prober"));
+
+	pr = blkid_new_probe();
+	if (pr && blkid_probe_set_device(pr, cxt->dev_fd, 0, 0) == 0) {
+		blkid_topology tp = blkid_probe_get_topology(pr);
+
+		if (tp) {
+			cxt->min_io_size = blkid_topology_get_minimum_io_size(tp);
+			cxt->optimal_io_size = blkid_topology_get_optimal_io_size(tp);
+			cxt->phy_sector_size = blkid_topology_get_physical_sector_size(tp);
+			cxt->alignment_offset = blkid_topology_get_alignment_offset(tp);
+
+			/* I/O size used by fdisk */
+			cxt->io_size = cxt->optimal_io_size;
+			if (!cxt->io_size)
+				/* optimal IO is optional, default to minimum IO */
+				cxt->io_size = cxt->min_io_size;
+		}
+	}
+	blkid_free_probe(pr);
+#endif
+
+	cxt->sector_size = get_sector_size(cxt->dev_fd);
+	if (!cxt->phy_sector_size) /* could not discover physical size */
+		cxt->phy_sector_size = cxt->sector_size;
+
+	/* no blkid or error, use default values */
+	if (!cxt->min_io_size)
+		cxt->min_io_size = cxt->sector_size;
+	if (!cxt->io_size)
+		cxt->io_size = cxt->sector_size;
+
+	DBG(CXT, ul_debugobj(cxt, "result: log/phy sector size: %ld/%ld",
+			cxt->sector_size, cxt->phy_sector_size));
+	DBG(CXT, ul_debugobj(cxt, "result: fdisk/min/optimal io: %ld/%ld/%ld",
+		       cxt->io_size, cxt->optimal_io_size, cxt->min_io_size));
+	return 0;
+}
+
+static int has_topology(struct fdisk_context *cxt)
+{
+	/*
+	 * Assume that the device provides topology info if
+	 * optimal_io_size is set or alignment_offset is set or
+	 * minimum_io_size is not power of 2.
+	 */
+	if (cxt &&
+	    (cxt->optimal_io_size ||
+	     cxt->alignment_offset ||
+	     !is_power_of_2(cxt->min_io_size)))
+		return 1;
+	return 0;
+}
+
+/*
+ * The LBA of the first partition is based on the device geometry and topology.
+ * This offset is generic (and recommended) for all labels.
+ *
+ * Returns: 0 on error or number of logical sectors.
+ */
+static fdisk_sector_t topology_get_first_lba(struct fdisk_context *cxt)
+{
+	fdisk_sector_t x = 0, res;
+
+	if (!cxt)
+		return 0;
+
+	if (!cxt->io_size)
+		fdisk_discover_topology(cxt);
+
+	/*
+	 * Align the begin of partitions to:
+	 *
+	 * a) topology
+	 *  a2) alignment offset
+	 *  a1) or physical sector (minimal_io_size, aka "grain")
+	 *
+	 * b) or default to 1MiB (2048 sectrors, Windows Vista default)
+	 *
+	 * c) or for very small devices use 1 phy.sector
+	 */
+	if (has_topology(cxt)) {
+		if (cxt->alignment_offset)
+			x = cxt->alignment_offset;
+		else if (cxt->io_size > 2048 * 512)
+			x = cxt->io_size;
+	}
+	/* default to 1MiB */
+	if (!x)
+		x = 2048 * 512;
+
+	res = x / cxt->sector_size;
+
+	/* don't use huge offset on small devices */
+	if (cxt->total_sectors <= res * 4)
+		res = cxt->phy_sector_size / cxt->sector_size;
+
+	return res;
+}
+
+static unsigned long topology_get_grain(struct fdisk_context *cxt)
+{
+	unsigned long res;
+
+	if (!cxt)
+		return 0;
+
+	if (!cxt->io_size)
+		fdisk_discover_topology(cxt);
+
+	res = cxt->io_size;
+
+	/* use 1MiB grain always when possible */
+	if (res < 2048 * 512)
+		res = 2048 * 512;
+
+	/* don't use huge grain on small devices */
+	if (cxt->total_sectors <= (res * 4 / cxt->sector_size))
+		res = cxt->phy_sector_size;
+
+	return res;
+}
+
+/**
+ * fdisk_reset_alignment:
+ * @cxt: fdisk context
+ *
+ * Resets alignment setting to the default and label specific values. This
+ * function does not change device properties (I/O limits, geometry etc.).
+ *
+ * Returns: 0 on success, < 0 in case of error.
+ */
+int fdisk_reset_alignment(struct fdisk_context *cxt)
+{
+	int rc = 0;
+
+	if (!cxt)
+		return -EINVAL;
+
+	DBG(CXT, ul_debugobj(cxt, "reseting alignment..."));
+
+	/* default */
+	cxt->grain = topology_get_grain(cxt);
+	cxt->first_lba = topology_get_first_lba(cxt);
+	cxt->last_lba = cxt->total_sectors - 1;
+
+	/* overwrite default by label stuff */
+	if (cxt->label && cxt->label->op->reset_alignment)
+		rc = cxt->label->op->reset_alignment(cxt);
+
+	DBG(CXT, ul_debugobj(cxt, "alignment reseted to: "
+			    "first LBA=%ju, last LBA=%ju, grain=%lu [rc=%d]",
+			    (uintmax_t) cxt->first_lba, (uintmax_t) cxt->last_lba,
+			    cxt->grain,	rc));
+	return rc;
+}
+
+
+fdisk_sector_t fdisk_scround(struct fdisk_context *cxt, fdisk_sector_t num)
+{
+	fdisk_sector_t un = fdisk_get_units_per_sector(cxt);
+	return (num + un - 1) / un;
+}
+
+fdisk_sector_t fdisk_cround(struct fdisk_context *cxt, fdisk_sector_t num)
+{
+	return fdisk_use_cylinders(cxt) ?
+			(num / fdisk_get_units_per_sector(cxt)) + 1 : num;
+}
+
+/**
+ * fdisk_reread_partition_table:
+ * @cxt: context
+ *
+ * Force *kernel* to re-read partition table on block devices.
+ *
+ * Returns: 0 on success, < 0 in case of error.
+ */
+int fdisk_reread_partition_table(struct fdisk_context *cxt)
+{
+	int i;
+	struct stat statbuf;
+
+	assert(cxt);
+	assert(cxt->dev_fd >= 0);
+
+	i = fstat(cxt->dev_fd, &statbuf);
+	if (i == 0 && S_ISBLK(statbuf.st_mode)) {
+		sync();
+#ifdef BLKRRPART
+		fdisk_info(cxt, _("Calling ioctl() to re-read partition table."));
+		i = ioctl(cxt->dev_fd, BLKRRPART);
+#else
+		errno = ENOSYS;
+		i = 1;
+#endif
+	}
+
+	if (i) {
+		fdisk_warn(cxt, _("Re-reading the partition table failed."));
+		fdisk_info(cxt,	_(
+			"The kernel still uses the old table. The "
+			"new table will be used at the next reboot "
+			"or after you run partprobe(8) or kpartx(8)."));
+		return -errno;
+	}
+
+	return 0;
+}
diff --git a/libblkid/libfdisk/src/ask.c b/libblkid/libfdisk/src/ask.c
new file mode 100644
index 0000000..7e0c3c2
--- /dev/null
+++ b/libblkid/libfdisk/src/ask.c
@@ -0,0 +1,1044 @@
+
+#include "strutils.h"
+#include "fdiskP.h"
+
+/**
+ * SECTION: ask
+ * @title: Ask
+ * @short_description: interface for dialog driven partitioning, warning and info messages
+ *
+ */
+
+static void fdisk_ask_menu_reset_items(struct fdisk_ask *ask);
+
+
+/**
+ * fdisk_set_ask:
+ * @cxt: context
+ * @ask_cb: callback
+ * @data: callback data
+ *
+ * Set callback for dialog driven partitioning and library warnings/errors.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_set_ask(struct fdisk_context *cxt,
+		int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *),
+		void *data)
+{
+	assert(cxt);
+
+	cxt->ask_cb = ask_cb;
+	cxt->ask_data = data;
+	return 0;
+}
+
+struct fdisk_ask *fdisk_new_ask(void)
+{
+	struct fdisk_ask *ask = calloc(1, sizeof(struct fdisk_ask));
+	DBG(ASK, ul_debugobj(ask, "alloc"));
+	ask->refcount = 1;
+	return ask;
+}
+
+void fdisk_reset_ask(struct fdisk_ask *ask)
+{
+	int refcount;
+
+	assert(ask);
+	free(ask->query);
+
+	DBG(ASK, ul_debugobj(ask, "reset"));
+	refcount = ask->refcount;
+
+	if (fdisk_is_ask(ask, MENU))
+		fdisk_ask_menu_reset_items(ask);
+
+	memset(ask, 0, sizeof(*ask));
+	ask->refcount = refcount;
+}
+
+/**
+ * fdisk_ref_ask:
+ * @ask: ask instance
+ *
+ * Incremparts reference counter.
+ */
+void fdisk_ref_ask(struct fdisk_ask *ask)
+{
+	if (ask)
+		ask->refcount++;
+}
+
+
+/**
+ * fdisk_unref_ask:
+ * @ask: ask instance
+ *
+ * De-incremparts reference counter, on zero the @ask is automatically
+ * deallocated.
+ */
+void fdisk_unref_ask(struct fdisk_ask *ask)
+{
+	if (!ask)
+		return;
+	ask->refcount--;
+
+	if (ask->refcount <= 0) {
+		fdisk_reset_ask(ask);
+		DBG(ASK, ul_debugobj(ask, "free"));
+		free(ask);
+	}
+}
+
+/**
+ * fdisk_ask_get_query:
+ * @ask: ask instance
+ *
+ * Returns: pointer to dialog string.
+ */
+const char *fdisk_ask_get_query(struct fdisk_ask *ask)
+{
+	assert(ask);
+	return ask->query;
+}
+
+int fdisk_ask_set_query(struct fdisk_ask *ask, const char *str)
+{
+	assert(ask);
+	return !strdup_to_struct_member(ask, query, str) ? -ENOMEM : 0;
+}
+
+/**
+ * fdisk_ask_get_type:
+ * @ask: ask instance
+ *
+ * Returns: FDISK_ASKTYPE_*
+ */
+int fdisk_ask_get_type(struct fdisk_ask *ask)
+{
+	assert(ask);
+	return ask->type;
+}
+
+int fdisk_ask_set_type(struct fdisk_ask *ask, int type)
+{
+	assert(ask);
+	ask->type = type;
+	return 0;
+}
+
+int fdisk_do_ask(struct fdisk_context *cxt, struct fdisk_ask *ask)
+{
+	int rc;
+
+	assert(ask);
+	assert(cxt);
+
+	DBG(ASK, ul_debugobj(ask, "do_ask for '%s'",
+				ask->query ? ask->query :
+				ask->type == FDISK_ASKTYPE_INFO  ? "info" :
+				ask->type == FDISK_ASKTYPE_WARNX ? "warnx" :
+				ask->type == FDISK_ASKTYPE_WARN  ? "warn" :
+				"?nothing?"));
+
+	if (!cxt->ask_cb) {
+		DBG(ASK, ul_debugobj(ask, "no ask callback specified!"));
+		return -EINVAL;
+	}
+
+	rc = cxt->ask_cb(cxt, ask, cxt->ask_data);
+
+	DBG(ASK, ul_debugobj(ask, "do_ask done [rc=%d]", rc));
+	return rc;
+}
+
+#define is_number_ask(a)  (fdisk_is_ask(a, NUMBER) || fdisk_is_ask(a, OFFSET))
+
+/**
+ * fdisk_ask_number_get_range:
+ * @ask: ask instance
+ *
+ * Returns: string with range (e.g. "1,3,5-10")
+ */
+const char *fdisk_ask_number_get_range(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_number_ask(ask));
+	return ask->data.num.range;
+}
+
+int fdisk_ask_number_set_range(struct fdisk_ask *ask, const char *range)
+{
+	assert(ask);
+	assert(is_number_ask(ask));
+	ask->data.num.range = range;
+	return 0;
+}
+
+/**
+ * fdisk_ask_number_get_default:
+ * @ask: ask instance
+ *
+ * Returns: default number
+ *
+ */
+uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_number_ask(ask));
+	return ask->data.num.dfl;
+}
+
+int fdisk_ask_number_set_default(struct fdisk_ask *ask, uint64_t dflt)
+{
+	assert(ask);
+	ask->data.num.dfl = dflt;
+	return 0;
+}
+
+/**
+ * fdisk_ask_number_get_low:
+ * @ask: ask instance
+ *
+ * Returns: minimal possible number when ask for numbers in range
+ */
+uint64_t fdisk_ask_number_get_low(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_number_ask(ask));
+	return ask->data.num.low;
+}
+
+int fdisk_ask_number_set_low(struct fdisk_ask *ask, uint64_t low)
+{
+	assert(ask);
+	ask->data.num.low = low;
+	return 0;
+}
+
+/**
+ * fdisk_ask_number_get_high:
+ * @ask: ask instance
+ *
+ * Returns: maximal possible number when ask for numbers in range
+ */
+uint64_t fdisk_ask_number_get_high(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_number_ask(ask));
+	return ask->data.num.hig;
+}
+
+int fdisk_ask_number_set_high(struct fdisk_ask *ask, uint64_t high)
+{
+	assert(ask);
+	ask->data.num.hig = high;
+	return 0;
+}
+
+/**
+ * fdisk_ask_number_get_result:
+ * @ask: ask instance
+ *
+ * Returns: result
+ */
+uint64_t fdisk_ask_number_get_result(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_number_ask(ask));
+	return ask->data.num.result;
+}
+
+/**
+ * fdisk_ask_number_set_result:
+ * @ask: ask instance
+ * @result: dialog result
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_number_set_result(struct fdisk_ask *ask, uint64_t result)
+{
+	assert(ask);
+	ask->data.num.result = result;
+	return 0;
+}
+
+/**
+ * fdisk_ask_number_get_base:
+ * @ask: ask instance
+ *
+ * Returns: base when user specify number in relative notation (+size)
+ */
+uint64_t fdisk_ask_number_get_base(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_number_ask(ask));
+	return ask->data.num.base;
+}
+
+int fdisk_ask_number_set_base(struct fdisk_ask *ask, uint64_t base)
+{
+	assert(ask);
+	ask->data.num.base = base;
+	return 0;
+}
+
+/**
+ * fdisk_ask_number_get_unit:
+ * @ask: ask instance
+ *
+ * Returns: number of bytes per the unit
+ */
+uint64_t fdisk_ask_number_get_unit(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_number_ask(ask));
+	return ask->data.num.unit;
+}
+
+int fdisk_ask_number_set_unit(struct fdisk_ask *ask, uint64_t unit)
+{
+	assert(ask);
+	ask->data.num.unit = unit;
+	return 0;
+}
+
+int fdisk_ask_number_is_relative(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_number_ask(ask));
+	return ask->data.num.relative;
+}
+
+/**
+ * fdisk_ask_number_set_relative
+ * @ask: ask instance
+ * @relative: 0 or 1
+ *
+ * Inform libfdisk that user specified number in relative notation rather than
+ * by explicit number. This info allows to fdisk do some optimization (e.g.
+ * align end of partiton, etc.)
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_number_set_relative(struct fdisk_ask *ask, int relative)
+{
+	assert(ask);
+	ask->data.num.relative = relative ? 1 : 0;
+	return 0;
+}
+
+/**
+ * fdisk_ask_number_inchars:
+ * @ask: ask instance
+ *
+ * For example for BSD is normal to address partition by chars rather than by
+ * number (first partition is 'a').
+ *
+ * Returns: 1 if number should be presented as chars
+ *
+ */
+int fdisk_ask_number_inchars(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_number_ask(ask));
+	return ask->data.num.inchars;
+}
+
+/*
+ * Generates string with list ranges (e.g. 1,2,5-8) for the 'cur'
+ */
+#define tochar(num)	((int) ('a' + num - 1))
+static char *mk_string_list(char *ptr, size_t *len, size_t *begin,
+			    size_t *run, ssize_t cur, int inchar)
+{
+	int rlen;
+
+	if (cur != -1) {
+		if (!*begin) {			/* begin of the list */
+			*begin = cur + 1;
+			return ptr;
+		}
+
+		if (*begin + *run == cur) {	/* no gap, continue */
+			(*run)++;
+			return ptr;
+		}
+	} else if (!*begin) {
+		*ptr = '\0';
+		return ptr;		/* end of empty list */
+	}
+
+					/* add to the list */
+	if (!*run)
+		rlen = inchar ? snprintf(ptr, *len, "%c,", tochar(*begin)) :
+				snprintf(ptr, *len, "%zu,", *begin);
+	else if (*run == 1)
+		rlen = inchar ?
+			snprintf(ptr, *len, "%c,%c,", tochar(*begin), tochar(*begin + 1)) :
+			snprintf(ptr, *len, "%zu,%zu,", *begin, *begin + 1);
+	else
+		rlen = inchar ?
+			snprintf(ptr, *len, "%c-%c,", tochar(*begin), tochar(*begin + *run)) :
+			snprintf(ptr, *len, "%zu-%zu,", *begin, *begin + *run);
+
+	if (rlen < 0 || (size_t) rlen + 1 > *len)
+		return NULL;
+
+	ptr += rlen;
+
+	if (rlen > 0 && *len > (size_t) rlen)
+		*len -= rlen;
+	else
+		*len = 0;
+
+	if (cur == -1 && *begin) {
+		/* end of the list */
+		*(ptr - 1) = '\0';	/* remove tailing ',' from the list */
+		return ptr;
+	}
+
+	*begin = cur + 1;
+	*run = 0;
+
+	return ptr;
+}
+
+/**
+ * fdisk_ask_partnum:
+ * @cxt: context
+ * @partnum: returns partition number
+ * @wantnew: 0|1
+ *
+ * High-level API to ask for used or unused partition number.
+ *
+ * Returns: 0 on success, < 0 on error, 1 if no free/used partition
+ */
+int fdisk_ask_partnum(struct fdisk_context *cxt, size_t *partnum, int wantnew)
+{
+	int rc = 0, inchar = 0;
+	char range[BUFSIZ], *ptr = range;
+	size_t i, len = sizeof(range), begin = 0, run = 0;
+	struct fdisk_ask *ask = NULL;
+	__typeof__(ask->data.num) *num;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(partnum);
+
+	if (cxt->label && cxt->label->flags & FDISK_LABEL_FL_INCHARS_PARTNO)
+		inchar = 1;
+
+	DBG(ASK, ul_debug("%s: asking for %s partition number "
+			  "(max: %zu, inchar: %s)",
+			cxt->label->name,
+			wantnew ? "new" : "used",
+			cxt->label->nparts_max,
+			inchar ? "yes" : "not"));
+
+	ask = fdisk_new_ask();
+	if (!ask)
+		return -ENOMEM;
+
+	fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+	num = &ask->data.num;
+
+	ask->data.num.inchars = inchar ? 1 : 0;
+
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+		int used = fdisk_is_partition_used(cxt, i);
+
+		if (wantnew && !used) {
+			ptr = mk_string_list(ptr, &len, &begin, &run, i, inchar);
+			if (!ptr) {
+				rc = -EINVAL;
+				break;
+			}
+			if (!num->low)
+				num->dfl = num->low = i + 1;
+			num->hig = i + 1;
+		} else if (!wantnew && used) {
+			ptr = mk_string_list(ptr, &len, &begin, &run, i, inchar);
+			if (!num->low)
+				num->low = i + 1;
+			num->dfl = num->hig = i + 1;
+		}
+	}
+
+	DBG(ASK, ul_debugobj(ask, "ask limits: low: %ju, high: %ju, default: %ju",
+				num->low, num->hig, num->dfl));
+
+	if (!rc && !wantnew && num->low == num->hig) {
+		if (num->low > 0) {
+			/* only one existing partiton, don't ask, return the number */
+			fdisk_ask_number_set_result(ask, num->low);
+			fdisk_info(cxt, _("Selected partition %ju"), num->low);
+
+		} else if (num->low == 0) {
+			fdisk_warnx(cxt, _("No partition is defined yet!"));
+			rc = 1;
+		}
+		goto dont_ask;
+	}
+	if (!rc && wantnew && num->low == num->hig) {
+		if (num->low > 0) {
+			/* only one free partition, don't ask, return the number */
+			fdisk_ask_number_set_result(ask, num->low);
+			fdisk_info(cxt, _("Selected partition %ju"), num->low);
+		}
+		if (num->low == 0) {
+			fdisk_warnx(cxt, _("No free partition available!"));
+			rc = 1;
+		}
+		goto dont_ask;
+	}
+	if (!rc) {
+		mk_string_list(ptr, &len, &begin, &run, -1, inchar);	/* terminate the list */
+		rc = fdisk_ask_number_set_range(ask, range);
+	}
+	if (!rc)
+		rc = fdisk_ask_set_query(ask, _("Partition number"));
+	if (!rc)
+		rc = fdisk_do_ask(cxt, ask);
+
+dont_ask:
+	if (!rc) {
+		*partnum = fdisk_ask_number_get_result(ask);
+		if (*partnum)
+			*partnum -= 1;
+	}
+	DBG(ASK, ul_debugobj(ask, "result: %ju [rc=%d]\n", fdisk_ask_number_get_result(ask), rc));
+	fdisk_unref_ask(ask);
+	return rc;
+}
+
+/**
+ * fdisk_ask_number:
+ * @cxt: context
+ * @low: minimal possible number
+ * @dflt: default suggestion
+ * @high: maximal possible number
+ * @query: question string
+ * @result: returns result
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_ask_number(struct fdisk_context *cxt,
+		     uintmax_t low,
+		     uintmax_t dflt,
+		     uintmax_t high,
+		     const char *query,
+		     uintmax_t *result)
+{
+	struct fdisk_ask *ask;
+	int rc;
+
+	assert(cxt);
+
+	ask = fdisk_new_ask();
+	if (!ask)
+		return -ENOMEM;
+
+	rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+	if (!rc)
+		fdisk_ask_number_set_low(ask, low);
+	if (!rc)
+		fdisk_ask_number_set_default(ask, dflt);
+	if (!rc)
+		fdisk_ask_number_set_high(ask, high);
+	if (!rc)
+		fdisk_ask_set_query(ask, query);
+	if (!rc)
+		rc = fdisk_do_ask(cxt, ask);
+	if (!rc)
+		*result = fdisk_ask_number_get_result(ask);
+
+	DBG(ASK, ul_debugobj(ask, "result: %ju [rc=%d]\n", *result, rc));
+	fdisk_unref_ask(ask);
+	return rc;
+}
+
+/**
+ * fdisk_ask_string_get_result:
+ * @ask: ask instance
+ *
+ * Returns: pointer to dialog result
+ */
+char *fdisk_ask_string_get_result(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(fdisk_is_ask(ask, STRING));
+	return ask->data.str.result;
+}
+
+/**
+ * fdisk_ask_string_set_result:
+ * @ask: ask instance
+ * @result: pointer to allocated buffer with string
+ *
+ * You don't have to care about the @result deallocation, libfdisk is going to
+ * deallocate the result when destroy @ask instance.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_string_set_result(struct fdisk_ask *ask, char *result)
+{
+	assert(ask);
+	ask->data.str.result = result;
+	return 0;
+}
+
+/**
+ * fdisk_ask_string:
+ * @cxt: context:
+ * @query: question string
+ * @result: returns allocated buffer
+ *
+ * High-level API to ask for strings. Don't forget to deallocate the @result.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_ask_string(struct fdisk_context *cxt,
+		     const char *query,
+		     char **result)
+{
+	struct fdisk_ask *ask;
+	int rc;
+
+	assert(cxt);
+
+	ask = fdisk_new_ask();
+	if (!ask)
+		return -ENOMEM;
+
+	rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_STRING);
+	if (!rc)
+		fdisk_ask_set_query(ask, query);
+	if (!rc)
+		rc = fdisk_do_ask(cxt, ask);
+	if (!rc)
+		*result = fdisk_ask_string_get_result(ask);
+
+	DBG(ASK, ul_debugobj(ask, "result: %s [rc=%d]\n", *result, rc));
+	fdisk_unref_ask(ask);
+	return rc;
+}
+
+/**
+ * fdisk_ask_yesno:
+ * @cxt: context
+ * @query: question string
+ * @result: returns 0 (no) or 1 (yes)
+ *
+ * Hight-level API to ask Yes/No questions
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_yesno(struct fdisk_context *cxt,
+		     const char *query,
+		     int *result)
+{
+	struct fdisk_ask *ask;
+	int rc;
+
+	assert(cxt);
+
+	ask = fdisk_new_ask();
+	if (!ask)
+		return -ENOMEM;
+
+	rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_YESNO);
+	if (!rc)
+		fdisk_ask_set_query(ask, query);
+	if (!rc)
+		rc = fdisk_do_ask(cxt, ask);
+	if (!rc)
+		*result = fdisk_ask_yesno_get_result(ask) == 1 ? 1 : 0;
+
+	DBG(ASK, ul_debugobj(ask, "result: %d [rc=%d]\n", *result, rc));
+	fdisk_unref_ask(ask);
+	return rc;
+}
+
+/**
+ * fdisk_ask_yesno_get_result:
+ * @ask: ask instance
+ *
+ * Returns: 0 or 1
+ */
+int fdisk_ask_yesno_get_result(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(fdisk_is_ask(ask, YESNO));
+	return ask->data.yesno.result;
+}
+
+/**
+ * fdisk_ask_yesno_set_result:
+ * @ask: ask instance
+ * @result: 1 or 0
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_yesno_set_result(struct fdisk_ask *ask, int result)
+{
+	assert(ask);
+	ask->data.yesno.result = result;
+	return 0;
+}
+
+/*
+ * menu
+ */
+int fdisk_ask_menu_set_default(struct fdisk_ask *ask, int dfl)
+{
+	assert(ask);
+	assert(fdisk_is_ask(ask, MENU));
+	ask->data.menu.dfl = dfl;
+	return 0;
+}
+
+/**
+ * fdisk_ask_menu_get_default:
+ * @ask: ask instance
+ *
+ * Returns: default menu item key
+ */
+int fdisk_ask_menu_get_default(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(fdisk_is_ask(ask, MENU));
+	return ask->data.menu.dfl;
+}
+
+/**
+ * fdisk_ask_menu_set_result:
+ * @ask: ask instance
+ * @key: result
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_menu_set_result(struct fdisk_ask *ask, int key)
+{
+	assert(ask);
+	assert(fdisk_is_ask(ask, MENU));
+	ask->data.menu.result = key;
+	DBG(ASK, ul_debugobj(ask, "menu result: %c\n", key));
+	return 0;
+
+}
+
+/**
+ * fdisk_ask_menu_get_result:
+ * @ask: ask instance
+ * @key: returns selected menu item key
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_ask_menu_get_result(struct fdisk_ask *ask, int *key)
+{
+	assert(ask);
+	assert(fdisk_is_ask(ask, MENU));
+	if (key)
+		*key =  ask->data.menu.result;
+	return 0;
+}
+
+/**
+ * fdisk_ask_menu_get_item:
+ * @ask: ask menu instance
+ * @idx: wanted menu item index
+ * @key: returns key of the menu item
+ * @name: returns name of the menu item
+ * @desc: returns description of the menu item
+ *
+ * Returns: 0 on success, <0 on error, >0 if idx out-of-range
+ */
+int fdisk_ask_menu_get_item(struct fdisk_ask *ask, size_t idx, int *key,
+			    const char **name, const char **desc)
+{
+	size_t i;
+	struct ask_menuitem *mi;
+
+	assert(ask);
+	assert(fdisk_is_ask(ask, MENU));
+
+	for (i = 0, mi = ask->data.menu.first; mi; mi = mi->next, i++) {
+		if (i == idx)
+			break;
+	}
+
+	if (!mi)
+		return 1;	/* no more items */
+	if (key)
+		*key = mi->key;
+	if (name)
+		*name = mi->name;
+	if (desc)
+		*desc = mi->desc;
+	return 0;
+}
+
+static void fdisk_ask_menu_reset_items(struct fdisk_ask *ask)
+{
+	struct ask_menuitem *mi;
+
+	assert(ask);
+	assert(fdisk_is_ask(ask, MENU));
+
+	for (mi = ask->data.menu.first; mi; ) {
+		struct ask_menuitem *next = mi->next;
+		free(mi);
+		mi = next;
+	}
+}
+
+/**
+ * fdisk_ask_menu_get_nitems:
+ * @ask: ask instance
+ *
+ * Returns: number of menu items
+ */
+size_t fdisk_ask_menu_get_nitems(struct fdisk_ask *ask)
+{
+	struct ask_menuitem *mi;
+	size_t n;
+
+	assert(ask);
+	assert(fdisk_is_ask(ask, MENU));
+
+	for (n = 0, mi = ask->data.menu.first; mi; mi = mi->next, n++);
+
+	return n;
+}
+
+int fdisk_ask_menu_add_item(struct fdisk_ask *ask, int key,
+			const char *name, const char *desc)
+{
+	struct ask_menuitem *mi;
+
+	assert(ask);
+	assert(fdisk_is_ask(ask, MENU));
+
+	mi = calloc(1, sizeof(*mi));
+	if (!mi)
+		return -ENOMEM;
+	mi->key = key;
+	mi->name = name;
+	mi->desc = desc;
+
+	if (!ask->data.menu.first)
+		ask->data.menu.first = mi;
+	else {
+	        struct ask_menuitem *last = ask->data.menu.first;
+
+		while (last->next)
+			last = last->next;
+		last->next = mi;
+	}
+
+	DBG(ASK, ul_debugobj(ask, "new menu item: %c, \"%s\" (%s)\n", mi->key, mi->name, mi->desc));
+	return 0;
+}
+
+
+/*
+ * print-like
+ */
+
+#define is_print_ask(a) (fdisk_is_ask(a, WARN) || fdisk_is_ask(a, WARNX) || fdisk_is_ask(a, INFO))
+
+/**
+ * fdisk_ask_print_get_errno:
+ * @ask: ask instance
+ *
+ * Returns: error number for warning/error messages
+ */
+int fdisk_ask_print_get_errno(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_print_ask(ask));
+	return ask->data.print.errnum;
+}
+
+int fdisk_ask_print_set_errno(struct fdisk_ask *ask, int errnum)
+{
+	assert(ask);
+	ask->data.print.errnum = errnum;
+	return 0;
+}
+
+/**
+ * fdisk_ask_print_get_mesg:
+ * @ask: ask instance
+ *
+ * Returns: pointer to message
+ */
+const char *fdisk_ask_print_get_mesg(struct fdisk_ask *ask)
+{
+	assert(ask);
+	assert(is_print_ask(ask));
+	return ask->data.print.mesg;
+}
+
+/* does not reallocate the message! */
+int fdisk_ask_print_set_mesg(struct fdisk_ask *ask, const char *mesg)
+{
+	assert(ask);
+	ask->data.print.mesg = mesg;
+	return 0;
+}
+
+static int do_vprint(struct fdisk_context *cxt, int errnum, int type,
+		     const char *fmt, va_list va)
+{
+	struct fdisk_ask *ask;
+	int rc;
+	char *mesg;
+
+	assert(cxt);
+
+	if (vasprintf(&mesg, fmt, va) < 0)
+		return -ENOMEM;
+
+	ask = fdisk_new_ask();
+	if (!ask) {
+		free(mesg);
+		return -ENOMEM;
+	}
+
+	fdisk_ask_set_type(ask, type);
+	fdisk_ask_print_set_mesg(ask, mesg);
+	if (errnum >= 0)
+		fdisk_ask_print_set_errno(ask, errnum);
+	rc = fdisk_do_ask(cxt, ask);
+
+	fdisk_unref_ask(ask);
+	free(mesg);
+	return rc;
+}
+
+/**
+ * fdisk_info:
+ * @cxt: context
+ * @fmt: printf-like formatted string
+ * @...: variable parametrs
+ *
+ * High-level API to print info messages,
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_info(struct fdisk_context *cxt, const char *fmt, ...)
+{
+	int rc;
+	va_list ap;
+
+	assert(cxt);
+	va_start(ap, fmt);
+	rc = do_vprint(cxt, -1, FDISK_ASKTYPE_INFO, fmt, ap);
+	va_end(ap);
+	return rc;
+}
+
+/**
+ * fdisk_info:
+ * @cxt: context
+ * @fmt: printf-like formatted string
+ * @...: variable parametrs
+ *
+ * High-level API to print warning message (errno expected)
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_warn(struct fdisk_context *cxt, const char *fmt, ...)
+{
+	int rc;
+	va_list ap;
+
+	assert(cxt);
+	va_start(ap, fmt);
+	rc = do_vprint(cxt, errno, FDISK_ASKTYPE_WARN, fmt, ap);
+	va_end(ap);
+	return rc;
+}
+
+/**
+ * fdisk_warnx:
+ * @cxt: context
+ * @fmt: printf-like formatted string
+ * @...: variable options
+ *
+ * High-level API to print warning message
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_warnx(struct fdisk_context *cxt, const char *fmt, ...)
+{
+	int rc;
+	va_list ap;
+
+	assert(cxt);
+	va_start(ap, fmt);
+	rc = do_vprint(cxt, -1, FDISK_ASKTYPE_WARNX, fmt, ap);
+	va_end(ap);
+	return rc;
+}
+
+int fdisk_info_new_partition(
+			struct fdisk_context *cxt,
+			int num, fdisk_sector_t start, fdisk_sector_t stop,
+			struct fdisk_parttype *t)
+{
+	int rc;
+	char *str = size_to_human_string(SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE,
+				     (uint64_t)(stop - start + 1) * cxt->sector_size);
+
+	rc = fdisk_info(cxt,
+			_("Created a new partition %d of type '%s' and of size %s."),
+			num, t ? t->name : _("Unknown"), str);
+	free(str);
+	return rc;
+}
+
+#ifdef TEST_PROGRAM
+int test_ranges(struct fdisk_test *ts, int argc, char *argv[])
+{
+	/*                1  -  3,       6,    8, 9,   11    13 */
+	size_t nums[] = { 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1 };
+	size_t numx[] = { 0, 0, 0 };
+	char range[BUFSIZ], *ptr = range;
+	size_t i, len = sizeof(range), begin = 0, run = 0;
+
+	for (i = 0; i < ARRAY_SIZE(nums); i++) {
+		if (!nums[i])
+			continue;
+		ptr = mk_string_list(ptr, &len, &begin, &run, i, 0);
+	}
+	mk_string_list(ptr, &len, &begin, &run, -1, 0);
+	printf("list: '%s'\n", range);
+
+	ptr = range;
+	len = sizeof(range), begin = 0, run = 0;
+	for (i = 0; i < ARRAY_SIZE(numx); i++) {
+		if (!numx[i])
+			continue;
+		ptr = mk_string_list(ptr, &len, &begin, &run, i, 0);
+	}
+	mk_string_list(ptr, &len, &begin, &run, -1, 0);
+	printf("empty list: '%s'\n", range);
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	struct fdisk_test tss[] = {
+	{ "--ranges",  test_ranges,    "generates ranges" },
+	{ NULL }
+	};
+
+	return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libblkid/libfdisk/src/bsd.c b/libblkid/libfdisk/src/bsd.c
new file mode 100644
index 0000000..618a3ee
--- /dev/null
+++ b/libblkid/libfdisk/src/bsd.c
@@ -0,0 +1,992 @@
+/*
+ * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com>
+ *
+ * Based on the original code from fdisk
+ *    written by Bernhard Fastenrath (fasten@informatik.uni-bonn.de)
+ *    with code from the NetBSD disklabel command.
+ *
+ *    Arnaldo Carvalho de Melo <acme@conectiva.com.br>, March 1999
+ *    David Huggins-Daines <dhuggins@linuxcare.com>, January 2000
+ */
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/param.h>
+
+#include "nls.h"
+#include "blkdev.h"
+#include "fdiskP.h"
+#include "pt-mbr.h"
+#include "pt-bsd.h"
+#include "all-io.h"
+
+
+/**
+ * SECTION: bsd
+ * @title: BSD
+ * @short_description: disk label specific functions
+ *
+ */
+
+static const char *bsd_dktypenames[] = {
+	"unknown",
+	"SMD",
+	"MSCP",
+	"old DEC",
+	"SCSI",
+	"ESDI",
+	"ST506",
+	"HP-IB",
+	"HP-FL",
+	"type 9",
+	"floppy",
+	0
+};
+#define BSD_DKMAXTYPES	(ARRAY_SIZE(bsd_dktypenames) - 1)
+
+static struct fdisk_parttype bsd_fstypes[] = {
+        {BSD_FS_UNUSED, "unused"},
+	{BSD_FS_SWAP,   "swap"},
+	{BSD_FS_V6,     "Version 6"},
+	{BSD_FS_V7,     "Version 7"},
+	{BSD_FS_SYSV,   "System V"},
+	{BSD_FS_V71K,   "4.1BSD"},
+	{BSD_FS_V8,     "Eighth Edition"},
+	{BSD_FS_BSDFFS, "4.2BSD"},
+#ifdef __alpha__
+	{BSD_FS_EXT2,   "ext2"},
+#else
+	{BSD_FS_MSDOS,  "MS-DOS"},
+#endif
+	{BSD_FS_BSDLFS, "4.4LFS"},
+	{BSD_FS_OTHER,  "unknown"},
+	{BSD_FS_HPFS,   "HPFS"},
+	{BSD_FS_ISO9660,"ISO-9660"},
+	{BSD_FS_BOOT,   "boot"},
+	{BSD_FS_ADOS,   "ADOS"},
+	{BSD_FS_HFS,    "HFS"},
+	{BSD_FS_ADVFS,	"AdvFS"},
+	{ 0, NULL }
+};
+#define BSD_FSMAXTYPES (ARRAY_SIZE(bsd_fstypes)-1)
+
+/*
+ * in-memory fdisk BSD stuff
+ */
+struct fdisk_bsd_label {
+	struct fdisk_label	head;		/* generic part */
+
+	struct dos_partition *dos_part;		/* parent */
+	struct bsd_disklabel bsd;		/* on disk label */
+#if defined (__alpha__)
+	/* We access this through a u_int64_t * when checksumming */
+	char bsdbuffer[BSD_BBSIZE] __attribute__((aligned(8)));
+#else
+	char bsdbuffer[BSD_BBSIZE];
+#endif
+};
+
+static int bsd_list_disklabel(struct fdisk_context *cxt);
+static int bsd_initlabel(struct fdisk_context *cxt);
+static int bsd_readlabel(struct fdisk_context *cxt);
+static void sync_disks(struct fdisk_context *cxt);
+
+static inline struct fdisk_bsd_label *self_label(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, BSD));
+
+	return (struct fdisk_bsd_label *) cxt->label;
+}
+
+static inline struct bsd_disklabel *self_disklabel(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, BSD));
+
+	return &((struct fdisk_bsd_label *) cxt->label)->bsd;
+}
+
+static struct fdisk_parttype *bsd_partition_parttype(
+		struct fdisk_context *cxt,
+		struct bsd_partition *p)
+{
+	struct fdisk_parttype *t
+		= fdisk_label_get_parttype_from_code(cxt->label, p->p_fstype);
+	return t ? : fdisk_new_unknown_parttype(p->p_fstype, NULL);
+}
+
+
+#if defined (__alpha__)
+static void alpha_bootblock_checksum (char *boot)
+{
+	uint64_t *dp = (uint64_t *) boot, sum = 0;
+	int i;
+
+	for (i = 0; i < 63; i++)
+		sum += dp[i];
+	dp[63] = sum;
+}
+#endif /* __alpha__ */
+
+#define HIDDEN_MASK	0x10
+
+static int is_bsd_partition_type(int type)
+{
+	return (type == MBR_FREEBSD_PARTITION ||
+		type == (MBR_FREEBSD_PARTITION ^ HIDDEN_MASK) ||
+		type == MBR_NETBSD_PARTITION ||
+		type == (MBR_NETBSD_PARTITION ^ HIDDEN_MASK) ||
+		type == MBR_OPENBSD_PARTITION ||
+		type == (MBR_OPENBSD_PARTITION ^ HIDDEN_MASK));
+}
+
+/*
+ * look for DOS partition usable for nested BSD partition table
+ */
+static int bsd_assign_dos_partition(struct fdisk_context *cxt)
+{
+	struct fdisk_bsd_label *l = self_label(cxt);
+	size_t i;
+
+	for (i = 0; i < 4; i++) {
+		fdisk_sector_t ss;
+
+		l->dos_part = fdisk_dos_get_partition(cxt->parent, i);
+
+		if (!l->dos_part || !is_bsd_partition_type(l->dos_part->sys_ind))
+			continue;
+
+		ss = dos_partition_get_start(l->dos_part);
+		if (!ss) {
+			fdisk_warnx(cxt, _("Partition %zd: has invalid starting "
+					   "sector 0."), i + 1);
+			return -1;
+		}
+
+		if (cxt->parent->dev_path) {
+			free(cxt->dev_path);
+			cxt->dev_path = fdisk_partname(
+						cxt->parent->dev_path, i + 1);
+		}
+
+		DBG(LABEL, ul_debug("partition %zu assigned to BSD", i + 1));
+		return 0;
+	}
+
+	fdisk_warnx(cxt, _("There is no *BSD partition on %s."),
+				cxt->parent->dev_path);
+	free(cxt->dev_path);
+	cxt->dev_path = NULL;
+	l->dos_part = NULL;
+	return 1;
+}
+
+static int bsd_probe_label(struct fdisk_context *cxt)
+{
+	int rc = 0;
+
+	if (cxt->parent)
+		rc = bsd_assign_dos_partition(cxt);	/* nested BSD partiotn table */
+	if (!rc)
+		rc = bsd_readlabel(cxt);
+	if (!rc)
+		return 1;	/* found BSD */
+	return 0;		/* not found */
+}
+
+static int set_parttype(
+		struct fdisk_context *cxt,
+		size_t partnum,
+		struct fdisk_parttype *t)
+{
+	struct bsd_partition *p;
+	struct bsd_disklabel *d = self_disklabel(cxt);
+
+	if (partnum >= d->d_npartitions || !t || t->code > UINT8_MAX)
+		return -EINVAL;
+
+	p = &d->d_partitions[partnum];
+	if (t->code == p->p_fstype)
+		return 0;
+
+	p->p_fstype = t->code;
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+static int bsd_add_partition(struct fdisk_context *cxt,
+			     struct fdisk_partition *pa,
+			     size_t *partno)
+{
+	struct fdisk_bsd_label *l = self_label(cxt);
+	struct bsd_disklabel *d = self_disklabel(cxt);
+	size_t i;
+	unsigned int begin = 0, end;
+	int rc = 0;
+
+	rc = fdisk_partition_next_partno(pa, cxt, &i);
+	if (rc)
+		return rc;
+	if (i >= BSD_MAXPARTITIONS)
+		return -ERANGE;
+	if (l->dos_part) {
+		begin = dos_partition_get_start(l->dos_part);
+		end = begin + dos_partition_get_size(l->dos_part) - 1;
+	} else
+		end = d->d_secperunit - 1;
+
+	/*
+	 * First sector
+	 */
+	if (pa && pa->start_follow_default)
+		;
+	else if (pa && fdisk_partition_has_start(pa)) {
+		if (pa->start < begin || pa->start > end)
+			return -ERANGE;
+		begin = pa->start;
+	} else {
+		struct fdisk_ask *ask = fdisk_new_ask();
+
+		if (!ask)
+			return -ENOMEM;
+		fdisk_ask_set_query(ask,
+			fdisk_use_cylinders(cxt) ?
+			_("First cylinder") : _("First sector"));
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+		fdisk_ask_number_set_low(ask, fdisk_cround(cxt, begin));
+		fdisk_ask_number_set_default(ask, fdisk_cround(cxt, begin));
+		fdisk_ask_number_set_high(ask, fdisk_cround(cxt, end));
+
+		rc = fdisk_do_ask(cxt, ask);
+		begin = fdisk_ask_number_get_result(ask);
+		fdisk_unref_ask(ask);
+		if (rc)
+			return rc;
+		if (fdisk_use_cylinders(cxt))
+			begin = (begin - 1) * d->d_secpercyl;
+	}
+
+	/*
+	 * Last sector
+	 */
+	if (pa && pa->end_follow_default)
+		;
+	else if (pa && fdisk_partition_has_size(pa)) {
+		if (begin + pa->size > end)
+			return -ERANGE;
+		end = begin + pa->size - 1ULL;
+	} else {
+		/* ask user by dialog */
+		struct fdisk_ask *ask = fdisk_new_ask();
+
+		if (!ask)
+			return -ENOMEM;
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+		if (fdisk_use_cylinders(cxt)) {
+			fdisk_ask_set_query(ask, _("Last cylinder, +cylinders or +size{K,M,G,T,P}"));
+			fdisk_ask_number_set_unit(ask,
+				     cxt->sector_size *
+				     fdisk_get_units_per_sector(cxt));
+		} else {
+			fdisk_ask_set_query(ask, _("Last sector, +sectors or +size{K,M,G,T,P}"));
+			fdisk_ask_number_set_unit(ask,cxt->sector_size);
+		}
+
+		fdisk_ask_number_set_low(ask, fdisk_cround(cxt, begin));
+		fdisk_ask_number_set_default(ask, fdisk_cround(cxt, end));
+		fdisk_ask_number_set_high(ask, fdisk_cround(cxt, end));
+		fdisk_ask_number_set_base(ask, fdisk_cround(cxt, begin));
+
+		rc = fdisk_do_ask(cxt, ask);
+		end = fdisk_ask_number_get_result(ask);
+		fdisk_unref_ask(ask);
+		if (rc)
+			return rc;
+		if (fdisk_use_cylinders(cxt))
+			end = end * d->d_secpercyl - 1;
+	}
+
+	d->d_partitions[i].p_size   = end - begin + 1;
+	d->d_partitions[i].p_offset = begin;
+	d->d_partitions[i].p_fstype = BSD_FS_UNUSED;
+
+	if (i >= d->d_npartitions)
+		d->d_npartitions = i + 1;
+	cxt->label->nparts_cur = d->d_npartitions;
+
+	if (pa && pa->type)
+		set_parttype(cxt, i, pa->type);
+
+	fdisk_label_set_changed(cxt->label, 1);
+	if (partno)
+		*partno = i;
+	return 0;
+}
+
+static int bsd_set_partition(struct fdisk_context *cxt, size_t n,
+			     struct fdisk_partition *pa)
+{
+	struct bsd_partition *p;
+	struct fdisk_bsd_label *l = self_label(cxt);
+	struct bsd_disklabel *d = self_disklabel(cxt);
+
+	if (n >= d->d_npartitions)
+		return -EINVAL;
+
+	p = &d->d_partitions[n];
+
+	/* we have to stay within parental DOS partition */
+	if (l->dos_part && (fdisk_partition_has_start(pa) ||
+			    fdisk_partition_has_size(pa))) {
+
+		fdisk_sector_t dosbegin = dos_partition_get_start(l->dos_part);
+		fdisk_sector_t dosend = dosbegin + dos_partition_get_size(l->dos_part) - 1;
+		fdisk_sector_t begin = fdisk_partition_has_start(pa) ? pa->start : p->p_offset;
+		fdisk_sector_t end = begin + (fdisk_partition_has_size(pa) ? pa->size : p->p_size) - 1;
+
+		if (begin < dosbegin || begin > dosend)
+			return -ERANGE;
+		if (end < dosbegin || end > dosend)
+			return -ERANGE;
+	}
+
+	if (pa->type) {
+		int rc = set_parttype(cxt, n, pa->type);
+		if (rc)
+			return rc;
+	}
+
+	if (fdisk_partition_has_start(pa))
+		d->d_partitions[n].p_offset = pa->start;
+	if (fdisk_partition_has_size(pa))
+		d->d_partitions[n].p_size = pa->size;
+
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+
+/* Returns 0 on success, < 0 on error. */
+static int bsd_create_disklabel(struct fdisk_context *cxt)
+{
+	int rc, yes = 0;
+	struct bsd_disklabel *d = self_disklabel(cxt);
+
+	fdisk_info(cxt, _("The device %s does not contain BSD disklabel."), cxt->dev_path);
+	rc = fdisk_ask_yesno(cxt,
+			_("Do you want to create a BSD disklabel?"),
+			&yes);
+	if (rc)
+		return rc;
+	if (!yes)
+		return 1;
+	if (cxt->parent) {
+		rc = bsd_assign_dos_partition(cxt);
+		if (rc == 1)
+			/* not found DOS partition usable for BSD label */
+			rc = -EINVAL;
+	}
+	if (rc)
+		return rc;
+
+	rc = bsd_initlabel(cxt);
+	if (!rc) {
+		int org = fdisk_is_details(cxt);
+
+		cxt->label->nparts_cur = d->d_npartitions;
+		cxt->label->nparts_max = BSD_MAXPARTITIONS;
+
+		fdisk_enable_details(cxt, 1);
+		bsd_list_disklabel(cxt);
+		fdisk_enable_details(cxt, org);
+	}
+
+	return rc;
+}
+
+static int bsd_delete_part(
+		struct fdisk_context *cxt,
+		size_t partnum)
+{
+	struct bsd_disklabel *d = self_disklabel(cxt);
+
+	d->d_partitions[partnum].p_size   = 0;
+	d->d_partitions[partnum].p_offset = 0;
+	d->d_partitions[partnum].p_fstype = BSD_FS_UNUSED;
+
+	if (d->d_npartitions == partnum + 1)
+		while (!d->d_partitions[d->d_npartitions - 1].p_size)
+			d->d_npartitions--;
+
+	cxt->label->nparts_cur = d->d_npartitions;
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+static int bsd_list_disklabel(struct fdisk_context *cxt)
+{
+	struct bsd_disklabel *d = self_disklabel(cxt);
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, BSD));
+
+	if (fdisk_is_details(cxt)) {
+		fdisk_info(cxt, "# %s:", cxt->dev_path);
+
+		if ((unsigned) d->d_type < BSD_DKMAXTYPES)
+			fdisk_info(cxt, _("type: %s"), bsd_dktypenames[d->d_type]);
+		else
+			fdisk_info(cxt, _("type: %d"), d->d_type);
+
+		fdisk_info(cxt, _("disk: %.*s"), (int) sizeof(d->d_typename), d->d_typename);
+		fdisk_info(cxt, _("label: %.*s"), (int) sizeof(d->d_packname), d->d_packname);
+
+		fdisk_info(cxt, _("flags: %s"),
+			d->d_flags & BSD_D_REMOVABLE ? _(" removable") :
+			d->d_flags & BSD_D_ECC ? _(" ecc") :
+			d->d_flags & BSD_D_BADSECT ? _(" badsect") : "");
+
+		/* On various machines the fields of *lp are short/int/long */
+		/* In order to avoid problems, we cast them all to long. */
+		fdisk_info(cxt, _("bytes/sector: %ld"), (long) d->d_secsize);
+		fdisk_info(cxt, _("sectors/track: %ld"), (long) d->d_nsectors);
+		fdisk_info(cxt, _("tracks/cylinder: %ld"), (long) d->d_ntracks);
+		fdisk_info(cxt, _("sectors/cylinder: %ld"), (long) d->d_secpercyl);
+		fdisk_info(cxt, _("cylinders: %ld"), (long) d->d_ncylinders);
+		fdisk_info(cxt, _("rpm: %d"), d->d_rpm);
+		fdisk_info(cxt, _("interleave: %d"), d->d_interleave);
+		fdisk_info(cxt, _("trackskew: %d"), d->d_trackskew);
+		fdisk_info(cxt, _("cylinderskew: %d"), d->d_cylskew);
+		fdisk_info(cxt, _("headswitch: %ld (milliseconds)"), (long) d->d_headswitch);
+		fdisk_info(cxt, _("track-to-track seek: %ld (milliseconds)"), (long) d->d_trkseek);
+	}
+
+	fdisk_info(cxt, _("partitions: %d"), d->d_npartitions);
+
+	return 0;
+}
+
+static int bsd_get_partition(struct fdisk_context *cxt, size_t n,
+			     struct fdisk_partition *pa)
+{
+	struct bsd_partition *p;
+	struct bsd_disklabel *d = self_disklabel(cxt);
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, BSD));
+
+	if (n >= d->d_npartitions)
+		return -EINVAL;
+
+	p = &d->d_partitions[n];
+
+	pa->used = p->p_size ? 1 : 0;
+	if (!pa->used)
+		return 0;
+
+	if (fdisk_use_cylinders(cxt) && d->d_secpercyl) {
+		pa->start_post = p->p_offset % d->d_secpercyl ? '*' : ' ';
+		pa->end_post = (p->p_offset + p->p_size) % d->d_secpercyl ? '*' : ' ';
+	}
+
+	pa->start = p->p_offset;
+	pa->size = p->p_size;
+	pa->type = bsd_partition_parttype(cxt, p);
+
+	if (p->p_fstype == BSD_FS_UNUSED || p->p_fstype == BSD_FS_BSDFFS) {
+		pa->fsize = p->p_fsize;
+		pa->bsize = p->p_fsize * p->p_frag;
+	}
+	if (p->p_fstype == BSD_FS_BSDFFS)
+		pa->cpg = p->p_cpg;
+
+	return 0;
+}
+
+static uint32_t ask_uint32(struct fdisk_context *cxt,
+		uint32_t dflt, char *mesg)
+{
+	uintmax_t res;
+
+	if (fdisk_ask_number(cxt, min(dflt, (uint32_t) 1), dflt,
+				UINT32_MAX, mesg, &res) == 0)
+		return res;
+	return dflt;
+}
+
+static uint16_t ask_uint16(struct fdisk_context *cxt,
+		uint16_t dflt, char *mesg)
+{
+	uintmax_t res;
+
+	if (fdisk_ask_number(cxt, min(dflt, (uint16_t) 1),
+				dflt, UINT16_MAX, mesg, &res) == 0)
+		return res;
+	return dflt;
+}
+
+/**
+ * fdisk_bsd_edit_disklabel:
+ * @cxt: context
+ *
+ * Edits fields in BSD disk label.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_bsd_edit_disklabel(struct fdisk_context *cxt)
+{
+	struct bsd_disklabel *d = self_disklabel(cxt);
+	uintmax_t res;
+
+#if defined (__alpha__) || defined (__ia64__)
+	if (fdisk_ask_number(cxt, DEFAULT_SECTOR_SIZE, d->d_secsize,
+			     UINT32_MAX, _("bytes/sector"), &res) == 0)
+		d->d_secsize = res;
+
+	d->d_nsectors = ask_uint32(cxt, d->d_nsectors, _("sectors/track"));
+	d->d_ntracks = ask_uint32(cxt, d->d_ntracks, _("tracks/cylinder"));
+	d->d_ncylinders = ask_uint32(cxt, d->d_ncylinders  ,_("cylinders"));
+#endif
+	if (fdisk_ask_number(cxt, 1, d->d_nsectors * d->d_ntracks,
+			     d->d_nsectors * d->d_ntracks,
+			     _("sectors/cylinder"), &res) == 0)
+		d->d_secpercyl = res;
+
+	d->d_rpm = ask_uint16(cxt, d->d_rpm, _("rpm"));
+	d->d_interleave = ask_uint16(cxt, d->d_interleave, _("interleave"));
+	d->d_trackskew = ask_uint16(cxt, d->d_trackskew, _("trackskew"));
+	d->d_cylskew = ask_uint16(cxt, d->d_cylskew, _("cylinderskew"));
+
+	d->d_headswitch = ask_uint32(cxt, d->d_headswitch, _("headswitch"));
+	d->d_trkseek = ask_uint32(cxt, d->d_trkseek, _("track-to-track seek"));
+
+	d->d_secperunit = d->d_secpercyl * d->d_ncylinders;
+	return 0;
+}
+
+static int bsd_get_bootstrap(struct fdisk_context *cxt,
+			char *path, void *ptr, int size)
+{
+	int fd;
+
+	if ((fd = open(path, O_RDONLY)) < 0) {
+		fdisk_warn(cxt, _("cannot open %s"), path);
+		return -errno;
+	}
+
+	if (read_all(fd, ptr, size) != size) {
+		fdisk_warn(cxt, _("cannot read %s"), path);
+		close(fd);
+		return -errno;
+	}
+
+	fdisk_info(cxt, _("The bootstrap file %s successfully loaded."), path);
+	close (fd);
+	return 0;
+}
+
+/**
+ * fdisk_bsd_write_bootstrap:
+ * @cxt: context
+ *
+ * Install bootstrap file to the BSD device
+ */
+int fdisk_bsd_write_bootstrap(struct fdisk_context *cxt)
+{
+	struct bsd_disklabel dl, *d = self_disklabel(cxt);
+	struct fdisk_bsd_label *l = self_label(cxt);
+	char *name = d->d_type == BSD_DTYPE_SCSI ? "sd" : "wd";
+	char buf[BUFSIZ];
+	char *res, *dp, *p;
+	int rc;
+	fdisk_sector_t sector;
+
+	snprintf(buf, sizeof(buf),
+		_("Bootstrap: %1$sboot -> boot%1$s (default %1$s)"),
+		name);
+	rc = fdisk_ask_string(cxt, buf, &res);
+	if (rc)
+		goto done;
+	if (res && *res)
+		name = res;
+
+	snprintf(buf, sizeof(buf), "%s/%sboot", BSD_LINUX_BOOTDIR, name);
+	rc = bsd_get_bootstrap(cxt, buf, l->bsdbuffer,	(int) d->d_secsize);
+	if (rc)
+		goto done;
+
+	/* We need a backup of the disklabel (might have changed). */
+	dp = &l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE];
+	memmove(&dl, dp, sizeof(struct bsd_disklabel));
+
+	/* The disklabel will be overwritten by 0's from bootxx anyway */
+	memset(dp, 0, sizeof(struct bsd_disklabel));
+
+	snprintf(buf, sizeof(buf), "%s/boot%s", BSD_LINUX_BOOTDIR, name);
+	rc = bsd_get_bootstrap(cxt, buf,
+			&l->bsdbuffer[d->d_secsize],
+			(int) d->d_bbsize - d->d_secsize);
+	if (rc)
+		goto done;
+
+	/* check end of the bootstrap */
+	for (p = dp; p < dp + sizeof(struct bsd_disklabel); p++) {
+		if (!*p)
+			continue;
+		fdisk_warnx(cxt, _("Bootstrap overlaps with disklabel!"));
+		return -EINVAL;
+	}
+
+	/* move disklabel back */
+	memmove(dp, &dl, sizeof(struct bsd_disklabel));
+
+	sector = 0;
+	if (l->dos_part)
+		sector = dos_partition_get_start(l->dos_part);
+#if defined (__alpha__)
+	alpha_bootblock_checksum(l->bsdbuffer);
+#endif
+	if (lseek(cxt->dev_fd, (off_t) sector * DEFAULT_SECTOR_SIZE, SEEK_SET) == -1) {
+		fdisk_warn(cxt, _("seek on %s failed"), cxt->dev_path);
+		rc = -errno;
+		goto done;
+	}
+	if (write_all(cxt->dev_fd, l->bsdbuffer, BSD_BBSIZE)) {
+		fdisk_warn(cxt, _("cannot write %s"), cxt->dev_path);
+		rc = -errno;
+		goto done;
+	}
+
+	fdisk_info(cxt, _("Bootstrap installed on %s."), cxt->dev_path);
+	sync_disks(cxt);
+
+	rc = 0;
+done:
+	free(res);
+	return rc;
+}
+
+static unsigned short bsd_dkcksum (struct bsd_disklabel *lp)
+{
+	unsigned short *start, *end;
+	unsigned short sum = 0;
+
+	start = (unsigned short *) lp;
+	end = (unsigned short *) &lp->d_partitions[lp->d_npartitions];
+	while (start < end)
+		sum ^= *start++;
+	return sum;
+}
+
+static int bsd_initlabel (struct fdisk_context *cxt)
+{
+	struct fdisk_bsd_label *l = self_label(cxt);
+	struct bsd_disklabel *d = self_disklabel(cxt);
+	struct bsd_partition *pp;
+
+	memset (d, 0, sizeof (struct bsd_disklabel));
+
+	d -> d_magic = BSD_DISKMAGIC;
+
+	if (strncmp (cxt->dev_path, "/dev/sd", 7) == 0)
+		d -> d_type = BSD_DTYPE_SCSI;
+	else
+		d -> d_type = BSD_DTYPE_ST506;
+
+#if !defined (__alpha__)
+	d -> d_flags = BSD_D_DOSPART;
+#else
+	d -> d_flags = 0;
+#endif
+	d -> d_secsize = DEFAULT_SECTOR_SIZE;		/* bytes/sector  */
+	d -> d_nsectors = cxt->geom.sectors;		/* sectors/track */
+	d -> d_ntracks = cxt->geom.heads;		/* tracks/cylinder (heads) */
+	d -> d_ncylinders = cxt->geom.cylinders;
+	d -> d_secpercyl  = cxt->geom.sectors * cxt->geom.heads;/* sectors/cylinder */
+	if (d -> d_secpercyl == 0)
+		d -> d_secpercyl = 1;		/* avoid segfaults */
+	d -> d_secperunit = d -> d_secpercyl * d -> d_ncylinders;
+
+	d -> d_rpm = 3600;
+	d -> d_interleave = 1;
+	d -> d_trackskew = 0;
+	d -> d_cylskew = 0;
+	d -> d_headswitch = 0;
+	d -> d_trkseek = 0;
+
+	d -> d_magic2 = BSD_DISKMAGIC;
+	d -> d_bbsize = BSD_BBSIZE;
+	d -> d_sbsize = BSD_SBSIZE;
+
+	if (l->dos_part) {
+		d->d_npartitions = 4;
+
+		pp = &d->d_partitions[2];	/* Partition C should be the NetBSD partition */
+		pp->p_offset = dos_partition_get_start(l->dos_part);
+		pp->p_size   = dos_partition_get_size(l->dos_part);
+		pp->p_fstype = BSD_FS_UNUSED;
+
+		pp = &d -> d_partitions[3];	/* Partition D should be the whole disk */
+		pp->p_offset = 0;
+		pp->p_size   = d->d_secperunit;
+		pp->p_fstype = BSD_FS_UNUSED;
+	} else {
+		d->d_npartitions = 3;
+
+		pp = &d->d_partitions[2];	/* Partition C should be the whole disk */
+		pp->p_offset = 0;
+		pp->p_size   = d->d_secperunit;
+		pp->p_fstype = BSD_FS_UNUSED;
+	}
+
+	return 0;
+}
+
+/*
+ * Read a bsd_disklabel from sector 0 or from the starting sector of p.
+ * If it has the right magic, return 0.
+ */
+static int bsd_readlabel(struct fdisk_context *cxt)
+{
+	struct fdisk_bsd_label *l;
+	struct bsd_disklabel *d;
+	int t;
+	off_t offset = 0;
+
+	l = self_label(cxt);
+	d = self_disklabel(cxt);
+
+	if (l->dos_part)
+		/* BSD is nested within DOS partition, get the begin of the
+		 * partition. Note that DOS uses native sector size. */
+		offset = dos_partition_get_start(l->dos_part) * cxt->sector_size;
+
+	if (lseek(cxt->dev_fd, offset, SEEK_SET) == -1)
+		return -1;
+	if (read_all(cxt->dev_fd, l->bsdbuffer, sizeof(l->bsdbuffer)) < 0)
+		return errno ? -errno : -1;
+
+	/* The offset to begin of the disk label. Note that BSD uses
+	 * 512-byte (default) sectors. */
+	memmove(d, &l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE
+			      + BSD_LABELOFFSET], sizeof(*d));
+
+	if (d->d_magic != BSD_DISKMAGIC || d->d_magic2 != BSD_DISKMAGIC) {
+		DBG(LABEL, ul_debug("not found magic"));
+		return -1;
+	}
+
+	for (t = d->d_npartitions; t < BSD_MAXPARTITIONS; t++) {
+		d->d_partitions[t].p_size   = 0;
+		d->d_partitions[t].p_offset = 0;
+		d->d_partitions[t].p_fstype = BSD_FS_UNUSED;
+	}
+
+	if (d->d_npartitions > BSD_MAXPARTITIONS)
+		fdisk_warnx(cxt, ("Too many partitions (%d, maximum is %d)."),
+				d->d_npartitions, BSD_MAXPARTITIONS);
+
+	/* let's follow in-PT geometry */
+	cxt->geom.sectors = d->d_nsectors;
+	cxt->geom.heads = d->d_ntracks;
+	cxt->geom.cylinders = d->d_ncylinders;
+
+	cxt->label->nparts_cur = d->d_npartitions;
+	cxt->label->nparts_max = BSD_MAXPARTITIONS;
+	DBG(LABEL, ul_debug("read BSD label"));
+	return 0;
+}
+
+static int bsd_write_disklabel(struct fdisk_context *cxt)
+{
+	off_t offset = 0;
+	struct fdisk_bsd_label *l = self_label(cxt);
+	struct bsd_disklabel *d = self_disklabel(cxt);
+
+
+	if (l->dos_part)
+		offset = dos_partition_get_start(l->dos_part) * cxt->sector_size;
+
+	d->d_checksum = 0;
+	d->d_checksum = bsd_dkcksum(d);
+
+	/* Update label within boot block. */
+	memmove(&l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE
+			   + BSD_LABELOFFSET], d, sizeof(*d));
+
+#if defined (__alpha__) && BSD_LABELSECTOR == 0
+	/* Write the checksum to the end of the first sector. */
+	alpha_bootblock_checksum(l->bsdbuffer);
+#endif
+	if (lseek(cxt->dev_fd, offset, SEEK_SET) == -1) {
+		fdisk_warn(cxt, _("seek on %s failed"), cxt->dev_path);
+		return -errno;
+	}
+	if (write_all(cxt->dev_fd, l->bsdbuffer, sizeof(l->bsdbuffer))) {
+		fdisk_warn(cxt, _("cannot write %s"), cxt->dev_path);
+		return -errno;
+	}
+	sync_disks(cxt);
+
+	fdisk_info(cxt, _("Disklabel written to %s."), cxt->dev_path);
+	return 0;
+}
+
+static void sync_disks(struct fdisk_context *cxt)
+{
+	fdisk_info(cxt, _("Syncing disks."));
+	sync();
+}
+
+static int bsd_translate_fstype (int linux_type)
+{
+	switch (linux_type) {
+	case 0x01: /* DOS 12-bit FAT   */
+	case 0x04: /* DOS 16-bit <32M  */
+	case 0x06: /* DOS 16-bit >=32M */
+	case 0xe1: /* DOS access       */
+	case 0xe3: /* DOS R/O          */
+#if !defined (__alpha__)
+	case 0xf2: /* DOS secondary    */
+		return BSD_FS_MSDOS;
+#endif
+	case 0x07: /* OS/2 HPFS        */
+		return BSD_FS_HPFS;
+	default:
+		break;
+	}
+
+	return BSD_FS_OTHER;
+}
+
+/**
+ * fdisk_bsd_link_partition:
+ * @cxt: context
+ *
+ * Links partition from parent (DOS) to nested BSD partition table.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_bsd_link_partition(struct fdisk_context *cxt)
+{
+	size_t k, i;
+	int rc;
+	struct dos_partition *p;
+	struct bsd_disklabel *d = self_disklabel(cxt);
+
+	if (!cxt->parent || !fdisk_is_label(cxt->parent, DOS)) {
+		fdisk_warnx(cxt, _("BSD label is not nested within a DOS partition."));
+		return -EINVAL;
+	}
+
+	/* ask for DOS partition */
+	rc = fdisk_ask_partnum(cxt->parent, &k, FALSE);
+	if (rc)
+		return rc;
+	/* ask for BSD partition */
+	rc = fdisk_ask_partnum(cxt, &i, TRUE);
+	if (rc)
+		return rc;
+
+	if (i >= BSD_MAXPARTITIONS)
+		return -EINVAL;
+
+	p = fdisk_dos_get_partition(cxt->parent, k);
+
+	d->d_partitions[i].p_size   = dos_partition_get_size(p);
+	d->d_partitions[i].p_offset = dos_partition_get_start(p);
+	d->d_partitions[i].p_fstype = bsd_translate_fstype(p->sys_ind);
+
+	if (i >= d->d_npartitions)
+		d->d_npartitions = i + 1;
+
+	cxt->label->nparts_cur = d->d_npartitions;
+	fdisk_label_set_changed(cxt->label, 1);
+
+	fdisk_info(cxt, _("BSD partition '%c' linked to DOS partition %zu."),
+			'a' + (int) i, k + 1);
+	return 0;
+}
+
+
+static int bsd_partition_is_used(
+		struct fdisk_context *cxt,
+		size_t partnum)
+{
+	struct bsd_disklabel *d = self_disklabel(cxt);
+
+	if (partnum >= BSD_MAXPARTITIONS)
+		return 0;
+
+	return d->d_partitions[partnum].p_size ? 1 : 0;
+}
+
+
+static const struct fdisk_label_operations bsd_operations =
+{
+	.probe		= bsd_probe_label,
+	.list		= bsd_list_disklabel,
+	.write		= bsd_write_disklabel,
+	.create		= bsd_create_disklabel,
+
+	.del_part	= bsd_delete_part,
+	.get_part	= bsd_get_partition,
+	.set_part	= bsd_set_partition,
+	.add_part	= bsd_add_partition,
+
+	.part_is_used   = bsd_partition_is_used,
+};
+
+static const struct fdisk_field bsd_fields[] =
+{
+	{ FDISK_FIELD_DEVICE,	N_("Slice"),	  1,	0 },
+	{ FDISK_FIELD_START,	N_("Start"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_END,	N_("End"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SECTORS,	N_("Sectors"),    5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_CYLINDERS,N_("Cylinders"),  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SIZE,	N_("Size"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_TYPE,	N_("Type"),	  8,	0 },
+	{ FDISK_FIELD_FSIZE,	N_("Fsize"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_BSIZE,	N_("Bsize"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_CPG,	N_("Cpg"),	  5,	FDISK_FIELDFL_NUMBER }
+};
+
+/*
+ * allocates BSD label driver
+ */
+struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt)
+{
+	struct fdisk_label *lb;
+	struct fdisk_bsd_label *bsd;
+
+	assert(cxt);
+
+	bsd = calloc(1, sizeof(*bsd));
+	if (!bsd)
+		return NULL;
+
+	/* initialize generic part of the driver */
+	lb = (struct fdisk_label *) bsd;
+	lb->name = "bsd";
+	lb->id = FDISK_DISKLABEL_BSD;
+	lb->op = &bsd_operations;
+	lb->parttypes = bsd_fstypes;
+	lb->nparttypes = ARRAY_SIZE(bsd_fstypes) - 1;
+
+	lb->fields = bsd_fields;
+	lb->nfields = ARRAY_SIZE(bsd_fields);
+
+	lb->flags |= FDISK_LABEL_FL_INCHARS_PARTNO;
+	lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+
+	return lb;
+}
diff --git a/libblkid/libfdisk/src/context.c b/libblkid/libfdisk/src/context.c
new file mode 100644
index 0000000..2a4d377
--- /dev/null
+++ b/libblkid/libfdisk/src/context.c
@@ -0,0 +1,1017 @@
+#ifdef HAVE_LIBBLKID
+# include <blkid.h>
+#endif
+
+#include "fdiskP.h"
+
+
+/**
+ * SECTION: context
+ * @title: Context
+ * @short_description: stores info about device, labels etc.
+ *
+ * The library distinguish between three types of partitioning objects.
+ *
+ * on-disk data
+ *    - disk label specific
+ *    - probed and read  by disklabel drivers when assign device to the context
+ *      or when switch to another disk label type
+ *    - only fdisk_write_disklabel() modify on-disk data
+ *
+ * in-memory data
+ *    - generic data and disklabel specific data stored in struct fdisk_label
+ *    - all partitioning operations are based on in-memory data only
+ *
+ * struct fdisk_partition
+ *    - provides abstraction to present partitions to users
+ *    - fdisk_partition is possible to gather to fdisk_table container
+ *    - used as unified template for new partitions
+ *    - the struct fdisk_partition is always completely independent object and
+ *      any change to the object has no effect to in-memory (or on-disk) label data
+ */
+
+/**
+ * fdisk_new_context:
+ *
+ * Returns: newly allocated libfdisk handler
+ */
+struct fdisk_context *fdisk_new_context(void)
+{
+	struct fdisk_context *cxt;
+
+	cxt = calloc(1, sizeof(*cxt));
+	if (!cxt)
+		return NULL;
+
+	DBG(CXT, ul_debugobj(cxt, "alloc"));
+	cxt->dev_fd = -1;
+	cxt->refcount = 1;
+
+	/*
+	 * Allocate label specific structs.
+	 *
+	 * This is necessary (for example) to store label specific
+	 * context setting.
+	 */
+	cxt->labels[ cxt->nlabels++ ] = fdisk_new_gpt_label(cxt);
+	cxt->labels[ cxt->nlabels++ ] = fdisk_new_dos_label(cxt);
+	cxt->labels[ cxt->nlabels++ ] = fdisk_new_bsd_label(cxt);
+	cxt->labels[ cxt->nlabels++ ] = fdisk_new_sgi_label(cxt);
+	cxt->labels[ cxt->nlabels++ ] = fdisk_new_sun_label(cxt);
+
+	return cxt;
+}
+
+static int init_nested_from_parent(struct fdisk_context *cxt, int isnew)
+{
+	struct fdisk_context *parent;
+
+	assert(cxt);
+	assert(cxt->parent);
+
+	parent = cxt->parent;
+
+	cxt->alignment_offset = parent->alignment_offset;
+	cxt->ask_cb =		parent->ask_cb;
+	cxt->ask_data =		parent->ask_data;
+	cxt->dev_fd =		parent->dev_fd;
+	cxt->first_lba =        parent->first_lba;
+	cxt->firstsector_bufsz = parent->firstsector_bufsz;
+	cxt->firstsector =	parent->firstsector;
+	cxt->geom =		parent->geom;
+	cxt->grain =            parent->grain;
+	cxt->io_size =          parent->io_size;
+	cxt->last_lba =		parent->last_lba;
+	cxt->min_io_size =      parent->min_io_size;
+	cxt->optimal_io_size =  parent->optimal_io_size;
+	cxt->phy_sector_size =  parent->phy_sector_size;
+	cxt->readonly =		parent->readonly;
+	cxt->script =		parent->script;
+	fdisk_ref_script(cxt->script);
+	cxt->sector_size =      parent->sector_size;
+	cxt->total_sectors =    parent->total_sectors;
+	cxt->user_geom =	parent->user_geom;
+	cxt->user_log_sector =	parent->user_log_sector;
+	cxt->user_pyh_sector =  parent->user_pyh_sector;
+
+	/* parent <--> nested independent setting, initialize for new nested 
+	 * contexts only */
+	if (isnew) {
+		cxt->listonly =	parent->listonly;
+		cxt->display_details =	parent->display_details;
+		cxt->display_in_cyl_units = parent->display_in_cyl_units;
+	}
+
+	free(cxt->dev_path);
+	cxt->dev_path = NULL;
+
+	if (parent->dev_path) {
+		cxt->dev_path =	strdup(parent->dev_path);
+		if (!cxt->dev_path)
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/**
+ * fdisk_new_nested_context:
+ * @parent: parental context
+ * @name: optional label name (e.g. "bsd")
+ *
+ * Create a new nested fdisk context for nested disk labels (e.g. BSD or PMBR).
+ * The function also probes for the nested label on the device if device is
+ * already assigned to parent.
+ *
+ * The new context is initialized according to @parent and both context shares
+ * some settings and file descriptor to the device. The child propagate some
+ * changes (like fdisk_assign_device()) to parent, but it does not work
+ * vice-versa. The behavior is undefined if you assign another device to
+ * parent.
+ *
+ * Returns: new context for nested partition table.
+ */
+struct fdisk_context *fdisk_new_nested_context(struct fdisk_context *parent,
+				const char *name)
+{
+	struct fdisk_context *cxt;
+	struct fdisk_label *lb = NULL;
+
+	assert(parent);
+
+	cxt = calloc(1, sizeof(*cxt));
+	if (!cxt)
+		return NULL;
+
+	DBG(CXT, ul_debugobj(parent, "alloc nested [%p]", cxt));
+	cxt->refcount = 1;
+
+	fdisk_ref_context(parent);
+	cxt->parent = parent;
+
+	if (init_nested_from_parent(cxt, 1) != 0)
+		return NULL;
+
+	if (name) {
+		if (strcmp(name, "bsd") == 0)
+			lb = cxt->labels[ cxt->nlabels++ ] = fdisk_new_bsd_label(cxt);
+		else if (strcmp(name, "dos") == 0)
+			lb = cxt->labels[ cxt->nlabels++ ] = fdisk_new_dos_label(cxt);
+	}
+
+	if (lb && parent->dev_fd >= 0) {
+		DBG(CXT, ul_debugobj(cxt, "probing for nested %s", lb->name));
+
+		cxt->label = lb;
+
+		if (lb->op->probe(cxt) == 1)
+			__fdisk_switch_label(cxt, lb);
+		else {
+			DBG(CXT, ul_debugobj(cxt, "not found %s label", lb->name));
+			if (lb->op->deinit)
+				lb->op->deinit(lb);
+			cxt->label = NULL;
+		}
+	}
+
+	return cxt;
+}
+
+
+/**
+ * fdisk_ref_context:
+ * @cxt: context pointer
+ *
+ * Increments reference counter.
+ */
+void fdisk_ref_context(struct fdisk_context *cxt)
+{
+	if (cxt)
+		cxt->refcount++;
+}
+
+/**
+ * fdisk_get_label:
+ * @cxt: context instance
+ * @name: label name (e.g. "gpt")
+ *
+ * If no @name specified then returns the current context label.
+ *
+ * The label is allocated and maintained within the context #cxt. There is
+ * nothing like reference counting for labels, you cannot delallocate the
+ * label.
+ *
+ * Returns: label struct or NULL in case of error.
+ */
+struct fdisk_label *fdisk_get_label(struct fdisk_context *cxt, const char *name)
+{
+	size_t i;
+
+	assert(cxt);
+
+	if (!name)
+		return cxt->label;
+
+	for (i = 0; i < cxt->nlabels; i++)
+		if (cxt->labels[i]
+		    && strcmp(cxt->labels[i]->name, name) == 0)
+			return cxt->labels[i];
+
+	DBG(CXT, ul_debugobj(cxt, "failed to found %s label driver", name));
+	return NULL;
+}
+
+/**
+ * fdisk_next_label:
+ * @cxt: context instance
+ * @lb: returns pointer to the next label
+ *
+ * <informalexample>
+ *   <programlisting>
+ *      // print all supported labels
+ *	struct fdisk_context *cxt = fdisk_new_context();
+ *	struct fdisk_label *lb = NULL;
+ *
+ *	while (fdisk_next_label(cxt, &lb) == 0)
+ *		print("label name: %s\n", fdisk_label_get_name(lb));
+ *	fdisk_unref_context(cxt);
+ *   </programlisting>
+ * </informalexample>
+ *
+ * Returns: <0 in case of error, 0 on success, 1 at the end.
+ */
+int fdisk_next_label(struct fdisk_context *cxt, struct fdisk_label **lb)
+{
+	size_t i;
+	struct fdisk_label *res = NULL;
+
+	if (!lb || !cxt)
+		return -EINVAL;
+
+	if (!*lb)
+		res = cxt->labels[0];
+	else {
+		for (i = 1; i < cxt->nlabels; i++) {
+			if (*lb == cxt->labels[i - 1]) {
+				res = cxt->labels[i];
+				break;
+			}
+		}
+	}
+
+	*lb = res;
+	return res ? 0 : 1;
+}
+
+/**
+ * fdisk_get_nlabels:
+ * @cxt: context
+ *
+ * Returns: number of supported label types
+ */
+size_t fdisk_get_nlabels(struct fdisk_context *cxt)
+{
+	return cxt ? cxt->nlabels : 0;
+}
+
+int __fdisk_switch_label(struct fdisk_context *cxt, struct fdisk_label *lb)
+{
+	if (!lb || !cxt)
+		return -EINVAL;
+	if (lb->disabled) {
+		DBG(CXT, ul_debugobj(cxt, "*** attempt to switch to disabled label %s -- ignore!", lb->name));
+		return -EINVAL;
+	}
+	cxt->label = lb;
+	DBG(CXT, ul_debugobj(cxt, "--> switching context to %s!", lb->name));
+	return 0;
+}
+
+/**
+ * fdisk_has_label:
+ * @cxt: fdisk context
+ *
+ * Returns: return 1 if there is label on the device.
+ */
+int fdisk_has_label(struct fdisk_context *cxt)
+{
+	return cxt && cxt->label;
+}
+
+/**
+ * fdisk_get_npartitions:
+ * @cxt: context
+ *
+ * The maximal number of the partitions depends on disklabel and does not
+ * have to describe the real limit of PT.
+ *
+ * For example the limit for MBR without extend partition is 4, with extended
+ * partition it's unlimited (so the function returns the current number of all
+ * partitions in this case).
+ *
+ * And for example for GPT it depends on space allocated on disk for array of
+ * entry records (usually 128).
+ *
+ * It's fine to use fdisk_get_npartitions() in loops, but don't forget that
+ * partition may be unused (see fdisk_is_partition_used()).
+ *
+ * <informalexample>
+ *   <programlisting>
+ *	struct fdisk_partition *pa = NULL;
+ *	size_t i, nmax = fdisk_get_npartitions(cxt);
+ *
+ *	for (i = 0; i < nmax; i++) {
+ *		if (!fdisk_is_partition_used(cxt, i))
+ *			continue;
+ *		... do something ...
+ *	}
+ *   </programlisting>
+ * </informalexample>
+ *
+ * Note that the recommended way to list partitions is to use
+ * fdisk_get_partitions() and struct fdisk_table than ask disk driver for each
+ * individual partitions.
+ *
+ * Returns: maximal number of partitions for the current label.
+ */
+size_t fdisk_get_npartitions(struct fdisk_context *cxt)
+{
+	return cxt && cxt->label ? cxt->label->nparts_max : 0;
+}
+
+/**
+ * fdisk_is_labeltype:
+ * @cxt: fdisk context
+ * @id: FDISK_DISKLABEL_*
+ *
+ * See also fdisk_is_label() macro in libfdisk.h.
+ *
+ * Returns: return 1 if the current label is @id
+ */
+int fdisk_is_labeltype(struct fdisk_context *cxt, enum fdisk_labeltype id)
+{
+	assert(cxt);
+
+	return cxt->label && fdisk_label_get_type(cxt->label) == id;
+}
+
+/**
+ * fdisk_get_parent:
+ * @cxt: nested fdisk context
+ *
+ * Returns: pointer to parental context, or NULL
+ */
+struct fdisk_context *fdisk_get_parent(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->parent;
+}
+
+static void reset_context(struct fdisk_context *cxt)
+{
+	size_t i;
+
+	DBG(CXT, ul_debugobj(cxt, "*** resetting context"));
+
+	/* reset drives' private data */
+	for (i = 0; i < cxt->nlabels; i++)
+		fdisk_deinit_label(cxt->labels[i]);
+
+	if (cxt->parent) {
+		/* the first sector may be independent on parent */
+		if (cxt->parent->firstsector != cxt->firstsector)
+			free(cxt->firstsector);
+	} else {
+		/* we close device only in primary context */
+		if (cxt->dev_fd > -1)
+			close(cxt->dev_fd);
+		free(cxt->firstsector);
+	}
+
+	free(cxt->dev_path);
+	cxt->dev_path = NULL;
+
+	cxt->dev_fd = -1;
+	cxt->firstsector = NULL;
+	cxt->firstsector_bufsz = 0;
+
+	fdisk_zeroize_device_properties(cxt);
+
+	fdisk_unref_script(cxt->script);
+	cxt->script = NULL;
+
+	cxt->label = NULL;
+}
+
+/*
+ * This function prints a warning if the device is not wiped (e.g. wipefs(8).
+ * Please don't call this function if there is already a PT.
+ *
+ * Returns: 0 if nothing found, < 0 on error, 1 if found a signature
+ */
+static int warn_wipe(struct fdisk_context *cxt)
+{
+#ifdef HAVE_LIBBLKID
+	blkid_probe pr;
+#endif
+	int rc = 0;
+
+	assert(cxt);
+
+	if (fdisk_has_label(cxt) || cxt->dev_fd < 0)
+		return -EINVAL;
+#ifdef HAVE_LIBBLKID
+	DBG(CXT, ul_debugobj(cxt, "wipe check: initialize libblkid prober"));
+
+	pr = blkid_new_probe();
+	if (!pr)
+		return -ENOMEM;
+	rc = blkid_probe_set_device(pr, cxt->dev_fd, 0, 0);
+	if (rc)
+		return rc;
+
+	blkid_probe_enable_superblocks(pr, 1);
+	blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE);
+	blkid_probe_enable_partitions(pr, 1);
+
+	/* we care about the first found FS/raid, so don't call blkid_do_probe()
+	 * in loop or don't use blkid_do_fullprobe() ... */
+	rc = blkid_do_probe(pr);
+	if (rc == 0) {
+		const char *name = NULL;
+
+		if (blkid_probe_lookup_value(pr, "TYPE", &name, 0) == 0 ||
+		    blkid_probe_lookup_value(pr, "PTTYPE", &name, 0) == 0) {
+			fdisk_warnx(cxt, _(
+				"%s: device contains a valid '%s' signature; it is "
+				"strongly recommended to wipe the device with "
+				"wipefs(8) if this is unexpected, in order to "
+				"avoid possible collisions"), cxt->dev_path, name);
+			rc = 1;
+		}
+	}
+
+	blkid_free_probe(pr);
+#endif
+	return rc;
+}
+
+/**
+ * fdisk_assign_device:
+ * @cxt: context
+ * @fname: path to the device to be handled
+ * @readonly: how to open the device
+ *
+ * Open the device, discovery topology, geometry, detect disklabel and switch
+ * the current label driver to reflect the probing result.
+ *
+ * Note that this function resets all generic setting in context. If the @cxt
+ * is nested context then the device is assigned to the parental context and
+ * necessary properties are copied to the @cxt. The change is propagated in
+ * child->parent direction only. It's impossible to use a different device for
+ * primary and nested contexts.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_assign_device(struct fdisk_context *cxt,
+			const char *fname, int readonly)
+{
+	int fd;
+
+	DBG(CXT, ul_debugobj(cxt, "assigning device %s", fname));
+	assert(cxt);
+
+	/* redirect request to parent */
+	if (cxt->parent) {
+		int rc, org = fdisk_is_listonly(cxt->parent);
+
+		/* assign_device() is sensitive to "listonly" mode, so let's
+		 * follow the current context setting for the parent to avoid 
+		 * unwanted extra warnings. */
+		fdisk_enable_listonly(cxt->parent, fdisk_is_listonly(cxt));
+
+		rc = fdisk_assign_device(cxt->parent, fname, readonly);
+		fdisk_enable_listonly(cxt->parent, org);
+
+		if (!rc)
+			rc = init_nested_from_parent(cxt, 0);
+		if (!rc)
+			fdisk_probe_labels(cxt);
+		return rc;
+	}
+
+	reset_context(cxt);
+
+	fd = open(fname, (readonly ? O_RDONLY : O_RDWR ) | O_CLOEXEC);
+	if (fd < 0)
+		return -errno;
+
+	cxt->readonly = readonly;
+	cxt->dev_fd = fd;
+	cxt->dev_path = strdup(fname);
+	if (!cxt->dev_path)
+		goto fail;
+
+	fdisk_discover_topology(cxt);
+	fdisk_discover_geometry(cxt);
+
+	if (fdisk_read_firstsector(cxt) < 0)
+		goto fail;
+
+	/* detect labels and apply labes specific stuff (e.g geomery)
+	 * to the context */
+	fdisk_probe_labels(cxt);
+
+	/* let's apply user geometry *after* label prober
+	 * to make it possible to override in-label setting */
+	fdisk_apply_user_device_properties(cxt);
+
+	/* warn about obsolete stuff on the device if we aren't in
+	 * list-only mode and there is not PT yet */
+	if (!fdisk_is_listonly(cxt) && !fdisk_has_label(cxt))
+		warn_wipe(cxt);
+
+	DBG(CXT, ul_debugobj(cxt, "initialized for %s [%s]",
+			      fname, readonly ? "READ-ONLY" : "READ-WRITE"));
+	return 0;
+fail:
+	DBG(CXT, ul_debugobj(cxt, "failed to assign device"));
+	return -errno;
+}
+
+/**
+ * fdisk_deassign_device:
+ * @cxt: context
+ * @nosync: disable fsync()
+ *
+ * Close device and call fsync(). If the @cxt is nested context than the
+ * request is redirected to the parent.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_deassign_device(struct fdisk_context *cxt, int nosync)
+{
+	assert(cxt);
+	assert(cxt->dev_fd >= 0);
+
+	if (cxt->parent) {
+		int rc = fdisk_deassign_device(cxt->parent, nosync);
+
+		if (!rc)
+			rc = init_nested_from_parent(cxt, 0);
+		return rc;
+	}
+
+	if (cxt->readonly)
+		close(cxt->dev_fd);
+	else {
+		if (fsync(cxt->dev_fd) || close(cxt->dev_fd)) {
+			fdisk_warn(cxt, _("%s: close device failed"),
+					cxt->dev_path);
+			return -errno;
+		}
+
+		if (!nosync) {
+			fdisk_info(cxt, _("Syncing disks."));
+			sync();
+		}
+	}
+
+	free(cxt->dev_path);
+	cxt->dev_path = NULL;
+
+	cxt->dev_fd = -1;
+
+	return 0;
+}
+
+/**
+ * fdisk_is_readonly:
+ * @cxt: context
+ *
+ * Returns: 1 if device open readonly
+ */
+int fdisk_is_readonly(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->readonly;
+}
+
+/**
+ * fdisk_unref_context:
+ * @cxt: fdisk context
+ *
+ * Deallocates context struct.
+ */
+void fdisk_unref_context(struct fdisk_context *cxt)
+{
+	int i;
+
+	if (!cxt)
+		return;
+
+	cxt->refcount--;
+	if (cxt->refcount <= 0) {
+		DBG(CXT, ul_debugobj(cxt, "freeing context %p for %s", cxt, cxt->dev_path));
+
+		reset_context(cxt);	/* this is sensitive to parent<->child relationship! */
+
+		/* deallocate label's private stuff */
+		for (i = 0; i < cxt->nlabels; i++) {
+			if (!cxt->labels[i])
+				continue;
+			if (cxt->labels[i]->op->free)
+				cxt->labels[i]->op->free(cxt->labels[i]);
+			else
+				free(cxt->labels[i]);
+		}
+
+		fdisk_unref_context(cxt->parent);
+		cxt->parent = NULL;
+
+		free(cxt);
+	}
+}
+
+
+/**
+ * fdisk_enable_details:
+ * @cxt: context
+ * @enable: true/flase
+ *
+ * Enables or disables "details" display mode. This function has effect to
+ * fdisk_partition_to_string() function.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_enable_details(struct fdisk_context *cxt, int enable)
+{
+	assert(cxt);
+	cxt->display_details = enable ? 1 : 0;
+	return 0;
+}
+
+/**
+ * fdisk_is_details:
+ * @cxt: context
+ *
+ * Returns: 1 if details are enabled
+ */
+int fdisk_is_details(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->display_details == 1;
+}
+
+/**
+ * fdisk_enable_listonly:
+ * @cxt: context
+ * @enable: true/flase
+ *
+ * Just list partition only, don't care about another details, mistakes, ...
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_enable_listonly(struct fdisk_context *cxt, int enable)
+{
+	assert(cxt);
+	cxt->listonly = enable ? 1 : 0;
+	return 0;
+}
+
+/**
+ * fdisk_is_listonly:
+ * @cxt: context
+ *
+ * Returns: 1 if list-only mode enabled
+ */
+int fdisk_is_listonly(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->listonly == 1;
+}
+
+
+/**
+ * fdisk_set_unit:
+ * @cxt: context
+ * @str: "cylinder" or "sector".
+ *
+ * This is pure shit, unfortunately for example Sun addresses begin of the
+ * partition by cylinders...
+ *
+ * Returns: 0 on succes, <0 on error.
+ */
+int fdisk_set_unit(struct fdisk_context *cxt, const char *str)
+{
+	assert(cxt);
+
+	cxt->display_in_cyl_units = 0;
+
+	if (!str)
+		return 0;
+
+	if (strcmp(str, "cylinder") == 0 || strcmp(str, "cylinders") == 0)
+		cxt->display_in_cyl_units = 1;
+
+	else if (strcmp(str, "sector") == 0 || strcmp(str, "sectors") == 0)
+		cxt->display_in_cyl_units = 0;
+
+	DBG(CXT, ul_debugobj(cxt, "display unit: %s", fdisk_get_unit(cxt, 0)));
+	return 0;
+}
+
+/**
+ * fdisk_get_unit:
+ * @cxt: context
+ * @n: FDISK_PLURAL or FDISK_SINGULAR
+ *
+ * Returns: unit name.
+ */
+const char *fdisk_get_unit(struct fdisk_context *cxt, int n)
+{
+	assert(cxt);
+
+	if (fdisk_use_cylinders(cxt))
+		return P_("cylinder", "cylinders", n);
+	return P_("sector", "sectors", n);
+}
+
+/**
+ * fdisk_use_cylinders:
+ * @cxt: context
+ *
+ * Returns: 1 if user wants to display in cylinders.
+ */
+int fdisk_use_cylinders(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->display_in_cyl_units == 1;
+}
+
+/**
+ * fdisk_get_units_per_sector:
+ * @cxt: context
+ *
+ * This is necessary only for brain dead situations when we use "cylinders";
+ *
+ * Returns: number of "units" per sector, default is 1 if display unit is sector.
+ */
+unsigned int fdisk_get_units_per_sector(struct fdisk_context *cxt)
+{
+	assert(cxt);
+
+	if (fdisk_use_cylinders(cxt)) {
+		assert(cxt->geom.heads);
+		return cxt->geom.heads * cxt->geom.sectors;
+	}
+	return 1;
+}
+
+/**
+ * fdisk_get_optimal_iosize:
+ * @cxt: context
+ *
+ * The optimal I/O is optional and does not have to be provided by device,
+ * anyway libfdisk never returns zero. If the optimal I/O size is not provided
+ * then libfdisk returns minimal I/O size or sector size.
+ *
+ * Returns: optimal I/O size in bytes.
+ */
+unsigned long fdisk_get_optimal_iosize(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->optimal_io_size ? cxt->optimal_io_size : cxt->io_size;
+}
+
+/**
+ * fdisk_get_minimal_iosize:
+ * @cxt: context
+ *
+ * Returns: minimal I/O size in bytes
+ */
+unsigned long fdisk_get_minimal_iosize(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->min_io_size;
+}
+
+/**
+ * fdisk_get_physector_size:
+ * @cxt: context
+ *
+ * Returns: physical sector size in bytes
+ */
+unsigned long fdisk_get_physector_size(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->phy_sector_size;
+}
+
+/**
+ * fdisk_get_sector_size:
+ * @cxt: context
+ *
+ * Returns: logical sector size in bytes
+ */
+unsigned long fdisk_get_sector_size(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->sector_size;
+}
+
+/**
+ * fdisk_get_alignment_offset
+ * @cxt: context
+ *
+ * The alignment offset is offset between logical and physical sectors. For
+ * backward compatibility the first logical sector on 4K disks does no have to
+ * start on the same place like physical sectors.
+ *
+ * Returns: alignment offset in bytes
+ */
+unsigned long fdisk_get_alignment_offset(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->alignment_offset;
+}
+
+/**
+ * fdisk_get_grain_size:
+ * @cxt: context
+ *
+ * Returns: grain in bytes used to align partitions (usually 1MiB)
+ */
+unsigned long fdisk_get_grain_size(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->grain;
+}
+
+/**
+ * fdisk_get_first_lba:
+ * @cxt: context
+ *
+ * Returns: first possible LBA on disk for data partitions.
+ */
+fdisk_sector_t fdisk_get_first_lba(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->first_lba;
+}
+
+/**
+ * fdisk_set_first_lba:
+ * @cxt: fdisk context
+ * @lba: first possible logical sector for data
+ *
+ * It's strongly recommended to use the default library setting. The first LBA
+ * is always reseted by fdisk_assign_device(), fdisk_override_geometry()
+ * and fdisk_reset_alignment(). This is very low level function and library
+ * does not check if your setting makes any sense.
+ *
+ * This function is necessary only when you want to work with very unusual
+ * partition tables like GPT protective MBR or hybrid partition tables on
+ * bootable media where the first partition may start on very crazy offsets.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+fdisk_sector_t fdisk_set_first_lba(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+	assert(cxt);
+	DBG(CXT, ul_debugobj(cxt, "setting first LBA from %ju to %ju",
+			(uintmax_t) cxt->first_lba, (uintmax_t) lba));
+	cxt->first_lba = lba;
+	return 0;
+}
+
+/**
+ * fdisk_get_last_lba:
+ * @cxt: fdisk context
+ *
+ * Note that the device has to be already assigned.
+ *
+ * Returns: last possible LBA on device
+ */
+fdisk_sector_t fdisk_get_last_lba(struct fdisk_context *cxt)
+{
+	return cxt->last_lba;
+}
+
+/**
+ * fdisk_set_last_lba:
+ * @cxt: fdisk context
+ * @lba: last possible logical sector
+ *
+ * It's strongly recommended to use the default library setting. The last LBA
+ * is always reseted by fdisk_assign_device(), fdisk_override_geometry() and
+ * fdisk_reset_alignment().
+ *
+ * The default is number of sectors on the device, but maybe modified by the
+ * current disklabel driver (for example GPT uses and of disk for backup
+ * header, so last_lba is smaller than total number of sectors).
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+fdisk_sector_t fdisk_set_last_lba(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+	assert(cxt);
+
+	if (lba > cxt->total_sectors - 1 && lba < 1)
+		return -ERANGE;
+	cxt->last_lba = lba;
+	return 0;
+}
+
+
+/**
+ * fdisk_get_nsectors:
+ * @cxt: context
+ *
+ * Returns: size of the device in logical sectors.
+ */
+fdisk_sector_t fdisk_get_nsectors(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->total_sectors;
+}
+
+/**
+ * fdisk_get_devname:
+ * @cxt: context
+ *
+ * Returns: device name.
+ */
+const char *fdisk_get_devname(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->dev_path;
+}
+
+/**
+ * fdisk_get_devfd:
+ * @cxt: context
+ *
+ * Retruns: device file descriptor.
+ */
+int fdisk_get_devfd(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->dev_fd;
+}
+
+/**
+ * fdisk_get_geom_heads:
+ * @cxt: context
+ *
+ * Returns: number of geometry heads.
+ */
+unsigned int fdisk_get_geom_heads(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->geom.heads;
+}
+/**
+ * fdisk_get_geom_sectors:
+ * @cxt: context
+ *
+ * Returns: number of geometry sectors.
+ */
+fdisk_sector_t fdisk_get_geom_sectors(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->geom.sectors;
+
+}
+
+/**
+ * fdisk_get_geom_cylinders:
+ * @cxt: context
+ *
+ * Returns: number of geometry cylinders
+ */
+fdisk_sector_t fdisk_get_geom_cylinders(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->geom.cylinders;
+}
+
+int fdisk_missing_geometry(struct fdisk_context *cxt)
+{
+	int rc;
+
+	assert(cxt);
+
+	if (!cxt || !cxt->label)
+		return 0;
+
+	rc = (fdisk_label_require_geometry(cxt->label) &&
+		    (!cxt->geom.heads || !cxt->geom.sectors
+				      || !cxt->geom.cylinders));
+
+	if (rc && !fdisk_is_listonly(cxt))
+		fdisk_warnx(cxt, _("Incomplete geometry setting."));
+
+	return rc;
+}
+
diff --git a/libblkid/libfdisk/src/dos.c b/libblkid/libfdisk/src/dos.c
new file mode 100644
index 0000000..2a06707
--- /dev/null
+++ b/libblkid/libfdisk/src/dos.c
@@ -0,0 +1,2331 @@
+/*
+ *
+ * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com>
+ *                    2012 Davidlohr Bueso <dave@gnu.org>
+ *
+ * This is re-written version for libfdisk, the original was fdiskdoslabel.c
+ * from util-linux fdisk.
+ */
+#include "c.h"
+#include "nls.h"
+#include "randutils.h"
+#include "pt-mbr.h"
+#include "strutils.h"
+
+#include "fdiskP.h"
+
+#include <ctype.h>
+
+#define MAXIMUM_PARTS	60
+#define ACTIVE_FLAG     0x80
+
+/**
+ * SECTION: dos
+ * @title: DOS (MBR)
+ * @short_description: disk label specific functions
+ *
+ */
+
+
+#define IS_EXTENDED(i) \
+	((i) == MBR_DOS_EXTENDED_PARTITION \
+	 || (i) == MBR_W95_EXTENDED_PARTITION \
+	 || (i) == MBR_LINUX_EXTENDED_PARTITION)
+
+/*
+ * per partition table entry data
+ *
+ * The four primary partitions have the same sectorbuffer
+ * and have NULL ex_entry.
+ *
+ * Each logical partition table entry has two pointers, one for the
+ * partition and one link to the next one.
+ */
+struct pte {
+	struct dos_partition *pt_entry;	/* on-disk MBR entry */
+	struct dos_partition *ex_entry;	/* on-disk EBR entry */
+	fdisk_sector_t offset;	        /* disk sector number */
+	unsigned char *sectorbuffer;	/* disk sector contents */
+
+	unsigned int changed : 1,
+		     private_sectorbuffer : 1;
+};
+
+/*
+ * in-memory fdisk GPT stuff
+ */
+struct fdisk_dos_label {
+	struct fdisk_label	head;		/* generic part */
+
+	struct pte	ptes[MAXIMUM_PARTS];	/* partition */
+	fdisk_sector_t	ext_offset;		/* start of the ext.partition */
+	size_t		ext_index;		/* ext.partition index (if ext_offset is set) */
+	unsigned int	compatible : 1,		/* is DOS compatible? */
+			non_pt_changed : 1;	/* MBR, but no PT changed */
+};
+
+/*
+ * Partition types
+ */
+static struct fdisk_parttype dos_parttypes[] = {
+	#include "pt-mbr-partnames.h"
+};
+
+#define set_hsc(h,s,c,sector) { \
+		s = sector % cxt->geom.sectors + 1;			\
+		sector /= cxt->geom.sectors;				\
+		h = sector % cxt->geom.heads;				\
+		sector /= cxt->geom.heads;				\
+		c = sector & 0xff;					\
+		s |= (sector >> 2) & 0xc0;				\
+	}
+
+
+#define sector(s)	((s) & 0x3f)
+#define cylinder(s, c)	((c) | (((s) & 0xc0) << 2))
+
+#define alignment_required(_x)	((_x)->grain != (_x)->sector_size)
+
+#define is_dos_compatible(_x) \
+		   (fdisk_is_label(_x, DOS) && \
+                    fdisk_dos_is_compatible(fdisk_get_label(_x, NULL)))
+
+#define cround(c, n)	fdisk_cround(c, n)
+
+
+static inline struct fdisk_dos_label *self_label(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	return (struct fdisk_dos_label *) cxt->label;
+}
+
+static inline struct pte *self_pte(struct fdisk_context *cxt, size_t i)
+{
+	struct fdisk_dos_label *l = self_label(cxt);
+
+	if (i >= ARRAY_SIZE(l->ptes))
+		return NULL;
+
+	return &l->ptes[i];
+}
+
+static inline struct dos_partition *self_partition(
+				struct fdisk_context *cxt,
+				size_t i)
+{
+	struct pte *pe = self_pte(cxt, i);
+	return pe ? pe->pt_entry : NULL;
+}
+
+struct dos_partition *fdisk_dos_get_partition(
+				struct fdisk_context *cxt,
+				size_t i)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	return self_partition(cxt, i);
+}
+
+static struct fdisk_parttype *dos_partition_parttype(
+		struct fdisk_context *cxt,
+		struct dos_partition *p)
+{
+	struct fdisk_parttype *t
+		= fdisk_label_get_parttype_from_code(cxt->label, p->sys_ind);
+	return t ? : fdisk_new_unknown_parttype(p->sys_ind, NULL);
+}
+
+/*
+ * Linux kernel cares about partition size only. Things like
+ * partition type or so are completely irrelevant -- kzak Nov-2013
+ */
+static int is_used_partition(struct dos_partition *p)
+{
+	return p && dos_partition_get_size(p) != 0;
+}
+
+static void partition_set_changed(
+				struct fdisk_context *cxt,
+				size_t i,
+				int changed)
+{
+	struct pte *pe = self_pte(cxt, i);
+
+	if (!pe)
+		return;
+
+	DBG(LABEL, ul_debug("DOS: setting %zu partition to %s", i,
+				changed ? "changed" : "unchanged"));
+
+	pe->changed = changed ? 1 : 0;
+	if (changed)
+		fdisk_label_set_changed(cxt->label, 1);
+}
+
+static fdisk_sector_t get_abs_partition_start(struct pte *pe)
+{
+	assert(pe);
+	assert(pe->pt_entry);
+
+	return pe->offset + dos_partition_get_start(pe->pt_entry);
+}
+
+static fdisk_sector_t get_abs_partition_end(struct pte *pe)
+{
+	fdisk_sector_t size;
+
+	assert(pe);
+	assert(pe->pt_entry);
+
+	size = dos_partition_get_size(pe->pt_entry);
+	return get_abs_partition_start(pe) + size - (size ? 1 : 0);
+}
+
+static int is_cleared_partition(struct dos_partition *p)
+{
+	return !(!p || p->boot_ind || p->bh || p->bs || p->bc ||
+		 p->sys_ind || p->eh || p->es || p->ec ||
+		 dos_partition_get_start(p) || dos_partition_get_size(p));
+}
+
+static int get_partition_unused_primary(struct fdisk_context *cxt,
+					struct fdisk_partition *pa,
+					size_t *partno)
+{
+	size_t org, n;
+	int rc;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(partno);
+
+	org = cxt->label->nparts_max;
+
+	cxt->label->nparts_max = 4;
+	rc = fdisk_partition_next_partno(pa, cxt, &n);
+	cxt->label->nparts_max = org;
+
+	if (rc == 1) {
+		fdisk_info(cxt, _("All primary partitions have been defined already."));
+		rc = -1;
+	} else if (rc == 0)
+		*partno = n;
+	return rc;
+}
+
+static int seek_sector(struct fdisk_context *cxt, fdisk_sector_t secno)
+{
+	off_t offset = (off_t) secno * cxt->sector_size;
+
+	return lseek(cxt->dev_fd, offset, SEEK_SET) == (off_t) -1 ? -errno : 0;
+}
+
+static int read_sector(struct fdisk_context *cxt, fdisk_sector_t secno,
+			unsigned char *buf)
+{
+	int rc = seek_sector(cxt, secno);
+	ssize_t r;
+
+	if (rc < 0)
+		return rc;
+
+	r = read(cxt->dev_fd, buf, cxt->sector_size);
+	if (r == (ssize_t) cxt->sector_size)
+		return 0;
+	if (r < 0)
+		return -errno;
+	return -1;
+}
+
+/* Allocate a buffer and read a partition table sector */
+static int read_pte(struct fdisk_context *cxt, size_t pno, fdisk_sector_t offset)
+{
+	int rc;
+	unsigned char *buf;
+	struct pte *pe = self_pte(cxt, pno);
+
+	buf = calloc(1, cxt->sector_size);
+	if (!buf)
+		return -ENOMEM;
+
+	DBG(LABEL, ul_debug("DOS: reading EBR %zu: offset=%ju, buffer=%p",
+				pno, (uintmax_t) offset, buf));
+
+	pe->offset = offset;
+	pe->sectorbuffer = buf;
+	pe->private_sectorbuffer = 1;
+
+	rc = read_sector(cxt, offset, pe->sectorbuffer);
+	if (rc) {
+		fdisk_warn(cxt, _("Failed to read extended partition table "
+				"(offset=%ju)"), (uintmax_t) offset);
+		return rc;
+	}
+
+	pe->changed = 0;
+	pe->pt_entry = pe->ex_entry = NULL;
+	return 0;
+}
+
+
+static void clear_partition(struct dos_partition *p)
+{
+	if (!p)
+		return;
+	p->boot_ind = 0;
+	p->bh = 0;
+	p->bs = 0;
+	p->bc = 0;
+	p->sys_ind = 0;
+	p->eh = 0;
+	p->es = 0;
+	p->ec = 0;
+	dos_partition_set_start(p,0);
+	dos_partition_set_size(p,0);
+}
+
+static void dos_init(struct fdisk_context *cxt)
+{
+	struct fdisk_dos_label *l = self_label(cxt);
+	size_t i;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	DBG(LABEL, ul_debug("DOS: initialize, first sector buffer %p", cxt->firstsector));
+
+	cxt->label->nparts_max = 4;	/* default, unlimited number of logical */
+
+	l->ext_index = 0;
+	l->ext_offset = 0;
+	l->non_pt_changed = 0;
+
+	memset(l->ptes, 0, sizeof(l->ptes));
+
+	for (i = 0; i < 4; i++) {
+		struct pte *pe = self_pte(cxt, i);
+
+		pe->pt_entry = mbr_get_partition(cxt->firstsector, i);
+		pe->ex_entry = NULL;
+		pe->offset = 0;
+		pe->sectorbuffer = cxt->firstsector;
+		pe->private_sectorbuffer = 0;
+		pe->changed = 0;
+	}
+
+	if (fdisk_is_listonly(cxt))
+		return;
+	/*
+	 * Various warnings...
+	 */
+	if (fdisk_missing_geometry(cxt))
+		fdisk_warnx(cxt, _("You can set geometry from the extra functions menu."));
+
+	if (is_dos_compatible(cxt)) {
+		fdisk_warnx(cxt, _("DOS-compatible mode is deprecated."));
+
+		if (cxt->sector_size != cxt->phy_sector_size)
+			fdisk_info(cxt, _(
+		"The device presents a logical sector size that is smaller than "
+		"the physical sector size. Aligning to a physical sector (or optimal "
+		"I/O) size boundary is recommended, or performance may be impacted."));
+	}
+
+	if (fdisk_use_cylinders(cxt))
+		fdisk_warnx(cxt, _("Cylinders as display units are deprecated."));
+
+	if (cxt->total_sectors > UINT_MAX) {
+		uint64_t bytes = cxt->total_sectors * cxt->sector_size;
+		char *szstr = size_to_human_string(SIZE_SUFFIX_SPACE
+					   | SIZE_SUFFIX_3LETTER, bytes);
+		fdisk_warnx(cxt,
+		_("The size of this disk is %s (%ju bytes). DOS "
+		  "partition table format can not be used on drives for "
+		  "volumes larger than %lu bytes for %lu-byte "
+		  "sectors. Use GUID partition table format (GPT)."),
+			szstr, bytes,
+			UINT_MAX * cxt->sector_size,
+			cxt->sector_size);
+		free(szstr);
+	}
+}
+
+/* callback called by libfdisk */
+static void dos_deinit(struct fdisk_label *lb)
+{
+	size_t i;
+	struct fdisk_dos_label *l = (struct fdisk_dos_label *) lb;
+
+	for (i = 0; i < ARRAY_SIZE(l->ptes); i++) {
+		struct pte *pe = &l->ptes[i];
+
+		if (pe->private_sectorbuffer && pe->sectorbuffer) {
+			DBG(LABEL, ul_debug("DOS: freeing pte %zu sector buffer %p",
+						i, pe->sectorbuffer));
+			free(pe->sectorbuffer);
+		}
+		pe->sectorbuffer = NULL;
+		pe->private_sectorbuffer = 0;
+	}
+
+	memset(l->ptes, 0, sizeof(l->ptes));
+}
+
+static void reset_pte(struct pte *pe)
+{
+	assert(pe);
+
+	if (pe->private_sectorbuffer) {
+		DBG(LABEL, ul_debug("  --> freeing pte sector buffer %p",
+					pe->sectorbuffer));
+		free(pe->sectorbuffer);
+	}
+	memset(pe, 0, sizeof(struct pte));
+}
+
+static int dos_delete_partition(struct fdisk_context *cxt, size_t partnum)
+{
+	struct fdisk_dos_label *l;
+	struct pte *pe;
+	struct dos_partition *p;
+	struct dos_partition *q;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	pe = self_pte(cxt, partnum);
+	if (!pe)
+		return -EINVAL;
+
+	DBG(LABEL, ul_debug("DOS: delete partiton %zu (max=%zu)", partnum,
+				cxt->label->nparts_max));
+
+	l = self_label(cxt);
+	p = pe->pt_entry;
+	q = pe->ex_entry;
+
+	/* Note that for the fifth partition (partnum == 4) we don't actually
+	   decrement partitions. */
+	if (partnum < 4) {
+		DBG(LABEL, ul_debug("--> delete primary"));
+		if (IS_EXTENDED(p->sys_ind) && partnum == l->ext_index) {
+			cxt->label->nparts_max = 4;
+			l->ptes[l->ext_index].ex_entry = NULL;
+			l->ext_offset = 0;
+			l->ext_index = 0;
+		}
+		partition_set_changed(cxt, partnum, 1);
+		clear_partition(p);
+	} else if (!q->sys_ind && partnum > 4) {
+		DBG(LABEL, ul_debug("--> delete logical [last in the chain]"));
+		reset_pte(&l->ptes[partnum]);
+		--cxt->label->nparts_max;
+		--partnum;
+		/* clear link to deleted partition */
+		clear_partition(l->ptes[partnum].ex_entry);
+		partition_set_changed(cxt, partnum, 1);
+	} else {
+		DBG(LABEL, ul_debug("--> delete logical [move down]"));
+		if (partnum > 4) {
+			DBG(LABEL, ul_debug(" --> delete %zu logical link", partnum));
+			p = l->ptes[partnum - 1].ex_entry;
+			*p = *q;
+			dos_partition_set_start(p, dos_partition_get_start(q));
+			dos_partition_set_size(p, dos_partition_get_size(q));
+			partition_set_changed(cxt, partnum - 1, 1);
+
+		} else if (cxt->label->nparts_max > 5) {
+			DBG(LABEL, ul_debug(" --> delete first logical link"));
+			pe = &l->ptes[5];	/* second logical */
+
+			if (pe->pt_entry)	/* prevent SEGFAULT */
+				dos_partition_set_start(pe->pt_entry,
+					       get_abs_partition_start(pe) -
+					       l->ext_offset);
+			pe->offset = l->ext_offset;
+			partition_set_changed(cxt, 5, 1);
+		}
+
+		if (cxt->label->nparts_max > 5) {
+			DBG(LABEL, ul_debug(" --> move ptes"));
+			cxt->label->nparts_max--;
+			reset_pte(&l->ptes[partnum]);
+			while (partnum < cxt->label->nparts_max) {
+				DBG(LABEL, ul_debug("  --> moving pte %zu <-- %zu", partnum, partnum + 1));
+				l->ptes[partnum] = l->ptes[partnum + 1];
+				partnum++;
+			}
+			memset(&l->ptes[partnum], 0, sizeof(struct pte));
+		} else {
+			DBG(LABEL, ul_debug(" --> the only logical: clear only"));
+			clear_partition(l->ptes[partnum].pt_entry);
+			cxt->label->nparts_max--;
+
+			if (partnum == 4) {
+				DBG(LABEL, ul_debug("  --> clear last logical"));
+				reset_pte(&l->ptes[partnum]);
+				partition_set_changed(cxt, l->ext_index, 1);
+			}
+		}
+	}
+
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+static void read_extended(struct fdisk_context *cxt, size_t ext)
+{
+	size_t i;
+	struct pte *pex, *pe;
+	struct dos_partition *p, *q;
+	struct fdisk_dos_label *l = self_label(cxt);
+
+	l->ext_index = ext;
+	pex = self_pte(cxt, ext);
+	pex->ex_entry = pex->pt_entry;
+
+	p = pex->pt_entry;
+	if (!dos_partition_get_start(p)) {
+		fdisk_warnx(cxt, _("Bad offset in primary extended partition."));
+		return;
+	}
+
+	DBG(LABEL, ul_debug("DOS: Reading extended %zu", ext));
+
+	while (IS_EXTENDED (p->sys_ind)) {
+		pe = self_pte(cxt, cxt->label->nparts_max);
+
+		if (cxt->label->nparts_max >= MAXIMUM_PARTS) {
+			/* This is not a Linux restriction, but
+			   this program uses arrays of size MAXIMUM_PARTS.
+			   Do not try to `improve' this test. */
+			struct pte *pre = self_pte(cxt,
+						cxt->label->nparts_max - 1);
+			fdisk_warnx(cxt,
+			_("Omitting partitions after #%zu. They will be deleted "
+			  "if you save this partition table."),
+				cxt->label->nparts_max);
+
+			clear_partition(pre->ex_entry);
+			partition_set_changed(cxt,
+					cxt->label->nparts_max - 1, 1);
+			return;
+		}
+
+		if (read_pte(cxt, cxt->label->nparts_max, l->ext_offset +
+						dos_partition_get_start(p)))
+			return;
+
+		if (!l->ext_offset)
+			l->ext_offset = dos_partition_get_start(p);
+
+		assert(pe->sectorbuffer);
+		q = p = mbr_get_partition(pe->sectorbuffer, 0);
+
+		for (i = 0; i < 4; i++, p++) {
+			if (!dos_partition_get_size(p))
+				continue;
+
+			if (IS_EXTENDED (p->sys_ind)) {
+				if (pe->ex_entry)
+					fdisk_warnx(cxt, _(
+					"Extra link pointer in partition "
+					"table %zu."),
+						cxt->label->nparts_max + 1);
+				else
+					pe->ex_entry = p;
+			} else if (p->sys_ind) {
+				if (pe->pt_entry)
+					fdisk_warnx(cxt, _(
+					"Ignoring extra data in partition "
+					"table %zu."),
+						cxt->label->nparts_max + 1);
+				else
+					pe->pt_entry = p;
+			}
+		}
+
+		/* very strange code here... */
+		if (!pe->pt_entry) {
+			if (q != pe->ex_entry)
+				pe->pt_entry = q;
+			else
+				pe->pt_entry = q + 1;
+		}
+		if (!pe->ex_entry) {
+			if (q != pe->pt_entry)
+				pe->ex_entry = q;
+			else
+				pe->ex_entry = q + 1;
+		}
+
+		p = pe->ex_entry;
+		cxt->label->nparts_cur = ++cxt->label->nparts_max;
+
+		DBG(LABEL, ul_debug("DOS: EBR[offset=%ju]: link: type=%x,  start=%u, size=%u; "
+				                         " data: type=%x, start=%u, size=%u",
+				    (uintmax_t) pe->offset,
+				    pe->ex_entry->sys_ind,
+				    dos_partition_get_start(pe->ex_entry),
+				    dos_partition_get_size(pe->ex_entry),
+				    pe->pt_entry->sys_ind,
+				    dos_partition_get_start(pe->pt_entry),
+				    dos_partition_get_size(pe->pt_entry)));
+
+	}
+
+	/* remove last empty EBR */
+	pe = self_pte(cxt, cxt->label->nparts_max - 1);
+	if (is_cleared_partition(pe->ex_entry) &&
+	    is_cleared_partition(pe->pt_entry)) {
+		DBG(LABEL, ul_debug("DOS: EBR[offset=%ju]: empty, remove", (uintmax_t) pe->offset));
+		reset_pte(pe);
+		cxt->label->nparts_max--;
+		cxt->label->nparts_cur--;
+	}
+
+	/* remove empty links */
+ remove:
+	q = self_partition(cxt, 4);
+	for (i = 4; i < cxt->label->nparts_max; i++) {
+		p = self_partition(cxt, i);
+
+		if (!dos_partition_get_size(p) &&
+		    (cxt->label->nparts_max > 5 || q->sys_ind)) {
+			fdisk_info(cxt, _("omitting empty partition (%zu)"), i+1);
+			dos_delete_partition(cxt, i);
+			goto remove; 	/* numbering changed */
+		}
+	}
+
+	DBG(LABEL, ul_debug("DOS: nparts_max: %zu", cxt->label->nparts_max));
+}
+
+static int dos_get_disklabel_id(struct fdisk_context *cxt, char **id)
+{
+	unsigned int num;
+
+	assert(cxt);
+	assert(id);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	num = mbr_get_id(cxt->firstsector);
+	if (asprintf(id, "0x%08x", num) > 0)
+		return 0;
+
+	return -ENOMEM;
+}
+
+static int dos_create_disklabel(struct fdisk_context *cxt)
+{
+	unsigned int id = 0;
+	int rc, has_id = 0;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	DBG(LABEL, ul_debug("DOS: creating new disklabel"));
+
+	if (cxt->script) {
+		char *end = NULL;
+		const char *s = fdisk_script_get_header(cxt->script, "label-id");
+
+		if (s) {
+			errno = 0;
+			id = strtol(s, &end, 16);
+			if (!errno && end && s < end)
+				has_id = 1;
+		}
+	}
+
+	/* random disk signature */
+	if (!has_id)
+		random_get_bytes(&id, sizeof(id));
+
+	dos_init(cxt);
+	rc = fdisk_init_firstsector_buffer(cxt);
+	if (rc)
+		return rc;
+	fdisk_label_set_changed(cxt->label, 1);
+
+	/* Generate an MBR ID for this disk */
+	mbr_set_id(cxt->firstsector, id);
+
+	/* Put MBR signature */
+	mbr_set_magic(cxt->firstsector);
+
+	fdisk_info(cxt, _("Created a new DOS disklabel with disk "
+			 "identifier 0x%08x."), id);
+	return 0;
+}
+
+static int dos_set_disklabel_id(struct fdisk_context *cxt)
+{
+	char *end = NULL, *str = NULL;
+	unsigned int id, old;
+	struct fdisk_dos_label *l;
+	int rc;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	DBG(LABEL, ul_debug("DOS: setting Id"));
+
+	l = self_label(cxt);
+	old = mbr_get_id(cxt->firstsector);
+	rc = fdisk_ask_string(cxt,
+			_("Enter the new disk identifier"), &str);
+	if (rc)
+		return rc;
+
+	errno = 0;
+	id = strtoul(str, &end, 0);
+	if (errno || str == end || (end && *end)) {
+		fdisk_warnx(cxt, _("Incorrect value."));
+		return -EINVAL;
+	}
+
+
+	mbr_set_id(cxt->firstsector, id);
+	l->non_pt_changed = 1;
+	fdisk_label_set_changed(cxt->label, 1);
+
+	fdisk_info(cxt, _("Disk identifier changed from 0x%08x to 0x%08x."),
+			old, id);
+	return 0;
+}
+
+static void get_partition_table_geometry(struct fdisk_context *cxt,
+			unsigned int *ph, unsigned int *ps)
+{
+	unsigned char *bufp = cxt->firstsector;
+	struct dos_partition *p;
+	int i, h, s, hh, ss;
+	int first = 1;
+	int bad = 0;
+
+	hh = ss = 0;
+	for (i = 0; i < 4; i++) {
+		p = mbr_get_partition(bufp, i);
+		if (p->sys_ind != 0) {
+			h = p->eh + 1;
+			s = (p->es & 077);
+			if (first) {
+				hh = h;
+				ss = s;
+				first = 0;
+			} else if (hh != h || ss != s)
+				bad = 1;
+		}
+	}
+
+	if (!first && !bad) {
+		*ph = hh;
+		*ps = ss;
+	}
+
+	DBG(LABEL, ul_debug("DOS PT geometry: heads=%u, sectors=%u", *ph, *ps));
+}
+
+static int dos_reset_alignment(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	/* overwrite necessary stuff by DOS deprecated stuff */
+	if (is_dos_compatible(cxt)) {
+		DBG(LABEL, ul_debug("DOS: reseting alignemnt for DOS-comaptiblem PT"));
+		if (cxt->geom.sectors)
+			cxt->first_lba = cxt->geom.sectors;	/* usually 63 */
+
+		cxt->grain = cxt->sector_size;			/* usually 512 */
+	}
+
+	return 0;
+}
+
+/* TODO: move to include/pt-dos.h and share with libblkid */
+#define AIX_MAGIC_STRING	"\xC9\xC2\xD4\xC1"
+#define AIX_MAGIC_STRLEN	(sizeof(AIX_MAGIC_STRING) - 1)
+
+static int dos_probe_label(struct fdisk_context *cxt)
+{
+	size_t i;
+	unsigned int h = 0, s = 0;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	/* ignore disks with AIX magic number */
+	if (memcmp(cxt->firstsector, AIX_MAGIC_STRING, AIX_MAGIC_STRLEN) == 0)
+		return 0;
+
+	if (!mbr_is_valid_magic(cxt->firstsector))
+		return 0;
+
+	dos_init(cxt);
+
+	get_partition_table_geometry(cxt, &h, &s);
+	if (h && s) {
+		cxt->geom.heads = h;
+	        cxt->geom.sectors = s;
+	}
+
+	for (i = 0; i < 4; i++) {
+		struct pte *pe = self_pte(cxt, i);
+
+		if (is_used_partition(pe->pt_entry))
+			cxt->label->nparts_cur++;
+
+		if (IS_EXTENDED (pe->pt_entry->sys_ind)) {
+			if (cxt->label->nparts_max != 4)
+				fdisk_warnx(cxt, _(
+				"Ignoring extra extended partition %zu"),
+					i + 1);
+			else
+				read_extended(cxt, i);
+		}
+	}
+
+	for (i = 3; i < cxt->label->nparts_max; i++) {
+		struct pte *pe = self_pte(cxt, i);
+		struct fdisk_dos_label *l = self_label(cxt);
+
+		if (!mbr_is_valid_magic(pe->sectorbuffer)) {
+			fdisk_info(cxt, _(
+			"Invalid flag 0x%02x%02x of EBR (for partition %zu) will "
+			"be corrected by w(rite)."),
+				pe->sectorbuffer[510],
+				pe->sectorbuffer[511],
+				i + 1);
+			partition_set_changed(cxt, i, 1);
+
+			/* mark also extended as changed to update the first EBR
+			 * in situation that there is no logical partitions at all */
+			partition_set_changed(cxt, l->ext_index, 1);
+		}
+	}
+
+	return 1;
+}
+
+static void set_partition(struct fdisk_context *cxt,
+			  int i, int doext, fdisk_sector_t start,
+			  fdisk_sector_t stop, int sysid, int boot)
+{
+	struct pte *pe = self_pte(cxt, i);
+	struct dos_partition *p;
+	fdisk_sector_t offset;
+
+	assert(!FDISK_IS_UNDEF(start));
+	assert(!FDISK_IS_UNDEF(stop));
+
+	if (doext) {
+		struct fdisk_dos_label *l = self_label(cxt);
+		p = pe->ex_entry;
+		offset = l->ext_offset;
+	} else {
+		p = pe->pt_entry;
+		offset = pe->offset;
+	}
+
+	DBG(LABEL, ul_debug("DOS: setting partition %d%s, offset=%zu, start=%zu, stop=%zu, sysid=%02x",
+				i, doext ? " [extended]" : "",
+				(size_t) offset,
+				(size_t) (start -  offset),
+				(size_t) (stop - start + 1),
+				sysid));
+
+	p->boot_ind = boot ? ACTIVE_FLAG : 0;
+	p->sys_ind = sysid;
+	dos_partition_set_start(p, start - offset);
+	dos_partition_set_size(p, stop - start + 1);
+
+	if (is_dos_compatible(cxt) && (start/(cxt->geom.sectors*cxt->geom.heads) > 1023))
+		start = cxt->geom.heads*cxt->geom.sectors*1024 - 1;
+	set_hsc(p->bh, p->bs, p->bc, start);
+	if (is_dos_compatible(cxt) && (stop/(cxt->geom.sectors*cxt->geom.heads) > 1023))
+		stop = cxt->geom.heads*cxt->geom.sectors*1024 - 1;
+	set_hsc(p->eh, p->es, p->ec, stop);
+	partition_set_changed(cxt, i, 1);
+}
+
+static fdisk_sector_t get_unused_start(struct fdisk_context *cxt,
+				 int part_n, fdisk_sector_t start,
+				 fdisk_sector_t first[], fdisk_sector_t last[])
+{
+	size_t i;
+
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+		fdisk_sector_t lastplusoff;
+		struct pte *pe = self_pte(cxt, i);
+
+		if (start == pe->offset)
+			start += cxt->first_lba;
+		lastplusoff = last[i] + ((part_n < 4) ? 0 : cxt->first_lba);
+		if (start >= first[i] && start <= lastplusoff)
+			start = lastplusoff + 1;
+	}
+
+	return start;
+}
+
+static void fill_bounds(struct fdisk_context *cxt,
+			fdisk_sector_t *first, fdisk_sector_t *last)
+{
+	size_t i;
+	struct pte *pe = self_pte(cxt, 0);
+	struct dos_partition *p;
+
+	for (i = 0; i < cxt->label->nparts_max; pe++,i++) {
+		p = pe->pt_entry;
+		if (is_cleared_partition(p) || IS_EXTENDED (p->sys_ind)) {
+			first[i] = 0xffffffff;
+			last[i] = 0;
+		} else {
+			first[i] = get_abs_partition_start(pe);
+			last[i]  = get_abs_partition_end(pe);
+		}
+	}
+}
+
+static int get_start_from_user(	struct fdisk_context *cxt,
+				fdisk_sector_t *start,
+				fdisk_sector_t low,
+				fdisk_sector_t dflt,
+				fdisk_sector_t limit,
+				struct fdisk_partition *pa)
+{
+	assert(start);
+
+	/* try to use tepmlate from 'pa' */
+	if (pa && pa->start_follow_default)
+		*start = dflt;
+
+	else if (pa && fdisk_partition_has_start(pa)) {
+		DBG(LABEL, ul_debug("DOS: start: wanted=%ju, low=%ju, limit=%ju",
+				(uintmax_t) pa->start, (uintmax_t) low, (uintmax_t) limit));
+		*start = pa->start;
+		if (*start < low || *start > limit) {
+			fdisk_warnx(cxt, _("Start sector %ju out of range."),
+					(uintmax_t) *start);
+			return -ERANGE;
+		}
+	} else {
+		/* ask user by dialog */
+		struct fdisk_ask *ask = fdisk_new_ask();
+		int rc;
+
+		if (!ask)
+			return -ENOMEM;
+		fdisk_ask_set_query(ask,
+			fdisk_use_cylinders(cxt) ?
+				_("First cylinder") : _("First sector"));
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+		fdisk_ask_number_set_low(ask, fdisk_cround(cxt, low));
+		fdisk_ask_number_set_default(ask, fdisk_cround(cxt, dflt));
+		fdisk_ask_number_set_high(ask, fdisk_cround(cxt, limit));
+
+		rc = fdisk_do_ask(cxt, ask);
+		*start = fdisk_ask_number_get_result(ask);
+		fdisk_unref_ask(ask);
+		if (rc)
+			return rc;
+		if (fdisk_use_cylinders(cxt)) {
+		        *start = (*start - 1)
+				* fdisk_get_units_per_sector(cxt);
+			if (*start < low)
+				*start = low;
+		}
+	}
+
+	DBG(LABEL, ul_debug("DOS: start is %ju", (uintmax_t) *start));
+	return 0;
+}
+
+static fdisk_sector_t get_possible_last(struct fdisk_context *cxt, size_t n)
+{
+	fdisk_sector_t limit;
+
+	if (n >= 4) {
+		/* logical partitions */
+		struct fdisk_dos_label *l = self_label(cxt);
+		struct pte *ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL;
+
+		if (!ext_pe)
+			return 0;
+		limit = get_abs_partition_end(ext_pe);
+	} else {
+		/* primary partitions */
+		if (fdisk_use_cylinders(cxt) || !cxt->total_sectors)
+			limit = cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders - 1;
+		else
+			limit = cxt->total_sectors - 1;
+
+		if (limit > UINT_MAX)
+			limit = UINT_MAX;
+	}
+
+	DBG(LABEL, ul_debug("DOS: last possible sector for #%zu is %ju",
+				n, (uintmax_t) limit));
+	return limit;
+}
+
+/* returns last free sector for area addressed by @start, the first[] and
+ * last[] are fill_bounds() results */
+static fdisk_sector_t get_unused_last(struct fdisk_context *cxt, size_t n,
+				fdisk_sector_t start,
+				fdisk_sector_t first[], fdisk_sector_t last[])
+{
+	size_t i;
+	fdisk_sector_t limit = get_possible_last(cxt, n);
+
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+		struct pte *pe = self_pte(cxt, i);
+
+		if (start < pe->offset && limit >= pe->offset)
+			limit = pe->offset - 1;
+		if (start < first[i] && limit >= first[i])
+			limit = first[i] - 1;
+	}
+
+	DBG(LABEL, ul_debug("DOS: unused sector for #%zu is %ju",
+				n, (uintmax_t) limit));
+	return limit;
+}
+
+static int add_partition(struct fdisk_context *cxt, size_t n,
+			 struct fdisk_partition *pa)
+{
+	int sys, read = 0, rc, isrel = 0;
+	size_t i;
+	struct fdisk_dos_label *l = self_label(cxt);
+	struct dos_partition *p = self_partition(cxt, n);
+	struct pte *ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL;
+
+	fdisk_sector_t start, stop = 0, limit, temp,
+		first[cxt->label->nparts_max],
+		last[cxt->label->nparts_max];
+
+	DBG(LABEL, ul_debug("DOS: adding partition %zu", n));
+
+	sys = pa && pa->type ? pa->type->code : MBR_LINUX_DATA_PARTITION;
+
+	if (is_used_partition(p)) {
+		fdisk_warnx(cxt, _("Partition %zu is already defined.  "
+			           "Delete it before re-adding it."),
+				n + 1);
+		return -EINVAL;
+	}
+	fill_bounds(cxt, first, last);
+	limit = get_possible_last(cxt, n);
+
+	if (n < 4) {
+		if (cxt->parent && fdisk_is_label(cxt->parent, GPT))
+			start = 1;		/* Bad boy modifies hybrid MBR */
+		else {
+			if (cxt->script && pa && fdisk_partition_has_start(pa)
+			    && pa->start < cxt->first_lba
+			    && pa->start >= 1)
+				fdisk_set_first_lba(cxt, 1);
+
+			start = cxt->first_lba;
+		}
+
+		if (l->ext_offset) {
+			assert(ext_pe);
+			first[l->ext_index] = l->ext_offset;
+			last[l->ext_index] = get_abs_partition_end(ext_pe);
+		}
+	} else {
+		assert(ext_pe);
+
+		if (cxt->script && pa && fdisk_partition_has_start(pa)
+		    && pa->start >= l->ext_offset
+		    && pa->start < l->ext_offset + cxt->first_lba)
+			fdisk_set_first_lba(cxt, 1);
+
+		start = l->ext_offset + cxt->first_lba;
+	}
+
+	if (fdisk_use_cylinders(cxt))
+		for (i = 0; i < cxt->label->nparts_max; i++) {
+			first[i] = (fdisk_cround(cxt, first[i]) - 1)
+				* fdisk_get_units_per_sector(cxt);
+		}
+
+	/*
+	 * Ask for first sector
+	 */
+	do {
+		fdisk_sector_t dflt, aligned;
+
+		temp = start;
+		dflt = start = get_unused_start(cxt, n, start, first, last);
+
+		if (n >= 4 && pa && fdisk_partition_has_start(pa) && cxt->script
+		    && cxt->first_lba > 1
+		    && temp == start - cxt->first_lba) {
+			fdisk_set_first_lba(cxt, 1);
+			start = pa->start;
+		}
+
+		/* the default sector should be aligned and unused */
+		do {
+			aligned = fdisk_align_lba_in_range(cxt, dflt, dflt, limit);
+			dflt = get_unused_start(cxt, n, aligned, first, last);
+		} while (dflt != aligned && dflt > aligned && dflt < limit);
+
+		if (dflt >= limit)
+			dflt = start;
+		if (start > limit)
+			break;
+		if (start >= temp + fdisk_get_units_per_sector(cxt)
+		    && read) {
+			fdisk_info(cxt, _("Sector %llu is already allocated."),
+					temp);
+			temp = start;
+			read = 0;
+			if (pa && (fdisk_partition_has_start(pa) ||
+				   pa->start_follow_default))
+				break;
+		}
+
+		if (!read && start == temp) {
+			rc = get_start_from_user(cxt, &start, temp, dflt, limit, pa);
+			if (rc)
+				return rc;
+			read = 1;
+		}
+	} while (start != temp || !read);
+
+	if (n == 4) {
+		/* The first EBR is stored at begin of the extended partition */
+		struct pte *pe = self_pte(cxt, n);
+		pe->offset = l->ext_offset;
+
+	} else if (n > 4) {
+		/* The second (and another) EBR */
+		struct pte *pe = self_pte(cxt, n);
+
+		pe->offset = start - cxt->first_lba;
+		if (pe->offset == l->ext_offset) { /* must be corrected */
+			pe->offset++;
+			if (cxt->first_lba == 1)
+				start++;
+		}
+	}
+
+	limit = get_unused_last(cxt, n, start, first, last);
+
+	if (start > limit) {
+		fdisk_info(cxt, _("No free sectors available."));
+		if (n > 4)
+			cxt->label->nparts_max--;
+		return -ENOSPC;
+	}
+
+	/*
+	 * Ask for last sector
+	 */
+	if (fdisk_cround(cxt, start) == fdisk_cround(cxt, limit))
+		stop = limit;
+	else if (pa && pa->end_follow_default)
+		stop = limit;
+	else if (pa && fdisk_partition_has_size(pa)) {
+		stop = start + pa->size - 1;
+		isrel = pa->size_explicit ? 0 : 1;
+	} else {
+		/* ask user by dialog */
+		struct fdisk_ask *ask = fdisk_new_ask();
+
+		if (!ask)
+			return -ENOMEM;
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+		if (fdisk_use_cylinders(cxt)) {
+			fdisk_ask_set_query(ask, _("Last cylinder, +cylinders or +size{K,M,G,T,P}"));
+			fdisk_ask_number_set_unit(ask,
+				     cxt->sector_size *
+				     fdisk_get_units_per_sector(cxt));
+		} else {
+			fdisk_ask_set_query(ask, _("Last sector, +sectors or +size{K,M,G,T,P}"));
+			fdisk_ask_number_set_unit(ask,cxt->sector_size);
+		}
+
+		fdisk_ask_number_set_low(ask, fdisk_cround(cxt, start));
+		fdisk_ask_number_set_default(ask, fdisk_cround(cxt, limit));
+		fdisk_ask_number_set_high(ask, fdisk_cround(cxt, limit));
+		fdisk_ask_number_set_base(ask, fdisk_cround(cxt, start));	/* base for relative input */
+
+		rc = fdisk_do_ask(cxt, ask);
+		stop = fdisk_ask_number_get_result(ask);
+		isrel = fdisk_ask_number_is_relative(ask);
+		fdisk_unref_ask(ask);
+		if (rc)
+			return rc;
+		if (fdisk_use_cylinders(cxt)) {
+			stop = stop * fdisk_get_units_per_sector(cxt) - 1;
+			if (stop >limit)
+				stop = limit;
+		}
+	}
+
+	DBG(LABEL, ul_debug("DOS: raw stop: %ju", (uintmax_t) stop));
+
+	if (stop > limit)
+		stop = limit;
+
+	if (stop < limit) {
+		if (isrel && alignment_required(cxt)) {
+			/* the last sector has not been exactly requested (but
+			 * defined by +size{K,M,G} convention), so be smart and
+			 * align the end of the partition. The next partition
+			 * will start at phy.block boundary.
+			 */
+			stop = fdisk_align_lba_in_range(cxt, stop, start, limit) - 1;
+			if (stop > limit)
+				stop = limit;
+		}
+	}
+
+	set_partition(cxt, n, 0, start, stop, sys, pa && pa->boot == 1 ? 1 : 0);
+	if (n > 4) {
+		struct pte *pe = self_pte(cxt, n);
+		set_partition(cxt, n - 1, 1, pe->offset, stop,
+					MBR_DOS_EXTENDED_PARTITION, 0);
+	}
+
+	/* report */
+	{
+		struct fdisk_parttype *t =
+			fdisk_label_get_parttype_from_code(cxt->label, sys);
+		fdisk_info_new_partition(cxt, n + 1, start, stop, t);
+		fdisk_unref_parttype(t);
+	}
+
+
+	if (IS_EXTENDED(sys)) {
+		struct pte *pen = self_pte(cxt, n);
+
+		l->ext_index = n;
+		l->ext_offset = start;
+		pen->ex_entry = p;
+	}
+
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+static int add_logical(struct fdisk_context *cxt,
+		       struct fdisk_partition *pa,
+		       size_t *partno)
+{
+	struct pte *pe;
+	int rc;
+
+	assert(cxt);
+	assert(partno);
+	assert(cxt->label);
+	assert(self_label(cxt)->ext_offset);
+
+	DBG(LABEL, ul_debug("DOS: nparts max: %zu", cxt->label->nparts_max));
+	pe = self_pte(cxt, cxt->label->nparts_max);
+
+	if (!pe->sectorbuffer) {
+		pe->sectorbuffer = calloc(1, cxt->sector_size);
+		if (!pe->sectorbuffer)
+			return -ENOMEM;
+		DBG(LABEL, ul_debug("DOS: logical: %zu: new EBR sector buffer %p",
+					cxt->label->nparts_max, pe->sectorbuffer));
+		pe->private_sectorbuffer = 1;
+	}
+	pe->pt_entry = mbr_get_partition(pe->sectorbuffer, 0);
+	pe->ex_entry = pe->pt_entry + 1;
+	pe->offset = 0;
+	partition_set_changed(cxt, cxt->label->nparts_max, 1);
+
+	cxt->label->nparts_max++;
+
+	/* this message makes sense only when we use extended/primary/logical
+	 * dialog. The dialog is disable for scripts, see dos_add_partition() */
+	if (!cxt->script)
+		fdisk_info(cxt, _("Adding logical partition %zu"),
+				cxt->label->nparts_max);
+	*partno = cxt->label->nparts_max - 1;
+	rc = add_partition(cxt, *partno, pa);
+
+	if (rc) {
+		/* reset on error */
+		cxt->label->nparts_max--;
+		pe->pt_entry = NULL;
+		pe->ex_entry = NULL;
+		pe->offset = 0;
+		pe->changed = 0;
+	}
+
+	return rc;
+}
+
+static void check(struct fdisk_context *cxt, size_t n,
+	   unsigned int h, unsigned int s, unsigned int c,
+	   unsigned int start)
+{
+	unsigned int total, real_s, real_c;
+
+	if (!is_dos_compatible(cxt))
+		return;
+
+	real_s = sector(s) - 1;
+	real_c = cylinder(s, c);
+	total = (real_c * cxt->geom.heads + h) * cxt->geom.sectors + real_s;
+
+	if (!total)
+		fdisk_warnx(cxt, _("Partition %zu: contains sector 0"), n);
+	if (h >= cxt->geom.heads)
+		fdisk_warnx(cxt, _("Partition %zu: head %d greater than "
+				   "maximum %d"), n, h + 1, cxt->geom.heads);
+	if (real_s >= cxt->geom.sectors)
+		fdisk_warnx(cxt, _("Partition %zu: sector %d greater than "
+				   "maximum %llu"), n, s, cxt->geom.sectors);
+	if (real_c >= cxt->geom.cylinders)
+		fdisk_warnx(cxt, _("Partition %zu: cylinder %d greater than "
+				   "maximum %llu"),
+				n, real_c + 1,
+				cxt->geom.cylinders);
+
+	if (cxt->geom.cylinders <= 1024 && start != total)
+		fdisk_warnx(cxt, _("Partition %zu: previous sectors %u "
+				   "disagrees with total %u"), n, start, total);
+}
+
+/* check_consistency() and long2chs() added Sat Mar 6 12:28:16 1993,
+ * faith@cs.unc.edu, based on code fragments from pfdisk by Gordon W. Ross,
+ * Jan.  1990 (version 1.2.1 by Gordon W. Ross Aug. 1990; Modified by S.
+ * Lubkin Oct.  1991). */
+
+static void
+long2chs(struct fdisk_context *cxt, unsigned long ls,
+	 unsigned int *c, unsigned int *h, unsigned int *s) {
+	int spc = cxt->geom.heads * cxt->geom.sectors;
+
+	*c = ls / spc;
+	ls = ls % spc;
+	*h = ls / cxt->geom.sectors;
+	*s = ls % cxt->geom.sectors + 1;	/* sectors count from 1 */
+}
+
+static void check_consistency(struct fdisk_context *cxt, struct dos_partition *p,
+			      size_t partition)
+{
+	unsigned int pbc, pbh, pbs;	/* physical beginning c, h, s */
+	unsigned int pec, peh, pes;	/* physical ending c, h, s */
+	unsigned int lbc, lbh, lbs;	/* logical beginning c, h, s */
+	unsigned int lec, leh, les;	/* logical ending c, h, s */
+
+	if (!is_dos_compatible(cxt))
+		return;
+
+	if (!cxt->geom.heads || !cxt->geom.sectors || (partition >= 4))
+		return;		/* do not check extended partitions */
+
+	/* physical beginning c, h, s */
+	pbc = (p->bc & 0xff) | ((p->bs << 2) & 0x300);
+	pbh = p->bh;
+	pbs = p->bs & 0x3f;
+
+	/* physical ending c, h, s */
+	pec = (p->ec & 0xff) | ((p->es << 2) & 0x300);
+	peh = p->eh;
+	pes = p->es & 0x3f;
+
+	/* compute logical beginning (c, h, s) */
+	long2chs(cxt, dos_partition_get_start(p), &lbc, &lbh, &lbs);
+
+	/* compute logical ending (c, h, s) */
+	long2chs(cxt, dos_partition_get_start(p) + dos_partition_get_size(p) - 1, &lec, &leh, &les);
+
+	/* Same physical / logical beginning? */
+	if (cxt->geom.cylinders <= 1024
+	    && (pbc != lbc || pbh != lbh || pbs != lbs)) {
+		fdisk_warnx(cxt, _("Partition %zu: different physical/logical "
+			"beginnings (non-Linux?): "
+			"phys=(%d, %d, %d), logical=(%d, %d, %d)"),
+			partition + 1,
+			pbc, pbh, pbs,
+			lbc, lbh, lbs);
+	}
+
+	/* Same physical / logical ending? */
+	if (cxt->geom.cylinders <= 1024
+	    && (pec != lec || peh != leh || pes != les)) {
+		fdisk_warnx(cxt, _("Partition %zu: different physical/logical "
+			"endings: phys=(%d, %d, %d), logical=(%d, %d, %d)"),
+			partition + 1,
+			pec, peh, pes,
+			lec, leh, les);
+	}
+
+	/* Ending on cylinder boundary? */
+	if (peh != (cxt->geom.heads - 1) || pes != cxt->geom.sectors) {
+		fdisk_warnx(cxt, _("Partition %zu: does not end on "
+				   "cylinder boundary."),
+			partition + 1);
+	}
+}
+
+static int dos_verify_disklabel(struct fdisk_context *cxt)
+{
+	size_t i, j;
+	fdisk_sector_t total = 1, n_sectors = cxt->total_sectors;
+	fdisk_sector_t first[cxt->label->nparts_max],
+		       last[cxt->label->nparts_max];
+	struct dos_partition *p;
+	struct fdisk_dos_label *l = self_label(cxt);
+
+	assert(fdisk_is_label(cxt, DOS));
+
+	fill_bounds(cxt, first, last);
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+		struct pte *pe = self_pte(cxt, i);
+
+		p = self_partition(cxt, i);
+		if (is_used_partition(p) && !IS_EXTENDED(p->sys_ind)) {
+			check_consistency(cxt, p, i);
+			if (get_abs_partition_start(pe) < first[i])
+				fdisk_warnx(cxt, _(
+					"Partition %zu: bad start-of-data."),
+					 i + 1);
+
+			check(cxt, i + 1, p->eh, p->es, p->ec, last[i]);
+			total += last[i] + 1 - first[i];
+
+			if (i == 0)
+				total += get_abs_partition_start(pe) - 1;
+
+			for (j = 0; j < i; j++) {
+				if ((first[i] >= first[j] && first[i] <= last[j])
+				    || ((last[i] <= last[j] && last[i] >= first[j]))) {
+
+					fdisk_warnx(cxt, _("Partition %zu: "
+						"overlaps partition %zu."),
+						j + 1, i + 1);
+
+					total += first[i] >= first[j] ?
+						first[i] : first[j];
+					total -= last[i] <= last[j] ?
+						last[i] : last[j];
+				}
+			}
+		}
+	}
+
+	if (l->ext_offset) {
+		fdisk_sector_t e_last;
+		struct pte *ext_pe = self_pte(cxt, l->ext_index);
+
+		e_last = get_abs_partition_end(ext_pe);
+
+		for (i = 4; i < cxt->label->nparts_max; i++) {
+			total++;
+			p = self_partition(cxt, i);
+
+			if (!p->sys_ind) {
+				if (i != 4 || i + 1 < cxt->label->nparts_max)
+					fdisk_warnx(cxt,
+						_("Partition %zu: empty."),
+						i + 1);
+			} else if (first[i] < l->ext_offset
+				   || last[i] > e_last) {
+
+				fdisk_warnx(cxt, _("Logical partition %zu: "
+					"not entirely in partition %zu."),
+					i + 1, l->ext_index + 1);
+			}
+		}
+	}
+
+	if (total > n_sectors)
+		fdisk_warnx(cxt, _("Total allocated sectors %llu greater "
+			"than the maximum %llu."), total, n_sectors);
+	else if (total < n_sectors)
+		fdisk_warnx(cxt, _("Remaining %lld unallocated %ld-byte "
+			"sectors."), n_sectors - total, cxt->sector_size);
+
+	return 0;
+}
+
+/*
+ * Ask the user for new partition type information (logical, extended).
+ * This function calls the actual partition adding logic - add_partition.
+ *
+ * API callback.
+ */
+static int dos_add_partition(struct fdisk_context *cxt,
+			     struct fdisk_partition *pa,
+			     size_t *partno)
+{
+	size_t i, free_primary = 0, free_sectors = 0;
+	fdisk_sector_t last = 0, grain;
+	int rc = 0;
+	struct fdisk_dos_label *l;
+	struct pte *ext_pe;
+	size_t res;		/* partno */
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	DBG(LABEL, ul_debug("DOS: new partition wanted"));
+
+	l = self_label(cxt);
+	ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL;
+
+	/*
+	 * partition template (@pa) based partitioning
+	 */
+
+	/* pa specifies start within extended partition, add logical */
+	if (pa && fdisk_partition_has_start(pa) && ext_pe
+	    && pa->start >= l->ext_offset
+	    && pa->start <= get_abs_partition_end(ext_pe)) {
+		DBG(LABEL, ul_debug("DOS: pa template %p: add logical", pa));
+		rc = add_logical(cxt, pa, &res);
+		goto done;
+
+	/* pa specifies that extended partition is wanted */
+	} else if (pa && pa->type && pa->type->code == MBR_DOS_EXTENDED_PARTITION) {
+		DBG(LABEL, ul_debug("DOS: pa template %p: add extened", pa));
+		if (l->ext_offset) {
+			fdisk_warnx(cxt, _("Extended partition already exists."));
+			return -EINVAL;
+		}
+		rc = get_partition_unused_primary(cxt, pa, &res);
+		if (rc == 0) {
+			rc = add_partition(cxt, res, pa);
+			goto done;
+		}
+
+	/* pa specifies start, but outside extended partition */
+	} else if (pa && fdisk_partition_has_start(pa) && l->ext_offset) {
+		DBG(LABEL, ul_debug("DOS: pa template %p: add primary", pa));
+		rc = get_partition_unused_primary(cxt, pa, &res);
+		if (rc == 0) {
+			rc = add_partition(cxt, res, pa);
+			goto done;
+		}
+	}
+
+	/*
+	 * dialog driven partitioning (it does not mean that @pa template is
+	 * completely ignored!)
+	 */
+
+	/* check if there is space for primary partition */
+	grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1;
+	last = cxt->first_lba;
+
+	for (i = 0; i < 4; i++) {
+		struct dos_partition *p = self_partition(cxt, i);
+
+		if (is_used_partition(p)) {
+			fdisk_sector_t start = dos_partition_get_start(p);
+			if (last + grain <= start)
+				free_sectors = 1;
+			last = start + dos_partition_get_size(p);
+		} else
+			free_primary++;
+	}
+	if (last + grain < cxt->total_sectors - 1)
+		free_sectors = 1;
+
+	if (!free_primary && cxt->label->nparts_max >= MAXIMUM_PARTS) {
+		fdisk_info(cxt, _("The maximum number of partitions has "
+				  "been created."));
+		return -EINVAL;
+	}
+	rc = 1;
+
+	if (!free_primary || !free_sectors) {
+		DBG(LABEL, ul_debug("DOS: primary impossible, add logical"));
+		if (l->ext_offset) {
+			if (!pa || fdisk_partition_has_start(pa)) {
+				if (!free_primary)
+					fdisk_info(cxt, _("All primary partitions are in use."));
+				else if (!free_sectors)
+					fdisk_info(cxt, _("All space for primary partitions is in use."));
+			}
+			rc = add_logical(cxt, pa, &res);
+		} else {
+			fdisk_info(cxt,
+			     _(	"Impossible to create another primary partition. "
+				"If you want to create more partitions, you must "
+				"replace a primary partition with an extended "
+				"partition first."));
+			return -EINVAL;
+		}
+	} else if (cxt->label->nparts_max >= MAXIMUM_PARTS) {
+		fdisk_info(cxt, _("All logical partitions are in use. "
+				  "Adding a primary partition."));
+		rc = get_partition_unused_primary(cxt, pa, &res);
+		if (rc == 0)
+			rc = add_partition(cxt, res, pa);
+	} else {
+		char hint[BUFSIZ];
+		struct fdisk_ask *ask;
+		int c;
+
+		/* the default layout for scripts is to create primary partitions */
+		if (cxt->script) {
+			rc = get_partition_unused_primary(cxt, pa, &res);
+			if (rc == 0)
+				rc = add_partition(cxt, res, pa);
+			goto done;
+		}
+
+		ask = fdisk_new_ask();
+		if (!ask)
+			return -ENOMEM;
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_MENU);
+		fdisk_ask_set_query(ask, _("Partition type"));
+		fdisk_ask_menu_set_default(ask, free_primary == 1
+						&& !l->ext_offset ? 'e' : 'p');
+		snprintf(hint, sizeof(hint),
+				_("%zu primary, %d extended, %zu free"),
+				4 - (l->ext_offset ? 1 : 0) - free_primary,
+				l->ext_offset ? 1 : 0,
+				free_primary);
+
+		fdisk_ask_menu_add_item(ask, 'p', _("primary"), hint);
+		if (!l->ext_offset)
+			fdisk_ask_menu_add_item(ask, 'e', _("extended"), _("container for logical partitions"));
+		else
+			fdisk_ask_menu_add_item(ask, 'l', _("logical"), _("numbered from 5"));
+
+		rc = fdisk_do_ask(cxt, ask);
+		if (rc)
+			return rc;
+		fdisk_ask_menu_get_result(ask, &c);
+		fdisk_unref_ask(ask);
+
+		if (c == 'p') {
+			rc = get_partition_unused_primary(cxt, pa, &res);
+			if (rc == 0)
+				rc = add_partition(cxt, res, pa);
+			goto done;
+		} else if (c == 'l' && l->ext_offset) {
+			rc = add_logical(cxt, pa, &res);
+			goto done;
+		} else if (c == 'e' && !l->ext_offset) {
+			rc = get_partition_unused_primary(cxt, pa, &res);
+			if (rc == 0) {
+				struct fdisk_partition *xpa = NULL;
+				struct fdisk_parttype *t;
+
+				t = fdisk_label_get_parttype_from_code(cxt->label,
+						MBR_DOS_EXTENDED_PARTITION);
+				if (!pa) {
+					pa = xpa = fdisk_new_partition();
+					if (!xpa)
+						return -ENOMEM;
+				}
+				fdisk_partition_set_type(pa, t);
+				rc = add_partition(cxt, res, pa);
+				if (xpa) {
+					fdisk_unref_partition(xpa);
+					pa = NULL;
+				}
+			}
+			goto done;
+		} else
+			fdisk_warnx(cxt, _("Invalid partition type `%c'."), c);
+	}
+done:
+	if (rc == 0) {
+		cxt->label->nparts_cur++;
+		if (partno)
+			*partno = res;
+	}
+	return rc;
+}
+
+static int write_sector(struct fdisk_context *cxt, fdisk_sector_t secno,
+			       unsigned char *buf)
+{
+	int rc;
+
+	rc = seek_sector(cxt, secno);
+	if (rc != 0) {
+		fdisk_warn(cxt, _("Cannot write sector %jd: seek failed"),
+				(uintmax_t) secno);
+		return rc;
+	}
+
+	DBG(LABEL, ul_debug("DOS: writting to sector %ju", (uintmax_t) secno));
+
+	if (write(cxt->dev_fd, buf, cxt->sector_size) != (ssize_t) cxt->sector_size)
+		return -errno;
+	return 0;
+}
+
+static int dos_write_disklabel(struct fdisk_context *cxt)
+{
+	struct fdisk_dos_label *l = self_label(cxt);
+	size_t i;
+	int rc = 0, mbr_changed = 0;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	mbr_changed = l->non_pt_changed;
+
+	/* MBR (primary partitions) */
+	if (!mbr_changed) {
+		for (i = 0; i < 4; i++) {
+			struct pte *pe = self_pte(cxt, i);
+			if (pe->changed)
+				mbr_changed = 1;
+		}
+	}
+	if (mbr_changed) {
+		mbr_set_magic(cxt->firstsector);
+		rc = write_sector(cxt, 0, cxt->firstsector);
+		if (rc)
+			goto done;
+	}
+
+	if (cxt->label->nparts_max <= 4 && l->ext_offset) {
+		/* we have empty extended partition, check if the partition has
+		 * been modified and then cleanup possible remaining EBR  */
+		struct pte *pe = self_pte(cxt, l->ext_index);
+		unsigned char empty[512] = { 0 };
+		fdisk_sector_t off = pe ? get_abs_partition_start(pe) : 0;
+
+		if (off && pe->changed) {
+			mbr_set_magic(empty);
+			write_sector(cxt, off, empty);
+		}
+	}
+
+	/* EBR (logical partitions) */
+	for (i = 4; i < cxt->label->nparts_max; i++) {
+		struct pte *pe = self_pte(cxt, i);
+
+		if (!pe->changed || !pe->offset || !pe->sectorbuffer)
+			continue;
+
+		mbr_set_magic(pe->sectorbuffer);
+		rc = write_sector(cxt, pe->offset, pe->sectorbuffer);
+		if (rc)
+			goto done;
+	}
+
+done:
+	return rc;
+}
+
+static int dos_locate_disklabel(struct fdisk_context *cxt, int n,
+		const char **name, off_t *offset, size_t *size)
+{
+	assert(cxt);
+
+	*name = NULL;
+	*offset = 0;
+	*size = 0;
+
+	switch (n) {
+	case 0:
+		*name = "MBR";
+		*offset = 0;
+		*size = 512;
+		break;
+	default:
+		/* extended partitions */
+		if (n - 1 + 4 < cxt->label->nparts_max) {
+			struct pte *pe = self_pte(cxt, n - 1 + 4);
+
+			assert(pe->private_sectorbuffer);
+
+			*name = "EBR";
+			*offset = pe->offset * cxt->sector_size;
+			*size = 512;
+		} else
+			return 1;
+		break;
+	}
+
+	return 0;
+}
+
+/*
+ * Check whether partition entries are ordered by their starting positions.
+ * Return 0 if OK. Return i if partition i should have been earlier.
+ * Two separate checks: primary and logical partitions.
+ */
+static int wrong_p_order(struct fdisk_context *cxt, size_t *prev)
+{
+	size_t last_p_start_pos = 0, p_start_pos;
+	size_t i, last_i = 0;
+
+	for (i = 0 ; i < cxt->label->nparts_max; i++) {
+
+		struct pte *pe = self_pte(cxt, i);
+		struct dos_partition *p = pe->pt_entry;
+
+		if (i == 4) {
+			last_i = 4;
+			last_p_start_pos = 0;
+		}
+		if (is_used_partition(p)) {
+			p_start_pos = get_abs_partition_start(pe);
+
+			if (last_p_start_pos > p_start_pos) {
+				if (prev)
+					*prev = last_i;
+				return i;
+			}
+
+			last_p_start_pos = p_start_pos;
+			last_i = i;
+		}
+	}
+	return 0;
+}
+
+static int dos_list_disklabel(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	return 0;
+}
+
+static int dos_get_partition(struct fdisk_context *cxt, size_t n,
+			     struct fdisk_partition *pa)
+{
+	struct dos_partition *p;
+	struct pte *pe;
+	struct fdisk_dos_label *lb;
+
+	assert(cxt);
+	assert(pa);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	lb = self_label(cxt);
+	pe = self_pte(cxt, n);
+	p = pe->pt_entry;
+	pa->used = !is_cleared_partition(p);
+	if (!pa->used)
+		return 0;
+
+	pa->type = dos_partition_parttype(cxt, p);
+	pa->boot = p->boot_ind == ACTIVE_FLAG ? 1 : 0;
+	pa->start = get_abs_partition_start(pe);
+	pa->size = dos_partition_get_size(p);
+	pa->container = lb->ext_offset && n == lb->ext_index;
+
+	if (n >= 4)
+		pa->parent_partno = lb->ext_index;
+
+	if (p->boot_ind && asprintf(&pa->attrs, "%02x", p->boot_ind) < 0)
+		return -ENOMEM;
+
+	/* start C/H/S */
+	if (asprintf(&pa->start_chs, "%d/%d/%d",
+				cylinder(p->bs, p->bc),
+				sector(p->bs),
+				p->bh) < 0)
+		return -ENOMEM;
+
+	/* end C/H/S */
+	if (asprintf(&pa->end_chs, "%d/%d/%d",
+				cylinder(p->es, p->ec),
+				sector(p->es),
+				p->eh) < 0)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int dos_set_partition(struct fdisk_context *cxt, size_t n,
+			     struct fdisk_partition *pa)
+{
+	struct fdisk_dos_label *l;
+	struct dos_partition *p;
+	struct pte *pe;
+	fdisk_sector_t start, size;
+
+	assert(cxt);
+	assert(pa);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	if (n >= cxt->label->nparts_max)
+		return -EINVAL;
+
+	if (pa->type && IS_EXTENDED(pa->type->code)) {
+		fdisk_warnx(cxt, _("You cannot change a partition into an "
+			"extended one or vice versa. Delete it first."));
+		return -EINVAL;
+	}
+
+	if (pa->type && !pa->type->code)
+		fdisk_warnx(cxt, _("Type 0 means free space to many systems. "
+				   "Having partitions of type 0 is probably unwise."));
+	l = self_label(cxt);
+	p = self_partition(cxt, n);
+	pe = self_pte(cxt, n);
+
+	FDISK_INIT_UNDEF(start);
+	FDISK_INIT_UNDEF(size);
+
+	if (fdisk_partition_has_start(pa))
+		start = pa->start;
+	if (fdisk_partition_has_size(pa))
+		size = pa->size;
+
+	if (pa->end_follow_default) {
+		fdisk_sector_t first[cxt->label->nparts_max],
+			 last[cxt->label->nparts_max],
+			 xlast;
+		struct pte *ext = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL; 
+
+		fill_bounds(cxt, first, last);
+
+		if (ext && l->ext_offset) {
+			first[l->ext_index] = l->ext_offset;
+			last[l->ext_index] = get_abs_partition_end(ext);
+		}
+		if (FDISK_IS_UNDEF(start))
+			start = get_abs_partition_start(pe);
+
+		DBG(LABEL, ul_debug("DOS: #%zu    now %ju +%ju sectors",
+			n, (uintmax_t) start, (uintmax_t) dos_partition_get_size(p)));
+
+		xlast = get_unused_last(cxt, n, start, first, last);
+		size = xlast ? xlast - start + 1: dos_partition_get_size(p);
+
+		DBG(LABEL, ul_debug("DOS: #%zu wanted %ju +%ju sectors",
+			n, (uintmax_t) start, (uintmax_t) size));
+	}
+
+	if (!FDISK_IS_UNDEF(start) || !FDISK_IS_UNDEF(size)) {
+		DBG(LABEL, ul_debug("DOS: resize partition"));
+
+		if (FDISK_IS_UNDEF(start))
+			start = get_abs_partition_start(pe);
+		if (FDISK_IS_UNDEF(size))
+			size = dos_partition_get_size(p);
+
+		set_partition(cxt, n, 0, start, start + size - 1,
+				pa->type  ? pa->type->code : p->sys_ind,
+				pa->boot == 1);
+	} else {
+		DBG(LABEL, ul_debug("DOS: keep size, modify properties"));
+		if (pa->type)
+			p->sys_ind = pa->type->code;
+		if (!FDISK_IS_UNDEF(pa->boot))
+			p->boot_ind = pa->boot == 1 ? ACTIVE_FLAG : 0;
+	}
+
+	partition_set_changed(cxt, n, 1);
+	return 0;
+}
+
+static void print_chain_of_logicals(struct fdisk_context *cxt)
+{
+	size_t i;
+	struct fdisk_dos_label *l = self_label(cxt);
+
+	fputc('\n', stdout);
+
+	for (i = 4; i < cxt->label->nparts_max; i++) {
+		struct pte *pe = self_pte(cxt, i);
+
+		fprintf(stderr, "#%02zu EBR [%10ju], "
+			"data[start=%10ju (%10ju), size=%10ju], "
+			"link[start=%10ju (%10ju), size=%10ju]\n",
+			i, (uintmax_t) pe->offset,
+			/* data */
+			(uintmax_t) dos_partition_get_start(pe->pt_entry),
+			(uintmax_t) get_abs_partition_start(pe),
+			(uintmax_t) dos_partition_get_size(pe->pt_entry),
+			/* link */
+			(uintmax_t) dos_partition_get_start(pe->ex_entry),
+			(uintmax_t) l->ext_offset + dos_partition_get_start(pe->ex_entry),
+			(uintmax_t) dos_partition_get_size(pe->ex_entry));
+	}
+}
+
+static int cmp_ebr_offsets(const void *a, const void *b)
+{
+	struct pte *ae = (struct pte *) a,
+		   *be = (struct pte *) b;
+
+	if (ae->offset == 0 && be->offset == 0)
+		return 0;
+	if (ae->offset == 0)
+		return 1;
+	if (be->offset == 0)
+		return -1;
+
+	return cmp_numbers(ae->offset, be->offset);
+}
+
+/*
+ * Fix the chain of logicals.
+ *
+ * The function does not modify data partitions within EBR tables
+ * (pte->pt_entry). It sorts the chain by EBR offsets and then update links
+ * (pte->ex_entry) between EBR tables.
+ *
+ */
+static void fix_chain_of_logicals(struct fdisk_context *cxt)
+{
+	struct fdisk_dos_label *l = self_label(cxt);
+	struct pte *last;
+	size_t i;
+
+	DBG(LABEL, print_chain_of_logicals(cxt));
+
+	/* Sort chain by EBR offsets */
+	qsort(&l->ptes[4], cxt->label->nparts_max - 4, sizeof(struct pte),
+			cmp_ebr_offsets);
+
+again:
+	/* Sort data partitions by start */
+	for (i = 4; i < cxt->label->nparts_max - 1; i++) {
+		struct pte *cur = self_pte(cxt, i),
+			   *nxt = self_pte(cxt, i + 1);
+
+		if (get_abs_partition_start(cur) >
+		    get_abs_partition_start(nxt)) {
+
+			struct dos_partition tmp = *cur->pt_entry;
+			fdisk_sector_t cur_start = get_abs_partition_start(cur),
+				 nxt_start = get_abs_partition_start(nxt);
+
+			/* swap data partitions */
+			*cur->pt_entry = *nxt->pt_entry;
+			*nxt->pt_entry = tmp;
+
+			/* Recount starts according to EBR offsets, the absolute
+			 * address tas to be still the same! */
+			dos_partition_set_start(cur->pt_entry, nxt_start - cur->offset);
+			dos_partition_set_start(nxt->pt_entry, cur_start - nxt->offset);
+
+			partition_set_changed(cxt, i, 1);
+			partition_set_changed(cxt, i + 1, 1);
+			goto again;
+		}
+	}
+
+	/* Update EBR links */
+	for (i = 4; i < cxt->label->nparts_max - 1; i++) {
+		struct pte *cur = self_pte(cxt, i),
+			   *nxt = self_pte(cxt, i + 1);
+
+		fdisk_sector_t noff = nxt->offset - l->ext_offset,
+			 ooff = dos_partition_get_start(cur->ex_entry);
+
+		if (noff == ooff)
+			continue;
+
+		DBG(LABEL, ul_debug("DOS: fix EBR [%10ju] link %ju -> %ju",
+			(uintmax_t) cur->offset,
+			(uintmax_t) ooff, (uintmax_t) noff));
+
+		set_partition(cxt, i, 1, nxt->offset,
+				get_abs_partition_end(nxt),
+				MBR_DOS_EXTENDED_PARTITION, 0);
+	}
+
+	/* always terminate the chain ! */
+	last = self_pte(cxt, cxt->label->nparts_max - 1);
+	if (last) {
+		clear_partition(last->ex_entry);
+		partition_set_changed(cxt, cxt->label->nparts_max - 1, 1);
+	}
+
+	DBG(LABEL, print_chain_of_logicals(cxt));
+}
+
+static int dos_reorder(struct fdisk_context *cxt)
+{
+	struct pte *pei, *pek;
+	size_t i,k;
+
+	if (!wrong_p_order(cxt, NULL)) {
+		fdisk_info(cxt, _("Nothing to do. Ordering is correct already."));
+		return 0;
+	}
+
+	while ((i = wrong_p_order(cxt, &k)) != 0 && i < 4) {
+		/* partition i should have come earlier, move it */
+		/* We have to move data in the MBR */
+		struct dos_partition *pi, *pk, *pe, pbuf;
+		pei = self_pte(cxt, i);
+		pek = self_pte(cxt, k);
+
+		pe = pei->ex_entry;
+		pei->ex_entry = pek->ex_entry;
+		pek->ex_entry = pe;
+
+		pi = pei->pt_entry;
+		pk = pek->pt_entry;
+
+		memmove(&pbuf, pi, sizeof(struct dos_partition));
+		memmove(pi, pk, sizeof(struct dos_partition));
+		memmove(pk, &pbuf, sizeof(struct dos_partition));
+
+		partition_set_changed(cxt, i, 1);
+		partition_set_changed(cxt, k, 1);
+	}
+
+	if (i)
+		fix_chain_of_logicals(cxt);
+
+	fdisk_info(cxt, _("Done."));
+	return 0;
+}
+
+/* TODO: use fdisk_set_partition() API */
+int fdisk_dos_move_begin(struct fdisk_context *cxt, size_t i)
+{
+	struct pte *pe;
+	struct dos_partition *p;
+	unsigned int new, free_start, curr_start, last;
+	uintmax_t res = 0;
+	size_t x;
+	int rc;
+
+	assert(cxt);
+	assert(fdisk_is_label(cxt, DOS));
+
+	pe = self_pte(cxt, i);
+	p = pe->pt_entry;
+
+	if (!is_used_partition(p) || IS_EXTENDED (p->sys_ind)) {
+		fdisk_warnx(cxt, _("Partition %zu: no data area."), i + 1);
+		return 0;
+	}
+
+	/* the default start is at the second sector of the disk or at the
+	 * second sector of the extended partition
+	 */
+	free_start = pe->offset ? pe->offset + 1 : 1;
+
+	curr_start = get_abs_partition_start(pe);
+
+	/* look for a free space before the current start of the partition */
+	for (x = 0; x < cxt->label->nparts_max; x++) {
+		unsigned int end;
+		struct pte *prev_pe = self_pte(cxt, x);
+		struct dos_partition *prev_p = prev_pe->pt_entry;
+
+		if (!prev_p)
+			continue;
+		end = get_abs_partition_start(prev_pe)
+		      + dos_partition_get_size(prev_p);
+
+		if (is_used_partition(prev_p) &&
+		    end > free_start && end <= curr_start)
+			free_start = end;
+	}
+
+	last = get_abs_partition_end(pe);
+
+	rc = fdisk_ask_number(cxt, free_start, curr_start, last,
+			_("New beginning of data"), &res);
+	if (rc)
+		return rc;
+
+	new = res - pe->offset;
+
+	if (new != dos_partition_get_size(p)) {
+		unsigned int sects = dos_partition_get_size(p)
+				+ dos_partition_get_start(p) - new;
+
+		dos_partition_set_size(p, sects);
+		dos_partition_set_start(p, new);
+
+		partition_set_changed(cxt, i, 1);
+	}
+
+	return rc;
+}
+
+static int dos_partition_is_used(
+		struct fdisk_context *cxt,
+		size_t i)
+{
+	struct dos_partition *p;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	if (i >= cxt->label->nparts_max)
+		return 0;
+
+	p = self_partition(cxt, i);
+
+	return p && !is_cleared_partition(p);
+}
+
+static int dos_toggle_partition_flag(
+		struct fdisk_context *cxt,
+		size_t i,
+		unsigned long flag)
+{
+	struct dos_partition *p;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, DOS));
+
+	if (i >= cxt->label->nparts_max)
+		return -EINVAL;
+
+	p = self_partition(cxt, i);
+
+	switch (flag) {
+	case DOS_FLAG_ACTIVE:
+		if (IS_EXTENDED(p->sys_ind) && !p->boot_ind)
+			fdisk_warnx(cxt, _("Partition %zu: is an extended "
+					"partition."), i + 1);
+
+		p->boot_ind = (p->boot_ind ? 0 : ACTIVE_FLAG);
+		partition_set_changed(cxt, i, 1);
+		fdisk_info(cxt,	p->boot_ind ?
+			_("The bootable flag on partition %zu is enabled now.") :
+			_("The bootable flag on partition %zu is disabled now."),
+			i + 1);
+		break;
+	default:
+		return 1;
+	}
+
+	return 0;
+}
+
+static const struct fdisk_field dos_fields[] =
+{
+	/* basic */
+	{ FDISK_FIELD_DEVICE,	N_("Device"),	 10,	0 },
+	{ FDISK_FIELD_BOOT,	N_("Boot"),	  1,	0 },
+	{ FDISK_FIELD_START,	N_("Start"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_END,	N_("End"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SECTORS,	N_("Sectors"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_CYLINDERS,N_("Cylinders"),  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SIZE,	N_("Size"),	  5,	FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY },
+	{ FDISK_FIELD_TYPEID,	N_("Id"),	  2,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_TYPE,	N_("Type"),	0.1,	0 },
+
+	/* expert mode */
+	{ FDISK_FIELD_SADDR,	N_("Start-C/H/S"), 1,   FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL },
+	{ FDISK_FIELD_EADDR,	N_("End-C/H/S"),   1,   FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL },
+	{ FDISK_FIELD_ATTR,	N_("Attrs"),	   2,   FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL }
+
+};
+
+static const struct fdisk_label_operations dos_operations =
+{
+	.probe		= dos_probe_label,
+	.write		= dos_write_disklabel,
+	.verify		= dos_verify_disklabel,
+	.create		= dos_create_disklabel,
+	.locate		= dos_locate_disklabel,
+	.list		= dos_list_disklabel,
+	.reorder	= dos_reorder,
+	.get_id		= dos_get_disklabel_id,
+	.set_id		= dos_set_disklabel_id,
+
+	.get_part	= dos_get_partition,
+	.set_part	= dos_set_partition,
+	.add_part	= dos_add_partition,
+	.del_part	= dos_delete_partition,
+
+	.part_toggle_flag = dos_toggle_partition_flag,
+	.part_is_used	= dos_partition_is_used,
+
+	.reset_alignment = dos_reset_alignment,
+
+	.deinit		= dos_deinit,
+};
+
+/*
+ * allocates DOS in-memory stuff
+ */
+struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt)
+{
+	struct fdisk_label *lb;
+	struct fdisk_dos_label *dos;
+
+	assert(cxt);
+
+	dos = calloc(1, sizeof(*dos));
+	if (!dos)
+		return NULL;
+
+	/* initialize generic part of the driver */
+	lb = (struct fdisk_label *) dos;
+	lb->name = "dos";
+	lb->id = FDISK_DISKLABEL_DOS;
+	lb->op = &dos_operations;
+	lb->parttypes = dos_parttypes;
+	lb->nparttypes = ARRAY_SIZE(dos_parttypes) - 1;
+	lb->fields = dos_fields;
+	lb->nfields = ARRAY_SIZE(dos_fields);
+
+	return lb;
+}
+
+/**
+ * fdisk_dos_enable_compatible:
+ * @lb: DOS label (see fdisk_get_label())
+ * @enable: 0 or 1
+ *
+ * Enables deprecated DOS compatible mode, in this mode library checks for
+ * cylinders boundary, cases about CHS addressing and another obscure things.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable)
+{
+	struct fdisk_dos_label *dos = (struct fdisk_dos_label *) lb;
+
+	if (!lb)
+		return -EINVAL;
+
+	dos->compatible = enable;
+	if (enable)
+		lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+	return 0;
+}
+
+/**
+ * fdisk_dos_is_compatible:
+ * @lb: DOS label
+ *
+ * Returns: 0 if DOS compatibility disabled, 1 if enabled
+ */
+int fdisk_dos_is_compatible(struct fdisk_label *lb)
+{
+	return ((struct fdisk_dos_label *) lb)->compatible;
+}
diff --git a/libblkid/libfdisk/src/fdiskP.h b/libblkid/libfdisk/src/fdiskP.h
new file mode 100644
index 0000000..b169a9f
--- /dev/null
+++ b/libblkid/libfdisk/src/fdiskP.h
@@ -0,0 +1,438 @@
+/*
+ * fdiskP.h - private library header file
+ *
+ * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#ifndef _LIBFDISK_PRIVATE_H
+#define _LIBFDISK_PRIVATE_H
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+
+#include "c.h"
+#include "libfdisk.h"
+
+#include "nls.h"		/* temporary before dialog API will be implamented */
+#include "list.h"
+#include "debug.h"
+#include <stdio.h>
+#include <stdarg.h>
+
+/* features */
+#define CONFIG_LIBFDISK_ASSERT
+
+#ifdef CONFIG_LIBFDISK_ASSERT
+#include <assert.h>
+#endif
+
+/*
+ * Debug
+ */
+#define LIBFDISK_DEBUG_HELP	(1 << 0)
+#define LIBFDISK_DEBUG_INIT	(1 << 1)
+#define LIBFDISK_DEBUG_CXT	(1 << 2)
+#define LIBFDISK_DEBUG_LABEL    (1 << 3)
+#define LIBFDISK_DEBUG_ASK      (1 << 4)
+#define LIBFDISK_DEBUG_PART	(1 << 6)
+#define LIBFDISK_DEBUG_PARTTYPE	(1 << 7)
+#define LIBFDISK_DEBUG_TAB	(1 << 8)
+#define LIBFDISK_DEBUG_SCRIPT	(1 << 9)
+#define LIBFDISK_DEBUG_ALL	0xFFFF
+
+UL_DEBUG_DECLARE_MASK(libfdisk);
+#define DBG(m, x)	__UL_DBG(libfdisk, LIBFDISK_DEBUG_, m, x)
+#define ON_DBG(m, x)	__UL_DBG_CALL(libfdisk, LIBFDISK_DEBUG_, m, x)
+#define DBG_FLUSH	__UL_DBG_FLUSH(libfdisk, LIBFDISK_DEBUG_)
+
+#ifdef TEST_PROGRAM
+struct fdisk_test {
+	const char	*name;
+	int		(*body)(struct fdisk_test *ts, int argc, char *argv[]);
+	const char	*usage;
+};
+
+/* test.c */
+extern int fdisk_run_test(struct fdisk_test *tests, int argc, char *argv[]);
+#endif
+
+
+/*
+ * Generic iterator
+ */
+struct fdisk_iter {
+        struct list_head        *p;		/* current position */
+        struct list_head        *head;		/* start position */
+	int			direction;	/* FDISK_ITER_{FOR,BACK}WARD */
+};
+
+#define IS_ITER_FORWARD(_i)	((_i)->direction == FDISK_ITER_FORWARD)
+#define IS_ITER_BACKWARD(_i)	((_i)->direction == FDISK_ITER_BACKWARD)
+
+#define FDISK_ITER_INIT(itr, list) \
+	do { \
+		(itr)->p = IS_ITER_FORWARD(itr) ? \
+				(list)->next : (list)->prev; \
+		(itr)->head = (list); \
+	} while(0)
+
+#define FDISK_ITER_ITERATE(itr, res, restype, member) \
+	do { \
+		res = list_entry((itr)->p, restype, member); \
+		(itr)->p = IS_ITER_FORWARD(itr) ? \
+				(itr)->p->next : (itr)->p->prev; \
+	} while(0)
+
+/*
+ * Partition types
+ */
+struct fdisk_parttype {
+	unsigned int	code;		/* type as number or zero */
+	char		*name;		/* description */
+	char		*typestr;	/* type as string or NULL */
+
+	unsigned int	flags;		/* FDISK_PARTTYPE_* flags */
+	int		refcount;	/* reference counter for allocated types */
+};
+
+enum {
+	FDISK_PARTTYPE_UNKNOWN		= (1 << 1),
+	FDISK_PARTTYPE_INVISIBLE	= (1 << 2),
+	FDISK_PARTTYPE_ALLOCATED	= (1 << 3)
+};
+
+#define fdisk_parttype_is_invisible(_x)	((_x) && ((_x)->flags & FDISK_PARTTYPE_INVISIBLE))
+#define fdisk_parttype_is_allocated(_x)	((_x) && ((_x)->flags & FDISK_PARTTYPE_ALLOCATED))
+
+struct fdisk_partition {
+	int		refcount;		/* reference counter */
+
+	size_t		partno;			/* partition number */
+	size_t		parent_partno;		/* for logical partitions */
+
+	fdisk_sector_t	start;			/* first sectors */
+	fdisk_sector_t	size;			/* size in sectors */
+
+	char		*name;			/* partition name */
+	char		*uuid;			/* partition UUID */
+	char		*attrs;			/* partition flags/attributes converted to string */
+	struct fdisk_parttype	*type;		/* partition type */
+
+	struct list_head	parts;		/* list of partitions */
+
+	/* extra fields for partition_to_string() */
+	char		start_post;		/* start postfix  (e.g. '+') */
+	char		end_post;		/* end postfix */
+	char		size_post;		/* size postfix */
+
+	uint64_t	fsize;			/* bsd junk */
+	uint64_t	bsize;
+	uint64_t	cpg;
+
+	char		*start_chs;		/* start C/H/S in string */
+	char		*end_chs;		/* end C/H/S in string */
+
+	unsigned int	boot;			/* MBR: bootable */
+
+	unsigned int	container : 1,			/* container partition (e.g. extended partition) */
+			end_follow_default : 1,		/* use default end */
+			freespace : 1,			/* this is free space */
+			partno_follow_default : 1,	/* use default partno */
+			size_explicit : 1,		/* don't align the size */
+			start_follow_default : 1,	/* use default start */
+			used : 1,			/* partition already used */
+			wholedisk : 1;			/* special system partition */
+};
+
+#define FDISK_INIT_UNDEF(_x)	((_x) = (__typeof__(_x)) -1)
+#define FDISK_IS_UNDEF(_x)	((_x) == (__typeof__(_x)) -1)
+
+struct fdisk_table {
+	struct list_head	parts;		/* partitions */
+	int			refcount;
+	size_t			nents;		/* number of partitions */
+};
+
+/*
+ * Legacy CHS based geometry
+ */
+struct fdisk_geometry {
+	unsigned int heads;
+	fdisk_sector_t sectors;
+	fdisk_sector_t cylinders;
+};
+
+/*
+ * Label specific operations
+ */
+struct fdisk_label_operations {
+	/* probe disk label */
+	int (*probe)(struct fdisk_context *cxt);
+	/* write in-memory changes to disk */
+	int (*write)(struct fdisk_context *cxt);
+	/* verify the partition table */
+	int (*verify)(struct fdisk_context *cxt);
+	/* create new disk label */
+	int (*create)(struct fdisk_context *cxt);
+	/* list disklabel details */
+	int (*list)(struct fdisk_context *cxt);
+	/* returns offset and size of the 'n' part of the PT */
+	int (*locate)(struct fdisk_context *cxt, int n, const char **name, off_t *offset, size_t *size);
+	/* reorder partitions */
+	int (*reorder)(struct fdisk_context *cxt);
+
+	/* get disk label ID */
+	int (*get_id)(struct fdisk_context *cxt, char **id);
+	/* set disk label ID */
+	int (*set_id)(struct fdisk_context *cxt);
+
+
+	/* new partition */
+	int (*add_part)(struct fdisk_context *cxt, struct fdisk_partition *pa,
+						size_t *partno);
+	/* delete partition */
+	int (*del_part)(struct fdisk_context *cxt, size_t partnum);
+
+	/* fill in partition struct */
+	int (*get_part)(struct fdisk_context *cxt, size_t n,
+						struct fdisk_partition *pa);
+	/* modify partition */
+	int (*set_part)(struct fdisk_context *cxt, size_t n,
+						struct fdisk_partition *pa);
+
+	/* return state of the partition */
+	int (*part_is_used)(struct fdisk_context *cxt, size_t partnum);
+
+	int (*part_toggle_flag)(struct fdisk_context *cxt, size_t i, unsigned long flag);
+
+	/* refresh alignment setting */
+	int (*reset_alignment)(struct fdisk_context *cxt);
+
+	/* free in-memory label stuff */
+	void (*free)(struct fdisk_label *lb);
+
+	/* deinit in-memory label stuff */
+	void (*deinit)(struct fdisk_label *lb);
+};
+
+/*
+ * The fields describes how to display libfdisk_partition
+ */
+struct fdisk_field {
+	int		id;		/* FDISK_FIELD_* */
+	const char	*name;		/* field name */
+	double		width;		/* field width (compatible with libsmartcols whint) */
+	int		flags;		/* FDISK_FIELDFL_* */
+};
+
+/* note that the defauls is to display a column always */
+enum {
+	FDISK_FIELDFL_DETAIL	= (1 << 1),	/* only display if fdisk_is_details() */
+	FDISK_FIELDFL_EYECANDY	= (1 << 2),	/* don't display if fdisk_is_details() */
+	FDISK_FIELDFL_NUMBER	= (1 << 3),	/* column display numbers */
+};
+
+/*
+ * Generic label
+ */
+struct fdisk_label {
+	const char		*name;		/* label name */
+	enum fdisk_labeltype	id;		/* FDISK_DISKLABEL_* */
+	struct fdisk_parttype	*parttypes;	/* supported partitions types */
+	size_t			nparttypes;	/* number of items in parttypes[] */
+
+	size_t			nparts_max;	/* maximal number of partitions */
+	size_t			nparts_cur;	/* number of currently used partitions */
+
+	int			flags;		/* FDISK_LABEL_FL_* flags */
+
+	unsigned int		changed:1,	/* label has been modified */
+				disabled:1;	/* this driver is disabled at all */
+
+	const struct fdisk_field *fields;	/* all possible fields */
+	size_t			nfields;
+
+	const struct fdisk_label_operations *op;
+};
+
+
+/* label driver flags */
+enum {
+	FDISK_LABEL_FL_REQUIRE_GEOMETRY = (1 << 2),
+	FDISK_LABEL_FL_INCHARS_PARTNO   = (1 << 3)
+};
+
+/* label allocators */
+extern struct fdisk_label *fdisk_new_gpt_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_sgi_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_sun_label(struct fdisk_context *cxt);
+
+
+struct ask_menuitem {
+	char	key;
+	const char	*name;
+	const char	*desc;
+
+	struct ask_menuitem *next;
+};
+
+/* fdisk dialog -- note that nothing from this stuff will be directly exported,
+ * we will have get/set() function for everything.
+ */
+struct fdisk_ask {
+	int		type;		/* FDISK_ASKTYPE_* */
+	char		*query;
+
+	int		refcount;
+
+	union {
+		/* FDISK_ASKTYPE_{NUMBER,OFFSET} */
+		struct ask_number {
+			uint64_t	hig;		/* high limit */
+			uint64_t	low;		/* low limit */
+			uint64_t	dfl;		/* default */
+			uint64_t	result;
+			uint64_t	base;		/* for relative results */
+			uint64_t	unit;		/* unit for offsets */
+			const char	*range;		/* by library generated list */
+			unsigned int	relative :1,
+					inchars  :1;
+		} num;
+		/* FDISK_ASKTYPE_{WARN,WARNX,..} */
+		struct ask_print {
+			const char	*mesg;
+			int		errnum;		/* errno */
+		} print;
+		/* FDISK_ASKTYPE_YESNO */
+		struct ask_yesno {
+			int		result;		/* TRUE or FALSE */
+		} yesno;
+		/* FDISK_ASKTYPE_STRING */
+		struct ask_string {
+			char		*result;	/* allocated */
+		} str;
+		/* FDISK_ASKTYPE_MENU */
+		struct ask_menu {
+			int		dfl;		/* default meni item */
+			int		result;
+			struct ask_menuitem *first;
+		} menu;
+	} data;
+};
+
+struct fdisk_context {
+	int dev_fd;         /* device descriptor */
+	char *dev_path;     /* device path */
+	int refcount;
+
+	unsigned char *firstsector; /* buffer with master boot record */
+	unsigned long firstsector_bufsz;
+
+	/* topology */
+	unsigned long io_size;		/* I/O size used by fdisk */
+	unsigned long optimal_io_size;	/* optional I/O returned by device */
+	unsigned long min_io_size;	/* minimal I/O size */
+	unsigned long phy_sector_size;	/* physical size */
+	unsigned long sector_size;	/* logical size */
+	unsigned long alignment_offset;
+
+	unsigned int readonly : 1,		/* don't write to the device */
+		     display_in_cyl_units : 1,	/* for obscure labels */
+		     display_details : 1,	/* expert display mode */
+		     listonly : 1;		/* list partition, nothing else */
+
+	/* alignment */
+	unsigned long grain;		/* alignment unit */
+	fdisk_sector_t first_lba;		/* recommended begin of the first partition */
+	fdisk_sector_t last_lba;		/* recomennded end of last partition */
+
+	/* geometry */
+	fdisk_sector_t total_sectors;	/* in logical sectors */
+	struct fdisk_geometry geom;
+
+	/* user setting to overwrite device default */
+	struct fdisk_geometry user_geom;
+	unsigned long user_pyh_sector;
+	unsigned long user_log_sector;
+
+	struct fdisk_label *label;	/* current label, pointer to labels[] */
+
+	size_t nlabels;			/* number of initialized label drivers */
+	struct fdisk_label *labels[8];	/* all supported labels,
+					 * FIXME: use any enum rather than hardcoded number */
+
+	int	(*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *);	/* fdisk dialogs callback */
+	void	*ask_data;		/* ask_cb() data */
+
+	struct fdisk_context	*parent;	/* for nested PT */
+	struct fdisk_script	*script;	/* what we want to follow */
+};
+
+/* partition.c */
+int fdisk_partition_next_partno(struct fdisk_partition *pa,
+				       struct fdisk_context *cxt,
+				       size_t *n);
+
+/* context.c */
+extern int __fdisk_switch_label(struct fdisk_context *cxt,
+				    struct fdisk_label *lb);
+extern int fdisk_missing_geometry(struct fdisk_context *cxt);
+
+/* alignment.c */
+fdisk_sector_t fdisk_scround(struct fdisk_context *cxt, fdisk_sector_t num);
+fdisk_sector_t fdisk_cround(struct fdisk_context *cxt, fdisk_sector_t num);
+
+extern int fdisk_discover_geometry(struct fdisk_context *cxt);
+extern int fdisk_discover_topology(struct fdisk_context *cxt);
+
+extern int fdisk_apply_user_device_properties(struct fdisk_context *cxt);
+extern void fdisk_zeroize_device_properties(struct fdisk_context *cxt);
+
+/* utils.c */
+extern int fdisk_init_firstsector_buffer(struct fdisk_context *cxt);
+extern int fdisk_read_firstsector(struct fdisk_context *cxt);
+extern char *fdisk_partname(const char *dev, size_t partno);
+
+/* label.c */
+extern int fdisk_probe_labels(struct fdisk_context *cxt);
+extern void fdisk_deinit_label(struct fdisk_label *lb);
+
+/* ask.c */
+struct fdisk_ask *fdisk_new_ask(void);
+void fdisk_reset_ask(struct fdisk_ask *ask);
+int fdisk_ask_set_query(struct fdisk_ask *ask, const char *str);
+int fdisk_ask_set_type(struct fdisk_ask *ask, int type);
+int fdisk_do_ask(struct fdisk_context *cxt, struct fdisk_ask *ask);
+int fdisk_ask_number_set_range(struct fdisk_ask *ask, const char *range);
+int fdisk_ask_number_set_default(struct fdisk_ask *ask, uint64_t dflt);
+int fdisk_ask_number_set_low(struct fdisk_ask *ask, uint64_t low);
+int fdisk_ask_number_set_high(struct fdisk_ask *ask, uint64_t high);
+int fdisk_ask_number_set_base(struct fdisk_ask *ask, uint64_t base);
+int fdisk_ask_number_set_unit(struct fdisk_ask *ask, uint64_t unit);
+int fdisk_ask_number_is_relative(struct fdisk_ask *ask);
+int fdisk_ask_menu_set_default(struct fdisk_ask *ask, int dfl);
+int fdisk_ask_menu_add_item(struct fdisk_ask *ask, int key,
+			const char *name, const char *desc);
+int fdisk_ask_print_set_errno(struct fdisk_ask *ask, int errnum);
+int fdisk_ask_print_set_mesg(struct fdisk_ask *ask, const char *mesg);
+int fdisk_info_new_partition(
+			struct fdisk_context *cxt,
+			int num, fdisk_sector_t start, fdisk_sector_t stop,
+			struct fdisk_parttype *t);
+
+/* dos.c */
+extern struct dos_partition *fdisk_dos_get_partition(
+				struct fdisk_context *cxt,
+				size_t i);
+
+#endif /* _LIBFDISK_PRIVATE_H */
diff --git a/libblkid/libfdisk/src/gpt.c b/libblkid/libfdisk/src/gpt.c
new file mode 100644
index 0000000..8c1c96c
--- /dev/null
+++ b/libblkid/libfdisk/src/gpt.c
@@ -0,0 +1,2565 @@
+/*
+ * Copyright (C) 2007 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org>
+ *
+ * GUID Partition Table (GPT) support. Based on UEFI Specs 2.3.1
+ * Chapter 5: GUID Partition Table (GPT) Disk Layout (Jun 27th, 2012).
+ * Some ideas and inspiration from GNU parted and gptfdisk.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <uuid.h>
+
+#include "fdiskP.h"
+
+#include "nls.h"
+#include "crc32.h"
+#include "blkdev.h"
+#include "bitops.h"
+#include "strutils.h"
+#include "all-io.h"
+
+/**
+ * SECTION: gpt
+ * @title: UEFI GPT
+ * @short_description: specific functionality
+ */
+
+#define GPT_HEADER_SIGNATURE 0x5452415020494645LL /* EFI PART */
+#define GPT_HEADER_REVISION_V1_02 0x00010200
+#define GPT_HEADER_REVISION_V1_00 0x00010000
+#define GPT_HEADER_REVISION_V0_99 0x00009900
+#define GPT_HEADER_MINSZ          92 /* bytes */
+
+#define GPT_PMBR_LBA        0
+#define GPT_MBR_PROTECTIVE  1
+#define GPT_MBR_HYBRID      2
+
+#define GPT_PRIMARY_PARTITION_TABLE_LBA 0x00000001
+
+#define EFI_PMBR_OSTYPE     0xEE
+#define MSDOS_MBR_SIGNATURE 0xAA55
+#define GPT_PART_NAME_LEN   (72 / sizeof(uint16_t))
+#define GPT_NPARTITIONS     128
+
+/* Globally unique identifier */
+struct gpt_guid {
+	uint32_t   time_low;
+	uint16_t   time_mid;
+	uint16_t   time_hi_and_version;
+	uint8_t    clock_seq_hi;
+	uint8_t    clock_seq_low;
+	uint8_t    node[6];
+};
+
+
+/* only checking that the GUID is 0 is enough to verify an empty partition. */
+#define GPT_UNUSED_ENTRY_GUID						\
+	((struct gpt_guid) { 0x00000000, 0x0000, 0x0000, 0x00, 0x00,	\
+			     { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }})
+
+/* Linux native partition type */
+#define GPT_DEFAULT_ENTRY_TYPE "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
+
+/*
+ * Attribute bits
+ */
+enum {
+	/* UEFI specific */
+	GPT_ATTRBIT_REQ      = 0,
+	GPT_ATTRBIT_NOBLOCK  = 1,
+	GPT_ATTRBIT_LEGACY   = 2,
+
+	/* GUID specific (range 48..64)*/
+	GPT_ATTRBIT_GUID_FIRST	= 48,
+	GPT_ATTRBIT_GUID_COUNT	= 16
+};
+
+#define GPT_ATTRSTR_REQ		"RequiredPartiton"
+#define GPT_ATTRSTR_NOBLOCK	"NoBlockIOProtocol"
+#define GPT_ATTRSTR_LEGACY	"LegacyBIOSBootable"
+
+/* The GPT Partition entry array contains an array of GPT entries. */
+struct gpt_entry {
+	struct gpt_guid     type; /* purpose and type of the partition */
+	struct gpt_guid     partition_guid;
+	uint64_t            lba_start;
+	uint64_t            lba_end;
+	uint64_t            attrs;
+	uint16_t            name[GPT_PART_NAME_LEN];
+}  __attribute__ ((packed));
+
+/* GPT header */
+struct gpt_header {
+	uint64_t            signature; /* header identification */
+	uint32_t            revision; /* header version */
+	uint32_t            size; /* in bytes */
+	uint32_t            crc32; /* header CRC checksum */
+	uint32_t            reserved1; /* must be 0 */
+	uint64_t            my_lba; /* LBA of block that contains this struct (LBA 1) */
+	uint64_t            alternative_lba; /* backup GPT header */
+	uint64_t            first_usable_lba; /* first usable logical block for partitions */
+	uint64_t            last_usable_lba; /* last usable logical block for partitions */
+	struct gpt_guid     disk_guid; /* unique disk identifier */
+	uint64_t            partition_entry_lba; /* LBA of start of partition entries array */
+	uint32_t            npartition_entries; /* total partition entries - normally 128 */
+	uint32_t            sizeof_partition_entry; /* bytes for each GUID pt */
+	uint32_t            partition_entry_array_crc32; /* partition CRC checksum */
+	uint8_t             reserved2[512 - 92]; /* must all be 0 */
+} __attribute__ ((packed));
+
+struct gpt_record {
+	uint8_t             boot_indicator; /* unused by EFI, set to 0x80 for bootable */
+	uint8_t             start_head; /* unused by EFI, pt start in CHS */
+	uint8_t             start_sector; /* unused by EFI, pt start in CHS */
+	uint8_t             start_track;
+	uint8_t             os_type; /* EFI and legacy non-EFI OS types */
+	uint8_t             end_head; /* unused by EFI, pt end in CHS */
+	uint8_t             end_sector; /* unused by EFI, pt end in CHS */
+	uint8_t             end_track; /* unused by EFI, pt end in CHS */
+	uint32_t            starting_lba; /* used by EFI - start addr of the on disk pt */
+	uint32_t            size_in_lba; /* used by EFI - size of pt in LBA */
+} __attribute__ ((packed));
+
+/* Protected MBR and legacy MBR share same structure */
+struct gpt_legacy_mbr {
+	uint8_t             boot_code[440];
+	uint32_t            unique_mbr_signature;
+	uint16_t            unknown;
+	struct gpt_record   partition_record[4];
+	uint16_t            signature;
+} __attribute__ ((packed));
+
+/*
+ * Here be dragons!
+ * See: http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
+ */
+#define DEF_GUID(_u, _n) \
+	{ \
+		.typestr = (_u), \
+		.name = (_n),    \
+	}
+
+static struct fdisk_parttype gpt_parttypes[] =
+{
+	/* Generic OS */
+	DEF_GUID("C12A7328-F81F-11D2-BA4B-00A0C93EC93B", N_("EFI System")),
+
+	DEF_GUID("024DEE41-33E7-11D3-9D69-0008C781F39F", N_("MBR partition scheme")),
+	DEF_GUID("D3BFE2DE-3DAF-11DF-BA40-E3A556D89593", N_("Intel Fast Flash")),
+
+	/* Hah!IdontneedEFI */
+	DEF_GUID("21686148-6449-6E6F-744E-656564454649", N_("BIOS boot")),
+
+	/* Windows */
+	DEF_GUID("E3C9E316-0B5C-4DB8-817D-F92DF00215AE", N_("Microsoft reserved")),
+	DEF_GUID("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", N_("Microsoft basic data")),
+	DEF_GUID("5808C8AA-7E8F-42E0-85D2-E1E90434CFB3", N_("Microsoft LDM metadata")),
+	DEF_GUID("AF9B60A0-1431-4F62-BC68-3311714A69AD", N_("Microsoft LDM data")),
+	DEF_GUID("DE94BBA4-06D1-4D40-A16A-BFD50179D6AC", N_("Windows recovery environment")),
+	DEF_GUID("37AFFC90-EF7D-4E96-91C3-2D7AE055B174", N_("IBM General Parallel Fs")),
+	DEF_GUID("E75CAF8F-F680-4CEE-AFA3-B001E56EFC2D", N_("Microsoft Storage Spaces")),
+
+	/* HP-UX */
+	DEF_GUID("75894C1E-3AEB-11D3-B7C1-7B03A0000000", N_("HP-UX data")),
+	DEF_GUID("E2A1E728-32E3-11D6-A682-7B03A0000000", N_("HP-UX service")),
+
+	/* Linux (http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec) */
+	DEF_GUID("0657FD6D-A4AB-43C4-84E5-0933C84B4F4F", N_("Linux swap")),
+	DEF_GUID("0FC63DAF-8483-4772-8E79-3D69D8477DE4", N_("Linux filesystem")),
+	DEF_GUID("3B8F8425-20E0-4F3B-907F-1A25A76F98E8", N_("Linux server data")),
+	DEF_GUID("44479540-F297-41B2-9AF7-D131D5F0458A", N_("Linux root (x86)")),
+	DEF_GUID("4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709", N_("Linux root (x86-64)")),
+	DEF_GUID("8DA63339-0007-60C0-C436-083AC8230908", N_("Linux reserved")),
+	DEF_GUID("933AC7E1-2EB4-4F13-B844-0E14E2AEF915", N_("Linux home")),
+	DEF_GUID("A19D880F-05FC-4D3B-A006-743F0F84911E", N_("Linux RAID")),
+	DEF_GUID("BC13C2FF-59E6-4262-A352-B275FD6F7172", N_("Linux extended boot")),
+	DEF_GUID("E6D6D379-F507-44C2-A23C-238F2A3DF928", N_("Linux LVM")),
+
+	/* FreeBSD */
+	DEF_GUID("516E7CB4-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD data")),
+	DEF_GUID("83BD6B9D-7F41-11DC-BE0B-001560B84F0F", N_("FreeBSD boot")),
+	DEF_GUID("516E7CB5-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD swap")),
+	DEF_GUID("516E7CB6-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD UFS")),
+	DEF_GUID("516E7CBA-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD ZFS")),
+	DEF_GUID("516E7CB8-6ECF-11D6-8FF8-00022D09712B", N_("FreeBSD Vinum")),
+
+	/* Apple OSX */
+	DEF_GUID("48465300-0000-11AA-AA11-00306543ECAC", N_("Apple HFS/HFS+")),
+	DEF_GUID("55465300-0000-11AA-AA11-00306543ECAC", N_("Apple UFS")),
+	DEF_GUID("52414944-0000-11AA-AA11-00306543ECAC", N_("Apple RAID")),
+	DEF_GUID("52414944-5F4F-11AA-AA11-00306543ECAC", N_("Apple RAID offline")),
+	DEF_GUID("426F6F74-0000-11AA-AA11-00306543ECAC", N_("Apple boot")),
+	DEF_GUID("4C616265-6C00-11AA-AA11-00306543ECAC", N_("Apple label")),
+	DEF_GUID("5265636F-7665-11AA-AA11-00306543ECAC", N_("Apple TV recovery")),
+	DEF_GUID("53746F72-6167-11AA-AA11-00306543ECAC", N_("Apple Core storage")),
+
+	/* Solaris */
+	DEF_GUID("6A82CB45-1DD2-11B2-99A6-080020736631", N_("Solaris boot")),
+	DEF_GUID("6A85CF4D-1DD2-11B2-99A6-080020736631", N_("Solaris root")),
+	/* same as Apple ZFS */
+	DEF_GUID("6A898CC3-1DD2-11B2-99A6-080020736631", N_("Solaris /usr & Apple ZFS")),
+	DEF_GUID("6A87C46F-1DD2-11B2-99A6-080020736631", N_("Solaris swap")),
+	DEF_GUID("6A8B642B-1DD2-11B2-99A6-080020736631", N_("Solaris backup")),
+	DEF_GUID("6A8EF2E9-1DD2-11B2-99A6-080020736631", N_("Solaris /var")),
+	DEF_GUID("6A90BA39-1DD2-11B2-99A6-080020736631", N_("Solaris /home")),
+	DEF_GUID("6A9283A5-1DD2-11B2-99A6-080020736631", N_("Solaris alternate sector")),
+	DEF_GUID("6A945A3B-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 1")),
+	DEF_GUID("6A9630D1-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 2")),
+	DEF_GUID("6A980767-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 3")),
+	DEF_GUID("6A96237F-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 4")),
+	DEF_GUID("6A8D2AC7-1DD2-11B2-99A6-080020736631", N_("Solaris reserved 5")),
+
+	/* NetBSD */
+	DEF_GUID("49F48D32-B10E-11DC-B99B-0019D1879648", N_("NetBSD swap")),
+	DEF_GUID("49F48D5A-B10E-11DC-B99B-0019D1879648", N_("NetBSD FFS")),
+	DEF_GUID("49F48D82-B10E-11DC-B99B-0019D1879648", N_("NetBSD LFS")),
+	DEF_GUID("2DB519C4-B10E-11DC-B99B-0019D1879648", N_("NetBSD concatenated")),
+	DEF_GUID("2DB519EC-B10E-11DC-B99B-0019D1879648", N_("NetBSD encrypted")),
+	DEF_GUID("49F48DAA-B10E-11DC-B99B-0019D1879648", N_("NetBSD RAID")),
+
+	/* ChromeOS */
+	DEF_GUID("FE3A2A5D-4F32-41A7-B725-ACCC3285A309", N_("ChromeOS kernel")),
+	DEF_GUID("3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC", N_("ChromeOS root fs")),
+	DEF_GUID("2E0A753D-9E48-43B0-8337-B15192CB1B5E", N_("ChromeOS reserved")),
+
+	/* MidnightBSD */
+	DEF_GUID("85D5E45A-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD data")),
+	DEF_GUID("85D5E45E-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD boot")),
+	DEF_GUID("85D5E45B-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD swap")),
+	DEF_GUID("0394Ef8B-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD UFS")),
+	DEF_GUID("85D5E45D-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD ZFS")),
+	DEF_GUID("85D5E45C-237C-11E1-B4B3-E89A8F7FC3A7", N_("MidnightBSD Vinum")),
+};
+
+/* gpt_entry macros */
+#define gpt_partition_start(_e)		le64_to_cpu((_e)->lba_start)
+#define gpt_partition_end(_e)		le64_to_cpu((_e)->lba_end)
+
+/*
+ * in-memory fdisk GPT stuff
+ */
+struct fdisk_gpt_label {
+	struct fdisk_label	head;		/* generic part */
+
+	/* gpt specific part */
+	struct gpt_header	*pheader;	/* primary header */
+	struct gpt_header	*bheader;	/* backup header */
+	struct gpt_entry	*ents;		/* entries (partitions) */
+};
+
+static void gpt_deinit(struct fdisk_label *lb);
+
+static inline struct fdisk_gpt_label *self_label(struct fdisk_context *cxt)
+{
+	return (struct fdisk_gpt_label *) cxt->label;
+}
+
+/*
+ * Returns the partition length, or 0 if end is before beginning.
+ */
+static uint64_t gpt_partition_size(const struct gpt_entry *e)
+{
+	uint64_t start = gpt_partition_start(e);
+	uint64_t end = gpt_partition_end(e);
+
+	return start > end ? 0 : end - start + 1ULL;
+}
+
+/* prints UUID in the real byte order! */
+static void gpt_debug_uuid(const char *mesg, struct gpt_guid *guid)
+{
+	const unsigned char *uuid = (unsigned char *) guid;
+
+	fprintf(stderr, "%s: "
+		"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+		mesg,
+		uuid[0], uuid[1], uuid[2], uuid[3],
+		uuid[4], uuid[5],
+		uuid[6], uuid[7],
+		uuid[8], uuid[9],
+		uuid[10], uuid[11], uuid[12], uuid[13], uuid[14],uuid[15]);
+}
+
+/*
+ * UUID is traditionally 16 byte big-endian array, except Intel EFI
+ * specification where the UUID is a structure of little-endian fields.
+ */
+static void swap_efi_guid(struct gpt_guid *uid)
+{
+	uid->time_low = swab32(uid->time_low);
+	uid->time_mid = swab16(uid->time_mid);
+	uid->time_hi_and_version = swab16(uid->time_hi_and_version);
+}
+
+static int string_to_guid(const char *in, struct gpt_guid *guid)
+{
+	if (uuid_parse(in, (unsigned char *) guid))	/* BE */
+		return -1;
+	swap_efi_guid(guid);				/* LE */
+	return 0;
+}
+
+static char *guid_to_string(const struct gpt_guid *guid, char *out)
+{
+	struct gpt_guid u = *guid;	/* LE */
+
+	swap_efi_guid(&u);		/* BE */
+	uuid_unparse_upper((unsigned char *) &u, out);
+
+	return out;
+}
+
+static struct fdisk_parttype *gpt_partition_parttype(
+		struct fdisk_context *cxt,
+		const struct gpt_entry *e)
+{
+	struct fdisk_parttype *t;
+	char str[37];
+
+	guid_to_string(&e->type, str);
+	t = fdisk_label_get_parttype_from_string(cxt->label, str);
+	return t ? : fdisk_new_unknown_parttype(0, str);
+}
+
+static void gpt_entry_set_type(struct gpt_entry *e, struct gpt_guid *uuid)
+{
+	e->type = *uuid;
+	DBG(LABEL, gpt_debug_uuid("new type", &(e->type)));
+}
+
+static void gpt_entry_set_name(struct gpt_entry *e, char *str)
+{
+	char name[GPT_PART_NAME_LEN] = { 0 };
+	size_t i, sz = strlen(str);
+
+	if (sz) {
+		if (sz > GPT_PART_NAME_LEN)
+			sz = GPT_PART_NAME_LEN;
+		memcpy(name, str, sz);
+	}
+
+	for (i = 0; i < GPT_PART_NAME_LEN; i++)
+		e->name[i] = cpu_to_le16((uint16_t) name[i]);
+}
+
+static int gpt_entry_set_uuid(struct gpt_entry *e, char *str)
+{
+	struct gpt_guid uuid;
+	int rc;
+
+	rc = string_to_guid(str, &uuid);
+	if (rc)
+		return rc;
+
+	e->partition_guid = uuid;
+	return 0;
+}
+
+
+static const char *gpt_get_header_revstr(struct gpt_header *header)
+{
+	if (!header)
+		goto unknown;
+
+	switch (header->revision) {
+	case GPT_HEADER_REVISION_V1_02:
+		return "1.2";
+	case GPT_HEADER_REVISION_V1_00:
+		return "1.0";
+	case GPT_HEADER_REVISION_V0_99:
+		return "0.99";
+	default:
+		goto unknown;
+	}
+
+unknown:
+	return "unknown";
+}
+
+static inline int partition_unused(const struct gpt_entry *e)
+{
+	return !memcmp(&e->type, &GPT_UNUSED_ENTRY_GUID,
+			sizeof(struct gpt_guid));
+}
+
+/*
+ * Builds a clean new valid protective MBR - will wipe out any existing data.
+ * Returns 0 on success, otherwise < 0 on error.
+ */
+static int gpt_mknew_pmbr(struct fdisk_context *cxt)
+{
+	struct gpt_legacy_mbr *pmbr = NULL;
+	int rc;
+
+	if (!cxt || !cxt->firstsector)
+		return -ENOSYS;
+
+	rc = fdisk_init_firstsector_buffer(cxt);
+	if (rc)
+		return rc;
+
+	pmbr = (struct gpt_legacy_mbr *) cxt->firstsector;
+
+	pmbr->signature = cpu_to_le16(MSDOS_MBR_SIGNATURE);
+	pmbr->partition_record[0].os_type      = EFI_PMBR_OSTYPE;
+	pmbr->partition_record[0].start_sector = 1;
+	pmbr->partition_record[0].end_head     = 0xFE;
+	pmbr->partition_record[0].end_sector   = 0xFF;
+	pmbr->partition_record[0].end_track    = 0xFF;
+	pmbr->partition_record[0].starting_lba = cpu_to_le32(1);
+	pmbr->partition_record[0].size_in_lba  =
+		cpu_to_le32(min((uint32_t) cxt->total_sectors - 1, 0xFFFFFFFF));
+
+	return 0;
+}
+
+/* some universal differences between the headers */
+static void gpt_mknew_header_common(struct fdisk_context *cxt,
+				    struct gpt_header *header, uint64_t lba)
+{
+	if (!cxt || !header)
+		return;
+
+	header->my_lba = cpu_to_le64(lba);
+
+	if (lba == GPT_PRIMARY_PARTITION_TABLE_LBA) { /* primary */
+		header->alternative_lba = cpu_to_le64(cxt->total_sectors - 1);
+		header->partition_entry_lba = cpu_to_le64(2);
+	} else { /* backup */
+		uint64_t esz = le32_to_cpu(header->npartition_entries) * sizeof(struct gpt_entry);
+		uint64_t esects = (esz + cxt->sector_size - 1) / cxt->sector_size;
+
+		header->alternative_lba = cpu_to_le64(GPT_PRIMARY_PARTITION_TABLE_LBA);
+		header->partition_entry_lba = cpu_to_le64(cxt->total_sectors - 1 - esects);
+	}
+}
+
+/*
+ * Builds a new GPT header (at sector lba) from a backup header2.
+ * If building a primary header, then backup is the secondary, and vice versa.
+ *
+ * Always pass a new (zeroized) header to build upon as we don't
+ * explicitly zero-set some values such as CRCs and reserved.
+ *
+ * Returns 0 on success, otherwise < 0 on error.
+ */
+static int gpt_mknew_header_from_bkp(struct fdisk_context *cxt,
+				     struct gpt_header *header,
+				     uint64_t lba,
+				     struct gpt_header *header2)
+{
+	if (!cxt || !header || !header2)
+		return -ENOSYS;
+
+	header->signature              = header2->signature;
+	header->revision               = header2->revision;
+	header->size                   = header2->size;
+	header->npartition_entries     = header2->npartition_entries;
+	header->sizeof_partition_entry = header2->sizeof_partition_entry;
+	header->first_usable_lba       = header2->first_usable_lba;
+	header->last_usable_lba        = header2->last_usable_lba;
+
+	memcpy(&header->disk_guid,
+	       &header2->disk_guid, sizeof(header2->disk_guid));
+	gpt_mknew_header_common(cxt, header, lba);
+
+	return 0;
+}
+
+static struct gpt_header *gpt_copy_header(struct fdisk_context *cxt,
+			   struct gpt_header *src)
+{
+	struct gpt_header *res;
+
+	if (!cxt || !src)
+		return NULL;
+
+	res = calloc(1, sizeof(*res));
+	if (!res) {
+		fdisk_warn(cxt, _("failed to allocate GPT header"));
+		return NULL;
+	}
+
+	res->my_lba                 = src->alternative_lba;
+	res->alternative_lba        = src->my_lba;
+
+	res->signature              = src->signature;
+	res->revision               = src->revision;
+	res->size                   = src->size;
+	res->npartition_entries     = src->npartition_entries;
+	res->sizeof_partition_entry = src->sizeof_partition_entry;
+	res->first_usable_lba       = src->first_usable_lba;
+	res->last_usable_lba        = src->last_usable_lba;
+
+	memcpy(&res->disk_guid, &src->disk_guid, sizeof(src->disk_guid));
+
+
+	if (res->my_lba == GPT_PRIMARY_PARTITION_TABLE_LBA)
+		res->partition_entry_lba = cpu_to_le64(2);
+	else {
+		uint64_t esz = le32_to_cpu(src->npartition_entries) * sizeof(struct gpt_entry);
+		uint64_t esects = (esz + cxt->sector_size - 1) / cxt->sector_size;
+
+		res->partition_entry_lba = cpu_to_le64(cxt->total_sectors - 1 - esects);
+	}
+
+	return res;
+}
+
+static void count_first_last_lba(struct fdisk_context *cxt,
+				 uint64_t *first, uint64_t *last)
+{
+	uint64_t esz = 0;
+
+	assert(cxt);
+
+	esz = sizeof(struct gpt_entry) * GPT_NPARTITIONS / cxt->sector_size;
+	*last = cxt->total_sectors - 2 - esz;
+	*first = esz + 2;
+
+	if (*first < cxt->first_lba && cxt->first_lba < *last)
+		/* Align according to topology */
+		*first = cxt->first_lba;
+}
+
+/*
+ * Builds a clean new GPT header (currently under revision 1.0).
+ *
+ * Always pass a new (zeroized) header to build upon as we don't
+ * explicitly zero-set some values such as CRCs and reserved.
+ *
+ * Returns 0 on success, otherwise < 0 on error.
+ */
+static int gpt_mknew_header(struct fdisk_context *cxt,
+			    struct gpt_header *header, uint64_t lba)
+{
+	uint64_t first, last;
+	int has_id = 0;
+
+	if (!cxt || !header)
+		return -ENOSYS;
+
+	header->signature = cpu_to_le64(GPT_HEADER_SIGNATURE);
+	header->revision  = cpu_to_le32(GPT_HEADER_REVISION_V1_00);
+	header->size      = cpu_to_le32(sizeof(struct gpt_header));
+
+	/*
+	 * 128 partitions are the default. It can go beyond that, but
+	 * we're creating a de facto header here, so no funny business.
+	 */
+	header->npartition_entries     = cpu_to_le32(GPT_NPARTITIONS);
+	header->sizeof_partition_entry = cpu_to_le32(sizeof(struct gpt_entry));
+
+	count_first_last_lba(cxt, &first, &last);
+	header->first_usable_lba = cpu_to_le64(first);
+	header->last_usable_lba  = cpu_to_le64(last);
+
+	gpt_mknew_header_common(cxt, header, lba);
+
+	if (cxt->script) {
+		const char *id = fdisk_script_get_header(cxt->script, "label-id");
+		if (id && string_to_guid(id, &header->disk_guid) == 0)
+			has_id = 1;
+	}
+
+	if (!has_id) {
+		uuid_generate_random((unsigned char *) &header->disk_guid);
+		swap_efi_guid(&header->disk_guid);
+	}
+	return 0;
+}
+
+/*
+ * Checks if there is a valid protective MBR partition table.
+ * Returns 0 if it is invalid or failure. Otherwise, return
+ * GPT_MBR_PROTECTIVE or GPT_MBR_HYBRID, depeding on the detection.
+ */
+static int valid_pmbr(struct fdisk_context *cxt)
+{
+	int i, part = 0, ret = 0; /* invalid by default */
+	struct gpt_legacy_mbr *pmbr = NULL;
+	uint32_t sz_lba = 0;
+
+	if (!cxt->firstsector)
+		goto done;
+
+	pmbr = (struct gpt_legacy_mbr *) cxt->firstsector;
+
+	if (le16_to_cpu(pmbr->signature) != MSDOS_MBR_SIGNATURE)
+		goto done;
+
+	/* LBA of the GPT partition header */
+	if (pmbr->partition_record[0].starting_lba !=
+	    cpu_to_le32(GPT_PRIMARY_PARTITION_TABLE_LBA))
+		goto done;
+
+	/* seems like a valid MBR was found, check DOS primary partitions */
+	for (i = 0; i < 4; i++) {
+		if (pmbr->partition_record[i].os_type == EFI_PMBR_OSTYPE) {
+			/*
+			 * Ok, we at least know that there's a protective MBR,
+			 * now check if there are other partition types for
+			 * hybrid MBR.
+			 */
+			part = i;
+			ret = GPT_MBR_PROTECTIVE;
+			goto check_hybrid;
+		}
+	}
+
+	if (ret != GPT_MBR_PROTECTIVE)
+		goto done;
+check_hybrid:
+	for (i = 0 ; i < 4; i++) {
+		if ((pmbr->partition_record[i].os_type != EFI_PMBR_OSTYPE) &&
+		    (pmbr->partition_record[i].os_type != 0x00))
+			ret = GPT_MBR_HYBRID;
+	}
+
+	/*
+	 * Protective MBRs take up the lesser of the whole disk
+	 * or 2 TiB (32bit LBA), ignoring the rest of the disk.
+	 * Some partitioning programs, nonetheless, choose to set
+	 * the size to the maximum 32-bit limitation, disregarding
+	 * the disk size.
+	 *
+	 * Hybrid MBRs do not necessarily comply with this.
+	 *
+	 * Consider a bad value here to be a warning to support dd-ing
+	 * an image from a smaller disk to a bigger disk.
+	 */
+	if (ret == GPT_MBR_PROTECTIVE) {
+		sz_lba = le32_to_cpu(pmbr->partition_record[part].size_in_lba);
+		if (sz_lba != (uint32_t) cxt->total_sectors - 1 && sz_lba != 0xFFFFFFFF) {
+			fdisk_warnx(cxt, _("GPT PMBR size mismatch (%u != %u) "
+					   "will be corrected by w(rite)."),
+					sz_lba,
+					(uint32_t) cxt->total_sectors - 1);
+			fdisk_label_set_changed(cxt->label, 1);
+		}
+	}
+done:
+	return ret;
+}
+
+static uint64_t last_lba(struct fdisk_context *cxt)
+{
+	struct stat s;
+	uint64_t sectors = 0;
+
+	memset(&s, 0, sizeof(s));
+	if (fstat(cxt->dev_fd, &s) == -1) {
+		fdisk_warn(cxt, _("gpt: stat() failed"));
+		return 0;
+	}
+
+	if (S_ISBLK(s.st_mode))
+		sectors = cxt->total_sectors - 1;
+	else if (S_ISREG(s.st_mode))
+		sectors = ((uint64_t) s.st_size /
+			   (uint64_t) cxt->sector_size) - 1ULL;
+	else
+		fdisk_warnx(cxt, _("gpt: cannot handle files with mode %o"), s.st_mode);
+
+	DBG(LABEL, ul_debug("GPT last LBA: %ju", sectors));
+	return sectors;
+}
+
+static ssize_t read_lba(struct fdisk_context *cxt, uint64_t lba,
+			void *buffer, const size_t bytes)
+{
+	off_t offset = lba * cxt->sector_size;
+
+	if (lseek(cxt->dev_fd, offset, SEEK_SET) == (off_t) -1)
+		return -1;
+	return read(cxt->dev_fd, buffer, bytes) != bytes;
+}
+
+
+/* Returns the GPT entry array */
+static struct gpt_entry *gpt_read_entries(struct fdisk_context *cxt,
+					 struct gpt_header *header)
+{
+	ssize_t sz;
+	struct gpt_entry *ret = NULL;
+	off_t offset;
+
+	assert(cxt);
+	assert(header);
+
+	sz = le32_to_cpu(header->npartition_entries) *
+	     le32_to_cpu(header->sizeof_partition_entry);
+
+	ret = calloc(1, sz);
+	if (!ret)
+		return NULL;
+	offset = le64_to_cpu(header->partition_entry_lba) *
+		       cxt->sector_size;
+
+	if (offset != lseek(cxt->dev_fd, offset, SEEK_SET))
+		goto fail;
+	if (sz != read(cxt->dev_fd, ret, sz))
+		goto fail;
+
+	return ret;
+
+fail:
+	free(ret);
+	return NULL;
+}
+
+static inline uint32_t count_crc32(const unsigned char *buf, size_t len)
+{
+	return (crc32(~0L, buf, len) ^ ~0L);
+}
+
+/*
+ * Recompute header and partition array 32bit CRC checksums.
+ * This function does not fail - if there's corruption, then it
+ * will be reported when checksuming it again (ie: probing or verify).
+ */
+static void gpt_recompute_crc(struct gpt_header *header, struct gpt_entry *ents)
+{
+	uint32_t crc = 0;
+	size_t entry_sz = 0;
+
+	if (!header)
+		return;
+
+	/* header CRC */
+	header->crc32 = 0;
+	crc = count_crc32((unsigned char *) header, le32_to_cpu(header->size));
+	header->crc32 = cpu_to_le32(crc);
+
+	/* partition entry array CRC */
+	header->partition_entry_array_crc32 = 0;
+	entry_sz = le32_to_cpu(header->npartition_entries) *
+		le32_to_cpu(header->sizeof_partition_entry);
+
+	crc = count_crc32((unsigned char *) ents, entry_sz);
+	header->partition_entry_array_crc32 = cpu_to_le32(crc);
+}
+
+/*
+ * Compute the 32bit CRC checksum of the partition table header.
+ * Returns 1 if it is valid, otherwise 0.
+ */
+static int gpt_check_header_crc(struct gpt_header *header, struct gpt_entry *ents)
+{
+	uint32_t crc, orgcrc = le32_to_cpu(header->crc32);
+
+	header->crc32 = 0;
+	crc = count_crc32((unsigned char *) header, le32_to_cpu(header->size));
+	header->crc32 = cpu_to_le32(orgcrc);
+
+	if (crc == le32_to_cpu(header->crc32))
+		return 1;
+
+	/*
+	 * If we have checksum mismatch it may be due to stale data,
+	 * like a partition being added or deleted. Recompute the CRC again
+	 * and make sure this is not the case.
+	 */
+	if (ents) {
+		gpt_recompute_crc(header, ents);
+		orgcrc = le32_to_cpu(header->crc32);
+		header->crc32 = 0;
+		crc = count_crc32((unsigned char *) header, le32_to_cpu(header->size));
+		header->crc32 = cpu_to_le32(orgcrc);
+
+		return crc == le32_to_cpu(header->crc32);
+	}
+
+	return 0;
+}
+
+/*
+ * It initializes the partition entry array.
+ * Returns 1 if the checksum is valid, otherwise 0.
+ */
+static int gpt_check_entryarr_crc(struct gpt_header *header,
+				  struct gpt_entry *ents)
+{
+	int ret = 0;
+	ssize_t entry_sz;
+	uint32_t crc;
+
+	if (!header || !ents)
+		goto done;
+
+	entry_sz = le32_to_cpu(header->npartition_entries) *
+		   le32_to_cpu(header->sizeof_partition_entry);
+
+	if (!entry_sz)
+		goto done;
+
+	crc = count_crc32((unsigned char *) ents, entry_sz);
+	ret = (crc == le32_to_cpu(header->partition_entry_array_crc32));
+done:
+	return ret;
+}
+
+static int gpt_check_lba_sanity(struct fdisk_context *cxt, struct gpt_header *header)
+{
+	int ret = 0;
+	uint64_t lu, fu, lastlba = last_lba(cxt);
+
+	fu = le64_to_cpu(header->first_usable_lba);
+	lu = le64_to_cpu(header->last_usable_lba);
+
+	/* check if first and last usable LBA make sense */
+	if (lu < fu) {
+		DBG(LABEL, ul_debug("error: header last LBA is before first LBA"));
+		goto done;
+	}
+
+	/* check if first and last usable LBAs with the disk's last LBA */
+	if (fu > lastlba || lu > lastlba) {
+		DBG(LABEL, ul_debug("error: header LBAs are after the disk's last LBA"));
+		goto done;
+	}
+
+	/* the header has to be outside usable range */
+	if (fu < GPT_PRIMARY_PARTITION_TABLE_LBA &&
+	    GPT_PRIMARY_PARTITION_TABLE_LBA < lu) {
+		DBG(LABEL, ul_debug("error: header outside of usable range"));
+		goto done;
+	}
+
+	ret = 1; /* sane */
+done:
+	return ret;
+}
+
+/* Check if there is a valid header signature */
+static int gpt_check_signature(struct gpt_header *header)
+{
+	return header->signature == cpu_to_le64(GPT_HEADER_SIGNATURE);
+}
+
+/*
+ * Return the specified GPT Header, or NULL upon failure/invalid.
+ * Note that all tests must pass to ensure a valid header,
+ * we do not rely on only testing the signature for a valid probe.
+ */
+static struct gpt_header *gpt_read_header(struct fdisk_context *cxt,
+					  uint64_t lba,
+					  struct gpt_entry **_ents)
+{
+	struct gpt_header *header = NULL;
+	struct gpt_entry *ents = NULL;
+	uint32_t hsz;
+
+	if (!cxt)
+		return NULL;
+
+	header = calloc(1, sizeof(*header));
+	if (!header)
+		return NULL;
+
+	/* read and verify header */
+	if (read_lba(cxt, lba, header, sizeof(struct gpt_header)) != 0)
+		goto invalid;
+
+	if (!gpt_check_signature(header))
+		goto invalid;
+
+	if (!gpt_check_header_crc(header, NULL))
+		goto invalid;
+
+	/* read and verify entries */
+	ents = gpt_read_entries(cxt, header);
+	if (!ents)
+		goto invalid;
+
+	if (!gpt_check_entryarr_crc(header, ents))
+		goto invalid;
+
+	if (!gpt_check_lba_sanity(cxt, header))
+		goto invalid;
+
+	/* valid header must be at MyLBA */
+	if (le64_to_cpu(header->my_lba) != lba)
+		goto invalid;
+
+	/* make sure header size is between 92 and sector size bytes */
+	hsz = le32_to_cpu(header->size);
+	if (hsz < GPT_HEADER_MINSZ || hsz > cxt->sector_size)
+		goto invalid;
+
+	if (_ents)
+		*_ents = ents;
+	else
+		free(ents);
+
+	DBG(LABEL, ul_debug("found valid GPT Header on LBA %ju", lba));
+	return header;
+invalid:
+	free(header);
+	free(ents);
+
+	DBG(LABEL, ul_debug("read GPT Header on LBA %ju failed", lba));
+	return NULL;
+}
+
+
+static int gpt_locate_disklabel(struct fdisk_context *cxt, int n,
+		const char **name, off_t *offset, size_t *size)
+{
+	struct fdisk_gpt_label *gpt;
+
+	assert(cxt);
+
+	*name = NULL;
+	*offset = 0;
+	*size = 0;
+
+	switch (n) {
+	case 0:
+		*name = "PMBR";
+		*offset = 0;
+		*size = 512;
+		break;
+	case 1:
+		*name = _("GPT Header");
+		*offset = GPT_PRIMARY_PARTITION_TABLE_LBA * cxt->sector_size;
+		*size = sizeof(struct gpt_header);
+		break;
+	case 2:
+		*name = _("GPT Entries");
+		gpt = self_label(cxt);
+		*offset = le64_to_cpu(gpt->pheader->partition_entry_lba) * cxt->sector_size;
+		*size = le32_to_cpu(gpt->pheader->npartition_entries) *
+			 le32_to_cpu(gpt->pheader->sizeof_partition_entry);
+		break;
+	default:
+		return 1;			/* no more chunks */
+	}
+
+	return 0;
+}
+
+
+
+/*
+ * Returns the number of partitions that are in use.
+ */
+static unsigned partitions_in_use(struct gpt_header *header,
+				  struct gpt_entry *ents)
+{
+	uint32_t i, used = 0;
+
+	if (!header || ! ents)
+		return 0;
+
+	for (i = 0; i < le32_to_cpu(header->npartition_entries); i++)
+		if (!partition_unused(&ents[i]))
+			used++;
+	return used;
+}
+
+
+/*
+ * Check if a partition is too big for the disk (sectors).
+ * Returns the faulting partition number, otherwise 0.
+ */
+static uint32_t check_too_big_partitions(struct gpt_header *header,
+				   struct gpt_entry *ents, uint64_t sectors)
+{
+	uint32_t i;
+
+	for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) {
+		if (partition_unused(&ents[i]))
+			continue;
+		if (gpt_partition_end(&ents[i]) >= sectors)
+			return i + 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Check if a partition ends before it begins
+ * Returns the faulting partition number, otherwise 0.
+ */
+static uint32_t check_start_after_end_paritions(struct gpt_header *header,
+						struct gpt_entry *ents)
+{
+	uint32_t i;
+
+	for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) {
+		if (partition_unused(&ents[i]))
+			continue;
+		if (gpt_partition_start(&ents[i]) > gpt_partition_end(&ents[i]))
+			return i + 1;
+	}
+
+	return 0;
+}
+
+/*
+ * Check if partition e1 overlaps with partition e2.
+ */
+static inline int partition_overlap(struct gpt_entry *e1, struct gpt_entry *e2)
+{
+	uint64_t start1 = gpt_partition_start(e1);
+	uint64_t end1   = gpt_partition_end(e1);
+	uint64_t start2 = gpt_partition_start(e2);
+	uint64_t end2   = gpt_partition_end(e2);
+
+	return (start1 && start2 && (start1 <= end2) != (end1 < start2));
+}
+
+/*
+ * Find any partitions that overlap.
+ */
+static uint32_t check_overlap_partitions(struct gpt_header *header,
+					 struct gpt_entry *ents)
+{
+	uint32_t i, j;
+
+	for (i = 0; i < le32_to_cpu(header->npartition_entries); i++)
+		for (j = 0; j < i; j++) {
+			if (partition_unused(&ents[i]) ||
+			    partition_unused(&ents[j]))
+				continue;
+			if (partition_overlap(&ents[i], &ents[j])) {
+				DBG(LABEL, ul_debug("GPT partitions overlap detected [%u vs. %u]", i, j));
+				return i + 1;
+			}
+		}
+
+	return 0;
+}
+
+/*
+ * Find the first available block after the starting point; returns 0 if
+ * there are no available blocks left, or error. From gdisk.
+ */
+static uint64_t find_first_available(struct gpt_header *header,
+				     struct gpt_entry *ents, uint64_t start)
+{
+	uint64_t first;
+	uint32_t i, first_moved = 0;
+
+	uint64_t fu, lu;
+
+	if (!header || !ents)
+		return 0;
+
+	fu = le64_to_cpu(header->first_usable_lba);
+	lu = le64_to_cpu(header->last_usable_lba);
+
+	/*
+	 * Begin from the specified starting point or from the first usable
+	 * LBA, whichever is greater...
+	 */
+	first = start < fu ? fu : start;
+
+	/*
+	 * Now search through all partitions; if first is within an
+	 * existing partition, move it to the next sector after that
+	 * partition and repeat. If first was moved, set firstMoved
+	 * flag; repeat until firstMoved is not set, so as to catch
+	 * cases where partitions are out of sequential order....
+	 */
+	do {
+		first_moved = 0;
+		for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) {
+			if (partition_unused(&ents[i]))
+				continue;
+			if (first < gpt_partition_start(&ents[i]))
+				continue;
+			if (first <= gpt_partition_end(&ents[i])) {
+				first = gpt_partition_end(&ents[i]) + 1;
+				first_moved = 1;
+			}
+		}
+	} while (first_moved == 1);
+
+	if (first > lu)
+		first = 0;
+
+	return first;
+}
+
+
+/* Returns last available sector in the free space pointed to by start. From gdisk. */
+static uint64_t find_last_free(struct gpt_header *header,
+			       struct gpt_entry *ents, uint64_t start)
+{
+	uint32_t i;
+	uint64_t nearest_start;
+
+	if (!header || !ents)
+		return 0;
+
+	nearest_start = le64_to_cpu(header->last_usable_lba);
+
+	for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) {
+		uint64_t ps = gpt_partition_start(&ents[i]);
+
+		if (nearest_start > ps && ps > start)
+			nearest_start = ps - 1;
+	}
+
+	return nearest_start;
+}
+
+/* Returns the last free sector on the disk. From gdisk. */
+static uint64_t find_last_free_sector(struct gpt_header *header,
+				      struct gpt_entry *ents)
+{
+	uint32_t i, last_moved;
+	uint64_t last = 0;
+
+	if (!header || !ents)
+		goto done;
+
+	/* start by assuming the last usable LBA is available */
+	last = le64_to_cpu(header->last_usable_lba);
+	do {
+		last_moved = 0;
+		for (i = 0; i < le32_to_cpu(header->npartition_entries); i++) {
+			if ((last >= gpt_partition_start(&ents[i])) &&
+			    (last <= gpt_partition_end(&ents[i]))) {
+				last = gpt_partition_start(&ents[i]) - 1;
+				last_moved = 1;
+			}
+		}
+	} while (last_moved == 1);
+done:
+	return last;
+}
+
+/*
+ * Finds the first available sector in the largest block of unallocated
+ * space on the disk. Returns 0 if there are no available blocks left.
+ * From gdisk.
+ */
+static uint64_t find_first_in_largest(struct gpt_header *header,
+				      struct gpt_entry *ents)
+{
+	uint64_t start = 0, first_sect, last_sect;
+	uint64_t segment_size, selected_size = 0, selected_segment = 0;
+
+	if (!header || !ents)
+		goto done;
+
+	do {
+		first_sect =  find_first_available(header, ents, start);
+		if (first_sect != 0) {
+			last_sect = find_last_free(header, ents, first_sect);
+			segment_size = last_sect - first_sect + 1;
+
+			if (segment_size > selected_size) {
+				selected_size = segment_size;
+				selected_segment = first_sect;
+			}
+			start = last_sect + 1;
+		}
+	} while (first_sect != 0);
+
+done:
+	return selected_segment;
+}
+
+/*
+ * Find the total number of free sectors, the number of segments in which
+ * they reside, and the size of the largest of those segments. From gdisk.
+ */
+static uint64_t get_free_sectors(struct fdisk_context *cxt, struct gpt_header *header,
+				 struct gpt_entry *ents, uint32_t *nsegments,
+				 uint64_t *largest_segment)
+{
+	uint32_t num = 0;
+	uint64_t first_sect, last_sect;
+	uint64_t largest_seg = 0, segment_sz;
+	uint64_t totfound = 0, start = 0; /* starting point for each search */
+
+	if (!cxt->total_sectors)
+		goto done;
+
+	do {
+		first_sect = find_first_available(header, ents, start);
+		if (first_sect) {
+			last_sect = find_last_free(header, ents, first_sect);
+			segment_sz = last_sect - first_sect + 1;
+
+			if (segment_sz > largest_seg)
+				largest_seg = segment_sz;
+			totfound += segment_sz;
+			num++;
+			start = last_sect + 1;
+		}
+	} while (first_sect);
+
+done:
+	if (nsegments)
+		*nsegments = num;
+	if (largest_segment)
+		*largest_segment = largest_seg;
+
+	return totfound;
+}
+
+static int gpt_probe_label(struct fdisk_context *cxt)
+{
+	int mbr_type;
+	struct fdisk_gpt_label *gpt;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+
+	/* TODO: it would be nice to support scenario when GPT headers are OK,
+	 *       but PMBR is corrupt */
+	mbr_type = valid_pmbr(cxt);
+	if (!mbr_type)
+		goto failed;
+
+	DBG(LABEL, ul_debug("found a %s MBR", mbr_type == GPT_MBR_PROTECTIVE ?
+			    "protective" : "hybrid"));
+
+	/* primary header */
+	gpt->pheader = gpt_read_header(cxt, GPT_PRIMARY_PARTITION_TABLE_LBA,
+				       &gpt->ents);
+
+	if (gpt->pheader)
+		/* primary OK, try backup from alternative LBA */
+		gpt->bheader = gpt_read_header(cxt,
+					le64_to_cpu(gpt->pheader->alternative_lba),
+					NULL);
+	else
+		/* primary corrupted -- try last LBA */
+		gpt->bheader = gpt_read_header(cxt, last_lba(cxt), &gpt->ents);
+
+	if (!gpt->pheader && !gpt->bheader)
+		goto failed;
+
+	/* primary OK, backup corrupted -- recovery */
+	if (gpt->pheader && !gpt->bheader) {
+		fdisk_warnx(cxt, _("The backup GPT table is corrupt, but the "
+				  "primary appears OK, so that will be used."));
+		gpt->bheader = gpt_copy_header(cxt, gpt->pheader);
+		if (!gpt->bheader)
+			goto failed;
+		gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+	/* primary corrupted, backup OK -- recovery */
+	} else if (!gpt->pheader && gpt->bheader) {
+		fdisk_warnx(cxt, _("The primary GPT table is corrupt, but the "
+				  "backup appears OK, so that will be used."));
+		gpt->pheader = gpt_copy_header(cxt, gpt->bheader);
+		if (!gpt->pheader)
+			goto failed;
+		gpt_recompute_crc(gpt->pheader, gpt->ents);
+	}
+
+	cxt->label->nparts_max = le32_to_cpu(gpt->pheader->npartition_entries);
+	cxt->label->nparts_cur = partitions_in_use(gpt->pheader, gpt->ents);
+	return 1;
+failed:
+	DBG(LABEL, ul_debug("GPT probe failed"));
+	gpt_deinit(cxt->label);
+	return 0;
+}
+
+/*
+ * Stolen from libblkid - can be removed once partition semantics
+ * are added to the fdisk API.
+ */
+static char *encode_to_utf8(unsigned char *src, size_t count)
+{
+	uint16_t c;
+	char *dest;
+	size_t i, j, len = count;
+
+	dest = calloc(1, count);
+	if (!dest)
+		return NULL;
+
+	for (j = i = 0; i + 2 <= count; i += 2) {
+		/* always little endian */
+		c = (src[i+1] << 8) | src[i];
+		if (c == 0) {
+			dest[j] = '\0';
+			break;
+		} else if (c < 0x80) {
+			if (j+1 >= len)
+				break;
+			dest[j++] = (uint8_t) c;
+		} else if (c < 0x800) {
+			if (j+2 >= len)
+				break;
+			dest[j++] = (uint8_t) (0xc0 | (c >> 6));
+			dest[j++] = (uint8_t) (0x80 | (c & 0x3f));
+		} else {
+			if (j+3 >= len)
+				break;
+			dest[j++] = (uint8_t) (0xe0 | (c >> 12));
+			dest[j++] = (uint8_t) (0x80 | ((c >> 6) & 0x3f));
+			dest[j++] = (uint8_t) (0x80 | (c & 0x3f));
+		}
+	}
+	dest[j] = '\0';
+
+	return dest;
+}
+
+static int gpt_entry_attrs_to_string(struct gpt_entry *e, char **res)
+{
+	unsigned int n, count = 0;
+	size_t l;
+	char *bits, *p;
+	uint64_t attrs;
+
+	assert(e);
+	assert(res);
+
+	*res = NULL;
+	attrs = le64_to_cpu(e->attrs);
+	if (!attrs)
+		return 0;	/* no attributes at all */
+
+	bits = (char *) &attrs;
+
+	/* Note that sizeof() is correct here, we need separators between
+	 * the strings so also count \0 is correct */
+	*res = calloc(1, sizeof(GPT_ATTRSTR_NOBLOCK) +
+			 sizeof(GPT_ATTRSTR_REQ) +
+			 sizeof(GPT_ATTRSTR_LEGACY) +
+			 sizeof("GUID:") + (GPT_ATTRBIT_GUID_COUNT * 3));
+	if (!*res)
+		return -errno;
+
+	p = *res;
+	if (isset(bits, GPT_ATTRBIT_REQ)) {
+		memcpy(p, GPT_ATTRSTR_REQ, (l = sizeof(GPT_ATTRSTR_REQ)));
+		p += l - 1;
+	}
+	if (isset(bits, GPT_ATTRBIT_NOBLOCK)) {
+		if (p > *res)
+			*p++ = ' ';
+		memcpy(p, GPT_ATTRSTR_NOBLOCK, (l = sizeof(GPT_ATTRSTR_NOBLOCK)));
+		p += l - 1;
+	}
+	if (isset(bits, GPT_ATTRBIT_LEGACY)) {
+		if (p > *res)
+			*p++ = ' ';
+		memcpy(p, GPT_ATTRSTR_LEGACY, (l = sizeof(GPT_ATTRSTR_LEGACY)));
+		p += l - 1;
+	}
+
+	for (n = GPT_ATTRBIT_GUID_FIRST;
+	     n < GPT_ATTRBIT_GUID_FIRST + GPT_ATTRBIT_GUID_COUNT; n++) {
+
+		if (!isset(bits, n))
+			continue;
+		if (!count) {
+			if (p > *res)
+				*p++ = ' ';
+			p += sprintf(p, "GUID:%u", n);
+		} else
+			p += sprintf(p, ",%u", n);
+		count++;
+	}
+
+	return 0;
+}
+
+static int gpt_entry_attrs_from_string(
+			struct fdisk_context *cxt,
+			struct gpt_entry *e,
+			const char *str)
+{
+	const char *p = str;
+	uint64_t attrs = 0;
+	char *bits;
+
+	assert(e);
+	assert(p);
+
+	DBG(LABEL, ul_debug("GPT: parsing string attributes '%s'", p));
+
+	bits = (char *) &attrs;
+
+	while (p && *p) {
+		int bit = -1;
+
+		while (isblank(*p)) p++;
+		if (!*p)
+			break;
+
+		DBG(LABEL, ul_debug(" parsing item '%s'", p));
+
+		if (strncmp(p, "GUID:", 5) == 0) {
+			p += 5;
+			continue;
+		} else if (strncmp(p, GPT_ATTRSTR_REQ,
+					sizeof(GPT_ATTRSTR_REQ) - 1) == 0) {
+			bit = GPT_ATTRBIT_REQ;
+			p += sizeof(GPT_ATTRSTR_REQ) - 1;
+		} else if (strncmp(p, GPT_ATTRSTR_LEGACY,
+					sizeof(GPT_ATTRSTR_LEGACY) - 1) == 0) {
+			bit = GPT_ATTRBIT_LEGACY;
+			p += sizeof(GPT_ATTRSTR_LEGACY) - 1;
+		} else if (strncmp(p, GPT_ATTRSTR_NOBLOCK,
+					sizeof(GPT_ATTRSTR_NOBLOCK) - 1) == 0) {
+			bit = GPT_ATTRBIT_NOBLOCK;
+			p += sizeof(GPT_ATTRSTR_NOBLOCK) - 1;
+		} else if (isdigit((unsigned int) *p)) {
+			char *end = NULL;
+
+			errno = 0;
+			bit = strtol(p, &end, 0);
+			if (errno || !end || end == str
+			    || bit < GPT_ATTRBIT_GUID_FIRST
+			    || bit >= GPT_ATTRBIT_GUID_FIRST + GPT_ATTRBIT_GUID_COUNT)
+				bit = -1;
+			else
+				p = end;
+		}
+
+		if (bit < 0) {
+			fdisk_warnx(cxt, _("unssuported GPT attribute bit '%s'"), p);
+			return -EINVAL;
+		}
+
+		setbit(bits, bit);
+
+		while (isblank(*p)) p++;
+		if (*p == ',')
+			p++;
+	}
+
+	e->attrs = cpu_to_le64(attrs);
+	return 0;
+}
+
+static int gpt_get_partition(struct fdisk_context *cxt, size_t n,
+			     struct fdisk_partition *pa)
+{
+	struct fdisk_gpt_label *gpt;
+	struct gpt_entry *e;
+	char u_str[37];
+	int rc = 0;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+
+	if ((uint32_t) n >= le32_to_cpu(gpt->pheader->npartition_entries))
+		return -EINVAL;
+
+	gpt = self_label(cxt);
+	e = &gpt->ents[n];
+
+	pa->used = !partition_unused(e) || gpt_partition_start(e);
+	if (!pa->used)
+		return 0;
+
+	pa->start = gpt_partition_start(e);
+	pa->size = gpt_partition_size(e);
+	pa->type = gpt_partition_parttype(cxt, e);
+
+	if (guid_to_string(&e->partition_guid, u_str)) {
+		pa->uuid = strdup(u_str);
+		if (!pa->uuid) {
+			rc = -errno;
+			goto done;
+		}
+	} else
+		pa->uuid = NULL;
+
+	rc = gpt_entry_attrs_to_string(e, &pa->attrs);
+	if (rc)
+		goto done;
+
+	pa->name = encode_to_utf8((unsigned char *)e->name, sizeof(e->name));
+	return 0;
+done:
+	fdisk_reset_partition(pa);
+	return rc;
+}
+
+
+static int gpt_set_partition(struct fdisk_context *cxt, size_t n,
+			     struct fdisk_partition *pa)
+{
+	struct fdisk_gpt_label *gpt;
+	struct gpt_entry *e;
+	int rc = 0;
+	uint64_t start, end;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+
+	if ((uint32_t) n >= le32_to_cpu(gpt->pheader->npartition_entries))
+		return -EINVAL;
+
+	FDISK_INIT_UNDEF(start);
+	FDISK_INIT_UNDEF(end);
+
+	gpt = self_label(cxt);
+	e = &gpt->ents[n];
+
+	if (pa->uuid) {
+		char new_u[37], old_u[37];
+
+		guid_to_string(&e->partition_guid, old_u);
+		rc = gpt_entry_set_uuid(e, pa->uuid);
+		if (rc)
+			return rc;
+		guid_to_string(&e->partition_guid, new_u);
+		fdisk_info(cxt, _("Partition UUID changed from %s to %s."),
+			old_u, new_u);
+	}
+
+	if (pa->name) {
+		char *old = encode_to_utf8((unsigned char *)e->name, sizeof(e->name));
+		gpt_entry_set_name(e, pa->name);
+
+		fdisk_info(cxt, _("Partition name changed from '%s' to '%.*s'."),
+			old, (int) GPT_PART_NAME_LEN, pa->name);
+		free(old);
+	}
+
+	if (pa->type && pa->type->typestr) {
+		struct gpt_guid typeid;
+
+		rc = string_to_guid(pa->type->typestr, &typeid);
+		if (rc)
+			return rc;
+		gpt_entry_set_type(e, &typeid);
+	}
+	if (pa->attrs) {
+		rc = gpt_entry_attrs_from_string(cxt, e, pa->attrs);
+		if (rc)
+			return rc;
+	}
+
+	if (fdisk_partition_has_start(pa))
+		start = pa->start;
+	if (fdisk_partition_has_size(pa))
+		end = gpt_partition_start(e) + pa->size - 1ULL;
+
+	if (pa->end_follow_default) {
+		/* enlarge */
+		if (!FDISK_IS_UNDEF(start))
+			start = gpt_partition_start(e);
+		end = find_last_free(gpt->bheader, gpt->ents, start);
+		if (!end)
+			FDISK_INIT_UNDEF(end);
+	}
+
+	if (!FDISK_IS_UNDEF(start))
+		e->lba_start = cpu_to_le64(start);
+	if (!FDISK_IS_UNDEF(end))
+		e->lba_end = cpu_to_le64(end);
+
+	gpt_recompute_crc(gpt->pheader, gpt->ents);
+	gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+	fdisk_label_set_changed(cxt->label, 1);
+	return rc;
+}
+
+
+/*
+ * List label partitions.
+ */
+static int gpt_list_disklabel(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	if (fdisk_is_details(cxt)) {
+		struct gpt_header *h = self_label(cxt)->pheader;
+
+		fdisk_info(cxt, _("First LBA: %ju"), h->first_usable_lba);
+		fdisk_info(cxt, _("Last LBA: %ju"), h->last_usable_lba);
+		/* TRANSLATORS: The LBA (Logical Block Address) of the backup GPT header. */
+		fdisk_info(cxt, _("Alternative LBA: %ju"), h->alternative_lba);
+		/* TRANSLATORS: The start of the array of partition entries. */
+		fdisk_info(cxt, _("Partition entries LBA: %ju"), h->partition_entry_lba);
+		fdisk_info(cxt, _("Allocated partition entries: %u"), h->npartition_entries);
+	}
+
+	return 0;
+}
+
+/*
+ * Write partitions.
+ * Returns 0 on success, or corresponding error otherwise.
+ */
+static int gpt_write_partitions(struct fdisk_context *cxt,
+				struct gpt_header *header, struct gpt_entry *ents)
+{
+	off_t offset = le64_to_cpu(header->partition_entry_lba) * cxt->sector_size;
+	uint32_t nparts = le32_to_cpu(header->npartition_entries);
+	uint32_t totwrite = nparts * le32_to_cpu(header->sizeof_partition_entry);
+	ssize_t rc;
+
+	if (offset != lseek(cxt->dev_fd, offset, SEEK_SET))
+		goto fail;
+
+	rc = write(cxt->dev_fd, ents, totwrite);
+	if (rc > 0 && totwrite == (uint32_t) rc)
+		return 0;
+fail:
+	return -errno;
+}
+
+/*
+ * Write a GPT header to a specified LBA
+ * Returns 0 on success, or corresponding error otherwise.
+ */
+static int gpt_write_header(struct fdisk_context *cxt,
+			    struct gpt_header *header, uint64_t lba)
+{
+	off_t offset = lba * cxt->sector_size;
+
+	if (offset != lseek(cxt->dev_fd, offset, SEEK_SET))
+		goto fail;
+	if (cxt->sector_size ==
+	    (size_t) write(cxt->dev_fd, header, cxt->sector_size))
+		return 0;
+fail:
+	return -errno;
+}
+
+/*
+ * Write the protective MBR.
+ * Returns 0 on success, or corresponding error otherwise.
+ */
+static int gpt_write_pmbr(struct fdisk_context *cxt)
+{
+	off_t offset;
+	struct gpt_legacy_mbr *pmbr = NULL;
+
+	assert(cxt);
+	assert(cxt->firstsector);
+
+	pmbr = (struct gpt_legacy_mbr *) cxt->firstsector;
+
+	/* zero out the legacy partitions */
+	memset(pmbr->partition_record, 0, sizeof(pmbr->partition_record));
+
+	pmbr->signature = cpu_to_le16(MSDOS_MBR_SIGNATURE);
+	pmbr->partition_record[0].os_type      = EFI_PMBR_OSTYPE;
+	pmbr->partition_record[0].start_sector = 1;
+	pmbr->partition_record[0].end_head     = 0xFE;
+	pmbr->partition_record[0].end_sector   = 0xFF;
+	pmbr->partition_record[0].end_track    = 0xFF;
+	pmbr->partition_record[0].starting_lba = cpu_to_le32(1);
+
+	/*
+	 * Set size_in_lba to the size of the disk minus one. If the size of the disk
+	 * is too large to be represented by a 32bit LBA (2Tb), set it to 0xFFFFFFFF.
+	 */
+	if (cxt->total_sectors - 1 > 0xFFFFFFFFULL)
+		pmbr->partition_record[0].size_in_lba = cpu_to_le32(0xFFFFFFFF);
+	else
+		pmbr->partition_record[0].size_in_lba =
+			cpu_to_le32(cxt->total_sectors - 1UL);
+
+	offset = GPT_PMBR_LBA * cxt->sector_size;
+	if (offset != lseek(cxt->dev_fd, offset, SEEK_SET))
+		goto fail;
+
+	/* pMBR covers the first sector (LBA) of the disk */
+	if (write_all(cxt->dev_fd, pmbr, cxt->sector_size))
+		goto fail;
+	return 0;
+fail:
+	return -errno;
+}
+
+/*
+ * Writes in-memory GPT and pMBR data to disk.
+ * Returns 0 if successful write, otherwise, a corresponding error.
+ * Any indication of error will abort the operation.
+ */
+static int gpt_write_disklabel(struct fdisk_context *cxt)
+{
+	struct fdisk_gpt_label *gpt;
+	int mbr_type;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+	mbr_type = valid_pmbr(cxt);
+
+	/* check that disk is big enough to handle the backup header */
+	if (le64_to_cpu(gpt->pheader->alternative_lba) > cxt->total_sectors)
+		goto err0;
+
+	/* check that the backup header is properly placed */
+	if (le64_to_cpu(gpt->pheader->alternative_lba) < cxt->total_sectors - 1)
+		/* TODO: correct this (with user authorization) and write */
+		goto err0;
+
+	if (check_overlap_partitions(gpt->pheader, gpt->ents))
+		goto err0;
+
+	/* recompute CRCs for both headers */
+	gpt_recompute_crc(gpt->pheader, gpt->ents);
+	gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+	/*
+	 * UEFI requires writing in this specific order:
+	 *   1) backup partition tables
+	 *   2) backup GPT header
+	 *   3) primary partition tables
+	 *   4) primary GPT header
+	 *   5) protective MBR
+	 *
+	 * If any write fails, we abort the rest.
+	 */
+	if (gpt_write_partitions(cxt, gpt->bheader, gpt->ents) != 0)
+		goto err1;
+	if (gpt_write_header(cxt, gpt->bheader,
+			     le64_to_cpu(gpt->pheader->alternative_lba)) != 0)
+		goto err1;
+	if (gpt_write_partitions(cxt, gpt->pheader, gpt->ents) != 0)
+		goto err1;
+	if (gpt_write_header(cxt, gpt->pheader, GPT_PRIMARY_PARTITION_TABLE_LBA) != 0)
+		goto err1;
+
+	if (mbr_type == GPT_MBR_HYBRID)
+		fdisk_warnx(cxt, _("The device contains hybrid MBR -- writing GPT only. "
+				   "You have to sync the MBR manually."));
+	else if (gpt_write_pmbr(cxt) != 0)
+		goto err1;
+
+	DBG(LABEL, ul_debug("GPT write success"));
+	return 0;
+err0:
+	DBG(LABEL, ul_debug("GPT write failed: incorrect input"));
+	errno = EINVAL;
+	return -EINVAL;
+err1:
+	DBG(LABEL, ul_debug("GPT write failed: %m"));
+	return -errno;
+}
+
+/*
+ * Verify data integrity and report any found problems for:
+ *   - primary and backup header validations
+ *   - paritition validations
+ */
+static int gpt_verify_disklabel(struct fdisk_context *cxt)
+{
+	int nerror = 0;
+	unsigned int ptnum;
+	struct fdisk_gpt_label *gpt;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+
+	if (!gpt || !gpt->bheader) {
+		nerror++;
+		fdisk_warnx(cxt, _("Disk does not contain a valid backup header."));
+	}
+
+	if (!gpt_check_header_crc(gpt->pheader, gpt->ents)) {
+		nerror++;
+		fdisk_warnx(cxt, _("Invalid primary header CRC checksum."));
+	}
+	if (gpt->bheader && !gpt_check_header_crc(gpt->bheader, gpt->ents)) {
+		nerror++;
+		fdisk_warnx(cxt, _("Invalid backup header CRC checksum."));
+	}
+
+	if (!gpt_check_entryarr_crc(gpt->pheader, gpt->ents)) {
+		nerror++;
+		fdisk_warnx(cxt, _("Invalid partition entry checksum."));
+	}
+
+	if (!gpt_check_lba_sanity(cxt, gpt->pheader)) {
+		nerror++;
+		fdisk_warnx(cxt, _("Invalid primary header LBA sanity checks."));
+	}
+	if (gpt->bheader && !gpt_check_lba_sanity(cxt, gpt->bheader)) {
+		nerror++;
+		fdisk_warnx(cxt, _("Invalid backup header LBA sanity checks."));
+	}
+
+	if (le64_to_cpu(gpt->pheader->my_lba) != GPT_PRIMARY_PARTITION_TABLE_LBA) {
+		nerror++;
+		fdisk_warnx(cxt, _("MyLBA mismatch with real position at primary header."));
+	}
+	if (gpt->bheader && le64_to_cpu(gpt->bheader->my_lba) != last_lba(cxt)) {
+		nerror++;
+		fdisk_warnx(cxt, _("MyLBA mismatch with real position at backup header."));
+
+	}
+	if (le64_to_cpu(gpt->pheader->alternative_lba) >= cxt->total_sectors) {
+		nerror++;
+		fdisk_warnx(cxt, _("Disk is too small to hold all data."));
+	}
+
+	/*
+	 * if the GPT is the primary table, check the alternateLBA
+	 * to see if it is a valid GPT
+	 */
+	if (gpt->bheader && (le64_to_cpu(gpt->pheader->my_lba) !=
+			     le64_to_cpu(gpt->bheader->alternative_lba))) {
+		nerror++;
+		fdisk_warnx(cxt, _("Primary and backup header mismatch."));
+	}
+
+	ptnum = check_overlap_partitions(gpt->pheader, gpt->ents);
+	if (ptnum) {
+		nerror++;
+		fdisk_warnx(cxt, _("Partition %u overlaps with partition %u."),
+				ptnum, ptnum+1);
+	}
+
+	ptnum = check_too_big_partitions(gpt->pheader, gpt->ents, cxt->total_sectors);
+	if (ptnum) {
+		nerror++;
+		fdisk_warnx(cxt, _("Partition %u is too big for the disk."),
+				ptnum);
+	}
+
+	ptnum = check_start_after_end_paritions(gpt->pheader, gpt->ents);
+	if (ptnum) {
+		nerror++;
+		fdisk_warnx(cxt, _("Partition %u ends before it starts."),
+				ptnum);
+	}
+
+	if (!nerror) { /* yay :-) */
+		uint32_t nsegments = 0;
+		uint64_t free_sectors = 0, largest_segment = 0;
+		char *strsz = NULL;
+
+		fdisk_info(cxt, _("No errors detected."));
+		fdisk_info(cxt, _("Header version: %s"), gpt_get_header_revstr(gpt->pheader));
+		fdisk_info(cxt, _("Using %u out of %d partitions."),
+		       partitions_in_use(gpt->pheader, gpt->ents),
+		       le32_to_cpu(gpt->pheader->npartition_entries));
+
+		free_sectors = get_free_sectors(cxt, gpt->pheader, gpt->ents,
+						&nsegments, &largest_segment);
+		if (largest_segment)
+			strsz = size_to_human_string(SIZE_SUFFIX_SPACE | SIZE_SUFFIX_3LETTER,
+					largest_segment * cxt->sector_size);
+
+		fdisk_info(cxt,
+			   P_("A total of %ju free sectors is available in %u segment.",
+			      "A total of %ju free sectors is available in %u segments "
+			      "(the largest is %s).", nsegments),
+			   free_sectors, nsegments, strsz);
+		free(strsz);
+
+	} else
+		fdisk_warnx(cxt,
+			P_("%d error detected.", "%d errors detected.", nerror),
+			nerror);
+
+	return 0;
+}
+
+/* Delete a single GPT partition, specified by partnum. */
+static int gpt_delete_partition(struct fdisk_context *cxt,
+				size_t partnum)
+{
+	struct fdisk_gpt_label *gpt;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+
+	if (partnum >= cxt->label->nparts_max
+	    ||  partition_unused(&gpt->ents[partnum]))
+		return -EINVAL;
+
+	/* hasta la vista, baby! */
+	memset(&gpt->ents[partnum], 0, sizeof(struct gpt_entry));
+	if (!partition_unused(&gpt->ents[partnum]))
+		return -EINVAL;
+	else {
+		gpt_recompute_crc(gpt->pheader, gpt->ents);
+		gpt_recompute_crc(gpt->bheader, gpt->ents);
+		cxt->label->nparts_cur--;
+		fdisk_label_set_changed(cxt->label, 1);
+	}
+
+	return 0;
+}
+
+
+/* Performs logical checks to add a new partition entry */
+static int gpt_add_partition(
+		struct fdisk_context *cxt,
+		struct fdisk_partition *pa,
+		size_t *partno)
+{
+	uint64_t user_f, user_l;	/* user input ranges for first and last sectors */
+	uint64_t disk_f, disk_l;	/* first and last available sector ranges on device*/
+	uint64_t dflt_f, dflt_l;	/* largest segment (default) */
+	struct gpt_guid typeid;
+	struct fdisk_gpt_label *gpt;
+	struct gpt_header *pheader;
+	struct gpt_entry *e, *ents;
+	struct fdisk_ask *ask = NULL;
+	size_t partnum;
+	int rc;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+	pheader = gpt->pheader;
+	ents = gpt->ents;
+
+	rc = fdisk_partition_next_partno(pa, cxt, &partnum);
+	if (rc) {
+		DBG(LABEL, ul_debug("GPT failed to get next partno"));
+		return rc;
+	}
+	if (!partition_unused(&ents[partnum])) {
+		fdisk_warnx(cxt, _("Partition %zu is already defined.  "
+			           "Delete it before re-adding it."), partnum +1);
+		return -ERANGE;
+	}
+	if (le32_to_cpu(pheader->npartition_entries) ==
+			partitions_in_use(pheader, ents)) {
+		fdisk_warnx(cxt, _("All partitions are already in use."));
+		return -ENOSPC;
+	}
+	if (!get_free_sectors(cxt, pheader, ents, NULL, NULL)) {
+		fdisk_warnx(cxt, _("No free sectors available."));
+		return -ENOSPC;
+	}
+
+	string_to_guid(pa && pa->type && pa->type->typestr ?
+				pa->type->typestr:
+				GPT_DEFAULT_ENTRY_TYPE, &typeid);
+
+	disk_f = find_first_available(pheader, ents, pheader->first_usable_lba);
+
+	/* if first sector no explicitly defined then ignore small gaps before
+	 * the first partition */
+	if ((!pa || !fdisk_partition_has_start(pa))
+	    && !partition_unused(&ents[0])
+	    && disk_f < gpt_partition_start(&ents[0])) {
+
+		do {
+			uint64_t x;
+			DBG(LABEL, ul_debug("testing first sector %ju", disk_f));
+			disk_f = find_first_available(pheader, ents, disk_f);
+			if (!disk_f)
+				break;
+			x = find_last_free(pheader, ents, disk_f);
+			if (x - disk_f >= cxt->grain / cxt->sector_size)
+				break;
+			DBG(LABEL, ul_debug("first sector %ju addresses to small space, continue...", disk_f));
+			disk_f = x + 1;
+		} while(1);
+
+		if (disk_f == 0)
+			disk_f = find_first_available(pheader, ents, pheader->first_usable_lba);
+	}
+
+	disk_l = find_last_free_sector(pheader, ents);
+
+	/* the default is the largest free space */
+	dflt_f = find_first_in_largest(pheader, ents);
+	dflt_l = find_last_free(pheader, ents, dflt_f);
+
+	/* align the default in range <dflt_f,dflt_l>*/
+	dflt_f = fdisk_align_lba_in_range(cxt, dflt_f, dflt_f, dflt_l);
+
+	/* first sector */
+	if (pa && pa->start_follow_default) {
+		user_f = dflt_f;
+
+	} else if (pa && fdisk_partition_has_start(pa)) {
+		DBG(LABEL, ul_debug("first sector defined: %ju", pa->start));
+		if (pa->start != find_first_available(pheader, ents, pa->start)) {
+			fdisk_warnx(cxt, _("Sector %ju already used."), pa->start);
+			return -ERANGE;
+		}
+		user_f = pa->start;
+	} else {
+		/*  ask by dialog */
+		for (;;) {
+			if (!ask)
+				ask = fdisk_new_ask();
+			else
+				fdisk_reset_ask(ask);
+
+			/* First sector */
+			fdisk_ask_set_query(ask, _("First sector"));
+			fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+			fdisk_ask_number_set_low(ask,     disk_f);	/* minimal */
+			fdisk_ask_number_set_default(ask, dflt_f);	/* default */
+			fdisk_ask_number_set_high(ask,    disk_l);	/* maximal */
+
+			rc = fdisk_do_ask(cxt, ask);
+			if (rc)
+				goto done;
+
+			user_f = fdisk_ask_number_get_result(ask);
+			if (user_f != find_first_available(pheader, ents, user_f)) {
+				fdisk_warnx(cxt, _("Sector %ju already used."), user_f);
+				continue;
+			}
+			break;
+		}
+	}
+
+
+	/* Last sector */
+	dflt_l = find_last_free(pheader, ents, user_f);
+
+	if (pa && pa->end_follow_default) {
+		user_l = dflt_l;
+
+	} else if (pa && fdisk_partition_has_size(pa)) {
+		user_l = user_f + pa->size - 1;
+		DBG(LABEL, ul_debug("size defined: %ju, end: %ju (last possible: %ju)",
+					pa->size, user_l, dflt_l));
+		if (user_l != dflt_l && !pa->size_explicit)
+			user_l = fdisk_align_lba_in_range(cxt, user_l, user_f, dflt_l) - 1;
+
+	} else {
+		for (;;) {
+			if (!ask)
+				ask = fdisk_new_ask();
+			else
+				fdisk_reset_ask(ask);
+
+			fdisk_ask_set_query(ask, _("Last sector, +sectors or +size{K,M,G,T,P}"));
+			fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+			fdisk_ask_number_set_low(ask,     user_f);	/* minimal */
+			fdisk_ask_number_set_default(ask, dflt_l);	/* default */
+			fdisk_ask_number_set_high(ask,    dflt_l);	/* maximal */
+			fdisk_ask_number_set_base(ask,    user_f);	/* base for relative input */
+			fdisk_ask_number_set_unit(ask,    cxt->sector_size);
+
+			rc = fdisk_do_ask(cxt, ask);
+			if (rc)
+				goto done;
+
+			user_l = fdisk_ask_number_get_result(ask);
+			if (fdisk_ask_number_is_relative(ask)) {
+				user_l = fdisk_align_lba_in_range(cxt, user_l, user_f, dflt_l) - 1;
+
+				/* no space for anything useful, use all space
+				if (user_l + (cxt->grain / cxt->sector_size) > dflt_l)
+					user_l = dflt_l;
+				*/
+			}
+
+			if (user_l > user_f && user_l <= disk_l)
+				break;
+		}
+	}
+
+
+	if (user_f > user_l || partnum >= cxt->label->nparts_max) {
+		fdisk_warnx(cxt, _("Could not create partition %zu"), partnum + 1);
+		rc = -EINVAL;
+		goto done;
+	}
+
+	assert(!FDISK_IS_UNDEF(user_l));
+	assert(!FDISK_IS_UNDEF(user_f));
+
+	e = &ents[partnum];
+	e->lba_end = cpu_to_le64(user_l);
+	e->lba_start = cpu_to_le64(user_f);
+
+	gpt_entry_set_type(e, &typeid);
+
+	if (pa && pa->uuid) {
+		/* Sometimes it's necessary to create a copy of the PT and
+		 * reuse already defined UUID
+		 */
+		rc = gpt_entry_set_uuid(e, pa->uuid);
+		if (rc)
+			goto done;
+	} else {
+		/* Any time a new partition entry is created a new GUID must be
+		 * generated for that partition, and every partition is guaranteed
+		 * to have a unique GUID.
+		 */
+		uuid_generate_random((unsigned char *) &e->partition_guid);
+		swap_efi_guid(&e->partition_guid);
+	}
+
+	if (pa && pa->name && *pa->name)
+		gpt_entry_set_name(e, pa->name);
+	if (pa && pa->attrs)
+		gpt_entry_attrs_from_string(cxt, e, pa->attrs);
+
+	DBG(LABEL, ul_debug("GPT new partition: partno=%zu, start=%ju, end=%ju, size=%ju",
+				partnum,
+				gpt_partition_start(e),
+				gpt_partition_end(e),
+				gpt_partition_size(e)));
+
+	gpt_recompute_crc(gpt->pheader, ents);
+	gpt_recompute_crc(gpt->bheader, ents);
+
+	/* report result */
+	{
+		struct fdisk_parttype *t;
+
+		cxt->label->nparts_cur++;
+		fdisk_label_set_changed(cxt->label, 1);
+
+		t = gpt_partition_parttype(cxt, &ents[partnum]);
+		fdisk_info_new_partition(cxt, partnum + 1, user_f, user_l, t);
+		fdisk_unref_parttype(t);
+	}
+
+	rc = 0;
+	if (partno)
+		*partno = partnum;
+done:
+	fdisk_unref_ask(ask);
+	return rc;
+}
+
+/*
+ * Create a new GPT disklabel - destroys any previous data.
+ */
+static int gpt_create_disklabel(struct fdisk_context *cxt)
+{
+	int rc = 0;
+	ssize_t esz = 0;
+	char str[37];
+	struct fdisk_gpt_label *gpt;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+
+	/* label private stuff has to be empty, see gpt_deinit() */
+	assert(gpt->pheader == NULL);
+	assert(gpt->bheader == NULL);
+
+	/*
+	 * When no header, entries or pmbr is set, we're probably
+	 * dealing with a new, empty disk - so always allocate memory
+	 * to deal with the data structures whatever the case is.
+	 */
+	rc = gpt_mknew_pmbr(cxt);
+	if (rc < 0)
+		goto done;
+
+	/* primary */
+	gpt->pheader = calloc(1, sizeof(*gpt->pheader));
+	if (!gpt->pheader) {
+		rc = -ENOMEM;
+		goto done;
+	}
+	rc = gpt_mknew_header(cxt, gpt->pheader, GPT_PRIMARY_PARTITION_TABLE_LBA);
+	if (rc < 0)
+		goto done;
+
+	/* backup ("copy" primary) */
+	gpt->bheader = calloc(1, sizeof(*gpt->bheader));
+	if (!gpt->bheader) {
+		rc = -ENOMEM;
+		goto done;
+	}
+	rc = gpt_mknew_header_from_bkp(cxt, gpt->bheader,
+			last_lba(cxt), gpt->pheader);
+	if (rc < 0)
+		goto done;
+
+	esz = le32_to_cpu(gpt->pheader->npartition_entries) *
+	      le32_to_cpu(gpt->pheader->sizeof_partition_entry);
+	gpt->ents = calloc(1, esz);
+	if (!gpt->ents) {
+		rc = -ENOMEM;
+		goto done;
+	}
+	gpt_recompute_crc(gpt->pheader, gpt->ents);
+	gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+	cxt->label->nparts_max = le32_to_cpu(gpt->pheader->npartition_entries);
+	cxt->label->nparts_cur = 0;
+
+	guid_to_string(&gpt->pheader->disk_guid, str);
+	fdisk_label_set_changed(cxt->label, 1);
+	fdisk_info(cxt, _("Created a new GPT disklabel (GUID: %s)."), str);
+done:
+	return rc;
+}
+
+static int gpt_get_disklabel_id(struct fdisk_context *cxt, char **id)
+{
+	struct fdisk_gpt_label *gpt;
+	char str[37];
+
+	assert(cxt);
+	assert(id);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+	guid_to_string(&gpt->pheader->disk_guid, str);
+
+	*id = strdup(str);
+	if (!*id)
+		return -ENOMEM;
+	return 0;
+}
+
+static int gpt_set_disklabel_id(struct fdisk_context *cxt)
+{
+	struct fdisk_gpt_label *gpt;
+	struct gpt_guid uuid;
+	char *str, *old, *new;
+	int rc;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+	if (fdisk_ask_string(cxt,
+			_("Enter new disk UUID (in 8-4-4-4-12 format)"), &str))
+		return -EINVAL;
+
+	rc = string_to_guid(str, &uuid);
+	free(str);
+
+	if (rc) {
+		fdisk_warnx(cxt, _("Failed to parse your UUID."));
+		return rc;
+	}
+
+	gpt_get_disklabel_id(cxt, &old);
+
+	gpt->pheader->disk_guid = uuid;
+	gpt->bheader->disk_guid = uuid;
+
+	gpt_recompute_crc(gpt->pheader, gpt->ents);
+	gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+	gpt_get_disklabel_id(cxt, &new);
+
+	fdisk_info(cxt, _("Disk identifier changed from %s to %s."), old, new);
+
+	free(old);
+	free(new);
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+static int gpt_part_is_used(struct fdisk_context *cxt, size_t i)
+{
+	struct fdisk_gpt_label *gpt;
+	struct gpt_entry *e;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+
+	if ((uint32_t) i >= le32_to_cpu(gpt->pheader->npartition_entries))
+		return 0;
+	e = &gpt->ents[i];
+
+	return !partition_unused(e) || gpt_partition_start(e);
+}
+
+/**
+ * fdisk_gpt_is_hybrid:
+ * @cxt: context
+ *
+ * The regular GPT contains PMBR (dummy protective MBR) where the protective
+ * MBR does not address any partitions.
+ *
+ * Hybrid GPT contains regular MBR where this partition table addresses the
+ * same partitions as GPT. It's recommended to not use hybrid GPT due to MBR
+ * limits.
+ *
+ * The libfdisk does not provide functionality to sync GPT and MBR, you have to
+ * directly access and modify (P)MBR (see fdisk_new_nested_context()).
+ *
+ * Returns: 1 if partition table detected as hybrid otherwise return 0
+ */
+int fdisk_gpt_is_hybrid(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return valid_pmbr(cxt) == GPT_MBR_HYBRID;
+}
+
+static int gpt_toggle_partition_flag(
+		struct fdisk_context *cxt,
+		size_t i,
+		unsigned long flag)
+{
+	struct fdisk_gpt_label *gpt;
+	uint64_t attrs, tmp;
+	char *bits;
+	const char *name = NULL;
+	int bit = -1, rc;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	DBG(LABEL, ul_debug("GPT entry attribute change requested partno=%zu", i));
+	gpt = self_label(cxt);
+
+	if ((uint32_t) i >= le32_to_cpu(gpt->pheader->npartition_entries))
+		return -EINVAL;
+
+	attrs = le64_to_cpu(gpt->ents[i].attrs);
+	bits = (char *) &attrs;
+
+	switch (flag) {
+	case GPT_FLAG_REQUIRED:
+		bit = GPT_ATTRBIT_REQ;
+		name = GPT_ATTRSTR_REQ;
+		break;
+	case GPT_FLAG_NOBLOCK:
+		bit = GPT_ATTRBIT_NOBLOCK;
+		name = GPT_ATTRSTR_NOBLOCK;
+		break;
+	case GPT_FLAG_LEGACYBOOT:
+		bit = GPT_ATTRBIT_LEGACY;
+		name = GPT_ATTRSTR_LEGACY;
+		break;
+	case GPT_FLAG_GUIDSPECIFIC:
+		rc = fdisk_ask_number(cxt, 48, 48, 63, _("Enter GUID specific bit"), &tmp);
+		if (rc)
+			return rc;
+		bit = tmp;
+		break;
+	default:
+		/* already specified PT_FLAG_GUIDSPECIFIC bit */
+		if (flag >= 48 && flag <= 63) {
+			bit = flag;
+			flag = GPT_FLAG_GUIDSPECIFIC;
+		}
+		break;
+	}
+
+	if (bit < 0) {
+		fdisk_warnx(cxt, _("failed to toggle unsupported bit %lu"), flag);
+		return -EINVAL;
+	}
+
+	if (!isset(bits, bit))
+		setbit(bits, bit);
+	else
+		clrbit(bits, bit);
+
+	gpt->ents[i].attrs = cpu_to_le64(attrs);
+
+	if (flag == GPT_FLAG_GUIDSPECIFIC)
+		fdisk_info(cxt, isset(bits, bit) ?
+			_("The GUID specific bit %d on partition %zu is enabled now.") :
+			_("The GUID specific bit %d on partition %zu is disabled now."),
+			bit, i + 1);
+	else
+		fdisk_info(cxt, isset(bits, bit) ?
+			_("The %s flag on partition %zu is enabled now.") :
+			_("The %s flag on partition %zu is disabled now."),
+			name, i + 1);
+
+	gpt_recompute_crc(gpt->pheader, gpt->ents);
+	gpt_recompute_crc(gpt->bheader, gpt->ents);
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+static int gpt_entry_cmp_start(const void *a, const void *b)
+{
+	struct gpt_entry *ae = (struct gpt_entry *) a,
+			 *be = (struct gpt_entry *) b;
+	int au = partition_unused(ae),
+	    bu = partition_unused(be);
+
+	if (au && bu)
+		return 0;
+	if (au)
+		return 1;
+	if (bu)
+		return -1;
+
+	return cmp_numbers(gpt_partition_start(ae), gpt_partition_start(be));
+}
+
+/* sort partition by start sector */
+static int gpt_reorder(struct fdisk_context *cxt)
+{
+	struct fdisk_gpt_label *gpt;
+	size_t nparts;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+	nparts = le32_to_cpu(gpt->pheader->npartition_entries);
+
+	qsort(gpt->ents, nparts, sizeof(struct gpt_entry),
+			gpt_entry_cmp_start);
+
+	gpt_recompute_crc(gpt->pheader, gpt->ents);
+	gpt_recompute_crc(gpt->bheader, gpt->ents);
+	fdisk_label_set_changed(cxt->label, 1);
+
+	fdisk_info(cxt, _("Done."));
+	return 0;
+}
+
+static int gpt_reset_alignment(struct fdisk_context *cxt)
+{
+	struct fdisk_gpt_label *gpt;
+	struct gpt_header *h;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, GPT));
+
+	gpt = self_label(cxt);
+	h = gpt ? gpt->pheader : NULL;
+
+	if (h) {
+		/* always follow existing table */
+		cxt->first_lba = h->first_usable_lba;
+		cxt->last_lba  = h->last_usable_lba;
+	} else {
+		/* estimate ranges for GPT */
+		uint64_t first, last;
+
+		count_first_last_lba(cxt, &first, &last);
+
+		if (cxt->first_lba < first)
+			cxt->first_lba = first;
+		if (cxt->last_lba > last)
+			cxt->last_lba = last;
+	}
+
+	return 0;
+}
+/*
+ * Deinitialize fdisk-specific variables
+ */
+static void gpt_deinit(struct fdisk_label *lb)
+{
+	struct fdisk_gpt_label *gpt = (struct fdisk_gpt_label *) lb;
+
+	if (!gpt)
+		return;
+
+	free(gpt->ents);
+	free(gpt->pheader);
+	free(gpt->bheader);
+
+	gpt->ents = NULL;
+	gpt->pheader = NULL;
+	gpt->bheader = NULL;
+}
+
+static const struct fdisk_label_operations gpt_operations =
+{
+	.probe		= gpt_probe_label,
+	.write		= gpt_write_disklabel,
+	.verify		= gpt_verify_disklabel,
+	.create		= gpt_create_disklabel,
+	.list		= gpt_list_disklabel,
+	.locate		= gpt_locate_disklabel,
+	.reorder	= gpt_reorder,
+	.get_id		= gpt_get_disklabel_id,
+	.set_id		= gpt_set_disklabel_id,
+
+	.get_part	= gpt_get_partition,
+	.set_part	= gpt_set_partition,
+	.add_part	= gpt_add_partition,
+	.del_part	= gpt_delete_partition,
+
+	.part_is_used	= gpt_part_is_used,
+	.part_toggle_flag = gpt_toggle_partition_flag,
+
+	.deinit		= gpt_deinit,
+
+	.reset_alignment = gpt_reset_alignment
+};
+
+static const struct fdisk_field gpt_fields[] =
+{
+	/* basic */
+	{ FDISK_FIELD_DEVICE,	N_("Device"),	 10,	0 },
+	{ FDISK_FIELD_START,	N_("Start"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_END,	N_("End"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SECTORS,	N_("Sectors"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SIZE,	N_("Size"),	  5,	FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY },
+	{ FDISK_FIELD_TYPE,	N_("Type"),	0.1,	FDISK_FIELDFL_EYECANDY },
+	/* expert */
+	{ FDISK_FIELD_TYPEID,	N_("Type-UUID"), 36,	FDISK_FIELDFL_DETAIL },
+	{ FDISK_FIELD_UUID,	N_("UUID"),	 36,	FDISK_FIELDFL_DETAIL },
+	{ FDISK_FIELD_NAME,	N_("Name"),	0.2,	FDISK_FIELDFL_DETAIL },
+	{ FDISK_FIELD_ATTR,	N_("Attrs"),	  0,	FDISK_FIELDFL_DETAIL }
+};
+
+/*
+ * allocates GPT in-memory stuff
+ */
+struct fdisk_label *fdisk_new_gpt_label(struct fdisk_context *cxt)
+{
+	struct fdisk_label *lb;
+	struct fdisk_gpt_label *gpt;
+
+	assert(cxt);
+
+	gpt = calloc(1, sizeof(*gpt));
+	if (!gpt)
+		return NULL;
+
+	/* initialize generic part of the driver */
+	lb = (struct fdisk_label *) gpt;
+	lb->name = "gpt";
+	lb->id = FDISK_DISKLABEL_GPT;
+	lb->op = &gpt_operations;
+	lb->parttypes = gpt_parttypes;
+	lb->nparttypes = ARRAY_SIZE(gpt_parttypes);
+
+	lb->fields = gpt_fields;
+	lb->nfields = ARRAY_SIZE(gpt_fields);
+
+	return lb;
+}
diff --git a/libblkid/libfdisk/src/init.c b/libblkid/libfdisk/src/init.c
new file mode 100644
index 0000000..61acb0a
--- /dev/null
+++ b/libblkid/libfdisk/src/init.c
@@ -0,0 +1,55 @@
+
+#include "fdiskP.h"
+
+
+/**
+ * SECTION: init
+ * @title: Library initialization
+ * @short_description: initialize debug stuff
+ *
+ */
+
+UL_DEBUG_DEFINE_MASK(libfdisk);
+UL_DEBUG_DEFINE_MASKNAMES(libfdisk) =
+{
+	{ "all",	LIBFDISK_DEBUG_ALL,	"info about all subsystems" },
+	{ "ask",	LIBFDISK_DEBUG_ASK,	"fdisk dialogs" },
+	{ "help",	LIBFDISK_DEBUG_HELP,	"this help" },
+	{ "cxt",	LIBFDISK_DEBUG_CXT,	"library context (handler)" },
+	{ "label",	LIBFDISK_DEBUG_LABEL,	"disk label utils" },
+	{ "part",	LIBFDISK_DEBUG_PART,	"partition utils" },
+	{ "parttype",	LIBFDISK_DEBUG_PARTTYPE,"partition type utils" },
+	{ "script",	LIBFDISK_DEBUG_SCRIPT,	"sfdisk-like scripts" },
+	{ "tab",	LIBFDISK_DEBUG_TAB,	"table utils"},
+	{ NULL, 0 }
+};
+
+/**
+ * fdisk_init_debug:
+ * @mask: debug mask (0xffff to enable full debuging)
+ *
+ * If the @mask is not specified then this function reads
+ * LIBFDISK_DEBUG environment variable to get the mask.
+ *
+ * Already initialized debugging stuff cannot be changed. It does not
+ * have effect to call this function twice.
+ *
+ * It's strongly recommended to use fdisk_init_debug(0) in your code.
+ */
+void fdisk_init_debug(int mask)
+{
+	if (libfdisk_debug_mask)
+		return;
+
+	__UL_INIT_DEBUG(libfdisk, LIBFDISK_DEBUG_, mask, LIBFDISK_DEBUG);
+
+
+	if (libfdisk_debug_mask != LIBFDISK_DEBUG_INIT
+	    && libfdisk_debug_mask != (LIBFDISK_DEBUG_HELP|LIBFDISK_DEBUG_INIT)) {
+
+		DBG(INIT, ul_debug("library debug mask: 0x%04x", libfdisk_debug_mask));
+	}
+
+	ON_DBG(HELP, ul_debug_print_masks("LIBFDISK_DEBUG",
+				UL_DEBUG_MASKNAMES(libfdisk)));
+}
diff --git a/libblkid/libfdisk/src/iter.c b/libblkid/libfdisk/src/iter.c
new file mode 100644
index 0000000..9a0b080
--- /dev/null
+++ b/libblkid/libfdisk/src/iter.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: iter
+ * @title: Iterator
+ * @short_description: unified iterator
+ *
+ * The iterator keeps the direction and the last position for access to the
+ * internal library tables/lists.
+ *
+ * It's very unusual to use the same iterator on multiple places in your
+ * application or share the same iterator, for this purpose libfdisk does not
+ * provide reference counting for this object. It's recommended to initialize
+ * the iterator by fdisk_new_iter() at begin of your function and then
+ * fdisk_free_iter() before you return from the function. 
+ *
+ * Don't forget to call fdisk_reset_iter() if you want to use the iterator more
+ * than once.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "fdiskP.h"
+
+/**
+ * fdisk_new_iter:
+ * @direction: FDISK_INTER_{FOR,BACK}WARD direction
+ *
+ * Returns: newly allocated generic libmount iterator.
+ */
+struct fdisk_iter *fdisk_new_iter(int direction)
+{
+	struct fdisk_iter *itr = calloc(1, sizeof(*itr));
+	if (!itr)
+		return NULL;
+	itr->direction = direction;
+	return itr;
+}
+
+/**
+ * fdisk_free_iter:
+ * @itr: iterator pointer
+ *
+ * Deallocates the iterator.
+ */
+void fdisk_free_iter(struct fdisk_iter *itr)
+{
+	free(itr);
+}
+
+/**
+ * fdisk_reset_iter:
+ * @itr: iterator pointer
+ * @direction: FDISK_INTER_{FOR,BACK}WARD or -1 to keep the direction unchanged
+ *
+ * Resets the iterator.
+ */
+void fdisk_reset_iter(struct fdisk_iter *itr, int direction)
+{
+	if (direction == -1)
+		direction = itr->direction;
+
+	memset(itr, 0, sizeof(*itr));
+	itr->direction = direction;
+}
+
+/**
+ * fdisk_iter_get_direction:
+ * @itr: iterator pointer
+ *
+ * Returns: FDISK_INTER_{FOR,BACK}WARD
+ */
+int fdisk_iter_get_direction(struct fdisk_iter *itr)
+{
+	return itr->direction;
+}
diff --git a/libblkid/libfdisk/src/label.c b/libblkid/libfdisk/src/label.c
new file mode 100644
index 0000000..750cfca
--- /dev/null
+++ b/libblkid/libfdisk/src/label.c
@@ -0,0 +1,569 @@
+
+#include "fdiskP.h"
+
+
+/**
+ * SECTION: label
+ * @title: Label
+ * @short_description: disk label (PT) specific data and functions
+ *
+ * The fdisk_new_context() initializes all label drivers, and allocate
+ * per-label specific data struct. This concept allows to store label specific
+ * settings to the label driver independently on the currently active label
+ * driver. Note that label struct cannot be deallocated, so there is no
+ * reference counting for fdisk_label objects. All is destroyed by
+ * fdisk_unref_context() only.
+ *
+ * Anyway, all label drives share in-memory first sector. The function
+ * fdisk_create_disklabel() overwrites the sector. But it's possible that
+ * label driver also uses another buffers, for example GPT uses more than only
+ * the first sector.
+ *
+ * All label operations are in-memory only, except fdisk_write_disklabel().
+ *
+ * All functions that use "struct fdisk_context" rather than "struct
+ * fdisk_label" use the currently active label driver.
+ */
+
+
+int fdisk_probe_labels(struct fdisk_context *cxt)
+{
+	size_t i;
+
+	cxt->label = NULL;
+
+	for (i = 0; i < cxt->nlabels; i++) {
+		struct fdisk_label *lb = cxt->labels[i];
+		struct fdisk_label *org = fdisk_get_label(cxt, NULL);
+		int rc;
+
+		if (!lb->op->probe)
+			continue;
+		if (lb->disabled) {
+			DBG(CXT, ul_debugobj(cxt, "%s: disabled -- ignore", lb->name));
+			continue;
+		}
+		DBG(CXT, ul_debugobj(cxt, "probing for %s", lb->name));
+
+		cxt->label = lb;
+		rc = lb->op->probe(cxt);
+		cxt->label = org;
+
+		if (rc != 1) {
+			if (lb->op->deinit)
+				lb->op->deinit(lb);	/* for sure */
+			continue;
+		}
+
+		__fdisk_switch_label(cxt, lb);
+		return 0;
+	}
+
+	DBG(CXT, ul_debugobj(cxt, "no label found"));
+	return 1; /* not found */
+}
+
+/**
+ * fdisk_label_get_name:
+ * @lb: label
+ *
+ * Returns: label name
+ */
+const char *fdisk_label_get_name(const struct fdisk_label *lb)
+{
+	return lb ? lb->name : NULL;
+}
+
+/**
+ * fdisk_label_is_labeltype:
+ * @lb: label
+ *
+ * Returns: FDISK_DISKLABEL_*.
+ */
+int fdisk_label_get_type(const struct fdisk_label *lb)
+{
+	return lb->id;
+}
+
+/**
+ * fdisk_label_require_geometry:
+ * @lb: label
+ *
+ * Returns: 1 if label requires CHS geometry
+ */
+int fdisk_label_require_geometry(const struct fdisk_label *lb)
+{
+	assert(lb);
+
+	return lb->flags & FDISK_LABEL_FL_REQUIRE_GEOMETRY ? 1 : 0;
+}
+
+/**
+ * fdisk_label_get_fields_ids
+ * @lb: label (or NULL for the current label)
+ * @cxt: context
+ * @ids: returns allocated array with FDISK_FIELD_* IDs
+ * @nids: returns number of items in fields
+ *
+ * This function returns the default fields for the label.
+ *
+ * Note that the set of the default fields depends on fdisk_enable_details()
+ * function. If the details are enabled then this function usually returns more
+ * fields.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_fields_ids(
+		const struct fdisk_label *lb,
+		struct fdisk_context *cxt,
+		int **ids, size_t *nids)
+{
+	size_t i, n;
+	int *c;
+
+	assert(cxt);
+
+	if (!lb)
+		lb = cxt->label;
+	if (!lb)
+		return -EINVAL;
+	if (!lb->fields || !lb->nfields)
+		return -ENOSYS;
+	c = calloc(lb->nfields, sizeof(int));
+	if (!c)
+		return -ENOMEM;
+	for (n = 0, i = 0; i < lb->nfields; i++) {
+		int id = lb->fields[i].id;
+
+		if ((fdisk_is_details(cxt) &&
+				(lb->fields[i].flags & FDISK_FIELDFL_EYECANDY))
+		     || (!fdisk_is_details(cxt) &&
+				(lb->fields[i].flags & FDISK_FIELDFL_DETAIL))
+		     || (id == FDISK_FIELD_SECTORS &&
+			         fdisk_use_cylinders(cxt))
+		     || (id == FDISK_FIELD_CYLINDERS &&
+			         !fdisk_use_cylinders(cxt)))
+			continue;
+
+		c[n++] = id;
+	}
+	if (ids)
+		*ids = c;
+	else
+		free(c);
+	if (nids)
+		*nids = n;
+	return 0;
+}
+
+/**
+ * fdisk_label_get_field:
+ * @lb: label
+ * @id: FDISK_FIELD_*
+ *
+ * The field struct describes data stored in struct fdisk_partition. The info
+ * about data is usable for example to generate human readable output (e.g.
+ * fdisk 'p'rint command). See fdisk_partition_to_stirng() and fdisk code.
+ *
+ * Returns: pointer to static instance of the field.
+ */
+const struct fdisk_field *fdisk_label_get_field(const struct fdisk_label *lb, int id)
+{
+	size_t i;
+
+	assert(lb);
+	assert(id > 0);
+
+	for (i = 0; i < lb->nfields; i++) {
+		if (lb->fields[i].id == id)
+			return &lb->fields[i];
+	}
+
+	return NULL;
+}
+
+/**
+ * fdisk_label_get_field_by_name
+ * @lb: label
+ * @name: field name
+ *
+ * Returns: pointer to static instance of the field.
+ */
+const struct fdisk_field *fdisk_label_get_field_by_name(
+				const struct fdisk_label *lb,
+				const char *name)
+{
+	size_t i;
+
+	assert(lb);
+	assert(name);
+
+	for (i = 0; i < lb->nfields; i++) {
+		if (lb->fields[i].name && strcasecmp(lb->fields[i].name, name) == 0)
+			return &lb->fields[i];
+	}
+
+	return NULL;
+}
+
+
+/**
+ * fdisk_field_get_id:
+ * @field: field instance
+ *
+ * Returns: field Id (FDISK_FIELD_*)
+ */
+int fdisk_field_get_id(const struct fdisk_field *field)
+{
+	return field ? field->id : -EINVAL;
+}
+
+/**
+ * fdisk_field_get_name:
+ * @field: field instance
+ *
+ * Returns: field name
+ */
+const char *fdisk_field_get_name(const struct fdisk_field *field)
+{
+	return field ? field->name : NULL;
+}
+
+/**
+ * fdisk_field_get_width:
+ * @field: field instance
+ *
+ * Returns: libsmartcols compatible width.
+ */
+double fdisk_field_get_width(const struct fdisk_field *field)
+{
+	return field ? field->width : -EINVAL;
+}
+
+/**
+ * fdisk_field_is_number:
+ * @field: field instance
+ *
+ * Returns: 1 if field represent number
+ */
+int fdisk_field_is_number(const struct fdisk_field *field)
+{
+	return field->flags ? field->flags & FDISK_FIELDFL_NUMBER : 0;
+}
+
+
+/**
+ * fdisk_write_disklabel:
+ * @cxt: fdisk context
+ *
+ * Write in-memory changes to disk. Be careful!
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_write_disklabel(struct fdisk_context *cxt)
+{
+	if (!cxt || !cxt->label || cxt->readonly)
+		return -EINVAL;
+	if (!cxt->label->op->write)
+		return -ENOSYS;
+	return cxt->label->op->write(cxt);
+}
+
+/**
+ * fdisk_verify_disklabel:
+ * @cxt: fdisk context
+ *
+ * Verifies the partition table.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_verify_disklabel(struct fdisk_context *cxt)
+{
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+	if (!cxt->label->op->verify)
+		return -ENOSYS;
+	if (fdisk_missing_geometry(cxt))
+		return -EINVAL;
+
+	return cxt->label->op->verify(cxt);
+}
+
+/**
+ * fdisk_list_disklabel:
+ * @cxt: fdisk context
+ *
+ * Lists details about disklabel, but no partitions.
+ *
+ * This function uses libfdisk ASK interface to print data. The details about
+ * partitions table are printed by FDISK_ASKTYPE_INFO.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_list_disklabel(struct fdisk_context *cxt)
+{
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+	if (!cxt->label->op->list)
+		return -ENOSYS;
+
+	return cxt->label->op->list(cxt);
+}
+
+/**
+ * fdisk_create_disklabel:
+ * @cxt: fdisk context
+ * @name: label name
+ *
+ * Creates a new disk label of type @name. If @name is NULL, then it will
+ * create a default system label type, either SUN or DOS. The function
+ * automaticaly switches the current label driver to @name. The function
+ * fdisk_get_label() returns the current label driver.
+ *
+ * The function modifies in-memory data only.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_create_disklabel(struct fdisk_context *cxt, const char *name)
+{
+	int haslabel = 0;
+	struct fdisk_label *lb;
+
+	if (!cxt)
+		return -EINVAL;
+
+	if (!name) { /* use default label creation */
+#ifdef __sparc__
+		name = "sun";
+#else
+		name = "dos";
+#endif
+	}
+
+	if (cxt->label) {
+		fdisk_deinit_label(cxt->label);
+		haslabel = 1;
+	}
+
+	lb = fdisk_get_label(cxt, name);
+	if (!lb || lb->disabled)
+		return -EINVAL;
+	if (!lb->op->create)
+		return -ENOSYS;
+
+	__fdisk_switch_label(cxt, lb);
+
+	if (haslabel && !cxt->parent)
+		fdisk_reset_device_properties(cxt);
+
+	DBG(CXT, ul_debugobj(cxt, "create a new %s label", lb->name));
+	return cxt->label->op->create(cxt);
+}
+
+/**
+ * fdisk_locate_disklabel:
+ * @cxt: context
+ * @n: N item
+ * @name: return item name
+ * @offset: return offset where is item
+ * @size: of the item
+ *
+ * Locate disklabel and returns info about @n item of the label. For example
+ * GPT is composed from two items, PMBR and GPT, n=0 return offset to PMBR and n=1
+ * return offset to GPT. For more details see 'D' expect fdisk command.
+ *
+ * Returns: 0 on succes, <0 on error, 1 no more items.
+ */
+int fdisk_locate_disklabel(struct fdisk_context *cxt, int n, const char **name,
+			   off_t *offset, size_t *size)
+{
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+	if (!cxt->label->op->locate)
+		return -ENOSYS;
+
+	DBG(CXT, ul_debugobj(cxt, "locating %d chunk of %s.", n, cxt->label->name));
+	return cxt->label->op->locate(cxt, n, name, offset, size);
+}
+
+
+/**
+ * fdisk_get_disklabel_id:
+ * @cxt: fdisk context
+ * @id: returns pointer to allocated string (MBR Id or GPT dirk UUID)
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_get_disklabel_id(struct fdisk_context *cxt, char **id)
+{
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+	if (!cxt->label->op->get_id)
+		return -ENOSYS;
+
+	DBG(CXT, ul_debugobj(cxt, "asking for disk %s ID", cxt->label->name));
+	return cxt->label->op->get_id(cxt, id);
+}
+
+/**
+ * fdisk_set_disklabel_id:
+ * @cxt: fdisk context
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_set_disklabel_id(struct fdisk_context *cxt)
+{
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+	if (!cxt->label->op->set_id)
+		return -ENOSYS;
+
+	DBG(CXT, ul_debugobj(cxt, "setting %s disk ID", cxt->label->name));
+	return cxt->label->op->set_id(cxt);
+}
+
+/**
+ * fdisk_set_partition_type:
+ * @cxt: fdisk context
+ * @partnum: partition number
+ * @t: new type
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_set_partition_type(struct fdisk_context *cxt,
+			     size_t partnum,
+			     struct fdisk_parttype *t)
+{
+	if (!cxt || !cxt->label || !t)
+		return -EINVAL;
+
+
+	if (cxt->label->op->set_part) {
+		struct fdisk_partition *pa = fdisk_new_partition();
+		int rc;
+
+		if (!pa)
+			return -ENOMEM;
+		fdisk_partition_set_type(pa, t);
+
+		DBG(CXT, ul_debugobj(cxt, "partition: %zd: set type", partnum));
+		rc = cxt->label->op->set_part(cxt, partnum, pa);
+		fdisk_unref_partition(pa);
+		return rc;
+	}
+
+	return -ENOSYS;
+}
+
+
+/**
+ * fdisk_toggle_partition_flag:
+ * @cxt: fdisk context
+ * @partnum: partition number
+ * @flag: flag ID
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_toggle_partition_flag(struct fdisk_context *cxt,
+			       size_t partnum,
+			       unsigned long flag)
+{
+	int rc;
+
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+	if (!cxt->label->op->part_toggle_flag)
+		return -ENOSYS;
+
+	rc = cxt->label->op->part_toggle_flag(cxt, partnum, flag);
+
+	DBG(CXT, ul_debugobj(cxt, "partition: %zd: toggle: 0x%04lx [rc=%d]", partnum, flag, rc));
+	return rc;
+}
+
+/**
+ * fdisk_reorder_partitions
+ * @cxt: fdisk context
+ *
+ * Sort partitions according to the partition start sector.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_reorder_partitions(struct fdisk_context *cxt)
+{
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+	if (!cxt->label->op->reorder)
+		return -ENOSYS;
+
+	return cxt->label->op->reorder(cxt);
+}
+
+/*
+ * Resets the current used label driver to initial state
+ */
+void fdisk_deinit_label(struct fdisk_label *lb)
+{
+	assert(lb);
+
+	/* private label information */
+	if (lb->op->deinit)
+		lb->op->deinit(lb);
+}
+
+/**
+ * fdisk_label_set_changed:
+ * @lb: label
+ * @changed: 0/1
+ *
+ * Marks in-memory data as changed, to force fdisk_write_disklabel() to write
+ * to device. This should be unnecessar by default, the library keeps track
+ * about changes.
+ */
+void fdisk_label_set_changed(struct fdisk_label *lb, int changed)
+{
+	assert(lb);
+	lb->changed = changed ? 1 : 0;
+}
+
+/**
+ * fdisk_label_is_changed:
+ * @lb: label
+ *
+ * Returns: 1 if in-memory data has been changed.
+ */
+int fdisk_label_is_changed(const struct fdisk_label *lb)
+{
+	assert(lb);
+	return lb ? lb->changed : 0;
+}
+
+/**
+ * fdisk_label_set_disabled:
+ * @lb: label
+ * @disabled: 0 or 1
+ *
+ * Mark label as disabled, then libfdisk is going to ignore the label when
+ * probe device for labels.
+ */
+void fdisk_label_set_disabled(struct fdisk_label *lb, int disabled)
+{
+	assert(lb);
+
+	DBG(LABEL, ul_debug("%s label %s",
+				lb->name,
+				disabled ? "DISABLED" : "ENABLED"));
+	lb->disabled = disabled ? 1 : 0;
+}
+
+/**
+ * fdisk_label_is_disabled:
+ * @lb: label
+ *
+ * Returns: 1 if label driver disabled.
+ */
+int fdisk_label_is_disabled(const struct fdisk_label *lb)
+{
+	assert(lb);
+	return lb ? lb->disabled : 0;
+}
diff --git a/libblkid/libfdisk/src/libfdisk.h b/libblkid/libfdisk/src/libfdisk.h
new file mode 100644
index 0000000..844e17e
--- /dev/null
+++ b/libblkid/libfdisk/src/libfdisk.h
@@ -0,0 +1,579 @@
+/*
+ * libfdisk.h - libfdisk API
+ *
+ * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _LIBFDISK_H
+#define _LIBFDISK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+/**
+ * LIBFDISK_VERSION:
+ *
+ * Library version string
+ */
+#define LIBFDISK_VERSION   "2.25.0"
+
+/**
+ * fdisk_context:
+ *
+ * Basic library handler.
+ */
+struct fdisk_context;
+
+/**
+ * fdisk_label:
+ *
+ * Disk label specific driver and setting.
+ */
+struct fdisk_label;
+
+/**
+ * fdisk_parttype:
+ *
+ * Partition type.
+ */
+struct fdisk_parttype;
+
+/**
+ * fdisk_partition:
+ *
+ * Partition abstraction (and template).
+ */
+struct fdisk_partition;
+
+/**
+ * fdisk_ask:
+ *
+ * Ask API handler for dialogs with users.
+ */
+struct fdisk_ask;
+
+/**
+ * fdisk_iter:
+ *
+ * Unified iterator.
+ */
+struct fdisk_iter;
+
+/**
+ * fdisk_table:
+ *
+ * Container for fdisk_partition objects
+ */
+struct fdisk_table;
+
+/**
+ * fdisk_field
+ *
+ * Output field description.
+ */
+struct fdisk_field;
+
+/**
+ * fdisk_script
+ *
+ * library handler for sfdisk compatible scripts
+ */
+struct fdisk_script;
+
+/**
+ * fdisk_sector_t
+ *
+ * LBA adresses type
+ */
+typedef uint64_t fdisk_sector_t;
+
+/**
+ * fdisk_labeltype:
+ *
+ * Supported partition table types (labels)
+ */
+enum fdisk_labeltype {
+	FDISK_DISKLABEL_DOS = (1 << 1),
+	FDISK_DISKLABEL_SUN = (1 << 2),
+	FDISK_DISKLABEL_SGI = (1 << 3),
+	FDISK_DISKLABEL_BSD = (1 << 4),
+	FDISK_DISKLABEL_GPT = (1 << 5)
+};
+
+/**
+ * fdisk_asktype:
+ *
+ * Ask API dialog types
+ */
+enum fdisk_asktype {
+	FDISK_ASKTYPE_NONE = 0,
+	FDISK_ASKTYPE_NUMBER,
+	FDISK_ASKTYPE_OFFSET,
+	FDISK_ASKTYPE_WARN,
+	FDISK_ASKTYPE_WARNX,
+	FDISK_ASKTYPE_INFO,
+	FDISK_ASKTYPE_YESNO,
+	FDISK_ASKTYPE_STRING,
+	FDISK_ASKTYPE_MENU
+};
+
+/* init.c */
+extern void fdisk_init_debug(int mask);
+
+/* context.h */
+
+#define FDISK_PLURAL	0
+#define FDISK_SINGULAR	1
+
+struct fdisk_context *fdisk_new_context(void);
+struct fdisk_context *fdisk_new_nested_context(struct fdisk_context *parent, const char *name);
+void fdisk_unref_context(struct fdisk_context *cxt);
+void fdisk_ref_context(struct fdisk_context *cxt);
+
+struct fdisk_context *fdisk_get_parent(struct fdisk_context *cxt);
+size_t fdisk_get_npartitions(struct fdisk_context *cxt);
+
+struct fdisk_label *fdisk_get_label(struct fdisk_context *cxt, const char *name);
+int fdisk_next_label(struct fdisk_context *cxt, struct fdisk_label **lb);
+size_t fdisk_get_nlabels(struct fdisk_context *cxt);
+
+int fdisk_has_label(struct fdisk_context *cxt);
+int fdisk_is_labeltype(struct fdisk_context *cxt, enum fdisk_labeltype id);
+#define fdisk_is_label(c, x) fdisk_is_labeltype(c, FDISK_DISKLABEL_ ## x)
+
+
+int fdisk_assign_device(struct fdisk_context *cxt,
+			const char *fname, int readonly);
+int fdisk_deassign_device(struct fdisk_context *cxt, int nosync);
+int fdisk_is_readonly(struct fdisk_context *cxt);
+
+int fdisk_enable_details(struct fdisk_context *cxt, int enable);
+int fdisk_is_details(struct fdisk_context *cxt);
+
+int fdisk_enable_listonly(struct fdisk_context *cxt, int enable);
+int fdisk_is_listonly(struct fdisk_context *cxt);
+
+int fdisk_set_unit(struct fdisk_context *cxt, const char *str);
+const char *fdisk_get_unit(struct fdisk_context *cxt, int n);
+int fdisk_use_cylinders(struct fdisk_context *cxt);
+unsigned int fdisk_get_units_per_sector(struct fdisk_context *cxt);
+
+unsigned long fdisk_get_optimal_iosize(struct fdisk_context *cxt);
+unsigned long fdisk_get_minimal_iosize(struct fdisk_context *cxt);
+unsigned long fdisk_get_physector_size(struct fdisk_context *cxt);
+unsigned long fdisk_get_sector_size(struct fdisk_context *cxt);
+unsigned long fdisk_get_alignment_offset(struct fdisk_context *cxt);
+unsigned long fdisk_get_grain_size(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_first_lba(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_set_first_lba(struct fdisk_context *cxt, fdisk_sector_t lba);
+fdisk_sector_t fdisk_get_last_lba(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_set_last_lba(struct fdisk_context *cxt, fdisk_sector_t lba);
+fdisk_sector_t fdisk_get_nsectors(struct fdisk_context *cxt);
+const char *fdisk_get_devname(struct fdisk_context *cxt);
+int fdisk_get_devfd(struct fdisk_context *cxt);
+
+unsigned int fdisk_get_geom_heads(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_geom_sectors(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_geom_cylinders(struct fdisk_context *cxt);
+
+
+
+/* parttype.c */
+struct fdisk_parttype *fdisk_new_parttype(void);
+void fdisk_ref_parttype(struct fdisk_parttype *t);
+void fdisk_unref_parttype(struct fdisk_parttype *t);
+int fdisk_parttype_set_name(struct fdisk_parttype *t, const char *str);
+int fdisk_parttype_set_typestr(struct fdisk_parttype *t, const char *str);
+int fdisk_parttype_set_code(struct fdisk_parttype *t, int code);
+size_t fdisk_label_get_nparttypes(const struct fdisk_label *lb);
+struct fdisk_parttype *fdisk_label_get_parttype(const struct fdisk_label *lb, size_t n);
+int fdisk_label_has_code_parttypes(const struct fdisk_label *lb);
+struct fdisk_parttype *fdisk_label_get_parttype_from_code(
+				const struct fdisk_label *lb,
+				unsigned int code);
+struct fdisk_parttype *fdisk_label_get_parttype_from_string(
+				const struct fdisk_label *lb,
+				const char *str);
+struct fdisk_parttype *fdisk_new_unknown_parttype(unsigned int code,
+						  const char *typestr);
+struct fdisk_parttype *fdisk_copy_parttype(const struct fdisk_parttype *type);
+struct fdisk_parttype *fdisk_label_parse_parttype(
+				const struct fdisk_label *lb,
+				const char *str);
+const char *fdisk_parttype_get_string(const struct fdisk_parttype *t);
+unsigned int fdisk_parttype_get_code(const struct fdisk_parttype *t);
+const char *fdisk_parttype_get_name(const struct fdisk_parttype *t);
+int fdisk_parttype_is_unknown(const struct fdisk_parttype *t);
+
+/* label.c */
+
+/**
+ * fdisk_fieldtype
+ *
+ * Types of fdisk_field
+ */
+enum fdisk_fieldtype {
+	FDISK_FIELD_NONE = 0,
+
+	/* generic */
+	FDISK_FIELD_DEVICE,
+	FDISK_FIELD_START,
+	FDISK_FIELD_END,
+	FDISK_FIELD_SECTORS,
+	FDISK_FIELD_CYLINDERS,
+	FDISK_FIELD_SIZE,
+	FDISK_FIELD_TYPE,
+	FDISK_FIELD_TYPEID,
+
+	/* label specific */
+	FDISK_FIELD_ATTR,
+	FDISK_FIELD_BOOT,
+	FDISK_FIELD_BSIZE,
+	FDISK_FIELD_CPG,
+	FDISK_FIELD_EADDR,
+	FDISK_FIELD_FSIZE,
+	FDISK_FIELD_NAME,
+	FDISK_FIELD_SADDR,
+	FDISK_FIELD_UUID,
+
+	FDISK_NFIELDS		/* must be last */
+};
+
+int fdisk_label_get_type(const struct fdisk_label *lb);
+const char *fdisk_label_get_name(const struct fdisk_label *lb);
+int fdisk_label_require_geometry(const struct fdisk_label *lb);
+
+
+extern int fdisk_write_disklabel(struct fdisk_context *cxt);
+extern int fdisk_verify_disklabel(struct fdisk_context *cxt);
+extern int fdisk_create_disklabel(struct fdisk_context *cxt, const char *name);
+extern int fdisk_list_disklabel(struct fdisk_context *cxt);
+extern int fdisk_locate_disklabel(struct fdisk_context *cxt, int n, const char **name, off_t *offset, size_t *size);
+
+extern int fdisk_get_disklabel_id(struct fdisk_context *cxt, char **id);
+extern int fdisk_set_disklabel_id(struct fdisk_context *cxt);
+
+extern int fdisk_get_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition **pa);
+extern int fdisk_set_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition *pa);
+extern int fdisk_add_partition(struct fdisk_context *cxt, struct fdisk_partition *pa, size_t *partno);
+extern int fdisk_delete_partition(struct fdisk_context *cxt, size_t partno);
+
+extern int fdisk_delete_all_partitions(struct fdisk_context *cxt);
+
+extern int fdisk_set_partition_type(struct fdisk_context *cxt, size_t partnum,
+			     struct fdisk_parttype *t);
+
+
+extern int fdisk_label_get_fields_ids(
+			const struct fdisk_label *lb,
+			struct fdisk_context *cxt,
+			int **ids, size_t *nids);
+
+extern const struct fdisk_field *fdisk_label_get_field(const struct fdisk_label *lb, int id);
+extern const struct fdisk_field *fdisk_label_get_field_by_name(
+			const struct fdisk_label *lb,
+			const char *name);
+
+extern int fdisk_field_get_id(const struct fdisk_field *field);
+extern const char *fdisk_field_get_name(const struct fdisk_field *field);
+extern double fdisk_field_get_width(const struct fdisk_field *field);
+extern int fdisk_field_is_number(const struct fdisk_field *field);
+
+
+extern void fdisk_label_set_changed(struct fdisk_label *lb, int changed);
+extern int fdisk_label_is_changed(const struct fdisk_label *lb);
+
+extern void fdisk_label_set_disabled(struct fdisk_label *lb, int disabled);
+extern int fdisk_label_is_disabled(const struct fdisk_label *lb);
+
+extern int fdisk_is_partition_used(struct fdisk_context *cxt, size_t n);
+
+extern int fdisk_toggle_partition_flag(struct fdisk_context *cxt, size_t partnum, unsigned long flag);
+
+extern struct fdisk_partition *fdisk_new_partition(void);
+extern void fdisk_reset_partition(struct fdisk_partition *pa);
+extern void fdisk_ref_partition(struct fdisk_partition *pa);
+extern void fdisk_unref_partition(struct fdisk_partition *pa);
+extern int fdisk_partition_is_freespace(struct fdisk_partition *pa);
+
+int fdisk_partition_set_start(struct fdisk_partition *pa, uint64_t off);
+int fdisk_partition_unset_start(struct fdisk_partition *pa);
+uint64_t fdisk_partition_get_start(struct fdisk_partition *pa);
+int fdisk_partition_has_start(struct fdisk_partition *pa);
+int fdisk_partition_cmp_start(struct fdisk_partition *a,
+			      struct fdisk_partition *b);
+int fdisk_partition_start_follow_default(struct fdisk_partition *pa, int enable);
+int fdisk_partition_start_is_default(struct fdisk_partition *pa);
+
+int fdisk_partition_set_size(struct fdisk_partition *pa, uint64_t sz);
+int fdisk_partition_unset_size(struct fdisk_partition *pa);
+uint64_t fdisk_partition_get_size(struct fdisk_partition *pa);
+int fdisk_partition_has_size(struct fdisk_partition *pa);
+int fdisk_partition_size_explicit(struct fdisk_partition *pa, int enable);
+
+int fdisk_partition_has_end(struct fdisk_partition *pa);
+fdisk_sector_t fdisk_partition_get_end(struct fdisk_partition *pa);
+
+int fdisk_partition_set_partno(struct fdisk_partition *pa, size_t num);
+int fdisk_partition_unset_partno(struct fdisk_partition *pa);
+size_t fdisk_partition_get_partno(struct fdisk_partition *pa);
+int fdisk_partition_has_partno(struct fdisk_partition *pa);
+int fdisk_partition_cmp_partno(struct fdisk_partition *a,
+	                               struct fdisk_partition *b);
+int fdisk_partition_partno_follow_default(struct fdisk_partition *pa, int enable);
+
+
+extern int fdisk_partition_set_type(struct fdisk_partition *pa, struct fdisk_parttype *type);
+extern struct fdisk_parttype *fdisk_partition_get_type(struct fdisk_partition *pa);
+extern int fdisk_partition_set_name(struct fdisk_partition *pa, const char *name);
+extern const char *fdisk_partition_get_name(struct fdisk_partition *pa);
+extern int fdisk_partition_set_uuid(struct fdisk_partition *pa, const char *uuid);
+extern int fdisk_partition_set_attrs(struct fdisk_partition *pa, const char *attrs);
+extern const char *fdisk_partition_get_uuid(struct fdisk_partition *pa);
+extern const char *fdisk_partition_get_attrs(struct fdisk_partition *pa);
+extern int fdisk_partition_is_nested(struct fdisk_partition *pa);
+extern int fdisk_partition_is_container(struct fdisk_partition *pa);
+extern int fdisk_partition_get_parent(struct fdisk_partition *pa, size_t *parent);
+extern int fdisk_partition_is_used(struct fdisk_partition *pa);
+extern int fdisk_partition_is_bootable(struct fdisk_partition *pa);
+extern int fdisk_partition_to_string(struct fdisk_partition *pa,
+				     struct fdisk_context *cxt,
+				     int id, char **data);
+
+int fdisk_partition_next_partno(struct fdisk_partition *pa,
+				       struct fdisk_context *cxt,
+				       size_t *n);
+
+extern int fdisk_partition_end_follow_default(struct fdisk_partition *pa, int enable);
+extern int fdisk_partition_end_is_default(struct fdisk_partition *pa);
+
+extern int fdisk_reorder_partitions(struct fdisk_context *cxt);
+
+/* table.c */
+extern struct fdisk_table *fdisk_new_table(void);
+extern int fdisk_reset_table(struct fdisk_table *tb);
+extern void fdisk_ref_table(struct fdisk_table *tb);
+extern void fdisk_unref_table(struct fdisk_table *tb);
+extern size_t fdisk_table_get_nents(struct fdisk_table *tb);
+extern int fdisk_table_is_empty(struct fdisk_table *tb);
+extern int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa);
+extern int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa);
+
+extern int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb);
+extern int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb);
+
+extern int fdisk_table_wrong_order(struct fdisk_table *tb);
+extern int fdisk_table_sort_partitions(struct fdisk_table *tb,
+			int (*cmp)(struct fdisk_partition *,
+				   struct fdisk_partition *));
+
+extern int fdisk_table_next_partition(
+			struct fdisk_table *tb,
+			struct fdisk_iter *itr,
+			struct fdisk_partition **pa);
+
+extern struct fdisk_partition *fdisk_table_get_partition(
+			struct fdisk_table *tb,
+			size_t n);
+extern int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb);
+
+/* alignment.c */
+#define FDISK_ALIGN_UP		1
+#define FDISK_ALIGN_DOWN	2
+#define FDISK_ALIGN_NEAREST	3
+
+fdisk_sector_t fdisk_align_lba(struct fdisk_context *cxt, fdisk_sector_t lba, int direction);
+fdisk_sector_t fdisk_align_lba_in_range(struct fdisk_context *cxt,
+				  fdisk_sector_t lba, fdisk_sector_t start, fdisk_sector_t stop);
+int fdisk_lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba);
+
+int fdisk_override_geometry(struct fdisk_context *cxt,
+			    unsigned int cylinders,
+			    unsigned int heads,
+			    unsigned int sectors);
+int fdisk_save_user_geometry(struct fdisk_context *cxt,
+			    unsigned int cylinders,
+			    unsigned int heads,
+			    unsigned int sectors);
+int fdisk_save_user_sector_size(struct fdisk_context *cxt,
+				unsigned int phy,
+				unsigned int log);
+int fdisk_has_user_device_properties(struct fdisk_context *cxt);
+int fdisk_reset_alignment(struct fdisk_context *cxt);
+int fdisk_reset_device_properties(struct fdisk_context *cxt);
+int fdisk_reread_partition_table(struct fdisk_context *cxt);
+
+/* iter.c */
+enum {
+
+	FDISK_ITER_FORWARD = 0,
+	FDISK_ITER_BACKWARD
+};
+extern struct fdisk_iter *fdisk_new_iter(int direction);
+extern void fdisk_free_iter(struct fdisk_iter *itr);
+extern void fdisk_reset_iter(struct fdisk_iter *itr, int direction);
+extern int fdisk_iter_get_direction(struct fdisk_iter *itr);
+
+
+/* dos.c */
+#define DOS_FLAG_ACTIVE	1
+
+extern int fdisk_dos_move_begin(struct fdisk_context *cxt, size_t i);
+extern int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable);
+extern int fdisk_dos_is_compatible(struct fdisk_label *lb);
+
+/* sun.h */
+extern int fdisk_sun_set_alt_cyl(struct fdisk_context *cxt);
+extern int fdisk_sun_set_xcyl(struct fdisk_context *cxt);
+extern int fdisk_sun_set_ilfact(struct fdisk_context *cxt);
+extern int fdisk_sun_set_rspeed(struct fdisk_context *cxt);
+extern int fdisk_sun_set_pcylcount(struct fdisk_context *cxt);
+
+/* bsd.c */
+extern int fdisk_bsd_edit_disklabel(struct fdisk_context *cxt);
+extern int fdisk_bsd_write_bootstrap(struct fdisk_context *cxt);
+extern int fdisk_bsd_link_partition(struct fdisk_context *cxt);
+
+/* sgi.h */
+#define SGI_FLAG_BOOT	1
+#define SGI_FLAG_SWAP	2
+extern int fdisk_sgi_set_bootfile(struct fdisk_context *cxt);
+extern int fdisk_sgi_create_info(struct fdisk_context *cxt);
+
+/* gpt */
+
+/* GPT partition attributes */
+enum {
+	/* System partition (disk partitioning utilities must preserve the
+	 * partition as is) */
+	GPT_FLAG_REQUIRED = 1,
+
+	/* EFI firmware should ignore the content of the partition and not try
+	 * to read from it */
+	GPT_FLAG_NOBLOCK,
+
+	/* Legacy BIOS bootable  */
+	GPT_FLAG_LEGACYBOOT,
+
+	/* bites 48-63, Defined and used by the individual partition type.
+	 *
+	 * The flag GPT_FLAG_GUIDSPECIFIC forces libfdisk to ask (by ask API)
+	 * for a bit number. If you want to toggle specific bit and avoid any
+	 * dialog, then use the bit number (in range 48..63). For example:
+	 *
+	 * // start dialog to ask for bit number
+	 * fdisk_toggle_partition_flag(cxt, n, GPT_FLAG_GUIDSPECIFIC);
+	 *
+	 * // toggle bit 60
+	 * fdisk_toggle_partition_flag(cxt, n, 60);
+	 */
+	GPT_FLAG_GUIDSPECIFIC
+};
+
+extern int fdisk_gpt_is_hybrid(struct fdisk_context *cxt);
+
+
+/* script.c */
+struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt);
+struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt,
+						 const char *filename);
+void fdisk_ref_script(struct fdisk_script *dp);
+void fdisk_unref_script(struct fdisk_script *dp);
+
+const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name);
+int fdisk_script_set_header(struct fdisk_script *dp, const char *name, const char *data);
+struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp);
+int fdisk_script_get_nlines(struct fdisk_script *dp);
+
+int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt);
+int fdisk_script_write_file(struct fdisk_script *dp, FILE *f);
+int fdisk_script_read_file(struct fdisk_script *dp, FILE *f);
+int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz);
+
+int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp);
+struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt);
+
+int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp);
+int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp);
+
+
+/* ask.c */
+#define fdisk_is_ask(a, x) (fdisk_ask_get_type(a) == FDISK_ASKTYPE_ ## x)
+
+int fdisk_set_ask(struct fdisk_context *cxt,
+		int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *),
+		void *data);
+
+
+void fdisk_ref_ask(struct fdisk_ask *ask);
+void fdisk_unref_ask(struct fdisk_ask *ask);
+const char *fdisk_ask_get_query(struct fdisk_ask *ask);
+int fdisk_ask_get_type(struct fdisk_ask *ask);
+const char *fdisk_ask_number_get_range(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_low(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_high(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_result(struct fdisk_ask *ask);
+int fdisk_ask_number_set_result(struct fdisk_ask *ask, uint64_t result);
+uint64_t fdisk_ask_number_get_base(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_unit(struct fdisk_ask *ask);
+int fdisk_ask_number_set_relative(struct fdisk_ask *ask, int relative);
+int fdisk_ask_number_inchars(struct fdisk_ask *ask);
+int fdisk_ask_partnum(struct fdisk_context *cxt, size_t *partnum, int wantnew);
+
+int fdisk_ask_number(struct fdisk_context *cxt,
+		     uintmax_t low,
+		     uintmax_t dflt,
+		     uintmax_t high,
+		     const char *query,
+		     uintmax_t *result);
+char *fdisk_ask_string_get_result(struct fdisk_ask *ask);
+int fdisk_ask_string_set_result(struct fdisk_ask *ask, char *result);
+int fdisk_ask_string(struct fdisk_context *cxt,
+		     const char *query,
+		     char **result);
+int fdisk_ask_yesno(struct fdisk_context *cxt,
+		     const char *query,
+		     int *result);
+int fdisk_ask_yesno_get_result(struct fdisk_ask *ask);
+int fdisk_ask_yesno_set_result(struct fdisk_ask *ask, int result);
+int fdisk_ask_menu_get_default(struct fdisk_ask *ask);
+int fdisk_ask_menu_set_result(struct fdisk_ask *ask, int key);
+int fdisk_ask_menu_get_result(struct fdisk_ask *ask, int *key);
+int fdisk_ask_menu_get_item(struct fdisk_ask *ask, size_t idx, int *key,
+			    const char **name, const char **desc);
+size_t fdisk_ask_menu_get_nitems(struct fdisk_ask *ask);
+int fdisk_ask_print_get_errno(struct fdisk_ask *ask);
+const char *fdisk_ask_print_get_mesg(struct fdisk_ask *ask);
+
+int fdisk_info(struct fdisk_context *cxt, const char *fmt, ...);
+int fdisk_warn(struct fdisk_context *cxt, const char *fmt, ...);
+int fdisk_warnx(struct fdisk_context *cxt, const char *fmt, ...);
+
+/* utils.h */
+extern char *fdisk_partname(const char *dev, size_t partno);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBFDISK_H */
diff --git a/libblkid/libfdisk/src/libfdisk.h.in b/libblkid/libfdisk/src/libfdisk.h.in
new file mode 100644
index 0000000..f82d5bd
--- /dev/null
+++ b/libblkid/libfdisk/src/libfdisk.h.in
@@ -0,0 +1,579 @@
+/*
+ * libfdisk.h - libfdisk API
+ *
+ * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _LIBFDISK_H
+#define _LIBFDISK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+/**
+ * LIBFDISK_VERSION:
+ *
+ * Library version string
+ */
+#define LIBFDISK_VERSION   "@LIBFDISK_VERSION@"
+
+/**
+ * fdisk_context:
+ *
+ * Basic library handler.
+ */
+struct fdisk_context;
+
+/**
+ * fdisk_label:
+ *
+ * Disk label specific driver and setting.
+ */
+struct fdisk_label;
+
+/**
+ * fdisk_parttype:
+ *
+ * Partition type.
+ */
+struct fdisk_parttype;
+
+/**
+ * fdisk_partition:
+ *
+ * Partition abstraction (and template).
+ */
+struct fdisk_partition;
+
+/**
+ * fdisk_ask:
+ *
+ * Ask API handler for dialogs with users.
+ */
+struct fdisk_ask;
+
+/**
+ * fdisk_iter:
+ *
+ * Unified iterator.
+ */
+struct fdisk_iter;
+
+/**
+ * fdisk_table:
+ *
+ * Container for fdisk_partition objects
+ */
+struct fdisk_table;
+
+/**
+ * fdisk_field
+ *
+ * Output field description.
+ */
+struct fdisk_field;
+
+/**
+ * fdisk_script
+ *
+ * library handler for sfdisk compatible scripts
+ */
+struct fdisk_script;
+
+/**
+ * fdisk_sector_t
+ *
+ * LBA adresses type
+ */
+typedef uint64_t fdisk_sector_t;
+
+/**
+ * fdisk_labeltype:
+ *
+ * Supported partition table types (labels)
+ */
+enum fdisk_labeltype {
+	FDISK_DISKLABEL_DOS = (1 << 1),
+	FDISK_DISKLABEL_SUN = (1 << 2),
+	FDISK_DISKLABEL_SGI = (1 << 3),
+	FDISK_DISKLABEL_BSD = (1 << 4),
+	FDISK_DISKLABEL_GPT = (1 << 5)
+};
+
+/**
+ * fdisk_asktype:
+ *
+ * Ask API dialog types
+ */
+enum fdisk_asktype {
+	FDISK_ASKTYPE_NONE = 0,
+	FDISK_ASKTYPE_NUMBER,
+	FDISK_ASKTYPE_OFFSET,
+	FDISK_ASKTYPE_WARN,
+	FDISK_ASKTYPE_WARNX,
+	FDISK_ASKTYPE_INFO,
+	FDISK_ASKTYPE_YESNO,
+	FDISK_ASKTYPE_STRING,
+	FDISK_ASKTYPE_MENU
+};
+
+/* init.c */
+extern void fdisk_init_debug(int mask);
+
+/* context.h */
+
+#define FDISK_PLURAL	0
+#define FDISK_SINGULAR	1
+
+struct fdisk_context *fdisk_new_context(void);
+struct fdisk_context *fdisk_new_nested_context(struct fdisk_context *parent, const char *name);
+void fdisk_unref_context(struct fdisk_context *cxt);
+void fdisk_ref_context(struct fdisk_context *cxt);
+
+struct fdisk_context *fdisk_get_parent(struct fdisk_context *cxt);
+size_t fdisk_get_npartitions(struct fdisk_context *cxt);
+
+struct fdisk_label *fdisk_get_label(struct fdisk_context *cxt, const char *name);
+int fdisk_next_label(struct fdisk_context *cxt, struct fdisk_label **lb);
+size_t fdisk_get_nlabels(struct fdisk_context *cxt);
+
+int fdisk_has_label(struct fdisk_context *cxt);
+int fdisk_is_labeltype(struct fdisk_context *cxt, enum fdisk_labeltype id);
+#define fdisk_is_label(c, x) fdisk_is_labeltype(c, FDISK_DISKLABEL_ ## x)
+
+
+int fdisk_assign_device(struct fdisk_context *cxt,
+			const char *fname, int readonly);
+int fdisk_deassign_device(struct fdisk_context *cxt, int nosync);
+int fdisk_is_readonly(struct fdisk_context *cxt);
+
+int fdisk_enable_details(struct fdisk_context *cxt, int enable);
+int fdisk_is_details(struct fdisk_context *cxt);
+
+int fdisk_enable_listonly(struct fdisk_context *cxt, int enable);
+int fdisk_is_listonly(struct fdisk_context *cxt);
+
+int fdisk_set_unit(struct fdisk_context *cxt, const char *str);
+const char *fdisk_get_unit(struct fdisk_context *cxt, int n);
+int fdisk_use_cylinders(struct fdisk_context *cxt);
+unsigned int fdisk_get_units_per_sector(struct fdisk_context *cxt);
+
+unsigned long fdisk_get_optimal_iosize(struct fdisk_context *cxt);
+unsigned long fdisk_get_minimal_iosize(struct fdisk_context *cxt);
+unsigned long fdisk_get_physector_size(struct fdisk_context *cxt);
+unsigned long fdisk_get_sector_size(struct fdisk_context *cxt);
+unsigned long fdisk_get_alignment_offset(struct fdisk_context *cxt);
+unsigned long fdisk_get_grain_size(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_first_lba(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_set_first_lba(struct fdisk_context *cxt, fdisk_sector_t lba);
+fdisk_sector_t fdisk_get_last_lba(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_set_last_lba(struct fdisk_context *cxt, fdisk_sector_t lba);
+fdisk_sector_t fdisk_get_nsectors(struct fdisk_context *cxt);
+const char *fdisk_get_devname(struct fdisk_context *cxt);
+int fdisk_get_devfd(struct fdisk_context *cxt);
+
+unsigned int fdisk_get_geom_heads(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_geom_sectors(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_geom_cylinders(struct fdisk_context *cxt);
+
+
+
+/* parttype.c */
+struct fdisk_parttype *fdisk_new_parttype(void);
+void fdisk_ref_parttype(struct fdisk_parttype *t);
+void fdisk_unref_parttype(struct fdisk_parttype *t);
+int fdisk_parttype_set_name(struct fdisk_parttype *t, const char *str);
+int fdisk_parttype_set_typestr(struct fdisk_parttype *t, const char *str);
+int fdisk_parttype_set_code(struct fdisk_parttype *t, int code);
+size_t fdisk_label_get_nparttypes(const struct fdisk_label *lb);
+struct fdisk_parttype *fdisk_label_get_parttype(const struct fdisk_label *lb, size_t n);
+int fdisk_label_has_code_parttypes(const struct fdisk_label *lb);
+struct fdisk_parttype *fdisk_label_get_parttype_from_code(
+				const struct fdisk_label *lb,
+				unsigned int code);
+struct fdisk_parttype *fdisk_label_get_parttype_from_string(
+				const struct fdisk_label *lb,
+				const char *str);
+struct fdisk_parttype *fdisk_new_unknown_parttype(unsigned int code,
+						  const char *typestr);
+struct fdisk_parttype *fdisk_copy_parttype(const struct fdisk_parttype *type);
+struct fdisk_parttype *fdisk_label_parse_parttype(
+				const struct fdisk_label *lb,
+				const char *str);
+const char *fdisk_parttype_get_string(const struct fdisk_parttype *t);
+unsigned int fdisk_parttype_get_code(const struct fdisk_parttype *t);
+const char *fdisk_parttype_get_name(const struct fdisk_parttype *t);
+int fdisk_parttype_is_unknown(const struct fdisk_parttype *t);
+
+/* label.c */
+
+/**
+ * fdisk_fieldtype
+ *
+ * Types of fdisk_field
+ */
+enum fdisk_fieldtype {
+	FDISK_FIELD_NONE = 0,
+
+	/* generic */
+	FDISK_FIELD_DEVICE,
+	FDISK_FIELD_START,
+	FDISK_FIELD_END,
+	FDISK_FIELD_SECTORS,
+	FDISK_FIELD_CYLINDERS,
+	FDISK_FIELD_SIZE,
+	FDISK_FIELD_TYPE,
+	FDISK_FIELD_TYPEID,
+
+	/* label specific */
+	FDISK_FIELD_ATTR,
+	FDISK_FIELD_BOOT,
+	FDISK_FIELD_BSIZE,
+	FDISK_FIELD_CPG,
+	FDISK_FIELD_EADDR,
+	FDISK_FIELD_FSIZE,
+	FDISK_FIELD_NAME,
+	FDISK_FIELD_SADDR,
+	FDISK_FIELD_UUID,
+
+	FDISK_NFIELDS		/* must be last */
+};
+
+int fdisk_label_get_type(const struct fdisk_label *lb);
+const char *fdisk_label_get_name(const struct fdisk_label *lb);
+int fdisk_label_require_geometry(const struct fdisk_label *lb);
+
+
+extern int fdisk_write_disklabel(struct fdisk_context *cxt);
+extern int fdisk_verify_disklabel(struct fdisk_context *cxt);
+extern int fdisk_create_disklabel(struct fdisk_context *cxt, const char *name);
+extern int fdisk_list_disklabel(struct fdisk_context *cxt);
+extern int fdisk_locate_disklabel(struct fdisk_context *cxt, int n, const char **name, off_t *offset, size_t *size);
+
+extern int fdisk_get_disklabel_id(struct fdisk_context *cxt, char **id);
+extern int fdisk_set_disklabel_id(struct fdisk_context *cxt);
+
+extern int fdisk_get_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition **pa);
+extern int fdisk_set_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition *pa);
+extern int fdisk_add_partition(struct fdisk_context *cxt, struct fdisk_partition *pa, size_t *partno);
+extern int fdisk_delete_partition(struct fdisk_context *cxt, size_t partno);
+
+extern int fdisk_delete_all_partitions(struct fdisk_context *cxt);
+
+extern int fdisk_set_partition_type(struct fdisk_context *cxt, size_t partnum,
+			     struct fdisk_parttype *t);
+
+
+extern int fdisk_label_get_fields_ids(
+			const struct fdisk_label *lb,
+			struct fdisk_context *cxt,
+			int **ids, size_t *nids);
+
+extern const struct fdisk_field *fdisk_label_get_field(const struct fdisk_label *lb, int id);
+extern const struct fdisk_field *fdisk_label_get_field_by_name(
+			const struct fdisk_label *lb,
+			const char *name);
+
+extern int fdisk_field_get_id(const struct fdisk_field *field);
+extern const char *fdisk_field_get_name(const struct fdisk_field *field);
+extern double fdisk_field_get_width(const struct fdisk_field *field);
+extern int fdisk_field_is_number(const struct fdisk_field *field);
+
+
+extern void fdisk_label_set_changed(struct fdisk_label *lb, int changed);
+extern int fdisk_label_is_changed(const struct fdisk_label *lb);
+
+extern void fdisk_label_set_disabled(struct fdisk_label *lb, int disabled);
+extern int fdisk_label_is_disabled(const struct fdisk_label *lb);
+
+extern int fdisk_is_partition_used(struct fdisk_context *cxt, size_t n);
+
+extern int fdisk_toggle_partition_flag(struct fdisk_context *cxt, size_t partnum, unsigned long flag);
+
+extern struct fdisk_partition *fdisk_new_partition(void);
+extern void fdisk_reset_partition(struct fdisk_partition *pa);
+extern void fdisk_ref_partition(struct fdisk_partition *pa);
+extern void fdisk_unref_partition(struct fdisk_partition *pa);
+extern int fdisk_partition_is_freespace(struct fdisk_partition *pa);
+
+int fdisk_partition_set_start(struct fdisk_partition *pa, uint64_t off);
+int fdisk_partition_unset_start(struct fdisk_partition *pa);
+uint64_t fdisk_partition_get_start(struct fdisk_partition *pa);
+int fdisk_partition_has_start(struct fdisk_partition *pa);
+int fdisk_partition_cmp_start(struct fdisk_partition *a,
+			      struct fdisk_partition *b);
+int fdisk_partition_start_follow_default(struct fdisk_partition *pa, int enable);
+int fdisk_partition_start_is_default(struct fdisk_partition *pa);
+
+int fdisk_partition_set_size(struct fdisk_partition *pa, uint64_t sz);
+int fdisk_partition_unset_size(struct fdisk_partition *pa);
+uint64_t fdisk_partition_get_size(struct fdisk_partition *pa);
+int fdisk_partition_has_size(struct fdisk_partition *pa);
+int fdisk_partition_size_explicit(struct fdisk_partition *pa, int enable);
+
+int fdisk_partition_has_end(struct fdisk_partition *pa);
+fdisk_sector_t fdisk_partition_get_end(struct fdisk_partition *pa);
+
+int fdisk_partition_set_partno(struct fdisk_partition *pa, size_t num);
+int fdisk_partition_unset_partno(struct fdisk_partition *pa);
+size_t fdisk_partition_get_partno(struct fdisk_partition *pa);
+int fdisk_partition_has_partno(struct fdisk_partition *pa);
+int fdisk_partition_cmp_partno(struct fdisk_partition *a,
+	                               struct fdisk_partition *b);
+int fdisk_partition_partno_follow_default(struct fdisk_partition *pa, int enable);
+
+
+extern int fdisk_partition_set_type(struct fdisk_partition *pa, struct fdisk_parttype *type);
+extern struct fdisk_parttype *fdisk_partition_get_type(struct fdisk_partition *pa);
+extern int fdisk_partition_set_name(struct fdisk_partition *pa, const char *name);
+extern const char *fdisk_partition_get_name(struct fdisk_partition *pa);
+extern int fdisk_partition_set_uuid(struct fdisk_partition *pa, const char *uuid);
+extern int fdisk_partition_set_attrs(struct fdisk_partition *pa, const char *attrs);
+extern const char *fdisk_partition_get_uuid(struct fdisk_partition *pa);
+extern const char *fdisk_partition_get_attrs(struct fdisk_partition *pa);
+extern int fdisk_partition_is_nested(struct fdisk_partition *pa);
+extern int fdisk_partition_is_container(struct fdisk_partition *pa);
+extern int fdisk_partition_get_parent(struct fdisk_partition *pa, size_t *parent);
+extern int fdisk_partition_is_used(struct fdisk_partition *pa);
+extern int fdisk_partition_is_bootable(struct fdisk_partition *pa);
+extern int fdisk_partition_to_string(struct fdisk_partition *pa,
+				     struct fdisk_context *cxt,
+				     int id, char **data);
+
+int fdisk_partition_next_partno(struct fdisk_partition *pa,
+				       struct fdisk_context *cxt,
+				       size_t *n);
+
+extern int fdisk_partition_end_follow_default(struct fdisk_partition *pa, int enable);
+extern int fdisk_partition_end_is_default(struct fdisk_partition *pa);
+
+extern int fdisk_reorder_partitions(struct fdisk_context *cxt);
+
+/* table.c */
+extern struct fdisk_table *fdisk_new_table(void);
+extern int fdisk_reset_table(struct fdisk_table *tb);
+extern void fdisk_ref_table(struct fdisk_table *tb);
+extern void fdisk_unref_table(struct fdisk_table *tb);
+extern size_t fdisk_table_get_nents(struct fdisk_table *tb);
+extern int fdisk_table_is_empty(struct fdisk_table *tb);
+extern int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa);
+extern int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa);
+
+extern int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb);
+extern int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb);
+
+extern int fdisk_table_wrong_order(struct fdisk_table *tb);
+extern int fdisk_table_sort_partitions(struct fdisk_table *tb,
+			int (*cmp)(struct fdisk_partition *,
+				   struct fdisk_partition *));
+
+extern int fdisk_table_next_partition(
+			struct fdisk_table *tb,
+			struct fdisk_iter *itr,
+			struct fdisk_partition **pa);
+
+extern struct fdisk_partition *fdisk_table_get_partition(
+			struct fdisk_table *tb,
+			size_t n);
+extern int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb);
+
+/* alignment.c */
+#define FDISK_ALIGN_UP		1
+#define FDISK_ALIGN_DOWN	2
+#define FDISK_ALIGN_NEAREST	3
+
+fdisk_sector_t fdisk_align_lba(struct fdisk_context *cxt, fdisk_sector_t lba, int direction);
+fdisk_sector_t fdisk_align_lba_in_range(struct fdisk_context *cxt,
+				  fdisk_sector_t lba, fdisk_sector_t start, fdisk_sector_t stop);
+int fdisk_lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba);
+
+int fdisk_override_geometry(struct fdisk_context *cxt,
+			    unsigned int cylinders,
+			    unsigned int heads,
+			    unsigned int sectors);
+int fdisk_save_user_geometry(struct fdisk_context *cxt,
+			    unsigned int cylinders,
+			    unsigned int heads,
+			    unsigned int sectors);
+int fdisk_save_user_sector_size(struct fdisk_context *cxt,
+				unsigned int phy,
+				unsigned int log);
+int fdisk_has_user_device_properties(struct fdisk_context *cxt);
+int fdisk_reset_alignment(struct fdisk_context *cxt);
+int fdisk_reset_device_properties(struct fdisk_context *cxt);
+int fdisk_reread_partition_table(struct fdisk_context *cxt);
+
+/* iter.c */
+enum {
+
+	FDISK_ITER_FORWARD = 0,
+	FDISK_ITER_BACKWARD
+};
+extern struct fdisk_iter *fdisk_new_iter(int direction);
+extern void fdisk_free_iter(struct fdisk_iter *itr);
+extern void fdisk_reset_iter(struct fdisk_iter *itr, int direction);
+extern int fdisk_iter_get_direction(struct fdisk_iter *itr);
+
+
+/* dos.c */
+#define DOS_FLAG_ACTIVE	1
+
+extern int fdisk_dos_move_begin(struct fdisk_context *cxt, size_t i);
+extern int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable);
+extern int fdisk_dos_is_compatible(struct fdisk_label *lb);
+
+/* sun.h */
+extern int fdisk_sun_set_alt_cyl(struct fdisk_context *cxt);
+extern int fdisk_sun_set_xcyl(struct fdisk_context *cxt);
+extern int fdisk_sun_set_ilfact(struct fdisk_context *cxt);
+extern int fdisk_sun_set_rspeed(struct fdisk_context *cxt);
+extern int fdisk_sun_set_pcylcount(struct fdisk_context *cxt);
+
+/* bsd.c */
+extern int fdisk_bsd_edit_disklabel(struct fdisk_context *cxt);
+extern int fdisk_bsd_write_bootstrap(struct fdisk_context *cxt);
+extern int fdisk_bsd_link_partition(struct fdisk_context *cxt);
+
+/* sgi.h */
+#define SGI_FLAG_BOOT	1
+#define SGI_FLAG_SWAP	2
+extern int fdisk_sgi_set_bootfile(struct fdisk_context *cxt);
+extern int fdisk_sgi_create_info(struct fdisk_context *cxt);
+
+/* gpt */
+
+/* GPT partition attributes */
+enum {
+	/* System partition (disk partitioning utilities must preserve the
+	 * partition as is) */
+	GPT_FLAG_REQUIRED = 1,
+
+	/* EFI firmware should ignore the content of the partition and not try
+	 * to read from it */
+	GPT_FLAG_NOBLOCK,
+
+	/* Legacy BIOS bootable  */
+	GPT_FLAG_LEGACYBOOT,
+
+	/* bites 48-63, Defined and used by the individual partition type.
+	 *
+	 * The flag GPT_FLAG_GUIDSPECIFIC forces libfdisk to ask (by ask API)
+	 * for a bit number. If you want to toggle specific bit and avoid any
+	 * dialog, then use the bit number (in range 48..63). For example:
+	 *
+	 * // start dialog to ask for bit number
+	 * fdisk_toggle_partition_flag(cxt, n, GPT_FLAG_GUIDSPECIFIC);
+	 *
+	 * // toggle bit 60
+	 * fdisk_toggle_partition_flag(cxt, n, 60);
+	 */
+	GPT_FLAG_GUIDSPECIFIC
+};
+
+extern int fdisk_gpt_is_hybrid(struct fdisk_context *cxt);
+
+
+/* script.c */
+struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt);
+struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt,
+						 const char *filename);
+void fdisk_ref_script(struct fdisk_script *dp);
+void fdisk_unref_script(struct fdisk_script *dp);
+
+const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name);
+int fdisk_script_set_header(struct fdisk_script *dp, const char *name, const char *data);
+struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp);
+int fdisk_script_get_nlines(struct fdisk_script *dp);
+
+int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt);
+int fdisk_script_write_file(struct fdisk_script *dp, FILE *f);
+int fdisk_script_read_file(struct fdisk_script *dp, FILE *f);
+int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz);
+
+int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp);
+struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt);
+
+int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp);
+int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp);
+
+
+/* ask.c */
+#define fdisk_is_ask(a, x) (fdisk_ask_get_type(a) == FDISK_ASKTYPE_ ## x)
+
+int fdisk_set_ask(struct fdisk_context *cxt,
+		int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *),
+		void *data);
+
+
+void fdisk_ref_ask(struct fdisk_ask *ask);
+void fdisk_unref_ask(struct fdisk_ask *ask);
+const char *fdisk_ask_get_query(struct fdisk_ask *ask);
+int fdisk_ask_get_type(struct fdisk_ask *ask);
+const char *fdisk_ask_number_get_range(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_low(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_high(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_result(struct fdisk_ask *ask);
+int fdisk_ask_number_set_result(struct fdisk_ask *ask, uint64_t result);
+uint64_t fdisk_ask_number_get_base(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_unit(struct fdisk_ask *ask);
+int fdisk_ask_number_set_relative(struct fdisk_ask *ask, int relative);
+int fdisk_ask_number_inchars(struct fdisk_ask *ask);
+int fdisk_ask_partnum(struct fdisk_context *cxt, size_t *partnum, int wantnew);
+
+int fdisk_ask_number(struct fdisk_context *cxt,
+		     uintmax_t low,
+		     uintmax_t dflt,
+		     uintmax_t high,
+		     const char *query,
+		     uintmax_t *result);
+char *fdisk_ask_string_get_result(struct fdisk_ask *ask);
+int fdisk_ask_string_set_result(struct fdisk_ask *ask, char *result);
+int fdisk_ask_string(struct fdisk_context *cxt,
+		     const char *query,
+		     char **result);
+int fdisk_ask_yesno(struct fdisk_context *cxt,
+		     const char *query,
+		     int *result);
+int fdisk_ask_yesno_get_result(struct fdisk_ask *ask);
+int fdisk_ask_yesno_set_result(struct fdisk_ask *ask, int result);
+int fdisk_ask_menu_get_default(struct fdisk_ask *ask);
+int fdisk_ask_menu_set_result(struct fdisk_ask *ask, int key);
+int fdisk_ask_menu_get_result(struct fdisk_ask *ask, int *key);
+int fdisk_ask_menu_get_item(struct fdisk_ask *ask, size_t idx, int *key,
+			    const char **name, const char **desc);
+size_t fdisk_ask_menu_get_nitems(struct fdisk_ask *ask);
+int fdisk_ask_print_get_errno(struct fdisk_ask *ask);
+const char *fdisk_ask_print_get_mesg(struct fdisk_ask *ask);
+
+int fdisk_info(struct fdisk_context *cxt, const char *fmt, ...);
+int fdisk_warn(struct fdisk_context *cxt, const char *fmt, ...);
+int fdisk_warnx(struct fdisk_context *cxt, const char *fmt, ...);
+
+/* utils.h */
+extern char *fdisk_partname(const char *dev, size_t partno);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBFDISK_H */
diff --git a/libblkid/libfdisk/src/libfdisk.sym b/libblkid/libfdisk/src/libfdisk.sym
new file mode 100644
index 0000000..bf85d4e
--- /dev/null
+++ b/libblkid/libfdisk/src/libfdisk.sym
@@ -0,0 +1,234 @@
+/*
+ * The symbol versioning ensures that a new application requiring symbol foo;
+ * can't run with old libblkid.so not providing foo;
+ * version info can't enforce this since we never change the SONAME.
+ *
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ */
+MOUNT_2.26 {
+global:
+	fdisk_add_partition;
+	fdisk_align_lba;
+	fdisk_align_lba_in_range;
+	fdisk_apply_script;
+	fdisk_apply_script_headers;
+	fdisk_apply_table;
+	fdisk_ask_get_query;
+	fdisk_ask_get_type;
+	fdisk_ask_menu_get_default;
+	fdisk_ask_menu_get_item;
+	fdisk_ask_menu_get_nitems;
+	fdisk_ask_menu_get_result;
+	fdisk_ask_menu_set_result;
+	fdisk_ask_number;
+	fdisk_ask_number_get_base;
+	fdisk_ask_number_get_default;
+	fdisk_ask_number_get_high;
+	fdisk_ask_number_get_low;
+	fdisk_ask_number_get_range;
+	fdisk_ask_number_get_result;
+	fdisk_ask_number_get_unit;
+	fdisk_ask_number_inchars;
+	fdisk_ask_number_set_relative;
+	fdisk_ask_number_set_result;
+	fdisk_ask_partnum;
+	fdisk_ask_print_get_errno;
+	fdisk_ask_print_get_mesg;
+	fdisk_ask_string;
+	fdisk_ask_string_get_result;
+	fdisk_ask_string_set_result;
+	fdisk_ask_yesno;
+	fdisk_ask_yesno_get_result;
+	fdisk_ask_yesno_set_result;
+	fdisk_assign_device;
+	fdisk_bsd_edit_disklabel;
+	fdisk_bsd_link_partition;
+	fdisk_bsd_write_bootstrap;
+	fdisk_copy_parttype;
+	fdisk_create_disklabel;
+	fdisk_deassign_device;
+	fdisk_delete_all_partitions;
+	fdisk_delete_partition;
+	fdisk_dos_enable_compatible;
+	fdisk_dos_is_compatible;
+	fdisk_dos_move_begin;
+	fdisk_enable_details;
+	fdisk_enable_listonly;
+	fdisk_field_get_id;
+	fdisk_field_get_name;
+	fdisk_field_get_width;
+	fdisk_field_is_number;
+	fdisk_free_iter;
+	fdisk_get_alignment_offset;
+	fdisk_get_devfd;
+	fdisk_get_devname;
+	fdisk_get_disklabel_id;
+	fdisk_get_first_lba;
+	fdisk_get_freespaces;
+	fdisk_get_geom_cylinders;
+	fdisk_get_geom_heads;
+	fdisk_get_geom_sectors;
+	fdisk_get_grain_size;
+	fdisk_get_label;
+	fdisk_get_last_lba;
+	fdisk_get_minimal_iosize;
+	fdisk_get_nlabels;
+	fdisk_get_npartitions;
+	fdisk_get_nsectors;
+	fdisk_get_optimal_iosize;
+	fdisk_get_parent;
+	fdisk_get_partition;
+	fdisk_get_partitions;
+	fdisk_get_physector_size;
+	fdisk_get_script;
+	fdisk_get_sector_size;
+	fdisk_get_unit;
+	fdisk_get_units_per_sector;
+	fdisk_gpt_is_hybrid;
+	fdisk_has_label;
+	fdisk_has_user_device_properties;
+	fdisk_info;
+	fdisk_init_debug;
+	fdisk_is_details;
+	fdisk_is_labeltype;
+	fdisk_is_listonly;
+	fdisk_is_partition_used;
+	fdisk_is_readonly;
+	fdisk_iter_get_direction;
+	fdisk_label_get_field;
+	fdisk_label_get_field_by_name;
+	fdisk_label_get_fields_ids;
+	fdisk_label_get_name;
+	fdisk_label_get_nparttypes;
+	fdisk_label_get_parttype;
+	fdisk_label_get_parttype_from_code;
+	fdisk_label_get_parttype_from_string;
+	fdisk_label_get_type;
+	fdisk_label_has_code_parttypes;
+	fdisk_label_is_changed;
+	fdisk_label_is_disabled;
+	fdisk_label_parse_parttype;
+	fdisk_label_require_geometry;
+	fdisk_label_set_changed;
+	fdisk_label_set_disabled;
+	fdisk_lba_is_phy_aligned;
+	fdisk_list_disklabel;
+	fdisk_locate_disklabel;
+	fdisk_new_context;
+	fdisk_new_iter;
+	fdisk_new_nested_context;
+	fdisk_new_partition;
+	fdisk_new_parttype;
+	fdisk_new_script;
+	fdisk_new_script_from_file;
+	fdisk_new_table;
+	fdisk_new_unknown_parttype;
+	fdisk_next_label;
+	fdisk_override_geometry;
+	fdisk_partition_cmp_partno;
+	fdisk_partition_cmp_start;
+	fdisk_partition_end_follow_default;
+	fdisk_partition_end_is_default;
+	fdisk_partition_get_attrs;
+	fdisk_partition_get_end;
+	fdisk_partition_get_name;
+	fdisk_partition_get_parent;
+	fdisk_partition_get_partno;
+	fdisk_partition_get_size;
+	fdisk_partition_get_start;
+	fdisk_partition_get_type;
+	fdisk_partition_get_uuid;
+	fdisk_partition_has_end;
+	fdisk_partition_has_partno;
+	fdisk_partition_has_size;
+	fdisk_partition_has_start;
+	fdisk_partition_is_bootable;
+	fdisk_partition_is_container;
+	fdisk_partition_is_freespace;
+	fdisk_partition_is_nested;
+	fdisk_partition_is_used;
+	fdisk_partition_next_partno;
+	fdisk_partition_partno_follow_default;
+	fdisk_partition_set_attrs;
+	fdisk_partition_set_name;
+	fdisk_partition_set_partno;
+	fdisk_partition_set_size;
+	fdisk_partition_set_start;
+	fdisk_partition_set_type;
+	fdisk_partition_set_uuid;
+	fdisk_partition_size_explicit;
+	fdisk_partition_start_follow_default;
+	fdisk_partition_start_is_default;
+	fdisk_toggle_partition_flag;
+	fdisk_partition_to_string;
+	fdisk_partition_unset_partno;
+	fdisk_partition_unset_size;
+	fdisk_partition_unset_start;
+	fdisk_partname;
+	fdisk_parttype_get_code;
+	fdisk_parttype_get_name;
+	fdisk_parttype_get_string;
+	fdisk_parttype_is_unknown;
+	fdisk_parttype_set_code;
+	fdisk_parttype_set_name;
+	fdisk_parttype_set_typestr;
+	fdisk_ref_ask;
+	fdisk_ref_context;
+	fdisk_ref_partition;
+	fdisk_ref_parttype;
+	fdisk_ref_script;
+	fdisk_ref_table;
+	fdisk_reorder_partitions;
+	fdisk_reread_partition_table;
+	fdisk_reset_alignment;
+	fdisk_reset_device_properties;
+	fdisk_reset_iter;
+	fdisk_reset_partition;
+	fdisk_reset_table;
+	fdisk_save_user_geometry;
+	fdisk_save_user_sector_size;
+	fdisk_script_get_header;
+	fdisk_script_get_nlines;
+	fdisk_script_get_table;
+	fdisk_script_read_context;
+	fdisk_script_read_file;
+	fdisk_script_read_line;
+	fdisk_script_set_header;
+	fdisk_script_write_file;
+	fdisk_set_ask;
+	fdisk_set_disklabel_id;
+	fdisk_set_first_lba;
+	fdisk_set_last_lba;
+	fdisk_set_partition;
+	fdisk_set_partition_type;
+	fdisk_set_script;
+	fdisk_set_unit;
+	fdisk_sgi_create_info;
+	fdisk_sgi_set_bootfile;
+	fdisk_sun_set_alt_cyl;
+	fdisk_sun_set_ilfact;
+	fdisk_sun_set_pcylcount;
+	fdisk_sun_set_rspeed;
+	fdisk_sun_set_xcyl;
+	fdisk_table_add_partition;
+	fdisk_table_get_nents;
+	fdisk_table_get_partition;
+	fdisk_table_is_empty;
+	fdisk_table_next_partition;
+	fdisk_table_remove_partition;
+	fdisk_table_sort_partitions;
+	fdisk_table_wrong_order;
+	fdisk_unref_ask;
+	fdisk_unref_context;
+	fdisk_unref_partition;
+	fdisk_unref_parttype;
+	fdisk_unref_script;
+	fdisk_unref_table;
+	fdisk_use_cylinders;
+	fdisk_verify_disklabel;
+	fdisk_warn;
+	fdisk_warnx;
+	fdisk_write_disklabel;
+local:
+	*;
+};
diff --git a/libblkid/libfdisk/src/partition.c b/libblkid/libfdisk/src/partition.c
new file mode 100644
index 0000000..8f84027
--- /dev/null
+++ b/libblkid/libfdisk/src/partition.c
@@ -0,0 +1,963 @@
+
+#include "c.h"
+#include "strutils.h"
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: partition
+ * @title: Partition
+ * @short_description: generic label independent partition abstraction
+ *
+ * The fdisk_partition provides label independent abstraction. The partitions
+ * are not directly connected with partition table (label) data. Any change to
+ * fdisk_partition does not affects in-memory or on-disk label data.
+ *
+ * The fdisk_partition is possible to use as a template for
+ * fdisk_add_partition() or fdisk_set_partition() operations.
+ */
+
+static void init_partition(struct fdisk_partition *pa)
+{
+	FDISK_INIT_UNDEF(pa->size);
+	FDISK_INIT_UNDEF(pa->start);
+	FDISK_INIT_UNDEF(pa->partno);
+	FDISK_INIT_UNDEF(pa->parent_partno);
+	FDISK_INIT_UNDEF(pa->boot);
+
+	INIT_LIST_HEAD(&pa->parts);
+}
+
+/**
+ * fdisk_new_partition:
+ *
+ * Returns: new instance.
+ */
+struct fdisk_partition *fdisk_new_partition(void)
+{
+	struct fdisk_partition *pa = calloc(1, sizeof(*pa));
+
+	pa->refcount = 1;
+	init_partition(pa);
+	DBG(PART, ul_debugobj(pa, "alloc"));
+	return pa;
+}
+
+/**
+ * fdisk_reset_partition:
+ * @pa: partition
+ *
+ * Resets partition content.
+ */
+void fdisk_reset_partition(struct fdisk_partition *pa)
+{
+	int ref;
+
+	if (!pa)
+		return;
+
+	DBG(PART, ul_debugobj(pa, "reset"));
+	ref = pa->refcount;
+
+	fdisk_unref_parttype(pa->type);
+	free(pa->name);
+	free(pa->uuid);
+	free(pa->attrs);
+
+	memset(pa, 0, sizeof(*pa));
+	pa->refcount = ref;
+
+	init_partition(pa);
+}
+
+/**
+ * fdisk_ref_partition:
+ * @pa: partition pointer
+ *
+ * Incremparts reference counter.
+ */
+void fdisk_ref_partition(struct fdisk_partition *pa)
+{
+	if (pa)
+		pa->refcount++;
+}
+
+/**
+ * fdisk_unref_partition:
+ * @pa: partition pointer
+ *
+ * De-incremparts reference counter, on zero the @pa is automatically
+ * deallocated.
+ */
+void fdisk_unref_partition(struct fdisk_partition *pa)
+{
+	if (!pa)
+		return;
+
+	pa->refcount--;
+	if (pa->refcount <= 0) {
+		fdisk_reset_partition(pa);
+		list_del(&pa->parts);
+		DBG(PART, ul_debugobj(pa, "free"));
+		free(pa);
+	}
+}
+
+/**
+ * fdisk_partition_set_start:
+ * @pa: partition
+ * @off: offset in sectors, maximal is UINT64_MAX-1
+ *
+ * Note that zero is valid offset too. Use fdisk_partition_unset_start() to
+ * undefine the offset. 
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_start(struct fdisk_partition *pa, fdisk_sector_t off)
+{
+	if (!pa)
+		return -EINVAL;
+	if (FDISK_IS_UNDEF(off))
+		return -ERANGE;
+	pa->start = off;
+	return 0;
+}
+
+/**
+ * fdisk_partition_unset_start:
+ * @pa: partition
+ *
+ * Sets the size as undefined. See fdisk_partition_has_start().
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_unset_start(struct fdisk_partition *pa)
+{
+	if (!pa)
+		return -EINVAL;
+	FDISK_INIT_UNDEF(pa->start);
+	return 0;
+}
+
+/**
+ * fdisk_partition_get_start:
+ * @pa: partition
+ *
+ * The zero is also valid offset. The function may return random undefined
+ * value when start offset is undefined (for example after
+ * fdisk_partition_unset_start()). Always use fdisk_partition_has_start() to be
+ * sure that you work with valid numbers.
+ *
+ * Returns: start offset in sectors
+ */
+fdisk_sector_t fdisk_partition_get_start(struct fdisk_partition *pa)
+{
+	return pa->start;
+}
+
+/**
+ * fdisk_partition_has_start:
+ * @pa: partition
+ *
+ * Returns: 1 or 0
+ */
+int fdisk_partition_has_start(struct fdisk_partition *pa)
+{
+	return pa && !FDISK_IS_UNDEF(pa->start);
+}
+
+
+/**
+ * fdisk_partition_cmp_start:
+ * @a: partition
+ * @b: partition
+ *
+ * Compares partitons according to start offset, See fdisk_sort_table().
+ *
+ * Return: 0 if the same, <0 if @b greater, >0 if @a greater.
+ */
+int fdisk_partition_cmp_start(struct fdisk_partition *a,
+			      struct fdisk_partition *b)
+{
+	int no_a = FDISK_IS_UNDEF(a->start),
+	    no_b = FDISK_IS_UNDEF(b->start);
+
+	if (no_a && no_b)
+		return 0;
+	if (no_a)
+		return -1;
+	if (no_b)
+		return 1;
+
+	return cmp_numbers(a->start, b->start);
+}
+
+/**
+ * fdisk_partition_start_follow_default
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * When @pa used as a tempalate for fdisk_add_partition() when force label driver
+ * to use the first possible space for the new partition.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_start_follow_default(struct fdisk_partition *pa, int enable)
+{
+	if (!pa)
+		return -EINVAL;
+	pa->start_follow_default = enable ? 1 : 0;
+	return 0;
+}
+
+/**
+ * fdisk_partition_start_is_default:
+ * @pa: partition
+ *
+ * See fdisk_partition_start_follow_default().
+ *
+ * Returns: 1 if the partition follows default
+ */
+int fdisk_partition_start_is_default(struct fdisk_partition *pa)
+{
+	assert(pa);
+	return pa->start_follow_default;
+}
+
+
+/**
+ * fdisk_partition_set_size:
+ * @pa: partition
+ * @sz: size in sectors, maximal is UIN64_MAX-1
+ *
+ * Note that zero is valid size too. Use fdisk_partition_unset_size() to
+ * undefine the size.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_size(struct fdisk_partition *pa, fdisk_sector_t sz)
+{
+	if (!pa)
+		return -EINVAL;
+	if (FDISK_IS_UNDEF(sz))
+		return -ERANGE;
+	pa->size = sz;
+	return 0;
+}
+
+/**
+ * fdisk_partition_unset_size:
+ * @pa: partition
+ *
+ * Sets the size as undefined. See fdisk_partition_has_size().
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_unset_size(struct fdisk_partition *pa)
+{
+	if (!pa)
+		return -EINVAL;
+	FDISK_INIT_UNDEF(pa->size);
+	return 0;
+}
+
+/**
+ * fdisk_partition_get_size:
+ * @pa: partition
+ *
+ * The zero is also valid size. The function may return random undefined
+ * value when size is undefined (for example after fdisk_partition_unset_size()).
+ * Always use fdisk_partition_has_size() to be sure that you work with valid 
+ * numbers.
+ *
+ * Returns: size offset in sectors
+ */
+fdisk_sector_t fdisk_partition_get_size(struct fdisk_partition *pa)
+{
+	return pa->size;
+}
+
+/**
+ * fdisk_partition_has_size:
+ * @pa: partition
+ *
+ * Returns: 1 or 0
+ */
+int fdisk_partition_has_size(struct fdisk_partition *pa)
+{
+	return pa && !FDISK_IS_UNDEF(pa->size);
+}
+
+/**
+ * fdisk_partition_size_explicit:
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * By default libfdisk aligns the size when add the new partition (by
+ * fdisk_add_partrition()). If you want to disable this functionality use
+ * @enable = 1.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_size_explicit(struct fdisk_partition *pa, int enable)
+{
+	if (!pa)
+		return -EINVAL;
+	pa->size_explicit = enable ? 1 : 0;
+	return 0;
+}
+
+/**
+ * fdisk_partition_set_partno:
+ * @pa: partition
+ * @num: partitin number (0 is the first partition, maximal is SIZE_MAX-1)
+ *
+ * Note that zero is valid partno too. Use fdisk_partition_unset_partno() to
+ * undefine the partno.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_partno(struct fdisk_partition *pa, size_t num)
+{
+	if (!pa)
+		return -EINVAL;
+	if (FDISK_IS_UNDEF(num))
+		return -ERANGE;
+	pa->partno = num;
+	return 0;
+}
+
+/**
+ * fdisk_partition_unset_partno:
+ * @pa: partition
+ *
+ * Sets the partno as undefined. See fdisk_partition_has_partno().
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_unset_partno(struct fdisk_partition *pa)
+{
+	if (!pa)
+		return -EINVAL;
+	FDISK_INIT_UNDEF(pa->partno);
+	return 0;
+}
+
+/**
+ * fdisk_partition_get_partno:
+ * @pa: partition
+ *
+ * The zero is also valid parition number. The function may return random
+ * value when partno is undefined (for example after fdisk_partition_unset_partno()).
+ * Always use fdisk_partition_has_partno() to be sure that you work with valid
+ * numbers.
+ *
+ * Returns: partition number (0 is the first partition)
+ */
+size_t fdisk_partition_get_partno(struct fdisk_partition *pa)
+{
+	return pa->partno;
+}
+
+/**
+ * fdisk_partition_has_partno:
+ * @pa: partition
+ *
+ * Returns: 1 or 0
+ */
+int fdisk_partition_has_partno(struct fdisk_partition *pa)
+{
+	return pa && !FDISK_IS_UNDEF(pa->partno);
+}
+
+
+/**
+ * fdisk_partition_cmp_partno:
+ * @a: partition
+ * @b: partition
+ *
+ * Compares partitons according to partition number See fdisk_sort_table().
+ *
+ * Return: 0 if the same, <0 if @b greater, >0 if @a greater.
+ */
+int fdisk_partition_cmp_partno(struct fdisk_partition *a,
+			       struct fdisk_partition *b)
+{
+	return a->partno - b->partno;
+}
+
+/**
+ * fdisk_partition_partno_follow_default
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * When @pa used as a tempalate for fdisk_add_partition() when force label driver
+ * to add a new partition to the default (next) position.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_partno_follow_default(struct fdisk_partition *pa, int enable)
+{
+	if (!pa)
+		return -EINVAL;
+	pa->partno_follow_default = enable ? 1 : 0;
+	return 0;
+}
+
+/**
+ * fdisk_partition_set_type:
+ * @pa: partition
+ * @type: partition type
+ *
+ * Sets parition type.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_type(struct fdisk_partition *pa,
+			     struct fdisk_parttype *type)
+{
+	if (!pa)
+		return -EINVAL;
+
+	fdisk_ref_parttype(type);
+	fdisk_unref_parttype(pa->type);
+	pa->type = type;
+
+	return 0;
+}
+
+/**
+ * fdisk_partition_get_type:
+ * @pa: partition
+ *
+ * Returns: pointer to partition type.
+ */
+struct fdisk_parttype *fdisk_partition_get_type(struct fdisk_partition *pa)
+{
+	return pa ? pa->type : NULL;
+}
+
+int fdisk_partition_set_name(struct fdisk_partition *pa, const char *name)
+{
+	char *p = NULL;
+
+	if (!pa)
+		return -EINVAL;
+	if (name) {
+	       p = strdup(name);
+	       if (!p)
+		       return -ENOMEM;
+	}
+	free(pa->name);
+	pa->name = p;
+	return 0;
+}
+
+const char *fdisk_partition_get_name(struct fdisk_partition *pa)
+{
+	return pa ? pa->name : NULL;
+}
+
+int fdisk_partition_set_uuid(struct fdisk_partition *pa, const char *uuid)
+{
+	char *p = NULL;
+
+	if (!pa)
+		return -EINVAL;
+	if (uuid) {
+	       p = strdup(uuid);
+	       if (!p)
+		       return -ENOMEM;
+	}
+	free(pa->uuid);
+	pa->uuid = p;
+	return 0;
+}
+
+/**
+ * fdisk_partition_has_end:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition has defined last sector
+ */
+int fdisk_partition_has_end(struct fdisk_partition *pa)
+{
+	return pa && !FDISK_IS_UNDEF(pa->start) && !FDISK_IS_UNDEF(pa->size);
+}
+
+/**
+ * fdisk_partition_get_end:
+ * @pa: partition
+ *
+ * This function may returns absolute non-sense, always check
+ * fdisk_partition_has_end().
+ *
+ * Note that partition end is defined by fdisk_partition_set_start() and
+ * fdisk_partition_set_size().
+ *
+ * Returns: last partition sector LBA.
+ */
+fdisk_sector_t fdisk_partition_get_end(struct fdisk_partition *pa)
+{
+	return pa->start + pa->size - (pa->size == 0 ? 0 : 1);
+}
+
+/**
+ * fdisk_partition_end_follow_default
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * When @pa used as a tempalate for fdisk_add_partition() when force label driver
+ * to use all the possible space for the new partition.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_end_follow_default(struct fdisk_partition *pa, int enable)
+{
+	if (!pa)
+		return -EINVAL;
+	pa->end_follow_default = enable ? 1 : 0;
+	return 0;
+}
+
+/**
+ * fdisk_partition_end_is_default:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition follows default
+ */
+int fdisk_partition_end_is_default(struct fdisk_partition *pa)
+{
+	assert(pa);
+	return pa->end_follow_default;
+}
+
+const char *fdisk_partition_get_uuid(struct fdisk_partition *pa)
+{
+	return pa ? pa->uuid : NULL;
+}
+
+const char *fdisk_partition_get_attrs(struct fdisk_partition *pa)
+{
+	return pa ? pa->attrs : NULL;
+}
+
+int fdisk_partition_set_attrs(struct fdisk_partition *pa, const char *attrs)
+{
+	char *p = NULL;
+
+	if (!pa)
+		return -EINVAL;
+	if (attrs) {
+	       p = strdup(attrs);
+	       if (!p)
+		       return -ENOMEM;
+	}
+	free(pa->attrs);
+	pa->attrs = p;
+	return 0;
+}
+
+int fdisk_partition_is_nested(struct fdisk_partition *pa)
+{
+	return pa && !FDISK_IS_UNDEF(pa->parent_partno);
+}
+
+int fdisk_partition_is_container(struct fdisk_partition *pa)
+{
+	return pa && pa->container;
+}
+
+int fdisk_partition_get_parent(struct fdisk_partition *pa, size_t *parent)
+{
+	if (pa && parent)
+		*parent = pa->parent_partno;
+	else
+		return -EINVAL;
+	return 0;
+}
+
+int fdisk_partition_is_used(struct fdisk_partition *pa)
+{
+	return pa && pa->used;
+}
+
+int fdisk_partition_is_bootable(struct fdisk_partition *pa)
+{
+	return pa && pa->boot == 1;
+}
+
+int fdisk_partition_is_freespace(struct fdisk_partition *pa)
+{
+	return pa && pa->freespace;
+}
+
+int fdisk_partition_next_partno(
+		struct fdisk_partition *pa,
+		struct fdisk_context *cxt,
+		size_t *n)
+{
+	assert(cxt);
+	assert(n);
+
+	if (pa && pa->partno_follow_default) {
+		size_t i;
+
+		DBG(PART, ul_debugobj(pa, "next partno (follow default)"));
+
+		for (i = 0; i < cxt->label->nparts_max; i++) {
+			if (!fdisk_is_partition_used(cxt, i)) {
+				*n = i;
+				return 0;
+			}
+		}
+		return -ERANGE;
+
+	} else if (pa && fdisk_partition_has_partno(pa)) {
+
+		DBG(PART, ul_debugobj(pa, "next partno (specified=%zu)", pa->partno));
+
+		if (pa->partno >= cxt->label->nparts_max)
+			return -ERANGE;
+		*n = pa->partno;
+	} else
+		return fdisk_ask_partnum(cxt, n, 1);
+
+	return 0;
+}
+
+/**
+ * fdisk_partition_to_string:
+ * @pa: partition
+ * @cxt: context
+ * @id: field (FDISK_FIELD_*)
+ * @data: returns string with allocated data
+ *
+ * Returns info about partition converted to printable string.
+ *
+ * For example
+ * <informalexample>
+ *   <programlisting>
+ *      struct fdisk_parition *pa;
+ *
+ *      fdisk_get_partition(cxt, 0, &pa);
+ *	fdisk_partition_to_string(pa, FDISK_FIELD_UUID, &data);
+ *	printf("first partition uuid: %s\n", data);
+ *	free(data);
+ *	fdisk_unref_partition(pa);
+ *   </programlisting>
+ * </informalexample>
+ *
+ * returns UUID for the first partition.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_partition_to_string(struct fdisk_partition *pa,
+			      struct fdisk_context *cxt,
+			      int id,
+			      char **data)
+{
+	char *p = NULL;
+	int rc = 0;
+	uint64_t x;
+
+	if (!pa || !cxt)
+		return -EINVAL;
+
+	switch (id) {
+	case FDISK_FIELD_DEVICE:
+		if (pa->freespace)
+			p = strdup(_("Free space"));
+		else if (fdisk_partition_has_partno(pa) && cxt->dev_path) {
+			if (cxt->label->flags & FDISK_LABEL_FL_INCHARS_PARTNO)
+				rc = asprintf(&p, "%c", (int) pa->partno + 'a');
+			else
+				p = fdisk_partname(cxt->dev_path, pa->partno + 1);
+		}
+		break;
+	case FDISK_FIELD_BOOT:
+		if (fdisk_partition_is_bootable(pa))
+			rc = asprintf(&p, "%c", pa->boot ? '*' : ' ');
+		break;
+	case FDISK_FIELD_START:
+		if (fdisk_partition_has_start(pa)) {
+			x = fdisk_cround(cxt, pa->start);
+			rc = pa->start_post ?
+				asprintf(&p, "%ju%c", x, pa->start_post) :
+				asprintf(&p, "%ju", x);
+		}
+		break;
+	case FDISK_FIELD_END:
+		if (fdisk_partition_has_end(pa)) {
+			x = fdisk_cround(cxt, fdisk_partition_get_end(pa));
+			rc = pa->end_post ?
+					asprintf(&p, "%ju%c", x, pa->end_post) :
+					asprintf(&p, "%ju", x);
+		}
+		break;
+	case FDISK_FIELD_SIZE:
+		if (fdisk_partition_has_size(pa)) {
+			uint64_t sz = pa->size * cxt->sector_size;
+
+			if (fdisk_is_details(cxt)) {
+				rc = pa->size_post ?
+						asprintf(&p, "%ju%c", sz, pa->size_post) :
+						asprintf(&p, "%ju", sz);
+			} else {
+				p = size_to_human_string(SIZE_SUFFIX_1LETTER, sz);
+				if (!p)
+					rc = -ENOMEM;
+			}
+		}
+		break;
+	case FDISK_FIELD_CYLINDERS:
+		rc = asprintf(&p, "%ju", (uintmax_t)
+			fdisk_cround(cxt, fdisk_partition_has_size(pa) ? pa->size : 0));
+		break;
+	case FDISK_FIELD_SECTORS:
+		rc = asprintf(&p, "%ju",
+			fdisk_partition_has_size(pa) ? (uintmax_t) pa->size : 0);
+		break;
+	case FDISK_FIELD_BSIZE:
+		rc = asprintf(&p, "%ju", pa->bsize);
+		break;
+	case FDISK_FIELD_FSIZE:
+		rc = asprintf(&p, "%ju", pa->fsize);
+		break;
+	case FDISK_FIELD_CPG:
+		rc = asprintf(&p, "%ju", pa->cpg);
+		break;
+	case FDISK_FIELD_TYPE:
+		p = pa->type && pa->type->name ? strdup(pa->type->name) : NULL;
+		break;
+	case FDISK_FIELD_TYPEID:
+		if (pa->type && fdisk_parttype_get_string(pa->type))
+			rc = asprintf(&p, "%s", fdisk_parttype_get_string(pa->type));
+		else if (pa->type)
+			rc = asprintf(&p, "%x", fdisk_parttype_get_code(pa->type));
+		break;
+	case FDISK_FIELD_UUID:
+		p = pa->uuid ? strdup(pa->uuid) : NULL;
+		break;
+	case FDISK_FIELD_NAME:
+		p = pa->name ? strdup(pa->name) : NULL;
+		break;
+	case FDISK_FIELD_ATTR:
+		p = pa->attrs ? strdup(pa->attrs) : NULL;
+		break;
+	case FDISK_FIELD_SADDR:
+		p = pa->start_chs ? strdup(pa->start_chs) : NULL;
+		break;
+	case FDISK_FIELD_EADDR:
+		p = pa->end_chs ? strdup(pa->end_chs) : NULL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (rc < 0)
+		rc = -ENOMEM;
+	else if (rc > 0)
+		rc = 0;
+
+	if (data)
+		*data = p;
+	return rc;
+}
+
+/**
+ * fdisk_get_partition:
+ * @cxt: context
+ * @partno: partition number (0 is the first partition)
+ * @pa: returns data about partition
+ *
+ * Reads disklabel and fills in @pa with data about partition @n.
+ *
+ * Note that partno may address unused partition and then this function does
+ * not fill anything to @pa.  See fdisk_is_partition_used(). If @pa points to
+ * NULL then the function allocates a newly allocated fdisk_partition struct,
+ * use fdisk_unref_partition() to deallocate.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_get_partition(struct fdisk_context *cxt, size_t partno,
+			struct fdisk_partition **pa)
+{
+	int rc;
+	struct fdisk_partition *np = NULL;
+
+	if (!cxt || !cxt->label || !pa)
+		return -EINVAL;
+	if (!cxt->label->op->get_part)
+		return -ENOSYS;
+	if (!fdisk_is_partition_used(cxt, partno))
+		return -EINVAL;
+
+	if (!*pa) {
+		np = *pa = fdisk_new_partition();
+		if (!*pa)
+			return -ENOMEM;
+	} else
+		fdisk_reset_partition(*pa);
+
+	(*pa)->partno = partno;
+	rc = cxt->label->op->get_part(cxt, partno, *pa);
+
+	if (rc) {
+		if (np) {
+			fdisk_unref_partition(np);
+			*pa = NULL;
+		} else
+			fdisk_reset_partition(*pa);
+	} else
+		(*pa)->size_explicit = 1;
+	return rc;
+}
+
+/**
+ * fdisk_set_partition:
+ * @cxt: context
+ * @partno: partition number (0 is the first partition)
+ * @pa: new partition setting
+ *
+ * Modifies disklabel according to setting with in @pa.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_set_partition(struct fdisk_context *cxt, size_t partno,
+			struct fdisk_partition *pa)
+{
+	if (!cxt || !cxt->label || !pa)
+		return -EINVAL;
+	if (!cxt->label->op->set_part)
+		return -ENOSYS;
+
+	DBG(CXT, ul_debugobj(cxt, "setting partition %zu %p (start=%ju, end=%ju, size=%ju, "
+		    "defaults(start=%s, end=%s, partno=%s)",
+		    partno, pa,
+		    (uintmax_t) fdisk_partition_get_start(pa),
+		    (uintmax_t) fdisk_partition_get_end(pa),
+		    (uintmax_t) fdisk_partition_get_size(pa),
+		    pa->start_follow_default ? "yes" : "no",
+		    pa->end_follow_default ? "yes" : "no",
+		    pa->partno_follow_default ? "yes" : "no"));
+
+	return cxt->label->op->set_part(cxt, partno, pa);
+}
+
+/**
+ * fdisk_add_partition:
+ * @cxt: fdisk context
+ * @pa: template for the partition (or NULL)
+ * @partno: NULL or returns new partition number
+ *
+ * If @pa is not specified or any @pa item is missiong the libfdisk will ask by
+ * fdisk_ask_ API.
+ *
+ * Adds a new partition to disklabel.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_add_partition(struct fdisk_context *cxt,
+			struct fdisk_partition *pa,
+			size_t *partno)
+{
+	int rc;
+
+	assert(cxt);
+	assert(cxt->label);
+
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+	if (!cxt->label->op->add_part)
+		return -ENOSYS;
+	if (fdisk_missing_geometry(cxt))
+		return -EINVAL;
+
+	if (pa)
+		DBG(CXT, ul_debugobj(cxt, "adding new partition %p (start=%ju, end=%ju, size=%ju, "
+			    "defaults(start=%s, end=%s, partno=%s)",
+			    pa,
+			    (uintmax_t) fdisk_partition_get_start(pa),
+			    (uintmax_t) fdisk_partition_get_end(pa),
+			    (uintmax_t) fdisk_partition_get_size(pa),
+			    pa->start_follow_default ? "yes" : "no",
+			    pa->end_follow_default ? "yes" : "no",
+			    pa->partno_follow_default ? "yes" : "no"));
+	else
+		DBG(CXT, ul_debugobj(cxt, "adding partition"));
+
+	rc = cxt->label->op->add_part(cxt, pa, partno);
+
+	DBG(CXT, ul_debugobj(cxt, "add partition done (rc=%d)", rc));
+	return rc;
+}
+
+/**
+ * fdisk_delete_partition:
+ * @cxt: fdisk context
+ * @partno: partition number to delete (0 is the first partition)
+ *
+ * Deletes a @partno partition from disklabel.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_delete_partition(struct fdisk_context *cxt, size_t partno)
+{
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+	if (!cxt->label->op->del_part)
+		return -ENOSYS;
+
+	DBG(CXT, ul_debugobj(cxt, "deleting %s partition number %zd",
+				cxt->label->name, partno));
+	return cxt->label->op->del_part(cxt, partno);
+}
+
+/**
+ * fdisk_delete_all_partitions:
+ * @cxt: fdisk context
+ *
+ * Delete all used partitions from disklabel.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_delete_all_partitions(struct fdisk_context *cxt)
+{
+	size_t i;
+	int rc;
+
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+
+		if (!fdisk_is_partition_used(cxt, i))
+			continue;
+		rc = fdisk_delete_partition(cxt, i);
+		if (rc)
+			break;
+	}
+
+	return rc;
+}
+
+/**
+ * fdisk_is_partition_used:
+ * @cxt: context
+ * @n: partition number (0 is the first partition)
+ *
+ * This is faster than fdisk_get_partition() + fdisk_partition_is_used().
+ *
+ * Returns: 0 or 1
+ */
+int fdisk_is_partition_used(struct fdisk_context *cxt, size_t n)
+{
+	if (!cxt || !cxt->label)
+		return -EINVAL;
+	if (!cxt->label->op->part_is_used)
+		return -ENOSYS;
+
+	return cxt->label->op->part_is_used(cxt, n);
+}
+
diff --git a/libblkid/libfdisk/src/parttype.c b/libblkid/libfdisk/src/parttype.c
new file mode 100644
index 0000000..aedf4e8
--- /dev/null
+++ b/libblkid/libfdisk/src/parttype.c
@@ -0,0 +1,401 @@
+
+#include <ctype.h>
+
+#include "nls.h"
+#include "fdiskP.h"
+
+/**
+ * SECTION: parttype
+ * @title: Partition types
+ * @short_description: abstraction to partition types
+ *
+ * There are two basic types of parttypes, string based (e.g. GPT)
+ * and code/hex based (e.g. MBR).
+ */
+
+/**
+ * fdisk_new_parttype:
+ *
+ * It's recommended to use fdisk_label_get_parttype_from_code() or
+ * fdisk_label_get_parttype_from_string() for well known types rather
+ * than allocate a new instance.
+ *
+ * Returns: new instance.
+ */
+struct fdisk_parttype *fdisk_new_parttype(void)
+{
+	struct fdisk_parttype *t = calloc(1, sizeof(*t));
+
+	t->refcount = 1;
+	t->flags = FDISK_PARTTYPE_ALLOCATED;
+	DBG(PARTTYPE, ul_debugobj(t, "alloc"));
+	return t;
+}
+
+/**
+ * fdisk_ref_parttype:
+ * @t: partition type
+ *
+ * Incremparts reference counter for allocated types
+ */
+void fdisk_ref_parttype(struct fdisk_parttype *t)
+{
+	if (fdisk_parttype_is_allocated(t))
+		t->refcount++;
+}
+
+/**
+ * fdisk_unref_parttype
+ * @t: partition pointer
+ *
+ * De-incremparts reference counter, on zero the @t is automatically
+ * deallocated.
+ */
+void fdisk_unref_parttype(struct fdisk_parttype *t)
+{
+	if (!fdisk_parttype_is_allocated(t))
+		return;
+
+	t->refcount--;
+	if (t->refcount <= 0) {
+		DBG(PARTTYPE, ul_debugobj(t, "free"));
+		free(t->typestr);
+		free(t->name);
+		free(t);
+	}
+}
+
+/**
+ * fdisk_parttype_set_name:
+ * @t: partition type
+ * @str: type name
+ *
+ * Sets type name to allocated partition type, for static types
+ * it returns -EINVAL.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_parttype_set_name(struct fdisk_parttype *t, const char *str)
+{
+	char *p = NULL;
+
+	if (!t || !fdisk_parttype_is_allocated(t))
+		return -EINVAL;
+	if (str) {
+		p = strdup(str);
+		if (!p)
+			return -ENOMEM;
+	}
+
+	free(t->name);
+	t->name = p;
+	return 0;
+}
+
+/**
+ * fdisk_parttype_set_typestr:
+ * @t: partition type
+ * @str: type identificator (e.g. GUID for GPT)
+ *
+ * Sets type string to allocated partition type, for static types
+ * it returns -EINVAL. Don't use this function for MBR, see
+ * fdisk_parttype_set_code().
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_parttype_set_typestr(struct fdisk_parttype *t, const char *str)
+{
+	char *p = NULL;
+
+	if (!t || !fdisk_parttype_is_allocated(t))
+		return -EINVAL;
+	if (str) {
+		p = strdup(str);
+		if (!p)
+			return -ENOMEM;
+	}
+
+	free(t->typestr);
+	t->typestr = p;
+	return 0;
+}
+
+/**
+ * fdisk_parttype_set_code:
+ * @t: partition type
+ * @code: type identificator (e.g. MBR type codes)
+ *
+ * Sets type code to allocated partition type, for static types it returns
+ * -EINVAL. Don't use this function for GPT, see fdisk_parttype_set_typestr().
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_parttype_set_code(struct fdisk_parttype *t, int code)
+{
+	if (!t || !fdisk_parttype_is_allocated(t))
+		return -EINVAL;
+	t->code = code;
+	return 0;
+}
+
+/**
+ * fdisk_label_get_nparttypes:
+ * @lb: label
+ *
+ * Returns: number of types supported by label.
+ */
+size_t fdisk_label_get_nparttypes(const struct fdisk_label *lb)
+{
+	if (!lb)
+		return 0;
+	return lb->nparttypes;
+}
+
+/**
+ * fdisk_label_get_parttype:
+ * @lb: label
+ * @n: number
+ *
+ * Returns: return parttype
+ */
+struct fdisk_parttype *fdisk_label_get_parttype(const struct fdisk_label *lb, size_t n)
+{
+	if (!lb || n >= lb->nparttypes)
+		return NULL;
+	return &lb->parttypes[n];
+}
+
+/**
+ * fdisk_label_has_code_parttypes:
+ * @lb: label
+ *
+ * Returns: 1 if the label uses code as partition type
+ *          identifiers (e.g. MBR) or 0.
+ */
+int fdisk_label_has_code_parttypes(const struct fdisk_label *lb)
+{
+	assert(lb);
+
+	if (lb->parttypes && lb->parttypes[0].typestr)
+		return 0;
+	return 1;
+}
+
+
+/**
+ * fdisk_label_get_parttype_from_code:
+ * @lb: label
+ * @code: code to search for
+ *
+ * Search for partition type in label-specific table. The result
+ * is pointer to static array of label types.
+ *
+ * Returns: partition type or NULL upon failure or invalid @code.
+ */
+struct fdisk_parttype *fdisk_label_get_parttype_from_code(
+				const struct fdisk_label *lb,
+				unsigned int code)
+{
+	size_t i;
+
+	assert(lb);
+
+	if (!lb->nparttypes)
+		return NULL;
+
+	for (i = 0; i < lb->nparttypes; i++)
+		if (lb->parttypes[i].code == code)
+			return &lb->parttypes[i];
+	return NULL;
+}
+
+/**
+ * fdisk_label_get_parttype_from_string:
+ * @lb: label
+ * @str: string to search for
+ *
+ * Search for partition type in label-specific table. The result
+ * is pointer to static array of label types.
+ *
+ * Returns: partition type or NULL upon failure or invalid @str.
+ */
+struct fdisk_parttype *fdisk_label_get_parttype_from_string(
+				const struct fdisk_label *lb,
+				const char *str)
+{
+	size_t i;
+
+	assert(lb);
+
+	if (!lb->nparttypes)
+		return NULL;
+
+	for (i = 0; i < lb->nparttypes; i++)
+		if (lb->parttypes[i].typestr
+		    && strcasecmp(lb->parttypes[i].typestr, str) == 0)
+			return &lb->parttypes[i];
+
+	return NULL;
+}
+
+/**
+ * fdisk_new_unknown_parttype:
+ * @code: type as number
+ * @typestr: type as string
+
+ * Allocates new 'unknown' partition type. Use fdisk_unref_parttype() to
+ * deallocate.
+ *
+ * Returns: newly allocated partition type, or NULL upon failure.
+ */
+struct fdisk_parttype *fdisk_new_unknown_parttype(unsigned int code,
+						  const char *typestr)
+{
+	struct fdisk_parttype *t = fdisk_new_parttype();
+
+	if (!t)
+		return NULL;
+
+	fdisk_parttype_set_name(t, _("unknown"));
+	fdisk_parttype_set_code(t, code);
+	fdisk_parttype_set_typestr(t, typestr);
+	t->flags |= FDISK_PARTTYPE_UNKNOWN;
+
+	return t;
+}
+
+/**
+ * fdisk_copy_parttype:
+ * @type: type to copy
+ *
+ * Use fdisk_unref_parttype() to deallocate.
+ *
+ * Returns: newly allocated partition type, or NULL upon failure.
+ */
+struct fdisk_parttype *fdisk_copy_parttype(const struct fdisk_parttype *type)
+{
+	struct fdisk_parttype *t = fdisk_new_parttype();
+
+	if (!t)
+		return NULL;
+
+	fdisk_parttype_set_name(t, type->name);
+	fdisk_parttype_set_code(t, type->code);
+	fdisk_parttype_set_typestr(t, type->typestr);
+
+	return t;
+}
+
+/**
+ * fdisk_label_parse_parttype:
+ * @lb: label
+ * @str: string to parse from
+ *
+ * Parses partition type from @str according to the label. Thefunction returns
+ * a pointer to static table of the partition types, or newly allocated
+ * partition type for unknown types (see fdisk_parttype_is_unknown(). It's
+ * safe to call fdisk_unref_parttype() for all results.
+ *
+ * Returns: pointer to type or NULL on error.
+ */
+struct fdisk_parttype *fdisk_label_parse_parttype(
+				const struct fdisk_label *lb,
+				const char *str)
+{
+	struct fdisk_parttype *types, *ret;
+	unsigned int code = 0;
+	char *typestr = NULL, *end = NULL;
+
+	assert(lb);
+
+	if (!lb->nparttypes)
+		return NULL;
+
+	DBG(LABEL, ul_debugobj((void *) lb, "parsing '%s' (%s) partition type",
+				str, lb->name));
+	types = lb->parttypes;
+
+	if (types[0].typestr == NULL && isxdigit(*str)) {
+
+		errno = 0;
+		code = strtol(str, &end, 16);
+
+		if (errno || *end != '\0') {
+			DBG(LABEL, ul_debugobj((void *) lb, "parsing failed: %m"));
+			return NULL;
+		}
+		ret = fdisk_label_get_parttype_from_code(lb, code);
+		if (ret)
+			goto done;
+	} else {
+		int i;
+
+		/* maybe specified by type string (e.g. UUID) */
+		ret = fdisk_label_get_parttype_from_string(lb, str);
+		if (ret)
+			goto done;
+
+		/* maybe specified by order number */
+		errno = 0;
+		i = strtol(str, &end, 0);
+		if (errno == 0 && *end == '\0' && i > 0
+		    && i - 1 < (int) lb->nparttypes) {
+			ret = &types[i - 1];
+			goto done;
+		}
+	}
+
+	ret = fdisk_new_unknown_parttype(code, typestr);
+done:
+	DBG(PARTTYPE, ul_debugobj(ret, "returns parsed '%s' partition type", ret->name));
+	return ret;
+}
+
+/**
+ * fdisk_parttype_get_string:
+ * @t: type
+ *
+ * Returns: partition type string (e.g. GUID for GPT)
+ */
+const char *fdisk_parttype_get_string(const struct fdisk_parttype *t)
+{
+	assert(t);
+	return t->typestr && *t->typestr ? t->typestr : NULL;
+}
+
+/**
+ * fdisk_parttype_get_code:
+ * @t: type
+ *
+ * Returns: partition type code (e.g. for MBR)
+ */
+unsigned int fdisk_parttype_get_code(const struct fdisk_parttype *t)
+{
+	assert(t);
+	return t->code;
+}
+
+/**
+ * fdisk_parttype_get_name:
+ * @t: type
+ *
+ * Returns: partition type human readable name
+ */
+const char *fdisk_parttype_get_name(const struct fdisk_parttype *t)
+{
+	assert(t);
+	return t->name;
+}
+
+/**
+ * fdisk_parttype_is_unknown:
+ * @t: type
+ *
+ * Checks for example result from fdisk_label_parse_parttype().
+ *
+ * Returns: 1 is type is "unknonw" or 0.
+ */
+int fdisk_parttype_is_unknown(const struct fdisk_parttype *t)
+{
+	return t && (t->flags & FDISK_PARTTYPE_UNKNOWN) ? 1 : 0;
+}
diff --git a/libblkid/libfdisk/src/script.c b/libblkid/libfdisk/src/script.c
new file mode 100644
index 0000000..83bda99
--- /dev/null
+++ b/libblkid/libfdisk/src/script.c
@@ -0,0 +1,1235 @@
+
+#include "fdiskP.h"
+#include "strutils.h"
+
+/**
+ * SECTION: script
+ * @title: Script
+ * @short_description: text based sfdisk compatible description of partition table
+ *
+ * The libfdisk scripts are based on original sfdisk script (dumps).  Each
+ * script has two parts: script headers and partition table entries
+ * (partitions).
+ *
+ * For more details about script format see sfdisk man page.
+ */
+
+/* script header (e.g. unit: sectors) */
+struct fdisk_scriptheader {
+	struct list_head	headers;
+	char			*name;
+	char			*data;
+};
+
+/* script control struct */
+struct fdisk_script {
+	struct fdisk_table	*table;
+	struct list_head	headers;
+	struct fdisk_context	*cxt;
+
+	int			refcount;
+
+	/* parser's state */
+	size_t			nlines;
+	int			fmt;		/* input format */
+	struct fdisk_label	*label;
+};
+
+
+static void fdisk_script_free_header(struct fdisk_script *dp, struct fdisk_scriptheader *fi)
+{
+	if (!fi)
+		return;
+
+	DBG(SCRIPT, ul_debugobj(fi, "free header %s", fi->name));
+	free(fi->name);
+	free(fi->data);
+	list_del(&fi->headers);
+	free(fi);
+}
+
+/**
+ * fdisk_new_script:
+ * @cxt: context
+ *
+ * The script hold fdisk_table and additional information to read/write
+ * script to the file.
+ *
+ * Returns: newly allocated script struct.
+ */
+struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt)
+{
+	struct fdisk_script *dp = NULL;
+
+	dp = calloc(1, sizeof(*dp));
+	if (!dp)
+		return NULL;
+
+	DBG(SCRIPT, ul_debugobj(dp, "alloc"));
+	dp->refcount = 1;
+	dp->cxt = cxt;
+	fdisk_ref_context(cxt);
+
+	dp->table = fdisk_new_table();
+	if (!dp->table) {
+		fdisk_unref_script(dp);
+		return NULL;
+	}
+
+	INIT_LIST_HEAD(&dp->headers);
+	return dp;
+}
+
+/**
+ * fdisk_new_script_from_file:
+ * @cxt: context
+ * @filename: path to the script file
+ *
+ * Allocates a new script and reads script from @filename.
+ *
+ * Returns: new script instance or NULL in case of error (check errno for more details).
+ */
+struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt,
+						 const char *filename)
+{
+	int rc;
+	FILE *f;
+	struct fdisk_script *dp, *res = NULL;
+
+	assert(cxt);
+	assert(filename);
+
+	DBG(SCRIPT, ul_debug("opening %s", filename));
+	f = fopen(filename, "r");
+	if (!f)
+		return NULL;
+
+	dp = fdisk_new_script(cxt);
+	if (!dp)
+		goto done;
+
+	rc = fdisk_script_read_file(dp, f);
+	if (rc) {
+		errno = -rc;
+		goto done;
+	}
+
+	res = dp;
+done:
+	fclose(f);
+	if (!res)
+		fdisk_unref_script(dp);
+	else
+		errno = 0;
+
+	return res;
+}
+
+/**
+ * fdisk_ref_script:
+ * @dp: script pointer
+ *
+ * Incremparts reference counter.
+ */
+void fdisk_ref_script(struct fdisk_script *dp)
+{
+	if (dp)
+		dp->refcount++;
+}
+
+static void fdisk_reset_script(struct fdisk_script *dp)
+{
+	assert(dp);
+
+	DBG(SCRIPT, ul_debugobj(dp, "reset"));
+	fdisk_unref_table(dp->table);
+	dp->table = NULL;
+
+	while (!list_empty(&dp->headers)) {
+		struct fdisk_scriptheader *fi = list_entry(dp->headers.next,
+						  struct fdisk_scriptheader, headers);
+		fdisk_script_free_header(dp, fi);
+	}
+	INIT_LIST_HEAD(&dp->headers);
+}
+
+/**
+ * fdisk_unref_script:
+ * @dp: script pointer
+ *
+ * De-incremparts reference counter, on zero the @dp is automatically
+ * deallocated.
+ */
+void fdisk_unref_script(struct fdisk_script *dp)
+{
+	if (!dp)
+		return;
+
+	dp->refcount--;
+	if (dp->refcount <= 0) {
+		fdisk_reset_script(dp);
+		fdisk_unref_context(dp->cxt);
+		DBG(SCRIPT, ul_debugobj(dp, "free script"));
+		free(dp);
+	}
+}
+
+static struct fdisk_scriptheader *script_get_header(struct fdisk_script *dp,
+						     const char *name)
+{
+	struct list_head *p;
+
+	list_for_each(p, &dp->headers) {
+		struct fdisk_scriptheader *fi = list_entry(p, struct fdisk_scriptheader, headers);
+
+		if (strcasecmp(fi->name, name) == 0)
+			return fi;
+	}
+
+	return NULL;
+}
+
+/**
+ * fdisk_script_get_header:
+ * @dp: script instance
+ * @name: header name
+ *
+ * Returns: pointer to header data or NULL.
+ */
+const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name)
+{
+	struct fdisk_scriptheader *fi;
+
+	assert(dp);
+	assert(name);
+
+	fi = script_get_header(dp, name);
+	return fi ? fi->data : NULL;
+}
+
+
+/**
+ * fdisk_script_set_header:
+ * @dp: script instance
+ * @name: header name
+ * @data: header data (or NULL)
+ *
+ * The headers are used as global options (in script) for whole partition
+ * table, always one header per line.
+ *
+ * If no @data specified then the header is removed. If header does not exist
+ * and @data specified then a new header added.
+ *
+ * Note that libfdisk allows to specify arbitrary custom header, the default
+ * build-in headers are "unit" and "label", and some label specific headers
+ * (for example "uuid" and "name" for GPT).
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_script_set_header(struct fdisk_script *dp,
+			    const char *name,
+			    const char *data)
+{
+	struct fdisk_scriptheader *fi;
+
+	assert(dp);
+	assert(name);
+
+	if (!dp || !name)
+		return -EINVAL;
+
+	fi = script_get_header(dp, name);
+	if (!fi && !data)
+		return 0;	/* want to remove header that does not exist, success */
+
+	if (!data) {
+		/* no data, remove the header */
+		fdisk_script_free_header(dp, fi);
+		return 0;
+	}
+
+	if (!fi) {
+		/* new header */
+		fi = calloc(1, sizeof(*fi));
+		if (!fi)
+			return -ENOMEM;
+		INIT_LIST_HEAD(&fi->headers);
+		fi->name = strdup(name);
+		fi->data = strdup(data);
+		if (!fi->data || !fi->name) {
+			fdisk_script_free_header(dp, fi);
+			return -ENOMEM;
+		}
+		list_add_tail(&fi->headers, &dp->headers);
+	} else {
+		/* update existing */
+		char *x = strdup(data);
+
+		if (!x)
+			return -ENOMEM;
+		free(fi->data);
+		fi->data = x;
+	}
+
+	if (strcmp(name, "label") == 0)
+		dp->label = NULL;
+
+	return 0;
+}
+
+/**
+ * fdisk_script_get_table:
+ * @dp: script
+ *
+ * The table (container with partitions) is possible to create by
+ * fdisk_script_read_context() or fdisk_script_read_file(), otherwise
+ * this function returns NULL.
+ *
+ * Returns: NULL or script.
+ */
+struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp)
+{
+	assert(dp);
+	return dp ? dp->table : NULL;
+}
+
+static struct fdisk_label *script_get_label(struct fdisk_script *dp)
+{
+	assert(dp);
+	assert(dp->cxt);
+
+	if (!dp->label) {
+		dp->label = fdisk_get_label(dp->cxt,
+					fdisk_script_get_header(dp, "label"));
+		DBG(SCRIPT, ul_debugobj(dp, "label '%s'", dp->label ? dp->label->name : ""));
+	}
+	return dp->label;
+}
+
+/**
+ * fdisk_script_get_nlines:
+ * @dp: script
+ *
+ * Returns: number of parsed lines or <0 on error.
+ */
+int fdisk_script_get_nlines(struct fdisk_script *dp)
+{
+	assert(dp);
+	return dp->nlines;
+}
+
+/**
+ * fdisk_script_read_context:
+ * @dp: script
+ * @cxt: context
+ *
+ * Reads data from the @cxt context (on disk partition table) into the script.
+ * If the context is no specified than defaults to context used for fdisk_new_script().
+ *
+ * Return: 0 on success, <0 on error.
+ */
+int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt)
+{
+	struct fdisk_label *lb;
+	int rc;
+	char *p = NULL;
+
+	assert(dp);
+
+	if (!cxt)
+		cxt = dp->cxt;
+
+	if (!dp || !cxt)
+		return -EINVAL;
+
+	DBG(SCRIPT, ul_debugobj(dp, "reading context into script"));
+	fdisk_reset_script(dp);
+
+	lb = fdisk_get_label(cxt, NULL);
+	if (!lb)
+		return -EINVAL;
+
+	/* allocate and fill new table */
+	rc = fdisk_get_partitions(cxt, &dp->table);
+	if (rc)
+		return rc;
+
+	/* generate headers */
+	rc = fdisk_script_set_header(dp, "label", fdisk_label_get_name(lb));
+
+	if (!rc && fdisk_get_disklabel_id(cxt, &p) == 0 && p) {
+		rc = fdisk_script_set_header(dp, "label-id", p);
+		free(p);
+	}
+	if (!rc && cxt->dev_path)
+		rc = fdisk_script_set_header(dp, "device", cxt->dev_path);
+	if (!rc)
+		rc = fdisk_script_set_header(dp, "unit", "sectors");
+
+	/* TODO: label specific headers (e.g. uuid for GPT) */
+
+	DBG(SCRIPT, ul_debugobj(dp, "read context done [rc=%d]", rc));
+	return rc;
+}
+
+/**
+ * fdisk_script_write_file:
+ * @dp: script
+ * @f: output file
+ *
+ * Writes script @dp to the ile @f.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_script_write_file(struct fdisk_script *dp, FILE *f)
+{
+	struct list_head *h;
+	struct fdisk_partition *pa;
+	struct fdisk_iter itr;
+	const char *devname = NULL;
+
+	assert(dp);
+	assert(f);
+
+	DBG(SCRIPT, ul_debugobj(dp, "writing script to file"));
+
+	/* script headers */
+	list_for_each(h, &dp->headers) {
+		struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
+		fprintf(f, "%s: %s\n", fi->name, fi->data);
+		if (strcmp(fi->name, "device") == 0)
+			devname = fi->data;
+	}
+
+	if (!dp->table) {
+		DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
+		return 0;
+	}
+
+	DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
+
+	fputc('\n', f);
+
+	fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+	while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
+		char *p = NULL;
+
+		if (devname)
+			p = fdisk_partname(devname, pa->partno + 1);
+		if (p) {
+			DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
+			fprintf(f, "%s :", p);
+		} else
+			fprintf(f, "%zu :", pa->partno + 1);
+
+		if (fdisk_partition_has_start(pa))
+			fprintf(f, " start=%12ju", pa->start);
+		if (fdisk_partition_has_size(pa))
+			fprintf(f, ", size=%12ju", pa->size);
+
+		if (pa->type && fdisk_parttype_get_string(pa->type))
+			fprintf(f, ", type=%s", fdisk_parttype_get_string(pa->type));
+		else if (pa->type)
+			fprintf(f, ", type=%x", fdisk_parttype_get_code(pa->type));
+
+		if (pa->uuid)
+			fprintf(f, ", uuid=%s", pa->uuid);
+		if (pa->name && *pa->name)
+			fprintf(f, ", name=\"%s\"", pa->name);
+
+		/* for MBR attr=80 means bootable */
+		if (pa->attrs) {
+			struct fdisk_label *lb = script_get_label(dp);
+
+			if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
+				fprintf(f, ", attrs=\"%s\"", pa->attrs);
+		}
+		if (fdisk_partition_is_bootable(pa))
+			fprintf(f, ", bootable");
+		fputc('\n', f);
+	}
+
+	DBG(SCRIPT, ul_debugobj(dp, "write script done"));
+	return 0;
+}
+
+static inline int is_header_line(const char *s)
+{
+	const char *p = strchr(s, ':');
+
+	if (!p || p == s || !*(p + 1) || strchr(s, '='))
+		return 0;
+
+	return 1;
+}
+
+/* parses "<name>: value", note modifies @s*/
+static int parse_header_line(struct fdisk_script *dp, char *s)
+{
+	int rc = -EINVAL;
+	char *name, *value;
+
+	DBG(SCRIPT, ul_debugobj(dp, "   parse header '%s'", s));
+
+	if (!s || !*s)
+		return -EINVAL;
+
+	name = s;
+	value = strchr(s, ':');
+	if (!value)
+		goto done;
+	*value = '\0';
+	value++;
+
+	ltrim_whitespace((unsigned char *) name);
+	rtrim_whitespace((unsigned char *) name);
+	ltrim_whitespace((unsigned char *) value);
+	rtrim_whitespace((unsigned char *) value);
+
+	if (strcmp(name, "label") == 0) {
+		if (dp->cxt && !fdisk_get_label(dp->cxt, value))
+			goto done;			/* unknown label name */
+	} else if (strcmp(name, "unit") == 0) {
+		if (strcmp(value, "sectors") != 0)
+			goto done;			/* only "sectors" supported */
+	} else if (strcmp(name, "label-id") == 0
+		   || strcmp(name, "device") == 0) {
+		;					/* whatever is posssible */
+	} else
+		goto done;				/* unknown header */
+
+	if (*name && *value)
+		rc = fdisk_script_set_header(dp, name, value);
+done:
+	if (rc)
+		DBG(SCRIPT, ul_debugobj(dp, "header parse error: "
+				"[rc=%d, name='%s', value='%s']",
+				rc, name, value));
+	return rc;
+
+}
+
+/* returns zero terminated string with next token and @str is updated */
+static char *next_token(char **str)
+{
+	char *tk_begin = NULL,
+	     *tk_end = NULL,
+	     *end = NULL,
+	     *p;
+	int open_quote = 0;
+
+	for (p = *str; p && *p; p++) {
+		if (!tk_begin) {
+			if (isblank(*p))
+				continue;
+			tk_begin = *p == '"' ? p + 1 : p;
+		}
+		if (*p == '"')
+			open_quote ^= 1;
+		if (open_quote)
+			continue;
+		if (isblank(*p) || *p == ',' || *p == ';' || *p == '"' )
+			tk_end = p;
+		else if (*(p + 1) == '\0')
+			tk_end = p + 1;
+		if (tk_begin && tk_end)
+			break;
+	}
+
+	if (!tk_end)
+		return NULL;
+	end = isblank(*tk_end) ? (char *) skip_blank(tk_end) : tk_end;
+	if (*end == ',' || *end == ';')
+		end++;
+
+	*tk_end = '\0';
+	*str = end;
+	return tk_begin;
+}
+
+static int next_number(char **s, uint64_t *num, int *power)
+{
+	char *tk;
+	int rc = -EINVAL;
+
+	assert(num);
+	assert(s);
+
+	tk = next_token(s);
+	if (tk)
+		rc = parse_size(tk, (uintmax_t *) num, power);
+	return rc;
+}
+
+static int next_string(char **s, char **str)
+{
+	char *tk;
+	int rc = -EINVAL;
+
+	assert(s);
+	assert(str);
+
+	tk = next_token(s);
+	if (tk) {
+		*str = strdup(tk);
+		rc = !*str ? -ENOMEM : 0;
+	}
+	return rc;
+}
+
+static int partno_from_devname(char *s)
+{
+	int pno;
+	size_t sz;
+	char *end, *p;
+
+	sz = rtrim_whitespace((unsigned char *)s);
+	p = s + sz - 1;
+
+	while (p > s && isdigit(*(p - 1)))
+		p--;
+
+	errno = 0;
+	pno = strtol(p, &end, 10);
+	if (errno || !end || p == end)
+		return -1;
+	return pno - 1;
+}
+
+/* dump format
+ * <device>: start=<num>, size=<num>, type=<string>, ...
+ */
+static int parse_script_line(struct fdisk_script *dp, char *s)
+{
+	char *p, *x;
+	struct fdisk_partition *pa;
+	int rc = 0;
+	uint64_t num;
+	int pno;
+
+	assert(dp);
+	assert(s);
+
+	DBG(SCRIPT, ul_debugobj(dp, "   parse script line: '%s'", s));
+
+	pa = fdisk_new_partition();
+	if (!pa)
+		return -ENOMEM;
+
+	fdisk_partition_start_follow_default(pa, 1);
+	fdisk_partition_end_follow_default(pa, 1);
+	fdisk_partition_partno_follow_default(pa, 1);
+
+	/* set partno */
+	p = strchr(s, ':');
+	x = strchr(s, '=');
+	if (p && (!x || p < x)) {
+		*p = '\0';
+		p++;
+
+		pno = partno_from_devname(s);
+		if (pno >= 0) {
+			fdisk_partition_partno_follow_default(pa, 0);
+			fdisk_partition_set_partno(pa, pno);
+		}
+	} else
+		p = s;
+
+	while (rc == 0 && p && *p) {
+
+		DBG(SCRIPT, ul_debugobj(dp, " parsing '%s'", p));
+		p = (char *) skip_blank(p);
+
+		if (!strncasecmp(p, "start=", 6)) {
+			p += 6;
+			rc = next_number(&p, &num, NULL);
+			if (!rc) {
+				fdisk_partition_set_start(pa, num);
+				fdisk_partition_start_follow_default(pa, 0);
+			}
+		} else if (!strncasecmp(p, "size=", 5)) {
+			int pow = 0;
+
+			p += 5;
+			rc = next_number(&p, &num, &pow);
+			if (!rc) {
+				if (pow)	/* specified as <num><suffix> */
+					num /= dp->cxt->sector_size;
+				else		/* specified as number of sectors */
+					fdisk_partition_size_explicit(pa, 1);
+				fdisk_partition_set_size(pa, num);
+				fdisk_partition_end_follow_default(pa, 0);
+			}
+
+		} else if (!strncasecmp(p, "bootable", 8)) {
+			char *tk = next_token(&p);
+			if (strcmp(tk, "bootable") == 0)
+				pa->boot = 1;
+			else
+				rc = -EINVAL;
+
+		} else if (!strncasecmp(p, "attrs=", 6)) {
+			p += 6;
+			rc = next_string(&p, &pa->attrs);
+
+		} else if (!strncasecmp(p, "uuid=", 5)) {
+			p += 5;
+			rc = next_string(&p, &pa->uuid);
+
+		} else if (!strncasecmp(p, "name=", 5)) {
+			p += 5;
+			rc = next_string(&p, &pa->name);
+
+		} else if (!strncasecmp(p, "type=", 5) ||
+
+			   !strncasecmp(p, "Id=", 3)) {		/* backward compatiility */
+			char *type;
+
+			p += (*p == 'I' ? 3 : 5);		/* "Id=" or "type=" */
+
+			rc = next_string(&p, &type);
+			if (rc)
+				break;
+			pa->type = fdisk_label_parse_parttype(
+					script_get_label(dp), type);
+			free(type);
+
+			if (!pa->type || fdisk_parttype_is_unknown(pa->type)) {
+				rc = -EINVAL;
+				fdisk_unref_parttype(pa->type);
+				pa->type = NULL;
+				break;
+			}
+
+		} else {
+			DBG(SCRIPT, ul_debugobj(dp, "script parse error: unknown field '%s'", p));
+			rc = -EINVAL;
+			break;
+		}
+	}
+
+	if (!rc)
+		rc = fdisk_table_add_partition(dp->table, pa);
+	if (rc)
+		DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
+
+	fdisk_unref_partition(pa);
+	return rc;
+}
+
+/* original sfdisk supports partition types shortcuts like 'L' = Linux native
+ */
+static struct fdisk_parttype *translate_type_shortcuts(struct fdisk_script *dp, char *str)
+{
+	struct fdisk_label *lb;
+	const char *type = NULL;
+
+	if (strlen(str) != 1)
+		return NULL;
+
+	lb = script_get_label(dp);
+	if (!lb)
+		return NULL;
+
+	if (lb->id == FDISK_DISKLABEL_DOS) {
+		switch (*str) {
+		case 'L':	/* Linux */
+			type = "83";
+			break;
+		case 'S':	/* Swap */
+			type = "82";
+			break;
+		case 'E':	/* Dos extended */
+			type = "05";
+			break;
+		case 'X':	/* Linux extended */
+			type = "85";
+			break;
+		}
+	} else if (lb->id == FDISK_DISKLABEL_GPT) {
+		switch (*str) {
+		case 'L':	/* Linux */
+			type = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
+			break;
+		case 'S':	/* Swap */
+			type = "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F";
+			break;
+		case 'H':	/* Home */
+			type = "933AC7E1-2EB4-4F13-B844-0E14E2AEF915";
+			break;
+		}
+	}
+
+	return type ? fdisk_label_parse_parttype(lb, type) : NULL;
+}
+
+/* simple format:
+ * <start>, <size>, <type>, <bootable>, ...
+ */
+static int parse_commas_line(struct fdisk_script *dp, char *s)
+{
+	int rc = 0;
+	char *p = s, *str;
+	struct fdisk_partition *pa;
+	enum { ITEM_START, ITEM_SIZE, ITEM_TYPE, ITEM_BOOTABLE };
+	int item = -1;
+
+	assert(dp);
+	assert(s);
+
+	pa = fdisk_new_partition();
+	if (!pa)
+		return -ENOMEM;
+
+	fdisk_partition_start_follow_default(pa, 1);
+	fdisk_partition_end_follow_default(pa, 1);
+	fdisk_partition_partno_follow_default(pa, 1);
+
+	while (rc == 0 && p && *p) {
+		uint64_t num;
+		char *begin;
+
+		p = (char *) skip_blank(p);
+		item++;
+
+		DBG(SCRIPT, ul_debugobj(dp, " parsing item %d ('%s')", item, p));
+		begin = p;
+
+		switch (item) {
+		case ITEM_START:
+			if (*p == ',' || *p == ';')
+				fdisk_partition_start_follow_default(pa, 1);
+			else {
+				rc = next_number(&p, &num, NULL);
+				if (!rc)
+					fdisk_partition_set_start(pa, num);
+				fdisk_partition_start_follow_default(pa, 0);
+			}
+			break;
+		case ITEM_SIZE:
+			if (*p == ',' || *p == ';' || *p == '+')
+				fdisk_partition_end_follow_default(pa, 1);
+			else {
+				int pow = 0;
+				rc = next_number(&p, &num, &pow);
+				if (!rc) {
+					if (pow) /* specified as <size><suffix> */
+						num /= dp->cxt->sector_size;
+					else	 /* specified as number of sectors */
+						fdisk_partition_size_explicit(pa, 1);
+					fdisk_partition_set_size(pa, num);
+				}
+				fdisk_partition_end_follow_default(pa, 0);
+			}
+			break;
+		case ITEM_TYPE:
+			if (*p == ',' || *p == ';')
+				break;	/* use default type */
+
+			rc = next_string(&p, &str);
+			if (rc)
+				break;
+
+			pa->type = translate_type_shortcuts(dp, str);
+			if (!pa->type)
+				pa->type = fdisk_label_parse_parttype(
+						script_get_label(dp), str);
+			free(str);
+
+			if (!pa->type || fdisk_parttype_is_unknown(pa->type)) {
+				rc = -EINVAL;
+				fdisk_unref_parttype(pa->type);
+				pa->type = NULL;
+				break;
+			}
+			break;
+		case ITEM_BOOTABLE:
+			if (*p == ',' || *p == ';')
+				break;
+			else {
+				char *tk = next_token(&p);
+				if (tk && *tk == '*' && *(tk + 1) == '\0')
+					pa->boot = 1;
+				else if (tk && *tk == '-' && *(tk + 1) == '\0')
+					pa->boot = 0;
+				else
+					rc = -EINVAL;
+			}
+			break;
+		default:
+			break;
+		}
+
+		if (begin == p)
+			p++;
+	}
+
+	if (!rc)
+		rc = fdisk_table_add_partition(dp->table, pa);
+	if (rc)
+		DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
+
+	fdisk_unref_partition(pa);
+	return rc;
+}
+
+/* modifies @s ! */
+int fdisk_script_read_buffer(struct fdisk_script *dp, char *s)
+{
+	int rc = 0;
+
+	assert(dp);
+	assert(s);
+
+	DBG(SCRIPT, ul_debugobj(dp, "  parsing buffer"));
+
+	s = (char *) skip_blank(s);
+	if (!s || !*s)
+		return 0;	/* nothing baby, ignore */
+
+	if (!dp->table) {
+		dp->table = fdisk_new_table();
+		if (!dp->table)
+			return -ENOMEM;
+	}
+
+	/* parse header lines only if no partition specified yet */
+	if (fdisk_table_is_empty(dp->table) && is_header_line(s))
+		rc = parse_header_line(dp, s);
+
+	/* parse script format */
+	else if (strchr(s, '='))
+		rc = parse_script_line(dp, s);
+
+	/* parse simple <value>, ... format */
+	else
+		rc = parse_commas_line(dp, s);
+
+	if (rc)
+		DBG(SCRIPT, ul_debugobj(dp, "%zu: parse error [rc=%d]",
+				dp->nlines, rc));
+	return rc;
+}
+
+/**
+ * fdisk_script_read_line:
+ * @dp: script
+ * @f: file
+ * @buf: buffer to store one line of the file
+ * @bufsz: buffer size
+ *
+ * Reads next line into dump.
+ *
+ * Returns: 0 on success, <0 on error, 1 when nothing to read.
+ */
+int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz)
+{
+	char *s;
+
+	assert(dp);
+	assert(f);
+
+	DBG(SCRIPT, ul_debugobj(dp, " parsing line %zu", dp->nlines));
+
+	/* read the next non-blank non-comment line */
+	do {
+		if (fgets(buf, bufsz, f) == NULL)
+			return 1;
+		dp->nlines++;
+		s = strchr(buf, '\n');
+		if (!s) {
+			/* Missing final newline?  Otherwise an extremely */
+			/* long line - assume file was corrupted */
+			if (feof(f)) {
+				DBG(SCRIPT, ul_debugobj(dp, "no final newline"));
+				s = strchr(buf, '\0');
+			} else {
+				DBG(SCRIPT, ul_debugobj(dp,
+					"%zu: missing newline at line", dp->nlines));
+				return -EINVAL;
+			}
+		}
+
+		*s = '\0';
+		if (--s >= buf && *s == '\r')
+			*s = '\0';
+		s = (char *) skip_blank(buf);
+	} while (*s == '\0' || *s == '#');
+
+	return fdisk_script_read_buffer(dp, s);
+}
+
+
+/**
+ * fdisk_script_read_file:
+ * @dp: script
+ * @f: input file
+ *
+ * Reads file @f into script @dp.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_script_read_file(struct fdisk_script *dp, FILE *f)
+{
+	char buf[BUFSIZ];
+	int rc;
+
+	assert(dp);
+	assert(f);
+
+	DBG(SCRIPT, ul_debugobj(dp, "parsing file"));
+
+	while (!feof(f)) {
+		rc = fdisk_script_read_line(dp, f, buf, sizeof(buf));
+		if (rc)
+			break;
+	}
+
+	if (rc == 1)
+		rc = 0;		/* end of file */
+
+	DBG(SCRIPT, ul_debugobj(dp, "parsing file done [rc=%d]", rc));
+	return rc;
+}
+
+/**
+ * fdisk_set_script:
+ * @cxt: context
+ * @dp: script (or NULL to remove previous reference)
+ *
+ * Sets reference to the @dp script. The script headers might be used by label
+ * drivers to overwrite built-in defaults (for example disk label Id) and label
+ * driver might optimize the default semantic to be more usable for scripts
+ * (for example to not ask for primary/logical/extended partition type).
+ *
+ * Note that script also contains reference to the fdisk context (see
+ * fdisk_new_script()). This context may be completely independent on
+ * context used for fdisk_set_script().
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp)
+{
+	assert(cxt);
+
+	/* unref old */
+	if (cxt->script)
+		fdisk_unref_script(cxt->script);
+
+	/* ref new */
+	cxt->script = dp;
+	if (cxt->script) {
+		DBG(CXT, ul_debugobj(cxt, "setting reference to script %p", cxt->script));
+		fdisk_ref_script(cxt->script);
+	}
+
+	return 0;
+}
+
+/**
+ * fdisk_get_script:
+ * @cxt: context
+ *
+ * Returns: the current script or NULL.
+ */
+struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	return cxt->script;
+}
+
+/**
+ * fdisk_apply_script_headers:
+ * @cxt: context
+ * @dp: script
+ *
+ * Associte context @cxt with script @dp and creates a new empty disklabel.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp)
+{
+	const char *name;
+
+	assert(cxt);
+	assert(dp);
+
+	DBG(SCRIPT, ul_debugobj(dp, "applying script headers"));
+	fdisk_set_script(cxt, dp);
+
+	/* create empty label */
+	name = fdisk_script_get_header(dp, "label");
+	if (!name)
+		return -EINVAL;
+
+	return fdisk_create_disklabel(cxt, name);
+}
+
+/**
+ * fdisk_apply_script:
+ * @cxt: context
+ * @dp: script
+ *
+ * This function creates a new disklabel and partition within context @cxt. You
+ * have to call fdisk_write_disklabel() to apply changes to the device.
+ *
+ * Returns: 0 on error, <0 on error.
+ */
+int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp)
+{
+	int rc;
+	struct fdisk_script *old;
+
+	assert(dp);
+	assert(cxt);
+
+	DBG(CXT, ul_debugobj(cxt, "applying script %p", dp));
+
+	old = fdisk_get_script(cxt);
+
+	/* create empty disk label */
+	rc = fdisk_apply_script_headers(cxt, dp);
+
+	/* create partitions */
+	if (!rc && dp->table)
+		rc = fdisk_apply_table(cxt, dp->table);
+
+	fdisk_set_script(cxt, old);
+	DBG(CXT, ul_debugobj(cxt, "script done [rc=%d]", rc));
+	return rc;
+}
+
+#ifdef TEST_PROGRAM
+int test_dump(struct fdisk_test *ts, int argc, char *argv[])
+{
+	char *devname = argv[1];
+	struct fdisk_context *cxt;
+	struct fdisk_script *dp;
+
+	cxt = fdisk_new_context();
+	fdisk_assign_device(cxt, devname, 1);
+
+	dp = fdisk_new_script(cxt);
+	fdisk_script_read_context(dp, NULL);
+
+	fdisk_script_write_file(dp, stdout);
+	fdisk_unref_script(dp);
+	fdisk_unref_context(cxt);
+
+	return 0;
+}
+
+int test_read(struct fdisk_test *ts, int argc, char *argv[])
+{
+	char *filename = argv[1];
+	struct fdisk_script *dp;
+	struct fdisk_context *cxt;
+	FILE *f;
+
+	if (!(f = fopen(filename, "r")))
+		err(EXIT_FAILURE, "%s: cannot open", filename);
+
+	cxt = fdisk_new_context();
+	dp = fdisk_new_script(cxt);
+
+	fdisk_script_read_file(dp, f);
+	fclose(f);
+
+	fdisk_script_write_file(dp, stdout);
+	fdisk_unref_script(dp);
+	fdisk_unref_context(cxt);
+
+	return 0;
+}
+
+int test_stdin(struct fdisk_test *ts, int argc, char *argv[])
+{
+	char buf[BUFSIZ];
+	struct fdisk_script *dp;
+	struct fdisk_context *cxt;
+	int rc = 0;
+
+	cxt = fdisk_new_context();
+	dp = fdisk_new_script(cxt);
+	fdisk_script_set_header(dp, "label", "dos");
+
+	printf("<start>, <size>, <type>, <bootable: *|->\n");
+	do {
+		struct fdisk_partition *pa;
+		size_t n = fdisk_table_get_nents(dp->table);
+
+		printf(" #%zu :\n", n + 1);
+		rc = fdisk_script_read_line(dp, stdin, buf, sizeof(buf));
+
+		if (rc == 0) {
+			pa = fdisk_table_get_partition(dp->table, n);
+			printf(" #%zu  %12ju %12ju\n",	n + 1,
+						fdisk_partition_get_start(pa),
+						fdisk_partition_get_size(pa));
+		}
+	} while (rc == 0);
+
+	if (!rc)
+		fdisk_script_write_file(dp, stdout);
+	fdisk_unref_script(dp);
+	fdisk_unref_context(cxt);
+
+	return rc;
+}
+
+int test_apply(struct fdisk_test *ts, int argc, char *argv[])
+{
+	char *devname = argv[1], *scriptname = argv[2];
+	struct fdisk_context *cxt;
+	struct fdisk_script *dp = NULL;
+	struct fdisk_table *tb = NULL;
+	struct fdisk_iter *itr = NULL;
+	struct fdisk_partition *pa = NULL;
+	int rc;
+
+	cxt = fdisk_new_context();
+	fdisk_assign_device(cxt, devname, 0);
+
+	dp = fdisk_new_script_from_file(cxt, scriptname);
+	if (!dp)
+		return -errno;
+
+	rc = fdisk_apply_script(cxt, dp);
+	if (rc)
+		goto done;
+	fdisk_unref_script(dp);
+
+	/* list result */
+	fdisk_list_disklabel(cxt);
+	fdisk_get_partitions(cxt, &tb);
+
+	itr = fdisk_new_iter(FDISK_ITER_FORWARD);
+	while (fdisk_table_next_partition(tb, itr, &pa) == 0) {
+		printf(" #%zu  %12ju %12ju\n",	fdisk_partition_get_partno(pa),
+						fdisk_partition_get_start(pa),
+						fdisk_partition_get_size(pa));
+	}
+
+done:
+	fdisk_free_iter(itr);
+	fdisk_unref_table(tb);
+
+	/*fdisk_write_disklabel(cxt);*/
+	fdisk_unref_context(cxt);
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	struct fdisk_test tss[] = {
+	{ "--dump",    test_dump,    "<device>            dump PT as script" },
+	{ "--read",    test_read,    "<file>              read PT script from file" },
+	{ "--apply",   test_apply,   "<device> <file>     try apply script from file to device" },
+	{ "--stdin",   test_stdin,   "                    read input like sfdisk" },
+	{ NULL }
+	};
+
+	return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libblkid/libfdisk/src/sgi.c b/libblkid/libfdisk/src/sgi.c
new file mode 100644
index 0000000..cd4cedf
--- /dev/null
+++ b/libblkid/libfdisk/src/sgi.c
@@ -0,0 +1,1185 @@
+/*
+ *
+ * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org>
+ *               2013 Karel Zak <kzak@redhat.com>
+ *
+ * This is a re-written version for libfdisk, the original was fdisksgilabel.c
+ * from util-linux fdisk, by:
+ *
+ *               Andreas Neuper, Sep 1998,
+ *               Arnaldo Carvalho de Melo <acme@conectiva.com.br>, Mar 1999,
+ *               Phillip Kesling <pkesling@sgi.com>, Mar 2003.
+ */
+
+#include "c.h"
+#include "nls.h"
+#include "all-io.h"
+
+#include "blkdev.h"
+
+#include "bitops.h"
+#include "pt-sgi.h"
+#include "pt-mbr.h"
+#include "fdiskP.h"
+
+/**
+ * SECTION: sgi
+ * @title: SGI
+ * @short_description: disk label specific functions
+ *
+ */
+
+/*
+ * in-memory fdisk SGI stuff
+ */
+struct fdisk_sgi_label {
+	struct fdisk_label	head;		/* generic fdisk part */
+	struct sgi_disklabel	*header;	/* on-disk data (pointer to cxt->firstsector) */
+
+	struct sgi_freeblocks {
+		unsigned int first;
+		unsigned int last;
+	} freelist[SGI_MAXPARTITIONS + 1];
+};
+
+static struct fdisk_parttype sgi_parttypes[] =
+{
+	{SGI_TYPE_VOLHDR,	N_("SGI volhdr")},
+	{SGI_TYPE_TRKREPL,	N_("SGI trkrepl")},
+	{SGI_TYPE_SECREPL,	N_("SGI secrepl")},
+	{SGI_TYPE_SWAP,		N_("SGI raw")},
+	{SGI_TYPE_BSD,		N_("SGI bsd")},
+	{SGI_TYPE_SYSV,		N_("SGI sysv")},
+	{SGI_TYPE_ENTIRE_DISK,	N_("SGI volume")},
+	{SGI_TYPE_EFS,		N_("SGI efs")},
+	{SGI_TYPE_LVOL,		N_("SGI lvol")},
+	{SGI_TYPE_RLVOL,	N_("SGI rlvol")},
+	{SGI_TYPE_XFS,		N_("SGI xfs")},
+	{SGI_TYPE_XFSLOG,	N_("SGI xfslog")},
+	{SGI_TYPE_XLV,		N_("SGI xlv")},
+	{SGI_TYPE_XVM,		N_("SGI xvm")},
+	{MBR_LINUX_SWAP_PARTITION, N_("Linux swap")},
+	{MBR_LINUX_DATA_PARTITION, N_("Linux native")},
+	{MBR_LINUX_LVM_PARTITION, N_("Linux LVM")},
+	{MBR_LINUX_RAID_PARTITION, N_("Linux RAID")},
+	{0, NULL }
+};
+
+static unsigned int sgi_get_start_sector(struct fdisk_context *cxt, int i );
+static unsigned int sgi_get_num_sectors(struct fdisk_context *cxt, int i );
+static int sgi_get_bootpartition(struct fdisk_context *cxt);
+static int sgi_get_swappartition(struct fdisk_context *cxt);
+
+/* Returns a pointer buffer with on-disk data. */
+static inline struct sgi_disklabel *self_disklabel(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SGI));
+
+	return ((struct fdisk_sgi_label *) cxt->label)->header;
+}
+
+/* Returns in-memory fdisk data. */
+static inline struct fdisk_sgi_label *self_label(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SGI));
+
+	return (struct fdisk_sgi_label *) cxt->label;
+}
+
+/*
+ * Information within second on-disk block
+ */
+#define	SGI_INFO_MAGIC		0x00072959
+
+struct sgi_info {
+	unsigned int   magic;		/* looks like a magic number */
+	unsigned int   a2;
+	unsigned int   a3;
+	unsigned int   a4;
+	unsigned int   b1;
+	unsigned short b2;
+	unsigned short b3;
+	unsigned int   c[16];
+	unsigned short d[3];
+	unsigned char  scsi_string[50];
+	unsigned char  serial[137];
+	unsigned short check1816;
+	unsigned char  installer[225];
+};
+
+static struct sgi_info *sgi_new_info(void)
+{
+	struct sgi_info *info = calloc(1, sizeof(struct sgi_info));
+
+	if (!info)
+		return NULL;
+
+	info->magic = cpu_to_be32(SGI_INFO_MAGIC);
+	info->b1 = cpu_to_be32(-1);
+	info->b2 = cpu_to_be16(-1);
+	info->b3 = cpu_to_be16(1);
+
+	/* You may want to replace this string !!!!!!! */
+	strcpy((char *) info->scsi_string, "IBM OEM 0662S12         3 30");
+	strcpy((char *) info->serial, "0000");
+	info->check1816 = cpu_to_be16(18 * 256 + 16);
+	strcpy((char *) info->installer, "Sfx version 5.3, Oct 18, 1994");
+
+	return info;
+}
+
+static void sgi_free_info(struct sgi_info *info)
+{
+	free(info);
+}
+
+/**
+ * fdisk_sgi_create_info:
+ * @cxt: context
+ *
+ * This function add hint about SGI label (e.g. set "sgilabel" as volume name)
+ * to the first SGI volume. This is probably old SGI convention without any
+ * effect to the device partitioning.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_sgi_create_info(struct fdisk_context *cxt)
+{
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+
+	/* I keep SGI's habit to write the sgilabel to the second block */
+	sgilabel->volume[0].block_num = cpu_to_be32(2);
+	sgilabel->volume[0].num_bytes = cpu_to_be32(sizeof(struct sgi_info));
+	strncpy((char *) sgilabel->volume[0].name, "sgilabel", 8);
+
+	fdisk_info(cxt, _("SGI info created on second sector."));
+	return 0;
+}
+
+
+/*
+ * only dealing with free blocks here
+ */
+static void set_freelist(struct fdisk_context *cxt,
+		size_t i, unsigned int f, unsigned int l)
+{
+	struct fdisk_sgi_label *sgi = self_label(cxt);
+
+	if (i < ARRAY_SIZE(sgi->freelist)) {
+		sgi->freelist[i].first = f;
+		sgi->freelist[i].last = l;
+	}
+}
+
+static void add_to_freelist(struct fdisk_context *cxt,
+		unsigned int f, unsigned int l)
+{
+	struct fdisk_sgi_label *sgi = self_label(cxt);
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(sgi->freelist); i++) {
+		if (sgi->freelist[i].last == 0)
+			break;
+	}
+	set_freelist(cxt, i, f, l);
+}
+
+static void clear_freelist(struct fdisk_context *cxt)
+{
+	struct fdisk_sgi_label *sgi = self_label(cxt);
+
+	memset(sgi->freelist, 0, sizeof(sgi->freelist));
+}
+
+static unsigned int is_in_freelist(struct fdisk_context *cxt, unsigned int b)
+{
+	struct fdisk_sgi_label *sgi = self_label(cxt);
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(sgi->freelist); i++) {
+		if (sgi->freelist[i].first <= b
+		    && sgi->freelist[i].last >= b)
+			return sgi->freelist[i].last;
+	}
+
+	return 0;
+}
+
+
+static int sgi_get_nsect(struct fdisk_context *cxt)
+{
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+	return be16_to_cpu(sgilabel->devparam.nsect);
+}
+
+static int sgi_get_ntrks(struct fdisk_context *cxt)
+{
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+	return be16_to_cpu(sgilabel->devparam.ntrks);
+}
+
+static size_t count_used_partitions(struct fdisk_context *cxt)
+{
+	size_t i, ct = 0;
+
+	for (i = 0; i < cxt->label->nparts_max; i++)
+		ct += sgi_get_num_sectors(cxt, i) > 0;
+
+	return ct;
+}
+
+static int sgi_probe_label(struct fdisk_context *cxt)
+{
+	struct fdisk_sgi_label *sgi;
+	struct sgi_disklabel *sgilabel;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SGI));
+	assert(sizeof(struct sgi_disklabel) <= 512);
+
+	/* map first sector to header */
+	sgi = (struct fdisk_sgi_label *) cxt->label;
+	sgi->header = (struct sgi_disklabel *) cxt->firstsector;
+	sgilabel = sgi->header;
+
+	if (be32_to_cpu(sgilabel->magic) != SGI_LABEL_MAGIC) {
+		sgi->header = NULL;
+		return 0;
+	}
+
+	/*
+	 * test for correct checksum
+	 */
+	if (sgi_pt_checksum(sgilabel) != 0)
+		fdisk_warnx(cxt, _("Detected an SGI disklabel with wrong checksum."));
+
+	clear_freelist(cxt);
+	cxt->label->nparts_max = SGI_MAXPARTITIONS;
+	cxt->label->nparts_cur = count_used_partitions(cxt);
+	return 1;
+}
+
+static int sgi_list_table(struct fdisk_context *cxt)
+{
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+	struct sgi_device_parameter *sgiparam = &sgilabel->devparam;
+	int rc = 0;
+
+	if (fdisk_is_details(cxt))
+		fdisk_info(cxt, _(
+			"Label geometry: %d heads, %llu sectors\n"
+			"                %llu cylinders, %d physical cylinders\n"
+			"                %d extra sects/cyl, interleave %d:1\n"),
+			cxt->geom.heads, cxt->geom.sectors,
+			cxt->geom.cylinders, be16_to_cpu(sgiparam->pcylcount),
+			(int) sgiparam->sparecyl, be16_to_cpu(sgiparam->ilfact));
+
+	fdisk_info(cxt, _("Bootfile: %s"), sgilabel->boot_file);
+	return rc;
+}
+
+static unsigned int sgi_get_start_sector(struct fdisk_context *cxt, int i)
+{
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+	return be32_to_cpu(sgilabel->partitions[i].first_block);
+}
+
+static unsigned int sgi_get_num_sectors(struct fdisk_context *cxt, int i)
+{
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+	return be32_to_cpu(sgilabel->partitions[i].num_blocks);
+}
+
+static int sgi_get_sysid(struct fdisk_context *cxt, int i)
+{
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+	return be32_to_cpu(sgilabel->partitions[i].type);
+}
+
+static int sgi_get_bootpartition(struct fdisk_context *cxt)
+{
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+	return be16_to_cpu(sgilabel->root_part_num);
+}
+
+static int sgi_get_swappartition(struct fdisk_context *cxt)
+{
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+	return be16_to_cpu(sgilabel->swap_part_num);
+}
+
+static unsigned int sgi_get_lastblock(struct fdisk_context *cxt)
+{
+	return cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders;
+}
+
+static struct fdisk_parttype *sgi_get_parttype(struct fdisk_context *cxt, size_t n)
+{
+	struct fdisk_parttype *t;
+
+	if (n >= cxt->label->nparts_max)
+		return NULL;
+
+	t = fdisk_label_get_parttype_from_code(cxt->label, sgi_get_sysid(cxt, n));
+	return t ? : fdisk_new_unknown_parttype(sgi_get_sysid(cxt, n), NULL);
+}
+
+/* fdisk_get_partition() backend */
+static int sgi_get_partition(struct fdisk_context *cxt, size_t n, struct fdisk_partition *pa)
+{
+	fdisk_sector_t start, len;
+
+	pa->used = sgi_get_num_sectors(cxt, n) > 0;
+	if (!pa->used)
+		return 0;
+
+	start = sgi_get_start_sector(cxt, n);
+	len = sgi_get_num_sectors(cxt, n);
+
+	pa->type = sgi_get_parttype(cxt, n);
+	pa->size = len;
+	pa->start = start;
+
+	if (pa->type && pa->type->code == SGI_TYPE_ENTIRE_DISK)
+		pa->wholedisk = 1;
+
+	pa->attrs = sgi_get_swappartition(cxt) == (int) n ? "swap" :
+		    sgi_get_bootpartition(cxt) == (int) n ? "boot" : NULL;
+	if (pa->attrs)
+		pa->attrs = strdup(pa->attrs);
+
+	return 0;
+}
+
+
+static int sgi_check_bootfile(struct fdisk_context *cxt, const char *name)
+{
+	size_t sz;
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+
+	sz = strlen(name);
+
+	if (sz < 3) {
+		/* "/a\n" is minimum */
+		fdisk_warnx(cxt, _("Invalid bootfile!  The bootfile must "
+				   "be an absolute non-zero pathname, "
+				   "e.g. \"/unix\" or \"/unix.save\"."));
+		return -EINVAL;
+
+	} else if (sz > sizeof(sgilabel->boot_file)) {
+		fdisk_warnx(cxt, P_("Name of bootfile is too long: %zu byte maximum.",
+				    "Name of bootfile is too long: %zu bytes maximum.",
+				    sizeof(sgilabel->boot_file)),
+			    sizeof(sgilabel->boot_file));
+		return -EINVAL;
+
+	} else if (*name != '/') {
+		fdisk_warnx(cxt, _("Bootfile must have a fully qualified pathname."));
+		return -EINVAL;
+	}
+
+	if (strncmp(name, (char *) sgilabel->boot_file,
+				sizeof(sgilabel->boot_file))) {
+		fdisk_warnx(cxt, _("Be aware that the bootfile is not checked "
+				   "for existence.  SGI's default is \"/unix\", "
+				   "and for backup \"/unix.save\"."));
+		return 0;	/* filename is correct and did change */
+	}
+
+	return 1;	/* filename did not change */
+}
+
+/**
+ * fdisk_sgi_set_bootfile:
+ * @cxt: context
+ *
+ * Allows to set SGI boot file. The function uses Ask API for dialog with
+ * user.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_sgi_set_bootfile(struct fdisk_context *cxt)
+{
+	int rc = 0;
+	size_t sz;
+	char *name = NULL;
+	struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+
+	fdisk_info(cxt, _("The current boot file is: %s"), sgilabel->boot_file);
+
+	rc = fdisk_ask_string(cxt, _("Enter of the new boot file"), &name);
+	if (rc == 0)
+		rc = sgi_check_bootfile(cxt, name);
+	if (rc) {
+		if (rc == 1)
+			fdisk_info(cxt, _("Boot file is unchanged."));
+		goto done;
+	}
+
+	memset(sgilabel->boot_file, 0, sizeof(sgilabel->boot_file));
+	sz = strlen(name);
+
+	assert(sz <= sizeof(sgilabel->boot_file));	/* see sgi_check_bootfile() */
+
+	memcpy(sgilabel->boot_file, name, sz);
+
+	fdisk_info(cxt, _("Bootfile has been changed to \"%s\"."), name);
+done:
+	free(name);
+	return rc;
+}
+
+static int sgi_write_disklabel(struct fdisk_context *cxt)
+{
+	struct sgi_disklabel *sgilabel;
+	struct sgi_info *info = NULL;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SGI));
+
+	sgilabel = self_disklabel(cxt);
+	sgilabel->csum = 0;
+	sgilabel->csum = cpu_to_be32(sgi_pt_checksum(sgilabel));
+
+	assert(sgi_pt_checksum(sgilabel) == 0);
+
+	if (lseek(cxt->dev_fd, 0, SEEK_SET) < 0)
+		goto err;
+	if (write_all(cxt->dev_fd, sgilabel, DEFAULT_SECTOR_SIZE))
+		goto err;
+	if (!strncmp((char *) sgilabel->volume[0].name, "sgilabel", 8)) {
+		/*
+		 * Keep this habit of first writing the "sgilabel".
+		 * I never tested whether it works without. (AN 1998-10-02)
+		 */
+		int infostartblock
+			= be32_to_cpu(sgilabel->volume[0].block_num);
+
+		if (lseek(cxt->dev_fd, (off_t) infostartblock *
+					DEFAULT_SECTOR_SIZE, SEEK_SET) < 0)
+			goto err;
+		info = sgi_new_info();
+		if (!info)
+			goto err;
+		if (write_all(cxt->dev_fd, info, sizeof(*info)))
+			goto err;
+	}
+
+	sgi_free_info(info);
+	return 0;
+err:
+	sgi_free_info(info);
+	return -errno;
+}
+
+static int compare_start(struct fdisk_context *cxt,
+			 const void *x, const void *y)
+{
+	/*
+	 * Sort according to start sectors and prefer the largest partition:
+	 * entry zero is the entire-disk entry.
+	 */
+	unsigned int i = *(int *) x;
+	unsigned int j = *(int *) y;
+	unsigned int a = sgi_get_start_sector(cxt, i);
+	unsigned int b = sgi_get_start_sector(cxt, j);
+	unsigned int c = sgi_get_num_sectors(cxt, i);
+	unsigned int d = sgi_get_num_sectors(cxt, j);
+
+	if (a == b)
+		return (d > c) ? 1 : (d == c) ? 0 : -1;
+	return (a > b) ? 1 : -1;
+}
+
+static void generic_swap(void *a0, void *b0, int size)
+{
+	char *a = a0, *b = b0;
+
+	for (; size > 0; --size, a++, b++) {
+		char t = *a;
+		*a = *b;
+		*b = t;
+	}
+}
+
+
+/* heap sort, based on Matt Mackall's linux kernel version */
+static void sort(void *base0, size_t num, size_t size, struct fdisk_context *cxt,
+		 int (*cmp_func)(struct fdisk_context *, const void *, const void *))
+{
+	/* pre-scale counters for performance */
+	int i = (num/2 - 1) * size;
+	size_t n = num * size, c, r;
+	char *base = base0;
+
+	/* heapify */
+	for ( ; i >= 0; i -= size) {
+		for (r = i; r * 2 + size < n; r  = c) {
+			c = r * 2 + size;
+			if (c < n - size &&
+			    cmp_func(cxt, base + c, base + c + size) < 0)
+				c += size;
+			if (cmp_func(cxt, base + r, base + c) >= 0)
+				break;
+			generic_swap(base + r, base + c, size);
+		}
+	}
+
+	/* sort */
+	for (i = n - size; i > 0; i -= size) {
+		generic_swap(base, base + i, size);
+		for (r = 0; r * 2 + size < (size_t) i; r = c) {
+			c = r * 2 + size;
+			if (c < i - size &&
+			    cmp_func(cxt, base + c, base + c + size) < 0)
+				c += size;
+			if (cmp_func(cxt, base + r, base + c) >= 0)
+				break;
+			generic_swap(base + r, base + c, size);
+		}
+	}
+}
+
+static int verify_disklabel(struct fdisk_context *cxt, int verbose)
+{
+	int Index[SGI_MAXPARTITIONS];	/* list of valid partitions */
+	int sortcount = 0;		/* number of used partitions, i.e. non-zero lengths */
+	int entire = 0, i = 0;
+	unsigned int start = 0;
+	long long gap = 0;	/* count unused blocks */
+	unsigned int lastblock = sgi_get_lastblock(cxt);
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SGI));
+
+	clear_freelist(cxt);
+	memset(Index, 0, sizeof(Index));
+
+	for (i=0; i < SGI_MAXPARTITIONS; i++) {
+		if (sgi_get_num_sectors(cxt, i) != 0) {
+			Index[sortcount++] = i;
+			if (sgi_get_sysid(cxt, i) == SGI_TYPE_ENTIRE_DISK
+			    && entire++ == 1) {
+				if (verbose)
+					fdisk_info(cxt, _("More than one entire "
+						"disk entry present."));
+			}
+		}
+	}
+	if (sortcount == 0) {
+		if (verbose)
+			fdisk_info(cxt, _("No partitions defined."));
+		if (lastblock)
+			add_to_freelist(cxt, 0, lastblock);
+		return (lastblock > 0) ? 1 : (lastblock == 0) ? 0 : -1;
+	}
+
+	sort(Index, sortcount, sizeof(Index[0]), cxt, compare_start);
+
+	if (sgi_get_sysid(cxt, Index[0]) == SGI_TYPE_ENTIRE_DISK) {
+		if (verbose && Index[0] != 10)
+			fdisk_info(cxt, _("IRIX likes it when partition 11 "
+					  "covers the entire disk."));
+
+		if (verbose && sgi_get_start_sector(cxt, Index[0]) != 0)
+			fdisk_info(cxt, _("The entire disk partition should "
+					  "start at block 0, not at block %d."),
+				   sgi_get_start_sector(cxt, Index[0]));
+
+		if (verbose && sgi_get_num_sectors(cxt, Index[0]) != lastblock)
+			DBG(LABEL, ul_debug(
+				"entire disk partition=%ds, but disk=%ds",
+				sgi_get_num_sectors(cxt, Index[0]),
+				lastblock));
+		lastblock = sgi_get_num_sectors(cxt, Index[0]);
+	} else if (verbose) {
+		fdisk_info(cxt, _("Partition 11 should cover the entire disk."));
+		DBG(LABEL, ul_debug("sysid=%d\tpartition=%d",
+			       sgi_get_sysid(cxt, Index[0]), Index[0]+1));
+	}
+	for (i=1, start=0; i<sortcount; i++) {
+		int cylsize = sgi_get_nsect(cxt) * sgi_get_ntrks(cxt);
+
+		if (verbose && cylsize
+		    && (sgi_get_start_sector(cxt, Index[i]) % cylsize) != 0)
+			DBG(LABEL, ul_debug("partition %d does not start on "
+					"cylinder boundary.", Index[i]+1));
+
+		if (verbose && cylsize
+		    && sgi_get_num_sectors(cxt, Index[i]) % cylsize != 0)
+			DBG(LABEL, ul_debug("partition %d does not end on "
+					"cylinder boundary.", Index[i]+1));
+
+		/* We cannot handle several "entire disk" entries. */
+		if (sgi_get_sysid(cxt, Index[i]) == SGI_TYPE_ENTIRE_DISK)
+			continue;
+
+		if (start > sgi_get_start_sector(cxt, Index[i])) {
+			if (verbose)
+				fdisk_info(cxt,
+					   P_("Partitions %d and %d overlap by %d sector.",
+					      "Partitions %d and %d overlap by %d sectors.",
+					      start - sgi_get_start_sector(cxt, Index[i])),
+					   Index[i-1]+1, Index[i]+1,
+					   start - sgi_get_start_sector(cxt, Index[i]));
+			if (gap >  0) gap = -gap;
+			if (gap == 0) gap = -1;
+		}
+		if (start < sgi_get_start_sector(cxt, Index[i])) {
+			if (verbose)
+				fdisk_info(cxt,
+					   P_("Unused gap of %8u sector: sector %8u",
+					      "Unused gap of %8u sectors: sectors %8u-%u",
+					      sgi_get_start_sector(cxt, Index[i]) - start),
+					   sgi_get_start_sector(cxt, Index[i]) - start,
+					   start, sgi_get_start_sector(cxt, Index[i])-1);
+			gap += sgi_get_start_sector(cxt, Index[i]) - start;
+			add_to_freelist(cxt, start,
+					sgi_get_start_sector(cxt, Index[i]));
+		}
+		start = sgi_get_start_sector(cxt, Index[i])
+			+ sgi_get_num_sectors(cxt, Index[i]);
+		/* Align free space on cylinder boundary. */
+		if (cylsize && start % cylsize)
+			start += cylsize - (start % cylsize);
+
+		DBG(LABEL, ul_debug("%2d:%12d\t%12d\t%12d", Index[i],
+				       sgi_get_start_sector(cxt, Index[i]),
+				       sgi_get_num_sectors(cxt, Index[i]),
+				       sgi_get_sysid(cxt, Index[i])));
+	}
+	if (start < lastblock) {
+		if (verbose)
+			fdisk_info(cxt, P_("Unused gap of %8u sector: sector %8u",
+					   "Unused gap of %8u sectors: sectors %8u-%u",
+					   lastblock - start),
+				   lastblock - start, start, lastblock-1);
+		gap += lastblock - start;
+		add_to_freelist(cxt, start, lastblock);
+	}
+	/*
+	 * Done with arithmetics. Go for details now.
+	 */
+	if (verbose) {
+		if (sgi_get_bootpartition(cxt) < 0
+		    || !sgi_get_num_sectors(cxt, sgi_get_bootpartition(cxt)))
+			fdisk_info(cxt, _("The boot partition does not exist."));
+
+		if (sgi_get_swappartition(cxt) < 0
+		    || !sgi_get_num_sectors(cxt, sgi_get_swappartition(cxt)))
+			fdisk_info(cxt, _("The swap partition does not exist."));
+
+		else if (sgi_get_sysid(cxt, sgi_get_swappartition(cxt)) != SGI_TYPE_SWAP
+		    && sgi_get_sysid(cxt, sgi_get_swappartition(cxt)) != MBR_LINUX_SWAP_PARTITION)
+			fdisk_info(cxt, _("The swap partition has no swap type."));
+
+		if (sgi_check_bootfile(cxt, "/unix"))
+			fdisk_info(cxt, _("You have chosen an unusual bootfile name."));
+	}
+
+	return (gap > 0) ? 1 : (gap == 0) ? 0 : -1;
+}
+
+static int sgi_verify_disklabel(struct fdisk_context *cxt)
+{
+	return verify_disklabel(cxt, 1);
+}
+
+static int sgi_gaps(struct fdisk_context *cxt)
+{
+	/*
+	 * returned value is:
+	 *  = 0 : disk is properly filled to the rim
+	 *  < 0 : there is an overlap
+	 *  > 0 : there is still some vacant space
+	 */
+	return verify_disklabel(cxt, 0);
+}
+
+/* Returns partition index of first entry marked as entire disk. */
+static int sgi_entire(struct fdisk_context *cxt)
+{
+	size_t i;
+
+	for (i = 0; i < SGI_MAXPARTITIONS; i++)
+		if (sgi_get_sysid(cxt, i) == SGI_TYPE_ENTIRE_DISK)
+			return i;
+	return -1;
+}
+
+static int set_partition(struct fdisk_context *cxt, size_t i,
+			     unsigned int start, unsigned int length, int sys)
+{
+	struct sgi_disklabel *sgilabel;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SGI));
+
+	sgilabel = self_disklabel(cxt);
+	sgilabel->partitions[i].type = cpu_to_be32(sys);
+	sgilabel->partitions[i].num_blocks = cpu_to_be32(length);
+	sgilabel->partitions[i].first_block = cpu_to_be32(start);
+
+	fdisk_label_set_changed(cxt->label, 1);
+
+	if (sgi_gaps(cxt) < 0)	/* rebuild freelist */
+		fdisk_warnx(cxt, _("Partition overlap on the disk."));
+	if (length) {
+		struct fdisk_parttype *t =
+				fdisk_label_get_parttype_from_code(cxt->label, sys);
+		fdisk_info_new_partition(cxt, i + 1, start, start + length, t);
+	}
+
+	return 0;
+}
+
+static void sgi_set_entire(struct fdisk_context *cxt)
+{
+	size_t n;
+
+	for (n = 10; n < cxt->label->nparts_max; n++) {
+		if (!sgi_get_num_sectors(cxt, n)) {
+			set_partition(cxt, n, 0, sgi_get_lastblock(cxt), SGI_TYPE_ENTIRE_DISK);
+			break;
+		}
+	}
+}
+
+static void sgi_set_volhdr(struct fdisk_context *cxt)
+{
+	size_t n;
+
+	for (n = 8; n < cxt->label->nparts_max; n++) {
+		if (!sgi_get_num_sectors(cxt, n)) {
+			/* Choose same default volume header size as IRIX fx uses. */
+			if (4096 < sgi_get_lastblock(cxt))
+				set_partition(cxt, n, 0, 4096, SGI_TYPE_VOLHDR);
+			break;
+		}
+	}
+}
+
+static int sgi_delete_partition(struct fdisk_context *cxt, size_t partnum)
+{
+	int rc;
+
+	assert(cxt);
+	assert(cxt->label);
+
+	if (partnum > cxt->label->nparts_max)
+		return -EINVAL;
+
+	rc = set_partition(cxt, partnum, 0, 0, 0);
+
+	cxt->label->nparts_cur = count_used_partitions(cxt);
+
+	return rc;
+}
+
+static int sgi_add_partition(struct fdisk_context *cxt,
+			     struct fdisk_partition *pa,
+			     size_t *partno)
+{
+	struct fdisk_sgi_label *sgi;
+	char mesg[256];
+	unsigned int first = 0, last = 0;
+	struct fdisk_ask *ask;
+	int sys = pa && pa->type ? pa->type->code : SGI_TYPE_XFS;
+	int rc;
+	size_t n;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SGI));
+
+	rc = fdisk_partition_next_partno(pa, cxt, &n);
+	if (rc)
+		return rc;
+	if (n == 10)
+		sys = SGI_TYPE_ENTIRE_DISK;
+	else if (n == 8)
+		sys = 0;
+
+	sgi = self_label(cxt);
+
+	if (sgi_get_num_sectors(cxt, n)) {
+		fdisk_warnx(cxt, _("Partition %zu is already defined.  "
+				   "Delete it before re-adding it."), n + 1);
+		return -EINVAL;
+	}
+	if (!cxt->script && sgi_entire(cxt) == -1 &&  sys != SGI_TYPE_ENTIRE_DISK) {
+		fdisk_info(cxt, _("Attempting to generate entire disk entry automatically."));
+		sgi_set_entire(cxt);
+		sgi_set_volhdr(cxt);
+	}
+	if (sgi_gaps(cxt) == 0 && sys != SGI_TYPE_ENTIRE_DISK) {
+		fdisk_warnx(cxt, _("The entire disk is already covered with partitions."));
+		return -EINVAL;
+	}
+	if (sgi_gaps(cxt) < 0) {
+		fdisk_warnx(cxt, _("You got a partition overlap on the disk. Fix it first!"));
+		return -EINVAL;
+	}
+
+	if (sys == SGI_TYPE_ENTIRE_DISK) {
+		first = 0;
+		last = sgi_get_lastblock(cxt);
+	} else {
+		first = sgi->freelist[0].first;
+		last  = sgi->freelist[0].last;
+	}
+
+	/* first sector */
+	if (pa && pa->start_follow_default)
+		;
+	else if (pa && fdisk_partition_has_start(pa)) {
+		first = pa->start;
+		last = is_in_freelist(cxt, first);
+
+		if (sys != SGI_TYPE_ENTIRE_DISK && !last)
+			return -ERANGE;
+	} else {
+		snprintf(mesg, sizeof(mesg), _("First %s"),
+				fdisk_get_unit(cxt, FDISK_SINGULAR));
+		ask = fdisk_new_ask();
+		if (!ask)
+			return -ENOMEM;
+
+		fdisk_ask_set_query(ask, mesg);
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+
+		fdisk_ask_number_set_low(ask,     fdisk_scround(cxt, first));	/* minimal */
+		fdisk_ask_number_set_default(ask, fdisk_scround(cxt, first));	/* default */
+		fdisk_ask_number_set_high(ask,    fdisk_scround(cxt, last) - 1); /* maximal */
+
+		rc = fdisk_do_ask(cxt, ask);
+		first = fdisk_ask_number_get_result(ask);
+		fdisk_unref_ask(ask);
+
+		if (rc)
+			return rc;
+		if (fdisk_use_cylinders(cxt))
+			first *= fdisk_get_units_per_sector(cxt);
+	}
+
+	if (first && sys == SGI_TYPE_ENTIRE_DISK)
+		fdisk_info(cxt, _("It is highly recommended that the "
+				  "eleventh partition covers the entire "
+				  "disk and is of type 'SGI volume'."));
+	if (!last)
+		last = is_in_freelist(cxt, first);
+
+	/* last sector */
+	if (pa && pa->end_follow_default)
+		last -= 1ULL;
+	else if (pa && fdisk_partition_has_size(pa)) {
+		if (first + pa->size - 1ULL > last)
+			return -ERANGE;
+		last = first + pa->size - 1ULL;
+	} else {
+		snprintf(mesg, sizeof(mesg),
+			 _("Last %s or +%s or +size{K,M,G,T,P}"),
+			 fdisk_get_unit(cxt, FDISK_SINGULAR),
+			 fdisk_get_unit(cxt, FDISK_PLURAL));
+
+		ask = fdisk_new_ask();
+		if (!ask)
+			return -ENOMEM;
+
+		fdisk_ask_set_query(ask, mesg);
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+		fdisk_ask_number_set_low(ask,     fdisk_scround(cxt, first));	/* minimal */
+		fdisk_ask_number_set_default(ask, fdisk_scround(cxt, last) - 1);/* default */
+		fdisk_ask_number_set_high(ask,    fdisk_scround(cxt, last) - 1);/* maximal */
+		fdisk_ask_number_set_base(ask,    fdisk_scround(cxt, first));
+
+		if (fdisk_use_cylinders(cxt))
+			fdisk_ask_number_set_unit(ask,
+				     cxt->sector_size *
+				     fdisk_get_units_per_sector(cxt));
+		else
+			fdisk_ask_number_set_unit(ask,cxt->sector_size);
+
+		rc = fdisk_do_ask(cxt, ask);
+		last = fdisk_ask_number_get_result(ask) + 1;
+
+		fdisk_unref_ask(ask);
+		if (rc)
+			return rc;
+		if (fdisk_use_cylinders(cxt))
+			last *= fdisk_get_units_per_sector(cxt);
+	}
+
+	if (sys == SGI_TYPE_ENTIRE_DISK
+	    && (first != 0 || last != sgi_get_lastblock(cxt)))
+		fdisk_info(cxt, _("It is highly recommended that the "
+				  "eleventh partition covers the entire "
+				  "disk and is of type 'SGI volume'."));
+
+	set_partition(cxt, n, first, last - first, sys);
+	cxt->label->nparts_cur = count_used_partitions(cxt);
+	if (partno)
+		*partno = n;
+	return 0;
+}
+
+static int sgi_create_disklabel(struct fdisk_context *cxt)
+{
+	struct fdisk_sgi_label *sgi;
+	struct sgi_disklabel *sgilabel;
+	int rc;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SGI));
+
+#ifdef HDIO_GETGEO
+	if (cxt->geom.heads && cxt->geom.sectors) {
+		fdisk_sector_t llsectors;
+
+		if (blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &llsectors) == 0) {
+			/* the get device size ioctl was successful */
+			fdisk_sector_t llcyls;
+			int sec_fac = cxt->sector_size / 512;
+
+			llcyls = llsectors / (cxt->geom.heads * cxt->geom.sectors * sec_fac);
+			cxt->geom.cylinders = llcyls;
+			if (cxt->geom.cylinders != llcyls)	/* truncated? */
+				cxt->geom.cylinders = ~0;
+		} else {
+			/* otherwise print error and use truncated version */
+			fdisk_warnx(cxt,
+				_("BLKGETSIZE ioctl failed on %s. "
+				  "Using geometry cylinder value of %llu. "
+				  "This value may be truncated for devices "
+				  "> 33.8 GB."), cxt->dev_path, cxt->geom.cylinders);
+		}
+	}
+#endif
+	rc = fdisk_init_firstsector_buffer(cxt);
+	if (rc)
+		return rc;
+
+	sgi = (struct fdisk_sgi_label *) cxt->label;
+	sgi->header = (struct sgi_disklabel *) cxt->firstsector;
+
+	sgilabel = sgi->header;
+
+	sgilabel->magic = cpu_to_be32(SGI_LABEL_MAGIC);
+	sgilabel->root_part_num = cpu_to_be16(0);
+	sgilabel->swap_part_num = cpu_to_be16(1);
+
+	/* sizeof(sgilabel->boot_file) = 16 > 6 */
+	memset(sgilabel->boot_file, 0, 16);
+	strcpy((char *) sgilabel->boot_file, "/unix");
+
+	sgilabel->devparam.skew			= (0);
+	sgilabel->devparam.gap1			= (0);
+	sgilabel->devparam.gap2			= (0);
+	sgilabel->devparam.sparecyl			= (0);
+	sgilabel->devparam.pcylcount	= cpu_to_be16(cxt->geom.cylinders);
+	sgilabel->devparam.head_vol0	= cpu_to_be16(0);
+	sgilabel->devparam.ntrks	= cpu_to_be16(cxt->geom.heads);
+	/* tracks/cylinder (heads) */
+	sgilabel->devparam.cmd_tag_queue_depth	= (0);
+	sgilabel->devparam.unused0			= (0);
+	sgilabel->devparam.unused1	= cpu_to_be16(0);
+	sgilabel->devparam.nsect	= cpu_to_be16(cxt->geom.sectors);
+	/* sectors/track */
+	sgilabel->devparam.bytes	= cpu_to_be16(cxt->sector_size);
+	sgilabel->devparam.ilfact	= cpu_to_be16(1);
+	sgilabel->devparam.flags	= cpu_to_be32(
+			SGI_DEVPARAM_TRACK_FWD
+			| SGI_DEVPARAM_IGNORE_ERRORS
+			| SGI_DEVPARAM_RESEEK);
+	sgilabel->devparam.datarate	= cpu_to_be32(0);
+	sgilabel->devparam.retries_on_error	= cpu_to_be32(1);
+	sgilabel->devparam.ms_per_word		= cpu_to_be32(0);
+	sgilabel->devparam.xylogics_gap1	= cpu_to_be16(0);
+	sgilabel->devparam.xylogics_syncdelay	= cpu_to_be16(0);
+	sgilabel->devparam.xylogics_readdelay	= cpu_to_be16(0);
+	sgilabel->devparam.xylogics_gap2	= cpu_to_be16(0);
+	sgilabel->devparam.xylogics_readgate	= cpu_to_be16(0);
+	sgilabel->devparam.xylogics_writecont	= cpu_to_be16(0);
+
+	memset(&(sgilabel->volume), 0,
+			sizeof(struct sgi_volume) * SGI_MAXVOLUMES);
+	memset(&(sgilabel->partitions), 0,
+			sizeof(struct sgi_partition) * SGI_MAXPARTITIONS);
+	cxt->label->nparts_max = SGI_MAXPARTITIONS;
+
+	/* don't create default layout when a script defined */
+	if (!cxt->script) {
+		sgi_set_entire(cxt);
+		sgi_set_volhdr(cxt);
+	}
+	cxt->label->nparts_cur = count_used_partitions(cxt);
+
+	fdisk_info(cxt, _("Created a new SGI disklabel."));
+	return 0;
+}
+
+static int sgi_set_partition(struct fdisk_context *cxt,
+		size_t i,
+		struct fdisk_partition *pa)
+{
+	struct sgi_disklabel *sgilabel;
+
+	if (i >= cxt->label->nparts_max)
+		return -EINVAL;
+
+	sgilabel = self_disklabel(cxt);
+
+	if (pa->type) {
+		struct fdisk_parttype *t = pa->type;
+
+		if (t->code > UINT32_MAX)
+			return -EINVAL;
+
+		if (sgi_get_num_sectors(cxt, i) == 0)	/* caught already before, ... */ {
+			fdisk_warnx(cxt, _("Sorry, only for non-empty partitions you can change the tag."));
+			return -EINVAL;
+		}
+
+		if ((i == 10 && t->code != SGI_TYPE_ENTIRE_DISK)
+		    || (i == 8 && t->code != 0))
+			fdisk_info(cxt, _("Consider leaving partition 9 as volume header (0), "
+					  "and partition 11 as entire volume (6), "
+					  "as IRIX expects it."));
+
+		if (cxt->script == NULL
+		    && ((t->code != SGI_TYPE_ENTIRE_DISK) && (t->code != SGI_TYPE_VOLHDR))
+		    && (sgi_get_start_sector(cxt, i) < 1)) {
+			int yes = 0;
+			fdisk_ask_yesno(cxt,
+				_("It is highly recommended that the partition at offset 0 "
+				  "is of type \"SGI volhdr\", the IRIX system will rely on it to "
+				  "retrieve from its directory standalone tools like sash and fx. "
+				  "Only the \"SGI volume\" entire disk section may violate this. "
+				  "Are you sure about tagging this partition differently?"), &yes);
+			if (!yes)
+				return 1;
+		}
+
+		sgilabel->partitions[i].type = cpu_to_be32(t->code);
+	}
+
+	if (fdisk_partition_has_start(pa))
+		sgilabel->partitions[i].first_block = cpu_to_be32(pa->start);
+	if (fdisk_partition_has_size(pa))
+		sgilabel->partitions[i].num_blocks = cpu_to_be32(pa->size);
+
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+
+static int sgi_partition_is_used(
+		struct fdisk_context *cxt,
+		size_t i)
+{
+	assert(cxt);
+	assert(fdisk_is_label(cxt, SGI));
+
+	if (i >= cxt->label->nparts_max)
+		return 0;
+	return sgi_get_num_sectors(cxt, i) ? 1 : 0;
+}
+
+static int sgi_toggle_partition_flag(struct fdisk_context *cxt, size_t i, unsigned long flag)
+{
+	struct sgi_disklabel *sgilabel;
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SGI));
+
+	if (i >= cxt->label->nparts_max)
+		return -EINVAL;
+
+	sgilabel = self_disklabel(cxt);
+
+	switch (flag) {
+	case SGI_FLAG_BOOT:
+		sgilabel->root_part_num =
+			be16_to_cpu(sgilabel->root_part_num) == i ?
+			0 : cpu_to_be16(i);
+		fdisk_label_set_changed(cxt->label, 1);
+		break;
+	case SGI_FLAG_SWAP:
+		sgilabel->swap_part_num =
+			be16_to_cpu(sgilabel->swap_part_num) == i ?
+			0 : cpu_to_be16(i);
+		fdisk_label_set_changed(cxt->label, 1);
+		break;
+	default:
+		return 1;
+	}
+
+	return 0;
+}
+
+static const struct fdisk_field sgi_fields[] =
+{
+	{ FDISK_FIELD_DEVICE,	N_("Device"),	 10,	0 },
+	{ FDISK_FIELD_START,	N_("Start"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_END,	N_("End"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SECTORS,	N_("Sectors"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_CYLINDERS,N_("Cylinders"),  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SIZE,	N_("Size"),	  5,	FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY },
+	{ FDISK_FIELD_TYPEID,	N_("Id"),	  2,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_TYPE,	N_("Type"),	0.1,	FDISK_FIELDFL_EYECANDY },
+	{ FDISK_FIELD_ATTR,	N_("Attrs"),	  0,	FDISK_FIELDFL_NUMBER }
+};
+
+static const struct fdisk_label_operations sgi_operations =
+{
+	.probe		= sgi_probe_label,
+	.write		= sgi_write_disklabel,
+	.verify		= sgi_verify_disklabel,
+	.create		= sgi_create_disklabel,
+	.list		= sgi_list_table,
+
+	.get_part	= sgi_get_partition,
+	.set_part	= sgi_set_partition,
+	.add_part	= sgi_add_partition,
+	.del_part	= sgi_delete_partition,
+
+	.part_is_used	= sgi_partition_is_used,
+	.part_toggle_flag = sgi_toggle_partition_flag
+};
+
+/* Allocates an SGI label driver. */
+struct fdisk_label *fdisk_new_sgi_label(struct fdisk_context *cxt)
+{
+	struct fdisk_label *lb;
+	struct fdisk_sgi_label *sgi;
+
+	assert(cxt);
+
+	sgi = calloc(1, sizeof(*sgi));
+	if (!sgi)
+		return NULL;
+
+	/* initialize generic part of the driver */
+	lb = (struct fdisk_label *) sgi;
+	lb->name = "sgi";
+	lb->id = FDISK_DISKLABEL_SGI;
+	lb->op = &sgi_operations;
+	lb->parttypes = sgi_parttypes;
+	lb->nparttypes = ARRAY_SIZE(sgi_parttypes) - 1;
+	lb->fields = sgi_fields;
+	lb->nfields = ARRAY_SIZE(sgi_fields);
+
+	lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+
+	return lb;
+}
diff --git a/libblkid/libfdisk/src/sun.c b/libblkid/libfdisk/src/sun.c
new file mode 100644
index 0000000..babff62
--- /dev/null
+++ b/libblkid/libfdisk/src/sun.c
@@ -0,0 +1,1130 @@
+/*
+ * Copyright (C) 2013 Karel Zak <kzak@redhat.com>
+ *
+ * Based on original code from fdisk:
+ *   Jakub Jelinek (jj@sunsite.mff.cuni.cz), July 1996
+ *   Merged with fdisk for other architectures, aeb, June 1998.
+ *   Arnaldo Carvalho de Melo <acme@conectiva.com.br> Mar 1999, Internationalization
+ */
+#include <stdio.h>		/* stderr */
+#include <stdlib.h>		/* qsort */
+#include <string.h>		/* strstr */
+#include <unistd.h>		/* write */
+#include <sys/ioctl.h>		/* ioctl */
+
+#include "nls.h"
+#include "blkdev.h"
+#include "bitops.h"
+
+#include "fdiskP.h"
+#include "pt-sun.h"
+#include "all-io.h"
+
+
+/**
+ * SECTION: sun
+ * @title: SUN
+ * @short_description: disk label specific functions
+ *
+ */
+
+/*
+ * in-memory fdisk SUN stuff
+ */
+struct fdisk_sun_label {
+	struct fdisk_label	head;		/* generic part */
+	struct sun_disklabel   *header;		/* on-disk data (pointer to cxt->firstsector) */
+};
+
+static struct fdisk_parttype sun_parttypes[] = {
+	{SUN_TAG_UNASSIGNED, N_("Unassigned")},
+	{SUN_TAG_BOOT, N_("Boot")},
+	{SUN_TAG_ROOT, N_("SunOS root")},
+	{SUN_TAG_SWAP, N_("SunOS swap")},
+	{SUN_TAG_USR, N_("SunOS usr")},
+	{SUN_TAG_WHOLEDISK, N_("Whole disk")},
+	{SUN_TAG_STAND, N_("SunOS stand")},
+	{SUN_TAG_VAR, N_("SunOS var")},
+	{SUN_TAG_HOME, N_("SunOS home")},
+	{SUN_TAG_ALTSCTR, N_("SunOS alt sectors")},
+	{SUN_TAG_CACHE, N_("SunOS cachefs")},
+	{SUN_TAG_RESERVED, N_("SunOS reserved")},
+	{SUN_TAG_LINUX_SWAP, N_("Linux swap")},
+	{SUN_TAG_LINUX_NATIVE, N_("Linux native")},
+	{SUN_TAG_LINUX_LVM, N_("Linux LVM")},
+	{SUN_TAG_LINUX_RAID, N_("Linux raid autodetect")},
+	{ 0, NULL }
+};
+
+/* return poiter buffer with on-disk data */
+static inline struct sun_disklabel *self_disklabel(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	return ((struct fdisk_sun_label *) cxt->label)->header;
+}
+
+/* return in-memory sun fdisk data */
+static inline struct fdisk_sun_label *self_label(struct fdisk_context *cxt)
+{
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	return (struct fdisk_sun_label *) cxt->label;
+}
+
+static void set_partition(struct fdisk_context *cxt, size_t i,
+		uint32_t start,uint32_t stop, uint16_t sysid)
+{
+	struct sun_disklabel *sunlabel = self_disklabel(cxt);
+	struct fdisk_parttype *t =
+			fdisk_label_get_parttype_from_code(cxt->label, sysid);
+
+	sunlabel->vtoc.infos[i].id = cpu_to_be16(sysid);
+	sunlabel->vtoc.infos[i].flags = cpu_to_be16(0);
+	sunlabel->partitions[i].start_cylinder =
+		cpu_to_be32(start / (cxt->geom.heads * cxt->geom.sectors));
+	sunlabel->partitions[i].num_sectors = cpu_to_be32(stop - start);
+	fdisk_label_set_changed(cxt->label, 1);
+
+	fdisk_info_new_partition(cxt, i + 1, start, stop, t);
+}
+
+static size_t count_used_partitions(struct fdisk_context *cxt)
+{
+	struct sun_disklabel *sunlabel = self_disklabel(cxt);
+	size_t ct = 0, i;
+
+	assert(sunlabel);
+
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+		if (sunlabel->partitions[i].num_sectors)
+			ct++;
+	}
+	return ct;
+}
+
+static int sun_probe_label(struct fdisk_context *cxt)
+{
+	struct fdisk_sun_label *sun;
+	struct sun_disklabel *sunlabel;
+	unsigned short *ush;
+	int csum;
+	int need_fixing = 0;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	/* map first sector to header */
+	sun = (struct fdisk_sun_label *) cxt->label;
+	sun->header = (struct sun_disklabel *) cxt->firstsector;
+	sunlabel = sun->header;
+
+	if (be16_to_cpu(sunlabel->magic) != SUN_LABEL_MAGIC) {
+		sun->header = NULL;
+		return 0;		/* failed */
+	}
+
+	ush = ((unsigned short *) (sunlabel + 1)) - 1;
+	for (csum = 0; ush >= (unsigned short *)sunlabel;)
+		csum ^= *ush--;
+
+	if (csum) {
+		fdisk_warnx(cxt, _("Detected sun disklabel with wrong checksum. "
+			      "Probably you'll have to set all the values, "
+			      "e.g. heads, sectors, cylinders and partitions "
+			      "or force a fresh label (s command in main menu)"));
+		return 1;
+	}
+
+	cxt->label->nparts_max = SUN_MAXPARTITIONS;
+	cxt->geom.heads = be16_to_cpu(sunlabel->nhead);
+	cxt->geom.cylinders = be16_to_cpu(sunlabel->ncyl);
+	cxt->geom.sectors = be16_to_cpu(sunlabel->nsect);
+
+	if (be32_to_cpu(sunlabel->vtoc.version) != SUN_VTOC_VERSION) {
+		fdisk_warnx(cxt, _("Detected sun disklabel with wrong version [%d]."),
+			be32_to_cpu(sunlabel->vtoc.version));
+		need_fixing = 1;
+	}
+	if (be32_to_cpu(sunlabel->vtoc.sanity) != SUN_VTOC_SANITY) {
+		fdisk_warnx(cxt, _("Detected sun disklabel with wrong vtoc.sanity [0x%08x]."),
+			be32_to_cpu(sunlabel->vtoc.sanity));
+		need_fixing = 1;
+	}
+	if (be16_to_cpu(sunlabel->vtoc.nparts) != SUN_MAXPARTITIONS) {
+		fdisk_warnx(cxt, _("Detected sun disklabel with wrong vtoc.nparts [%u]."),
+			be16_to_cpu(sunlabel->vtoc.nparts));
+		need_fixing = 1;
+	}
+	if (need_fixing) {
+		fdisk_warnx(cxt, _("Warning: Wrong values need to be fixed up and "
+			           "will be corrected by w(rite)"));
+
+		sunlabel->vtoc.version = cpu_to_be32(SUN_VTOC_VERSION);
+		sunlabel->vtoc.sanity = cpu_to_be32(SUN_VTOC_SANITY);
+		sunlabel->vtoc.nparts = cpu_to_be16(SUN_MAXPARTITIONS);
+
+		ush = (unsigned short *)sunlabel;
+		csum = 0;
+		while(ush < (unsigned short *)(&sunlabel->csum))
+			csum ^= *ush++;
+		sunlabel->csum = csum;
+
+		fdisk_label_set_changed(cxt->label, 1);
+	}
+
+	cxt->label->nparts_cur = count_used_partitions(cxt);
+
+	return 1;
+}
+
+static void ask_geom(struct fdisk_context *cxt)
+{
+	uintmax_t res;
+
+	assert(cxt);
+
+	if (fdisk_ask_number(cxt, 1, 1, 1024, _("Heads"), &res) == 0)
+		cxt->geom.heads = res;
+	if (fdisk_ask_number(cxt, 1, 1, 1024, _("Sectors/track"), &res) == 0)
+		cxt->geom.sectors = res;
+	if (fdisk_ask_number(cxt, 1, 1, USHRT_MAX, _("Cylinders"), &res) == 0)
+		cxt->geom.cylinders = res;
+}
+
+static int sun_create_disklabel(struct fdisk_context *cxt)
+{
+	unsigned int ndiv;
+	struct fdisk_sun_label *sun;		/* libfdisk sun handler */
+	struct sun_disklabel *sunlabel;	/* on disk data */
+	int rc = 0;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	/* map first sector to header */
+	rc = fdisk_init_firstsector_buffer(cxt);
+	if (rc)
+		return rc;
+
+	sun = (struct fdisk_sun_label *) cxt->label;
+	sun->header = (struct sun_disklabel *) cxt->firstsector;
+
+	sunlabel = sun->header;
+
+	cxt->label->nparts_max = SUN_MAXPARTITIONS;
+
+	sunlabel->magic = cpu_to_be16(SUN_LABEL_MAGIC);
+	sunlabel->vtoc.version = cpu_to_be32(SUN_VTOC_VERSION);
+	sunlabel->vtoc.sanity = cpu_to_be32(SUN_VTOC_SANITY);
+	sunlabel->vtoc.nparts = cpu_to_be16(SUN_MAXPARTITIONS);
+
+#ifdef HDIO_GETGEO
+	if (cxt->geom.heads && cxt->geom.sectors) {
+		fdisk_sector_t llsectors;
+
+		if (blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &llsectors) == 0) {
+			int sec_fac = cxt->sector_size / 512;
+			fdisk_sector_t llcyls;
+
+			llcyls = llsectors / (cxt->geom.heads * cxt->geom.sectors * sec_fac);
+			cxt->geom.cylinders = llcyls;
+			if (cxt->geom.cylinders != llcyls)
+				cxt->geom.cylinders = ~0;
+		} else {
+			fdisk_warnx(cxt,
+				_("BLKGETSIZE ioctl failed on %s. "
+				  "Using geometry cylinder value of %llu. "
+				  "This value may be truncated for devices "
+				  "> 33.8 GB."),
+				cxt->dev_path, cxt->geom.cylinders);
+		}
+	} else
+#endif
+		ask_geom(cxt);
+
+	sunlabel->acyl   = cpu_to_be16(0);
+	sunlabel->pcyl   = cpu_to_be16(cxt->geom.cylinders);
+	sunlabel->rpm    = cpu_to_be16(5400);
+	sunlabel->intrlv = cpu_to_be16(1);
+	sunlabel->apc    = cpu_to_be16(0);
+
+	sunlabel->nhead  = cpu_to_be16(cxt->geom.heads);
+	sunlabel->nsect  = cpu_to_be16(cxt->geom.sectors);
+	sunlabel->ncyl   = cpu_to_be16(cxt->geom.cylinders);
+
+	snprintf((char *) sunlabel->label_id, sizeof(sunlabel->label_id),
+		 "Linux cyl %ju alt %u hd %u sec %ju",
+		 (uintmax_t) cxt->geom.cylinders,
+		 be16_to_cpu(sunlabel->acyl),
+		 cxt->geom.heads,
+		 (uintmax_t) cxt->geom.sectors);
+
+	if (cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors >= 150 * 2048) {
+	        ndiv = cxt->geom.cylinders - (50 * 2048 / (cxt->geom.heads * cxt->geom.sectors)); /* 50M swap */
+	} else
+	        ndiv = cxt->geom.cylinders * 2 / 3;
+
+	/* create the default layout only if no-script defined */
+	if (!cxt->script) {
+		set_partition(cxt, 0, 0, ndiv * cxt->geom.heads * cxt->geom.sectors,
+			  SUN_TAG_LINUX_NATIVE);
+		set_partition(cxt, 1, ndiv * cxt->geom.heads * cxt->geom.sectors,
+			  cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors,
+			  SUN_TAG_LINUX_SWAP);
+		sunlabel->vtoc.infos[1].flags |= cpu_to_be16(SUN_FLAG_UNMNT);
+
+		set_partition(cxt, 2, 0,
+			  cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors,
+			  SUN_TAG_WHOLEDISK);
+	}
+
+	{
+		unsigned short *ush = (unsigned short *)sunlabel;
+		unsigned short csum = 0;
+		while(ush < (unsigned short *)(&sunlabel->csum))
+			csum ^= *ush++;
+		sunlabel->csum = csum;
+	}
+
+	fdisk_label_set_changed(cxt->label, 1);
+	cxt->label->nparts_cur = count_used_partitions(cxt);
+
+	fdisk_info(cxt, _("Created a new Sun disklabel."));
+	return 0;
+}
+
+static int sun_toggle_partition_flag(struct fdisk_context *cxt, size_t i, unsigned long flag)
+{
+	struct sun_disklabel *sunlabel;
+	struct sun_info *p;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	if (i >= cxt->label->nparts_max)
+		return -EINVAL;
+
+	sunlabel = self_disklabel(cxt);
+	p = &sunlabel->vtoc.infos[i];
+
+	switch (flag) {
+	case SUN_FLAG_UNMNT:
+		p->flags ^= cpu_to_be16(SUN_FLAG_UNMNT);
+		fdisk_label_set_changed(cxt->label, 1);
+		break;
+	case SUN_FLAG_RONLY:
+		p->flags ^= cpu_to_be16(SUN_FLAG_RONLY);
+		fdisk_label_set_changed(cxt->label, 1);
+		break;
+	default:
+		return 1;
+	}
+
+	return 0;
+}
+
+static void fetch_sun(struct fdisk_context *cxt,
+		      uint32_t *starts,
+		      uint32_t *lens,
+		      uint32_t *start,
+		      uint32_t *stop)
+{
+	struct sun_disklabel *sunlabel;
+	int continuous = 1;
+	size_t i;
+
+	assert(cxt);
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	sunlabel = self_disklabel(cxt);
+
+	*start = 0;
+	*stop = cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors;
+
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+		struct sun_partition *part = &sunlabel->partitions[i];
+		struct sun_info *info = &sunlabel->vtoc.infos[i];
+
+		if (part->num_sectors &&
+		    be16_to_cpu(info->id) != SUN_TAG_UNASSIGNED &&
+		    be16_to_cpu(info->id) != SUN_TAG_WHOLEDISK) {
+			starts[i] = be32_to_cpu(part->start_cylinder) *
+				     cxt->geom.heads * cxt->geom.sectors;
+			lens[i] = be32_to_cpu(part->num_sectors);
+			if (continuous) {
+				if (starts[i] == *start)
+					*start += lens[i];
+				else if (starts[i] + lens[i] >= *stop)
+					*stop = starts[i];
+				else
+					continuous = 0;
+				        /* There will be probably more gaps
+					  than one, so lets check afterwards */
+			}
+		} else {
+			starts[i] = 0;
+			lens[i] = 0;
+		}
+	}
+}
+
+#ifdef HAVE_QSORT_R
+static int verify_sun_cmp(int *a, int *b, void *data)
+{
+    unsigned int *verify_sun_starts = (unsigned int *) data;
+
+    if (*a == -1)
+	    return 1;
+    if (*b == -1)
+	    return -1;
+    if (verify_sun_starts[*a] > verify_sun_starts[*b])
+	    return 1;
+    return -1;
+}
+#endif
+
+static int sun_verify_disklabel(struct fdisk_context *cxt)
+{
+    uint32_t starts[SUN_MAXPARTITIONS], lens[SUN_MAXPARTITIONS], start, stop;
+    uint32_t i,j,k,starto,endo;
+#ifdef HAVE_QSORT_R
+    int array[SUN_MAXPARTITIONS];
+    unsigned int *verify_sun_starts;
+#endif
+    assert(cxt);
+    assert(cxt->label);
+    assert(fdisk_is_label(cxt, SUN));
+
+    fetch_sun(cxt, starts, lens, &start, &stop);
+
+    for (k = 0; k < 7; k++) {
+	for (i = 0; i < SUN_MAXPARTITIONS; i++) {
+	    if (k && (lens[i] % (cxt->geom.heads * cxt->geom.sectors)))
+	        fdisk_warnx(cxt, _("Partition %u doesn't end on cylinder boundary."), i+1);
+	    if (lens[i]) {
+	        for (j = 0; j < i; j++)
+	            if (lens[j]) {
+	                if (starts[j] == starts[i]+lens[i]) {
+	                    starts[j] = starts[i]; lens[j] += lens[i];
+	                    lens[i] = 0;
+	                } else if (starts[i] == starts[j]+lens[j]){
+	                    lens[j] += lens[i];
+	                    lens[i] = 0;
+	                } else if (!k) {
+	                    if (starts[i] < starts[j]+lens[j] &&
+				starts[j] < starts[i]+lens[i]) {
+	                        starto = starts[i];
+	                        if (starts[j] > starto)
+					starto = starts[j];
+	                        endo = starts[i]+lens[i];
+	                        if (starts[j]+lens[j] < endo)
+					endo = starts[j]+lens[j];
+	                        fdisk_warnx(cxt, _("Partition %u overlaps with others in "
+				       "sectors %u-%u."), i+1, starto, endo);
+	                    }
+	                }
+	            }
+	    }
+	}
+    }
+
+#ifdef HAVE_QSORT_R
+    for (i = 0; i < SUN_MAXPARTITIONS; i++) {
+        if (lens[i])
+            array[i] = i;
+        else
+            array[i] = -1;
+    }
+    verify_sun_starts = starts;
+
+    qsort_r(array,ARRAY_SIZE(array),sizeof(array[0]),
+	  (int (*)(const void *,const void *,void *)) verify_sun_cmp,
+	  verify_sun_starts);
+
+    if (array[0] == -1) {
+	fdisk_info(cxt, _("No partitions defined."));
+	return 0;
+    }
+    stop = cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors;
+    if (starts[array[0]])
+        fdisk_warnx(cxt, _("Unused gap - sectors 0-%u."), starts[array[0]]);
+    for (i = 0; i < 7 && array[i+1] != -1; i++) {
+        fdisk_warnx(cxt, _("Unused gap - sectors %u-%u."),
+	       (starts[array[i]] + lens[array[i]]),
+	       starts[array[i+1]]);
+    }
+    start = (starts[array[i]] + lens[array[i]]);
+    if (start < stop)
+        fdisk_warnx(cxt, _("Unused gap - sectors %u-%u."), start, stop);
+#endif
+    return 0;
+}
+
+
+static int is_free_sector(struct fdisk_context *cxt,
+		fdisk_sector_t s, uint32_t starts[], uint32_t lens[])
+{
+	size_t i;
+
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+		if (lens[i] && starts[i] <= s
+		    && starts[i] + lens[i] > s)
+			return 0;
+	}
+	return 1;
+}
+
+static int sun_add_partition(
+		struct fdisk_context *cxt,
+		struct fdisk_partition *pa,
+		size_t *partno)
+{
+	struct sun_disklabel *sunlabel = self_disklabel(cxt);
+	uint32_t starts[SUN_MAXPARTITIONS], lens[SUN_MAXPARTITIONS];
+	struct sun_partition *part;
+	struct sun_info *info;
+	uint32_t start, stop, stop2;
+	int whole_disk = 0;
+	int sys = pa && pa->type ? pa->type->code : SUN_TAG_LINUX_NATIVE;
+	int rc;
+	size_t n;
+
+	char mesg[256];
+	size_t i;
+	unsigned int first, last;
+
+	rc = fdisk_partition_next_partno(pa, cxt, &n);
+	if (rc)
+		return rc;
+
+	part = &sunlabel->partitions[n];
+	info = &sunlabel->vtoc.infos[n];
+
+	if (part->num_sectors && be16_to_cpu(info->id) != SUN_TAG_UNASSIGNED) {
+		fdisk_info(cxt, _("Partition %zu is already defined.  Delete "
+			"it before re-adding it."), n + 1);
+		return -EINVAL;
+	}
+
+	fetch_sun(cxt, starts, lens, &start, &stop);
+
+	if (stop <= start) {
+		if (n == 2)
+			whole_disk = 1;
+		else {
+			fdisk_info(cxt, _("Other partitions already cover the "
+				"whole disk. Delete some/shrink them before retry."));
+			return -EINVAL;
+		}
+	}
+
+	if (pa && pa->start_follow_default)
+		first = start;
+	else if (pa && fdisk_partition_has_start(pa)) {
+		first = pa->start;
+
+		if (!whole_disk && !is_free_sector(cxt, first, starts, lens))
+			return -ERANGE;
+	} else {
+		struct fdisk_ask *ask;
+
+		snprintf(mesg, sizeof(mesg), _("First %s"),
+				fdisk_get_unit(cxt, FDISK_SINGULAR));
+		for (;;) {
+			ask = fdisk_new_ask();
+			if (!ask)
+				return -ENOMEM;
+
+			fdisk_ask_set_query(ask, mesg);
+			fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+
+			if (whole_disk) {
+				fdisk_ask_number_set_low(ask,     0);	/* minimal */
+				fdisk_ask_number_set_default(ask, 0);	/* default */
+				fdisk_ask_number_set_high(ask,    0);	/* maximal */
+			} else {
+				fdisk_ask_number_set_low(ask,     fdisk_scround(cxt, start));	/* minimal */
+				fdisk_ask_number_set_default(ask, fdisk_scround(cxt, start));	/* default */
+				fdisk_ask_number_set_high(ask,    fdisk_scround(cxt, stop));	/* maximal */
+			}
+			rc = fdisk_do_ask(cxt, ask);
+			first = fdisk_ask_number_get_result(ask);
+			fdisk_unref_ask(ask);
+			if (rc)
+				return rc;
+
+			if (fdisk_use_cylinders(cxt))
+				first *= fdisk_get_units_per_sector(cxt);
+
+			/* ewt asks to add: "don't start a partition at cyl 0"
+			   However, edmundo@rano.demon.co.uk writes:
+			   "In addition to having a Sun partition table, to be able to
+			   boot from the disc, the first partition, /dev/sdX1, must
+			   start at cylinder 0. This means that /dev/sdX1 contains
+			   the partition table and the boot block, as these are the
+			   first two sectors of the disc. Therefore you must be
+			   careful what you use /dev/sdX1 for. In particular, you must
+			   not use a partition starting at cylinder 0 for Linux swap,
+			   as that would overwrite the partition table and the boot
+			   block. You may, however, use such a partition for a UFS
+			   or EXT2 file system, as these file systems leave the first
+			   1024 bytes undisturbed. */
+			/* On the other hand, one should not use partitions
+			   starting at block 0 in an md, or the label will
+			   be trashed. */
+			if (!is_free_sector(cxt, first, starts,  lens) && !whole_disk) {
+				if (n == 2 && !first) {
+				    whole_disk = 1;
+				    break;
+				}
+				fdisk_warnx(cxt, _("Sector %d is already allocated"), first);
+			} else
+				break;
+		}
+	}
+
+	if (n == 2 && first != 0)
+		fdisk_warnx(cxt, _("It is highly recommended that the "
+				   "third partition covers the whole disk "
+				   "and is of type `Whole disk'"));
+
+	if (!fdisk_use_cylinders(cxt)) {
+		/* Starting sector has to be properly aligned */
+		int cs = cxt->geom.heads * cxt->geom.sectors;
+		int x = first % cs;
+
+		if (x) {
+			fdisk_info(cxt, _("Aligning the first sector from %u to %u "
+					  "to be on cylinder boundary."),
+					first, first + cs - x);
+			first += cs - x;
+		}
+	}
+
+	stop = cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors;	/* ancient */
+	stop2 = stop;
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+		if (starts[i] > first && starts[i] < stop)
+			stop = starts[i];
+	}
+
+	/* last */
+	if (pa && pa->end_follow_default)
+		last = whole_disk || (n == 2 && !first) ? stop2 : stop;
+	else if (pa && fdisk_partition_has_size(pa)) {
+		last = first + pa->size - 1ULL;
+
+		if (!whole_disk && last > stop)
+			return -ERANGE;
+	} else {
+		struct fdisk_ask *ask = fdisk_new_ask();
+
+		if (!ask)
+			return -ENOMEM;
+
+		snprintf(mesg, sizeof(mesg),
+			 _("Last %s or +%s or +size{K,M,G,T,P}"),
+			 fdisk_get_unit(cxt, FDISK_SINGULAR),
+			 fdisk_get_unit(cxt, FDISK_PLURAL));
+		fdisk_ask_set_query(ask, mesg);
+		fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+		if (whole_disk) {
+			fdisk_ask_number_set_low(ask,     fdisk_scround(cxt, stop2));	/* minimal */
+			fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop2));	/* default */
+			fdisk_ask_number_set_high(ask,    fdisk_scround(cxt, stop2));	/* maximal */
+			fdisk_ask_number_set_base(ask,    0);
+		} else if (n == 2 && !first) {
+			fdisk_ask_number_set_low(ask,     fdisk_scround(cxt, first));	/* minimal */
+			fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop2));	/* default */
+			fdisk_ask_number_set_high(ask,    fdisk_scround(cxt, stop2));	/* maximal */
+			fdisk_ask_number_set_base(ask,	  fdisk_scround(cxt, first));
+		} else {
+			fdisk_ask_number_set_low(ask,     fdisk_scround(cxt, first));	/* minimal */
+			fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop));	/* default */
+			fdisk_ask_number_set_high(ask,    fdisk_scround(cxt, stop));	/* maximal */
+			fdisk_ask_number_set_base(ask,    fdisk_scround(cxt, first));
+		}
+
+		if (fdisk_use_cylinders(cxt))
+			fdisk_ask_number_set_unit(ask,
+				     cxt->sector_size *
+				     fdisk_get_units_per_sector(cxt));
+		else
+			fdisk_ask_number_set_unit(ask,	cxt->sector_size);
+
+		rc = fdisk_do_ask(cxt, ask);
+		last = fdisk_ask_number_get_result(ask);
+
+		fdisk_unref_ask(ask);
+		if (rc)
+			return rc;
+		if (fdisk_use_cylinders(cxt))
+			last *= fdisk_get_units_per_sector(cxt);
+	}
+
+	if (n == 2 && !first) {
+		if (last >= stop2) {
+		    whole_disk = 1;
+		    last = stop2;
+		} else if (last > stop) {
+		    fdisk_warnx(cxt,
+   _("You haven't covered the whole disk with the 3rd partition, but your value\n"
+     "%lu %s covers some other partition. Your entry has been changed\n"
+     "to %lu %s"),
+			(unsigned long) fdisk_scround(cxt, last), fdisk_get_unit(cxt, FDISK_SINGULAR),
+			(unsigned long) fdisk_scround(cxt, stop), fdisk_get_unit(cxt, FDISK_SINGULAR));
+		    last = stop;
+		}
+	} else if (!whole_disk && last > stop)
+		last = stop;
+
+	if (whole_disk)
+		sys = SUN_TAG_WHOLEDISK;
+
+	set_partition(cxt, n, first, last, sys);
+	cxt->label->nparts_cur = count_used_partitions(cxt);
+	if (partno)
+		*partno = n;
+	return 0;
+}
+
+static int sun_delete_partition(struct fdisk_context *cxt,
+		size_t partnum)
+{
+	struct sun_disklabel *sunlabel;
+	struct sun_partition *part;
+	struct sun_info *info;
+	unsigned int nsec;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	sunlabel = self_disklabel(cxt);
+	part = &sunlabel->partitions[partnum];
+	info = &sunlabel->vtoc.infos[partnum];
+
+	if (partnum == 2 &&
+	    be16_to_cpu(info->id) == SUN_TAG_WHOLEDISK &&
+	    !part->start_cylinder &&
+	    (nsec = be32_to_cpu(part->num_sectors))
+	    == cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders)
+		fdisk_info(cxt, _("If you want to maintain SunOS/Solaris compatibility, "
+			 "consider leaving this "
+			 "partition as Whole disk (5), starting at 0, with %u "
+			 "sectors"), nsec);
+	info->id = cpu_to_be16(SUN_TAG_UNASSIGNED);
+	part->num_sectors = 0;
+	cxt->label->nparts_cur = count_used_partitions(cxt);
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+
+static int sun_list_disklabel(struct fdisk_context *cxt)
+{
+	struct sun_disklabel *sunlabel;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	sunlabel = self_disklabel(cxt);
+
+	if (fdisk_is_details(cxt)) {
+		fdisk_info(cxt,
+		_("Label geometry: %d rpm, %d alternate and %d physical cylinders,\n"
+		  "                %d extra sects/cyl, interleave %d:1"),
+		       be16_to_cpu(sunlabel->rpm),
+		       be16_to_cpu(sunlabel->acyl),
+		       be16_to_cpu(sunlabel->pcyl),
+		       be16_to_cpu(sunlabel->apc),
+		       be16_to_cpu(sunlabel->intrlv));
+		fdisk_info(cxt, _("Label ID: %s"), sunlabel->label_id);
+		fdisk_info(cxt, _("Volume ID: %s"),
+		       *sunlabel->vtoc.volume_id ? sunlabel->vtoc.volume_id : _("<none>"));
+	}
+
+	return 0;
+}
+
+static struct fdisk_parttype *sun_get_parttype(
+		struct fdisk_context *cxt,
+		size_t n)
+{
+	struct sun_disklabel *sunlabel = self_disklabel(cxt);
+	struct fdisk_parttype *t;
+
+	if (n >= cxt->label->nparts_max)
+		return NULL;
+
+	t = fdisk_label_get_parttype_from_code(cxt->label,
+			be16_to_cpu(sunlabel->vtoc.infos[n].id));
+	return t ? : fdisk_new_unknown_parttype(be16_to_cpu(sunlabel->vtoc.infos[n].id), NULL);
+}
+
+
+static int sun_get_partition(struct fdisk_context *cxt, size_t n,
+			      struct fdisk_partition *pa)
+{
+	struct sun_disklabel *sunlabel;
+	struct sun_partition *part;
+	uint16_t flags;
+	uint32_t start, len;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	if (n >= cxt->label->nparts_max)
+		return -EINVAL;
+
+	sunlabel = self_disklabel(cxt);
+	part = &sunlabel->partitions[n];
+
+	pa->used = part->num_sectors ? 1 : 0;
+	if (!pa->used)
+		return 0;
+
+	flags = be16_to_cpu(sunlabel->vtoc.infos[n].flags);
+	start = be32_to_cpu(part->start_cylinder)
+			* cxt->geom.heads * cxt->geom.sectors;
+	len = be32_to_cpu(part->num_sectors);
+
+	pa->type = sun_get_parttype(cxt, n);
+	if (pa->type && pa->type->code == SUN_TAG_WHOLEDISK)
+		pa->wholedisk = 1;
+
+	if (flags & SUN_FLAG_UNMNT || flags & SUN_FLAG_RONLY) {
+		if (asprintf(&pa->attrs, "%c%c",
+				flags & SUN_FLAG_UNMNT ? 'u' : ' ',
+				flags & SUN_FLAG_RONLY ? 'r' : ' ') < 0)
+			return -ENOMEM;
+	}
+
+	pa->start = start;
+	pa->size = len;
+
+	return 0;
+}
+
+/**
+ * fdisk_sun_set_alt_cyl:
+ * @cxt: context
+ *
+ * Sets number of alternative cylinders. This function uses libfdisk Ask API
+ * for dialog with user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_alt_cyl(struct fdisk_context *cxt)
+{
+	struct sun_disklabel *sunlabel = self_disklabel(cxt);
+	uintmax_t res;
+	int rc = fdisk_ask_number(cxt, 0,			/* low */
+			be16_to_cpu(sunlabel->acyl),		/* default */
+			65535,					/* high */
+			_("Number of alternate cylinders"),	/* query */
+			&res);					/* result */
+	if (rc)
+		return rc;
+
+	sunlabel->acyl = cpu_to_be16(res);
+	return 0;
+}
+
+/**
+ * fdisk_sun_set_xcyl:
+ * @cxt: context
+ *
+ * Sets number of extra sectors per cylinder. This function uses libfdisk Ask API
+ * for dialog with user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_xcyl(struct fdisk_context *cxt)
+{
+	struct sun_disklabel *sunlabel = self_disklabel(cxt);
+	uintmax_t res;
+	int rc = fdisk_ask_number(cxt, 0,			/* low */
+			be16_to_cpu(sunlabel->apc),		/* default */
+			cxt->geom.sectors,			/* high */
+			_("Extra sectors per cylinder"),	/* query */
+			&res);					/* result */
+	if (rc)
+		return rc;
+	sunlabel->apc = cpu_to_be16(res);
+	return 0;
+}
+
+/**
+ * fdisk_sun_set_ilfact:
+ * @cxt: context
+ *
+ * Sets interleave factor. This function uses libfdisk Ask API for dialog with
+ * user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_ilfact(struct fdisk_context *cxt)
+{
+	struct sun_disklabel *sunlabel = self_disklabel(cxt);
+	uintmax_t res;
+	int rc = fdisk_ask_number(cxt, 1,			/* low */
+			be16_to_cpu(sunlabel->intrlv),		/* default */
+			32,					/* high */
+			_("Interleave factor"),	/* query */
+			&res);					/* result */
+	if (rc)
+		return rc;
+	sunlabel->intrlv = cpu_to_be16(res);
+	return 0;
+}
+
+/**
+ * fdisk_sun_set_rspeed
+ * @cxt: context
+ *
+ * Sets rotation speed. This function uses libfdisk Ask API for dialog with
+ * user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_rspeed(struct fdisk_context *cxt)
+{
+	struct sun_disklabel *sunlabel = self_disklabel(cxt);
+	uintmax_t res;
+	int rc = fdisk_ask_number(cxt, 1,			/* low */
+			be16_to_cpu(sunlabel->rpm),		/* default */
+			USHRT_MAX,				/* high */
+			_("Rotation speed (rpm)"),		/* query */
+			&res);					/* result */
+	if (rc)
+		return rc;
+	sunlabel->rpm = cpu_to_be16(res);
+	return 0;
+}
+
+/**
+ * fdisk_sun_set_pcylcount
+ * @cxt: context
+ *
+ * Sets number of physical cylinders. This function uses libfdisk Ask API for
+ * dialog with user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_pcylcount(struct fdisk_context *cxt)
+{
+	struct sun_disklabel *sunlabel = self_disklabel(cxt);
+	uintmax_t res;
+	int rc = fdisk_ask_number(cxt, 0,			/* low */
+			be16_to_cpu(sunlabel->pcyl),		/* default */
+			USHRT_MAX,				/* high */
+			_("Number of physical cylinders"),	/* query */
+			&res);					/* result */
+	if (!rc)
+		return rc;
+	sunlabel->pcyl = cpu_to_be16(res);
+	return 0;
+}
+
+static int sun_write_disklabel(struct fdisk_context *cxt)
+{
+	struct sun_disklabel *sunlabel;
+	unsigned short *ush;
+	unsigned short csum = 0;
+	const size_t sz = sizeof(struct sun_disklabel);
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	sunlabel = self_disklabel(cxt);
+
+	/* Maybe geometry has been modified */
+	sunlabel->nhead = cpu_to_be16(cxt->geom.heads);
+	sunlabel->nsect = cpu_to_be16(cxt->geom.sectors);
+
+	if (cxt->geom.cylinders != be16_to_cpu(sunlabel->ncyl))
+		sunlabel->ncyl = cpu_to_be16( cxt->geom.cylinders
+				      - be16_to_cpu(sunlabel->acyl) );
+
+	ush = (unsigned short *) sunlabel;
+
+	while(ush < (unsigned short *)(&sunlabel->csum))
+		csum ^= *ush++;
+	sunlabel->csum = csum;
+	if (lseek(cxt->dev_fd, 0, SEEK_SET) < 0)
+		return -errno;
+	if (write_all(cxt->dev_fd, sunlabel, sz) != 0)
+		return -errno;
+
+	return 0;
+}
+
+static int sun_set_partition(
+		struct fdisk_context *cxt,
+		size_t i,
+		struct fdisk_partition *pa)
+{
+	struct sun_disklabel *sunlabel;
+	struct sun_partition *part;
+	struct sun_info *info;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	sunlabel = self_disklabel(cxt);
+
+	if (i >= cxt->label->nparts_max)
+		return -EINVAL;
+
+	if (pa->type) {
+		struct fdisk_parttype *t = pa->type;
+
+		if (t->code > UINT16_MAX)
+			return -EINVAL;
+
+		if (i == 2 && t->code != SUN_TAG_WHOLEDISK)
+			fdisk_info(cxt, _("Consider leaving partition 3 as Whole disk (5),\n"
+			         "as SunOS/Solaris expects it and even Linux likes it.\n"));
+
+		part = &sunlabel->partitions[i];
+		info = &sunlabel->vtoc.infos[i];
+
+		if (cxt->script == NULL &&
+		    t->code == SUN_TAG_LINUX_SWAP && !part->start_cylinder) {
+			int yes, rc;
+
+			rc = fdisk_ask_yesno(cxt,
+			      _("It is highly recommended that the partition at offset 0\n"
+			      "is UFS, EXT2FS filesystem or SunOS swap. Putting Linux swap\n"
+			      "there may destroy your partition table and bootblock.\n"
+			      "Are you sure you want to tag the partition as Linux swap?"), &yes);
+			if (rc)
+				return rc;
+			if (!yes)
+				return 1;
+		}
+
+		switch (t->code) {
+		case SUN_TAG_SWAP:
+		case SUN_TAG_LINUX_SWAP:
+			/* swaps are not mountable by default */
+			info->flags |= cpu_to_be16(SUN_FLAG_UNMNT);
+			break;
+		default:
+			/* assume other types are mountable;
+			   user can change it anyway */
+			info->flags &= ~cpu_to_be16(SUN_FLAG_UNMNT);
+			break;
+		}
+		info->id = cpu_to_be16(t->code);
+	}
+
+	if (fdisk_partition_has_start(pa))
+		sunlabel->partitions[i].start_cylinder =
+			cpu_to_be32(pa->start / (cxt->geom.heads * cxt->geom.sectors));
+	if (fdisk_partition_has_size(pa))
+		sunlabel->partitions[i].num_sectors = cpu_to_be32(pa->size);
+
+	fdisk_label_set_changed(cxt->label, 1);
+	return 0;
+}
+
+
+static int sun_reset_alignment(struct fdisk_context *cxt __attribute__((__unused__)))
+{
+	return 0;
+}
+
+
+static int sun_partition_is_used(
+		struct fdisk_context *cxt,
+		size_t i)
+{
+	struct sun_disklabel *sunlabel;
+
+	assert(cxt);
+	assert(cxt->label);
+	assert(fdisk_is_label(cxt, SUN));
+
+	if (i >= cxt->label->nparts_max)
+		return 0;
+
+	sunlabel = self_disklabel(cxt);
+	return sunlabel->partitions[i].num_sectors ? 1 : 0;
+}
+
+static const struct fdisk_field sun_fields[] =
+{
+	{ FDISK_FIELD_DEVICE,	N_("Device"),	 10,	0 },
+	{ FDISK_FIELD_START,	N_("Start"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_END,	N_("End"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SECTORS,	N_("Sectors"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_CYLINDERS,N_("Cylinders"),  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_SIZE,	N_("Size"),	  5,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_TYPEID,	N_("Id"),	  2,	FDISK_FIELDFL_NUMBER },
+	{ FDISK_FIELD_TYPE,	N_("Type"),	0.1,	0 },
+	{ FDISK_FIELD_ATTR,	N_("Flags"),	  0,	FDISK_FIELDFL_NUMBER }
+};
+
+const struct fdisk_label_operations sun_operations =
+{
+	.probe		= sun_probe_label,
+	.write		= sun_write_disklabel,
+	.verify		= sun_verify_disklabel,
+	.create		= sun_create_disklabel,
+	.list		= sun_list_disklabel,
+
+	.get_part	= sun_get_partition,
+	.set_part	= sun_set_partition,
+	.add_part	= sun_add_partition,
+	.del_part	= sun_delete_partition,
+
+	.part_is_used	= sun_partition_is_used,
+	.part_toggle_flag = sun_toggle_partition_flag,
+
+	.reset_alignment = sun_reset_alignment,
+};
+
+/*
+ * allocates SUN label driver
+ */
+struct fdisk_label *fdisk_new_sun_label(struct fdisk_context *cxt)
+{
+	struct fdisk_label *lb;
+	struct fdisk_sun_label *sun;
+
+	assert(cxt);
+
+	sun = calloc(1, sizeof(*sun));
+	if (!sun)
+		return NULL;
+
+	/* initialize generic part of the driver */
+	lb = (struct fdisk_label *) sun;
+	lb->name = "sun";
+	lb->id = FDISK_DISKLABEL_SUN;
+	lb->op = &sun_operations;
+	lb->parttypes = sun_parttypes;
+	lb->nparttypes = ARRAY_SIZE(sun_parttypes) - 1;
+	lb->fields = sun_fields;
+	lb->nfields = ARRAY_SIZE(sun_fields);
+	lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+
+	return lb;
+}
diff --git a/libblkid/libfdisk/src/table.c b/libblkid/libfdisk/src/table.c
new file mode 100644
index 0000000..1add09f
--- /dev/null
+++ b/libblkid/libfdisk/src/table.c
@@ -0,0 +1,664 @@
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: table
+ * @title: Table
+ * @short_description: container for fdisk partitions
+ *
+ * The fdisk_table is simple container for fdisk_partitions. The table is no
+ * directly connected to label data (partition table), and table changes don't
+ * affect in-memory or on-disk data.
+ */
+
+/**
+ * fdisk_new_table:
+ *
+ * The table is a container for struct fdisk_partition entries. The container
+ * does not have any real connection with label (partition table) and with
+ * real on-disk data.
+ *
+ * Returns: newly allocated table struct.
+ */
+struct fdisk_table *fdisk_new_table(void)
+{
+	struct fdisk_table *tb = NULL;
+
+	tb = calloc(1, sizeof(*tb));
+	if (!tb)
+		return NULL;
+
+	DBG(TAB, ul_debugobj(tb, "alloc"));
+	tb->refcount = 1;
+	INIT_LIST_HEAD(&tb->parts);
+	return tb;
+}
+
+/**
+ * fdisk_reset_table:
+ * @tb: tab pointer
+ *
+ * Removes all entries (partitions) from the table. The parititons with zero
+ * reference count will be deallocated. This function does not modify partition
+ * table.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int fdisk_reset_table(struct fdisk_table *tb)
+{
+	if (!tb)
+		return -EINVAL;
+
+	DBG(TAB, ul_debugobj(tb, "reset"));
+
+	while (!list_empty(&tb->parts)) {
+		struct fdisk_partition *pa = list_entry(tb->parts.next,
+				                  struct fdisk_partition, parts);
+		fdisk_table_remove_partition(tb, pa);
+	}
+
+	tb->nents = 0;
+	return 0;
+}
+
+/**
+ * fdisk_ref_table:
+ * @tb: table pointer
+ *
+ * Incremparts reference counter.
+ */
+void fdisk_ref_table(struct fdisk_table *tb)
+{
+	if (tb)
+		tb->refcount++;
+}
+
+/**
+ * fdisk_unref_table:
+ * @tb: table pointer
+ *
+ * De-incremparts reference counter, on zero the @tb is automatically
+ * deallocated.
+ */
+void fdisk_unref_table(struct fdisk_table *tb)
+{
+	if (!tb)
+		return;
+
+	tb->refcount--;
+	if (tb->refcount <= 0) {
+		fdisk_reset_table(tb);
+
+		DBG(TAB, ul_debugobj(tb, "free"));
+		free(tb);
+	}
+}
+
+/**
+ * fdisk_table_is_empty:
+ * @tb: pointer to tab
+ *
+ * Returns: 1 if the table is without filesystems, or 0.
+ */
+int fdisk_table_is_empty(struct fdisk_table *tb)
+{
+	return tb == NULL || list_empty(&tb->parts) ? 1 : 0;
+}
+
+/**
+ * fdisk_table_get_nents:
+ * @tb: pointer to tab
+ *
+ * Returns: number of entries in table.
+ */
+size_t fdisk_table_get_nents(struct fdisk_table *tb)
+{
+	return tb ? tb->nents : 0;
+}
+
+/**
+ * fdisk_table_next_partition:
+ * @tb: tab pointer
+ * @itr: iterator
+ * @pa: returns the next tab entry
+ *
+ * Returns: 0 on success, negative number in case of error or 1 at the end of list.
+ *
+ * Example:
+ * <informalexample>
+ *   <programlisting>
+ *	while(fdisk_table_next_partition(tb, itr, &pa) == 0) {
+ *		...
+ *	}
+ *   </programlisting>
+ * </informalexample>
+ */
+int fdisk_table_next_partition(
+			struct fdisk_table *tb,
+			struct fdisk_iter *itr,
+			struct fdisk_partition **pa)
+{
+	int rc = 1;
+
+	assert(tb);
+	assert(itr);
+	assert(pa);
+
+	if (!tb || !itr || !pa)
+		return -EINVAL;
+	*pa = NULL;
+
+	if (!itr->head)
+		FDISK_ITER_INIT(itr, &tb->parts);
+	if (itr->p != itr->head) {
+		FDISK_ITER_ITERATE(itr, *pa, struct fdisk_partition, parts);
+		rc = 0;
+	}
+
+	return rc;
+}
+
+struct fdisk_partition *fdisk_table_get_partition(
+			struct fdisk_table *tb,
+			size_t n)
+{
+	struct fdisk_partition *pa = NULL;
+	struct fdisk_iter itr;
+
+	if (!tb)
+		return NULL;
+
+	fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+	while (fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+		if (n == 0)
+			return pa;
+		n--;
+	}
+
+	return NULL;
+}
+
+/**
+ * fdisk_table_add_partition
+ * @tb: tab pointer
+ * @pa: new entry
+ *
+ * Adds a new entry to table and increment @pa reference counter. Don't forget to
+ * use fdisk_unref_pa() after fdisk_table_add_partition() if you want to keep
+ * the @pa referenced by the table only.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa)
+{
+	assert(tb);
+	assert(pa);
+
+	if (!tb || !pa)
+		return -EINVAL;
+
+	fdisk_ref_partition(pa);
+	list_add_tail(&pa->parts, &tb->parts);
+	tb->nents++;
+
+	DBG(TAB, ul_debugobj(tb, "add entry %p [start=%ju, end=%ju, size=%ju, %s %s %s]",
+			pa,
+			(uintmax_t) fdisk_partition_get_start(pa),
+			(uintmax_t) fdisk_partition_get_end(pa),
+			(uintmax_t) fdisk_partition_get_size(pa),
+			fdisk_partition_is_freespace(pa) ? "freespace" : "",
+			fdisk_partition_is_nested(pa)    ? "nested"    : "",
+			fdisk_partition_is_container(pa) ? "container" : "primary"));
+	return 0;
+}
+
+/* inserts @pa after @poz */
+static int table_insert_partition(
+			struct fdisk_table *tb,
+			struct fdisk_partition *poz,
+			struct fdisk_partition *pa)
+{
+	assert(tb);
+	assert(pa);
+
+	fdisk_ref_partition(pa);
+	if (poz)
+		list_add(&pa->parts, &poz->parts);
+	else
+		list_add(&pa->parts, &tb->parts);
+	tb->nents++;
+
+	DBG(TAB, ul_debugobj(tb, "insert entry %p pre=%p [start=%ju, end=%ju, size=%ju, %s %s %s]",
+			pa, poz ? poz : NULL,
+			(uintmax_t) fdisk_partition_get_start(pa),
+			(uintmax_t) fdisk_partition_get_end(pa),
+			(uintmax_t) fdisk_partition_get_size(pa),
+			fdisk_partition_is_freespace(pa) ? "freespace" : "",
+			fdisk_partition_is_nested(pa)    ? "nested"    : "",
+			fdisk_partition_is_container(pa) ? "container" : ""));
+	return 0;
+}
+
+/**
+ * fdisk_table_remove_partition
+ * @tb: tab pointer
+ * @pa: new entry
+ *
+ * Removes the @pa from the table and de-increment reference counter of the @pa. The
+ * partition with zero reference counter will be deallocated. Don't forget to use
+ * fdisk_ref_partition() before call fdisk_table_remove_partition() if you want
+ * to use @pa later.
+ *
+ * Returns: 0 on success or negative number in case of error.
+ */
+int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa)
+{
+	assert(tb);
+	assert(pa);
+
+	if (!tb || !pa)
+		return -EINVAL;
+
+	DBG(TAB, ul_debugobj(tb, "remove entry %p", pa));
+	list_del(&pa->parts);
+	INIT_LIST_HEAD(&pa->parts);
+
+	fdisk_unref_partition(pa);
+	tb->nents--;
+
+	return 0;
+}
+
+/**
+ * fdisk_get_partitions
+ * @cxt: fdisk context
+ * @tb: returns table
+ *
+ * This function adds partitions from disklabel to @table, it allocates a new
+ * table if if @table points to NULL.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb)
+{
+	size_t i;
+
+	if (!cxt || !cxt->label || !tb)
+		return -EINVAL;
+	if (!cxt->label->op->get_part)
+		return -ENOSYS;
+
+	DBG(CXT, ul_debugobj(cxt, "get table"));
+
+	if (!*tb && !(*tb = fdisk_new_table()))
+		return -ENOMEM;
+
+	for (i = 0; i < cxt->label->nparts_max; i++) {
+		struct fdisk_partition *pa = NULL;
+
+		if (fdisk_get_partition(cxt, i, &pa) != 0)
+			continue;
+		if (fdisk_partition_is_used(pa))
+			fdisk_table_add_partition(*tb, pa);
+		fdisk_unref_partition(pa);
+	}
+
+	return 0;
+}
+
+static void debug_print_table(struct fdisk_table *tb)
+{
+	struct fdisk_iter itr;
+	struct fdisk_partition *pa;
+
+	fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+	while (fdisk_table_next_partition(tb, &itr, &pa) == 0)
+		ul_debugobj(tb, "partition %p [partno=%zu, start=%ju, end=%ju, size=%ju] ",
+			    pa, pa->partno,
+			    (uintmax_t) fdisk_partition_get_start(pa),
+			    (uintmax_t) fdisk_partition_get_end(pa),
+			    (uintmax_t) fdisk_partition_get_size(pa));
+
+}
+
+
+typedef	int (*fdisk_partcmp_t)(struct fdisk_partition *, struct fdisk_partition *);
+
+static int cmp_parts_wrapper(struct list_head *a, struct list_head *b, void *data)
+{
+	struct fdisk_partition *pa = list_entry(a, struct fdisk_partition, parts),
+			       *pb = list_entry(b, struct fdisk_partition, parts);
+
+	fdisk_partcmp_t cmp = (fdisk_partcmp_t) data;
+
+	return cmp(pa, pb);
+}
+
+
+/**
+ * fdisk_table_sort_partitions:
+ * @tb: table
+ * @cmp: compare function
+ *
+ * Sort partition in the table.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_table_sort_partitions(struct fdisk_table *tb,
+			int (*cmp)(struct fdisk_partition *,
+				   struct fdisk_partition *))
+{
+	if (!tb)
+		return -EINVAL;
+
+	DBG(TAB, ul_debugobj(tb, "Before sort:"));
+	ON_DBG(TAB, debug_print_table(tb));
+
+	list_sort(&tb->parts, cmp_parts_wrapper, (void *) cmp);
+
+	DBG(TAB, ul_debugobj(tb, "After sort:"));
+	ON_DBG(TAB, debug_print_table(tb));
+
+	return 0;
+}
+
+/* allocates a new freespace description */
+static int new_freespace(struct fdisk_context *cxt,
+			 fdisk_sector_t start,
+			 fdisk_sector_t end,
+			 struct fdisk_partition *parent,
+			 struct fdisk_partition **pa)
+{
+	assert(cxt);
+	assert(pa);
+
+	*pa = NULL;
+
+	if (start == end)
+		return 0;
+	*pa = fdisk_new_partition();
+	if (!*pa)
+		return -ENOMEM;
+
+	assert(start);
+	assert(end);
+	assert(end > start);
+
+	(*pa)->freespace = 1;
+	(*pa)->start = fdisk_align_lba_in_range(cxt, start, start, end);
+	(*pa)->size = end - (*pa)->start + 1ULL;
+
+	if (parent)
+		(*pa)->parent_partno = parent->partno;
+	return 0;
+}
+
+/* add freespace description to the right place within @tb */
+static int table_add_freespace(
+			struct fdisk_context *cxt,
+			struct fdisk_table *tb,
+			fdisk_sector_t start,
+			fdisk_sector_t end,
+			struct fdisk_partition *parent)
+{
+	struct fdisk_partition *pa, *x, *real_parent = NULL, *best = NULL;
+	struct fdisk_iter itr;
+	int rc = 0;
+
+	assert(tb);
+
+	rc = new_freespace(cxt, start, end, parent, &pa);
+	if (rc)
+		return -ENOMEM;
+	if (!pa)
+		return 0;
+
+	assert(fdisk_partition_has_start(pa));
+	assert(fdisk_partition_has_end(pa));
+
+	DBG(TAB, ul_debugobj(tb, "adding freespace"));
+
+	fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+	if (parent && fdisk_partition_has_partno(parent)) {
+		while (fdisk_table_next_partition(tb, &itr, &x) == 0) {
+			if (!fdisk_partition_has_partno(x))
+				continue;
+			if (x->partno == parent->partno) {
+				real_parent = x;
+				break;
+			}
+		}
+		if (!real_parent) {
+			DBG(TAB, ul_debugobj(tb, "not found freespace parent (partno=%zu)",
+					parent->partno));
+			fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+		}
+	}
+
+	while (fdisk_table_next_partition(tb, &itr, &x) == 0) {
+		fdisk_sector_t end, best_end = 0;
+
+		if (!fdisk_partition_has_end(x))
+			continue;
+
+		end = fdisk_partition_get_end(x);
+		if (best)
+			best_end = fdisk_partition_get_end(best);
+
+		if (end < pa->start && (!best || best_end < end))
+			best = x;
+	}
+
+	if (!best && real_parent)
+		best = real_parent;
+	rc = table_insert_partition(tb, best, pa);
+
+	fdisk_unref_partition(pa);
+
+	DBG(TAB, ul_debugobj(tb, "adding freespace DONE [rc=%d]", rc));
+	return rc;
+}
+
+/* analyze @cont(ainer) in @parts and add all detected freespace into @tb, note
+ * that @parts has to be sorted by partition starts */
+static int check_container_freespace(struct fdisk_context *cxt,
+				     struct fdisk_table *parts,
+				     struct fdisk_table *tb,
+				     struct fdisk_partition *cont)
+{
+	struct fdisk_iter itr;
+	struct fdisk_partition *pa;
+	fdisk_sector_t x, last, grain, lastplusoff;
+	int rc = 0;
+
+	assert(cxt);
+	assert(parts);
+	assert(tb);
+	assert(cont);
+	assert(fdisk_partition_has_start(cont));
+
+	DBG(TAB, ul_debugobj(tb, "analyze container 0x%p", cont));
+
+	last = fdisk_partition_get_start(cont);
+	grain = cxt->grain > cxt->sector_size ?	cxt->grain / cxt->sector_size : 1;
+	fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+	DBG(CXT, ul_debugobj(cxt, "initialized:  last=%ju, grain=%ju", last, grain));
+
+	while (fdisk_table_next_partition(parts, &itr, &pa) == 0) {
+
+		DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju", pa->partno, pa->start));
+
+		if (!pa->used || !fdisk_partition_is_nested(pa)
+			      || !fdisk_partition_has_start(pa))
+			continue;
+
+		DBG(CXT, ul_debugobj(cxt, "freespace container analyze: partno=%zu, start=%ju, end=%ju",
+					pa->partno,
+					(uintmax_t) fdisk_partition_get_start(pa),
+					(uintmax_t) fdisk_partition_get_end(pa)));
+
+		lastplusoff = last + cxt->first_lba;
+		if (pa->start > lastplusoff && pa->start - lastplusoff > grain)
+			rc = table_add_freespace(cxt, tb, lastplusoff, pa->start, cont);
+		if (rc)
+			goto done;
+		last = fdisk_partition_get_end(pa);
+	}
+
+	/* free-space remaining in extended partition */
+	x = fdisk_partition_get_start(cont) + fdisk_partition_get_size(cont) - 1;
+	lastplusoff = last + cxt->first_lba;
+	if (lastplusoff < x && x - lastplusoff > grain) {
+		DBG(TAB, ul_debugobj(tb, "add remaining space in container 0x%p", cont));
+		rc = table_add_freespace(cxt, tb, lastplusoff, x, cont);
+	}
+
+done:
+	DBG(TAB, ul_debugobj(tb, "analyze container 0x%p DONE [rc=%d]", cont, rc));
+	return rc;
+}
+
+
+/**
+ * fdisk_get_freespaces
+ * @cxt: fdisk context
+ * @tb: returns table
+ *
+ * This function adds freespace (described by fdisk_partition) to @table, it
+ * allocates a new table if the @table points to NULL.
+ *
+ * Note that free space smaller than grain (see fdisk_get_grain()) is ignored.
+
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb)
+{
+	int rc = 0;
+	fdisk_sector_t last, grain;
+	struct fdisk_table *parts = NULL;
+	struct fdisk_partition *pa;
+	struct fdisk_iter itr;
+
+	DBG(CXT, ul_debugobj(cxt, "get freespace"));
+
+	if (!cxt || !cxt->label || !tb)
+		return -EINVAL;
+	if (!*tb && !(*tb = fdisk_new_table()))
+		return -ENOMEM;
+
+	rc = fdisk_get_partitions(cxt, &parts);
+	if (rc)
+		goto done;
+
+	fdisk_table_sort_partitions(parts, fdisk_partition_cmp_start);
+	fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+	last = cxt->first_lba;
+	grain = cxt->grain > cxt->sector_size ?	cxt->grain / cxt->sector_size : 1;
+
+	DBG(CXT, ul_debugobj(cxt, "initialized:  last=%ju, grain=%ju", last, grain));
+
+	/* analyze gaps between partitions */
+	while (rc == 0 && fdisk_table_next_partition(parts, &itr, &pa) == 0) {
+
+		DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju", pa->partno, pa->start));
+
+		if (!pa->used || pa->wholedisk || fdisk_partition_is_nested(pa)
+			      || !fdisk_partition_has_start(pa))
+			continue;
+		DBG(CXT, ul_debugobj(cxt, "freespace analyze: partno=%zu, start=%ju, end=%ju",
+					pa->partno,
+					(uintmax_t) fdisk_partition_get_start(pa),
+					(uintmax_t) fdisk_partition_get_end(pa)));
+		if (last + grain <= pa->start) {
+			rc = table_add_freespace(cxt, *tb,
+				last + (last > cxt->first_lba ? 1 : 0),
+				pa->start - 1, NULL);
+		}
+		/* add gaps between logical partitions */
+		if (fdisk_partition_is_container(pa))
+			rc = check_container_freespace(cxt, parts, *tb, pa);
+		last = fdisk_partition_get_end(pa);
+	}
+
+	/* add free-space behind last partition to the end of the table (so
+	 * don't use table_add_freespace()) */
+	if (rc == 0 && last + grain < cxt->total_sectors - 1) {
+		DBG(CXT, ul_debugobj(cxt, "freespace behind last partition detected"));
+		rc = new_freespace(cxt,
+			last + (last > cxt->first_lba ? 1 : 0),
+			cxt->last_lba, NULL, &pa);
+		if (pa) {
+			fdisk_table_add_partition(*tb, pa);
+			fdisk_unref_partition(pa);
+		}
+	}
+
+done:
+	fdisk_unref_table(parts);
+
+	DBG(CXT, ul_debugobj(cxt, "get freespace DONE [rc=%d]", rc));
+	return rc;
+}
+
+/**
+ * fdisk_table_wrong_order:
+ * @tb: table
+ *
+ * Returns: 1 of the table is not in disk order
+ */
+int fdisk_table_wrong_order(struct fdisk_table *tb)
+{
+	struct fdisk_partition *pa;
+	struct fdisk_iter itr;
+	fdisk_sector_t last = 0;
+
+	DBG(TAB, ul_debugobj(tb, "wrong older check"));
+
+	fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+	while (tb && fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+		if (!fdisk_partition_has_start(pa))
+			continue;
+		if (pa->start < last)
+			return 1;
+		last = pa->start;
+	}
+	return 0;
+}
+
+/**
+ * fdisk_apply_table:
+ * @cxt: context
+ * @tb: table
+ *
+ * Add partitions from table @tb to the in-memory disk label. See
+ * fdisk_add_partition(), fdisk_delete_all_partitions(). The partitons
+ * that does not define start (or does not follow the default start)
+ * are ingored.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb)
+{
+	struct fdisk_partition *pa;
+	struct fdisk_iter itr;
+	int rc = 0;
+
+	assert(cxt);
+	assert(tb);
+
+	DBG(TAB, ul_debugobj(tb, "applying to context %p", cxt));
+
+	fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+	while (tb && fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+		if (!fdisk_partition_has_start(pa) && !pa->start_follow_default)
+			continue;
+		rc = fdisk_add_partition(cxt, pa, NULL);
+		if (rc)
+			break;
+	}
+
+	return rc;
+}
+
diff --git a/libblkid/libfdisk/src/test.c b/libblkid/libfdisk/src/test.c
new file mode 100644
index 0000000..31ed7e0
--- /dev/null
+++ b/libblkid/libfdisk/src/test.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Routines for TEST_PROGRAMs
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#ifndef TEST_PROGRAM
+#define TEST_PROGRAM
+#endif
+
+#include "fdiskP.h"
+
+int fdisk_run_test(struct fdisk_test *tests, int argc, char *argv[])
+{
+	int rc = -1;
+	struct fdisk_test *ts;
+
+	assert(tests);
+	assert(argc);
+	assert(argv);
+
+	if (argc < 2 ||
+	    strcmp(argv[1], "--help") == 0 ||
+	    strcmp(argv[1], "-h") == 0)
+		goto usage;
+
+	fdisk_init_debug(0);
+
+	for (ts = tests; ts->name; ts++) {
+		if (strcmp(ts->name, argv[1]) == 0) {
+			rc = ts->body(ts, argc - 1, argv + 1);
+			if (rc)
+				printf("FAILED [rc=%d]", rc);
+			break;
+		}
+	}
+
+	if (rc < 0 && ts->name == NULL)
+		goto usage;
+
+	return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+usage:
+	printf("\nUsage:\n\t%s <test> [testoptions]\nTests:\n",
+			program_invocation_short_name);
+	for (ts = tests; ts->name; ts++) {
+		printf("\t%-15s", ts->name);
+		if (ts->usage)
+			printf(" %s\n", ts->usage);
+	}
+	printf("\n");
+	return EXIT_FAILURE;
+}
diff --git a/libblkid/libfdisk/src/utils.c b/libblkid/libfdisk/src/utils.c
new file mode 100644
index 0000000..482a306
--- /dev/null
+++ b/libblkid/libfdisk/src/utils.c
@@ -0,0 +1,154 @@
+
+#include "fdiskP.h"
+#include "pathnames.h"
+
+#include <ctype.h>
+
+/**
+ * SECTION: utils
+ * @title: Utils
+ * @short_description: misc fdisk functions
+ */
+
+/*
+ * Zeros in-memory first sector buffer
+ */
+int fdisk_init_firstsector_buffer(struct fdisk_context *cxt)
+{
+	if (!cxt)
+		return -EINVAL;
+
+	if (!cxt->firstsector || cxt->firstsector_bufsz != cxt->sector_size) {
+		/* Let's allocate a new buffer if no allocated yet, or the
+		 * current buffer has incorrect size */
+		if (!cxt->parent || cxt->parent->firstsector != cxt->firstsector)
+			free(cxt->firstsector);
+
+		DBG(CXT, ul_debugobj(cxt, "initialize in-memory first sector "
+				"buffer [sector_size=%lu]", cxt->sector_size));
+		cxt->firstsector = calloc(1, cxt->sector_size);
+		if (!cxt->firstsector)
+			return -ENOMEM;
+
+		cxt->firstsector_bufsz = cxt->sector_size;
+		return 0;
+	}
+
+	DBG(CXT, ul_debugobj(cxt, "zeroize in-memory first sector buffer"));
+	memset(cxt->firstsector, 0, cxt->firstsector_bufsz);
+	return 0;
+}
+
+int fdisk_read_firstsector(struct fdisk_context *cxt)
+{
+	ssize_t r;
+	int rc;
+
+	assert(cxt);
+	assert(cxt->sector_size);
+
+	rc = fdisk_init_firstsector_buffer(cxt);
+	if (rc)
+		return rc;
+
+	assert(cxt->sector_size == cxt->firstsector_bufsz);
+
+	DBG(CXT, ul_debugobj(cxt, "reading first sector "
+				"buffer [sector_size=%lu]", cxt->sector_size));
+
+	r = lseek(cxt->dev_fd, 0, SEEK_SET);
+	if (r == -1)
+	{
+		DBG(CXT, ul_debugobj(cxt, "failed to seek to first sector %m"));
+		return -errno;
+	}
+
+	r = read(cxt->dev_fd, cxt->firstsector, cxt->sector_size);
+
+	if (r != cxt->sector_size) {
+		if (!errno)
+			errno = EINVAL;	/* probably too small file/device */
+		DBG(CXT, ul_debugobj(cxt, "failed to read first sector %m"));
+		return -errno;
+	}
+
+	return 0;
+}
+
+/**
+ * fdisk_partname:
+ * @dev: device name
+ * @partno: partition name
+ *
+ * Return: allocated buffer with partition name, use free() to deallocate.
+ */
+char *fdisk_partname(const char *dev, size_t partno)
+{
+	char *res = NULL;
+	const char *p = "";
+	int w = 0;
+
+	if (!dev || !*dev) {
+		if (asprintf(&res, "%zd", partno) > 0)
+			return res;
+		return NULL;
+	}
+
+	w = strlen(dev);
+	if (isdigit(dev[w - 1]))
+#ifdef __GNU__
+		p = "s";
+#else
+		p = "p";
+#endif
+
+	/* devfs kludge - note: fdisk partition names are not supposed
+	   to equal kernel names, so there is no reason to do this */
+	if (strcmp(dev + w - 4, "disc") == 0) {
+		w -= 4;
+		p = "part";
+	}
+
+	/* udev names partitions by appending -partN
+	   e.g. ata-SAMSUNG_SV8004H_0357J1FT712448-part1 */
+	if ((strncmp(dev, _PATH_DEV_BYID, sizeof(_PATH_DEV_BYID) - 1) == 0) ||
+	     strncmp(dev, _PATH_DEV_BYPATH, sizeof(_PATH_DEV_BYPATH) - 1) == 0) {
+	       p = "-part";
+	}
+
+	if (asprintf(&res, "%.*s%s%zu", w, dev, p, partno) > 0)
+		return res;
+
+	return NULL;
+}
+
+#ifdef TEST_PROGRAM
+struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt) { return NULL; }
+struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt) { return NULL; }
+
+int test_partnames(struct fdisk_test *ts, int argc, char *argv[])
+{
+	size_t i;
+	const char *disk = argv[1];
+
+	for (i = 0; i < 5; i++) {
+		char *p = fdisk_partname(disk, i + 1);
+		if (p)
+			printf("%zu: '%s'\n", i + 1, p);
+		free(p);
+	}
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	struct fdisk_test tss[] = {
+		{ "--partnames",  test_partnames,  "<diskname>" },
+		{ NULL }
+	};
+
+	return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libblkid/libuuid/COPYING b/libblkid/libuuid/COPYING
new file mode 100644
index 0000000..0e902cf
--- /dev/null
+++ b/libblkid/libuuid/COPYING
@@ -0,0 +1,5 @@
+This library is free software; you can redistribute it and/or
+modify it under the terms of the Modified BSD License.
+
+The complete text of the license is available in the
+../Documentation/licenses/COPYING.BSD-3 file.
diff --git a/libblkid/libuuid/Makemodule.am b/libblkid/libuuid/Makemodule.am
new file mode 100644
index 0000000..166be5c
--- /dev/null
+++ b/libblkid/libuuid/Makemodule.am
@@ -0,0 +1,10 @@
+if BUILD_LIBUUID
+
+include libuuid/man/Makemodule.am
+include libuuid/src/Makemodule.am
+
+pkgconfig_DATA += libuuid/uuid.pc
+PATHFILES      += libuuid/uuid.pc
+EXTRA_DIST     += libuuid/COPYING
+
+endif # BUILD_LIBUUID
diff --git a/libblkid/libuuid/man/.gitignore b/libblkid/libuuid/man/.gitignore
new file mode 100644
index 0000000..7957ad2
--- /dev/null
+++ b/libblkid/libuuid/man/.gitignore
@@ -0,0 +1,3 @@
+uuid_generate_random.3
+uuid_generate_time.3
+uuid_generate_time_safe.3
diff --git a/libblkid/libuuid/man/Makemodule.am b/libblkid/libuuid/man/Makemodule.am
new file mode 100644
index 0000000..81287d5
--- /dev/null
+++ b/libblkid/libuuid/man/Makemodule.am
@@ -0,0 +1,14 @@
+
+dist_man_MANS += \
+	libuuid/man/uuid.3 \
+	libuuid/man/uuid_clear.3 \
+	libuuid/man/uuid_compare.3 \
+	libuuid/man/uuid_copy.3 \
+	libuuid/man/uuid_generate.3 \
+	libuuid/man/uuid_is_null.3 \
+	libuuid/man/uuid_parse.3 \
+	libuuid/man/uuid_time.3 \
+	libuuid/man/uuid_unparse.3 \
+	libuuid/man/uuid_generate_random.3 \
+	libuuid/man/uuid_generate_time.3 \
+	libuuid/man/uuid_generate_time_safe.3
diff --git a/libblkid/libuuid/man/uuid.3 b/libblkid/libuuid/man/uuid.3
new file mode 100644
index 0000000..37b0499
--- /dev/null
+++ b/libblkid/libuuid/man/uuid.3
@@ -0,0 +1,65 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" %Begin-Header%
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, and the entire permission notice in its entirety,
+.\"    including the disclaimer of warranties.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\"    products derived from this software without specific prior
+.\"    written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+.\" WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+.\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+.\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+.\" USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+.\" DAMAGE.
+.\" %End-Header%
+.\"
+.\" Created  Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID 3 "May 2009" "util-linux" "Libuuid API"
+.SH NAME
+uuid \- DCE compatible Universally Unique Identifier library
+.SH SYNOPSIS
+.B #include <uuid.h>
+.SH DESCRIPTION
+The UUID library is used to generate unique identifiers for objects
+that may be accessible beyond the local system.  This library
+generates UUIDs compatible with those created by the Open Software
+Foundation (OSF) Distributed Computing Environment (DCE) utility
+.BR uuidgen .
+.sp
+The UUIDs generated by this library can be reasonably expected to be
+unique within a system, and unique across all systems.  They could
+be used, for instance, to generate unique HTTP cookies across multiple
+web servers without communication between the servers, and without fear
+of a name clash.
+.SH "CONFORMING TO"
+OSF DCE 1.1
+.SH AUTHOR
+Theodore Y.\& Ts'o
+.SH AVAILABILITY
+.B libuuid
+is part of the util-linux package since version 2.15.1 and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+.SH "SEE ALSO"
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3),
+.BR uuid_parse (3),
+.BR uuid_time (3),
+.BR uuid_unparse (3)
diff --git a/libblkid/libuuid/man/uuid_clear.3 b/libblkid/libuuid/man/uuid_clear.3
new file mode 100644
index 0000000..70fca02
--- /dev/null
+++ b/libblkid/libuuid/man/uuid_clear.3
@@ -0,0 +1,62 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" %Begin-Header%
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, and the entire permission notice in its entirety,
+.\"    including the disclaimer of warranties.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\"    products derived from this software without specific prior
+.\"    written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+.\" WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+.\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+.\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+.\" USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+.\" DAMAGE.
+.\" %End-Header%
+.\"
+.\" Created  Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_CLEAR 3 "May 2009" "util-linux" "Libuuid API"
+.SH NAME
+uuid_clear \- reset value of UUID variable to the NULL value
+.SH SYNOPSIS
+.nf
+.B #include <uuid.h>
+.sp
+.BI "void uuid_clear(uuid_t " uu );
+.fi
+.SH DESCRIPTION
+The
+.B uuid_clear
+function sets the value of the supplied uuid variable
+.I uu
+to the NULL value.
+.SH AUTHOR
+Theodore Y.\& Ts'o
+.SH AVAILABILITY
+.B libuuid
+is part of the util-linux package since version 2.15.1 and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+.na
+.SH "SEE ALSO"
+.BR uuid (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3),
+.BR uuid_parse (3),
+.BR uuid_unparse (3)
+.ad
diff --git a/libblkid/libuuid/man/uuid_compare.3 b/libblkid/libuuid/man/uuid_compare.3
new file mode 100644
index 0000000..f91181a
--- /dev/null
+++ b/libblkid/libuuid/man/uuid_compare.3
@@ -0,0 +1,68 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" %Begin-Header%
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, and the entire permission notice in its entirety,
+.\"    including the disclaimer of warranties.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\"    products derived from this software without specific prior
+.\"    written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+.\" WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+.\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+.\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+.\" USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+.\" DAMAGE.
+.\" %End-Header%
+.\"
+.\" Created  Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_COMPARE 3 "May 2009" "util-linux" "Libuuid API"
+.SH NAME
+uuid_compare \- compare whether two UUIDs are the same
+.SH SYNOPSIS
+.nf
+.B #include <uuid.h>
+.sp
+.BI "int uuid_compare(uuid_t " uu1 ", uuid_t " uu2)
+.fi
+.SH DESCRIPTION
+The
+.B uuid_compare
+function compares the two supplied uuid variables
+.IR uu1 " and " uu2
+to each other.
+.SH RETURN VALUE
+Returns an integer less than, equal to, or greater than zero if
+.I uu1
+is found,  respectively, to be lexicographically less than, equal, or
+greater than
+.IR uu2 .
+.SH AUTHOR
+Theodore Y.\& Ts'o
+.SH AVAILABILITY
+.B libuuid
+is part of the util-linux package since version 2.15.1 and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+.na
+.SH "SEE ALSO"
+.BR uuid (3),
+.BR uuid_clear (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3),
+.BR uuid_parse (3),
+.BR uuid_unparse (3)
+.ad
diff --git a/libblkid/libuuid/man/uuid_copy.3 b/libblkid/libuuid/man/uuid_copy.3
new file mode 100644
index 0000000..5159fa6
--- /dev/null
+++ b/libblkid/libuuid/man/uuid_copy.3
@@ -0,0 +1,64 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" %Begin-Header%
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, and the entire permission notice in its entirety,
+.\"    including the disclaimer of warranties.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\"    products derived from this software without specific prior
+.\"    written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+.\" WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+.\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+.\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+.\" USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+.\" DAMAGE.
+.\" %End-Header%
+.\"
+.\" Created  Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_COPY 3 "May 2009" "util-linux" "Libuuid API"
+.SH NAME
+uuid_copy \- copy a UUID value
+.SH SYNOPSIS
+.nf
+.B #include <uuid.h>
+.sp
+.BI "void uuid_copy(uuid_t " dst ", uuid_t " src);
+.fi
+.SH DESCRIPTION
+The
+.B uuid_copy
+function copies the UUID variable
+.IR src " to " dst .
+.SH RETURN VALUE
+The copied UUID is returned in the location pointed to by
+.IR dst .
+.SH AUTHOR
+Theodore Y.\& Ts'o
+.SH AVAILABILITY
+.B libuuid
+is part of the util-linux package since version 2.15.1 and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+.na
+.SH "SEE ALSO"
+.BR uuid (3),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3),
+.BR uuid_parse (3),
+.BR uuid_unparse (3)
+.ad
diff --git a/libblkid/libuuid/man/uuid_generate.3 b/libblkid/libuuid/man/uuid_generate.3
new file mode 100644
index 0000000..19904d7
--- /dev/null
+++ b/libblkid/libuuid/man/uuid_generate.3
@@ -0,0 +1,126 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" %Begin-Header%
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, and the entire permission notice in its entirety,
+.\"    including the disclaimer of warranties.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\"    products derived from this software without specific prior
+.\"    written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+.\" WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+.\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+.\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+.\" USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+.\" DAMAGE.
+.\" %End-Header%
+.\"
+.\" Created  Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_GENERATE 3 "May 2009" "util-linux" "Libuuid API"
+.SH NAME
+uuid_generate, uuid_generate_random, uuid_generate_time,
+uuid_generate_time_safe \- create a new unique UUID value
+.SH SYNOPSIS
+.nf
+.B #include <uuid.h>
+.sp
+.BI "void uuid_generate(uuid_t " out );
+.BI "void uuid_generate_random(uuid_t " out );
+.BI "void uuid_generate_time(uuid_t " out );
+.BI "int uuid_generate_time_safe(uuid_t " out );
+.fi
+.SH DESCRIPTION
+The
+.B uuid_generate
+function creates a new universally unique identifier (UUID).  The uuid will
+be generated based on high-quality randomness from
+.IR /dev/urandom ,
+if available.  If it is not available, then
+.B uuid_generate
+will use an alternative algorithm which uses the current time, the
+local ethernet MAC address (if available), and random data generated
+using a pseudo-random generator.
+.sp
+The
+.B uuid_generate_random
+function forces the use of the all-random UUID format, even if
+a high-quality random number generator (i.e.,
+.IR /dev/urandom )
+is not available, in which case a pseudo-random
+generator will be substituted.  Note that the use of a pseudo-random
+generator may compromise the uniqueness of UUIDs
+generated in this fashion.
+.sp
+The
+.B uuid_generate_time
+function forces the use of the alternative algorithm which uses the
+current time and the local ethernet MAC address (if available).
+This algorithm used to be the default one used to generate UUID, but
+because of the use of the ethernet MAC address, it can leak
+information about when and where the UUID was generated.  This can cause
+privacy problems in some applications, so the
+.B uuid_generate
+function only uses this algorithm if a high-quality source of
+randomness is not available.  To guarantee uniqueness of UUIDs generated
+by concurrently running processes, the uuid library uses global
+clock state counter (if the process has permissions to gain exclusive access
+to this file) and/or the
+.B uuidd
+daemon, if it is running already or can be spawned by the process (if
+installed and the process has enough permissions to run it).  If neither of
+these two synchronization mechanisms can be used, it is theoretically possible
+that two concurrently running processes obtain the same UUID(s).  To tell
+whether the UUID has been generated in a safe manner, use
+.BR uuid_generate_time_safe .
+.sp
+The
+.B uuid_generate_time_safe
+is similar to
+.BR uuid_generate_time ,
+except that it returns a value which denotes whether any of the synchronization
+mechanisms (see above) has been used.
+.sp
+The UUID is 16 bytes (128 bits) long, which gives approximately 3.4x10^38
+unique values (there are approximately 10^80 elementary particles in
+the universe according to Carl Sagan's
+.IR Cosmos ).
+The new UUID can reasonably be considered unique among all UUIDs created
+on the local system, and among UUIDs created on other systems in the past
+and in the future.
+.SH RETURN VALUE
+The newly created UUID is returned in the memory location pointed to by
+.IR out .
+.B uuid_generate_time_safe
+returns zero if the UUID has been generated in a safe manner, \-1 otherwise.
+.SH "CONFORMING TO"
+OSF DCE 1.1
+.SH AUTHOR
+Theodore Y.\& Ts'o
+.SH AVAILABILITY
+.B libuuid
+is part of the util-linux package since version 2.15.1 and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+.SH "SEE ALSO"
+.BR uuid (3),
+.BR uuidgen (1),
+.BR uuidd (8),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_is_null (3),
+.BR uuid_parse (3),
+.BR uuid_time (3),
+.BR uuid_unparse (3)
diff --git a/libblkid/libuuid/man/uuid_is_null.3 b/libblkid/libuuid/man/uuid_is_null.3
new file mode 100644
index 0000000..86a7a50
--- /dev/null
+++ b/libblkid/libuuid/man/uuid_is_null.3
@@ -0,0 +1,64 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" %Begin-Header%
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, and the entire permission notice in its entirety,
+.\"    including the disclaimer of warranties.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\"    products derived from this software without specific prior
+.\"    written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+.\" WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+.\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+.\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+.\" USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+.\" DAMAGE.
+.\" %End-Header%
+.\"
+.\" Created  Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_IS_NULL 3 "May 2009" "util-linux" "Libuuid API"
+.SH NAME
+uuid_is_null \- compare the value of the UUID to the NULL value
+.SH SYNOPSIS
+.nf
+.B #include <uuid.h>
+.sp
+.BI "int uuid_is_null(uuid_t " uu );
+.fi
+.SH DESCRIPTION
+The
+.B uuid_is_null
+function compares the value of the supplied UUID variable
+.I uu
+to the NULL value.  If the value is equal to the NULL UUID, 1 is returned,
+otherwise 0 is returned.
+.SH AUTHOR
+Theodore Y.\& Ts'o
+.SH AVAILABILITY
+.B libuuid
+is part of the util-linux package since version 2.15.1 and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+.na
+.SH "SEE ALSO"
+.BR uuid (3),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_time (3),
+.BR uuid_parse (3),
+.BR uuid_unparse (3)
+.ad
diff --git a/libblkid/libuuid/man/uuid_parse.3 b/libblkid/libuuid/man/uuid_parse.3
new file mode 100644
index 0000000..31a5926
--- /dev/null
+++ b/libblkid/libuuid/man/uuid_parse.3
@@ -0,0 +1,73 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" %Begin-Header%
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, and the entire permission notice in its entirety,
+.\"    including the disclaimer of warranties.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\"    products derived from this software without specific prior
+.\"    written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+.\" WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+.\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+.\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+.\" USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+.\" DAMAGE.
+.\" %End-Header%
+.\"
+.\" Created  Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_PARSE 3 "May 2009" "util-linux" "Libuuid API"
+.SH NAME
+uuid_parse \- convert an input UUID string into binary representation
+.SH SYNOPSIS
+.nf
+.B #include <uuid.h>
+.sp
+.BI "int uuid_parse( char *" in ", uuid_t " uu );
+.fi
+.SH DESCRIPTION
+The
+.B uuid_parse
+function converts the UUID string given by
+.I in
+into the binary representation.  The input UUID is a string of the form
+1b4e28ba\-2fa1\-11d2\-883f\-b9a761bde3fb (in
+.BR printf (3)
+format "%08x\-%04x\-%04x\-%04x\-%012x", 36 bytes plus the trailing '\e0').
+.SH RETURN VALUE
+Upon successfully parsing the input string, 0 is returned, and the UUID is
+stored in the location pointed to by
+.IR uu ,
+otherwise \-1 is returned.
+.SH "CONFORMING TO"
+OSF DCE 1.1
+.SH AUTHOR
+Theodore Y.\& Ts'o
+.SH AVAILABILITY
+.B libuuid
+is part of the util-linux package since version 2.15.1 and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+.na
+.SH "SEE ALSO"
+.BR uuid (3),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3),
+.BR uuid_time (3),
+.BR uuid_unparse (3)
+.ad
diff --git a/libblkid/libuuid/man/uuid_time.3 b/libblkid/libuuid/man/uuid_time.3
new file mode 100644
index 0000000..483676b
--- /dev/null
+++ b/libblkid/libuuid/man/uuid_time.3
@@ -0,0 +1,78 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" %Begin-Header%
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, and the entire permission notice in its entirety,
+.\"    including the disclaimer of warranties.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\"    products derived from this software without specific prior
+.\"    written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+.\" WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+.\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+.\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+.\" USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+.\" DAMAGE.
+.\" %End-Header%
+.\"
+.\" Created  Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_TIME 3 "May 2009" "util-linux" "Libuuid API"
+.SH NAME
+uuid_time \- extract the time at which the UUID was created
+.SH SYNOPSIS
+.nf
+.B #include <uuid.h>
+.sp
+.BI "time_t uuid_time(uuid_t " uu ", struct timeval *" ret_tv )
+.fi
+.SH DESCRIPTION
+The
+.B uuid_time
+function extracts the time at which the supplied time-based UUID
+.I uu
+was created.  Note that the UUID creation time is only encoded within
+certain types of UUIDs.  This function can only reasonably expect to
+extract the creation time for UUIDs created with the
+.BR uuid_generate_time (3)
+and
+.BR uuid_generate_time_safe (3)
+functions.  It may or may not work with UUIDs created by other mechanisms.
+.SH "RETURN VALUES"
+The time at which the UUID was created, in seconds since January 1, 1970 GMT
+(the epoch), is returned (see
+.BR time "(2))."
+The time at which the UUID was created, in seconds and microseconds since
+the epoch, is also stored in the location pointed to by
+.I ret_tv
+(see
+.BR gettimeofday "(2))."
+.SH AUTHOR
+Theodore Y.\& Ts'o
+.SH AVAILABILITY
+.B libuuid
+is part of the util-linux package since version 2.15.1 and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+.na
+.SH "SEE ALSO"
+.BR uuid (3),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_is_null (3),
+.BR uuid_parse (3),
+.BR uuid_unparse (3)
+.ad
diff --git a/libblkid/libuuid/man/uuid_unparse.3 b/libblkid/libuuid/man/uuid_unparse.3
new file mode 100644
index 0000000..1e0116d
--- /dev/null
+++ b/libblkid/libuuid/man/uuid_unparse.3
@@ -0,0 +1,81 @@
+.\" Copyright 1999 Andreas Dilger (adilger@enel.ucalgary.ca)
+.\"
+.\" %Begin-Header%
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, and the entire permission notice in its entirety,
+.\"    including the disclaimer of warranties.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\"    products derived from this software without specific prior
+.\"    written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+.\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+.\" WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+.\" LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+.\" OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+.\" BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+.\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+.\" USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+.\" DAMAGE.
+.\" %End-Header%
+.\"
+.\" Created  Wed Mar 10 17:42:12 1999, Andreas Dilger
+.TH UUID_UNPARSE 3 "May 2009" "util-linux" "Libuuid API"
+.SH NAME
+uuid_unparse \- convert an UUID from binary representation to a string
+.SH SYNOPSIS
+.nf
+.B #include <uuid.h>
+.sp
+.BI "void uuid_unparse(uuid_t " uu ", char *" out );
+.BI "void uuid_unparse_upper(uuid_t " uu ", char *" out );
+.BI "void uuid_unparse_lower(uuid_t " uu ", char *" out );
+.fi
+.SH DESCRIPTION
+The
+.B uuid_unparse
+function converts the supplied UUID
+.I uu
+from the binary representation into a 36-byte string (plus tailing '\e0')
+of the form 1b4e28ba\-2fa1\-11d2\-883f\-0016d3cca427 and stores this
+value in the character string pointed to by
+.IR out .
+The case of the hex digits returned by
+.B uuid_unparse
+may be upper or lower case, and is
+dependent on the system-dependent local default.
+.PP
+If the case of the
+hex digits is important then the functions
+.B uuid_unparse_upper
+and
+.B uuid_unparse_lower
+may be used.
+.SH "CONFORMING TO"
+OSF DCE 1.1
+.SH AUTHOR
+Theodore Y.\& Ts'o
+.SH AVAILABILITY
+.B libuuid
+is part of the util-linux package since version 2.15.1 and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
+.na
+.SH "SEE ALSO"
+.BR uuid (3),
+.BR uuid_clear (3),
+.BR uuid_compare (3),
+.BR uuid_copy (3),
+.BR uuid_generate (3),
+.BR uuid_time (3),
+.BR uuid_is_null (3),
+.BR uuid_parse (3)
+.ad
diff --git a/libblkid/libuuid/src/Makemodule.am b/libblkid/libuuid/src/Makemodule.am
new file mode 100644
index 0000000..061aff2
--- /dev/null
+++ b/libblkid/libuuid/src/Makemodule.am
@@ -0,0 +1,61 @@
+
+check_PROGRAMS += test_uuid
+test_uuid_SOURCES = libuuid/src/test_uuid.c
+test_uuid_LDADD = libuuid.la $(SOCKET_LIBS)
+test_uuid_CFLAGS = -I$(ul_libuuid_incdir)
+
+# includes
+uuidincdir = $(includedir)/uuid
+uuidinc_HEADERS = libuuid/src/uuid.h
+
+usrlib_exec_LTLIBRARIES += libuuid.la
+
+libuuid_la_SOURCES = \
+	libuuid/src/clear.c \
+	libuuid/src/compare.c \
+	libuuid/src/copy.c \
+	libuuid/src/gen_uuid.c \
+	libuuid/src/isnull.c \
+	libuuid/src/pack.c \
+	libuuid/src/parse.c \
+	libuuid/src/unpack.c \
+	libuuid/src/unparse.c \
+	libuuid/src/uuidd.h \
+	libuuid/src/uuidd.h \
+	libuuid/src/uuidP.h \
+	libuuid/src/uuid_time.c \
+	$(uuidinc_HEADERS) \
+	lib/randutils.c
+
+libuuid_la_DEPENDENCIES = libuuid/src/libuuid.sym
+libuuid_la_LIBADD       = $(SOCKET_LIBS)
+
+libuuid_la_CFLAGS = \
+	 $(SOLIB_CFLAGS) \
+	 -I$(ul_libuuid_incdir) \
+	 -Ilibuuid/src
+
+libuuid_la_LDFLAGS = \
+	$(SOLIB_LDFLAGS) \
+	-Wl,--version-script=$(top_srcdir)/libuuid/src/libuuid.sym \
+	-version-info $(LIBUUID_VERSION_INFO)
+
+EXTRA_DIST += libuuid/src/libuuid.sym
+
+# move lib from $(usrlib_execdir) to $(libdir) if needed
+install-exec-hook-libuuid:
+	if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libuuid.so"; then \
+		mkdir -p $(DESTDIR)$(libdir); \
+		mv $(DESTDIR)$(usrlib_execdir)/libuuid.so.* $(DESTDIR)$(libdir); \
+		so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libuuid.so); \
+		so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \
+		(cd $(DESTDIR)$(usrlib_execdir) && \
+			rm -f libuuid.so && \
+			$(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libuuid.so); \
+	fi
+
+uninstall-hook-libuuid:
+	rm -f $(DESTDIR)$(libdir)/libuuid.so*
+
+INSTALL_EXEC_HOOKS += install-exec-hook-libuuid
+UNINSTALL_HOOKS += uninstall-hook-libuuid
diff --git a/libblkid/libuuid/src/clear.c b/libblkid/libuuid/src/clear.c
new file mode 100644
index 0000000..2d91fee
--- /dev/null
+++ b/libblkid/libuuid/src/clear.c
@@ -0,0 +1,43 @@
+/*
+ * clear.c -- Clear a UUID
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include "string.h"
+
+#include "uuidP.h"
+
+void uuid_clear(uuid_t uu)
+{
+	memset(uu, 0, 16);
+}
+
diff --git a/libblkid/libuuid/src/compare.c b/libblkid/libuuid/src/compare.c
new file mode 100644
index 0000000..8f3437a
--- /dev/null
+++ b/libblkid/libuuid/src/compare.c
@@ -0,0 +1,55 @@
+/*
+ * compare.c --- compare whether or not two UUIDs are the same
+ *
+ * Returns 0 if the two UUIDs are different, and 1 if they are the same.
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include "uuidP.h"
+#include <string.h>
+
+#define UUCMP(u1,u2) if (u1 != u2) return((u1 < u2) ? -1 : 1);
+
+int uuid_compare(const uuid_t uu1, const uuid_t uu2)
+{
+	struct uuid	uuid1, uuid2;
+
+	uuid_unpack(uu1, &uuid1);
+	uuid_unpack(uu2, &uuid2);
+
+	UUCMP(uuid1.time_low, uuid2.time_low);
+	UUCMP(uuid1.time_mid, uuid2.time_mid);
+	UUCMP(uuid1.time_hi_and_version, uuid2.time_hi_and_version);
+	UUCMP(uuid1.clock_seq, uuid2.clock_seq);
+	return memcmp(uuid1.node, uuid2.node, 6);
+}
+
diff --git a/libblkid/libuuid/src/copy.c b/libblkid/libuuid/src/copy.c
new file mode 100644
index 0000000..ead33aa
--- /dev/null
+++ b/libblkid/libuuid/src/copy.c
@@ -0,0 +1,45 @@
+/*
+ * copy.c --- copy UUIDs
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include "uuidP.h"
+
+void uuid_copy(uuid_t dst, const uuid_t src)
+{
+	unsigned char		*cp1;
+	const unsigned char	*cp2;
+	int			i;
+
+	for (i=0, cp1 = dst, cp2 = src; i < 16; i++)
+		*cp1++ = *cp2++;
+}
diff --git a/libblkid/libuuid/src/gen_uuid.c b/libblkid/libuuid/src/gen_uuid.c
new file mode 100644
index 0000000..eb79339
--- /dev/null
+++ b/libblkid/libuuid/src/gen_uuid.c
@@ -0,0 +1,545 @@
+/*
+ * gen_uuid.c --- generate a DCE-compatible uuid
+ *
+ * Copyright (C) 1996, 1997, 1998, 1999 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifdef _WIN32
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#define UUID MYUUID
+#endif
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <sys/stat.h>
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+#ifdef HAVE_NET_IF_H
+#include <net/if.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NET_IF_DL_H
+#include <net/if_dl.h>
+#endif
+#if defined(__linux__) && defined(HAVE_SYS_SYSCALL_H)
+#include <sys/syscall.h>
+#endif
+
+#include "all-io.h"
+#include "uuidP.h"
+#include "uuidd.h"
+#include "randutils.h"
+#include "c.h"
+
+#ifdef HAVE_TLS
+#define THREAD_LOCAL static __thread
+#else
+#define THREAD_LOCAL static
+#endif
+
+#ifdef _WIN32
+static void gettimeofday (struct timeval *tv, void *dummy)
+{
+	FILETIME	ftime;
+	uint64_t	n;
+
+	GetSystemTimeAsFileTime (&ftime);
+	n = (((uint64_t) ftime.dwHighDateTime << 32)
+	     + (uint64_t) ftime.dwLowDateTime);
+	if (n) {
+		n /= 10;
+		n -= ((369 * 365 + 89) * (uint64_t) 86400) * 1000000;
+	}
+
+	tv->tv_sec = n / 1000000;
+	tv->tv_usec = n % 1000000;
+}
+
+static int getuid (void)
+{
+	return 1;
+}
+#endif
+
+/*
+ * Get the ethernet hardware address, if we can find it...
+ *
+ * XXX for a windows version, probably should use GetAdaptersInfo:
+ * http://www.codeguru.com/cpp/i-n/network/networkinformation/article.php/c5451
+ * commenting out get_node_id just to get gen_uuid to compile under windows
+ * is not the right way to go!
+ */
+static int get_node_id(unsigned char *node_id)
+{
+#ifdef HAVE_NET_IF_H
+	int		sd;
+	struct ifreq	ifr, *ifrp;
+	struct ifconf	ifc;
+	char buf[1024];
+	int		n, i;
+	unsigned char	*a;
+#ifdef HAVE_NET_IF_DL_H
+	struct sockaddr_dl *sdlp;
+#endif
+
+/*
+ * BSD 4.4 defines the size of an ifreq to be
+ * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len
+ * However, under earlier systems, sa_len isn't present, so the size is
+ * just sizeof(struct ifreq)
+ */
+#ifdef HAVE_SA_LEN
+#define ifreq_size(i) max(sizeof(struct ifreq),\
+     sizeof((i).ifr_name)+(i).ifr_addr.sa_len)
+#else
+#define ifreq_size(i) sizeof(struct ifreq)
+#endif /* HAVE_SA_LEN */
+
+	sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+	if (sd < 0) {
+		return -1;
+	}
+	memset(buf, 0, sizeof(buf));
+	ifc.ifc_len = sizeof(buf);
+	ifc.ifc_buf = buf;
+	if (ioctl (sd, SIOCGIFCONF, (char *)&ifc) < 0) {
+		close(sd);
+		return -1;
+	}
+	n = ifc.ifc_len;
+	for (i = 0; i < n; i+= ifreq_size(*ifrp) ) {
+		ifrp = (struct ifreq *)((char *) ifc.ifc_buf+i);
+		strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ);
+#ifdef SIOCGIFHWADDR
+		if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0)
+			continue;
+		a = (unsigned char *) &ifr.ifr_hwaddr.sa_data;
+#else
+#ifdef SIOCGENADDR
+		if (ioctl(sd, SIOCGENADDR, &ifr) < 0)
+			continue;
+		a = (unsigned char *) ifr.ifr_enaddr;
+#else
+#ifdef HAVE_NET_IF_DL_H
+		sdlp = (struct sockaddr_dl *) &ifrp->ifr_addr;
+		if ((sdlp->sdl_family != AF_LINK) || (sdlp->sdl_alen != 6))
+			continue;
+		a = (unsigned char *) &sdlp->sdl_data[sdlp->sdl_nlen];
+#else
+		/*
+		 * XXX we don't have a way of getting the hardware
+		 * address
+		 */
+		close(sd);
+		return 0;
+#endif /* HAVE_NET_IF_DL_H */
+#endif /* SIOCGENADDR */
+#endif /* SIOCGIFHWADDR */
+		if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5])
+			continue;
+		if (node_id) {
+			memcpy(node_id, a, 6);
+			close(sd);
+			return 1;
+		}
+	}
+	close(sd);
+#endif
+	return 0;
+}
+
+/* Assume that the gettimeofday() has microsecond granularity */
+#define MAX_ADJUSTMENT 10
+
+/*
+ * Get clock from global sequence clock counter.
+ *
+ * Return -1 if the clock counter could not be opened/locked (in this case
+ * pseudorandom value is returned in @ret_clock_seq), otherwise return 0.
+ */
+static int get_clock(uint32_t *clock_high, uint32_t *clock_low,
+		     uint16_t *ret_clock_seq, int *num)
+{
+	THREAD_LOCAL int		adjustment = 0;
+	THREAD_LOCAL struct timeval	last = {0, 0};
+	THREAD_LOCAL int		state_fd = -2;
+	THREAD_LOCAL FILE		*state_f;
+	THREAD_LOCAL uint16_t		clock_seq;
+	struct timeval			tv;
+	uint64_t			clock_reg;
+	mode_t				save_umask;
+	int				len;
+	int				ret = 0;
+
+	if (state_fd == -2) {
+		save_umask = umask(0);
+		state_fd = open(LIBUUID_CLOCK_FILE, O_RDWR|O_CREAT|O_CLOEXEC, 0660);
+		(void) umask(save_umask);
+		if (state_fd != -1) {
+			state_f = fdopen(state_fd, "r+" UL_CLOEXECSTR);
+			if (!state_f) {
+				close(state_fd);
+				state_fd = -1;
+				ret = -1;
+			}
+		}
+		else
+			ret = -1;
+	}
+	if (state_fd >= 0) {
+		rewind(state_f);
+		while (flock(state_fd, LOCK_EX) < 0) {
+			if ((errno == EAGAIN) || (errno == EINTR))
+				continue;
+			fclose(state_f);
+			close(state_fd);
+			state_fd = -1;
+			ret = -1;
+			break;
+		}
+	}
+	if (state_fd >= 0) {
+		unsigned int cl;
+		unsigned long tv1, tv2;
+		int a;
+
+		if (fscanf(state_f, "clock: %04x tv: %lu %lu adj: %d\n",
+			   &cl, &tv1, &tv2, &a) == 4) {
+			clock_seq = cl & 0x3FFF;
+			last.tv_sec = tv1;
+			last.tv_usec = tv2;
+			adjustment = a;
+		}
+	}
+
+	if ((last.tv_sec == 0) && (last.tv_usec == 0)) {
+		random_get_bytes(&clock_seq, sizeof(clock_seq));
+		clock_seq &= 0x3FFF;
+		gettimeofday(&last, 0);
+		last.tv_sec--;
+	}
+
+try_again:
+	gettimeofday(&tv, 0);
+	if ((tv.tv_sec < last.tv_sec) ||
+	    ((tv.tv_sec == last.tv_sec) &&
+	     (tv.tv_usec < last.tv_usec))) {
+		clock_seq = (clock_seq+1) & 0x3FFF;
+		adjustment = 0;
+		last = tv;
+	} else if ((tv.tv_sec == last.tv_sec) &&
+	    (tv.tv_usec == last.tv_usec)) {
+		if (adjustment >= MAX_ADJUSTMENT)
+			goto try_again;
+		adjustment++;
+	} else {
+		adjustment = 0;
+		last = tv;
+	}
+
+	clock_reg = tv.tv_usec*10 + adjustment;
+	clock_reg += ((uint64_t) tv.tv_sec)*10000000;
+	clock_reg += (((uint64_t) 0x01B21DD2) << 32) + 0x13814000;
+
+	if (num && (*num > 1)) {
+		adjustment += *num - 1;
+		last.tv_usec += adjustment / 10;
+		adjustment = adjustment % 10;
+		last.tv_sec += last.tv_usec / 1000000;
+		last.tv_usec = last.tv_usec % 1000000;
+	}
+
+	if (state_fd >= 0) {
+		rewind(state_f);
+		len = fprintf(state_f,
+			      "clock: %04x tv: %016lu %08lu adj: %08d\n",
+			      clock_seq, last.tv_sec, last.tv_usec, adjustment);
+		fflush(state_f);
+		if (ftruncate(state_fd, len) < 0) {
+			fprintf(state_f, "                   \n");
+			fflush(state_f);
+		}
+		rewind(state_f);
+		flock(state_fd, LOCK_UN);
+	}
+
+	*clock_high = clock_reg >> 32;
+	*clock_low = clock_reg;
+	*ret_clock_seq = clock_seq;
+	return ret;
+}
+
+#if defined(HAVE_UUIDD) && defined(HAVE_SYS_UN_H)
+/*
+ * Try using the uuidd daemon to generate the UUID
+ *
+ * Returns 0 on success, non-zero on failure.
+ */
+static int get_uuid_via_daemon(int op, uuid_t out, int *num)
+{
+	char op_buf[64];
+	int op_len;
+	int s;
+	ssize_t ret;
+	int32_t reply_len = 0, expected = 16;
+	struct sockaddr_un srv_addr;
+
+	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+		return -1;
+
+	srv_addr.sun_family = AF_UNIX;
+	strcpy(srv_addr.sun_path, UUIDD_SOCKET_PATH);
+
+	if (connect(s, (const struct sockaddr *) &srv_addr,
+		    sizeof(struct sockaddr_un)) < 0)
+		goto fail;
+
+	op_buf[0] = op;
+	op_len = 1;
+	if (op == UUIDD_OP_BULK_TIME_UUID) {
+		memcpy(op_buf+1, num, sizeof(*num));
+		op_len += sizeof(*num);
+		expected += sizeof(*num);
+	}
+
+	ret = write(s, op_buf, op_len);
+	if (ret < 1)
+		goto fail;
+
+	ret = read_all(s, (char *) &reply_len, sizeof(reply_len));
+	if (ret < 0)
+		goto fail;
+
+	if (reply_len != expected)
+		goto fail;
+
+	ret = read_all(s, op_buf, reply_len);
+
+	if (op == UUIDD_OP_BULK_TIME_UUID)
+		memcpy(op_buf+16, num, sizeof(int));
+
+	memcpy(out, op_buf, 16);
+
+	close(s);
+	return ((ret == expected) ? 0 : -1);
+
+fail:
+	close(s);
+	return -1;
+}
+
+#else /* !defined(HAVE_UUIDD) && defined(HAVE_SYS_UN_H) */
+static int get_uuid_via_daemon(int op, uuid_t out, int *num)
+{
+	return -1;
+}
+#endif
+
+int __uuid_generate_time(uuid_t out, int *num)
+{
+	static unsigned char node_id[6];
+	static int has_init = 0;
+	struct uuid uu;
+	uint32_t	clock_mid;
+	int ret;
+
+	if (!has_init) {
+		if (get_node_id(node_id) <= 0) {
+			random_get_bytes(node_id, 6);
+			/*
+			 * Set multicast bit, to prevent conflicts
+			 * with IEEE 802 addresses obtained from
+			 * network cards
+			 */
+			node_id[0] |= 0x01;
+		}
+		has_init = 1;
+	}
+	ret = get_clock(&clock_mid, &uu.time_low, &uu.clock_seq, num);
+	uu.clock_seq |= 0x8000;
+	uu.time_mid = (uint16_t) clock_mid;
+	uu.time_hi_and_version = ((clock_mid >> 16) & 0x0FFF) | 0x1000;
+	memcpy(uu.node, node_id, 6);
+	uuid_pack(&uu, out);
+	return ret;
+}
+
+/*
+ * Generate time-based UUID and store it to @out
+ *
+ * Tries to guarantee uniqueness of the generated UUIDs by obtaining them from the uuidd daemon,
+ * or, if uuidd is not usable, by using the global clock state counter (see get_clock()).
+ * If neither of these is possible (e.g. because of insufficient permissions), it generates
+ * the UUID anyway, but returns -1. Otherwise, returns 0.
+ */
+static int uuid_generate_time_generic(uuid_t out) {
+#ifdef HAVE_TLS
+	THREAD_LOCAL int		num = 0;
+	THREAD_LOCAL struct uuid	uu;
+	THREAD_LOCAL time_t		last_time = 0;
+	time_t				now;
+
+	if (num > 0) {
+		now = time(0);
+		if (now > last_time+1)
+			num = 0;
+	}
+	if (num <= 0) {
+		num = 1000;
+		if (get_uuid_via_daemon(UUIDD_OP_BULK_TIME_UUID,
+					out, &num) == 0) {
+			last_time = time(0);
+			uuid_unpack(out, &uu);
+			num--;
+			return 0;
+		}
+		num = 0;
+	}
+	if (num > 0) {
+		uu.time_low++;
+		if (uu.time_low == 0) {
+			uu.time_mid++;
+			if (uu.time_mid == 0)
+				uu.time_hi_and_version++;
+		}
+		num--;
+		uuid_pack(&uu, out);
+		return 0;
+	}
+#else
+	if (get_uuid_via_daemon(UUIDD_OP_TIME_UUID, out, 0) == 0)
+		return 0;
+#endif
+
+	return __uuid_generate_time(out, 0);
+}
+
+/*
+ * Generate time-based UUID and store it to @out.
+ *
+ * Discards return value from uuid_generate_time_generic()
+ */
+void uuid_generate_time(uuid_t out)
+{
+	(void)uuid_generate_time_generic(out);
+}
+
+
+int uuid_generate_time_safe(uuid_t out)
+{
+	return uuid_generate_time_generic(out);
+}
+
+
+void __uuid_generate_random(uuid_t out, int *num)
+{
+	uuid_t	buf;
+	struct uuid uu;
+	int i, n;
+
+	if (!num || !*num)
+		n = 1;
+	else
+		n = *num;
+
+	for (i = 0; i < n; i++) {
+		random_get_bytes(buf, sizeof(buf));
+		uuid_unpack(buf, &uu);
+
+		uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;
+		uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF)
+			| 0x4000;
+		uuid_pack(&uu, out);
+		out += sizeof(uuid_t);
+	}
+}
+
+void uuid_generate_random(uuid_t out)
+{
+	int	num = 1;
+	/* No real reason to use the daemon for random uuid's -- yet */
+
+	__uuid_generate_random(out, &num);
+}
+
+/*
+ * Check whether good random source (/dev/random or /dev/urandom)
+ * is available.
+ */
+static int have_random_source(void)
+{
+	struct stat s;
+
+	return (!stat("/dev/random", &s) || !stat("/dev/urandom", &s));
+}
+
+
+/*
+ * This is the generic front-end to uuid_generate_random and
+ * uuid_generate_time.  It uses uuid_generate_random only if
+ * /dev/urandom is available, since otherwise we won't have
+ * high-quality randomness.
+ */
+void uuid_generate(uuid_t out)
+{
+	if (have_random_source())
+		uuid_generate_random(out);
+	else
+		uuid_generate_time(out);
+}
diff --git a/libblkid/libuuid/src/isnull.c b/libblkid/libuuid/src/isnull.c
new file mode 100644
index 0000000..931e7e7
--- /dev/null
+++ b/libblkid/libuuid/src/isnull.c
@@ -0,0 +1,48 @@
+/*
+ * isnull.c --- Check whether or not the UUID is null
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include "uuidP.h"
+
+/* Returns 1 if the uuid is the NULL uuid */
+int uuid_is_null(const uuid_t uu)
+{
+	const unsigned char 	*cp;
+	int			i;
+
+	for (i=0, cp = uu; i < 16; i++)
+		if (*cp++)
+			return 0;
+	return 1;
+}
+
diff --git a/libblkid/libuuid/src/libuuid.sym b/libblkid/libuuid/src/libuuid.sym
new file mode 100644
index 0000000..28a2076
--- /dev/null
+++ b/libblkid/libuuid/src/libuuid.sym
@@ -0,0 +1,48 @@
+/*
+ * The symbol versioning ensures that a new application requiring symbol 'foo'
+ * can't run with old libbrary.so not providing 'foo' - the global SONAME
+ * version info can't enforce this since we never change the SONAME.
+ *
+ * The original libuuid from e2fsprogs (<=1.41.5) does not to use
+ * symbol versioning -- all the original symbols are in UUID_1.0 now.
+ *
+ * Copyright (C) 2011-2014 Karel Zak <kzak@redhat.com>
+ */
+UUID_1.0 {
+global:
+	uuid_clear;
+	uuid_compare;
+	uuid_copy;
+	uuid_generate;
+	uuid_generate_random;
+	uuid_generate_time;
+	uuid_is_null;
+	uuid_parse;
+	uuid_unparse;
+	uuid_unparse_lower;
+	uuid_unparse_upper;
+	uuid_time;
+	uuid_type;
+	uuid_variant;
+};
+
+/*
+ * version(s) since util-linux 2.20
+ */
+UUID_2.20 {
+global:
+	uuid_generate_time_safe;
+} UUID_1.0;
+
+
+/*
+ * __uuid_* this is not part of the official API, this is
+ * uuidd (uuid daemon) specific stuff. Hell.
+ */
+UUIDD_PRIVATE {
+global:
+	__uuid_generate_time;
+	__uuid_generate_random;
+local:
+	*;
+};
diff --git a/libblkid/libuuid/src/pack.c b/libblkid/libuuid/src/pack.c
new file mode 100644
index 0000000..6e12476
--- /dev/null
+++ b/libblkid/libuuid/src/pack.c
@@ -0,0 +1,69 @@
+/*
+ * Internal routine for packing UUIDs
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <string.h>
+#include "uuidP.h"
+
+void uuid_pack(const struct uuid *uu, uuid_t ptr)
+{
+	uint32_t	tmp;
+	unsigned char	*out = ptr;
+
+	tmp = uu->time_low;
+	out[3] = (unsigned char) tmp;
+	tmp >>= 8;
+	out[2] = (unsigned char) tmp;
+	tmp >>= 8;
+	out[1] = (unsigned char) tmp;
+	tmp >>= 8;
+	out[0] = (unsigned char) tmp;
+
+	tmp = uu->time_mid;
+	out[5] = (unsigned char) tmp;
+	tmp >>= 8;
+	out[4] = (unsigned char) tmp;
+
+	tmp = uu->time_hi_and_version;
+	out[7] = (unsigned char) tmp;
+	tmp >>= 8;
+	out[6] = (unsigned char) tmp;
+
+	tmp = uu->clock_seq;
+	out[9] = (unsigned char) tmp;
+	tmp >>= 8;
+	out[8] = (unsigned char) tmp;
+
+	memcpy(out+10, uu->node, 6);
+}
+
diff --git a/libblkid/libuuid/src/parse.c b/libblkid/libuuid/src/parse.c
new file mode 100644
index 0000000..074383e
--- /dev/null
+++ b/libblkid/libuuid/src/parse.c
@@ -0,0 +1,79 @@
+/*
+ * parse.c --- UUID parsing
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "uuidP.h"
+
+int uuid_parse(const char *in, uuid_t uu)
+{
+	struct uuid	uuid;
+	int 		i;
+	const char	*cp;
+	char		buf[3];
+
+	if (strlen(in) != 36)
+		return -1;
+	for (i=0, cp = in; i <= 36; i++,cp++) {
+		if ((i == 8) || (i == 13) || (i == 18) ||
+		    (i == 23)) {
+			if (*cp == '-')
+				continue;
+			else
+				return -1;
+		}
+		if (i== 36)
+			if (*cp == 0)
+				continue;
+		if (!isxdigit(*cp))
+			return -1;
+	}
+	uuid.time_low = strtoul(in, NULL, 16);
+	uuid.time_mid = strtoul(in+9, NULL, 16);
+	uuid.time_hi_and_version = strtoul(in+14, NULL, 16);
+	uuid.clock_seq = strtoul(in+19, NULL, 16);
+	cp = in+24;
+	buf[2] = 0;
+	for (i=0; i < 6; i++) {
+		buf[0] = *cp++;
+		buf[1] = *cp++;
+		uuid.node[i] = strtoul(buf, NULL, 16);
+	}
+
+	uuid_pack(&uuid, uu);
+	return 0;
+}
diff --git a/libblkid/libuuid/src/test_uuid.c b/libblkid/libuuid/src/test_uuid.c
new file mode 100644
index 0000000..e03138f
--- /dev/null
+++ b/libblkid/libuuid/src/test_uuid.c
@@ -0,0 +1,180 @@
+/*
+ * tst_uuid.c --- test program from the UUID library
+ *
+ * Copyright (C) 1996, 1997, 1998 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifdef _WIN32
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#define UUID MYUUID
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "uuid.h"
+
+static int test_uuid(const char * uuid, int isValid)
+{
+	static const char * validStr[2] = {"invalid", "valid"};
+	uuid_t uuidBits;
+	int parsedOk;
+
+	parsedOk = uuid_parse(uuid, uuidBits) == 0;
+
+	printf("%s is %s", uuid, validStr[isValid]);
+	if (parsedOk != isValid) {
+		printf(" but uuid_parse says %s\n", validStr[parsedOk]);
+		return 1;
+	}
+	printf(", OK\n");
+	return 0;
+}
+
+#ifdef __GNUC__
+#define ATTR(x) __attribute__(x)
+#else
+#define ATTR(x)
+#endif
+
+int
+main(int argc ATTR((unused)) , char **argv ATTR((unused)))
+{
+	uuid_t		buf, tst;
+	char		str[100];
+	struct timeval	tv;
+	time_t		time_reg;
+	unsigned char	*cp;
+	int i;
+	int failed = 0;
+	int type, variant;
+
+	uuid_generate(buf);
+	uuid_unparse(buf, str);
+	printf("UUID generate = %s\n", str);
+	printf("UUID: ");
+	for (i=0, cp = (unsigned char *) &buf; i < 16; i++) {
+		printf("%02x", *cp++);
+	}
+	printf("\n");
+	type = uuid_type(buf); 	variant = uuid_variant(buf);
+	printf("UUID type = %d, UUID variant = %d\n", type, variant);
+	if (variant != UUID_VARIANT_DCE) {
+		printf("Incorrect UUID Variant; was expecting DCE!\n");
+		failed++;
+	}
+	printf("\n");
+
+	uuid_generate_random(buf);
+	uuid_unparse(buf, str);
+	printf("UUID random string = %s\n", str);
+	printf("UUID: ");
+	for (i=0, cp = (unsigned char *) &buf; i < 16; i++) {
+		printf("%02x", *cp++);
+	}
+	printf("\n");
+	type = uuid_type(buf); 	variant = uuid_variant(buf);
+	printf("UUID type = %d, UUID variant = %d\n", type, variant);
+	if (variant != UUID_VARIANT_DCE) {
+		printf("Incorrect UUID Variant; was expecting DCE!\n");
+		failed++;
+	}
+	if (type != 4) {
+		printf("Incorrect UUID type; was expecting "
+		       "4 (random type)!\n");
+		failed++;
+	}
+	printf("\n");
+
+	uuid_generate_time(buf);
+	uuid_unparse(buf, str);
+	printf("UUID string = %s\n", str);
+	printf("UUID time: ");
+	for (i=0, cp = (unsigned char *) &buf; i < 16; i++) {
+		printf("%02x", *cp++);
+	}
+	printf("\n");
+	type = uuid_type(buf); 	variant = uuid_variant(buf);
+	printf("UUID type = %d, UUID variant = %d\n", type, variant);
+	if (variant != UUID_VARIANT_DCE) {
+		printf("Incorrect UUID Variant; was expecting DCE!\n");
+		failed++;
+	}
+	if (type != 1) {
+		printf("Incorrect UUID type; was expecting "
+		       "1 (time-based type)!\\n");
+		failed++;
+	}
+	tv.tv_sec = 0;
+	tv.tv_usec = 0;
+	time_reg = uuid_time(buf, &tv);
+	printf("UUID time is: (%ld, %ld): %s\n", tv.tv_sec, tv.tv_usec,
+	       ctime(&time_reg));
+	uuid_parse(str, tst);
+	if (!uuid_compare(buf, tst))
+		printf("UUID parse and compare succeeded.\n");
+	else {
+		printf("UUID parse and compare failed!\n");
+		failed++;
+	}
+	uuid_clear(tst);
+	if (uuid_is_null(tst))
+		printf("UUID clear and is null succeeded.\n");
+	else {
+		printf("UUID clear and is null failed!\n");
+		failed++;
+	}
+	uuid_copy(buf, tst);
+	if (!uuid_compare(buf, tst))
+		printf("UUID copy and compare succeeded.\n");
+	else {
+		printf("UUID copy and compare failed!\n");
+		failed++;
+	}
+	failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981b", 1);
+	failed += test_uuid("84949CC5-4701-4A84-895B-354C584A981B", 1);
+	failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981bc", 0);
+	failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981", 0);
+	failed += test_uuid("84949cc5x4701-4a84-895b-354c584a981b", 0);
+	failed += test_uuid("84949cc504701-4a84-895b-354c584a981b", 0);
+	failed += test_uuid("84949cc5-470104a84-895b-354c584a981b", 0);
+	failed += test_uuid("84949cc5-4701-4a840895b-354c584a981b", 0);
+	failed += test_uuid("84949cc5-4701-4a84-895b0354c584a981b", 0);
+	failed += test_uuid("g4949cc5-4701-4a84-895b-354c584a981b", 0);
+	failed += test_uuid("84949cc5-4701-4a84-895b-354c584a981g", 0);
+
+	if (failed) {
+		printf("%d failures.\n", failed);
+		exit(1);
+	}
+	return 0;
+}
diff --git a/libblkid/libuuid/src/unpack.c b/libblkid/libuuid/src/unpack.c
new file mode 100644
index 0000000..beaaff3
--- /dev/null
+++ b/libblkid/libuuid/src/unpack.c
@@ -0,0 +1,63 @@
+/*
+ * Internal routine for unpacking UUID
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <string.h>
+#include "uuidP.h"
+
+void uuid_unpack(const uuid_t in, struct uuid *uu)
+{
+	const uint8_t	*ptr = in;
+	uint32_t		tmp;
+
+	tmp = *ptr++;
+	tmp = (tmp << 8) | *ptr++;
+	tmp = (tmp << 8) | *ptr++;
+	tmp = (tmp << 8) | *ptr++;
+	uu->time_low = tmp;
+
+	tmp = *ptr++;
+	tmp = (tmp << 8) | *ptr++;
+	uu->time_mid = tmp;
+
+	tmp = *ptr++;
+	tmp = (tmp << 8) | *ptr++;
+	uu->time_hi_and_version = tmp;
+
+	tmp = *ptr++;
+	tmp = (tmp << 8) | *ptr++;
+	uu->clock_seq = tmp;
+
+	memcpy(uu->node, ptr, 6);
+}
+
diff --git a/libblkid/libuuid/src/unparse.c b/libblkid/libuuid/src/unparse.c
new file mode 100644
index 0000000..a95bbb0
--- /dev/null
+++ b/libblkid/libuuid/src/unparse.c
@@ -0,0 +1,76 @@
+/*
+ * unparse.c -- convert a UUID to string
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+
+#include "uuidP.h"
+
+static const char *fmt_lower =
+	"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x";
+
+static const char *fmt_upper =
+	"%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X";
+
+#ifdef UUID_UNPARSE_DEFAULT_UPPER
+#define FMT_DEFAULT fmt_upper
+#else
+#define FMT_DEFAULT fmt_lower
+#endif
+
+static void uuid_unparse_x(const uuid_t uu, char *out, const char *fmt)
+{
+	struct uuid uuid;
+
+	uuid_unpack(uu, &uuid);
+	sprintf(out, fmt,
+		uuid.time_low, uuid.time_mid, uuid.time_hi_and_version,
+		uuid.clock_seq >> 8, uuid.clock_seq & 0xFF,
+		uuid.node[0], uuid.node[1], uuid.node[2],
+		uuid.node[3], uuid.node[4], uuid.node[5]);
+}
+
+void uuid_unparse_lower(const uuid_t uu, char *out)
+{
+	uuid_unparse_x(uu, out,	fmt_lower);
+}
+
+void uuid_unparse_upper(const uuid_t uu, char *out)
+{
+	uuid_unparse_x(uu, out,	fmt_upper);
+}
+
+void uuid_unparse(const uuid_t uu, char *out)
+{
+	uuid_unparse_x(uu, out, FMT_DEFAULT);
+}
diff --git a/libblkid/libuuid/src/uuid.h b/libblkid/libuuid/src/uuid.h
new file mode 100644
index 0000000..30bd4c0
--- /dev/null
+++ b/libblkid/libuuid/src/uuid.h
@@ -0,0 +1,104 @@
+/*
+ * Public include file for the UUID library
+ *
+ * Copyright (C) 1996, 1997, 1998 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifndef _UUID_UUID_H
+#define _UUID_UUID_H
+
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/time.h>
+#endif
+#include <time.h>
+
+typedef unsigned char uuid_t[16];
+
+/* UUID Variant definitions */
+#define UUID_VARIANT_NCS	0
+#define UUID_VARIANT_DCE	1
+#define UUID_VARIANT_MICROSOFT	2
+#define UUID_VARIANT_OTHER	3
+
+/* UUID Type definitions */
+#define UUID_TYPE_DCE_TIME   1
+#define UUID_TYPE_DCE_RANDOM 4
+
+/* Allow UUID constants to be defined */
+#ifdef __GNUC__
+#define UUID_DEFINE(name,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15) \
+	static const uuid_t name __attribute__ ((unused)) = {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15}
+#else
+#define UUID_DEFINE(name,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15) \
+	static const uuid_t name = {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15}
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* clear.c */
+extern void uuid_clear(uuid_t uu);
+
+/* compare.c */
+extern int uuid_compare(const uuid_t uu1, const uuid_t uu2);
+
+/* copy.c */
+extern void uuid_copy(uuid_t dst, const uuid_t src);
+
+/* gen_uuid.c */
+extern void uuid_generate(uuid_t out);
+extern void uuid_generate_random(uuid_t out);
+extern void uuid_generate_time(uuid_t out);
+extern int uuid_generate_time_safe(uuid_t out);
+
+/* isnull.c */
+extern int uuid_is_null(const uuid_t uu);
+
+/* parse.c */
+extern int uuid_parse(const char *in, uuid_t uu);
+
+/* unparse.c */
+extern void uuid_unparse(const uuid_t uu, char *out);
+extern void uuid_unparse_lower(const uuid_t uu, char *out);
+extern void uuid_unparse_upper(const uuid_t uu, char *out);
+
+/* uuid_time.c */
+extern time_t uuid_time(const uuid_t uu, struct timeval *ret_tv);
+extern int uuid_type(const uuid_t uu);
+extern int uuid_variant(const uuid_t uu);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UUID_UUID_H */
diff --git a/libblkid/libuuid/src/uuidP.h b/libblkid/libuuid/src/uuidP.h
new file mode 100644
index 0000000..86a5e26
--- /dev/null
+++ b/libblkid/libuuid/src/uuidP.h
@@ -0,0 +1,61 @@
+/*
+ * uuid.h -- private header file for uuids
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include "uuid.h"
+
+#define LIBUUID_CLOCK_FILE	"/var/lib/libuuid/clock.txt"
+
+/*
+ * Offset between 15-Oct-1582 and 1-Jan-70
+ */
+#define TIME_OFFSET_HIGH 0x01B21DD2
+#define TIME_OFFSET_LOW  0x13814000
+
+struct uuid {
+	uint32_t	time_low;
+	uint16_t	time_mid;
+	uint16_t	time_hi_and_version;
+	uint16_t	clock_seq;
+	uint8_t	node[6];
+};
+
+
+/*
+ * prototypes
+ */
+void uuid_pack(const struct uuid *uu, uuid_t ptr);
+void uuid_unpack(const uuid_t in, struct uuid *uu);
diff --git a/libblkid/libuuid/src/uuid_time.c b/libblkid/libuuid/src/uuid_time.c
new file mode 100644
index 0000000..f25f5c9
--- /dev/null
+++ b/libblkid/libuuid/src/uuid_time.c
@@ -0,0 +1,171 @@
+/*
+ * uuid_time.c --- Interpret the time field from a uuid.  This program
+ * 	violates the UUID abstraction barrier by reaching into the guts
+ *	of a UUID and interpreting it.
+ *
+ * Copyright (C) 1998, 1999 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifdef _WIN32
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#define UUID MYUUID
+#endif
+
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <time.h>
+
+#include "uuidP.h"
+
+time_t uuid_time(const uuid_t uu, struct timeval *ret_tv)
+{
+	struct timeval		tv;
+	struct uuid		uuid;
+	uint32_t		high;
+	uint64_t		clock_reg;
+
+	uuid_unpack(uu, &uuid);
+
+	high = uuid.time_mid | ((uuid.time_hi_and_version & 0xFFF) << 16);
+	clock_reg = uuid.time_low | ((uint64_t) high << 32);
+
+	clock_reg -= (((uint64_t) 0x01B21DD2) << 32) + 0x13814000;
+	tv.tv_sec = clock_reg / 10000000;
+	tv.tv_usec = (clock_reg % 10000000) / 10;
+
+	if (ret_tv)
+		*ret_tv = tv;
+
+	return tv.tv_sec;
+}
+
+int uuid_type(const uuid_t uu)
+{
+	struct uuid		uuid;
+
+	uuid_unpack(uu, &uuid);
+	return ((uuid.time_hi_and_version >> 12) & 0xF);
+}
+
+int uuid_variant(const uuid_t uu)
+{
+	struct uuid		uuid;
+	int			var;
+
+	uuid_unpack(uu, &uuid);
+	var = uuid.clock_seq;
+
+	if ((var & 0x8000) == 0)
+		return UUID_VARIANT_NCS;
+	if ((var & 0x4000) == 0)
+		return UUID_VARIANT_DCE;
+	if ((var & 0x2000) == 0)
+		return UUID_VARIANT_MICROSOFT;
+	return UUID_VARIANT_OTHER;
+}
+
+#ifdef DEBUG
+static const char *variant_string(int variant)
+{
+	switch (variant) {
+	case UUID_VARIANT_NCS:
+		return "NCS";
+	case UUID_VARIANT_DCE:
+		return "DCE";
+	case UUID_VARIANT_MICROSOFT:
+		return "Microsoft";
+	default:
+		return "Other";
+	}
+}
+
+
+int
+main(int argc, char **argv)
+{
+	uuid_t		buf;
+	time_t		time_reg;
+	struct timeval	tv;
+	int		type, variant;
+
+	if (argc != 2) {
+		fprintf(stderr, "Usage: %s uuid\n", argv[0]);
+		exit(1);
+	}
+	if (uuid_parse(argv[1], buf)) {
+		fprintf(stderr, "Invalid UUID: %s\n", argv[1]);
+		exit(1);
+	}
+	variant = uuid_variant(buf);
+	type = uuid_type(buf);
+	time_reg = uuid_time(buf, &tv);
+
+	printf("UUID variant is %d (%s)\n", variant, variant_string(variant));
+	if (variant != UUID_VARIANT_DCE) {
+		printf("Warning: This program only knows how to interpret "
+		       "DCE UUIDs.\n\tThe rest of the output is likely "
+		       "to be incorrect!!\n");
+	}
+	printf("UUID type is %d", type);
+	switch (type) {
+	case 1:
+		printf(" (time based)\n");
+		break;
+	case 2:
+		printf(" (DCE)\n");
+		break;
+	case 3:
+		printf(" (name-based)\n");
+		break;
+	case 4:
+		printf(" (random)\n");
+		break;
+	default:
+		printf("\n");
+	}
+	if (type != 1) {
+		printf("Warning: not a time-based UUID, so UUID time "
+		       "decoding will likely not work!\n");
+	}
+	printf("UUID time is: (%ld, %ld): %s\n", tv.tv_sec, tv.tv_usec,
+	       ctime(&time_reg));
+
+	return 0;
+}
+#endif
diff --git a/libblkid/libuuid/src/uuidd.h b/libblkid/libuuid/src/uuidd.h
new file mode 100644
index 0000000..2f70968
--- /dev/null
+++ b/libblkid/libuuid/src/uuidd.h
@@ -0,0 +1,54 @@
+/*
+ * Definitions used by the uuidd daemon
+ *
+ * Copyright (C) 2007 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#ifndef _UUID_UUIDD_H
+#define _UUID_UUIDD_H
+
+#define UUIDD_DIR		_PATH_LOCALSTATEDIR "/uuidd"
+#define UUIDD_SOCKET_PATH	UUIDD_DIR "/request"
+#define UUIDD_PIDFILE_PATH	UUIDD_DIR "/uuidd.pid"
+#define UUIDD_PATH		"/usr/sbin/uuidd"
+
+#define UUIDD_OP_GETPID			0
+#define UUIDD_OP_GET_MAXOP		1
+#define UUIDD_OP_TIME_UUID		2
+#define UUIDD_OP_RANDOM_UUID		3
+#define UUIDD_OP_BULK_TIME_UUID		4
+#define UUIDD_OP_BULK_RANDOM_UUID	5
+#define UUIDD_MAX_OP			UUIDD_OP_BULK_RANDOM_UUID
+
+extern int __uuid_generate_time(uuid_t out, int *num);
+extern void __uuid_generate_random(uuid_t out, int *num);
+
+#endif /* _UUID_UUID_H */
diff --git a/libblkid/libuuid/uuid.pc.in b/libblkid/libuuid/uuid.pc.in
new file mode 100644
index 0000000..875de19
--- /dev/null
+++ b/libblkid/libuuid/uuid.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@usrlib_execdir@
+includedir=@includedir@
+
+Name: uuid
+Description: Universally unique id library
+Version: @LIBUUID_VERSION@
+Requires:
+Cflags: -I${includedir}/uuid
+Libs: -L${libdir} -luuid
diff --git a/libblkid/samples/.gitignore b/libblkid/samples/.gitignore
new file mode 100644
index 0000000..4efeb62
--- /dev/null
+++ b/libblkid/samples/.gitignore
@@ -0,0 +1,4 @@
+mkfs
+partitions
+superblocks
+topology
diff --git a/libblkid/samples/Makemodule.am b/libblkid/samples/Makemodule.am
new file mode 100644
index 0000000..0ffbf14
--- /dev/null
+++ b/libblkid/samples/Makemodule.am
@@ -0,0 +1,22 @@
+
+check_PROGRAMS += \
+	sample-mkfs \
+	sample-partitions \
+	sample-superblocks \
+	sample-topology
+
+sample_mkfs_SOURCES = libblkid/samples/mkfs.c
+sample_mkfs_LDADD = libblkid.la
+sample_mkfs_CFLAGS = -I$(ul_libblkid_incdir)
+
+sample_partitions_SOURCES = libblkid/samples/partitions.c
+sample_partitions_LDADD = libblkid.la
+sample_partitions_CFLAGS = -I$(ul_libblkid_incdir)
+
+sample_superblocks_SOURCES = libblkid/samples/superblocks.c
+sample_superblocks_LDADD = libblkid.la
+sample_superblocks_CFLAGS = -I$(ul_libblkid_incdir)
+
+sample_topology_SOURCES = libblkid/samples/topology.c
+sample_topology_LDADD = libblkid.la
+sample_topology_CFLAGS = -I$(ul_libblkid_incdir)
diff --git a/libblkid/samples/mkfs.c b/libblkid/samples/mkfs.c
new file mode 100644
index 0000000..5c3ebe7
--- /dev/null
+++ b/libblkid/samples/mkfs.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <blkid.h>
+
+#include "c.h"
+
+int main(int argc, char *argv[])
+{
+	int rc;
+	char *devname;
+	blkid_probe pr;
+	blkid_topology tp;
+
+	if (argc < 2) {
+		fprintf(stderr, "usage: %s <device>  "
+			"-- checks based on libblkid for mkfs-like programs.\n",
+			program_invocation_short_name);
+		return EXIT_FAILURE;
+	}
+
+	devname = argv[1];
+	pr = blkid_new_probe_from_filename(devname);
+	if (!pr)
+		err(EXIT_FAILURE, "%s: faild to create a new libblkid probe",
+				devname);
+
+	/*
+	 * check Filesystems / Partitions overwrite
+	 */
+
+	/* enable partitions probing (superblocks are enabled by default) */
+	blkid_probe_enable_partitions(pr, TRUE);
+
+	rc = blkid_do_fullprobe(pr);
+	if (rc == -1)
+		errx(EXIT_FAILURE, "%s: blkid_do_fullprobe() failed", devname);
+	else if (rc == 0) {
+		const char *type;
+
+		if (!blkid_probe_lookup_value(pr, "TYPE", &type, NULL))
+			errx(EXIT_FAILURE, "%s: appears to contain an existing "
+					"%s superblock", devname, type);
+
+		if (!blkid_probe_lookup_value(pr, "PTTYPE", &type, NULL))
+			errx(EXIT_FAILURE, "%s: appears to contain an partition "
+					"table (%s)", devname, type);
+	}
+
+	/*
+	 * get topology details
+	 */
+	tp = blkid_probe_get_topology(pr);
+	if (!tp)
+		errx(EXIT_FAILURE, "%s: failed to read topology", devname);
+
+
+	/* ... your mkfs.<type> code or so ...
+
+	off = blkid_topology_get_alignment_offset(tp);
+
+	 */
+
+	blkid_free_probe(pr);
+
+	return EXIT_SUCCESS;
+}
diff --git a/libblkid/samples/partitions.c b/libblkid/samples/partitions.c
new file mode 100644
index 0000000..fe0ad48
--- /dev/null
+++ b/libblkid/samples/partitions.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <blkid.h>
+#include "c.h"
+
+int main(int argc, char *argv[])
+{
+	int i, nparts;
+	char *devname;
+	blkid_probe pr;
+	blkid_partlist ls;
+	blkid_parttable root_tab;
+
+	if (argc < 2) {
+		fprintf(stderr, "usage: %s <device|file>  "
+				"-- prints partitions\n",
+				program_invocation_short_name);
+		return EXIT_FAILURE;
+	}
+
+	devname = argv[1];
+	pr = blkid_new_probe_from_filename(devname);
+	if (!pr)
+		err(EXIT_FAILURE, "%s: faild to create a new libblkid probe",
+				devname);
+	/* Binary interface */
+	ls = blkid_probe_get_partitions(pr);
+	if (!ls)
+		errx(EXIT_FAILURE, "%s: failed to read partitions\n", devname);
+
+	/*
+	 * Print info about the primary (root) partition table
+	 */
+	root_tab = blkid_partlist_get_table(ls);
+	if (!root_tab)
+		errx(EXIT_FAILURE, "%s: does not contains any "
+				 "known partition table\n", devname);
+
+	printf("size: %jd, sector size: %u, PT: %s, offset: %jd, id=%s\n---\n",
+		blkid_probe_get_size(pr),
+		blkid_probe_get_sectorsize(pr),
+		blkid_parttable_get_type(root_tab),
+		blkid_parttable_get_offset(root_tab),
+		blkid_parttable_get_id(root_tab));
+
+	/*
+	 * List partitions
+	 */
+	nparts = blkid_partlist_numof_partitions(ls);
+	if (!nparts)
+		goto done;
+
+	for (i = 0; i < nparts; i++) {
+		const char *p;
+		blkid_partition par = blkid_partlist_get_partition(ls, i);
+		blkid_parttable tab = blkid_partition_get_table(par);
+
+		printf("#%d: %10llu %10llu  0x%x",
+			blkid_partition_get_partno(par),
+			(unsigned long long) blkid_partition_get_start(par),
+			(unsigned long long) blkid_partition_get_size(par),
+			blkid_partition_get_type(par));
+
+		if (root_tab != tab)
+			/* subpartition (BSD, Minix, ...) */
+			printf(" (%s)", blkid_parttable_get_type(tab));
+
+		p = blkid_partition_get_name(par);
+		if (p)
+			printf(" name='%s'", p);
+		p = blkid_partition_get_uuid(par);
+		if (p)
+			printf(" uuid='%s'", p);
+		p = blkid_partition_get_type_string(par);
+		if (p)
+			printf(" type='%s'", p);
+
+		putc('\n', stdout);
+	}
+
+done:
+	blkid_free_probe(pr);
+	return EXIT_SUCCESS;
+}
diff --git a/libblkid/samples/superblocks.c b/libblkid/samples/superblocks.c
new file mode 100644
index 0000000..20e39c9
--- /dev/null
+++ b/libblkid/samples/superblocks.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <blkid.h>
+
+#include "c.h"
+
+int main(int argc, char *argv[])
+{
+	int rc;
+	char *devname;
+	blkid_probe pr;
+
+	if (argc < 2) {
+		fprintf(stderr, "usage: %s <device>  "
+				"-- prints superblocks details about the device\n",
+				program_invocation_short_name);
+		return EXIT_FAILURE;
+	}
+
+	devname = argv[1];
+	pr = blkid_new_probe_from_filename(devname);
+	if (!pr)
+		err(EXIT_FAILURE, "%s: faild to create a new libblkid probe",
+				devname);
+
+	/* enable topology probing */
+	blkid_probe_enable_superblocks(pr, TRUE);
+
+	/* set all flags */
+	blkid_probe_set_superblocks_flags(pr,
+			BLKID_SUBLKS_LABEL | BLKID_SUBLKS_LABELRAW |
+			BLKID_SUBLKS_UUID | BLKID_SUBLKS_UUIDRAW |
+			BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE |
+			BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION |
+			BLKID_SUBLKS_MAGIC);
+
+	rc = blkid_do_safeprobe(pr);
+	if (rc == -1)
+		errx(EXIT_FAILURE, "%s: blkid_do_safeprobe() failed", devname);
+	else if (rc == 1)
+		warnx("%s: cannot gather information about superblocks", devname);
+	else {
+		int i, nvals = blkid_probe_numof_values(pr);
+
+		for (i = 0; i < nvals; i++) {
+			const char *name, *data;
+
+			blkid_probe_get_value(pr, i, &name, &data, NULL);
+			printf("\t%s = %s\n", name, data);
+		}
+	}
+
+	blkid_free_probe(pr);
+	return EXIT_SUCCESS;
+}
diff --git a/libblkid/samples/topology.c b/libblkid/samples/topology.c
new file mode 100644
index 0000000..de1c3a5
--- /dev/null
+++ b/libblkid/samples/topology.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <blkid.h>
+
+#include "c.h"
+
+int main(int argc, char *argv[])
+{
+	int rc;
+	char *devname;
+	blkid_probe pr;
+	blkid_topology tp;
+
+	if (argc < 2) {
+		fprintf(stderr, "usage: %s <device>  "
+				"-- prints topology details about the device\n",
+				program_invocation_short_name);
+		return EXIT_FAILURE;
+	}
+
+	devname = argv[1];
+	pr = blkid_new_probe_from_filename(devname);
+	if (!pr)
+		err(EXIT_FAILURE, "%s: faild to create a new libblkid probe",
+				devname);
+	/*
+	 * Binary interface
+	 */
+	tp = blkid_probe_get_topology(pr);
+	if (tp) {
+		printf("----- binary interface:\n");
+		printf("\talignment offset     : %lu\n",
+				blkid_topology_get_alignment_offset(tp));
+		printf("\tminimum io size      : %lu\n",
+				blkid_topology_get_minimum_io_size(tp));
+		printf("\toptimal io size      : %lu\n",
+				blkid_topology_get_optimal_io_size(tp));
+		printf("\tlogical sector size  : %lu\n",
+				blkid_topology_get_logical_sector_size(tp));
+		printf("\tphysical sector size : %lu\n",
+				blkid_topology_get_physical_sector_size(tp));
+	}
+
+	/*
+	 * NAME=value interface
+	 */
+
+	/* enable topology probing */
+	blkid_probe_enable_topology(pr, TRUE);
+
+	/* disable superblocks probing (enabled by default) */
+	blkid_probe_enable_superblocks(pr, FALSE);
+
+	rc = blkid_do_fullprobe(pr);
+	if (rc == -1)
+		errx(EXIT_FAILURE, "%s: blkid_do_fullprobe() failed", devname);
+	else if (rc == 1)
+		warnx("%s: missing topology information", devname);
+	else {
+		int i, nvals = blkid_probe_numof_values(pr);
+
+		printf("----- NAME=value interface (values: %d):\n", nvals);
+
+		for (i = 0; i < nvals; i++) {
+			const char *name, *data;
+
+			blkid_probe_get_value(pr, i, &name, &data, NULL);
+			printf("\t%s = %s\n", name, data);
+		}
+	}
+
+	blkid_free_probe(pr);
+	return EXIT_SUCCESS;
+}
diff --git a/libblkid/src/.gitignore b/libblkid/src/.gitignore
new file mode 100644
index 0000000..af34f58
--- /dev/null
+++ b/libblkid/src/.gitignore
@@ -0,0 +1 @@
+blkid.h
diff --git a/libblkid/src/bitops.h b/libblkid/src/bitops.h
new file mode 100644
index 0000000..f5974e9
--- /dev/null
+++ b/libblkid/src/bitops.h
@@ -0,0 +1,126 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#ifndef BITOPS_H
+#define BITOPS_H
+
+#include <stdint.h>
+#include <sys/param.h>
+
+#if defined(HAVE_BYTESWAP_H)
+# include <byteswap.h>
+#endif
+
+#define HAVE_SYS_ENDIAN_H
+#if defined(HAVE_ENDIAN_H)
+#  include <endian.h>
+#elif defined(HAVE_SYS_ENDIAN_H)	/* BSDs have them here */
+#  include <sys/endian.h>
+#endif
+
+#if defined(__OpenBSD__)
+# include <sys/types.h>
+# define be16toh(x) betoh16(x)
+# define be32toh(x) betoh32(x)
+# define be64toh(x) betoh64(x)
+#endif
+
+/*
+ * Fallbacks
+ */
+#ifndef bswap_16
+# define bswap_16(x)   ((((x) & 0x00FF) << 8) | \
+			(((x) & 0xFF00) >> 8))
+#endif
+
+#ifndef bswap_32
+# define bswap_32(x)   ((((x) & 0x000000FF) << 24) | \
+			(((x) & 0x0000FF00) << 8)  | \
+			(((x) & 0x00FF0000) >> 8)  | \
+			(((x) & 0xFF000000) >> 24))
+#endif
+
+#ifndef bswap_64
+# define bswap_64(x) ((((x) & 0x00000000000000FFULL) << 56) | \
+                      (((x) & 0x000000000000FF00ULL) << 40) | \
+                      (((x) & 0x0000000000FF0000ULL) << 24) | \
+                      (((x) & 0x00000000FF000000ULL) << 8)  | \
+                      (((x) & 0x000000FF00000000ULL) >> 8)  | \
+                      (((x) & 0x0000FF0000000000ULL) >> 24) | \
+                      (((x) & 0x00FF000000000000ULL) >> 40) | \
+                      (((x) & 0xFF00000000000000ULL) >> 56))
+#endif
+
+#ifndef htobe16
+//# if !defined(WORDS_BIGENDIAN)
+//#  define htobe16(x) bswap_16 (x)
+#  define htole16(x) (x)
+#  define be16toh(x) bswap_16 (x)
+#  define le16toh(x) (x)
+//#  define htobe32(x) bswap_32 (x)
+#  define htole32(x) (x)
+#  define be32toh(x) bswap_32 (x)
+#  define le32toh(x) (x)
+//#  define htobe64(x) bswap_64 (x)
+#  define htole64(x) (x)
+#  define be64toh(x) bswap_64 (x)
+#  define le64toh(x) (x)
+/*
+# else
+#  define htobe16(x) (x)
+#  define htole16(x) bswap_16 (x)
+#  define be16toh(x) (x)
+#  define le16toh(x) bswap_16 (x)
+#  define htobe32(x) (x)
+#  define htole32(x) bswap_32 (x)
+#  define be32toh(x) (x)
+#  define le32toh(x) bswap_32 (x)
+#  define htobe64(x) (x)
+#  define htole64(x) bswap_64 (x)
+#  define be64toh(x) (x)
+#  define le64toh(x) bswap_64 (x)
+# endif
+*/
+#endif
+/*
+ * Byte swab macros (based on linux/byteorder/swab.h)
+ */
+#define swab16(x) bswap_16(x)
+#define swab32(x) bswap_32(x)
+#define swab64(x) bswap_64(x)
+
+#define cpu_to_le16(x) ((uint16_t) htole16(x))
+#define cpu_to_le32(x) ((uint32_t) htole32(x))
+#define cpu_to_le64(x) ((uint64_t) htole64(x))
+
+#define cpu_to_be16(x) ((uint16_t) htobe16(x))
+#define cpu_to_be32(x) ((uint32_t) htobe32(x))
+#define cpu_to_be64(x) ((uint64_t) htobe64(x))
+
+#define le16_to_cpu(x) ((uint16_t) le16toh(x))
+#define le32_to_cpu(x) ((uint32_t) le32toh(x))
+#define le64_to_cpu(x) ((uint64_t) le64toh(x))
+
+#define be16_to_cpu(x) ((uint16_t) be16toh(x))
+#define be32_to_cpu(x) ((uint32_t) be32toh(x))
+#define be64_to_cpu(x) ((uint64_t) be64toh(x))
+
+/*
+ * Bit map related macros. Usually provided by libc.
+ */
+#ifndef NBBY
+# define NBBY            CHAR_BIT
+#endif
+
+#ifndef setbit
+# define setbit(a,i)	((a)[(i)/NBBY] |= 1<<((i)%NBBY))
+# define clrbit(a,i)	((a)[(i)/NBBY] &= ~(1<<((i)%NBBY)))
+# define isset(a,i)	((a)[(i)/NBBY] & (1<<((i)%NBBY)))
+# define isclr(a,i)	(((a)[(i)/NBBY] & (1<<((i)%NBBY))) == 0)
+#endif
+
+#endif /* BITOPS_H */
+
diff --git a/libblkid/src/blkdev.h b/libblkid/src/blkdev.h
new file mode 100644
index 0000000..ade0887
--- /dev/null
+++ b/libblkid/src/blkdev.h
@@ -0,0 +1,146 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ *
+ * Written by Karel Zak <kzak@redhat.com>
+ */
+#ifndef BLKDEV_H
+#define BLKDEV_H
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_IOCCOM_H
+# include <sys/ioccom.h> /* for _IO macro on e.g. Solaris */
+#endif
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef HAVE_SYS_MKDEV_H
+# include <sys/mkdev.h>		/* major and minor on Solaris */
+#endif
+
+#define DEFAULT_SECTOR_SIZE       512
+
+#ifdef __linux__
+/* very basic ioclts, should be available everywhere */
+# ifndef BLKROSET
+#  define BLKROSET   _IO(0x12,93)	/* set device read-only (0 = read-write) */
+#  define BLKROGET   _IO(0x12,94)	/* get read-only status (0 = read_write) */
+#  define BLKRRPART  _IO(0x12,95)	/* re-read partition table */
+#  define BLKGETSIZE _IO(0x12,96)	/* return device size /512 (long *arg) */
+#  define BLKFLSBUF  _IO(0x12,97)	/* flush buffer cache */
+#  define BLKRASET   _IO(0x12,98)	/* set read ahead for block device */
+#  define BLKRAGET   _IO(0x12,99)	/* get current read ahead setting */
+#  define BLKFRASET  _IO(0x12,100)	/* set filesystem (mm/filemap.c) read-ahead */
+#  define BLKFRAGET  _IO(0x12,101)	/* get filesystem (mm/filemap.c) read-ahead */
+#  define BLKSECTSET _IO(0x12,102)	/* set max sectors per request (ll_rw_blk.c) */
+#  define BLKSECTGET _IO(0x12,103)	/* get max sectors per request (ll_rw_blk.c) */
+#  define BLKSSZGET  _IO(0x12,104)	/* get block device sector size */
+
+/* ioctls introduced in 2.2.16, removed in 2.5.58 */
+#  define BLKELVGET  _IOR(0x12,106,size_t) /* elevator get */
+#  define BLKELVSET  _IOW(0x12,107,size_t) /* elevator set */
+
+#  define BLKBSZGET  _IOR(0x12,112,size_t)
+#  define BLKBSZSET  _IOW(0x12,113,size_t)
+# endif /* !BLKROSET */
+
+# ifndef BLKGETSIZE64
+#  define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size in bytes (u64 *arg) */
+# endif
+
+/* block device topology ioctls, introduced in 2.6.32 (commit ac481c20) */
+# ifndef BLKIOMIN
+#  define BLKIOMIN   _IO(0x12,120)
+#  define BLKIOOPT   _IO(0x12,121)
+#  define BLKALIGNOFF _IO(0x12,122)
+#  define BLKPBSZGET _IO(0x12,123)
+# endif
+
+/* discard zeroes support, introduced in 2.6.33 (commit 98262f27) */
+# ifndef BLKDISCARDZEROES
+#  define BLKDISCARDZEROES _IO(0x12,124)
+# endif
+
+/* filesystem freeze, introduced in 2.6.29 (commit fcccf502) */
+# ifndef FIFREEZE
+#  define FIFREEZE   _IOWR('X', 119, int)    /* Freeze */
+#  define FITHAW     _IOWR('X', 120, int)    /* Thaw */
+# endif
+
+/* uniform CD-ROM information */
+# ifndef CDROM_GET_CAPABILITY
+#  define CDROM_GET_CAPABILITY 0x5331
+# endif
+
+#endif /* __linux */
+
+
+#ifdef APPLE_DARWIN
+# define BLKGETSIZE DKIOCGETBLOCKCOUNT32
+#endif
+
+#ifndef HDIO_GETGEO
+# ifdef __linux__
+#  define HDIO_GETGEO 0x0301
+# endif
+
+struct hd_geometry {
+	unsigned char heads;
+	unsigned char sectors;
+	unsigned short cylinders;	/* truncated */
+	unsigned long start;
+};
+#endif /* HDIO_GETGEO */
+
+
+/* are we working with block device? */
+int is_blkdev(int fd);
+
+/* Determine size in bytes */
+off_t blkdev_find_size (int fd);
+
+/* get size in bytes */
+int blkdev_get_size(int fd, unsigned long long *bytes);
+
+/* get 512-byte sector count */
+int blkdev_get_sectors(int fd, unsigned long long *sectors);
+
+/* get hardware sector size */
+int blkdev_get_sector_size(int fd, int *sector_size);
+
+/* specifies whether or not the device is misaligned */
+int blkdev_is_misaligned(int fd);
+
+/* get physical block device size */
+int blkdev_get_physector_size(int fd, int *sector_size);
+
+/* is the device cdrom capable? */
+int blkdev_is_cdrom(int fd);
+
+/* get device's geometry - legacy */
+int blkdev_get_geometry(int fd, unsigned int *h, unsigned int *s);
+
+/* SCSI device types.  Copied almost as-is from kernel header.
+ * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux.git;a=blob;f=include/scsi/scsi.h */
+#define SCSI_TYPE_DISK			0x00
+#define SCSI_TYPE_TAPE			0x01
+#define SCSI_TYPE_PRINTER		0x02
+#define SCSI_TYPE_PROCESSOR		0x03	/* HP scanners use this */
+#define SCSI_TYPE_WORM			0x04	/* Treated as ROM by our system */
+#define SCSI_TYPE_ROM			0x05
+#define SCSI_TYPE_SCANNER		0x06
+#define SCSI_TYPE_MOD			0x07	/* Magneto-optical disk - treated as SCSI_TYPE_DISK */
+#define SCSI_TYPE_MEDIUM_CHANGER	0x08
+#define SCSI_TYPE_COMM			0x09	/* Communications device */
+#define SCSI_TYPE_RAID			0x0c
+#define SCSI_TYPE_ENCLOSURE		0x0d	/* Enclosure Services Device */
+#define SCSI_TYPE_RBC			0x0e
+#define SCSI_TYPE_OSD			0x11
+#define SCSI_TYPE_NO_LUN		0x7f
+
+/* convert scsi type code to name */
+const char *blkdev_scsi_type_to_name(int type);
+
+
+#endif /* BLKDEV_H */
diff --git a/libblkid/src/blkid.h.in b/libblkid/src/blkid.h.in
new file mode 100644
index 0000000..4f5fe2a
--- /dev/null
+++ b/libblkid/src/blkid.h.in
@@ -0,0 +1,414 @@
+/*
+ * blkid.h - Interface for libblkid, a library to identify block devices
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _BLKID_BLKID_H
+#define _BLKID_BLKID_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define BLKID_VERSION   "@LIBBLKID_VERSION@"
+#define BLKID_DATE      "@LIBBLKID_DATE@"
+
+/**
+ * blkid_dev:
+ *
+ * The device object keeps information about one device
+ */
+typedef struct blkid_struct_dev *blkid_dev;
+
+/**
+ * blkid_cache:
+ *
+ * information about all system devices
+ */
+typedef struct blkid_struct_cache *blkid_cache;
+
+/**
+ * blkid_probe:
+ *
+ * low-level probing setting
+ */
+typedef struct blkid_struct_probe *blkid_probe;
+
+/**
+ * blkid_topology:
+ *
+ * device topology information
+ */
+typedef struct blkid_struct_topology *blkid_topology;
+
+/**
+ * blkid_partlist
+ *
+ * list of all detected partitions and partitions tables
+ */
+typedef struct blkid_struct_partlist *blkid_partlist;
+
+/**
+ * blkid_partition:
+ *
+ * information about a partition
+ */
+typedef struct blkid_struct_partition *blkid_partition;
+
+/**
+ * blkid_parttable:
+ *
+ * information about a partition table
+ */
+typedef struct blkid_struct_parttable *blkid_parttable;
+
+/**
+ * blkid_loff_t:
+ *
+ * 64-bit signed number for offsets and sizes
+ */
+typedef int64_t blkid_loff_t;
+
+/**
+ * blkid_tag_iterate:
+ *
+ * tags iterator for high-level (blkid_cache) API
+ */
+typedef struct blkid_struct_tag_iterate *blkid_tag_iterate;
+
+/**
+ * blkid_dev_iterate:
+ *
+ * devices iterator for high-level (blkid_cache) API
+ */
+typedef struct blkid_struct_dev_iterate *blkid_dev_iterate;
+
+/*
+ * Flags for blkid_get_dev
+ *
+ * BLKID_DEV_CREATE	Create an empty device structure if not found
+ *			in the cache.
+ * BLKID_DEV_VERIFY	Make sure the device structure corresponds
+ *			with reality.
+ * BLKID_DEV_FIND	Just look up a device entry, and return NULL
+ *			if it is not found.
+ * BLKID_DEV_NORMAL	Get a valid device structure, either from the
+ *			cache or by probing the device.
+ */
+#define BLKID_DEV_FIND		0x0000
+#define BLKID_DEV_CREATE	0x0001
+#define BLKID_DEV_VERIFY	0x0002
+#define BLKID_DEV_NORMAL	(BLKID_DEV_CREATE | BLKID_DEV_VERIFY)
+
+
+#ifndef __GNUC_PREREQ
+# if defined __GNUC__ && defined __GNUC_MINOR__
+#  define __GNUC_PREREQ(maj, min)  ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+# else
+#  define __GNUC_PREREQ(maj, min) 0
+# endif
+#endif
+
+#ifndef __ul_attribute__
+# if __GNUC_PREREQ (3, 4)
+#  define __ul_attribute__(_a_) __attribute__(_a_)
+# else
+#  define __ul_attribute__(_a_)
+# endif
+#endif
+
+/* cache.c */
+extern void blkid_init_debug(int mask);
+extern void blkid_put_cache(blkid_cache cache);
+extern int blkid_get_cache(blkid_cache *cache, const char *filename);
+extern void blkid_gc_cache(blkid_cache cache);
+
+/* dev.c */
+extern const char *blkid_dev_devname(blkid_dev dev)
+			__ul_attribute__((warn_unused_result));
+
+extern blkid_dev_iterate blkid_dev_iterate_begin(blkid_cache cache);
+extern int blkid_dev_set_search(blkid_dev_iterate iter,
+				char *search_type, char *search_value);
+extern int blkid_dev_next(blkid_dev_iterate iterate, blkid_dev *dev);
+extern void blkid_dev_iterate_end(blkid_dev_iterate iterate);
+
+/* devno.c */
+extern char *blkid_devno_to_devname(dev_t devno)
+			__ul_attribute__((warn_unused_result));
+extern int blkid_devno_to_wholedisk(dev_t dev, char *diskname,
+                        size_t len, dev_t *diskdevno)
+			__ul_attribute__((warn_unused_result));
+
+/* devname.c */
+extern int blkid_probe_all(blkid_cache cache);
+extern int blkid_probe_all_new(blkid_cache cache);
+extern int blkid_probe_all_removable(blkid_cache cache);
+
+extern blkid_dev blkid_get_dev(blkid_cache cache, const char *devname, int flags);
+
+/* getsize.c */
+extern blkid_loff_t blkid_get_dev_size(int fd);
+
+/* verify.c */
+extern blkid_dev blkid_verify(blkid_cache cache, blkid_dev dev);
+
+/* read.c */
+
+/* resolve.c */
+extern char *blkid_get_tag_value(blkid_cache cache, const char *tagname,
+				       const char *devname)
+			__ul_attribute__((warn_unused_result));
+extern char *blkid_get_devname(blkid_cache cache, const char *token,
+			       const char *value)
+			__ul_attribute__((warn_unused_result));
+
+/* tag.c */
+extern blkid_tag_iterate blkid_tag_iterate_begin(blkid_dev dev);
+extern int blkid_tag_next(blkid_tag_iterate iterate,
+			      const char **type, const char **value);
+extern void blkid_tag_iterate_end(blkid_tag_iterate iterate);
+extern int blkid_dev_has_tag(blkid_dev dev, const char *type, const char *value);
+
+extern blkid_dev blkid_find_dev_with_tag(blkid_cache cache,
+					 const char *type,
+					 const char *value);
+
+extern int blkid_parse_tag_string(const char *token, char **ret_type, char **ret_val);
+
+/* version.c */
+extern int blkid_parse_version_string(const char *ver_string)
+			__ul_attribute__((nonnull));
+extern int blkid_get_library_version(const char **ver_string,
+				     const char **date_string);
+
+/* encode.c */
+extern int blkid_encode_string(const char *str, char *str_enc, size_t len);
+extern int blkid_safe_string(const char *str, char *str_safe, size_t len);
+
+/* evaluate.c */
+extern int blkid_send_uevent(const char *devname, const char *action);
+extern char *blkid_evaluate_tag(const char *token, const char *value,
+				blkid_cache *cache)
+			__ul_attribute__((warn_unused_result));
+extern char *blkid_evaluate_spec(const char *spec, blkid_cache *cache)
+			__ul_attribute__((warn_unused_result));
+
+/* probe.c */
+extern blkid_probe blkid_new_probe(void)
+			__ul_attribute__((warn_unused_result));
+extern blkid_probe blkid_new_probe_from_filename(const char *filename)
+			__ul_attribute__((warn_unused_result));
+extern void blkid_free_probe(blkid_probe pr);
+
+extern void blkid_reset_probe(blkid_probe pr);
+
+extern int blkid_probe_set_device(blkid_probe pr, int fd,
+	                blkid_loff_t off, blkid_loff_t size);
+
+extern dev_t blkid_probe_get_devno(blkid_probe pr)
+			__ul_attribute__((nonnull));
+
+extern dev_t blkid_probe_get_wholedisk_devno(blkid_probe pr)
+			__ul_attribute__((nonnull));
+
+extern int blkid_probe_is_wholedisk(blkid_probe pr)
+			__ul_attribute__((nonnull));
+
+extern blkid_loff_t blkid_probe_get_size(blkid_probe pr);
+extern blkid_loff_t blkid_probe_get_offset(blkid_probe pr);
+extern unsigned int blkid_probe_get_sectorsize(blkid_probe pr);
+extern blkid_loff_t blkid_probe_get_sectors(blkid_probe pr);
+
+extern int blkid_probe_get_fd(blkid_probe pr);
+
+/*
+ * superblocks probing
+ */
+extern int blkid_known_fstype(const char *fstype);
+
+extern int blkid_superblocks_get_name(size_t idx, const char **name, int *usage);
+
+extern int blkid_probe_enable_superblocks(blkid_probe pr, int enable);
+
+#define BLKID_SUBLKS_LABEL	(1 << 1) /* read LABEL from superblock */
+#define BLKID_SUBLKS_LABELRAW	(1 << 2) /* read and define LABEL_RAW result value*/
+#define BLKID_SUBLKS_UUID	(1 << 3) /* read UUID from superblock */
+#define BLKID_SUBLKS_UUIDRAW	(1 << 4) /* read and define UUID_RAW result value */
+#define BLKID_SUBLKS_TYPE	(1 << 5) /* define TYPE result value */
+#define BLKID_SUBLKS_SECTYPE	(1 << 6) /* define compatible fs type (second type) */
+#define BLKID_SUBLKS_USAGE	(1 << 7) /* define USAGE result value */
+#define BLKID_SUBLKS_VERSION	(1 << 8) /* read FS type from superblock */
+#define BLKID_SUBLKS_MAGIC	(1 << 9) /* define SBMAGIC and SBMAGIC_OFFSET */
+#define BLKID_SUBLKS_BADCSUM	(1 << 10) /* allow a bad checksum */
+
+#define BLKID_SUBLKS_DEFAULT	(BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | \
+				 BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE)
+
+extern int blkid_probe_set_superblocks_flags(blkid_probe pr, int flags);
+extern int blkid_probe_reset_superblocks_filter(blkid_probe pr);
+extern int blkid_probe_invert_superblocks_filter(blkid_probe pr);
+
+/**
+ * BLKID_FLTR_NOTIN
+ */
+#define BLKID_FLTR_NOTIN		1
+/**
+ * BLKID_FLTR_ONLYIN
+ */
+#define BLKID_FLTR_ONLYIN		2
+extern int blkid_probe_filter_superblocks_type(blkid_probe pr, int flag, char *names[]);
+
+#define BLKID_USAGE_FILESYSTEM		(1 << 1)
+#define BLKID_USAGE_RAID		(1 << 2)
+#define BLKID_USAGE_CRYPTO		(1 << 3)
+#define BLKID_USAGE_OTHER		(1 << 4)
+extern int blkid_probe_filter_superblocks_usage(blkid_probe pr, int flag, int usage);
+
+/*
+ * topology probing
+ */
+extern int blkid_probe_enable_topology(blkid_probe pr, int enable);
+
+/* binary interface */
+extern blkid_topology blkid_probe_get_topology(blkid_probe pr);
+
+extern unsigned long blkid_topology_get_alignment_offset(blkid_topology tp)
+			__ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_minimum_io_size(blkid_topology tp)
+			__ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_optimal_io_size(blkid_topology tp)
+			__ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_logical_sector_size(blkid_topology tp)
+			__ul_attribute__((nonnull));
+extern unsigned long blkid_topology_get_physical_sector_size(blkid_topology tp)
+			__ul_attribute__((nonnull));
+
+/*
+ * partitions probing
+ */
+extern int blkid_known_pttype(const char *pttype);
+
+extern int blkid_probe_enable_partitions(blkid_probe pr, int enable);
+
+extern int blkid_probe_reset_partitions_filter(blkid_probe pr);
+extern int blkid_probe_invert_partitions_filter(blkid_probe pr);
+extern int blkid_probe_filter_partitions_type(blkid_probe pr, int flag, char *names[]);
+
+/* partitions probing flags */
+#define BLKID_PARTS_FORCE_GPT		(1 << 1)
+#define BLKID_PARTS_ENTRY_DETAILS	(1 << 2)
+#define BLKID_PARTS_MAGIC		(1 << 3)
+extern int blkid_probe_set_partitions_flags(blkid_probe pr, int flags);
+
+/* binary interface */
+extern blkid_partlist blkid_probe_get_partitions(blkid_probe pr);
+
+extern int blkid_partlist_numof_partitions(blkid_partlist ls);
+extern blkid_parttable blkid_partlist_get_table(blkid_partlist ls);
+extern blkid_partition blkid_partlist_get_partition(blkid_partlist ls, int n);
+extern blkid_partition blkid_partlist_get_partition_by_partno(blkid_partlist ls, int n);
+extern blkid_partition blkid_partlist_devno_to_partition(blkid_partlist ls, dev_t devno);
+extern blkid_parttable blkid_partition_get_table(blkid_partition par);
+
+extern const char *blkid_partition_get_name(blkid_partition par);
+extern const char *blkid_partition_get_uuid(blkid_partition par);
+extern int blkid_partition_get_partno(blkid_partition par);
+extern blkid_loff_t blkid_partition_get_start(blkid_partition par);
+extern blkid_loff_t blkid_partition_get_size(blkid_partition par);
+
+extern int blkid_partition_get_type(blkid_partition par)
+			__ul_attribute__((nonnull));
+
+extern const char *blkid_partition_get_type_string(blkid_partition par);
+
+extern unsigned long long blkid_partition_get_flags(blkid_partition par)
+			__ul_attribute__((nonnull));
+
+extern int blkid_partition_is_logical(blkid_partition par)
+			__ul_attribute__((nonnull));
+extern int blkid_partition_is_extended(blkid_partition par)
+			__ul_attribute__((nonnull));
+extern int blkid_partition_is_primary(blkid_partition par)
+			__ul_attribute__((nonnull));
+
+extern const char *blkid_parttable_get_type(blkid_parttable tab);
+extern const char *blkid_parttable_get_id(blkid_parttable tab);
+
+extern blkid_loff_t blkid_parttable_get_offset(blkid_parttable tab);
+extern blkid_partition blkid_parttable_get_parent(blkid_parttable tab);
+
+/*
+ * NAME=value low-level interface
+ */
+extern int blkid_do_probe(blkid_probe pr);
+extern int blkid_do_safeprobe(blkid_probe pr);
+extern int blkid_do_fullprobe(blkid_probe pr);
+
+extern int blkid_probe_numof_values(blkid_probe pr);
+extern int blkid_probe_get_value(blkid_probe pr, int num, const char **name,
+                        const char **data, size_t *len);
+extern int blkid_probe_lookup_value(blkid_probe pr, const char *name,
+                        const char **data, size_t *len);
+extern int blkid_probe_has_value(blkid_probe pr, const char *name)
+			__ul_attribute__((nonnull));
+
+extern int blkid_do_wipe(blkid_probe pr, int dryrun);
+extern int blkid_probe_step_back(blkid_probe pr);
+
+/*
+ * Deprecated functions/macros
+ */
+#ifndef BLKID_DISABLE_DEPRECATED
+
+#define BLKID_PROBREQ_LABEL     BLKID_SUBLKS_LABEL
+#define BLKID_PROBREQ_LABELRAW  BLKID_SUBLKS_LABELRAW
+#define BLKID_PROBREQ_UUID      BLKID_SUBLKS_UUID
+#define BLKID_PROBREQ_UUIDRAW   BLKID_SUBLKS_UUIDRAW
+#define BLKID_PROBREQ_TYPE      BLKID_SUBLKS_TYPE
+#define BLKID_PROBREQ_SECTYPE   BLKID_SUBLKS_SECTYPE
+#define BLKID_PROBREQ_USAGE     BLKID_SUBLKS_USAGE
+#define BLKID_PROBREQ_VERSION   BLKID_SUBLKS_VERSION
+
+extern int blkid_probe_set_request(blkid_probe pr, int flags)
+			__ul_attribute__((deprecated));
+
+extern int blkid_probe_filter_usage(blkid_probe pr, int flag, int usage)
+			__ul_attribute__((deprecated));
+
+extern int blkid_probe_filter_types(blkid_probe pr, int flag, char *names[])
+			__ul_attribute__((deprecated));
+
+extern int blkid_probe_invert_filter(blkid_probe pr)
+			__ul_attribute__((deprecated));
+
+extern int blkid_probe_reset_filter(blkid_probe pr)
+			__ul_attribute__((deprecated));
+
+#endif /* BLKID_DISABLE_DEPRECATED */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _BLKID_BLKID_H */
diff --git a/libblkid/src/blkidP.h b/libblkid/src/blkidP.h
new file mode 100644
index 0000000..fbf4e71
--- /dev/null
+++ b/libblkid/src/blkidP.h
@@ -0,0 +1,545 @@
+/*
+ * blkidP.h - Internal interfaces for libblkid
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#ifndef _BLKID_BLKIDP_H
+#define _BLKID_BLKIDP_H
+
+/* Always confirm that /dev/disk-by symlinks match with LABEL/UUID on device */
+/* #define CONFIG_BLKID_VERIFY_UDEV 1 */
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include "c.h"
+#include "bitops.h"	/* $(top_srcdir)/include/ */
+#include "blkdev.h"
+
+#include "debug.h"
+#include "blkid.h"
+#include "list.h"
+
+/*
+ * This describes the attributes of a specific device.
+ * We can traverse all of the tags by bid_tags (linking to the tag bit_names).
+ * The bid_label and bid_uuid fields are shortcuts to the LABEL and UUID tag
+ * values, if they exist.
+ */
+struct blkid_struct_dev
+{
+	struct list_head	bid_devs;	/* All devices in the cache */
+	struct list_head	bid_tags;	/* All tags for this device */
+	blkid_cache		bid_cache;	/* Dev belongs to this cache */
+	char			*bid_name;	/* Device inode pathname */
+	char			*bid_type;	/* Preferred device TYPE */
+	int			bid_pri;	/* Device priority */
+	dev_t			bid_devno;	/* Device major/minor number */
+	time_t			bid_time;	/* Last update time of device */
+	suseconds_t		bid_utime;	/* Last update time (microseconds) */
+	unsigned int		bid_flags;	/* Device status bitflags */
+	char			*bid_label;	/* Shortcut to device LABEL */
+	char			*bid_uuid;	/* Shortcut to binary UUID */
+};
+
+#define BLKID_BID_FL_VERIFIED	0x0001	/* Device data validated from disk */
+#define BLKID_BID_FL_INVALID	0x0004	/* Device is invalid */
+#define BLKID_BID_FL_REMOVABLE	0x0008	/* Device added by blkid_probe_all_removable() */
+
+/*
+ * Each tag defines a NAME=value pair for a particular device.  The tags
+ * are linked via bit_names for a single device, so that traversing the
+ * names list will get you a list of all tags associated with a device.
+ * They are also linked via bit_values for all devices, so one can easily
+ * search all tags with a given NAME for a specific value.
+ */
+struct blkid_struct_tag
+{
+	struct list_head	bit_tags;	/* All tags for this device */
+	struct list_head	bit_names;	/* All tags with given NAME */
+	char			*bit_name;	/* NAME of tag (shared) */
+	char			*bit_val;	/* value of tag */
+	blkid_dev		bit_dev;	/* pointer to device */
+};
+typedef struct blkid_struct_tag *blkid_tag;
+
+/*
+ * Chain IDs
+ */
+enum {
+	BLKID_CHAIN_SUBLKS,	/* FS/RAID superblocks (enabled by default) */
+	BLKID_CHAIN_TOPLGY,	/* Block device topology */
+	BLKID_CHAIN_PARTS,	/* Partition tables */
+
+	BLKID_NCHAINS		/* number of chains */
+};
+
+struct blkid_chain {
+	const struct blkid_chaindrv *driver;	/* chain driver */
+
+	int		enabled;	/* boolean */
+	int		flags;		/* BLKID_<chain>_* */
+	int		binary;		/* boolean */
+	int		idx;		/* index of the current prober (or -1) */
+	unsigned long	*fltr;		/* filter or NULL */
+	void		*data;		/* private chain data or NULL */
+};
+
+/*
+ * Chain driver
+ */
+struct blkid_chaindrv {
+	const size_t	id;		/* BLKID_CHAIN_* */
+	const char	*name;		/* name of chain (for debug purpose) */
+	const int	dflt_flags;	/* default chain flags */
+	const int	dflt_enabled;	/* default enabled boolean */
+	int		has_fltr;	/* boolean */
+
+	const struct blkid_idinfo **idinfos; /* description of probing functions */
+	const size_t	nidinfos;	/* number of idinfos */
+
+	/* driver operations */
+	int		(*probe)(blkid_probe, struct blkid_chain *);
+	int		(*safeprobe)(blkid_probe, struct blkid_chain *);
+	void		(*free_data)(blkid_probe, void *);
+};
+
+/*
+ * Low-level probe result
+ */
+#define BLKID_PROBVAL_BUFSIZ	128
+
+#define BLKID_NVALS_SUBLKS	18
+#define BLKID_NVALS_TOPLGY	5
+#define BLKID_NVALS_PARTS	13
+
+/* Max number of all values in probing result */
+#define BLKID_NVALS             (BLKID_NVALS_SUBLKS + \
+				 BLKID_NVALS_TOPLGY + \
+				 BLKID_NVALS_PARTS)
+
+struct blkid_prval
+{
+	const char	*name;			/* value name */
+	unsigned char	data[BLKID_PROBVAL_BUFSIZ]; /* value data */
+	size_t		len;			/* length of value data */
+
+	struct blkid_chain	*chain;		/* owner */
+};
+
+/*
+ * Filesystem / Raid magic strings
+ */
+struct blkid_idmag
+{
+	const char	*magic;		/* magic string */
+	unsigned int	len;		/* length of magic */
+
+	long		kboff;		/* kilobyte offset of superblock */
+	unsigned int	sboff;		/* byte offset within superblock */
+};
+
+/*
+ * Filesystem / Raid description
+ */
+struct blkid_idinfo
+{
+	const char	*name;		/* fs, raid or partition table name */
+	int		usage;		/* BLKID_USAGE_* flag */
+	int		flags;		/* BLKID_IDINFO_* flags */
+	int		minsz;		/* minimal device size */
+
+					/* probe function */
+	int		(*probefunc)(blkid_probe pr, const struct blkid_idmag *mag);
+
+	struct blkid_idmag	magics[];	/* NULL or array with magic strings */
+};
+
+#define BLKID_NONE_MAGIC	{{ NULL }}
+
+/*
+ * tolerant FS - can share the same device with more filesystems (e.g. typical
+ * on CD-ROMs). We need this flag to detect ambivalent results (e.g. valid fat
+ * and valid linux swap on the same device).
+ */
+#define BLKID_IDINFO_TOLERANT	(1 << 1)
+
+struct blkid_bufinfo {
+	unsigned char		*data;
+	blkid_loff_t		off;
+	blkid_loff_t		len;
+	struct list_head	bufs;	/* list of buffers */
+};
+
+/*
+ * Low-level probing control struct
+ */
+struct blkid_struct_probe
+{
+	int			fd;		/* device file descriptor */
+	blkid_loff_t		off;		/* begin of data on the device */
+	blkid_loff_t		size;		/* end of data on the device */
+
+	dev_t			devno;		/* device number (st.st_rdev) */
+	dev_t			disk_devno;	/* devno of the whole-disk or 0 */
+	unsigned int		blkssz;		/* sector size (BLKSSZGET ioctl) */
+	mode_t			mode;		/* struct stat.sb_mode */
+
+	int			flags;		/* private libray flags */
+	int			prob_flags;	/* always zeroized by blkid_do_*() */
+
+	blkid_loff_t		wipe_off;	/* begin of the wiped area */
+	blkid_loff_t		wipe_size;	/* size of the wiped area */
+	struct blkid_chain	*wipe_chain;	/* superblock, partition, ... */
+
+	struct list_head	buffers;	/* list of buffers */
+
+	struct blkid_chain	chains[BLKID_NCHAINS];	/* array of chains */
+	struct blkid_chain	*cur_chain;		/* current chain */
+
+	struct blkid_prval	vals[BLKID_NVALS];	/* results */
+	int			nvals;		/* number of assigned vals */
+
+	struct blkid_struct_probe *parent;	/* for clones */
+	struct blkid_struct_probe *disk_probe;	/* whole-disk probing */
+};
+
+/* private flags library flags */
+#define BLKID_FL_PRIVATE_FD	(1 << 1)	/* see blkid_new_probe_from_filename() */
+#define BLKID_FL_TINY_DEV	(1 << 2)	/* <= 1.47MiB (floppy or so) */
+#define BLKID_FL_CDROM_DEV	(1 << 3)	/* is a CD/DVD drive */
+#define BLKID_FL_NOSCAN_DEV	(1 << 4)	/* do not scan this device */
+
+/* private per-probing flags */
+#define BLKID_PROBE_FL_IGNORE_PT (1 << 1)	/* ignore partition table */
+
+extern blkid_probe blkid_clone_probe(blkid_probe parent);
+extern blkid_probe blkid_probe_get_wholedisk_probe(blkid_probe pr);
+
+/*
+ * Evaluation methods (for blkid_eval_* API)
+ */
+enum {
+	BLKID_EVAL_UDEV = 0,
+	BLKID_EVAL_SCAN,
+
+	__BLKID_EVAL_LAST
+};
+
+/*
+ * Library config options
+ */
+struct blkid_config {
+	int eval[__BLKID_EVAL_LAST];	/* array with EVALUATION=<udev,cache> options */
+	int nevals;			/* number of elems in eval array */
+	int uevent;			/* SEND_UEVENT=<yes|not> option */
+	char *cachefile;		/* CACHE_FILE=<path> option */
+};
+
+extern struct blkid_config *blkid_read_config(const char *filename)
+			__ul_attribute__((warn_unused_result));
+extern void blkid_free_config(struct blkid_config *conf);
+
+/*
+ * Minimum number of seconds between device probes, even when reading
+ * from the cache.  This is to avoid re-probing all devices which were
+ * just probed by another program that does not share the cache.
+ */
+#define BLKID_PROBE_MIN		2
+
+/*
+ * Time in seconds an entry remains verified in the in-memory cache
+ * before being reverified (in case of long-running processes that
+ * keep a cache in memory and continue to use it for a long time).
+ */
+#define BLKID_PROBE_INTERVAL	200
+
+/* This describes an entire blkid cache file and probed devices.
+ * We can traverse all of the found devices via bic_list.
+ * We can traverse all of the tag types by bic_tags, which hold empty tags
+ * for each tag type.  Those tags can be used as list_heads for iterating
+ * through all devices with a specific tag type (e.g. LABEL).
+ */
+struct blkid_struct_cache
+{
+	struct list_head	bic_devs;	/* List head of all devices */
+	struct list_head	bic_tags;	/* List head of all tag types */
+	time_t			bic_time;	/* Last probe time */
+	time_t			bic_ftime;	/* Mod time of the cachefile */
+	unsigned int		bic_flags;	/* Status flags of the cache */
+	char			*bic_filename;	/* filename of cache */
+	blkid_probe		probe;		/* low-level probing stuff */
+};
+
+#define BLKID_BIC_FL_PROBED	0x0002	/* We probed /proc/partition devices */
+#define BLKID_BIC_FL_CHANGED	0x0004	/* Cache has changed from disk */
+
+/* config file */
+#define BLKID_CONFIG_FILE	"/etc/blkid.conf"
+
+/* cache file on systemds with /run */
+#define BLKID_RUNTIME_TOPDIR	"/run"
+#define BLKID_RUNTIME_DIR	BLKID_RUNTIME_TOPDIR "/blkid"
+#define BLKID_CACHE_FILE	BLKID_RUNTIME_DIR "/blkid.tab"
+
+/* old systems */
+#define BLKID_CACHE_FILE_OLD	"/etc/blkid.tab"
+
+#define BLKID_PROBE_OK	 0
+#define BLKID_PROBE_NONE 1
+
+#define BLKID_ERR_IO	 5
+#define BLKID_ERR_PROC	 9
+#define BLKID_ERR_MEM	12
+#define BLKID_ERR_CACHE	14
+#define BLKID_ERR_DEV	19
+#define BLKID_ERR_PARAM	22
+#define BLKID_ERR_BIG	27
+
+/*
+ * Priority settings for different types of devices
+ */
+#define BLKID_PRI_UBI	50
+#define BLKID_PRI_DM	40
+#define BLKID_PRI_EVMS	30
+#define BLKID_PRI_LVM	20
+#define BLKID_PRI_MD	10
+
+#define BLKID_DEBUG_HELP	(1 << 0)
+#define BLKID_DEBUG_INIT	(1 << 1)
+#define BLKID_DEBUG_CACHE	(1 << 2)
+#define BLKID_DEBUG_CONFIG	(1 << 3)
+#define BLKID_DEBUG_DEV		(1 << 4)
+#define BLKID_DEBUG_DEVNAME	(1 << 5)
+#define BLKID_DEBUG_DEVNO	(1 << 6)
+#define BLKID_DEBUG_EVALUATE	(1 << 7)
+#define BLKID_DEBUG_LOWPROBE	(1 << 8)
+#define BLKID_DEBUG_PROBE	(1 << 9)
+#define BLKID_DEBUG_READ	(1 << 10)
+#define BLKID_DEBUG_SAVE	(1 << 11)
+#define BLKID_DEBUG_TAG		(1 << 12)
+#define BLKID_DEBUG_ALL		0xFFFF		/* (1 << 16) aka FFFF is expected by API */
+
+UL_DEBUG_DECLARE_MASK(libblkid);
+#define DBG(m, x)	__UL_DBG(libblkid, BLKID_DEBUG_, m, x)
+#define ON_DBG(m, x)    __UL_DBG_CALL(libblkid, BLKID_DEBUG_, m, x)
+
+extern void blkid_debug_dump_dev(blkid_dev dev);
+extern void blkid_debug_dump_tag(blkid_tag tag);
+
+
+/* devno.c */
+struct dir_list {
+	char	*name;
+	struct dir_list *next;
+};
+extern void blkid__scan_dir(char *, dev_t, struct dir_list **, char **)
+			__attribute__((nonnull(1,4)));
+extern int blkid_driver_has_major(const char *drvname, int major)
+			__attribute__((warn_unused_result));
+
+/* lseek.c */
+extern blkid_loff_t blkid_llseek(int fd, blkid_loff_t offset, int whence);
+
+/* read.c */
+extern void blkid_read_cache(blkid_cache cache)
+			__attribute__((nonnull));
+
+/* save.c */
+extern int blkid_flush_cache(blkid_cache cache)
+			__attribute__((nonnull));
+
+/* cache */
+extern char *blkid_safe_getenv(const char *arg)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern char *blkid_get_cache_filename(struct blkid_config *conf)
+			__attribute__((warn_unused_result));
+/*
+ * Functions to create and find a specific tag type: tag.c
+ */
+extern void blkid_free_tag(blkid_tag tag);
+extern blkid_tag blkid_find_tag_dev(blkid_dev dev, const char *type)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern int blkid_set_tag(blkid_dev dev, const char *name,
+			 const char *value, const int vlength)
+			__attribute__((nonnull(1,2)));
+
+/*
+ * Functions to create and find a specific tag type: dev.c
+ */
+extern blkid_dev blkid_new_dev(void)
+			__attribute__((warn_unused_result));
+extern void blkid_free_dev(blkid_dev dev);
+
+/* probe.c */
+extern int blkid_probe_is_tiny(blkid_probe pr)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+extern int blkid_probe_is_cdrom(blkid_probe pr)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern unsigned char *blkid_probe_get_buffer(blkid_probe pr,
+                                blkid_loff_t off, blkid_loff_t len)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern unsigned char *blkid_probe_get_sector(blkid_probe pr, unsigned int sector)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern int blkid_probe_get_dimension(blkid_probe pr,
+	                blkid_loff_t *off, blkid_loff_t *size)
+			__attribute__((nonnull));
+
+extern int blkid_probe_set_dimension(blkid_probe pr,
+	                blkid_loff_t off, blkid_loff_t size)
+			__attribute__((nonnull));
+
+extern int blkid_probe_get_idmag(blkid_probe pr, const struct blkid_idinfo *id,
+			blkid_loff_t *offset, const struct blkid_idmag **res)
+			__attribute__((nonnull(1)));
+
+/* returns superblok according to 'struct blkid_idmag' */
+#define blkid_probe_get_sb(_pr, _mag, type) \
+			((type *) blkid_probe_get_buffer((_pr),\
+					(_mag)->kboff << 10, sizeof(type)))
+
+extern blkid_partlist blkid_probe_get_partlist(blkid_probe pr)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern int blkid_probe_is_covered_by_pt(blkid_probe pr,
+					blkid_loff_t offset, blkid_loff_t size)
+			__attribute__((warn_unused_result));
+
+extern void blkid_probe_chain_reset_vals(blkid_probe pr, struct blkid_chain *chn)
+			__attribute__((nonnull));
+extern int blkid_probe_chain_copy_vals(blkid_probe pr,
+				       struct blkid_chain *chn,
+			               struct blkid_prval *vals,
+				       int nvals)
+			__attribute__((nonnull));
+
+extern struct blkid_prval *blkid_probe_assign_value(blkid_probe pr,
+					const char *name)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern int blkid_probe_reset_last_value(blkid_probe pr)
+			__attribute__((nonnull));
+extern void blkid_probe_append_vals(blkid_probe pr,
+				    struct blkid_prval *vals,
+				    int nvals)
+			__attribute__((nonnull));
+
+extern struct blkid_chain *blkid_probe_get_chain(blkid_probe pr)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern struct blkid_prval *__blkid_probe_get_value(blkid_probe pr, int num)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern struct blkid_prval *__blkid_probe_lookup_value(blkid_probe pr, const char *name)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern unsigned long *blkid_probe_get_filter(blkid_probe pr, int chain, int create)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern int __blkid_probe_invert_filter(blkid_probe pr, int chain)
+			__attribute__((nonnull));
+extern int __blkid_probe_reset_filter(blkid_probe pr, int chain)
+			__attribute__((nonnull));
+extern int __blkid_probe_filter_types(blkid_probe pr, int chain, int flag, char *names[])
+			__attribute__((nonnull));
+
+extern void *blkid_probe_get_binary_data(blkid_probe pr, struct blkid_chain *chn)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+
+extern int blkid_probe_set_value(blkid_probe pr, const char *name,
+				unsigned char *data, size_t len)
+			__attribute__((nonnull));
+
+extern int blkid_probe_vsprintf_value(blkid_probe pr, const char *name,
+				const char *fmt, va_list ap)
+			__attribute__((nonnull));
+
+extern int blkid_probe_sprintf_value(blkid_probe pr, const char *name,
+				const char *fmt, ...)
+			__attribute__((nonnull))
+			__attribute__ ((__format__ (__printf__, 3, 4)));
+
+extern int blkid_probe_set_magic(blkid_probe pr, blkid_loff_t offset,
+				size_t len, unsigned char *magic)
+			__attribute__((nonnull));
+
+extern int blkid_probe_verify_csum(blkid_probe pr, uint64_t csum, uint64_t expected)
+			__attribute__((nonnull));
+
+extern void blkid_unparse_uuid(const unsigned char *uuid, char *str, size_t len)
+			__attribute__((nonnull));
+extern int blkid_uuid_is_empty(const unsigned char *buf, size_t len);
+
+extern size_t blkid_rtrim_whitespace(unsigned char *str)
+			__attribute__((nonnull));
+extern size_t blkid_ltrim_whitespace(unsigned char *str)
+			__attribute__((nonnull));
+
+extern void blkid_probe_set_wiper(blkid_probe pr, blkid_loff_t off,
+				  blkid_loff_t size)
+			__attribute__((nonnull));
+extern int blkid_probe_is_wiped(blkid_probe pr, struct blkid_chain **chn,
+		                blkid_loff_t off, blkid_loff_t size)
+			__attribute__((nonnull))
+			__attribute__((warn_unused_result));
+extern void blkid_probe_use_wiper(blkid_probe pr, blkid_loff_t off, blkid_loff_t size)
+			__attribute__((nonnull));
+
+/* filter bitmap macros */
+#define blkid_bmp_wordsize		(8 * sizeof(unsigned long))
+#define blkid_bmp_idx_bit(item)		(1UL << ((item) % blkid_bmp_wordsize))
+#define blkid_bmp_idx_byte(item)	((item) / blkid_bmp_wordsize)
+
+#define blkid_bmp_set_item(bmp, item)	\
+		((bmp)[ blkid_bmp_idx_byte(item) ] |= blkid_bmp_idx_bit(item))
+
+#define blkid_bmp_unset_item(bmp, item)	\
+		((bmp)[ blkid_bmp_idx_byte(item) ] &= ~blkid_bmp_idx_bit(item))
+
+#define blkid_bmp_get_item(bmp, item)	\
+		((bmp)[ blkid_bmp_idx_byte(item) ] & blkid_bmp_idx_bit(item))
+
+#define blkid_bmp_nwords(max_items) \
+		(((max_items) + blkid_bmp_wordsize) / blkid_bmp_wordsize)
+
+#define blkid_bmp_nbytes(max_items) \
+		(blkid_bmp_nwords(max_items) * sizeof(unsigned long))
+
+/* encode.c */
+extern size_t blkid_encode_to_utf8(int enc, unsigned char *dest, size_t len,
+				const unsigned char *src, size_t count)
+			__attribute__((nonnull));
+
+#define BLKID_ENC_UTF16BE	0
+#define BLKID_ENC_UTF16LE	1
+
+#endif /* _BLKID_BLKIDP_H */
diff --git a/libblkid/src/c.h b/libblkid/src/c.h
new file mode 100644
index 0000000..6fcbc6d
--- /dev/null
+++ b/libblkid/src/c.h
@@ -0,0 +1,295 @@
+/*
+ * Fundamental C definitions.
+ */
+
+#ifndef UTIL_LINUX_C_H
+#define UTIL_LINUX_C_H
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "blkid.h"
+#ifdef HAVE_ERR_H
+# include <err.h>
+#endif
+
+#ifndef HAVE_USLEEP
+# include <time.h>
+#endif
+
+/*
+ * Compiler specific stuff
+ */
+#ifndef __GNUC_PREREQ
+# if defined __GNUC__ && defined __GNUC_MINOR__
+#  define __GNUC_PREREQ(maj, min) \
+	((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+# else
+#  define __GNUC_PREREQ(maj, min) 0
+# endif
+#endif
+
+#ifdef __GNUC__
+
+/* &a[0] degrades to a pointer: a different type from an array */
+# define __must_be_array(a) \
+	UL_BUILD_BUG_ON_ZERO(__builtin_types_compatible_p(__typeof__(a), __typeof__(&a[0])))
+
+# define ignore_result(x) ({ \
+	__typeof__(x) __dummy __attribute__((__unused__)) = (x); (void) __dummy; \
+})
+
+#else /* !__GNUC__ */
+# define __must_be_array(a)	0
+# define __attribute__(_arg_)
+# define ignore_result(x) ((void) (x))
+#endif /* !__GNUC__ */
+
+/*
+ * Function attributes
+ */
+#ifndef __ul_alloc_size
+# if __GNUC_PREREQ (4, 3)
+#  define __ul_alloc_size(s) __attribute__((alloc_size(s)))
+# else
+#  define __ul_alloc_size(s)
+# endif
+#endif
+
+#ifndef __ul_calloc_size
+# if __GNUC_PREREQ (4, 3)
+#  define __ul_calloc_size(n, s) __attribute__((alloc_size(n, s)))
+# else
+#  define __ul_calloc_size(n, s)
+# endif
+#endif
+
+/* Force a compilation error if condition is true, but also produce a
+ * result (of value 0 and type size_t), so the expression can be used
+ * e.g. in a structure initializer (or where-ever else comma expressions
+ * aren't permitted).
+ */
+#define UL_BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
+#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
+
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
+#endif
+
+#ifndef PATH_MAX
+# define PATH_MAX 4096
+#endif
+
+#ifndef TRUE
+# define TRUE 1
+#endif
+
+#ifndef FALSE
+# define FALSE 0
+#endif
+
+#ifndef min
+# define min(x, y) ({				\
+	__typeof__(x) _min1 = (x);		\
+	__typeof__(y) _min2 = (y);		\
+	(void) (&_min1 == &_min2);		\
+	_min1 < _min2 ? _min1 : _min2; })
+#endif
+
+#ifndef max
+# define max(x, y) ({				\
+	__typeof__(x) _max1 = (x);		\
+	__typeof__(y) _max2 = (y);		\
+	(void) (&_max1 == &_max2);		\
+	_max1 > _max2 ? _max1 : _max2; })
+#endif
+
+#ifndef cmp_numbers
+# define cmp_numbers(x, y) __extension__ ({     \
+        __typeof__(x) _a = (x);                 \
+        __typeof__(y) _b = (y);                 \
+        (void) (&_a == &_b);                    \
+        _a == _b ? 0 : _a > _b ? 1 : -1; })
+#endif
+
+#ifndef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#endif
+
+#ifndef container_of
+#define container_of(ptr, type, member) ({                       \
+	const __typeof__( ((type *)0)->member ) *__mptr = (ptr); \
+	(type *)( (char *)__mptr - offsetof(type,member) );})
+#endif
+
+#ifndef HAVE_PROGRAM_INVOCATION_SHORT_NAME
+# ifdef HAVE___PROGNAME
+extern char *__progname;
+#  define program_invocation_short_name __progname
+# else
+#  ifdef HAVE_GETEXECNAME
+#   define program_invocation_short_name \
+		prog_inv_sh_nm_from_file(getexecname(), 0)
+#  else
+#   define program_invocation_short_name \
+		prog_inv_sh_nm_from_file(__FILE__, 1)
+#  endif
+static char prog_inv_sh_nm_buf[256];
+static inline char *
+prog_inv_sh_nm_from_file(char *f, char stripext)
+{
+	char *t;
+
+	if ((t = strrchr(f, '/')) != NULL)
+		t++;
+	else
+		t = f;
+
+	strncpy(prog_inv_sh_nm_buf, t, sizeof(prog_inv_sh_nm_buf) - 1);
+	prog_inv_sh_nm_buf[sizeof(prog_inv_sh_nm_buf) - 1] = '\0';
+
+	if (stripext && (t = strrchr(prog_inv_sh_nm_buf, '.')) != NULL)
+		*t = '\0';
+
+	return prog_inv_sh_nm_buf;
+}
+# endif
+#endif
+
+
+#ifndef HAVE_ERR_H
+static inline void
+errmsg(char doexit, int excode, char adderr, const char *fmt, ...)
+{
+	fprintf(stderr, "%s: ", program_invocation_short_name);
+	if (fmt != NULL) {
+		va_list argp;
+		va_start(argp, fmt);
+		vfprintf(stderr, fmt, argp);
+		va_end(argp);
+		if (adderr)
+			fprintf(stderr, ": ");
+	}
+	if (adderr)
+		fprintf(stderr, "%m");
+	fprintf(stderr, "\n");
+	if (doexit)
+		exit(excode);
+}
+
+#ifndef HAVE_ERR
+# define err(E, FMT...) errmsg(1, E, 1, FMT)
+#endif
+
+#ifndef HAVE_ERRX
+# define errx(E, FMT...) errmsg(1, E, 0, FMT)
+#endif
+
+#ifndef HAVE_WARN
+# define warn(FMT...) errmsg(0, 0, 1, FMT)
+#endif
+
+#ifndef HAVE_WARNX
+# define warnx(FMT...) errmsg(0, 0, 0, FMT)
+#endif
+#endif /* !HAVE_ERR_H */
+
+
+static inline __attribute__((const)) int is_power_of_2(unsigned long num)
+{
+	return (num != 0 && ((num & (num - 1)) == 0));
+}
+
+#ifndef HAVE_LOFF_T
+typedef int64_t loff_t;
+#endif
+
+#if !defined(HAVE_DIRFD) && (!defined(HAVE_DECL_DIRFD) || HAVE_DECL_DIRFD == 0) && defined(HAVE_DIR_DD_FD)
+#include <sys/types.h>
+#include <dirent.h>
+static inline int dirfd(DIR *d)
+{
+	return d->dd_fd;
+}
+#endif
+
+/*
+ * Fallback defines for old versions of glibc
+ */
+#include <fcntl.h>
+
+#ifdef O_CLOEXEC
+#define UL_CLOEXECSTR   "e"
+#else
+#define UL_CLOEXECSTR   ""
+#endif
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+#ifndef AI_ADDRCONFIG
+#define AI_ADDRCONFIG 0x0020
+#endif
+
+#ifndef IUTF8
+#define IUTF8 0040000
+#endif
+
+/*
+ * MAXHOSTNAMELEN replacement
+ */
+static inline size_t get_hostname_max(void)
+{
+	long len = sysconf(255);
+
+	if (0 < len)
+		return len;
+
+#ifdef MAXHOSTNAMELEN
+	return MAXHOSTNAMELEN;
+#elif HOST_NAME_MAX
+	return HOST_NAME_MAX;
+#endif
+	return 64;
+}
+
+/*
+ * Constant strings for usage() functions. For more info see
+ * Documentation/howto-usage-function.txt and sys-utils/arch.c
+ */
+#define USAGE_HEADER     _("\nUsage:\n")
+#define USAGE_OPTIONS    _("\nOptions:\n")
+#define USAGE_SEPARATOR  _("\n")
+#define USAGE_HELP       _(" -h, --help     display this help and exit\n")
+#define USAGE_VERSION    _(" -V, --version  output version information and exit\n")
+#define USAGE_MAN_TAIL(_man)   _("\nFor more details see %s.\n"), _man
+
+#define UTIL_LINUX_VERSION _("%s from %s\n"), program_invocation_short_name, PACKAGE_STRING
+
+/*
+ * scanf modifiers for "strings allocation"
+ */
+#ifdef HAVE_SCANF_MS_MODIFIER
+#define UL_SCNsA	"%ms"
+#elif defined(HAVE_SCANF_AS_MODIFIER)
+#define UL_SCNsA	"%as"
+#endif
+
+/*
+ * seek stuff
+ */
+#ifndef SEEK_DATA
+# define SEEK_DATA	3
+#endif
+#ifndef SEEK_HOLE
+# define SEEK_HOLE	4
+#endif
+
+#endif /* UTIL_LINUX_C_H */
diff --git a/libblkid/src/cache.c b/libblkid/src/cache.c
new file mode 100644
index 0000000..b576df8
--- /dev/null
+++ b/libblkid/src/cache.c
@@ -0,0 +1,226 @@
+/*
+ * cache.c - allocation/initialization/free routines for cache
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include "blkidP.h"
+#include "env.h"
+
+/**
+ * SECTION:cache
+ * @title: Cache
+ * @short_description: basic routines to work with libblkid cache
+ *
+ * Block device information is normally kept in a cache file blkid.tab and is
+ * verified to still be valid before being returned to the user (if the user has
+ * read permission on the raw block device, otherwise not).  The cache file also
+ * allows unprivileged users (normally anyone other than root, or those not in the
+ * "disk" group) to locate devices by label/id.  The standard location of the
+ * cache file can be overridden by the environment variable BLKID_FILE.
+ *
+ * In situations where one is getting information about a single known device, it
+ * does not impact performance whether the cache is used or not (unless you are
+ * not able to read the block device directly).  If you are dealing with multiple
+ * devices, use of the cache is highly recommended (even if empty) as devices will
+ * be scanned at most one time and the on-disk cache will be updated if possible.
+ * There is rarely a reason not to use the cache.
+ *
+ * In some cases (modular kernels), block devices are not even visible until after
+ * they are accessed the first time, so it is critical that there is some way to
+ * locate these devices without enumerating only visible devices, so the use of
+ * the cache file is required in this situation.
+ */
+static const char *get_default_cache_filename(void)
+{
+	struct stat st;
+
+	if (stat(BLKID_RUNTIME_TOPDIR, &st) == 0 && S_ISDIR(st.st_mode))
+		return BLKID_CACHE_FILE;	/* cache in /run */
+
+	return BLKID_CACHE_FILE_OLD;	/* cache in /etc */
+}
+
+/* returns allocated path to cache */
+char *blkid_get_cache_filename(struct blkid_config *conf)
+{
+	char *filename;
+
+	filename = safe_getenv("BLKID_FILE");
+	if (filename)
+		filename = strdup(filename);
+	else if (conf)
+		filename = conf->cachefile ? strdup(conf->cachefile) : NULL;
+	else {
+		struct blkid_config *c = blkid_read_config(NULL);
+		if (!c)
+			filename = strdup(get_default_cache_filename());
+		else {
+			filename = c->cachefile;  /* already allocated */
+			c->cachefile = NULL;
+			blkid_free_config(c);
+		}
+	}
+	return filename;
+}
+
+/**
+ * blkid_get_cache:
+ * @cache: pointer to return cache handler
+ * @filename: path to the cache file or NULL for the default path
+ *
+ * Allocates and initialize librray cache handler.
+ *
+ * Returns: 0 on success or number less than zero in case of error.
+ */
+int blkid_get_cache(blkid_cache *ret_cache, const char *filename)
+{
+	blkid_cache cache;
+
+	if (!ret_cache)
+		return -BLKID_ERR_PARAM;
+
+	blkid_init_debug(0);
+
+	DBG(CACHE, ul_debug("creating blkid cache (using %s)",
+				filename ? filename : "default cache"));
+
+	if (!(cache = (blkid_cache) calloc(1, sizeof(struct blkid_struct_cache))))
+		return -BLKID_ERR_MEM;
+
+	INIT_LIST_HEAD(&cache->bic_devs);
+	INIT_LIST_HEAD(&cache->bic_tags);
+
+	if (filename && !*filename)
+		filename = NULL;
+	if (filename)
+		cache->bic_filename = strdup(filename);
+	else
+		cache->bic_filename = blkid_get_cache_filename(NULL);
+
+	blkid_read_cache(cache);
+	*ret_cache = cache;
+	return 0;
+}
+
+/**
+ * blkid_put_cache:
+ * @cache: cache handler
+ *
+ * Saves changes to cache file.
+ */
+void blkid_put_cache(blkid_cache cache)
+{
+	if (!cache)
+		return;
+
+	(void) blkid_flush_cache(cache);
+
+	DBG(CACHE, ul_debug("freeing cache struct"));
+
+	/* DBG(CACHE, ul_debug_dump_cache(cache)); */
+
+	while (!list_empty(&cache->bic_devs)) {
+		blkid_dev dev = list_entry(cache->bic_devs.next,
+					   struct blkid_struct_dev,
+					    bid_devs);
+		blkid_free_dev(dev);
+	}
+
+	while (!list_empty(&cache->bic_tags)) {
+		blkid_tag tag = list_entry(cache->bic_tags.next,
+					   struct blkid_struct_tag,
+					   bit_tags);
+
+		while (!list_empty(&tag->bit_names)) {
+			blkid_tag bad = list_entry(tag->bit_names.next,
+						   struct blkid_struct_tag,
+						   bit_names);
+
+			DBG(CACHE, ul_debug("warning: unfreed tag %s=%s",
+						bad->bit_name, bad->bit_val));
+			blkid_free_tag(bad);
+		}
+		blkid_free_tag(tag);
+	}
+
+	blkid_free_probe(cache->probe);
+
+	free(cache->bic_filename);
+	free(cache);
+}
+
+/**
+ * blkid_gc_cache:
+ * @cache: cache handler
+ *
+ * Removes garbage (non-existing devices) from the cache.
+ */
+void blkid_gc_cache(blkid_cache cache)
+{
+	struct list_head *p, *pnext;
+	struct stat st;
+
+	if (!cache)
+		return;
+
+	list_for_each_safe(p, pnext, &cache->bic_devs) {
+		blkid_dev dev = list_entry(p, struct blkid_struct_dev, bid_devs);
+		if (stat(dev->bid_name, &st) < 0) {
+			DBG(CACHE, ul_debug("freeing %s", dev->bid_name));
+			blkid_free_dev(dev);
+			cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+		} else {
+			DBG(CACHE, ul_debug("Device %s exists", dev->bid_name));
+		}
+	}
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char** argv)
+{
+	blkid_cache cache = NULL;
+	int ret;
+
+	blkid_init_debug(BLKID_DEBUG_ALL);
+
+	if ((argc > 2)) {
+		fprintf(stderr, "Usage: %s [filename] \n", argv[0]);
+		exit(1);
+	}
+
+	if ((ret = blkid_get_cache(&cache, argv[1])) < 0) {
+		fprintf(stderr, "error %d parsing cache file %s\n", ret,
+			argv[1] ? argv[1] : blkid_get_cache_filename(NULL));
+		exit(1);
+	}
+	if ((ret = blkid_get_cache(&cache, "/dev/null")) != 0) {
+		fprintf(stderr, "%s: error creating cache (%d)\n",
+			argv[0], ret);
+		exit(1);
+	}
+	if ((ret = blkid_probe_all(cache)) < 0)
+		fprintf(stderr, "error probing devices\n");
+
+	blkid_put_cache(cache);
+
+	return ret;
+}
+#endif
diff --git a/libblkid/src/canonicalize.h b/libblkid/src/canonicalize.h
new file mode 100644
index 0000000..7a18aca
--- /dev/null
+++ b/libblkid/src/canonicalize.h
@@ -0,0 +1,21 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library Public License for more details.
+ */
+#ifndef CANONICALIZE_H
+#define CANONICALIZE_H
+
+#include "c.h"	/* for PATH_MAX */
+
+extern char *canonicalize_path(const char *path);
+extern char *canonicalize_path_restricted(const char *path);
+extern char *canonicalize_dm_name(const char *ptname);
+
+#endif /* CANONICALIZE_H */
diff --git a/libblkid/src/closestream.h b/libblkid/src/closestream.h
new file mode 100644
index 0000000..7842456
--- /dev/null
+++ b/libblkid/src/closestream.h
@@ -0,0 +1,71 @@
+#ifndef UTIL_LINUX_CLOSESTREAM_H
+#define UTIL_LINUX_CLOSESTREAM_H
+
+#include <stdio.h>
+#ifdef HAVE_STDIO_EXT_H
+#include <stdio_ext.h>
+#endif
+#include <unistd.h>
+
+#include "c.h"
+#include "nls.h"
+
+#ifndef HAVE___FPENDING
+static inline int
+__fpending(FILE *stream __attribute__((__unused__)))
+{
+	return 0;
+}
+#endif
+
+static inline int
+close_stream(FILE * stream)
+{
+	const int some_pending = (__fpending(stream) != 0);
+	const int prev_fail = (ferror(stream) != 0);
+	const int fclose_fail = (fclose(stream) != 0);
+
+	if (prev_fail || (fclose_fail && (some_pending || errno != EBADF))) {
+		if (!fclose_fail && !(errno == EPIPE))
+			errno = 0;
+		return EOF;
+	}
+	return 0;
+}
+
+/* Meant to be used atexit(close_stdout); */
+static inline void
+close_stdout(void)
+{
+	if (close_stream(stdout) != 0 && !(errno == EPIPE)) {
+		if (errno)
+			warn(_("write error"));
+		else
+			warnx(_("write error"));
+		_exit(EXIT_FAILURE);
+	}
+
+	if (close_stream(stderr) != 0)
+		_exit(EXIT_FAILURE);
+}
+
+#ifndef HAVE_FSYNC
+static inline int
+fsync(int fd __attribute__((__unused__)))
+{
+	return 0;
+}
+#endif
+
+static inline int
+close_fd(int fd)
+{
+	const int fsync_fail = (fsync(fd) != 0);
+	const int close_fail = (close(fd) != 0);
+
+	if (fsync_fail || close_fail)
+		return EOF;
+	return 0;
+}
+
+#endif /* UTIL_LINUX_CLOSESTREAM_H */
diff --git a/libblkid/src/colors.h b/libblkid/src/colors.h
new file mode 100644
index 0000000..97efc48
--- /dev/null
+++ b/libblkid/src/colors.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef UTIL_LINUX_COLORS_H
+#define UTIL_LINUX_COLORS_H
+
+#include <stdio.h>
+#include <unistd.h>
+
+#define UL_COLOR_RESET		"\033[0m"
+#define UL_COLOR_BOLD		"\033[1m"
+#define UL_COLOR_HALFBRIGHT	"\033[2m"
+#define UL_COLOR_UNDERSCORE	"\033[4m"
+#define UL_COLOR_BLINK		"\033[5m"
+#define UL_COLOR_REVERSE	"\033[7m"
+
+/* Standard colors */
+#define UL_COLOR_BLACK		"\033[30m"
+#define UL_COLOR_RED		"\033[31m"
+#define UL_COLOR_GREEN		"\033[32m"
+#define UL_COLOR_BROWN		"\033[33m"	/* well, brown */
+#define UL_COLOR_BLUE		"\033[34m"
+#define UL_COLOR_MAGENTA	"\033[35m"
+#define UL_COLOR_CYAN		"\033[36m"
+#define UL_COLOR_GRAY		"\033[37m"
+
+/* Bold variants */
+#define UL_COLOR_DARK_GRAY	"\033[1;30m"
+#define UL_COLOR_BOLD_RED	"\033[1;31m"
+#define UL_COLOR_BOLD_GREEN	"\033[1;32m"
+#define UL_COLOR_BOLD_YELLOW	"\033[1;33m"
+#define UL_COLOR_BOLD_BLUE	"\033[1;34m"
+#define UL_COLOR_BOLD_MAGENTA	"\033[1;35m"
+#define UL_COLOR_BOLD_CYAN	"\033[1;36m"
+
+#define UL_COLOR_WHITE		"\033[1;37m"
+
+/* --color[=WHEN] */
+enum colortmode {
+	UL_COLORMODE_AUTO = 0,
+	UL_COLORMODE_NEVER,
+	UL_COLORMODE_ALWAYS,
+	UL_COLORMODE_UNDEF,
+
+	__UL_NCOLORMODES	/* last */
+};
+
+extern int colormode_from_string(const char *str);
+extern int colormode_or_err(const char *str, const char *errmsg);
+
+/* Initialize the global variable UL_COLOR_TERM_OK */
+extern int colors_init(int mode, const char *util_name);
+
+/* Returns 1 or 0 */
+extern int colors_wanted(void);
+
+/* temporary enable/disable colors */
+extern void colors_off(void);
+extern void colors_on(void);
+
+
+/* Set the color */
+extern void color_fenable(const char *seq, FILE *f);
+
+extern void color_scheme_fenable(const char *name, const char *dflt, FILE *f);
+extern const char *color_scheme_get_sequence(const char *name, const char *dflt);
+
+static inline void color_enable(const char *seq)
+{
+	color_fenable(seq, stdout);
+}
+
+static inline void color_scheme_enable(const char *name, const char *dflt)
+{
+	color_scheme_fenable(name, dflt, stdout);
+}
+
+/* Reset colors to default */
+extern void color_fdisable(FILE *f);
+
+static inline void color_disable(void)
+{
+	color_fdisable(stdout);
+}
+
+/* converts "red" to UL_COLOR_RED, etc. */
+extern const char *color_sequence_from_colorname(const char *str);
+
+
+#endif /* UTIL_LINUX_COLORS_H */
diff --git a/libblkid/src/config.c b/libblkid/src/config.c
new file mode 100644
index 0000000..3c7f312
--- /dev/null
+++ b/libblkid/src/config.c
@@ -0,0 +1,198 @@
+/*
+ * config.c - blkid.conf routines
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <stdint.h>
+#include <stdarg.h>
+
+#include "blkidP.h"
+#include "env.h"
+
+static int parse_evaluate(struct blkid_config *conf, char *s)
+{
+	while(s && *s) {
+		char *sep;
+
+		if (conf->nevals >= __BLKID_EVAL_LAST)
+			goto err;
+		sep = strchr(s, ',');
+		if (sep)
+			*sep = '\0';
+		if (strcmp(s, "udev") == 0)
+			conf->eval[conf->nevals] = BLKID_EVAL_UDEV;
+		else if (strcmp(s, "scan") == 0)
+			conf->eval[conf->nevals] = BLKID_EVAL_SCAN;
+		else
+			goto err;
+		conf->nevals++;
+		if (sep)
+			s = sep + 1;
+		else
+			break;
+	}
+	return 0;
+err:
+	DBG(CONFIG, ul_debug(
+		"config file: unknown evaluation method '%s'.", s));
+	return -1;
+}
+
+static int parse_next(FILE *fd, struct blkid_config *conf)
+{
+	char buf[BUFSIZ];
+	char *s;
+
+	/* read the next non-blank non-comment line */
+	do {
+		if (fgets (buf, sizeof(buf), fd) == NULL)
+			return feof(fd) ? 0 : -1;
+		s = strchr (buf, '\n');
+		if (!s) {
+			/* Missing final newline?  Otherwise extremely */
+			/* long line - assume file was corrupted */
+			if (feof(fd))
+				s = strchr (buf, '\0');
+			else {
+				DBG(CONFIG, ul_debug(
+					"config file: missing newline at line '%s'.",
+					buf));
+				return -1;
+			}
+		}
+		*s = '\0';
+		if (--s >= buf && *s == '\r')
+			*s = '\0';
+
+		s = buf;
+		while (*s == ' ' || *s == '\t')		/* skip space */
+			s++;
+
+	} while (*s == '\0' || *s == '#');
+
+	if (!strncmp(s, "SEND_UEVENT=", 12)) {
+		s += 13;
+		if (*s && !strcasecmp(s, "yes"))
+			conf->uevent = TRUE;
+		else if (*s)
+			conf->uevent = FALSE;
+	} else if (!strncmp(s, "CACHE_FILE=", 11)) {
+		s += 11;
+		if (*s)
+			conf->cachefile = strdup(s);
+	} else if (!strncmp(s, "EVALUATE=", 9)) {
+		s += 9;
+		if (*s && parse_evaluate(conf, s) == -1)
+			return -1;
+	} else {
+		DBG(CONFIG, ul_debug(
+			"config file: unknown option '%s'.", s));
+		return -1;
+	}
+	return 0;
+}
+
+/* return real config data or built-in default */
+struct blkid_config *blkid_read_config(const char *filename)
+{
+	struct blkid_config *conf;
+	FILE *f;
+
+	if (!filename)
+		filename = safe_getenv("BLKID_CONF");
+	if (!filename)
+		filename = BLKID_CONFIG_FILE;
+
+	conf = (struct blkid_config *) calloc(1, sizeof(*conf));
+	if (!conf)
+		return NULL;
+	conf->uevent = -1;
+
+	DBG(CONFIG, ul_debug("reading config file: %s.", filename));
+
+	f = fopen(filename, "r" UL_CLOEXECSTR);
+	if (!f) {
+		DBG(CONFIG, ul_debug("%s: does not exist, using built-in default", filename));
+		goto dflt;
+	}
+	while (!feof(f)) {
+		if (parse_next(f, conf)) {
+			DBG(CONFIG, ul_debug("%s: parse error", filename));
+			goto err;
+		}
+	}
+dflt:
+	if (!conf->nevals) {
+		conf->eval[0] = BLKID_EVAL_UDEV;
+		conf->eval[1] = BLKID_EVAL_SCAN;
+		conf->nevals = 2;
+	}
+	if (!conf->cachefile)
+		conf->cachefile = strdup(BLKID_CACHE_FILE);
+	if (conf->uevent == -1)
+		conf->uevent = TRUE;
+	if (f)
+		fclose(f);
+	return conf;
+err:
+	free(conf);
+	fclose(f);
+	return NULL;
+}
+
+void blkid_free_config(struct blkid_config *conf)
+{
+	if (!conf)
+		return;
+	free(conf->cachefile);
+	free(conf);
+}
+
+#ifdef TEST_PROGRAM
+/*
+ * usage: tst_config [<filename>]
+ */
+int main(int argc, char *argv[])
+{
+	int i;
+	struct blkid_config *conf;
+	char *filename = NULL;
+
+	blkid_init_debug(BLKID_DEBUG_ALL);
+
+	if (argc == 2)
+		filename = argv[1];
+
+	conf = blkid_read_config(filename);
+	if (!conf)
+		return EXIT_FAILURE;
+
+	printf("EVALUATE:    ");
+	for (i = 0; i < conf->nevals; i++)
+		printf("%s ", conf->eval[i] == BLKID_EVAL_UDEV ? "udev" : "scan");
+	printf("\n");
+
+	printf("SEND UEVENT: %s\n", conf->uevent ? "TRUE" : "FALSE");
+	printf("CACHE_FILE:  %s\n", conf->cachefile);
+
+	blkid_free_config(conf);
+	return EXIT_SUCCESS;
+}
+#endif
diff --git a/libblkid/src/crc32.h b/libblkid/src/crc32.h
new file mode 100644
index 0000000..2616910
--- /dev/null
+++ b/libblkid/src/crc32.h
@@ -0,0 +1,10 @@
+#ifndef UL_NG_CRC32_H
+#define UL_NG_CRC32_H
+
+#include <sys/types.h>
+#include <stdint.h>
+
+extern uint32_t crc32(uint32_t seed, const unsigned char *buf, size_t len);
+
+#endif
+
diff --git a/libblkid/src/debug.h b/libblkid/src/debug.h
new file mode 100644
index 0000000..0229ab3
--- /dev/null
+++ b/libblkid/src/debug.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef UTIL_LINUX_DEBUG_H
+#define UTIL_LINUX_DEBUG_H
+
+
+/*
+ * util-linux debug macros
+ *
+ * The debug stuff is based on <name>_debug_mask that controls what outputs is
+ * expected. The mask is usually initialized by <NAME>_DEBUG= env.variable
+ *
+ * After successful initialization the flag <PREFIX>_DEBUG_INIT is always set
+ * to the mask (this flag is required). The <PREFIX> is usually library API
+ * prefix (e.g. MNT_) or program name (e.g. CFDISK_)
+ *
+ * In the code is possible to use
+ *
+ *	DBG(FOO, ul_debug("this is output for foo"));
+ *
+ * where for the FOO has to be defined <PREFIX>_DEBUG_FOO.
+ *
+ * It's possible to initialize the mask by comma delimited strings with
+ * subsystem names (e.g. "LIBMOUNT_DEBUG=options,tab"). In this case is
+ * necessary to define mask names array. This functionality is optional.
+ *
+ * It's stringly recommended to use UL_* macros to define/declare/use
+ * the debug stuff.
+ *
+ * See disk-utils/cfdisk.c: cfdisk_init_debug()  for programs debug
+ *  or libmount/src/init.c: mnt_init_debug()     for library debug
+ *
+ */
+
+#include <stdarg.h>
+#include <string.h>
+
+struct ul_debug_maskname {
+	const char *name;
+	int mask;
+	const char *help;
+};
+#define UL_DEBUG_EMPTY_MASKNAMES {{ NULL, 0, NULL }}
+#define UL_DEBUG_DEFINE_MASKNAMES(m) static const struct ul_debug_maskname m ## _masknames[]
+#define UL_DEBUG_MASKNAMES(m)	m ## _masknames
+
+#define UL_DEBUG_DEFINE_MASK(m) int m ## _debug_mask
+#define UL_DEBUG_DECLARE_MASK(m) extern UL_DEBUG_DEFINE_MASK(m)
+
+/* p - flag prefix, m - flag postfix */
+#define UL_DEBUG_DEFINE_FLAG(p, m) p ## m
+
+/* l - library name, p - flag prefix, m - flag postfix, x - function */
+#define __UL_DBG(l, p, m, x) \
+	do { \
+		if ((p ## m) & l ## _debug_mask) { \
+			fprintf(stderr, "%d: %s: %8s: ", getpid(), # l, # m); \
+			x; \
+		} \
+	} while (0)
+
+#define __UL_DBG_CALL(l, p, m, x) \
+	do { \
+		if ((p ## m) & l ## _debug_mask) { \
+			x; \
+		} \
+	} while (0)
+
+#define __UL_DBG_FLUSH(l, p) \
+	do { \
+		if (l ## _debug_mask && \
+		    l ## _debug_mask != p ## INIT) { \
+			fflush(stderr); \
+		} \
+	} while (0)
+
+
+#define __UL_INIT_DEBUG(lib, pref, mask, env) \
+	do { \
+		if (lib ## _debug_mask & pref ## INIT) \
+		; \
+		else if (!mask) { \
+			char *str = getenv(# env); \
+			if (str) \
+				lib ## _debug_mask = ul_debug_parse_envmask(lib ## _masknames, str); \
+		} else \
+			lib ## _debug_mask = mask; \
+		lib ## _debug_mask |= pref ## INIT; \
+	} while (0)
+
+
+static inline void __attribute__ ((__format__ (__printf__, 1, 2)))
+ul_debug(const char *mesg, ...)
+{
+	va_list ap;
+	va_start(ap, mesg);
+	vfprintf(stderr, mesg, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+}
+
+static inline void __attribute__ ((__format__ (__printf__, 2, 3)))
+ul_debugobj(void *handler, const char *mesg, ...)
+{
+	va_list ap;
+
+	if (handler)
+		fprintf(stderr, "[%p]: ", handler);
+	va_start(ap, mesg);
+	vfprintf(stderr, mesg, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+}
+
+static inline int ul_debug_parse_envmask(
+			const struct ul_debug_maskname flagnames[],
+			const char *mask)
+{
+	int res;
+	char *ptr;
+
+	/* let's check for a numeric mask first */
+	res = strtoul(mask, &ptr, 0);
+
+	/* perhaps it's a comma-separated string? */
+	if (ptr && *ptr && flagnames && flagnames[0].name) {
+		char *msbuf, *ms, *name;
+		res = 0;
+
+		ms = msbuf = strdup(mask);
+		if (!ms)
+			return res;
+
+		while ((name = strtok_r(ms, ",", &ptr))) {
+			const struct ul_debug_maskname *d;
+			ms = ptr;
+
+			for (d = flagnames; d && d->name; d++) {
+				if (strcmp(name, d->name) == 0) {
+					res |= d->mask;
+					break;
+				}
+			}
+			/* nothing else we can do by OR-ing the mask */
+			if (res == 0xffff)
+				break;
+		}
+		free(msbuf);
+	} else if (ptr && strcmp(ptr, "all") == 0)
+		res = 0xffff;
+
+	return res;
+}
+
+static inline void ul_debug_print_masks(
+			const char *env,
+			const struct ul_debug_maskname flagnames[])
+{
+	const struct ul_debug_maskname *d;
+
+	if (!flagnames)
+		return;
+
+	fprintf(stderr, "Available \"%s=<name>[,...]|<mask>\" debug masks:\n",
+			env);
+	for (d = flagnames; d && d->name; d++) {
+		if (!d->help)
+			continue;
+		fprintf(stderr, "   %-8s [0x%04x] : %s\n",
+				d->name, d->mask, d->help);
+	}
+}
+
+#endif /* UTIL_LINUX_DEBUG_H */
diff --git a/libblkid/src/dev.c b/libblkid/src/dev.c
new file mode 100644
index 0000000..9491331
--- /dev/null
+++ b/libblkid/src/dev.c
@@ -0,0 +1,274 @@
+/*
+ * dev.c - allocation/initialization/free routines for dev
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "blkidP.h"
+
+/*
+ * NOTE: reference manual is not structured as code. The following section is a generic
+ * section for all high-level cache search+iterate routines.
+ */
+
+/**
+ * SECTION:search
+ * @title: Search and iterate
+ * @short_description: search devices and iterate over devices in the cache.
+ *
+ * Note that high-level probing API provides information about superblocks
+ * (filesystems/raids) only.  For partitions and topology is necessary to use
+ * the low-level API.
+ */
+
+blkid_dev blkid_new_dev(void)
+{
+	blkid_dev dev;
+
+	if (!(dev = (blkid_dev) calloc(1, sizeof(struct blkid_struct_dev))))
+		return NULL;
+
+	INIT_LIST_HEAD(&dev->bid_devs);
+	INIT_LIST_HEAD(&dev->bid_tags);
+
+	return dev;
+}
+
+void blkid_free_dev(blkid_dev dev)
+{
+	if (!dev)
+		return;
+
+	DBG(DEV,
+	    ul_debug("  freeing dev %s (%s)", dev->bid_name, dev->bid_type ?
+		   dev->bid_type : "(null)"));
+	DBG(DEV, blkid_debug_dump_dev(dev));
+
+	list_del(&dev->bid_devs);
+	while (!list_empty(&dev->bid_tags)) {
+		blkid_tag tag = list_entry(dev->bid_tags.next,
+					   struct blkid_struct_tag,
+					   bit_tags);
+		blkid_free_tag(tag);
+	}
+	free(dev->bid_name);
+	free(dev);
+}
+
+/*
+ * Given a blkid device, return its name
+ */
+const char *blkid_dev_devname(blkid_dev dev)
+{
+	return dev ? dev->bid_name : NULL;
+}
+
+void blkid_debug_dump_dev(blkid_dev dev)
+{
+	struct list_head *p;
+
+	if (!dev) {
+		printf("  dev: NULL\n");
+		return;
+	}
+
+	fprintf(stderr, "  dev: name = %s\n", dev->bid_name);
+	fprintf(stderr, "  dev: DEVNO=\"0x%0llx\"\n", (long long)dev->bid_devno);
+	fprintf(stderr, "  dev: TIME=\"%ld.%ld\"\n", (long)dev->bid_time, (long)dev->bid_utime);
+	fprintf(stderr, "  dev: PRI=\"%d\"\n", dev->bid_pri);
+	fprintf(stderr, "  dev: flags = 0x%08X\n", dev->bid_flags);
+
+	list_for_each(p, &dev->bid_tags) {
+		blkid_tag tag = list_entry(p, struct blkid_struct_tag, bit_tags);
+		if (tag)
+			fprintf(stderr, "    tag: %s=\"%s\"\n", tag->bit_name,
+			       tag->bit_val);
+		else
+			fprintf(stderr, "    tag: NULL\n");
+	}
+}
+
+/*
+ * dev iteration routines for the public libblkid interface.
+ *
+ * These routines do not expose the list.h implementation, which are a
+ * contamination of the namespace, and which force us to reveal far, far
+ * too much of our internal implemenation.  I'm not convinced I want
+ * to keep list.h in the long term, anyway.  It's fine for kernel
+ * programming, but performance is not the #1 priority for this
+ * library, and I really don't like the tradeoff of type-safety for
+ * performance for this application.  [tytso:20030125.2007EST]
+ */
+
+/*
+ * This series of functions iterate over all devices in a blkid cache
+ */
+#define DEV_ITERATE_MAGIC	0x01a5284c
+
+struct blkid_struct_dev_iterate {
+	int			magic;
+	blkid_cache		cache;
+	char			*search_type;
+	char			*search_value;
+	struct list_head	*p;
+};
+
+blkid_dev_iterate blkid_dev_iterate_begin(blkid_cache cache)
+{
+	blkid_dev_iterate iter;
+
+	if (!cache) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	iter = malloc(sizeof(struct blkid_struct_dev_iterate));
+	if (iter) {
+		iter->magic = DEV_ITERATE_MAGIC;
+		iter->cache = cache;
+		iter->p	= cache->bic_devs.next;
+		iter->search_type = 0;
+		iter->search_value = 0;
+	}
+	return iter;
+}
+
+int blkid_dev_set_search(blkid_dev_iterate iter,
+				 char *search_type, char *search_value)
+{
+	char *new_type, *new_value;
+
+	if (!iter || iter->magic != DEV_ITERATE_MAGIC || !search_type ||
+	    !search_value)
+		return -1;
+	new_type = malloc(strlen(search_type)+1);
+	new_value = malloc(strlen(search_value)+1);
+	if (!new_type || !new_value) {
+		free(new_type);
+		free(new_value);
+		return -1;
+	}
+	strcpy(new_type, search_type);
+	strcpy(new_value, search_value);
+	free(iter->search_type);
+	free(iter->search_value);
+	iter->search_type = new_type;
+	iter->search_value = new_value;
+	return 0;
+}
+
+/*
+ * Return 0 on success, -1 on error
+ */
+int blkid_dev_next(blkid_dev_iterate iter,
+			  blkid_dev *ret_dev)
+{
+	blkid_dev		dev;
+
+	if  (!ret_dev || !iter || iter->magic != DEV_ITERATE_MAGIC)
+		return -1;
+	*ret_dev = 0;
+	while (iter->p != &iter->cache->bic_devs) {
+		dev = list_entry(iter->p, struct blkid_struct_dev, bid_devs);
+		iter->p = iter->p->next;
+		if (iter->search_type &&
+		    !blkid_dev_has_tag(dev, iter->search_type,
+				       iter->search_value))
+			continue;
+		*ret_dev = dev;
+		return 0;
+	}
+	return -1;
+}
+
+void blkid_dev_iterate_end(blkid_dev_iterate iter)
+{
+	if (!iter || iter->magic != DEV_ITERATE_MAGIC)
+		return;
+	iter->magic = 0;
+	free(iter->search_type);
+	free(iter->search_value);
+	free(iter);
+}
+
+#ifdef TEST_PROGRAM
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+
+void __attribute__((__noreturn__)) usage(char *prog)
+{
+	fprintf(stderr, "Usage: %s [-f blkid_file] [-m debug_mask]\n", prog);
+	fprintf(stderr, "\tList all devices and exit\n");
+	exit(1);
+}
+
+int main(int argc, char **argv)
+{
+	blkid_dev_iterate	iter;
+	blkid_cache 		cache = NULL;
+	blkid_dev		dev;
+	int			c, ret;
+	char			*tmp;
+	char			*file = NULL;
+	char			*search_type = NULL;
+	char			*search_value = NULL;
+
+	while ((c = getopt (argc, argv, "m:f:")) != EOF)
+		switch (c) {
+		case 'f':
+			file = optarg;
+			break;
+		case 'm':
+		{
+			int mask = strtoul (optarg, &tmp, 0);
+			if (*tmp) {
+				fprintf(stderr, "Invalid debug mask: %s\n",
+					optarg);
+				exit(1);
+			}
+			blkid_init_debug(mask);
+			break;
+		}
+		case '?':
+			usage(argv[0]);
+		}
+	if (argc >= optind+2) {
+		search_type = argv[optind];
+		search_value = argv[optind+1];
+		optind += 2;
+	}
+	if (argc != optind)
+		usage(argv[0]);
+
+	if ((ret = blkid_get_cache(&cache, file)) != 0) {
+		fprintf(stderr, "%s: error creating cache (%d)\n",
+			argv[0], ret);
+		exit(1);
+	}
+
+	iter = blkid_dev_iterate_begin(cache);
+	if (search_type)
+		blkid_dev_set_search(iter, search_type, search_value);
+	while (blkid_dev_next(iter, &dev) == 0) {
+		printf("Device: %s\n", blkid_dev_devname(dev));
+	}
+	blkid_dev_iterate_end(iter);
+
+
+	blkid_put_cache(cache);
+	return (0);
+}
+#endif
diff --git a/libblkid/src/devname.c b/libblkid/src/devname.c
new file mode 100644
index 0000000..d98af04
--- /dev/null
+++ b/libblkid/src/devname.c
@@ -0,0 +1,676 @@
+/*
+ * devname.c - get a dev by its device inode name
+ *
+ * Copyright (C) Andries Brouwer
+ * Copyright (C) 1999, 2000, 2001, 2002, 2003 Theodore Ts'o
+ * Copyright (C) 2001 Andreas Dilger
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#define _GNU_SOURCE 1
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <ctype.h>
+#include <fcntl.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#include <dirent.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <time.h>
+#include <sys/sysmacros.h>
+
+#include "blkidP.h"
+
+#include "canonicalize.h"		/* $(top_srcdir)/include */
+#include "pathnames.h"
+#include "sysfs.h"
+#include "at.h"
+
+/*
+ * Find a dev struct in the cache by device name, if available.
+ *
+ * If there is no entry with the specified device name, and the create
+ * flag is set, then create an empty device entry.
+ */
+blkid_dev blkid_get_dev(blkid_cache cache, const char *devname, int flags)
+{
+	blkid_dev dev = NULL, tmp;
+	struct list_head *p, *pnext;
+
+	if (!cache || !devname)
+		return NULL;
+
+	list_for_each(p, &cache->bic_devs) {
+		tmp = list_entry(p, struct blkid_struct_dev, bid_devs);
+		if (strcmp(tmp->bid_name, devname))
+			continue;
+
+		DBG(DEVNAME, ul_debug("found devname %s in cache", tmp->bid_name));
+		dev = tmp;
+		break;
+	}
+
+	if (!dev && (flags & BLKID_DEV_CREATE)) {
+		if (access(devname, F_OK) < 0)
+			return NULL;
+		dev = blkid_new_dev();
+		if (!dev)
+			return NULL;
+		dev->bid_time = INT_MIN;
+		dev->bid_name = strdup(devname);
+		dev->bid_cache = cache;
+		list_add_tail(&dev->bid_devs, &cache->bic_devs);
+		cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+	}
+
+	if (flags & BLKID_DEV_VERIFY) {
+		dev = blkid_verify(cache, dev);
+		if (!dev || !(dev->bid_flags & BLKID_BID_FL_VERIFIED))
+			return dev;
+		/*
+		 * If the device is verified, then search the blkid
+		 * cache for any entries that match on the type, uuid,
+		 * and label, and verify them; if a cache entry can
+		 * not be verified, then it's stale and so we remove
+		 * it.
+		 */
+		list_for_each_safe(p, pnext, &cache->bic_devs) {
+			blkid_dev dev2 = list_entry(p, struct blkid_struct_dev, bid_devs);
+			if (dev2->bid_flags & BLKID_BID_FL_VERIFIED)
+				continue;
+			if (!dev->bid_type || !dev2->bid_type ||
+			    strcmp(dev->bid_type, dev2->bid_type))
+				continue;
+			if (dev->bid_label && dev2->bid_label &&
+			    strcmp(dev->bid_label, dev2->bid_label))
+				continue;
+			if (dev->bid_uuid && dev2->bid_uuid &&
+			    strcmp(dev->bid_uuid, dev2->bid_uuid))
+				continue;
+			if ((dev->bid_label && !dev2->bid_label) ||
+			    (!dev->bid_label && dev2->bid_label) ||
+			    (dev->bid_uuid && !dev2->bid_uuid) ||
+			    (!dev->bid_uuid && dev2->bid_uuid))
+				continue;
+			dev2 = blkid_verify(cache, dev2);
+			if (dev2 && !(dev2->bid_flags & BLKID_BID_FL_VERIFIED))
+				blkid_free_dev(dev2);
+		}
+	}
+	return dev;
+}
+
+/* Directories where we will try to search for device names */
+static const char *dirlist[] = { "/dev", "/devfs", "/devices", NULL };
+
+static int is_dm_leaf(const char *devname)
+{
+	struct dirent	*de, *d_de;
+	DIR		*dir, *d_dir;
+	char		path[256];
+	int		ret = 1;
+
+	if ((dir = opendir("/sys/block")) == NULL)
+		return 0;
+	while ((de = readdir(dir)) != NULL) {
+		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..") ||
+		    !strcmp(de->d_name, devname) ||
+		    strncmp(de->d_name, "dm-", 3) ||
+		    strlen(de->d_name) > sizeof(path)-32)
+			continue;
+		sprintf(path, "/sys/block/%s/slaves", de->d_name);
+		if ((d_dir = opendir(path)) == NULL)
+			continue;
+		while ((d_de = readdir(d_dir)) != NULL) {
+			if (!strcmp(d_de->d_name, devname)) {
+				ret = 0;
+				break;
+			}
+		}
+		closedir(d_dir);
+		if (!ret)
+			break;
+	}
+	closedir(dir);
+	return ret;
+}
+
+/*
+ * Probe a single block device to add to the device cache.
+ */
+static void probe_one(blkid_cache cache, const char *ptname,
+		      dev_t devno, int pri, int only_if_new, int removable)
+{
+	blkid_dev dev = NULL;
+	struct list_head *p, *pnext;
+	const char **dir;
+	char *devname = NULL;
+
+	/* See if we already have this device number in the cache. */
+	list_for_each_safe(p, pnext, &cache->bic_devs) {
+		blkid_dev tmp = list_entry(p, struct blkid_struct_dev,
+					   bid_devs);
+		if (tmp->bid_devno == devno) {
+			if (only_if_new && !access(tmp->bid_name, F_OK))
+				return;
+			dev = blkid_verify(cache, tmp);
+			if (dev && (dev->bid_flags & BLKID_BID_FL_VERIFIED))
+				break;
+			dev = 0;
+		}
+	}
+	if (dev && dev->bid_devno == devno)
+		goto set_pri;
+
+	/* Try to translate private device-mapper dm-<N> names
+	 * to standard /dev/mapper/<name>.
+	 */
+	if (!strncmp(ptname, "dm-", 3) && isdigit(ptname[3])) {
+		devname = canonicalize_dm_name(ptname);
+		if (!devname)
+			blkid__scan_dir("/dev/mapper", devno, 0, &devname);
+		if (devname)
+			goto get_dev;
+	}
+
+	/*
+	 * Take a quick look at /dev/ptname for the device number.  We check
+	 * all of the likely device directories.  If we don't find it, or if
+	 * the stat information doesn't check out, use blkid_devno_to_devname()
+	 * to find it via an exhaustive search for the device major/minor.
+	 */
+	for (dir = dirlist; *dir; dir++) {
+		struct stat st;
+		char device[256];
+
+		snprintf(device, sizeof(device), "%s/%s", *dir, ptname);
+		if ((dev = blkid_get_dev(cache, device, BLKID_DEV_FIND)) &&
+		    dev->bid_devno == devno)
+			goto set_pri;
+
+		if (stat(device, &st) == 0 &&
+		    (S_ISBLK(st.st_mode) ||
+		     (S_ISCHR(st.st_mode) && !strncmp(ptname, "ubi", 3))) &&
+		    st.st_rdev == devno) {
+			devname = strdup(device);
+			goto get_dev;
+		}
+	}
+	/* Do a short-cut scan of /dev/mapper first */
+	if (!devname)
+		blkid__scan_dir("/dev/mapper", devno, 0, &devname);
+	if (!devname) {
+		devname = blkid_devno_to_devname(devno);
+		if (!devname)
+			return;
+	}
+
+get_dev:
+	dev = blkid_get_dev(cache, devname, BLKID_DEV_NORMAL);
+	free(devname);
+
+set_pri:
+	if (dev) {
+		if (pri)
+			dev->bid_pri = pri;
+		else if (!strncmp(dev->bid_name, "/dev/mapper/", 11)) {
+			dev->bid_pri = BLKID_PRI_DM;
+			if (is_dm_leaf(ptname))
+				dev->bid_pri += 5;
+		} else if (!strncmp(ptname, "md", 2))
+			dev->bid_pri = BLKID_PRI_MD;
+		if (removable)
+			dev->bid_flags |= BLKID_BID_FL_REMOVABLE;
+	}
+	return;
+}
+
+#define PROC_PARTITIONS "/proc/partitions"
+#define VG_DIR		"/proc/lvm/VGs"
+
+/*
+ * This function initializes the UUID cache with devices from the LVM
+ * proc hierarchy.  We currently depend on the names of the LVM
+ * hierarchy giving us the device structure in /dev.  (XXX is this a
+ * safe thing to do?)
+ */
+#ifdef VG_DIR
+static dev_t lvm_get_devno(const char *lvm_device)
+{
+	FILE *lvf;
+	char buf[1024];
+	int ma, mi;
+	dev_t ret = 0;
+
+	DBG(DEVNAME, ul_debug("opening %s", lvm_device));
+	if ((lvf = fopen(lvm_device, "r" UL_CLOEXECSTR)) == NULL) {
+		DBG(DEVNAME, ul_debug("%s: (%d) %m", lvm_device, errno));
+		return 0;
+	}
+
+	while (fgets(buf, sizeof(buf), lvf)) {
+		if (sscanf(buf, "device: %d:%d", &ma, &mi) == 2) {
+			ret = makedev(ma, mi);
+			break;
+		}
+	}
+	fclose(lvf);
+
+	return ret;
+}
+
+static void lvm_probe_all(blkid_cache cache, int only_if_new)
+{
+	DIR		*vg_list;
+	struct dirent	*vg_iter;
+	int		vg_len = strlen(VG_DIR);
+	dev_t		dev;
+
+	if ((vg_list = opendir(VG_DIR)) == NULL)
+		return;
+
+	DBG(DEVNAME, ul_debug("probing LVM devices under %s", VG_DIR));
+
+	while ((vg_iter = readdir(vg_list)) != NULL) {
+		DIR		*lv_list;
+		char		*vdirname;
+		char		*vg_name;
+		struct dirent	*lv_iter;
+
+		vg_name = vg_iter->d_name;
+		if (!strcmp(vg_name, ".") || !strcmp(vg_name, ".."))
+			continue;
+		vdirname = malloc(vg_len + strlen(vg_name) + 8);
+		if (!vdirname)
+			goto exit;
+		sprintf(vdirname, "%s/%s/LVs", VG_DIR, vg_name);
+
+		lv_list = opendir(vdirname);
+		free(vdirname);
+		if (lv_list == NULL)
+			continue;
+
+		while ((lv_iter = readdir(lv_list)) != NULL) {
+			char		*lv_name, *lvm_device;
+
+			lv_name = lv_iter->d_name;
+			if (!strcmp(lv_name, ".") || !strcmp(lv_name, ".."))
+				continue;
+
+			lvm_device = malloc(vg_len + strlen(vg_name) +
+					    strlen(lv_name) + 8);
+			if (!lvm_device) {
+				closedir(lv_list);
+				goto exit;
+			}
+			sprintf(lvm_device, "%s/%s/LVs/%s", VG_DIR, vg_name,
+				lv_name);
+			dev = lvm_get_devno(lvm_device);
+			sprintf(lvm_device, "%s/%s", vg_name, lv_name);
+			DBG(DEVNAME, ul_debug("LVM dev %s: devno 0x%04X",
+						  lvm_device,
+						  (unsigned int) dev));
+			probe_one(cache, lvm_device, dev, BLKID_PRI_LVM,
+				  only_if_new, 0);
+			free(lvm_device);
+		}
+		closedir(lv_list);
+	}
+exit:
+	closedir(vg_list);
+}
+#endif
+
+#define PROC_EVMS_VOLUMES "/proc/evms/volumes"
+
+static int
+evms_probe_all(blkid_cache cache, int only_if_new)
+{
+	char line[100];
+	int ma, mi, sz, num = 0;
+	FILE *procpt;
+	char device[110];
+
+	procpt = fopen(PROC_EVMS_VOLUMES, "r" UL_CLOEXECSTR);
+	if (!procpt)
+		return 0;
+	while (fgets(line, sizeof(line), procpt)) {
+		if (sscanf (line, " %d %d %d %*s %*s %[^\n ]",
+			    &ma, &mi, &sz, device) != 4)
+			continue;
+
+		DBG(DEVNAME, ul_debug("Checking partition %s (%d, %d)",
+					  device, ma, mi));
+
+		probe_one(cache, device, makedev(ma, mi), BLKID_PRI_EVMS,
+			  only_if_new, 0);
+		num++;
+	}
+	fclose(procpt);
+	return num;
+}
+
+static void
+ubi_probe_all(blkid_cache cache, int only_if_new)
+{
+	const char **dirname;
+
+	for (dirname = dirlist; *dirname; dirname++) {
+		DIR		*dir;
+		struct dirent	*iter;
+
+		DBG(DEVNAME, ul_debug("probing UBI volumes under %s",
+					  *dirname));
+
+		dir = opendir(*dirname);
+		if (dir == NULL)
+			continue ;
+
+		while ((iter = readdir(dir)) != NULL) {
+			char		*name;
+			struct stat	st;
+			dev_t		dev;
+
+			name = iter->d_name;
+#ifdef _DIRENT_HAVE_D_TYPE
+			if (iter->d_type != DT_UNKNOWN &&
+			    iter->d_type != DT_CHR && iter->d_type != DT_LNK)
+				continue;
+#endif
+			if (!strcmp(name, ".") || !strcmp(name, "..") ||
+			    !strstr(name, "ubi"))
+				continue;
+			if (!strcmp(name, "ubi_ctrl"))
+				continue;
+			if (fstat_at(dirfd(dir), *dirname, name, &st, 0))
+				continue;
+
+			dev = st.st_rdev;
+
+			if (!S_ISCHR(st.st_mode) || !minor(dev))
+				continue;
+			DBG(DEVNAME, ul_debug("UBI vol %s/%s: devno 0x%04X",
+				  *dirname, name, (int) dev));
+			probe_one(cache, name, dev, BLKID_PRI_UBI, only_if_new, 0);
+		}
+		closedir(dir);
+	}
+}
+
+/*
+ * Read the device data for all available block devices in the system.
+ */
+static int probe_all(blkid_cache cache, int only_if_new)
+{
+	FILE *proc;
+	char line[1024];
+	char ptname0[128 + 1], ptname1[128 + 1], *ptname = 0;
+	char *ptnames[2];
+	dev_t devs[2];
+	int ma, mi;
+	unsigned long long sz;
+	int lens[2] = { 0, 0 };
+	int which = 0, last = 0;
+	struct list_head *p, *pnext;
+
+	ptnames[0] = ptname0;
+	ptnames[1] = ptname1;
+
+	if (!cache)
+		return -BLKID_ERR_PARAM;
+
+	if (cache->bic_flags & BLKID_BIC_FL_PROBED &&
+	    time(0) - cache->bic_time < BLKID_PROBE_INTERVAL)
+		return 0;
+
+	blkid_read_cache(cache);
+	evms_probe_all(cache, only_if_new);
+#ifdef VG_DIR
+	lvm_probe_all(cache, only_if_new);
+#endif
+	ubi_probe_all(cache, only_if_new);
+
+	proc = fopen(PROC_PARTITIONS, "r" UL_CLOEXECSTR);
+	if (!proc)
+		return -BLKID_ERR_PROC;
+
+	while (fgets(line, sizeof(line), proc)) {
+		last = which;
+		which ^= 1;
+		ptname = ptnames[which];
+
+		if (sscanf(line, " %d %d %llu %128[^\n ]",
+			   &ma, &mi, &sz, ptname) != 4)
+			continue;
+		devs[which] = makedev(ma, mi);
+
+		DBG(DEVNAME, ul_debug("read partition name %s", ptname));
+
+		/* Skip whole disk devs unless they have no partitions.
+		 * If base name of device has changed, also
+		 * check previous dev to see if it didn't have a partn.
+		 * heuristic: partition name ends in a digit, & partition
+		 * names contain whole device name as substring.
+		 *
+		 * Skip extended partitions.
+		 * heuristic: size is 1
+		 *
+		 * FIXME: skip /dev/{ida,cciss,rd} whole-disk devs
+		 */
+
+		lens[which] = strlen(ptname);
+
+		/* ends in a digit, clearly a partition, so check */
+		if (isdigit(ptname[lens[which] - 1])) {
+			DBG(DEVNAME, ul_debug("partition dev %s, devno 0x%04X",
+				   ptname, (unsigned int) devs[which]));
+
+			if (sz > 1)
+				probe_one(cache, ptname, devs[which], 0,
+					  only_if_new, 0);
+			lens[which] = 0;	/* mark as checked */
+		}
+
+		/*
+		 * If last was a whole disk and we just found a partition
+		 * on it, remove the whole-disk dev from the cache if
+		 * it exists.
+		 */
+		if (lens[last] && !strncmp(ptnames[last], ptname, lens[last])) {
+			list_for_each_safe(p, pnext, &cache->bic_devs) {
+				blkid_dev tmp;
+
+				/* find blkid dev for the whole-disk devno */
+				tmp = list_entry(p, struct blkid_struct_dev,
+						 bid_devs);
+				if (tmp->bid_devno == devs[last]) {
+					DBG(DEVNAME, ul_debug("freeing %s",
+						       tmp->bid_name));
+					blkid_free_dev(tmp);
+					cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+					break;
+				}
+			}
+			lens[last] = 0;
+		}
+		/*
+		 * If last was not checked because it looked like a whole-disk
+		 * dev, and the device's base name has changed,
+		 * check last as well.
+		 */
+		if (lens[last] && strncmp(ptnames[last], ptname, lens[last])) {
+			DBG(DEVNAME, ul_debug("whole dev %s, devno 0x%04X",
+				   ptnames[last], (unsigned int) devs[last]));
+			probe_one(cache, ptnames[last], devs[last], 0,
+				  only_if_new, 0);
+			lens[last] = 0;
+		}
+	}
+
+	/* Handle the last device if it wasn't partitioned */
+	if (lens[which])
+		probe_one(cache, ptname, devs[which], 0, only_if_new, 0);
+
+	fclose(proc);
+	blkid_flush_cache(cache);
+	return 0;
+}
+
+/* Don't use it by default -- it's pretty slow (because cdroms, floppy, ...)
+ */
+static int probe_all_removable(blkid_cache cache)
+{
+	DIR *dir;
+	struct dirent *d;
+
+	if (!cache)
+		return -BLKID_ERR_PARAM;
+
+	dir = opendir(_PATH_SYS_BLOCK);
+	if (!dir)
+		return -BLKID_ERR_PROC;
+
+	while((d = readdir(dir))) {
+		struct sysfs_cxt sysfs = UL_SYSFSCXT_EMPTY;
+		int removable = 0;
+		dev_t devno;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+		if (d->d_type != DT_UNKNOWN && d->d_type != DT_LNK)
+			continue;
+#endif
+		if (d->d_name[0] == '.' &&
+		    ((d->d_name[1] == 0) ||
+		     ((d->d_name[1] == '.') && (d->d_name[2] == 0))))
+			continue;
+
+		devno = sysfs_devname_to_devno(d->d_name, NULL);
+		if (!devno)
+			continue;
+
+		if (sysfs_init(&sysfs, devno, NULL) == 0) {
+			if (sysfs_read_int(&sysfs, "removable", &removable) != 0)
+				removable = 0;
+			sysfs_deinit(&sysfs);
+		}
+
+		if (removable)
+			probe_one(cache, d->d_name, devno, 0, 0, 1);
+	}
+
+	closedir(dir);
+	return 0;
+}
+
+
+/**
+ * blkid_probe_all:
+ * @cache: cache handler
+ *
+ * Probes all block devices.
+ *
+ * Returns: 0 on success, or number less than zero in case of error.
+ */
+int blkid_probe_all(blkid_cache cache)
+{
+	int ret;
+
+	DBG(PROBE, ul_debug("Begin blkid_probe_all()"));
+	ret = probe_all(cache, 0);
+	if (ret == 0) {
+		cache->bic_time = time(0);
+		cache->bic_flags |= BLKID_BIC_FL_PROBED;
+	}
+	DBG(PROBE, ul_debug("End blkid_probe_all() [rc=%d]", ret));
+	return ret;
+}
+
+/**
+ * blkid_probe_all_new:
+ * @cache: cache handler
+ *
+ * Probes all new block devices.
+ *
+ * Returns: 0 on success, or number less than zero in case of error.
+ */
+int blkid_probe_all_new(blkid_cache cache)
+{
+	int ret;
+
+	DBG(PROBE, ul_debug("Begin blkid_probe_all_new()"));
+	ret = probe_all(cache, 1);
+	DBG(PROBE, ul_debug("End blkid_probe_all_new() [rc=%d]", ret));
+	return ret;
+}
+
+/**
+ * blkid_probe_all_removable:
+ * @cache: cache handler
+ *
+ * The libblkid probing is based on devices from /proc/partitions by default.
+ * This file usually does not contain removable devices (e.g. CDROMs) and this kind
+ * of devices are invisible for libblkid.
+ *
+ * This function adds removable block devices to @cache (probing is based on
+ * information from the /sys directory). Don't forget that removable devices
+ * (floppies, CDROMs, ...) could be pretty slow. It's very bad idea to call
+ * this function by default.
+ *
+ * Note that devices which were detected by this function won't be written to
+ * blkid.tab cache file.
+ *
+ * Returns: 0 on success, or number less than zero in case of error.
+ */
+int blkid_probe_all_removable(blkid_cache cache)
+{
+	int ret;
+
+	DBG(PROBE, ul_debug("Begin blkid_probe_all_removable()"));
+	ret = probe_all_removable(cache);
+	DBG(PROBE, ul_debug("End blkid_probe_all_removable() [rc=%d]", ret));
+	return ret;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+	blkid_cache cache = NULL;
+	int ret;
+
+	blkid_init_debug(BLKID_DEBUG_ALL);
+	if (argc != 1) {
+		fprintf(stderr, "Usage: %s\n"
+			"Probe all devices and exit\n", argv[0]);
+		exit(1);
+	}
+	if ((ret = blkid_get_cache(&cache, "/dev/null")) != 0) {
+		fprintf(stderr, "%s: error creating cache (%d)\n",
+			argv[0], ret);
+		exit(1);
+	}
+	if (blkid_probe_all(cache) < 0)
+		printf("%s: error probing devices\n", argv[0]);
+
+	if (blkid_probe_all_removable(cache) < 0)
+		printf("%s: error probing removable devices\n", argv[0]);
+
+	blkid_put_cache(cache);
+	return (0);
+}
+#endif
diff --git a/libblkid/src/devno.c b/libblkid/src/devno.c
new file mode 100644
index 0000000..f4a36e4
--- /dev/null
+++ b/libblkid/src/devno.c
@@ -0,0 +1,375 @@
+/*
+ * devno.c - find a particular device by its device number (major/minor)
+ *
+ * Copyright (C) 2000, 2001, 2003 Theodore Ts'o
+ * Copyright (C) 2001 Andreas Dilger
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include <dirent.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_SYS_MKDEV_H
+#include <sys/mkdev.h>
+#endif
+#include <fcntl.h>
+#include <inttypes.h>
+
+#include "blkidP.h"
+#include "pathnames.h"
+#include "at.h"
+#include "sysfs.h"
+
+static char *blkid_strconcat(const char *a, const char *b, const char *c)
+{
+	char *res, *p;
+	size_t len, al, bl, cl;
+
+	al = a ? strlen(a) : 0;
+	bl = b ? strlen(b) : 0;
+	cl = c ? strlen(c) : 0;
+
+	len = al + bl + cl;
+	if (!len)
+		return NULL;
+	p = res = malloc(len + 1);
+	if (!res)
+		return NULL;
+	if (al) {
+		memcpy(p, a, al);
+		p += al;
+	}
+	if (bl) {
+		memcpy(p, b, bl);
+		p += bl;
+	}
+	if (cl) {
+		memcpy(p, c, cl);
+		p += cl;
+	}
+	*p = '\0';
+	return res;
+}
+
+/*
+ * This function adds an entry to the directory list
+ */
+static void add_to_dirlist(const char *dir, const char *subdir,
+				struct dir_list **list)
+{
+	struct dir_list *dp;
+
+	dp = malloc(sizeof(struct dir_list));
+	if (!dp)
+		return;
+	dp->name = subdir ? blkid_strconcat(dir, "/", subdir) :
+		   dir ? strdup(dir) : NULL;
+
+	if (!dp->name) {
+		free(dp);
+		return;
+	}
+	dp->next = *list;
+	*list = dp;
+}
+
+/*
+ * This function frees a directory list
+ */
+static void free_dirlist(struct dir_list **list)
+{
+	struct dir_list *dp, *next;
+
+	for (dp = *list; dp; dp = next) {
+		next = dp->next;
+		free(dp->name);
+		free(dp);
+	}
+	*list = NULL;
+}
+
+void blkid__scan_dir(char *dirname, dev_t devno, struct dir_list **list,
+		     char **devname)
+{
+	DIR	*dir;
+	struct dirent *dp;
+	struct stat st;
+
+	if ((dir = opendir(dirname)) == NULL)
+		return;
+
+	while ((dp = readdir(dir)) != NULL) {
+#ifdef _DIRENT_HAVE_D_TYPE
+		if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_BLK &&
+		    dp->d_type != DT_LNK && dp->d_type != DT_DIR)
+			continue;
+#endif
+		if (dp->d_name[0] == '.' &&
+		    ((dp->d_name[1] == 0) ||
+		     ((dp->d_name[1] == '.') && (dp->d_name[2] == 0))))
+			continue;
+
+		if (fstat_at(dirfd(dir), dirname, dp->d_name, &st, 0))
+			continue;
+
+		if (S_ISBLK(st.st_mode) && st.st_rdev == devno) {
+			*devname = blkid_strconcat(dirname, "/", dp->d_name);
+			DBG(DEVNO, ul_debug("found 0x%llx at %s", (long long)devno,
+				   *devname));
+			break;
+		}
+
+		if (!list || !S_ISDIR(st.st_mode))
+			continue;
+
+		/* add subdirectory (but not symlink) to the list */
+#ifdef _DIRENT_HAVE_D_TYPE
+		if (dp->d_type == DT_LNK)
+			continue;
+		if (dp->d_type == DT_UNKNOWN)
+#endif
+		{
+			if (fstat_at(dirfd(dir), dirname, dp->d_name, &st, 1) ||
+			    !S_ISDIR(st.st_mode))
+				continue;	/* symlink or lstat() failed */
+		}
+
+		if (*dp->d_name == '.' || (
+#ifdef _DIRENT_HAVE_D_TYPE
+		    dp->d_type == DT_DIR &&
+#endif
+		    strcmp(dp->d_name, "shm") == 0))
+			/* ignore /dev/.{udev,mount,mdadm} and /dev/shm */
+			continue;
+
+		add_to_dirlist(dirname, dp->d_name, list);
+	}
+	closedir(dir);
+	return;
+}
+
+/* Directories where we will try to search for device numbers */
+static const char *devdirs[] = { "/devices", "/devfs", "/dev", NULL };
+
+/**
+ * SECTION: misc
+ * @title: Miscellaneous utils
+ * @short_description: mix of various utils for low-level and high-level API
+ */
+
+
+
+static char *scandev_devno_to_devpath(dev_t devno)
+{
+	struct dir_list *list = NULL, *new_list = NULL;
+	char *devname = NULL;
+	const char **dir;
+
+	/*
+	 * Add the starting directories to search in reverse order of
+	 * importance, since we are using a stack...
+	 */
+	for (dir = devdirs; *dir; dir++)
+		add_to_dirlist(*dir, NULL, &list);
+
+	while (list) {
+		struct dir_list *current = list;
+
+		list = list->next;
+		DBG(DEVNO, ul_debug("directory %s", current->name));
+		blkid__scan_dir(current->name, devno, &new_list, &devname);
+		free(current->name);
+		free(current);
+		if (devname)
+			break;
+		/*
+		 * If we're done checking at this level, descend to
+		 * the next level of subdirectories. (breadth-first)
+		 */
+		if (list == NULL) {
+			list = new_list;
+			new_list = NULL;
+		}
+	}
+	free_dirlist(&list);
+	free_dirlist(&new_list);
+
+	return devname;
+}
+
+/**
+ * blkid_devno_to_devname:
+ * @devno: device number
+ *
+ * This function finds the pathname to a block device with a given
+ * device number.
+ *
+ * Returns: a pointer to allocated memory to the pathname on success,
+ * and NULL on failure.
+ */
+char *blkid_devno_to_devname(dev_t devno)
+{
+	char *path = NULL;
+	char buf[PATH_MAX];
+
+	path = sysfs_devno_to_devpath(devno, buf, sizeof(buf));
+	if (path)
+		path = strdup(path);
+	if (!path)
+		path = scandev_devno_to_devpath(devno);
+
+	if (!path) {
+		DBG(DEVNO, ul_debug("blkid: couldn't find devno 0x%04lx",
+			   (unsigned long) devno));
+	} else {
+		DBG(DEVNO, ul_debug("found devno 0x%04llx as %s", (long long)devno, path));
+	}
+
+	return path;
+}
+
+
+/**
+ * blkid_devno_to_wholedisk:
+ * @dev: device number
+ * @diskname: buffer to return diskname (or NULL)
+ * @len: diskname buffer size (or 0)
+ * @diskdevno: pointer to returns devno of entire disk (or NULL)
+ *
+ * This function uses sysfs to convert the @devno device number to the *name*
+ * of the whole disk. The function DOES NOT return full device name. The @dev
+ * argument could be partition or whole disk -- both is converted.
+ *
+ * For example: sda1, 0x0801 --> sda, 0x0800
+ *
+ * For conversion to the full disk *path* use blkid_devno_to_devname(), for
+ * example:
+ *
+ * <informalexample>
+ *  <programlisting>
+ *
+ *	dev_t dev = 0x0801, disk;		// sda1 = 8:1
+ *	char *diskpath, diskname[32];
+ *
+ *	blkid_devno_to_wholedisk(dev, diskname, sizeof(diskname), &disk);
+ *	diskpath = blkid_devno_to_devname(disk);
+ *
+ *	// print "0x0801: sda, /dev/sda, 8:0
+ *	printf("0x%x: %s, %s, %d:%d\n",
+ *		dev, diskname, diskpath, major(disk), minor(disk));
+ *
+ *	free(diskpath);
+ *
+ *  </programlisting>
+ * </informalexample>
+ *
+ * Returns: 0 on success or -1 in case of error.
+ */
+int blkid_devno_to_wholedisk(dev_t dev, char *diskname,
+			size_t len, dev_t *diskdevno)
+{
+	return sysfs_devno_to_wholedisk( dev, diskname, len, diskdevno);
+}
+
+/*
+ * Returns 1 if the @major number is associated with @drvname.
+ */
+int blkid_driver_has_major(const char *drvname, int major)
+{
+	FILE *f;
+	char buf[128];
+	int match = 0;
+
+	f = fopen(_PATH_PROC_DEVICES, "r" UL_CLOEXECSTR);
+	if (!f)
+		return 0;
+
+	while (fgets(buf, sizeof(buf), f)) {	/* skip to block dev section */
+		if (strncmp("Block devices:\n", buf, sizeof(buf)) == 0)
+			break;
+	}
+
+	while (fgets(buf, sizeof(buf), f)) {
+		int maj;
+		char name[64 + 1];
+
+		if (sscanf(buf, "%d %64[^\n ]", &maj, name) != 2)
+			continue;
+
+		if (maj == major && strcmp(name, drvname) == 0) {
+			match = 1;
+			break;
+		}
+	}
+
+	fclose(f);
+
+	DBG(DEVNO, ul_debug("major %d %s associated with '%s' driver",
+			major, match ? "is" : "is NOT", drvname));
+	return match;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char** argv)
+{
+	char	*devname, *tmp;
+	char	diskname[PATH_MAX];
+	int	major, minor;
+	dev_t	devno, disk_devno;
+	const char *errmsg = "Couldn't parse %s: %s\n";
+
+	blkid_init_debug(BLKID_DEBUG_ALL);
+	if ((argc != 2) && (argc != 3)) {
+		fprintf(stderr, "Usage:\t%s device_number\n\t%s major minor\n"
+			"Resolve a device number to a device name\n",
+			argv[0], argv[0]);
+		exit(1);
+	}
+	if (argc == 2) {
+		devno = strtoul(argv[1], &tmp, 0);
+		if (*tmp) {
+			fprintf(stderr, errmsg, "device number", argv[1]);
+			exit(1);
+		}
+	} else {
+		major = strtoul(argv[1], &tmp, 0);
+		if (*tmp) {
+			fprintf(stderr, errmsg, "major number", argv[1]);
+			exit(1);
+		}
+		minor = strtoul(argv[2], &tmp, 0);
+		if (*tmp) {
+			fprintf(stderr, errmsg, "minor number", argv[2]);
+			exit(1);
+		}
+		devno = makedev(major, minor);
+	}
+	printf("Looking for device 0x%04llx\n", (long long)devno);
+	devname = blkid_devno_to_devname(devno);
+	free(devname);
+
+	printf("Looking for whole-device for 0x%04llx\n", (long long)devno);
+	if (blkid_devno_to_wholedisk(devno, diskname,
+				sizeof(diskname), &disk_devno) == 0)
+		printf("found devno 0x%04llx as /dev/%s\n", (long long) disk_devno, diskname);
+
+	return 0;
+}
+#endif
diff --git a/libblkid/src/encode.c b/libblkid/src/encode.c
new file mode 100644
index 0000000..ff57be4
--- /dev/null
+++ b/libblkid/src/encode.c
@@ -0,0 +1,340 @@
+
+/*
+ * encode.c - string conversion routines (mostly for compatibility with
+ *            udev/volume_id)
+ *
+ * Copyright (C) 2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "blkidP.h"
+
+#define UDEV_ALLOWED_CHARS_INPUT               "/ $%?,"
+
+/**
+ * SECTION: encode
+ * @title: Encoding utils
+ * @short_description: encode strings to safe udev-compatible formats
+ *
+ */
+
+/* count of characters used to encode one unicode char */
+static int utf8_encoded_expected_len(const char *str)
+{
+	unsigned char c = (unsigned char)str[0];
+
+	if (c < 0x80)
+		return 1;
+	if ((c & 0xe0) == 0xc0)
+		return 2;
+	if ((c & 0xf0) == 0xe0)
+		return 3;
+	if ((c & 0xf8) == 0xf0)
+		return 4;
+	if ((c & 0xfc) == 0xf8)
+		return 5;
+	if ((c & 0xfe) == 0xfc)
+		return 6;
+	return 0;
+}
+
+/* decode one unicode char */
+static int utf8_encoded_to_unichar(const char *str)
+{
+	int unichar;
+	int len;
+	int i;
+
+	len = utf8_encoded_expected_len(str);
+	switch (len) {
+	case 1:
+		return (int)str[0];
+	case 2:
+		unichar = str[0] & 0x1f;
+		break;
+	case 3:
+		unichar = (int)str[0] & 0x0f;
+		break;
+	case 4:
+		unichar = (int)str[0] & 0x07;
+		break;
+	case 5:
+		unichar = (int)str[0] & 0x03;
+		break;
+	case 6:
+		unichar = (int)str[0] & 0x01;
+		break;
+	default:
+		return -1;
+	}
+
+	for (i = 1; i < len; i++) {
+		if (((int)str[i] & 0xc0) != 0x80)
+			return -1;
+		unichar <<= 6;
+		unichar |= (int)str[i] & 0x3f;
+	}
+
+	return unichar;
+}
+
+/* expected size used to encode one unicode char */
+static int utf8_unichar_to_encoded_len(int unichar)
+{
+	if (unichar < 0x80)
+		return 1;
+	if (unichar < 0x800)
+		return 2;
+	if (unichar < 0x10000)
+		return 3;
+	if (unichar < 0x200000)
+		return 4;
+	if (unichar < 0x4000000)
+		return 5;
+	return 6;
+}
+
+/* check if unicode char has a valid numeric range */
+static int utf8_unichar_valid_range(int unichar)
+{
+	if (unichar > 0x10ffff)
+		return 0;
+	if ((unichar & 0xfffff800) == 0xd800)
+		return 0;
+	if ((unichar > 0xfdcf) && (unichar < 0xfdf0))
+		return 0;
+	if ((unichar & 0xffff) == 0xffff)
+		return 0;
+	return 1;
+}
+
+/* validate one encoded unicode char and return its length */
+static int utf8_encoded_valid_unichar(const char *str)
+{
+	int len;
+	int unichar;
+	int i;
+
+	len = utf8_encoded_expected_len(str);
+	if (len == 0)
+		return -1;
+
+	/* ascii is valid */
+	if (len == 1)
+		return 1;
+
+	/* check if expected encoded chars are available */
+	for (i = 0; i < len; i++)
+		if ((str[i] & 0x80) != 0x80)
+			return -1;
+
+	unichar = utf8_encoded_to_unichar(str);
+
+	/* check if encoded length matches encoded value */
+	if (utf8_unichar_to_encoded_len(unichar) != len)
+		return -1;
+
+	/* check if value has valid range */
+	if (!utf8_unichar_valid_range(unichar))
+		return -1;
+
+	return len;
+}
+
+static int replace_whitespace(const char *str, char *to, size_t len)
+{
+	size_t i, j;
+
+	/* strip trailing whitespace */
+	len = strnlen(str, len);
+	while (len && isspace(str[len-1]))
+		len--;
+
+	/* strip leading whitespace */
+	i = 0;
+	while (isspace(str[i]) && (i < len))
+		i++;
+
+	j = 0;
+	while (i < len) {
+		/* substitute multiple whitespace with a single '_' */
+		if (isspace(str[i])) {
+			while (isspace(str[i]))
+				i++;
+			to[j++] = '_';
+		}
+		to[j++] = str[i++];
+	}
+	to[j] = '\0';
+	return 0;
+}
+
+static int is_whitelisted(char c, const char *white)
+{
+	if ((c >= '0' && c <= '9') ||
+	    (c >= 'A' && c <= 'Z') ||
+	    (c >= 'a' && c <= 'z') ||
+	    strchr("#+-.:=@_", c) != NULL ||
+	    (white != NULL && strchr(white, c) != NULL))
+		return 1;
+	return 0;
+}
+
+/* allow chars in whitelist, plain ascii, hex-escaping and valid utf8 */
+static int replace_chars(char *str, const char *white)
+{
+	size_t i = 0;
+	int replaced = 0;
+
+	while (str[i] != '\0') {
+		int len;
+
+		if (is_whitelisted(str[i], white)) {
+			i++;
+			continue;
+		}
+
+		/* accept hex encoding */
+		if (str[i] == '\\' && str[i+1] == 'x') {
+			i += 2;
+			continue;
+		}
+
+		/* accept valid utf8 */
+		len = utf8_encoded_valid_unichar(&str[i]);
+		if (len > 1) {
+			i += len;
+			continue;
+		}
+
+		/* if space is allowed, replace whitespace with ordinary space */
+		if (isspace(str[i]) && white != NULL && strchr(white, ' ') != NULL) {
+			str[i] = ' ';
+			i++;
+			replaced++;
+			continue;
+		}
+
+		/* everything else is replaced with '_' */
+		str[i] = '_';
+		i++;
+		replaced++;
+	}
+	return replaced;
+}
+
+size_t blkid_encode_to_utf8(int enc, unsigned char *dest, size_t len,
+			const unsigned char *src, size_t count)
+{
+	size_t i, j;
+	uint16_t c;
+
+	for (j = i = 0; i + 2 <= count; i += 2) {
+		if (enc == BLKID_ENC_UTF16LE)
+			c = (src[i+1] << 8) | src[i];
+		else /* BLKID_ENC_UTF16BE */
+			c = (src[i] << 8) | src[i+1];
+		if (c == 0) {
+			dest[j] = '\0';
+			break;
+		} else if (c < 0x80) {
+			if (j+1 >= len)
+				break;
+			dest[j++] = (uint8_t) c;
+		} else if (c < 0x800) {
+			if (j+2 >= len)
+				break;
+			dest[j++] = (uint8_t) (0xc0 | (c >> 6));
+			dest[j++] = (uint8_t) (0x80 | (c & 0x3f));
+		} else {
+			if (j+3 >= len)
+				break;
+			dest[j++] = (uint8_t) (0xe0 | (c >> 12));
+			dest[j++] = (uint8_t) (0x80 | ((c >> 6) & 0x3f));
+			dest[j++] = (uint8_t) (0x80 | (c & 0x3f));
+		}
+	}
+	dest[j] = '\0';
+	return j;
+}
+
+/**
+ * blkid_encode_string:
+ * @str: input string to be encoded
+ * @str_enc: output string to store the encoded input string
+ * @len: maximum size of the output string, which may be
+ *       four times as long as the input string
+ *
+ * Encode all potentially unsafe characters of a string to the
+ * corresponding hex value prefixed by '\x'.
+ *
+ * Returns: 0 if the entire string was copied, non-zero otherwise.
+ **/
+int blkid_encode_string(const char *str, char *str_enc, size_t len)
+{
+	size_t i, j;
+
+	if (!str || !str_enc || !len)
+		return -1;
+
+	for (i = 0, j = 0; str[i] != '\0'; i++) {
+		int seqlen;
+
+		seqlen = utf8_encoded_valid_unichar(&str[i]);
+		if (seqlen > 1) {
+			if (len-j < (size_t)seqlen)
+				goto err;
+			memcpy(&str_enc[j], &str[i], seqlen);
+			j += seqlen;
+			i += (seqlen-1);
+		} else if (str[i] == '\\' || !is_whitelisted(str[i], NULL)) {
+			if (len-j < 4)
+				goto err;
+			sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]);
+			j += 4;
+		} else {
+			if (len-j < 1)
+				goto err;
+			str_enc[j] = str[i];
+			j++;
+		}
+		if (j+3 >= len)
+			goto err;
+	}
+	if (len-j < 1)
+		goto err;
+	str_enc[j] = '\0';
+	return 0;
+err:
+	return -1;
+}
+
+/**
+ * blkid_safe_string:
+ * @str: input string
+ * @str_safe: output string
+ * @len: size of output string
+ *
+ * Allows plain ascii, hex-escaping and valid utf8. Replaces all whitespaces
+ * with '_'.
+ *
+ * Returns: 0 on success or -1 in case of error.
+ */
+int blkid_safe_string(const char *str, char *str_safe, size_t len)
+{
+	if (!str || !str_safe || !len)
+		return -1;
+	replace_whitespace(str, str_safe, len);
+	replace_chars(str_safe, UDEV_ALLOWED_CHARS_INPUT);
+	return 0;
+}
diff --git a/libblkid/src/env.h b/libblkid/src/env.h
new file mode 100644
index 0000000..a53d310
--- /dev/null
+++ b/libblkid/src/env.h
@@ -0,0 +1,16 @@
+#ifndef UTIL_LINUX_ENV_H
+#define UTIL_LINUX_ENV_H
+
+#include "c.h"
+
+extern void sanitize_env(void);
+extern char *safe_getenv(const char *arg);
+
+static inline void xsetenv(char const *name, char const *val, int overwrite)
+{
+	if (setenv(name, val, overwrite) != 0)
+		err(EXIT_FAILURE, "failed to set the %s environment variable", name);
+}
+
+#endif /* UTIL_LINUX_ENV_H */
+
diff --git a/libblkid/src/evaluate.c b/libblkid/src/evaluate.c
new file mode 100644
index 0000000..3d9a76b
--- /dev/null
+++ b/libblkid/src/evaluate.c
@@ -0,0 +1,325 @@
+/*
+ * evaluate.c - very high-level API to evaluate LABELs or UUIDs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <stdint.h>
+#include <stdarg.h>
+
+#include "pathnames.h"
+#include "canonicalize.h"
+#include "closestream.h"
+
+#include "blkidP.h"
+
+/**
+ * SECTION:evaluate
+ * @title: Tags and Spec evaluation
+ * @short_description: top-level API for LABEL and UUID evaluation.
+ *
+ * This API provides very simple and portable way how evaluate LABEL and UUID
+ * tags.  The blkid_evaluate_tag() and blkid_evaluate_spec() work on 2.4 and
+ * 2.6 systems and on systems with or without udev. Currently, the libblkid
+ * library supports "udev" and "scan" methods. The "udev" method uses udev
+ * /dev/disk/by-* symlinks and the "scan" method scans all block devices from
+ * the /proc/partitions file. The evaluation could be controlled by the
+ * /etc/blkid.conf config file. The default is to try "udev" and then "scan"
+ * method.
+ *
+ * The blkid_evaluate_tag() also automatically informs udevd when an obsolete
+ * /dev/disk/by-* symlink is detected.
+ *
+ * If you are not sure how translate LABEL or UUID to the device name use this
+ * API.
+ */
+
+#ifdef CONFIG_BLKID_VERIFY_UDEV
+/* returns zero when the device has NAME=value (LABEL/UUID) */
+static int verify_tag(const char *devname, const char *name, const char *value)
+{
+	blkid_probe pr;
+	int fd = -1, rc = -1;
+	size_t len;
+	const char *data;
+	int errsv = 0;
+
+	pr = blkid_new_probe();
+	if (!pr)
+		return -1;
+
+	blkid_probe_enable_superblocks(pr, TRUE);
+	blkid_probe_set_superblocks_flags(pr,
+			BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID);
+
+	blkid_probe_enable_partitions(pr, TRUE);
+	blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
+
+	fd = open(devname, O_RDONLY|O_CLOEXEC);
+	if (fd < 0) {
+		errsv = errno;
+		goto done;
+	}
+	if (blkid_probe_set_device(pr, fd, 0, 0))
+		goto done;
+	rc = blkid_do_safeprobe(pr);
+	if (rc)
+		goto done;
+	rc = blkid_probe_lookup_value(pr, name, &data, &len);
+	if (!rc)
+		rc = memcmp(value, data, len);
+done:
+	DBG(EVALUATE, ul_debug("%s: %s verification %s",
+			devname, name, rc == 0 ? "PASS" : "FAILED"));
+	if (fd >= 0)
+		close(fd);
+	blkid_free_probe(pr);
+
+	/* for non-root users we use unverified udev links */
+	return errsv == EACCES ? 0 : rc;
+}
+#endif /* CONFIG_BLKID_VERIFY_UDEV*/
+
+/**
+ * blkid_send_uevent:
+ * @devname: absolute path to the device
+ * @action: event string
+ *
+ * Returns: -1 in case of failure, or 0 on success.
+ */
+int blkid_send_uevent(const char *devname, const char *action)
+{
+	char uevent[PATH_MAX];
+	struct stat st;
+	FILE *f;
+	int rc = -1;
+
+	DBG(EVALUATE, ul_debug("%s: uevent '%s' requested", devname, action));
+
+	if (!devname || !action)
+		return -1;
+	if (stat(devname, &st) || !S_ISBLK(st.st_mode))
+		return -1;
+
+	snprintf(uevent, sizeof(uevent), "/sys/dev/block/%d:%d/uevent",
+			major(st.st_rdev), minor(st.st_rdev));
+
+	f = fopen(uevent, "w" UL_CLOEXECSTR);
+	if (f) {
+		rc = 0;
+		if (fputs(action, f) >= 0)
+			rc = 0;
+		if (close_stream(f) != 0)
+			DBG(EVALUATE, ul_debug("write failed: %s", uevent));
+	}
+	DBG(EVALUATE, ul_debug("%s: send uevent %s",
+			uevent, rc == 0 ? "SUCCES" : "FAILED"));
+	return rc;
+}
+
+static char *evaluate_by_udev(const char *token, const char *value, int uevent)
+{
+	char dev[PATH_MAX];
+	char *path = NULL;
+	size_t len;
+	struct stat st;
+
+	DBG(EVALUATE, ul_debug("evaluating by udev %s=%s", token, value));
+
+	if (!strcmp(token, "UUID"))
+		strcpy(dev, _PATH_DEV_BYUUID "/");
+	else if (!strcmp(token, "LABEL"))
+		strcpy(dev, _PATH_DEV_BYLABEL "/");
+	else if (!strcmp(token, "PARTLABEL"))
+		strcpy(dev, _PATH_DEV_BYPARTLABEL "/");
+	else if (!strcmp(token, "PARTUUID"))
+		strcpy(dev, _PATH_DEV_BYPARTUUID "/");
+	else {
+		DBG(EVALUATE, ul_debug("unsupported token %s", token));
+		return NULL;	/* unsupported tag */
+	}
+
+	len = strlen(dev);
+	if (blkid_encode_string(value, &dev[len], sizeof(dev) - len) != 0)
+		return NULL;
+
+	DBG(EVALUATE, ul_debug("expected udev link: %s", dev));
+
+	if (stat(dev, &st))
+		goto failed;	/* link or device does not exist */
+
+	if (!S_ISBLK(st.st_mode))
+		return NULL;
+
+	path = canonicalize_path(dev);
+	if (!path)
+		return NULL;
+
+#ifdef CONFIG_BLKID_VERIFY_UDEV
+	if (verify_tag(path, token, value))
+		goto failed;
+#endif
+	return path;
+
+failed:
+	DBG(EVALUATE, ul_debug("failed to evaluate by udev"));
+
+	if (uevent && path)
+		blkid_send_uevent(path, "change");
+	free(path);
+	return NULL;
+}
+
+static char *evaluate_by_scan(const char *token, const char *value,
+		blkid_cache *cache, struct blkid_config *conf)
+{
+	blkid_cache c = cache ? *cache : NULL;
+	char *res;
+
+	DBG(EVALUATE, ul_debug("evaluating by blkid scan %s=%s", token, value));
+
+	if (!c) {
+		char *cachefile = blkid_get_cache_filename(conf);
+		blkid_get_cache(&c, cachefile);
+		free(cachefile);
+	}
+	if (!c)
+		return NULL;
+
+	res = blkid_get_devname(c, token, value);
+
+	if (cache)
+		*cache = c;
+	else
+		blkid_put_cache(c);
+
+	return res;
+}
+
+/**
+ * blkid_evaluate_tag:
+ * @token: token name (e.g "LABEL" or "UUID") or unparsed tag (e.g. "LABEL=foo")
+ * @value: token data (e.g. "foo")
+ * @cache: pointer to cache (or NULL when you don't want to re-use the cache)
+ *
+ * Returns: allocated string with a device name.
+ */
+char *blkid_evaluate_tag(const char *token, const char *value, blkid_cache *cache)
+{
+	struct blkid_config *conf = NULL;
+	char *t = NULL, *v = NULL;
+	char *ret = NULL;
+	int i;
+
+	if (!token)
+		return NULL;
+
+	if (!cache || !*cache)
+		blkid_init_debug(0);
+
+	DBG(EVALUATE, ul_debug("evaluating  %s%s%s", token, value ? "=" : "",
+		   value ? value : ""));
+
+	if (!value) {
+		if (!strchr(token, '=')) {
+			ret = strdup(token);
+			goto out;
+		}
+		blkid_parse_tag_string(token, &t, &v);
+		if (!t || !v)
+			goto out;
+		token = t;
+		value = v;
+	}
+
+	conf = blkid_read_config(NULL);
+	if (!conf)
+		goto out;
+
+	for (i = 0; i < conf->nevals; i++) {
+		if (conf->eval[i] == BLKID_EVAL_UDEV)
+			ret = evaluate_by_udev(token, value, conf->uevent);
+		else if (conf->eval[i] == BLKID_EVAL_SCAN)
+			ret = evaluate_by_scan(token, value, cache, conf);
+		if (ret)
+			break;
+	}
+
+	DBG(EVALUATE, ul_debug("%s=%s evaluated as %s", token, value, ret));
+out:
+	blkid_free_config(conf);
+	free(t);
+	free(v);
+	return ret;
+}
+
+/**
+ * blkid_evaluate_spec:
+ * @spec: unparsed tag (e.g. "LABEL=foo") or path (e.g. /dev/dm-0)
+ * @cache: pointer to cache (or NULL when you don't want to re-use the cache)
+ *
+ * All returned paths are canonicalized, device-mapper paths are converted
+ * to the /dev/mapper/name format.
+ *
+ * Returns: allocated string with a device name.
+ */
+char *blkid_evaluate_spec(const char *spec, blkid_cache *cache)
+{
+	char *t = NULL, *v = NULL, *res;
+
+	if (!spec)
+		return NULL;
+
+	if (strchr(spec, '=') &&
+	    blkid_parse_tag_string(spec, &t, &v) != 0)	/* parse error */
+		return NULL;
+
+	if (v)
+		res = blkid_evaluate_tag(t, v, cache);
+	else
+		res = canonicalize_path(spec);
+
+	free(t);
+	free(v);
+	return res;
+}
+
+
+#ifdef TEST_PROGRAM
+int main(int argc, char *argv[])
+{
+	blkid_cache cache = NULL;
+	char *res;
+
+	if (argc < 2) {
+		fprintf(stderr, "usage: %s <tag> | <spec>\n", argv[0]);
+		return EXIT_FAILURE;
+	}
+
+	blkid_init_debug(0);
+
+	res = blkid_evaluate_spec(argv[1], &cache);
+	if (res)
+		printf("%s\n", res);
+	if (cache)
+		blkid_put_cache(cache);
+
+	return res ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+#endif
diff --git a/libblkid/src/exec_shell.h b/libblkid/src/exec_shell.h
new file mode 100644
index 0000000..a2aa757
--- /dev/null
+++ b/libblkid/src/exec_shell.h
@@ -0,0 +1 @@
+extern void __attribute__((__noreturn__)) exec_shell(void);
diff --git a/libblkid/src/fileutils.h b/libblkid/src/fileutils.h
new file mode 100644
index 0000000..3353f69
--- /dev/null
+++ b/libblkid/src/fileutils.h
@@ -0,0 +1,33 @@
+#ifndef UTIL_LINUX_FILEUTILS
+#define UTIL_LINUX_FILEUTILS
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "c.h"
+
+extern int xmkstemp(char **tmpname, char *dir);
+
+static inline FILE *xfmkstemp(char **tmpname, char *dir)
+{
+	int fd;
+	FILE *ret;
+
+	fd = xmkstemp(tmpname, dir);
+	if (fd == -1)
+		return NULL;
+
+	if (!(ret = fdopen(fd, "w+" UL_CLOEXECSTR))) {
+		close(fd);
+		return NULL;
+	}
+	return ret;
+}
+
+extern int get_fd_tabsize(void);
+
+extern int mkdir_p(const char *path, mode_t mode);
+extern char *stripoff_last_component(char *path);
+
+#endif /* UTIL_LINUX_FILEUTILS */
diff --git a/libblkid/src/getsize.c b/libblkid/src/getsize.c
new file mode 100644
index 0000000..abe6ebc
--- /dev/null
+++ b/libblkid/src/getsize.c
@@ -0,0 +1,34 @@
+/*
+ * getsize.c --- get the size of a partition.
+ *
+ * Copyright (C) 1995, 1995 Theodore Ts'o.
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "blkidP.h"
+
+/**
+ * blkid_get_dev_size:
+ * @fd: file descriptor
+ *
+ * Returns: size (in bytes) of the block device or size of the regular file or 0.
+ */
+blkid_loff_t blkid_get_dev_size(int fd)
+{
+	unsigned long long bytes;
+
+	if (blkdev_get_size(fd, &bytes))
+		return 0;
+
+	return bytes;
+}
+
diff --git a/libblkid/src/init.c b/libblkid/src/init.c
new file mode 100644
index 0000000..eead6c7
--- /dev/null
+++ b/libblkid/src/init.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2008-2013 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: init
+ * @title: Library initialization
+ * @short_description: initialize debuging
+ */
+
+#include <stdarg.h>
+
+#include "blkidP.h"
+
+UL_DEBUG_DEFINE_MASK(libblkid);
+UL_DEBUG_DEFINE_MASKNAMES(libblkid) =
+{
+	{ "all", BLKID_DEBUG_ALL,	"info about all subsystems" },
+	{ "cache", BLKID_DEBUG_CACHE,	"blkid tags cache" },
+	{ "config", BLKID_DEBUG_CONFIG, "config file utils" },
+	{ "dev", BLKID_DEBUG_DEV,       "device utils" },
+	{ "devname", BLKID_DEBUG_DEVNAME, "/proc/partitions evaluation" },
+	{ "devno", BLKID_DEBUG_DEVNO,	"convertions to device name" },
+	{ "evaluate", BLKID_DEBUG_EVALUATE, "tags resolving" },
+	{ "help", BLKID_DEBUG_HELP,	"this help" },
+	{ "lowprobe", BLKID_DEBUG_LOWPROBE, "superblock/raids/partitions probing" },
+	{ "probe", BLKID_DEBUG_PROBE,	"devices verification" },
+	{ "read", BLKID_DEBUG_READ,	"cache parsing" },
+	{ "save", BLKID_DEBUG_SAVE,	"cache writing" },
+	{ "tag", BLKID_DEBUG_TAG,	"tags utils" },
+	{ NULL, 0, NULL }
+};
+
+/**
+ * blkid_init_debug:
+ * @mask: debug mask (0xffff to enable full debuging)
+ *
+ * If the @mask is not specified then this function reads
+ * LIBBLKID_DEBUG environment variable to get the mask.
+ *
+ * Already initialized debugging stuff cannot be changed. It does not
+ * have effect to call this function twice.
+ */
+void blkid_init_debug(int mask)
+{
+	if (libblkid_debug_mask)
+		return;
+
+	__UL_INIT_DEBUG(libblkid, BLKID_DEBUG_, mask, LIBBLKID_DEBUG);
+
+	if (libblkid_debug_mask != BLKID_DEBUG_INIT
+	    && libblkid_debug_mask != (BLKID_DEBUG_HELP|BLKID_DEBUG_INIT)) {
+		const char *ver = NULL;
+		const char *date = NULL;
+
+		blkid_get_library_version(&ver, &date);
+		DBG(INIT, ul_debug("library debug mask: 0x%04x", libblkid_debug_mask));
+		DBG(INIT, ul_debug("library version: %s [%s]", ver, date));
+
+	}
+	ON_DBG(HELP, ul_debug_print_masks("LIBBLKID_DEBUG",
+				UL_DEBUG_MASKNAMES(libblkid)));
+}
diff --git a/libblkid/src/ismounted.h b/libblkid/src/ismounted.h
new file mode 100644
index 0000000..57918cb
--- /dev/null
+++ b/libblkid/src/ismounted.h
@@ -0,0 +1,14 @@
+#ifndef IS_MOUNTED_H
+#define IS_MOUNTED_H
+
+#define MF_MOUNTED	1
+#define MF_ISROOT	2
+#define MF_READONLY	4
+#define MF_SWAP		8
+#define MF_BUSY		16
+
+extern int is_mounted(const char *file);
+extern int check_mount_point(const char *device, int *mount_flags,
+				 char *mtpt, int mtlen);
+
+#endif /* IS_MOUNTED_H */
diff --git a/libblkid/src/libblkid.sym b/libblkid/src/libblkid.sym
new file mode 100644
index 0000000..6b3cf08
--- /dev/null
+++ b/libblkid/src/libblkid.sym
@@ -0,0 +1,166 @@
+/*
+ * The symbol versioning ensures that a new application requiring symbol 'foo'
+ * can't run with old library.so not providing 'foo' - the global SONAME
+ * version info can't enforce this since we never change the SONAME.
+ *
+ * The original libblkid from e2fsprogs (<=1.41.4) does not to use
+ * symbol versioning -- all the original symbols are in BLKID_1.0 now.
+ *
+ * Copyright (C) 2009-2014 Karel Zak <kzak@redhat.com> 
+ */
+BLKID_1.0 {
+global:
+	blkid_dev_devname;
+	blkid_dev_has_tag;
+	blkid_dev_iterate_begin;
+	blkid_dev_iterate_end;
+	blkid_dev_next;
+	blkid_devno_to_devname;
+	blkid_dev_set_search;
+	blkid_find_dev_with_tag;
+	blkid_gc_cache;
+	blkid_get_cache;
+	blkid_get_dev;
+	blkid_get_devname;
+	blkid_get_dev_size;
+	blkid_get_library_version;
+	blkid_get_tag_value;
+	blkid_known_fstype;
+	blkid_parse_tag_string;
+	blkid_parse_version_string;
+	blkid_probe_all;
+	blkid_probe_all_new;
+	blkid_put_cache;
+	blkid_tag_iterate_begin;
+	blkid_tag_iterate_end;
+	blkid_tag_next;
+	blkid_verify;
+local:
+	*;
+};
+
+
+/*
+ * symbols since util-linux 2.15
+ */
+BLKID_2.15 {
+global:
+	blkid_do_probe;
+	blkid_do_safeprobe;
+	blkid_encode_string;
+	blkid_evaluate_tag;
+	blkid_free_probe;
+	blkid_new_probe;
+	blkid_probe_filter_types;
+	blkid_probe_filter_usage;
+	blkid_probe_get_value;
+	blkid_probe_has_value;
+	blkid_probe_invert_filter;
+	blkid_probe_lookup_value;
+	blkid_probe_numof_values;
+	blkid_probe_reset_filter;
+	blkid_probe_set_device;
+	blkid_probe_set_request;
+	blkid_reset_probe;
+	blkid_safe_string;
+	blkid_send_uevent;
+} BLKID_1.0;
+
+/*
+ * symbols since util-linux 2.17
+ */
+BLKID_2.17 {
+global:
+	blkid_devno_to_wholedisk;
+	blkid_do_fullprobe;
+	blkid_known_pttype;
+	blkid_new_probe_from_filename;
+	blkid_partition_get_name;
+	blkid_partition_get_partno;
+	blkid_partition_get_size;
+	blkid_partition_get_start;
+	blkid_partition_get_table;
+	blkid_partition_get_type;
+	blkid_partition_get_type_string;
+	blkid_partition_get_uuid;
+	blkid_partition_is_extended;
+	blkid_partition_is_logical;
+	blkid_partition_is_primary;
+	blkid_partlist_get_partition;
+	blkid_partlist_numof_partitions;
+	blkid_parttable_get_offset;
+	blkid_parttable_get_parent;
+	blkid_parttable_get_type;
+	blkid_probe_enable_partitions;
+	blkid_probe_enable_superblocks;
+	blkid_probe_enable_topology;
+	blkid_probe_filter_partitions_type;
+	blkid_probe_filter_superblocks_type;
+	blkid_probe_filter_superblocks_usage;
+	blkid_probe_get_devno;
+	blkid_probe_get_partitions;
+	blkid_probe_get_sectorsize;
+	blkid_probe_get_sectors;
+	blkid_probe_get_size;
+	blkid_probe_get_topology;
+	blkid_probe_invert_partitions_filter;
+	blkid_probe_invert_superblocks_filter;
+	blkid_probe_reset_partitions_filter;
+	blkid_probe_reset_superblocks_filter;
+	blkid_probe_set_partitions_flags;
+	blkid_probe_set_superblocks_flags;
+	blkid_topology_get_alignment_offset;
+	blkid_topology_get_logical_sector_size;
+	blkid_topology_get_minimum_io_size;
+	blkid_topology_get_optimal_io_size;
+	blkid_topology_get_physical_sector_size;
+} BLKID_2.15;
+
+/*
+ * symbols since util-linux 2.18
+ */
+BLKID_2.18 {
+global:
+	blkid_partition_get_flags;
+	blkid_partlist_devno_to_partition;
+	blkid_partlist_get_table;
+	blkid_probe_all_removable;
+	blkid_probe_get_fd;
+	blkid_probe_get_offset;
+	blkid_probe_get_wholedisk_devno;
+	blkid_probe_is_wholedisk;
+} BLKID_2.17;
+
+/*
+ * symbols since util-linux 2.20
+ */
+BLKID_2.20 {
+global:
+	blkid_evaluate_spec;
+	blkid_superblocks_get_name;
+} BLKID_2.18;
+
+/*
+ * symbols since util-linux 2.21
+ */
+BLKID_2.21 {
+global:
+	blkid_do_wipe;
+} BLKID_2.20;
+
+/*
+ * symbols since util-linux 2.23
+ */
+BLKID_2.23 {
+global:
+	blkid_probe_step_back;
+	blkid_parttable_get_id;
+	blkid_init_debug;
+} BLKID_2.21;
+
+/*
+ * symbols since util-linux 2.25
+ */
+BLKID_2.25 {
+	blkid_partlist_get_partition_by_partno;
+} BLKID_2.23;
diff --git a/libblkid/src/linux_version.h b/libblkid/src/linux_version.h
new file mode 100644
index 0000000..a6a1e99
--- /dev/null
+++ b/libblkid/src/linux_version.h
@@ -0,0 +1,14 @@
+#ifndef LINUX_VERSION_H
+#define LINUX_VERSION_H
+
+#ifdef HAVE_LINUX_VERSION_H
+# include <linux/version.h>
+#endif
+
+#ifndef KERNEL_VERSION
+# define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+#endif
+
+int get_linux_version(void);
+
+#endif /* LINUX_VERSION_H */
diff --git a/libblkid/src/list.h b/libblkid/src/list.h
new file mode 100644
index 0000000..7b60672
--- /dev/null
+++ b/libblkid/src/list.h
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 1999-2008 by Theodore Ts'o
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * (based on list.h from e2fsprogs)
+ * Merge sort based on kernel's implementation.
+ */
+
+#ifndef UTIL_LINUX_LIST_H
+#define UTIL_LINUX_LIST_H
+
+/* TODO: use AC_C_INLINE */
+#ifdef __GNUC__
+#define _INLINE_ static __inline__
+#else                         /* For Watcom C */
+#define _INLINE_ static inline
+#endif
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+	struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+	struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+_INLINE_ void __list_add(struct list_head * add,
+	struct list_head * prev,
+	struct list_head * next)
+{
+	next->prev = add;
+	add->next = next;
+	add->prev = prev;
+	prev->next = add;
+}
+
+/**
+ * list_add - add a new entry
+ * @add:	new entry to be added
+ * @head:	list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+_INLINE_ void list_add(struct list_head *add, struct list_head *head)
+{
+	__list_add(add, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @add:	new entry to be added
+ * @head:	list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+_INLINE_ void list_add_tail(struct list_head *add, struct list_head *head)
+{
+	__list_add(add, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+_INLINE_ void __list_del(struct list_head * prev,
+				  struct list_head * next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry:	the element to delete from the list.
+ *
+ * list_empty() on @entry does not return true after this, @entry is
+ * in an undefined state.
+ */
+_INLINE_ void list_del(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry:	the element to delete from the list.
+ */
+_INLINE_ void list_del_init(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head:	the list to test.
+ */
+_INLINE_ int list_empty(struct list_head *head)
+{
+	return head->next == head;
+}
+
+/**
+ * list_entry_is_last - tests whether is entry last in the list
+ * @entry:	the entry to test.
+ * @head:	the list to test.
+ */
+_INLINE_ int list_entry_is_last(struct list_head *entry, struct list_head *head)
+{
+	return head->prev == entry;
+}
+
+/**
+ * list_splice - join two lists
+ * @list:	the new list to add.
+ * @head:	the place to add it in the first list.
+ */
+_INLINE_ void list_splice(struct list_head *list, struct list_head *head)
+{
+	struct list_head *first = list->next;
+
+	if (first != list) {
+		struct list_head *last = list->prev;
+		struct list_head *at = head->next;
+
+		first->prev = head;
+		head->next = first;
+
+		last->next = at;
+		at->prev = last;
+	}
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:	the &struct list_head pointer.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) ({              \
+	const typeof( ((type *)0)->member ) *__mptr = (ptr);   \
+	(type *)( (char *)__mptr - offsetof(type,member) );})
+
+
+#define list_first_entry(head, type, member) \
+	((head) && (head)->next != (head) ? list_entry((head)->next, type, member) : NULL)
+
+#define list_last_entry(head, type, member) \
+	((head) && (head)->prev != (head) ? list_entry((head)->prev, type, member) : NULL)
+
+/**
+ * list_for_each - iterate over elements in a list
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+#define list_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_backwardly - iterate over elements in a list in reverse
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+#define list_for_each_backwardly(pos, head) \
+	for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over elements in a list, but don't dereference
+ *                      pos after the body is done (in case it is freed)
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @pnext:	the &struct list_head to use as a pointer to the next item.
+ * @head:	the head for your list (not included in iteration).
+ */
+#define list_for_each_safe(pos, pnext, head) \
+	for (pos = (head)->next, pnext = pos->next; pos != (head); \
+	     pos = pnext, pnext = pos->next)
+
+#define MAX_LIST_LENGTH_BITS 20
+
+/*
+ * Returns a list organized in an intermediate format suited
+ * to chaining of merge() calls: null-terminated, no reserved or
+ * sentinel head node, "prev" links not maintained.
+ */
+_INLINE_ struct list_head *merge(int (*cmp)(struct list_head *a,
+					  struct list_head *b),
+			       struct list_head *a, struct list_head *b)
+{
+	struct list_head head, *tail = &head;
+
+	while (a && b) {
+		/* if equal, take 'a' -- important for sort stability */
+		if ((*cmp)(a, b) <= 0) {
+			tail->next = a;
+			a = a->next;
+		} else {
+			tail->next = b;
+			b = b->next;
+		}
+		tail = tail->next;
+	}
+	tail->next = a ? a : b;
+	return head.next;
+}
+
+/*
+ * Combine final list merge with restoration of standard doubly-linked
+ * list structure.  This approach duplicates code from merge(), but
+ * runs faster than the tidier alternatives of either a separate final
+ * prev-link restoration pass, or maintaining the prev links
+ * throughout.
+ */
+_INLINE_ void merge_and_restore_back_links(int (*cmp)(struct list_head *a,
+						    struct list_head *b),
+					 struct list_head *head,
+					 struct list_head *a, struct list_head *b)
+{
+	struct list_head *tail = head;
+
+	while (a && b) {
+		/* if equal, take 'a' -- important for sort stability */
+		if ((*cmp)(a, b) <= 0) {
+			tail->next = a;
+			a->prev = tail;
+			a = a->next;
+		} else {
+			tail->next = b;
+			b->prev = tail;
+			b = b->next;
+		}
+		tail = tail->next;
+	}
+	tail->next = a ? a : b;
+
+	do {
+		/*
+		 * In worst cases this loop may run many iterations.
+		 * Continue callbacks to the client even though no
+		 * element comparison is needed, so the client's cmp()
+		 * routine can invoke cond_resched() periodically.
+		 */
+		(*cmp)(tail->next, tail->next);
+
+		tail->next->prev = tail;
+		tail = tail->next;
+	} while (tail->next);
+
+	tail->next = head;
+	head->prev = tail;
+}
+
+
+/**
+ * list_sort - sort a list
+ * @head: the list to sort
+ * @cmp: the elements comparison function
+ *
+ * This function implements "merge sort", which has O(nlog(n))
+ * complexity.
+ *
+ * The comparison function @cmp must return a negative value if @a
+ * should sort before @b, and a positive value if @a should sort after
+ * @b. If @a and @b are equivalent, and their original relative
+ * ordering is to be preserved, @cmp must return 0.
+ */
+_INLINE_ void list_sort(struct list_head *head,
+			int (*cmp)(struct list_head *a,
+				   struct list_head *b))
+{
+	struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists
+							   -- last slot is a sentinel */
+	size_t lev;  /* index into part[] */
+	size_t max_lev = 0;
+	struct list_head *list;
+
+	if (list_empty(head))
+		return;
+
+	memset(part, 0, sizeof(part));
+
+	head->prev->next = NULL;
+	list = head->next;
+
+	while (list) {
+		struct list_head *cur = list;
+		list = list->next;
+		cur->next = NULL;
+
+		for (lev = 0; part[lev]; lev++) {
+			cur = merge(cmp, part[lev], cur);
+			part[lev] = NULL;
+		}
+		if (lev > max_lev) {
+			/* list passed to list_sort() too long for efficiency */
+			if (lev >= ARRAY_SIZE(part) - 1)
+				lev--;
+			max_lev = lev;
+		}
+		part[lev] = cur;
+	}
+
+	for (lev = 0; lev < max_lev; lev++)
+		if (part[lev])
+			list = merge(cmp, part[lev], list);
+
+	merge_and_restore_back_links(cmp, head, part[max_lev], list);
+}
+
+#undef _INLINE_
+
+#endif /* UTIL_LINUX_LIST_H */
diff --git a/libblkid/src/llseek.c b/libblkid/src/llseek.c
new file mode 100644
index 0000000..6733478
--- /dev/null
+++ b/libblkid/src/llseek.c
@@ -0,0 +1,142 @@
+/*
+ * llseek.c -- stub calling the llseek system call
+ *
+ * Copyright (C) 1994, 1995, 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#define _LARGEFILE_SOURCE
+#define _LARGEFILE64_SOURCE
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef __MSDOS__
+#include <io.h>
+#endif
+
+#include "blkidP.h"
+
+#ifdef __linux__
+
+#if defined(HAVE_LSEEK64) && defined(HAVE_LSEEK64_PROTOTYPE)
+
+#define my_llseek lseek64
+
+#elif defined(HAVE_LLSEEK)
+#include <syscall.h>
+
+#ifndef HAVE_LLSEEK_PROTOTYPE
+extern long long llseek(int fd, long long offset, int origin);
+#endif
+
+#define my_llseek llseek
+
+#else	/* ! HAVE_LLSEEK */
+
+#if SIZEOF_LONG == SIZEOF_LONG_LONG
+
+#define llseek lseek
+
+#else /* SIZEOF_LONG != SIZEOF_LONG_LONG */
+
+#include <linux/unistd.h>
+
+#ifndef __NR__llseek
+#define __NR__llseek            140
+#endif
+
+#ifndef __i386__
+static int _llseek(unsigned int, unsigned long, unsigned long,
+		   blkid_loff_t *, unsigned int);
+
+static _syscall5(int, _llseek, unsigned int, fd, unsigned long, offset_high,
+		 unsigned long, offset_low, blkid_loff_t *, result,
+		 unsigned int, origin)
+#endif
+/*
+static blkid_loff_t my_llseek(int fd, blkid_loff_t offset, int origin)
+{
+	blkid_loff_t result;
+	int retval;
+
+#ifndef __i386__
+	retval = _llseek(fd, ((unsigned long long) offset) >> 32,
+			 ((unsigned long long)offset) & 0xffffffff,
+			 &result, origin);
+#else
+	retval = syscall(__NR__llseek, fd, ((unsigned long long) offset) >> 32,
+			 ((unsigned long long)offset) & 0xffffffff,
+			 &result, origin);
+#endif
+	return (retval == -1 ? (blkid_loff_t) retval : result);
+}
+*/
+#endif	/* __alpha__ || __ia64__ */
+
+#endif /* HAVE_LLSEEK */
+
+blkid_loff_t blkid_llseek(int fd, blkid_loff_t offset, int whence)
+{
+	blkid_loff_t result;
+	static int do_compat = 0;
+
+	if ((sizeof(off_t) >= sizeof(blkid_loff_t)) ||
+	    (offset < ((blkid_loff_t) 1 << ((sizeof(off_t)*8) -1))))
+		return lseek(fd, (off_t) offset, whence);
+
+	if (do_compat) {
+		errno = EOVERFLOW;
+		return -1;
+	}
+
+	result = lseek64(fd, offset, whence);
+	if (result == -1 && errno == ENOSYS) {
+		/*
+		 * Just in case this code runs on top of an old kernel
+		 * which does not support the llseek system call
+		 */
+		do_compat++;
+		errno = EOVERFLOW;
+	}
+	return result;
+}
+
+#else /* !linux */
+
+#ifndef EOVERFLOW
+#ifdef EXT2_ET_INVALID_ARGUMENT
+#define EOVERFLOW EXT2_ET_INVALID_ARGUMENT
+#else
+#define EOVERFLOW 112
+#endif
+#endif
+
+blkid_loff_t blkid_llseek(int fd, blkid_loff_t offset, int origin)
+{
+#if defined(HAVE_LSEEK64) && defined(HAVE_LSEEK64_PROTOTYPE)
+	return lseek64 (fd, offset, origin);
+#else
+	if ((sizeof(off_t) < sizeof(blkid_loff_t)) &&
+	    (offset >= ((blkid_loff_t) 1 << ((sizeof(off_t)*8) - 1)))) {
+		errno = EOVERFLOW;
+		return -1;
+	}
+	return lseek(fd, (off_t) offset, origin);
+#endif
+}
+
+#endif	/* linux */
+
+
diff --git a/libblkid/src/loopdev.h b/libblkid/src/loopdev.h
new file mode 100644
index 0000000..573a569
--- /dev/null
+++ b/libblkid/src/loopdev.h
@@ -0,0 +1,193 @@
+#ifndef UTIL_LINUX_LOOPDEV_H
+#define UTIL_LINUX_LOOPDEV_H
+
+#include "sysfs.h"
+
+/*
+ * loop_info.lo_encrypt_type
+ */
+#define LO_CRYPT_NONE	0
+#define LO_CRYPT_XOR	1
+#define LO_CRYPT_DES	2
+#define LO_CRYPT_CRYPTOAPI 18
+
+#define LOOP_SET_FD		0x4C00
+#define LOOP_CLR_FD		0x4C01
+/*
+ * Obsolete (kernel < 2.6)
+ *
+ * #define LOOP_SET_STATUS	0x4C02
+ * #define LOOP_GET_STATUS	0x4C03
+ */
+#define LOOP_SET_STATUS64	0x4C04
+#define LOOP_GET_STATUS64	0x4C05
+/* #define LOOP_CHANGE_FD	0x4C06 */
+#define LOOP_SET_CAPACITY	0x4C07
+
+/* /dev/loop-control interface */
+#ifndef LOOP_CTL_ADD
+# define LOOP_CTL_ADD		0x4C80
+# define LOOP_CTL_REMOVE	0x4C81
+# define LOOP_CTL_GET_FREE	0x4C82
+#endif
+
+/*
+ * loop_info.lo_flags
+ */
+enum {
+	LO_FLAGS_READ_ONLY  = 1,
+	LO_FLAGS_USE_AOPS   = 2,
+	LO_FLAGS_AUTOCLEAR  = 4,	/* kernel >= 2.6.25 */
+	LO_FLAGS_PARTSCAN   = 8,	/* kernel >= 3.2 */
+};
+
+#define LO_NAME_SIZE	64
+#define LO_KEY_SIZE	32
+
+/*
+ * Linux LOOP_{SET,GET}_STATUS64 ioctl struct
+ */
+struct loop_info64 {
+	uint64_t	lo_device;
+	uint64_t	lo_inode;
+	uint64_t	lo_rdevice;
+	uint64_t	lo_offset;
+	uint64_t	lo_sizelimit; /* bytes, 0 == max available */
+	uint32_t	lo_number;
+	uint32_t	lo_encrypt_type;
+	uint32_t	lo_encrypt_key_size;
+	uint32_t	lo_flags;
+	uint8_t		lo_file_name[LO_NAME_SIZE];
+	uint8_t		lo_crypt_name[LO_NAME_SIZE];
+	uint8_t		lo_encrypt_key[LO_KEY_SIZE];
+	uint64_t	lo_init[2];
+};
+
+#define LOOPDEV_MAJOR		7	/* loop major number */
+#define LOOPDEV_DEFAULT_NNODES	8	/* default number of loop devices */
+
+struct loopdev_iter {
+	FILE		*proc;		/* /proc/partitions */
+	DIR		*sysblock;	/* /sys/block */
+	int		ncur;		/* current position */
+	int		*minors;	/* ary of minor numbers (when scan whole /dev) */
+	int		nminors;	/* number of items in *minors */
+	int		ct_perm;	/* count permission problems */
+	int		ct_succ;	/* count number of detected devices */
+
+	unsigned int	done:1;		/* scanning done */
+	unsigned int	default_check:1;/* check first LOOPDEV_NLOOPS */
+	int		flags;		/* LOOPITER_FL_* flags */
+};
+
+enum {
+	LOOPITER_FL_FREE	= (1 << 0),
+	LOOPITER_FL_USED	= (1 << 1)
+};
+
+/*
+ * handler for work with loop devices
+ */
+struct loopdev_cxt {
+	char		device[128];	/* device path (e.g. /dev/loop<N>) */
+	char		*filename;	/* backing file for loopcxt_set_... */
+	int		fd;		/* open(/dev/looo<N>) */
+	int		mode;		/* fd mode O_{RDONLY,RDWR} */
+
+	int		flags;		/* LOOPDEV_FL_* flags */
+	unsigned int	has_info:1;	/* .info contains data */
+	unsigned int	extra_check:1;	/* unusual stuff for iterator */
+	unsigned int	info_failed:1;	/* LOOP_GET_STATUS ioctl failed */
+	unsigned int    control_ok:1;	/* /dev/loop-control success */
+
+	struct sysfs_cxt	sysfs;	/* pointer to /sys/dev/block/<maj:min>/ */
+	struct loop_info64	info;	/* for GET/SET ioctl */
+	struct loopdev_iter	iter;	/* scans /sys or /dev for used/free devices */
+};
+
+#define UL_LOOPDEVCXT_EMPTY { .fd = -1, .sysfs = UL_SYSFSCXT_EMPTY }
+
+/*
+ * loopdev_cxt.flags
+ */
+enum {
+	LOOPDEV_FL_RDONLY	= (1 << 0),	/* open(/dev/loop) mode; default */
+	LOOPDEV_FL_RDWR		= (1 << 1),	/* necessary for loop setup only */
+	LOOPDEV_FL_OFFSET	= (1 << 4),
+	LOOPDEV_FL_NOSYSFS	= (1 << 5),
+	LOOPDEV_FL_NOIOCTL	= (1 << 6),
+	LOOPDEV_FL_DEVSUBDIR	= (1 << 7),
+	LOOPDEV_FL_CONTROL	= (1 << 8),	/* system with /dev/loop-control */
+	LOOPDEV_FL_SIZELIMIT	= (1 << 9)
+};
+
+/*
+ * High-level
+ */
+extern int loopmod_supports_partscan(void);
+
+extern int is_loopdev(const char *device);
+extern int loopdev_is_autoclear(const char *device);
+
+extern char *loopdev_get_backing_file(const char *device);
+extern int loopdev_is_used(const char *device, const char *filename,
+			   uint64_t offset, int flags);
+extern char *loopdev_find_by_backing_file(const char *filename,
+					  uint64_t offset, int flags);
+extern int loopcxt_find_unused(struct loopdev_cxt *lc);
+extern int loopdev_delete(const char *device);
+extern int loopdev_count_by_backing_file(const char *filename, char **loopdev);
+
+/*
+ * Low-level
+ */
+extern int loopcxt_init(struct loopdev_cxt *lc, int flags)
+				__attribute__ ((warn_unused_result));
+extern void loopcxt_deinit(struct loopdev_cxt *lc);
+
+extern int loopcxt_set_device(struct loopdev_cxt *lc, const char *device)
+				__attribute__ ((warn_unused_result));
+extern int loopcxt_has_device(struct loopdev_cxt *lc);
+extern int loopcxt_add_device(struct loopdev_cxt *lc);
+extern char *loopcxt_strdup_device(struct loopdev_cxt *lc);
+extern const char *loopcxt_get_device(struct loopdev_cxt *lc);
+extern struct sysfs_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc);
+extern struct loop_info64 *loopcxt_get_info(struct loopdev_cxt *lc);
+
+extern int loopcxt_get_fd(struct loopdev_cxt *lc);
+extern int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode);
+
+extern int loopcxt_init_iterator(struct loopdev_cxt *lc, int flags);
+extern int loopcxt_deinit_iterator(struct loopdev_cxt *lc);
+extern int loopcxt_next(struct loopdev_cxt *lc);
+
+extern int loopcxt_setup_device(struct loopdev_cxt *lc);
+extern int loopcxt_delete_device(struct loopdev_cxt *lc);
+extern int loopcxt_set_capacity(struct loopdev_cxt *lc);
+
+int loopcxt_set_offset(struct loopdev_cxt *lc, uint64_t offset);
+int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit);
+int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags);
+int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename);
+
+extern char *loopcxt_get_backing_file(struct loopdev_cxt *lc);
+extern int loopcxt_get_backing_devno(struct loopdev_cxt *lc, dev_t *devno);
+extern int loopcxt_get_backing_inode(struct loopdev_cxt *lc, ino_t *ino);
+extern int loopcxt_get_offset(struct loopdev_cxt *lc, uint64_t *offset);
+extern int loopcxt_get_sizelimit(struct loopdev_cxt *lc, uint64_t *size);
+extern int loopcxt_get_encrypt_type(struct loopdev_cxt *lc, uint32_t *type);
+extern const char *loopcxt_get_crypt_name(struct loopdev_cxt *lc);
+extern int loopcxt_is_autoclear(struct loopdev_cxt *lc);
+extern int loopcxt_is_readonly(struct loopdev_cxt *lc);
+extern int loopcxt_is_partscan(struct loopdev_cxt *lc);
+extern int loopcxt_find_by_backing_file(struct loopdev_cxt *lc,
+				const char *filename,
+                                uint64_t offset, int flags);
+
+extern int loopcxt_is_used(struct loopdev_cxt *lc,
+                    struct stat *st,
+                    const char *backing_file,
+                    uint64_t offset,
+                    int flags);
+
+#endif /* UTIL_LINUX_LOOPDEV_H */
diff --git a/libblkid/src/mangle.h b/libblkid/src/mangle.h
new file mode 100644
index 0000000..ec492b5
--- /dev/null
+++ b/libblkid/src/mangle.h
@@ -0,0 +1,26 @@
+#ifndef UTIL_LINUX_MANGLE_H
+#define UTIL_LINUX_MANGLE_H
+
+/*
+ * Functions for \oct encoding used in mtab/fstab/swaps/etc.
+ */
+
+extern char *mangle(const char *s);
+
+extern void unmangle_to_buffer(const char *s, char *buf, size_t len);
+void unhexmangle_to_buffer(const char *s, char *buf, size_t len);
+
+extern char *unmangle(const char *s, char **end);
+
+static inline void unmangle_string(char *s)
+{
+	unmangle_to_buffer(s, s, strlen(s) + 1);
+}
+
+static inline void unhexmangle_string(char *s)
+{
+	unhexmangle_to_buffer(s, s, strlen(s) + 1);
+}
+
+#endif /* UTIL_LINUX_MANGLE_H */
+
diff --git a/libblkid/src/match.h b/libblkid/src/match.h
new file mode 100644
index 0000000..94440c2
--- /dev/null
+++ b/libblkid/src/match.h
@@ -0,0 +1,12 @@
+/*
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef UTIL_LINUX_MATCH_H
+#define UTIL_LINUX_MATCH_H
+
+extern int match_fstype(const char *type, const char *pattern);
+
+#endif /* UTIL_LINUX_MATCH_H */
diff --git a/libblkid/src/mbsalign.h b/libblkid/src/mbsalign.h
new file mode 100644
index 0000000..5eaf606
--- /dev/null
+++ b/libblkid/src/mbsalign.h
@@ -0,0 +1,56 @@
+/* Align/Truncate a string in a given screen width
+   Copyright (C) 2009-2010 Free Software Foundation, Inc.
+   Copyright (C) 2010-2013 Karel Zak <kzak@redhat.com>
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as published by
+   the Free Software Foundation, either version 2.1 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   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, see <http://www.gnu.org/licenses/>.  */
+#ifndef UTIL_LINUX_MBSALIGN_H
+# define UTIL_LINUX_MBSALIGN_H
+# include <stddef.h>
+
+typedef enum { MBS_ALIGN_LEFT, MBS_ALIGN_RIGHT, MBS_ALIGN_CENTER } mbs_align_t;
+
+enum {
+  /* Use unibyte mode for invalid multibyte strings or
+     or when heap memory is exhausted.  */
+  MBA_UNIBYTE_FALLBACK = 0x0001,
+
+#if 0 /* Other possible options.  */
+  /* Skip invalid multibyte chars rather than failing  */
+  MBA_IGNORE_INVALID   = 0x0002,
+
+  /* Align multibyte strings using "figure space" (\u2007)  */
+  MBA_USE_FIGURE_SPACE = 0x0004,
+
+  /* Don't add any padding  */
+  MBA_TRUNCATE_ONLY    = 0x0008,
+
+  /* Don't truncate  */
+  MBA_PAD_ONLY         = 0x0010,
+#endif
+};
+
+extern size_t mbs_truncate(char *str, size_t *width);
+
+extern size_t mbsalign (const char *src, char *dest,
+			size_t dest_size,  size_t *width,
+			mbs_align_t align, int flags);
+
+extern size_t mbs_safe_nwidth(const char *buf, size_t bufsz, size_t *sz);
+extern size_t mbs_safe_width(const char *s);
+
+extern char *mbs_safe_encode(const char *s, size_t *width);
+extern char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf);
+extern size_t mbs_safe_encode_size(size_t bytes);
+
+#endif /* UTIL_LINUX_MBSALIGN_H */
diff --git a/libblkid/src/md5.h b/libblkid/src/md5.h
new file mode 100644
index 0000000..d997e37
--- /dev/null
+++ b/libblkid/src/md5.h
@@ -0,0 +1,29 @@
+#ifndef MD5_H
+#define MD5_H
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#else
+typedef unsigned int uint32_t;
+#endif
+
+#define MD5LENGTH 16
+
+struct MD5Context {
+	uint32_t buf[4];
+	uint32_t bits[2];
+	unsigned char in[64];
+};
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, unsigned char const *buf,
+	       unsigned len);
+void MD5Final(unsigned char digest[MD5LENGTH], struct MD5Context *context);
+void MD5Transform(uint32_t buf[4], uint32_t const in[16]);
+
+/*
+ * This is needed to make RSAREF happy on some MS-DOS compilers.
+ */
+typedef struct MD5Context MD5_CTX;
+
+#endif /* !MD5_H */
diff --git a/libblkid/src/nls.h b/libblkid/src/nls.h
new file mode 100644
index 0000000..3eabfe6
--- /dev/null
+++ b/libblkid/src/nls.h
@@ -0,0 +1,115 @@
+#ifndef UTIL_LINUX_NLS_H
+#define UTIL_LINUX_NLS_H
+
+int main(int argc, char *argv[]);
+
+#ifndef LOCALEDIR
+#define LOCALEDIR "/usr/share/locale"
+#endif
+
+#ifdef HAVE_LOCALE_H
+# include <locale.h>
+#else
+# undef setlocale
+# define setlocale(Category, Locale) /* empty */
+struct lconv
+{
+	char *decimal_point;
+};
+# undef localeconv
+# define localeconv() NULL
+#endif
+
+#ifdef ENABLE_NLS
+# include <libintl.h>
+# define _(Text) gettext (Text)
+# ifdef gettext_noop
+#  define N_(String) gettext_noop (String)
+# else
+#  define N_(String) (String)
+# endif
+# define P_(Singular, Plural, n) ngettext (Singular, Plural, n)
+#else
+# undef bindtextdomain
+# define bindtextdomain(Domain, Directory) /* empty */
+# undef textdomain
+# define textdomain(Domain) /* empty */
+# define _(Text) (Text)
+# define N_(Text) (Text)
+# define P_(Singular, Plural, n) ((n) == 1 ? (Singular) : (Plural))
+#endif
+
+#ifdef HAVE_LANGINFO_H
+# include <langinfo.h>
+#else
+
+typedef int nl_item;
+extern char *langinfo_fallback(nl_item item);
+
+# define nl_langinfo	langinfo_fallback
+
+enum {
+	CODESET = 1,
+	RADIXCHAR,
+	THOUSEP,
+	D_T_FMT,
+	D_FMT,
+	T_FMT,
+	T_FMT_AMPM,
+	AM_STR,
+	PM_STR,
+
+	DAY_1,
+	DAY_2,
+	DAY_3,
+	DAY_4,
+	DAY_5,
+	DAY_6,
+	DAY_7,
+
+	ABDAY_1,
+	ABDAY_2,
+	ABDAY_3,
+	ABDAY_4,
+	ABDAY_5,
+	ABDAY_6,
+	ABDAY_7,
+
+	MON_1,
+	MON_2,
+	MON_3,
+	MON_4,
+	MON_5,
+	MON_6,
+	MON_7,
+	MON_8,
+	MON_9,
+	MON_10,
+	MON_11,
+	MON_12,
+
+	ABMON_1,
+	ABMON_2,
+	ABMON_3,
+	ABMON_4,
+	ABMON_5,
+	ABMON_6,
+	ABMON_7,
+	ABMON_8,
+	ABMON_9,
+	ABMON_10,
+	ABMON_11,
+	ABMON_12,
+
+	ERA_D_FMT,
+	ERA_D_T_FMT,
+	ERA_T_FMT,
+	ALT_DIGITS,
+	CRNCYSTR,
+	YESEXPR,
+	NOEXPR
+};
+
+#endif /* !HAVE_LANGINFO_H */
+
+#endif /* UTIL_LINUX_NLS_H */
diff --git a/libblkid/src/partitions/aix.c b/libblkid/src/partitions/aix.c
new file mode 100644
index 0000000..4efdfa3
--- /dev/null
+++ b/libblkid/src/partitions/aix.c
@@ -0,0 +1,57 @@
+/*
+ * aix partitions
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+#include "aix.h"
+
+static int probe_aix_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	blkid_partlist ls;
+	blkid_parttable tab;
+
+	if (blkid_partitions_need_typeonly(pr))
+		/* caller does not ask for details about partitions */
+		return BLKID_PROBE_OK;
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		return BLKID_PROBE_NONE;
+
+	tab = blkid_partlist_new_parttable(ls, "aix", 0);
+	if (!tab)
+		return -ENOMEM;
+
+	return BLKID_PROBE_OK;
+}
+
+/*
+ * We know nothing about AIX on-disk structures. Everything what we know is the
+ * magic number at begin of the disk.
+ *
+ * Note, Linux kernel is tring to be smart and AIX signature is ignored when
+ * there is a valid DOS partitions table. We don't support such behavior. All
+ * fdisk-like programs has to properly wipe the fist sector. Everything other
+ * is a bug.
+ */
+const struct blkid_idinfo aix_pt_idinfo =
+{
+	.name		= "aix",
+	.probefunc	= probe_aix_pt,
+	.magics		=
+	{
+		{ .magic = BLKID_AIX_MAGIC_STRING, .len = BLKID_AIX_MAGIC_STRLEN },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/partitions/aix.h b/libblkid/src/partitions/aix.h
new file mode 100644
index 0000000..f767c5a
--- /dev/null
+++ b/libblkid/src/partitions/aix.h
@@ -0,0 +1,7 @@
+#ifndef BLKID_PARTITIONS_AIX_H
+#define BLKID_PARTITIONS_AIX_H
+
+#define BLKID_AIX_MAGIC_STRING	"\xC9\xC2\xD4\xC1"
+#define BLKID_AIX_MAGIC_STRLEN	(sizeof(BLKID_AIX_MAGIC_STRING) - 1)
+
+#endif
diff --git a/libblkid/src/partitions/bsd.c b/libblkid/src/partitions/bsd.c
new file mode 100644
index 0000000..d83f2cf
--- /dev/null
+++ b/libblkid/src/partitions/bsd.c
@@ -0,0 +1,179 @@
+/*
+ * BSD/OSF partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Inspired by fdisk, partx, Linux kernel, libparted and openbsd header files.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+#include "pt-bsd.h"
+
+/* Returns 'blkid_idmag' in 512-sectors */
+#define BLKID_MAG_SECTOR(_mag)  (((_mag)->kboff / 2)  + ((_mag)->sboff >> 9))
+
+/* Returns 'blkid_idmag' in bytes */
+#define BLKID_MAG_OFFSET(_mag)  ((_mag)->kboff << 10) + ((_mag)->sboff)
+
+/* Returns 'blkid_idmag' offset in bytes within the last sector */
+#define BLKID_MAG_LASTOFFSET(_mag) \
+		 (BLKID_MAG_OFFSET(_mag) - (BLKID_MAG_SECTOR(_mag) << 9))
+
+static int probe_bsd_pt(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct bsd_disklabel *l;
+	struct bsd_partition *p;
+	const char *name = "bsd" ;
+	blkid_parttable tab = NULL;
+	blkid_partition parent;
+	blkid_partlist ls;
+	int i, nparts = BSD_MAXPARTITIONS;
+	unsigned char *data;
+	int rc = BLKID_PROBE_NONE;
+
+	if (blkid_partitions_need_typeonly(pr))
+		/* caller does not ask for details about partitions */
+		return rc;
+
+	data = blkid_probe_get_sector(pr, BLKID_MAG_SECTOR(mag));
+	if (!data) {
+		if (errno)
+			rc = -errno;
+		goto nothing;
+	}
+
+	l = (struct bsd_disklabel *) data + BLKID_MAG_LASTOFFSET(mag);
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		goto nothing;
+
+	/* try to determine the real type of BSD system according to
+	 * (parental) primary partition */
+	parent = blkid_partlist_get_parent(ls);
+	if (parent) {
+		switch(blkid_partition_get_type(parent)) {
+		case MBR_FREEBSD_PARTITION:
+			name = "freebsd";
+			break;
+		case MBR_NETBSD_PARTITION:
+			name = "netbsd";
+			break;
+		case MBR_OPENBSD_PARTITION:
+			name = "openbsd";
+			break;
+		default:
+			DBG(LOWPROBE, ul_debug(
+				"WARNING: BSD label detected on unknown (0x%x) "
+				"primary partition",
+				blkid_partition_get_type(parent)));
+			break;
+		}
+	}
+
+	tab = blkid_partlist_new_parttable(ls, name, BLKID_MAG_OFFSET(mag));
+	if (!tab) {
+		rc = -ENOMEM;
+		goto nothing;
+	}
+
+	if (le16_to_cpu(l->d_npartitions) < BSD_MAXPARTITIONS)
+		nparts = le16_to_cpu(l->d_npartitions);
+
+	else if (le16_to_cpu(l->d_npartitions) > BSD_MAXPARTITIONS)
+		DBG(LOWPROBE, ul_debug(
+			"WARNING: ignore %d more BSD partitions",
+			le16_to_cpu(l->d_npartitions) - BSD_MAXPARTITIONS));
+
+	for (i = 0, p = l->d_partitions; i < nparts; i++, p++) {
+		blkid_partition par;
+		uint32_t start, size;
+
+		/* TODO: in fdisk-mode returns all non-zero (p_size) partitions */
+		if (p->p_fstype == BSD_FS_UNUSED)
+			continue;
+
+		start = le32_to_cpu(p->p_offset);
+		size = le32_to_cpu(p->p_size);
+
+		if (parent && blkid_partition_get_start(parent) == start
+			   && blkid_partition_get_size(parent) == size) {
+			DBG(LOWPROBE, ul_debug(
+				"WARNING: BSD partition (%d) same like parent, "
+				"ignore", i));
+			continue;
+		}
+		if (parent && !blkid_is_nested_dimension(parent, start, size)) {
+			DBG(LOWPROBE, ul_debug(
+				"WARNING: BSD partition (%d) overflow "
+				"detected, ignore", i));
+			continue;
+		}
+
+		par = blkid_partlist_add_partition(ls, tab, start, size);
+		if (!par) {
+			rc = -ENOMEM;
+			goto nothing;
+		}
+
+		blkid_partition_set_type(par, p->p_fstype);
+	}
+
+	return BLKID_PROBE_OK;
+
+nothing:
+	return rc;
+}
+
+
+/*
+ * All BSD variants use the same magic string (little-endian),
+ * and the same disklabel.
+ *
+ * The difference between {Free,Open,...}BSD is in the (parental)
+ * primary partition type.
+ *
+ * See also: http://en.wikipedia.org/wiki/BSD_disklabel
+ *
+ * The location of BSD disk label is architecture specific and in defined by
+ * LABELSECTOR and LABELOFFSET macros in the disklabel.h file. The location
+ * also depends on BSD variant, FreeBSD uses only one location, NetBSD and
+ * OpenBSD are more creative...
+ *
+ * The basic overview:
+ *
+ * arch                    | LABELSECTOR | LABELOFFSET
+ * ------------------------+-------------+------------
+ * amd64 arm hppa hppa64   |             |
+ * i386, macppc, mvmeppc   |      1      |      0
+ * sgi, aviion, sh, socppc |             |
+ * ------------------------+-------------+------------
+ * alpha luna88k mac68k    |      0      |     64
+ * sparc(OpenBSD) vax      |             |
+ * ------------------------+-------------+------------
+ * sparc64 sparc(NetBSD)   |      0      |    128
+ * ------------------------+-------------+------------
+ *
+ * ...and more (see http://fxr.watson.org/fxr/ident?v=NETBSD;i=LABELSECTOR)
+ *
+ */
+const struct blkid_idinfo bsd_pt_idinfo =
+{
+	.name		= "bsd",
+	.probefunc	= probe_bsd_pt,
+	.magics		=
+	{
+		{ .magic = "\x57\x45\x56\x82", .len = 4, .sboff = 512 },
+		{ .magic = "\x57\x45\x56\x82", .len = 4, .sboff = 64 },
+		{ .magic = "\x57\x45\x56\x82", .len = 4, .sboff = 128 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/partitions/dos.c b/libblkid/src/partitions/dos.c
new file mode 100644
index 0000000..2539908
--- /dev/null
+++ b/libblkid/src/partitions/dos.c
@@ -0,0 +1,307 @@
+/*
+ * MS-DOS partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Inspired by fdisk, partx, Linux kernel and libparted.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+#include "aix.h"
+
+/* see superblocks/vfat.c */
+extern int blkid_probe_is_vfat(blkid_probe pr);
+
+static const struct dos_subtypes {
+	unsigned char type;
+	const struct blkid_idinfo *id;
+} dos_nested[] = {
+	{ MBR_FREEBSD_PARTITION, &bsd_pt_idinfo },
+	{ MBR_NETBSD_PARTITION, &bsd_pt_idinfo },
+	{ MBR_OPENBSD_PARTITION, &bsd_pt_idinfo },
+	{ MBR_UNIXWARE_PARTITION, &unixware_pt_idinfo },
+	{ MBR_SOLARIS_X86_PARTITION, &solaris_x86_pt_idinfo },
+	{ MBR_MINIX_PARTITION, &minix_pt_idinfo }
+};
+
+static inline int is_extended(struct dos_partition *p)
+{
+	return (p->sys_ind == MBR_DOS_EXTENDED_PARTITION ||
+		p->sys_ind == MBR_W95_EXTENDED_PARTITION ||
+		p->sys_ind == MBR_LINUX_EXTENDED_PARTITION);
+}
+
+static int parse_dos_extended(blkid_probe pr, blkid_parttable tab,
+		uint32_t ex_start, uint32_t ex_size, int ssf)
+{
+	blkid_partlist ls = blkid_probe_get_partlist(pr);
+	uint32_t cur_start = ex_start, cur_size = ex_size;
+	unsigned char *data;
+	int ct_nodata = 0;	/* count ext.partitions without data partitions */
+	int i;
+
+	while (1) {
+		struct dos_partition *p, *p0;
+		uint32_t start, size;
+
+		if (++ct_nodata > 100)
+			return BLKID_PROBE_OK;
+		data = blkid_probe_get_sector(pr, cur_start);
+		if (!data) {
+			if (errno)
+				return -errno;
+			goto leave;	/* malformed partition? */
+		}
+
+		if (!mbr_is_valid_magic(data))
+			goto leave;
+
+		p0 = mbr_get_partition(data, 0);
+
+		/* Usually, the first entry is the real data partition,
+		 * the 2nd entry is the next extended partition, or empty,
+		 * and the 3rd and 4th entries are unused.
+		 * However, DRDOS sometimes has the extended partition as
+		 * the first entry (when the data partition is empty),
+		 * and OS/2 seems to use all four entries.
+		 * -- Linux kernel fs/partitions/dos.c
+		 *
+		 * See also http://en.wikipedia.org/wiki/Extended_boot_record
+		 */
+
+		/* Parse data partition */
+		for (p = p0, i = 0; i < 4; i++, p++) {
+			uint32_t abs_start;
+			blkid_partition par;
+
+			/* the start is relative to the parental ext.partition */
+			start = dos_partition_get_start(p) * ssf;
+			size = dos_partition_get_size(p) * ssf;
+			abs_start = cur_start + start;	/* absolute start */
+
+			if (!size || is_extended(p))
+				continue;
+			if (i >= 2) {
+				/* extra checks to detect real data on
+				 * 3rd and 4th entries */
+				if (start + size > cur_size)
+					continue;
+				if (abs_start < ex_start)
+					continue;
+				if (abs_start + size > ex_start + ex_size)
+					continue;
+			}
+
+			par = blkid_partlist_add_partition(ls, tab, abs_start, size);
+			if (!par)
+				return -ENOMEM;
+
+			blkid_partition_set_type(par, p->sys_ind);
+			blkid_partition_set_flags(par, p->boot_ind);
+			blkid_partition_gen_uuid(par);
+			ct_nodata = 0;
+		}
+		/* The first nested ext.partition should be a link to the next
+		 * logical partition. Everything other (recursive ext.partitions)
+		 * is junk.
+		 */
+		for (p = p0, i = 0; i < 4; i++, p++) {
+			start = dos_partition_get_start(p) * ssf;
+			size = dos_partition_get_size(p) * ssf;
+
+			if (size && is_extended(p))
+				break;
+		}
+		if (i == 4)
+			goto leave;
+
+		cur_start = ex_start + start;
+		cur_size = size;
+	}
+leave:
+	return BLKID_PROBE_OK;
+}
+
+static int probe_dos_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	int i;
+	int ssf;
+	blkid_parttable tab = NULL;
+	blkid_partlist ls;
+	struct dos_partition *p0, *p;
+	unsigned char *data;
+	uint32_t start, size, id;
+	char idstr[37];
+
+
+	data = blkid_probe_get_sector(pr, 0);
+	if (!data) {
+		if (errno)
+			return -errno;
+		goto nothing;
+	}
+
+	/* ignore disks with AIX magic number -- for more details see aix.c */
+	if (memcmp(data, BLKID_AIX_MAGIC_STRING, BLKID_AIX_MAGIC_STRLEN) == 0)
+		goto nothing;
+
+	/*
+	 * Now that the 55aa signature is present, this is probably
+	 * either the boot sector of a FAT filesystem or a DOS-type
+	 * partition table.
+	 */
+	if (blkid_probe_is_vfat(pr) == 1) {
+		DBG(LOWPROBE, ul_debug("probably FAT -- ignore"));
+		goto nothing;
+	}
+
+	p0 = mbr_get_partition(data, 0);
+
+	/*
+	 * Reject PT where boot indicator is not 0 or 0x80.
+	 */
+	for (p = p0, i = 0; i < 4; i++, p++)
+		if (p->boot_ind != 0 && p->boot_ind != 0x80) {
+			DBG(LOWPROBE, ul_debug("missing boot indicator -- ignore"));
+			goto nothing;
+		}
+
+	/*
+	 * GPT uses valid MBR
+	 */
+	for (p = p0, i = 0; i < 4; i++, p++) {
+		if (p->sys_ind == MBR_GPT_PARTITION) {
+			DBG(LOWPROBE, ul_debug("probably GPT -- ignore"));
+			goto nothing;
+		}
+	}
+
+	blkid_probe_use_wiper(pr, MBR_PT_OFFSET, 512 - MBR_PT_OFFSET);
+
+	id = mbr_get_id(data);
+	if (id)
+		snprintf(idstr, sizeof(idstr), "%08x", id);
+
+	/*
+	 * Well, all checks pass, it's MS-DOS partiton table
+	 */
+	if (blkid_partitions_need_typeonly(pr)) {
+		/* Non-binary interface -- caller does not ask for details
+		 * about partitions, just set generic varibles only. */
+		if (id)
+			blkid_partitions_strcpy_ptuuid(pr, idstr);
+		return 0;
+	}
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		goto nothing;
+
+	/* sector size factor (the start and size are in the real sectors, but
+	 * we need to convert all sizes to 512 logical sectors
+	 */
+	ssf = blkid_probe_get_sectorsize(pr) / 512;
+
+	/* allocate a new partition table */
+	tab = blkid_partlist_new_parttable(ls, "dos", MBR_PT_OFFSET);
+	if (!tab)
+		return -ENOMEM;
+
+	if (id)
+		blkid_parttable_set_id(tab, (unsigned char *) idstr);
+
+	/* Parse primary partitions */
+	for (p = p0, i = 0; i < 4; i++, p++) {
+		blkid_partition par;
+
+		start = dos_partition_get_start(p) * ssf;
+		size = dos_partition_get_size(p) * ssf;
+
+		if (!size) {
+			/* Linux kernel ignores empty partitions, but partno for
+			 * the empty primary partitions is not reused */
+			blkid_partlist_increment_partno(ls);
+			continue;
+		}
+		par = blkid_partlist_add_partition(ls, tab, start, size);
+		if (!par)
+			return -ENOMEM;
+
+		blkid_partition_set_type(par, p->sys_ind);
+		blkid_partition_set_flags(par, p->boot_ind);
+		blkid_partition_gen_uuid(par);
+	}
+
+	/* Linux uses partition numbers greater than 4
+	 * for all logical partition and all nested partition tables (bsd, ..)
+	 */
+	blkid_partlist_set_partno(ls, 5);
+
+	/* Parse logical partitions */
+	for (p = p0, i = 0; i < 4; i++, p++) {
+		start = dos_partition_get_start(p) * ssf;
+		size = dos_partition_get_size(p) * ssf;
+
+		if (!size)
+			continue;
+		if (is_extended(p) &&
+		    parse_dos_extended(pr, tab, start, size, ssf) == -1)
+			goto nothing;
+	}
+
+	/* Parse subtypes (nested partitions) on large disks */
+	if (!blkid_probe_is_tiny(pr)) {
+		for (p = p0, i = 0; i < 4; i++, p++) {
+			size_t n;
+			int rc;
+
+			if (!dos_partition_get_size(p) || is_extended(p))
+				continue;
+
+			for (n = 0; n < ARRAY_SIZE(dos_nested); n++) {
+				if (dos_nested[n].type != p->sys_ind)
+					continue;
+
+				rc = blkid_partitions_do_subprobe(pr,
+						blkid_partlist_get_partition(ls, i),
+						dos_nested[n].id);
+				if (rc < 0)
+					return rc;
+				break;
+			}
+		}
+	}
+	return BLKID_PROBE_OK;
+
+nothing:
+	return BLKID_PROBE_NONE;
+}
+
+
+const struct blkid_idinfo dos_pt_idinfo =
+{
+	.name		= "dos",
+	.probefunc	= probe_dos_pt,
+	.magics		=
+	{
+		/* DOS master boot sector:
+		 *
+		 *     0 | Code Area
+		 *   440 | Optional Disk signature
+		 *   446 | Partition table
+		 *   510 | 0x55
+		 *   511 | 0xAA
+		 */
+		{ .magic = "\x55\xAA", .len = 2, .sboff = 510 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/partitions/gpt.c b/libblkid/src/partitions/gpt.c
new file mode 100644
index 0000000..665577f
--- /dev/null
+++ b/libblkid/src/partitions/gpt.c
@@ -0,0 +1,470 @@
+/*
+ * EFI GPT partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * This code is not copy & past from any other implementation.
+ *
+ * For more information about GPT start your study at:
+ * http://en.wikipedia.org/wiki/GUID_Partition_Table
+ * http://technet.microsoft.com/en-us/library/cc739412(WS.10).aspx
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <limits.h>
+
+#include "partitions.h"
+#include "crc32.h"
+
+#define GPT_PRIMARY_LBA	1
+
+/* Signature - “EFI PART” */
+#define GPT_HEADER_SIGNATURE 0x5452415020494645ULL
+#define GPT_HEADER_SIGNATURE_STR "EFI PART"
+
+/* basic types */
+typedef uint16_t efi_char16_t;
+
+/* UUID */
+typedef struct {
+	uint32_t time_low;
+	uint16_t time_mid;
+	uint16_t time_hi_and_version;
+	uint8_t clock_seq_hi;
+	uint8_t clock_seq_low;
+	uint8_t node[6];
+} efi_guid_t;
+
+
+#define GPT_UNUSED_ENTRY_GUID \
+	    ((efi_guid_t) { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, \
+	                    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }})
+struct gpt_header {
+	uint64_t	signature;		/* "EFI PART" */
+	uint32_t	revision;
+	uint32_t	header_size;		/* usually 92 bytes */
+	uint32_t	header_crc32;		/* checksum of header with this
+						 * field zeroed during calculation */
+	uint32_t	reserved1;
+
+	uint64_t	my_lba;			/* location of this header copy */
+	uint64_t	alternate_lba;		/* location of the other header copy */
+	uint64_t	first_usable_lba;	/* lirst usable LBA for partitions */
+	uint64_t	last_usable_lba;	/* last usable LBA for partitions */
+
+	efi_guid_t	disk_guid;		/* disk UUID */
+
+	uint64_t	partition_entries_lba;	/* always 2 in primary header copy */
+	uint32_t	num_partition_entries;
+	uint32_t	sizeof_partition_entry;
+	uint32_t	partition_entry_array_crc32;
+
+	/*
+	 * The rest of the block is reserved by UEFI and must be zero. EFI
+	 * standard handles this by:
+	 *
+	 * uint8_t		reserved2[ BLKSSZGET - 92 ];
+	 *
+	 * This definition is useless in practice. It is necessary to read
+	 * whole block from the device rather than sizeof(struct gpt_header)
+	 * only.
+	 */
+} __attribute__ ((packed));
+
+/*** not used
+struct gpt_entry_attributes {
+	uint64_t	required_to_function:1;
+	uint64_t	reserved:47;
+        uint64_t	type_guid_specific:16;
+} __attribute__ ((packed));
+***/
+
+struct gpt_entry {
+	efi_guid_t	partition_type_guid;	/* type UUID */
+	efi_guid_t	unique_partition_guid;	/* partition UUID */
+	uint64_t	starting_lba;
+	uint64_t	ending_lba;
+
+	/*struct gpt_entry_attributes	attributes;*/
+
+	uint64_t	attributes;
+
+	efi_char16_t	partition_name[72 / sizeof(efi_char16_t)]; /* UTF-16LE string*/
+} __attribute__ ((packed));
+
+
+/*
+ * EFI uses crc32 with ~0 seed and xor's with ~0 at the end.
+ */
+static inline uint32_t count_crc32(const unsigned char *buf, size_t len)
+{
+	return (crc32(~0L, buf, len) ^ ~0L);
+}
+
+static inline unsigned char *get_lba_buffer(blkid_probe pr,
+					uint64_t lba, size_t bytes)
+{
+	return blkid_probe_get_buffer(pr,
+			blkid_probe_get_sectorsize(pr) * lba, bytes);
+}
+
+static inline int guidcmp(efi_guid_t left, efi_guid_t right)
+{
+	return memcmp(&left, &right, sizeof (efi_guid_t));
+}
+
+/*
+ * UUID is traditionally 16 byte big-endian array, except Intel EFI
+ * specification where the UUID is a structure of little-endian fields.
+ */
+static void swap_efi_guid(efi_guid_t *uid)
+{
+	uid->time_low = swab32(uid->time_low);
+	uid->time_mid = swab16(uid->time_mid);
+	uid->time_hi_and_version = swab16(uid->time_hi_and_version);
+}
+
+static int last_lba(blkid_probe pr, uint64_t *lba)
+{
+	blkid_loff_t sz = blkid_probe_get_size(pr);
+	unsigned int ssz = blkid_probe_get_sectorsize(pr);
+
+	if (sz < ssz)
+		return -1;
+
+	*lba = (sz / ssz) - 1ULL;
+	return 0;
+}
+
+/*
+ * Protective (legacy) MBR.
+ *
+ * This MBR contains standard DOS partition table with a single partition, type
+ * of 0xEE.  The partition usually encompassing the entire GPT drive - or 2TiB
+ * for large disks.
+ *
+ * Note that Apple uses GPT/MBR hybrid disks, where the DOS partition table is
+ * synchronized with GPT. This synchronization has many restriction of course
+ * (due DOS PT limitations).
+ *
+ * Note that the PMBR detection is optional (enabled by default) and could be
+ * disabled by BLKID_PARTS_FOPCE_GPT flag (see also blkid_paertitions_set_flags()).
+ */
+static int is_pmbr_valid(blkid_probe pr, int *has)
+{
+	int flags = blkid_partitions_get_flags(pr);
+	unsigned char *data;
+	struct dos_partition *p;
+	int i;
+
+	if (has)
+		*has = 0;
+	if (flags & BLKID_PARTS_FORCE_GPT)
+		goto ok;			/* skip PMBR check */
+
+	data = blkid_probe_get_sector(pr, 0);
+	if (!data) {
+		if (errno)
+			return -errno;
+		goto failed;
+	}
+
+	if (!mbr_is_valid_magic(data))
+		goto failed;
+
+	for (i = 0, p = mbr_get_partition(data, 0); i < 4; i++, p++) {
+		if (p->sys_ind == MBR_GPT_PARTITION)
+			goto ok;
+	}
+failed:
+	return 0;
+ok:
+	if (has)
+		*has = 1;
+	return 1;
+}
+
+/*
+ * Reads GPT header to @hdr and returns a pointer to @hdr or NULL in case of
+ * error. The function also returns GPT entries in @ents.
+ *
+ * Note, this function does not allocate any memory. The GPT header has fixed
+ * size so we use stack, and @ents returns memory from libblkid buffer (so the
+ * next blkid_probe_get_buffer() will overwrite this buffer).
+ *
+ * This function checks validity of header and entries array. A corrupted
+ * header is not returned.
+ */
+static struct gpt_header *get_gpt_header(
+				blkid_probe pr, struct gpt_header *hdr,
+				struct gpt_entry **ents, uint64_t lba,
+				uint64_t lastlba)
+{
+	struct gpt_header *h;
+	uint32_t crc, orgcrc;
+	uint64_t lu, fu;
+	size_t esz;
+	uint32_t hsz, ssz;
+
+	ssz = blkid_probe_get_sectorsize(pr);
+
+	/* whole sector is allocated for GPT header */
+	h = (struct gpt_header *) get_lba_buffer(pr, lba, ssz);
+	if (!h)
+		return NULL;
+
+	if (le64_to_cpu(h->signature) != GPT_HEADER_SIGNATURE)
+		return NULL;
+
+	hsz = le32_to_cpu(h->header_size);
+
+	/* EFI: The HeaderSize must be greater than 92 and must be less
+	 *      than or equal to the logical block size.
+	 */
+	if (hsz > ssz || hsz < sizeof(*h))
+		return NULL;
+
+	/* Header has to be verified when header_crc32 is zero */
+	orgcrc = h->header_crc32;
+	h->header_crc32 = 0;
+	crc = count_crc32((unsigned char *) h, hsz);
+	h->header_crc32 = orgcrc;
+
+	if (crc != le32_to_cpu(orgcrc)) {
+		DBG(LOWPROBE, ul_debug("GPT header corrupted"));
+		return NULL;
+	}
+
+	/* Valid header has to be at MyLBA */
+	if (le64_to_cpu(h->my_lba) != lba) {
+		DBG(LOWPROBE, ul_debug(
+			"GPT->MyLBA mismatch with real position"));
+		return NULL;
+	}
+
+	fu = le64_to_cpu(h->first_usable_lba);
+	lu = le64_to_cpu(h->last_usable_lba);
+
+	/* Check if First and Last usable LBA makes sense */
+	if (lu < fu || fu > lastlba || lu > lastlba) {
+		DBG(LOWPROBE, ul_debug(
+			"GPT->{First,Last}UsableLBA out of range"));
+		return NULL;
+	}
+
+	/* The header has to be outside usable range */
+	if (fu < lba && lba < lu) {
+		DBG(LOWPROBE, ul_debug("GPT header is inside usable area"));
+		return NULL;
+	}
+
+	if (le32_to_cpu(h->num_partition_entries) == 0 ||
+	    le32_to_cpu(h->sizeof_partition_entry) == 0 ||
+	    ULONG_MAX / le32_to_cpu(h->num_partition_entries) < le32_to_cpu(h->sizeof_partition_entry)) {
+		DBG(LOWPROBE, ul_debug("GPT entries undefined"));
+		return NULL;
+	}
+
+	/* Size of blocks with GPT entries */
+	esz = le32_to_cpu(h->num_partition_entries) *
+			le32_to_cpu(h->sizeof_partition_entry);
+
+	/* The header seems valid, save it
+	 * (we don't care about zeros in hdr->reserved2 area) */
+	memcpy(hdr, h, sizeof(*h));
+	h = hdr;
+
+	/* Read GPT entries */
+	*ents = (struct gpt_entry *) get_lba_buffer(pr,
+				le64_to_cpu(h->partition_entries_lba), esz);
+	if (!*ents) {
+		DBG(LOWPROBE, ul_debug("GPT entries unreadable"));
+		return NULL;
+	}
+
+	/* Validate entries */
+	crc = count_crc32((unsigned char *) *ents, esz);
+	if (crc != le32_to_cpu(h->partition_entry_array_crc32)) {
+		DBG(LOWPROBE, ul_debug("GPT entries corrupted"));
+		return NULL;
+	}
+
+	return h;
+}
+
+static int probe_gpt_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	uint64_t lastlba = 0, lba;
+	struct gpt_header hdr, *h;
+	struct gpt_entry *e;
+	blkid_parttable tab = NULL;
+	blkid_partlist ls;
+	uint64_t fu, lu;
+	uint32_t ssf, i;
+	efi_guid_t guid;
+	int ret;
+
+	if (last_lba(pr, &lastlba))
+		goto nothing;
+
+	ret = is_pmbr_valid(pr, NULL);
+	if (ret < 0)
+		return ret;
+	else if (ret == 0)
+		goto nothing;
+
+	errno = 0;
+	h = get_gpt_header(pr, &hdr, &e, (lba = GPT_PRIMARY_LBA), lastlba);
+	if (!h && !errno)
+		h = get_gpt_header(pr, &hdr, &e, (lba = lastlba), lastlba);
+
+	if (!h) {
+		if (errno)
+			return -errno;
+		goto nothing;
+	}
+
+	blkid_probe_use_wiper(pr, lba * blkid_probe_get_size(pr), 8);
+
+	if (blkid_probe_set_magic(pr, blkid_probe_get_sectorsize(pr) * lba,
+			      sizeof(GPT_HEADER_SIGNATURE_STR) - 1,
+			      (unsigned char *) GPT_HEADER_SIGNATURE_STR))
+		goto err;
+
+	guid = h->disk_guid;
+	swap_efi_guid(&guid);
+
+	if (blkid_partitions_need_typeonly(pr)) {
+		/* Non-binary interface -- caller does not ask for details
+		 * about partitions, just set generic varibles only. */
+		blkid_partitions_set_ptuuid(pr, (unsigned char *) &guid);
+		return BLKID_PROBE_OK;
+	}
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		goto nothing;
+
+	tab = blkid_partlist_new_parttable(ls, "gpt",
+				blkid_probe_get_sectorsize(pr) * lba);
+	if (!tab)
+		goto err;
+
+	blkid_parttable_set_uuid(tab, (const unsigned char *) &guid);
+
+	ssf = blkid_probe_get_sectorsize(pr) / 512;
+
+	fu = le64_to_cpu(h->first_usable_lba);
+	lu = le64_to_cpu(h->last_usable_lba);
+
+	for (i = 0; i < le32_to_cpu(h->num_partition_entries); i++, e++) {
+
+		blkid_partition par;
+		uint64_t start = le64_to_cpu(e->starting_lba);
+		uint64_t size = le64_to_cpu(e->ending_lba) -
+					le64_to_cpu(e->starting_lba) + 1ULL;
+
+		/* 00000000-0000-0000-0000-000000000000 entry */
+		if (!guidcmp(e->partition_type_guid, GPT_UNUSED_ENTRY_GUID)) {
+			blkid_partlist_increment_partno(ls);
+			continue;
+		}
+		/* the partition has to inside usable range */
+		if (start < fu || start + size - 1 > lu) {
+			DBG(LOWPROBE, ul_debug(
+				"GPT entry[%d] overflows usable area - ignore",
+				i));
+			blkid_partlist_increment_partno(ls);
+			continue;
+		}
+
+		par = blkid_partlist_add_partition(ls, tab,
+					start * ssf, size * ssf);
+		if (!par)
+			goto err;
+
+		blkid_partition_set_utf8name(par,
+			(unsigned char *) e->partition_name,
+			sizeof(e->partition_name), BLKID_ENC_UTF16LE);
+
+		guid = e->unique_partition_guid;
+		swap_efi_guid(&guid);
+		blkid_partition_set_uuid(par, (const unsigned char *) &guid);
+
+		guid = e->partition_type_guid;
+		swap_efi_guid(&guid);
+		blkid_partition_set_type_uuid(par, (const unsigned char *) &guid);
+
+		blkid_partition_set_flags(par, le64_to_cpu(e->attributes));
+	}
+
+	return BLKID_PROBE_OK;
+
+nothing:
+	return BLKID_PROBE_NONE;
+
+err:
+	return -ENOMEM;
+}
+
+
+const struct blkid_idinfo gpt_pt_idinfo =
+{
+	.name		= "gpt",
+	.probefunc	= probe_gpt_pt,
+	.minsz		= 1024 * 1440 + 1,	/* ignore floppies */
+
+	/*
+	 * It would be possible to check for DOS signature (0xAA55), but
+	 * unfortunately almost all EFI GPT implemenations allow to optionaly
+	 * skip the legacy MBR. We follows this behavior and MBR is optional.
+	 * See is_valid_pmbr().
+	 *
+	 * It means we have to always call probe_gpt_pt().
+	 */
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
+
+/* probe for *alone* protective MBR */
+static int probe_pmbr_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	int has = 0;
+	struct gpt_entry *e;
+	uint64_t lastlba = 0;
+	struct gpt_header hdr;
+
+	if (last_lba(pr, &lastlba))
+		goto nothing;
+
+	is_pmbr_valid(pr, &has);
+	if (!has)
+		goto nothing;
+
+	if (!get_gpt_header(pr, &hdr, &e, GPT_PRIMARY_LBA, lastlba) &&
+	    !get_gpt_header(pr, &hdr, &e, lastlba, lastlba))
+		return 0;
+nothing:
+	return 1;
+}
+
+const struct blkid_idinfo pmbr_pt_idinfo =
+{
+	.name		= "PMBR",
+	.probefunc	= probe_pmbr_pt,
+	.magics		=
+	{
+		{ .magic = "\x55\xAA", .len = 2, .sboff = 510 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/partitions/mac.c b/libblkid/src/partitions/mac.c
new file mode 100644
index 0000000..4282605
--- /dev/null
+++ b/libblkid/src/partitions/mac.c
@@ -0,0 +1,192 @@
+/*
+ * mac partitions parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+
+#define MAC_PARTITION_MAGIC		0x504d
+#define MAC_PARTITION_MAGIC_OLD		0x5453
+
+/*
+ * Mac partition entry
+ * http://developer.apple.com/legacy/mac/library/documentation/mac/Devices/Devices-126.html
+ */
+struct mac_partition {
+	uint16_t	signature;	/* expected to be MAC_PARTITION_MAGIC */
+	uint16_t	reserved;	/* reserved */
+	uint32_t	map_count;	/* # blocks in partition map */
+	uint32_t	start_block;	/* absolute starting block # of partition */
+	uint32_t	block_count;	/* number of blocks in partition */
+	char		name[32];	/* partition name */
+	char		type[32];	/* string type description */
+	uint32_t	data_start;	/* rel block # of first data block */
+	uint32_t	data_count;	/* number of data blocks */
+	uint32_t	status;		/* partition status bits */
+	uint32_t	boot_start;	/* first logical block of boot code */
+	uint32_t	boot_size;	/* size of boot code, in bytes */
+	uint32_t	boot_load;	/* boot code load address */
+	uint32_t	boot_load2;	/* reserved */
+	uint32_t	boot_entry;	/* boot code entry point */
+	uint32_t	boot_entry2;	/* reserved */
+	uint32_t	boot_cksum;	/* boot code checksum */
+	char		processor[16];	/* identifies ISA of boot */
+
+	/* there is more stuff after this that we don't need */
+} __attribute__((packed));
+
+/*
+ * Driver descriptor structure, in block 0
+ * http://developer.apple.com/legacy/mac/library/documentation/mac/Devices/Devices-121.html
+ */
+struct mac_driver_desc {
+	uint16_t	signature;	/* expected to be MAC_DRIVER_MAGIC */
+	uint16_t	block_size;	/* block size of the device */
+	uint32_t	block_count;	/* number of blocks on the device */
+
+	/* there is more stuff after this that we don't need */
+} __attribute__((packed));
+
+static inline unsigned char *get_mac_block(
+					blkid_probe pr,
+					uint16_t block_size,
+					uint32_t num)
+{
+	return blkid_probe_get_buffer(pr,
+			(blkid_loff_t) num * block_size, block_size);
+}
+
+static inline int has_part_signature(struct mac_partition *p)
+{
+	return	be16_to_cpu(p->signature) == MAC_PARTITION_MAGIC ||
+		be16_to_cpu(p->signature) == MAC_PARTITION_MAGIC_OLD;
+}
+
+static int probe_mac_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct mac_driver_desc *md;
+	struct mac_partition *p;
+	blkid_parttable tab = NULL;
+	blkid_partlist ls;
+	uint16_t block_size;
+	uint16_t ssf;	/* sector size fragment */
+	uint32_t nblks, i;
+
+
+	/* The driver descriptor record is always located at physical block 0,
+	 * the first block on the disk.
+	 */
+	md = (struct mac_driver_desc *) blkid_probe_get_sector(pr, 0);
+	if (!md) {
+		if (errno)
+			return -errno;
+		goto nothing;
+	}
+
+	block_size = be16_to_cpu(md->block_size);
+
+	/* The partition map always begins at physical block 1,
+	 * the second block on the disk.
+	 */
+	p = (struct mac_partition *) get_mac_block(pr, block_size, 1);
+	if (!p) {
+		if (errno)
+			return -errno;
+		goto nothing;
+	}
+
+	/* check the first partition signature */
+	if (!has_part_signature(p))
+		goto nothing;
+
+	if (blkid_partitions_need_typeonly(pr))
+		/* caller does not ask for details about partitions */
+		return 0;
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		goto nothing;
+
+	tab = blkid_partlist_new_parttable(ls, "mac", 0);
+	if (!tab)
+		goto err;
+
+	ssf = block_size / 512;
+	nblks = be32_to_cpu(p->map_count);
+
+	for (i = 1; i <= nblks; ++i) {
+		blkid_partition par;
+		uint32_t start;
+		uint32_t size;
+
+		p = (struct mac_partition *) get_mac_block(pr, block_size, i);
+		if (!p) {
+			if (errno)
+				return -errno;
+			goto nothing;
+		}
+		if (!has_part_signature(p))
+			goto nothing;
+
+		if (be32_to_cpu(p->map_count) != nblks) {
+			DBG(LOWPROBE, ul_debug(
+				"mac: inconsisten map_count in partition map, "
+				"entry[0]: %d, entry[%d]: %d",
+				nblks, i - 1,
+				be32_to_cpu(p->map_count)));
+		}
+
+		/*
+		 * note that libparted ignores some mac partitions according to
+		 * the partition name (e.g. "Apple_Free" or "Apple_Void"). We
+		 * follows Linux kernel and all partitions are visible
+		 */
+
+		start = be32_to_cpu(p->start_block) * ssf;
+		size = be32_to_cpu(p->block_count) * ssf;
+
+		par = blkid_partlist_add_partition(ls, tab, start, size);
+		if (!par)
+			goto err;
+
+		blkid_partition_set_name(par, (unsigned char *) p->name,
+						sizeof(p->name));
+
+		blkid_partition_set_type_string(par, (unsigned char *) p->type,
+						sizeof(p->type));
+	}
+
+	return BLKID_PROBE_OK;
+
+nothing:
+	return BLKID_PROBE_NONE;
+err:
+	return -ENOMEM;
+}
+
+/*
+ * Mac disk always begin with "Driver Descriptor Record"
+ * (struct mac_driver_desc) and magic 0x4552.
+ */
+const struct blkid_idinfo mac_pt_idinfo =
+{
+	.name		= "mac",
+	.probefunc	= probe_mac_pt,
+	.magics		=
+	{
+		/* big-endian magic string */
+		{ .magic = "\x45\x52", .len = 2 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/partitions/minix.c b/libblkid/src/partitions/minix.c
new file mode 100644
index 0000000..43c9d9a
--- /dev/null
+++ b/libblkid/src/partitions/minix.c
@@ -0,0 +1,102 @@
+/*
+ * Minix partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+#include "minix.h"
+
+static int probe_minix_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct dos_partition *p;
+	blkid_parttable tab = NULL;
+	blkid_partition parent;
+	blkid_partlist ls;
+	unsigned char *data;
+	int i;
+
+	data = blkid_probe_get_sector(pr, 0);
+	if (!data) {
+		if (errno)
+			return -errno;
+		goto nothing;
+	}
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		goto nothing;
+
+	/* Parent is required, because Minix uses the same PT as DOS and
+	 * difference is only in primary partition (parent) type.
+	 */
+	parent = blkid_partlist_get_parent(ls);
+	if (!parent)
+		goto nothing;
+
+	if (blkid_partition_get_type(parent) != MBR_MINIX_PARTITION)
+		goto nothing;
+
+	if (blkid_partitions_need_typeonly(pr))
+		/* caller does not ask for details about partitions */
+		return BLKID_PROBE_OK;
+
+	tab = blkid_partlist_new_parttable(ls, "minix", MBR_PT_OFFSET);
+	if (!tab)
+		goto err;
+
+	for (i = 0, p = mbr_get_partition(data, 0);
+			i < MINIX_MAXPARTITIONS; i++, p++) {
+
+		uint32_t start, size;
+		blkid_partition par;
+
+		if (p->sys_ind != MBR_MINIX_PARTITION)
+			continue;
+
+		start = dos_partition_get_start(p);
+		size = dos_partition_get_size(p);
+
+		if (parent && !blkid_is_nested_dimension(parent, start, size)) {
+			DBG(LOWPROBE, ul_debug(
+				"WARNING: minix partition (%d) overflow "
+				"detected, ignore", i));
+			continue;
+		}
+
+		par = blkid_partlist_add_partition(ls, tab, start, size);
+		if (!par)
+			goto err;
+
+		blkid_partition_set_type(par, p->sys_ind);
+		blkid_partition_set_flags(par, p->boot_ind);
+	}
+
+	return BLKID_PROBE_OK;
+
+nothing:
+	return BLKID_PROBE_NONE;
+err:
+	return -ENOMEM;
+}
+
+/* same as DOS */
+const struct blkid_idinfo minix_pt_idinfo =
+{
+	.name		= "minix",
+	.probefunc	= probe_minix_pt,
+	.magics		=
+	{
+		{ .magic = "\x55\xAA", .len = 2, .sboff = 510 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/partitions/partitions.c b/libblkid/src/partitions/partitions.c
new file mode 100644
index 0000000..25f1828
--- /dev/null
+++ b/libblkid/src/partitions/partitions.c
@@ -0,0 +1,1513 @@
+/*
+ * partitions - partition tables parsing
+ *
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <errno.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdarg.h>
+
+#include "partitions.h"
+#include "sysfs.h"
+
+/**
+ * SECTION: partitions
+ * @title: Partitions probing
+ * @short_description: partitions tables detection and parsing
+ *
+ * This chain supports binary and NAME=value interfaces, but complete PT
+ * description is provided by binary interface only. The libblkid prober is
+ * compatible with kernel partition tables parser. The parser does not return
+ * empty (size=0) partitions or special hidden partitions.
+ *
+ * NAME=value interface, supported tags:
+ *
+ * @PTTYPE: partition table type (dos, gpt, etc.).
+ *
+ * @PTUUID: partition table id (uuid for gpt, hex for dos).
+
+ * @PART_ENTRY_SCHEME: partition table type
+ *
+ * @PART_ENTRY_NAME: partition name (gpt and mac only)
+ *
+ * @PART_ENTRY_UUID: partition UUID (gpt, or pseudo IDs for MBR)
+ *
+ * @PART_ENTRY_TYPE: partition type, 0xNN (e.g 0x82) or type UUID (gpt only) or type string (mac)
+ *
+ * @PART_ENTRY_FLAGS: partition flags (e.g. boot_ind) or  attributes (e.g. gpt attributes)
+ *
+ * @PART_ENTRY_NUMBER: partition number
+ *
+ * @PART_ENTRY_OFFSET: the begin of the partition
+ *
+ * @PART_ENTRY_SIZE: size of the partition
+ *
+ * @PART_ENTRY_DISK: whole-disk maj:min
+ *
+ * Example:
+ *
+ * <informalexample>
+ *  <programlisting>
+ * blkid_probe pr;
+ * const char *ptname;
+ *
+ * pr = blkid_new_probe_from_filename(devname);
+ * if (!pr)
+ *	err("%s: faild to open device", devname);
+ *
+ * blkid_probe_enable_partitions(pr, TRUE);
+ * blkid_do_fullprobe(pr);
+ *
+ * blkid_probe_lookup_value(pr, "PTTYPE", &ptname, NULL);
+ * printf("%s partition type detected\n", pttype);
+ *
+ * blkid_free_probe(pr);
+ *
+ * // don't forget to check return codes in your code!
+ *  </programlisting>
+ * </informalexample>
+ *
+ * Binary interface:
+ *
+ * <informalexample>
+ *  <programlisting>
+ * blkid_probe pr;
+ * blkid_partlist ls;
+ * int nparts, i;
+ *
+ * pr = blkid_new_probe_from_filename(devname);
+ * if (!pr)
+ *	err("%s: faild to open device", devname);
+ *
+ * ls = blkid_probe_get_partitions(pr);
+ * nparts = blkid_partlist_numof_partitions(ls);
+ *
+ * for (i = 0; i < nparts; i++) {
+ *      blkid_partition par = blkid_partlist_get_partition(ls, i);
+ *      printf("#%d: %llu %llu  0x%x",
+ *               blkid_partition_get_partno(par),
+ *               blkid_partition_get_start(par),
+ *               blkid_partition_get_size(par),
+ *               blkid_partition_get_type(par));
+ * }
+ *
+ * blkid_free_probe(pr);
+ *
+ * // don't forget to check return codes in your code!
+ *  </programlisting>
+ * </informalexample>
+ */
+
+/*
+ * Chain driver function
+ */
+static int partitions_probe(blkid_probe pr, struct blkid_chain *chn);
+static void partitions_free_data(blkid_probe pr, void *data);
+
+/*
+ * Partitions chain probing functions
+ */
+static const struct blkid_idinfo *idinfos[] =
+{
+	&aix_pt_idinfo,
+	&sgi_pt_idinfo,
+	&sun_pt_idinfo,
+	&dos_pt_idinfo,
+	&gpt_pt_idinfo,
+	&pmbr_pt_idinfo,	/* always after GPT */
+	&mac_pt_idinfo,
+	&ultrix_pt_idinfo,
+	&bsd_pt_idinfo,
+	&unixware_pt_idinfo,
+	&solaris_x86_pt_idinfo,
+	&minix_pt_idinfo
+};
+
+/*
+ * Driver definition
+ */
+const struct blkid_chaindrv partitions_drv = {
+	.id           = BLKID_CHAIN_PARTS,
+	.name         = "partitions",
+	.dflt_enabled = FALSE,
+	.idinfos      = idinfos,
+	.nidinfos     = ARRAY_SIZE(idinfos),
+	.has_fltr     = TRUE,
+	.probe        = partitions_probe,
+	.safeprobe    = partitions_probe,
+	.free_data    = partitions_free_data
+};
+
+
+/*
+ * For compatibility with the rest of libblkid API (with the old high-level
+ * API) we use completely opaque typedefs for all structs. Don't forget that
+ * the final blkid_* types are pointers! See blkid.h.
+ *
+ * [Just for the record, I hate typedef for pointers --kzak]
+ */
+
+/* exported as opaque type "blkid_parttable" */
+struct blkid_struct_parttable {
+	const char	*type;		/* partition table type */
+	blkid_loff_t	offset;		/* begin of the partition table (in bytes) */
+	int		nparts;		/* number of partitions */
+	blkid_partition	parent;		/* parent of nested partition table */
+	char		id[37];		/* PT identifier (e.g. UUID for GPT) */
+
+	struct list_head t_tabs;	/* all tables */
+};
+
+/* exported as opaque type "blkid_partition" */
+struct blkid_struct_partition {
+	blkid_loff_t	start;		/* begin of the partition (512-bytes sectors) */
+	blkid_loff_t	size;		/* size of the partitions (512-bytes sectors) */
+
+	int		type;		/* partition type */
+	char		typestr[37];	/* partition type string (GPT and Mac) */
+
+	unsigned long long flags;	/* partition flags / attributes */
+
+	int		partno;		/* partition number */
+	char		uuid[37];	/* UUID (when supported by PT), e.g GPT */
+	unsigned char	name[128];	/* Partition in UTF8 name (when supporte by PT), e.g. Mac */
+
+	blkid_parttable	tab;		/* partition table */
+};
+
+/* exported as opaque type "blkid_partlist" */
+struct blkid_struct_partlist {
+	int		next_partno;	/* next partition number */
+	blkid_partition next_parent;	/* next parent if parsing nested PT */
+
+	int		nparts;		/* number of partitions */
+	int		nparts_max;	/* max.number of partitions */
+	blkid_partition	parts;		/* array of partitions */
+
+	struct list_head l_tabs;	/* list of partition tables */
+};
+
+static int blkid_partitions_probe_partition(blkid_probe pr);
+
+/**
+ * blkid_probe_enable_partitions:
+ * @pr: probe
+ * @enable: TRUE/FALSE
+ *
+ * Enables/disables the partitions probing for non-binary interface.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_enable_partitions(blkid_probe pr, int enable)
+{
+	if (!pr)
+		return -1;
+	pr->chains[BLKID_CHAIN_PARTS].enabled = enable;
+	return 0;
+}
+
+/**
+ * blkid_probe_set_partitions_flags:
+ * @pr: prober
+ * @flags: BLKID_PARTS_* flags
+ *
+ * Sets probing flags to the partitions prober. This function is optional.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_set_partitions_flags(blkid_probe pr, int flags)
+{
+	if (!pr)
+		return -1;
+	pr->chains[BLKID_CHAIN_PARTS].flags = flags;
+	return 0;
+}
+
+/**
+ * blkid_probe_reset_partitions_filter:
+ * @pr: prober
+ *
+ * Resets partitions probing filter
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_reset_partitions_filter(blkid_probe pr)
+{
+	return __blkid_probe_reset_filter(pr, BLKID_CHAIN_PARTS);
+}
+
+/**
+ * blkid_probe_invert_partitions_filter:
+ * @pr: prober
+ *
+ * Inverts partitions probing filter
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_invert_partitions_filter(blkid_probe pr)
+{
+	return __blkid_probe_invert_filter(pr, BLKID_CHAIN_PARTS);
+}
+
+/**
+ * blkid_probe_filter_partitions_type:
+ * @pr: prober
+ * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
+ * @names: NULL terminated array of probing function names (e.g. "vfat").
+ *
+ *  %BLKID_FLTR_NOTIN  - probe for all items which are NOT IN @names
+ *
+ *  %BLKID_FLTR_ONLYIN - probe for items which are IN @names
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_filter_partitions_type(blkid_probe pr, int flag, char *names[])
+{
+	return __blkid_probe_filter_types(pr, BLKID_CHAIN_PARTS, flag, names);
+}
+
+/**
+ * blkid_probe_get_partitions:
+ * @pr: probe
+ *
+ * This is a binary interface for partitions. See also blkid_partlist_*
+ * functions.
+ *
+ * This function is independent on blkid_do_[safe,full]probe() and
+ * blkid_probe_enable_partitions() calls.
+ *
+ * WARNING: the returned object will be overwritten by the next
+ *          blkid_probe_get_partitions() call for the same @pr. If you want to
+ *          use more blkid_partlist objects in the same time you have to create
+ *          more blkid_probe handlers (see blkid_new_probe()).
+ *
+ * Returns: list of partitions, or NULL in case of error.
+ */
+blkid_partlist blkid_probe_get_partitions(blkid_probe pr)
+{
+	return (blkid_partlist) blkid_probe_get_binary_data(pr,
+			&pr->chains[BLKID_CHAIN_PARTS]);
+}
+
+/* for internal usage only */
+blkid_partlist blkid_probe_get_partlist(blkid_probe pr)
+{
+	return (blkid_partlist) pr->chains[BLKID_CHAIN_PARTS].data;
+}
+
+static void blkid_probe_set_partlist(blkid_probe pr, blkid_partlist ls)
+{
+	pr->chains[BLKID_CHAIN_PARTS].data = ls;
+}
+
+static void ref_parttable(blkid_parttable tab)
+{
+	tab->nparts++;
+}
+
+static void unref_parttable(blkid_parttable tab)
+{
+	tab->nparts--;
+
+	if (tab->nparts <= 0) {
+		list_del(&tab->t_tabs);
+		free(tab);
+	}
+}
+
+/* free all allocated parttables */
+static void free_parttables(blkid_partlist ls)
+{
+	if (!ls || !ls->l_tabs.next)
+		return;
+
+	/* remove unassigned partition tables */
+	while (!list_empty(&ls->l_tabs)) {
+		blkid_parttable tab = list_entry(ls->l_tabs.next,
+					struct blkid_struct_parttable, t_tabs);
+		unref_parttable(tab);
+	}
+}
+
+static void reset_partlist(blkid_partlist ls)
+{
+	if (!ls)
+		return;
+
+	free_parttables(ls);
+
+	if (ls->next_partno) {
+		/* already initialized - reset */
+		int tmp_nparts = ls->nparts_max;
+		blkid_partition tmp_parts = ls->parts;
+
+		memset(ls, 0, sizeof(struct blkid_struct_partlist));
+
+		ls->nparts_max = tmp_nparts;
+		ls->parts = tmp_parts;
+	}
+
+	ls->nparts = 0;
+	ls->next_partno = 1;
+	INIT_LIST_HEAD(&ls->l_tabs);
+
+	DBG(LOWPROBE, ul_debug("partlist reset"));
+}
+
+static blkid_partlist partitions_init_data(struct blkid_chain *chn)
+{
+	blkid_partlist ls;
+
+	if (chn->data)
+		ls = (blkid_partlist) chn->data;
+	else {
+		/* allocate the new list of partitions */
+		ls = calloc(1, sizeof(struct blkid_struct_partlist));
+		if (!ls)
+			return NULL;
+		chn->data = (void *) ls;
+	}
+
+	reset_partlist(ls);
+
+	DBG(LOWPROBE, ul_debug("parts: initialized partitions list (%p, size=%d)",
+		ls, ls->nparts_max));
+	return ls;
+}
+
+static void partitions_free_data(blkid_probe pr __attribute__((__unused__)),
+				 void *data)
+{
+	blkid_partlist ls = (blkid_partlist) data;
+
+	if (!ls)
+		return;
+
+	free_parttables(ls);
+
+	/* deallocate partitions and partlist */
+	free(ls->parts);
+	free(ls);
+}
+
+blkid_parttable blkid_partlist_new_parttable(blkid_partlist ls,
+				const char *type, blkid_loff_t offset)
+{
+	blkid_parttable tab;
+
+	tab = calloc(1, sizeof(struct blkid_struct_parttable));
+	if (!tab)
+		return NULL;
+	tab->type = type;
+	tab->offset = offset;
+	tab->parent = ls->next_parent;
+
+	INIT_LIST_HEAD(&tab->t_tabs);
+	list_add_tail(&tab->t_tabs, &ls->l_tabs);
+
+	DBG(LOWPROBE, ul_debug("parts: create a new partition table "
+		       "(%p, type=%s, offset=%"PRId64")", tab, type, offset));
+	return tab;
+}
+
+static blkid_partition new_partition(blkid_partlist ls, blkid_parttable tab)
+{
+	blkid_partition par;
+
+	if (ls->nparts + 1 > ls->nparts_max) {
+		/* Linux kernel has DISK_MAX_PARTS=256, but it's too much for
+		 * generic Linux machine -- let start with 32 partititions.
+		 */
+		void *tmp = realloc(ls->parts, (ls->nparts_max + 32) *
+					sizeof(struct blkid_struct_partition));
+		if (!tmp)
+			return NULL;
+		ls->parts = tmp;
+		ls->nparts_max += 32;
+	}
+
+	par = &ls->parts[ls->nparts++];
+	memset(par, 0, sizeof(struct blkid_struct_partition));
+
+	ref_parttable(tab);
+	par->tab = tab;
+	par->partno = blkid_partlist_increment_partno(ls);
+
+	return par;
+}
+
+blkid_partition blkid_partlist_add_partition(blkid_partlist ls,
+					blkid_parttable tab,
+					blkid_loff_t start, blkid_loff_t size)
+{
+	blkid_partition par = new_partition(ls, tab);
+
+	if (!par)
+		return NULL;
+
+	par->start = start;
+	par->size = size;
+
+	DBG(LOWPROBE, ul_debug("parts: add partition (%p start=%"
+		PRId64 ", size=%" PRId64 ", table=%p)",
+		par, par->start, par->size, tab));
+	return par;
+}
+
+/* allows to modify used partitions numbers (for example for logical partitions) */
+int blkid_partlist_set_partno(blkid_partlist ls, int partno)
+{
+	if (!ls)
+		return -1;
+	ls->next_partno = partno;
+	return 0;
+}
+
+int blkid_partlist_increment_partno(blkid_partlist ls)
+{
+	return ls ? ls->next_partno++ : -1;
+}
+
+/* allows to set "parent" for the next nested partition */
+int blkid_partlist_set_parent(blkid_partlist ls, blkid_partition par)
+{
+	if (!ls)
+		return -1;
+	ls->next_parent = par;
+	return 0;
+}
+
+blkid_partition blkid_partlist_get_parent(blkid_partlist ls)
+{
+	if (!ls)
+		return NULL;
+	return ls->next_parent;
+}
+
+int blkid_partitions_need_typeonly(blkid_probe pr)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+	return chn && chn->data && chn->binary ? FALSE : TRUE;
+}
+
+/* get private chain flags */
+int blkid_partitions_get_flags(blkid_probe pr)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+	return chn ? chn->flags : 0;
+}
+
+/* check if @start and @size are within @par partition */
+int blkid_is_nested_dimension(blkid_partition par,
+			blkid_loff_t start, blkid_loff_t size)
+{
+	blkid_loff_t pstart;
+	blkid_loff_t psize;
+
+	if (!par)
+		return 0;
+
+	pstart = blkid_partition_get_start(par);
+	psize = blkid_partition_get_size(par);
+
+	if (start < pstart || start + size > pstart + psize)
+		return 0;
+
+	return 1;
+}
+
+static int idinfo_probe(blkid_probe pr, const struct blkid_idinfo *id,
+			struct blkid_chain *chn)
+{
+	const struct blkid_idmag *mag = NULL;
+	blkid_loff_t off;
+	int rc = BLKID_PROBE_NONE;		/* default is nothing */
+
+	if (pr->size <= 0 || (id->minsz && id->minsz > pr->size))
+		goto nothing;	/* the device is too small */
+	if (pr->flags & BLKID_FL_NOSCAN_DEV)
+		goto nothing;
+
+	rc = blkid_probe_get_idmag(pr, id, &off, &mag);
+	if (rc != BLKID_PROBE_OK)
+		goto nothing;
+
+	/* final check by probing function */
+	if (id->probefunc) {
+		DBG(LOWPROBE, ul_debug(
+			"%s: ---> call probefunc()", id->name));
+		rc = id->probefunc(pr, mag);
+		if (rc < 0) {
+			/* reset after error */
+			reset_partlist(blkid_probe_get_partlist(pr));
+			if (chn && !chn->binary)
+				blkid_probe_chain_reset_vals(pr, chn);
+			DBG(LOWPROBE, ul_debug("%s probefunc failed, rc %d",
+						  id->name, rc));
+		}
+		if (rc == BLKID_PROBE_OK && mag && chn && !chn->binary)
+			rc = blkid_probe_set_magic(pr, off, mag->len,
+					(unsigned char *) mag->magic);
+
+		DBG(LOWPROBE, ul_debug("%s: <--- (rc = %d)", id->name, rc));
+	}
+
+	return rc;
+
+nothing:
+	return BLKID_PROBE_NONE;
+}
+
+/*
+ * The blkid_do_probe() backend.
+ */
+static int partitions_probe(blkid_probe pr, struct blkid_chain *chn)
+{
+	int rc = BLKID_PROBE_NONE;
+	size_t i;
+
+	if (!pr || chn->idx < -1)
+		return -EINVAL;
+
+	blkid_probe_chain_reset_vals(pr, chn);
+
+	if (pr->flags & BLKID_FL_NOSCAN_DEV)
+		return BLKID_PROBE_NONE;
+
+	if (chn->binary)
+		partitions_init_data(chn);
+
+	if (!pr->wipe_size && (pr->prob_flags & BLKID_PROBE_FL_IGNORE_PT))
+		goto details_only;
+
+	DBG(LOWPROBE, ul_debug("--> starting probing loop [PARTS idx=%d]",
+		chn->idx));
+
+	i = chn->idx < 0 ? 0 : chn->idx + 1U;
+
+	for ( ; i < ARRAY_SIZE(idinfos); i++) {
+		const char *name;
+
+		chn->idx = i;
+
+		/* apply filter */
+		if (chn->fltr && blkid_bmp_get_item(chn->fltr, i))
+			continue;
+
+		/* apply checks from idinfo */
+		rc = idinfo_probe(pr, idinfos[i], chn);
+		if (rc < 0)
+			break;
+		if (rc != BLKID_PROBE_OK)
+			continue;
+
+		name = idinfos[i]->name;
+
+		if (!chn->binary)
+			/*
+			 * Non-binary interface, set generic variables. Note
+			 * that the another variables could be set in prober
+			 * functions.
+			 */
+			blkid_probe_set_value(pr, "PTTYPE",
+						(unsigned char *) name,
+						strlen(name) + 1);
+
+		DBG(LOWPROBE, ul_debug("<-- leaving probing loop (type=%s) [PARTS idx=%d]",
+			name, chn->idx));
+		rc = BLKID_PROBE_OK;
+		break;
+	}
+
+	if (rc != BLKID_PROBE_OK) {
+		DBG(LOWPROBE, ul_debug("<-- leaving probing loop (failed=%d) [PARTS idx=%d]",
+			rc, chn->idx));
+	}
+
+details_only:
+	/*
+	 * Gather PART_ENTRY_* values if the current device is a partition.
+	 */
+	if ((rc == BLKID_PROBE_OK || rc == BLKID_PROBE_NONE) && !chn->binary &&
+	    (blkid_partitions_get_flags(pr) & BLKID_PARTS_ENTRY_DETAILS)) {
+
+		int xrc = blkid_partitions_probe_partition(pr);
+
+		/* partition entry probing is optional, and "not-found" from
+		 * this sub-probing must not to overwrite previous success. */
+		if (xrc < 0)
+			rc = xrc;			/* always propagate errors */
+		else if (rc == BLKID_PROBE_NONE)
+			rc = xrc;
+	}
+
+	DBG(LOWPROBE, ul_debug("partitions probe done [rc=%d]",	rc));
+	return rc;
+}
+
+/* Probe for nested partition table within the parental partition */
+int blkid_partitions_do_subprobe(blkid_probe pr, blkid_partition parent,
+		const struct blkid_idinfo *id)
+{
+	blkid_probe prc;
+	int rc;
+	blkid_partlist ls;
+	blkid_loff_t sz, off;
+
+	DBG(LOWPROBE, ul_debug(
+		"parts: ----> %s subprobe requested (parent=%p)",
+		id->name, parent));
+
+	if (!pr || !parent || !parent->size)
+		return -EINVAL;
+	if (pr->flags & BLKID_FL_NOSCAN_DEV)
+		return BLKID_PROBE_NONE;
+
+	/* range defined by parent */
+	sz = ((blkid_loff_t) parent->size) << 9;
+	off = ((blkid_loff_t) parent->start) << 9;
+
+	if (off < pr->off || pr->off + pr->size < off + sz) {
+		DBG(LOWPROBE, ul_debug(
+			"ERROR: parts: <---- '%s' subprobe: overflow detected.",
+			id->name));
+		return -ENOSPC;
+	}
+
+	/* create private prober */
+	prc = blkid_clone_probe(pr);
+	if (!prc)
+		return -ENOMEM;
+
+	blkid_probe_set_dimension(prc, off, sz);
+
+	/* clone is always with reset chain, fix it */
+	prc->cur_chain = blkid_probe_get_chain(pr);
+
+	/*
+	 * Set 'parent' to the current list of the partitions and use the list
+	 * in cloned prober (so the cloned prober will extend the current list
+	 * of partitions rather than create a new).
+	 */
+	ls = blkid_probe_get_partlist(pr);
+	blkid_partlist_set_parent(ls, parent);
+
+	blkid_probe_set_partlist(prc, ls);
+
+	rc = idinfo_probe(prc, id, blkid_probe_get_chain(pr));
+
+	blkid_probe_set_partlist(prc, NULL);
+	blkid_partlist_set_parent(ls, NULL);
+
+	blkid_free_probe(prc);	/* free cloned prober */
+
+	DBG(LOWPROBE, ul_debug(
+		"parts: <---- %s subprobe done (parent=%p, rc=%d)",
+		id->name, parent, rc));
+
+	return rc;
+}
+
+static int blkid_partitions_probe_partition(blkid_probe pr)
+{
+	blkid_probe disk_pr = NULL;
+	blkid_partlist ls;
+	blkid_partition par;
+	dev_t devno;
+
+	DBG(LOWPROBE, ul_debug("parts: start probing for partition entry"));
+
+	if (pr->flags & BLKID_FL_NOSCAN_DEV)
+		goto nothing;
+
+	devno = blkid_probe_get_devno(pr);
+	if (!devno)
+		goto nothing;
+
+	disk_pr = blkid_probe_get_wholedisk_probe(pr);
+	if (!disk_pr)
+		goto nothing;
+
+	/* parse PT */
+	ls = blkid_probe_get_partitions(disk_pr);
+	if (!ls)
+		goto nothing;
+
+	par = blkid_partlist_devno_to_partition(ls, devno);
+	if (!par)
+		goto nothing;
+	else {
+		const char *v;
+		blkid_parttable tab = blkid_partition_get_table(par);
+		dev_t disk = blkid_probe_get_devno(disk_pr);
+
+		if (tab) {
+			v = blkid_parttable_get_type(tab);
+			if (v)
+				blkid_probe_set_value(pr, "PART_ENTRY_SCHEME",
+					(unsigned char *) v, strlen(v) + 1);
+		}
+
+		v = blkid_partition_get_name(par);
+		if (v)
+			blkid_probe_set_value(pr, "PART_ENTRY_NAME",
+				(unsigned char *) v, strlen(v) + 1);
+
+		v = blkid_partition_get_uuid(par);
+		if (v)
+			blkid_probe_set_value(pr, "PART_ENTRY_UUID",
+				(unsigned char *) v, strlen(v) + 1);
+
+		/* type */
+		v = blkid_partition_get_type_string(par);
+		if (v)
+			blkid_probe_set_value(pr, "PART_ENTRY_TYPE",
+				(unsigned char *) v, strlen(v) + 1);
+		else
+			blkid_probe_sprintf_value(pr, "PART_ENTRY_TYPE",
+				"0x%x", blkid_partition_get_type(par));
+
+		if (blkid_partition_get_flags(par))
+			blkid_probe_sprintf_value(pr, "PART_ENTRY_FLAGS",
+				"0x%llx", blkid_partition_get_flags(par));
+
+		blkid_probe_sprintf_value(pr, "PART_ENTRY_NUMBER",
+				"%d", blkid_partition_get_partno(par));
+
+		blkid_probe_sprintf_value(pr, "PART_ENTRY_OFFSET", "%jd",
+				blkid_partition_get_start(par));
+		blkid_probe_sprintf_value(pr, "PART_ENTRY_SIZE", "%jd",
+				blkid_partition_get_size(par));
+
+		blkid_probe_sprintf_value(pr, "PART_ENTRY_DISK", "%u:%u",
+				major(disk), minor(disk));
+	}
+
+	DBG(LOWPROBE, ul_debug("parts: end probing for partition entry [success]"));
+	return BLKID_PROBE_OK;
+
+nothing:
+	DBG(LOWPROBE, ul_debug("parts: end probing for partition entry [nothing]"));
+	return BLKID_PROBE_NONE;
+
+
+}
+
+/*
+ * Returns 1 if the device is whole-disk and the area specified by @offset and
+ * @size is covered by any partition.
+ */
+int blkid_probe_is_covered_by_pt(blkid_probe pr,
+				 blkid_loff_t offset, blkid_loff_t size)
+{
+	blkid_probe prc = NULL;
+	blkid_partlist ls = NULL;
+	blkid_loff_t start, end;
+	int nparts, i, rc = 0;
+
+	DBG(LOWPROBE, ul_debug(
+		"=> checking if off=%jd size=%jd covered by PT",
+		offset, size));
+
+	if (pr->flags & BLKID_FL_NOSCAN_DEV)
+		goto done;
+
+	prc = blkid_clone_probe(pr);
+	if (!prc)
+		goto done;
+
+	ls = blkid_probe_get_partitions(prc);
+	if (!ls)
+		goto done;
+
+	nparts = blkid_partlist_numof_partitions(ls);
+	if (!nparts)
+		goto done;
+
+	end = (offset + size) >> 9;
+	start = offset >> 9;
+
+	/* check if the partition table fits into the device */
+	for (i = 0; i < nparts; i++) {
+		blkid_partition par = &ls->parts[i];
+
+		if (par->start + par->size > (pr->size >> 9)) {
+			DBG(LOWPROBE, ul_debug("partition #%d overflows "
+				"device (off=%" PRId64 " size=%" PRId64 ")",
+				par->partno, par->start, par->size));
+			goto done;
+		}
+	}
+
+	/* check if the requested area is covered by PT */
+	for (i = 0; i < nparts; i++) {
+		blkid_partition par = &ls->parts[i];
+
+		if (start >= par->start && end <= par->start + par->size) {
+			rc = 1;
+			break;
+		}
+	}
+done:
+	blkid_free_probe(prc);
+
+	DBG(LOWPROBE, ul_debug("<= %s covered by PT", rc ? "IS" : "NOT"));
+	return rc;
+}
+
+/**
+ * blkid_known_pttype:
+ * @pttype: partiton name
+ *
+ * Returns: 1 for known or 0 for unknown partition type.
+ */
+int blkid_known_pttype(const char *pttype)
+{
+	size_t i;
+
+	if (!pttype)
+		return 0;
+
+	for (i = 0; i < ARRAY_SIZE(idinfos); i++) {
+		const struct blkid_idinfo *id = idinfos[i];
+		if (strcmp(id->name, pttype) == 0)
+			return 1;
+	}
+	return 0;
+}
+
+/**
+ * blkid_partlist_numof_partitions:
+ * @ls: partitions list
+ *
+ * Returns: number of partitions in the list or -1 in case of error.
+ */
+int blkid_partlist_numof_partitions(blkid_partlist ls)
+{
+	return ls ? ls->nparts : -1;
+}
+
+/**
+ * blkid_partlist_get_table:
+ * @ls: partitions list
+ *
+ * Returns: top-level partition table or NULL of there is not a partition table
+ * on the device.
+ */
+blkid_parttable blkid_partlist_get_table(blkid_partlist ls)
+{
+	if (!ls || list_empty(&ls->l_tabs))
+		return NULL;
+
+	return list_entry(ls->l_tabs.next,
+			struct blkid_struct_parttable, t_tabs);
+}
+
+
+/**
+ * blkid_partlist_get_partition:
+ * @ls: partitions list
+ * @n: partition number in range 0..N, where 'N' is blkid_partlist_numof_partitions().
+ *
+ * It's possible that the list of partitions is *empty*, but there is a valid
+ * partition table on the disk. This happen when on-disk details about
+ * partitions are unknown or the partition table is empty.
+ *
+ * See also blkid_partlist_get_table().
+ *
+ * Returns: partition object or NULL in case or error.
+ */
+blkid_partition blkid_partlist_get_partition(blkid_partlist ls, int n)
+{
+	if (!ls || n < 0 || n >= ls->nparts)
+		return NULL;
+
+	return &ls->parts[n];
+}
+
+/**
+ * blkid_partlist_get_partition_by_partno
+ * @ls: partitions list
+ * @n: the partition number (e.g. 'N' from sda'N')
+ *
+ * This does not assume any order of the input blkid_partlist.  And correctly
+ * handles "out of order" partition tables.  partition N is located after
+ * partition N+1 on the disk.
+ *
+ * Returns: partition object or NULL in case or error.
+ */
+blkid_partition blkid_partlist_get_partition_by_partno(blkid_partlist ls, int n)
+{
+	int i, nparts;
+	blkid_partition par;
+
+	if (!ls)
+		return NULL;
+
+	nparts = blkid_partlist_numof_partitions(ls);
+	for (i = 0; i < nparts; i++) {
+		par = blkid_partlist_get_partition(ls, i);
+		if (n == blkid_partition_get_partno(par))
+			return par;
+	}
+	return NULL;
+}
+
+
+/**
+ * blkid_partlist_devno_to_partition:
+ * @ls: partitions list
+ * @devno: requested partition
+ *
+ * This function tries to get start and size for @devno from sysfs and
+ * returns a partition from @ls which matches with the values from sysfs.
+ *
+ * This function is necessary when you want to make a relation between an entry
+ * in the partition table (@ls) and block devices in your system.
+ *
+ * Returns: partition object or NULL in case or error.
+ */
+blkid_partition blkid_partlist_devno_to_partition(blkid_partlist ls, dev_t devno)
+{
+	struct sysfs_cxt sysfs;
+	uint64_t start, size;
+	int i, rc, partno = 0;
+
+	if (!ls)
+		return NULL;
+
+	DBG(LOWPROBE, ul_debug("triyng to convert devno 0x%llx to partition",
+			(long long) devno));
+
+	if (sysfs_init(&sysfs, devno, NULL)) {
+		DBG(LOWPROBE, ul_debug("failed t init sysfs context"));
+		return NULL;
+	}
+	rc = sysfs_read_u64(&sysfs, "size", &size);
+	if (!rc) {
+		rc = sysfs_read_u64(&sysfs, "start", &start);
+		if (rc) {
+			/* try to get partition number from DM uuid.
+			 */
+			char *uuid = sysfs_strdup(&sysfs, "dm/uuid");
+			char *tmp = uuid;
+			char *prefix = uuid ? strsep(&tmp, "-") : NULL;
+
+			if (prefix && strncasecmp(prefix, "part", 4) == 0) {
+				char *end = NULL;
+
+				partno = strtol(prefix + 4, &end, 10);
+				if (prefix == end || (end && *end))
+					partno = 0;
+				else
+					rc = 0;		/* success */
+			}
+			free(uuid);
+		}
+	}
+
+	sysfs_deinit(&sysfs);
+
+	if (rc)
+		return NULL;
+
+	if (partno) {
+		DBG(LOWPROBE, ul_debug("mapped by DM, using partno %d", partno));
+
+		/*
+		 * Partition mapped by kpartx does not provide "start" offset
+		 * in /sys, but if we know partno and size of the partition
+		 * that we can probably make the releation bettween the device
+		 * and an entry in partition table.
+		 */
+		 for (i = 0; i < ls->nparts; i++) {
+			 blkid_partition par = &ls->parts[i];
+
+			 if (partno != blkid_partition_get_partno(par))
+				 continue;
+
+			 if ((blkid_loff_t) size == blkid_partition_get_size(par) ||
+			     (blkid_partition_is_extended(par) && size <= 1024))
+				 return par;
+
+		 }
+		 return NULL;
+	}
+
+	DBG(LOWPROBE, ul_debug("searching by offset/size"));
+
+	for (i = 0; i < ls->nparts; i++) {
+		blkid_partition par = &ls->parts[i];
+
+		if (blkid_partition_get_start(par) == (blkid_loff_t) start &&
+		    blkid_partition_get_size(par) == (blkid_loff_t) size)
+			return par;
+
+		/* exception for extended dos partitions */
+		if (blkid_partition_get_start(par) == (blkid_loff_t) start &&
+		    blkid_partition_is_extended(par) && size <= 1024)
+			return par;
+
+	}
+
+	DBG(LOWPROBE, ul_debug("not found partition for device"));
+	return NULL;
+}
+
+
+int blkid_parttable_set_uuid(blkid_parttable tab, const unsigned char *id)
+{
+	if (!tab)
+		return -1;
+
+	blkid_unparse_uuid(id, tab->id, sizeof(tab->id));
+	return 0;
+}
+
+int blkid_parttable_set_id(blkid_parttable tab, const unsigned char *id)
+{
+	if (!tab)
+		return -1;
+
+	strncpy(tab->id, (const char *) id, sizeof(tab->id));
+	return 0;
+}
+
+/* set PTUUID variable for non-binary API */
+int blkid_partitions_set_ptuuid(blkid_probe pr, unsigned char *uuid)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+	struct blkid_prval *v;
+
+	if (chn->binary || blkid_uuid_is_empty(uuid, 16))
+		return 0;
+
+	v = blkid_probe_assign_value(pr, "PTUUID");
+
+	blkid_unparse_uuid(uuid, (char *) v->data, sizeof(v->data));
+	v->len = 37;
+
+	return 0;
+}
+
+/* set PTUUID variable for non-binary API for tables where
+ * the ID is just a string */
+int blkid_partitions_strcpy_ptuuid(blkid_probe pr, char *str)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+	struct blkid_prval *v;
+	size_t len;
+
+	if (chn->binary || !str || !*str)
+		return 0;
+
+	len = strlen((char *) str);
+	if (len > BLKID_PROBVAL_BUFSIZ)
+		len = BLKID_PROBVAL_BUFSIZ;
+
+	v = blkid_probe_assign_value(pr, "PTUUID");
+	if (v) {
+		if (len == BLKID_PROBVAL_BUFSIZ)
+			len--;		/* make a space for \0 */
+
+		memcpy((char *) v->data, str, len);
+		v->data[len] = '\0';
+		v->len = len + 1;
+		return 0;
+	}
+	return -1;
+}
+
+/**
+ * blkid_parttable_get_id:
+ * @tab: partition table
+ *
+ * The ID is GPT disk UUID or DOS disk ID (in hex format).
+ *
+ * Returns: partition table ID (for example GPT disk UUID) or NULL
+ */
+const char *blkid_parttable_get_id(blkid_parttable tab)
+{
+	return tab && *tab->id ? tab->id : NULL;
+}
+
+
+int blkid_partition_set_type(blkid_partition par, int type)
+{
+	if (!par)
+		return -1;
+	par->type = type;
+	return 0;
+}
+
+/**
+ * blkid_parttable_get_type:
+ * @tab: partition table
+ *
+ * Returns: partition table type (type name, e.g. "dos", "gpt", ...)
+ */
+const char *blkid_parttable_get_type(blkid_parttable tab)
+{
+	return tab ? tab->type : NULL;
+}
+
+/**
+ * blkid_parttable_get_parent:
+ * @tab: partition table
+ *
+ * Returns: parent for nexted partitition tables or NULL.
+ */
+blkid_partition blkid_parttable_get_parent(blkid_parttable tab)
+{
+	return tab ? tab->parent : NULL;
+}
+
+/**
+ * blkid_parttable_get_offset:
+ * @tab: partition table
+ *
+ * Note the position is relative to begin of the device as defined by
+ * blkid_probe_set_device() for primary partition table, and relative
+ * to parental partition for nested patition tables.
+ *
+ * <informalexample>
+ *   <programlisting>
+ * off_t offset;
+ * blkid_partition parent = blkid_parttable_get_parent(tab);
+ *
+ * offset = blkid_parttable_get_offset(tab);
+ *
+ * if (parent)
+ *      / * 'tab' is nested partition table * /
+ *	offset += blkid_partition_get_start(parent);
+ *   </programlisting>
+ * </informalexample>
+
+ * Returns: position (in bytes) of the partition table or -1 in case of error.
+ *
+ */
+blkid_loff_t blkid_parttable_get_offset(blkid_parttable tab)
+{
+	return tab ? tab->offset : -1;
+}
+
+/**
+ * blkid_partition_get_table:
+ * @par: partition
+ *
+ * The "parttable" describes partition table. The table is usually the same for
+ * all partitions -- except nested partition tables.
+ *
+ * For example bsd, solaris, etc. use a nested partition table within
+ * standard primary dos partition:
+ *
+ * <informalexample>
+ *   <programlisting>
+ *
+ *  -- dos partition table
+ *  0: sda1     dos primary partition
+ *  1: sda2     dos primary partition
+ *     -- bsd partition table (with in sda2)
+ *  2:    sda5  bds partition
+ *  3:    sda6  bds partition
+ *
+ *   </programlisting>
+ * </informalexample>
+ *
+ * The library does not to use a separate partition table object for dos logical
+ * partitions (partitions within extended partition). It's possible to
+ * differentiate between logical, extended and primary partitions by
+ *
+ *	blkid_partition_is_{extended,primary,logical}().
+ *
+ * Returns: partition table object or NULL in case of error.
+ */
+blkid_parttable blkid_partition_get_table(blkid_partition par)
+{
+	return par ? par->tab : NULL;
+}
+
+static int partition_get_logical_type(blkid_partition par)
+{
+	blkid_parttable tab;
+
+	if (!par)
+		return -1;
+
+	tab = blkid_partition_get_table(par);
+	if (!tab || !tab->type)
+		return -1;
+
+	if (tab->parent)
+		return 'L';  /* report nested partitions as logical */
+
+	if (!strcmp(tab->type, "dos")) {
+		if (par->partno > 4)
+			return 'L';	/* logical */
+
+	        if(par->type == MBR_DOS_EXTENDED_PARTITION ||
+                   par->type == MBR_W95_EXTENDED_PARTITION ||
+		   par->type == MBR_LINUX_EXTENDED_PARTITION)
+			return 'E';
+	}
+	return 'P';
+}
+
+/**
+ * blkid_partition_is_primary:
+ * @par: partition
+ *
+ * Note, this function returns FALSE for DOS extended partitions and
+ * all partitions in nested partition tables.
+ *
+ * Returns: 1 if the partitions is primary partition or 0 if not.
+ */
+int blkid_partition_is_primary(blkid_partition par)
+{
+	return partition_get_logical_type(par) == 'P' ? TRUE : FALSE;
+}
+
+/**
+ * blkid_partition_is_extended:
+ * @par: partition
+ *
+ * Returns: 1 if the partitions is extended (dos, windows or linux)
+ * partition or 0 if not.
+ */
+int blkid_partition_is_extended(blkid_partition par)
+{
+	return partition_get_logical_type(par) == 'E' ? TRUE : FALSE;
+}
+
+/**
+ * blkid_partition_is_logical:
+ * @par: partition
+ *
+ * Note that this function returns TRUE for all partitions in all
+ * nested partition tables (e.g. BSD labels).
+ *
+ * Returns: 1 if the partitions is logical partition or 0 if not.
+ */
+int blkid_partition_is_logical(blkid_partition par)
+{
+	return partition_get_logical_type(par) == 'L' ? TRUE : FALSE;
+}
+
+static void set_string(unsigned char *item, size_t max,
+				const unsigned char *data, size_t len)
+{
+	if (len >= max)
+		len = max - 1;
+
+	memcpy(item, data, len);
+	item[len] = '\0';
+
+	blkid_rtrim_whitespace(item);
+}
+
+int blkid_partition_set_name(blkid_partition par,
+		const unsigned char *name, size_t len)
+{
+	if (!par)
+		return -1;
+
+	set_string(par->name, sizeof(par->name), name, len);
+	return 0;
+}
+
+int blkid_partition_set_utf8name(blkid_partition par, const unsigned char *name,
+		size_t len, int enc)
+{
+	if (!par)
+		return -1;
+
+	blkid_encode_to_utf8(enc, par->name, sizeof(par->name), name, len);
+	blkid_rtrim_whitespace(par->name);
+	return 0;
+}
+
+int blkid_partition_set_uuid(blkid_partition par, const unsigned char *uuid)
+{
+	if (!par)
+		return -1;
+
+	blkid_unparse_uuid(uuid, par->uuid, sizeof(par->uuid));
+	return 0;
+}
+
+int blkid_partition_gen_uuid(blkid_partition par)
+{
+	if (!par || !par->tab || !*par->tab->id)
+		return -1;
+
+	snprintf(par->uuid, sizeof(par->uuid), "%s-%02x",
+			par->tab->id, par->partno);
+	return 0;
+}
+
+/**
+ * blkid_partition_get_name:
+ * @par: partition
+ *
+ * Returns: partition name string if supported by PT (e.g. Mac) or NULL.
+ */
+const char *blkid_partition_get_name(blkid_partition par)
+{
+	return par && *par->name ? (char *) par->name : NULL;
+}
+
+/**
+ * blkid_partition_get_uuid:
+ * @par: partition
+ *
+ * Returns: partition UUID string if supported by PT (e.g. GPT) or NULL.
+ */
+const char *blkid_partition_get_uuid(blkid_partition par)
+{
+	return par && *par->uuid ? par->uuid : NULL;
+}
+
+/**
+ * blkid_partition_get_partno:
+ * @par: partition
+ *
+ * Returns: proposed partitin number (e.g. 'N' from sda'N') or -1 in case of
+ * error. Note that the number is generate by library independenly on your OS.
+ */
+int blkid_partition_get_partno(blkid_partition par)
+{
+	return par ? par->partno : -1;
+}
+
+/**
+ * blkid_partition_get_start:
+ * @par: partition
+ *
+ * Be careful if you _not_ probe whole disk:
+ *
+ * 1) the offset is usully relative to begin of the disk -- but if you probe a
+ *    fragment of the disk only -- then the offset could be still relative to
+ *    the begin of the disk rather that relative to the fragment.
+ *
+ * 2) the offset for nested partitions could be releative to parent (e.g. Solaris)
+ *    _or_ relative to the begin of the whole disk (e.g. bsd).
+ *
+ * You don't have to care about such details if you proble whole disk. In such
+ * a case libblkid always returns the offset relative to the begin of the disk.
+ *
+ * Returns: start of the partition (in 512-sectors).
+ */
+blkid_loff_t blkid_partition_get_start(blkid_partition par)
+{
+	return par ? par->start : -1;
+}
+
+/**
+ * blkid_partition_get_size:
+ * @par: partition
+ *
+ * WARNING: be very careful when you work with MS-DOS extended partitions. The
+ *          library always returns full size of the partition. If you want add
+ *          the partition to the Linux system (BLKPG_ADD_PARTITION ioctl) you
+ *          need to reduce the size of the partition to 1 or 2 blocks. The
+ *          rest of the partition has to be unaccessible for mkfs or mkswap
+ *          programs, we need a small space for boot loaders only.
+ *
+ *          For some unknown reason this (safe) practice is not to used for
+ *          nested BSD, Solaris, ..., partition tables in Linux kernel.
+ *
+ * Returns: size of the partition (in 512-sectors).
+ */
+blkid_loff_t blkid_partition_get_size(blkid_partition par)
+{
+	return par ? par->size : -1;
+}
+
+/**
+ * blkid_partition_get_type:
+ * @par: partition
+ *
+ * Returns: partition type.
+ */
+int blkid_partition_get_type(blkid_partition par)
+{
+	return par->type;
+}
+
+/* Sets partition 'type' for PT where the type is defined by string rather
+ * than by number
+ */
+int blkid_partition_set_type_string(blkid_partition par,
+		const unsigned char *type, size_t len)
+{
+	if (!par)
+		return -1;
+
+	set_string((unsigned char *) par->typestr,
+			sizeof(par->typestr), type, len);
+	return 0;
+}
+
+/* Sets partition 'type' for PT where the type is defined by UUIDrather
+ * than by number
+ */
+int blkid_partition_set_type_uuid(blkid_partition par, const unsigned char *uuid)
+{
+	if (!par)
+		return -1;
+
+	blkid_unparse_uuid(uuid, par->typestr, sizeof(par->typestr));
+	return 0;
+}
+
+/**
+ * blkid_partition_get_type_string:
+ * @par: partition
+ *
+ * The type string is supported by a small subset of partition tables (e.g Mac
+ * and EFI GPT).  Note that GPT uses type UUID and this function returns this
+ * UUID as string.
+ *
+ * Returns: partition type string or NULL.
+ */
+const char *blkid_partition_get_type_string(blkid_partition par)
+{
+	return par && *par->typestr ? par->typestr : NULL;
+}
+
+
+int blkid_partition_set_flags(blkid_partition par, unsigned long long flags)
+{
+	if (!par)
+		return -1;
+	par->flags = flags;
+	return 0;
+}
+
+/**
+ * blkid_partition_get_flags
+ * @par: partition
+ *
+ * Returns: partition flags (or attributes for gpt).
+ */
+unsigned long long blkid_partition_get_flags(blkid_partition par)
+{
+	return par->flags;
+}
+
diff --git a/libblkid/src/partitions/partitions.h b/libblkid/src/partitions/partitions.h
new file mode 100644
index 0000000..3651bbb
--- /dev/null
+++ b/libblkid/src/partitions/partitions.h
@@ -0,0 +1,71 @@
+#ifndef BLKID_PARTITIONS_H
+#define BLKID_PARTITIONS_H
+
+#include "blkidP.h"
+#include "pt-mbr.h"
+
+extern int blkid_partitions_get_flags(blkid_probe pr);
+
+extern blkid_parttable blkid_partlist_new_parttable(blkid_partlist ls,
+				const char *type, blkid_loff_t offset);
+
+extern int blkid_parttable_set_uuid(blkid_parttable tab, const unsigned char *id);
+extern int blkid_parttable_set_id(blkid_parttable tab, const unsigned char *id);
+
+extern blkid_partition blkid_partlist_add_partition(blkid_partlist ls,
+				blkid_parttable tab,
+				blkid_loff_t start, blkid_loff_t size);
+
+extern int blkid_partlist_set_partno(blkid_partlist ls, int partno);
+extern int blkid_partlist_increment_partno(blkid_partlist ls);
+
+extern blkid_partition blkid_partlist_get_parent(blkid_partlist ls);
+
+extern int blkid_partitions_do_subprobe(blkid_probe pr,
+			blkid_partition parent, const struct blkid_idinfo *id);
+
+extern int blkid_partitions_need_typeonly(blkid_probe pr);
+extern int blkid_partitions_set_ptuuid(blkid_probe pr, unsigned char *uuid);
+extern int blkid_partitions_strcpy_ptuuid(blkid_probe pr, char *str);
+
+
+extern int blkid_is_nested_dimension(blkid_partition par,
+                        blkid_loff_t start, blkid_loff_t size);
+
+extern int blkid_partition_set_name(blkid_partition par,
+		const unsigned char *name, size_t len);
+
+extern int blkid_partition_set_utf8name(blkid_partition par,
+		const unsigned char *name, size_t len, int enc);
+
+extern int blkid_partition_set_uuid(blkid_partition par,
+		const unsigned char *uuid);
+extern int blkid_partition_gen_uuid(blkid_partition par);
+
+extern int blkid_partition_set_type(blkid_partition par, int type);
+
+extern int blkid_partition_set_type_string(blkid_partition par,
+                const unsigned char *type, size_t len);
+
+extern int blkid_partition_set_type_uuid(blkid_partition par,
+		const unsigned char *uuid);
+
+extern int blkid_partition_set_flags(blkid_partition par, unsigned long long flags);
+
+/*
+ * partition probers
+ */
+extern const struct blkid_idinfo aix_pt_idinfo;
+extern const struct blkid_idinfo bsd_pt_idinfo;
+extern const struct blkid_idinfo unixware_pt_idinfo;
+extern const struct blkid_idinfo solaris_x86_pt_idinfo;
+extern const struct blkid_idinfo sun_pt_idinfo;
+extern const struct blkid_idinfo sgi_pt_idinfo;
+extern const struct blkid_idinfo mac_pt_idinfo;
+extern const struct blkid_idinfo dos_pt_idinfo;
+extern const struct blkid_idinfo minix_pt_idinfo;
+extern const struct blkid_idinfo gpt_pt_idinfo;
+extern const struct blkid_idinfo pmbr_pt_idinfo;
+extern const struct blkid_idinfo ultrix_pt_idinfo;
+
+#endif /* BLKID_PARTITIONS_H */
diff --git a/libblkid/src/partitions/sgi.c b/libblkid/src/partitions/sgi.c
new file mode 100644
index 0000000..99c0bf1
--- /dev/null
+++ b/libblkid/src/partitions/sgi.c
@@ -0,0 +1,87 @@
+/*
+ * sgi partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+#include "pt-sgi.h"
+
+static int probe_sgi_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct sgi_disklabel *l;
+	struct sgi_partition *p;
+	blkid_parttable tab = NULL;
+	blkid_partlist ls;
+	int i;
+
+	l = (struct sgi_disklabel *) blkid_probe_get_sector(pr, 0);
+	if (!l) {
+		if (errno)
+			return -errno;
+		goto nothing;
+	}
+
+	if (sgi_pt_checksum(l)) {
+		DBG(LOWPROBE, ul_debug(
+			"detected corrupted sgi disk label -- ignore"));
+		goto nothing;
+	}
+
+	if (blkid_partitions_need_typeonly(pr))
+		/* caller does not ask for details about partitions */
+		return BLKID_PROBE_OK;
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		goto nothing;
+
+	tab = blkid_partlist_new_parttable(ls, "sgi", 0);
+	if (!tab)
+		goto err;
+
+	for(i = 0, p = &l->partitions[0]; i < SGI_MAXPARTITIONS; i++, p++) {
+		uint32_t size = be32_to_cpu(p->num_blocks);
+		uint32_t start = be32_to_cpu(p->first_block);
+		uint32_t type = be32_to_cpu(p->type);
+		blkid_partition par;
+
+		if (!size) {
+			blkid_partlist_increment_partno(ls);
+			continue;
+		}
+		par = blkid_partlist_add_partition(ls, tab, start, size);
+		if (!par)
+			goto err;
+
+		blkid_partition_set_type(par, type);
+	}
+
+	return BLKID_PROBE_OK;
+
+nothing:
+	return BLKID_PROBE_NONE;
+err:
+	return -ENOMEM;
+}
+
+const struct blkid_idinfo sgi_pt_idinfo =
+{
+	.name		= "sgi",
+	.probefunc	= probe_sgi_pt,
+	.magics		=
+	{
+		{ .magic = "\x0B\xE5\xA9\x41", .len = 4	},
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/partitions/solaris_x86.c b/libblkid/src/partitions/solaris_x86.c
new file mode 100644
index 0000000..4ac9be5
--- /dev/null
+++ b/libblkid/src/partitions/solaris_x86.c
@@ -0,0 +1,154 @@
+/*
+ * Solaris x86 partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+
+/*
+ * Solaris-x86 is always within primary dos partition (nested PT table).  The
+ * solaris-x86 vtoc allows to split the entire partition to "slices". The
+ * offset (start) of the slice is always relatively to the primary dos
+ * partition.
+ *
+ * Note that Solaris-SPARC uses entire disk with a different partitionning
+ * scheme.
+ */
+
+/* some other implementation than Linux kernel assume 8 partitions only */
+#define SOLARIS_MAXPARTITIONS	16
+
+/* disklabel (vtoc) location  */
+#define SOLARIS_SECTOR		1			/* in 512-sectors */
+#define SOLARIS_OFFSET		(SOLARIS_SECTOR << 9)	/* in bytes */
+#define SOLARIS_MAGICOFFSET	(SOLARIS_OFFSET + 12)	/* v_sanity offset in bytes */
+
+/* slice tags */
+#define SOLARIS_TAG_WHOLEDISK	5
+
+struct solaris_slice {
+	uint16_t s_tag;      /* ID tag of partition */
+	uint16_t s_flag;     /* permission flags */
+	uint32_t s_start;    /* start sector no of partition */
+	uint32_t s_size;     /* # of blocks in partition */
+} __attribute__((packed));
+
+struct solaris_vtoc {
+	unsigned int v_bootinfo[3];     /* info needed by mboot (unsupported) */
+
+	uint32_t     v_sanity;          /* to verify vtoc sanity */
+	uint32_t     v_version;         /* layout version */
+	char         v_volume[8];       /* volume name */
+	uint16_t     v_sectorsz;        /* sector size in bytes */
+	uint16_t     v_nparts;          /* number of partitions */
+	unsigned int v_reserved[10];    /* free space */
+
+	struct solaris_slice v_slice[SOLARIS_MAXPARTITIONS]; /* slices */
+
+	unsigned int timestamp[SOLARIS_MAXPARTITIONS]; /* timestamp (unsupported) */
+	char         v_asciilabel[128];	/* for compatibility */
+} __attribute__((packed));
+
+static int probe_solaris_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct solaris_vtoc *l;	/* disk label */
+	struct solaris_slice *p;	/* partitsion */
+	blkid_parttable tab = NULL;
+	blkid_partition parent;
+	blkid_partlist ls;
+	int i;
+	uint16_t nparts;
+
+	l = (struct solaris_vtoc *) blkid_probe_get_sector(pr, SOLARIS_SECTOR);
+	if (!l) {
+		if (errno)
+			return -errno;
+		goto nothing;
+	}
+
+	if (le32_to_cpu(l->v_version) != 1) {
+		DBG(LOWPROBE, ul_debug(
+			"WARNING: unsupported solaris x86 version %d, ignore",
+			le32_to_cpu(l->v_version)));
+		goto nothing;
+	}
+
+	if (blkid_partitions_need_typeonly(pr))
+		/* caller does not ask for details about partitions */
+		return BLKID_PROBE_OK;
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		goto nothing;
+
+	parent = blkid_partlist_get_parent(ls);
+
+	tab = blkid_partlist_new_parttable(ls, "solaris", SOLARIS_OFFSET);
+	if (!tab)
+		goto err;
+
+	nparts = le16_to_cpu(l->v_nparts);
+	if (nparts > SOLARIS_MAXPARTITIONS)
+		nparts = SOLARIS_MAXPARTITIONS;
+
+	for (i = 1, p = &l->v_slice[0];	i < nparts; i++, p++) {
+
+		uint32_t start = le32_to_cpu(p->s_start);
+		uint32_t size = le32_to_cpu(p->s_size);
+		blkid_partition par;
+
+		if (size == 0 || le16_to_cpu(p->s_tag) == SOLARIS_TAG_WHOLEDISK)
+			continue;
+
+		if (parent)
+			/* Solaris slices are relative to the parent (primary
+			 * DOS partition) */
+			start += blkid_partition_get_start(parent);
+
+		if (parent && !blkid_is_nested_dimension(parent, start, size)) {
+			DBG(LOWPROBE, ul_debug(
+				"WARNING: solaris partition (%d) overflow "
+				"detected, ignore", i));
+			continue;
+		}
+
+		par = blkid_partlist_add_partition(ls, tab, start, size);
+		if (!par)
+			goto err;
+
+		blkid_partition_set_type(par, le16_to_cpu(p->s_tag));
+		blkid_partition_set_flags(par, le16_to_cpu(p->s_flag));
+	}
+
+	return BLKID_PROBE_OK;
+
+nothing:
+	return BLKID_PROBE_NONE;
+err:
+	return -ENOMEM;
+}
+
+const struct blkid_idinfo solaris_x86_pt_idinfo =
+{
+	.name		= "solaris",
+	.probefunc	= probe_solaris_pt,
+	.magics		=
+	{
+		{
+		  .magic = "\xEE\xDE\x0D\x60",	/* little-endian magic string */
+		  .len = 4,			/* v_sanity size in bytes */
+		  .sboff = SOLARIS_MAGICOFFSET	/* offset of v_sanity */
+		},
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/partitions/sun.c b/libblkid/src/partitions/sun.c
new file mode 100644
index 0000000..22ee450
--- /dev/null
+++ b/libblkid/src/partitions/sun.c
@@ -0,0 +1,125 @@
+/*
+ * sun (solaris-sparc) partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#include "pt-sun.h"
+#include "partitions.h"
+
+static int probe_sun_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct sun_disklabel *l;
+	struct sun_partition *p;
+	blkid_parttable tab = NULL;
+	blkid_partlist ls;
+	uint16_t nparts;
+	blkid_loff_t spc;
+	int i, use_vtoc;
+
+	l = (struct sun_disklabel *) blkid_probe_get_sector(pr, 0);
+	if (!l) {
+		if (errno)
+			return -errno;
+		goto nothing;
+	}
+
+	if (sun_pt_checksum(l)) {
+		DBG(LOWPROBE, ul_debug(
+			"detected corrupted sun disk label -- ignore"));
+		goto nothing;
+	}
+
+	if (blkid_partitions_need_typeonly(pr))
+		/* caller does not ask for details about partitions */
+		return BLKID_PROBE_OK;
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		goto nothing;
+
+	tab = blkid_partlist_new_parttable(ls, "sun", 0);
+	if (!tab)
+		goto err;
+
+	/* sectors per cylinder (partition offset is in cylinders...) */
+	spc = be16_to_cpu(l->nhead) * be16_to_cpu(l->nsect);
+
+	DBG(LOWPROBE, ul_debug("Sun VTOC sanity=%u version=%u nparts=%u",
+			be32_to_cpu(l->vtoc.sanity),
+			be32_to_cpu(l->vtoc.version),
+			be16_to_cpu(l->vtoc.nparts)));
+
+	/* Check to see if we can use the VTOC table */
+	use_vtoc = ((be32_to_cpu(l->vtoc.sanity) == SUN_VTOC_SANITY) &&
+		    (be32_to_cpu(l->vtoc.version) == SUN_VTOC_VERSION) &&
+		    (be16_to_cpu(l->vtoc.nparts) <= SUN_MAXPARTITIONS));
+
+	/* Use 8 partition entries if not specified in validated VTOC */
+	nparts = use_vtoc ? be16_to_cpu(l->vtoc.nparts) : SUN_MAXPARTITIONS;
+
+	/*
+	 * So that old Linux-Sun partitions continue to work,
+	 * alow the VTOC to be used under the additional condition ...
+	 */
+	use_vtoc = use_vtoc || !(l->vtoc.sanity || l->vtoc.version || l->vtoc.nparts);
+
+	for (i = 0, p = l->partitions; i < nparts; i++, p++) {
+
+		blkid_loff_t start, size;
+		uint16_t type = 0, flags = 0;
+		blkid_partition par;
+
+		start = be32_to_cpu(p->start_cylinder) * spc;
+		size = be32_to_cpu(p->num_sectors);
+		if (use_vtoc) {
+			type = be16_to_cpu(l->vtoc.infos[i].id);
+			flags = be16_to_cpu(l->vtoc.infos[i].flags);
+		}
+
+		if (type == SUN_TAG_WHOLEDISK || !size) {
+			blkid_partlist_increment_partno(ls);
+			continue;
+		}
+		par = blkid_partlist_add_partition(ls, tab, start, size);
+		if (!par)
+			goto err;
+
+		if (type)
+			blkid_partition_set_type(par, type);
+		if (flags)
+			blkid_partition_set_flags(par, flags);
+	}
+	return BLKID_PROBE_OK;
+
+nothing:
+	return BLKID_PROBE_NONE;
+err:
+	return -ENOMEM;
+}
+
+
+const struct blkid_idinfo sun_pt_idinfo =
+{
+	.name		= "sun",
+	.probefunc	= probe_sun_pt,
+	.magics		=
+	{
+		{
+		  .magic = "\xDA\xBE",		/* big-endian magic string */
+		  .len = 2,
+		  .sboff = offsetof(struct sun_disklabel, magic)
+		},
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/partitions/ultrix.c b/libblkid/src/partitions/ultrix.c
new file mode 100644
index 0000000..9c060be
--- /dev/null
+++ b/libblkid/src/partitions/ultrix.c
@@ -0,0 +1,99 @@
+/*
+ * uktrix partition parsing code
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+
+#define ULTRIX_MAXPARTITIONS	8
+
+#define ULTRIX_MAGIC		0x032957
+#define ULTRIX_MAGIC_STR	"\x02\x29\x57"
+
+/* sector with partition table */
+#define ULTRIX_SECTOR		((16384 - sizeof(struct ultrix_disklabel)) >> 9)
+/* position of partition table within ULTRIX_SECTOR */
+#define ULTRIX_OFFSET		(512 - sizeof(struct ultrix_disklabel))
+
+struct ultrix_disklabel {
+	int32_t	pt_magic;	/* magic no. indicating part. info exits */
+	int32_t	pt_valid;	/* set by driver if pt is current */
+	struct  pt_info {
+		int32_t		pi_nblocks; /* no. of sectors */
+		uint32_t	pi_blkoff;  /* block offset for start */
+	} pt_part[ULTRIX_MAXPARTITIONS];
+} __attribute__((packed));
+
+
+static int probe_ultrix_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	unsigned char *data;
+	struct ultrix_disklabel *l;
+	blkid_parttable tab = NULL;
+	blkid_partlist ls;
+	int i;
+
+	data = blkid_probe_get_sector(pr, ULTRIX_SECTOR);
+	if (!data) {
+		if (errno)
+			return -errno;
+		goto nothing;
+	}
+
+	l = (struct ultrix_disklabel *) (data + ULTRIX_OFFSET);
+
+	if (l->pt_magic != ULTRIX_MAGIC || l->pt_valid != 1)
+		goto nothing;
+
+	if (blkid_probe_set_magic(pr, (ULTRIX_SECTOR << 9) + ULTRIX_OFFSET,
+			sizeof(ULTRIX_MAGIC_STR) - 1,
+			(unsigned char *) ULTRIX_MAGIC_STR))
+		goto err;
+
+	if (blkid_partitions_need_typeonly(pr))
+		/* caller does not ask for details about partitions */
+		return BLKID_PROBE_OK;
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		goto nothing;
+
+	tab = blkid_partlist_new_parttable(ls, "ultrix", 0);
+	if (!tab)
+		goto err;
+
+	for (i = 0; i < ULTRIX_MAXPARTITIONS; i++) {
+		if (!l->pt_part[i].pi_nblocks)
+			 blkid_partlist_increment_partno(ls);
+		else {
+			if (!blkid_partlist_add_partition(ls, tab,
+						l->pt_part[i].pi_blkoff,
+						l->pt_part[i].pi_nblocks))
+				goto err;
+		}
+	}
+
+	return BLKID_PROBE_OK;
+nothing:
+	return BLKID_PROBE_NONE;
+err:
+	return -ENOMEM;
+}
+
+const struct blkid_idinfo ultrix_pt_idinfo =
+{
+	.name		= "ultrix",
+	.probefunc	= probe_ultrix_pt,
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/partitions/unixware.c b/libblkid/src/partitions/unixware.c
new file mode 100644
index 0000000..6742bcf
--- /dev/null
+++ b/libblkid/src/partitions/unixware.c
@@ -0,0 +1,197 @@
+/*
+ * unixware partition parsing code
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ *
+ * The intersting information about unixware PT:
+ *   - Linux kernel / partx
+ *   - vtoc(7) SCO UNIX command man page
+ *   - evms source code (http://evms.sourceforge.net/)
+ *   - vxtools source code (http://martin.hinner.info/fs/vxfs/)
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "partitions.h"
+
+/* disklabel location */
+#define UNIXWARE_SECTOR		29
+#define UNIXWARE_OFFSET		(UNIXWARE_SECTOR << 9)	/* offset in bytes */
+#define UNIXWARE_KBOFFSET	(UNIXWARE_OFFSET >> 10)	/* offset in 1024-blocks */
+
+/* disklabel->d_magic offset within the last 1024 block */
+#define UNIXWARE_MAGICOFFSET	(UNIXWARE_OFFSET - UNIXWARE_KBOFFSET + 4)
+
+#define UNIXWARE_VTOCMAGIC	0x600DDEEEUL
+#define UNIXWARE_MAXPARTITIONS	16
+
+/* unixware_partition->s_label flags */
+#define UNIXWARE_TAG_UNUSED       0x0000  /* unused partition */
+#define UNIXWARE_TAG_BOOT         0x0001  /* boot fs */
+#define UNIXWARE_TAG_ROOT         0x0002  /* root fs */
+#define UNIXWARE_TAG_SWAP         0x0003  /* swap fs */
+#define UNIXWARE_TAG_USER         0x0004  /* user fs */
+#define UNIXWARE_TAG_ENTIRE_DISK  0x0005  /* whole disk */
+#define UNIXWARE_TAG_ALT_S        0x0006  /* alternate sector space */
+#define UNIXWARE_TAG_OTHER        0x0007  /* non unix */
+#define UNIXWARE_TAG_ALT_T        0x0008  /* alternate track space */
+#define UNIXWARE_TAG_STAND        0x0009  /* stand partition */
+#define UNIXWARE_TAG_VAR          0x000a  /* var partition */
+#define UNIXWARE_TAG_HOME         0x000b  /* home partition */
+#define UNIXWARE_TAG_DUMP         0x000c  /* dump partition */
+#define UNIXWARE_TAG_ALT_ST       0x000d  /* alternate sector track */
+#define UNIXWARE_TAG_VM_PUBLIC    0x000e  /* volume mgt public partition */
+#define UNIXWARE_TAG_VM_PRIVATE   0x000f  /* volume mgt private partition */
+
+
+/* unixware_partition->s_flags flags */
+#define UNIXWARE_FLAG_VALID	0x0200
+
+struct unixware_partition {
+	uint16_t	s_label;	/* partition label (tag) */
+	uint16_t	s_flags;	/* permission flags */
+	uint32_t	start_sect;	/* starting sector */
+	uint32_t	nr_sects;	/* number of sectors */
+} __attribute__((packed));
+
+struct unixware_disklabel {
+	uint32_t	d_type;		/* drive type */
+	uint32_t	d_magic;	/* the magic number */
+	uint32_t	d_version;	/* version number */
+	char		d_serial[12];	/* serial number of the device */
+	uint32_t	d_ncylinders;	/* # of data cylinders per device */
+	uint32_t	d_ntracks;	/* # of tracks per cylinder */
+	uint32_t	d_nsectors;	/* # of data sectors per track */
+	uint32_t	d_secsize;	/* # of bytes per sector */
+	uint32_t	d_part_start;	/* # of first sector of this partition */
+	uint32_t	d_unknown1[12];	/* ? */
+	uint32_t	d_alt_tbl;	/* byte offset of alternate table */
+	uint32_t	d_alt_len;	/* byte length of alternate table */
+	uint32_t	d_phys_cyl;	/* # of physical cylinders per device */
+	uint32_t	d_phys_trk;	/* # of physical tracks per cylinder */
+	uint32_t	d_phys_sec;	/* # of physical sectors per track */
+	uint32_t	d_phys_bytes;	/* # of physical bytes per sector */
+	uint32_t	d_unknown2;	/* ? */
+	uint32_t	d_unknown3;	/* ? */
+	uint32_t	d_pad[8];	/* pad */
+
+	struct unixware_vtoc {
+		uint32_t	v_magic;	/* the magic number */
+		uint32_t	v_version;	/* version number */
+		char		v_name[8];	/* volume name */
+		uint16_t	v_nslices;	/* # of partitions */
+		uint16_t	v_unknown1;	/* ? */
+		uint32_t	v_reserved[10];	/* reserved */
+
+		struct unixware_partition
+			v_slice[UNIXWARE_MAXPARTITIONS]; /* partition */
+	} __attribute__((packed)) vtoc;
+};
+
+static int probe_unixware_pt(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct unixware_disklabel *l;
+	struct unixware_partition *p;
+	blkid_parttable tab = NULL;
+	blkid_partition parent;
+	blkid_partlist ls;
+	int i;
+
+	l = (struct unixware_disklabel *)
+			blkid_probe_get_sector(pr, UNIXWARE_SECTOR);
+	if (!l) {
+		if (errno)
+			return -errno;
+		goto nothing;
+	}
+
+	if (le32_to_cpu(l->vtoc.v_magic) != UNIXWARE_VTOCMAGIC)
+		goto nothing;
+
+	if (blkid_partitions_need_typeonly(pr))
+		/* caller does not ask for details about partitions */
+		return BLKID_PROBE_OK;
+
+	ls = blkid_probe_get_partlist(pr);
+	if (!ls)
+		goto nothing;
+
+	parent = blkid_partlist_get_parent(ls);
+
+	tab = blkid_partlist_new_parttable(ls, "unixware", UNIXWARE_OFFSET);
+	if (!tab)
+		goto err;
+
+	/* Skip the first partition that describe whole disk
+	 */
+	for (i = 1, p = &l->vtoc.v_slice[1];
+			i < UNIXWARE_MAXPARTITIONS; i++, p++) {
+
+		uint32_t start, size;
+		uint16_t tag, flg;
+		blkid_partition par;
+
+		tag = le16_to_cpu(p->s_label);
+		flg = le16_to_cpu(p->s_flags);
+
+		if (tag == UNIXWARE_TAG_UNUSED ||
+		    tag == UNIXWARE_TAG_ENTIRE_DISK ||
+		    flg != UNIXWARE_FLAG_VALID)
+			continue;
+
+		start = le32_to_cpu(p->start_sect);
+		size = le32_to_cpu(p->nr_sects);
+
+		if (parent && !blkid_is_nested_dimension(parent, start, size)) {
+			DBG(LOWPROBE, ul_debug(
+				"WARNING: unixware partition (%d) overflow "
+				"detected, ignore", i));
+			continue;
+		}
+
+		par = blkid_partlist_add_partition(ls, tab, start, size);
+		if (!par)
+			goto err;
+
+		blkid_partition_set_type(par, tag);
+		blkid_partition_set_flags(par, flg);
+	}
+
+	return BLKID_PROBE_OK;
+
+nothing:
+	return BLKID_PROBE_NONE;
+err:
+	return -ENOMEM;
+}
+
+
+/*
+ * The unixware partition table is within primary DOS partition.  The PT is
+ * located on 29 sector, PT magic string is d_magic member of 'struct
+ * unixware_disklabel'.
+ */
+const struct blkid_idinfo unixware_pt_idinfo =
+{
+	.name		= "unixware",
+	.probefunc	= probe_unixware_pt,
+	.minsz		= 1024 * 1440 + 1,		/* ignore floppies */
+	.magics		=
+	{
+		{
+		  .magic = "\x0D\x60\xE5\xCA",	/* little-endian magic string */
+		  .len = 4,			/* d_magic size in bytes */
+		  .kboff = UNIXWARE_KBOFFSET,
+		  .sboff = UNIXWARE_MAGICOFFSET
+		},
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/pathnames.h b/libblkid/src/pathnames.h
new file mode 100644
index 0000000..0d21b98
--- /dev/null
+++ b/libblkid/src/pathnames.h
@@ -0,0 +1,196 @@
+/*
+ * Vaguely based on
+ *	@(#)pathnames.h	5.3 (Berkeley) 5/9/89
+ * This code is in the public domain.
+ */
+#ifndef PATHNAMES_H
+#define PATHNAMES_H
+
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+
+#ifndef __STDC__
+# error "we need an ANSI compiler"
+#endif
+
+/* used by kernel in /proc (e.g. /proc/swaps) for deleted files */
+#define PATH_DELETED_SUFFIX	"\\040(deleted)"
+#define PATH_DELETED_SUFFIX_SZ	(sizeof(PATH_DELETED_SUFFIX) - 1)
+
+/* DEFPATHs from <paths.h> don't include /usr/local */
+#undef _PATH_DEFPATH
+#define	_PATH_DEFPATH	        "/usr/local/bin:/bin:/usr/bin"
+
+#undef _PATH_DEFPATH_ROOT
+#define	_PATH_DEFPATH_ROOT	"/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
+
+#define _PATH_SECURETTY		"/etc/securetty"
+#define _PATH_WTMPLOCK		"/etc/wtmplock"
+
+#define	_PATH_HUSHLOGIN		".hushlogin"
+#define	_PATH_HUSHLOGINS	"/etc/hushlogins"
+
+#define _PATH_NOLOGIN_TXT	"/etc/nologin.txt"
+
+#ifndef _PATH_MAILDIR
+#define	_PATH_MAILDIR		"/var/spool/mail"
+#endif
+#define	_PATH_MOTDFILE		"/etc/motd"
+#define	_PATH_NOLOGIN		"/etc/nologin"
+#define	_PATH_VAR_NOLOGIN	"/var/run/nologin"
+
+#define _PATH_LOGIN		"/bin/login"
+#define _PATH_INITTAB		"/etc/inittab"
+#define _PATH_RC		"/etc/rc"
+#define _PATH_REBOOT		"/sbin/reboot"
+#define _PATH_SHUTDOWN		"/sbin/shutdown"
+#define _PATH_SINGLE		"/etc/singleboot"
+#define _PATH_SHUTDOWN_CONF	"/etc/shutdown.conf"
+
+#define _PATH_SECURE		"/etc/securesingle"
+#define _PATH_USERTTY           "/etc/usertty"
+
+#define _PATH_TERMCOLORS_DIRNAME "terminal-colors.d"
+#define _PATH_TERMCOLORS_DIR	"/etc/" _PATH_TERMCOLORS_DIRNAME
+
+/* used in login-utils/shutdown.c */
+
+/* used in login-utils/setpwnam.h and login-utils/islocal.c */
+#define _PATH_PASSWD		"/etc/passwd"
+
+/* used in login-utils/newgrp and login-utils/setpwnam.h*/
+#define _PATH_GSHADOW		"/etc/gshadow"
+
+/* used in login-utils/setpwnam.h */
+#define _PATH_GROUP		"/etc/group"
+#define _PATH_SHADOW_PASSWD	"/etc/shadow"
+#define _PATH_SHELLS		"/etc/shells"
+
+/* used in term-utils/agetty.c */
+#define _PATH_ISSUE		"/etc/issue"
+#define _PATH_OS_RELEASE	"/etc/os-release"
+#define _PATH_NUMLOCK_ON	_PATH_LOCALSTATEDIR "/numlock-on"
+
+#define _PATH_LOGINDEFS		"/etc/login.defs"
+
+/* used in misc-utils/look.c */
+#define _PATH_WORDS             "/usr/share/dict/words"
+#define _PATH_WORDS_ALT         "/usr/share/dict/web2"
+
+/* mount paths */
+#define _PATH_UMOUNT		"/bin/umount"
+
+#define _PATH_FILESYSTEMS	"/etc/filesystems"
+#define _PATH_PROC_SWAPS	"/proc/swaps"
+#define _PATH_PROC_FILESYSTEMS	"/proc/filesystems"
+#define _PATH_PROC_MOUNTS	"/proc/mounts"
+#define _PATH_PROC_PARTITIONS	"/proc/partitions"
+#define _PATH_PROC_DEVICES	"/proc/devices"
+#define _PATH_PROC_MOUNTINFO	"/proc/self/mountinfo"
+#define _PATH_PROC_LOCKS        "/proc/locks"
+#define _PATH_PROC_CDROMINFO	"/proc/sys/dev/cdrom/info"
+
+#define _PATH_PROC_UIDMAP	"/proc/self/uid_map"
+#define _PATH_PROC_GIDMAP	"/proc/self/gid_map"
+
+#define _PATH_PROC_ATTR_CURRENT	"/proc/self/attr/current"
+#define _PATH_PROC_ATTR_EXEC	"/proc/self/attr/exec"
+#define _PATH_PROC_CAPLASTCAP	"/proc/sys/kernel/cap_last_cap"
+
+
+#define _PATH_SYS_BLOCK		"/sys/block"
+#define _PATH_SYS_DEVBLOCK	"/sys/dev/block"
+#define _PATH_SYS_CLASS		"/sys/class"
+#define _PATH_SYS_SCSI		"/sys/bus/scsi"
+
+#define _PATH_SYS_SELINUX	"/sys/fs/selinux"
+#define _PATH_SYS_APPARMOR	"/sys/kernel/security/apparmor"
+
+#ifndef _PATH_MOUNTED
+# ifdef MOUNTED					/* deprecated */
+#  define _PATH_MOUNTED		MOUNTED
+# else
+#  define _PATH_MOUNTED		"/etc/mtab"
+# endif
+#endif
+
+#ifndef _PATH_MNTTAB
+# ifdef MNTTAB					/* deprecated */
+#  define _PATH_MNTTAB		MNTTAB
+# else
+#  define _PATH_MNTTAB		"/etc/fstab"
+# endif
+#endif
+
+#define _PATH_MNTTAB_DIR	_PATH_MNTTAB ".d"
+
+#define _PATH_MOUNTED_LOCK	_PATH_MOUNTED "~"
+#define _PATH_MOUNTED_TMP	_PATH_MOUNTED ".tmp"
+
+#ifndef _PATH_DEV
+  /*
+   * The tailing '/' in _PATH_DEV is there for compatibility with libc.
+   */
+# define _PATH_DEV		"/dev/"
+#endif
+
+#define _PATH_DEV_MEM		"/dev/mem"
+
+#define _PATH_DEV_LOOP		"/dev/loop"
+#define _PATH_DEV_LOOPCTL	"/dev/loop-control"
+#define _PATH_DEV_TTY		"/dev/tty"
+
+
+/* udev paths */
+#define _PATH_DEV_BYLABEL	"/dev/disk/by-label"
+#define _PATH_DEV_BYUUID	"/dev/disk/by-uuid"
+#define _PATH_DEV_BYID		"/dev/disk/by-id"
+#define _PATH_DEV_BYPATH	"/dev/disk/by-path"
+#define _PATH_DEV_BYPARTLABEL	"/dev/disk/by-partlabel"
+#define _PATH_DEV_BYPARTUUID	"/dev/disk/by-partuuid"
+
+/* hwclock paths */
+#ifdef CONFIG_ADJTIME_PATH
+# define _PATH_ADJTIME		CONFIG_ADJTIME_PATH
+#else
+# define _PATH_ADJTIME		"/etc/adjtime"
+#endif
+
+#define _PATH_LASTDATE		"/var/lib/lastdate"
+#ifdef __ia64__
+# define _PATH_RTC_DEV		"/dev/efirtc"
+#else
+# define _PATH_RTC_DEV		"/dev/rtc"
+#endif
+
+#ifndef _PATH_BTMP
+#define _PATH_BTMP		"/var/log/btmp"
+#endif
+
+/* raw paths*/
+#define _PATH_RAWDEVDIR		"/dev/raw/"
+#define _PATH_RAWDEVCTL		_PATH_RAWDEVDIR "rawctl"
+/* deprecated */
+#define _PATH_RAWDEVCTL_OLD	"/dev/rawctl"
+
+/* wdctl path */
+#define _PATH_WATCHDOG_DEV	"/dev/watchdog"
+
+/* ipc paths */
+#define _PATH_PROC_SYSV_MSG	"/proc/sysvipc/msg"
+#define _PATH_PROC_SYSV_SEM	"/proc/sysvipc/sem"
+#define _PATH_PROC_SYSV_SHM	"/proc/sysvipc/shm"
+#define _PATH_PROC_IPC_MSGMAX	"/proc/sys/kernel/msgmax"
+#define _PATH_PROC_IPC_MSGMNB	"/proc/sys/kernel/msgmnb"
+#define _PATH_PROC_IPC_MSGMNI	"/proc/sys/kernel/msgmni"
+#define _PATH_PROC_IPC_SEM	"/proc/sys/kernel/sem"
+#define _PATH_PROC_IPC_SHMALL	"/proc/sys/kernel/shmall"
+#define _PATH_PROC_IPC_SHMMAX	"/proc/sys/kernel/shmmax"
+#define _PATH_PROC_IPC_SHMMNI	"/proc/sys/kernel/shmmni"
+
+/* kernel command line */
+#define _PATH_PROC_CMDLINE	"/proc/cmdline"
+
+#endif /* PATHNAMES_H */
+
diff --git a/libblkid/src/probe.c b/libblkid/src/probe.c
new file mode 100644
index 0000000..d476b7a
--- /dev/null
+++ b/libblkid/src/probe.c
@@ -0,0 +1,1815 @@
+/*
+ * Low-level libblkid probing API
+ *
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: lowprobe
+ * @title: Low-level probing
+ * @short_description: low-level prober initialization
+ *
+ * The low-level probing routines always and directly read information from
+ * the selected (see blkid_probe_set_device()) device.
+ *
+ * The probing routines are grouped together into separate chains. Currently,
+ * the library provides superblocks, partitions and topology chains.
+ *
+ * The probing routines is possible to filter (enable/disable) by type (e.g.
+ * fstype "vfat" or partype "gpt") or by usage flags (e.g. BLKID_USAGE_RAID).
+ * These filters are per-chain. Note that always when you touch the chain
+ * filter the current probing position is reset and probing starts from
+ * scratch.  It means that the chain filter should not be modified during
+ * probing, for example in loop where you call blkid_do_probe().
+ *
+ * For more details see the chain specific documentation.
+ *
+ * The low-level API provides two ways how access to probing results.
+ *
+ *   1. The NAME=value (tag) interface. This interface is older and returns all data
+ *      as strings. This interface is generic for all chains.
+ *
+ *   2. The binary interfaces. These interfaces return data in the native formats.
+ *      The interface is always specific to the probing chain.
+ *
+ *  Note that the previous probing result (binary or NAME=value) is always
+ *  zeroized when a chain probing function is called. For example:
+ *
+ * <informalexample>
+ *   <programlisting>
+ *     blkid_probe_enable_partitions(pr, TRUE);
+ *     blkid_probe_enable_superblocks(pr, FALSE);
+ *
+ *     blkid_do_safeprobe(pr);
+ *   </programlisting>
+ * </informalexample>
+ *
+ * overwrites the previous probing result for the partitions chain, the superblocks
+ * result is not modified.
+ */
+
+/**
+ * SECTION: lowprobe-tags
+ * @title: Low-level tags
+ * @short_description: generic NAME=value interface.
+ *
+ * The probing routines inside the chain are mutually exclusive by default --
+ * only few probing routines are marked as "tolerant". The "tolerant" probing
+ * routines are used for filesystem which can share the same device with any
+ * other filesystem. The blkid_do_safeprobe() checks for the "tolerant" flag.
+ *
+ * The SUPERBLOCKS chain is enabled by default. The all others chains is
+ * necessary to enable by blkid_probe_enable_'CHAINNAME'(). See chains specific
+ * documentation.
+ *
+ * The blkid_do_probe() function returns a result from only one probing
+ * routine, and the next call from the next probing routine. It means you need
+ * to call the function in loop, for example:
+ *
+ * <informalexample>
+ *   <programlisting>
+ *	while((blkid_do_probe(pr) == 0)
+ *		... use result ...
+ *   </programlisting>
+ * </informalexample>
+ *
+ * The blkid_do_safeprobe() is the same as blkid_do_probe(), but returns only
+ * first probing result for every enabled chain. This function checks for
+ * ambivalent results (e.g. more "intolerant" filesystems superblocks on the
+ * device).
+ *
+ * The probing result is set of NAME=value pairs (the NAME is always unique).
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#ifdef HAVE_LINUX_CDROM_H
+#include <linux/cdrom.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <limits.h>
+
+#ifdef HAVE_LIBUUID
+# include <uuid.h>
+#endif
+
+#include "blkidP.h"
+#include <blkid.h>
+#include "all-io.h"
+#include "sysfs.h"
+#include "strutils.h"
+
+/* chains */
+extern const struct blkid_chaindrv superblocks_drv;
+extern const struct blkid_chaindrv topology_drv;
+extern const struct blkid_chaindrv partitions_drv;
+
+/*
+ * All supported chains
+ */
+static const struct blkid_chaindrv *chains_drvs[] = {
+	[BLKID_CHAIN_SUBLKS] = &superblocks_drv,
+	[BLKID_CHAIN_TOPLGY] = &topology_drv,
+	[BLKID_CHAIN_PARTS] = &partitions_drv
+};
+
+static void blkid_probe_reset_vals(blkid_probe pr);
+static void blkid_probe_reset_buffer(blkid_probe pr);
+
+/**
+ * blkid_new_probe:
+ *
+ * Returns: a pointer to the newly allocated probe struct or NULL in case of error.
+ */
+blkid_probe blkid_new_probe(void)
+{
+	int i;
+	blkid_probe pr;
+
+	blkid_init_debug(0);
+	pr = calloc(1, sizeof(struct blkid_struct_probe));
+	if (!pr)
+		return NULL;
+
+	DBG(LOWPROBE, ul_debug("allocate a new probe %p", pr));
+
+	/* initialize chains */
+	for (i = 0; i < BLKID_NCHAINS; i++) {
+		pr->chains[i].driver = chains_drvs[i];
+		pr->chains[i].flags = chains_drvs[i]->dflt_flags;
+		pr->chains[i].enabled = chains_drvs[i]->dflt_enabled;
+	}
+	INIT_LIST_HEAD(&pr->buffers);
+	return pr;
+}
+
+/*
+ * Clone @parent, the new clone shares all, but except:
+ *
+ *	- probing result
+ *	- bufferes if another device (or offset) is set to the prober
+ */
+blkid_probe blkid_clone_probe(blkid_probe parent)
+{
+	blkid_probe pr;
+
+	if (!parent)
+		return NULL;
+
+	DBG(LOWPROBE, ul_debug("allocate a probe clone"));
+
+	pr = blkid_new_probe();
+	if (!pr)
+		return NULL;
+
+	pr->fd = parent->fd;
+	pr->off = parent->off;
+	pr->size = parent->size;
+	pr->devno = parent->devno;
+	pr->disk_devno = parent->disk_devno;
+	pr->blkssz = parent->blkssz;
+	pr->flags = parent->flags;
+	pr->parent = parent;
+
+	pr->flags &= ~BLKID_FL_PRIVATE_FD;
+
+	return pr;
+}
+
+
+
+/**
+ * blkid_new_probe_from_filename:
+ * @filename: device or regular file
+ *
+ * This function is same as call open(filename), blkid_new_probe() and
+ * blkid_probe_set_device(pr, fd, 0, 0).
+ *
+ * The @filename is closed by blkid_free_probe() or by the
+ * blkid_probe_set_device() call.
+ *
+ * Returns: a pointer to the newly allocated probe struct or NULL in case of
+ * error.
+ */
+blkid_probe blkid_new_probe_from_filename(const char *filename)
+{
+	int fd = -1;
+	blkid_probe pr = NULL;
+
+	if (!filename)
+		return NULL;
+
+	fd = open(filename, O_RDONLY|O_CLOEXEC);
+	if (fd < 0)
+		return NULL;
+
+	pr = blkid_new_probe();
+	if (!pr)
+		goto err;
+
+	if (blkid_probe_set_device(pr, fd, 0, 0))
+		goto err;
+
+	pr->flags |= BLKID_FL_PRIVATE_FD;
+	return pr;
+err:
+	if (fd >= 0)
+		close(fd);
+	blkid_free_probe(pr);
+	return NULL;
+}
+
+/**
+ * blkid_free_probe:
+ * @pr: probe
+ *
+ * Deallocates the probe struct, buffers and all allocated
+ * data that are associated with this probing control struct.
+ */
+void blkid_free_probe(blkid_probe pr)
+{
+	int i;
+
+	if (!pr)
+		return;
+
+	for (i = 0; i < BLKID_NCHAINS; i++) {
+		struct blkid_chain *ch = &pr->chains[i];
+
+		if (ch->driver->free_data)
+			ch->driver->free_data(pr, ch->data);
+		free(ch->fltr);
+	}
+
+	if ((pr->flags & BLKID_FL_PRIVATE_FD) && pr->fd >= 0)
+		close(pr->fd);
+	blkid_probe_reset_buffer(pr);
+	blkid_free_probe(pr->disk_probe);
+
+	DBG(LOWPROBE, ul_debug("free probe %p", pr));
+	free(pr);
+}
+
+
+/*
+ * Removes chain values from probing result.
+ */
+void blkid_probe_chain_reset_vals(blkid_probe pr, struct blkid_chain *chn)
+{
+	int nvals = pr->nvals;
+	int i, x;
+
+	for (x = 0, i = 0; i < pr->nvals; i++) {
+		struct blkid_prval *v = &pr->vals[i];
+
+		if (v->chain != chn && x == i) {
+			x++;
+			continue;
+		}
+		if (v->chain == chn) {
+			--nvals;
+			continue;
+		}
+		memcpy(&pr->vals[x++], v, sizeof(struct blkid_prval));
+	}
+	pr->nvals = nvals;
+}
+
+static void blkid_probe_chain_reset_position(struct blkid_chain *chn)
+{
+	if (chn)
+		chn->idx = -1;
+}
+
+/*
+ * Copies chain values from probing result to @vals, the max size of @vals is
+ * @nvals and returns real number of values.
+ */
+int blkid_probe_chain_copy_vals(blkid_probe pr, struct blkid_chain *chn,
+		struct blkid_prval *vals, int nvals)
+{
+	int i, x;
+
+	for (x = 0, i = 0; i < pr->nvals && x < nvals; i++) {
+		struct blkid_prval *v = &pr->vals[i];
+
+		if (v->chain != chn)
+			continue;
+		memcpy(&vals[x++], v, sizeof(struct blkid_prval));
+	}
+	return x;
+}
+
+/*
+ * Appends values from @vals to the probing result
+ */
+void blkid_probe_append_vals(blkid_probe pr, struct blkid_prval *vals, int nvals)
+{
+	int i = 0;
+
+	while (i < nvals && pr->nvals < BLKID_NVALS) {
+		memcpy(&pr->vals[pr->nvals++], &vals[i++],
+				sizeof(struct blkid_prval));
+	}
+}
+
+static void blkid_probe_reset_vals(blkid_probe pr)
+{
+	memset(pr->vals, 0, sizeof(pr->vals));
+	pr->nvals = 0;
+}
+
+struct blkid_chain *blkid_probe_get_chain(blkid_probe pr)
+{
+	return pr->cur_chain;
+}
+
+static const char *blkid_probe_get_probername(blkid_probe pr)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+	if (chn && chn->idx >= 0 && chn->idx < chn->driver->nidinfos)
+		return chn->driver->idinfos[chn->idx]->name;
+
+	return NULL;
+}
+
+void *blkid_probe_get_binary_data(blkid_probe pr, struct blkid_chain *chn)
+{
+	int rc, org_prob_flags;
+	struct blkid_chain *org_chn;
+
+	/* save the current setting -- the binary API has to be completely
+	 * independent on the current probing status
+	 */
+	org_chn = pr->cur_chain;
+	org_prob_flags = pr->prob_flags;
+
+	pr->cur_chain = chn;
+	pr->prob_flags = 0;
+	chn->binary = TRUE;
+	blkid_probe_chain_reset_position(chn);
+
+	rc = chn->driver->probe(pr, chn);
+
+	chn->binary = FALSE;
+	blkid_probe_chain_reset_position(chn);
+
+	/* restore the original setting
+	 */
+	pr->cur_chain = org_chn;
+	pr->prob_flags = org_prob_flags;
+
+	if (rc != 0)
+		return NULL;
+
+	DBG(LOWPROBE, ul_debug("returning %s binary data", chn->driver->name));
+	return chn->data;
+}
+
+
+/**
+ * blkid_reset_probe:
+ * @pr: probe
+ *
+ * Zeroize probing results and resets the current probing (this has impact to
+ * blkid_do_probe() only). This function does not touch probing filters and
+ * keeps assigned device.
+ */
+void blkid_reset_probe(blkid_probe pr)
+{
+	int i;
+
+	if (!pr)
+		return;
+
+	blkid_probe_reset_vals(pr);
+	blkid_probe_set_wiper(pr, 0, 0);
+
+	pr->cur_chain = NULL;
+
+	for (i = 0; i < BLKID_NCHAINS; i++)
+		blkid_probe_chain_reset_position(&pr->chains[i]);
+}
+
+/***
+static int blkid_probe_dump_filter(blkid_probe pr, int chain)
+{
+	struct blkid_chain *chn;
+	int i;
+
+	if (!pr || chain < 0 || chain >= BLKID_NCHAINS)
+		return -1;
+
+	chn = &pr->chains[chain];
+
+	if (!chn->fltr)
+		return -1;
+
+	for (i = 0; i < chn->driver->nidinfos; i++) {
+		const struct blkid_idinfo *id = chn->driver->idinfos[i];
+
+		DBG(LOWPROBE, ul_debug("%d: %s: %s",
+			i,
+			id->name,
+			blkid_bmp_get_item(chn->fltr, i)
+				? "disabled" : "enabled <--"));
+	}
+	return 0;
+}
+***/
+
+/*
+ * Returns properly initialized chain filter
+ */
+unsigned long *blkid_probe_get_filter(blkid_probe pr, int chain, int create)
+{
+	struct blkid_chain *chn;
+
+	if (chain < 0 || chain >= BLKID_NCHAINS)
+		return NULL;
+
+	chn = &pr->chains[chain];
+
+	/* always when you touch the chain filter all indexes are reset and
+	 * probing starts from scratch
+	 */
+	blkid_probe_chain_reset_position(chn);
+	pr->cur_chain = NULL;
+
+	if (!chn->driver->has_fltr || (!chn->fltr && !create))
+		return NULL;
+
+	if (!chn->fltr)
+		chn->fltr = calloc(1, blkid_bmp_nbytes(chn->driver->nidinfos));
+	else
+		memset(chn->fltr, 0, blkid_bmp_nbytes(chn->driver->nidinfos));
+
+	/* blkid_probe_dump_filter(pr, chain); */
+	return chn->fltr;
+}
+
+/*
+ * Generic private functions for filter setting
+ */
+int __blkid_probe_invert_filter(blkid_probe pr, int chain)
+{
+	size_t i;
+	struct blkid_chain *chn;
+
+	chn = &pr->chains[chain];
+
+	if (!chn->driver->has_fltr || !chn->fltr)
+		return -1;
+
+	for (i = 0; i < blkid_bmp_nwords(chn->driver->nidinfos); i++)
+		chn->fltr[i] = ~chn->fltr[i];
+
+	DBG(LOWPROBE, ul_debug("probing filter inverted"));
+	/* blkid_probe_dump_filter(pr, chain); */
+	return 0;
+}
+
+int __blkid_probe_reset_filter(blkid_probe pr, int chain)
+{
+	return blkid_probe_get_filter(pr, chain, FALSE) ? 0 : -1;
+}
+
+int __blkid_probe_filter_types(blkid_probe pr, int chain, int flag, char *names[])
+{
+	unsigned long *fltr;
+	struct blkid_chain *chn;
+	size_t i;
+
+	fltr = blkid_probe_get_filter(pr, chain, TRUE);
+	if (!fltr)
+		return -1;
+
+	chn = &pr->chains[chain];
+
+	for (i = 0; i < chn->driver->nidinfos; i++) {
+		int has = 0;
+		const struct blkid_idinfo *id = chn->driver->idinfos[i];
+		char **n;
+
+		for (n = names; *n; n++) {
+			if (!strcmp(id->name, *n)) {
+				has = 1;
+				break;
+			}
+		}
+		if (flag & BLKID_FLTR_ONLYIN) {
+		       if (!has)
+				blkid_bmp_set_item(fltr, i);
+		} else if (flag & BLKID_FLTR_NOTIN) {
+			if (has)
+				blkid_bmp_set_item(fltr, i);
+		}
+	}
+
+	DBG(LOWPROBE, ul_debug("%s: a new probing type-filter initialized",
+		chn->driver->name));
+	/* blkid_probe_dump_filter(pr, chain); */
+	return 0;
+}
+
+unsigned char *blkid_probe_get_buffer(blkid_probe pr,
+				blkid_loff_t off, blkid_loff_t len)
+{
+	struct list_head *p;
+	struct blkid_bufinfo *bf = NULL;
+
+	if (pr->size <= 0) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	if (pr->parent &&
+	    pr->parent->devno == pr->devno &&
+	    pr->parent->off <= pr->off &&
+	    pr->parent->off + pr->parent->size >= pr->off + pr->size) {
+		/*
+		 * This is a cloned prober and points to the same area as
+		 * parent. Let's use parent's buffers.
+		 *
+		 * Note that pr->off (and pr->parent->off) is always from the
+		 * beginig of the device.
+		 */
+		return blkid_probe_get_buffer(pr->parent,
+				pr->off + off - pr->parent->off, len);
+	}
+
+	list_for_each(p, &pr->buffers) {
+		struct blkid_bufinfo *x =
+				list_entry(p, struct blkid_bufinfo, bufs);
+
+		if (x->off <= off && off + len <= x->off + x->len) {
+			DBG(LOWPROBE, ul_debug("\treuse buffer: off=%jd len=%jd pr=%p",
+							x->off, x->len, pr));
+			bf = x;
+			break;
+		}
+	}
+	if (!bf) {
+		ssize_t ret;
+
+		if (blkid_llseek(pr->fd, pr->off + off, SEEK_SET) < 0) {
+			errno = 0;
+			return NULL;
+		}
+
+		/* someone trying to overflow some buffers? */
+		if (len > ULONG_MAX - sizeof(struct blkid_bufinfo)) {
+			errno = ENOMEM;
+			return NULL;
+		}
+
+		/* allocate info and space for data by why call */
+		bf = calloc(1, sizeof(struct blkid_bufinfo) + len);
+		if (!bf) {
+			errno = ENOMEM;
+			return NULL;
+		}
+
+		bf->data = ((unsigned char *) bf) + sizeof(struct blkid_bufinfo);
+		bf->len = len;
+		bf->off = off;
+		INIT_LIST_HEAD(&bf->bufs);
+
+		DBG(LOWPROBE, ul_debug("\tbuffer read: off=%jd len=%jd pr=%p",
+				off, len, pr));
+
+		ret = read(pr->fd, bf->data, len);
+		if (ret != (ssize_t) len) {
+			DBG(LOWPROBE, ul_debug("\tbuffer read: return %zd error %m", ret));
+			free(bf);
+			if (ret >= 0)
+				errno = 0;
+			return NULL;
+		}
+		list_add_tail(&bf->bufs, &pr->buffers);
+	}
+
+	errno = 0;
+	return off ? bf->data + (off - bf->off) : bf->data;
+}
+
+
+static void blkid_probe_reset_buffer(blkid_probe pr)
+{
+	uint64_t read_ct = 0, len_ct = 0;
+
+	if (!pr || list_empty(&pr->buffers))
+		return;
+
+	DBG(LOWPROBE, ul_debug("reseting probing buffers pr=%p", pr));
+
+	while (!list_empty(&pr->buffers)) {
+		struct blkid_bufinfo *bf = list_entry(pr->buffers.next,
+						struct blkid_bufinfo, bufs);
+		read_ct++;
+		len_ct += bf->len;
+		list_del(&bf->bufs);
+		free(bf);
+	}
+
+	DBG(LOWPROBE, ul_debug("buffers summary: %"PRIu64" bytes "
+			"by %"PRIu64" read() call(s)",
+			len_ct, read_ct));
+
+	INIT_LIST_HEAD(&pr->buffers);
+}
+
+/*
+ * Small devices need a special care.
+ */
+int blkid_probe_is_tiny(blkid_probe pr)
+{
+	return pr->flags & BLKID_FL_TINY_DEV;
+}
+
+/*
+ * CDROMs may fail when probed for RAID (last sector problem)
+ */
+int blkid_probe_is_cdrom(blkid_probe pr)
+{
+	return pr->flags & BLKID_FL_CDROM_DEV;
+}
+
+/**
+ * blkid_probe_set_device:
+ * @pr: probe
+ * @fd: device file descriptor
+ * @off: begin of probing area
+ * @size: size of probing area (zero means whole device/file)
+ *
+ * Assigns the device to probe control struct, resets internal buffers and
+ * resets the current probing.
+ *
+ * Returns: -1 in case of failure, or 0 on success.
+ */
+int blkid_probe_set_device(blkid_probe pr, int fd,
+		blkid_loff_t off, blkid_loff_t size)
+{
+	struct stat sb;
+
+	if (!pr)
+		return -1;
+
+	blkid_reset_probe(pr);
+	blkid_probe_reset_buffer(pr);
+
+	if ((pr->flags & BLKID_FL_PRIVATE_FD) && pr->fd >= 0)
+		close(pr->fd);
+
+	pr->flags &= ~BLKID_FL_PRIVATE_FD;
+	pr->flags &= ~BLKID_FL_TINY_DEV;
+	pr->flags &= ~BLKID_FL_CDROM_DEV;
+	pr->prob_flags = 0;
+	pr->fd = fd;
+	pr->off = off;
+	pr->size = 0;
+	pr->devno = 0;
+	pr->disk_devno = 0;
+	pr->mode = 0;
+	pr->blkssz = 0;
+	pr->wipe_off = 0;
+	pr->wipe_size = 0;
+	pr->wipe_chain = NULL;
+
+#if defined(POSIX_FADV_RANDOM) && defined(HAVE_POSIX_FADVISE)
+	/* Disable read-ahead */
+	posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);
+#endif
+	if (fstat(fd, &sb))
+		goto err;
+
+	if (!S_ISBLK(sb.st_mode) && !S_ISCHR(sb.st_mode) && !S_ISREG(sb.st_mode))
+		goto err;
+
+
+	pr->mode = sb.st_mode;
+	if (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode))
+		pr->devno = sb.st_rdev;
+
+	if (size)
+		pr->size = size;
+	else {
+		if (S_ISBLK(sb.st_mode)) {
+			if (blkdev_get_size(fd, (unsigned long long *) &pr->size)) {
+				DBG(LOWPROBE, ul_debug("failed to get device size"));
+				goto err;
+			}
+		} else if (S_ISCHR(sb.st_mode))
+			pr->size = 1;		/* UBI devices are char... */
+		else if (S_ISREG(sb.st_mode))
+			pr->size = sb.st_size;	/* regular file */
+
+		if (pr->off > pr->size)
+			goto err;
+
+		/* The probing area cannot be larger than whole device, pr->off
+		 * is offset within the device */
+		pr->size -= pr->off;
+	}
+
+	if (pr->size <= 1440 * 1024 && !S_ISCHR(sb.st_mode))
+		pr->flags |= BLKID_FL_TINY_DEV;
+
+	if (S_ISBLK(sb.st_mode) && sysfs_devno_is_lvm_private(sb.st_rdev)) {
+		DBG(LOWPROBE, ul_debug("ignore private LVM device"));
+		pr->flags |= BLKID_FL_NOSCAN_DEV;
+	}
+
+#ifdef CDROM_GET_CAPABILITY
+	else if (S_ISBLK(sb.st_mode) &&
+	    !blkid_probe_is_tiny(pr) &&
+	    blkid_probe_is_wholedisk(pr) &&
+	    ioctl(fd, CDROM_GET_CAPABILITY, NULL) >= 0)
+		pr->flags |= BLKID_FL_CDROM_DEV;
+#endif
+
+	DBG(LOWPROBE, ul_debug("ready for low-probing, offset=%jd, size=%jd",
+				pr->off, pr->size));
+	DBG(LOWPROBE, ul_debug("whole-disk: %s, regfile: %s",
+		blkid_probe_is_wholedisk(pr) ?"YES" : "NO",
+		S_ISREG(pr->mode) ? "YES" : "NO"));
+
+	return 0;
+err:
+	DBG(LOWPROBE, ul_debug("failed to prepare a device for low-probing"));
+	return -1;
+
+}
+
+int blkid_probe_get_dimension(blkid_probe pr,
+		blkid_loff_t *off, blkid_loff_t *size)
+{
+	*off = pr->off;
+	*size = pr->size;
+	return 0;
+}
+
+int blkid_probe_set_dimension(blkid_probe pr,
+		blkid_loff_t off, blkid_loff_t size)
+{
+	DBG(LOWPROBE, ul_debug(
+		"changing probing area pr=%p: size=%llu, off=%llu "
+		"-to-> size=%llu, off=%llu",
+		pr,
+		(unsigned long long) pr->size,
+		(unsigned long long) pr->off,
+		(unsigned long long) size,
+		(unsigned long long) off));
+
+	pr->off = off;
+	pr->size = size;
+	pr->flags &= ~BLKID_FL_TINY_DEV;
+
+	if (pr->size <= 1440 * 1024 && !S_ISCHR(pr->mode))
+		pr->flags |= BLKID_FL_TINY_DEV;
+
+	blkid_probe_reset_buffer(pr);
+
+	return 0;
+}
+
+/*
+ * Check for matching magic value.
+ * Returns BLKID_PROBE_OK if found, BLKID_PROBE_NONE if not found
+ * or no magic present, or negative value on error.
+ */
+int blkid_probe_get_idmag(blkid_probe pr, const struct blkid_idinfo *id,
+			blkid_loff_t *offset, const struct blkid_idmag **res)
+{
+	const struct blkid_idmag *mag = NULL;
+	blkid_loff_t off = 0;
+
+	if (id)
+		mag = &id->magics[0];
+	if (res)
+		*res = NULL;
+
+	/* try to detect by magic string */
+	while(mag && mag->magic) {
+		unsigned char *buf;
+
+		off = (mag->kboff + (mag->sboff >> 10)) << 10;
+		buf = blkid_probe_get_buffer(pr, off, 1024);
+
+		if (!buf && errno)
+			return -errno;
+		if (buf && !memcmp(mag->magic,
+				buf + (mag->sboff & 0x3ff), mag->len)) {
+			DBG(LOWPROBE, ul_debug("\tmagic sboff=%u, kboff=%ld",
+				mag->sboff, mag->kboff));
+			if (offset)
+				*offset = off + (mag->sboff & 0x3ff);
+			if (res)
+				*res = mag;
+			return BLKID_PROBE_OK;
+		}
+		mag++;
+	}
+
+	if (id && id->magics[0].magic)
+		/* magic string(s) defined, but not found */
+		return BLKID_PROBE_NONE;
+
+	return BLKID_PROBE_OK;
+}
+
+static inline void blkid_probe_start(blkid_probe pr)
+{
+	if (pr) {
+		DBG(LOWPROBE, ul_debug("%p: start probe", pr));
+		pr->cur_chain = NULL;
+		pr->prob_flags = 0;
+		blkid_probe_set_wiper(pr, 0, 0);
+	}
+}
+
+static inline void blkid_probe_end(blkid_probe pr)
+{
+	if (pr) {
+		DBG(LOWPROBE, ul_debug("%p: end probe", pr));
+		pr->cur_chain = NULL;
+		pr->prob_flags = 0;
+		blkid_probe_set_wiper(pr, 0, 0);
+	}
+}
+
+/**
+ * blkid_do_probe:
+ * @pr: prober
+ *
+ * Calls probing functions in all enabled chains. The superblocks chain is
+ * enabled by default. The blkid_do_probe() stores result from only one
+ * probing function. It's necessary to call this routine in a loop to get
+ * results from all probing functions in all chains. The probing is reset
+ * by blkid_reset_probe() or by filter functions.
+ *
+ * This is string-based NAME=value interface only.
+ *
+ * <example>
+ *   <title>basic case - use the first result only</title>
+ *   <programlisting>
+ *
+ *	if (blkid_do_probe(pr) == 0) {
+ *		int nvals = blkid_probe_numof_values(pr);
+ *		for (n = 0; n < nvals; n++) {
+ *			if (blkid_probe_get_value(pr, n, &name, &data, &len) == 0)
+ *				printf("%s = %s\n", name, data);
+ *		}
+ *	}
+ *  </programlisting>
+ * </example>
+ *
+ * <example>
+ *   <title>advanced case - probe for all signatures</title>
+ *   <programlisting>
+ *
+ *	while (blkid_do_probe(pr) == 0) {
+ *		int nvals = blkid_probe_numof_values(pr);
+ *		...
+ *	}
+ *  </programlisting>
+ * </example>
+ *
+ * See also blkid_reset_probe().
+ *
+ * Returns: 0 on success, 1 when probing is done and -1 in case of error.
+ */
+int blkid_do_probe(blkid_probe pr)
+{
+	int rc = 1;
+
+	if (!pr)
+		return -1;
+
+	if (pr->flags & BLKID_FL_NOSCAN_DEV)
+		return 1;
+
+	do {
+		struct blkid_chain *chn = pr->cur_chain;
+
+		if (!chn) {
+			blkid_probe_start(pr);
+			chn = pr->cur_chain = &pr->chains[0];
+		}
+		/* we go to the next chain only when the previous probing
+		 * result was nothing (rc == 1) and when the current chain is
+		 * disabled or we are at end of the current chain (chain->idx +
+		 * 1 == sizeof chain) or the current chain bailed out right at
+		 * the start (chain->idx == -1)
+		 */
+		else if (rc == 1 && (chn->enabled == FALSE ||
+				     chn->idx + 1 == (int) chn->driver->nidinfos ||
+				     chn->idx == -1)) {
+
+			size_t idx = chn->driver->id + 1;
+
+			if (idx < BLKID_NCHAINS)
+				chn = pr->cur_chain = &pr->chains[idx];
+			else {
+				blkid_probe_end(pr);
+				return 1;	/* all chains already probed */
+			}
+		}
+
+		chn->binary = FALSE;		/* for sure... */
+
+		DBG(LOWPROBE, ul_debug("chain probe %s %s (idx=%d)",
+				chn->driver->name,
+				chn->enabled? "ENABLED" : "DISABLED",
+				chn->idx));
+
+		if (!chn->enabled)
+			continue;
+
+		/* rc: -1 = error, 0 = success, 1 = no result */
+		rc = chn->driver->probe(pr, chn);
+
+	} while (rc == 1);
+
+	return rc;
+}
+
+/**
+ * blkid_do_wipe:
+ * @pr: prober
+ * @dryrun: if TRUE then don't touch the device.
+ *
+ * This function erases the current signature detected by @pr. The @pr has to
+ * be open in O_RDWR mode, BLKID_SUBLKS_MAGIC or/and BLKID_PARTS_MAGIC flags
+ * has to be enabled (if you want to errase also superblock with broken check
+ * sums then use BLKID_SUBLKS_BADCSUM too).
+ *
+ * After successful signature removing the @pr prober will be moved one step
+ * back and the next blkid_do_probe() call will again call previously called
+ * probing function.
+ *
+ *  <example>
+ *  <title>wipe all filesystems or raids from the device</title>
+ *   <programlisting>
+ *      fd = open(devname, O_RDWR|O_CLOEXEC);
+ *      blkid_probe_set_device(pr, fd, 0, 0);
+ *
+ *      blkid_probe_enable_superblocks(pr, 1);
+ *      blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_MAGIC);
+ *
+ *	while (blkid_do_probe(pr) == 0)
+ *		blkid_do_wipe(pr, FALSE);
+ *  </programlisting>
+ * </example>
+ *
+ * See also blkid_probe_step_back() if you cannot use this build-in wipe
+ * function, but you want to use libblkid probing as a source for wiping.
+ *
+ * Returns: 0 on success, and -1 in case of error.
+ */
+int blkid_do_wipe(blkid_probe pr, int dryrun)
+{
+	const char *off = NULL;
+	size_t len = 0;
+	loff_t offset, l;
+	char buf[BUFSIZ];
+	int fd, rc = 0;
+	struct blkid_chain *chn;
+
+	if (!pr)
+		return -1;
+
+	chn = pr->cur_chain;
+	if (!chn)
+		return -1;
+
+	switch (chn->driver->id) {
+	case BLKID_CHAIN_SUBLKS:
+		rc = blkid_probe_lookup_value(pr, "SBMAGIC_OFFSET", &off, NULL);
+		if (!rc)
+			rc = blkid_probe_lookup_value(pr, "SBMAGIC", NULL, &len);
+		break;
+	case BLKID_CHAIN_PARTS:
+		rc = blkid_probe_lookup_value(pr, "PTMAGIC_OFFSET", &off, NULL);
+		if (!rc)
+			rc = blkid_probe_lookup_value(pr, "PTMAGIC", NULL, &len);
+		break;
+	default:
+		return 0;
+	}
+
+	if (rc || len == 0 || off == NULL)
+		return 0;
+
+	offset = strtoll(off, NULL, 10);
+	fd = blkid_probe_get_fd(pr);
+	if (fd < 0)
+		return -1;
+
+	if (len > sizeof(buf))
+		len = sizeof(buf);
+
+	DBG(LOWPROBE, ul_debug(
+	    "do_wipe [offset=0x%jx, len=%zd, chain=%s, idx=%d, dryrun=%s]\n",
+	    offset, len, chn->driver->name, chn->idx, dryrun ? "yes" : "not"));
+
+	l = lseek(fd, offset, SEEK_SET);
+	if (l == (off_t) -1)
+		return -1;
+
+	memset(buf, 0, len);
+
+	if (!dryrun && len) {
+		if (write_all(fd, buf, len))
+			return -1;
+		fsync(fd);
+		return blkid_probe_step_back(pr);
+	}
+
+	return 0;
+}
+
+/**
+ * blkid_probe_step_back:
+ * @pr: prober
+ *
+ * This function move pointer to the probing chain one step back -- it means
+ * that the previously used probing function will be called again in the next
+ * blkid_do_probe() call.
+ *
+ * This is necessary for example if you erase or modify on-disk superblock
+ * according to the current libblkid probing result.
+ *
+ * <example>
+ *  <title>wipe all superblock, but use libblkid only for probing</title>
+ *  <programlisting>
+ *      pr = blkid_new_probe_from_filename(devname);
+ *
+ *      blkid_probe_enable_superblocks(pr, 1);
+ *      blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_MAGIC);
+ *
+ *      blkid_probe_enable_partitions(pr, 1);
+ *      blkid_probe_set_partitions_flags(pr, BLKID_PARTS_MAGIC);
+ *
+ *	while (blkid_do_probe(pr) == 0) {
+ *		const char *ostr = NULL;
+ *		size_t len = 0;
+ *
+ *		// superblocks
+ *		if (blkid_probe_lookup_value(pr, "SBMAGIC_OFFSET", &ostr, NULL) == 0)
+ *			blkid_probe_lookup_value(pr, "SBMAGIC", NULL, &len);
+ *
+ *		// partition tables
+ *		if (len == 0 && blkid_probe_lookup_value(pr, "PTMAGIC_OFFSET", &ostr, NULL) == 0)
+ *			blkid_probe_lookup_value(pr, "PTMAGIC", NULL, &len);
+ *
+ *		if (!len || !str)
+ *			continue;
+ *
+ *		// convert ostr to the real offset by off = strtoll(ostr, NULL, 10);
+ *              // use your stuff to errase @len bytes at the @off
+ *              ....
+ *
+ *		// retry the last probing to check for backup superblocks ..etc.
+ *              blkid_probe_step_back(pr);
+ *	}
+ *  </programlisting>
+ * </example>
+ *
+ * Returns: 0 on success, and -1 in case of error.
+ */
+int blkid_probe_step_back(blkid_probe pr)
+{
+	struct blkid_chain *chn;
+
+	if (!pr)
+		return -1;
+
+	chn = pr->cur_chain;
+	if (!chn)
+		return -1;
+
+	blkid_probe_reset_buffer(pr);
+
+	if (chn->idx >= 0) {
+		chn->idx--;
+		DBG(LOWPROBE, ul_debug("step back: moving %s chain index to %d",
+			chn->driver->name,
+			chn->idx));
+	}
+
+	if (chn->idx == -1) {
+		/* blkid_do_probe() goes to the next chain if the index
+		 * of the current chain is -1, so we have to set the
+		 * chain pointer to the previous chain.
+		 */
+		size_t idx = chn->driver->id > 0 ? chn->driver->id - 1 : 0;
+
+		DBG(LOWPROBE, ul_debug("step back: moving to previous chain"));
+
+		if (idx > 0)
+			pr->cur_chain = &pr->chains[idx];
+		else if (idx == 0)
+			pr->cur_chain = NULL;
+	}
+
+	return 0;
+}
+
+/**
+ * blkid_do_safeprobe:
+ * @pr: prober
+ *
+ * This function gathers probing results from all enabled chains and checks
+ * for ambivalent results (e.g. more filesystems on the device).
+ *
+ * This is string-based NAME=value interface only.
+ *
+ * Note about suberblocks chain -- the function does not check for filesystems
+ * when a RAID signature is detected.  The function also does not check for
+ * collision between RAIDs. The first detected RAID is returned. The function
+ * checks for collision between partition table and RAID signature -- it's
+ * recommended to enable partitions chain together with superblocks chain.
+ *
+ * Returns: 0 on success, 1 if nothing is detected, -2 if ambivalen result is
+ * detected and -1 on case of error.
+ */
+int blkid_do_safeprobe(blkid_probe pr)
+{
+	int i, count = 0, rc = 0;
+
+	if (!pr)
+		return -1;
+	if (pr->flags & BLKID_FL_NOSCAN_DEV)
+		return 1;
+
+	blkid_probe_start(pr);
+
+	for (i = 0; i < BLKID_NCHAINS; i++) {
+		struct blkid_chain *chn;
+
+		chn = pr->cur_chain = &pr->chains[i];
+		chn->binary = FALSE;		/* for sure... */
+
+		DBG(LOWPROBE, ul_debug("chain safeprobe %s %s",
+				chn->driver->name,
+				chn->enabled? "ENABLED" : "DISABLED"));
+
+		if (!chn->enabled)
+			continue;
+
+		blkid_probe_chain_reset_position(chn);
+
+		rc = chn->driver->safeprobe(pr, chn);
+
+		blkid_probe_chain_reset_position(chn);
+
+		/* rc: -2 ambivalent, -1 = error, 0 = success, 1 = no result */
+		if (rc < 0)
+			goto done;	/* error */
+		if (rc == 0)
+			count++;	/* success */
+	}
+
+done:
+	blkid_probe_end(pr);
+	if (rc < 0)
+		return rc;
+	return count ? 0 : 1;
+}
+
+/**
+ * blkid_do_fullprobe:
+ * @pr: prober
+ *
+ * This function gathers probing results from all enabled chains. Same as
+ * blkid_do_safeprobe() but does not check for collision between probing
+ * result.
+ *
+ * This is string-based NAME=value interface only.
+ *
+ * Returns: 0 on success, 1 if nothing is detected or -1 on case of error.
+ */
+int blkid_do_fullprobe(blkid_probe pr)
+{
+	int i, count = 0, rc = 0;
+
+	if (!pr)
+		return -1;
+	if (pr->flags & BLKID_FL_NOSCAN_DEV)
+		return 1;
+
+	blkid_probe_start(pr);
+
+	for (i = 0; i < BLKID_NCHAINS; i++) {
+		struct blkid_chain *chn;
+
+		chn = pr->cur_chain = &pr->chains[i];
+		chn->binary = FALSE;		/* for sure... */
+
+		DBG(LOWPROBE, ul_debug("chain fullprobe %s: %s",
+				chn->driver->name,
+				chn->enabled? "ENABLED" : "DISABLED"));
+
+		if (!chn->enabled)
+			continue;
+
+		blkid_probe_chain_reset_position(chn);
+
+		rc = chn->driver->probe(pr, chn);
+
+		blkid_probe_chain_reset_position(chn);
+
+		/* rc: -1 = error, 0 = success, 1 = no result */
+		if (rc < 0)
+			goto done;	/* error */
+		if (rc == 0)
+			count++;	/* success */
+	}
+
+done:
+	blkid_probe_end(pr);
+	if (rc < 0)
+		return rc;
+	return count ? 0 : 1;
+}
+
+/* same sa blkid_probe_get_buffer() but works with 512-sectors */
+unsigned char *blkid_probe_get_sector(blkid_probe pr, unsigned int sector)
+{
+	return blkid_probe_get_buffer(pr, ((blkid_loff_t) sector) << 9, 0x200);
+}
+
+struct blkid_prval *blkid_probe_assign_value(
+			blkid_probe pr, const char *name)
+{
+	struct blkid_prval *v;
+
+	if (pr->nvals >= BLKID_NVALS)
+		return NULL;
+
+	v = &pr->vals[pr->nvals];
+	v->name = name;
+	v->chain = pr->cur_chain;
+	pr->nvals++;
+
+	DBG(LOWPROBE, ul_debug("assigning %s [%s]", name, v->chain->driver->name));
+	return v;
+}
+
+int blkid_probe_reset_last_value(blkid_probe pr)
+{
+	struct blkid_prval *v;
+
+	if (pr->nvals == 0)
+		return -1;
+
+	v = &pr->vals[pr->nvals - 1];
+
+	DBG(LOWPROBE, ul_debug("un-assigning %s [%s]", v->name, v->chain->driver->name));
+
+	memset(v, 0, sizeof(struct blkid_prval));
+	pr->nvals--;
+
+	return 0;
+
+}
+
+int blkid_probe_set_value(blkid_probe pr, const char *name,
+		unsigned char *data, size_t len)
+{
+	struct blkid_prval *v;
+
+	if (len > BLKID_PROBVAL_BUFSIZ)
+		len = BLKID_PROBVAL_BUFSIZ;
+
+	v = blkid_probe_assign_value(pr, name);
+	if (!v)
+		return -1;
+
+	memcpy(v->data, data, len);
+	v->len = len;
+	return 0;
+}
+
+int blkid_probe_vsprintf_value(blkid_probe pr, const char *name,
+		const char *fmt, va_list ap)
+{
+	struct blkid_prval *v;
+	ssize_t len;
+
+	v = blkid_probe_assign_value(pr, name);
+	if (!v)
+		return -1;
+
+	len = vsnprintf((char *) v->data, sizeof(v->data), fmt, ap);
+
+	if (len <= 0 || (size_t) len >= sizeof(v->data)) {
+		blkid_probe_reset_last_value(pr);
+		return -1;
+	}
+	v->len = len + 1;
+	return 0;
+}
+
+int blkid_probe_sprintf_value(blkid_probe pr, const char *name,
+		const char *fmt, ...)
+{
+	int rc;
+	va_list ap;
+
+	va_start(ap, fmt);
+	rc = blkid_probe_vsprintf_value(pr, name, fmt, ap);
+	va_end(ap);
+
+	return rc;
+}
+
+int blkid_probe_set_magic(blkid_probe pr, blkid_loff_t offset,
+			size_t len, unsigned char *magic)
+{
+	int rc = 0;
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+	if (!chn || !len || chn->binary)
+		return 0;
+
+	switch (chn->driver->id) {
+	case BLKID_CHAIN_SUBLKS:
+		if (!(chn->flags & BLKID_SUBLKS_MAGIC))
+			return 0;
+		rc = blkid_probe_set_value(pr, "SBMAGIC", magic, len);
+		if (!rc)
+			rc = blkid_probe_sprintf_value(pr,
+					"SBMAGIC_OFFSET", "%llu", (unsigned long long)offset);
+		break;
+	case BLKID_CHAIN_PARTS:
+		if (!(chn->flags & BLKID_PARTS_MAGIC))
+			return 0;
+		rc = blkid_probe_set_value(pr, "PTMAGIC", magic, len);
+		if (!rc)
+			rc = blkid_probe_sprintf_value(pr,
+					"PTMAGIC_OFFSET", "%llu", (unsigned long long)offset);
+		break;
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+int blkid_probe_verify_csum(blkid_probe pr, uint64_t csum, uint64_t expected)
+{
+	if (csum != expected) {
+		struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+		DBG(LOWPROBE, ul_debug(
+				"incorrect checksum for type %s,"
+				" got %jX, expected %jX",
+				blkid_probe_get_probername(pr),
+				csum, expected));
+		/*
+		 * Accept bad checksum if BLKID_SUBLKS_BADCSUM flags is set
+		 */
+		if (chn->driver->id == BLKID_CHAIN_SUBLKS
+		    && (chn->flags & BLKID_SUBLKS_BADCSUM)) {
+			blkid_probe_set_value(pr, "SBBADCSUM", (unsigned char *) "1", 2);
+			goto accept;
+		}
+		return 0;	/* bad checksum */
+	}
+
+accept:
+	return 1;
+}
+
+/**
+ * blkid_probe_get_devno:
+ * @pr: probe
+ *
+ * Returns: block device number, or 0 for regular files.
+ */
+dev_t blkid_probe_get_devno(blkid_probe pr)
+{
+	return pr->devno;
+}
+
+/**
+ * blkid_probe_get_wholedisk_devno:
+ * @pr: probe
+ *
+ * Returns: device number of the wholedisk, or 0 for regular files.
+ */
+dev_t blkid_probe_get_wholedisk_devno(blkid_probe pr)
+{
+	if (!pr->disk_devno) {
+		dev_t devno, disk_devno = 0;
+
+		devno = blkid_probe_get_devno(pr);
+		if (!devno)
+			return 0;
+
+		 if (blkid_devno_to_wholedisk(devno, NULL, 0, &disk_devno) == 0)
+			pr->disk_devno = disk_devno;
+	}
+	return pr->disk_devno;
+}
+
+/**
+ * blkid_probe_is_wholedisk:
+ * @pr: probe
+ *
+ * Returns: 1 if the device is whole-disk or 0.
+ */
+int blkid_probe_is_wholedisk(blkid_probe pr)
+{
+	dev_t devno, disk_devno;
+
+	devno = blkid_probe_get_devno(pr);
+	if (!devno)
+		return 0;
+
+	disk_devno = blkid_probe_get_wholedisk_devno(pr);
+	if (!disk_devno)
+		return 0;
+
+	return devno == disk_devno;
+}
+
+blkid_probe blkid_probe_get_wholedisk_probe(blkid_probe pr)
+{
+	dev_t disk;
+
+	if (blkid_probe_is_wholedisk(pr))
+		return NULL;			/* this is not partition */
+
+	if (pr->parent)
+		/* this is cloned blkid_probe, use parent's stuff */
+		return blkid_probe_get_wholedisk_probe(pr->parent);
+
+	disk = blkid_probe_get_wholedisk_devno(pr);
+
+	if (pr->disk_probe && pr->disk_probe->devno != disk) {
+		/* we have disk prober, but for another disk... close it */
+		blkid_free_probe(pr->disk_probe);
+		pr->disk_probe = NULL;
+	}
+
+	if (!pr->disk_probe) {
+		/* Open a new disk prober */
+		char *disk_path = blkid_devno_to_devname(disk);
+
+		if (!disk_path)
+			return NULL;
+
+		DBG(LOWPROBE, ul_debug("allocate a wholedisk probe"));
+
+		pr->disk_probe = blkid_new_probe_from_filename(disk_path);
+
+		free(disk_path);
+
+		if (!pr->disk_probe)
+			return NULL;	/* ENOMEM? */
+	}
+
+	return pr->disk_probe;
+}
+
+/**
+ * blkid_probe_get_size:
+ * @pr: probe
+ *
+ * This function returns size of probing area as defined by blkid_probe_set_device().
+ * If the size of the probing area is unrestricted then this function returns
+ * the real size of device. See also blkid_get_dev_size().
+ *
+ * Returns: size in bytes or -1 in case of error.
+ */
+blkid_loff_t blkid_probe_get_size(blkid_probe pr)
+{
+	return pr ? pr->size : -1;
+}
+
+/**
+ * blkid_probe_get_offset:
+ * @pr: probe
+ *
+ * This function returns offset of probing area as defined by blkid_probe_set_device().
+ *
+ * Returns: offset in bytes or -1 in case of error.
+ */
+blkid_loff_t blkid_probe_get_offset(blkid_probe pr)
+{
+	return pr ? pr->off : -1;
+}
+
+/**
+ * blkid_probe_get_fd:
+ * @pr: probe
+ *
+ * Returns: file descriptor for assigned device/file or -1 in case of error.
+ */
+int blkid_probe_get_fd(blkid_probe pr)
+{
+	return pr ? pr->fd : -1;
+}
+
+/**
+ * blkid_probe_get_sectorsize:
+ * @pr: probe or NULL (for NULL returns 512)
+ *
+ * Returns: block device logical sector size (BLKSSZGET ioctl, default 512).
+ */
+unsigned int blkid_probe_get_sectorsize(blkid_probe pr)
+{
+	if (!pr)
+		return DEFAULT_SECTOR_SIZE;  /*... and good luck! */
+
+	if (pr->blkssz)
+		return pr->blkssz;
+
+	if (S_ISBLK(pr->mode) &&
+	    blkdev_get_sector_size(pr->fd, (int *) &pr->blkssz) == 0)
+		return pr->blkssz;
+
+	pr->blkssz = DEFAULT_SECTOR_SIZE;
+	return pr->blkssz;
+}
+
+/**
+ * blkid_probe_get_sectors:
+ * @pr: probe
+ *
+ * Returns: 512-byte sector count or -1 in case of error.
+ */
+blkid_loff_t blkid_probe_get_sectors(blkid_probe pr)
+{
+	return pr ? pr->size >> 9 : -1;
+}
+
+/**
+ * blkid_probe_numof_values:
+ * @pr: probe
+ *
+ * Returns: number of values in probing result or -1 in case of error.
+ */
+int blkid_probe_numof_values(blkid_probe pr)
+{
+	if (!pr)
+		return -1;
+	return pr->nvals;
+}
+
+/**
+ * blkid_probe_get_value:
+ * @pr: probe
+ * @num: wanted value in range 0..N, where N is blkid_probe_numof_values() - 1
+ * @name: pointer to return value name or NULL
+ * @data: pointer to return value data or NULL
+ * @len: pointer to return value length or NULL
+ *
+ * Note, the @len returns length of the @data, including the terminating
+ * '\0' character.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_get_value(blkid_probe pr, int num, const char **name,
+			const char **data, size_t *len)
+{
+	struct blkid_prval *v = __blkid_probe_get_value(pr, num);
+
+	if (!v)
+		return -1;
+	if (name)
+		*name = v->name;
+	if (data)
+		*data = (char *) v->data;
+	if (len)
+		*len = v->len;
+
+	DBG(LOWPROBE, ul_debug("returning %s value", v->name));
+	return 0;
+}
+
+/**
+ * blkid_probe_lookup_value:
+ * @pr: probe
+ * @name: name of value
+ * @data: pointer to return value data or NULL
+ * @len: pointer to return value length or NULL
+ *
+ * Note, the @len returns length of the @data, including the terminating
+ * '\0' character.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_lookup_value(blkid_probe pr, const char *name,
+			const char **data, size_t *len)
+{
+	struct blkid_prval *v = __blkid_probe_lookup_value(pr, name);
+
+	if (!v)
+		return -1;
+	if (data)
+		*data = (char *) v->data;
+	if (len)
+		*len = v->len;
+	return 0;
+}
+
+/**
+ * blkid_probe_has_value:
+ * @pr: probe
+ * @name: name of value
+ *
+ * Returns: 1 if value exist in probing result, otherwise 0.
+ */
+int blkid_probe_has_value(blkid_probe pr, const char *name)
+{
+	if (blkid_probe_lookup_value(pr, name, NULL, NULL) == 0)
+		return 1;
+	return 0;
+}
+
+struct blkid_prval *__blkid_probe_get_value(blkid_probe pr, int num)
+{
+	if (num < 0 || num >= pr->nvals)
+		return NULL;
+
+	return &pr->vals[num];
+}
+
+struct blkid_prval *__blkid_probe_lookup_value(blkid_probe pr, const char *name)
+{
+	int i;
+
+	if (!pr->nvals)
+		return NULL;
+
+	for (i = 0; i < pr->nvals; i++) {
+		struct blkid_prval *v = &pr->vals[i];
+
+		if (v->name && strcmp(name, v->name) == 0) {
+			DBG(LOWPROBE, ul_debug("returning %s value", v->name));
+			return v;
+		}
+	}
+	return NULL;
+}
+
+
+/* converts DCE UUID (uuid[16]) to human readable string
+ * - the @len should be always 37 */
+#ifdef HAVE_LIBUUID
+void blkid_unparse_uuid(const unsigned char *uuid, char *str,
+			size_t len __attribute__((__unused__)))
+{
+	uuid_unparse(uuid, str);
+}
+#else
+void blkid_unparse_uuid(const unsigned char *uuid, char *str, size_t len)
+{
+	snprintf(str, len,
+		"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+		uuid[0], uuid[1], uuid[2], uuid[3],
+		uuid[4], uuid[5],
+		uuid[6], uuid[7],
+		uuid[8], uuid[9],
+		uuid[10], uuid[11], uuid[12], uuid[13], uuid[14],uuid[15]);
+}
+#endif
+
+/* like uuid_is_null() from libuuid, but works with arbitrary size of UUID */
+int blkid_uuid_is_empty(const unsigned char *buf, size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len; i++)
+		if (buf[i])
+			return 0;
+	return 1;
+}
+
+/* Removes whitespace from the right-hand side of a string (trailing
+ * whitespace).
+ *
+ * Returns size of the new string (without \0).
+ */
+size_t blkid_rtrim_whitespace(unsigned char *str)
+{
+	return rtrim_whitespace(str);
+}
+
+/* Removes whitespace from the left-hand side of a string.
+ *
+ * Returns size of the new string (without \0).
+ */
+size_t blkid_ltrim_whitespace(unsigned char *str)
+{
+	return ltrim_whitespace(str);
+}
+
+/*
+ * Some mkfs-like utils wipe some parts (usually begin) of the device.
+ * For example LVM (pvcreate) or mkswap(8). This information could be used
+ * for later resolution to conflicts between superblocks.
+ *
+ * For example we found valid LVM superblock, LVM wipes 8KiB at the begin of
+ * the device. If we found another signature (for example MBR) within the
+ * wiped area then the signature has been added later and LVM superblock
+ * should be ignore.
+ *
+ * Note that this heuristic is not 100% reliable, for example "pvcreate --zero
+ * n" allows to keep the begin of the device unmodified. It's probably better
+ * to use this heuristic for conflicts between superblocks and partition tables
+ * than for conflicts between filesystem superblocks -- existence of unwanted
+ * partition table is very unusual, because PT is pretty visible (parsed and
+ * interpreted by kernel).
+ *
+ * Note that we usually expect only one signature on the device, it means that
+ * we have to remember only one wiped area from previously successfully
+ * detected signature.
+ *
+ * blkid_probe_set_wiper() -- defines wiped area (e.g. LVM)
+ * blkid_probe_use_wiper() -- try to use area (e.g. MBR)
+ *
+ * Note that there is not relation between _wiper and blkid_to_wipe().
+ *
+ */
+void blkid_probe_set_wiper(blkid_probe pr, blkid_loff_t off, blkid_loff_t size)
+{
+	struct blkid_chain *chn;
+
+	if (!size) {
+		DBG(LOWPROBE, ul_debug("zeroize wiper"));
+		pr->wipe_size = pr->wipe_off = 0;
+		pr->wipe_chain = NULL;
+		return;
+	}
+
+	chn = pr->cur_chain;
+
+	if (!chn || !chn->driver ||
+	    chn->idx < 0 || (size_t) chn->idx >= chn->driver->nidinfos)
+		return;
+
+	pr->wipe_size = size;
+	pr->wipe_off = off;
+	pr->wipe_chain = chn;
+
+	DBG(LOWPROBE,
+		ul_debug("wiper set to %s::%s off=%jd size=%jd",
+			chn->driver->name,
+			chn->driver->idinfos[chn->idx]->name,
+			pr->wipe_off, pr->wipe_size));
+	return;
+}
+
+/*
+ * Returns 1 if the <@off,@size> area was wiped
+ */
+int blkid_probe_is_wiped(blkid_probe pr, struct blkid_chain **chn,
+		     blkid_loff_t off, blkid_loff_t size)
+{
+	if (!size)
+		return 0;
+
+	if (pr->wipe_off <= off && off + size <= pr->wipe_off + pr->wipe_size) {
+		*chn = pr->wipe_chain;
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ *  Try to use any area -- if the area has been previously wiped then the
+ *  previous probing result should be ignored (reseted).
+ */
+void blkid_probe_use_wiper(blkid_probe pr, blkid_loff_t off, blkid_loff_t size)
+{
+	struct blkid_chain *chn = NULL;
+
+	if (blkid_probe_is_wiped(pr, &chn, off, size) && chn) {
+		DBG(LOWPROBE, ul_debug("previously wiped area modified "
+				       " -- ignore previous results"));
+		blkid_probe_set_wiper(pr, 0, 0);
+		blkid_probe_chain_reset_vals(pr, chn);
+	}
+}
diff --git a/libblkid/src/pt-bsd.h b/libblkid/src/pt-bsd.h
new file mode 100644
index 0000000..9bf47a5
--- /dev/null
+++ b/libblkid/src/pt-bsd.h
@@ -0,0 +1,156 @@
+#ifndef UTIL_LINUX_PT_BSD_H
+#define UTIL_LINUX_PT_BSD_H
+
+#define BSD_MAXPARTITIONS	16
+#define BSD_FS_UNUSED		0
+
+#ifndef BSD_DISKMAGIC
+# define BSD_DISKMAGIC     ((uint32_t) 0x82564557)
+#endif
+
+#define BSD_LINUX_BOOTDIR "/usr/ucb/mdec"
+
+#if defined (__alpha__) || defined (__powerpc__) || \
+    defined (__ia64__) || defined (__hppa__)
+# define BSD_LABELSECTOR   0
+# define BSD_LABELOFFSET   64
+#else
+# define BSD_LABELSECTOR   1
+# define BSD_LABELOFFSET   0
+#endif
+
+#define	BSD_BBSIZE        8192		/* size of boot area, with label */
+#define	BSD_SBSIZE        8192		/* max size of fs superblock */
+
+struct bsd_disklabel {
+	uint32_t	d_magic;		/* the magic number */
+	int16_t		d_type;			/* drive type */
+	int16_t		d_subtype;		/* controller/d_type specific */
+	char		d_typename[16];		/* type name, e.g. "eagle" */
+	char		d_packname[16];		/* pack identifier */
+
+			/* disk geometry: */
+	uint32_t	d_secsize;		/* # of bytes per sector */
+	uint32_t	d_nsectors;		/* # of data sectors per track */
+	uint32_t	d_ntracks;		/* # of tracks per cylinder */
+	uint32_t	d_ncylinders;		/* # of data cylinders per unit */
+	uint32_t	d_secpercyl;		/* # of data sectors per cylinder */
+	uint32_t	d_secperunit;		/* # of data sectors per unit */
+
+	/*
+	 * Spares (bad sector replacements) below
+	 * are not counted in d_nsectors or d_secpercyl.
+	 * Spare sectors are assumed to be physical sectors
+	 * which occupy space at the end of each track and/or cylinder.
+	 */
+	uint16_t	d_sparespertrack;	/* # of spare sectors per track */
+	uint16_t	d_sparespercyl;		/* # of spare sectors per cylinder */
+
+	/*
+	 * Alternate cylinders include maintenance, replacement,
+	 * configuration description areas, etc.
+	 */
+	uint32_t	d_acylinders;		/* # of alt. cylinders per unit */
+
+			/* hardware characteristics: */
+	/*
+	 * d_interleave, d_trackskew and d_cylskew describe perturbations
+	 * in the media format used to compensate for a slow controller.
+	 * Interleave is physical sector interleave, set up by the formatter
+	 * or controller when formatting.  When interleaving is in use,
+	 * logically adjacent sectors are not physically contiguous,
+	 * but instead are separated by some number of sectors.
+	 * It is specified as the ratio of physical sectors traversed
+	 * per logical sector.  Thus an interleave of 1:1 implies contiguous
+	 * layout, while 2:1 implies that logical sector 0 is separated
+	 * by one sector from logical sector 1.
+	 * d_trackskew is the offset of sector 0 on track N
+	 * relative to sector 0 on track N-1 on the same cylinder.
+	 * Finally, d_cylskew is the offset of sector 0 on cylinder N
+	 * relative to sector 0 on cylinder N-1.
+	 */
+	uint16_t	d_rpm;			/* rotational speed */
+	uint16_t	d_interleave;		/* hardware sector interleave */
+	uint16_t	d_trackskew;		/* sector 0 skew, per track */
+	uint16_t	d_cylskew;		/* sector 0 skew, per cylinder */
+	uint32_t	d_headswitch;		/* head switch time, usec */
+	uint32_t	d_trkseek;		/* track-to-track seek, usec */
+	uint32_t	d_flags;		/* generic flags */
+	uint32_t	d_drivedata[5];		/* drive-type specific information */
+	uint32_t	d_spare[5];		/* reserved for future use */
+	uint32_t	d_magic2;		/* the magic number (again) */
+	uint16_t	d_checksum;		/* xor of data incl. partitions */
+
+			/* filesystem and partition information: */
+	uint16_t	d_npartitions;	        /* number of partitions in following */
+	uint32_t	d_bbsize;	        /* size of boot area at sn0, bytes */
+	uint32_t	d_sbsize;	        /* max size of fs superblock, bytes */
+
+	struct bsd_partition	 {		/* the partition table */
+		uint32_t	p_size;	        /* number of sectors in partition */
+		uint32_t	p_offset;       /* starting sector */
+		uint32_t	p_fsize;        /* filesystem basic fragment size */
+		uint8_t		p_fstype;	/* filesystem type, see below */
+		uint8_t		p_frag;		/* filesystem fragments per block */
+		uint16_t	p_cpg;	        /* filesystem cylinders per group */
+	} __attribute__((packed)) d_partitions[BSD_MAXPARTITIONS];	/* actually may be more */
+} __attribute__((packed));
+
+
+/* d_type values: */
+#define	BSD_DTYPE_SMD		1		/* SMD, XSMD; VAX hp/up */
+#define	BSD_DTYPE_MSCP		2		/* MSCP */
+#define	BSD_DTYPE_DEC		3		/* other DEC (rk, rl) */
+#define	BSD_DTYPE_SCSI		4		/* SCSI */
+#define	BSD_DTYPE_ESDI		5		/* ESDI interface */
+#define	BSD_DTYPE_ST506		6		/* ST506 etc. */
+#define	BSD_DTYPE_HPIB		7		/* CS/80 on HP-IB */
+#define BSD_DTYPE_HPFL		8		/* HP Fiber-link */
+#define	BSD_DTYPE_FLOPPY	10		/* floppy */
+
+/* d_subtype values: */
+#define BSD_DSTYPE_INDOSPART	0x8		/* is inside dos partition */
+#define BSD_DSTYPE_DOSPART(s)	((s) & 3)	/* dos partition number */
+#define BSD_DSTYPE_GEOMETRY	0x10		/* drive params in label */
+
+/*
+ * Filesystem type and version.
+ * Used to interpret other filesystem-specific
+ * per-partition information.
+ */
+#define	BSD_FS_UNUSED	0		/* unused */
+#define	BSD_FS_SWAP    	1		/* swap */
+#define	BSD_FS_V6      	2		/* Sixth Edition */
+#define	BSD_FS_V7      	3		/* Seventh Edition */
+#define	BSD_FS_SYSV    	4		/* System V */
+#define	BSD_FS_V71K    	5		/* V7 with 1K blocks (4.1, 2.9) */
+#define	BSD_FS_V8      	6		/* Eighth Edition, 4K blocks */
+#define	BSD_FS_BSDFFS	7		/* 4.2BSD fast file system */
+#define	BSD_FS_BSDLFS	9		/* 4.4BSD log-structured file system */
+#define	BSD_FS_OTHER	10		/* in use, but unknown/unsupported */
+#define	BSD_FS_HPFS	11		/* OS/2 high-performance file system */
+#define	BSD_FS_ISO9660	12		/* ISO-9660 filesystem (cdrom) */
+#define BSD_FS_ISOFS	BSD_FS_ISO9660
+#define	BSD_FS_BOOT	13		/* partition contains bootstrap */
+#define BSD_FS_ADOS	14		/* AmigaDOS fast file system */
+#define BSD_FS_HFS	15		/* Macintosh HFS */
+#define BSD_FS_ADVFS	16		/* Digital Unix AdvFS */
+
+/* this is annoying, but it's also the way it is :-( */
+#ifdef __alpha__
+#define	BSD_FS_EXT2	8		/* ext2 file system */
+#else
+#define	BSD_FS_MSDOS	8		/* MS-DOS file system */
+#endif
+
+/*
+ * flags shared by various drives:
+ */
+#define	BSD_D_REMOVABLE	0x01		/* removable media */
+#define	BSD_D_ECC      	0x02		/* supports ECC */
+#define	BSD_D_BADSECT	0x04		/* supports bad sector forw. */
+#define	BSD_D_RAMDISK	0x08		/* disk emulator */
+#define	BSD_D_CHAIN    	0x10		/* can do back-back transfers */
+#define	BSD_D_DOSPART	0x20		/* within MSDOS partition */
+
+#endif /* UTIL_LINUX_PT_BSD_H */
diff --git a/libblkid/src/pt-mbr.h b/libblkid/src/pt-mbr.h
new file mode 100644
index 0000000..1279e3c
--- /dev/null
+++ b/libblkid/src/pt-mbr.h
@@ -0,0 +1,178 @@
+#ifndef UTIL_LINUX_PT_MBR_H
+#define UTIL_LINUX_PT_MBR_H
+
+struct dos_partition {
+	unsigned char boot_ind;		/* 0x80 - active */
+	unsigned char bh, bs, bc;	/* begin CHS */
+	unsigned char sys_ind;
+	unsigned char eh, es, ec;	/* end CHS */
+	unsigned char start_sect[4];
+	unsigned char nr_sects[4];
+} __attribute__((packed));
+
+#define MBR_PT_OFFSET		0x1be
+
+static inline struct dos_partition *mbr_get_partition(unsigned char *mbr, int i)
+{
+	return (struct dos_partition *)
+		(mbr + MBR_PT_OFFSET + (i * sizeof(struct dos_partition)));
+}
+
+/* assemble badly aligned little endian integer */
+static inline unsigned int __dos_assemble_4le(const unsigned char *p)
+{
+	return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+}
+
+static inline void __dos_store_4le(unsigned char *p, unsigned int val)
+{
+	p[0] = (val & 0xff);
+	p[1] = ((val >> 8) & 0xff);
+	p[2] = ((val >> 16) & 0xff);
+	p[3] = ((val >> 24) & 0xff);
+}
+
+static inline unsigned int dos_partition_get_start(struct dos_partition *p)
+{
+	return __dos_assemble_4le(&(p->start_sect[0]));
+}
+
+static inline void dos_partition_set_start(struct dos_partition *p, unsigned int n)
+{
+	__dos_store_4le(p->start_sect, n);
+}
+
+static inline unsigned int dos_partition_get_size(struct dos_partition *p)
+{
+	return __dos_assemble_4le(&(p->nr_sects[0]));
+}
+
+static inline void dos_partition_set_size(struct dos_partition *p, unsigned int n)
+{
+	__dos_store_4le(p->nr_sects, n);
+}
+
+static inline int mbr_is_valid_magic(const unsigned char *mbr)
+{
+	return mbr[510] == 0x55 && mbr[511] == 0xaa ? 1 : 0;
+}
+
+static inline void mbr_set_magic(unsigned char *b)
+{
+	b[510] = 0x55;
+	b[511] = 0xaa;
+}
+
+static inline unsigned int mbr_get_id(const unsigned char *mbr)
+{
+	return __dos_assemble_4le(&mbr[440]);
+}
+
+static inline void mbr_set_id(unsigned char *b, unsigned int id)
+{
+	__dos_store_4le(&b[440], id);
+}
+
+enum {
+	MBR_EMPTY_PARTITION		= 0x00,
+	MBR_FAT12_PARTITION		= 0x01,
+	MBR_XENIX_ROOT_PARTITION	= 0x02,
+	MBR_XENIX_USR_PARTITION		= 0x03,
+	MBR_FAT16_LESS32M_PARTITION	= 0x04,
+	MBR_DOS_EXTENDED_PARTITION	= 0x05,
+	MBR_FAT16_PARTITION		= 0x06, /* DOS 16-bit >=32M */
+	MBR_HPFS_NTFS_PARTITION		= 0x07, /* OS/2 IFS, eg, HPFS or NTFS or QNX */
+	MBR_AIX_PARTITION		= 0x08, /* AIX boot (AIX -- PS/2 port) or SplitDrive */
+	MBR_AIX_BOOTABLE_PARTITION	= 0x09, /* AIX data or Coherent */
+	MBR_OS2_BOOTMNGR_PARTITION	= 0x0a, /* OS/2 Boot Manager */
+	MBR_W95_FAT32_PARTITION		= 0x0b,
+	MBR_W95_FAT32_LBA_PARTITION	= 0x0c, /* LBA really is `Extended Int 13h' */
+	MBR_W95_FAT16_LBA_PARTITION	= 0x0e,
+	MBR_W95_EXTENDED_PARTITION	= 0x0f,
+	MBR_OPUS_PARTITION		= 0x10,
+	MBR_HIDDEN_FAT12_PARTITION	= 0x11,
+	MBR_COMPAQ_DIAGNOSTICS_PARTITION = 0x12,
+	MBR_HIDDEN_FAT16_L32M_PARTITION	= 0x14,
+	MBR_HIDDEN_FAT16_PARTITION	= 0x16,
+	MBR_HIDDEN_HPFS_NTFS_PARTITION	= 0x17,
+	MBR_AST_SMARTSLEEP_PARTITION	= 0x18,
+	MBR_HIDDEN_W95_FAT32_PARTITION	= 0x1b,
+	MBR_HIDDEN_W95_FAT32LBA_PARTITION = 0x1c,
+	MBR_HIDDEN_W95_FAT16LBA_PARTITION = 0x1e,
+	MBR_NEC_DOS_PARTITION		= 0x24,
+	MBR_PLAN9_PARTITION		= 0x39,
+	MBR_PARTITIONMAGIC_PARTITION	= 0x3c,
+	MBR_VENIX80286_PARTITION	= 0x40,
+	MBR_PPC_PREP_BOOT_PARTITION	= 0x41,
+	MBR_SFS_PARTITION		= 0x42,
+	MBR_QNX_4X_PARTITION		= 0x4d,
+	MBR_QNX_4X_2ND_PARTITION	= 0x4e,
+	MBR_QNX_4X_3RD_PARTITION	= 0x4f,
+	MBR_DM_PARTITION		= 0x50,
+	MBR_DM6_AUX1_PARTITION		= 0x51, /* (or Novell) */
+	MBR_CPM_PARTITION		= 0x52, /* CP/M or Microport SysV/AT */
+	MBR_DM6_AUX3_PARTITION		= 0x53,
+	MBR_DM6_PARTITION		= 0x54,
+	MBR_EZ_DRIVE_PARTITION		= 0x55,
+	MBR_GOLDEN_BOW_PARTITION	= 0x56,
+	MBR_PRIAM_EDISK_PARTITION	= 0x5c,
+	MBR_SPEEDSTOR_PARTITION		= 0x61,
+	MBR_GNU_HURD_PARTITION		= 0x63, /* GNU HURD or Mach or Sys V/386 (such as ISC UNIX) */
+	MBR_UNIXWARE_PARTITION		= MBR_GNU_HURD_PARTITION,
+	MBR_NETWARE_286_PARTITION	= 0x64,
+	MBR_NETWARE_386_PARTITION	= 0x65,
+	MBR_DISKSECURE_MULTIBOOT_PARTITION = 0x70,
+	MBR_PC_IX_PARTITION		= 0x75,
+	MBR_OLD_MINIX_PARTITION		= 0x80, /* Minix 1.4a and earlier */
+	MBR_MINIX_PARTITION		= 0x81, /* Minix 1.4b and later */
+	MBR_LINUX_SWAP_PARTITION	= 0x82,
+	MBR_SOLARIS_X86_PARTITION	= MBR_LINUX_SWAP_PARTITION,
+	MBR_LINUX_DATA_PARTITION	= 0x83,
+	MBR_OS2_HIDDEN_DRIVE_PARTITION	= 0x84,
+	MBR_LINUX_EXTENDED_PARTITION	= 0x85,
+	MBR_NTFS_VOL_SET1_PARTITION	= 0x86,
+	MBR_NTFS_VOL_SET2_PARTITION	= 0x87,
+	MBR_LINUX_PLAINTEXT_PARTITION	= 0x88,
+	MBR_LINUX_LVM_PARTITION		= 0x8e,
+	MBR_AMOEBA_PARTITION		= 0x93,
+	MBR_AMOEBA_BBT_PARTITION	= 0x94, /* (bad block table) */
+	MBR_BSD_OS_PARTITION		= 0x9f, /* BSDI */
+	MBR_THINKPAD_HIBERNATION_PARTITION = 0xa0,
+	MBR_FREEBSD_PARTITION		= 0xa5, /* various BSD flavours */
+	MBR_OPENBSD_PARTITION		= 0xa6,
+	MBR_NEXTSTEP_PARTITION		= 0xa7,
+	MBR_DARWIN_UFS_PARTITION	= 0xa8,
+	MBR_NETBSD_PARTITION		= 0xa9,
+	MBR_DARWIN_BOOT_PARTITION	= 0xab,
+	MBR_HFS_HFS_PARTITION		= 0xaf,
+	MBR_BSDI_FS_PARTITION		= 0xb7,
+	MBR_BSDI_SWAP_PARTITION		= 0xb8,
+	MBR_BOOTWIZARD_HIDDEN_PARTITION	= 0xbb,
+	MBR_SOLARIS_BOOT_PARTITION	= 0xbe,
+	MBR_SOLARIS_PARTITION		= 0xbf,
+	MBR_DRDOS_FAT12_PARTITION	= 0xc1,
+	MBR_DRDOS_FAT16_L32M_PARTITION	= 0xc4,
+	MBR_DRDOS_FAT16_PARTITION	= 0xc6,
+	MBR_SYRINX_PARTITION		= 0xc7,
+	MBR_NONFS_DATA_PARTITION	= 0xda,
+	MBR_CPM_CTOS_PARTITION		= 0xdb, /* CP/M or Concurrent CP/M or Concurrent DOS or CTOS */
+	MBR_DELL_UTILITY_PARTITION	= 0xde, /* Dell PowerEdge Server utilities */
+	MBR_BOOTIT_PARTITION		= 0xdf, /* BootIt EMBRM */
+	MBR_DOS_ACCESS_PARTITION	= 0xe1, /* DOS access or SpeedStor 12-bit FAT extended partition */
+	MBR_DOS_RO_PARTITION		= 0xe3, /* DOS R/O or SpeedStor */
+	MBR_SPEEDSTOR_EXTENDED_PARTITION = 0xe4, /* SpeedStor 16-bit FAT extended partition < 1024 cyl. */
+	MBR_BEOS_FS_PARTITION		= 0xeb,
+	MBR_GPT_PARTITION		= 0xee, /* Intel EFI GUID Partition Table */
+	MBR_EFI_SYSTEM_PARTITION	= 0xef, /* Intel EFI System Partition */
+	MBR_LINUX_PARISC_BOOT_PARTITION	= 0xf0, /* Linux/PA-RISC boot loader */
+	MBR_SPEEDSTOR1_PARTITION	= 0xf1,
+	MBR_SPEEDSTOR2_PARTITION	= 0xf4, /* SpeedStor large partition */
+	MBR_DOS_SECONDARY_PARTITION	= 0xf2, /* DOS 3.3+ secondary */
+	MBR_VMWARE_VMFS_PARTITION	= 0xfb,
+	MBR_VMWARE_VMKCORE_PARTITION	= 0xfc, /* VMware kernel dump partition */
+	MBR_LINUX_RAID_PARTITION	= 0xfd, /* New (2.2.x) raid partition with autodetect using persistent superblock */
+	MBR_LANSTEP_PARTITION		= 0xfe, /* SpeedStor >1024 cyl. or LANstep */
+	MBR_XENIX_BBT_PARTITION		= 0xff, /* Xenix Bad Block Table */
+};
+
+#endif /* UTIL_LINUX_PT_MBR_H */
diff --git a/libblkid/src/read.c b/libblkid/src/read.c
new file mode 100644
index 0000000..99a9684
--- /dev/null
+++ b/libblkid/src/read.c
@@ -0,0 +1,507 @@
+/*
+ * read.c - read the blkid cache from disk, to avoid scanning all devices
+ *
+ * Copyright (C) 2001, 2003 Theodore Y. Ts'o
+ * Copyright (C) 2001 Andreas Dilger
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+
+#include "blkidP.h"
+
+#ifdef HAVE_STDLIB_H
+# ifndef _XOPEN_SOURCE
+#  define _XOPEN_SOURCE 600	/* for inclusion of strtoull */
+# endif
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_STRTOULL
+#define STRTOULL strtoull /* defined in stdlib.h if you try hard enough */
+#else
+/* FIXME: need to support real strtoull here */
+#define STRTOULL strtoul
+#endif
+
+#ifdef TEST_PROGRAM
+#define blkid_debug_dump_dev(dev)	(debug_dump_dev(dev))
+static void debug_dump_dev(blkid_dev dev);
+#endif
+
+/*
+ * File format:
+ *
+ *	<device [<NAME="value"> ...]>device_name</device>
+ *
+ *	The following tags are required for each entry:
+ *	<ID="id">	unique (within this file) ID number of this device
+ *	<TIME="sec.usec"> (time_t and suseconds_t) time this entry was last
+ *	                 read from disk
+ *	<TYPE="type">	(detected) type of filesystem/data for this partition
+ *
+ *	The following tags may be present, depending on the device contents
+ *	<LABEL="label">	(user supplied) label (volume name, etc)
+ *	<UUID="uuid">	(generated) universally unique identifier (serial no)
+ */
+
+static char *skip_over_blank(char *cp)
+{
+	while (*cp && isspace(*cp))
+		cp++;
+	return cp;
+}
+
+static char *skip_over_word(char *cp)
+{
+	char ch;
+
+	while ((ch = *cp)) {
+		/* If we see a backslash, skip the next character */
+		if (ch == '\\') {
+			cp++;
+			if (*cp == '\0')
+				break;
+			cp++;
+			continue;
+		}
+		if (isspace(ch) || ch == '<' || ch == '>')
+			break;
+		cp++;
+	}
+	return cp;
+}
+
+static char *strip_line(char *line)
+{
+	char	*p;
+
+	line = skip_over_blank(line);
+
+	p = line + strlen(line) - 1;
+
+	while (*line) {
+		if (isspace(*p))
+			*p-- = '\0';
+		else
+			break;
+	}
+
+	return line;
+}
+
+#if 0
+static char *parse_word(char **buf)
+{
+	char *word, *next;
+
+	word = *buf;
+	if (*word == '\0')
+		return NULL;
+
+	word = skip_over_blank(word);
+	next = skip_over_word(word);
+	if (*next) {
+		char *end = next - 1;
+		if (*end == '"' || *end == '\'')
+			*end = '\0';
+		*next++ = '\0';
+	}
+	*buf = next;
+
+	if (*word == '"' || *word == '\'')
+		word++;
+	return word;
+}
+#endif
+
+/*
+ * Start parsing a new line from the cache.
+ *
+ * line starts with "<device" return 1 -> continue parsing line
+ * line starts with "<foo", empty, or # return 0 -> skip line
+ * line starts with other, return -BLKID_ERR_CACHE -> error
+ */
+static int parse_start(char **cp)
+{
+	char *p;
+
+	p = strip_line(*cp);
+
+	/* Skip comment or blank lines.  We can't just NUL the first '#' char,
+	 * in case it is inside quotes, or escaped.
+	 */
+	if (*p == '\0' || *p == '#')
+		return 0;
+
+	if (!strncmp(p, "<device", 7)) {
+		DBG(READ, ul_debug("found device header: %8s", p));
+		p += 7;
+
+		*cp = p;
+		return 1;
+	}
+
+	if (*p == '<')
+		return 0;
+
+	return -BLKID_ERR_CACHE;
+}
+
+/* Consume the remaining XML on the line (cosmetic only) */
+static int parse_end(char **cp)
+{
+	*cp = skip_over_blank(*cp);
+
+	if (!strncmp(*cp, "</device>", 9)) {
+		DBG(READ, ul_debug("found device trailer %9s", *cp));
+		*cp += 9;
+		return 0;
+	}
+
+	return -BLKID_ERR_CACHE;
+}
+
+/*
+ * Allocate a new device struct with device name filled in.  Will handle
+ * finding the device on lines of the form:
+ * <device foo=bar>devname</device>
+ * <device>devname<foo>bar</foo></device>
+ */
+static int parse_dev(blkid_cache cache, blkid_dev *dev, char **cp)
+{
+	char *start, *tmp, *end, *name;
+	int ret;
+
+	if ((ret = parse_start(cp)) <= 0)
+		return ret;
+
+	start = tmp = strchr(*cp, '>');
+	if (!start) {
+		DBG(READ, ul_debug("blkid: short line parsing dev: %s", *cp));
+		return -BLKID_ERR_CACHE;
+	}
+	start = skip_over_blank(start + 1);
+	end = skip_over_word(start);
+
+	DBG(READ, ul_debug("device should be %*s",
+			       (int)(end - start), start));
+
+	if (**cp == '>')
+		*cp = end;
+	else
+		(*cp)++;
+
+	*tmp = '\0';
+
+	if (!(tmp = strrchr(end, '<')) || parse_end(&tmp) < 0) {
+		DBG(READ, ul_debug("blkid: missing </device> ending: %s", end));
+	} else if (tmp)
+		*tmp = '\0';
+
+	if (end - start <= 1) {
+		DBG(READ, ul_debug("blkid: empty device name: %s", *cp));
+		return -BLKID_ERR_CACHE;
+	}
+
+	name = strndup(start, end - start);
+	if (name == NULL)
+		return -BLKID_ERR_MEM;
+
+	DBG(READ, ul_debug("found dev %s", name));
+
+	if (!(*dev = blkid_get_dev(cache, name, BLKID_DEV_CREATE))) {
+		free(name);
+		return -BLKID_ERR_MEM;
+	}
+
+	free(name);
+	return 1;
+}
+
+/*
+ * Extract a tag of the form NAME="value" from the line.
+ */
+static int parse_token(char **name, char **value, char **cp)
+{
+	char *end;
+
+	if (!name || !value || !cp)
+		return -BLKID_ERR_PARAM;
+
+	if (!(*value = strchr(*cp, '=')))
+		return 0;
+
+	**value = '\0';
+	*name = strip_line(*cp);
+	*value = skip_over_blank(*value + 1);
+
+	if (**value == '"') {
+		char *p = end = *value + 1;
+
+		/* convert 'foo\"bar'  to 'foo"bar' */
+		while (*p) {
+			if (*p == '\\') {
+				p++;
+				*end = *p;
+			} else {
+				*end = *p;
+				if (*p == '"')
+					break;
+			}
+			p++;
+			end++;
+		}
+
+		if (*end != '"') {
+			DBG(READ, ul_debug("unbalanced quotes at: %s", *value));
+			*cp = *value;
+			return -BLKID_ERR_CACHE;
+		}
+		(*value)++;
+		*end = '\0';
+		end = ++p;
+	} else {
+		end = skip_over_word(*value);
+		if (*end) {
+			*end = '\0';
+			end++;
+		}
+	}
+	*cp = end;
+
+	return 1;
+}
+
+/*
+ * Extract a tag of the form <NAME>value</NAME> from the line.
+ */
+/*
+static int parse_xml(char **name, char **value, char **cp)
+{
+	char *end;
+
+	if (!name || !value || !cp)
+		return -BLKID_ERR_PARAM;
+
+	*name = strip_line(*cp);
+
+	if ((*name)[0] != '<' || (*name)[1] == '/')
+		return 0;
+
+	FIXME: finish this.
+}
+*/
+
+/*
+ * Extract a tag from the line.
+ *
+ * Return 1 if a valid tag was found.
+ * Return 0 if no tag found.
+ * Return -ve error code.
+ */
+static int parse_tag(blkid_cache cache, blkid_dev dev, char **cp)
+{
+	char *name = NULL;
+	char *value = NULL;
+	int ret;
+
+	if (!cache || !dev)
+		return -BLKID_ERR_PARAM;
+
+	if ((ret = parse_token(&name, &value, cp)) <= 0 /* &&
+	    (ret = parse_xml(&name, &value, cp)) <= 0 */)
+		return ret;
+
+	/* Some tags are stored directly in the device struct */
+	if (!strcmp(name, "DEVNO"))
+		dev->bid_devno = STRTOULL(value, 0, 0);
+	else if (!strcmp(name, "PRI"))
+		dev->bid_pri = strtol(value, 0, 0);
+	else if (!strcmp(name, "TIME")) {
+		char *end = NULL;
+		dev->bid_time = STRTOULL(value, &end, 0);
+		if (end && *end == '.')
+			dev->bid_utime = STRTOULL(end + 1, 0, 0);
+	} else
+		ret = blkid_set_tag(dev, name, value, strlen(value));
+
+	DBG(READ, ul_debug("    tag: %s=\"%s\"", name, value));
+
+	return ret < 0 ? ret : 1;
+}
+
+/*
+ * Parse a single line of data, and return a newly allocated dev struct.
+ * Add the new device to the cache struct, if one was read.
+ *
+ * Lines are of the form <device [TAG="value" ...]>/dev/foo</device>
+ *
+ * Returns -ve value on error.
+ * Returns 0 otherwise.
+ * If a valid device was read, *dev_p is non-NULL, otherwise it is NULL
+ * (e.g. comment lines, unknown XML content, etc).
+ */
+static int blkid_parse_line(blkid_cache cache, blkid_dev *dev_p, char *cp)
+{
+	blkid_dev dev;
+	int ret;
+
+	if (!cache || !dev_p)
+		return -BLKID_ERR_PARAM;
+
+	*dev_p = NULL;
+
+	DBG(READ, ul_debug("line: %s", cp));
+
+	if ((ret = parse_dev(cache, dev_p, &cp)) <= 0)
+		return ret;
+
+	dev = *dev_p;
+
+	while ((ret = parse_tag(cache, dev, &cp)) > 0) {
+		;
+	}
+
+	if (dev->bid_type == NULL) {
+		DBG(READ, ul_debug("blkid: device %s has no TYPE",dev->bid_name));
+		blkid_free_dev(dev);
+		goto done;
+	}
+
+	DBG(READ, blkid_debug_dump_dev(dev));
+
+done:
+	return ret;
+}
+
+/*
+ * Parse the specified filename, and return the data in the supplied or
+ * a newly allocated cache struct.  If the file doesn't exist, return a
+ * new empty cache struct.
+ */
+void blkid_read_cache(blkid_cache cache)
+{
+	FILE *file;
+	char buf[4096];
+	int fd, lineno = 0;
+	struct stat st;
+
+	/*
+	 * If the file doesn't exist, then we just return an empty
+	 * struct so that the cache can be populated.
+	 */
+	if ((fd = open(cache->bic_filename, O_RDONLY|O_CLOEXEC)) < 0)
+		return;
+	if (fstat(fd, &st) < 0)
+		goto errout;
+	if ((st.st_mtime == cache->bic_ftime) ||
+	    (cache->bic_flags & BLKID_BIC_FL_CHANGED)) {
+		DBG(CACHE, ul_debug("skipping re-read of %s",
+					cache->bic_filename));
+		goto errout;
+	}
+
+	DBG(CACHE, ul_debug("reading cache file %s",
+				cache->bic_filename));
+
+	file = fdopen(fd, "r" UL_CLOEXECSTR);
+	if (!file)
+		goto errout;
+
+	while (fgets(buf, sizeof(buf), file)) {
+		blkid_dev dev;
+		unsigned int end;
+
+		lineno++;
+		if (buf[0] == 0)
+			continue;
+		end = strlen(buf) - 1;
+		/* Continue reading next line if it ends with a backslash */
+		while (end < (sizeof(buf) - 2) && buf[end] == '\\' &&
+		       fgets(buf + end, sizeof(buf) - end, file)) {
+			end = strlen(buf) - 1;
+			lineno++;
+		}
+
+		if (blkid_parse_line(cache, &dev, buf) < 0) {
+			DBG(READ, ul_debug("blkid: bad format on line %d", lineno));
+			continue;
+		}
+	}
+	fclose(file);
+
+	/*
+	 * Initially we do not need to write out the cache file.
+	 */
+	cache->bic_flags &= ~BLKID_BIC_FL_CHANGED;
+	cache->bic_ftime = st.st_mtime;
+
+	return;
+errout:
+	close(fd);
+	return;
+}
+
+#ifdef TEST_PROGRAM
+static void debug_dump_dev(blkid_dev dev)
+{
+	struct list_head *p;
+
+	if (!dev) {
+		printf("  dev: NULL\n");
+		return;
+	}
+
+	printf("  dev: name = %s\n", dev->bid_name);
+	printf("  dev: DEVNO=\"0x%0llx\"\n", (long long)dev->bid_devno);
+	printf("  dev: TIME=\"%ld.%ld\"\n", (long)dev->bid_time, (long)dev->bid_utime);
+	printf("  dev: PRI=\"%d\"\n", dev->bid_pri);
+	printf("  dev: flags = 0x%08X\n", dev->bid_flags);
+
+	list_for_each(p, &dev->bid_tags) {
+		blkid_tag tag = list_entry(p, struct blkid_struct_tag, bit_tags);
+		if (tag)
+			printf("    tag: %s=\"%s\"\n", tag->bit_name,
+			       tag->bit_val);
+		else
+			printf("    tag: NULL\n");
+	}
+	printf("\n");
+}
+
+int main(int argc, char**argv)
+{
+	blkid_cache cache = NULL;
+	int ret;
+
+	blkid_init_debug(BLKID_DEBUG_ALL);
+	if (argc > 2) {
+		fprintf(stderr, "Usage: %s [filename]\n"
+			"Test parsing of the cache (filename)\n", argv[0]);
+		exit(1);
+	}
+	if ((ret = blkid_get_cache(&cache, argv[1])) < 0)
+		fprintf(stderr, "error %d reading cache file %s\n", ret,
+			argv[1] ? argv[1] : blkid_get_cache_filename(NULL));
+
+	blkid_put_cache(cache);
+
+	return ret;
+}
+#endif
diff --git a/libblkid/src/resolve.c b/libblkid/src/resolve.c
new file mode 100644
index 0000000..59f0fea
--- /dev/null
+++ b/libblkid/src/resolve.c
@@ -0,0 +1,130 @@
+/*
+ * resolve.c - resolve names and tags into specific devices
+ *
+ * Copyright (C) 2001, 2003 Theodore Ts'o.
+ * Copyright (C) 2001 Andreas Dilger
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "blkidP.h"
+
+/*
+ * Find a tagname (e.g. LABEL or UUID) on a specific device.
+ */
+char *blkid_get_tag_value(blkid_cache cache, const char *tagname,
+			  const char *devname)
+{
+	blkid_tag found;
+	blkid_dev dev;
+	blkid_cache c = cache;
+	char *ret = NULL;
+
+	DBG(TAG, ul_debug("looking for %s on %s", tagname, devname));
+
+	if (!devname)
+		return NULL;
+	if (!cache && blkid_get_cache(&c, NULL) < 0)
+		return NULL;
+
+	if ((dev = blkid_get_dev(c, devname, BLKID_DEV_NORMAL)) &&
+	    (found = blkid_find_tag_dev(dev, tagname)))
+		ret = found->bit_val ? strdup(found->bit_val) : NULL;
+
+	if (!cache)
+		blkid_put_cache(c);
+
+	return ret;
+}
+
+/*
+ * Locate a device name from a token (NAME=value string), or (name, value)
+ * pair.  In the case of a token, value is ignored.  If the "token" is not
+ * of the form "NAME=value" and there is no value given, then it is assumed
+ * to be the actual devname and a copy is returned.
+ */
+char *blkid_get_devname(blkid_cache cache, const char *token,
+			const char *value)
+{
+	blkid_dev dev;
+	blkid_cache c = cache;
+	char *t = 0, *v = 0;
+	char *ret = NULL;
+
+	if (!token)
+		return NULL;
+	if (!cache && blkid_get_cache(&c, NULL) < 0)
+		return NULL;
+
+	DBG(TAG, ul_debug("looking for %s%s%s %s", token, value ? "=" : "",
+		   value ? value : "", cache ? "in cache" : "from disk"));
+
+	if (!value) {
+		if (!strchr(token, '=')) {
+			ret = strdup(token);
+			goto out;
+		}
+		blkid_parse_tag_string(token, &t, &v);
+		if (!t || !v)
+			goto out;
+		token = t;
+		value = v;
+	}
+
+	dev = blkid_find_dev_with_tag(c, token, value);
+	if (!dev)
+		goto out;
+
+	ret = dev->bid_name ? strdup(dev->bid_name) : NULL;
+out:
+	free(t);
+	free(v);
+	if (!cache)
+		blkid_put_cache(c);
+	return ret;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+	char *value;
+	blkid_cache cache;
+
+	blkid_init_debug(BLKID_DEBUG_ALL);
+	if (argc != 2 && argc != 3) {
+		fprintf(stderr, "Usage:\t%s tagname=value\n"
+			"\t%s tagname devname\n"
+			"Find which device holds a given token or\n"
+			"Find what the value of a tag is in a device\n",
+			argv[0], argv[0]);
+		exit(1);
+	}
+	if (blkid_get_cache(&cache, "/dev/null") < 0) {
+		fprintf(stderr, "Couldn't get blkid cache\n");
+		exit(1);
+	}
+
+	if (argv[2]) {
+		value = blkid_get_tag_value(cache, argv[1], argv[2]);
+		printf("%s has tag %s=%s\n", argv[2], argv[1],
+		       value ? value : "<missing>");
+	} else {
+		value = blkid_get_devname(cache, argv[1], NULL);
+		printf("%s has tag %s\n", value ? value : "<none>", argv[1]);
+	}
+	blkid_put_cache(cache);
+	return value ? 0 : 1;
+}
+#endif
diff --git a/libblkid/src/save.c b/libblkid/src/save.c
new file mode 100644
index 0000000..bdc9c59
--- /dev/null
+++ b/libblkid/src/save.c
@@ -0,0 +1,241 @@
+/*
+ * save.c - write the cache struct to disk
+ *
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+
+#include "closestream.h"
+
+#include "blkidP.h"
+
+
+static void save_quoted(const char *data, FILE *file)
+{
+	const char *p;
+
+	fputc('"', file);
+	for (p = data; p && *p; p++) {
+		if ((unsigned char) *p == 0x22 ||		/* " */
+		    (unsigned char) *p == 0x5c)			/* \ */
+			fputc('\\', file);
+
+		fputc(*p, file);
+	}
+	fputc('"', file);
+}
+static int save_dev(blkid_dev dev, FILE *file)
+{
+	struct list_head *p;
+
+	if (!dev || dev->bid_name[0] != '/')
+		return 0;
+
+	DBG(SAVE, ul_debug("device %s, type %s", dev->bid_name, dev->bid_type ?
+		   dev->bid_type : "(null)"));
+
+	fprintf(file, "<device DEVNO=\"0x%04lx\" TIME=\"%ld.%ld\"",
+			(unsigned long) dev->bid_devno,
+			(long) dev->bid_time,
+			(long) dev->bid_utime);
+
+	if (dev->bid_pri)
+		fprintf(file, " PRI=\"%d\"", dev->bid_pri);
+
+	list_for_each(p, &dev->bid_tags) {
+		blkid_tag tag = list_entry(p, struct blkid_struct_tag, bit_tags);
+
+		fputc(' ', file);			/* space between tags */
+		fputs(tag->bit_name, file);		/* tag NAME */
+		fputc('=', file);			/* separator between NAME and VALUE */
+		save_quoted(tag->bit_val, file);	/* tag "VALUE" */
+	}
+	fprintf(file, ">%s</device>\n", dev->bid_name);
+
+	return 0;
+}
+
+/*
+ * Write out the cache struct to the cache file on disk.
+ */
+int blkid_flush_cache(blkid_cache cache)
+{
+	struct list_head *p;
+	char *tmp = NULL;
+	char *opened = NULL;
+	char *filename;
+	FILE *file = NULL;
+	int fd, ret = 0;
+	struct stat st;
+
+	if (list_empty(&cache->bic_devs) ||
+	    !(cache->bic_flags & BLKID_BIC_FL_CHANGED)) {
+		DBG(SAVE, ul_debug("skipping cache file write"));
+		return 0;
+	}
+
+	filename = cache->bic_filename ? cache->bic_filename :
+					 blkid_get_cache_filename(NULL);
+	if (!filename)
+		return -BLKID_ERR_PARAM;
+
+	if (strncmp(filename,
+	    BLKID_RUNTIME_DIR "/", sizeof(BLKID_RUNTIME_DIR)) == 0) {
+
+		/* default destination, create the directory if necessary */
+		if (stat(BLKID_RUNTIME_DIR, &st)
+		    && errno == ENOENT
+		    && mkdir(BLKID_RUNTIME_DIR, S_IWUSR|
+						S_IRUSR|S_IRGRP|S_IROTH|
+						S_IXUSR|S_IXGRP|S_IXOTH) != 0
+		    && errno != EEXIST) {
+			DBG(SAVE, ul_debug("can't create %s directory for cache file",
+					BLKID_RUNTIME_DIR));
+			return 0;
+		}
+	}
+
+	/* If we can't write to the cache file, then don't even try */
+	if (((ret = stat(filename, &st)) < 0 && errno != ENOENT) ||
+	    (ret == 0 && access(filename, W_OK) < 0)) {
+		DBG(SAVE, ul_debug("can't write to cache file %s", filename));
+		return 0;
+	}
+
+	/*
+	 * Try and create a temporary file in the same directory so
+	 * that in case of error we don't overwrite the cache file.
+	 * If the cache file doesn't yet exist, it isn't a regular
+	 * file (e.g. /dev/null or a socket), or we couldn't create
+	 * a temporary file then we open it directly.
+	 */
+	if (ret == 0 && S_ISREG(st.st_mode)) {
+		tmp = malloc(strlen(filename) + 8);
+		if (tmp) {
+			sprintf(tmp, "%s-XXXXXX", filename);
+			fd = mkstemp(tmp);
+			if (fd >= 0) {
+				if (fchmod(fd, 0644) != 0)
+					DBG(SAVE, ul_debug("%s: fchmod failed", filename));
+				else if ((file = fdopen(fd, "w" UL_CLOEXECSTR)))
+					opened = tmp;
+				if (!file)
+					close(fd);
+			}
+		}
+	}
+
+	if (!file) {
+		file = fopen(filename, "w" UL_CLOEXECSTR);
+		opened = filename;
+	}
+
+	DBG(SAVE, ul_debug("writing cache file %s (really %s)",
+		   filename, opened));
+
+	if (!file) {
+		ret = errno;
+		goto errout;
+	}
+
+	list_for_each(p, &cache->bic_devs) {
+		blkid_dev dev = list_entry(p, struct blkid_struct_dev, bid_devs);
+		if (!dev->bid_type || (dev->bid_flags & BLKID_BID_FL_REMOVABLE))
+			continue;
+		if ((ret = save_dev(dev, file)) < 0)
+			break;
+	}
+
+	if (ret >= 0) {
+		cache->bic_flags &= ~BLKID_BIC_FL_CHANGED;
+		ret = 1;
+	}
+
+	if (close_stream(file) != 0)
+		DBG(SAVE, ul_debug("write failed: %s", filename));
+
+	if (opened != filename) {
+		if (ret < 0) {
+			unlink(opened);
+			DBG(SAVE, ul_debug("unlinked temp cache %s", opened));
+		} else {
+			char *backup;
+
+			backup = malloc(strlen(filename) + 5);
+			if (backup) {
+				sprintf(backup, "%s.old", filename);
+				unlink(backup);
+				if (link(filename, backup)) {
+					DBG(SAVE, ul_debug("can't link %s to %s",
+							filename, backup));
+				}
+				free(backup);
+			}
+			if (rename(opened, filename)) {
+				ret = errno;
+				DBG(SAVE, ul_debug("can't rename %s to %s",
+						opened, filename));
+			} else {
+				DBG(SAVE, ul_debug("moved temp cache %s", opened));
+			}
+		}
+	}
+
+errout:
+	free(tmp);
+	if (filename != cache->bic_filename)
+		free(filename);
+	return ret;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+	blkid_cache cache = NULL;
+	int ret;
+
+	blkid_init_debug(BLKID_DEBUG_ALL);
+	if (argc > 2) {
+		fprintf(stderr, "Usage: %s [filename]\n"
+			"Test loading/saving a cache (filename)\n", argv[0]);
+		exit(1);
+	}
+
+	if ((ret = blkid_get_cache(&cache, "/dev/null")) != 0) {
+		fprintf(stderr, "%s: error creating cache (%d)\n",
+			argv[0], ret);
+		exit(1);
+	}
+	if ((ret = blkid_probe_all(cache)) < 0) {
+		fprintf(stderr, "error (%d) probing devices\n", ret);
+		exit(1);
+	}
+	cache->bic_filename = strdup(argv[1]);
+
+	if ((ret = blkid_flush_cache(cache)) < 0) {
+		fprintf(stderr, "error (%d) saving cache\n", ret);
+		exit(1);
+	}
+
+	blkid_put_cache(cache);
+
+	return ret;
+}
+#endif
diff --git a/libblkid/src/strutils.h b/libblkid/src/strutils.h
new file mode 100644
index 0000000..4d8463a
--- /dev/null
+++ b/libblkid/src/strutils.h
@@ -0,0 +1,204 @@
+#ifndef UTIL_LINUX_STRUTILS
+#define UTIL_LINUX_STRUTILS
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+/* default strtoxx_or_err() exit code */
+#ifndef STRTOXX_EXIT_CODE
+# define STRTOXX_EXIT_CODE EXIT_FAILURE
+#endif
+
+
+extern int parse_size(const char *str, uintmax_t *res, int *power);
+extern int strtosize(const char *str, uintmax_t *res);
+extern uintmax_t strtosize_or_err(const char *str, const char *errmesg);
+
+extern int16_t strtos16_or_err(const char *str, const char *errmesg);
+extern uint16_t strtou16_or_err(const char *str, const char *errmesg);
+
+extern int32_t strtos32_or_err(const char *str, const char *errmesg);
+extern uint32_t strtou32_or_err(const char *str, const char *errmesg);
+
+extern int64_t strtos64_or_err(const char *str, const char *errmesg);
+extern uint64_t strtou64_or_err(const char *str, const char *errmesg);
+
+extern double strtod_or_err(const char *str, const char *errmesg);
+
+extern long strtol_or_err(const char *str, const char *errmesg);
+extern unsigned long strtoul_or_err(const char *str, const char *errmesg);
+
+extern void strtotimeval_or_err(const char *str, struct timeval *tv,
+		const char *errmesg);
+
+extern int isdigit_string(const char *str);
+
+#ifndef HAVE_MEMPCPY
+extern void *mempcpy(void *restrict dest, const void *restrict src, size_t n);
+#endif
+#ifndef HAVE_STRNLEN
+extern size_t strnlen(const char *s, size_t maxlen);
+#endif
+#ifndef HAVE_STRNDUP
+extern char *strndup(const char *s, size_t n);
+#endif
+#ifndef HAVE_STRNCHR
+extern char *strnchr(const char *s, size_t maxlen, int c);
+#endif
+
+/* caller guarantees n > 0 */
+static inline void xstrncpy(char *dest, const char *src, size_t n)
+{
+	strncpy(dest, src, n-1);
+	dest[n-1] = 0;
+}
+
+static inline char *strdup_to_offset(void *stru, size_t offset, const char *str)
+{
+	char *n = NULL;
+	char **o = (char **) ((char *) stru + offset);
+
+	if (str) {
+		n = strdup(str);
+		if (!n)
+			return NULL;
+	}
+
+	free(*o);
+	*o = n;
+	return n;
+}
+
+#define strdup_to_struct_member(_s, _m, _str) \
+		strdup_to_offset((void *) _s, offsetof(__typeof__(*(_s)), _m), _str)
+
+extern void strmode(mode_t mode, char *str);
+
+/* Options for size_to_human_string() */
+enum
+{
+        SIZE_SUFFIX_1LETTER = 0,
+        SIZE_SUFFIX_3LETTER = 1,
+        SIZE_SUFFIX_SPACE   = 2
+};
+
+extern char *size_to_human_string(int options, uint64_t bytes);
+
+extern int string_to_idarray(const char *list, int ary[], size_t arysz,
+			   int (name2id)(const char *, size_t));
+extern int string_add_to_idarray(const char *list, int ary[],
+				 size_t arysz, int *ary_pos,
+				 int (name2id)(const char *, size_t));
+
+extern int string_to_bitarray(const char *list, char *ary,
+			    int (*name2bit)(const char *, size_t));
+
+extern int string_to_bitmask(const char *list,
+			     unsigned long *mask,
+			     long (*name2flag)(const char *, size_t));
+extern int parse_range(const char *str, int *lower, int *upper, int def);
+
+extern int streq_except_trailing_slash(const char *s1, const char *s2);
+
+/*
+ * Match string beginning.
+ */
+static inline const char *startswith(const char *s, const char *prefix)
+{
+	size_t sz = prefix ? strlen(prefix) : 0;
+
+        if (s && sz && strncmp(s, prefix, sz) == 0)
+                return s + sz;
+	return NULL;
+}
+
+/*
+ * Case insensitive match string beginning.
+ */
+static inline const char *startswith_no_case(const char *s, const char *prefix)
+{
+	size_t sz = prefix ? strlen(prefix) : 0;
+
+        if (s && sz && strncasecmp(s, prefix, sz) == 0)
+                return s + sz;
+	return NULL;
+}
+
+/*
+ * Match string ending.
+ */
+static inline const char *endswith(const char *s, const char *postfix)
+{
+	size_t sl = s ? strlen(s) : 0;
+	size_t pl = postfix ? strlen(postfix) : 0;
+
+	if (pl == 0)
+		return (char *)s + sl;
+	if (sl < pl)
+		return NULL;
+	if (memcmp(s + sl - pl, postfix, pl) != 0)
+		return NULL;
+	return (char *)s + sl - pl;
+}
+
+/*
+ * Skip leading white space.
+ */
+static inline const char *skip_space(const char *p)
+{
+	while (isspace(*p))
+		++p;
+	return p;
+}
+
+static inline const char *skip_blank(const char *p)
+{
+	while (isblank(*p))
+		++p;
+	return p;
+}
+
+
+/* Removes whitespace from the right-hand side of a string (trailing
+ * whitespace).
+ *
+ * Returns size of the new string (without \0).
+ */
+static inline size_t rtrim_whitespace(unsigned char *str)
+{
+	size_t i = strlen((char *) str);
+
+	while (i) {
+		i--;
+		if (!isspace(str[i])) {
+			i++;
+			break;
+		}
+	}
+	str[i] = '\0';
+	return i;
+}
+
+/* Removes whitespace from the left-hand side of a string.
+ *
+ * Returns size of the new string (without \0).
+ */
+static inline size_t ltrim_whitespace(unsigned char *str)
+{
+	size_t len;
+	unsigned char *p;
+
+	for (p = str; p && isspace(*p); p++);
+
+	len = strlen((char *) p);
+
+	if (len && p > str)
+		memmove(str, p, len + 1);
+
+	return len;
+}
+
+#endif
diff --git a/libblkid/src/superblocks/adaptec_raid.c b/libblkid/src/superblocks/adaptec_raid.c
new file mode 100644
index 0000000..65fd5b8
--- /dev/null
+++ b/libblkid/src/superblocks/adaptec_raid.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct adaptec_metadata {
+
+	uint32_t	b0idcode;
+	uint8_t		lunsave[8];
+	uint16_t	sdtype;
+	uint16_t	ssavecyl;
+	uint8_t		ssavehed;
+	uint8_t		ssavesec;
+	uint8_t		sb0flags;
+	uint8_t		jbodEnable;
+	uint8_t		lundsave;
+	uint8_t		svpdirty;
+	uint16_t	biosInfo;
+	uint16_t	svwbskip;
+	uint16_t	svwbcln;
+	uint16_t	svwbmax;
+	uint16_t	res3;
+	uint16_t	svwbmin;
+	uint16_t	res4;
+	uint16_t	svrcacth;
+	uint16_t	svwcacth;
+	uint16_t	svwbdly;
+	uint8_t		svsdtime;
+	uint8_t		res5;
+	uint16_t	firmval;
+	uint16_t	firmbln;
+	uint32_t	firmblk;
+	uint32_t	fstrsvrb;
+	uint16_t	svBlockStorageTid;
+	uint16_t	svtid;
+	uint8_t		svseccfl;
+	uint8_t		res6;
+	uint8_t		svhbanum;
+	uint8_t		resver;
+	uint32_t	drivemagic;
+	uint8_t		reserved[20];
+	uint8_t		testnum;
+	uint8_t		testflags;
+	uint16_t	maxErrorCount;
+	uint32_t	count;
+	uint32_t	startTime;
+	uint32_t	interval;
+	uint8_t		tstxt0;
+	uint8_t		tstxt1;
+	uint8_t		serNum[32];
+	uint8_t		res8[102];
+	uint32_t	fwTestMagic;
+	uint32_t	fwTestSeqNum;
+	uint8_t		fwTestRes[8];
+	uint32_t	smagic;
+	uint32_t	raidtbl;
+	uint16_t	raidline;
+	uint8_t		res9[0xF6];
+} __attribute__((packed));
+
+#define AD_SIGNATURE	0x4450544D	/* "DPTM" */
+#define AD_MAGIC	0x37FC4D1E
+
+static int probe_adraid(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	uint64_t off;
+	struct adaptec_metadata *ad;
+
+	if (pr->size < 0x10000)
+		return BLKID_PROBE_NONE;
+
+	if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+		return BLKID_PROBE_NONE;
+
+	off = ((pr->size / 0x200)-1) * 0x200;
+	ad = (struct adaptec_metadata *)
+			blkid_probe_get_buffer(pr,
+					off,
+					sizeof(struct adaptec_metadata));
+	if (!ad)
+		return errno ? -errno : BLKID_PROBE_NONE;;
+
+	if (ad->smagic != be32_to_cpu(AD_SIGNATURE))
+		return BLKID_PROBE_NONE;
+	if (ad->b0idcode != be32_to_cpu(AD_MAGIC))
+		return BLKID_PROBE_NONE;
+	if (blkid_probe_sprintf_version(pr, "%u", ad->resver) != 0)
+		return BLKID_PROBE_NONE;
+	if (blkid_probe_set_magic(pr, off, sizeof(ad->b0idcode),
+				(unsigned char *) &ad->b0idcode))
+		return BLKID_PROBE_NONE;
+
+	return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo adraid_idinfo = {
+	.name		= "adaptec_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_adraid,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/bcache.c b/libblkid/src/superblocks/bcache.c
new file mode 100644
index 0000000..b3e397b
--- /dev/null
+++ b/libblkid/src/superblocks/bcache.c
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2013 Rolf Fokkens <rolf@fokkens.nl>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Based on code fragments from bcache-tools by Kent Overstreet:
+ * http://evilpiepirate.org/git/bcache-tools.git
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+
+#include "superblocks.h"
+#include "crc64.h"
+
+#define SB_LABEL_SIZE      32
+#define SB_JOURNAL_BUCKETS 256U
+
+#define node(i, j)         ((i)->d + (j))
+#define end(i)             node(i, (i)->keys)
+
+static const char bcache_magic[] = {
+	0xc6, 0x85, 0x73, 0xf6, 0x4e, 0x1a, 0x45, 0xca,
+	0x82, 0x65, 0xf5, 0x7f, 0x48, 0xba, 0x6d, 0x81
+};
+
+struct bcache_super_block {
+	uint64_t		csum;
+	uint64_t		offset;	/* sector where this sb was written */
+	uint64_t		version;
+
+	uint8_t			magic[16];
+
+	uint8_t			uuid[16];
+	union {
+		uint8_t		set_uuid[16];
+		uint64_t	set_magic;
+	};
+	uint8_t			label[SB_LABEL_SIZE];
+
+	uint64_t		flags;
+	uint64_t		seq;
+	uint64_t		pad[8];
+
+	union {
+	struct {
+		/* Cache devices */
+		uint64_t	nbuckets;	/* device size */
+
+		uint16_t	block_size;	/* sectors */
+		uint16_t	bucket_size;	/* sectors */
+
+		uint16_t	nr_in_set;
+		uint16_t	nr_this_dev;
+	};
+	struct {
+		/* Backing devices */
+		uint64_t	data_offset;
+
+		/*
+		 * block_size from the cache device section is still used by
+		 * backing devices, so don't add anything here until we fix
+		 * things to not need it for backing devices anymore
+		 */
+	};
+	};
+
+	uint32_t		last_mount;	/* time_t */
+
+	uint16_t		first_bucket;
+	union {
+		uint16_t	njournal_buckets;
+		uint16_t	keys;
+	};
+	uint64_t		d[SB_JOURNAL_BUCKETS];	/* journal buckets */
+};
+
+/* magic string */
+#define BCACHE_SB_MAGIC     bcache_magic
+/* magic string len */
+#define BCACHE_SB_MAGIC_LEN sizeof (bcache_magic)
+/* super block offset */
+#define BCACHE_SB_OFF       0x1000
+/* supper block offset in kB */
+#define BCACHE_SB_KBOFF     (BCACHE_SB_OFF >> 10)
+/* magic string offset within super block */
+#define BCACHE_SB_MAGIC_OFF offsetof (struct bcache_super_block, magic)
+
+static uint64_t bcache_crc64(struct bcache_super_block *bcs)
+{
+	unsigned char *data = (unsigned char *) bcs;
+	size_t sz;
+
+	data += 8;		/* skip csum field */
+	sz = (unsigned char *) end(bcs) - data;
+
+	return crc64(0xFFFFFFFFFFFFFFFFULL, data, sz) ^ 0xFFFFFFFFFFFFFFFFULL;
+}
+
+static int probe_bcache (blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct bcache_super_block *bcs;
+
+	bcs = blkid_probe_get_sb(pr, mag, struct bcache_super_block);
+	if (!bcs)
+		return errno ? -errno : BLKID_PROBE_NONE;
+
+	if (le64_to_cpu(bcs->offset) != BCACHE_SB_OFF / 512)
+		return BLKID_PROBE_NONE;
+	if (!blkid_probe_verify_csum(pr, bcache_crc64(bcs), le64_to_cpu(bcs->csum)))
+		return BLKID_PROBE_NONE;
+
+	if (blkid_probe_set_uuid(pr, bcs->uuid) < 0)
+		return BLKID_PROBE_NONE;
+
+	return BLKID_PROBE_OK;
+};
+
+const struct blkid_idinfo bcache_idinfo =
+{
+	.name		= "bcache",
+	.usage		= BLKID_USAGE_OTHER,
+	.probefunc	= probe_bcache,
+	.minsz		= 8192,
+	.magics		=
+	{
+		{ .magic = BCACHE_SB_MAGIC
+		, .len   = BCACHE_SB_MAGIC_LEN
+		, .kboff = BCACHE_SB_KBOFF
+		, .sboff = BCACHE_SB_MAGIC_OFF
+		} ,
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/befs.c b/libblkid/src/superblocks/befs.c
new file mode 100644
index 0000000..e4453bc
--- /dev/null
+++ b/libblkid/src/superblocks/befs.c
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2010 Jeroen Oortwijn <oortwijn@gmail.com>
+ *
+ * Partly based on the Haiku BFS driver by
+ *     Axel Dörfler <axeld@pinc-software.de>
+ *
+ * Also inspired by the Linux BeFS driver by
+ *     Will Dyson <will_dyson@pobox.com>, et al.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "superblocks.h"
+
+#define B_OS_NAME_LENGTH	0x20
+#define SUPER_BLOCK_MAGIC1	0x42465331	/* BFS1 */
+#define SUPER_BLOCK_MAGIC2	0xdd121031
+#define SUPER_BLOCK_MAGIC3	0x15b6830e
+#define SUPER_BLOCK_FS_ENDIAN	0x42494745	/* BIGE */
+#define INODE_MAGIC1		0x3bbe0ad9
+#define BPLUSTREE_MAGIC		0x69f6c2e8
+#define BPLUSTREE_NULL		-1LL
+#define NUM_DIRECT_BLOCKS	12
+#define B_UINT64_TYPE		0x554c4c47	/* ULLG */
+#define KEY_NAME		"be:volume_id"
+#define KEY_SIZE		8
+
+#define FS16_TO_CPU(value, fs_is_le) (fs_is_le ? le16_to_cpu(value) \
+							: be16_to_cpu(value))
+#define FS32_TO_CPU(value, fs_is_le) (fs_is_le ? le32_to_cpu(value) \
+							: be32_to_cpu(value))
+#define FS64_TO_CPU(value, fs_is_le) (fs_is_le ? le64_to_cpu(value) \
+							: be64_to_cpu(value))
+
+typedef struct block_run {
+	int32_t		allocation_group;
+	uint16_t	start;
+	uint16_t	len;
+} __attribute__((packed)) block_run, inode_addr;
+
+struct befs_super_block {
+	char		name[B_OS_NAME_LENGTH];
+	int32_t		magic1;
+	int32_t		fs_byte_order;
+	uint32_t	block_size;
+	uint32_t	block_shift;
+	int64_t		num_blocks;
+	int64_t		used_blocks;
+	int32_t		inode_size;
+	int32_t		magic2;
+	int32_t		blocks_per_ag;
+	int32_t		ag_shift;
+	int32_t		num_ags;
+	int32_t		flags;
+	block_run	log_blocks;
+	int64_t		log_start;
+	int64_t		log_end;
+	int32_t		magic3;
+	inode_addr	root_dir;
+	inode_addr	indices;
+	int32_t		pad[8];
+} __attribute__((packed));
+
+typedef struct data_stream {
+	block_run	direct[NUM_DIRECT_BLOCKS];
+	int64_t		max_direct_range;
+	block_run	indirect;
+	int64_t		max_indirect_range;
+	block_run	double_indirect;
+	int64_t		max_double_indirect_range;
+	int64_t		size;
+} __attribute__((packed)) data_stream;
+
+struct befs_inode {
+	int32_t		magic1;
+	inode_addr	inode_num;
+	int32_t		uid;
+	int32_t		gid;
+	int32_t		mode;
+	int32_t		flags;
+	int64_t		create_time;
+	int64_t		last_modified_time;
+	inode_addr	parent;
+	inode_addr	attributes;
+	uint32_t	type;
+	int32_t		inode_size;
+	uint32_t	etc;
+	data_stream	data;
+	int32_t		pad[4];
+	int32_t		small_data[0];
+} __attribute__((packed));
+
+struct small_data {
+	uint32_t	type;
+	uint16_t	name_size;
+	uint16_t	data_size;
+	char		name[0];
+} __attribute__((packed));
+
+struct bplustree_header {
+	uint32_t	magic;
+	uint32_t	node_size;
+	uint32_t	max_number_of_levels;
+	uint32_t	data_type;
+	int64_t		root_node_pointer;
+	int64_t		free_node_pointer;
+	int64_t		maximum_size;
+} __attribute__((packed));
+
+struct bplustree_node {
+	int64_t		left_link;
+	int64_t		right_link;
+	int64_t		overflow_link;
+	uint16_t	all_key_count;
+	uint16_t	all_key_length;
+	char		name[0];
+} __attribute__((packed));
+
+static unsigned char *get_block_run(blkid_probe pr, const struct befs_super_block *bs,
+					const struct block_run *br, int fs_le)
+{
+	return blkid_probe_get_buffer(pr,
+			((blkid_loff_t) FS32_TO_CPU(br->allocation_group, fs_le)
+					<< FS32_TO_CPU(bs->ag_shift, fs_le)
+					<< FS32_TO_CPU(bs->block_shift, fs_le))
+				+ ((blkid_loff_t) FS16_TO_CPU(br->start, fs_le)
+					<< FS32_TO_CPU(bs->block_shift, fs_le)),
+			(blkid_loff_t) FS16_TO_CPU(br->len, fs_le)
+					<< FS32_TO_CPU(bs->block_shift, fs_le));
+}
+
+static unsigned char *get_custom_block_run(blkid_probe pr,
+				const struct befs_super_block *bs,
+				const struct block_run *br,
+				int64_t offset, uint32_t length, int fs_le)
+{
+	if (offset + length > (int64_t) FS16_TO_CPU(br->len, fs_le)
+					<< FS32_TO_CPU(bs->block_shift, fs_le))
+		return NULL;
+
+	return blkid_probe_get_buffer(pr,
+			((blkid_loff_t) FS32_TO_CPU(br->allocation_group, fs_le)
+					<< FS32_TO_CPU(bs->ag_shift, fs_le)
+					<< FS32_TO_CPU(bs->block_shift, fs_le))
+				+ ((blkid_loff_t) FS16_TO_CPU(br->start, fs_le)
+					<< FS32_TO_CPU(bs->block_shift, fs_le))
+				+ offset,
+			length);
+}
+
+static unsigned char *get_tree_node(blkid_probe pr, const struct befs_super_block *bs,
+				const struct data_stream *ds,
+				int64_t start, uint32_t length, int fs_le)
+{
+	if (start < (int64_t) FS64_TO_CPU(ds->max_direct_range, fs_le)) {
+		int64_t br_len;
+		size_t i;
+
+		for (i = 0; i < NUM_DIRECT_BLOCKS; i++) {
+			br_len = (int64_t) FS16_TO_CPU(ds->direct[i].len, fs_le)
+					<< FS32_TO_CPU(bs->block_shift, fs_le);
+			if (start < br_len)
+				return get_custom_block_run(pr, bs,
+							&ds->direct[i],
+							start, length, fs_le);
+			else
+				start -= br_len;
+		}
+	} else if (start < (int64_t) FS64_TO_CPU(ds->max_indirect_range, fs_le)) {
+		struct block_run *br;
+		int64_t max_br, br_len, i;
+
+		start -= FS64_TO_CPU(ds->max_direct_range, fs_le);
+		max_br = ((int64_t) FS16_TO_CPU(ds->indirect.len, fs_le)
+					<< FS32_TO_CPU(bs->block_shift, fs_le))
+				/ sizeof(struct block_run);
+
+		br = (struct block_run *) get_block_run(pr, bs, &ds->indirect,
+									fs_le);
+		if (!br)
+			return NULL;
+
+		for (i = 0; i < max_br; i++) {
+			br_len = (int64_t) FS16_TO_CPU(br[i].len, fs_le)
+					<< FS32_TO_CPU(bs->block_shift, fs_le);
+			if (start < br_len)
+				return get_custom_block_run(pr, bs, &br[i],
+							start, length, fs_le);
+			else
+				start -= br_len;
+		}
+	} else if (start < (int64_t) FS64_TO_CPU(ds->max_double_indirect_range, fs_le)) {
+		struct block_run *br;
+		int64_t di_br_size, br_per_di_br, di_index, i_index;
+
+		start -= (int64_t) FS64_TO_CPU(ds->max_indirect_range, fs_le);
+
+		di_br_size = (int64_t) FS16_TO_CPU(ds->double_indirect.len,
+				fs_le) << FS32_TO_CPU(bs->block_shift, fs_le);
+		if (di_br_size == 0)
+			return NULL;
+
+		br_per_di_br = di_br_size / sizeof(struct block_run);
+		if (br_per_di_br == 0)
+			return NULL;
+
+		di_index = start / (br_per_di_br * di_br_size);
+		i_index = (start % (br_per_di_br * di_br_size)) / di_br_size;
+		start = (start % (br_per_di_br * di_br_size)) % di_br_size;
+
+		br = (struct block_run *) get_block_run(pr, bs,
+						&ds->double_indirect, fs_le);
+		if (!br)
+			return NULL;
+
+		br = (struct block_run *) get_block_run(pr, bs, &br[di_index],
+									fs_le);
+		if (!br)
+			return NULL;
+
+		return get_custom_block_run(pr, bs, &br[i_index], start, length,
+									fs_le);
+	}
+	return NULL;
+}
+
+static int32_t compare_keys(const char keys1[], uint16_t keylengths1[], int32_t index,
+			const char *key2, uint16_t keylength2, int fs_le)
+{
+	const char *key1;
+	uint16_t keylength1;
+	int32_t result;
+
+	key1 = &keys1[index == 0 ? 0 : FS16_TO_CPU(keylengths1[index - 1],
+									fs_le)];
+	keylength1 = FS16_TO_CPU(keylengths1[index], fs_le)
+			- (index == 0 ? 0 : FS16_TO_CPU(keylengths1[index - 1],
+									fs_le));
+
+	result = strncmp(key1, key2, min(keylength1, keylength2));
+
+	if (result == 0)
+		return keylength1 - keylength2;
+
+	return result;
+}
+
+static int64_t get_key_value(blkid_probe pr, const struct befs_super_block *bs,
+			const struct befs_inode *bi, const char *key, int fs_le)
+{
+	struct bplustree_header *bh;
+	struct bplustree_node *bn;
+	uint16_t *keylengths;
+	int64_t *values;
+	int64_t node_pointer;
+	int32_t first, last, mid, cmp;
+
+	errno = 0;
+	bh = (struct bplustree_header *) get_tree_node(pr, bs, &bi->data, 0,
+					sizeof(struct bplustree_header), fs_le);
+	if (!bh)
+		return errno ? -errno : -ENOENT;
+
+	if ((int32_t) FS32_TO_CPU(bh->magic, fs_le) != BPLUSTREE_MAGIC)
+		return -ENOENT;
+
+	node_pointer = FS64_TO_CPU(bh->root_node_pointer, fs_le);
+
+	do {
+		errno = 0;
+		bn = (struct bplustree_node *) get_tree_node(pr, bs, &bi->data,
+			node_pointer, FS32_TO_CPU(bh->node_size, fs_le), fs_le);
+		if (!bn)
+			return errno ? -errno : -ENOENT;
+
+		keylengths = (uint16_t *) ((uint8_t *) bn
+				+ ((sizeof(struct bplustree_node)
+					+ FS16_TO_CPU(bn->all_key_length, fs_le)
+					+ sizeof(int64_t) - 1)
+						& ~(sizeof(int64_t) - 1)));
+		values = (int64_t *) ((uint8_t *) keylengths
+					+ FS16_TO_CPU(bn->all_key_count, fs_le)
+						* sizeof(uint16_t));
+		first = 0;
+		mid = 0;
+		last = FS16_TO_CPU(bn->all_key_count, fs_le) - 1;
+
+		cmp = compare_keys(bn->name, keylengths, last, key, strlen(key),
+									fs_le);
+		if (cmp == 0) {
+			if ((int64_t) FS64_TO_CPU(bn->overflow_link, fs_le)
+							== BPLUSTREE_NULL)
+				return FS64_TO_CPU(values[last], fs_le);
+			else
+				node_pointer = FS64_TO_CPU(values[last], fs_le);
+		} else if (cmp < 0)
+			node_pointer = FS64_TO_CPU(bn->overflow_link, fs_le);
+		else {
+			while (first <= last) {
+				mid = (first + last) / 2;
+
+				cmp = compare_keys(bn->name, keylengths, mid,
+						key, strlen(key), fs_le);
+				if (cmp == 0) {
+					if ((int64_t) FS64_TO_CPU(bn->overflow_link,
+						fs_le) == BPLUSTREE_NULL)
+						return FS64_TO_CPU(values[mid],
+									fs_le);
+					else
+						break;
+				} else if (cmp < 0)
+					first = mid + 1;
+				else
+					last = mid - 1;
+			}
+			if (cmp < 0)
+				node_pointer = FS64_TO_CPU(values[mid + 1],
+									fs_le);
+			else
+				node_pointer = FS64_TO_CPU(values[mid], fs_le);
+		}
+	} while ((int64_t) FS64_TO_CPU(bn->overflow_link, fs_le)
+						!= BPLUSTREE_NULL);
+	return 0;
+}
+
+static int get_uuid(blkid_probe pr, const struct befs_super_block *bs,
+					uint64_t * const uuid, int fs_le)
+{
+	struct befs_inode *bi;
+	struct small_data *sd;
+
+	bi = (struct befs_inode *) get_block_run(pr, bs, &bs->root_dir, fs_le);
+	if (!bi)
+		return errno ? -errno : BLKID_PROBE_NONE;
+
+	if (FS32_TO_CPU(bi->magic1, fs_le) != INODE_MAGIC1)
+		return BLKID_PROBE_NONE;
+
+	sd = (struct small_data *) bi->small_data;
+
+	do {
+		if (FS32_TO_CPU(sd->type, fs_le) == B_UINT64_TYPE
+			&& FS16_TO_CPU(sd->name_size, fs_le) == strlen(KEY_NAME)
+			&& FS16_TO_CPU(sd->data_size, fs_le) == KEY_SIZE
+			&& strcmp(sd->name, KEY_NAME) == 0) {
+
+			memcpy(uuid,
+			       sd->name + FS16_TO_CPU(sd->name_size, fs_le) + 3,
+			       sizeof(uint64_t));
+
+			break;
+		} else if (FS32_TO_CPU(sd->type, fs_le) == 0
+				&& FS16_TO_CPU(sd->name_size, fs_le) == 0
+				&& FS16_TO_CPU(sd->data_size, fs_le) == 0)
+			break;
+
+		sd = (struct small_data *) ((uint8_t *) sd
+				+ sizeof(struct small_data)
+				+ FS16_TO_CPU(sd->name_size, fs_le) + 3
+				+ FS16_TO_CPU(sd->data_size, fs_le) + 1);
+
+	} while ((intptr_t) sd < (intptr_t) bi
+				+ (int32_t) FS32_TO_CPU(bi->inode_size, fs_le)
+				- (int32_t) sizeof(struct small_data));
+	if (*uuid == 0
+		&& (FS32_TO_CPU(bi->attributes.allocation_group, fs_le) != 0
+			|| FS16_TO_CPU(bi->attributes.start, fs_le) != 0
+			|| FS16_TO_CPU(bi->attributes.len, fs_le) != 0)) {
+		int64_t value;
+
+		bi = (struct befs_inode *) get_block_run(pr, bs,
+							&bi->attributes, fs_le);
+		if (!bi)
+			return errno ? -errno : BLKID_PROBE_NONE;
+
+		if (FS32_TO_CPU(bi->magic1, fs_le) != INODE_MAGIC1)
+			return BLKID_PROBE_NONE;
+
+		value = get_key_value(pr, bs, bi, KEY_NAME, fs_le);
+		if (value < 0)
+			return value == -ENOENT ? BLKID_PROBE_NONE : value;
+
+		else if (value > 0) {
+			bi = (struct befs_inode *) blkid_probe_get_buffer(pr,
+				value << FS32_TO_CPU(bs->block_shift, fs_le),
+				FS32_TO_CPU(bs->block_size, fs_le));
+			if (!bi)
+				return errno ? -errno : BLKID_PROBE_NONE;
+
+			if (FS32_TO_CPU(bi->magic1, fs_le) != INODE_MAGIC1)
+				return 1;
+
+			if (FS32_TO_CPU(bi->type, fs_le) == B_UINT64_TYPE
+				&& FS64_TO_CPU(bi->data.size, fs_le) == KEY_SIZE
+				&& FS16_TO_CPU(bi->data.direct[0].len, fs_le)
+									== 1) {
+				uint64_t *attr_data;
+
+				attr_data = (uint64_t *) get_block_run(pr, bs,
+						&bi->data.direct[0], fs_le);
+				if (!attr_data)
+					return errno ? -errno : BLKID_PROBE_NONE;
+
+				*uuid = *attr_data;
+			}
+		}
+	}
+	return 0;
+}
+
+static int probe_befs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct befs_super_block *bs;
+	const char *version = NULL;
+	uint64_t volume_id = 0;
+	int fs_le, ret;
+
+	bs = (struct befs_super_block *) blkid_probe_get_buffer(pr,
+					mag->sboff - B_OS_NAME_LENGTH,
+					sizeof(struct befs_super_block));
+	if (!bs)
+		return errno ? -errno : BLKID_PROBE_NONE;
+
+	if (le32_to_cpu(bs->magic1) == SUPER_BLOCK_MAGIC1
+		&& le32_to_cpu(bs->magic2) == SUPER_BLOCK_MAGIC2
+		&& le32_to_cpu(bs->magic3) == SUPER_BLOCK_MAGIC3
+		&& le32_to_cpu(bs->fs_byte_order) == SUPER_BLOCK_FS_ENDIAN) {
+		fs_le = 1;
+		version = "little-endian";
+	} else if (be32_to_cpu(bs->magic1) == SUPER_BLOCK_MAGIC1
+		&& be32_to_cpu(bs->magic2) == SUPER_BLOCK_MAGIC2
+		&& be32_to_cpu(bs->magic3) == SUPER_BLOCK_MAGIC3
+		&& be32_to_cpu(bs->fs_byte_order) == SUPER_BLOCK_FS_ENDIAN) {
+		fs_le = 0;
+		version = "big-endian";
+	} else
+		return BLKID_PROBE_NONE;
+
+	ret = get_uuid(pr, bs, &volume_id, fs_le);
+
+	if (ret != 0)
+		return ret;
+
+	/*
+	 * all checks pass, set LABEL, VERSION and UUID
+	 */
+	if (strlen(bs->name))
+		blkid_probe_set_label(pr, (unsigned char *) bs->name,
+							sizeof(bs->name));
+	if (version)
+		blkid_probe_set_version(pr, version);
+
+	if (volume_id)
+		blkid_probe_sprintf_uuid(pr, (unsigned char *) &volume_id,
+					sizeof(volume_id), "%016" PRIx64,
+					FS64_TO_CPU(volume_id, fs_le));
+	return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo befs_idinfo =
+{
+	.name		= "befs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_befs,
+	.minsz		= 1024 * 1440,
+	.magics		= {
+		{ .magic = "BFS1", .len = 4, .sboff = B_OS_NAME_LENGTH },
+		{ .magic = "1SFB", .len = 4, .sboff = B_OS_NAME_LENGTH },
+		{ .magic = "BFS1", .len = 4, .sboff = 0x200 +
+							B_OS_NAME_LENGTH },
+		{ .magic = "1SFB", .len = 4, .sboff = 0x200 +
+							B_OS_NAME_LENGTH },
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/bfs.c b/libblkid/src/superblocks/bfs.c
new file mode 100644
index 0000000..8a34c58
--- /dev/null
+++ b/libblkid/src/superblocks/bfs.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include "superblocks.h"
+
+/*
+ * BFS actually has two different labels in the superblock, each
+ * of them only 6 bytes long.  Until we find out what their use
+ * we just ignore them.
+ */
+const struct blkid_idinfo bfs_idinfo =
+{
+	.name		= "bfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.magics		= {
+		{ .magic = "\xce\xfa\xad\x1b", .len = 4 },
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/btrfs.c b/libblkid/src/superblocks/btrfs.c
new file mode 100644
index 0000000..7ce3dff
--- /dev/null
+++ b/libblkid/src/superblocks/btrfs.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct btrfs_super_block {
+	uint8_t csum[32];
+	uint8_t fsid[16];
+	uint64_t bytenr;
+	uint64_t flags;
+	uint8_t magic[8];
+	uint64_t generation;
+	uint64_t root;
+	uint64_t chunk_root;
+	uint64_t log_root;
+	uint64_t log_root_transid;
+	uint64_t total_bytes;
+	uint64_t bytes_used;
+	uint64_t root_dir_objectid;
+	uint64_t num_devices;
+	uint32_t sectorsize;
+	uint32_t nodesize;
+	uint32_t leafsize;
+	uint32_t stripesize;
+	uint32_t sys_chunk_array_size;
+	uint64_t chunk_root_generation;
+	uint64_t compat_flags;
+	uint64_t compat_ro_flags;
+	uint64_t incompat_flags;
+	uint16_t csum_type;
+	uint8_t root_level;
+	uint8_t chunk_root_level;
+	uint8_t log_root_level;
+	struct btrfs_dev_item {
+		uint64_t devid;
+		uint64_t total_bytes;
+		uint64_t bytes_used;
+		uint32_t io_align;
+		uint32_t io_width;
+		uint32_t sector_size;
+		uint64_t type;
+		uint64_t generation;
+		uint64_t start_offset;
+		uint32_t dev_group;
+		uint8_t seek_speed;
+		uint8_t bandwidth;
+		uint8_t uuid[16];
+		uint8_t fsid[16];
+	} __attribute__ ((__packed__)) dev_item;
+	uint8_t label[256];
+} __attribute__ ((__packed__));
+
+static int probe_btrfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct btrfs_super_block *bfs;
+
+	bfs = blkid_probe_get_sb(pr, mag, struct btrfs_super_block);
+	if (!bfs)
+		return errno ? -errno : 1;
+
+	if (*bfs->label)
+		blkid_probe_set_label(pr,
+				(unsigned char *) bfs->label,
+				sizeof(bfs->label));
+
+	blkid_probe_set_uuid(pr, bfs->fsid);
+	blkid_probe_set_uuid_as(pr, bfs->dev_item.uuid, "UUID_SUB");
+
+	return 0;
+}
+
+const struct blkid_idinfo btrfs_idinfo =
+{
+	.name		= "btrfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_btrfs,
+	.minsz		= 1024 * 1024,
+	.magics		=
+	{
+	  { .magic = "_BHRfS_M", .len = 8, .sboff = 0x40, .kboff = 64 },
+	  { NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/cramfs.c b/libblkid/src/superblocks/cramfs.c
new file mode 100644
index 0000000..6d01b0b
--- /dev/null
+++ b/libblkid/src/superblocks/cramfs.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct cramfs_super
+{
+	uint8_t		magic[4];
+	uint32_t	size;
+	uint32_t	flags;
+	uint32_t	future;
+	uint8_t		signature[16];
+	struct cramfs_info
+	{
+		uint32_t	crc;
+		uint32_t	edition;
+		uint32_t	blocks;
+		uint32_t	files;
+	} __attribute__((packed)) info;
+	uint8_t		name[16];
+} __attribute__((packed));
+
+static int probe_cramfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct cramfs_super *cs;
+
+	cs = blkid_probe_get_sb(pr, mag, struct cramfs_super);
+	if (!cs)
+		return errno ? -errno : 1;
+
+	blkid_probe_set_label(pr, cs->name, sizeof(cs->name));
+	return 0;
+}
+
+const struct blkid_idinfo cramfs_idinfo =
+{
+	.name		= "cramfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_cramfs,
+	.magics		=
+	{
+		{ "\x45\x3d\xcd\x28", 4, 0, 0 },
+		{ "\x28\xcd\x3d\x45", 4, 0, 0 },
+		{ NULL }
+	}
+};
+
+
diff --git a/libblkid/src/superblocks/ddf_raid.c b/libblkid/src/superblocks/ddf_raid.c
new file mode 100644
index 0000000..fc2c39d
--- /dev/null
+++ b/libblkid/src/superblocks/ddf_raid.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+/* http://www.snia.org/standards/home */
+#define DDF_GUID_LENGTH			24
+#define DDF_REV_LENGTH			8
+#define DDF_MAGIC			0xDE11DE11
+
+
+struct ddf_header {
+	uint32_t	signature;
+	uint32_t	crc;
+	uint8_t		guid[DDF_GUID_LENGTH];
+	char		ddf_rev[8];	/* 01.02.00 */
+	uint32_t	seq;		/* starts at '1' */
+	uint32_t	timestamp;
+	uint8_t		openflag;
+	uint8_t		foreignflag;
+	uint8_t		enforcegroups;
+	uint8_t		pad0;		/* 0xff */
+	uint8_t		pad1[12];	/* 12 * 0xff */
+	/* 64 bytes so far */
+	uint8_t		header_ext[32];	/* reserved: fill with 0xff */
+	uint64_t	primary_lba;
+	uint64_t	secondary_lba;
+	uint8_t		type;
+	uint8_t		pad2[3];	/* 0xff */
+	uint32_t	workspace_len;	/* sectors for vendor space -
+					 * at least 32768(sectors) */
+	uint64_t	workspace_lba;
+	uint16_t	max_pd_entries;	/* one of 15, 63, 255, 1023, 4095 */
+	uint16_t	max_vd_entries; /* 2^(4,6,8,10,12)-1 : i.e. as above */
+	uint16_t	max_partitions; /* i.e. max num of configuration
+					   record entries per disk */
+	uint16_t	config_record_len; /* 1 +ROUNDUP(max_primary_element_entries
+				           *12/512) */
+	uint16_t	max_primary_element_entries; /* 16, 64, 256, 1024, or 4096 */
+	uint8_t		pad3[54];	/* 0xff */
+	/* 192 bytes so far */
+	uint32_t	controller_section_offset;
+	uint32_t	controller_section_length;
+	uint32_t	phys_section_offset;
+	uint32_t	phys_section_length;
+	uint32_t	virt_section_offset;
+	uint32_t	virt_section_length;
+	uint32_t	config_section_offset;
+	uint32_t	config_section_length;
+	uint32_t	data_section_offset;
+	uint32_t	data_section_length;
+	uint32_t	bbm_section_offset;
+	uint32_t	bbm_section_length;
+	uint32_t	diag_space_offset;
+	uint32_t	diag_space_length;
+	uint32_t	vendor_offset;
+	uint32_t	vendor_length;
+	/* 256 bytes so far */
+	uint8_t		pad4[256];	/* 0xff */
+} __attribute__((packed));
+
+static int probe_ddf(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	int hdrs[] = { 1, 257 };
+	size_t i;
+	struct ddf_header *ddf = NULL;
+	char version[DDF_REV_LENGTH + 1];
+	uint64_t off, lba;
+
+	if (pr->size < 0x30000)
+		return 1;
+
+	for (i = 0; i < ARRAY_SIZE(hdrs); i++) {
+		off = ((pr->size / 0x200) - hdrs[i]) * 0x200;
+
+		ddf = (struct ddf_header *) blkid_probe_get_buffer(pr,
+					off,
+					sizeof(struct ddf_header));
+		if (!ddf)
+			return errno ? -errno : 1;
+		if (ddf->signature == cpu_to_be32(DDF_MAGIC) ||
+		    ddf->signature == cpu_to_le32(DDF_MAGIC))
+			break;
+		ddf = NULL;
+	}
+
+	if (!ddf)
+		return 1;
+
+	lba = ddf->signature == cpu_to_be32(DDF_MAGIC) ?
+			be64_to_cpu(ddf->primary_lba) :
+			le64_to_cpu(ddf->primary_lba);
+
+	if (lba > 0) {
+		/* check primary header */
+		unsigned char *buf;
+
+		buf = blkid_probe_get_buffer(pr,
+					lba << 9, sizeof(ddf->signature));
+		if (!buf)
+			return errno ? -errno : 1;
+
+		if (memcmp(buf, &ddf->signature, 4) != 0)
+			return 1;
+	}
+
+	blkid_probe_strncpy_uuid(pr, ddf->guid, sizeof(ddf->guid));
+
+	memcpy(version, ddf->ddf_rev, sizeof(ddf->ddf_rev));
+	*(version + sizeof(ddf->ddf_rev)) = '\0';
+
+	if (blkid_probe_set_version(pr, version) != 0)
+		return 1;
+	if (blkid_probe_set_magic(pr, off,
+			sizeof(ddf->signature),
+			(unsigned char *) &ddf->signature))
+		return 1;
+	return 0;
+}
+
+const struct blkid_idinfo ddfraid_idinfo = {
+	.name		= "ddf_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_ddf,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/drbd.c b/libblkid/src/superblocks/drbd.c
new file mode 100644
index 0000000..e88e9f3
--- /dev/null
+++ b/libblkid/src/superblocks/drbd.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2009 by Bastian Friedrich <bastian.friedrich@collax.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * defines, structs taken from drbd source; file names represent drbd source
+ * files.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stddef.h>
+
+#include "superblocks.h"
+
+/*
+ * drbd/linux/drbd.h
+ */
+#define DRBD_MAGIC 0x83740267
+
+/*
+ * user/drbdmeta.c
+ * We only support v08 for now
+ */
+#define DRBD_MD_MAGIC_08         (DRBD_MAGIC+4)
+#define DRBD_MD_MAGIC_84_UNCLEAN (DRBD_MAGIC+5)
+
+/*
+ * drbd/linux/drbd.h
+ */
+enum drbd_uuid_index {
+	UI_CURRENT,
+	UI_BITMAP,
+	UI_HISTORY_START,
+	UI_HISTORY_END,
+	UI_SIZE,		/* nl-packet: number of dirty bits */
+	UI_FLAGS,		/* nl-packet: flags */
+	UI_EXTENDED_SIZE	/* Everything. */
+};
+
+/*
+ * user/drbdmeta.c
+ * Minor modifications wrt. types
+ */
+struct md_on_disk_08 {
+	uint64_t la_sect;         /* last agreed size. */
+	uint64_t uuid[UI_SIZE];   /* UUIDs */
+	uint64_t device_uuid;
+	uint64_t reserved_u64_1;
+	uint32_t flags;
+	uint32_t magic;
+	uint32_t md_size_sect;
+	int32_t  al_offset;       /* signed sector offset to this block */
+	uint32_t al_nr_extents;   /* important for restoring the AL */
+	int32_t  bm_offset;       /* signed sector offset to the bitmap, from here */
+	uint32_t bm_bytes_per_bit;
+	uint32_t reserved_u32[4];
+
+	char reserved[8 * 512 - (8*(UI_SIZE+3)+4*11)];
+};
+
+
+static int probe_drbd(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct md_on_disk_08 *md;
+	off_t off;
+
+	off = pr->size - sizeof(*md);
+
+	/* Small devices cannot be drbd (?) */
+	if (pr->size < 0x10000)
+		return 1;
+
+	md = (struct md_on_disk_08 *)
+			blkid_probe_get_buffer(pr,
+					off,
+					sizeof(struct md_on_disk_08));
+	if (!md)
+		return errno ? -errno : 1;
+
+	if (be32_to_cpu(md->magic) != DRBD_MD_MAGIC_08 &&
+			be32_to_cpu(md->magic) != DRBD_MD_MAGIC_84_UNCLEAN)
+		return 1;
+
+	/*
+	 * DRBD does not have "real" uuids; the following resembles DRBD's
+	 * notion of uuids (64 bit, see struct above)
+	 */
+	blkid_probe_sprintf_uuid(pr,
+		(unsigned char *) &md->device_uuid, sizeof(md->device_uuid),
+		"%" PRIx64, be64_to_cpu(md->device_uuid));
+
+	blkid_probe_set_version(pr, "v08");
+
+	if (blkid_probe_set_magic(pr,
+				off + offsetof(struct md_on_disk_08, magic),
+				sizeof(md->magic),
+				(unsigned char *) &md->magic))
+		return 1;
+
+	return 0;
+}
+
+const struct blkid_idinfo drbd_idinfo =
+{
+	.name		= "drbd",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_drbd,
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/superblocks/drbdproxy_datalog.c b/libblkid/src/superblocks/drbdproxy_datalog.c
new file mode 100644
index 0000000..af59722
--- /dev/null
+++ b/libblkid/src/superblocks/drbdproxy_datalog.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 by Philipp Marek <philipp.marek@linbit.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stddef.h>
+
+#include "superblocks.h"
+
+
+struct log_header_t {
+	uint64_t magic;
+	uint64_t version;
+
+	unsigned char uuid[16];
+
+	uint64_t flags;
+} __attribute__((packed));
+
+
+static int probe_drbdproxy_datalog(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct log_header_t *lh;
+
+	lh = (struct log_header_t *) blkid_probe_get_buffer(pr, 0, sizeof(*lh));
+	if (!lh)
+		return errno ? -errno : 1;
+
+	blkid_probe_set_uuid(pr, lh->uuid);
+	blkid_probe_sprintf_version(pr, "v%jd", le64_to_cpu(lh->version));
+
+	return 0;
+}
+
+const struct blkid_idinfo drbdproxy_datalog_idinfo =
+{
+	.name		= "drbdproxy_datalog",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_drbdproxy_datalog,
+	.minsz		= 16*1024,
+	.magics		=
+	{
+		{ .magic = "DRBDdlh*", .len = 8, .sboff = 0, .kboff = 0 },
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/erofs.c b/libblkid/src/superblocks/erofs.c
new file mode 100644
index 0000000..0bf1591
--- /dev/null
+++ b/libblkid/src/superblocks/erofs.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 Gao Xiang
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License
+ */
+#include <stddef.h>
+#include <string.h>
+
+#include "superblocks.h"
+
+#define EROFS_SUPER_OFFSET      1024
+#define EROFS_SB_KBOFF		(EROFS_SUPER_OFFSET >> 10)
+
+#define EROFS_SUPER_MAGIC_V1	"\xe2\xe1\xf5\xe0"
+#define EROFS_MAGIC_OFF		0
+
+/* All in little-endian */
+struct erofs_super_block {
+	uint32_t	magic;
+	uint32_t	checksum;
+	uint32_t	feature_compat;
+	uint8_t		blkszbits;
+	uint8_t		reserved;
+
+	uint16_t	root_nid;
+	uint64_t	inos;
+
+	uint64_t	build_time;
+	uint32_t	build_time_nsec;
+	uint32_t	blocks;
+	uint32_t	meta_blkaddr;
+	uint32_t	xattr_blkaddr;
+	uint8_t		uuid[16];
+	uint8_t		volume_name[16];
+	uint32_t	feature_incompat;
+	uint8_t		reserved2[44];
+};
+
+static int probe_erofs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct erofs_super_block *sb;
+
+	sb = blkid_probe_get_sb(pr, mag, struct erofs_super_block);
+	if (!sb)
+		return errno ? -errno : BLKID_PROBE_NONE;
+
+	if (sb->volume_name[0])
+		blkid_probe_set_label(pr, (unsigned char *)sb->volume_name,
+				      sizeof(sb->volume_name));
+
+	blkid_probe_set_uuid(pr, sb->uuid);
+
+	return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo erofs_idinfo =
+{
+	.name           = "erofs",
+	.usage          = BLKID_USAGE_FILESYSTEM,
+	.probefunc      = probe_erofs,
+	.magics         =
+        {
+		{
+			.magic = EROFS_SUPER_MAGIC_V1,
+			.len = 4,
+			.kboff = EROFS_SB_KBOFF,
+			.sboff = EROFS_MAGIC_OFF,
+		}, { NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/exfat.c b/libblkid/src/superblocks/exfat.c
new file mode 100644
index 0000000..b526560
--- /dev/null
+++ b/libblkid/src/superblocks/exfat.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2010 Andrew Nayenko <resver@gmail.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include "superblocks.h"
+
+struct exfat_super_block {
+	uint8_t jump[3];
+	uint8_t oem_name[8];
+	uint8_t	__unused1[53];
+	uint64_t block_start;
+	uint64_t block_count;
+	uint32_t fat_block_start;
+	uint32_t fat_block_count;
+	uint32_t cluster_block_start;
+	uint32_t cluster_count;
+	uint32_t rootdir_cluster;
+	uint8_t volume_serial[4];
+	struct {
+		uint8_t minor;
+		uint8_t major;
+	} version;
+	uint16_t volume_state;
+	uint8_t block_bits;
+	uint8_t bpc_bits;
+	uint8_t fat_count;
+	uint8_t drive_no;
+	uint8_t allocated_percent;
+} __attribute__((__packed__));
+
+struct exfat_entry_label {
+	uint8_t type;
+	uint8_t length;
+	uint8_t name[30];
+} __attribute__((__packed__));
+
+#define BLOCK_SIZE(sb) (1 << (sb)->block_bits)
+#define CLUSTER_SIZE(sb) (BLOCK_SIZE(sb) << (sb)->bpc_bits)
+#define EXFAT_FIRST_DATA_CLUSTER 2
+#define EXFAT_LAST_DATA_CLUSTER 0xffffff6
+#define EXFAT_ENTRY_SIZE 32
+
+#define EXFAT_ENTRY_EOD		0x00
+#define EXFAT_ENTRY_LABEL	0x83
+
+static blkid_loff_t block_to_offset(const struct exfat_super_block *sb,
+		blkid_loff_t block)
+{
+	return (blkid_loff_t) block << sb->block_bits;
+}
+
+static blkid_loff_t cluster_to_block(const struct exfat_super_block *sb,
+		uint32_t cluster)
+{
+	return le32_to_cpu(sb->cluster_block_start) +
+			((blkid_loff_t) (cluster - EXFAT_FIRST_DATA_CLUSTER)
+					<< sb->bpc_bits);
+}
+
+static blkid_loff_t cluster_to_offset(const struct exfat_super_block *sb,
+		uint32_t cluster)
+{
+	return block_to_offset(sb, cluster_to_block(sb, cluster));
+}
+
+static uint32_t next_cluster(blkid_probe pr,
+		const struct exfat_super_block *sb, uint32_t cluster)
+{
+	uint32_t *next;
+	blkid_loff_t fat_offset;
+
+	fat_offset = block_to_offset(sb, le32_to_cpu(sb->fat_block_start))
+		+ (blkid_loff_t) cluster * sizeof(cluster);
+	next = (uint32_t *) blkid_probe_get_buffer(pr, fat_offset,
+			sizeof(uint32_t));
+	if (!next)
+		return 0;
+	return le32_to_cpu(*next);
+}
+
+static struct exfat_entry_label *find_label(blkid_probe pr,
+		const struct exfat_super_block *sb)
+{
+	uint32_t cluster = le32_to_cpu(sb->rootdir_cluster);
+	blkid_loff_t offset = cluster_to_offset(sb, cluster);
+	uint8_t *entry;
+
+	for (;;) {
+		entry = (uint8_t *) blkid_probe_get_buffer(pr, offset,
+				EXFAT_ENTRY_SIZE);
+		if (!entry)
+			return NULL;
+		if (entry[0] == EXFAT_ENTRY_EOD)
+			return NULL;
+		if (entry[0] == EXFAT_ENTRY_LABEL)
+			return (struct exfat_entry_label *) entry;
+		offset += EXFAT_ENTRY_SIZE;
+		if (offset % CLUSTER_SIZE(sb) == 0) {
+			cluster = next_cluster(pr, sb, cluster);
+			if (cluster < EXFAT_FIRST_DATA_CLUSTER)
+				return NULL;
+			if (cluster > EXFAT_LAST_DATA_CLUSTER)
+				return NULL;
+			offset = cluster_to_offset(sb, cluster);
+		}
+	}
+}
+
+static int probe_exfat(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct exfat_super_block *sb;
+	struct exfat_entry_label *label;
+
+	sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block);
+	if (!sb)
+		return errno ? -errno : BLKID_PROBE_NONE;
+
+	label = find_label(pr, sb);
+	if (label)
+		blkid_probe_set_utf8label(pr, label->name,
+				min(label->length * 2, 30), BLKID_ENC_UTF16LE);
+	else if (errno)
+		return -errno;
+
+	blkid_probe_sprintf_uuid(pr, sb->volume_serial, 4,
+			"%02hhX%02hhX-%02hhX%02hhX",
+			sb->volume_serial[3], sb->volume_serial[2],
+			sb->volume_serial[1], sb->volume_serial[0]);
+
+	blkid_probe_sprintf_version(pr, "%u.%u",
+			sb->version.major, sb->version.minor);
+
+	return BLKID_PROBE_OK;
+}
+
+const struct blkid_idinfo exfat_idinfo =
+{
+	.name		= "exfat",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_exfat,
+	.magics		=
+	{
+		{ .magic = "EXFAT   ", .len = 8, .sboff = 3 },
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/ext.c b/libblkid/src/superblocks/ext.c
new file mode 100644
index 0000000..5b1d179
--- /dev/null
+++ b/libblkid/src/superblocks/ext.c
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 1999, 2001 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+#ifdef __linux__
+#include <sys/utsname.h>
+#endif
+#include <time.h>
+
+#include "superblocks.h"
+
+struct ext2_super_block {
+	uint32_t		s_inodes_count;
+	uint32_t		s_blocks_count;
+	uint32_t		s_r_blocks_count;
+	uint32_t		s_free_blocks_count;
+	uint32_t		s_free_inodes_count;
+	uint32_t		s_first_data_block;
+	uint32_t		s_log_block_size;
+	uint32_t		s_dummy3[7];
+	unsigned char		s_magic[2];
+	uint16_t		s_state;
+	uint16_t		s_errors;
+	uint16_t		s_minor_rev_level;
+	uint32_t		s_lastcheck;
+	uint32_t		s_checkinterval;
+	uint32_t		s_creator_os;
+	uint32_t		s_rev_level;
+	uint16_t		s_def_resuid;
+	uint16_t		s_def_resgid;
+	uint32_t		s_first_ino;
+	uint16_t		s_inode_size;
+	uint16_t		s_block_group_nr;
+	uint32_t		s_feature_compat;
+	uint32_t		s_feature_incompat;
+	uint32_t		s_feature_ro_compat;
+	unsigned char		s_uuid[16];
+	char			s_volume_name[16];
+	char			s_last_mounted[64];
+	uint32_t		s_algorithm_usage_bitmap;
+	uint8_t			s_prealloc_blocks;
+	uint8_t			s_prealloc_dir_blocks;
+	uint16_t		s_reserved_gdt_blocks;
+	uint8_t			s_journal_uuid[16];
+	uint32_t		s_journal_inum;
+	uint32_t		s_journal_dev;
+	uint32_t		s_last_orphan;
+	uint32_t		s_hash_seed[4];
+	uint8_t			s_def_hash_version;
+	uint8_t			s_jnl_backup_type;
+	uint16_t		s_reserved_word_pad;
+	uint32_t		s_default_mount_opts;
+	uint32_t		s_first_meta_bg;
+	uint32_t		s_mkfs_time;
+	uint32_t		s_jnl_blocks[17];
+	uint32_t		s_blocks_count_hi;
+	uint32_t		s_r_blocks_count_hi;
+	uint32_t		s_free_blocks_hi;
+	uint16_t		s_min_extra_isize;
+	uint16_t		s_want_extra_isize;
+	uint32_t		s_flags;
+	uint16_t		s_raid_stride;
+	uint16_t		s_mmp_interval;
+	uint64_t		s_mmp_block;
+	uint32_t		s_raid_stripe_width;
+	uint32_t		s_reserved[163];
+} __attribute__((packed));
+
+/* magic string */
+#define EXT_SB_MAGIC				"\123\357"
+/* supper block offset */
+#define EXT_SB_OFF				0x400
+/* supper block offset in kB */
+#define EXT_SB_KBOFF				(EXT_SB_OFF >> 10)
+/* magic string offset within super block */
+#define EXT_MAG_OFF				0x38
+
+
+
+/* for s_flags */
+#define EXT2_FLAGS_TEST_FILESYS		0x0004
+
+/* for s_feature_compat */
+#define EXT3_FEATURE_COMPAT_HAS_JOURNAL		0x0004
+
+/* for s_feature_ro_compat */
+#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER	0x0001
+#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE	0x0002
+#define EXT2_FEATURE_RO_COMPAT_BTREE_DIR	0x0004
+#define EXT4_FEATURE_RO_COMPAT_HUGE_FILE	0x0008
+#define EXT4_FEATURE_RO_COMPAT_GDT_CSUM		0x0010
+#define EXT4_FEATURE_RO_COMPAT_DIR_NLINK	0x0020
+#define EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE	0x0040
+
+/* for s_feature_incompat */
+#define EXT2_FEATURE_INCOMPAT_FILETYPE		0x0002
+#define EXT3_FEATURE_INCOMPAT_RECOVER		0x0004
+#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV	0x0008
+#define EXT2_FEATURE_INCOMPAT_META_BG		0x0010
+#define EXT4_FEATURE_INCOMPAT_EXTENTS		0x0040 /* extents support */
+#define EXT4_FEATURE_INCOMPAT_64BIT		0x0080
+#define EXT4_FEATURE_INCOMPAT_MMP		0x0100
+#define EXT4_FEATURE_INCOMPAT_FLEX_BG		0x0200
+
+#define EXT2_FEATURE_RO_COMPAT_SUPP	(EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \
+					 EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \
+					 EXT2_FEATURE_RO_COMPAT_BTREE_DIR)
+#define EXT2_FEATURE_INCOMPAT_SUPP	(EXT2_FEATURE_INCOMPAT_FILETYPE| \
+					 EXT2_FEATURE_INCOMPAT_META_BG)
+#define EXT2_FEATURE_INCOMPAT_UNSUPPORTED	~EXT2_FEATURE_INCOMPAT_SUPP
+#define EXT2_FEATURE_RO_COMPAT_UNSUPPORTED	~EXT2_FEATURE_RO_COMPAT_SUPP
+
+#define EXT3_FEATURE_RO_COMPAT_SUPP	(EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \
+					 EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \
+					 EXT2_FEATURE_RO_COMPAT_BTREE_DIR)
+#define EXT3_FEATURE_INCOMPAT_SUPP	(EXT2_FEATURE_INCOMPAT_FILETYPE| \
+					 EXT3_FEATURE_INCOMPAT_RECOVER| \
+					 EXT2_FEATURE_INCOMPAT_META_BG)
+#define EXT3_FEATURE_INCOMPAT_UNSUPPORTED	~EXT3_FEATURE_INCOMPAT_SUPP
+#define EXT3_FEATURE_RO_COMPAT_UNSUPPORTED	~EXT3_FEATURE_RO_COMPAT_SUPP
+
+/*
+ * Starting in 2.6.29, ext4 can be used to support filesystems
+ * without a journal.
+ */
+#define EXT4_SUPPORTS_EXT2 KERNEL_VERSION(2, 6, 29)
+
+/*
+ * reads superblock and returns:
+ *	fc = feature_compat
+ *	fi = feature_incompat
+ *	frc = feature_ro_compat
+ */
+static struct ext2_super_block *ext_get_super(
+		blkid_probe pr, uint32_t *fc, uint32_t *fi, uint32_t *frc)
+{
+	struct ext2_super_block *es;
+
+	es = (struct ext2_super_block *)
+			blkid_probe_get_buffer(pr, EXT_SB_OFF, 0x200);
+	if (!es)
+		return NULL;
+	if (fc)
+		*fc = le32_to_cpu(es->s_feature_compat);
+	if (fi)
+		*fi = le32_to_cpu(es->s_feature_incompat);
+	if (frc)
+		*frc = le32_to_cpu(es->s_feature_ro_compat);
+
+	return es;
+}
+
+static void ext_get_info(blkid_probe pr, int ver, struct ext2_super_block *es)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+	DBG(PROBE, ul_debug("ext2_sb.compat = %08X:%08X:%08X",
+		   le32_to_cpu(es->s_feature_compat),
+		   le32_to_cpu(es->s_feature_incompat),
+		   le32_to_cpu(es->s_feature_ro_compat)));
+
+	if (strlen(es->s_volume_name))
+		blkid_probe_set_label(pr, (unsigned char *) es->s_volume_name,
+					sizeof(es->s_volume_name));
+	blkid_probe_set_uuid(pr, es->s_uuid);
+
+	if (le32_to_cpu(es->s_feature_compat) & EXT3_FEATURE_COMPAT_HAS_JOURNAL)
+		blkid_probe_set_uuid_as(pr, es->s_journal_uuid, "EXT_JOURNAL");
+
+	if (ver != 2 && (chn->flags & BLKID_SUBLKS_SECTYPE) &&
+	    ((le32_to_cpu(es->s_feature_incompat) & EXT2_FEATURE_INCOMPAT_UNSUPPORTED) == 0))
+		blkid_probe_set_value(pr, "SEC_TYPE",
+				(unsigned char *) "ext2",
+				sizeof("ext2"));
+
+	blkid_probe_sprintf_version(pr, "%u.%u",
+		le32_to_cpu(es->s_rev_level),
+		le16_to_cpu(es->s_minor_rev_level));
+}
+
+
+static int probe_jbd(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct ext2_super_block *es;
+	uint32_t fi;
+
+	es = ext_get_super(pr, NULL, &fi, NULL);
+	if (!es)
+		return errno ? -errno : 1;
+	if (!(fi & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV))
+		return 1;
+
+	ext_get_info(pr, 2, es);
+	blkid_probe_set_uuid_as(pr, es->s_uuid, "LOGUUID");
+
+	return 0;
+}
+
+static int probe_ext2(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct ext2_super_block *es;
+	uint32_t fc, frc, fi;
+
+	es = ext_get_super(pr, &fc, &fi, &frc);
+	if (!es)
+		return errno ? -errno : 1;
+
+	/* Distinguish between ext3 and ext2 */
+	if (fc & EXT3_FEATURE_COMPAT_HAS_JOURNAL)
+		return 1;
+
+	/* Any features which ext2 doesn't understand */
+	if ((frc & EXT2_FEATURE_RO_COMPAT_UNSUPPORTED) ||
+	    (fi  & EXT2_FEATURE_INCOMPAT_UNSUPPORTED))
+		return 1;
+
+	ext_get_info(pr, 2, es);
+	return 0;
+}
+
+static int probe_ext3(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct ext2_super_block *es;
+	uint32_t fc, frc, fi;
+
+	es = ext_get_super(pr, &fc, &fi, &frc);
+	if (!es)
+		return errno ? -errno : 1;
+
+	/* ext3 requires journal */
+	if (!(fc & EXT3_FEATURE_COMPAT_HAS_JOURNAL))
+		return 1;
+
+	/* Any features which ext3 doesn't understand */
+	if ((frc & EXT3_FEATURE_RO_COMPAT_UNSUPPORTED) ||
+	    (fi  & EXT3_FEATURE_INCOMPAT_UNSUPPORTED))
+		return 1;
+
+	ext_get_info(pr, 3, es);
+	return 0;
+}
+
+
+static int probe_ext4dev(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct ext2_super_block *es;
+	uint32_t fc, frc, fi;
+
+	es = ext_get_super(pr, &fc, &fi, &frc);
+	if (!es)
+		return errno ? -errno : 1;
+
+	/* Distinguish from jbd */
+	if (fi & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)
+		return 1;
+
+	if (!(le32_to_cpu(es->s_flags) & EXT2_FLAGS_TEST_FILESYS))
+		return 1;
+
+	ext_get_info(pr, 4, es);
+	return 0;
+}
+
+static int probe_ext4(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct ext2_super_block *es;
+	uint32_t fc, frc, fi;
+
+	es = ext_get_super(pr, &fc, &fi, &frc);
+	if (!es)
+		return errno ? -errno : 1;
+
+	/* Distinguish from jbd */
+	if (fi & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)
+		return 1;
+
+	/* Ext4 has at least one feature which ext3 doesn't understand */
+	if (!(frc & EXT3_FEATURE_RO_COMPAT_UNSUPPORTED) &&
+	    !(fi  & EXT3_FEATURE_INCOMPAT_UNSUPPORTED))
+		return 1;
+
+	/*
+	 * If the filesystem is a OK for use by in-development
+	 * filesystem code, and ext4dev is supported or ext4 is not
+	 * supported, then don't call ourselves ext4, so we can redo
+	 * the detection and mark the filesystem as ext4dev.
+	 *
+	 * If the filesystem is marked as in use by production
+	 * filesystem, then it can only be used by ext4 and NOT by
+	 * ext4dev.
+	 */
+	if (le32_to_cpu(es->s_flags) & EXT2_FLAGS_TEST_FILESYS)
+		return 1;
+
+	ext_get_info(pr, 4, es);
+	return 0;
+}
+
+#define BLKID_EXT_MAGICS \
+	{ \
+		{	 \
+			.magic = EXT_SB_MAGIC, \
+			.len = sizeof(EXT_SB_MAGIC) - 1, \
+			.kboff = EXT_SB_KBOFF, \
+			.sboff = EXT_MAG_OFF \
+		}, \
+		{ NULL } \
+	}
+
+const struct blkid_idinfo jbd_idinfo =
+{
+	.name		= "jbd",
+	.usage		= BLKID_USAGE_OTHER,
+	.probefunc	= probe_jbd,
+	.magics		= BLKID_EXT_MAGICS
+};
+
+const struct blkid_idinfo ext2_idinfo =
+{
+	.name		= "ext2",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_ext2,
+	.magics		= BLKID_EXT_MAGICS
+};
+
+const struct blkid_idinfo ext3_idinfo =
+{
+	.name		= "ext3",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_ext3,
+	.magics		= BLKID_EXT_MAGICS
+};
+
+const struct blkid_idinfo ext4_idinfo =
+{
+	.name		= "ext4",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_ext4,
+	.magics		= BLKID_EXT_MAGICS
+};
+
+const struct blkid_idinfo ext4dev_idinfo =
+{
+	.name		= "ext4dev",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_ext4dev,
+	.magics		= BLKID_EXT_MAGICS
+};
+
diff --git a/libblkid/src/superblocks/f2fs.c b/libblkid/src/superblocks/f2fs.c
new file mode 100644
index 0000000..2bf0f5e
--- /dev/null
+++ b/libblkid/src/superblocks/f2fs.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2013 Alejandro Martinez Ruiz <alex@nowcomputing.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License
+ */
+
+#include <stddef.h>
+#include <string.h>
+
+#include "superblocks.h"
+
+#define F2FS_MAGIC		"\x10\x20\xF5\xF2"
+#define F2FS_MAGIC_OFF		0
+#define F2FS_UUID_SIZE		16
+#define F2FS_LABEL_SIZE		512
+#define F2FS_SB1_OFF		0x400
+#define F2FS_SB1_KBOFF		(F2FS_SB1_OFF >> 10)
+#define F2FS_SB2_OFF		0x1400
+#define F2FS_SB2_KBOFF		(F2FS_SB2_OFF >> 10)
+
+struct f2fs_super_block {					/* According to version 1.1 */
+/* 0x00 */	uint32_t	magic;				/* Magic Number */
+/* 0x04 */	uint16_t	major_ver;			/* Major Version */
+/* 0x06 */	uint16_t	minor_ver;			/* Minor Version */
+/* 0x08 */	uint32_t	log_sectorsize;			/* log2 sector size in bytes */
+/* 0x0C */	uint32_t	log_sectors_per_block;		/* log2 # of sectors per block */
+/* 0x10 */	uint32_t	log_blocksize;			/* log2 block size in bytes */
+/* 0x14 */	uint32_t	log_blocks_per_seg;		/* log2 # of blocks per segment */
+/* 0x18 */	uint32_t	segs_per_sec;			/* # of segments per section */
+/* 0x1C */	uint32_t	secs_per_zone;			/* # of sections per zone */
+/* 0x20 */	uint32_t	checksum_offset;		/* checksum offset inside super block */
+/* 0x24 */	uint64_t	block_count;			/* total # of user blocks */
+/* 0x2C */	uint32_t	section_count;			/* total # of sections */
+/* 0x30 */	uint32_t	segment_count;			/* total # of segments */
+/* 0x34 */	uint32_t	segment_count_ckpt;		/* # of segments for checkpoint */
+/* 0x38 */	uint32_t	segment_count_sit;		/* # of segments for SIT */
+/* 0x3C */	uint32_t	segment_count_nat;		/* # of segments for NAT */
+/* 0x40 */	uint32_t	segment_count_ssa;		/* # of segments for SSA */
+/* 0x44 */	uint32_t	segment_count_main;		/* # of segments for main area */
+/* 0x48 */	uint32_t	segment0_blkaddr;		/* start block address of segment 0 */
+/* 0x4C */	uint32_t	cp_blkaddr;			/* start block address of checkpoint */
+/* 0x50 */	uint32_t	sit_blkaddr;			/* start block address of SIT */
+/* 0x54 */	uint32_t	nat_blkaddr;			/* start block address of NAT */
+/* 0x58 */	uint32_t	ssa_blkaddr;			/* start block address of SSA */
+/* 0x5C */	uint32_t	main_blkaddr;			/* start block address of main area */
+/* 0x60 */	uint32_t	root_ino;			/* root inode number */
+/* 0x64 */	uint32_t	node_ino;			/* node inode number */
+/* 0x68 */	uint32_t	meta_ino;			/* meta inode number */
+/* 0x6C */	uint8_t		uuid[F2FS_UUID_SIZE];		/* 128-bit uuid for volume */
+/* 0x7C */	uint16_t	volume_name[F2FS_LABEL_SIZE];	/* volume name */
+#if 0
+/* 0x47C */	uint32_t	extension_count;		/* # of extensions below */
+/* 0x480 */	uint8_t		extension_list[64][8];		/* extension array */
+#endif
+} __attribute__((packed));
+
+static int probe_f2fs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct f2fs_super_block *sb;
+	uint16_t major, minor;
+
+	sb = blkid_probe_get_sb(pr, mag, struct f2fs_super_block);
+	if (!sb)
+		return errno ? -errno : 1;
+
+	major = le16_to_cpu(sb->major_ver);
+	minor = le16_to_cpu(sb->minor_ver);
+
+	/* For version 1.0 we cannot know the correct sb structure */
+	if (major == 1 && minor == 0)
+		return 0;
+
+	if (*((unsigned char *) sb->volume_name))
+		blkid_probe_set_utf8label(pr, (unsigned char *) sb->volume_name,
+						sizeof(sb->volume_name),
+						BLKID_ENC_UTF16LE);
+
+	blkid_probe_set_uuid(pr, sb->uuid);
+	blkid_probe_sprintf_version(pr, "%u.%u", major, minor);
+	return 0;
+}
+
+const struct blkid_idinfo f2fs_idinfo =
+{
+	.name           = "f2fs",
+	.usage          = BLKID_USAGE_FILESYSTEM,
+	.probefunc      = probe_f2fs,
+	.magics         =
+        {
+		{
+			.magic = F2FS_MAGIC,
+			.len = 4,
+			.kboff = F2FS_SB1_KBOFF,
+			.sboff = F2FS_MAGIC_OFF
+		},
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/gfs.c b/libblkid/src/superblocks/gfs.c
new file mode 100644
index 0000000..1b26558
--- /dev/null
+++ b/libblkid/src/superblocks/gfs.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+/* Common gfs/gfs2 constants: */
+#define GFS_LOCKNAME_LEN        64
+
+/* gfs1 constants: */
+#define GFS_FORMAT_FS           1309
+#define GFS_FORMAT_MULTI        1401
+/* gfs2 constants: */
+#define GFS2_FORMAT_FS          1801
+#define GFS2_FORMAT_MULTI       1900
+
+struct gfs2_meta_header {
+	uint32_t mh_magic;
+	uint32_t mh_type;
+	uint64_t __pad0;          /* Was generation number in gfs1 */
+	uint32_t mh_format;
+	uint32_t __pad1;          /* Was incarnation number in gfs1 */
+};
+
+struct gfs2_inum {
+	uint64_t no_formal_ino;
+	uint64_t no_addr;
+};
+
+struct gfs2_sb {
+	struct gfs2_meta_header sb_header;
+
+	uint32_t sb_fs_format;
+	uint32_t sb_multihost_format;
+	uint32_t  __pad0;  /* Was superblock flags in gfs1 */
+
+	uint32_t sb_bsize;
+	uint32_t sb_bsize_shift;
+	uint32_t __pad1;   /* Was journal segment size in gfs1 */
+
+	struct gfs2_inum sb_master_dir; /* Was jindex dinode in gfs1 */
+	struct gfs2_inum __pad2; /* Was rindex dinode in gfs1 */
+	struct gfs2_inum sb_root_dir;
+
+	char sb_lockproto[GFS_LOCKNAME_LEN];
+	char sb_locktable[GFS_LOCKNAME_LEN];
+
+	struct gfs2_inum __pad3; /* Was quota inode in gfs1 */
+	struct gfs2_inum __pad4; /* Was licence inode in gfs1 */
+	uint8_t sb_uuid[16]; /* The UUID maybe 0 for backwards compat */
+} __attribute__((packed));
+
+static int probe_gfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct gfs2_sb *sbd;
+
+	sbd = blkid_probe_get_sb(pr, mag, struct gfs2_sb);
+	if (!sbd)
+		return errno ? -errno : 1;
+
+	if (be32_to_cpu(sbd->sb_fs_format) == GFS_FORMAT_FS &&
+	    be32_to_cpu(sbd->sb_multihost_format) == GFS_FORMAT_MULTI)
+	{
+		if (*sbd->sb_locktable)
+			blkid_probe_set_label(pr,
+				(unsigned char *) sbd->sb_locktable,
+				sizeof(sbd->sb_locktable));
+
+		blkid_probe_set_uuid(pr, sbd->sb_uuid);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int probe_gfs2(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct gfs2_sb *sbd;
+
+	sbd = blkid_probe_get_sb(pr, mag, struct gfs2_sb);
+	if (!sbd)
+		return errno ? -errno : 1;
+
+	if (be32_to_cpu(sbd->sb_fs_format) == GFS2_FORMAT_FS &&
+	    be32_to_cpu(sbd->sb_multihost_format) == GFS2_FORMAT_MULTI)
+	{
+		if (*sbd->sb_locktable)
+			blkid_probe_set_label(pr,
+				(unsigned char *) sbd->sb_locktable,
+				sizeof(sbd->sb_locktable));
+		blkid_probe_set_uuid(pr, sbd->sb_uuid);
+		blkid_probe_set_version(pr, "1");
+		return 0;
+	}
+	return 1;
+}
+
+const struct blkid_idinfo gfs_idinfo =
+{
+	.name		= "gfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_gfs,
+	.minsz		= 32 * 1024 * 1024,	/* minimal size of GFS journal */
+	.magics		=
+	{
+		{ .magic = "\x01\x16\x19\x70", .len = 4, .kboff = 64 },
+		{ NULL }
+	}
+};
+
+const struct blkid_idinfo gfs2_idinfo =
+{
+	.name		= "gfs2",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_gfs2,
+	.minsz		= 32 * 1024 * 1024,	/* minimal size of GFS journal */
+	.magics		=
+	{
+		{ .magic = "\x01\x16\x19\x70", .len = 4, .kboff = 64 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/hfs.c b/libblkid/src/superblocks/hfs.c
new file mode 100644
index 0000000..fe57241
--- /dev/null
+++ b/libblkid/src/superblocks/hfs.c
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2004-2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "superblocks.h"
+#include "md5.h"
+
+/* HFS / HFS+ */
+struct hfs_finder_info {
+        uint32_t        boot_folder;
+        uint32_t        start_app;
+        uint32_t        open_folder;
+        uint32_t        os9_folder;
+        uint32_t        reserved;
+        uint32_t        osx_folder;
+        uint8_t         id[8];
+} __attribute__((packed));
+
+struct hfs_mdb {
+        uint8_t         signature[2];
+        uint32_t        cr_date;
+        uint32_t        ls_Mod;
+        uint16_t        atrb;
+        uint16_t        nm_fls;
+        uint16_t        vbm_st;
+        uint16_t        alloc_ptr;
+        uint16_t        nm_al_blks;
+        uint32_t        al_blk_size;
+        uint32_t        clp_size;
+        uint16_t        al_bl_st;
+        uint32_t        nxt_cnid;
+        uint16_t        free_bks;
+        uint8_t         label_len;
+        uint8_t         label[27];
+        uint32_t        vol_bkup;
+        uint16_t        vol_seq_num;
+        uint32_t        wr_cnt;
+        uint32_t        xt_clump_size;
+        uint32_t        ct_clump_size;
+        uint16_t        num_root_dirs;
+        uint32_t        file_count;
+        uint32_t        dir_count;
+        struct hfs_finder_info finder_info;
+        uint8_t         embed_sig[2];
+        uint16_t        embed_startblock;
+        uint16_t        embed_blockcount;
+} __attribute__((packed));
+
+
+#define HFS_NODE_LEAF			0xff
+#define HFSPLUS_POR_CNID		1
+
+struct hfsplus_bnode_descriptor {
+	uint32_t		next;
+	uint32_t		prev;
+	uint8_t		type;
+	uint8_t		height;
+	uint16_t		num_recs;
+	uint16_t		reserved;
+} __attribute__((packed));
+
+struct hfsplus_bheader_record {
+	uint16_t		depth;
+	uint32_t		root;
+	uint32_t		leaf_count;
+	uint32_t		leaf_head;
+	uint32_t		leaf_tail;
+	uint16_t		node_size;
+} __attribute__((packed));
+
+struct hfsplus_catalog_key {
+	uint16_t	key_len;
+	uint32_t	parent_id;
+	uint16_t	unicode_len;
+	uint8_t		unicode[255 * 2];
+} __attribute__((packed));
+
+struct hfsplus_extent {
+	uint32_t		start_block;
+	uint32_t		block_count;
+} __attribute__((packed));
+
+#define HFSPLUS_EXTENT_COUNT		8
+struct hfsplus_fork {
+	uint64_t		total_size;
+	uint32_t		clump_size;
+	uint32_t		total_blocks;
+	struct hfsplus_extent extents[HFSPLUS_EXTENT_COUNT];
+} __attribute__((packed));
+
+struct hfsplus_vol_header {
+	uint8_t		signature[2];
+	uint16_t		version;
+	uint32_t		attributes;
+	uint32_t		last_mount_vers;
+	uint32_t		reserved;
+	uint32_t		create_date;
+	uint32_t		modify_date;
+	uint32_t		backup_date;
+	uint32_t		checked_date;
+	uint32_t		file_count;
+	uint32_t		folder_count;
+	uint32_t		blocksize;
+	uint32_t		total_blocks;
+	uint32_t		free_blocks;
+	uint32_t		next_alloc;
+	uint32_t		rsrc_clump_sz;
+	uint32_t		data_clump_sz;
+	uint32_t		next_cnid;
+	uint32_t		write_count;
+	uint64_t		encodings_bmp;
+	struct hfs_finder_info finder_info;
+	struct hfsplus_fork alloc_file;
+	struct hfsplus_fork ext_file;
+	struct hfsplus_fork cat_file;
+	struct hfsplus_fork attr_file;
+	struct hfsplus_fork start_file;
+}  __attribute__((packed));
+
+#define HFSPLUS_SECTOR_SIZE        512
+
+static int hfs_set_uuid(blkid_probe pr, unsigned char const *hfs_info, size_t len)
+{
+	static unsigned char const hash_init[MD5LENGTH] = {
+		0xb3, 0xe2, 0x0f, 0x39, 0xf2, 0x92, 0x11, 0xd6,
+		0x97, 0xa4, 0x00, 0x30, 0x65, 0x43, 0xec, 0xac
+	};
+	unsigned char uuid[MD5LENGTH];
+	struct MD5Context md5c;
+
+	if (memcmp(hfs_info, "\0\0\0\0\0\0\0\0", len) == 0)
+		return -1;
+	MD5Init(&md5c);
+	MD5Update(&md5c, hash_init, MD5LENGTH);
+	MD5Update(&md5c, hfs_info, len);
+	MD5Final(uuid, &md5c);
+	uuid[6] = 0x30 | (uuid[6] & 0x0f);
+	uuid[8] = 0x80 | (uuid[8] & 0x3f);
+	return blkid_probe_set_uuid(pr, uuid);
+}
+
+static int probe_hfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct hfs_mdb	*hfs;
+
+	hfs = blkid_probe_get_sb(pr, mag, struct hfs_mdb);
+	if (!hfs)
+		return errno ? -errno : 1;
+
+	if ((memcmp(hfs->embed_sig, "H+", 2) == 0) ||
+	    (memcmp(hfs->embed_sig, "HX", 2) == 0))
+		return 1;	/* Not hfs, but an embedded HFS+ */
+
+	hfs_set_uuid(pr, hfs->finder_info.id, sizeof(hfs->finder_info.id));
+
+	blkid_probe_set_label(pr, hfs->label, hfs->label_len);
+	return 0;
+}
+
+static int probe_hfsplus(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct hfsplus_extent extents[HFSPLUS_EXTENT_COUNT];
+	struct hfsplus_bnode_descriptor *descr;
+	struct hfsplus_bheader_record *bnode;
+	struct hfsplus_catalog_key *key;
+	struct hfsplus_vol_header *hfsplus;
+	struct hfs_mdb *sbd;
+	unsigned int alloc_block_size;
+	unsigned int alloc_first_block;
+	unsigned int embed_first_block;
+	unsigned int off = 0;
+	unsigned int blocksize;
+	unsigned int cat_block;
+	unsigned int ext_block_start;
+	unsigned int ext_block_count;
+	unsigned int record_count;
+	unsigned int leaf_node_head;
+	unsigned int leaf_node_count;
+	unsigned int leaf_node_size;
+	unsigned int leaf_block;
+	int ext;
+	uint64_t leaf_off;
+	unsigned char *buf;
+
+	sbd = blkid_probe_get_sb(pr, mag, struct hfs_mdb);
+	if (!sbd)
+		return errno ? -errno : 1;
+
+	/* Check for a HFS+ volume embedded in a HFS volume */
+	if (memcmp(sbd->signature, "BD", 2) == 0) {
+		if ((memcmp(sbd->embed_sig, "H+", 2) != 0) &&
+		    (memcmp(sbd->embed_sig, "HX", 2) != 0))
+			/* This must be an HFS volume, so fail */
+			return 1;
+
+		alloc_block_size = be32_to_cpu(sbd->al_blk_size);
+		alloc_first_block = be16_to_cpu(sbd->al_bl_st);
+		embed_first_block = be16_to_cpu(sbd->embed_startblock);
+		off = (alloc_first_block * 512) +
+			(embed_first_block * alloc_block_size);
+
+		buf = blkid_probe_get_buffer(pr,
+				off + (mag->kboff * 1024),
+				sizeof(struct hfsplus_vol_header));
+		hfsplus = (struct hfsplus_vol_header *) buf;
+
+	} else
+		hfsplus = blkid_probe_get_sb(pr, mag,
+				struct hfsplus_vol_header);
+
+	if (!hfsplus)
+		return errno ? -errno : 1;
+
+	if ((memcmp(hfsplus->signature, "H+", 2) != 0) &&
+	    (memcmp(hfsplus->signature, "HX", 2) != 0))
+		return 1;
+
+	hfs_set_uuid(pr, hfsplus->finder_info.id, sizeof(hfsplus->finder_info.id));
+
+	blocksize = be32_to_cpu(hfsplus->blocksize);
+	if (blocksize < HFSPLUS_SECTOR_SIZE)
+		return 1;
+
+	memcpy(extents, hfsplus->cat_file.extents, sizeof(extents));
+	cat_block = be32_to_cpu(extents[0].start_block);
+
+	buf = blkid_probe_get_buffer(pr,
+			off + ((blkid_loff_t) cat_block * blocksize), 0x2000);
+	if (!buf)
+		return errno ? -errno : 0;
+
+	bnode = (struct hfsplus_bheader_record *)
+		&buf[sizeof(struct hfsplus_bnode_descriptor)];
+
+	leaf_node_head = be32_to_cpu(bnode->leaf_head);
+	leaf_node_size = be16_to_cpu(bnode->node_size);
+	leaf_node_count = be32_to_cpu(bnode->leaf_count);
+	if (leaf_node_count == 0)
+		return 0;
+
+	leaf_block = (leaf_node_head * leaf_node_size) / blocksize;
+
+	/* get physical location */
+	for (ext = 0; ext < HFSPLUS_EXTENT_COUNT; ext++) {
+		ext_block_start = be32_to_cpu(extents[ext].start_block);
+		ext_block_count = be32_to_cpu(extents[ext].block_count);
+		if (ext_block_count == 0)
+			return 0;
+
+		/* this is our extent */
+		if (leaf_block < ext_block_count)
+			break;
+
+		leaf_block -= ext_block_count;
+	}
+	if (ext == HFSPLUS_EXTENT_COUNT)
+		return 0;
+
+	leaf_off = (ext_block_start + leaf_block) * blocksize;
+
+	buf = blkid_probe_get_buffer(pr,
+				(blkid_loff_t) off + leaf_off,
+				leaf_node_size);
+	if (!buf)
+		return errno ? -errno : 0;
+
+	descr = (struct hfsplus_bnode_descriptor *) buf;
+	record_count = be16_to_cpu(descr->num_recs);
+	if (record_count == 0)
+		return 0;
+
+	if (descr->type != HFS_NODE_LEAF)
+		return 0;
+
+	key = (struct hfsplus_catalog_key *)
+		&buf[sizeof(struct hfsplus_bnode_descriptor)];
+
+	if (be32_to_cpu(key->parent_id) != HFSPLUS_POR_CNID)
+		return 0;
+
+	blkid_probe_set_utf8label(pr, key->unicode,
+			be16_to_cpu(key->unicode_len) * 2,
+			BLKID_ENC_UTF16BE);
+	return 0;
+}
+
+const struct blkid_idinfo hfs_idinfo =
+{
+	.name		= "hfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_hfs,
+	.flags		= BLKID_IDINFO_TOLERANT,
+	.magics		=
+	{
+		{ .magic = "BD", .len = 2, .kboff = 1 },
+		{ NULL }
+	}
+};
+
+const struct blkid_idinfo hfsplus_idinfo =
+{
+	.name		= "hfsplus",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_hfsplus,
+	.magics		=
+	{
+		{ .magic = "BD", .len = 2, .kboff = 1 },
+		{ .magic = "H+", .len = 2, .kboff = 1 },
+		{ .magic = "HX", .len = 2, .kboff = 1 },
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/highpoint_raid.c b/libblkid/src/superblocks/highpoint_raid.c
new file mode 100644
index 0000000..ad3b538
--- /dev/null
+++ b/libblkid/src/superblocks/highpoint_raid.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct hpt45x_metadata {
+	uint32_t	magic;
+};
+
+#define HPT45X_MAGIC_OK			0x5a7816f3
+#define HPT45X_MAGIC_BAD		0x5a7816fd
+
+static int probe_highpoint45x(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct hpt45x_metadata *hpt;
+	uint64_t off;
+	uint32_t magic;
+
+	if (pr->size < 0x10000)
+		return 1;
+	if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+		return 1;
+
+	off = ((pr->size / 0x200) - 11) * 0x200;
+	hpt = (struct hpt45x_metadata *)
+			blkid_probe_get_buffer(pr,
+					off,
+					sizeof(struct hpt45x_metadata));
+	if (!hpt)
+		return errno ? -errno : 1;
+	magic = le32_to_cpu(hpt->magic);
+	if (magic != HPT45X_MAGIC_OK && magic != HPT45X_MAGIC_BAD)
+		return 1;
+	if (blkid_probe_set_magic(pr, off, sizeof(hpt->magic),
+				(unsigned char *) &hpt->magic))
+		return 1;
+	return 0;
+}
+
+static int probe_highpoint37x(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+		return 1;
+	return 0;
+}
+
+
+const struct blkid_idinfo highpoint45x_idinfo = {
+	.name		= "hpt45x_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_highpoint45x,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+const struct blkid_idinfo highpoint37x_idinfo = {
+	.name		= "hpt37x_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_highpoint37x,
+	.magics		= {
+		/*
+		 * Superblok offset:                      4608 bytes  (9 sectors)
+		 * Magic string offset within superblock:   32 bytes
+		 *
+		 * kboff = (4608 + 32) / 1024
+		 * sboff = (4608 + 32) % kboff
+		 */
+		{ .magic = "\xf0\x16\x78\x5a", .len = 4, .kboff = 4, .sboff = 544 },
+		{ .magic = "\xfd\x16\x78\x5a", .len = 4, .kboff = 4, .sboff = 544 },
+		{ NULL }
+	}
+};
+
+
diff --git a/libblkid/src/superblocks/hpfs.c b/libblkid/src/superblocks/hpfs.c
new file mode 100644
index 0000000..0565d37
--- /dev/null
+++ b/libblkid/src/superblocks/hpfs.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct hpfs_boot_block
+{
+	uint8_t		jmp[3];
+	uint8_t		oem_id[8];
+	uint8_t		bytes_per_sector[2];
+	uint8_t		sectors_per_cluster;
+	uint8_t		n_reserved_sectors[2];
+	uint8_t		n_fats;
+	uint8_t		n_rootdir_entries[2];
+	uint8_t		n_sectors_s[2];
+	uint8_t		media_byte;
+	uint16_t	sectors_per_fat;
+	uint16_t	sectors_per_track;
+	uint16_t	heads_per_cyl;
+	uint32_t	n_hidden_sectors;
+	uint32_t	n_sectors_l;
+	uint8_t		drive_number;
+	uint8_t		mbz;
+	uint8_t		sig_28h;
+	uint8_t		vol_serno[4];
+	uint8_t		vol_label[11];
+	uint8_t		sig_hpfs[8];
+	uint8_t		pad[448];
+	uint8_t		magic[2];
+} __attribute__((packed));
+
+struct hpfs_super_block
+{
+	uint8_t		magic[4];
+	uint8_t		magic1[4];
+	uint8_t		version;
+} __attribute__((packed));
+
+struct hpfs_spare_super
+{
+	uint8_t		magic[4];
+	uint8_t		magic1[4];
+} __attribute__((packed));
+
+
+#define HPFS_SB_OFFSET			0x2000
+#define HPFS_SBSPARE_OFFSET		0x2200
+
+static int probe_hpfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct hpfs_super_block *hs;
+	struct hpfs_spare_super *hss;
+	struct hpfs_boot_block *hbb;
+	uint8_t version;
+
+	/* super block */
+	hs = blkid_probe_get_sb(pr, mag, struct hpfs_super_block);
+	if (!hs)
+		return errno ? -errno : 1;
+	version = hs->version;
+
+	/* spare super block */
+	hss = (struct hpfs_spare_super *)
+			blkid_probe_get_buffer(pr,
+				HPFS_SBSPARE_OFFSET,
+				sizeof(struct hpfs_spare_super));
+	if (!hss)
+		return errno ? -errno : 1;
+	if (memcmp(hss->magic, "\x49\x18\x91\xf9", 4) != 0)
+		return 1;
+
+	/* boot block (with UUID and LABEL) */
+	hbb = (struct hpfs_boot_block *)
+			blkid_probe_get_buffer(pr,
+				0,
+				sizeof(struct hpfs_boot_block));
+	if (!hbb)
+		return errno ? -errno : 1;
+	if (memcmp(hbb->magic, "\x55\xaa", 2) == 0 &&
+	    memcmp(hbb->sig_hpfs, "HPFS", 4) == 0 &&
+	    hbb->sig_28h == 0x28) {
+		blkid_probe_set_label(pr, hbb->vol_label, sizeof(hbb->vol_label));
+		blkid_probe_sprintf_uuid(pr,
+				hbb->vol_serno, sizeof(hbb->vol_serno),
+				"%02X%02X-%02X%02X",
+				hbb->vol_serno[3], hbb->vol_serno[2],
+				hbb->vol_serno[1], hbb->vol_serno[0]);
+	}
+	blkid_probe_sprintf_version(pr, "%u", version);
+
+	return 0;
+}
+
+const struct blkid_idinfo hpfs_idinfo =
+{
+	.name		= "hpfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_hpfs,
+	.magics		=
+	{
+		{
+		  .magic = "\x49\xe8\x95\xf9",
+		  .len = 4,
+		  .kboff = (HPFS_SB_OFFSET >> 10)
+		},
+		{ NULL }
+	}
+};
+
+
diff --git a/libblkid/src/superblocks/iso9660.c b/libblkid/src/superblocks/iso9660.c
new file mode 100644
index 0000000..d099467
--- /dev/null
+++ b/libblkid/src/superblocks/iso9660.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired also by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include "superblocks.h"
+
+struct iso9660_date {
+	unsigned char year[4];
+	unsigned char month[2];
+	unsigned char day[2];
+	unsigned char hour[2];
+	unsigned char minute[2];
+	unsigned char second[2];
+	unsigned char hundredth[2];
+	unsigned char offset;
+} __attribute__ ((packed));
+
+/* PVD - Primary volume descriptor */
+struct iso_volume_descriptor {
+	unsigned char	vd_type;
+	unsigned char	vd_id[5];
+	unsigned char	vd_version;
+	unsigned char	flags;
+	unsigned char	system_id[32];
+	unsigned char	volume_id[32];
+	unsigned char	unused[8];
+	unsigned char	space_size[8];
+	unsigned char	escape_sequences[8];
+	unsigned char  unused1[222];
+	unsigned char  publisher_id[128];
+	unsigned char  unused2[128];
+	unsigned char  application_id[128];
+	unsigned char  unused3[111];
+	struct iso9660_date created;
+	struct iso9660_date modified;
+} __attribute__((packed));
+
+/* Boot Record */
+struct boot_record {
+	unsigned char	vd_type;
+	unsigned char	vd_id[5];
+	unsigned char	vd_version;
+	unsigned char	boot_system_id[32];
+	unsigned char	boot_id[32];
+	unsigned char	unused[1];
+} __attribute__((packed));
+
+#define ISO_SUPERBLOCK_OFFSET		0x8000
+#define ISO_SECTOR_SIZE			0x800
+#define ISO_VD_OFFSET			(ISO_SUPERBLOCK_OFFSET + ISO_SECTOR_SIZE)
+#define ISO_VD_BOOT_RECORD		0x0
+#define ISO_VD_SUPPLEMENTARY		0x2
+#define ISO_VD_END			0xff
+#define ISO_VD_MAX			16
+
+struct high_sierra_volume_descriptor {
+	unsigned char	foo[8];
+	unsigned char	type;
+	unsigned char	id[5];
+	unsigned char	version;
+	unsigned char	unused1;
+	unsigned char	system_id[32];
+	unsigned char   volume_id[32];
+} __attribute__((packed));
+
+/* returns 1 if the begin of @ascii is equal to @utf16 string.
+ */
+static int ascii_eq_utf16be(unsigned char *ascii,
+			unsigned char *utf16, size_t len)
+{
+	size_t a, u;
+
+	for (a = 0, u = 0; u < len; a++, u += 2) {
+		if (utf16[u] != 0x0 || ascii[a] != utf16[u + 1])
+			return 0;
+	}
+	return 1;
+}
+
+/* old High Sierra format */
+static int probe_iso9660_hsfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct high_sierra_volume_descriptor *iso;
+
+	iso = blkid_probe_get_sb(pr, mag, struct high_sierra_volume_descriptor);
+	if (!iso)
+		return errno ? -errno : 1;
+
+	blkid_probe_set_version(pr, "High Sierra");
+	blkid_probe_set_label(pr, iso->volume_id, sizeof(iso->volume_id));
+	return 0;
+}
+
+static int probe_iso9660_set_uuid (blkid_probe pr, const struct iso9660_date *date)
+{
+	unsigned char buffer[16];
+	unsigned int i, zeros = 0;
+
+	buffer[0] = date->year[0];
+	buffer[1] = date->year[1];
+	buffer[2] = date->year[2];
+	buffer[3] = date->year[3];
+	buffer[4] = date->month[0];
+	buffer[5] = date->month[1];
+	buffer[6] = date->day[0];
+	buffer[7] = date->day[1];
+	buffer[8] = date->hour[0];
+	buffer[9] = date->hour[1];
+	buffer[10] = date->minute[0];
+	buffer[11] = date->minute[1];
+	buffer[12] = date->second[0];
+	buffer[13] = date->second[1];
+	buffer[14] = date->hundredth[0];
+	buffer[15] = date->hundredth[1];
+
+	/* count the number of zeros ('0') in the date buffer */
+	for (i = 0, zeros = 0; i < sizeof(buffer); i++)
+		if (buffer[i] == '0')
+			zeros++;
+
+	/* due to the iso9660 standard if all date fields are '0' and offset is 0, the date is unset */
+	if (zeros == sizeof(buffer) && date->offset == 0)
+		return 0;
+
+	/* generate an UUID using this date and return success */
+	blkid_probe_sprintf_uuid (pr, buffer, sizeof(buffer),
+		"%c%c%c%c-%c%c-%c%c-%c%c-%c%c-%c%c-%c%c",
+		buffer[0], buffer[1], buffer[2], buffer[3],
+		buffer[4], buffer[5],
+		buffer[6], buffer[7],
+		buffer[8], buffer[9],
+		buffer[10], buffer[11],
+		buffer[12], buffer[13],
+		buffer[14], buffer[15]);
+
+	return 1;
+}
+
+static int is_str_empty(const unsigned char *str, size_t len)
+{
+	size_t i;
+
+	if (!str || !*str)
+		return 1;
+
+	for (i = 0; i < len; i++)
+		if (!isspace(str[i]))
+			return 0;
+	return 1;
+}
+
+/* iso9660 [+ Microsoft Joliet Extension] */
+int probe_iso9660(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct iso_volume_descriptor *iso;
+	unsigned char label[32];
+	int i;
+	int off;
+
+	if (strcmp(mag->magic, "CDROM") == 0)
+		return probe_iso9660_hsfs(pr, mag);
+
+	iso = blkid_probe_get_sb(pr, mag, struct iso_volume_descriptor);
+	if (!iso)
+		return errno ? -errno : 1;
+
+	memcpy(label, iso->volume_id, sizeof(label));
+
+	if (!is_str_empty(iso->system_id, sizeof(iso->system_id)))
+		blkid_probe_set_id_label(pr, "SYSTEM_ID",
+				iso->system_id, sizeof(iso->system_id));
+
+	if (!is_str_empty(iso->publisher_id, sizeof(iso->publisher_id)))
+		blkid_probe_set_id_label(pr, "PUBLISHER_ID",
+				iso->publisher_id, sizeof(iso->publisher_id));
+
+	if (!is_str_empty(iso->application_id, sizeof(iso->application_id)))
+		blkid_probe_set_id_label(pr, "APPLICATION_ID",
+				iso->application_id, sizeof(iso->application_id));
+
+	/* create an UUID using the modified/created date */
+	if (! probe_iso9660_set_uuid(pr, &iso->modified))
+		probe_iso9660_set_uuid(pr, &iso->created);
+
+	/* Joliet Extension and Boot Record */
+	off = ISO_VD_OFFSET;
+	for (i = 0; i < ISO_VD_MAX; i++) {
+		struct boot_record *boot= (struct boot_record *)
+			blkid_probe_get_buffer(pr,
+					off,
+					max(sizeof(struct boot_record),
+					    sizeof(struct iso_volume_descriptor)));
+
+		if (boot == NULL || boot->vd_type == ISO_VD_END)
+			break;
+
+		if (boot->vd_type == ISO_VD_BOOT_RECORD) {
+			if (!is_str_empty(boot->boot_system_id,
+					  sizeof(boot->boot_system_id)))
+				blkid_probe_set_id_label(pr, "BOOT_SYSTEM_ID",
+							boot->boot_system_id,
+							sizeof(boot->boot_system_id));
+			off += ISO_SECTOR_SIZE;
+			continue;
+		}
+
+		/* Not a Boot record, lets see if its supplemntary volume descriptor */
+		iso = (struct iso_volume_descriptor *) boot;
+
+		if (iso->vd_type != ISO_VD_SUPPLEMENTARY) {
+			off += ISO_SECTOR_SIZE;
+			continue;
+		}
+
+		if (memcmp(iso->escape_sequences, "%/@", 3) == 0 ||
+		    memcmp(iso->escape_sequences, "%/C", 3) == 0 ||
+		    memcmp(iso->escape_sequences, "%/E", 3) == 0) {
+
+			blkid_probe_set_version(pr, "Joliet Extension");
+
+			/* Is the Joliet (UTF16BE) label equal to the label in
+			 * the PVD? If yes, use PVD label.  The Jolied version
+			 * of the label could be trimed (because UTF16..).
+			 */
+			if (ascii_eq_utf16be(label, iso->volume_id, 32))
+				break;
+
+			blkid_probe_set_utf8label(pr,
+					iso->volume_id,
+					sizeof(iso->volume_id),
+					BLKID_ENC_UTF16BE);
+			goto has_label;
+		}
+		off += ISO_SECTOR_SIZE;
+	}
+
+	/* Joliet not found, let use standard iso label */
+	blkid_probe_set_label(pr, label, sizeof(label));
+
+has_label:
+	return 0;
+}
+
+const struct blkid_idinfo iso9660_idinfo =
+{
+	.name		= "iso9660",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_iso9660,
+	.flags		= BLKID_IDINFO_TOLERANT,
+	.magics		=
+	{
+		{ .magic = "CD001", .len = 5, .kboff = 32, .sboff = 1 },
+		{ .magic = "CDROM", .len = 5, .kboff = 32, .sboff = 9 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/iso9660.h b/libblkid/src/superblocks/iso9660.h
new file mode 100644
index 0000000..a8d729d
--- /dev/null
+++ b/libblkid/src/superblocks/iso9660.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) 2013 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef _BLKID_ISO9660_H
+#define _BLKID_ISO9660_H
+
+#include "blkidP.h"
+
+extern int probe_iso9660(blkid_probe pr, const struct blkid_idmag *mag);
+
+#endif /* _BLKID_ISO9660_H */
diff --git a/libblkid/src/superblocks/isw_raid.c b/libblkid/src/superblocks/isw_raid.c
new file mode 100644
index 0000000..065c2b2
--- /dev/null
+++ b/libblkid/src/superblocks/isw_raid.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct isw_metadata {
+	uint8_t		sig[32];
+	uint32_t	check_sum;
+	uint32_t	mpb_size;
+	uint32_t	family_num;
+	uint32_t	generation_num;
+};
+
+#define ISW_SIGNATURE		"Intel Raid ISM Cfg Sig. "
+
+
+static int probe_iswraid(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	uint64_t off;
+	struct isw_metadata *isw;
+
+	if (pr->size < 0x10000)
+		return 1;
+	if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+		return 1;
+
+	off = ((pr->size / 0x200) - 2) * 0x200;
+	isw = (struct isw_metadata *)
+			blkid_probe_get_buffer(pr,
+					off,
+					sizeof(struct isw_metadata));
+	if (!isw)
+		return errno ? -errno : 1;
+
+	if (memcmp(isw->sig, ISW_SIGNATURE, sizeof(ISW_SIGNATURE)-1) != 0)
+		return 1;
+	if (blkid_probe_sprintf_version(pr, "%6s",
+			&isw->sig[sizeof(ISW_SIGNATURE)-1]) != 0)
+		return 1;
+	if (blkid_probe_set_magic(pr, off, sizeof(isw->sig),
+				(unsigned char *) isw->sig))
+		return 1;
+	return 0;
+}
+
+const struct blkid_idinfo iswraid_idinfo = {
+	.name		= "isw_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_iswraid,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/jfs.c b/libblkid/src/superblocks/jfs.c
new file mode 100644
index 0000000..ac684d8
--- /dev/null
+++ b/libblkid/src/superblocks/jfs.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct jfs_super_block {
+	unsigned char	js_magic[4];
+	uint32_t	js_version;
+	uint64_t	js_size;
+	uint32_t	js_bsize;	/* 4: aggregate block size in bytes */
+	uint16_t	js_l2bsize;	/* 2: log2 of s_bsize */
+	uint16_t	js_l2bfactor;	/* 2: log2(s_bsize/hardware block size) */
+	uint32_t	js_pbsize;	/* 4: hardware/LVM block size in bytes */
+	uint16_t	js_l2pbsize;	/* 2: log2 of s_pbsize */
+	uint16_t	js_pad;		/* 2: padding necessary for alignment */
+	uint32_t	js_dummy2[26];
+	unsigned char	js_uuid[16];
+	unsigned char	js_label[16];
+	unsigned char	js_loguuid[16];
+};
+
+static int probe_jfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct jfs_super_block *js;
+
+	js = blkid_probe_get_sb(pr, mag, struct jfs_super_block);
+	if (!js)
+		return errno ? -errno : 1;
+	if (le32_to_cpu(js->js_bsize) != (1U << le16_to_cpu(js->js_l2bsize)))
+		return 1;
+	if (le32_to_cpu(js->js_pbsize) != (1U << le16_to_cpu(js->js_l2pbsize)))
+		return 1;
+	if ((le16_to_cpu(js->js_l2bsize) - le16_to_cpu(js->js_l2pbsize)) !=
+	    le16_to_cpu(js->js_l2bfactor))
+		return 1;
+
+	if (strlen((char *) js->js_label))
+		blkid_probe_set_label(pr, js->js_label, sizeof(js->js_label));
+	blkid_probe_set_uuid(pr, js->js_uuid);
+	return 0;
+}
+
+
+const struct blkid_idinfo jfs_idinfo =
+{
+	.name		= "jfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_jfs,
+	.minsz		= 16 * 1024 * 1024,
+	.magics		=
+	{
+		{ .magic = "JFS1", .len = 4, .kboff = 32 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/jmicron_raid.c b/libblkid/src/superblocks/jmicron_raid.c
new file mode 100644
index 0000000..ca79867
--- /dev/null
+++ b/libblkid/src/superblocks/jmicron_raid.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct jm_metadata {
+	int8_t		signature[2];
+	uint8_t		minor_version;
+	uint8_t		major_version;
+	uint16_t	checksum;
+};
+
+#define JM_SIGNATURE		"JM"
+
+static int probe_jmraid(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	uint64_t off;
+	struct jm_metadata *jm;
+
+	if (pr->size < 0x10000)
+		return 1;
+	if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+		return 1;
+
+	off = ((pr->size / 0x200) - 1) * 0x200;
+	jm = (struct jm_metadata *)
+		blkid_probe_get_buffer(pr,
+				off,
+				sizeof(struct jm_metadata));
+	if (!jm)
+		return errno ? -errno : 1;
+
+	if (memcmp(jm->signature, JM_SIGNATURE, sizeof(JM_SIGNATURE) - 1) != 0)
+		return 1;
+	if (blkid_probe_sprintf_version(pr, "%u.%u",
+				jm->major_version, jm->minor_version) != 0)
+		return 1;
+	if (blkid_probe_set_magic(pr, off, sizeof(jm->signature),
+				(unsigned char *) jm->signature))
+		return 1;
+	return 0;
+}
+
+const struct blkid_idinfo jmraid_idinfo = {
+	.name		= "jmicron_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_jmraid,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/linux_raid.c b/libblkid/src/superblocks/linux_raid.c
new file mode 100644
index 0000000..cf29141
--- /dev/null
+++ b/libblkid/src/superblocks/linux_raid.c
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct mdp0_super_block {
+	uint32_t	md_magic;
+	uint32_t	major_version;
+	uint32_t	minor_version;
+	uint32_t	patch_version;
+	uint32_t	gvalid_words;
+	uint32_t	set_uuid0;
+	uint32_t	ctime;
+	uint32_t	level;
+	uint32_t	size;
+	uint32_t	nr_disks;
+	uint32_t	raid_disks;
+	uint32_t	md_minor;
+	uint32_t	not_persistent;
+	uint32_t	set_uuid1;
+	uint32_t	set_uuid2;
+	uint32_t	set_uuid3;
+};
+
+/*
+ * Version-1, little-endian.
+ */
+struct mdp1_super_block {
+	/* constant array information - 128 bytes */
+	uint32_t	magic;		/* MD_SB_MAGIC: 0xa92b4efc - little endian */
+	uint32_t	major_version;	/* 1 */
+	uint32_t	feature_map;	/* 0 for now */
+	uint32_t	pad0;		/* always set to 0 when writing */
+
+	uint8_t		set_uuid[16];	/* user-space generated. */
+	unsigned char	set_name[32];	/* set and interpreted by user-space */
+
+	uint64_t	ctime;		/* lo 40 bits are seconds, top 24 are microseconds or 0*/
+	uint32_t	level;		/* -4 (multipath), -1 (linear), 0,1,4,5 */
+	uint32_t	layout;		/* only for raid5 currently */
+	uint64_t	size;		/* used size of component devices, in 512byte sectors */
+
+	uint32_t	chunksize;	/* in 512byte sectors */
+	uint32_t	raid_disks;
+	uint32_t	bitmap_offset;	/* sectors after start of superblock that bitmap starts
+					 * NOTE: signed, so bitmap can be before superblock
+					 * only meaningful of feature_map[0] is set.
+					 */
+
+	/* These are only valid with feature bit '4' */
+	uint32_t	new_level;	/* new level we are reshaping to		*/
+	uint64_t	reshape_position;	/* next address in array-space for reshape */
+	uint32_t	delta_disks;	/* change in number of raid_disks		*/
+	uint32_t	new_layout;	/* new layout					*/
+	uint32_t	new_chunk;	/* new chunk size (bytes)			*/
+	uint8_t		pad1[128-124];	/* set to 0 when written */
+
+	/* constant this-device information - 64 bytes */
+	uint64_t	data_offset;	/* sector start of data, often 0 */
+	uint64_t	data_size;	/* sectors in this device that can be used for data */
+	uint64_t	super_offset;	/* sector start of this superblock */
+	uint64_t	recovery_offset;/* sectors before this offset (from data_offset) have been recovered */
+	uint32_t	dev_number;	/* permanent identifier of this  device - not role in raid */
+	uint32_t	cnt_corrected_read; /* number of read errors that were corrected by re-writing */
+	uint8_t		device_uuid[16]; /* user-space setable, ignored by kernel */
+        uint8_t		devflags;        /* per-device flags.  Only one defined...*/
+	uint8_t		pad2[64-57];	/* set to 0 when writing */
+
+	/* array state information - 64 bytes */
+	uint64_t	utime;		/* 40 bits second, 24 btes microseconds */
+	uint64_t	events;		/* incremented when superblock updated */
+	uint64_t	resync_offset;	/* data before this offset (from data_offset) known to be in sync */
+	uint32_t	sb_csum;	/* checksum up to dev_roles[max_dev] */
+	uint32_t	max_dev;	/* size of dev_roles[] array to consider */
+	uint8_t		pad3[64-32];	/* set to 0 when writing */
+
+	/* device state information. Indexed by dev_number.
+	 * 2 bytes per device
+	 * Note there are no per-device state flags. State information is rolled
+	 * into the 'roles' value.  If a device is spare or faulty, then it doesn't
+	 * have a meaningful role.
+	 */
+	uint16_t	dev_roles[0];	/* role in array, or 0xffff for a spare, or 0xfffe for faulty */
+};
+
+
+#define MD_RESERVED_BYTES		0x10000
+#define MD_SB_MAGIC			0xa92b4efc
+
+static int probe_raid0(blkid_probe pr, blkid_loff_t off)
+{
+	struct mdp0_super_block *mdp0;
+	union {
+		uint32_t ints[4];
+		uint8_t bytes[16];
+	} uuid;
+	uint32_t ma, mi, pa;
+	uint64_t size;
+
+	if (pr->size < MD_RESERVED_BYTES)
+		return 1;
+	mdp0 = (struct mdp0_super_block *)
+			blkid_probe_get_buffer(pr,
+				off,
+				sizeof(struct mdp0_super_block));
+	if (!mdp0)
+		return errno ? -errno : 1;
+
+	memset(uuid.ints, 0, sizeof(uuid.ints));
+
+	if (le32_to_cpu(mdp0->md_magic) == MD_SB_MAGIC) {
+		uuid.ints[0] = swab32(mdp0->set_uuid0);
+		if (le32_to_cpu(mdp0->minor_version) >= 90) {
+			uuid.ints[1] = swab32(mdp0->set_uuid1);
+			uuid.ints[2] = swab32(mdp0->set_uuid2);
+			uuid.ints[3] = swab32(mdp0->set_uuid3);
+		}
+		ma = le32_to_cpu(mdp0->major_version);
+		mi = le32_to_cpu(mdp0->minor_version);
+		pa = le32_to_cpu(mdp0->patch_version);
+		size = le32_to_cpu(mdp0->size);
+
+	} else if (be32_to_cpu(mdp0->md_magic) == MD_SB_MAGIC) {
+		uuid.ints[0] = mdp0->set_uuid0;
+		if (be32_to_cpu(mdp0->minor_version) >= 90) {
+			uuid.ints[1] = mdp0->set_uuid1;
+			uuid.ints[2] = mdp0->set_uuid2;
+			uuid.ints[3] = mdp0->set_uuid3;
+		}
+		ma = be32_to_cpu(mdp0->major_version);
+		mi = be32_to_cpu(mdp0->minor_version);
+		pa = be32_to_cpu(mdp0->patch_version);
+		size = be32_to_cpu(mdp0->size);
+	} else
+		return 1;
+
+	size <<= 10;	/* convert KiB to bytes */
+
+	if (pr->size < 0 || (uint64_t) pr->size < size + MD_RESERVED_BYTES)
+		/* device is too small */
+		return 1;
+
+	if (off < 0 || (uint64_t) off < size)
+		/* no space before superblock */
+		return 1;
+
+	/*
+	 * Check for collisions between RAID and partition table
+	 *
+	 * For example the superblock is at the end of the last partition, it's
+	 * the same position as at the end of the disk...
+	 */
+	if ((S_ISREG(pr->mode) || blkid_probe_is_wholedisk(pr)) &&
+	    blkid_probe_is_covered_by_pt(pr,
+			off - size,				/* min. start  */
+			size + MD_RESERVED_BYTES)) {		/* min. length */
+
+		/* ignore this superblock, it's within any partition and
+		 * we are working with whole-disk now */
+		return 1;
+	}
+
+	if (blkid_probe_sprintf_version(pr, "%u.%u.%u", ma, mi, pa) != 0)
+		return 1;
+	if (blkid_probe_set_uuid(pr, (unsigned char *) uuid.bytes) != 0)
+		return 1;
+	if (blkid_probe_set_magic(pr, off, sizeof(mdp0->md_magic),
+				(unsigned char *) &mdp0->md_magic))
+		return 1;
+	return 0;
+}
+
+static int probe_raid1(blkid_probe pr, off_t off)
+{
+	struct mdp1_super_block *mdp1;
+
+	mdp1 = (struct mdp1_super_block *)
+			blkid_probe_get_buffer(pr,
+				off,
+				sizeof(struct mdp1_super_block));
+	if (!mdp1)
+		return errno ? -errno : 1;
+	if (le32_to_cpu(mdp1->magic) != MD_SB_MAGIC)
+		return 1;
+	if (le32_to_cpu(mdp1->major_version) != 1U)
+		return 1;
+	if (le64_to_cpu(mdp1->super_offset) != (uint64_t) off >> 9)
+		return 1;
+	if (blkid_probe_set_uuid(pr, (unsigned char *) mdp1->set_uuid) != 0)
+		return 1;
+	if (blkid_probe_set_uuid_as(pr,
+			(unsigned char *) mdp1->device_uuid, "UUID_SUB") != 0)
+		return 1;
+	if (blkid_probe_set_label(pr, mdp1->set_name,
+				sizeof(mdp1->set_name)) != 0)
+		return 1;
+	if (blkid_probe_set_magic(pr, off, sizeof(mdp1->magic),
+				(unsigned char *) &mdp1->magic))
+		return 1;
+	return 0;
+}
+
+int probe_raid(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	const char *ver = NULL;
+	int ret = BLKID_PROBE_NONE;
+
+	if (pr->size > MD_RESERVED_BYTES) {
+		/* version 0 at the end of the device */
+		uint64_t sboff = (pr->size & ~(MD_RESERVED_BYTES - 1))
+			- MD_RESERVED_BYTES;
+		ret = probe_raid0(pr, sboff);
+		if (ret < 1)
+			return ret;	/* error */
+
+		/* version 1.0 at the end of the device */
+		sboff = (pr->size & ~(0x1000 - 1)) - 0x2000;
+		ret = probe_raid1(pr, sboff);
+		if (ret < 0)
+			return ret;	/* error */
+		if (ret == 0)
+			ver = "1.0";
+	}
+
+	if (!ver) {
+		/* version 1.1 at the start of the device */
+		ret = probe_raid1(pr, 0);
+		if (ret == 0)
+			ver = "1.1";
+
+		/* version 1.2 at 4k offset from the start */
+		else if (ret == BLKID_PROBE_NONE) {
+			ret = probe_raid1(pr, 0x1000);
+			if (ret == 0)
+				ver = "1.2";
+		}
+	}
+
+	if (ver) {
+		blkid_probe_set_version(pr, ver);
+		return BLKID_PROBE_OK;
+	}
+	return ret;
+}
+
+
+const struct blkid_idinfo linuxraid_idinfo = {
+	.name		= "linux_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_raid,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/lsi_raid.c b/libblkid/src/superblocks/lsi_raid.c
new file mode 100644
index 0000000..697b0fe
--- /dev/null
+++ b/libblkid/src/superblocks/lsi_raid.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct lsi_metadata {
+	uint8_t		sig[6];
+};
+
+
+#define LSI_SIGNATURE		"$XIDE$"
+
+static int probe_lsiraid(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	uint64_t off;
+	struct lsi_metadata *lsi;
+
+	if (pr->size < 0x10000)
+		return 1;
+	if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+		return 1;
+
+	off = ((pr->size / 0x200) - 1) * 0x200;
+	lsi = (struct lsi_metadata *)
+		blkid_probe_get_buffer(pr,
+				off,
+				sizeof(struct lsi_metadata));
+	if (!lsi)
+		return errno ? -errno : 1;
+
+	if (memcmp(lsi->sig, LSI_SIGNATURE, sizeof(LSI_SIGNATURE)-1) != 0)
+		return 1;
+	if (blkid_probe_set_magic(pr, off, sizeof(lsi->sig),
+				(unsigned char *) lsi->sig))
+		return 1;
+	return 0;
+}
+
+const struct blkid_idinfo lsiraid_idinfo = {
+	.name		= "lsi_mega_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_lsiraid,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/luks.c b/libblkid/src/superblocks/luks.c
new file mode 100644
index 0000000..00696f2
--- /dev/null
+++ b/libblkid/src/superblocks/luks.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+#define LUKS_CIPHERNAME_L		32
+#define LUKS_CIPHERMODE_L		32
+#define LUKS_HASHSPEC_L			32
+#define LUKS_DIGESTSIZE			20
+#define LUKS_SALTSIZE			32
+#define LUKS_MAGIC_L			6
+#define UUID_STRING_L			40
+
+struct luks_phdr {
+	uint8_t		magic[LUKS_MAGIC_L];
+	uint16_t	version;
+	uint8_t		cipherName[LUKS_CIPHERNAME_L];
+	uint8_t		cipherMode[LUKS_CIPHERMODE_L];
+	uint8_t		hashSpec[LUKS_HASHSPEC_L];
+	uint32_t	payloadOffset;
+	uint32_t	keyBytes;
+	uint8_t		mkDigest[LUKS_DIGESTSIZE];
+	uint8_t		mkDigestSalt[LUKS_SALTSIZE];
+	uint32_t	mkDigestIterations;
+	uint8_t		uuid[UUID_STRING_L];
+} __attribute__((packed));
+
+static int probe_luks(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct luks_phdr *header;
+
+	header = blkid_probe_get_sb(pr, mag, struct luks_phdr);
+	if (header == NULL)
+		return errno ? -errno : 1;
+
+	blkid_probe_strncpy_uuid(pr, (unsigned char *) header->uuid,
+			sizeof(header->uuid));
+	blkid_probe_sprintf_version(pr, "%u", be16_to_cpu(header->version));
+	return 0;
+}
+
+const struct blkid_idinfo luks_idinfo =
+{
+	.name		= "crypto_LUKS",
+	.usage		= BLKID_USAGE_CRYPTO,
+	.probefunc	= probe_luks,
+	.magics		=
+	{
+		{ .magic = "LUKS\xba\xbe", .len = 6 },
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/lvm.c b/libblkid/src/superblocks/lvm.c
new file mode 100644
index 0000000..3c6df74
--- /dev/null
+++ b/libblkid/src/superblocks/lvm.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2012 Milan Broz <mbroz@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+#define LVM1_ID_LEN 128
+#define LVM2_ID_LEN 32
+
+struct lvm2_pv_label_header {
+	/* label_header */
+	uint8_t		id[8];		/* LABELONE */
+	uint64_t	sector_xl;	/* Sector number of this label */
+	uint32_t	crc_xl;		/* From next field to end of sector */
+	uint32_t	offset_xl;	/* Offset from start of struct to contents */
+	uint8_t		type[8];	/* LVM2 001 */
+	/* pv_header */
+	uint8_t		pv_uuid[LVM2_ID_LEN];
+} __attribute__ ((packed));
+
+struct lvm1_pv_label_header {
+	uint8_t id[2];			/* HM */
+	uint16_t version;		/* version 1 or 2 */
+	uint32_t _notused[10];		/* lvm1 internals */
+	uint8_t pv_uuid[LVM1_ID_LEN];
+} __attribute__ ((packed));
+
+#define LVM2_LABEL_SIZE 512
+static unsigned int lvm2_calc_crc(const void *buf, unsigned int size)
+{
+	static const unsigned int crctab[] = {
+		0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
+		0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
+		0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
+		0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
+	};
+	unsigned int i, crc = 0xf597a6cf;
+	const uint8_t *data = (const uint8_t *) buf;
+
+	for (i = 0; i < size; i++) {
+		crc ^= *data++;
+		crc = (crc >> 4) ^ crctab[crc & 0xf];
+		crc = (crc >> 4) ^ crctab[crc & 0xf];
+	}
+	return crc;
+}
+
+/* Length of real UUID is always LVM2_ID_LEN */
+static void format_lvm_uuid(char *dst_uuid, char *src_uuid)
+{
+	unsigned int i, b;
+
+	for (i = 0, b = 1; i < LVM2_ID_LEN; i++, b <<= 1) {
+		if (b & 0x4444440)
+			*dst_uuid++ = '-';
+		*dst_uuid++ = *src_uuid++;
+	}
+	*dst_uuid = '\0';
+}
+
+static int probe_lvm2(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	int sector = mag->kboff << 1;
+	struct lvm2_pv_label_header *label;
+	char uuid[LVM2_ID_LEN + 7];
+	unsigned char *buf;
+
+	buf = blkid_probe_get_buffer(pr,
+			mag->kboff << 10,
+			512 + sizeof(struct lvm2_pv_label_header));
+	if (!buf)
+		return errno ? -errno : 1;
+
+	/* buf is at 0k or 1k offset; find label inside */
+	if (memcmp(buf, "LABELONE", 8) == 0) {
+		label = (struct lvm2_pv_label_header *) buf;
+	} else if (memcmp(buf + 512, "LABELONE", 8) == 0) {
+		label = (struct lvm2_pv_label_header *)(buf + 512);
+		sector++;
+	} else {
+		return 1;
+	}
+
+	if (le64_to_cpu(label->sector_xl) != (unsigned) sector)
+		return 1;
+
+	if (!blkid_probe_verify_csum(
+		pr, lvm2_calc_crc(
+			&label->offset_xl, LVM2_LABEL_SIZE -
+			((char *) &label->offset_xl - (char *) label)),
+			le32_to_cpu(label->crc_xl)))
+		return 1;
+
+	format_lvm_uuid(uuid, (char *) label->pv_uuid);
+	blkid_probe_sprintf_uuid(pr, label->pv_uuid, sizeof(label->pv_uuid),
+			"%s", uuid);
+
+	/* the mag->magic is the same string as label->type,
+	 * but zero terminated */
+	blkid_probe_set_version(pr, mag->magic);
+
+	/* LVM (pvcreate) wipes begin of the device -- let's remember this
+	 * to resolve conflicts bettween LVM and partition tables, ...
+	 */
+	blkid_probe_set_wiper(pr, 0, 8 * 1024);
+
+	return 0;
+}
+
+static int probe_lvm1(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct lvm1_pv_label_header *label;
+	char uuid[LVM2_ID_LEN + 7];
+	unsigned int version;
+
+	label = blkid_probe_get_sb(pr, mag, struct lvm1_pv_label_header);
+	if (!label)
+		return errno ? -errno : 1;
+
+	version = le16_to_cpu(label->version);
+	if (version != 1 && version != 2)
+		return 1;
+
+	format_lvm_uuid(uuid, (char *) label->pv_uuid);
+	blkid_probe_sprintf_uuid(pr, label->pv_uuid, sizeof(label->pv_uuid),
+			"%s", uuid);
+
+	return 0;
+}
+
+struct verity_sb {
+	uint8_t  signature[8];	/* "verity\0\0" */
+	uint32_t version;	/* superblock version */
+	uint32_t hash_type;	/* 0 - Chrome OS, 1 - normal */
+	uint8_t  uuid[16];	/* UUID of hash device */
+	uint8_t  algorithm[32];/* hash algorithm name */
+	uint32_t data_block_size; /* data block in bytes */
+	uint32_t hash_block_size; /* hash block in bytes */
+	uint64_t data_blocks;	/* number of data blocks */
+	uint16_t salt_size;	/* salt size */
+	uint8_t  _pad1[6];
+	uint8_t  salt[256];	/* salt */
+	uint8_t  _pad2[168];
+} __attribute__((packed));
+
+static int probe_verity(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct verity_sb *sb;
+	unsigned int version;
+
+	sb = blkid_probe_get_sb(pr, mag, struct verity_sb);
+	if (sb == NULL)
+		return errno ? -errno : 1;
+
+	version = le32_to_cpu(sb->version);
+	if (version != 1)
+		return 1;
+
+	blkid_probe_set_uuid(pr, sb->uuid);
+	blkid_probe_sprintf_version(pr, "%u", version);
+	return 0;
+}
+
+/* NOTE: the original libblkid uses "lvm2pv" as a name */
+const struct blkid_idinfo lvm2_idinfo =
+{
+	.name		= "LVM2_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_lvm2,
+	.magics		=
+	{
+		{ .magic = "LVM2 001", .len = 8, .sboff = 0x218 },
+		{ .magic = "LVM2 001", .len = 8, .sboff = 0x018 },
+		{ .magic = "LVM2 001", .len = 8, .kboff = 1, .sboff = 0x018 },
+		{ .magic = "LVM2 001", .len = 8, .kboff = 1, .sboff = 0x218 },
+		{ NULL }
+	}
+};
+
+const struct blkid_idinfo lvm1_idinfo =
+{
+	.name		= "LVM1_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_lvm1,
+	.magics		=
+	{
+		{ .magic = "HM", .len = 2 },
+		{ NULL }
+	}
+};
+
+const struct blkid_idinfo snapcow_idinfo =
+{
+	.name		= "DM_snapshot_cow",
+	.usage		= BLKID_USAGE_OTHER,
+	.magics		=
+	{
+		{ .magic = "SnAp", .len = 4 },
+		{ NULL }
+	}
+};
+
+const struct blkid_idinfo verity_hash_idinfo =
+{
+	.name		= "DM_verity_hash",
+	.usage		= BLKID_USAGE_CRYPTO,
+	.probefunc	= probe_verity,
+	.magics		=
+	{
+		{ .magic = "verity\0\0", .len = 8 },
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/minix.c b/libblkid/src/superblocks/minix.c
new file mode 100644
index 0000000..9ea49fe
--- /dev/null
+++ b/libblkid/src/superblocks/minix.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008-2013 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <string.h>
+#include "superblocks.h"
+#include "minix.h"
+
+#define minix_swab16(doit, num)	((uint16_t) (doit ? swab16(num) : num))
+#define minix_swab32(doit, num)	((uint32_t) (doit ? swab32(num) : num))
+
+static int get_minix_version(const unsigned char *data, int *other_endian)
+{
+	struct minix_super_block *sb = (struct minix_super_block *) data;
+	struct minix3_super_block *sb3 = (struct minix3_super_block *) data;
+	int version = 0;
+
+	*other_endian = 0;
+
+	switch (sb->s_magic) {
+	case MINIX_SUPER_MAGIC:
+	case MINIX_SUPER_MAGIC2:
+		version = 1;
+		break;
+	case MINIX2_SUPER_MAGIC:
+	case MINIX2_SUPER_MAGIC2:
+		version = 2;
+		break;
+	default:
+		if (sb3->s_magic == MINIX3_SUPER_MAGIC)
+			version = 3;
+		break;
+	}
+
+	if (!version) {
+		*other_endian = 1;
+
+		switch (swab16(sb->s_magic)) {
+		case MINIX_SUPER_MAGIC:
+		case MINIX_SUPER_MAGIC2:
+			version = 1;
+			break;
+		case MINIX2_SUPER_MAGIC:
+		case MINIX2_SUPER_MAGIC2:
+			version = 2;
+			break;
+		default:
+			if (sb3->s_magic == MINIX3_SUPER_MAGIC)
+				version = 3;
+			break;
+		}
+	}
+	if (!version)
+		return -1;
+
+	DBG(LOWPROBE, ul_debug("minix version %d detected [%s]", version,
+#if defined(WORDS_BIGENDIAN)
+	*other_endian ? "LE" : "BE"
+#else
+	*other_endian ? "BE" : "LE"
+#endif
+	));
+	return version;
+}
+
+static int probe_minix(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	unsigned char *ext;
+	const unsigned char *data;
+	int version = 0, swabme = 0;
+
+	data = blkid_probe_get_buffer(pr, 1024,
+			max(sizeof(struct minix_super_block),
+			    sizeof(struct minix3_super_block)));
+	if (!data)
+		return errno ? -errno : 1;
+	version = get_minix_version(data, &swabme);
+	if (version < 1)
+		return 1;
+
+	if (version <= 2) {
+		struct minix_super_block *sb = (struct minix_super_block *) data;
+		int zones, ninodes, imaps, zmaps, firstz;
+
+		if (sb->s_imap_blocks == 0 || sb->s_zmap_blocks == 0)
+			return 1;
+
+		zones = version == 2 ? minix_swab32(swabme, sb->s_zones) :
+				       minix_swab16(swabme, sb->s_nzones);
+		ninodes = minix_swab16(swabme, sb->s_ninodes);
+		imaps   = minix_swab16(swabme, sb->s_imap_blocks);
+		zmaps   = minix_swab16(swabme, sb->s_zmap_blocks);
+		firstz  = minix_swab16(swabme, sb->s_firstdatazone);
+
+		/* sanity checks to be sure that the FS is really minix */
+		if (imaps * MINIX_BLOCK_SIZE * 8 < ninodes + 1)
+			return 1;
+		if (zmaps * MINIX_BLOCK_SIZE * 8 < zones - firstz + 1)
+			return 1;
+
+	} else if (version == 3) {
+		struct minix3_super_block *sb = (struct minix3_super_block *) data;
+
+		if (sb->s_imap_blocks == 0 || sb->s_zmap_blocks == 0)
+			return 1;
+	}
+
+	/* unfortunately, some parts of ext3 is sometimes possible to
+	 * interpreted as minix superblock. So check for extN magic
+	 * string. (For extN magic string and offsets see ext.c.)
+	 */
+	ext = blkid_probe_get_buffer(pr, 0x400 + 0x38, 2);
+	if (!ext)
+		return errno ? -errno : 1;
+	else if (memcmp(ext, "\123\357", 2) == 0)
+		return 1;
+
+	blkid_probe_sprintf_version(pr, "%d", version);
+	return 0;
+}
+
+const struct blkid_idinfo minix_idinfo =
+{
+	.name		= "minix",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_minix,
+	.magics		=
+	{
+		/* version 1 - LE */
+		{ .magic = "\177\023", .len = 2, .kboff = 1, .sboff = 0x10 },
+		{ .magic = "\217\023", .len = 2, .kboff = 1, .sboff = 0x10 },
+
+		/* version 1 - BE */
+		{ .magic = "\023\177", .len = 2, .kboff = 1, .sboff = 0x10 },
+		{ .magic = "\023\217", .len = 2, .kboff = 1, .sboff = 0x10 },
+
+		/* version 2 - LE */
+		{ .magic = "\150\044", .len = 2, .kboff = 1, .sboff = 0x10 },
+		{ .magic = "\170\044", .len = 2, .kboff = 1, .sboff = 0x10 },
+
+		/* version 2 - BE */
+		{ .magic = "\044\150", .len = 2, .kboff = 1, .sboff = 0x10 },
+		{ .magic = "\044\170", .len = 2, .kboff = 1, .sboff = 0x10 },
+
+		/* version 3 - LE */
+		{ .magic = "\132\115", .len = 2, .kboff = 1, .sboff = 0x18 },
+
+		/* version 3 - BE */
+		{ .magic = "\115\132", .len = 2, .kboff = 1, .sboff = 0x18 },
+
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/netware.c b/libblkid/src/superblocks/netware.c
new file mode 100644
index 0000000..af81cf5
--- /dev/null
+++ b/libblkid/src/superblocks/netware.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct netware_super_block {
+	uint8_t		SBH_Signature[4];
+	uint16_t	SBH_VersionMajor;
+	uint16_t	SBH_VersionMinor;
+	uint16_t	SBH_VersionMediaMajor;
+	uint16_t	SBH_VersionMediaMinor;
+	uint32_t	SBH_ItemsMoved;
+	uint8_t		SBH_InternalID[16];
+	uint32_t	SBH_PackedSize;
+	uint32_t	SBH_Checksum;
+	uint32_t	supersyncid;
+	int64_t		superlocation[4];
+	uint32_t	physSizeUsed;
+	uint32_t	sizeUsed;
+	uint32_t	superTimeStamp;
+	uint32_t	reserved0[1];
+	int64_t		SBH_LoggedPoolDataBlk;
+	int64_t		SBH_PoolDataBlk;
+	uint8_t		SBH_OldInternalID[16];
+	uint32_t	SBH_PoolToLVStartUTC;
+	uint32_t	SBH_PoolToLVEndUTC;
+	uint16_t	SBH_VersionMediaMajorCreate;
+	uint16_t	SBH_VersionMediaMinorCreate;
+	uint32_t	SBH_BlocksMoved;
+	uint32_t	SBH_TempBTSpBlk;
+	uint32_t	SBH_TempFTSpBlk;
+	uint32_t	SBH_TempFTSpBlk1;
+	uint32_t	SBH_TempFTSpBlk2;
+	uint32_t	nssMagicNumber;
+	uint32_t	poolClassID;
+	uint32_t	poolID;
+	uint32_t	createTime;
+	int64_t		SBH_LoggedVolumeDataBlk;
+	int64_t		SBH_VolumeDataBlk;
+	int64_t		SBH_SystemBeastBlkNum;
+	uint64_t	totalblocks;
+	uint16_t	SBH_Name[64];
+	uint8_t		SBH_VolumeID[16];
+	uint8_t		SBH_PoolID[16];
+	uint8_t		SBH_PoolInternalID[16];
+	uint64_t	SBH_Lsn;
+	uint32_t	SBH_SS_Enabled;
+	uint32_t	SBH_SS_CreateTime;
+	uint8_t		SBH_SS_OriginalPoolID[16];
+	uint8_t		SBH_SS_OriginalVolumeID[16];
+	uint8_t		SBH_SS_Guid[16];
+	uint16_t	SBH_SS_OriginalName[64];
+	uint32_t	reserved2[64-(2+46)];
+} __attribute__((__packed__));
+
+static int probe_netware(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct netware_super_block *nw;
+
+	nw = blkid_probe_get_sb(pr, mag, struct netware_super_block);
+	if (!nw)
+		return errno ? -errno : 1;
+
+	blkid_probe_set_uuid(pr, nw->SBH_PoolID);
+
+	blkid_probe_sprintf_version(pr, "%u.%02u",
+		 le16_to_cpu(nw->SBH_VersionMediaMajor),
+		 le16_to_cpu(nw->SBH_VersionMediaMinor));
+
+	return 0;
+}
+
+const struct blkid_idinfo netware_idinfo =
+{
+	.name		= "nss",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_netware,
+	.magics		=
+	{
+		{ .magic = "SPB5", .len = 4, .kboff = 4 },
+		{ NULL }
+	}
+};
+
+
diff --git a/libblkid/src/superblocks/nilfs.c b/libblkid/src/superblocks/nilfs.c
new file mode 100644
index 0000000..3f03901
--- /dev/null
+++ b/libblkid/src/superblocks/nilfs.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010 by Jiro SEKIBA <jir@unicus.jp>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License
+ */
+#include <stddef.h>
+#include <string.h>
+
+#include "superblocks.h"
+#include "crc32.h"
+
+struct nilfs_super_block {
+	uint32_t	s_rev_level;
+	uint16_t	s_minor_rev_level;
+	uint16_t	s_magic;
+
+	uint16_t	s_bytes;
+
+	uint16_t	s_flags;
+	uint32_t	s_crc_seed;
+	uint32_t	s_sum;
+
+	uint32_t	s_log_block_size;
+
+	uint64_t	s_nsegments;
+	uint64_t	s_dev_size;
+	uint64_t	s_first_data_block;
+	uint32_t	s_blocks_per_segment;
+	uint32_t	s_r_segments_percentage;
+
+	uint64_t	s_last_cno;
+	uint64_t	s_last_pseg;
+	uint64_t	s_last_seq;
+	uint64_t	s_free_blocks_count;
+
+	uint64_t	s_ctime;
+
+	uint64_t	s_mtime;
+	uint64_t	s_wtime;
+	uint16_t	s_mnt_count;
+	uint16_t	s_max_mnt_count;
+	uint16_t	s_state;
+	uint16_t	s_errors;
+	uint64_t	s_lastcheck;
+
+	uint32_t	s_checkinterval;
+	uint32_t	s_creator_os;
+	uint16_t	s_def_resuid;
+	uint16_t	s_def_resgid;
+	uint32_t	s_first_ino;
+
+	uint16_t	s_inode_size;
+	uint16_t	s_dat_entry_size;
+	uint16_t	s_checkpoint_size;
+	uint16_t	s_segment_usage_size;
+
+	uint8_t		s_uuid[16];
+	char		s_volume_name[80];
+
+	uint32_t	s_c_interval;
+	uint32_t	s_c_block_max;
+	uint32_t	s_reserved[192];
+};
+
+#define NILFS_SB_MAGIC		0x3434
+#define NILFS_SB_OFFSET		0x400
+
+static int nilfs_valid_sb(blkid_probe pr, struct nilfs_super_block *sb)
+{
+	static unsigned char sum[4];
+	const int sumoff = offsetof(struct nilfs_super_block, s_sum);
+	size_t bytes;
+	uint32_t crc;
+
+	if (!sb || le16_to_cpu(sb->s_magic) != NILFS_SB_MAGIC)
+		return 0;
+
+	bytes = le16_to_cpu(sb->s_bytes);
+	crc = crc32(le32_to_cpu(sb->s_crc_seed), (unsigned char *)sb, sumoff);
+	crc = crc32(crc, sum, 4);
+	crc = crc32(crc, (unsigned char *)sb + sumoff + 4, bytes - sumoff - 4);
+
+	return blkid_probe_verify_csum(pr, crc, le32_to_cpu(sb->s_sum));
+}
+
+static int probe_nilfs2(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct nilfs_super_block *sb, *sbp, *sbb;
+	int valid[2], swp = 0;
+
+	/* primary */
+	sbp = (struct nilfs_super_block *) blkid_probe_get_buffer(
+			pr, NILFS_SB_OFFSET, sizeof(struct nilfs_super_block));
+	if (!sbp)
+		return errno ? -errno : 1;
+	/* backup */
+	sbb = (struct nilfs_super_block *) blkid_probe_get_buffer(
+			pr, ((pr->size / 0x200) - 8) * 0x200, sizeof(struct nilfs_super_block));
+	if (!sbb)
+		return errno ? -errno : 1;
+
+	/*
+	 * Compare two super blocks and set 1 in swp if the secondary
+	 * super block is valid and newer.  Otherwise, set 0 in swp.
+	 */
+	valid[0] = nilfs_valid_sb(pr, sbp);
+	valid[1] = nilfs_valid_sb(pr, sbb);
+	if (!valid[0] && !valid[1])
+		return 1;
+
+	swp = valid[1] && (!valid[0] ||
+			   le64_to_cpu(sbp->s_last_cno) >
+			   le64_to_cpu(sbb->s_last_cno));
+	sb = swp ? sbb : sbp;
+
+	DBG(LOWPROBE, ul_debug("nilfs2: primary=%d, backup=%d, swap=%d",
+				valid[0], valid[1], swp));
+
+	if (strlen(sb->s_volume_name))
+		blkid_probe_set_label(pr, (unsigned char *) sb->s_volume_name,
+				      sizeof(sb->s_volume_name));
+
+	blkid_probe_set_uuid(pr, sb->s_uuid);
+	blkid_probe_sprintf_version(pr, "%u", le32_to_cpu(sb->s_rev_level));
+
+	return 0;
+}
+
+const struct blkid_idinfo nilfs2_idinfo =
+{
+	.name		= "nilfs2",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_nilfs2,
+	/* default min.size is 128MiB, but 1MiB for "mkfs.nilfs2 -b 1024 -B 16" */
+	.minsz		= (1024 * 1024),
+	.magics		= BLKID_NONE_MAGIC
+};
diff --git a/libblkid/src/superblocks/ntfs.c b/libblkid/src/superblocks/ntfs.c
new file mode 100644
index 0000000..8ff9ccd
--- /dev/null
+++ b/libblkid/src/superblocks/ntfs.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "superblocks.h"
+
+struct ntfs_bios_parameters {
+	uint16_t	sector_size;	/* Size of a sector in bytes. */
+	uint8_t		sectors_per_cluster;	/* Size of a cluster in sectors. */
+	uint16_t	reserved_sectors;	/* zero */
+	uint8_t		fats;			/* zero */
+	uint16_t	root_entries;		/* zero */
+	uint16_t	sectors;		/* zero */
+	uint8_t		media_type;		/* 0xf8 = hard disk */
+	uint16_t	sectors_per_fat;	/* zero */
+	uint16_t	sectors_per_track;	/* irrelevant */
+	uint16_t	heads;			/* irrelevant */
+	uint32_t	hidden_sectors;		/* zero */
+	uint32_t	large_sectors;		/* zero */
+} __attribute__ ((__packed__));
+
+struct ntfs_super_block {
+	uint8_t		jump[3];
+	uint8_t		oem_id[8];	/* magic string */
+
+	struct ntfs_bios_parameters	bpb;
+
+	uint16_t	unused[2];
+	uint64_t	number_of_sectors;
+	uint64_t	mft_cluster_location;
+	uint64_t	mft_mirror_cluster_location;
+	int8_t		clusters_per_mft_record;
+	uint8_t		reserved1[3];
+	int8_t		cluster_per_index_record;
+	uint8_t		reserved2[3];
+	uint64_t	volume_serial;
+	uint32_t	checksum;
+} __attribute__((packed));
+
+struct master_file_table_record {
+	uint32_t	magic;
+	uint16_t	usa_ofs;
+	uint16_t	usa_count;
+	uint64_t	lsn;
+	uint16_t	sequence_number;
+	uint16_t	link_count;
+	uint16_t	attrs_offset;
+	uint16_t	flags;
+	uint32_t	bytes_in_use;
+	uint32_t	bytes_allocated;
+} __attribute__((__packed__));
+
+struct file_attribute {
+	uint32_t	type;
+	uint32_t	len;
+	uint8_t		non_resident;
+	uint8_t		name_len;
+	uint16_t	name_offset;
+	uint16_t	flags;
+	uint16_t	instance;
+	uint32_t	value_len;
+	uint16_t	value_offset;
+} __attribute__((__packed__));
+
+#define MFT_RECORD_VOLUME	3
+#define NTFS_MAX_CLUSTER_SIZE	(64 * 1024)
+
+enum {
+	MFT_RECORD_ATTR_VOLUME_NAME		= 0x60,
+	MFT_RECORD_ATTR_END			= 0xffffffff
+};
+
+static int probe_ntfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct ntfs_super_block *ns;
+	struct master_file_table_record *mft;
+
+	uint32_t sectors_per_cluster, mft_record_size, attr_off;
+	uint16_t sector_size;
+	uint64_t nr_clusters, off;
+	unsigned char *buf_mft;
+
+	ns = blkid_probe_get_sb(pr, mag, struct ntfs_super_block);
+	if (!ns)
+		return errno ? -errno : 1;
+
+	/*
+	 * Check bios parameters block
+	 */
+	sector_size = le16_to_cpu(ns->bpb.sector_size);
+	sectors_per_cluster = ns->bpb.sectors_per_cluster;
+
+	if (sector_size < 256 || sector_size > 4096)
+		return 1;
+
+	switch (sectors_per_cluster) {
+	case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128:
+		break;
+	default:
+		return 1;
+	}
+
+	if ((uint16_t) le16_to_cpu(ns->bpb.sector_size) *
+			ns->bpb.sectors_per_cluster > NTFS_MAX_CLUSTER_SIZE)
+		return 1;
+
+	/* Unused fields must be zero */
+	if (le16_to_cpu(ns->bpb.reserved_sectors)
+	    || le16_to_cpu(ns->bpb.root_entries)
+	    || le16_to_cpu(ns->bpb.sectors)
+	    || le16_to_cpu(ns->bpb.sectors_per_fat)
+	    || le32_to_cpu(ns->bpb.large_sectors)
+	    || ns->bpb.fats)
+		return 1;
+
+	if ((uint8_t) ns->clusters_per_mft_record < 0xe1
+	    || (uint8_t) ns->clusters_per_mft_record > 0xf7) {
+
+		switch (ns->clusters_per_mft_record) {
+		case 1: case 2: case 4: case 8: case 16: case 32: case 64:
+			break;
+		default:
+			return 1;
+		}
+	}
+
+	if (ns->clusters_per_mft_record > 0)
+		mft_record_size = ns->clusters_per_mft_record *
+				  sectors_per_cluster * sector_size;
+	else
+		mft_record_size = 1 << (0 - ns->clusters_per_mft_record);
+
+	nr_clusters = le64_to_cpu(ns->number_of_sectors) / sectors_per_cluster;
+
+	if ((le64_to_cpu(ns->mft_cluster_location) > nr_clusters) ||
+	    (le64_to_cpu(ns->mft_mirror_cluster_location) > nr_clusters))
+		return 1;
+
+
+	off = le64_to_cpu(ns->mft_cluster_location) * sector_size *
+		sectors_per_cluster;
+
+	DBG(LOWPROBE, ul_debug("NTFS: sector_size=%d, mft_record_size=%d, "
+			"sectors_per_cluster=%d, nr_clusters=%ju "
+			"cluster_offset=%jd",
+			(int) sector_size, mft_record_size,
+			sectors_per_cluster, nr_clusters,
+			off));
+
+	buf_mft = blkid_probe_get_buffer(pr, off, mft_record_size);
+	if (!buf_mft)
+		return errno ? -errno : 1;
+
+	if (memcmp(buf_mft, "FILE", 4))
+		return 1;
+
+	off += MFT_RECORD_VOLUME * mft_record_size;
+
+	buf_mft = blkid_probe_get_buffer(pr, off, mft_record_size);
+	if (!buf_mft)
+		return errno ? -errno : 1;
+
+	if (memcmp(buf_mft, "FILE", 4))
+		return 1;
+
+	mft = (struct master_file_table_record *) buf_mft;
+	attr_off = le16_to_cpu(mft->attrs_offset);
+
+	while (attr_off < mft_record_size &&
+	       attr_off <= le32_to_cpu(mft->bytes_allocated)) {
+
+		uint32_t attr_len;
+		struct file_attribute *attr;
+
+		attr = (struct file_attribute *) (buf_mft + attr_off);
+		attr_len = le32_to_cpu(attr->len);
+		if (!attr_len)
+			break;
+
+		if (le32_to_cpu(attr->type) == MFT_RECORD_ATTR_END)
+			break;
+		if (le32_to_cpu(attr->type) == MFT_RECORD_ATTR_VOLUME_NAME) {
+			unsigned int val_off = le16_to_cpu(attr->value_offset);
+			unsigned int val_len = le32_to_cpu(attr->value_len);
+			unsigned char *val = ((uint8_t *) attr) + val_off;
+
+			blkid_probe_set_utf8label(pr, val, val_len, BLKID_ENC_UTF16LE);
+			break;
+		}
+
+		if (UINT_MAX - attr_len < attr_off)
+			break;
+		attr_off += attr_len;
+	}
+
+	blkid_probe_sprintf_uuid(pr,
+			(unsigned char *) &ns->volume_serial,
+			sizeof(ns->volume_serial),
+			"%016" PRIX64, le64_to_cpu(ns->volume_serial));
+	return 0;
+}
+
+
+const struct blkid_idinfo ntfs_idinfo =
+{
+	.name		= "ntfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_ntfs,
+	.magics		=
+	{
+		{ .magic = "NTFS    ", .len = 8, .sboff = 3 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/nvidia_raid.c b/libblkid/src/superblocks/nvidia_raid.c
new file mode 100644
index 0000000..5db8ec2
--- /dev/null
+++ b/libblkid/src/superblocks/nvidia_raid.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct nv_metadata {
+	uint8_t		vendor[8];
+	uint32_t	size;
+	uint32_t	chksum;
+	uint16_t	version;
+} __attribute__((packed));
+
+#define NVIDIA_SIGNATURE		"NVIDIA"
+
+static int probe_nvraid(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	uint64_t off;
+	struct nv_metadata *nv;
+
+	if (pr->size < 0x10000)
+		return 1;
+	if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+		return 1;
+
+	off = ((pr->size / 0x200) - 2) * 0x200;
+	nv = (struct nv_metadata *)
+		blkid_probe_get_buffer(pr,
+				off,
+				sizeof(struct nv_metadata));
+	if (!nv)
+		return errno ? -errno : 1;
+
+	if (memcmp(nv->vendor, NVIDIA_SIGNATURE, sizeof(NVIDIA_SIGNATURE)-1) != 0)
+		return 1;
+	if (blkid_probe_sprintf_version(pr, "%u", le16_to_cpu(nv->version)) != 0)
+		return 1;
+	if (blkid_probe_set_magic(pr, off, sizeof(nv->vendor),
+				(unsigned char *) nv->vendor))
+		return 1;
+	return 0;
+}
+
+const struct blkid_idinfo nvraid_idinfo = {
+	.name		= "nvidia_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_nvraid,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/ocfs.c b/libblkid/src/superblocks/ocfs.c
new file mode 100644
index 0000000..3fe199d
--- /dev/null
+++ b/libblkid/src/superblocks/ocfs.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 1999, 2001 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct ocfs_volume_header {
+	unsigned char	minor_version[4];
+	unsigned char	major_version[4];
+	unsigned char	signature[128];
+	char		mount[128];
+	unsigned char   mount_len[2];
+} __attribute__((packed));
+
+struct ocfs_volume_label {
+	unsigned char	disk_lock[48];
+	char		label[64];
+	unsigned char	label_len[2];
+	unsigned char   vol_id[16];
+	unsigned char   vol_id_len[2];
+} __attribute__((packed));
+
+#define ocfsmajor(o) ( (uint32_t) o.major_version[0] \
+                   + (((uint32_t) o.major_version[1]) << 8) \
+                   + (((uint32_t) o.major_version[2]) << 16) \
+                   + (((uint32_t) o.major_version[3]) << 24))
+
+#define ocfsminor(o) ( (uint32_t) o.minor_version[0] \
+                   + (((uint32_t) o.minor_version[1]) << 8) \
+                   + (((uint32_t) o.minor_version[2]) << 16) \
+                   + (((uint32_t) o.minor_version[3]) << 24))
+
+#define ocfslabellen(o)	((uint32_t)o.label_len[0] + (((uint32_t) o.label_len[1]) << 8))
+#define ocfsmountlen(o)	((uint32_t)o.mount_len[0] + (((uint32_t) o.mount_len[1]) << 8))
+
+struct ocfs2_super_block {
+	uint8_t		i_signature[8];
+	uint32_t	i_generation;
+	int16_t		i_suballoc_slot;
+	uint16_t	i_suballoc_bit;
+	uint32_t	i_reserved0;
+	uint32_t	i_clusters;
+	uint32_t	i_uid;
+	uint32_t	i_gid;
+	uint64_t	i_size;
+	uint16_t	i_mode;
+	uint16_t	i_links_count;
+	uint32_t	i_flags;
+	uint64_t	i_atime;
+	uint64_t	i_ctime;
+	uint64_t	i_mtime;
+	uint64_t	i_dtime;
+	uint64_t	i_blkno;
+	uint64_t	i_last_eb_blk;
+	uint32_t	i_fs_generation;
+	uint32_t	i_atime_nsec;
+	uint32_t	i_ctime_nsec;
+	uint32_t	i_mtime_nsec;
+	uint64_t	i_reserved1[9];
+	uint64_t	i_pad1;
+	uint16_t	s_major_rev_level;
+	uint16_t	s_minor_rev_level;
+	uint16_t	s_mnt_count;
+	int16_t		s_max_mnt_count;
+	uint16_t	s_state;
+	uint16_t	s_errors;
+	uint32_t	s_checkinterval;
+	uint64_t	s_lastcheck;
+	uint32_t	s_creator_os;
+	uint32_t	s_feature_compat;
+	uint32_t	s_feature_incompat;
+	uint32_t	s_feature_ro_compat;
+	uint64_t	s_root_blkno;
+	uint64_t	s_system_dir_blkno;
+	uint32_t	s_blocksize_bits;
+	uint32_t	s_clustersize_bits;
+	uint16_t	s_max_slots;
+	uint16_t	s_reserved1;
+	uint32_t	s_reserved2;
+	uint64_t	s_first_cluster_group;
+	uint8_t		s_label[64];
+	uint8_t		s_uuid[16];
+} __attribute__((packed));
+
+struct oracle_asm_disk_label {
+	char dummy[32];
+	char dl_tag[8];
+	char dl_id[24];
+} __attribute__((packed));
+
+static int probe_ocfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	unsigned char *buf;
+	struct ocfs_volume_header ovh;
+	struct ocfs_volume_label ovl;
+	uint32_t maj, min;
+
+	/* header */
+	buf = blkid_probe_get_buffer(pr, mag->kboff << 10,
+			sizeof(struct ocfs_volume_header));
+	if (!buf)
+		return errno ? -errno : 1;
+	memcpy(&ovh, buf, sizeof(ovh));
+
+	/* label */
+	buf = blkid_probe_get_buffer(pr, (mag->kboff << 10) + 512,
+			sizeof(struct ocfs_volume_label));
+	if (!buf)
+		return errno ? -errno : 1;
+	memcpy(&ovl, buf, sizeof(ovl));
+
+	maj = ocfsmajor(ovh);
+	min = ocfsminor(ovh);
+
+	if (maj == 1)
+		blkid_probe_set_value(pr, "SEC_TYPE",
+				(unsigned char *) "ocfs1", sizeof("ocfs1"));
+	else if (maj >= 9)
+		blkid_probe_set_value(pr, "SEC_TYPE",
+				(unsigned char *) "ntocfs", sizeof("ntocfs"));
+
+	blkid_probe_set_label(pr, (unsigned char *) ovl.label,
+				ocfslabellen(ovl));
+	blkid_probe_set_value(pr, "MOUNT", (unsigned char *) ovh.mount,
+				ocfsmountlen(ovh));
+	blkid_probe_set_uuid(pr, ovl.vol_id);
+	blkid_probe_sprintf_version(pr, "%u.%u", maj, min);
+	return 0;
+}
+
+static int probe_ocfs2(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct ocfs2_super_block *osb;
+
+	osb = blkid_probe_get_sb(pr, mag, struct ocfs2_super_block);
+	if (!osb)
+		return errno ? -errno : 1;
+
+	blkid_probe_set_label(pr, (unsigned char *) osb->s_label, sizeof(osb->s_label));
+	blkid_probe_set_uuid(pr, osb->s_uuid);
+
+	blkid_probe_sprintf_version(pr, "%u.%u",
+		le16_to_cpu(osb->s_major_rev_level),
+		le16_to_cpu(osb->s_minor_rev_level));
+
+	return 0;
+}
+
+static int probe_oracleasm(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct oracle_asm_disk_label *dl;
+
+	dl = blkid_probe_get_sb(pr, mag, struct oracle_asm_disk_label);
+	if (!dl)
+		return errno ? -errno : 1;
+
+	blkid_probe_set_label(pr, (unsigned char *) dl->dl_id, sizeof(dl->dl_id));
+	return 0;
+}
+
+
+const struct blkid_idinfo ocfs_idinfo =
+{
+	.name		= "ocfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_ocfs,
+	.minsz		= 14000 * 1024,
+	.magics		=
+	{
+		{ .magic = "OracleCFS", .len = 9, .kboff = 8 },
+		{ NULL }
+	}
+};
+
+const struct blkid_idinfo ocfs2_idinfo =
+{
+	.name		= "ocfs2",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_ocfs2,
+	.minsz		= 14000 * 1024,
+	.magics		=
+	{
+		{ .magic = "OCFSV2", .len = 6, .kboff = 1 },
+		{ .magic = "OCFSV2", .len = 6, .kboff = 2 },
+		{ .magic = "OCFSV2", .len = 6, .kboff = 4 },
+		{ .magic = "OCFSV2", .len = 6, .kboff = 8 },
+		{ NULL }
+	}
+};
+
+/* Oracle ASM (Automatic Storage Management) */
+const struct blkid_idinfo oracleasm_idinfo =
+{
+	.name		= "oracleasm",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_oracleasm,
+	.magics		=
+	{
+		{ .magic = "ORCLDISK", .len = 8, .sboff = 32 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/promise_raid.c b/libblkid/src/superblocks/promise_raid.c
new file mode 100644
index 0000000..678460a
--- /dev/null
+++ b/libblkid/src/superblocks/promise_raid.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct promise_metadata {
+	uint8_t	sig[24];
+};
+
+#define PDC_CONFIG_OFF		0x1200
+#define PDC_SIGNATURE		"Promise Technology, Inc."
+
+static int probe_pdcraid(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	unsigned int i;
+	static unsigned int sectors[] = {
+	  63, 255, 256, 16, 399, 591, 675, 735, 911, 974, 991, 951, 3087, 0
+	};
+
+	if (pr->size < 0x40000)
+		return 1;
+	if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+		return 1;
+
+	for (i = 0; sectors[i] != 0; i++) {
+		uint64_t off;
+		struct promise_metadata *pdc;
+
+		off = ((pr->size / 0x200) - sectors[i]) * 0x200;
+		pdc = (struct promise_metadata *)
+				blkid_probe_get_buffer(pr,
+					off,
+					sizeof(struct promise_metadata));
+		if (!pdc)
+			return errno ? -errno : 1;
+
+		if (memcmp(pdc->sig, PDC_SIGNATURE,
+				sizeof(PDC_SIGNATURE) - 1) == 0) {
+
+			if (blkid_probe_set_magic(pr, off, sizeof(pdc->sig),
+						(unsigned char *) pdc->sig))
+				return 1;
+			return 0;
+		}
+	}
+	return 1;
+}
+
+const struct blkid_idinfo pdcraid_idinfo = {
+	.name		= "promise_fasttrack_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_pdcraid,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/refs.c b/libblkid/src/superblocks/refs.c
new file mode 100644
index 0000000..ea81f20
--- /dev/null
+++ b/libblkid/src/superblocks/refs.c
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "superblocks.h"
+
+
+const struct blkid_idinfo refs_idinfo =
+{
+	.name		= "ReFS",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.magics		=
+	{
+		{ .magic = "\000\000\000ReFS\000", .len = 8 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/reiserfs.c b/libblkid/src/superblocks/reiserfs.c
new file mode 100644
index 0000000..edbaaa9
--- /dev/null
+++ b/libblkid/src/superblocks/reiserfs.c
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 1999, 2001 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct reiserfs_super_block {
+	uint32_t	rs_blocks_count;
+	uint32_t	rs_free_blocks;
+	uint32_t	rs_root_block;
+	uint32_t	rs_journal_block;
+	uint32_t	rs_journal_dev;
+	uint32_t	rs_orig_journal_size;
+	uint32_t	rs_dummy2[5];
+	uint16_t	rs_blocksize;
+	uint16_t	rs_dummy3[3];
+	unsigned char	rs_magic[12];
+	uint32_t	rs_dummy4[5];
+	unsigned char	rs_uuid[16];
+	char		rs_label[16];
+} __attribute__((packed));
+
+struct reiser4_super_block {
+	unsigned char	rs4_magic[16];
+	uint16_t	rs4_dummy[2];
+	unsigned char	rs4_uuid[16];
+	unsigned char	rs4_label[16];
+	uint64_t	rs4_dummy2;
+} __attribute__((packed));
+
+static int probe_reiser(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct reiserfs_super_block *rs;
+	unsigned int blocksize;
+
+	rs = blkid_probe_get_sb(pr, mag, struct reiserfs_super_block);
+	if (!rs)
+		return errno ? -errno : 1;
+
+	blocksize = le16_to_cpu(rs->rs_blocksize);
+
+	/* The blocksize must be at least 512B */
+	if ((blocksize >> 9) == 0)
+		return 1;
+
+	/* If the superblock is inside the journal, we have the wrong one */
+	if (mag->kboff / (blocksize >> 9) > le32_to_cpu(rs->rs_journal_block) / 2)
+		return 1;
+
+	/* LABEL/UUID are only valid for later versions of Reiserfs v3.6. */
+	if (mag->magic[6] == '2' || mag->magic[6] == '3') {
+		if (*rs->rs_label)
+			blkid_probe_set_label(pr,
+					(unsigned char *) rs->rs_label,
+					sizeof(rs->rs_label));
+		blkid_probe_set_uuid(pr, rs->rs_uuid);
+	}
+
+	if (mag->magic[6] == '3')
+		blkid_probe_set_version(pr, "JR");
+	else if (mag->magic[6] == '2')
+		blkid_probe_set_version(pr, "3.6");
+	else
+		blkid_probe_set_version(pr, "3.5");
+
+	return 0;
+}
+
+static int probe_reiser4(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct reiser4_super_block *rs4;
+
+	rs4 = blkid_probe_get_sb(pr, mag, struct reiser4_super_block);
+	if (!rs4)
+		return errno ? -errno : 1;
+
+	if (*rs4->rs4_label)
+		blkid_probe_set_label(pr, rs4->rs4_label, sizeof(rs4->rs4_label));
+	blkid_probe_set_uuid(pr, rs4->rs4_uuid);
+	blkid_probe_set_version(pr, "4");
+
+	return 0;
+}
+
+
+const struct blkid_idinfo reiser_idinfo =
+{
+	.name		= "reiserfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_reiser,
+	.minsz		= 128 * 1024,
+	.magics		=
+	{
+		{ .magic = "ReIsErFs",  .len = 8, .kboff = 8,  .sboff = 0x34 },
+		{ .magic = "ReIsEr2Fs", .len = 9, .kboff = 64, .sboff = 0x34 },
+		{ .magic = "ReIsEr3Fs", .len = 9, .kboff = 64, .sboff = 0x34 },
+		{ .magic = "ReIsErFs",  .len = 8, .kboff = 64, .sboff = 0x34 },
+		{ .magic = "ReIsErFs",  .len = 8, .kboff =  8, .sboff = 20   },
+		{ NULL }
+	}
+};
+
+const struct blkid_idinfo reiser4_idinfo =
+{
+	.name		= "reiser4",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_reiser4,
+	.minsz		= 128 * 1024,
+	.magics		=
+	{
+		{ .magic = "ReIsEr4", .len = 7, .kboff = 64 },
+		{ NULL }
+	}
+};
+
+
+
+
diff --git a/libblkid/src/superblocks/romfs.c b/libblkid/src/superblocks/romfs.c
new file mode 100644
index 0000000..8e63c10
--- /dev/null
+++ b/libblkid/src/superblocks/romfs.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 1999, 2001 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct romfs_super_block {
+	unsigned char	ros_magic[8];
+	uint32_t	ros_dummy1[2];
+	unsigned char	ros_volume[16];
+} __attribute__((packed));
+
+static int probe_romfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct romfs_super_block *ros;
+
+	ros = blkid_probe_get_sb(pr, mag, struct romfs_super_block);
+	if (!ros)
+		return errno ? -errno : 1;
+
+	if (strlen((char *) ros->ros_volume))
+		blkid_probe_set_label(pr, ros->ros_volume,
+				sizeof(ros->ros_volume));
+	return 0;
+}
+
+const struct blkid_idinfo romfs_idinfo =
+{
+	.name		= "romfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_romfs,
+	.magics		=
+	{
+		{ .magic = "-rom1fs-", .len = 8 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/silicon_raid.c b/libblkid/src/superblocks/silicon_raid.c
new file mode 100644
index 0000000..edbefbc
--- /dev/null
+++ b/libblkid/src/superblocks/silicon_raid.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#include "superblocks.h"
+
+struct silicon_metadata {
+	uint8_t		unknown0[0x2E];
+	uint8_t		ascii_version[0x36 - 0x2E];
+	int8_t		diskname[0x56 - 0x36];
+	int8_t		unknown1[0x60 - 0x56];
+	uint32_t	magic;
+	int8_t		unknown1a[0x6C - 0x64];
+	uint32_t	array_sectors_low;
+	uint32_t	array_sectors_high;
+	int8_t		unknown2[0x78 - 0x74];
+	uint32_t	thisdisk_sectors;
+	int8_t		unknown3[0x100 - 0x7C];
+	int8_t		unknown4[0x104 - 0x100];
+	uint16_t	product_id;
+	uint16_t	vendor_id;
+	uint16_t	minor_ver;
+	uint16_t	major_ver;
+	uint8_t		seconds;
+	uint8_t		minutes;
+	uint8_t		hour;
+	uint8_t		day;
+	uint8_t		month;
+	uint8_t		year;
+	uint16_t	raid0_stride;
+	int8_t		unknown6[0x116 - 0x114];
+	uint8_t		disk_number;
+	uint8_t		type;			/* SILICON_TYPE_* */
+	int8_t		drives_per_striped_set;
+	int8_t		striped_set_number;
+	int8_t		drives_per_mirrored_set;
+	int8_t		mirrored_set_number;
+	uint32_t	rebuild_ptr_low;
+	uint32_t	rebuild_ptr_high;
+	uint32_t	incarnation_no;
+	uint8_t		member_status;
+	uint8_t		mirrored_set_state;	/* SILICON_MIRROR_* */
+	uint8_t		reported_device_location;
+	uint8_t		idechannel;
+	uint8_t		auto_rebuild;
+	uint8_t		unknown8;
+	uint8_t		text_type[0x13E - 0x12E];
+	uint16_t	checksum1;
+	int8_t		assumed_zeros[0x1FE - 0x140];
+	uint16_t	checksum2;
+} __attribute__((packed));
+
+#define SILICON_MAGIC		0x2F000000
+
+static uint16_t silraid_checksum(struct silicon_metadata *sil)
+{
+	int sum = 0;
+	unsigned short count = offsetof(struct silicon_metadata, checksum1) / 2;
+	uint16_t *p = (uint16_t *) sil;
+
+	while (count--) {
+		uint16_t x = *p++;
+		sum += le16_to_cpu(x);
+	}
+
+	return (-sum & 0xFFFF);
+}
+
+static int probe_silraid(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	uint64_t off;
+	struct silicon_metadata *sil;
+
+	if (pr->size < 0x10000)
+		return 1;
+	if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+		return 1;
+
+	off = ((pr->size / 0x200) - 1) * 0x200;
+
+	sil = (struct silicon_metadata *)
+			blkid_probe_get_buffer(pr, off,
+				sizeof(struct silicon_metadata));
+	if (!sil)
+		return errno ? -errno : 1;
+
+	if (le32_to_cpu(sil->magic) != SILICON_MAGIC)
+		return 1;
+	if (sil->disk_number >= 8)
+		return 1;
+	if (!blkid_probe_verify_csum(pr, silraid_checksum(sil), le16_to_cpu(sil->checksum1)))
+		return 1;
+
+	if (blkid_probe_sprintf_version(pr, "%u.%u",
+				le16_to_cpu(sil->major_ver),
+				le16_to_cpu(sil->minor_ver)) != 0)
+		return 1;
+
+	if (blkid_probe_set_magic(pr,
+			off + offsetof(struct silicon_metadata, magic),
+			sizeof(sil->magic),
+			(unsigned char *) &sil->magic))
+		return 1;
+	return 0;
+}
+
+const struct blkid_idinfo silraid_idinfo = {
+	.name		= "silicon_medley_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_silraid,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/squashfs.c b/libblkid/src/superblocks/squashfs.c
new file mode 100644
index 0000000..8ed2838
--- /dev/null
+++ b/libblkid/src/superblocks/squashfs.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "bitops.h"	/* swab16() */
+#include "superblocks.h"
+
+struct sqsh_super_block {
+	uint32_t	s_magic;
+	uint32_t	inodes;
+	uint32_t	bytes_used_2;
+	uint32_t	uid_start_2;
+	uint32_t	guid_start_2;
+	uint32_t	inode_table_start_2;
+	uint32_t	directory_table_start_2;
+	uint16_t	s_major;
+	uint16_t	s_minor;
+} __attribute__((packed));
+
+static int probe_squashfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct sqsh_super_block *sq;
+	uint16_t major;
+	uint16_t minor;
+
+	sq = blkid_probe_get_sb(pr, mag, struct sqsh_super_block);
+	if (!sq)
+		return errno ? -errno : 1;
+
+	major = le16_to_cpu(sq->s_major);
+	minor = le16_to_cpu(sq->s_minor);
+	if (major < 4)
+		return 1;
+
+	blkid_probe_sprintf_version(pr, "%u.%u", major, minor);
+
+	return 0;
+}
+
+static int probe_squashfs3(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct sqsh_super_block *sq;
+	uint16_t major;
+	uint16_t minor;
+
+	sq = blkid_probe_get_sb(pr, mag, struct sqsh_super_block);
+	if (!sq)
+		return errno ? -errno : 1;
+
+	if (strcmp(mag->magic, "sqsh") == 0) {
+		major = be16_to_cpu(sq->s_major);
+		minor = be16_to_cpu(sq->s_minor);
+	} else {
+		major = le16_to_cpu(sq->s_major);
+		minor = le16_to_cpu(sq->s_minor);
+	}
+
+	if (major > 3)
+		return 1;
+
+	blkid_probe_sprintf_version(pr, "%u.%u", major, minor);
+
+	return 0;
+}
+
+const struct blkid_idinfo squashfs_idinfo =
+{
+	.name		= "squashfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_squashfs,
+	.magics		=
+	{
+		{ .magic = "hsqs", .len = 4 },
+		{ NULL }
+	}
+};
+
+const struct blkid_idinfo squashfs3_idinfo =
+{
+	.name		= "squashfs3",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_squashfs3,
+	.magics		=
+	{
+		{ .magic = "sqsh", .len = 4 }, /* big endian */
+		{ .magic = "hsqs", .len = 4 }, /* little endian */
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/superblocks.c b/libblkid/src/superblocks/superblocks.c
new file mode 100644
index 0000000..9d0d2cb
--- /dev/null
+++ b/libblkid/src/superblocks/superblocks.c
@@ -0,0 +1,814 @@
+/*
+ * superblocks.c - reads information from filesystem and raid superblocks
+ *
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdarg.h>
+
+#include "superblocks.h"
+
+/**
+ * SECTION:superblocks
+ * @title: Superblocks probing
+ * @short_description: filesystems and raids superblocks probing.
+ *
+ * The library API has been originally designed for superblocks probing only.
+ * This is reason why some *deprecated* superblock specific functions don't use
+ * '_superblocks_' namespace in the function name. Please, don't use these
+ * functions in new code.
+ *
+ * The 'superblocks' probers support NAME=value (tags) interface only. The
+ * superblocks probing is enabled by default (and controlled by
+ * blkid_probe_enable_superblocks()).
+ *
+ * Currently supported tags:
+ *
+ * @TYPE: filesystem type
+ *
+ * @SEC_TYPE: secondary filesystem type
+ *
+ * @LABEL: filesystem label
+ *
+ * @LABEL_RAW: raw label from FS superblock
+ *
+ * @UUID: filesystem UUID (lower case)
+ *
+ * @UUID_SUB: subvolume uuid (e.g. btrfs)
+ *
+ * @LOGUUID: external log UUID (e.g. xfs)
+ *
+ * @UUID_RAW: raw UUID from FS superblock
+ *
+ * @EXT_JOURNAL: external journal UUID
+ *
+ * @USAGE:  usage string: "raid", "filesystem", ...
+ *
+ * @VERSION: filesystem version
+ *
+ * @MOUNT: cluster mount name (?) -- ocfs only
+ *
+ * @SBMAGIC: super block magic string
+ *
+ * @SBMAGIC_OFFSET: offset of SBMAGIC
+ *
+ * @FSSIZE: size of filessystem [not-implemented yet]
+ *
+ * @SYSTEM_ID: ISO9660 system identifier
+ *
+ * @PUBLISHER_ID: ISO9660 publisher identifier
+ *
+ * @APPLICATION_ID: ISO9660 application identifier
+ *
+ * @BOOT_SYSTEM_ID: ISO9660 boot system identifier
+ */
+
+static int superblocks_probe(blkid_probe pr, struct blkid_chain *chn);
+static int superblocks_safeprobe(blkid_probe pr, struct blkid_chain *chn);
+
+static int blkid_probe_set_usage(blkid_probe pr, int usage);
+
+
+/*
+ * Superblocks chains probing functions
+ */
+static const struct blkid_idinfo *idinfos[] =
+{
+	/* RAIDs */
+	&linuxraid_idinfo,
+	&ddfraid_idinfo,
+	&iswraid_idinfo,
+	&lsiraid_idinfo,
+	&viaraid_idinfo,
+	&silraid_idinfo,
+	&nvraid_idinfo,
+	&pdcraid_idinfo,
+	&highpoint45x_idinfo,
+	&highpoint37x_idinfo,
+	&adraid_idinfo,
+	&jmraid_idinfo,
+
+	&bcache_idinfo,
+	&drbd_idinfo,
+	&drbdproxy_datalog_idinfo,
+	&lvm2_idinfo,
+	&lvm1_idinfo,
+	&snapcow_idinfo,
+	&verity_hash_idinfo,
+	&luks_idinfo,
+	&vmfs_volume_idinfo,
+
+	/* Filesystems */
+	&vfat_idinfo,
+	&swsuspend_idinfo,
+	&swap_idinfo,
+	&xfs_idinfo,
+	&xfs_log_idinfo,
+	&ext4dev_idinfo,
+	&ext4_idinfo,
+	&ext3_idinfo,
+	&ext2_idinfo,
+	&jbd_idinfo,
+	&reiser_idinfo,
+	&reiser4_idinfo,
+	&jfs_idinfo,
+	&udf_idinfo,
+	&iso9660_idinfo,
+	&zfs_idinfo,
+	&hfsplus_idinfo,
+	&hfs_idinfo,
+	&ufs_idinfo,
+	&hpfs_idinfo,
+	&sysv_idinfo,
+        &xenix_idinfo,
+	&ntfs_idinfo,
+	&refs_idinfo,
+	&cramfs_idinfo,
+	&romfs_idinfo,
+	&minix_idinfo,
+	&gfs_idinfo,
+	&gfs2_idinfo,
+	&ocfs_idinfo,
+	&ocfs2_idinfo,
+	&oracleasm_idinfo,
+	&vxfs_idinfo,
+	&squashfs_idinfo,
+	&squashfs3_idinfo,
+	&netware_idinfo,
+	&btrfs_idinfo,
+	&ubifs_idinfo,
+	&bfs_idinfo,
+	&vmfs_fs_idinfo,
+	&befs_idinfo,
+	&nilfs2_idinfo,
+	&exfat_idinfo,
+	&f2fs_idinfo,
+	&erofs_idinfo
+};
+
+/*
+ * Driver definition
+ */
+const struct blkid_chaindrv superblocks_drv = {
+	.id           = BLKID_CHAIN_SUBLKS,
+	.name         = "superblocks",
+	.dflt_enabled = TRUE,
+	.dflt_flags   = BLKID_SUBLKS_DEFAULT,
+	.idinfos      = idinfos,
+	.nidinfos     = ARRAY_SIZE(idinfos),
+	.has_fltr     = TRUE,
+	.probe        = superblocks_probe,
+	.safeprobe    = superblocks_safeprobe,
+};
+
+/**
+ * blkid_probe_enable_superblocks:
+ * @pr: probe
+ * @enable: TRUE/FALSE
+ *
+ * Enables/disables the superblocks probing for non-binary interface.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_enable_superblocks(blkid_probe pr, int enable)
+{
+	if (!pr)
+		return -1;
+	pr->chains[BLKID_CHAIN_SUBLKS].enabled = enable;
+	return 0;
+}
+
+/**
+ * blkid_probe_set_superblocks_flags:
+ * @pr: prober
+ * @flags: BLKID_SUBLKS_* flags
+ *
+ * Sets probing flags to the superblocks prober. This function is optional, the
+ * default are BLKID_SUBLKS_DEFAULTS flags.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_set_superblocks_flags(blkid_probe pr, int flags)
+{
+	if (!pr)
+		return -1;
+
+	pr->chains[BLKID_CHAIN_SUBLKS].flags = flags;
+	return 0;
+}
+
+/**
+ * blkid_probe_reset_superblocks_filter:
+ * @pr: prober
+ *
+ * Resets superblocks probing filter
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_reset_superblocks_filter(blkid_probe pr)
+{
+	return __blkid_probe_reset_filter(pr, BLKID_CHAIN_SUBLKS);
+}
+
+/**
+ * blkid_probe_invert_superblocks_filter:
+ * @pr: prober
+ *
+ * Inverts superblocks probing filter
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_invert_superblocks_filter(blkid_probe pr)
+{
+	return __blkid_probe_invert_filter(pr, BLKID_CHAIN_SUBLKS);
+}
+
+/**
+ * blkid_probe_filter_superblocks_type:
+ * @pr: prober
+ * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
+ * @names: NULL terminated array of probing function names (e.g. "vfat").
+ *
+ *  %BLKID_FLTR_NOTIN  - probe for all items which are NOT IN @names;
+ *
+ *  %BLKID_FLTR_ONLYIN - probe for items which are IN @names
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_filter_superblocks_type(blkid_probe pr, int flag, char *names[])
+{
+	return __blkid_probe_filter_types(pr, BLKID_CHAIN_SUBLKS, flag, names);
+}
+
+/**
+ * blkid_probe_filter_superblocks_usage:
+ * @pr: prober
+ * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
+ * @usage: BLKID_USAGE_* flags
+ *
+ *  %BLKID_FLTR_NOTIN  - probe for all items which are NOT IN @usage;
+ *
+ *  %BLKID_FLTR_ONLYIN - probe for items which are IN @usage
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_filter_superblocks_usage(blkid_probe pr, int flag, int usage)
+{
+	unsigned long *fltr;
+	struct blkid_chain *chn;
+	size_t i;
+
+	fltr = blkid_probe_get_filter(pr, BLKID_CHAIN_SUBLKS, TRUE);
+	if (!fltr)
+		return -1;
+
+	chn = &pr->chains[BLKID_CHAIN_SUBLKS];
+
+	for (i = 0; i < chn->driver->nidinfos; i++) {
+		const struct blkid_idinfo *id = chn->driver->idinfos[i];
+
+		if (id->usage & usage) {
+			if (flag & BLKID_FLTR_NOTIN)
+				blkid_bmp_set_item(chn->fltr, i);
+		} else if (flag & BLKID_FLTR_ONLYIN)
+			blkid_bmp_set_item(chn->fltr, i);
+	}
+	DBG(LOWPROBE, ul_debug("a new probing usage-filter initialized"));
+	return 0;
+}
+
+/**
+ * blkid_known_fstype:
+ * @fstype: filesystem name
+ *
+ * Returns: 1 for known filesytems, or 0 for unknown filesystem.
+ */
+int blkid_known_fstype(const char *fstype)
+{
+	size_t i;
+
+	if (!fstype)
+		return 0;
+
+	for (i = 0; i < ARRAY_SIZE(idinfos); i++) {
+		const struct blkid_idinfo *id = idinfos[i];
+		if (strcmp(id->name, fstype) == 0)
+			return 1;
+	}
+	return 0;
+}
+
+/**
+ * blkid_superblocks_get_name:
+ * @idx: number >= 0
+ * @name: returns name of supported filesystem/raid (optional)
+ * @usage: returns BLKID_USAGE_* flags, (optional)
+ *
+ * Returns: -1 if @idx is out of range, or 0 on success.
+ */
+int blkid_superblocks_get_name(size_t idx, const char **name, int *usage)
+{
+	if (idx < ARRAY_SIZE(idinfos)) {
+		if (name)
+			*name = idinfos[idx]->name;
+		if (usage)
+			*usage = idinfos[idx]->usage;
+		return 0;
+	}
+	return -1;
+}
+
+/*
+ * The blkid_do_probe() backend.
+ */
+static int superblocks_probe(blkid_probe pr, struct blkid_chain *chn)
+{
+	size_t i;
+	int rc = BLKID_PROBE_NONE;
+
+	if (!pr || chn->idx < -1)
+		return -EINVAL;
+
+	blkid_probe_chain_reset_vals(pr, chn);
+
+	if (pr->flags & BLKID_FL_NOSCAN_DEV)
+		return BLKID_PROBE_NONE;
+
+	if (pr->size <= 0 || (pr->size <= 1024 && !S_ISCHR(pr->mode)))
+		/* Ignore very very small block devices or regular files (e.g.
+		 * extended partitions). Note that size of the UBI char devices
+		 * is 1 byte */
+		return BLKID_PROBE_NONE;
+
+	DBG(LOWPROBE, ul_debug("--> starting probing loop [SUBLKS idx=%d]",
+		chn->idx));
+
+	i = chn->idx < 0 ? 0 : chn->idx + 1U;
+
+	for ( ; i < ARRAY_SIZE(idinfos); i++) {
+		const struct blkid_idinfo *id;
+		const struct blkid_idmag *mag = NULL;
+		blkid_loff_t off = 0;
+
+		chn->idx = i;
+		id = idinfos[i];
+
+		if (chn->fltr && blkid_bmp_get_item(chn->fltr, i)) {
+			DBG(LOWPROBE, ul_debug("filter out: %s", id->name));
+			rc = BLKID_PROBE_NONE;
+			continue;
+		}
+
+		if (id->minsz && id->minsz > pr->size) {
+			rc = BLKID_PROBE_NONE;
+			continue;	/* the device is too small */
+		}
+
+		/* don't probe for RAIDs, swap or journal on CD/DVDs */
+		if ((id->usage & (BLKID_USAGE_RAID | BLKID_USAGE_OTHER)) &&
+		    blkid_probe_is_cdrom(pr)) {
+			rc = BLKID_PROBE_NONE;
+			continue;
+		}
+
+		/* don't probe for RAIDs on floppies */
+		if ((id->usage & BLKID_USAGE_RAID) && blkid_probe_is_tiny(pr)) {
+			rc = BLKID_PROBE_NONE;
+			continue;
+		}
+
+		DBG(LOWPROBE, ul_debug("[%zd] %s:", i, id->name));
+
+		rc = blkid_probe_get_idmag(pr, id, &off, &mag);
+		if (rc < 0)
+			break;
+		if (rc != BLKID_PROBE_OK)
+			continue;
+
+		/* final check by probing function */
+		if (id->probefunc) {
+			DBG(LOWPROBE, ul_debug("\tcall probefunc()"));
+			rc = id->probefunc(pr, mag);
+			if (rc != BLKID_PROBE_OK) {
+				blkid_probe_chain_reset_vals(pr, chn);
+				if (rc < 0)
+					break;
+				continue;
+			}
+		}
+
+		/* all cheks passed */
+		if (chn->flags & BLKID_SUBLKS_TYPE)
+			rc = blkid_probe_set_value(pr, "TYPE",
+				(unsigned char *) id->name,
+				strlen(id->name) + 1);
+
+		if (!rc)
+			rc = blkid_probe_set_usage(pr, id->usage);
+
+		if (!rc && mag)
+			rc = blkid_probe_set_magic(pr, off, mag->len,
+					(unsigned char *) mag->magic);
+		if (rc) {
+			blkid_probe_chain_reset_vals(pr, chn);
+			DBG(LOWPROBE, ul_debug("failed to set result -- ignore"));
+			continue;
+		}
+
+		DBG(LOWPROBE, ul_debug("<-- leaving probing loop (type=%s) [SUBLKS idx=%d]",
+			id->name, chn->idx));
+		return BLKID_PROBE_OK;
+	}
+
+	DBG(LOWPROBE, ul_debug("<-- leaving probing loop (failed=%d) [SUBLKS idx=%d]",
+			rc, chn->idx));
+	return rc;
+}
+
+/*
+ * This is the same function as blkid_do_probe(), but returns only one result
+ * (cannot be used in while()) and checks for ambivalen results (more
+ * filesystems on the device) -- in such case returns -2.
+ *
+ * The function does not check for filesystems when a RAID or crypto signature
+ * is detected.  The function also does not check for collision between RAIDs
+ * and crypto devices. The first detected RAID or crypto device is returned.
+ *
+ * The function does not probe for ambivalent results on very small devices
+ * (e.g. floppies), on small devices the first detected filesystem is returned.
+ */
+static int superblocks_safeprobe(blkid_probe pr, struct blkid_chain *chn)
+{
+	struct blkid_prval vals[BLKID_NVALS_SUBLKS];
+	int nvals = BLKID_NVALS_SUBLKS;
+	int idx = -1;
+	int count = 0;
+	int intol = 0;
+	int rc;
+
+	if (pr->flags & BLKID_FL_NOSCAN_DEV)
+		return BLKID_PROBE_NONE;
+
+	while ((rc = superblocks_probe(pr, chn)) == 0) {
+
+		if (blkid_probe_is_tiny(pr) && !count)
+			return BLKID_PROBE_OK;	/* floppy or so -- returns the first result. */
+
+		count++;
+
+		if (chn->idx >= 0 &&
+		    idinfos[chn->idx]->usage & (BLKID_USAGE_RAID | BLKID_USAGE_CRYPTO))
+			break;
+
+		if (chn->idx >= 0 &&
+		    !(idinfos[chn->idx]->flags & BLKID_IDINFO_TOLERANT))
+			intol++;
+
+		if (count == 1) {
+			/* save the first result */
+			nvals = blkid_probe_chain_copy_vals(pr, chn, vals, nvals);
+			idx = chn->idx;
+		}
+	}
+
+	if (rc < 0)
+		return rc;		/* error */
+
+	if (count > 1 && intol) {
+		DBG(LOWPROBE, ul_debug("ERROR: superblocks chain: "
+			       "ambivalent result detected (%d filesystems)!",
+			       count));
+		return -2;		/* error, ambivalent result (more FS) */
+	}
+	if (!count)
+		return BLKID_PROBE_NONE;
+
+	if (idx != -1) {
+		/* restore the first result */
+		blkid_probe_chain_reset_vals(pr, chn);
+		blkid_probe_append_vals(pr, vals, nvals);
+		chn->idx = idx;
+	}
+
+	/*
+	 * The RAID device could be partitioned. The problem are RAID1 devices
+	 * where the partition table is visible from underlaying devices. We
+	 * have to ignore such partition tables.
+	 */
+	if (chn->idx >= 0 && idinfos[chn->idx]->usage & BLKID_USAGE_RAID)
+		pr->prob_flags |= BLKID_PROBE_FL_IGNORE_PT;
+
+	return BLKID_PROBE_OK;
+}
+
+int blkid_probe_set_version(blkid_probe pr, const char *version)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+	if (chn->flags & BLKID_SUBLKS_VERSION)
+		return blkid_probe_set_value(pr, "VERSION",
+			   (unsigned char *) version, strlen(version) + 1);
+	return 0;
+}
+
+
+int blkid_probe_sprintf_version(blkid_probe pr, const char *fmt, ...)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+	int rc = 0;
+
+	if (chn->flags & BLKID_SUBLKS_VERSION) {
+		va_list ap;
+
+		va_start(ap, fmt);
+		rc = blkid_probe_vsprintf_value(pr, "VERSION", fmt, ap);
+		va_end(ap);
+	}
+	return rc;
+}
+
+static int blkid_probe_set_usage(blkid_probe pr, int usage)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+	char *u = NULL;
+
+	if (!(chn->flags & BLKID_SUBLKS_USAGE))
+		return 0;
+
+	if (usage & BLKID_USAGE_FILESYSTEM)
+		u = "filesystem";
+	else if (usage & BLKID_USAGE_RAID)
+		u = "raid";
+	else if (usage & BLKID_USAGE_CRYPTO)
+		u = "crypto";
+	else if (usage & BLKID_USAGE_OTHER)
+		u = "other";
+	else
+		u = "unknown";
+
+	return blkid_probe_set_value(pr, "USAGE", (unsigned char *) u, strlen(u) + 1);
+}
+
+int blkid_probe_set_id_label(blkid_probe pr, const char *name,
+			     unsigned char *data, size_t len)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+	struct blkid_prval *v;
+
+	if (!(chn->flags & BLKID_SUBLKS_LABEL))
+		return 0;
+
+	v = blkid_probe_assign_value(pr, name);
+	if (!v)
+		return -1;
+
+	if (len >= BLKID_PROBVAL_BUFSIZ)
+		len = BLKID_PROBVAL_BUFSIZ - 1;			/* make a space for \0 */
+
+	memcpy(v->data, data, len);
+	v->data[len] = '\0';
+
+	/* remove white spaces */
+	v->len = blkid_rtrim_whitespace(v->data) + 1;
+	if (v->len > 1)
+		v->len = blkid_ltrim_whitespace(v->data) + 1;
+
+	if (v->len <= 1)
+		blkid_probe_reset_last_value(pr);		/* ignore empty */
+	return 0;
+}
+
+int blkid_probe_set_label(blkid_probe pr, unsigned char *label, size_t len)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+	struct blkid_prval *v;
+	if (len > BLKID_PROBVAL_BUFSIZ)
+		len = BLKID_PROBVAL_BUFSIZ;
+
+	if ((chn->flags & BLKID_SUBLKS_LABELRAW) &&
+	    blkid_probe_set_value(pr, "LABEL_RAW", label, len) < 0)
+		return -1;
+	if (!(chn->flags & BLKID_SUBLKS_LABEL))
+		return 0;
+	v = blkid_probe_assign_value(pr, "LABEL");
+	if (!v)
+		return -1;
+
+	if (len == BLKID_PROBVAL_BUFSIZ)
+		len--;				/* make a space for \0 */
+
+	memcpy(v->data, label, len);
+	v->data[len] = '\0';
+
+	v->len = blkid_rtrim_whitespace(v->data) + 1;
+	if (v->len == 1)
+		blkid_probe_reset_last_value(pr);
+	return 0;
+}
+
+int blkid_probe_set_utf8label(blkid_probe pr, unsigned char *label,
+				size_t len, int enc)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+	struct blkid_prval *v;
+
+	if ((chn->flags & BLKID_SUBLKS_LABELRAW) &&
+	    blkid_probe_set_value(pr, "LABEL_RAW", label, len) < 0)
+		return -1;
+	if (!(chn->flags & BLKID_SUBLKS_LABEL))
+		return 0;
+	v = blkid_probe_assign_value(pr, "LABEL");
+	if (!v)
+		return -1;
+
+	blkid_encode_to_utf8(enc, v->data, sizeof(v->data), label, len);
+	v->len = blkid_rtrim_whitespace(v->data) + 1;
+	if (v->len == 1)
+		blkid_probe_reset_last_value(pr);
+	return 0;
+}
+
+int blkid_probe_sprintf_uuid(blkid_probe pr, unsigned char *uuid,
+				size_t len, const char *fmt, ...)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+	int rc = -1;
+	va_list ap;
+
+	if (len > BLKID_PROBVAL_BUFSIZ)
+		len = BLKID_PROBVAL_BUFSIZ;
+
+	if (blkid_uuid_is_empty(uuid, len))
+		return 0;
+
+	if ((chn->flags & BLKID_SUBLKS_UUIDRAW) &&
+	    blkid_probe_set_value(pr, "UUID_RAW", uuid, len) < 0)
+		return -1;
+	if (!(chn->flags & BLKID_SUBLKS_UUID))
+		return 0;
+
+	va_start(ap, fmt);
+	rc = blkid_probe_vsprintf_value(pr, "UUID", fmt, ap);
+	va_end(ap);
+
+	/* convert to lower case (..be paranoid) */
+	if (!rc) {
+		size_t i;
+		struct blkid_prval *v = __blkid_probe_get_value(pr,
+						blkid_probe_numof_values(pr));
+		if (v) {
+			for (i = 0; i < v->len; i++)
+				if (v->data[i] >= 'A' && v->data[i] <= 'F')
+					v->data[i] = (v->data[i] - 'A') + 'a';
+		}
+	}
+	return rc;
+}
+
+/* function to set UUIDs that are in suberblocks stored as strings */
+int blkid_probe_strncpy_uuid(blkid_probe pr, unsigned char *str, size_t len)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+	struct blkid_prval *v;
+
+	if (str == NULL || *str == '\0')
+		return -1;
+	if (!len)
+		len = strlen((char *) str);
+	if (len > BLKID_PROBVAL_BUFSIZ)
+		len = BLKID_PROBVAL_BUFSIZ;
+
+	if ((chn->flags & BLKID_SUBLKS_UUIDRAW) &&
+	    blkid_probe_set_value(pr, "UUID_RAW", str, len) < 0)
+		return -1;
+	if (!(chn->flags & BLKID_SUBLKS_UUID))
+		return 0;
+
+	v = blkid_probe_assign_value(pr, "UUID");
+	if (v) {
+		if (len == BLKID_PROBVAL_BUFSIZ)
+			len--;		/* make a space for \0 */
+
+		memcpy((char *) v->data, str, len);
+		v->data[len] = '\0';
+		v->len = len + 1;
+		return 0;
+	}
+	return -1;
+}
+
+/* default _set_uuid function to set DCE UUIDs */
+int blkid_probe_set_uuid_as(blkid_probe pr, unsigned char *uuid, const char *name)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+	struct blkid_prval *v;
+
+	if (blkid_uuid_is_empty(uuid, 16))
+		return 0;
+
+	if (!name) {
+		if ((chn->flags & BLKID_SUBLKS_UUIDRAW) &&
+		    blkid_probe_set_value(pr, "UUID_RAW", uuid, 16) < 0)
+			return -1;
+		if (!(chn->flags & BLKID_SUBLKS_UUID))
+			return 0;
+
+		v = blkid_probe_assign_value(pr, "UUID");
+	} else
+		v = blkid_probe_assign_value(pr, name);
+
+	blkid_unparse_uuid(uuid, (char *) v->data, sizeof(v->data));
+	v->len = 37;
+
+	return 0;
+}
+
+int blkid_probe_set_uuid(blkid_probe pr, unsigned char *uuid)
+{
+	return blkid_probe_set_uuid_as(pr, uuid, NULL);
+}
+
+/**
+ * blkid_probe_set_request:
+ * @pr: probe
+ * @flags: BLKID_PROBREQ_* (deprecated) or BLKID_SUBLKS_* flags
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ *
+ * Deprecated: Use blkid_probe_set_superblocks_flags().
+ */
+int blkid_probe_set_request(blkid_probe pr, int flags)
+{
+	return blkid_probe_set_superblocks_flags(pr, flags);
+}
+
+/**
+ * blkid_probe_reset_filter:
+ * @pr: prober
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ *
+ * Deprecated: Use blkid_probe_reset_superblocks_filter().
+ */
+int blkid_probe_reset_filter(blkid_probe pr)
+{
+	return __blkid_probe_reset_filter(pr, BLKID_CHAIN_SUBLKS);
+}
+
+/**
+ * blkid_probe_invert_filter:
+ * @pr: prober
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ *
+ * Deprecated: Use blkid_probe_invert_superblocks_filter().
+ */
+int blkid_probe_invert_filter(blkid_probe pr)
+{
+	return __blkid_probe_invert_filter(pr, BLKID_CHAIN_SUBLKS);
+}
+
+/**
+ * blkid_probe_filter_types
+ * @pr: prober
+ * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
+ * @names: NULL terminated array of probing function names (e.g. "vfat").
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ *
+ * Deprecated: Use blkid_probe_filter_superblocks_types().
+ */
+int blkid_probe_filter_types(blkid_probe pr, int flag, char *names[])
+{
+	return __blkid_probe_filter_types(pr, BLKID_CHAIN_SUBLKS, flag, names);
+}
+
+/**
+ * blkid_probe_filter_usage
+ * @pr: prober
+ * @flag: filter BLKID_FLTR_{NOTIN,ONLYIN} flag
+ * @usage: BLKID_USAGE_* flags
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ *
+ * Deprecated: Use blkid_probe_filter_superblocks_usage().
+ */
+int blkid_probe_filter_usage(blkid_probe pr, int flag, int usage)
+{
+	return blkid_probe_filter_superblocks_usage(pr, flag, usage);
+}
+
+
diff --git a/libblkid/src/superblocks/superblocks.h b/libblkid/src/superblocks/superblocks.h
new file mode 100644
index 0000000..808ae6a
--- /dev/null
+++ b/libblkid/src/superblocks/superblocks.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#ifndef _BLKID_SUPERBLOCKS_H
+#define _BLKID_SUPERBLOCKS_H
+
+#include "blkidP.h"
+
+extern const struct blkid_idinfo cramfs_idinfo;
+extern const struct blkid_idinfo swap_idinfo;
+extern const struct blkid_idinfo swsuspend_idinfo;
+extern const struct blkid_idinfo adraid_idinfo;
+extern const struct blkid_idinfo ddfraid_idinfo;
+extern const struct blkid_idinfo iswraid_idinfo;
+extern const struct blkid_idinfo jmraid_idinfo;
+extern const struct blkid_idinfo lsiraid_idinfo;
+extern const struct blkid_idinfo nvraid_idinfo;
+extern const struct blkid_idinfo pdcraid_idinfo;
+extern const struct blkid_idinfo silraid_idinfo;
+extern const struct blkid_idinfo viaraid_idinfo;
+extern const struct blkid_idinfo linuxraid_idinfo;
+extern const struct blkid_idinfo ext4dev_idinfo;
+extern const struct blkid_idinfo ext4_idinfo;
+extern const struct blkid_idinfo ext3_idinfo;
+extern const struct blkid_idinfo ext2_idinfo;
+extern const struct blkid_idinfo jbd_idinfo;
+extern const struct blkid_idinfo jfs_idinfo;
+extern const struct blkid_idinfo xfs_idinfo;
+extern const struct blkid_idinfo xfs_log_idinfo;
+extern const struct blkid_idinfo gfs_idinfo;
+extern const struct blkid_idinfo gfs2_idinfo;
+extern const struct blkid_idinfo romfs_idinfo;
+extern const struct blkid_idinfo ocfs_idinfo;
+extern const struct blkid_idinfo ocfs2_idinfo;
+extern const struct blkid_idinfo oracleasm_idinfo;
+extern const struct blkid_idinfo reiser_idinfo;
+extern const struct blkid_idinfo reiser4_idinfo;
+extern const struct blkid_idinfo hfs_idinfo;
+extern const struct blkid_idinfo hfsplus_idinfo;
+extern const struct blkid_idinfo ntfs_idinfo;
+extern const struct blkid_idinfo refs_idinfo;
+extern const struct blkid_idinfo iso9660_idinfo;
+extern const struct blkid_idinfo udf_idinfo;
+extern const struct blkid_idinfo vxfs_idinfo;
+extern const struct blkid_idinfo minix_idinfo;
+extern const struct blkid_idinfo vfat_idinfo;
+extern const struct blkid_idinfo ufs_idinfo;
+extern const struct blkid_idinfo hpfs_idinfo;
+extern const struct blkid_idinfo lvm2_idinfo;
+extern const struct blkid_idinfo lvm1_idinfo;
+extern const struct blkid_idinfo snapcow_idinfo;
+extern const struct blkid_idinfo verity_hash_idinfo;
+extern const struct blkid_idinfo luks_idinfo;
+extern const struct blkid_idinfo highpoint37x_idinfo;
+extern const struct blkid_idinfo highpoint45x_idinfo;
+extern const struct blkid_idinfo squashfs_idinfo;
+extern const struct blkid_idinfo squashfs3_idinfo;
+extern const struct blkid_idinfo netware_idinfo;
+extern const struct blkid_idinfo sysv_idinfo;
+extern const struct blkid_idinfo xenix_idinfo;
+extern const struct blkid_idinfo btrfs_idinfo;
+extern const struct blkid_idinfo ubifs_idinfo;
+extern const struct blkid_idinfo zfs_idinfo;
+extern const struct blkid_idinfo bfs_idinfo;
+extern const struct blkid_idinfo vmfs_volume_idinfo;
+extern const struct blkid_idinfo vmfs_fs_idinfo;
+extern const struct blkid_idinfo drbd_idinfo;
+extern const struct blkid_idinfo drbdproxy_datalog_idinfo;
+extern const struct blkid_idinfo befs_idinfo;
+extern const struct blkid_idinfo nilfs2_idinfo;
+extern const struct blkid_idinfo exfat_idinfo;
+extern const struct blkid_idinfo f2fs_idinfo;
+extern const struct blkid_idinfo bcache_idinfo;
+extern const struct blkid_idinfo erofs_idinfo;
+
+/*
+ * superblock functions
+ */
+extern int blkid_probe_set_version(blkid_probe pr, const char *version);
+extern int blkid_probe_sprintf_version(blkid_probe pr, const char *fmt, ...)
+		__attribute__ ((__format__ (__printf__, 2, 3)));
+
+extern int blkid_probe_set_label(blkid_probe pr, unsigned char *label, size_t len);
+extern int blkid_probe_set_utf8label(blkid_probe pr, unsigned char *label,
+                size_t len, int enc);
+extern int blkid_probe_sprintf_uuid(blkid_probe pr, unsigned char *uuid,
+                size_t len, const char *fmt, ...)
+		__attribute__ ((__format__ (__printf__, 4, 5)));
+extern int blkid_probe_strncpy_uuid(blkid_probe pr, unsigned char *str, size_t len);
+
+extern int blkid_probe_set_uuid(blkid_probe pr, unsigned char *uuid);
+extern int blkid_probe_set_uuid_as(blkid_probe pr, unsigned char *uuid, const char *name);
+
+extern int blkid_probe_set_id_label(blkid_probe pr, const char *name,
+			     unsigned char *data, size_t len);
+
+#endif /* _BLKID_SUPERBLOCKS_H */
diff --git a/libblkid/src/superblocks/swap.c b/libblkid/src/superblocks/swap.c
new file mode 100644
index 0000000..3f21391
--- /dev/null
+++ b/libblkid/src/superblocks/swap.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+/* linux-2.6/include/linux/swap.h */
+struct swap_header_v1_2 {
+     /*	char		bootbits[1024];	*/ /* Space for disklabel etc. */
+	uint32_t	version;
+	uint32_t	lastpage;
+	uint32_t	nr_badpages;
+	unsigned char	uuid[16];
+	unsigned char	volume[16];
+	uint32_t	padding[117];
+	uint32_t	badpages[1];
+} __attribute__((packed));
+
+#define PAGESIZE_MIN	0xff6	/* 4086 (arm, i386, ...) */
+#define PAGESIZE_MAX	0xfff6	/* 65526 (ia64) */
+
+#define TOI_MAGIC_STRING	"\xed\xc3\x02\xe9\x98\x56\xe5\x0c"
+#define TOI_MAGIC_STRLEN	(sizeof(TOI_MAGIC_STRING) - 1)
+
+static int swap_set_info(blkid_probe pr, const char *version)
+{
+	struct swap_header_v1_2 *hdr;
+
+	/* Swap header always located at offset of 1024 bytes */
+	hdr = (struct swap_header_v1_2 *) blkid_probe_get_buffer(pr, 1024,
+				sizeof(struct swap_header_v1_2));
+	if (!hdr)
+		return errno ? -errno : 1;
+
+	/* SWAPSPACE2 - check for wrong version or zeroed pagecount */
+	if (strcmp(version, "1") == 0) {
+		if (hdr->version != 1 && swab32(hdr->version) != 1) {
+			DBG(LOWPROBE, ul_debug("incorrect swap version"));
+			return 1;
+		}
+		if (hdr->lastpage == 0) {
+			DBG(LOWPROBE, ul_debug("not set last swap page"));
+			return 1;
+		}
+	}
+
+	/* arbitrary sanity check.. is there any garbage down there? */
+	if (hdr->padding[32] == 0 && hdr->padding[33] == 0) {
+		if (hdr->volume[0] && blkid_probe_set_label(pr, hdr->volume,
+				sizeof(hdr->volume)) < 0)
+			return 1;
+		if (blkid_probe_set_uuid(pr, hdr->uuid) < 0)
+			return 1;
+	}
+
+	blkid_probe_set_version(pr, version);
+	return 0;
+}
+
+static int probe_swap(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	unsigned char *buf;
+
+	if (!mag)
+		return 1;
+
+	/* TuxOnIce keeps valid swap header at the end of the 1st page */
+	buf = blkid_probe_get_buffer(pr, 0, TOI_MAGIC_STRLEN);
+	if (!buf)
+		return errno ? -errno : 1;
+
+	if (memcmp(buf, TOI_MAGIC_STRING, TOI_MAGIC_STRLEN) == 0)
+		return 1;	/* Ignore swap signature, it's TuxOnIce */
+
+	if (!memcmp(mag->magic, "SWAP-SPACE", mag->len)) {
+		/* swap v0 doesn't support LABEL or UUID */
+		blkid_probe_set_version(pr, "0");
+		return 0;
+
+	} else if (!memcmp(mag->magic, "SWAPSPACE2", mag->len))
+		return swap_set_info(pr, "1");
+
+	return 1;
+}
+
+static int probe_swsuspend(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	if (!mag)
+		return 1;
+	if (!memcmp(mag->magic, "S1SUSPEND", mag->len))
+		return swap_set_info(pr, "s1suspend");
+	if (!memcmp(mag->magic, "S2SUSPEND", mag->len))
+		return swap_set_info(pr, "s2suspend");
+	if (!memcmp(mag->magic, "ULSUSPEND", mag->len))
+		return swap_set_info(pr, "ulsuspend");
+	if (!memcmp(mag->magic, TOI_MAGIC_STRING, mag->len))
+		return swap_set_info(pr, "tuxonice");
+	if (!memcmp(mag->magic, "LINHIB0001", mag->len))
+		return swap_set_info(pr, "linhib0001");
+
+	return 1;	/* no signature detected */
+}
+
+const struct blkid_idinfo swap_idinfo =
+{
+	.name		= "swap",
+	.usage		= BLKID_USAGE_OTHER,
+	.probefunc	= probe_swap,
+	.minsz		= 10 * 4096,	/* 10 pages */
+	.magics		=
+	{
+		{ "SWAP-SPACE", 10, 0,  0xff6 },
+		{ "SWAPSPACE2", 10, 0,  0xff6 },
+		{ "SWAP-SPACE", 10, 0, 0x1ff6 },
+		{ "SWAPSPACE2", 10, 0, 0x1ff6 },
+		{ "SWAP-SPACE", 10, 0, 0x3ff6 },
+		{ "SWAPSPACE2", 10, 0, 0x3ff6 },
+		{ "SWAP-SPACE", 10, 0, 0x7ff6 },
+		{ "SWAPSPACE2", 10, 0, 0x7ff6 },
+		{ "SWAP-SPACE", 10, 0, 0xfff6 },
+		{ "SWAPSPACE2", 10, 0, 0xfff6 },
+		{ NULL }
+	}
+};
+
+
+const struct blkid_idinfo swsuspend_idinfo =
+{
+	.name		= "swsuspend",
+	.usage		= BLKID_USAGE_OTHER,
+	.probefunc	= probe_swsuspend,
+	.minsz		= 10 * 4096,	/* 10 pages */
+	.magics		=
+	{
+		{ TOI_MAGIC_STRING, TOI_MAGIC_STRLEN, 0, 0 },
+		{ "S1SUSPEND", 9, 0, 0xff6 },
+		{ "S2SUSPEND", 9, 0, 0xff6 },
+		{ "ULSUSPEND", 9, 0, 0xff6 },
+		{ "LINHIB0001",10,0, 0xff6 },
+
+		{ "S1SUSPEND", 9, 0, 0x1ff6 },
+		{ "S2SUSPEND", 9, 0, 0x1ff6 },
+		{ "ULSUSPEND", 9, 0, 0x1ff6 },
+		{ "LINHIB0001",10,0, 0x1ff6 },
+
+		{ "S1SUSPEND", 9, 0, 0x3ff6 },
+		{ "S2SUSPEND", 9, 0, 0x3ff6 },
+		{ "ULSUSPEND", 9, 0, 0x3ff6 },
+		{ "LINHIB0001",10,0, 0x3ff6 },
+
+		{ "S1SUSPEND", 9, 0, 0x7ff6 },
+		{ "S2SUSPEND", 9, 0, 0x7ff6 },
+		{ "ULSUSPEND", 9, 0, 0x7ff6 },
+		{ "LINHIB0001",10,0, 0x7ff6 },
+
+		{ "S1SUSPEND", 9, 0, 0xfff6 },
+		{ "S2SUSPEND", 9, 0, 0xfff6 },
+		{ "ULSUSPEND", 9, 0, 0xfff6 },
+		{ "LINHIB0001",10,0, 0xfff6 },
+
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/sysv.c b/libblkid/src/superblocks/sysv.c
new file mode 100644
index 0000000..4b34591
--- /dev/null
+++ b/libblkid/src/superblocks/sysv.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This is written from sratch according to Linux kernel fs/sysv/super.c file.
+ * It seems that sysv probing code in libvolume_id and also in the original
+ * blkid is useless.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#include "superblocks.h"
+
+#define XENIX_NICINOD				100
+#define XENIX_NICFREE				100
+
+struct xenix_super_block {
+	uint16_t	s_isize;
+	uint32_t	s_fsize;
+	uint16_t	s_nfree;
+	uint32_t	s_free[XENIX_NICFREE];
+	uint16_t	s_ninode;
+	uint16_t	s_inode[XENIX_NICINOD];
+	uint8_t		s_flock;
+	uint8_t		s_ilock;
+	uint8_t		s_fmod;
+	uint8_t		s_ronly;
+	uint32_t	s_time;
+	uint32_t	s_tfree;
+	uint16_t	s_tinode;
+	uint16_t	s_dinfo[4];
+	uint8_t		s_fname[6];
+	uint8_t		s_fpack[6];
+	uint8_t		s_clean;
+	uint8_t		s_fill[371];
+	uint32_t	s_magic;
+	uint32_t	s_type;
+} __attribute__((packed));
+
+
+#define SYSV_NICINOD			100
+#define SYSV_NICFREE			50
+
+struct sysv_super_block
+{
+	uint16_t	s_isize;
+	uint16_t	s_pad0;
+	uint32_t	s_fsize;
+	uint16_t	s_nfree;
+	uint16_t	s_pad1;
+	uint32_t	s_free[SYSV_NICFREE];
+	uint16_t	s_ninode;
+	uint16_t	s_pad2;
+	uint16_t	s_inode[SYSV_NICINOD];
+	uint8_t		s_flock;
+	uint8_t		s_ilock;
+	uint8_t		s_fmod;
+	uint8_t		s_ronly;
+	uint32_t	s_time;
+	uint16_t	s_dinfo[4];
+	uint32_t	s_tfree;
+	uint16_t	s_tinode;
+	uint16_t	s_pad3;
+	uint8_t		s_fname[6];
+	uint8_t		s_fpack[6];
+	uint32_t	s_fill[12];
+	uint32_t	s_state;
+	uint32_t	s_magic;
+	uint32_t	s_type;
+};
+
+static int probe_xenix(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct xenix_super_block *sb;
+
+	sb = blkid_probe_get_sb(pr, mag, struct xenix_super_block);
+	if (!sb)
+		return errno ? -errno : 1;
+	blkid_probe_set_label(pr, sb->s_fname, sizeof(sb->s_fname));
+	return 0;
+}
+
+#define SYSV_BLOCK_SIZE	1024
+
+/* Note that we don't probe for Coherent FS, this FS does not have
+ * magic string. (It requires to probe fname/fpack field..)
+ */
+static int probe_sysv(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct sysv_super_block *sb;
+	int blocks[] = {0, 9, 15, 18};
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(blocks); i++) {
+		int off = blocks[i] * SYSV_BLOCK_SIZE + SYSV_BLOCK_SIZE/2;
+
+		sb = (struct sysv_super_block *)
+			blkid_probe_get_buffer(pr,
+					off,
+					sizeof(struct sysv_super_block));
+		if (!sb)
+			return errno ? -errno : 1;
+
+		if (sb->s_magic == cpu_to_le32(0xfd187e20) ||
+		    sb->s_magic == cpu_to_be32(0xfd187e20)) {
+
+			if (blkid_probe_set_label(pr, sb->s_fname,
+						sizeof(sb->s_fname)))
+				return 1;
+
+			if (blkid_probe_set_magic(pr,
+					off + offsetof(struct sysv_super_block,
+								s_magic),
+					sizeof(sb->s_magic),
+					(unsigned char *) &sb->s_magic))
+				return 1;
+
+			return 0;
+		}
+	}
+	return 1;
+}
+
+const struct blkid_idinfo xenix_idinfo =
+{
+	.name		= "xenix",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_xenix,
+	.magics		=
+	{
+		{ .magic = "\x2b\x55\x44", .len = 3, .kboff = 1, .sboff = 0x400 },
+		{ .magic = "\x44\x55\x2b", .len = 3, .kboff = 1, .sboff = 0x400 },
+		{ NULL }
+	}
+};
+
+const struct blkid_idinfo sysv_idinfo =
+{
+	.name		= "sysv",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_sysv,
+
+	/* SYSV is BE and LE and superblock could be on four positions. It's
+	 * simpler to probe for the magic string by .probefunc().
+	 */
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/superblocks/ubifs.c b/libblkid/src/superblocks/ubifs.c
new file mode 100644
index 0000000..dc84260
--- /dev/null
+++ b/libblkid/src/superblocks/ubifs.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2009 Corentin Chary <corentincj@iksaif.net>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+/*
+ * struct ubifs_ch - common header node.
+ * @magic: UBIFS node magic number (%UBIFS_NODE_MAGIC)
+ * @crc: CRC-32 checksum of the node header
+ * @sqnum: sequence number
+ * @len: full node length
+ * @node_type: node type
+ * @group_type: node group type
+ * @padding: reserved for future, zeroes
+ *
+ * Every UBIFS node starts with this common part. If the node has a key, the
+ * key always goes next.
+ */
+struct ubifs_ch {
+	uint32_t magic;
+	uint32_t crc;
+	uint64_t sqnum;
+	uint32_t len;
+	uint8_t node_type;
+	uint8_t group_type;
+	uint8_t padding[2];
+} __attribute__ ((packed));
+
+/*
+ * struct ubifs_sb_node - superblock node.
+ * @ch: common header
+ * @padding: reserved for future, zeroes
+ * @key_hash: type of hash function used in keys
+ * @key_fmt: format of the key
+ * @flags: file-system flags (%UBIFS_FLG_BIGLPT, etc)
+ * @min_io_size: minimal input/output unit size
+ * @leb_size: logical eraseblock size in bytes
+ * @leb_cnt: count of LEBs used by file-system
+ * @max_leb_cnt: maximum count of LEBs used by file-system
+ * @max_bud_bytes: maximum amount of data stored in buds
+ * @log_lebs: log size in logical eraseblocks
+ * @lpt_lebs: number of LEBs used for lprops table
+ * @orph_lebs: number of LEBs used for recording orphans
+ * @jhead_cnt: count of journal heads
+ * @fanout: tree fanout (max. number of links per indexing node)
+ * @lsave_cnt: number of LEB numbers in LPT's save table
+ * @fmt_version: UBIFS on-flash format version
+ * @default_compr: default compression algorithm (%UBIFS_COMPR_LZO, etc)
+ * @padding1: reserved for future, zeroes
+ * @rp_uid: reserve pool UID
+ * @rp_gid: reserve pool GID
+ * @rp_size: size of the reserved pool in bytes
+ * @padding2: reserved for future, zeroes
+ * @time_gran: time granularity in nanoseconds
+ * @uuid: UUID generated when the file system image was created
+ * @ro_compat_version: UBIFS R/O compatibility version
+ */
+struct ubifs_sb_node {
+	struct ubifs_ch ch;
+	uint8_t padding[2];
+	uint8_t key_hash;
+	uint8_t key_fmt;
+	uint32_t flags;
+	uint32_t min_io_size;
+	uint32_t leb_size;
+	uint32_t leb_cnt;
+	uint32_t max_leb_cnt;
+	uint64_t max_bud_bytes;
+	uint32_t log_lebs;
+	uint32_t lpt_lebs;
+	uint32_t orph_lebs;
+	uint32_t jhead_cnt;
+	uint32_t fanout;
+	uint32_t lsave_cnt;
+	uint32_t fmt_version;
+	uint16_t default_compr;
+	uint8_t padding1[2];
+	uint32_t rp_uid;
+	uint32_t rp_gid;
+	uint64_t rp_size;
+	uint32_t time_gran;
+	uint8_t uuid[16];
+	uint32_t ro_compat_version;
+	uint8_t padding2[3968];
+} __attribute__ ((packed));
+
+static int probe_ubifs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct ubifs_sb_node *sb;
+
+	sb = blkid_probe_get_sb(pr, mag, struct ubifs_sb_node);
+	if (!sb)
+		return errno ? -errno : 1;
+
+	blkid_probe_set_uuid(pr, sb->uuid);
+	blkid_probe_sprintf_version(pr, "w%dr%d",
+			le32_to_cpu(sb->fmt_version),
+			le32_to_cpu(sb->ro_compat_version));
+	return 0;
+}
+
+const struct blkid_idinfo ubifs_idinfo =
+{
+	.name		= "ubifs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_ubifs,
+	.magics		=
+	{
+		{ .magic = "\x31\x18\x10\x06", .len = 4 },
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/udf.c b/libblkid/src/superblocks/udf.c
new file mode 100644
index 0000000..5cde3cc
--- /dev/null
+++ b/libblkid/src/superblocks/udf.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+#include "iso9660.h"
+
+struct volume_descriptor {
+	struct descriptor_tag {
+		uint16_t	id;
+		uint16_t	version;
+		uint8_t		checksum;
+		uint8_t		reserved;
+		uint16_t	serial;
+		uint16_t	crc;
+		uint16_t	crc_len;
+		uint32_t	location;
+	} __attribute__((packed)) tag;
+
+	union {
+		struct anchor_descriptor {
+			uint32_t	length;
+			uint32_t	location;
+		} __attribute__((packed)) anchor;
+
+		struct primary_descriptor {
+			uint32_t	seq_num;
+			uint32_t	desc_num;
+			struct dstring {
+				uint8_t	clen;
+				uint8_t	c[31];
+			} __attribute__((packed)) ident;
+		} __attribute__((packed)) primary;
+
+	} __attribute__((packed)) type;
+
+} __attribute__((packed));
+
+struct volume_structure_descriptor {
+	uint8_t		type;
+	uint8_t		id[5];
+	uint8_t		version;
+} __attribute__((packed));
+
+#define UDF_VSD_OFFSET			0x8000LL
+
+static int probe_udf(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct volume_descriptor *vd;
+	struct volume_structure_descriptor *vsd;
+	unsigned int bs;
+	unsigned int pbs[2];
+	unsigned int b;
+	unsigned int type;
+	unsigned int count;
+	unsigned int loc;
+	unsigned int i;
+
+	/* The block size of a UDF filesystem is that of the underlying
+	 * storage; we check later on for the special case of image files,
+	 * which may have the 2048-byte block size of optical media. */
+	pbs[0] = blkid_probe_get_sectorsize(pr);
+	pbs[1] = 0x800;
+
+	/* check for a Volume Structure Descriptor (VSD); each is
+	 * 2048 bytes long */
+	for (b = 0; b < 0x8000; b += 0x800) {
+		vsd = (struct volume_structure_descriptor *)
+			blkid_probe_get_buffer(pr,
+					UDF_VSD_OFFSET + b,
+					sizeof(*vsd));
+		if (!vsd)
+			return errno ? -errno : 1;
+		if (vsd->id[0] != '\0')
+			goto nsr;
+	}
+	return 1;
+
+nsr:
+	/* search the list of VSDs for a NSR descriptor */
+	for (b = 0; b < 64; b++) {
+		vsd = (struct volume_structure_descriptor *)
+			blkid_probe_get_buffer(pr,
+					UDF_VSD_OFFSET + ((blkid_loff_t) b * 0x800),
+					sizeof(*vsd));
+		if (!vsd)
+			return errno ? -errno : 1;
+		if (vsd->id[0] == '\0')
+			return 1;
+		if (memcmp(vsd->id, "NSR02", 5) == 0)
+			goto anchor;
+		if (memcmp(vsd->id, "NSR03", 5) == 0)
+			goto anchor;
+	}
+	return 1;
+
+anchor:
+	/* read Anchor Volume Descriptor (AVDP), checking block size */
+	for (i = 0; i < 2; i++) {
+		vd = (struct volume_descriptor *)
+			blkid_probe_get_buffer(pr, 256 * pbs[i], sizeof(*vd));
+		if (!vd)
+			return errno ? -errno : 1;
+
+		type = le16_to_cpu(vd->tag.id);
+		if (type == 2) /* TAG_ID_AVDP */
+			goto real_blksz;
+	}
+	return 0;
+
+real_blksz:
+	/* Use the actual block size from here on out */
+	bs = pbs[i];
+
+	/* get descriptor list address and block count */
+	count = le32_to_cpu(vd->type.anchor.length) / bs;
+	loc = le32_to_cpu(vd->type.anchor.location);
+
+	/* check if the list is usable */
+	for (b = 0; b < count; b++) {
+		vd = (struct volume_descriptor *)
+			blkid_probe_get_buffer(pr,
+					(blkid_loff_t) (loc + b) * bs,
+					sizeof(*vd));
+		if (!vd)
+			return errno ? -errno : 1;
+	}
+
+	/* Try extract all possible ISO9660 information -- if there is
+	 * usable LABEL in ISO header then use it, otherwise read UDF
+	 * specific LABEL */
+	if (probe_iso9660(pr, mag) == 0 &&
+	    __blkid_probe_lookup_value(pr, "LABEL") != NULL)
+		return 0;
+
+	/* Read UDF label */
+	for (b = 0; b < count; b++) {
+		vd = (struct volume_descriptor *)
+			blkid_probe_get_buffer(pr,
+					(blkid_loff_t) (loc + b) * bs,
+					sizeof(*vd));
+		if (!vd)
+			return errno ? -errno : 1;
+		type = le16_to_cpu(vd->tag.id);
+		if (type == 0)
+			break;
+		if (le32_to_cpu(vd->tag.location) != loc + b)
+			break;
+		if (type == 1) { /* TAG_ID_PVD */
+			uint8_t clen = vd->type.primary.ident.clen;
+
+			if (clen == 8)
+				blkid_probe_set_label(pr,
+						vd->type.primary.ident.c, 31);
+			else if (clen == 16)
+				blkid_probe_set_utf8label(pr,
+						vd->type.primary.ident.c,
+						31, BLKID_ENC_UTF16BE);
+
+			if (clen == 8 || clen == 16)
+				break;
+		}
+	}
+
+	return 0;
+}
+
+
+const struct blkid_idinfo udf_idinfo =
+{
+	.name		= "udf",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_udf,
+	.flags		= BLKID_IDINFO_TOLERANT,
+	.magics		=
+	{
+		{ .magic = "BEA01", .len = 5, .kboff = 32, .sboff = 1 },
+		{ .magic = "BOOT2", .len = 5, .kboff = 32, .sboff = 1 },
+		{ .magic = "CD001", .len = 5, .kboff = 32, .sboff = 1 },
+		{ .magic = "CDW02", .len = 5, .kboff = 32, .sboff = 1 },
+		{ .magic = "NSR02", .len = 5, .kboff = 32, .sboff = 1 },
+		{ .magic = "NSR03", .len = 5, .kboff = 32, .sboff = 1 },
+		{ .magic = "TEA01", .len = 5, .kboff = 32, .sboff = 1 },
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/ufs.c b/libblkid/src/superblocks/ufs.c
new file mode 100644
index 0000000..6ef2acd
--- /dev/null
+++ b/libblkid/src/superblocks/ufs.c
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#include "superblocks.h"
+
+struct ufs_super_block {
+	uint32_t	fs_link;
+	uint32_t	fs_rlink;
+	uint32_t	fs_sblkno;
+	uint32_t	fs_cblkno;
+	uint32_t	fs_iblkno;
+	uint32_t	fs_dblkno;
+	uint32_t	fs_cgoffset;
+	uint32_t	fs_cgmask;
+	uint32_t	fs_time;
+	uint32_t	fs_size;
+	uint32_t	fs_dsize;
+	uint32_t	fs_ncg;
+	uint32_t	fs_bsize;
+	uint32_t	fs_fsize;
+	uint32_t	fs_frag;
+	uint32_t	fs_minfree;
+	uint32_t	fs_rotdelay;
+	uint32_t	fs_rps;
+	uint32_t	fs_bmask;
+	uint32_t	fs_fmask;
+	uint32_t	fs_bshift;
+	uint32_t	fs_fshift;
+	uint32_t	fs_maxcontig;
+	uint32_t	fs_maxbpg;
+	uint32_t	fs_fragshift;
+	uint32_t	fs_fsbtodb;
+	uint32_t	fs_sbsize;
+	uint32_t	fs_csmask;
+	uint32_t	fs_csshift;
+	uint32_t	fs_nindir;
+	uint32_t	fs_inopb;
+	uint32_t	fs_nspf;
+	uint32_t	fs_optim;
+	uint32_t	fs_npsect_state;
+	uint32_t	fs_interleave;
+	uint32_t	fs_trackskew;
+	uint32_t	fs_id[2];
+	uint32_t	fs_csaddr;
+	uint32_t	fs_cssize;
+	uint32_t	fs_cgsize;
+	uint32_t	fs_ntrak;
+	uint32_t	fs_nsect;
+	uint32_t	fs_spc;
+	uint32_t	fs_ncyl;
+	uint32_t	fs_cpg;
+	uint32_t	fs_ipg;
+	uint32_t	fs_fpg;
+	struct ufs_csum {
+		uint32_t	cs_ndir;
+		uint32_t	cs_nbfree;
+		uint32_t	cs_nifree;
+		uint32_t	cs_nffree;
+	} fs_cstotal;
+	int8_t		fs_fmod;
+	int8_t		fs_clean;
+	int8_t		fs_ronly;
+	int8_t		fs_flags;
+	union {
+		struct {
+			int8_t	fs_fsmnt[512];
+			uint32_t	fs_cgrotor;
+			uint32_t	fs_csp[31];
+			uint32_t	fs_maxcluster;
+			uint32_t	fs_cpc;
+			uint16_t	fs_opostbl[16][8];
+		} fs_u1;
+		struct {
+			int8_t		fs_fsmnt[468];
+			uint8_t		fs_volname[32];
+			uint64_t	fs_swuid;
+			int32_t		fs_pad;
+			uint32_t	fs_cgrotor;
+			uint32_t	fs_ocsp[28];
+			uint32_t	fs_contigdirs;
+			uint32_t	fs_csp;
+			uint32_t	fs_maxcluster;
+			uint32_t	fs_active;
+			int32_t		fs_old_cpc;
+			int32_t		fs_maxbsize;
+			int64_t		fs_sparecon64[17];
+			int64_t		fs_sblockloc;
+			struct ufs2_csum_total {
+				uint64_t	cs_ndir;
+				uint64_t	cs_nbfree;
+				uint64_t	cs_nifree;
+				uint64_t	cs_nffree;
+				uint64_t	cs_numclusters;
+				uint64_t	cs_spare[3];
+			} fs_cstotal;
+			struct ufs_timeval {
+				int32_t		tv_sec;
+				int32_t		tv_usec;
+			} fs_time;
+			int64_t		fs_size;
+			int64_t		fs_dsize;
+			uint64_t	fs_csaddr;
+			int64_t		fs_pendingblocks;
+			int32_t		fs_pendinginodes;
+		} __attribute__((packed)) fs_u2;
+	}  fs_u11;
+	union {
+		struct {
+			int32_t		fs_sparecon[53];
+			int32_t		fs_reclaim;
+			int32_t		fs_sparecon2[1];
+			int32_t		fs_state;
+			uint32_t	fs_qbmask[2];
+			uint32_t	fs_qfmask[2];
+		} fs_sun;
+		struct {
+			int32_t		fs_sparecon[53];
+			int32_t		fs_reclaim;
+			int32_t		fs_sparecon2[1];
+			uint32_t	fs_npsect;
+			uint32_t	fs_qbmask[2];
+			uint32_t	fs_qfmask[2];
+		} fs_sunx86;
+		struct {
+			int32_t		fs_sparecon[50];
+			int32_t		fs_contigsumsize;
+			int32_t		fs_maxsymlinklen;
+			int32_t		fs_inodefmt;
+			uint32_t	fs_maxfilesize[2];
+			uint32_t	fs_qbmask[2];
+			uint32_t	fs_qfmask[2];
+			int32_t		fs_state;
+		} fs_44;
+	} fs_u2;
+	int32_t		fs_postblformat;
+	int32_t		fs_nrpos;
+	int32_t		fs_postbloff;
+	int32_t		fs_rotbloff;
+	uint32_t	fs_magic;
+	uint8_t		fs_space[1];
+} __attribute__((packed));
+
+#define UFS_MAGIC			0x00011954
+#define UFS2_MAGIC			0x19540119
+#define UFS_MAGIC_FEA			0x00195612
+#define UFS_MAGIC_LFN			0x00095014
+#define UFS_MAGIC_SEC			0x00612195
+#define UFS_MAGIC_4GB			0x05231994
+
+static int probe_ufs(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	int offsets[] = { 0, 8, 64, 256 };
+	uint32_t mags[] = {
+		UFS2_MAGIC, UFS_MAGIC, UFS_MAGIC_FEA, UFS_MAGIC_LFN,
+		UFS_MAGIC_SEC, UFS_MAGIC_4GB
+	};
+	size_t i;
+	uint32_t magic;
+	struct ufs_super_block *ufs;
+	int is_be;
+
+	for (i = 0; i < ARRAY_SIZE(offsets); i++) {
+		uint32_t magLE, magBE;
+		size_t y;
+
+		ufs = (struct ufs_super_block *)
+				blkid_probe_get_buffer(pr,
+					offsets[i] * 1024,
+					sizeof(struct ufs_super_block));
+		if (!ufs)
+			return errno ? -errno : 1;
+
+		magBE = be32_to_cpu(ufs->fs_magic);
+		magLE = le32_to_cpu(ufs->fs_magic);
+
+		for (y = 0; y < ARRAY_SIZE(mags); y++) {
+			if (magLE == mags[y] || magBE == mags[y]) {
+				magic = mags[y];
+				is_be = (magBE == mags[y]);
+				goto found;
+			}
+		}
+	}
+
+	return 1;
+
+found:
+	if (magic == UFS2_MAGIC) {
+		blkid_probe_set_version(pr, "2");
+		blkid_probe_set_label(pr, ufs->fs_u11.fs_u2.fs_volname,
+				sizeof(ufs->fs_u11.fs_u2.fs_volname));
+	} else
+		blkid_probe_set_version(pr, "1");
+	if (ufs->fs_id[0] || ufs->fs_id[1])
+	{
+		if (is_be)
+			blkid_probe_sprintf_uuid(pr,
+					 (unsigned char *) &ufs->fs_id,
+						 sizeof(ufs->fs_id),
+						 "%08x%08x",
+						 be32_to_cpu(ufs->fs_id[0]),
+						 be32_to_cpu(ufs->fs_id[1]));
+		else
+			blkid_probe_sprintf_uuid(pr,
+					 (unsigned char *) &ufs->fs_id,
+						 sizeof(ufs->fs_id),
+						 "%08x%08x",
+						 le32_to_cpu(ufs->fs_id[0]),
+						 le32_to_cpu(ufs->fs_id[1]));
+	}
+
+	if (blkid_probe_set_magic(pr,
+			(offsets[i] * 1024) +
+				offsetof(struct ufs_super_block, fs_magic),
+			sizeof(ufs->fs_magic),
+			(unsigned char *) &ufs->fs_magic))
+		return 1;
+
+	return 0;
+}
+
+/*
+ * According to libvolume_id the UFS superblock could be on four positions.
+ * The original libblkid has checked one position (.kboff=8) only.
+ *
+ * We know four UFS magic strings and UFS could be both little-endian and
+ * big-endian. ... so we have:
+ *
+ *	4 position * 4 string * 2 version = 32 magic strings
+ *
+ * It seems simpler to check for these string in probing function that hardcode
+ * all in the .magic array.
+ */
+const struct blkid_idinfo ufs_idinfo =
+{
+	.name		= "ufs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_ufs,
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/superblocks/vfat.c b/libblkid/src/superblocks/vfat.c
new file mode 100644
index 0000000..f38deac
--- /dev/null
+++ b/libblkid/src/superblocks/vfat.c
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+/* Yucky misaligned values */
+struct vfat_super_block {
+/* 00*/	unsigned char	vs_ignored[3];
+/* 03*/	unsigned char	vs_sysid[8];
+/* 0b*/	unsigned char	vs_sector_size[2];
+/* 0d*/	uint8_t		vs_cluster_size;
+/* 0e*/	uint16_t	vs_reserved;
+/* 10*/	uint8_t		vs_fats;
+/* 11*/	unsigned char	vs_dir_entries[2];
+/* 13*/	unsigned char	vs_sectors[2];
+/* 15*/	unsigned char	vs_media;
+/* 16*/	uint16_t	vs_fat_length;
+/* 18*/	uint16_t	vs_secs_track;
+/* 1a*/	uint16_t	vs_heads;
+/* 1c*/	uint32_t	vs_hidden;
+/* 20*/	uint32_t	vs_total_sect;
+/* 24*/	uint32_t	vs_fat32_length;
+/* 28*/	uint16_t	vs_flags;
+/* 2a*/	uint8_t		vs_version[2];
+/* 2c*/	uint32_t	vs_root_cluster;
+/* 30*/	uint16_t	vs_fsinfo_sector;
+/* 32*/	uint16_t	vs_backup_boot;
+/* 34*/	uint16_t	vs_reserved2[6];
+/* 40*/	unsigned char	vs_unknown[3];
+/* 43*/	unsigned char	vs_serno[4];
+/* 47*/	unsigned char	vs_label[11];
+/* 52*/	unsigned char   vs_magic[8];
+/* 5a*/	unsigned char	vs_dummy2[0x1fe - 0x5a];
+/*1fe*/	unsigned char	vs_pmagic[2];
+} __attribute__((packed));
+
+/* Yucky misaligned values */
+struct msdos_super_block {
+/* 00*/	unsigned char	ms_ignored[3];
+/* 03*/	unsigned char	ms_sysid[8];
+/* 0b*/	unsigned char	ms_sector_size[2];
+/* 0d*/	uint8_t		ms_cluster_size;
+/* 0e*/	uint16_t	ms_reserved;
+/* 10*/	uint8_t		ms_fats;
+/* 11*/	unsigned char	ms_dir_entries[2];
+/* 13*/	unsigned char	ms_sectors[2]; /* =0 iff V3 or later */
+/* 15*/	unsigned char	ms_media;
+/* 16*/	uint16_t	ms_fat_length; /* Sectors per FAT */
+/* 18*/	uint16_t	ms_secs_track;
+/* 1a*/	uint16_t	ms_heads;
+/* 1c*/	uint32_t	ms_hidden;
+/* V3 BPB */
+/* 20*/	uint32_t	ms_total_sect; /* iff ms_sectors == 0 */
+/* V4 BPB */
+/* 24*/	unsigned char	ms_unknown[3]; /* Phys drive no., resvd, V4 sig (0x29) */
+/* 27*/	unsigned char	ms_serno[4];
+/* 2b*/	unsigned char	ms_label[11];
+/* 36*/	unsigned char   ms_magic[8];
+/* 3e*/	unsigned char	ms_dummy2[0x1fe - 0x3e];
+/*1fe*/	unsigned char	ms_pmagic[2];
+} __attribute__((packed));
+
+struct vfat_dir_entry {
+	uint8_t		name[11];
+	uint8_t		attr;
+	uint16_t	time_creat;
+	uint16_t	date_creat;
+	uint16_t	time_acc;
+	uint16_t	date_acc;
+	uint16_t	cluster_high;
+	uint16_t	time_write;
+	uint16_t	date_write;
+	uint16_t	cluster_low;
+	uint32_t	size;
+} __attribute__((packed));
+
+struct fat32_fsinfo {
+	uint8_t signature1[4];
+	uint32_t reserved1[120];
+	uint8_t signature2[4];
+	uint32_t free_clusters;
+	uint32_t next_cluster;
+	uint32_t reserved2[4];
+} __attribute__((packed));
+
+/* maximum number of clusters */
+#define FAT12_MAX 0xFF4
+#define FAT16_MAX 0xFFF4
+#define FAT32_MAX 0x0FFFFFF6
+
+#define FAT_ATTR_VOLUME_ID		0x08
+#define FAT_ATTR_DIR			0x10
+#define FAT_ATTR_LONG_NAME		0x0f
+#define FAT_ATTR_MASK			0x3f
+#define FAT_ENTRY_FREE			0xe5
+
+static const char *no_name = "NO NAME    ";
+
+#define unaligned_le16(x) \
+		(((unsigned char *) x)[0] + (((unsigned char *) x)[1] << 8))
+
+/*
+ * Look for LABEL (name) in the FAT root directory.
+ */
+static unsigned char *search_fat_label(blkid_probe pr,
+				uint64_t offset, uint32_t entries)
+{
+	struct vfat_dir_entry *ent, *dir = NULL;
+	uint32_t i;
+
+	DBG(LOWPROBE, ul_debug("\tlook for label in root-dir "
+			"(entries: %d, offset: %jd)", entries, offset));
+
+	if (!blkid_probe_is_tiny(pr)) {
+		/* large disk, read whole root directory */
+		dir = (struct vfat_dir_entry *)
+			blkid_probe_get_buffer(pr,
+					offset,
+					(blkid_loff_t) entries *
+						sizeof(struct vfat_dir_entry));
+		if (!dir)
+			return NULL;
+	}
+
+	for (i = 0; i < entries; i++) {
+		/*
+		 * The root directory could be relatively large (4-16kB).
+		 * Fortunately, the LABEL is usually the first entry in the
+		 * directory. On tiny disks we call read() per entry.
+		 */
+		if (!dir)
+			ent = (struct vfat_dir_entry *)
+				blkid_probe_get_buffer(pr,
+					(blkid_loff_t) offset + (i *
+						sizeof(struct vfat_dir_entry)),
+					sizeof(struct vfat_dir_entry));
+		else
+			ent = &dir[i];
+
+		if (!ent || ent->name[0] == 0x00)
+			break;
+
+		if ((ent->name[0] == FAT_ENTRY_FREE) ||
+		    (ent->cluster_high != 0 || ent->cluster_low != 0) ||
+		    ((ent->attr & FAT_ATTR_MASK) == FAT_ATTR_LONG_NAME))
+			continue;
+
+		if ((ent->attr & (FAT_ATTR_VOLUME_ID | FAT_ATTR_DIR)) ==
+		    FAT_ATTR_VOLUME_ID) {
+			DBG(LOWPROBE, ul_debug("\tfound fs LABEL at entry %d", i));
+			return ent->name;
+		}
+	}
+	return NULL;
+}
+
+static int fat_valid_superblock(const struct blkid_idmag *mag,
+			struct msdos_super_block *ms,
+			struct vfat_super_block *vs,
+			uint32_t *cluster_count, uint32_t *fat_size)
+{
+	uint16_t sector_size, dir_entries, reserved;
+	uint32_t sect_count, __fat_size, dir_size, __cluster_count, fat_length;
+	uint32_t max_count;
+
+	/* extra check for FATs without magic strings */
+	if (mag->len <= 2) {
+		/* Old floppies have a valid MBR signature */
+		if (ms->ms_pmagic[0] != 0x55 || ms->ms_pmagic[1] != 0xAA)
+			return 0;
+
+		/*
+		 * OS/2 and apparently DFSee will place a FAT12/16-like
+		 * pseudo-superblock in the first 512 bytes of non-FAT
+		 * filesystems --- at least JFS and HPFS, and possibly others.
+		 * So we explicitly check for those filesystems at the
+		 * FAT12/16 filesystem magic field identifier, and if they are
+		 * present, we rule this out as a FAT filesystem, despite the
+		 * FAT-like pseudo-header.
+		 */
+		if ((memcmp(ms->ms_magic, "JFS     ", 8) == 0) ||
+		    (memcmp(ms->ms_magic, "HPFS    ", 8) == 0))
+			return 0;
+	}
+
+	/* fat counts(Linux kernel expects at least 1 FAT table) */
+	if (!ms->ms_fats)
+		return 0;
+	if (!ms->ms_reserved)
+		return 0;
+	if (!(0xf8 <= ms->ms_media || ms->ms_media == 0xf0))
+		return 0;
+	if (!is_power_of_2(ms->ms_cluster_size))
+		return 0;
+
+	sector_size = unaligned_le16(&ms->ms_sector_size);
+	if (!is_power_of_2(sector_size) ||
+	    sector_size < 512 || sector_size > 4096)
+		return 0;
+
+	dir_entries = unaligned_le16(&ms->ms_dir_entries);
+	reserved =  le16_to_cpu(ms->ms_reserved);
+	sect_count = unaligned_le16(&ms->ms_sectors);
+
+	if (sect_count == 0)
+		sect_count = le32_to_cpu(ms->ms_total_sect);
+
+	fat_length = le16_to_cpu(ms->ms_fat_length);
+	if (fat_length == 0)
+		fat_length = le32_to_cpu(vs->vs_fat32_length);
+
+	__fat_size = fat_length * ms->ms_fats;
+	dir_size = ((dir_entries * sizeof(struct vfat_dir_entry)) +
+					(sector_size-1)) / sector_size;
+
+	__cluster_count = (sect_count - (reserved + __fat_size + dir_size)) /
+							ms->ms_cluster_size;
+	if (!ms->ms_fat_length && vs->vs_fat32_length)
+		max_count = FAT32_MAX;
+	else
+		max_count = __cluster_count > FAT12_MAX ? FAT16_MAX : FAT12_MAX;
+
+	if (__cluster_count > max_count)
+		return 0;
+
+	if (fat_size)
+		*fat_size = __fat_size;
+	if (cluster_count)
+		*cluster_count = __cluster_count;
+
+	return 1;	/* valid */
+}
+
+/*
+ * This function is used by MBR partition table parser to avoid
+ * misinterpretation of FAT filesystem.
+ */
+int blkid_probe_is_vfat(blkid_probe pr)
+{
+	struct vfat_super_block *vs;
+	struct msdos_super_block *ms;
+	const struct blkid_idmag *mag = NULL;
+	int rc;
+
+	rc = blkid_probe_get_idmag(pr, &vfat_idinfo, NULL, &mag);
+	if (rc < 0)
+		return rc;	/* error */
+	if (rc != BLKID_PROBE_OK || !mag)
+		return 0;
+
+	ms = blkid_probe_get_sb(pr, mag, struct msdos_super_block);
+	if (!ms)
+		return errno ? -errno : 0;
+	vs = blkid_probe_get_sb(pr, mag, struct vfat_super_block);
+	if (!vs)
+		return errno ? -errno : 0;
+
+	return fat_valid_superblock(mag, ms, vs, NULL, NULL);
+}
+
+/* FAT label extraction from the root directory taken from Kay
+ * Sievers's volume_id library */
+static int probe_vfat(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct vfat_super_block *vs;
+	struct msdos_super_block *ms;
+	const unsigned char *vol_label = 0;
+	unsigned char *vol_serno = NULL, vol_label_buf[11];
+	uint16_t sector_size = 0, reserved;
+	uint32_t cluster_count, fat_size;
+	const char *version = NULL;
+
+	ms = blkid_probe_get_sb(pr, mag, struct msdos_super_block);
+	if (!ms)
+		return errno ? -errno : 1;
+
+	vs = blkid_probe_get_sb(pr, mag, struct vfat_super_block);
+	if (!vs)
+		return errno ? -errno : 1;
+
+	if (!fat_valid_superblock(mag, ms, vs, &cluster_count, &fat_size))
+		return 1;
+
+	sector_size = unaligned_le16(&ms->ms_sector_size);
+	reserved =  le16_to_cpu(ms->ms_reserved);
+
+	if (ms->ms_fat_length) {
+		/* the label may be an attribute in the root directory */
+		uint32_t root_start = (reserved + fat_size) * sector_size;
+		uint32_t root_dir_entries = unaligned_le16(&vs->vs_dir_entries);
+
+		vol_label = search_fat_label(pr, root_start, root_dir_entries);
+		if (vol_label) {
+			memcpy(vol_label_buf, vol_label, 11);
+			vol_label = vol_label_buf;
+		}
+
+		if (!vol_label || !memcmp(vol_label, no_name, 11))
+			vol_label = ms->ms_label;
+		vol_serno = ms->ms_serno;
+
+		blkid_probe_set_value(pr, "SEC_TYPE", (unsigned char *) "msdos",
+                              sizeof("msdos"));
+
+		if (cluster_count < FAT12_MAX)
+			version = "FAT12";
+		else if (cluster_count < FAT16_MAX)
+			version = "FAT16";
+
+	} else if (vs->vs_fat32_length) {
+		unsigned char *buf;
+		uint16_t fsinfo_sect;
+		int maxloop = 100;
+
+		/* Search the FAT32 root dir for the label attribute */
+		uint32_t buf_size = vs->vs_cluster_size * sector_size;
+		uint32_t start_data_sect = reserved + fat_size;
+		uint32_t entries = le32_to_cpu(vs->vs_fat32_length) *
+					sector_size / sizeof(uint32_t);
+		uint32_t next = le32_to_cpu(vs->vs_root_cluster);
+
+		while (next && next < entries && --maxloop) {
+			uint32_t next_sect_off;
+			uint64_t next_off, fat_entry_off;
+			int count;
+
+			next_sect_off = (next - 2) * vs->vs_cluster_size;
+			next_off = (uint64_t)(start_data_sect + next_sect_off) *
+				sector_size;
+
+			count = buf_size / sizeof(struct vfat_dir_entry);
+
+			vol_label = search_fat_label(pr, next_off, count);
+			if (vol_label) {
+				memcpy(vol_label_buf, vol_label, 11);
+				vol_label = vol_label_buf;
+				break;
+			}
+
+			/* get FAT entry */
+			fat_entry_off = ((uint64_t) reserved * sector_size) +
+				(next * sizeof(uint32_t));
+			buf = blkid_probe_get_buffer(pr, fat_entry_off, buf_size);
+			if (buf == NULL)
+				break;
+
+			/* set next cluster */
+			next = le32_to_cpu(*((uint32_t *) buf)) & 0x0fffffff;
+		}
+
+		version = "FAT32";
+
+		if (!vol_label || !memcmp(vol_label, no_name, 11))
+			vol_label = vs->vs_label;
+		vol_serno = vs->vs_serno;
+
+		/*
+		 * FAT32 should have a valid signature in the fsinfo block,
+		 * but also allow all bytes set to '\0', because some volumes
+		 * do not set the signature at all.
+		 */
+		fsinfo_sect = le16_to_cpu(vs->vs_fsinfo_sector);
+		if (fsinfo_sect) {
+			struct fat32_fsinfo *fsinfo;
+
+			buf = blkid_probe_get_buffer(pr,
+					(blkid_loff_t) fsinfo_sect * sector_size,
+					sizeof(struct fat32_fsinfo));
+			if (buf == NULL)
+				return errno ? -errno : 1;
+
+			fsinfo = (struct fat32_fsinfo *) buf;
+			if (memcmp(fsinfo->signature1, "\x52\x52\x61\x41", 4) != 0 &&
+			    memcmp(fsinfo->signature1, "\x52\x52\x64\x41", 4) != 0 &&
+			    memcmp(fsinfo->signature1, "\x00\x00\x00\x00", 4) != 0)
+				return 1;
+			if (memcmp(fsinfo->signature2, "\x72\x72\x41\x61", 4) != 0 &&
+			    memcmp(fsinfo->signature2, "\x00\x00\x00\x00", 4) != 0)
+				return 1;
+		}
+	}
+
+	if (vol_label && memcmp(vol_label, no_name, 11))
+		blkid_probe_set_label(pr, (unsigned char *) vol_label, 11);
+
+	/* We can't just print them as %04X, because they are unaligned */
+	if (vol_serno)
+		blkid_probe_sprintf_uuid(pr, vol_serno, 4, "%02X%02X-%02X%02X",
+			vol_serno[3], vol_serno[2], vol_serno[1], vol_serno[0]);
+	if (version)
+		blkid_probe_set_version(pr, version);
+
+	return 0;
+}
+
+
+const struct blkid_idinfo vfat_idinfo =
+{
+	.name		= "vfat",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_vfat,
+	.magics		=
+	{
+		{ .magic = "MSWIN",    .len = 5, .sboff = 0x52 },
+		{ .magic = "FAT32   ", .len = 8, .sboff = 0x52 },
+		{ .magic = "MSDOS",    .len = 5, .sboff = 0x36 },
+		{ .magic = "FAT16   ", .len = 8, .sboff = 0x36 },
+		{ .magic = "FAT12   ", .len = 8, .sboff = 0x36 },
+		{ .magic = "FAT     ", .len = 8, .sboff = 0x36 },
+		{ .magic = "\353",     .len = 1, },
+		{ .magic = "\351",     .len = 1, },
+		{ .magic = "\125\252", .len = 2, .sboff = 0x1fe },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/via_raid.c b/libblkid/src/superblocks/via_raid.c
new file mode 100644
index 0000000..ee3ab65
--- /dev/null
+++ b/libblkid/src/superblocks/via_raid.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * Inspired by libvolume_id by
+ *     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct via_metadata {
+	uint16_t	signature;
+	uint8_t		version_number;
+	struct via_array {
+		uint16_t	disk_bit_mask;
+		uint8_t		disk_array_ex;
+		uint32_t	capacity_low;
+		uint32_t	capacity_high;
+		uint32_t	serial_checksum;
+	} __attribute__((packed)) array;
+	uint32_t	serial_checksum[8];
+	uint8_t		checksum;
+} __attribute__((packed));
+
+#define VIA_SIGNATURE		0xAA55
+
+/* 8 bit checksum on first 50 bytes of metadata. */
+static uint8_t via_checksum(struct via_metadata *v)
+{
+	uint8_t i = 50, cs = 0;
+
+	while (i--)
+		cs += ((uint8_t*) v)[i];
+
+	return cs;
+}
+
+static int probe_viaraid(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	uint64_t off;
+	struct via_metadata *v;
+
+	if (pr->size < 0x10000)
+		return 1;
+	if (!S_ISREG(pr->mode) && !blkid_probe_is_wholedisk(pr))
+		return 1;
+
+	off = ((pr->size / 0x200)-1) * 0x200;
+
+	v = (struct via_metadata *)
+			blkid_probe_get_buffer(pr,
+				off,
+				sizeof(struct via_metadata));
+	if (!v)
+		return errno ? -errno : 1;
+
+	if (le16_to_cpu(v->signature) != VIA_SIGNATURE)
+		return 1;
+	if (v->version_number > 2)
+		return 1;
+	if (!blkid_probe_verify_csum(pr, via_checksum(v), v->checksum))
+		return 1;
+
+	if (blkid_probe_sprintf_version(pr, "%u", v->version_number) != 0)
+		return 1;
+	if (blkid_probe_set_magic(pr, off,
+				sizeof(v->signature),
+				(unsigned char *) &v->signature))
+		return 1;
+	return 0;
+}
+
+const struct blkid_idinfo viaraid_idinfo = {
+	.name		= "via_raid_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_viaraid,
+	.magics		= BLKID_NONE_MAGIC
+};
+
+
diff --git a/libblkid/src/superblocks/vmfs.c b/libblkid/src/superblocks/vmfs.c
new file mode 100644
index 0000000..fac87ab
--- /dev/null
+++ b/libblkid/src/superblocks/vmfs.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2009 Mike Hommey <mh@glandium.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include "superblocks.h"
+
+struct vmfs_fs_info {
+	uint32_t magic;
+	uint32_t volume_version;
+	uint8_t version;
+	uint8_t uuid[16];
+	uint32_t mode;
+	char label[128];
+} __attribute__ ((__packed__));
+
+struct vmfs_volume_info {
+	uint32_t magic;
+	uint32_t ver;
+	uint8_t irrelevant[122];
+	uint8_t uuid[16];
+} __attribute__ ((__packed__));
+
+static int probe_vmfs_fs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct vmfs_fs_info *header;
+
+	header = blkid_probe_get_sb(pr, mag, struct vmfs_fs_info);
+	if (header == NULL)
+		return errno ? -errno : 1;
+
+	blkid_probe_sprintf_uuid(pr, (unsigned char *) header->uuid, 16,
+		"%02x%02x%02x%02x-%02x%02x%02x%02x-"
+		"%02x%02x-%02x%02x%02x%02x%02x%02x",
+		header->uuid[3], header->uuid[2], header->uuid[1],
+		header->uuid[0], header->uuid[7], header->uuid[6],
+		header->uuid[5], header->uuid[4], header->uuid[9],
+		header->uuid[8], header->uuid[10], header->uuid[11],
+		header->uuid[12], header->uuid[13], header->uuid[14],
+		header->uuid[15]);
+
+	blkid_probe_set_label(pr, (unsigned char *) header->label,
+					sizeof(header->label));
+	blkid_probe_sprintf_version(pr, "%u", header->version);
+	return 0;
+}
+
+static int probe_vmfs_volume(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct vmfs_volume_info *header;
+	unsigned char *lvm_uuid;
+
+	header = blkid_probe_get_sb(pr, mag, struct vmfs_volume_info);
+	if (header == NULL)
+		return errno ? -errno : 1;
+
+	blkid_probe_sprintf_value(pr, "UUID_SUB",
+		"%02x%02x%02x%02x-%02x%02x%02x%02x-"
+		"%02x%02x-%02x%02x%02x%02x%02x%02x",
+		header->uuid[3], header->uuid[2], header->uuid[1],
+		header->uuid[0], header->uuid[7], header->uuid[6],
+		header->uuid[5], header->uuid[4], header->uuid[9],
+		header->uuid[8], header->uuid[10], header->uuid[11],
+		header->uuid[12], header->uuid[13], header->uuid[14],
+		header->uuid[15]);
+	blkid_probe_sprintf_version(pr, "%u", le32_to_cpu(header->ver));
+
+	lvm_uuid = blkid_probe_get_buffer(pr,
+				1024 * 1024 /* Start of the volume info */
+				+ 512 /* Offset to lvm info */
+				+ 20 /* Offset in lvm info */, 35);
+	if (lvm_uuid)
+		blkid_probe_strncpy_uuid(pr, lvm_uuid, 35);
+
+	return 0;
+}
+
+const struct blkid_idinfo vmfs_fs_idinfo =
+{
+	.name		= "VMFS",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_vmfs_fs,
+	.magics		=
+	{
+		{ .magic = "\x5e\xf1\xab\x2f", .len = 4, .kboff = 2048 },
+		{ NULL }
+	}
+};
+
+const struct blkid_idinfo vmfs_volume_idinfo =
+{
+	.name		= "VMFS_volume_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_vmfs_volume,
+	.magics		=
+	{
+		{ .magic = "\x0d\xd0\x01\xc0", .len = 4, .kboff = 1024 },
+		{ NULL }
+	}
+};
diff --git a/libblkid/src/superblocks/vxfs.c b/libblkid/src/superblocks/vxfs.c
new file mode 100644
index 0000000..19d284c
--- /dev/null
+++ b/libblkid/src/superblocks/vxfs.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+
+#include "superblocks.h"
+
+struct vxfs_super_block {
+	uint32_t		vs_magic;
+	int32_t			vs_version;
+};
+
+static int probe_vxfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct vxfs_super_block *vxs;
+
+	vxs = blkid_probe_get_sb(pr, mag, struct vxfs_super_block);
+	if (!vxs)
+		return errno ? -errno : 1;
+
+	blkid_probe_sprintf_version(pr, "%u", (unsigned int) vxs->vs_version);
+	return 0;
+}
+
+
+const struct blkid_idinfo vxfs_idinfo =
+{
+	.name		= "vxfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_vxfs,
+	.magics		=
+	{
+		{ .magic = "\365\374\001\245", .len = 4, .kboff = 1 },
+		{ NULL }
+	}
+};
+
diff --git a/libblkid/src/superblocks/xfs.c b/libblkid/src/superblocks/xfs.c
new file mode 100644
index 0000000..a6c04a2
--- /dev/null
+++ b/libblkid/src/superblocks/xfs.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 1999 by Andries Brouwer
+ * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
+ * Copyright (C) 2001 by Andreas Dilger
+ * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2013 Eric Sandeen <sandeen@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "superblocks.h"
+
+struct xfs_super_block {
+	uint32_t	sb_magicnum;	/* magic number == XFS_SB_MAGIC */
+	uint32_t	sb_blocksize;	/* logical block size, bytes */
+	uint64_t	sb_dblocks;	/* number of data blocks */
+	uint64_t	sb_rblocks;	/* number of realtime blocks */
+	uint64_t	sb_rextents;	/* number of realtime extents */
+	unsigned char	sb_uuid[16];	/* file system unique id */
+	uint64_t	sb_logstart;	/* starting block of log if internal */
+	uint64_t	sb_rootino;	/* root inode number */
+	uint64_t	sb_rbmino;	/* bitmap inode for realtime extents */
+	uint64_t	sb_rsumino;	/* summary inode for rt bitmap */
+	uint32_t	sb_rextsize;	/* realtime extent size, blocks */
+	uint32_t	sb_agblocks;	/* size of an allocation group */
+	uint32_t	sb_agcount;	/* number of allocation groups */
+	uint32_t	sb_rbmblocks;	/* number of rt bitmap blocks */
+	uint32_t	sb_logblocks;	/* number of log blocks */
+
+	uint16_t	sb_versionnum;	/* header version == XFS_SB_VERSION */
+	uint16_t	sb_sectsize;	/* volume sector size, bytes */
+	uint16_t	sb_inodesize;	/* inode size, bytes */
+	uint16_t	sb_inopblock;	/* inodes per block */
+	char		sb_fname[12];	/* file system name */
+	uint8_t		sb_blocklog;	/* log2 of sb_blocksize */
+	uint8_t		sb_sectlog;	/* log2 of sb_sectsize */
+	uint8_t		sb_inodelog;	/* log2 of sb_inodesize */
+	uint8_t		sb_inopblog;	/* log2 of sb_inopblock */
+	uint8_t		sb_agblklog;	/* log2 of sb_agblocks (rounded up) */
+	uint8_t		sb_rextslog;	/* log2 of sb_rextents */
+	uint8_t		sb_inprogress;	/* mkfs is in progress, don't mount */
+	uint8_t		sb_imax_pct;	/* max % of fs for inode space */
+					/* statistics */
+	uint64_t	sb_icount;	/* allocated inodes */
+	uint64_t	sb_ifree;	/* free inodes */
+	uint64_t	sb_fdblocks;	/* free data blocks */
+	uint64_t	sb_frextents;	/* free realtime extents */
+
+	/* this is not all... but enough for libblkid */
+
+} __attribute__((packed));
+
+#define XFS_MIN_BLOCKSIZE_LOG	9	/* i.e. 512 bytes */
+#define XFS_MAX_BLOCKSIZE_LOG	16	/* i.e. 65536 bytes */
+#define XFS_MIN_BLOCKSIZE	(1 << XFS_MIN_BLOCKSIZE_LOG)
+#define XFS_MAX_BLOCKSIZE	(1 << XFS_MAX_BLOCKSIZE_LOG)
+#define XFS_MIN_SECTORSIZE_LOG	9	/* i.e. 512 bytes */
+#define XFS_MAX_SECTORSIZE_LOG	15	/* i.e. 32768 bytes */
+#define XFS_MIN_SECTORSIZE	(1 << XFS_MIN_SECTORSIZE_LOG)
+#define XFS_MAX_SECTORSIZE	(1 << XFS_MAX_SECTORSIZE_LOG)
+
+#define	XFS_DINODE_MIN_LOG	8
+#define	XFS_DINODE_MAX_LOG	11
+#define	XFS_DINODE_MIN_SIZE	(1 << XFS_DINODE_MIN_LOG)
+#define	XFS_DINODE_MAX_SIZE	(1 << XFS_DINODE_MAX_LOG)
+
+#define	XFS_MAX_RTEXTSIZE	(1024 * 1024 * 1024)	/* 1GB */
+#define	XFS_DFL_RTEXTSIZE	(64 * 1024)	        /* 64kB */
+#define	XFS_MIN_RTEXTSIZE	(4 * 1024)		/* 4kB */
+
+#define XFS_MIN_AG_BLOCKS	64
+#define XFS_MAX_DBLOCKS(s) ((uint64_t)(s)->sb_agcount * (s)->sb_agblocks)
+#define XFS_MIN_DBLOCKS(s) ((uint64_t)((s)->sb_agcount - 1) *	\
+			 (s)->sb_agblocks + XFS_MIN_AG_BLOCKS)
+
+
+static void sb_from_disk(struct xfs_super_block *from,
+			 struct xfs_super_block *to)
+{
+
+	to->sb_magicnum = be32_to_cpu(from->sb_magicnum);
+	to->sb_blocksize = be32_to_cpu(from->sb_blocksize);
+	to->sb_dblocks = be64_to_cpu(from->sb_dblocks);
+	to->sb_rblocks = be64_to_cpu(from->sb_rblocks);
+	to->sb_rextents = be64_to_cpu(from->sb_rextents);
+	to->sb_logstart = be64_to_cpu(from->sb_logstart);
+	to->sb_rootino = be64_to_cpu(from->sb_rootino);
+	to->sb_rbmino = be64_to_cpu(from->sb_rbmino);
+	to->sb_rsumino = be64_to_cpu(from->sb_rsumino);
+	to->sb_rextsize = be32_to_cpu(from->sb_rextsize);
+	to->sb_agblocks = be32_to_cpu(from->sb_agblocks);
+	to->sb_agcount = be32_to_cpu(from->sb_agcount);
+	to->sb_rbmblocks = be32_to_cpu(from->sb_rbmblocks);
+	to->sb_logblocks = be32_to_cpu(from->sb_logblocks);
+	to->sb_versionnum = be16_to_cpu(from->sb_versionnum);
+	to->sb_sectsize = be16_to_cpu(from->sb_sectsize);
+	to->sb_inodesize = be16_to_cpu(from->sb_inodesize);
+	to->sb_inopblock = be16_to_cpu(from->sb_inopblock);
+	to->sb_blocklog = from->sb_blocklog;
+	to->sb_sectlog = from->sb_sectlog;
+	to->sb_inodelog = from->sb_inodelog;
+	to->sb_inopblog = from->sb_inopblog;
+	to->sb_agblklog = from->sb_agblklog;
+	to->sb_rextslog = from->sb_rextslog;
+	to->sb_inprogress = from->sb_inprogress;
+	to->sb_imax_pct = from->sb_imax_pct;
+	to->sb_icount = be64_to_cpu(from->sb_icount);
+	to->sb_ifree = be64_to_cpu(from->sb_ifree);
+	to->sb_fdblocks = be64_to_cpu(from->sb_fdblocks);
+	to->sb_frextents = be64_to_cpu(from->sb_frextents);
+}
+
+static int xfs_verify_sb(struct xfs_super_block *ondisk)
+{
+	struct xfs_super_block sb, *sbp = &sb;
+
+	/* beXX_to_cpu(), but don't convert UUID and fsname! */
+	sb_from_disk(ondisk, sbp);
+
+	/* sanity checks, we don't want to rely on magic string only */
+	if (sbp->sb_agcount <= 0					||
+	    sbp->sb_sectsize < XFS_MIN_SECTORSIZE			||
+	    sbp->sb_sectsize > XFS_MAX_SECTORSIZE			||
+	    sbp->sb_sectlog < XFS_MIN_SECTORSIZE_LOG			||
+	    sbp->sb_sectlog > XFS_MAX_SECTORSIZE_LOG			||
+	    sbp->sb_sectsize != (1 << sbp->sb_sectlog)			||
+	    sbp->sb_blocksize < XFS_MIN_BLOCKSIZE			||
+	    sbp->sb_blocksize > XFS_MAX_BLOCKSIZE			||
+	    sbp->sb_blocklog < XFS_MIN_BLOCKSIZE_LOG			||
+	    sbp->sb_blocklog > XFS_MAX_BLOCKSIZE_LOG			||
+	    sbp->sb_blocksize != (1 << sbp->sb_blocklog)		||
+	    sbp->sb_inodesize < XFS_DINODE_MIN_SIZE			||
+	    sbp->sb_inodesize > XFS_DINODE_MAX_SIZE			||
+	    sbp->sb_inodelog < XFS_DINODE_MIN_LOG			||
+	    sbp->sb_inodelog > XFS_DINODE_MAX_LOG			||
+	    sbp->sb_inodesize != (1 << sbp->sb_inodelog)		||
+	    (sbp->sb_blocklog - sbp->sb_inodelog != sbp->sb_inopblog)	||
+	    (sbp->sb_rextsize * sbp->sb_blocksize > XFS_MAX_RTEXTSIZE)	||
+	    (sbp->sb_rextsize * sbp->sb_blocksize < XFS_MIN_RTEXTSIZE)	||
+	    (sbp->sb_imax_pct > 100 /* zero sb_imax_pct is valid */)	||
+	    sbp->sb_dblocks == 0					||
+	    sbp->sb_dblocks > XFS_MAX_DBLOCKS(sbp)			||
+	    sbp->sb_dblocks < XFS_MIN_DBLOCKS(sbp))
+		return 0;
+
+	/* TODO: version 5 has also checksum CRC32, maybe we can check it too */
+
+	return 1;
+}
+
+static int probe_xfs(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	struct xfs_super_block *xs;
+
+	xs = blkid_probe_get_sb(pr, mag, struct xfs_super_block);
+	if (!xs)
+		return errno ? -errno : 1;
+
+	if (!xfs_verify_sb(xs))
+		return 1;
+
+	if (strlen(xs->sb_fname))
+		blkid_probe_set_label(pr, (unsigned char *) xs->sb_fname,
+				sizeof(xs->sb_fname));
+	blkid_probe_set_uuid(pr, xs->sb_uuid);
+	return 0;
+}
+
+const struct blkid_idinfo xfs_idinfo =
+{
+	.name		= "xfs",
+	.usage		= BLKID_USAGE_FILESYSTEM,
+	.probefunc	= probe_xfs,
+	.magics		=
+	{
+		{ .magic = "XFSB", .len = 4 },
+		{ NULL }
+	}
+};
+
+struct xlog_rec_header {
+	uint32_t	h_magicno;
+	uint32_t	h_dummy1[1];
+	uint32_t	h_version;
+	uint32_t	h_len;
+	uint32_t	h_dummy2[71];
+	uint32_t	h_fmt;
+	unsigned char	h_uuid[16];
+} __attribute__((packed));
+
+#define XLOG_HEADER_MAGIC_NUM 0xFEEDbabe
+
+/*
+ * For very small filesystems, the minimum log size
+ * can be smaller, but that seems vanishingly unlikely
+ * when used with an external log (which is used for
+ * performance reasons; tiny conflicts with that goal).
+ */
+#define XFS_MIN_LOG_BYTES	(10 * 1024 * 1024)
+
+#define XLOG_FMT_LINUX_LE	1
+#define XLOG_FMT_LINUX_BE	2
+#define XLOG_FMT_IRIX_BE	3
+
+#define XLOG_VERSION_1		1
+#define XLOG_VERSION_2		2	/* Large IClogs, Log sunit */
+#define XLOG_VERSION_OKBITS	(XLOG_VERSION_1 | XLOG_VERSION_2)
+
+static int xlog_valid_rec_header(struct xlog_rec_header *rhead)
+{
+	uint32_t hlen;
+
+	if (rhead->h_magicno != cpu_to_be32(XLOG_HEADER_MAGIC_NUM))
+		return 0;
+
+	if (!rhead->h_version ||
+            (be32_to_cpu(rhead->h_version) & (~XLOG_VERSION_OKBITS)))
+		return 0;
+
+	/* LR body must have data or it wouldn't have been written */
+	hlen = be32_to_cpu(rhead->h_len);
+	if (hlen <= 0 || hlen > INT_MAX)
+		return 0;
+
+	if (rhead->h_fmt != cpu_to_be32(XLOG_FMT_LINUX_LE) &&
+	    rhead->h_fmt != cpu_to_be32(XLOG_FMT_LINUX_BE) &&
+	    rhead->h_fmt != cpu_to_be32(XLOG_FMT_IRIX_BE))
+		return 0;
+
+	return 1;
+}
+
+/* xlog record header will be in some sector in the first 256k */
+static int probe_xfs_log(blkid_probe pr, const struct blkid_idmag *mag)
+{
+	int i;
+	struct xlog_rec_header *rhead;
+	unsigned char *buf;
+
+	buf = blkid_probe_get_buffer(pr, 0, 256*1024);
+	if (!buf)
+		return errno ? -errno : 1;
+
+	if (memcmp(buf, "XFSB", 4) == 0)
+		return 1;			/* this is regular XFS, ignore */
+
+	/* check the first 512 512-byte sectors */
+	for (i = 0; i < 512; i++) {
+		rhead = (struct xlog_rec_header *)&buf[i*512];
+
+		if (xlog_valid_rec_header(rhead)) {
+			blkid_probe_set_uuid_as(pr, rhead->h_uuid, "LOGUUID");
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+const struct blkid_idinfo xfs_log_idinfo =
+{
+	.name		= "xfs_external_log",
+	.usage		= BLKID_USAGE_OTHER,
+	.probefunc	= probe_xfs_log,
+	.magics		= BLKID_NONE_MAGIC,
+	.minsz		= XFS_MIN_LOG_BYTES,
+};
diff --git a/libblkid/src/superblocks/zfs.c b/libblkid/src/superblocks/zfs.c
new file mode 100644
index 0000000..86da59d
--- /dev/null
+++ b/libblkid/src/superblocks/zfs.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2009-2010 by Andreas Dilger <adilger@sun.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#include "superblocks.h"
+
+#define VDEV_LABEL_UBERBLOCK	(128 * 1024ULL)
+#define VDEV_LABEL_NVPAIR	( 16 * 1024ULL)
+#define VDEV_LABEL_SIZE		(256 * 1024ULL)
+
+/* #include <sys/uberblock_impl.h> */
+#define UBERBLOCK_MAGIC         0x00bab10c              /* oo-ba-bloc!  */
+struct zfs_uberblock {
+	uint64_t	ub_magic;	/* UBERBLOCK_MAGIC		*/
+	uint64_t	ub_version;	/* SPA_VERSION			*/
+	uint64_t	ub_txg;		/* txg of last sync		*/
+	uint64_t	ub_guid_sum;	/* sum of all vdev guids	*/
+	uint64_t	ub_timestamp;	/* UTC time of last sync	*/
+	char		ub_rootbp;	/* MOS objset_phys_t		*/
+} __attribute__((packed));
+
+#define ZFS_TRIES	64
+#define ZFS_WANT	 4
+
+#define DATA_TYPE_UINT64 8
+#define DATA_TYPE_STRING 9
+
+struct nvpair {
+	uint32_t	nvp_size;
+	uint32_t	nvp_unkown;
+	uint32_t	nvp_namelen;
+	char		nvp_name[0]; /* aligned to 4 bytes */
+	/* aligned ptr array for string arrays */
+	/* aligned array of data for value */
+};
+
+struct nvstring {
+	uint32_t	nvs_type;
+	uint32_t	nvs_elem;
+	uint32_t	nvs_strlen;
+	unsigned char	nvs_string[0];
+};
+
+struct nvuint64 {
+	uint32_t	nvu_type;
+	uint32_t	nvu_elem;
+	uint64_t	nvu_value;
+};
+
+struct nvlist {
+	uint32_t	nvl_unknown[3];
+	struct nvpair	nvl_nvpair;
+};
+
+#define nvdebug(fmt, ...)	do { } while(0)
+/*#define nvdebug(fmt, a...)	fprintf(stderr, fmt, ##a)*/
+
+static void zfs_extract_guid_name(blkid_probe pr, loff_t offset)
+{
+	struct nvlist *nvl;
+	struct nvpair *nvp;
+	size_t left = 4096;
+	int found = 0;
+
+	offset = (offset & ~(VDEV_LABEL_SIZE - 1)) + VDEV_LABEL_NVPAIR;
+
+	/* Note that we currently assume that the desired fields are within
+	 * the first 4k (left) of the nvlist.  This is true for all pools
+	 * I've seen, and simplifies this code somewhat, because we don't
+	 * have to handle an nvpair crossing a buffer boundary. */
+	nvl = (struct nvlist *)blkid_probe_get_buffer(pr, offset, left);
+	if (nvl == NULL)
+		return;
+
+	nvdebug("zfs_extract: nvlist offset %llu\n", offset);
+
+	nvp = &nvl->nvl_nvpair;
+	while (left > sizeof(*nvp) && nvp->nvp_size != 0 && found < 3) {
+		int avail;   /* tracks that name/value data fits in nvp_size */
+		int namesize;
+
+		nvp->nvp_size = be32_to_cpu(nvp->nvp_size);
+		nvp->nvp_namelen = be32_to_cpu(nvp->nvp_namelen);
+		avail = nvp->nvp_size - nvp->nvp_namelen - sizeof(*nvp);
+
+		nvdebug("left %zd nvp_size %u\n", left, nvp->nvp_size);
+		if (left < nvp->nvp_size || avail < 0)
+			break;
+
+		namesize = (nvp->nvp_namelen + 3) & ~3;
+
+		nvdebug("nvlist: size %u, namelen %u, name %*s\n",
+			nvp->nvp_size, nvp->nvp_namelen, nvp->nvp_namelen,
+			nvp->nvp_name);
+		if (strncmp(nvp->nvp_name, "name", nvp->nvp_namelen) == 0) {
+			struct nvstring *nvs = (void *)(nvp->nvp_name+namesize);
+
+			nvs->nvs_type = be32_to_cpu(nvs->nvs_type);
+			nvs->nvs_strlen = be32_to_cpu(nvs->nvs_strlen);
+			if (nvs->nvs_strlen > UINT_MAX - sizeof(*nvs))
+				break;
+			avail -= nvs->nvs_strlen + sizeof(*nvs);
+			nvdebug("nvstring: type %u string %*s\n", nvs->nvs_type,
+				nvs->nvs_strlen, nvs->nvs_string);
+			if (nvs->nvs_type == DATA_TYPE_STRING && avail >= 0)
+				blkid_probe_set_label(pr, nvs->nvs_string,
+						      nvs->nvs_strlen);
+			found++;
+		} else if (strncmp(nvp->nvp_name, "guid",
+				   nvp->nvp_namelen) == 0) {
+			struct nvuint64 *nvu = (void *)(nvp->nvp_name+namesize);
+			uint64_t nvu_value;
+
+			memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value));
+			nvu->nvu_type = be32_to_cpu(nvu->nvu_type);
+			nvu_value = be64_to_cpu(nvu_value);
+			avail -= sizeof(*nvu);
+			nvdebug("nvuint64: type %u value %"PRIu64"\n",
+				nvu->nvu_type, nvu_value);
+			if (nvu->nvu_type == DATA_TYPE_UINT64 && avail >= 0)
+				blkid_probe_sprintf_value(pr, "UUID_SUB",
+							  "%"PRIu64, nvu_value);
+			found++;
+		} else if (strncmp(nvp->nvp_name, "pool_guid",
+				   nvp->nvp_namelen) == 0) {
+			struct nvuint64 *nvu = (void *)(nvp->nvp_name+namesize);
+			uint64_t nvu_value;
+
+			memcpy(&nvu_value, &nvu->nvu_value, sizeof(nvu_value));
+			nvu->nvu_type = be32_to_cpu(nvu->nvu_type);
+			nvu_value = be64_to_cpu(nvu_value);
+			avail -= sizeof(*nvu);
+			nvdebug("nvuint64: type %u value %"PRIu64"\n",
+				nvu->nvu_type, nvu_value);
+			if (nvu->nvu_type == DATA_TYPE_UINT64 && avail >= 0)
+				blkid_probe_sprintf_uuid(pr, (unsigned char *)
+							 &nvu_value,
+							 sizeof(nvu_value),
+							 "%"PRIu64, nvu_value);
+			found++;
+		}
+		if (left > nvp->nvp_size)
+			left -= nvp->nvp_size;
+		else
+			left = 0;
+		nvp = (struct nvpair *)((char *)nvp + nvp->nvp_size);
+	}
+}
+
+#define zdebug(fmt, ...)	do {} while(0)
+/*#define zdebug(fmt, a...)	fprintf(stderr, fmt, ##a)*/
+
+/* ZFS has 128x1kB host-endian root blocks, stored in 2 areas at the start
+ * of the disk, and 2 areas at the end of the disk.  Check only some of them...
+ * #4 (@ 132kB) is the first one written on a new filesystem. */
+static int probe_zfs(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	uint64_t swab_magic = swab64(UBERBLOCK_MAGIC);
+	struct zfs_uberblock *ub;
+	int swab_endian;
+	loff_t offset, ub_offset = 0;
+	int tried;
+	int found;
+
+	zdebug("probe_zfs\n");
+	/* Look for at least 4 uberblocks to ensure a positive match */
+	for (tried = found = 0, offset = VDEV_LABEL_UBERBLOCK;
+	     tried < ZFS_TRIES && found < ZFS_WANT;
+	     tried++, offset += 4096) {
+		/* also try the second uberblock copy */
+		if (tried == (ZFS_TRIES / 2))
+			offset = VDEV_LABEL_SIZE + VDEV_LABEL_UBERBLOCK;
+
+		ub = (struct zfs_uberblock *)
+			blkid_probe_get_buffer(pr, offset,
+					       sizeof(struct zfs_uberblock));
+		if (ub == NULL)
+			return errno ? -errno : 1;
+
+		if (ub->ub_magic == UBERBLOCK_MAGIC) {
+			ub_offset = offset;
+			found++;
+		}
+
+		if ((swab_endian = (ub->ub_magic == swab_magic))) {
+			ub_offset = offset;
+			found++;
+		}
+
+		zdebug("probe_zfs: found %s-endian uberblock at %llu\n",
+		       swab_endian ? "big" : "little", offset >> 10);
+	}
+
+	if (found < 4)
+		return 1;
+
+	/* If we found the 4th uberblock, then we will have exited from the
+	 * scanning loop immediately, and ub will be a valid uberblock. */
+	blkid_probe_sprintf_version(pr, "%" PRIu64, swab_endian ?
+				    swab64(ub->ub_version) : ub->ub_version);
+
+	zfs_extract_guid_name(pr, offset);
+
+	if (blkid_probe_set_magic(pr, ub_offset,
+				sizeof(ub->ub_magic),
+				(unsigned char *) &ub->ub_magic))
+		return 1;
+
+	return 0;
+}
+
+const struct blkid_idinfo zfs_idinfo =
+{
+	.name		= "zfs_member",
+	.usage		= BLKID_USAGE_RAID,
+	.probefunc	= probe_zfs,
+	.minsz		= 64 * 1024 * 1024,
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/sysfs.h b/libblkid/src/sysfs.h
new file mode 100644
index 0000000..1de624a
--- /dev/null
+++ b/libblkid/src/sysfs.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 Karel Zak <kzak@redhat.com>
+ */
+#ifndef UTIL_LINUX_SYSFS_H
+#define UTIL_LINUX_SYSFS_H
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <inttypes.h>
+#include <dirent.h>
+
+struct sysfs_cxt {
+	dev_t	devno;
+	int	dir_fd;		/* /sys/block/<name> */
+	char	*dir_path;
+	struct sysfs_cxt *parent;
+
+	unsigned int	scsi_host,
+			scsi_channel,
+			scsi_target,
+			scsi_lun;
+
+	unsigned int	has_hctl : 1;
+};
+
+#define UL_SYSFSCXT_EMPTY { 0, -1, NULL, NULL, 0, 0, 0, 0, 0 }
+
+extern char *sysfs_devno_attribute_path(dev_t devno, char *buf,
+                                 size_t bufsiz, const char *attr);
+extern int sysfs_devno_has_attribute(dev_t devno, const char *attr);
+extern char *sysfs_devno_path(dev_t devno, char *buf, size_t bufsiz);
+extern char *sysfs_devno_to_devpath(dev_t devno, char *buf, size_t bufsiz);
+extern dev_t sysfs_devname_to_devno(const char *name, const char *parent);
+
+extern int sysfs_init(struct sysfs_cxt *cxt, dev_t devno, struct sysfs_cxt *parent)
+					__attribute__ ((warn_unused_result));
+extern void sysfs_deinit(struct sysfs_cxt *cxt);
+
+extern DIR *sysfs_opendir(struct sysfs_cxt *cxt, const char *attr);
+
+extern int sysfs_stat(struct sysfs_cxt *cxt, const char *attr, struct stat *st);
+extern ssize_t sysfs_readlink(struct sysfs_cxt *cxt, const char *attr,
+	                   char *buf, size_t bufsiz);
+extern int sysfs_has_attribute(struct sysfs_cxt *cxt, const char *attr);
+
+extern int sysfs_scanf(struct sysfs_cxt *cxt,  const char *attr,
+		       const char *fmt, ...)
+		        __attribute__ ((format (scanf, 3, 4)));
+
+extern int sysfs_read_s64(struct sysfs_cxt *cxt, const char *attr, int64_t *res);
+extern int sysfs_read_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t *res);
+extern int sysfs_read_int(struct sysfs_cxt *cxt, const char *attr, int *res);
+
+extern int sysfs_write_string(struct sysfs_cxt *cxt, const char *attr, const char *str);
+extern int sysfs_write_u64(struct sysfs_cxt *cxt, const char *attr, uint64_t num);
+
+extern char *sysfs_get_devname(struct sysfs_cxt *cxt, char *buf, size_t bufsiz);
+
+extern char *sysfs_strdup(struct sysfs_cxt *cxt, const char *attr);
+
+extern int sysfs_count_dirents(struct sysfs_cxt *cxt, const char *attr);
+extern int sysfs_count_partitions(struct sysfs_cxt *cxt, const char *devname);
+extern dev_t sysfs_partno_to_devno(struct sysfs_cxt *cxt, int partno);
+extern char *sysfs_get_slave(struct sysfs_cxt *cxt);
+
+extern char *sysfs_get_devchain(struct sysfs_cxt *cxt, char *buf, size_t bufsz);
+extern int sysfs_next_subsystem(struct sysfs_cxt *cxt, char *devchain, char **subsys);
+extern int sysfs_is_hotpluggable(struct sysfs_cxt *cxt);
+
+extern int sysfs_is_partition_dirent(DIR *dir, struct dirent *d,
+			const char *parent_name);
+
+extern int sysfs_devno_to_wholedisk(dev_t dev, char *diskname,
+            size_t len, dev_t *diskdevno);
+
+extern int sysfs_devno_is_lvm_private(dev_t devno);
+extern int sysfs_devno_is_wholedisk(dev_t devno);
+
+extern int sysfs_scsi_get_hctl(struct sysfs_cxt *cxt, int *h,
+			       int *c, int *t, int *l);
+extern char *sysfs_scsi_host_strdup_attribute(struct sysfs_cxt *cxt,
+                const char *type, const char *attr);
+extern int sysfs_scsi_host_is(struct sysfs_cxt *cxt, const char *type);
+extern int sysfs_scsi_has_attribute(struct sysfs_cxt *cxt, const char *attr);
+extern int sysfs_scsi_path_contains(struct sysfs_cxt *cxt, const char *pattern);
+
+#endif /* UTIL_LINUX_SYSFS_H */
diff --git a/libblkid/src/tag.c b/libblkid/src/tag.c
new file mode 100644
index 0000000..6fcaa7e
--- /dev/null
+++ b/libblkid/src/tag.c
@@ -0,0 +1,475 @@
+/*
+ * tag.c - allocation/initialization/free routines for tag structs
+ *
+ * Copyright (C) 2001 Andreas Dilger
+ * Copyright (C) 2003 Theodore Ts'o
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ * %End-Header%
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "blkidP.h"
+
+static blkid_tag blkid_new_tag(void)
+{
+	blkid_tag tag;
+
+	if (!(tag = (blkid_tag) calloc(1, sizeof(struct blkid_struct_tag))))
+		return NULL;
+
+	INIT_LIST_HEAD(&tag->bit_tags);
+	INIT_LIST_HEAD(&tag->bit_names);
+
+	return tag;
+}
+
+void blkid_debug_dump_tag(blkid_tag tag)
+{
+	if (!tag) {
+		fprintf(stderr, "    tag: NULL\n");
+		return;
+	}
+
+	fprintf(stderr, "    tag: %s=\"%s\"\n", tag->bit_name, tag->bit_val);
+}
+
+void blkid_free_tag(blkid_tag tag)
+{
+	if (!tag)
+		return;
+
+	DBG(TAG, ul_debug("    freeing tag %s=%s", tag->bit_name,
+		   tag->bit_val ? tag->bit_val : "(NULL)"));
+	DBG(TAG, blkid_debug_dump_tag(tag));
+
+	list_del(&tag->bit_tags);	/* list of tags for this device */
+	list_del(&tag->bit_names);	/* list of tags with this type */
+
+	free(tag->bit_name);
+	free(tag->bit_val);
+
+	free(tag);
+}
+
+/*
+ * Find the desired tag on a device.  If value is NULL, then the
+ * first such tag is returned, otherwise return only exact tag if found.
+ */
+blkid_tag blkid_find_tag_dev(blkid_dev dev, const char *type)
+{
+	struct list_head *p;
+
+	list_for_each(p, &dev->bid_tags) {
+		blkid_tag tmp = list_entry(p, struct blkid_struct_tag,
+					   bit_tags);
+
+		if (!strcmp(tmp->bit_name, type))
+			return tmp;
+	}
+	return NULL;
+}
+
+int blkid_dev_has_tag(blkid_dev dev, const char *type,
+			     const char *value)
+{
+	blkid_tag		tag;
+
+	tag = blkid_find_tag_dev(dev, type);
+	if (!value)
+		return (tag != NULL);
+	if (!tag || strcmp(tag->bit_val, value))
+		return 0;
+	return 1;
+}
+
+/*
+ * Find the desired tag type in the cache.
+ * We return the head tag for this tag type.
+ */
+static blkid_tag blkid_find_head_cache(blkid_cache cache, const char *type)
+{
+	blkid_tag head = NULL, tmp;
+	struct list_head *p;
+
+	if (!cache || !type)
+		return NULL;
+
+	list_for_each(p, &cache->bic_tags) {
+		tmp = list_entry(p, struct blkid_struct_tag, bit_tags);
+		if (!strcmp(tmp->bit_name, type)) {
+			DBG(TAG, ul_debug("    found cache tag head %s", type));
+			head = tmp;
+			break;
+		}
+	}
+	return head;
+}
+
+/*
+ * Set a tag on an existing device.
+ *
+ * If value is NULL, then delete the tagsfrom the device.
+ */
+int blkid_set_tag(blkid_dev dev, const char *name,
+		  const char *value, const int vlength)
+{
+	blkid_tag	t = 0, head = 0;
+	char		*val = 0;
+	char		**dev_var = 0;
+
+	if (value && !(val = strndup(value, vlength)))
+		return -BLKID_ERR_MEM;
+
+	/*
+	 * Certain common tags are linked directly to the device struct
+	 * We need to know what they are before we do anything else because
+	 * the function name parameter might get freed later on.
+	 */
+	if (!strcmp(name, "TYPE"))
+		dev_var = &dev->bid_type;
+	else if (!strcmp(name, "LABEL"))
+		dev_var = &dev->bid_label;
+	else if (!strcmp(name, "UUID"))
+		dev_var = &dev->bid_uuid;
+
+	t = blkid_find_tag_dev(dev, name);
+	if (!value) {
+		if (t)
+			blkid_free_tag(t);
+	} else if (t) {
+		if (!strcmp(t->bit_val, val)) {
+			/* Same thing, exit */
+			free(val);
+			return 0;
+		}
+		free(t->bit_val);
+		t->bit_val = val;
+	} else {
+		/* Existing tag not present, add to device */
+		if (!(t = blkid_new_tag()))
+			goto errout;
+		t->bit_name = strdup(name);
+		t->bit_val = val;
+		t->bit_dev = dev;
+
+		list_add_tail(&t->bit_tags, &dev->bid_tags);
+
+		if (dev->bid_cache) {
+			head = blkid_find_head_cache(dev->bid_cache,
+						     t->bit_name);
+			if (!head) {
+				head = blkid_new_tag();
+				if (!head)
+					goto errout;
+
+				DBG(TAG, ul_debug("    creating new cache tag head %s", name));
+				head->bit_name = strdup(name);
+				if (!head->bit_name)
+					goto errout;
+				list_add_tail(&head->bit_tags,
+					      &dev->bid_cache->bic_tags);
+			}
+			list_add_tail(&t->bit_names, &head->bit_names);
+		}
+	}
+
+	/* Link common tags directly to the device struct */
+	if (dev_var)
+		*dev_var = val;
+
+	if (dev->bid_cache)
+		dev->bid_cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+	return 0;
+
+errout:
+	if (t)
+		blkid_free_tag(t);
+	else
+		free(val);
+	if (head)
+		blkid_free_tag(head);
+	return -BLKID_ERR_MEM;
+}
+
+
+/*
+ * Parse a "NAME=value" string.  This is slightly different than
+ * parse_token, because that will end an unquoted value at a space, while
+ * this will assume that an unquoted value is the rest of the token (e.g.
+ * if we are passed an already quoted string from the command-line we don't
+ * have to both quote and escape quote so that the quotes make it to
+ * us).
+ *
+ * Returns 0 on success, and -1 on failure.
+ */
+int blkid_parse_tag_string(const char *token, char **ret_type, char **ret_val)
+{
+	char *name, *value, *cp;
+
+	DBG(TAG, ul_debug("trying to parse '%s' as a tag", token));
+
+	if (!token || !(cp = strchr(token, '=')))
+		return -1;
+
+	name = strdup(token);
+	if (!name)
+		return -1;
+	value = name + (cp - token);
+	*value++ = '\0';
+	if (*value == '"' || *value == '\'') {
+		char c = *value++;
+		if (!(cp = strrchr(value, c)))
+			goto errout; /* missing closing quote */
+		*cp = '\0';
+	}
+
+	if (ret_val) {
+		value = value && *value ? strdup(value) : NULL;
+		if (!value)
+			goto errout;
+		*ret_val = value;
+	}
+
+	if (ret_type)
+		*ret_type = name;
+	else
+		free(name);
+
+	return 0;
+
+errout:
+	DBG(TAG, ul_debug("parse error: '%s'", token));
+	free(name);
+	return -1;
+}
+
+/*
+ * Tag iteration routines for the public libblkid interface.
+ *
+ * These routines do not expose the list.h implementation, which are a
+ * contamination of the namespace, and which force us to reveal far, far
+ * too much of our internal implemenation.  I'm not convinced I want
+ * to keep list.h in the long term, anyway.  It's fine for kernel
+ * programming, but performance is not the #1 priority for this
+ * library, and I really don't like the tradeoff of type-safety for
+ * performance for this application.  [tytso:20030125.2007EST]
+ */
+
+/*
+ * This series of functions iterate over all tags in a device
+ */
+#define TAG_ITERATE_MAGIC	0x01a5284c
+
+struct blkid_struct_tag_iterate {
+	int			magic;
+	blkid_dev		dev;
+	struct list_head	*p;
+};
+
+blkid_tag_iterate blkid_tag_iterate_begin(blkid_dev dev)
+{
+	blkid_tag_iterate	iter;
+
+	if (!dev) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	iter = malloc(sizeof(struct blkid_struct_tag_iterate));
+	if (iter) {
+		iter->magic = TAG_ITERATE_MAGIC;
+		iter->dev = dev;
+		iter->p	= dev->bid_tags.next;
+	}
+	return (iter);
+}
+
+/*
+ * Return 0 on success, -1 on error
+ */
+int blkid_tag_next(blkid_tag_iterate iter,
+			  const char **type, const char **value)
+{
+	blkid_tag tag;
+
+	if (!type || !value ||
+	    !iter || iter->magic != TAG_ITERATE_MAGIC ||
+	    iter->p == &iter->dev->bid_tags)
+		return -1;
+
+	*type = 0;
+	*value = 0;
+	tag = list_entry(iter->p, struct blkid_struct_tag, bit_tags);
+	*type = tag->bit_name;
+	*value = tag->bit_val;
+	iter->p = iter->p->next;
+	return 0;
+}
+
+void blkid_tag_iterate_end(blkid_tag_iterate iter)
+{
+	if (!iter || iter->magic != TAG_ITERATE_MAGIC)
+		return;
+	iter->magic = 0;
+	free(iter);
+}
+
+/*
+ * This function returns a device which matches a particular
+ * type/value pair.  If there is more than one device that matches the
+ * search specification, it returns the one with the highest priority
+ * value.  This allows us to give preference to EVMS or LVM devices.
+ */
+blkid_dev blkid_find_dev_with_tag(blkid_cache cache,
+					 const char *type,
+					 const char *value)
+{
+	blkid_tag	head;
+	blkid_dev	dev;
+	int		pri;
+	struct list_head *p;
+	int		probe_new = 0;
+
+	if (!cache || !type || !value)
+		return NULL;
+
+	blkid_read_cache(cache);
+
+	DBG(TAG, ul_debug("looking for %s=%s in cache", type, value));
+
+try_again:
+	pri = -1;
+	dev = 0;
+	head = blkid_find_head_cache(cache, type);
+
+	if (head) {
+		list_for_each(p, &head->bit_names) {
+			blkid_tag tmp = list_entry(p, struct blkid_struct_tag,
+						   bit_names);
+
+			if (!strcmp(tmp->bit_val, value) &&
+			    (tmp->bit_dev->bid_pri > pri) &&
+			    !access(tmp->bit_dev->bid_name, F_OK)) {
+				dev = tmp->bit_dev;
+				pri = dev->bid_pri;
+			}
+		}
+	}
+	if (dev && !(dev->bid_flags & BLKID_BID_FL_VERIFIED)) {
+		dev = blkid_verify(cache, dev);
+		if (!dev || (dev && (dev->bid_flags & BLKID_BID_FL_VERIFIED)))
+			goto try_again;
+	}
+
+	if (!dev && !probe_new) {
+		if (blkid_probe_all_new(cache) < 0)
+			return NULL;
+		probe_new++;
+		goto try_again;
+	}
+
+	if (!dev && !(cache->bic_flags & BLKID_BIC_FL_PROBED)) {
+		if (blkid_probe_all(cache) < 0)
+			return NULL;
+		goto try_again;
+	}
+	return dev;
+}
+
+#ifdef TEST_PROGRAM
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+extern char *optarg;
+extern int optind;
+#endif
+
+void __attribute__((__noreturn__)) usage(char *prog)
+{
+	fprintf(stderr, "Usage: %s [-f blkid_file] [-m debug_mask] device "
+		"[type value]\n",
+		prog);
+	fprintf(stderr, "\tList all tags for a device and exit\n");
+	exit(1);
+}
+
+int main(int argc, char **argv)
+{
+	blkid_tag_iterate	iter;
+	blkid_cache 		cache = NULL;
+	blkid_dev		dev;
+	int			c, ret, found;
+	int			flags = BLKID_DEV_FIND;
+	char			*tmp;
+	char			*file = NULL;
+	char			*devname = NULL;
+	char			*search_type = NULL;
+	char			*search_value = NULL;
+	const char		*type, *value;
+
+	while ((c = getopt (argc, argv, "m:f:")) != EOF)
+		switch (c) {
+		case 'f':
+			file = optarg;
+			break;
+		case 'm':
+		{
+			int mask = strtoul (optarg, &tmp, 0);
+			if (*tmp) {
+				fprintf(stderr, "Invalid debug mask: %s\n",
+					optarg);
+				exit(1);
+			}
+			blkid_init_debug(mask);
+			break;
+		}
+		case '?':
+			usage(argv[0]);
+		}
+	if (argc > optind)
+		devname = argv[optind++];
+	if (argc > optind)
+		search_type = argv[optind++];
+	if (argc > optind)
+		search_value = argv[optind++];
+	if (!devname || (argc != optind))
+		usage(argv[0]);
+
+	if ((ret = blkid_get_cache(&cache, file)) != 0) {
+		fprintf(stderr, "%s: error creating cache (%d)\n",
+			argv[0], ret);
+		exit(1);
+	}
+
+	dev = blkid_get_dev(cache, devname, flags);
+	if (!dev) {
+		fprintf(stderr, "%s: Can not find device in blkid cache\n",
+			devname);
+		exit(1);
+	}
+	if (search_type) {
+		found = blkid_dev_has_tag(dev, search_type, search_value);
+		printf("Device %s: (%s, %s) %s\n", blkid_dev_devname(dev),
+		       search_type, search_value ? search_value : "NULL",
+		       found ? "FOUND" : "NOT FOUND");
+		return(!found);
+	}
+	printf("Device %s...\n", blkid_dev_devname(dev));
+
+	iter = blkid_tag_iterate_begin(dev);
+	while (blkid_tag_next(iter, &type, &value) == 0) {
+		printf("\tTag %s has value %s\n", type, value);
+	}
+	blkid_tag_iterate_end(iter);
+
+	blkid_put_cache(cache);
+	return (0);
+}
+#endif
diff --git a/libblkid/src/topology/dm.c b/libblkid/src/topology/dm.c
new file mode 100644
index 0000000..6add2f7
--- /dev/null
+++ b/libblkid/src/topology/dm.c
@@ -0,0 +1,136 @@
+/*
+ * device-mapper (dm) topology
+ * -- this is fallback for old systems where the topology information is not
+ *    exported by sysfs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "topology.h"
+
+static int is_dm_device(dev_t devno)
+{
+	return blkid_driver_has_major("device-mapper", major(devno));
+}
+
+static int probe_dm_tp(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	const char *paths[] = {
+		"/usr/local/sbin/dmsetup",
+		"/usr/sbin/dmsetup",
+		"/sbin/dmsetup"
+	};
+	int dmpipe[] = { -1, -1 }, stripes, stripesize;
+	char *cmd = NULL;
+	FILE *stream = NULL;
+	long long  offset, size;
+	size_t i;
+	dev_t devno = blkid_probe_get_devno(pr);
+
+	if (!devno)
+		goto nothing;		/* probably not a block device */
+	if (!is_dm_device(devno))
+		goto nothing;
+
+	for (i = 0; i < ARRAY_SIZE(paths); i++) {
+		struct stat sb;
+		if (stat(paths[i], &sb) == 0) {
+			cmd = (char *) paths[i];
+			break;
+		}
+	}
+
+	if (!cmd)
+		goto nothing;
+	if (pipe(dmpipe) < 0) {
+		DBG(LOWPROBE, ul_debug("Failed to open pipe: errno=%d", errno));
+		goto nothing;
+	}
+
+	switch (fork()) {
+	case 0:
+	{
+		char *dmargv[7], maj[16], min[16];
+
+		/* Plumbing */
+		close(dmpipe[0]);
+
+		if (dmpipe[1] != STDOUT_FILENO)
+			dup2(dmpipe[1], STDOUT_FILENO);
+
+		/* The libblkid library could linked with setuid programs */
+		if (setgid(getgid()) < 0)
+			 exit(1);
+		if (setuid(getuid()) < 0)
+			 exit(1);
+
+		snprintf(maj, sizeof(maj), "%d", major(devno));
+		snprintf(min, sizeof(min), "%d", minor(devno));
+
+		dmargv[0] = cmd;
+		dmargv[1] = "table";
+		dmargv[2] = "-j";
+		dmargv[3] = maj;
+		dmargv[4] = "-m";
+		dmargv[5] = min;
+		dmargv[6] = NULL;
+
+		execv(dmargv[0], dmargv);
+
+		DBG(LOWPROBE, ul_debug("Failed to execute %s: errno=%d", cmd, errno));
+		exit(1);
+	}
+	case -1:
+		DBG(LOWPROBE, ul_debug("Failed to forking: errno=%d", errno));
+		goto nothing;
+	default:
+		break;
+	}
+
+	stream = fdopen(dmpipe[0], "r" UL_CLOEXECSTR);
+	if (!stream)
+		goto nothing;
+
+	if (fscanf(stream, "%lld %lld striped %d %d ",
+			&offset, &size, &stripes, &stripesize) != 0)
+		goto nothing;
+
+	blkid_topology_set_minimum_io_size(pr, stripesize << 9);
+	blkid_topology_set_optimal_io_size(pr, (stripes * stripesize) << 9);
+
+	fclose(stream);
+	close(dmpipe[1]);
+	return 0;
+
+nothing:
+	if (stream)
+		fclose(stream);
+	else if (dmpipe[0] != -1)
+		close(dmpipe[0]);
+	if (dmpipe[1] != -1)
+		close(dmpipe[1]);
+	return 1;
+}
+
+const struct blkid_idinfo dm_tp_idinfo =
+{
+	.name		= "dm",
+	.probefunc	= probe_dm_tp,
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/evms.c b/libblkid/src/topology/evms.c
new file mode 100644
index 0000000..1fce25a
--- /dev/null
+++ b/libblkid/src/topology/evms.c
@@ -0,0 +1,78 @@
+/*
+ * Evms topology
+ * -- this is fallback for old systems where the toplogy information is not
+ *    exported by sysfs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "topology.h"
+
+#define EVMS_MAJOR		117
+
+#ifndef _IOT__IOTBASE_u_int32_t
+#define _IOT__IOTBASE_u_int32_t IOT_SIMPLE(uint32_t)
+#endif
+#define _IOT_evms_stripe_info _IOT (_IOTS(uint32_t), 2, 0, 0, 0, 0)
+#define EVMS_GET_STRIPE_INFO	_IOR(EVMS_MAJOR, 0xF0, struct evms_stripe_info)
+
+struct evms_stripe_info {
+	uint32_t	size;		/* stripe unit 512-byte blocks */
+	uint32_t	width;		/* the number of stripe members or RAID data disks */
+} evms_stripe_info;
+
+static int is_evms_device(dev_t devno)
+{
+	if (major(devno) == EVMS_MAJOR)
+		return 1;
+	return blkid_driver_has_major("evms", major(devno));
+}
+
+static int probe_evms_tp(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	struct evms_stripe_info evms;
+	dev_t devno = blkid_probe_get_devno(pr);
+
+	if (!devno)
+		goto nothing;		/* probably not a block device */
+
+	if (!is_evms_device(devno))
+		goto nothing;
+
+	memset(&evms, 0, sizeof(evms));
+
+	if (ioctl(pr->fd, EVMS_GET_STRIPE_INFO, &evms))
+		goto nothing;
+
+	blkid_topology_set_minimum_io_size(pr, evms.size << 9);
+	blkid_topology_set_optimal_io_size(pr, (evms.size * evms.width) << 9);
+
+	return 0;
+
+nothing:
+	return 1;
+}
+
+const struct blkid_idinfo evms_tp_idinfo =
+{
+	.name		= "evms",
+	.probefunc	= probe_evms_tp,
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/ioctl.c b/libblkid/src/topology/ioctl.c
new file mode 100644
index 0000000..3aba09e
--- /dev/null
+++ b/libblkid/src/topology/ioctl.c
@@ -0,0 +1,74 @@
+/*
+ * ioctl based topology -- gathers topology information
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "topology.h"
+
+/*
+ * ioctl topology values
+ */
+static struct topology_val {
+
+	long  ioc;
+
+	/* functions to set probing result */
+	int (*set_ulong)(blkid_probe, unsigned long);
+	int (*set_int)(blkid_probe, int);
+
+} topology_vals[] = {
+	{ BLKALIGNOFF, NULL, blkid_topology_set_alignment_offset },
+	{ BLKIOMIN, blkid_topology_set_minimum_io_size },
+	{ BLKIOOPT, blkid_topology_set_optimal_io_size },
+	{ BLKPBSZGET, blkid_topology_set_physical_sector_size }
+	/* we read BLKSSZGET in topology.c */
+};
+
+static int probe_ioctl_tp(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(topology_vals); i++) {
+		struct topology_val *val = &topology_vals[i];
+		int rc = 1;
+		unsigned int data;
+
+		if (ioctl(pr->fd, val->ioc, &data) == -1)
+			goto nothing;
+
+		if (val->set_int)
+			rc = val->set_int(pr, (int) data);
+		else
+			rc = val->set_ulong(pr, (unsigned long) data);
+		if (rc)
+			goto err;
+	}
+
+	return 0;
+nothing:
+	return 1;
+err:
+	return -1;
+}
+
+const struct blkid_idinfo ioctl_tp_idinfo =
+{
+	.name		= "ioctl",
+	.probefunc	= probe_ioctl_tp,
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/lvm.c b/libblkid/src/topology/lvm.c
new file mode 100644
index 0000000..20a66b4
--- /dev/null
+++ b/libblkid/src/topology/lvm.c
@@ -0,0 +1,148 @@
+/*
+ * lvm topology
+ * -- this is fallback for old systems where the topology information is not
+ *    exported by sysfs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "topology.h"
+
+#ifndef LVM_BLK_MAJOR
+# define LVM_BLK_MAJOR     58
+#endif
+
+static int is_lvm_device(dev_t devno)
+{
+	if (major(devno) == LVM_BLK_MAJOR)
+		return 1;
+	return blkid_driver_has_major("lvm", major(devno));
+}
+
+static int probe_lvm_tp(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	const char *paths[] = {
+		"/usr/local/sbin/lvdisplay",
+		"/usr/sbin/lvdisplay",
+		"/sbin/lvdisplay"
+	};
+	int lvpipe[] = { -1, -1 }, stripes = 0, stripesize = 0;
+	FILE *stream = NULL;
+	char *cmd = NULL, *devname = NULL, buf[1024];
+	size_t i;
+	dev_t devno = blkid_probe_get_devno(pr);
+
+	if (!devno)
+		goto nothing;		/* probably not a block device */
+	if (!is_lvm_device(devno))
+		goto nothing;
+
+	for (i = 0; i < ARRAY_SIZE(paths); i++) {
+		struct stat sb;
+		if (stat(paths[i], &sb) == 0) {
+			cmd = (char *) paths[i];
+			break;
+		}
+	}
+
+	if (!cmd)
+		goto nothing;
+
+	devname = blkid_devno_to_devname(devno);
+	if (!devname)
+		goto nothing;
+
+	if (pipe(lvpipe) < 0) {
+		DBG(LOWPROBE, ul_debug("Failed to open pipe: errno=%d", errno));
+		goto nothing;
+	}
+
+	switch (fork()) {
+	case 0:
+	{
+		char *lvargv[3];
+
+		/* Plumbing */
+		close(lvpipe[0]);
+
+		if (lvpipe[1] != STDOUT_FILENO)
+			dup2(lvpipe[1], STDOUT_FILENO);
+
+		/* The libblkid library could linked with setuid programs */
+		if (setgid(getgid()) < 0)
+			 exit(1);
+		if (setuid(getuid()) < 0)
+			 exit(1);
+
+		lvargv[0] = cmd;
+		lvargv[1] = devname;
+		lvargv[2] = NULL;
+
+		execv(lvargv[0], lvargv);
+
+		DBG(LOWPROBE, ul_debug("Failed to execute %s: errno=%d", cmd, errno));
+		exit(1);
+	}
+	case -1:
+		DBG(LOWPROBE, ul_debug("Failed to forking: errno=%d", errno));
+		goto nothing;
+	default:
+		break;
+	}
+
+	stream = fdopen(lvpipe[0], "r" UL_CLOEXECSTR);
+	if (!stream)
+		goto nothing;
+
+	while (fgets(buf, sizeof(buf), stream) != NULL) {
+		if (!strncmp(buf, "Stripes", 7))
+			sscanf(buf, "Stripes %d", &stripes);
+
+		if (!strncmp(buf, "Stripe size", 11))
+			sscanf(buf, "Stripe size (KByte) %d", &stripesize);
+	}
+
+	if (!stripes)
+		goto nothing;
+
+	blkid_topology_set_minimum_io_size(pr, stripesize << 10);
+	blkid_topology_set_optimal_io_size(pr, (stripes * stripesize) << 10);
+
+	free(devname);
+	fclose(stream);
+	close(lvpipe[1]);
+	return 0;
+
+nothing:
+	free(devname);
+	if (stream)
+		fclose(stream);
+	else if (lvpipe[0] != -1)
+		close(lvpipe[0]);
+	if (lvpipe[1] != -1)
+		close(lvpipe[1]);
+	return 1;
+}
+
+const struct blkid_idinfo lvm_tp_idinfo =
+{
+	.name		= "lvm",
+	.probefunc	= probe_lvm_tp,
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/md.c b/libblkid/src/topology/md.c
new file mode 100644
index 0000000..c232197
--- /dev/null
+++ b/libblkid/src/topology/md.c
@@ -0,0 +1,155 @@
+/*
+ * Linux Software RAID (md) topology
+ * -- this is fallback for old systems where the topology information is not
+ *    exported by sysfs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "topology.h"
+
+#ifndef MD_MAJOR
+#define MD_MAJOR	9
+#endif
+
+#ifndef _IOT__IOTBASE_uint32_t
+#define _IOT__IOTBASE_uint32_t IOT_SIMPLE(uint32_t)
+#endif
+#define _IOT_md_array_info _IOT (_IOTS(uint32_t), 18, 0, 0, 0, 0)
+#define GET_ARRAY_INFO          _IOR (MD_MAJOR, 0x11, struct md_array_info)
+
+struct md_array_info {
+	/*
+	 * Generic constant information
+	 */
+	uint32_t major_version;
+	uint32_t minor_version;
+	uint32_t patch_version;
+	uint32_t ctime;
+	uint32_t level;
+	uint32_t size;
+	uint32_t nr_disks;
+	uint32_t raid_disks;
+	uint32_t md_minor;
+	uint32_t not_persistent;
+
+	/*
+	 * Generic state information
+	 */
+	uint32_t utime;	  /*  0 Superblock update time		  */
+	uint32_t state;	  /*  1 State bits (clean, ...)		  */
+	uint32_t active_disks;  /*  2 Number of currently active disks  */
+	uint32_t working_disks; /*  3 Number of working disks		  */
+	uint32_t failed_disks;  /*  4 Number of failed disks		  */
+	uint32_t spare_disks;	  /*  5 Number of spare disks		  */
+
+	/*
+	 * Personality information
+	 */
+	uint32_t layout;	  /*  0 the array's physical layout	  */
+	uint32_t chunk_size;	  /*  1 chunk size in bytes		  */
+
+};
+
+static int is_md_device(dev_t devno)
+{
+	if (major(devno) == MD_MAJOR)
+		return 1;
+	return blkid_driver_has_major("md", major(devno));
+}
+
+static int probe_md_tp(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	int fd = -1;
+	dev_t disk = 0;
+	dev_t devno = blkid_probe_get_devno(pr);
+	struct md_array_info md;
+
+	if (!devno)
+		goto nothing;		/* probably not a block device */
+
+	if (!is_md_device(devno))
+		goto nothing;
+
+	if (blkid_devno_to_wholedisk(devno, NULL, 0, &disk))
+		goto nothing;
+
+	if (disk == devno)
+		fd = pr->fd;
+	else {
+		char *diskpath = blkid_devno_to_devname(disk);
+
+		if (!diskpath)
+			goto nothing;
+
+		fd = open(diskpath, O_RDONLY|O_CLOEXEC);
+		free(diskpath);
+
+                if (fd == -1)
+			goto nothing;
+	}
+
+	memset(&md, 0, sizeof(md));
+
+	if (ioctl(fd, GET_ARRAY_INFO, &md))
+		goto nothing;
+
+	if (fd >= 0 && fd != pr->fd) {
+		close(fd);
+		fd = -1;
+	}
+
+	/*
+	 * Ignore levels we don't want aligned (e.g. linear)
+	 * and deduct disk(s) from stripe width on RAID4/5/6
+	 */
+	switch (md.level) {
+	case 6:
+		md.raid_disks--;
+		/* fallthrough */
+	case 5:
+	case 4:
+		md.raid_disks--;
+		/* fallthrough */
+	case 1:
+	case 0:
+	case 10:
+		break;
+	default:
+		goto nothing;
+	}
+
+	blkid_topology_set_minimum_io_size(pr, md.chunk_size);
+	blkid_topology_set_optimal_io_size(pr, md.chunk_size * md.raid_disks);
+
+	return 0;
+
+nothing:
+	if (fd >= 0 && fd != pr->fd)
+		close(fd);
+	return 1;
+}
+
+const struct blkid_idinfo md_tp_idinfo =
+{
+	.name		= "md",
+	.probefunc	= probe_md_tp,
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/sysfs.c b/libblkid/src/topology/sysfs.c
new file mode 100644
index 0000000..a04b20a
--- /dev/null
+++ b/libblkid/src/topology/sysfs.c
@@ -0,0 +1,119 @@
+/*
+ * sysfs based topology -- gathers topology information from Linux sysfs
+ *
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * For more information see Linux kernel Documentation/ABI/testing/sysfs-block.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "sysfs.h"
+#include "topology.h"
+
+/*
+ * Sysfs topology values (since 2.6.31, May 2009).
+ */
+static struct topology_val {
+
+	/* /sys/dev/block/<maj>:<min>/<ATTR> */
+	const char *attr;
+
+	/* functions to set probing resut */
+	int (*set_ulong)(blkid_probe, unsigned long);
+	int (*set_int)(blkid_probe, int);
+
+} topology_vals[] = {
+	{ "alignment_offset", NULL, blkid_topology_set_alignment_offset },
+	{ "queue/minimum_io_size", blkid_topology_set_minimum_io_size },
+	{ "queue/optimal_io_size", blkid_topology_set_optimal_io_size },
+	{ "queue/physical_block_size", blkid_topology_set_physical_sector_size },
+};
+
+static int probe_sysfs_tp(blkid_probe pr,
+		const struct blkid_idmag *mag __attribute__((__unused__)))
+{
+	dev_t dev, disk = 0;
+	int rc;
+	struct sysfs_cxt sysfs = UL_SYSFSCXT_EMPTY,
+			 parent = UL_SYSFSCXT_EMPTY;
+	size_t i, count = 0;
+
+	dev = blkid_probe_get_devno(pr);
+	if (!dev || sysfs_init(&sysfs, dev, NULL) != 0)
+		return 1;
+
+	rc = 1;		/* nothing (default) */
+
+	for (i = 0; i < ARRAY_SIZE(topology_vals); i++) {
+		struct topology_val *val = &topology_vals[i];
+		int ok = sysfs_has_attribute(&sysfs, val->attr);
+
+		rc = 1;	/* nothing */
+
+		if (!ok) {
+			if (!disk) {
+				/*
+				 * Read atrributes from "disk" if the current
+				 * device is a partition.
+				 */
+				disk = blkid_probe_get_wholedisk_devno(pr);
+				if (disk && disk != dev) {
+					if (sysfs_init(&parent, disk, NULL) != 0)
+						goto done;
+
+					sysfs.parent = &parent;
+					ok = sysfs_has_attribute(&sysfs,
+								 val->attr);
+				}
+			}
+			if (!ok)
+				continue;	/* attribute does not exist */
+		}
+
+		if (val->set_ulong) {
+			uint64_t data;
+
+			if (sysfs_read_u64(&sysfs, val->attr, &data) != 0)
+				continue;
+			rc = val->set_ulong(pr, (unsigned long) data);
+
+		} else if (val->set_int) {
+			int64_t data;
+
+			if (sysfs_read_s64(&sysfs, val->attr, &data) != 0)
+				continue;
+			rc = val->set_int(pr, (int) data);
+		}
+
+		if (rc < 0)
+			goto done;	/* error */
+		if (rc == 0)
+			count++;
+	}
+
+done:
+	sysfs_deinit(&sysfs);
+	sysfs_deinit(&parent);
+
+	if (count)
+		return 0;		/* success */
+	return rc;			/* error or nothing */
+}
+
+const struct blkid_idinfo sysfs_tp_idinfo =
+{
+	.name		= "sysfs",
+	.probefunc	= probe_sysfs_tp,
+	.magics		= BLKID_NONE_MAGIC
+};
+
diff --git a/libblkid/src/topology/topology.c b/libblkid/src/topology/topology.c
new file mode 100644
index 0000000..93fa380
--- /dev/null
+++ b/libblkid/src/topology/topology.c
@@ -0,0 +1,362 @@
+/*
+ * topology - gathers information about device topology
+ *
+ * Copyright 2009 Red Hat, Inc.  All rights reserved.
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+#include "topology.h"
+
+/**
+ * SECTION:topology
+ * @title: Topology information
+ * @short_description: block device topology information.
+ *
+ * The topology chain provides details about Linux block devices, for more
+ * information see:
+ *
+ *      Linux kernel Documentation/ABI/testing/sysfs-block
+ *
+ * NAME=value (tags) interface is enabled by blkid_probe_enable_topology(),
+ * and provides:
+ *
+ * @LOGICAL_SECTOR_SIZE: this is the smallest unit the storage device can
+ *                       address. It is typically 512 bytes.
+ *
+ * @PHYSICAL_SECTOR_SIZE: this is the smallest unit a physical storage device
+ *                        can write atomically. It is usually the same as the
+ *                        logical sector size but may be bigger.
+ *
+ * @MINIMUM_IO_SIZE: minimum size which is the device's preferred unit of I/O.
+ *                   For RAID arrays it is often the stripe chunk size.
+ *
+ * @OPTIMAL_IO_SIZE: usually the stripe width for RAID or zero. For RAID arrays
+ *                   it is usually the stripe width or the internal track size.
+ *
+ * @ALIGNMENT_OFFSET: indicates how many bytes the beginning of the device is
+ *                    offset from the disk's natural alignment.
+ *
+ * The NAME=value tags are not defined when the corresponding topology value
+ * is zero. The MINIMUM_IO_SIZE should be always defined if kernel provides
+ * topology information.
+ *
+ * Binary interface:
+ *
+ * blkid_probe_get_topology()
+ *
+ * blkid_topology_get_'VALUENAME'()
+ */
+static int topology_probe(blkid_probe pr, struct blkid_chain *chn);
+static void topology_free(blkid_probe pr, void *data);
+static int topology_is_complete(blkid_probe pr);
+static int topology_set_logical_sector_size(blkid_probe pr);
+
+/*
+ * Binary interface
+ */
+struct blkid_struct_topology {
+	unsigned long	alignment_offset;
+	unsigned long	minimum_io_size;
+	unsigned long	optimal_io_size;
+	unsigned long	logical_sector_size;
+	unsigned long	physical_sector_size;
+};
+
+/*
+ * Topology chain probing functions
+ */
+static const struct blkid_idinfo *idinfos[] =
+{
+#ifdef __linux__
+	&ioctl_tp_idinfo,
+	&sysfs_tp_idinfo,
+	&md_tp_idinfo,
+	&dm_tp_idinfo,
+	&lvm_tp_idinfo,
+	&evms_tp_idinfo
+#endif
+};
+
+
+/*
+ * Driver definition
+ */
+const struct blkid_chaindrv topology_drv = {
+	.id           = BLKID_CHAIN_TOPLGY,
+	.name         = "topology",
+	.dflt_enabled = FALSE,
+	.idinfos      = idinfos,
+	.nidinfos     = ARRAY_SIZE(idinfos),
+	.probe        = topology_probe,
+	.safeprobe    = topology_probe,
+	.free_data    = topology_free
+};
+
+/**
+ * blkid_probe_enable_topology:
+ * @pr: probe
+ * @enable: TRUE/FALSE
+ *
+ * Enables/disables the topology probing for non-binary interface.
+ *
+ * Returns: 0 on success, or -1 in case of error.
+ */
+int blkid_probe_enable_topology(blkid_probe pr, int enable)
+{
+	if (!pr)
+		return -1;
+	pr->chains[BLKID_CHAIN_TOPLGY].enabled = enable;
+	return 0;
+}
+
+/**
+ * blkid_probe_get_topology:
+ * @pr: probe
+ *
+ * This is a binary interface for topology values. See also blkid_topology_*
+ * functions.
+ *
+ * This function is independent on blkid_do_[safe,full]probe() and
+ * blkid_probe_enable_topology() calls.
+ *
+ * WARNING: the returned object will be overwritten by the next
+ *          blkid_probe_get_topology() call for the same @pr. If you want to
+ *          use more blkid_topopogy objects in the same time you have to create
+ *          more blkid_probe handlers (see blkid_new_probe()).
+ *
+ * Returns: blkid_topopogy, or NULL in case of error.
+ */
+blkid_topology blkid_probe_get_topology(blkid_probe pr)
+{
+	return (blkid_topology) blkid_probe_get_binary_data(pr,
+			&pr->chains[BLKID_CHAIN_TOPLGY]);
+}
+
+/*
+ * The blkid_do_probe() backend.
+ */
+static int topology_probe(blkid_probe pr, struct blkid_chain *chn)
+{
+	size_t i;
+
+	if (!pr || chn->idx < -1)
+		return -1;
+
+	if (!S_ISBLK(pr->mode))
+		return -EINVAL;	/* nothing, works with block devices only */
+
+	if (chn->binary) {
+		DBG(LOWPROBE, ul_debug("initialize topology binary data"));
+
+		if (chn->data)
+			/* reset binary data */
+			memset(chn->data, 0,
+					sizeof(struct blkid_struct_topology));
+		else {
+			chn->data = calloc(1,
+					sizeof(struct blkid_struct_topology));
+			if (!chn->data)
+				return -ENOMEM;
+		}
+	}
+
+	blkid_probe_chain_reset_vals(pr, chn);
+
+	DBG(LOWPROBE, ul_debug("--> starting probing loop [TOPOLOGY idx=%d]",
+		chn->idx));
+
+	i = chn->idx < 0 ? 0 : chn->idx + 1U;
+
+	for ( ; i < ARRAY_SIZE(idinfos); i++) {
+		const struct blkid_idinfo *id = idinfos[i];
+
+		chn->idx = i;
+
+		if (id->probefunc) {
+			DBG(LOWPROBE, ul_debug("%s: call probefunc()", id->name));
+			if (id->probefunc(pr, NULL) != 0)
+				continue;
+		}
+
+		if (!topology_is_complete(pr))
+			continue;
+
+		/* generic for all probing drivers */
+		topology_set_logical_sector_size(pr);
+
+		DBG(LOWPROBE, ul_debug("<-- leaving probing loop (type=%s) [TOPOLOGY idx=%d]",
+			id->name, chn->idx));
+		return BLKID_PROBE_OK;
+	}
+
+	DBG(LOWPROBE, ul_debug("<-- leaving probing loop (failed) [TOPOLOGY idx=%d]",
+		chn->idx));
+	return BLKID_PROBE_NONE;
+}
+
+static void topology_free(blkid_probe pr __attribute__((__unused__)),
+			  void *data)
+{
+	free(data);
+}
+
+static int topology_set_value(blkid_probe pr, const char *name,
+				size_t structoff, unsigned long data)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+	if (!chn)
+		return -1;
+	if (!data)
+		return 0;	/* ignore zeros */
+
+	if (chn->binary) {
+		memcpy(chn->data + structoff, &data, sizeof(data));
+		return 0;
+	}
+	return blkid_probe_sprintf_value(pr, name, "%lu", data);
+}
+
+
+/* the topology info is complete when we have at least "minimum_io_size" which
+ * is provided by all blkid topology drivers */
+static int topology_is_complete(blkid_probe pr)
+{
+	struct blkid_chain *chn = blkid_probe_get_chain(pr);
+
+	if (!chn)
+		return FALSE;
+
+	if (chn->binary && chn->data) {
+		blkid_topology tp = (blkid_topology) chn->data;
+		if (tp->minimum_io_size)
+			return TRUE;
+	}
+
+	return __blkid_probe_lookup_value(pr, "MINIMUM_IO_SIZE") ? TRUE : FALSE;
+}
+
+int blkid_topology_set_alignment_offset(blkid_probe pr, int val)
+{
+	unsigned long xval;
+
+	/* Welcome to Hell. The kernel is able to return -1 as an
+	 * alignment_offset if no compatible sizes and alignments
+	 * exist for stacked devices.
+	 *
+	 * There is no way how libblkid caller can respond to the value -1, so
+	 * we will hide this corner case...
+	 *
+	 * (TODO: maybe we can export an extra boolean value 'misaligned' rather
+	 *  then complete hide this problem.)
+	 */
+	xval = val < 0 ? 0 : val;
+
+	return topology_set_value(pr,
+			"ALIGNMENT_OFFSET",
+			offsetof(struct blkid_struct_topology, alignment_offset),
+			xval);
+}
+
+int blkid_topology_set_minimum_io_size(blkid_probe pr, unsigned long val)
+{
+	return topology_set_value(pr,
+			"MINIMUM_IO_SIZE",
+			offsetof(struct blkid_struct_topology, minimum_io_size),
+			val);
+}
+
+int blkid_topology_set_optimal_io_size(blkid_probe pr, unsigned long val)
+{
+	return topology_set_value(pr,
+			"OPTIMAL_IO_SIZE",
+			offsetof(struct blkid_struct_topology, optimal_io_size),
+			val);
+}
+
+/* BLKSSZGET is provided on all systems since 2.3.3 -- so we don't have to
+ * waste time with sysfs.
+ */
+static int topology_set_logical_sector_size(blkid_probe pr)
+{
+	unsigned long val = blkid_probe_get_sectorsize(pr);
+
+	if (!val)
+		return -1;
+
+	return topology_set_value(pr,
+			"LOGICAL_SECTOR_SIZE",
+			offsetof(struct blkid_struct_topology, logical_sector_size),
+			val);
+}
+
+int blkid_topology_set_physical_sector_size(blkid_probe pr, unsigned long val)
+{
+	return topology_set_value(pr,
+			"PHYSICAL_SECTOR_SIZE",
+			offsetof(struct blkid_struct_topology, physical_sector_size),
+			val);
+}
+
+/**
+ * blkid_topology_get_alignment_offset:
+ * @tp: topology
+ *
+ * Returns: alignment offset in bytes or 0.
+ */
+unsigned long blkid_topology_get_alignment_offset(blkid_topology tp)
+{
+	return tp->alignment_offset;
+}
+
+/**
+ * blkid_topology_get_minimum_io_size:
+ * @tp: topology
+ *
+ * Returns: minimum io size in bytes or 0.
+ */
+unsigned long blkid_topology_get_minimum_io_size(blkid_topology tp)
+{
+	return tp->minimum_io_size;
+}
+
+/**
+ * blkid_topology_get_optimal_io_size
+ * @tp: topology
+ *
+ * Returns: optimal io size in bytes or 0.
+ */
+unsigned long blkid_topology_get_optimal_io_size(blkid_topology tp)
+{
+	return tp->optimal_io_size;
+}
+
+/**
+ * blkid_topology_get_logical_sector_size
+ * @tp: topology
+ *
+ * Returns: logical sector size (BLKSSZGET ioctl) in bytes or 0.
+ */
+unsigned long blkid_topology_get_logical_sector_size(blkid_topology tp)
+{
+	return tp->logical_sector_size;
+}
+
+/**
+ * blkid_topology_get_physical_sector_size
+ * @tp: topology
+ *
+ * Returns: logical sector size (BLKSSZGET ioctl) in bytes or 0.
+ */
+unsigned long blkid_topology_get_physical_sector_size(blkid_topology tp)
+{
+	return tp->physical_sector_size;
+}
+
diff --git a/libblkid/src/topology/topology.h b/libblkid/src/topology/topology.h
new file mode 100644
index 0000000..6d2f433
--- /dev/null
+++ b/libblkid/src/topology/topology.h
@@ -0,0 +1,24 @@
+#ifndef BLKID_TOPOLOGY_H
+#define BLKID_TOPOLOGY_H
+
+#include "blkidP.h"
+
+extern int blkid_topology_set_alignment_offset(blkid_probe pr, int val);
+extern int blkid_topology_set_minimum_io_size(blkid_probe pr, unsigned long val);
+extern int blkid_topology_set_optimal_io_size(blkid_probe pr, unsigned long val);
+extern int blkid_topology_set_physical_sector_size(blkid_probe pr, unsigned long val);
+
+/*
+ * topology probers
+ */
+#ifdef __linux__
+extern const struct blkid_idinfo ioctl_tp_idinfo;
+extern const struct blkid_idinfo md_tp_idinfo;
+extern const struct blkid_idinfo evms_tp_idinfo;
+extern const struct blkid_idinfo sysfs_tp_idinfo;
+extern const struct blkid_idinfo dm_tp_idinfo;
+extern const struct blkid_idinfo lvm_tp_idinfo;
+#endif
+
+#endif /* BLKID_TOPOLOGY_H */
+
diff --git a/libblkid/src/verify.c b/libblkid/src/verify.c
new file mode 100644
index 0000000..06f6d53
--- /dev/null
+++ b/libblkid/src/verify.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+
+#include "blkidP.h"
+#include "sysfs.h"
+
+static void blkid_probe_to_tags(blkid_probe pr, blkid_dev dev)
+{
+	const char *data;
+	const char *name;
+	int nvals, n;
+	size_t len;
+
+	nvals = blkid_probe_numof_values(pr);
+
+	for (n = 0; n < nvals; n++) {
+		if (blkid_probe_get_value(pr, n, &name, &data, &len) != 0)
+			continue;
+		if (strncmp(name, "PART_ENTRY_", 11) == 0) {
+			if (strcmp(name, "PART_ENTRY_UUID") == 0)
+				blkid_set_tag(dev, "PARTUUID", data, len);
+			else if (strcmp(name, "PART_ENTRY_NAME") == 0)
+				blkid_set_tag(dev, "PARTLABEL", data, len);
+
+		} else if (!strstr(name, "_ID")) {
+			/* superblock UUID, LABEL, ...
+			 * but not {SYSTEM,APPLICATION,..._ID} */
+			blkid_set_tag(dev, name, data, len);
+		}
+	}
+}
+
+/*
+ * Verify that the data in dev is consistent with what is on the actual
+ * block device (using the devname field only).  Normally this will be
+ * called when finding items in the cache, but for long running processes
+ * is also desirable to revalidate an item before use.
+ *
+ * If we are unable to revalidate the data, we return the old data and
+ * do not set the BLKID_BID_FL_VERIFIED flag on it.
+ */
+blkid_dev blkid_verify(blkid_cache cache, blkid_dev dev)
+{
+	blkid_tag_iterate iter;
+	const char *type, *value;
+	struct stat st;
+	time_t diff, now;
+	int fd;
+
+	if (!dev || !cache)
+		return NULL;
+
+	now = time(0);
+	diff = now - dev->bid_time;
+
+	if (stat(dev->bid_name, &st) < 0) {
+		DBG(PROBE, ul_debug("blkid_verify: error %m (%d) while "
+			   "trying to stat %s", errno,
+			   dev->bid_name));
+	open_err:
+		if ((errno == EPERM) || (errno == EACCES) || (errno == ENOENT)) {
+			/* We don't have read permission, just return cache data. */
+			DBG(PROBE, ul_debug("returning unverified data for %s",
+						dev->bid_name));
+			return dev;
+		}
+		blkid_free_dev(dev);
+		return NULL;
+	}
+
+	if (now >= dev->bid_time &&
+#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
+	    (st.st_mtime < dev->bid_time ||
+	        (st.st_mtime == dev->bid_time &&
+		 st.st_mtim.tv_nsec / 1000 <= dev->bid_utime)) &&
+#else
+	    st.st_mtime <= dev->bid_time &&
+#endif
+	    (diff < BLKID_PROBE_MIN ||
+		(dev->bid_flags & BLKID_BID_FL_VERIFIED &&
+		 diff < BLKID_PROBE_INTERVAL)))
+		return dev;
+
+#ifndef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
+	DBG(PROBE, ul_debug("need to revalidate %s (cache time %lu, stat time %lu,\t"
+		   "time since last check %lu)",
+		   dev->bid_name, (unsigned long)dev->bid_time,
+		   (unsigned long)st.st_mtime, (unsigned long)diff));
+#else
+	DBG(PROBE, ul_debug("need to revalidate %s (cache time %lu.%lu, stat time %lu.%lu,\t"
+		   "time since last check %lu)",
+		   dev->bid_name,
+		   (unsigned long)dev->bid_time, (unsigned long)dev->bid_utime,
+		   (unsigned long)st.st_mtime, (unsigned long)st.st_mtim.tv_nsec / 1000,
+		   (unsigned long)diff));
+#endif
+
+	if (sysfs_devno_is_lvm_private(st.st_rdev)) {
+		blkid_free_dev(dev);
+		return NULL;
+	}
+	if (!cache->probe) {
+		cache->probe = blkid_new_probe();
+		if (!cache->probe) {
+			blkid_free_dev(dev);
+			return NULL;
+		}
+	}
+
+	fd = open(dev->bid_name, O_RDONLY|O_CLOEXEC);
+	if (fd < 0) {
+		DBG(PROBE, ul_debug("blkid_verify: error %m (%d) while "
+					"opening %s", errno,
+					dev->bid_name));
+		goto open_err;
+	}
+
+	if (blkid_probe_set_device(cache->probe, fd, 0, 0)) {
+		/* failed to read the device */
+		close(fd);
+		blkid_free_dev(dev);
+		return NULL;
+	}
+
+	/* remove old cache info */
+	iter = blkid_tag_iterate_begin(dev);
+	while (blkid_tag_next(iter, &type, &value) == 0)
+		blkid_set_tag(dev, type, NULL, 0);
+	blkid_tag_iterate_end(iter);
+
+	/* enable superblocks probing */
+	blkid_probe_enable_superblocks(cache->probe, TRUE);
+	blkid_probe_set_superblocks_flags(cache->probe,
+		BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
+		BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE);
+
+	/* enable partitions probing */
+	blkid_probe_enable_partitions(cache->probe, TRUE);
+	blkid_probe_set_partitions_flags(cache->probe, BLKID_PARTS_ENTRY_DETAILS);
+
+	/* probe */
+	if (blkid_do_safeprobe(cache->probe)) {
+		/* found nothing or error */
+		blkid_free_dev(dev);
+		dev = NULL;
+	}
+
+	if (dev) {
+#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
+		struct timeval tv;
+		if (!gettimeofday(&tv, NULL)) {
+			dev->bid_time = tv.tv_sec;
+			dev->bid_utime = tv.tv_usec;
+		} else
+#endif
+			dev->bid_time = time(0);
+
+		dev->bid_devno = st.st_rdev;
+		dev->bid_flags |= BLKID_BID_FL_VERIFIED;
+		cache->bic_flags |= BLKID_BIC_FL_CHANGED;
+
+		blkid_probe_to_tags(cache->probe, dev);
+
+		DBG(PROBE, ul_debug("%s: devno 0x%04llx, type %s",
+			   dev->bid_name, (long long)st.st_rdev, dev->bid_type));
+	}
+
+	blkid_reset_probe(cache->probe);
+	blkid_probe_reset_superblocks_filter(cache->probe);
+	close(fd);
+	return dev;
+}
+
+#ifdef TEST_PROGRAM
+int main(int argc, char **argv)
+{
+	blkid_dev dev;
+	blkid_cache cache;
+	int ret;
+
+	if (argc != 2) {
+		fprintf(stderr, "Usage: %s device\n"
+			"Probe a single device to determine type\n", argv[0]);
+		exit(1);
+	}
+	if ((ret = blkid_get_cache(&cache, "/dev/null")) != 0) {
+		fprintf(stderr, "%s: error creating cache (%d)\n",
+			argv[0], ret);
+		exit(1);
+	}
+	dev = blkid_get_dev(cache, argv[1], BLKID_DEV_NORMAL);
+	if (!dev) {
+		printf("%s: %s has an unsupported type\n", argv[0], argv[1]);
+		return (1);
+	}
+	printf("TYPE='%s'\n", dev->bid_type ? dev->bid_type : "(null)");
+	if (dev->bid_label)
+		printf("LABEL='%s'\n", dev->bid_label);
+	if (dev->bid_uuid)
+		printf("UUID='%s'\n", dev->bid_uuid);
+
+	blkid_free_dev(dev);
+	return (0);
+}
+#endif
diff --git a/libblkid/src/version.c b/libblkid/src/version.c
new file mode 100644
index 0000000..9d129f7
--- /dev/null
+++ b/libblkid/src/version.c
@@ -0,0 +1,62 @@
+/*
+ * version.c --- Return the version of the blkid library
+ *
+ * Copyright (C) 2004 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Lesser General
+ * Public License.
+ * %End-Header%
+ */
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include <blkid.h>
+
+/* LIBBLKID_* defined in the global config.h */
+static const char *lib_version = LIBBLKID_VERSION;	/* release version */
+static const char *lib_date = LIBBLKID_DATE;
+
+/**
+ * blkid_parse_version_string:
+ * @ver_string:  version string (e.g. "2.16.0")
+ *
+ * Returns: release version code.
+ */
+int blkid_parse_version_string(const char *ver_string)
+{
+	const char *cp;
+	int version = 0;
+
+	for (cp = ver_string; *cp; cp++) {
+		if (*cp == '.')
+			continue;
+		if (!isdigit(*cp))
+			break;
+		version = (version * 10) + (*cp - '0');
+	}
+	return version;
+}
+
+/**
+ * blkid_get_library_version:
+ * @ver_string: returns relese version (!= SONAME version)
+ * @date_string: returns date
+ *
+ * Returns: release version code.
+ */
+int blkid_get_library_version(const char **ver_string,
+			       const char **date_string)
+{
+	if (ver_string)
+		*ver_string = lib_version;
+	if (date_string)
+		*date_string = lib_date;
+
+	return blkid_parse_version_string(lib_version);
+}
diff --git a/libblkid/src/widechar.h b/libblkid/src/widechar.h
new file mode 100644
index 0000000..b023b5f
--- /dev/null
+++ b/libblkid/src/widechar.h
@@ -0,0 +1,38 @@
+/* Declarations for wide characters */
+/* This file must be included last because the redefinition of wchar_t may
+   cause conflicts when system include files were included after it. */
+
+#ifdef HAVE_WIDECHAR
+
+# include <wchar.h>
+# include <wctype.h>
+
+#else /* !HAVE_WIDECHAR */
+
+# include <ctype.h>
+  /* Fallback for types */
+# define wchar_t char
+# define wint_t int
+# define WEOF EOF
+  /* Fallback for input operations */
+# define fgetwc fgetc
+# define getwc getc
+# define getwchar getchar
+# define fgetws fgets
+  /* Fallback for output operations */
+# define fputwc fputc
+# define putwc putc
+# define putwchar putchar
+# define fputws fputs
+  /* Fallback for character classification */
+# define iswgraph isgraph
+# define iswprint isprint
+# define iswspace isspace
+  /* Fallback for string functions */
+# define wcschr strchr
+# define wcsdup strdup
+# define wcslen strlen
+
+# define wcwidth(c) 1
+
+#endif /* HAVE_WIDECHAR */
diff --git a/libblkid/src/xalloc.h b/libblkid/src/xalloc.h
new file mode 100644
index 0000000..f012fb2
--- /dev/null
+++ b/libblkid/src/xalloc.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2010 Davidlohr Bueso <dave@gnu.org>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * General memory allocation wrappers for malloc, realloc, calloc and strdup
+ */
+
+#ifndef UTIL_LINUX_XALLOC_H
+#define UTIL_LINUX_XALLOC_H
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "c.h"
+
+#ifndef XALLOC_EXIT_CODE
+# define XALLOC_EXIT_CODE EXIT_FAILURE
+#endif
+
+static inline __ul_alloc_size(1)
+void *xmalloc(const size_t size)
+{
+        void *ret = malloc(size);
+
+        if (!ret && size)
+                err(XALLOC_EXIT_CODE, "cannot allocate %zu bytes", size);
+        return ret;
+}
+
+static inline __ul_alloc_size(2)
+void *xrealloc(void *ptr, const size_t size)
+{
+        void *ret = realloc(ptr, size);
+
+        if (!ret && size)
+                err(XALLOC_EXIT_CODE, "cannot allocate %zu bytes", size);
+        return ret;
+}
+
+static inline __ul_calloc_size(1, 2)
+void *xcalloc(const size_t nelems, const size_t size)
+{
+        void *ret = calloc(nelems, size);
+
+        if (!ret && size && nelems)
+                err(XALLOC_EXIT_CODE, "cannot allocate %zu bytes", size);
+        return ret;
+}
+
+static inline char __attribute__((warn_unused_result)) *xstrdup(const char *str)
+{
+        char *ret;
+
+        if (!str)
+                return NULL;
+
+        ret = strdup(str);
+
+        if (!ret)
+                err(XALLOC_EXIT_CODE, "cannot duplicate string");
+        return ret;
+}
+
+static inline char * __attribute__((warn_unused_result)) xstrndup(const char *str, size_t size)
+{
+        char *ret;
+
+        if (!str)
+                return NULL;
+
+        ret = strndup(str, size);
+
+        if (!ret)
+                err(XALLOC_EXIT_CODE, "cannot duplicate string");
+        return ret;
+}
+
+
+static inline int __attribute__ ((__format__(printf, 2, 3)))
+    xasprintf(char **strp, const char *fmt, ...)
+{
+	int ret;
+	va_list args;
+	va_start(args, fmt);
+	ret = vasprintf(&(*strp), fmt, args);
+	va_end(args);
+	if (ret < 0)
+		err(XALLOC_EXIT_CODE, "cannot allocate string");
+	return ret;
+}
+
+static inline int xvasprintf(char **strp, const char *fmt, va_list ap)
+{
+	int ret = vasprintf(&(*strp), fmt, ap);
+	if (ret < 0)
+		err(XALLOC_EXIT_CODE, "cannot allocate string");
+	return ret;
+}
+
+
+static inline char * __attribute__((warn_unused_result)) xgethostname(void)
+{
+	char *name;
+	size_t sz = get_hostname_max() + 1;
+
+	name = xmalloc(sizeof(char) * sz);
+
+	if (gethostname(name, sz) != 0) {
+		free(name);
+		return NULL;
+	}
+	name[sz - 1] = '\0';
+	return name;
+}
+
+#endif
diff --git a/libcrecovery/Android.mk b/libcrecovery/Android.mk
new file mode 100644
index 0000000..734e7b1
--- /dev/null
+++ b/libcrecovery/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH := $(call my-dir)
+
+ifneq ($(TARGET_SIMULATOR),true)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := system.c popen.c
+LOCAL_MODULE := libcrecovery
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := system.c popen.c
+LOCAL_MODULE := libcrecovery
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_SHARED_LIBRARY)
+
+endif
diff --git a/libcrecovery/common.h b/libcrecovery/common.h
new file mode 100644
index 0000000..59af22e
--- /dev/null
+++ b/libcrecovery/common.h
@@ -0,0 +1,10 @@
+#ifndef LIBCRECOVERY_COMMON_H
+#define LIBCRECOVERY_COMMON_H
+
+#include <stdio.h>
+
+int __system(const char *command);
+FILE * __popen(const char *program, const char *type);
+int __pclose(FILE *iop);
+
+#endif
\ No newline at end of file
diff --git a/libcrecovery/defines.h b/libcrecovery/defines.h
new file mode 100755
index 0000000..9b1f287
--- /dev/null
+++ b/libcrecovery/defines.h
@@ -0,0 +1,2 @@
+#undef _PATH_BSHELL
+#define _PATH_BSHELL "/system/bin/sh"
diff --git a/libcrecovery/popen.c b/libcrecovery/popen.c
new file mode 100644
index 0000000..73d3c74
--- /dev/null
+++ b/libcrecovery/popen.c
@@ -0,0 +1,169 @@
+/*	$OpenBSD: popen.c,v 1.17 2005/08/08 08:05:34 espie Exp $ */
+/*
+ * Copyright (c) 1988, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software written by Ken Arnold and
+ * published in UNIX Review, Vol. 6, No. 8.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/wait.h>
+
+#include <signal.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <paths.h>
+
+#include "defines.h"
+
+static struct pid {
+	struct pid *next;
+	FILE *fp;
+	pid_t pid;
+} *pidlist;
+
+FILE *
+__popen(const char *program, const char *type)
+{
+	struct pid * volatile cur;
+	FILE *iop;
+	int pdes[2];
+	pid_t pid;
+
+	if ((*type != 'r' && *type != 'w') || type[1] != '\0') {
+		errno = EINVAL;
+		return (NULL);
+	}
+
+	if ((cur = malloc(sizeof(struct pid))) == NULL)
+		return (NULL);
+
+	if (pipe(pdes) < 0) {
+		free(cur);
+		return (NULL);
+	}
+
+	switch (pid = vfork()) {
+	case -1:			/* Error. */
+		(void)close(pdes[0]);
+		(void)close(pdes[1]);
+		free(cur);
+		return (NULL);
+		/* NOTREACHED */
+	case 0:				/* Child. */
+	    {
+		struct pid *pcur;
+		/*
+		 * because vfork() instead of fork(), must leak FILE *,
+		 * but luckily we are terminally headed for an execl()
+		 */
+		for (pcur = pidlist; pcur; pcur = pcur->next)
+			close(fileno(pcur->fp));
+
+		if (*type == 'r') {
+			int tpdes1 = pdes[1];
+
+			(void) close(pdes[0]);
+			/*
+			 * We must NOT modify pdes, due to the
+			 * semantics of vfork.
+			 */
+			if (tpdes1 != STDOUT_FILENO) {
+				(void)dup2(tpdes1, STDOUT_FILENO);
+				(void)close(tpdes1);
+				tpdes1 = STDOUT_FILENO;
+			}
+		} else {
+			(void)close(pdes[1]);
+			if (pdes[0] != STDIN_FILENO) {
+				(void)dup2(pdes[0], STDIN_FILENO);
+				(void)close(pdes[0]);
+			}
+		}
+		execl(_PATH_BSHELL, "sh", "-c", program, (char *)NULL);
+		_exit(127);
+		/* NOTREACHED */
+	    }
+	}
+
+	/* Parent; assume fdopen can't fail. */
+	if (*type == 'r') {
+		iop = fdopen(pdes[0], type);
+		(void)close(pdes[1]);
+	} else {
+		iop = fdopen(pdes[1], type);
+		(void)close(pdes[0]);
+	}
+
+	/* Link into list of file descriptors. */
+	cur->fp = iop;
+	cur->pid =  pid;
+	cur->next = pidlist;
+	pidlist = cur;
+
+	return (iop);
+}
+
+/*
+ * pclose --
+ *	Pclose returns -1 if stream is not associated with a `popened' command,
+ *	if already `pclosed', or waitpid returns an error.
+ */
+int
+__pclose(FILE *iop)
+{
+	struct pid *cur, *last;
+	int pstat;
+	pid_t pid;
+
+	/* Find the appropriate file pointer. */
+	for (last = NULL, cur = pidlist; cur; last = cur, cur = cur->next)
+		if (cur->fp == iop)
+			break;
+
+	if (cur == NULL)
+		return (-1);
+
+	(void)fclose(iop);
+
+	do {
+		pid = waitpid(cur->pid, &pstat, 0);
+	} while (pid == -1 && errno == EINTR);
+
+	/* Remove the entry from the linked list. */
+	if (last == NULL)
+		pidlist = cur->next;
+	else
+		last->next = cur->next;
+	free(cur);
+
+	return (pid == -1 ? -1 : pstat);
+}
diff --git a/libcrecovery/system.c b/libcrecovery/system.c
new file mode 100644
index 0000000..c5dd550
--- /dev/null
+++ b/libcrecovery/system.c
@@ -0,0 +1,76 @@
+/*	$OpenBSD: system.c,v 1.8 2005/08/08 08:05:37 espie Exp $ */
+/*
+ * Copyright (c) 1988 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <paths.h>
+#include <sys/wait.h>
+
+#include "defines.h"
+
+extern char **environ;
+
+int
+__system(const char *command)
+{
+  pid_t pid;
+	sig_t intsave, quitsave;
+	sigset_t mask, omask;
+	int pstat;
+	char *argp[] = {"sh", "-c", NULL, NULL};
+
+	if (!command)		/* just checking... */
+		return(1);
+
+	argp[2] = (char *)command;
+
+	sigemptyset(&mask);
+	sigaddset(&mask, SIGCHLD);
+	sigprocmask(SIG_BLOCK, &mask, &omask);
+	switch (pid = vfork()) {
+	case -1:			/* error */
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		return(-1);
+	case 0:				/* child */
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		execve(_PATH_BSHELL, argp, environ);
+    _exit(127);
+  }
+
+	intsave = (sig_t)  signal(SIGINT, SIG_IGN);
+	quitsave = (sig_t) signal(SIGQUIT, SIG_IGN);
+	pid = waitpid(pid, (int *)&pstat, 0);
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+	(void)signal(SIGINT, intsave);
+	(void)signal(SIGQUIT, quitsave);
+	return (pid == -1 ? -1 : pstat);
+}
diff --git a/libmincrypt/Android.mk b/libmincrypt/Android.mk
new file mode 100644
index 0000000..774e918
--- /dev/null
+++ b/libmincrypt/Android.mk
@@ -0,0 +1,27 @@
+# Copyright 2008 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libmincrypttwrp
+LOCAL_MODULE_TAGS := optional
+LOCAL_C_INCLUDES := $(commands_recovery_local_path)/libmincrypt/includes
+LOCAL_SRC_FILES := dsa_sig.c p256.c p256_ec.c p256_ecdsa.c rsa.c sha.c sha256.c
+LOCAL_CFLAGS := -Wall -Werror
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libmincrypttwrp
+LOCAL_MODULE_TAGS := optional
+LOCAL_C_INCLUDES := $(commands_recovery_local_path)/libmincrypt/includes
+LOCAL_SRC_FILES := dsa_sig.c p256.c p256_ec.c p256_ecdsa.c rsa.c sha.c sha256.c
+LOCAL_CFLAGS := -Wall -Werror
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libmincrypttwrp
+LOCAL_MODULE_TAGS := optional
+LOCAL_C_INCLUDES := $(commands_recovery_local_path)/libmincrypt/includes
+LOCAL_SRC_FILES := dsa_sig.c p256.c p256_ec.c p256_ecdsa.c rsa.c sha.c sha256.c
+LOCAL_CFLAGS := -Wall -Werror
+include $(BUILD_HOST_STATIC_LIBRARY)
diff --git a/libmincrypt/NOTICE b/libmincrypt/NOTICE
new file mode 100644
index 0000000..430d3d6
--- /dev/null
+++ b/libmincrypt/NOTICE
@@ -0,0 +1,23 @@
+ Copyright 2008, The Android Open Source Project
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+     * Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+     * Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+     * Neither the name of Google Inc. nor the names of its contributors may
+       be used to endorse or promote products derived from this software
+       without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR 
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 
+ EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/libmincrypt/dsa_sig.c b/libmincrypt/dsa_sig.c
new file mode 100644
index 0000000..101314b
--- /dev/null
+++ b/libmincrypt/dsa_sig.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "mincrypt/dsa_sig.h"
+#include "mincrypt/p256.h"
+
+/**
+ * Trims off the leading zero bytes and copy it to a buffer aligning it to the end.
+ */
+static inline int trim_to_p256_bytes(unsigned char dst[P256_NBYTES], unsigned char *src,
+        int src_len) {
+    int dst_offset;
+    while (*src == '\0' && src_len > 0) {
+        src++;
+        src_len--;
+    }
+    if (src_len > P256_NBYTES || src_len < 1) {
+        return 0;
+    }
+    dst_offset = P256_NBYTES - src_len;
+    memset(dst, 0, dst_offset);
+    memcpy(dst + dst_offset, src, src_len);
+    return 1;
+}
+
+/**
+ * Unpacks the ASN.1 DSA signature sequence.
+ */
+int dsa_sig_unpack(unsigned char* sig, int sig_len, p256_int* r_int, p256_int* s_int) {
+    /*
+     * Structure is:
+     *   0x30 0xNN  SEQUENCE + s_length
+     *     0x02 0xNN  INTEGER + r_length
+     *       0xAA 0xBB ..   r_length bytes of "r" (offset 4)
+     *     0x02 0xNN  INTEGER + s_length
+     *       0xMM 0xNN ..   s_length bytes of "s" (offset 6 + r_len)
+     */
+    int seq_len;
+    unsigned char r_bytes[P256_NBYTES];
+    unsigned char s_bytes[P256_NBYTES];
+    int r_len;
+    int s_len;
+
+    memset(r_bytes, 0, sizeof(r_bytes));
+    memset(s_bytes, 0, sizeof(s_bytes));
+
+    /*
+     * Must have at least:
+     * 2 bytes sequence header and length
+     * 2 bytes R integer header and length
+     * 1 byte of R
+     * 2 bytes S integer header and length
+     * 1 byte of S
+     *
+     * 8 bytes total
+     */
+    if (sig_len < 8 || sig[0] != 0x30 || sig[2] != 0x02) {
+        return 0;
+    }
+
+    seq_len = sig[1];
+    if ((seq_len <= 0) || (seq_len + 2 != sig_len)) {
+        return 0;
+    }
+
+    r_len = sig[3];
+    /*
+     * Must have at least:
+     * 2 bytes for R header and length
+     * 2 bytes S integer header and length
+     * 1 byte of S
+     */
+    if ((r_len < 1) || (r_len > seq_len - 5) || (sig[4 + r_len] != 0x02)) {
+        return 0;
+    }
+    s_len = sig[5 + r_len];
+
+    /**
+     * Must have:
+     * 2 bytes for R header and length
+     * r_len bytes for R
+     * 2 bytes S integer header and length
+     */
+    if ((s_len < 1) || (s_len != seq_len - 4 - r_len)) {
+        return 0;
+    }
+
+    /*
+     * ASN.1 encoded integers are zero-padded for positive integers. Make sure we have
+     * a correctly-sized buffer and that the resulting integer isn't too large.
+     */
+    if (!trim_to_p256_bytes(r_bytes, &sig[4], r_len)
+            || !trim_to_p256_bytes(s_bytes, &sig[6 + r_len], s_len)) {
+        return 0;
+    }
+
+    p256_from_bin(r_bytes, r_int);
+    p256_from_bin(s_bytes, s_int);
+
+    return 1;
+}
diff --git a/libmincrypt/includes/mincrypt/dsa_sig.h b/libmincrypt/includes/mincrypt/dsa_sig.h
new file mode 100644
index 0000000..b0d91cd
--- /dev/null
+++ b/libmincrypt/includes/mincrypt/dsa_sig.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_DSA_SIG_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_DSA_SIG_H_
+
+#include "mincrypt/p256.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Returns 0 if input sig is not a valid ASN.1 sequence
+int dsa_sig_unpack(unsigned char* sig, int sig_len, p256_int* r_int, p256_int* s_int);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SYSTEM_CORE_INCLUDE_MINCRYPT_DSA_SIG_H_ */
diff --git a/libmincrypt/includes/mincrypt/hash-internal.h b/libmincrypt/includes/mincrypt/hash-internal.h
new file mode 100644
index 0000000..c813b44
--- /dev/null
+++ b/libmincrypt/includes/mincrypt/hash-internal.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+struct HASH_CTX;  // forward decl
+
+typedef struct HASH_VTAB {
+  void (* const init)(struct HASH_CTX*);
+  void (* const update)(struct HASH_CTX*, const void*, int);
+  const uint8_t* (* const final)(struct HASH_CTX*);
+  const uint8_t* (* const hash)(const void*, int, uint8_t*);
+  int size;
+} HASH_VTAB;
+
+typedef struct HASH_CTX {
+  const HASH_VTAB * f;
+  uint64_t count;
+  uint8_t buf[64];
+  uint32_t state[8];  // upto SHA2
+} HASH_CTX;
+
+#define HASH_init(ctx) (ctx)->f->init(ctx)
+#define HASH_update(ctx, data, len) (ctx)->f->update(ctx, data, len)
+#define HASH_final(ctx) (ctx)->f->final(ctx)
+#define HASH_hash(data, len, digest) (ctx)->f->hash(data, len, digest)
+#define HASH_size(ctx) (ctx)->f->size
+
+#ifdef __cplusplus
+}
+#endif  // __cplusplus
+
+#endif  // SYSTEM_CORE_INCLUDE_MINCRYPT_HASH_INTERNAL_H_
diff --git a/libmincrypt/includes/mincrypt/p256.h b/libmincrypt/includes/mincrypt/p256.h
new file mode 100644
index 0000000..465a1b9
--- /dev/null
+++ b/libmincrypt/includes/mincrypt/p256.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_LITE_P256_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_LITE_P256_H_
+
+// Collection of routines manipulating 256 bit unsigned integers.
+// Just enough to implement ecdsa-p256 and related algorithms.
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define P256_BITSPERDIGIT 32
+#define P256_NDIGITS 8
+#define P256_NBYTES 32
+
+typedef int p256_err;
+typedef uint32_t p256_digit;
+typedef int32_t p256_sdigit;
+typedef uint64_t p256_ddigit;
+typedef int64_t p256_sddigit;
+
+// Defining p256_int as struct to leverage struct assigment.
+typedef struct {
+  p256_digit a[P256_NDIGITS];
+} p256_int;
+
+extern const p256_int SECP256r1_n;  // Curve order
+extern const p256_int SECP256r1_p;  // Curve prime
+extern const p256_int SECP256r1_b;  // Curve param
+
+// Initialize a p256_int to zero.
+void p256_init(p256_int* a);
+
+// Clear a p256_int to zero.
+void p256_clear(p256_int* a);
+
+// Return bit. Index 0 is least significant.
+int p256_get_bit(const p256_int* a, int index);
+
+// b := a % MOD
+void p256_mod(
+    const p256_int* MOD,
+    const p256_int* a,
+    p256_int* b);
+
+// c := a * (top_b | b) % MOD
+void p256_modmul(
+    const p256_int* MOD,
+    const p256_int* a,
+    const p256_digit top_b,
+    const p256_int* b,
+    p256_int* c);
+
+// b := 1 / a % MOD
+// MOD best be SECP256r1_n
+void p256_modinv(
+    const p256_int* MOD,
+    const p256_int* a,
+    p256_int* b);
+
+// b := 1 / a % MOD
+// MOD best be SECP256r1_n
+// Faster than p256_modinv()
+void p256_modinv_vartime(
+    const p256_int* MOD,
+    const p256_int* a,
+    p256_int* b);
+
+// b := a << (n % P256_BITSPERDIGIT)
+// Returns the bits shifted out of most significant digit.
+p256_digit p256_shl(const p256_int* a, int n, p256_int* b);
+
+// b := a >> (n % P256_BITSPERDIGIT)
+void p256_shr(const p256_int* a, int n, p256_int* b);
+
+int p256_is_zero(const p256_int* a);
+int p256_is_odd(const p256_int* a);
+int p256_is_even(const p256_int* a);
+
+// Returns -1, 0 or 1.
+int p256_cmp(const p256_int* a, const p256_int *b);
+
+// c: = a - b
+// Returns -1 on borrow.
+int p256_sub(const p256_int* a, const p256_int* b, p256_int* c);
+
+// c := a + b
+// Returns 1 on carry.
+int p256_add(const p256_int* a, const p256_int* b, p256_int* c);
+
+// c := a + (single digit)b
+// Returns carry 1 on carry.
+int p256_add_d(const p256_int* a, p256_digit b, p256_int* c);
+
+// ec routines.
+
+// {out_x,out_y} := nG
+void p256_base_point_mul(const p256_int *n,
+                         p256_int *out_x,
+                         p256_int *out_y);
+
+// {out_x,out_y} := n{in_x,in_y}
+void p256_point_mul(const p256_int *n,
+                    const p256_int *in_x,
+                    const p256_int *in_y,
+                    p256_int *out_x,
+                    p256_int *out_y);
+
+// {out_x,out_y} := n1G + n2{in_x,in_y}
+void p256_points_mul_vartime(
+    const p256_int *n1, const p256_int *n2,
+    const p256_int *in_x, const p256_int *in_y,
+    p256_int *out_x, p256_int *out_y);
+
+// Return whether point {x,y} is on curve.
+int p256_is_valid_point(const p256_int* x, const p256_int* y);
+
+// Outputs big-endian binary form. No leading zero skips.
+void p256_to_bin(const p256_int* src, uint8_t dst[P256_NBYTES]);
+
+// Reads from big-endian binary form,
+// thus pre-pad with leading zeros if short.
+void p256_from_bin(const uint8_t src[P256_NBYTES], p256_int* dst);
+
+#define P256_DIGITS(x) ((x)->a)
+#define P256_DIGIT(x,y) ((x)->a[y])
+
+#define P256_ZERO {{0}}
+#define P256_ONE {{1}}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // SYSTEM_CORE_INCLUDE_MINCRYPT_LITE_P256_H_
diff --git a/libmincrypt/includes/mincrypt/p256_ecdsa.h b/libmincrypt/includes/mincrypt/p256_ecdsa.h
new file mode 100644
index 0000000..da339fa
--- /dev/null
+++ b/libmincrypt/includes/mincrypt/p256_ecdsa.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_P256_ECDSA_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_P256_ECDSA_H_
+
+// Using current directory as relative include path here since
+// this code typically gets lifted into a variety of build systems
+// and directory structures.
+#include "p256.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Returns 0 if {r,s} is not a signature on message for
+// public key {key_x,key_y}.
+//
+// Note: message is a p256_int.
+// Convert from a binary string using p256_from_bin().
+int p256_ecdsa_verify(const p256_int* key_x,
+                      const p256_int* key_y,
+                      const p256_int* message,
+                      const p256_int* r, const p256_int* s);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // SYSTEM_CORE_INCLUDE_MINCRYPT_P256_ECDSA_H_
diff --git a/libmincrypt/includes/mincrypt/rsa.h b/libmincrypt/includes/mincrypt/rsa.h
new file mode 100644
index 0000000..3d0556b
--- /dev/null
+++ b/libmincrypt/includes/mincrypt/rsa.h
@@ -0,0 +1,58 @@
+/* rsa.h
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+**     * Redistributions of source code must retain the above copyright
+**       notice, this list of conditions and the following disclaimer.
+**     * Redistributions in binary form must reproduce the above copyright
+**       notice, this list of conditions and the following disclaimer in the
+**       documentation and/or other materials provided with the distribution.
+**     * Neither the name of Google Inc. nor the names of its contributors may
+**       be used to endorse or promote products derived from this software
+**       without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+** EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_RSA_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_RSA_H_
+
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define RSANUMBYTES 256           /* 2048 bit key length */
+#define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t))
+
+typedef struct RSAPublicKey {
+    int len;                  /* Length of n[] in number of uint32_t */
+    uint32_t n0inv;           /* -1 / n[0] mod 2^32 */
+    uint32_t n[RSANUMWORDS];  /* modulus as little endian array */
+    uint32_t rr[RSANUMWORDS]; /* R^2 as little endian array */
+    int exponent;             /* 3 or 65537 */
+} RSAPublicKey;
+
+int RSA_verify(const RSAPublicKey *key,
+               const uint8_t* signature,
+               const int len,
+               const uint8_t* hash,
+               const int hash_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // SYSTEM_CORE_INCLUDE_MINCRYPT_RSA_H_
diff --git a/libmincrypt/includes/mincrypt/sha.h b/libmincrypt/includes/mincrypt/sha.h
new file mode 100644
index 0000000..ef60aab
--- /dev/null
+++ b/libmincrypt/includes/mincrypt/sha.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2005 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_SHA1_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_SHA1_H_
+
+#include <stdint.h>
+#include "hash-internal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+typedef HASH_CTX SHA_CTX;
+
+void SHA_init(SHA_CTX* ctx);
+void SHA_update(SHA_CTX* ctx, const void* data, int len);
+const uint8_t* SHA_final(SHA_CTX* ctx);
+
+// Convenience method. Returns digest address.
+// NOTE: *digest needs to hold SHA_DIGEST_SIZE bytes.
+const uint8_t* SHA_hash(const void* data, int len, uint8_t* digest);
+
+#define SHA_DIGEST_SIZE 20
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif  // SYSTEM_CORE_INCLUDE_MINCRYPT_SHA1_H_
diff --git a/libmincrypt/includes/mincrypt/sha256.h b/libmincrypt/includes/mincrypt/sha256.h
new file mode 100644
index 0000000..3a87c31
--- /dev/null
+++ b/libmincrypt/includes/mincrypt/sha256.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SYSTEM_CORE_INCLUDE_MINCRYPT_SHA256_H_
+#define SYSTEM_CORE_INCLUDE_MINCRYPT_SHA256_H_
+
+#include <stdint.h>
+#include "hash-internal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+typedef HASH_CTX SHA256_CTX;
+
+void SHA256_init(SHA256_CTX* ctx);
+void SHA256_update(SHA256_CTX* ctx, const void* data, int len);
+const uint8_t* SHA256_final(SHA256_CTX* ctx);
+
+// Convenience method. Returns digest address.
+const uint8_t* SHA256_hash(const void* data, int len, uint8_t* digest);
+
+#define SHA256_DIGEST_SIZE 32
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif  // SYSTEM_CORE_INCLUDE_MINCRYPT_SHA256_H_
diff --git a/libmincrypt/p256.c b/libmincrypt/p256.c
new file mode 100644
index 0000000..555a07a
--- /dev/null
+++ b/libmincrypt/p256.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This is an implementation of the P256 elliptic curve group. It's written to
+// be portable 32-bit, although it's still constant-time.
+//
+// WARNING: Implementing these functions in a constant-time manner is far from
+//          obvious. Be careful when touching this code.
+//
+// See http://www.imperialviolet.org/2010/12/04/ecc.html ([1]) for background.
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "mincrypt/p256.h"
+
+const p256_int SECP256r1_n =  // curve order
+  {{0xfc632551, 0xf3b9cac2, 0xa7179e84, 0xbce6faad, -1, -1, 0, -1}};
+
+const p256_int SECP256r1_p =  // curve field size
+  {{-1, -1, -1, 0, 0, 0, 1, -1 }};
+
+const p256_int SECP256r1_b =  // curve b
+  {{0x27d2604b, 0x3bce3c3e, 0xcc53b0f6, 0x651d06b0,
+    0x769886bc, 0xb3ebbd55, 0xaa3a93e7, 0x5ac635d8}};
+
+void p256_init(p256_int* a) {
+  memset(a, 0, sizeof(*a));
+}
+
+void p256_clear(p256_int* a) { p256_init(a); }
+
+int p256_get_bit(const p256_int* scalar, int bit) {
+  return (P256_DIGIT(scalar, bit / P256_BITSPERDIGIT)
+              >> (bit & (P256_BITSPERDIGIT - 1))) & 1;
+}
+
+int p256_is_zero(const p256_int* a) {
+  int i, result = 0;
+  for (i = 0; i < P256_NDIGITS; ++i) result |= P256_DIGIT(a, i);
+  return !result;
+}
+
+// top, c[] += a[] * b
+// Returns new top
+static p256_digit mulAdd(const p256_int* a,
+                         p256_digit b,
+                         p256_digit top,
+                         p256_digit* c) {
+  int i;
+  p256_ddigit carry = 0;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    carry += *c;
+    carry += (p256_ddigit)P256_DIGIT(a, i) * b;
+    *c++ = (p256_digit)carry;
+    carry >>= P256_BITSPERDIGIT;
+  }
+  return top + (p256_digit)carry;
+}
+
+// top, c[] -= top_a, a[]
+static p256_digit subTop(p256_digit top_a,
+                         const p256_digit* a,
+                         p256_digit top_c,
+                         p256_digit* c) {
+  int i;
+  p256_sddigit borrow = 0;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    borrow += *c;
+    borrow -= *a++;
+    *c++ = (p256_digit)borrow;
+    borrow >>= P256_BITSPERDIGIT;
+  }
+  borrow += top_c;
+  borrow -= top_a;
+  top_c = (p256_digit)borrow;
+  assert((borrow >> P256_BITSPERDIGIT) == 0);
+  return top_c;
+}
+
+// top, c[] -= MOD[] & mask (0 or -1)
+// returns new top.
+static p256_digit subM(const p256_int* MOD,
+                       p256_digit top,
+                       p256_digit* c,
+                       p256_digit mask) {
+  int i;
+  p256_sddigit borrow = 0;
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    borrow += *c;
+    borrow -= P256_DIGIT(MOD, i) & mask;
+    *c++ = (p256_digit)borrow;
+    borrow >>= P256_BITSPERDIGIT;
+  }
+  return top + (p256_digit)borrow;
+}
+
+// top, c[] += MOD[] & mask (0 or -1)
+// returns new top.
+static p256_digit addM(const p256_int* MOD,
+                       p256_digit top,
+                       p256_digit* c,
+                       p256_digit mask) {
+  int i;
+  p256_ddigit carry = 0;
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    carry += *c;
+    carry += P256_DIGIT(MOD, i) & mask;
+    *c++ = (p256_digit)carry;
+    carry >>= P256_BITSPERDIGIT;
+  }
+  return top + (p256_digit)carry;
+}
+
+// c = a * b mod MOD. c can be a and/or b.
+void p256_modmul(const p256_int* MOD,
+                 const p256_int* a,
+                 const p256_digit top_b,
+                 const p256_int* b,
+                 p256_int* c) {
+  p256_digit tmp[P256_NDIGITS * 2 + 1] = { 0 };
+  p256_digit top = 0;
+  int i;
+
+  // Multiply/add into tmp.
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    if (i) tmp[i + P256_NDIGITS - 1] = top;
+    top = mulAdd(a, P256_DIGIT(b, i), 0, tmp + i);
+  }
+
+  // Multiply/add top digit
+  tmp[i + P256_NDIGITS - 1] = top;
+  top = mulAdd(a, top_b, 0, tmp + i);
+
+  // Reduce tmp, digit by digit.
+  for (; i >= 0; --i) {
+    p256_digit reducer[P256_NDIGITS] = { 0 };
+    p256_digit top_reducer;
+
+    // top can be any value at this point.
+    // Guestimate reducer as top * MOD, since msw of MOD is -1.
+    top_reducer = mulAdd(MOD, top, 0, reducer);
+
+    // Subtract reducer from top | tmp.
+    top = subTop(top_reducer, reducer, top, tmp + i);
+
+    // top is now either 0 or 1. Make it 0, fixed-timing.
+    assert(top <= 1);
+
+    top = subM(MOD, top, tmp + i, ~(top - 1));
+
+    assert(top == 0);
+
+    // We have now reduced the top digit off tmp. Fetch new top digit.
+    top = tmp[i + P256_NDIGITS - 1];
+  }
+
+  // tmp might still be larger than MOD, yet same bit length.
+  // Make sure it is less, fixed-timing.
+  addM(MOD, 0, tmp, subM(MOD, 0, tmp, -1));
+
+  memcpy(c, tmp, P256_NBYTES);
+}
+int p256_is_odd(const p256_int* a) { return P256_DIGIT(a, 0) & 1; }
+int p256_is_even(const p256_int* a) { return !(P256_DIGIT(a, 0) & 1); }
+
+p256_digit p256_shl(const p256_int* a, int n, p256_int* b) {
+  int i;
+  p256_digit top = P256_DIGIT(a, P256_NDIGITS - 1);
+
+  n %= P256_BITSPERDIGIT;
+  for (i = P256_NDIGITS - 1; i > 0; --i) {
+    p256_digit accu = (P256_DIGIT(a, i) << n);
+    accu |= (P256_DIGIT(a, i - 1) >> (P256_BITSPERDIGIT - n));
+    P256_DIGIT(b, i) = accu;
+  }
+  P256_DIGIT(b, i) = (P256_DIGIT(a, i) << n);
+
+  top = (p256_digit)((((p256_ddigit)top) << n) >> P256_BITSPERDIGIT);
+
+  return top;
+}
+
+void p256_shr(const p256_int* a, int n, p256_int* b) {
+  int i;
+
+  n %= P256_BITSPERDIGIT;
+  for (i = 0; i < P256_NDIGITS - 1; ++i) {
+    p256_digit accu = (P256_DIGIT(a, i) >> n);
+    accu |= (P256_DIGIT(a, i + 1) << (P256_BITSPERDIGIT - n));
+    P256_DIGIT(b, i) = accu;
+  }
+  P256_DIGIT(b, i) = (P256_DIGIT(a, i) >> n);
+}
+
+static void p256_shr1(const p256_int* a, int highbit, p256_int* b) {
+  int i;
+
+  for (i = 0; i < P256_NDIGITS - 1; ++i) {
+    p256_digit accu = (P256_DIGIT(a, i) >> 1);
+    accu |= (P256_DIGIT(a, i + 1) << (P256_BITSPERDIGIT - 1));
+    P256_DIGIT(b, i) = accu;
+  }
+  P256_DIGIT(b, i) = (P256_DIGIT(a, i) >> 1) |
+      (highbit << (P256_BITSPERDIGIT - 1));
+}
+
+// Return -1, 0, 1 for a < b, a == b or a > b respectively.
+int p256_cmp(const p256_int* a, const p256_int* b) {
+  int i;
+  p256_sddigit borrow = 0;
+  p256_digit notzero = 0;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    borrow += (p256_sddigit)P256_DIGIT(a, i) - P256_DIGIT(b, i);
+    // Track whether any result digit is ever not zero.
+    // Relies on !!(non-zero) evaluating to 1, e.g., !!(-1) evaluating to 1.
+    notzero |= !!((p256_digit)borrow);
+    borrow >>= P256_BITSPERDIGIT;
+  }
+  return (int)borrow | notzero;
+}
+
+// c = a - b. Returns borrow: 0 or -1.
+int p256_sub(const p256_int* a, const p256_int* b, p256_int* c) {
+  int i;
+  p256_sddigit borrow = 0;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    borrow += (p256_sddigit)P256_DIGIT(a, i) - P256_DIGIT(b, i);
+    if (c) P256_DIGIT(c, i) = (p256_digit)borrow;
+    borrow >>= P256_BITSPERDIGIT;
+  }
+  return (int)borrow;
+}
+
+// c = a + b. Returns carry: 0 or 1.
+int p256_add(const p256_int* a, const p256_int* b, p256_int* c) {
+  int i;
+  p256_ddigit carry = 0;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    carry += (p256_ddigit)P256_DIGIT(a, i) + P256_DIGIT(b, i);
+    if (c) P256_DIGIT(c, i) = (p256_digit)carry;
+    carry >>= P256_BITSPERDIGIT;
+  }
+  return (int)carry;
+}
+
+// b = a + d. Returns carry, 0 or 1.
+int p256_add_d(const p256_int* a, p256_digit d, p256_int* b) {
+  int i;
+  p256_ddigit carry = d;
+
+  for (i = 0; i < P256_NDIGITS; ++i) {
+    carry += (p256_ddigit)P256_DIGIT(a, i);
+    if (b) P256_DIGIT(b, i) = (p256_digit)carry;
+    carry >>= P256_BITSPERDIGIT;
+  }
+  return (int)carry;
+}
+
+// b = 1/a mod MOD, binary euclid.
+void p256_modinv_vartime(const p256_int* MOD,
+                         const p256_int* a,
+                         p256_int* b) {
+  p256_int R = P256_ZERO;
+  p256_int S = P256_ONE;
+  p256_int U = *MOD;
+  p256_int V = *a;
+
+  for (;;) {
+    if (p256_is_even(&U)) {
+      p256_shr1(&U, 0, &U);
+      if (p256_is_even(&R)) {
+        p256_shr1(&R, 0, &R);
+      } else {
+        // R = (R+MOD)/2
+        p256_shr1(&R, p256_add(&R, MOD, &R), &R);
+      }
+    } else if (p256_is_even(&V)) {
+      p256_shr1(&V, 0, &V);
+      if (p256_is_even(&S)) {
+        p256_shr1(&S, 0, &S);
+      } else {
+        // S = (S+MOD)/2
+        p256_shr1(&S, p256_add(&S, MOD, &S) , &S);
+      }
+    } else {  // U,V both odd.
+      if (!p256_sub(&V, &U, NULL)) {
+        p256_sub(&V, &U, &V);
+        if (p256_sub(&S, &R, &S)) p256_add(&S, MOD, &S);
+        if (p256_is_zero(&V)) break;  // done.
+      } else {
+        p256_sub(&U, &V, &U);
+        if (p256_sub(&R, &S, &R)) p256_add(&R, MOD, &R);
+      }
+    }
+  }
+
+  p256_mod(MOD, &R, b);
+}
+
+void p256_mod(const p256_int* MOD,
+              const p256_int* in,
+              p256_int* out) {
+  if (out != in) *out = *in;
+  addM(MOD, 0, P256_DIGITS(out), subM(MOD, 0, P256_DIGITS(out), -1));
+}
+
+// Verify y^2 == x^3 - 3x + b mod p
+// and 0 < x < p and 0 < y < p
+int p256_is_valid_point(const p256_int* x, const p256_int* y) {
+  p256_int y2, x3;
+
+  if (p256_cmp(&SECP256r1_p, x) <= 0 ||
+      p256_cmp(&SECP256r1_p, y) <= 0 ||
+      p256_is_zero(x) ||
+      p256_is_zero(y)) return 0;
+
+  p256_modmul(&SECP256r1_p, y, 0, y, &y2);  // y^2
+
+  p256_modmul(&SECP256r1_p, x, 0, x, &x3);  // x^2
+  p256_modmul(&SECP256r1_p, x, 0, &x3, &x3);  // x^3
+  if (p256_sub(&x3, x, &x3)) p256_add(&x3, &SECP256r1_p, &x3);  // x^3 - x
+  if (p256_sub(&x3, x, &x3)) p256_add(&x3, &SECP256r1_p, &x3);  // x^3 - 2x
+  if (p256_sub(&x3, x, &x3)) p256_add(&x3, &SECP256r1_p, &x3);  // x^3 - 3x
+  if (p256_add(&x3, &SECP256r1_b, &x3))  // x^3 - 3x + b
+    p256_sub(&x3, &SECP256r1_p, &x3);
+
+  return p256_cmp(&y2, &x3) == 0;
+}
+
+void p256_from_bin(const uint8_t src[P256_NBYTES], p256_int* dst) {
+  int i;
+  const uint8_t* p = &src[0];
+
+  for (i = P256_NDIGITS - 1; i >= 0; --i) {
+    P256_DIGIT(dst, i) =
+        (p[0] << 24) |
+        (p[1] << 16) |
+        (p[2] << 8) |
+        p[3];
+    p += 4;
+  }
+}
diff --git a/libmincrypt/p256_ec.c b/libmincrypt/p256_ec.c
new file mode 100644
index 0000000..90262cc
--- /dev/null
+++ b/libmincrypt/p256_ec.c
@@ -0,0 +1,1279 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// This is an implementation of the P256 elliptic curve group. It's written to
+// be portable 32-bit, although it's still constant-time.
+//
+// WARNING: Implementing these functions in a constant-time manner is far from
+//          obvious. Be careful when touching this code.
+//
+// See http://www.imperialviolet.org/2010/12/04/ecc.html ([1]) for background.
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "mincrypt/p256.h"
+
+typedef uint8_t u8;
+typedef uint32_t u32;
+typedef int32_t s32;
+typedef uint64_t u64;
+
+/* Our field elements are represented as nine 32-bit limbs.
+ *
+ * The value of an felem (field element) is:
+ *   x[0] + (x[1] * 2**29) + (x[2] * 2**57) + ... + (x[8] * 2**228)
+ *
+ * That is, each limb is alternately 29 or 28-bits wide in little-endian
+ * order.
+ *
+ * This means that an felem hits 2**257, rather than 2**256 as we would like. A
+ * 28, 29, ... pattern would cause us to hit 2**256, but that causes problems
+ * when multiplying as terms end up one bit short of a limb which would require
+ * much bit-shifting to correct.
+ *
+ * Finally, the values stored in an felem are in Montgomery form. So the value
+ * |y| is stored as (y*R) mod p, where p is the P-256 prime and R is 2**257.
+ */
+typedef u32 limb;
+#define NLIMBS 9
+typedef limb felem[NLIMBS];
+
+static const limb kBottom28Bits = 0xfffffff;
+static const limb kBottom29Bits = 0x1fffffff;
+
+/* kOne is the number 1 as an felem. It's 2**257 mod p split up into 29 and
+ * 28-bit words. */
+static const felem kOne = {
+    2, 0, 0, 0xffff800,
+    0x1fffffff, 0xfffffff, 0x1fbfffff, 0x1ffffff,
+    0
+};
+static const felem kZero = {0};
+static const felem kP = {
+    0x1fffffff, 0xfffffff, 0x1fffffff, 0x3ff,
+    0, 0, 0x200000, 0xf000000,
+    0xfffffff
+};
+static const felem k2P = {
+    0x1ffffffe, 0xfffffff, 0x1fffffff, 0x7ff,
+    0, 0, 0x400000, 0xe000000,
+    0x1fffffff
+};
+/* kPrecomputed contains precomputed values to aid the calculation of scalar
+ * multiples of the base point, G. It's actually two, equal length, tables
+ * concatenated.
+ *
+ * The first table contains (x,y) felem pairs for 16 multiples of the base
+ * point, G.
+ *
+ *   Index  |  Index (binary) | Value
+ *       0  |           0000  | 0G (all zeros, omitted)
+ *       1  |           0001  | G
+ *       2  |           0010  | 2**64G
+ *       3  |           0011  | 2**64G + G
+ *       4  |           0100  | 2**128G
+ *       5  |           0101  | 2**128G + G
+ *       6  |           0110  | 2**128G + 2**64G
+ *       7  |           0111  | 2**128G + 2**64G + G
+ *       8  |           1000  | 2**192G
+ *       9  |           1001  | 2**192G + G
+ *      10  |           1010  | 2**192G + 2**64G
+ *      11  |           1011  | 2**192G + 2**64G + G
+ *      12  |           1100  | 2**192G + 2**128G
+ *      13  |           1101  | 2**192G + 2**128G + G
+ *      14  |           1110  | 2**192G + 2**128G + 2**64G
+ *      15  |           1111  | 2**192G + 2**128G + 2**64G + G
+ *
+ * The second table follows the same style, but the terms are 2**32G,
+ * 2**96G, 2**160G, 2**224G.
+ *
+ * This is ~2KB of data. */
+static const limb kPrecomputed[NLIMBS * 2 * 15 * 2] = {
+    0x11522878, 0xe730d41, 0xdb60179, 0x4afe2ff, 0x12883add, 0xcaddd88, 0x119e7edc, 0xd4a6eab, 0x3120bee,
+    0x1d2aac15, 0xf25357c, 0x19e45cdd, 0x5c721d0, 0x1992c5a5, 0xa237487, 0x154ba21, 0x14b10bb, 0xae3fe3,
+    0xd41a576, 0x922fc51, 0x234994f, 0x60b60d3, 0x164586ae, 0xce95f18, 0x1fe49073, 0x3fa36cc, 0x5ebcd2c,
+    0xb402f2f, 0x15c70bf, 0x1561925c, 0x5a26704, 0xda91e90, 0xcdc1c7f, 0x1ea12446, 0xe1ade1e, 0xec91f22,
+    0x26f7778, 0x566847e, 0xa0bec9e, 0x234f453, 0x1a31f21a, 0xd85e75c, 0x56c7109, 0xa267a00, 0xb57c050,
+    0x98fb57, 0xaa837cc, 0x60c0792, 0xcfa5e19, 0x61bab9e, 0x589e39b, 0xa324c5, 0x7d6dee7, 0x2976e4b,
+    0x1fc4124a, 0xa8c244b, 0x1ce86762, 0xcd61c7e, 0x1831c8e0, 0x75774e1, 0x1d96a5a9, 0x843a649, 0xc3ab0fa,
+    0x6e2e7d5, 0x7673a2a, 0x178b65e8, 0x4003e9b, 0x1a1f11c2, 0x7816ea, 0xf643e11, 0x58c43df, 0xf423fc2,
+    0x19633ffa, 0x891f2b2, 0x123c231c, 0x46add8c, 0x54700dd, 0x59e2b17, 0x172db40f, 0x83e277d, 0xb0dd609,
+    0xfd1da12, 0x35c6e52, 0x19ede20c, 0xd19e0c0, 0x97d0f40, 0xb015b19, 0x449e3f5, 0xe10c9e, 0x33ab581,
+    0x56a67ab, 0x577734d, 0x1dddc062, 0xc57b10d, 0x149b39d, 0x26a9e7b, 0xc35df9f, 0x48764cd, 0x76dbcca,
+    0xca4b366, 0xe9303ab, 0x1a7480e7, 0x57e9e81, 0x1e13eb50, 0xf466cf3, 0x6f16b20, 0x4ba3173, 0xc168c33,
+    0x15cb5439, 0x6a38e11, 0x73658bd, 0xb29564f, 0x3f6dc5b, 0x53b97e, 0x1322c4c0, 0x65dd7ff, 0x3a1e4f6,
+    0x14e614aa, 0x9246317, 0x1bc83aca, 0xad97eed, 0xd38ce4a, 0xf82b006, 0x341f077, 0xa6add89, 0x4894acd,
+    0x9f162d5, 0xf8410ef, 0x1b266a56, 0xd7f223, 0x3e0cb92, 0xe39b672, 0x6a2901a, 0x69a8556, 0x7e7c0,
+    0x9b7d8d3, 0x309a80, 0x1ad05f7f, 0xc2fb5dd, 0xcbfd41d, 0x9ceb638, 0x1051825c, 0xda0cf5b, 0x812e881,
+    0x6f35669, 0x6a56f2c, 0x1df8d184, 0x345820, 0x1477d477, 0x1645db1, 0xbe80c51, 0xc22be3e, 0xe35e65a,
+    0x1aeb7aa0, 0xc375315, 0xf67bc99, 0x7fdd7b9, 0x191fc1be, 0x61235d, 0x2c184e9, 0x1c5a839, 0x47a1e26,
+    0xb7cb456, 0x93e225d, 0x14f3c6ed, 0xccc1ac9, 0x17fe37f3, 0x4988989, 0x1a90c502, 0x2f32042, 0xa17769b,
+    0xafd8c7c, 0x8191c6e, 0x1dcdb237, 0x16200c0, 0x107b32a1, 0x66c08db, 0x10d06a02, 0x3fc93, 0x5620023,
+    0x16722b27, 0x68b5c59, 0x270fcfc, 0xfad0ecc, 0xe5de1c2, 0xeab466b, 0x2fc513c, 0x407f75c, 0xbaab133,
+    0x9705fe9, 0xb88b8e7, 0x734c993, 0x1e1ff8f, 0x19156970, 0xabd0f00, 0x10469ea7, 0x3293ac0, 0xcdc98aa,
+    0x1d843fd, 0xe14bfe8, 0x15be825f, 0x8b5212, 0xeb3fb67, 0x81cbd29, 0xbc62f16, 0x2b6fcc7, 0xf5a4e29,
+    0x13560b66, 0xc0b6ac2, 0x51ae690, 0xd41e271, 0xf3e9bd4, 0x1d70aab, 0x1029f72, 0x73e1c35, 0xee70fbc,
+    0xad81baf, 0x9ecc49a, 0x86c741e, 0xfe6be30, 0x176752e7, 0x23d416, 0x1f83de85, 0x27de188, 0x66f70b8,
+    0x181cd51f, 0x96b6e4c, 0x188f2335, 0xa5df759, 0x17a77eb6, 0xfeb0e73, 0x154ae914, 0x2f3ec51, 0x3826b59,
+    0xb91f17d, 0x1c72949, 0x1362bf0a, 0xe23fddf, 0xa5614b0, 0xf7d8f, 0x79061, 0x823d9d2, 0x8213f39,
+    0x1128ae0b, 0xd095d05, 0xb85c0c2, 0x1ecb2ef, 0x24ddc84, 0xe35e901, 0x18411a4a, 0xf5ddc3d, 0x3786689,
+    0x52260e8, 0x5ae3564, 0x542b10d, 0x8d93a45, 0x19952aa4, 0x996cc41, 0x1051a729, 0x4be3499, 0x52b23aa,
+    0x109f307e, 0x6f5b6bb, 0x1f84e1e7, 0x77a0cfa, 0x10c4df3f, 0x25a02ea, 0xb048035, 0xe31de66, 0xc6ecaa3,
+    0x28ea335, 0x2886024, 0x1372f020, 0xf55d35, 0x15e4684c, 0xf2a9e17, 0x1a4a7529, 0xcb7beb1, 0xb2a78a1,
+    0x1ab21f1f, 0x6361ccf, 0x6c9179d, 0xb135627, 0x1267b974, 0x4408bad, 0x1cbff658, 0xe3d6511, 0xc7d76f,
+    0x1cc7a69, 0xe7ee31b, 0x54fab4f, 0x2b914f, 0x1ad27a30, 0xcd3579e, 0xc50124c, 0x50daa90, 0xb13f72,
+    0xb06aa75, 0x70f5cc6, 0x1649e5aa, 0x84a5312, 0x329043c, 0x41c4011, 0x13d32411, 0xb04a838, 0xd760d2d,
+    0x1713b532, 0xbaa0c03, 0x84022ab, 0x6bcf5c1, 0x2f45379, 0x18ae070, 0x18c9e11e, 0x20bca9a, 0x66f496b,
+    0x3eef294, 0x67500d2, 0xd7f613c, 0x2dbbeb, 0xb741038, 0xe04133f, 0x1582968d, 0xbe985f7, 0x1acbc1a,
+    0x1a6a939f, 0x33e50f6, 0xd665ed4, 0xb4b7bd6, 0x1e5a3799, 0x6b33847, 0x17fa56ff, 0x65ef930, 0x21dc4a,
+    0x2b37659, 0x450fe17, 0xb357b65, 0xdf5efac, 0x15397bef, 0x9d35a7f, 0x112ac15f, 0x624e62e, 0xa90ae2f,
+    0x107eecd2, 0x1f69bbe, 0x77d6bce, 0x5741394, 0x13c684fc, 0x950c910, 0x725522b, 0xdc78583, 0x40eeabb,
+    0x1fde328a, 0xbd61d96, 0xd28c387, 0x9e77d89, 0x12550c40, 0x759cb7d, 0x367ef34, 0xae2a960, 0x91b8bdc,
+    0x93462a9, 0xf469ef, 0xb2e9aef, 0xd2ca771, 0x54e1f42, 0x7aaa49, 0x6316abb, 0x2413c8e, 0x5425bf9,
+    0x1bed3e3a, 0xf272274, 0x1f5e7326, 0x6416517, 0xea27072, 0x9cedea7, 0x6e7633, 0x7c91952, 0xd806dce,
+    0x8e2a7e1, 0xe421e1a, 0x418c9e1, 0x1dbc890, 0x1b395c36, 0xa1dc175, 0x1dc4ef73, 0x8956f34, 0xe4b5cf2,
+    0x1b0d3a18, 0x3194a36, 0x6c2641f, 0xe44124c, 0xa2f4eaa, 0xa8c25ba, 0xf927ed7, 0x627b614, 0x7371cca,
+    0xba16694, 0x417bc03, 0x7c0a7e3, 0x9c35c19, 0x1168a205, 0x8b6b00d, 0x10e3edc9, 0x9c19bf2, 0x5882229,
+    0x1b2b4162, 0xa5cef1a, 0x1543622b, 0x9bd433e, 0x364e04d, 0x7480792, 0x5c9b5b3, 0xe85ff25, 0x408ef57,
+    0x1814cfa4, 0x121b41b, 0xd248a0f, 0x3b05222, 0x39bb16a, 0xc75966d, 0xa038113, 0xa4a1769, 0x11fbc6c,
+    0x917e50e, 0xeec3da8, 0x169d6eac, 0x10c1699, 0xa416153, 0xf724912, 0x15cd60b7, 0x4acbad9, 0x5efc5fa,
+    0xf150ed7, 0x122b51, 0x1104b40a, 0xcb7f442, 0xfbb28ff, 0x6ac53ca, 0x196142cc, 0x7bf0fa9, 0x957651,
+    0x4e0f215, 0xed439f8, 0x3f46bd5, 0x5ace82f, 0x110916b6, 0x6db078, 0xffd7d57, 0xf2ecaac, 0xca86dec,
+    0x15d6b2da, 0x965ecc9, 0x1c92b4c2, 0x1f3811, 0x1cb080f5, 0x2d8b804, 0x19d1c12d, 0xf20bd46, 0x1951fa7,
+    0xa3656c3, 0x523a425, 0xfcd0692, 0xd44ddc8, 0x131f0f5b, 0xaf80e4a, 0xcd9fc74, 0x99bb618, 0x2db944c,
+    0xa673090, 0x1c210e1, 0x178c8d23, 0x1474383, 0x10b8743d, 0x985a55b, 0x2e74779, 0x576138, 0x9587927,
+    0x133130fa, 0xbe05516, 0x9f4d619, 0xbb62570, 0x99ec591, 0xd9468fe, 0x1d07782d, 0xfc72e0b, 0x701b298,
+    0x1863863b, 0x85954b8, 0x121a0c36, 0x9e7fedf, 0xf64b429, 0x9b9d71e, 0x14e2f5d8, 0xf858d3a, 0x942eea8,
+    0xda5b765, 0x6edafff, 0xa9d18cc, 0xc65e4ba, 0x1c747e86, 0xe4ea915, 0x1981d7a1, 0x8395659, 0x52ed4e2,
+    0x87d43b7, 0x37ab11b, 0x19d292ce, 0xf8d4692, 0x18c3053f, 0x8863e13, 0x4c146c0, 0x6bdf55a, 0x4e4457d,
+    0x16152289, 0xac78ec2, 0x1a59c5a2, 0x2028b97, 0x71c2d01, 0x295851f, 0x404747b, 0x878558d, 0x7d29aa4,
+    0x13d8341f, 0x8daefd7, 0x139c972d, 0x6b7ea75, 0xd4a9dde, 0xff163d8, 0x81d55d7, 0xa5bef68, 0xb7b30d8,
+    0xbe73d6f, 0xaa88141, 0xd976c81, 0x7e7a9cc, 0x18beb771, 0xd773cbd, 0x13f51951, 0x9d0c177, 0x1c49a78,
+};
+
+
+/* Field element operations: */
+
+/* NON_ZERO_TO_ALL_ONES returns:
+ *   0xffffffff for 0 < x <= 2**31
+ *   0 for x == 0 or x > 2**31.
+ *
+ * x must be a u32 or an equivalent type such as limb. */
+#define NON_ZERO_TO_ALL_ONES(x) ((((u32)(x) - 1) >> 31) - 1)
+
+/* felem_reduce_carry adds a multiple of p in order to cancel |carry|,
+ * which is a term at 2**257.
+ *
+ * On entry: carry < 2**3, inout[0,2,...] < 2**29, inout[1,3,...] < 2**28.
+ * On exit: inout[0,2,..] < 2**30, inout[1,3,...] < 2**29. */
+static void felem_reduce_carry(felem inout, limb carry) {
+  const u32 carry_mask = NON_ZERO_TO_ALL_ONES(carry);
+
+  inout[0] += carry << 1;
+  inout[3] += 0x10000000 & carry_mask;
+  /* carry < 2**3 thus (carry << 11) < 2**14 and we added 2**28 in the
+   * previous line therefore this doesn't underflow. */
+  inout[3] -= carry << 11;
+  inout[4] += (0x20000000 - 1) & carry_mask;
+  inout[5] += (0x10000000 - 1) & carry_mask;
+  inout[6] += (0x20000000 - 1) & carry_mask;
+  inout[6] -= carry << 22;
+  /* This may underflow if carry is non-zero but, if so, we'll fix it in the
+   * next line. */
+  inout[7] -= 1 & carry_mask;
+  inout[7] += carry << 25;
+}
+
+/* felem_sum sets out = in+in2.
+ *
+ * On entry, in[i]+in2[i] must not overflow a 32-bit word.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29 */
+static void felem_sum(felem out, const felem in, const felem in2) {
+  limb carry = 0;
+  unsigned i;
+
+  for (i = 0;; i++) {
+    out[i] = in[i] + in2[i];
+    out[i] += carry;
+    carry = out[i] >> 29;
+    out[i] &= kBottom29Bits;
+
+    i++;
+    if (i == NLIMBS)
+      break;
+
+    out[i] = in[i] + in2[i];
+    out[i] += carry;
+    carry = out[i] >> 28;
+    out[i] &= kBottom28Bits;
+  }
+
+  felem_reduce_carry(out, carry);
+}
+
+#define two31m3 (((limb)1) << 31) - (((limb)1) << 3)
+#define two30m2 (((limb)1) << 30) - (((limb)1) << 2)
+#define two30p13m2 (((limb)1) << 30) + (((limb)1) << 13) - (((limb)1) << 2)
+#define two31m2 (((limb)1) << 31) - (((limb)1) << 2)
+#define two31p24m2 (((limb)1) << 31) + (((limb)1) << 24) - (((limb)1) << 2)
+#define two30m27m2 (((limb)1) << 30) - (((limb)1) << 27) - (((limb)1) << 2)
+
+/* zero31 is 0 mod p. */
+static const felem zero31 = { two31m3, two30m2, two31m2, two30p13m2, two31m2, two30m2, two31p24m2, two30m27m2, two31m2 };
+
+/* felem_diff sets out = in-in2.
+ *
+ * On entry: in[0,2,...] < 2**30, in[1,3,...] < 2**29 and
+ *           in2[0,2,...] < 2**30, in2[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_diff(felem out, const felem in, const felem in2) {
+  limb carry = 0;
+  unsigned i;
+
+   for (i = 0;; i++) {
+    out[i] = in[i] - in2[i];
+    out[i] += zero31[i];
+    out[i] += carry;
+    carry = out[i] >> 29;
+    out[i] &= kBottom29Bits;
+
+    i++;
+    if (i == NLIMBS)
+      break;
+
+    out[i] = in[i] - in2[i];
+    out[i] += zero31[i];
+    out[i] += carry;
+    carry = out[i] >> 28;
+    out[i] &= kBottom28Bits;
+  }
+
+  felem_reduce_carry(out, carry);
+}
+
+/* felem_reduce_degree sets out = tmp/R mod p where tmp contains 64-bit words
+ * with the same 29,28,... bit positions as an felem.
+ *
+ * The values in felems are in Montgomery form: x*R mod p where R = 2**257.
+ * Since we just multiplied two Montgomery values together, the result is
+ * x*y*R*R mod p. We wish to divide by R in order for the result also to be
+ * in Montgomery form.
+ *
+ * On entry: tmp[i] < 2**64
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29 */
+static void felem_reduce_degree(felem out, u64 tmp[17]) {
+   /* The following table may be helpful when reading this code:
+    *
+    * Limb number:   0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10...
+    * Width (bits):  29| 28| 29| 28| 29| 28| 29| 28| 29| 28| 29
+    * Start bit:     0 | 29| 57| 86|114|143|171|200|228|257|285
+    *   (odd phase): 0 | 28| 57| 85|114|142|171|199|228|256|285 */
+  limb tmp2[18], carry, x, xMask;
+  unsigned i;
+
+  /* tmp contains 64-bit words with the same 29,28,29-bit positions as an
+   * felem. So the top of an element of tmp might overlap with another
+   * element two positions down. The following loop eliminates this
+   * overlap. */
+  tmp2[0] = (limb)(tmp[0] & kBottom29Bits);
+
+  /* In the following we use "(limb) tmp[x]" and "(limb) (tmp[x]>>32)" to try
+   * and hint to the compiler that it can do a single-word shift by selecting
+   * the right register rather than doing a double-word shift and truncating
+   * afterwards. */
+  tmp2[1] = ((limb) tmp[0]) >> 29;
+  tmp2[1] |= (((limb)(tmp[0] >> 32)) << 3) & kBottom28Bits;
+  tmp2[1] += ((limb) tmp[1]) & kBottom28Bits;
+  carry = tmp2[1] >> 28;
+  tmp2[1] &= kBottom28Bits;
+
+  for (i = 2; i < 17; i++) {
+    tmp2[i] = ((limb)(tmp[i - 2] >> 32)) >> 25;
+    tmp2[i] += ((limb)(tmp[i - 1])) >> 28;
+    tmp2[i] += (((limb)(tmp[i - 1] >> 32)) << 4) & kBottom29Bits;
+    tmp2[i] += ((limb) tmp[i]) & kBottom29Bits;
+    tmp2[i] += carry;
+    carry = tmp2[i] >> 29;
+    tmp2[i] &= kBottom29Bits;
+
+    i++;
+    if (i == 17)
+      break;
+    tmp2[i] = ((limb)(tmp[i - 2] >> 32)) >> 25;
+    tmp2[i] += ((limb)(tmp[i - 1])) >> 29;
+    tmp2[i] += (((limb)(tmp[i - 1] >> 32)) << 3) & kBottom28Bits;
+    tmp2[i] += ((limb) tmp[i]) & kBottom28Bits;
+    tmp2[i] += carry;
+    carry = tmp2[i] >> 28;
+    tmp2[i] &= kBottom28Bits;
+  }
+
+  tmp2[17] = ((limb)(tmp[15] >> 32)) >> 25;
+  tmp2[17] += ((limb)(tmp[16])) >> 29;
+  tmp2[17] += (((limb)(tmp[16] >> 32)) << 3);
+  tmp2[17] += carry;
+
+  /* Montgomery elimination of terms.
+   *
+   * Since R is 2**257, we can divide by R with a bitwise shift if we can
+   * ensure that the right-most 257 bits are all zero. We can make that true by
+   * adding multiplies of p without affecting the value.
+   *
+   * So we eliminate limbs from right to left. Since the bottom 29 bits of p
+   * are all ones, then by adding tmp2[0]*p to tmp2 we'll make tmp2[0] == 0.
+   * We can do that for 8 further limbs and then right shift to eliminate the
+   * extra factor of R. */
+  for (i = 0;; i += 2) {
+    tmp2[i + 1] += tmp2[i] >> 29;
+    x = tmp2[i] & kBottom29Bits;
+    xMask = NON_ZERO_TO_ALL_ONES(x);
+    tmp2[i] = 0;
+
+    /* The bounds calculations for this loop are tricky. Each iteration of
+     * the loop eliminates two words by adding values to words to their
+     * right.
+     *
+     * The following table contains the amounts added to each word (as an
+     * offset from the value of i at the top of the loop). The amounts are
+     * accounted for from the first and second half of the loop separately
+     * and are written as, for example, 28 to mean a value <2**28.
+     *
+     * Word:                   3   4   5   6   7   8   9   10
+     * Added in top half:     28  11      29  21  29  28
+     *                                        28  29
+     *                                            29
+     * Added in bottom half:      29  10      28  21  28   28
+     *                                            29
+     *
+     * The value that is currently offset 7 will be offset 5 for the next
+     * iteration and then offset 3 for the iteration after that. Therefore
+     * the total value added will be the values added at 7, 5 and 3.
+     *
+     * The following table accumulates these values. The sums at the bottom
+     * are written as, for example, 29+28, to mean a value < 2**29+2**28.
+     *
+     * Word:                   3   4   5   6   7   8   9  10  11  12  13
+     *                        28  11  10  29  21  29  28  28  28  28  28
+     *                            29  28  11  28  29  28  29  28  29  28
+     *                                    29  28  21  21  29  21  29  21
+     *                                        10  29  28  21  28  21  28
+     *                                        28  29  28  29  28  29  28
+     *                                            11  10  29  10  29  10
+     *                                            29  28  11  28  11
+     *                                                    29      29
+     *                        --------------------------------------------
+     *                                                30+ 31+ 30+ 31+ 30+
+     *                                                28+ 29+ 28+ 29+ 21+
+     *                                                21+ 28+ 21+ 28+ 10
+     *                                                10  21+ 10  21+
+     *                                                    11      11
+     *
+     * So the greatest amount is added to tmp2[10] and tmp2[12]. If
+     * tmp2[10/12] has an initial value of <2**29, then the maximum value
+     * will be < 2**31 + 2**30 + 2**28 + 2**21 + 2**11, which is < 2**32,
+     * as required. */
+    tmp2[i + 3] += (x << 10) & kBottom28Bits;
+    tmp2[i + 4] += (x >> 18);
+
+    tmp2[i + 6] += (x << 21) & kBottom29Bits;
+    tmp2[i + 7] += x >> 8;
+
+    /* At position 200, which is the starting bit position for word 7, we
+     * have a factor of 0xf000000 = 2**28 - 2**24. */
+    tmp2[i + 7] += 0x10000000 & xMask;
+    /* Word 7 is 28 bits wide, so the 2**28 term exactly hits word 8. */
+    tmp2[i + 8] += (x - 1) & xMask;
+    tmp2[i + 7] -= (x << 24) & kBottom28Bits;
+    tmp2[i + 8] -= x >> 4;
+
+    tmp2[i + 8] += 0x20000000 & xMask;
+    tmp2[i + 8] -= x;
+    tmp2[i + 8] += (x << 28) & kBottom29Bits;
+    tmp2[i + 9] += ((x >> 1) - 1) & xMask;
+
+    if (i+1 == NLIMBS)
+      break;
+    tmp2[i + 2] += tmp2[i + 1] >> 28;
+    x = tmp2[i + 1] & kBottom28Bits;
+    xMask = NON_ZERO_TO_ALL_ONES(x);
+    tmp2[i + 1] = 0;
+
+    tmp2[i + 4] += (x << 11) & kBottom29Bits;
+    tmp2[i + 5] += (x >> 18);
+
+    tmp2[i + 7] += (x << 21) & kBottom28Bits;
+    tmp2[i + 8] += x >> 7;
+
+    /* At position 199, which is the starting bit of the 8th word when
+     * dealing with a context starting on an odd word, we have a factor of
+     * 0x1e000000 = 2**29 - 2**25. Since we have not updated i, the 8th
+     * word from i+1 is i+8. */
+    tmp2[i + 8] += 0x20000000 & xMask;
+    tmp2[i + 9] += (x - 1) & xMask;
+    tmp2[i + 8] -= (x << 25) & kBottom29Bits;
+    tmp2[i + 9] -= x >> 4;
+
+    tmp2[i + 9] += 0x10000000 & xMask;
+    tmp2[i + 9] -= x;
+    tmp2[i + 10] += (x - 1) & xMask;
+  }
+
+  /* We merge the right shift with a carry chain. The words above 2**257 have
+   * widths of 28,29,... which we need to correct when copying them down.  */
+  carry = 0;
+  for (i = 0; i < 8; i++) {
+    /* The maximum value of tmp2[i + 9] occurs on the first iteration and
+     * is < 2**30+2**29+2**28. Adding 2**29 (from tmp2[i + 10]) is
+     * therefore safe. */
+    out[i] = tmp2[i + 9];
+    out[i] += carry;
+    out[i] += (tmp2[i + 10] << 28) & kBottom29Bits;
+    carry = out[i] >> 29;
+    out[i] &= kBottom29Bits;
+
+    i++;
+    out[i] = tmp2[i + 9] >> 1;
+    out[i] += carry;
+    carry = out[i] >> 28;
+    out[i] &= kBottom28Bits;
+  }
+
+  out[8] = tmp2[17];
+  out[8] += carry;
+  carry = out[8] >> 29;
+  out[8] &= kBottom29Bits;
+
+  felem_reduce_carry(out, carry);
+}
+
+/* felem_square sets out=in*in.
+ *
+ * On entry: in[0,2,...] < 2**30, in[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_square(felem out, const felem in) {
+  u64 tmp[17];
+
+  tmp[0] = ((u64) in[0]) * in[0];
+  tmp[1] = ((u64) in[0]) * (in[1] << 1);
+  tmp[2] = ((u64) in[0]) * (in[2] << 1) +
+           ((u64) in[1]) * (in[1] << 1);
+  tmp[3] = ((u64) in[0]) * (in[3] << 1) +
+           ((u64) in[1]) * (in[2] << 1);
+  tmp[4] = ((u64) in[0]) * (in[4] << 1) +
+           ((u64) in[1]) * (in[3] << 2) + ((u64) in[2]) * in[2];
+  tmp[5] = ((u64) in[0]) * (in[5] << 1) + ((u64) in[1]) *
+           (in[4] << 1) + ((u64) in[2]) * (in[3] << 1);
+  tmp[6] = ((u64) in[0]) * (in[6] << 1) + ((u64) in[1]) *
+           (in[5] << 2) + ((u64) in[2]) * (in[4] << 1) +
+           ((u64) in[3]) * (in[3] << 1);
+  tmp[7] = ((u64) in[0]) * (in[7] << 1) + ((u64) in[1]) *
+           (in[6] << 1) + ((u64) in[2]) * (in[5] << 1) +
+           ((u64) in[3]) * (in[4] << 1);
+  /* tmp[8] has the greatest value of 2**61 + 2**60 + 2**61 + 2**60 + 2**60,
+   * which is < 2**64 as required. */
+  tmp[8] = ((u64) in[0]) * (in[8] << 1) + ((u64) in[1]) *
+           (in[7] << 2) + ((u64) in[2]) * (in[6] << 1) +
+           ((u64) in[3]) * (in[5] << 2) + ((u64) in[4]) * in[4];
+  tmp[9] = ((u64) in[1]) * (in[8] << 1) + ((u64) in[2]) *
+           (in[7] << 1) + ((u64) in[3]) * (in[6] << 1) +
+           ((u64) in[4]) * (in[5] << 1);
+  tmp[10] = ((u64) in[2]) * (in[8] << 1) + ((u64) in[3]) *
+            (in[7] << 2) + ((u64) in[4]) * (in[6] << 1) +
+            ((u64) in[5]) * (in[5] << 1);
+  tmp[11] = ((u64) in[3]) * (in[8] << 1) + ((u64) in[4]) *
+            (in[7] << 1) + ((u64) in[5]) * (in[6] << 1);
+  tmp[12] = ((u64) in[4]) * (in[8] << 1) +
+            ((u64) in[5]) * (in[7] << 2) + ((u64) in[6]) * in[6];
+  tmp[13] = ((u64) in[5]) * (in[8] << 1) +
+            ((u64) in[6]) * (in[7] << 1);
+  tmp[14] = ((u64) in[6]) * (in[8] << 1) +
+            ((u64) in[7]) * (in[7] << 1);
+  tmp[15] = ((u64) in[7]) * (in[8] << 1);
+  tmp[16] = ((u64) in[8]) * in[8];
+
+  felem_reduce_degree(out, tmp);
+}
+
+/* felem_mul sets out=in*in2.
+ *
+ * On entry: in[0,2,...] < 2**30, in[1,3,...] < 2**29 and
+ *           in2[0,2,...] < 2**30, in2[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_mul(felem out, const felem in, const felem in2) {
+  u64 tmp[17];
+
+  tmp[0] = ((u64) in[0]) * in2[0];
+  tmp[1] = ((u64) in[0]) * (in2[1] << 0) +
+           ((u64) in[1]) * (in2[0] << 0);
+  tmp[2] = ((u64) in[0]) * (in2[2] << 0) + ((u64) in[1]) *
+           (in2[1] << 1) + ((u64) in[2]) * (in2[0] << 0);
+  tmp[3] = ((u64) in[0]) * (in2[3] << 0) + ((u64) in[1]) *
+           (in2[2] << 0) + ((u64) in[2]) * (in2[1] << 0) +
+           ((u64) in[3]) * (in2[0] << 0);
+  tmp[4] = ((u64) in[0]) * (in2[4] << 0) + ((u64) in[1]) *
+           (in2[3] << 1) + ((u64) in[2]) * (in2[2] << 0) +
+           ((u64) in[3]) * (in2[1] << 1) +
+           ((u64) in[4]) * (in2[0] << 0);
+  tmp[5] = ((u64) in[0]) * (in2[5] << 0) + ((u64) in[1]) *
+           (in2[4] << 0) + ((u64) in[2]) * (in2[3] << 0) +
+           ((u64) in[3]) * (in2[2] << 0) + ((u64) in[4]) *
+           (in2[1] << 0) + ((u64) in[5]) * (in2[0] << 0);
+  tmp[6] = ((u64) in[0]) * (in2[6] << 0) + ((u64) in[1]) *
+           (in2[5] << 1) + ((u64) in[2]) * (in2[4] << 0) +
+           ((u64) in[3]) * (in2[3] << 1) + ((u64) in[4]) *
+           (in2[2] << 0) + ((u64) in[5]) * (in2[1] << 1) +
+           ((u64) in[6]) * (in2[0] << 0);
+  tmp[7] = ((u64) in[0]) * (in2[7] << 0) + ((u64) in[1]) *
+           (in2[6] << 0) + ((u64) in[2]) * (in2[5] << 0) +
+           ((u64) in[3]) * (in2[4] << 0) + ((u64) in[4]) *
+           (in2[3] << 0) + ((u64) in[5]) * (in2[2] << 0) +
+           ((u64) in[6]) * (in2[1] << 0) +
+           ((u64) in[7]) * (in2[0] << 0);
+  /* tmp[8] has the greatest value but doesn't overflow. See logic in
+   * felem_square. */
+  tmp[8] = ((u64) in[0]) * (in2[8] << 0) + ((u64) in[1]) *
+           (in2[7] << 1) + ((u64) in[2]) * (in2[6] << 0) +
+           ((u64) in[3]) * (in2[5] << 1) + ((u64) in[4]) *
+           (in2[4] << 0) + ((u64) in[5]) * (in2[3] << 1) +
+           ((u64) in[6]) * (in2[2] << 0) + ((u64) in[7]) *
+           (in2[1] << 1) + ((u64) in[8]) * (in2[0] << 0);
+  tmp[9] = ((u64) in[1]) * (in2[8] << 0) + ((u64) in[2]) *
+           (in2[7] << 0) + ((u64) in[3]) * (in2[6] << 0) +
+           ((u64) in[4]) * (in2[5] << 0) + ((u64) in[5]) *
+           (in2[4] << 0) + ((u64) in[6]) * (in2[3] << 0) +
+           ((u64) in[7]) * (in2[2] << 0) +
+           ((u64) in[8]) * (in2[1] << 0);
+  tmp[10] = ((u64) in[2]) * (in2[8] << 0) + ((u64) in[3]) *
+            (in2[7] << 1) + ((u64) in[4]) * (in2[6] << 0) +
+            ((u64) in[5]) * (in2[5] << 1) + ((u64) in[6]) *
+            (in2[4] << 0) + ((u64) in[7]) * (in2[3] << 1) +
+            ((u64) in[8]) * (in2[2] << 0);
+  tmp[11] = ((u64) in[3]) * (in2[8] << 0) + ((u64) in[4]) *
+            (in2[7] << 0) + ((u64) in[5]) * (in2[6] << 0) +
+            ((u64) in[6]) * (in2[5] << 0) + ((u64) in[7]) *
+            (in2[4] << 0) + ((u64) in[8]) * (in2[3] << 0);
+  tmp[12] = ((u64) in[4]) * (in2[8] << 0) + ((u64) in[5]) *
+            (in2[7] << 1) + ((u64) in[6]) * (in2[6] << 0) +
+            ((u64) in[7]) * (in2[5] << 1) +
+            ((u64) in[8]) * (in2[4] << 0);
+  tmp[13] = ((u64) in[5]) * (in2[8] << 0) + ((u64) in[6]) *
+            (in2[7] << 0) + ((u64) in[7]) * (in2[6] << 0) +
+            ((u64) in[8]) * (in2[5] << 0);
+  tmp[14] = ((u64) in[6]) * (in2[8] << 0) + ((u64) in[7]) *
+            (in2[7] << 1) + ((u64) in[8]) * (in2[6] << 0);
+  tmp[15] = ((u64) in[7]) * (in2[8] << 0) +
+            ((u64) in[8]) * (in2[7] << 0);
+  tmp[16] = ((u64) in[8]) * (in2[8] << 0);
+
+  felem_reduce_degree(out, tmp);
+}
+
+static void felem_assign(felem out, const felem in) {
+  memcpy(out, in, sizeof(felem));
+}
+
+/* felem_inv calculates |out| = |in|^{-1}
+ *
+ * Based on Fermat's Little Theorem:
+ *   a^p = a (mod p)
+ *   a^{p-1} = 1 (mod p)
+ *   a^{p-2} = a^{-1} (mod p)
+ */
+static void felem_inv(felem out, const felem in) {
+  felem ftmp, ftmp2;
+  /* each e_I will hold |in|^{2^I - 1} */
+  felem e2, e4, e8, e16, e32, e64;
+  unsigned i;
+
+  felem_square(ftmp, in); /* 2^1 */
+  felem_mul(ftmp, in, ftmp); /* 2^2 - 2^0 */
+  felem_assign(e2, ftmp);
+  felem_square(ftmp, ftmp); /* 2^3 - 2^1 */
+  felem_square(ftmp, ftmp); /* 2^4 - 2^2 */
+  felem_mul(ftmp, ftmp, e2); /* 2^4 - 2^0 */
+  felem_assign(e4, ftmp);
+  felem_square(ftmp, ftmp); /* 2^5 - 2^1 */
+  felem_square(ftmp, ftmp); /* 2^6 - 2^2 */
+  felem_square(ftmp, ftmp); /* 2^7 - 2^3 */
+  felem_square(ftmp, ftmp); /* 2^8 - 2^4 */
+  felem_mul(ftmp, ftmp, e4); /* 2^8 - 2^0 */
+  felem_assign(e8, ftmp);
+  for (i = 0; i < 8; i++) {
+    felem_square(ftmp, ftmp);
+  } /* 2^16 - 2^8 */
+  felem_mul(ftmp, ftmp, e8); /* 2^16 - 2^0 */
+  felem_assign(e16, ftmp);
+  for (i = 0; i < 16; i++) {
+    felem_square(ftmp, ftmp);
+  } /* 2^32 - 2^16 */
+  felem_mul(ftmp, ftmp, e16); /* 2^32 - 2^0 */
+  felem_assign(e32, ftmp);
+  for (i = 0; i < 32; i++) {
+    felem_square(ftmp, ftmp);
+  } /* 2^64 - 2^32 */
+  felem_assign(e64, ftmp);
+  felem_mul(ftmp, ftmp, in); /* 2^64 - 2^32 + 2^0 */
+  for (i = 0; i < 192; i++) {
+    felem_square(ftmp, ftmp);
+  } /* 2^256 - 2^224 + 2^192 */
+
+  felem_mul(ftmp2, e64, e32); /* 2^64 - 2^0 */
+  for (i = 0; i < 16; i++) {
+    felem_square(ftmp2, ftmp2);
+  } /* 2^80 - 2^16 */
+  felem_mul(ftmp2, ftmp2, e16); /* 2^80 - 2^0 */
+  for (i = 0; i < 8; i++) {
+    felem_square(ftmp2, ftmp2);
+  } /* 2^88 - 2^8 */
+  felem_mul(ftmp2, ftmp2, e8); /* 2^88 - 2^0 */
+  for (i = 0; i < 4; i++) {
+    felem_square(ftmp2, ftmp2);
+  } /* 2^92 - 2^4 */
+  felem_mul(ftmp2, ftmp2, e4); /* 2^92 - 2^0 */
+  felem_square(ftmp2, ftmp2); /* 2^93 - 2^1 */
+  felem_square(ftmp2, ftmp2); /* 2^94 - 2^2 */
+  felem_mul(ftmp2, ftmp2, e2); /* 2^94 - 2^0 */
+  felem_square(ftmp2, ftmp2); /* 2^95 - 2^1 */
+  felem_square(ftmp2, ftmp2); /* 2^96 - 2^2 */
+  felem_mul(ftmp2, ftmp2, in); /* 2^96 - 3 */
+
+  felem_mul(out, ftmp2, ftmp); /* 2^256 - 2^224 + 2^192 + 2^96 - 3 */
+}
+
+/* felem_scalar_3 sets out=3*out.
+ *
+ * On entry: out[0,2,...] < 2**30, out[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_scalar_3(felem out) {
+  limb carry = 0;
+  unsigned i;
+
+  for (i = 0;; i++) {
+    out[i] *= 3;
+    out[i] += carry;
+    carry = out[i] >> 29;
+    out[i] &= kBottom29Bits;
+
+    i++;
+    if (i == NLIMBS)
+      break;
+
+    out[i] *= 3;
+    out[i] += carry;
+    carry = out[i] >> 28;
+    out[i] &= kBottom28Bits;
+  }
+
+  felem_reduce_carry(out, carry);
+}
+
+/* felem_scalar_4 sets out=4*out.
+ *
+ * On entry: out[0,2,...] < 2**30, out[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_scalar_4(felem out) {
+  limb carry = 0, next_carry;
+  unsigned i;
+
+  for (i = 0;; i++) {
+    next_carry = out[i] >> 27;
+    out[i] <<= 2;
+    out[i] &= kBottom29Bits;
+    out[i] += carry;
+    carry = next_carry + (out[i] >> 29);
+    out[i] &= kBottom29Bits;
+
+    i++;
+    if (i == NLIMBS)
+      break;
+
+    next_carry = out[i] >> 26;
+    out[i] <<= 2;
+    out[i] &= kBottom28Bits;
+    out[i] += carry;
+    carry = next_carry + (out[i] >> 28);
+    out[i] &= kBottom28Bits;
+  }
+
+  felem_reduce_carry(out, carry);
+}
+
+/* felem_scalar_8 sets out=8*out.
+ *
+ * On entry: out[0,2,...] < 2**30, out[1,3,...] < 2**29.
+ * On exit: out[0,2,...] < 2**30, out[1,3,...] < 2**29. */
+static void felem_scalar_8(felem out) {
+  limb carry = 0, next_carry;
+  unsigned i;
+
+  for (i = 0;; i++) {
+    next_carry = out[i] >> 26;
+    out[i] <<= 3;
+    out[i] &= kBottom29Bits;
+    out[i] += carry;
+    carry = next_carry + (out[i] >> 29);
+    out[i] &= kBottom29Bits;
+
+    i++;
+    if (i == NLIMBS)
+      break;
+
+    next_carry = out[i] >> 25;
+    out[i] <<= 3;
+    out[i] &= kBottom28Bits;
+    out[i] += carry;
+    carry = next_carry + (out[i] >> 28);
+    out[i] &= kBottom28Bits;
+  }
+
+  felem_reduce_carry(out, carry);
+}
+
+/* felem_is_zero_vartime returns 1 iff |in| == 0. It takes a variable amount of
+ * time depending on the value of |in|. */
+static char felem_is_zero_vartime(const felem in) {
+  limb carry;
+  int i;
+  limb tmp[NLIMBS];
+
+  felem_assign(tmp, in);
+
+  /* First, reduce tmp to a minimal form. */
+  do {
+    carry = 0;
+    for (i = 0;; i++) {
+      tmp[i] += carry;
+      carry = tmp[i] >> 29;
+      tmp[i] &= kBottom29Bits;
+
+      i++;
+      if (i == NLIMBS)
+        break;
+
+      tmp[i] += carry;
+      carry = tmp[i] >> 28;
+      tmp[i] &= kBottom28Bits;
+    }
+
+    felem_reduce_carry(tmp, carry);
+  } while (carry);
+
+  /* tmp < 2**257, so the only possible zero values are 0, p and 2p. */
+  return memcmp(tmp, kZero, sizeof(tmp)) == 0 ||
+         memcmp(tmp, kP, sizeof(tmp)) == 0 ||
+         memcmp(tmp, k2P, sizeof(tmp)) == 0;
+}
+
+
+/* Group operations:
+ *
+ * Elements of the elliptic curve group are represented in Jacobian
+ * coordinates: (x, y, z). An affine point (x', y') is x'=x/z**2, y'=y/z**3 in
+ * Jacobian form. */
+
+/* point_double sets {x_out,y_out,z_out} = 2*{x,y,z}.
+ *
+ * See http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l */
+static void point_double(felem x_out, felem y_out, felem z_out, const felem x,
+                         const felem y, const felem z) {
+  felem delta, gamma, alpha, beta, tmp, tmp2;
+
+  felem_square(delta, z);
+  felem_square(gamma, y);
+  felem_mul(beta, x, gamma);
+
+  felem_sum(tmp, x, delta);
+  felem_diff(tmp2, x, delta);
+  felem_mul(alpha, tmp, tmp2);
+  felem_scalar_3(alpha);
+
+  felem_sum(tmp, y, z);
+  felem_square(tmp, tmp);
+  felem_diff(tmp, tmp, gamma);
+  felem_diff(z_out, tmp, delta);
+
+  felem_scalar_4(beta);
+  felem_square(x_out, alpha);
+  felem_diff(x_out, x_out, beta);
+  felem_diff(x_out, x_out, beta);
+
+  felem_diff(tmp, beta, x_out);
+  felem_mul(tmp, alpha, tmp);
+  felem_square(tmp2, gamma);
+  felem_scalar_8(tmp2);
+  felem_diff(y_out, tmp, tmp2);
+}
+
+/* point_add_mixed sets {x_out,y_out,z_out} = {x1,y1,z1} + {x2,y2,1}.
+ * (i.e. the second point is affine.)
+ *
+ * See http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
+ *
+ * Note that this function does not handle P+P, infinity+P nor P+infinity
+ * correctly. */
+static void point_add_mixed(felem x_out, felem y_out, felem z_out,
+                            const felem x1, const felem y1, const felem z1,
+                            const felem x2, const felem y2) {
+  felem z1z1, z1z1z1, s2, u2, h, i, j, r, rr, v, tmp;
+
+  felem_square(z1z1, z1);
+  felem_sum(tmp, z1, z1);
+
+  felem_mul(u2, x2, z1z1);
+  felem_mul(z1z1z1, z1, z1z1);
+  felem_mul(s2, y2, z1z1z1);
+  felem_diff(h, u2, x1);
+  felem_sum(i, h, h);
+  felem_square(i, i);
+  felem_mul(j, h, i);
+  felem_diff(r, s2, y1);
+  felem_sum(r, r, r);
+  felem_mul(v, x1, i);
+
+  felem_mul(z_out, tmp, h);
+  felem_square(rr, r);
+  felem_diff(x_out, rr, j);
+  felem_diff(x_out, x_out, v);
+  felem_diff(x_out, x_out, v);
+
+  felem_diff(tmp, v, x_out);
+  felem_mul(y_out, tmp, r);
+  felem_mul(tmp, y1, j);
+  felem_diff(y_out, y_out, tmp);
+  felem_diff(y_out, y_out, tmp);
+}
+
+/* point_add sets {x_out,y_out,z_out} = {x1,y1,z1} + {x2,y2,z2}.
+ *
+ * See http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
+ *
+ * Note that this function does not handle P+P, infinity+P nor P+infinity
+ * correctly. */
+static void point_add(felem x_out, felem y_out, felem z_out, const felem x1,
+                      const felem y1, const felem z1, const felem x2,
+                      const felem y2, const felem z2) {
+  felem z1z1, z1z1z1, z2z2, z2z2z2, s1, s2, u1, u2, h, i, j, r, rr, v, tmp;
+
+  felem_square(z1z1, z1);
+  felem_square(z2z2, z2);
+  felem_mul(u1, x1, z2z2);
+
+  felem_sum(tmp, z1, z2);
+  felem_square(tmp, tmp);
+  felem_diff(tmp, tmp, z1z1);
+  felem_diff(tmp, tmp, z2z2);
+
+  felem_mul(z2z2z2, z2, z2z2);
+  felem_mul(s1, y1, z2z2z2);
+
+  felem_mul(u2, x2, z1z1);
+  felem_mul(z1z1z1, z1, z1z1);
+  felem_mul(s2, y2, z1z1z1);
+  felem_diff(h, u2, u1);
+  felem_sum(i, h, h);
+  felem_square(i, i);
+  felem_mul(j, h, i);
+  felem_diff(r, s2, s1);
+  felem_sum(r, r, r);
+  felem_mul(v, u1, i);
+
+  felem_mul(z_out, tmp, h);
+  felem_square(rr, r);
+  felem_diff(x_out, rr, j);
+  felem_diff(x_out, x_out, v);
+  felem_diff(x_out, x_out, v);
+
+  felem_diff(tmp, v, x_out);
+  felem_mul(y_out, tmp, r);
+  felem_mul(tmp, s1, j);
+  felem_diff(y_out, y_out, tmp);
+  felem_diff(y_out, y_out, tmp);
+}
+
+/* point_add_or_double_vartime sets {x_out,y_out,z_out} = {x1,y1,z1} +
+ *                                                        {x2,y2,z2}.
+ *
+ * See http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl
+ *
+ * This function handles the case where {x1,y1,z1}={x2,y2,z2}. */
+static void point_add_or_double_vartime(
+    felem x_out, felem y_out, felem z_out, const felem x1, const felem y1,
+    const felem z1, const felem x2, const felem y2, const felem z2) {
+  felem z1z1, z1z1z1, z2z2, z2z2z2, s1, s2, u1, u2, h, i, j, r, rr, v, tmp;
+  char x_equal, y_equal;
+
+  felem_square(z1z1, z1);
+  felem_square(z2z2, z2);
+  felem_mul(u1, x1, z2z2);
+
+  felem_sum(tmp, z1, z2);
+  felem_square(tmp, tmp);
+  felem_diff(tmp, tmp, z1z1);
+  felem_diff(tmp, tmp, z2z2);
+
+  felem_mul(z2z2z2, z2, z2z2);
+  felem_mul(s1, y1, z2z2z2);
+
+  felem_mul(u2, x2, z1z1);
+  felem_mul(z1z1z1, z1, z1z1);
+  felem_mul(s2, y2, z1z1z1);
+  felem_diff(h, u2, u1);
+  x_equal = felem_is_zero_vartime(h);
+  felem_sum(i, h, h);
+  felem_square(i, i);
+  felem_mul(j, h, i);
+  felem_diff(r, s2, s1);
+  y_equal = felem_is_zero_vartime(r);
+  if (x_equal && y_equal) {
+    point_double(x_out, y_out, z_out, x1, y1, z1);
+    return;
+  }
+  felem_sum(r, r, r);
+  felem_mul(v, u1, i);
+
+  felem_mul(z_out, tmp, h);
+  felem_square(rr, r);
+  felem_diff(x_out, rr, j);
+  felem_diff(x_out, x_out, v);
+  felem_diff(x_out, x_out, v);
+
+  felem_diff(tmp, v, x_out);
+  felem_mul(y_out, tmp, r);
+  felem_mul(tmp, s1, j);
+  felem_diff(y_out, y_out, tmp);
+  felem_diff(y_out, y_out, tmp);
+}
+
+/* copy_conditional sets out=in if mask = 0xffffffff in constant time.
+ *
+ * On entry: mask is either 0 or 0xffffffff. */
+static void copy_conditional(felem out, const felem in, limb mask) {
+  int i;
+
+  for (i = 0; i < NLIMBS; i++) {
+    const limb tmp = mask & (in[i] ^ out[i]);
+    out[i] ^= tmp;
+  }
+}
+
+/* select_affine_point sets {out_x,out_y} to the index'th entry of table.
+ * On entry: index < 16, table[0] must be zero. */
+static void select_affine_point(felem out_x, felem out_y, const limb* table,
+                                limb index) {
+  limb i, j;
+
+  memset(out_x, 0, sizeof(felem));
+  memset(out_y, 0, sizeof(felem));
+
+  for (i = 1; i < 16; i++) {
+    limb mask = i ^ index;
+    mask |= mask >> 2;
+    mask |= mask >> 1;
+    mask &= 1;
+    mask--;
+    for (j = 0; j < NLIMBS; j++, table++) {
+      out_x[j] |= *table & mask;
+    }
+    for (j = 0; j < NLIMBS; j++, table++) {
+      out_y[j] |= *table & mask;
+    }
+  }
+}
+
+/* select_jacobian_point sets {out_x,out_y,out_z} to the index'th entry of
+ * table. On entry: index < 16, table[0] must be zero. */
+static void select_jacobian_point(felem out_x, felem out_y, felem out_z,
+                                  const limb* table, limb index) {
+  limb i, j;
+
+  memset(out_x, 0, sizeof(felem));
+  memset(out_y, 0, sizeof(felem));
+  memset(out_z, 0, sizeof(felem));
+
+  /* The implicit value at index 0 is all zero. We don't need to perform that
+   * iteration of the loop because we already set out_* to zero. */
+  table += 3 * NLIMBS;
+
+  // Hit all entries to obscure cache profiling.
+  for (i = 1; i < 16; i++) {
+    limb mask = i ^ index;
+    mask |= mask >> 2;
+    mask |= mask >> 1;
+    mask &= 1;
+    mask--;
+    for (j = 0; j < NLIMBS; j++, table++) {
+      out_x[j] |= *table & mask;
+    }
+    for (j = 0; j < NLIMBS; j++, table++) {
+      out_y[j] |= *table & mask;
+    }
+    for (j = 0; j < NLIMBS; j++, table++) {
+      out_z[j] |= *table & mask;
+    }
+  }
+}
+
+/* scalar_base_mult sets {nx,ny,nz} = scalar*G where scalar is a little-endian
+ * number. Note that the value of scalar must be less than the order of the
+ * group. */
+static void scalar_base_mult(felem nx, felem ny, felem nz,
+                             const p256_int* scalar) {
+  int i, j;
+  limb n_is_infinity_mask = -1, p_is_noninfinite_mask, mask;
+  u32 table_offset;
+
+  felem px, py;
+  felem tx, ty, tz;
+
+  memset(nx, 0, sizeof(felem));
+  memset(ny, 0, sizeof(felem));
+  memset(nz, 0, sizeof(felem));
+
+  /* The loop adds bits at positions 0, 64, 128 and 192, followed by
+   * positions 32,96,160 and 224 and does this 32 times. */
+  for (i = 0; i < 32; i++) {
+    if (i) {
+      point_double(nx, ny, nz, nx, ny, nz);
+    }
+    table_offset = 0;
+    for (j = 0; j <= 32; j += 32) {
+      char bit0 = p256_get_bit(scalar, 31 - i + j);
+      char bit1 = p256_get_bit(scalar, 95 - i + j);
+      char bit2 = p256_get_bit(scalar, 159 - i + j);
+      char bit3 = p256_get_bit(scalar, 223 - i + j);
+      limb index = bit0 | (bit1 << 1) | (bit2 << 2) | (bit3 << 3);
+
+      select_affine_point(px, py, kPrecomputed + table_offset, index);
+      table_offset += 30 * NLIMBS;
+
+      /* Since scalar is less than the order of the group, we know that
+       * {nx,ny,nz} != {px,py,1}, unless both are zero, which we handle
+       * below. */
+      point_add_mixed(tx, ty, tz, nx, ny, nz, px, py);
+      /* The result of point_add_mixed is incorrect if {nx,ny,nz} is zero
+       * (a.k.a.  the point at infinity). We handle that situation by
+       * copying the point from the table. */
+      copy_conditional(nx, px, n_is_infinity_mask);
+      copy_conditional(ny, py, n_is_infinity_mask);
+      copy_conditional(nz, kOne, n_is_infinity_mask);
+
+      /* Equally, the result is also wrong if the point from the table is
+       * zero, which happens when the index is zero. We handle that by
+       * only copying from {tx,ty,tz} to {nx,ny,nz} if index != 0. */
+      p_is_noninfinite_mask = NON_ZERO_TO_ALL_ONES(index);
+      mask = p_is_noninfinite_mask & ~n_is_infinity_mask;
+      copy_conditional(nx, tx, mask);
+      copy_conditional(ny, ty, mask);
+      copy_conditional(nz, tz, mask);
+      /* If p was not zero, then n is now non-zero. */
+      n_is_infinity_mask &= ~p_is_noninfinite_mask;
+    }
+  }
+}
+
+/* point_to_affine converts a Jacobian point to an affine point. If the input
+ * is the point at infinity then it returns (0, 0) in constant time. */
+static void point_to_affine(felem x_out, felem y_out, const felem nx,
+                            const felem ny, const felem nz) {
+  felem z_inv, z_inv_sq;
+  felem_inv(z_inv, nz);
+  felem_square(z_inv_sq, z_inv);
+  felem_mul(x_out, nx, z_inv_sq);
+  felem_mul(z_inv, z_inv, z_inv_sq);
+  felem_mul(y_out, ny, z_inv);
+}
+
+/* scalar_base_mult sets {nx,ny,nz} = scalar*{x,y}. */
+static void scalar_mult(felem nx, felem ny, felem nz, const felem x,
+                        const felem y, const p256_int* scalar) {
+  int i;
+  felem px, py, pz, tx, ty, tz;
+  felem precomp[16][3];
+  limb n_is_infinity_mask, index, p_is_noninfinite_mask, mask;
+
+  /* We precompute 0,1,2,... times {x,y}. */
+  memset(precomp, 0, sizeof(felem) * 3);
+  memcpy(&precomp[1][0], x, sizeof(felem));
+  memcpy(&precomp[1][1], y, sizeof(felem));
+  memcpy(&precomp[1][2], kOne, sizeof(felem));
+
+  for (i = 2; i < 16; i += 2) {
+    point_double(precomp[i][0], precomp[i][1], precomp[i][2],
+                 precomp[i / 2][0], precomp[i / 2][1], precomp[i / 2][2]);
+
+    point_add_mixed(precomp[i + 1][0], precomp[i + 1][1], precomp[i + 1][2],
+                    precomp[i][0], precomp[i][1], precomp[i][2], x, y);
+  }
+
+  memset(nx, 0, sizeof(felem));
+  memset(ny, 0, sizeof(felem));
+  memset(nz, 0, sizeof(felem));
+  n_is_infinity_mask = -1;
+
+  /* We add in a window of four bits each iteration and do this 64 times. */
+  for (i = 0; i < 256; i += 4) {
+    if (i) {
+      point_double(nx, ny, nz, nx, ny, nz);
+      point_double(nx, ny, nz, nx, ny, nz);
+      point_double(nx, ny, nz, nx, ny, nz);
+      point_double(nx, ny, nz, nx, ny, nz);
+    }
+
+    index = (p256_get_bit(scalar, 255 - i - 0) << 3) |
+            (p256_get_bit(scalar, 255 - i - 1) << 2) |
+            (p256_get_bit(scalar, 255 - i - 2) << 1) |
+            p256_get_bit(scalar, 255 - i - 3);
+
+    /* See the comments in scalar_base_mult about handling infinities. */
+    select_jacobian_point(px, py, pz, precomp[0][0], index);
+    point_add(tx, ty, tz, nx, ny, nz, px, py, pz);
+    copy_conditional(nx, px, n_is_infinity_mask);
+    copy_conditional(ny, py, n_is_infinity_mask);
+    copy_conditional(nz, pz, n_is_infinity_mask);
+
+    p_is_noninfinite_mask = NON_ZERO_TO_ALL_ONES(index);
+    mask = p_is_noninfinite_mask & ~n_is_infinity_mask;
+
+    copy_conditional(nx, tx, mask);
+    copy_conditional(ny, ty, mask);
+    copy_conditional(nz, tz, mask);
+    n_is_infinity_mask &= ~p_is_noninfinite_mask;
+  }
+}
+
+#define kRDigits {2, 0, 0, 0xfffffffe, 0xffffffff, 0xffffffff, 0xfffffffd, 1} // 2^257 mod p256.p
+
+#define kRInvDigits {0x80000000, 1, 0xffffffff, 0, 0x80000001, 0xfffffffe, 1, 0x7fffffff}  // 1 / 2^257 mod p256.p
+
+static const p256_int kR = { kRDigits };
+static const p256_int kRInv = { kRInvDigits };
+
+/* to_montgomery sets out = R*in. */
+static void to_montgomery(felem out, const p256_int* in) {
+  p256_int in_shifted;
+  int i;
+
+  p256_init(&in_shifted);
+  p256_modmul(&SECP256r1_p, in, 0, &kR, &in_shifted);
+
+  for (i = 0; i < NLIMBS; i++) {
+    if ((i & 1) == 0) {
+      out[i] = P256_DIGIT(&in_shifted, 0) & kBottom29Bits;
+      p256_shr(&in_shifted, 29, &in_shifted);
+    } else {
+      out[i] = P256_DIGIT(&in_shifted, 0) & kBottom28Bits;
+      p256_shr(&in_shifted, 28, &in_shifted);
+    }
+  }
+
+  p256_clear(&in_shifted);
+}
+
+/* from_montgomery sets out=in/R. */
+static void from_montgomery(p256_int* out, const felem in) {
+  p256_int result, tmp;
+  int i, top;
+
+  p256_init(&result);
+  p256_init(&tmp);
+
+  p256_add_d(&tmp, in[NLIMBS - 1], &result);
+  for (i = NLIMBS - 2; i >= 0; i--) {
+    if ((i & 1) == 0) {
+      top = p256_shl(&result, 29, &tmp);
+    } else {
+      top = p256_shl(&result, 28, &tmp);
+    }
+    top |= p256_add_d(&tmp, in[i], &result);
+  }
+
+  p256_modmul(&SECP256r1_p, &kRInv, top, &result, out);
+
+  p256_clear(&result);
+  p256_clear(&tmp);
+}
+
+/* p256_base_point_mul sets {out_x,out_y} = nG, where n is < the
+ * order of the group. */
+void p256_base_point_mul(const p256_int* n, p256_int* out_x, p256_int* out_y) {
+  felem x, y, z;
+
+  scalar_base_mult(x, y, z, n);
+
+  {
+    felem x_affine, y_affine;
+
+    point_to_affine(x_affine, y_affine, x, y, z);
+    from_montgomery(out_x, x_affine);
+    from_montgomery(out_y, y_affine);
+  }
+}
+
+/* p256_points_mul_vartime sets {out_x,out_y} = n1*G + n2*{in_x,in_y}, where
+ * n1 and n2 are < the order of the group.
+ *
+ * As indicated by the name, this function operates in variable time. This
+ * is safe because it's used for signature validation which doesn't deal
+ * with secrets. */
+void p256_points_mul_vartime(
+    const p256_int* n1, const p256_int* n2, const p256_int* in_x,
+    const p256_int* in_y, p256_int* out_x, p256_int* out_y) {
+  felem x1, y1, z1, x2, y2, z2, px, py;
+
+  /* If both scalars are zero, then the result is the point at infinity. */
+  if (p256_is_zero(n1) != 0 && p256_is_zero(n2) != 0) {
+    p256_clear(out_x);
+    p256_clear(out_y);
+    return;
+  }
+
+  to_montgomery(px, in_x);
+  to_montgomery(py, in_y);
+  scalar_base_mult(x1, y1, z1, n1);
+  scalar_mult(x2, y2, z2, px, py, n2);
+
+  if (p256_is_zero(n2) != 0) {
+    /* If n2 == 0, then {x2,y2,z2} is zero and the result is just
+         * {x1,y1,z1}. */
+  } else if (p256_is_zero(n1) != 0) {
+    /* If n1 == 0, then {x1,y1,z1} is zero and the result is just
+         * {x2,y2,z2}. */
+    memcpy(x1, x2, sizeof(x2));
+    memcpy(y1, y2, sizeof(y2));
+    memcpy(z1, z2, sizeof(z2));
+  } else {
+    /* This function handles the case where {x1,y1,z1} == {x2,y2,z2}. */
+    point_add_or_double_vartime(x1, y1, z1, x1, y1, z1, x2, y2, z2);
+  }
+
+  point_to_affine(px, py, x1, y1, z1);
+  from_montgomery(out_x, px);
+  from_montgomery(out_y, py);
+}
diff --git a/libmincrypt/p256_ecdsa.c b/libmincrypt/p256_ecdsa.c
new file mode 100644
index 0000000..f2264b0
--- /dev/null
+++ b/libmincrypt/p256_ecdsa.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of Google Inc. nor the names of its contributors may
+ *       be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "mincrypt/p256_ecdsa.h"
+#include "mincrypt/p256.h"
+
+int p256_ecdsa_verify(const p256_int* key_x, const p256_int* key_y,
+                      const p256_int* message,
+                      const p256_int* r, const p256_int* s) {
+  p256_int u, v;
+
+  // Check public key.
+  if (!p256_is_valid_point(key_x, key_y)) return 0;
+
+  // Check r and s are != 0 % n.
+  p256_mod(&SECP256r1_n, r, &u);
+  p256_mod(&SECP256r1_n, s, &v);
+  if (p256_is_zero(&u) || p256_is_zero(&v)) return 0;
+
+  p256_modinv_vartime(&SECP256r1_n, s, &v);
+  p256_modmul(&SECP256r1_n, message, 0, &v, &u);  // message / s % n
+  p256_modmul(&SECP256r1_n, r, 0, &v, &v);  // r / s % n
+
+  p256_points_mul_vartime(&u, &v,
+                          key_x, key_y,
+                          &u, &v);
+
+  p256_mod(&SECP256r1_n, &u, &u);  // (x coord % p) % n
+  return p256_cmp(r, &u) == 0;
+}
+
diff --git a/libmincrypt/rsa.c b/libmincrypt/rsa.c
new file mode 100644
index 0000000..9061b3a
--- /dev/null
+++ b/libmincrypt/rsa.c
@@ -0,0 +1,308 @@
+/* rsa.c
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+**     * Redistributions of source code must retain the above copyright
+**       notice, this list of conditions and the following disclaimer.
+**     * Redistributions in binary form must reproduce the above copyright
+**       notice, this list of conditions and the following disclaimer in the
+**       documentation and/or other materials provided with the distribution.
+**     * Neither the name of Google Inc. nor the names of its contributors may
+**       be used to endorse or promote products derived from this software
+**       without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+** EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "mincrypt/rsa.h"
+#include "mincrypt/sha.h"
+#include "mincrypt/sha256.h"
+
+// a[] -= mod
+static void subM(const RSAPublicKey* key,
+                 uint32_t* a) {
+    int64_t A = 0;
+    int i;
+    for (i = 0; i < key->len; ++i) {
+        A += (uint64_t)a[i] - key->n[i];
+        a[i] = (uint32_t)A;
+        A >>= 32;
+    }
+}
+
+// return a[] >= mod
+static int geM(const RSAPublicKey* key,
+               const uint32_t* a) {
+    int i;
+    for (i = key->len; i;) {
+        --i;
+        if (a[i] < key->n[i]) return 0;
+        if (a[i] > key->n[i]) return 1;
+    }
+    return 1;  // equal
+}
+
+// montgomery c[] += a * b[] / R % mod
+static void montMulAdd(const RSAPublicKey* key,
+                       uint32_t* c,
+                       const uint32_t a,
+                       const uint32_t* b) {
+    uint64_t A = (uint64_t)a * b[0] + c[0];
+    uint32_t d0 = (uint32_t)A * key->n0inv;
+    uint64_t B = (uint64_t)d0 * key->n[0] + (uint32_t)A;
+    int i;
+
+    for (i = 1; i < key->len; ++i) {
+        A = (A >> 32) + (uint64_t)a * b[i] + c[i];
+        B = (B >> 32) + (uint64_t)d0 * key->n[i] + (uint32_t)A;
+        c[i - 1] = (uint32_t)B;
+    }
+
+    A = (A >> 32) + (B >> 32);
+
+    c[i - 1] = (uint32_t)A;
+
+    if (A >> 32) {
+        subM(key, c);
+    }
+}
+
+// montgomery c[] = a[] * b[] / R % mod
+static void montMul(const RSAPublicKey* key,
+                    uint32_t* c,
+                    const uint32_t* a,
+                    const uint32_t* b) {
+    int i;
+    for (i = 0; i < key->len; ++i) {
+        c[i] = 0;
+    }
+    for (i = 0; i < key->len; ++i) {
+        montMulAdd(key, c, a[i], b);
+    }
+}
+
+// In-place public exponentiation.
+// Input and output big-endian byte array in inout.
+static void modpow(const RSAPublicKey* key,
+                   uint8_t* inout) {
+    uint32_t a[RSANUMWORDS];
+    uint32_t aR[RSANUMWORDS];
+    uint32_t aaR[RSANUMWORDS];
+    uint32_t* aaa = 0;
+    int i;
+
+    // Convert from big endian byte array to little endian word array.
+    for (i = 0; i < key->len; ++i) {
+        uint32_t tmp =
+            (inout[((key->len - 1 - i) * 4) + 0] << 24) |
+            (inout[((key->len - 1 - i) * 4) + 1] << 16) |
+            (inout[((key->len - 1 - i) * 4) + 2] << 8) |
+            (inout[((key->len - 1 - i) * 4) + 3] << 0);
+        a[i] = tmp;
+    }
+
+    if (key->exponent == 65537) {
+        aaa = aaR;  // Re-use location.
+        montMul(key, aR, a, key->rr);  // aR = a * RR / R mod M
+        for (i = 0; i < 16; i += 2) {
+            montMul(key, aaR, aR, aR);  // aaR = aR * aR / R mod M
+            montMul(key, aR, aaR, aaR);  // aR = aaR * aaR / R mod M
+        }
+        montMul(key, aaa, aR, a);  // aaa = aR * a / R mod M
+    } else if (key->exponent == 3) {
+        aaa = aR;  // Re-use location.
+        montMul(key, aR, a, key->rr);  /* aR = a * RR / R mod M   */
+        montMul(key, aaR, aR, aR);     /* aaR = aR * aR / R mod M */
+        montMul(key, aaa, aaR, a);     /* aaa = aaR * a / R mod M */
+    }
+
+    // Make sure aaa < mod; aaa is at most 1x mod too large.
+    if (geM(key, aaa)) {
+        subM(key, aaa);
+    }
+
+    // Convert to bigendian byte array
+    for (i = key->len - 1; i >= 0; --i) {
+        uint32_t tmp = aaa[i];
+        *inout++ = tmp >> 24;
+        *inout++ = tmp >> 16;
+        *inout++ = tmp >> 8;
+        *inout++ = tmp >> 0;
+    }
+}
+
+// Expected PKCS1.5 signature padding bytes, for a keytool RSA signature.
+// Has the 0-length optional parameter encoded in the ASN1 (as opposed to the
+// other flavor which omits the optional parameter entirely). This code does not
+// accept signatures without the optional parameter.
+
+/*
+static const uint8_t sha_padding[RSANUMBYTES] = {
+    0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x21, 0x30,
+    0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a,
+    0x05, 0x00, 0x04, 0x14,
+
+    // 20 bytes of hash go here.
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+};
+*/
+
+// SHA-1 of PKCS1.5 signature sha_padding for 2048 bit, as above.
+// At the location of the bytes of the hash all 00 are hashed.
+static const uint8_t kExpectedPadShaRsa2048[SHA_DIGEST_SIZE] = {
+    0xdc, 0xbd, 0xbe, 0x42, 0xd5, 0xf5, 0xa7, 0x2e,
+    0x6e, 0xfc, 0xf5, 0x5d, 0xaf, 0x9d, 0xea, 0x68,
+    0x7c, 0xfb, 0xf1, 0x67
+};
+
+/*
+static const uint8_t sha256_padding[RSANUMBYTES] = {
+    0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x31, 0x30,
+    0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
+    0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20,
+
+    // 32 bytes of hash go here.
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+};
+*/
+
+// SHA-256 of PKCS1.5 signature sha256_padding for 2048 bit, as above.
+// At the location of the bytes of the hash all 00 are hashed.
+static const uint8_t kExpectedPadSha256Rsa2048[SHA256_DIGEST_SIZE] = {
+    0xab, 0x28, 0x8d, 0x8a, 0xd7, 0xd9, 0x59, 0x92,
+    0xba, 0xcc, 0xf8, 0x67, 0x20, 0xe1, 0x15, 0x2e,
+    0x39, 0x8d, 0x80, 0x36, 0xd6, 0x6f, 0xf0, 0xfd,
+    0x90, 0xe8, 0x7d, 0x8b, 0xe1, 0x7c, 0x87, 0x59,
+};
+
+// Verify a 2048-bit RSA PKCS1.5 signature against an expected hash.
+// Both e=3 and e=65537 are supported.  hash_len may be
+// SHA_DIGEST_SIZE (== 20) to indicate a SHA-1 hash, or
+// SHA256_DIGEST_SIZE (== 32) to indicate a SHA-256 hash.  No other
+// values are supported.
+//
+// Returns 1 on successful verification, 0 on failure.
+int RSA_verify(const RSAPublicKey *key,
+               const uint8_t *signature,
+               const int len,
+               const uint8_t *hash,
+               const int hash_len) {
+    uint8_t buf[RSANUMBYTES];
+    int i;
+    const uint8_t* padding_hash;
+
+    if (key->len != RSANUMWORDS) {
+        return 0;  // Wrong key passed in.
+    }
+
+    if (len != sizeof(buf)) {
+        return 0;  // Wrong input length.
+    }
+
+    if (hash_len != SHA_DIGEST_SIZE &&
+        hash_len != SHA256_DIGEST_SIZE) {
+        return 0;  // Unsupported hash.
+    }
+
+    if (key->exponent != 3 && key->exponent != 65537) {
+        return 0;  // Unsupported exponent.
+    }
+
+    for (i = 0; i < len; ++i) {  // Copy input to local workspace.
+        buf[i] = signature[i];
+    }
+
+    modpow(key, buf);  // In-place exponentiation.
+
+    // Xor sha portion, so it all becomes 00 iff equal.
+    for (i = len - hash_len; i < len; ++i) {
+        buf[i] ^= *hash++;
+    }
+
+    // Hash resulting buf, in-place.
+    switch (hash_len) {
+        case SHA_DIGEST_SIZE:
+            padding_hash = kExpectedPadShaRsa2048;
+            SHA_hash(buf, len, buf);
+            break;
+        case SHA256_DIGEST_SIZE:
+            padding_hash = kExpectedPadSha256Rsa2048;
+            SHA256_hash(buf, len, buf);
+            break;
+        default:
+            return 0;
+    }
+
+    // Compare against expected hash value.
+    for (i = 0; i < hash_len; ++i) {
+        if (buf[i] != padding_hash[i]) {
+            return 0;
+        }
+    }
+
+    return 1;  // All checked out OK.
+}
diff --git a/libmincrypt/sha.c b/libmincrypt/sha.c
new file mode 100644
index 0000000..5bef32e
--- /dev/null
+++ b/libmincrypt/sha.c
@@ -0,0 +1,155 @@
+/* sha.c
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+**     * Redistributions of source code must retain the above copyright
+**       notice, this list of conditions and the following disclaimer.
+**     * Redistributions in binary form must reproduce the above copyright
+**       notice, this list of conditions and the following disclaimer in the
+**       documentation and/or other materials provided with the distribution.
+**     * Neither the name of Google Inc. nor the names of its contributors may
+**       be used to endorse or promote products derived from this software
+**       without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+** EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+// Optimized for minimal code size.
+
+#include "mincrypt/sha.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#define rol(bits, value) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+static void SHA1_Transform(SHA_CTX* ctx) {
+    uint32_t W[80];
+    uint32_t A, B, C, D, E;
+    uint8_t* p = ctx->buf;
+    int t;
+
+    for(t = 0; t < 16; ++t) {
+        uint32_t tmp =  *p++ << 24;
+        tmp |= *p++ << 16;
+        tmp |= *p++ << 8;
+        tmp |= *p++;
+        W[t] = tmp;
+    }
+
+    for(; t < 80; t++) {
+        W[t] = rol(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
+    }
+
+    A = ctx->state[0];
+    B = ctx->state[1];
+    C = ctx->state[2];
+    D = ctx->state[3];
+    E = ctx->state[4];
+
+    for(t = 0; t < 80; t++) {
+        uint32_t tmp = rol(5,A) + E + W[t];
+
+        if (t < 20)
+            tmp += (D^(B&(C^D))) + 0x5A827999;
+        else if ( t < 40)
+            tmp += (B^C^D) + 0x6ED9EBA1;
+        else if ( t < 60)
+            tmp += ((B&C)|(D&(B|C))) + 0x8F1BBCDC;
+        else
+            tmp += (B^C^D) + 0xCA62C1D6;
+
+        E = D;
+        D = C;
+        C = rol(30,B);
+        B = A;
+        A = tmp;
+    }
+
+    ctx->state[0] += A;
+    ctx->state[1] += B;
+    ctx->state[2] += C;
+    ctx->state[3] += D;
+    ctx->state[4] += E;
+}
+
+static const HASH_VTAB SHA_VTAB = {
+    SHA_init,
+    SHA_update,
+    SHA_final,
+    SHA_hash,
+    SHA_DIGEST_SIZE
+};
+
+void SHA_init(SHA_CTX* ctx) {
+    ctx->f = &SHA_VTAB;
+    ctx->state[0] = 0x67452301;
+    ctx->state[1] = 0xEFCDAB89;
+    ctx->state[2] = 0x98BADCFE;
+    ctx->state[3] = 0x10325476;
+    ctx->state[4] = 0xC3D2E1F0;
+    ctx->count = 0;
+}
+
+
+void SHA_update(SHA_CTX* ctx, const void* data, int len) {
+    int i = (int) (ctx->count & 63);
+    const uint8_t* p = (const uint8_t*)data;
+
+    ctx->count += len;
+
+    while (len--) {
+        ctx->buf[i++] = *p++;
+        if (i == 64) {
+            SHA1_Transform(ctx);
+            i = 0;
+        }
+    }
+}
+
+
+const uint8_t* SHA_final(SHA_CTX* ctx) {
+    uint8_t *p = ctx->buf;
+    uint64_t cnt = ctx->count * 8;
+    int i;
+
+    SHA_update(ctx, (uint8_t*)"\x80", 1);
+    while ((ctx->count & 63) != 56) {
+        SHA_update(ctx, (uint8_t*)"\0", 1);
+    }
+    for (i = 0; i < 8; ++i) {
+        uint8_t tmp = (uint8_t) (cnt >> ((7 - i) * 8));
+        SHA_update(ctx, &tmp, 1);
+    }
+
+    for (i = 0; i < 5; i++) {
+        uint32_t tmp = ctx->state[i];
+        *p++ = tmp >> 24;
+        *p++ = tmp >> 16;
+        *p++ = tmp >> 8;
+        *p++ = tmp >> 0;
+    }
+
+    return ctx->buf;
+}
+
+/* Convenience function */
+const uint8_t* SHA_hash(const void* data, int len, uint8_t* digest) {
+    SHA_CTX ctx;
+    SHA_init(&ctx);
+    SHA_update(&ctx, data, len);
+    memcpy(digest, SHA_final(&ctx), SHA_DIGEST_SIZE);
+    return digest;
+}
diff --git a/libmincrypt/sha256.c b/libmincrypt/sha256.c
new file mode 100644
index 0000000..eb6e308
--- /dev/null
+++ b/libmincrypt/sha256.c
@@ -0,0 +1,184 @@
+/* sha256.c
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+**     * Redistributions of source code must retain the above copyright
+**       notice, this list of conditions and the following disclaimer.
+**     * Redistributions in binary form must reproduce the above copyright
+**       notice, this list of conditions and the following disclaimer in the
+**       documentation and/or other materials provided with the distribution.
+**     * Neither the name of Google Inc. nor the names of its contributors may
+**       be used to endorse or promote products derived from this software
+**       without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY Google Inc. ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+** EVENT SHALL Google Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+// Optimized for minimal code size.
+
+#include "mincrypt/sha256.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#define ror(value, bits) (((value) >> (bits)) | ((value) << (32 - (bits))))
+#define shr(value, bits) ((value) >> (bits))
+
+static const uint32_t K[64] = {
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 };
+
+static void SHA256_Transform(SHA256_CTX* ctx) {
+    uint32_t W[64];
+    uint32_t A, B, C, D, E, F, G, H;
+    uint8_t* p = ctx->buf;
+    int t;
+
+    for(t = 0; t < 16; ++t) {
+        uint32_t tmp =  *p++ << 24;
+        tmp |= *p++ << 16;
+        tmp |= *p++ << 8;
+        tmp |= *p++;
+        W[t] = tmp;
+    }
+
+    for(; t < 64; t++) {
+        uint32_t s0 = ror(W[t-15], 7) ^ ror(W[t-15], 18) ^ shr(W[t-15], 3);
+        uint32_t s1 = ror(W[t-2], 17) ^ ror(W[t-2], 19) ^ shr(W[t-2], 10);
+        W[t] = W[t-16] + s0 + W[t-7] + s1;
+    }
+
+    A = ctx->state[0];
+    B = ctx->state[1];
+    C = ctx->state[2];
+    D = ctx->state[3];
+    E = ctx->state[4];
+    F = ctx->state[5];
+    G = ctx->state[6];
+    H = ctx->state[7];
+
+    for(t = 0; t < 64; t++) {
+        uint32_t s0 = ror(A, 2) ^ ror(A, 13) ^ ror(A, 22);
+        uint32_t maj = (A & B) ^ (A & C) ^ (B & C);
+        uint32_t t2 = s0 + maj;
+        uint32_t s1 = ror(E, 6) ^ ror(E, 11) ^ ror(E, 25);
+        uint32_t ch = (E & F) ^ ((~E) & G);
+        uint32_t t1 = H + s1 + ch + K[t] + W[t];
+
+        H = G;
+        G = F;
+        F = E;
+        E = D + t1;
+        D = C;
+        C = B;
+        B = A;
+        A = t1 + t2;
+    }
+
+    ctx->state[0] += A;
+    ctx->state[1] += B;
+    ctx->state[2] += C;
+    ctx->state[3] += D;
+    ctx->state[4] += E;
+    ctx->state[5] += F;
+    ctx->state[6] += G;
+    ctx->state[7] += H;
+}
+
+static const HASH_VTAB SHA256_VTAB = {
+    SHA256_init,
+    SHA256_update,
+    SHA256_final,
+    SHA256_hash,
+    SHA256_DIGEST_SIZE
+};
+
+void SHA256_init(SHA256_CTX* ctx) {
+    ctx->f = &SHA256_VTAB;
+    ctx->state[0] = 0x6a09e667;
+    ctx->state[1] = 0xbb67ae85;
+    ctx->state[2] = 0x3c6ef372;
+    ctx->state[3] = 0xa54ff53a;
+    ctx->state[4] = 0x510e527f;
+    ctx->state[5] = 0x9b05688c;
+    ctx->state[6] = 0x1f83d9ab;
+    ctx->state[7] = 0x5be0cd19;
+    ctx->count = 0;
+}
+
+
+void SHA256_update(SHA256_CTX* ctx, const void* data, int len) {
+    int i = (int) (ctx->count & 63);
+    const uint8_t* p = (const uint8_t*)data;
+
+    ctx->count += len;
+
+    while (len--) {
+        ctx->buf[i++] = *p++;
+        if (i == 64) {
+            SHA256_Transform(ctx);
+            i = 0;
+        }
+    }
+}
+
+
+const uint8_t* SHA256_final(SHA256_CTX* ctx) {
+    uint8_t *p = ctx->buf;
+    uint64_t cnt = ctx->count * 8;
+    int i;
+
+    SHA256_update(ctx, (uint8_t*)"\x80", 1);
+    while ((ctx->count & 63) != 56) {
+        SHA256_update(ctx, (uint8_t*)"\0", 1);
+    }
+    for (i = 0; i < 8; ++i) {
+        uint8_t tmp = (uint8_t) (cnt >> ((7 - i) * 8));
+        SHA256_update(ctx, &tmp, 1);
+    }
+
+    for (i = 0; i < 8; i++) {
+        uint32_t tmp = ctx->state[i];
+        *p++ = tmp >> 24;
+        *p++ = tmp >> 16;
+        *p++ = tmp >> 8;
+        *p++ = tmp >> 0;
+    }
+
+    return ctx->buf;
+}
+
+/* Convenience function */
+const uint8_t* SHA256_hash(const void* data, int len, uint8_t* digest) {
+    SHA256_CTX ctx;
+    SHA256_init(&ctx);
+    SHA256_update(&ctx, data, len);
+    memcpy(digest, SHA256_final(&ctx), SHA256_DIGEST_SIZE);
+    return digest;
+}
diff --git a/libpixelflinger/Android.bp b/libpixelflinger/Android.bp
new file mode 100644
index 0000000..a23e386
--- /dev/null
+++ b/libpixelflinger/Android.bp
@@ -0,0 +1,88 @@
+cc_defaults {
+    name: "libpixelflingertwrp_defaults",
+
+    cflags: [
+        "-fstrict-aliasing",
+        "-fomit-frame-pointer",
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-function",
+    ],
+    export_include_dirs: ["include"],
+    header_libs: ["libbase_headers"],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libutils",
+    ],
+
+    arch: {
+        arm: {
+            neon: {
+                cflags: ["-D__ARM_HAVE_NEON"],
+            },
+        },
+    },
+}
+
+cc_library_static {
+    name: "libpixelflingertwrp-arm",
+    defaults: ["libpixelflingertwrp_defaults"],
+
+    srcs: [
+        "fixed.cpp",
+        "picker.cpp",
+        "pixelflinger.cpp",
+        "trap.cpp",
+        "scanline.cpp",
+    ],
+
+    arch: {
+        arm: {
+            instruction_set: "arm",
+        },
+    },
+}
+
+cc_library_static {
+    name: "libpixelflinger_twrp",
+    defaults: ["libpixelflingertwrp_defaults"],
+
+    srcs: [
+        "codeflinger/ARMAssemblerInterface.cpp",
+        "codeflinger/ARMAssemblerProxy.cpp",
+        "codeflinger/CodeCache.cpp",
+        "codeflinger/GGLAssembler.cpp",
+        "codeflinger/load_store.cpp",
+        "codeflinger/blending.cpp",
+        "codeflinger/texturing.cpp",
+        "format.cpp",
+        "clear.cpp",
+        "raster.cpp",
+        "buffer.cpp",
+    ],
+    whole_static_libs: ["libpixelflingertwrp-arm"],
+
+    arch: {
+        arm: {
+            srcs: [
+                "codeflinger/ARMAssembler.cpp",
+                "codeflinger/disassem.c",
+                "col32cb16blend.S",
+                "t32cb16blend.S",
+            ],
+
+            neon: {
+                srcs: ["col32cb16blend_neon.S"],
+            },
+        },
+        arm64: {
+            srcs: [
+                "codeflinger/Arm64Assembler.cpp",
+                "codeflinger/Arm64Disassembler.cpp",
+                "arch-arm64/col32cb16blend.S",
+                "arch-arm64/t32cb16blend.S",
+            ],
+        },
+    },
+}
diff --git a/libpixelflinger/Android.mk.orig b/libpixelflinger/Android.mk.orig
new file mode 100644
index 0000000..5a0bc79
--- /dev/null
+++ b/libpixelflinger/Android.mk.orig
@@ -0,0 +1,106 @@
+LOCAL_PATH:= system/core/libpixelflinger
+include $(CLEAR_VARS)
+
+#
+# C/C++ and ARMv5 objects
+#
+
+include $(CLEAR_VARS)
+
+ifneq ($(wildcard system/core/libpixelflinger/codeflinger/x86/X86Assembler.cpp),)
+    ifeq ($(TARGET_ARCH),x86)
+        TW_HAVE_X86_ACCELERATED_PIXELFLINGER := true
+    endif
+endif
+
+PIXELFLINGER_SRC_FILES += \
+    codeflinger/CodeCache.cpp \
+    format.cpp \
+    clear.cpp \
+    raster.cpp \
+    buffer.cpp
+
+ifneq ($(wildcard system/core/libpixelflinger/codeflinger/tinyutils/VectorImpl.cpp),)
+    PIXELFLINGER_SRC_FILES += \
+        codeflinger/tinyutils/SharedBuffer.cpp \
+        codeflinger/tinyutils/VectorImpl.cpp
+endif
+
+ifneq ($(TW_HAVE_X86_ACCELERATED_PIXELFLINGER),true)
+PIXELFLINGER_SRC_FILES += \
+	codeflinger/ARMAssemblerInterface.cpp \
+	codeflinger/ARMAssemblerProxy.cpp \
+	codeflinger/GGLAssembler.cpp \
+	codeflinger/load_store.cpp \
+	codeflinger/blending.cpp \
+	codeflinger/texturing.cpp \
+	fixed.cpp \
+	picker.cpp \
+	pixelflinger.cpp \
+	trap.cpp \
+	scanline.cpp
+else
+PIXELFLINGER_SRC_FILES_x86 := \
+	codeflinger/x86/X86Assembler.cpp \
+	codeflinger/x86/GGLX86Assembler.cpp \
+	codeflinger/x86/load_store.cpp \
+	codeflinger/x86/blending.cpp \
+	codeflinger/x86/texturing.cpp \
+	fixed.cpp \
+	picker.cpp \
+	pixelflinger.cpp \
+	trap.cpp \
+	scanline.cpp
+
+PIXELFLINGER_C_INCLUDES_x86 := \
+	external/libenc
+
+endif
+
+PIXELFLINGER_CFLAGS := -fstrict-aliasing -fomit-frame-pointer
+
+PIXELFLINGER_SRC_FILES_arm := \
+	codeflinger/ARMAssembler.cpp \
+	codeflinger/disassem.c \
+	col32cb16blend.S \
+	t32cb16blend.S \
+
+ifeq ($(ARCH_ARM_HAVE_NEON),true)
+PIXELFLINGER_SRC_FILES_arm += col32cb16blend_neon.S
+PIXELFLINGER_CFLAGS_arm += -D__ARM_HAVE_NEON
+endif
+
+PIXELFLINGER_SRC_FILES_arm64 := \
+	codeflinger/Arm64Assembler.cpp \
+	codeflinger/Arm64Disassembler.cpp \
+	arch-arm64/col32cb16blend.S \
+	arch-arm64/t32cb16blend.S \
+
+#
+# Static library version
+#
+
+include $(CLEAR_VARS)
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 26; echo $$?),0)
+LOCAL_CLANG := false
+endif
+LOCAL_MODULE:= libpixelflinger_twrp
+LOCAL_SRC_FILES := $(PIXELFLINGER_SRC_FILES)
+LOCAL_SRC_FILES_arm := $(PIXELFLINGER_SRC_FILES_arm)
+LOCAL_SRC_FILES_arm64 := $(PIXELFLINGER_SRC_FILES_arm64)
+LOCAL_SRC_FILES_x86 := $(PIXELFLINGER_SRC_FILES_x86)
+LOCAL_SRC_FILES_mips := $(PIXELFLINGER_SRC_FILES_mips)
+ifneq ($(shell test $(PLATFORM_SDK_VERSION) -gt 20; echo $$?),0)
+    LOCAL_SRC_FILES += $(LOCAL_SRC_FILES_$(TARGET_ARCH))
+endif
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_C_INCLUDES += $(LOCAL_EXPORT_C_INCLUDE_DIRS)
+LOCAL_CFLAGS := $(PIXELFLINGER_CFLAGS)
+LOCAL_CPPFLAGS := -Wno-unused-function
+LOCAL_STATIC_LIBRARIES += libbase libutils libcutils
+LOCAL_C_INCLUDES_x86 := $(PIXELFLINGER_C_INCLUDES_x86)
+ifeq ($(TW_HAVE_X86_ACCELERATED_PIXELFLINGER),true)
+LOCAL_WHOLE_STATIC_LIBRARIES += libenc
+LOCAL_C_INCLUDES += external/libenc
+endif
+include $(BUILD_STATIC_LIBRARY)
diff --git a/libpixelflinger/MODULE_LICENSE_APACHE2 b/libpixelflinger/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libpixelflinger/MODULE_LICENSE_APACHE2
diff --git a/libpixelflinger/NOTICE b/libpixelflinger/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/libpixelflinger/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2008, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/libpixelflinger/arch-arm64/col32cb16blend.S b/libpixelflinger/arch-arm64/col32cb16blend.S
new file mode 100644
index 0000000..84596f9
--- /dev/null
+++ b/libpixelflinger/arch-arm64/col32cb16blend.S
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+    .text
+    .balign 0
+
+    .global scanline_col32cb16blend_arm64
+
+//
+// This function alpha blends a fixed color into a destination scanline, using
+// the formula:
+//
+//     d = s + (((a + (a >> 7)) * d) >> 8)
+//
+// where d is the destination pixel,
+//       s is the source color,
+//       a is the alpha channel of the source color.
+//
+
+// x0 = destination buffer pointer
+// w1 = color value
+// w2 = count
+
+
+scanline_col32cb16blend_arm64:
+
+    lsr         w5, w1, #24                     // shift down alpha
+    mov         w9, #0xff                       // create mask
+    add         w5, w5, w5, lsr #7              // add in top bit
+    mov         w4, #256                        // create #0x100
+    sub         w5, w4, w5                      // invert alpha
+    and         w10, w1, #0xff                  // extract red
+    and         w12, w9, w1, lsr #8             // extract green
+    and         w4,  w9, w1, lsr #16            // extract blue
+    lsl         w10, w10, #5                    // prescale red
+    lsl         w12, w12, #6                    // prescale green
+    lsl         w4,  w4,  #5                    // prescale blue
+    lsr         w9,  w9,  #2                    // create dest green mask
+
+1:
+    ldrh        w8, [x0]                        // load dest pixel
+    subs        w2, w2, #1                      // decrement loop counter
+    lsr         w6, w8, #11                     // extract dest red
+    and         w7, w9, w8, lsr #5              // extract dest green
+    and         w8, w8, #0x1f                   // extract dest blue
+
+    madd        w6, w6, w5, w10                 // dest red * alpha + src red
+    madd        w7, w7, w5, w12                 // dest green * alpha + src green
+    madd        w8, w8, w5, w4                  // dest blue * alpha + src blue
+
+    lsr         w6, w6, #8                      // shift down red
+    lsr         w7, w7, #8                      // shift down green
+    lsl         w6, w6, #11                     // shift red into 565
+    orr         w6, w6, w7, lsl #5              // shift green into 565
+    orr         w6, w6, w8, lsr #8              // shift blue into 565
+
+    strh        w6, [x0], #2                    // store pixel to dest, update ptr
+    b.ne        1b                              // if count != 0, loop
+
+    ret
+
+
+
diff --git a/libpixelflinger/arch-arm64/t32cb16blend.S b/libpixelflinger/arch-arm64/t32cb16blend.S
new file mode 100644
index 0000000..a9733c0
--- /dev/null
+++ b/libpixelflinger/arch-arm64/t32cb16blend.S
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+    .text
+    .balign 0
+
+    .global scanline_t32cb16blend_arm64
+
+/*
+ * .macro pixel
+ *
+ *  This macro alpha blends RGB565 original pixel located in either
+ *  top or bottom 16 bits of DREG register with SRC 32 bit pixel value
+ *  and writes the result to FB register
+ *
+ * \DREG is a 32-bit register containing *two* original destination RGB565
+ *       pixels, with the even one in the low-16 bits, and the odd one in the
+ *       high 16 bits.
+ *
+ * \SRC is a 32-bit 0xAABBGGRR pixel value, with pre-multiplied colors.
+ *
+ * \FB is a target register that will contain the blended pixel values.
+ *
+ * \ODD is either 0 or 1 and indicates if we're blending the lower or
+ *      upper 16-bit pixels in DREG into FB
+ *
+ *
+ * clobbered: w6, w7, w15, w16, w17
+ *
+ */
+
+.macro pixel,   DREG, SRC, FB, ODD
+
+    // SRC = 0xAABBGGRR
+    lsr     w7, \SRC, #24               // sA
+    add     w7, w7, w7, lsr #7          // sA + (sA >> 7)
+    mov     w6, #0x100
+    sub     w7, w6, w7                  // sA = 0x100 - (sA+(sA>>7))
+
+1:
+
+.if \ODD //Blending odd pixel present in top 16 bits of DREG register
+
+    // red
+    lsr     w16, \DREG, #(16 + 11)
+    mul     w16, w7, w16
+    lsr     w6, \SRC, #3
+    and     w6, w6, #0x1F
+    add     w16, w6, w16, lsr #8
+    cmp     w16, #0x1F
+    orr     w17, \FB, #(0x1F<<(16 + 11))
+    orr     w15, \FB, w16, lsl #(16 + 11)
+    csel    \FB, w17, w15, hi
+        // green
+        and     w6, \DREG, #(0x3F<<(16 + 5))
+        lsr     w17,w6,#(16+5)
+        mul     w6, w7, w17
+        lsr     w16, \SRC, #(8+2)
+        and     w16, w16, #0x3F
+        add     w6, w16, w6, lsr #8
+        cmp     w6, #0x3F
+        orr     w17, \FB, #(0x3F<<(16 + 5))
+        orr     w15, \FB, w6, lsl #(16 + 5)
+        csel    \FB, w17, w15, hi
+            // blue
+            and     w16, \DREG, #(0x1F << 16)
+            lsr     w17,w16,#16
+            mul     w16, w7, w17
+            lsr     w6, \SRC, #(8+8+3)
+            and     w6, w6, #0x1F
+            add     w16, w6, w16, lsr #8
+            cmp     w16, #0x1F
+            orr     w17, \FB, #(0x1F << 16)
+            orr     w15, \FB, w16, lsl #16
+            csel    \FB, w17, w15, hi
+
+.else //Blending even pixel present in bottom 16 bits of DREG register
+
+    // red
+    lsr     w16, \DREG, #11
+    and     w16, w16, #0x1F
+    mul     w16, w7, w16
+    lsr     w6, \SRC, #3
+    and     w6, w6, #0x1F
+    add     w16, w6, w16, lsr #8
+    cmp     w16, #0x1F
+    mov     w17, #(0x1F<<11)
+    lsl     w15, w16, #11
+    csel    \FB, w17, w15, hi
+
+
+        // green
+        and     w6, \DREG, #(0x3F<<5)
+        mul     w6, w7, w6
+        lsr     w16, \SRC, #(8+2)
+        and     w16, w16, #0x3F
+        add     w6, w16, w6, lsr #(5+8)
+        cmp     w6, #0x3F
+        orr     w17, \FB, #(0x3F<<5)
+        orr     w15, \FB, w6, lsl #5
+        csel    \FB, w17, w15, hi
+
+            // blue
+            and     w16, \DREG, #0x1F
+            mul     w16, w7, w16
+            lsr     w6, \SRC, #(8+8+3)
+            and     w6, w6, #0x1F
+            add     w16, w6, w16, lsr #8
+            cmp     w16, #0x1F
+            orr     w17, \FB, #0x1F
+            orr     w15, \FB, w16
+            csel    \FB, w17, w15, hi
+
+.endif // End of blending even pixel
+
+.endm // End of pixel macro
+
+
+// x0:  dst ptr
+// x1:  src ptr
+// w2:  count
+// w3:  d
+// w4:  s0
+// w5:  s1
+// w6:  pixel
+// w7:  pixel
+// w8:  free
+// w9:  free
+// w10: free
+// w11: free
+// w12: scratch
+// w14: pixel
+
+scanline_t32cb16blend_arm64:
+
+    // align DST to 32 bits
+    tst     x0, #0x3
+    b.eq    aligned
+    subs    w2, w2, #1
+    b.lo    return
+
+last:
+    ldr     w4, [x1], #4
+    ldrh    w3, [x0]
+    pixel   w3, w4, w12, 0
+    strh    w12, [x0], #2
+
+aligned:
+    subs    w2, w2, #2
+    b.lo    9f
+
+    // The main loop is unrolled twice and processes 4 pixels
+8:
+    ldp   w4,w5, [x1], #8
+    add     x0, x0, #4
+    // it's all zero, skip this pixel
+    orr     w3, w4, w5
+    cbz     w3, 7f
+
+    // load the destination
+    ldr     w3, [x0, #-4]
+    // stream the destination
+    pixel   w3, w4, w12, 0
+    pixel   w3, w5, w12, 1
+    str     w12, [x0, #-4]
+
+    // 2nd iteration of the loop, don't stream anything
+    subs    w2, w2, #2
+    csel    w4, w5, w4, lt
+    blt     9f
+    ldp     w4,w5, [x1], #8
+    add     x0, x0, #4
+    orr     w3, w4, w5
+    cbz     w3, 7f
+    ldr     w3, [x0, #-4]
+    pixel   w3, w4, w12, 0
+    pixel   w3, w5, w12, 1
+    str     w12, [x0, #-4]
+
+7:  subs    w2, w2, #2
+    bhs     8b
+    mov     w4, w5
+
+9:  adds    w2, w2, #1
+    b.lo    return
+    b       last
+
+return:
+    ret
diff --git a/libpixelflinger/arch-mips/col32cb16blend.S b/libpixelflinger/arch-mips/col32cb16blend.S
new file mode 100644
index 0000000..810294c
--- /dev/null
+++ b/libpixelflinger/arch-mips/col32cb16blend.S
@@ -0,0 +1,134 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+       .macro pixel dreg src f sR sG sB shift
+
+#if __mips==32 && __mips_isa_rev>=2
+       /* extract red */
+       ext $t4,\src,\shift+11,5
+       mul $t4,$t4,\f
+
+       /* extract green */
+       ext $t5,\src,\shift+5,6
+       mul $t5,$t5,\f
+
+       /* extract blue */
+       ext $t6,\src,\shift,5
+       mul $t6,$t6,\f
+#else
+       /* extract red */
+       srl $t4,\src,\shift+11
+       andi $t4, 0x1f
+       mul $t4,$t4,\f
+
+       /* extract green */
+       srl $t5,\src,\shift+5
+       andi $t5, 0x3f
+       mul $t5,$t5,\f
+
+       /* extract blue */
+       srl $t6,\src,\shift
+       andi $t6, 0x1f
+       mul $t6,$t6,\f
+#endif
+
+       srl $t4,$t4,8
+       srl $t5,$t5,8
+       srl $t6,$t6,8
+       addu $t4,$t4,\sR
+       addu $t5,$t5,\sG
+       addu \dreg,$t6,\sB
+       sll $t4,$t4,11
+       sll $t5,$t5,5
+       or \dreg,\dreg,$t4
+       or \dreg,\dreg,$t5
+       andi \dreg, 0xffff
+       .endm
+
+       .text
+       .balign 4
+
+       .global scanline_col32cb16blend_mips
+       .ent    scanline_col32cb16blend_mips
+scanline_col32cb16blend_mips:
+
+       /* check if count is zero */
+       srl     $v0,$a1,24 /* sA */
+       beqz    $a2,done
+       li      $t4, 0x100
+       srl     $v1,$v0,7
+       addu    $v0,$v1,$v0
+       subu    $v0,$t4,$v0 /* f */
+#if __mips==32 && __mips_isa_rev>=2
+       ext     $a3,$a1,3,5 /* sR */
+       ext     $t0,$a1,10,6 /* sG */
+       ext     $t1,$a1,19,5 /* sB */
+#else
+       srl     $a3, $a1, 3
+       andi    $a3, 0x1f    /* sR */
+       srl     $t0, $a1, 10
+       andi    $t0, 0x3f    /* sG */
+       srl     $t1, $a1, 19
+       andi    $t1, 0x1f    /* sB */
+#endif
+
+       /* check if cnt is at least 4 */
+       addiu   $a2,$a2,-4
+       bltz    $a2,tail
+
+loop_4pixels:
+       lw      $t7,0($a0)
+       lw      $t8,4($a0)
+       addiu   $a0,$a0,8
+       addiu   $a2,$a2,-4
+       pixel   $t2 $t7 $v0 $a3 $t0 $t1 0
+       pixel   $t3 $t7 $v0 $a3 $t0 $t1 16
+#if __mips==32 && __mips_isa_rev>=2
+       ins     $t2,$t3,16,16
+#else
+       sll $t3, 16
+       or  $t2, $t2, $t3
+#endif
+       pixel   $t7 $t8 $v0 $a3 $t0 $t1 0
+       pixel   $t3 $t8 $v0 $a3 $t0 $t1 16
+#if __mips==32 && __mips_isa_rev>=2
+       ins     $t7,$t3,16,16
+#else
+       sll $t3, 16
+       or  $t7, $t7, $t3
+#endif
+       sw      $t2,-8($a0)
+       sw      $t7,-4($a0)
+       bgez    $a2, loop_4pixels
+
+tail:
+       /* the pixel count underran, restore it now */
+       addiu   $a2,$a2,4
+
+       /* handle the last 0..3 pixels */
+       beqz    $a2,done
+
+loop_1pixel:
+       lhu     $t7,0($a0)
+       addiu   $a0,$a0,2
+       addiu   $a2,$a2,-1
+       pixel   $t2 $t7 $v0 $a3 $t0 $t1 0
+       sh      $t2, -2($a0)
+       bnez    $a2,loop_1pixel
+
+done:
+       j       $ra
+       .end    scanline_col32cb16blend_mips
diff --git a/libpixelflinger/arch-mips/t32cb16blend.S b/libpixelflinger/arch-mips/t32cb16blend.S
new file mode 100644
index 0000000..1d2fb8f
--- /dev/null
+++ b/libpixelflinger/arch-mips/t32cb16blend.S
@@ -0,0 +1,273 @@
+/* libs/pixelflinger/t32cb16blend.S
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifdef DEBUG
+#define DBG
+#else
+#define DBG #
+#endif
+
+/*
+ * blend one of 2 16bpp RGB pixels held in dreg selected by shift
+ * with the 32bpp ABGR pixel held in src and store the result in fb
+ *
+ * Assumes that the dreg data is little endian and that
+ * the the second pixel (shift==16) will be merged into
+ * the fb result
+ *
+ * Uses $t0,$t6,$t7,$t8
+ */
+
+#if __mips==32 && __mips_isa_rev>=2
+    .macro pixel dreg src fb shift
+    /*
+     * sA = s >> 24
+     * f = 0x100 - (sA + (sA>>7))
+     */
+DBG .set    noat
+DBG rdhwr   $at,$2
+DBG .set    at
+
+    srl  $t7,\src,24
+    srl  $t6,$t7,7
+    addu $t7,$t6
+    li   $t6,0x100
+    subu $t7,$t6,$t7
+
+    /* red */
+    ext  $t8,\dreg,\shift+6+5,5         # dst[\shift:15..11]
+    mul  $t6,$t8,$t7
+    ext  $t0,\dreg,\shift+5,6           # start green extraction dst[\shift:10..5]
+    ext  $t8,\src,3,5               # src[7..3]
+    srl  $t6,8
+    addu $t8,$t6
+.if \shift!=0
+    sll  $t8,\shift+11
+    or   \fb,$t8
+.else
+    sll  \fb,$t8,11
+.endif
+
+    /* green */
+    mul  $t8,$t0,$t7
+    ext  $t0,\dreg,\shift,5         # start blue extraction dst[\shift:4..0]
+    ext  $t6,\src,2+8,6             # src[15..10]
+    srl  $t8,8
+    addu $t8,$t6
+
+    /* blue */
+    mul  $t0,$t0,$t7
+    sll  $t8, $t8, \shift+5
+    or   \fb, \fb, $t8
+    ext  $t6,\src,(3+8+8),5
+    srl  $t8,$t0,8
+    addu $t8,$t6
+    sll  $t8, $t8, \shift
+    or   \fb, \fb, $t8
+
+DBG .set    noat
+DBG rdhwr $t8,$2
+DBG subu  $t8,$at
+DBG sltu  $at,$t8,$v0
+DBG movn  $v0,$t8,$at
+DBG sgtu  $at,$t8,$v1
+DBG movn  $v1,$t8,$at
+DBG .set    at
+    .endm
+
+#else
+
+    .macro pixel dreg src fb shift
+    /*
+     * sA = s >> 24
+     * f = 0x100 - (sA + (sA>>7))
+     */
+DBG .set    push
+DBG .set    noat
+DBG .set    mips32r2
+DBG rdhwr   $at,$2
+DBG .set    pop
+
+    srl  $t7,\src,24
+    srl  $t6,$t7,7
+    addu $t7,$t6
+    li   $t6,0x100
+    subu $t7,$t6,$t7
+
+    /*
+     * red
+     * dR = (d >> (6 + 5)) & 0x1f;
+     * dR = (f*dR)>>8
+     * sR = (s >> (   3)) & 0x1f;
+     * sR += dR
+     * fb |= sR << 11
+     */
+    srl  $t8,\dreg,\shift+6+5
+.if \shift==0
+    and  $t8,0x1f
+.endif
+    mul  $t8,$t8,$t7
+    srl  $t6,\src,3
+    and  $t6,0x1f
+    srl  $t8,8
+    addu $t8,$t6
+.if \shift!=0
+    sll  $t8,\shift+11
+    or   \fb,$t8
+.else
+    sll  \fb,$t8,11
+.endif
+
+        /*
+     * green
+     * dG = (d >> 5) & 0x3f
+     * dG = (f*dG) >> 8
+     * sG = (s >> ( 8+2))&0x3F;
+     */
+    srl  $t8,\dreg,\shift+5
+    and  $t8,0x3f
+    mul  $t8,$t8,$t7
+    srl  $t6,\src,8+2
+    and  $t6,0x3f
+    srl  $t8,8
+    addu $t8,$t6
+    sll  $t8,\shift + 5
+    or   \fb,$t8
+
+    /* blue */
+.if \shift!=0
+    srl  $t8,\dreg,\shift
+    and  $t8,0x1f
+.else
+    and  $t8,\dreg,0x1f
+.endif
+    mul  $t8,$t8,$t7
+    srl  $t6,\src,(8+8+3)
+    and  $t6,0x1f
+    srl  $t8,8
+    addu $t8,$t6
+.if \shift!=0
+    sll  $t8,\shift
+.endif
+    or   \fb,$t8
+DBG .set    push
+DBG .set    noat
+DBG .set    mips32r2
+DBG rdhwr   $t8,$2
+DBG subu    $t8,$at
+DBG sltu    $at,$t8,$v0
+DBG movn    $v0,$t8,$at
+DBG sgtu    $at,$t8,$v1
+DBG movn    $v1,$t8,$at
+DBG .set    pop
+    .endm
+#endif
+
+    .text
+    .balign 4
+
+    .global scanline_t32cb16blend_mips
+    .ent    scanline_t32cb16blend_mips
+scanline_t32cb16blend_mips:
+DBG li    $v0,0xffffffff
+DBG li    $v1,0
+    /* Align the destination if necessary */
+    and   $t0,$a0,3
+    beqz  $t0,aligned
+
+    /* as long as there is at least one pixel */
+    beqz  $a2,done
+
+    lw    $t4,($a1)
+    addu  $a0,2
+    addu  $a1,4
+    beqz  $t4,1f
+    lhu   $t3,-2($a0)
+    pixel $t3,$t4,$t1,0
+    sh    $t1,-2($a0)
+1:  subu  $a2,1
+
+aligned:
+    /* Check to see if its worth unrolling the loop */
+    subu  $a2,4
+    bltz  $a2,tail
+
+    /* Process 4 pixels at a time */
+fourpixels:
+    /* 1st pair of pixels */
+    lw    $t4,0($a1)
+    lw    $t5,4($a1)
+    addu  $a0,8
+    addu  $a1,16
+
+    /* both are zero, skip this pair */
+    or    $t3,$t4,$t5
+    beqz  $t3,1f
+
+    /* load the destination */
+    lw    $t3,-8($a0)
+
+    pixel $t3,$t4,$t1,0
+    andi  $t1, 0xFFFF
+    pixel $t3,$t5,$t1,16
+    sw    $t1,-8($a0)
+
+1:
+    /* 2nd pair of pixels */
+    lw    $t4,-8($a1)
+    lw    $t5,-4($a1)
+
+    /* both are zero, skip this pair */
+    or    $t3,$t4,$t5
+    beqz  $t3,1f
+
+    /* load the destination */
+    lw    $t3,-4($a0)
+
+    pixel $t3,$t4,$t1,0
+    andi  $t1, 0xFFFF
+    pixel $t3,$t5,$t1,16
+    sw    $t1,-4($a0)
+
+1:  subu  $a2,4
+    bgtz  $a2,fourpixels
+
+tail:
+    /* the pixel count underran, restore it now */
+    addu  $a2,4
+
+    /* handle the last 0..3 pixels */
+    beqz  $a2,done
+onepixel:
+    lw    $t4,($a1)
+    addu  $a0,2
+    addu  $a1,4
+    beqz  $t4,1f
+    lhu   $t3,-2($a0)
+    pixel $t3,$t4,$t1,0
+    sh    $t1,-2($a0)
+1:  subu  $a2,1
+    bnez  $a2,onepixel
+done:
+DBG .set    push
+DBG .set    mips32r2
+DBG rdhwr   $a0,$3
+DBG mul     $v0,$a0
+DBG mul     $v1,$a0
+DBG .set    pop
+    j     $ra
+    .end    scanline_t32cb16blend_mips
diff --git a/libpixelflinger/arch-mips64/col32cb16blend.S b/libpixelflinger/arch-mips64/col32cb16blend.S
new file mode 100644
index 0000000..5baffb1
--- /dev/null
+++ b/libpixelflinger/arch-mips64/col32cb16blend.S
@@ -0,0 +1,108 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+    .macro pixel dreg src f sR sG sB shift
+
+    /* extract red */
+.if \shift < 32
+    dext   $t0,\src,\shift+11,5
+.else
+    dextu  $t0,\src,\shift+11,5
+.endif
+    mul    $t0,$t0,\f
+
+    /* extract green */
+.if \shift < 32
+    dext   $t1,\src,\shift+5,6
+.else
+    dextu  $t1,\src,\shift+5,6
+.endif
+    mul    $t1,$t1,\f
+
+    /* extract blue */
+.if \shift < 32
+    dext   $t2,\src,\shift,5
+.else
+    dextu  $t2,\src,\shift,5
+.endif
+    mul    $t2,$t2,\f
+
+    srl    $t0,$t0,8
+    srl    $t1,$t1,8
+    srl    $t2,$t2,8
+    addu   $t0,$t0,\sR
+    addu   $t1,$t1,\sG
+    addu   \dreg,$t2,\sB
+    sll    $t0,$t0,11
+    sll    $t1,$t1,5
+    or     \dreg,\dreg,$t0
+    or     \dreg,\dreg,$t1
+    .endm
+
+    .text
+    .balign 4
+
+    .global scanline_col32cb16blend_mips64
+    .ent    scanline_col32cb16blend_mips64
+scanline_col32cb16blend_mips64:
+
+    /* check if count is zero */
+    srl     $v0,$a1,24 /* sA */
+    beqz    $a2,done
+    li      $t0, 0x100
+    srl     $v1,$v0,7
+    addu    $v0,$v1,$v0
+    subu    $v0,$t0,$v0 /* f */
+    ext     $a3,$a1,3,5 /* sR */
+    ext     $a4,$a1,10,6 /* sG */
+    ext     $a5,$a1,19,5 /* sB */
+
+    /* check if cnt is at least 4 */
+    addiu   $a2,$a2,-4
+    bltz    $a2,tail
+
+loop_4pixels:
+    ld      $t3,0($a0)
+    daddiu  $a0,$a0,8
+    addiu   $a2,$a2,-4
+    pixel   $a6 $t3 $v0 $a3 $a4 $a5 0
+    pixel   $a7 $t3 $v0 $a3 $a4 $a5 16
+    pixel   $t8 $t3 $v0 $a3 $a4 $a5 32
+    pixel   $t9 $t3 $v0 $a3 $a4 $a5 48
+    dins    $a6,$a7,16,16
+    dinsu   $a6,$t8,32,16
+    dinsu   $a6,$t9,48,16
+    sd      $a6,-8($a0)
+    bgez    $a2, loop_4pixels
+
+tail:
+    /* the pixel count underran, restore it now */
+    addiu   $a2,$a2,4
+
+    /* handle the last 0..3 pixels */
+    beqz    $a2,done
+
+loop_1pixel:
+    lhu     $t3,0($a0)
+    daddiu  $a0,$a0,2
+    addiu   $a2,$a2,-1
+    pixel   $a6 $t3 $v0 $a3 $a4 $a5 0
+    sh      $a6, -2($a0)
+    bnez    $a2,loop_1pixel
+
+done:
+    j       $ra
+    .end    scanline_col32cb16blend_mips64
diff --git a/libpixelflinger/arch-mips64/t32cb16blend.S b/libpixelflinger/arch-mips64/t32cb16blend.S
new file mode 100644
index 0000000..3cb5f93
--- /dev/null
+++ b/libpixelflinger/arch-mips64/t32cb16blend.S
@@ -0,0 +1,172 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifdef DEBUG
+#define DBG
+#else
+#define DBG #
+#endif
+
+/*
+ * blend one of 2 16bpp RGB pixels held in dreg selected by shift
+ * with the 32bpp ABGR pixel held in src and store the result in fb
+ *
+ * Assumes that the dreg data is little endian and that
+ * the the second pixel (shift==16) will be merged into
+ * the fb result
+ *
+ * Uses $a4,$t2,$t3,$t8
+ */
+
+    .macro pixel dreg src fb shift
+    /*
+     * sA = s >> 24
+     * f = 0x100 - (sA + (sA>>7))
+     */
+    srl     $t3,\src,24
+    srl     $t2,$t3,7
+    addu    $t3,$t2
+    li      $t2,0x100
+    subu    $t3,$t2,$t3
+
+    /* red */
+    ext     $t8,\dreg,\shift+6+5,5                  # dst[\shift:15..11]
+    mul     $t2,$t8,$t3
+    ext     $a4,\dreg,\shift+5,6                    # start green extraction dst[\shift:10..5]
+    ext     $t8,\src,3,5                            # src[7..3]
+    srl     $t2,8
+    addu    $t8,$t2
+.if \shift!=0
+    sll     $t8,\shift+11                           # dst[\shift:15..11]
+    or      \fb,$t8
+.else
+    sll     \fb,$t8,11
+.endif
+
+    /* green */
+    mul     $t8,$a4,$t3
+    ext     $a4,\dreg,\shift,5                      # start blue extraction dst[\shift:4..0]
+    ext     $t2,\src,2+8,6                          # src[15..10]
+    srl     $t8,8
+    addu    $t8,$t2
+
+    /* blue */
+    mul     $a4,$a4,$t3
+    sll     $t8, $t8, \shift+5                  # finish green insertion dst[\shift:10..5]
+    or      \fb, \fb, $t8
+    ext     $t2,\src,(3+8+8),5
+    srl     $t8,$a4,8
+    addu    $t8,$t2
+    sll     $t8, $t8, \shift
+    or      \fb, \fb, $t8
+    .endm
+
+    .text
+    .balign 4
+
+    .global scanline_t32cb16blend_mips64
+    .ent    scanline_t32cb16blend_mips64
+scanline_t32cb16blend_mips64:
+    daddiu  $sp, $sp, -40
+DBG li      $v0,0xffffffff
+DBG li      $v1,0
+    /* Align the destination if necessary */
+    and     $a4,$a0,3
+    beqz    $a4,aligned
+
+    /* as long as there is at least one pixel */
+    beqz    $a2,done
+
+    lw      $t0,($a1)
+    daddu   $a0,2
+    daddu   $a1,4
+    beqz    $t0,1f
+    lhu     $a7,-2($a0)
+    pixel   $a7,$t0,$a5,0
+    sh      $a5,-2($a0)
+1:  subu    $a2,1
+
+aligned:
+    /* Check to see if its worth unrolling the loop */
+    subu    $a2,4
+    bltz    $a2,tail
+
+    /* Process 4 pixels at a time */
+fourpixels:
+    /* 1st pair of pixels */
+    lw      $t0,0($a1)
+    lw      $t1,4($a1)
+    daddu   $a0,8
+    daddu   $a1,16
+
+    /* both are zero, skip this pair */
+    or      $a7,$t0,$t1
+    beqz    $a7,1f
+
+    /* load the destination */
+    lw      $a7,-8($a0)
+
+    pixel   $a7,$t0,$a5,0
+    andi    $a5, 0xFFFF
+    pixel   $a7,$t1,$a5,16
+    sw      $a5,-8($a0)
+
+1:
+    /* 2nd pair of pixels */
+    lw      $t0,-8($a1)
+    lw      $t1,-4($a1)
+
+    /* both are zero, skip this pair */
+    or      $a7,$t0,$t1
+    beqz    $a7,1f
+
+    /* load the destination */
+    lw      $a7,-4($a0)
+
+    pixel   $a7,$t0,$a5,0
+    andi    $a5, 0xFFFF
+    pixel   $a7,$t1,$a5,16
+    sw      $a5,-4($a0)
+
+1:  subu    $a2,4
+    bgtz    $a2,fourpixels
+
+tail:
+    /* the pixel count underran, restore it now */
+    addu    $a2,4
+
+    /* handle the last 0..3 pixels */
+    beqz    $a2,done
+onepixel:
+    lw      $t0,($a1)
+    daddu   $a0,2
+    daddu   $a1,4
+    beqz    $t0,1f
+    lhu     $a7,-2($a0)
+    pixel   $a7,$t0,$a5,0
+    sh      $a5,-2($a0)
+1:  subu    $a2,1
+    bnez    $a2,onepixel
+done:
+DBG .set    push
+DBG .set    mips32r2
+DBG rdhwr   $a0,$3
+DBG mul     $v0,$a0
+DBG mul     $v1,$a0
+DBG .set    pop
+    daddiu  $sp, $sp, 40
+    j       $ra
+    .end    scanline_t32cb16blend_mips64
diff --git a/libpixelflinger/buffer.cpp b/libpixelflinger/buffer.cpp
new file mode 100644
index 0000000..ea9514c
--- /dev/null
+++ b/libpixelflinger/buffer.cpp
@@ -0,0 +1,389 @@
+/* libs/pixelflinger/buffer.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#include <assert.h>
+
+#include <android-base/macros.h>
+
+#include "buffer.h"
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+static void read_pixel(const surface_t* s, context_t* c,
+        uint32_t x, uint32_t y, pixel_t* pixel);
+static void write_pixel(const surface_t* s, context_t* c,
+        uint32_t x, uint32_t y, const pixel_t* pixel);
+static void readRGB565(const surface_t* s, context_t* c,
+        uint32_t x, uint32_t y, pixel_t* pixel);
+static void readABGR8888(const surface_t* s, context_t* c,
+        uint32_t x, uint32_t y, pixel_t* pixel);
+
+static uint32_t logic_op(int op, uint32_t s, uint32_t d);
+static uint32_t extract(uint32_t v, int h, int l, int bits);
+static uint32_t expand(uint32_t v, int sbits, int dbits);
+static uint32_t downshift_component(uint32_t in, uint32_t v,
+        int sh, int sl, int dh, int dl, int ch, int cl, int dither);
+
+// ----------------------------------------------------------------------------
+
+void ggl_init_texture(context_t* c)
+{
+    for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; i++) {
+        texture_t& t = c->state.texture[i];
+        t.s_coord = GGL_ONE_TO_ONE;
+        t.t_coord = GGL_ONE_TO_ONE;
+        t.s_wrap = GGL_REPEAT;
+        t.t_wrap = GGL_REPEAT;
+        t.min_filter = GGL_NEAREST;
+        t.mag_filter = GGL_NEAREST;
+        t.env = GGL_MODULATE;
+    }
+    c->activeTMU = &(c->state.texture[0]);
+}
+
+void ggl_set_surface(context_t* c, surface_t* dst, const GGLSurface* src)
+{
+    dst->width = src->width;
+    dst->height = src->height;
+    dst->stride = src->stride;
+    dst->data = src->data;
+    dst->format = src->format;
+    dst->dirty = 1;
+    if (__builtin_expect(dst->stride < 0, false)) {
+        const GGLFormat& pixelFormat(c->formats[dst->format]);
+        const int32_t bpr = -dst->stride * pixelFormat.size;
+        dst->data += bpr * (dst->height-1);
+    }
+}
+
+static void pick_read_write(surface_t* s)
+{
+    // Choose best reader/writers.
+    switch (s->format) {
+        case GGL_PIXEL_FORMAT_RGBA_8888:    s->read = readABGR8888;  break;
+        case GGL_PIXEL_FORMAT_RGB_565:      s->read = readRGB565;    break;
+        default:                            s->read = read_pixel;    break;
+    }
+    s->write = write_pixel;
+}
+
+void ggl_pick_texture(context_t* c)
+{
+    for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; ++i) {
+        surface_t& s = c->state.texture[i].surface;
+        if ((!c->state.texture[i].enable) || (!s.dirty))
+            continue;
+        s.dirty = 0;
+        pick_read_write(&s);
+        generated_tex_vars_t& gen = c->generated_vars.texture[i];
+        gen.width   = s.width;
+        gen.height  = s.height;
+        gen.stride  = s.stride;
+        gen.data    = uintptr_t(s.data);
+    }
+}
+
+void ggl_pick_cb(context_t* c)
+{
+    surface_t& s = c->state.buffers.color;
+    if (s.dirty) {
+        s.dirty = 0;
+        pick_read_write(&s);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+void read_pixel(const surface_t* s, context_t* c,
+        uint32_t x, uint32_t y, pixel_t* pixel)
+{
+    assert((x < s->width) && (y < s->height));
+
+    const GGLFormat* f = &(c->formats[s->format]);
+    int32_t index = x + (s->stride * y);
+    uint8_t* const data = s->data + index * f->size;
+    uint32_t v = 0;
+    switch (f->size) {
+        case 1:		v = *data;									break;
+        case 2:		v = *(uint16_t*)data;						break;
+        case 3:		v = (data[2]<<16)|(data[1]<<8)|data[0];     break;
+        case 4:		v = GGL_RGBA_TO_HOST(*(uint32_t*)data);		break;
+    }
+    for (int i=0 ; i<4 ; i++) {
+        pixel->s[i] = f->c[i].h - f->c[i].l;
+        if (pixel->s[i])
+            pixel->c[i] = extract(v,  f->c[i].h,  f->c[i].l, f->size*8);
+    }
+}
+
+void readRGB565(const surface_t* s, context_t* /*c*/,
+        uint32_t x, uint32_t y, pixel_t* pixel)
+{
+    uint16_t v = *(reinterpret_cast<uint16_t*>(s->data) + (x + (s->stride * y)));
+    pixel->c[0] = 0;
+    pixel->c[1] = v>>11;
+    pixel->c[2] = (v>>5)&0x3F;
+    pixel->c[3] = v&0x1F;
+    pixel->s[0] = 0;
+    pixel->s[1] = 5;
+    pixel->s[2] = 6;
+    pixel->s[3] = 5;
+}
+
+void readABGR8888(const surface_t* s, context_t* /*c*/,
+        uint32_t x, uint32_t y, pixel_t* pixel)
+{
+    uint32_t v = *(reinterpret_cast<uint32_t*>(s->data) + (x + (s->stride * y)));
+    v = GGL_RGBA_TO_HOST(v);
+    pixel->c[0] = v>>24;        // A
+    pixel->c[1] = v&0xFF;       // R
+    pixel->c[2] = (v>>8)&0xFF;  // G
+    pixel->c[3] = (v>>16)&0xFF; // B
+    pixel->s[0] = 
+    pixel->s[1] = 
+    pixel->s[2] = 
+    pixel->s[3] = 8;
+}
+
+void write_pixel(const surface_t* s, context_t* c,
+        uint32_t x, uint32_t y, const pixel_t* pixel)
+{
+    assert((x < s->width) && (y < s->height));
+
+    int dither = -1;
+    if (c->state.enables & GGL_ENABLE_DITHER) {
+        dither = c->ditherMatrix[ (x & GGL_DITHER_MASK) +
+                ((y & GGL_DITHER_MASK)<<GGL_DITHER_ORDER_SHIFT) ];
+    }
+
+    const GGLFormat* f = &(c->formats[s->format]);
+    int32_t index = x + (s->stride * y);
+    uint8_t* const data = s->data + index * f->size;
+        
+    uint32_t mask = 0;
+    uint32_t v = 0;
+    for (int i=0 ; i<4 ; i++) {
+        const int component_mask = 1 << i;
+        if (f->components>=GGL_LUMINANCE &&
+                (i==GGLFormat::GREEN || i==GGLFormat::BLUE)) {
+            // destinations L formats don't have G or B
+            continue;
+        }
+        const int l = f->c[i].l;
+        const int h = f->c[i].h;
+        if (h && (c->state.mask.color & component_mask)) {
+            mask |= (((1<<(h-l))-1)<<l);
+            uint32_t u = pixel->c[i];
+            int32_t pixelSize = pixel->s[i];
+            if (pixelSize < (h-l)) {
+                u = expand(u, pixelSize, h-l);
+                pixelSize = h-l;
+            }
+            v = downshift_component(v, u, pixelSize, 0, h, l, 0, 0, dither);
+        }
+    }
+
+    if ((c->state.mask.color != 0xF) || 
+        (c->state.enables & GGL_ENABLE_LOGIC_OP)) {
+        uint32_t d = 0;
+        switch (f->size) {
+            case 1:	d = *data;									break;
+            case 2:	d = *(uint16_t*)data;						break;
+            case 3:	d = (data[2]<<16)|(data[1]<<8)|data[0];     break;
+            case 4:	d = GGL_RGBA_TO_HOST(*(uint32_t*)data);		break;
+        }
+        if (c->state.enables & GGL_ENABLE_LOGIC_OP) {
+            v = logic_op(c->state.logic_op.opcode, v, d);            
+            v &= mask;
+        }
+        v |= (d & ~mask);
+    }
+
+    switch (f->size) {
+        case 1:		*data = v;									break;
+        case 2:		*(uint16_t*)data = v;						break;
+        case 3:
+            data[0] = v;
+            data[1] = v>>8;
+            data[2] = v>>16;
+            break;
+        case 4:		*(uint32_t*)data = GGL_HOST_TO_RGBA(v);     break;
+    }
+}
+
+static uint32_t logic_op(int op, uint32_t s, uint32_t d)
+{
+    switch(op) {
+    case GGL_CLEAR:         return 0;
+    case GGL_AND:           return s & d;
+    case GGL_AND_REVERSE:   return s & ~d;
+    case GGL_COPY:          return s;
+    case GGL_AND_INVERTED:  return ~s & d;
+    case GGL_NOOP:          return d;
+    case GGL_XOR:           return s ^ d;
+    case GGL_OR:            return s | d;
+    case GGL_NOR:           return ~(s | d);
+    case GGL_EQUIV:         return ~(s ^ d);
+    case GGL_INVERT:        return ~d;
+    case GGL_OR_REVERSE:    return s | ~d;
+    case GGL_COPY_INVERTED: return ~s;
+    case GGL_OR_INVERTED:   return ~s | d;
+    case GGL_NAND:          return ~(s & d);
+    case GGL_SET:           return ~0;
+    };
+    return s;
+}            
+
+
+uint32_t ggl_expand(uint32_t v, int sbits, int dbits)
+{
+    return expand(v, sbits, dbits);
+}
+
+uint32_t ggl_pack_color(context_t* c, int32_t format,
+        GGLcolor r, GGLcolor g, GGLcolor b, GGLcolor a)
+{
+    const GGLFormat* f = &(c->formats[format]);
+    uint32_t p = 0;
+    const int32_t hbits = GGL_COLOR_BITS;
+    const int32_t lbits = GGL_COLOR_BITS - 8;
+    p = downshift_component(p, r,   hbits, lbits,  f->rh, f->rl, 0, 1, -1);
+    p = downshift_component(p, g,   hbits, lbits,  f->gh, f->gl, 0, 1, -1);
+    p = downshift_component(p, b,   hbits, lbits,  f->bh, f->bl, 0, 1, -1);
+    p = downshift_component(p, a,   hbits, lbits,  f->ah, f->al, 0, 1, -1);
+    switch (f->size) {
+        case 1:
+            p |= p << 8;
+            FALLTHROUGH_INTENDED;
+        case 2:
+            p |= p << 16;
+    }
+    return p;
+}
+
+// ----------------------------------------------------------------------------
+
+// extract a component from a word
+uint32_t extract(uint32_t v, int h, int l, int bits)
+{
+	assert(h);
+	if (l) {
+		v >>= l;
+	}
+	if (h != bits) {
+		v &= (1<<(h-l))-1;
+	}
+	return v;
+}
+
+// expand a component from sbits to dbits
+uint32_t expand(uint32_t v, int sbits, int dbits)
+{
+    if (dbits > sbits) {
+        assert(sbits);
+        if (sbits==1) {
+            v = (v<<dbits) - v;
+        } else {
+            if (dbits % sbits) {
+                v <<= (dbits-sbits);
+                dbits -= sbits;
+                do {
+                    v |= v>>sbits;
+                    dbits -= sbits;
+                    sbits *= 2;
+                } while (dbits>0);
+            } else {
+                dbits -= sbits;
+                do {
+                    v |= v<<sbits;
+                    dbits -= sbits;
+                    if (sbits*2 < dbits) {
+                        sbits *= 2;
+                    }
+                } while (dbits > 0);
+            }
+        }
+    }
+	return v;
+}
+
+// downsample a component from sbits to dbits
+// and shift / construct the pixel
+uint32_t downshift_component(	uint32_t in, uint32_t v,
+                                int sh, int sl,		// src
+                                int dh, int dl,		// dst
+                                int ch, int cl,		// clear
+                                int dither)
+{
+	const int sbits = sh-sl;
+	const int dbits = dh-dl;
+    
+	assert(sbits>=dbits);
+
+
+    if (sbits>dbits) {
+        if (dither>=0) {
+            v -= (v>>dbits);				// fix up
+            const int shift = (GGL_DITHER_BITS - (sbits-dbits));
+            if (shift >= 0)   v += (dither >> shift) << sl;
+            else              v += (dither << (-shift)) << sl;
+        } else {
+            // don't do that right now, so we can reproduce the same
+            // artifacts we get on ARM (Where we don't do this)
+            // -> this is not really needed if we don't dither
+            //if (dBits > 1) { // result already OK if dBits==1
+            //    v -= (v>>dbits);				// fix up
+            //    v += 1 << ((sbits-dbits)-1);	// rounding
+            //}
+        }
+    }
+
+
+	// we need to clear the high bits of the source
+	if (ch) {
+		v <<= 32-sh;
+		sl += 32-sh;
+        sh = 32;
+	}
+	
+	if (dl) {
+		if (cl || (sbits>dbits)) {
+			v >>= sh-dbits;
+			sl = 0;
+			sh = dbits;
+            in |= v<<dl;
+		} else {
+			// sbits==dbits and we don't need to clean the lower bits
+			// so we just have to shift the component to the right location
+            int shift = dh-sh;
+            in |= v<<shift;
+		}
+	} else {
+		// destination starts at bit 0
+		// ie: sh-dh == sh-dbits
+		int shift = sh-dh;
+		if (shift > 0)      in |= v>>shift;
+		else if (shift < 0) in |= v<<shift;
+		else                in |= v;
+	}
+	return in;
+}
+
+// ----------------------------------------------------------------------------
+}; // namespace android
diff --git a/libpixelflinger/buffer.h b/libpixelflinger/buffer.h
new file mode 100644
index 0000000..9c9e4bc
--- /dev/null
+++ b/libpixelflinger/buffer.h
@@ -0,0 +1,39 @@
+/* libs/pixelflinger/buffer.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#ifndef ANDROID_GGL_TEXTURE_H
+#define ANDROID_GGL_TEXTURE_H
+
+#include <private/pixelflinger/ggl_context.h>
+
+namespace android {
+
+void ggl_init_texture(context_t* c);
+
+void ggl_set_surface(context_t* c, surface_t* dst, const GGLSurface* src);
+
+void ggl_pick_texture(context_t* c);
+void ggl_pick_cb(context_t* c);
+
+uint32_t ggl_expand(uint32_t v, int sbits, int dbits);
+uint32_t ggl_pack_color(context_t* c, int32_t format,
+            GGLcolor r, GGLcolor g, GGLcolor b, GGLcolor a);
+
+}; // namespace android
+
+#endif // ANDROID_GGL_TEXTURE_H
diff --git a/libpixelflinger/clear.cpp b/libpixelflinger/clear.cpp
new file mode 100644
index 0000000..b962456
--- /dev/null
+++ b/libpixelflinger/clear.cpp
@@ -0,0 +1,171 @@
+/* libs/pixelflinger/clear.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+#include <cutils/memory.h>
+
+#include "clear.h"
+#include "buffer.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static void ggl_clear(void* c, GGLbitfield mask);
+static void ggl_clearColorx(void* c,
+        GGLclampx r, GGLclampx g, GGLclampx b, GGLclampx a);        
+static void ggl_clearDepthx(void* c, GGLclampx depth);
+static void ggl_clearStencil(void* c, GGLint s);
+
+// ----------------------------------------------------------------------------
+
+void ggl_init_clear(context_t* c)
+{
+    GGLContext& procs = *(GGLContext*)c;
+    GGL_INIT_PROC(procs, clear);
+    GGL_INIT_PROC(procs, clearColorx);
+    GGL_INIT_PROC(procs, clearDepthx);
+    GGL_INIT_PROC(procs, clearStencil);
+    c->state.clear.dirty =  GGL_STENCIL_BUFFER_BIT |
+                            GGL_COLOR_BUFFER_BIT |
+                            GGL_DEPTH_BUFFER_BIT;
+    c->state.clear.depth = FIXED_ONE;
+}
+
+// ----------------------------------------------------------------------------
+
+static void memset2d(context_t* c, const surface_t& s, uint32_t packed,
+        uint32_t l, uint32_t t, uint32_t w, uint32_t h)
+{
+    const uint32_t size = c->formats[s.format].size;
+    const int32_t stride = s.stride * size;
+    uint8_t* dst = (uint8_t*)s.data + (l + t*s.stride)*size;
+    w *= size;
+
+    if (ggl_likely(int32_t(w) == stride)) {
+        // clear the whole thing in one call
+        w *= h;
+        h = 1;
+    }
+
+    switch (size) {
+    case 1:
+        do {
+            memset(dst, packed, w);
+            dst += stride;
+        } while(--h);
+        break;
+    case 2:
+        do {
+            android_memset16((uint16_t*)dst, packed, w);
+            dst += stride;
+        } while(--h);
+        break;
+    case 3: // XXX: 24-bit clear.
+        break;
+    case 4:
+        do {
+            android_memset32((uint32_t*)dst, packed, w);
+            dst += stride;
+        } while(--h);
+        break;
+    }    
+}
+
+static inline GGLfixed fixedToZ(GGLfixed z) {
+    return GGLfixed(((int64_t(z) << 16) - z) >> 16);
+}
+
+static void ggl_clear(void* con, GGLbitfield mask)
+{
+    GGL_CONTEXT(c, con);
+
+    // XXX: rgba-dithering, rgba-masking
+    // XXX: handle all formats of Z and S
+
+    const uint32_t l = c->state.scissor.left;
+    const uint32_t t = c->state.scissor.top;
+    uint32_t w = c->state.scissor.right - l;
+    uint32_t h = c->state.scissor.bottom - t;
+
+    if (!w || !h)
+        return;
+
+    // unexsiting buffers have no effect...
+    if (c->state.buffers.color.format == 0)
+        mask &= ~GGL_COLOR_BUFFER_BIT;
+
+    if (c->state.buffers.depth.format == 0)
+        mask &= ~GGL_DEPTH_BUFFER_BIT;
+
+    if (c->state.buffers.stencil.format == 0)
+        mask &= ~GGL_STENCIL_BUFFER_BIT;
+
+    if (mask & GGL_COLOR_BUFFER_BIT) {
+        if (c->state.clear.dirty & GGL_COLOR_BUFFER_BIT) {
+            c->state.clear.dirty &= ~GGL_COLOR_BUFFER_BIT;
+
+            uint32_t colorPacked = ggl_pack_color(c,
+                    c->state.buffers.color.format,
+                    gglFixedToIteratedColor(c->state.clear.r),
+                    gglFixedToIteratedColor(c->state.clear.g),
+                    gglFixedToIteratedColor(c->state.clear.b),
+                    gglFixedToIteratedColor(c->state.clear.a));
+
+            c->state.clear.colorPacked = GGL_HOST_TO_RGBA(colorPacked);
+        }
+        const uint32_t packed = c->state.clear.colorPacked;
+        memset2d(c, c->state.buffers.color, packed, l, t, w, h);
+    }
+    if (mask & GGL_DEPTH_BUFFER_BIT) {
+        if (c->state.clear.dirty & GGL_DEPTH_BUFFER_BIT) {
+            c->state.clear.dirty &= ~GGL_DEPTH_BUFFER_BIT;
+            uint32_t depth = fixedToZ(c->state.clear.depth);
+            c->state.clear.depthPacked = (depth<<16)|depth;
+        }
+        const uint32_t packed = c->state.clear.depthPacked;
+        memset2d(c, c->state.buffers.depth, packed, l, t, w, h);
+    }
+
+    // XXX: do stencil buffer
+}
+
+static void ggl_clearColorx(void* con,
+        GGLclampx r, GGLclampx g, GGLclampx b, GGLclampx a)
+{
+    GGL_CONTEXT(c, con);
+    c->state.clear.r = gglClampx(r);
+    c->state.clear.g = gglClampx(g);
+    c->state.clear.b = gglClampx(b);
+    c->state.clear.a = gglClampx(a);
+    c->state.clear.dirty |= GGL_COLOR_BUFFER_BIT;
+}
+
+static void ggl_clearDepthx(void* con, GGLclampx depth)
+{
+    GGL_CONTEXT(c, con);
+    c->state.clear.depth = gglClampx(depth);
+    c->state.clear.dirty |= GGL_DEPTH_BUFFER_BIT;
+}
+
+static void ggl_clearStencil(void* con, GGLint s)
+{
+    GGL_CONTEXT(c, con);
+    c->state.clear.stencil = s;
+    c->state.clear.dirty |= GGL_STENCIL_BUFFER_BIT;
+}
+
+}; // namespace android
diff --git a/libpixelflinger/clear.h b/libpixelflinger/clear.h
new file mode 100644
index 0000000..b071df0
--- /dev/null
+++ b/libpixelflinger/clear.h
@@ -0,0 +1,30 @@
+/* libs/pixelflinger/clear.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+#ifndef ANDROID_GGL_CLEAR_H
+#define ANDROID_GGL_CLEAR_H
+
+#include <pixelflinger/pixelflinger.h>
+#include <private/pixelflinger/ggl_context.h>
+
+namespace android {
+
+void ggl_init_clear(context_t* c);
+
+}; // namespace android
+
+#endif // ANDROID_GGL_CLEAR_H
diff --git a/libpixelflinger/codeflinger/ARMAssembler.cpp b/libpixelflinger/codeflinger/ARMAssembler.cpp
new file mode 100644
index 0000000..f47b6e4
--- /dev/null
+++ b/libpixelflinger/codeflinger/ARMAssembler.cpp
@@ -0,0 +1,579 @@
+/* libs/pixelflinger/codeflinger/ARMAssembler.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "ARMAssembler"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <cutils/properties.h>
+#include <log/log.h>
+#include <private/pixelflinger/ggl_context.h>
+
+#include "ARMAssembler.h"
+#include "CodeCache.h"
+#include "disassem.h"
+
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark ARMAssembler...
+#endif
+
+ARMAssembler::ARMAssembler(const sp<Assembly>& assembly)
+    :   ARMAssemblerInterface(),
+        mAssembly(assembly)
+{
+    mBase = mPC = (uint32_t *)assembly->base();
+    mDuration = ggl_system_time();
+}
+
+ARMAssembler::~ARMAssembler()
+{
+}
+
+uint32_t* ARMAssembler::pc() const
+{
+    return mPC;
+}
+
+uint32_t* ARMAssembler::base() const
+{
+    return mBase;
+}
+
+void ARMAssembler::reset()
+{
+    mBase = mPC = (uint32_t *)mAssembly->base();
+    mBranchTargets.clear();
+    mLabels.clear();
+    mLabelsInverseMapping.clear();
+    mComments.clear();
+}
+
+int ARMAssembler::getCodegenArch()
+{
+    return CODEGEN_ARCH_ARM;
+}
+
+// ----------------------------------------------------------------------------
+
+void ARMAssembler::disassemble(const char* name)
+{
+    if (name) {
+        printf("%s:\n", name);
+    }
+    size_t count = pc()-base();
+    uint32_t* i = base();
+    while (count--) {
+        ssize_t label = mLabelsInverseMapping.indexOfKey(i);
+        if (label >= 0) {
+            printf("%s:\n", mLabelsInverseMapping.valueAt(label));
+        }
+        ssize_t comment = mComments.indexOfKey(i);
+        if (comment >= 0) {
+            printf("; %s\n", mComments.valueAt(comment));
+        }
+        printf("%08x:    %08x    ", uintptr_t(i), int(i[0]));
+        ::disassemble((uintptr_t)i);
+        i++;
+    }
+}
+
+void ARMAssembler::comment(const char* string)
+{
+    mComments.add(mPC, string);
+}
+
+void ARMAssembler::label(const char* theLabel)
+{
+    mLabels.add(theLabel, mPC);
+    mLabelsInverseMapping.add(mPC, theLabel);
+}
+
+void ARMAssembler::B(int cc, const char* label)
+{
+    mBranchTargets.add(branch_target_t(label, mPC));
+    *mPC++ = (cc<<28) | (0xA<<24) | 0;
+}
+
+void ARMAssembler::BL(int cc, const char* label)
+{
+    mBranchTargets.add(branch_target_t(label, mPC));
+    *mPC++ = (cc<<28) | (0xB<<24) | 0;
+}
+
+#if 0
+#pragma mark -
+#pragma mark Prolog/Epilog & Generate...
+#endif
+
+
+void ARMAssembler::prolog()
+{
+    // write dummy prolog code
+    mPrologPC = mPC;
+    STM(AL, FD, SP, 1, LSAVED);
+}
+
+void ARMAssembler::epilog(uint32_t touched)
+{
+    touched &= LSAVED;
+    if (touched) {
+        // write prolog code
+        uint32_t* pc = mPC;
+        mPC = mPrologPC;
+        STM(AL, FD, SP, 1, touched | LLR);
+        mPC = pc;
+        // write epilog code
+        LDM(AL, FD, SP, 1, touched | LLR);
+        BX(AL, LR);
+    } else {   // heh, no registers to save!
+        // write prolog code
+        uint32_t* pc = mPC;
+        mPC = mPrologPC;
+        MOV(AL, 0, R0, R0); // NOP
+        mPC = pc;
+        // write epilog code
+        BX(AL, LR);
+    }
+}
+
+int ARMAssembler::generate(const char* name)
+{
+    // fixup all the branches
+    size_t count = mBranchTargets.size();
+    while (count--) {
+        const branch_target_t& bt = mBranchTargets[count];
+        uint32_t* target_pc = mLabels.valueFor(bt.label);
+        LOG_ALWAYS_FATAL_IF(!target_pc,
+                "error resolving branch targets, target_pc is null");
+        int32_t offset = int32_t(target_pc - (bt.pc+2));
+        *bt.pc |= offset & 0xFFFFFF;
+    }
+
+    mAssembly->resize( int(pc()-base())*4 );
+
+    // the instruction cache is flushed by CodeCache
+    const int64_t duration = ggl_system_time() - mDuration;
+    const char * const format = "generated %s (%d ins) at [%p:%p] in %lld ns\n";
+    ALOGI(format, name, int(pc()-base()), base(), pc(), duration);
+
+    char value[PROPERTY_VALUE_MAX];
+    property_get("debug.pf.disasm", value, "0");
+    if (atoi(value) != 0) {
+        printf(format, name, int(pc()-base()), base(), pc(), duration);
+        disassemble(name);
+    }
+
+    return OK;
+}
+
+uint32_t* ARMAssembler::pcForLabel(const char* label)
+{
+    return mLabels.valueFor(label);
+}
+
+// ----------------------------------------------------------------------------
+
+#if 0
+#pragma mark -
+#pragma mark Data Processing...
+#endif
+
+void ARMAssembler::dataProcessing(int opcode, int cc,
+        int s, int Rd, int Rn, uint32_t Op2)
+{
+    *mPC++ = (cc<<28) | (opcode<<21) | (s<<20) | (Rn<<16) | (Rd<<12) | Op2;
+}
+
+#if 0
+#pragma mark -
+#pragma mark Multiply...
+#endif
+
+// multiply...
+void ARMAssembler::MLA(int cc, int s,
+        int Rd, int Rm, int Rs, int Rn) {
+    if (Rd == Rm) { int t = Rm; Rm=Rs; Rs=t; }
+    LOG_FATAL_IF(Rd==Rm, "MLA(r%u,r%u,r%u,r%u)", Rd,Rm,Rs,Rn);
+    *mPC++ =    (cc<<28) | (1<<21) | (s<<20) |
+                (Rd<<16) | (Rn<<12) | (Rs<<8) | 0x90 | Rm;
+}
+void ARMAssembler::MUL(int cc, int s,
+        int Rd, int Rm, int Rs) {
+    if (Rd == Rm) { int t = Rm; Rm=Rs; Rs=t; }
+    LOG_FATAL_IF(Rd==Rm, "MUL(r%u,r%u,r%u)", Rd,Rm,Rs);
+    *mPC++ = (cc<<28) | (s<<20) | (Rd<<16) | (Rs<<8) | 0x90 | Rm;
+}
+void ARMAssembler::UMULL(int cc, int s,
+        int RdLo, int RdHi, int Rm, int Rs) {
+    LOG_FATAL_IF(RdLo==Rm || RdHi==Rm || RdLo==RdHi,
+                        "UMULL(r%u,r%u,r%u,r%u)", RdLo,RdHi,Rm,Rs);
+    *mPC++ =    (cc<<28) | (1<<23) | (s<<20) |
+                (RdHi<<16) | (RdLo<<12) | (Rs<<8) | 0x90 | Rm;
+}
+void ARMAssembler::UMUAL(int cc, int s,
+        int RdLo, int RdHi, int Rm, int Rs) {
+    LOG_FATAL_IF(RdLo==Rm || RdHi==Rm || RdLo==RdHi,
+                        "UMUAL(r%u,r%u,r%u,r%u)", RdLo,RdHi,Rm,Rs);
+    *mPC++ =    (cc<<28) | (1<<23) | (1<<21) | (s<<20) |
+                (RdHi<<16) | (RdLo<<12) | (Rs<<8) | 0x90 | Rm;
+}
+void ARMAssembler::SMULL(int cc, int s,
+        int RdLo, int RdHi, int Rm, int Rs) {
+    LOG_FATAL_IF(RdLo==Rm || RdHi==Rm || RdLo==RdHi,
+                        "SMULL(r%u,r%u,r%u,r%u)", RdLo,RdHi,Rm,Rs);
+    *mPC++ =    (cc<<28) | (1<<23) | (1<<22) | (s<<20) |
+                (RdHi<<16) | (RdLo<<12) | (Rs<<8) | 0x90 | Rm;
+}
+void ARMAssembler::SMUAL(int cc, int s,
+        int RdLo, int RdHi, int Rm, int Rs) {
+    LOG_FATAL_IF(RdLo==Rm || RdHi==Rm || RdLo==RdHi,
+                        "SMUAL(r%u,r%u,r%u,r%u)", RdLo,RdHi,Rm,Rs);
+    *mPC++ =    (cc<<28) | (1<<23) | (1<<22) | (1<<21) | (s<<20) |
+                (RdHi<<16) | (RdLo<<12) | (Rs<<8) | 0x90 | Rm;
+}
+
+#if 0
+#pragma mark -
+#pragma mark Branches...
+#endif
+
+// branches...
+void ARMAssembler::B(int cc, uint32_t* pc)
+{
+    int32_t offset = int32_t(pc - (mPC+2));
+    *mPC++ = (cc<<28) | (0xA<<24) | (offset & 0xFFFFFF);
+}
+
+void ARMAssembler::BL(int cc, uint32_t* pc)
+{
+    int32_t offset = int32_t(pc - (mPC+2));
+    *mPC++ = (cc<<28) | (0xB<<24) | (offset & 0xFFFFFF);
+}
+
+void ARMAssembler::BX(int cc, int Rn)
+{
+    *mPC++ = (cc<<28) | 0x12FFF10 | Rn;
+}
+
+#if 0
+#pragma mark -
+#pragma mark Data Transfer...
+#endif
+
+// data transfert...
+void ARMAssembler::LDR(int cc, int Rd, int Rn, uint32_t offset) {
+    *mPC++ = (cc<<28) | (1<<26) | (1<<20) | (Rn<<16) | (Rd<<12) | offset;
+}
+void ARMAssembler::LDRB(int cc, int Rd, int Rn, uint32_t offset) {
+    *mPC++ = (cc<<28) | (1<<26) | (1<<22) | (1<<20) | (Rn<<16) | (Rd<<12) | offset;
+}
+void ARMAssembler::STR(int cc, int Rd, int Rn, uint32_t offset) {
+    *mPC++ = (cc<<28) | (1<<26) | (Rn<<16) | (Rd<<12) | offset;
+}
+void ARMAssembler::STRB(int cc, int Rd, int Rn, uint32_t offset) {
+    *mPC++ = (cc<<28) | (1<<26) | (1<<22) | (Rn<<16) | (Rd<<12) | offset;
+}
+
+void ARMAssembler::LDRH(int cc, int Rd, int Rn, uint32_t offset) {
+    *mPC++ = (cc<<28) | (1<<20) | (Rn<<16) | (Rd<<12) | 0xB0 | offset;
+}
+void ARMAssembler::LDRSB(int cc, int Rd, int Rn, uint32_t offset) {
+    *mPC++ = (cc<<28) | (1<<20) | (Rn<<16) | (Rd<<12) | 0xD0 | offset;
+}
+void ARMAssembler::LDRSH(int cc, int Rd, int Rn, uint32_t offset) {
+    *mPC++ = (cc<<28) | (1<<20) | (Rn<<16) | (Rd<<12) | 0xF0 | offset;
+}
+void ARMAssembler::STRH(int cc, int Rd, int Rn, uint32_t offset) {
+    *mPC++ = (cc<<28) | (Rn<<16) | (Rd<<12) | 0xB0 | offset;
+}
+
+#if 0
+#pragma mark -
+#pragma mark Block Data Transfer...
+#endif
+
+// block data transfer...
+void ARMAssembler::LDM(int cc, int dir,
+        int Rn, int W, uint32_t reg_list)
+{   //                    ED FD EA FA      IB IA DB DA
+    const uint8_t P[8] = { 1, 0, 1, 0,      1, 0, 1, 0 };
+    const uint8_t U[8] = { 1, 1, 0, 0,      1, 1, 0, 0 };
+    *mPC++ = (cc<<28) | (4<<25) | (uint32_t(P[dir])<<24) |
+            (uint32_t(U[dir])<<23) | (1<<20) | (W<<21) | (Rn<<16) | reg_list;
+}
+
+void ARMAssembler::STM(int cc, int dir,
+        int Rn, int W, uint32_t reg_list)
+{   //                    ED FD EA FA      IB IA DB DA
+    const uint8_t P[8] = { 0, 1, 0, 1,      1, 0, 1, 0 };
+    const uint8_t U[8] = { 0, 0, 1, 1,      1, 1, 0, 0 };
+    *mPC++ = (cc<<28) | (4<<25) | (uint32_t(P[dir])<<24) |
+            (uint32_t(U[dir])<<23) | (0<<20) | (W<<21) | (Rn<<16) | reg_list;
+}
+
+#if 0
+#pragma mark -
+#pragma mark Special...
+#endif
+
+// special...
+void ARMAssembler::SWP(int cc, int Rn, int Rd, int Rm) {
+    *mPC++ = (cc<<28) | (2<<23) | (Rn<<16) | (Rd << 12) | 0x90 | Rm;
+}
+void ARMAssembler::SWPB(int cc, int Rn, int Rd, int Rm) {
+    *mPC++ = (cc<<28) | (2<<23) | (1<<22) | (Rn<<16) | (Rd << 12) | 0x90 | Rm;
+}
+void ARMAssembler::SWI(int cc, uint32_t comment) {
+    *mPC++ = (cc<<28) | (0xF<<24) | comment;
+}
+
+#if 0
+#pragma mark -
+#pragma mark DSP instructions...
+#endif
+
+// DSP instructions...
+void ARMAssembler::PLD(int Rn, uint32_t offset) {
+    LOG_ALWAYS_FATAL_IF(!((offset&(1<<24)) && !(offset&(1<<21))),
+                        "PLD only P=1, W=0");
+    *mPC++ = 0xF550F000 | (Rn<<16) | offset;
+}
+
+void ARMAssembler::CLZ(int cc, int Rd, int Rm)
+{
+    *mPC++ = (cc<<28) | 0x16F0F10| (Rd<<12) | Rm;
+}
+
+void ARMAssembler::QADD(int cc,  int Rd, int Rm, int Rn)
+{
+    *mPC++ = (cc<<28) | 0x1000050 | (Rn<<16) | (Rd<<12) | Rm;
+}
+
+void ARMAssembler::QDADD(int cc,  int Rd, int Rm, int Rn)
+{
+    *mPC++ = (cc<<28) | 0x1400050 | (Rn<<16) | (Rd<<12) | Rm;
+}
+
+void ARMAssembler::QSUB(int cc,  int Rd, int Rm, int Rn)
+{
+    *mPC++ = (cc<<28) | 0x1200050 | (Rn<<16) | (Rd<<12) | Rm;
+}
+
+void ARMAssembler::QDSUB(int cc,  int Rd, int Rm, int Rn)
+{
+    *mPC++ = (cc<<28) | 0x1600050 | (Rn<<16) | (Rd<<12) | Rm;
+}
+
+void ARMAssembler::SMUL(int cc, int xy,
+                int Rd, int Rm, int Rs)
+{
+    *mPC++ = (cc<<28) | 0x1600080 | (Rd<<16) | (Rs<<8) | (xy<<4) | Rm;
+}
+
+void ARMAssembler::SMULW(int cc, int y,
+                int Rd, int Rm, int Rs)
+{
+    *mPC++ = (cc<<28) | 0x12000A0 | (Rd<<16) | (Rs<<8) | (y<<4) | Rm;
+}
+
+void ARMAssembler::SMLA(int cc, int xy,
+                int Rd, int Rm, int Rs, int Rn)
+{
+    *mPC++ = (cc<<28) | 0x1000080 | (Rd<<16) | (Rn<<12) | (Rs<<8) | (xy<<4) | Rm;
+}
+
+void ARMAssembler::SMLAL(int cc, int xy,
+                int RdHi, int RdLo, int Rs, int Rm)
+{
+    *mPC++ = (cc<<28) | 0x1400080 | (RdHi<<16) | (RdLo<<12) | (Rs<<8) | (xy<<4) | Rm;
+}
+
+void ARMAssembler::SMLAW(int cc, int y,
+                int Rd, int Rm, int Rs, int Rn)
+{
+    *mPC++ = (cc<<28) | 0x1200080 | (Rd<<16) | (Rn<<12) | (Rs<<8) | (y<<4) | Rm;
+}
+
+#if 0
+#pragma mark -
+#pragma mark Byte/half word extract and extend (ARMv6+ only)...
+#endif
+
+void ARMAssembler::UXTB16(int cc, int Rd, int Rm, int rotate)
+{
+    *mPC++ = (cc<<28) | 0x6CF0070 | (Rd<<12) | ((rotate >> 3) << 10) | Rm;
+}
+#if 0
+#pragma mark -
+#pragma mark Bit manipulation (ARMv7+ only)...
+#endif
+
+// Bit manipulation (ARMv7+ only)...
+void ARMAssembler::UBFX(int cc, int Rd, int Rn, int lsb, int width)
+{
+    *mPC++ = (cc<<28) | 0x7E00000 | ((width-1)<<16) | (Rd<<12) | (lsb<<7) | 0x50 | Rn;
+}
+
+#if 0
+#pragma mark -
+#pragma mark Addressing modes...
+#endif
+
+int ARMAssembler::buildImmediate(
+        uint32_t immediate, uint32_t& rot, uint32_t& imm)
+{
+    rot = 0;
+    imm = immediate;
+    if (imm > 0x7F) { // skip the easy cases
+        while (!(imm&3)  || (imm&0xFC000000)) {
+            uint32_t newval;
+            newval = imm >> 2;
+            newval |= (imm&3) << 30;
+            imm = newval;
+            rot += 2;
+            if (rot == 32) {
+                rot = 0;
+                break;
+            }
+        }
+    }
+    rot = (16 - (rot>>1)) & 0xF;
+
+    if (imm>=0x100)
+        return -EINVAL;
+
+    if (((imm>>(rot<<1)) | (imm<<(32-(rot<<1)))) != immediate)
+        return -1;
+
+    return 0;
+}
+
+// shifters...
+
+bool ARMAssembler::isValidImmediate(uint32_t immediate)
+{
+    uint32_t rot, imm;
+    return buildImmediate(immediate, rot, imm) == 0;
+}
+
+uint32_t ARMAssembler::imm(uint32_t immediate)
+{
+    uint32_t rot, imm;
+    int err = buildImmediate(immediate, rot, imm);
+
+    LOG_ALWAYS_FATAL_IF(err==-EINVAL,
+                        "immediate %08x cannot be encoded",
+                        immediate);
+
+    LOG_ALWAYS_FATAL_IF(err,
+                        "immediate (%08x) encoding bogus!",
+                        immediate);
+
+    return (1<<25) | (rot<<8) | imm;
+}
+
+uint32_t ARMAssembler::reg_imm(int Rm, int type, uint32_t shift)
+{
+    return ((shift&0x1F)<<7) | ((type&0x3)<<5) | (Rm&0xF);
+}
+
+uint32_t ARMAssembler::reg_rrx(int Rm)
+{
+    return (ROR<<5) | (Rm&0xF);
+}
+
+uint32_t ARMAssembler::reg_reg(int Rm, int type, int Rs)
+{
+    return ((Rs&0xF)<<8) | ((type&0x3)<<5) | (1<<4) | (Rm&0xF);
+}
+
+// addressing modes...
+// LDR(B)/STR(B)/PLD (immediate and Rm can be negative, which indicate U=0)
+uint32_t ARMAssembler::immed12_pre(int32_t immed12, int W)
+{
+    LOG_ALWAYS_FATAL_IF(abs(immed12) >= 0x800,
+                        "LDR(B)/STR(B)/PLD immediate too big (%08x)",
+                        immed12);
+    return (1<<24) | (((uint32_t(immed12)>>31)^1)<<23) |
+            ((W&1)<<21) | (abs(immed12)&0x7FF);
+}
+
+uint32_t ARMAssembler::immed12_post(int32_t immed12)
+{
+    LOG_ALWAYS_FATAL_IF(abs(immed12) >= 0x800,
+                        "LDR(B)/STR(B)/PLD immediate too big (%08x)",
+                        immed12);
+
+    return (((uint32_t(immed12)>>31)^1)<<23) | (abs(immed12)&0x7FF);
+}
+
+uint32_t ARMAssembler::reg_scale_pre(int Rm, int type,
+        uint32_t shift, int W)
+{
+    return  (1<<25) | (1<<24) |
+            (((uint32_t(Rm)>>31)^1)<<23) | ((W&1)<<21) |
+            reg_imm(abs(Rm), type, shift);
+}
+
+uint32_t ARMAssembler::reg_scale_post(int Rm, int type, uint32_t shift)
+{
+    return (1<<25) | (((uint32_t(Rm)>>31)^1)<<23) | reg_imm(abs(Rm), type, shift);
+}
+
+// LDRH/LDRSB/LDRSH/STRH (immediate and Rm can be negative, which indicate U=0)
+uint32_t ARMAssembler::immed8_pre(int32_t immed8, int W)
+{
+    uint32_t offset = abs(immed8);
+
+    LOG_ALWAYS_FATAL_IF(abs(immed8) >= 0x100,
+                        "LDRH/LDRSB/LDRSH/STRH immediate too big (%08x)",
+                        immed8);
+
+    return  (1<<24) | (1<<22) | (((uint32_t(immed8)>>31)^1)<<23) |
+            ((W&1)<<21) | (((offset&0xF0)<<4)|(offset&0xF));
+}
+
+uint32_t ARMAssembler::immed8_post(int32_t immed8)
+{
+    uint32_t offset = abs(immed8);
+
+    LOG_ALWAYS_FATAL_IF(abs(immed8) >= 0x100,
+                        "LDRH/LDRSB/LDRSH/STRH immediate too big (%08x)",
+                        immed8);
+
+    return (1<<22) | (((uint32_t(immed8)>>31)^1)<<23) |
+            (((offset&0xF0)<<4) | (offset&0xF));
+}
+
+uint32_t ARMAssembler::reg_pre(int Rm, int W)
+{
+    return (1<<24) | (((uint32_t(Rm)>>31)^1)<<23) | ((W&1)<<21) | (abs(Rm)&0xF);
+}
+
+uint32_t ARMAssembler::reg_post(int Rm)
+{
+    return (((uint32_t(Rm)>>31)^1)<<23) | (abs(Rm)&0xF);
+}
+
+}; // namespace android
diff --git a/libpixelflinger/codeflinger/ARMAssembler.h b/libpixelflinger/codeflinger/ARMAssembler.h
new file mode 100644
index 0000000..76acf7e
--- /dev/null
+++ b/libpixelflinger/codeflinger/ARMAssembler.h
@@ -0,0 +1,187 @@
+/* libs/pixelflinger/codeflinger/ARMAssembler.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+#ifndef ANDROID_ARMASSEMBLER_H
+#define ANDROID_ARMASSEMBLER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "tinyutils/smartpointer.h"
+#include "utils/Vector.h"
+#include "utils/KeyedVector.h"
+
+#include "ARMAssemblerInterface.h"
+#include "CodeCache.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class ARMAssembler : public ARMAssemblerInterface
+{
+public:
+    explicit    ARMAssembler(const sp<Assembly>& assembly);
+    virtual     ~ARMAssembler();
+
+    uint32_t*   base() const;
+    uint32_t*   pc() const;
+
+
+    void        disassemble(const char* name);
+
+    // ------------------------------------------------------------------------
+    // ARMAssemblerInterface...
+    // ------------------------------------------------------------------------
+
+    virtual void    reset();
+
+    virtual int     generate(const char* name);
+    virtual int     getCodegenArch();
+
+    virtual void    prolog();
+    virtual void    epilog(uint32_t touched);
+    virtual void    comment(const char* string);
+
+
+    // -----------------------------------------------------------------------
+    // shifters and addressing modes
+    // -----------------------------------------------------------------------
+
+    // shifters...
+    virtual bool        isValidImmediate(uint32_t immed);
+    virtual int         buildImmediate(uint32_t i, uint32_t& rot, uint32_t& imm);
+
+    virtual uint32_t    imm(uint32_t immediate);
+    virtual uint32_t    reg_imm(int Rm, int type, uint32_t shift);
+    virtual uint32_t    reg_rrx(int Rm);
+    virtual uint32_t    reg_reg(int Rm, int type, int Rs);
+
+    // addressing modes...
+    // LDR(B)/STR(B)/PLD
+    // (immediate and Rm can be negative, which indicates U=0)
+    virtual uint32_t    immed12_pre(int32_t immed12, int W=0);
+    virtual uint32_t    immed12_post(int32_t immed12);
+    virtual uint32_t    reg_scale_pre(int Rm, int type=0, uint32_t shift=0, int W=0);
+    virtual uint32_t    reg_scale_post(int Rm, int type=0, uint32_t shift=0);
+
+    // LDRH/LDRSB/LDRSH/STRH
+    // (immediate and Rm can be negative, which indicates U=0)
+    virtual uint32_t    immed8_pre(int32_t immed8, int W=0);
+    virtual uint32_t    immed8_post(int32_t immed8);
+    virtual uint32_t    reg_pre(int Rm, int W=0);
+    virtual uint32_t    reg_post(int Rm);
+
+
+    virtual void    dataProcessing(int opcode, int cc, int s,
+                                int Rd, int Rn,
+                                uint32_t Op2);
+    virtual void MLA(int cc, int s,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void MUL(int cc, int s,
+                int Rd, int Rm, int Rs);
+    virtual void UMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void UMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void SMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void SMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+
+    virtual void B(int cc, uint32_t* pc);
+    virtual void BL(int cc, uint32_t* pc);
+    virtual void BX(int cc, int Rn);
+    virtual void label(const char* theLabel);
+    virtual void B(int cc, const char* label);
+    virtual void BL(int cc, const char* label);
+
+    virtual uint32_t* pcForLabel(const char* label);
+
+    virtual void LDR (int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void LDRB(int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void STR (int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void STRB(int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void LDRH (int cc, int Rd,
+                int Rn, uint32_t offset = __immed8_pre(0));
+    virtual void LDRSB(int cc, int Rd, 
+                int Rn, uint32_t offset = __immed8_pre(0));
+    virtual void LDRSH(int cc, int Rd,
+                int Rn, uint32_t offset = __immed8_pre(0));
+    virtual void STRH (int cc, int Rd,
+                int Rn, uint32_t offset = __immed8_pre(0));
+
+
+    virtual void LDM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list);
+    virtual void STM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list);
+
+    virtual void SWP(int cc, int Rn, int Rd, int Rm);
+    virtual void SWPB(int cc, int Rn, int Rd, int Rm);
+    virtual void SWI(int cc, uint32_t comment);
+
+    virtual void PLD(int Rn, uint32_t offset);
+    virtual void CLZ(int cc, int Rd, int Rm);
+    virtual void QADD(int cc, int Rd, int Rm, int Rn);
+    virtual void QDADD(int cc, int Rd, int Rm, int Rn);
+    virtual void QSUB(int cc, int Rd, int Rm, int Rn);
+    virtual void QDSUB(int cc, int Rd, int Rm, int Rn);
+    virtual void SMUL(int cc, int xy,
+                int Rd, int Rm, int Rs);
+    virtual void SMULW(int cc, int y,
+                int Rd, int Rm, int Rs);
+    virtual void SMLA(int cc, int xy,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void SMLAL(int cc, int xy,
+                int RdHi, int RdLo, int Rs, int Rm);
+    virtual void SMLAW(int cc, int y,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void UXTB16(int cc, int Rd, int Rm, int rotate);
+    virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width);
+
+private:
+                ARMAssembler(const ARMAssembler& rhs);
+                ARMAssembler& operator = (const ARMAssembler& rhs);
+
+    sp<Assembly>    mAssembly;
+    uint32_t*       mBase;
+    uint32_t*       mPC;
+    uint32_t*       mPrologPC;
+    int64_t         mDuration;
+    
+    struct branch_target_t {
+        inline branch_target_t() : label(0), pc(0) { }
+        inline branch_target_t(const char* l, uint32_t* p)
+            : label(l), pc(p) { }
+        const char* label;
+        uint32_t*   pc;
+    };
+    
+    Vector<branch_target_t>                 mBranchTargets;
+    KeyedVector< const char*, uint32_t* >   mLabels;
+    KeyedVector< uint32_t*, const char* >   mLabelsInverseMapping;
+    KeyedVector< uint32_t*, const char* >   mComments;
+};
+
+}; // namespace android
+
+#endif //ANDROID_ARMASSEMBLER_H
diff --git a/libpixelflinger/codeflinger/ARMAssemblerInterface.cpp b/libpixelflinger/codeflinger/ARMAssemblerInterface.cpp
new file mode 100644
index 0000000..c96cf4b
--- /dev/null
+++ b/libpixelflinger/codeflinger/ARMAssemblerInterface.cpp
@@ -0,0 +1,90 @@
+/* libs/pixelflinger/codeflinger/ARMAssemblerInterface.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+#define LOG_TAG "pixelflinger-code"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <log/log.h>
+
+#include "ARMAssemblerInterface.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+ARMAssemblerInterface::~ARMAssemblerInterface()
+{
+}
+
+// --------------------------------------------------------------------
+
+// The following two functions are static and used for initializers
+// in the original ARM code. The above versions (without __), are now
+// virtual, and can be overridden in the MIPS code. But since these are
+// needed at initialization time, they must be static. Not thrilled with
+// this implementation, but it works...
+
+uint32_t ARMAssemblerInterface::__immed12_pre(int32_t immed12, int W)
+{
+    LOG_ALWAYS_FATAL_IF(abs(immed12) >= 0x800,
+                        "LDR(B)/STR(B)/PLD immediate too big (%08x)",
+                        immed12);
+    return (1<<24) | (((uint32_t(immed12)>>31)^1)<<23) |
+            ((W&1)<<21) | (abs(immed12)&0x7FF);
+}
+
+uint32_t ARMAssemblerInterface::__immed8_pre(int32_t immed8, int W)
+{
+    uint32_t offset = abs(immed8);
+
+    LOG_ALWAYS_FATAL_IF(abs(immed8) >= 0x100,
+                        "LDRH/LDRSB/LDRSH/STRH immediate too big (%08x)",
+                        immed8);
+
+    return  (1<<24) | (1<<22) | (((uint32_t(immed8)>>31)^1)<<23) |
+            ((W&1)<<21) | (((offset&0xF0)<<4)|(offset&0xF));
+}
+
+// The following four functions are required for address manipulation
+// These are virtual functions, which can be overridden by architectures
+// that need special handling of address values (e.g. 64-bit arch)
+
+void ARMAssemblerInterface::ADDR_LDR(int cc, int Rd,
+     int Rn, uint32_t offset)
+{
+    LDR(cc, Rd, Rn, offset);
+}
+void ARMAssemblerInterface::ADDR_STR(int cc, int Rd,
+     int Rn, uint32_t offset)
+{
+    STR(cc, Rd, Rn, offset);
+}
+void ARMAssemblerInterface::ADDR_ADD(int cc, int s,
+     int Rd, int Rn, uint32_t Op2)
+{
+    dataProcessing(opADD, cc, s, Rd, Rn, Op2);
+}
+void ARMAssemblerInterface::ADDR_SUB(int cc, int s,
+     int Rd, int Rn, uint32_t Op2)
+{
+    dataProcessing(opSUB, cc, s, Rd, Rn, Op2);
+}
+}; // namespace android
+
diff --git a/libpixelflinger/codeflinger/ARMAssemblerInterface.h b/libpixelflinger/codeflinger/ARMAssemblerInterface.h
new file mode 100644
index 0000000..72935ac
--- /dev/null
+++ b/libpixelflinger/codeflinger/ARMAssemblerInterface.h
@@ -0,0 +1,349 @@
+/* libs/pixelflinger/codeflinger/ARMAssemblerInterface.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#ifndef ANDROID_ARMASSEMBLER_INTERFACE_H
+#define ANDROID_ARMASSEMBLER_INTERFACE_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class ARMAssemblerInterface
+{
+public:
+    virtual ~ARMAssemblerInterface();
+
+    enum {
+        EQ, NE, CS, CC, MI, PL, VS, VC, HI, LS, GE, LT, GT, LE, AL, NV,
+        HS = CS,
+        LO = CC
+    };
+    enum {
+        S = 1
+    };
+    enum {
+        LSL, LSR, ASR, ROR
+    };
+    enum {
+        ED, FD, EA, FA,
+        IB, IA, DB, DA
+    };
+    enum {
+        R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15,
+        SP = R13,
+        LR = R14,
+        PC = R15
+    };
+    enum {
+        #define LIST(rr) L##rr=1<<rr
+        LIST(R0), LIST(R1), LIST(R2), LIST(R3), LIST(R4), LIST(R5), LIST(R6),
+        LIST(R7), LIST(R8), LIST(R9), LIST(R10), LIST(R11), LIST(R12),
+        LIST(R13), LIST(R14), LIST(R15),
+        LIST(SP), LIST(LR), LIST(PC),
+        #undef LIST
+        LSAVED = LR4|LR5|LR6|LR7|LR8|LR9|LR10|LR11 | LLR
+    };
+
+    enum {
+        CODEGEN_ARCH_ARM = 1, CODEGEN_ARCH_MIPS, CODEGEN_ARCH_ARM64, CODEGEN_ARCH_MIPS64
+    };
+
+    // -----------------------------------------------------------------------
+    // shifters and addressing modes
+    // -----------------------------------------------------------------------
+
+    // these static versions are used for initializers on LDxx/STxx below
+    static uint32_t    __immed12_pre(int32_t immed12, int W=0);
+    static uint32_t    __immed8_pre(int32_t immed12, int W=0);
+
+    virtual bool        isValidImmediate(uint32_t immed) = 0;
+    virtual int         buildImmediate(uint32_t i, uint32_t& rot, uint32_t& imm) = 0;
+
+    virtual uint32_t    imm(uint32_t immediate) = 0;
+    virtual uint32_t    reg_imm(int Rm, int type, uint32_t shift) = 0;
+    virtual uint32_t    reg_rrx(int Rm) = 0;
+    virtual uint32_t    reg_reg(int Rm, int type, int Rs) = 0;
+
+    // addressing modes... 
+    // LDR(B)/STR(B)/PLD
+    // (immediate and Rm can be negative, which indicates U=0)
+    virtual uint32_t    immed12_pre(int32_t immed12, int W=0) = 0;
+    virtual uint32_t    immed12_post(int32_t immed12) = 0;
+    virtual uint32_t    reg_scale_pre(int Rm, int type=0, uint32_t shift=0, int W=0) = 0;
+    virtual uint32_t    reg_scale_post(int Rm, int type=0, uint32_t shift=0) = 0;
+
+    // LDRH/LDRSB/LDRSH/STRH
+    // (immediate and Rm can be negative, which indicates U=0)
+    virtual uint32_t    immed8_pre(int32_t immed8, int W=0) = 0;
+    virtual uint32_t    immed8_post(int32_t immed8) = 0;
+    virtual uint32_t    reg_pre(int Rm, int W=0) = 0;
+    virtual uint32_t    reg_post(int Rm) = 0;
+
+    // -----------------------------------------------------------------------
+    // basic instructions & code generation
+    // -----------------------------------------------------------------------
+
+    // generate the code
+    virtual void reset() = 0;
+    virtual int  generate(const char* name) = 0;
+    virtual void disassemble(const char* name) = 0;
+    virtual int  getCodegenArch() = 0;
+    
+    // construct prolog and epilog
+    virtual void prolog() = 0;
+    virtual void epilog(uint32_t touched) = 0;
+    virtual void comment(const char* string) = 0;
+
+    // data processing...
+    enum {
+        opAND, opEOR, opSUB, opRSB, opADD, opADC, opSBC, opRSC, 
+        opTST, opTEQ, opCMP, opCMN, opORR, opMOV, opBIC, opMVN,
+        opADD64, opSUB64
+    };
+
+    virtual void
+            dataProcessing( int opcode, int cc, int s,
+                            int Rd, int Rn,
+                            uint32_t Op2) = 0;
+    
+    // multiply...
+    virtual void MLA(int cc, int s,
+                int Rd, int Rm, int Rs, int Rn) = 0;
+    virtual void MUL(int cc, int s,
+                int Rd, int Rm, int Rs) = 0;
+    virtual void UMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs) = 0;
+    virtual void UMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs) = 0;
+    virtual void SMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs) = 0;
+    virtual void SMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs) = 0;
+
+    // branches...
+    virtual void B(int cc, uint32_t* pc) = 0;
+    virtual void BL(int cc, uint32_t* pc) = 0;
+    virtual void BX(int cc, int Rn) = 0;
+
+    virtual void label(const char* theLabel) = 0;
+    virtual void B(int cc, const char* label) = 0;
+    virtual void BL(int cc, const char* label) = 0;
+
+    // valid only after generate() has been called
+    virtual uint32_t* pcForLabel(const char* label) = 0;
+
+    // data transfer...
+    virtual void LDR (int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0)) = 0;
+    virtual void LDRB(int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0)) = 0;
+    virtual void STR (int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0)) = 0;
+    virtual void STRB(int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0)) = 0;
+
+    virtual void LDRH (int cc, int Rd,
+                int Rn, uint32_t offset = __immed8_pre(0)) = 0;
+    virtual void LDRSB(int cc, int Rd, 
+                int Rn, uint32_t offset = __immed8_pre(0)) = 0;
+    virtual void LDRSH(int cc, int Rd,
+                int Rn, uint32_t offset = __immed8_pre(0)) = 0;
+    virtual void STRH (int cc, int Rd,
+                int Rn, uint32_t offset = __immed8_pre(0)) = 0;
+
+    // block data transfer...
+    virtual void LDM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list) = 0;
+    virtual void STM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list) = 0;
+
+    // special...
+    virtual void SWP(int cc, int Rn, int Rd, int Rm) = 0;
+    virtual void SWPB(int cc, int Rn, int Rd, int Rm) = 0;
+    virtual void SWI(int cc, uint32_t comment) = 0;
+
+    // DSP instructions...
+    enum {
+        // B=0, T=1
+        //     yx
+        xyBB = 0, // 0000,
+        xyTB = 2, // 0010,
+        xyBT = 4, // 0100,
+        xyTT = 6, // 0110,
+        yB   = 0, // 0000,
+        yT   = 4, // 0100
+    };
+
+    virtual void PLD(int Rn, uint32_t offset) = 0;
+
+    virtual void CLZ(int cc, int Rd, int Rm) = 0;
+    
+    virtual void QADD(int cc, int Rd, int Rm, int Rn) = 0;
+    virtual void QDADD(int cc, int Rd, int Rm, int Rn) = 0;
+    virtual void QSUB(int cc, int Rd, int Rm, int Rn) = 0;
+    virtual void QDSUB(int cc, int Rd, int Rm, int Rn) = 0;
+    
+    virtual void SMUL(int cc, int xy,
+                int Rd, int Rm, int Rs) = 0;
+    virtual void SMULW(int cc, int y,
+                int Rd, int Rm, int Rs) = 0;
+    virtual void SMLA(int cc, int xy,
+                int Rd, int Rm, int Rs, int Rn) = 0;
+    virtual void SMLAL(int cc, int xy,
+                int RdHi, int RdLo, int Rs, int Rm) = 0;
+    virtual void SMLAW(int cc, int y,
+                int Rd, int Rm, int Rs, int Rn) = 0;
+
+    // byte/half word extract...
+    virtual void UXTB16(int cc, int Rd, int Rm, int rotate) = 0;
+
+    // bit manipulation...
+    virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width) = 0;
+
+    // -----------------------------------------------------------------------
+    // convenience...
+    // -----------------------------------------------------------------------
+    inline void
+    ADC(int cc, int s, int Rd, int Rn, uint32_t Op2) {
+        dataProcessing(opADC, cc, s, Rd, Rn, Op2);
+    }
+    inline void
+    ADD(int cc, int s, int Rd, int Rn, uint32_t Op2) {
+        dataProcessing(opADD, cc, s, Rd, Rn, Op2);
+    }
+    inline void
+    AND(int cc, int s, int Rd, int Rn, uint32_t Op2) {
+        dataProcessing(opAND, cc, s, Rd, Rn, Op2);
+    }
+    inline void
+    BIC(int cc, int s, int Rd, int Rn, uint32_t Op2) {
+        dataProcessing(opBIC, cc, s, Rd, Rn, Op2);
+    }
+    inline void
+    EOR(int cc, int s, int Rd, int Rn, uint32_t Op2) {
+        dataProcessing(opEOR, cc, s, Rd, Rn, Op2);
+    }
+    inline void
+    MOV(int cc, int s, int Rd, uint32_t Op2) {
+        dataProcessing(opMOV, cc, s, Rd, 0, Op2);
+    }
+    inline void
+    MVN(int cc, int s, int Rd, uint32_t Op2) {
+        dataProcessing(opMVN, cc, s, Rd, 0, Op2);
+    }
+    inline void
+    ORR(int cc, int s, int Rd, int Rn, uint32_t Op2) {
+        dataProcessing(opORR, cc, s, Rd, Rn, Op2);
+    }
+    inline void
+    RSB(int cc, int s, int Rd, int Rn, uint32_t Op2) {
+        dataProcessing(opRSB, cc, s, Rd, Rn, Op2);
+    }
+    inline void
+    RSC(int cc, int s, int Rd, int Rn, uint32_t Op2) {
+        dataProcessing(opRSC, cc, s, Rd, Rn, Op2);
+    }
+    inline void
+    SBC(int cc, int s, int Rd, int Rn, uint32_t Op2) {
+        dataProcessing(opSBC, cc, s, Rd, Rn, Op2);
+    }
+    inline void
+    SUB(int cc, int s, int Rd, int Rn, uint32_t Op2) {
+        dataProcessing(opSUB, cc, s, Rd, Rn, Op2);
+    }
+    inline void
+    TEQ(int cc, int Rn, uint32_t Op2) {
+        dataProcessing(opTEQ, cc, 1, 0, Rn, Op2);
+    }
+    inline void
+    TST(int cc, int Rn, uint32_t Op2) {
+        dataProcessing(opTST, cc, 1, 0, Rn, Op2);
+    }
+    inline void
+    CMP(int cc, int Rn, uint32_t Op2) {
+        dataProcessing(opCMP, cc, 1, 0, Rn, Op2);
+    }
+    inline void
+    CMN(int cc, int Rn, uint32_t Op2) {
+        dataProcessing(opCMN, cc, 1, 0, Rn, Op2);
+    }
+
+    inline void SMULBB(int cc, int Rd, int Rm, int Rs) {
+        SMUL(cc, xyBB, Rd, Rm, Rs);    }
+    inline void SMULTB(int cc, int Rd, int Rm, int Rs) {
+        SMUL(cc, xyTB, Rd, Rm, Rs);    }
+    inline void SMULBT(int cc, int Rd, int Rm, int Rs) {
+        SMUL(cc, xyBT, Rd, Rm, Rs);    }
+    inline void SMULTT(int cc, int Rd, int Rm, int Rs) {
+        SMUL(cc, xyTT, Rd, Rm, Rs);    }
+
+    inline void SMULWB(int cc, int Rd, int Rm, int Rs) {
+        SMULW(cc, yB, Rd, Rm, Rs);    }
+    inline void SMULWT(int cc, int Rd, int Rm, int Rs) {
+        SMULW(cc, yT, Rd, Rm, Rs);    }
+
+    inline void
+    SMLABB(int cc, int Rd, int Rm, int Rs, int Rn) {
+        SMLA(cc, xyBB, Rd, Rm, Rs, Rn);    }
+    inline void
+    SMLATB(int cc, int Rd, int Rm, int Rs, int Rn) {
+        SMLA(cc, xyTB, Rd, Rm, Rs, Rn);    }
+    inline void
+    SMLABT(int cc, int Rd, int Rm, int Rs, int Rn) {
+        SMLA(cc, xyBT, Rd, Rm, Rs, Rn);    }
+    inline void
+    SMLATT(int cc, int Rd, int Rm, int Rs, int Rn) {
+        SMLA(cc, xyTT, Rd, Rm, Rs, Rn);    }
+
+    inline void
+    SMLALBB(int cc, int RdHi, int RdLo, int Rs, int Rm) {
+        SMLAL(cc, xyBB, RdHi, RdLo, Rs, Rm);    }
+    inline void
+    SMLALTB(int cc, int RdHi, int RdLo, int Rs, int Rm) {
+        SMLAL(cc, xyTB, RdHi, RdLo, Rs, Rm);    }
+    inline void
+    SMLALBT(int cc, int RdHi, int RdLo, int Rs, int Rm) {
+        SMLAL(cc, xyBT, RdHi, RdLo, Rs, Rm);    }
+    inline void
+    SMLALTT(int cc, int RdHi, int RdLo, int Rs, int Rm) {
+        SMLAL(cc, xyTT, RdHi, RdLo, Rs, Rm);    }
+
+    inline void
+    SMLAWB(int cc, int Rd, int Rm, int Rs, int Rn) {
+        SMLAW(cc, yB, Rd, Rm, Rs, Rn);    }
+    inline void
+    SMLAWT(int cc, int Rd, int Rm, int Rs, int Rn) {
+        SMLAW(cc, yT, Rd, Rm, Rs, Rn);    }
+
+    // Address loading/storing/manipulation
+    virtual void ADDR_LDR(int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void ADDR_STR (int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void ADDR_ADD(int cc, int s, int Rd,
+                int Rn, uint32_t Op2);
+    virtual void ADDR_SUB(int cc, int s, int Rd,
+                int Rn, uint32_t Op2);
+};
+
+}; // namespace android
+
+#endif //ANDROID_ARMASSEMBLER_INTERFACE_H
diff --git a/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp b/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp
new file mode 100644
index 0000000..816de48
--- /dev/null
+++ b/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp
@@ -0,0 +1,311 @@
+/* libs/pixelflinger/codeflinger/ARMAssemblerProxy.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "ARMAssemblerProxy.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+ARMAssemblerProxy::ARMAssemblerProxy()
+    : mTarget(0)
+{
+}
+
+ARMAssemblerProxy::ARMAssemblerProxy(ARMAssemblerInterface* target)
+    : mTarget(target)
+{
+}
+
+ARMAssemblerProxy::~ARMAssemblerProxy()
+{
+    delete mTarget;
+}
+
+void ARMAssemblerProxy::setTarget(ARMAssemblerInterface* target)
+{
+    delete mTarget;
+    mTarget = target;
+}
+
+void ARMAssemblerProxy::reset() {
+    mTarget->reset();
+}
+int ARMAssemblerProxy::generate(const char* name) {
+    return mTarget->generate(name);
+}
+void ARMAssemblerProxy::disassemble(const char* name) {
+    return mTarget->disassemble(name);
+}
+int ARMAssemblerProxy::getCodegenArch()
+{
+    return mTarget->getCodegenArch();
+}
+void ARMAssemblerProxy::prolog() {
+    mTarget->prolog();
+}
+void ARMAssemblerProxy::epilog(uint32_t touched) {
+    mTarget->epilog(touched);
+}
+void ARMAssemblerProxy::comment(const char* string) {
+    mTarget->comment(string);
+}
+
+
+
+// addressing modes
+
+bool ARMAssemblerProxy::isValidImmediate(uint32_t immed)
+{
+    return mTarget->isValidImmediate(immed);
+}
+
+int ARMAssemblerProxy::buildImmediate(uint32_t i, uint32_t& rot, uint32_t& imm)
+{
+    return mTarget->buildImmediate(i, rot, imm);
+}
+
+
+
+uint32_t ARMAssemblerProxy::imm(uint32_t immediate)
+{
+    return mTarget->imm(immediate);
+}
+
+uint32_t ARMAssemblerProxy::reg_imm(int Rm, int type, uint32_t shift)
+{
+    return mTarget->reg_imm(Rm, type, shift);
+}
+
+uint32_t ARMAssemblerProxy::reg_rrx(int Rm)
+{
+    return mTarget->reg_rrx(Rm);
+}
+
+uint32_t ARMAssemblerProxy::reg_reg(int Rm, int type, int Rs)
+{
+    return mTarget->reg_reg(Rm, type, Rs);
+}
+
+
+// addressing modes...
+// LDR(B)/STR(B)/PLD
+// (immediate and Rm can be negative, which indicates U=0)
+uint32_t ARMAssemblerProxy::immed12_pre(int32_t immed12, int W)
+{
+    return mTarget->immed12_pre(immed12, W);
+}
+
+uint32_t ARMAssemblerProxy::immed12_post(int32_t immed12)
+{
+    return mTarget->immed12_post(immed12);
+}
+
+uint32_t ARMAssemblerProxy::reg_scale_pre(int Rm, int type, uint32_t shift, int W)
+{
+    return mTarget->reg_scale_pre(Rm, type, shift, W);
+}
+
+uint32_t ARMAssemblerProxy::reg_scale_post(int Rm, int type, uint32_t shift)
+{
+    return mTarget->reg_scale_post(Rm, type, shift);
+}
+
+
+// LDRH/LDRSB/LDRSH/STRH
+// (immediate and Rm can be negative, which indicates U=0)
+uint32_t ARMAssemblerProxy::immed8_pre(int32_t immed8, int W)
+{
+    return mTarget->immed8_pre(immed8, W);
+}
+
+uint32_t ARMAssemblerProxy::immed8_post(int32_t immed8)
+{
+    return mTarget->immed8_post(immed8);
+}
+
+uint32_t ARMAssemblerProxy::reg_pre(int Rm, int W)
+{
+    return mTarget->reg_pre(Rm, W);
+}
+
+uint32_t ARMAssemblerProxy::reg_post(int Rm)
+{
+    return mTarget->reg_post(Rm);
+}
+
+
+//------------------------------------------------------------------------
+
+
+
+void ARMAssemblerProxy::dataProcessing( int opcode, int cc, int s,
+                                        int Rd, int Rn, uint32_t Op2)
+{
+    mTarget->dataProcessing(opcode, cc, s, Rd, Rn, Op2);
+}
+
+void ARMAssemblerProxy::MLA(int cc, int s, int Rd, int Rm, int Rs, int Rn) {
+    mTarget->MLA(cc, s, Rd, Rm, Rs, Rn);
+}
+void ARMAssemblerProxy::MUL(int cc, int s, int Rd, int Rm, int Rs) {
+    mTarget->MUL(cc, s, Rd, Rm, Rs);
+}
+void ARMAssemblerProxy::UMULL(int cc, int s,
+            int RdLo, int RdHi, int Rm, int Rs) {
+    mTarget->UMULL(cc, s, RdLo, RdHi, Rm, Rs); 
+}
+void ARMAssemblerProxy::UMUAL(int cc, int s,
+            int RdLo, int RdHi, int Rm, int Rs) {
+    mTarget->UMUAL(cc, s, RdLo, RdHi, Rm, Rs); 
+}
+void ARMAssemblerProxy::SMULL(int cc, int s,
+            int RdLo, int RdHi, int Rm, int Rs) {
+    mTarget->SMULL(cc, s, RdLo, RdHi, Rm, Rs); 
+}
+void ARMAssemblerProxy::SMUAL(int cc, int s,
+            int RdLo, int RdHi, int Rm, int Rs) {
+    mTarget->SMUAL(cc, s, RdLo, RdHi, Rm, Rs); 
+}
+
+void ARMAssemblerProxy::B(int cc, uint32_t* pc) {
+    mTarget->B(cc, pc); 
+}
+void ARMAssemblerProxy::BL(int cc, uint32_t* pc) {
+    mTarget->BL(cc, pc); 
+}
+void ARMAssemblerProxy::BX(int cc, int Rn) {
+    mTarget->BX(cc, Rn); 
+}
+void ARMAssemblerProxy::label(const char* theLabel) {
+    mTarget->label(theLabel);
+}
+void ARMAssemblerProxy::B(int cc, const char* label) {
+    mTarget->B(cc, label);
+}
+void ARMAssemblerProxy::BL(int cc, const char* label) {
+    mTarget->BL(cc, label);
+}
+
+uint32_t* ARMAssemblerProxy::pcForLabel(const char* label) {
+    return mTarget->pcForLabel(label);
+}
+
+void ARMAssemblerProxy::LDR(int cc, int Rd, int Rn, uint32_t offset) {
+    mTarget->LDR(cc, Rd, Rn, offset);
+}
+void ARMAssemblerProxy::LDRB(int cc, int Rd, int Rn, uint32_t offset) {
+    mTarget->LDRB(cc, Rd, Rn, offset);
+}
+void ARMAssemblerProxy::STR(int cc, int Rd, int Rn, uint32_t offset) {
+    mTarget->STR(cc, Rd, Rn, offset);
+}
+void ARMAssemblerProxy::STRB(int cc, int Rd, int Rn, uint32_t offset) {
+    mTarget->STRB(cc, Rd, Rn, offset);
+}
+void ARMAssemblerProxy::LDRH(int cc, int Rd, int Rn, uint32_t offset) {
+    mTarget->LDRH(cc, Rd, Rn, offset);
+}
+void ARMAssemblerProxy::LDRSB(int cc, int Rd, int Rn, uint32_t offset) {
+    mTarget->LDRSB(cc, Rd, Rn, offset);
+}
+void ARMAssemblerProxy::LDRSH(int cc, int Rd, int Rn, uint32_t offset) {
+    mTarget->LDRSH(cc, Rd, Rn, offset);
+}
+void ARMAssemblerProxy::STRH(int cc, int Rd, int Rn, uint32_t offset) {
+    mTarget->STRH(cc, Rd, Rn, offset);
+}
+void ARMAssemblerProxy::LDM(int cc, int dir, int Rn, int W, uint32_t reg_list) {
+    mTarget->LDM(cc, dir, Rn, W, reg_list);
+}
+void ARMAssemblerProxy::STM(int cc, int dir, int Rn, int W, uint32_t reg_list) {
+    mTarget->STM(cc, dir, Rn, W, reg_list);
+}
+
+void ARMAssemblerProxy::SWP(int cc, int Rn, int Rd, int Rm) {
+    mTarget->SWP(cc, Rn, Rd, Rm);
+}
+void ARMAssemblerProxy::SWPB(int cc, int Rn, int Rd, int Rm) {
+    mTarget->SWPB(cc, Rn, Rd, Rm);
+}
+void ARMAssemblerProxy::SWI(int cc, uint32_t comment) {
+    mTarget->SWI(cc, comment);
+}
+
+
+void ARMAssemblerProxy::PLD(int Rn, uint32_t offset) {
+    mTarget->PLD(Rn, offset);
+}
+void ARMAssemblerProxy::CLZ(int cc, int Rd, int Rm) {
+    mTarget->CLZ(cc, Rd, Rm);
+}
+void ARMAssemblerProxy::QADD(int cc, int Rd, int Rm, int Rn) {
+    mTarget->QADD(cc, Rd, Rm, Rn);
+}
+void ARMAssemblerProxy::QDADD(int cc, int Rd, int Rm, int Rn) {
+    mTarget->QDADD(cc, Rd, Rm, Rn);
+}
+void ARMAssemblerProxy::QSUB(int cc, int Rd, int Rm, int Rn) {
+    mTarget->QSUB(cc, Rd, Rm, Rn);
+}
+void ARMAssemblerProxy::QDSUB(int cc, int Rd, int Rm, int Rn) {
+    mTarget->QDSUB(cc, Rd, Rm, Rn);
+}
+void ARMAssemblerProxy::SMUL(int cc, int xy, int Rd, int Rm, int Rs) {
+    mTarget->SMUL(cc, xy, Rd, Rm, Rs);
+}
+void ARMAssemblerProxy::SMULW(int cc, int y, int Rd, int Rm, int Rs) {
+    mTarget->SMULW(cc, y, Rd, Rm, Rs);
+}
+void ARMAssemblerProxy::SMLA(int cc, int xy, int Rd, int Rm, int Rs, int Rn) {
+    mTarget->SMLA(cc, xy, Rd, Rm, Rs, Rn);
+}
+void ARMAssemblerProxy::SMLAL(  int cc, int xy,
+                                int RdHi, int RdLo, int Rs, int Rm) {
+    mTarget->SMLAL(cc, xy, RdHi, RdLo, Rs, Rm);
+}
+void ARMAssemblerProxy::SMLAW(int cc, int y, int Rd, int Rm, int Rs, int Rn) {
+    mTarget->SMLAW(cc, y, Rd, Rm, Rs, Rn);
+}
+
+void ARMAssemblerProxy::UXTB16(int cc, int Rd, int Rm, int rotate) {
+    mTarget->UXTB16(cc, Rd, Rm, rotate);
+}
+
+void ARMAssemblerProxy::UBFX(int cc, int Rd, int Rn, int lsb, int width) {
+    mTarget->UBFX(cc, Rd, Rn, lsb, width);
+}
+
+void ARMAssemblerProxy::ADDR_LDR(int cc, int Rd, int Rn, uint32_t offset) {
+     mTarget->ADDR_LDR(cc, Rd, Rn, offset);
+}
+void ARMAssemblerProxy::ADDR_STR(int cc, int Rd, int Rn, uint32_t offset) {
+     mTarget->ADDR_STR(cc, Rd, Rn, offset);
+}
+void ARMAssemblerProxy::ADDR_ADD(int cc, int s, int Rd, int Rn, uint32_t Op2){
+     mTarget->ADDR_ADD(cc, s, Rd, Rn, Op2);
+}
+void ARMAssemblerProxy::ADDR_SUB(int cc, int s, int Rd, int Rn, uint32_t Op2){
+     mTarget->ADDR_SUB(cc, s, Rd, Rn, Op2);
+}
+
+}; // namespace android
+
diff --git a/libpixelflinger/codeflinger/ARMAssemblerProxy.h b/libpixelflinger/codeflinger/ARMAssemblerProxy.h
new file mode 100644
index 0000000..10d0390
--- /dev/null
+++ b/libpixelflinger/codeflinger/ARMAssemblerProxy.h
@@ -0,0 +1,164 @@
+/* libs/pixelflinger/codeflinger/ARMAssemblerProxy.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#ifndef ANDROID_ARMASSEMBLER_PROXY_H
+#define ANDROID_ARMASSEMBLER_PROXY_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "ARMAssemblerInterface.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class ARMAssemblerProxy : public ARMAssemblerInterface
+{
+public:
+    // ARMAssemblerProxy take ownership of the target
+
+                ARMAssemblerProxy();
+    explicit    ARMAssemblerProxy(ARMAssemblerInterface* target);
+    virtual     ~ARMAssemblerProxy();
+
+    void setTarget(ARMAssemblerInterface* target);
+
+    virtual void    reset();
+    virtual int     generate(const char* name);
+    virtual void    disassemble(const char* name);
+    virtual int     getCodegenArch();
+
+    virtual void    prolog();
+    virtual void    epilog(uint32_t touched);
+    virtual void    comment(const char* string);
+
+    // -----------------------------------------------------------------------
+    // shifters and addressing modes
+    // -----------------------------------------------------------------------
+
+    virtual bool        isValidImmediate(uint32_t immed);
+    virtual int         buildImmediate(uint32_t i, uint32_t& rot, uint32_t& imm);
+
+    virtual uint32_t    imm(uint32_t immediate);
+    virtual uint32_t    reg_imm(int Rm, int type, uint32_t shift);
+    virtual uint32_t    reg_rrx(int Rm);
+    virtual uint32_t    reg_reg(int Rm, int type, int Rs);
+
+    // addressing modes...
+    // LDR(B)/STR(B)/PLD
+    // (immediate and Rm can be negative, which indicates U=0)
+    virtual uint32_t    immed12_pre(int32_t immed12, int W=0);
+    virtual uint32_t    immed12_post(int32_t immed12);
+    virtual uint32_t    reg_scale_pre(int Rm, int type=0, uint32_t shift=0, int W=0);
+    virtual uint32_t    reg_scale_post(int Rm, int type=0, uint32_t shift=0);
+
+    // LDRH/LDRSB/LDRSH/STRH
+    // (immediate and Rm can be negative, which indicates U=0)
+    virtual uint32_t    immed8_pre(int32_t immed8, int W=0);
+    virtual uint32_t    immed8_post(int32_t immed8);
+    virtual uint32_t    reg_pre(int Rm, int W=0);
+    virtual uint32_t    reg_post(int Rm);
+
+
+    virtual void    dataProcessing(int opcode, int cc, int s,
+                                int Rd, int Rn,
+                                uint32_t Op2);
+    virtual void MLA(int cc, int s,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void MUL(int cc, int s,
+                int Rd, int Rm, int Rs);
+    virtual void UMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void UMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void SMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void SMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+
+    virtual void B(int cc, uint32_t* pc);
+    virtual void BL(int cc, uint32_t* pc);
+    virtual void BX(int cc, int Rn);
+    virtual void label(const char* theLabel);
+    virtual void B(int cc, const char* label);
+    virtual void BL(int cc, const char* label);
+
+    uint32_t* pcForLabel(const char* label);
+
+    virtual void LDR (int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void LDRB(int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void STR (int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void STRB(int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void LDRH (int cc, int Rd,
+                int Rn, uint32_t offset = __immed8_pre(0));
+    virtual void LDRSB(int cc, int Rd, 
+                int Rn, uint32_t offset = __immed8_pre(0));
+    virtual void LDRSH(int cc, int Rd,
+                int Rn, uint32_t offset = __immed8_pre(0));
+    virtual void STRH (int cc, int Rd,
+                int Rn, uint32_t offset = __immed8_pre(0));
+    virtual void LDM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list);
+    virtual void STM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list);
+
+    virtual void SWP(int cc, int Rn, int Rd, int Rm);
+    virtual void SWPB(int cc, int Rn, int Rd, int Rm);
+    virtual void SWI(int cc, uint32_t comment);
+
+    virtual void PLD(int Rn, uint32_t offset);
+    virtual void CLZ(int cc, int Rd, int Rm);
+    virtual void QADD(int cc, int Rd, int Rm, int Rn);
+    virtual void QDADD(int cc, int Rd, int Rm, int Rn);
+    virtual void QSUB(int cc, int Rd, int Rm, int Rn);
+    virtual void QDSUB(int cc, int Rd, int Rm, int Rn);
+    virtual void SMUL(int cc, int xy,
+                int Rd, int Rm, int Rs);
+    virtual void SMULW(int cc, int y,
+                int Rd, int Rm, int Rs);
+    virtual void SMLA(int cc, int xy,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void SMLAL(int cc, int xy,
+                int RdHi, int RdLo, int Rs, int Rm);
+    virtual void SMLAW(int cc, int y,
+                int Rd, int Rm, int Rs, int Rn);
+
+    virtual void UXTB16(int cc, int Rd, int Rm, int rotate);
+    virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width);
+
+    virtual void ADDR_LDR(int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void ADDR_STR (int cc, int Rd,
+                int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void ADDR_ADD(int cc, int s, int Rd,
+                int Rn, uint32_t Op2);
+    virtual void ADDR_SUB(int cc, int s, int Rd,
+                int Rn, uint32_t Op2);
+
+private:
+    ARMAssemblerInterface*  mTarget;
+};
+
+}; // namespace android
+
+#endif //ANDROID_ARMASSEMBLER_PROXY_H
diff --git a/libpixelflinger/codeflinger/Arm64Assembler.cpp b/libpixelflinger/codeflinger/Arm64Assembler.cpp
new file mode 100644
index 0000000..271a9b9
--- /dev/null
+++ b/libpixelflinger/codeflinger/Arm64Assembler.cpp
@@ -0,0 +1,1240 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define LOG_TAG "ArmToArm64Assembler"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cutils/properties.h>
+#include <log/log.h>
+#include <private/pixelflinger/ggl_context.h>
+
+#include "codeflinger/Arm64Assembler.h"
+#include "codeflinger/Arm64Disassembler.h"
+#include "codeflinger/CodeCache.h"
+
+/*
+** --------------------------------------------
+** Support for Arm64 in GGLAssembler JIT
+** --------------------------------------------
+**
+** Approach
+** - GGLAssembler and associated files are largely un-changed.
+** - A translator class maps ArmAssemblerInterface calls to
+**   generate Arm64 instructions.
+**
+** ----------------------
+** ArmToArm64Assembler
+** ----------------------
+**
+** - Subclassed from ArmAssemblerInterface
+**
+** - Translates each ArmAssemblerInterface call to generate
+**   one or more Arm64 instructions  as necessary.
+**
+** - Does not implement ArmAssemblerInterface portions unused by GGLAssembler
+**   It calls NOT_IMPLEMENTED() for such cases, which in turn logs
+**    a fatal message.
+**
+** - Uses A64_.. series of functions to generate instruction machine code
+**   for Arm64 instructions. These functions also log the instruction
+**   to LOG, if ARM64_ASM_DEBUG define is set to 1
+**
+** - Dumps machine code and eqvt assembly if "debug.pf.disasm" option is set
+**   It uses arm64_disassemble to perform disassembly
+**
+** - Uses register 13 (SP in ARM), 15 (PC in ARM), 16, 17 for storing
+**   intermediate results. GGLAssembler does not use SP and PC as these
+**   registers are marked as reserved. The temporary registers are not
+**   saved/restored on stack as these are caller-saved registers in Arm64
+**
+** - Uses CSEL instruction to support conditional execution. The result is
+**   stored in a temporary register and then copied to the target register
+**   if the condition is true.
+**
+** - In the case of conditional data transfer instructions, conditional
+**   branch is used to skip over instruction, if the condition is false
+**
+** - Wherever possible, immediate values are transferred to temporary
+**   register prior to processing. This simplifies overall implementation
+**   as instructions requiring immediate values are converted to
+**   move immediate instructions followed by register-register instruction.
+**
+** --------------------------------------------
+** ArmToArm64Assembler unit test bench
+** --------------------------------------------
+**
+** - Tests ArmToArm64Assembler interface for all the possible
+**   ways in which GGLAssembler uses ArmAssemblerInterface interface.
+**
+** - Uses test jacket (written in assembly) to set the registers,
+**   condition flags prior to calling generated instruction. It also
+**   copies registers and flags at the end of execution. Caller then
+**   checks if generated code performed correct operation based on
+**   output registers and flags.
+**
+** - Broadly contains three type of tests, (i) data operation tests
+**   (ii) data transfer tests and (iii) LDM/STM tests.
+**
+** ----------------------
+** Arm64 disassembler
+** ----------------------
+** - This disassembler disassembles only those machine codes which can be
+**   generated by ArmToArm64Assembler. It has a unit testbench which
+**   tests all the instructions supported by the disassembler.
+**
+** ------------------------------------------------------------------
+** ARMAssembler/ARMAssemblerInterface/ARMAssemblerProxy changes
+** ------------------------------------------------------------------
+**
+** - In existing code, addresses were being handled as 32 bit values at
+**   certain places.
+**
+** - Added a new set of functions for address load/store/manipulation.
+**   These are ADDR_LDR, ADDR_STR, ADDR_ADD, ADDR_SUB and they map to
+**   default 32 bit implementations in ARMAssemblerInterface.
+**
+** - ArmToArm64Assembler maps these functions to appropriate 64 bit
+**   functions.
+**
+** ----------------------
+** GGLAssembler changes
+** ----------------------
+** - Since ArmToArm64Assembler can generate 4 Arm64 instructions for
+**   each call in worst case, the memory required is set to 4 times
+**   ARM memory
+**
+** - Address load/store/manipulation were changed to use new functions
+**   added in the ARMAssemblerInterface.
+**
+*/
+
+
+#define NOT_IMPLEMENTED()  LOG_FATAL("Arm instruction %s not yet implemented\n", __func__)
+
+#define ARM64_ASM_DEBUG 0
+
+#if ARM64_ASM_DEBUG
+    #define LOG_INSTR(...) ALOGD("\t" __VA_ARGS__)
+    #define LOG_LABEL(...) ALOGD(__VA_ARGS__)
+#else
+    #define LOG_INSTR(...) ((void)0)
+    #define LOG_LABEL(...) ((void)0)
+#endif
+
+namespace android {
+
+static __unused const char* shift_codes[] =
+{
+    "LSL", "LSR", "ASR", "ROR"
+};
+static __unused const char *cc_codes[] =
+{
+    "EQ", "NE", "CS", "CC", "MI",
+    "PL", "VS", "VC", "HI", "LS",
+    "GE", "LT", "GT", "LE", "AL", "NV"
+};
+
+ArmToArm64Assembler::ArmToArm64Assembler(const sp<Assembly>& assembly)
+    :   ARMAssemblerInterface(),
+        mAssembly(assembly)
+{
+    mBase = mPC = (uint32_t *)assembly->base();
+    mDuration = ggl_system_time();
+    mZeroReg = 13;
+    mTmpReg1 = 15;
+    mTmpReg2 = 16;
+    mTmpReg3 = 17;
+}
+
+ArmToArm64Assembler::ArmToArm64Assembler(void *base)
+    :   ARMAssemblerInterface(), mAssembly(NULL)
+{
+    mBase = mPC = (uint32_t *)base;
+    mDuration = ggl_system_time();
+    // Regs 13, 15, 16, 17 are used as temporary registers
+    mZeroReg = 13;
+    mTmpReg1 = 15;
+    mTmpReg2 = 16;
+    mTmpReg3 = 17;
+}
+
+ArmToArm64Assembler::~ArmToArm64Assembler()
+{
+}
+
+uint32_t* ArmToArm64Assembler::pc() const
+{
+    return mPC;
+}
+
+uint32_t* ArmToArm64Assembler::base() const
+{
+    return mBase;
+}
+
+void ArmToArm64Assembler::reset()
+{
+    if(mAssembly == NULL)
+        mPC = mBase;
+    else
+        mBase = mPC = (uint32_t *)mAssembly->base();
+    mBranchTargets.clear();
+    mLabels.clear();
+    mLabelsInverseMapping.clear();
+    mComments.clear();
+#if ARM64_ASM_DEBUG
+    ALOGI("RESET\n");
+#endif
+}
+
+int ArmToArm64Assembler::getCodegenArch()
+{
+    return CODEGEN_ARCH_ARM64;
+}
+
+// ----------------------------------------------------------------------------
+
+void ArmToArm64Assembler::disassemble(const char* name)
+{
+    if(name)
+    {
+        printf("%s:\n", name);
+    }
+    size_t count = pc()-base();
+    uint32_t* i = base();
+    while (count--)
+    {
+        ssize_t label = mLabelsInverseMapping.indexOfKey(i);
+        if (label >= 0)
+        {
+            printf("%s:\n", mLabelsInverseMapping.valueAt(label));
+        }
+        ssize_t comment = mComments.indexOfKey(i);
+        if (comment >= 0)
+        {
+            printf("; %s\n", mComments.valueAt(comment));
+        }
+        printf("%p:    %08x    ", i, uint32_t(i[0]));
+        {
+            char instr[256];
+            ::arm64_disassemble(*i, instr);
+            printf("%s\n", instr);
+        }
+        i++;
+    }
+}
+
+void ArmToArm64Assembler::comment(const char* string)
+{
+    mComments.add(mPC, string);
+    LOG_INSTR("//%s\n", string);
+}
+
+void ArmToArm64Assembler::label(const char* theLabel)
+{
+    mLabels.add(theLabel, mPC);
+    mLabelsInverseMapping.add(mPC, theLabel);
+    LOG_LABEL("%s:\n", theLabel);
+}
+
+void ArmToArm64Assembler::B(int cc, const char* label)
+{
+    mBranchTargets.add(branch_target_t(label, mPC));
+    LOG_INSTR("B%s %s\n", cc_codes[cc], label );
+    *mPC++ = (0x54 << 24) | cc;
+}
+
+void ArmToArm64Assembler::BL(int /*cc*/, const char* /*label*/)
+{
+    NOT_IMPLEMENTED(); //Not Required
+}
+
+// ----------------------------------------------------------------------------
+//Prolog/Epilog & Generate...
+// ----------------------------------------------------------------------------
+
+void ArmToArm64Assembler::prolog()
+{
+    // write prolog code
+    mPrologPC = mPC;
+    *mPC++ = A64_MOVZ_X(mZeroReg,0,0);
+}
+
+void ArmToArm64Assembler::epilog(uint32_t /*touched*/)
+{
+    // write epilog code
+    static const int XLR = 30;
+    *mPC++ = A64_RET(XLR);
+}
+
+int ArmToArm64Assembler::generate(const char* name)
+{
+    // fixup all the branches
+    size_t count = mBranchTargets.size();
+    while (count--)
+    {
+        const branch_target_t& bt = mBranchTargets[count];
+        uint32_t* target_pc = mLabels.valueFor(bt.label);
+        LOG_ALWAYS_FATAL_IF(!target_pc,
+                "error resolving branch targets, target_pc is null");
+        int32_t offset = int32_t(target_pc - bt.pc);
+        *bt.pc |= (offset & 0x7FFFF) << 5;
+    }
+
+    if(mAssembly != NULL)
+        mAssembly->resize( int(pc()-base())*4 );
+
+    // the instruction cache is flushed by CodeCache
+    const int64_t duration = ggl_system_time() - mDuration;
+    const char * const format = "generated %s (%d ins) at [%p:%p] in %" PRId64 "ns\n";
+    ALOGI(format, name, int(pc()-base()), base(), pc(), duration);
+
+
+    char value[PROPERTY_VALUE_MAX];
+    property_get("debug.pf.disasm", value, "0");
+    if (atoi(value) != 0)
+    {
+        printf(format, name, int(pc()-base()), base(), pc(), duration);
+        disassemble(name);
+    }
+    return OK;
+}
+
+uint32_t* ArmToArm64Assembler::pcForLabel(const char* label)
+{
+    return mLabels.valueFor(label);
+}
+
+// ----------------------------------------------------------------------------
+// Data Processing...
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::dataProcessingCommon(int opcode,
+        int s, int Rd, int Rn, uint32_t Op2)
+{
+    if(opcode != opSUB && s == 1)
+    {
+        NOT_IMPLEMENTED(); //Not required
+        return;
+    }
+
+    if(opcode != opSUB && opcode != opADD && opcode != opAND &&
+       opcode != opORR && opcode != opMVN)
+    {
+        NOT_IMPLEMENTED(); //Not required
+        return;
+    }
+
+    if(Op2 == OPERAND_REG_IMM && mAddrMode.reg_imm_shift > 31)
+        {
+        NOT_IMPLEMENTED();
+        return;
+    }
+
+    //Store immediate in temporary register and convert
+    //immediate operation into register operation
+    if(Op2 == OPERAND_IMM)
+    {
+        int imm = mAddrMode.immediate;
+        *mPC++ = A64_MOVZ_W(mTmpReg2, imm & 0x0000FFFF, 0);
+        *mPC++ = A64_MOVK_W(mTmpReg2, (imm >> 16) & 0x0000FFFF, 16);
+        Op2 = mTmpReg2;
+    }
+
+
+    {
+        uint32_t shift;
+        uint32_t amount;
+        uint32_t Rm;
+
+        if(Op2 == OPERAND_REG_IMM)
+        {
+            shift   = mAddrMode.reg_imm_type;
+            amount  = mAddrMode.reg_imm_shift;
+            Rm      = mAddrMode.reg_imm_Rm;
+        }
+        else if(Op2 < OPERAND_REG)
+        {
+            shift   = 0;
+            amount  = 0;
+            Rm      = Op2;
+        }
+        else
+        {
+            NOT_IMPLEMENTED(); //Not required
+            return;
+        }
+
+        switch(opcode)
+        {
+            case opADD: *mPC++ = A64_ADD_W(Rd, Rn, Rm, shift, amount); break;
+            case opAND: *mPC++ = A64_AND_W(Rd, Rn, Rm, shift, amount); break;
+            case opORR: *mPC++ = A64_ORR_W(Rd, Rn, Rm, shift, amount); break;
+            case opMVN: *mPC++ = A64_ORN_W(Rd, Rn, Rm, shift, amount); break;
+            case opSUB: *mPC++ = A64_SUB_W(Rd, Rn, Rm, shift, amount, s);break;
+        };
+
+    }
+}
+
+void ArmToArm64Assembler::dataProcessing(int opcode, int cc,
+        int s, int Rd, int Rn, uint32_t Op2)
+{
+    uint32_t Wd;
+
+    if(cc != AL)
+        Wd = mTmpReg1;
+    else
+        Wd = Rd;
+
+    if(opcode == opADD || opcode == opAND || opcode == opORR ||opcode == opSUB)
+    {
+        dataProcessingCommon(opcode, s, Wd, Rn, Op2);
+    }
+    else if(opcode == opCMP)
+    {
+        dataProcessingCommon(opSUB, 1, mTmpReg3, Rn, Op2);
+    }
+    else if(opcode == opRSB)
+    {
+        dataProcessingCommon(opSUB, s, Wd, Rn, Op2);
+        dataProcessingCommon(opSUB, s, Wd, mZeroReg, Wd);
+    }
+    else if(opcode == opMOV)
+    {
+        dataProcessingCommon(opORR, 0, Wd, mZeroReg, Op2);
+        if(s == 1)
+        {
+            dataProcessingCommon(opSUB, 1, mTmpReg3, Wd, mZeroReg);
+        }
+    }
+    else if(opcode == opMVN)
+    {
+        dataProcessingCommon(opMVN, s, Wd, mZeroReg, Op2);
+    }
+    else if(opcode == opBIC)
+    {
+        dataProcessingCommon(opMVN, s, mTmpReg3, mZeroReg, Op2);
+        dataProcessingCommon(opAND, s, Wd, Rn, mTmpReg3);
+    }
+    else
+    {
+        NOT_IMPLEMENTED();
+        return;
+    }
+
+    if(cc != AL)
+    {
+        *mPC++ = A64_CSEL_W(Rd, mTmpReg1, Rd, cc);
+    }
+}
+// ----------------------------------------------------------------------------
+// Address Processing...
+// ----------------------------------------------------------------------------
+
+void ArmToArm64Assembler::ADDR_ADD(int cc,
+        int s, int Rd, int Rn, uint32_t Op2)
+{
+    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+    if(s  != 0) { NOT_IMPLEMENTED(); return;} //Not required
+
+
+    if(Op2 == OPERAND_REG_IMM && mAddrMode.reg_imm_type == LSL)
+    {
+        int Rm = mAddrMode.reg_imm_Rm;
+        int amount = mAddrMode.reg_imm_shift;
+        *mPC++ = A64_ADD_X_Wm_SXTW(Rd, Rn, Rm, amount);
+    }
+    else if(Op2 < OPERAND_REG)
+    {
+        int Rm = Op2;
+        int amount = 0;
+        *mPC++ = A64_ADD_X_Wm_SXTW(Rd, Rn, Rm, amount);
+    }
+    else if(Op2 == OPERAND_IMM)
+    {
+        int imm = mAddrMode.immediate;
+        *mPC++ = A64_MOVZ_W(mTmpReg1, imm & 0x0000FFFF, 0);
+        *mPC++ = A64_MOVK_W(mTmpReg1, (imm >> 16) & 0x0000FFFF, 16);
+
+        int Rm = mTmpReg1;
+        int amount = 0;
+        *mPC++ = A64_ADD_X_Wm_SXTW(Rd, Rn, Rm, amount);
+    }
+    else
+    {
+        NOT_IMPLEMENTED(); //Not required
+    }
+}
+
+void ArmToArm64Assembler::ADDR_SUB(int cc,
+        int s, int Rd, int Rn, uint32_t Op2)
+{
+    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+    if(s  != 0) { NOT_IMPLEMENTED(); return;} //Not required
+
+    if(Op2 == OPERAND_REG_IMM && mAddrMode.reg_imm_type == LSR)
+    {
+        *mPC++ = A64_ADD_W(mTmpReg1, mZeroReg, mAddrMode.reg_imm_Rm,
+                           LSR, mAddrMode.reg_imm_shift);
+        *mPC++ = A64_SUB_X_Wm_SXTW(Rd, Rn, mTmpReg1, 0);
+    }
+    else
+    {
+        NOT_IMPLEMENTED(); //Not required
+    }
+}
+
+// ----------------------------------------------------------------------------
+// multiply...
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::MLA(int cc, int s,int Rd, int Rm, int Rs, int Rn)
+{
+    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+
+    *mPC++ = A64_MADD_W(Rd, Rm, Rs, Rn);
+    if(s == 1)
+        dataProcessingCommon(opSUB, 1, mTmpReg1, Rd, mZeroReg);
+}
+void ArmToArm64Assembler::MUL(int cc, int s, int Rd, int Rm, int Rs)
+{
+    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+    if(s  != 0) { NOT_IMPLEMENTED(); return;} //Not required
+    *mPC++ = A64_MADD_W(Rd, Rm, Rs, mZeroReg);
+}
+void ArmToArm64Assembler::UMULL(int /*cc*/, int /*s*/,
+        int /*RdLo*/, int /*RdHi*/, int /*Rm*/, int /*Rs*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+void ArmToArm64Assembler::UMUAL(int /*cc*/, int /*s*/,
+        int /*RdLo*/, int /*RdHi*/, int /*Rm*/, int /*Rs*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+void ArmToArm64Assembler::SMULL(int /*cc*/, int /*s*/,
+        int /*RdLo*/, int /*RdHi*/, int /*Rm*/, int /*Rs*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+void ArmToArm64Assembler::SMUAL(int /*cc*/, int /*s*/,
+        int /*RdLo*/, int /*RdHi*/, int /*Rm*/, int /*Rs*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+
+// ----------------------------------------------------------------------------
+// branches relative to PC...
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::B(int /*cc*/, uint32_t* /*pc*/){
+    NOT_IMPLEMENTED(); //Not required
+}
+
+void ArmToArm64Assembler::BL(int /*cc*/, uint32_t* /*pc*/){
+    NOT_IMPLEMENTED(); //Not required
+}
+
+void ArmToArm64Assembler::BX(int /*cc*/, int /*Rn*/){
+    NOT_IMPLEMENTED(); //Not required
+}
+
+// ----------------------------------------------------------------------------
+// data transfer...
+// ----------------------------------------------------------------------------
+enum dataTransferOp
+{
+    opLDR,opLDRB,opLDRH,opSTR,opSTRB,opSTRH
+};
+
+void ArmToArm64Assembler::dataTransfer(int op, int cc,
+                            int Rd, int Rn, uint32_t op_type, uint32_t size)
+{
+    const int XSP = 31;
+    if(Rn == SP)
+        Rn = XSP;
+
+    if(op_type == OPERAND_IMM)
+    {
+        int addrReg;
+        int imm = mAddrMode.immediate;
+        if(imm >= 0 && imm < (1<<12))
+            *mPC++ = A64_ADD_IMM_X(mTmpReg1, mZeroReg, imm, 0);
+        else if(imm < 0 && -imm < (1<<12))
+            *mPC++ = A64_SUB_IMM_X(mTmpReg1, mZeroReg, -imm, 0);
+        else
+        {
+            NOT_IMPLEMENTED();
+            return;
+        }
+
+        addrReg = Rn;
+        if(mAddrMode.preindex == true || mAddrMode.postindex == true)
+        {
+            *mPC++ = A64_ADD_X(mTmpReg2, addrReg, mTmpReg1);
+            if(mAddrMode.preindex == true)
+                addrReg = mTmpReg2;
+        }
+
+        if(cc != AL)
+            *mPC++ = A64_B_COND(cc^1, 8);
+
+        *mPC++ = A64_LDRSTR_Wm_SXTW_0(op, size, Rd, addrReg, mZeroReg);
+
+        if(mAddrMode.writeback == true)
+            *mPC++ = A64_CSEL_X(Rn, mTmpReg2, Rn, cc);
+    }
+    else if(op_type == OPERAND_REG_OFFSET)
+    {
+        if(cc != AL)
+            *mPC++ = A64_B_COND(cc^1, 8);
+        *mPC++ = A64_LDRSTR_Wm_SXTW_0(op, size, Rd, Rn, mAddrMode.reg_offset);
+
+    }
+    else if(op_type > OPERAND_UNSUPPORTED)
+    {
+        if(cc != AL)
+            *mPC++ = A64_B_COND(cc^1, 8);
+        *mPC++ = A64_LDRSTR_Wm_SXTW_0(op, size, Rd, Rn, mZeroReg);
+    }
+    else
+    {
+        NOT_IMPLEMENTED(); // Not required
+    }
+    return;
+
+}
+void ArmToArm64Assembler::ADDR_LDR(int cc, int Rd, int Rn, uint32_t op_type)
+{
+    return dataTransfer(opLDR, cc, Rd, Rn, op_type, 64);
+}
+void ArmToArm64Assembler::ADDR_STR(int cc, int Rd, int Rn, uint32_t op_type)
+{
+    return dataTransfer(opSTR, cc, Rd, Rn, op_type, 64);
+}
+void ArmToArm64Assembler::LDR(int cc, int Rd, int Rn, uint32_t op_type)
+{
+    return dataTransfer(opLDR, cc, Rd, Rn, op_type);
+}
+void ArmToArm64Assembler::LDRB(int cc, int Rd, int Rn, uint32_t op_type)
+{
+    return dataTransfer(opLDRB, cc, Rd, Rn, op_type);
+}
+void ArmToArm64Assembler::STR(int cc, int Rd, int Rn, uint32_t op_type)
+{
+    return dataTransfer(opSTR, cc, Rd, Rn, op_type);
+}
+
+void ArmToArm64Assembler::STRB(int cc, int Rd, int Rn, uint32_t op_type)
+{
+    return dataTransfer(opSTRB, cc, Rd, Rn, op_type);
+}
+
+void ArmToArm64Assembler::LDRH(int cc, int Rd, int Rn, uint32_t op_type)
+{
+    return dataTransfer(opLDRH, cc, Rd, Rn, op_type);
+}
+void ArmToArm64Assembler::LDRSB(int /*cc*/, int /*Rd*/, int /*Rn*/, uint32_t /*offset*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+void ArmToArm64Assembler::LDRSH(int /*cc*/, int /*Rd*/, int /*Rn*/, uint32_t /*offset*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+
+void ArmToArm64Assembler::STRH(int cc, int Rd, int Rn, uint32_t op_type)
+{
+    return dataTransfer(opSTRH, cc, Rd, Rn, op_type);
+}
+
+// ----------------------------------------------------------------------------
+// block data transfer...
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::LDM(int cc, int dir,
+        int Rn, int W, uint32_t reg_list)
+{
+    const int XSP = 31;
+    if(cc != AL || dir != IA || W == 0 || Rn != SP)
+    {
+        NOT_IMPLEMENTED();
+        return;
+    }
+
+    for(int i = 0; i < 32; ++i)
+    {
+        if((reg_list & (1 << i)))
+        {
+            int reg = i;
+            int size = 16;
+            *mPC++ = A64_LDR_IMM_PostIndex(reg, XSP, size);
+        }
+    }
+}
+
+void ArmToArm64Assembler::STM(int cc, int dir,
+        int Rn, int W, uint32_t reg_list)
+{
+    const int XSP = 31;
+    if(cc != AL || dir != DB || W == 0 || Rn != SP)
+    {
+        NOT_IMPLEMENTED();
+        return;
+    }
+
+    for(int i = 31; i >= 0; --i)
+    {
+        if((reg_list & (1 << i)))
+        {
+            int size = -16;
+            int reg  = i;
+            *mPC++ = A64_STR_IMM_PreIndex(reg, XSP, size);
+        }
+    }
+}
+
+// ----------------------------------------------------------------------------
+// special...
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::SWP(int /*cc*/, int /*Rn*/, int /*Rd*/, int /*Rm*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+void ArmToArm64Assembler::SWPB(int /*cc*/, int /*Rn*/, int /*Rd*/, int /*Rm*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+void ArmToArm64Assembler::SWI(int /*cc*/, uint32_t /*comment*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+
+// ----------------------------------------------------------------------------
+// DSP instructions...
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::PLD(int /*Rn*/, uint32_t /*offset*/) {
+    NOT_IMPLEMENTED(); //Not required
+}
+
+void ArmToArm64Assembler::CLZ(int /*cc*/, int /*Rd*/, int /*Rm*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+
+void ArmToArm64Assembler::QADD(int /*cc*/, int /*Rd*/, int /*Rm*/, int /*Rn*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+
+void ArmToArm64Assembler::QDADD(int /*cc*/, int /*Rd*/, int /*Rm*/, int /*Rn*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+
+void ArmToArm64Assembler::QSUB(int /*cc*/, int /*Rd*/, int /*Rm*/, int /*Rn*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+
+void ArmToArm64Assembler::QDSUB(int /*cc*/, int /*Rd*/, int /*Rm*/, int /*Rn*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+}
+
+// ----------------------------------------------------------------------------
+// 16 x 16 multiplication
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::SMUL(int cc, int xy,
+                int Rd, int Rm, int Rs)
+{
+    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+
+    if (xy & xyTB)
+        *mPC++ = A64_SBFM_W(mTmpReg1, Rm, 16, 31);
+    else
+        *mPC++ = A64_SBFM_W(mTmpReg1, Rm, 0, 15);
+
+    if (xy & xyBT)
+        *mPC++ = A64_SBFM_W(mTmpReg2, Rs, 16, 31);
+    else
+        *mPC++ = A64_SBFM_W(mTmpReg2, Rs, 0, 15);
+
+    *mPC++ = A64_MADD_W(Rd,mTmpReg1,mTmpReg2, mZeroReg);
+}
+// ----------------------------------------------------------------------------
+// 32 x 16 multiplication
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::SMULW(int cc, int y, int Rd, int Rm, int Rs)
+{
+    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+
+    if (y & yT)
+        *mPC++ = A64_SBFM_W(mTmpReg1, Rs, 16, 31);
+    else
+        *mPC++ = A64_SBFM_W(mTmpReg1, Rs, 0, 15);
+
+    *mPC++ = A64_SBFM_W(mTmpReg2, Rm, 0, 31);
+    *mPC++ = A64_SMADDL(mTmpReg3,mTmpReg1,mTmpReg2, mZeroReg);
+    *mPC++ = A64_UBFM_X(Rd,mTmpReg3, 16, 47);
+}
+// ----------------------------------------------------------------------------
+// 16 x 16 multiplication and accumulate
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::SMLA(int cc, int xy, int Rd, int Rm, int Rs, int Rn)
+{
+    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+    if(xy != xyBB) { NOT_IMPLEMENTED(); return;} //Not required
+
+    *mPC++ = A64_SBFM_W(mTmpReg1, Rm, 0, 15);
+    *mPC++ = A64_SBFM_W(mTmpReg2, Rs, 0, 15);
+    *mPC++ = A64_MADD_W(Rd, mTmpReg1, mTmpReg2, Rn);
+}
+
+void ArmToArm64Assembler::SMLAL(int /*cc*/, int /*xy*/,
+                int /*RdHi*/, int /*RdLo*/, int /*Rs*/, int /*Rm*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+    return;
+}
+
+void ArmToArm64Assembler::SMLAW(int /*cc*/, int /*y*/,
+                int /*Rd*/, int /*Rm*/, int /*Rs*/, int /*Rn*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+    return;
+}
+
+// ----------------------------------------------------------------------------
+// Byte/half word extract and extend
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::UXTB16(int cc, int Rd, int Rm, int rotate)
+{
+    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+
+    *mPC++ = A64_EXTR_W(mTmpReg1, Rm, Rm, rotate * 8);
+
+    uint32_t imm = 0x00FF00FF;
+    *mPC++ = A64_MOVZ_W(mTmpReg2, imm & 0xFFFF, 0);
+    *mPC++ = A64_MOVK_W(mTmpReg2, (imm >> 16) & 0x0000FFFF, 16);
+    *mPC++ = A64_AND_W(Rd,mTmpReg1, mTmpReg2);
+}
+
+// ----------------------------------------------------------------------------
+// Bit manipulation
+// ----------------------------------------------------------------------------
+void ArmToArm64Assembler::UBFX(int cc, int Rd, int Rn, int lsb, int width)
+{
+    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+    *mPC++ = A64_UBFM_W(Rd, Rn, lsb, lsb + width - 1);
+}
+// ----------------------------------------------------------------------------
+// Shifters...
+// ----------------------------------------------------------------------------
+int ArmToArm64Assembler::buildImmediate(
+        uint32_t immediate, uint32_t& rot, uint32_t& imm)
+{
+    rot = 0;
+    imm = immediate;
+    return 0; // Always true
+}
+
+
+bool ArmToArm64Assembler::isValidImmediate(uint32_t immediate)
+{
+    uint32_t rot, imm;
+    return buildImmediate(immediate, rot, imm) == 0;
+}
+
+uint32_t ArmToArm64Assembler::imm(uint32_t immediate)
+{
+    mAddrMode.immediate = immediate;
+    mAddrMode.writeback = false;
+    mAddrMode.preindex  = false;
+    mAddrMode.postindex = false;
+    return OPERAND_IMM;
+
+}
+
+uint32_t ArmToArm64Assembler::reg_imm(int Rm, int type, uint32_t shift)
+{
+    mAddrMode.reg_imm_Rm = Rm;
+    mAddrMode.reg_imm_type = type;
+    mAddrMode.reg_imm_shift = shift;
+    return OPERAND_REG_IMM;
+}
+
+uint32_t ArmToArm64Assembler::reg_rrx(int /*Rm*/)
+{
+    NOT_IMPLEMENTED();
+    return OPERAND_UNSUPPORTED;
+}
+
+uint32_t ArmToArm64Assembler::reg_reg(int /*Rm*/, int /*type*/, int /*Rs*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+    return OPERAND_UNSUPPORTED;
+}
+// ----------------------------------------------------------------------------
+// Addressing modes...
+// ----------------------------------------------------------------------------
+uint32_t ArmToArm64Assembler::immed12_pre(int32_t immed12, int W)
+{
+    mAddrMode.immediate = immed12;
+    mAddrMode.writeback = W;
+    mAddrMode.preindex  = true;
+    mAddrMode.postindex = false;
+    return OPERAND_IMM;
+}
+
+uint32_t ArmToArm64Assembler::immed12_post(int32_t immed12)
+{
+    mAddrMode.immediate = immed12;
+    mAddrMode.writeback = true;
+    mAddrMode.preindex  = false;
+    mAddrMode.postindex = true;
+    return OPERAND_IMM;
+}
+
+uint32_t ArmToArm64Assembler::reg_scale_pre(int Rm, int type,
+        uint32_t shift, int W)
+{
+    if(type != 0 || shift != 0 || W != 0)
+    {
+        NOT_IMPLEMENTED(); //Not required
+        return OPERAND_UNSUPPORTED;
+    }
+    else
+    {
+        mAddrMode.reg_offset = Rm;
+        return OPERAND_REG_OFFSET;
+    }
+}
+
+uint32_t ArmToArm64Assembler::reg_scale_post(int /*Rm*/, int /*type*/, uint32_t /*shift*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+    return OPERAND_UNSUPPORTED;
+}
+
+uint32_t ArmToArm64Assembler::immed8_pre(int32_t immed8, int W)
+{
+    mAddrMode.immediate = immed8;
+    mAddrMode.writeback = W;
+    mAddrMode.preindex  = true;
+    mAddrMode.postindex = false;
+    return OPERAND_IMM;
+}
+
+uint32_t ArmToArm64Assembler::immed8_post(int32_t immed8)
+{
+    mAddrMode.immediate = immed8;
+    mAddrMode.writeback = true;
+    mAddrMode.preindex  = false;
+    mAddrMode.postindex = true;
+    return OPERAND_IMM;
+}
+
+uint32_t ArmToArm64Assembler::reg_pre(int Rm, int W)
+{
+    if(W != 0)
+    {
+        NOT_IMPLEMENTED(); //Not required
+        return OPERAND_UNSUPPORTED;
+    }
+    else
+    {
+        mAddrMode.reg_offset = Rm;
+        return OPERAND_REG_OFFSET;
+    }
+}
+
+uint32_t ArmToArm64Assembler::reg_post(int /*Rm*/)
+{
+    NOT_IMPLEMENTED(); //Not required
+    return OPERAND_UNSUPPORTED;
+}
+
+// ----------------------------------------------------------------------------
+// A64 instructions
+// ----------------------------------------------------------------------------
+
+static __unused const char * dataTransferOpName[] =
+{
+    "LDR","LDRB","LDRH","STR","STRB","STRH"
+};
+
+static const uint32_t dataTransferOpCode [] =
+{
+    ((0xB8u << 24) | (0x3 << 21) | (0x6 << 13) | (0x0 << 12) |(0x1 << 11)),
+    ((0x38u << 24) | (0x3 << 21) | (0x6 << 13) | (0x1 << 12) |(0x1 << 11)),
+    ((0x78u << 24) | (0x3 << 21) | (0x6 << 13) | (0x0 << 12) |(0x1 << 11)),
+    ((0xB8u << 24) | (0x1 << 21) | (0x6 << 13) | (0x0 << 12) |(0x1 << 11)),
+    ((0x38u << 24) | (0x1 << 21) | (0x6 << 13) | (0x1 << 12) |(0x1 << 11)),
+    ((0x78u << 24) | (0x1 << 21) | (0x6 << 13) | (0x0 << 12) |(0x1 << 11))
+};
+uint32_t ArmToArm64Assembler::A64_LDRSTR_Wm_SXTW_0(uint32_t op,
+                            uint32_t size, uint32_t Rt,
+                            uint32_t Rn, uint32_t Rm)
+{
+    if(size == 32)
+    {
+        LOG_INSTR("%s W%d, [X%d, W%d, SXTW #0]\n",
+                   dataTransferOpName[op], Rt, Rn, Rm);
+        return(dataTransferOpCode[op] | (Rm << 16) | (Rn << 5) | Rt);
+    }
+    else
+    {
+        LOG_INSTR("%s X%d, [X%d, W%d, SXTW #0]\n",
+                  dataTransferOpName[op], Rt, Rn, Rm);
+        return(dataTransferOpCode[op] | (0x1<<30) | (Rm<<16) | (Rn<<5)|Rt);
+    }
+}
+
+uint32_t ArmToArm64Assembler::A64_STR_IMM_PreIndex(uint32_t Rt,
+                            uint32_t Rn, int32_t simm)
+{
+    if(Rn == 31)
+        LOG_INSTR("STR W%d, [SP, #%d]!\n", Rt, simm);
+    else
+        LOG_INSTR("STR W%d, [X%d, #%d]!\n", Rt, Rn, simm);
+
+    uint32_t imm9 = (unsigned)(simm) & 0x01FF;
+    return (0xB8 << 24) | (imm9 << 12) | (0x3 << 10) | (Rn << 5) | Rt;
+}
+
+uint32_t ArmToArm64Assembler::A64_LDR_IMM_PostIndex(uint32_t Rt,
+                            uint32_t Rn, int32_t simm)
+{
+    if(Rn == 31)
+        LOG_INSTR("LDR W%d, [SP], #%d\n",Rt,simm);
+    else
+        LOG_INSTR("LDR W%d, [X%d], #%d\n",Rt, Rn, simm);
+
+    uint32_t imm9 = (unsigned)(simm) & 0x01FF;
+    return (0xB8 << 24) | (0x1 << 22) |
+             (imm9 << 12) | (0x1 << 10) | (Rn << 5) | Rt;
+
+}
+uint32_t ArmToArm64Assembler::A64_ADD_X_Wm_SXTW(uint32_t Rd,
+                               uint32_t Rn,
+                               uint32_t Rm,
+                               uint32_t amount)
+{
+    LOG_INSTR("ADD X%d, X%d, W%d, SXTW #%d\n", Rd, Rn, Rm, amount);
+    return ((0x8B << 24) | (0x1 << 21) |(Rm << 16) |
+              (0x6 << 13) | (amount << 10) | (Rn << 5) | Rd);
+
+}
+
+uint32_t ArmToArm64Assembler::A64_SUB_X_Wm_SXTW(uint32_t Rd,
+                               uint32_t Rn,
+                               uint32_t Rm,
+                               uint32_t amount)
+{
+    LOG_INSTR("SUB X%d, X%d, W%d, SXTW #%d\n", Rd, Rn, Rm, amount);
+    return ((0xCB << 24) | (0x1 << 21) |(Rm << 16) |
+            (0x6 << 13) | (amount << 10) | (Rn << 5) | Rd);
+
+}
+
+uint32_t ArmToArm64Assembler::A64_B_COND(uint32_t cc, uint32_t offset)
+{
+    LOG_INSTR("B.%s #.+%d\n", cc_codes[cc], offset);
+    return (0x54 << 24) | ((offset/4) << 5) | (cc);
+
+}
+uint32_t ArmToArm64Assembler::A64_ADD_X(uint32_t Rd, uint32_t Rn,
+                                          uint32_t Rm, uint32_t shift,
+                                          uint32_t amount)
+{
+    LOG_INSTR("ADD X%d, X%d, X%d, %s #%d\n",
+               Rd, Rn, Rm, shift_codes[shift], amount);
+    return ((0x8B << 24) | (shift << 22) | ( Rm << 16) |
+            (amount << 10) |(Rn << 5) | Rd);
+}
+uint32_t ArmToArm64Assembler::A64_ADD_IMM_X(uint32_t Rd, uint32_t Rn,
+                                          uint32_t imm, uint32_t shift)
+{
+    LOG_INSTR("ADD X%d, X%d, #%d, LSL #%d\n", Rd, Rn, imm, shift);
+    return (0x91 << 24) | ((shift/12) << 22) | (imm << 10) | (Rn << 5) | Rd;
+}
+
+uint32_t ArmToArm64Assembler::A64_SUB_IMM_X(uint32_t Rd, uint32_t Rn,
+                                          uint32_t imm, uint32_t shift)
+{
+    LOG_INSTR("SUB X%d, X%d, #%d, LSL #%d\n", Rd, Rn, imm, shift);
+    return (0xD1 << 24) | ((shift/12) << 22) | (imm << 10) | (Rn << 5) | Rd;
+}
+
+uint32_t ArmToArm64Assembler::A64_ADD_W(uint32_t Rd, uint32_t Rn,
+                                          uint32_t Rm, uint32_t shift,
+                                          uint32_t amount)
+{
+    LOG_INSTR("ADD W%d, W%d, W%d, %s #%d\n",
+               Rd, Rn, Rm, shift_codes[shift], amount);
+    return ((0x0B << 24) | (shift << 22) | ( Rm << 16) |
+            (amount << 10) |(Rn << 5) | Rd);
+}
+
+uint32_t ArmToArm64Assembler::A64_SUB_W(uint32_t Rd, uint32_t Rn,
+                                          uint32_t Rm, uint32_t shift,
+                                          uint32_t amount,
+                                          uint32_t setflag)
+{
+    if(setflag == 0)
+    {
+        LOG_INSTR("SUB W%d, W%d, W%d, %s #%d\n",
+               Rd, Rn, Rm, shift_codes[shift], amount);
+        return ((0x4B << 24) | (shift << 22) | ( Rm << 16) |
+                (amount << 10) |(Rn << 5) | Rd);
+    }
+    else
+    {
+        LOG_INSTR("SUBS W%d, W%d, W%d, %s #%d\n",
+                   Rd, Rn, Rm, shift_codes[shift], amount);
+        return ((0x6B << 24) | (shift << 22) | ( Rm << 16) |
+                (amount << 10) |(Rn << 5) | Rd);
+    }
+}
+
+uint32_t ArmToArm64Assembler::A64_AND_W(uint32_t Rd, uint32_t Rn,
+                                          uint32_t Rm, uint32_t shift,
+                                          uint32_t amount)
+{
+    LOG_INSTR("AND W%d, W%d, W%d, %s #%d\n",
+               Rd, Rn, Rm, shift_codes[shift], amount);
+    return ((0x0A << 24) | (shift << 22) | ( Rm << 16) |
+            (amount << 10) |(Rn << 5) | Rd);
+}
+
+uint32_t ArmToArm64Assembler::A64_ORR_W(uint32_t Rd, uint32_t Rn,
+                                          uint32_t Rm, uint32_t shift,
+                                          uint32_t amount)
+{
+    LOG_INSTR("ORR W%d, W%d, W%d, %s #%d\n",
+               Rd, Rn, Rm, shift_codes[shift], amount);
+    return ((0x2A << 24) | (shift << 22) | ( Rm << 16) |
+            (amount << 10) |(Rn << 5) | Rd);
+}
+
+uint32_t ArmToArm64Assembler::A64_ORN_W(uint32_t Rd, uint32_t Rn,
+                                          uint32_t Rm, uint32_t shift,
+                                          uint32_t amount)
+{
+    LOG_INSTR("ORN W%d, W%d, W%d, %s #%d\n",
+               Rd, Rn, Rm, shift_codes[shift], amount);
+    return ((0x2A << 24) | (shift << 22) | (0x1 << 21) | ( Rm << 16) |
+            (amount << 10) |(Rn << 5) | Rd);
+}
+
+uint32_t ArmToArm64Assembler::A64_CSEL_X(uint32_t Rd, uint32_t Rn,
+                                           uint32_t Rm, uint32_t cond)
+{
+    LOG_INSTR("CSEL X%d, X%d, X%d, %s\n", Rd, Rn, Rm, cc_codes[cond]);
+    return ((0x9A << 24)|(0x1 << 23)|(Rm << 16) |(cond << 12)| (Rn << 5) | Rd);
+}
+
+uint32_t ArmToArm64Assembler::A64_CSEL_W(uint32_t Rd, uint32_t Rn,
+                                           uint32_t Rm, uint32_t cond)
+{
+    LOG_INSTR("CSEL W%d, W%d, W%d, %s\n", Rd, Rn, Rm, cc_codes[cond]);
+    return ((0x1A << 24)|(0x1 << 23)|(Rm << 16) |(cond << 12)| (Rn << 5) | Rd);
+}
+
+uint32_t ArmToArm64Assembler::A64_RET(uint32_t Rn)
+{
+    LOG_INSTR("RET X%d\n", Rn);
+    return ((0xD6 << 24) | (0x1 << 22) | (0x1F << 16) | (Rn << 5));
+}
+
+uint32_t ArmToArm64Assembler::A64_MOVZ_X(uint32_t Rd, uint32_t imm,
+                                         uint32_t shift)
+{
+    LOG_INSTR("MOVZ X%d, #0x%x, LSL #%d\n", Rd, imm, shift);
+    return(0xD2 << 24) | (0x1 << 23) | ((shift/16) << 21) |  (imm << 5) | Rd;
+}
+
+uint32_t ArmToArm64Assembler::A64_MOVK_W(uint32_t Rd, uint32_t imm,
+                                         uint32_t shift)
+{
+    LOG_INSTR("MOVK W%d, #0x%x, LSL #%d\n", Rd, imm, shift);
+    return (0x72 << 24) | (0x1 << 23) | ((shift/16) << 21) | (imm << 5) | Rd;
+}
+
+uint32_t ArmToArm64Assembler::A64_MOVZ_W(uint32_t Rd, uint32_t imm,
+                                         uint32_t shift)
+{
+    LOG_INSTR("MOVZ W%d, #0x%x, LSL #%d\n", Rd, imm, shift);
+    return(0x52 << 24) | (0x1 << 23) | ((shift/16) << 21) |  (imm << 5) | Rd;
+}
+
+uint32_t ArmToArm64Assembler::A64_SMADDL(uint32_t Rd, uint32_t Rn,
+                                           uint32_t Rm, uint32_t Ra)
+{
+    LOG_INSTR("SMADDL X%d, W%d, W%d, X%d\n",Rd, Rn, Rm, Ra);
+    return ((0x9B << 24) | (0x1 << 21) | (Rm << 16)|(Ra << 10)|(Rn << 5) | Rd);
+}
+
+uint32_t ArmToArm64Assembler::A64_MADD_W(uint32_t Rd, uint32_t Rn,
+                                           uint32_t Rm, uint32_t Ra)
+{
+    LOG_INSTR("MADD W%d, W%d, W%d, W%d\n",Rd, Rn, Rm, Ra);
+    return ((0x1B << 24) | (Rm << 16) | (Ra << 10) |(Rn << 5) | Rd);
+}
+
+uint32_t ArmToArm64Assembler::A64_SBFM_W(uint32_t Rd, uint32_t Rn,
+                                           uint32_t immr, uint32_t imms)
+{
+    LOG_INSTR("SBFM W%d, W%d, #%d, #%d\n", Rd, Rn, immr, imms);
+    return ((0x13 << 24) | (immr << 16) | (imms << 10) | (Rn << 5) | Rd);
+
+}
+uint32_t ArmToArm64Assembler::A64_UBFM_W(uint32_t Rd, uint32_t Rn,
+                                           uint32_t immr, uint32_t imms)
+{
+    LOG_INSTR("UBFM W%d, W%d, #%d, #%d\n", Rd, Rn, immr, imms);
+    return ((0x53 << 24) | (immr << 16) | (imms << 10) | (Rn << 5) | Rd);
+
+}
+uint32_t ArmToArm64Assembler::A64_UBFM_X(uint32_t Rd, uint32_t Rn,
+                                           uint32_t immr, uint32_t imms)
+{
+    LOG_INSTR("UBFM X%d, X%d, #%d, #%d\n", Rd, Rn, immr, imms);
+    return ((0xD3 << 24) | (0x1 << 22) |
+            (immr << 16) | (imms << 10) | (Rn << 5) | Rd);
+
+}
+uint32_t ArmToArm64Assembler::A64_EXTR_W(uint32_t Rd, uint32_t Rn,
+                                           uint32_t Rm, uint32_t lsb)
+{
+    LOG_INSTR("EXTR W%d, W%d, W%d, #%d\n", Rd, Rn, Rm, lsb);
+    return (0x13 << 24)|(0x1 << 23) | (Rm << 16) | (lsb << 10)|(Rn << 5) | Rd;
+}
+
+}; // namespace android
diff --git a/libpixelflinger/codeflinger/Arm64Assembler.h b/libpixelflinger/codeflinger/Arm64Assembler.h
new file mode 100644
index 0000000..527c757
--- /dev/null
+++ b/libpixelflinger/codeflinger/Arm64Assembler.h
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef ANDROID_ARMTOARM64ASSEMBLER_H
+#define ANDROID_ARMTOARM64ASSEMBLER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "tinyutils/smartpointer.h"
+#include "utils/Vector.h"
+#include "utils/KeyedVector.h"
+
+#include "tinyutils/smartpointer.h"
+#include "codeflinger/ARMAssemblerInterface.h"
+#include "codeflinger/CodeCache.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class ArmToArm64Assembler : public ARMAssemblerInterface
+{
+public:
+    explicit    ArmToArm64Assembler(const sp<Assembly>& assembly);
+    explicit    ArmToArm64Assembler(void *base);
+    virtual     ~ArmToArm64Assembler();
+
+    uint32_t*   base() const;
+    uint32_t*   pc() const;
+
+
+    void        disassemble(const char* name);
+
+    // ------------------------------------------------------------------------
+    // ARMAssemblerInterface...
+    // ------------------------------------------------------------------------
+
+    virtual void    reset();
+
+    virtual int     generate(const char* name);
+    virtual int     getCodegenArch();
+
+    virtual void    prolog();
+    virtual void    epilog(uint32_t touched);
+    virtual void    comment(const char* string);
+
+
+    // -----------------------------------------------------------------------
+    // shifters and addressing modes
+    // -----------------------------------------------------------------------
+
+    // shifters...
+    virtual bool        isValidImmediate(uint32_t immed);
+    virtual int         buildImmediate(uint32_t i, uint32_t& rot, uint32_t& imm);
+
+    virtual uint32_t    imm(uint32_t immediate);
+    virtual uint32_t    reg_imm(int Rm, int type, uint32_t shift);
+    virtual uint32_t    reg_rrx(int Rm);
+    virtual uint32_t    reg_reg(int Rm, int type, int Rs);
+
+    // addressing modes...
+    virtual uint32_t    immed12_pre(int32_t immed12, int W=0);
+    virtual uint32_t    immed12_post(int32_t immed12);
+    virtual uint32_t    reg_scale_pre(int Rm, int type=0, uint32_t shift=0, int W=0);
+    virtual uint32_t    reg_scale_post(int Rm, int type=0, uint32_t shift=0);
+    virtual uint32_t    immed8_pre(int32_t immed8, int W=0);
+    virtual uint32_t    immed8_post(int32_t immed8);
+    virtual uint32_t    reg_pre(int Rm, int W=0);
+    virtual uint32_t    reg_post(int Rm);
+
+
+    virtual void    dataProcessing(int opcode, int cc, int s,
+                                int Rd, int Rn,
+                                uint32_t Op2);
+    virtual void MLA(int cc, int s,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void MUL(int cc, int s,
+                int Rd, int Rm, int Rs);
+    virtual void UMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void UMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void SMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void SMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+
+    virtual void B(int cc, uint32_t* pc);
+    virtual void BL(int cc, uint32_t* pc);
+    virtual void BX(int cc, int Rn);
+    virtual void label(const char* theLabel);
+    virtual void B(int cc, const char* label);
+    virtual void BL(int cc, const char* label);
+
+    virtual uint32_t* pcForLabel(const char* label);
+
+    virtual void ADDR_LDR(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void ADDR_ADD(int cc, int s, int Rd,
+                int Rn, uint32_t Op2);
+    virtual void ADDR_SUB(int cc, int s, int Rd,
+                int Rn, uint32_t Op2);
+    virtual void ADDR_STR (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+
+    virtual void LDR (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRB(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void STR (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void STRB(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRH (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRSB(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRSH(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void STRH (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+
+
+    virtual void LDM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list);
+    virtual void STM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list);
+
+    virtual void SWP(int cc, int Rn, int Rd, int Rm);
+    virtual void SWPB(int cc, int Rn, int Rd, int Rm);
+    virtual void SWI(int cc, uint32_t comment);
+
+    virtual void PLD(int Rn, uint32_t offset);
+    virtual void CLZ(int cc, int Rd, int Rm);
+    virtual void QADD(int cc, int Rd, int Rm, int Rn);
+    virtual void QDADD(int cc, int Rd, int Rm, int Rn);
+    virtual void QSUB(int cc, int Rd, int Rm, int Rn);
+    virtual void QDSUB(int cc, int Rd, int Rm, int Rn);
+    virtual void SMUL(int cc, int xy,
+                int Rd, int Rm, int Rs);
+    virtual void SMULW(int cc, int y,
+                int Rd, int Rm, int Rs);
+    virtual void SMLA(int cc, int xy,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void SMLAL(int cc, int xy,
+                int RdHi, int RdLo, int Rs, int Rm);
+    virtual void SMLAW(int cc, int y,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void UXTB16(int cc, int Rd, int Rm, int rotate);
+    virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width);
+
+private:
+    ArmToArm64Assembler(const ArmToArm64Assembler& rhs);
+    ArmToArm64Assembler& operator = (const ArmToArm64Assembler& rhs);
+
+    // -----------------------------------------------------------------------
+    // helper functions
+    // -----------------------------------------------------------------------
+
+    void dataTransfer(int operation, int cc, int Rd, int Rn,
+                      uint32_t operand_type, uint32_t size = 32);
+    void dataProcessingCommon(int opcode, int s,
+                      int Rd, int Rn, uint32_t Op2);
+
+    // -----------------------------------------------------------------------
+    // Arm64 instructions
+    // -----------------------------------------------------------------------
+    uint32_t A64_B_COND(uint32_t cc, uint32_t offset);
+    uint32_t A64_RET(uint32_t Rn);
+
+    uint32_t A64_LDRSTR_Wm_SXTW_0(uint32_t operation,
+                                uint32_t size, uint32_t Rt,
+                                uint32_t Rn, uint32_t Rm);
+
+    uint32_t A64_STR_IMM_PreIndex(uint32_t Rt, uint32_t Rn, int32_t simm);
+    uint32_t A64_LDR_IMM_PostIndex(uint32_t Rt,uint32_t Rn, int32_t simm);
+
+    uint32_t A64_ADD_X_Wm_SXTW(uint32_t Rd, uint32_t Rn, uint32_t Rm,
+                               uint32_t amount);
+    uint32_t A64_SUB_X_Wm_SXTW(uint32_t Rd, uint32_t Rn, uint32_t Rm,
+                               uint32_t amount);
+
+    uint32_t A64_ADD_IMM_X(uint32_t Rd, uint32_t Rn,
+                           uint32_t imm, uint32_t shift = 0);
+    uint32_t A64_SUB_IMM_X(uint32_t Rd, uint32_t Rn,
+                           uint32_t imm, uint32_t shift = 0);
+
+    uint32_t A64_ADD_X(uint32_t Rd, uint32_t Rn,
+                       uint32_t Rm, uint32_t shift = 0, uint32_t amount = 0);
+    uint32_t A64_ADD_W(uint32_t Rd, uint32_t Rn, uint32_t Rm,
+                       uint32_t shift = 0, uint32_t amount = 0);
+    uint32_t A64_SUB_W(uint32_t Rd, uint32_t Rn, uint32_t Rm,
+                       uint32_t shift = 0, uint32_t amount = 0,
+                       uint32_t setflag = 0);
+    uint32_t A64_AND_W(uint32_t Rd, uint32_t Rn,
+                       uint32_t Rm, uint32_t shift = 0, uint32_t amount = 0);
+    uint32_t A64_ORR_W(uint32_t Rd, uint32_t Rn,
+                       uint32_t Rm, uint32_t shift = 0, uint32_t amount = 0);
+    uint32_t A64_ORN_W(uint32_t Rd, uint32_t Rn,
+                       uint32_t Rm, uint32_t shift = 0, uint32_t amount = 0);
+
+    uint32_t A64_MOVZ_W(uint32_t Rd, uint32_t imm, uint32_t shift);
+    uint32_t A64_MOVZ_X(uint32_t Rd, uint32_t imm, uint32_t shift);
+    uint32_t A64_MOVK_W(uint32_t Rd, uint32_t imm, uint32_t shift);
+
+    uint32_t A64_SMADDL(uint32_t Rd, uint32_t Rn, uint32_t Rm, uint32_t Ra);
+    uint32_t A64_MADD_W(uint32_t Rd, uint32_t Rn, uint32_t Rm, uint32_t Ra);
+
+    uint32_t A64_SBFM_W(uint32_t Rd, uint32_t Rn,
+                        uint32_t immr, uint32_t imms);
+    uint32_t A64_UBFM_W(uint32_t Rd, uint32_t Rn,
+                        uint32_t immr, uint32_t imms);
+    uint32_t A64_UBFM_X(uint32_t Rd, uint32_t Rn,
+                        uint32_t immr, uint32_t imms);
+
+    uint32_t A64_EXTR_W(uint32_t Rd, uint32_t Rn, uint32_t Rm, uint32_t lsb);
+    uint32_t A64_CSEL_X(uint32_t Rd, uint32_t Rn, uint32_t Rm, uint32_t cond);
+    uint32_t A64_CSEL_W(uint32_t Rd, uint32_t Rn, uint32_t Rm, uint32_t cond);
+
+    uint32_t*       mBase;
+    uint32_t*       mPC;
+    uint32_t*       mPrologPC;
+    int64_t         mDuration;
+    uint32_t        mTmpReg1, mTmpReg2, mTmpReg3, mZeroReg;
+
+    struct branch_target_t {
+        inline branch_target_t() : label(0), pc(0) { }
+        inline branch_target_t(const char* l, uint32_t* p)
+            : label(l), pc(p) { }
+        const char* label;
+        uint32_t*   pc;
+    };
+
+    sp<Assembly>    mAssembly;
+    Vector<branch_target_t>                 mBranchTargets;
+    KeyedVector< const char*, uint32_t* >   mLabels;
+    KeyedVector< uint32_t*, const char* >   mLabelsInverseMapping;
+    KeyedVector< uint32_t*, const char* >   mComments;
+
+    enum operand_type_t
+    {
+        OPERAND_REG = 0x20,
+        OPERAND_IMM,
+        OPERAND_REG_IMM,
+        OPERAND_REG_OFFSET,
+        OPERAND_UNSUPPORTED
+    };
+
+    struct addr_mode_t {
+        int32_t         immediate;
+        bool            writeback;
+        bool            preindex;
+        bool            postindex;
+        int32_t         reg_imm_Rm;
+        int32_t         reg_imm_type;
+        uint32_t        reg_imm_shift;
+        int32_t         reg_offset;
+    } mAddrMode;
+
+};
+
+}; // namespace android
+
+#endif //ANDROID_ARM64ASSEMBLER_H
diff --git a/libpixelflinger/codeflinger/Arm64Disassembler.cpp b/libpixelflinger/codeflinger/Arm64Disassembler.cpp
new file mode 100644
index 0000000..70f1ff1
--- /dev/null
+++ b/libpixelflinger/codeflinger/Arm64Disassembler.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <string.h>
+
+struct disasm_table_entry_t
+{
+    uint32_t       mask;
+    uint32_t       value;
+    const char*    instr_template;
+};
+
+
+static disasm_table_entry_t disasm_table[] =
+{
+    {0xff000000, 0x91000000, "add <xd|sp>, <xn|sp>, #<imm1>, <shift1>"},
+    {0xff000000, 0xd1000000, "sub <xd|sp>, <xn|sp>, #<imm1>, <shift1>"},
+    {0xff200000, 0x8b000000, "add <xd>, <xn>, <xm>, <shift2> #<amt1>"},
+    {0xff200000, 0x0b000000, "add <wd>, <wn>, <wm>, <shift2> #<amt1>"},
+    {0xff200000, 0x4b000000, "sub <wd>, <wn>, <wm>, <shift2> #<amt1>"},
+    {0xff200000, 0x6b000000, "subs <wd>, <wn>, <wm>, <shift2> #<amt1>"},
+    {0xff200000, 0x0a000000, "and <wd>, <wn>, <wm>, <shift2> #<amt1>"},
+    {0xff200000, 0x2a000000, "orr <wd>, <wn>, <wm>, <shift2> #<amt1>"},
+    {0xff200000, 0x2a200000, "orn <wd>, <wn>, <wm>, <shift2> #<amt1>"},
+    {0xff800000, 0x72800000, "movk <wd>, #<imm2>, lsl #<shift3>"},
+    {0xff800000, 0x52800000, "movz <wd>, #<imm2>, lsl #<shift3>"},
+    {0xff800000, 0xd2800000, "movz <xd>, #<imm2>, lsl #<shift3>"},
+    {0xffe00c00, 0x1a800000, "csel <wd>, <wn>, <wm>, <cond1>"},
+    {0xffe00c00, 0x9a800000, "csel <xd>, <xn>, <xm>, <cond1>"},
+    {0xffe00c00, 0x5a800000, "csinv <wd>, <wn>, <wm>, <cond1>"},
+    {0xffe08000, 0x1b000000, "madd <wd>, <wn>, <wm>, <wa>"},
+    {0xffe08000, 0x9b200000, "smaddl <xd>, <wn>, <wm>, <xa>"},
+    {0xffe04c00, 0xb8604800, "ldr <wt>, [<xn|sp>, <r1><m1>, <ext1> #<amt2>]"},
+    {0xffe04c00, 0xb8204800, "str <wt>, [<xn|sp>, <r1><m1>, <ext1> #<amt2>]"},
+    {0xffe04c00, 0xf8604800, "ldr <xt>, [<xn|sp>, <r1><m1>, <ext1> #<amt3>]"},
+    {0xffe04c00, 0xf8204800, "str <xt>, [<xn|sp>, <r1><m1>, <ext1> #<amt3>]"},
+    {0xffe04c00, 0x38604800, "ldrb <wt>, [<xn|sp>, <r1><m1>, <ext1> <amt5>]"},
+    {0xffe04c00, 0x38204800, "strb <wt>, [<xn|sp>, <r1><m1>, <ext1> <amt5>]"},
+    {0xffe04c00, 0x78604800, "ldrh <wt>, [<xn|sp>, <r1><m1>, <ext1> #<amt6>]"},
+    {0xffe04c00, 0x78204800, "strh <wt>, [<xn|sp>, <r1><m1>, <ext1> #<amt6>]"},
+    {0xffe00c00, 0xb8400400, "ldr <wt>, [<xn|sp>], #<simm1>"},
+    {0xffe00c00, 0xb8000c00, "str <wt>, [<xn|sp>, #<simm1>]!"},
+    {0xffc00000, 0x13000000, "sbfm <wd>, <wn>, #<immr1>, #<imms1>"},
+    {0xffc00000, 0x53000000, "ubfm <wd>, <wn>, #<immr1>, #<imms1>"},
+    {0xffc00000, 0xd3400000, "ubfm <xd>, <xn>, #<immr1>, #<imms1>"},
+    {0xffe00000, 0x13800000, "extr <wd>, <wn>, <wm>, #<lsb1>"},
+    {0xff000000, 0x54000000, "b.<cond2> <label1>"},
+    {0xfffffc1f, 0xd65f0000, "ret <xn>"},
+    {0xffe00000, 0x8b200000, "add <xd|sp>, <xn|sp>, <r2><m1>, <ext2> #<amt4>"},
+    {0xffe00000, 0xcb200000, "sub <xd|sp>, <xn|sp>, <r2><m1>, <ext2> #<amt4>"}
+};
+
+static int32_t bits_signed(uint32_t instr, uint32_t msb, uint32_t lsb)
+{
+    int32_t value;
+    value   = ((int32_t)instr) << (31 - msb);
+    value >>= (31 - msb);
+    value >>= lsb;
+    return value;
+}
+static uint32_t bits_unsigned(uint32_t instr, uint32_t msb, uint32_t lsb)
+{
+    uint32_t width = msb - lsb + 1;
+    uint32_t mask  = (1 << width) - 1;
+    return ((instr >> lsb) & mask);
+}
+
+static void get_token(const char *instr, uint32_t index, char *token)
+{
+    uint32_t i, j;
+    for(i = index, j = 0; i < strlen(instr); ++i)
+    {
+        if(instr[index] == '<' && instr[i] == '>')
+        {
+            token[j++] = instr[i];
+            break;
+        }
+        else if(instr[index] != '<' && instr[i] == '<')
+        {
+            break;
+        }
+        else
+        {
+            token[j++] = instr[i];
+        }
+    }
+    token[j] = '\0';
+    return;
+}
+
+
+static const char * token_cc_table[] =
+{
+    "eq", "ne", "cs", "cc", "mi",
+    "pl", "vs", "vc", "hi", "ls",
+    "ge", "lt", "gt", "le", "al", "nv"
+};
+
+static void decode_rx_zr_token(uint32_t reg, const char *prefix, char *instr_part)
+{
+    if(reg == 31)
+        sprintf(instr_part, "%s%s", prefix, "zr");
+    else
+        sprintf(instr_part, "%s%d", prefix, reg);
+}
+
+static void decode_token(uint32_t code, char *token, char *instr_part)
+{
+    if(strcmp(token, "<imm1>") == 0)
+        sprintf(instr_part, "0x%x", bits_unsigned(code, 21,10));
+    else if(strcmp(token, "<imm2>") == 0)
+        sprintf(instr_part, "0x%x", bits_unsigned(code, 20,5));
+    else if(strcmp(token, "<shift1>") == 0)
+        sprintf(instr_part, "lsl #%d", bits_unsigned(code, 23,22) * 12);
+    else if(strcmp(token, "<shift2>") == 0)
+    {
+        static const char * shift2_table[] = { "lsl", "lsr", "asr", "ror"};
+        sprintf(instr_part, "%s", shift2_table[bits_unsigned(code, 23,22)]);
+    }
+    else if(strcmp(token, "<shift3>") == 0)
+        sprintf(instr_part, "%d", bits_unsigned(code, 22,21) * 16);
+    else if(strcmp(token, "<amt1>") == 0)
+        sprintf(instr_part, "%d", bits_unsigned(code, 15,10));
+    else if(strcmp(token, "<amt2>") == 0)
+        sprintf(instr_part, "%d", bits_unsigned(code, 12,12) * 2);
+    else if(strcmp(token, "<amt3>") == 0)
+        sprintf(instr_part, "%d", bits_unsigned(code, 12,12) * 3);
+    else if(strcmp(token, "<amt4>") == 0)
+        sprintf(instr_part, "%d", bits_unsigned(code, 12,10));
+    else if(strcmp(token, "<amt5>") == 0)
+    {
+        static const char * amt5_table[] = {"", "#0"};
+        sprintf(instr_part, "%s", amt5_table[bits_unsigned(code, 12,12)]);
+    }
+    else if(strcmp(token, "<amt6>") == 0)
+        sprintf(instr_part, "%d", bits_unsigned(code, 12,12));
+    else if(strcmp(token, "<simm1>") == 0)
+        sprintf(instr_part, "%d", bits_signed(code, 20,12));
+    else if(strcmp(token, "<immr1>") == 0)
+        sprintf(instr_part, "%d", bits_unsigned(code, 21,16));
+    else if(strcmp(token, "<imms1>") == 0)
+        sprintf(instr_part, "%d", bits_unsigned(code, 15,10));
+    else if(strcmp(token, "<lsb1>") == 0)
+        sprintf(instr_part, "%d", bits_unsigned(code, 15,10));
+    else if(strcmp(token, "<cond1>") == 0)
+        sprintf(instr_part, "%s", token_cc_table[bits_unsigned(code, 15,12)]);
+    else if(strcmp(token, "<cond2>") == 0)
+        sprintf(instr_part, "%s", token_cc_table[bits_unsigned(code, 4,0)]);
+    else if(strcmp(token, "<r1>") == 0)
+    {
+        const char * token_r1_table[] =
+        {
+            "reserved", "reserved", "w", "x",
+            "reserved", "reserved", "w", "x"
+        };
+        sprintf(instr_part, "%s", token_r1_table[bits_unsigned(code, 15,13)]);
+    }
+    else if(strcmp(token, "<r2>") == 0)
+    {
+        static const char * token_r2_table[] =
+        {
+                "w","w","w", "x", "w", "w", "w", "x"
+        };
+        sprintf(instr_part, "%s", token_r2_table[bits_unsigned(code, 15,13)]);
+    }
+    else if(strcmp(token, "<m1>") == 0)
+    {
+        uint32_t reg = bits_unsigned(code, 20,16);
+        if(reg == 31)
+            sprintf(instr_part, "%s", "zr");
+        else
+            sprintf(instr_part, "%d", reg);
+    }
+    else if(strcmp(token, "<ext1>") == 0)
+    {
+        static const char * token_ext1_table[] =
+        {
+             "reserved","reserved","uxtw", "lsl",
+             "reserved","reserved", "sxtw", "sxtx"
+        };
+        sprintf(instr_part, "%s", token_ext1_table[bits_unsigned(code, 15,13)]);
+    }
+    else if(strcmp(token, "<ext2>") == 0)
+    {
+        static const char * token_ext2_table[] =
+        {
+                "uxtb","uxth","uxtw","uxtx",
+                "sxtb","sxth","sxtw","sxtx"
+        };
+        sprintf(instr_part, "%s", token_ext2_table[bits_unsigned(code, 15,13)]);
+    }
+    else if (strcmp(token, "<label1>") == 0)
+    {
+        int32_t offset = bits_signed(code, 23,5) * 4;
+        if(offset > 0)
+            sprintf(instr_part, "#.+%d", offset);
+        else
+            sprintf(instr_part, "#.-%d", -offset);
+    }
+    else if (strcmp(token, "<xn|sp>") == 0)
+    {
+        uint32_t reg = bits_unsigned(code, 9, 5);
+        if(reg == 31)
+            sprintf(instr_part, "%s", "sp");
+        else
+            sprintf(instr_part, "x%d", reg);
+    }
+    else if (strcmp(token, "<xd|sp>") == 0)
+    {
+        uint32_t reg = bits_unsigned(code, 4, 0);
+        if(reg == 31)
+            sprintf(instr_part, "%s", "sp");
+        else
+            sprintf(instr_part, "x%d", reg);
+    }
+    else if (strcmp(token, "<xn>") == 0)
+        decode_rx_zr_token(bits_unsigned(code, 9, 5), "x", instr_part);
+    else if (strcmp(token, "<xd>") == 0)
+        decode_rx_zr_token(bits_unsigned(code, 4, 0), "x", instr_part);
+    else if (strcmp(token, "<xm>") == 0)
+        decode_rx_zr_token(bits_unsigned(code, 20, 16), "x", instr_part);
+    else if (strcmp(token, "<xa>") == 0)
+        decode_rx_zr_token(bits_unsigned(code, 14, 10), "x", instr_part);
+    else if (strcmp(token, "<xt>") == 0)
+        decode_rx_zr_token(bits_unsigned(code, 4, 0), "x", instr_part);
+    else if (strcmp(token, "<wn>") == 0)
+        decode_rx_zr_token(bits_unsigned(code, 9, 5), "w", instr_part);
+    else if (strcmp(token, "<wd>") == 0)
+        decode_rx_zr_token(bits_unsigned(code, 4, 0), "w", instr_part);
+    else if (strcmp(token, "<wm>") == 0)
+        decode_rx_zr_token(bits_unsigned(code, 20, 16), "w", instr_part);
+    else if (strcmp(token, "<wa>") == 0)
+        decode_rx_zr_token(bits_unsigned(code, 14, 10), "w", instr_part);
+    else if (strcmp(token, "<wt>") == 0)
+        decode_rx_zr_token(bits_unsigned(code, 4, 0), "w", instr_part);
+    else
+    {
+        sprintf(instr_part, "error");
+    }
+    return;
+}
+
+int arm64_disassemble(uint32_t code, char* instr)
+{
+    uint32_t i;
+    char token[256];
+    char instr_part[256];
+
+    if(instr == NULL)
+        return -1;
+
+    bool matched = false;
+    disasm_table_entry_t *entry = NULL;
+    for(i = 0; i < sizeof(disasm_table)/sizeof(disasm_table_entry_t); ++i)
+    {
+        entry = &disasm_table[i];
+        if((code & entry->mask) == entry->value)
+        {
+            matched = true;
+            break;
+        }
+    }
+    if(matched == false)
+    {
+        strcpy(instr, "Unknown Instruction");
+        return -1;
+    }
+    else
+    {
+        uint32_t index = 0;
+        uint32_t length = strlen(entry->instr_template);
+        instr[0] = '\0';
+        do
+        {
+            get_token(entry->instr_template, index, token);
+            if(token[0] == '<')
+            {
+                decode_token(code, token, instr_part);
+                strcat(instr, instr_part);
+            }
+            else
+            {
+                strcat(instr, token);
+            }
+            index += strlen(token);
+        }while(index < length);
+        return 0;
+    }
+}
diff --git a/libpixelflinger/codeflinger/Arm64Disassembler.h b/libpixelflinger/codeflinger/Arm64Disassembler.h
new file mode 100644
index 0000000..86f3aba
--- /dev/null
+++ b/libpixelflinger/codeflinger/Arm64Disassembler.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef ANDROID_ARM64DISASSEMBLER_H
+#define ANDROID_ARM64DISASSEMBLER_H
+
+#include <inttypes.h>
+int arm64_disassemble(uint32_t code, char* instr);
+
+#endif //ANDROID_ARM64ASSEMBLER_H
diff --git a/libpixelflinger/codeflinger/CodeCache.cpp b/libpixelflinger/codeflinger/CodeCache.cpp
new file mode 100644
index 0000000..32691a3
--- /dev/null
+++ b/libpixelflinger/codeflinger/CodeCache.cpp
@@ -0,0 +1,217 @@
+/* libs/pixelflinger/codeflinger/CodeCache.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "CodeCache"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <cutils/ashmem.h>
+#include <log/log.h>
+
+#include "CodeCache.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+#if defined(__arm__) || defined(__aarch64__)
+#include <unistd.h>
+#include <errno.h>
+#endif
+
+#if defined(__mips__)
+#include <asm/cachectl.h>
+#include <errno.h>
+#endif
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+
+// A dlmalloc mspace is used to manage the code cache over a mmaped region.
+#define HAVE_MMAP 0
+#define HAVE_MREMAP 0
+#define HAVE_MORECORE 0
+#define MALLOC_ALIGNMENT 16
+#define MSPACES 1
+#define NO_MALLINFO 1
+#define ONLY_MSPACES 1
+// Custom heap error handling.
+#define PROCEED_ON_ERROR 0
+static void heap_error(const char* msg, const char* function, void* p);
+#define CORRUPTION_ERROR_ACTION(m) \
+    heap_error("HEAP MEMORY CORRUPTION", __FUNCTION__, NULL)
+#define USAGE_ERROR_ACTION(m,p) \
+    heap_error("ARGUMENT IS INVALID HEAP ADDRESS", __FUNCTION__, p)
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wexpansion-to-defined"
+#pragma GCC diagnostic ignored "-Wnull-pointer-arithmetic"
+#include "../../../../external/dlmalloc/malloc.c"
+#pragma GCC diagnostic pop
+
+static void heap_error(const char* msg, const char* function, void* p) {
+    ALOG(LOG_FATAL, LOG_TAG, "@@@ ABORTING: CODE FLINGER: %s IN %s addr=%p",
+         msg, function, p);
+    /* So that we can get a memory dump around p */
+    *((int **) 0xdeadbaad) = (int *) p;
+}
+
+// ----------------------------------------------------------------------------
+
+static void* gExecutableStore = NULL;
+static mspace gMspace = NULL;
+const size_t kMaxCodeCacheCapacity = 1024 * 1024;
+
+static mspace getMspace()
+{
+    if (gExecutableStore == NULL) {
+        int fd = ashmem_create_region("CodeFlinger code cache",
+                                      kMaxCodeCacheCapacity);
+        LOG_ALWAYS_FATAL_IF(fd < 0,
+                            "Creating code cache, ashmem_create_region "
+                            "failed with error '%s'", strerror(errno));
+        gExecutableStore = mmap(NULL, kMaxCodeCacheCapacity,
+                                PROT_READ | PROT_WRITE | PROT_EXEC,
+                                MAP_PRIVATE, fd, 0);
+        LOG_ALWAYS_FATAL_IF(gExecutableStore == MAP_FAILED,
+                            "Creating code cache, mmap failed with error "
+                            "'%s'", strerror(errno));
+        close(fd);
+        gMspace = create_mspace_with_base(gExecutableStore, kMaxCodeCacheCapacity,
+                                          /*locked=*/ false);
+        mspace_set_footprint_limit(gMspace, kMaxCodeCacheCapacity);
+    }
+    return gMspace;
+}
+
+Assembly::Assembly(size_t size)
+    : mCount(0), mSize(0)
+{
+    mBase = (uint32_t*)mspace_malloc(getMspace(), size);
+    LOG_ALWAYS_FATAL_IF(mBase == NULL,
+                        "Failed to create Assembly of size %zd in executable "
+                        "store of size %zd", size, kMaxCodeCacheCapacity);
+    mSize = size;
+}
+
+Assembly::~Assembly()
+{
+    mspace_free(getMspace(), mBase);
+}
+
+void Assembly::incStrong(const void*) const
+{
+    mCount.fetch_add(1, std::memory_order_relaxed);
+}
+
+void Assembly::decStrong(const void*) const
+{
+    if (mCount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+        delete this;
+    }
+}
+
+ssize_t Assembly::size() const
+{
+    if (!mBase) return NO_MEMORY;
+    return mSize;
+}
+
+uint32_t* Assembly::base() const
+{
+    return mBase;
+}
+
+ssize_t Assembly::resize(size_t newSize)
+{
+    mBase = (uint32_t*)mspace_realloc(getMspace(), mBase, newSize);
+    LOG_ALWAYS_FATAL_IF(mBase == NULL,
+                        "Failed to resize Assembly to %zd in code cache "
+                        "of size %zd", newSize, kMaxCodeCacheCapacity);
+    mSize = newSize;
+    return size();
+}
+
+// ----------------------------------------------------------------------------
+
+CodeCache::CodeCache(size_t size)
+    : mCacheSize(size), mCacheInUse(0)
+{
+    pthread_mutex_init(&mLock, 0);
+}
+
+CodeCache::~CodeCache()
+{
+    pthread_mutex_destroy(&mLock);
+}
+
+sp<Assembly> CodeCache::lookup(const AssemblyKeyBase& keyBase) const
+{
+    pthread_mutex_lock(&mLock);
+    sp<Assembly> r;
+    ssize_t index = mCacheData.indexOfKey(key_t(keyBase));
+    if (index >= 0) {
+        const cache_entry_t& e = mCacheData.valueAt(index);
+        e.when = mWhen++;
+        r = e.entry;
+    }
+    pthread_mutex_unlock(&mLock);
+    return r;
+}
+
+int CodeCache::cache(  const AssemblyKeyBase& keyBase,
+                            const sp<Assembly>& assembly)
+{
+    pthread_mutex_lock(&mLock);
+
+    const ssize_t assemblySize = assembly->size();
+    while (mCacheInUse + assemblySize > mCacheSize) {
+        // evict the LRU
+        size_t lru = 0;
+        size_t count = mCacheData.size();
+        for (size_t i=0 ; i<count ; i++) {
+            const cache_entry_t& e = mCacheData.valueAt(i);
+            if (e.when < mCacheData.valueAt(lru).when) {
+                lru = i;
+            }
+        }
+        const cache_entry_t& e = mCacheData.valueAt(lru);
+        mCacheInUse -= e.entry->size();
+        mCacheData.removeItemsAt(lru);
+    }
+
+    ssize_t err = mCacheData.add(key_t(keyBase), cache_entry_t(assembly, mWhen));
+    if (err >= 0) {
+        mCacheInUse += assemblySize;
+        mWhen++;
+        // synchronize caches...
+        char* base = reinterpret_cast<char*>(assembly->base());
+        char* curr = reinterpret_cast<char*>(base + assembly->size());
+        __builtin___clear_cache(base, curr);
+    }
+
+    pthread_mutex_unlock(&mLock);
+    return err;
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
diff --git a/libpixelflinger/codeflinger/CodeCache.h b/libpixelflinger/codeflinger/CodeCache.h
new file mode 100644
index 0000000..9326453
--- /dev/null
+++ b/libpixelflinger/codeflinger/CodeCache.h
@@ -0,0 +1,136 @@
+/* libs/pixelflinger/codeflinger/CodeCache.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#ifndef ANDROID_CODECACHE_H
+#define ANDROID_CODECACHE_H
+
+#include <atomic>
+#include <stdint.h>
+#include <pthread.h>
+#include <sys/types.h>
+
+#include "utils/KeyedVector.h"
+#include "tinyutils/smartpointer.h"
+
+namespace android {
+
+using namespace tinyutils;
+
+// ----------------------------------------------------------------------------
+
+class AssemblyKeyBase {
+public:
+    virtual ~AssemblyKeyBase() { }
+    virtual int compare_type(const AssemblyKeyBase& key) const = 0;
+};
+
+template  <typename T>
+class AssemblyKey : public AssemblyKeyBase
+{
+public:
+    explicit AssemblyKey(const T& rhs) : mKey(rhs) { }
+    virtual int compare_type(const AssemblyKeyBase& key) const {
+        const T& rhs = static_cast<const AssemblyKey&>(key).mKey;
+        return android::compare_type(mKey, rhs);
+    }
+private:
+    T mKey;
+};
+
+// ----------------------------------------------------------------------------
+
+class Assembly
+{
+public:
+    explicit    Assembly(size_t size);
+    virtual     ~Assembly();
+
+    ssize_t     size() const;
+    uint32_t*   base() const;
+    ssize_t     resize(size_t size);
+
+    // protocol for sp<>
+            void    incStrong(const void* id) const;
+            void    decStrong(const void* id) const;
+    typedef void    weakref_type;
+
+private:
+    mutable std::atomic<int32_t>     mCount;
+            uint32_t*   mBase;
+            size_t      mSize;
+};
+
+// ----------------------------------------------------------------------------
+
+class CodeCache
+{
+public:
+// pretty simple cache API...
+    explicit            CodeCache(size_t size);
+                        ~CodeCache();
+
+    sp<Assembly>        lookup(const AssemblyKeyBase& key) const;
+
+    int                 cache(const AssemblyKeyBase& key,
+                              const sp<Assembly>& assembly);
+
+private:
+    // nothing to see here...
+    struct cache_entry_t {
+        inline cache_entry_t() { }
+        inline cache_entry_t(const sp<Assembly>& a, int64_t w)
+                : entry(a), when(w) { }
+        sp<Assembly>            entry;
+        mutable int64_t         when;
+    };
+
+    class key_t {
+        friend int compare_type(
+            const key_value_pair_t<key_t, cache_entry_t>&,
+            const key_value_pair_t<key_t, cache_entry_t>&);
+        const AssemblyKeyBase* mKey;
+    public:
+        key_t() { };
+        explicit key_t(const AssemblyKeyBase& k) : mKey(&k)  { }
+    };
+
+    mutable pthread_mutex_t             mLock;
+    mutable int64_t                     mWhen;
+    size_t                              mCacheSize;
+    size_t                              mCacheInUse;
+    KeyedVector<key_t, cache_entry_t>   mCacheData;
+
+    friend int compare_type(
+        const key_value_pair_t<key_t, cache_entry_t>&,
+        const key_value_pair_t<key_t, cache_entry_t>&);
+};
+
+// KeyedVector uses compare_type(), which is more efficient, than
+// just using operator < ()
+inline int compare_type(
+    const key_value_pair_t<CodeCache::key_t, CodeCache::cache_entry_t>& lhs,
+    const key_value_pair_t<CodeCache::key_t, CodeCache::cache_entry_t>& rhs)
+{
+    return lhs.key.mKey->compare_type(*(rhs.key.mKey));
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif //ANDROID_CODECACHE_H
diff --git a/libpixelflinger/codeflinger/GGLAssembler.cpp b/libpixelflinger/codeflinger/GGLAssembler.cpp
new file mode 100644
index 0000000..04e285d
--- /dev/null
+++ b/libpixelflinger/codeflinger/GGLAssembler.cpp
@@ -0,0 +1,1195 @@
+/* libs/pixelflinger/codeflinger/GGLAssembler.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "GGLAssembler"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <log/log.h>
+
+#include "GGLAssembler.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+GGLAssembler::GGLAssembler(ARMAssemblerInterface* target)
+    : ARMAssemblerProxy(target),
+      RegisterAllocator(ARMAssemblerProxy::getCodegenArch()), mOptLevel(7)
+{
+}
+
+GGLAssembler::~GGLAssembler()
+{
+}
+
+void GGLAssembler::prolog()
+{
+    ARMAssemblerProxy::prolog();
+}
+
+void GGLAssembler::epilog(uint32_t touched)
+{
+    ARMAssemblerProxy::epilog(touched);
+}
+
+void GGLAssembler::reset(int opt_level)
+{
+    ARMAssemblerProxy::reset();
+    RegisterAllocator::reset();
+    mOptLevel = opt_level;
+}
+
+// ---------------------------------------------------------------------------
+
+int GGLAssembler::scanline(const needs_t& needs, context_t const* c)
+{
+    int err = 0;
+    int opt_level = mOptLevel;
+    while (opt_level >= 0) {
+        reset(opt_level);
+        err = scanline_core(needs, c);
+        if (err == 0)
+            break;
+        opt_level--;
+    }
+    
+    // XXX: in theory, pcForLabel is not valid before generate()
+    uint32_t* fragment_start_pc = pcForLabel("fragment_loop");
+    uint32_t* fragment_end_pc = pcForLabel("epilog");
+    const int per_fragment_ops = int(fragment_end_pc - fragment_start_pc);
+    
+    // build a name for our pipeline
+    char name[64];    
+    sprintf(name,
+            "scanline__%08X:%08X_%08X_%08X [%3d ipp]",
+            needs.p, needs.n, needs.t[0], needs.t[1], per_fragment_ops);
+
+    if (err) {
+        ALOGE("Error while generating ""%s""\n", name);
+        disassemble(name);
+        return -1;
+    }
+
+    return generate(name);
+}
+
+int GGLAssembler::scanline_core(const needs_t& needs, context_t const* c)
+{
+    mBlendFactorCached = 0;
+    mBlending = 0;
+    mMasking = 0;
+    mAA        = GGL_READ_NEEDS(P_AA, needs.p);
+    mDithering = GGL_READ_NEEDS(P_DITHER, needs.p);
+    mAlphaTest = GGL_READ_NEEDS(P_ALPHA_TEST, needs.p) + GGL_NEVER;
+    mDepthTest = GGL_READ_NEEDS(P_DEPTH_TEST, needs.p) + GGL_NEVER;
+    mFog       = GGL_READ_NEEDS(P_FOG, needs.p) != 0;
+    mSmooth    = GGL_READ_NEEDS(SHADE, needs.n) != 0;
+    mBuilderContext.needs = needs;
+    mBuilderContext.c = c;
+    mBuilderContext.Rctx = reserveReg(R0); // context always in R0
+    mCbFormat = c->formats[ GGL_READ_NEEDS(CB_FORMAT, needs.n) ];
+
+    // ------------------------------------------------------------------------
+
+    decodeLogicOpNeeds(needs);
+
+    decodeTMUNeeds(needs, c);
+
+    mBlendSrc  = ggl_needs_to_blendfactor(GGL_READ_NEEDS(BLEND_SRC, needs.n));
+    mBlendDst  = ggl_needs_to_blendfactor(GGL_READ_NEEDS(BLEND_DST, needs.n));
+    mBlendSrcA = ggl_needs_to_blendfactor(GGL_READ_NEEDS(BLEND_SRCA, needs.n));
+    mBlendDstA = ggl_needs_to_blendfactor(GGL_READ_NEEDS(BLEND_DSTA, needs.n));
+
+    if (!mCbFormat.c[GGLFormat::ALPHA].h) {
+        if ((mBlendSrc == GGL_ONE_MINUS_DST_ALPHA) ||
+            (mBlendSrc == GGL_DST_ALPHA)) {
+            mBlendSrc = GGL_ONE;
+        }
+        if ((mBlendSrcA == GGL_ONE_MINUS_DST_ALPHA) ||
+            (mBlendSrcA == GGL_DST_ALPHA)) {
+            mBlendSrcA = GGL_ONE;
+        }
+        if ((mBlendDst == GGL_ONE_MINUS_DST_ALPHA) ||
+            (mBlendDst == GGL_DST_ALPHA)) {
+            mBlendDst = GGL_ONE;
+        }
+        if ((mBlendDstA == GGL_ONE_MINUS_DST_ALPHA) ||
+            (mBlendDstA == GGL_DST_ALPHA)) {
+            mBlendDstA = GGL_ONE;
+        }
+    }
+
+    // if we need the framebuffer, read it now
+    const int blending =    blending_codes(mBlendSrc, mBlendDst) |
+                            blending_codes(mBlendSrcA, mBlendDstA);
+
+    // XXX: handle special cases, destination not modified...
+    if ((mBlendSrc==GGL_ZERO) && (mBlendSrcA==GGL_ZERO) &&
+        (mBlendDst==GGL_ONE) && (mBlendDstA==GGL_ONE)) {
+        // Destination unmodified (beware of logic ops)
+    } else if ((mBlendSrc==GGL_ZERO) && (mBlendSrcA==GGL_ZERO) &&
+        (mBlendDst==GGL_ZERO) && (mBlendDstA==GGL_ZERO)) {
+        // Destination is zero (beware of logic ops)
+    }
+    
+    int fbComponents = 0;
+    const int masking = GGL_READ_NEEDS(MASK_ARGB, needs.n);
+    for (int i=0 ; i<4 ; i++) {
+        const int mask = 1<<i;
+        component_info_t& info = mInfo[i];
+        int fs = i==GGLFormat::ALPHA ? mBlendSrcA : mBlendSrc;
+        int fd = i==GGLFormat::ALPHA ? mBlendDstA : mBlendDst;
+        if (fs==GGL_SRC_ALPHA_SATURATE && i==GGLFormat::ALPHA)
+            fs = GGL_ONE;
+        info.masked =   !!(masking & mask);
+        info.inDest =   !info.masked && mCbFormat.c[i].h && 
+                        ((mLogicOp & LOGIC_OP_SRC) || (!mLogicOp));
+        if (mCbFormat.components >= GGL_LUMINANCE &&
+                (i==GGLFormat::GREEN || i==GGLFormat::BLUE)) {
+            info.inDest = false;
+        }
+        info.needed =   (i==GGLFormat::ALPHA) && 
+                        (isAlphaSourceNeeded() || mAlphaTest != GGL_ALWAYS);
+        info.replaced = !!(mTextureMachine.replaced & mask);
+        info.iterated = (!info.replaced && (info.inDest || info.needed)); 
+        info.smooth =   mSmooth && info.iterated;
+        info.fog =      mFog && info.inDest && (i != GGLFormat::ALPHA);
+        info.blend =    (fs != int(GGL_ONE)) || (fd > int(GGL_ZERO));
+
+        mBlending |= (info.blend ? mask : 0);
+        mMasking |= (mCbFormat.c[i].h && info.masked) ? mask : 0;
+        fbComponents |= mCbFormat.c[i].h ? mask : 0;
+    }
+
+    mAllMasked = (mMasking == fbComponents);
+    if (mAllMasked) {
+        mDithering = 0;
+    }
+    
+    fragment_parts_t parts;
+
+    // ------------------------------------------------------------------------
+    prolog();
+    // ------------------------------------------------------------------------
+
+    build_scanline_prolog(parts, needs);
+
+    if (registerFile().status())
+        return registerFile().status();
+
+    // ------------------------------------------------------------------------
+    label("fragment_loop");
+    // ------------------------------------------------------------------------
+    {
+        Scratch regs(registerFile());
+
+        if (mDithering) {
+            // update the dither index.
+            MOV(AL, 0, parts.count.reg,
+                    reg_imm(parts.count.reg, ROR, GGL_DITHER_ORDER_SHIFT));
+            ADD(AL, 0, parts.count.reg, parts.count.reg,
+                    imm( 1 << (32 - GGL_DITHER_ORDER_SHIFT)));
+            MOV(AL, 0, parts.count.reg,
+                    reg_imm(parts.count.reg, ROR, 32 - GGL_DITHER_ORDER_SHIFT));
+        }
+
+        // XXX: could we do an early alpha-test here in some cases?
+        // It would probaly be used only with smooth-alpha and no texture
+        // (or no alpha component in the texture).
+
+        // Early z-test
+        if (mAlphaTest==GGL_ALWAYS) {
+            build_depth_test(parts, Z_TEST|Z_WRITE);
+        } else {
+            // we cannot do the z-write here, because
+            // it might be killed by the alpha-test later
+            build_depth_test(parts, Z_TEST);
+        }
+
+        { // texture coordinates
+            Scratch scratches(registerFile());
+
+            // texel generation
+            build_textures(parts, regs);
+            if (registerFile().status())
+                return registerFile().status();
+        }
+
+        if ((blending & (FACTOR_DST|BLEND_DST)) || 
+                (mMasking && !mAllMasked) ||
+                (mLogicOp & LOGIC_OP_DST)) 
+        {
+            // blending / logic_op / masking need the framebuffer
+            mDstPixel.setTo(regs.obtain(), &mCbFormat);
+
+            // load the framebuffer pixel
+            comment("fetch color-buffer");
+            load(parts.cbPtr, mDstPixel);
+        }
+
+        if (registerFile().status())
+            return registerFile().status();
+
+        pixel_t pixel;
+        int directTex = mTextureMachine.directTexture;
+        if (directTex | parts.packed) {
+            // note: we can't have both here
+            // iterated color or direct texture
+            pixel = directTex ? parts.texel[directTex-1] : parts.iterated;
+            pixel.flags &= ~CORRUPTIBLE;
+        } else {
+            if (mDithering) {
+                const int ctxtReg = mBuilderContext.Rctx;
+                const int mask = GGL_DITHER_SIZE-1;
+                parts.dither = reg_t(regs.obtain());
+                AND(AL, 0, parts.dither.reg, parts.count.reg, imm(mask));
+                ADDR_ADD(AL, 0, parts.dither.reg, ctxtReg, parts.dither.reg);
+                LDRB(AL, parts.dither.reg, parts.dither.reg,
+                        immed12_pre(GGL_OFFSETOF(ditherMatrix)));
+            }
+        
+            // allocate a register for the resulting pixel
+            pixel.setTo(regs.obtain(), &mCbFormat, FIRST);
+
+            build_component(pixel, parts, GGLFormat::ALPHA,    regs);
+
+            if (mAlphaTest!=GGL_ALWAYS) {
+                // only handle the z-write part here. We know z-test
+                // was successful, as well as alpha-test.
+                build_depth_test(parts, Z_WRITE);
+            }
+
+            build_component(pixel, parts, GGLFormat::RED,      regs);
+            build_component(pixel, parts, GGLFormat::GREEN,    regs);
+            build_component(pixel, parts, GGLFormat::BLUE,     regs);
+
+            pixel.flags |= CORRUPTIBLE;
+        }
+
+        if (registerFile().status())
+            return registerFile().status();
+        
+        if (pixel.reg == -1) {
+            // be defensive here. if we're here it's probably
+            // that this whole fragment is a no-op.
+            pixel = mDstPixel;
+        }
+        
+        if (!mAllMasked) {
+            // logic operation
+            build_logic_op(pixel, regs);
+    
+            // masking
+            build_masking(pixel, regs); 
+    
+            comment("store");
+            store(parts.cbPtr, pixel, WRITE_BACK);
+        }
+    }
+
+    if (registerFile().status())
+        return registerFile().status();
+
+    // update the iterated color...
+    if (parts.reload != 3) {
+        build_smooth_shade(parts);
+    }
+
+    // update iterated z
+    build_iterate_z(parts);
+
+    // update iterated fog
+    build_iterate_f(parts);
+
+    SUB(AL, S, parts.count.reg, parts.count.reg, imm(1<<16));
+    B(PL, "fragment_loop");
+    label("epilog");
+    epilog(registerFile().touched());
+
+    if ((mAlphaTest!=GGL_ALWAYS) || (mDepthTest!=GGL_ALWAYS)) {
+        if (mDepthTest!=GGL_ALWAYS) {
+            label("discard_before_textures");
+            build_iterate_texture_coordinates(parts);
+        }
+        label("discard_after_textures");
+        build_smooth_shade(parts);
+        build_iterate_z(parts);
+        build_iterate_f(parts);
+        if (!mAllMasked) {
+            ADDR_ADD(AL, 0, parts.cbPtr.reg, parts.cbPtr.reg, imm(parts.cbPtr.size>>3));
+        }
+        SUB(AL, S, parts.count.reg, parts.count.reg, imm(1<<16));
+        B(PL, "fragment_loop");
+        epilog(registerFile().touched());
+    }
+
+    return registerFile().status();
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::build_scanline_prolog(
+    fragment_parts_t& parts, const needs_t& needs)
+{
+    Scratch scratches(registerFile());
+
+    // compute count
+    comment("compute ct (# of pixels to process)");
+    parts.count.setTo(obtainReg());
+    int Rx = scratches.obtain();    
+    int Ry = scratches.obtain();
+    CONTEXT_LOAD(Rx, iterators.xl);
+    CONTEXT_LOAD(parts.count.reg, iterators.xr);
+    CONTEXT_LOAD(Ry, iterators.y);
+
+    // parts.count = iterators.xr - Rx
+    SUB(AL, 0, parts.count.reg, parts.count.reg, Rx);
+    SUB(AL, 0, parts.count.reg, parts.count.reg, imm(1));
+
+    if (mDithering) {
+        // parts.count.reg = 0xNNNNXXDD
+        // NNNN = count-1
+        // DD   = dither offset
+        // XX   = 0xxxxxxx (x = garbage)
+        Scratch scratches(registerFile());
+        int tx = scratches.obtain();
+        int ty = scratches.obtain();
+        AND(AL, 0, tx, Rx, imm(GGL_DITHER_MASK));
+        AND(AL, 0, ty, Ry, imm(GGL_DITHER_MASK));
+        ADD(AL, 0, tx, tx, reg_imm(ty, LSL, GGL_DITHER_ORDER_SHIFT));
+        ORR(AL, 0, parts.count.reg, tx, reg_imm(parts.count.reg, LSL, 16));
+    } else {
+        // parts.count.reg = 0xNNNN0000
+        // NNNN = count-1
+        MOV(AL, 0, parts.count.reg, reg_imm(parts.count.reg, LSL, 16));
+    }
+
+    if (!mAllMasked) {
+        // compute dst ptr
+        comment("compute color-buffer pointer");
+        const int cb_bits = mCbFormat.size*8;
+        int Rs = scratches.obtain();
+        parts.cbPtr.setTo(obtainReg(), cb_bits);
+        CONTEXT_LOAD(Rs, state.buffers.color.stride);
+        CONTEXT_ADDR_LOAD(parts.cbPtr.reg, state.buffers.color.data);
+        SMLABB(AL, Rs, Ry, Rs, Rx);  // Rs = Rx + Ry*Rs
+        base_offset(parts.cbPtr, parts.cbPtr, Rs);
+        scratches.recycle(Rs);
+    }
+    
+    // init fog
+    const int need_fog = GGL_READ_NEEDS(P_FOG, needs.p);
+    if (need_fog) {
+        comment("compute initial fog coordinate");
+        Scratch scratches(registerFile());
+        int dfdx = scratches.obtain();
+        int ydfdy = scratches.obtain();
+        int f = ydfdy;
+        CONTEXT_LOAD(dfdx,  generated_vars.dfdx);
+        CONTEXT_LOAD(ydfdy, iterators.ydfdy);
+        MLA(AL, 0, f, Rx, dfdx, ydfdy);
+        CONTEXT_STORE(f, generated_vars.f);
+    }
+
+    // init Z coordinate
+    if ((mDepthTest != GGL_ALWAYS) || GGL_READ_NEEDS(P_MASK_Z, needs.p)) {
+        parts.z = reg_t(obtainReg());
+        comment("compute initial Z coordinate");
+        Scratch scratches(registerFile());
+        int dzdx = scratches.obtain();
+        int ydzdy = parts.z.reg;
+        CONTEXT_LOAD(dzdx,  generated_vars.dzdx);   // 1.31 fixed-point
+        CONTEXT_LOAD(ydzdy, iterators.ydzdy);       // 1.31 fixed-point
+        MLA(AL, 0, parts.z.reg, Rx, dzdx, ydzdy);
+
+        // we're going to index zbase of parts.count
+        // zbase = base + (xl-count + stride*y)*2
+        int Rs = dzdx;
+        int zbase = scratches.obtain();
+        CONTEXT_LOAD(Rs, state.buffers.depth.stride);
+        CONTEXT_ADDR_LOAD(zbase, state.buffers.depth.data);
+        SMLABB(AL, Rs, Ry, Rs, Rx);
+        ADD(AL, 0, Rs, Rs, reg_imm(parts.count.reg, LSR, 16));
+        ADDR_ADD(AL, 0, zbase, zbase, reg_imm(Rs, LSL, 1));
+        CONTEXT_ADDR_STORE(zbase, generated_vars.zbase);
+    }
+
+    // init texture coordinates
+    init_textures(parts.coords, reg_t(Rx), reg_t(Ry));
+    scratches.recycle(Ry);
+
+    // iterated color
+    init_iterated_color(parts, reg_t(Rx));
+
+    // init coverage factor application (anti-aliasing)
+    if (mAA) {
+        parts.covPtr.setTo(obtainReg(), 16);
+        CONTEXT_ADDR_LOAD(parts.covPtr.reg, state.buffers.coverage);
+        ADDR_ADD(AL, 0, parts.covPtr.reg, parts.covPtr.reg, reg_imm(Rx, LSL, 1));
+    }
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::build_component( pixel_t& pixel,
+                                    const fragment_parts_t& parts,
+                                    int component,
+                                    Scratch& regs)
+{
+    static char const * comments[] = {"alpha", "red", "green", "blue"};
+    comment(comments[component]);
+
+    // local register file
+    Scratch scratches(registerFile());
+    const int dst_component_size = pixel.component_size(component);
+
+    component_t temp(-1);
+    build_incoming_component( temp, dst_component_size,
+            parts, component, scratches, regs);
+
+    if (mInfo[component].inDest) {
+
+        // blending...
+        build_blending( temp, mDstPixel, component, scratches );
+
+        // downshift component and rebuild pixel...
+        downshift(pixel, component, temp, parts.dither);
+    }
+}
+
+void GGLAssembler::build_incoming_component(
+                                    component_t& temp,
+                                    int dst_size,
+                                    const fragment_parts_t& parts,
+                                    int component,
+                                    Scratch& scratches,
+                                    Scratch& global_regs)
+{
+    const uint32_t component_mask = 1<<component;
+
+    // Figure out what we need for the blending stage...
+    int fs = component==GGLFormat::ALPHA ? mBlendSrcA : mBlendSrc;
+    int fd = component==GGLFormat::ALPHA ? mBlendDstA : mBlendDst;
+    if (fs==GGL_SRC_ALPHA_SATURATE && component==GGLFormat::ALPHA) {
+        fs = GGL_ONE;
+    }
+
+    // Figure out what we need to extract and for what reason
+    const int blending = blending_codes(fs, fd);
+
+    // Are we actually going to blend?
+    const int need_blending = (fs != int(GGL_ONE)) || (fd > int(GGL_ZERO));
+    
+    // expand the source if the destination has more bits
+    int need_expander = false;
+    for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT-1 ; i++) {
+        texture_unit_t& tmu = mTextureMachine.tmu[i];
+        if ((tmu.format_idx) &&
+            (parts.texel[i].component_size(component) < dst_size)) {
+            need_expander = true;
+        }
+    }
+
+    // do we need to extract this component?
+    const bool multiTexture = mTextureMachine.activeUnits > 1;
+    const int blend_needs_alpha_source = (component==GGLFormat::ALPHA) &&
+                                        (isAlphaSourceNeeded());
+    int need_extract = mInfo[component].needed;
+    if (mInfo[component].inDest)
+    {
+        need_extract |= ((need_blending ?
+                (blending & (BLEND_SRC|FACTOR_SRC)) : need_expander));
+        need_extract |= (mTextureMachine.mask != mTextureMachine.replaced);
+        need_extract |= mInfo[component].smooth;
+        need_extract |= mInfo[component].fog;
+        need_extract |= mDithering;
+        need_extract |= multiTexture;
+    }
+
+    if (need_extract) {
+        Scratch& regs = blend_needs_alpha_source ? global_regs : scratches;
+        component_t fragment;
+
+        // iterated color
+        build_iterated_color(fragment, parts, component, regs);
+
+        // texture environement (decal, modulate, replace)
+        build_texture_environment(fragment, parts, component, regs);
+
+        // expand the source if the destination has more bits
+        if (need_expander && (fragment.size() < dst_size)) {
+            // we're here only if we fetched a texel
+            // (so we know for sure fragment is CORRUPTIBLE)
+            expand(fragment, fragment, dst_size);
+        }
+
+        // We have a few specific things to do for the alpha-channel
+        if ((component==GGLFormat::ALPHA) &&
+            (mInfo[component].needed || fragment.size()<dst_size))
+        {
+            // convert to integer_t first and make sure
+            // we don't corrupt a needed register
+            if (fragment.l) {
+                component_t incoming(fragment);
+                modify(fragment, regs);
+                MOV(AL, 0, fragment.reg, reg_imm(incoming.reg, LSR, incoming.l));
+                fragment.h -= fragment.l;
+                fragment.l = 0;
+            }
+
+            // coverage factor application
+            build_coverage_application(fragment, parts, regs);
+
+            // alpha-test
+            build_alpha_test(fragment, parts);
+
+            if (blend_needs_alpha_source) {
+                // We keep only 8 bits for the blending stage
+                const int shift = fragment.h <= 8 ? 0 : fragment.h-8;
+                if (fragment.flags & CORRUPTIBLE) {
+                    fragment.flags &= ~CORRUPTIBLE;
+                    mAlphaSource.setTo(fragment.reg,
+                            fragment.size(), fragment.flags);
+                    if (shift) {
+                        MOV(AL, 0, mAlphaSource.reg,
+                            reg_imm(mAlphaSource.reg, LSR, shift));
+                    }
+                } else {
+                    // XXX: it would better to do this in build_blend_factor()
+                    // so we can avoid the extra MOV below.
+                    mAlphaSource.setTo(regs.obtain(),
+                            fragment.size(), CORRUPTIBLE);
+                    if (shift) {
+                        MOV(AL, 0, mAlphaSource.reg,
+                            reg_imm(fragment.reg, LSR, shift));
+                    } else {
+                        MOV(AL, 0, mAlphaSource.reg, fragment.reg);
+                    }
+                }
+                mAlphaSource.s -= shift;
+            }
+        }
+
+        // fog...
+        build_fog( fragment, component, regs );
+
+        temp = fragment;
+    } else {
+        if (mInfo[component].inDest) {
+            // extraction not needed and replace
+            // we just select the right component
+            if ((mTextureMachine.replaced & component_mask) == 0) {
+                // component wasn't replaced, so use it!
+                temp = component_t(parts.iterated, component);
+            }
+            for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; i++) {
+                const texture_unit_t& tmu = mTextureMachine.tmu[i];
+                if ((tmu.mask & component_mask) &&
+                    ((tmu.replaced & component_mask) == 0)) {
+                    temp = component_t(parts.texel[i], component);
+                }
+            }
+        }
+    }
+}
+
+bool GGLAssembler::isAlphaSourceNeeded() const
+{
+    // XXX: also needed for alpha-test
+    const int bs = mBlendSrc;
+    const int bd = mBlendDst;
+    return  bs==GGL_SRC_ALPHA_SATURATE ||
+            bs==GGL_SRC_ALPHA || bs==GGL_ONE_MINUS_SRC_ALPHA ||
+            bd==GGL_SRC_ALPHA || bd==GGL_ONE_MINUS_SRC_ALPHA ; 
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::build_smooth_shade(const fragment_parts_t& parts)
+{
+    if (mSmooth && !parts.iterated_packed) {
+        // update the iterated color in a pipelined way...
+        comment("update iterated color");
+        Scratch scratches(registerFile());
+
+        const int reload = parts.reload;
+        for (int i=0 ; i<4 ; i++) {
+            if (!mInfo[i].iterated) 
+                continue;
+                
+            int c = parts.argb[i].reg;
+            int dx = parts.argb_dx[i].reg;
+            
+            if (reload & 1) {
+                c = scratches.obtain();
+                CONTEXT_LOAD(c, generated_vars.argb[i].c);
+            }
+            if (reload & 2) {
+                dx = scratches.obtain();
+                CONTEXT_LOAD(dx, generated_vars.argb[i].dx);
+            }
+            
+            if (mSmooth) {
+                ADD(AL, 0, c, c, dx);
+            }
+            
+            if (reload & 1) {
+                CONTEXT_STORE(c, generated_vars.argb[i].c);
+                scratches.recycle(c);
+            }
+            if (reload & 2) {
+                scratches.recycle(dx);
+            }
+        }
+    }
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::build_coverage_application(component_t& fragment,
+        const fragment_parts_t& parts, Scratch& regs)
+{
+    // here fragment.l is guarenteed to be 0
+    if (mAA) {
+        // coverages are 1.15 fixed-point numbers
+        comment("coverage application");
+
+        component_t incoming(fragment);
+        modify(fragment, regs);
+
+        Scratch scratches(registerFile());
+        int cf = scratches.obtain();
+        LDRH(AL, cf, parts.covPtr.reg, immed8_post(2));
+        if (fragment.h > 31) {
+            fragment.h--;
+            SMULWB(AL, fragment.reg, incoming.reg, cf);
+        } else {
+            MOV(AL, 0, fragment.reg, reg_imm(incoming.reg, LSL, 1));
+            SMULWB(AL, fragment.reg, fragment.reg, cf);
+        }
+    }
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::build_alpha_test(component_t& fragment,
+                                    const fragment_parts_t& /*parts*/)
+{
+    if (mAlphaTest != GGL_ALWAYS) {
+        comment("Alpha Test");
+        Scratch scratches(registerFile());
+        int ref = scratches.obtain();
+        const int shift = GGL_COLOR_BITS-fragment.size();
+        CONTEXT_LOAD(ref, state.alpha_test.ref);
+        if (shift) CMP(AL, fragment.reg, reg_imm(ref, LSR, shift));
+        else       CMP(AL, fragment.reg, ref);
+        int cc = NV;
+        switch (mAlphaTest) {
+        case GGL_NEVER:     cc = NV;    break;
+        case GGL_LESS:      cc = LT;    break;
+        case GGL_EQUAL:     cc = EQ;    break;
+        case GGL_LEQUAL:    cc = LS;    break;
+        case GGL_GREATER:   cc = HI;    break;
+        case GGL_NOTEQUAL:  cc = NE;    break;
+        case GGL_GEQUAL:    cc = HS;    break;
+        }
+        B(cc^1, "discard_after_textures");
+    }
+}
+
+// ---------------------------------------------------------------------------
+            
+void GGLAssembler::build_depth_test(
+        const fragment_parts_t& parts, uint32_t mask)
+{
+    mask &= Z_TEST|Z_WRITE;
+    const needs_t& needs = mBuilderContext.needs;
+    const int zmask = GGL_READ_NEEDS(P_MASK_Z, needs.p);
+    Scratch scratches(registerFile());
+
+    if (mDepthTest != GGL_ALWAYS || zmask) {
+        int cc=AL, ic=AL;
+        switch (mDepthTest) {
+        case GGL_LESS:      ic = HI;    break;
+        case GGL_EQUAL:     ic = EQ;    break;
+        case GGL_LEQUAL:    ic = HS;    break;
+        case GGL_GREATER:   ic = LT;    break;
+        case GGL_NOTEQUAL:  ic = NE;    break;
+        case GGL_GEQUAL:    ic = LS;    break;
+        case GGL_NEVER:
+            // this never happens, because it's taken care of when 
+            // computing the needs. but we keep it for completness.
+            comment("Depth Test (NEVER)");
+            B(AL, "discard_before_textures");
+            return;
+        case GGL_ALWAYS:
+            // we're here because zmask is enabled
+            mask &= ~Z_TEST;    // test always passes.
+            break;
+        }
+        
+        // inverse the condition
+        cc = ic^1;
+        
+        if ((mask & Z_WRITE) && !zmask) {
+            mask &= ~Z_WRITE;
+        }
+        
+        if (!mask)
+            return;
+
+        comment("Depth Test");
+
+        int zbase = scratches.obtain();
+        int depth = scratches.obtain();
+        int z = parts.z.reg;
+        
+        CONTEXT_ADDR_LOAD(zbase, generated_vars.zbase);  // stall
+        ADDR_SUB(AL, 0, zbase, zbase, reg_imm(parts.count.reg, LSR, 15));
+            // above does zbase = zbase + ((count >> 16) << 1)
+
+        if (mask & Z_TEST) {
+            LDRH(AL, depth, zbase);  // stall
+            CMP(AL, depth, reg_imm(z, LSR, 16));
+            B(cc, "discard_before_textures");
+        }
+        if (mask & Z_WRITE) {
+            if (mask == Z_WRITE) {
+                // only z-write asked, cc is meaningless
+                ic = AL;
+            }
+            MOV(AL, 0, depth, reg_imm(z, LSR, 16));
+            STRH(ic, depth, zbase);
+        }
+    }
+}
+
+void GGLAssembler::build_iterate_z(const fragment_parts_t& parts)
+{
+    const needs_t& needs = mBuilderContext.needs;
+    if ((mDepthTest != GGL_ALWAYS) || GGL_READ_NEEDS(P_MASK_Z, needs.p)) {
+        Scratch scratches(registerFile());
+        int dzdx = scratches.obtain();
+        CONTEXT_LOAD(dzdx, generated_vars.dzdx);    // stall
+        ADD(AL, 0, parts.z.reg, parts.z.reg, dzdx); 
+    }
+}
+
+void GGLAssembler::build_iterate_f(const fragment_parts_t& /*parts*/)
+{
+    const needs_t& needs = mBuilderContext.needs;
+    if (GGL_READ_NEEDS(P_FOG, needs.p)) {
+        Scratch scratches(registerFile());
+        int dfdx = scratches.obtain();
+        int f = scratches.obtain();
+        CONTEXT_LOAD(f,     generated_vars.f);
+        CONTEXT_LOAD(dfdx,  generated_vars.dfdx);   // stall
+        ADD(AL, 0, f, f, dfdx);
+        CONTEXT_STORE(f,    generated_vars.f);
+    }
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::build_logic_op(pixel_t& pixel, Scratch& regs)
+{
+    const needs_t& needs = mBuilderContext.needs;
+    const int opcode = GGL_READ_NEEDS(LOGIC_OP, needs.n) | GGL_CLEAR;
+    if (opcode == GGL_COPY)
+        return;
+    
+    comment("logic operation");
+
+    pixel_t s(pixel);
+    if (!(pixel.flags & CORRUPTIBLE)) {
+        pixel.reg = regs.obtain();
+        pixel.flags |= CORRUPTIBLE;
+    }
+    
+    pixel_t d(mDstPixel);
+    switch(opcode) {
+    case GGL_CLEAR:         MOV(AL, 0, pixel.reg, imm(0));          break;
+    case GGL_AND:           AND(AL, 0, pixel.reg, s.reg, d.reg);    break;
+    case GGL_AND_REVERSE:   BIC(AL, 0, pixel.reg, s.reg, d.reg);    break;
+    case GGL_COPY:                                                  break;
+    case GGL_AND_INVERTED:  BIC(AL, 0, pixel.reg, d.reg, s.reg);    break;
+    case GGL_NOOP:          MOV(AL, 0, pixel.reg, d.reg);           break;
+    case GGL_XOR:           EOR(AL, 0, pixel.reg, s.reg, d.reg);    break;
+    case GGL_OR:            ORR(AL, 0, pixel.reg, s.reg, d.reg);    break;
+    case GGL_NOR:           ORR(AL, 0, pixel.reg, s.reg, d.reg);
+                            MVN(AL, 0, pixel.reg, pixel.reg);       break;
+    case GGL_EQUIV:         EOR(AL, 0, pixel.reg, s.reg, d.reg);
+                            MVN(AL, 0, pixel.reg, pixel.reg);       break;
+    case GGL_INVERT:        MVN(AL, 0, pixel.reg, d.reg);           break;
+    case GGL_OR_REVERSE:    // s | ~d == ~(~s & d)
+                            BIC(AL, 0, pixel.reg, d.reg, s.reg);
+                            MVN(AL, 0, pixel.reg, pixel.reg);       break;
+    case GGL_COPY_INVERTED: MVN(AL, 0, pixel.reg, s.reg);           break;
+    case GGL_OR_INVERTED:   // ~s | d == ~(s & ~d)
+                            BIC(AL, 0, pixel.reg, s.reg, d.reg);
+                            MVN(AL, 0, pixel.reg, pixel.reg);       break;
+    case GGL_NAND:          AND(AL, 0, pixel.reg, s.reg, d.reg);
+                            MVN(AL, 0, pixel.reg, pixel.reg);       break;
+    case GGL_SET:           MVN(AL, 0, pixel.reg, imm(0));          break;
+    };        
+}
+
+// ---------------------------------------------------------------------------
+
+static uint32_t find_bottom(uint32_t val)
+{
+    uint32_t i = 0;
+    while (!(val & (3<<i)))
+        i+= 2;
+    return i;
+}
+
+static void normalize(uint32_t& val, uint32_t& rot)
+{
+    rot = 0;
+    while (!(val&3)  || (val & 0xFC000000)) {
+        uint32_t newval;
+        newval = val >> 2;
+        newval |= (val&3) << 30;
+        val = newval;
+        rot += 2;
+        if (rot == 32) {
+            rot = 0;
+            break;
+        }
+    }
+}
+
+void GGLAssembler::build_and_immediate(int d, int s, uint32_t mask, int bits)
+{
+    uint32_t rot;
+    uint32_t size = ((bits>=32) ? 0 : (1LU << bits)) - 1;
+    mask &= size;
+
+    if (mask == size) {
+        if (d != s)
+            MOV( AL, 0, d, s);
+        return;
+    }
+    
+    if ((getCodegenArch() == CODEGEN_ARCH_MIPS) ||
+        (getCodegenArch() == CODEGEN_ARCH_MIPS64)) {
+        // MIPS can do 16-bit imm in 1 instr, 32-bit in 3 instr
+        // the below ' while (mask)' code is buggy on mips
+        // since mips returns true on isValidImmediate()
+        // then we get multiple AND instr (positive logic)
+        AND( AL, 0, d, s, imm(mask) );
+        return;
+    }
+    else if (getCodegenArch() == CODEGEN_ARCH_ARM64) {
+        AND( AL, 0, d, s, imm(mask) );
+        return;
+    }
+
+    int negative_logic = !isValidImmediate(mask);
+    if (negative_logic) {
+        mask = ~mask & size;
+    }
+    normalize(mask, rot);
+
+    if (mask) {
+        while (mask) {
+            uint32_t bitpos = find_bottom(mask);
+            int shift = rot + bitpos;
+            uint32_t m = mask & (0xff << bitpos);
+            mask &= ~m;
+            m >>= bitpos;
+            int32_t newMask =  (m<<shift) | (m>>(32-shift));
+            if (!negative_logic) {
+                AND( AL, 0, d, s, imm(newMask) );
+            } else {
+                BIC( AL, 0, d, s, imm(newMask) );
+            }
+            s = d;
+        }
+    } else {
+        MOV( AL, 0, d, imm(0));
+    }
+}		
+
+void GGLAssembler::build_masking(pixel_t& pixel, Scratch& regs)
+{
+    if (!mMasking || mAllMasked) {
+        return;
+    }
+
+    comment("color mask");
+
+    pixel_t fb(mDstPixel);
+    pixel_t s(pixel);
+    if (!(pixel.flags & CORRUPTIBLE)) {
+        pixel.reg = regs.obtain();
+        pixel.flags |= CORRUPTIBLE;
+    }
+
+    int mask = 0;
+    for (int i=0 ; i<4 ; i++) {
+        const int component_mask = 1<<i;
+        const int h = fb.format.c[i].h;
+        const int l = fb.format.c[i].l;
+        if (h && (!(mMasking & component_mask))) {
+            mask |= ((1<<(h-l))-1) << l;
+        }
+    }
+
+    // There is no need to clear the masked components of the source
+    // (unless we applied a logic op), because they're already zeroed 
+    // by construction (masked components are not computed)
+
+    if (mLogicOp) {
+        const needs_t& needs = mBuilderContext.needs;
+        const int opcode = GGL_READ_NEEDS(LOGIC_OP, needs.n) | GGL_CLEAR;
+        if (opcode != GGL_CLEAR) {
+            // clear masked component of source
+            build_and_immediate(pixel.reg, s.reg, mask, fb.size());
+            s = pixel;
+        }
+    }
+
+    // clear non masked components of destination
+    build_and_immediate(fb.reg, fb.reg, ~mask, fb.size()); 
+
+    // or back the channels that were masked
+    if (s.reg == fb.reg) {
+         // this is in fact a MOV
+        if (s.reg == pixel.reg) {
+            // ugh. this in in fact a nop
+        } else {
+            MOV(AL, 0, pixel.reg, fb.reg);
+        }
+    } else {
+        ORR(AL, 0, pixel.reg, s.reg, fb.reg);
+    }
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::base_offset(
+        const pointer_t& d, const pointer_t& b, const reg_t& o)
+{
+    switch (b.size) {
+    case 32:
+        ADDR_ADD(AL, 0, d.reg, b.reg, reg_imm(o.reg, LSL, 2));
+        break;
+    case 24:
+        if (d.reg == b.reg) {
+            ADDR_ADD(AL, 0, d.reg, b.reg, reg_imm(o.reg, LSL, 1));
+            ADDR_ADD(AL, 0, d.reg, d.reg, o.reg);
+        } else {
+            ADDR_ADD(AL, 0, d.reg, o.reg, reg_imm(o.reg, LSL, 1));
+            ADDR_ADD(AL, 0, d.reg, d.reg, b.reg);
+        }
+        break;
+    case 16:
+        ADDR_ADD(AL, 0, d.reg, b.reg, reg_imm(o.reg, LSL, 1));
+        break;
+    case 8:
+        ADDR_ADD(AL, 0, d.reg, b.reg, o.reg);
+        break;
+    }
+}
+
+// ----------------------------------------------------------------------------
+// cheezy register allocator...
+// ----------------------------------------------------------------------------
+
+// Modified to support MIPS processors, in a very simple way. We retain the
+// (Arm) limit of 16 total registers, but shift the mapping of those registers
+// from 0-15, to 2-17. Register 0 on Mips cannot be used as GP registers, and
+// register 1 has a traditional use as a temp).
+
+RegisterAllocator::RegisterAllocator(int arch) : mRegs(arch)
+{
+}
+
+void RegisterAllocator::reset()
+{
+    mRegs.reset();
+}
+
+int RegisterAllocator::reserveReg(int reg)
+{
+    return mRegs.reserve(reg);
+}
+
+int RegisterAllocator::obtainReg()
+{
+    return mRegs.obtain();
+}
+
+void RegisterAllocator::recycleReg(int reg)
+{
+    mRegs.recycle(reg);
+}
+
+RegisterAllocator::RegisterFile& RegisterAllocator::registerFile()
+{
+    return mRegs;
+}
+
+// ----------------------------------------------------------------------------
+
+RegisterAllocator::RegisterFile::RegisterFile(int codegen_arch)
+    : mRegs(0), mTouched(0), mStatus(0), mArch(codegen_arch), mRegisterOffset(0)
+{
+    if ((mArch == ARMAssemblerInterface::CODEGEN_ARCH_MIPS) ||
+        (mArch == ARMAssemblerInterface::CODEGEN_ARCH_MIPS64)) {
+        mRegisterOffset = 2;    // ARM has regs 0..15, MIPS offset to 2..17
+    }
+    reserve(ARMAssemblerInterface::SP);
+    reserve(ARMAssemblerInterface::PC);
+}
+
+RegisterAllocator::RegisterFile::RegisterFile(const RegisterFile& rhs, int codegen_arch)
+    : mRegs(rhs.mRegs), mTouched(rhs.mTouched), mArch(codegen_arch), mRegisterOffset(0)
+{
+    if ((mArch == ARMAssemblerInterface::CODEGEN_ARCH_MIPS) ||
+        (mArch == ARMAssemblerInterface::CODEGEN_ARCH_MIPS64)) {
+        mRegisterOffset = 2;    // ARM has regs 0..15, MIPS offset to 2..17
+    }
+}
+
+RegisterAllocator::RegisterFile::~RegisterFile()
+{
+}
+
+bool RegisterAllocator::RegisterFile::operator == (const RegisterFile& rhs) const
+{
+    return (mRegs == rhs.mRegs);
+}
+
+void RegisterAllocator::RegisterFile::reset()
+{
+    mRegs = mTouched = mStatus = 0;
+    reserve(ARMAssemblerInterface::SP);
+    reserve(ARMAssemblerInterface::PC);
+}
+
+// RegisterFile::reserve() take a register parameter in the
+// range 0-15 (Arm compatible), but on a Mips processor, will
+// return the actual allocated register in the range 2-17.
+int RegisterAllocator::RegisterFile::reserve(int reg)
+{
+    reg += mRegisterOffset;
+    LOG_ALWAYS_FATAL_IF(isUsed(reg),
+                        "reserving register %d, but already in use",
+                        reg);
+    mRegs |= (1<<reg);
+    mTouched |= mRegs;
+    return reg;
+}
+
+// This interface uses regMask in range 2-17 on MIPS, no translation.
+void RegisterAllocator::RegisterFile::reserveSeveral(uint32_t regMask)
+{
+    mRegs |= regMask;
+    mTouched |= regMask;
+}
+
+int RegisterAllocator::RegisterFile::isUsed(int reg) const
+{
+    LOG_ALWAYS_FATAL_IF(reg>=16+(int)mRegisterOffset, "invalid register %d", reg);
+    return mRegs & (1<<reg);
+}
+
+int RegisterAllocator::RegisterFile::obtain()
+{
+    const char priorityList[14] = {  0,  1, 2, 3, 
+                                    12, 14, 4, 5, 
+                                     6,  7, 8, 9,
+                                    10, 11 };
+    const int nbreg = sizeof(priorityList);
+    int i, r, reg;
+    for (i=0 ; i<nbreg ; i++) {
+        r = priorityList[i];
+        if (!isUsed(r + mRegisterOffset)) {
+            break;
+        }
+    }
+    // this is not an error anymore because, we'll try again with
+    // a lower optimization level.
+    //ALOGE_IF(i >= nbreg, "pixelflinger ran out of registers\n");
+    if (i >= nbreg) {
+        mStatus |= OUT_OF_REGISTERS;
+        // we return SP so we can more easily debug things
+        // the code will never be run anyway.
+        return ARMAssemblerInterface::SP; 
+    }
+    reg = reserve(r);  // Param in Arm range 0-15, returns range 2-17 on Mips.
+    return reg;
+}
+
+bool RegisterAllocator::RegisterFile::hasFreeRegs() const
+{
+    uint32_t regs = mRegs >> mRegisterOffset;   // MIPS fix.
+    return ((regs & 0xFFFF) == 0xFFFF) ? false : true;
+}
+
+int RegisterAllocator::RegisterFile::countFreeRegs() const
+{
+    uint32_t regs = mRegs >> mRegisterOffset;   // MIPS fix.
+    int f = ~regs & 0xFFFF;
+    // now count number of 1
+   f = (f & 0x5555) + ((f>>1) & 0x5555);
+   f = (f & 0x3333) + ((f>>2) & 0x3333);
+   f = (f & 0x0F0F) + ((f>>4) & 0x0F0F);
+   f = (f & 0x00FF) + ((f>>8) & 0x00FF);
+   return f;
+}
+
+void RegisterAllocator::RegisterFile::recycle(int reg)
+{
+    // commented out, since common failure of running out of regs
+    // triggers this assertion. Since the code is not execectued
+    // in that case, it does not matter. No reason to FATAL err.
+    // LOG_FATAL_IF(!isUsed(reg),
+    //         "recycling unallocated register %d",
+    //         reg);
+    mRegs &= ~(1<<reg);
+}
+
+void RegisterAllocator::RegisterFile::recycleSeveral(uint32_t regMask)
+{
+    // commented out, since common failure of running out of regs
+    // triggers this assertion. Since the code is not execectued
+    // in that case, it does not matter. No reason to FATAL err.
+    // LOG_FATAL_IF((mRegs & regMask)!=regMask,
+    //         "recycling unallocated registers "
+    //         "(recycle=%08x, allocated=%08x, unallocated=%08x)",
+    //         regMask, mRegs, mRegs&regMask);
+    mRegs &= ~regMask;
+}
+
+uint32_t RegisterAllocator::RegisterFile::touched() const
+{
+    return mTouched;
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
diff --git a/libpixelflinger/codeflinger/GGLAssembler.h b/libpixelflinger/codeflinger/GGLAssembler.h
new file mode 100644
index 0000000..47dbf3a
--- /dev/null
+++ b/libpixelflinger/codeflinger/GGLAssembler.h
@@ -0,0 +1,572 @@
+/* libs/pixelflinger/codeflinger/GGLAssembler.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#ifndef ANDROID_GGLASSEMBLER_H
+#define ANDROID_GGLASSEMBLER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <private/pixelflinger/ggl_context.h>
+
+#include "ARMAssemblerProxy.h"
+
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+#define CONTEXT_ADDR_LOAD(REG, FIELD) \
+    ADDR_LDR(AL, REG, mBuilderContext.Rctx, immed12_pre(GGL_OFFSETOF(FIELD)))
+
+#define CONTEXT_ADDR_STORE(REG, FIELD) \
+    ADDR_STR(AL, REG, mBuilderContext.Rctx, immed12_pre(GGL_OFFSETOF(FIELD)))
+
+#define CONTEXT_LOAD(REG, FIELD) \
+    LDR(AL, REG, mBuilderContext.Rctx, immed12_pre(GGL_OFFSETOF(FIELD)))
+
+#define CONTEXT_STORE(REG, FIELD) \
+    STR(AL, REG, mBuilderContext.Rctx, immed12_pre(GGL_OFFSETOF(FIELD)))
+
+
+class RegisterAllocator
+{
+public:
+    class RegisterFile;
+    
+                    RegisterAllocator(int arch);  // NOLINT, implicit
+    RegisterFile&   registerFile();
+    int             reserveReg(int reg);
+    int             obtainReg();
+    void            recycleReg(int reg);
+    void            reset();
+
+    class RegisterFile
+    {
+    public:
+                            RegisterFile(int arch);  // NOLINT, implicit
+                            RegisterFile(const RegisterFile& rhs, int arch);
+                            ~RegisterFile();
+
+                void        reset();
+
+                bool operator == (const RegisterFile& rhs) const;
+                bool operator != (const RegisterFile& rhs) const {
+                    return !operator == (rhs);
+                }
+
+                int         reserve(int reg);
+                void        reserveSeveral(uint32_t regMask);
+
+                void        recycle(int reg);
+                void        recycleSeveral(uint32_t regMask);
+
+                int         obtain();
+        inline  int         isUsed(int reg) const;
+
+                bool        hasFreeRegs() const;
+                int         countFreeRegs() const;                
+
+                uint32_t    touched() const;
+        inline  uint32_t    status() const { return mStatus; }
+        
+        enum {
+            OUT_OF_REGISTERS = 0x1
+        };
+
+    private:
+        uint32_t    mRegs;
+        uint32_t    mTouched;
+        uint32_t    mStatus;
+        int         mArch;
+        uint32_t    mRegisterOffset;    // lets reg alloc use 2..17 for mips
+                                        // while arm uses 0..15
+    };
+ 
+    class Scratch
+    {
+    public:
+            explicit Scratch(RegisterFile& regFile)
+                : mRegFile(regFile), mScratch(0) { 
+            }
+            ~Scratch() {
+                mRegFile.recycleSeveral(mScratch);
+            }
+        int obtain() { 
+            int reg = mRegFile.obtain();
+            mScratch |= 1<<reg;
+            return reg;
+        }
+        void recycle(int reg) {
+            mRegFile.recycle(reg);
+            mScratch &= ~(1<<reg);
+        }
+        bool isUsed(int reg) {
+            return (mScratch & (1<<reg));
+        }
+        int countFreeRegs() {
+            return mRegFile.countFreeRegs();
+        }
+    private:
+        RegisterFile&   mRegFile;
+        uint32_t        mScratch;
+    };
+
+    class Spill
+    {
+    public:
+        Spill(RegisterFile& regFile, ARMAssemblerInterface& gen, uint32_t reglist)
+            : mRegFile(regFile), mGen(gen), mRegList(reglist), mCount(0)
+        {
+            if (reglist) {
+                int count = 0;
+                while (reglist) {
+                    count++;
+                    reglist &= ~(1 << (31 - __builtin_clz(reglist)));
+                }
+                if (count == 1) {
+                    int reg = 31 - __builtin_clz(mRegList);
+                    mGen.STR(mGen.AL, reg, mGen.SP, mGen.immed12_pre(-4, 1));
+                } else {
+                    mGen.STM(mGen.AL, mGen.DB, mGen.SP, 1, mRegList);
+                }
+                mRegFile.recycleSeveral(mRegList);
+                mCount = count;
+            }
+        }
+        ~Spill() {
+            if (mRegList) {
+                if (mCount == 1) {
+                    int reg = 31 - __builtin_clz(mRegList);
+                    mGen.LDR(mGen.AL, reg, mGen.SP, mGen.immed12_post(4));
+                } else {
+                    mGen.LDM(mGen.AL, mGen.IA, mGen.SP, 1, mRegList);
+                }
+                mRegFile.reserveSeveral(mRegList);
+            }
+        }
+    private:
+        RegisterFile&           mRegFile;
+        ARMAssemblerInterface&  mGen;
+        uint32_t                mRegList;
+        int                     mCount;
+    };
+    
+private:
+    RegisterFile    mRegs;
+};
+
+// ----------------------------------------------------------------------------
+
+class GGLAssembler : public ARMAssemblerProxy, public RegisterAllocator
+{
+public:
+
+    explicit    GGLAssembler(ARMAssemblerInterface* target);
+    virtual     ~GGLAssembler();
+
+    uint32_t*   base() const { return 0; } // XXX
+    uint32_t*   pc() const { return 0; } // XXX
+
+    void        reset(int opt_level);
+
+    virtual void    prolog();
+    virtual void    epilog(uint32_t touched);
+
+        // generate scanline code for given needs
+    int         scanline(const needs_t& needs, context_t const* c);
+    int         scanline_core(const needs_t& needs, context_t const* c);
+
+        enum {
+            CLEAR_LO    = 0x0001,
+            CLEAR_HI    = 0x0002,
+            CORRUPTIBLE = 0x0004,
+            FIRST       = 0x0008
+        };
+
+        enum { //load/store flags
+            WRITE_BACK  = 0x0001
+        };
+
+        struct reg_t {
+            reg_t() : reg(-1), flags(0) {
+            }
+            reg_t(int r, int f=0)  // NOLINT, implicit
+                : reg(r), flags(f) {
+            }
+            void setTo(int r, int f=0) {
+                reg=r; flags=f;
+            }
+            int         reg;
+            uint16_t    flags;
+        };
+
+        struct integer_t : public reg_t {
+            integer_t() : reg_t(), s(0) {
+            }
+            integer_t(int r, int sz=32, int f=0)  // NOLINT, implicit
+                : reg_t(r, f), s(sz) {
+            }
+            void setTo(int r, int sz=32, int f=0) {
+                reg_t::setTo(r, f); s=sz;
+            }
+            int8_t s;
+            inline int size() const { return s; }
+        };
+        
+        struct pixel_t : public reg_t {
+            pixel_t() : reg_t() {
+                memset(&format, 0, sizeof(GGLFormat));
+            }
+            pixel_t(int r, const GGLFormat* fmt, int f=0)
+                : reg_t(r, f), format(*fmt) {
+            }
+            void setTo(int r, const GGLFormat* fmt, int f=0) {
+                reg_t::setTo(r, f); format = *fmt;
+            }
+            GGLFormat format;
+            inline int hi(int c) const { return format.c[c].h; }
+            inline int low(int c) const { return format.c[c].l; }
+            inline int mask(int c) const { return ((1<<size(c))-1) << low(c); }
+            inline int size() const { return format.size*8; }
+            inline int size(int c) const { return component_size(c); }
+            inline int component_size(int c) const { return hi(c) - low(c); }
+        };
+
+        struct component_t : public reg_t {
+            component_t() : reg_t(), h(0), l(0) {
+            }
+            component_t(int r, int f=0)  // NOLINT, implicit
+                : reg_t(r, f), h(0), l(0) {
+            }
+            component_t(int r, int lo, int hi, int f=0)
+                : reg_t(r, f), h(hi), l(lo) {
+            }
+            explicit component_t(const integer_t& rhs)
+                : reg_t(rhs.reg, rhs.flags), h(rhs.s), l(0) {
+            }
+            explicit component_t(const pixel_t& rhs, int component) {
+                setTo(  rhs.reg, 
+                        rhs.format.c[component].l,
+                        rhs.format.c[component].h,
+                        rhs.flags|CLEAR_LO|CLEAR_HI);
+            }
+            void setTo(int r, int lo=0, int hi=0, int f=0) {
+                reg_t::setTo(r, f); h=hi; l=lo;
+            }
+            int8_t h;
+            int8_t l;
+            inline int size() const { return h-l; }
+        };
+
+        struct pointer_t : public reg_t {
+            pointer_t() : reg_t(), size(0) {
+            }
+            pointer_t(int r, int s, int f=0)
+                : reg_t(r, f), size(s) {
+            }
+            void setTo(int r, int s, int f=0) {
+                reg_t::setTo(r, f); size=s;
+            }
+            int8_t size;
+        };
+
+
+private:
+    // GGLAssembler hides RegisterAllocator's and ARMAssemblerProxy's reset
+    // methods by providing a reset method with a different parameter set. The
+    // intent of GGLAssembler's reset method is to wrap the inherited reset
+    // methods, so make these methods private in order to prevent direct calls
+    // to these methods from clients.
+    using RegisterAllocator::reset;
+    using ARMAssemblerProxy::reset;
+
+    struct tex_coord_t {
+        reg_t       s;
+        reg_t       t;
+        pointer_t   ptr;
+    };
+
+    struct fragment_parts_t {
+        uint32_t    packed  : 1;
+        uint32_t    reload  : 2;
+        uint32_t    iterated_packed  : 1;
+        pixel_t     iterated;
+        pointer_t   cbPtr;
+        pointer_t   covPtr;
+        reg_t       count;
+        reg_t       argb[4];
+        reg_t       argb_dx[4];
+        reg_t       z;
+        reg_t       dither;
+        pixel_t     texel[GGL_TEXTURE_UNIT_COUNT];
+        tex_coord_t coords[GGL_TEXTURE_UNIT_COUNT];
+    };
+    
+    struct texture_unit_t {
+        int         format_idx;
+        GGLFormat   format;
+        int         bits;
+        int         swrap;
+        int         twrap;
+        int         env;
+        int         pot;
+        int         linear;
+        uint8_t     mask;
+        uint8_t     replaced;
+    };
+
+    struct texture_machine_t {
+        texture_unit_t  tmu[GGL_TEXTURE_UNIT_COUNT];
+        uint8_t         mask;
+        uint8_t         replaced;
+        uint8_t         directTexture;
+        uint8_t         activeUnits;
+    };
+
+    struct component_info_t {
+        bool    masked      : 1;
+        bool    inDest      : 1;
+        bool    needed      : 1;
+        bool    replaced    : 1;
+        bool    iterated    : 1;
+        bool    smooth      : 1;
+        bool    blend       : 1;
+        bool    fog         : 1;
+    };
+
+    struct builder_context_t {
+        context_t const*    c;
+        needs_t             needs;
+        int                 Rctx;
+    };
+
+    template <typename T>
+    void modify(T& r, Scratch& regs)
+    {
+        if (!(r.flags & CORRUPTIBLE)) {
+            r.reg = regs.obtain();
+            r.flags |= CORRUPTIBLE;
+        }
+    }
+
+    // helpers
+    void    base_offset(const pointer_t& d, const pointer_t& b, const reg_t& o);
+
+    // texture environement
+    void    modulate(   component_t& dest,
+                        const component_t& incoming,
+                        const pixel_t& texel, int component);
+
+    void    decal(  component_t& dest,
+                    const component_t& incoming,
+                    const pixel_t& texel, int component);
+
+    void    blend(  component_t& dest,
+                    const component_t& incoming,
+                    const pixel_t& texel, int component, int tmu);
+
+    void    add(  component_t& dest,
+                    const component_t& incoming,
+                    const pixel_t& texel, int component);
+
+    // load/store stuff
+    void    store(const pointer_t& addr, const pixel_t& src, uint32_t flags=0);
+    void    load(const pointer_t& addr, const pixel_t& dest, uint32_t flags=0);
+    void    extract(integer_t& d, const pixel_t& s, int component);    
+    void    extract(component_t& d, const pixel_t& s, int component);    
+    void    extract(integer_t& d, int s, int h, int l, int bits=32);
+    void    expand(integer_t& d, const integer_t& s, int dbits);
+    void    expand(integer_t& d, const component_t& s, int dbits);
+    void    expand(component_t& d, const component_t& s, int dbits);
+    void    downshift(pixel_t& d, int component, component_t s, const reg_t& dither);
+
+
+    void    mul_factor( component_t& d,
+                        const integer_t& v,
+                        const integer_t& f);
+
+    void    mul_factor_add( component_t& d,
+                            const integer_t& v,
+                            const integer_t& f,
+                            const component_t& a);
+
+    void    component_add(  component_t& d,
+                            const integer_t& dst,
+                            const integer_t& src);
+
+    void    component_sat(  const component_t& v);
+
+
+    void    build_scanline_prolog(  fragment_parts_t& parts,
+                                    const needs_t& needs);
+
+    void    build_smooth_shade(const fragment_parts_t& parts);
+
+    void    build_component(    pixel_t& pixel,
+                                const fragment_parts_t& parts,
+                                int component,
+                                Scratch& global_scratches);
+                                
+    void    build_incoming_component(
+                                component_t& temp,
+                                int dst_size,
+                                const fragment_parts_t& parts,
+                                int component,
+                                Scratch& scratches,
+                                Scratch& global_scratches);
+
+    void    init_iterated_color(fragment_parts_t& parts, const reg_t& x);
+
+    void    build_iterated_color(   component_t& fragment,
+                                    const fragment_parts_t& parts,
+                                    int component,
+                                    Scratch& regs);
+
+    void    decodeLogicOpNeeds(const needs_t& needs);
+    
+    void    decodeTMUNeeds(const needs_t& needs, context_t const* c);
+
+    void    init_textures(  tex_coord_t* coords,
+                            const reg_t& x,
+                            const reg_t& y);
+
+    void    build_textures( fragment_parts_t& parts,
+                            Scratch& regs);
+
+    void    filter8(   const fragment_parts_t& parts,
+                        pixel_t& texel, const texture_unit_t& tmu,
+                        int U, int V, pointer_t& txPtr,
+                        int FRAC_BITS);
+
+    void    filter16(   const fragment_parts_t& parts,
+                        pixel_t& texel, const texture_unit_t& tmu,
+                        int U, int V, pointer_t& txPtr,
+                        int FRAC_BITS);
+
+    void    filter24(   const fragment_parts_t& parts,
+                        pixel_t& texel, const texture_unit_t& tmu,
+                        int U, int V, pointer_t& txPtr,
+                        int FRAC_BITS);
+
+    void    filter32(   const fragment_parts_t& parts,
+                        pixel_t& texel, const texture_unit_t& tmu,
+                        int U, int V, pointer_t& txPtr,
+                        int FRAC_BITS);
+
+    void    build_texture_environment(  component_t& fragment,
+                                        const fragment_parts_t& parts,
+                                        int component,
+                                        Scratch& regs);
+
+    void    wrapping(   int d,
+                        int coord, int size,
+                        int tx_wrap, int tx_linear);
+
+    void    build_fog(  component_t& temp,
+                        int component,
+                        Scratch& parent_scratches);
+
+    void    build_blending(     component_t& in_out,
+                                const pixel_t& pixel,
+                                int component,
+                                Scratch& parent_scratches);
+
+    void    build_blend_factor(
+                integer_t& factor, int f, int component,
+                const pixel_t& dst_pixel,
+                integer_t& fragment,
+                integer_t& fb,
+                Scratch& scratches);
+
+    void    build_blendFOneMinusF(  component_t& temp,
+                                    const integer_t& factor, 
+                                    const integer_t& fragment,
+                                    const integer_t& fb);
+
+    void    build_blendOneMinusFF(  component_t& temp,
+                                    const integer_t& factor, 
+                                    const integer_t& fragment,
+                                    const integer_t& fb);
+
+    void build_coverage_application(component_t& fragment,
+                                    const fragment_parts_t& parts,
+                                    Scratch& regs);
+
+    void build_alpha_test(component_t& fragment, const fragment_parts_t& parts);
+
+    enum { Z_TEST=1, Z_WRITE=2 }; 
+    void build_depth_test(const fragment_parts_t& parts, uint32_t mask);
+    void build_iterate_z(const fragment_parts_t& parts);
+    void build_iterate_f(const fragment_parts_t& parts);
+    void build_iterate_texture_coordinates(const fragment_parts_t& parts);
+
+    void build_logic_op(pixel_t& pixel, Scratch& regs);
+
+    void build_masking(pixel_t& pixel, Scratch& regs);
+
+    void build_and_immediate(int d, int s, uint32_t mask, int bits);
+
+    bool    isAlphaSourceNeeded() const;
+
+    enum {
+        FACTOR_SRC=1, FACTOR_DST=2, BLEND_SRC=4, BLEND_DST=8 
+    };
+    
+    enum {
+        LOGIC_OP=1, LOGIC_OP_SRC=2, LOGIC_OP_DST=4
+    };
+
+    static int blending_codes(int fs, int fd);
+
+    builder_context_t   mBuilderContext;
+    texture_machine_t   mTextureMachine;
+    component_info_t    mInfo[4];
+    int                 mBlending;
+    int                 mMasking;
+    int                 mAllMasked;
+    int                 mLogicOp;
+    int                 mAlphaTest;
+    int                 mAA;
+    int                 mDithering;
+    int                 mDepthTest;
+
+    int             mSmooth;
+    int             mFog;
+    pixel_t         mDstPixel;
+    
+    GGLFormat       mCbFormat;
+    
+    int             mBlendFactorCached;
+    integer_t       mAlphaSource;
+    
+    int             mBaseRegister;
+    
+    int             mBlendSrc;
+    int             mBlendDst;
+    int             mBlendSrcA;
+    int             mBlendDstA;
+    
+    int             mOptLevel;
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_GGLASSEMBLER_H
diff --git a/libpixelflinger/codeflinger/MIPS64Assembler.cpp b/libpixelflinger/codeflinger/MIPS64Assembler.cpp
new file mode 100644
index 0000000..d6d2156
--- /dev/null
+++ b/libpixelflinger/codeflinger/MIPS64Assembler.cpp
@@ -0,0 +1,1447 @@
+/* libs/pixelflinger/codeflinger/MIPS64Assembler.cpp
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+
+/* MIPS64 assembler and ARM->MIPS64 assembly translator
+**
+** The approach is utilize MIPSAssembler generator, using inherited MIPS64Assembler
+** that overrides just the specific MIPS64r6 instructions.
+** For now ArmToMips64Assembler is copied over from ArmToMipsAssembler class,
+** changing some MIPS64r6 related stuff.
+**
+*/
+
+#define LOG_TAG "MIPS64Assembler"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <cutils/properties.h>
+#include <log/log.h>
+#include <private/pixelflinger/ggl_context.h>
+
+#include "MIPS64Assembler.h"
+#include "CodeCache.h"
+#include "mips64_disassem.h"
+
+#define NOT_IMPLEMENTED()  LOG_ALWAYS_FATAL("Arm instruction %s not yet implemented\n", __func__)
+#define __unused __attribute__((__unused__))
+
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark ArmToMips64Assembler...
+#endif
+
+ArmToMips64Assembler::ArmToMips64Assembler(const sp<Assembly>& assembly,
+                                           char *abuf, int linesz, int instr_count)
+    :   ARMAssemblerInterface(),
+        mArmDisassemblyBuffer(abuf),
+        mArmLineLength(linesz),
+        mArmInstrCount(instr_count),
+        mInum(0),
+        mAssembly(assembly)
+{
+    mMips = new MIPS64Assembler(assembly, this);
+    mArmPC = (uint32_t **) malloc(ARM_MAX_INSTUCTIONS * sizeof(uint32_t *));
+    init_conditional_labels();
+}
+
+ArmToMips64Assembler::ArmToMips64Assembler(void* assembly)
+    :   ARMAssemblerInterface(),
+        mArmDisassemblyBuffer(NULL),
+        mInum(0),
+        mAssembly(NULL)
+{
+    mMips = new MIPS64Assembler(assembly, this);
+    mArmPC = (uint32_t **) malloc(ARM_MAX_INSTUCTIONS * sizeof(uint32_t *));
+    init_conditional_labels();
+}
+
+ArmToMips64Assembler::~ArmToMips64Assembler()
+{
+    delete mMips;
+    free((void *) mArmPC);
+}
+
+uint32_t* ArmToMips64Assembler::pc() const
+{
+    return mMips->pc();
+}
+
+uint32_t* ArmToMips64Assembler::base() const
+{
+    return mMips->base();
+}
+
+void ArmToMips64Assembler::reset()
+{
+    cond.labelnum = 0;
+    mInum = 0;
+    mMips->reset();
+}
+
+int ArmToMips64Assembler::getCodegenArch()
+{
+    return CODEGEN_ARCH_MIPS64;
+}
+
+void ArmToMips64Assembler::comment(const char* string)
+{
+    mMips->comment(string);
+}
+
+void ArmToMips64Assembler::label(const char* theLabel)
+{
+    mMips->label(theLabel);
+}
+
+void ArmToMips64Assembler::disassemble(const char* name)
+{
+    mMips->disassemble(name);
+}
+
+void ArmToMips64Assembler::init_conditional_labels()
+{
+    int i;
+    for (i=0;i<99; ++i) {
+        sprintf(cond.label[i], "cond_%d", i);
+    }
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Prolog/Epilog & Generate...
+#endif
+
+void ArmToMips64Assembler::prolog()
+{
+    mArmPC[mInum++] = pc();  // save starting PC for this instr
+
+    mMips->DADDIU(R_sp, R_sp, -(5 * 8));
+    mMips->SD(R_s0, R_sp, 0);
+    mMips->SD(R_s1, R_sp, 8);
+    mMips->SD(R_s2, R_sp, 16);
+    mMips->SD(R_s3, R_sp, 24);
+    mMips->SD(R_s4, R_sp, 32);
+    mMips->MOVE(R_v0, R_a0);    // move context * passed in a0 to v0 (arm r0)
+}
+
+void ArmToMips64Assembler::epilog(uint32_t touched __unused)
+{
+    mArmPC[mInum++] = pc();  // save starting PC for this instr
+
+    mMips->LD(R_s0, R_sp, 0);
+    mMips->LD(R_s1, R_sp, 8);
+    mMips->LD(R_s2, R_sp, 16);
+    mMips->LD(R_s3, R_sp, 24);
+    mMips->LD(R_s4, R_sp, 32);
+    mMips->DADDIU(R_sp, R_sp, (5 * 8));
+    mMips->JR(R_ra);
+
+}
+
+int ArmToMips64Assembler::generate(const char* name)
+{
+    return mMips->generate(name);
+}
+
+void ArmToMips64Assembler::fix_branches()
+{
+    mMips->fix_branches();
+}
+
+uint32_t* ArmToMips64Assembler::pcForLabel(const char* label)
+{
+    return mMips->pcForLabel(label);
+}
+
+void ArmToMips64Assembler::set_condition(int mode, int R1, int R2) {
+    if (mode == 2) {
+        cond.type = SBIT_COND;
+    } else {
+        cond.type = CMP_COND;
+    }
+    cond.r1 = R1;
+    cond.r2 = R2;
+}
+
+//----------------------------------------------------------
+
+#if 0
+#pragma mark -
+#pragma mark Addressing modes & shifters...
+#endif
+
+
+// do not need this for MIPS, but it is in the Interface (virtual)
+int ArmToMips64Assembler::buildImmediate(
+        uint32_t immediate, uint32_t& rot, uint32_t& imm)
+{
+    // for MIPS, any 32-bit immediate is OK
+    rot = 0;
+    imm = immediate;
+    return 0;
+}
+
+// shifters...
+
+bool ArmToMips64Assembler::isValidImmediate(uint32_t immediate __unused)
+{
+    // for MIPS, any 32-bit immediate is OK
+    return true;
+}
+
+uint32_t ArmToMips64Assembler::imm(uint32_t immediate)
+{
+    amode.value = immediate;
+    return AMODE_IMM;
+}
+
+uint32_t ArmToMips64Assembler::reg_imm(int Rm, int type, uint32_t shift)
+{
+    amode.reg = Rm;
+    amode.stype = type;
+    amode.value = shift;
+    return AMODE_REG_IMM;
+}
+
+uint32_t ArmToMips64Assembler::reg_rrx(int Rm __unused)
+{
+    // reg_rrx mode is not used in the GLLAssember code at this time
+    return AMODE_UNSUPPORTED;
+}
+
+uint32_t ArmToMips64Assembler::reg_reg(int Rm __unused, int type __unused,
+                                       int Rs __unused)
+{
+    // reg_reg mode is not used in the GLLAssember code at this time
+    return AMODE_UNSUPPORTED;
+}
+
+
+// addressing modes...
+// LDR(B)/STR(B)/PLD (immediate and Rm can be negative, which indicate U=0)
+uint32_t ArmToMips64Assembler::immed12_pre(int32_t immed12, int W)
+{
+    LOG_ALWAYS_FATAL_IF(abs(immed12) >= 0x800,
+                        "LDR(B)/STR(B)/PLD immediate too big (%08x)",
+                        immed12);
+    amode.value = immed12;
+    amode.writeback = W;
+    return AMODE_IMM_12_PRE;
+}
+
+uint32_t ArmToMips64Assembler::immed12_post(int32_t immed12)
+{
+    LOG_ALWAYS_FATAL_IF(abs(immed12) >= 0x800,
+                        "LDR(B)/STR(B)/PLD immediate too big (%08x)",
+                        immed12);
+
+    amode.value = immed12;
+    return AMODE_IMM_12_POST;
+}
+
+uint32_t ArmToMips64Assembler::reg_scale_pre(int Rm, int type,
+        uint32_t shift, int W)
+{
+    LOG_ALWAYS_FATAL_IF(W | type | shift, "reg_scale_pre adv modes not yet implemented");
+
+    amode.reg = Rm;
+    // amode.stype = type;      // more advanced modes not used in GGLAssembler yet
+    // amode.value = shift;
+    // amode.writeback = W;
+    return AMODE_REG_SCALE_PRE;
+}
+
+uint32_t ArmToMips64Assembler::reg_scale_post(int Rm __unused, int type __unused,
+                                              uint32_t shift __unused)
+{
+    LOG_ALWAYS_FATAL("adr mode reg_scale_post not yet implemented\n");
+    return AMODE_UNSUPPORTED;
+}
+
+// LDRH/LDRSB/LDRSH/STRH (immediate and Rm can be negative, which indicate U=0)
+uint32_t ArmToMips64Assembler::immed8_pre(int32_t immed8, int W __unused)
+{
+    LOG_ALWAYS_FATAL("adr mode immed8_pre not yet implemented\n");
+
+    LOG_ALWAYS_FATAL_IF(abs(immed8) >= 0x100,
+                        "LDRH/LDRSB/LDRSH/STRH immediate too big (%08x)",
+                        immed8);
+    return AMODE_IMM_8_PRE;
+}
+
+uint32_t ArmToMips64Assembler::immed8_post(int32_t immed8)
+{
+    LOG_ALWAYS_FATAL_IF(abs(immed8) >= 0x100,
+                        "LDRH/LDRSB/LDRSH/STRH immediate too big (%08x)",
+                        immed8);
+    amode.value = immed8;
+    return AMODE_IMM_8_POST;
+}
+
+uint32_t ArmToMips64Assembler::reg_pre(int Rm, int W)
+{
+    LOG_ALWAYS_FATAL_IF(W, "reg_pre writeback not yet implemented");
+    amode.reg = Rm;
+    return AMODE_REG_PRE;
+}
+
+uint32_t ArmToMips64Assembler::reg_post(int Rm __unused)
+{
+    LOG_ALWAYS_FATAL("adr mode reg_post not yet implemented\n");
+    return AMODE_UNSUPPORTED;
+}
+
+
+
+// ----------------------------------------------------------------------------
+
+#if 0
+#pragma mark -
+#pragma mark Data Processing...
+#endif
+
+// check if the operand registers from a previous CMP or S-bit instruction
+// would be overwritten by this instruction. If so, move the value to a
+// safe register.
+// Note that we cannot tell at _this_ instruction time if a future (conditional)
+// instruction will _also_ use this value (a defect of the simple 1-pass, one-
+// instruction-at-a-time translation). Therefore we must be conservative and
+// save the value before it is overwritten. This costs an extra MOVE instr.
+
+void ArmToMips64Assembler::protectConditionalOperands(int Rd)
+{
+    if (Rd == cond.r1) {
+        mMips->MOVE(R_cmp, cond.r1);
+        cond.r1 = R_cmp;
+    }
+    if (cond.type == CMP_COND && Rd == cond.r2) {
+        mMips->MOVE(R_cmp2, cond.r2);
+        cond.r2 = R_cmp2;
+    }
+}
+
+
+// interprets the addressing mode, and generates the common code
+// used by the majority of data-processing ops. Many MIPS instructions
+// have a register-based form and a different immediate form. See
+// opAND below for an example. (this could be inlined)
+//
+// this works with the imm(), reg_imm() methods above, which are directly
+// called by the GLLAssembler.
+// note: _signed parameter defaults to false (un-signed)
+// note: tmpReg parameter defaults to 1, MIPS register AT
+int ArmToMips64Assembler::dataProcAdrModes(int op, int& source, bool _signed, int tmpReg)
+{
+    if (op < AMODE_REG) {
+        source = op;
+        return SRC_REG;
+    } else if (op == AMODE_IMM) {
+        if ((!_signed && amode.value > 0xffff)
+                || (_signed && ((int)amode.value < -32768 || (int)amode.value > 32767) )) {
+            mMips->LUI(tmpReg, (amode.value >> 16));
+            if (amode.value & 0x0000ffff) {
+                mMips->ORI(tmpReg, tmpReg, (amode.value & 0x0000ffff));
+            }
+            source = tmpReg;
+            return SRC_REG;
+        } else {
+            source = amode.value;
+            return SRC_IMM;
+        }
+    } else if (op == AMODE_REG_IMM) {
+        switch (amode.stype) {
+            case LSL: mMips->SLL(tmpReg, amode.reg, amode.value); break;
+            case LSR: mMips->SRL(tmpReg, amode.reg, amode.value); break;
+            case ASR: mMips->SRA(tmpReg, amode.reg, amode.value); break;
+            case ROR: mMips->ROTR(tmpReg, amode.reg, amode.value); break;
+        }
+        source = tmpReg;
+        return SRC_REG;
+    } else {  // adr mode RRX is not used in GGL Assembler at this time
+        // we are screwed, this should be exception, assert-fail or something
+        LOG_ALWAYS_FATAL("adr mode reg_rrx not yet implemented\n");
+        return SRC_ERROR;
+    }
+}
+
+
+void ArmToMips64Assembler::dataProcessing(int opcode, int cc,
+        int s, int Rd, int Rn, uint32_t Op2)
+{
+    int src;    // src is modified by dataProcAdrModes() - passed as int&
+
+    if (cc != AL) {
+        protectConditionalOperands(Rd);
+        // the branch tests register(s) set by prev CMP or instr with 'S' bit set
+        // inverse the condition to jump past this conditional instruction
+        ArmToMips64Assembler::B(cc^1, cond.label[++cond.labelnum]);
+    } else {
+        mArmPC[mInum++] = pc();  // save starting PC for this instr
+    }
+
+    switch (opcode) {
+    case opAND:
+        if (dataProcAdrModes(Op2, src) == SRC_REG) {
+            mMips->AND(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->ANDI(Rd, Rn, src);
+        }
+        break;
+
+    case opADD:
+        // set "signed" to true for adr modes
+        if (dataProcAdrModes(Op2, src, true) == SRC_REG) {
+            mMips->ADDU(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->ADDIU(Rd, Rn, src);
+        }
+        break;
+
+    case opSUB:
+        // set "signed" to true for adr modes
+        if (dataProcAdrModes(Op2, src, true) == SRC_REG) {
+            mMips->SUBU(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->SUBIU(Rd, Rn, src);
+        }
+        break;
+
+    case opADD64:
+        // set "signed" to true for adr modes
+        if (dataProcAdrModes(Op2, src, true) == SRC_REG) {
+            mMips->DADDU(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->DADDIU(Rd, Rn, src);
+        }
+        break;
+
+    case opSUB64:
+        // set "signed" to true for adr modes
+        if (dataProcAdrModes(Op2, src, true) == SRC_REG) {
+            mMips->DSUBU(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->DSUBIU(Rd, Rn, src);
+        }
+        break;
+
+    case opEOR:
+        if (dataProcAdrModes(Op2, src) == SRC_REG) {
+            mMips->XOR(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->XORI(Rd, Rn, src);
+        }
+        break;
+
+    case opORR:
+        if (dataProcAdrModes(Op2, src) == SRC_REG) {
+            mMips->OR(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->ORI(Rd, Rn, src);
+        }
+        break;
+
+    case opBIC:
+        if (dataProcAdrModes(Op2, src) == SRC_IMM) {
+            // if we are 16-bit imnmediate, load to AT reg
+            mMips->ORI(R_at, 0, src);
+            src = R_at;
+        }
+        mMips->NOT(R_at, src);
+        mMips->AND(Rd, Rn, R_at);
+        break;
+
+    case opRSB:
+        if (dataProcAdrModes(Op2, src) == SRC_IMM) {
+            // if we are 16-bit imnmediate, load to AT reg
+            mMips->ORI(R_at, 0, src);
+            src = R_at;
+        }
+        mMips->SUBU(Rd, src, Rn);   // subu with the parameters reversed
+        break;
+
+    case opMOV:
+        if (Op2 < AMODE_REG) {  // op2 is reg # in this case
+            mMips->MOVE(Rd, Op2);
+        } else if (Op2 == AMODE_IMM) {
+            if (amode.value > 0xffff) {
+                mMips->LUI(Rd, (amode.value >> 16));
+                if (amode.value & 0x0000ffff) {
+                    mMips->ORI(Rd, Rd, (amode.value & 0x0000ffff));
+                }
+             } else {
+                mMips->ORI(Rd, 0, amode.value);
+            }
+        } else if (Op2 == AMODE_REG_IMM) {
+            switch (amode.stype) {
+            case LSL: mMips->SLL(Rd, amode.reg, amode.value); break;
+            case LSR: mMips->SRL(Rd, amode.reg, amode.value); break;
+            case ASR: mMips->SRA(Rd, amode.reg, amode.value); break;
+            case ROR: mMips->ROTR(Rd, amode.reg, amode.value); break;
+            }
+        }
+        else {
+            // adr mode RRX is not used in GGL Assembler at this time
+            mMips->UNIMPL();
+        }
+        break;
+
+    case opMVN:     // this is a 1's complement: NOT
+        if (Op2 < AMODE_REG) {  // op2 is reg # in this case
+            mMips->NOR(Rd, Op2, 0);     // NOT is NOR with 0
+            break;
+        } else if (Op2 == AMODE_IMM) {
+            if (amode.value > 0xffff) {
+                mMips->LUI(Rd, (amode.value >> 16));
+                if (amode.value & 0x0000ffff) {
+                    mMips->ORI(Rd, Rd, (amode.value & 0x0000ffff));
+                }
+             } else {
+                mMips->ORI(Rd, 0, amode.value);
+             }
+        } else if (Op2 == AMODE_REG_IMM) {
+            switch (amode.stype) {
+            case LSL: mMips->SLL(Rd, amode.reg, amode.value); break;
+            case LSR: mMips->SRL(Rd, amode.reg, amode.value); break;
+            case ASR: mMips->SRA(Rd, amode.reg, amode.value); break;
+            case ROR: mMips->ROTR(Rd, amode.reg, amode.value); break;
+            }
+        }
+        else {
+            // adr mode RRX is not used in GGL Assembler at this time
+            mMips->UNIMPL();
+        }
+        mMips->NOR(Rd, Rd, 0);     // NOT is NOR with 0
+        break;
+
+    case opCMP:
+        // Either operand of a CMP instr could get overwritten by a subsequent
+        // conditional instruction, which is ok, _UNLESS_ there is a _second_
+        // conditional instruction. Under MIPS, this requires doing the comparison
+        // again (SLT), and the original operands must be available. (and this
+        // pattern of multiple conditional instructions from same CMP _is_ used
+        // in GGL-Assembler)
+        //
+        // For now, if a conditional instr overwrites the operands, we will
+        // move them to dedicated temp regs. This is ugly, and inefficient,
+        // and should be optimized.
+        //
+        // WARNING: making an _Assumption_ that CMP operand regs will NOT be
+        // trashed by intervening NON-conditional instructions. In the general
+        // case this is legal, but it is NOT currently done in GGL-Assembler.
+
+        cond.type = CMP_COND;
+        cond.r1 = Rn;
+        if (dataProcAdrModes(Op2, src, false, R_cmp2) == SRC_REG) {
+            cond.r2 = src;
+        } else {                        // adr mode was SRC_IMM
+            mMips->ORI(R_cmp2, R_zero, src);
+            cond.r2 = R_cmp2;
+        }
+
+        break;
+
+
+    case opTST:
+    case opTEQ:
+    case opCMN:
+    case opADC:
+    case opSBC:
+    case opRSC:
+        mMips->UNIMPL(); // currently unused in GGL Assembler code
+        break;
+    }
+
+    if (cc != AL) {
+        mMips->label(cond.label[cond.labelnum]);
+    }
+    if (s && opcode != opCMP) {
+        cond.type = SBIT_COND;
+        cond.r1 = Rd;
+    }
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Multiply...
+#endif
+
+// multiply, accumulate
+void ArmToMips64Assembler::MLA(int cc __unused, int s,
+        int Rd, int Rm, int Rs, int Rn) {
+
+    //ALOGW("MLA");
+    mArmPC[mInum++] = pc();  // save starting PC for this instr
+
+    mMips->MUL(R_at, Rm, Rs);
+    mMips->ADDU(Rd, R_at, Rn);
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = Rd;
+    }
+}
+
+void ArmToMips64Assembler::MUL(int cc __unused, int s,
+        int Rd, int Rm, int Rs) {
+    mArmPC[mInum++] = pc();
+    mMips->MUL(Rd, Rm, Rs);
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = Rd;
+    }
+}
+
+void ArmToMips64Assembler::UMULL(int cc __unused, int s,
+        int RdLo, int RdHi, int Rm, int Rs) {
+    mArmPC[mInum++] = pc();
+    mMips->MUH(RdHi, Rm, Rs);
+    mMips->MUL(RdLo, Rm, Rs);
+
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = RdHi;     // BUG...
+        LOG_ALWAYS_FATAL("Condition on UMULL must be on 64-bit result\n");
+    }
+}
+
+void ArmToMips64Assembler::UMUAL(int cc __unused, int s,
+        int RdLo __unused, int RdHi, int Rm __unused, int Rs __unused) {
+    LOG_FATAL_IF(RdLo==Rm || RdHi==Rm || RdLo==RdHi,
+                        "UMUAL(r%u,r%u,r%u,r%u)", RdLo,RdHi,Rm,Rs);
+    // *mPC++ =    (cc<<28) | (1<<23) | (1<<21) | (s<<20) |
+    //             (RdHi<<16) | (RdLo<<12) | (Rs<<8) | 0x90 | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = RdHi;     // BUG...
+        LOG_ALWAYS_FATAL("Condition on UMULL must be on 64-bit result\n");
+    }
+}
+
+void ArmToMips64Assembler::SMULL(int cc __unused, int s,
+        int RdLo __unused, int RdHi, int Rm __unused, int Rs __unused) {
+    LOG_FATAL_IF(RdLo==Rm || RdHi==Rm || RdLo==RdHi,
+                        "SMULL(r%u,r%u,r%u,r%u)", RdLo,RdHi,Rm,Rs);
+    // *mPC++ =    (cc<<28) | (1<<23) | (1<<22) | (s<<20) |
+    //             (RdHi<<16) | (RdLo<<12) | (Rs<<8) | 0x90 | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = RdHi;     // BUG...
+        LOG_ALWAYS_FATAL("Condition on SMULL must be on 64-bit result\n");
+    }
+}
+void ArmToMips64Assembler::SMUAL(int cc __unused, int s,
+        int RdLo __unused, int RdHi, int Rm __unused, int Rs __unused) {
+    LOG_FATAL_IF(RdLo==Rm || RdHi==Rm || RdLo==RdHi,
+                        "SMUAL(r%u,r%u,r%u,r%u)", RdLo,RdHi,Rm,Rs);
+    // *mPC++ =    (cc<<28) | (1<<23) | (1<<22) | (1<<21) | (s<<20) |
+    //             (RdHi<<16) | (RdLo<<12) | (Rs<<8) | 0x90 | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = RdHi;     // BUG...
+        LOG_ALWAYS_FATAL("Condition on SMUAL must be on 64-bit result\n");
+    }
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Branches...
+#endif
+
+// branches...
+
+void ArmToMips64Assembler::B(int cc, const char* label)
+{
+    mArmPC[mInum++] = pc();
+    if (cond.type == SBIT_COND) { cond.r2 = R_zero; }
+
+    switch(cc) {
+        case EQ: mMips->BEQ(cond.r1, cond.r2, label); break;
+        case NE: mMips->BNE(cond.r1, cond.r2, label); break;
+        case HS: mMips->BGEU(cond.r1, cond.r2, label); break;
+        case LO: mMips->BLTU(cond.r1, cond.r2, label); break;
+        case MI: mMips->BLT(cond.r1, cond.r2, label); break;
+        case PL: mMips->BGE(cond.r1, cond.r2, label); break;
+
+        case HI: mMips->BGTU(cond.r1, cond.r2, label); break;
+        case LS: mMips->BLEU(cond.r1, cond.r2, label); break;
+        case GE: mMips->BGE(cond.r1, cond.r2, label); break;
+        case LT: mMips->BLT(cond.r1, cond.r2, label); break;
+        case GT: mMips->BGT(cond.r1, cond.r2, label); break;
+        case LE: mMips->BLE(cond.r1, cond.r2, label); break;
+        case AL: mMips->B(label); break;
+        case NV: /* B Never - no instruction */ break;
+
+        case VS:
+        case VC:
+        default:
+            LOG_ALWAYS_FATAL("Unsupported cc: %02x\n", cc);
+            break;
+    }
+}
+
+void ArmToMips64Assembler::BL(int cc __unused, const char* label __unused)
+{
+    LOG_ALWAYS_FATAL("branch-and-link not supported yet\n");
+    mArmPC[mInum++] = pc();
+}
+
+// no use for Branches with integer PC, but they're in the Interface class ....
+void ArmToMips64Assembler::B(int cc __unused, uint32_t* to_pc __unused)
+{
+    LOG_ALWAYS_FATAL("branch to absolute PC not supported, use Label\n");
+    mArmPC[mInum++] = pc();
+}
+
+void ArmToMips64Assembler::BL(int cc __unused, uint32_t* to_pc __unused)
+{
+    LOG_ALWAYS_FATAL("branch to absolute PC not supported, use Label\n");
+    mArmPC[mInum++] = pc();
+}
+
+void ArmToMips64Assembler::BX(int cc __unused, int Rn __unused)
+{
+    LOG_ALWAYS_FATAL("branch to absolute PC not supported, use Label\n");
+    mArmPC[mInum++] = pc();
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Data Transfer...
+#endif
+
+// data transfer...
+void ArmToMips64Assembler::LDR(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed12_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            amode.writeback = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_12_PRE:
+            if (Rn == ARMAssemblerInterface::SP) {
+                Rn = R_sp;      // convert LDR via Arm SP to LW via Mips SP
+            }
+            mMips->LW(Rd, Rn, amode.value);
+            if (amode.writeback) {      // OPTIONAL writeback on pre-index mode
+                mMips->DADDIU(Rn, Rn, amode.value);
+            }
+            break;
+        case AMODE_IMM_12_POST:
+            if (Rn == ARMAssemblerInterface::SP) {
+                Rn = R_sp;      // convert STR thru Arm SP to STR thru Mips SP
+            }
+            mMips->LW(Rd, Rn, 0);
+            mMips->DADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_SCALE_PRE:
+            // we only support simple base + index, no advanced modes for this one yet
+            mMips->DADDU(R_at, Rn, amode.reg);
+            mMips->LW(Rd, R_at, 0);
+            break;
+    }
+}
+
+void ArmToMips64Assembler::LDRB(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed12_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            amode.writeback = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_12_PRE:
+            mMips->LBU(Rd, Rn, amode.value);
+            if (amode.writeback) {      // OPTIONAL writeback on pre-index mode
+                mMips->DADDIU(Rn, Rn, amode.value);
+            }
+            break;
+        case AMODE_IMM_12_POST:
+            mMips->LBU(Rd, Rn, 0);
+            mMips->DADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_SCALE_PRE:
+            // we only support simple base + index, no advanced modes for this one yet
+            mMips->DADDU(R_at, Rn, amode.reg);
+            mMips->LBU(Rd, R_at, 0);
+            break;
+    }
+
+}
+
+void ArmToMips64Assembler::STR(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed12_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            amode.writeback = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_12_PRE:
+            if (Rn == ARMAssemblerInterface::SP) {
+                Rn = R_sp;  // convert STR thru Arm SP to SW thru Mips SP
+            }
+            if (amode.writeback) {      // OPTIONAL writeback on pre-index mode
+                // If we will writeback, then update the index reg, then store.
+                // This correctly handles stack-push case.
+                mMips->DADDIU(Rn, Rn, amode.value);
+                mMips->SW(Rd, Rn, 0);
+            } else {
+                // No writeback so store offset by value
+                mMips->SW(Rd, Rn, amode.value);
+            }
+            break;
+        case AMODE_IMM_12_POST:
+            mMips->SW(Rd, Rn, 0);
+            mMips->DADDIU(Rn, Rn, amode.value);  // post index always writes back
+            break;
+        case AMODE_REG_SCALE_PRE:
+            // we only support simple base + index, no advanced modes for this one yet
+            mMips->DADDU(R_at, Rn, amode.reg);
+            mMips->SW(Rd, R_at, 0);
+            break;
+    }
+}
+
+void ArmToMips64Assembler::STRB(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed12_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            amode.writeback = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_12_PRE:
+            mMips->SB(Rd, Rn, amode.value);
+            if (amode.writeback) {      // OPTIONAL writeback on pre-index mode
+                mMips->DADDIU(Rn, Rn, amode.value);
+            }
+            break;
+        case AMODE_IMM_12_POST:
+            mMips->SB(Rd, Rn, 0);
+            mMips->DADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_SCALE_PRE:
+            // we only support simple base + index, no advanced modes for this one yet
+            mMips->DADDU(R_at, Rn, amode.reg);
+            mMips->SB(Rd, R_at, 0);
+            break;
+    }
+}
+
+void ArmToMips64Assembler::LDRH(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed8_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_8_PRE:      // no support yet for writeback
+            mMips->LHU(Rd, Rn, amode.value);
+            break;
+        case AMODE_IMM_8_POST:
+            mMips->LHU(Rd, Rn, 0);
+            mMips->DADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_PRE:
+            // we only support simple base +/- index
+            if (amode.reg >= 0) {
+                mMips->DADDU(R_at, Rn, amode.reg);
+            } else {
+                mMips->DSUBU(R_at, Rn, abs(amode.reg));
+            }
+            mMips->LHU(Rd, R_at, 0);
+            break;
+    }
+}
+
+void ArmToMips64Assembler::LDRSB(int cc __unused, int Rd __unused,
+                                 int Rn __unused, uint32_t offset __unused)
+{
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMips64Assembler::LDRSH(int cc __unused, int Rd __unused,
+                                 int Rn __unused, uint32_t offset __unused)
+{
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMips64Assembler::STRH(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed8_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_8_PRE:      // no support yet for writeback
+            mMips->SH(Rd, Rn, amode.value);
+            break;
+        case AMODE_IMM_8_POST:
+            mMips->SH(Rd, Rn, 0);
+            mMips->DADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_PRE:
+            // we only support simple base +/- index
+            if (amode.reg >= 0) {
+                mMips->DADDU(R_at, Rn, amode.reg);
+            } else {
+                mMips->DSUBU(R_at, Rn, abs(amode.reg));
+            }
+            mMips->SH(Rd, R_at, 0);
+            break;
+    }
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Block Data Transfer...
+#endif
+
+// block data transfer...
+void ArmToMips64Assembler::LDM(int cc __unused, int dir __unused,
+        int Rn __unused, int W __unused, uint32_t reg_list __unused)
+{   //                        ED FD EA FA      IB IA DB DA
+    // const uint8_t P[8] = { 1, 0, 1, 0,      1, 0, 1, 0 };
+    // const uint8_t U[8] = { 1, 1, 0, 0,      1, 1, 0, 0 };
+    // *mPC++ = (cc<<28) | (4<<25) | (uint32_t(P[dir])<<24) |
+    //         (uint32_t(U[dir])<<23) | (1<<20) | (W<<21) | (Rn<<16) | reg_list;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMips64Assembler::STM(int cc __unused, int dir __unused,
+        int Rn __unused, int W __unused, uint32_t reg_list __unused)
+{   //                        FA EA FD ED      IB IA DB DA
+    // const uint8_t P[8] = { 0, 1, 0, 1,      1, 0, 1, 0 };
+    // const uint8_t U[8] = { 0, 0, 1, 1,      1, 1, 0, 0 };
+    // *mPC++ = (cc<<28) | (4<<25) | (uint32_t(P[dir])<<24) |
+    //         (uint32_t(U[dir])<<23) | (0<<20) | (W<<21) | (Rn<<16) | reg_list;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Special...
+#endif
+
+// special...
+void ArmToMips64Assembler::SWP(int cc __unused, int Rn __unused,
+                               int Rd __unused, int Rm __unused) {
+    // *mPC++ = (cc<<28) | (2<<23) | (Rn<<16) | (Rd << 12) | 0x90 | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMips64Assembler::SWPB(int cc __unused, int Rn __unused,
+                                int Rd __unused, int Rm __unused) {
+    // *mPC++ = (cc<<28) | (2<<23) | (1<<22) | (Rn<<16) | (Rd << 12) | 0x90 | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMips64Assembler::SWI(int cc __unused, uint32_t comment __unused) {
+    // *mPC++ = (cc<<28) | (0xF<<24) | comment;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+
+#if 0
+#pragma mark -
+#pragma mark DSP instructions...
+#endif
+
+// DSP instructions...
+void ArmToMips64Assembler::PLD(int Rn __unused, uint32_t offset) {
+    LOG_ALWAYS_FATAL_IF(!((offset&(1<<24)) && !(offset&(1<<21))),
+                        "PLD only P=1, W=0");
+    // *mPC++ = 0xF550F000 | (Rn<<16) | offset;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMips64Assembler::CLZ(int cc __unused, int Rd, int Rm)
+{
+    mArmPC[mInum++] = pc();
+    mMips->CLZ(Rd, Rm);
+}
+
+void ArmToMips64Assembler::QADD(int cc __unused, int Rd __unused,
+                                int Rm __unused, int Rn __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1000050 | (Rn<<16) | (Rd<<12) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMips64Assembler::QDADD(int cc __unused, int Rd __unused,
+                                 int Rm __unused, int Rn __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1400050 | (Rn<<16) | (Rd<<12) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMips64Assembler::QSUB(int cc __unused, int Rd __unused,
+                                int Rm __unused, int Rn __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1200050 | (Rn<<16) | (Rd<<12) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMips64Assembler::QDSUB(int cc __unused, int Rd __unused,
+                                 int Rm __unused, int Rn __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1600050 | (Rn<<16) | (Rd<<12) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+// 16 x 16 signed multiply (like SMLAxx without the accumulate)
+void ArmToMips64Assembler::SMUL(int cc __unused, int xy,
+                int Rd, int Rm, int Rs)
+{
+    mArmPC[mInum++] = pc();
+
+    // the 16 bits may be in the top or bottom half of 32-bit source reg,
+    // as defined by the codes BB, BT, TB, TT (compressed param xy)
+    // where x corresponds to Rm and y to Rs
+
+    // select half-reg for Rm
+    if (xy & xyTB) {
+        // use top 16-bits
+        mMips->SRA(R_at, Rm, 16);
+    } else {
+        // use bottom 16, but sign-extend to 32
+        mMips->SEH(R_at, Rm);
+    }
+    // select half-reg for Rs
+    if (xy & xyBT) {
+        // use top 16-bits
+        mMips->SRA(R_at2, Rs, 16);
+    } else {
+        // use bottom 16, but sign-extend to 32
+        mMips->SEH(R_at2, Rs);
+    }
+    mMips->MUL(Rd, R_at, R_at2);
+}
+
+// signed 32b x 16b multiple, save top 32-bits of 48-bit result
+void ArmToMips64Assembler::SMULW(int cc __unused, int y,
+                int Rd, int Rm, int Rs)
+{
+    mArmPC[mInum++] = pc();
+
+    // the selector yT or yB refers to reg Rs
+    if (y & yT) {
+        // zero the bottom 16-bits, with 2 shifts, it can affect result
+        mMips->SRL(R_at, Rs, 16);
+        mMips->SLL(R_at, R_at, 16);
+
+    } else {
+        // move low 16-bit half, to high half
+        mMips->SLL(R_at, Rs, 16);
+    }
+    mMips->MUH(Rd, Rm, R_at);
+}
+
+// 16 x 16 signed multiply, accumulate: Rd = Rm{16} * Rs{16} + Rn
+void ArmToMips64Assembler::SMLA(int cc __unused, int xy,
+                int Rd, int Rm, int Rs, int Rn)
+{
+    mArmPC[mInum++] = pc();
+
+    // the 16 bits may be in the top or bottom half of 32-bit source reg,
+    // as defined by the codes BB, BT, TB, TT (compressed param xy)
+    // where x corresponds to Rm and y to Rs
+
+    // select half-reg for Rm
+    if (xy & xyTB) {
+        // use top 16-bits
+        mMips->SRA(R_at, Rm, 16);
+    } else {
+        // use bottom 16, but sign-extend to 32
+        mMips->SEH(R_at, Rm);
+    }
+    // select half-reg for Rs
+    if (xy & xyBT) {
+        // use top 16-bits
+        mMips->SRA(R_at2, Rs, 16);
+    } else {
+        // use bottom 16, but sign-extend to 32
+        mMips->SEH(R_at2, Rs);
+    }
+
+    mMips->MUL(R_at, R_at, R_at2);
+    mMips->ADDU(Rd, R_at, Rn);
+}
+
+void ArmToMips64Assembler::SMLAL(int cc __unused, int xy __unused,
+                                 int RdHi __unused, int RdLo __unused,
+                                 int Rs __unused, int Rm __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1400080 | (RdHi<<16) | (RdLo<<12) | (Rs<<8) | (xy<<4) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMips64Assembler::SMLAW(int cc __unused, int y __unused,
+                                 int Rd __unused, int Rm __unused,
+                                 int Rs __unused, int Rn __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1200080 | (Rd<<16) | (Rn<<12) | (Rs<<8) | (y<<4) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+// used by ARMv6 version of GGLAssembler::filter32
+void ArmToMips64Assembler::UXTB16(int cc __unused, int Rd, int Rm, int rotate)
+{
+    mArmPC[mInum++] = pc();
+
+    //Rd[31:16] := ZeroExtend((Rm ROR (8 * sh))[23:16]),
+    //Rd[15:0] := ZeroExtend((Rm ROR (8 * sh))[7:0]). sh 0-3.
+
+    mMips->ROTR(R_at2, Rm, rotate * 8);
+    mMips->LUI(R_at, 0xFF);
+    mMips->ORI(R_at, R_at, 0xFF);
+    mMips->AND(Rd, R_at2, R_at);
+}
+
+void ArmToMips64Assembler::UBFX(int cc __unused, int Rd __unused, int Rn __unused,
+                                int lsb __unused, int width __unused)
+{
+     /* Placeholder for UBFX */
+     mArmPC[mInum++] = pc();
+
+     mMips->NOP2();
+     NOT_IMPLEMENTED();
+}
+
+// ----------------------------------------------------------------------------
+// Address Processing...
+// ----------------------------------------------------------------------------
+
+void ArmToMips64Assembler::ADDR_ADD(int cc,
+        int s, int Rd, int Rn, uint32_t Op2)
+{
+//    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+//    if(s  != 0) { NOT_IMPLEMENTED(); return;} //Not required
+    dataProcessing(opADD64, cc, s, Rd, Rn, Op2);
+}
+
+void ArmToMips64Assembler::ADDR_SUB(int cc,
+        int s, int Rd, int Rn, uint32_t Op2)
+{
+//    if(cc != AL){ NOT_IMPLEMENTED(); return;} //Not required
+//    if(s  != 0) { NOT_IMPLEMENTED(); return;} //Not required
+    dataProcessing(opSUB64, cc, s, Rd, Rn, Op2);
+}
+
+void ArmToMips64Assembler::ADDR_LDR(int cc __unused, int Rd,
+                                    int Rn, uint32_t offset) {
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed12_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            amode.writeback = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_12_PRE:
+            if (Rn == ARMAssemblerInterface::SP) {
+                Rn = R_sp;      // convert LDR via Arm SP to LW via Mips SP
+            }
+            mMips->LD(Rd, Rn, amode.value);
+            if (amode.writeback) {      // OPTIONAL writeback on pre-index mode
+                mMips->DADDIU(Rn, Rn, amode.value);
+            }
+            break;
+        case AMODE_IMM_12_POST:
+            if (Rn == ARMAssemblerInterface::SP) {
+                Rn = R_sp;      // convert STR thru Arm SP to STR thru Mips SP
+            }
+            mMips->LD(Rd, Rn, 0);
+            mMips->DADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_SCALE_PRE:
+            // we only support simple base + index, no advanced modes for this one yet
+            mMips->DADDU(R_at, Rn, amode.reg);
+            mMips->LD(Rd, R_at, 0);
+            break;
+    }
+}
+
+void ArmToMips64Assembler::ADDR_STR(int cc __unused, int Rd,
+                                    int Rn, uint32_t offset) {
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed12_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            amode.writeback = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_12_PRE:
+            if (Rn == ARMAssemblerInterface::SP) {
+                Rn = R_sp;  // convert STR thru Arm SP to SW thru Mips SP
+            }
+            if (amode.writeback) {      // OPTIONAL writeback on pre-index mode
+                // If we will writeback, then update the index reg, then store.
+                // This correctly handles stack-push case.
+                mMips->DADDIU(Rn, Rn, amode.value);
+                mMips->SD(Rd, Rn, 0);
+            } else {
+                // No writeback so store offset by value
+                mMips->SD(Rd, Rn, amode.value);
+            }
+            break;
+        case AMODE_IMM_12_POST:
+            mMips->SD(Rd, Rn, 0);
+            mMips->DADDIU(Rn, Rn, amode.value);  // post index always writes back
+            break;
+        case AMODE_REG_SCALE_PRE:
+            // we only support simple base + index, no advanced modes for this one yet
+            mMips->DADDU(R_at, Rn, amode.reg);
+            mMips->SD(Rd, R_at, 0);
+            break;
+    }
+}
+
+#if 0
+#pragma mark -
+#pragma mark MIPS Assembler...
+#endif
+
+
+//**************************************************************************
+//**************************************************************************
+//**************************************************************************
+
+
+/* MIPS64 assembler
+** this is a subset of mips64r6, targeted specifically at ARM instruction
+** replacement in the pixelflinger/codeflinger code.
+**
+** This class is extended from MIPSAssembler class and overrides only
+** MIPS64r6 specific stuff.
+*/
+
+MIPS64Assembler::MIPS64Assembler(const sp<Assembly>& assembly, ArmToMips64Assembler *parent)
+    : MIPSAssembler::MIPSAssembler(assembly, NULL), mParent(parent)
+{
+}
+
+MIPS64Assembler::MIPS64Assembler(void* assembly, ArmToMips64Assembler *parent)
+    : MIPSAssembler::MIPSAssembler(assembly), mParent(parent)
+{
+}
+
+MIPS64Assembler::~MIPS64Assembler()
+{
+}
+
+void MIPS64Assembler::reset()
+{
+    if (mAssembly != NULL) {
+        mBase = mPC = (uint32_t *)mAssembly->base();
+    } else {
+        mPC = mBase = base();
+    }
+    mBranchTargets.clear();
+    mLabels.clear();
+    mLabelsInverseMapping.clear();
+    mComments.clear();
+}
+
+
+void MIPS64Assembler::disassemble(const char* name __unused)
+{
+    char di_buf[140];
+
+    bool arm_disasm_fmt = (mParent->mArmDisassemblyBuffer == NULL) ? false : true;
+
+    typedef char dstr[40];
+    dstr *lines = (dstr *)mParent->mArmDisassemblyBuffer;
+
+    if (mParent->mArmDisassemblyBuffer != NULL) {
+        for (int i=0; i<mParent->mArmInstrCount; ++i) {
+            string_detab(lines[i]);
+        }
+    }
+
+    size_t count = pc()-base();
+    uint32_t* mipsPC = base();
+
+    while (count--) {
+        ssize_t label = mLabelsInverseMapping.indexOfKey(mipsPC);
+        if (label >= 0) {
+            ALOGW("%s:\n", mLabelsInverseMapping.valueAt(label));
+        }
+        ssize_t comment = mComments.indexOfKey(mipsPC);
+        if (comment >= 0) {
+            ALOGW("; %s\n", mComments.valueAt(comment));
+        }
+        ::mips_disassem(mipsPC, di_buf, arm_disasm_fmt);
+        string_detab(di_buf);
+        string_pad(di_buf, 30);
+        ALOGW("%08lx:    %08x    %s", uintptr_t(mipsPC), uint32_t(*mipsPC), di_buf);
+        mipsPC++;
+    }
+}
+
+void MIPS64Assembler::fix_branches()
+{
+    // fixup all the branches
+    size_t count = mBranchTargets.size();
+    while (count--) {
+        const branch_target_t& bt = mBranchTargets[count];
+        uint32_t* target_pc = mLabels.valueFor(bt.label);
+        LOG_ALWAYS_FATAL_IF(!target_pc,
+                "error resolving branch targets, target_pc is null");
+        int32_t offset = int32_t(target_pc - (bt.pc+1));
+        *bt.pc |= offset & 0x00FFFF;
+    }
+}
+
+void MIPS64Assembler::DADDU(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (daddu_fn<<FUNC_SHF)
+                    | (Rs<<RS_SHF) | (Rt<<RT_SHF) | (Rd<<RD_SHF);
+}
+
+void MIPS64Assembler::DADDIU(int Rt, int Rs, int16_t imm)
+{
+    *mPC++ = (daddiu_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | (imm & MSK_16);
+}
+
+void MIPS64Assembler::DSUBU(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (dsubu_fn<<FUNC_SHF) |
+                        (Rs<<RS_SHF) | (Rt<<RT_SHF) | (Rd<<RD_SHF) ;
+}
+
+void MIPS64Assembler::DSUBIU(int Rt, int Rs, int16_t imm)   // really addiu(d, s, -j)
+{
+    *mPC++ = (daddiu_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | ((-imm) & MSK_16);
+}
+
+void MIPS64Assembler::MUL(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (mul_fn<<RE_SHF) | (sop30_fn<<FUNC_SHF) |
+                        (Rs<<RS_SHF) | (Rt<<RT_SHF) | (Rd<<RD_SHF) ;
+}
+
+void MIPS64Assembler::MUH(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (muh_fn<<RE_SHF) | (sop30_fn<<FUNC_SHF) |
+                        (Rs<<RS_SHF) | (Rt<<RT_SHF) | (Rd<<RD_SHF) ;
+}
+
+void MIPS64Assembler::CLO(int Rd, int Rs)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (17<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (1<<RE_SHF);
+}
+
+void MIPS64Assembler::CLZ(int Rd, int Rs)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (16<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (1<<RE_SHF);
+}
+
+void MIPS64Assembler::LD(int Rt, int Rbase, int16_t offset)
+{
+    *mPC++ = (ld_op<<OP_SHF) | (Rbase<<RS_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+void MIPS64Assembler::SD(int Rt, int Rbase, int16_t offset)
+{
+    *mPC++ = (sd_op<<OP_SHF) | (Rbase<<RS_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+void MIPS64Assembler::LUI(int Rt, int16_t offset)
+{
+    *mPC++ = (aui_op<<OP_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+
+void MIPS64Assembler::JR(int Rs)
+{
+        *mPC++ = (spec_op<<OP_SHF) | (Rs<<RS_SHF) | (jalr_fn << FUNC_SHF);
+        MIPS64Assembler::NOP();
+}
+
+}; // namespace android:
diff --git a/libpixelflinger/codeflinger/MIPS64Assembler.h b/libpixelflinger/codeflinger/MIPS64Assembler.h
new file mode 100644
index 0000000..b43e5da
--- /dev/null
+++ b/libpixelflinger/codeflinger/MIPS64Assembler.h
@@ -0,0 +1,404 @@
+/* libs/pixelflinger/codeflinger/MIPS64Assembler.h
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef ANDROID_MIPS64ASSEMBLER_H
+#define ANDROID_MIPS64ASSEMBLER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "utils/KeyedVector.h"
+#include "utils/Vector.h"
+#include "tinyutils/smartpointer.h"
+
+#include "ARMAssemblerInterface.h"
+#include "MIPSAssembler.h"
+#include "CodeCache.h"
+
+namespace android {
+
+class MIPS64Assembler;    // forward reference
+
+// this class mimics ARMAssembler interface
+// intent is to translate each ARM instruction to 1 or more MIPS instr
+// implementation calls MIPS64Assembler class to generate mips code
+class ArmToMips64Assembler : public ARMAssemblerInterface
+{
+public:
+                ArmToMips64Assembler(const sp<Assembly>& assembly,
+                        char *abuf = 0, int linesz = 0, int instr_count = 0);
+                ArmToMips64Assembler(void* assembly);
+    virtual     ~ArmToMips64Assembler();
+
+    uint32_t*   base() const;
+    uint32_t*   pc() const;
+    void        disassemble(const char* name);
+
+    virtual void    reset();
+
+    virtual int     generate(const char* name);
+    virtual int     getCodegenArch();
+
+    virtual void    prolog();
+    virtual void    epilog(uint32_t touched);
+    virtual void    comment(const char* string);
+    // for testing purposes
+    void        fix_branches();
+    void        set_condition(int mode, int R1, int R2);
+
+
+    // -----------------------------------------------------------------------
+    // shifters and addressing modes
+    // -----------------------------------------------------------------------
+
+    // shifters...
+    virtual bool        isValidImmediate(uint32_t immed);
+    virtual int         buildImmediate(uint32_t i, uint32_t& rot, uint32_t& imm);
+
+    virtual uint32_t    imm(uint32_t immediate);
+    virtual uint32_t    reg_imm(int Rm, int type, uint32_t shift);
+    virtual uint32_t    reg_rrx(int Rm);
+    virtual uint32_t    reg_reg(int Rm, int type, int Rs);
+
+    // addressing modes...
+    // LDR(B)/STR(B)/PLD
+    // (immediate and Rm can be negative, which indicates U=0)
+    virtual uint32_t    immed12_pre(int32_t immed12, int W=0);
+    virtual uint32_t    immed12_post(int32_t immed12);
+    virtual uint32_t    reg_scale_pre(int Rm, int type=0, uint32_t shift=0, int W=0);
+    virtual uint32_t    reg_scale_post(int Rm, int type=0, uint32_t shift=0);
+
+    // LDRH/LDRSB/LDRSH/STRH
+    // (immediate and Rm can be negative, which indicates U=0)
+    virtual uint32_t    immed8_pre(int32_t immed8, int W=0);
+    virtual uint32_t    immed8_post(int32_t immed8);
+    virtual uint32_t    reg_pre(int Rm, int W=0);
+    virtual uint32_t    reg_post(int Rm);
+
+
+
+
+    virtual void    dataProcessing(int opcode, int cc, int s,
+                                int Rd, int Rn,
+                                uint32_t Op2);
+    virtual void MLA(int cc, int s,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void MUL(int cc, int s,
+                int Rd, int Rm, int Rs);
+    virtual void UMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void UMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void SMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void SMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+
+    virtual void B(int cc, uint32_t* pc);
+    virtual void BL(int cc, uint32_t* pc);
+    virtual void BX(int cc, int Rn);
+    virtual void label(const char* theLabel);
+    virtual void B(int cc, const char* label);
+    virtual void BL(int cc, const char* label);
+
+    virtual uint32_t* pcForLabel(const char* label);
+
+    virtual void LDR (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRB(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void STR (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void STRB(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRH (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRSB(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRSH(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void STRH (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+
+    virtual void LDM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list);
+    virtual void STM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list);
+
+    virtual void SWP(int cc, int Rn, int Rd, int Rm);
+    virtual void SWPB(int cc, int Rn, int Rd, int Rm);
+    virtual void SWI(int cc, uint32_t comment);
+
+    virtual void PLD(int Rn, uint32_t offset);
+    virtual void CLZ(int cc, int Rd, int Rm);
+    virtual void QADD(int cc, int Rd, int Rm, int Rn);
+    virtual void QDADD(int cc, int Rd, int Rm, int Rn);
+    virtual void QSUB(int cc, int Rd, int Rm, int Rn);
+    virtual void QDSUB(int cc, int Rd, int Rm, int Rn);
+    virtual void SMUL(int cc, int xy,
+                int Rd, int Rm, int Rs);
+    virtual void SMULW(int cc, int y,
+                int Rd, int Rm, int Rs);
+    virtual void SMLA(int cc, int xy,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void SMLAL(int cc, int xy,
+                int RdHi, int RdLo, int Rs, int Rm);
+    virtual void SMLAW(int cc, int y,
+                int Rd, int Rm, int Rs, int Rn);
+
+    // byte/half word extract...
+    virtual void UXTB16(int cc, int Rd, int Rm, int rotate);
+
+    // bit manipulation...
+    virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width);
+
+    // Address loading/storing/manipulation
+    virtual void ADDR_LDR(int cc, int Rd, int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void ADDR_STR(int cc, int Rd, int Rn, uint32_t offset = __immed12_pre(0));
+    virtual void ADDR_ADD(int cc, int s, int Rd, int Rn, uint32_t Op2);
+    virtual void ADDR_SUB(int cc, int s, int Rd, int Rn, uint32_t Op2);
+
+    // this is some crap to share is MIPS64Assembler class for debug
+    char *      mArmDisassemblyBuffer;
+    int         mArmLineLength;
+    int         mArmInstrCount;
+
+    int         mInum;      // current arm instuction number (0..n)
+    uint32_t**  mArmPC;     // array: PC for 1st mips instr of
+                            //      each translated ARM instr
+
+
+private:
+    ArmToMips64Assembler(const ArmToMips64Assembler& rhs);
+    ArmToMips64Assembler& operator = (const ArmToMips64Assembler& rhs);
+
+    void init_conditional_labels(void);
+
+    void protectConditionalOperands(int Rd);
+
+    // reg__tmp set to MIPS AT, reg 1
+    int dataProcAdrModes(int op, int& source, bool sign = false, int reg_tmp = 1);
+
+    sp<Assembly>        mAssembly;
+    MIPS64Assembler*    mMips;
+
+
+    enum misc_constants_t {
+        ARM_MAX_INSTUCTIONS = 512  // based on ASSEMBLY_SCRATCH_SIZE
+    };
+
+    enum {
+        SRC_REG = 0,
+        SRC_IMM,
+        SRC_ERROR = -1
+    };
+
+    enum addr_modes {
+        // start above the range of legal mips reg #'s (0-31)
+        AMODE_REG = 0x20,
+        AMODE_IMM, AMODE_REG_IMM,               // for data processing
+        AMODE_IMM_12_PRE, AMODE_IMM_12_POST,    // for load/store
+        AMODE_REG_SCALE_PRE, AMODE_IMM_8_PRE,
+        AMODE_IMM_8_POST, AMODE_REG_PRE,
+        AMODE_UNSUPPORTED
+    };
+
+    struct addr_mode_t {    // address modes for current ARM instruction
+        int         reg;
+        int         stype;
+        uint32_t    value;
+        bool        writeback;  // writeback the adr reg after modification
+    } amode;
+
+    enum cond_types {
+        CMP_COND = 1,
+        SBIT_COND
+    };
+
+    struct cond_mode_t {    // conditional-execution info for current ARM instruction
+        cond_types  type;
+        int         r1;
+        int         r2;
+        int         labelnum;
+        char        label[100][10];
+    } cond;
+};
+
+
+
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+
+// This is the basic MIPS64 assembler, which just creates the opcodes in memory.
+// All the more complicated work is done in ArmToMips64Assember above.
+// Inherits MIPSAssembler class, and overrides only MIPS64r6 specific stuff
+
+class MIPS64Assembler : public MIPSAssembler
+{
+public:
+                MIPS64Assembler(const sp<Assembly>& assembly, ArmToMips64Assembler *parent);
+                MIPS64Assembler(void* assembly, ArmToMips64Assembler *parent);
+    virtual     ~MIPS64Assembler();
+
+    virtual void        reset();
+    virtual void        disassemble(const char* name);
+
+    void        fix_branches();
+
+    // ------------------------------------------------------------------------
+    // MIPS64AssemblerInterface...
+    // ------------------------------------------------------------------------
+
+#if 0
+#pragma mark -
+#pragma mark Arithmetic...
+#endif
+
+    void DADDU(int Rd, int Rs, int Rt);
+    void DADDIU(int Rt, int Rs, int16_t imm);
+    void DSUBU(int Rd, int Rs, int Rt);
+    void DSUBIU(int Rt, int Rs, int16_t imm);
+    virtual void MUL(int Rd, int Rs, int Rt);
+    void MUH(int Rd, int Rs, int Rt);
+
+#if 0
+#pragma mark -
+#pragma mark Logical...
+#endif
+
+    virtual void CLO(int Rd, int Rs);
+    virtual void CLZ(int Rd, int Rs);
+
+#if 0
+#pragma mark -
+#pragma mark Load/store...
+#endif
+
+    void LD(int Rt, int Rbase, int16_t offset);
+    void SD(int Rt, int Rbase, int16_t offset);
+    virtual void LUI(int Rt, int16_t offset);
+
+#if 0
+#pragma mark -
+#pragma mark Branch...
+#endif
+
+    void JR(int Rs);
+
+
+protected:
+    ArmToMips64Assembler *mParent;
+
+    // opcode field of all instructions
+    enum opcode_field {
+        spec_op, regimm_op, j_op, jal_op,                  // 0x00 - 0x03
+        beq_op, bne_op, pop06_op, pop07_op,                // 0x04 - 0x07
+        pop10_op, addiu_op, slti_op, sltiu_op,             // 0x08 - 0x0b
+        andi_op, ori_op, xori_op, aui_op,                  // 0x0c - 0x0f
+        cop0_op, cop1_op, cop2_op, rsrv_opc_0,             // 0x10 - 0x13
+        rsrv_opc_1, rsrv_opc_2, pop26_op, pop27_op,        // 0x14 - 0x17
+        pop30_op, daddiu_op, rsrv_opc_3, rsrv_opc_4,       // 0x18 - 0x1b
+        rsrv_opc_5, daui_op, msa_op, spec3_op,             // 0x1c - 0x1f
+        lb_op, lh_op, rsrv_opc_6, lw_op,                   // 0x20 - 0x23
+        lbu_op, lhu_op, rsrv_opc_7, lwu_op,                // 0x24 - 0x27
+        sb_op, sh_op, rsrv_opc_8, sw_op,                   // 0x28 - 0x2b
+        rsrv_opc_9, rsrv_opc_10, rsrv_opc_11, rsrv_opc_12, // 0x2c - 0x2f
+        rsrv_opc_13, lwc1_op, bc_op, rsrv_opc_14,          // 0x2c - 0x2f
+        rsrv_opc_15, ldc1_op, pop66_op, ld_op,             // 0x30 - 0x33
+        rsrv_opc_16, swc1_op, balc_op, pcrel_op,           // 0x34 - 0x37
+        rsrv_opc_17, sdc1_op, pop76_op, sd_op              // 0x38 - 0x3b
+    };
+
+
+    // func field for special opcode
+    enum func_spec_op {
+        sll_fn, rsrv_spec_0, srl_fn, sra_fn,
+        sllv_fn, lsa_fn, srlv_fn, srav_fn,
+        rsrv_spec_1, jalr_fn, rsrv_spec_2, rsrv_spec_3,
+        syscall_fn, break_fn, sdbbp_fn, sync_fn,
+        clz_fn, clo_fn, dclz_fn, dclo_fn,
+        dsllv_fn, dlsa_fn, dsrlv_fn, dsrav_fn,
+        sop30_fn, sop31_fn, sop32_fn, sop33_fn,
+        sop34_fn, sop35_fn, sop36_fn, sop37_fn,
+        add_fn, addu_fn, sub_fn, subu_fn,
+        and_fn, or_fn, xor_fn, nor_fn,
+        rsrv_spec_4, rsrv_spec_5, slt_fn, sltu_fn,
+        dadd_fn, daddu_fn, dsub_fn, dsubu_fn,
+        tge_fn, tgeu_fn, tlt_fn, tltu_fn,
+        teq_fn, seleqz_fn, tne_fn, selnez_fn,
+        dsll_fn, rsrv_spec_6, dsrl_fn, dsra_fn,
+        dsll32_fn, rsrv_spec_7, dsrl32_fn, dsra32_fn
+    };
+
+    // func field for spec3 opcode
+    enum func_spec3_op {
+        ext_fn, dextm_fn, dextu_fn, dext_fn,
+        ins_fn, dinsm_fn, dinsu_fn, dins_fn,
+        cachee_fn = 0x1b, sbe_fn, she_fn, sce_fn, swe_fn,
+        bshfl_fn, prefe_fn = 0x23, dbshfl_fn, cache_fn, sc_fn, scd_fn,
+        lbue_fn, lhue_fn, lbe_fn = 0x2c, lhe_fn, lle_fn, lwe_fn,
+        pref_fn = 0x35, ll_fn, lld_fn, rdhwr_fn = 0x3b
+    };
+
+    // sa field for spec3 opcodes, with BSHFL function
+    enum func_spec3_bshfl {
+        bitswap_fn,
+        wsbh_fn = 0x02,
+        dshd_fn = 0x05,
+        seb_fn = 0x10,
+        seh_fn = 0x18
+    };
+
+    // rt field of regimm opcodes.
+    enum regimm_fn {
+        bltz_fn, bgez_fn,
+        dahi_fn = 0x6,
+        nal_fn = 0x10, bal_fn, bltzall_fn, bgezall_fn,
+        sigrie_fn = 0x17,
+        dati_fn = 0x1e, synci_fn
+    };
+
+    enum muldiv_fn {
+        mul_fn = 0x02, muh_fn
+    };
+
+    enum mips_inst_shifts {
+        OP_SHF       = 26,
+        JTARGET_SHF  = 0,
+        RS_SHF       = 21,
+        RT_SHF       = 16,
+        RD_SHF       = 11,
+        RE_SHF       = 6,
+        SA_SHF       = RE_SHF,  // synonym
+        IMM_SHF      = 0,
+        FUNC_SHF     = 0,
+
+        // mask values
+        MSK_16       = 0xffff,
+
+
+        CACHEOP_SHF  = 18,
+        CACHESEL_SHF = 16,
+    };
+};
+
+
+}; // namespace android
+
+#endif //ANDROID_MIPS64ASSEMBLER_H
diff --git a/libpixelflinger/codeflinger/MIPSAssembler.cpp b/libpixelflinger/codeflinger/MIPSAssembler.cpp
new file mode 100644
index 0000000..7de8cc1
--- /dev/null
+++ b/libpixelflinger/codeflinger/MIPSAssembler.cpp
@@ -0,0 +1,1955 @@
+/* libs/pixelflinger/codeflinger/MIPSAssembler.cpp
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+
+/* MIPS assembler and ARM->MIPS assembly translator
+**
+** The approach is to leave the GGLAssembler and associated files largely
+** un-changed, still utilizing all Arm instruction generation. Via the
+** ArmToMipsAssembler (subclassed from ArmAssemblerInterface) each Arm
+** instruction is translated to one or more Mips instructions as necessary. This
+** is clearly less efficient than a direct implementation within the
+** GGLAssembler, but is far cleaner, more maintainable, and has yielded very
+** significant performance gains on Mips compared to the generic pixel pipeline.
+**
+**
+** GGLAssembler changes
+**
+** - The register allocator has been modified to re-map Arm registers 0-15 to mips
+** registers 2-17. Mips register 0 cannot be used as general-purpose register,
+** and register 1 has traditional uses as a short-term temporary.
+**
+** - Added some early bailouts for OUT_OF_REGISTERS in texturing.cpp and
+** GGLAssembler.cpp, since this is not fatal, and can be retried at lower
+** optimization level.
+**
+**
+** ARMAssembler and ARMAssemblerInterface changes
+**
+** Refactored ARM address-mode static functions (imm(), reg_imm(), imm12_pre(), etc.)
+** to virtual, so they can be overridden in MIPSAssembler. The implementation of these
+** functions on ARM is moved from ARMAssemblerInterface.cpp to ARMAssembler.cpp, and
+** is unchanged from the original. (This required duplicating 2 of these as static
+** functions in ARMAssemblerInterface.cpp so they could be used as static initializers).
+*/
+
+#define LOG_TAG "MIPSAssembler"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <cutils/properties.h>
+#include <log/log.h>
+#include <private/pixelflinger/ggl_context.h>
+
+#include "CodeCache.h"
+#include "MIPSAssembler.h"
+#include "mips_disassem.h"
+
+#define __unused __attribute__((__unused__))
+
+// Choose MIPS arch variant following gcc flags
+#if defined(__mips__) && __mips==32 && __mips_isa_rev>=2
+#define mips32r2 1
+#else
+#define mips32r2 0
+#endif
+
+
+#define NOT_IMPLEMENTED()  LOG_ALWAYS_FATAL("Arm instruction %s not yet implemented\n", __func__)
+
+
+
+// ----------------------------------------------------------------------------
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark ArmToMipsAssembler...
+#endif
+
+ArmToMipsAssembler::ArmToMipsAssembler(const sp<Assembly>& assembly,
+                                       char *abuf, int linesz, int instr_count)
+    :   ARMAssemblerInterface(),
+        mArmDisassemblyBuffer(abuf),
+        mArmLineLength(linesz),
+        mArmInstrCount(instr_count),
+        mInum(0),
+        mAssembly(assembly)
+{
+    mMips = new MIPSAssembler(assembly, this);
+    mArmPC = (uint32_t **) malloc(ARM_MAX_INSTUCTIONS * sizeof(uint32_t *));
+    init_conditional_labels();
+}
+
+ArmToMipsAssembler::~ArmToMipsAssembler()
+{
+    delete mMips;
+    free((void *) mArmPC);
+}
+
+uint32_t* ArmToMipsAssembler::pc() const
+{
+    return mMips->pc();
+}
+
+uint32_t* ArmToMipsAssembler::base() const
+{
+    return mMips->base();
+}
+
+void ArmToMipsAssembler::reset()
+{
+    cond.labelnum = 0;
+    mInum = 0;
+    mMips->reset();
+}
+
+int ArmToMipsAssembler::getCodegenArch()
+{
+    return CODEGEN_ARCH_MIPS;
+}
+
+void ArmToMipsAssembler::comment(const char* string)
+{
+    mMips->comment(string);
+}
+
+void ArmToMipsAssembler::label(const char* theLabel)
+{
+    mMips->label(theLabel);
+}
+
+void ArmToMipsAssembler::disassemble(const char* name)
+{
+    mMips->disassemble(name);
+}
+
+void ArmToMipsAssembler::init_conditional_labels()
+{
+    int i;
+    for (i=0;i<99; ++i) {
+        sprintf(cond.label[i], "cond_%d", i);
+    }
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Prolog/Epilog & Generate...
+#endif
+
+void ArmToMipsAssembler::prolog()
+{
+    mArmPC[mInum++] = pc();  // save starting PC for this instr
+
+    mMips->ADDIU(R_sp, R_sp, -(5 * 4));
+    mMips->SW(R_s0, R_sp, 0);
+    mMips->SW(R_s1, R_sp, 4);
+    mMips->SW(R_s2, R_sp, 8);
+    mMips->SW(R_s3, R_sp, 12);
+    mMips->SW(R_s4, R_sp, 16);
+    mMips->MOVE(R_v0, R_a0);    // move context * passed in a0 to v0 (arm r0)
+}
+
+void ArmToMipsAssembler::epilog(uint32_t touched __unused)
+{
+    mArmPC[mInum++] = pc();  // save starting PC for this instr
+
+    mMips->LW(R_s0, R_sp, 0);
+    mMips->LW(R_s1, R_sp, 4);
+    mMips->LW(R_s2, R_sp, 8);
+    mMips->LW(R_s3, R_sp, 12);
+    mMips->LW(R_s4, R_sp, 16);
+    mMips->ADDIU(R_sp, R_sp, (5 * 4));
+    mMips->JR(R_ra);
+
+}
+
+int ArmToMipsAssembler::generate(const char* name)
+{
+    return mMips->generate(name);
+}
+
+uint32_t* ArmToMipsAssembler::pcForLabel(const char* label)
+{
+    return mMips->pcForLabel(label);
+}
+
+
+
+//----------------------------------------------------------
+
+#if 0
+#pragma mark -
+#pragma mark Addressing modes & shifters...
+#endif
+
+
+// do not need this for MIPS, but it is in the Interface (virtual)
+int ArmToMipsAssembler::buildImmediate(
+        uint32_t immediate, uint32_t& rot, uint32_t& imm)
+{
+    // for MIPS, any 32-bit immediate is OK
+    rot = 0;
+    imm = immediate;
+    return 0;
+}
+
+// shifters...
+
+bool ArmToMipsAssembler::isValidImmediate(uint32_t immediate __unused)
+{
+    // for MIPS, any 32-bit immediate is OK
+    return true;
+}
+
+uint32_t ArmToMipsAssembler::imm(uint32_t immediate)
+{
+    // ALOGW("immediate value %08x at pc %08x\n", immediate, (int)pc());
+    amode.value = immediate;
+    return AMODE_IMM;
+}
+
+uint32_t ArmToMipsAssembler::reg_imm(int Rm, int type, uint32_t shift)
+{
+    amode.reg = Rm;
+    amode.stype = type;
+    amode.value = shift;
+    return AMODE_REG_IMM;
+}
+
+uint32_t ArmToMipsAssembler::reg_rrx(int Rm __unused)
+{
+    // reg_rrx mode is not used in the GLLAssember code at this time
+    return AMODE_UNSUPPORTED;
+}
+
+uint32_t ArmToMipsAssembler::reg_reg(int Rm __unused, int type __unused,
+                                     int Rs __unused)
+{
+    // reg_reg mode is not used in the GLLAssember code at this time
+    return AMODE_UNSUPPORTED;
+}
+
+
+// addressing modes...
+// LDR(B)/STR(B)/PLD (immediate and Rm can be negative, which indicate U=0)
+uint32_t ArmToMipsAssembler::immed12_pre(int32_t immed12, int W)
+{
+    LOG_ALWAYS_FATAL_IF(abs(immed12) >= 0x800,
+                        "LDR(B)/STR(B)/PLD immediate too big (%08x)",
+                        immed12);
+    amode.value = immed12;
+    amode.writeback = W;
+    return AMODE_IMM_12_PRE;
+}
+
+uint32_t ArmToMipsAssembler::immed12_post(int32_t immed12)
+{
+    LOG_ALWAYS_FATAL_IF(abs(immed12) >= 0x800,
+                        "LDR(B)/STR(B)/PLD immediate too big (%08x)",
+                        immed12);
+
+    amode.value = immed12;
+    return AMODE_IMM_12_POST;
+}
+
+uint32_t ArmToMipsAssembler::reg_scale_pre(int Rm, int type,
+        uint32_t shift, int W)
+{
+    LOG_ALWAYS_FATAL_IF(W | type | shift, "reg_scale_pre adv modes not yet implemented");
+
+    amode.reg = Rm;
+    // amode.stype = type;      // more advanced modes not used in GGLAssembler yet
+    // amode.value = shift;
+    // amode.writeback = W;
+    return AMODE_REG_SCALE_PRE;
+}
+
+uint32_t ArmToMipsAssembler::reg_scale_post(int Rm __unused, int type __unused,
+                                            uint32_t shift __unused)
+{
+    LOG_ALWAYS_FATAL("adr mode reg_scale_post not yet implemented\n");
+    return AMODE_UNSUPPORTED;
+}
+
+// LDRH/LDRSB/LDRSH/STRH (immediate and Rm can be negative, which indicate U=0)
+uint32_t ArmToMipsAssembler::immed8_pre(int32_t immed8, int W __unused)
+{
+    // uint32_t offset = abs(immed8);
+
+    LOG_ALWAYS_FATAL("adr mode immed8_pre not yet implemented\n");
+
+    LOG_ALWAYS_FATAL_IF(abs(immed8) >= 0x100,
+                        "LDRH/LDRSB/LDRSH/STRH immediate too big (%08x)",
+                        immed8);
+    return AMODE_IMM_8_PRE;
+}
+
+uint32_t ArmToMipsAssembler::immed8_post(int32_t immed8)
+{
+    // uint32_t offset = abs(immed8);
+
+    LOG_ALWAYS_FATAL_IF(abs(immed8) >= 0x100,
+                        "LDRH/LDRSB/LDRSH/STRH immediate too big (%08x)",
+                        immed8);
+    amode.value = immed8;
+    return AMODE_IMM_8_POST;
+}
+
+uint32_t ArmToMipsAssembler::reg_pre(int Rm, int W)
+{
+    LOG_ALWAYS_FATAL_IF(W, "reg_pre writeback not yet implemented");
+    amode.reg = Rm;
+    return AMODE_REG_PRE;
+}
+
+uint32_t ArmToMipsAssembler::reg_post(int Rm __unused)
+{
+    LOG_ALWAYS_FATAL("adr mode reg_post not yet implemented\n");
+    return AMODE_UNSUPPORTED;
+}
+
+
+
+// ----------------------------------------------------------------------------
+
+#if 0
+#pragma mark -
+#pragma mark Data Processing...
+#endif
+
+// check if the operand registers from a previous CMP or S-bit instruction
+// would be overwritten by this instruction. If so, move the value to a
+// safe register.
+// Note that we cannot tell at _this_ instruction time if a future (conditional)
+// instruction will _also_ use this value (a defect of the simple 1-pass, one-
+// instruction-at-a-time translation). Therefore we must be conservative and
+// save the value before it is overwritten. This costs an extra MOVE instr.
+
+void ArmToMipsAssembler::protectConditionalOperands(int Rd)
+{
+    if (Rd == cond.r1) {
+        mMips->MOVE(R_cmp, cond.r1);
+        cond.r1 = R_cmp;
+    }
+    if (cond.type == CMP_COND && Rd == cond.r2) {
+        mMips->MOVE(R_cmp2, cond.r2);
+        cond.r2 = R_cmp2;
+    }
+}
+
+
+// interprets the addressing mode, and generates the common code
+// used by the majority of data-processing ops. Many MIPS instructions
+// have a register-based form and a different immediate form. See
+// opAND below for an example. (this could be inlined)
+//
+// this works with the imm(), reg_imm() methods above, which are directly
+// called by the GLLAssembler.
+// note: _signed parameter defaults to false (un-signed)
+// note: tmpReg parameter defaults to 1, MIPS register AT
+int ArmToMipsAssembler::dataProcAdrModes(int op, int& source, bool _signed, int tmpReg)
+{
+    if (op < AMODE_REG) {
+        source = op;
+        return SRC_REG;
+    } else if (op == AMODE_IMM) {
+        if ((!_signed && amode.value > 0xffff)
+                || (_signed && ((int)amode.value < -32768 || (int)amode.value > 32767) )) {
+            mMips->LUI(tmpReg, (amode.value >> 16));
+            if (amode.value & 0x0000ffff) {
+                mMips->ORI(tmpReg, tmpReg, (amode.value & 0x0000ffff));
+            }
+            source = tmpReg;
+            return SRC_REG;
+        } else {
+            source = amode.value;
+            return SRC_IMM;
+        }
+    } else if (op == AMODE_REG_IMM) {
+        switch (amode.stype) {
+            case LSL: mMips->SLL(tmpReg, amode.reg, amode.value); break;
+            case LSR: mMips->SRL(tmpReg, amode.reg, amode.value); break;
+            case ASR: mMips->SRA(tmpReg, amode.reg, amode.value); break;
+            case ROR: if (mips32r2) {
+                          mMips->ROTR(tmpReg, amode.reg, amode.value);
+                      } else {
+                          mMips->RORIsyn(tmpReg, amode.reg, amode.value);
+                      }
+                      break;
+        }
+        source = tmpReg;
+        return SRC_REG;
+    } else {  // adr mode RRX is not used in GGL Assembler at this time
+        // we are screwed, this should be exception, assert-fail or something
+        LOG_ALWAYS_FATAL("adr mode reg_rrx not yet implemented\n");
+        return SRC_ERROR;
+    }
+}
+
+
+void ArmToMipsAssembler::dataProcessing(int opcode, int cc,
+        int s, int Rd, int Rn, uint32_t Op2)
+{
+    int src;    // src is modified by dataProcAdrModes() - passed as int&
+
+
+    if (cc != AL) {
+        protectConditionalOperands(Rd);
+        // the branch tests register(s) set by prev CMP or instr with 'S' bit set
+        // inverse the condition to jump past this conditional instruction
+        ArmToMipsAssembler::B(cc^1, cond.label[++cond.labelnum]);
+    } else {
+        mArmPC[mInum++] = pc();  // save starting PC for this instr
+    }
+
+    switch (opcode) {
+    case opAND:
+        if (dataProcAdrModes(Op2, src) == SRC_REG) {
+            mMips->AND(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->ANDI(Rd, Rn, src);
+        }
+        break;
+
+    case opADD:
+        // set "signed" to true for adr modes
+        if (dataProcAdrModes(Op2, src, true) == SRC_REG) {
+            mMips->ADDU(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->ADDIU(Rd, Rn, src);
+        }
+        break;
+
+    case opSUB:
+        // set "signed" to true for adr modes
+        if (dataProcAdrModes(Op2, src, true) == SRC_REG) {
+            mMips->SUBU(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->SUBIU(Rd, Rn, src);
+        }
+        break;
+
+    case opEOR:
+        if (dataProcAdrModes(Op2, src) == SRC_REG) {
+            mMips->XOR(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->XORI(Rd, Rn, src);
+        }
+        break;
+
+    case opORR:
+        if (dataProcAdrModes(Op2, src) == SRC_REG) {
+            mMips->OR(Rd, Rn, src);
+        } else {                        // adr mode was SRC_IMM
+            mMips->ORI(Rd, Rn, src);
+        }
+        break;
+
+    case opBIC:
+        if (dataProcAdrModes(Op2, src) == SRC_IMM) {
+            // if we are 16-bit imnmediate, load to AT reg
+            mMips->ORI(R_at, 0, src);
+            src = R_at;
+        }
+        mMips->NOT(R_at, src);
+        mMips->AND(Rd, Rn, R_at);
+        break;
+
+    case opRSB:
+        if (dataProcAdrModes(Op2, src) == SRC_IMM) {
+            // if we are 16-bit imnmediate, load to AT reg
+            mMips->ORI(R_at, 0, src);
+            src = R_at;
+        }
+        mMips->SUBU(Rd, src, Rn);   // subu with the parameters reversed
+        break;
+
+    case opMOV:
+        if (Op2 < AMODE_REG) {  // op2 is reg # in this case
+            mMips->MOVE(Rd, Op2);
+        } else if (Op2 == AMODE_IMM) {
+            if (amode.value > 0xffff) {
+                mMips->LUI(Rd, (amode.value >> 16));
+                if (amode.value & 0x0000ffff) {
+                    mMips->ORI(Rd, Rd, (amode.value & 0x0000ffff));
+                }
+             } else {
+                mMips->ORI(Rd, 0, amode.value);
+            }
+        } else if (Op2 == AMODE_REG_IMM) {
+            switch (amode.stype) {
+            case LSL: mMips->SLL(Rd, amode.reg, amode.value); break;
+            case LSR: mMips->SRL(Rd, amode.reg, amode.value); break;
+            case ASR: mMips->SRA(Rd, amode.reg, amode.value); break;
+            case ROR: if (mips32r2) {
+                          mMips->ROTR(Rd, amode.reg, amode.value);
+                      } else {
+                          mMips->RORIsyn(Rd, amode.reg, amode.value);
+                      }
+                      break;
+            }
+        }
+        else {
+            // adr mode RRX is not used in GGL Assembler at this time
+            mMips->UNIMPL();
+        }
+        break;
+
+    case opMVN:     // this is a 1's complement: NOT
+        if (Op2 < AMODE_REG) {  // op2 is reg # in this case
+            mMips->NOR(Rd, Op2, 0);     // NOT is NOR with 0
+            break;
+        } else if (Op2 == AMODE_IMM) {
+            if (amode.value > 0xffff) {
+                mMips->LUI(Rd, (amode.value >> 16));
+                if (amode.value & 0x0000ffff) {
+                    mMips->ORI(Rd, Rd, (amode.value & 0x0000ffff));
+                }
+             } else {
+                mMips->ORI(Rd, 0, amode.value);
+             }
+        } else if (Op2 == AMODE_REG_IMM) {
+            switch (amode.stype) {
+            case LSL: mMips->SLL(Rd, amode.reg, amode.value); break;
+            case LSR: mMips->SRL(Rd, amode.reg, amode.value); break;
+            case ASR: mMips->SRA(Rd, amode.reg, amode.value); break;
+            case ROR: if (mips32r2) {
+                          mMips->ROTR(Rd, amode.reg, amode.value);
+                      } else {
+                          mMips->RORIsyn(Rd, amode.reg, amode.value);
+                      }
+                      break;
+            }
+        }
+        else {
+            // adr mode RRX is not used in GGL Assembler at this time
+            mMips->UNIMPL();
+        }
+        mMips->NOR(Rd, Rd, 0);     // NOT is NOR with 0
+        break;
+
+    case opCMP:
+        // Either operand of a CMP instr could get overwritten by a subsequent
+        // conditional instruction, which is ok, _UNLESS_ there is a _second_
+        // conditional instruction. Under MIPS, this requires doing the comparison
+        // again (SLT), and the original operands must be available. (and this
+        // pattern of multiple conditional instructions from same CMP _is_ used
+        // in GGL-Assembler)
+        //
+        // For now, if a conditional instr overwrites the operands, we will
+        // move them to dedicated temp regs. This is ugly, and inefficient,
+        // and should be optimized.
+        //
+        // WARNING: making an _Assumption_ that CMP operand regs will NOT be
+        // trashed by intervening NON-conditional instructions. In the general
+        // case this is legal, but it is NOT currently done in GGL-Assembler.
+
+        cond.type = CMP_COND;
+        cond.r1 = Rn;
+        if (dataProcAdrModes(Op2, src, false, R_cmp2) == SRC_REG) {
+            cond.r2 = src;
+        } else {                        // adr mode was SRC_IMM
+            mMips->ORI(R_cmp2, R_zero, src);
+            cond.r2 = R_cmp2;
+        }
+
+        break;
+
+
+    case opTST:
+    case opTEQ:
+    case opCMN:
+    case opADC:
+    case opSBC:
+    case opRSC:
+        mMips->UNIMPL(); // currently unused in GGL Assembler code
+        break;
+    }
+
+    if (cc != AL) {
+        mMips->label(cond.label[cond.labelnum]);
+    }
+    if (s && opcode != opCMP) {
+        cond.type = SBIT_COND;
+        cond.r1 = Rd;
+    }
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Multiply...
+#endif
+
+// multiply, accumulate
+void ArmToMipsAssembler::MLA(int cc __unused, int s,
+        int Rd, int Rm, int Rs, int Rn) {
+
+    mArmPC[mInum++] = pc();  // save starting PC for this instr
+
+    mMips->MUL(R_at, Rm, Rs);
+    mMips->ADDU(Rd, R_at, Rn);
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = Rd;
+    }
+}
+
+void ArmToMipsAssembler::MUL(int cc __unused, int s,
+        int Rd, int Rm, int Rs) {
+    mArmPC[mInum++] = pc();
+    mMips->MUL(Rd, Rm, Rs);
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = Rd;
+    }
+}
+
+void ArmToMipsAssembler::UMULL(int cc __unused, int s,
+        int RdLo, int RdHi, int Rm, int Rs) {
+    mArmPC[mInum++] = pc();
+    mMips->MULT(Rm, Rs);
+    mMips->MFHI(RdHi);
+    mMips->MFLO(RdLo);
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = RdHi;     // BUG...
+        LOG_ALWAYS_FATAL("Condition on UMULL must be on 64-bit result\n");
+    }
+}
+
+void ArmToMipsAssembler::UMUAL(int cc __unused, int s,
+        int RdLo __unused, int RdHi, int Rm __unused, int Rs __unused) {
+    LOG_FATAL_IF(RdLo==Rm || RdHi==Rm || RdLo==RdHi,
+                        "UMUAL(r%u,r%u,r%u,r%u)", RdLo,RdHi,Rm,Rs);
+    // *mPC++ =    (cc<<28) | (1<<23) | (1<<21) | (s<<20) |
+    //             (RdHi<<16) | (RdLo<<12) | (Rs<<8) | 0x90 | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = RdHi;     // BUG...
+        LOG_ALWAYS_FATAL("Condition on UMULL must be on 64-bit result\n");
+    }
+}
+
+void ArmToMipsAssembler::SMULL(int cc __unused, int s,
+        int RdLo __unused, int RdHi, int Rm __unused, int Rs __unused) {
+    LOG_FATAL_IF(RdLo==Rm || RdHi==Rm || RdLo==RdHi,
+                        "SMULL(r%u,r%u,r%u,r%u)", RdLo,RdHi,Rm,Rs);
+    // *mPC++ =    (cc<<28) | (1<<23) | (1<<22) | (s<<20) |
+    //             (RdHi<<16) | (RdLo<<12) | (Rs<<8) | 0x90 | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = RdHi;     // BUG...
+        LOG_ALWAYS_FATAL("Condition on SMULL must be on 64-bit result\n");
+    }
+}
+void ArmToMipsAssembler::SMUAL(int cc __unused, int s,
+        int RdLo __unused, int RdHi, int Rm __unused, int Rs __unused) {
+    LOG_FATAL_IF(RdLo==Rm || RdHi==Rm || RdLo==RdHi,
+                        "SMUAL(r%u,r%u,r%u,r%u)", RdLo,RdHi,Rm,Rs);
+    // *mPC++ =    (cc<<28) | (1<<23) | (1<<22) | (1<<21) | (s<<20) |
+    //             (RdHi<<16) | (RdLo<<12) | (Rs<<8) | 0x90 | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+    if (s) {
+        cond.type = SBIT_COND;
+        cond.r1 = RdHi;     // BUG...
+        LOG_ALWAYS_FATAL("Condition on SMUAL must be on 64-bit result\n");
+    }
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Branches...
+#endif
+
+// branches...
+
+void ArmToMipsAssembler::B(int cc, const char* label)
+{
+    mArmPC[mInum++] = pc();
+    if (cond.type == SBIT_COND) { cond.r2 = R_zero; }
+
+    switch(cc) {
+        case EQ: mMips->BEQ(cond.r1, cond.r2, label); break;
+        case NE: mMips->BNE(cond.r1, cond.r2, label); break;
+        case HS: mMips->BGEU(cond.r1, cond.r2, label); break;
+        case LO: mMips->BLTU(cond.r1, cond.r2, label); break;
+        case MI: mMips->BLT(cond.r1, cond.r2, label); break;
+        case PL: mMips->BGE(cond.r1, cond.r2, label); break;
+
+        case HI: mMips->BGTU(cond.r1, cond.r2, label); break;
+        case LS: mMips->BLEU(cond.r1, cond.r2, label); break;
+        case GE: mMips->BGE(cond.r1, cond.r2, label); break;
+        case LT: mMips->BLT(cond.r1, cond.r2, label); break;
+        case GT: mMips->BGT(cond.r1, cond.r2, label); break;
+        case LE: mMips->BLE(cond.r1, cond.r2, label); break;
+        case AL: mMips->B(label); break;
+        case NV: /* B Never - no instruction */ break;
+
+        case VS:
+        case VC:
+        default:
+            LOG_ALWAYS_FATAL("Unsupported cc: %02x\n", cc);
+            break;
+    }
+}
+
+void ArmToMipsAssembler::BL(int cc __unused, const char* label __unused)
+{
+    LOG_ALWAYS_FATAL("branch-and-link not supported yet\n");
+    mArmPC[mInum++] = pc();
+}
+
+// no use for Branches with integer PC, but they're in the Interface class ....
+void ArmToMipsAssembler::B(int cc __unused, uint32_t* to_pc __unused)
+{
+    LOG_ALWAYS_FATAL("branch to absolute PC not supported, use Label\n");
+    mArmPC[mInum++] = pc();
+}
+
+void ArmToMipsAssembler::BL(int cc __unused, uint32_t* to_pc __unused)
+{
+    LOG_ALWAYS_FATAL("branch to absolute PC not supported, use Label\n");
+    mArmPC[mInum++] = pc();
+}
+
+void ArmToMipsAssembler::BX(int cc __unused, int Rn __unused)
+{
+    LOG_ALWAYS_FATAL("branch to absolute PC not supported, use Label\n");
+    mArmPC[mInum++] = pc();
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Data Transfer...
+#endif
+
+// data transfer...
+void ArmToMipsAssembler::LDR(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed12_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            amode.writeback = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_12_PRE:
+            if (Rn == ARMAssemblerInterface::SP) {
+                Rn = R_sp;      // convert LDR via Arm SP to LW via Mips SP
+            }
+            mMips->LW(Rd, Rn, amode.value);
+            if (amode.writeback) {      // OPTIONAL writeback on pre-index mode
+                mMips->ADDIU(Rn, Rn, amode.value);
+            }
+            break;
+        case AMODE_IMM_12_POST:
+            if (Rn == ARMAssemblerInterface::SP) {
+                Rn = R_sp;      // convert STR thru Arm SP to STR thru Mips SP
+            }
+            mMips->LW(Rd, Rn, 0);
+            mMips->ADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_SCALE_PRE:
+            // we only support simple base + index, no advanced modes for this one yet
+            mMips->ADDU(R_at, Rn, amode.reg);
+            mMips->LW(Rd, R_at, 0);
+            break;
+    }
+}
+
+void ArmToMipsAssembler::LDRB(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed12_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            amode.writeback = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_12_PRE:
+            mMips->LBU(Rd, Rn, amode.value);
+            if (amode.writeback) {      // OPTIONAL writeback on pre-index mode
+                mMips->ADDIU(Rn, Rn, amode.value);
+            }
+            break;
+        case AMODE_IMM_12_POST:
+            mMips->LBU(Rd, Rn, 0);
+            mMips->ADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_SCALE_PRE:
+            // we only support simple base + index, no advanced modes for this one yet
+            mMips->ADDU(R_at, Rn, amode.reg);
+            mMips->LBU(Rd, R_at, 0);
+            break;
+    }
+
+}
+
+void ArmToMipsAssembler::STR(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed12_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            amode.writeback = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_12_PRE:
+            if (Rn == ARMAssemblerInterface::SP) {
+                Rn = R_sp;  // convert STR thru Arm SP to SW thru Mips SP
+            }
+            if (amode.writeback) {      // OPTIONAL writeback on pre-index mode
+                // If we will writeback, then update the index reg, then store.
+                // This correctly handles stack-push case.
+                mMips->ADDIU(Rn, Rn, amode.value);
+                mMips->SW(Rd, Rn, 0);
+            } else {
+                // No writeback so store offset by value
+                mMips->SW(Rd, Rn, amode.value);
+            }
+            break;
+        case AMODE_IMM_12_POST:
+            mMips->SW(Rd, Rn, 0);
+            mMips->ADDIU(Rn, Rn, amode.value);  // post index always writes back
+            break;
+        case AMODE_REG_SCALE_PRE:
+            // we only support simple base + index, no advanced modes for this one yet
+            mMips->ADDU(R_at, Rn, amode.reg);
+            mMips->SW(Rd, R_at, 0);
+            break;
+    }
+}
+
+void ArmToMipsAssembler::STRB(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed12_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            amode.writeback = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_12_PRE:
+            mMips->SB(Rd, Rn, amode.value);
+            if (amode.writeback) {      // OPTIONAL writeback on pre-index mode
+                mMips->ADDIU(Rn, Rn, amode.value);
+            }
+            break;
+        case AMODE_IMM_12_POST:
+            mMips->SB(Rd, Rn, 0);
+            mMips->ADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_SCALE_PRE:
+            // we only support simple base + index, no advanced modes for this one yet
+            mMips->ADDU(R_at, Rn, amode.reg);
+            mMips->SB(Rd, R_at, 0);
+            break;
+    }
+}
+
+void ArmToMipsAssembler::LDRH(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed8_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_8_PRE:      // no support yet for writeback
+            mMips->LHU(Rd, Rn, amode.value);
+            break;
+        case AMODE_IMM_8_POST:
+            mMips->LHU(Rd, Rn, 0);
+            mMips->ADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_PRE:
+            // we only support simple base +/- index
+            if (amode.reg >= 0) {
+                mMips->ADDU(R_at, Rn, amode.reg);
+            } else {
+                mMips->SUBU(R_at, Rn, abs(amode.reg));
+            }
+            mMips->LHU(Rd, R_at, 0);
+            break;
+    }
+}
+
+void ArmToMipsAssembler::LDRSB(int cc __unused, int Rd __unused,
+                               int Rn __unused, uint32_t offset __unused)
+{
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMipsAssembler::LDRSH(int cc __unused, int Rd __unused,
+                               int Rn __unused, uint32_t offset __unused)
+{
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMipsAssembler::STRH(int cc __unused, int Rd, int Rn, uint32_t offset)
+{
+    mArmPC[mInum++] = pc();
+    // work-around for ARM default address mode of immed8_pre(0)
+    if (offset > AMODE_UNSUPPORTED) offset = 0;
+    switch (offset) {
+        case 0:
+            amode.value = 0;
+            // fall thru to next case ....
+        case AMODE_IMM_8_PRE:      // no support yet for writeback
+            mMips->SH(Rd, Rn, amode.value);
+            break;
+        case AMODE_IMM_8_POST:
+            mMips->SH(Rd, Rn, 0);
+            mMips->ADDIU(Rn, Rn, amode.value);
+            break;
+        case AMODE_REG_PRE:
+            // we only support simple base +/- index
+            if (amode.reg >= 0) {
+                mMips->ADDU(R_at, Rn, amode.reg);
+            } else {
+                mMips->SUBU(R_at, Rn, abs(amode.reg));
+            }
+            mMips->SH(Rd, R_at, 0);
+            break;
+    }
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Block Data Transfer...
+#endif
+
+// block data transfer...
+void ArmToMipsAssembler::LDM(int cc __unused, int dir __unused,
+        int Rn __unused, int W __unused, uint32_t reg_list __unused)
+{   //                        ED FD EA FA      IB IA DB DA
+    // const uint8_t P[8] = { 1, 0, 1, 0,      1, 0, 1, 0 };
+    // const uint8_t U[8] = { 1, 1, 0, 0,      1, 1, 0, 0 };
+    // *mPC++ = (cc<<28) | (4<<25) | (uint32_t(P[dir])<<24) |
+    //         (uint32_t(U[dir])<<23) | (1<<20) | (W<<21) | (Rn<<16) | reg_list;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMipsAssembler::STM(int cc __unused, int dir __unused,
+        int Rn __unused, int W __unused, uint32_t reg_list __unused)
+{   //                        FA EA FD ED      IB IA DB DA
+    // const uint8_t P[8] = { 0, 1, 0, 1,      1, 0, 1, 0 };
+    // const uint8_t U[8] = { 0, 0, 1, 1,      1, 1, 0, 0 };
+    // *mPC++ = (cc<<28) | (4<<25) | (uint32_t(P[dir])<<24) |
+    //         (uint32_t(U[dir])<<23) | (0<<20) | (W<<21) | (Rn<<16) | reg_list;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Special...
+#endif
+
+// special...
+void ArmToMipsAssembler::SWP(int cc __unused, int Rn __unused,
+                             int Rd __unused, int Rm __unused) {
+    // *mPC++ = (cc<<28) | (2<<23) | (Rn<<16) | (Rd << 12) | 0x90 | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMipsAssembler::SWPB(int cc __unused, int Rn __unused,
+                              int Rd __unused, int Rm __unused) {
+    // *mPC++ = (cc<<28) | (2<<23) | (1<<22) | (Rn<<16) | (Rd << 12) | 0x90 | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMipsAssembler::SWI(int cc __unused, uint32_t comment __unused) {
+    // *mPC++ = (cc<<28) | (0xF<<24) | comment;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+
+#if 0
+#pragma mark -
+#pragma mark DSP instructions...
+#endif
+
+// DSP instructions...
+void ArmToMipsAssembler::PLD(int Rn __unused, uint32_t offset) {
+    LOG_ALWAYS_FATAL_IF(!((offset&(1<<24)) && !(offset&(1<<21))),
+                        "PLD only P=1, W=0");
+    // *mPC++ = 0xF550F000 | (Rn<<16) | offset;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMipsAssembler::CLZ(int cc __unused, int Rd, int Rm)
+{
+    mArmPC[mInum++] = pc();
+    mMips->CLZ(Rd, Rm);
+}
+
+void ArmToMipsAssembler::QADD(int cc __unused,  int Rd __unused,
+                              int Rm __unused, int Rn __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1000050 | (Rn<<16) | (Rd<<12) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMipsAssembler::QDADD(int cc __unused,  int Rd __unused,
+                               int Rm __unused, int Rn __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1400050 | (Rn<<16) | (Rd<<12) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMipsAssembler::QSUB(int cc __unused,  int Rd __unused,
+                              int Rm __unused, int Rn __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1200050 | (Rn<<16) | (Rd<<12) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMipsAssembler::QDSUB(int cc __unused,  int Rd __unused,
+                               int Rm __unused, int Rn __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1600050 | (Rn<<16) | (Rd<<12) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+// 16 x 16 signed multiply (like SMLAxx without the accumulate)
+void ArmToMipsAssembler::SMUL(int cc __unused, int xy,
+                int Rd, int Rm, int Rs)
+{
+    mArmPC[mInum++] = pc();
+
+    // the 16 bits may be in the top or bottom half of 32-bit source reg,
+    // as defined by the codes BB, BT, TB, TT (compressed param xy)
+    // where x corresponds to Rm and y to Rs
+
+    // select half-reg for Rm
+    if (xy & xyTB) {
+        // use top 16-bits
+        mMips->SRA(R_at, Rm, 16);
+    } else {
+        // use bottom 16, but sign-extend to 32
+        if (mips32r2) {
+            mMips->SEH(R_at, Rm);
+        } else {
+            mMips->SLL(R_at, Rm, 16);
+            mMips->SRA(R_at, R_at, 16);
+        }
+    }
+    // select half-reg for Rs
+    if (xy & xyBT) {
+        // use top 16-bits
+        mMips->SRA(R_at2, Rs, 16);
+    } else {
+        // use bottom 16, but sign-extend to 32
+        if (mips32r2) {
+            mMips->SEH(R_at2, Rs);
+        } else {
+            mMips->SLL(R_at2, Rs, 16);
+            mMips->SRA(R_at2, R_at2, 16);
+        }
+    }
+    mMips->MUL(Rd, R_at, R_at2);
+}
+
+// signed 32b x 16b multiple, save top 32-bits of 48-bit result
+void ArmToMipsAssembler::SMULW(int cc __unused, int y,
+                int Rd, int Rm, int Rs)
+{
+    mArmPC[mInum++] = pc();
+
+    // the selector yT or yB refers to reg Rs
+    if (y & yT) {
+        // zero the bottom 16-bits, with 2 shifts, it can affect result
+        mMips->SRL(R_at, Rs, 16);
+        mMips->SLL(R_at, R_at, 16);
+
+    } else {
+        // move low 16-bit half, to high half
+        mMips->SLL(R_at, Rs, 16);
+    }
+    mMips->MULT(Rm, R_at);
+    mMips->MFHI(Rd);
+}
+
+// 16 x 16 signed multiply, accumulate: Rd = Rm{16} * Rs{16} + Rn
+void ArmToMipsAssembler::SMLA(int cc __unused, int xy,
+                int Rd, int Rm, int Rs, int Rn)
+{
+    mArmPC[mInum++] = pc();
+
+    // the 16 bits may be in the top or bottom half of 32-bit source reg,
+    // as defined by the codes BB, BT, TB, TT (compressed param xy)
+    // where x corresponds to Rm and y to Rs
+
+    // select half-reg for Rm
+    if (xy & xyTB) {
+        // use top 16-bits
+        mMips->SRA(R_at, Rm, 16);
+    } else {
+        // use bottom 16, but sign-extend to 32
+        if (mips32r2) {
+            mMips->SEH(R_at, Rm);
+        } else {
+            mMips->SLL(R_at, Rm, 16);
+            mMips->SRA(R_at, R_at, 16);
+        }
+    }
+    // select half-reg for Rs
+    if (xy & xyBT) {
+        // use top 16-bits
+        mMips->SRA(R_at2, Rs, 16);
+    } else {
+        // use bottom 16, but sign-extend to 32
+        if (mips32r2) {
+            mMips->SEH(R_at2, Rs);
+        } else {
+            mMips->SLL(R_at2, Rs, 16);
+            mMips->SRA(R_at2, R_at2, 16);
+        }
+    }
+
+    mMips->MUL(R_at, R_at, R_at2);
+    mMips->ADDU(Rd, R_at, Rn);
+}
+
+void ArmToMipsAssembler::SMLAL(int cc __unused, int xy __unused,
+                               int RdHi __unused, int RdLo __unused,
+                               int Rs __unused, int Rm __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1400080 | (RdHi<<16) | (RdLo<<12) | (Rs<<8) | (xy<<4) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+void ArmToMipsAssembler::SMLAW(int cc __unused, int y __unused,
+                               int Rd __unused, int Rm __unused,
+                               int Rs __unused, int Rn __unused)
+{
+    // *mPC++ = (cc<<28) | 0x1200080 | (Rd<<16) | (Rn<<12) | (Rs<<8) | (y<<4) | Rm;
+    mArmPC[mInum++] = pc();
+    mMips->NOP2();
+    NOT_IMPLEMENTED();
+}
+
+// used by ARMv6 version of GGLAssembler::filter32
+void ArmToMipsAssembler::UXTB16(int cc __unused, int Rd, int Rm, int rotate)
+{
+    mArmPC[mInum++] = pc();
+
+    //Rd[31:16] := ZeroExtend((Rm ROR (8 * sh))[23:16]),
+    //Rd[15:0] := ZeroExtend((Rm ROR (8 * sh))[7:0]). sh 0-3.
+
+    mMips->ROTR(Rm, Rm, rotate * 8);
+    mMips->AND(Rd, Rm, 0x00FF00FF);
+}
+
+void ArmToMipsAssembler::UBFX(int cc __unused, int Rd __unused,
+                              int Rn __unused, int lsb __unused,
+                              int width __unused)
+{
+     /* Placeholder for UBFX */
+     mArmPC[mInum++] = pc();
+
+     mMips->NOP2();
+     NOT_IMPLEMENTED();
+}
+
+
+
+
+
+#if 0
+#pragma mark -
+#pragma mark MIPS Assembler...
+#endif
+
+
+//**************************************************************************
+//**************************************************************************
+//**************************************************************************
+
+
+/* mips assembler
+** this is a subset of mips32r2, targeted specifically at ARM instruction
+** replacement in the pixelflinger/codeflinger code.
+**
+** To that end, there is no need for floating point, or priviledged
+** instructions. This all runs in user space, no float.
+**
+** The syntax makes no attempt to be as complete as the assember, with
+** synthetic instructions, and automatic recognition of immedate operands
+** (use the immediate form of the instruction), etc.
+**
+** We start with mips32r1, and may add r2 and dsp extensions if cpu
+** supports. Decision will be made at compile time, based on gcc
+** options. (makes sense since android will be built for a a specific
+** device)
+*/
+
+MIPSAssembler::MIPSAssembler(const sp<Assembly>& assembly, ArmToMipsAssembler *parent)
+    : mParent(parent),
+    mAssembly(assembly)
+{
+    mBase = mPC = (uint32_t *)assembly->base();
+    mDuration = ggl_system_time();
+}
+
+MIPSAssembler::MIPSAssembler(void* assembly)
+    : mParent(NULL), mAssembly(NULL)
+{
+    mBase = mPC = (uint32_t *)assembly;
+}
+
+MIPSAssembler::~MIPSAssembler()
+{
+}
+
+
+uint32_t* MIPSAssembler::pc() const
+{
+    return mPC;
+}
+
+uint32_t* MIPSAssembler::base() const
+{
+    return mBase;
+}
+
+void MIPSAssembler::reset()
+{
+    mBase = mPC = (uint32_t *)mAssembly->base();
+    mBranchTargets.clear();
+    mLabels.clear();
+    mLabelsInverseMapping.clear();
+    mComments.clear();
+}
+
+
+// convert tabs to spaces, and remove any newline
+// works with strings of limited size (makes a temp copy)
+#define TABSTOP 8
+void MIPSAssembler::string_detab(char *s)
+{
+    char *os = s;
+    char temp[100];
+    char *t = temp;
+    int len = 99;
+    int i = TABSTOP;
+
+    while (*s && len-- > 0) {
+        if (*s == '\n') { s++; continue; }
+        if (*s == '\t') {
+            s++;
+            for ( ; i>0; i--) {*t++ = ' '; len--; }
+        } else {
+            *t++ = *s++;
+        }
+        if (i <= 0) i = TABSTOP;
+        i--;
+    }
+    *t = '\0';
+    strcpy(os, temp);
+}
+
+void MIPSAssembler::string_pad(char *s, int padded_len)
+{
+    int len = strlen(s);
+    s += len;
+    for (int i = padded_len - len; i > 0; --i) {
+        *s++ = ' ';
+    }
+    *s = '\0';
+}
+
+// ----------------------------------------------------------------------------
+
+void MIPSAssembler::disassemble(const char* name)
+{
+    char di_buf[140];
+
+    if (name) {
+        ALOGW("%s:\n", name);
+    }
+
+    bool arm_disasm_fmt = (mParent->mArmDisassemblyBuffer == NULL) ? false : true;
+
+    typedef char dstr[40];
+    dstr *lines = (dstr *)mParent->mArmDisassemblyBuffer;
+
+    if (mParent->mArmDisassemblyBuffer != NULL) {
+        for (int i=0; i<mParent->mArmInstrCount; ++i) {
+            string_detab(lines[i]);
+        }
+    }
+
+    size_t count = pc()-base();
+    uint32_t* mipsPC = base();
+    while (count--) {
+        ssize_t label = mLabelsInverseMapping.indexOfKey(mipsPC);
+        if (label >= 0) {
+            ALOGW("%s:\n", mLabelsInverseMapping.valueAt(label));
+        }
+        ssize_t comment = mComments.indexOfKey(mipsPC);
+        if (comment >= 0) {
+            ALOGW("; %s\n", mComments.valueAt(comment));
+        }
+        // ALOGW("%08x:    %08x    ", int(i), int(i[0]));
+        ::mips_disassem(mipsPC, di_buf, arm_disasm_fmt);
+        string_detab(di_buf);
+        string_pad(di_buf, 30);
+        ALOGW("0x%p:    %08x    %s", mipsPC, uint32_t(*mipsPC), di_buf);
+        mipsPC++;
+    }
+}
+
+void MIPSAssembler::comment(const char* string)
+{
+    mComments.add(pc(), string);
+}
+
+void MIPSAssembler::label(const char* theLabel)
+{
+    mLabels.add(theLabel, pc());
+    mLabelsInverseMapping.add(pc(), theLabel);
+}
+
+
+void MIPSAssembler::prolog()
+{
+    // empty - done in ArmToMipsAssembler
+}
+
+void MIPSAssembler::epilog(uint32_t touched __unused)
+{
+    // empty - done in ArmToMipsAssembler
+}
+
+int MIPSAssembler::generate(const char* name)
+{
+    // fixup all the branches
+    size_t count = mBranchTargets.size();
+    while (count--) {
+        const branch_target_t& bt = mBranchTargets[count];
+        uint32_t* target_pc = mLabels.valueFor(bt.label);
+        LOG_ALWAYS_FATAL_IF(!target_pc,
+                "error resolving branch targets, target_pc is null");
+        int32_t offset = int32_t(target_pc - (bt.pc+1));
+        *bt.pc |= offset & 0x00FFFF;
+    }
+
+    mAssembly->resize( int(pc()-base())*4 );
+
+    // the instruction & data caches are flushed by CodeCache
+    const int64_t duration = ggl_system_time() - mDuration;
+    const char * const format = "generated %s (%d ins) at [%p:%p] in %" PRId64 " ns\n";
+    ALOGI(format, name, int(pc()-base()), base(), pc(), duration);
+
+    char value[PROPERTY_VALUE_MAX];
+    value[0] = '\0';
+
+    property_get("debug.pf.disasm", value, "0");
+
+    if (atoi(value) != 0) {
+        disassemble(name);
+    }
+
+    return OK;
+}
+
+uint32_t* MIPSAssembler::pcForLabel(const char* label)
+{
+    return mLabels.valueFor(label);
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Arithmetic...
+#endif
+
+void MIPSAssembler::ADDU(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (addu_fn<<FUNC_SHF)
+                    | (Rs<<RS_SHF) | (Rt<<RT_SHF) | (Rd<<RD_SHF);
+}
+
+// MD00086 pdf says this is: ADDIU rt, rs, imm -- they do not use Rd
+void MIPSAssembler::ADDIU(int Rt, int Rs, int16_t imm)
+{
+    *mPC++ = (addiu_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | (imm & MSK_16);
+}
+
+
+void MIPSAssembler::SUBU(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (subu_fn<<FUNC_SHF) |
+                        (Rs<<RS_SHF) | (Rt<<RT_SHF) | (Rd<<RD_SHF) ;
+}
+
+
+void MIPSAssembler::SUBIU(int Rt, int Rs, int16_t imm)   // really addiu(d, s, -j)
+{
+    *mPC++ = (addiu_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | ((-imm) & MSK_16);
+}
+
+
+void MIPSAssembler::NEGU(int Rd, int Rs)    // really subu(d, zero, s)
+{
+    MIPSAssembler::SUBU(Rd, 0, Rs);
+}
+
+void MIPSAssembler::MUL(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec2_op<<OP_SHF) | (mul_fn<<FUNC_SHF) |
+                        (Rs<<RS_SHF) | (Rt<<RT_SHF) | (Rd<<RD_SHF) ;
+}
+
+void MIPSAssembler::MULT(int Rs, int Rt)    // dest is hi,lo
+{
+    *mPC++ = (spec_op<<OP_SHF) | (mult_fn<<FUNC_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF);
+}
+
+void MIPSAssembler::MULTU(int Rs, int Rt)    // dest is hi,lo
+{
+    *mPC++ = (spec_op<<OP_SHF) | (multu_fn<<FUNC_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF);
+}
+
+void MIPSAssembler::MADD(int Rs, int Rt)    // hi,lo = hi,lo + Rs * Rt
+{
+    *mPC++ = (spec2_op<<OP_SHF) | (madd_fn<<FUNC_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF);
+}
+
+void MIPSAssembler::MADDU(int Rs, int Rt)    // hi,lo = hi,lo + Rs * Rt
+{
+    *mPC++ = (spec2_op<<OP_SHF) | (maddu_fn<<FUNC_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF);
+}
+
+
+void MIPSAssembler::MSUB(int Rs, int Rt)    // hi,lo = hi,lo - Rs * Rt
+{
+    *mPC++ = (spec2_op<<OP_SHF) | (msub_fn<<FUNC_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF);
+}
+
+void MIPSAssembler::MSUBU(int Rs, int Rt)    // hi,lo = hi,lo - Rs * Rt
+{
+    *mPC++ = (spec2_op<<OP_SHF) | (msubu_fn<<FUNC_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF);
+}
+
+
+void MIPSAssembler::SEB(int Rd, int Rt)    // sign-extend byte (mips32r2)
+{
+    *mPC++ = (spec3_op<<OP_SHF) | (bshfl_fn<<FUNC_SHF) | (seb_fn << SA_SHF) |
+                    (Rt<<RT_SHF) | (Rd<<RD_SHF);
+}
+
+void MIPSAssembler::SEH(int Rd, int Rt)    // sign-extend half-word (mips32r2)
+{
+    *mPC++ = (spec3_op<<OP_SHF) | (bshfl_fn<<FUNC_SHF) | (seh_fn << SA_SHF) |
+                    (Rt<<RT_SHF) | (Rd<<RD_SHF);
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Comparisons...
+#endif
+
+void MIPSAssembler::SLT(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (slt_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::SLTI(int Rt, int Rs, int16_t imm)
+{
+    *mPC++ = (slti_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | (imm & MSK_16);
+}
+
+
+void MIPSAssembler::SLTU(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (sltu_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::SLTIU(int Rt, int Rs, int16_t imm)
+{
+    *mPC++ = (sltiu_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | (imm & MSK_16);
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Logical...
+#endif
+
+void MIPSAssembler::AND(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (and_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::ANDI(int Rt, int Rs, uint16_t imm)      // todo: support larger immediate
+{
+    *mPC++ = (andi_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | (imm & MSK_16);
+}
+
+
+void MIPSAssembler::OR(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (or_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::ORI(int Rt, int Rs, uint16_t imm)
+{
+    *mPC++ = (ori_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | (imm & MSK_16);
+}
+
+void MIPSAssembler::NOR(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (nor_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::NOT(int Rd, int Rs)
+{
+    MIPSAssembler::NOR(Rd, Rs, 0);  // NOT(d,s) = NOR(d,s,zero)
+}
+
+void MIPSAssembler::XOR(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (xor_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::XORI(int Rt, int Rs, uint16_t imm)  // todo: support larger immediate
+{
+    *mPC++ = (xori_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | (imm & MSK_16);
+}
+
+void MIPSAssembler::SLL(int Rd, int Rt, int shft)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (sll_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rt<<RT_SHF) | (shft<<RE_SHF);
+}
+
+void MIPSAssembler::SLLV(int Rd, int Rt, int Rs)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (sllv_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::SRL(int Rd, int Rt, int shft)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (srl_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rt<<RT_SHF) | (shft<<RE_SHF);
+}
+
+void MIPSAssembler::SRLV(int Rd, int Rt, int Rs)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (srlv_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::SRA(int Rd, int Rt, int shft)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (sra_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rt<<RT_SHF) | (shft<<RE_SHF);
+}
+
+void MIPSAssembler::SRAV(int Rd, int Rt, int Rs)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (srav_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::ROTR(int Rd, int Rt, int shft)      // mips32r2
+{
+    // note weird encoding (SRL + 1)
+    *mPC++ = (spec_op<<OP_SHF) | (srl_fn<<FUNC_SHF) |
+                        (1<<RS_SHF) | (Rd<<RD_SHF) | (Rt<<RT_SHF) | (shft<<RE_SHF);
+}
+
+void MIPSAssembler::ROTRV(int Rd, int Rt, int Rs)       // mips32r2
+{
+    // note weird encoding (SRLV + 1)
+    *mPC++ = (spec_op<<OP_SHF) | (srlv_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF) | (1<<RE_SHF);
+}
+
+// uses at2 register (mapped to some appropriate mips reg)
+void MIPSAssembler::RORsyn(int Rd, int Rt, int Rs)
+{
+    // synthetic: d = t rotated by s
+    MIPSAssembler::NEGU(R_at2, Rs);
+    MIPSAssembler::SLLV(R_at2, Rt, R_at2);
+    MIPSAssembler::SRLV(Rd, Rt, Rs);
+    MIPSAssembler::OR(Rd, Rd, R_at2);
+}
+
+// immediate version - uses at2 register (mapped to some appropriate mips reg)
+void MIPSAssembler::RORIsyn(int Rd, int Rt, int rot)
+{
+    // synthetic: d = t rotated by immed rot
+    // d = s >> rot | s << (32-rot)
+    MIPSAssembler::SLL(R_at2, Rt, 32-rot);
+    MIPSAssembler::SRL(Rd, Rt, rot);
+    MIPSAssembler::OR(Rd, Rd, R_at2);
+}
+
+void MIPSAssembler::CLO(int Rd, int Rs)
+{
+    // Rt field must have same gpr # as Rd
+    *mPC++ = (spec2_op<<OP_SHF) | (clo_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rd<<RT_SHF);
+}
+
+void MIPSAssembler::CLZ(int Rd, int Rs)
+{
+    // Rt field must have same gpr # as Rd
+    *mPC++ = (spec2_op<<OP_SHF) | (clz_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rd<<RT_SHF);
+}
+
+void MIPSAssembler::WSBH(int Rd, int Rt)      // mips32r2
+{
+    *mPC++ = (spec3_op<<OP_SHF) | (bshfl_fn<<FUNC_SHF) | (wsbh_fn << SA_SHF) |
+                        (Rt<<RT_SHF) | (Rd<<RD_SHF);
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Load/store...
+#endif
+
+void MIPSAssembler::LW(int Rt, int Rbase, int16_t offset)
+{
+    *mPC++ = (lw_op<<OP_SHF) | (Rbase<<RS_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+void MIPSAssembler::SW(int Rt, int Rbase, int16_t offset)
+{
+    *mPC++ = (sw_op<<OP_SHF) | (Rbase<<RS_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+// lb is sign-extended
+void MIPSAssembler::LB(int Rt, int Rbase, int16_t offset)
+{
+    *mPC++ = (lb_op<<OP_SHF) | (Rbase<<RS_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+void MIPSAssembler::LBU(int Rt, int Rbase, int16_t offset)
+{
+    *mPC++ = (lbu_op<<OP_SHF) | (Rbase<<RS_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+void MIPSAssembler::SB(int Rt, int Rbase, int16_t offset)
+{
+    *mPC++ = (sb_op<<OP_SHF) | (Rbase<<RS_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+// lh is sign-extended
+void MIPSAssembler::LH(int Rt, int Rbase, int16_t offset)
+{
+    *mPC++ = (lh_op<<OP_SHF) | (Rbase<<RS_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+void MIPSAssembler::LHU(int Rt, int Rbase, int16_t offset)
+{
+    *mPC++ = (lhu_op<<OP_SHF) | (Rbase<<RS_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+void MIPSAssembler::SH(int Rt, int Rbase, int16_t offset)
+{
+    *mPC++ = (sh_op<<OP_SHF) | (Rbase<<RS_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+void MIPSAssembler::LUI(int Rt, int16_t offset)
+{
+    *mPC++ = (lui_op<<OP_SHF) | (Rt<<RT_SHF) | (offset & MSK_16);
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Register move...
+#endif
+
+void MIPSAssembler::MOVE(int Rd, int Rs)
+{
+    // encoded as "or rd, rs, zero"
+    *mPC++ = (spec_op<<OP_SHF) | (or_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (0<<RT_SHF);
+}
+
+void MIPSAssembler::MOVN(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (movn_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::MOVZ(int Rd, int Rs, int Rt)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (movz_fn<<FUNC_SHF) |
+                        (Rd<<RD_SHF) | (Rs<<RS_SHF) | (Rt<<RT_SHF);
+}
+
+void MIPSAssembler::MFHI(int Rd)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (mfhi_fn<<FUNC_SHF) | (Rd<<RD_SHF);
+}
+
+void MIPSAssembler::MFLO(int Rd)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (mflo_fn<<FUNC_SHF) | (Rd<<RD_SHF);
+}
+
+void MIPSAssembler::MTHI(int Rs)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (mthi_fn<<FUNC_SHF) | (Rs<<RS_SHF);
+}
+
+void MIPSAssembler::MTLO(int Rs)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (mtlo_fn<<FUNC_SHF) | (Rs<<RS_SHF);
+}
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Branch...
+#endif
+
+// temporarily forcing a NOP into branch-delay slot, just to be safe
+// todo: remove NOP, optimze use of delay slots
+void MIPSAssembler::B(const char* label)
+{
+    mBranchTargets.add(branch_target_t(label, mPC));
+
+    // encoded as BEQ zero, zero, offset
+    *mPC++ = (beq_op<<OP_SHF) | (0<<RT_SHF)
+                        | (0<<RS_SHF) | 0;  // offset filled in later
+
+    MIPSAssembler::NOP();
+}
+
+void MIPSAssembler::BEQ(int Rs, int Rt, const char* label)
+{
+    mBranchTargets.add(branch_target_t(label, mPC));
+    *mPC++ = (beq_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | 0;
+    MIPSAssembler::NOP();
+}
+
+void MIPSAssembler::BNE(int Rs, int Rt, const char* label)
+{
+    mBranchTargets.add(branch_target_t(label, mPC));
+    *mPC++ = (bne_op<<OP_SHF) | (Rt<<RT_SHF) | (Rs<<RS_SHF) | 0;
+    MIPSAssembler::NOP();
+}
+
+void MIPSAssembler::BLEZ(int Rs, const char* label)
+{
+    mBranchTargets.add(branch_target_t(label, mPC));
+    *mPC++ = (blez_op<<OP_SHF) | (0<<RT_SHF) | (Rs<<RS_SHF) | 0;
+    MIPSAssembler::NOP();
+}
+
+void MIPSAssembler::BLTZ(int Rs, const char* label)
+{
+    mBranchTargets.add(branch_target_t(label, mPC));
+    *mPC++ = (regimm_op<<OP_SHF) | (bltz_fn<<RT_SHF) | (Rs<<RS_SHF) | 0;
+    MIPSAssembler::NOP();
+}
+
+void MIPSAssembler::BGTZ(int Rs, const char* label)
+{
+    mBranchTargets.add(branch_target_t(label, mPC));
+    *mPC++ = (bgtz_op<<OP_SHF) | (0<<RT_SHF) | (Rs<<RS_SHF) | 0;
+    MIPSAssembler::NOP();
+}
+
+
+void MIPSAssembler::BGEZ(int Rs, const char* label)
+{
+    mBranchTargets.add(branch_target_t(label, mPC));
+    *mPC++ = (regimm_op<<OP_SHF) | (bgez_fn<<RT_SHF) | (Rs<<RS_SHF) | 0;
+    MIPSAssembler::NOP();
+}
+
+void MIPSAssembler::JR(int Rs)
+{
+    *mPC++ = (spec_op<<OP_SHF) | (Rs<<RS_SHF) | (jr_fn << FUNC_SHF);
+    MIPSAssembler::NOP();
+}
+
+
+#if 0
+#pragma mark -
+#pragma mark Synthesized Branch...
+#endif
+
+// synthetic variants of branches (using slt & friends)
+void MIPSAssembler::BEQZ(int Rs, const char* label)
+{
+    BEQ(Rs, R_zero, label);
+}
+
+void MIPSAssembler::BNEZ(int Rs __unused, const char* label)
+{
+    BNE(R_at, R_zero, label);
+}
+
+void MIPSAssembler::BGE(int Rs, int Rt, const char* label)
+{
+    SLT(R_at, Rs, Rt);
+    BEQ(R_at, R_zero, label);
+}
+
+void MIPSAssembler::BGEU(int Rs, int Rt, const char* label)
+{
+    SLTU(R_at, Rs, Rt);
+    BEQ(R_at, R_zero, label);
+}
+
+void MIPSAssembler::BGT(int Rs, int Rt, const char* label)
+{
+    SLT(R_at, Rt, Rs);   // rev
+    BNE(R_at, R_zero, label);
+}
+
+void MIPSAssembler::BGTU(int Rs, int Rt, const char* label)
+{
+    SLTU(R_at, Rt, Rs);   // rev
+    BNE(R_at, R_zero, label);
+}
+
+void MIPSAssembler::BLE(int Rs, int Rt, const char* label)
+{
+    SLT(R_at, Rt, Rs);   // rev
+    BEQ(R_at, R_zero, label);
+}
+
+void MIPSAssembler::BLEU(int Rs, int Rt, const char* label)
+{
+    SLTU(R_at, Rt, Rs);  // rev
+    BEQ(R_at, R_zero, label);
+}
+
+void MIPSAssembler::BLT(int Rs, int Rt, const char* label)
+{
+    SLT(R_at, Rs, Rt);
+    BNE(R_at, R_zero, label);
+}
+
+void MIPSAssembler::BLTU(int Rs, int Rt, const char* label)
+{
+    SLTU(R_at, Rs, Rt);
+    BNE(R_at, R_zero, label);
+}
+
+
+
+
+#if 0
+#pragma mark -
+#pragma mark Misc...
+#endif
+
+void MIPSAssembler::NOP(void)
+{
+    // encoded as "sll zero, zero, 0", which is all zero
+    *mPC++ = (spec_op<<OP_SHF) | (sll_fn<<FUNC_SHF);
+}
+
+// using this as special opcode for not-yet-implemented ARM instruction
+void MIPSAssembler::NOP2(void)
+{
+    // encoded as "sll zero, zero, 2", still a nop, but a unique code
+    *mPC++ = (spec_op<<OP_SHF) | (sll_fn<<FUNC_SHF) | (2 << RE_SHF);
+}
+
+// using this as special opcode for purposefully NOT implemented ARM instruction
+void MIPSAssembler::UNIMPL(void)
+{
+    // encoded as "sll zero, zero, 3", still a nop, but a unique code
+    *mPC++ = (spec_op<<OP_SHF) | (sll_fn<<FUNC_SHF) | (3 << RE_SHF);
+}
+
+
+}; // namespace android:
diff --git a/libpixelflinger/codeflinger/MIPSAssembler.h b/libpixelflinger/codeflinger/MIPSAssembler.h
new file mode 100644
index 0000000..c1178b6
--- /dev/null
+++ b/libpixelflinger/codeflinger/MIPSAssembler.h
@@ -0,0 +1,557 @@
+/* libs/pixelflinger/codeflinger/MIPSAssembler.h
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef ANDROID_MIPSASSEMBLER_H
+#define ANDROID_MIPSASSEMBLER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "tinyutils/smartpointer.h"
+#include "utils/KeyedVector.h"
+#include "utils/Vector.h"
+
+#include "ARMAssemblerInterface.h"
+#include "CodeCache.h"
+
+namespace android {
+
+class MIPSAssembler;    // forward reference
+
+// this class mimics ARMAssembler interface
+//  intent is to translate each ARM instruction to 1 or more MIPS instr
+//  implementation calls MIPSAssembler class to generate mips code
+class ArmToMipsAssembler : public ARMAssemblerInterface
+{
+public:
+                ArmToMipsAssembler(const sp<Assembly>& assembly,
+                        char *abuf = 0, int linesz = 0, int instr_count = 0);
+    virtual     ~ArmToMipsAssembler();
+
+    uint32_t*   base() const;
+    uint32_t*   pc() const;
+    void        disassemble(const char* name);
+
+    virtual void    reset();
+
+    virtual int     generate(const char* name);
+    virtual int     getCodegenArch();
+
+    virtual void    prolog();
+    virtual void    epilog(uint32_t touched);
+    virtual void    comment(const char* string);
+
+
+    // -----------------------------------------------------------------------
+    // shifters and addressing modes
+    // -----------------------------------------------------------------------
+
+    // shifters...
+    virtual bool        isValidImmediate(uint32_t immed);
+    virtual int         buildImmediate(uint32_t i, uint32_t& rot, uint32_t& imm);
+
+    virtual uint32_t    imm(uint32_t immediate);
+    virtual uint32_t    reg_imm(int Rm, int type, uint32_t shift);
+    virtual uint32_t    reg_rrx(int Rm);
+    virtual uint32_t    reg_reg(int Rm, int type, int Rs);
+
+    // addressing modes...
+    // LDR(B)/STR(B)/PLD
+    // (immediate and Rm can be negative, which indicates U=0)
+    virtual uint32_t    immed12_pre(int32_t immed12, int W=0);
+    virtual uint32_t    immed12_post(int32_t immed12);
+    virtual uint32_t    reg_scale_pre(int Rm, int type=0, uint32_t shift=0, int W=0);
+    virtual uint32_t    reg_scale_post(int Rm, int type=0, uint32_t shift=0);
+
+    // LDRH/LDRSB/LDRSH/STRH
+    // (immediate and Rm can be negative, which indicates U=0)
+    virtual uint32_t    immed8_pre(int32_t immed8, int W=0);
+    virtual uint32_t    immed8_post(int32_t immed8);
+    virtual uint32_t    reg_pre(int Rm, int W=0);
+    virtual uint32_t    reg_post(int Rm);
+
+
+
+
+    virtual void    dataProcessing(int opcode, int cc, int s,
+                                int Rd, int Rn,
+                                uint32_t Op2);
+    virtual void MLA(int cc, int s,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void MUL(int cc, int s,
+                int Rd, int Rm, int Rs);
+    virtual void UMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void UMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void SMULL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+    virtual void SMUAL(int cc, int s,
+                int RdLo, int RdHi, int Rm, int Rs);
+
+    virtual void B(int cc, uint32_t* pc);
+    virtual void BL(int cc, uint32_t* pc);
+    virtual void BX(int cc, int Rn);
+    virtual void label(const char* theLabel);
+    virtual void B(int cc, const char* label);
+    virtual void BL(int cc, const char* label);
+
+    virtual uint32_t* pcForLabel(const char* label);
+
+    virtual void LDR (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRB(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void STR (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void STRB(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRH (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRSB(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void LDRSH(int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+    virtual void STRH (int cc, int Rd,
+                int Rn, uint32_t offset = 0);
+
+    virtual void LDM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list);
+    virtual void STM(int cc, int dir,
+                int Rn, int W, uint32_t reg_list);
+
+    virtual void SWP(int cc, int Rn, int Rd, int Rm);
+    virtual void SWPB(int cc, int Rn, int Rd, int Rm);
+    virtual void SWI(int cc, uint32_t comment);
+
+    virtual void PLD(int Rn, uint32_t offset);
+    virtual void CLZ(int cc, int Rd, int Rm);
+    virtual void QADD(int cc, int Rd, int Rm, int Rn);
+    virtual void QDADD(int cc, int Rd, int Rm, int Rn);
+    virtual void QSUB(int cc, int Rd, int Rm, int Rn);
+    virtual void QDSUB(int cc, int Rd, int Rm, int Rn);
+    virtual void SMUL(int cc, int xy,
+                int Rd, int Rm, int Rs);
+    virtual void SMULW(int cc, int y,
+                int Rd, int Rm, int Rs);
+    virtual void SMLA(int cc, int xy,
+                int Rd, int Rm, int Rs, int Rn);
+    virtual void SMLAL(int cc, int xy,
+                int RdHi, int RdLo, int Rs, int Rm);
+    virtual void SMLAW(int cc, int y,
+                int Rd, int Rm, int Rs, int Rn);
+
+    // byte/half word extract...
+    virtual void UXTB16(int cc, int Rd, int Rm, int rotate);
+
+    // bit manipulation...
+    virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width);
+
+    // this is some crap to share is MIPSAssembler class for debug
+    char *      mArmDisassemblyBuffer;
+    int         mArmLineLength;
+    int         mArmInstrCount;
+
+    int         mInum;      // current arm instuction number (0..n)
+    uint32_t**  mArmPC;     // array: PC for 1st mips instr of
+                            //      each translated ARM instr
+
+
+private:
+    ArmToMipsAssembler(const ArmToMipsAssembler& rhs);
+    ArmToMipsAssembler& operator = (const ArmToMipsAssembler& rhs);
+
+    void init_conditional_labels(void);
+
+    void protectConditionalOperands(int Rd);
+
+    // reg__tmp set to MIPS AT, reg 1
+    int dataProcAdrModes(int op, int& source, bool sign = false, int reg_tmp = 1);
+
+    sp<Assembly>        mAssembly;
+    MIPSAssembler*      mMips;
+
+
+    enum misc_constants_t {
+        ARM_MAX_INSTUCTIONS = 512  // based on ASSEMBLY_SCRATCH_SIZE
+    };
+
+    enum {
+        SRC_REG = 0,
+        SRC_IMM,
+        SRC_ERROR = -1
+    };
+
+    enum addr_modes {
+        // start above the range of legal mips reg #'s (0-31)
+        AMODE_REG = 0x20,
+        AMODE_IMM, AMODE_REG_IMM,               // for data processing
+        AMODE_IMM_12_PRE, AMODE_IMM_12_POST,    // for load/store
+        AMODE_REG_SCALE_PRE, AMODE_IMM_8_PRE,
+        AMODE_IMM_8_POST, AMODE_REG_PRE,
+        AMODE_UNSUPPORTED
+    };
+
+    struct addr_mode_t {    // address modes for current ARM instruction
+        int         reg;
+        int         stype;
+        uint32_t    value;
+        bool        writeback;  // writeback the adr reg after modification
+    } amode;
+
+    enum cond_types {
+        CMP_COND = 1,
+        SBIT_COND
+    };
+
+    struct cond_mode_t {    // conditional-execution info for current ARM instruction
+        cond_types  type;
+        int         r1;
+        int         r2;
+        int         labelnum;
+        char        label[100][10];
+    } cond;
+
+};
+
+
+
+
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
+
+// This is the basic MIPS assembler, which just creates the opcodes in memory.
+// All the more complicated work is done in ArmToMipsAssember above.
+
+class MIPSAssembler
+{
+public:
+                MIPSAssembler(const sp<Assembly>& assembly, ArmToMipsAssembler *parent);
+                MIPSAssembler(void* assembly);
+    virtual     ~MIPSAssembler();
+
+    virtual uint32_t*   base() const;
+    virtual uint32_t*   pc() const;
+    virtual void        reset();
+
+    virtual void        disassemble(const char* name);
+
+    virtual void        prolog();
+    virtual void        epilog(uint32_t touched);
+    virtual int         generate(const char* name);
+    virtual void        comment(const char* string);
+    virtual void        label(const char* string);
+
+    // valid only after generate() has been called
+    virtual uint32_t*   pcForLabel(const char* label);
+
+
+    // ------------------------------------------------------------------------
+    // MIPSAssemblerInterface...
+    // ------------------------------------------------------------------------
+
+#if 0
+#pragma mark -
+#pragma mark Arithmetic...
+#endif
+
+    void ADDU(int Rd, int Rs, int Rt);
+    void ADDIU(int Rt, int Rs, int16_t imm);
+    void SUBU(int Rd, int Rs, int Rt);
+    void SUBIU(int Rt, int Rs, int16_t imm);
+    void NEGU(int Rd, int Rs);
+    void MUL(int Rd, int Rs, int Rt);
+    void MULT(int Rs, int Rt);      // dest is hi,lo
+    void MULTU(int Rs, int Rt);     // dest is hi,lo
+    void MADD(int Rs, int Rt);      // hi,lo = hi,lo + Rs * Rt
+    void MADDU(int Rs, int Rt);     // hi,lo = hi,lo + Rs * Rt
+    void MSUB(int Rs, int Rt);      // hi,lo = hi,lo - Rs * Rt
+    void MSUBU(int Rs, int Rt);     // hi,lo = hi,lo - Rs * Rt
+    void SEB(int Rd, int Rt);       // sign-extend byte (mips32r2)
+    void SEH(int Rd, int Rt);       // sign-extend half-word (mips32r2)
+
+
+#if 0
+#pragma mark -
+#pragma mark Comparisons...
+#endif
+
+    void SLT(int Rd, int Rs, int Rt);
+    void SLTI(int Rt, int Rs, int16_t imm);
+    void SLTU(int Rd, int Rs, int Rt);
+    void SLTIU(int Rt, int Rs, int16_t imm);
+
+
+#if 0
+#pragma mark -
+#pragma mark Logical...
+#endif
+
+    void AND(int Rd, int Rs, int Rt);
+    void ANDI(int Rd, int Rs, uint16_t imm);
+    void OR(int Rd, int Rs, int Rt);
+    void ORI(int Rt, int Rs, uint16_t imm);
+    void NOR(int Rd, int Rs, int Rt);
+    void NOT(int Rd, int Rs);
+    void XOR(int Rd, int Rs, int Rt);
+    void XORI(int Rt, int Rs, uint16_t imm);
+
+    void SLL(int Rd, int Rt, int shft);
+    void SLLV(int Rd, int Rt, int Rs);
+    void SRL(int Rd, int Rt, int shft);
+    void SRLV(int Rd, int Rt, int Rs);
+    void SRA(int Rd, int Rt, int shft);
+    void SRAV(int Rd, int Rt, int Rs);
+    void ROTR(int Rd, int Rt, int shft);    // mips32r2
+    void ROTRV(int Rd, int Rt, int Rs);     // mips32r2
+    void RORsyn(int Rd, int Rs, int Rt);    // synthetic: d = s rotated by t
+    void RORIsyn(int Rd, int Rt, int rot);  // synthetic: d = s rotated by immed
+
+    void CLO(int Rd, int Rs);
+    void CLZ(int Rd, int Rs);
+    void WSBH(int Rd, int Rt);
+
+
+#if 0
+#pragma mark -
+#pragma mark Load/store...
+#endif
+
+    void LW(int Rt, int Rbase, int16_t offset);
+    void SW(int Rt, int Rbase, int16_t offset);
+    void LB(int Rt, int Rbase, int16_t offset);
+    void LBU(int Rt, int Rbase, int16_t offset);
+    void SB(int Rt, int Rbase, int16_t offset);
+    void LH(int Rt, int Rbase, int16_t offset);
+    void LHU(int Rt, int Rbase, int16_t offset);
+    void SH(int Rt, int Rbase, int16_t offset);
+    void LUI(int Rt, int16_t offset);
+
+#if 0
+#pragma mark -
+#pragma mark Register moves...
+#endif
+
+    void MOVE(int Rd, int Rs);
+    void MOVN(int Rd, int Rs, int Rt);
+    void MOVZ(int Rd, int Rs, int Rt);
+    void MFHI(int Rd);
+    void MFLO(int Rd);
+    void MTHI(int Rs);
+    void MTLO(int Rs);
+
+#if 0
+#pragma mark -
+#pragma mark Branch...
+#endif
+
+    void B(const char* label);
+    void BEQ(int Rs, int Rt, const char* label);
+    void BNE(int Rs, int Rt, const char* label);
+    void BGEZ(int Rs, const char* label);
+    void BGTZ(int Rs, const char* label);
+    void BLEZ(int Rs, const char* label);
+    void BLTZ(int Rs, const char* label);
+    void JR(int Rs);
+
+
+#if 0
+#pragma mark -
+#pragma mark Synthesized Branch...
+#endif
+
+    // synthetic variants of above (using slt & friends)
+    void BEQZ(int Rs, const char* label);
+    void BNEZ(int Rs, const char* label);
+    void BGE(int Rs, int Rt, const char* label);
+    void BGEU(int Rs, int Rt, const char* label);
+    void BGT(int Rs, int Rt, const char* label);
+    void BGTU(int Rs, int Rt, const char* label);
+    void BLE(int Rs, int Rt, const char* label);
+    void BLEU(int Rs, int Rt, const char* label);
+    void BLT(int Rs, int Rt, const char* label);
+    void BLTU(int Rs, int Rt, const char* label);
+
+#if 0
+#pragma mark -
+#pragma mark Misc...
+#endif
+
+    void NOP(void);
+    void NOP2(void);
+    void UNIMPL(void);
+
+
+
+
+
+protected:
+    virtual void string_detab(char *s);
+    virtual void string_pad(char *s, int padded_len);
+
+    ArmToMipsAssembler *mParent;
+    sp<Assembly>    mAssembly;
+    uint32_t*       mBase;
+    uint32_t*       mPC;
+    uint32_t*       mPrologPC;
+    int64_t         mDuration;
+
+    struct branch_target_t {
+        inline branch_target_t() : label(0), pc(0) { }
+        inline branch_target_t(const char* l, uint32_t* p)
+            : label(l), pc(p) { }
+        const char* label;
+        uint32_t*   pc;
+    };
+
+    Vector<branch_target_t>                 mBranchTargets;
+    KeyedVector< const char*, uint32_t* >   mLabels;
+    KeyedVector< uint32_t*, const char* >   mLabelsInverseMapping;
+    KeyedVector< uint32_t*, const char* >   mComments;
+
+
+
+
+    // opcode field of all instructions
+    enum opcode_field {
+        spec_op, regimm_op, j_op, jal_op,           // 00
+        beq_op, bne_op, blez_op, bgtz_op,
+        addi_op, addiu_op, slti_op, sltiu_op,       // 08
+        andi_op, ori_op, xori_op, lui_op,
+        cop0_op, cop1_op, cop2_op, cop1x_op,        // 10
+        beql_op, bnel_op, blezl_op, bgtzl_op,
+        daddi_op, daddiu_op, ldl_op, ldr_op,        // 18
+        spec2_op, jalx_op, mdmx_op, spec3_op,
+        lb_op, lh_op, lwl_op, lw_op,                // 20
+        lbu_op, lhu_op, lwr_op, lwu_op,
+        sb_op, sh_op, swl_op, sw_op,                // 28
+        sdl_op, sdr_op, swr_op, cache_op,
+        ll_op, lwc1_op, lwc2_op, pref_op,           // 30
+        lld_op, ldc1_op, ldc2_op, ld_op,
+        sc_op, swc1_op, swc2_op, rsrv_3b_op,        // 38
+        scd_op, sdc1_op, sdc2_op, sd_op
+    };
+
+
+    // func field for special opcode
+    enum func_spec_op {
+        sll_fn, movc_fn, srl_fn, sra_fn,            // 00
+        sllv_fn, pmon_fn, srlv_fn, srav_fn,
+        jr_fn, jalr_fn, movz_fn, movn_fn,           // 08
+        syscall_fn, break_fn, spim_fn, sync_fn,
+        mfhi_fn, mthi_fn, mflo_fn, mtlo_fn,         // 10
+        dsllv_fn, rsrv_spec_2, dsrlv_fn, dsrav_fn,
+        mult_fn, multu_fn, div_fn, divu_fn,         // 18
+        dmult_fn, dmultu_fn, ddiv_fn, ddivu_fn,
+        add_fn, addu_fn, sub_fn, subu_fn,           // 20
+        and_fn, or_fn, xor_fn, nor_fn,
+        rsrv_spec_3, rsrv_spec_4, slt_fn, sltu_fn,  // 28
+        dadd_fn, daddu_fn, dsub_fn, dsubu_fn,
+        tge_fn, tgeu_fn, tlt_fn, tltu_fn,           // 30
+        teq_fn, rsrv_spec_5, tne_fn, rsrv_spec_6,
+        dsll_fn, rsrv_spec_7, dsrl_fn, dsra_fn,     // 38
+        dsll32_fn, rsrv_spec_8, dsrl32_fn, dsra32_fn
+    };
+
+    // func field for spec2 opcode
+    enum func_spec2_op {
+        madd_fn, maddu_fn, mul_fn, rsrv_spec2_3,
+        msub_fn, msubu_fn,
+        clz_fn = 0x20, clo_fn,
+        dclz_fn = 0x24, dclo_fn,
+        sdbbp_fn = 0x3f
+    };
+
+    // func field for spec3 opcode
+    enum func_spec3_op {
+        ext_fn, dextm_fn, dextu_fn, dext_fn,
+        ins_fn, dinsm_fn, dinsu_fn, dins_fn,
+        bshfl_fn = 0x20,
+        dbshfl_fn = 0x24,
+        rdhwr_fn = 0x3b
+    };
+
+    // sa field for spec3 opcodes, with BSHFL function
+    enum func_spec3_bshfl {
+        wsbh_fn = 0x02,
+        seb_fn = 0x10,
+        seh_fn = 0x18
+    };
+
+    // rt field of regimm opcodes.
+    enum regimm_fn {
+        bltz_fn, bgez_fn, bltzl_fn, bgezl_fn,
+        rsrv_ri_fn4, rsrv_ri_fn5, rsrv_ri_fn6, rsrv_ri_fn7,
+        tgei_fn, tgeiu_fn, tlti_fn, tltiu_fn,
+        teqi_fn, rsrv_ri_fn_0d, tnei_fn, rsrv_ri_fn0f,
+        bltzal_fn, bgezal_fn, bltzall_fn, bgezall_fn,
+        bposge32_fn= 0x1c,
+        synci_fn = 0x1f
+    };
+
+
+    // func field for mad opcodes (MIPS IV).
+    enum mad_func {
+        madd_fp_op      = 0x08, msub_fp_op      = 0x0a,
+        nmadd_fp_op     = 0x0c, nmsub_fp_op     = 0x0e
+    };
+
+
+    enum mips_inst_shifts {
+        OP_SHF       = 26,
+        JTARGET_SHF  = 0,
+        RS_SHF       = 21,
+        RT_SHF       = 16,
+        RD_SHF       = 11,
+        RE_SHF       = 6,
+        SA_SHF       = RE_SHF,  // synonym
+        IMM_SHF      = 0,
+        FUNC_SHF     = 0,
+
+        // mask values
+        MSK_16       = 0xffff,
+
+
+        CACHEOP_SHF  = 18,
+        CACHESEL_SHF = 16,
+    };
+};
+
+enum mips_regnames {
+    R_zero = 0,
+            R_at,   R_v0,   R_v1,   R_a0,   R_a1,   R_a2,   R_a3,
+#if __mips_isa_rev < 6
+    R_t0,   R_t1,   R_t2,   R_t3,   R_t4,   R_t5,   R_t6,   R_t7,
+#else
+    R_a4,   R_a5,   R_a6,   R_a7,   R_t0,   R_t1,   R_t2,   R_t3,
+#endif
+    R_s0,   R_s1,   R_s2,   R_s3,   R_s4,   R_s5,   R_s6,   R_s7,
+    R_t8,   R_t9,   R_k0,   R_k1,   R_gp,   R_sp,   R_s8,   R_ra,
+    R_lr = R_s8,
+
+    // arm regs 0-15 are mips regs 2-17 (meaning s0 & s1 are used)
+    R_at2  = R_s2,    // R_at2 = 18 = s2
+    R_cmp  = R_s3,    // R_cmp = 19 = s3
+    R_cmp2 = R_s4     // R_cmp2 = 20 = s4
+};
+
+
+
+}; // namespace android
+
+#endif //ANDROID_MIPSASSEMBLER_H
diff --git a/libpixelflinger/codeflinger/armreg.h b/libpixelflinger/codeflinger/armreg.h
new file mode 100644
index 0000000..fde81ba
--- /dev/null
+++ b/libpixelflinger/codeflinger/armreg.h
@@ -0,0 +1,300 @@
+/*	$NetBSD: armreg.h,v 1.28 2003/10/31 16:30:15 scw Exp $	*/
+
+/*-
+ * Copyright (c) 1998, 2001 Ben Harris
+ * Copyright (c) 1994-1996 Mark Brinicombe.
+ * Copyright (c) 1994 Brini.
+ * All rights reserved.
+ *
+ * This code is derived from software written for Brini by Mark Brinicombe
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by Brini.
+ * 4. The name of the company nor the name of the author may be used to
+ *    endorse or promote products derived from this software without specific
+ *    prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY BRINI ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL BRINI OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: /repoman/r/ncvs/src/sys/arm/include/armreg.h,v 1.3 2005/11/21 19:06:25 cognet Exp $
+ */
+
+#ifndef MACHINE_ARMREG_H
+#define MACHINE_ARMREG_H
+#define INSN_SIZE	4
+#define INSN_COND_MASK	0xf0000000	/* Condition mask */
+#define PSR_MODE        0x0000001f      /* mode mask */
+#define PSR_USR26_MODE  0x00000000
+#define PSR_FIQ26_MODE  0x00000001
+#define PSR_IRQ26_MODE  0x00000002
+#define PSR_SVC26_MODE  0x00000003
+#define PSR_USR32_MODE  0x00000010
+#define PSR_FIQ32_MODE  0x00000011
+#define PSR_IRQ32_MODE  0x00000012
+#define PSR_SVC32_MODE  0x00000013
+#define PSR_ABT32_MODE  0x00000017
+#define PSR_UND32_MODE  0x0000001b
+#define PSR_SYS32_MODE  0x0000001f
+#define PSR_32_MODE     0x00000010
+#define PSR_FLAGS	0xf0000000    /* flags */
+
+#define PSR_C_bit (1 << 29)       /* carry */
+
+/* The high-order byte is always the implementor */
+#define CPU_ID_IMPLEMENTOR_MASK	0xff000000
+#define CPU_ID_ARM_LTD		0x41000000 /* 'A' */
+#define CPU_ID_DEC		0x44000000 /* 'D' */
+#define CPU_ID_INTEL		0x69000000 /* 'i' */
+#define	CPU_ID_TI		0x54000000 /* 'T' */
+
+/* How to decide what format the CPUID is in. */
+#define CPU_ID_ISOLD(x)		(((x) & 0x0000f000) == 0x00000000)
+#define CPU_ID_IS7(x)		(((x) & 0x0000f000) == 0x00007000)
+#define CPU_ID_ISNEW(x)		(!CPU_ID_ISOLD(x) && !CPU_ID_IS7(x))
+
+/* On ARM3 and ARM6, this byte holds the foundry ID. */
+#define CPU_ID_FOUNDRY_MASK	0x00ff0000
+#define CPU_ID_FOUNDRY_VLSI	0x00560000
+
+/* On ARM7 it holds the architecture and variant (sub-model) */
+#define CPU_ID_7ARCH_MASK	0x00800000
+#define CPU_ID_7ARCH_V3		0x00000000
+#define CPU_ID_7ARCH_V4T	0x00800000
+#define CPU_ID_7VARIANT_MASK	0x007f0000
+
+/* On more recent ARMs, it does the same, but in a different format */
+#define CPU_ID_ARCH_MASK	0x000f0000
+#define CPU_ID_ARCH_V3		0x00000000
+#define CPU_ID_ARCH_V4		0x00010000
+#define CPU_ID_ARCH_V4T		0x00020000
+#define CPU_ID_ARCH_V5		0x00030000
+#define CPU_ID_ARCH_V5T		0x00040000
+#define CPU_ID_ARCH_V5TE	0x00050000
+#define CPU_ID_VARIANT_MASK	0x00f00000
+
+/* Next three nybbles are part number */
+#define CPU_ID_PARTNO_MASK	0x0000fff0
+
+/* Intel XScale has sub fields in part number */
+#define CPU_ID_XSCALE_COREGEN_MASK	0x0000e000 /* core generation */
+#define CPU_ID_XSCALE_COREREV_MASK	0x00001c00 /* core revision */
+#define CPU_ID_XSCALE_PRODUCT_MASK	0x000003f0 /* product number */
+
+/* And finally, the revision number. */
+#define CPU_ID_REVISION_MASK	0x0000000f
+
+/* Individual CPUs are probably best IDed by everything but the revision. */
+#define CPU_ID_CPU_MASK		0xfffffff0
+
+/* Fake CPU IDs for ARMs without CP15 */
+#define CPU_ID_ARM2		0x41560200
+#define CPU_ID_ARM250		0x41560250
+
+/* Pre-ARM7 CPUs -- [15:12] == 0 */
+#define CPU_ID_ARM3		0x41560300
+#define CPU_ID_ARM600		0x41560600
+#define CPU_ID_ARM610		0x41560610
+#define CPU_ID_ARM620		0x41560620
+
+/* ARM7 CPUs -- [15:12] == 7 */
+#define CPU_ID_ARM700		0x41007000 /* XXX This is a guess. */
+#define CPU_ID_ARM710		0x41007100
+#define CPU_ID_ARM7500		0x41027100 /* XXX This is a guess. */
+#define CPU_ID_ARM710A		0x41047100 /* inc ARM7100 */
+#define CPU_ID_ARM7500FE	0x41077100
+#define CPU_ID_ARM710T		0x41807100
+#define CPU_ID_ARM720T		0x41807200
+#define CPU_ID_ARM740T8K	0x41807400 /* XXX no MMU, 8KB cache */
+#define CPU_ID_ARM740T4K	0x41817400 /* XXX no MMU, 4KB cache */
+
+/* Post-ARM7 CPUs */
+#define CPU_ID_ARM810		0x41018100
+#define CPU_ID_ARM920T		0x41129200
+#define CPU_ID_ARM920T_ALT	0x41009200
+#define CPU_ID_ARM922T		0x41029220
+#define CPU_ID_ARM940T		0x41029400 /* XXX no MMU */
+#define CPU_ID_ARM946ES		0x41049460 /* XXX no MMU */
+#define	CPU_ID_ARM966ES		0x41049660 /* XXX no MMU */
+#define	CPU_ID_ARM966ESR1	0x41059660 /* XXX no MMU */
+#define CPU_ID_ARM1020E		0x4115a200 /* (AKA arm10 rev 1) */
+#define CPU_ID_ARM1022ES	0x4105a220
+#define CPU_ID_SA110		0x4401a100
+#define CPU_ID_SA1100		0x4401a110
+#define	CPU_ID_TI925T		0x54029250
+#define CPU_ID_SA1110		0x6901b110
+#define CPU_ID_IXP1200		0x6901c120
+#define CPU_ID_80200		0x69052000
+#define CPU_ID_PXA250    	0x69052100 /* sans core revision */
+#define CPU_ID_PXA210    	0x69052120
+#define CPU_ID_PXA250A		0x69052100 /* 1st version Core */
+#define CPU_ID_PXA210A		0x69052120 /* 1st version Core */
+#define CPU_ID_PXA250B		0x69052900 /* 3rd version Core */
+#define CPU_ID_PXA210B		0x69052920 /* 3rd version Core */
+#define CPU_ID_PXA250C		0x69052d00 /* 4th version Core */
+#define CPU_ID_PXA210C		0x69052d20 /* 4th version Core */
+#define	CPU_ID_80321_400	0x69052420
+#define	CPU_ID_80321_600	0x69052430
+#define	CPU_ID_80321_400_B0	0x69052c20
+#define	CPU_ID_80321_600_B0	0x69052c30
+#define	CPU_ID_IXP425_533	0x690541c0
+#define	CPU_ID_IXP425_400	0x690541d0
+#define	CPU_ID_IXP425_266	0x690541f0
+
+/* ARM3-specific coprocessor 15 registers */
+#define ARM3_CP15_FLUSH		1
+#define ARM3_CP15_CONTROL	2
+#define ARM3_CP15_CACHEABLE	3
+#define ARM3_CP15_UPDATEABLE	4
+#define ARM3_CP15_DISRUPTIVE	5	
+
+/* ARM3 Control register bits */
+#define ARM3_CTL_CACHE_ON	0x00000001
+#define ARM3_CTL_SHARED		0x00000002
+#define ARM3_CTL_MONITOR	0x00000004
+
+/*
+ * Post-ARM3 CP15 registers:
+ *
+ *	1	Control register
+ *
+ *	2	Translation Table Base
+ *
+ *	3	Domain Access Control
+ *
+ *	4	Reserved
+ *
+ *	5	Fault Status
+ *
+ *	6	Fault Address
+ *
+ *	7	Cache/write-buffer Control
+ *
+ *	8	TLB Control
+ *
+ *	9	Cache Lockdown
+ *
+ *	10	TLB Lockdown
+ *
+ *	11	Reserved
+ *
+ *	12	Reserved
+ *
+ *	13	Process ID (for FCSE)
+ *
+ *	14	Reserved
+ *
+ *	15	Implementation Dependent
+ */
+
+/* Some of the definitions below need cleaning up for V3/V4 architectures */
+
+/* CPU control register (CP15 register 1) */
+#define CPU_CONTROL_MMU_ENABLE	0x00000001 /* M: MMU/Protection unit enable */
+#define CPU_CONTROL_AFLT_ENABLE	0x00000002 /* A: Alignment fault enable */
+#define CPU_CONTROL_DC_ENABLE	0x00000004 /* C: IDC/DC enable */
+#define CPU_CONTROL_WBUF_ENABLE 0x00000008 /* W: Write buffer enable */
+#define CPU_CONTROL_32BP_ENABLE 0x00000010 /* P: 32-bit exception handlers */
+#define CPU_CONTROL_32BD_ENABLE 0x00000020 /* D: 32-bit addressing */
+#define CPU_CONTROL_LABT_ENABLE 0x00000040 /* L: Late abort enable */
+#define CPU_CONTROL_BEND_ENABLE 0x00000080 /* B: Big-endian mode */
+#define CPU_CONTROL_SYST_ENABLE 0x00000100 /* S: System protection bit */
+#define CPU_CONTROL_ROM_ENABLE	0x00000200 /* R: ROM protection bit */
+#define CPU_CONTROL_CPCLK	0x00000400 /* F: Implementation defined */
+#define CPU_CONTROL_BPRD_ENABLE 0x00000800 /* Z: Branch prediction enable */
+#define CPU_CONTROL_IC_ENABLE   0x00001000 /* I: IC enable */
+#define CPU_CONTROL_VECRELOC	0x00002000 /* V: Vector relocation */
+#define CPU_CONTROL_ROUNDROBIN	0x00004000 /* RR: Predictable replacement */
+#define CPU_CONTROL_V4COMPAT	0x00008000 /* L4: ARMv4 compat LDR R15 etc */
+
+#define CPU_CONTROL_IDC_ENABLE	CPU_CONTROL_DC_ENABLE
+
+/* XScale Auxillary Control Register (CP15 register 1, opcode2 1) */
+#define	XSCALE_AUXCTL_K		0x00000001 /* dis. write buffer coalescing */
+#define	XSCALE_AUXCTL_P		0x00000002 /* ECC protect page table access */
+#define	XSCALE_AUXCTL_MD_WB_RA	0x00000000 /* mini-D$ wb, read-allocate */
+#define	XSCALE_AUXCTL_MD_WB_RWA	0x00000010 /* mini-D$ wb, read/write-allocate */
+#define	XSCALE_AUXCTL_MD_WT	0x00000020 /* mini-D$ wt, read-allocate */
+#define	XSCALE_AUXCTL_MD_MASK	0x00000030
+
+/* Cache type register definitions */
+#define	CPU_CT_ISIZE(x)		((x) & 0xfff)		/* I$ info */
+#define	CPU_CT_DSIZE(x)		(((x) >> 12) & 0xfff)	/* D$ info */
+#define	CPU_CT_S		(1U << 24)		/* split cache */
+#define	CPU_CT_CTYPE(x)		(((x) >> 25) & 0xf)	/* cache type */
+
+#define	CPU_CT_CTYPE_WT		0	/* write-through */
+#define	CPU_CT_CTYPE_WB1	1	/* write-back, clean w/ read */
+#define	CPU_CT_CTYPE_WB2	2	/* w/b, clean w/ cp15,7 */
+#define	CPU_CT_CTYPE_WB6	6	/* w/b, cp15,7, lockdown fmt A */
+#define	CPU_CT_CTYPE_WB7	7	/* w/b, cp15,7, lockdown fmt B */
+
+#define	CPU_CT_xSIZE_LEN(x)	((x) & 0x3)		/* line size */
+#define	CPU_CT_xSIZE_M		(1U << 2)		/* multiplier */
+#define	CPU_CT_xSIZE_ASSOC(x)	(((x) >> 3) & 0x7)	/* associativity */
+#define	CPU_CT_xSIZE_SIZE(x)	(((x) >> 6) & 0x7)	/* size */
+
+/* Fault status register definitions */
+
+#define FAULT_TYPE_MASK 0x0f
+#define FAULT_USER      0x10
+
+#define FAULT_WRTBUF_0  0x00 /* Vector Exception */
+#define FAULT_WRTBUF_1  0x02 /* Terminal Exception */
+#define FAULT_BUSERR_0  0x04 /* External Abort on Linefetch -- Section */
+#define FAULT_BUSERR_1  0x06 /* External Abort on Linefetch -- Page */
+#define FAULT_BUSERR_2  0x08 /* External Abort on Non-linefetch -- Section */
+#define FAULT_BUSERR_3  0x0a /* External Abort on Non-linefetch -- Page */
+#define FAULT_BUSTRNL1  0x0c /* External abort on Translation -- Level 1 */
+#define FAULT_BUSTRNL2  0x0e /* External abort on Translation -- Level 2 */
+#define FAULT_ALIGN_0   0x01 /* Alignment */
+#define FAULT_ALIGN_1   0x03 /* Alignment */
+#define FAULT_TRANS_S   0x05 /* Translation -- Section */
+#define FAULT_TRANS_P   0x07 /* Translation -- Page */
+#define FAULT_DOMAIN_S  0x09 /* Domain -- Section */
+#define FAULT_DOMAIN_P  0x0b /* Domain -- Page */
+#define FAULT_PERM_S    0x0d /* Permission -- Section */
+#define FAULT_PERM_P    0x0f /* Permission -- Page */
+
+#define	FAULT_IMPRECISE	0x400	/* Imprecise exception (XSCALE) */
+
+/*
+ * Address of the vector page, low and high versions.
+ */
+#define	ARM_VECTORS_LOW		0x00000000U
+#define	ARM_VECTORS_HIGH	0xffff0000U
+
+/*
+ * ARM Instructions
+ *
+ *       3 3 2 2 2                              
+ *       1 0 9 8 7                                                     0
+ *      +-------+-------------------------------------------------------+
+ *      | cond  |              instruction dependant                    |
+ *      |c c c c|                                                       |
+ *      +-------+-------------------------------------------------------+
+ */
+
+#define INSN_SIZE		4		/* Always 4 bytes */
+#define INSN_COND_MASK		0xf0000000	/* Condition mask */
+#define INSN_COND_AL		0xe0000000	/* Always condition */
+
+#endif /* !MACHINE_ARMREG_H */
diff --git a/libpixelflinger/codeflinger/blending.cpp b/libpixelflinger/codeflinger/blending.cpp
new file mode 100644
index 0000000..2cbb00f
--- /dev/null
+++ b/libpixelflinger/codeflinger/blending.cpp
@@ -0,0 +1,674 @@
+/* libs/pixelflinger/codeflinger/blending.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "pixelflinger-code"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <android-base/macros.h>
+#include <log/log.h>
+
+#include "GGLAssembler.h"
+
+namespace android {
+
+void GGLAssembler::build_fog(
+                        component_t& temp,      // incomming fragment / output
+                        int component,
+                        Scratch& regs)
+{
+   if (mInfo[component].fog) {
+        Scratch scratches(registerFile());
+        comment("fog");
+
+        integer_t fragment(temp.reg, temp.h, temp.flags);
+        if (!(temp.flags & CORRUPTIBLE)) {
+            temp.reg = regs.obtain();
+            temp.flags |= CORRUPTIBLE;
+        }
+
+        integer_t fogColor(scratches.obtain(), 8, CORRUPTIBLE); 
+        LDRB(AL, fogColor.reg, mBuilderContext.Rctx,
+                immed12_pre(GGL_OFFSETOF(state.fog.color[component])));
+
+        integer_t factor(scratches.obtain(), 16, CORRUPTIBLE);
+        CONTEXT_LOAD(factor.reg, generated_vars.f);
+
+        // clamp fog factor (TODO: see if there is a way to guarantee
+        // we won't overflow, when setting the iterators)
+        BIC(AL, 0, factor.reg, factor.reg, reg_imm(factor.reg, ASR, 31));
+        CMP(AL, factor.reg, imm( 0x10000 ));
+        MOV(HS, 0, factor.reg, imm( 0x10000 ));
+
+        build_blendFOneMinusF(temp, factor, fragment, fogColor);
+    }
+}
+
+void GGLAssembler::build_blending(
+                        component_t& temp,      // incomming fragment / output
+                        const pixel_t& pixel,   // framebuffer
+                        int component,
+                        Scratch& regs)
+{
+   if (!mInfo[component].blend)
+        return;
+        
+    int fs = component==GGLFormat::ALPHA ? mBlendSrcA : mBlendSrc;
+    int fd = component==GGLFormat::ALPHA ? mBlendDstA : mBlendDst;
+    if (fs==GGL_SRC_ALPHA_SATURATE && component==GGLFormat::ALPHA)
+        fs = GGL_ONE;
+    const int blending = blending_codes(fs, fd);
+    if (!temp.size()) {
+        // here, blending will produce something which doesn't depend on
+        // that component (eg: GL_ZERO:GL_*), so the register has not been
+        // allocated yet. Will never be used as a source.
+        temp = component_t(regs.obtain(), CORRUPTIBLE);
+    }
+
+    // we are doing real blending...
+    // fb:          extracted dst
+    // fragment:    extracted src
+    // temp:        component_t(fragment) and result
+
+    // scoped register allocator
+    Scratch scratches(registerFile());
+    comment("blending");
+
+    // we can optimize these cases a bit...
+    // (1) saturation is not needed
+    // (2) we can use only one multiply instead of 2
+    // (3) we can reduce the register pressure
+    //      R = S*f + D*(1-f) = (S-D)*f + D
+    //      R = S*(1-f) + D*f = (D-S)*f + S
+
+    const bool same_factor_opt1 =
+        (fs==GGL_DST_COLOR && fd==GGL_ONE_MINUS_DST_COLOR) ||
+        (fs==GGL_SRC_COLOR && fd==GGL_ONE_MINUS_SRC_COLOR) ||
+        (fs==GGL_DST_ALPHA && fd==GGL_ONE_MINUS_DST_ALPHA) ||
+        (fs==GGL_SRC_ALPHA && fd==GGL_ONE_MINUS_SRC_ALPHA);
+
+    const bool same_factor_opt2 =
+        (fs==GGL_ONE_MINUS_DST_COLOR && fd==GGL_DST_COLOR) ||
+        (fs==GGL_ONE_MINUS_SRC_COLOR && fd==GGL_SRC_COLOR) || 
+        (fs==GGL_ONE_MINUS_DST_ALPHA && fd==GGL_DST_ALPHA) ||
+        (fs==GGL_ONE_MINUS_SRC_ALPHA && fd==GGL_SRC_ALPHA);
+
+
+    // XXX: we could also optimize these cases:
+    // R = S*f + D*f = (S+D)*f
+    // R = S*(1-f) + D*(1-f) = (S+D)*(1-f)
+    // R = S*D + D*S = 2*S*D
+
+
+    // see if we need to extract 'component' from the destination (fb)
+    integer_t fb;
+    if (blending & (BLEND_DST|FACTOR_DST)) { 
+        fb.setTo(scratches.obtain(), 32); 
+        extract(fb, pixel, component);
+        if (mDithering) {
+            // XXX: maybe what we should do instead, is simply
+            // expand fb -or- fragment to the larger of the two
+            if (fb.size() < temp.size()) {
+                // for now we expand 'fb' to min(fragment, 8)
+                int new_size = temp.size() < 8 ? temp.size() : 8;
+                expand(fb, fb, new_size);
+            }
+        }
+    }
+
+
+    // convert input fragment to integer_t
+    if (temp.l && (temp.flags & CORRUPTIBLE)) {
+        MOV(AL, 0, temp.reg, reg_imm(temp.reg, LSR, temp.l));
+        temp.h -= temp.l;
+        temp.l = 0;
+    }
+    integer_t fragment(temp.reg, temp.size(), temp.flags);
+
+    // if not done yet, convert input fragment to integer_t
+    if (temp.l) {
+        // here we know temp is not CORRUPTIBLE
+        fragment.reg = scratches.obtain();
+        MOV(AL, 0, fragment.reg, reg_imm(temp.reg, LSR, temp.l));
+        fragment.flags |= CORRUPTIBLE;
+    }
+
+    if (!(temp.flags & CORRUPTIBLE)) {
+        // temp is not corruptible, but since it's the destination it
+        // will be modified, so we need to allocate a new register.
+        temp.reg = regs.obtain();
+        temp.flags &= ~CORRUPTIBLE;
+        fragment.flags &= ~CORRUPTIBLE;
+    }
+
+    if ((blending & BLEND_SRC) && !same_factor_opt1) {
+        // source (fragment) is needed for the blending stage
+        // so it's not CORRUPTIBLE (unless we're doing same_factor_opt1)
+        fragment.flags &= ~CORRUPTIBLE;
+    }
+
+
+    if (same_factor_opt1) {
+        //  R = S*f + D*(1-f) = (S-D)*f + D
+        integer_t factor;
+        build_blend_factor(factor, fs, 
+                component, pixel, fragment, fb, scratches);
+        // fb is always corruptible from this point
+        fb.flags |= CORRUPTIBLE;
+        build_blendFOneMinusF(temp, factor, fragment, fb);
+    } else if (same_factor_opt2) {
+        //  R = S*(1-f) + D*f = (D-S)*f + S
+        integer_t factor;
+        // fb is always corrruptible here
+        fb.flags |= CORRUPTIBLE;
+        build_blend_factor(factor, fd,
+                component, pixel, fragment, fb, scratches);
+        build_blendOneMinusFF(temp, factor, fragment, fb);
+    } else {
+        integer_t src_factor;
+        integer_t dst_factor;
+
+        // if destination (fb) is not needed for the blending stage, 
+        // then it can be marked as CORRUPTIBLE
+        if (!(blending & BLEND_DST)) {
+            fb.flags |= CORRUPTIBLE;
+        }
+
+        // XXX: try to mark some registers as CORRUPTIBLE
+        // in most case we could make those corruptible
+        // when we're processing the last component
+        // but not always, for instance
+        //    when fragment is constant and not reloaded
+        //    when fb is needed for logic-ops or masking
+        //    when a register is aliased (for instance with mAlphaSource)
+
+        // blend away...
+        if (fs==GGL_ZERO) {
+            if (fd==GGL_ZERO) {         // R = 0
+                // already taken care of
+            } else if (fd==GGL_ONE) {   // R = D
+                // already taken care of
+            } else {                    // R = D*fd
+                // compute fd
+                build_blend_factor(dst_factor, fd,
+                        component, pixel, fragment, fb, scratches);
+                mul_factor(temp, fb, dst_factor);
+            }
+        } else if (fs==GGL_ONE) {
+            if (fd==GGL_ZERO) {         // R = S
+                // NOP, taken care of
+            } else if (fd==GGL_ONE) {   // R = S + D
+                component_add(temp, fb, fragment); // args order matters
+                component_sat(temp);
+            } else {                    // R = S + D*fd
+                // compute fd
+                build_blend_factor(dst_factor, fd,
+                        component, pixel, fragment, fb, scratches);
+                mul_factor_add(temp, fb, dst_factor, component_t(fragment));
+                component_sat(temp);
+            }
+        } else {
+            // compute fs
+            build_blend_factor(src_factor, fs, 
+                    component, pixel, fragment, fb, scratches);
+            if (fd==GGL_ZERO) {         // R = S*fs
+                mul_factor(temp, fragment, src_factor);
+            } else if (fd==GGL_ONE) {   // R = S*fs + D
+                mul_factor_add(temp, fragment, src_factor, component_t(fb));
+                component_sat(temp);
+            } else {                    // R = S*fs + D*fd
+                mul_factor(temp, fragment, src_factor);
+                if (scratches.isUsed(src_factor.reg))
+                    scratches.recycle(src_factor.reg);
+                // compute fd
+                build_blend_factor(dst_factor, fd,
+                        component, pixel, fragment, fb, scratches);
+                mul_factor_add(temp, fb, dst_factor, temp);
+                if (!same_factor_opt1 && !same_factor_opt2) {
+                    component_sat(temp);
+                }
+            }
+        }
+    }
+
+    // now we can be corrupted (it's the dest)
+    temp.flags |= CORRUPTIBLE;
+}
+
+void GGLAssembler::build_blend_factor(
+        integer_t& factor, int f, int component,
+        const pixel_t& dst_pixel,
+        integer_t& fragment,
+        integer_t& fb,
+        Scratch& scratches)
+{
+    integer_t src_alpha(fragment);
+
+    // src_factor/dst_factor won't be used after blending,
+    // so it's fine to mark them as CORRUPTIBLE (if not aliased)
+    factor.flags |= CORRUPTIBLE;
+
+    switch(f) {
+    case GGL_ONE_MINUS_SRC_ALPHA:
+    case GGL_SRC_ALPHA:
+        if (component==GGLFormat::ALPHA && !isAlphaSourceNeeded()) {
+            // we're processing alpha, so we already have
+            // src-alpha in fragment, and we need src-alpha just this time.
+        } else {
+           // alpha-src will be needed for other components
+            if (!mBlendFactorCached || mBlendFactorCached==f) {
+                src_alpha = mAlphaSource;
+                factor = mAlphaSource;
+                factor.flags &= ~CORRUPTIBLE;           
+                // we already computed the blend factor before, nothing to do.
+                if (mBlendFactorCached)
+                    return;
+                // this is the first time, make sure to compute the blend
+                // factor properly.
+                mBlendFactorCached = f;
+                break;
+            } else {
+                // we have a cached alpha blend factor, but we want another one,
+                // this should really not happen because by construction,
+                // we cannot have BOTH source and destination
+                // blend factors use ALPHA *and* ONE_MINUS_ALPHA (because
+                // the blending stage uses the f/(1-f) optimization
+                
+                // for completeness, we handle this case though. Since there
+                // are only 2 choices, this meens we want "the other one"
+                // (1-factor)
+                factor = mAlphaSource;
+                factor.flags &= ~CORRUPTIBLE;           
+                RSB(AL, 0, factor.reg, factor.reg, imm((1<<factor.s)));
+                mBlendFactorCached = f;
+                return;
+            }                
+        }
+        FALLTHROUGH_INTENDED;
+    case GGL_ONE_MINUS_DST_COLOR:
+    case GGL_DST_COLOR:
+    case GGL_ONE_MINUS_SRC_COLOR:
+    case GGL_SRC_COLOR:
+    case GGL_ONE_MINUS_DST_ALPHA:
+    case GGL_DST_ALPHA:
+    case GGL_SRC_ALPHA_SATURATE:
+        // help us find out what register we can use for the blend-factor
+        // CORRUPTIBLE registers are chosen first, or a new one is allocated.
+        if (fragment.flags & CORRUPTIBLE) {
+            factor.setTo(fragment.reg, 32, CORRUPTIBLE);
+            fragment.flags &= ~CORRUPTIBLE;
+        } else if (fb.flags & CORRUPTIBLE) {
+            factor.setTo(fb.reg, 32, CORRUPTIBLE);
+            fb.flags &= ~CORRUPTIBLE;
+        } else {
+            factor.setTo(scratches.obtain(), 32, CORRUPTIBLE);
+        } 
+        break;
+    }
+
+    // XXX: doesn't work if size==1
+
+    switch(f) {
+    case GGL_ONE_MINUS_DST_COLOR:
+    case GGL_DST_COLOR:
+        factor.s = fb.s;
+        ADD(AL, 0, factor.reg, fb.reg, reg_imm(fb.reg, LSR, fb.s-1));
+        break;
+    case GGL_ONE_MINUS_SRC_COLOR:
+    case GGL_SRC_COLOR:
+        factor.s = fragment.s;
+        ADD(AL, 0, factor.reg, fragment.reg,
+            reg_imm(fragment.reg, LSR, fragment.s-1));
+        break;
+    case GGL_ONE_MINUS_SRC_ALPHA:
+    case GGL_SRC_ALPHA:
+        factor.s = src_alpha.s;
+        ADD(AL, 0, factor.reg, src_alpha.reg,
+                reg_imm(src_alpha.reg, LSR, src_alpha.s-1));
+        break;
+    case GGL_ONE_MINUS_DST_ALPHA:
+    case GGL_DST_ALPHA:
+        // XXX: should be precomputed
+        extract(factor, dst_pixel, GGLFormat::ALPHA);
+        ADD(AL, 0, factor.reg, factor.reg,
+                reg_imm(factor.reg, LSR, factor.s-1));
+        break;
+    case GGL_SRC_ALPHA_SATURATE:
+        // XXX: should be precomputed
+        // XXX: f = min(As, 1-Ad)
+        // btw, we're guaranteed that Ad's size is <= 8, because
+        // it's extracted from the framebuffer
+        break;
+    }
+
+    switch(f) {
+    case GGL_ONE_MINUS_DST_COLOR:
+    case GGL_ONE_MINUS_SRC_COLOR:
+    case GGL_ONE_MINUS_DST_ALPHA:
+    case GGL_ONE_MINUS_SRC_ALPHA:
+        RSB(AL, 0, factor.reg, factor.reg, imm((1<<factor.s)));
+    }
+    
+    // don't need more than 8-bits for the blend factor
+    // and this will prevent overflows in the multiplies later
+    if (factor.s > 8) {
+        MOV(AL, 0, factor.reg, reg_imm(factor.reg, LSR, factor.s-8));
+        factor.s = 8;
+    }
+}
+
+int GGLAssembler::blending_codes(int fs, int fd)
+{
+    int blending = 0;
+    switch(fs) {
+    case GGL_ONE:
+        blending |= BLEND_SRC;
+        break;
+
+    case GGL_ONE_MINUS_DST_COLOR:
+    case GGL_DST_COLOR:
+        blending |= FACTOR_DST|BLEND_SRC;
+        break;
+    case GGL_ONE_MINUS_DST_ALPHA:
+    case GGL_DST_ALPHA:
+        // no need to extract 'component' from the destination
+        // for the blend factor, because we need ALPHA only.
+        blending |= BLEND_SRC;
+        break;
+
+    case GGL_ONE_MINUS_SRC_COLOR:
+    case GGL_SRC_COLOR:    
+        blending |= FACTOR_SRC|BLEND_SRC;
+        break;
+    case GGL_ONE_MINUS_SRC_ALPHA:
+    case GGL_SRC_ALPHA:
+    case GGL_SRC_ALPHA_SATURATE:
+        blending |= FACTOR_SRC|BLEND_SRC;
+        break;
+    }
+    switch(fd) {
+    case GGL_ONE:
+        blending |= BLEND_DST;
+        break;
+
+    case GGL_ONE_MINUS_DST_COLOR:
+    case GGL_DST_COLOR:
+        blending |= FACTOR_DST|BLEND_DST;
+        break;
+    case GGL_ONE_MINUS_DST_ALPHA:
+    case GGL_DST_ALPHA:
+        blending |= FACTOR_DST|BLEND_DST;
+        break;
+
+    case GGL_ONE_MINUS_SRC_COLOR:
+    case GGL_SRC_COLOR:    
+        blending |= FACTOR_SRC|BLEND_DST;
+        break;
+    case GGL_ONE_MINUS_SRC_ALPHA:
+    case GGL_SRC_ALPHA:
+        // no need to extract 'component' from the source
+        // for the blend factor, because we need ALPHA only.
+        blending |= BLEND_DST;
+        break;
+    }
+    return blending;
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::build_blendFOneMinusF(
+        component_t& temp,
+        const integer_t& factor, 
+        const integer_t& fragment,
+        const integer_t& fb)
+{
+    //  R = S*f + D*(1-f) = (S-D)*f + D
+    Scratch scratches(registerFile());
+    // compute S-D
+    integer_t diff(fragment.flags & CORRUPTIBLE ?
+            fragment.reg : scratches.obtain(), fb.size(), CORRUPTIBLE);
+    const int shift = fragment.size() - fb.size();
+    if (shift>0)        RSB(AL, 0, diff.reg, fb.reg, reg_imm(fragment.reg, LSR, shift));
+    else if (shift<0)   RSB(AL, 0, diff.reg, fb.reg, reg_imm(fragment.reg, LSL,-shift));
+    else                RSB(AL, 0, diff.reg, fb.reg, fragment.reg);
+    mul_factor_add(temp, diff, factor, component_t(fb));
+}
+
+void GGLAssembler::build_blendOneMinusFF(
+        component_t& temp,
+        const integer_t& factor, 
+        const integer_t& fragment,
+        const integer_t& fb)
+{
+    //  R = S*f + D*(1-f) = (S-D)*f + D
+    Scratch scratches(registerFile());
+    // compute D-S
+    integer_t diff(fb.flags & CORRUPTIBLE ?
+            fb.reg : scratches.obtain(), fb.size(), CORRUPTIBLE);
+    const int shift = fragment.size() - fb.size();
+    if (shift>0)        SUB(AL, 0, diff.reg, fb.reg, reg_imm(fragment.reg, LSR, shift));
+    else if (shift<0)   SUB(AL, 0, diff.reg, fb.reg, reg_imm(fragment.reg, LSL,-shift));
+    else                SUB(AL, 0, diff.reg, fb.reg, fragment.reg);
+    mul_factor_add(temp, diff, factor, component_t(fragment));
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::mul_factor(  component_t& d,
+                                const integer_t& v,
+                                const integer_t& f)
+{
+    int vs = v.size();
+    int fs = f.size();
+    int ms = vs+fs;
+
+    // XXX: we could have special cases for 1 bit mul
+
+    // all this code below to use the best multiply instruction
+    // wrt the parameters size. We take advantage of the fact
+    // that the 16-bits multiplies allow a 16-bit shift
+    // The trick is that we just make sure that we have at least 8-bits
+    // per component (which is enough for a 8 bits display).
+
+    int xy;
+    int vshift = 0;
+    int fshift = 0;
+    int smulw = 0;
+
+    if (vs<16) {
+        if (fs<16) {
+            xy = xyBB;
+        } else if (GGL_BETWEEN(fs, 24, 31)) {
+            ms -= 16;
+            xy = xyTB;
+        } else {
+            // eg: 15 * 18  ->  15 * 15
+            fshift = fs - 15;
+            ms -= fshift;
+            xy = xyBB;
+        }
+    } else if (GGL_BETWEEN(vs, 24, 31)) {
+        if (fs<16) {
+            ms -= 16;
+            xy = xyTB;
+        } else if (GGL_BETWEEN(fs, 24, 31)) {
+            ms -= 32;
+            xy = xyTT;
+        } else {
+            // eg: 24 * 18  ->  8 * 18
+            fshift = fs - 15;
+            ms -= 16 + fshift;
+            xy = xyTB;
+        }
+    } else {
+        if (fs<16) {
+            // eg: 18 * 15  ->  15 * 15
+            vshift = vs - 15;
+            ms -= vshift;
+            xy = xyBB;
+        } else if (GGL_BETWEEN(fs, 24, 31)) {
+            // eg: 18 * 24  ->  15 * 8
+            vshift = vs - 15;
+            ms -= 16 + vshift;
+            xy = xyBT;
+        } else {
+            // eg: 18 * 18  ->  (15 * 18)>>16
+            fshift = fs - 15;
+            ms -= 16 + fshift;
+            xy = yB;    //XXX SMULWB
+            smulw = 1;
+        }
+    }
+
+    ALOGE_IF(ms>=32, "mul_factor overflow vs=%d, fs=%d", vs, fs);
+
+    int vreg = v.reg;
+    int freg = f.reg;
+    if (vshift) {
+        MOV(AL, 0, d.reg, reg_imm(vreg, LSR, vshift));
+        vreg = d.reg;
+    }
+    if (fshift) {
+        MOV(AL, 0, d.reg, reg_imm(vreg, LSR, fshift));
+        freg = d.reg;
+    }
+    if (smulw)  SMULW(AL, xy, d.reg, vreg, freg);
+    else        SMUL(AL, xy, d.reg, vreg, freg);
+
+
+    d.h = ms;
+    if (mDithering) {
+        d.l = 0; 
+    } else {
+        d.l = fs; 
+        d.flags |= CLEAR_LO;
+    }
+}
+
+void GGLAssembler::mul_factor_add(  component_t& d,
+                                    const integer_t& v,
+                                    const integer_t& f,
+                                    const component_t& a)
+{
+    // XXX: we could have special cases for 1 bit mul
+    Scratch scratches(registerFile());
+
+    int vs = v.size();
+    int fs = f.size();
+    int as = a.h;
+    int ms = vs+fs;
+
+    ALOGE_IF(ms>=32, "mul_factor_add overflow vs=%d, fs=%d, as=%d", vs, fs, as);
+
+    integer_t add(a.reg, a.h, a.flags);
+
+    // 'a' is a component_t but it is guaranteed to have
+    // its high bits set to 0. However in the dithering case,
+    // we can't get away with truncating the potentially bad bits
+    // so extraction is needed.
+
+   if ((mDithering) && (a.size() < ms)) {
+        // we need to expand a
+        if (!(a.flags & CORRUPTIBLE)) {
+            // ... but it's not corruptible, so we need to pick a
+            // temporary register.
+            // Try to uses the destination register first (it's likely
+            // to be usable, unless it aliases an input).
+            if (d.reg!=a.reg && d.reg!=v.reg && d.reg!=f.reg) {
+                add.reg = d.reg;
+            } else {
+                add.reg = scratches.obtain();
+            }
+        }
+        expand(add, a, ms); // extracts and expands
+        as = ms;
+    }
+
+    if (ms == as) {
+        if (vs<16 && fs<16) SMLABB(AL, d.reg, v.reg, f.reg, add.reg);
+        else                MLA(AL, 0, d.reg, v.reg, f.reg, add.reg);
+    } else {
+        int temp = d.reg;
+        if (temp == add.reg) {
+            // the mul will modify add.reg, we need an intermediary reg
+            if (v.flags & CORRUPTIBLE)      temp = v.reg;
+            else if (f.flags & CORRUPTIBLE) temp = f.reg;
+            else                            temp = scratches.obtain();
+        }
+
+        if (vs<16 && fs<16) SMULBB(AL, temp, v.reg, f.reg);
+        else                MUL(AL, 0, temp, v.reg, f.reg);
+
+        if (ms>as) {
+            ADD(AL, 0, d.reg, temp, reg_imm(add.reg, LSL, ms-as));
+        } else if (ms<as) {
+            // not sure if we should expand the mul instead?
+            ADD(AL, 0, d.reg, temp, reg_imm(add.reg, LSR, as-ms));
+        }
+    }
+
+    d.h = ms;
+    if (mDithering) {
+        d.l = a.l; 
+    } else {
+        d.l = fs>a.l ? fs : a.l;
+        d.flags |= CLEAR_LO;
+    }
+}
+
+void GGLAssembler::component_add(component_t& d,
+        const integer_t& dst, const integer_t& src)
+{
+    // here we're guaranteed that fragment.size() >= fb.size()
+    const int shift = src.size() - dst.size();
+    if (!shift) {
+        ADD(AL, 0, d.reg, src.reg, dst.reg);
+    } else {
+        ADD(AL, 0, d.reg, src.reg, reg_imm(dst.reg, LSL, shift));
+    }
+
+    d.h = src.size();
+    if (mDithering) {
+        d.l = 0;
+    } else {
+        d.l = shift;
+        d.flags |= CLEAR_LO;
+    }
+}
+
+void GGLAssembler::component_sat(const component_t& v)
+{
+    const int one = ((1<<v.size())-1)<<v.l;
+    CMP(AL, v.reg, imm( 1<<v.h ));
+    if (isValidImmediate(one)) {
+        MOV(HS, 0, v.reg, imm( one ));
+    } else if (isValidImmediate(~one)) {
+        MVN(HS, 0, v.reg, imm( ~one ));
+    } else {
+        MOV(HS, 0, v.reg, imm( 1<<v.h ));
+        SUB(HS, 0, v.reg, v.reg, imm( 1<<v.l ));
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
diff --git a/libpixelflinger/codeflinger/disassem.c b/libpixelflinger/codeflinger/disassem.c
new file mode 100644
index 0000000..5cbd63d
--- /dev/null
+++ b/libpixelflinger/codeflinger/disassem.c
@@ -0,0 +1,714 @@
+/*	$NetBSD: disassem.c,v 1.14 2003/03/27 16:58:36 mycroft Exp $	*/
+
+/*-
+ * Copyright (c) 1996 Mark Brinicombe.
+ * Copyright (c) 1996 Brini.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by Brini.
+ * 4. The name of the company nor the name of the author may be used to
+ *    endorse or promote products derived from this software without specific
+ *    prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY BRINI ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL BRINI OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * RiscBSD kernel project
+ *
+ * db_disasm.c
+ *
+ * Kernel disassembler
+ *
+ * Created      : 10/02/96
+ *
+ * Structured after the sparc/sparc/db_disasm.c by David S. Miller &
+ * Paul Kranenburg
+ *
+ * This code is not complete. Not all instructions are disassembled.
+ */
+
+#include <sys/cdefs.h>
+//__FBSDID("$FreeBSD: /repoman/r/ncvs/src/sys/arm/arm/disassem.c,v 1.2 2005/01/05 21:58:47 imp Exp $");
+#include <sys/param.h>
+#include <stdio.h>
+
+#include "disassem.h"
+#include "armreg.h"
+//#include <ddb/ddb.h>
+
+/*
+ * General instruction format
+ *
+ *	insn[cc][mod]	[operands]
+ *
+ * Those fields with an uppercase format code indicate that the field
+ * follows directly after the instruction before the separator i.e.
+ * they modify the instruction rather than just being an operand to
+ * the instruction. The only exception is the writeback flag which
+ * follows a operand.
+ *
+ *
+ * 2 - print Operand 2 of a data processing instruction
+ * d - destination register (bits 12-15)
+ * n - n register (bits 16-19)
+ * s - s register (bits 8-11)
+ * o - indirect register rn (bits 16-19) (used by swap)
+ * m - m register (bits 0-3)
+ * a - address operand of ldr/str instruction
+ * e - address operand of ldrh/strh instruction
+ * l - register list for ldm/stm instruction
+ * f - 1st fp operand (register) (bits 12-14)
+ * g - 2nd fp operand (register) (bits 16-18)
+ * h - 3rd fp operand (register/immediate) (bits 0-4)
+ * j - xtb rotate literal (bits 10-11)
+ * i - bfx lsb literal (bits 7-11)
+ * w - bfx width literal (bits 16-20)
+ * b - branch address
+ * t - thumb branch address (bits 24, 0-23)
+ * k - breakpoint comment (bits 0-3, 8-19)
+ * X - block transfer type
+ * Y - block transfer type (r13 base)
+ * c - comment field bits(0-23)
+ * p - saved or current status register
+ * F - PSR transfer fields
+ * D - destination-is-r15 (P) flag on TST, TEQ, CMP, CMN
+ * L - co-processor transfer size
+ * S - set status flag
+ * P - fp precision
+ * Q - fp precision (for ldf/stf)
+ * R - fp rounding
+ * v - co-processor data transfer registers + addressing mode
+ * W - writeback flag
+ * x - instruction in hex
+ * # - co-processor number
+ * y - co-processor data processing registers
+ * z - co-processor register transfer registers
+ */
+
+struct arm32_insn {
+	u_int mask;
+	u_int pattern;
+	char* name;
+	char* format;
+};
+
+static const struct arm32_insn arm32_i[] = {
+    { 0x0fffffff, 0x0ff00000, "imb",	"c" },		/* Before swi */
+    { 0x0fffffff, 0x0ff00001, "imbrange",	"c" },	/* Before swi */
+    { 0x0f000000, 0x0f000000, "swi",	"c" },
+    { 0xfe000000, 0xfa000000, "blx",	"t" },		/* Before b and bl */
+    { 0x0f000000, 0x0a000000, "b",	"b" },
+    { 0x0f000000, 0x0b000000, "bl",	"b" },
+    { 0x0fe000f0, 0x00000090, "mul",	"Snms" },
+    { 0x0fe000f0, 0x00200090, "mla",	"Snmsd" },
+    { 0x0fe000f0, 0x00800090, "umull",	"Sdnms" },
+    { 0x0fe000f0, 0x00c00090, "smull",	"Sdnms" },
+    { 0x0fe000f0, 0x00a00090, "umlal",	"Sdnms" },
+    { 0x0fe000f0, 0x00e00090, "smlal",	"Sdnms" },
+    { 0x0fff03f0, 0x06cf0070, "uxtb16", "dmj" },
+    { 0x0fe00070, 0x07e00050, "ubfx",   "dmiw" },
+    { 0x0d700000, 0x04200000, "strt",	"daW" },
+    { 0x0d700000, 0x04300000, "ldrt",	"daW" },
+    { 0x0d700000, 0x04600000, "strbt",	"daW" },
+    { 0x0d700000, 0x04700000, "ldrbt",	"daW" },
+    { 0x0c500000, 0x04000000, "str",	"daW" },
+    { 0x0c500000, 0x04100000, "ldr",	"daW" },
+    { 0x0c500000, 0x04400000, "strb",	"daW" },
+    { 0x0c500000, 0x04500000, "ldrb",	"daW" },
+    { 0x0e1f0000, 0x080d0000, "stm",	"YnWl" },/* separate out r13 base */
+    { 0x0e1f0000, 0x081d0000, "ldm",	"YnWl" },/* separate out r13 base */    
+    { 0x0e100000, 0x08000000, "stm",	"XnWl" },
+    { 0x0e100000, 0x08100000, "ldm",	"XnWl" },    
+    { 0x0e1000f0, 0x00100090, "ldrb",	"deW" },
+    { 0x0e1000f0, 0x00000090, "strb",	"deW" },
+    { 0x0e1000f0, 0x001000d0, "ldrsb",	"deW" },
+    { 0x0e1000f0, 0x001000b0, "ldrh",	"deW" },
+    { 0x0e1000f0, 0x000000b0, "strh",	"deW" },
+    { 0x0e1000f0, 0x001000f0, "ldrsh",	"deW" },
+    { 0x0f200090, 0x00200090, "und",	"x" },	/* Before data processing */
+    { 0x0e1000d0, 0x000000d0, "und",	"x" },	/* Before data processing */
+    { 0x0ff00ff0, 0x01000090, "swp",	"dmo" },
+    { 0x0ff00ff0, 0x01400090, "swpb",	"dmo" },
+    { 0x0fbf0fff, 0x010f0000, "mrs",	"dp" },	/* Before data processing */
+    { 0x0fb0fff0, 0x0120f000, "msr",	"pFm" },/* Before data processing */
+    { 0x0fb0f000, 0x0320f000, "msr",	"pF2" },/* Before data processing */
+    { 0x0ffffff0, 0x012fff10, "bx",     "m" },
+    { 0x0fff0ff0, 0x016f0f10, "clz",	"dm" },
+    { 0x0ffffff0, 0x012fff30, "blx",	"m" },
+    { 0xfff000f0, 0xe1200070, "bkpt",	"k" },
+    { 0x0de00000, 0x00000000, "and",	"Sdn2" },
+    { 0x0de00000, 0x00200000, "eor",	"Sdn2" },
+    { 0x0de00000, 0x00400000, "sub",	"Sdn2" },
+    { 0x0de00000, 0x00600000, "rsb",	"Sdn2" },
+    { 0x0de00000, 0x00800000, "add",	"Sdn2" },
+    { 0x0de00000, 0x00a00000, "adc",	"Sdn2" },
+    { 0x0de00000, 0x00c00000, "sbc",	"Sdn2" },
+    { 0x0de00000, 0x00e00000, "rsc",	"Sdn2" },
+    { 0x0df00000, 0x01100000, "tst",	"Dn2" },
+    { 0x0df00000, 0x01300000, "teq",	"Dn2" },
+    { 0x0df00000, 0x01500000, "cmp",	"Dn2" },
+    { 0x0df00000, 0x01700000, "cmn",	"Dn2" },
+    { 0x0de00000, 0x01800000, "orr",	"Sdn2" },
+    { 0x0de00000, 0x01a00000, "mov",	"Sd2" },
+    { 0x0de00000, 0x01c00000, "bic",	"Sdn2" },
+    { 0x0de00000, 0x01e00000, "mvn",	"Sd2" },
+    { 0x0ff08f10, 0x0e000100, "adf",	"PRfgh" },
+    { 0x0ff08f10, 0x0e100100, "muf",	"PRfgh" },
+    { 0x0ff08f10, 0x0e200100, "suf",	"PRfgh" },
+    { 0x0ff08f10, 0x0e300100, "rsf",	"PRfgh" },
+    { 0x0ff08f10, 0x0e400100, "dvf",	"PRfgh" },
+    { 0x0ff08f10, 0x0e500100, "rdf",	"PRfgh" },
+    { 0x0ff08f10, 0x0e600100, "pow",	"PRfgh" },
+    { 0x0ff08f10, 0x0e700100, "rpw",	"PRfgh" },
+    { 0x0ff08f10, 0x0e800100, "rmf",	"PRfgh" },
+    { 0x0ff08f10, 0x0e900100, "fml",	"PRfgh" },
+    { 0x0ff08f10, 0x0ea00100, "fdv",	"PRfgh" },
+    { 0x0ff08f10, 0x0eb00100, "frd",	"PRfgh" },
+    { 0x0ff08f10, 0x0ec00100, "pol",	"PRfgh" },
+    { 0x0f008f10, 0x0e000100, "fpbop",	"PRfgh" },
+    { 0x0ff08f10, 0x0e008100, "mvf",	"PRfh" },
+    { 0x0ff08f10, 0x0e108100, "mnf",	"PRfh" },
+    { 0x0ff08f10, 0x0e208100, "abs",	"PRfh" },
+    { 0x0ff08f10, 0x0e308100, "rnd",	"PRfh" },
+    { 0x0ff08f10, 0x0e408100, "sqt",	"PRfh" },
+    { 0x0ff08f10, 0x0e508100, "log",	"PRfh" },
+    { 0x0ff08f10, 0x0e608100, "lgn",	"PRfh" },
+    { 0x0ff08f10, 0x0e708100, "exp",	"PRfh" },
+    { 0x0ff08f10, 0x0e808100, "sin",	"PRfh" },
+    { 0x0ff08f10, 0x0e908100, "cos",	"PRfh" },
+    { 0x0ff08f10, 0x0ea08100, "tan",	"PRfh" },
+    { 0x0ff08f10, 0x0eb08100, "asn",	"PRfh" },
+    { 0x0ff08f10, 0x0ec08100, "acs",	"PRfh" },
+    { 0x0ff08f10, 0x0ed08100, "atn",	"PRfh" },
+    { 0x0f008f10, 0x0e008100, "fpuop",	"PRfh" },
+    { 0x0e100f00, 0x0c000100, "stf",	"QLv" },
+    { 0x0e100f00, 0x0c100100, "ldf",	"QLv" },
+    { 0x0ff00f10, 0x0e000110, "flt",	"PRgd" },
+    { 0x0ff00f10, 0x0e100110, "fix",	"PRdh" },
+    { 0x0ff00f10, 0x0e200110, "wfs",	"d" },
+    { 0x0ff00f10, 0x0e300110, "rfs",	"d" },
+    { 0x0ff00f10, 0x0e400110, "wfc",	"d" },
+    { 0x0ff00f10, 0x0e500110, "rfc",	"d" },
+    { 0x0ff0ff10, 0x0e90f110, "cmf",	"PRgh" },
+    { 0x0ff0ff10, 0x0eb0f110, "cnf",	"PRgh" },
+    { 0x0ff0ff10, 0x0ed0f110, "cmfe",	"PRgh" },
+    { 0x0ff0ff10, 0x0ef0f110, "cnfe",	"PRgh" },
+    { 0xff100010, 0xfe000010, "mcr2",	"#z" },
+    { 0x0f100010, 0x0e000010, "mcr",	"#z" },
+    { 0xff100010, 0xfe100010, "mrc2",	"#z" },
+    { 0x0f100010, 0x0e100010, "mrc",	"#z" },
+    { 0xff000010, 0xfe000000, "cdp2",	"#y" },
+    { 0x0f000010, 0x0e000000, "cdp",	"#y" },
+    { 0xfe100090, 0xfc100000, "ldc2",	"L#v" },
+    { 0x0e100090, 0x0c100000, "ldc",	"L#v" },
+    { 0xfe100090, 0xfc000000, "stc2",	"L#v" },
+    { 0x0e100090, 0x0c000000, "stc",	"L#v" },
+    { 0xf550f000, 0xf550f000, "pld",	"ne" },
+    { 0x0ff00ff0, 0x01000050, "qaad",	"dmn" },
+    { 0x0ff00ff0, 0x01400050, "qdaad",	"dmn" },
+    { 0x0ff00ff0, 0x01600050, "qdsub",	"dmn" },
+    { 0x0ff00ff0, 0x01200050, "dsub",	"dmn" },
+    { 0x0ff000f0, 0x01000080, "smlabb",	"nmsd" },   // d & n inverted!!
+    { 0x0ff000f0, 0x010000a0, "smlatb",	"nmsd" },   // d & n inverted!!
+    { 0x0ff000f0, 0x010000c0, "smlabt",	"nmsd" },   // d & n inverted!!
+    { 0x0ff000f0, 0x010000e0, "smlatt",	"nmsd" },   // d & n inverted!!
+    { 0x0ff000f0, 0x01400080, "smlalbb","ndms" },   // d & n inverted!!
+    { 0x0ff000f0, 0x014000a0, "smlaltb","ndms" },   // d & n inverted!!
+    { 0x0ff000f0, 0x014000c0, "smlalbt","ndms" },   // d & n inverted!!
+    { 0x0ff000f0, 0x014000e0, "smlaltt","ndms" },   // d & n inverted!!
+    { 0x0ff000f0, 0x01200080, "smlawb", "nmsd" },   // d & n inverted!!
+    { 0x0ff0f0f0, 0x012000a0, "smulwb","nms" },   // d & n inverted!!
+    { 0x0ff000f0, 0x012000c0, "smlawt", "nmsd" },   // d & n inverted!!
+    { 0x0ff0f0f0, 0x012000e0, "smulwt","nms" },   // d & n inverted!!
+    { 0x0ff0f0f0, 0x01600080, "smulbb","nms" },   // d & n inverted!!
+    { 0x0ff0f0f0, 0x016000a0, "smultb","nms" },   // d & n inverted!!
+    { 0x0ff0f0f0, 0x016000c0, "smulbt","nms" },   // d & n inverted!!
+    { 0x0ff0f0f0, 0x016000e0, "smultt","nms" },   // d & n inverted!!
+    { 0x00000000, 0x00000000, NULL,	NULL }
+};
+
+static char const arm32_insn_conditions[][4] = {
+	"eq", "ne", "cs", "cc",
+	"mi", "pl", "vs", "vc",
+	"hi", "ls", "ge", "lt",
+	"gt", "le", "",   "nv"
+};
+
+static char const insn_block_transfers[][4] = {
+	"da", "ia", "db", "ib"
+};
+
+static char const insn_stack_block_transfers[][4] = {
+	"ed", "ea", "fd", "fa"
+};
+
+static char const op_shifts[][4] = {
+	"lsl", "lsr", "asr", "ror"
+};
+
+static char const insn_fpa_rounding[][2] = {
+	"", "p", "m", "z"
+};
+
+static char const insn_fpa_precision[][2] = {
+	"s", "d", "e", "p"
+};
+
+static char const insn_fpaconstants[][8] = {
+	"0.0", "1.0", "2.0", "3.0",
+	"4.0", "5.0", "0.5", "10.0"
+};
+
+#define insn_condition(x)	arm32_insn_conditions[((x) >> 28) & 0x0f]
+#define insn_blktrans(x)	insn_block_transfers[((x) >> 23) & 3]
+#define insn_stkblktrans(x)	insn_stack_block_transfers[(3*(((x) >> 20)&1))^(((x) >> 23)&3)]
+#define op2_shift(x)		op_shifts[((x) >> 5) & 3]
+#define insn_fparnd(x)		insn_fpa_rounding[((x) >> 5) & 0x03]
+#define insn_fpaprec(x)		insn_fpa_precision[((((x) >> 18) & 2)|((x) >> 7)) & 1]
+#define insn_fpaprect(x)	insn_fpa_precision[((((x) >> 21) & 2)|((x) >> 15)) & 1]
+#define insn_fpaimm(x)		insn_fpaconstants[(x) & 0x07]
+
+/* Local prototypes */
+static void disasm_register_shift(const disasm_interface_t *di, u_int insn);
+static void disasm_print_reglist(const disasm_interface_t *di, u_int insn);
+static void disasm_insn_ldrstr(const disasm_interface_t *di, u_int insn,
+    u_int loc);
+static void disasm_insn_ldrhstrh(const disasm_interface_t *di, u_int insn,
+    u_int loc);
+static void disasm_insn_ldcstc(const disasm_interface_t *di, u_int insn,
+    u_int loc);
+static u_int disassemble_readword(u_int address);
+static void disassemble_printaddr(u_int address);
+
+u_int
+disasm(const disasm_interface_t *di, u_int loc, int __unused altfmt)
+{
+	const struct arm32_insn *i_ptr = &arm32_i[0];
+	u_int insn = di->di_readword(loc);
+	int matchp = 0;
+	int branch;
+	char* f_ptr;
+	int fmt = 0;
+
+/*	di->di_printf("loc=%08x insn=%08x : ", loc, insn);*/
+
+	while (i_ptr->name) {
+		if ((insn & i_ptr->mask) ==  i_ptr->pattern) {
+			matchp = 1;
+			break;
+		}
+		i_ptr++;
+	}
+
+	if (!matchp) {
+		di->di_printf("und%s\t%08x\n", insn_condition(insn), insn);
+		return(loc + INSN_SIZE);
+	}
+
+	/* If instruction forces condition code, don't print it. */
+	if ((i_ptr->mask & 0xf0000000) == 0xf0000000)
+		di->di_printf("%s", i_ptr->name);
+	else
+		di->di_printf("%s%s", i_ptr->name, insn_condition(insn));
+
+	f_ptr = i_ptr->format;
+
+	/* Insert tab if there are no instruction modifiers */
+
+	if (*(f_ptr) < 'A' || *(f_ptr) > 'Z') {
+		++fmt;
+		di->di_printf("\t");
+	}
+
+	while (*f_ptr) {
+		switch (*f_ptr) {
+		/* 2 - print Operand 2 of a data processing instruction */
+		case '2':
+			if (insn & 0x02000000) {
+				int rotate= ((insn >> 7) & 0x1e);
+
+				di->di_printf("#0x%08x",
+					      (insn & 0xff) << (32 - rotate) |
+					      (insn & 0xff) >> rotate);
+			} else {  
+				disasm_register_shift(di, insn);
+			}
+			break;
+		/* d - destination register (bits 12-15) */
+		case 'd':
+			di->di_printf("r%d", ((insn >> 12) & 0x0f));
+			break;
+		/* D - insert 'p' if Rd is R15 */
+		case 'D':
+			if (((insn >> 12) & 0x0f) == 15)
+				di->di_printf("p");
+			break;
+		/* n - n register (bits 16-19) */
+		case 'n':
+			di->di_printf("r%d", ((insn >> 16) & 0x0f));
+			break;
+		/* s - s register (bits 8-11) */
+		case 's':
+			di->di_printf("r%d", ((insn >> 8) & 0x0f));
+			break;
+		/* o - indirect register rn (bits 16-19) (used by swap) */
+		case 'o':
+			di->di_printf("[r%d]", ((insn >> 16) & 0x0f));
+			break;
+		/* m - m register (bits 0-4) */
+		case 'm':
+			di->di_printf("r%d", ((insn >> 0) & 0x0f));
+			break;
+		/* a - address operand of ldr/str instruction */
+		case 'a':
+			disasm_insn_ldrstr(di, insn, loc);
+			break;
+		/* e - address operand of ldrh/strh instruction */
+		case 'e':
+			disasm_insn_ldrhstrh(di, insn, loc);
+			break;
+		/* l - register list for ldm/stm instruction */
+		case 'l':
+			disasm_print_reglist(di, insn);
+			break;
+		/* f - 1st fp operand (register) (bits 12-14) */
+		case 'f':
+			di->di_printf("f%d", (insn >> 12) & 7);
+			break;
+		/* g - 2nd fp operand (register) (bits 16-18) */
+		case 'g':
+			di->di_printf("f%d", (insn >> 16) & 7);
+			break;
+		/* h - 3rd fp operand (register/immediate) (bits 0-4) */
+		case 'h':
+			if (insn & (1 << 3))
+				di->di_printf("#%s", insn_fpaimm(insn));
+			else
+				di->di_printf("f%d", insn & 7);
+			break;
+		/* j - xtb rotate literal (bits 10-11) */
+		case 'j':
+			di->di_printf("ror #%d", ((insn >> 10) & 3) << 3);
+			break;
+        /* i - bfx lsb literal (bits 7-11) */
+        case 'i':
+            di->di_printf("#%d", (insn >> 7) & 31);
+            break;
+        /* w - bfx width literal (bits 16-20) */
+        case 'w':
+            di->di_printf("#%d", 1 + ((insn >> 16) & 31));
+            break;
+		/* b - branch address */
+		case 'b':
+			branch = ((insn << 2) & 0x03ffffff);
+			if (branch & 0x02000000)
+				branch |= 0xfc000000;
+			di->di_printaddr(loc + 8 + branch);
+			break;
+		/* t - blx address */
+		case 't':
+			branch = ((insn << 2) & 0x03ffffff) |
+			    (insn >> 23 & 0x00000002);
+			if (branch & 0x02000000)
+				branch |= 0xfc000000;
+			di->di_printaddr(loc + 8 + branch);
+			break;
+		/* X - block transfer type */
+		case 'X':
+			di->di_printf("%s", insn_blktrans(insn));
+			break;
+		/* Y - block transfer type (r13 base) */
+		case 'Y':
+			di->di_printf("%s", insn_stkblktrans(insn));
+			break;
+		/* c - comment field bits(0-23) */
+		case 'c':
+			di->di_printf("0x%08x", (insn & 0x00ffffff));
+			break;
+		/* k - breakpoint comment (bits 0-3, 8-19) */
+		case 'k':
+			di->di_printf("0x%04x",
+			    (insn & 0x000fff00) >> 4 | (insn & 0x0000000f));
+			break;
+		/* p - saved or current status register */
+		case 'p':
+			if (insn & 0x00400000)
+				di->di_printf("spsr");
+			else
+				di->di_printf("cpsr");
+			break;
+		/* F - PSR transfer fields */
+		case 'F':
+			di->di_printf("_");
+			if (insn & (1 << 16))
+				di->di_printf("c");
+			if (insn & (1 << 17))
+				di->di_printf("x");
+			if (insn & (1 << 18))
+				di->di_printf("s");
+			if (insn & (1 << 19))
+				di->di_printf("f");
+			break;
+		/* B - byte transfer flag */
+		case 'B':
+			if (insn & 0x00400000)
+				di->di_printf("b");
+			break;
+		/* L - co-processor transfer size */
+		case 'L':
+			if (insn & (1 << 22))
+				di->di_printf("l");
+			break;
+		/* S - set status flag */
+		case 'S':
+			if (insn & 0x00100000)
+				di->di_printf("s");
+			break;
+		/* P - fp precision */
+		case 'P':
+			di->di_printf("%s", insn_fpaprec(insn));
+			break;
+		/* Q - fp precision (for ldf/stf) */
+		case 'Q':
+			break;
+		/* R - fp rounding */
+		case 'R':
+			di->di_printf("%s", insn_fparnd(insn));
+			break;
+		/* W - writeback flag */
+		case 'W':
+			if (insn & (1 << 21))
+				di->di_printf("!");
+			break;
+		/* # - co-processor number */
+		case '#':
+			di->di_printf("p%d", (insn >> 8) & 0x0f);
+			break;
+		/* v - co-processor data transfer registers+addressing mode */
+		case 'v':
+			disasm_insn_ldcstc(di, insn, loc);
+			break;
+		/* x - instruction in hex */
+		case 'x':
+			di->di_printf("0x%08x", insn);
+			break;
+		/* y - co-processor data processing registers */
+		case 'y':
+			di->di_printf("%d, ", (insn >> 20) & 0x0f);
+
+			di->di_printf("c%d, c%d, c%d", (insn >> 12) & 0x0f,
+			    (insn >> 16) & 0x0f, insn & 0x0f);
+
+			di->di_printf(", %d", (insn >> 5) & 0x07);
+			break;
+		/* z - co-processor register transfer registers */
+		case 'z':
+			di->di_printf("%d, ", (insn >> 21) & 0x07);
+			di->di_printf("r%d, c%d, c%d, %d",
+			    (insn >> 12) & 0x0f, (insn >> 16) & 0x0f,
+			    insn & 0x0f, (insn >> 5) & 0x07);
+
+/*			if (((insn >> 5) & 0x07) != 0)
+				di->di_printf(", %d", (insn >> 5) & 0x07);*/
+			break;
+		default:
+			di->di_printf("[%c - unknown]", *f_ptr);
+			break;
+		}
+		if (*(f_ptr+1) >= 'A' && *(f_ptr+1) <= 'Z')
+			++f_ptr;
+		else if (*(++f_ptr)) {
+			++fmt;
+			if (fmt == 1)
+				di->di_printf("\t");
+			else
+				di->di_printf(", ");
+		}
+	};
+
+	di->di_printf("\n");
+
+	return(loc + INSN_SIZE);
+}
+
+
+static void
+disasm_register_shift(const disasm_interface_t *di, u_int insn)
+{
+	di->di_printf("r%d", (insn & 0x0f));
+	if ((insn & 0x00000ff0) == 0)
+		;
+	else if ((insn & 0x00000ff0) == 0x00000060)
+		di->di_printf(", rrx");
+	else {
+		if (insn & 0x10)
+			di->di_printf(", %s r%d", op2_shift(insn),
+			    (insn >> 8) & 0x0f);
+		else
+			di->di_printf(", %s #%d", op2_shift(insn),
+			    (insn >> 7) & 0x1f);
+	}
+}
+
+
+static void
+disasm_print_reglist(const disasm_interface_t *di, u_int insn)
+{
+	int loop;
+	int start;
+	int comma;
+
+	di->di_printf("{");
+	start = -1;
+	comma = 0;
+
+	for (loop = 0; loop < 17; ++loop) {
+		if (start != -1) {
+			if (loop == 16 || !(insn & (1 << loop))) {
+				if (comma)
+					di->di_printf(", ");
+				else
+					comma = 1;
+        			if (start == loop - 1)
+        				di->di_printf("r%d", start);
+        			else
+        				di->di_printf("r%d-r%d", start, loop - 1);
+        			start = -1;
+        		}
+        	} else {
+        		if (insn & (1 << loop))
+        			start = loop;
+        	}
+        }
+	di->di_printf("}");
+
+	if (insn & (1 << 22))
+		di->di_printf("^");
+}
+
+static void
+disasm_insn_ldrstr(const disasm_interface_t *di, u_int insn, u_int loc)
+{
+	int offset;
+
+	offset = insn & 0xfff;
+	if ((insn & 0x032f0000) == 0x010f0000) {
+		/* rA = pc, immediate index */
+		if (insn & 0x00800000)
+			loc += offset;
+		else
+			loc -= offset;
+		di->di_printaddr(loc + 8);
+ 	} else {
+		di->di_printf("[r%d", (insn >> 16) & 0x0f);
+		if ((insn & 0x03000fff) != 0x01000000) {
+			di->di_printf("%s, ", (insn & (1 << 24)) ? "" : "]");
+			if (!(insn & 0x00800000))
+				di->di_printf("-");
+			if (insn & (1 << 25))
+				disasm_register_shift(di, insn);
+			else
+				di->di_printf("#0x%03x", offset);
+		}
+		if (insn & (1 << 24))
+			di->di_printf("]");
+	}
+}
+
+static void
+disasm_insn_ldrhstrh(const disasm_interface_t *di, u_int insn, u_int loc)
+{
+	int offset;
+
+	offset = ((insn & 0xf00) >> 4) | (insn & 0xf);
+	if ((insn & 0x004f0000) == 0x004f0000) {
+		/* rA = pc, immediate index */
+		if (insn & 0x00800000)
+			loc += offset;
+		else
+			loc -= offset;
+		di->di_printaddr(loc + 8);
+ 	} else {
+		di->di_printf("[r%d", (insn >> 16) & 0x0f);
+		if ((insn & 0x01400f0f) != 0x01400000) {
+			di->di_printf("%s, ", (insn & (1 << 24)) ? "" : "]");
+			if (!(insn & 0x00800000))
+				di->di_printf("-");
+			if (insn & (1 << 22))
+				di->di_printf("#0x%02x", offset);
+			else
+				di->di_printf("r%d", (insn & 0x0f));
+		}
+		if (insn & (1 << 24))
+			di->di_printf("]");
+	}
+}
+
+static void
+disasm_insn_ldcstc(const disasm_interface_t *di, u_int insn, u_int __unused loc)
+{
+	if (((insn >> 8) & 0xf) == 1)
+		di->di_printf("f%d, ", (insn >> 12) & 0x07);
+	else
+		di->di_printf("c%d, ", (insn >> 12) & 0x0f);
+
+	di->di_printf("[r%d", (insn >> 16) & 0x0f);
+
+	di->di_printf("%s, ", (insn & (1 << 24)) ? "" : "]");
+
+	if (!(insn & (1 << 23)))
+		di->di_printf("-");
+
+	di->di_printf("#0x%03x", (insn & 0xff) << 2);
+
+	if (insn & (1 << 24))
+		di->di_printf("]");
+
+	if (insn & (1 << 21))
+		di->di_printf("!");
+}
+
+static u_int
+disassemble_readword(u_int address)
+{
+	return(*((u_int *)address));
+}
+
+static void
+disassemble_printaddr(u_int address)
+{
+	printf("0x%08x", address);
+}
+
+static const disasm_interface_t disassemble_di = {
+	disassemble_readword, disassemble_printaddr, printf
+};
+
+void
+disassemble(u_int address)
+{
+
+	(void)disasm(&disassemble_di, address, 0);
+}
+
+/* End of disassem.c */
diff --git a/libpixelflinger/codeflinger/disassem.h b/libpixelflinger/codeflinger/disassem.h
new file mode 100644
index 0000000..c7c60b6
--- /dev/null
+++ b/libpixelflinger/codeflinger/disassem.h
@@ -0,0 +1,65 @@
+/*	$NetBSD: disassem.h,v 1.4 2001/03/04 04:15:58 matt Exp $	*/
+
+/*-
+ * Copyright (c) 1997 Mark Brinicombe.
+ * Copyright (c) 1997 Causality Limited.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by Mark Brinicombe.
+ * 4. The name of the company nor the name of the author may be used to
+ *    endorse or promote products derived from this software without specific
+ *    prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Define the interface structure required by the disassembler.
+ *
+ * $FreeBSD: /repoman/r/ncvs/src/sys/arm/include/disassem.h,v 1.2 2005/01/05 21:58:48 imp Exp $
+ */
+
+#ifndef ANDROID_MACHINE_DISASSEM_H
+#define ANDROID_MACHINE_DISASSEM_H
+
+#include <sys/types.h>
+
+#if __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+	u_int	(*di_readword)(u_int);
+	void	(*di_printaddr)(u_int);
+	int	(*di_printf)(const char *, ...);
+} disasm_interface_t;
+
+/* Prototypes for callable functions */
+
+u_int disasm(const disasm_interface_t *, u_int, int);
+void disassemble(u_int);
+
+#if __cplusplus
+}
+#endif
+
+#endif /* !ANDROID_MACHINE_DISASSEM_H */
diff --git a/libpixelflinger/codeflinger/load_store.cpp b/libpixelflinger/codeflinger/load_store.cpp
new file mode 100644
index 0000000..4db0a49
--- /dev/null
+++ b/libpixelflinger/codeflinger/load_store.cpp
@@ -0,0 +1,384 @@
+/* libs/pixelflinger/codeflinger/load_store.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "pixelflinger-code"
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <log/log.h>
+
+#include "GGLAssembler.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+void GGLAssembler::store(const pointer_t& addr, const pixel_t& s, uint32_t flags)
+{
+    const int bits = addr.size;
+    const int inc = (flags & WRITE_BACK)?1:0;
+    switch (bits) {
+    case 32:
+        if (inc)    STR(AL, s.reg, addr.reg, immed12_post(4));
+        else        STR(AL, s.reg, addr.reg);
+        break;
+    case 24:
+        // 24 bits formats are a little special and used only for RGB
+        // 0x00BBGGRR is unpacked as R,G,B
+        STRB(AL, s.reg, addr.reg, immed12_pre(0));
+        MOV(AL, 0, s.reg, reg_imm(s.reg, ROR, 8));
+        STRB(AL, s.reg, addr.reg, immed12_pre(1));
+        MOV(AL, 0, s.reg, reg_imm(s.reg, ROR, 8));
+        STRB(AL, s.reg, addr.reg, immed12_pre(2));
+        if (!(s.flags & CORRUPTIBLE)) {
+            MOV(AL, 0, s.reg, reg_imm(s.reg, ROR, 16));
+        }
+        if (inc)
+            ADD(AL, 0, addr.reg, addr.reg, imm(3));
+        break;
+    case 16:
+        if (inc)    STRH(AL, s.reg, addr.reg, immed8_post(2));
+        else        STRH(AL, s.reg, addr.reg);
+        break;
+    case  8:
+        if (inc)    STRB(AL, s.reg, addr.reg, immed12_post(1));
+        else        STRB(AL, s.reg, addr.reg);
+        break;
+    }
+}
+
+void GGLAssembler::load(const pointer_t& addr, const pixel_t& s, uint32_t flags)
+{
+    Scratch scratches(registerFile());
+    int s0;
+
+    const int bits = addr.size;
+    const int inc = (flags & WRITE_BACK)?1:0;
+    switch (bits) {
+    case 32:
+        if (inc)    LDR(AL, s.reg, addr.reg, immed12_post(4));
+        else        LDR(AL, s.reg, addr.reg);
+        break;
+    case 24:
+        // 24 bits formats are a little special and used only for RGB
+        // R,G,B is packed as 0x00BBGGRR
+        s0 = scratches.obtain();
+        if (s.reg != addr.reg) {
+            LDRB(AL, s.reg, addr.reg, immed12_pre(0));      // R
+            LDRB(AL, s0, addr.reg, immed12_pre(1));         // G
+            ORR(AL, 0, s.reg, s.reg, reg_imm(s0, LSL, 8));
+            LDRB(AL, s0, addr.reg, immed12_pre(2));         // B
+            ORR(AL, 0, s.reg, s.reg, reg_imm(s0, LSL, 16));
+        } else {
+            int s1 = scratches.obtain();
+            LDRB(AL, s1, addr.reg, immed12_pre(0));         // R
+            LDRB(AL, s0, addr.reg, immed12_pre(1));         // G
+            ORR(AL, 0, s1, s1, reg_imm(s0, LSL, 8));
+            LDRB(AL, s0, addr.reg, immed12_pre(2));         // B
+            ORR(AL, 0, s.reg, s1, reg_imm(s0, LSL, 16));
+        }
+        if (inc)
+            ADD(AL, 0, addr.reg, addr.reg, imm(3));
+        break;
+    case 16:
+        if (inc)    LDRH(AL, s.reg, addr.reg, immed8_post(2));
+        else        LDRH(AL, s.reg, addr.reg);
+        break;
+    case  8:
+        if (inc)    LDRB(AL, s.reg, addr.reg, immed12_post(1));
+        else        LDRB(AL, s.reg, addr.reg);
+        break;
+    }
+}
+
+void GGLAssembler::extract(integer_t& d, int s, int h, int l, int bits)
+{
+    const int maskLen = h-l;
+
+#ifdef __mips__
+    assert(maskLen<=11);
+#else
+    assert(maskLen<=8);
+#endif
+    assert(h);
+
+    if (h != bits) {
+        const int mask = ((1<<maskLen)-1) << l;
+        if (isValidImmediate(mask)) {
+            AND(AL, 0, d.reg, s, imm(mask));    // component = packed & mask;
+        } else if (isValidImmediate(~mask)) {
+            BIC(AL, 0, d.reg, s, imm(~mask));   // component = packed & mask;
+        } else {
+            MOV(AL, 0, d.reg, reg_imm(s, LSL, 32-h));
+            l += 32-h;
+            h = 32;
+        }
+        s = d.reg;
+    }
+
+    if (l) {
+        MOV(AL, 0, d.reg, reg_imm(s, LSR, l));  // component = packed >> l;
+        s = d.reg;
+    }
+
+    if (s != d.reg) {
+        MOV(AL, 0, d.reg, s);
+    }
+
+    d.s = maskLen;
+}
+
+void GGLAssembler::extract(integer_t& d, const pixel_t& s, int component)
+{
+    extract(d,  s.reg,
+                s.format.c[component].h,
+                s.format.c[component].l,
+                s.size());
+}
+
+void GGLAssembler::extract(component_t& d, const pixel_t& s, int component)
+{
+    integer_t r(d.reg, 32, d.flags);
+    extract(r,  s.reg,
+                s.format.c[component].h,
+                s.format.c[component].l,
+                s.size());
+    d = component_t(r);
+}
+
+
+void GGLAssembler::expand(integer_t& d, const component_t& s, int dbits)
+{
+    if (s.l || (s.flags & CLEAR_HI)) {
+        extract(d, s.reg, s.h, s.l, 32);
+        expand(d, d, dbits);
+    } else {
+        expand(d, integer_t(s.reg, s.size(), s.flags), dbits);
+    }
+}
+
+void GGLAssembler::expand(component_t& d, const component_t& s, int dbits)
+{
+    integer_t r(d.reg, 32, d.flags);
+    expand(r, s, dbits);
+    d = component_t(r);
+}
+
+void GGLAssembler::expand(integer_t& dst, const integer_t& src, int dbits)
+{
+    assert(src.size());
+
+    int sbits = src.size();
+    int s = src.reg;
+    int d = dst.reg;
+
+    // be sure to set 'dst' after we read 'src' as they may be identical
+    dst.s = dbits;
+    dst.flags = 0;
+
+    if (dbits<=sbits) {
+        if (s != d) {
+            MOV(AL, 0, d, s);
+        }
+        return;
+    }
+
+    if (sbits == 1) {
+        RSB(AL, 0, d, s, reg_imm(s, LSL, dbits));
+            // d = (s<<dbits) - s;
+        return;
+    }
+
+    if (dbits % sbits) {
+        MOV(AL, 0, d, reg_imm(s, LSL, dbits-sbits));
+            // d = s << (dbits-sbits);
+        dbits -= sbits;
+        do {
+            ORR(AL, 0, d, d, reg_imm(d, LSR, sbits));
+                // d |= d >> sbits;
+            dbits -= sbits;
+            sbits *= 2;
+        } while(dbits>0);
+        return;
+    }
+
+    dbits -= sbits;
+    do {
+        ORR(AL, 0, d, s, reg_imm(s, LSL, sbits));
+            // d |= d<<sbits;
+        s = d;
+        dbits -= sbits;
+        if (sbits*2 < dbits) {
+            sbits *= 2;
+        }
+    } while(dbits>0);
+}
+
+void GGLAssembler::downshift(
+        pixel_t& d, int component, component_t s, const reg_t& dither)
+{
+    Scratch scratches(registerFile());
+
+    int sh = s.h;
+    int sl = s.l;
+    int maskHiBits = (sh!=32) ? ((s.flags & CLEAR_HI)?1:0) : 0;
+    int maskLoBits = (sl!=0)  ? ((s.flags & CLEAR_LO)?1:0) : 0;
+    int sbits = sh - sl;
+
+    int dh = d.format.c[component].h;
+    int dl = d.format.c[component].l;
+    int dbits = dh - dl;
+    int dithering = 0;
+
+    ALOGE_IF(sbits<dbits, "sbits (%d) < dbits (%d) in downshift", sbits, dbits);
+
+    if (sbits>dbits) {
+        // see if we need to dither
+        dithering = mDithering;
+    }
+
+    int ireg = d.reg;
+    if (!(d.flags & FIRST)) {
+        if (s.flags & CORRUPTIBLE)  {
+            ireg = s.reg;
+        } else {
+            ireg = scratches.obtain();
+        }
+    }
+    d.flags &= ~FIRST;
+
+    if (maskHiBits) {
+        // we need to mask the high bits (and possibly the lowbits too)
+        // and we might be able to use immediate mask.
+        if (!dithering) {
+            // we don't do this if we only have maskLoBits because we can
+            // do it more efficiently below (in the case where dl=0)
+            const int offset = sh - dbits;
+            if (dbits<=8 && offset >= 0) {
+                const uint32_t mask = ((1<<dbits)-1) << offset;
+                if (isValidImmediate(mask) || isValidImmediate(~mask)) {
+                    build_and_immediate(ireg, s.reg, mask, 32);
+                    sl = offset;
+                    s.reg = ireg;
+                    sbits = dbits;
+                    maskLoBits = maskHiBits = 0;
+                }
+            }
+        } else {
+            // in the dithering case though, we need to preserve the lower bits
+            const uint32_t mask = ((1<<sbits)-1) << sl;
+            if (isValidImmediate(mask) || isValidImmediate(~mask)) {
+                build_and_immediate(ireg, s.reg, mask, 32);
+                s.reg = ireg;
+                maskLoBits = maskHiBits = 0;
+            }
+        }
+    }
+
+    // XXX: we could special case (maskHiBits & !maskLoBits)
+    // like we do for maskLoBits below, but it happens very rarely
+    // that we have maskHiBits only and the conditions necessary to lead
+    // to better code (like doing d |= s << 24)
+
+    if (maskHiBits) {
+        MOV(AL, 0, ireg, reg_imm(s.reg, LSL, 32-sh));
+        sl += 32-sh;
+        sh = 32;
+        s.reg = ireg;
+        maskHiBits = 0;
+    }
+
+    //	Downsampling should be performed as follows:
+    //  V * ((1<<dbits)-1) / ((1<<sbits)-1)
+    //	V * [(1<<dbits)/((1<<sbits)-1)	-	1/((1<<sbits)-1)]
+    //	V * [1/((1<<sbits)-1)>>dbits	-	1/((1<<sbits)-1)]
+    //	V/((1<<(sbits-dbits))-(1>>dbits))	-	(V>>sbits)/((1<<sbits)-1)>>sbits
+    //	V/((1<<(sbits-dbits))-(1>>dbits))	-	(V>>sbits)/(1-(1>>sbits))
+    //
+    //	By approximating (1>>dbits) and (1>>sbits) to 0:
+    //
+    //		V>>(sbits-dbits)	-	V>>sbits
+    //
+	//  A good approximation is V>>(sbits-dbits),
+    //  but better one (needed for dithering) is:
+    //
+    //		(V>>(sbits-dbits)<<sbits	-	V)>>sbits
+    //		(V<<dbits	-	V)>>sbits
+    //		(V	-	V>>dbits)>>(sbits-dbits)
+
+    // Dithering is done here
+    if (dithering) {
+        comment("dithering");
+        if (sl) {
+            MOV(AL, 0, ireg, reg_imm(s.reg, LSR, sl));
+            sh -= sl;
+            sl = 0;
+            s.reg = ireg;
+        }
+        // scaling (V-V>>dbits)
+        SUB(AL, 0, ireg, s.reg, reg_imm(s.reg, LSR, dbits));
+        const int shift = (GGL_DITHER_BITS - (sbits-dbits));
+        if (shift>0)        ADD(AL, 0, ireg, ireg, reg_imm(dither.reg, LSR, shift));
+        else if (shift<0)   ADD(AL, 0, ireg, ireg, reg_imm(dither.reg, LSL,-shift));
+        else                ADD(AL, 0, ireg, ireg, dither.reg);
+        s.reg = ireg;
+    }
+
+    if ((maskLoBits|dithering) && (sh > dbits)) {
+        int shift = sh-dbits;
+        if (dl) {
+            MOV(AL, 0, ireg, reg_imm(s.reg, LSR, shift));
+            if (ireg == d.reg) {
+                MOV(AL, 0, d.reg, reg_imm(ireg, LSL, dl));
+            } else {
+                ORR(AL, 0, d.reg, d.reg, reg_imm(ireg, LSL, dl));
+            }
+        } else {
+            if (ireg == d.reg) {
+                MOV(AL, 0, d.reg, reg_imm(s.reg, LSR, shift));
+            } else {
+                ORR(AL, 0, d.reg, d.reg, reg_imm(s.reg, LSR, shift));
+            }
+        }
+    } else {
+        int shift = sh-dh;
+        if (shift>0) {
+            if (ireg == d.reg) {
+                MOV(AL, 0, d.reg, reg_imm(s.reg, LSR, shift));
+            } else {
+                ORR(AL, 0, d.reg, d.reg, reg_imm(s.reg, LSR, shift));
+            }
+        } else if (shift<0) {
+            if (ireg == d.reg) {
+                MOV(AL, 0, d.reg, reg_imm(s.reg, LSL, -shift));
+            } else {
+                ORR(AL, 0, d.reg, d.reg, reg_imm(s.reg, LSL, -shift));
+            }
+        } else {
+            if (ireg == d.reg) {
+                if (s.reg != d.reg) {
+                    MOV(AL, 0, d.reg, s.reg);
+                }
+            } else {
+                ORR(AL, 0, d.reg, d.reg, s.reg);
+            }
+        }
+    }
+}
+
+}; // namespace android
diff --git a/libpixelflinger/codeflinger/mips64_disassem.c b/libpixelflinger/codeflinger/mips64_disassem.c
new file mode 100644
index 0000000..8528299
--- /dev/null
+++ b/libpixelflinger/codeflinger/mips64_disassem.c
@@ -0,0 +1,584 @@
+/*  $NetBSD: db_disasm.c,v 1.19 2007/02/28 04:21:53 thorpej Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *  The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ralph Campbell.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *  from: @(#)kadb.c    8.1 (Berkeley) 6/10/93
+ */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <android/log.h>
+
+#include "mips_opcode.h"
+
+#define __unused __attribute__((__unused__))
+
+static char *sprintf_buffer;
+static int sprintf_buf_len;
+
+typedef uint64_t db_addr_t;
+static void db_printf(const char* fmt, ...);
+
+static const char * const op_name[64] = {
+/* 0 */ "spec", "bcond", "j", "jal", "beq", "bne", "blez", "bgtz",
+/* 8 */ "pop10", "addiu", "slti", "sltiu", "andi", "ori", "xori", "aui",
+/*16 */ "cop0", "cop1", "cop2", "?", "?", "?", "pop26", "pop27",
+/*24 */ "pop30", "daddiu", "?", "?", "?", "daui", "msa", "op37",
+/*32 */ "lb", "lh", "?",  "lw", "lbu", "lhu", "?", "lwu",
+/*40 */ "sb", "sh", "?", "sw", "?", "?", "?", "?",
+/*48 */ "?", "lwc1", "bc", "?", "?",  "ldc1", "pop66", "ld",
+/*56 */ "?", "swc1", "balc", "pcrel", "?", "sdc1", "pop76", "sd"
+};
+
+static const char * const spec_name[64] = {
+/* 0 */ "sll", "?", "srl", "sra", "sllv", "?", "srlv", "srav",
+/* 8 */ "?", "jalr", "?", "?", "syscall", "break", "sdbpp", "sync",
+/*16 */ "clz", "clo", "dclz", "dclo", "dsllv", "dlsa", "dsrlv", "dsrav",
+/*24 */ "sop30", "sop31", "sop32", "sop33", "sop34", "sop35", "sop36", "sop37",
+/*32 */ "add", "addu", "sub", "subu", "and", "or", "xor", "nor",
+/*40 */ "?", "?", "slt", "sltu", "dadd", "daddu", "dsub", "dsubu",
+/*48 */ "tge", "tgeu", "tlt", "tltu", "teq", "seleqz", "tne", "selnez",
+/*56 */ "dsll", "?", "dsrl", "dsra", "dsll32", "?", "dsrl32", "dsra32"
+};
+
+static const char * const bcond_name[32] = {
+/* 0 */ "bltz", "bgez", "?", "?", "?", "?", "dahi", "?",
+/* 8 */ "?", "?", "?", "?", "?", "?", "?", "?",
+/*16 */ "nal", "bal", "?", "?", "?", "?", "?", "sigrie",
+/*24 */ "?", "?", "?", "?", "?", "?", "dati", "synci",
+};
+
+static const char * const cop1_name[64] = {
+/* 0 */ "fadd",  "fsub", "fmpy", "fdiv", "fsqrt","fabs", "fmov", "fneg",
+/* 8 */ "fop08","fop09","fop0a","fop0b","fop0c","fop0d","fop0e","fop0f",
+/*16 */ "fop10","fop11","fop12","fop13","fop14","fop15","fop16","fop17",
+/*24 */ "fop18","fop19","fop1a","fop1b","fop1c","fop1d","fop1e","fop1f",
+/*32 */ "fcvts","fcvtd","fcvte","fop23","fcvtw","fop25","fop26","fop27",
+/*40 */ "fop28","fop29","fop2a","fop2b","fop2c","fop2d","fop2e","fop2f",
+/*48 */ "fcmp.f","fcmp.un","fcmp.eq","fcmp.ueq","fcmp.olt","fcmp.ult",
+    "fcmp.ole","fcmp.ule",
+/*56 */ "fcmp.sf","fcmp.ngle","fcmp.seq","fcmp.ngl","fcmp.lt","fcmp.nge",
+    "fcmp.le","fcmp.ngt"
+};
+
+static const char * const fmt_name[16] = {
+    "s",    "d",    "e",    "fmt3",
+    "w",    "fmt5", "fmt6", "fmt7",
+    "fmt8", "fmt9", "fmta", "fmtb",
+    "fmtc", "fmtd", "fmte", "fmtf"
+};
+
+static char * const mips_reg_name[32] = {
+    "zero", "at",   "v0",   "v1",   "a0",   "a1",   "a2",   "a3",
+    "a4",   "a5",   "a6",   "a7",   "t0",   "t1",   "t2",   "t3",
+    "s0",   "s1",   "s2",   "s3",   "s4",   "s5",   "s6",   "s7",
+    "t8",   "t9",   "k0",   "k1",   "gp",   "sp",   "s8",   "ra"
+};
+
+static char * alt_arm_reg_name[32] = {  // hacked names for comparison with ARM code
+    "zero", "at",   "r0",   "r1",   "r2",   "r3",   "r4",   "r5",
+    "r6",   "r7",   "r8",   "r9",   "r10",  "r11",  "r12",  "r13",
+    "r14",  "r15",  "at2",  "cmp",  "s4",   "s5",   "s6",   "s7",
+    "t8",   "t9",   "k0",   "k1",   "gp",   "sp",   "s8",   "ra"
+};
+
+static char * const * reg_name =  &mips_reg_name[0];
+
+static const char * const c0_opname[64] = {
+    "c0op00","tlbr",  "tlbwi", "c0op03","c0op04","c0op05","tlbwr", "c0op07",
+    "tlbp",  "c0op11","c0op12","c0op13","c0op14","c0op15","c0op16","c0op17",
+    "rfe",   "c0op21","c0op22","c0op23","c0op24","c0op25","c0op26","c0op27",
+    "eret",  "c0op31","c0op32","c0op33","c0op34","c0op35","c0op36","c0op37",
+    "c0op40","c0op41","c0op42","c0op43","c0op44","c0op45","c0op46","c0op47",
+    "c0op50","c0op51","c0op52","c0op53","c0op54","c0op55","c0op56","c0op57",
+    "c0op60","c0op61","c0op62","c0op63","c0op64","c0op65","c0op66","c0op67",
+    "c0op70","c0op71","c0op72","c0op73","c0op74","c0op75","c0op77","c0op77",
+};
+
+static const char * const c0_reg[32] = {
+    "index",    "random",   "tlblo0",  "tlblo1",
+    "context",  "pagemask", "wired",   "cp0r7",
+    "badvaddr", "count",    "tlbhi",   "compare",
+    "status",   "cause",    "epc",     "prid",
+    "config",   "lladdr",   "watchlo", "watchhi",
+    "xcontext", "cp0r21",   "cp0r22",  "debug",
+    "depc",     "perfcnt",  "ecc",     "cacheerr",
+    "taglo",    "taghi",    "errepc",  "desave"
+};
+
+static void print_addr(db_addr_t);
+db_addr_t mips_disassem(db_addr_t loc, char *di_buffer, int alt_dis_format);
+
+
+/*
+ * Disassemble instruction 'insn' nominally at 'loc'.
+ * 'loc' may in fact contain a breakpoint instruction.
+ */
+static db_addr_t
+db_disasm_insn(int insn, db_addr_t loc, bool altfmt __unused)
+{
+    bool bdslot = false;
+    InstFmt i;
+
+    i.word = insn;
+
+    switch (i.JType.op) {
+    case OP_SPECIAL:
+        if (i.word == 0) {
+            db_printf("nop");
+            break;
+        }
+        if (i.word == 0x0080) {
+            db_printf("NIY");
+            break;
+        }
+        if (i.word == 0x00c0) {
+            db_printf("NOT IMPL");
+            break;
+        }
+        /* Special cases --------------------------------------------------
+         * "addu" is a "move" only in 32-bit mode.  What's the correct
+         * answer - never decode addu/daddu as "move"?
+         */
+        if ( (i.RType.func == OP_ADDU && i.RType.rt == 0)  ||
+             (i.RType.func == OP_OR   && i.RType.rt == 0) ) {
+            db_printf("move\t%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rs]);
+            break;
+        }
+
+        if (i.RType.func == OP_SRL && (i.RType.rs & 1) == 1) {
+            db_printf("rotr\t%s,%s,%d", reg_name[i.RType.rd],
+                reg_name[i.RType.rt], i.RType.shamt);
+            break;
+        }
+        if (i.RType.func == OP_SRLV && (i.RType.shamt & 1) == 1) {
+            db_printf("rotrv\t%s,%s,%s", reg_name[i.RType.rd],
+                reg_name[i.RType.rt], reg_name[i.RType.rs]);
+            break;
+        }
+
+        if (i.RType.func == OP_SOP30) {
+            if (i.RType.shamt == OP_MUL) {
+                db_printf("mul");
+            } else if (i.RType.shamt == OP_MUH) {
+                db_printf("muh");
+            }
+            db_printf("\t%s,%s,%s", reg_name[i.RType.rd],
+                reg_name[i.RType.rs], reg_name[i.RType.rt]);
+            break;
+        }
+        if (i.RType.func == OP_SOP31) {
+            if (i.RType.shamt == OP_MUL) {
+                db_printf("mulu");
+            } else if (i.RType.shamt == OP_MUH) {
+                db_printf("muhu");
+            }
+            db_printf("\t%s,%s,%s", reg_name[i.RType.rd],
+                reg_name[i.RType.rs], reg_name[i.RType.rt]);
+            break;
+        }
+
+        if (i.RType.func == OP_JALR && i.RType.rd == 0) {
+            db_printf("jr\t%s", reg_name[i.RType.rs]);
+            bdslot = true;
+            break;
+        }
+
+        db_printf("%s", spec_name[i.RType.func]);
+        switch (i.RType.func) {
+        case OP_SLL:
+        case OP_SRL:
+        case OP_SRA:
+        case OP_DSLL:
+
+        case OP_DSRL:
+        case OP_DSRA:
+        case OP_DSLL32:
+        case OP_DSRL32:
+        case OP_DSRA32:
+            db_printf("\t%s,%s,%d",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt],
+                i.RType.shamt);
+            break;
+
+        case OP_SLLV:
+        case OP_SRLV:
+        case OP_SRAV:
+        case OP_DSLLV:
+        case OP_DSRLV:
+        case OP_DSRAV:
+            db_printf("\t%s,%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt],
+                reg_name[i.RType.rs]);
+            break;
+
+        case OP_CLZ:
+        case OP_CLO:
+        case OP_DCLZ:
+        case OP_DCLO:
+            db_printf("\t%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rs]);
+            break;
+
+        case OP_JALR:
+            db_printf("\t");
+            if (i.RType.rd != 31) {
+                db_printf("%s,", reg_name[i.RType.rd]);
+            }
+            db_printf("%s", reg_name[i.RType.rs]);
+            bdslot = true;
+            break;
+
+        case OP_SYSCALL:
+        case OP_SYNC:
+            break;
+
+        case OP_BREAK:
+            db_printf("\t%d", (i.RType.rs << 5) | i.RType.rt);
+            break;
+
+        default:
+            db_printf("\t%s,%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rs],
+                reg_name[i.RType.rt]);
+        }
+        break;
+
+    case OP_SPECIAL3:
+        if (i.RType.func == OP_EXT)
+            db_printf("ext\t%s,%s,%d,%d",
+                    reg_name[i.RType.rt],
+                    reg_name[i.RType.rs],
+                    i.RType.shamt,
+                    i.RType.rd+1);
+        else if (i.RType.func == OP_DEXT)
+            db_printf("dext\t%s,%s,%d,%d",
+                    reg_name[i.RType.rt],
+                    reg_name[i.RType.rs],
+                    i.RType.shamt,
+                    i.RType.rd+1);
+        else if (i.RType.func == OP_DEXTM)
+            db_printf("dextm\t%s,%s,%d,%d",
+                    reg_name[i.RType.rt],
+                    reg_name[i.RType.rs],
+                    i.RType.shamt,
+                    i.RType.rd+33);
+        else if (i.RType.func == OP_DEXTU)
+            db_printf("dextu\t%s,%s,%d,%d",
+                    reg_name[i.RType.rt],
+                    reg_name[i.RType.rs],
+                    i.RType.shamt+32,
+                    i.RType.rd+1);
+        else if (i.RType.func == OP_INS)
+            db_printf("ins\t%s,%s,%d,%d",
+                    reg_name[i.RType.rt],
+                    reg_name[i.RType.rs],
+                    i.RType.shamt,
+                    i.RType.rd-i.RType.shamt+1);
+        else if (i.RType.func == OP_DINS)
+            db_printf("dins\t%s,%s,%d,%d",
+                    reg_name[i.RType.rt],
+                    reg_name[i.RType.rs],
+                    i.RType.shamt,
+                    i.RType.rd-i.RType.shamt+1);
+        else if (i.RType.func == OP_DINSM)
+            db_printf("dinsm\t%s,%s,%d,%d",
+                    reg_name[i.RType.rt],
+                    reg_name[i.RType.rs],
+                    i.RType.shamt,
+                    i.RType.rd-i.RType.shamt+33);
+        else if (i.RType.func == OP_DINSU)
+            db_printf("dinsu\t%s,%s,%d,%d",
+                    reg_name[i.RType.rt],
+                    reg_name[i.RType.rs],
+                    i.RType.shamt+32,
+                    i.RType.rd-i.RType.shamt+1);
+        else if (i.RType.func == OP_BSHFL && i.RType.shamt == OP_WSBH)
+            db_printf("wsbh\t%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt]);
+        else if (i.RType.func == OP_BSHFL && i.RType.shamt == OP_SEB)
+            db_printf("seb\t%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt]);
+        else if (i.RType.func == OP_BSHFL && i.RType.shamt == OP_SEH)
+            db_printf("seh\t%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt]);
+        else if (i.RType.func == OP_RDHWR)
+            db_printf("rdhwr\t%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt]);
+        else
+            db_printf("Unknown");
+        break;
+
+    case OP_BCOND:
+        db_printf("%s\t%s,", bcond_name[i.IType.rt],
+            reg_name[i.IType.rs]);
+        goto pr_displ;
+
+    case OP_BLEZ:
+    case OP_BGTZ:
+        db_printf("%s\t%s,", op_name[i.IType.op],
+            reg_name[i.IType.rs]);
+        goto pr_displ;
+
+    case OP_BEQ:
+        if (i.IType.rs == 0 && i.IType.rt == 0) {
+            db_printf("b\t");
+            goto pr_displ;
+        }
+        /* FALLTHROUGH */
+    case OP_BNE:
+        db_printf("%s\t%s,%s,", op_name[i.IType.op],
+            reg_name[i.IType.rs],
+            reg_name[i.IType.rt]);
+    pr_displ:
+        print_addr(loc + 4 + ((short)i.IType.imm << 2));
+        bdslot = true;
+        break;
+
+    case OP_COP0:
+        switch (i.RType.rs) {
+        case OP_BCx:
+        case OP_BCy:
+
+            db_printf("bc0%c\t",
+                "ft"[i.RType.rt & COPz_BC_TF_MASK]);
+            goto pr_displ;
+
+        case OP_MT:
+            db_printf("mtc0\t%s,%s",
+                reg_name[i.RType.rt],
+                c0_reg[i.RType.rd]);
+            break;
+
+        case OP_DMT:
+            db_printf("dmtc0\t%s,%s",
+                reg_name[i.RType.rt],
+                c0_reg[i.RType.rd]);
+            break;
+
+        case OP_MF:
+            db_printf("mfc0\t%s,%s",
+                reg_name[i.RType.rt],
+                c0_reg[i.RType.rd]);
+            break;
+
+        case OP_DMF:
+            db_printf("dmfc0\t%s,%s",
+                reg_name[i.RType.rt],
+                c0_reg[i.RType.rd]);
+            break;
+
+        default:
+            db_printf("%s", c0_opname[i.FRType.func]);
+        }
+        break;
+
+    case OP_COP1:
+        switch (i.RType.rs) {
+        case OP_BCx:
+        case OP_BCy:
+            db_printf("bc1%c\t",
+                "ft"[i.RType.rt & COPz_BC_TF_MASK]);
+            goto pr_displ;
+
+        case OP_MT:
+            db_printf("mtc1\t%s,f%d",
+                reg_name[i.RType.rt],
+                i.RType.rd);
+            break;
+
+        case OP_MF:
+            db_printf("mfc1\t%s,f%d",
+                reg_name[i.RType.rt],
+                i.RType.rd);
+            break;
+
+        case OP_CT:
+            db_printf("ctc1\t%s,f%d",
+                reg_name[i.RType.rt],
+                i.RType.rd);
+            break;
+
+        case OP_CF:
+            db_printf("cfc1\t%s,f%d",
+                reg_name[i.RType.rt],
+                i.RType.rd);
+            break;
+
+        default:
+            db_printf("%s.%s\tf%d,f%d,f%d",
+                cop1_name[i.FRType.func],
+                fmt_name[i.FRType.fmt],
+                i.FRType.fd, i.FRType.fs, i.FRType.ft);
+        }
+        break;
+
+    case OP_J:
+    case OP_JAL:
+        db_printf("%s\t", op_name[i.JType.op]);
+        print_addr((loc & 0xFFFFFFFFF0000000) | (i.JType.target << 2));
+        bdslot = true;
+        break;
+
+    case OP_LWC1:
+    case OP_SWC1:
+        db_printf("%s\tf%d,", op_name[i.IType.op],
+            i.IType.rt);
+        goto loadstore;
+
+    case OP_LB:
+    case OP_LH:
+    case OP_LW:
+    case OP_LD:
+    case OP_LBU:
+    case OP_LHU:
+    case OP_LWU:
+    case OP_SB:
+    case OP_SH:
+    case OP_SW:
+    case OP_SD:
+        db_printf("%s\t%s,", op_name[i.IType.op],
+            reg_name[i.IType.rt]);
+    loadstore:
+        db_printf("%d(%s)", (short)i.IType.imm,
+            reg_name[i.IType.rs]);
+        break;
+
+    case OP_ORI:
+    case OP_XORI:
+        if (i.IType.rs == 0) {
+            db_printf("li\t%s,0x%x",
+                reg_name[i.IType.rt],
+                i.IType.imm);
+            break;
+        }
+        /* FALLTHROUGH */
+    case OP_ANDI:
+        db_printf("%s\t%s,%s,0x%x", op_name[i.IType.op],
+            reg_name[i.IType.rt],
+            reg_name[i.IType.rs],
+            i.IType.imm);
+        break;
+
+    case OP_AUI:
+        if (i.IType.rs == 0) {
+            db_printf("lui\t%s,0x%x", reg_name[i.IType.rt],
+                i.IType.imm);
+        } else {
+            db_printf("%s\t%s,%s,%d", op_name[i.IType.op],
+            reg_name[i.IType.rt], reg_name[i.IType.rs],
+            (short)i.IType.imm);
+        }
+        break;
+
+    case OP_ADDIU:
+    case OP_DADDIU:
+        if (i.IType.rs == 0) {
+            db_printf("li\t%s,%d",
+                reg_name[i.IType.rt],
+                (short)i.IType.imm);
+            break;
+        }
+        /* FALLTHROUGH */
+    default:
+        db_printf("%s\t%s,%s,%d", op_name[i.IType.op],
+            reg_name[i.IType.rt],
+            reg_name[i.IType.rs],
+            (short)i.IType.imm);
+    }
+    // db_printf("\n");
+    // if (bdslot) {
+    //     db_printf("   bd: ");
+    //     mips_disassem(loc+4);
+    //     return (loc + 8);
+    // }
+    return (loc + 4);
+}
+
+static void
+print_addr(db_addr_t loc)
+{
+    db_printf("0x%08lx", loc);
+}
+
+static void db_printf(const char* fmt, ...)
+{
+    int cnt;
+    va_list argp;
+    va_start(argp, fmt);
+    if (sprintf_buffer) {
+        cnt = vsnprintf(sprintf_buffer, sprintf_buf_len, fmt, argp);
+        sprintf_buffer += cnt;
+        sprintf_buf_len -= cnt;
+    } else {
+        vprintf(fmt, argp);
+    }
+    va_end(argp);
+}
+
+/*
+ * Disassemble instruction at 'loc'.
+ * Return address of start of next instruction.
+ * Since this function is used by 'examine' and by 'step'
+ * "next instruction" does NOT mean the next instruction to
+ * be executed but the 'linear' next instruction.
+ */
+db_addr_t
+mips_disassem(db_addr_t loc, char *di_buffer, int alt_dis_format)
+{
+    u_int32_t instr;
+
+    if (alt_dis_format) {   // use ARM register names for disassembly
+        reg_name = &alt_arm_reg_name[0];
+    }
+
+    sprintf_buffer = di_buffer;     // quick 'n' dirty printf() vs sprintf()
+    sprintf_buf_len = 39;           // should be passed in
+
+    instr =  *(u_int32_t *)loc;
+    return (db_disasm_insn(instr, loc, false));
+}
diff --git a/libpixelflinger/codeflinger/mips64_disassem.h b/libpixelflinger/codeflinger/mips64_disassem.h
new file mode 100644
index 0000000..c94f04f
--- /dev/null
+++ b/libpixelflinger/codeflinger/mips64_disassem.h
@@ -0,0 +1,56 @@
+/*  $NetBSD: db_disasm.c,v 1.19 2007/02/28 04:21:53 thorpej Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *  The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ralph Campbell.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *  from: @(#)kadb.c    8.1 (Berkeley) 6/10/93
+ */
+
+
+
+#ifndef ANDROID_MIPS_DISASSEM_H
+#define ANDROID_MIPS_DISASSEM_H
+
+#include <sys/types.h>
+
+#if __cplusplus
+extern "C" {
+#endif
+
+/* Prototypes for callable functions */
+
+void mips_disassem(uint32_t *location, char *di_buffer, int alt_fmt);
+
+#if __cplusplus
+}
+#endif
+
+#endif /* !ANDROID_MIPS_DISASSEM_H */
diff --git a/libpixelflinger/codeflinger/mips_disassem.c b/libpixelflinger/codeflinger/mips_disassem.c
new file mode 100644
index 0000000..1fe6806
--- /dev/null
+++ b/libpixelflinger/codeflinger/mips_disassem.c
@@ -0,0 +1,592 @@
+/*  $NetBSD: db_disasm.c,v 1.19 2007/02/28 04:21:53 thorpej Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *  The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ralph Campbell.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *  from: @(#)kadb.c    8.1 (Berkeley) 6/10/93
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <sys/cdefs.h>
+
+#include <sys/types.h>
+#include "mips_opcode.h"
+
+
+// #include <sys/systm.h>
+// #include <sys/param.h>
+
+// #include <machine/reg.h>
+// #include <machine/cpu.h>
+/*#include <machine/param.h>*/
+// #include <machine/db_machdep.h>
+
+// #include <ddb/db_interface.h>
+// #include <ddb/db_output.h>
+// #include <ddb/db_extern.h>
+// #include <ddb/db_sym.h>
+
+#define __unused __attribute__((__unused__))
+
+static char *sprintf_buffer;
+static int sprintf_buf_len;
+
+
+typedef uint32_t db_addr_t;
+static void db_printf(const char* fmt, ...);
+
+static const char * const op_name[64] = {
+/* 0 */ "spec", "bcond","j  ",    "jal",  "beq",  "bne",  "blez", "bgtz",
+/* 8 */ "addi", "addiu","slti", "sltiu","andi", "ori",  "xori", "lui",
+/*16 */ "cop0", "cop1", "cop2", "cop3", "beql", "bnel", "blezl","bgtzl",
+/*24 */ "daddi","daddiu","ldl", "ldr",  "op34", "op35", "op36", "op37",
+/*32 */ "lb ",   "lh ",   "lwl",  "lw ",   "lbu",  "lhu",  "lwr",  "lwu",
+/*40 */ "sb ",   "sh ",   "swl",  "sw ",   "sdl",  "sdr",  "swr",  "cache",
+/*48 */ "ll ",   "lwc1", "lwc2", "lwc3", "lld",  "ldc1", "ldc2", "ld ",
+/*56 */ "sc ",   "swc1", "swc2", "swc3", "scd",  "sdc1", "sdc2", "sd "
+};
+
+static const char * const spec_name[64] = {
+/* 0 */ "sll",  "spec01","srl", "sra",  "sllv", "spec05","srlv","srav",
+/* 8 */ "jr",   "jalr", "movz","movn","syscall","break","spec16","sync",
+/*16 */ "mfhi", "mthi", "mflo", "mtlo", "dsllv","spec25","dsrlv","dsrav",
+/*24 */ "mult", "multu","div",  "divu", "dmult","dmultu","ddiv","ddivu",
+/*32 */ "add",  "addu", "sub",  "subu", "and",  "or ",   "xor",  "nor",
+/*40 */ "spec50","spec51","slt","sltu", "dadd","daddu","dsub","dsubu",
+/*48 */ "tge","tgeu","tlt","tltu","teq","spec65","tne","spec67",
+/*56 */ "dsll","spec71","dsrl","dsra","dsll32","spec75","dsrl32","dsra32"
+};
+
+static const char * const spec2_name[64] = {     /* QED RM4650, R5000, etc. */
+/* 0x00 */ "madd", "maddu", "mul", "spec3", "msub", "msubu", "rsrv6", "rsrv7",
+/* 0x08 */ "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv",
+/* 0x10 */ "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv",
+/* 0x18 */ "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv",
+/* 0x20 */ "clz",  "clo",  "rsrv", "rsrv", "dclz", "dclo", "rsrv", "rsrv",
+/* 0x28 */ "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv",
+/* 0x30 */ "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv", "rsrv",
+/* 0x38 */ "rsrv", "rsrv", "rsrv", "resv", "rsrv", "rsrv", "rsrv", "sdbbp"
+};
+
+static const char * const bcond_name[32] = {
+/* 0 */ "bltz", "bgez", "bltzl", "bgezl", "?", "?", "?", "?",
+/* 8 */ "tgei", "tgeiu", "tlti", "tltiu", "teqi", "?", "tnei", "?",
+/*16 */ "bltzal", "bgezal", "bltzall", "bgezall", "?", "?", "?", "?",
+/*24 */ "?", "?", "?", "?", "?", "?", "?", "?",
+};
+
+static const char * const cop1_name[64] = {
+/* 0 */ "fadd",  "fsub", "fmpy", "fdiv", "fsqrt","fabs", "fmov", "fneg",
+/* 8 */ "fop08","fop09","fop0a","fop0b","fop0c","fop0d","fop0e","fop0f",
+/*16 */ "fop10","fop11","fop12","fop13","fop14","fop15","fop16","fop17",
+/*24 */ "fop18","fop19","fop1a","fop1b","fop1c","fop1d","fop1e","fop1f",
+/*32 */ "fcvts","fcvtd","fcvte","fop23","fcvtw","fop25","fop26","fop27",
+/*40 */ "fop28","fop29","fop2a","fop2b","fop2c","fop2d","fop2e","fop2f",
+/*48 */ "fcmp.f","fcmp.un","fcmp.eq","fcmp.ueq","fcmp.olt","fcmp.ult",
+    "fcmp.ole","fcmp.ule",
+/*56 */ "fcmp.sf","fcmp.ngle","fcmp.seq","fcmp.ngl","fcmp.lt","fcmp.nge",
+    "fcmp.le","fcmp.ngt"
+};
+
+static const char * const fmt_name[16] = {
+    "s",    "d",    "e",    "fmt3",
+    "w",    "fmt5", "fmt6", "fmt7",
+    "fmt8", "fmt9", "fmta", "fmtb",
+    "fmtc", "fmtd", "fmte", "fmtf"
+};
+
+#if defined(__mips_n32) || defined(__mips_n64)
+static char * const reg_name[32] = {
+    "zero", "at",   "v0",   "v1",   "a0",   "a1",   "a2",   "a3",
+    "a4",   "a5",   "a6",   "a7",   "t0",   "t1",   "t2",   "t3",
+    "s0",   "s1",   "s2",   "s3",   "s4",   "s5",   "s6",   "s7",
+    "t8",   "t9",   "k0",   "k1",   "gp",   "sp",   "s8",   "ra"
+};
+#else
+
+static char * alt_arm_reg_name[32] = {  // hacked names for comparison with ARM code
+    "zero", "at",   "r0",   "r1",   "r2",   "r3",   "r4",   "r5",
+    "r6",   "r7",   "r8",   "r9",   "r10",  "r11",  "r12",  "r13",
+    "r14",  "r15",  "at2",  "cmp",  "s4",   "s5",   "s6",   "s7",
+    "t8",   "t9",   "k0",   "k1",   "gp",   "sp",   "s8",   "ra"
+};
+
+static char * mips_reg_name[32] = {
+    "zero", "at",   "v0",   "v1",   "a0",   "a1",   "a2",   "a3",
+    "t0",   "t1",   "t2",   "t3",   "t4",   "t5",   "t6",   "t7",
+    "s0",   "s1",   "s2",   "s3",   "s4",   "s5",   "s6",   "s7",
+    "t8",   "t9",   "k0",   "k1",   "gp",   "sp",   "s8",   "ra"
+};
+
+static char ** reg_name =  &mips_reg_name[0];
+
+#endif /* __mips_n32 || __mips_n64 */
+
+static const char * const c0_opname[64] = {
+    "c0op00","tlbr",  "tlbwi", "c0op03","c0op04","c0op05","tlbwr", "c0op07",
+    "tlbp",  "c0op11","c0op12","c0op13","c0op14","c0op15","c0op16","c0op17",
+    "rfe",   "c0op21","c0op22","c0op23","c0op24","c0op25","c0op26","c0op27",
+    "eret",  "c0op31","c0op32","c0op33","c0op34","c0op35","c0op36","c0op37",
+    "c0op40","c0op41","c0op42","c0op43","c0op44","c0op45","c0op46","c0op47",
+    "c0op50","c0op51","c0op52","c0op53","c0op54","c0op55","c0op56","c0op57",
+    "c0op60","c0op61","c0op62","c0op63","c0op64","c0op65","c0op66","c0op67",
+    "c0op70","c0op71","c0op72","c0op73","c0op74","c0op75","c0op77","c0op77",
+};
+
+static const char * const c0_reg[32] = {
+    "index",    "random",   "tlblo0",  "tlblo1",
+    "context",  "pagemask", "wired",   "cp0r7",
+    "badvaddr", "count",    "tlbhi",   "compare",
+    "status",   "cause",    "epc",     "prid",
+    "config",   "lladdr",   "watchlo", "watchhi",
+    "xcontext", "cp0r21",   "cp0r22",  "debug",
+    "depc",     "perfcnt",  "ecc",     "cacheerr",
+    "taglo",    "taghi",    "errepc",  "desave"
+};
+
+static void print_addr(db_addr_t);
+db_addr_t mips_disassem(db_addr_t loc, char *di_buffer, int alt_dis_format);
+
+
+/*
+ * Disassemble instruction 'insn' nominally at 'loc'.
+ * 'loc' may in fact contain a breakpoint instruction.
+ */
+static db_addr_t
+db_disasm_insn(int insn, db_addr_t loc, bool altfmt __unused)
+{
+    bool bdslot = false;
+    InstFmt i;
+
+    i.word = insn;
+
+    switch (i.JType.op) {
+    case OP_SPECIAL:
+        if (i.word == 0) {
+            db_printf("nop");
+            break;
+        }
+        if (i.word == 0x0080) {
+            db_printf("NIY");
+            break;
+        }
+        if (i.word == 0x00c0) {
+            db_printf("NOT IMPL");
+            break;
+        }
+        /* Special cases --------------------------------------------------
+         * "addu" is a "move" only in 32-bit mode.  What's the correct
+         * answer - never decode addu/daddu as "move"?
+         */
+        if ( (i.RType.func == OP_ADDU && i.RType.rt == 0)  ||
+             (i.RType.func == OP_OR   && i.RType.rt == 0) ) {
+            db_printf("move\t%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rs]);
+            break;
+        }
+        // mips32r2, rotr & rotrv
+        if (i.RType.func == OP_SRL && (i.RType.rs & 1) == 1) {
+            db_printf("rotr\t%s,%s,%d", reg_name[i.RType.rd],
+                reg_name[i.RType.rt], i.RType.shamt);
+            break;
+        }
+        if (i.RType.func == OP_SRLV && (i.RType.shamt & 1) == 1) {
+            db_printf("rotrv\t%s,%s,%s", reg_name[i.RType.rd],
+                reg_name[i.RType.rt], reg_name[i.RType.rs]);
+            break;
+        }
+
+
+        db_printf("%s", spec_name[i.RType.func]);
+        switch (i.RType.func) {
+        case OP_SLL:
+        case OP_SRL:
+        case OP_SRA:
+        case OP_DSLL:
+
+        case OP_DSRL:
+        case OP_DSRA:
+        case OP_DSLL32:
+        case OP_DSRL32:
+        case OP_DSRA32:
+            db_printf("\t%s,%s,%d",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt],
+                i.RType.shamt);
+            break;
+
+        case OP_SLLV:
+        case OP_SRLV:
+        case OP_SRAV:
+        case OP_DSLLV:
+        case OP_DSRLV:
+        case OP_DSRAV:
+            db_printf("\t%s,%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt],
+                reg_name[i.RType.rs]);
+            break;
+
+        case OP_MFHI:
+        case OP_MFLO:
+            db_printf("\t%s", reg_name[i.RType.rd]);
+            break;
+
+        case OP_JR:
+        case OP_JALR:
+            db_printf("\t%s", reg_name[i.RType.rs]);
+            bdslot = true;
+            break;
+        case OP_MTLO:
+        case OP_MTHI:
+            db_printf("\t%s", reg_name[i.RType.rs]);
+            break;
+
+        case OP_MULT:
+        case OP_MULTU:
+        case OP_DMULT:
+        case OP_DMULTU:
+        case OP_DIV:
+        case OP_DIVU:
+        case OP_DDIV:
+        case OP_DDIVU:
+            db_printf("\t%s,%s",
+                reg_name[i.RType.rs],
+                reg_name[i.RType.rt]);
+            break;
+
+
+        case OP_SYSCALL:
+        case OP_SYNC:
+            break;
+
+        case OP_BREAK:
+            db_printf("\t%d", (i.RType.rs << 5) | i.RType.rt);
+            break;
+
+        default:
+            db_printf("\t%s,%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rs],
+                reg_name[i.RType.rt]);
+        }
+        break;
+
+    case OP_SPECIAL2:
+        if (i.RType.func == OP_MUL)
+            db_printf("%s\t%s,%s,%s",
+                spec2_name[i.RType.func & 0x3f],
+                    reg_name[i.RType.rd],
+                    reg_name[i.RType.rs],
+                    reg_name[i.RType.rt]);
+        else
+            db_printf("%s\t%s,%s",
+                spec2_name[i.RType.func & 0x3f],
+                    reg_name[i.RType.rs],
+                    reg_name[i.RType.rt]);
+
+        break;
+
+    case OP_SPECIAL3:
+        if (i.RType.func == OP_EXT)
+            db_printf("ext\t%s,%s,%d,%d",
+                    reg_name[i.RType.rt],
+                    reg_name[i.RType.rs],
+                    i.RType.shamt,
+                    i.RType.rd+1);
+        else if (i.RType.func == OP_INS)
+            db_printf("ins\t%s,%s,%d,%d",
+                    reg_name[i.RType.rt],
+                    reg_name[i.RType.rs],
+                    i.RType.shamt,
+                    i.RType.rd-i.RType.shamt+1);
+        else if (i.RType.func == OP_BSHFL && i.RType.shamt == OP_WSBH)
+            db_printf("wsbh\t%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt]);
+        else if (i.RType.func == OP_BSHFL && i.RType.shamt == OP_SEB)
+            db_printf("seb\t%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt]);
+        else if (i.RType.func == OP_BSHFL && i.RType.shamt == OP_SEH)
+            db_printf("seh\t%s,%s",
+                reg_name[i.RType.rd],
+                reg_name[i.RType.rt]);
+        else
+            db_printf("Unknown");
+        break;
+
+    case OP_BCOND:
+        db_printf("%s\t%s,", bcond_name[i.IType.rt],
+            reg_name[i.IType.rs]);
+        goto pr_displ;
+
+    case OP_BLEZ:
+    case OP_BLEZL:
+    case OP_BGTZ:
+    case OP_BGTZL:
+        db_printf("%s\t%s,", op_name[i.IType.op],
+            reg_name[i.IType.rs]);
+        goto pr_displ;
+
+    case OP_BEQ:
+    case OP_BEQL:
+        if (i.IType.rs == 0 && i.IType.rt == 0) {
+            db_printf("b  \t");
+            goto pr_displ;
+        }
+        /* FALLTHROUGH */
+    case OP_BNE:
+    case OP_BNEL:
+        db_printf("%s\t%s,%s,", op_name[i.IType.op],
+            reg_name[i.IType.rs],
+            reg_name[i.IType.rt]);
+    pr_displ:
+        print_addr(loc + 4 + ((short)i.IType.imm << 2));
+        bdslot = true;
+        break;
+
+    case OP_COP0:
+        switch (i.RType.rs) {
+        case OP_BCx:
+        case OP_BCy:
+
+            db_printf("bc0%c\t",
+                "ft"[i.RType.rt & COPz_BC_TF_MASK]);
+            goto pr_displ;
+
+        case OP_MT:
+            db_printf("mtc0\t%s,%s",
+                reg_name[i.RType.rt],
+                c0_reg[i.RType.rd]);
+            break;
+
+        case OP_DMT:
+            db_printf("dmtc0\t%s,%s",
+                reg_name[i.RType.rt],
+                c0_reg[i.RType.rd]);
+            break;
+
+        case OP_MF:
+            db_printf("mfc0\t%s,%s",
+                reg_name[i.RType.rt],
+                c0_reg[i.RType.rd]);
+            break;
+
+        case OP_DMF:
+            db_printf("dmfc0\t%s,%s",
+                reg_name[i.RType.rt],
+                c0_reg[i.RType.rd]);
+            break;
+
+        default:
+            db_printf("%s", c0_opname[i.FRType.func]);
+        }
+        break;
+
+    case OP_COP1:
+        switch (i.RType.rs) {
+        case OP_BCx:
+        case OP_BCy:
+            db_printf("bc1%c\t",
+                "ft"[i.RType.rt & COPz_BC_TF_MASK]);
+            goto pr_displ;
+
+        case OP_MT:
+            db_printf("mtc1\t%s,f%d",
+                reg_name[i.RType.rt],
+                i.RType.rd);
+            break;
+
+        case OP_MF:
+            db_printf("mfc1\t%s,f%d",
+                reg_name[i.RType.rt],
+                i.RType.rd);
+            break;
+
+        case OP_CT:
+            db_printf("ctc1\t%s,f%d",
+                reg_name[i.RType.rt],
+                i.RType.rd);
+            break;
+
+        case OP_CF:
+            db_printf("cfc1\t%s,f%d",
+                reg_name[i.RType.rt],
+                i.RType.rd);
+            break;
+
+        default:
+            db_printf("%s.%s\tf%d,f%d,f%d",
+                cop1_name[i.FRType.func],
+                fmt_name[i.FRType.fmt],
+                i.FRType.fd, i.FRType.fs, i.FRType.ft);
+        }
+        break;
+
+    case OP_J:
+    case OP_JAL:
+        db_printf("%s\t", op_name[i.JType.op]);
+        print_addr((loc & 0xF0000000) | (i.JType.target << 2));
+        bdslot = true;
+        break;
+
+    case OP_LWC1:
+    case OP_SWC1:
+        db_printf("%s\tf%d,", op_name[i.IType.op],
+            i.IType.rt);
+        goto loadstore;
+
+    case OP_LB:
+    case OP_LH:
+    case OP_LW:
+    case OP_LD:
+    case OP_LBU:
+    case OP_LHU:
+    case OP_LWU:
+    case OP_SB:
+    case OP_SH:
+    case OP_SW:
+    case OP_SD:
+        db_printf("%s\t%s,", op_name[i.IType.op],
+            reg_name[i.IType.rt]);
+    loadstore:
+        db_printf("%d(%s)", (short)i.IType.imm,
+            reg_name[i.IType.rs]);
+        break;
+
+    case OP_ORI:
+    case OP_XORI:
+        if (i.IType.rs == 0) {
+            db_printf("li\t%s,0x%x",
+                reg_name[i.IType.rt],
+                i.IType.imm);
+            break;
+        }
+        /* FALLTHROUGH */
+    case OP_ANDI:
+        db_printf("%s\t%s,%s,0x%x", op_name[i.IType.op],
+            reg_name[i.IType.rt],
+            reg_name[i.IType.rs],
+            i.IType.imm);
+        break;
+
+    case OP_LUI:
+        db_printf("%s\t%s,0x%x", op_name[i.IType.op],
+            reg_name[i.IType.rt],
+            i.IType.imm);
+        break;
+
+    case OP_CACHE:
+        db_printf("%s\t0x%x,0x%x(%s)",
+            op_name[i.IType.op],
+            i.IType.rt,
+            i.IType.imm,
+            reg_name[i.IType.rs]);
+        break;
+
+    case OP_ADDI:
+    case OP_DADDI:
+    case OP_ADDIU:
+    case OP_DADDIU:
+        if (i.IType.rs == 0) {
+            db_printf("li\t%s,%d",
+                reg_name[i.IType.rt],
+                (short)i.IType.imm);
+            break;
+        }
+        /* FALLTHROUGH */
+    default:
+        db_printf("%s\t%s,%s,%d", op_name[i.IType.op],
+            reg_name[i.IType.rt],
+            reg_name[i.IType.rs],
+            (short)i.IType.imm);
+    }
+    // db_printf("\n");
+    // if (bdslot) {
+    //     db_printf("   bd: ");
+    //     mips_disassem(loc+4);
+    //     return (loc + 8);
+    // }
+    return (loc + 4);
+}
+
+static void
+print_addr(db_addr_t loc)
+{
+    db_printf("0x%08x", loc);
+}
+
+
+
+static void db_printf(const char* fmt, ...)
+{
+    int cnt;
+    va_list argp;
+    va_start(argp, fmt);
+    if (sprintf_buffer) {
+        cnt = vsnprintf(sprintf_buffer, sprintf_buf_len, fmt, argp);
+        sprintf_buffer += cnt;
+        sprintf_buf_len -= cnt;
+    } else {
+        vprintf(fmt, argp);
+    }
+    va_end(argp);
+}
+
+
+/*
+ * Disassemble instruction at 'loc'.
+ * Return address of start of next instruction.
+ * Since this function is used by 'examine' and by 'step'
+ * "next instruction" does NOT mean the next instruction to
+ * be executed but the 'linear' next instruction.
+ */
+db_addr_t
+mips_disassem(db_addr_t loc, char *di_buffer, int alt_dis_format)
+{
+    u_int32_t instr;
+
+    if (alt_dis_format) {   // use ARM register names for disassembly
+        reg_name = &alt_arm_reg_name[0];
+    }
+
+    sprintf_buffer = di_buffer;     // quick 'n' dirty printf() vs sprintf()
+    sprintf_buf_len = 39;           // should be passed in
+
+    instr =  *(u_int32_t *)loc;
+    return (db_disasm_insn(instr, loc, false));
+}
+
diff --git a/libpixelflinger/codeflinger/mips_disassem.h b/libpixelflinger/codeflinger/mips_disassem.h
new file mode 100644
index 0000000..2d5b7f5
--- /dev/null
+++ b/libpixelflinger/codeflinger/mips_disassem.h
@@ -0,0 +1,66 @@
+/*  $NetBSD: db_disasm.c,v 1.19 2007/02/28 04:21:53 thorpej Exp $   */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *  The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ralph Campbell.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *  from: @(#)kadb.c    8.1 (Berkeley) 6/10/93
+ */
+
+
+
+#ifndef ANDROID_MIPS_DISASSEM_H
+#define ANDROID_MIPS_DISASSEM_H
+
+#include <sys/types.h>
+
+#if __cplusplus
+extern "C" {
+#endif
+
+
+// could add an interface like this, but I have not
+// typedef struct {
+//  u_int   (*di_readword)(u_int);
+//  void    (*di_printaddr)(u_int);
+//  void    (*di_printf)(const char *, ...);
+// } disasm_interface_t;
+
+/* Prototypes for callable functions */
+
+// u_int disasm(const disasm_interface_t *, u_int, int);
+
+void mips_disassem(uint32_t *location, char *di_buffer, int alt_fmt);
+
+#if __cplusplus
+}
+#endif
+
+#endif /* !ANDROID_MIPS_DISASSEM_H */
diff --git a/libpixelflinger/codeflinger/mips_opcode.h b/libpixelflinger/codeflinger/mips_opcode.h
new file mode 100644
index 0000000..45bb19e
--- /dev/null
+++ b/libpixelflinger/codeflinger/mips_opcode.h
@@ -0,0 +1,410 @@
+/*  $NetBSD: mips_opcode.h,v 1.12 2005/12/11 12:18:09 christos Exp $    */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *  The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ralph Campbell.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *  @(#)mips_opcode.h   8.1 (Berkeley) 6/10/93
+ */
+
+/*
+ * Define the instruction formats and opcode values for the
+ * MIPS instruction set.
+ */
+
+#include <endian.h>
+
+/*
+ * Define the instruction formats.
+ */
+typedef union {
+    unsigned word;
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+    struct {
+        unsigned imm: 16;
+        unsigned rt: 5;
+        unsigned rs: 5;
+        unsigned op: 6;
+    } IType;
+
+    struct {
+        unsigned target: 26;
+        unsigned op: 6;
+    } JType;
+
+    struct {
+        unsigned func: 6;
+        unsigned shamt: 5;
+        unsigned rd: 5;
+        unsigned rt: 5;
+        unsigned rs: 5;
+        unsigned op: 6;
+    } RType;
+
+    struct {
+        unsigned func: 6;
+        unsigned fd: 5;
+        unsigned fs: 5;
+        unsigned ft: 5;
+        unsigned fmt: 4;
+        unsigned : 1;       /* always '1' */
+        unsigned op: 6;     /* always '0x11' */
+    } FRType;
+#endif
+#if BYTE_ORDER == BIG_ENDIAN
+    struct {
+        unsigned op: 6;
+        unsigned rs: 5;
+        unsigned rt: 5;
+        unsigned imm: 16;
+    } IType;
+
+    struct {
+        unsigned op: 6;
+        unsigned target: 26;
+    } JType;
+
+    struct {
+        unsigned op: 6;
+        unsigned rs: 5;
+        unsigned rt: 5;
+        unsigned rd: 5;
+        unsigned shamt: 5;
+        unsigned func: 6;
+    } RType;
+
+    struct {
+        unsigned op: 6;     /* always '0x11' */
+        unsigned : 1;       /* always '1' */
+        unsigned fmt: 4;
+        unsigned ft: 5;
+        unsigned fs: 5;
+        unsigned fd: 5;
+        unsigned func: 6;
+    } FRType;
+#endif
+} InstFmt;
+
+/*
+ * Values for the 'op' field.
+ */
+#define OP_SPECIAL  000
+#define OP_BCOND    001
+#define OP_J        002
+#define OP_JAL      003
+#define OP_BEQ      004
+#define OP_BNE      005
+#define OP_BLEZ     006
+#define OP_BGTZ     007
+
+#if __mips_isa_rev < 6
+#define OP_ADDI     010
+#else
+#define OP_POP10    010
+#endif
+
+#define OP_ADDIU    011
+#define OP_SLTI     012
+#define OP_SLTIU    013
+#define OP_ANDI     014
+#define OP_ORI      015
+#define OP_XORI     016
+
+#if __mips_isa_rev < 6
+#define OP_LUI      017
+#else
+#define OP_AUI      017
+#endif
+
+#define OP_COP0     020
+#define OP_COP1     021
+#define OP_COP2     022
+
+#if __mips_isa_rev < 6
+#define OP_COP3     023
+#define OP_BEQL     024
+#define OP_BNEL     025
+#define OP_BLEZL    026
+#define OP_BGTZL    027
+#define OP_DADDI    030
+#else
+#define OP_POP26    026
+#define OP_POP27    027
+#define OP_POP30    030
+#endif
+
+#define OP_DADDIU   031
+
+#if __mips_isa_rev < 6
+#define OP_LDL      032
+#define OP_LDR      033
+#define OP_SPECIAL2 034
+#else
+#define OP_DAUI     035
+#endif
+
+#define OP_SPECIAL3 037
+
+#define OP_LB       040
+#define OP_LH       041
+
+#if __mips_isa_rev < 6
+#define OP_LWL      042
+#endif
+
+#define OP_LW       043
+#define OP_LBU      044
+#define OP_LHU      045
+#define OP_LWR      046
+#define OP_LHU      045
+
+#if __mips_isa_rev < 6
+#define OP_LWR      046
+#endif
+
+#define OP_LWU      047
+
+#define OP_SB       050
+#define OP_SH       051
+
+#if __mips_isa_rev < 6
+#define OP_SWL      052
+#endif
+
+#define OP_SW       053
+
+#if __mips_isa_rev < 6
+#define OP_SDL      054
+#define OP_SDR      055
+#define OP_SWR      056
+#define OP_CACHE    057
+#define OP_LL       060
+#define OP_LWC0     OP_LL
+#define OP_LWC1     061
+#define OP_LWC2     062
+#define OP_LWC3     063
+#define OP_LLD      064
+#else
+#define OP_LWC1     061
+#define OP_BC       062
+#endif
+
+#define OP_LDC1     065
+#define OP_LD       067
+
+#if __mips_isa_rev < 6
+#define OP_SC       070
+#define OP_SWC0     OP_SC
+#endif
+
+#define OP_SWC1     071
+
+#if __mips_isa_rev < 6
+#define OP_SWC2     072
+#define OP_SWC3     073
+#define OP_SCD      074
+#else
+#define OP_BALC     072
+#endif
+
+#define OP_SDC1     075
+#define OP_SD       077
+
+/*
+ * Values for the 'func' field when 'op' == OP_SPECIAL.
+ */
+#define OP_SLL      000
+#define OP_SRL      002
+#define OP_SRA      003
+#define OP_SLLV     004
+#define OP_SRLV     006
+#define OP_SRAV     007
+
+#if __mips_isa_rev < 6
+#define OP_JR       010
+#endif
+
+#define OP_JALR     011
+#define OP_SYSCALL  014
+#define OP_BREAK    015
+#define OP_SYNC     017
+
+#if __mips_isa_rev < 6
+#define OP_MFHI     020
+#define OP_MTHI     021
+#define OP_MFLO     022
+#define OP_MTLO     023
+#else
+#define OP_CLZ      020
+#define OP_CLO      021
+#define OP_DCLZ     022
+#define OP_DCLO     023
+#endif
+
+#define OP_DSLLV    024
+#define OP_DSRLV    026
+#define OP_DSRAV    027
+
+#if __mips_isa_rev < 6
+#define OP_MULT     030
+#define OP_MULTU    031
+#define OP_DIV      032
+#define OP_DIVU     033
+#define OP_DMULT    034
+#define OP_DMULTU   035
+#define OP_DDIV     036
+#define OP_DDIVU    037
+#else
+#define OP_SOP30    030
+#define OP_SOP31    031
+#define OP_SOP32    032
+#define OP_SOP33    033
+#define OP_SOP34    034
+#define OP_SOP35    035
+#define OP_SOP36    036
+#define OP_SOP37    037
+#endif
+
+#define OP_ADD      040
+#define OP_ADDU     041
+#define OP_SUB      042
+#define OP_SUBU     043
+#define OP_AND      044
+#define OP_OR       045
+#define OP_XOR      046
+#define OP_NOR      047
+
+#define OP_SLT      052
+#define OP_SLTU     053
+#define OP_DADD     054
+#define OP_DADDU    055
+#define OP_DSUB     056
+#define OP_DSUBU    057
+
+#define OP_TGE      060
+#define OP_TGEU     061
+#define OP_TLT      062
+#define OP_TLTU     063
+#define OP_TEQ      064
+#define OP_TNE      066
+
+#define OP_DSLL     070
+#define OP_DSRL     072
+#define OP_DSRA     073
+#define OP_DSLL32   074
+#define OP_DSRL32   076
+#define OP_DSRA32   077
+
+#if __mips_isa_rev < 6
+/*
+ * Values for the 'func' field when 'op' == OP_SPECIAL2.
+ * OP_SPECIAL2 opcodes are removed in mips32r6
+ */
+#define OP_MAD      000     /* QED */
+#define OP_MADU     001     /* QED */
+#define OP_MUL      002     /* QED */
+#endif
+
+/*
+ * Values for the 'func' field when 'op' == OP_SPECIAL3.
+ */
+#define OP_EXT      000
+#define OP_DEXTM    001
+#define OP_DEXTU    002
+#define OP_DEXT     003
+#define OP_INS      004
+#define OP_DINSM    005
+#define OP_DINSU    006
+#define OP_DINS     007
+#define OP_BSHFL    040
+#define OP_RDHWR    073
+
+/*
+ * Values for the 'shamt' field when OP_SPECIAL3 && func OP_BSHFL.
+ */
+
+#define OP_WSBH     002
+#define OP_SEB      020
+#define OP_SEH      030
+
+#if __mips_isa_rev == 6
+/*
+ * Values for the 'shamt' field when OP_SOP30.
+ */
+#define OP_MUL      002
+#define OP_MUH      003
+#endif
+
+/*
+ * Values for the 'func' field when 'op' == OP_BCOND.
+ */
+#define OP_BLTZ     000
+#define OP_BGEZ     001
+
+#if __mips_isa_rev < 6
+#define OP_BLTZL    002
+#define OP_BGEZL    003
+#define OP_TGEI     010
+#define OP_TGEIU    011
+#define OP_TLTI     012
+#define OP_TLTIU    013
+#define OP_TEQI     014
+#define OP_TNEI     016
+#define OP_BLTZAL   020
+#define OP_BGEZAL   021
+#define OP_BLTZALL  022
+#define OP_BGEZALL  023
+#else
+#define OP_NAL      020
+#define OP_BAL      021
+#endif
+
+/*
+ * Values for the 'rs' field when 'op' == OP_COPz.
+ */
+#define OP_MF       000
+#define OP_DMF      001
+#define OP_MT       004
+#define OP_DMT      005
+#define OP_BCx      010
+#define OP_BCy      014
+#define OP_CF       002
+#define OP_CT       006
+
+/*
+ * Values for the 'rt' field when 'op' == OP_COPz.
+ */
+#define COPz_BC_TF_MASK 0x01
+#define COPz_BC_TRUE    0x01
+#define COPz_BC_FALSE   0x00
+#define COPz_BCL_TF_MASK    0x02
+#define COPz_BCL_TRUE   0x02
+#define COPz_BCL_FALSE  0x00
diff --git a/libpixelflinger/codeflinger/texturing.cpp b/libpixelflinger/codeflinger/texturing.cpp
new file mode 100644
index 0000000..e6997bd
--- /dev/null
+++ b/libpixelflinger/codeflinger/texturing.cpp
@@ -0,0 +1,1261 @@
+/* libs/pixelflinger/codeflinger/texturing.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "pixelflinger-code"
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <log/log.h>
+
+#include "GGLAssembler.h"
+
+namespace android {
+
+// ---------------------------------------------------------------------------
+
+// iterators are initialized like this:
+// (intToFixedCenter(x) * dx)>>16 + x0
+// ((x<<16 + 0x8000) * dx)>>16 + x0
+// ((x<<16)*dx + (0x8000*dx))>>16 + x0
+// ( (x*dx) + dx>>1 ) + x0
+// (x*dx) + (dx>>1 + x0)
+
+void GGLAssembler::init_iterated_color(fragment_parts_t& parts, const reg_t& x)
+{
+    context_t const* c = mBuilderContext.c;
+
+    if (mSmooth) {
+        // NOTE: we could take this case in the mDithering + !mSmooth case,
+        // but this would use up to 4 more registers for the color components
+        // for only a little added quality.
+        // Currently, this causes the system to run out of registers in
+        // some case (see issue #719496)
+
+        comment("compute initial iterated color (smooth and/or dither case)");
+
+        parts.iterated_packed = 0;
+        parts.packed = 0;
+
+        // 0x1: color component
+        // 0x2: iterators
+        const int optReload = mOptLevel >> 1;
+        if (optReload >= 3)         parts.reload = 0; // reload nothing
+        else if (optReload == 2)    parts.reload = 2; // reload iterators
+        else if (optReload == 1)    parts.reload = 1; // reload colors
+        else if (optReload <= 0)    parts.reload = 3; // reload both
+
+        if (!mSmooth) {
+            // we're not smoothing (just dithering), we never have to 
+            // reload the iterators
+            parts.reload &= ~2;
+        }
+
+        Scratch scratches(registerFile());
+        const int t0 = (parts.reload & 1) ? scratches.obtain() : 0;
+        const int t1 = (parts.reload & 2) ? scratches.obtain() : 0;
+        for (int i=0 ; i<4 ; i++) {
+            if (!mInfo[i].iterated)
+                continue;            
+            
+            // this component exists in the destination and is not replaced
+            // by a texture unit.
+            const int c = (parts.reload & 1) ? t0 : obtainReg();              
+            if (i==0) CONTEXT_LOAD(c, iterators.ydady);
+            if (i==1) CONTEXT_LOAD(c, iterators.ydrdy);
+            if (i==2) CONTEXT_LOAD(c, iterators.ydgdy);
+            if (i==3) CONTEXT_LOAD(c, iterators.ydbdy);
+            parts.argb[i].reg = c;
+
+            if (mInfo[i].smooth) {
+                parts.argb_dx[i].reg = (parts.reload & 2) ? t1 : obtainReg();
+                const int dvdx = parts.argb_dx[i].reg;
+                CONTEXT_LOAD(dvdx, generated_vars.argb[i].dx);
+                MLA(AL, 0, c, x.reg, dvdx, c);
+                
+                // adjust the color iterator to make sure it won't overflow
+                if (!mAA) {
+                    // this is not needed when we're using anti-aliasing
+                    // because we will (have to) clamp the components
+                    // anyway.
+                    int end = scratches.obtain();
+                    MOV(AL, 0, end, reg_imm(parts.count.reg, LSR, 16));
+                    MLA(AL, 1, end, dvdx, end, c);
+                    SUB(MI, 0, c, c, end);
+                    BIC(AL, 0, c, c, reg_imm(c, ASR, 31)); 
+                    scratches.recycle(end);
+                }
+            }
+            
+            if (parts.reload & 1) {
+                CONTEXT_STORE(c, generated_vars.argb[i].c);
+            }
+        }
+    } else {
+        // We're not smoothed, so we can 
+        // just use a packed version of the color and extract the
+        // components as needed (or not at all if we don't blend)
+
+        // figure out if we need the iterated color
+        int load = 0;
+        for (int i=0 ; i<4 ; i++) {
+            component_info_t& info = mInfo[i];
+            if ((info.inDest || info.needed) && !info.replaced)
+                load |= 1;
+        }
+        
+        parts.iterated_packed = 1;
+        parts.packed = (!mTextureMachine.mask && !mBlending
+                && !mFog && !mDithering);
+        parts.reload = 0;
+        if (load || parts.packed) {
+            if (mBlending || mDithering || mInfo[GGLFormat::ALPHA].needed) {
+                comment("load initial iterated color (8888 packed)");
+                parts.iterated.setTo(obtainReg(),
+                        &(c->formats[GGL_PIXEL_FORMAT_RGBA_8888]));
+                CONTEXT_LOAD(parts.iterated.reg, packed8888);
+            } else {
+                comment("load initial iterated color (dest format packed)");
+
+                parts.iterated.setTo(obtainReg(), &mCbFormat);
+
+                // pre-mask the iterated color
+                const int bits = parts.iterated.size();
+                const uint32_t size = ((bits>=32) ? 0 : (1LU << bits)) - 1;
+                uint32_t mask = 0;
+                if (mMasking) {
+                    for (int i=0 ; i<4 ; i++) {
+                        const int component_mask = 1<<i;
+                        const int h = parts.iterated.format.c[i].h;
+                        const int l = parts.iterated.format.c[i].l;
+                        if (h && (!(mMasking & component_mask))) {
+                            mask |= ((1<<(h-l))-1) << l;
+                        }
+                    }
+                }
+
+                if (mMasking && ((mask & size)==0)) {
+                    // none of the components are present in the mask
+                } else {
+                    CONTEXT_LOAD(parts.iterated.reg, packed);
+                    if (mCbFormat.size == 1) {
+                        AND(AL, 0, parts.iterated.reg,
+                                parts.iterated.reg, imm(0xFF));
+                    } else if (mCbFormat.size == 2) {
+                        MOV(AL, 0, parts.iterated.reg,
+                                reg_imm(parts.iterated.reg, LSR, 16));
+                    }
+                }
+
+                // pre-mask the iterated color
+                if (mMasking) {
+                    build_and_immediate(parts.iterated.reg, parts.iterated.reg,
+                            mask, bits);
+                }
+            }
+        }
+    }
+}
+
+void GGLAssembler::build_iterated_color(
+        component_t& fragment,
+        const fragment_parts_t& parts,
+        int component,
+        Scratch& regs)
+{
+    fragment.setTo( regs.obtain(), 0, 32, CORRUPTIBLE); 
+
+    if (!mInfo[component].iterated)
+        return;
+
+    if (parts.iterated_packed) {
+        // iterated colors are packed, extract the one we need
+        extract(fragment, parts.iterated, component);
+    } else {
+        fragment.h = GGL_COLOR_BITS;
+        fragment.l = GGL_COLOR_BITS - 8;
+        fragment.flags |= CLEAR_LO;
+        // iterated colors are held in their own register,
+        // (smooth and/or dithering case)
+        if (parts.reload==3) {
+            // this implies mSmooth
+            Scratch scratches(registerFile());
+            int dx = scratches.obtain();
+            CONTEXT_LOAD(fragment.reg, generated_vars.argb[component].c);
+            CONTEXT_LOAD(dx, generated_vars.argb[component].dx);
+            ADD(AL, 0, dx, fragment.reg, dx);
+            CONTEXT_STORE(dx, generated_vars.argb[component].c);
+        } else if (parts.reload & 1) {
+            CONTEXT_LOAD(fragment.reg, generated_vars.argb[component].c);
+        } else {
+            // we don't reload, so simply rename the register and mark as
+            // non CORRUPTIBLE so that the texture env or blending code
+            // won't modify this (renamed) register
+            regs.recycle(fragment.reg);
+            fragment.reg = parts.argb[component].reg;
+            fragment.flags &= ~CORRUPTIBLE;
+        }
+        if (mInfo[component].smooth && mAA) {
+            // when using smooth shading AND anti-aliasing, we need to clamp
+            // the iterators because there is always an extra pixel on the
+            // edges, which most of the time will cause an overflow
+            // (since technically its outside of the domain).
+            BIC(AL, 0, fragment.reg, fragment.reg,
+                    reg_imm(fragment.reg, ASR, 31));
+            component_sat(fragment);
+        }
+    }
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::decodeLogicOpNeeds(const needs_t& needs)
+{
+    // gather some informations about the components we need to process...
+    const int opcode = GGL_READ_NEEDS(LOGIC_OP, needs.n) | GGL_CLEAR;
+    switch(opcode) {
+    case GGL_COPY:
+        mLogicOp = 0;
+        break;
+    case GGL_CLEAR:
+    case GGL_SET:
+        mLogicOp = LOGIC_OP;
+        break;
+    case GGL_AND:
+    case GGL_AND_REVERSE:
+    case GGL_AND_INVERTED:
+    case GGL_XOR:
+    case GGL_OR:
+    case GGL_NOR:
+    case GGL_EQUIV:
+    case GGL_OR_REVERSE:
+    case GGL_OR_INVERTED:
+    case GGL_NAND:
+        mLogicOp = LOGIC_OP|LOGIC_OP_SRC|LOGIC_OP_DST;
+        break;
+    case GGL_NOOP:
+    case GGL_INVERT:
+        mLogicOp = LOGIC_OP|LOGIC_OP_DST;
+        break;        
+    case GGL_COPY_INVERTED:
+        mLogicOp = LOGIC_OP|LOGIC_OP_SRC;
+        break;
+    };        
+}
+
+void GGLAssembler::decodeTMUNeeds(const needs_t& needs, context_t const* c)
+{
+    uint8_t replaced=0;
+    mTextureMachine.mask = 0;
+    mTextureMachine.activeUnits = 0;
+    for (int i=GGL_TEXTURE_UNIT_COUNT-1 ; i>=0 ; i--) {
+        texture_unit_t& tmu = mTextureMachine.tmu[i];
+        if (replaced == 0xF) {
+            // all components are replaced, skip this TMU.
+            tmu.format_idx = 0;
+            tmu.mask = 0;
+            tmu.replaced = replaced;
+            continue;
+        }
+        tmu.format_idx = GGL_READ_NEEDS(T_FORMAT, needs.t[i]);
+        tmu.format = c->formats[tmu.format_idx];
+        tmu.bits = tmu.format.size*8;
+        tmu.swrap = GGL_READ_NEEDS(T_S_WRAP, needs.t[i]);
+        tmu.twrap = GGL_READ_NEEDS(T_T_WRAP, needs.t[i]);
+        tmu.env = ggl_needs_to_env(GGL_READ_NEEDS(T_ENV, needs.t[i]));
+        tmu.pot = GGL_READ_NEEDS(T_POT, needs.t[i]);
+        tmu.linear = GGL_READ_NEEDS(T_LINEAR, needs.t[i])
+                && tmu.format.size!=3; // XXX: only 8, 16 and 32 modes for now
+
+        // 5551 linear filtering is not supported
+        if (tmu.format_idx == GGL_PIXEL_FORMAT_RGBA_5551)
+            tmu.linear = 0;
+        
+        tmu.mask = 0;
+        tmu.replaced = replaced;
+
+        if (tmu.format_idx) {
+            mTextureMachine.activeUnits++;
+            if (tmu.format.c[0].h)    tmu.mask |= 0x1;
+            if (tmu.format.c[1].h)    tmu.mask |= 0x2;
+            if (tmu.format.c[2].h)    tmu.mask |= 0x4;
+            if (tmu.format.c[3].h)    tmu.mask |= 0x8;
+            if (tmu.env == GGL_REPLACE) {
+                replaced |= tmu.mask;
+            } else if (tmu.env == GGL_DECAL) {
+                if (!tmu.format.c[GGLFormat::ALPHA].h) {
+                    // if we don't have alpha, decal does nothing
+                    tmu.mask = 0;
+                } else {
+                    // decal always ignores At
+                    tmu.mask &= ~(1<<GGLFormat::ALPHA);
+                }
+            }
+        }
+        mTextureMachine.mask |= tmu.mask;
+        //printf("%d: mask=%08lx, replaced=%08lx\n",
+        //    i, int(tmu.mask), int(tmu.replaced));
+    }
+    mTextureMachine.replaced = replaced;
+    mTextureMachine.directTexture = 0;
+    //printf("replaced=%08lx\n", mTextureMachine.replaced);
+}
+
+
+void GGLAssembler::init_textures(
+        tex_coord_t* coords,
+        const reg_t& x, const reg_t& y)
+{
+    const needs_t& needs = mBuilderContext.needs;
+    int Rx = x.reg;
+    int Ry = y.reg;
+
+    if (mTextureMachine.mask) {
+        comment("compute texture coordinates");
+    }
+
+    // init texture coordinates for each tmu
+    const int cb_format_idx = GGL_READ_NEEDS(CB_FORMAT, needs.n);
+    const bool multiTexture = mTextureMachine.activeUnits > 1;
+    for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT; i++) {
+        const texture_unit_t& tmu = mTextureMachine.tmu[i];
+        if (tmu.format_idx == 0)
+            continue;
+        if ((tmu.swrap == GGL_NEEDS_WRAP_11) &&
+            (tmu.twrap == GGL_NEEDS_WRAP_11)) 
+        {
+            // 1:1 texture
+            pointer_t& txPtr = coords[i].ptr;
+            txPtr.setTo(obtainReg(), tmu.bits);
+            CONTEXT_LOAD(txPtr.reg, state.texture[i].iterators.ydsdy);
+            ADD(AL, 0, Rx, Rx, reg_imm(txPtr.reg, ASR, 16));    // x += (s>>16)
+            CONTEXT_LOAD(txPtr.reg, state.texture[i].iterators.ydtdy);
+            ADD(AL, 0, Ry, Ry, reg_imm(txPtr.reg, ASR, 16));    // y += (t>>16)
+            // merge base & offset
+            CONTEXT_LOAD(txPtr.reg, generated_vars.texture[i].stride);
+            SMLABB(AL, Rx, Ry, txPtr.reg, Rx);               // x+y*stride
+            CONTEXT_ADDR_LOAD(txPtr.reg, generated_vars.texture[i].data);
+            base_offset(txPtr, txPtr, Rx);
+        } else {
+            Scratch scratches(registerFile());
+            reg_t& s = coords[i].s;
+            reg_t& t = coords[i].t;
+            // s = (x * dsdx)>>16 + ydsdy
+            // s = (x * dsdx)>>16 + (y*dsdy)>>16 + s0
+            // t = (x * dtdx)>>16 + ydtdy
+            // t = (x * dtdx)>>16 + (y*dtdy)>>16 + t0
+            s.setTo(obtainReg());
+            t.setTo(obtainReg());
+            const int need_w = GGL_READ_NEEDS(W, needs.n);
+            if (need_w) {
+                CONTEXT_LOAD(s.reg, state.texture[i].iterators.ydsdy);
+                CONTEXT_LOAD(t.reg, state.texture[i].iterators.ydtdy);
+            } else {
+                int ydsdy = scratches.obtain();
+                int ydtdy = scratches.obtain();
+                CONTEXT_LOAD(s.reg, generated_vars.texture[i].dsdx);
+                CONTEXT_LOAD(ydsdy, state.texture[i].iterators.ydsdy);
+                CONTEXT_LOAD(t.reg, generated_vars.texture[i].dtdx);
+                CONTEXT_LOAD(ydtdy, state.texture[i].iterators.ydtdy);
+                MLA(AL, 0, s.reg, Rx, s.reg, ydsdy);
+                MLA(AL, 0, t.reg, Rx, t.reg, ydtdy);
+            }
+            
+            if ((mOptLevel&1)==0) {
+                CONTEXT_STORE(s.reg, generated_vars.texture[i].spill[0]);
+                CONTEXT_STORE(t.reg, generated_vars.texture[i].spill[1]);
+                recycleReg(s.reg);
+                recycleReg(t.reg);
+            }
+        }
+
+        // direct texture?
+        if (!multiTexture && !mBlending && !mDithering && !mFog && 
+            cb_format_idx == tmu.format_idx && !tmu.linear &&
+            mTextureMachine.replaced == tmu.mask) 
+        {
+                mTextureMachine.directTexture = i + 1; 
+        }
+    }
+}
+
+void GGLAssembler::build_textures(  fragment_parts_t& parts,
+                                    Scratch& regs)
+{
+    // We don't have a way to spill registers automatically
+    // spill depth and AA regs, when we know we may have to.
+    // build the spill list...
+    uint32_t spill_list = 0;
+    for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT; i++) {
+        const texture_unit_t& tmu = mTextureMachine.tmu[i];
+        if (tmu.format_idx == 0)
+            continue;
+        if (tmu.linear) {
+            // we may run out of register if we have linear filtering
+            // at 1 or 4 bytes / pixel on any texture unit.
+            if (tmu.format.size == 1) {
+                // if depth and AA enabled, we'll run out of 1 register
+                if (parts.z.reg > 0 && parts.covPtr.reg > 0)
+                    spill_list |= 1<<parts.covPtr.reg;
+            }
+            if (tmu.format.size == 4) {
+                // if depth or AA enabled, we'll run out of 1 or 2 registers
+                if (parts.z.reg > 0)
+                    spill_list |= 1<<parts.z.reg;
+                if (parts.covPtr.reg > 0)   
+                    spill_list |= 1<<parts.covPtr.reg;
+            }
+        }
+    }
+
+    Spill spill(registerFile(), *this, spill_list);
+
+    for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT; i++) {
+        const texture_unit_t& tmu = mTextureMachine.tmu[i];
+        if (tmu.format_idx == 0)
+            continue;
+
+        pointer_t& txPtr = parts.coords[i].ptr;
+        pixel_t& texel = parts.texel[i];
+
+        // repeat...
+        if ((tmu.swrap == GGL_NEEDS_WRAP_11) &&
+            (tmu.twrap == GGL_NEEDS_WRAP_11))
+        { // 1:1 textures
+            comment("fetch texel");
+            texel.setTo(regs.obtain(), &tmu.format);
+            load(txPtr, texel, WRITE_BACK);
+        } else {
+            Scratch scratches(registerFile());
+            reg_t& s = parts.coords[i].s;
+            reg_t& t = parts.coords[i].t;
+            if ((mOptLevel&1)==0) {
+                comment("reload s/t (multitexture or linear filtering)");
+                s.reg = scratches.obtain();
+                t.reg = scratches.obtain();
+                CONTEXT_LOAD(s.reg, generated_vars.texture[i].spill[0]);
+                CONTEXT_LOAD(t.reg, generated_vars.texture[i].spill[1]);
+            }
+
+            if (registerFile().status() & RegisterFile::OUT_OF_REGISTERS)
+                return;
+
+            comment("compute repeat/clamp");
+            int u       = scratches.obtain();
+            int v       = scratches.obtain();
+            int width   = scratches.obtain();
+            int height  = scratches.obtain();
+            int U = 0;
+            int V = 0;
+
+            if (registerFile().status() & RegisterFile::OUT_OF_REGISTERS)
+                return;
+
+            CONTEXT_LOAD(width,  generated_vars.texture[i].width);
+            CONTEXT_LOAD(height, generated_vars.texture[i].height);
+
+            int FRAC_BITS = 0;
+            if (tmu.linear) {
+                // linear interpolation
+                if (tmu.format.size == 1) {
+                    // for 8-bits textures, we can afford
+                    // 7 bits of fractional precision at no
+                    // additional cost (we can't do 8 bits
+                    // because filter8 uses signed 16 bits muls)
+                    FRAC_BITS = 7;
+                } else if (tmu.format.size == 2) {
+                    // filter16() is internally limited to 4 bits, so:
+                    // FRAC_BITS=2 generates less instructions,
+                    // FRAC_BITS=3,4,5 creates unpleasant artifacts,
+                    // FRAC_BITS=6+ looks good
+                    FRAC_BITS = 6;
+                } else if (tmu.format.size == 4) {
+                    // filter32() is internally limited to 8 bits, so:
+                    // FRAC_BITS=4 looks good
+                    // FRAC_BITS=5+ looks better, but generates 3 extra ipp
+                    FRAC_BITS = 6;
+                } else {
+                    // for all other cases we use 4 bits.
+                    FRAC_BITS = 4;
+                }
+            }
+            wrapping(u, s.reg, width,  tmu.swrap, FRAC_BITS);
+            wrapping(v, t.reg, height, tmu.twrap, FRAC_BITS);
+
+            if (tmu.linear) {
+                comment("compute linear filtering offsets");
+                // pixel size scale
+                const int shift = 31 - gglClz(tmu.format.size);
+                U = scratches.obtain();
+                V = scratches.obtain();
+
+                if (registerFile().status() & RegisterFile::OUT_OF_REGISTERS)
+                    return;
+
+                // sample the texel center
+                SUB(AL, 0, u, u, imm(1<<(FRAC_BITS-1)));
+                SUB(AL, 0, v, v, imm(1<<(FRAC_BITS-1)));
+
+                // get the fractionnal part of U,V
+                AND(AL, 0, U, u, imm((1<<FRAC_BITS)-1));
+                AND(AL, 0, V, v, imm((1<<FRAC_BITS)-1));
+
+                // compute width-1 and height-1
+                SUB(AL, 0, width,  width,  imm(1));
+                SUB(AL, 0, height, height, imm(1));
+
+                // get the integer part of U,V and clamp/wrap
+                // and compute offset to the next texel
+                if (tmu.swrap == GGL_NEEDS_WRAP_REPEAT) {
+                    // u has already been REPEATed
+                    MOV(AL, 1, u, reg_imm(u, ASR, FRAC_BITS));
+                    MOV(MI, 0, u, width);                    
+                    CMP(AL, u, width);
+                    MOV(LT, 0, width, imm(1 << shift));
+                    if (shift)
+                        MOV(GE, 0, width, reg_imm(width, LSL, shift));
+                    RSB(GE, 0, width, width, imm(0));
+                } else {
+                    // u has not been CLAMPed yet
+                    // algorithm:
+                    // if ((u>>4) >= width)
+                    //      u = width<<4
+                    //      width = 0
+                    // else
+                    //      width = 1<<shift
+                    // u = u>>4; // get integer part
+                    // if (u<0)
+                    //      u = 0
+                    //      width = 0
+                    // generated_vars.rt = width
+                    
+                    CMP(AL, width, reg_imm(u, ASR, FRAC_BITS));
+                    MOV(LE, 0, u, reg_imm(width, LSL, FRAC_BITS));
+                    MOV(LE, 0, width, imm(0));
+                    MOV(GT, 0, width, imm(1 << shift));
+                    MOV(AL, 1, u, reg_imm(u, ASR, FRAC_BITS));
+                    MOV(MI, 0, u, imm(0));
+                    MOV(MI, 0, width, imm(0));
+                }
+                CONTEXT_STORE(width, generated_vars.rt);
+
+                const int stride = width;
+                CONTEXT_LOAD(stride, generated_vars.texture[i].stride);
+                if (tmu.twrap == GGL_NEEDS_WRAP_REPEAT) {
+                    // v has already been REPEATed
+                    MOV(AL, 1, v, reg_imm(v, ASR, FRAC_BITS));
+                    MOV(MI, 0, v, height);
+                    CMP(AL, v, height);
+                    MOV(LT, 0, height, imm(1 << shift));
+                    if (shift)
+                        MOV(GE, 0, height, reg_imm(height, LSL, shift));
+                    RSB(GE, 0, height, height, imm(0));
+                    MUL(AL, 0, height, stride, height);
+                } else {
+                    // v has not been CLAMPed yet
+                    CMP(AL, height, reg_imm(v, ASR, FRAC_BITS));
+                    MOV(LE, 0, v, reg_imm(height, LSL, FRAC_BITS));
+                    MOV(LE, 0, height, imm(0));
+                    if (shift) {
+                        MOV(GT, 0, height, reg_imm(stride, LSL, shift));
+                    } else {
+                        MOV(GT, 0, height, stride);
+                    }
+                    MOV(AL, 1, v, reg_imm(v, ASR, FRAC_BITS));
+                    MOV(MI, 0, v, imm(0));
+                    MOV(MI, 0, height, imm(0));
+                }
+                CONTEXT_STORE(height, generated_vars.lb);
+            }
+    
+            scratches.recycle(width);
+            scratches.recycle(height);
+
+            // iterate texture coordinates...
+            comment("iterate s,t");
+            int dsdx = scratches.obtain();
+            int dtdx = scratches.obtain();
+
+            if (registerFile().status() & RegisterFile::OUT_OF_REGISTERS)
+                return;
+
+            CONTEXT_LOAD(dsdx, generated_vars.texture[i].dsdx);
+            CONTEXT_LOAD(dtdx, generated_vars.texture[i].dtdx);
+            ADD(AL, 0, s.reg, s.reg, dsdx);
+            ADD(AL, 0, t.reg, t.reg, dtdx);
+            if ((mOptLevel&1)==0) {
+                CONTEXT_STORE(s.reg, generated_vars.texture[i].spill[0]);
+                CONTEXT_STORE(t.reg, generated_vars.texture[i].spill[1]);
+                scratches.recycle(s.reg);
+                scratches.recycle(t.reg);
+            }
+            scratches.recycle(dsdx);
+            scratches.recycle(dtdx);
+
+            // merge base & offset...
+            comment("merge base & offset");
+            texel.setTo(regs.obtain(), &tmu.format);
+            txPtr.setTo(texel.reg, tmu.bits);
+            int stride = scratches.obtain();
+
+            if (registerFile().status() & RegisterFile::OUT_OF_REGISTERS)
+                return;
+
+            CONTEXT_LOAD(stride,    generated_vars.texture[i].stride);
+            CONTEXT_ADDR_LOAD(txPtr.reg, generated_vars.texture[i].data);
+            SMLABB(AL, u, v, stride, u);    // u+v*stride 
+            base_offset(txPtr, txPtr, u);
+
+            // load texel
+            if (!tmu.linear) {
+                comment("fetch texel");
+                load(txPtr, texel, 0);
+            } else {
+                // recycle registers we don't need anymore
+                scratches.recycle(u);
+                scratches.recycle(v);
+                scratches.recycle(stride);
+
+                comment("fetch texel, bilinear");
+                switch (tmu.format.size) {
+                case 1:  filter8(parts, texel, tmu, U, V, txPtr, FRAC_BITS); break;
+                case 2: filter16(parts, texel, tmu, U, V, txPtr, FRAC_BITS); break;
+                case 3: filter24(parts, texel, tmu, U, V, txPtr, FRAC_BITS); break;
+                case 4: filter32(parts, texel, tmu, U, V, txPtr, FRAC_BITS); break;
+                }
+            }            
+        }
+    }
+}
+
+void GGLAssembler::build_iterate_texture_coordinates(
+    const fragment_parts_t& parts)
+{
+    for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT; i++) {
+        const texture_unit_t& tmu = mTextureMachine.tmu[i];
+        if (tmu.format_idx == 0)
+            continue;
+
+        if ((tmu.swrap == GGL_NEEDS_WRAP_11) &&
+            (tmu.twrap == GGL_NEEDS_WRAP_11))
+        { // 1:1 textures
+            const pointer_t& txPtr = parts.coords[i].ptr;
+            ADD(AL, 0, txPtr.reg, txPtr.reg, imm(txPtr.size>>3));
+        } else {
+            Scratch scratches(registerFile());
+            int s = parts.coords[i].s.reg;
+            int t = parts.coords[i].t.reg;
+            if ((mOptLevel&1)==0) {
+                s = scratches.obtain();
+                t = scratches.obtain();
+                CONTEXT_LOAD(s, generated_vars.texture[i].spill[0]);
+                CONTEXT_LOAD(t, generated_vars.texture[i].spill[1]);
+            }
+            int dsdx = scratches.obtain();
+            int dtdx = scratches.obtain();
+            CONTEXT_LOAD(dsdx, generated_vars.texture[i].dsdx);
+            CONTEXT_LOAD(dtdx, generated_vars.texture[i].dtdx);
+            ADD(AL, 0, s, s, dsdx);
+            ADD(AL, 0, t, t, dtdx);
+            if ((mOptLevel&1)==0) {
+                CONTEXT_STORE(s, generated_vars.texture[i].spill[0]);
+                CONTEXT_STORE(t, generated_vars.texture[i].spill[1]);
+            }
+        }
+    }
+}
+
+void GGLAssembler::filter8(
+        const fragment_parts_t& /*parts*/,
+        pixel_t& texel, const texture_unit_t& tmu,
+        int U, int V, pointer_t& txPtr,
+        int FRAC_BITS)
+{
+    if (tmu.format.components != GGL_ALPHA &&
+        tmu.format.components != GGL_LUMINANCE)
+    {
+        // this is a packed format, and we don't support
+        // linear filtering (it's probably RGB 332)
+        // Should not happen with OpenGL|ES
+        LDRB(AL, texel.reg, txPtr.reg);
+        return;
+    }
+
+    // ------------------------
+    // about ~22 cycles / pixel
+    Scratch scratches(registerFile());
+
+    int pixel= scratches.obtain();
+    int d    = scratches.obtain();
+    int u    = scratches.obtain();
+    int k    = scratches.obtain();
+    int rt   = scratches.obtain();
+    int lb   = scratches.obtain();
+
+    // RB -> U * V
+
+    CONTEXT_LOAD(rt, generated_vars.rt);
+    CONTEXT_LOAD(lb, generated_vars.lb);
+
+    int offset = pixel;
+    ADD(AL, 0, offset, lb, rt);
+    LDRB(AL, pixel, txPtr.reg, reg_scale_pre(offset));
+    SMULBB(AL, u, U, V);
+    SMULBB(AL, d, pixel, u);
+    RSB(AL, 0, k, u, imm(1<<(FRAC_BITS*2)));
+    
+    // LB -> (1-U) * V
+    RSB(AL, 0, U, U, imm(1<<FRAC_BITS));
+    LDRB(AL, pixel, txPtr.reg, reg_scale_pre(lb));
+    SMULBB(AL, u, U, V);
+    SMLABB(AL, d, pixel, u, d);
+    SUB(AL, 0, k, k, u);
+    
+    // LT -> (1-U)*(1-V)
+    RSB(AL, 0, V, V, imm(1<<FRAC_BITS));
+    LDRB(AL, pixel, txPtr.reg);
+    SMULBB(AL, u, U, V);
+    SMLABB(AL, d, pixel, u, d);
+
+    // RT -> U*(1-V)
+    LDRB(AL, pixel, txPtr.reg, reg_scale_pre(rt));
+    SUB(AL, 0, u, k, u);
+    SMLABB(AL, texel.reg, pixel, u, d);
+    
+    for (int i=0 ; i<4 ; i++) {
+        if (!texel.format.c[i].h) continue;
+        texel.format.c[i].h = FRAC_BITS*2+8;
+        texel.format.c[i].l = FRAC_BITS*2; // keeping 8 bits in enough
+    }
+    texel.format.size = 4;
+    texel.format.bitsPerPixel = 32;
+    texel.flags |= CLEAR_LO;
+}
+
+void GGLAssembler::filter16(
+        const fragment_parts_t& /*parts*/,
+        pixel_t& texel, const texture_unit_t& tmu,
+        int U, int V, pointer_t& txPtr,
+        int FRAC_BITS)
+{    
+    // compute the mask
+    // XXX: it would be nice if the mask below could be computed
+    // automatically.
+    uint32_t mask = 0;
+    int shift = 0;
+    int prec = 0;
+    switch (tmu.format_idx) {
+        case GGL_PIXEL_FORMAT_RGB_565:
+            // source: 00000ggg.ggg00000 | rrrrr000.000bbbbb
+            // result: gggggggg.gggrrrrr | rrrrr0bb.bbbbbbbb
+            mask = 0x07E0F81F;
+            shift = 16;
+            prec = 5;
+            break;
+        case GGL_PIXEL_FORMAT_RGBA_4444:
+            // 0000,1111,0000,1111 | 0000,1111,0000,1111
+            mask = 0x0F0F0F0F;
+            shift = 12;
+            prec = 4;
+            break;
+        case GGL_PIXEL_FORMAT_LA_88:
+            // 0000,0000,1111,1111 | 0000,0000,1111,1111
+            // AALL -> 00AA | 00LL
+            mask = 0x00FF00FF;
+            shift = 8;
+            prec = 8;
+            break;
+        default:
+            // unsupported format, do something sensical...
+            ALOGE("Unsupported 16-bits texture format (%d)", tmu.format_idx);
+            LDRH(AL, texel.reg, txPtr.reg);
+            return;
+    }
+
+    const int adjust = FRAC_BITS*2 - prec;
+    const int round  = 0;
+
+    // update the texel format
+    texel.format.size = 4;
+    texel.format.bitsPerPixel = 32;
+    texel.flags |= CLEAR_HI|CLEAR_LO;
+    for (int i=0 ; i<4 ; i++) {
+        if (!texel.format.c[i].h) continue;
+        const uint32_t offset = (mask & tmu.format.mask(i)) ? 0 : shift;
+        texel.format.c[i].h = tmu.format.c[i].h + offset + prec;
+        texel.format.c[i].l = texel.format.c[i].h - (tmu.format.bits(i) + prec);
+    }
+
+    // ------------------------
+    // about ~40 cycles / pixel
+    Scratch scratches(registerFile());
+
+    int pixel= scratches.obtain();
+    int d    = scratches.obtain();
+    int u    = scratches.obtain();
+    int k    = scratches.obtain();
+
+    // RB -> U * V
+    int offset = pixel;
+    CONTEXT_LOAD(offset, generated_vars.rt);
+    CONTEXT_LOAD(u, generated_vars.lb);
+    ADD(AL, 0, offset, offset, u);
+
+    LDRH(AL, pixel, txPtr.reg, reg_pre(offset));
+    SMULBB(AL, u, U, V);
+    ORR(AL, 0, pixel, pixel, reg_imm(pixel, LSL, shift));
+    build_and_immediate(pixel, pixel, mask, 32);
+    if (adjust) {
+        if (round)
+            ADD(AL, 0, u, u, imm(1<<(adjust-1)));
+        MOV(AL, 0, u, reg_imm(u, LSR, adjust));
+    }
+    MUL(AL, 0, d, pixel, u);
+    RSB(AL, 0, k, u, imm(1<<prec));
+    
+    // LB -> (1-U) * V
+    CONTEXT_LOAD(offset, generated_vars.lb);
+    RSB(AL, 0, U, U, imm(1<<FRAC_BITS));
+    LDRH(AL, pixel, txPtr.reg, reg_pre(offset));
+    SMULBB(AL, u, U, V);
+    ORR(AL, 0, pixel, pixel, reg_imm(pixel, LSL, shift));
+    build_and_immediate(pixel, pixel, mask, 32);
+    if (adjust) {
+        if (round)
+            ADD(AL, 0, u, u, imm(1<<(adjust-1)));
+        MOV(AL, 0, u, reg_imm(u, LSR, adjust));
+    }
+    MLA(AL, 0, d, pixel, u, d);
+    SUB(AL, 0, k, k, u);
+    
+    // LT -> (1-U)*(1-V)
+    RSB(AL, 0, V, V, imm(1<<FRAC_BITS));
+    LDRH(AL, pixel, txPtr.reg);
+    SMULBB(AL, u, U, V);
+    ORR(AL, 0, pixel, pixel, reg_imm(pixel, LSL, shift));
+    build_and_immediate(pixel, pixel, mask, 32);
+    if (adjust) {
+        if (round)
+            ADD(AL, 0, u, u, imm(1<<(adjust-1)));
+        MOV(AL, 0, u, reg_imm(u, LSR, adjust));
+    }
+    MLA(AL, 0, d, pixel, u, d);
+
+    // RT -> U*(1-V)            
+    CONTEXT_LOAD(offset, generated_vars.rt);
+    LDRH(AL, pixel, txPtr.reg, reg_pre(offset));
+    SUB(AL, 0, u, k, u);
+    ORR(AL, 0, pixel, pixel, reg_imm(pixel, LSL, shift));
+    build_and_immediate(pixel, pixel, mask, 32);
+    MLA(AL, 0, texel.reg, pixel, u, d);
+}
+
+void GGLAssembler::filter24(
+        const fragment_parts_t& /*parts*/,
+        pixel_t& texel, const texture_unit_t& /*tmu*/,
+        int /*U*/, int /*V*/, pointer_t& txPtr,
+        int /*FRAC_BITS*/)
+{
+    // not supported yet (currently disabled)
+    load(txPtr, texel, 0);
+}
+
+void GGLAssembler::filter32(
+        const fragment_parts_t& /*parts*/,
+        pixel_t& texel, const texture_unit_t& /*tmu*/,
+        int U, int V, pointer_t& txPtr,
+        int FRAC_BITS)
+{
+    const int adjust = FRAC_BITS*2 - 8;
+    const int round  = 0;
+
+    // ------------------------
+    // about ~38 cycles / pixel
+    Scratch scratches(registerFile());
+    
+    int pixel= scratches.obtain();
+    int dh   = scratches.obtain();
+    int u    = scratches.obtain();
+    int k    = scratches.obtain();
+
+    int temp = scratches.obtain();
+    int dl   = scratches.obtain();
+    int mask = scratches.obtain();
+
+    MOV(AL, 0, mask, imm(0xFF));
+    ORR(AL, 0, mask, mask, imm(0xFF0000));
+
+    // RB -> U * V
+    int offset = pixel;
+    CONTEXT_LOAD(offset, generated_vars.rt);
+    CONTEXT_LOAD(u, generated_vars.lb);
+    ADD(AL, 0, offset, offset, u);
+
+    LDR(AL, pixel, txPtr.reg, reg_scale_pre(offset));
+    SMULBB(AL, u, U, V);
+    AND(AL, 0, temp, mask, pixel);
+    if (adjust) {
+        if (round)
+            ADD(AL, 0, u, u, imm(1<<(adjust-1)));
+        MOV(AL, 0, u, reg_imm(u, LSR, adjust));
+    }
+    MUL(AL, 0, dh, temp, u);
+    AND(AL, 0, temp, mask, reg_imm(pixel, LSR, 8));
+    MUL(AL, 0, dl, temp, u);
+    RSB(AL, 0, k, u, imm(0x100));
+
+    // LB -> (1-U) * V
+    CONTEXT_LOAD(offset, generated_vars.lb);
+    RSB(AL, 0, U, U, imm(1<<FRAC_BITS));
+    LDR(AL, pixel, txPtr.reg, reg_scale_pre(offset));
+    SMULBB(AL, u, U, V);
+    AND(AL, 0, temp, mask, pixel);
+    if (adjust) {
+        if (round)
+            ADD(AL, 0, u, u, imm(1<<(adjust-1)));
+        MOV(AL, 0, u, reg_imm(u, LSR, adjust));
+    }
+    MLA(AL, 0, dh, temp, u, dh);    
+    AND(AL, 0, temp, mask, reg_imm(pixel, LSR, 8));
+    MLA(AL, 0, dl, temp, u, dl);
+    SUB(AL, 0, k, k, u);
+
+    // LT -> (1-U)*(1-V)
+    RSB(AL, 0, V, V, imm(1<<FRAC_BITS));
+    LDR(AL, pixel, txPtr.reg);
+    SMULBB(AL, u, U, V);
+    AND(AL, 0, temp, mask, pixel);
+    if (adjust) {
+        if (round)
+            ADD(AL, 0, u, u, imm(1<<(adjust-1)));
+        MOV(AL, 0, u, reg_imm(u, LSR, adjust));
+    }
+    MLA(AL, 0, dh, temp, u, dh);    
+    AND(AL, 0, temp, mask, reg_imm(pixel, LSR, 8));
+    MLA(AL, 0, dl, temp, u, dl);
+
+    // RT -> U*(1-V)            
+    CONTEXT_LOAD(offset, generated_vars.rt);
+    LDR(AL, pixel, txPtr.reg, reg_scale_pre(offset));
+    SUB(AL, 0, u, k, u);
+    AND(AL, 0, temp, mask, pixel);
+    MLA(AL, 0, dh, temp, u, dh);    
+    AND(AL, 0, temp, mask, reg_imm(pixel, LSR, 8));
+    MLA(AL, 0, dl, temp, u, dl);
+
+    AND(AL, 0, dh, mask, reg_imm(dh, LSR, 8));
+    AND(AL, 0, dl, dl, reg_imm(mask, LSL, 8));
+    ORR(AL, 0, texel.reg, dh, dl);
+}
+
+void GGLAssembler::build_texture_environment(
+        component_t& fragment,
+        const fragment_parts_t& parts,
+        int component,
+        Scratch& regs)
+{
+    const uint32_t component_mask = 1<<component;
+    const bool multiTexture = mTextureMachine.activeUnits > 1;
+    for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; i++) {
+        texture_unit_t& tmu = mTextureMachine.tmu[i];
+
+        if (tmu.mask & component_mask) {
+            // replace or modulate with this texture
+            if ((tmu.replaced & component_mask) == 0) {
+                // not replaced by a later tmu...
+
+                Scratch scratches(registerFile());
+                pixel_t texel(parts.texel[i]);
+
+                if (multiTexture && 
+                    tmu.swrap == GGL_NEEDS_WRAP_11 &&
+                    tmu.twrap == GGL_NEEDS_WRAP_11)
+                {
+                    texel.reg = scratches.obtain();
+                    texel.flags |= CORRUPTIBLE;
+                    comment("fetch texel (multitexture 1:1)");
+                    load(parts.coords[i].ptr, texel, WRITE_BACK);
+                 }
+
+                component_t incoming(fragment);
+                modify(fragment, regs);
+                
+                switch (tmu.env) {
+                case GGL_REPLACE:
+                    extract(fragment, texel, component);
+                    break;
+                case GGL_MODULATE:
+                    modulate(fragment, incoming, texel, component);
+                    break;
+                case GGL_DECAL:
+                    decal(fragment, incoming, texel, component);
+                    break;
+                case GGL_BLEND:
+                    blend(fragment, incoming, texel, component, i);
+                    break;
+                case GGL_ADD:
+                    add(fragment, incoming, texel, component);
+                    break;
+                }
+            }
+        }
+    }
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::wrapping(
+            int d,
+            int coord, int size,
+            int tx_wrap, int tx_linear)
+{
+    // notes:
+    // if tx_linear is set, we need 4 extra bits of precision on the result
+    // SMULL/UMULL is 3 cycles
+    Scratch scratches(registerFile());
+    int c = coord;
+    if (tx_wrap == GGL_NEEDS_WRAP_REPEAT) {
+        // UMULL takes 4 cycles (interlocked), and we can get away with
+        // 2 cycles using SMULWB, but we're loosing 16 bits of precision
+        // out of 32 (this is not a problem because the iterator keeps
+        // its full precision)
+        // UMULL(AL, 0, size, d, c, size);
+        // note: we can't use SMULTB because it's signed.
+        MOV(AL, 0, d, reg_imm(c, LSR, 16-tx_linear));
+        SMULWB(AL, d, d, size);
+    } else if (tx_wrap == GGL_NEEDS_WRAP_CLAMP_TO_EDGE) {
+        if (tx_linear) {
+            // 1 cycle
+            MOV(AL, 0, d, reg_imm(coord, ASR, 16-tx_linear));
+        } else {
+            // 4 cycles (common case)
+            MOV(AL, 0, d, reg_imm(coord, ASR, 16));
+            BIC(AL, 0, d, d, reg_imm(d, ASR, 31));
+            CMP(AL, d, size);
+            SUB(GE, 0, d, size, imm(1));
+        }
+    }
+}
+
+// ---------------------------------------------------------------------------
+
+void GGLAssembler::modulate(
+        component_t& dest, 
+        const component_t& incoming,
+        const pixel_t& incomingTexel, int component)
+{
+    Scratch locals(registerFile());
+    integer_t texel(locals.obtain(), 32, CORRUPTIBLE);            
+    extract(texel, incomingTexel, component);
+
+    const int Nt = texel.size();
+        // Nt should always be less than 10 bits because it comes
+        // from the TMU.
+
+    int Ni = incoming.size();
+        // Ni could be big because it comes from previous MODULATEs
+
+    if (Nt == 1) {
+        // texel acts as a bit-mask
+        // dest = incoming & ((texel << incoming.h)-texel)
+        RSB(AL, 0, dest.reg, texel.reg, reg_imm(texel.reg, LSL, incoming.h));
+        AND(AL, 0, dest.reg, dest.reg, incoming.reg);
+        dest.l = incoming.l;
+        dest.h = incoming.h;
+        dest.flags |= (incoming.flags & CLEAR_LO);
+    } else if (Ni == 1) {
+        MOV(AL, 0, dest.reg, reg_imm(incoming.reg, LSL, 31-incoming.h));
+        AND(AL, 0, dest.reg, texel.reg, reg_imm(dest.reg, ASR, 31));
+        dest.l = 0;
+        dest.h = Nt;
+    } else {
+        int inReg = incoming.reg;
+        int shift = incoming.l;
+        if ((Nt + Ni) > 32) {
+            // we will overflow, reduce the precision of Ni to 8 bits
+            // (Note Nt cannot be more than 10 bits which happens with 
+            // 565 textures and GGL_LINEAR)
+            shift += Ni-8;
+            Ni = 8;
+        }
+
+        // modulate by the component with the lowest precision
+        if (Nt >= Ni) {
+            if (shift) {
+                // XXX: we should be able to avoid this shift
+                // when shift==16 && Nt<16 && Ni<16, in which
+                // we could use SMULBT below.
+                MOV(AL, 0, dest.reg, reg_imm(inReg, LSR, shift));
+                inReg = dest.reg;
+                shift = 0;
+            }
+            // operation:           (Cf*Ct)/((1<<Ni)-1)
+            // approximated with:   Cf*(Ct + Ct>>(Ni-1))>>Ni
+            // this operation doesn't change texel's size
+            ADD(AL, 0, dest.reg, inReg, reg_imm(inReg, LSR, Ni-1));
+            if (Nt<16 && Ni<16) SMULBB(AL, dest.reg, texel.reg, dest.reg);
+            else                MUL(AL, 0, dest.reg, texel.reg, dest.reg);
+            dest.l = Ni;
+            dest.h = Nt + Ni;            
+        } else {
+            if (shift && (shift != 16)) {
+                // if shift==16, we can use 16-bits mul instructions later
+                MOV(AL, 0, dest.reg, reg_imm(inReg, LSR, shift));
+                inReg = dest.reg;
+                shift = 0;
+            }
+            // operation:           (Cf*Ct)/((1<<Nt)-1)
+            // approximated with:   Ct*(Cf + Cf>>(Nt-1))>>Nt
+            // this operation doesn't change incoming's size
+            Scratch scratches(registerFile());
+            int t = (texel.flags & CORRUPTIBLE) ? texel.reg : dest.reg;
+            if (t == inReg)
+                t = scratches.obtain();
+            ADD(AL, 0, t, texel.reg, reg_imm(texel.reg, LSR, Nt-1));
+            if (Nt<16 && Ni<16) {
+                if (shift==16)  SMULBT(AL, dest.reg, t, inReg);
+                else            SMULBB(AL, dest.reg, t, inReg);
+            } else              MUL(AL, 0, dest.reg, t, inReg);
+            dest.l = Nt;
+            dest.h = Nt + Ni;
+        }
+
+        // low bits are not valid
+        dest.flags |= CLEAR_LO;
+
+        // no need to keep more than 8 bits/component
+        if (dest.size() > 8)
+            dest.l = dest.h-8;
+    }
+}
+
+void GGLAssembler::decal(
+        component_t& dest, 
+        const component_t& incoming,
+        const pixel_t& incomingTexel, int component)
+{
+    // RGBA:
+    // Cv = Cf*(1 - At) + Ct*At = Cf + (Ct - Cf)*At
+    // Av = Af
+    Scratch locals(registerFile());
+    integer_t texel(locals.obtain(), 32, CORRUPTIBLE);            
+    integer_t factor(locals.obtain(), 32, CORRUPTIBLE);
+    extract(texel, incomingTexel, component);
+    extract(factor, incomingTexel, GGLFormat::ALPHA);
+
+    // no need to keep more than 8-bits for decal 
+    int Ni = incoming.size();
+    int shift = incoming.l;
+    if (Ni > 8) {
+        shift += Ni-8;
+        Ni = 8;
+    }
+    integer_t incomingNorm(incoming.reg, Ni, incoming.flags);
+    if (shift) {
+        MOV(AL, 0, dest.reg, reg_imm(incomingNorm.reg, LSR, shift));
+        incomingNorm.reg = dest.reg;
+        incomingNorm.flags |= CORRUPTIBLE;
+    }
+    ADD(AL, 0, factor.reg, factor.reg, reg_imm(factor.reg, LSR, factor.s-1));
+    build_blendOneMinusFF(dest, factor, incomingNorm, texel);
+}
+
+void GGLAssembler::blend(
+        component_t& dest, 
+        const component_t& incoming,
+        const pixel_t& incomingTexel, int component, int tmu)
+{
+    // RGBA:
+    // Cv = (1 - Ct)*Cf + Ct*Cc = Cf + (Cc - Cf)*Ct
+    // Av = At*Af
+
+    if (component == GGLFormat::ALPHA) {
+        modulate(dest, incoming, incomingTexel, component);
+        return;
+    }
+    
+    Scratch locals(registerFile());
+    integer_t color(locals.obtain(), 8, CORRUPTIBLE);            
+    integer_t factor(locals.obtain(), 32, CORRUPTIBLE);
+    LDRB(AL, color.reg, mBuilderContext.Rctx,
+            immed12_pre(GGL_OFFSETOF(state.texture[tmu].env_color[component])));
+    extract(factor, incomingTexel, component);
+
+    // no need to keep more than 8-bits for blend 
+    int Ni = incoming.size();
+    int shift = incoming.l;
+    if (Ni > 8) {
+        shift += Ni-8;
+        Ni = 8;
+    }
+    integer_t incomingNorm(incoming.reg, Ni, incoming.flags);
+    if (shift) {
+        MOV(AL, 0, dest.reg, reg_imm(incomingNorm.reg, LSR, shift));
+        incomingNorm.reg = dest.reg;
+        incomingNorm.flags |= CORRUPTIBLE;
+    }
+    ADD(AL, 0, factor.reg, factor.reg, reg_imm(factor.reg, LSR, factor.s-1));
+    build_blendOneMinusFF(dest, factor, incomingNorm, color);
+}
+
+void GGLAssembler::add(
+        component_t& dest, 
+        const component_t& incoming,
+        const pixel_t& incomingTexel, int component)
+{
+    // RGBA:
+    // Cv = Cf + Ct;
+    Scratch locals(registerFile());
+    
+    component_t incomingTemp(incoming);
+
+    // use "dest" as a temporary for extracting the texel, unless "dest"
+    // overlaps "incoming".
+    integer_t texel(dest.reg, 32, CORRUPTIBLE);
+    if (dest.reg == incomingTemp.reg)
+        texel.reg = locals.obtain();
+    extract(texel, incomingTexel, component);
+
+    if (texel.s < incomingTemp.size()) {
+        expand(texel, texel, incomingTemp.size());
+    } else if (texel.s > incomingTemp.size()) {
+        if (incomingTemp.flags & CORRUPTIBLE) {
+            expand(incomingTemp, incomingTemp, texel.s);
+        } else {
+            incomingTemp.reg = locals.obtain();
+            expand(incomingTemp, incoming, texel.s);
+        }
+    }
+
+    if (incomingTemp.l) {
+        ADD(AL, 0, dest.reg, texel.reg,
+                reg_imm(incomingTemp.reg, LSR, incomingTemp.l));
+    } else {
+        ADD(AL, 0, dest.reg, texel.reg, incomingTemp.reg);
+    }
+    dest.l = 0;
+    dest.h = texel.size();
+    component_sat(dest);
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
diff --git a/libpixelflinger/codeflinger/tinyutils/smartpointer.h b/libpixelflinger/codeflinger/tinyutils/smartpointer.h
new file mode 100644
index 0000000..23a5f7e
--- /dev/null
+++ b/libpixelflinger/codeflinger/tinyutils/smartpointer.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PIXELFLINGER_SMART_POINTER_H
+#define ANDROID_PIXELFLINGER_SMART_POINTER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <stdlib.h>
+
+// ---------------------------------------------------------------------------
+namespace android {
+namespace tinyutils {
+
+// ---------------------------------------------------------------------------
+
+#define COMPARE(_op_)                                           \
+inline bool operator _op_ (const sp<T>& o) const {              \
+    return m_ptr _op_ o.m_ptr;                                  \
+}                                                               \
+inline bool operator _op_ (const T* o) const {                  \
+    return m_ptr _op_ o;                                        \
+}                                                               \
+template<typename U>                                            \
+inline bool operator _op_ (const sp<U>& o) const {              \
+    return m_ptr _op_ o.m_ptr;                                  \
+}                                                               \
+template<typename U>                                            \
+inline bool operator _op_ (const U* o) const {                  \
+    return m_ptr _op_ o;                                        \
+}
+
+// ---------------------------------------------------------------------------
+
+template <typename T>
+class sp
+{
+public:
+    inline sp() : m_ptr(0) { }
+
+    sp(T* other);  // NOLINT, implicit
+    sp(const sp<T>& other);
+    template<typename U> sp(U* other);  // NOLINT, implicit
+    template<typename U> sp(const sp<U>& other);  // NOLINT, implicit
+
+    ~sp();
+    
+    // Assignment
+
+    sp& operator = (T* other);
+    sp& operator = (const sp<T>& other);
+    
+    template<typename U> sp& operator = (const sp<U>& other);
+    template<typename U> sp& operator = (U* other);
+    
+    // Reset
+    void clear();
+    
+    // Accessors
+
+    inline  T&      operator* () const  { return *m_ptr; }
+    inline  T*      operator-> () const { return m_ptr;  }
+    inline  T*      get() const         { return m_ptr; }
+
+    // Operators
+        
+    COMPARE(==)
+    COMPARE(!=)
+    COMPARE(>)
+    COMPARE(<)
+    COMPARE(<=)
+    COMPARE(>=)
+
+private:    
+    template<typename Y> friend class sp;
+
+    T*              m_ptr;
+};
+
+// ---------------------------------------------------------------------------
+// No user serviceable parts below here.
+
+template<typename T>
+sp<T>::sp(T* other)
+    : m_ptr(other)
+{
+    if (other) other->incStrong(this);
+}
+
+template<typename T>
+sp<T>::sp(const sp<T>& other)
+    : m_ptr(other.m_ptr)
+{
+    if (m_ptr) m_ptr->incStrong(this);
+}
+
+template<typename T> template<typename U>
+sp<T>::sp(U* other) : m_ptr(other)
+{
+    if (other) other->incStrong(this);
+}
+
+template<typename T> template<typename U>
+sp<T>::sp(const sp<U>& other)
+    : m_ptr(other.m_ptr)
+{
+    if (m_ptr) m_ptr->incStrong(this);
+}
+
+template<typename T>
+sp<T>::~sp()
+{
+    if (m_ptr) m_ptr->decStrong(this);
+}
+
+template<typename T>
+sp<T>& sp<T>::operator = (const sp<T>& other) {
+    if (other.m_ptr) other.m_ptr->incStrong(this);
+    if (m_ptr) m_ptr->decStrong(this);
+    m_ptr = other.m_ptr;
+    return *this;
+}
+
+template<typename T>
+sp<T>& sp<T>::operator = (T* other)
+{
+    if (other) other->incStrong(this);
+    if (m_ptr) m_ptr->decStrong(this);
+    m_ptr = other;
+    return *this;
+}
+
+template<typename T> template<typename U>
+sp<T>& sp<T>::operator = (const sp<U>& other)
+{
+    if (other.m_ptr) other.m_ptr->incStrong(this);
+    if (m_ptr) m_ptr->decStrong(this);
+    m_ptr = other.m_ptr;
+    return *this;
+}
+
+template<typename T> template<typename U>
+sp<T>& sp<T>::operator = (U* other)
+{
+    if (other) other->incStrong(this);
+    if (m_ptr) m_ptr->decStrong(this);
+    m_ptr = other;
+    return *this;
+}
+
+template<typename T>
+void sp<T>::clear()
+{
+    if (m_ptr) {
+        m_ptr->decStrong(this);
+        m_ptr = 0;
+    }
+}
+
+// ---------------------------------------------------------------------------
+
+} // namespace tinyutils
+} // namespace android
+
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_PIXELFLINGER_SMART_POINTER_H
diff --git a/libpixelflinger/col32cb16blend.S b/libpixelflinger/col32cb16blend.S
new file mode 100644
index 0000000..39f94e1
--- /dev/null
+++ b/libpixelflinger/col32cb16blend.S
@@ -0,0 +1,77 @@
+/* libs/pixelflinger/col32cb16blend.S
+ *
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+    .text
+    .balign 4
+
+    .global scanline_col32cb16blend_arm
+
+//
+// This function alpha blends a fixed color into a destination scanline, using
+// the formula:
+//
+//     d = s + (((a + (a >> 7)) * d) >> 8)
+//
+// where d is the destination pixel,
+//       s is the source color,
+//       a is the alpha channel of the source color.
+//
+
+// r0 = destination buffer pointer
+// r1 = color value
+// r2 = count
+
+
+scanline_col32cb16blend_arm:
+    push        {r4-r10, lr}                    // stack ARM regs
+
+    mov         r5, r1, lsr #24                 // shift down alpha
+    mov         r9, #0xff                       // create mask
+    add         r5, r5, r5, lsr #7              // add in top bit
+    rsb         r5, r5, #256                    // invert alpha
+    and         r10, r1, #0xff                  // extract red
+    and         r12, r9, r1, lsr #8             // extract green
+    and         r4, r9, r1, lsr #16             // extract blue
+    mov         r10, r10, lsl #5                // prescale red
+    mov         r12, r12, lsl #6                // prescale green
+    mov         r4, r4, lsl #5                  // prescale blue
+    mov         r9, r9, lsr #2                  // create dest green mask
+
+1:
+    ldrh        r8, [r0]                        // load dest pixel
+    subs        r2, r2, #1                      // decrement loop counter
+    mov         r6, r8, lsr #11                 // extract dest red
+    and         r7, r9, r8, lsr #5              // extract dest green
+    and         r8, r8, #0x1f                   // extract dest blue
+
+    smlabb      r6, r6, r5, r10                 // dest red * alpha + src red
+    smlabb      r7, r7, r5, r12                 // dest green * alpha + src green
+    smlabb      r8, r8, r5, r4                  // dest blue * alpha + src blue
+
+    mov         r6, r6, lsr #8                  // shift down red
+    mov         r7, r7, lsr #8                  // shift down green
+    mov         r6, r6, lsl #11                 // shift red into 565
+    orr         r6, r7, lsl #5                  // shift green into 565
+    orr         r6, r8, lsr #8                  // shift blue into 565
+
+    strh        r6, [r0], #2                    // store pixel to dest, update ptr
+    bne         1b                              // if count != 0, loop
+
+    pop         {r4-r10, pc}                    // return
+
+
+
diff --git a/libpixelflinger/col32cb16blend_neon.S b/libpixelflinger/col32cb16blend_neon.S
new file mode 100644
index 0000000..7ad34b0
--- /dev/null
+++ b/libpixelflinger/col32cb16blend_neon.S
@@ -0,0 +1,153 @@
+/* libs/pixelflinger/col32cb16blend_neon.S
+ *
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+    .text
+    .balign 4
+
+    .global scanline_col32cb16blend_neon
+
+//
+// This function alpha blends a fixed color into a destination scanline, using
+// the formula:
+//
+//     d = s + (((a + (a >> 7)) * d) >> 8)
+//
+// where d is the destination pixel,
+//       s is the source color,
+//       a is the alpha channel of the source color.
+//
+// The NEON implementation processes 16 pixels per iteration. The remaining 0 - 15
+// pixels are processed in ARM code.
+//
+
+// r0 = destination buffer pointer
+// r1 = color pointer
+// r2 = count
+
+
+scanline_col32cb16blend_neon:
+    push        {r4-r11, lr}                    // stack ARM regs
+
+    vmov.u16    q15, #256                       // create alpha constant
+    movs        r3, r2, lsr #4                  // calc. sixteens iterations
+    vmov.u16    q14, #0x1f                      // create blue mask
+
+    beq         2f                              // if r3 == 0, branch to singles
+
+    vld4.8      {d0[], d2[], d4[], d6[]}, [r1]  // load color into four registers
+                                                //  split and duplicate them, such that
+                                                //  d0 = 8 equal red values
+                                                //  d2 = 8 equal green values
+                                                //  d4 = 8 equal blue values
+                                                //  d6 = 8 equal alpha values
+    vshll.u8    q0, d0, #5                      // shift up red and widen
+    vshll.u8    q1, d2, #6                      // shift up green and widen
+    vshll.u8    q2, d4, #5                      // shift up blue and widen
+
+    vshr.u8     d7, d6, #7                      // extract top bit of alpha
+    vaddl.u8    q3, d6, d7                      // add top bit into alpha
+    vsub.u16    q3, q15, q3                     // invert alpha
+
+1:
+    // This loop processes 16 pixels per iteration. In the comments, references to
+    // the first eight pixels are suffixed with "0" (red0, green0, blue0), 
+    // the second eight are suffixed "1".
+                                                // q8  = dst red0
+                                                // q9  = dst green0
+                                                // q10 = dst blue0
+                                                // q13 = dst red1
+                                                // q12 = dst green1
+                                                // q11 = dst blue1
+
+    vld1.16     {d20, d21, d22, d23}, [r0]      // load 16 dest pixels
+    vshr.u16    q8, q10, #11                    // shift dst red0 to low 5 bits
+    pld         [r0, #63]                       // preload next dest pixels
+    vshl.u16    q9, q10, #5                     // shift dst green0 to top 6 bits
+    vand        q10, q10, q14                   // extract dst blue0
+    vshr.u16    q9, q9, #10                     // shift dst green0 to low 6 bits
+    vmul.u16    q8, q8, q3                      // multiply dst red0 by src alpha
+    vshl.u16    q12, q11, #5                    // shift dst green1 to top 6 bits
+    vmul.u16    q9, q9, q3                      // multiply dst green0 by src alpha
+    vshr.u16    q13, q11, #11                   // shift dst red1 to low 5 bits
+    vmul.u16    q10, q10, q3                    // multiply dst blue0 by src alpha
+    vshr.u16    q12, q12, #10                   // shift dst green1 to low 6 bits
+    vand        q11, q11, q14                   // extract dst blue1
+    vadd.u16    q8, q8, q0                      // add src red to dst red0
+    vmul.u16    q13, q13, q3                    // multiply dst red1 by src alpha
+    vadd.u16    q9, q9, q1                      // add src green to dst green0 
+    vmul.u16    q12, q12, q3                    // multiply dst green1 by src alpha
+    vadd.u16    q10, q10, q2                    // add src blue to dst blue0
+    vmul.u16    q11, q11, q3                    // multiply dst blue1 by src alpha
+    vshr.u16    q8, q8, #8                      // shift down red0
+    vadd.u16    q13, q13, q0                    // add src red to dst red1
+    vshr.u16    q9, q9, #8                      // shift down green0
+    vadd.u16    q12, q12, q1                    // add src green to dst green1
+    vshr.u16    q10, q10, #8                    // shift down blue0
+    vadd.u16    q11, q11, q2                    // add src blue to dst blue1
+    vsli.u16    q10, q9, #5                     // shift & insert green0 into blue0
+    vshr.u16    q13, q13, #8                    // shift down red1
+    vsli.u16    q10, q8, #11                    // shift & insert red0 into blue0    
+    vshr.u16    q12, q12, #8                    // shift down green1
+    vshr.u16    q11, q11, #8                    // shift down blue1
+    subs        r3, r3, #1                      // decrement loop counter
+    vsli.u16    q11, q12, #5                    // shift & insert green1 into blue1
+    vsli.u16    q11, q13, #11                   // shift & insert red1 into blue1
+
+    vst1.16     {d20, d21, d22, d23}, [r0]!     // write 16 pixels back to dst
+    bne         1b                              // if count != 0, loop
+
+2:
+    ands        r3, r2, #15                     // calc. single iterations 
+    beq         4f                              // if r3 == 0, exit
+
+    ldr         r4, [r1]                        // load source color
+    mov         r5, r4, lsr #24                 // shift down alpha
+    add         r5, r5, r5, lsr #7              // add in top bit
+    rsb         r5, r5, #256                    // invert alpha
+    and         r11, r4, #0xff                  // extract red
+    ubfx        r12, r4, #8, #8                 // extract green
+    ubfx        r4, r4, #16, #8                 // extract blue
+    mov         r11, r11, lsl #5                // prescale red
+    mov         r12, r12, lsl #6                // prescale green
+    mov         r4, r4, lsl #5                  // prescale blue
+
+3:
+    ldrh        r8, [r0]                        // load dest pixel
+    subs        r3, r3, #1                      // decrement loop counter
+    mov         r6, r8, lsr #11                 // extract dest red
+    ubfx        r7, r8, #5, #6                  // extract dest green
+    and         r8, r8, #0x1f                   // extract dest blue
+
+    smlabb      r6, r6, r5, r11                 // dest red * alpha + src red
+    smlabb      r7, r7, r5, r12                 // dest green * alpha + src green
+    smlabb      r8, r8, r5, r4                  // dest blue * alpha + src blue
+
+    mov         r6, r6, lsr #8                  // shift down red
+    mov         r7, r7, lsr #8                  // shift down green
+    mov         r6, r6, lsl #11                 // shift red into 565
+    orr         r6, r7, lsl #5                  // shift green into 565
+    orr         r6, r8, lsr #8                  // shift blue into 565
+
+    strh        r6, [r0], #2                    // store pixel to dest, update ptr
+    bne         3b                              // if count != 0, loop
+4:
+
+    pop         {r4-r11, pc}                    // return
+
+
+
diff --git a/libpixelflinger/fixed.cpp b/libpixelflinger/fixed.cpp
new file mode 100644
index 0000000..de6b479
--- /dev/null
+++ b/libpixelflinger/fixed.cpp
@@ -0,0 +1,329 @@
+/* libs/pixelflinger/fixed.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+#include <stdio.h>
+
+#include <private/pixelflinger/ggl_context.h>
+#include <private/pixelflinger/ggl_fixed.h>
+
+
+// ------------------------------------------------------------------------
+
+int32_t gglRecipQNormalized(int32_t x, int* exponent)
+{
+    const int32_t s = x>>31;
+    uint32_t a = s ? -x : x;
+
+    // the result will overflow, so just set it to the biggest/inf value
+    if (ggl_unlikely(a <= 2LU)) {
+        *exponent = 0;
+        return s ? FIXED_MIN : FIXED_MAX;
+    }
+
+    // Newton-Raphson iteration:
+    // x = r*(2 - a*r)
+
+    const int32_t lz = gglClz(a);
+    a <<= lz;  // 0.32
+    uint32_t r = a;
+    // note: if a == 0x80000000, this means x was a power-of-2, in this
+    // case we don't need to compute anything. We get the reciprocal for
+    // (almost) free.
+    if (a != 0x80000000) {
+        r = (0x2E800 << (30-16)) - (r>>(2-1)); // 2.30, r = 2.90625 - 2*a
+        // 0.32 + 2.30 = 2.62 -> 2.30
+        // 2.30 + 2.30 = 4.60 -> 2.30
+        r = (((2LU<<30) - uint32_t((uint64_t(a)*r) >> 32)) * uint64_t(r)) >> 30;
+        r = (((2LU<<30) - uint32_t((uint64_t(a)*r) >> 32)) * uint64_t(r)) >> 30;
+    }
+
+    // shift right 1-bit to make room for the sign bit
+    *exponent = 30-lz-1;
+    r >>= 1;
+    return s ? -r : r;
+}
+
+int32_t gglRecipQ(GGLfixed x, int q)
+{
+    int shift;
+    x = gglRecipQNormalized(x, &shift);
+    shift += 16-q;
+    if (shift > 0)
+        x += 1L << (shift-1);   // rounding
+    x >>= shift;
+    return x;
+}    
+
+// ------------------------------------------------------------------------
+
+static const GGLfixed ggl_sqrt_reciproc_approx_tab[8] = {
+    // 1/sqrt(x) with x = 1-N/16, N=[8...1]
+    0x16A09, 0x15555, 0x143D1, 0x134BF, 0x1279A, 0x11C01, 0x111AC, 0x10865
+};
+
+GGLfixed gglSqrtRecipx(GGLfixed x)
+{
+    if (x == 0)         return FIXED_MAX;
+    if (x == FIXED_ONE) return x;
+    const GGLfixed a = x;
+    const int32_t lz = gglClz(x);
+    x = ggl_sqrt_reciproc_approx_tab[(a>>(28-lz))&0x7];
+    const int32_t exp = lz - 16;
+    if (exp <= 0)   x >>= -exp>>1;
+    else            x <<= (exp>>1) + (exp & 1);        
+    if (exp & 1) {
+        x = gglMulx(x, ggl_sqrt_reciproc_approx_tab[0])>>1;
+    }
+    // 2 Newton-Raphson iterations: x = x/2*(3-(a*x)*x)
+    x = gglMulx((x>>1),(0x30000 - gglMulx(gglMulx(a,x),x)));
+    x = gglMulx((x>>1),(0x30000 - gglMulx(gglMulx(a,x),x)));
+    return x;
+}
+
+GGLfixed gglSqrtx(GGLfixed a)
+{
+    // Compute a full precision square-root (24 bits accuracy)
+    GGLfixed r = 0;
+    GGLfixed bit = 0x800000;
+    int32_t bshift = 15;
+    do {
+        GGLfixed temp = bit + (r<<1);
+        if (bshift >= 8)    temp <<= (bshift-8);
+        else                temp >>= (8-bshift);
+        if (a >= temp) {
+            r += bit;
+            a -= temp;
+        }
+        bshift--;
+    } while (bit>>=1);
+    return r;
+}
+
+// ------------------------------------------------------------------------
+
+static const GGLfixed ggl_log_approx_tab[] = {
+    // -ln(x)/ln(2) with x = N/16, N=[8...16]
+    0xFFFF, 0xd47f, 0xad96, 0x8a62, 0x6a3f, 0x4caf, 0x3151, 0x17d6, 0x0000
+};
+
+static const GGLfixed ggl_alog_approx_tab[] = { // domain [0 - 1.0]
+	0xffff, 0xeac0, 0xd744, 0xc567, 0xb504, 0xa5fe, 0x9837, 0x8b95, 0x8000
+};
+
+GGLfixed gglPowx(GGLfixed x, GGLfixed y)
+{
+    // prerequisite: 0 <= x <= 1, and y >=0
+
+    // pow(x,y) = 2^(y*log2(x))
+    // =  2^(y*log2(x*(2^exp)*(2^-exp))))
+    // =  2^(y*(log2(X)-exp))
+    // =  2^(log2(X)*y - y*exp)
+    // =  2^( - (-log2(X)*y + y*exp) )
+    
+    int32_t exp = gglClz(x) - 16;
+    GGLfixed f = x << exp;
+    x = (f & 0x0FFF)<<4;
+    f = (f >> 12) & 0x7;
+    GGLfixed p = gglMulAddx(
+            ggl_log_approx_tab[f+1] - ggl_log_approx_tab[f], x,
+            ggl_log_approx_tab[f]);
+    p = gglMulAddx(p, y, y*exp);
+    exp = gglFixedToIntFloor(p);
+    if (exp < 31) {
+        p = gglFracx(p);
+        x = (p & 0x1FFF)<<3;
+        p >>= 13;    
+        p = gglMulAddx(
+                ggl_alog_approx_tab[p+1] - ggl_alog_approx_tab[p], x,
+                ggl_alog_approx_tab[p]);
+        p >>= exp;
+    } else {
+        p = 0;
+    }
+    return p;
+        // ( powf((a*65536.0f), (b*65536.0f)) ) * 65536.0f;
+}
+
+// ------------------------------------------------------------------------
+
+int32_t gglDivQ(GGLfixed n, GGLfixed d, int32_t i)
+{
+    //int32_t r =int32_t((int64_t(n)<<i)/d);
+    const int32_t ds = n^d;
+    if (n<0) n = -n;
+    if (d<0) d = -d;
+    int nd = gglClz(d) - gglClz(n);
+    i += nd + 1;
+    if (nd > 0) d <<= nd;
+    else        n <<= -nd;
+    uint32_t q = 0;
+
+    int j = i & 7;
+    i >>= 3;
+
+    // gcc deals with the code below pretty well.
+    // we get 3.75 cycles per bit in the main loop
+    // and 8 cycles per bit in the termination loop
+    if (ggl_likely(i)) {
+        n -= d;
+        do {
+            q <<= 8;
+            if (n>=0)   q |= 128;
+            else        n += d;
+            n = n*2 - d;
+            if (n>=0)   q |= 64;
+            else        n += d;
+            n = n*2 - d;
+            if (n>=0)   q |= 32;
+            else        n += d;
+            n = n*2 - d;
+            if (n>=0)   q |= 16;
+            else        n += d;
+            n = n*2 - d;
+            if (n>=0)   q |= 8;
+            else        n += d;
+            n = n*2 - d;
+            if (n>=0)   q |= 4;
+            else        n += d;
+            n = n*2 - d;
+            if (n>=0)   q |= 2;
+            else        n += d;
+            n = n*2 - d;
+            if (n>=0)   q |= 1;
+            else        n += d;
+            
+            if (--i == 0)
+                goto finish;
+
+            n = n*2 - d;
+        } while(true);
+        do {
+            q <<= 1;
+            n = n*2 - d;
+            if (n>=0)   q |= 1;
+            else        n += d;
+        finish: ;
+        } while (j--);
+        return (ds<0) ? -q : q;
+    }
+
+    n -= d;
+    if (n>=0)   q |= 1;
+    else        n += d;
+    j--;
+    goto finish;
+}
+
+// ------------------------------------------------------------------------
+
+// assumes that the int32_t values of a, b, and c are all positive
+// use when both a and b are larger than c
+
+template <typename T>
+static inline void swap(T& a, T& b) {
+    T t(a);
+    a = b;
+    b = t;
+}
+
+static __attribute__((noinline))
+int32_t slow_muldiv(uint32_t a, uint32_t b, uint32_t c)
+{
+	// first we compute a*b as a 64-bit integer
+    // (GCC generates umull with the code below)
+    uint64_t ab = uint64_t(a)*b;
+    uint32_t hi = ab>>32;
+    uint32_t lo = ab;
+    uint32_t result;
+
+	// now perform the division
+	if (hi >= c) {
+	overflow:
+		result = 0x7fffffff;  // basic overflow
+	} else if (hi == 0) {
+		result = lo/c;  // note: c can't be 0
+		if ((result >> 31) != 0)  // result must fit in 31 bits
+			goto overflow;
+	} else {
+		uint32_t r = hi;
+		int bits = 31;
+	    result = 0;
+		do {
+			r = (r << 1) | (lo >> 31);
+			lo <<= 1;
+			result <<= 1;
+			if (r >= c) {
+				r -= c;
+				result |= 1;
+			}
+		} while (bits--);
+	}
+	return int32_t(result);
+}
+
+// assumes a >= 0 and c >= b >= 0
+static inline
+int32_t quick_muldiv(int32_t a, int32_t b, int32_t c)
+{
+    int32_t r = 0, q = 0, i;
+    int leading = gglClz(a);
+    i = 32 - leading;
+    a <<= leading;
+    do {
+        r <<= 1;
+        if (a < 0)
+            r += b;
+        a <<= 1;
+        q <<= 1;
+        if (r >= c) {
+            r -= c;
+            q++;
+        }
+        asm(""::); // gcc generates better code this way
+        if (r >= c) {
+            r -= c;
+            q++;
+        }
+    }
+    while (--i);
+    return q;
+}
+
+// this function computes a*b/c with 64-bit intermediate accuracy
+// overflows (e.g. division by 0) are handled and return INT_MAX
+
+int32_t gglMulDivi(int32_t a, int32_t b, int32_t c)
+{
+	int32_t result;
+	int32_t sign = a^b^c;
+
+	if (a < 0) a = -a;
+	if (b < 0) b = -b;
+	if (c < 0) c = -c;
+
+    if (a < b) {
+        swap(a, b);
+    }
+    
+	if (b <= c) result = quick_muldiv(a, b, c);
+	else        result = slow_muldiv((uint32_t)a, (uint32_t)b, (uint32_t)c);
+	
+	if (sign < 0)
+		result = -result;
+	  
+    return result;
+}
diff --git a/libpixelflinger/format.cpp b/libpixelflinger/format.cpp
new file mode 100644
index 0000000..6546e8c
--- /dev/null
+++ b/libpixelflinger/format.cpp
@@ -0,0 +1,76 @@
+/* libs/pixelflinger/format.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+#include <stdio.h>
+#include <pixelflinger/format.h>
+
+namespace android {
+
+static GGLFormat const gPixelFormatInfos[] =
+{   //          Alpha    Red     Green   Blue
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  4, 32, {{32,24,   8, 0,  16, 8,  24,16 }}, GGL_RGBA },   // PIXEL_FORMAT_RGBA_8888
+    {  4, 24, {{ 0, 0,   8, 0,  16, 8,  24,16 }}, GGL_RGB  },   // PIXEL_FORMAT_RGBX_8888
+    {  3, 24, {{ 0, 0,   8, 0,  16, 8,  24,16 }}, GGL_RGB  },   // PIXEL_FORMAT_RGB_888
+    {  2, 16, {{ 0, 0,  16,11,  11, 5,   5, 0 }}, GGL_RGB  },   // PIXEL_FORMAT_RGB_565
+    {  4, 32, {{32,24,  24,16,  16, 8,   8, 0 }}, GGL_RGBA },   // PIXEL_FORMAT_BGRA_8888
+    {  2, 16, {{ 1, 0,  16,11,  11, 6,   6, 1 }}, GGL_RGBA },   // PIXEL_FORMAT_RGBA_5551
+    {  2, 16, {{ 4, 0,  16,12,  12, 8,   8, 4 }}, GGL_RGBA },   // PIXEL_FORMAT_RGBA_4444
+    {  1,  8, {{ 8, 0,   0, 0,   0, 0,   0, 0 }}, GGL_ALPHA},   // PIXEL_FORMAT_A8
+    {  1,  8, {{ 0, 0,   8, 0,   8, 0,   8, 0 }}, GGL_LUMINANCE},//PIXEL_FORMAT_L8
+    {  2, 16, {{16, 8,   8, 0,   8, 0,   8, 0 }}, GGL_LUMINANCE_ALPHA},// PIXEL_FORMAT_LA_88
+    {  1,  8, {{ 0, 0,   8, 5,   5, 2,   2, 0 }}, GGL_RGB  },   // PIXEL_FORMAT_RGB_332
+    
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    
+    {  2, 16, {{  0, 0, 16, 0,   0, 0,   0, 0 }}, GGL_DEPTH_COMPONENT},
+    {  1,  8, {{  8, 0,  0, 0,   0, 0,   0, 0 }}, GGL_STENCIL_INDEX  },
+    {  4, 24, {{  0, 0, 24, 0,   0, 0,   0, 0 }}, GGL_DEPTH_COMPONENT},
+    {  4,  8, {{ 32,24,  0, 0,   0, 0,   0, 0 }}, GGL_STENCIL_INDEX  },
+
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+    {  0,  0, {{ 0, 0,   0, 0,   0, 0,   0, 0 }},        0 },   // PIXEL_FORMAT_NONE
+
+};
+
+}; // namespace android
+
+
+const GGLFormat* gglGetPixelFormatTable(size_t* numEntries)
+{
+    if (numEntries) {
+        *numEntries = sizeof(android::gPixelFormatInfos)/sizeof(GGLFormat);
+    }
+    return android::gPixelFormatInfos;
+}
diff --git a/libpixelflinger/include/pixelflinger/format.h b/libpixelflinger/include/pixelflinger/format.h
new file mode 100644
index 0000000..82eeca4
--- /dev/null
+++ b/libpixelflinger/include/pixelflinger/format.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PIXELFLINGER_FORMAT_H
+#define ANDROID_PIXELFLINGER_FORMAT_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+enum GGLPixelFormat {
+    // these constants need to match those
+    // in graphics/PixelFormat.java, ui/PixelFormat.h, BlitHardware.h
+    GGL_PIXEL_FORMAT_UNKNOWN    =   0,
+    GGL_PIXEL_FORMAT_NONE       =   0,
+
+    GGL_PIXEL_FORMAT_RGBA_8888   =   1,  // 4x8-bit ARGB
+    GGL_PIXEL_FORMAT_RGBX_8888   =   2,  // 3x8-bit RGB stored in 32-bit chunks
+    GGL_PIXEL_FORMAT_RGB_888     =   3,  // 3x8-bit RGB
+    GGL_PIXEL_FORMAT_RGB_565     =   4,  // 16-bit RGB
+    GGL_PIXEL_FORMAT_BGRA_8888   =   5,  // 4x8-bit BGRA
+    GGL_PIXEL_FORMAT_RGBA_5551   =   6,  // 16-bit RGBA
+    GGL_PIXEL_FORMAT_RGBA_4444   =   7,  // 16-bit RGBA
+
+    GGL_PIXEL_FORMAT_A_8         =   8,  // 8-bit A
+    GGL_PIXEL_FORMAT_L_8         =   9,  // 8-bit L (R=G=B = L)
+    GGL_PIXEL_FORMAT_LA_88       = 0xA,  // 16-bit LA
+    GGL_PIXEL_FORMAT_RGB_332     = 0xB,  // 8-bit RGB (non paletted)
+
+    // reserved range. don't use.
+    GGL_PIXEL_FORMAT_RESERVED_10 = 0x10,
+    GGL_PIXEL_FORMAT_RESERVED_11 = 0x11,
+    GGL_PIXEL_FORMAT_RESERVED_12 = 0x12,
+    GGL_PIXEL_FORMAT_RESERVED_13 = 0x13,
+    GGL_PIXEL_FORMAT_RESERVED_14 = 0x14,
+    GGL_PIXEL_FORMAT_RESERVED_15 = 0x15,
+    GGL_PIXEL_FORMAT_RESERVED_16 = 0x16,
+    GGL_PIXEL_FORMAT_RESERVED_17 = 0x17,
+
+    // reserved/special formats
+    GGL_PIXEL_FORMAT_Z_16       =  0x18,
+    GGL_PIXEL_FORMAT_S_8        =  0x19,
+    GGL_PIXEL_FORMAT_SZ_24      =  0x1A,
+    GGL_PIXEL_FORMAT_SZ_8       =  0x1B,
+
+    // reserved range. don't use.
+    GGL_PIXEL_FORMAT_RESERVED_20 = 0x20,
+    GGL_PIXEL_FORMAT_RESERVED_21 = 0x21,
+};
+
+enum GGLFormatComponents {
+	GGL_STENCIL_INDEX		= 0x1901,
+	GGL_DEPTH_COMPONENT		= 0x1902,
+	GGL_ALPHA				= 0x1906,
+	GGL_RGB					= 0x1907,
+	GGL_RGBA				= 0x1908,
+	GGL_LUMINANCE			= 0x1909,
+	GGL_LUMINANCE_ALPHA		= 0x190A,
+};
+
+enum GGLFormatComponentIndex {
+    GGL_INDEX_ALPHA   = 0,
+    GGL_INDEX_RED     = 1,
+    GGL_INDEX_GREEN   = 2,
+    GGL_INDEX_BLUE    = 3,
+    GGL_INDEX_STENCIL = 0,
+    GGL_INDEX_DEPTH   = 1,
+    GGL_INDEX_Y       = 0,
+    GGL_INDEX_CB      = 1,
+    GGL_INDEX_CR      = 2,
+};
+
+typedef struct {
+#ifdef __cplusplus
+    enum {
+        ALPHA   = GGL_INDEX_ALPHA,
+        RED     = GGL_INDEX_RED,
+        GREEN   = GGL_INDEX_GREEN,
+        BLUE    = GGL_INDEX_BLUE,
+        STENCIL = GGL_INDEX_STENCIL,
+        DEPTH   = GGL_INDEX_DEPTH,
+        LUMA    = GGL_INDEX_Y,
+        CHROMAB = GGL_INDEX_CB,
+        CHROMAR = GGL_INDEX_CR,
+    };
+    inline uint32_t mask(int i) const {
+            return ((1<<(c[i].h-c[i].l))-1)<<c[i].l;
+    }
+    inline uint32_t bits(int i) const {
+            return c[i].h - c[i].l;
+    }
+#endif
+	uint8_t     size;	// bytes per pixel
+    uint8_t     bitsPerPixel;
+    union {    
+        struct {
+            uint8_t     ah;		// alpha high bit position + 1
+            uint8_t     al;		// alpha low bit position
+            uint8_t     rh;		// red high bit position + 1
+            uint8_t     rl;		// red low bit position
+            uint8_t     gh;		// green high bit position + 1
+            uint8_t     gl;		// green low bit position
+            uint8_t     bh;		// blue high bit position + 1
+            uint8_t     bl;		// blue low bit position
+        };
+        struct {
+            uint8_t h;
+            uint8_t l;
+        } __attribute__((__packed__)) c[4];        
+    } __attribute__((__packed__));
+	uint16_t    components;	// GGLFormatComponents
+} GGLFormat;
+
+
+#ifdef __cplusplus
+extern "C" const GGLFormat* gglGetPixelFormatTable(size_t* numEntries = 0);
+#else
+const GGLFormat* gglGetPixelFormatTable(size_t* numEntries);
+#endif
+
+
+// ----------------------------------------------------------------------------
+
+#endif // ANDROID_PIXELFLINGER_FORMAT_H
diff --git a/libpixelflinger/include/pixelflinger/pixelflinger.h b/libpixelflinger/include/pixelflinger/pixelflinger.h
new file mode 100644
index 0000000..8a2b442
--- /dev/null
+++ b/libpixelflinger/include/pixelflinger/pixelflinger.h
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PIXELFLINGER_H
+#define ANDROID_PIXELFLINGER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <pixelflinger/format.h>
+
+// GGL types
+
+typedef int8_t			GGLbyte;		// b
+typedef int16_t			GGLshort;		// s
+typedef int32_t			GGLint;			// i
+typedef ssize_t			GGLsizei;		// i
+typedef int32_t			GGLfixed;		// x
+typedef int32_t			GGLclampx;		// x
+typedef float			GGLfloat;		// f
+typedef float			GGLclampf;		// f
+typedef double			GGLdouble;		// d
+typedef double			GGLclampd;		// d
+typedef uint8_t			GGLubyte;		// ub
+typedef uint8_t			GGLboolean;		// ub
+typedef uint16_t		GGLushort;		// us
+typedef uint32_t		GGLuint;		// ui
+typedef unsigned int	GGLenum;		// ui
+typedef unsigned int	GGLbitfield;	// ui
+typedef void			GGLvoid;
+typedef int32_t         GGLfixed32;
+typedef	int32_t         GGLcolor;
+typedef int32_t         GGLcoord;
+
+// ----------------------------------------------------------------------------
+
+#define GGL_MAX_VIEWPORT_DIMS           4096
+#define GGL_MAX_TEXTURE_SIZE            4096
+#define GGL_MAX_ALIASED_POINT_SIZE      0x7FFFFFF
+#define GGL_MAX_SMOOTH_POINT_SIZE       2048
+#define GGL_MAX_SMOOTH_LINE_WIDTH       2048
+
+// ----------------------------------------------------------------------------
+
+// All these names are compatible with their OpenGL equivalents
+// some of them are listed only for completeness
+enum GGLNames {
+	GGL_FALSE						= 0,
+	GGL_TRUE						= 1,
+
+	// enable/disable
+    GGL_SCISSOR_TEST                = 0x0C11,
+	GGL_TEXTURE_2D					= 0x0DE1,
+	GGL_ALPHA_TEST					= 0x0BC0,
+	GGL_BLEND						= 0x0BE2,
+	GGL_COLOR_LOGIC_OP				= 0x0BF2,
+	GGL_DITHER						= 0x0BD0,
+	GGL_STENCIL_TEST				= 0x0B90,
+	GGL_DEPTH_TEST					= 0x0B71,
+    GGL_AA                          = 0x80000001,
+    GGL_W_LERP                      = 0x80000004,
+    GGL_POINT_SMOOTH_NICE           = 0x80000005,
+
+    // buffers, pixel drawing/reading
+    GGL_COLOR                       = 0x1800,
+    
+    // fog
+    GGL_FOG                         = 0x0B60,
+    
+	// shade model
+	GGL_FLAT						= 0x1D00,
+	GGL_SMOOTH						= 0x1D01,
+
+	// Texture parameter name
+	GGL_TEXTURE_MIN_FILTER			= 0x2801,
+	GGL_TEXTURE_MAG_FILTER			= 0x2800,
+	GGL_TEXTURE_WRAP_S				= 0x2802,
+	GGL_TEXTURE_WRAP_T				= 0x2803,
+	GGL_TEXTURE_WRAP_R				= 0x2804,
+
+	// Texture Filter	
+	GGL_NEAREST						= 0x2600,
+	GGL_LINEAR						= 0x2601,
+	GGL_NEAREST_MIPMAP_NEAREST		= 0x2700,
+	GGL_LINEAR_MIPMAP_NEAREST		= 0x2701,
+	GGL_NEAREST_MIPMAP_LINEAR		= 0x2702,
+	GGL_LINEAR_MIPMAP_LINEAR		= 0x2703,
+
+	// Texture Wrap Mode
+	GGL_CLAMP						= 0x2900,
+	GGL_REPEAT						= 0x2901,
+    GGL_CLAMP_TO_EDGE               = 0x812F,
+
+	// Texture Env Mode
+	GGL_REPLACE						= 0x1E01,
+	GGL_MODULATE					= 0x2100,
+	GGL_DECAL						= 0x2101,
+	GGL_ADD							= 0x0104,
+
+	// Texture Env Parameter
+	GGL_TEXTURE_ENV_MODE			= 0x2200,
+	GGL_TEXTURE_ENV_COLOR			= 0x2201,
+
+	// Texture Env Target
+	GGL_TEXTURE_ENV					= 0x2300,
+
+    // Texture coord generation
+    GGL_TEXTURE_GEN_MODE            = 0x2500,
+    GGL_S                           = 0x2000,
+    GGL_T                           = 0x2001,
+    GGL_R                           = 0x2002,
+    GGL_Q                           = 0x2003,
+    GGL_ONE_TO_ONE                  = 0x80000002,
+    GGL_AUTOMATIC                   = 0x80000003,
+
+    // AlphaFunction
+    GGL_NEVER                       = 0x0200,
+    GGL_LESS                        = 0x0201,
+    GGL_EQUAL                       = 0x0202,
+    GGL_LEQUAL                      = 0x0203,
+    GGL_GREATER                     = 0x0204,
+    GGL_NOTEQUAL                    = 0x0205,
+    GGL_GEQUAL                      = 0x0206,
+    GGL_ALWAYS                      = 0x0207,
+
+    // LogicOp
+    GGL_CLEAR                       = 0x1500,   // 0
+    GGL_AND                         = 0x1501,   // s & d
+    GGL_AND_REVERSE                 = 0x1502,   // s & ~d
+    GGL_COPY                        = 0x1503,   // s
+    GGL_AND_INVERTED                = 0x1504,   // ~s & d
+    GGL_NOOP                        = 0x1505,   // d
+    GGL_XOR                         = 0x1506,   // s ^ d
+    GGL_OR                          = 0x1507,   // s | d
+    GGL_NOR                         = 0x1508,   // ~(s | d)
+    GGL_EQUIV                       = 0x1509,   // ~(s ^ d)
+    GGL_INVERT                      = 0x150A,   // ~d
+    GGL_OR_REVERSE                  = 0x150B,   // s | ~d
+    GGL_COPY_INVERTED               = 0x150C,   // ~s 
+    GGL_OR_INVERTED                 = 0x150D,   // ~s | d
+    GGL_NAND                        = 0x150E,   // ~(s & d)
+    GGL_SET                         = 0x150F,   // 1
+
+	// blending equation & function
+	GGL_ZERO                        = 0,		// SD
+	GGL_ONE                         = 1,		// SD
+	GGL_SRC_COLOR                   = 0x0300,	//  D
+	GGL_ONE_MINUS_SRC_COLOR         = 0x0301,	//	D
+	GGL_SRC_ALPHA                   = 0x0302,	// SD
+	GGL_ONE_MINUS_SRC_ALPHA			= 0x0303,	// SD
+	GGL_DST_ALPHA					= 0x0304,	// SD
+	GGL_ONE_MINUS_DST_ALPHA			= 0x0305,	// SD
+	GGL_DST_COLOR					= 0x0306,	// S
+	GGL_ONE_MINUS_DST_COLOR			= 0x0307,	// S
+	GGL_SRC_ALPHA_SATURATE			= 0x0308,	// S
+    
+    // clear bits
+    GGL_DEPTH_BUFFER_BIT            = 0x00000100,
+    GGL_STENCIL_BUFFER_BIT          = 0x00000400,
+    GGL_COLOR_BUFFER_BIT            = 0x00004000,
+
+    // errors
+    GGL_NO_ERROR                    = 0,
+    GGL_INVALID_ENUM                = 0x0500,
+    GGL_INVALID_VALUE               = 0x0501,
+    GGL_INVALID_OPERATION           = 0x0502,
+    GGL_STACK_OVERFLOW              = 0x0503,
+    GGL_STACK_UNDERFLOW             = 0x0504,
+    GGL_OUT_OF_MEMORY               = 0x0505
+};
+
+// ----------------------------------------------------------------------------
+
+typedef struct {
+    GGLsizei    version;    // always set to sizeof(GGLSurface)
+    GGLuint     width;      // width in pixels
+    GGLuint     height;     // height in pixels
+    GGLint      stride;     // stride in pixels
+    GGLubyte*   data;       // pointer to the bits
+    GGLubyte    format;     // pixel format
+    GGLubyte    rfu[3];     // must be zero
+    // these values are dependent on the used format
+    union {
+        GGLint  compressedFormat;
+        GGLint  vstride;
+    };
+    void*       reserved;
+} GGLSurface;
+
+
+typedef struct {
+    // immediate rendering
+    void (*pointx)(void *con, const GGLcoord* v, GGLcoord r);
+    void (*linex)(void *con, 
+            const GGLcoord* v0, const GGLcoord* v1, GGLcoord width);
+    void (*recti)(void* c, GGLint l, GGLint t, GGLint r, GGLint b); 
+    void (*trianglex)(void* c,
+            GGLcoord const* v0, GGLcoord const* v1, GGLcoord const* v2);
+
+    // scissor
+    void (*scissor)(void* c, GGLint x, GGLint y, GGLsizei width, GGLsizei height);
+
+    // Set the textures and color buffers
+    void (*activeTexture)(void* c, GGLuint tmu);
+    void (*bindTexture)(void* c, const GGLSurface* surface);
+    void (*colorBuffer)(void* c, const GGLSurface* surface);
+    void (*readBuffer)(void* c, const GGLSurface* surface);
+    void (*depthBuffer)(void* c, const GGLSurface* surface);
+    void (*bindTextureLod)(void* c, GGLuint tmu, const GGLSurface* surface);
+
+    // enable/disable features
+    void (*enable)(void* c, GGLenum name);
+    void (*disable)(void* c, GGLenum name);
+    void (*enableDisable)(void* c, GGLenum name, GGLboolean en);
+
+    // specify the fragment's color
+    void (*shadeModel)(void* c, GGLenum mode);
+    void (*color4xv)(void* c, const GGLclampx* color);
+    // specify color iterators (16.16)
+    void (*colorGrad12xv)(void* c, const GGLcolor* grad);
+
+    // specify Z coordinate iterators (0.32)
+    void (*zGrad3xv)(void* c, const GGLfixed32* grad);
+
+    // specify W coordinate iterators (16.16)
+    void (*wGrad3xv)(void* c, const GGLfixed* grad);
+
+    // specify fog iterator & color (16.16)
+    void (*fogGrad3xv)(void* c, const GGLfixed* grad);
+    void (*fogColor3xv)(void* c, const GGLclampx* color);
+
+    // specify blending parameters
+    void (*blendFunc)(void* c, GGLenum src, GGLenum dst);
+    void (*blendFuncSeparate)(void* c,  GGLenum src, GGLenum dst,
+                                        GGLenum srcAlpha, GGLenum dstAplha);
+
+    // texture environnement (REPLACE / MODULATE / DECAL / BLEND)
+    void (*texEnvi)(void* c,    GGLenum target,
+                                GGLenum pname,
+                                GGLint param);
+
+    void (*texEnvxv)(void* c, GGLenum target,
+            GGLenum pname, const GGLfixed* params);
+
+    // texture parameters (Wrapping, filter)
+    void (*texParameteri)(void* c,  GGLenum target,
+                                    GGLenum pname,
+                                    GGLint param);
+
+    // texture iterators (16.16)
+    void (*texCoord2i)(void* c, GGLint s, GGLint t);
+    void (*texCoord2x)(void* c, GGLfixed s, GGLfixed t);
+    
+    // s, dsdx, dsdy, scale, t, dtdx, dtdy, tscale
+    // This api uses block floating-point for S and T texture coordinates.
+    // All values are given in 16.16, scaled by 'scale'. In other words,
+    // set scale to 0, for 16.16 values.
+    void (*texCoordGradScale8xv)(void* c, GGLint tmu, const int32_t* grad8);
+    
+    void (*texGeni)(void* c, GGLenum coord, GGLenum pname, GGLint param);
+
+    // masking
+    void (*colorMask)(void* c,  GGLboolean red,
+                                GGLboolean green,
+                                GGLboolean blue,
+                                GGLboolean alpha);
+
+    void (*depthMask)(void* c, GGLboolean flag);
+
+    void (*stencilMask)(void* c, GGLuint mask);
+
+    // alpha func
+    void (*alphaFuncx)(void* c, GGLenum func, GGLclampx ref);
+
+    // depth func
+    void (*depthFunc)(void* c, GGLenum func);
+
+    // logic op
+    void (*logicOp)(void* c, GGLenum opcode); 
+
+    // clear
+    void (*clear)(void* c, GGLbitfield mask);
+    void (*clearColorx)(void* c,
+            GGLclampx r, GGLclampx g, GGLclampx b, GGLclampx a);
+    void (*clearDepthx)(void* c, GGLclampx depth);
+    void (*clearStencil)(void* c, GGLint s);
+
+    // framebuffer operations
+    void (*copyPixels)(void* c, GGLint x, GGLint y,
+            GGLsizei width, GGLsizei height, GGLenum type);
+    void (*rasterPos2x)(void* c, GGLfixed x, GGLfixed y);
+    void (*rasterPos2i)(void* c, GGLint x, GGLint y);
+} GGLContext;
+
+// ----------------------------------------------------------------------------
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// construct / destroy the context
+ssize_t gglInit(GGLContext** context);
+ssize_t gglUninit(GGLContext* context);
+
+GGLint gglBitBlit(
+        GGLContext* c,
+        int tmu,
+        GGLint crop[4],
+        GGLint where[4]);
+
+#ifdef __cplusplus
+};
+#endif
+
+// ----------------------------------------------------------------------------
+
+#endif // ANDROID_PIXELFLINGER_H
diff --git a/libpixelflinger/include/private/pixelflinger/ggl_context.h b/libpixelflinger/include/private/pixelflinger/ggl_context.h
new file mode 100644
index 0000000..563b0f1
--- /dev/null
+++ b/libpixelflinger/include/private/pixelflinger/ggl_context.h
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_GGL_CONTEXT_H
+#define ANDROID_GGL_CONTEXT_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/types.h>
+#include <endian.h>
+
+#include <pixelflinger/pixelflinger.h>
+#include <private/pixelflinger/ggl_fixed.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+
+inline uint32_t GGL_RGBA_TO_HOST(uint32_t v) {
+    return v;
+}
+inline uint32_t GGL_HOST_TO_RGBA(uint32_t v) {
+    return v;
+}
+
+#else
+
+inline uint32_t GGL_RGBA_TO_HOST(uint32_t v) {
+#if defined(__mips__) && __mips_isa_rev>=2
+    uint32_t r;
+    __asm__("wsbh %0, %1;"
+        "rotr %0, %0, 16"
+        : "=r" (r)
+        : "r" (v)
+        );
+    return r;
+#else
+    return (v<<24) | (v>>24) | ((v<<8)&0xff0000) | ((v>>8)&0xff00);
+#endif
+}
+inline uint32_t GGL_HOST_TO_RGBA(uint32_t v) {
+#if defined(__mips__) && __mips_isa_rev>=2
+    uint32_t r;
+    __asm__("wsbh %0, %1;"
+        "rotr %0, %0, 16"
+        : "=r" (r)
+        : "r" (v)
+        );
+    return r;
+#else
+    return (v<<24) | (v>>24) | ((v<<8)&0xff0000) | ((v>>8)&0xff00);
+#endif
+}
+
+#endif
+
+// ----------------------------------------------------------------------------
+
+const int GGL_DITHER_BITS = 6;  // dither weights stored on 6 bits
+const int GGL_DITHER_ORDER_SHIFT= 3;
+const int GGL_DITHER_ORDER      = (1<<GGL_DITHER_ORDER_SHIFT);
+const int GGL_DITHER_SIZE       = GGL_DITHER_ORDER * GGL_DITHER_ORDER;
+const int GGL_DITHER_MASK       = GGL_DITHER_ORDER-1;
+
+// ----------------------------------------------------------------------------
+
+const int GGL_SUBPIXEL_BITS = 4;
+
+// TRI_FRACTION_BITS defines the number of bits we want to use
+// for the sub-pixel coordinates during the edge stepping, the
+// value shouldn't be more than 7, or bad things are going to
+// happen when drawing large triangles (8 doesn't work because
+// 32 bit muls will loose the sign bit)
+
+#define  TRI_FRACTION_BITS  (GGL_SUBPIXEL_BITS)
+#define  TRI_ONE            (1 << TRI_FRACTION_BITS)
+#define  TRI_HALF           (1 << (TRI_FRACTION_BITS-1))
+#define  TRI_FROM_INT(x)    ((x) << TRI_FRACTION_BITS)
+#define  TRI_FRAC(x)        ((x)                 &  (TRI_ONE-1))
+#define  TRI_FLOOR(x)       ((x)                 & ~(TRI_ONE-1))
+#define  TRI_CEIL(x)        (((x) + (TRI_ONE-1)) & ~(TRI_ONE-1))
+#define  TRI_ROUND(x)       (((x) +  TRI_HALF  ) & ~(TRI_ONE-1))
+
+#define  TRI_ROUDNING       (1 << (16 - TRI_FRACTION_BITS - 1))
+#define  TRI_FROM_FIXED(x)  (((x)+TRI_ROUDNING) >> (16-TRI_FRACTION_BITS))
+
+#define  TRI_SNAP_NEXT_HALF(x)   (TRI_CEIL((x)+TRI_HALF) - TRI_HALF)
+#define  TRI_SNAP_PREV_HALF(x)   (TRI_CEIL((x)-TRI_HALF) - TRI_HALF)
+
+// ----------------------------------------------------------------------------
+
+const int GGL_COLOR_BITS = 24;
+
+// To maintain 8-bits color chanels, with a maximum GGLSurface
+// size of 4096 and GGL_SUBPIXEL_BITS=4, we need 8 + 12 + 4 = 24 bits
+// for encoding the color iterators
+
+inline GGLcolor gglFixedToIteratedColor(GGLfixed c) {
+    return (c << 8) - c;
+}
+
+// ----------------------------------------------------------------------------
+
+template<bool> struct CTA;
+template<> struct CTA<true> { };
+
+#define GGL_CONTEXT(con, c)         context_t *(con) = static_cast<context_t *>(c) /* NOLINT */
+#define GGL_OFFSETOF(field)         uintptr_t(&(((context_t*)0)->field))
+#define GGL_INIT_PROC(p, f)         p.f = ggl_ ## f;
+#define GGL_BETWEEN(x, L, H)        (uint32_t((x)-(L)) <= ((H)-(L)))
+
+#define ggl_likely(x)	__builtin_expect(!!(x), 1)
+#define ggl_unlikely(x)	__builtin_expect(!!(x), 0)
+
+const int GGL_TEXTURE_UNIT_COUNT    = 2;
+const int GGL_TMU_STATE             = 0x00000001;
+const int GGL_CB_STATE              = 0x00000002;
+const int GGL_PIXEL_PIPELINE_STATE  = 0x00000004;
+
+// ----------------------------------------------------------------------------
+
+#define GGL_RESERVE_NEEDS(name, l, s)                               \
+    const uint32_t  GGL_NEEDS_##name##_MASK = (((1LU<<(s))-1)<<(l));  \
+    const uint32_t  GGL_NEEDS_##name##_SHIFT = (l);
+
+#define GGL_BUILD_NEEDS(val, name)                                  \
+    (((val)<<(GGL_NEEDS_##name##_SHIFT)) & GGL_NEEDS_##name##_MASK)
+
+#define GGL_READ_NEEDS(name, n)                                     \
+    (uint32_t((n) & GGL_NEEDS_##name##_MASK) >> GGL_NEEDS_##name##_SHIFT)
+
+#define GGL_NEED_MASK(name)     (uint32_t(GGL_NEEDS_##name##_MASK))
+#define GGL_NEED(name, val)     GGL_BUILD_NEEDS(val, name)
+
+GGL_RESERVE_NEEDS( CB_FORMAT,       0, 6 )
+GGL_RESERVE_NEEDS( SHADE,           6, 1 )
+GGL_RESERVE_NEEDS( W,               7, 1 )
+GGL_RESERVE_NEEDS( BLEND_SRC,       8, 4 )
+GGL_RESERVE_NEEDS( BLEND_DST,      12, 4 )
+GGL_RESERVE_NEEDS( BLEND_SRCA,     16, 4 )
+GGL_RESERVE_NEEDS( BLEND_DSTA,     20, 4 )
+GGL_RESERVE_NEEDS( LOGIC_OP,       24, 4 )
+GGL_RESERVE_NEEDS( MASK_ARGB,      28, 4 )
+
+GGL_RESERVE_NEEDS( P_ALPHA_TEST,    0, 3 )
+GGL_RESERVE_NEEDS( P_AA,            3, 1 )
+GGL_RESERVE_NEEDS( P_DEPTH_TEST,    4, 3 )
+GGL_RESERVE_NEEDS( P_MASK_Z,        7, 1 )
+GGL_RESERVE_NEEDS( P_DITHER,        8, 1 )
+GGL_RESERVE_NEEDS( P_FOG,           9, 1 )
+GGL_RESERVE_NEEDS( P_RESERVED1,    10,22 )
+
+GGL_RESERVE_NEEDS( T_FORMAT,        0, 6 )
+GGL_RESERVE_NEEDS( T_RESERVED0,     6, 1 )
+GGL_RESERVE_NEEDS( T_POT,           7, 1 )
+GGL_RESERVE_NEEDS( T_S_WRAP,        8, 2 )
+GGL_RESERVE_NEEDS( T_T_WRAP,       10, 2 )
+GGL_RESERVE_NEEDS( T_ENV,          12, 3 )
+GGL_RESERVE_NEEDS( T_LINEAR,       15, 1 )
+
+const int GGL_NEEDS_WRAP_CLAMP_TO_EDGE  = 0;
+const int GGL_NEEDS_WRAP_REPEAT         = 1;
+const int GGL_NEEDS_WRAP_11             = 2;
+
+inline uint32_t ggl_wrap_to_needs(uint32_t e) {
+    switch (e) {
+    case GGL_CLAMP:         return GGL_NEEDS_WRAP_CLAMP_TO_EDGE;
+    case GGL_REPEAT:        return GGL_NEEDS_WRAP_REPEAT;
+    }
+    return 0;
+}
+
+inline uint32_t ggl_blendfactor_to_needs(uint32_t b) {
+    if (b <= 1) return b;
+    return (b & 0xF)+2;
+}
+
+inline uint32_t ggl_needs_to_blendfactor(uint32_t n) {
+    if (n <= 1) return n;
+    return (n - 2) + 0x300;
+}
+
+inline uint32_t ggl_env_to_needs(uint32_t e) {
+    switch (e) {
+    case GGL_REPLACE:   return 0;
+    case GGL_MODULATE:  return 1;
+    case GGL_DECAL:     return 2;
+    case GGL_BLEND:     return 3;
+    case GGL_ADD:       return 4;
+    }
+    return 0;
+}
+
+inline uint32_t ggl_needs_to_env(uint32_t n) {
+    const uint32_t envs[] = { GGL_REPLACE, GGL_MODULATE, 
+            GGL_DECAL, GGL_BLEND, GGL_ADD };
+    return envs[n];
+
+}
+
+// ----------------------------------------------------------------------------
+
+enum {
+    GGL_ENABLE_BLENDING     = 0x00000001,
+    GGL_ENABLE_SMOOTH       = 0x00000002,
+    GGL_ENABLE_AA           = 0x00000004,
+    GGL_ENABLE_LOGIC_OP     = 0x00000008,
+    GGL_ENABLE_ALPHA_TEST   = 0x00000010,
+    GGL_ENABLE_SCISSOR_TEST = 0x00000020,
+    GGL_ENABLE_TMUS         = 0x00000040,
+    GGL_ENABLE_DEPTH_TEST   = 0x00000080,
+    GGL_ENABLE_STENCIL_TEST = 0x00000100,
+    GGL_ENABLE_W            = 0x00000200,
+    GGL_ENABLE_DITHER       = 0x00000400,
+    GGL_ENABLE_FOG          = 0x00000800,
+    GGL_ENABLE_POINT_AA_NICE= 0x00001000
+};
+
+// ----------------------------------------------------------------------------
+
+struct needs_filter_t;
+struct needs_t {
+    inline int match(const needs_filter_t& filter);
+    inline bool operator == (const needs_t& rhs) const {
+        return  (n==rhs.n) &&
+                (p==rhs.p) &&
+                (t[0]==rhs.t[0]) &&
+                (t[1]==rhs.t[1]);
+    }
+    inline bool operator != (const needs_t& rhs) const {
+        return !operator == (rhs);
+    }
+    uint32_t    n;
+    uint32_t    p;
+    uint32_t    t[GGL_TEXTURE_UNIT_COUNT];
+};
+
+inline int compare_type(const needs_t& lhs, const needs_t& rhs) {
+    return memcmp(&lhs, &rhs, sizeof(needs_t));
+}
+
+struct needs_filter_t {
+    needs_t     value;
+    needs_t     mask;
+};
+
+int needs_t::match(const needs_filter_t& filter) {
+    uint32_t result = 
+        ((filter.value.n ^ n)       & filter.mask.n)    |
+        ((filter.value.p ^ p)       & filter.mask.p)    |
+        ((filter.value.t[0] ^ t[0]) & filter.mask.t[0]) |
+        ((filter.value.t[1] ^ t[1]) & filter.mask.t[1]);
+    return (result == 0);
+}
+
+// ----------------------------------------------------------------------------
+
+struct context_t;
+class Assembly;
+
+struct blend_state_t {
+	uint32_t			src;
+	uint32_t			dst;
+	uint32_t			src_alpha;
+	uint32_t			dst_alpha;
+	uint8_t				reserved;
+	uint8_t				alpha_separate;
+	uint8_t				operation;
+	uint8_t				equation;
+};
+
+struct mask_state_t {
+    uint8_t             color;
+    uint8_t             depth;
+    uint32_t            stencil;
+};
+
+struct clear_state_t {
+    GGLclampx           r;
+    GGLclampx           g;
+    GGLclampx           b;
+    GGLclampx           a;
+    GGLclampx           depth;
+    GGLint              stencil;
+    uint32_t            colorPacked;
+    uint32_t            depthPacked;
+    uint32_t            stencilPacked;
+    uint32_t            dirty;
+};
+
+struct fog_state_t {
+    uint8_t     color[4];
+};
+
+struct logic_op_state_t {
+    uint16_t            opcode;
+};
+
+struct alpha_test_state_t {
+    uint16_t            func;
+    GGLcolor            ref;
+};
+
+struct depth_test_state_t {
+    uint16_t            func;
+    GGLclampx           clearValue;
+};
+
+struct scissor_t {
+    uint32_t            user_left;
+    uint32_t            user_right;
+    uint32_t            user_top;
+    uint32_t            user_bottom;
+    uint32_t            left;
+    uint32_t            right;
+    uint32_t            top;
+    uint32_t            bottom;
+};
+
+struct pixel_t {
+    uint32_t    c[4];
+    uint8_t     s[4];
+};
+
+struct surface_t {
+    union {
+        GGLSurface          s;
+        // Keep the following struct field types in line with the corresponding
+        // GGLSurface fields to avoid mismatches leading to errors.
+        struct {
+            GGLsizei        reserved;
+            GGLuint         width;
+            GGLuint         height;
+            GGLint          stride;
+            GGLubyte*       data;
+            GGLubyte        format;
+            GGLubyte        dirty;
+            GGLubyte        pad[2];
+        };
+    };
+    void                (*read) (const surface_t* s, context_t* c,
+                                uint32_t x, uint32_t y, pixel_t* pixel);
+    void                (*write)(const surface_t* s, context_t* c,
+                                uint32_t x, uint32_t y, const pixel_t* pixel);
+};
+
+// ----------------------------------------------------------------------------
+
+struct texture_shade_t {
+    union {
+        struct {
+            int32_t             is0;
+            int32_t             idsdx;
+            int32_t             idsdy;
+            int                 sscale;
+            int32_t             it0;
+            int32_t             idtdx;
+            int32_t             idtdy;
+            int                 tscale;
+        };
+        struct {
+            int32_t             v;
+            int32_t             dx;
+            int32_t             dy;
+            int                 scale;
+        } st[2];
+    };
+};
+
+struct texture_iterators_t {
+    // these are not encoded in the same way than in the
+    // texture_shade_t structure
+    union {
+        struct {
+            GGLfixed			ydsdy;
+            GGLfixed            dsdx;
+            GGLfixed            dsdy;
+            int                 sscale;
+            GGLfixed			ydtdy;
+            GGLfixed            dtdx;
+            GGLfixed            dtdy;
+            int                 tscale;
+        };
+        struct {
+            GGLfixed			ydvdy;
+            GGLfixed            dvdx;
+            GGLfixed            dvdy;
+            int                 scale;
+        } st[2];
+    };
+};
+
+struct texture_t {
+	surface_t			surface;
+	texture_iterators_t	iterators;
+    texture_shade_t     shade;
+	uint32_t			s_coord;
+	uint32_t            t_coord;
+	uint16_t			s_wrap;
+	uint16_t            t_wrap;
+	uint16_t            min_filter;
+	uint16_t            mag_filter;
+    uint16_t            env;
+    uint8_t             env_color[4];
+	uint8_t				enable;
+	uint8_t				dirty;
+};
+
+struct raster_t {
+    GGLfixed            x;
+    GGLfixed            y;
+};
+
+struct framebuffer_t {
+    surface_t           color;
+    surface_t           read;
+	surface_t			depth;
+	surface_t			stencil;
+    int16_t             *coverage;
+    size_t              coverageBufferSize;
+};
+
+// ----------------------------------------------------------------------------
+
+struct iterators_t {
+	int32_t             xl;
+	int32_t             xr;
+    int32_t             y;
+	GGLcolor			ydady;
+	GGLcolor			ydrdy;
+	GGLcolor			ydgdy;
+	GGLcolor			ydbdy;
+	GGLfixed			ydzdy;
+	GGLfixed			ydwdy;
+	GGLfixed			ydfdy;
+};
+
+struct shade_t {
+	GGLcolor			a0;
+    GGLcolor            dadx;
+    GGLcolor            dady;
+	GGLcolor			r0;
+    GGLcolor            drdx;
+    GGLcolor            drdy;
+	GGLcolor			g0;
+    GGLcolor            dgdx;
+    GGLcolor            dgdy;
+	GGLcolor			b0;
+    GGLcolor            dbdx;
+    GGLcolor            dbdy;
+	uint32_t            z0;
+    GGLfixed32          dzdx;
+    GGLfixed32          dzdy;
+	GGLfixed            w0;
+    GGLfixed            dwdx;
+    GGLfixed            dwdy;
+	uint32_t			f0;
+    GGLfixed            dfdx;
+    GGLfixed            dfdy;
+};
+
+// these are used in the generated code
+// we use this mirror structure to improve
+// data locality in the pixel pipeline
+struct generated_tex_vars_t {
+    uint32_t    width;
+    uint32_t    height;
+    uint32_t    stride;
+    uintptr_t   data;
+    int32_t     dsdx;
+    int32_t     dtdx;
+    int32_t     spill[2];
+};
+
+struct generated_vars_t {
+    struct {
+        int32_t c;
+        int32_t dx;
+    } argb[4];
+    int32_t     aref;
+    int32_t     dzdx;
+    int32_t     zbase;
+    int32_t     f;
+    int32_t     dfdx;
+    int32_t     spill[3];
+    generated_tex_vars_t    texture[GGL_TEXTURE_UNIT_COUNT];
+    int32_t     rt;
+    int32_t     lb;
+};
+
+// ----------------------------------------------------------------------------
+
+struct state_t {
+	framebuffer_t		buffers;
+	texture_t			texture[GGL_TEXTURE_UNIT_COUNT];
+    scissor_t           scissor;
+    raster_t            raster;
+	blend_state_t		blend;
+    alpha_test_state_t  alpha_test;
+    depth_test_state_t  depth_test;
+    mask_state_t        mask;
+    clear_state_t       clear;
+    fog_state_t         fog;
+    logic_op_state_t    logic_op;
+    uint32_t            enables;
+    uint32_t            enabled_tmu;
+    needs_t             needs;
+};
+
+// ----------------------------------------------------------------------------
+
+struct context_t {
+	GGLContext          procs;
+	state_t             state;
+    shade_t             shade;
+	iterators_t         iterators;
+    generated_vars_t    generated_vars                __attribute__((aligned(32)));
+    uint8_t             ditherMatrix[GGL_DITHER_SIZE] __attribute__((aligned(32)));
+    uint32_t            packed;
+    uint32_t            packed8888;
+    const GGLFormat*    formats;
+    uint32_t            dirty;
+    texture_t*          activeTMU;
+    uint32_t            activeTMUIndex;
+
+    void                (*init_y)(context_t* c, int32_t y);
+	void                (*step_y)(context_t* c);
+	void                (*scanline)(context_t* c);
+    void                (*span)(context_t* c);
+    void                (*rect)(context_t* c, size_t yc);
+    
+    void*               base;
+    Assembly*           scanline_as;
+    GGLenum             error;
+};
+
+// ----------------------------------------------------------------------------
+
+void ggl_init_context(context_t* context);
+void ggl_uninit_context(context_t* context);
+void ggl_error(context_t* c, GGLenum error);
+int64_t ggl_system_time();
+
+// ----------------------------------------------------------------------------
+
+};
+
+#endif // ANDROID_GGL_CONTEXT_H
+
diff --git a/libpixelflinger/include/private/pixelflinger/ggl_fixed.h b/libpixelflinger/include/private/pixelflinger/ggl_fixed.h
new file mode 100644
index 0000000..4217a89
--- /dev/null
+++ b/libpixelflinger/include/private/pixelflinger/ggl_fixed.h
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_GGL_FIXED_H
+#define ANDROID_GGL_FIXED_H
+
+#include <math.h>
+#include <pixelflinger/pixelflinger.h>
+
+// ----------------------------------------------------------------------------
+
+#define CONST           __attribute__((const))
+#define ALWAYS_INLINE   __attribute__((always_inline))
+
+const GGLfixed FIXED_BITS = 16;
+const GGLfixed FIXED_EPSILON  = 1;
+const GGLfixed FIXED_ONE  = 1L<<FIXED_BITS;
+const GGLfixed FIXED_HALF = 1L<<(FIXED_BITS-1);
+const GGLfixed FIXED_MIN  = 0x80000000L;
+const GGLfixed FIXED_MAX  = 0x7FFFFFFFL;
+
+inline GGLfixed gglIntToFixed(GGLfixed i)       ALWAYS_INLINE ;
+inline GGLfixed gglFixedToIntRound(GGLfixed f)  ALWAYS_INLINE ;
+inline GGLfixed gglFixedToIntFloor(GGLfixed f)  ALWAYS_INLINE ;
+inline GGLfixed gglFixedToIntCeil(GGLfixed f)   ALWAYS_INLINE ;
+inline GGLfixed gglFracx(GGLfixed v)            ALWAYS_INLINE ;
+inline GGLfixed gglFloorx(GGLfixed v)           ALWAYS_INLINE ;
+inline GGLfixed gglCeilx(GGLfixed v)            ALWAYS_INLINE ;
+inline GGLfixed gglCenterx(GGLfixed v)          ALWAYS_INLINE ;
+inline GGLfixed gglRoundx(GGLfixed v)           ALWAYS_INLINE ;
+
+GGLfixed gglIntToFixed(GGLfixed i) {
+    return i<<FIXED_BITS;
+}
+GGLfixed gglFixedToIntRound(GGLfixed f) {
+    return (f + FIXED_HALF)>>FIXED_BITS;
+}
+GGLfixed gglFixedToIntFloor(GGLfixed f) {
+    return f>>FIXED_BITS;
+}
+GGLfixed gglFixedToIntCeil(GGLfixed f) {
+    return (f + ((1<<FIXED_BITS) - 1))>>FIXED_BITS;
+}
+
+GGLfixed gglFracx(GGLfixed v) {
+    return v & ((1<<FIXED_BITS)-1);
+}
+GGLfixed gglFloorx(GGLfixed v) {
+    return gglFixedToIntFloor(v)<<FIXED_BITS;
+}
+GGLfixed gglCeilx(GGLfixed v) {
+    return gglFixedToIntCeil(v)<<FIXED_BITS;
+}
+GGLfixed gglCenterx(GGLfixed v) {
+    return gglFloorx(v + FIXED_HALF) | FIXED_HALF;
+}
+GGLfixed gglRoundx(GGLfixed v) {
+    return gglFixedToIntRound(v)<<FIXED_BITS;
+}
+
+// conversion from (unsigned) int, short, byte to fixed...
+#define GGL_B_TO_X(_x)      GGLfixed( ((int32_t(_x)+1)>>1)<<10 )
+#define GGL_S_TO_X(_x)      GGLfixed( ((int32_t(_x)+1)>>1)<<2 )
+#define GGL_I_TO_X(_x)      GGLfixed( ((int32_t(_x)>>1)+1)>>14 )
+#define GGL_UB_TO_X(_x)     GGLfixed(   uint32_t(_x) +      \
+                                        (uint32_t(_x)<<8) + \
+                                        (uint32_t(_x)>>7) )
+#define GGL_US_TO_X(_x)     GGLfixed( (_x) + ((_x)>>15) )
+#define GGL_UI_TO_X(_x)     GGLfixed( (((_x)>>1)+1)>>15 )
+
+// ----------------------------------------------------------------------------
+
+GGLfixed gglPowx(GGLfixed x, GGLfixed y) CONST;
+GGLfixed gglSqrtx(GGLfixed a) CONST;
+GGLfixed gglSqrtRecipx(GGLfixed x) CONST;
+int32_t gglMulDivi(int32_t a, int32_t b, int32_t c);
+
+int32_t gglRecipQNormalized(int32_t x, int* exponent);
+int32_t gglRecipQ(GGLfixed x, int q) CONST;
+
+inline GGLfixed gglRecip(GGLfixed x) CONST;
+inline GGLfixed gglRecip(GGLfixed x) {
+    return gglRecipQ(x, 16);
+}
+
+inline GGLfixed gglRecip28(GGLfixed x) CONST;
+int32_t gglRecip28(GGLfixed x) {
+    return gglRecipQ(x, 28);
+}
+
+// ----------------------------------------------------------------------------
+
+#if defined(__arm__) && !defined(__thumb__)
+
+// inline ARM implementations
+inline GGLfixed gglMulx(GGLfixed x, GGLfixed y, int shift) CONST;
+__attribute__((always_inline)) inline GGLfixed gglMulx(GGLfixed x, GGLfixed y, int shift) {
+    GGLfixed result, t;
+    if (__builtin_constant_p(shift)) {
+    asm("smull  %[lo], %[hi], %[x], %[y]            \n"
+        "movs   %[lo], %[lo], lsr %[rshift]         \n"
+        "adc    %[lo], %[lo], %[hi], lsl %[lshift]  \n"
+        : [lo]"=r"(result), [hi]"=r"(t), [x]"=r"(x)
+        : "%[x]"(x), [y]"r"(y), [lshift] "I"(32-shift), [rshift] "I"(shift)
+        : "cc"
+        );
+    } else {
+    asm("smull  %[lo], %[hi], %[x], %[y]            \n"
+        "movs   %[lo], %[lo], lsr %[rshift]         \n"
+        "adc    %[lo], %[lo], %[hi], lsl %[lshift]  \n"
+        : [lo]"=&r"(result), [hi]"=&r"(t), [x]"=&r"(x)
+        : "%[x]"(x), [y]"r"(y), [lshift] "r"(32-shift), [rshift] "r"(shift)
+        : "cc"
+        );
+    }
+    return result;
+}
+
+inline GGLfixed gglMulAddx(GGLfixed x, GGLfixed y, GGLfixed a, int shift) CONST;
+__attribute__((always_inline)) inline GGLfixed gglMulAddx(GGLfixed x, GGLfixed y, GGLfixed a,
+                                                          int shift) {
+    GGLfixed result, t;
+    if (__builtin_constant_p(shift)) {
+    asm("smull  %[lo], %[hi], %[x], %[y]            \n"
+        "add    %[lo], %[a],  %[lo], lsr %[rshift]  \n"
+        "add    %[lo], %[lo], %[hi], lsl %[lshift]  \n"
+        : [lo]"=&r"(result), [hi]"=&r"(t), [x]"=&r"(x)
+        : "%[x]"(x), [y]"r"(y), [a]"r"(a), [lshift] "I"(32-shift), [rshift] "I"(shift)
+        );
+    } else {
+    asm("smull  %[lo], %[hi], %[x], %[y]            \n"
+        "add    %[lo], %[a],  %[lo], lsr %[rshift]  \n"
+        "add    %[lo], %[lo], %[hi], lsl %[lshift]  \n"
+        : [lo]"=&r"(result), [hi]"=&r"(t), [x]"=&r"(x)
+        : "%[x]"(x), [y]"r"(y), [a]"r"(a), [lshift] "r"(32-shift), [rshift] "r"(shift)
+        );
+    }
+    return result;
+}
+
+inline GGLfixed gglMulSubx(GGLfixed x, GGLfixed y, GGLfixed a, int shift) CONST;
+inline GGLfixed gglMulSubx(GGLfixed x, GGLfixed y, GGLfixed a, int shift) {
+    GGLfixed result, t;
+    if (__builtin_constant_p(shift)) {
+    asm("smull  %[lo], %[hi], %[x], %[y]            \n"
+        "rsb    %[lo], %[a],  %[lo], lsr %[rshift]  \n"
+        "add    %[lo], %[lo], %[hi], lsl %[lshift]  \n"
+        : [lo]"=&r"(result), [hi]"=&r"(t), [x]"=&r"(x)
+        : "%[x]"(x), [y]"r"(y), [a]"r"(a), [lshift] "I"(32-shift), [rshift] "I"(shift)
+        );
+    } else {
+    asm("smull  %[lo], %[hi], %[x], %[y]            \n"
+        "rsb    %[lo], %[a],  %[lo], lsr %[rshift]  \n"
+        "add    %[lo], %[lo], %[hi], lsl %[lshift]  \n"
+        : [lo]"=&r"(result), [hi]"=&r"(t), [x]"=&r"(x)
+        : "%[x]"(x), [y]"r"(y), [a]"r"(a), [lshift] "r"(32-shift), [rshift] "r"(shift)
+        );
+    }
+    return result;
+}
+
+inline int64_t gglMulii(int32_t x, int32_t y) CONST;
+inline int64_t gglMulii(int32_t x, int32_t y)
+{
+    // 64-bits result: r0=low, r1=high
+    union {
+        struct {
+            int32_t lo;
+            int32_t hi;
+        } s;
+        int64_t res;
+    };
+    asm("smull %0, %1, %2, %3   \n"
+        : "=r"(s.lo), "=&r"(s.hi)
+        : "%r"(x), "r"(y)
+        :
+        );
+    return res;
+}
+#elif defined(__mips__) && __mips_isa_rev < 6
+
+/*inline MIPS implementations*/
+inline GGLfixed gglMulx(GGLfixed a, GGLfixed b, int shift) CONST;
+inline GGLfixed gglMulx(GGLfixed a, GGLfixed b, int shift) {
+    GGLfixed result,tmp,tmp1,tmp2;
+
+    if (__builtin_constant_p(shift)) {
+        if (shift == 0) {
+            asm ("mult %[a], %[b] \t\n"
+              "mflo  %[res]   \t\n"
+            : [res]"=&r"(result),[tmp]"=&r"(tmp)
+            : [a]"r"(a),[b]"r"(b)
+            : "%hi","%lo"
+            );
+        } else if (shift == 32)
+        {
+            asm ("mult %[a], %[b] \t\n"
+            "li  %[tmp],1\t\n"
+            "sll  %[tmp],%[tmp],0x1f\t\n"
+            "mflo %[res]   \t\n"
+            "addu %[tmp1],%[tmp],%[res] \t\n"
+            "sltu %[tmp1],%[tmp1],%[tmp]\t\n"   /*obit*/
+            "sra %[tmp],%[tmp],0x1f \t\n"
+            "mfhi  %[res]   \t\n"
+            "addu %[res],%[res],%[tmp]\t\n"
+            "addu %[res],%[res],%[tmp1]\t\n"
+            : [res]"=&r"(result),[tmp]"=&r"(tmp),[tmp1]"=&r"(tmp1)
+            : [a]"r"(a),[b]"r"(b),[shift]"I"(shift)
+            : "%hi","%lo"
+            );
+        } else if ((shift >0) && (shift < 32))
+        {
+            asm ("mult %[a], %[b] \t\n"
+            "li  %[tmp],1 \t\n"
+            "sll  %[tmp],%[tmp],%[shiftm1] \t\n"
+            "mflo  %[res]   \t\n"
+            "addu %[tmp1],%[tmp],%[res] \t\n"
+            "sltu %[tmp1],%[tmp1],%[tmp] \t\n"  /*obit?*/
+            "addu  %[res],%[res],%[tmp] \t\n"
+            "mfhi  %[tmp]   \t\n"
+            "addu  %[tmp],%[tmp],%[tmp1] \t\n"
+            "sll   %[tmp],%[tmp],%[lshift] \t\n"
+            "srl   %[res],%[res],%[rshift]    \t\n"
+            "or    %[res],%[res],%[tmp] \t\n"
+            : [res]"=&r"(result),[tmp]"=&r"(tmp),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+            : [a]"r"(a),[b]"r"(b),[lshift]"I"(32-shift),[rshift]"I"(shift),[shiftm1]"I"(shift-1)
+            : "%hi","%lo"
+            );
+        } else {
+            asm ("mult %[a], %[b] \t\n"
+            "li  %[tmp],1 \t\n"
+            "sll  %[tmp],%[tmp],%[shiftm1] \t\n"
+            "mflo  %[res]   \t\n"
+            "addu %[tmp1],%[tmp],%[res] \t\n"
+            "sltu %[tmp1],%[tmp1],%[tmp] \t\n"  /*obit?*/
+            "sra  %[tmp2],%[tmp],0x1f \t\n"
+            "addu  %[res],%[res],%[tmp] \t\n"
+            "mfhi  %[tmp]   \t\n"
+            "addu  %[tmp],%[tmp],%[tmp2] \t\n"
+            "addu  %[tmp],%[tmp],%[tmp1] \t\n"            /*tmp=hi*/
+            "srl   %[tmp2],%[res],%[rshift]    \t\n"
+            "srav  %[res], %[tmp],%[rshift]\t\n"
+            "sll   %[tmp],%[tmp],1 \t\n"
+            "sll   %[tmp],%[tmp],%[norbits] \t\n"
+            "or    %[tmp],%[tmp],%[tmp2] \t\n"
+            "movz  %[res],%[tmp],%[bit5] \t\n"
+            : [res]"=&r"(result),[tmp]"=&r"(tmp),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+            : [a]"r"(a),[b]"r"(b),[norbits]"I"(~(shift)),[rshift]"I"(shift),[shiftm1] "I"(shift-1),[bit5]"I"(shift & 0x20)
+            : "%hi","%lo"
+            );
+        }
+    } else {
+        asm ("mult %[a], %[b] \t\n"
+        "li  %[tmp],1 \t\n"
+        "sll  %[tmp],%[tmp],%[shiftm1] \t\n"
+        "mflo  %[res]   \t\n"
+        "addu %[tmp1],%[tmp],%[res] \t\n"
+        "sltu %[tmp1],%[tmp1],%[tmp] \t\n"  /*obit?*/
+        "sra  %[tmp2],%[tmp],0x1f \t\n"
+        "addu  %[res],%[res],%[tmp] \t\n"
+        "mfhi  %[tmp]   \t\n"
+        "addu  %[tmp],%[tmp],%[tmp2] \t\n"
+        "addu  %[tmp],%[tmp],%[tmp1] \t\n"            /*tmp=hi*/
+        "srl   %[tmp2],%[res],%[rshift]    \t\n"
+        "srav  %[res], %[tmp],%[rshift]\t\n"
+        "sll   %[tmp],%[tmp],1 \t\n"
+        "sll   %[tmp],%[tmp],%[norbits] \t\n"
+        "or    %[tmp],%[tmp],%[tmp2] \t\n"
+        "movz  %[res],%[tmp],%[bit5] \t\n"
+         : [res]"=&r"(result),[tmp]"=&r"(tmp),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+         : [a]"r"(a),[b]"r"(b),[norbits]"r"(~(shift)),[rshift] "r"(shift),[shiftm1]"r"(shift-1),[bit5] "r"(shift & 0x20)
+         : "%hi","%lo"
+         );
+        }
+
+        return result;
+}
+
+inline GGLfixed gglMulAddx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) CONST;
+inline GGLfixed gglMulAddx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) {
+    GGLfixed result,t,tmp1,tmp2;
+
+    if (__builtin_constant_p(shift)) {
+        if (shift == 0) {
+                 asm ("mult %[a], %[b] \t\n"
+                 "mflo  %[lo]   \t\n"
+                 "addu  %[lo],%[lo],%[c]    \t\n"
+                 : [lo]"=&r"(result)
+                 : [a]"r"(a),[b]"r"(b),[c]"r"(c)
+                 : "%hi","%lo"
+                 );
+                } else if (shift == 32) {
+                    asm ("mult %[a], %[b] \t\n"
+                    "mfhi  %[lo]   \t\n"
+                    "addu  %[lo],%[lo],%[c]    \t\n"
+                    : [lo]"=&r"(result)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c)
+                    : "%hi","%lo"
+                    );
+                } else if ((shift>0) && (shift<32)) {
+                    asm ("mult %[a], %[b] \t\n"
+                    "mflo  %[res]   \t\n"
+                    "mfhi  %[t]   \t\n"
+                    "srl   %[res],%[res],%[rshift]    \t\n"
+                    "sll   %[t],%[t],%[lshift]     \t\n"
+                    "or  %[res],%[res],%[t]    \t\n"
+                    "addu  %[res],%[res],%[c]    \t\n"
+                    : [res]"=&r"(result),[t]"=&r"(t)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c),[lshift]"I"(32-shift),[rshift]"I"(shift)
+                    : "%hi","%lo"
+                    );
+                } else {
+                    asm ("mult %[a], %[b] \t\n"
+                    "nor %[tmp1],$zero,%[shift]\t\n"
+                    "mflo  %[res]   \t\n"
+                    "mfhi  %[t]   \t\n"
+                    "srl   %[res],%[res],%[shift]    \t\n"
+                    "sll   %[tmp2],%[t],1     \t\n"
+                    "sllv  %[tmp2],%[tmp2],%[tmp1]     \t\n"
+                    "or  %[tmp1],%[tmp2],%[res]    \t\n"
+                    "srav  %[res],%[t],%[shift]     \t\n"
+                    "andi %[tmp2],%[shift],0x20\t\n"
+                    "movz %[res],%[tmp1],%[tmp2]\t\n"
+                    "addu  %[res],%[res],%[c]    \t\n"
+                    : [res]"=&r"(result),[t]"=&r"(t),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c),[shift]"I"(shift)
+                    : "%hi","%lo"
+                    );
+                }
+            } else {
+                asm ("mult %[a], %[b] \t\n"
+                "nor %[tmp1],$zero,%[shift]\t\n"
+                "mflo  %[res]   \t\n"
+                "mfhi  %[t]   \t\n"
+                "srl   %[res],%[res],%[shift]    \t\n"
+                "sll   %[tmp2],%[t],1     \t\n"
+                "sllv  %[tmp2],%[tmp2],%[tmp1]     \t\n"
+                "or  %[tmp1],%[tmp2],%[res]    \t\n"
+                "srav  %[res],%[t],%[shift]     \t\n"
+                "andi %[tmp2],%[shift],0x20\t\n"
+                "movz %[res],%[tmp1],%[tmp2]\t\n"
+                "addu  %[res],%[res],%[c]    \t\n"
+                : [res]"=&r"(result),[t]"=&r"(t),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+                : [a]"r"(a),[b]"r"(b),[c]"r"(c),[shift]"r"(shift)
+                : "%hi","%lo"
+                );
+            }
+            return result;
+}
+
+inline GGLfixed gglMulSubx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) CONST;
+inline GGLfixed gglMulSubx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) {
+    GGLfixed result,t,tmp1,tmp2;
+
+    if (__builtin_constant_p(shift)) {
+        if (shift == 0) {
+                 asm ("mult %[a], %[b] \t\n"
+                 "mflo  %[lo]   \t\n"
+                 "subu  %[lo],%[lo],%[c]    \t\n"
+                 : [lo]"=&r"(result)
+                 : [a]"r"(a),[b]"r"(b),[c]"r"(c)
+                 : "%hi","%lo"
+                 );
+                } else if (shift == 32) {
+                    asm ("mult %[a], %[b] \t\n"
+                    "mfhi  %[lo]   \t\n"
+                    "subu  %[lo],%[lo],%[c]    \t\n"
+                    : [lo]"=&r"(result)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c)
+                    : "%hi","%lo"
+                    );
+                } else if ((shift>0) && (shift<32)) {
+                    asm ("mult %[a], %[b] \t\n"
+                    "mflo  %[res]   \t\n"
+                    "mfhi  %[t]   \t\n"
+                    "srl   %[res],%[res],%[rshift]    \t\n"
+                    "sll   %[t],%[t],%[lshift]     \t\n"
+                    "or  %[res],%[res],%[t]    \t\n"
+                    "subu  %[res],%[res],%[c]    \t\n"
+                    : [res]"=&r"(result),[t]"=&r"(t)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c),[lshift]"I"(32-shift),[rshift]"I"(shift)
+                    : "%hi","%lo"
+                    );
+                } else {
+                    asm ("mult %[a], %[b] \t\n"
+                    "nor %[tmp1],$zero,%[shift]\t\n"
+                     "mflo  %[res]   \t\n"
+                     "mfhi  %[t]   \t\n"
+                     "srl   %[res],%[res],%[shift]    \t\n"
+                     "sll   %[tmp2],%[t],1     \t\n"
+                     "sllv  %[tmp2],%[tmp2],%[tmp1]     \t\n"
+                     "or  %[tmp1],%[tmp2],%[res]    \t\n"
+                     "srav  %[res],%[t],%[shift]     \t\n"
+                     "andi %[tmp2],%[shift],0x20\t\n"
+                     "movz %[res],%[tmp1],%[tmp2]\t\n"
+                     "subu  %[res],%[res],%[c]    \t\n"
+                     : [res]"=&r"(result),[t]"=&r"(t),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+                     : [a]"r"(a),[b]"r"(b),[c]"r"(c),[shift]"I"(shift)
+                     : "%hi","%lo"
+                     );
+                    }
+                } else {
+                asm ("mult %[a], %[b] \t\n"
+                "nor %[tmp1],$zero,%[shift]\t\n"
+                "mflo  %[res]   \t\n"
+                "mfhi  %[t]   \t\n"
+                "srl   %[res],%[res],%[shift]    \t\n"
+                "sll   %[tmp2],%[t],1     \t\n"
+                "sllv  %[tmp2],%[tmp2],%[tmp1]     \t\n"
+                "or  %[tmp1],%[tmp2],%[res]    \t\n"
+                "srav  %[res],%[t],%[shift]     \t\n"
+                "andi %[tmp2],%[shift],0x20\t\n"
+                "movz %[res],%[tmp1],%[tmp2]\t\n"
+                "subu  %[res],%[res],%[c]    \t\n"
+                : [res]"=&r"(result),[t]"=&r"(t),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+                : [a]"r"(a),[b]"r"(b),[c]"r"(c),[shift]"r"(shift)
+                : "%hi","%lo"
+                );
+            }
+    return result;
+}
+
+inline int64_t gglMulii(int32_t x, int32_t y) CONST;
+inline int64_t gglMulii(int32_t x, int32_t y) {
+    union {
+        struct {
+#if defined(__MIPSEL__)
+            int32_t lo;
+            int32_t hi;
+#elif defined(__MIPSEB__)
+            int32_t hi;
+            int32_t lo;
+#endif
+        } s;
+        int64_t res;
+    }u;
+    asm("mult %2, %3 \t\n"
+        "mfhi %1   \t\n"
+        "mflo %0   \t\n"
+        : "=r"(u.s.lo), "=&r"(u.s.hi)
+        : "%r"(x), "r"(y)
+	: "%hi","%lo"
+        );
+    return u.res;
+}
+
+#elif defined(__aarch64__)
+
+// inline AArch64 implementations
+
+inline GGLfixed gglMulx(GGLfixed x, GGLfixed y, int shift) CONST;
+inline GGLfixed gglMulx(GGLfixed x, GGLfixed y, int shift)
+{
+    GGLfixed result;
+    GGLfixed round;
+
+    asm("mov    %x[round], #1                        \n"
+        "lsl    %x[round], %x[round], %x[shift]      \n"
+        "lsr    %x[round], %x[round], #1             \n"
+        "smaddl %x[result], %w[x], %w[y],%x[round]   \n"
+        "lsr    %x[result], %x[result], %x[shift]    \n"
+        : [round]"=&r"(round), [result]"=&r"(result) \
+        : [x]"r"(x), [y]"r"(y), [shift] "r"(shift)   \
+        :
+       );
+    return result;
+}
+inline GGLfixed gglMulAddx(GGLfixed x, GGLfixed y, GGLfixed a, int shift) CONST;
+inline GGLfixed gglMulAddx(GGLfixed x, GGLfixed y, GGLfixed a, int shift)
+{
+    GGLfixed result;
+    asm("smull  %x[result], %w[x], %w[y]                     \n"
+        "lsr    %x[result], %x[result], %x[shift]            \n"
+        "add    %w[result], %w[result], %w[a]                \n"
+        : [result]"=&r"(result)                               \
+        : [x]"r"(x), [y]"r"(y), [a]"r"(a), [shift] "r"(shift) \
+        :
+        );
+    return result;
+}
+
+inline GGLfixed gglMulSubx(GGLfixed x, GGLfixed y, GGLfixed a, int shift) CONST;
+inline GGLfixed gglMulSubx(GGLfixed x, GGLfixed y, GGLfixed a, int shift)
+{
+
+    GGLfixed result;
+
+    asm("smull  %x[result], %w[x], %w[y]                     \n"
+        "lsr    %x[result], %x[result], %x[shift]            \n"
+        "sub    %w[result], %w[result], %w[a]                \n"
+        : [result]"=&r"(result)                               \
+        : [x]"r"(x), [y]"r"(y), [a]"r"(a), [shift] "r"(shift) \
+        :
+        );
+    return result;
+}
+inline int64_t gglMulii(int32_t x, int32_t y) CONST;
+inline int64_t gglMulii(int32_t x, int32_t y)
+{
+    int64_t res;
+    asm("smull  %x0, %w1, %w2 \n"
+        : "=r"(res)
+        : "%r"(x), "r"(y)
+        :
+        );
+    return res;
+}
+
+#elif defined(__mips__) && __mips_isa_rev == 6
+
+/*inline MIPS implementations*/
+inline GGLfixed gglMulx(GGLfixed a, GGLfixed b, int shift) CONST;
+inline GGLfixed gglMulx(GGLfixed a, GGLfixed b, int shift) {
+    GGLfixed result,tmp,tmp1,tmp2;
+
+    if (__builtin_constant_p(shift)) {
+        if (shift == 0) {
+            asm ("mul %[res], %[a], %[b] \t\n"
+            : [res]"=&r"(result)
+            : [a]"r"(a),[b]"r"(b)
+            );
+        } else if (shift == 32)
+        {
+            asm ("mul %[res], %[a], %[b] \t\n"
+            "li  %[tmp],1\t\n"
+            "sll  %[tmp],%[tmp],0x1f\t\n"
+            "addu %[tmp1],%[tmp],%[res] \t\n"
+            "muh %[res], %[a], %[b] \t\n"
+            "sltu %[tmp1],%[tmp1],%[tmp]\t\n"   /*obit*/
+            "sra %[tmp],%[tmp],0x1f \t\n"
+            "addu %[res],%[res],%[tmp]\t\n"
+            "addu %[res],%[res],%[tmp1]\t\n"
+            : [res]"=&r"(result),[tmp]"=&r"(tmp),[tmp1]"=&r"(tmp1)
+            : [a]"r"(a),[b]"r"(b),[shift]"I"(shift)
+            );
+        } else if ((shift >0) && (shift < 32))
+        {
+            asm ("mul %[res], %[a], %[b] \t\n"
+            "li  %[tmp],1 \t\n"
+            "sll  %[tmp],%[tmp],%[shiftm1] \t\n"
+            "addu %[tmp1],%[tmp],%[res] \t\n"
+            "sltu %[tmp1],%[tmp1],%[tmp] \t\n"  /*obit?*/
+            "addu  %[res],%[res],%[tmp] \t\n"
+            "muh %[tmp], %[a], %[b] \t\n"
+            "addu  %[tmp],%[tmp],%[tmp1] \t\n"
+            "sll   %[tmp],%[tmp],%[lshift] \t\n"
+            "srl   %[res],%[res],%[rshift]    \t\n"
+            "or    %[res],%[res],%[tmp] \t\n"
+            : [res]"=&r"(result),[tmp]"=&r"(tmp),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+            : [a]"r"(a),[b]"r"(b),[lshift]"I"(32-shift),[rshift]"I"(shift),[shiftm1]"I"(shift-1)
+            );
+        } else {
+            asm ("mul %[res], %[a], %[b] \t\n"
+            "li  %[tmp],1 \t\n"
+            "sll  %[tmp],%[tmp],%[shiftm1] \t\n"
+            "addu %[tmp1],%[tmp],%[res] \t\n"
+            "sltu %[tmp1],%[tmp1],%[tmp] \t\n"  /*obit?*/
+            "sra  %[tmp2],%[tmp],0x1f \t\n"
+            "addu  %[res],%[res],%[tmp] \t\n"
+            "muh  %[tmp], %[a], %[b]   \t\n"
+            "addu  %[tmp],%[tmp],%[tmp2] \t\n"
+            "addu  %[tmp],%[tmp],%[tmp1] \t\n"            /*tmp=hi*/
+            "srl   %[tmp2],%[res],%[rshift]    \t\n"
+            "srav  %[res], %[tmp],%[rshift]\t\n"
+            "sll   %[tmp],%[tmp],1 \t\n"
+            "sll   %[tmp],%[tmp],%[norbits] \t\n"
+            "or    %[tmp],%[tmp],%[tmp2] \t\n"
+            "seleqz  %[tmp],%[tmp],%[bit5] \t\n"
+            "selnez  %[res],%[res],%[bit5] \t\n"
+            "or    %[res],%[res],%[tmp] \t\n"
+            : [res]"=&r"(result),[tmp]"=&r"(tmp),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+            : [a]"r"(a),[b]"r"(b),[norbits]"I"(~(shift)),[rshift]"I"(shift),[shiftm1] "I"(shift-1),[bit5]"I"(shift & 0x20)
+            );
+        }
+    } else {
+        asm ("mul %[res], %[a], %[b] \t\n"
+        "li  %[tmp],1 \t\n"
+        "sll  %[tmp],%[tmp],%[shiftm1] \t\n"
+        "addu %[tmp1],%[tmp],%[res] \t\n"
+        "sltu %[tmp1],%[tmp1],%[tmp] \t\n"  /*obit?*/
+        "sra  %[tmp2],%[tmp],0x1f \t\n"
+        "addu  %[res],%[res],%[tmp] \t\n"
+        "muh  %[tmp], %[a], %[b] \t\n"
+        "addu  %[tmp],%[tmp],%[tmp2] \t\n"
+        "addu  %[tmp],%[tmp],%[tmp1] \t\n"            /*tmp=hi*/
+        "srl   %[tmp2],%[res],%[rshift]    \t\n"
+        "srav  %[res], %[tmp],%[rshift]\t\n"
+        "sll   %[tmp],%[tmp],1 \t\n"
+        "sll   %[tmp],%[tmp],%[norbits] \t\n"
+        "or    %[tmp],%[tmp],%[tmp2] \t\n"
+        "seleqz  %[tmp],%[tmp],%[bit5] \t\n"
+        "selnez  %[res],%[res],%[bit5] \t\n"
+        "or    %[res],%[res],%[tmp] \t\n"
+         : [res]"=&r"(result),[tmp]"=&r"(tmp),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+         : [a]"r"(a),[b]"r"(b),[norbits]"r"(~(shift)),[rshift] "r"(shift),[shiftm1]"r"(shift-1),[bit5] "r"(shift & 0x20)
+         );
+        }
+        return result;
+}
+
+inline GGLfixed gglMulAddx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) CONST;
+inline GGLfixed gglMulAddx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) {
+    GGLfixed result,t,tmp1,tmp2;
+
+    if (__builtin_constant_p(shift)) {
+        if (shift == 0) {
+                 asm ("mul %[lo], %[a], %[b] \t\n"
+                 "addu  %[lo],%[lo],%[c]    \t\n"
+                 : [lo]"=&r"(result)
+                 : [a]"r"(a),[b]"r"(b),[c]"r"(c)
+                 );
+                } else if (shift == 32) {
+                    asm ("muh %[lo], %[a], %[b] \t\n"
+                    "addu  %[lo],%[lo],%[c]    \t\n"
+                    : [lo]"=&r"(result)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c)
+                    );
+                } else if ((shift>0) && (shift<32)) {
+                    asm ("mul %[res], %[a], %[b] \t\n"
+                    "muh  %[t], %[a], %[b] \t\n"
+                    "srl   %[res],%[res],%[rshift]    \t\n"
+                    "sll   %[t],%[t],%[lshift]     \t\n"
+                    "or  %[res],%[res],%[t]    \t\n"
+                    "addu  %[res],%[res],%[c]    \t\n"
+                    : [res]"=&r"(result),[t]"=&r"(t)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c),[lshift]"I"(32-shift),[rshift]"I"(shift)
+                    );
+                } else {
+                    asm ("mul %[res], %[a], %[b] \t\n"
+                    "muh %[t], %[a], %[b] \t\n"
+                    "nor %[tmp1],$zero,%[shift]\t\n"
+                    "srl   %[res],%[res],%[shift]    \t\n"
+                    "sll   %[tmp2],%[t],1     \t\n"
+                    "sllv  %[tmp2],%[tmp2],%[tmp1]     \t\n"
+                    "or  %[tmp1],%[tmp2],%[res]    \t\n"
+                    "srav  %[res],%[t],%[shift]     \t\n"
+                    "andi %[tmp2],%[shift],0x20\t\n"
+                    "seleqz %[tmp1],%[tmp1],%[tmp2]\t\n"
+                    "selnez %[res],%[res],%[tmp2]\t\n"
+                    "or %[res],%[res],%[tmp1]\t\n"
+                    "addu  %[res],%[res],%[c]    \t\n"
+                    : [res]"=&r"(result),[t]"=&r"(t),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c),[shift]"I"(shift)
+                    );
+                }
+            } else {
+                asm ("mul %[res], %[a], %[b] \t\n"
+                "muh %[t], %[a], %[b] \t\n"
+                "nor %[tmp1],$zero,%[shift]\t\n"
+                "srl   %[res],%[res],%[shift]    \t\n"
+                "sll   %[tmp2],%[t],1     \t\n"
+                "sllv  %[tmp2],%[tmp2],%[tmp1]     \t\n"
+                "or  %[tmp1],%[tmp2],%[res]    \t\n"
+                "srav  %[res],%[t],%[shift]     \t\n"
+                "andi %[tmp2],%[shift],0x20\t\n"
+                "seleqz %[tmp1],%[tmp1],%[tmp2]\t\n"
+                "selnez %[res],%[res],%[tmp2]\t\n"
+                "or %[res],%[res],%[tmp1]\t\n"
+                "addu  %[res],%[res],%[c]    \t\n"
+                : [res]"=&r"(result),[t]"=&r"(t),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+                : [a]"r"(a),[b]"r"(b),[c]"r"(c),[shift]"r"(shift)
+                );
+            }
+            return result;
+}
+
+inline GGLfixed gglMulSubx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) CONST;
+inline GGLfixed gglMulSubx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) {
+    GGLfixed result,t,tmp1,tmp2;
+
+    if (__builtin_constant_p(shift)) {
+        if (shift == 0) {
+                 asm ("mul %[lo], %[a], %[b] \t\n"
+                 "subu  %[lo],%[lo],%[c]    \t\n"
+                 : [lo]"=&r"(result)
+                 : [a]"r"(a),[b]"r"(b),[c]"r"(c)
+                 );
+                } else if (shift == 32) {
+                    asm ("muh %[lo], %[a], %[b] \t\n"
+                    "subu  %[lo],%[lo],%[c]    \t\n"
+                    : [lo]"=&r"(result)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c)
+                    );
+                } else if ((shift>0) && (shift<32)) {
+                    asm ("mul %[res], %[a], %[b] \t\n"
+                    "muh %[t], %[a], %[b] \t\n"
+                    "srl   %[res],%[res],%[rshift]    \t\n"
+                    "sll   %[t],%[t],%[lshift]     \t\n"
+                    "or  %[res],%[res],%[t]    \t\n"
+                    "subu  %[res],%[res],%[c]    \t\n"
+                    : [res]"=&r"(result),[t]"=&r"(t)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c),[lshift]"I"(32-shift),[rshift]"I"(shift)
+                    );
+                } else {
+                    asm ("mul %[res], %[a], %[b] \t\n"
+                    "muh %[t], %[a], %[b] \t\n"
+                    "nor %[tmp1],$zero,%[shift]\t\n"
+                    "srl   %[res],%[res],%[shift]    \t\n"
+                    "sll   %[tmp2],%[t],1     \t\n"
+                    "sllv  %[tmp2],%[tmp2],%[tmp1]     \t\n"
+                    "or  %[tmp1],%[tmp2],%[res]    \t\n"
+                    "srav  %[res],%[t],%[shift]     \t\n"
+                    "andi %[tmp2],%[shift],0x20\t\n"
+                    "seleqz %[tmp1],%[tmp1],%[tmp2]\t\n"
+                    "selnez %[res],%[res],%[tmp2]\t\n"
+                    "or %[res],%[res],%[tmp1]\t\n"
+                    "subu  %[res],%[res],%[c]    \t\n"
+                    : [res]"=&r"(result),[t]"=&r"(t),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+                    : [a]"r"(a),[b]"r"(b),[c]"r"(c),[shift]"I"(shift)
+                     );
+                    }
+                } else {
+                asm ("mul %[res], %[a], %[b] \t\n"
+                "muh %[t], %[a], %[b] \t\n"
+                "nor %[tmp1],$zero,%[shift]\t\n"
+                "srl   %[res],%[res],%[shift]    \t\n"
+                "sll   %[tmp2],%[t],1     \t\n"
+                "sllv  %[tmp2],%[tmp2],%[tmp1]     \t\n"
+                "or  %[tmp1],%[tmp2],%[res]    \t\n"
+                "srav  %[res],%[t],%[shift]     \t\n"
+                "andi %[tmp2],%[shift],0x20\t\n"
+                "seleqz %[tmp1],%[tmp1],%[tmp2]\t\n"
+                "selnez %[res],%[res],%[tmp2]\t\n"
+                "or %[res],%[res],%[tmp1]\t\n"
+                "subu  %[res],%[res],%[c]    \t\n"
+                : [res]"=&r"(result),[t]"=&r"(t),[tmp1]"=&r"(tmp1),[tmp2]"=&r"(tmp2)
+                : [a]"r"(a),[b]"r"(b),[c]"r"(c),[shift]"r"(shift)
+                );
+            }
+    return result;
+}
+
+inline int64_t gglMulii(int32_t x, int32_t y) CONST;
+inline int64_t gglMulii(int32_t x, int32_t y) {
+    union {
+        struct {
+#if defined(__MIPSEL__)
+            int32_t lo;
+            int32_t hi;
+#elif defined(__MIPSEB__)
+            int32_t hi;
+            int32_t lo;
+#endif
+        } s;
+        int64_t res;
+    }u;
+    asm("mul %0, %2, %3 \t\n"
+        "muh %1, %2, %3 \t\n"
+        : "=r"(u.s.lo), "=&r"(u.s.hi)
+        : "%r"(x), "r"(y)
+        );
+    return u.res;
+}
+
+#else // ----------------------------------------------------------------------
+
+inline GGLfixed gglMulx(GGLfixed a, GGLfixed b, int shift) CONST;
+inline GGLfixed gglMulx(GGLfixed a, GGLfixed b, int shift) {
+    return GGLfixed((int64_t(a)*b + (1<<(shift-1)))>>shift);
+}
+inline GGLfixed gglMulAddx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) CONST;
+inline GGLfixed gglMulAddx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) {
+    return GGLfixed((int64_t(a)*b)>>shift) + c;
+}
+inline GGLfixed gglMulSubx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) CONST;
+inline GGLfixed gglMulSubx(GGLfixed a, GGLfixed b, GGLfixed c, int shift) {
+    return GGLfixed((int64_t(a)*b)>>shift) - c;
+}
+inline int64_t gglMulii(int32_t a, int32_t b) CONST;
+inline int64_t gglMulii(int32_t a, int32_t b) {
+    return int64_t(a)*b;
+}
+
+#endif
+
+// ------------------------------------------------------------------------
+
+inline GGLfixed gglMulx(GGLfixed a, GGLfixed b) CONST;
+inline GGLfixed gglMulx(GGLfixed a, GGLfixed b) {
+    return gglMulx(a, b, 16);
+}
+inline GGLfixed gglMulAddx(GGLfixed a, GGLfixed b, GGLfixed c) CONST;
+inline GGLfixed gglMulAddx(GGLfixed a, GGLfixed b, GGLfixed c) {
+    return gglMulAddx(a, b, c, 16);
+}
+inline GGLfixed gglMulSubx(GGLfixed a, GGLfixed b, GGLfixed c) CONST;
+inline GGLfixed gglMulSubx(GGLfixed a, GGLfixed b, GGLfixed c) {
+    return gglMulSubx(a, b, c, 16);
+}
+
+// ------------------------------------------------------------------------
+
+inline int32_t gglClz(int32_t x) CONST;
+inline int32_t gglClz(int32_t x)
+{
+#if (defined(__arm__) && !defined(__thumb__)) || defined(__mips__) || defined(__aarch64__)
+    return __builtin_clz(x);
+#else
+    if (!x) return 32;
+    int32_t exp = 31;
+    if (x & 0xFFFF0000) { exp -=16; x >>= 16; }
+    if (x & 0x0000ff00) { exp -= 8; x >>= 8; }
+    if (x & 0x000000f0) { exp -= 4; x >>= 4; }
+    if (x & 0x0000000c) { exp -= 2; x >>= 2; }
+    if (x & 0x00000002) { exp -= 1; }
+    return exp;
+#endif
+}
+
+// ------------------------------------------------------------------------
+
+int32_t gglDivQ(GGLfixed n, GGLfixed d, int32_t i) CONST;
+
+inline int32_t gglDivQ16(GGLfixed n, GGLfixed d) CONST;
+inline int32_t gglDivQ16(GGLfixed n, GGLfixed d) {
+    return gglDivQ(n, d, 16);
+}
+
+inline int32_t gglDivx(GGLfixed n, GGLfixed d) CONST;
+inline int32_t gglDivx(GGLfixed n, GGLfixed d) {
+    return gglDivQ(n, d, 16);
+}
+
+// ------------------------------------------------------------------------
+
+inline GGLfixed gglRecipFast(GGLfixed x) CONST;
+inline GGLfixed gglRecipFast(GGLfixed x)
+{
+    // This is a really bad approximation of 1/x, but it's also
+    // very fast. x must be strictly positive.
+    // if x between [0.5, 1[ , then 1/x = 3-2*x
+    // (we use 2.30 fixed-point)
+    const int32_t lz = gglClz(x);
+    return (0xC0000000 - (x << (lz - 1))) >> (30-lz);
+}
+
+// ------------------------------------------------------------------------
+
+inline GGLfixed gglClampx(GGLfixed c) CONST;
+inline GGLfixed gglClampx(GGLfixed c)
+{
+#if defined(__thumb__)
+    // clamp without branches
+    c &= ~(c>>31);  c = FIXED_ONE - c;
+    c &= ~(c>>31);  c = FIXED_ONE - c;
+#else
+#if defined(__arm__)
+    // I don't know why gcc thinks its smarter than me! The code below
+    // clamps to zero in one instruction, but gcc won't generate it and
+    // replace it by a cmp + movlt (it's quite amazing actually).
+    asm("bic %0, %1, %1, asr #31\n" : "=r"(c) : "r"(c));
+#elif defined(__aarch64__)
+    asm("bic %w0, %w1, %w1, asr #31\n" : "=r"(c) : "r"(c));
+#else
+    c &= ~(c>>31);
+#endif
+    if (c>FIXED_ONE)
+        c = FIXED_ONE;
+#endif
+    return c;
+}
+
+// ------------------------------------------------------------------------
+
+#endif // ANDROID_GGL_FIXED_H
diff --git a/libpixelflinger/picker.cpp b/libpixelflinger/picker.cpp
new file mode 100644
index 0000000..aa55229
--- /dev/null
+++ b/libpixelflinger/picker.cpp
@@ -0,0 +1,173 @@
+/* libs/pixelflinger/picker.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#include <stdio.h>
+
+#include "buffer.h"
+#include "scanline.h"
+#include "picker.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+void ggl_init_picker(context_t* /*c*/)
+{
+}
+
+void ggl_pick(context_t* c)
+{
+    if (ggl_likely(!c->dirty))
+        return;
+        
+    // compute needs, see if they changed...
+    const uint32_t enables = c->state.enables;
+    needs_t new_needs(c->state.needs);
+
+    if (c->dirty & GGL_CB_STATE) {
+        new_needs.n &= ~GGL_NEEDS_CB_FORMAT_MASK;
+        new_needs.n |= GGL_BUILD_NEEDS(c->state.buffers.color.format, CB_FORMAT);
+        if (enables & GGL_ENABLE_BLENDING)
+            c->dirty |= GGL_PIXEL_PIPELINE_STATE;
+    }
+
+    if (c->dirty & GGL_PIXEL_PIPELINE_STATE) {
+        uint32_t n = GGL_BUILD_NEEDS(c->state.buffers.color.format, CB_FORMAT);
+        uint32_t p = 0;
+        if (enables & GGL_ENABLE_BLENDING) {
+            uint32_t src = c->state.blend.src;
+            uint32_t dst = c->state.blend.dst;
+            uint32_t src_alpha = c->state.blend.src_alpha;
+            uint32_t dst_alpha = c->state.blend.dst_alpha;
+            const GGLFormat& cbf = c->formats[ c->state.buffers.color.format ];
+            if (!cbf.c[GGLFormat::ALPHA].h) {
+                if ((src == GGL_ONE_MINUS_DST_ALPHA) ||
+                    (src == GGL_DST_ALPHA)) {
+                    src = GGL_ONE;
+                }
+                if ((src_alpha == GGL_ONE_MINUS_DST_ALPHA) ||
+                    (src_alpha == GGL_DST_ALPHA)) {
+                    src_alpha = GGL_ONE;
+                }
+                if ((dst == GGL_ONE_MINUS_DST_ALPHA) ||
+                    (dst == GGL_DST_ALPHA)) {
+                    dst = GGL_ONE;
+                }
+                if ((dst_alpha == GGL_ONE_MINUS_DST_ALPHA) ||
+                    (dst_alpha == GGL_DST_ALPHA)) {
+                    dst_alpha = GGL_ONE;
+                }
+            }
+
+            src       = ggl_blendfactor_to_needs(src);
+            dst       = ggl_blendfactor_to_needs(dst);
+            src_alpha = ggl_blendfactor_to_needs(src_alpha);
+            dst_alpha = ggl_blendfactor_to_needs(dst_alpha);
+                    
+            n |= GGL_BUILD_NEEDS( src, BLEND_SRC );
+            n |= GGL_BUILD_NEEDS( dst, BLEND_DST );
+            if (c->state.blend.alpha_separate) {
+                n |= GGL_BUILD_NEEDS( src_alpha, BLEND_SRCA );
+                n |= GGL_BUILD_NEEDS( dst_alpha, BLEND_DSTA );
+            } else {
+                n |= GGL_BUILD_NEEDS( src, BLEND_SRCA );
+                n |= GGL_BUILD_NEEDS( dst, BLEND_DSTA );
+            }
+        } else {
+            n |= GGL_BUILD_NEEDS( GGL_ONE,  BLEND_SRC );
+            n |= GGL_BUILD_NEEDS( GGL_ZERO, BLEND_DST );
+            n |= GGL_BUILD_NEEDS( GGL_ONE,  BLEND_SRCA );
+            n |= GGL_BUILD_NEEDS( GGL_ZERO, BLEND_DSTA );
+        }
+
+
+        n |= GGL_BUILD_NEEDS(c->state.mask.color^0xF,               MASK_ARGB);
+        n |= GGL_BUILD_NEEDS((enables & GGL_ENABLE_SMOOTH)  ?1:0,   SHADE);
+        if (enables & GGL_ENABLE_TMUS) {
+            n |= GGL_BUILD_NEEDS((enables & GGL_ENABLE_W)       ?1:0,   W);
+        }
+        p |= GGL_BUILD_NEEDS((enables & GGL_ENABLE_DITHER)  ?1:0,   P_DITHER);
+        p |= GGL_BUILD_NEEDS((enables & GGL_ENABLE_AA)      ?1:0,   P_AA);
+        p |= GGL_BUILD_NEEDS((enables & GGL_ENABLE_FOG)     ?1:0,   P_FOG);
+
+        if (enables & GGL_ENABLE_LOGIC_OP) {
+            n |= GGL_BUILD_NEEDS(c->state.logic_op.opcode, LOGIC_OP);
+        } else {
+            n |= GGL_BUILD_NEEDS(GGL_COPY, LOGIC_OP);
+        }
+
+        if (enables & GGL_ENABLE_ALPHA_TEST) {
+            p |= GGL_BUILD_NEEDS(c->state.alpha_test.func, P_ALPHA_TEST);
+        } else {
+            p |= GGL_BUILD_NEEDS(GGL_ALWAYS, P_ALPHA_TEST);
+        }
+
+        if (enables & GGL_ENABLE_DEPTH_TEST) {
+            p |= GGL_BUILD_NEEDS(c->state.depth_test.func, P_DEPTH_TEST);
+            p |= GGL_BUILD_NEEDS(c->state.mask.depth&1, P_MASK_Z);
+        } else {
+            p |= GGL_BUILD_NEEDS(GGL_ALWAYS, P_DEPTH_TEST);
+            // writing to the z-buffer is always disabled if depth-test
+            // is disabled.
+        }
+        new_needs.n = n;
+        new_needs.p = p;
+    }
+
+    if (c->dirty & GGL_TMU_STATE) {
+        int idx = 0;
+        for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; ++i) {
+            const texture_t& tx = c->state.texture[i];
+            if (tx.enable) {
+                uint32_t t = 0;
+                t |= GGL_BUILD_NEEDS(tx.surface.format, T_FORMAT);
+                t |= GGL_BUILD_NEEDS(ggl_env_to_needs(tx.env), T_ENV);
+                t |= GGL_BUILD_NEEDS(0, T_POT);       // XXX: not used yet
+                if (tx.s_coord==GGL_ONE_TO_ONE && tx.t_coord==GGL_ONE_TO_ONE) {
+                    // we encode 1-to-1 into the wrap mode
+                    t |= GGL_BUILD_NEEDS(GGL_NEEDS_WRAP_11, T_S_WRAP);
+                    t |= GGL_BUILD_NEEDS(GGL_NEEDS_WRAP_11, T_T_WRAP);
+                } else {
+                    t |= GGL_BUILD_NEEDS(ggl_wrap_to_needs(tx.s_wrap), T_S_WRAP);
+                    t |= GGL_BUILD_NEEDS(ggl_wrap_to_needs(tx.t_wrap), T_T_WRAP);
+                }
+                if (tx.mag_filter == GGL_LINEAR) {
+                    t |= GGL_BUILD_NEEDS(1, T_LINEAR);
+                }
+                if (tx.min_filter == GGL_LINEAR) {
+                    t |= GGL_BUILD_NEEDS(1, T_LINEAR);
+                }
+                new_needs.t[idx++] = t;
+            } else {
+                new_needs.t[i] = 0;
+            }
+        }
+    }
+
+    if (new_needs != c->state.needs) {
+        c->state.needs = new_needs;
+        ggl_pick_texture(c);
+        ggl_pick_cb(c);
+        ggl_pick_scanline(c);
+    }
+    c->dirty = 0;
+}
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
diff --git a/libpixelflinger/picker.h b/libpixelflinger/picker.h
new file mode 100644
index 0000000..9cdbc3c
--- /dev/null
+++ b/libpixelflinger/picker.h
@@ -0,0 +1,31 @@
+/* libs/pixelflinger/picker.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#ifndef ANDROID_PICKER_H
+#define ANDROID_PICKER_H
+
+#include <private/pixelflinger/ggl_context.h>
+
+namespace android {
+
+void ggl_init_picker(context_t* c);
+void ggl_pick(context_t* c);
+
+}; // namespace android
+
+#endif
diff --git a/libpixelflinger/pixelflinger.cpp b/libpixelflinger/pixelflinger.cpp
new file mode 100644
index 0000000..fd449b2
--- /dev/null
+++ b/libpixelflinger/pixelflinger.cpp
@@ -0,0 +1,836 @@
+/* libs/pixelflinger/pixelflinger.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <sys/time.h>
+
+#include <pixelflinger/pixelflinger.h>
+#include <private/pixelflinger/ggl_context.h>
+
+#include "buffer.h"
+#include "clear.h"
+#include "picker.h"
+#include "raster.h"
+#include "scanline.h"
+#include "trap.h"
+
+#include "codeflinger/GGLAssembler.h"
+#include "codeflinger/CodeCache.h"
+
+#include <stdio.h> 
+
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+// 8x8 Bayer dither matrix
+static const uint8_t gDitherMatrix[GGL_DITHER_SIZE] = {
+     0, 32,  8, 40,  2, 34, 10, 42,
+    48, 16, 56, 24, 50, 18, 58, 26,
+    12, 44,  4, 36, 14, 46,  6, 38,
+    60, 28, 52, 20, 62, 30, 54, 22,
+     3, 35, 11, 43,  1, 33,  9, 41,
+    51, 19, 59, 27, 49, 17, 57, 25,
+    15, 47,  7, 39, 13, 45,  5, 37,
+    63, 31, 55, 23, 61, 29, 53, 21
+};
+
+static void ggl_init_procs(context_t* c);
+static void ggl_set_scissor(context_t* c);
+
+static void ggl_enable_blending(context_t* c, int enable);
+static void ggl_enable_scissor_test(context_t* c, int enable);
+static void ggl_enable_alpha_test(context_t* c, int enable);
+static void ggl_enable_logic_op(context_t* c, int enable);
+static void ggl_enable_dither(context_t* c, int enable);
+static void ggl_enable_stencil_test(context_t* c, int enable);
+static void ggl_enable_depth_test(context_t* c, int enable);
+static void ggl_enable_aa(context_t* c, int enable);
+static void ggl_enable_point_aa_nice(context_t* c, int enable);
+static void ggl_enable_texture2d(context_t* c, int enable);
+static void ggl_enable_w_lerp(context_t* c, int enable);
+static void ggl_enable_fog(context_t* c, int enable);
+
+static inline int min(int a, int b) CONST;
+static inline int min(int a, int b) {
+    return a < b ? a : b;
+}
+
+static inline int max(int a, int b) CONST;
+static inline int max(int a, int b) {
+    return a < b ? b : a;
+}
+
+// ----------------------------------------------------------------------------
+
+void ggl_error(context_t* c, GGLenum error)
+{
+    if (c->error == GGL_NO_ERROR)
+        c->error = error;
+}
+
+// ----------------------------------------------------------------------------
+
+static void ggl_bindTexture(void* con, const GGLSurface* surface)
+{
+    GGL_CONTEXT(c, con);
+    if (surface->format != c->activeTMU->surface.format)
+        ggl_state_changed(c, GGL_TMU_STATE);    
+    ggl_set_surface(c, &(c->activeTMU->surface), surface);
+}
+
+
+static void ggl_bindTextureLod(void* con, GGLuint tmu,const GGLSurface* surface)
+{
+    GGL_CONTEXT(c, con);
+    // All LODs must have the same format
+    ggl_set_surface(c, &c->state.texture[tmu].surface, surface);
+}
+
+static void ggl_colorBuffer(void* con, const GGLSurface* surface)
+{
+    GGL_CONTEXT(c, con);
+    if (surface->format != c->state.buffers.color.format)
+        ggl_state_changed(c, GGL_CB_STATE);
+
+    if (surface->width > c->state.buffers.coverageBufferSize) {
+        // allocate the coverage factor buffer
+        free(c->state.buffers.coverage);
+        c->state.buffers.coverage = (int16_t*)malloc(surface->width * 2);
+        c->state.buffers.coverageBufferSize =
+                c->state.buffers.coverage ? surface->width : 0;
+    }
+    ggl_set_surface(c, &(c->state.buffers.color), surface);
+    if (c->state.buffers.read.format == 0) {
+        ggl_set_surface(c, &(c->state.buffers.read), surface);
+    }
+    ggl_set_scissor(c);
+}
+
+static void ggl_readBuffer(void* con, const GGLSurface* surface)
+{
+    GGL_CONTEXT(c, con);
+    ggl_set_surface(c, &(c->state.buffers.read), surface);
+}
+
+static void ggl_depthBuffer(void* con, const GGLSurface* surface)
+{
+    GGL_CONTEXT(c, con);
+    if (surface->format == GGL_PIXEL_FORMAT_Z_16) {
+        ggl_set_surface(c, &(c->state.buffers.depth), surface);
+    } else {
+        c->state.buffers.depth.format = GGL_PIXEL_FORMAT_NONE;
+        ggl_enable_depth_test(c, 0);
+    }
+}
+
+static void ggl_scissor(void* con, GGLint x, GGLint y,
+        GGLsizei width, GGLsizei height)
+{
+    GGL_CONTEXT(c, con);
+    c->state.scissor.user_left = x;
+    c->state.scissor.user_top = y;
+    c->state.scissor.user_right = x + width;
+    c->state.scissor.user_bottom = y + height;
+    ggl_set_scissor(c);
+}
+
+// ----------------------------------------------------------------------------
+
+static void enable_disable(context_t* c, GGLenum name, int en)
+{
+    switch (name) {
+    case GGL_BLEND:             ggl_enable_blending(c, en);      break;
+    case GGL_SCISSOR_TEST:      ggl_enable_scissor_test(c, en);  break;
+    case GGL_ALPHA_TEST:        ggl_enable_alpha_test(c, en);    break;
+    case GGL_COLOR_LOGIC_OP:    ggl_enable_logic_op(c, en);      break;
+    case GGL_DITHER:            ggl_enable_dither(c, en);        break;
+    case GGL_STENCIL_TEST:      ggl_enable_stencil_test(c, en);  break;
+    case GGL_DEPTH_TEST:        ggl_enable_depth_test(c, en);    break;
+    case GGL_AA:                ggl_enable_aa(c, en);            break;
+    case GGL_TEXTURE_2D:        ggl_enable_texture2d(c, en);     break;
+    case GGL_W_LERP:            ggl_enable_w_lerp(c, en);        break;
+    case GGL_FOG:               ggl_enable_fog(c, en);           break;
+    case GGL_POINT_SMOOTH_NICE: ggl_enable_point_aa_nice(c, en); break;
+    }
+}
+
+static void ggl_enable(void* con, GGLenum name)
+{
+    GGL_CONTEXT(c, con);
+    enable_disable(c, name, 1);
+}
+
+static void ggl_disable(void* con, GGLenum name)
+{
+    GGL_CONTEXT(c, con);
+    enable_disable(c, name, 0);
+}
+
+static void ggl_enableDisable(void* con, GGLenum name, GGLboolean en)
+{
+    GGL_CONTEXT(c, con);
+    enable_disable(c, name, en ? 1 : 0);
+}
+
+// ----------------------------------------------------------------------------
+
+static void ggl_shadeModel(void* con, GGLenum mode)
+{
+    GGL_CONTEXT(c, con);
+    switch (mode) {
+    case GGL_FLAT:
+        if (c->state.enables & GGL_ENABLE_SMOOTH) {
+            c->state.enables &= ~GGL_ENABLE_SMOOTH;
+            ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+        }
+        break;
+    case GGL_SMOOTH:
+        if (!(c->state.enables & GGL_ENABLE_SMOOTH)) {
+            c->state.enables |= GGL_ENABLE_SMOOTH;
+            ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+        }
+        break;
+    default:
+        ggl_error(c, GGL_INVALID_ENUM);
+    }
+}
+
+static void ggl_color4xv(void* con, const GGLclampx* color)
+{
+    GGL_CONTEXT(c, con);
+	c->shade.r0 = gglFixedToIteratedColor(color[0]);
+	c->shade.g0 = gglFixedToIteratedColor(color[1]);
+	c->shade.b0 = gglFixedToIteratedColor(color[2]);
+	c->shade.a0 = gglFixedToIteratedColor(color[3]);
+}
+
+static void ggl_colorGrad12xv(void* con, const GGLcolor* grad)
+{
+    GGL_CONTEXT(c, con);
+    // it is very important to round the iterated value here because
+    // the rasterizer doesn't clamp them, therefore the iterated value
+    //must absolutely be correct.
+    // GGLColor is encoded as 8.16 value
+    const int32_t round = 0x8000;
+	c->shade.r0   = grad[ 0] + round;
+	c->shade.drdx = grad[ 1];
+	c->shade.drdy = grad[ 2];
+	c->shade.g0   = grad[ 3] + round;
+	c->shade.dgdx = grad[ 4];
+	c->shade.dgdy = grad[ 5];
+	c->shade.b0   = grad[ 6] + round;
+	c->shade.dbdx = grad[ 7];
+	c->shade.dbdy = grad[ 8];
+	c->shade.a0   = grad[ 9] + round;
+	c->shade.dadx = grad[10];
+	c->shade.dady = grad[11];
+}
+
+static void ggl_zGrad3xv(void* con, const GGLfixed32* grad)
+{
+    GGL_CONTEXT(c, con);
+    // z iterators are encoded as 0.32 fixed point and the z-buffer
+    // holds 16 bits, the rounding value is 0x8000.
+    const uint32_t round = 0x8000;
+	c->shade.z0   = grad[0] + round;
+	c->shade.dzdx = grad[1];
+	c->shade.dzdy = grad[2];
+}
+
+static void ggl_wGrad3xv(void* con, const GGLfixed* grad)
+{
+    GGL_CONTEXT(c, con);
+	c->shade.w0   = grad[0];
+	c->shade.dwdx = grad[1];
+	c->shade.dwdy = grad[2];
+}
+
+// ----------------------------------------------------------------------------
+
+static void ggl_fogGrad3xv(void* con, const GGLfixed* grad)
+{
+    GGL_CONTEXT(c, con);
+	c->shade.f0     = grad[0];
+	c->shade.dfdx   = grad[1];
+	c->shade.dfdy   = grad[2];
+}
+
+static void ggl_fogColor3xv(void* con, const GGLclampx* color)
+{
+    GGL_CONTEXT(c, con);
+    const int32_t r = gglClampx(color[0]);
+    const int32_t g = gglClampx(color[1]);
+    const int32_t b = gglClampx(color[2]);
+    c->state.fog.color[GGLFormat::ALPHA]= 0xFF; // unused
+	c->state.fog.color[GGLFormat::RED]  = (r - (r>>8))>>8;
+	c->state.fog.color[GGLFormat::GREEN]= (g - (g>>8))>>8;
+	c->state.fog.color[GGLFormat::BLUE] = (b - (b>>8))>>8;
+}
+
+static void ggl_enable_fog(context_t* c, int enable)
+{
+    const int e = (c->state.enables & GGL_ENABLE_FOG)?1:0;
+    if (e != enable) {
+        if (enable) c->state.enables |= GGL_ENABLE_FOG;
+        else        c->state.enables &= ~GGL_ENABLE_FOG;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static void ggl_blendFunc(void* con, GGLenum src, GGLenum dst)
+{
+    GGL_CONTEXT(c, con);
+    c->state.blend.src = src;
+    c->state.blend.src_alpha = src;
+    c->state.blend.dst = dst;
+    c->state.blend.dst_alpha = dst;
+    c->state.blend.alpha_separate = 0;
+    if (c->state.enables & GGL_ENABLE_BLENDING) {
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+static void ggl_blendFuncSeparate(void* con,
+        GGLenum src, GGLenum dst,
+        GGLenum srcAlpha, GGLenum dstAplha)
+{
+    GGL_CONTEXT(c, con);
+    c->state.blend.src = src;
+    c->state.blend.src_alpha = srcAlpha;
+    c->state.blend.dst = dst;
+    c->state.blend.dst_alpha = dstAplha;
+    c->state.blend.alpha_separate = 1;
+    if (c->state.enables & GGL_ENABLE_BLENDING) {
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static void ggl_texEnvi(void* con,	GGLenum target,
+							GGLenum pname,
+							GGLint param)
+{
+    GGL_CONTEXT(c, con);
+    if (target != GGL_TEXTURE_ENV || pname != GGL_TEXTURE_ENV_MODE) {
+        ggl_error(c, GGL_INVALID_ENUM);
+        return;
+    }
+    switch (param) {
+    case GGL_REPLACE:
+    case GGL_MODULATE:
+    case GGL_DECAL:
+    case GGL_BLEND:
+    case GGL_ADD:
+        if (c->activeTMU->env != param) {
+            c->activeTMU->env = param;
+            ggl_state_changed(c, GGL_TMU_STATE);
+        }
+        break;
+    default:
+        ggl_error(c, GGL_INVALID_ENUM);
+    }
+}
+
+static void ggl_texEnvxv(void* con, GGLenum target,
+        GGLenum pname, const GGLfixed* params)
+{
+    GGL_CONTEXT(c, con);
+    if (target != GGL_TEXTURE_ENV) {
+        ggl_error(c, GGL_INVALID_ENUM);
+        return;
+    }
+    switch (pname) {
+    case GGL_TEXTURE_ENV_MODE:
+        ggl_texEnvi(con, target, pname, params[0]);
+        break;
+    case GGL_TEXTURE_ENV_COLOR: {
+        uint8_t* const color = c->activeTMU->env_color;
+        const GGLclampx r = gglClampx(params[0]);
+        const GGLclampx g = gglClampx(params[1]);
+        const GGLclampx b = gglClampx(params[2]);
+        const GGLclampx a = gglClampx(params[3]);
+        color[0] = (a-(a>>8))>>8;
+        color[1] = (r-(r>>8))>>8;
+        color[2] = (g-(g>>8))>>8;
+        color[3] = (b-(b>>8))>>8;
+        break;
+    }
+    default:
+        ggl_error(c, GGL_INVALID_ENUM);
+        return;
+    }
+}
+
+
+static void ggl_texParameteri(void* con,
+        GGLenum target,
+        GGLenum pname,
+        GGLint param)
+{
+    GGL_CONTEXT(c, con);
+    if (target != GGL_TEXTURE_2D) {
+        ggl_error(c, GGL_INVALID_ENUM);
+        return;
+    }
+
+    if (param == GGL_CLAMP_TO_EDGE)
+        param = GGL_CLAMP;
+
+    uint16_t* what = 0;
+    switch (pname) {
+    case GGL_TEXTURE_WRAP_S:
+        if ((param == GGL_CLAMP) ||
+            (param == GGL_REPEAT)) {
+            what = &c->activeTMU->s_wrap;
+        }
+        break;
+    case GGL_TEXTURE_WRAP_T:
+        if ((param == GGL_CLAMP) ||
+            (param == GGL_REPEAT)) {
+            what = &c->activeTMU->t_wrap;
+        }
+        break;
+    case GGL_TEXTURE_MIN_FILTER:
+        if ((param == GGL_NEAREST) ||
+            (param == GGL_NEAREST_MIPMAP_NEAREST) ||
+            (param == GGL_NEAREST_MIPMAP_LINEAR)) {
+            what = &c->activeTMU->min_filter;
+            param = GGL_NEAREST;
+        }
+        if ((param == GGL_LINEAR) ||
+            (param == GGL_LINEAR_MIPMAP_NEAREST) ||
+            (param == GGL_LINEAR_MIPMAP_LINEAR)) {
+            what = &c->activeTMU->min_filter;
+            param = GGL_LINEAR;
+        }
+        break;
+    case GGL_TEXTURE_MAG_FILTER:
+        if ((param == GGL_NEAREST) ||
+            (param == GGL_LINEAR)) {
+            what = &c->activeTMU->mag_filter;
+        }
+        break;
+    }
+    
+    if (!what) {
+        ggl_error(c, GGL_INVALID_ENUM);
+        return;
+    }
+    
+    if (*what != param) {
+        *what = param;
+        ggl_state_changed(c, GGL_TMU_STATE);
+    }
+}
+
+static void ggl_texCoordGradScale8xv(void* con, GGLint tmu, const int32_t* grad)
+{
+    GGL_CONTEXT(c, con);
+    texture_t& u = c->state.texture[tmu];
+	u.shade.is0   = grad[0];
+	u.shade.idsdx = grad[1];
+	u.shade.idsdy = grad[2];
+	u.shade.it0   = grad[3];
+	u.shade.idtdx = grad[4];
+	u.shade.idtdy = grad[5];
+    u.shade.sscale= grad[6];
+    u.shade.tscale= grad[7];
+}
+
+static void ggl_texCoord2x(void* con, GGLfixed s, GGLfixed t)
+{
+    GGL_CONTEXT(c, con);
+	c->activeTMU->shade.is0 = s;
+	c->activeTMU->shade.it0 = t;
+    c->activeTMU->shade.sscale= 0;
+    c->activeTMU->shade.tscale= 0;
+}
+
+static void ggl_texCoord2i(void* con, GGLint s, GGLint t)
+{
+    ggl_texCoord2x(con, s<<16, t<<16);
+}
+
+static void ggl_texGeni(void* con, GGLenum coord, GGLenum pname, GGLint param)
+{
+    GGL_CONTEXT(c, con);
+    if (pname != GGL_TEXTURE_GEN_MODE) {
+        ggl_error(c, GGL_INVALID_ENUM);
+        return;
+    }
+
+    uint32_t* coord_ptr = 0;
+    if (coord == GGL_S)         coord_ptr = &(c->activeTMU->s_coord);
+    else if (coord == GGL_T)    coord_ptr = &(c->activeTMU->t_coord);
+
+    if (coord_ptr) {
+        if (*coord_ptr != uint32_t(param)) {
+            *coord_ptr = uint32_t(param);
+            ggl_state_changed(c, GGL_TMU_STATE);
+        }
+    } else {
+        ggl_error(c, GGL_INVALID_ENUM);
+    }
+}
+
+static void ggl_activeTexture(void* con, GGLuint tmu)
+{
+    GGL_CONTEXT(c, con);
+    if (tmu >= GGLuint(GGL_TEXTURE_UNIT_COUNT)) {
+        ggl_error(c, GGL_INVALID_ENUM);
+        return;
+    }
+    c->activeTMUIndex = tmu;
+    c->activeTMU = &(c->state.texture[tmu]);
+}
+
+// ----------------------------------------------------------------------------
+
+static void ggl_colorMask(void* con, GGLboolean r,
+                                     GGLboolean g,
+                                     GGLboolean b,
+                                     GGLboolean a)
+{
+    GGL_CONTEXT(c, con);
+    int mask = 0;
+    if (a)  mask |= 1 << GGLFormat::ALPHA;
+    if (r)  mask |= 1 << GGLFormat::RED;
+    if (g)  mask |= 1 << GGLFormat::GREEN;
+    if (b)  mask |= 1 << GGLFormat::BLUE;
+    if (c->state.mask.color != mask) {
+        c->state.mask.color = mask;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+static void ggl_depthMask(void* con, GGLboolean flag)
+{
+    GGL_CONTEXT(c, con);
+    if (c->state.mask.depth != flag?1:0) {
+        c->state.mask.depth = flag?1:0;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+static void ggl_stencilMask(void* con, GGLuint mask)
+{
+    GGL_CONTEXT(c, con);
+    if (c->state.mask.stencil != mask) {
+        c->state.mask.stencil = mask;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static void ggl_alphaFuncx(void* con, GGLenum func, GGLclampx ref)
+{
+    GGL_CONTEXT(c, con);
+    if ((func < GGL_NEVER) || (func > GGL_ALWAYS)) {
+        ggl_error(c, GGL_INVALID_ENUM);
+        return;
+    }
+    c->state.alpha_test.ref = gglFixedToIteratedColor(gglClampx(ref));
+    if (c->state.alpha_test.func != func) {
+        c->state.alpha_test.func = func;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static void ggl_depthFunc(void* con, GGLenum func)
+{
+    GGL_CONTEXT(c, con);
+    if ((func < GGL_NEVER) || (func > GGL_ALWAYS)) {
+        ggl_error(c, GGL_INVALID_ENUM);
+        return;
+    }
+    if (c->state.depth_test.func != func) {
+        c->state.depth_test.func = func;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static void ggl_logicOp(void* con, GGLenum opcode)
+{
+    GGL_CONTEXT(c, con);
+    if ((opcode < GGL_CLEAR) || (opcode > GGL_SET)) {
+        ggl_error(c, GGL_INVALID_ENUM);
+        return;
+    }
+    if (c->state.logic_op.opcode != opcode) {
+        c->state.logic_op.opcode = opcode;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+
+// ----------------------------------------------------------------------------
+
+void ggl_set_scissor(context_t* c)
+{
+    if (c->state.enables & GGL_ENABLE_SCISSOR_TEST) {
+        const int32_t l = c->state.scissor.user_left;
+        const int32_t t = c->state.scissor.user_top;
+        const int32_t r = c->state.scissor.user_right;
+        const int32_t b = c->state.scissor.user_bottom;
+        c->state.scissor.left   = max(0, l);
+        c->state.scissor.right  = min(c->state.buffers.color.width, r);
+        c->state.scissor.top    = max(0, t);
+        c->state.scissor.bottom = min(c->state.buffers.color.height, b);
+    } else {
+        c->state.scissor.left   = 0;
+        c->state.scissor.top    = 0;
+        c->state.scissor.right  = c->state.buffers.color.width;
+        c->state.scissor.bottom = c->state.buffers.color.height;
+    }
+}
+
+void ggl_enable_blending(context_t* c, int enable)
+{
+    const int e = (c->state.enables & GGL_ENABLE_BLENDING)?1:0;
+    if (e != enable) {
+        if (enable) c->state.enables |= GGL_ENABLE_BLENDING;
+        else        c->state.enables &= ~GGL_ENABLE_BLENDING;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+void ggl_enable_scissor_test(context_t* c, int enable)
+{
+    const int e = (c->state.enables & GGL_ENABLE_SCISSOR_TEST)?1:0;
+    if (e != enable) {
+        if (enable) c->state.enables |= GGL_ENABLE_SCISSOR_TEST;
+        else        c->state.enables &= ~GGL_ENABLE_SCISSOR_TEST;
+        ggl_set_scissor(c);
+    }
+}
+
+void ggl_enable_alpha_test(context_t* c, int enable)
+{
+    const int e = (c->state.enables & GGL_ENABLE_ALPHA_TEST)?1:0;
+    if (e != enable) {
+        if (enable) c->state.enables |= GGL_ENABLE_ALPHA_TEST;
+        else        c->state.enables &= ~GGL_ENABLE_ALPHA_TEST;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+void ggl_enable_logic_op(context_t* c, int enable)
+{
+    const int e = (c->state.enables & GGL_ENABLE_LOGIC_OP)?1:0;
+    if (e != enable) {
+        if (enable) c->state.enables |= GGL_ENABLE_LOGIC_OP;
+        else        c->state.enables &= ~GGL_ENABLE_LOGIC_OP;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+void ggl_enable_dither(context_t* c, int enable)
+{
+    const int e = (c->state.enables & GGL_ENABLE_DITHER)?1:0;
+    if (e != enable) {
+        if (enable) c->state.enables |= GGL_ENABLE_DITHER;
+        else        c->state.enables &= ~GGL_ENABLE_DITHER;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+void ggl_enable_stencil_test(context_t* /*c*/, int /*enable*/)
+{
+}
+
+void ggl_enable_depth_test(context_t* c, int enable)
+{
+    if (c->state.buffers.depth.format == 0)
+        enable = 0;
+    const int e = (c->state.enables & GGL_ENABLE_DEPTH_TEST)?1:0;
+    if (e != enable) {
+        if (enable) c->state.enables |= GGL_ENABLE_DEPTH_TEST;
+        else        c->state.enables &= ~GGL_ENABLE_DEPTH_TEST;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+void ggl_enable_aa(context_t* c, int enable)
+{
+    const int e = (c->state.enables & GGL_ENABLE_AA)?1:0;
+    if (e != enable) {
+        if (enable) c->state.enables |= GGL_ENABLE_AA;
+        else        c->state.enables &= ~GGL_ENABLE_AA;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+void ggl_enable_point_aa_nice(context_t* c, int enable)
+{
+    const int e = (c->state.enables & GGL_ENABLE_POINT_AA_NICE)?1:0;
+    if (e != enable) {
+        if (enable) c->state.enables |= GGL_ENABLE_POINT_AA_NICE;
+        else        c->state.enables &= ~GGL_ENABLE_POINT_AA_NICE;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+void ggl_enable_w_lerp(context_t* c, int enable)
+{
+    const int e = (c->state.enables & GGL_ENABLE_W)?1:0;
+    if (e != enable) {
+        if (enable) c->state.enables |= GGL_ENABLE_W;
+        else        c->state.enables &= ~GGL_ENABLE_W;
+        ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE);
+    }
+}
+
+void ggl_enable_texture2d(context_t* c, int enable)
+{
+    if (c->activeTMU->enable != enable) {
+        const uint32_t tmu = c->activeTMUIndex;
+        c->activeTMU->enable = enable;
+        const uint32_t mask = 1UL << tmu;
+        if (enable)                 c->state.enabled_tmu |= mask;
+        else                        c->state.enabled_tmu &= ~mask;
+        if (c->state.enabled_tmu)   c->state.enables |= GGL_ENABLE_TMUS;
+        else                        c->state.enables &= ~GGL_ENABLE_TMUS;
+        ggl_state_changed(c, GGL_TMU_STATE);
+    }
+}
+
+        
+// ----------------------------------------------------------------------------
+
+int64_t ggl_system_time()
+{
+    struct timespec t;
+    t.tv_sec = t.tv_nsec = 0;
+    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t);
+    return int64_t(t.tv_sec)*1000000000LL + t.tv_nsec;
+}
+
+// ----------------------------------------------------------------------------
+
+void ggl_init_procs(context_t* c)
+{
+    GGLContext& procs = *(GGLContext*)c;
+    GGL_INIT_PROC(procs, scissor);
+    GGL_INIT_PROC(procs, activeTexture);
+    GGL_INIT_PROC(procs, bindTexture);
+    GGL_INIT_PROC(procs, bindTextureLod);
+    GGL_INIT_PROC(procs, colorBuffer);
+    GGL_INIT_PROC(procs, readBuffer);
+    GGL_INIT_PROC(procs, depthBuffer);
+    GGL_INIT_PROC(procs, enable);
+    GGL_INIT_PROC(procs, disable);
+    GGL_INIT_PROC(procs, enableDisable);
+    GGL_INIT_PROC(procs, shadeModel);
+    GGL_INIT_PROC(procs, color4xv);
+    GGL_INIT_PROC(procs, colorGrad12xv);
+    GGL_INIT_PROC(procs, zGrad3xv);
+    GGL_INIT_PROC(procs, wGrad3xv);
+    GGL_INIT_PROC(procs, fogGrad3xv);
+    GGL_INIT_PROC(procs, fogColor3xv);
+    GGL_INIT_PROC(procs, blendFunc);
+    GGL_INIT_PROC(procs, blendFuncSeparate);
+    GGL_INIT_PROC(procs, texEnvi);
+    GGL_INIT_PROC(procs, texEnvxv);
+    GGL_INIT_PROC(procs, texParameteri);
+    GGL_INIT_PROC(procs, texCoord2i);
+    GGL_INIT_PROC(procs, texCoord2x);
+    GGL_INIT_PROC(procs, texCoordGradScale8xv);
+    GGL_INIT_PROC(procs, texGeni);
+    GGL_INIT_PROC(procs, colorMask);
+    GGL_INIT_PROC(procs, depthMask);
+    GGL_INIT_PROC(procs, stencilMask);
+    GGL_INIT_PROC(procs, alphaFuncx);
+    GGL_INIT_PROC(procs, depthFunc);
+    GGL_INIT_PROC(procs, logicOp);
+    ggl_init_clear(c);
+}
+
+void ggl_init_context(context_t* c)
+{
+    memset(c, 0, sizeof(context_t));
+    ggl_init_procs(c);
+    ggl_init_trap(c);
+    ggl_init_scanline(c);
+    ggl_init_texture(c);
+    ggl_init_picker(c);
+    ggl_init_raster(c);
+    c->formats = gglGetPixelFormatTable();
+    c->state.blend.src = GGL_ONE;
+    c->state.blend.dst = GGL_ZERO;
+    c->state.blend.src_alpha = GGL_ONE;
+    c->state.blend.dst_alpha = GGL_ZERO;
+    c->state.mask.color = 0xF;
+    c->state.mask.depth = 0;
+    c->state.mask.stencil = 0xFFFFFFFF;
+    c->state.logic_op.opcode = GGL_COPY;
+    c->state.alpha_test.func = GGL_ALWAYS;
+    c->state.depth_test.func = GGL_LESS;
+    c->state.depth_test.clearValue = FIXED_ONE;
+    c->shade.w0 = FIXED_ONE;
+    memcpy(c->ditherMatrix, gDitherMatrix, sizeof(gDitherMatrix));
+}
+
+void ggl_uninit_context(context_t* c)
+{
+    ggl_uninit_scanline(c);
+}
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+// ----------------------------------------------------------------------------
+
+
+
+using namespace android;
+
+ssize_t gglInit(GGLContext** context)
+{
+    void* const base = malloc(sizeof(context_t) + 32);
+	if (base) {
+        // always align the context on cache lines
+        context_t *c = (context_t *)((ptrdiff_t(base)+31) & ~0x1FL);
+        ggl_init_context(c);
+        c->base = base;
+		*context = (GGLContext*)c;
+	} else {
+		return -1;
+	}
+	return 0;
+}
+
+ssize_t gglUninit(GGLContext* con)
+{
+    GGL_CONTEXT(c, (void*)con);
+    ggl_uninit_context(c);
+	free(c->base);
+	return 0;
+}
+
diff --git a/libpixelflinger/raster.cpp b/libpixelflinger/raster.cpp
new file mode 100644
index 0000000..e95c2c8
--- /dev/null
+++ b/libpixelflinger/raster.cpp
@@ -0,0 +1,216 @@
+/* libs/pixelflinger/raster.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+
+#include <string.h>
+
+#include "raster.h"
+#include "trap.h"
+
+namespace android {
+
+static void ggl_rasterPos2x(void* con, GGLfixed x, GGLfixed y);
+static void ggl_rasterPos2i(void* con, GGLint x, GGLint y);
+static void ggl_copyPixels(void* con, GGLint xs, GGLint ys,
+        GGLsizei width, GGLsizei height, GGLenum type);
+
+void ggl_init_raster(context_t* c)
+{
+    GGLContext& procs = *(GGLContext*)c;
+    GGL_INIT_PROC(procs, copyPixels);
+    GGL_INIT_PROC(procs, rasterPos2x);
+    GGL_INIT_PROC(procs, rasterPos2i);
+}
+
+void ggl_rasterPos2x(void* con, GGLfixed x, GGLfixed y)
+{
+    GGL_CONTEXT(c, con);
+    // raster pos should be processed just like glVertex
+    c->state.raster.x = x;
+    c->state.raster.y = y;
+}
+
+void ggl_rasterPos2i(void* con, GGLint x, GGLint y)
+{
+    ggl_rasterPos2x(con, gglIntToFixed(x), gglIntToFixed(y));
+}
+
+void ggl_copyPixels(void* con, GGLint xs, GGLint ys,
+        GGLsizei width, GGLsizei height, GGLenum /*type*/)
+{
+    GGL_CONTEXT(c, con);
+
+    // color-buffer
+    surface_t* cb = &(c->state.buffers.color);
+
+    // undefined behaviour if we try to copy from outside the surface
+    if (uint32_t(xs) > cb->width)
+        return;
+    if (uint32_t(ys) > cb->height)
+        return;
+    if (uint32_t(xs + width) > cb->width)
+        return;
+    if (uint32_t(ys + height) > cb->height)
+        return;
+
+    // copy to current raster position
+    GGLint xd = gglFixedToIntRound(c->state.raster.x);
+    GGLint yd = gglFixedToIntRound(c->state.raster.y);
+
+    // clip to scissor
+    if (xd < GGLint(c->state.scissor.left)) {
+        GGLint offset = GGLint(c->state.scissor.left) - xd;
+        xd = GGLint(c->state.scissor.left);
+        xs += offset;
+        width -= offset;
+    }
+    if (yd < GGLint(c->state.scissor.top)) {
+        GGLint offset = GGLint(c->state.scissor.top) - yd;
+        yd = GGLint(c->state.scissor.top);
+        ys += offset;
+        height -= offset;
+    }
+    if ((xd + width) > GGLint(c->state.scissor.right)) {
+        width = GGLint(c->state.scissor.right) - xd;
+    }
+    if ((yd + height) > GGLint(c->state.scissor.bottom)) {
+        height = GGLint(c->state.scissor.bottom) - yd;
+    }
+
+    if (width<=0 || height<=0) {
+        return; // nothing to copy
+    }
+
+    if (xs==xd && ys==yd) {
+        // nothing to do, but be careful, this might not be true when we support
+        // gglPixelTransfer, gglPixelMap and gglPixelZoom
+        return;
+    }
+
+    const GGLFormat* fp = &(c->formats[cb->format]);
+    uint8_t* src = reinterpret_cast<uint8_t*>(cb->data)
+            + (xs + (cb->stride * ys)) * fp->size;
+    uint8_t* dst = reinterpret_cast<uint8_t*>(cb->data)
+            + (xd + (cb->stride * yd)) * fp->size;
+    const size_t bpr = cb->stride * fp->size;
+    const size_t rowsize = width * fp->size;
+    size_t yc = height;
+
+    if (ys < yd) {
+        // bottom to top
+        src += height * bpr;
+        dst += height * bpr;
+        do {
+            dst -= bpr;
+            src -= bpr;
+            memcpy(dst, src, rowsize);
+        } while (--yc);
+    } else {
+        if (ys == yd) {
+            // might be right to left
+            do {
+                memmove(dst, src, rowsize);
+                dst += bpr;
+                src += bpr;
+            } while (--yc);
+        } else {
+            // top to bottom
+            do {
+                memcpy(dst, src, rowsize);
+                dst += bpr;
+                src += bpr;
+            } while (--yc);
+        }
+    }
+}
+
+}; // namespace android
+
+using namespace android;
+
+GGLint gglBitBlit(GGLContext* con, int tmu, GGLint crop[4], GGLint where[4])
+{
+    GGL_CONTEXT(c, (void*)con);
+
+     GGLint x = where[0];
+     GGLint y = where[1];
+     GGLint w = where[2];
+     GGLint h = where[3];
+
+    // exclsively enable this tmu
+    c->procs.activeTexture(c, tmu);
+    c->procs.disable(c, GGL_W_LERP);
+
+    uint32_t tmus = 1UL<<tmu;
+    if (c->state.enabled_tmu != tmus) {
+        c->activeTMU->enable = 1;
+        c->state.enabled_tmu = tmus;
+        c->state.enables |= GGL_ENABLE_TMUS;
+        ggl_state_changed(c, GGL_TMU_STATE);
+    }
+
+    const GGLint Wcr = crop[2];
+    const GGLint Hcr = crop[3];
+    if ((w == Wcr) && (h == Hcr)) {
+        c->procs.texGeni(c, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+        c->procs.texGeni(c, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+        const GGLint Ucr = crop[0];
+        const GGLint Vcr = crop[1];
+        const GGLint s0  = Ucr - x;
+        const GGLint t0  = Vcr - y;
+        c->procs.texCoord2i(c, s0, t0);
+        c->procs.recti(c, x, y, x+w, y+h);
+    } else {
+        int32_t texcoords[8];
+        x = gglIntToFixed(x);
+        y = gglIntToFixed(y); 
+    
+        // we CLAMP here, which works with premultiplied (s,t)
+        c->procs.texParameteri(c, GGL_TEXTURE_2D, GGL_TEXTURE_WRAP_S, GGL_CLAMP);
+        c->procs.texParameteri(c, GGL_TEXTURE_2D, GGL_TEXTURE_WRAP_T, GGL_CLAMP);
+        c->procs.texGeni(c, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_AUTOMATIC);
+        c->procs.texGeni(c, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_AUTOMATIC);
+    
+        const GGLint Ucr = crop[0] << 16;
+        const GGLint Vcr = crop[1] << 16;
+        const GGLint Wcr = crop[2] << 16;
+        const GGLint Hcr = crop[3] << 16;
+    
+        // computes texture coordinates (pre-multiplied)
+        int32_t dsdx = Wcr / w;   // dsdx = ((Wcr/w)/Wt)*Wt
+        int32_t dtdy = Hcr / h;   // dtdy = ((Hcr/h)/Ht)*Ht
+        int32_t s0   = Ucr - gglMulx(dsdx, x); // s0 = Ucr - x * dsdx
+        int32_t t0   = Vcr - gglMulx(dtdy, y); // t0 = Vcr - y * dtdy
+        texcoords[0] = s0;
+        texcoords[1] = dsdx;
+        texcoords[2] = 0;
+        texcoords[3] = t0;
+        texcoords[4] = 0;
+        texcoords[5] = dtdy;
+        texcoords[6] = 0;
+        texcoords[7] = 0;
+        c->procs.texCoordGradScale8xv(c, tmu, texcoords);
+        c->procs.recti(c, 
+                gglFixedToIntRound(x),
+                gglFixedToIntRound(y),
+                gglFixedToIntRound(x)+w,
+                gglFixedToIntRound(y)+h);
+    }
+    return 0;
+}
+
diff --git a/libpixelflinger/raster.h b/libpixelflinger/raster.h
new file mode 100644
index 0000000..9f0f240
--- /dev/null
+++ b/libpixelflinger/raster.h
@@ -0,0 +1,33 @@
+/* libs/pixelflinger/raster.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#ifndef ANDROID_GGL_RASTER_H
+#define ANDROID_GGL_RASTER_H
+
+#include <private/pixelflinger/ggl_context.h>
+
+namespace android {
+
+void ggl_init_raster(context_t* c);
+
+void gglCopyPixels(void* c, GGLint x, GGLint y, GGLsizei width, GGLsizei height, GGLenum type);
+void gglRasterPos2d(void* c, GGLint x, GGLint y);
+
+}; // namespace android
+
+#endif // ANDROID_GGL_RASTER_H
diff --git a/libpixelflinger/scanline.cpp b/libpixelflinger/scanline.cpp
new file mode 100644
index 0000000..4cc23c7
--- /dev/null
+++ b/libpixelflinger/scanline.cpp
@@ -0,0 +1,2373 @@
+/* libs/pixelflinger/scanline.cpp
+**
+** Copyright 2006-2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "pixelflinger"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cutils/memory.h>
+#include <log/log.h>
+
+#include "buffer.h"
+#include "scanline.h"
+
+#include "codeflinger/CodeCache.h"
+#include "codeflinger/GGLAssembler.h"
+#if defined(__arm__)
+#include "codeflinger/ARMAssembler.h"
+#elif defined(__aarch64__)
+#include "codeflinger/Arm64Assembler.h"
+#elif defined(__mips__) && !defined(__LP64__) && __mips_isa_rev < 6
+#include "codeflinger/MIPSAssembler.h"
+#elif defined(__mips__) && defined(__LP64__)
+#include "codeflinger/MIPS64Assembler.h"
+#endif
+//#include "codeflinger/ARMAssemblerOptimizer.h"
+
+// ----------------------------------------------------------------------------
+
+#define ANDROID_CODEGEN_GENERIC     0   // force generic pixel pipeline
+#define ANDROID_CODEGEN_C           1   // hand-written C, fallback generic 
+#define ANDROID_CODEGEN_ASM         2   // hand-written asm, fallback generic
+#define ANDROID_CODEGEN_GENERATED   3   // hand-written asm, fallback codegen
+
+#ifdef NDEBUG
+#   define ANDROID_RELEASE
+#   define ANDROID_CODEGEN      ANDROID_CODEGEN_GENERATED
+#else
+#   define ANDROID_DEBUG
+#   define ANDROID_CODEGEN      ANDROID_CODEGEN_GENERATED
+#endif
+
+#if defined(__arm__) || (defined(__mips__) && ((!defined(__LP64__) && __mips_isa_rev < 6) || defined(__LP64__))) || defined(__aarch64__)
+#   define ANDROID_ARM_CODEGEN  1
+#else
+#   define ANDROID_ARM_CODEGEN  0
+#endif
+
+#define DEBUG__CODEGEN_ONLY     0
+
+/* Set to 1 to dump to the log the states that need a new
+ * code-generated scanline callback, i.e. those that don't
+ * have a corresponding shortcut function.
+ */
+#define DEBUG_NEEDS  0
+
+#if defined( __mips__) && ((!defined(__LP64__) && __mips_isa_rev < 6) || defined(__LP64__))
+#define ASSEMBLY_SCRATCH_SIZE   4096
+#elif defined(__aarch64__)
+#define ASSEMBLY_SCRATCH_SIZE   8192
+#else
+#define ASSEMBLY_SCRATCH_SIZE   2048
+#endif
+
+// ----------------------------------------------------------------------------
+namespace android {
+// ----------------------------------------------------------------------------
+
+static void init_y(context_t*, int32_t);
+static void init_y_noop(context_t*, int32_t);
+static void init_y_packed(context_t*, int32_t);
+static void init_y_error(context_t*, int32_t);
+
+static void step_y__generic(context_t* c);
+static void step_y__nop(context_t*);
+static void step_y__smooth(context_t* c);
+static void step_y__tmu(context_t* c);
+static void step_y__w(context_t* c);
+
+static void scanline(context_t* c);
+static void scanline_perspective(context_t* c);
+static void scanline_perspective_single(context_t* c);
+static void scanline_t32cb16blend(context_t* c);
+static void scanline_t32cb16blend_dither(context_t* c);
+static void scanline_t32cb16blend_srca(context_t* c);
+static void scanline_t32cb16blend_clamp(context_t* c);
+static void scanline_t32cb16blend_clamp_dither(context_t* c);
+static void scanline_t32cb16blend_clamp_mod(context_t* c);
+static void scanline_x32cb16blend_clamp_mod(context_t* c);
+static void scanline_t32cb16blend_clamp_mod_dither(context_t* c);
+static void scanline_x32cb16blend_clamp_mod_dither(context_t* c);
+static void scanline_t32cb16(context_t* c);
+static void scanline_t32cb16_dither(context_t* c);
+static void scanline_t32cb16_clamp(context_t* c);
+static void scanline_t32cb16_clamp_dither(context_t* c);
+static void scanline_col32cb16blend(context_t* c);
+static void scanline_t16cb16_clamp(context_t* c);
+static void scanline_t16cb16blend_clamp_mod(context_t* c);
+static void scanline_memcpy(context_t* c);
+static void scanline_memset8(context_t* c);
+static void scanline_memset16(context_t* c);
+static void scanline_memset32(context_t* c);
+static void scanline_noop(context_t* c);
+static void scanline_set(context_t* c);
+static void scanline_clear(context_t* c);
+
+static void rect_generic(context_t* c, size_t yc);
+static void rect_memcpy(context_t* c, size_t yc);
+
+#if defined( __arm__)
+extern "C" void scanline_t32cb16blend_arm(uint16_t*, uint32_t*, size_t);
+extern "C" void scanline_t32cb16_arm(uint16_t *dst, uint32_t *src, size_t ct);
+extern "C" void scanline_col32cb16blend_neon(uint16_t *dst, uint32_t *col, size_t ct);
+extern "C" void scanline_col32cb16blend_arm(uint16_t *dst, uint32_t col, size_t ct);
+#elif defined(__aarch64__)
+extern "C" void scanline_t32cb16blend_arm64(uint16_t*, uint32_t*, size_t);
+extern "C" void scanline_col32cb16blend_arm64(uint16_t *dst, uint32_t col, size_t ct);
+#elif defined(__mips__) && !defined(__LP64__) && __mips_isa_rev < 6
+extern "C" void scanline_t32cb16blend_mips(uint16_t*, uint32_t*, size_t);
+#elif defined(__mips__) && defined(__LP64__)
+extern "C" void scanline_t32cb16blend_mips64(uint16_t*, uint32_t*, size_t);
+extern "C" void scanline_col32cb16blend_mips64(uint16_t *dst, uint32_t col, size_t ct);
+#endif
+
+// ----------------------------------------------------------------------------
+
+static inline uint16_t  convertAbgr8888ToRgb565(uint32_t  pix)
+{
+    return uint16_t( ((pix << 8) & 0xf800) |
+                      ((pix >> 5) & 0x07e0) |
+                      ((pix >> 19) & 0x001f) );
+}
+
+struct shortcut_t {
+    needs_filter_t  filter;
+    const char*     desc;
+    void            (*scanline)(context_t*);
+    void            (*init_y)(context_t*, int32_t);
+};
+
+// Keep in sync with needs
+
+/* To understand the values here, have a look at:
+ *     system/core/include/private/pixelflinger/ggl_context.h
+ *
+ * Especially the lines defining and using GGL_RESERVE_NEEDS
+ *
+ * Quick reminders:
+ *   - the last nibble of the first value is the destination buffer format.
+ *   - the last nibble of the third value is the source texture format
+ *   - formats: 4=rgb565 1=abgr8888 2=xbgr8888
+ *
+ * In the descriptions below:
+ *
+ *   SRC      means we copy the source pixels to the destination
+ *
+ *   SRC_OVER means we blend the source pixels to the destination
+ *            with dstFactor = 1-srcA, srcFactor=1  (premultiplied source).
+ *            This mode is otherwise called 'blend'.
+ *
+ *   SRCA_OVER means we blend the source pixels to the destination
+ *             with dstFactor=srcA*(1-srcA) srcFactor=srcA (non-premul source).
+ *             This mode is otherwise called 'blend_srca'
+ *
+ *   clamp    means we fetch source pixels from a texture with u/v clamping
+ *
+ *   mod      means the source pixels are modulated (multiplied) by the
+ *            a/r/g/b of the current context's color. Typically used for
+ *            fade-in / fade-out.
+ *
+ *   dither   means we dither 32 bit values to 16 bits
+ */
+static shortcut_t shortcuts[] = {
+    { { { 0x03515104, 0x00000077, { 0x00000A01, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, blend SRC_OVER", scanline_t32cb16blend, init_y_noop },
+    { { { 0x03010104, 0x00000077, { 0x00000A01, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, SRC", scanline_t32cb16, init_y_noop  },
+    /* same as first entry, but with dithering */
+    { { { 0x03515104, 0x00000177, { 0x00000A01, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, blend SRC_OVER dither", scanline_t32cb16blend_dither, init_y_noop },
+    /* same as second entry, but with dithering */
+    { { { 0x03010104, 0x00000177, { 0x00000A01, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, SRC dither", scanline_t32cb16_dither, init_y_noop  },
+    /* this is used during the boot animation - CHEAT: ignore dithering */
+    { { { 0x03545404, 0x00000077, { 0x00000A01, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFEFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, blend dst:ONE_MINUS_SRCA src:SRCA", scanline_t32cb16blend_srca, init_y_noop },
+    /* special case for arbitrary texture coordinates (think scaling) */
+    { { { 0x03515104, 0x00000077, { 0x00000001, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, SRC_OVER clamp", scanline_t32cb16blend_clamp, init_y },
+    { { { 0x03515104, 0x00000177, { 0x00000001, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, SRC_OVER clamp dither", scanline_t32cb16blend_clamp_dither, init_y },
+    /* another case used during emulation */
+    { { { 0x03515104, 0x00000077, { 0x00001001, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, SRC_OVER clamp modulate", scanline_t32cb16blend_clamp_mod, init_y },
+    /* and this */
+    { { { 0x03515104, 0x00000077, { 0x00001002, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, x888 tx, SRC_OVER clamp modulate", scanline_x32cb16blend_clamp_mod, init_y },
+    { { { 0x03515104, 0x00000177, { 0x00001001, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, SRC_OVER clamp modulate dither", scanline_t32cb16blend_clamp_mod_dither, init_y },
+    { { { 0x03515104, 0x00000177, { 0x00001002, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, x888 tx, SRC_OVER clamp modulate dither", scanline_x32cb16blend_clamp_mod_dither, init_y },
+    { { { 0x03010104, 0x00000077, { 0x00000001, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, SRC clamp", scanline_t32cb16_clamp, init_y  },
+    { { { 0x03010104, 0x00000077, { 0x00000002, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, x888 tx, SRC clamp", scanline_t32cb16_clamp, init_y  },
+    { { { 0x03010104, 0x00000177, { 0x00000001, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 8888 tx, SRC clamp dither", scanline_t32cb16_clamp_dither, init_y  },
+    { { { 0x03010104, 0x00000177, { 0x00000002, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, x888 tx, SRC clamp dither", scanline_t32cb16_clamp_dither, init_y  },
+    { { { 0x03010104, 0x00000077, { 0x00000004, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 565 tx, SRC clamp", scanline_t16cb16_clamp, init_y  },
+    { { { 0x03515104, 0x00000077, { 0x00001004, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0x0000003F } } },
+        "565 fb, 565 tx, SRC_OVER clamp", scanline_t16cb16blend_clamp_mod, init_y  },
+    { { { 0x03515104, 0x00000077, { 0x00000000, 0x00000000 } },
+        { 0xFFFFFFFF, 0xFFFFFFFF, { 0xFFFFFFFF, 0xFFFFFFFF } } },
+        "565 fb, 8888 fixed color", scanline_col32cb16blend, init_y_packed  },  
+    { { { 0x00000000, 0x00000000, { 0x00000000, 0x00000000 } },
+        { 0x00000000, 0x00000007, { 0x00000000, 0x00000000 } } },
+        "(nop) alpha test", scanline_noop, init_y_noop },
+    { { { 0x00000000, 0x00000000, { 0x00000000, 0x00000000 } },
+        { 0x00000000, 0x00000070, { 0x00000000, 0x00000000 } } },
+        "(nop) depth test", scanline_noop, init_y_noop },
+    { { { 0x05000000, 0x00000000, { 0x00000000, 0x00000000 } },
+        { 0x0F000000, 0x00000080, { 0x00000000, 0x00000000 } } },
+        "(nop) logic_op", scanline_noop, init_y_noop },
+    { { { 0xF0000000, 0x00000000, { 0x00000000, 0x00000000 } },
+        { 0xF0000000, 0x00000080, { 0x00000000, 0x00000000 } } },
+        "(nop) color mask", scanline_noop, init_y_noop },
+    { { { 0x0F000000, 0x00000077, { 0x00000000, 0x00000000 } },
+        { 0xFF000000, 0x000000F7, { 0x00000000, 0x00000000 } } },
+        "(set) logic_op", scanline_set, init_y_noop },
+    { { { 0x00000000, 0x00000077, { 0x00000000, 0x00000000 } },
+        { 0xFF000000, 0x000000F7, { 0x00000000, 0x00000000 } } },
+        "(clear) logic_op", scanline_clear, init_y_noop },
+    { { { 0x03000000, 0x00000077, { 0x00000000, 0x00000000 } },
+        { 0xFFFFFF00, 0x000000F7, { 0x00000000, 0x00000000 } } },
+        "(clear) blending 0/0", scanline_clear, init_y_noop },
+    { { { 0x00000000, 0x00000000, { 0x00000000, 0x00000000 } },
+        { 0x0000003F, 0x00000000, { 0x00000000, 0x00000000 } } },
+        "(error) invalid color-buffer format", scanline_noop, init_y_error },
+};
+static const needs_filter_t noblend1to1 = {
+        // (disregard dithering, see below)
+        { 0x03010100, 0x00000077, { 0x00000A00, 0x00000000 } },
+        { 0xFFFFFFC0, 0xFFFFFEFF, { 0xFFFFFFC0, 0x0000003F } }
+};
+static  const needs_filter_t fill16noblend = {
+        { 0x03010100, 0x00000077, { 0x00000000, 0x00000000 } },
+        { 0xFFFFFFC0, 0xFFFFFFFF, { 0x0000003F, 0x0000003F } }
+};
+
+// ----------------------------------------------------------------------------
+
+#if ANDROID_ARM_CODEGEN
+
+#if defined(__mips__) && ((!defined(__LP64__) && __mips_isa_rev < 6) || defined(__LP64__))
+static CodeCache gCodeCache(32 * 1024);
+#elif defined(__aarch64__)
+static CodeCache gCodeCache(48 * 1024);
+#else
+static CodeCache gCodeCache(12 * 1024);
+#endif
+
+class ScanlineAssembly : public Assembly {
+    AssemblyKey<needs_t> mKey;
+public:
+    ScanlineAssembly(needs_t needs, size_t size)
+        : Assembly(size), mKey(needs) { }
+    const AssemblyKey<needs_t>& key() const { return mKey; }
+};
+#endif
+
+// ----------------------------------------------------------------------------
+
+void ggl_init_scanline(context_t* c)
+{
+    c->init_y = init_y;
+    c->step_y = step_y__generic;
+    c->scanline = scanline;
+}
+
+void ggl_uninit_scanline(context_t* c)
+{
+    if (c->state.buffers.coverage)
+        free(c->state.buffers.coverage);
+#if ANDROID_ARM_CODEGEN
+    if (c->scanline_as)
+        c->scanline_as->decStrong(c);
+#endif
+}
+
+// ----------------------------------------------------------------------------
+
+static void pick_scanline(context_t* c)
+{
+#if (!defined(DEBUG__CODEGEN_ONLY) || (DEBUG__CODEGEN_ONLY == 0))
+
+#if ANDROID_CODEGEN == ANDROID_CODEGEN_GENERIC
+    c->init_y = init_y;
+    c->step_y = step_y__generic;
+    c->scanline = scanline;
+    return;
+#endif
+
+    //printf("*** needs [%08lx:%08lx:%08lx:%08lx]\n",
+    //    c->state.needs.n, c->state.needs.p,
+    //    c->state.needs.t[0], c->state.needs.t[1]);
+
+    // first handle the special case that we cannot test with a filter
+    const uint32_t cb_format = GGL_READ_NEEDS(CB_FORMAT, c->state.needs.n);
+    if (GGL_READ_NEEDS(T_FORMAT, c->state.needs.t[0]) == cb_format) {
+        if (c->state.needs.match(noblend1to1)) {
+            // this will match regardless of dithering state, since both
+            // src and dest have the same format anyway, there is no dithering
+            // to be done.
+            const GGLFormat* f =
+                &(c->formats[GGL_READ_NEEDS(T_FORMAT, c->state.needs.t[0])]);
+            if ((f->components == GGL_RGB) ||
+                (f->components == GGL_RGBA) ||
+                (f->components == GGL_LUMINANCE) ||
+                (f->components == GGL_LUMINANCE_ALPHA))
+            {
+                // format must have all of RGB components
+                // (so the current color doesn't show through)
+                c->scanline = scanline_memcpy;
+                c->init_y = init_y_noop;
+                return;
+            }
+        }
+    }
+
+    if (c->state.needs.match(fill16noblend)) {
+        c->init_y = init_y_packed;
+        switch (c->formats[cb_format].size) {
+        case 1: c->scanline = scanline_memset8;  return;
+        case 2: c->scanline = scanline_memset16; return;
+        case 4: c->scanline = scanline_memset32; return;
+        }
+    }
+
+    const int numFilters = sizeof(shortcuts)/sizeof(shortcut_t);
+    for (int i=0 ; i<numFilters ; i++) {
+        if (c->state.needs.match(shortcuts[i].filter)) {
+            c->scanline = shortcuts[i].scanline;
+            c->init_y = shortcuts[i].init_y;
+            return;
+        }
+    }
+
+#if DEBUG_NEEDS
+    ALOGI("Needs: n=0x%08x p=0x%08x t0=0x%08x t1=0x%08x",
+         c->state.needs.n, c->state.needs.p,
+         c->state.needs.t[0], c->state.needs.t[1]);
+#endif
+
+#endif // DEBUG__CODEGEN_ONLY
+
+    c->init_y = init_y;
+    c->step_y = step_y__generic;
+
+#if ANDROID_ARM_CODEGEN
+    // we're going to have to generate some code...
+    // here, generate code for our pixel pipeline
+    const AssemblyKey<needs_t> key(c->state.needs);
+    sp<Assembly> assembly = gCodeCache.lookup(key);
+    if (assembly == 0) {
+        // create a new assembly region
+        sp<ScanlineAssembly> a = new ScanlineAssembly(c->state.needs, 
+                ASSEMBLY_SCRATCH_SIZE);
+        // initialize our assembler
+#if defined(__arm__)
+        GGLAssembler assembler( new ARMAssembler(a) );
+        //GGLAssembler assembler(
+        //        new ARMAssemblerOptimizer(new ARMAssembler(a)) );
+#endif
+#if defined(__mips__) && !defined(__LP64__) && __mips_isa_rev < 6
+        GGLAssembler assembler( new ArmToMipsAssembler(a) );
+#elif defined(__mips__) && defined(__LP64__)
+        GGLAssembler assembler( new ArmToMips64Assembler(a) );
+#elif defined(__aarch64__)
+        GGLAssembler assembler( new ArmToArm64Assembler(a) );
+#endif
+        // generate the scanline code for the given needs
+        bool err = assembler.scanline(c->state.needs, c) != 0;
+        if (ggl_likely(!err)) {
+            // finally, cache this assembly
+            err = gCodeCache.cache(a->key(), a) < 0;
+        }
+        if (ggl_unlikely(err)) {
+            ALOGE("error generating or caching assembly. Reverting to NOP.");
+            c->scanline = scanline_noop;
+            c->init_y = init_y_noop;
+            c->step_y = step_y__nop;
+            return;
+        }
+        assembly = a;
+    }
+
+    // release the previous assembly
+    if (c->scanline_as) {
+        c->scanline_as->decStrong(c);
+    }
+
+    //ALOGI("using generated pixel-pipeline");
+    c->scanline_as = assembly.get();
+    c->scanline_as->incStrong(c); //  hold on to assembly
+    c->scanline = (void(*)(context_t* c))assembly->base();
+#else
+//    ALOGW("using generic (slow) pixel-pipeline");
+    c->scanline = scanline;
+#endif
+}
+
+void ggl_pick_scanline(context_t* c)
+{
+    pick_scanline(c);
+    if ((c->state.enables & GGL_ENABLE_W) &&
+        (c->state.enables & GGL_ENABLE_TMUS))
+    {
+        c->span = c->scanline;
+        c->scanline = scanline_perspective;
+        if (!(c->state.enabled_tmu & (c->state.enabled_tmu - 1))) {
+            // only one TMU enabled
+            c->scanline = scanline_perspective_single;
+        }
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static void blending(context_t* c, pixel_t* fragment, pixel_t* fb);
+static void blend_factor(context_t* c, pixel_t* r, uint32_t factor,
+        const pixel_t* src, const pixel_t* dst);
+static void rescale(uint32_t& u, uint8_t& su, uint32_t& v, uint8_t& sv);
+
+#if ANDROID_ARM_CODEGEN && (ANDROID_CODEGEN == ANDROID_CODEGEN_GENERATED)
+
+// no need to compile the generic-pipeline, it can't be reached
+void scanline(context_t*)
+{
+}
+
+#else
+
+void rescale(uint32_t& u, uint8_t& su, uint32_t& v, uint8_t& sv)
+{
+    if (su && sv) {
+        if (su > sv) {
+            v = ggl_expand(v, sv, su);
+            sv = su;
+        } else if (su < sv) {
+            u = ggl_expand(u, su, sv);
+            su = sv;
+        }
+    }
+}
+
+void blending(context_t* c, pixel_t* fragment, pixel_t* fb)
+{
+    rescale(fragment->c[0], fragment->s[0], fb->c[0], fb->s[0]);
+    rescale(fragment->c[1], fragment->s[1], fb->c[1], fb->s[1]);
+    rescale(fragment->c[2], fragment->s[2], fb->c[2], fb->s[2]);
+    rescale(fragment->c[3], fragment->s[3], fb->c[3], fb->s[3]);
+
+    pixel_t sf, df;
+    blend_factor(c, &sf, c->state.blend.src, fragment, fb);
+    blend_factor(c, &df, c->state.blend.dst, fragment, fb);
+
+    fragment->c[1] =
+            gglMulAddx(fragment->c[1], sf.c[1], gglMulx(fb->c[1], df.c[1]));
+    fragment->c[2] =
+            gglMulAddx(fragment->c[2], sf.c[2], gglMulx(fb->c[2], df.c[2]));
+    fragment->c[3] =
+            gglMulAddx(fragment->c[3], sf.c[3], gglMulx(fb->c[3], df.c[3]));
+
+    if (c->state.blend.alpha_separate) {
+        blend_factor(c, &sf, c->state.blend.src_alpha, fragment, fb);
+        blend_factor(c, &df, c->state.blend.dst_alpha, fragment, fb);
+    }
+
+    fragment->c[0] =
+            gglMulAddx(fragment->c[0], sf.c[0], gglMulx(fb->c[0], df.c[0]));
+
+    // clamp to 1.0
+    if (fragment->c[0] >= (1LU<<fragment->s[0]))
+        fragment->c[0] = (1<<fragment->s[0])-1;
+    if (fragment->c[1] >= (1LU<<fragment->s[1]))
+        fragment->c[1] = (1<<fragment->s[1])-1;
+    if (fragment->c[2] >= (1LU<<fragment->s[2]))
+        fragment->c[2] = (1<<fragment->s[2])-1;
+    if (fragment->c[3] >= (1LU<<fragment->s[3]))
+        fragment->c[3] = (1<<fragment->s[3])-1;
+}
+
+static inline int blendfactor(uint32_t x, uint32_t size, uint32_t def = 0)
+{
+    if (!size)
+        return def;
+
+    // scale to 16 bits
+    if (size > 16) {
+        x >>= (size - 16);
+    } else if (size < 16) {
+        x = ggl_expand(x, size, 16);
+    }
+    x += x >> 15;
+    return x;
+}
+
+void blend_factor(context_t* /*c*/, pixel_t* r, 
+        uint32_t factor, const pixel_t* src, const pixel_t* dst)
+{
+    switch (factor) {
+        case GGL_ZERO:
+            r->c[1] = 
+            r->c[2] = 
+            r->c[3] = 
+            r->c[0] = 0;
+            break;
+        case GGL_ONE:
+            r->c[1] = 
+            r->c[2] = 
+            r->c[3] = 
+            r->c[0] = FIXED_ONE;
+            break;
+        case GGL_DST_COLOR:
+            r->c[1] = blendfactor(dst->c[1], dst->s[1]);
+            r->c[2] = blendfactor(dst->c[2], dst->s[2]);
+            r->c[3] = blendfactor(dst->c[3], dst->s[3]);
+            r->c[0] = blendfactor(dst->c[0], dst->s[0]);
+            break;
+        case GGL_SRC_COLOR:
+            r->c[1] = blendfactor(src->c[1], src->s[1]);
+            r->c[2] = blendfactor(src->c[2], src->s[2]);
+            r->c[3] = blendfactor(src->c[3], src->s[3]);
+            r->c[0] = blendfactor(src->c[0], src->s[0]);
+            break;
+        case GGL_ONE_MINUS_DST_COLOR:
+            r->c[1] = FIXED_ONE - blendfactor(dst->c[1], dst->s[1]);
+            r->c[2] = FIXED_ONE - blendfactor(dst->c[2], dst->s[2]);
+            r->c[3] = FIXED_ONE - blendfactor(dst->c[3], dst->s[3]);
+            r->c[0] = FIXED_ONE - blendfactor(dst->c[0], dst->s[0]);
+            break;
+        case GGL_ONE_MINUS_SRC_COLOR:
+            r->c[1] = FIXED_ONE - blendfactor(src->c[1], src->s[1]);
+            r->c[2] = FIXED_ONE - blendfactor(src->c[2], src->s[2]);
+            r->c[3] = FIXED_ONE - blendfactor(src->c[3], src->s[3]);
+            r->c[0] = FIXED_ONE - blendfactor(src->c[0], src->s[0]);
+            break;
+        case GGL_SRC_ALPHA:
+            r->c[1] = 
+            r->c[2] = 
+            r->c[3] = 
+            r->c[0] = blendfactor(src->c[0], src->s[0], FIXED_ONE);
+            break;
+        case GGL_ONE_MINUS_SRC_ALPHA:
+            r->c[1] = 
+            r->c[2] = 
+            r->c[3] = 
+            r->c[0] = FIXED_ONE - blendfactor(src->c[0], src->s[0], FIXED_ONE);
+            break;
+        case GGL_DST_ALPHA:
+            r->c[1] = 
+            r->c[2] = 
+            r->c[3] = 
+            r->c[0] = blendfactor(dst->c[0], dst->s[0], FIXED_ONE);
+            break;
+        case GGL_ONE_MINUS_DST_ALPHA:
+            r->c[1] = 
+            r->c[2] = 
+            r->c[3] = 
+            r->c[0] = FIXED_ONE - blendfactor(dst->c[0], dst->s[0], FIXED_ONE);
+            break;
+        case GGL_SRC_ALPHA_SATURATE:
+            // XXX: GGL_SRC_ALPHA_SATURATE
+            break;
+    }
+}
+
+static GGLfixed wrapping(int32_t coord, uint32_t size, int tx_wrap)
+{
+    GGLfixed d;
+    if (tx_wrap == GGL_REPEAT) {
+        d = (uint32_t(coord)>>16) * size;
+    } else if (tx_wrap == GGL_CLAMP) { // CLAMP_TO_EDGE semantics
+        const GGLfixed clamp_min = FIXED_HALF;
+        const GGLfixed clamp_max = (size << 16) - FIXED_HALF;
+        if (coord < clamp_min)     coord = clamp_min;
+        if (coord > clamp_max)     coord = clamp_max;
+        d = coord;
+    } else { // 1:1
+        const GGLfixed clamp_min = 0;
+        const GGLfixed clamp_max = (size << 16);
+        if (coord < clamp_min)     coord = clamp_min;
+        if (coord > clamp_max)     coord = clamp_max;
+        d = coord;
+    }
+    return d;
+}
+
+static inline
+GGLcolor ADJUST_COLOR_ITERATOR(GGLcolor v, GGLcolor dvdx, int len)
+{
+    const int32_t end = dvdx * (len-1) + v;
+    if (end < 0)
+        v -= end;
+    v &= ~(v>>31);
+    return v;
+}
+
+void scanline(context_t* c)
+{
+    const uint32_t enables = c->state.enables;
+    const int xs = c->iterators.xl;
+    const int x1 = c->iterators.xr;
+	int xc = x1 - xs;
+    const int16_t* covPtr = c->state.buffers.coverage + xs;
+
+    // All iterated values are sampled at the pixel center
+
+    // reset iterators for that scanline...
+    GGLcolor r, g, b, a;
+    iterators_t& ci = c->iterators;
+    if (enables & GGL_ENABLE_SMOOTH) {
+        r = (xs * c->shade.drdx) + ci.ydrdy;
+        g = (xs * c->shade.dgdx) + ci.ydgdy;
+        b = (xs * c->shade.dbdx) + ci.ydbdy;
+        a = (xs * c->shade.dadx) + ci.ydady;
+        r = ADJUST_COLOR_ITERATOR(r, c->shade.drdx, xc);
+        g = ADJUST_COLOR_ITERATOR(g, c->shade.dgdx, xc);
+        b = ADJUST_COLOR_ITERATOR(b, c->shade.dbdx, xc);
+        a = ADJUST_COLOR_ITERATOR(a, c->shade.dadx, xc);
+    } else {
+        r = ci.ydrdy;
+        g = ci.ydgdy;
+        b = ci.ydbdy;
+        a = ci.ydady;
+    }
+
+    // z iterators are 1.31
+    GGLfixed z = (xs * c->shade.dzdx) + ci.ydzdy;
+    GGLfixed f = (xs * c->shade.dfdx) + ci.ydfdy;
+
+    struct {
+        GGLfixed s, t;
+    } tc[GGL_TEXTURE_UNIT_COUNT];
+    if (enables & GGL_ENABLE_TMUS) {
+        for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; ++i) {
+            if (c->state.texture[i].enable) {
+                texture_iterators_t& ti = c->state.texture[i].iterators;
+                if (enables & GGL_ENABLE_W) {
+                    tc[i].s = ti.ydsdy;
+                    tc[i].t = ti.ydtdy;
+                } else {
+                    tc[i].s = (xs * ti.dsdx) + ti.ydsdy;
+                    tc[i].t = (xs * ti.dtdx) + ti.ydtdy;
+                }
+            }
+        }
+    }
+
+    pixel_t fragment;
+    pixel_t texel;
+    pixel_t fb;
+
+	uint32_t x = xs;
+	uint32_t y = c->iterators.y;
+
+	while (xc--) {
+    
+        { // just a scope
+
+		// read color (convert to 8 bits by keeping only the integer part)
+        fragment.s[1] = fragment.s[2] =
+        fragment.s[3] = fragment.s[0] = 8;
+        fragment.c[1] = r >> (GGL_COLOR_BITS-8);
+        fragment.c[2] = g >> (GGL_COLOR_BITS-8);
+        fragment.c[3] = b >> (GGL_COLOR_BITS-8);
+        fragment.c[0] = a >> (GGL_COLOR_BITS-8);
+
+		// texturing
+        if (enables & GGL_ENABLE_TMUS) {
+            for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; ++i) {
+                texture_t& tx = c->state.texture[i];
+                if (!tx.enable)
+                    continue;
+                texture_iterators_t& ti = tx.iterators;
+                int32_t u, v;
+
+                // s-coordinate
+                if (tx.s_coord != GGL_ONE_TO_ONE) {
+                    const int w = tx.surface.width;
+                    u = wrapping(tc[i].s, w, tx.s_wrap);
+                    tc[i].s += ti.dsdx;
+                } else {
+                    u = (((tx.shade.is0>>16) + x)<<16) + FIXED_HALF;
+                }
+
+                // t-coordinate
+                if (tx.t_coord != GGL_ONE_TO_ONE) {
+                    const int h = tx.surface.height;
+                    v = wrapping(tc[i].t, h, tx.t_wrap);
+                    tc[i].t += ti.dtdx;
+                } else {
+                    v = (((tx.shade.it0>>16) + y)<<16) + FIXED_HALF;
+                }
+
+                // read texture
+                if (tx.mag_filter == GGL_NEAREST &&
+                    tx.min_filter == GGL_NEAREST)
+                {
+                    u >>= 16;
+                    v >>= 16;
+                    tx.surface.read(&tx.surface, c, u, v, &texel);
+                } else {
+                    const int w = tx.surface.width;
+                    const int h = tx.surface.height;
+                    u -= FIXED_HALF;
+                    v -= FIXED_HALF;
+                    int u0 = u >> 16;
+                    int v0 = v >> 16;
+                    int u1 = u0 + 1;
+                    int v1 = v0 + 1;
+                    if (tx.s_wrap == GGL_REPEAT) {
+                        if (u0<0)  u0 += w;
+                        if (u1<0)  u1 += w;
+                        if (u0>=w) u0 -= w;
+                        if (u1>=w) u1 -= w;
+                    } else {
+                        if (u0<0)  u0 = 0;
+                        if (u1<0)  u1 = 0;
+                        if (u0>=w) u0 = w-1;
+                        if (u1>=w) u1 = w-1;
+                    }
+                    if (tx.t_wrap == GGL_REPEAT) {
+                        if (v0<0)  v0 += h;
+                        if (v1<0)  v1 += h;
+                        if (v0>=h) v0 -= h;
+                        if (v1>=h) v1 -= h;
+                    } else {
+                        if (v0<0)  v0 = 0;
+                        if (v1<0)  v1 = 0;
+                        if (v0>=h) v0 = h-1;
+                        if (v1>=h) v1 = h-1;
+                    }
+                    pixel_t texels[4];
+                    uint32_t mm[4];
+                    tx.surface.read(&tx.surface, c, u0, v0, &texels[0]);
+                    tx.surface.read(&tx.surface, c, u0, v1, &texels[1]);
+                    tx.surface.read(&tx.surface, c, u1, v0, &texels[2]);
+                    tx.surface.read(&tx.surface, c, u1, v1, &texels[3]);
+                    u = (u >> 12) & 0xF; 
+                    v = (v >> 12) & 0xF;
+                    u += u>>3;
+                    v += v>>3;
+                    mm[0] = (0x10 - u) * (0x10 - v);
+                    mm[1] = (0x10 - u) * v;
+                    mm[2] = u * (0x10 - v);
+                    mm[3] = 0x100 - (mm[0] + mm[1] + mm[2]);
+                    for (int j=0 ; j<4 ; j++) {
+                        texel.s[j] = texels[0].s[j];
+                        if (!texel.s[j]) continue;
+                        texel.s[j] += 8;
+                        texel.c[j] =    texels[0].c[j]*mm[0] +
+                                        texels[1].c[j]*mm[1] +
+                                        texels[2].c[j]*mm[2] +
+                                        texels[3].c[j]*mm[3] ;
+                    }
+                }
+
+                // Texture environnement...
+                for (int j=0 ; j<4 ; j++) {
+                    uint32_t& Cf = fragment.c[j];
+                    uint32_t& Ct = texel.c[j];
+                    uint8_t& sf  = fragment.s[j];
+                    uint8_t& st  = texel.s[j];
+                    uint32_t At = texel.c[0];
+                    uint8_t sat = texel.s[0];
+                    switch (tx.env) {
+                    case GGL_REPLACE:
+                        if (st) {
+                            Cf = Ct;
+                            sf = st;
+                        }
+                        break;
+                    case GGL_MODULATE:
+                        if (st) {
+                            uint32_t factor = Ct + (Ct>>(st-1));
+                            Cf = (Cf * factor) >> st;
+                        }
+                        break;
+                    case GGL_DECAL:
+                        if (sat) {
+                            rescale(Cf, sf, Ct, st);
+                            Cf += ((Ct - Cf) * (At + (At>>(sat-1)))) >> sat;
+                        }
+                        break;
+                    case GGL_BLEND:
+                        if (st) {
+                            uint32_t Cc = tx.env_color[i];
+                            if (sf>8)       Cc = (Cc * ((1<<sf)-1))>>8;
+                            else if (sf<8)  Cc = (Cc - (Cc>>(8-sf)))>>(8-sf);
+                            uint32_t factor = Ct + (Ct>>(st-1));
+                            Cf = ((((1<<st) - factor) * Cf) + Ct*Cc)>>st;
+                        }
+                        break;
+                    case GGL_ADD:
+                        if (st) {
+                            rescale(Cf, sf, Ct, st);
+                            Cf += Ct;
+                        }
+                        break;
+                    }
+                }
+            }
+		}
+    
+        // coverage application
+        if (enables & GGL_ENABLE_AA) {
+            int16_t cf = *covPtr++;
+            fragment.c[0] = (int64_t(fragment.c[0]) * cf) >> 15;
+        }
+        
+        // alpha-test
+        if (enables & GGL_ENABLE_ALPHA_TEST) {
+            GGLcolor ref = c->state.alpha_test.ref;
+            GGLcolor alpha = (uint64_t(fragment.c[0]) *
+                    ((1<<GGL_COLOR_BITS)-1)) / ((1<<fragment.s[0])-1);
+            switch (c->state.alpha_test.func) {
+            case GGL_NEVER:     goto discard;
+            case GGL_LESS:      if (alpha<ref)  break; goto discard;
+            case GGL_EQUAL:     if (alpha==ref) break; goto discard;
+            case GGL_LEQUAL:    if (alpha<=ref) break; goto discard;
+            case GGL_GREATER:   if (alpha>ref)  break; goto discard;
+            case GGL_NOTEQUAL:  if (alpha!=ref) break; goto discard;
+            case GGL_GEQUAL:    if (alpha>=ref) break; goto discard;
+            }
+        }
+        
+        // depth test
+        if (c->state.buffers.depth.format) {
+            if (enables & GGL_ENABLE_DEPTH_TEST) {
+                surface_t* cb = &(c->state.buffers.depth);
+                uint16_t* p = (uint16_t*)(cb->data)+(x+(cb->stride*y));
+                uint16_t zz = uint32_t(z)>>(16);
+                uint16_t depth = *p;
+                switch (c->state.depth_test.func) {
+                case GGL_NEVER:     goto discard;
+                case GGL_LESS:      if (zz<depth)    break; goto discard;
+                case GGL_EQUAL:     if (zz==depth)   break; goto discard;
+                case GGL_LEQUAL:    if (zz<=depth)   break; goto discard;
+                case GGL_GREATER:   if (zz>depth)    break; goto discard;
+                case GGL_NOTEQUAL:  if (zz!=depth)   break; goto discard;
+                case GGL_GEQUAL:    if (zz>=depth)   break; goto discard;
+                }
+                // depth buffer is not enabled, if depth-test is not enabled
+/*
+        fragment.s[1] = fragment.s[2] =
+        fragment.s[3] = fragment.s[0] = 8;
+        fragment.c[1] = 
+        fragment.c[2] = 
+        fragment.c[3] = 
+        fragment.c[0] = 255 - (zz>>8);
+*/
+                if (c->state.mask.depth) {
+                    *p = zz;
+                }
+            }
+        }
+
+        // fog
+        if (enables & GGL_ENABLE_FOG) {
+            for (int i=1 ; i<=3 ; i++) {
+                GGLfixed fc = (c->state.fog.color[i] * 0x10000) / 0xFF;
+                uint32_t& c = fragment.c[i];
+                uint8_t& s  = fragment.s[i];
+                c = (c * 0x10000) / ((1<<s)-1);
+                c = gglMulAddx(c, f, gglMulx(fc, 0x10000 - f));
+                s = 16;
+            }
+        }
+
+        // blending
+        if (enables & GGL_ENABLE_BLENDING) {
+            fb.c[1] = fb.c[2] = fb.c[3] = fb.c[0] = 0; // placate valgrind
+            fb.s[1] = fb.s[2] = fb.s[3] = fb.s[0] = 0;
+            c->state.buffers.color.read(
+                    &(c->state.buffers.color), c, x, y, &fb);
+            blending( c, &fragment, &fb );
+        }
+
+		// write
+        c->state.buffers.color.write(
+                &(c->state.buffers.color), c, x, y, &fragment);
+        }
+
+discard:
+		// iterate...
+        x += 1;
+        if (enables & GGL_ENABLE_SMOOTH) {
+            r += c->shade.drdx;
+            g += c->shade.dgdx;
+            b += c->shade.dbdx;
+            a += c->shade.dadx;
+        }
+        z += c->shade.dzdx;
+        f += c->shade.dfdx;
+	}
+}
+
+#endif // ANDROID_ARM_CODEGEN && (ANDROID_CODEGEN == ANDROID_CODEGEN_GENERATED)
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark Scanline
+#endif
+
+/* Used to parse a 32-bit source texture linearly. Usage is:
+ *
+ * horz_iterator32  hi(context);
+ * while (...) {
+ *    uint32_t  src_pixel = hi.get_pixel32();
+ *    ...
+ * }
+ *
+ * Use only for one-to-one texture mapping.
+ */
+struct horz_iterator32 {
+    explicit horz_iterator32(context_t* c) {
+        const int x = c->iterators.xl;
+        const int y = c->iterators.y;
+        texture_t& tx = c->state.texture[0];
+        const int32_t u = (tx.shade.is0>>16) + x;
+        const int32_t v = (tx.shade.it0>>16) + y;
+        m_src = reinterpret_cast<uint32_t*>(tx.surface.data)+(u+(tx.surface.stride*v));
+    }
+    uint32_t  get_pixel32() {
+        return *m_src++;
+    }
+protected:
+    uint32_t* m_src;
+};
+
+/* A variant for 16-bit source textures. */
+struct horz_iterator16 {
+    explicit horz_iterator16(context_t* c) {
+        const int x = c->iterators.xl;
+        const int y = c->iterators.y;
+        texture_t& tx = c->state.texture[0];
+        const int32_t u = (tx.shade.is0>>16) + x;
+        const int32_t v = (tx.shade.it0>>16) + y;
+        m_src = reinterpret_cast<uint16_t*>(tx.surface.data)+(u+(tx.surface.stride*v));
+    }
+    uint16_t  get_pixel16() {
+        return *m_src++;
+    }
+protected:
+    uint16_t* m_src;
+};
+
+/* A clamp iterator is used to iterate inside a texture with GGL_CLAMP.
+ * After initialization, call get_src16() or get_src32() to get the current
+ * texture pixel value.
+ */
+struct clamp_iterator {
+    explicit clamp_iterator(context_t* c) {
+        const int xs = c->iterators.xl;
+        texture_t& tx = c->state.texture[0];
+        texture_iterators_t& ti = tx.iterators;
+        m_s = (xs * ti.dsdx) + ti.ydsdy;
+        m_t = (xs * ti.dtdx) + ti.ydtdy;
+        m_ds = ti.dsdx;
+        m_dt = ti.dtdx;
+        m_width_m1 = tx.surface.width - 1;
+        m_height_m1 = tx.surface.height - 1;
+        m_data = tx.surface.data;
+        m_stride = tx.surface.stride;
+    }
+    uint16_t get_pixel16() {
+        int  u, v;
+        get_uv(u, v);
+        uint16_t* src = reinterpret_cast<uint16_t*>(m_data) + (u + (m_stride*v));
+        return src[0];
+    }
+    uint32_t get_pixel32() {
+        int  u, v;
+        get_uv(u, v);
+        uint32_t* src = reinterpret_cast<uint32_t*>(m_data) + (u + (m_stride*v));
+        return src[0];
+    }
+private:
+    void   get_uv(int& u, int& v) {
+        int  uu = m_s >> 16;
+        int  vv = m_t >> 16;
+        if (uu < 0)
+            uu = 0;
+        if (uu > m_width_m1)
+            uu = m_width_m1;
+        if (vv < 0)
+            vv = 0;
+        if (vv > m_height_m1)
+            vv = m_height_m1;
+        u = uu;
+        v = vv;
+        m_s += m_ds;
+        m_t += m_dt;
+    }
+
+    GGLfixed  m_s, m_t;
+    GGLfixed  m_ds, m_dt;
+    int       m_width_m1, m_height_m1;
+    uint8_t*  m_data;
+    int       m_stride;
+};
+
+/*
+ * The 'horizontal clamp iterator' variant corresponds to the case where
+ * the 'v' coordinate doesn't change. This is useful to avoid one mult and
+ * extra adds / checks per pixels, if the blending/processing operation after
+ * this is very fast.
+ */
+static int is_context_horizontal(const context_t* c) {
+    return (c->state.texture[0].iterators.dtdx == 0);
+}
+
+struct horz_clamp_iterator {
+    uint16_t  get_pixel16() {
+        int  u = m_s >> 16;
+        m_s += m_ds;
+        if (u < 0)
+            u = 0;
+        if (u > m_width_m1)
+            u = m_width_m1;
+        const uint16_t* src = reinterpret_cast<const uint16_t*>(m_data);
+        return src[u];
+    }
+    uint32_t  get_pixel32() {
+        int  u = m_s >> 16;
+        m_s += m_ds;
+        if (u < 0)
+            u = 0;
+        if (u > m_width_m1)
+            u = m_width_m1;
+        const uint32_t* src = reinterpret_cast<const uint32_t*>(m_data);
+        return src[u];
+    }
+protected:
+    void init(const context_t* c, int shift);
+    GGLfixed       m_s;
+    GGLfixed       m_ds;
+    int            m_width_m1;
+    const uint8_t* m_data;
+};
+
+void horz_clamp_iterator::init(const context_t* c, int shift)
+{
+    const int xs = c->iterators.xl;
+    const texture_t& tx = c->state.texture[0];
+    const texture_iterators_t& ti = tx.iterators;
+    m_s = (xs * ti.dsdx) + ti.ydsdy;
+    m_ds = ti.dsdx;
+    m_width_m1 = tx.surface.width-1;
+    m_data = tx.surface.data;
+
+    GGLfixed t = (xs * ti.dtdx) + ti.ydtdy;
+    int      v = t >> 16;
+    if (v < 0)
+        v = 0;
+    else if (v >= (int)tx.surface.height)
+        v = (int)tx.surface.height-1;
+
+    m_data += (tx.surface.stride*v) << shift;
+}
+
+struct horz_clamp_iterator16 : horz_clamp_iterator {
+    explicit horz_clamp_iterator16(const context_t* c) {
+        init(c,1);
+    };
+};
+
+struct horz_clamp_iterator32 : horz_clamp_iterator {
+    explicit horz_clamp_iterator32(context_t* c) {
+        init(c,2);
+    };
+};
+
+/* This is used to perform dithering operations.
+ */
+struct ditherer {
+    explicit ditherer(const context_t* c) {
+        const int x = c->iterators.xl;
+        const int y = c->iterators.y;
+        m_line = &c->ditherMatrix[ ((y & GGL_DITHER_MASK)<<GGL_DITHER_ORDER_SHIFT) ];
+        m_index = x & GGL_DITHER_MASK;
+    }
+    void step(void) {
+        m_index++;
+    }
+    int  get_value(void) {
+        int ret = m_line[m_index & GGL_DITHER_MASK];
+        m_index++;
+        return ret;
+    }
+    uint16_t abgr8888ToRgb565(uint32_t s) {
+        uint32_t r = s & 0xff;
+        uint32_t g = (s >> 8) & 0xff;
+        uint32_t b = (s >> 16) & 0xff;
+        return rgb888ToRgb565(r,g,b);
+    }
+    /* The following assumes that r/g/b are in the 0..255 range each */
+    uint16_t rgb888ToRgb565(uint32_t& r, uint32_t& g, uint32_t &b) {
+        int threshold = get_value();
+        /* dither in on GGL_DITHER_BITS, and each of r, g, b is on 8 bits */
+        r += (threshold >> (GGL_DITHER_BITS-8 +5));
+        g += (threshold >> (GGL_DITHER_BITS-8 +6));
+        b += (threshold >> (GGL_DITHER_BITS-8 +5));
+        if (r > 0xff)
+            r = 0xff;
+        if (g > 0xff)
+            g = 0xff;
+        if (b > 0xff)
+            b = 0xff;
+        return uint16_t(((r & 0xf8) << 8) | ((g & 0xfc) << 3) | (b >> 3));
+    }
+protected:
+    const uint8_t* m_line;
+    int            m_index;
+};
+
+/* This structure is used to blend (SRC_OVER) 32-bit source pixels
+ * onto 16-bit destination ones. Usage is simply:
+ *
+ *   blender.blend(<32-bit-src-pixel-value>,<ptr-to-16-bit-dest-pixel>)
+ */
+struct blender_32to16 {
+    explicit blender_32to16(context_t* /*c*/) { }
+    void write(uint32_t s, uint16_t* dst) {
+        if (s == 0)
+            return;
+        s = GGL_RGBA_TO_HOST(s);
+        int sA = (s>>24);
+        if (sA == 0xff) {
+            *dst = convertAbgr8888ToRgb565(s);
+        } else {
+            int f = 0x100 - (sA + (sA>>7));
+            int sR = (s >> (   3))&0x1F;
+            int sG = (s >> ( 8+2))&0x3F;
+            int sB = (s >> (16+3))&0x1F;
+            uint16_t d = *dst;
+            int dR = (d>>11)&0x1f;
+            int dG = (d>>5)&0x3f;
+            int dB = (d)&0x1f;
+            sR += (f*dR)>>8;
+            sG += (f*dG)>>8;
+            sB += (f*dB)>>8;
+            *dst = uint16_t((sR<<11)|(sG<<5)|sB);
+        }
+    }
+    void write(uint32_t s, uint16_t* dst, ditherer& di) {
+        if (s == 0) {
+            di.step();
+            return;
+        }
+        s = GGL_RGBA_TO_HOST(s);
+        int sA = (s>>24);
+        if (sA == 0xff) {
+            *dst = di.abgr8888ToRgb565(s);
+        } else {
+            int threshold = di.get_value() << (8 - GGL_DITHER_BITS);
+            int f = 0x100 - (sA + (sA>>7));
+            int sR = (s >> (   3))&0x1F;
+            int sG = (s >> ( 8+2))&0x3F;
+            int sB = (s >> (16+3))&0x1F;
+            uint16_t d = *dst;
+            int dR = (d>>11)&0x1f;
+            int dG = (d>>5)&0x3f;
+            int dB = (d)&0x1f;
+            sR = ((sR << 8) + f*dR + threshold)>>8;
+            sG = ((sG << 8) + f*dG + threshold)>>8;
+            sB = ((sB << 8) + f*dB + threshold)>>8;
+            if (sR > 0x1f) sR = 0x1f;
+            if (sG > 0x3f) sG = 0x3f;
+            if (sB > 0x1f) sB = 0x1f;
+            *dst = uint16_t((sR<<11)|(sG<<5)|sB);
+        }
+    }
+};
+
+/* This blender does the same for the 'blend_srca' operation.
+ * where dstFactor=srcA*(1-srcA) srcFactor=srcA
+ */
+struct blender_32to16_srcA {
+    explicit blender_32to16_srcA(const context_t* /*c*/) { }
+    void write(uint32_t s, uint16_t* dst) {
+        if (!s) {
+            return;
+        }
+        uint16_t d = *dst;
+        s = GGL_RGBA_TO_HOST(s);
+        int sR = (s >> (   3))&0x1F;
+        int sG = (s >> ( 8+2))&0x3F;
+        int sB = (s >> (16+3))&0x1F;
+        int sA = (s>>24);
+        int f1 = (sA + (sA>>7));
+        int f2 = 0x100-f1;
+        int dR = (d>>11)&0x1f;
+        int dG = (d>>5)&0x3f;
+        int dB = (d)&0x1f;
+        sR = (f1*sR + f2*dR)>>8;
+        sG = (f1*sG + f2*dG)>>8;
+        sB = (f1*sB + f2*dB)>>8;
+        *dst = uint16_t((sR<<11)|(sG<<5)|sB);
+    }
+};
+
+/* Common init code the modulating blenders */
+struct blender_modulate {
+    void init(const context_t* c) {
+        const int r = c->iterators.ydrdy >> (GGL_COLOR_BITS-8);
+        const int g = c->iterators.ydgdy >> (GGL_COLOR_BITS-8);
+        const int b = c->iterators.ydbdy >> (GGL_COLOR_BITS-8);
+        const int a = c->iterators.ydady >> (GGL_COLOR_BITS-8);
+        m_r = r + (r >> 7);
+        m_g = g + (g >> 7);
+        m_b = b + (b >> 7);
+        m_a = a + (a >> 7);
+    }
+protected:
+    int m_r, m_g, m_b, m_a;
+};
+
+/* This blender does a normal blend after modulation.
+ */
+struct blender_32to16_modulate : blender_modulate {
+    explicit blender_32to16_modulate(const context_t* c) {
+        init(c);
+    }
+    void write(uint32_t s, uint16_t* dst) {
+        // blend source and destination
+        if (!s) {
+            return;
+        }
+        s = GGL_RGBA_TO_HOST(s);
+
+        /* We need to modulate s */
+        uint32_t  sA = (s >> 24);
+        uint32_t  sB = (s >> 16) & 0xff;
+        uint32_t  sG = (s >> 8) & 0xff;
+        uint32_t  sR = s & 0xff;
+
+        sA = (sA*m_a) >> 8;
+        /* Keep R/G/B scaled to 5.8 or 6.8 fixed float format */
+        sR = (sR*m_r) >> (8 - 5);
+        sG = (sG*m_g) >> (8 - 6);
+        sB = (sB*m_b) >> (8 - 5);
+
+        /* Now do a normal blend */
+        int f = 0x100 - (sA + (sA>>7));
+        uint16_t d = *dst;
+        int dR = (d>>11)&0x1f;
+        int dG = (d>>5)&0x3f;
+        int dB = (d)&0x1f;
+        sR = (sR + f*dR)>>8;
+        sG = (sG + f*dG)>>8;
+        sB = (sB + f*dB)>>8;
+        *dst = uint16_t((sR<<11)|(sG<<5)|sB);
+    }
+    void write(uint32_t s, uint16_t* dst, ditherer& di) {
+        // blend source and destination
+        if (!s) {
+            di.step();
+            return;
+        }
+        s = GGL_RGBA_TO_HOST(s);
+
+        /* We need to modulate s */
+        uint32_t  sA = (s >> 24);
+        uint32_t  sB = (s >> 16) & 0xff;
+        uint32_t  sG = (s >> 8) & 0xff;
+        uint32_t  sR = s & 0xff;
+
+        sA = (sA*m_a) >> 8;
+        /* keep R/G/B scaled to 5.8 or 6.8 fixed float format */
+        sR = (sR*m_r) >> (8 - 5);
+        sG = (sG*m_g) >> (8 - 6);
+        sB = (sB*m_b) >> (8 - 5);
+
+        /* Scale threshold to 0.8 fixed float format */
+        int threshold = di.get_value() << (8 - GGL_DITHER_BITS);
+        int f = 0x100 - (sA + (sA>>7));
+        uint16_t d = *dst;
+        int dR = (d>>11)&0x1f;
+        int dG = (d>>5)&0x3f;
+        int dB = (d)&0x1f;
+        sR = (sR + f*dR + threshold)>>8;
+        sG = (sG + f*dG + threshold)>>8;
+        sB = (sB + f*dB + threshold)>>8;
+        if (sR > 0x1f) sR = 0x1f;
+        if (sG > 0x3f) sG = 0x3f;
+        if (sB > 0x1f) sB = 0x1f;
+        *dst = uint16_t((sR<<11)|(sG<<5)|sB);
+    }
+};
+
+/* same as 32to16_modulate, except that the input is xRGB, instead of ARGB */
+struct blender_x32to16_modulate : blender_modulate {
+    explicit blender_x32to16_modulate(const context_t* c) {
+        init(c);
+    }
+    void write(uint32_t s, uint16_t* dst) {
+        s = GGL_RGBA_TO_HOST(s);
+
+        uint32_t  sB = (s >> 16) & 0xff;
+        uint32_t  sG = (s >> 8) & 0xff;
+        uint32_t  sR = s & 0xff;
+
+        /* Keep R/G/B in 5.8 or 6.8 format */
+        sR = (sR*m_r) >> (8 - 5);
+        sG = (sG*m_g) >> (8 - 6);
+        sB = (sB*m_b) >> (8 - 5);
+
+        int f = 0x100 - m_a;
+        uint16_t d = *dst;
+        int dR = (d>>11)&0x1f;
+        int dG = (d>>5)&0x3f;
+        int dB = (d)&0x1f;
+        sR = (sR + f*dR)>>8;
+        sG = (sG + f*dG)>>8;
+        sB = (sB + f*dB)>>8;
+        *dst = uint16_t((sR<<11)|(sG<<5)|sB);
+    }
+    void write(uint32_t s, uint16_t* dst, ditherer& di) {
+        s = GGL_RGBA_TO_HOST(s);
+
+        uint32_t  sB = (s >> 16) & 0xff;
+        uint32_t  sG = (s >> 8) & 0xff;
+        uint32_t  sR = s & 0xff;
+
+        sR = (sR*m_r) >> (8 - 5);
+        sG = (sG*m_g) >> (8 - 6);
+        sB = (sB*m_b) >> (8 - 5);
+
+        /* Now do a normal blend */
+        int threshold = di.get_value() << (8 - GGL_DITHER_BITS);
+        int f = 0x100 - m_a;
+        uint16_t d = *dst;
+        int dR = (d>>11)&0x1f;
+        int dG = (d>>5)&0x3f;
+        int dB = (d)&0x1f;
+        sR = (sR + f*dR + threshold)>>8;
+        sG = (sG + f*dG + threshold)>>8;
+        sB = (sB + f*dB + threshold)>>8;
+        if (sR > 0x1f) sR = 0x1f;
+        if (sG > 0x3f) sG = 0x3f;
+        if (sB > 0x1f) sB = 0x1f;
+        *dst = uint16_t((sR<<11)|(sG<<5)|sB);
+    }
+};
+
+/* Same as above, but source is 16bit rgb565 */
+struct blender_16to16_modulate : blender_modulate {
+    explicit blender_16to16_modulate(const context_t* c) {
+        init(c);
+    }
+    void write(uint16_t s16, uint16_t* dst) {
+        uint32_t  s = s16;
+
+        uint32_t  sR = s >> 11;
+        uint32_t  sG = (s >> 5) & 0x3f;
+        uint32_t  sB = s & 0x1f;
+
+        sR = (sR*m_r);
+        sG = (sG*m_g);
+        sB = (sB*m_b);
+
+        int f = 0x100 - m_a;
+        uint16_t d = *dst;
+        int dR = (d>>11)&0x1f;
+        int dG = (d>>5)&0x3f;
+        int dB = (d)&0x1f;
+        sR = (sR + f*dR)>>8;
+        sG = (sG + f*dG)>>8;
+        sB = (sB + f*dB)>>8;
+        *dst = uint16_t((sR<<11)|(sG<<5)|sB);
+    }
+};
+
+/* This is used to iterate over a 16-bit destination color buffer.
+ * Usage is:
+ *
+ *   dst_iterator16  di(context);
+ *   while (di.count--) {
+ *       <do stuff with dest pixel at di.dst>
+ *       di.dst++;
+ *   }
+ */
+struct dst_iterator16 {
+    explicit dst_iterator16(const context_t* c) {
+        const int x = c->iterators.xl;
+        const int width = c->iterators.xr - x;
+        const int32_t y = c->iterators.y;
+        const surface_t* cb = &(c->state.buffers.color);
+        count = width;
+        dst = reinterpret_cast<uint16_t*>(cb->data) + (x+(cb->stride*y));
+    }
+    int        count;
+    uint16_t*  dst;
+};
+
+
+static void scanline_t32cb16_clamp(context_t* c)
+{
+    dst_iterator16  di(c);
+
+    if (is_context_horizontal(c)) {
+        /* Special case for simple horizontal scaling */
+        horz_clamp_iterator32 ci(c);
+        while (di.count--) {
+            uint32_t s = ci.get_pixel32();
+            *di.dst++ = convertAbgr8888ToRgb565(s);
+        }
+    } else {
+        /* General case */
+        clamp_iterator ci(c);
+        while (di.count--) {
+            uint32_t s = ci.get_pixel32();
+            *di.dst++ = convertAbgr8888ToRgb565(s);
+        }
+    }
+}
+
+static void scanline_t32cb16_dither(context_t* c)
+{
+    horz_iterator32 si(c);
+    dst_iterator16  di(c);
+    ditherer        dither(c);
+
+    while (di.count--) {
+        uint32_t s = si.get_pixel32();
+        *di.dst++ = dither.abgr8888ToRgb565(s);
+    }
+}
+
+static void scanline_t32cb16_clamp_dither(context_t* c)
+{
+    dst_iterator16  di(c);
+    ditherer        dither(c);
+
+    if (is_context_horizontal(c)) {
+        /* Special case for simple horizontal scaling */
+        horz_clamp_iterator32 ci(c);
+        while (di.count--) {
+            uint32_t s = ci.get_pixel32();
+            *di.dst++ = dither.abgr8888ToRgb565(s);
+        }
+    } else {
+        /* General case */
+        clamp_iterator ci(c);
+        while (di.count--) {
+            uint32_t s = ci.get_pixel32();
+            *di.dst++ = dither.abgr8888ToRgb565(s);
+        }
+    }
+}
+
+static void scanline_t32cb16blend_dither(context_t* c)
+{
+    dst_iterator16 di(c);
+    ditherer       dither(c);
+    blender_32to16 bl(c);
+    horz_iterator32  hi(c);
+    while (di.count--) {
+        uint32_t s = hi.get_pixel32();
+        bl.write(s, di.dst, dither);
+        di.dst++;
+    }
+}
+
+static void scanline_t32cb16blend_clamp(context_t* c)
+{
+    dst_iterator16  di(c);
+    blender_32to16  bl(c);
+
+    if (is_context_horizontal(c)) {
+        horz_clamp_iterator32 ci(c);
+        while (di.count--) {
+            uint32_t s = ci.get_pixel32();
+            bl.write(s, di.dst);
+            di.dst++;
+        }
+    } else {
+        clamp_iterator ci(c);
+        while (di.count--) {
+            uint32_t s = ci.get_pixel32();
+            bl.write(s, di.dst);
+            di.dst++;
+        }
+    }
+}
+
+static void scanline_t32cb16blend_clamp_dither(context_t* c)
+{
+    dst_iterator16 di(c);
+    ditherer       dither(c);
+    blender_32to16 bl(c);
+
+    clamp_iterator ci(c);
+    while (di.count--) {
+        uint32_t s = ci.get_pixel32();
+        bl.write(s, di.dst, dither);
+        di.dst++;
+    }
+}
+
+void scanline_t32cb16blend_clamp_mod(context_t* c)
+{
+    dst_iterator16 di(c);
+    blender_32to16_modulate bl(c);
+
+    clamp_iterator ci(c);
+    while (di.count--) {
+        uint32_t s = ci.get_pixel32();
+        bl.write(s, di.dst);
+        di.dst++;
+    }
+}
+
+void scanline_t32cb16blend_clamp_mod_dither(context_t* c)
+{
+    dst_iterator16 di(c);
+    blender_32to16_modulate bl(c);
+    ditherer dither(c);
+
+    clamp_iterator ci(c);
+    while (di.count--) {
+        uint32_t s = ci.get_pixel32();
+        bl.write(s, di.dst, dither);
+        di.dst++;
+    }
+}
+
+/* Variant of scanline_t32cb16blend_clamp_mod with a xRGB texture */
+void scanline_x32cb16blend_clamp_mod(context_t* c)
+{
+    dst_iterator16 di(c);
+    blender_x32to16_modulate  bl(c);
+
+    clamp_iterator ci(c);
+    while (di.count--) {
+        uint32_t s = ci.get_pixel32();
+        bl.write(s, di.dst);
+        di.dst++;
+    }
+}
+
+void scanline_x32cb16blend_clamp_mod_dither(context_t* c)
+{
+    dst_iterator16 di(c);
+    blender_x32to16_modulate  bl(c);
+    ditherer dither(c);
+
+    clamp_iterator ci(c);
+    while (di.count--) {
+        uint32_t s = ci.get_pixel32();
+        bl.write(s, di.dst, dither);
+        di.dst++;
+    }
+}
+
+void scanline_t16cb16_clamp(context_t* c)
+{
+    dst_iterator16  di(c);
+
+    /* Special case for simple horizontal scaling */
+    if (is_context_horizontal(c)) {
+        horz_clamp_iterator16 ci(c);
+        while (di.count--) {
+            *di.dst++ = ci.get_pixel16();
+        }
+    } else {
+        clamp_iterator ci(c);
+        while (di.count--) {
+            *di.dst++ = ci.get_pixel16();
+        }
+    }
+}
+
+
+
+template <typename T, typename U>
+static inline __attribute__((const))
+T interpolate(int y, T v0, U dvdx, U dvdy) {
+    // interpolates in pixel's centers
+    // v = v0 + (y + 0.5) * dvdy + (0.5 * dvdx)
+    return (y * dvdy) + (v0 + ((dvdy + dvdx) >> 1));
+}
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#endif
+
+void init_y(context_t* c, int32_t ys)
+{
+    const uint32_t enables = c->state.enables;
+
+    // compute iterators...
+    iterators_t& ci = c->iterators;
+    
+    // sample in the center
+    ci.y = ys;
+
+    if (enables & (GGL_ENABLE_DEPTH_TEST|GGL_ENABLE_W|GGL_ENABLE_FOG)) {
+        ci.ydzdy = interpolate(ys, c->shade.z0, c->shade.dzdx, c->shade.dzdy);
+        ci.ydwdy = interpolate(ys, c->shade.w0, c->shade.dwdx, c->shade.dwdy);
+        ci.ydfdy = interpolate(ys, c->shade.f0, c->shade.dfdx, c->shade.dfdy);
+    }
+
+    if (ggl_unlikely(enables & GGL_ENABLE_SMOOTH)) {
+        ci.ydrdy = interpolate(ys, c->shade.r0, c->shade.drdx, c->shade.drdy);
+        ci.ydgdy = interpolate(ys, c->shade.g0, c->shade.dgdx, c->shade.dgdy);
+        ci.ydbdy = interpolate(ys, c->shade.b0, c->shade.dbdx, c->shade.dbdy);
+        ci.ydady = interpolate(ys, c->shade.a0, c->shade.dadx, c->shade.dady);
+        c->step_y = step_y__smooth;
+    } else {
+        ci.ydrdy = c->shade.r0;
+        ci.ydgdy = c->shade.g0;
+        ci.ydbdy = c->shade.b0;
+        ci.ydady = c->shade.a0;
+        // XXX: do only if needed, or make sure this is fast
+        c->packed = ggl_pack_color(c, c->state.buffers.color.format,
+                ci.ydrdy, ci.ydgdy, ci.ydbdy, ci.ydady);
+        c->packed8888 = ggl_pack_color(c, GGL_PIXEL_FORMAT_RGBA_8888, 
+                ci.ydrdy, ci.ydgdy, ci.ydbdy, ci.ydady);
+    }
+
+    // initialize the variables we need in the shader
+    generated_vars_t& gen = c->generated_vars;
+    gen.argb[GGLFormat::ALPHA].c  = ci.ydady;
+    gen.argb[GGLFormat::ALPHA].dx = c->shade.dadx;
+    gen.argb[GGLFormat::RED  ].c  = ci.ydrdy;
+    gen.argb[GGLFormat::RED  ].dx = c->shade.drdx;
+    gen.argb[GGLFormat::GREEN].c  = ci.ydgdy;
+    gen.argb[GGLFormat::GREEN].dx = c->shade.dgdx;
+    gen.argb[GGLFormat::BLUE ].c  = ci.ydbdy;
+    gen.argb[GGLFormat::BLUE ].dx = c->shade.dbdx;
+    gen.dzdx = c->shade.dzdx;
+    gen.f    = ci.ydfdy;
+    gen.dfdx = c->shade.dfdx;
+
+    if (enables & GGL_ENABLE_TMUS) {
+        for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; ++i) {
+            texture_t& t = c->state.texture[i];
+            if (!t.enable) continue;
+
+            texture_iterators_t& ti = t.iterators;
+            if (t.s_coord == GGL_ONE_TO_ONE && t.t_coord == GGL_ONE_TO_ONE) {
+                // we need to set all of these to 0 because in some cases
+                // step_y__generic() or step_y__tmu() will be used and
+                // therefore will update dtdy, however, in 1:1 mode
+                // this is always done by the scanline rasterizer.
+                ti.dsdx = ti.dsdy = ti.dtdx = ti.dtdy = 0;
+                ti.ydsdy = t.shade.is0;
+                ti.ydtdy = t.shade.it0;
+            } else {
+                const int adjustSWrap = ((t.s_wrap==GGL_CLAMP)?0:16);
+                const int adjustTWrap = ((t.t_wrap==GGL_CLAMP)?0:16);
+                ti.sscale = t.shade.sscale + adjustSWrap;
+                ti.tscale = t.shade.tscale + adjustTWrap;
+                if (!(enables & GGL_ENABLE_W)) {
+                    // S coordinate
+                    const int32_t sscale = ti.sscale;
+                    const int32_t sy = interpolate(ys,
+                            t.shade.is0, t.shade.idsdx, t.shade.idsdy);
+                    if (sscale>=0) {
+                        ti.ydsdy= sy            << sscale;
+                        ti.dsdx = t.shade.idsdx << sscale; 
+                        ti.dsdy = t.shade.idsdy << sscale;
+                    } else {
+                        ti.ydsdy= sy            >> -sscale;
+                        ti.dsdx = t.shade.idsdx >> -sscale; 
+                        ti.dsdy = t.shade.idsdy >> -sscale;
+                    }
+                    // T coordinate
+                    const int32_t tscale = ti.tscale;
+                    const int32_t ty = interpolate(ys,
+                            t.shade.it0, t.shade.idtdx, t.shade.idtdy);
+                    if (tscale>=0) {
+                        ti.ydtdy= ty            << tscale;
+                        ti.dtdx = t.shade.idtdx << tscale; 
+                        ti.dtdy = t.shade.idtdy << tscale;
+                    } else {
+                        ti.ydtdy= ty            >> -tscale;
+                        ti.dtdx = t.shade.idtdx >> -tscale; 
+                        ti.dtdy = t.shade.idtdy >> -tscale;
+                    }
+                }
+            }
+            // mirror for generated code...
+            generated_tex_vars_t& gen = c->generated_vars.texture[i];
+            gen.width   = t.surface.width;
+            gen.height  = t.surface.height;
+            gen.stride  = t.surface.stride;
+            gen.data    = uintptr_t(t.surface.data);
+            gen.dsdx = ti.dsdx;
+            gen.dtdx = ti.dtdx;
+        }
+    }
+
+    // choose the y-stepper
+    c->step_y = step_y__nop;
+    if (enables & GGL_ENABLE_FOG) {
+        c->step_y = step_y__generic;
+    } else if (enables & GGL_ENABLE_TMUS) {
+        if (enables & GGL_ENABLE_SMOOTH) {
+            c->step_y = step_y__generic;
+        } else if (enables & GGL_ENABLE_W) {
+            c->step_y = step_y__w;
+        } else {
+            c->step_y = step_y__tmu;
+        }
+    } else {
+        if (enables & GGL_ENABLE_SMOOTH) {
+            c->step_y = step_y__smooth;
+        }
+    }
+    
+    // choose the rectangle blitter
+    c->rect = rect_generic;
+    if ((c->step_y == step_y__nop) &&
+        (c->scanline == scanline_memcpy))
+    {
+        c->rect = rect_memcpy;
+    }
+}
+
+void init_y_packed(context_t* c, int32_t y0)
+{
+    uint8_t f = c->state.buffers.color.format;
+    c->packed = ggl_pack_color(c, f,
+            c->shade.r0, c->shade.g0, c->shade.b0, c->shade.a0);
+    c->packed8888 = ggl_pack_color(c, GGL_PIXEL_FORMAT_RGBA_8888,
+            c->shade.r0, c->shade.g0, c->shade.b0, c->shade.a0);
+    c->iterators.y = y0;
+    c->step_y = step_y__nop;
+    // choose the rectangle blitter
+    c->rect = rect_generic;
+    if (c->scanline == scanline_memcpy) {
+        c->rect = rect_memcpy;
+    }
+}
+
+void init_y_noop(context_t* c, int32_t y0)
+{
+    c->iterators.y = y0;
+    c->step_y = step_y__nop;
+    // choose the rectangle blitter
+    c->rect = rect_generic;
+    if (c->scanline == scanline_memcpy) {
+        c->rect = rect_memcpy;
+    }
+}
+
+void init_y_error(context_t* c, int32_t y0)
+{
+    // woooops, shoud never happen,
+    // fail gracefully (don't display anything)
+    init_y_noop(c, y0);
+    ALOGE("color-buffer has an invalid format!");
+}
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#endif
+
+void step_y__generic(context_t* c)
+{
+    const uint32_t enables = c->state.enables;
+
+    // iterate...
+    iterators_t& ci = c->iterators;
+    ci.y += 1;
+                
+    if (enables & GGL_ENABLE_SMOOTH) {
+        ci.ydrdy += c->shade.drdy;
+        ci.ydgdy += c->shade.dgdy;
+        ci.ydbdy += c->shade.dbdy;
+        ci.ydady += c->shade.dady;
+    }
+
+    const uint32_t mask =
+            GGL_ENABLE_DEPTH_TEST |
+            GGL_ENABLE_W |
+            GGL_ENABLE_FOG;
+    if (enables & mask) {
+        ci.ydzdy += c->shade.dzdy;
+        ci.ydwdy += c->shade.dwdy;
+        ci.ydfdy += c->shade.dfdy;
+    }
+
+    if ((enables & GGL_ENABLE_TMUS) && (!(enables & GGL_ENABLE_W))) {
+        for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; ++i) {
+            if (c->state.texture[i].enable) {
+                texture_iterators_t& ti = c->state.texture[i].iterators;
+                ti.ydsdy += ti.dsdy;
+                ti.ydtdy += ti.dtdy;
+            }
+        }
+    }
+}
+
+void step_y__nop(context_t* c)
+{
+    c->iterators.y += 1;
+    c->iterators.ydzdy += c->shade.dzdy;
+}
+
+void step_y__smooth(context_t* c)
+{
+    iterators_t& ci = c->iterators;
+    ci.y += 1;
+    ci.ydrdy += c->shade.drdy;
+    ci.ydgdy += c->shade.dgdy;
+    ci.ydbdy += c->shade.dbdy;
+    ci.ydady += c->shade.dady;
+    ci.ydzdy += c->shade.dzdy;
+}
+
+void step_y__w(context_t* c)
+{
+    iterators_t& ci = c->iterators;
+    ci.y += 1;
+    ci.ydzdy += c->shade.dzdy;
+    ci.ydwdy += c->shade.dwdy;
+}
+
+void step_y__tmu(context_t* c)
+{
+    iterators_t& ci = c->iterators;
+    ci.y += 1;
+    ci.ydzdy += c->shade.dzdy;
+    for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; ++i) {
+        if (c->state.texture[i].enable) {
+            texture_iterators_t& ti = c->state.texture[i].iterators;
+            ti.ydsdy += ti.dsdy;
+            ti.ydtdy += ti.dtdy;
+        }
+    }
+}
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#endif
+
+void scanline_perspective(context_t* c)
+{
+    struct {
+        union {
+            struct {
+                int32_t s, sq;
+                int32_t t, tq;
+            } sqtq;
+            struct {
+                int32_t v, q;
+            } st[2];
+        };
+    } tc[GGL_TEXTURE_UNIT_COUNT] __attribute__((aligned(16)));
+
+    // XXX: we should have a special case when dwdx = 0
+
+    // 32 pixels spans works okay. 16 is a lot better,
+    // but hey, it's a software renderer...
+    const uint32_t SPAN_BITS = 5; 
+    const uint32_t ys = c->iterators.y;
+    const uint32_t xs = c->iterators.xl;
+    const uint32_t x1 = c->iterators.xr;
+	const uint32_t xc = x1 - xs;
+    uint32_t remainder = xc & ((1<<SPAN_BITS)-1);
+    uint32_t numSpans = xc >> SPAN_BITS;
+
+    const iterators_t& ci = c->iterators;
+    int32_t w0 = (xs * c->shade.dwdx) + ci.ydwdy;
+    int32_t q0 = gglRecipQ(w0, 30);
+    const int iwscale = 32 - gglClz(q0);
+
+    const int32_t dwdx = c->shade.dwdx << SPAN_BITS;
+    int32_t xl = c->iterators.xl;
+
+    // We process s & t with a loop to reduce the code size
+    // (and i-cache pressure).
+
+    for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; ++i) {
+        const texture_t& tmu = c->state.texture[i];
+        if (!tmu.enable) continue;
+        int32_t s =   tmu.shade.is0 +
+                     (tmu.shade.idsdy * ys) + (tmu.shade.idsdx * xs) +
+                     ((tmu.shade.idsdx + tmu.shade.idsdy)>>1);
+        int32_t t =   tmu.shade.it0 +
+                     (tmu.shade.idtdy * ys) + (tmu.shade.idtdx * xs) +
+                     ((tmu.shade.idtdx + tmu.shade.idtdy)>>1);
+        tc[i].sqtq.s  = s;
+        tc[i].sqtq.t  = t;
+        tc[i].sqtq.sq = gglMulx(s, q0, iwscale);
+        tc[i].sqtq.tq = gglMulx(t, q0, iwscale);
+    }
+
+    int32_t span = 0;
+    do {
+        int32_t w1;
+        if (ggl_likely(numSpans)) {
+            w1 = w0 + dwdx;
+        } else {
+            if (remainder) {
+                // finish off the scanline...
+                span = remainder;
+                w1 = (c->shade.dwdx * span) + w0;
+            } else {
+                break;
+            }
+        }
+        int32_t q1 = gglRecipQ(w1, 30);
+        for (int i=0 ; i<GGL_TEXTURE_UNIT_COUNT ; ++i) {
+            texture_t& tmu = c->state.texture[i];
+            if (!tmu.enable) continue;
+            texture_iterators_t& ti = tmu.iterators;
+
+            for (int j=0 ; j<2 ; j++) {
+                int32_t v = tc[i].st[j].v;
+                if (span)   v += (tmu.shade.st[j].dx)*span;
+                else        v += (tmu.shade.st[j].dx)<<SPAN_BITS;
+                const int32_t v0 = tc[i].st[j].q;
+                const int32_t v1 = gglMulx(v, q1, iwscale);
+                int32_t dvdx = v1 - v0;
+                if (span)   dvdx /= span;
+                else        dvdx >>= SPAN_BITS;
+                tc[i].st[j].v = v;
+                tc[i].st[j].q = v1;
+
+                const int scale = ti.st[j].scale + (iwscale - 30);
+                if (scale >= 0) {
+                    ti.st[j].ydvdy = v0   << scale;
+                    ti.st[j].dvdx  = dvdx << scale;
+                } else {
+                    ti.st[j].ydvdy = v0   >> -scale;
+                    ti.st[j].dvdx  = dvdx >> -scale;
+                }
+            }
+            generated_tex_vars_t& gen = c->generated_vars.texture[i];
+            gen.dsdx = ti.st[0].dvdx;
+            gen.dtdx = ti.st[1].dvdx;
+        }
+        c->iterators.xl = xl;
+        c->iterators.xr = xl = xl + (span ? span : (1<<SPAN_BITS));
+        w0 = w1;
+        q0 = q1;
+        c->span(c);
+    } while(numSpans--);
+}
+
+void scanline_perspective_single(context_t* c)
+{
+    // 32 pixels spans works okay. 16 is a lot better,
+    // but hey, it's a software renderer...
+    const uint32_t SPAN_BITS = 5; 
+    const uint32_t ys = c->iterators.y;
+    const uint32_t xs = c->iterators.xl;
+    const uint32_t x1 = c->iterators.xr;
+	const uint32_t xc = x1 - xs;
+
+    const iterators_t& ci = c->iterators;
+    int32_t w = (xs * c->shade.dwdx) + ci.ydwdy;
+    int32_t iw = gglRecipQ(w, 30);
+    const int iwscale = 32 - gglClz(iw);
+
+    const int i = 31 - gglClz(c->state.enabled_tmu);
+    generated_tex_vars_t& gen = c->generated_vars.texture[i];
+    texture_t& tmu = c->state.texture[i];
+    texture_iterators_t& ti = tmu.iterators;
+    const int sscale = ti.sscale + (iwscale - 30);
+    const int tscale = ti.tscale + (iwscale - 30);
+    int32_t s =   tmu.shade.is0 +
+                 (tmu.shade.idsdy * ys) + (tmu.shade.idsdx * xs) +
+                 ((tmu.shade.idsdx + tmu.shade.idsdy)>>1);
+    int32_t t =   tmu.shade.it0 +
+                 (tmu.shade.idtdy * ys) + (tmu.shade.idtdx * xs) +
+                 ((tmu.shade.idtdx + tmu.shade.idtdy)>>1);
+    int32_t s0 = gglMulx(s, iw, iwscale);
+    int32_t t0 = gglMulx(t, iw, iwscale);
+    int32_t xl = c->iterators.xl;
+
+    int32_t sq, tq, dsdx, dtdx;
+    int32_t premainder = xc & ((1<<SPAN_BITS)-1);
+    uint32_t numSpans = xc >> SPAN_BITS;
+    if (c->shade.dwdx == 0) {
+        // XXX: we could choose to do this if the error is small enough
+        numSpans = 0;
+        premainder = xc;
+        goto no_perspective;
+    }
+
+    if (premainder) {
+        w += c->shade.dwdx   * premainder;
+        iw = gglRecipQ(w, 30);
+no_perspective:        
+        s += tmu.shade.idsdx * premainder;
+        t += tmu.shade.idtdx * premainder;
+        sq = gglMulx(s, iw, iwscale);
+        tq = gglMulx(t, iw, iwscale);
+        dsdx = (sq - s0) / premainder;
+        dtdx = (tq - t0) / premainder;
+        c->iterators.xl = xl;
+        c->iterators.xr = xl = xl + premainder;
+        goto finish;
+    }
+
+    while (numSpans--) {
+        w += c->shade.dwdx   << SPAN_BITS;
+        s += tmu.shade.idsdx << SPAN_BITS;
+        t += tmu.shade.idtdx << SPAN_BITS;
+        iw = gglRecipQ(w, 30);
+        sq = gglMulx(s, iw, iwscale);
+        tq = gglMulx(t, iw, iwscale);
+        dsdx = (sq - s0) >> SPAN_BITS;
+        dtdx = (tq - t0) >> SPAN_BITS;
+        c->iterators.xl = xl;
+        c->iterators.xr = xl = xl + (1<<SPAN_BITS);
+finish:
+        if (sscale >= 0) {
+            ti.ydsdy = s0   << sscale;
+            ti.dsdx  = dsdx << sscale;
+        } else {
+            ti.ydsdy = s0   >>-sscale;
+            ti.dsdx  = dsdx >>-sscale;
+        }
+        if (tscale >= 0) {
+            ti.ydtdy = t0   << tscale;
+            ti.dtdx  = dtdx << tscale;
+        } else {
+            ti.ydtdy = t0   >>-tscale;
+            ti.dtdx  = dtdx >>-tscale;
+        }
+        s0 = sq;
+        t0 = tq;
+        gen.dsdx = ti.dsdx;
+        gen.dtdx = ti.dtdx;
+        c->span(c);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+void scanline_col32cb16blend(context_t* c)
+{
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    union {
+        uint16_t* dst;
+        uint32_t* dst32;
+    };
+    dst = reinterpret_cast<uint16_t*>(cb->data) + (x+(cb->stride*y));
+
+#if ((ANDROID_CODEGEN >= ANDROID_CODEGEN_ASM) && defined(__arm__))
+#if defined(__ARM_HAVE_NEON) && BYTE_ORDER == LITTLE_ENDIAN
+    scanline_col32cb16blend_neon(dst, &(c->packed8888), ct);
+#else  // defined(__ARM_HAVE_NEON) && BYTE_ORDER == LITTLE_ENDIAN
+    scanline_col32cb16blend_arm(dst, GGL_RGBA_TO_HOST(c->packed8888), ct);
+#endif // defined(__ARM_HAVE_NEON) && BYTE_ORDER == LITTLE_ENDIAN
+#elif ((ANDROID_CODEGEN >= ANDROID_CODEGEN_ASM) && defined(__aarch64__))
+    scanline_col32cb16blend_arm64(dst, GGL_RGBA_TO_HOST(c->packed8888), ct);
+#elif ((ANDROID_CODEGEN >= ANDROID_CODEGEN_ASM) && (defined(__mips__) && defined(__LP64__)))
+    scanline_col32cb16blend_mips64(dst, GGL_RGBA_TO_HOST(c->packed8888), ct);
+#else
+    uint32_t s = GGL_RGBA_TO_HOST(c->packed8888);
+    int sA = (s>>24);
+    int f = 0x100 - (sA + (sA>>7));
+    while (ct--) {
+        uint16_t d = *dst;
+        int dR = (d>>11)&0x1f;
+        int dG = (d>>5)&0x3f;
+        int dB = (d)&0x1f;
+        int sR = (s >> (   3))&0x1F;
+        int sG = (s >> ( 8+2))&0x3F;
+        int sB = (s >> (16+3))&0x1F;
+        sR += (f*dR)>>8;
+        sG += (f*dG)>>8;
+        sB += (f*dB)>>8;
+        *dst++ = uint16_t((sR<<11)|(sG<<5)|sB);
+    }
+#endif
+
+}
+
+void scanline_t32cb16(context_t* c)
+{
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;    
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    union {
+        uint16_t* dst;
+        uint32_t* dst32;
+    };
+    dst = reinterpret_cast<uint16_t*>(cb->data) + (x+(cb->stride*y));
+
+    surface_t* tex = &(c->state.texture[0].surface);
+    const int32_t u = (c->state.texture[0].shade.is0>>16) + x;
+    const int32_t v = (c->state.texture[0].shade.it0>>16) + y;
+    uint32_t *src = reinterpret_cast<uint32_t*>(tex->data)+(u+(tex->stride*v));
+    uint32_t s, d;
+
+    if (ct==1 || uintptr_t(dst)&2) {
+last_one:
+        s = GGL_RGBA_TO_HOST( *src++ );
+        *dst++ = convertAbgr8888ToRgb565(s);
+        ct--;
+    }
+
+    while (ct >= 2) {
+#if BYTE_ORDER == BIG_ENDIAN
+        s = GGL_RGBA_TO_HOST( *src++ );
+        d = convertAbgr8888ToRgb565_hi16(s);
+
+        s = GGL_RGBA_TO_HOST( *src++ );
+        d |= convertAbgr8888ToRgb565(s);
+#else
+        s = GGL_RGBA_TO_HOST( *src++ );
+        d = convertAbgr8888ToRgb565(s);
+
+        s = GGL_RGBA_TO_HOST( *src++ );
+        d |= convertAbgr8888ToRgb565(s) << 16;
+#endif
+        *dst32++ = d;
+        ct -= 2;
+    }
+    
+    if (ct > 0) {
+        goto last_one;
+    }
+}
+
+void scanline_t32cb16blend(context_t* c)
+{
+#if ((ANDROID_CODEGEN >= ANDROID_CODEGEN_ASM) && (defined(__arm__) || defined(__aarch64__) || \
+    (defined(__mips__) && ((!defined(__LP64__) && __mips_isa_rev < 6) || defined(__LP64__)))))
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    uint16_t* dst = reinterpret_cast<uint16_t*>(cb->data) + (x+(cb->stride*y));
+
+    surface_t* tex = &(c->state.texture[0].surface);
+    const int32_t u = (c->state.texture[0].shade.is0>>16) + x;
+    const int32_t v = (c->state.texture[0].shade.it0>>16) + y;
+    uint32_t *src = reinterpret_cast<uint32_t*>(tex->data)+(u+(tex->stride*v));
+
+#ifdef __arm__
+    scanline_t32cb16blend_arm(dst, src, ct);
+#elif defined(__aarch64__)
+    scanline_t32cb16blend_arm64(dst, src, ct);
+#elif defined(__mips__) && !defined(__LP64__) && __mips_isa_rev < 6
+    scanline_t32cb16blend_mips(dst, src, ct);
+#elif defined(__mips__) && defined(__LP64__)
+    scanline_t32cb16blend_mips64(dst, src, ct);
+#endif
+#else
+    dst_iterator16  di(c);
+    horz_iterator32  hi(c);
+    blender_32to16  bl(c);
+    while (di.count--) {
+        uint32_t s = hi.get_pixel32();
+        bl.write(s, di.dst);
+        di.dst++;
+    }
+#endif
+}
+
+void scanline_t32cb16blend_srca(context_t* c)
+{
+    dst_iterator16  di(c);
+    horz_iterator32  hi(c);
+    blender_32to16_srcA  blender(c);
+
+    while (di.count--) {
+        uint32_t s = hi.get_pixel32();
+        blender.write(s,di.dst);
+        di.dst++;
+    }
+}
+
+void scanline_t16cb16blend_clamp_mod(context_t* c)
+{
+    const int a = c->iterators.ydady >> (GGL_COLOR_BITS-8);
+    if (a == 0) {
+        return;
+    }
+
+    if (a == 255) {
+        scanline_t16cb16_clamp(c);
+        return;
+    }
+
+    dst_iterator16  di(c);
+    blender_16to16_modulate  blender(c);
+    clamp_iterator  ci(c);
+
+    while (di.count--) {
+        uint16_t s = ci.get_pixel16();
+        blender.write(s, di.dst);
+        di.dst++;
+    }
+}
+
+void scanline_memcpy(context_t* c)
+{
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    const GGLFormat* fp = &(c->formats[cb->format]);
+    uint8_t* dst = reinterpret_cast<uint8_t*>(cb->data) +
+                            (x + (cb->stride * y)) * fp->size;
+
+    surface_t* tex = &(c->state.texture[0].surface);
+    const int32_t u = (c->state.texture[0].shade.is0>>16) + x;
+    const int32_t v = (c->state.texture[0].shade.it0>>16) + y;
+    uint8_t *src = reinterpret_cast<uint8_t*>(tex->data) +
+                            (u + (tex->stride * v)) * fp->size;
+
+    const size_t size = ct * fp->size;
+    memcpy(dst, src, size);
+}
+
+void scanline_memset8(context_t* c)
+{
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    uint8_t* dst = reinterpret_cast<uint8_t*>(cb->data) + (x+(cb->stride*y));
+    uint32_t packed = c->packed;
+    memset(dst, packed, ct);
+}
+
+void scanline_memset16(context_t* c)
+{
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    uint16_t* dst = reinterpret_cast<uint16_t*>(cb->data) + (x+(cb->stride*y));
+    uint32_t packed = c->packed;
+    android_memset16(dst, packed, ct*2);
+}
+
+void scanline_memset32(context_t* c)
+{
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    uint32_t* dst = reinterpret_cast<uint32_t*>(cb->data) + (x+(cb->stride*y));
+    uint32_t packed = GGL_HOST_TO_RGBA(c->packed);
+    android_memset32(dst, packed, ct*4);
+}
+
+void scanline_clear(context_t* c)
+{
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    const GGLFormat* fp = &(c->formats[cb->format]);
+    uint8_t* dst = reinterpret_cast<uint8_t*>(cb->data) +
+                            (x + (cb->stride * y)) * fp->size;
+    const size_t size = ct * fp->size;
+    memset(dst, 0, size);
+}
+
+void scanline_set(context_t* c)
+{
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    const GGLFormat* fp = &(c->formats[cb->format]);
+    uint8_t* dst = reinterpret_cast<uint8_t*>(cb->data) +
+                            (x + (cb->stride * y)) * fp->size;
+    const size_t size = ct * fp->size;
+    memset(dst, 0xFF, size);
+}
+
+void scanline_noop(context_t* /*c*/)
+{
+}
+
+void rect_generic(context_t* c, size_t yc)
+{
+    do {
+        c->scanline(c);
+        c->step_y(c);
+    } while (--yc);
+}
+
+void rect_memcpy(context_t* c, size_t yc)
+{
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    const GGLFormat* fp = &(c->formats[cb->format]);
+    uint8_t* dst = reinterpret_cast<uint8_t*>(cb->data) +
+                            (x + (cb->stride * y)) * fp->size;
+
+    surface_t* tex = &(c->state.texture[0].surface);
+    const int32_t u = (c->state.texture[0].shade.is0>>16) + x;
+    const int32_t v = (c->state.texture[0].shade.it0>>16) + y;
+    uint8_t *src = reinterpret_cast<uint8_t*>(tex->data) +
+                            (u + (tex->stride * v)) * fp->size;
+
+    if (cb->stride == tex->stride && ct == size_t(cb->stride)) {
+        memcpy(dst, src, ct * fp->size * yc);
+    } else {
+        const size_t size = ct * fp->size;
+        const size_t dbpr = cb->stride  * fp->size;
+        const size_t sbpr = tex->stride * fp->size;
+        do {
+            memcpy(dst, src, size);
+            dst += dbpr;
+            src += sbpr;        
+        } while (--yc);
+    }
+}
+// ----------------------------------------------------------------------------
+}; // namespace android
+
diff --git a/libpixelflinger/scanline.h b/libpixelflinger/scanline.h
new file mode 100644
index 0000000..b6f4d37
--- /dev/null
+++ b/libpixelflinger/scanline.h
@@ -0,0 +1,32 @@
+/* libs/pixelflinger/scanline.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#ifndef ANDROID_SCANLINE_H
+#define ANDROID_SCANLINE_H
+
+#include <private/pixelflinger/ggl_context.h>
+
+namespace android {
+
+void ggl_init_scanline(context_t* c);
+void ggl_uninit_scanline(context_t* c);
+void ggl_pick_scanline(context_t* c);
+
+}; // namespace android
+
+#endif
diff --git a/libpixelflinger/t32cb16blend.S b/libpixelflinger/t32cb16blend.S
new file mode 100644
index 0000000..5e4995a
--- /dev/null
+++ b/libpixelflinger/t32cb16blend.S
@@ -0,0 +1,203 @@
+/* libs/pixelflinger/t32cb16blend.S
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+	.text
+	.syntax unified
+	.balign 4
+	
+	.global scanline_t32cb16blend_arm
+
+
+/*
+ * .macro pixel
+ *
+ * \DREG is a 32-bit register containing *two* original destination RGB565 
+ *       pixels, with the even one in the low-16 bits, and the odd one in the
+ *       high 16 bits.
+ *
+ * \SRC is a 32-bit 0xAABBGGRR pixel value, with pre-multiplied colors.
+ *
+ * \FB is a target register that will contain the blended pixel values.
+ *
+ * \ODD is either 0 or 1 and indicates if we're blending the lower or 
+ *      upper 16-bit pixels in DREG into FB
+ *
+ *
+ * clobbered: r6, r7, lr
+ *
+ */
+
+.macro pixel,   DREG, SRC, FB, ODD
+
+    // SRC = 0xAABBGGRR
+    mov     r7, \SRC, lsr #24           // sA
+    add     r7, r7, r7, lsr #7          // sA + (sA >> 7)
+    rsb     r7, r7, #0x100              // sA = 0x100 - (sA+(sA>>7))
+
+1:
+
+.if \ODD
+
+    // red
+    mov     lr, \DREG, lsr #(16 + 11)
+    smulbb  lr, r7, lr
+    mov     r6, \SRC, lsr #3
+    and     r6, r6, #0x1F
+    add     lr, r6, lr, lsr #8
+    cmp     lr, #0x1F
+    orrhs   \FB, \FB, #(0x1F<<(16 + 11))
+    orrlo   \FB, \FB, lr, lsl #(16 + 11)
+
+        // green
+        and     r6, \DREG, #(0x3F<<(16 + 5))
+        smulbt  r6, r7, r6
+        mov     lr, \SRC, lsr #(8+2)
+        and     lr, lr, #0x3F
+        add     r6, lr, r6, lsr #(5+8)
+        cmp     r6, #0x3F
+        orrhs   \FB, \FB, #(0x3F<<(16 + 5))
+        orrlo   \FB, \FB, r6, lsl #(16 + 5)
+
+            // blue
+            and     lr, \DREG, #(0x1F << 16)
+            smulbt  lr, r7, lr
+            mov     r6, \SRC, lsr #(8+8+3)
+            and     r6, r6, #0x1F
+            add     lr, r6, lr, lsr #8
+            cmp     lr, #0x1F
+            orrhs   \FB, \FB, #(0x1F << 16)
+            orrlo   \FB, \FB, lr, lsl #16
+
+.else
+
+    // red
+    mov     lr, \DREG, lsr #11
+    and     lr, lr, #0x1F
+    smulbb  lr, r7, lr
+    mov     r6, \SRC, lsr #3
+    and     r6, r6, #0x1F
+    add     lr, r6, lr, lsr #8
+    cmp     lr, #0x1F
+    movhs   \FB, #(0x1F<<11)
+    movlo   \FB, lr, lsl #11
+
+
+        // green
+        and     r6, \DREG, #(0x3F<<5)
+        smulbb  r6, r7, r6
+        mov     lr, \SRC, lsr #(8+2)
+        and     lr, lr, #0x3F
+        add     r6, lr, r6, lsr #(5+8)
+        cmp     r6, #0x3F
+        orrhs   \FB, \FB, #(0x3F<<5)
+        orrlo   \FB, \FB, r6, lsl #5
+
+            // blue
+            and     lr, \DREG, #0x1F
+            smulbb  lr, r7, lr
+            mov     r6, \SRC, lsr #(8+8+3)
+            and     r6, r6, #0x1F
+            add     lr, r6, lr, lsr #8
+            cmp     lr, #0x1F
+            orrhs   \FB, \FB, #0x1F
+            orrlo   \FB, \FB, lr
+
+.endif
+
+    .endm
+    
+
+// r0:  dst ptr
+// r1:  src ptr
+// r2:  count
+// r3:  d
+// r4:  s0
+// r5:  s1
+// r6:  pixel
+// r7:  pixel
+// r8:  free
+// r9:  free
+// r10: free
+// r11: free
+// r12: scratch
+// r14: pixel
+
+scanline_t32cb16blend_arm:
+    stmfd	sp!, {r4-r7, lr}
+
+    pld     [r0]
+    pld     [r1]
+
+    // align DST to 32 bits
+    tst     r0, #0x3
+    beq     aligned
+    subs    r2, r2, #1
+    ldmfdlo sp!, {r4-r7, lr}        // return
+    bxlo    lr
+
+last:
+    ldr     r4, [r1], #4
+    ldrh    r3, [r0]
+    pixel   r3, r4, r12, 0
+    strh    r12, [r0], #2
+
+aligned:
+    subs    r2, r2, #2
+    blo     9f
+
+    // The main loop is unrolled twice and processes 4 pixels
+8:  ldmia   r1!, {r4, r5}
+    // stream the source
+    pld     [r1, #32]
+    add     r0, r0, #4
+    // it's all zero, skip this pixel
+    orrs    r3, r4, r5
+    beq     7f
+    
+    // load the destination
+    ldr     r3, [r0, #-4]
+    // stream the destination
+    pld     [r0, #32]
+    pixel   r3, r4, r12, 0
+    pixel   r3, r5, r12, 1
+    // effectively, we're getting write-combining by virtue of the
+    // cpu's write-back cache.
+    str     r12, [r0, #-4]
+
+    // 2nd iterration of the loop, don't stream anything
+    subs    r2, r2, #2
+    movlt   r4, r5
+    blt     9f
+    ldmia   r1!, {r4, r5}
+    add     r0, r0, #4
+    orrs    r3, r4, r5
+    beq     7f
+    ldr     r3, [r0, #-4]
+    pixel   r3, r4, r12, 0
+    pixel   r3, r5, r12, 16
+    str     r12, [r0, #-4]
+
+    
+7:  subs    r2, r2, #2
+    bhs     8b
+    mov     r4, r5
+
+9:  adds    r2, r2, #1
+    ldmfdlo sp!, {r4-r7, lr}        // return
+    bxlo    lr
+    b       last
diff --git a/libpixelflinger/trap.cpp b/libpixelflinger/trap.cpp
new file mode 100644
index 0000000..06ad237
--- /dev/null
+++ b/libpixelflinger/trap.cpp
@@ -0,0 +1,1173 @@
+/* libs/pixelflinger/trap.cpp
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "pixelflinger-trap"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <cutils/memory.h>
+#include <log/log.h>
+
+#include "trap.h"
+#include "picker.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+// enable to see triangles edges
+#define DEBUG_TRANGLES  0
+
+// ----------------------------------------------------------------------------
+
+static void pointx_validate(void *con, const GGLcoord* c, GGLcoord r);
+static void pointx(void *con, const GGLcoord* c, GGLcoord r);
+static void aa_pointx(void *con, const GGLcoord* c, GGLcoord r);
+static void aa_nice_pointx(void *con, const GGLcoord* c, GGLcoord r);
+
+static void linex_validate(void *con, const GGLcoord* v0, const GGLcoord* v1, GGLcoord w);
+static void linex(void *con, const GGLcoord* v0, const GGLcoord* v1, GGLcoord w);
+static void aa_linex(void *con, const GGLcoord* v0, const GGLcoord* v1, GGLcoord w);
+
+static void recti_validate(void* c, GGLint l, GGLint t, GGLint r, GGLint b); 
+static void recti(void* c, GGLint l, GGLint t, GGLint r, GGLint b); 
+
+static void trianglex_validate(void*,
+        const GGLcoord*, const GGLcoord*, const GGLcoord*);
+static void trianglex_small(void*,
+        const GGLcoord*, const GGLcoord*, const GGLcoord*);
+static void trianglex_big(void*,
+        const GGLcoord*, const GGLcoord*, const GGLcoord*);
+static void aa_trianglex(void*,
+        const GGLcoord*, const GGLcoord*, const GGLcoord*);
+static void trianglex_debug(void* con,
+        const GGLcoord*, const GGLcoord*, const GGLcoord*);
+
+static void aapolyx(void* con,
+        const GGLcoord* pts, int count);
+
+static inline int min(int a, int b) CONST;
+static inline int max(int a, int b) CONST;
+static inline int min(int a, int b, int c) CONST;
+static inline int max(int a, int b, int c) CONST;
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark Tools
+#endif
+
+inline int min(int a, int b) {
+    return a<b ? a : b;
+}
+inline int max(int a, int b) {
+    return a<b ? b : a;
+}
+inline int min(int a, int b, int c) {
+    return min(a,min(b,c));
+}
+inline int max(int a, int b, int c) {
+    return max(a,max(b,c));
+}
+
+template <typename T>
+static inline void swap(T& a, T& b) {
+    T t(a);
+    a = b;
+    b = t;
+}
+
+static void
+triangle_dump_points( const GGLcoord*  v0,
+                      const GGLcoord*  v1,
+                      const GGLcoord*  v2 )
+{
+    float tri = 1.0f / TRI_ONE;
+    ALOGD("  P0=(%.3f, %.3f)  [%08x, %08x]\n"
+          "  P1=(%.3f, %.3f)  [%08x, %08x]\n"
+          "  P2=(%.3f, %.3f)  [%08x, %08x]\n",
+          v0[0]*tri, v0[1]*tri, v0[0], v0[1],
+          v1[0]*tri, v1[1]*tri, v1[0], v1[1],
+          v2[0]*tri, v2[1]*tri, v2[0], v2[1] );
+}
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark Misc
+#endif
+
+void ggl_init_trap(context_t* c)
+{
+    ggl_state_changed(c, GGL_PIXEL_PIPELINE_STATE|GGL_TMU_STATE|GGL_CB_STATE);
+}
+
+void ggl_state_changed(context_t* c, int flags)
+{
+    if (ggl_likely(!c->dirty)) {
+        c->procs.pointx     = pointx_validate;
+        c->procs.linex      = linex_validate;
+        c->procs.recti      = recti_validate;
+        c->procs.trianglex  = trianglex_validate;
+    }
+    c->dirty |= uint32_t(flags);
+}
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark Point
+#endif
+
+void pointx_validate(void *con, const GGLcoord* v, GGLcoord rad)
+{
+    GGL_CONTEXT(c, con);
+    ggl_pick(c);
+    if (c->state.needs.p & GGL_NEED_MASK(P_AA)) {
+        if (c->state.enables & GGL_ENABLE_POINT_AA_NICE) {
+            c->procs.pointx = aa_nice_pointx;
+        } else {
+            c->procs.pointx = aa_pointx;
+        }
+    } else {
+        c->procs.pointx = pointx;
+    }
+    c->procs.pointx(con, v, rad);
+}
+
+void pointx(void *con, const GGLcoord* v, GGLcoord rad)
+{
+    GGL_CONTEXT(c, con);
+    GGLcoord halfSize = TRI_ROUND(rad) >> 1;
+    if (halfSize == 0)
+        halfSize = TRI_HALF;
+    GGLcoord xc = v[0]; 
+    GGLcoord yc = v[1];
+    if (halfSize & TRI_HALF) { // size odd
+        xc = TRI_FLOOR(xc) + TRI_HALF;
+        yc = TRI_FLOOR(yc) + TRI_HALF;
+    } else { // size even
+        xc = TRI_ROUND(xc);
+        yc = TRI_ROUND(yc);
+    }
+    GGLint l = (xc - halfSize) >> TRI_FRACTION_BITS;
+    GGLint t = (yc - halfSize) >> TRI_FRACTION_BITS;
+    GGLint r = (xc + halfSize) >> TRI_FRACTION_BITS;
+    GGLint b = (yc + halfSize) >> TRI_FRACTION_BITS;
+    recti(c, l, t, r, b);
+}
+
+// This way of computing the coverage factor, is more accurate and gives
+// better results for small circles, but it is also a lot slower.
+// Here we use super-sampling.
+static int32_t coverageNice(GGLcoord x, GGLcoord y, 
+        GGLcoord rmin, GGLcoord rmax, GGLcoord rr)
+{
+    const GGLcoord d2 = x*x + y*y;
+    if (d2 >= rmax) return 0;
+    if (d2 < rmin)  return 0x7FFF;
+
+    const int kSamples              =  4;
+    const int kInc                  =  4;    // 1/4 = 0.25
+    const int kCoverageUnit         =  1;    // 1/(4^2) = 0.0625
+    const GGLcoord kCoordOffset     = -6;    // -0.375
+
+    int hits = 0;
+    int x_sample = x + kCoordOffset;
+    for (int i=0 ; i<kSamples ; i++, x_sample += kInc) {
+        const int xval = rr - (x_sample * x_sample);
+        int y_sample = y + kCoordOffset;
+        for (int j=0 ; j<kSamples ; j++, y_sample += kInc) {
+            if (xval - (y_sample * y_sample) > 0)
+                hits += kCoverageUnit;
+        }
+    }
+    return min(0x7FFF, hits << (15 - kSamples));
+}
+
+
+void aa_nice_pointx(void *con, const GGLcoord* v, GGLcoord size)
+{
+    GGL_CONTEXT(c, con);
+
+    GGLcoord rad = ((size + 1)>>1);
+    GGLint l = (v[0] - rad) >> TRI_FRACTION_BITS;
+    GGLint t = (v[1] - rad) >> TRI_FRACTION_BITS;
+    GGLint r = (v[0] + rad + (TRI_ONE-1)) >> TRI_FRACTION_BITS;
+    GGLint b = (v[1] + rad + (TRI_ONE-1)) >> TRI_FRACTION_BITS;
+    GGLcoord xstart = TRI_FROM_INT(l) - v[0] + TRI_HALF; 
+    GGLcoord ystart = TRI_FROM_INT(t) - v[1] + TRI_HALF; 
+
+    // scissor...
+    if (l < GGLint(c->state.scissor.left)) {
+        xstart += TRI_FROM_INT(c->state.scissor.left-l);
+        l = GGLint(c->state.scissor.left);
+    }
+    if (t < GGLint(c->state.scissor.top)) {
+        ystart += TRI_FROM_INT(c->state.scissor.top-t);
+        t = GGLint(c->state.scissor.top);
+    }
+    if (r > GGLint(c->state.scissor.right)) {
+        r = GGLint(c->state.scissor.right);
+    }
+    if (b > GGLint(c->state.scissor.bottom)) {
+        b = GGLint(c->state.scissor.bottom);
+    }
+
+    int xc = r - l;
+    int yc = b - t;
+    if (xc>0 && yc>0) {
+        int16_t* covPtr = c->state.buffers.coverage;
+        const int32_t sqr2Over2 = 0xC; // rounded up
+        GGLcoord rr = rad*rad;
+        GGLcoord rmin = (rad - sqr2Over2)*(rad - sqr2Over2);
+        GGLcoord rmax = (rad + sqr2Over2)*(rad + sqr2Over2);
+        GGLcoord y = ystart;
+        c->iterators.xl = l;
+        c->iterators.xr = r;
+        c->init_y(c, t);
+        do {
+            // compute coverage factors for each pixel
+            GGLcoord x = xstart;
+            for (int i=l ; i<r ; i++) {
+                covPtr[i] = coverageNice(x, y, rmin, rmax, rr);
+                x += TRI_ONE;
+            }
+            y += TRI_ONE;
+            c->scanline(c);
+            c->step_y(c);
+        } while (--yc);
+    }
+}
+
+// This is a cheap way of computing the coverage factor for a circle.
+// We just lerp between the circles of radii r-sqrt(2)/2 and r+sqrt(2)/2
+static inline int32_t coverageFast(GGLcoord x, GGLcoord y,
+        GGLcoord rmin, GGLcoord rmax, GGLcoord scale)
+{
+    const GGLcoord d2 = x*x + y*y;
+    if (d2 >= rmax) return 0;
+    if (d2 < rmin)  return 0x7FFF;
+    return 0x7FFF - (d2-rmin)*scale;
+}
+
+void aa_pointx(void *con, const GGLcoord* v, GGLcoord size)
+{
+    GGL_CONTEXT(c, con);
+
+    GGLcoord rad = ((size + 1)>>1);
+    GGLint l = (v[0] - rad) >> TRI_FRACTION_BITS;
+    GGLint t = (v[1] - rad) >> TRI_FRACTION_BITS;
+    GGLint r = (v[0] + rad + (TRI_ONE-1)) >> TRI_FRACTION_BITS;
+    GGLint b = (v[1] + rad + (TRI_ONE-1)) >> TRI_FRACTION_BITS;
+    GGLcoord xstart = TRI_FROM_INT(l) - v[0] + TRI_HALF; 
+    GGLcoord ystart = TRI_FROM_INT(t) - v[1] + TRI_HALF; 
+
+    // scissor...
+    if (l < GGLint(c->state.scissor.left)) {
+        xstart += TRI_FROM_INT(c->state.scissor.left-l);
+        l = GGLint(c->state.scissor.left);
+    }
+    if (t < GGLint(c->state.scissor.top)) {
+        ystart += TRI_FROM_INT(c->state.scissor.top-t);
+        t = GGLint(c->state.scissor.top);
+    }
+    if (r > GGLint(c->state.scissor.right)) {
+        r = GGLint(c->state.scissor.right);
+    }
+    if (b > GGLint(c->state.scissor.bottom)) {
+        b = GGLint(c->state.scissor.bottom);
+    }
+
+    int xc = r - l;
+    int yc = b - t;
+    if (xc>0 && yc>0) {
+        int16_t* covPtr = c->state.buffers.coverage;
+        rad <<= 4;
+        const int32_t sqr2Over2 = 0xB5;    // fixed-point 24.8
+        GGLcoord rmin = rad - sqr2Over2;
+        GGLcoord rmax = rad + sqr2Over2;
+        GGLcoord scale;
+        rmin *= rmin;
+        rmax *= rmax;
+        scale = 0x800000 / (rmax - rmin);
+        rmin >>= 8;
+        rmax >>= 8;
+
+        GGLcoord y = ystart;
+        c->iterators.xl = l;
+        c->iterators.xr = r;
+        c->init_y(c, t);
+
+        do {
+            // compute coverage factors for each pixel
+            GGLcoord x = xstart;
+            for (int i=l ; i<r ; i++) {
+                covPtr[i] = coverageFast(x, y, rmin, rmax, scale);
+                x += TRI_ONE;
+            }
+            y += TRI_ONE;
+            c->scanline(c);
+            c->step_y(c);
+        } while (--yc);
+    }
+}
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark Line
+#endif
+
+void linex_validate(void *con, const GGLcoord* v0, const GGLcoord* v1, GGLcoord w)
+{
+    GGL_CONTEXT(c, con);
+    ggl_pick(c);
+    if (c->state.needs.p & GGL_NEED_MASK(P_AA)) {
+        c->procs.linex = aa_linex;
+    } else {
+        c->procs.linex = linex;
+    }
+    c->procs.linex(con, v0, v1, w);
+}
+
+static void linex(void *con, const GGLcoord* v0, const GGLcoord* v1, GGLcoord width)
+{
+    GGLcoord v[4][2];
+    v[0][0] = v0[0];    v[0][1] = v0[1];
+    v[1][0] = v1[0];    v[1][1] = v1[1];
+    v0 = v[0];
+    v1 = v[1];
+    const GGLcoord dx = abs(v0[0] - v1[0]);
+    const GGLcoord dy = abs(v0[1] - v1[1]);
+    GGLcoord nx, ny;
+    nx = ny = 0;
+
+    GGLcoord halfWidth = TRI_ROUND(width) >> 1;
+    if (halfWidth == 0)
+        halfWidth = TRI_HALF;
+
+    ((dx > dy) ? ny : nx) = halfWidth;
+    v[2][0] = v1[0];    v[2][1] = v1[1];
+    v[3][0] = v0[0];    v[3][1] = v0[1];
+    v[0][0] += nx;      v[0][1] += ny;
+    v[1][0] += nx;      v[1][1] += ny;
+    v[2][0] -= nx;      v[2][1] -= ny;
+    v[3][0] -= nx;      v[3][1] -= ny;
+    trianglex_big(con, v[0], v[1], v[2]);
+    trianglex_big(con, v[0], v[2], v[3]);
+}
+
+static void aa_linex(void *con, const GGLcoord* v0, const GGLcoord* v1, GGLcoord width)
+{
+    GGLcoord v[4][2];
+    v[0][0] = v0[0];    v[0][1] = v0[1];
+    v[1][0] = v1[0];    v[1][1] = v1[1];
+    v0 = v[0];
+    v1 = v[1];
+    
+    const GGLcoord dx = v0[0] - v1[0];
+    const GGLcoord dy = v0[1] - v1[1];
+    GGLcoord nx = -dy;
+    GGLcoord ny =  dx;
+
+    // generally, this will be well below 1.0
+    const GGLfixed norm = gglMulx(width, gglSqrtRecipx(nx*nx+ny*ny), 4);
+    nx = gglMulx(nx, norm, 21);
+    ny = gglMulx(ny, norm, 21);
+    
+    v[2][0] = v1[0];    v[2][1] = v1[1];
+    v[3][0] = v0[0];    v[3][1] = v0[1];
+    v[0][0] += nx;      v[0][1] += ny;
+    v[1][0] += nx;      v[1][1] += ny;
+    v[2][0] -= nx;      v[2][1] -= ny;
+    v[3][0] -= nx;      v[3][1] -= ny;
+    aapolyx(con, v[0], 4);        
+}
+
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark Rect
+#endif
+
+void recti_validate(void *con, GGLint l, GGLint t, GGLint r, GGLint b)
+{
+    GGL_CONTEXT(c, con);
+    ggl_pick(c);
+    c->procs.recti = recti;
+    c->procs.recti(con, l, t, r, b);
+}
+
+void recti(void* con, GGLint l, GGLint t, GGLint r, GGLint b)
+{
+    GGL_CONTEXT(c, con);
+
+    // scissor...
+    if (l < GGLint(c->state.scissor.left))
+        l = GGLint(c->state.scissor.left);
+    if (t < GGLint(c->state.scissor.top))
+        t = GGLint(c->state.scissor.top);
+    if (r > GGLint(c->state.scissor.right))
+        r = GGLint(c->state.scissor.right);
+    if (b > GGLint(c->state.scissor.bottom))
+        b = GGLint(c->state.scissor.bottom);
+
+    int xc = r - l;
+    int yc = b - t;
+    if (xc>0 && yc>0) {
+        c->iterators.xl = l;
+        c->iterators.xr = r;
+        c->init_y(c, t);
+        c->rect(c, yc);
+    }
+}
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark Triangle / Debugging
+#endif
+
+static void scanline_set(context_t* c)
+{
+    int32_t x = c->iterators.xl;
+    size_t ct = c->iterators.xr - x;
+    int32_t y = c->iterators.y;
+    surface_t* cb = &(c->state.buffers.color);
+    const GGLFormat* fp = &(c->formats[cb->format]);
+    uint8_t* dst = reinterpret_cast<uint8_t*>(cb->data) +
+                            (x + (cb->stride * y)) * fp->size;
+    const size_t size = ct * fp->size;
+    memset(dst, 0xFF, size);
+}
+
+static void trianglex_debug(void* con,
+        const GGLcoord* v0, const GGLcoord* v1, const GGLcoord* v2)
+{
+    GGL_CONTEXT(c, con);
+    if (c->state.needs.p & GGL_NEED_MASK(P_AA)) {
+        aa_trianglex(con,v0,v1,v2);
+    } else {
+        trianglex_big(con,v0,v1,v2);
+    }
+	void (*save_scanline)(context_t*)  = c->scanline;
+    c->scanline = scanline_set;
+    linex(con, v0, v1, TRI_ONE);
+    linex(con, v1, v2, TRI_ONE);
+    linex(con, v2, v0, TRI_ONE);
+    c->scanline = save_scanline;
+}
+
+static void trianglex_xor(void* con,
+        const GGLcoord* v0, const GGLcoord* v1, const GGLcoord* v2)
+{
+    trianglex_big(con,v0,v1,v2);
+    trianglex_small(con,v0,v1,v2);
+}
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#pragma mark Triangle
+#endif
+
+void trianglex_validate(void *con,
+        const GGLcoord* v0, const GGLcoord* v1, const GGLcoord* v2)
+{
+    GGL_CONTEXT(c, con);
+    ggl_pick(c);
+    if (c->state.needs.p & GGL_NEED_MASK(P_AA)) {
+        c->procs.trianglex = DEBUG_TRANGLES ? trianglex_debug : aa_trianglex;
+    } else {
+        c->procs.trianglex = DEBUG_TRANGLES ? trianglex_debug : trianglex_big;
+    }
+    c->procs.trianglex(con, v0, v1, v2);
+}
+
+// ----------------------------------------------------------------------------
+
+void trianglex_small(void* con,
+        const GGLcoord* v0, const GGLcoord* v1, const GGLcoord* v2)
+{
+    GGL_CONTEXT(c, con);
+
+    // vertices are in 28.4 fixed point, which allows
+    // us to use 32 bits multiplies below.
+    int32_t x0 = v0[0];
+    int32_t y0 = v0[1];
+    int32_t x1 = v1[0];
+    int32_t y1 = v1[1];
+    int32_t x2 = v2[0];
+    int32_t y2 = v2[1];
+
+    int32_t dx01 = x0 - x1;
+    int32_t dy20 = y2 - y0;
+    int32_t dy01 = y0 - y1;
+    int32_t dx20 = x2 - x0;
+
+    // The code below works only with CCW triangles
+    // so if we get a CW triangle, we need to swap two of its vertices
+    if (dx01*dy20 < dy01*dx20) {
+        swap(x0, x1);
+        swap(y0, y1);
+        dx01 = x0 - x1;
+        dy01 = y0 - y1;
+        dx20 = x2 - x0;
+        dy20 = y2 - y0;
+    }
+    int32_t dx12 = x1 - x2;
+    int32_t dy12 = y1 - y2;
+
+    // bounding box & scissor
+    const int32_t bminx = TRI_FLOOR(min(x0, x1, x2)) >> TRI_FRACTION_BITS;
+    const int32_t bminy = TRI_FLOOR(min(y0, y1, y2)) >> TRI_FRACTION_BITS;
+    const int32_t bmaxx = TRI_CEIL( max(x0, x1, x2)) >> TRI_FRACTION_BITS;
+    const int32_t bmaxy = TRI_CEIL( max(y0, y1, y2)) >> TRI_FRACTION_BITS;
+    const int32_t minx = max(bminx, c->state.scissor.left);
+    const int32_t miny = max(bminy, c->state.scissor.top);
+    const int32_t maxx = min(bmaxx, c->state.scissor.right);
+    const int32_t maxy = min(bmaxy, c->state.scissor.bottom);
+    if ((minx >= maxx) || (miny >= maxy))
+        return; // too small or clipped out...
+
+    // step equations to the bounding box and snap to pixel center
+    const int32_t my = (miny << TRI_FRACTION_BITS) + TRI_HALF;
+    const int32_t mx = (minx << TRI_FRACTION_BITS) + TRI_HALF;
+    int32_t ey0 = dy01 * (x0 - mx) - dx01 * (y0 - my);
+    int32_t ey1 = dy12 * (x1 - mx) - dx12 * (y1 - my);
+    int32_t ey2 = dy20 * (x2 - mx) - dx20 * (y2 - my);
+
+    // right-exclusive fill rule, to avoid rare cases
+    // of over drawing
+    if (dy01<0 || (dy01 == 0 && dx01>0)) ey0++;
+    if (dy12<0 || (dy12 == 0 && dx12>0)) ey1++;
+    if (dy20<0 || (dy20 == 0 && dx20>0)) ey2++;
+    
+    c->init_y(c, miny);
+    for (int32_t y = miny; y < maxy; y++) {
+        int32_t ex0 = ey0;
+        int32_t ex1 = ey1;
+        int32_t ex2 = ey2;    
+        int32_t xl, xr;
+        for (xl=minx ; xl<maxx ; xl++) {
+            if (ex0>0 && ex1>0 && ex2>0)
+                break; // all strictly positive
+            ex0 -= dy01 << TRI_FRACTION_BITS;
+            ex1 -= dy12 << TRI_FRACTION_BITS;
+            ex2 -= dy20 << TRI_FRACTION_BITS;
+        }
+        xr = xl;
+        for ( ; xr<maxx ; xr++) {
+            if (!(ex0>0 && ex1>0 && ex2>0))
+                break; // not all strictly positive
+            ex0 -= dy01 << TRI_FRACTION_BITS;
+            ex1 -= dy12 << TRI_FRACTION_BITS;
+            ex2 -= dy20 << TRI_FRACTION_BITS;
+        }
+
+        if (xl < xr) {
+            c->iterators.xl = xl;
+            c->iterators.xr = xr;
+            c->scanline(c);
+        }
+        c->step_y(c);
+
+        ey0 += dx01 << TRI_FRACTION_BITS;
+        ey1 += dx12 << TRI_FRACTION_BITS;
+        ey2 += dx20 << TRI_FRACTION_BITS;
+    }
+}
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#endif
+
+// the following routine fills a triangle via edge stepping, which
+// unfortunately requires divisions in the setup phase to get right,
+// it should probably only be used for relatively large trianges
+
+
+// x = y*DX/DY    (ou DX and DY are constants, DY > 0, et y >= 0)
+// 
+// for an equation of the type:
+//      x' = y*K/2^p     (with K and p constants "carefully chosen")
+// 
+// We can now do a DDA without precision loss. We define 'e' by:
+//      x' - x = y*(DX/DY - K/2^p) = y*e
+// 
+// If we choose K = round(DX*2^p/DY) then,
+//      abs(e) <= 1/2^(p+1) by construction
+// 
+// therefore abs(x'-x) = y*abs(e) <= y/2^(p+1) <= DY/2^(p+1) <= DMAX/2^(p+1)
+// 
+// which means that if DMAX <= 2^p, therefore abs(x-x') <= 1/2, including
+// at the last line. In fact, it's even a strict inequality except in one
+// extrem case (DY == DMAX et e = +/- 1/2)
+// 
+// Applying that to our coordinates, we need 2^p >= 4096*16 = 65536
+// so p = 16 is enough, we're so lucky!
+
+const int TRI_ITERATORS_BITS = 16;
+
+struct Edge
+{
+  int32_t  x;      // edge position in 16.16 coordinates
+  int32_t  x_incr; // on each step, increment x by that amount
+  int32_t  y_top;  // starting scanline, 16.4 format
+  int32_t  y_bot;
+};
+
+static void
+edge_dump( Edge*  edge )
+{
+  ALOGI( "  top=%d (%.3f)  bot=%d (%.3f)  x=%d (%.3f)  ix=%d (%.3f)",
+        edge->y_top, edge->y_top/float(TRI_ONE),
+		edge->y_bot, edge->y_bot/float(TRI_ONE),
+		edge->x, edge->x/float(FIXED_ONE),
+		edge->x_incr, edge->x_incr/float(FIXED_ONE) );
+}
+
+static void
+triangle_dump_edges( Edge*  edges,
+                     int            count )
+{ 
+    ALOGI( "%d edge%s:\n", count, count == 1 ? "" : "s" );
+	for ( ; count > 0; count--, edges++ )
+	  edge_dump( edges );
+}
+
+// the following function sets up an edge, it assumes
+// that ymin and ymax are in already in the 'reduced'
+// format
+static __attribute__((noinline))
+void edge_setup(
+        Edge*           edges,
+        int*            pcount,
+        const GGLcoord* p1,
+        const GGLcoord* p2,
+        int32_t         ymin,
+        int32_t         ymax )
+{
+	const GGLfixed*  top = p1;
+	const GGLfixed*  bot = p2;
+	Edge*    edge = edges + *pcount;
+
+	if (top[1] > bot[1]) {
+        swap(top, bot);
+	}
+
+	int  y1 = top[1] | 1;
+	int  y2 = bot[1] | 1;
+	int  dy = y2 - y1;
+
+	if ( dy == 0 || y1 > ymax || y2 < ymin )
+		return;
+
+	if ( y1 > ymin )
+		ymin = TRI_SNAP_NEXT_HALF(y1);
+	
+	if ( y2 < ymax )
+		ymax = TRI_SNAP_PREV_HALF(y2);
+
+	if ( ymin > ymax )  // when the edge doesn't cross any scanline
+	  return;
+
+	const int x1 = top[0];
+	const int dx = bot[0] - x1;
+    const int shift = TRI_ITERATORS_BITS - TRI_FRACTION_BITS;
+
+	// setup edge fields
+    // We add 0.5 to edge->x here because it simplifies the rounding
+    // in triangle_sweep_edges() -- this doesn't change the ordering of 'x'
+	edge->x      = (x1 << shift) + (1LU << (TRI_ITERATORS_BITS-1));
+	edge->x_incr = 0;
+	edge->y_top  = ymin;
+	edge->y_bot  = ymax;
+
+	if (ggl_likely(ymin <= ymax && dx)) {
+        edge->x_incr = gglDivQ16(dx, dy);
+    }
+    if (ggl_likely(y1 < ymin)) {
+        int32_t xadjust = (edge->x_incr * (ymin-y1)) >> TRI_FRACTION_BITS;
+        edge->x += xadjust;
+    }
+  
+	++*pcount;
+}
+
+
+static void
+triangle_sweep_edges( Edge*  left,
+                      Edge*  right,
+					  int            ytop,
+					  int            ybot,
+					  context_t*     c )
+{
+    int count = ((ybot - ytop)>>TRI_FRACTION_BITS) + 1;
+    if (count<=0) return;
+
+    // sort the edges horizontally
+    if ((left->x > right->x) || 
+        ((left->x == right->x) && (left->x_incr > right->x_incr))) {
+        swap(left, right);
+    }
+
+    int left_x = left->x;
+    int right_x = right->x;
+    const int left_xi = left->x_incr;
+    const int right_xi  = right->x_incr;
+    left->x  += left_xi * count;
+    right->x += right_xi * count;
+
+	const int xmin = c->state.scissor.left;
+	const int xmax = c->state.scissor.right;
+    do {
+        // horizontal scissoring
+        const int32_t xl = max(left_x  >> TRI_ITERATORS_BITS, xmin);
+        const int32_t xr = min(right_x >> TRI_ITERATORS_BITS, xmax);
+        left_x  += left_xi;
+        right_x += right_xi;
+        // invoke the scanline rasterizer
+        if (ggl_likely(xl < xr)) {
+            c->iterators.xl = xl;
+            c->iterators.xr = xr;
+            c->scanline(c);
+        }
+		c->step_y(c);
+	} while (--count);
+}
+
+
+void trianglex_big(void* con,
+        const GGLcoord* v0, const GGLcoord* v1, const GGLcoord* v2)
+{
+    GGL_CONTEXT(c, con);
+
+    Edge edges[3];
+	int num_edges = 0;
+	int32_t ymin = TRI_FROM_INT(c->state.scissor.top)    + TRI_HALF;
+	int32_t ymax = TRI_FROM_INT(c->state.scissor.bottom) - TRI_HALF;
+	    
+	edge_setup( edges, &num_edges, v0, v1, ymin, ymax );
+	edge_setup( edges, &num_edges, v0, v2, ymin, ymax );
+	edge_setup( edges, &num_edges, v1, v2, ymin, ymax );
+
+    if (ggl_unlikely(num_edges<2))  // for really tiny triangles that don't
+		return;                     // cross any scanline centers
+
+    Edge* left  = &edges[0];
+    Edge* right = &edges[1];
+    Edge* other = &edges[2];
+    int32_t y_top = min(left->y_top, right->y_top);
+    int32_t y_bot = max(left->y_bot, right->y_bot);
+
+	if (ggl_likely(num_edges==3)) {
+        y_top = min(y_top, edges[2].y_top);
+        y_bot = max(y_bot, edges[2].y_bot);
+		if (edges[0].y_top > y_top) {
+            other = &edges[0];
+            left  = &edges[2];
+		} else if (edges[1].y_top > y_top) {
+            other = &edges[1];
+            right = &edges[2];
+		}
+    }
+
+    c->init_y(c, y_top >> TRI_FRACTION_BITS);
+
+    int32_t y_mid = min(left->y_bot, right->y_bot);
+    triangle_sweep_edges( left, right, y_top, y_mid, c );
+
+    // second scanline sweep loop, if necessary
+    y_mid += TRI_ONE;
+    if (y_mid <= y_bot) {
+        ((left->y_bot == y_bot) ? right : left) = other;
+        if (other->y_top < y_mid) {
+            other->x += other->x_incr;
+        }
+        triangle_sweep_edges( left, right, y_mid, y_bot, c );
+    }
+}
+
+void aa_trianglex(void* con,
+        const GGLcoord* a, const GGLcoord* b, const GGLcoord* c)
+{
+    GGLcoord pts[6] = { a[0], a[1], b[0], b[1], c[0], c[1] };
+    aapolyx(con, pts, 3);
+}
+
+// ----------------------------------------------------------------------------
+#if 0
+#pragma mark -
+#endif
+
+struct AAEdge
+{
+    GGLfixed x;         // edge position in 12.16 coordinates
+    GGLfixed x_incr;    // on each y step, increment x by that amount
+    GGLfixed y_incr;    // on each x step, increment y by that amount
+    int16_t y_top;      // starting scanline, 12.4 format
+    int16_t y_bot;      // starting scanline, 12.4 format
+    void dump();
+};
+
+void AAEdge::dump()
+{
+    float tri  = 1.0f / TRI_ONE;
+    float iter = 1.0f / (1<<TRI_ITERATORS_BITS);
+    float fix  = 1.0f / FIXED_ONE;
+    ALOGD(   "x=%08x (%.3f), "
+            "x_incr=%08x (%.3f), y_incr=%08x (%.3f), "
+            "y_top=%08x (%.3f), y_bot=%08x (%.3f) ",
+        x, x*fix,
+        x_incr, x_incr*iter,
+        y_incr, y_incr*iter,
+        y_top, y_top*tri,
+        y_bot, y_bot*tri );
+}
+
+// the following function sets up an edge, it assumes
+// that ymin and ymax are in already in the 'reduced'
+// format
+static __attribute__((noinline))
+void aa_edge_setup(
+        AAEdge*         edges,
+        int*            pcount,
+        const GGLcoord* p1,
+        const GGLcoord* p2,
+        int32_t         ymin,
+        int32_t         ymax )
+{
+    const GGLfixed*  top = p1;
+    const GGLfixed*  bot = p2;
+    AAEdge* edge = edges + *pcount;
+
+    if (top[1] > bot[1])
+        swap(top, bot);
+
+    int  y1 = top[1];
+    int  y2 = bot[1];
+    int  dy = y2 - y1;
+
+    if (dy==0 || y1>ymax || y2<ymin)
+        return;
+
+    if (y1 > ymin)
+        ymin = y1;
+    
+    if (y2 < ymax)
+        ymax = y2;
+
+    const int x1 = top[0];
+    const int dx = bot[0] - x1;
+    const int shift = FIXED_BITS - TRI_FRACTION_BITS;
+
+    // setup edge fields
+    edge->x      = x1 << shift;
+    edge->x_incr = 0;
+    edge->y_top  = ymin;
+    edge->y_bot  = ymax;
+    edge->y_incr = 0x7FFFFFFF;
+
+    if (ggl_likely(ymin <= ymax && dx)) {
+        edge->x_incr = gglDivQ16(dx, dy);
+        if (dx != 0) {
+            edge->y_incr = abs(gglDivQ16(dy, dx));
+        }
+    }
+    if (ggl_likely(y1 < ymin)) {
+        int32_t xadjust = (edge->x_incr * (ymin-y1))
+                >> (TRI_FRACTION_BITS + TRI_ITERATORS_BITS - FIXED_BITS);
+        edge->x += xadjust;
+    }
+  
+    ++*pcount;
+}
+
+
+typedef int (*compar_t)(const void*, const void*);
+static int compare_edges(const AAEdge *e0, const AAEdge *e1) {
+    if (e0->y_top > e1->y_top)      return 1;
+    if (e0->y_top < e1->y_top)      return -1;
+    if (e0->x > e1->x)              return 1;
+    if (e0->x < e1->x)              return -1;
+    if (e0->x_incr > e1->x_incr)    return 1;
+    if (e0->x_incr < e1->x_incr)    return -1;
+    return 0; // same edges, should never happen
+}
+
+static inline 
+void SET_COVERAGE(int16_t*& p, int32_t value, ssize_t n)
+{
+    android_memset16((uint16_t*)p, value, n*2);
+    p += n;
+}
+
+static inline 
+void ADD_COVERAGE(int16_t*& p, int32_t value)
+{
+    value = *p + value;
+    if (value >= 0x8000)
+        value = 0x7FFF;
+    *p++ = value;
+}
+
+static inline
+void SUB_COVERAGE(int16_t*& p, int32_t value)
+{
+    value = *p - value;
+    value &= ~(value>>31);
+    *p++ = value;
+}
+
+void aapolyx(void* con,
+        const GGLcoord* pts, int count)
+{
+    /*
+     * NOTE: This routine assumes that the polygon has been clipped to the
+     * viewport already, that is, no vertex lies outside of the framebuffer.
+     * If this happens, the code below won't corrupt memory but the 
+     * coverage values may not be correct.
+     */
+    
+    GGL_CONTEXT(c, con);
+
+    // we do only quads for now (it's used for thick lines)
+    if ((count>4) || (count<2)) return;
+
+    // take scissor into account
+    const int xmin = c->state.scissor.left;
+    const int xmax = c->state.scissor.right;
+    if (xmin >= xmax) return;
+
+    // generate edges from the vertices
+    int32_t ymin = TRI_FROM_INT(c->state.scissor.top);
+    int32_t ymax = TRI_FROM_INT(c->state.scissor.bottom);
+    if (ymin >= ymax) return;
+
+    AAEdge edges[4];
+    int num_edges = 0;
+    GGLcoord const * p = pts;
+    for (int i=0 ; i<count-1 ; i++, p+=2) {
+        aa_edge_setup(edges, &num_edges, p, p+2, ymin, ymax);
+    }
+    aa_edge_setup(edges, &num_edges, p, pts, ymin, ymax );
+    if (ggl_unlikely(num_edges<2))
+        return;
+
+    // sort the edge list top to bottom, left to right.
+    qsort(edges, num_edges, sizeof(AAEdge), (compar_t)compare_edges);
+
+    int16_t* const covPtr = c->state.buffers.coverage;
+    memset(covPtr+xmin, 0, (xmax-xmin)*sizeof(*covPtr));
+
+    // now, sweep all edges in order
+    // start with the 2 first edges. We know that they share their top
+    // vertex, by construction.
+    int i = 2;
+    AAEdge* left  = &edges[0];
+    AAEdge* right = &edges[1];
+    int32_t yt = left->y_top;
+    GGLfixed l = left->x;
+    GGLfixed r = right->x;
+    int retire = 0;
+    int16_t* coverage;
+
+    // at this point we can initialize the rasterizer    
+    c->init_y(c, yt>>TRI_FRACTION_BITS);
+    c->iterators.xl = xmax;
+    c->iterators.xr = xmin;
+
+    do {
+        int32_t y = min(min(left->y_bot, right->y_bot), TRI_FLOOR(yt + TRI_ONE));
+        const int32_t shift = TRI_FRACTION_BITS + TRI_ITERATORS_BITS - FIXED_BITS;
+        const int cf_shift = (1 + TRI_FRACTION_BITS*2 + TRI_ITERATORS_BITS - 15);
+
+        // compute xmin and xmax for the left edge
+        GGLfixed l_min = gglMulAddx(left->x_incr, y - left->y_top, left->x, shift);
+        GGLfixed l_max = l;
+        l = l_min;
+        if (l_min > l_max)
+            swap(l_min, l_max);
+
+        // compute xmin and xmax for the right edge
+        GGLfixed r_min = gglMulAddx(right->x_incr, y - right->y_top, right->x, shift);
+        GGLfixed r_max = r;
+        r = r_min;
+        if (r_min > r_max)
+            swap(r_min, r_max);
+
+        // make sure we're not touching coverage values outside of the
+        // framebuffer
+        l_min &= ~(l_min>>31);
+        r_min &= ~(r_min>>31);
+        l_max &= ~(l_max>>31);
+        r_max &= ~(r_max>>31);
+        if (gglFixedToIntFloor(l_min) >= xmax) l_min = gglIntToFixed(xmax)-1;
+        if (gglFixedToIntFloor(r_min) >= xmax) r_min = gglIntToFixed(xmax)-1;
+        if (gglFixedToIntCeil(l_max) >= xmax)  l_max = gglIntToFixed(xmax)-1;
+        if (gglFixedToIntCeil(r_max) >= xmax)  r_max = gglIntToFixed(xmax)-1;
+
+        // compute the integer versions of the above
+        const GGLfixed l_min_i = gglFloorx(l_min);
+        const GGLfixed l_max_i = gglCeilx (l_max);
+        const GGLfixed r_min_i = gglFloorx(r_min);
+        const GGLfixed r_max_i = gglCeilx (r_max);
+
+        // clip horizontally using the scissor
+        const int xml = max(xmin, gglFixedToIntFloor(l_min_i));
+        const int xmr = min(xmax, gglFixedToIntFloor(r_max_i));
+
+        // if we just stepped to a new scanline, render the previous one.
+        // and clear the coverage buffer
+        if (retire) {
+            if (c->iterators.xl < c->iterators.xr)
+                c->scanline(c);
+            c->step_y(c);
+            memset(covPtr+xmin, 0, (xmax-xmin)*sizeof(*covPtr));
+            c->iterators.xl = xml;
+            c->iterators.xr = xmr;
+        } else {
+            // update the horizontal range of this scanline
+            c->iterators.xl = min(c->iterators.xl, xml);
+            c->iterators.xr = max(c->iterators.xr, xmr);
+        }
+
+        coverage = covPtr + gglFixedToIntFloor(l_min_i);
+        if (l_min_i == gglFloorx(l_max)) {
+            
+            /*
+             *  fully traverse this pixel vertically
+             *       l_max
+             *  +-----/--+  yt
+             *  |    /   |  
+             *  |   /    |
+             *  |  /     |
+             *  +-/------+  y
+             *   l_min  (l_min_i + TRI_ONE)
+             */
+              
+            GGLfixed dx = l_max - l_min;
+            int32_t dy = y - yt;
+            int cf = gglMulx((dx >> 1) + (l_min_i + FIXED_ONE - l_max), dy,
+                FIXED_BITS + TRI_FRACTION_BITS - 15);
+            ADD_COVERAGE(coverage, cf);
+            // all pixels on the right have cf = 1.0
+        } else {
+            /*
+             *  spans several pixels in one scanline
+             *            l_max
+             *  +--------+--/-----+  yt
+             *  |        |/       |
+             *  |       /|        |
+             *  |     /  |        |
+             *  +---/----+--------+  y
+             *   l_min (l_min_i + TRI_ONE)
+             */
+
+            // handle the first pixel separately...
+            const int32_t y_incr = left->y_incr;
+            int32_t dx = TRI_FROM_FIXED(l_min_i - l_min) + TRI_ONE;
+            int32_t cf = (dx * dx * y_incr) >> cf_shift;
+            ADD_COVERAGE(coverage, cf);
+
+            // following pixels get covered by y_incr, but we need
+            // to fix-up the cf to account for previous partial pixel
+            dx = TRI_FROM_FIXED(l_min - l_min_i);
+            cf -= (dx * dx * y_incr) >> cf_shift;
+            for (int x = l_min_i+FIXED_ONE ; x < l_max_i-FIXED_ONE ; x += FIXED_ONE) {
+                cf += y_incr >> (TRI_ITERATORS_BITS-15);
+                ADD_COVERAGE(coverage, cf);
+            }
+            
+            // and the last pixel
+            dx = TRI_FROM_FIXED(l_max - l_max_i) - TRI_ONE;
+            cf += (dx * dx * y_incr) >> cf_shift;
+            ADD_COVERAGE(coverage, cf);
+        }
+        
+        // now, fill up all fully covered pixels
+        coverage = covPtr + gglFixedToIntFloor(l_max_i);
+        int cf = ((y - yt) << (15 - TRI_FRACTION_BITS));
+        if (ggl_likely(cf >= 0x8000)) {
+            SET_COVERAGE(coverage, 0x7FFF, ((r_max - l_max_i)>>FIXED_BITS)+1);
+        } else {
+            for (int x=l_max_i ; x<r_max ; x+=FIXED_ONE) {
+                ADD_COVERAGE(coverage, cf);
+            }
+        }
+        
+        // subtract the coverage of the right edge
+        coverage = covPtr + gglFixedToIntFloor(r_min_i); 
+        if (r_min_i == gglFloorx(r_max)) {
+            GGLfixed dx = r_max - r_min;
+            int32_t dy = y - yt;
+            int cf = gglMulx((dx >> 1) + (r_min_i + FIXED_ONE - r_max), dy,
+                FIXED_BITS + TRI_FRACTION_BITS - 15);
+            SUB_COVERAGE(coverage, cf);
+            // all pixels on the right have cf = 1.0
+        } else {
+            // handle the first pixel separately...
+            const int32_t y_incr = right->y_incr;
+            int32_t dx = TRI_FROM_FIXED(r_min_i - r_min) + TRI_ONE;
+            int32_t cf = (dx * dx * y_incr) >> cf_shift;
+            SUB_COVERAGE(coverage, cf);
+            
+            // following pixels get covered by y_incr, but we need
+            // to fix-up the cf to account for previous partial pixel
+            dx = TRI_FROM_FIXED(r_min - r_min_i);
+            cf -= (dx * dx * y_incr) >> cf_shift;
+            for (int x = r_min_i+FIXED_ONE ; x < r_max_i-FIXED_ONE ; x += FIXED_ONE) {
+                cf += y_incr >> (TRI_ITERATORS_BITS-15);
+                SUB_COVERAGE(coverage, cf);
+            }
+            
+            // and the last pixel
+            dx = TRI_FROM_FIXED(r_max - r_max_i) - TRI_ONE;
+            cf += (dx * dx * y_incr) >> cf_shift;
+            SUB_COVERAGE(coverage, cf);
+        }
+
+        // did we reach the end of an edge? if so, get a new one.
+        if (y == left->y_bot || y == right->y_bot) {
+            // bail out if we're done
+            if (i>=num_edges)
+                break;
+            if (y == left->y_bot)
+                left = &edges[i++];
+            if (y == right->y_bot)
+                right = &edges[i++];
+        }
+
+        // next scanline
+        yt = y;
+        
+        // did we just finish a scanline?        
+        retire = (y << (32-TRI_FRACTION_BITS)) == 0;
+    } while (true);
+
+    // render the last scanline
+    if (c->iterators.xl < c->iterators.xr)
+        c->scanline(c);
+}
+
+}; // namespace android
diff --git a/libpixelflinger/trap.h b/libpixelflinger/trap.h
new file mode 100644
index 0000000..7cce7b3
--- /dev/null
+++ b/libpixelflinger/trap.h
@@ -0,0 +1,31 @@
+/* libs/pixelflinger/trap.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+
+#ifndef ANDROID_TRAP_H
+#define ANDROID_TRAP_H
+
+#include <private/pixelflinger/ggl_context.h>
+
+namespace android {
+
+void ggl_init_trap(context_t* c);
+void ggl_state_changed(context_t* c, int flags);
+
+}; // namespace android
+
+#endif
diff --git a/libtar/Android.mk b/libtar/Android.mk
new file mode 100755
index 0000000..80ded75
--- /dev/null
+++ b/libtar/Android.mk
@@ -0,0 +1,95 @@
+LOCAL_PATH := $(call my-dir)
+
+# Build shared library
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libtar
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := append.c block.c decode.c encode.c extract.c handle.c output.c util.c wrapper.c basename.c strmode.c libtar_hash.c libtar_list.c dirname.c android_utils.c
+LOCAL_C_INCLUDES += $(LOCAL_PATH) \
+                    external/zlib
+LOCAL_SHARED_LIBRARIES += libz libc
+
+LOCAL_C_INCLUDES += external/libselinux/include
+LOCAL_SHARED_LIBRARIES += libselinux
+
+ifeq ($(TW_INCLUDE_CRYPTO_FBE), true)
+    LOCAL_STATIC_LIBRARIES += libvold libscrypt_static
+    LOCAL_SHARED_LIBRARIES +=  \
+        android.hardware.boot@1.0 \
+        android.hardware.confirmationui@1.0 \
+        android.hardware.gatekeeper@1.0 \
+        android.hardware.keymaster@3.0 \
+        android.hardware.keymaster@4.0 \
+        android.hardware.keymaster@4.1 \
+        android.hardware.weaver@1.0 \
+        android.security.apc-ndk  \
+        android.system.keystore2-V1-ndk \
+        android.security.authorization-ndk \
+        android.security.maintenance-ndk \
+        libkeystoreinfo \
+        libselinux \
+        libbinder_ndk \
+        libext4_utils \
+        libbase \
+        libcrypto \
+        libcutils \
+        libf2fs_sparseblock \
+        libkeymaster_messages \
+        libkeymint_support \
+        libkeystore-attestation-application-id \
+        libhardware \
+        libprotobuf-cpp-lite \
+        libfscrypt \
+        libhidlbase \
+        libutils \
+        libbinder \
+        libfs_mgr \
+        libkeymaster4support \
+        libkeymaster4_1support \
+        libf2fs_sparseblock \
+        libkeyutils \
+        liblog \
+        libhwbinder \
+        libchrome \
+        libbootloader_message \
+        libgatekeeper_aidl
+
+    LOCAL_CFLAGS += -DUSE_FSCRYPT
+    ifeq ($(TW_USE_FSCRYPT_POLICY), 1)
+        LOCAL_CFLAGS += -DUSE_FSCRYPT_POLICY_V1
+    else
+        LOCAL_CFLAGS += -DUSE_FSCRYPT_POLICY_V2
+    endif
+    LOCAL_C_INCLUDES += system/vold
+endif
+
+ifeq ($(TW_LIBTAR_DEBUG),true)
+    LOCAL_CFLAGS += -DTW_LIBTAR_DEBUG
+endif
+
+include $(BUILD_SHARED_LIBRARY)
+
+# Build static library
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libtar_static
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := append.c block.c decode.c encode.c extract.c handle.c output.c util.c wrapper.c basename.c strmode.c libtar_hash.c libtar_list.c dirname.c android_utils.c
+LOCAL_C_INCLUDES += $(LOCAL_PATH) \
+                    external/zlib
+LOCAL_STATIC_LIBRARIES += libz libc
+
+LOCAL_C_INCLUDES += external/libselinux/include
+LOCAL_STATIC_LIBRARIES += libselinux
+
+ifeq ($(TW_INCLUDE_CRYPTO_FBE), true)
+    LOCAL_CFLAGS += -DUSE_FSCRYPT
+    LOCAL_C_INCLUDES += system/vold
+endif
+
+ifeq ($(TW_LIBTAR_DEBUG),true)
+    LOCAL_CFLAGS += -DTW_LIBTAR_DEBUG
+endif
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/libtar/COPYRIGHT b/libtar/COPYRIGHT
new file mode 100644
index 0000000..2471ec0
--- /dev/null
+++ b/libtar/COPYRIGHT
@@ -0,0 +1,35 @@
+Copyright (c) 1998-2003  University of Illinois Board of Trustees
+Copyright (c) 1998-2003  Mark D. Roth
+All rights reserved.
+
+Developed by: Campus Information Technologies and Educational Services,
+              University of Illinois at Urbana-Champaign
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+``Software''), to deal with the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimers.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimers in the
+  documentation and/or other materials provided with the distribution.
+
+* Neither the names of Campus Information Technologies and Educational
+  Services, University of Illinois at Urbana-Champaign, nor the names
+  of its contributors may be used to endorse or promote products derived
+  from this Software without specific prior written permission.
+
+THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
+
diff --git a/libtar/ChangeLog b/libtar/ChangeLog
new file mode 100644
index 0000000..03bef68
--- /dev/null
+++ b/libtar/ChangeLog
@@ -0,0 +1,335 @@
+	NOTE:
+	All releases below marked (Chris Frey) are maintenance releases
+	done by Chris Frey, temporarily stepping in for Mark Roth.
+	These releases are git-based only and can be found at:
+		http://repo.or.cz/w/libtar.git
+
+	Both git downloads and tarball downloads are possible at this site.
+
+
+libtar 1.2.20 - 2013/10/09 (Chris Frey)
+-------------
+      Added extern "C" protectors to listhash.h
+      Added autoconf checks for __thread compiler support
+      Fixed size_t overflow bug, as reported by Timo Warns
+      Fixed thread-safe bug in th_get_pathname() (Sergey Zhitomirsky)
+
+
+libtar 1.2.19 - 2012/12/11 (Chris Frey)
+-------------
+      Removed varargs.h and all dependencies, to avoid user compile errors
+
+      Fixed some short int / int compiler warnings in va_arg() usage
+
+      Fixed some gcc built-in compiler warnings
+
+      Changed autoconf support code from AC_RUN_ to AC_COMPILE_ to fix
+      issues reported during cross-compiling.
+
+      Applied most of Jan Cermak's const char* function argument patch.
+
+
+libtar 1.2.18 - 2012/08/02 (Chris Frey)
+-------------
+      Added more forgiving CRC checking logic when reading tar files
+
+      Note: If your application uses the macro th_crc_ok(), then to gain full
+      advantage of the changes in this version, you will need to recompile
+      your application against the new headers.  Otherwise, the library is
+      drop-in replaceable, as usual.
+
+
+libtar 1.2.17 - 2012/07/24 (Chris Frey)
+-------------
+      Applied Tim Band's checksum patch from mailing list (thanks!)
+
+
+libtar 1.2.16 - 2012/05/17 (Chris Frey)
+-------------
+      Fixed build system to allow for out-of-source tree builds
+
+
+libtar 1.2.15 - 2012/05/10 (Chris Frey)
+-------------
+Chris Frey (1):
+      Fixed harmless buffer overflow which is caught by FORTIFY on some systems
+
+
+libtar 1.2.14 - 2011/12/22 (Chris Frey)
+-------------
+Chris Frey (1):
+      Fixed truncation check, so 100 char names get GNU extension support when enabled
+
+
+libtar 1.2.13 - 2011/06/13 (Chris Frey)
+-------------
+Chris Frey (10):
+      Fixed incorrect URL in readme
+      Added autoconf/ as macro dir
+      Added autogen.sh script to build a fresh configure
+      Renamed autoconf/aclocal.m4 to psg.m4 so aclocal isn't so confused
+      Removed m4 includes, and straightened out [] m4 quoting for modern autoconfs
+      Removed auto-generated files
+      Added datarootdir to Makefile.in's
+      Fixed header warnings
+      Applied Marcin Gibula's patch fixing tar_extract_glob()
+      Changed root Makefile.in to Makefile.am, which make autoreconf workable
+
+Glenn McGrath (1):
+      Use libtool to build dynamic library
+
+James Morrison (1):
+      Document stupidity of tartype_t in libtar.c.
+
+Magnus Holmgren (1):
+      Escape hyphens that should be minus signs in man pages.
+
+Per Lidén (2):
+      Fix memory leak in th_get_pathname
+      Reduce memory used by libtar when extracting files.
+
+------------------------------------------------------------------------------
+
+libtar 1.2.11 - 3/2/03
+-------------
+
+- updated autoconf macros, compat code, and listhash code
+- fixed tar_extract_regfile() to pass mode argument to open()
+  (caused EPERM on Solaris NFS clients)
+- updated README
+
+------------------------------------------------------------------------------
+
+libtar 1.2.10 - 12/15/02
+-------------
+
+- updated README
+- minor Makefile fixes
+- fixed TH_ISREG() macro to not return true for hard links
+
+------------------------------------------------------------------------------
+
+libtar 1.2.9 - 11/19/02
+------------
+
+- fixed th_read() to return 1 on EOF
+  (thanks to Yves Crespin <Crespin.Quartz@WANADOO.FR> for the bug report)
+- minor portability fixes
+  (thanks to Yves Crespin <Crespin.Quartz@WANADOO.FR> for the bug report)
+- fixed segfault on extracting filenames with 8-bit ASCII characters
+  (thanks to Per Liden <per@FUKT.BTH.SE> for the patch)
+- fixed TH_ISDIR() macro and th_get_mode() function to handle old
+  archives that don't set the typeflag field right for directories
+- use 0777 instead of 0755 in mkdirhier()
+  (thanks to Yves Crespin <Crespin.Quartz@WANADOO.FR> for the bug report)
+
+------------------------------------------------------------------------------
+
+libtar 1.2.8 - 9/13/02
+------------
+
+- added "-I../listhash" to CPPFLAGS in libtar/Makefile.in
+  (thanks to Kris Warkentin <kewarken@QNX.COM> for the bug report)
+- added .PHONY target to Makefile.in
+  (thanks to Steven Engelhardt <sengelha@YAHOO.COM> for the bug report)
+
+------------------------------------------------------------------------------
+
+libtar 1.2.7 - 9/12/02
+------------
+
+- fixed minor bugs in listhash code
+  (thanks to Jim Knoble <jmknoble@pobox.com> for the bug reports)
+
+------------------------------------------------------------------------------
+
+libtar 1.2.6 - 9/10/02
+------------
+
+- updated COPYRIGHT file
+- do not check magic field by default
+  (replaced TAR_IGNORE_MAGIC option with TAR_CHECK_MAGIC to enable check)
+- fixed th_get_mode() not to modify S_IFMT bits if they were already set
+- fixed TH_IS*() macros to check the S_IFMT mode bits in addition to typeflag
+  (this allows us to handle old tar archives that set mode bits but not
+  typeflag field for directories and other special files)
+- updated to autoconf-2.53
+- restructured autoconf macros
+- added "b" to gzoflags in gzopen_frontend() for win32 compatibility
+  (thanks to Kris Eric Warkentin <kewarken@QNX.COM> for reporting this)
+- if O_BINARY is defined (as on win32), set that bit in oflags in tar_open()
+  (thanks to Kris Eric Warkentin <kewarken@QNX.COM> for reporting this)
+- also use O_BINARY in when calling open() from tar_extract_regfile()
+  (based on patch from Graeme Peterson <gp@qnx.com>)
+- added COMPAT_FUNC_MAKEDEV macro to handle 3-arg version of makedev()
+  (based on patch from Graeme Peterson <gp@qnx.com>)
+
+------------------------------------------------------------------------------
+
+libtar 1.2.5 - 2/20/02
+------------
+
+- updated to autoconf-2.52
+- improved Makefile portability
+- fixed memory leak in hard-link detection code
+  (thanks to Michael Kamp <kamp@HITT.NL> for the bug report)
+- fixed memory leak in symlink handling code
+  (thanks to Michael Kamp <kamp@HITT.NL> for the bug report)
+- fixed memory leak in GNU long filename code
+
+------------------------------------------------------------------------------
+
+libtar 1.2.4 - 7/24/01
+------------
+
+- code cleanups to make gcc -Wall happy
+  (thanks to Jim Knoble <jmknoble@POBOX.COM> for the patch)
+- call utime() before chmod() in tar_set_file_perms() for cygwin
+  (thanks to Kris Eric Warkentin <kewarken@QNX.COM> for reporting this)
+- added "-g" flag to trigger GNU extensions in libtar binary
+- fixed buffer termination bugs in POSIX filename prefix encoding
+  (thanks to Joerg Schilling <schilling@fokus.gmd.de> for reporting this)
+- fixed bug in th_crc_calc() for filenames with 8-bit ASCII characters
+  (thanks to Hamdouni El Bachir <bach@zehc.net> for reporting the bug
+   and Antoniu-George SAVU <santoniu@libertysurf.fr> for the patch)
+- fixed backwards conditional expression in th_read()
+  (thanks to Antoniu-George SAVU <santoniu@LIBERTYSURF.FR> for the patch)
+- added new tar_open() options to replace compile-time settings:
+  TAR_IGNORE_EOT, TAR_IGNORE_MAGIC, TAR_CHECK_VERSION, TAR_IGNORE_CRC
+  (based on feedback from Kris Eric Warkentin <kewarken@QNX.COM>)
+
+------------------------------------------------------------------------------
+
+libtar 1.2.3 - 6/26/01
+------------
+
+- misc portability fixes for OpenBSD
+- fixed libtar.h to work with C++ programs
+- fixed tar_extract_file() to properly check for pre-existing symlinks
+  (based on patch from Per Lid?n <per@fukt.hk-r.se>)
+- fixed hash creation in tar_init()
+- replaced mkdirhier() with non-recursive version
+- updated autoconf macros, compat code, and listhash code
+- reformatted code for readability
+
+------------------------------------------------------------------------------
+
+libtar 1.2.2 - 1/12/01
+------------
+
+- fixed th_print_long_ls() to not truncate user and group names
+- code cleanups to make -Wall happy
+
+------------------------------------------------------------------------------
+
+libtar 1.2.1 - 1/8/01
+------------
+
+- updated WSG_ENCAP autoconf macro
+- fixed autoconf macros to behave properly when a config.cache file
+  is present
+- fixed doc/Makefile.in to create links during compilation, not
+  installation
+- fixed listhash manpage .so link lists
+
+------------------------------------------------------------------------------
+
+libtar 1.2 - 1/4/01
+----------
+
+- minor code cleanups
+
+------------------------------------------------------------------------------
+
+libtar 1.1.b8 - 1/2/01
+-------------
+
+- updated WSG_ENCAP autoconf macro
+
+------------------------------------------------------------------------------
+
+libtar 1.1.b7 - 12/13/00
+-------------
+
+- fixed autoconf snprintf() test to make sure it NUL-terminates
+
+------------------------------------------------------------------------------
+
+libtar 1.1.b6 - 11/30/00
+-------------
+
+- added $(DESTDIR) to Makefiles
+- Makefile changes to support WSG_PKG and WSG_ENCAP autoconf macros
+- changed lib/output.c to use strftime() where available
+
+------------------------------------------------------------------------------
+
+libtar 1.1.b5 - 10/29/00
+-------------
+
+- Makefile fix
+
+------------------------------------------------------------------------------
+
+libtar 1.1.b4 - 10/29/00
+-------------
+
+- more directory reorganization
+- minor Makefile cleanups
+- minor portability fixes
+- added function typecasting to avoid compiler warnings
+
+------------------------------------------------------------------------------
+
+libtar 1.1.b3 - 10/26/00
+-------------
+
+- updated aclocal.m4
+- updated README
+- updated manpages
+- minor directory structure changes because of CVS setup
+
+------------------------------------------------------------------------------
+
+libtar 1.1.b2 - 10/5/00
+-------------
+
+- added --without-zlib configure option
+- minor portability fixes
+
+------------------------------------------------------------------------------
+
+libtar 1.1.b1 - 8/21/00
+-------------
+
+- API changes:
+  - implemented tar_fdopen()
+  - implemented tar_fd()
+  - added TAR **t argument to tar_open() instead of returning dynamic memory
+  - if TAR_NOOVERWRITE is set in options and O_CREAT is set in oflags,
+    tar_open() automatically sets O_EXCL as well
+
+------------------------------------------------------------------------------
+
+libtar 1.1.b0 - 7/10/00
+-------------
+
+- API changes:
+  - replaced internal table of tar file types with a tartype_t passed to
+    tar_open() by the caller
+    (allows file access methods to be defined dynamically)
+  - fixed tar_append_tree() to grok normal files as well as directories
+  - replaced mk_dirs_for_file() with mkdirhier() from epkg
+  - replaced strtok_r() with strsep()
+  - updated list/hash code to new interface
+
+- autoconf changes:
+  - added aclocal.m4 to clean up configure.in
+  - minor portability fixes related to lib/fnmatch.c
+
+- fixed a bug in tar_open() where the result of open() was being
+  checked for 0 instead of -1 to detect error
+
+- updated libtar driver program to handle both .tar.gz and ordinary .tar
+  via the -z option
+
diff --git a/libtar/ChangeLog-1.0.x b/libtar/ChangeLog-1.0.x
new file mode 100644
index 0000000..23b06b3
--- /dev/null
+++ b/libtar/ChangeLog-1.0.x
@@ -0,0 +1,141 @@
+libtar 1.0.2 - 6/21/00
+------------
+
+- tar_set_file_perms() now calls chown() only if the effective user ID is 0
+  (workaround for IRIX and HP-UX, which allow file giveaways)
+
+- tar_set_file_perms() now calls chmod() or lchmod() after chown()
+  (this fixes a problem with extracting setuid files under Linux)
+
+- removed calls to fchown() and fchmod() from tar_extract_regfile()
+
+- fixed bugs in th_read() which didn't set errno properly
+
+- removed various unused variables
+
+----------------------------------------------------------------------
+
+libtar 1.0.1 - 4/1/00
+------------
+
+- removed libgen.h include from dirname and basename compat code
+
+- added lib/fnmatch.c compatability module from OpenBSD
+
+- fixed several objdirs bugs in libtar/Makefile.in
+
+- misc Makefile changes (added $CPPFLAGS support, added -o flag to compile
+  commands, use $CFLAGS on link line, etc)
+
+- removed "inline" keyword from all source files to prevent portability
+  problems
+
+- updated README
+
+----------------------------------------------------------------------
+
+libtar 1.0 - 1/2/00
+----------
+
+- various portability fixes
+
+- "make install" now runs mkencap and epkg if they're available
+
+- libmisc is now integrated into libtar
+
+----------------------------------------------------------------------
+
+libtar 0.5.6 beta - 12/16/99
+-----------------
+
+- changed API to allow better error reporting via errno
+
+- added manpages to document libtar API
+
+- replaced symbolic_mode() call with strmode() compatibility code
+
+----------------------------------------------------------------------
+
+libtar 0.5.5 beta - 11/16/99
+-----------------
+
+- fixed conditional expression in extract.c to check if we're overwriting
+  a pre-existing file
+
+- many improvements to libtar.c driver program (better error checking,
+  added -C and -v options, etc)
+
+- changed API to include list of canned file types, instead of passing
+  function pointers to tar_open()
+
+- fixed tar_set_file_perms() to not complain about chown() if not root
+  and not to call utime() on a symlink
+
+- added hash code for extracting hard links in other directory paths
+
+- fixed tar_extract_glob() to only print filenames if TAR_VERBOSE option
+  is set
+
+- replaced GNU basename(), dirname(), and strdup() compatibility code
+  with OpenBSD versions
+
+- configure performs super-anal checking of basename() and dirname()
+
+----------------------------------------------------------------------
+
+libtar 0.5.4 beta - 11/13/99
+-----------------
+
+- portability fix: use ranlib instead of ar -s
+
+- misc fixes in append.c, extract.c, and wrapper.c to do error checking
+
+- fixed a bug in tar_append_file() in append.c which added some garbage
+  characters to encoded symlink names (wasn't NULL-terminating the result
+  of readlink())
+
+- fixed a bug in symbolic_mode() in output.c concerning setuid and setgid
+  bit displaying
+
+- fixed tar_extract_all() in wrapper.c to only call print_long_ls() if
+  the TAR_VERBOSE option is set
+
+- added libtar_version constant string to handle.c for external configure
+  scripts to detect what version of libtar is installed
+
+----------------------------------------------------------------------
+
+libtar 0.5.3 beta - 09/27/99
+-----------------
+
+- fixed mk_dirs_for_file() to avoid broken dirname() implementations
+
+- misc portability fixes
+
+- merged old "compat" and "libds" directories into new "misc" directory
+  and cleaned up Makefiles
+
+----------------------------------------------------------------------
+
+libtar 0.5.2 beta - 09/10/99
+-----------------
+
+- use calloc() instead of malloc() in tar_open() to fix a bounds-checking
+  bug in tar_extract_all()
+
+- fix tar_extract_all() to properly honor the prefix argument
+
+----------------------------------------------------------------------
+
+libtar 0.5.1 beta - 08/27/99
+-----------------
+
+- misc portability fixes
+
+----------------------------------------------------------------------
+
+libtar 0.5 beta - 07/05/99
+---------------
+
+- first public release
+
diff --git a/libtar/INSTALL b/libtar/INSTALL
new file mode 100644
index 0000000..50dbe43
--- /dev/null
+++ b/libtar/INSTALL
@@ -0,0 +1,183 @@
+Basic Installation
+==================
+
+   These are generic installation instructions.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, a file
+`config.cache' that saves the results of its tests to speed up
+reconfiguring, and a file `config.log' containing compiler output
+(useful mainly for debugging `configure').
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If at some point `config.cache'
+contains results you don't want to keep, you may remove or edit it.
+
+   The file `configure.in' is used to create `configure' by a program
+called `autoconf'.  You only need `configure.in' if you want to change
+it or regenerate `configure' using a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.  If you're
+     using `csh' on an old version of System V, you might need to type
+     `sh ./configure' instead to prevent `csh' from trying to execute
+     `configure' itself.
+
+     Running `configure' takes awhile.  While running, it prints some
+     messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Optionally, type `make check' to run any self-tests that come with
+     the package.
+
+  4. Type `make install' to install the programs and any data files and
+     documentation.
+
+  5. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  To also remove the
+     files that `configure' created (so you can compile the package for
+     a different kind of computer), type `make distclean'.  There is
+     also a `make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+Compilers and Options
+=====================
+
+   Some systems require unusual options for compilation or linking that
+the `configure' script does not know about.  You can give `configure'
+initial values for variables by setting them in the environment.  Using
+a Bourne-compatible shell, you can do that on the command line like
+this:
+     CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
+
+Or on systems that have the `env' program, you can do it like this:
+     env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
+
+Compiling For Multiple Architectures
+====================================
+
+   You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+   If you have to use a `make' that does not supports the `VPATH'
+variable, you have to compile the package for one architecture at a time
+in the source code directory.  After you have installed the package for
+one architecture, use `make distclean' before reconfiguring for another
+architecture.
+
+Installation Names
+==================
+
+   By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc.  You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like `--bindir=PATH' to specify different values for particular
+kinds of files.  Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+   Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+   There may be some features `configure' can not figure out
+automatically, but needs to determine by the type of host the package
+will run on.  Usually `configure' can figure that out, but if it prints
+a message saying it can not guess the host type, give it the
+`--host=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name with three fields:
+     CPU-COMPANY-SYSTEM
+
+See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the host type.
+
+   If you are building compiler tools for cross-compiling, you can also
+use the `--target=TYPE' option to select the type of system they will
+produce code for and the `--build=TYPE' option to select the type of
+system on which you are compiling the package.
+
+Sharing Defaults
+================
+
+   If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Operation Controls
+==================
+
+   `configure' recognizes the following options to control how it
+operates.
+
+`--cache-file=FILE'
+     Use and save the results of the tests in FILE instead of
+     `./config.cache'.  Set FILE to `/dev/null' to disable caching, for
+     debugging `configure'.
+
+`--help'
+     Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to `/dev/null' (any error
+     messages will still be shown).
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`--version'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`configure' also accepts some other, not widely useful, options.
+
diff --git a/libtar/README b/libtar/README
new file mode 100644
index 0000000..91f466c
--- /dev/null
+++ b/libtar/README
@@ -0,0 +1,121 @@
+libtar - C library for manipulating tar files
+======
+
+libtar is a library for manipulating tar files from within C programs.
+Here are some of its features:
+
+  * Handles both POSIX tar file format and the GNU extensions.
+  * API provides functions for easy use, such as tar_extract_all().
+  * Also provides functions for more granular use, such as 
+    tar_append_regfile().
+
+
+Installation
+------------
+
+To build libtar, you should be able to simply run these commands:
+
+   ./configure
+   make
+   make install
+
+
+Encap Package Support
+---------------------
+
+To build this software as an Encap package, you can pass the
+--enable-encap option to configure.  This will be automatically
+enabled if the epkg or mkencap programs are detected on the system,
+but can be overridden by the --disable-encap option.
+
+When building an Encap package, the configure script will automatically
+adjust the installation prefix to use an appropriate Encap package
+directory.  It does this using a heuristic algorithm which examines the
+values of the ${ENCAP_SOURCE} and ${ENCAP_TARGET} environment variables
+and the argument to configure's --prefix option.
+
+If mkencap was detected on the system, it will be automatically run during
+"make install".  By default, epkg will also be run, but this can be
+inhibited with the --disable-epkg-install configure option.
+
+For information on the Encap package management system, see the WSG
+Encap Archive:
+
+   http://www.encap.org/
+
+
+zlib Support
+------------
+
+The configure script will attempt to find the zlib library on your system
+for use with the libtar driver program.  The zlib package is available from:
+
+   http://www.gzip.org/zlib/
+
+If zlib is installed on your system, but you do not wish to use it,
+specify the --without-zlib option when you invoke configure.
+
+
+More Information
+----------------
+
+For documentation of the libtar API, see the enclosed manpages.  For more
+information on the libtar package, see:
+
+   http://www-dev.cites.uiuc.edu/libtar/
+
+Source code for the latest version of libtar will be available there, as
+well as Encap binary distributions for many common platforms.
+
+
+Supported Platforms
+-------------------
+
+I develop and test libtar on the following platforms:
+
+   AIX 4.3.3 and 5.1
+   HP-UX 11.00
+   IRIX 6.5
+   RedHat Linux 7.2
+   Solaris 8 and 9
+
+It should also build on the following platforms, but I do not actively
+support them:
+
+   AIX 3.2.5
+   AIX 4.2.1
+   Cygwin
+   FreeBSD
+   HP-UX 10.20
+   Linux/libc5
+   OpenBSD
+   Solaris 2.5
+   Solaris 2.6
+   Solaris 7
+
+If you successfully build libtar on another platform, please email me a
+patch and/or configuration information.
+
+
+Compatibility Code
+------------------
+
+libtar depends on some library calls which are not available or not
+usable on some platforms.  To accomodate these systems, I've included
+a version of these calls in the compat subdirectory.
+
+I've slightly modified these functions for integration into this source
+tree, but the functionality has not been modified from the original
+source.  Please note that while this code should work for you, I didn't
+write it, so please don't send me bug reports on it.
+
+
+Author
+------
+
+Feedback and bug reports are welcome.
+
+Mark D. Roth <roth@uiuc.edu>
+Campus Information Technologies and Educational Services
+University of Illinois at Urbana-Champaign
+
diff --git a/libtar/TODO b/libtar/TODO
new file mode 100644
index 0000000..f2f859f
--- /dev/null
+++ b/libtar/TODO
@@ -0,0 +1,21 @@
+Functionality:
+--------------
+
+* add list mode to allow nodes to be inserted in any arbitrary location
+* add "*_hash_iterate()" function
+* add flags argument to *_list_del() that allows the listptr to be set
+  to the previous or next element
+* add a generic pointer type to replace *_listptr_t and *_hashptr_t ???
+
+
+Code Cleanup:
+-------------
+
+* rename functions:
+	*_list_next	=> *_listptr_next()
+	*_list_prev	=> *_listptr_prev()
+	*_hash_next	=> *_hashptr_next()
+* start using "*_list_t" and "*_hash_t" instead of "*_list_t *" and
+  "*_hash_t *" ?
+* add prefixes to structure member field names
+
diff --git a/libtar/android_utils.c b/libtar/android_utils.c
new file mode 100644
index 0000000..4aa3425
--- /dev/null
+++ b/libtar/android_utils.c
@@ -0,0 +1,123 @@
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <string.h>
+#include <linux/limits.h>
+#include <errno.h>
+
+#include "libtar.h"
+#include "android_utils.h"
+
+/* This code may come in handy later if we ever need to extend to storing more user.inode_* xattrs
+#define USER_INODE_SEPARATOR "\0"
+#define ANDROID_USER_INODE_XATTR_PREFIX "user.inode_"
+#define ANDROID_USER_INODE_XATTR_PREFIX_LEN strlen(ANDROID_USER_INODE_XATTR_PREFIX)
+
+char* scan_xattrs_for_user_inode (const char *realname, size_t *return_size)
+{
+	ssize_t size;
+	char xattr_list[PATH_MAX];
+	size = listxattr(realname, xattr_list, sizeof(xattr_list));
+	if (size < 0) {
+		return NULL;
+	}
+	char xattr[T_BLOCKSIZE];
+	char *xattr_ptr;
+	int first = 1;
+	*return_size = 0;
+	for (int i = 0; i < size; i++) {
+		if (xattr_list[i]) {
+			xattr_ptr = xattr_list + i;
+			if (strncmp(xattr_ptr, ANDROID_USER_INODE_XATTR_PREFIX, ANDROID_USER_INODE_XATTR_PREFIX_LEN) == 0) {
+				// found a user.inode xattr
+				if (first) {
+					first = 0;
+					strcpy(xattr, xattr_ptr);
+					*return_size = strlen(xattr_ptr);
+				} else {
+					char *ptr = xattr + *return_size;
+					snprintf(ptr, T_BLOCKSIZE - *return_size, "%s", xattr_ptr);
+					*return_size += strlen(xattr_ptr) + 1; // + 1 for null separator
+					if (*return_size >= T_BLOCKSIZE) {
+						*return_size = 0;
+						return NULL;
+					}
+				}
+			}
+			i += strlen(xattr_ptr);
+		}
+	}
+	if (first)
+		return NULL;
+	return strdup(xattr);
+}*/
+
+/*
+ * get_path_inode and write_path_inode were taken from frameworks/native/cmds/installd/utils.cpp
+ */
+
+static int get_path_inode(const char* path, ino_t *inode) {
+	struct stat buf;
+	memset(&buf, 0, sizeof(buf));
+	if (stat(path, &buf) != 0) {
+		printf("failed to stat %s\n", path);
+		return -1;
+	}
+	*inode = buf.st_ino;
+	return 0;
+}
+
+/**
+ * Write the inode of a specific child file into the given xattr on the
+ * parent directory. This allows you to find the child later, even if its
+ * name is encrypted.
+ */
+int write_path_inode(const char* parent, const char* name, const char* inode_xattr) {
+	ino_t inode = 0;
+	uint64_t inode_raw = 0;
+	char path[PATH_MAX];
+	snprintf(path, PATH_MAX, "%s/%s", parent, name);
+
+	if (mkdirhier(path) == -1) {
+		printf("failed to mkdirhier for %s\n", path);
+		return -1;
+	}
+
+	if (get_path_inode(path, &inode) != 0) {
+		return -1;
+	}
+
+	// Check to see if already set correctly
+	if (getxattr(parent, inode_xattr, &inode_raw, sizeof(inode_raw)) == sizeof(inode_raw)) {
+		if (inode_raw == inode) {
+			// Already set correctly; skip writing
+			return 0;
+		}
+	}
+
+	inode_raw = inode;
+	printf("setting %s on %s pointing to %s\n", inode_xattr, parent, path);
+	if (setxattr(parent, inode_xattr, &inode_raw, sizeof(inode_raw), 0) != 0 && errno != EOPNOTSUPP) {
+		printf("Failed to write xattr %s at %s (%s)\n", inode_xattr, parent, strerror(errno));
+		return -1;
+	}
+	return 0;
+}
diff --git a/libtar/android_utils.h b/libtar/android_utils.h
new file mode 100644
index 0000000..72cb928
--- /dev/null
+++ b/libtar/android_utils.h
@@ -0,0 +1,22 @@
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef LIBTAR_ANDROID_UTILS_H
+#define LIBTAR_ANDROID_UTILS_H
+
+int write_path_inode(const char* parent, const char* name, const char* inode_xattr);
+
+#endif
diff --git a/libtar/append.c b/libtar/append.c
new file mode 100755
index 0000000..7557158
--- /dev/null
+++ b/libtar/append.c
@@ -0,0 +1,468 @@
+/*
+**  Copyright 1998-2003 University of Illinois Board of Trustees
+**  Copyright 1998-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  append.c - libtar code to append files to a tar archive
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <internal.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <stdbool.h>
+
+#include <sys/capability.h>
+#include <sys/xattr.h>
+#include <linux/fs.h>
+#include <linux/xattr.h>
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <selinux/selinux.h>
+
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
+#endif
+
+#include "android_utils.h"
+
+#ifdef TW_LIBTAR_DEBUG
+#define DEBUG 1
+#endif
+
+struct tar_dev
+{
+	dev_t td_dev;
+	libtar_hash_t *td_h;
+};
+typedef struct tar_dev tar_dev_t;
+
+struct tar_ino
+{
+	ino_t ti_ino;
+	char ti_name[MAXPATHLEN];
+};
+typedef struct tar_ino tar_ino_t;
+
+
+/* free memory associated with a tar_dev_t */
+void
+tar_dev_free(tar_dev_t *tdp)
+{
+	libtar_hash_free(tdp->td_h, free);
+	free(tdp);
+}
+
+
+/* appends a file to the tar archive */
+int
+tar_append_file(TAR *t, const char *realname, const char *savename)
+{
+	struct stat s;
+	int i;
+	libtar_hashptr_t hp;
+	tar_dev_t *td = NULL;
+	tar_ino_t *ti = NULL;
+	char path[MAXPATHLEN];
+
+#ifdef DEBUG
+	printf("==> tar_append_file(TAR=0x%p (\"%s\"), realname=\"%s\", "
+	       "savename=\"%s\")\n", (void*) t, t->pathname, realname,
+	       (savename ? savename : "[NULL]"));
+#endif
+
+	if (lstat(realname, &s) != 0)
+	{
+#ifdef DEBUG
+		perror("lstat()");
+#endif
+		return -1;
+	}
+
+	/* set header block */
+#ifdef DEBUG
+	puts("tar_append_file(): setting header block...");
+#endif
+	memset(&(t->th_buf), 0, sizeof(struct tar_header));
+	th_set_from_stat(t, &s);
+
+	/* set the header path */
+#ifdef DEBUG
+	puts("tar_append_file(): setting header path...");
+#endif
+	th_set_path(t, (savename ? savename : realname));
+
+	/* get selinux context */
+	if (t->options & TAR_STORE_SELINUX)
+	{
+		if (t->th_buf.selinux_context != NULL)
+		{
+			free(t->th_buf.selinux_context);
+			t->th_buf.selinux_context = NULL;
+		}
+
+		char* selinux_context = NULL;
+		if (lgetfilecon(realname, &selinux_context) >= 0)
+		{
+			t->th_buf.selinux_context = strdup(selinux_context);
+			printf("  ==> set selinux context: %s\n", selinux_context);
+			freecon(selinux_context);
+		}
+		else
+		{
+#ifdef DEBUG
+			perror("Failed to get selinux context");
+#endif
+		}
+	}
+
+#ifdef USE_FSCRYPT
+	if (TH_ISDIR(t) && t->options & TAR_STORE_FSCRYPT_POL)
+	{
+		if (t->th_buf.fep != NULL)
+		{
+			free(t->th_buf.fep);
+			t->th_buf.fep = NULL;
+		}
+#ifdef USE_FSCRYPT_POLICY_V1
+		t->th_buf.fep = (struct fscrypt_policy_v1 *)malloc(sizeof(struct fscrypt_policy_v1));
+#else
+		t->th_buf.fep = (struct fscrypt_policy_v2 *)malloc(sizeof(struct fscrypt_policy_v2));
+#endif
+		if (!t->th_buf.fep) {
+			printf("malloc fs_encryption_policy\n");
+			return -1;
+		}
+
+		if (fscrypt_policy_get_struct(realname, t->th_buf.fep)) {
+#ifdef USE_FSCRYPT_POLICY_V1
+			uint8_t tar_policy[FS_KEY_DESCRIPTOR_SIZE];
+			char policy_hex[FS_KEY_DESCRIPTOR_SIZE_HEX];
+#else
+			uint8_t tar_policy[FSCRYPT_KEY_IDENTIFIER_SIZE];
+			char policy_hex[FSCRYPT_KEY_IDENTIFIER_HEX_SIZE];
+#endif
+			memset(tar_policy, 0, sizeof(tar_policy));
+#ifdef USE_FSCRYPT_POLICY_V1
+			bytes_to_hex(t->th_buf.fep->master_key_descriptor, FS_KEY_DESCRIPTOR_SIZE, policy_hex);
+#else
+			bytes_to_hex(t->th_buf.fep->master_key_identifier, FSCRYPT_KEY_IDENTIFIER_SIZE, policy_hex);
+#endif
+			if (lookup_ref_key(t->th_buf.fep,  &tar_policy[0])) {
+				if (strncmp((char *) tar_policy, USER_CE_FSCRYPT_POLICY, sizeof(USER_CE_FSCRYPT_POLICY) - 1) == 0 
+				|| strncmp((char *) tar_policy, USER_DE_FSCRYPT_POLICY, sizeof(USER_DE_FSCRYPT_POLICY) - 1) == 0 
+				|| strncmp((char *) tar_policy, SYSTEM_DE_FSCRYPT_POLICY, sizeof(SYSTEM_DE_FSCRYPT_POLICY)) == 0) {
+#ifdef USE_FSCRYPT_POLICY_V1
+					memcpy(t->th_buf.fep->master_key_descriptor, tar_policy, FS_KEY_DESCRIPTOR_SIZE);
+					printf("found fscrypt policy '%s' - '%s' - '%s'\n", realname, t->th_buf.fep->master_key_descriptor, policy_hex);
+#else
+					memcpy(t->th_buf.fep->master_key_identifier, tar_policy, FSCRYPT_KEY_IDENTIFIER_SIZE);
+					printf("found fscrypt policy '%s' - '%s' - '%s'\n", realname, t->th_buf.fep->master_key_identifier, policy_hex);
+#endif
+				} else {
+					printf("failed to match fscrypt tar policy for '%s' - '%s'\n", realname, policy_hex);
+					free(t->th_buf.fep);
+					t->th_buf.fep = NULL;
+				}
+			} else {
+				printf("failed to lookup fscrypt tar policy for '%s' - '%s'\n", realname, policy_hex);
+				free(t->th_buf.fep);
+				t->th_buf.fep = NULL;
+			}
+		}
+		else {
+			// no policy found, but this is not an error as not all dirs will have a policy
+			free(t->th_buf.fep);
+			t->th_buf.fep = NULL;
+		}
+	}
+#endif
+
+	/* get posix file capabilities */
+	if (TH_ISREG(t) && t->options & TAR_STORE_POSIX_CAP)
+	{
+		if (t->th_buf.has_cap_data)
+		{
+			memset(&t->th_buf.cap_data, 0, sizeof(struct vfs_cap_data));
+			t->th_buf.has_cap_data = 0;
+		}
+
+		if (getxattr(realname, XATTR_NAME_CAPS, &t->th_buf.cap_data, sizeof(struct vfs_cap_data)) >= 0)
+		{
+			t->th_buf.has_cap_data = 1;
+#if 1 //def DEBUG
+			print_caps(&t->th_buf.cap_data);
+#endif
+		}
+	}
+
+	/* get android user.default xattr */
+	if (TH_ISDIR(t) && t->options & TAR_STORE_ANDROID_USER_XATTR)
+	{
+		if (getxattr(realname, "user.default", NULL, 0) >= 0)
+		{
+			t->th_buf.has_user_default = 1;
+#if 1 //def DEBUG
+			printf("storing xattr user.default\n");
+#endif
+		}
+		if (getxattr(realname, "user.inode_cache", NULL, 0) >= 0)
+		{
+			t->th_buf.has_user_cache = 1;
+#if 1 //def DEBUG
+			printf("storing xattr user.inode_cache\n");
+#endif
+		}
+		if (getxattr(realname, "user.inode_code_cache", NULL, 0) >= 0)
+		{
+			t->th_buf.has_user_code_cache = 1;
+#if 1 //def DEBUG
+			printf("storing xattr user.inode_code_cache\n");
+#endif
+		}
+	}
+
+	/* check if it's a hardlink */
+#ifdef DEBUG
+	puts("tar_append_file(): checking inode cache for hardlink...");
+#endif
+	libtar_hashptr_reset(&hp);
+	if (libtar_hash_getkey(t->h, &hp, &(s.st_dev),
+			       (libtar_matchfunc_t)dev_match) != 0)
+		td = (tar_dev_t *)libtar_hashptr_data(&hp);
+	else
+	{
+#ifdef DEBUG
+		printf("+++ adding hash for device (0x%x, 0x%x)...\n",
+		       major(s.st_dev), minor(s.st_dev));
+#endif
+		td = (tar_dev_t *)calloc(1, sizeof(tar_dev_t));
+		td->td_dev = s.st_dev;
+		td->td_h = libtar_hash_new(256, (libtar_hashfunc_t)ino_hash);
+		if (td->td_h == NULL)
+			return -1;
+		if (libtar_hash_add(t->h, td) == -1)
+			return -1;
+	}
+	libtar_hashptr_reset(&hp);
+	if (libtar_hash_getkey(td->td_h, &hp, &(s.st_ino),
+			       (libtar_matchfunc_t)ino_match) != 0)
+	{
+		ti = (tar_ino_t *)libtar_hashptr_data(&hp);
+#ifdef DEBUG
+		printf("    tar_append_file(): encoding hard link \"%s\" "
+		       "to \"%s\"...\n", realname, ti->ti_name);
+#endif
+		t->th_buf.typeflag = LNKTYPE;
+		th_set_link(t, ti->ti_name);
+	}
+	else
+	{
+#ifdef DEBUG
+		printf("+++ adding entry: device (0x%d,0x%x), inode %lu"
+		       "(\"%s\")...\n", major(s.st_dev), minor(s.st_dev),
+		       (unsigned long) s.st_ino, realname);
+#endif
+		ti = (tar_ino_t *)calloc(1, sizeof(tar_ino_t));
+		if (ti == NULL)
+			return -1;
+		ti->ti_ino = s.st_ino;
+		snprintf(ti->ti_name, sizeof(ti->ti_name), "%s",
+			 savename ? savename : realname);
+		libtar_hash_add(td->td_h, ti);
+	}
+
+	/* check if it's a symlink */
+	if (TH_ISSYM(t))
+	{
+		i = readlink(realname, path, sizeof(path));
+		if (i == -1)
+			return -1;
+		if (i >= MAXPATHLEN)
+			i = MAXPATHLEN - 1;
+		path[i] = '\0';
+#ifdef DEBUG
+		printf("tar_append_file(): encoding symlink \"%s\" -> "
+		       "\"%s\"...\n", realname, path);
+#endif
+		th_set_link(t, path);
+	}
+
+	/* print file info */
+	if (t->options & TAR_VERBOSE)
+		printf("%s\n", th_get_pathname(t));
+
+#ifdef DEBUG
+	puts("tar_append_file(): writing header");
+#endif
+	/* write header */
+	if (th_write(t) != 0)
+	{
+#ifdef DEBUG
+		printf("t->fd = %ld\n", t->fd);
+#endif
+		return -1;
+	}
+#ifdef DEBUG
+	puts("tar_append_file(): back from th_write()");
+#endif
+
+	/* if it's a regular file, write the contents as well */
+	if (TH_ISREG(t) && tar_append_regfile(t, realname) != 0)
+		return -1;
+
+	return 0;
+}
+
+
+/* write EOF indicator */
+int
+tar_append_eof(TAR *t)
+{
+	int i, j;
+	char block[T_BLOCKSIZE];
+
+	memset(&block, 0, T_BLOCKSIZE);
+	for (j = 0; j < 2; j++)
+	{
+		i = tar_block_write(t, &block);
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+
+/* add file contents to a tarchive */
+int
+tar_append_regfile(TAR *t, const char *realname)
+{
+	char block[T_BLOCKSIZE];
+	int filefd;
+	int64_t i, size;
+	ssize_t j;
+	int rv = -1;
+
+#if defined(O_BINARY)
+	filefd = open(realname, O_RDONLY|O_BINARY);
+#else
+	filefd = open(realname, O_RDONLY);
+#endif
+	if (filefd == -1)
+	{
+#ifdef DEBUG
+		perror("open()");
+#endif
+		return -1;
+	}
+
+	size = th_get_size(t);
+	for (i = size; i > T_BLOCKSIZE; i -= T_BLOCKSIZE)
+	{
+		j = read(filefd, &block, T_BLOCKSIZE);
+		if (j != T_BLOCKSIZE)
+		{
+			if (j != -1)
+				errno = EINVAL;
+			goto fail;
+		}
+		if (tar_block_write(t, &block) == -1)
+			goto fail;
+	}
+
+	if (i > 0)
+	{
+		j = read(filefd, &block, i);
+		if (j == -1)
+			goto fail;
+		memset(&(block[i]), 0, T_BLOCKSIZE - i);
+		if (tar_block_write(t, &block) == -1)
+			goto fail;
+	}
+
+	/* success! */
+	rv = 0;
+fail:
+	close(filefd);
+
+	return rv;
+}
+
+
+/* add file contents to a tarchive */
+int
+tar_append_file_contents(TAR *t, const char *savename, mode_t mode,
+                         uid_t uid, gid_t gid, void *buf, size_t len)
+{
+	struct stat st;
+
+	memset(&st, 0, sizeof(st));
+	st.st_mode = S_IFREG | mode;
+	st.st_uid = uid;
+	st.st_gid = gid;
+	st.st_mtime = time(NULL);
+	st.st_size = len;
+
+	th_set_from_stat(t, &st);
+	th_set_path(t, savename);
+
+	/* write header */
+	if (th_write(t) != 0)
+	{
+#ifdef DEBUG
+		fprintf(stderr, "tar_append_file_contents(): could not write header, t->fd = %ld\n", t->fd);
+#endif
+		return -1;
+	}
+
+	return tar_append_buffer(t, buf, len);
+}
+
+int
+tar_append_buffer(TAR *t, void *buf, size_t len)
+{
+	char block[T_BLOCKSIZE];
+	int i;
+	size_t size = len;
+
+	for (i = size; i > T_BLOCKSIZE; i -= T_BLOCKSIZE)
+	{
+		if (tar_block_write(t, buf) == -1)
+			return -1;
+		buf = (char *)buf + T_BLOCKSIZE;
+	}
+
+	if (i > 0)
+	{
+		memcpy(block, buf, i);
+		memset(&(block[i]), 0, T_BLOCKSIZE - i);
+		if (tar_block_write(t, &block) == -1)
+			return -1;
+	}
+
+	return 0;
+}
+
diff --git a/libtar/basename.c b/libtar/basename.c
new file mode 100644
index 0000000..3f4d315
--- /dev/null
+++ b/libtar/basename.c
@@ -0,0 +1,74 @@
+/*	$OpenBSD: basename.c,v 1.4 1999/05/30 17:10:30 espie Exp $	*/
+
+/*
+ * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef lint
+//static char rcsid[] = "$OpenBSD: basename.c,v 1.4 1999/05/30 17:10:30 espie Exp $";
+#endif /* not lint */
+
+#include <errno.h>
+#include <string.h>
+#include <sys/param.h>
+
+char *
+openbsd_basename(path)
+	const char *path;
+{
+	static char bname[MAXPATHLEN];
+	register const char *endp, *startp;
+
+	/* Empty or NULL string gets treated as "." */
+	if (path == NULL || *path == '\0') {
+		(void)strcpy(bname, ".");
+		return(bname);
+	}
+
+	/* Strip trailing slashes */
+	endp = path + strlen(path) - 1;
+	while (endp > path && *endp == '/')
+		endp--;
+
+	/* All slashes becomes "/" */
+	if (endp == path && *endp == '/') {
+		(void)strcpy(bname, "/");
+		return(bname);
+	}
+
+	/* Find the start of the base */
+	startp = endp;
+	while (startp > path && *(startp - 1) != '/')
+		startp--;
+
+	if (endp - startp + 1 > (int)sizeof(bname)) {
+		errno = ENAMETOOLONG;
+		return(NULL);
+	}
+	(void)strncpy(bname, startp, endp - startp + 1);
+	bname[endp - startp + 1] = '\0';
+	return(bname);
+}
diff --git a/libtar/block.c b/libtar/block.c
new file mode 100755
index 0000000..b46d55a
--- /dev/null
+++ b/libtar/block.c
@@ -0,0 +1,758 @@
+/*
+**  Copyright 1998-2003 University of Illinois Board of Trustees
+**  Copyright 1998-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  block.c - libtar code to handle tar archive header blocks
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <internal.h>
+#include <errno.h>
+
+#ifdef STDC_HEADERS
+# include <string.h>
+# include <stdlib.h>
+#endif
+
+#ifdef TW_LIBTAR_DEBUG
+#define DEBUG 1
+#endif
+
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
+#endif
+
+#define BIT_ISSET(bitmask, bit) ((bitmask) & (bit))
+
+// Used to identify selinux_context in extended ('x')
+// metadata. From RedHat implementation.
+#define SELINUX_TAG "RHT.security.selinux="
+#define SELINUX_TAG_LEN strlen(SELINUX_TAG)
+
+// Used to identify fscrypt_policy in extended ('x')
+#define FSCRYPT_TAG "TWRP.security.fscrypt="
+#define FSCRYPT_TAG_LEN strlen(FSCRYPT_TAG)
+
+// Used to identify Posix capabilities in extended ('x')
+#define CAPABILITIES_TAG "SCHILY.xattr.security.capability="
+#define CAPABILITIES_TAG_LEN strlen(CAPABILITIES_TAG)
+
+// Used to identify Android user.default xattr in extended ('x')
+#define ANDROID_USER_DEFAULT_TAG "ANDROID.user.default"
+#define ANDROID_USER_DEFAULT_TAG_LEN strlen(ANDROID_USER_DEFAULT_TAG)
+
+// Used to identify Android user.inode_cache xattr in extended ('x')
+#define ANDROID_USER_CACHE_TAG "ANDROID.user.inode_cache"
+#define ANDROID_USER_CACHE_TAG_LEN strlen(ANDROID_USER_CACHE_TAG)
+
+// Used to identify Android user.inode_code_cache xattr in extended ('x')
+#define ANDROID_USER_CODE_CACHE_TAG "ANDROID.user.inode_code_cache"
+#define ANDROID_USER_CODE_CACHE_TAG_LEN strlen(ANDROID_USER_CODE_CACHE_TAG)
+
+/* read a header block */
+/* FIXME: the return value of this function should match the return value
+	  of tar_block_read(), which is a macro which references a prototype
+	  that returns a ssize_t.  So far, this is safe, since tar_block_read()
+	  only ever reads 512 (T_BLOCKSIZE) bytes at a time, so any difference
+	  in size of ssize_t and int is of negligible risk.  BUT, if
+	  T_BLOCKSIZE ever changes, or ever becomes a variable parameter
+	  controllable by the user, all the code that calls it,
+	  including this function and all code that calls it, should be
+	  fixed for security reasons.
+	  Thanks to Chris Palmer for the critique.
+*/
+int
+th_read_internal(TAR *t)
+{
+	int i;
+	int num_zero_blocks = 0;
+
+#ifdef DEBUG
+	printf("==> th_read_internal(TAR=\"%s\")\n", t->pathname);
+#endif
+
+	while ((i = tar_block_read(t, &(t->th_buf))) == T_BLOCKSIZE)
+	{
+		/* two all-zero blocks mark EOF */
+		if (t->th_buf.name[0] == '\0')
+		{
+			num_zero_blocks++;
+			if (!BIT_ISSET(t->options, TAR_IGNORE_EOT)
+			    && num_zero_blocks >= 2)
+				return 0;	/* EOF */
+			else
+				continue;
+		}
+
+		/* verify magic and version */
+		if (BIT_ISSET(t->options, TAR_CHECK_MAGIC)
+		    && strncmp(t->th_buf.magic, TMAGIC, TMAGLEN - 1) != 0)
+		{
+#ifdef DEBUG
+			puts("!!! unknown magic value in tar header");
+#endif
+			return -2;
+		}
+
+		if (BIT_ISSET(t->options, TAR_CHECK_VERSION)
+		    && strncmp(t->th_buf.version, TVERSION, TVERSLEN) != 0)
+		{
+#ifdef DEBUG
+			puts("!!! unknown version value in tar header");
+#endif
+			return -2;
+		}
+
+		/* check chksum */
+		if (!BIT_ISSET(t->options, TAR_IGNORE_CRC)
+		    && !th_crc_ok(t))
+		{
+#ifdef DEBUG
+			puts("!!! tar header checksum error");
+#endif
+			return -2;
+		}
+
+		break;
+	}
+
+#ifdef DEBUG
+	printf("<== th_read_internal(): returning %d\n", i);
+#endif
+	return i;
+}
+
+
+/* wrapper function for th_read_internal() to handle GNU extensions */
+int
+th_read(TAR *t)
+{
+	int i;
+	size_t sz, j, blocks;
+	char *ptr;
+
+#ifdef DEBUG
+	printf("==> th_read(t=0x%p)\n", (void *)t);
+#endif
+
+	if (t->th_buf.gnu_longname != NULL)
+		free(t->th_buf.gnu_longname);
+	if (t->th_buf.gnu_longlink != NULL)
+		free(t->th_buf.gnu_longlink);
+	if (t->th_buf.selinux_context != NULL)
+		free(t->th_buf.selinux_context);
+
+#ifdef USE_FSCRYPT
+	if (t->th_buf.fep != NULL)
+		free(t->th_buf.fep);
+#endif
+
+	if (t->th_buf.has_cap_data)
+	{
+		memset(&t->th_buf.cap_data, 0, sizeof(struct vfs_cap_data));
+		t->th_buf.has_cap_data = 0;
+	}
+	t->th_buf.has_user_default = 0;
+	t->th_buf.has_user_cache = 0;
+	t->th_buf.has_user_code_cache = 0;
+
+	memset(&(t->th_buf), 0, sizeof(struct tar_header));
+
+	i = th_read_internal(t);
+	if (i == 0)
+		return 1;
+	else if (i != T_BLOCKSIZE)
+	{
+		if (i != -1)
+			errno = EINVAL;
+		return -1;
+	}
+
+	/* check for GNU long link extention */
+	if (TH_ISLONGLINK(t))
+	{
+		sz = th_get_size(t);
+		blocks = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0);
+		if (blocks > ((size_t)-1 / T_BLOCKSIZE))
+		{
+			errno = E2BIG;
+			return -1;
+		}
+#ifdef DEBUG
+		printf("    th_read(): GNU long linkname detected "
+		       "(%zu bytes, %zu blocks)\n", sz, blocks);
+#endif
+		t->th_buf.gnu_longlink = (char *)malloc(blocks * T_BLOCKSIZE);
+		if (t->th_buf.gnu_longlink == NULL)
+			return -1;
+
+		for (j = 0, ptr = t->th_buf.gnu_longlink; j < blocks;
+		     j++, ptr += T_BLOCKSIZE)
+		{
+#ifdef DEBUG
+			printf("    th_read(): reading long linkname "
+			       "(%zu blocks left, ptr == %p)\n", blocks-j, (void *) ptr);
+#endif
+			i = tar_block_read(t, ptr);
+			if (i != T_BLOCKSIZE)
+			{
+				if (i != -1)
+					errno = EINVAL;
+				return -1;
+			}
+#ifdef DEBUG
+			printf("    th_read(): read block == \"%s\"\n", ptr);
+#endif
+		}
+#ifdef DEBUG
+		printf("    th_read(): t->th_buf.gnu_longlink == \"%s\"\n",
+		       t->th_buf.gnu_longlink);
+#endif
+
+		i = th_read_internal(t);
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+	}
+
+	/* check for GNU long name extention */
+	if (TH_ISLONGNAME(t))
+	{
+		sz = th_get_size(t);
+		blocks = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0);
+		if (blocks > ((size_t)-1 / T_BLOCKSIZE))
+		{
+			errno = E2BIG;
+			return -1;
+		}
+#ifdef DEBUG
+		printf("    th_read(): GNU long filename detected "
+		       "(%zu bytes, %zu blocks)\n", sz, blocks);
+#endif
+		t->th_buf.gnu_longname = (char *)malloc(blocks * T_BLOCKSIZE);
+		if (t->th_buf.gnu_longname == NULL)
+			return -1;
+
+		for (j = 0, ptr = t->th_buf.gnu_longname; j < blocks;
+		     j++, ptr += T_BLOCKSIZE)
+		{
+#ifdef DEBUG
+			printf("    th_read(): reading long filename "
+			       "(%zu blocks left, ptr == %p)\n", blocks-j, (void *) ptr);
+#endif
+			i = tar_block_read(t, ptr);
+			if (i != T_BLOCKSIZE)
+			{
+				if (i != -1)
+					errno = EINVAL;
+				return -1;
+			}
+#ifdef DEBUG
+			printf("    th_read(): read block == \"%s\"\n", ptr);
+#endif
+		}
+#ifdef DEBUG
+		printf("    th_read(): t->th_buf.gnu_longname == \"%s\"\n",
+		       t->th_buf.gnu_longname);
+#endif
+
+		i = th_read_internal(t);
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+	}
+
+	// Extended headers (selinux contexts, posix file capabilities and encryption policies)
+	while(TH_ISEXTHEADER(t) || TH_ISPOLHEADER(t))
+	{
+		sz = th_get_size(t);
+
+		if(sz >= T_BLOCKSIZE) // Not supported
+		{
+#ifdef DEBUG
+			printf("    th_read(): Extended header is too long!\n");
+#endif
+		}
+		else
+		{
+			char buf[T_BLOCKSIZE];
+			i = tar_block_read(t, buf);
+			if (i != T_BLOCKSIZE)
+			{
+				if (i != -1)
+					errno = EINVAL;
+				return -1;
+			}
+
+			// To be sure
+			buf[T_BLOCKSIZE-1] = 0;
+
+			int len = strlen(buf);
+			// posix capabilities
+			char *start = strstr(buf, CAPABILITIES_TAG);
+			if (start && start+CAPABILITIES_TAG_LEN < buf+len)
+			{
+				start += CAPABILITIES_TAG_LEN;
+				memcpy(&t->th_buf.cap_data, start, sizeof(struct vfs_cap_data));
+				t->th_buf.has_cap_data = 1;
+#ifdef DEBUG
+				printf("    th_read(): Posix capabilities detected\n");
+#endif
+			} // end posix capabilities
+			// selinux contexts
+			start = strstr(buf, SELINUX_TAG);
+			if (start && start+SELINUX_TAG_LEN < buf+len)
+			{
+				start += SELINUX_TAG_LEN;
+				char *end = strchr(start, '\n');
+				if(end)
+				{
+					t->th_buf.selinux_context = strndup(start, end-start);
+#ifdef DEBUG
+					printf("    th_read(): SELinux context xattr detected: %s\n", t->th_buf.selinux_context);
+#endif
+				}
+			} // end selinux contexts
+			// android user.default xattr
+			start = strstr(buf, ANDROID_USER_DEFAULT_TAG);
+			if (start)
+			{
+				t->th_buf.has_user_default = 1;
+#ifdef DEBUG
+				printf("    th_read(): android user.default xattr detected\n");
+#endif
+			} // end android user.default xattr
+			// android user.inode_cache xattr
+			start = strstr(buf, ANDROID_USER_CACHE_TAG);
+			if (start)
+			{
+				t->th_buf.has_user_cache = 1;
+#ifdef DEBUG
+				printf("    th_read(): android user.inode_cache xattr detected\n");
+#endif
+			} // end android user.inode_cache xattr
+			// android user.inode_code_cache xattr
+			start = strstr(buf, ANDROID_USER_CODE_CACHE_TAG);
+			if (start)
+			{
+				t->th_buf.has_user_code_cache = 1;
+#ifdef DEBUG
+				printf("    th_read(): android user.inode_code_cache xattr detected\n");
+#endif
+			} // end android user.inode_code_cache xattr
+
+#ifdef USE_FSCRYPT
+			start = strstr(buf, FSCRYPT_TAG);
+			if (start && start+FSCRYPT_TAG_LEN < buf+len) {
+#ifdef USE_FSCRYPT_POLICY_V1
+				t->th_buf.fep = (struct fscrypt_policy_v1*)malloc(sizeof(struct fscrypt_policy_v1));
+#else
+				t->th_buf.fep = (struct fscrypt_policy_v2*)malloc(sizeof(struct fscrypt_policy_v2));
+#endif
+				if (!t->th_buf.fep) {
+					printf("malloc failed for fscrypt policy\n");
+					return -1;
+				}
+				start += FSCRYPT_TAG_LEN;
+				if (*start == '0') {
+					start++;
+#ifdef USE_FSCRYPT_POLICY_V1
+					char *newline_check = start + sizeof(struct fscrypt_policy_v1);
+#else
+					char *newline_check = start + sizeof(struct fscrypt_policy_v2);
+#endif
+					if (*newline_check != '\n')
+						printf("did not find newline char in expected location, continuing anyway...\n");
+#ifdef USE_FSCRYPT_POLICY_V1
+					memcpy(t->th_buf.fep, start, sizeof(struct fscrypt_policy_v1));
+#else
+					memcpy(t->th_buf.fep, start, sizeof(struct fscrypt_policy_v2));
+#endif
+#ifdef DEBUG
+					printf("    th_read(): FSCrypt policy detected: %i %i %i %i %s\n",
+						(int)t->th_buf.fep->version,
+						(int)t->th_buf.fep->contents_encryption_mode,
+						(int)t->th_buf.fep->filenames_encryption_mode,
+						(int)t->th_buf.fep->flags,
+#ifdef USE_FSCRYPT_POLICY_V1
+						t->th_buf.fep->master_key_descriptor);
+#else
+						t->th_buf.fep->master_key_identifier);
+#endif
+#endif
+				}
+				else {
+					printf("     invalid fscrypt header found\n");
+				}
+			}
+#endif // USE_FSCRYPT
+		}
+
+		i = th_read_internal(t);
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/* write an extended block */
+static int
+th_write_extended(TAR *t, char* buf, uint64_t sz)
+{
+	char type2;
+	uint64_t sz2;
+	int i;
+
+	/* save old size and type */
+	type2 = t->th_buf.typeflag;
+	sz2 = th_get_size(t);
+
+	/* write out initial header block with fake size and type */
+	t->th_buf.typeflag = TH_EXT_TYPE;
+
+	if(sz >= T_BLOCKSIZE) // impossible
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	th_set_size(t, sz);
+	th_finish(t);
+	i = tar_block_write(t, &(t->th_buf));
+	if (i != T_BLOCKSIZE)
+	{
+		if (i != -1)
+			errno = EINVAL;
+		return -1;
+	}
+
+	i = tar_block_write(t, buf);
+	if (i != T_BLOCKSIZE)
+	{
+		if (i != -1)
+			errno = EINVAL;
+		return -1;
+	}
+
+	/* reset type and size to original values */
+	t->th_buf.typeflag = type2;
+	th_set_size(t, sz2);
+	memset(buf, 0, T_BLOCKSIZE);
+	return 0;
+}
+
+/* write a header block */
+int
+th_write(TAR *t)
+{
+	int i, j;
+	char type2;
+	uint64_t sz, sz2, total_sz = 0;
+	char *ptr;
+	char buf[T_BLOCKSIZE];
+
+#ifdef DEBUG
+	printf("==> th_write(TAR=\"%s\")\n", t->pathname);
+	th_print(t);
+#endif
+
+	if ((t->options & TAR_GNU) && t->th_buf.gnu_longlink != NULL)
+	{
+#ifdef DEBUG
+		printf("th_write(): using gnu_longlink (\"%s\")\n",
+		       t->th_buf.gnu_longlink);
+#endif
+		/* save old size and type */
+		type2 = t->th_buf.typeflag;
+		sz2 = th_get_size(t);
+
+		/* write out initial header block with fake size and type */
+		t->th_buf.typeflag = GNU_LONGLINK_TYPE;
+		sz = strlen(t->th_buf.gnu_longlink);
+		th_set_size(t, sz);
+		th_finish(t);
+		i = tar_block_write(t, &(t->th_buf));
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+
+		/* write out extra blocks containing long name */
+		for (j = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0),
+		     ptr = t->th_buf.gnu_longlink; j > 1;
+		     j--, ptr += T_BLOCKSIZE)
+		{
+			i = tar_block_write(t, ptr);
+			if (i != T_BLOCKSIZE)
+			{
+				if (i != -1)
+					errno = EINVAL;
+				return -1;
+			}
+		}
+		memset(buf, 0, T_BLOCKSIZE);
+		strncpy(buf, ptr, T_BLOCKSIZE);
+		i = tar_block_write(t, &buf);
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+
+		/* reset type and size to original values */
+		t->th_buf.typeflag = type2;
+		th_set_size(t, sz2);
+	}
+
+	if ((t->options & TAR_GNU) && t->th_buf.gnu_longname != NULL)
+	{
+#ifdef DEBUG
+		printf("th_write(): using gnu_longname (\"%s\")\n",
+		       t->th_buf.gnu_longname);
+#endif
+		/* save old size and type */
+		type2 = t->th_buf.typeflag;
+		sz2 = th_get_size(t);
+
+		/* write out initial header block with fake size and type */
+		t->th_buf.typeflag = GNU_LONGNAME_TYPE;
+		sz = strlen(t->th_buf.gnu_longname);
+		th_set_size(t, sz);
+		th_finish(t);
+		i = tar_block_write(t, &(t->th_buf));
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+
+		/* write out extra blocks containing long name */
+		for (j = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0),
+		     ptr = t->th_buf.gnu_longname; j > 1;
+		     j--, ptr += T_BLOCKSIZE)
+		{
+			i = tar_block_write(t, ptr);
+			if (i != T_BLOCKSIZE)
+			{
+				if (i != -1)
+					errno = EINVAL;
+				return -1;
+			}
+		}
+		memset(buf, 0, T_BLOCKSIZE);
+		strncpy(buf, ptr, T_BLOCKSIZE);
+		i = tar_block_write(t, &buf);
+		if (i != T_BLOCKSIZE)
+		{
+			if (i != -1)
+				errno = EINVAL;
+			return -1;
+		}
+
+		/* reset type and size to original values */
+		t->th_buf.typeflag = type2;
+		th_set_size(t, sz2);
+	}
+
+	memset(buf, 0, T_BLOCKSIZE);
+	ptr = buf;
+
+	if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL)
+	{
+#ifdef DEBUG
+		printf("th_write(): using selinux_context (\"%s\")\n",
+		       t->th_buf.selinux_context);
+#endif
+		/* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *content* *newline* */
+		//                                                       size   newline
+		sz = SELINUX_TAG_LEN + strlen(t->th_buf.selinux_context) + 3  +    1;
+
+		if(sz >= 100) // another ascci digit for size
+			++sz;
+
+		total_sz += sz;
+		snprintf(ptr, T_BLOCKSIZE, "%d "SELINUX_TAG"%s\n", (int)sz, t->th_buf.selinux_context);
+		ptr += sz;
+	}
+
+#ifdef USE_FSCRYPT
+	if((t->options & TAR_STORE_FSCRYPT_POL) && t->th_buf.fep != NULL)
+	{
+#ifdef DEBUG
+#ifdef USE_FSCRYPT_POLICY_V1
+		printf("th_write(): using fscrypt_policy %s\n",
+		       t->th_buf.fep->master_key_descriptor);
+#else
+		printf("th_write(): using fscrypt_policy %s\n",
+		       t->th_buf.fep->master_key_identifier);
+#endif
+#endif
+		/* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *version code* *content* *newline* */
+		//                                                       size   newline
+#ifdef USE_FSCRYPT_POLICY_V1
+		sz = FSCRYPT_TAG_LEN + sizeof(struct fscrypt_policy_v1) + 1 + 3  +    1;
+#else
+		sz = FSCRYPT_TAG_LEN + sizeof(struct fscrypt_policy_v2) + 1 + 3  +    1;
+#endif
+
+		if(sz >= 100) // another ascci digit for size
+			++sz;
+
+		if (total_sz + sz >= T_BLOCKSIZE)
+		{
+			if (th_write_extended(t, &buf[0], total_sz))
+				return -1;
+			ptr = buf;
+			total_sz = sz;
+		}
+		else
+			total_sz += sz;
+
+		snprintf(ptr, T_BLOCKSIZE, "%d "FSCRYPT_TAG"0", (int)sz);
+#ifdef USE_FSCRYPT_POLICY_V1
+		memcpy(ptr + sz - sizeof(struct fscrypt_policy_v1) - 1, t->th_buf.fep, sizeof(struct fscrypt_policy_v1));
+#else
+		memcpy(ptr + sz - sizeof(struct fscrypt_policy_v2) - 1, t->th_buf.fep, sizeof(struct fscrypt_policy_v2));
+#endif
+		char *nlptr = ptr + sz - 1;
+		*nlptr = '\n';
+		ptr += sz;
+	}
+#endif
+
+	if((t->options & TAR_STORE_POSIX_CAP) && t->th_buf.has_cap_data)
+	{
+#ifdef DEBUG
+		printf("th_write(): has a posix capability\n");
+#endif
+		sz = CAPABILITIES_TAG_LEN + sizeof(struct vfs_cap_data) + 3 + 1;
+
+		if(sz >= 100) // another ascci digit for size
+			++sz;
+
+		if (total_sz + sz >= T_BLOCKSIZE)
+		{
+			if (th_write_extended(t, &buf[0], total_sz))
+				return -1;
+			ptr = buf;
+			total_sz = sz;
+		}
+		else
+			total_sz += sz;
+
+		snprintf(ptr, T_BLOCKSIZE, "%d "CAPABILITIES_TAG, (int)sz);
+		memcpy(ptr + CAPABILITIES_TAG_LEN + 3, &t->th_buf.cap_data, sizeof(struct vfs_cap_data));
+		char *nlptr = ptr + sz - 1;
+		*nlptr = '\n';
+		ptr += sz;
+	}
+	if (t->options & TAR_STORE_ANDROID_USER_XATTR)
+	{
+		if (t->th_buf.has_user_default) {
+#ifdef DEBUG
+			printf("th_write(): has android user.default xattr\n");
+#endif
+			sz = ANDROID_USER_DEFAULT_TAG_LEN + 3 + 1;
+
+			if (total_sz + sz >= T_BLOCKSIZE)
+			{
+				if (th_write_extended(t, &buf[0], total_sz))
+					return -1;
+				ptr = buf;
+				total_sz = sz;
+			}
+			else
+				total_sz += sz;
+
+			snprintf(ptr, T_BLOCKSIZE, "%d "ANDROID_USER_DEFAULT_TAG, (int)sz);
+			char *nlptr = ptr + sz - 1;
+			*nlptr = '\n';
+			ptr += sz;
+		}
+		if (t->th_buf.has_user_cache) {
+#ifdef DEBUG
+			printf("th_write(): has android user.inode_cache xattr\n");
+#endif
+			sz = ANDROID_USER_CACHE_TAG_LEN + 3 + 1;
+
+			if (total_sz + sz >= T_BLOCKSIZE)
+			{
+				if (th_write_extended(t, &buf[0], total_sz))
+					return -1;
+				ptr = buf;
+				total_sz = sz;
+			}
+			else
+				total_sz += sz;
+
+			snprintf(ptr, T_BLOCKSIZE, "%d "ANDROID_USER_CACHE_TAG, (int)sz);
+			char *nlptr = ptr + sz - 1;
+			*nlptr = '\n';
+			ptr += sz;
+		}
+		if (t->th_buf.has_user_code_cache) {
+#ifdef DEBUG
+			printf("th_write(): has android user.inode_code_cache xattr\n");
+#endif
+			sz = ANDROID_USER_CODE_CACHE_TAG_LEN + 3 + 1;
+
+			if (total_sz + sz >= T_BLOCKSIZE)
+			{
+				if (th_write_extended(t, &buf[0], total_sz))
+					return -1;
+				ptr = buf;
+				total_sz = sz;
+			}
+			else
+				total_sz += sz;
+
+			snprintf(ptr, T_BLOCKSIZE, "%d "ANDROID_USER_CODE_CACHE_TAG, (int)sz);
+			char *nlptr = ptr + sz - 1;
+			*nlptr = '\n';
+			ptr += sz;
+		}
+	}
+	if (total_sz > 0 && th_write_extended(t, &buf[0], total_sz)) // write any outstanding tar extended header
+		return -1;
+
+	th_finish(t);
+
+#ifdef DEBUG
+	/* print tar header */
+	th_print(t);
+#endif
+
+	i = tar_block_write(t, &(t->th_buf));
+	if (i != T_BLOCKSIZE)
+	{
+		if (i != -1)
+			errno = EINVAL;
+		return -1;
+	}
+
+#ifdef DEBUG
+	puts("th_write(): returning 0");
+#endif
+	return 0;
+}
+
+
diff --git a/libtar/compat.h b/libtar/compat.h
new file mode 100644
index 0000000..16b3c3b
--- /dev/null
+++ b/libtar/compat.h
@@ -0,0 +1,257 @@
+/* prototypes for borrowed "compatibility" code */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+
+#include <stdarg.h>
+#include <stddef.h>
+
+#ifdef HAVE_LIBGEN_H
+# include <libgen.h>
+#endif
+
+
+#if defined(NEED_BASENAME) && !defined(HAVE_BASENAME)
+
+# ifdef basename
+#  undef basename		/* fix glibc brokenness */
+# endif
+
+char *openbsd_basename(const char *);
+# define basename openbsd_basename
+
+#endif /* NEED_BASENAME && ! HAVE_BASENAME */
+
+
+#if defined(NEED_DIRNAME) && !defined(HAVE_DIRNAME)
+
+char *openbsd_dirname(const char *);
+# define dirname openbsd_dirname
+
+#endif /* NEED_DIRNAME && ! HAVE_DIRNAME */
+
+
+#ifdef NEED_FNMATCH
+# ifndef HAVE_FNMATCH
+
+#  define FNM_NOMATCH	1	/* Match failed. */
+
+#  define FNM_NOESCAPE	0x01	/* Disable backslash escaping. */
+#  define FNM_PATHNAME	0x02	/* Slash must be matched by slash. */
+#  define FNM_PERIOD	0x04	/* Period must be matched by period. */
+
+#  define FNM_LEADING_DIR 0x08	/* Ignore /<tail> after Imatch. */
+#  define FNM_CASEFOLD	0x10	/* Case insensitive search. */
+#  define FNM_IGNORECASE FNM_CASEFOLD
+#  define FNM_FILE_NAME FNM_PATHNAME
+
+int openbsd_fnmatch(const char *, const char *, int);
+#  define fnmatch openbsd_fnmatch
+
+# else /* HAVE_FNMATCH */
+
+#  ifdef HAVE_FNMATCH_H
+#   include <fnmatch.h>
+#  endif
+
+# endif /* ! HAVE_FNMATCH */
+#endif /* NEED_FNMATCH */
+
+
+#ifdef NEED_GETHOSTBYNAME_R
+
+# include <netdb.h>
+
+# if GETHOSTBYNAME_R_NUM_ARGS != 6
+
+int compat_gethostbyname_r(const char *, struct hostent *,
+			   char *, size_t, struct hostent **, int *);
+
+#  define gethostbyname_r compat_gethostbyname_r
+
+# endif /* GETHOSTBYNAME_R_NUM_ARGS != 6 */
+
+#endif /* NEED_GETHOSTBYNAME_R */
+
+
+#if defined(NEED_GETHOSTNAME) && !defined(HAVE_GETHOSTNAME)
+
+int gethostname(char *, size_t);
+
+#endif /* NEED_GETHOSTNAME && ! HAVE_GETHOSTNAME */
+
+
+#ifdef NEED_GETSERVBYNAME_R
+
+# include <netdb.h>
+
+# if GETSERVBYNAME_R_NUM_ARGS != 6
+
+int compat_getservbyname_r(const char *, const char *, struct servent *,
+			   char *, size_t, struct servent **);
+
+#  define getservbyname_r compat_getservbyname_r
+
+# endif /* GETSERVBYNAME_R_NUM_ARGS != 6 */
+
+#endif /* NEED_GETSERVBYNAME_R */
+
+
+
+#ifdef NEED_GLOB
+# ifndef HAVE_GLOB
+
+typedef struct {
+	int gl_pathc;		/* Count of total paths so far. */
+	int gl_matchc;		/* Count of paths matching pattern. */
+	int gl_offs;		/* Reserved at beginning of gl_pathv. */
+	int gl_flags;		/* Copy of flags parameter to glob. */
+	char **gl_pathv;	/* List of paths matching pattern. */
+				/* Copy of errfunc parameter to glob. */
+	int (*gl_errfunc)(const char *, int);
+
+	/*
+	 * Alternate filesystem access methods for glob; replacement
+	 * versions of closedir(3), readdir(3), opendir(3), stat(2)
+	 * and lstat(2).
+	 */
+	void (*gl_closedir)(void *);
+	struct dirent *(*gl_readdir)(void *);
+	void *(*gl_opendir)(const char *);
+	int (*gl_lstat)(const char *, struct stat *);
+	int (*gl_stat)(const char *, struct stat *);
+} glob_t;
+
+/* Flags */
+#  define GLOB_APPEND	0x0001	/* Append to output from previous call. */
+#  define GLOB_DOOFFS	0x0002	/* Use gl_offs. */
+#  define GLOB_ERR	0x0004	/* Return on error. */
+#  define GLOB_MARK	0x0008	/* Append / to matching directories. */
+#  define GLOB_NOCHECK	0x0010	/* Return pattern itself if nothing matches. */
+#  define GLOB_NOSORT	0x0020	/* Don't sort. */
+
+#  define GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */
+#  define GLOB_BRACE	0x0080	/* Expand braces ala csh. */
+#  define GLOB_MAGCHAR	0x0100	/* Pattern had globbing characters. */
+#  define GLOB_NOMAGIC	0x0200	/* GLOB_NOCHECK without magic chars (csh). */
+#  define GLOB_QUOTE	0x0400	/* Quote special chars with \. */
+#  define GLOB_TILDE	0x0800	/* Expand tilde names from the passwd file. */
+#  define GLOB_NOESCAPE	0x1000	/* Disable backslash escaping. */
+
+/* Error values returned by glob(3) */
+#  define GLOB_NOSPACE	(-1)	/* Malloc call failed. */
+#  define GLOB_ABORTED	(-2)	/* Unignored error. */
+#  define GLOB_NOMATCH	(-3)	/* No match and GLOB_NOCHECK not set. */
+#  define GLOB_NOSYS	(-4)	/* Function not supported. */
+#  define GLOB_ABEND	GLOB_ABORTED
+
+int openbsd_glob(const char *, int, int (*)(const char *, int), glob_t *);
+void openbsd_globfree(glob_t *);
+#  define glob openbsd_glob
+#  define globfree openbsd_globfree
+
+# else /* HAVE_GLOB */
+
+#  ifdef HAVE_GLOB_H
+#   include <glob.h>
+#  endif
+
+# endif /* ! HAVE_GLOB */
+#endif /* NEED_GLOB */
+
+
+#if defined(NEED_INET_ATON) && !defined(HAVE_INET_ATON)
+
+int inet_aton(const char *, struct in_addr *);
+
+#endif /* NEED_INET_ATON && ! HAVE_INET_ATON */
+
+
+#ifdef NEED_MAKEDEV
+
+# ifdef MAJOR_IN_MKDEV
+#  include <sys/mkdev.h>
+# else
+#  ifdef MAJOR_IN_SYSMACROS
+#   include <sys/sysmacros.h>
+#  endif
+# endif
+
+/*
+** On most systems makedev() has two args.
+** Some weird systems, like QNX6, have makedev() functions that expect
+** an extra first argument for "node", which can be 0 for a local
+** machine.
+*/
+
+# ifdef MAKEDEV_THREE_ARGS
+#  define compat_makedev(maj, min)	makedev(0, maj, min)
+# else
+#  define compat_makedev		makedev
+# endif
+
+#endif /* NEED_MAKEDEV */
+
+
+#if defined(NEED_SNPRINTF) && !defined(HAVE_SNPRINTF)
+
+int mutt_snprintf(char *, size_t, const char *, ...);
+int mutt_vsnprintf(char *, size_t, const char *, va_list);
+#define snprintf mutt_snprintf
+#define vsnprintf mutt_vsnprintf
+
+#endif /* NEED_SNPRINTF && ! HAVE_SNPRINTF */
+
+
+#if defined(NEED_STRLCAT) && !defined(HAVE_STRLCAT)
+
+size_t strlcat(char *, const char *, size_t);
+
+#endif /* NEED_STRLCAT && ! HAVE_STRLCAT */
+
+
+#if defined(NEED_STRLCPY) && !defined(HAVE_STRLCPY)
+
+size_t strlcpy(char *, const char *, size_t);
+
+#endif /* NEED_STRLCPY && ! HAVE_STRLCPY */
+
+
+#if defined(NEED_STRDUP) && !defined(HAVE_STRDUP)
+
+char *openbsd_strdup(const char *);
+# define strdup openbsd_strdup
+
+#endif /* NEED_STRDUP && ! HAVE_STRDUP */
+
+
+#if defined(NEED_STRMODE) && !defined(HAVE_STRMODE)
+
+void strmode(register mode_t, register char *);
+
+#endif /* NEED_STRMODE && ! HAVE_STRMODE */
+
+
+#if defined(NEED_STRRSTR) && !defined(HAVE_STRRSTR)
+
+char *strrstr(char *, char *);
+
+#endif /* NEED_STRRSTR && ! HAVE_STRRSTR */
+
+
+#ifdef NEED_STRSEP
+
+# ifdef HAVE_STRSEP
+#  define _LINUX_SOURCE_COMPAT		/* needed on AIX 4.3.3 */
+# else
+
+char *strsep(register char **, register const char *);
+
+# endif
+
+#endif /* NEED_STRSEP */
+
+
diff --git a/libtar/config.h b/libtar/config.h
new file mode 100644
index 0000000..c658020
--- /dev/null
+++ b/libtar/config.h
@@ -0,0 +1,188 @@
+/* config.h.  Generated by configure.  */
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define if your system has a working basename */
+/* #undef HAVE_BASENAME */
+
+/* Define to 1 if you have the <ctype.h> header file. */
+/* #undef HAVE_CTYPE_H */
+
+/* Define to 1 if the system has the type `dev_t'. */
+#define HAVE_DEV_T 1
+
+/* Define if your system has a working dirname */
+/* #undef HAVE_DIRNAME */
+
+/* Define to 1 if your system has a working POSIX `fnmatch' function. */
+#define HAVE_FNMATCH 1
+
+/* Define to 1 if you have the <fnmatch.h> header file. */
+#define HAVE_FNMATCH_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `lchown' function. */
+#define HAVE_LCHOWN 1
+
+/* Define to 1 if you have the <libgen.h> header file. */
+#define HAVE_LIBGEN_H 1
+
+/* Define to 1 if you have the `z' library (-lz). */
+#define HAVE_LIBZ 1
+
+/* Define to 1 if the system has the type `major_t'. */
+/* #undef HAVE_MAJOR_T */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if the system has the type `minor_t'. */
+/* #undef HAVE_MINOR_T */
+
+/* Define to 1 if the system has the type `nlink_t'. */
+#define HAVE_NLINK_T 1
+
+/* Define if your system has a working snprintf */
+#define HAVE_SNPRINTF 1
+
+/* Define to 1 if the system has the type `socklen_t'. */
+#define HAVE_SOCKLEN_T 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define if you have the strdup function */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strftime' function. */
+#define HAVE_STRFTIME 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define if you have the strlcpy function */
+/* #undef HAVE_STRLCPY */
+
+/* Define if you have the strmode function */
+/* #undef HAVE_STRMODE */
+
+/* Define if you have the strsep function */
+#define HAVE_STRSEP 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if the system has the type `uint64_t'. */
+#define HAVE_UINT64_T 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if `major', `minor', and `makedev' are declared in <mkdev.h>.
+   */
+/* #undef MAJOR_IN_MKDEV */
+
+/* Define to 1 if `major', `minor', and `makedev' are declared in
+   <sysmacros.h>. */
+/* #undef MAJOR_IN_SYSMACROS */
+
+/* Define as 1 if makedev expects three arguments */
+/* #undef MAKEDEV_THREE_ARGS */
+
+/* Define if you want to use the basename function */
+#define NEED_BASENAME 1
+
+/* Define if you want to use the dirname function */
+#define NEED_DIRNAME 1
+
+/* Define if you want to use the fnmatch function */
+#define NEED_FNMATCH 1
+
+/* Define if you want to use the makedev function */
+#define NEED_MAKEDEV 1
+
+/* Define if you want to use the snprintf function */
+#define NEED_SNPRINTF 1
+
+/* Define if you want to use the strdup function */
+#define NEED_STRDUP 1
+
+/* Define if you want to use the strlcpy function */
+#define NEED_STRLCPY 1
+
+/* Define if you want to use the strmode function */
+#define NEED_STRMODE 1
+
+/* Define if you want to use the strsep function */
+#define NEED_STRSEP 1
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "libtar"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "libtar 1.2.11"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "libtar"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.2.11"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define to 1 if on AIX 3.
+   System headers sometimes define this.
+   We just want to avoid a redefinition error message.  */
+#ifndef _ALL_SOURCE
+/* # undef _ALL_SOURCE */
+#endif
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `unsigned long' if not defined in system header files. */
+/* #undef dev_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `unsigned int' if not defined in system header files. */
+#define major_t unsigned int
+
+/* Define to `unsigned int' if not defined in system header files. */
+#define minor_t unsigned int
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef mode_t */
+
+/* Define to `unsigned short' if not defined in system header files. */
+/* #undef nlink_t */
+
+/* Define to `long' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `unsigned' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to `unsigned long' if not defined in system header files. */
+/* #undef socklen_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+/* Define to `long long' if not defined in system header files. */
+/* #undef uint64_t */
diff --git a/libtar/decode.c b/libtar/decode.c
new file mode 100644
index 0000000..1a3d0ee
--- /dev/null
+++ b/libtar/decode.c
@@ -0,0 +1,135 @@
+/*
+**  Copyright 1998-2003 University of Illinois Board of Trustees
+**  Copyright 1998-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  decode.c - libtar code to decode tar header blocks
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <internal.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include <pwd.h>
+#include <grp.h>
+
+#ifdef STDC_HEADERS
+# include <string.h>
+#endif
+
+
+/* determine full path name */
+char *
+th_get_pathname(TAR *t)
+{
+	if (t->th_buf.gnu_longname)
+		return t->th_buf.gnu_longname;
+
+	/* allocate the th_pathname buffer if not already */
+	if (t->th_pathname == NULL)
+	{
+		t->th_pathname = malloc(MAXPATHLEN * sizeof(char));
+		if (t->th_pathname == NULL)
+			/* out of memory */
+			return NULL;
+	}
+
+	if (t->th_buf.prefix[0] == '\0')
+	{
+		snprintf(t->th_pathname, MAXPATHLEN, "%.100s", t->th_buf.name);
+	}
+	else
+	{
+		snprintf(t->th_pathname, MAXPATHLEN, "%.155s/%.100s",
+			 t->th_buf.prefix, t->th_buf.name);
+	}
+
+	/* will be deallocated in tar_close() */
+	return t->th_pathname;
+}
+
+
+uid_t
+th_get_uid(TAR *t)
+{
+	int uid;
+	struct passwd *pw;
+
+	if (!(t->options & TAR_USE_NUMERIC_ID)) {
+		pw = getpwnam(t->th_buf.uname);
+		if (pw != NULL)
+			return pw->pw_uid;
+	}
+
+	/* if the password entry doesn't exist */
+	sscanf(t->th_buf.uid, "%o", &uid);
+	return uid;
+}
+
+
+gid_t
+th_get_gid(TAR *t)
+{
+	int gid;
+	struct group *gr;
+
+	if (!(t->options & TAR_USE_NUMERIC_ID)) {
+		gr = getgrnam(t->th_buf.gname);
+		if (gr != NULL)
+			return gr->gr_gid;
+	}
+
+	/* if the group entry doesn't exist */
+	sscanf(t->th_buf.gid, "%o", &gid);
+	return gid;
+}
+
+
+mode_t
+th_get_mode(TAR *t)
+{
+	mode_t mode;
+
+	mode = (mode_t)oct_to_int(t->th_buf.mode, sizeof(t->th_buf.mode));
+	if (! (mode & S_IFMT))
+	{
+		switch (t->th_buf.typeflag)
+		{
+		case SYMTYPE:
+			mode |= S_IFLNK;
+			break;
+		case CHRTYPE:
+			mode |= S_IFCHR;
+			break;
+		case BLKTYPE:
+			mode |= S_IFBLK;
+			break;
+		case DIRTYPE:
+			mode |= S_IFDIR;
+			break;
+		case FIFOTYPE:
+			mode |= S_IFIFO;
+			break;
+		case AREGTYPE:
+			if (t->th_buf.name[strnlen(t->th_buf.name, T_NAMELEN) - 1] == '/')
+			{
+				mode |= S_IFDIR;
+				break;
+			}
+			/* FALLTHROUGH */
+		case LNKTYPE:
+		case REGTYPE:
+		default:
+			mode |= S_IFREG;
+		}
+	}
+
+	return mode;
+}
+
+
diff --git a/libtar/dirname.c b/libtar/dirname.c
new file mode 100644
index 0000000..31dbe13
--- /dev/null
+++ b/libtar/dirname.c
@@ -0,0 +1,77 @@
+/*	$OpenBSD: dirname.c,v 1.4 1999/05/30 17:10:30 espie Exp $	*/
+
+/*
+ * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef lint
+//static char rcsid[] = "$OpenBSD: dirname.c,v 1.4 1999/05/30 17:10:30 espie Exp $";
+#endif /* not lint */
+
+#include <errno.h>
+#include <string.h>
+#include <sys/param.h>
+
+char *
+openbsd_dirname(path)
+	const char *path;
+{
+	static char bname[MAXPATHLEN];
+	register const char *endp;
+
+	/* Empty or NULL string gets treated as "." */
+	if (path == NULL || *path == '\0') {
+		(void)strcpy(bname, ".");
+		return(bname);
+	}
+
+	/* Strip trailing slashes */
+	endp = path + strlen(path) - 1;
+	while (endp > path && *endp == '/')
+		endp--;
+
+	/* Find the start of the dir */
+	while (endp > path && *endp != '/')
+		endp--;
+
+	/* Either the dir is "/" or there are no slashes */
+	if (endp == path) {
+		(void)strcpy(bname, *endp == '/' ? "/" : ".");
+		return(bname);
+	} else {
+		do {
+			endp--;
+		} while (endp > path && *endp == '/');
+	}
+
+	if (endp - path + 1 > (int)sizeof(bname)) {
+		errno = ENAMETOOLONG;
+		return(NULL);
+	}
+	(void)strncpy(bname, path, endp - path + 1);
+	bname[endp - path + 1] = '\0';
+	return(bname);
+}
diff --git a/libtar/encode.c b/libtar/encode.c
new file mode 100644
index 0000000..1e679d8
--- /dev/null
+++ b/libtar/encode.c
@@ -0,0 +1,239 @@
+/*
+**  Copyright 1998-2003 University of Illinois Board of Trustees
+**  Copyright 1998-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  encode.c - libtar code to encode tar header blocks
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <internal.h>
+
+#include <stdio.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/sysmacros.h>
+
+#ifdef STDC_HEADERS
+# include <string.h>
+# include <stdlib.h>
+#endif
+
+
+/* magic, version, and checksum */
+void
+th_finish(TAR *t)
+{
+	if (t->options & TAR_GNU)
+	{
+		/* we're aiming for this result, but must do it in
+		 * two calls to avoid FORTIFY segfaults on some Linux
+		 * systems:
+		 *      strncpy(t->th_buf.magic, "ustar  ", 8);
+		 */
+		strncpy(t->th_buf.magic, "ustar ", 6);
+		strncpy(t->th_buf.version, " ", 2);
+	}
+	else
+	{
+		strncpy(t->th_buf.version, TVERSION, TVERSLEN);
+		strncpy(t->th_buf.magic, TMAGIC, TMAGLEN);
+	}
+
+	int_to_oct(th_crc_calc(t), t->th_buf.chksum, 8);
+}
+
+
+/* map a file mode to a typeflag */
+void
+th_set_type(TAR *t, mode_t mode)
+{
+	if (S_ISLNK(mode))
+		t->th_buf.typeflag = SYMTYPE;
+	if (S_ISREG(mode))
+		t->th_buf.typeflag = REGTYPE;
+	if (S_ISDIR(mode))
+		t->th_buf.typeflag = DIRTYPE;
+	if (S_ISCHR(mode))
+		t->th_buf.typeflag = CHRTYPE;
+	if (S_ISBLK(mode))
+		t->th_buf.typeflag = BLKTYPE;
+	if (S_ISFIFO(mode) || S_ISSOCK(mode))
+		t->th_buf.typeflag = FIFOTYPE;
+}
+
+
+/* encode file path */
+void
+th_set_path(TAR *t, const char *pathname)
+{
+	char suffix[2] = "";
+	char *tmp;
+	size_t pathname_len = strlen(pathname);
+
+#ifdef DEBUG
+	printf("in th_set_path(th, pathname=\"%s\")\n", pathname);
+#endif
+
+	if (t->th_buf.gnu_longname != NULL)
+		free(t->th_buf.gnu_longname);
+	t->th_buf.gnu_longname = NULL;
+
+	/* old archive compatibility (not needed for gnu): add trailing / to directories */
+	if (pathname[pathname_len - 1] != '/' && TH_ISDIR(t))
+		strcpy(suffix, "/");
+
+	if (pathname_len >= T_NAMELEN && (t->options & TAR_GNU))
+	{
+		/* GNU-style long name (no file name length limit) */
+		t->th_buf.gnu_longname = strdup(pathname);
+		strncpy(t->th_buf.name, t->th_buf.gnu_longname, T_NAMELEN);
+	}
+	else if (pathname_len >= T_NAMELEN)
+	{
+		/* POSIX-style prefix field:
+		 *   The maximum length of a file name is limited to 256 characters,
+		 *   provided that the file name can be split at a directory separator
+		 *   in two parts.  The first part being at most 155 bytes long and
+		 *   the second part being at most 100 bytes long.  So, in most cases
+		 *   the maximum file name length will be shorter than 256 characters.
+		 */
+		char tail_path[T_NAMELEN + 1];
+		tmp = strchr(&(pathname[pathname_len - T_NAMELEN]), '/');
+		if (tmp == NULL)
+		{
+			printf("!!! '/' not found in \"%s\"\n", pathname);
+			return;
+		}
+		snprintf(tail_path, T_NAMELEN + 1, "%s%s", &tmp[1], suffix);
+		strncpy(t->th_buf.name, tail_path, T_NAMELEN);
+
+		/*
+		 * first part, max = 155 == sizeof(t->th_buf.prefix) , include NULL if it fits
+		 * trailing '/' is added during decode: decode.c/th_get_pathname()
+		 */
+		if (tmp - pathname >= 155) {
+			strncpy(t->th_buf.prefix, pathname, 155);
+		} else {
+			snprintf(t->th_buf.prefix, (tmp - pathname + 1), "%s", pathname);
+		}
+	}
+	else {
+		/* any short name for all formats, or classic tar format (99 chars max) */
+		snprintf(t->th_buf.name, T_NAMELEN, "%s%s", pathname, suffix);
+	}
+
+#ifdef DEBUG
+	puts("returning from th_set_path()...");
+#endif
+}
+
+
+/* encode link path */
+void
+th_set_link(TAR *t, const char *linkname)
+{
+#ifdef DEBUG
+	printf("==> th_set_link(th, linkname=\"%s\")\n", linkname);
+#endif
+
+	if (strlen(linkname) >= T_NAMELEN && (t->options & TAR_GNU))
+	{
+		/* --format=gnu: GNU-style long name (no file name length limit) */
+		t->th_buf.gnu_longlink = strdup(linkname);
+		strcpy(t->th_buf.linkname, "././@LongLink");
+	}
+	else if (strlen(linkname) >= T_NAMELEN)
+	{
+		/* --format=ustar: 100 chars max limit for symbolic links */
+		strncpy(t->th_buf.linkname, linkname, T_NAMELEN);
+		if (t->th_buf.gnu_longlink != NULL)
+			free(t->th_buf.gnu_longlink);
+		t->th_buf.gnu_longlink = NULL;
+	} else {
+		/* all short links or v7 tar format: The maximum length of a symbolic link name is limited to 99 characters */
+		snprintf(t->th_buf.linkname, T_NAMELEN, "%s", linkname);
+		if (t->th_buf.gnu_longlink != NULL)
+			free(t->th_buf.gnu_longlink);
+		t->th_buf.gnu_longlink = NULL;
+	}
+}
+
+
+/* encode device info */
+void
+th_set_device(TAR *t, dev_t device)
+{
+#ifdef DEBUG
+	printf("th_set_device(): major = %d, minor = %d\n",
+	       major(device), minor(device));
+#endif
+	int_to_oct(major(device), t->th_buf.devmajor, 8);
+	int_to_oct(minor(device), t->th_buf.devminor, 8);
+}
+
+
+/* encode user info */
+void
+th_set_user(TAR *t, uid_t uid)
+{
+	struct passwd *pw;
+
+	if (!(t->options & TAR_USE_NUMERIC_ID)) {
+		pw = getpwuid(uid);
+		if (pw != NULL)
+			strlcpy(t->th_buf.uname, pw->pw_name, sizeof(t->th_buf.uname));
+	}
+
+	int_to_oct(uid, t->th_buf.uid, 8);
+}
+
+
+/* encode group info */
+void
+th_set_group(TAR *t, gid_t gid)
+{
+	struct group *gr;
+
+	if (!(t->options & TAR_USE_NUMERIC_ID)) {
+		gr = getgrgid(gid);
+		if (gr != NULL)
+			strlcpy(t->th_buf.gname, gr->gr_name, sizeof(t->th_buf.gname));
+	}
+
+	int_to_oct(gid, t->th_buf.gid, 8);
+}
+
+
+/* encode file mode */
+void
+th_set_mode(TAR *t, mode_t fmode)
+{
+	if (S_ISSOCK(fmode))
+	{
+		fmode &= ~S_IFSOCK;
+		fmode |= S_IFIFO;
+	}
+	int_to_oct(fmode, (t)->th_buf.mode, 8);
+}
+
+
+void
+th_set_from_stat(TAR *t, struct stat *s)
+{
+	th_set_type(t, s->st_mode);
+	if (S_ISCHR(s->st_mode) || S_ISBLK(s->st_mode))
+		th_set_device(t, s->st_rdev);
+	th_set_user(t, s->st_uid);
+	th_set_group(t, s->st_gid);
+	th_set_mode(t, s->st_mode);
+	th_set_mtime(t, s->st_mtime);
+	if (S_ISREG(s->st_mode))
+		th_set_size(t, s->st_size);
+	else
+		th_set_size(t, 0);
+}
diff --git a/libtar/extract.c b/libtar/extract.c
new file mode 100755
index 0000000..08e2914
--- /dev/null
+++ b/libtar/extract.c
@@ -0,0 +1,704 @@
+/*
+**  Copyright 1998-2003 University of Illinois Board of Trustees
+**  Copyright 1998-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  extract.c - libtar code to extract a file from a tar archive
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <internal.h>
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <utime.h>
+
+#include <sys/capability.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <selinux/selinux.h>
+
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
+#endif
+
+#ifdef TW_LIBTAR_DEBUG
+#define DEBUG 1
+#endif
+
+#include "android_utils.h"
+
+const unsigned long long progress_size = (unsigned long long)(T_BLOCKSIZE);
+
+static int
+tar_set_file_perms(TAR *t, const char *realname)
+{
+	mode_t mode;
+	uid_t uid;
+	gid_t gid;
+	struct utimbuf ut;
+	const char *filename;
+	char *pn;
+
+	pn = th_get_pathname(t);
+	filename = (realname ? realname : pn);
+	mode = th_get_mode(t);
+	uid = th_get_uid(t);
+	gid = th_get_gid(t);
+	ut.modtime = ut.actime = th_get_mtime(t);
+
+#ifdef DEBUG
+	printf("tar_set_file_perms(): setting perms: %s (mode %04o, uid %d, gid %d)\n",
+		filename, mode, uid, gid);
+#endif
+
+	/* change owner/group */
+	if (geteuid() == 0)
+#ifdef HAVE_LCHOWN
+		if (lchown(filename, uid, gid) == -1)
+		{
+# ifdef DEBUG
+			fprintf(stderr, "lchown(\"%s\", %d, %d): %s\n",
+				filename, uid, gid, strerror(errno));
+# endif
+#else /* ! HAVE_LCHOWN */
+		if (!TH_ISSYM(t) && chown(filename, uid, gid) == -1)
+		{
+# ifdef DEBUG
+			fprintf(stderr, "chown(\"%s\", %d, %d): %s\n",
+				filename, uid, gid, strerror(errno));
+# endif
+#endif /* HAVE_LCHOWN */
+			return -1;
+		}
+
+	/* change access/modification time */
+	if (!TH_ISSYM(t) && utime(filename, &ut) == -1)
+	{
+#ifdef DEBUG
+		perror("utime()");
+#endif
+		return -1;
+	}
+
+	/* change permissions */
+	if (!TH_ISSYM(t) && chmod(filename, mode) == -1)
+	{
+#ifdef DEBUG
+		perror("chmod()");
+#endif
+		return -1;
+	}
+
+	return 0;
+}
+
+
+/* switchboard */
+int
+tar_extract_file(TAR *t, const char *realname, const char *prefix, const int *progress_fd)
+{
+	int i;
+#ifdef LIBTAR_FILE_HASH
+	char *lnp;
+	char *pn;
+	int pathname_len;
+	int realname_len;
+#endif
+
+	if (t->options & TAR_NOOVERWRITE)
+	{
+		struct stat s;
+
+		if (lstat(realname, &s) == 0 || errno != ENOENT)
+		{
+			errno = EEXIST;
+			return -1;
+		}
+	}
+
+	if (TH_ISDIR(t))
+	{
+		i = tar_extract_dir(t, realname);
+		if (i == 1)
+			i = 0;
+	}
+	else if (TH_ISLNK(t))
+		i = tar_extract_hardlink(t, realname, prefix);
+	else if (TH_ISSYM(t))
+		i = tar_extract_symlink(t, realname);
+	else if (TH_ISCHR(t))
+		i = tar_extract_chardev(t, realname);
+	else if (TH_ISBLK(t))
+		i = tar_extract_blockdev(t, realname);
+	else if (TH_ISFIFO(t))
+		i = tar_extract_fifo(t, realname);
+	else /* if (TH_ISREG(t)) */
+		i = tar_extract_regfile(t, realname, progress_fd);
+
+	if (i != 0) {
+		fprintf(stderr, "tar_extract_file(): failed to extract %s !!!\n", realname);
+		return i;
+	}
+
+	i = tar_set_file_perms(t, realname);
+	if (i != 0) {
+		fprintf(stderr, "tar_extract_file(): failed to set permissions on %s !!!\n", realname);
+		return i;
+	}
+
+	if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL)
+	{
+#ifdef DEBUG
+		printf("tar_extract_file(): restoring SELinux context %s to file %s\n", t->th_buf.selinux_context, realname);
+#endif
+		if (lsetfilecon(realname, t->th_buf.selinux_context) < 0)
+			fprintf(stderr, "tar_extract_file(): failed to restore SELinux context %s to file %s !!!\n", t->th_buf.selinux_context, realname);
+	}
+
+	if((t->options & TAR_STORE_POSIX_CAP) && t->th_buf.has_cap_data)
+	{
+#if 1 //def DEBUG
+		printf("tar_extract_file(): restoring posix capabilities to file %s\n", realname);
+		print_caps(&t->th_buf.cap_data);
+#endif
+		if (setxattr(realname, XATTR_NAME_CAPS, &t->th_buf.cap_data, sizeof(struct vfs_cap_data), 0) < 0)
+			fprintf(stderr, "tar_extract_file(): failed to restore posix capabilities to file %s !!!\n", realname);
+	}
+
+#ifdef LIBTAR_FILE_HASH
+	pn = th_get_pathname(t);
+	pathname_len = strlen(pn) + 1;
+	realname_len = strlen(realname) + 1;
+	lnp = (char *)calloc(1, pathname_len + realname_len);
+	if (lnp == NULL)
+		return -1;
+	strcpy(&lnp[0], pn);
+	strcpy(&lnp[pathname_len], realname);
+#ifdef DEBUG
+	printf("tar_extract_file(): calling libtar_hash_add(): key=\"%s\", "
+	       "value=\"%s\"\n", pn, realname);
+#endif
+	if (libtar_hash_add(t->h, lnp) != 0)
+		return -1;
+	free(lnp);
+#endif
+
+	return 0;
+}
+
+
+/* extract regular file */
+int
+tar_extract_regfile(TAR *t, const char *realname, const int *progress_fd)
+{
+	int64_t size, i;
+	ssize_t k;
+	int fdout;
+	char buf[T_BLOCKSIZE];
+	const char *filename;
+	char *pn;
+
+#ifdef DEBUG
+	printf("  ==> tar_extract_regfile(realname=\"%s\")\n", realname);
+#endif
+
+	if (!TH_ISREG(t))
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	pn = th_get_pathname(t);
+	filename = (realname ? realname : pn);
+	size = th_get_size(t);
+
+	if (mkdirhier(dirname(filename)) == -1)
+		return -1;
+
+	printf("  ==> extracting: %s (file size %" PRId64 " bytes)\n",
+			filename, size);
+
+	fdout = open(filename, O_WRONLY | O_CREAT | O_TRUNC
+#ifdef O_BINARY
+		     | O_BINARY
+#endif
+		    , 0666);
+	if (fdout == -1)
+	{
+#ifdef DEBUG
+		perror("open()");
+#endif
+		return -1;
+	}
+
+	/* extract the file */
+	for (i = size; i > 0; i -= T_BLOCKSIZE)
+	{
+		k = tar_block_read(t, buf);
+		if (k != T_BLOCKSIZE)
+		{
+			if (k != -1)
+				errno = EINVAL;
+			close(fdout);
+			return -1;
+		}
+
+		/* write block to output file */
+		if (write(fdout, buf,
+			  ((i > T_BLOCKSIZE) ? T_BLOCKSIZE : i)) == -1)
+		{
+			close(fdout);
+			return -1;
+		}
+		else
+		{
+			if (*progress_fd != 0)
+				write(*progress_fd, &progress_size, sizeof(progress_size));
+		}
+	}
+
+	/* close output file */
+	if (close(fdout) == -1)
+		return -1;
+
+#ifdef DEBUG
+	printf("### done extracting %s\n", filename);
+#endif
+
+	return 0;
+}
+
+
+/* skip regfile */
+int
+tar_skip_regfile(TAR *t)
+{
+	int64_t size, i;
+	ssize_t k;
+	char buf[T_BLOCKSIZE];
+
+	if (!TH_ISREG(t))
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	size = th_get_size(t);
+	for (i = size; i > 0; i -= T_BLOCKSIZE)
+	{
+		k = tar_block_read(t, buf);
+		if (k != T_BLOCKSIZE)
+		{
+			if (k != -1)
+				errno = EINVAL;
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+
+/* hardlink */
+int
+tar_extract_hardlink(TAR * t, const char *realname, const char *prefix)
+{
+	const char *filename;
+	char *pn;
+	char *linktgt = NULL;
+	char *newtgt = NULL;
+	char *lnp;
+	libtar_hashptr_t hp;
+
+	if (!TH_ISLNK(t))
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	pn = th_get_pathname(t);
+	filename = (realname ? realname : pn);
+	if (mkdirhier(dirname(filename)) == -1)
+		return -1;
+	if (unlink(filename) == -1 && errno != ENOENT)
+		return -1;
+	libtar_hashptr_reset(&hp);
+	if (libtar_hash_getkey(t->h, &hp, th_get_linkname(t),
+			       (libtar_matchfunc_t)libtar_str_match) != 0)
+	{
+		lnp = (char *)libtar_hashptr_data(&hp);
+		linktgt = &lnp[strlen(lnp) + 1];
+	}
+	else
+		linktgt = th_get_linkname(t);
+
+	newtgt = strdup(linktgt);
+	sprintf(linktgt, "%s/%s", prefix, newtgt);
+
+	printf("  ==> extracting: %s (link to %s)\n", filename, linktgt);
+
+	if (link(linktgt, filename) == -1)
+	{
+		fprintf(stderr, "tar_extract_hardlink(): failed restore of hardlink '%s' but returning as if nothing bad happened\n", filename);
+		return 0; // Used to be -1
+	}
+
+	return 0;
+}
+
+
+/* symlink */
+int
+tar_extract_symlink(TAR *t, const char *realname)
+{
+	const char *filename;
+	char *pn;
+
+	if (!TH_ISSYM(t))
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	pn = th_get_pathname(t);
+	filename = (realname ? realname : pn);
+	if (mkdirhier(dirname(filename)) == -1)
+		return -1;
+
+	if (unlink(filename) == -1 && errno != ENOENT)
+		return -1;
+
+	printf("  ==> extracting: %s (symlink to %s)\n",
+	       filename, th_get_linkname(t));
+
+	if (symlink(th_get_linkname(t), filename) == -1)
+	{
+#ifdef DEBUG
+		perror("symlink()");
+#endif
+		return -1;
+	}
+
+	return 0;
+}
+
+
+/* character device */
+int
+tar_extract_chardev(TAR *t, const char *realname)
+{
+	mode_t mode;
+	unsigned long devmaj, devmin;
+	const char *filename;
+	char *pn;
+
+	if (!TH_ISCHR(t))
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	pn = th_get_pathname(t);
+	filename = (realname ? realname : pn);
+	mode = th_get_mode(t);
+	devmaj = th_get_devmajor(t);
+	devmin = th_get_devminor(t);
+
+	if (mkdirhier(dirname(filename)) == -1)
+		return -1;
+
+	printf("  ==> extracting: %s (character device %ld,%ld)\n",
+	       filename, devmaj, devmin);
+
+	if (mknod(filename, mode | S_IFCHR,
+		  compat_makedev(devmaj, devmin)) == -1)
+	{
+		fprintf(stderr, "tar_extract_chardev(): failed restore of character device '%s' but returning as if nothing bad happened\n", filename);
+		return 0; // Used to be -1
+	}
+
+	return 0;
+}
+
+
+/* block device */
+int
+tar_extract_blockdev(TAR *t, const char *realname)
+{
+	mode_t mode;
+	unsigned long devmaj, devmin;
+	const char *filename;
+	char *pn;
+
+	if (!TH_ISBLK(t))
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	pn = th_get_pathname(t);
+	filename = (realname ? realname : pn);
+	mode = th_get_mode(t);
+	devmaj = th_get_devmajor(t);
+	devmin = th_get_devminor(t);
+
+	if (mkdirhier(dirname(filename)) == -1)
+		return -1;
+
+	printf("  ==> extracting: %s (block device %ld,%ld)\n",
+	       filename, devmaj, devmin);
+
+	if (mknod(filename, mode | S_IFBLK,
+		  compat_makedev(devmaj, devmin)) == -1)
+	{
+		fprintf(stderr, "tar_extract_blockdev(): failed restore of block device '%s' but returning as if nothing bad happened\n", filename);
+		return 0; // Used to be -1
+	}
+
+	return 0;
+}
+
+/* directory */
+int
+tar_extract_dir(TAR *t, const char *realname)
+{
+	mode_t mode;
+	const char *filename;
+	char *pn;
+
+	if (!TH_ISDIR(t))
+	{
+		errno = EINVAL;
+		return -1;
+	}
+	pn = th_get_pathname(t);
+	filename = (realname ? realname : pn);
+	mode = th_get_mode(t);
+
+	if (mkdirhier(dirname(filename)) == -1)
+		return -1;
+
+	printf("  ==> extracting: %s (mode %04o, directory)\n", filename,
+	       mode);
+
+	if (mkdir(filename, mode) == -1)
+	{
+		if (errno == EEXIST)
+		{
+			if (chmod(filename, mode) == -1)
+			{
+#ifdef DEBUG
+				perror("chmod()");
+#endif
+				return -1;
+			}
+			else
+			{
+#if 1 //def DEBUG
+				puts("  *** using existing directory");
+#endif
+				return 1;
+			}
+		}
+		else
+		{
+#ifdef DEBUG
+			perror("mkdir()");
+#endif
+			return -1;
+		}
+	}
+
+	if (t->options & TAR_STORE_ANDROID_USER_XATTR)
+	{
+		if (t->th_buf.has_user_default) {
+#if 1 //def DEBUG
+			printf("tar_extract_file(): restoring android user.default xattr to %s\n", realname);
+#endif
+			if (setxattr(realname, "user.default", NULL, 0, 0) < 0) {
+				fprintf(stderr, "tar_extract_file(): failed to restore android user.default to file %s !!!\n", realname);
+				return -1;
+			}
+		}
+		if (t->th_buf.has_user_cache) {
+#if 1 //def DEBUG
+			printf("tar_extract_file(): restoring android user.inode_cache xattr to %s\n", realname);
+#endif
+			if (write_path_inode(realname, "cache", "user.inode_cache"))
+				return -1;
+		}
+		if (t->th_buf.has_user_code_cache) {
+#if 1 //def DEBUG
+			printf("tar_extract_file(): restoring android user.inode_code_cache xattr to %s\n", realname);
+#endif
+			if (write_path_inode(realname, "code_cache", "user.inode_code_cache"))
+				return -1;
+		}
+	}
+
+#ifdef USE_FSCRYPT
+	if(t->th_buf.fep != NULL)
+	{
+#ifdef USE_FSCRYPT_POLICY_V1
+		char policy_hex[FS_KEY_DESCRIPTOR_SIZE_HEX];
+#else
+		char policy_hex[FSCRYPT_KEY_IDENTIFIER_HEX_SIZE];
+#endif
+#ifdef DEBUG
+#ifdef USE_FSCRYPT_POLICY_V1
+		bytes_to_hex(t->th_buf.fep->master_key_descriptor, FS_KEY_DESCRIPTOR_SIZE, policy_hex);
+#else
+		bytes_to_hex(t->th_buf.fep->master_key_identifier, FSCRYPT_KEY_IDENTIFIER_SIZE, policy_hex);
+#endif
+		printf("tar_extract_dir(): restoring fscrypt policy %s to dir %s\n", (char *)policy_hex, realname);
+#endif
+		bool policy_lookup_error = false;
+#ifdef USE_FSCRYPT_POLICY_V1
+		uint8_t binary_policy[FS_KEY_DESCRIPTOR_SIZE];
+		memset(&binary_policy, 0, FS_KEY_DESCRIPTOR_SIZE);
+#else
+		uint8_t binary_policy[FSCRYPT_KEY_IDENTIFIER_SIZE];
+		memset(&binary_policy, 0, FSCRYPT_KEY_IDENTIFIER_SIZE);
+#endif
+
+#ifdef USE_FSCRYPT_POLICY_V1
+		if (!lookup_ref_tar(t->th_buf.fep->master_key_descriptor, &binary_policy[0])) {
+			printf("error looking up fscrypt policy for '%s' - %s\n", realname, t->th_buf.fep->master_key_descriptor);
+			policy_lookup_error = true;
+		}
+		memcpy(&t->th_buf.fep->master_key_descriptor, binary_policy, FS_KEY_DESCRIPTOR_SIZE);
+		bytes_to_hex(t->th_buf.fep->master_key_descriptor, FS_KEY_DESCRIPTOR_SIZE, policy_hex);
+#else
+		if (!lookup_ref_tar(t->th_buf.fep->master_key_identifier, &binary_policy[0])) {
+			printf("error looking up fscrypt policy for '%s' - %s\n", realname, t->th_buf.fep->master_key_identifier);
+			policy_lookup_error = true;
+		}
+		memcpy(&t->th_buf.fep->master_key_identifier, binary_policy, FSCRYPT_KEY_IDENTIFIER_SIZE);
+		bytes_to_hex(t->th_buf.fep->master_key_identifier, FSCRYPT_KEY_IDENTIFIER_SIZE, policy_hex);
+#endif
+		if (!policy_lookup_error) 
+		{
+			printf("attempting to restore policy: %s\n", policy_hex);
+			if (!fscrypt_policy_set_struct(realname, t->th_buf.fep))
+			{
+				printf("tar_extract_file(): failed to restore fscrypt policy to dir '%s' '%s'!!!\n", realname, policy_hex);
+				//return -1; // This may not be an error in some cases, so log and ignore
+			}
+		} else
+			printf("No policy was found. Continuing restore.");
+	}
+	else
+		printf("NULL FSCRYPT\n");
+#endif
+
+	return 0;
+}
+
+
+/* FIFO */
+int
+tar_extract_fifo(TAR *t, const char *realname)
+{
+	mode_t mode;
+	const char *filename;
+	char *pn;
+
+	if (!TH_ISFIFO(t))
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	pn = th_get_pathname(t);
+	filename = (realname ? realname : pn);
+	mode = th_get_mode(t);
+
+	if (mkdirhier(dirname(filename)) == -1)
+		return -1;
+
+
+	printf("  ==> extracting: %s (fifo)\n", filename);
+
+	if (mkfifo(filename, mode) == -1)
+	{
+#ifdef DEBUG
+		perror("mkfifo()");
+#endif
+		return -1;
+	}
+
+	return 0;
+}
+
+/* extract file contents from a tarchive */
+int
+tar_extract_file_contents(TAR *t, void *buf, size_t *lenp)
+{
+	char block[T_BLOCKSIZE];
+	int64_t size, i;
+	ssize_t k;
+
+#ifdef DEBUG
+	printf("  ==> tar_extract_file_contents\n");
+#endif
+
+	if (!TH_ISREG(t))
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	size = th_get_size(t);
+	if ((uint64_t)size > *lenp)
+	{
+		errno = ENOSPC;
+		return -1;
+	}
+
+	/* extract the file */
+	for (i = size; i >= T_BLOCKSIZE; i -= T_BLOCKSIZE)
+	{
+		k = tar_block_read(t, buf);
+		if (k != T_BLOCKSIZE)
+		{
+			if (k != -1)
+				errno = EINVAL;
+			return -1;
+		}
+		buf = (char *)buf + T_BLOCKSIZE;
+	}
+	if (i > 0) {
+		k = tar_block_read(t, block);
+		if (k != T_BLOCKSIZE)
+		{
+			if (k != -1)
+				errno = EINVAL;
+			return -1;
+		}
+		memcpy(buf, block, i);
+	}
+	*lenp = (size_t)size;
+
+#ifdef DEBUG
+	printf("### done extracting contents\n");
+#endif
+	return 0;
+}
diff --git a/libtar/fnmatch.c b/libtar/fnmatch.c
new file mode 100644
index 0000000..fe75f0e
--- /dev/null
+++ b/libtar/fnmatch.c
@@ -0,0 +1,237 @@
+/*	$OpenBSD: fnmatch.c,v 1.6 1998/03/19 00:29:59 millert Exp $	*/
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)fnmatch.c	8.2 (Berkeley) 4/16/94";
+#else
+static char rcsid[] = "$OpenBSD: fnmatch.c,v 1.6 1998/03/19 00:29:59 millert Exp $";
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+/*
+ * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
+ * Compares a filename or pathname to a pattern.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#ifdef STDC_HEADERS
+# include <string.h>
+#endif
+
+#ifdef HAVE_CTYPE_H
+# include <ctype.h>
+#endif
+
+#include <compat.h>
+
+
+#define	EOS	'\0'
+
+#define	RANGE_MATCH	1
+#define	RANGE_NOMATCH	0
+#define	RANGE_ERROR	(-1)
+
+#ifdef NO_IBM_COMPILER_HORKAGE
+static int rangematch (const char *, char, int, char **);
+#else
+static int rangematch ();
+#endif
+
+int
+fnmatch(pattern, string, flags)
+	const char *pattern, *string;
+	int flags;
+{
+	const char *stringstart;
+	char *newp;
+	char c, test;
+
+	for (stringstart = string;;)
+		switch (c = *pattern++) {
+		case EOS:
+			if ((flags & FNM_LEADING_DIR) && *string == '/')
+				return (0);
+			return (*string == EOS ? 0 : FNM_NOMATCH);
+		case '?':
+			if (*string == EOS)
+				return (FNM_NOMATCH);
+			if (*string == '/' && (flags & FNM_PATHNAME))
+				return (FNM_NOMATCH);
+			if (*string == '.' && (flags & FNM_PERIOD) &&
+			    (string == stringstart ||
+			    ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+				return (FNM_NOMATCH);
+			++string;
+			break;
+		case '*':
+			c = *pattern;
+			/* Collapse multiple stars. */
+			while (c == '*')
+				c = *++pattern;
+
+			if (*string == '.' && (flags & FNM_PERIOD) &&
+			    (string == stringstart ||
+			    ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+				return (FNM_NOMATCH);
+
+			/* Optimize for pattern with * at end or before /. */
+			if (c == EOS) {
+				if (flags & FNM_PATHNAME)
+					return ((flags & FNM_LEADING_DIR) ||
+					    strchr(string, '/') == NULL ?
+					    0 : FNM_NOMATCH);
+				else
+					return (0);
+			} else if (c == '/' && (flags & FNM_PATHNAME)) {
+				if ((string = strchr(string, '/')) == NULL)
+					return (FNM_NOMATCH);
+				break;
+			}
+
+			/* General case, use recursion. */
+			while ((test = *string) != EOS) {
+				if (!fnmatch(pattern, string, flags & ~FNM_PERIOD))
+					return (0);
+				if (test == '/' && (flags & FNM_PATHNAME))
+					break;
+				++string;
+			}
+			return (FNM_NOMATCH);
+		case '[':
+			if (*string == EOS)
+				return (FNM_NOMATCH);
+			if (*string == '/' && (flags & FNM_PATHNAME))
+				return (FNM_NOMATCH);
+			if (*string == '.' && (flags & FNM_PERIOD) &&
+			    (string == stringstart ||
+			    ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+				return (FNM_NOMATCH);
+
+			switch (rangematch(pattern, *string, flags, &newp)) {
+			case RANGE_ERROR:
+				/* not a good range, treat as normal text */
+				goto normal;
+			case RANGE_MATCH:
+				pattern = newp;
+				break;
+			case RANGE_NOMATCH:
+				return (FNM_NOMATCH);
+			}
+			++string;
+			break;
+		case '\\':
+			if (!(flags & FNM_NOESCAPE)) {
+				if ((c = *pattern++) == EOS) {
+					c = '\\';
+					--pattern;
+				}
+			}
+			/* FALLTHROUGH */
+		default:
+		normal:
+			if (c != *string && !((flags & FNM_CASEFOLD) &&
+				 (tolower((unsigned char)c) ==
+				 tolower((unsigned char)*string))))
+				return (FNM_NOMATCH);
+			++string;
+			break;
+		}
+	/* NOTREACHED */
+}
+
+static int
+rangematch(pattern, test, flags, newp)
+	const char *pattern;
+	char test;
+	int flags;
+	char **newp;
+{
+	int negate, ok;
+	char c, c2;
+
+	/*
+	 * A bracket expression starting with an unquoted circumflex
+	 * character produces unspecified results (IEEE 1003.2-1992,
+	 * 3.13.2).  This implementation treats it like '!', for
+	 * consistency with the regular expression syntax.
+	 * J.T. Conklin (conklin@ngai.kaleida.com)
+	 */
+	if ((negate = (*pattern == '!' || *pattern == '^')))
+		++pattern;
+
+	if (flags & FNM_CASEFOLD)
+		test = tolower((unsigned char)test);
+
+	/*
+	 * A right bracket shall lose its special meaning and represent
+	 * itself in a bracket expression if it occurs first in the list.
+	 * -- POSIX.2 2.8.3.2
+	 */
+	ok = 0;
+	c = *pattern++;
+	do {
+		if (c == '\\' && !(flags & FNM_NOESCAPE))
+			c = *pattern++;
+		if (c == EOS)
+			return (RANGE_ERROR);
+		if (c == '/' && (flags & FNM_PATHNAME))
+			return (RANGE_NOMATCH);
+		if ((flags & FNM_CASEFOLD))
+			c = tolower((unsigned char)c);
+		if (*pattern == '-'
+		    && (c2 = *(pattern+1)) != EOS && c2 != ']') {
+			pattern += 2;
+			if (c2 == '\\' && !(flags & FNM_NOESCAPE))
+				c2 = *pattern++;
+			if (c2 == EOS)
+				return (RANGE_ERROR);
+			if (flags & FNM_CASEFOLD)
+				c2 = tolower((unsigned char)c2);
+			if (c <= test && test <= c2)
+				ok = 1;
+		} else if (c == test)
+			ok = 1;
+	} while ((c = *pattern++) != ']');
+
+	*newp = (char *)pattern;
+	return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
+}
diff --git a/libtar/gethostbyname_r.c b/libtar/gethostbyname_r.c
new file mode 100644
index 0000000..5264b84
--- /dev/null
+++ b/libtar/gethostbyname_r.c
@@ -0,0 +1,41 @@
+/*
+**  Copyright 2002 University of Illinois Board of Trustees
+**  Copyright 2002 Mark D. Roth
+**  All rights reserved.
+**
+**  gethostbyname_r.c - gethostbyname_r() function for compatibility library
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <config.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <netdb.h>
+
+
+int
+compat_gethostbyname_r(const char *name, struct hostent *hp,
+		       char *buf, size_t buflen,
+		       struct hostent **hpp, int *herr)
+{
+#if GETHOSTBYNAME_R_NUM_ARGS == 5
+	*hpp = gethostbyname_r(name, hp, buf, buflen, herr);
+
+	if (*hpp == NULL)
+		return -1;
+	return 0;
+#elif GETHOSTBYNAME_R_NUM_ARGS == 3
+	struct hostent_data hdata;
+
+	if (gethostbyname_r(name, hp, &hdata) == -1)
+		return -1;
+	*hpp = hp;
+	return 0;
+#endif /* GETHOSTBYNAME_R_NUM_ARGS == 5 */
+}
+
+
diff --git a/libtar/gethostname.c b/libtar/gethostname.c
new file mode 100644
index 0000000..1abaae1
--- /dev/null
+++ b/libtar/gethostname.c
@@ -0,0 +1,36 @@
+/* gethostname.c: minimal substitute for missing gethostname() function
+ * created 2000-Mar-02 jmk
+ * requires SVR4 uname() and -lc
+ *
+ * by Jim Knoble <jmknoble@pobox.com>
+ * Copyright ? 2000 Jim Knoble
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without fee,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear in
+ * supporting documentation.
+ *
+ * This software is provided "as is", without warranty of any kind,
+ * express or implied, including but not limited to the warranties of
+ * merchantability, fitness for a particular purpose and
+ * noninfringement. In no event shall the author(s) be liable for any
+ * claim, damages or other liability, whether in an action of contract,
+ * tort or otherwise, arising from, out of or in connection with the
+ * software or the use or other dealings in the software.
+ */
+
+#include <string.h>
+#include <sys/utsname.h>
+
+int gethostname(char *name, size_t len)
+{
+   struct utsname u;
+   int status = uname(&u);
+   if (-1 != status) {
+      strncpy(name, u.nodename, len);
+      name[len - 1] = '\0';
+   }
+   return(status);
+}
+
diff --git a/libtar/getservbyname_r.c b/libtar/getservbyname_r.c
new file mode 100644
index 0000000..e386bc9
--- /dev/null
+++ b/libtar/getservbyname_r.c
@@ -0,0 +1,41 @@
+/*
+**  Copyright 2002 University of Illinois Board of Trustees
+**  Copyright 2002 Mark D. Roth
+**  All rights reserved.
+**
+**  getservbyname_r.c - getservbyname_r() function for compatibility library
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <config.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <netdb.h>
+
+
+int
+compat_getservbyname_r(const char *name, const char *proto,
+		       struct servent *sp, char *buf, size_t buflen,
+		       struct servent **spp)
+{
+#if GETSERVBYNAME_R_NUM_ARGS == 5
+	*spp = getservbyname_r(name, proto, sp, buf, buflen);
+
+	if (*spp == NULL)
+		return -1;
+	return 0;
+#elif GETSERVBYNAME_R_NUM_ARGS == 4
+	struct servent_data sdata;
+
+	if (getservbyname_r(name, proto, sp, &sdata) == -1)
+		return -1;
+	*spp = sp;
+	return 0;
+#endif /* GETSERVBYNAME_R_NUM_ARGS == 5 */
+}
+
+
diff --git a/libtar/glob.c b/libtar/glob.c
new file mode 100644
index 0000000..9ee235a
--- /dev/null
+++ b/libtar/glob.c
@@ -0,0 +1,873 @@
+/*
+ * Copyright (c) 1989, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)glob.c	8.3 (Berkeley) 10/13/93";
+#else
+static char rcsid[] = "$OpenBSD: glob.c,v 1.8 1998/08/14 21:39:30 deraadt Exp $";
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+/*
+ * glob(3) -- a superset of the one defined in POSIX 1003.2.
+ *
+ * The [!...] convention to negate a range is supported (SysV, Posix, ksh).
+ *
+ * Optional extra services, controlled by flags not defined by POSIX:
+ *
+ * GLOB_QUOTE:
+ *	Escaping convention: \ inhibits any special meaning the following
+ *	character might have (except \ at end of string is retained).
+ * GLOB_MAGCHAR:
+ *	Set in gl_flags if pattern contained a globbing character.
+ * GLOB_NOMAGIC:
+ *	Same as GLOB_NOCHECK, but it will only append pattern if it did
+ *	not contain any magic characters.  [Used in csh style globbing]
+ * GLOB_ALTDIRFUNC:
+ *	Use alternately specified directory access functions.
+ * GLOB_TILDE:
+ *	expand ~user/foo to the /home/dir/of/user/foo
+ * GLOB_BRACE:
+ *	expand {1,2}{a,b} to 1a 1b 2a 2b
+ * gl_matchc:
+ *	Number of matches in the current invocation of glob.
+ */
+
+#include <config.h>
+
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdio.h>
+
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <string.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <compat.h>
+
+
+#define	DOLLAR		'$'
+#define	DOT		'.'
+#define	EOS		'\0'
+#define	LBRACKET	'['
+#define	NOT		'!'
+#define	QUESTION	'?'
+#define	QUOTE		'\\'
+#define	RANGE		'-'
+#define	RBRACKET	']'
+#define	SEP		'/'
+#define	STAR		'*'
+#define	TILDE		'~'
+#define	UNDERSCORE	'_'
+#define	LBRACE		'{'
+#define	RBRACE		'}'
+#define	SLASH		'/'
+#define	COMMA		','
+
+#ifndef DEBUG
+
+#define	M_QUOTE		0x8000
+#define	M_PROTECT	0x4000
+#define	M_MASK		0xffff
+#define	M_ASCII		0x00ff
+
+typedef u_short Char;
+
+#else
+
+#define	M_QUOTE		0x80
+#define	M_PROTECT	0x40
+#define	M_MASK		0xff
+#define	M_ASCII		0x7f
+
+typedef char Char;
+
+#endif
+
+
+#define	CHAR(c)		((Char)((c)&M_ASCII))
+#define	META(c)		((Char)((c)|M_QUOTE))
+#define	M_ALL		META('*')
+#define	M_END		META(']')
+#define	M_NOT		META('!')
+#define	M_ONE		META('?')
+#define	M_RNG		META('-')
+#define	M_SET		META('[')
+#define	ismeta(c)	(((c)&M_QUOTE) != 0)
+
+
+static int	 compare (const void *, const void *);
+static void	 g_Ctoc (const Char *, char *);
+static int	 g_lstat (Char *, struct stat *, glob_t *);
+static DIR	*g_opendir (Char *, glob_t *);
+static Char	*g_strchr (Char *, int);
+#ifdef notdef
+static Char	*g_strcat (Char *, const Char *);
+#endif
+static int	 g_stat (Char *, struct stat *, glob_t *);
+static int	 glob0 (const Char *, glob_t *);
+static int	 glob1 (Char *, glob_t *);
+static int	 glob2 (Char *, Char *, Char *, glob_t *);
+static int	 glob3 (Char *, Char *, Char *, Char *, glob_t *);
+static int	 globextend (const Char *, glob_t *);
+static const Char *	globtilde (const Char *, Char *, size_t, glob_t *);
+static int	 globexp1 (const Char *, glob_t *);
+static int	 globexp2 (const Char *, const Char *, glob_t *, int *);
+static int	 match (Char *, Char *, Char *);
+#ifdef DEBUG
+static void	 qprintf (const char *, Char *);
+#endif
+
+int
+openbsd_glob(pattern, flags, errfunc, pglob)
+	const char *pattern;
+	int flags, (*errfunc) __P((const char *, int));
+	glob_t *pglob;
+{
+	const u_char *patnext;
+	int c;
+	Char *bufnext, *bufend, patbuf[MAXPATHLEN+1];
+
+	patnext = (u_char *) pattern;
+	if (!(flags & GLOB_APPEND)) {
+		pglob->gl_pathc = 0;
+		pglob->gl_pathv = NULL;
+		if (!(flags & GLOB_DOOFFS))
+			pglob->gl_offs = 0;
+	}
+	pglob->gl_flags = flags & ~GLOB_MAGCHAR;
+	pglob->gl_errfunc = errfunc;
+	pglob->gl_matchc = 0;
+
+	bufnext = patbuf;
+	bufend = bufnext + MAXPATHLEN;
+	if (flags & GLOB_NOESCAPE)
+	    while (bufnext < bufend && (c = *patnext++) != EOS)
+		    *bufnext++ = c;
+	else {
+		/* Protect the quoted characters. */
+		while (bufnext < bufend && (c = *patnext++) != EOS)
+			if (c == QUOTE) {
+				if ((c = *patnext++) == EOS) {
+					c = QUOTE;
+					--patnext;
+				}
+				*bufnext++ = c | M_PROTECT;
+			}
+			else
+				*bufnext++ = c;
+	}
+	*bufnext = EOS;
+
+	if (flags & GLOB_BRACE)
+		return globexp1(patbuf, pglob);
+	else
+		return glob0(patbuf, pglob);
+}
+
+/*
+ * Expand recursively a glob {} pattern. When there is no more expansion
+ * invoke the standard globbing routine to glob the rest of the magic
+ * characters
+ */
+static int globexp1(pattern, pglob)
+	const Char *pattern;
+	glob_t *pglob;
+{
+	const Char* ptr = pattern;
+	int rv;
+
+	/* Protect a single {}, for find(1), like csh */
+	if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)
+		return glob0(pattern, pglob);
+
+	while ((ptr = (const Char *) g_strchr((Char *) ptr, LBRACE)) != NULL)
+		if (!globexp2(ptr, pattern, pglob, &rv))
+			return rv;
+
+	return glob0(pattern, pglob);
+}
+
+
+/*
+ * Recursive brace globbing helper. Tries to expand a single brace.
+ * If it succeeds then it invokes globexp1 with the new pattern.
+ * If it fails then it tries to glob the rest of the pattern and returns.
+ */
+static int globexp2(ptr, pattern, pglob, rv)
+	const Char *ptr, *pattern;
+	glob_t *pglob;
+	int *rv;
+{
+	int     i;
+	Char   *lm, *ls;
+	const Char *pe, *pm, *pl;
+	Char    patbuf[MAXPATHLEN + 1];
+
+	/* copy part up to the brace */
+	for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
+		continue;
+	ls = lm;
+
+	/* Find the balanced brace */
+	for (i = 0, pe = ++ptr; *pe; pe++)
+		if (*pe == LBRACKET) {
+			/* Ignore everything between [] */
+			for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)
+				continue;
+			if (*pe == EOS) {
+				/*
+				 * We could not find a matching RBRACKET.
+				 * Ignore and just look for RBRACE
+				 */
+				pe = pm;
+			}
+		}
+		else if (*pe == LBRACE)
+			i++;
+		else if (*pe == RBRACE) {
+			if (i == 0)
+				break;
+			i--;
+		}
+
+	/* Non matching braces; just glob the pattern */
+	if (i != 0 || *pe == EOS) {
+		*rv = glob0(patbuf, pglob);
+		return 0;
+	}
+
+	for (i = 0, pl = pm = ptr; pm <= pe; pm++)
+		switch (*pm) {
+		case LBRACKET:
+			/* Ignore everything between [] */
+			for (pl = pm++; *pm != RBRACKET && *pm != EOS; pm++)
+				continue;
+			if (*pm == EOS) {
+				/*
+				 * We could not find a matching RBRACKET.
+				 * Ignore and just look for RBRACE
+				 */
+				pm = pl;
+			}
+			break;
+
+		case LBRACE:
+			i++;
+			break;
+
+		case RBRACE:
+			if (i) {
+			    i--;
+			    break;
+			}
+			/* FALLTHROUGH */
+		case COMMA:
+			if (i && *pm == COMMA)
+				break;
+			else {
+				/* Append the current string */
+				for (lm = ls; (pl < pm); *lm++ = *pl++)
+					continue;
+				/*
+				 * Append the rest of the pattern after the
+				 * closing brace
+				 */
+				for (pl = pe + 1; (*lm++ = *pl++) != EOS;)
+					continue;
+
+				/* Expand the current pattern */
+#ifdef DEBUG
+				qprintf("globexp2:", patbuf);
+#endif
+				*rv = globexp1(patbuf, pglob);
+
+				/* move after the comma, to the next string */
+				pl = pm + 1;
+			}
+			break;
+
+		default:
+			break;
+		}
+	*rv = 0;
+	return 0;
+}
+
+
+
+/*
+ * expand tilde from the passwd file.
+ */
+static const Char *
+globtilde(pattern, patbuf, patbuf_len, pglob)
+	const Char *pattern;
+	Char *patbuf;
+	size_t patbuf_len;
+	glob_t *pglob;
+{
+	struct passwd *pwd;
+	char *h;
+	const Char *p;
+	Char *b, *eb;
+
+	if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE))
+		return pattern;
+
+	/* Copy up to the end of the string or / */
+	eb = &patbuf[patbuf_len - 1];
+	for (p = pattern + 1, h = (char *) patbuf;
+	    h < (char *)eb && *p && *p != SLASH; *h++ = *p++)
+		continue;
+
+	*h = EOS;
+
+	if (((char *) patbuf)[0] == EOS) {
+		/*
+		 * handle a plain ~ or ~/ by expanding $HOME
+		 * first and then trying the password file
+		 */
+#ifdef HAVE_ISSETUGID
+		if (issetugid() != 0 || (h = getenv("HOME")) == NULL) {
+#endif
+			if ((pwd = getpwuid(getuid())) == NULL)
+				return pattern;
+			else
+				h = pwd->pw_dir;
+#ifdef HAVE_ISSETUGID
+		}
+#endif
+	}
+	else {
+		/*
+		 * Expand a ~user
+		 */
+		if ((pwd = getpwnam((char*) patbuf)) == NULL)
+			return pattern;
+		else
+			h = pwd->pw_dir;
+	}
+
+	/* Copy the home directory */
+	for (b = patbuf; b < eb && *h; *b++ = *h++)
+		continue;
+
+	/* Append the rest of the pattern */
+	while (b < eb && (*b++ = *p++) != EOS)
+		continue;
+	*b = EOS;
+
+	return patbuf;
+}
+
+
+/*
+ * The main glob() routine: compiles the pattern (optionally processing
+ * quotes), calls glob1() to do the real pattern matching, and finally
+ * sorts the list (unless unsorted operation is requested).  Returns 0
+ * if things went well, nonzero if errors occurred.  It is not an error
+ * to find no matches.
+ */
+static int
+glob0(pattern, pglob)
+	const Char *pattern;
+	glob_t *pglob;
+{
+	const Char *qpatnext;
+	int c, err, oldpathc;
+	Char *bufnext, patbuf[MAXPATHLEN+1];
+
+	qpatnext = globtilde(pattern, patbuf, sizeof(patbuf) / sizeof(Char),
+	    pglob);
+	oldpathc = pglob->gl_pathc;
+	bufnext = patbuf;
+
+	/* We don't need to check for buffer overflow any more. */
+	while ((c = *qpatnext++) != EOS) {
+		switch (c) {
+		case LBRACKET:
+			c = *qpatnext;
+			if (c == NOT)
+				++qpatnext;
+			if (*qpatnext == EOS ||
+			    g_strchr((Char *) qpatnext+1, RBRACKET) == NULL) {
+				*bufnext++ = LBRACKET;
+				if (c == NOT)
+					--qpatnext;
+				break;
+			}
+			*bufnext++ = M_SET;
+			if (c == NOT)
+				*bufnext++ = M_NOT;
+			c = *qpatnext++;
+			do {
+				*bufnext++ = CHAR(c);
+				if (*qpatnext == RANGE &&
+				    (c = qpatnext[1]) != RBRACKET) {
+					*bufnext++ = M_RNG;
+					*bufnext++ = CHAR(c);
+					qpatnext += 2;
+				}
+			} while ((c = *qpatnext++) != RBRACKET);
+			pglob->gl_flags |= GLOB_MAGCHAR;
+			*bufnext++ = M_END;
+			break;
+		case QUESTION:
+			pglob->gl_flags |= GLOB_MAGCHAR;
+			*bufnext++ = M_ONE;
+			break;
+		case STAR:
+			pglob->gl_flags |= GLOB_MAGCHAR;
+			/* collapse adjacent stars to one,
+			 * to avoid exponential behavior
+			 */
+			if (bufnext == patbuf || bufnext[-1] != M_ALL)
+			    *bufnext++ = M_ALL;
+			break;
+		default:
+			*bufnext++ = CHAR(c);
+			break;
+		}
+	}
+	*bufnext = EOS;
+#ifdef DEBUG
+	qprintf("glob0:", patbuf);
+#endif
+
+	if ((err = glob1(patbuf, pglob)) != 0)
+		return(err);
+
+	/*
+	 * If there was no match we are going to append the pattern
+	 * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified
+	 * and the pattern did not contain any magic characters
+	 * GLOB_NOMAGIC is there just for compatibility with csh.
+	 */
+	if (pglob->gl_pathc == oldpathc) {
+		if ((pglob->gl_flags & GLOB_NOCHECK) ||
+		    ((pglob->gl_flags & GLOB_NOMAGIC) &&
+		    !(pglob->gl_flags & GLOB_MAGCHAR)))
+			return(globextend(pattern, pglob));
+		else
+			return(GLOB_NOMATCH);
+	}
+	if (!(pglob->gl_flags & GLOB_NOSORT))
+		qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,
+		    pglob->gl_pathc - oldpathc, sizeof(char *), compare);
+	return(0);
+}
+
+static int
+compare(p, q)
+	const void *p, *q;
+{
+	return(strcmp(*(char **)p, *(char **)q));
+}
+
+static int
+glob1(pattern, pglob)
+	Char *pattern;
+	glob_t *pglob;
+{
+	Char pathbuf[MAXPATHLEN+1];
+
+	/* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
+	if (*pattern == EOS)
+		return(0);
+	return(glob2(pathbuf, pathbuf, pattern, pglob));
+}
+
+/*
+ * The functions glob2 and glob3 are mutually recursive; there is one level
+ * of recursion for each segment in the pattern that contains one or more
+ * meta characters.
+ */
+static int
+glob2(pathbuf, pathend, pattern, pglob)
+	Char *pathbuf, *pathend, *pattern;
+	glob_t *pglob;
+{
+	struct stat sb;
+	Char *p, *q;
+	int anymeta;
+
+	/*
+	 * Loop over pattern segments until end of pattern or until
+	 * segment with meta character found.
+	 */
+	for (anymeta = 0;;) {
+		if (*pattern == EOS) {		/* End of pattern? */
+			*pathend = EOS;
+			if (g_lstat(pathbuf, &sb, pglob))
+				return(0);
+
+			if (((pglob->gl_flags & GLOB_MARK) &&
+			    pathend[-1] != SEP) && (S_ISDIR(sb.st_mode)
+			    || (S_ISLNK(sb.st_mode) &&
+			    (g_stat(pathbuf, &sb, pglob) == 0) &&
+			    S_ISDIR(sb.st_mode)))) {
+				*pathend++ = SEP;
+				*pathend = EOS;
+			}
+			++pglob->gl_matchc;
+			return(globextend(pathbuf, pglob));
+		}
+
+		/* Find end of next segment, copy tentatively to pathend. */
+		q = pathend;
+		p = pattern;
+		while (*p != EOS && *p != SEP) {
+			if (ismeta(*p))
+				anymeta = 1;
+			*q++ = *p++;
+		}
+
+		if (!anymeta) {		/* No expansion, do next segment. */
+			pathend = q;
+			pattern = p;
+			while (*pattern == SEP)
+				*pathend++ = *pattern++;
+		} else			/* Need expansion, recurse. */
+			return(glob3(pathbuf, pathend, pattern, p, pglob));
+	}
+	/* NOTREACHED */
+}
+
+static int
+glob3(pathbuf, pathend, pattern, restpattern, pglob)
+	Char *pathbuf, *pathend, *pattern, *restpattern;
+	glob_t *pglob;
+{
+	register struct dirent *dp;
+	DIR *dirp;
+	int err;
+	char buf[MAXPATHLEN];
+
+	/*
+	 * The readdirfunc declaration can't be prototyped, because it is
+	 * assigned, below, to two functions which are prototyped in glob.h
+	 * and dirent.h as taking pointers to differently typed opaque
+	 * structures.
+	 */
+	struct dirent *(*readdirfunc)();
+
+	*pathend = EOS;
+	errno = 0;
+
+	if ((dirp = g_opendir(pathbuf, pglob)) == NULL) {
+		/* TODO: don't call for ENOENT or ENOTDIR? */
+		if (pglob->gl_errfunc) {
+			g_Ctoc(pathbuf, buf);
+			if (pglob->gl_errfunc(buf, errno) ||
+			    pglob->gl_flags & GLOB_ERR)
+				return (GLOB_ABORTED);
+		}
+		return(0);
+	}
+
+	err = 0;
+
+	/* Search directory for matching names. */
+	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
+		readdirfunc = pglob->gl_readdir;
+	else
+		readdirfunc = readdir;
+	while ((dp = (*readdirfunc)(dirp))) {
+		register u_char *sc;
+		register Char *dc;
+
+		/* Initial DOT must be matched literally. */
+		if (dp->d_name[0] == DOT && *pattern != DOT)
+			continue;
+		for (sc = (u_char *) dp->d_name, dc = pathend;
+		     (*dc++ = *sc++) != EOS;)
+			continue;
+		if (!match(pathend, pattern, restpattern)) {
+			*pathend = EOS;
+			continue;
+		}
+		err = glob2(pathbuf, --dc, restpattern, pglob);
+		if (err)
+			break;
+	}
+
+	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
+		(*pglob->gl_closedir)(dirp);
+	else
+		closedir(dirp);
+	return(err);
+}
+
+
+/*
+ * Extend the gl_pathv member of a glob_t structure to accomodate a new item,
+ * add the new item, and update gl_pathc.
+ *
+ * This assumes the BSD realloc, which only copies the block when its size
+ * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
+ * behavior.
+ *
+ * Return 0 if new item added, error code if memory couldn't be allocated.
+ *
+ * Invariant of the glob_t structure:
+ *	Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
+ *	gl_pathv points to (gl_offs + gl_pathc + 1) items.
+ */
+static int
+globextend(path, pglob)
+	const Char *path;
+	glob_t *pglob;
+{
+	register char **pathv;
+	register int i;
+	u_int newsize;
+	char *copy;
+	const Char *p;
+
+	newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs);
+	pathv = pglob->gl_pathv ?
+		    realloc((char *)pglob->gl_pathv, newsize) :
+		    malloc(newsize);
+	if (pathv == NULL) {
+		if (pglob->gl_pathv)
+			free(pglob->gl_pathv);
+		return(GLOB_NOSPACE);
+	}
+
+	if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {
+		/* first time around -- clear initial gl_offs items */
+		pathv += pglob->gl_offs;
+		for (i = pglob->gl_offs; --i >= 0; )
+			*--pathv = NULL;
+	}
+	pglob->gl_pathv = pathv;
+
+	for (p = path; *p++;)
+		continue;
+	if ((copy = malloc(p - path)) != NULL) {
+		g_Ctoc(path, copy);
+		pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
+	}
+	pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;
+	return(copy == NULL ? GLOB_NOSPACE : 0);
+}
+
+
+/*
+ * pattern matching function for filenames.  Each occurrence of the *
+ * pattern causes a recursion level.
+ */
+static int
+match(name, pat, patend)
+	register Char *name, *pat, *patend;
+{
+	int ok, negate_range;
+	Char c, k;
+
+	while (pat < patend) {
+		c = *pat++;
+		switch (c & M_MASK) {
+		case M_ALL:
+			if (pat == patend)
+				return(1);
+			do
+			    if (match(name, pat, patend))
+				    return(1);
+			while (*name++ != EOS);
+			return(0);
+		case M_ONE:
+			if (*name++ == EOS)
+				return(0);
+			break;
+		case M_SET:
+			ok = 0;
+			if ((k = *name++) == EOS)
+				return(0);
+			if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)
+				++pat;
+			while (((c = *pat++) & M_MASK) != M_END)
+				if ((*pat & M_MASK) == M_RNG) {
+					if (c <= k && k <= pat[1])
+						ok = 1;
+					pat += 2;
+				} else if (c == k)
+					ok = 1;
+			if (ok == negate_range)
+				return(0);
+			break;
+		default:
+			if (*name++ != c)
+				return(0);
+			break;
+		}
+	}
+	return(*name == EOS);
+}
+
+/* Free allocated data belonging to a glob_t structure. */
+void
+openbsd_globfree(pglob)
+	glob_t *pglob;
+{
+	register int i;
+	register char **pp;
+
+	if (pglob->gl_pathv != NULL) {
+		pp = pglob->gl_pathv + pglob->gl_offs;
+		for (i = pglob->gl_pathc; i--; ++pp)
+			if (*pp)
+				free(*pp);
+		free(pglob->gl_pathv);
+	}
+}
+
+static DIR *
+g_opendir(str, pglob)
+	register Char *str;
+	glob_t *pglob;
+{
+	char buf[MAXPATHLEN];
+
+	if (!*str)
+		strcpy(buf, ".");
+	else
+		g_Ctoc(str, buf);
+
+	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
+		return((*pglob->gl_opendir)(buf));
+
+	return(opendir(buf));
+}
+
+static int
+g_lstat(fn, sb, pglob)
+	register Char *fn;
+	struct stat *sb;
+	glob_t *pglob;
+{
+	char buf[MAXPATHLEN];
+
+	g_Ctoc(fn, buf);
+	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
+		return((*pglob->gl_lstat)(buf, sb));
+	return(lstat(buf, sb));
+}
+
+static int
+g_stat(fn, sb, pglob)
+	register Char *fn;
+	struct stat *sb;
+	glob_t *pglob;
+{
+	char buf[MAXPATHLEN];
+
+	g_Ctoc(fn, buf);
+	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
+		return((*pglob->gl_stat)(buf, sb));
+	return(stat(buf, sb));
+}
+
+static Char *
+g_strchr(str, ch)
+	Char *str;
+	int ch;
+{
+	do {
+		if (*str == ch)
+			return (str);
+	} while (*str++);
+	return (NULL);
+}
+
+#ifdef notdef
+static Char *
+g_strcat(dst, src)
+	Char *dst;
+	const Char* src;
+{
+	Char *sdst = dst;
+
+	while (*dst++)
+		continue;
+	--dst;
+	while((*dst++ = *src++) != EOS)
+	    continue;
+
+	return (sdst);
+}
+#endif
+
+static void
+g_Ctoc(str, buf)
+	register const Char *str;
+	char *buf;
+{
+	register char *dc;
+
+	for (dc = buf; (*dc++ = *str++) != EOS;)
+		continue;
+}
+
+#ifdef DEBUG
+static void
+qprintf(str, s)
+	const char *str;
+	register Char *s;
+{
+	register Char *p;
+
+	(void)printf("%s:\n", str);
+	for (p = s; *p; p++)
+		(void)printf("%c", CHAR(*p));
+	(void)printf("\n");
+	for (p = s; *p; p++)
+		(void)printf("%c", *p & M_PROTECT ? '"' : ' ');
+	(void)printf("\n");
+	for (p = s; *p; p++)
+		(void)printf("%c", ismeta(*p) ? '_' : ' ');
+	(void)printf("\n");
+}
+#endif
diff --git a/libtar/handle.c b/libtar/handle.c
new file mode 100644
index 0000000..a26c094
--- /dev/null
+++ b/libtar/handle.c
@@ -0,0 +1,132 @@
+/*
+**  Copyright 1998-2003 University of Illinois Board of Trustees
+**  Copyright 1998-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  handle.c - libtar code for initializing a TAR handle
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <internal.h>
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+#endif
+
+
+const char libtar_version[] = PACKAGE_VERSION;
+
+static tartype_t default_type = { open, close, read, write };
+
+
+static int
+tar_init(TAR **t, const char *pathname, tartype_t *type,
+	 int oflags, int mode __unused, int options)
+{
+	if ((oflags & O_ACCMODE) == O_RDWR)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	*t = (TAR *)calloc(1, sizeof(TAR));
+	if (*t == NULL)
+		return -1;
+
+	(*t)->pathname = pathname;
+	(*t)->options = options;
+	(*t)->type = (type ? type : &default_type);
+	(*t)->oflags = oflags;
+
+	if ((oflags & O_ACCMODE) == O_RDONLY)
+		(*t)->h = libtar_hash_new(256,
+					  (libtar_hashfunc_t)path_hashfunc);
+	else
+		(*t)->h = libtar_hash_new(16, (libtar_hashfunc_t)dev_hash);
+	if ((*t)->h == NULL)
+	{
+		free(*t);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+/* open a new tarfile handle */
+int
+tar_open(TAR **t, const char *pathname, tartype_t *type,
+	 int oflags, int mode, int options)
+{
+	if (tar_init(t, pathname, type, oflags, mode, options) == -1)
+		return -1;
+
+	if ((options & TAR_NOOVERWRITE) && (oflags & O_CREAT))
+		oflags |= O_EXCL;
+
+#ifdef O_BINARY
+	oflags |= O_BINARY;
+#endif
+
+	(*t)->fd = (*((*t)->type->openfunc))(pathname, oflags, mode);
+	if ((*t)->fd == -1)
+	{
+		libtar_hash_free((*t)->h, NULL);
+		free(*t);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+int
+tar_fdopen(TAR **t, int fd, const char *pathname, tartype_t *type,
+	   int oflags, int mode, int options)
+{
+	if (tar_init(t, pathname, type, oflags, mode, options) == -1)
+		return -1;
+
+	(*t)->fd = fd;
+	return 0;
+}
+
+
+int
+tar_fd(TAR *t)
+{
+	return t->fd;
+}
+
+
+/* close tarfile handle */
+int
+tar_close(TAR *t)
+{
+	int i;
+
+	i = (*(t->type->closefunc))(t->fd);
+
+	if (t->h != NULL)
+		libtar_hash_free(t->h, ((t->oflags & O_ACCMODE) == O_RDONLY
+					? free
+					: (libtar_freefunc_t)tar_dev_free));
+	if (t->th_pathname != NULL)
+		free(t->th_pathname);
+	free(t);
+
+	return i;
+}
+
+
diff --git a/libtar/inet_aton.c b/libtar/inet_aton.c
new file mode 100644
index 0000000..a935d5a
--- /dev/null
+++ b/libtar/inet_aton.c
@@ -0,0 +1,27 @@
+/*
+**  Copyright 2002 University of Illinois Board of Trustees
+**  Copyright 2002 Mark D. Roth
+**  All rights reserved.
+**
+**  inet_aton.c - inet_aton() function for compatibility library
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+
+int
+inet_aton(const char *cp, struct in_addr *inp)
+{
+	inp->s_addr = inet_addr(cp);
+	if (inp->s_addr == -1)
+		return 0;
+	return 1;
+}
+
+
diff --git a/libtar/internal.h b/libtar/internal.h
new file mode 100644
index 0000000..23243d2
--- /dev/null
+++ b/libtar/internal.h
@@ -0,0 +1,17 @@
+/*
+**  Copyright 2002-2003 University of Illinois Board of Trustees
+**  Copyright 2002-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  internal.h - internal header file for libtar
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <config.h>
+#include <compat.h>
+
+#include <libtar.h>
+
diff --git a/libtar/libtar.h b/libtar/libtar.h
new file mode 100755
index 0000000..04782b2
--- /dev/null
+++ b/libtar/libtar.h
@@ -0,0 +1,360 @@
+/*
+**  Copyright 1998-2003 University of Illinois Board of Trustees
+**  Copyright 1998-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  libtar.h - header file for libtar library
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#ifndef LIBTAR_H
+#define LIBTAR_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/capability.h>
+#include "tar.h"
+
+#include "libtar_listhash.h"
+
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+/* useful constants */
+/* see FIXME note in block.c regarding T_BLOCKSIZE */
+#define T_BLOCKSIZE		512
+#define T_NAMELEN		100
+#define T_PREFIXLEN		155
+#define T_MAXPATHLEN		(T_NAMELEN + T_PREFIXLEN)
+
+/* GNU extensions for typeflag */
+#define GNU_LONGNAME_TYPE	'L'
+#define GNU_LONGLINK_TYPE	'K'
+
+/* extended metadata for next file - used to store selinux_context */
+#define TH_EXT_TYPE		'x'
+#define TH_POL_TYPE_DO_NOT_USE		'p'
+
+/* our version of the tar header structure */
+struct tar_header
+{
+	char name[100];
+	char mode[8];
+	char uid[8];
+	char gid[8];
+	char size[12];
+	char mtime[12];
+	char chksum[8];
+	char typeflag;
+	char linkname[100];
+	char magic[6];
+	char version[2];
+	char uname[32];
+	char gname[32];
+	char devmajor[8];
+	char devminor[8];
+	char prefix[155];
+	char padding[12];
+	char *gnu_longname;
+	char *gnu_longlink;
+	char *selinux_context;
+#ifdef USE_FSCRYPT
+#ifdef USE_FSCRYPT_POLICY_V1
+	struct fscrypt_policy_v1 *fep;
+#else
+	struct fscrypt_policy_v2  *fep;
+#endif
+#endif
+	int has_cap_data;
+	struct vfs_cap_data cap_data;
+	int has_user_default;
+	int has_user_cache;
+	int has_user_code_cache;
+};
+
+
+/***** handle.c ************************************************************/
+
+typedef int (*openfunc_t)(const char *, int, ...);
+typedef int (*closefunc_t)(int);
+typedef ssize_t (*readfunc_t)(int, void *, size_t);
+typedef ssize_t (*writefunc_t)(int, const void *, size_t);
+
+typedef struct
+{
+	openfunc_t openfunc;
+	closefunc_t closefunc;
+	readfunc_t readfunc;
+	writefunc_t writefunc;
+}
+tartype_t;
+
+typedef struct
+{
+	tartype_t *type;
+	const char *pathname;
+	long fd;
+	int oflags;
+	int options;
+	struct tar_header th_buf;
+	libtar_hash_t *h;
+
+	/* introduced in libtar 1.2.21 */
+	char *th_pathname;
+}
+TAR;
+
+/* constant values for the TAR options field */
+#define TAR_GNU			 1	/* use GNU extensions */
+#define TAR_VERBOSE		 2	/* output file info to stdout */
+#define TAR_NOOVERWRITE		 4	/* don't overwrite existing files */
+#define TAR_IGNORE_EOT		 8	/* ignore double zero blocks as EOF */
+#define TAR_CHECK_MAGIC		16	/* check magic in file header */
+#define TAR_CHECK_VERSION	32	/* check version in file header */
+#define TAR_IGNORE_CRC		64	/* ignore CRC in file header */
+#define TAR_STORE_SELINUX	128	/* store selinux context */
+#define TAR_USE_NUMERIC_ID	256	/* favor numeric owner over names */
+
+#ifdef USE_FSCRYPT
+#define TAR_STORE_FSCRYPT_POL 512 /* store fscrypt crypto policy */
+#endif
+
+#define TAR_STORE_POSIX_CAP	1024	/* store posix file capabilities */
+#define TAR_STORE_ANDROID_USER_XATTR	2048	/* store android user.* xattr */
+
+/* this is obsolete - it's here for backwards-compatibility only */
+#define TAR_IGNORE_MAGIC	0
+
+extern const char libtar_version[];
+
+
+/* open a new tarfile handle */
+int tar_open(TAR **t, const char *pathname, tartype_t *type,
+	     int oflags, int mode, int options);
+
+/* make a tarfile handle out of a previously-opened descriptor */
+int tar_fdopen(TAR **t, int fd, const char *pathname, tartype_t *type,
+	       int oflags, int mode, int options);
+
+/* returns the descriptor associated with t */
+int tar_fd(TAR *t);
+
+/* close tarfile handle */
+int tar_close(TAR *t);
+
+
+/***** append.c ************************************************************/
+
+/* forward declaration to appease the compiler */
+struct tar_dev;
+
+/* cleanup function */
+void tar_dev_free(struct tar_dev *tdp);
+
+/* Appends a file to the tar archive.
+ * Arguments:
+ *    t        = TAR handle to append to
+ *    realname = path of file to append
+ *    savename = name to save the file under in the archive
+ */
+int tar_append_file(TAR *t, const char *realname, const char *savename);
+
+/* write EOF indicator */
+int tar_append_eof(TAR *t);
+
+/* add file contents to a tarchive */
+int tar_append_regfile(TAR *t, const char *realname);
+
+/* Appends in-memory file contents to a tarchive.
+ * Arguments:
+ *    t        = TAR handle to append to
+ *    savename = name to save the file under in the archive
+ *    mode     = mode
+ *    uid, gid = owner
+ *    buf, len = in-memory buffer
+ */
+int tar_append_file_contents(TAR *t, const char *savename, mode_t mode,
+                             uid_t uid, gid_t gid, void *buf, size_t len);
+
+/* add buffer to a tarchive */
+int tar_append_buffer(TAR *t, void *buf, size_t len);
+
+/***** block.c *************************************************************/
+
+/* macros for reading/writing tarchive blocks */
+#define tar_block_read(t, buf) \
+	(*((t)->type->readfunc))((t)->fd, (char *)(buf), T_BLOCKSIZE)
+#define tar_block_write(t, buf) \
+	(*((t)->type->writefunc))((t)->fd, (char *)(buf), T_BLOCKSIZE)
+
+/* read/write a header block */
+int th_read(TAR *t);
+int th_write(TAR *t);
+
+
+/***** decode.c ************************************************************/
+
+/* determine file type */
+#define TH_ISREG(t)	((t)->th_buf.typeflag == REGTYPE \
+			 || (t)->th_buf.typeflag == AREGTYPE \
+			 || (t)->th_buf.typeflag == CONTTYPE \
+			 || (S_ISREG((mode_t)oct_to_int((t)->th_buf.mode, sizeof((t)->th_buf.mode))) \
+			     && (t)->th_buf.typeflag != LNKTYPE))
+#define TH_ISLNK(t)	((t)->th_buf.typeflag == LNKTYPE)
+#define TH_ISSYM(t)	((t)->th_buf.typeflag == SYMTYPE \
+			 || S_ISLNK((mode_t)oct_to_int((t)->th_buf.mode, sizeof((t)->th_buf.mode))))
+#define TH_ISCHR(t)	((t)->th_buf.typeflag == CHRTYPE \
+			 || S_ISCHR((mode_t)oct_to_int((t)->th_buf.mode, sizeof((t)->th_buf.mode))))
+#define TH_ISBLK(t)	((t)->th_buf.typeflag == BLKTYPE \
+			 || S_ISBLK((mode_t)oct_to_int((t)->th_buf.mode, sizeof((t)->th_buf.mode))))
+#define TH_ISDIR(t)	((t)->th_buf.typeflag == DIRTYPE \
+			 || S_ISDIR((mode_t)oct_to_int((t)->th_buf.mode, sizeof((t)->th_buf.mode))) \
+			 || ((t)->th_buf.typeflag == AREGTYPE \
+			     && strnlen((t)->th_buf.name, T_NAMELEN) \
+			     && ((t)->th_buf.name[strnlen((t)->th_buf.name, T_NAMELEN) - 1] == '/')))
+#define TH_ISFIFO(t)	((t)->th_buf.typeflag == FIFOTYPE \
+			 || S_ISFIFO((mode_t)oct_to_int((t)->th_buf.mode, sizeof((t)->th_buf.mode))))
+#define TH_ISLONGNAME(t)	((t)->th_buf.typeflag == GNU_LONGNAME_TYPE)
+#define TH_ISLONGLINK(t)	((t)->th_buf.typeflag == GNU_LONGLINK_TYPE)
+#define TH_ISEXTHEADER(t)	((t)->th_buf.typeflag == TH_EXT_TYPE)
+#define TH_ISPOLHEADER(t)	((t)->th_buf.typeflag == TH_POL_TYPE_DO_NOT_USE)
+
+/* decode tar header info */
+#define th_get_crc(t) oct_to_int((t)->th_buf.chksum, sizeof((t)->th_buf.chksum))
+#define th_get_size(t) oct_to_int_ex((t)->th_buf.size, sizeof((t)->th_buf.size))
+#define th_get_mtime(t) oct_to_int_ex((t)->th_buf.mtime, sizeof((t)->th_buf.mtime))
+#define th_get_devmajor(t) oct_to_int((t)->th_buf.devmajor, sizeof((t)->th_buf.devmajor))
+#define th_get_devminor(t) oct_to_int((t)->th_buf.devminor, sizeof((t)->th_buf.devminor))
+#define th_get_linkname(t) ((t)->th_buf.gnu_longlink \
+                            ? (t)->th_buf.gnu_longlink \
+                            : (t)->th_buf.linkname)
+char *th_get_pathname(TAR *t);
+mode_t th_get_mode(TAR *t);
+uid_t th_get_uid(TAR *t);
+gid_t th_get_gid(TAR *t);
+
+
+/***** encode.c ************************************************************/
+
+/* encode file info in th_header */
+void th_set_type(TAR *t, mode_t mode);
+void th_set_path(TAR *t, const char *pathname);
+void th_set_link(TAR *t, const char *linkname);
+void th_set_device(TAR *t, dev_t device);
+void th_set_user(TAR *t, uid_t uid);
+void th_set_group(TAR *t, gid_t gid);
+void th_set_mode(TAR *t, mode_t fmode);
+#define th_set_mtime(t, fmtime) \
+	int_to_oct_ex((fmtime), (t)->th_buf.mtime, sizeof((t)->th_buf.mtime))
+#define th_set_size(t, fsize) \
+	int_to_oct_ex((fsize), (t)->th_buf.size, sizeof((t)->th_buf.size))
+
+/* encode everything at once (except the pathname and linkname) */
+void th_set_from_stat(TAR *t, struct stat *s);
+
+/* encode magic, version, and crc - must be done after everything else is set */
+void th_finish(TAR *t);
+
+
+/***** extract.c ***********************************************************/
+
+/* sequentially extract next file from t */
+int tar_extract_file(TAR *t, const char *realname, const char *prefix, const int *progress_fd);
+
+/* extract different file types */
+int tar_extract_dir(TAR *t, const char *realname);
+int tar_extract_hardlink(TAR *t, const char *realname, const char *prefix);
+int tar_extract_symlink(TAR *t, const char *realname);
+int tar_extract_chardev(TAR *t, const char *realname);
+int tar_extract_blockdev(TAR *t, const char *realname);
+int tar_extract_fifo(TAR *t, const char *realname);
+
+/* for regfiles, we need to extract the content blocks as well */
+int tar_extract_regfile(TAR *t, const char *realname, const int *progress_fd);
+int tar_skip_regfile(TAR *t);
+
+/* extract regfile to buffer */
+int tar_extract_file_contents(TAR *t, void *buf, size_t *lenp);
+
+/***** output.c ************************************************************/
+
+/* print the tar header */
+void th_print(TAR *t);
+
+/* print "ls -l"-like output for the file described by th */
+void th_print_long_ls(TAR *t);
+
+
+/***** util.c *************************************************************/
+
+/* hashing function for pathnames */
+int path_hashfunc(char *key, int numbuckets);
+
+/* matching function for dev_t's */
+int dev_match(dev_t *dev1, dev_t *dev2);
+
+/* matching function for ino_t's */
+int ino_match(ino_t *ino1, ino_t *ino2);
+
+/* hashing function for dev_t's */
+int dev_hash(dev_t *dev);
+
+/* hashing function for ino_t's */
+int ino_hash(ino_t *inode);
+
+/* create any necessary dirs */
+int mkdirhier(char *path);
+
+/* calculate header checksum */
+int th_crc_calc(TAR *t);
+
+/* calculate a signed header checksum */
+int th_signed_crc_calc(TAR *t);
+
+/* compare checksums in a forgiving way */
+#define th_crc_ok(t) (th_get_crc(t) == th_crc_calc(t) || th_get_crc(t) == th_signed_crc_calc(t))
+
+/* string-octal to integer conversion */
+int64_t oct_to_int(char *oct, size_t len);
+
+/* string-octal or binary to integer conversion */
+int64_t oct_to_int_ex(char *oct, size_t len);
+
+/* integer to NULL-terminated string-octal conversion */
+void int_to_oct(int64_t num, char *oct, size_t octlen);
+
+/* integer to string-octal conversion, or binary as necessary */
+void int_to_oct_ex(int64_t num, char *oct, size_t octlen);
+
+/* prints posix file capabilities */
+void print_caps(struct vfs_cap_data *cap_data);
+
+
+/***** wrapper.c **********************************************************/
+
+/* extract groups of files */
+int tar_extract_glob(TAR *t, char *globname, char *prefix);
+int tar_extract_all(TAR *t, char *prefix, const int *progress_fd);
+
+/* add a whole tree of files */
+int tar_append_tree(TAR *t, char *realdir, char *savedir);
+
+/* find an entry */
+int tar_find(TAR *t, char *searchstr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ! LIBTAR_H */
+
diff --git a/libtar/libtar_hash.c b/libtar/libtar_hash.c
new file mode 100644
index 0000000..796ebbe
--- /dev/null
+++ b/libtar/libtar_hash.c
@@ -0,0 +1,344 @@
+/* listhash/libtar_hash.c.  Generated from hash.c.in by configure. */
+
+/*
+**  Copyright 1998-2002 University of Illinois Board of Trustees
+**  Copyright 1998-2002 Mark D. Roth
+**  All rights reserved. 
+**
+**  libtar_hash.c - hash table routines
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <config.h>
+#include <compat.h>
+
+#include <libtar_listhash.h>
+
+#include <stdio.h>
+#include <errno.h>
+
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+#endif
+
+
+/*
+** libtar_hashptr_reset() - reset a hash pointer
+*/
+void
+libtar_hashptr_reset(libtar_hashptr_t *hp)
+{
+	libtar_listptr_reset(&(hp->node));
+	hp->bucket = -1;
+}
+
+
+/*
+** libtar_hashptr_data() - retrieve the data being pointed to
+*/
+void *
+libtar_hashptr_data(libtar_hashptr_t *hp)
+{
+	return libtar_listptr_data(&(hp->node));
+}
+
+
+/*
+** libtar_str_hashfunc() - default hash function, optimized for
+**				      7-bit strings
+*/
+unsigned int
+libtar_str_hashfunc(char *key, unsigned int num_buckets)
+{
+#if 0
+	register unsigned result = 0;
+	register int i;
+
+	if (key == NULL)
+		return 0;
+
+	for (i = 0; *key != '\0' && i < 32; i++)
+		result = result * 33U + *key++;
+
+	return (result % num_buckets);
+#else
+	if (key == NULL)
+		return 0;
+
+	return (key[0] % num_buckets);
+#endif
+}
+
+
+/*
+** libtar_hash_nents() - return number of elements from hash
+*/
+unsigned int
+libtar_hash_nents(libtar_hash_t *h)
+{
+	return h->nents;
+}
+
+
+/*
+** libtar_hash_new() - create a new hash
+*/
+libtar_hash_t *
+libtar_hash_new(int num, libtar_hashfunc_t hashfunc)
+{
+	libtar_hash_t *hash;
+
+	hash = (libtar_hash_t *)calloc(1, sizeof(libtar_hash_t));
+	if (hash == NULL)
+		return NULL;
+	hash->numbuckets = num;
+	if (hashfunc != NULL)
+		hash->hashfunc = hashfunc;
+	else
+		hash->hashfunc = (libtar_hashfunc_t)libtar_str_hashfunc;
+
+	hash->table = (libtar_list_t **)calloc(num, sizeof(libtar_list_t *));
+	if (hash->table == NULL)
+	{
+		free(hash);
+		return NULL;
+	}
+
+	return hash;
+}
+
+
+/*
+** libtar_hash_next() - get next element in hash
+** returns:
+**	1			data found
+**	0			end of list
+*/
+int
+libtar_hash_next(libtar_hash_t *h,
+			    libtar_hashptr_t *hp)
+{
+#ifdef DS_DEBUG
+	printf("==> libtar_hash_next(h=0x%lx, hp={%d,0x%lx})\n",
+	       h, hp->bucket, hp->node);
+#endif
+
+	if (hp->bucket >= 0 && hp->node != NULL &&
+	    libtar_list_next(h->table[hp->bucket], &(hp->node)) != 0)
+	{
+#ifdef DS_DEBUG
+		printf("    libtar_hash_next(): found additional "
+		       "data in current bucket (%d), returing 1\n",
+		       hp->bucket);
+#endif
+		return 1;
+	}
+
+#ifdef DS_DEBUG
+	printf("    libtar_hash_next(): done with bucket %d\n",
+	       hp->bucket);
+#endif
+
+	for (hp->bucket++; hp->bucket < h->numbuckets; hp->bucket++)
+	{
+#ifdef DS_DEBUG
+		printf("    libtar_hash_next(): "
+		       "checking bucket %d\n", hp->bucket);
+#endif
+		hp->node = NULL;
+		if (h->table[hp->bucket] != NULL &&
+		    libtar_list_next(h->table[hp->bucket],
+		    				&(hp->node)) != 0)
+		{
+#ifdef DS_DEBUG
+			printf("    libtar_hash_next(): "
+			       "found data in bucket %d, returing 1\n",
+			       hp->bucket);
+#endif
+			return 1;
+		}
+	}
+
+	if (hp->bucket == h->numbuckets)
+	{
+#ifdef DS_DEBUG
+		printf("    libtar_hash_next(): hash pointer "
+		       "wrapped to 0\n");
+#endif
+		hp->bucket = -1;
+		hp->node = NULL;
+	}
+
+#ifdef DS_DEBUG
+	printf("<== libtar_hash_next(): no more data, "
+	       "returning 0\n");
+#endif
+	return 0;
+}
+
+
+/*
+** libtar_hash_del() - delete an entry from the hash
+** returns:
+**	0			success
+**	-1 (and sets errno)	failure
+*/
+int
+libtar_hash_del(libtar_hash_t *h,
+			   libtar_hashptr_t *hp)
+{
+	if (hp->bucket < 0
+	    || hp->bucket >= h->numbuckets
+	    || h->table[hp->bucket] == NULL
+	    || hp->node == NULL)
+	{
+		errno = EINVAL;
+		return -1;
+	}
+
+	libtar_list_del(h->table[hp->bucket], &(hp->node));
+	h->nents--;
+	return 0;
+}
+
+
+/*
+** libtar_hash_empty() - empty the hash
+*/
+void
+libtar_hash_empty(libtar_hash_t *h, libtar_freefunc_t freefunc)
+{
+	int i;
+
+	for (i = 0; i < h->numbuckets; i++)
+		if (h->table[i] != NULL)
+			libtar_list_empty(h->table[i], freefunc);
+
+	h->nents = 0;
+}
+
+
+/*
+** libtar_hash_free() - delete all of the nodes in the hash
+*/
+void
+libtar_hash_free(libtar_hash_t *h, libtar_freefunc_t freefunc)
+{
+	int i;
+
+	for (i = 0; i < h->numbuckets; i++)
+		if (h->table[i] != NULL)
+			libtar_list_free(h->table[i], freefunc);
+
+	free(h->table);
+	free(h);
+}
+
+
+/*
+** libtar_hash_search() - iterative search for an element in a hash
+** returns:
+**	1			match found
+**	0			no match
+*/
+int
+libtar_hash_search(libtar_hash_t *h,
+			      libtar_hashptr_t *hp, void *data,
+			      libtar_matchfunc_t matchfunc)
+{
+	while (libtar_hash_next(h, hp) != 0)
+		if ((*matchfunc)(data, libtar_listptr_data(&(hp->node))) != 0)
+			return 1;
+
+	return 0;
+}
+
+
+/*
+** libtar_hash_getkey() - hash-based search for an element in a hash
+** returns:
+**	1			match found
+**	0			no match
+*/
+int
+libtar_hash_getkey(libtar_hash_t *h,
+			      libtar_hashptr_t *hp, void *key,
+			      libtar_matchfunc_t matchfunc)
+{
+#ifdef DS_DEBUG
+	printf("==> libtar_hash_getkey(h=0x%lx, hp={%d,0x%lx}, "
+	       "key=0x%lx, matchfunc=0x%lx)\n",
+	       h, hp->bucket, hp->node, key, matchfunc);
+#endif
+
+	if (hp->bucket == -1)
+	{
+		hp->bucket = (*(h->hashfunc))(key, h->numbuckets);
+#ifdef DS_DEBUG
+		printf("    libtar_hash_getkey(): hp->bucket "
+		       "set to %d\n", hp->bucket);
+#endif
+	}
+
+	if (h->table[hp->bucket] == NULL)
+	{
+#ifdef DS_DEBUG
+		printf("    libtar_hash_getkey(): no list "
+		       "for bucket %d, returning 0\n", hp->bucket);
+#endif
+		hp->bucket = -1;
+		return 0;
+	}
+
+#ifdef DS_DEBUG
+	printf("<== libtar_hash_getkey(): "
+	       "returning libtar_list_search()\n");
+#endif
+	return libtar_list_search(h->table[hp->bucket], &(hp->node),
+					     key, matchfunc);
+}
+
+
+/*
+** libtar_hash_add() - add an element to the hash
+** returns:
+**	0			success
+**	-1 (and sets errno)	failure
+*/
+int
+libtar_hash_add(libtar_hash_t *h, void *data)
+{
+	int bucket, i;
+
+#ifdef DS_DEBUG
+	printf("==> libtar_hash_add(h=0x%lx, data=0x%lx)\n",
+	       h, data);
+#endif
+
+	bucket = (*(h->hashfunc))(data, h->numbuckets);
+#ifdef DS_DEBUG
+	printf("    libtar_hash_add(): inserting in bucket %d\n",
+	       bucket);
+#endif
+	if (h->table[bucket] == NULL)
+	{
+#ifdef DS_DEBUG
+		printf("    libtar_hash_add(): creating new list\n");
+#endif
+		h->table[bucket] = libtar_list_new(LIST_QUEUE, NULL);
+	}
+
+#ifdef DS_DEBUG
+	printf("<== libtar_hash_add(): "
+	       "returning libtar_list_add()\n");
+#endif
+	i = libtar_list_add(h->table[bucket], data);
+	if (i == 0)
+		h->nents++;
+	return i;
+}
+
+
diff --git a/libtar/libtar_list.c b/libtar/libtar_list.c
new file mode 100644
index 0000000..2c5febb
--- /dev/null
+++ b/libtar/libtar_list.c
@@ -0,0 +1,458 @@
+/* listhash/libtar_list.c.  Generated from list.c.in by configure. */
+
+/*
+**  Copyright 1998-2002 University of Illinois Board of Trustees
+**  Copyright 1998-2002 Mark D. Roth
+**  All rights reserved.
+**
+**  libtar_list.c - linked list routines
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <config.h>
+#include <compat.h>
+
+#include <libtar_listhash.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <sys/param.h>
+
+#ifdef STDC_HEADERS
+# include <string.h>
+# include <stdlib.h>
+#endif
+
+
+/*
+** libtar_listptr_reset() - reset a list pointer
+*/
+void
+libtar_listptr_reset(libtar_listptr_t *lp)
+{
+	*lp = NULL;
+}
+
+
+/*
+** libtar_listptr_data() - retrieve the data pointed to by lp
+*/
+void *
+libtar_listptr_data(libtar_listptr_t *lp)
+{
+	return (*lp)->data;
+}
+
+
+/*
+** libtar_list_new() - create a new, empty list
+*/
+libtar_list_t *
+libtar_list_new(int flags, libtar_cmpfunc_t cmpfunc)
+{
+	libtar_list_t *newlist;
+
+#ifdef DS_DEBUG
+	printf("in libtar_list_new(%d, 0x%lx)\n", flags, cmpfunc);
+#endif
+
+	if (flags != LIST_USERFUNC
+	    && flags != LIST_STACK
+	    && flags != LIST_QUEUE)
+	{
+		errno = EINVAL;
+		return NULL;
+	}
+
+	newlist = (libtar_list_t *)calloc(1, sizeof(libtar_list_t));
+	if (cmpfunc != NULL)
+		newlist->cmpfunc = cmpfunc;
+	else
+		newlist->cmpfunc = (libtar_cmpfunc_t)strcmp;
+	newlist->flags = flags;
+
+	return newlist;
+}
+
+
+/*
+** libtar_list_iterate() - call a function for every element
+**				      in a list
+*/
+int
+libtar_list_iterate(libtar_list_t *l,
+			       libtar_iterate_func_t plugin,
+			       void *state)
+{
+	libtar_listptr_t n;
+
+	if (l == NULL)
+		return -1;
+
+	for (n = l->first; n != NULL; n = n->next)
+	{
+		if ((*plugin)(n->data, state) == -1)
+			return -1;
+	}
+
+	return 0;
+}
+
+
+/*
+** libtar_list_empty() - empty the list
+*/
+void
+libtar_list_empty(libtar_list_t *l, libtar_freefunc_t freefunc)
+{
+	libtar_listptr_t n;
+
+	for (n = l->first; n != NULL; n = l->first)
+	{
+		l->first = n->next;
+		if (freefunc != NULL)
+			(*freefunc)(n->data);
+		free(n);
+	}
+
+	l->nents = 0;
+}
+
+
+/*
+** libtar_list_free() - remove and free() the whole list
+*/
+void
+libtar_list_free(libtar_list_t *l, libtar_freefunc_t freefunc)
+{
+	libtar_list_empty(l, freefunc);
+	free(l);
+}
+
+
+/*
+** libtar_list_nents() - return number of elements in the list
+*/
+unsigned int
+libtar_list_nents(libtar_list_t *l)
+{
+	return l->nents;
+}
+
+
+/*
+** libtar_list_add() - adds an element to the list
+** returns:
+**	0			success
+**	-1 (and sets errno)	failure
+*/
+int
+libtar_list_add(libtar_list_t *l, void *data)
+{
+	libtar_listptr_t n, m;
+
+#ifdef DS_DEBUG
+	printf("==> libtar_list_add(\"%s\")\n", (char *)data);
+#endif
+
+	n = (libtar_listptr_t)malloc(sizeof(struct libtar_node));
+	if (n == NULL)
+		return -1;
+	n->data = data;
+	l->nents++;
+
+#ifdef DS_DEBUG
+	printf("    libtar_list_add(): allocated data\n");
+#endif
+
+	/* if the list is empty */
+	if (l->first == NULL)
+	{
+		l->last = l->first = n;
+		n->next = n->prev = NULL;
+#ifdef DS_DEBUG
+		printf("<== libtar_list_add(): list was empty; "
+		       "added first element and returning 0\n");
+#endif
+		return 0;
+	}
+
+#ifdef DS_DEBUG
+	printf("    libtar_list_add(): list not empty\n");
+#endif
+
+	if (l->flags == LIST_STACK)
+	{
+		n->prev = NULL;
+		n->next = l->first;
+		if (l->first != NULL)
+			l->first->prev = n;
+		l->first = n;
+#ifdef DS_DEBUG
+		printf("<== libtar_list_add(): LIST_STACK set; "
+		       "added in front\n");
+#endif
+		return 0;
+	}
+
+	if (l->flags == LIST_QUEUE)
+	{
+		n->prev = l->last;
+		n->next = NULL;
+		if (l->last != NULL)
+			l->last->next = n;
+		l->last = n;
+#ifdef DS_DEBUG
+		printf("<== libtar_list_add(): LIST_QUEUE set; "
+		       "added at end\n");
+#endif
+		return 0;
+	}
+
+	for (m = l->first; m != NULL; m = m->next)
+		if ((*(l->cmpfunc))(data, m->data) < 0)
+		{
+			/*
+			** if we find one that's bigger,
+			** insert data before it
+			*/
+#ifdef DS_DEBUG
+			printf("    libtar_list_add(): gotcha..."
+			       "inserting data\n");
+#endif
+			if (m == l->first)
+			{
+				l->first = n;
+				n->prev = NULL;
+				m->prev = n;
+				n->next = m;
+#ifdef DS_DEBUG
+				printf("<== libtar_list_add(): "
+				       "added first, returning 0\n");
+#endif
+				return 0;
+			}
+			m->prev->next = n;
+			n->prev = m->prev;
+			m->prev = n;
+			n->next = m;
+#ifdef DS_DEBUG
+			printf("<== libtar_list_add(): added middle,"
+			       " returning 0\n");
+#endif
+			return 0;
+		}
+
+#ifdef DS_DEBUG
+	printf("    libtar_list_add(): new data larger than current "
+	       "list elements\n");
+#endif
+
+	/* if we get here, data is bigger than everything in the list */
+	l->last->next = n;
+	n->prev = l->last;
+	l->last = n;
+	n->next = NULL;
+#ifdef DS_DEBUG
+	printf("<== libtar_list_add(): added end, returning 0\n");
+#endif
+	return 0;
+}
+
+
+/*
+** libtar_list_del() - remove the element pointed to by n
+**				  from the list l
+*/
+void
+libtar_list_del(libtar_list_t *l, libtar_listptr_t *n)
+{
+	libtar_listptr_t m;
+
+#ifdef DS_DEBUG
+	printf("==> libtar_list_del()\n");
+#endif
+
+	l->nents--;
+
+	m = (*n)->next;
+
+	if ((*n)->prev)
+		(*n)->prev->next = (*n)->next;
+	else
+		l->first = (*n)->next;
+	if ((*n)->next)
+		(*n)->next->prev = (*n)->prev;
+	else
+		l->last = (*n)->prev;
+
+	free(*n);
+	*n = m;
+}
+
+
+/*
+** libtar_list_next() - get the next element in the list
+** returns:
+**	1			success
+**	0			end of list
+*/
+int
+libtar_list_next(libtar_list_t *l,
+			    libtar_listptr_t *n)
+{
+	if (*n == NULL)
+		*n = l->first;
+	else
+		*n = (*n)->next;
+
+	return (*n != NULL ? 1 : 0);
+}
+
+
+/*
+** libtar_list_prev() - get the previous element in the list
+** returns:
+**	1			success
+**	0			end of list
+*/
+int
+libtar_list_prev(libtar_list_t *l,
+			    libtar_listptr_t *n)
+{
+	if (*n == NULL)
+		*n = l->last;
+	else
+		*n = (*n)->prev;
+
+	return (*n != NULL ? 1 : 0);
+}
+
+
+/*
+** libtar_str_match() - string matching function
+** returns:
+**	1			match
+**	0			no match
+*/
+int
+libtar_str_match(char *check, char *data)
+{
+	return !strcmp(check, data);
+}
+
+
+/*
+** libtar_list_add_str() - splits string str into delim-delimited
+**				      elements and adds them to list l
+** returns:
+**	0			success
+**	-1 (and sets errno)	failure
+*/
+int
+libtar_list_add_str(libtar_list_t *l,
+			       char *str, char *delim)
+{
+	char tmp[10240];
+	char *tokp, *nextp = tmp;
+
+	strlcpy(tmp, str, sizeof(tmp));
+	while ((tokp = strsep(&nextp, delim)) != NULL)
+	{
+		if (*tokp == '\0')
+			continue;
+		if (libtar_list_add(l, strdup(tokp)))
+			return -1;
+	}
+
+	return 0;
+}
+
+
+/*
+** libtar_list_search() - find an entry in a list
+** returns:
+**	1			match found
+**	0			no match
+*/
+int
+libtar_list_search(libtar_list_t *l,
+			      libtar_listptr_t *n, void *data,
+			      libtar_matchfunc_t matchfunc)
+{
+#ifdef DS_DEBUG
+	printf("==> libtar_list_search(l=0x%lx, n=0x%lx, \"%s\")\n",
+	       l, n, (char *)data);
+#endif
+
+	if (matchfunc == NULL)
+		matchfunc = (libtar_matchfunc_t)libtar_str_match;
+
+	if (*n == NULL)
+		*n = l->first;
+	else
+		*n = (*n)->next;
+
+	for (; *n != NULL; *n = (*n)->next)
+	{
+#ifdef DS_DEBUG
+		printf("checking against \"%s\"\n", (char *)(*n)->data);
+#endif
+		if ((*(matchfunc))(data, (*n)->data) != 0)
+			return 1;
+	}
+
+#ifdef DS_DEBUG
+	printf("no matches found\n");
+#endif
+	return 0;
+}
+
+
+/*
+** libtar_list_dup() - copy an existing list
+*/
+libtar_list_t *
+libtar_list_dup(libtar_list_t *l)
+{
+	libtar_list_t *newlist;
+	libtar_listptr_t n;
+
+	newlist = libtar_list_new(l->flags, l->cmpfunc);
+	for (n = l->first; n != NULL; n = n->next)
+		libtar_list_add(newlist, n->data);
+
+#ifdef DS_DEBUG
+	printf("returning from libtar_list_dup()\n");
+#endif
+	return newlist;
+}
+
+
+/*
+** libtar_list_merge() - merge two lists into a new list
+*/
+libtar_list_t *
+libtar_list_merge(libtar_cmpfunc_t cmpfunc, int flags,
+			     libtar_list_t *list1,
+			     libtar_list_t *list2)
+{
+	libtar_list_t *newlist;
+	libtar_listptr_t n;
+
+	newlist = libtar_list_new(flags, cmpfunc);
+
+	n = NULL;
+	while (libtar_list_next(list1, &n) != 0)
+		libtar_list_add(newlist, n->data);
+	n = NULL;
+	while (libtar_list_next(list2, &n) != 0)
+		libtar_list_add(newlist, n->data);
+
+	return newlist;
+}
+
+
diff --git a/libtar/libtar_listhash.h b/libtar/libtar_listhash.h
new file mode 100644
index 0000000..48c0d74
--- /dev/null
+++ b/libtar/libtar_listhash.h
@@ -0,0 +1,203 @@
+/* listhash/libtar_listhash.h.  Generated from listhash.h.in by configure. */
+
+/*
+**  Copyright 1998-2002 University of Illinois Board of Trustees
+**  Copyright 1998-2002 Mark D. Roth
+**  All rights reserved.
+**
+**  libtar_listhash.h - header file for listhash module
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#ifndef libtar_LISTHASH_H
+#define libtar_LISTHASH_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/***** list.c **********************************************************/
+
+/*
+** Comparison function (used to determine order of elements in a list)
+** returns less than, equal to, or greater than 0
+** if data1 is less than, equal to, or greater than data2
+*/
+typedef int (*libtar_cmpfunc_t)(void *, void *);
+
+/*
+** Free function (for freeing allocated memory in each element)
+*/
+typedef void (*libtar_freefunc_t)(void *);
+
+/*
+** Plugin function for libtar_list_iterate()
+*/
+typedef int (*libtar_iterate_func_t)(void *, void *);
+
+/*
+** Matching function (used to find elements in a list)
+** first argument is the data to search for
+** second argument is the list element it's being compared to
+** returns 0 if no match is found, non-zero otherwise
+*/
+typedef int (*libtar_matchfunc_t)(void *, void *);
+
+
+struct libtar_node
+{
+	void *data;
+	struct libtar_node *next;
+	struct libtar_node *prev;
+};
+typedef struct libtar_node *libtar_listptr_t;
+
+struct libtar_list
+{
+	libtar_listptr_t first;
+	libtar_listptr_t last;
+	libtar_cmpfunc_t cmpfunc;
+	int flags;
+	unsigned int nents;
+};
+typedef struct libtar_list libtar_list_t;
+
+
+/* values for flags */
+#define LIST_USERFUNC	0	/* use cmpfunc() to order */
+#define LIST_STACK	1	/* new elements go in front */
+#define LIST_QUEUE	2	/* new elements go at the end */
+
+
+/* reset a list pointer */
+void libtar_listptr_reset(libtar_listptr_t *);
+
+/* retrieve the data being pointed to */
+void *libtar_listptr_data(libtar_listptr_t *);
+
+/* creates a new, empty list */
+libtar_list_t *libtar_list_new(int, libtar_cmpfunc_t);
+
+/* call a function for every element in a list */
+int libtar_list_iterate(libtar_list_t *,
+				   libtar_iterate_func_t, void *);
+
+/* empty the list */
+void libtar_list_empty(libtar_list_t *,
+				  libtar_freefunc_t);
+
+/* remove and free() the entire list */
+void libtar_list_free(libtar_list_t *,
+				 libtar_freefunc_t);
+
+/* add elements */
+int libtar_list_add(libtar_list_t *, void *);
+
+/* removes an element from the list - returns -1 on error */
+void libtar_list_del(libtar_list_t *,
+				libtar_listptr_t *);
+
+/* returns 1 when valid data is returned, or 0 at end of list */
+int libtar_list_next(libtar_list_t *,
+				libtar_listptr_t *);
+
+/* returns 1 when valid data is returned, or 0 at end of list */
+int libtar_list_prev(libtar_list_t *,
+				libtar_listptr_t *);
+
+/* return 1 if the data matches a list entry, 0 otherwise */
+int libtar_list_search(libtar_list_t *,
+				  libtar_listptr_t *, void *,
+				  libtar_matchfunc_t);
+
+/* return number of elements from list */
+unsigned int libtar_list_nents(libtar_list_t *);
+
+/* adds elements from a string delimited by delim */
+int libtar_list_add_str(libtar_list_t *, char *, char *);
+
+/* string matching function */
+int libtar_str_match(char *, char *);
+
+
+/***** hash.c **********************************************************/
+
+/*
+** Hashing function (determines which bucket the given key hashes into)
+** first argument is the key to hash
+** second argument is the total number of buckets
+** returns the bucket number
+*/
+typedef unsigned int (*libtar_hashfunc_t)(void *, unsigned int);
+
+
+struct libtar_hashptr
+{
+	int bucket;
+	libtar_listptr_t node;
+};
+typedef struct libtar_hashptr libtar_hashptr_t;
+
+struct libtar_hash
+{
+	int numbuckets;
+	libtar_list_t **table;
+	libtar_hashfunc_t hashfunc;
+	unsigned int nents;
+};
+typedef struct libtar_hash libtar_hash_t;
+
+
+/* reset a hash pointer */
+void libtar_hashptr_reset(libtar_hashptr_t *);
+
+/* retrieve the data being pointed to */
+void *libtar_hashptr_data(libtar_hashptr_t *);
+
+/* default hash function, optimized for 7-bit strings */
+unsigned int libtar_str_hashfunc(char *, unsigned int);
+
+/* return number of elements from hash */
+unsigned int libtar_hash_nents(libtar_hash_t *);
+
+/* create a new hash */
+libtar_hash_t *libtar_hash_new(int, libtar_hashfunc_t);
+
+/* empty the hash */
+void libtar_hash_empty(libtar_hash_t *,
+				  libtar_freefunc_t);
+
+/* delete all the libtar_nodes of the hash and clean up */
+void libtar_hash_free(libtar_hash_t *,
+				 libtar_freefunc_t);
+
+/* returns 1 when valid data is returned, or 0 at end of list */
+int libtar_hash_next(libtar_hash_t *,
+				libtar_hashptr_t *);
+
+/* return 1 if the data matches a list entry, 0 otherwise */
+int libtar_hash_search(libtar_hash_t *,
+				  libtar_hashptr_t *, void *,
+				  libtar_matchfunc_t);
+
+/* return 1 if the key matches a list entry, 0 otherwise */
+int libtar_hash_getkey(libtar_hash_t *,
+				  libtar_hashptr_t *, void *,
+				  libtar_matchfunc_t);
+
+/* inserting data */
+int libtar_hash_add(libtar_hash_t *, void *);
+
+/* delete an entry */
+int libtar_hash_del(libtar_hash_t *,
+			       libtar_hashptr_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ! libtar_LISTHASH_H */
+
diff --git a/libtar/output.c b/libtar/output.c
new file mode 100755
index 0000000..015179d
--- /dev/null
+++ b/libtar/output.c
@@ -0,0 +1,148 @@
+/*
+**  Copyright 1998-2003 University of Illinois Board of Trustees
+**  Copyright 1998-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  output.c - libtar code to print out tar header blocks
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <internal.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#include <limits.h>
+#include <sys/param.h>
+
+#ifdef STDC_HEADERS
+# include <string.h>
+#endif
+
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
+#endif
+
+#ifndef _POSIX_LOGIN_NAME_MAX
+# define _POSIX_LOGIN_NAME_MAX	9
+#endif
+
+
+void
+th_print(TAR *t)
+{
+	puts("\nPrinting tar header:");
+	printf("  name     = \"%.100s\"\n", t->th_buf.name);
+	printf("  mode     = \"%.8s\"\n", t->th_buf.mode);
+	printf("  uid      = \"%.8s\"\n", t->th_buf.uid);
+	printf("  gid      = \"%.8s\"\n", t->th_buf.gid);
+	printf("  size     = \"%.12s\"\n", t->th_buf.size);
+	printf("  mtime    = \"%.12s\"\n", t->th_buf.mtime);
+	printf("  chksum   = \"%.8s\"\n", t->th_buf.chksum);
+	printf("  typeflag = \'%c\'\n", t->th_buf.typeflag);
+	printf("  linkname = \"%.100s\"\n", t->th_buf.linkname);
+	printf("  magic    = \"%.6s\"\n", t->th_buf.magic);
+	/*printf("  version  = \"%.2s\"\n", t->th_buf.version); */
+	/*printf("  version[0] = \'%c\',version[1] = \'%c\'\n",
+	       t->th_buf.version[0], t->th_buf.version[1]);*/
+	printf("  uname    = \"%.32s\"\n", t->th_buf.uname);
+	printf("  gname    = \"%.32s\"\n", t->th_buf.gname);
+	printf("  devmajor = \"%.8s\"\n", t->th_buf.devmajor);
+	printf("  devminor = \"%.8s\"\n", t->th_buf.devminor);
+	printf("  prefix   = \"%.155s\"\n", t->th_buf.prefix);
+	printf("  padding  = \"%.12s\"\n", t->th_buf.padding);
+	printf("  gnu_longname = \"%s\"\n",
+	       (t->th_buf.gnu_longname ? t->th_buf.gnu_longname : "[NULL]"));
+	printf("  gnu_longlink = \"%s\"\n",
+	       (t->th_buf.gnu_longlink ? t->th_buf.gnu_longlink : "[NULL]"));
+		   
+#ifdef USE_FSCRYPT
+#ifdef USE_FSCRYPT_POLICY_V1
+	printf("  fep = \"%s\"\n",
+	       (t->th_buf.fep ? t->th_buf.fep->master_key_descriptor : (uint8_t*) "[NULL]"));
+#else
+	printf("  fep = \"%s\"\n",
+	       (t->th_buf.fep ? t->th_buf.fep->master_key_identifier : (uint8_t*) "[NULL]"));
+#endif
+#endif
+}
+
+
+void
+th_print_long_ls(TAR *t)
+{
+	char modestring[12];
+	struct passwd *pw;
+	struct group *gr;
+	uid_t uid;
+	gid_t gid;
+	char username[_POSIX_LOGIN_NAME_MAX];
+	char groupname[_POSIX_LOGIN_NAME_MAX];
+	time_t mtime;
+	struct tm *mtm;
+
+#ifdef HAVE_STRFTIME
+	char timebuf[18];
+#else
+	const char *months[] = {
+		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+	};
+#endif
+
+	uid = th_get_uid(t);
+	pw = getpwuid(uid);
+	if ((t->options & TAR_USE_NUMERIC_ID) || pw == NULL)
+		snprintf(username, sizeof(username), "%d", uid);
+	else
+		strlcpy(username, pw->pw_name, sizeof(username));
+
+	gid = th_get_gid(t);
+	gr = getgrgid(gid);
+	if ((t->options & TAR_USE_NUMERIC_ID) || gr == NULL)
+		snprintf(groupname, sizeof(groupname), "%d", gid);
+	else
+		strlcpy(groupname, gr->gr_name, sizeof(groupname));
+
+	strmode(th_get_mode(t), modestring);
+	printf("%.10s %-8.8s %-8.8s ", modestring, username, groupname);
+
+	if (TH_ISCHR(t) || TH_ISBLK(t))
+		printf(" %3d, %3d ", (int)th_get_devmajor(t), (int)th_get_devminor(t));
+	else
+		printf("%9ld ", (long)th_get_size(t));
+
+	mtime = th_get_mtime(t);
+	mtm = localtime(&mtime);
+#ifdef HAVE_STRFTIME
+	strftime(timebuf, sizeof(timebuf), "%h %e %H:%M %Y", mtm);
+	printf("%s", timebuf);
+#else
+	printf("%.3s %2d %2d:%02d %4d",
+	       months[mtm->tm_mon],
+	       mtm->tm_mday, mtm->tm_hour, mtm->tm_min, mtm->tm_year + 1900);
+#endif
+
+	printf(" %s", th_get_pathname(t));
+
+	if (TH_ISSYM(t) || TH_ISLNK(t))
+	{
+		if (TH_ISSYM(t))
+			printf(" -> ");
+		else
+			printf(" link to ");
+		if ((t->options & TAR_GNU) && t->th_buf.gnu_longlink != NULL)
+			printf("%s", t->th_buf.gnu_longlink);
+		else
+			printf("%.100s", t->th_buf.linkname);
+	}
+
+	putchar('\n');
+}
+
+
diff --git a/libtar/snprintf.c b/libtar/snprintf.c
new file mode 100644
index 0000000..8c228d4
--- /dev/null
+++ b/libtar/snprintf.c
@@ -0,0 +1,761 @@
+/**************************************************************
+ * Original:
+ * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
+ * A bombproof version of doprnt (dopr) included.
+ * Sigh.  This sort of thing is always nasty do deal with.  Note that
+ * the version here does not include floating point...
+ *
+ * snprintf() is used instead of sprintf() as it does limit checks
+ * for string length.  This covers a nasty loophole.
+ *
+ * The other functions are there to prevent NULL pointers from
+ * causing nast effects.
+ *
+ * More Recently:
+ *  Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43
+ *  This was ugly.  It is still ugly.  I opted out of floating point
+ *  numbers, but the formatter understands just about everything
+ *  from the normal C string format, at least as far as I can tell from
+ *  the Solaris 2.5 printf(3S) man page.
+ *
+ *  Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1
+ *    Ok, added some minimal floating point support, which means this
+ *    probably requires libm on most operating systems.  Don't yet
+ *    support the exponent (e,E) and sigfig (g,G).  Also, fmtint()
+ *    was pretty badly broken, it just wasn't being exercised in ways
+ *    which showed it, so that's been fixed.  Also, formated the code
+ *    to mutt conventions, and removed dead code left over from the
+ *    original.  Also, there is now a builtin-test, just compile with:
+ *           gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm
+ *    and run snprintf for results.
+ * 
+ *  Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i
+ *    The PGP code was using unsigned hexadecimal formats. 
+ *    Unfortunately, unsigned formats simply didn't work.
+ *
+ *  Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8
+ *    The original code assumed that both snprintf() and vsnprintf() were
+ *    missing.  Some systems only have snprintf() but not vsnprintf(), so
+ *    the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF.
+ *
+ *  Chris Frey <cdfrey@foursquare.net> 2012/09/05
+ *    Removed varargs.h and all dependency on it.
+ *
+ **************************************************************/
+
+#include <config.h>
+
+#if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF)
+
+#include <string.h>
+# include <ctype.h>
+#include <sys/types.h>
+
+#include <stdarg.h>
+#define VA_LOCAL_DECL   va_list ap
+#define VA_START(f)     va_start(ap, f)
+#define VA_SHIFT(v,t)  ;   /* no-op for ANSI */
+#define VA_END          va_end(ap)
+
+/*int snprintf (char *str, size_t count, const char *fmt, ...);*/
+/*int vsnprintf (char *str, size_t count, const char *fmt, va_list arg);*/
+
+static void dopr (char *buffer, size_t maxlen, const char *format, 
+                  va_list args);
+static void fmtstr (char *buffer, size_t *currlen, size_t maxlen,
+		    char *value, int flags, int min, int max);
+static void fmtint (char *buffer, size_t *currlen, size_t maxlen,
+		    long value, int base, int min, int max, int flags);
+static void fmtfp (char *buffer, size_t *currlen, size_t maxlen,
+		   long double fvalue, int min, int max, int flags);
+static void dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c );
+
+/*
+ * dopr(): poor man's version of doprintf
+ */
+
+/* format read states */
+#define DP_S_DEFAULT 0
+#define DP_S_FLAGS   1
+#define DP_S_MIN     2
+#define DP_S_DOT     3
+#define DP_S_MAX     4
+#define DP_S_MOD     5
+#define DP_S_CONV    6
+#define DP_S_DONE    7
+
+/* format flags - Bits */
+#define DP_F_MINUS 	(1 << 0)
+#define DP_F_PLUS  	(1 << 1)
+#define DP_F_SPACE 	(1 << 2)
+#define DP_F_NUM   	(1 << 3)
+#define DP_F_ZERO  	(1 << 4)
+#define DP_F_UP    	(1 << 5)
+#define DP_F_UNSIGNED 	(1 << 6)
+
+/* Conversion Flags */
+#define DP_C_SHORT   1
+#define DP_C_LONG    2
+#define DP_C_LDOUBLE 3
+
+#define char_to_int(p) (p - '0')
+#define MAX(p,q) ((p >= q) ? p : q)
+
+static void dopr (char *buffer, size_t maxlen, const char *format, va_list args)
+{
+  char ch;
+  long value;
+  long double fvalue;
+  char *strvalue;
+  int min;
+  int max;
+  int state;
+  int flags;
+  int cflags;
+  size_t currlen;
+  
+  state = DP_S_DEFAULT;
+  currlen = flags = cflags = min = 0;
+  max = -1;
+  ch = *format++;
+
+  while (state != DP_S_DONE)
+  {
+    if ((ch == '\0') || (currlen >= maxlen)) 
+      state = DP_S_DONE;
+
+    switch(state) 
+    {
+    case DP_S_DEFAULT:
+      if (ch == '%') 
+	state = DP_S_FLAGS;
+      else 
+	dopr_outch (buffer, &currlen, maxlen, ch);
+      ch = *format++;
+      break;
+    case DP_S_FLAGS:
+      switch (ch) 
+      {
+      case '-':
+	flags |= DP_F_MINUS;
+        ch = *format++;
+	break;
+      case '+':
+	flags |= DP_F_PLUS;
+        ch = *format++;
+	break;
+      case ' ':
+	flags |= DP_F_SPACE;
+        ch = *format++;
+	break;
+      case '#':
+	flags |= DP_F_NUM;
+        ch = *format++;
+	break;
+      case '0':
+	flags |= DP_F_ZERO;
+        ch = *format++;
+	break;
+      default:
+	state = DP_S_MIN;
+	break;
+      }
+      break;
+    case DP_S_MIN:
+      if (isdigit((unsigned char)ch)) 
+      {
+	min = 10*min + char_to_int (ch);
+	ch = *format++;
+      } 
+      else if (ch == '*') 
+      {
+	min = va_arg (args, int);
+	ch = *format++;
+	state = DP_S_DOT;
+      } 
+      else 
+	state = DP_S_DOT;
+      break;
+    case DP_S_DOT:
+      if (ch == '.') 
+      {
+	state = DP_S_MAX;
+	ch = *format++;
+      } 
+      else 
+	state = DP_S_MOD;
+      break;
+    case DP_S_MAX:
+      if (isdigit((unsigned char)ch)) 
+      {
+	if (max < 0)
+	  max = 0;
+	max = 10*max + char_to_int (ch);
+	ch = *format++;
+      } 
+      else if (ch == '*') 
+      {
+	max = va_arg (args, int);
+	ch = *format++;
+	state = DP_S_MOD;
+      } 
+      else 
+	state = DP_S_MOD;
+      break;
+    case DP_S_MOD:
+      /* Currently, we don't support Long Long, bummer */
+      switch (ch) 
+      {
+      case 'h':
+	cflags = DP_C_SHORT;
+	ch = *format++;
+	break;
+      case 'l':
+	cflags = DP_C_LONG;
+	ch = *format++;
+	break;
+      case 'L':
+	cflags = DP_C_LDOUBLE;
+	ch = *format++;
+	break;
+      default:
+	break;
+      }
+      state = DP_S_CONV;
+      break;
+    case DP_S_CONV:
+      switch (ch) 
+      {
+      case 'd':
+      case 'i':
+	if (cflags == DP_C_SHORT) 
+	  value = va_arg (args, int);
+	else if (cflags == DP_C_LONG)
+	  value = va_arg (args, long int);
+	else
+	  value = va_arg (args, int);
+	fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
+	break;
+      case 'o':
+	flags |= DP_F_UNSIGNED;
+	if (cflags == DP_C_SHORT)
+	  value = va_arg (args, unsigned int);
+	else if (cflags == DP_C_LONG)
+	  value = va_arg (args, unsigned long int);
+	else
+	  value = va_arg (args, unsigned int);
+	fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags);
+	break;
+      case 'u':
+	flags |= DP_F_UNSIGNED;
+	if (cflags == DP_C_SHORT)
+	  value = va_arg (args, unsigned int);
+	else if (cflags == DP_C_LONG)
+	  value = va_arg (args, unsigned long int);
+	else
+	  value = va_arg (args, unsigned int);
+	fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
+	break;
+      case 'X':
+	flags |= DP_F_UP;
+      case 'x':
+	flags |= DP_F_UNSIGNED;
+	if (cflags == DP_C_SHORT)
+	  value = va_arg (args, unsigned int);
+	else if (cflags == DP_C_LONG)
+	  value = va_arg (args, unsigned long int);
+	else
+	  value = va_arg (args, unsigned int);
+	fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags);
+	break;
+      case 'f':
+	if (cflags == DP_C_LDOUBLE)
+	  fvalue = va_arg (args, long double);
+	else
+	  fvalue = va_arg (args, double);
+	/* um, floating point? */
+	fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
+	break;
+      case 'E':
+	flags |= DP_F_UP;
+      case 'e':
+	if (cflags == DP_C_LDOUBLE)
+	  fvalue = va_arg (args, long double);
+	else
+	  fvalue = va_arg (args, double);
+	break;
+      case 'G':
+	flags |= DP_F_UP;
+      case 'g':
+	if (cflags == DP_C_LDOUBLE)
+	  fvalue = va_arg (args, long double);
+	else
+	  fvalue = va_arg (args, double);
+	break;
+      case 'c':
+	dopr_outch (buffer, &currlen, maxlen, va_arg (args, int));
+	break;
+      case 's':
+	strvalue = va_arg (args, char *);
+	if (max < 0) 
+	  max = maxlen; /* ie, no max */
+	fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max);
+	break;
+      case 'p':
+	strvalue = va_arg (args, void *);
+	fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min, max, flags);
+	break;
+      case 'n':
+	if (cflags == DP_C_SHORT) 
+	{
+	  short int *num;
+	  num = va_arg (args, short int *);
+	  *num = currlen;
+        } 
+	else if (cflags == DP_C_LONG) 
+	{
+	  long int *num;
+	  num = va_arg (args, long int *);
+	  *num = currlen;
+        } 
+	else 
+	{
+	  int *num;
+	  num = va_arg (args, int *);
+	  *num = currlen;
+        }
+	break;
+      case '%':
+	dopr_outch (buffer, &currlen, maxlen, ch);
+	break;
+      case 'w':
+	/* not supported yet, treat as next char */
+	ch = *format++;
+	break;
+      default:
+	/* Unknown, skip */
+	break;
+      }
+      ch = *format++;
+      state = DP_S_DEFAULT;
+      flags = cflags = min = 0;
+      max = -1;
+      break;
+    case DP_S_DONE:
+      break;
+    default:
+      /* hmm? */
+      break; /* some picky compilers need this */
+    }
+  }
+  if (currlen < maxlen - 1) 
+    buffer[currlen] = '\0';
+  else 
+    buffer[maxlen - 1] = '\0';
+}
+
+static void fmtstr (char *buffer, size_t *currlen, size_t maxlen,
+		    char *value, int flags, int min, int max)
+{
+  int padlen, strln;     /* amount to pad */
+  int cnt = 0;
+  
+  if (value == 0)
+  {
+    value = "<NULL>";
+  }
+
+  for (strln = 0; value[strln]; ++strln); /* strlen */
+  padlen = min - strln;
+  if (padlen < 0) 
+    padlen = 0;
+  if (flags & DP_F_MINUS) 
+    padlen = -padlen; /* Left Justify */
+
+  while ((padlen > 0) && (cnt < max)) 
+  {
+    dopr_outch (buffer, currlen, maxlen, ' ');
+    --padlen;
+    ++cnt;
+  }
+  while (*value && (cnt < max)) 
+  {
+    dopr_outch (buffer, currlen, maxlen, *value++);
+    ++cnt;
+  }
+  while ((padlen < 0) && (cnt < max)) 
+  {
+    dopr_outch (buffer, currlen, maxlen, ' ');
+    ++padlen;
+    ++cnt;
+  }
+}
+
+/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
+
+static void fmtint (char *buffer, size_t *currlen, size_t maxlen,
+		    long value, int base, int min, int max, int flags)
+{
+  int signvalue = 0;
+  unsigned long uvalue;
+  char convert[20];
+  int place = 0;
+  int spadlen = 0; /* amount to space pad */
+  int zpadlen = 0; /* amount to zero pad */
+  int caps = 0;
+  
+  if (max < 0)
+    max = 0;
+
+  uvalue = value;
+
+  if(!(flags & DP_F_UNSIGNED))
+  {
+    if( value < 0 ) {
+      signvalue = '-';
+      uvalue = -value;
+    }
+    else
+      if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
+	signvalue = '+';
+    else
+      if (flags & DP_F_SPACE)
+	signvalue = ' ';
+  }
+  
+  if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
+
+  do {
+    convert[place++] =
+      (caps? "0123456789ABCDEF":"0123456789abcdef")
+      [uvalue % (unsigned)base  ];
+    uvalue = (uvalue / (unsigned)base );
+  } while(uvalue && (place < 20));
+  if (place == 20) place--;
+  convert[place] = 0;
+
+  zpadlen = max - place;
+  spadlen = min - MAX (max, place) - (signvalue ? 1 : 0);
+  if (zpadlen < 0) zpadlen = 0;
+  if (spadlen < 0) spadlen = 0;
+  if (flags & DP_F_ZERO)
+  {
+    zpadlen = MAX(zpadlen, spadlen);
+    spadlen = 0;
+  }
+  if (flags & DP_F_MINUS) 
+    spadlen = -spadlen; /* Left Justifty */
+
+#ifdef DEBUG_SNPRINTF
+  dprint (1, (debugfile, "zpad: %d, spad: %d, min: %d, max: %d, place: %d\n",
+      zpadlen, spadlen, min, max, place));
+#endif
+
+  /* Spaces */
+  while (spadlen > 0) 
+  {
+    dopr_outch (buffer, currlen, maxlen, ' ');
+    --spadlen;
+  }
+
+  /* Sign */
+  if (signvalue) 
+    dopr_outch (buffer, currlen, maxlen, signvalue);
+
+  /* Zeros */
+  if (zpadlen > 0) 
+  {
+    while (zpadlen > 0)
+    {
+      dopr_outch (buffer, currlen, maxlen, '0');
+      --zpadlen;
+    }
+  }
+
+  /* Digits */
+  while (place > 0) 
+    dopr_outch (buffer, currlen, maxlen, convert[--place]);
+  
+  /* Left Justified spaces */
+  while (spadlen < 0) {
+    dopr_outch (buffer, currlen, maxlen, ' ');
+    ++spadlen;
+  }
+}
+
+static long double abs_val (long double value)
+{
+  long double result = value;
+
+  if (value < 0)
+    result = -value;
+
+  return result;
+}
+
+static long double pow10_ (int exp)
+{
+  long double result = 1;
+
+  while (exp)
+  {
+    result *= 10;
+    exp--;
+  }
+  
+  return result;
+}
+
+static long round_ (long double value)
+{
+  long intpart;
+
+  intpart = value;
+  value = value - intpart;
+  if (value >= 0.5)
+    intpart++;
+
+  return intpart;
+}
+
+static void fmtfp (char *buffer, size_t *currlen, size_t maxlen,
+		   long double fvalue, int min, int max, int flags)
+{
+  int signvalue = 0;
+  long double ufvalue;
+  char iconvert[20];
+  char fconvert[20];
+  int iplace = 0;
+  int fplace = 0;
+  int padlen = 0; /* amount to pad */
+  int zpadlen = 0; 
+  int caps = 0;
+  long intpart;
+  long fracpart;
+  
+  /* 
+   * AIX manpage says the default is 0, but Solaris says the default
+   * is 6, and sprintf on AIX defaults to 6
+   */
+  if (max < 0)
+    max = 6;
+
+  ufvalue = abs_val (fvalue);
+
+  if (fvalue < 0)
+    signvalue = '-';
+  else
+    if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
+      signvalue = '+';
+    else
+      if (flags & DP_F_SPACE)
+	signvalue = ' ';
+
+#if 0
+  if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
+#endif
+
+  intpart = ufvalue;
+
+  /* 
+   * Sorry, we only support 9 digits past the decimal because of our 
+   * conversion method
+   */
+  if (max > 9)
+    max = 9;
+
+  /* We "cheat" by converting the fractional part to integer by
+   * multiplying by a factor of 10
+   */
+  fracpart = round_ ((pow10_ (max)) * (ufvalue - intpart));
+
+  if (fracpart >= pow10_ (max))
+  {
+    intpart++;
+    fracpart -= pow10_ (max);
+  }
+
+#ifdef DEBUG_SNPRINTF
+  dprint (1, (debugfile, "fmtfp: %f =? %d.%d\n", fvalue, intpart, fracpart));
+#endif
+
+  /* Convert integer part */
+  do {
+    iconvert[iplace++] =
+      (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10];
+    intpart = (intpart / 10);
+  } while(intpart && (iplace < 20));
+  if (iplace == 20) iplace--;
+  iconvert[iplace] = 0;
+
+  /* Convert fractional part */
+  do {
+    fconvert[fplace++] =
+      (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10];
+    fracpart = (fracpart / 10);
+  } while(fracpart && (fplace < 20));
+  if (fplace == 20) fplace--;
+  fconvert[fplace] = 0;
+
+  /* -1 for decimal point, another -1 if we are printing a sign */
+  padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0); 
+  zpadlen = max - fplace;
+  if (zpadlen < 0)
+    zpadlen = 0;
+  if (padlen < 0) 
+    padlen = 0;
+  if (flags & DP_F_MINUS) 
+    padlen = -padlen; /* Left Justifty */
+
+  if ((flags & DP_F_ZERO) && (padlen > 0)) 
+  {
+    if (signvalue) 
+    {
+      dopr_outch (buffer, currlen, maxlen, signvalue);
+      --padlen;
+      signvalue = 0;
+    }
+    while (padlen > 0)
+    {
+      dopr_outch (buffer, currlen, maxlen, '0');
+      --padlen;
+    }
+  }
+  while (padlen > 0)
+  {
+    dopr_outch (buffer, currlen, maxlen, ' ');
+    --padlen;
+  }
+  if (signvalue) 
+    dopr_outch (buffer, currlen, maxlen, signvalue);
+
+  while (iplace > 0) 
+    dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]);
+
+  /*
+   * Decimal point.  This should probably use locale to find the correct
+   * char to print out.
+   */
+  dopr_outch (buffer, currlen, maxlen, '.');
+
+  while (fplace > 0) 
+    dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]);
+
+  while (zpadlen > 0)
+  {
+    dopr_outch (buffer, currlen, maxlen, '0');
+    --zpadlen;
+  }
+
+  while (padlen < 0) 
+  {
+    dopr_outch (buffer, currlen, maxlen, ' ');
+    ++padlen;
+  }
+}
+
+static void dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c)
+{
+  if (*currlen < maxlen)
+    buffer[(*currlen)++] = c;
+}
+#endif /* !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) */
+
+#ifndef HAVE_VSNPRINTF
+int mutt_vsnprintf (char *str, size_t count, const char *fmt, va_list args)
+{
+  str[0] = 0;
+  dopr(str, count, fmt, args);
+  return(strlen(str));
+}
+#endif /* !HAVE_VSNPRINTF */
+
+#ifndef HAVE_SNPRINTF
+int mutt_snprintf (char *str,size_t count,const char *fmt,...)
+{
+  VA_LOCAL_DECL;
+
+  VA_START (fmt);
+  VA_SHIFT (str, char *);
+  VA_SHIFT (count, size_t );
+  VA_SHIFT (fmt, char *);
+  (void) mutt_vsnprintf(str, count, fmt, ap);
+  VA_END;
+  return(strlen(str));
+}
+
+#ifdef TEST_SNPRINTF
+#ifndef LONG_STRING
+#define LONG_STRING 1024
+#endif
+int main (void)
+{
+  char buf1[LONG_STRING];
+  char buf2[LONG_STRING];
+  char *fp_fmt[] = {
+    "%-1.5f",
+    "%1.5f",
+    "%123.9f",
+    "%10.5f",
+    "% 10.5f",
+    "%+22.9f",
+    "%+4.9f",
+    "%01.3f",
+    "%4f",
+    "%3.1f",
+    "%3.2f",
+    NULL
+  };
+  double fp_nums[] = { -1.5, 134.21, 91340.2, 341.1234, 0203.9, 0.96, 0.996, 
+    0.9996, 1.996, 4.136, 0};
+  char *int_fmt[] = {
+    "%-1.5d",
+    "%1.5d",
+    "%123.9d",
+    "%5.5d",
+    "%10.5d",
+    "% 10.5d",
+    "%+22.33d",
+    "%01.3d",
+    "%4d",
+    NULL
+  };
+  long int_nums[] = { -1, 134, 91340, 341, 0203, 0};
+  int x, y;
+  int fail = 0;
+  int num = 0;
+
+  printf ("Testing snprintf format codes against system sprintf...\n");
+
+  for (x = 0; fp_fmt[x] != NULL ; x++)
+    for (y = 0; fp_nums[y] != 0 ; y++)
+    {
+      mutt_snprintf (buf1, sizeof (buf1), fp_fmt[x], fp_nums[y]);
+      sprintf (buf2, fp_fmt[x], fp_nums[y]);
+      if (strcmp (buf1, buf2))
+      {
+	printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf  = %s\n", 
+	    fp_fmt[x], buf1, buf2);
+	fail++;
+      }
+      num++;
+    }
+
+  for (x = 0; int_fmt[x] != NULL ; x++)
+    for (y = 0; int_nums[y] != 0 ; y++)
+    {
+      mutt_snprintf (buf1, sizeof (buf1), int_fmt[x], int_nums[y]);
+      sprintf (buf2, int_fmt[x], int_nums[y]);
+      if (strcmp (buf1, buf2))
+      {
+	printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf  = %s\n", 
+	    int_fmt[x], buf1, buf2);
+	fail++;
+      }
+      num++;
+    }
+  printf ("%d tests failed out of %d.\n", fail, num);
+}
+#endif /* SNPRINTF_TEST */
+
+#endif /* !HAVE_SNPRINTF */
diff --git a/libtar/strdup.c b/libtar/strdup.c
new file mode 100644
index 0000000..75e9af5
--- /dev/null
+++ b/libtar/strdup.c
@@ -0,0 +1,62 @@
+/*	$OpenBSD: strdup.c,v 1.3 1997/08/20 04:18:52 millert Exp $	*/
+
+/*
+ * Copyright (c) 1988, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)strdup.c	8.1 (Berkeley) 6/4/93";
+#else
+static char *rcsid = "$OpenBSD: strdup.c,v 1.3 1997/08/20 04:18:52 millert Exp $";
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+#include <sys/types.h>
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+char *
+openbsd_strdup(str)
+	const char *str;
+{
+	size_t siz;
+	char *copy;
+
+	siz = strlen(str) + 1;
+	if ((copy = malloc(siz)) == NULL)
+		return(NULL);
+	(void)memcpy(copy, str, siz);
+	return(copy);
+}
diff --git a/libtar/strlcat.c b/libtar/strlcat.c
new file mode 100644
index 0000000..3912539
--- /dev/null
+++ b/libtar/strlcat.c
@@ -0,0 +1,72 @@
+/*	$OpenBSD: strlcat.c,v 1.5 2001/01/13 16:17:24 millert Exp $	*/
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char *rcsid = "$OpenBSD: strlcat.c,v 1.5 2001/01/13 16:17:24 millert Exp $";
+#endif /* LIBC_SCCS and not lint */
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left).  At most siz-1 characters
+ * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(initial dst) + strlen(src); if retval >= siz,
+ * truncation occurred.
+ */
+size_t strlcat(dst, src, siz)
+	char *dst;
+	const char *src;
+	size_t siz;
+{
+	register char *d = dst;
+	register const char *s = src;
+	register size_t n = siz;
+	size_t dlen;
+
+	/* Find the end of dst and adjust bytes left but don't go past end */
+	while (n-- != 0 && *d != '\0')
+		d++;
+	dlen = d - dst;
+	n = siz - dlen;
+
+	if (n == 0)
+		return(dlen + strlen(s));
+	while (*s != '\0') {
+		if (n != 1) {
+			*d++ = *s;
+			n--;
+		}
+		s++;
+	}
+	*d = '\0';
+
+	return(dlen + (s - src));	/* count does not include NUL */
+}
diff --git a/libtar/strlcpy.c b/libtar/strlcpy.c
new file mode 100644
index 0000000..300a28b
--- /dev/null
+++ b/libtar/strlcpy.c
@@ -0,0 +1,68 @@
+/*	$OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $	*/
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char *rcsid = "$OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $";
+#endif /* LIBC_SCCS and not lint */
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy src to string dst of size siz.  At most siz-1 characters
+ * will be copied.  Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t strlcpy(dst, src, siz)
+	char *dst;
+	const char *src;
+	size_t siz;
+{
+	register char *d = dst;
+	register const char *s = src;
+	register size_t n = siz;
+
+	/* Copy as many bytes as will fit */
+	if (n != 0 && --n != 0) {
+		do {
+			if ((*d++ = *s++) == 0)
+				break;
+		} while (--n != 0);
+	}
+
+	/* Not enough room in dst, add NUL and traverse rest of src */
+	if (n == 0) {
+		if (siz != 0)
+			*d = '\0';		/* NUL-terminate dst */
+		while (*s++)
+			;
+	}
+
+	return(s - src - 1);	/* count does not include NUL */
+}
diff --git a/libtar/strmode.c b/libtar/strmode.c
new file mode 100644
index 0000000..5e7f15e
--- /dev/null
+++ b/libtar/strmode.c
@@ -0,0 +1,152 @@
+/*-
+ * Copyright (c) 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char *rcsid = "$OpenBSD: strmode.c,v 1.3 1997/06/13 13:57:20 deraadt Exp $";
+#endif /* LIBC_SCCS and not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+
+void
+strmode(mode, p)
+	register mode_t mode;
+	register char *p;
+{
+	 /* print type */
+	switch (mode & S_IFMT) {
+	case S_IFDIR:			/* directory */
+		*p++ = 'd';
+		break;
+	case S_IFCHR:			/* character special */
+		*p++ = 'c';
+		break;
+	case S_IFBLK:			/* block special */
+		*p++ = 'b';
+		break;
+	case S_IFREG:			/* regular */
+		*p++ = '-';
+		break;
+	case S_IFLNK:			/* symbolic link */
+		*p++ = 'l';
+		break;
+	case S_IFSOCK:			/* socket */
+		*p++ = 's';
+		break;
+#ifdef S_IFIFO
+	case S_IFIFO:			/* fifo */
+		*p++ = 'p';
+		break;
+#endif
+#ifdef S_IFWHT
+	case S_IFWHT:			/* whiteout */
+		*p++ = 'w';
+		break;
+#endif
+	default:			/* unknown */
+		*p++ = '?';
+		break;
+	}
+	/* usr */
+	if (mode & S_IRUSR)
+		*p++ = 'r';
+	else
+		*p++ = '-';
+	if (mode & S_IWUSR)
+		*p++ = 'w';
+	else
+		*p++ = '-';
+	switch (mode & (S_IXUSR | S_ISUID)) {
+	case 0:
+		*p++ = '-';
+		break;
+	case S_IXUSR:
+		*p++ = 'x';
+		break;
+	case S_ISUID:
+		*p++ = 'S';
+		break;
+	case S_IXUSR | S_ISUID:
+		*p++ = 's';
+		break;
+	}
+	/* group */
+	if (mode & S_IRGRP)
+		*p++ = 'r';
+	else
+		*p++ = '-';
+	if (mode & S_IWGRP)
+		*p++ = 'w';
+	else
+		*p++ = '-';
+	switch (mode & (S_IXGRP | S_ISGID)) {
+	case 0:
+		*p++ = '-';
+		break;
+	case S_IXGRP:
+		*p++ = 'x';
+		break;
+	case S_ISGID:
+		*p++ = 'S';
+		break;
+	case S_IXGRP | S_ISGID:
+		*p++ = 's';
+		break;
+	}
+	/* other */
+	if (mode & S_IROTH)
+		*p++ = 'r';
+	else
+		*p++ = '-';
+	if (mode & S_IWOTH)
+		*p++ = 'w';
+	else
+		*p++ = '-';
+	switch (mode & (S_IXOTH | S_ISVTX)) {
+	case 0:
+		*p++ = '-';
+		break;
+	case S_IXOTH:
+		*p++ = 'x';
+		break;
+	case S_ISVTX:
+		*p++ = 'T';
+		break;
+	case S_IXOTH | S_ISVTX:
+		*p++ = 't';
+		break;
+	}
+	*p++ = ' ';		/* will be a '+' if ACL's implemented */
+	*p = '\0';
+}
diff --git a/libtar/strrstr.c b/libtar/strrstr.c
new file mode 100644
index 0000000..097ef91
--- /dev/null
+++ b/libtar/strrstr.c
@@ -0,0 +1,40 @@
+/*
+**  Copyright 1998-2002 University of Illinois Board of Trustees
+**  Copyright 1998-2002 Mark D. Roth
+**  All rights reserved.
+**
+**  strrstr.c - strrstr() function for compatibility library
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <string.h>
+
+
+/*
+** find the last occurrance of find in string
+*/
+char *
+strrstr(char *string, char *find)
+{
+	size_t stringlen, findlen;
+	char *cp;
+
+	findlen = strlen(find);
+	stringlen = strlen(string);
+	if (findlen > stringlen)
+		return NULL;
+
+	for (cp = string + stringlen - findlen; cp >= string; cp--)
+		if (strncmp(cp, find, findlen) == 0)
+			return cp;
+
+	return NULL;
+}
+
+
diff --git a/libtar/strsep.c b/libtar/strsep.c
new file mode 100644
index 0000000..3132962
--- /dev/null
+++ b/libtar/strsep.c
@@ -0,0 +1,87 @@
+/*	$OpenBSD: strsep.c,v 1.3 1997/08/20 04:28:14 millert Exp $	*/
+
+/*-
+ * Copyright (c) 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)strsep.c	8.1 (Berkeley) 6/4/93";
+#else
+static char *rcsid = "$OpenBSD: strsep.c,v 1.3 1997/08/20 04:28:14 millert Exp $";
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+#ifndef HAVE_STRSEP
+/*
+ * Get next token from string *stringp, where tokens are possibly-empty
+ * strings separated by characters from delim.  
+ *
+ * Writes NULs into the string at *stringp to end tokens.
+ * delim need not remain constant from call to call.
+ * On return, *stringp points past the last NUL written (if there might
+ * be further tokens), or is NULL (if there are definitely no more tokens).
+ *
+ * If *stringp is NULL, strsep returns NULL.
+ */
+char *
+strsep(stringp, delim)
+	register char **stringp;
+	register const char *delim;
+{
+	register char *s;
+	register const char *spanp;
+	register int c, sc;
+	char *tok;
+
+	if ((s = *stringp) == NULL)
+		return (NULL);
+	for (tok = s;;) {
+		c = *s++;
+		spanp = delim;
+		do {
+			if ((sc = *spanp++) == c) {
+				if (c == 0)
+					s = NULL;
+				else
+					s[-1] = 0;
+				*stringp = s;
+				return (tok);
+			}
+		} while (sc != 0);
+	}
+	/* NOTREACHED */
+}
+#endif /* ! HAVE_STRSEP */
diff --git a/libtar/tar.h b/libtar/tar.h
new file mode 100644
index 0000000..ddfef75
--- /dev/null
+++ b/libtar/tar.h
@@ -0,0 +1,108 @@
+/* Extended tar format from POSIX.1.
+   Copyright (C) 1992, 1996 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Written by David J. MacKenzie.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#ifndef	_TAR_H
+#define	_TAR_H	1
+
+/* A tar archive consists of 512-byte blocks.
+   Each file in the archive has a header block followed by 0+ data blocks.
+   Two blocks of NUL bytes indicate the end of the archive.  */
+
+/* The fields of header blocks:
+   All strings are stored as ISO 646 (approximately ASCII) strings.
+
+   Fields are numeric unless otherwise noted below; numbers are ISO 646
+   representations of octal numbers, with leading zeros as needed.
+
+   linkname is only valid when typeflag==LNKTYPE.  It doesn't use prefix;
+   files that are links to pathnames >100 chars long can not be stored
+   in a tar archive.
+
+   If typeflag=={LNKTYPE,SYMTYPE,DIRTYPE} then size must be 0.
+
+   devmajor and devminor are only valid for typeflag=={BLKTYPE,CHRTYPE}.
+
+   chksum contains the sum of all 512 bytes in the header block,
+   treating each byte as an 8-bit unsigned value and treating the
+   8 bytes of chksum as blank characters.
+
+   uname and gname are used in preference to uid and gid, if those
+   names exist locally.
+
+   Field Name	Byte Offset	Length in Bytes	Field Type
+   name		0		100		NUL-terminated if NUL fits
+   mode		100		8
+   uid		108		8
+   gid		116		8
+   size		124		12
+   mtime	136		12
+   chksum	148		8
+   typeflag	156		1		see below
+   linkname	157		100		NUL-terminated if NUL fits
+   magic	257		6		must be TMAGIC (NUL term.)
+   version	263		2		must be TVERSION
+   uname	265		32		NUL-terminated
+   gname	297		32		NUL-terminated
+   devmajor	329		8
+   devminor	337		8
+   prefix	345		155		NUL-terminated if NUL fits
+
+   If the first character of prefix is '\0', the file name is name;
+   otherwise, it is prefix/name.  Files whose pathnames don't fit in that
+   length can not be stored in a tar archive.  */
+
+/* The bits in mode: */
+#define TSUID	04000
+#define TSGID	02000
+#define TSVTX	01000
+#define TUREAD	00400
+#define TUWRITE	00200
+#define TUEXEC	00100
+#define TGREAD	00040
+#define TGWRITE	00020
+#define TGEXEC	00010
+#define TOREAD	00004
+#define TOWRITE	00002
+#define TOEXEC	00001
+
+/* The values for typeflag:
+   Values 'A'-'Z' are reserved for custom implementations.
+   All other values are reserved for future POSIX.1 revisions.  */
+
+#define REGTYPE		'0'	/* Regular file (preferred code).  */
+#define AREGTYPE	'\0'	/* Regular file (alternate code).  */
+#define LNKTYPE		'1'	/* Hard link.  */
+#define SYMTYPE		'2'	/* Symbolic link (hard if not supported).  */
+#define CHRTYPE		'3'	/* Character special.  */
+#define BLKTYPE		'4'	/* Block special.  */
+#define DIRTYPE		'5'	/* Directory.  */
+#define FIFOTYPE	'6'	/* Named pipe.  */
+#define CONTTYPE	'7'	/* Contiguous file */
+ /* (regular file if not supported).  */
+
+/* Contents of magic field and its length.  */
+#define TMAGIC	"ustar"
+#define TMAGLEN	6
+
+/* Contents of the version field and its length.  */
+#define TVERSION	"00"
+#define TVERSLEN	2
+
+#endif /* tar.h */
diff --git a/libtar/util.c b/libtar/util.c
new file mode 100644
index 0000000..7fb3f51
--- /dev/null
+++ b/libtar/util.c
@@ -0,0 +1,221 @@
+/*
+**  Copyright 1998-2003 University of Illinois Board of Trustees
+**  Copyright 1998-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  util.c - miscellaneous utility code for libtar
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <internal.h>
+
+#include <stdio.h>
+#include <sys/param.h>
+#include <errno.h>
+#include <linux/capability.h>
+
+#ifdef STDC_HEADERS
+# include <string.h>
+#endif
+
+
+/* hashing function for pathnames */
+int
+path_hashfunc(char *key, int numbuckets)
+{
+	char buf[MAXPATHLEN];
+	char *p;
+
+	strcpy(buf, key);
+	p = basename(buf);
+
+	return (((unsigned int)p[0]) % numbuckets);
+}
+
+
+/* matching function for dev_t's */
+int
+dev_match(dev_t *dev1, dev_t *dev2)
+{
+	return !memcmp(dev1, dev2, sizeof(dev_t));
+}
+
+
+/* matching function for ino_t's */
+int
+ino_match(ino_t *ino1, ino_t *ino2)
+{
+	return !memcmp(ino1, ino2, sizeof(ino_t));
+}
+
+
+/* hashing function for dev_t's */
+int
+dev_hash(dev_t *dev)
+{
+	return *dev % 16;
+}
+
+
+/* hashing function for ino_t's */
+int
+ino_hash(ino_t *inode)
+{
+	return *inode % 256;
+}
+
+
+/*
+** mkdirhier() - create all directories in a given path
+** returns:
+**	0			success
+**	1			all directories already exist
+**	-1 (and sets errno)	error
+*/
+int
+mkdirhier(char *path)
+{
+	char src[MAXPATHLEN], dst[MAXPATHLEN] = "";
+	char *dirp, *nextp = src;
+	int retval = 1;
+
+	if (strlcpy(src, path, sizeof(src)) > sizeof(src))
+	{
+		errno = ENAMETOOLONG;
+		return -1;
+	}
+
+	if (path[0] == '/')
+		strcpy(dst, "/");
+
+	while ((dirp = strsep(&nextp, "/")) != NULL)
+	{
+		if (*dirp == '\0')
+			continue;
+
+		if (dst[0] != '\0')
+			strcat(dst, "/");
+		strcat(dst, dirp);
+
+		if (mkdir(dst, 0777) == -1)
+		{
+			if (errno != EEXIST)
+				return -1;
+		}
+		else
+			retval = 0;
+	}
+
+	return retval;
+}
+
+
+/* calculate header checksum */
+int
+th_crc_calc(TAR *t)
+{
+	int i, sum = 0;
+
+	for (i = 0; i < T_BLOCKSIZE; i++)
+		sum += ((unsigned char *)(&(t->th_buf)))[i];
+	for (i = 0; i < 8; i++)
+		sum += (' ' - (unsigned char)t->th_buf.chksum[i]);
+
+	return sum;
+}
+
+/* calculate a signed header checksum */
+int
+th_signed_crc_calc(TAR *t)
+{
+	int i, sum = 0;
+
+	for (i = 0; i < T_BLOCKSIZE; i++)
+		sum += ((signed char *)(&(t->th_buf)))[i];
+	for (i = 0; i < 8; i++)
+		sum += (' ' - (signed char)t->th_buf.chksum[i]);
+
+	return sum;
+}
+
+/* string-octal to integer conversion */
+int64_t
+oct_to_int(char *oct, size_t octlen)
+{
+	long long int val;
+	char tmp[octlen + 1];
+
+	memcpy(tmp, oct, octlen);
+	tmp[octlen] = '\0';
+	return sscanf(oct, "%llo", &val) == 1 ? (int64_t)val : 0;
+}
+
+
+/* string-octal or binary to integer conversion */
+int64_t oct_to_int_ex(char *oct, size_t octlen)
+{
+	if (*(unsigned char *)oct & 0x80) {
+		int64_t val = 0;
+		char tmp[octlen];
+		unsigned char *p;
+		unsigned int i;
+
+		memcpy(tmp, oct, octlen);
+		*tmp &= 0x7f;
+		p = (unsigned char *)tmp + octlen - sizeof(val);
+		for (i = 0; i < sizeof(val); ++i) {
+			val <<= 8;
+			val |= *(p++);
+		}
+		return val;
+	}
+	return oct_to_int(oct, octlen);
+}
+
+
+/* integer to NULL-terminated string-octal conversion */
+void int_to_oct(int64_t num, char *oct, size_t octlen)
+{
+	char tmp[sizeof(num)*3 + 1];
+	int olen;
+
+	olen = sprintf(tmp, "%0*llo", (int)octlen, (long long)num);
+	memcpy(oct, tmp + olen - octlen + 1, octlen);
+}
+
+
+/* integer to string-octal conversion, or binary as necessary */
+void
+int_to_oct_ex(int64_t num, char *oct, size_t octlen)
+{
+	if (num < 0 || num >= ((int64_t)1 << ((octlen - 1) * 3))) {
+		unsigned char *p;
+		unsigned int i;
+
+		memset(oct, 0, octlen);
+		p = (unsigned char *)oct + octlen;
+		for (i = 0; i < sizeof(num); ++i) {
+			*(--p) = num & 0xff;
+			num >>= 8;
+		}
+		if (num < 0) {
+			for (; i < octlen; ++i) {
+				*(--p) = 0xff;
+			}
+		}
+		*(unsigned char *)oct |= 0x80;
+		return;
+	}
+	int_to_oct(num, oct, octlen);
+}
+
+void print_caps(struct vfs_cap_data *cap_data) {
+	printf("     magic_etc=%u \n", cap_data->magic_etc);
+	printf("     data[0].permitted=%u \n", cap_data->data[0].permitted);
+	printf("     data[0].inheritable=%u \n", cap_data->data[0].inheritable);
+	printf("     data[1].permitted=%u \n", cap_data->data[1].permitted);
+	printf("     data[1].inheritable=%u \n", cap_data->data[1].inheritable);
+}
diff --git a/libtar/wrapper.c b/libtar/wrapper.c
new file mode 100644
index 0000000..d3d285f
--- /dev/null
+++ b/libtar/wrapper.c
@@ -0,0 +1,181 @@
+/*
+**  Copyright 1998-2003 University of Illinois Board of Trustees
+**  Copyright 1998-2003 Mark D. Roth
+**  All rights reserved.
+**
+**  wrapper.c - libtar high-level wrapper code
+**
+**  Mark D. Roth <roth@uiuc.edu>
+**  Campus Information Technologies and Educational Services
+**  University of Illinois at Urbana-Champaign
+*/
+
+#include <internal.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include <dirent.h>
+#include <errno.h>
+
+#ifdef STDC_HEADERS
+# include <string.h>
+#endif
+
+
+int
+tar_extract_glob(TAR *t, char *globname, char *prefix)
+{
+	char *filename;
+	char buf[MAXPATHLEN];
+	int i, fd = 0;
+
+	while ((i = th_read(t)) == 0)
+	{
+		filename = th_get_pathname(t);
+		if (fnmatch(globname, filename, FNM_PATHNAME | FNM_PERIOD))
+		{
+			if (TH_ISREG(t) && tar_skip_regfile(t))
+				return -1;
+			continue;
+		}
+		if (t->options & TAR_VERBOSE)
+			th_print_long_ls(t);
+		if (prefix != NULL)
+			snprintf(buf, sizeof(buf), "%s/%s", prefix, filename);
+		else
+			strlcpy(buf, filename, sizeof(buf));
+		if (tar_extract_file(t, buf, prefix, &fd) != 0)
+			return -1;
+	}
+
+	return (i == 1 ? 0 : -1);
+}
+
+
+int
+tar_extract_all(TAR *t, char *prefix, const int *progress_fd)
+{
+	char *filename;
+	char buf[MAXPATHLEN];
+	int i;
+
+#ifdef DEBUG
+	printf("==> tar_extract_all(TAR *t, \"%s\")\n",
+	       (prefix ? prefix : "(null)"));
+#endif
+
+	while ((i = th_read(t)) == 0)
+	{
+#ifdef DEBUG
+		puts("    tar_extract_all(): calling th_get_pathname()");
+#endif
+		filename = th_get_pathname(t);
+		if (t->options & TAR_VERBOSE)
+			th_print_long_ls(t);
+		if (prefix != NULL)
+			snprintf(buf, sizeof(buf), "%s/%s", prefix, filename);
+		else
+			strlcpy(buf, filename, sizeof(buf));
+#ifdef DEBUG
+		printf("    tar_extract_all(): calling tar_extract_file(t, "
+		       "\"%s\")\n", buf);
+#endif
+		if (tar_extract_file(t, buf, prefix, progress_fd) != 0)
+			return -1;
+	}
+
+	return (i == 1 ? 0 : -1);
+}
+
+
+int
+tar_append_tree(TAR *t, char *realdir, char *savedir)
+{
+	char realpath[MAXPATHLEN];
+	char savepath[MAXPATHLEN];
+	struct dirent *dent;
+	DIR *dp;
+	struct stat s;
+
+#ifdef DEBUG
+	printf("==> tar_append_tree(0x%lx, \"%s\", \"%s\")\n",
+	       t, realdir, (savedir ? savedir : "[NULL]"));
+#endif
+
+	if (tar_append_file(t, realdir, savedir) != 0)
+		return -1;
+
+#ifdef DEBUG
+	puts("    tar_append_tree(): done with tar_append_file()...");
+#endif
+
+	dp = opendir(realdir);
+	if (dp == NULL)
+	{
+		if (errno == ENOTDIR)
+			return 0;
+		return -1;
+	}
+	while ((dent = readdir(dp)) != NULL)
+	{
+		if (strcmp(dent->d_name, ".") == 0 ||
+		    strcmp(dent->d_name, "..") == 0)
+			continue;
+
+		snprintf(realpath, MAXPATHLEN, "%s/%s", realdir,
+			 dent->d_name);
+		if (savedir)
+			snprintf(savepath, MAXPATHLEN, "%s/%s", savedir,
+				 dent->d_name);
+
+		if (lstat(realpath, &s) != 0)
+			return -1;
+
+		if (S_ISDIR(s.st_mode))
+		{
+			if (tar_append_tree(t, realpath,
+					    (savedir ? savepath : NULL)) != 0)
+				return -1;
+			continue;
+		}
+
+		if (tar_append_file(t, realpath,
+				    (savedir ? savepath : NULL)) != 0)
+			return -1;
+	}
+
+	closedir(dp);
+
+	return 0;
+}
+
+
+int
+tar_find(TAR *t, char *searchstr)
+{
+	if (!searchstr)
+		return 0;
+
+	char *filename;
+	int i, entryfound = 0;
+#ifdef DEBUG
+	printf("==> tar_find(0x%lx, %s)\n", (long unsigned int)t, searchstr);
+#endif
+	while ((i = th_read(t)) == 0) {
+		filename = th_get_pathname(t);
+		if (fnmatch(searchstr, filename, FNM_FILE_NAME | FNM_PERIOD) == 0) {
+			entryfound++;
+#ifdef DEBUG
+			printf("Found matching entry: %s\n", filename);
+#endif
+			break;
+		}
+	}
+#ifdef DEBUG
+	if (!entryfound)
+		printf("No matching entry found.\n");
+#endif
+
+	return entryfound;
+}
diff --git a/minadbd/Android.bp b/minadbd/Android.bp
index 9d3f733..ec56ef0 100644
--- a/minadbd/Android.bp
+++ b/minadbd/Android.bp
@@ -33,6 +33,7 @@
     cpp_std: "experimental",
 
     include_dirs: [
+        "bootable/recovery/otautil/include",
         "packages/modules/adb",
     ],
 
@@ -79,6 +80,7 @@
     // adb_install.cpp
     visibility: [
         "//bootable/recovery/install",
+        "//bootable/recovery/twrpinstall",
     ],
 }
 
diff --git a/minadbd/fuse_adb_provider.h b/minadbd/fuse_adb_provider.h
old mode 100644
new mode 100755
diff --git a/minadbd/minadbd.cpp b/minadbd/minadbd.cpp
old mode 100644
new mode 100755
index 1df342c..83586a6
--- a/minadbd/minadbd.cpp
+++ b/minadbd/minadbd.cpp
@@ -69,8 +69,8 @@
   init_transport_registration();
   usb_init();
 
-  VLOG(ADB) << "Event loop starting";
-  fdevent_loop();
+    //VLOG(ADB) << "Event loop starting";
+    fdevent_loop();
 
   return 0;
 }
diff --git a/minadbd/minadbd_services.cpp b/minadbd/minadbd_services.cpp
old mode 100644
new mode 100755
index 0abe867..7401847
--- a/minadbd/minadbd_services.cpp
+++ b/minadbd/minadbd_services.cpp
@@ -357,3 +357,10 @@
   }
   return unique_fd{};
 }
+
+#if PLATFORM_SDK_VERSION == 23
+int service_to_fd(const char* name) {
+    atransport transport;
+    return service_to_fd(name, &transport);
+}
+#endif
diff --git a/minui/Android.mk b/minui/Android.mk
new file mode 100755
index 0000000..5ccdf4f
--- /dev/null
+++ b/minui/Android.mk
@@ -0,0 +1,160 @@
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# libminui (static library)
+# ===============================
+# include $(CLEAR_VARS)
+
+# LOCAL_SRC_FILES := \
+#     events.cpp \
+#     graphics.cpp \
+#     graphics_drm.cpp \
+#     graphics_fbdev.cpp \
+#     graphics_overlay.cpp \
+#     resources.cpp
+
+# LOCAL_C_INCLUDES := external/libcxx/include external/libpng
+
+# ifeq ($(TW_TARGET_USES_QCOM_BSP), true)
+#   LOCAL_CFLAGS += -DMSM_BSP
+#   LOCAL_SRC_FILES += graphics_overlay.cpp
+#   ifeq ($(TARGET_PREBUILT_KERNEL),)
+#     ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 28; echo $$?),0)
+#       LOCAL_REQUIRED_MODULES := $(TARGET_OUT_INTERMEDIATES)/KERNEL_OBJ/usr
+#     else
+#       LOCAL_ADDITIONAL_DEPENDENCIES := $(TARGET_OUT_INTERMEDIATES)/KERNEL_OBJ/usr
+#     endif
+#     LOCAL_C_INCLUDES += $(TARGET_OUT_INTERMEDIATES)/KERNEL_OBJ/usr/include
+#   else
+#     ifeq ($(TARGET_CUSTOM_KERNEL_HEADERS),)
+#       LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
+#     else
+#       LOCAL_C_INCLUDES += $(TARGET_CUSTOM_KERNEL_HEADERS)
+#     endif
+#   endif
+# else
+#   LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
+#   # The header files required for adf graphics can cause compile errors
+#   # with adf graphics.
+#   LOCAL_SRC_FILES += graphics_adf.cpp
+#   LOCAL_WHOLE_STATIC_LIBRARIES += libadf
+# endif
+
+# ifeq ($(TW_NEW_ION_HEAP), true)
+#   LOCAL_CFLAGS += -DNEW_ION_HEAP
+# endif
+
+# LOCAL_STATIC_LIBRARIES += libpng libbase
+# ifneq ($(wildcard external/libdrm/Android.common.mk),)
+# LOCAL_WHOLE_STATIC_LIBRARIES += libdrm_platform
+# else
+# LOCAL_WHOLE_STATIC_LIBRARIES += libdrm
+# endif
+# ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 26; echo $$?),0)
+#     LOCAL_CFLAGS += -DHAS_LIBSYNC
+#     LOCAL_WHOLE_STATIC_LIBRARIES += libsync_recovery
+# endif
+
+# LOCAL_CFLAGS += -Wall -Werror -std=c++14 -Wno-unused-private-field
+# LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
+# LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+
+# LOCAL_MODULE := libminui
+
+# LOCAL_CLANG := true
+
+# # This used to compare against values in double-quotes (which are just
+# # ordinary characters in this context).  Strip double-quotes from the
+# # value so that either will work.
+
+# ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),ABGR_8888)
+#   LOCAL_CFLAGS += -DRECOVERY_ABGR
+# endif
+# ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBA_8888)
+#   LOCAL_CFLAGS += -DRECOVERY_RGBA
+# endif
+# ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),RGBX_8888)
+#   LOCAL_CFLAGS += -DRECOVERY_RGBX
+# endif
+# ifeq ($(subst ",,$(TARGET_RECOVERY_PIXEL_FORMAT)),BGRA_8888)
+#   LOCAL_CFLAGS += -DRECOVERY_BGRA
+# endif
+
+# ifneq ($(TARGET_RECOVERY_OVERSCAN_PERCENT),)
+#   LOCAL_CFLAGS += -DOVERSCAN_PERCENT=$(TARGET_RECOVERY_OVERSCAN_PERCENT)
+# else
+#   LOCAL_CFLAGS += -DOVERSCAN_PERCENT=0
+# endif
+
+# ifneq ($(TW_BRIGHTNESS_PATH),)
+#   LOCAL_CFLAGS += -DTW_BRIGHTNESS_PATH=\"$(TW_BRIGHTNESS_PATH)\"
+# endif
+# ifneq ($(TW_MAX_BRIGHTNESS),)
+#   LOCAL_CFLAGS += -DTW_MAX_BRIGHTNESS=$(TW_MAX_BRIGHTNESS)
+# else
+#   LOCAL_CFLAGS += -DTW_MAX_BRIGHTNESS=255
+# endif
+# ifneq ($(TW_NO_SCREEN_BLANK),)
+#   LOCAL_CFLAGS += -DTW_NO_SCREEN_BLANK
+# endif
+# ifneq ($(BOARD_USE_CUSTOM_RECOVERY_FONT),)
+#   LOCAL_CFLAGS += -DBOARD_USE_CUSTOM_RECOVERY_FONT=$(BOARD_USE_CUSTOM_RECOVERY_FONT)
+# endif
+# ifeq ($(wildcard system/core/healthd/animation.h),)
+#     TARGET_GLOBAL_CFLAGS += -DTW_NO_MINUI_CUSTOM_FONTS
+#     CLANG_TARGET_GLOBAL_CFLAGS += -DTW_NO_MINUI_CUSTOM_FONTS
+# endif
+# ifneq ($(TARGET_RECOVERY_DEFAULT_ROTATION),)
+#   LOCAL_CFLAGS += -DDEFAULT_ROTATION=$(TARGET_RECOVERY_DEFAULT_ROTATION)
+# else
+#   LOCAL_CFLAGS += -DDEFAULT_ROTATION=ROTATION_NONE
+# endif
+
+# include $(BUILD_STATIC_LIBRARY)
+
+# libminui (shared library)
+# ===============================
+# Used by OEMs for factory test images.
+# include $(CLEAR_VARS)
+# LOCAL_CLANG := true
+# LOCAL_MODULE := libminui
+# LOCAL_WHOLE_STATIC_LIBRARIES += libminui
+# LOCAL_SHARED_LIBRARIES := \
+#     libpng \
+#     libbase
+
+# LOCAL_CFLAGS := -Wall -Werror -std=c++14 -Wno-unused-private-field
+# LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+# LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+# include $(BUILD_SHARED_LIBRARY)
+
+# include $(CLEAR_VARS)
+# LOCAL_MODULE := minuitest
+# LOCAL_MODULE_TAGS := optional
+# LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT_SBIN)
+# LOCAL_SRC_FILES := main.cpp
+# LOCAL_SHARED_LIBRARIES := libbinder libminui libpng libz libutils libstdc++ libcutils liblog libm libc
+# LOCAL_C_INCLUDES := external/libcxx/include external/libpng
+# ifneq ($(TARGET_ARCH), arm64)
+#     ifneq ($(TARGET_ARCH), x86_64)
+#         LOCAL_LDFLAGS += -Wl,-dynamic-linker,/sbin/linker
+#     else
+#         LOCAL_LDFLAGS += -Wl,-dynamic-linker,/sbin/linker64
+#     endif
+# else
+#     LOCAL_LDFLAGS += -Wl,-dynamic-linker,/sbin/linker64
+# endif
+# include $(BUILD_EXECUTABLE)
diff --git a/minui/font_10x18.h b/minui/font_10x18.h
new file mode 100644
index 0000000..30dfb9c
--- /dev/null
+++ b/minui/font_10x18.h
@@ -0,0 +1,214 @@
+struct {
+  unsigned width;
+  unsigned height;
+  unsigned char_width;
+  unsigned char_height;
+  unsigned char rundata[2973];
+} font = {
+  .width = 960,
+  .height = 18,
+  .char_width = 10,
+  .char_height = 18,
+  .rundata = {
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x55,0x82,0x06,0x82,0x02,0x82,0x10,0x82,
+0x11,0x83,0x08,0x82,0x0a,0x82,0x04,0x82,0x46,0x82,0x08,0x82,0x07,0x84,0x06,
+0x84,0x0a,0x81,0x03,0x88,0x04,0x84,0x04,0x88,0x04,0x84,0x06,0x84,0x1e,0x81,
+0x0e,0x81,0x0a,0x84,0x06,0x84,0x07,0x82,0x05,0x85,0x07,0x84,0x04,0x86,0x04,
+0x88,0x02,0x88,0x04,0x84,0x04,0x82,0x04,0x82,0x02,0x88,0x05,0x86,0x01,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,
+0x86,0x06,0x84,0x04,0x86,0x06,0x84,0x04,0x88,0x02,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
+0x88,0x03,0x86,0x0e,0x86,0x06,0x82,0x11,0x82,0x10,0x82,0x18,0x82,0x0f,0x84,
+0x0d,0x82,0x1c,0x82,0x09,0x84,0x7f,0x16,0x84,0x05,0x82,0x05,0x84,0x07,0x83,
+0x02,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x03,0x86,0x04,
+0x83,0x02,0x82,0x03,0x82,0x01,0x82,0x07,0x82,0x09,0x82,0x06,0x82,0x3e,0x82,
+0x04,0x84,0x06,0x83,0x06,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x03,
+0x82,0x09,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,
+0x1c,0x82,0x0e,0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x05,0x84,0x04,
+0x82,0x02,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,
+0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x04,
+0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x03,0x82,0x02,0x82,
+0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02,
+0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x03,0x82,0x08,0x82,0x0c,
+0x82,0x05,0x84,0x11,0x82,0x0f,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,
+0x1c,0x82,0x0b,0x82,0x7f,0x15,0x82,0x08,0x82,0x08,0x82,0x05,0x82,0x01,0x82,
+0x01,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x02,0x82,0x01,
+0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x07,0x82,
+0x08,0x82,0x08,0x82,0x3d,0x82,0x03,0x82,0x02,0x82,0x04,0x84,0x05,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x06,0x83,0x03,0x82,0x08,0x82,0x04,0x81,0x09,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x1a,0x82,0x10,0x82,0x06,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,
+0x02,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x08,0x82,0x0c,0x82,0x04,0x82,0x02,0x82,
+0x11,0x82,0x0e,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,0x0b,0x82,0x0b,
+0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x05,0x82,
+0x02,0x83,0x1a,0x82,0x07,0x81,0x02,0x81,0x07,0x82,0x01,0x82,0x02,0x82,0x01,
+0x82,0x05,0x82,0x01,0x84,0x04,0x82,0x01,0x82,0x07,0x82,0x08,0x82,0x08,0x82,
+0x06,0x82,0x02,0x82,0x06,0x82,0x28,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,
+0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x84,0x03,0x82,0x08,0x82,
+0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x19,0x82,0x12,0x82,0x05,
+0x82,0x04,0x82,0x02,0x82,0x02,0x84,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,
+0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,
+0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,0x02,0x83,
+0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,0x05,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,
+0x82,0x04,0x82,0x09,0x82,0x0b,0x82,0x03,0x82,0x04,0x82,0x20,0x82,0x18,0x82,
+0x0e,0x82,0x10,0x82,0x0b,0x82,0x0b,0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,
+0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x88,0x01,0x82,0x01,0x82,0x06,0x83,
+0x01,0x82,0x04,0x84,0x08,0x81,0x08,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,
+0x82,0x28,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x08,0x82,0x04,0x82,
+0x01,0x82,0x03,0x82,0x08,0x82,0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x18,0x82,0x06,0x88,0x06,0x82,0x04,0x82,0x04,0x82,0x02,0x82,0x01,0x85,
+0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,
+0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,
+0x02,0x82,0x04,0x82,0x08,0x88,0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,
+0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x84,0x06,
+0x84,0x08,0x82,0x05,0x82,0x09,0x82,0x0b,0x82,0x2b,0x82,0x18,0x82,0x0e,0x82,
+0x10,0x82,0x1c,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x11,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x09,0x82,0x06,0x82,0x12,0x82,
+0x0a,0x82,0x06,0x84,0x07,0x82,0x27,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,
+0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,0x83,0x04,0x82,0x01,0x83,
+0x08,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x83,0x07,0x83,0x05,
+0x82,0x16,0x82,0x08,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,
+0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,
+0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,
+0x0a,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,
+0x82,0x04,0x84,0x06,0x84,0x08,0x82,0x05,0x82,0x0a,0x82,0x0a,0x82,0x23,0x85,
+0x03,0x82,0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x84,0x04,0x86,0x05,
+0x85,0x01,0x81,0x02,0x82,0x01,0x83,0x05,0x84,0x09,0x84,0x02,0x82,0x03,0x82,
+0x06,0x82,0x05,0x81,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x83,0x06,0x84,0x04,
+0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x02,0x82,0x01,0x84,0x04,0x86,0x03,0x86,
+0x04,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x03,0x87,0x05,0x82,0x08,0x82,0x08,0x82,0x26,0x82,
+0x11,0x82,0x01,0x82,0x04,0x86,0x07,0x82,0x05,0x83,0x12,0x82,0x0a,0x82,0x04,
+0x88,0x02,0x88,0x0c,0x88,0x10,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,
+0x06,0x83,0x04,0x82,0x03,0x82,0x03,0x83,0x02,0x82,0x03,0x83,0x02,0x82,0x07,
+0x82,0x06,0x84,0x05,0x82,0x02,0x83,0x05,0x83,0x07,0x83,0x04,0x82,0x18,0x82,
+0x06,0x82,0x04,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x86,0x04,
+0x82,0x08,0x82,0x04,0x82,0x02,0x86,0x04,0x86,0x04,0x82,0x02,0x84,0x02,0x88,
+0x05,0x82,0x0a,0x82,0x03,0x85,0x05,0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,
+0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,
+0x04,0x82,0x02,0x82,0x03,0x82,0x05,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x03,
+0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x08,0x82,0x08,0x82,
+0x06,0x82,0x0a,0x82,0x0a,0x82,0x22,0x82,0x03,0x82,0x02,0x83,0x02,0x82,0x04,
+0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x02,0x82,0x05,0x82,0x06,0x82,
+0x03,0x83,0x02,0x83,0x02,0x82,0x06,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,0x07,
+0x82,0x05,0x88,0x02,0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,
+0x04,0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,
+0x03,0x82,0x04,0x82,0x08,0x82,0x02,0x84,0x09,0x82,0x09,0x84,0x23,0x82,0x11,
+0x82,0x01,0x82,0x06,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x01,0x82,0x11,0x82,
+0x0a,0x82,0x06,0x84,0x07,0x82,0x26,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x08,
+0x83,0x09,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x02,0x82,0x04,0x82,0x05,0x82,
+0x06,0x82,0x02,0x82,0x05,0x83,0x01,0x82,0x17,0x82,0x16,0x82,0x06,0x82,0x05,
+0x82,0x01,0x82,0x01,0x82,0x02,0x88,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
+0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82,
+0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x86,0x04,0x82,0x04,0x82,0x02,
+0x86,0x09,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,
+0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,0x82,0x27,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,
+0x82,0x01,0x82,0x08,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x07,
+0x82,0x0a,0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,
+0x04,0x84,0x04,0x82,0x04,0x82,0x07,0x82,0x06,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x0f,0x88,0x05,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x02,0x82,0x01,0x82,
+0x0d,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,0x82,0x26,0x82,0x05,0x82,0x04,
+0x82,0x05,0x82,0x07,0x82,0x0c,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,
+0x05,0x82,0x05,0x82,0x04,0x82,0x08,0x82,0x18,0x82,0x14,0x82,0x07,0x82,0x05,
+0x82,0x01,0x84,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
+0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82,
+0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,
+0x82,0x02,0x82,0x0a,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,
+0x01,0x82,0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,
+0x82,0x22,0x87,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x88,
+0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,
+0x84,0x09,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x86,0x05,
+0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,
+0x05,0x82,0x05,0x82,0x04,0x82,0x06,0x82,0x07,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x10,0x82,0x01,0x82,0x07,0x82,0x01,0x82,0x04,0x82,0x01,0x83,0x02,0x82,
+0x03,0x83,0x0f,0x82,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x25,0x82,0x07,
+0x82,0x02,0x82,0x06,0x82,0x06,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x09,0x82,
+0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,0x82,0x08,0x82,0x19,0x82,0x05,
+0x88,0x05,0x82,0x08,0x82,0x05,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
+0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,
+0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x03,0x82,0x09,0x82,0x05,0x82,
+0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x06,
+0x82,0x06,0x82,0x08,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,0x82,0x06,0x82,0x03,
+0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x85,0x08,0x82,0x05,0x82,
+0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,0x06,0x82,0x04,0x82,
+0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,
+0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x38,0x82,0x01,0x82,0x04,0x82,0x01,0x82,
+0x01,0x82,0x04,0x84,0x01,0x82,0x01,0x82,0x03,0x82,0x10,0x82,0x08,0x82,0x30,
+0x83,0x06,0x82,0x07,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x08,0x82,0x04,0x82,
+0x07,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,
+0x82,0x03,0x81,0x04,0x82,0x1a,0x82,0x10,0x82,0x10,0x82,0x08,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,
+0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,
+0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,
+0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x02,0x84,0x02,0x82,0x03,0x82,0x03,0x82,
+0x04,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x05,0x83,0x02,0x83,0x03,
+0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,
+0x82,0x07,0x85,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,
+0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,
+0x06,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x04,0x84,0x04,
+0x82,0x04,0x82,0x04,0x82,0x09,0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x82,
+0x01,0x82,0x05,0x86,0x04,0x82,0x01,0x82,0x01,0x82,0x01,0x83,0x01,0x84,0x10,
+0x82,0x06,0x82,0x1d,0x83,0x11,0x83,0x05,0x82,0x09,0x84,0x07,0x82,0x05,0x82,
+0x09,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x06,0x83,0x07,0x83,0x09,0x82,
+0x0e,0x82,0x0a,0x82,0x06,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,
+0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x09,0x82,
+0x02,0x83,0x02,0x82,0x04,0x82,0x05,0x82,0x06,0x82,0x01,0x82,0x04,0x82,0x04,
+0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,
+0x03,0x82,0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x06,
+0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x05,0x82,0x05,0x82,0x09,0x82,0x0d,0x82,0x07,0x82,0x21,0x82,0x04,0x82,0x02,
+0x83,0x02,0x82,0x04,0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x03,0x82,
+0x04,0x82,0x06,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x03,
+0x82,0x06,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x03,0x82,
+0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x07,0x82,0x04,
+0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x02,0x83,0x05,0x82,0x05,0x88,0x03,0x82,
+0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x0a,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x1c,0x82,0x06,0x82,0x02,0x83,0x03,0x84,0x02,0x82,0x10,0x82,0x04,0x82,
+0x1e,0x83,0x11,0x83,0x05,0x82,0x0a,0x82,0x05,0x88,0x02,0x88,0x04,0x84,0x09,
+0x82,0x05,0x84,0x06,0x84,0x05,0x82,0x09,0x84,0x06,0x84,0x07,0x83,0x07,0x83,
+0x0a,0x81,0x0e,0x81,0x0b,0x82,0x07,0x85,0x03,0x82,0x04,0x82,0x02,0x86,0x06,
+0x84,0x04,0x86,0x04,0x88,0x02,0x82,0x0a,0x84,0x01,0x81,0x02,0x82,0x04,0x82,
+0x02,0x88,0x04,0x83,0x05,0x82,0x04,0x82,0x02,0x88,0x02,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x0a,0x85,0x03,0x82,0x04,0x82,0x04,0x84,
+0x07,0x82,0x07,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
+0x82,0x05,0x88,0x03,0x86,0x09,0x82,0x03,0x86,0x22,0x85,0x01,0x81,0x02,0x82,
+0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x85,0x05,0x82,0x07,0x86,0x03,
+0x82,0x04,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x88,0x02,0x82,
+0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x83,0x06,
+0x83,0x01,0x82,0x03,0x82,0x08,0x86,0x06,0x84,0x05,0x83,0x01,0x82,0x05,0x82,
+0x06,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x83,0x01,0x82,0x03,0x87,0x06,
+0x84,0x05,0x82,0x05,0x84,0x7f,0x15,0x83,0x7f,0x14,0x83,0x7f,0x5e,0x82,0x7f,
+0x05,0x89,0x47,0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x4e,
+0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,0x82,0x04,0x82,0x17,0x82,0x03,0x82,
+0x34,0x82,0x0e,0x82,0x48,0x82,0x04,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,
+0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x49,0x82,0x02,0x82,
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0c,0x86,0x19,0x85,0x35,0x82,0x0e,0x82,0x4a,
+0x84,0x3f,
+0x00,
+  }
+};
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index b24c2b1..41a3661 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -502,3 +502,7 @@
 void gr_rotate(GRRotation rot) {
   rotation = rot;
 }
+
+bool gr_has_multiple_connectors() {
+  return gr_backend->HasMultipleConnectors();
+}
diff --git a/minui/graphics.h b/minui/graphics.h
index 5408c93..ff063ae 100644
--- a/minui/graphics.h
+++ b/minui/graphics.h
@@ -40,8 +40,11 @@
   // Blank (or unblank) the specific screen.
   virtual void Blank(bool blank, DrmConnector index) = 0;
 
+  // Return true if the device supports multiple connectors.
+  virtual bool HasMultipleConnectors() = 0;
+
   // Device cleanup when drawing is done.
-  virtual ~MinuiBackend() {};
+  virtual ~MinuiBackend() = default;
 };
 
 #endif  // _GRAPHICS_H_
diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp
index c557022..6c3a5bd 100644
--- a/minui/graphics_drm.cpp
+++ b/minui/graphics_drm.cpp
@@ -200,6 +200,10 @@
   }
 }
 
+bool MinuiBackendDrm::HasMultipleConnectors() {
+  return (drm[DRM_SEC].GRSurfaceDrms[0] && drm[DRM_SEC].GRSurfaceDrms[1]);
+}
+
 static drmModeCrtc* find_crtc_for_connector(int fd, drmModeRes* resources,
                                             drmModeConnector* connector) {
   // Find the encoder. If we already have one, just use it.
diff --git a/minui/graphics_drm.h b/minui/graphics_drm.h
index fe3beaf..a8c9886 100644
--- a/minui/graphics_drm.h
+++ b/minui/graphics_drm.h
@@ -60,6 +60,7 @@
   GRSurface* Flip() override;
   void Blank(bool) override;
   void Blank(bool blank, DrmConnector index) override;
+  bool HasMultipleConnectors() override;
 
  private:
   void DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc);
diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp
index 1cb0c0a..4a7d325 100644
--- a/minui/graphics_fbdev.cpp
+++ b/minui/graphics_fbdev.cpp
@@ -47,6 +47,11 @@
   fprintf(stderr, "Unsupported multiple connectors, blank = %d, index = %d\n", blank, index);
 }
 
+bool MinuiBackendFbdev::HasMultipleConnectors() {
+  fprintf(stderr, "Unsupported multiple connectors\n");
+  return false;
+}
+
 void MinuiBackendFbdev::SetDisplayedFramebuffer(size_t n) {
   if (n > 1 || !double_buffered) return;
 
diff --git a/minui/graphics_fbdev.h b/minui/graphics_fbdev.h
index 7e193c4..c772428 100644
--- a/minui/graphics_fbdev.h
+++ b/minui/graphics_fbdev.h
@@ -57,6 +57,7 @@
   GRSurface* Flip() override;
   void Blank(bool) override;
   void Blank(bool blank, DrmConnector index) override;
+  bool HasMultipleConnectors() override;
 
  private:
   void SetDisplayedFramebuffer(size_t n);
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index f9be82f..2353ed3 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -129,6 +129,7 @@
 void gr_flip();
 void gr_fb_blank(bool blank);
 void gr_fb_blank(bool blank, int index);
+bool gr_has_multiple_connectors();
 
 // Clears entire surface to current color.
 void gr_clear();
diff --git a/minui/main.cpp b/minui/main.cpp
new file mode 100644
index 0000000..4c3c52c
--- /dev/null
+++ b/minui/main.cpp
@@ -0,0 +1,113 @@
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include <time.h>
+
+#include <minui/minui.h>
+#include "graphics.h"
+
+int main() {
+	// It might be a good idea to add some blending tests.
+	// The only blending done currently is around the font / text
+	int i = 0;
+    printf("Initializing graphics.\n");
+	if (gr_init() != 0) {
+	    printf("Error initializing graphics.\n");
+	    return -1;
+	}
+	printf("Starting tests\n");
+	printf("Red\n");
+	gr_color(255, 0, 0, 255);
+	gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+	gr_flip();
+	sleep(1);
+	printf("Green\n");
+	gr_color(0, 225, 0, 255);
+	gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+	gr_flip();
+	sleep(1);
+	printf("Blue\n");
+	gr_color(0, 0, 255, 255);
+	gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+	gr_flip();
+	sleep(1);
+	printf("White\n");
+	gr_color(255, 255, 255, 255);
+	gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+	gr_flip();
+	sleep(1);
+	printf("4 colors, 1 in each corner\n");
+	gr_color(255, 0, 0, 255);
+	gr_fill(0, 0, gr_fb_width() / 2, gr_fb_height() / 2);
+	gr_color(0, 255, 0, 255);
+	gr_fill(0, gr_fb_height() / 2, gr_fb_width() / 2, gr_fb_height());
+	gr_color(0, 0, 255, 255);
+	gr_fill(gr_fb_width() / 2, 0, gr_fb_width(), gr_fb_height() / 2);
+	gr_color(255, 255, 255, 255);
+	gr_fill(gr_fb_width() / 2, gr_fb_height() / 2, gr_fb_width(), gr_fb_height());
+	gr_flip();
+	sleep(3);
+	printf("4 colors, vertical stripes\n");
+	gr_color(255, 0, 0, 255);
+	gr_fill(0, 0, gr_fb_width() / 4, gr_fb_height());
+	gr_color(0, 255, 0, 255);
+	gr_fill(gr_fb_width() / 4, 0, gr_fb_width() / 2, gr_fb_height());
+	gr_color(0, 0, 255, 255);
+	gr_fill(gr_fb_width() / 2, 0, gr_fb_width() * 3 / 4, gr_fb_height());
+	gr_color(255, 255, 255, 255);
+	gr_fill(gr_fb_width() * 3 / 4, 0, gr_fb_width(), gr_fb_height());
+	gr_flip();
+	sleep(3);
+	printf("Gradients, 1 in each corner\n");
+	gr_color(0, 0, 0, 255);
+	gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+	for (i = 0; i < 255; i++) {
+		gr_color(i, 0, 0, 255);
+		gr_fill(i, 0, i+1, gr_fb_height() / 2);
+		gr_color(0, i, 0, 255);
+		gr_fill(i, gr_fb_height() / 2, i+1, gr_fb_height());
+		gr_color(0, 0, i, 255);
+		gr_fill(i + (gr_fb_width() / 2), 0, i + (gr_fb_width() / 2) + 1, gr_fb_height() / 2);
+		gr_color(i, i, i, 255);
+		gr_fill(i + (gr_fb_width() / 2), gr_fb_height() / 2, i + (gr_fb_width() / 2) + 1, gr_fb_height());
+	}
+	gr_flip();
+	sleep(3);
+	printf("White with RGB text\n");
+	gr_color(255, 255, 255, 255);
+	gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+	gr_color(255, 0, 0, 255);
+	gr_text(gr_sys_font(), 10, 10, "RED red RED", false);
+	gr_color(0, 255, 0, 255);
+	gr_text(gr_sys_font(), 10, 50, "GREEN green GREEN", false);
+	gr_color(0, 0, 255, 255);
+	gr_text(gr_sys_font(), 10, 90, "BLUE blue BLUE", false);
+	gr_flip();
+	sleep(3);
+	printf("PNG test with /res/images/test.png\n");
+	GRSurface* icon[2];
+	gr_color(0, 0, 0, 255);
+	gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+	res_create_display_surface("test", icon);
+	GRSurface* surface = icon[0];
+	gr_blit(surface, 0, 0, gr_get_width(surface), gr_get_height(surface), 10, 10);
+	gr_flip();
+	res_free_surface(surface);
+	sleep(3);
+	printf("Exit graphics.\n");
+	gr_exit();
+	printf("Done.\n");
+	return 0;
+}
diff --git a/minui/roboto_10x18.h b/minui/roboto_10x18.h
new file mode 100644
index 0000000..2d1535b
--- /dev/null
+++ b/minui/roboto_10x18.h
@@ -0,0 +1,197 @@
+struct {
+  unsigned width;
+  unsigned height;
+  unsigned char_width;
+  unsigned char_height;
+  unsigned char rundata[2718];
+} font = {
+  .width = 960,
+  .height = 18,
+  .char_width = 10,
+  .char_height = 18,
+  .rundata = {
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x3b,0x81,0x29,0x81,0x06,0x81,0x3f,0x81,0x7f,0x7f,0x7f,0x37,0x83,0x05,0x81,
+0x0a,0x83,0x7f,0x7f,0x2f,0x81,0x0e,0x81,0x29,0x82,0x07,0x81,0x01,0x82,0x07,
+0x81,0x02,0x81,0x05,0x83,0x05,0x82,0x0a,0x82,0x09,0x82,0x09,0x81,0x08,0x81,
+0x3e,0x81,0x05,0x84,0x06,0x84,0x06,0x83,0x06,0x84,0x09,0x82,0x05,0x85,0x06,
+0x84,0x04,0x87,0x04,0x84,0x07,0x84,0x39,0x83,0x06,0x84,0x07,0x81,0x06,0x86,
+0x06,0x84,0x04,0x86,0x04,0x87,0x04,0x87,0x04,0x84,0x04,0x82,0x04,0x82,0x03,
+0x86,0x08,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x07,0x82,0x05,0x82,0x02,0x82,
+0x05,0x81,0x04,0x84,0x04,0x86,0x06,0x84,0x04,0x86,0x06,0x84,0x04,0x88,0x02,
+0x82,0x05,0x81,0x01,0x82,0x06,0x81,0x01,0x81,0x07,0x81,0x02,0x81,0x05,0x82,
+0x02,0x82,0x04,0x82,0x02,0x87,0x06,0x81,0x07,0x82,0x0b,0x81,0x08,0x81,0x12,
+0x82,0x10,0x82,0x18,0x82,0x10,0x83,0x0d,0x82,0x0b,0x82,0x08,0x82,0x06,0x82,
+0x09,0x83,0x4c,0x82,0x48,0x81,0x07,0x82,0x07,0x81,0x28,0x82,0x07,0x81,0x01,
+0x82,0x07,0x81,0x02,0x81,0x04,0x82,0x01,0x82,0x03,0x81,0x02,0x81,0x08,0x81,
+0x02,0x81,0x08,0x82,0x08,0x82,0x08,0x82,0x3c,0x82,0x04,0x82,0x02,0x82,0x04,
+0x85,0x05,0x82,0x01,0x82,0x04,0x82,0x02,0x82,0x07,0x83,0x05,0x85,0x05,0x82,
+0x02,0x81,0x09,0x82,0x03,0x82,0x02,0x82,0x05,0x82,0x02,0x82,0x37,0x82,0x01,
+0x82,0x04,0x81,0x03,0x82,0x06,0x82,0x05,0x82,0x03,0x82,0x04,0x83,0x01,0x82,
+0x03,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x08,0x82,0x02,0x82,0x03,0x82,0x04,
+0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x07,0x82,0x05,0x82,
+0x02,0x82,0x05,0x81,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02,
+0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x05,0x81,
+0x02,0x81,0x05,0x82,0x01,0x81,0x07,0x81,0x02,0x82,0x04,0x81,0x03,0x82,0x04,
+0x82,0x07,0x82,0x06,0x81,0x08,0x81,0x0b,0x81,0x07,0x82,0x13,0x81,0x10,0x82,
+0x18,0x82,0x0f,0x84,0x0d,0x82,0x1d,0x82,0x0a,0x82,0x4c,0x82,0x47,0x82,0x07,
+0x82,0x07,0x82,0x27,0x82,0x07,0x81,0x01,0x82,0x06,0x81,0x02,0x81,0x04,0x82,
+0x03,0x82,0x02,0x81,0x02,0x81,0x02,0x81,0x04,0x82,0x02,0x82,0x08,0x81,0x08,
+0x81,0x0a,0x81,0x12,0x82,0x28,0x81,0x05,0x81,0x04,0x81,0x07,0x82,0x04,0x82,
+0x03,0x82,0x03,0x81,0x04,0x81,0x07,0x83,0x05,0x81,0x08,0x82,0x0c,0x82,0x04,
+0x81,0x04,0x82,0x04,0x81,0x04,0x81,0x36,0x82,0x03,0x81,0x03,0x81,0x05,0x82,
+0x04,0x83,0x05,0x82,0x04,0x81,0x03,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,
+0x82,0x09,0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,
+0x03,0x82,0x02,0x82,0x04,0x82,0x07,0x83,0x03,0x83,0x02,0x83,0x04,0x81,0x02,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x81,
+0x03,0x82,0x04,0x81,0x06,0x82,0x05,0x82,0x05,0x81,0x02,0x81,0x05,0x82,0x01,
+0x82,0x02,0x81,0x02,0x82,0x03,0x82,0x02,0x82,0x04,0x81,0x03,0x82,0x08,0x81,
+0x07,0x81,0x08,0x81,0x0b,0x81,0x07,0x83,0x13,0x81,0x0f,0x82,0x18,0x82,0x0e,
+0x82,0x10,0x82,0x1d,0x82,0x0a,0x82,0x4c,0x82,0x47,0x81,0x08,0x82,0x08,0x81,
+0x27,0x82,0x07,0x81,0x01,0x81,0x05,0x88,0x02,0x82,0x07,0x81,0x02,0x81,0x01,
+0x82,0x05,0x81,0x02,0x81,0x08,0x81,0x08,0x82,0x0a,0x82,0x07,0x81,0x09,0x82,
+0x27,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x0a,0x81,0x08,0x81,0x06,0x81,0x01,
+0x82,0x05,0x81,0x08,0x82,0x0c,0x81,0x05,0x81,0x04,0x82,0x04,0x81,0x04,0x81,
+0x05,0x82,0x08,0x82,0x2a,0x81,0x03,0x81,0x02,0x81,0x03,0x81,0x04,0x81,0x01,
+0x82,0x04,0x82,0x04,0x81,0x03,0x82,0x04,0x82,0x02,0x82,0x05,0x81,0x02,0x82,
+0x09,0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,
+0x82,0x02,0x81,0x05,0x82,0x07,0x83,0x03,0x83,0x02,0x84,0x03,0x81,0x02,0x82,
+0x05,0x81,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
+0x82,0x0b,0x82,0x05,0x82,0x05,0x81,0x02,0x82,0x04,0x81,0x03,0x81,0x02,0x82,
+0x01,0x82,0x04,0x81,0x01,0x82,0x05,0x82,0x01,0x82,0x08,0x82,0x07,0x81,0x08,
+0x82,0x0a,0x81,0x06,0x82,0x01,0x81,0x1a,0x85,0x04,0x82,0x01,0x83,0x06,0x84,
+0x06,0x83,0x01,0x82,0x04,0x84,0x06,0x82,0x07,0x85,0x04,0x82,0x01,0x83,0x06,
+0x83,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x81,0x01,0x82,0x01,0x82,
+0x03,0x82,0x01,0x84,0x05,0x84,0x04,0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x03,
+0x82,0x01,0x84,0x04,0x84,0x04,0x86,0x04,0x82,0x04,0x82,0x02,0x82,0x04,0x81,
+0x02,0x82,0x06,0x81,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x03,0x86,0x07,
+0x81,0x08,0x82,0x08,0x81,0x27,0x82,0x11,0x81,0x02,0x81,0x05,0x82,0x06,0x84,
+0x01,0x81,0x06,0x83,0x12,0x82,0x0a,0x82,0x05,0x81,0x01,0x81,0x01,0x81,0x07,
+0x82,0x27,0x81,0x05,0x82,0x04,0x82,0x06,0x82,0x09,0x82,0x07,0x82,0x05,0x82,
+0x01,0x82,0x04,0x85,0x05,0x81,0x01,0x83,0x08,0x82,0x06,0x81,0x02,0x82,0x05,
+0x81,0x04,0x81,0x05,0x82,0x08,0x82,0x0a,0x83,0x04,0x86,0x04,0x82,0x0c,0x82,
+0x02,0x81,0x02,0x81,0x01,0x81,0x02,0x81,0x04,0x81,0x01,0x82,0x04,0x82,0x03,
+0x82,0x03,0x82,0x08,0x82,0x05,0x81,0x02,0x82,0x09,0x82,0x07,0x81,0x09,0x82,
+0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x01,0x82,0x05,0x82,0x07,0x84,0x02,
+0x83,0x02,0x82,0x01,0x81,0x03,0x81,0x02,0x82,0x05,0x81,0x02,0x82,0x04,0x82,
+0x02,0x81,0x06,0x81,0x02,0x82,0x03,0x82,0x04,0x82,0x0a,0x82,0x05,0x82,0x05,
+0x81,0x03,0x81,0x03,0x82,0x03,0x81,0x01,0x81,0x01,0x81,0x01,0x81,0x05,0x83,
+0x07,0x84,0x07,0x82,0x08,0x81,0x09,0x81,0x0a,0x81,0x06,0x81,0x02,0x81,0x19,
+0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x83,
+0x03,0x82,0x02,0x82,0x04,0x85,0x04,0x83,0x01,0x83,0x03,0x83,0x02,0x82,0x06,
+0x82,0x08,0x82,0x06,0x82,0x02,0x81,0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,
+0x02,0x83,0x03,0x82,0x03,0x82,0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,
+0x83,0x03,0x83,0x03,0x81,0x03,0x82,0x02,0x82,0x04,0x82,0x07,0x82,0x04,0x82,
+0x03,0x81,0x03,0x82,0x02,0x82,0x02,0x82,0x02,0x81,0x03,0x82,0x02,0x81,0x05,
+0x81,0x04,0x82,0x07,0x81,0x08,0x81,0x08,0x82,0x08,0x81,0x27,0x82,0x11,0x81,
+0x02,0x81,0x06,0x83,0x08,0x81,0x07,0x82,0x13,0x82,0x0a,0x82,0x05,0x85,0x04,
+0x88,0x23,0x82,0x05,0x82,0x04,0x82,0x06,0x82,0x08,0x82,0x06,0x83,0x06,0x81,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x07,0x81,0x08,0x83,0x06,
+0x82,0x02,0x82,0x19,0x83,0x11,0x83,0x09,0x82,0x03,0x81,0x01,0x81,0x02,0x81,
+0x02,0x81,0x03,0x82,0x02,0x81,0x04,0x86,0x04,0x82,0x08,0x82,0x05,0x81,0x02,
+0x86,0x05,0x86,0x03,0x81,0x09,0x88,0x05,0x82,0x0a,0x82,0x03,0x84,0x06,0x82,
+0x07,0x82,0x01,0x81,0x01,0x81,0x01,0x82,0x02,0x82,0x01,0x82,0x02,0x81,0x02,
+0x82,0x05,0x81,0x02,0x82,0x03,0x82,0x03,0x81,0x06,0x81,0x02,0x86,0x06,0x83,
+0x08,0x82,0x05,0x82,0x05,0x81,0x03,0x82,0x02,0x81,0x04,0x81,0x01,0x81,0x01,
+0x81,0x01,0x81,0x06,0x82,0x08,0x82,0x08,0x81,0x09,0x81,0x09,0x82,0x09,0x81,
+0x06,0x81,0x02,0x82,0x1d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x81,0x03,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,
+0x04,0x82,0x05,0x82,0x08,0x82,0x06,0x82,0x01,0x81,0x08,0x82,0x05,0x82,0x01,
+0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x81,0x09,0x82,0x07,0x82,0x04,0x82,0x03,
+0x82,0x02,0x81,0x04,0x81,0x02,0x82,0x02,0x81,0x04,0x81,0x01,0x82,0x05,0x82,
+0x02,0x82,0x07,0x82,0x08,0x81,0x08,0x82,0x08,0x81,0x07,0x83,0x03,0x81,0x19,
+0x82,0x11,0x81,0x02,0x81,0x08,0x82,0x07,0x81,0x01,0x82,0x03,0x82,0x01,0x81,
+0x02,0x82,0x0e,0x81,0x0c,0x81,0x06,0x82,0x06,0x88,0x0d,0x86,0x10,0x81,0x06,
+0x82,0x04,0x82,0x06,0x82,0x07,0x82,0x09,0x82,0x04,0x82,0x02,0x82,0x09,0x82,
+0x03,0x81,0x04,0x82,0x05,0x82,0x06,0x82,0x03,0x81,0x06,0x85,0x18,0x82,0x15,
+0x83,0x06,0x82,0x04,0x81,0x01,0x81,0x02,0x81,0x02,0x81,0x03,0x81,0x03,0x82,
+0x03,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x05,0x81,0x02,0x82,0x09,0x82,0x07,
+0x81,0x03,0x84,0x02,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x01,0x82,
+0x05,0x82,0x07,0x82,0x01,0x81,0x01,0x81,0x01,0x82,0x02,0x82,0x02,0x82,0x01,
+0x81,0x02,0x82,0x05,0x81,0x02,0x86,0x04,0x81,0x06,0x81,0x02,0x82,0x03,0x82,
+0x07,0x82,0x07,0x82,0x05,0x82,0x05,0x81,0x03,0x82,0x02,0x81,0x04,0x83,0x01,
+0x83,0x05,0x83,0x08,0x82,0x07,0x82,0x09,0x81,0x0a,0x81,0x09,0x81,0x25,0x85,
+0x03,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x88,0x04,0x82,0x06,
+0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x08,0x82,0x06,0x84,0x08,0x82,
+0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x83,0x07,0x82,0x07,0x82,
+0x04,0x82,0x04,0x81,0x02,0x81,0x04,0x81,0x02,0x82,0x02,0x81,0x05,0x82,0x06,
+0x82,0x02,0x82,0x06,0x82,0x07,0x82,0x09,0x82,0x09,0x82,0x04,0x82,0x01,0x82,
+0x01,0x82,0x2a,0x87,0x08,0x82,0x05,0x81,0x01,0x81,0x02,0x81,0x02,0x81,0x02,
+0x82,0x01,0x82,0x0e,0x81,0x0c,0x81,0x05,0x82,0x01,0x82,0x07,0x82,0x26,0x81,
+0x06,0x82,0x04,0x82,0x06,0x82,0x06,0x82,0x0b,0x82,0x03,0x87,0x08,0x82,0x03,
+0x81,0x04,0x82,0x05,0x82,0x06,0x81,0x04,0x82,0x09,0x81,0x18,0x82,0x08,0x86,
+0x08,0x82,0x06,0x82,0x04,0x81,0x01,0x81,0x02,0x81,0x02,0x81,0x03,0x86,0x03,
+0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x05,0x81,0x02,0x82,0x09,0x82,0x07,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,
+0x82,0x07,0x82,0x01,0x83,0x01,0x82,0x02,0x82,0x03,0x81,0x01,0x81,0x02,0x82,
+0x05,0x81,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x81,0x08,0x82,0x06,
+0x82,0x05,0x82,0x05,0x81,0x04,0x81,0x01,0x82,0x05,0x82,0x02,0x82,0x05,0x81,
+0x01,0x82,0x07,0x82,0x06,0x82,0x0a,0x81,0x0a,0x81,0x09,0x81,0x24,0x82,0x02,
+0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,0x82,
+0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x08,0x82,0x06,0x84,0x08,
+0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0b,0x83,0x04,0x82,0x07,
+0x82,0x04,0x82,0x04,0x81,0x01,0x82,0x04,0x81,0x01,0x81,0x01,0x81,0x01,0x82,
+0x05,0x82,0x07,0x81,0x02,0x82,0x06,0x81,0x0a,0x81,0x08,0x82,0x08,0x81,0x06,
+0x81,0x03,0x83,0x2c,0x81,0x02,0x81,0x05,0x81,0x04,0x82,0x04,0x81,0x02,0x81,
+0x02,0x81,0x01,0x82,0x03,0x83,0x0f,0x82,0x0a,0x82,0x11,0x82,0x25,0x82,0x07,
+0x81,0x04,0x81,0x07,0x82,0x05,0x82,0x07,0x81,0x04,0x82,0x03,0x87,0x03,0x81,
+0x04,0x82,0x03,0x81,0x04,0x82,0x05,0x81,0x07,0x81,0x04,0x82,0x08,0x82,0x19,
+0x84,0x10,0x83,0x0e,0x81,0x01,0x81,0x02,0x81,0x01,0x82,0x02,0x82,0x04,0x81,
+0x03,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x09,
+0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x81,0x04,0x82,
+0x03,0x82,0x03,0x81,0x04,0x82,0x07,0x82,0x02,0x81,0x02,0x82,0x02,0x82,0x03,
+0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x02,0x81,0x05,0x81,0x06,0x82,0x05,0x83,0x03,0x82,0x04,0x83,0x06,0x82,0x02,
+0x82,0x04,0x82,0x02,0x81,0x07,0x82,0x06,0x81,0x0b,0x81,0x0a,0x82,0x08,0x81,
+0x23,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x81,0x03,0x82,0x04,
+0x82,0x02,0x82,0x0a,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,
+0x08,0x82,0x06,0x82,0x01,0x82,0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x03,0x82,0x0d,0x81,0x04,0x82,0x07,0x82,0x03,0x83,0x04,0x83,0x06,0x82,0x02,
+0x82,0x05,0x81,0x01,0x82,0x06,0x84,0x06,0x81,0x0b,0x81,0x08,0x82,0x08,0x81,
+0x27,0x82,0x10,0x81,0x02,0x81,0x05,0x82,0x02,0x83,0x04,0x81,0x02,0x81,0x02,
+0x81,0x02,0x81,0x04,0x82,0x0f,0x82,0x0a,0x82,0x11,0x82,0x07,0x82,0x12,0x82,
+0x08,0x81,0x08,0x82,0x02,0x82,0x07,0x82,0x05,0x81,0x08,0x82,0x03,0x81,0x08,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x06,0x81,0x07,0x82,0x02,0x82,
+0x08,0x82,0x06,0x82,0x08,0x82,0x0b,0x82,0x0e,0x82,0x0a,0x82,0x04,0x81,0x01,
+0x83,0x01,0x82,0x03,0x81,0x05,0x82,0x02,0x82,0x04,0x82,0x03,0x83,0x01,0x82,
+0x03,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x08,0x82,0x03,0x82,0x02,0x82,0x04,
+0x82,0x05,0x82,0x05,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x03,0x82,0x07,0x82,
+0x05,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x09,0x82,0x02,
+0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x06,0x82,0x06,0x82,0x03,0x82,
+0x05,0x82,0x06,0x82,0x02,0x81,0x04,0x82,0x03,0x82,0x06,0x82,0x05,0x82,0x0b,
+0x81,0x0b,0x81,0x08,0x81,0x23,0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x03,0x82,0x04,0x82,0x06,0x83,0x01,
+0x83,0x03,0x82,0x04,0x82,0x05,0x82,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,
+0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,
+0x83,0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x08,0x82,0x02,0x82,0x04,0x82,
+0x03,0x81,0x03,0x83,0x01,0x84,0x05,0x82,0x06,0x82,0x02,0x82,0x04,0x82,0x02,
+0x81,0x07,0x83,0x05,0x82,0x0b,0x81,0x08,0x82,0x08,0x81,0x27,0x82,0x10,0x81,
+0x02,0x81,0x06,0x85,0x09,0x82,0x04,0x84,0x01,0x82,0x0f,0x81,0x0a,0x81,0x1b,
+0x82,0x12,0x82,0x08,0x81,0x09,0x84,0x08,0x82,0x04,0x87,0x04,0x84,0x09,0x82,
+0x05,0x84,0x06,0x84,0x07,0x81,0x08,0x84,0x06,0x84,0x07,0x82,0x08,0x82,0x27,
+0x82,0x04,0x82,0x09,0x81,0x06,0x81,0x02,0x87,0x05,0x84,0x04,0x86,0x04,0x87,
+0x04,0x82,0x09,0x85,0x03,0x82,0x04,0x82,0x03,0x86,0x04,0x84,0x05,0x82,0x04,
+0x82,0x02,0x87,0x02,0x82,0x05,0x82,0x02,0x82,0x05,0x81,0x04,0x84,0x04,0x82,
+0x0a,0x86,0x02,0x82,0x04,0x82,0x03,0x85,0x07,0x82,0x07,0x85,0x06,0x82,0x06,
+0x82,0x02,0x81,0x04,0x82,0x04,0x82,0x05,0x82,0x05,0x87,0x06,0x81,0x0b,0x82,
+0x07,0x81,0x23,0x84,0x01,0x82,0x03,0x82,0x01,0x83,0x06,0x84,0x06,0x83,0x01,
+0x82,0x04,0x85,0x05,0x82,0x07,0x83,0x01,0x82,0x03,0x82,0x04,0x82,0x03,0x86,
+0x06,0x82,0x06,0x82,0x03,0x81,0x04,0x86,0x03,0x82,0x01,0x82,0x01,0x82,0x02,
+0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x03,0x82,
+0x09,0x84,0x06,0x84,0x05,0x84,0x01,0x82,0x05,0x82,0x06,0x81,0x03,0x82,0x03,
+0x82,0x03,0x82,0x07,0x82,0x05,0x86,0x07,0x81,0x08,0x82,0x08,0x81,0x45,0x81,
+0x27,0x81,0x0a,0x81,0x1c,0x81,0x1b,0x82,0x78,0x81,0x2e,0x82,0x7f,0x30,0x82,
+0x5e,0x81,0x0c,0x81,0x07,0x81,0x0f,0x88,0x4d,0x82,0x1a,0x82,0x37,0x82,0x0e,
+0x82,0x4b,0x82,0x13,0x82,0x07,0x82,0x07,0x82,0x45,0x81,0x28,0x81,0x08,0x81,
+0x1c,0x81,0x1c,0x81,0x78,0x81,0x30,0x84,0x7f,0x7f,0x0e,0x83,0x0a,0x81,0x05,
+0x83,0x64,0x82,0x1a,0x82,0x37,0x82,0x0e,0x82,0x4b,0x82,0x14,0x81,0x07,0x82,
+0x07,0x81,0x70,0x81,0x06,0x81,0x7f,0x7f,0x7f,0x7f,0x6e,0x85,0x1a,0x82,0x38,
+0x82,0x0e,0x82,0x4a,0x82,0x16,0x81,0x0e,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x7f,0x62,
+0x00,
+  }
+};
diff --git a/minui/roboto_15x24.h b/minui/roboto_15x24.h
new file mode 100644
index 0000000..aea1cbf
--- /dev/null
+++ b/minui/roboto_15x24.h
@@ -0,0 +1,281 @@
+struct {
+  unsigned width;
+  unsigned height;
+  unsigned char_width;
+  unsigned char_height;
+  unsigned char rundata[3979];
+} font = {
+  .width = 1440,
+  .height = 24,
+  .char_width = 15,
+  .char_height = 24,
+  .rundata = {
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x6e,0x81,0x3e,0x81,
+0x07,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x76,0x84,0x17,0x84,0x7f,0x7f,0x7f,0x7f,
+0x4c,0x81,0x3d,0x82,0x07,0x82,0x5f,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x15,0x84,
+0x07,0x82,0x0e,0x84,0x45,0x82,0x24,0x82,0x1a,0x83,0x16,0x82,0x10,0x82,0x0e,
+0x82,0x0a,0x82,0x0d,0x84,0x7f,0x62,0x82,0x0b,0x82,0x0b,0x82,0x3b,0x82,0x0c,
+0x82,0x02,0x82,0x0a,0x82,0x03,0x81,0x0a,0x84,0x08,0x83,0x0e,0x84,0x0b,0x82,
+0x10,0x82,0x09,0x82,0x5e,0x81,0x09,0x84,0x0b,0x84,0x0b,0x84,0x0b,0x84,0x0e,
+0x83,0x08,0x88,0x09,0x84,0x07,0x8a,0x08,0x84,0x0b,0x84,0x56,0x84,0x1c,0x82,
+0x08,0x87,0x0b,0x85,0x07,0x87,0x09,0x89,0x06,0x8a,0x07,0x85,0x06,0x82,0x08,
+0x82,0x06,0x86,0x0f,0x82,0x06,0x82,0x07,0x82,0x04,0x82,0x0a,0x83,0x09,0x82,
+0x03,0x82,0x08,0x82,0x06,0x85,0x07,0x87,0x0b,0x85,0x07,0x87,0x0c,0x84,0x06,
+0x8d,0x03,0x82,0x07,0x82,0x02,0x83,0x08,0x83,0x01,0x82,0x0a,0x82,0x02,0x82,
+0x08,0x82,0x03,0x82,0x08,0x82,0x04,0x8b,0x09,0x82,0x09,0x82,0x10,0x82,0x0d,
+0x81,0x1d,0x82,0x18,0x82,0x24,0x82,0x19,0x84,0x16,0x82,0x10,0x82,0x0e,0x82,
+0x0a,0x82,0x0d,0x84,0x7f,0x61,0x82,0x0c,0x82,0x0c,0x82,0x3a,0x82,0x0c,0x82,
+0x02,0x82,0x0a,0x82,0x03,0x81,0x08,0x87,0x06,0x82,0x01,0x82,0x0c,0x86,0x0a,
+0x82,0x0f,0x82,0x0b,0x82,0x5c,0x82,0x08,0x87,0x07,0x86,0x0a,0x87,0x08,0x87,
+0x0c,0x83,0x08,0x88,0x07,0x87,0x06,0x8a,0x07,0x87,0x07,0x87,0x54,0x87,0x08,
+0x86,0x0c,0x82,0x08,0x89,0x07,0x88,0x06,0x89,0x07,0x89,0x06,0x8a,0x05,0x88,
+0x05,0x82,0x08,0x82,0x06,0x86,0x0f,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x0a,
+0x83,0x08,0x83,0x03,0x83,0x07,0x82,0x04,0x88,0x06,0x89,0x07,0x88,0x06,0x89,
+0x08,0x88,0x04,0x8d,0x03,0x82,0x07,0x82,0x03,0x82,0x08,0x82,0x02,0x82,0x04,
+0x81,0x05,0x82,0x03,0x82,0x06,0x82,0x04,0x82,0x07,0x83,0x04,0x8b,0x09,0x82,
+0x0a,0x82,0x0f,0x82,0x0c,0x83,0x1d,0x82,0x17,0x82,0x24,0x82,0x18,0x83,0x18,
+0x82,0x2c,0x82,0x0f,0x82,0x7f,0x60,0x83,0x0c,0x82,0x0d,0x82,0x39,0x82,0x0c,
+0x82,0x02,0x82,0x0a,0x82,0x02,0x82,0x07,0x83,0x03,0x83,0x04,0x82,0x03,0x81,
+0x04,0x81,0x06,0x83,0x03,0x82,0x09,0x82,0x0f,0x82,0x0b,0x82,0x5c,0x82,0x07,
+0x82,0x04,0x82,0x0a,0x83,0x09,0x82,0x04,0x83,0x06,0x82,0x04,0x83,0x0a,0x84,
+0x08,0x82,0x0d,0x82,0x13,0x82,0x06,0x82,0x04,0x83,0x06,0x82,0x04,0x82,0x52,
+0x82,0x04,0x82,0x07,0x82,0x05,0x82,0x09,0x84,0x07,0x82,0x05,0x83,0x06,0x82,
+0x05,0x82,0x05,0x82,0x05,0x83,0x06,0x82,0x0d,0x82,0x0d,0x82,0x05,0x82,0x04,
+0x82,0x08,0x82,0x08,0x82,0x11,0x82,0x06,0x82,0x05,0x82,0x06,0x82,0x0a,0x83,
+0x08,0x83,0x03,0x83,0x07,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x06,0x82,0x06,
+0x82,0x05,0x82,0x05,0x82,0x05,0x83,0x06,0x83,0x04,0x83,0x08,0x83,0x08,0x82,
+0x07,0x82,0x03,0x82,0x08,0x82,0x02,0x82,0x03,0x83,0x04,0x82,0x03,0x83,0x04,
+0x83,0x05,0x82,0x06,0x82,0x0d,0x82,0x0a,0x82,0x0a,0x82,0x0f,0x82,0x0c,0x83,
+0x1e,0x81,0x17,0x82,0x24,0x82,0x18,0x82,0x19,0x82,0x2c,0x82,0x0f,0x82,0x74,
+0x82,0x69,0x82,0x0d,0x82,0x0d,0x82,0x39,0x82,0x0c,0x82,0x02,0x82,0x0a,0x82,
+0x02,0x82,0x07,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x06,0x82,0x04,
+0x82,0x09,0x82,0x0e,0x82,0x0d,0x82,0x1c,0x82,0x3c,0x82,0x08,0x82,0x05,0x82,
+0x09,0x83,0x09,0x82,0x05,0x82,0x05,0x83,0x05,0x82,0x09,0x85,0x07,0x82,0x0d,
+0x82,0x13,0x82,0x07,0x82,0x05,0x82,0x05,0x82,0x05,0x83,0x51,0x82,0x04,0x82,
+0x06,0x82,0x07,0x82,0x08,0x84,0x07,0x82,0x06,0x82,0x05,0x82,0x07,0x82,0x04,
+0x82,0x06,0x83,0x05,0x82,0x0d,0x82,0x0c,0x82,0x07,0x82,0x03,0x82,0x08,0x82,
+0x08,0x82,0x11,0x82,0x06,0x82,0x04,0x83,0x06,0x82,0x0a,0x84,0x06,0x84,0x03,
+0x84,0x06,0x82,0x03,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,
+0x04,0x82,0x06,0x82,0x06,0x82,0x06,0x82,0x08,0x83,0x08,0x82,0x07,0x82,0x03,
+0x83,0x06,0x83,0x02,0x82,0x03,0x83,0x03,0x82,0x05,0x82,0x04,0x82,0x06,0x82,
+0x05,0x83,0x0c,0x83,0x0a,0x82,0x0b,0x82,0x0e,0x82,0x0c,0x81,0x01,0x82,0x35,
+0x82,0x24,0x82,0x18,0x82,0x19,0x82,0x2c,0x82,0x0f,0x82,0x74,0x82,0x69,0x82,
+0x0d,0x82,0x0d,0x83,0x38,0x82,0x0c,0x82,0x02,0x81,0x0b,0x81,0x03,0x82,0x07,
+0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x81,0x07,0x82,0x04,0x82,0x09,0x82,
+0x0e,0x82,0x0d,0x82,0x0d,0x82,0x0d,0x82,0x3c,0x82,0x07,0x82,0x06,0x82,0x09,
+0x83,0x08,0x83,0x05,0x82,0x0d,0x82,0x09,0x85,0x07,0x82,0x0d,0x82,0x12,0x83,
+0x07,0x82,0x05,0x82,0x05,0x82,0x06,0x82,0x57,0x83,0x04,0x82,0x09,0x81,0x08,
+0x84,0x07,0x82,0x06,0x82,0x05,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x05,0x82,
+0x0d,0x82,0x0c,0x82,0x07,0x82,0x03,0x82,0x08,0x82,0x08,0x82,0x11,0x82,0x06,
+0x82,0x03,0x83,0x07,0x82,0x0a,0x84,0x06,0x84,0x03,0x85,0x05,0x82,0x03,0x82,
+0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x06,0x83,0x05,
+0x82,0x06,0x82,0x08,0x83,0x08,0x82,0x07,0x82,0x04,0x82,0x06,0x82,0x03,0x82,
+0x03,0x83,0x03,0x82,0x05,0x83,0x02,0x83,0x07,0x82,0x04,0x82,0x0d,0x82,0x0b,
+0x82,0x0b,0x82,0x0e,0x82,0x0b,0x82,0x01,0x82,0x29,0x84,0x08,0x82,0x01,0x84,
+0x0a,0x86,0x0a,0x83,0x02,0x82,0x08,0x84,0x0a,0x86,0x0a,0x83,0x02,0x82,0x06,
+0x82,0x01,0x84,0x09,0x84,0x0e,0x82,0x0a,0x82,0x04,0x83,0x08,0x82,0x07,0x81,
+0x02,0x83,0x03,0x83,0x05,0x81,0x02,0x84,0x0b,0x84,0x09,0x81,0x02,0x84,0x0a,
+0x83,0x02,0x82,0x08,0x81,0x02,0x84,0x08,0x84,0x08,0x88,0x07,0x82,0x06,0x82,
+0x05,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x04,
+0x83,0x05,0x83,0x06,0x89,0x09,0x82,0x0d,0x82,0x0d,0x83,0x38,0x82,0x0c,0x82,
+0x02,0x81,0x08,0x8b,0x05,0x82,0x0b,0x82,0x03,0x81,0x02,0x82,0x07,0x83,0x02,
+0x82,0x0a,0x81,0x0f,0x82,0x0d,0x82,0x0d,0x82,0x0d,0x82,0x3c,0x82,0x07,0x82,
+0x06,0x82,0x09,0x83,0x10,0x82,0x0d,0x82,0x08,0x82,0x01,0x83,0x07,0x82,0x0d,
+0x82,0x12,0x82,0x08,0x82,0x05,0x82,0x05,0x82,0x06,0x82,0x08,0x82,0x0d,0x82,
+0x13,0x81,0x06,0x89,0x06,0x81,0x14,0x82,0x05,0x81,0x04,0x84,0x02,0x82,0x06,
+0x82,0x02,0x82,0x06,0x82,0x06,0x82,0x04,0x82,0x0e,0x82,0x07,0x82,0x05,0x82,
+0x0d,0x82,0x0b,0x82,0x0d,0x82,0x08,0x82,0x08,0x82,0x11,0x82,0x06,0x82,0x03,
+0x82,0x08,0x82,0x0a,0x82,0x01,0x82,0x05,0x84,0x03,0x82,0x01,0x82,0x05,0x82,
+0x02,0x82,0x09,0x82,0x03,0x82,0x07,0x82,0x03,0x82,0x09,0x82,0x03,0x82,0x06,
+0x83,0x05,0x82,0x10,0x83,0x08,0x82,0x07,0x82,0x04,0x82,0x06,0x82,0x03,0x82,
+0x03,0x81,0x01,0x82,0x02,0x82,0x06,0x82,0x02,0x82,0x08,0x82,0x03,0x83,0x0c,
+0x82,0x0c,0x82,0x0b,0x82,0x0e,0x82,0x0b,0x82,0x02,0x81,0x28,0x87,0x06,0x88,
+0x08,0x88,0x08,0x88,0x07,0x86,0x09,0x86,0x09,0x88,0x06,0x88,0x08,0x84,0x0e,
+0x82,0x0a,0x82,0x03,0x83,0x09,0x82,0x07,0x87,0x01,0x85,0x04,0x88,0x09,0x87,
+0x07,0x81,0x01,0x86,0x08,0x88,0x08,0x87,0x06,0x87,0x07,0x88,0x07,0x82,0x06,
+0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x04,0x83,0x03,0x83,
+0x05,0x82,0x05,0x82,0x07,0x89,0x09,0x82,0x0d,0x82,0x0d,0x83,0x38,0x82,0x1b,
+0x82,0x03,0x81,0x08,0x83,0x0b,0x85,0x01,0x82,0x09,0x85,0x1a,0x83,0x0d,0x83,
+0x09,0x81,0x02,0x82,0x01,0x81,0x0b,0x82,0x3b,0x82,0x08,0x82,0x06,0x82,0x09,
+0x83,0x0f,0x82,0x0d,0x82,0x09,0x82,0x01,0x83,0x07,0x87,0x07,0x83,0x01,0x84,
+0x0c,0x82,0x09,0x83,0x03,0x82,0x06,0x82,0x06,0x82,0x08,0x82,0x0d,0x82,0x11,
+0x83,0x06,0x89,0x06,0x83,0x12,0x82,0x05,0x81,0x03,0x82,0x02,0x82,0x02,0x81,
+0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x05,0x82,0x0e,0x82,0x07,0x83,0x04,
+0x82,0x0d,0x82,0x0b,0x82,0x0d,0x82,0x08,0x82,0x08,0x82,0x11,0x82,0x06,0x82,
+0x02,0x82,0x09,0x82,0x0a,0x82,0x01,0x82,0x04,0x82,0x01,0x82,0x03,0x82,0x02,
+0x82,0x04,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x07,0x82,0x03,0x82,0x09,0x82,
+0x03,0x82,0x06,0x82,0x06,0x83,0x0f,0x83,0x08,0x82,0x07,0x82,0x05,0x82,0x04,
+0x82,0x05,0x81,0x02,0x82,0x01,0x82,0x02,0x82,0x07,0x84,0x0a,0x82,0x02,0x82,
+0x0c,0x83,0x0c,0x82,0x0c,0x82,0x0d,0x82,0x0a,0x82,0x03,0x82,0x26,0x82,0x04,
+0x82,0x06,0x83,0x03,0x83,0x07,0x82,0x05,0x82,0x06,0x82,0x04,0x83,0x06,0x82,
+0x04,0x82,0x0a,0x82,0x0a,0x83,0x03,0x83,0x06,0x82,0x04,0x83,0x09,0x82,0x0e,
+0x82,0x0a,0x82,0x03,0x82,0x0a,0x82,0x07,0x82,0x03,0x84,0x02,0x82,0x04,0x82,
+0x04,0x83,0x07,0x82,0x04,0x83,0x06,0x82,0x04,0x83,0x06,0x82,0x04,0x83,0x08,
+0x83,0x0a,0x82,0x04,0x82,0x08,0x82,0x0b,0x82,0x06,0x82,0x05,0x82,0x05,0x82,
+0x04,0x82,0x03,0x83,0x03,0x82,0x06,0x82,0x03,0x82,0x06,0x82,0x05,0x82,0x0d,
+0x82,0x0a,0x82,0x0d,0x82,0x0d,0x83,0x38,0x82,0x1b,0x82,0x02,0x82,0x09,0x85,
+0x0e,0x81,0x0b,0x83,0x1b,0x82,0x0f,0x82,0x09,0x87,0x0b,0x82,0x3b,0x82,0x08,
+0x82,0x06,0x82,0x09,0x83,0x0e,0x83,0x09,0x85,0x09,0x82,0x02,0x83,0x07,0x88,
+0x06,0x89,0x0b,0x82,0x0a,0x86,0x07,0x82,0x06,0x82,0x27,0x85,0x17,0x84,0x0f,
+0x82,0x05,0x82,0x03,0x81,0x03,0x82,0x02,0x81,0x06,0x82,0x02,0x82,0x06,0x88,
+0x06,0x82,0x0e,0x82,0x07,0x83,0x04,0x88,0x07,0x88,0x05,0x82,0x0d,0x8c,0x08,
+0x82,0x11,0x82,0x06,0x85,0x0a,0x82,0x0a,0x82,0x01,0x82,0x04,0x82,0x01,0x82,
+0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x06,0x82,0x04,
+0x82,0x09,0x82,0x03,0x82,0x04,0x83,0x08,0x85,0x0c,0x83,0x08,0x82,0x07,0x82,
+0x05,0x82,0x04,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x81,0x08,0x84,0x0a,
+0x86,0x0c,0x82,0x0d,0x82,0x0c,0x82,0x0d,0x82,0x0a,0x82,0x03,0x82,0x26,0x82,
+0x04,0x83,0x05,0x82,0x05,0x82,0x06,0x83,0x05,0x82,0x05,0x83,0x05,0x82,0x05,
+0x83,0x04,0x82,0x0a,0x82,0x0a,0x82,0x05,0x82,0x06,0x82,0x05,0x82,0x09,0x82,
+0x0e,0x82,0x0a,0x82,0x02,0x82,0x0b,0x82,0x07,0x82,0x03,0x83,0x03,0x83,0x03,
+0x82,0x05,0x82,0x06,0x83,0x05,0x82,0x06,0x82,0x05,0x82,0x05,0x83,0x05,0x82,
+0x08,0x82,0x0b,0x82,0x04,0x82,0x08,0x82,0x0b,0x82,0x06,0x82,0x06,0x82,0x04,
+0x82,0x04,0x82,0x02,0x84,0x03,0x82,0x07,0x82,0x01,0x82,0x07,0x83,0x03,0x83,
+0x0c,0x83,0x0a,0x82,0x0d,0x82,0x0e,0x82,0x0a,0x82,0x2c,0x82,0x1b,0x82,0x02,
+0x82,0x0b,0x85,0x0b,0x82,0x09,0x85,0x1b,0x82,0x0f,0x82,0x0b,0x83,0x09,0x8b,
+0x36,0x81,0x09,0x82,0x06,0x82,0x09,0x83,0x0e,0x82,0x0a,0x86,0x07,0x82,0x03,
+0x83,0x07,0x82,0x04,0x83,0x05,0x84,0x03,0x83,0x09,0x82,0x0b,0x87,0x07,0x82,
+0x04,0x83,0x25,0x85,0x1b,0x85,0x0b,0x82,0x06,0x82,0x02,0x82,0x03,0x82,0x02,
+0x81,0x05,0x82,0x04,0x82,0x05,0x8a,0x04,0x82,0x0e,0x82,0x07,0x83,0x04,0x88,
+0x07,0x88,0x05,0x82,0x04,0x86,0x03,0x8c,0x08,0x82,0x11,0x82,0x06,0x86,0x09,
+0x82,0x0a,0x82,0x02,0x82,0x02,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
+0x02,0x82,0x09,0x82,0x03,0x8a,0x04,0x82,0x09,0x82,0x03,0x89,0x0a,0x85,0x0a,
+0x83,0x08,0x82,0x07,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x01,0x82,0x02,0x81,
+0x01,0x82,0x09,0x82,0x0c,0x84,0x0c,0x82,0x0e,0x82,0x0d,0x82,0x0c,0x82,0x3d,
+0x83,0x05,0x82,0x05,0x82,0x06,0x82,0x0d,0x82,0x06,0x82,0x05,0x82,0x06,0x82,
+0x09,0x82,0x09,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0e,0x82,0x0a,
+0x82,0x01,0x83,0x0b,0x82,0x07,0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x05,0x82,
+0x06,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x05,0x82,0x06,0x82,0x08,0x82,0x0b,
+0x82,0x0e,0x82,0x0b,0x82,0x06,0x82,0x06,0x82,0x03,0x82,0x06,0x82,0x01,0x82,
+0x01,0x81,0x03,0x82,0x07,0x82,0x01,0x82,0x08,0x82,0x03,0x82,0x0c,0x83,0x09,
+0x83,0x0e,0x82,0x0e,0x83,0x07,0x86,0x04,0x82,0x24,0x82,0x1b,0x81,0x03,0x82,
+0x0d,0x84,0x0a,0x81,0x02,0x84,0x03,0x83,0x01,0x83,0x03,0x82,0x15,0x82,0x0f,
+0x82,0x0b,0x84,0x08,0x8b,0x15,0x86,0x1a,0x82,0x09,0x82,0x06,0x82,0x09,0x83,
+0x0d,0x82,0x0f,0x83,0x06,0x82,0x03,0x83,0x0e,0x82,0x05,0x83,0x05,0x82,0x09,
+0x82,0x0a,0x82,0x04,0x83,0x06,0x89,0x24,0x83,0x21,0x84,0x08,0x82,0x07,0x81,
+0x03,0x82,0x03,0x81,0x03,0x81,0x05,0x82,0x04,0x82,0x05,0x82,0x06,0x83,0x03,
+0x82,0x0e,0x82,0x07,0x83,0x04,0x82,0x0d,0x82,0x0b,0x82,0x04,0x86,0x03,0x82,
+0x08,0x82,0x08,0x82,0x11,0x82,0x06,0x82,0x02,0x82,0x09,0x82,0x0a,0x82,0x02,
+0x82,0x02,0x82,0x02,0x82,0x03,0x82,0x03,0x83,0x02,0x82,0x02,0x82,0x09,0x82,
+0x03,0x88,0x06,0x82,0x09,0x82,0x03,0x82,0x05,0x83,0x0c,0x84,0x08,0x83,0x08,
+0x82,0x07,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x81,0x03,0x84,0x08,0x84,
+0x0b,0x83,0x0c,0x83,0x0e,0x82,0x0d,0x82,0x0c,0x82,0x39,0x87,0x05,0x82,0x05,
+0x82,0x06,0x82,0x0d,0x82,0x06,0x82,0x05,0x8a,0x09,0x82,0x09,0x82,0x06,0x82,
+0x06,0x82,0x05,0x82,0x09,0x82,0x0e,0x82,0x0a,0x85,0x0c,0x82,0x07,0x82,0x04,
+0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x06,0x82,0x06,0x82,0x06,0x82,0x05,0x82,
+0x05,0x82,0x06,0x82,0x08,0x82,0x0b,0x85,0x0b,0x82,0x0b,0x82,0x06,0x82,0x06,
+0x82,0x03,0x82,0x06,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x08,0x83,0x09,0x82,
+0x03,0x82,0x0c,0x82,0x0a,0x82,0x0f,0x82,0x0f,0x83,0x06,0x82,0x02,0x83,0x02,
+0x82,0x25,0x82,0x18,0x8b,0x0d,0x82,0x09,0x82,0x01,0x82,0x02,0x82,0x02,0x82,
+0x03,0x83,0x02,0x82,0x15,0x82,0x0f,0x82,0x0a,0x82,0x01,0x82,0x0c,0x82,0x3a,
+0x82,0x09,0x82,0x06,0x82,0x09,0x83,0x0c,0x82,0x11,0x82,0x05,0x82,0x04,0x83,
+0x0e,0x82,0x05,0x83,0x05,0x82,0x09,0x82,0x09,0x82,0x06,0x82,0x08,0x83,0x02,
+0x82,0x24,0x83,0x0c,0x89,0x0d,0x83,0x08,0x82,0x07,0x81,0x03,0x82,0x03,0x81,
+0x03,0x81,0x04,0x89,0x05,0x82,0x07,0x82,0x03,0x82,0x0e,0x82,0x07,0x82,0x05,
+0x82,0x0d,0x82,0x0b,0x82,0x08,0x82,0x03,0x82,0x08,0x82,0x08,0x82,0x11,0x82,
+0x06,0x82,0x03,0x82,0x08,0x82,0x0a,0x82,0x03,0x81,0x02,0x81,0x03,0x82,0x03,
+0x82,0x04,0x82,0x02,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x0c,0x82,0x09,0x82,
+0x03,0x82,0x06,0x82,0x0e,0x82,0x08,0x83,0x08,0x82,0x07,0x82,0x06,0x82,0x02,
+0x82,0x06,0x84,0x03,0x84,0x07,0x86,0x0b,0x82,0x0c,0x82,0x0f,0x82,0x0d,0x82,
+0x0c,0x82,0x37,0x89,0x05,0x82,0x05,0x82,0x06,0x82,0x0d,0x82,0x06,0x82,0x05,
+0x8a,0x09,0x82,0x09,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0e,0x82,
+0x0a,0x85,0x0c,0x82,0x07,0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x06,
+0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x05,0x82,0x06,0x82,0x08,0x82,0x0d,0x85,
+0x09,0x82,0x0b,0x82,0x06,0x82,0x07,0x82,0x02,0x81,0x07,0x82,0x01,0x81,0x02,
+0x82,0x01,0x82,0x09,0x83,0x0a,0x82,0x01,0x83,0x0b,0x82,0x0c,0x82,0x0e,0x82,
+0x0e,0x82,0x07,0x82,0x04,0x86,0x3f,0x8b,0x05,0x82,0x06,0x82,0x08,0x82,0x02,
+0x81,0x04,0x81,0x02,0x82,0x04,0x85,0x16,0x82,0x0f,0x82,0x0e,0x81,0x0c,0x82,
+0x39,0x82,0x0a,0x82,0x06,0x82,0x09,0x83,0x0b,0x83,0x11,0x82,0x05,0x8b,0x0c,
+0x82,0x06,0x82,0x05,0x82,0x08,0x83,0x09,0x82,0x06,0x82,0x0d,0x82,0x25,0x85,
+0x09,0x89,0x0a,0x85,0x09,0x82,0x07,0x81,0x03,0x82,0x03,0x81,0x02,0x82,0x04,
+0x8a,0x04,0x82,0x07,0x82,0x03,0x83,0x07,0x82,0x04,0x82,0x07,0x82,0x05,0x82,
+0x0d,0x82,0x0b,0x83,0x07,0x82,0x03,0x82,0x08,0x82,0x08,0x82,0x11,0x82,0x06,
+0x82,0x03,0x83,0x07,0x82,0x0a,0x82,0x03,0x84,0x03,0x82,0x03,0x82,0x05,0x82,
+0x01,0x82,0x02,0x83,0x07,0x83,0x03,0x82,0x0c,0x83,0x07,0x83,0x03,0x82,0x06,
+0x83,0x04,0x82,0x07,0x82,0x08,0x83,0x08,0x82,0x07,0x82,0x06,0x82,0x01,0x83,
+0x07,0x83,0x03,0x84,0x07,0x82,0x02,0x82,0x0b,0x82,0x0b,0x82,0x10,0x82,0x0e,
+0x82,0x0b,0x82,0x36,0x83,0x04,0x83,0x05,0x82,0x05,0x82,0x06,0x82,0x0d,0x82,
+0x06,0x82,0x05,0x82,0x11,0x82,0x09,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x09,
+0x82,0x0e,0x82,0x0a,0x82,0x02,0x82,0x0b,0x82,0x07,0x82,0x04,0x82,0x04,0x82,
+0x03,0x82,0x05,0x82,0x06,0x82,0x06,0x82,0x06,0x82,0x05,0x82,0x05,0x82,0x06,
+0x82,0x08,0x82,0x10,0x83,0x08,0x82,0x0b,0x82,0x06,0x82,0x07,0x82,0x01,0x82,
+0x08,0x83,0x03,0x81,0x01,0x82,0x08,0x85,0x09,0x82,0x01,0x82,0x0b,0x83,0x0d,
+0x82,0x0d,0x82,0x0e,0x82,0x0f,0x82,0x43,0x82,0x02,0x82,0x08,0x83,0x05,0x82,
+0x08,0x81,0x03,0x81,0x04,0x81,0x02,0x82,0x05,0x84,0x16,0x82,0x0f,0x82,0x1b,
+0x82,0x39,0x82,0x0a,0x83,0x05,0x82,0x09,0x83,0x0a,0x83,0x0a,0x82,0x06,0x82,
+0x0b,0x83,0x07,0x82,0x05,0x82,0x06,0x82,0x05,0x82,0x08,0x82,0x0a,0x82,0x06,
+0x82,0x0c,0x83,0x27,0x85,0x18,0x84,0x15,0x82,0x02,0x82,0x02,0x82,0x02,0x81,
+0x05,0x82,0x06,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x04,0x82,0x06,
+0x83,0x05,0x82,0x0d,0x82,0x0c,0x82,0x07,0x82,0x03,0x82,0x08,0x82,0x08,0x82,
+0x0a,0x82,0x05,0x82,0x06,0x82,0x04,0x83,0x06,0x82,0x0a,0x82,0x03,0x84,0x03,
+0x82,0x03,0x82,0x05,0x85,0x03,0x82,0x07,0x82,0x04,0x82,0x0d,0x82,0x05,0x81,
+0x01,0x82,0x04,0x82,0x06,0x83,0x04,0x83,0x06,0x82,0x08,0x83,0x08,0x82,0x07,
+0x82,0x07,0x84,0x08,0x83,0x04,0x82,0x07,0x82,0x04,0x82,0x0a,0x82,0x0a,0x83,
+0x10,0x82,0x0e,0x82,0x0b,0x82,0x36,0x82,0x05,0x83,0x05,0x82,0x05,0x82,0x06,
+0x83,0x05,0x82,0x05,0x82,0x06,0x82,0x05,0x83,0x10,0x82,0x09,0x83,0x05,0x82,
+0x06,0x82,0x05,0x82,0x09,0x82,0x0e,0x82,0x0a,0x82,0x02,0x83,0x0a,0x82,0x07,
+0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x06,0x83,0x05,0x82,0x06,0x82,
+0x05,0x82,0x05,0x82,0x06,0x82,0x08,0x82,0x0a,0x82,0x05,0x82,0x08,0x82,0x0b,
+0x82,0x06,0x82,0x08,0x81,0x01,0x82,0x08,0x83,0x03,0x84,0x08,0x82,0x01,0x82,
+0x09,0x82,0x01,0x82,0x0b,0x82,0x0e,0x82,0x0d,0x82,0x0d,0x83,0x54,0x82,0x02,
+0x82,0x09,0x82,0x04,0x83,0x07,0x82,0x03,0x81,0x04,0x81,0x02,0x82,0x06,0x82,
+0x18,0x82,0x0d,0x82,0x1c,0x82,0x39,0x81,0x0c,0x82,0x04,0x83,0x09,0x83,0x09,
+0x83,0x0c,0x82,0x05,0x82,0x0b,0x83,0x07,0x82,0x04,0x83,0x06,0x83,0x04,0x82,
+0x08,0x82,0x0a,0x83,0x05,0x82,0x0c,0x82,0x2b,0x83,0x15,0x84,0x18,0x81,0x03,
+0x83,0x01,0x83,0x05,0x82,0x07,0x83,0x03,0x82,0x06,0x83,0x04,0x83,0x05,0x82,
+0x05,0x82,0x06,0x82,0x06,0x82,0x0d,0x82,0x0c,0x83,0x06,0x82,0x03,0x82,0x08,
+0x82,0x08,0x82,0x0a,0x82,0x05,0x82,0x06,0x82,0x05,0x83,0x05,0x82,0x0a,0x82,
+0x04,0x82,0x04,0x82,0x03,0x82,0x06,0x84,0x03,0x83,0x05,0x83,0x04,0x82,0x0d,
+0x83,0x05,0x83,0x04,0x82,0x06,0x83,0x05,0x82,0x06,0x82,0x08,0x83,0x08,0x83,
+0x05,0x83,0x07,0x84,0x08,0x82,0x05,0x82,0x06,0x83,0x04,0x83,0x09,0x82,0x0a,
+0x82,0x11,0x82,0x0e,0x82,0x0b,0x82,0x36,0x82,0x05,0x83,0x05,0x83,0x04,0x82,
+0x07,0x82,0x05,0x82,0x06,0x82,0x04,0x83,0x06,0x82,0x10,0x82,0x0a,0x82,0x04,
+0x83,0x06,0x82,0x05,0x82,0x09,0x82,0x0e,0x82,0x0a,0x82,0x03,0x82,0x0a,0x82,
+0x07,0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x07,0x82,0x04,0x83,0x06,
+0x82,0x05,0x82,0x06,0x82,0x04,0x83,0x08,0x82,0x0a,0x83,0x04,0x82,0x08,0x82,
+0x03,0x82,0x06,0x83,0x04,0x83,0x08,0x83,0x09,0x82,0x04,0x83,0x08,0x82,0x03,
+0x82,0x09,0x83,0x0b,0x82,0x0f,0x82,0x0d,0x82,0x0d,0x83,0x38,0x82,0x1a,0x81,
+0x03,0x82,0x09,0x88,0x08,0x81,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x85,0x17,
+0x82,0x0d,0x82,0x29,0x82,0x1d,0x82,0x0c,0x82,0x0c,0x83,0x02,0x83,0x0a,0x83,
+0x09,0x89,0x06,0x84,0x01,0x83,0x0c,0x83,0x07,0x84,0x01,0x83,0x08,0x83,0x01,
+0x83,0x09,0x82,0x0b,0x84,0x01,0x83,0x07,0x82,0x02,0x84,0x09,0x82,0x0d,0x82,
+0x13,0x81,0x15,0x82,0x10,0x82,0x08,0x81,0x0f,0x82,0x08,0x82,0x03,0x8a,0x06,
+0x83,0x02,0x84,0x05,0x89,0x07,0x89,0x06,0x82,0x0d,0x84,0x02,0x84,0x03,0x82,
+0x08,0x82,0x06,0x86,0x09,0x83,0x01,0x83,0x07,0x82,0x06,0x83,0x04,0x8a,0x02,
+0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x07,0x83,0x04,0x89,0x05,0x82,0x0e,0x8a,
+0x04,0x82,0x07,0x82,0x05,0x84,0x02,0x83,0x09,0x83,0x09,0x84,0x01,0x84,0x08,
+0x83,0x09,0x82,0x05,0x82,0x06,0x82,0x06,0x82,0x09,0x82,0x09,0x8b,0x09,0x82,
+0x0f,0x82,0x0a,0x82,0x37,0x89,0x05,0x88,0x08,0x88,0x07,0x89,0x06,0x84,0x02,
+0x82,0x0a,0x82,0x0a,0x89,0x06,0x82,0x05,0x82,0x06,0x88,0x0b,0x82,0x0a,0x82,
+0x04,0x82,0x06,0x88,0x04,0x82,0x04,0x82,0x04,0x82,0x03,0x82,0x05,0x82,0x07,
+0x84,0x01,0x83,0x07,0x84,0x01,0x83,0x07,0x83,0x02,0x84,0x08,0x82,0x0b,0x83,
+0x02,0x83,0x08,0x87,0x06,0x8a,0x08,0x83,0x09,0x82,0x05,0x82,0x07,0x83,0x03,
+0x83,0x08,0x83,0x0a,0x89,0x09,0x82,0x0d,0x82,0x0d,0x83,0x38,0x82,0x1a,0x81,
+0x03,0x82,0x0a,0x86,0x0f,0x84,0x05,0x86,0x01,0x83,0x16,0x82,0x0d,0x82,0x29,
+0x82,0x1d,0x82,0x0c,0x82,0x0d,0x86,0x0b,0x83,0x09,0x89,0x07,0x86,0x0d,0x83,
+0x08,0x86,0x0a,0x85,0x0a,0x82,0x0c,0x86,0x08,0x86,0x0b,0x82,0x0d,0x82,0x3b,
+0x82,0x09,0x81,0x0e,0x82,0x08,0x82,0x03,0x89,0x08,0x86,0x07,0x88,0x08,0x89,
+0x06,0x82,0x0e,0x87,0x05,0x82,0x08,0x82,0x06,0x86,0x0a,0x85,0x08,0x82,0x07,
+0x82,0x04,0x8a,0x02,0x82,0x0a,0x82,0x03,0x82,0x07,0x83,0x05,0x86,0x07,0x82,
+0x0f,0x86,0x01,0x83,0x03,0x82,0x07,0x82,0x07,0x86,0x0a,0x83,0x0a,0x87,0x0a,
+0x82,0x09,0x82,0x06,0x81,0x05,0x83,0x06,0x83,0x08,0x82,0x09,0x8b,0x09,0x82,
+0x0f,0x82,0x0a,0x82,0x37,0x85,0x02,0x82,0x05,0x81,0x01,0x85,0x0b,0x85,0x09,
+0x85,0x01,0x82,0x08,0x86,0x0a,0x82,0x0b,0x85,0x01,0x82,0x06,0x82,0x05,0x82,
+0x06,0x88,0x0b,0x82,0x0a,0x82,0x04,0x83,0x05,0x88,0x04,0x82,0x04,0x82,0x04,
+0x82,0x03,0x82,0x05,0x82,0x09,0x85,0x08,0x87,0x09,0x85,0x01,0x82,0x08,0x82,
+0x0c,0x86,0x0a,0x84,0x09,0x86,0x01,0x82,0x09,0x82,0x0a,0x81,0x05,0x82,0x07,
+0x82,0x05,0x82,0x08,0x83,0x0a,0x89,0x09,0x82,0x0d,0x82,0x0d,0x82,0x68,0x81,
+0x3b,0x82,0x0b,0x82,0x2a,0x82,0x2a,0x82,0x7f,0x35,0x82,0x46,0x83,0x7f,0x7f,
+0x08,0x82,0x7f,0x10,0x82,0x10,0x82,0x09,0x82,0x18,0x8a,0x76,0x82,0x28,0x82,
+0x54,0x82,0x14,0x82,0x71,0x82,0x1d,0x82,0x0d,0x82,0x0d,0x82,0x68,0x81,0x3b,
+0x82,0x0b,0x82,0x2a,0x82,0x2a,0x82,0x7f,0x35,0x82,0x48,0x86,0x7f,0x7f,0x7f,
+0x15,0x84,0x0e,0x82,0x07,0x84,0x18,0x8a,0x76,0x82,0x28,0x82,0x54,0x82,0x14,
+0x82,0x71,0x82,0x1d,0x83,0x0c,0x82,0x0d,0x82,0x7f,0x26,0x82,0x09,0x82,0x2b,
+0x81,0x7f,0x62,0x81,0x7f,0x7f,0x7f,0x64,0x84,0x17,0x84,0x7f,0x12,0x82,0x04,
+0x82,0x28,0x83,0x54,0x82,0x14,0x82,0x70,0x83,0x1e,0x83,0x0b,0x82,0x0c,0x82,
+0x7f,0x28,0x82,0x07,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x27,0x88,0x26,
+0x84,0x55,0x82,0x14,0x82,0x6e,0x84,0x21,0x81,0x0b,0x82,0x0c,0x81,0x7f,0x2a,
+0x81,0x07,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x2a,0x84,0x28,0x83,0x7f,
+0x5d,0x83,0x64,
+0x00,
+  }
+};
diff --git a/minui/roboto_23x41.h b/minui/roboto_23x41.h
new file mode 100644
index 0000000..793f784
--- /dev/null
+++ b/minui/roboto_23x41.h
@@ -0,0 +1,461 @@
+struct {
+  unsigned width;
+  unsigned height;
+  unsigned char_width;
+  unsigned char_height;
+  unsigned char rundata[6679];
+} font = {
+  .width = 2208,
+  .height = 41,
+  .char_width = 23,
+  .char_height = 41,
+  .rundata = {
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x7f,0x7f,0x7f,0x17,0x83,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x2e,0x83,0x60,0x82,0x0a,0x81,0x7f,0x7f,
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0b,0x87,0x23,0x86,0x7f,0x7f,0x7f,0x7f,
+0x7f,0x7f,0x7f,0x05,0x83,0x5f,0x83,0x09,0x83,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x7f,0x7f,0x7f,0x0a,0x87,0x23,0x86,0x7f,0x7f,0x7f,0x7f,0x7f,0x39,0x82,0x26,
+0x82,0x7f,0x21,0x83,0x5e,0x83,0x0b,0x83,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x7f,0x7f,0x09,0x87,0x23,0x86,0x40,0x84,0x27,0x83,0x37,0x83,0x27,0x86,0x20,
+0x83,0x1a,0x83,0x17,0x83,0x0c,0x83,0x16,0x86,0x7f,0x7f,0x5a,0x85,0x10,0x83,
+0x11,0x85,0x7f,0x1d,0x87,0x5c,0x83,0x0b,0x83,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x7f,0x7f,0x7f,0x09,0x83,0x2a,0x83,0x41,0x83,0x27,0x83,0x37,0x83,0x26,0x86,
+0x21,0x83,0x1a,0x83,0x17,0x83,0x0c,0x83,0x16,0x86,0x7f,0x7f,0x59,0x84,0x12,
+0x83,0x13,0x84,0x58,0x83,0x11,0x83,0x03,0x83,0x10,0x83,0x04,0x83,0x0a,0x8a,
+0x0c,0x84,0x16,0x85,0x14,0x83,0x18,0x83,0x0d,0x83,0x7f,0x11,0x83,0x0d,0x86,
+0x13,0x84,0x11,0x86,0x11,0x86,0x16,0x83,0x0d,0x8c,0x0e,0x86,0x0b,0x91,0x0c,
+0x87,0x0f,0x86,0x7f,0x05,0x86,0x2a,0x83,0x0d,0x8c,0x10,0x87,0x0a,0x8c,0x0c,
+0x90,0x07,0x90,0x0d,0x86,0x0a,0x83,0x0d,0x83,0x09,0x89,0x16,0x83,0x08,0x83,
+0x0b,0x84,0x06,0x83,0x10,0x84,0x0e,0x84,0x03,0x83,0x0d,0x83,0x0a,0x86,0x0c,
+0x8d,0x0f,0x86,0x0c,0x8c,0x10,0x87,0x09,0x94,0x04,0x83,0x0d,0x83,0x03,0x83,
+0x0e,0x84,0x01,0x83,0x10,0x83,0x03,0x84,0x0b,0x84,0x03,0x84,0x0d,0x84,0x04,
+0x92,0x0c,0x83,0x0e,0x83,0x19,0x83,0x14,0x83,0x2b,0x83,0x26,0x83,0x37,0x83,
+0x25,0x87,0x21,0x83,0x1a,0x83,0x17,0x83,0x0c,0x83,0x19,0x83,0x7f,0x7f,0x59,
+0x83,0x13,0x83,0x14,0x83,0x58,0x83,0x11,0x83,0x03,0x83,0x10,0x83,0x04,0x83,
+0x09,0x8c,0x09,0x88,0x12,0x89,0x12,0x83,0x17,0x83,0x0f,0x83,0x7f,0x0f,0x83,
+0x0c,0x8a,0x0e,0x87,0x0f,0x8a,0x0d,0x8a,0x13,0x84,0x0d,0x8c,0x0c,0x8a,0x09,
+0x91,0x0a,0x8b,0x0b,0x8a,0x7f,0x01,0x8a,0x28,0x83,0x0d,0x8e,0x0c,0x8b,0x08,
+0x8e,0x0a,0x90,0x07,0x90,0x0a,0x8c,0x07,0x83,0x0d,0x83,0x09,0x89,0x16,0x83,
+0x08,0x83,0x0a,0x84,0x07,0x83,0x10,0x85,0x0d,0x84,0x03,0x84,0x0c,0x83,0x08,
+0x8a,0x0a,0x8f,0x0b,0x8a,0x0a,0x8e,0x0c,0x8b,0x07,0x94,0x04,0x83,0x0d,0x83,
+0x03,0x84,0x0d,0x84,0x01,0x83,0x08,0x81,0x07,0x83,0x03,0x84,0x0b,0x84,0x04,
+0x84,0x0b,0x84,0x05,0x92,0x0c,0x83,0x0e,0x84,0x18,0x83,0x14,0x83,0x2b,0x84,
+0x25,0x83,0x37,0x83,0x24,0x84,0x25,0x83,0x43,0x83,0x19,0x83,0x7f,0x33,0x83,
+0x7f,0x22,0x84,0x13,0x83,0x14,0x83,0x58,0x83,0x11,0x83,0x03,0x83,0x10,0x83,
+0x04,0x83,0x09,0x84,0x05,0x84,0x08,0x83,0x02,0x83,0x11,0x8b,0x11,0x83,0x17,
+0x83,0x0f,0x83,0x7f,0x0f,0x83,0x0b,0x8c,0x0b,0x89,0x0e,0x8c,0x0b,0x8c,0x11,
+0x85,0x0c,0x8d,0x0b,0x8b,0x09,0x91,0x09,0x8d,0x09,0x8c,0x7e,0x8c,0x0e,0x87,
+0x11,0x85,0x0c,0x8f,0x0a,0x8e,0x06,0x8f,0x09,0x90,0x07,0x90,0x09,0x8e,0x06,
+0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x09,0x84,0x08,0x83,0x10,0x85,
+0x0c,0x85,0x03,0x85,0x0b,0x83,0x06,0x8e,0x08,0x90,0x08,0x8e,0x08,0x8f,0x0a,
+0x8d,0x06,0x94,0x04,0x83,0x0d,0x83,0x03,0x84,0x0d,0x83,0x02,0x83,0x08,0x81,
+0x07,0x83,0x04,0x84,0x09,0x84,0x05,0x84,0x0b,0x84,0x05,0x92,0x0c,0x83,0x0f,
+0x83,0x18,0x83,0x13,0x85,0x2b,0x83,0x25,0x83,0x37,0x83,0x24,0x83,0x26,0x83,
+0x43,0x83,0x19,0x83,0x7f,0x33,0x83,0x7f,0x22,0x83,0x14,0x83,0x15,0x83,0x57,
+0x83,0x11,0x83,0x03,0x83,0x10,0x82,0x05,0x83,0x08,0x84,0x07,0x83,0x07,0x83,
+0x04,0x83,0x05,0x82,0x08,0x85,0x04,0x84,0x10,0x83,0x16,0x83,0x11,0x83,0x7f,
+0x0e,0x83,0x0a,0x85,0x04,0x85,0x09,0x86,0x01,0x83,0x0d,0x84,0x05,0x84,0x0a,
+0x84,0x05,0x84,0x11,0x85,0x0c,0x84,0x13,0x84,0x06,0x82,0x16,0x84,0x09,0x84,
+0x05,0x84,0x08,0x85,0x04,0x85,0x7d,0x84,0x04,0x84,0x0c,0x8b,0x0f,0x85,0x0c,
+0x83,0x08,0x85,0x08,0x85,0x06,0x84,0x06,0x83,0x08,0x85,0x08,0x83,0x14,0x83,
+0x15,0x85,0x06,0x85,0x05,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x08,
+0x84,0x09,0x83,0x10,0x86,0x0b,0x85,0x03,0x85,0x0b,0x83,0x05,0x85,0x06,0x85,
+0x07,0x83,0x09,0x85,0x06,0x85,0x06,0x85,0x07,0x83,0x08,0x85,0x08,0x85,0x05,
+0x85,0x0e,0x83,0x0c,0x83,0x0d,0x83,0x04,0x83,0x0c,0x84,0x02,0x83,0x07,0x82,
+0x07,0x83,0x05,0x84,0x07,0x84,0x07,0x84,0x09,0x84,0x14,0x83,0x0d,0x83,0x0f,
+0x83,0x18,0x83,0x13,0x85,0x53,0x83,0x37,0x83,0x24,0x83,0x26,0x83,0x43,0x83,
+0x19,0x83,0x7f,0x33,0x83,0x7f,0x22,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x11,
+0x83,0x03,0x83,0x10,0x82,0x05,0x82,0x09,0x83,0x08,0x84,0x06,0x83,0x04,0x83,
+0x05,0x82,0x08,0x83,0x07,0x83,0x10,0x83,0x16,0x83,0x11,0x83,0x2c,0x83,0x5d,
+0x83,0x0b,0x83,0x08,0x83,0x09,0x83,0x04,0x83,0x0d,0x83,0x07,0x84,0x08,0x84,
+0x07,0x84,0x0f,0x86,0x0c,0x83,0x13,0x84,0x1e,0x84,0x09,0x84,0x07,0x84,0x07,
+0x83,0x08,0x83,0x7c,0x84,0x06,0x84,0x0a,0x83,0x06,0x84,0x0e,0x86,0x0b,0x83,
+0x0a,0x84,0x07,0x84,0x08,0x84,0x05,0x83,0x0a,0x84,0x07,0x83,0x14,0x83,0x15,
+0x84,0x09,0x83,0x05,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x08,0x83,
+0x0a,0x83,0x10,0x86,0x0b,0x85,0x03,0x86,0x0a,0x83,0x05,0x84,0x08,0x84,0x07,
+0x83,0x0b,0x83,0x06,0x84,0x08,0x84,0x07,0x83,0x0a,0x84,0x06,0x84,0x09,0x83,
+0x0e,0x83,0x0c,0x83,0x0d,0x83,0x04,0x84,0x0b,0x84,0x03,0x82,0x07,0x83,0x06,
+0x82,0x06,0x84,0x07,0x84,0x08,0x83,0x09,0x84,0x13,0x84,0x0d,0x83,0x0f,0x84,
+0x17,0x83,0x13,0x82,0x01,0x83,0x52,0x83,0x37,0x83,0x24,0x83,0x26,0x83,0x43,
+0x83,0x19,0x83,0x7f,0x33,0x83,0x7f,0x22,0x83,0x14,0x83,0x15,0x83,0x57,0x83,
+0x11,0x83,0x03,0x83,0x0f,0x83,0x04,0x83,0x09,0x83,0x09,0x83,0x06,0x83,0x04,
+0x83,0x04,0x82,0x09,0x83,0x07,0x83,0x10,0x83,0x15,0x83,0x13,0x83,0x2b,0x83,
+0x5d,0x83,0x0b,0x83,0x08,0x83,0x10,0x83,0x0c,0x83,0x09,0x83,0x08,0x83,0x09,
+0x83,0x0f,0x86,0x0c,0x83,0x13,0x83,0x1e,0x84,0x0a,0x83,0x09,0x83,0x06,0x84,
+0x08,0x83,0x7c,0x83,0x08,0x83,0x09,0x83,0x09,0x83,0x0c,0x87,0x0b,0x83,0x0b,
+0x83,0x06,0x84,0x0a,0x84,0x04,0x83,0x0b,0x84,0x06,0x83,0x14,0x83,0x14,0x84,
+0x0a,0x84,0x04,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x07,0x84,0x0a,
+0x83,0x10,0x86,0x0a,0x86,0x03,0x87,0x09,0x83,0x04,0x84,0x0a,0x84,0x06,0x83,
+0x0b,0x84,0x04,0x84,0x0a,0x84,0x06,0x83,0x0b,0x83,0x06,0x83,0x0a,0x84,0x0d,
+0x83,0x0c,0x83,0x0d,0x83,0x04,0x84,0x0b,0x83,0x04,0x83,0x06,0x83,0x06,0x82,
+0x07,0x84,0x05,0x84,0x09,0x84,0x07,0x84,0x13,0x84,0x0e,0x83,0x10,0x83,0x17,
+0x83,0x12,0x83,0x01,0x83,0x52,0x83,0x37,0x83,0x24,0x83,0x26,0x83,0x43,0x83,
+0x19,0x83,0x7f,0x33,0x83,0x7f,0x22,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x11,
+0x82,0x04,0x82,0x10,0x83,0x04,0x83,0x09,0x83,0x09,0x83,0x06,0x83,0x04,0x83,
+0x04,0x82,0x09,0x83,0x07,0x83,0x10,0x82,0x16,0x83,0x13,0x83,0x14,0x83,0x14,
+0x83,0x5d,0x82,0x0b,0x83,0x0a,0x83,0x0f,0x83,0x0c,0x83,0x09,0x83,0x08,0x83,
+0x09,0x83,0x0e,0x83,0x01,0x83,0x0c,0x83,0x13,0x83,0x1e,0x83,0x0b,0x83,0x09,
+0x83,0x06,0x83,0x0a,0x83,0x0d,0x83,0x14,0x83,0x5f,0x83,0x08,0x83,0x0b,0x82,
+0x0c,0x83,0x01,0x83,0x0b,0x83,0x0b,0x83,0x06,0x83,0x0c,0x83,0x04,0x83,0x0c,
+0x83,0x06,0x83,0x14,0x83,0x14,0x83,0x0c,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,
+0x19,0x83,0x08,0x83,0x06,0x84,0x0b,0x83,0x10,0x87,0x09,0x86,0x03,0x87,0x09,
+0x83,0x04,0x83,0x0c,0x83,0x06,0x83,0x0c,0x83,0x04,0x83,0x0c,0x83,0x06,0x83,
+0x0b,0x83,0x06,0x83,0x0b,0x83,0x0d,0x83,0x0c,0x83,0x0d,0x83,0x05,0x83,0x0a,
+0x84,0x04,0x83,0x06,0x83,0x05,0x83,0x07,0x84,0x05,0x84,0x0a,0x83,0x07,0x84,
+0x13,0x83,0x0f,0x83,0x10,0x83,0x17,0x83,0x12,0x83,0x01,0x83,0x40,0x86,0x0c,
+0x83,0x03,0x85,0x11,0x86,0x10,0x85,0x03,0x83,0x0d,0x85,0x0f,0x8a,0x0f,0x85,
+0x04,0x82,0x08,0x83,0x03,0x85,0x0f,0x86,0x17,0x83,0x0c,0x83,0x07,0x84,0x0e,
+0x83,0x0a,0x83,0x02,0x83,0x07,0x83,0x09,0x83,0x03,0x85,0x10,0x86,0x0d,0x82,
+0x04,0x85,0x10,0x85,0x04,0x82,0x09,0x83,0x02,0x85,0x11,0x86,0x0c,0x8d,0x0a,
+0x83,0x09,0x83,0x08,0x84,0x09,0x83,0x03,0x84,0x0f,0x83,0x05,0x84,0x08,0x84,
+0x07,0x83,0x0a,0x83,0x07,0x8f,0x0e,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x11,
+0x82,0x04,0x82,0x0b,0x92,0x06,0x83,0x12,0x83,0x04,0x83,0x03,0x82,0x0a,0x83,
+0x06,0x84,0x10,0x82,0x16,0x83,0x13,0x83,0x14,0x83,0x14,0x83,0x5c,0x83,0x0b,
+0x83,0x0a,0x83,0x0f,0x83,0x0c,0x83,0x09,0x83,0x14,0x83,0x0d,0x84,0x01,0x83,
+0x0c,0x83,0x12,0x83,0x1e,0x83,0x0c,0x83,0x09,0x83,0x06,0x83,0x0a,0x83,0x0d,
+0x83,0x14,0x83,0x5f,0x83,0x07,0x83,0x0d,0x82,0x0b,0x83,0x01,0x84,0x0a,0x83,
+0x0b,0x83,0x05,0x84,0x0c,0x83,0x04,0x83,0x0c,0x83,0x06,0x83,0x14,0x83,0x13,
+0x83,0x0d,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x05,0x84,
+0x0c,0x83,0x10,0x83,0x01,0x83,0x08,0x83,0x01,0x83,0x03,0x83,0x01,0x84,0x08,
+0x83,0x03,0x84,0x0c,0x84,0x05,0x83,0x0c,0x83,0x03,0x84,0x0c,0x84,0x05,0x83,
+0x0b,0x83,0x06,0x83,0x0b,0x83,0x0d,0x83,0x0c,0x83,0x0d,0x83,0x05,0x84,0x09,
+0x84,0x04,0x83,0x06,0x83,0x05,0x83,0x08,0x84,0x03,0x84,0x0b,0x84,0x05,0x84,
+0x13,0x84,0x0f,0x83,0x10,0x84,0x16,0x83,0x11,0x83,0x03,0x83,0x3d,0x8a,0x0a,
+0x83,0x01,0x89,0x0d,0x89,0x0d,0x89,0x01,0x83,0x0b,0x89,0x0d,0x8a,0x0d,0x89,
+0x01,0x83,0x08,0x83,0x01,0x89,0x0d,0x86,0x17,0x83,0x0c,0x83,0x06,0x84,0x0f,
+0x83,0x0a,0x83,0x01,0x86,0x03,0x87,0x07,0x83,0x01,0x89,0x0d,0x89,0x0b,0x83,
+0x01,0x89,0x0c,0x89,0x01,0x83,0x09,0x83,0x01,0x88,0x0d,0x8a,0x0a,0x8d,0x0a,
+0x83,0x09,0x83,0x08,0x84,0x09,0x83,0x03,0x84,0x06,0x82,0x06,0x84,0x06,0x84,
+0x06,0x84,0x08,0x84,0x08,0x84,0x07,0x8f,0x0e,0x83,0x14,0x83,0x15,0x83,0x57,
+0x83,0x11,0x82,0x04,0x82,0x0b,0x92,0x06,0x84,0x12,0x83,0x02,0x83,0x03,0x83,
+0x0a,0x84,0x04,0x84,0x11,0x82,0x16,0x83,0x13,0x83,0x14,0x83,0x14,0x83,0x5c,
+0x83,0x0b,0x83,0x0a,0x83,0x0f,0x83,0x18,0x83,0x13,0x84,0x0d,0x83,0x02,0x83,
+0x0c,0x83,0x12,0x83,0x1d,0x84,0x0c,0x84,0x07,0x84,0x06,0x83,0x0a,0x83,0x0d,
+0x83,0x14,0x83,0x1a,0x82,0x0a,0x8f,0x08,0x81,0x21,0x83,0x07,0x82,0x06,0x84,
+0x04,0x83,0x09,0x83,0x03,0x83,0x0a,0x83,0x0b,0x83,0x05,0x83,0x0d,0x83,0x04,
+0x83,0x0d,0x83,0x05,0x83,0x14,0x83,0x13,0x83,0x14,0x83,0x0d,0x83,0x0c,0x83,
+0x19,0x83,0x08,0x83,0x04,0x84,0x0d,0x83,0x10,0x83,0x01,0x84,0x07,0x83,0x01,
+0x83,0x03,0x83,0x01,0x85,0x07,0x83,0x03,0x83,0x0e,0x83,0x05,0x83,0x0c,0x83,
+0x03,0x83,0x0e,0x83,0x05,0x83,0x0b,0x83,0x06,0x83,0x1b,0x83,0x0c,0x83,0x0d,
+0x83,0x05,0x84,0x09,0x83,0x06,0x82,0x06,0x83,0x05,0x82,0x0a,0x83,0x02,0x84,
+0x0d,0x83,0x05,0x83,0x13,0x84,0x10,0x83,0x11,0x83,0x16,0x83,0x11,0x83,0x03,
+0x83,0x3c,0x8c,0x09,0x8e,0x0b,0x8b,0x0b,0x8e,0x0a,0x8b,0x0c,0x8a,0x0c,0x8e,
+0x08,0x8e,0x0f,0x83,0x17,0x83,0x0c,0x83,0x06,0x84,0x0f,0x83,0x0a,0x8b,0x01,
+0x89,0x06,0x8e,0x0a,0x8c,0x0a,0x8e,0x0a,0x8e,0x09,0x8d,0x0b,0x8c,0x09,0x8d,
+0x0a,0x83,0x09,0x83,0x08,0x84,0x08,0x84,0x03,0x84,0x05,0x83,0x06,0x84,0x06,
+0x84,0x06,0x83,0x09,0x84,0x08,0x84,0x07,0x8f,0x0e,0x83,0x14,0x83,0x15,0x83,
+0x57,0x83,0x24,0x92,0x07,0x83,0x12,0x88,0x03,0x82,0x0c,0x83,0x03,0x84,0x29,
+0x83,0x15,0x83,0x0f,0x81,0x03,0x83,0x03,0x81,0x10,0x83,0x5b,0x83,0x0c,0x83,
+0x0a,0x83,0x0f,0x83,0x17,0x83,0x13,0x84,0x0d,0x83,0x03,0x83,0x0c,0x83,0x01,
+0x85,0x0c,0x83,0x03,0x85,0x15,0x83,0x0e,0x84,0x05,0x84,0x07,0x83,0x0a,0x83,
+0x3f,0x84,0x0a,0x8f,0x08,0x83,0x1e,0x83,0x07,0x82,0x06,0x86,0x04,0x82,0x09,
+0x83,0x03,0x83,0x0a,0x83,0x0a,0x83,0x06,0x83,0x14,0x83,0x0d,0x83,0x05,0x83,
+0x14,0x83,0x13,0x83,0x14,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x03,
+0x84,0x0e,0x83,0x10,0x83,0x02,0x83,0x06,0x84,0x01,0x83,0x03,0x83,0x02,0x84,
+0x07,0x83,0x03,0x83,0x0e,0x83,0x05,0x83,0x0c,0x83,0x03,0x83,0x0e,0x83,0x05,
+0x83,0x0b,0x83,0x07,0x83,0x1a,0x83,0x0c,0x83,0x0d,0x83,0x06,0x83,0x08,0x84,
+0x06,0x83,0x04,0x85,0x04,0x82,0x0a,0x84,0x01,0x84,0x0d,0x84,0x03,0x84,0x12,
+0x84,0x11,0x83,0x11,0x84,0x15,0x83,0x11,0x83,0x03,0x84,0x3b,0x84,0x04,0x84,
+0x09,0x85,0x05,0x84,0x0b,0x84,0x04,0x84,0x0a,0x84,0x05,0x85,0x09,0x84,0x05,
+0x84,0x0e,0x83,0x10,0x84,0x05,0x85,0x08,0x85,0x05,0x84,0x0f,0x83,0x17,0x83,
+0x0c,0x83,0x05,0x84,0x10,0x83,0x0a,0x85,0x02,0x84,0x01,0x83,0x02,0x84,0x06,
+0x85,0x05,0x84,0x0a,0x84,0x05,0x84,0x09,0x85,0x05,0x84,0x0a,0x84,0x05,0x85,
+0x09,0x85,0x05,0x84,0x09,0x84,0x05,0x84,0x0c,0x83,0x11,0x83,0x09,0x83,0x09,
+0x83,0x08,0x83,0x05,0x83,0x05,0x83,0x06,0x83,0x08,0x84,0x04,0x84,0x0a,0x83,
+0x08,0x83,0x12,0x84,0x0f,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x28,0x83,0x05,
+0x82,0x0b,0x85,0x12,0x84,0x04,0x82,0x0d,0x89,0x2a,0x83,0x15,0x83,0x0e,0x88,
+0x01,0x83,0x10,0x83,0x5b,0x83,0x0c,0x83,0x0a,0x83,0x0f,0x83,0x17,0x83,0x0e,
+0x88,0x0e,0x83,0x03,0x83,0x0c,0x8b,0x0a,0x83,0x01,0x89,0x12,0x83,0x10,0x8b,
+0x08,0x83,0x0a,0x83,0x3c,0x87,0x0a,0x8f,0x08,0x86,0x1a,0x84,0x07,0x82,0x05,
+0x83,0x02,0x82,0x04,0x82,0x08,0x84,0x03,0x84,0x09,0x83,0x09,0x84,0x06,0x83,
+0x14,0x83,0x0d,0x83,0x05,0x83,0x14,0x83,0x13,0x83,0x14,0x83,0x0d,0x83,0x0c,
+0x83,0x19,0x83,0x08,0x83,0x02,0x84,0x0f,0x83,0x10,0x83,0x02,0x83,0x06,0x83,
+0x02,0x83,0x03,0x83,0x03,0x84,0x06,0x83,0x03,0x83,0x0e,0x83,0x05,0x83,0x0b,
+0x84,0x03,0x83,0x0e,0x83,0x05,0x83,0x0a,0x83,0x08,0x84,0x19,0x83,0x0c,0x83,
+0x0d,0x83,0x06,0x84,0x07,0x84,0x06,0x83,0x04,0x85,0x03,0x83,0x0b,0x87,0x0f,
+0x83,0x03,0x83,0x13,0x84,0x11,0x83,0x12,0x83,0x15,0x83,0x10,0x83,0x05,0x83,
+0x3a,0x83,0x07,0x84,0x08,0x84,0x07,0x84,0x09,0x84,0x06,0x84,0x08,0x84,0x07,
+0x84,0x08,0x84,0x07,0x83,0x0e,0x83,0x0f,0x84,0x07,0x84,0x08,0x84,0x07,0x84,
+0x0e,0x83,0x17,0x83,0x0c,0x83,0x04,0x84,0x11,0x83,0x0a,0x84,0x04,0x85,0x05,
+0x84,0x05,0x84,0x07,0x84,0x08,0x84,0x07,0x84,0x08,0x84,0x07,0x84,0x08,0x84,
+0x07,0x84,0x09,0x84,0x08,0x82,0x09,0x83,0x07,0x84,0x0b,0x83,0x11,0x83,0x09,
+0x83,0x09,0x84,0x07,0x83,0x05,0x83,0x05,0x83,0x06,0x83,0x09,0x83,0x04,0x83,
+0x0b,0x84,0x06,0x84,0x12,0x84,0x0f,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x28,
+0x83,0x04,0x83,0x0c,0x86,0x17,0x83,0x0e,0x86,0x2c,0x83,0x15,0x83,0x0e,0x8d,
+0x0f,0x83,0x5b,0x82,0x0d,0x83,0x0a,0x83,0x0f,0x83,0x16,0x83,0x0f,0x87,0x0e,
+0x83,0x04,0x83,0x0c,0x8c,0x09,0x8e,0x11,0x83,0x12,0x87,0x0a,0x84,0x09,0x83,
+0x3a,0x88,0x22,0x88,0x18,0x83,0x08,0x82,0x04,0x83,0x03,0x82,0x04,0x83,0x07,
+0x83,0x05,0x83,0x09,0x8f,0x07,0x83,0x14,0x83,0x0d,0x83,0x05,0x8e,0x09,0x8f,
+0x07,0x83,0x14,0x93,0x0c,0x83,0x19,0x83,0x08,0x88,0x10,0x83,0x10,0x83,0x02,
+0x84,0x05,0x83,0x02,0x83,0x03,0x83,0x03,0x85,0x05,0x83,0x03,0x83,0x0e,0x83,
+0x05,0x83,0x0b,0x83,0x04,0x83,0x0e,0x83,0x05,0x83,0x09,0x84,0x09,0x86,0x16,
+0x83,0x0c,0x83,0x0d,0x83,0x07,0x83,0x07,0x83,0x07,0x83,0x04,0x82,0x01,0x82,
+0x03,0x83,0x0c,0x85,0x10,0x84,0x01,0x84,0x12,0x84,0x12,0x83,0x12,0x83,0x15,
+0x83,0x10,0x83,0x05,0x83,0x3a,0x83,0x08,0x83,0x08,0x83,0x09,0x83,0x09,0x83,
+0x08,0x83,0x08,0x83,0x09,0x83,0x08,0x83,0x09,0x83,0x0d,0x83,0x0f,0x83,0x09,
+0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x83,0x04,0x84,0x11,0x83,
+0x0a,0x83,0x06,0x84,0x06,0x83,0x05,0x83,0x09,0x83,0x08,0x83,0x09,0x83,0x08,
+0x83,0x09,0x83,0x08,0x83,0x09,0x83,0x09,0x83,0x14,0x83,0x08,0x83,0x0b,0x83,
+0x11,0x83,0x09,0x83,0x0a,0x83,0x06,0x84,0x05,0x84,0x04,0x84,0x05,0x83,0x09,
+0x84,0x02,0x84,0x0b,0x84,0x06,0x84,0x11,0x84,0x0f,0x83,0x15,0x83,0x16,0x83,
+0x56,0x83,0x28,0x83,0x04,0x83,0x0d,0x88,0x14,0x82,0x0f,0x85,0x2d,0x83,0x15,
+0x83,0x11,0x88,0x0a,0x92,0x52,0x83,0x0d,0x83,0x0a,0x83,0x0f,0x83,0x15,0x84,
+0x0f,0x89,0x0b,0x83,0x05,0x83,0x0b,0x85,0x04,0x85,0x08,0x85,0x05,0x85,0x0f,
+0x83,0x11,0x8b,0x09,0x83,0x08,0x84,0x38,0x88,0x26,0x88,0x15,0x83,0x08,0x82,
+0x05,0x83,0x03,0x82,0x05,0x82,0x07,0x83,0x05,0x83,0x09,0x8f,0x07,0x83,0x14,
+0x83,0x0d,0x83,0x05,0x8e,0x09,0x8f,0x07,0x83,0x07,0x89,0x04,0x93,0x0c,0x83,
+0x19,0x83,0x08,0x88,0x10,0x83,0x10,0x83,0x03,0x83,0x04,0x83,0x03,0x83,0x03,
+0x83,0x04,0x84,0x05,0x83,0x03,0x83,0x0e,0x83,0x05,0x83,0x09,0x85,0x04,0x83,
+0x0e,0x83,0x05,0x8f,0x0c,0x87,0x13,0x83,0x0c,0x83,0x0d,0x83,0x07,0x83,0x06,
+0x84,0x08,0x82,0x04,0x82,0x01,0x83,0x02,0x82,0x0d,0x85,0x11,0x83,0x01,0x83,
+0x12,0x84,0x13,0x83,0x12,0x84,0x14,0x83,0x0f,0x84,0x06,0x83,0x44,0x83,0x08,
+0x83,0x09,0x84,0x07,0x83,0x09,0x83,0x07,0x84,0x09,0x83,0x07,0x83,0x0a,0x83,
+0x0d,0x83,0x0e,0x84,0x09,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,
+0x83,0x03,0x84,0x12,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,
+0x07,0x84,0x09,0x84,0x07,0x83,0x09,0x84,0x06,0x84,0x09,0x83,0x09,0x83,0x14,
+0x83,0x16,0x83,0x11,0x83,0x09,0x83,0x0a,0x83,0x06,0x83,0x06,0x84,0x03,0x85,
+0x04,0x83,0x0b,0x88,0x0d,0x83,0x06,0x83,0x11,0x84,0x0f,0x84,0x15,0x83,0x16,
+0x84,0x0c,0x85,0x44,0x83,0x28,0x82,0x05,0x83,0x0e,0x88,0x12,0x82,0x0f,0x87,
+0x2c,0x83,0x15,0x83,0x12,0x85,0x0c,0x92,0x1f,0x8d,0x26,0x83,0x0d,0x83,0x0a,
+0x83,0x0f,0x83,0x15,0x83,0x16,0x84,0x0a,0x83,0x05,0x83,0x0b,0x84,0x07,0x83,
+0x08,0x84,0x08,0x83,0x0f,0x83,0x10,0x84,0x04,0x85,0x08,0x85,0x04,0x86,0x36,
+0x87,0x2c,0x87,0x12,0x83,0x09,0x82,0x04,0x83,0x04,0x82,0x05,0x82,0x06,0x83,
+0x06,0x84,0x08,0x90,0x06,0x83,0x14,0x83,0x0d,0x83,0x05,0x8e,0x09,0x8f,0x07,
+0x83,0x07,0x89,0x04,0x93,0x0c,0x83,0x19,0x83,0x08,0x89,0x0f,0x83,0x10,0x83,
+0x03,0x84,0x03,0x83,0x03,0x83,0x03,0x83,0x05,0x84,0x04,0x83,0x03,0x83,0x0e,
+0x83,0x05,0x90,0x05,0x83,0x0e,0x83,0x05,0x8e,0x0f,0x87,0x11,0x83,0x0c,0x83,
+0x0d,0x83,0x07,0x84,0x05,0x83,0x09,0x83,0x02,0x83,0x02,0x82,0x02,0x82,0x0d,
+0x85,0x11,0x87,0x12,0x84,0x13,0x83,0x13,0x83,0x14,0x83,0x60,0x83,0x08,0x83,
+0x0a,0x83,0x07,0x83,0x13,0x83,0x0a,0x83,0x07,0x83,0x0a,0x83,0x0d,0x83,0x0e,
+0x83,0x0a,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x83,0x02,0x84,
+0x13,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x07,0x83,0x0b,
+0x83,0x07,0x83,0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,0x83,0x14,0x84,0x15,0x83,
+0x11,0x83,0x09,0x83,0x0a,0x84,0x05,0x83,0x07,0x83,0x03,0x85,0x04,0x83,0x0c,
+0x86,0x0e,0x84,0x05,0x83,0x10,0x84,0x0f,0x84,0x16,0x83,0x17,0x85,0x09,0x88,
+0x07,0x83,0x38,0x83,0x28,0x82,0x05,0x82,0x12,0x86,0x11,0x82,0x03,0x84,0x07,
+0x84,0x01,0x84,0x06,0x83,0x22,0x83,0x15,0x83,0x11,0x87,0x0b,0x92,0x1f,0x8d,
+0x26,0x82,0x0e,0x83,0x0a,0x83,0x0f,0x83,0x14,0x83,0x18,0x83,0x09,0x83,0x06,
+0x83,0x16,0x84,0x07,0x83,0x09,0x84,0x0d,0x83,0x10,0x83,0x08,0x83,0x09,0x8e,
+0x34,0x87,0x30,0x87,0x0f,0x83,0x0a,0x82,0x04,0x83,0x04,0x82,0x05,0x82,0x06,
+0x83,0x07,0x83,0x08,0x83,0x09,0x85,0x05,0x83,0x14,0x83,0x0d,0x83,0x05,0x83,
+0x14,0x83,0x13,0x83,0x07,0x89,0x04,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,
+0x83,0x03,0x84,0x0e,0x83,0x10,0x83,0x04,0x83,0x02,0x84,0x03,0x83,0x03,0x83,
+0x05,0x85,0x03,0x83,0x03,0x83,0x0e,0x83,0x05,0x8f,0x06,0x83,0x0e,0x83,0x05,
+0x8f,0x11,0x86,0x0f,0x83,0x0c,0x83,0x0d,0x83,0x08,0x83,0x05,0x83,0x09,0x83,
+0x02,0x82,0x03,0x82,0x01,0x83,0x0d,0x86,0x11,0x85,0x12,0x84,0x14,0x83,0x13,
+0x83,0x14,0x83,0x59,0x8a,0x08,0x83,0x0a,0x83,0x07,0x83,0x13,0x83,0x0a,0x83,
+0x07,0x90,0x0d,0x83,0x0e,0x83,0x0a,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,
+0x83,0x0c,0x88,0x14,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,
+0x07,0x83,0x0b,0x83,0x07,0x83,0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,0x83,0x15,
+0x85,0x13,0x83,0x11,0x83,0x09,0x83,0x0b,0x83,0x04,0x84,0x07,0x83,0x03,0x86,
+0x03,0x83,0x0c,0x86,0x0f,0x83,0x04,0x84,0x10,0x84,0x0f,0x83,0x17,0x83,0x18,
+0x84,0x08,0x8a,0x06,0x83,0x38,0x83,0x27,0x83,0x05,0x82,0x14,0x85,0x0f,0x82,
+0x03,0x86,0x05,0x84,0x03,0x84,0x05,0x83,0x22,0x83,0x15,0x83,0x10,0x84,0x01,
+0x83,0x12,0x83,0x27,0x8d,0x25,0x83,0x0e,0x83,0x0a,0x83,0x0f,0x83,0x13,0x83,
+0x1a,0x83,0x07,0x84,0x06,0x83,0x17,0x83,0x07,0x83,0x0a,0x83,0x0d,0x83,0x0f,
+0x84,0x08,0x84,0x09,0x89,0x01,0x83,0x34,0x85,0x35,0x85,0x0d,0x84,0x0a,0x82,
+0x04,0x83,0x04,0x82,0x05,0x82,0x06,0x83,0x07,0x83,0x08,0x83,0x0b,0x83,0x05,
+0x83,0x14,0x83,0x0d,0x83,0x05,0x83,0x14,0x83,0x13,0x83,0x0d,0x83,0x04,0x83,
+0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x03,0x85,0x0d,0x83,0x10,0x83,0x04,
+0x84,0x01,0x83,0x04,0x83,0x03,0x83,0x06,0x84,0x03,0x83,0x03,0x83,0x0e,0x83,
+0x05,0x8d,0x08,0x83,0x0e,0x83,0x05,0x83,0x09,0x84,0x13,0x84,0x0e,0x83,0x0c,
+0x83,0x0d,0x83,0x08,0x83,0x04,0x84,0x09,0x83,0x02,0x82,0x03,0x82,0x01,0x83,
+0x0c,0x87,0x11,0x85,0x11,0x84,0x15,0x83,0x13,0x84,0x13,0x83,0x56,0x8d,0x08,
+0x83,0x0a,0x83,0x07,0x83,0x13,0x83,0x0a,0x83,0x07,0x90,0x0d,0x83,0x0e,0x83,
+0x0a,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x88,0x14,0x83,0x0a,
+0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x07,0x83,0x0b,0x83,0x07,0x83,
+0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,0x83,0x16,0x88,0x0f,0x83,0x11,0x83,0x09,
+0x83,0x0b,0x83,0x04,0x83,0x08,0x84,0x01,0x83,0x01,0x83,0x03,0x83,0x0d,0x84,
+0x10,0x83,0x04,0x83,0x10,0x84,0x10,0x84,0x16,0x83,0x17,0x85,0x07,0x84,0x04,
+0x84,0x04,0x84,0x38,0x83,0x23,0x92,0x12,0x84,0x0d,0x83,0x02,0x83,0x02,0x83,
+0x03,0x84,0x05,0x84,0x04,0x83,0x22,0x83,0x15,0x83,0x10,0x83,0x03,0x83,0x11,
+0x83,0x59,0x82,0x0f,0x83,0x0a,0x83,0x0f,0x83,0x12,0x84,0x1a,0x83,0x07,0x83,
+0x07,0x83,0x17,0x83,0x07,0x83,0x0a,0x83,0x0d,0x83,0x0f,0x83,0x0a,0x83,0x0b,
+0x85,0x03,0x83,0x34,0x85,0x14,0x8f,0x13,0x84,0x0d,0x83,0x0b,0x82,0x04,0x83,
+0x04,0x82,0x05,0x82,0x05,0x8f,0x07,0x83,0x0c,0x83,0x04,0x83,0x14,0x83,0x0d,
+0x83,0x05,0x83,0x14,0x83,0x13,0x83,0x0d,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,
+0x19,0x83,0x08,0x83,0x04,0x85,0x0c,0x83,0x10,0x83,0x04,0x84,0x01,0x83,0x04,
+0x83,0x03,0x83,0x07,0x84,0x02,0x83,0x03,0x83,0x0e,0x83,0x05,0x83,0x12,0x83,
+0x0e,0x83,0x05,0x83,0x0a,0x84,0x13,0x83,0x0e,0x83,0x0c,0x83,0x0d,0x83,0x08,
+0x84,0x03,0x83,0x0a,0x83,0x01,0x83,0x03,0x82,0x01,0x83,0x0b,0x84,0x01,0x84,
+0x11,0x83,0x12,0x83,0x16,0x83,0x14,0x83,0x13,0x83,0x55,0x8e,0x08,0x83,0x0a,
+0x83,0x07,0x83,0x13,0x83,0x0a,0x83,0x07,0x90,0x0d,0x83,0x0e,0x83,0x0a,0x83,
+0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x88,0x14,0x83,0x0a,0x83,0x06,
+0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x07,0x83,0x0b,0x83,0x07,0x83,0x0a,0x83,
+0x06,0x83,0x0a,0x83,0x09,0x83,0x19,0x87,0x0d,0x83,0x11,0x83,0x09,0x83,0x0b,
+0x84,0x03,0x83,0x09,0x83,0x01,0x83,0x01,0x83,0x02,0x83,0x0e,0x84,0x10,0x84,
+0x03,0x83,0x0f,0x84,0x12,0x84,0x15,0x83,0x16,0x84,0x09,0x83,0x06,0x8a,0x39,
+0x83,0x23,0x92,0x12,0x84,0x0d,0x82,0x02,0x83,0x04,0x83,0x02,0x83,0x07,0x84,
+0x02,0x84,0x22,0x83,0x15,0x83,0x11,0x81,0x04,0x82,0x12,0x83,0x58,0x83,0x0f,
+0x83,0x0a,0x83,0x0f,0x83,0x11,0x84,0x1b,0x83,0x06,0x92,0x13,0x83,0x07,0x83,
+0x0a,0x83,0x0d,0x83,0x0f,0x83,0x0a,0x83,0x13,0x83,0x35,0x87,0x11,0x8f,0x10,
+0x87,0x0d,0x83,0x0b,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x05,0x8f,0x07,0x83,
+0x0c,0x83,0x04,0x83,0x0d,0x83,0x04,0x83,0x0d,0x83,0x05,0x83,0x14,0x83,0x13,
+0x83,0x0d,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,0x19,0x83,0x08,0x83,0x05,0x84,
+0x0c,0x83,0x10,0x83,0x05,0x86,0x05,0x83,0x03,0x83,0x07,0x85,0x01,0x83,0x03,
+0x83,0x0e,0x83,0x05,0x83,0x12,0x83,0x0e,0x83,0x05,0x83,0x0b,0x83,0x14,0x83,
+0x0d,0x83,0x0c,0x83,0x0d,0x83,0x09,0x83,0x03,0x83,0x0b,0x82,0x01,0x82,0x04,
+0x86,0x0b,0x83,0x02,0x84,0x11,0x83,0x11,0x84,0x16,0x83,0x14,0x84,0x12,0x83,
+0x55,0x84,0x07,0x83,0x08,0x83,0x0a,0x83,0x07,0x83,0x13,0x83,0x0a,0x83,0x07,
+0x83,0x1a,0x83,0x0e,0x83,0x0a,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,
+0x0c,0x83,0x02,0x84,0x13,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,
+0x83,0x07,0x83,0x0b,0x83,0x07,0x83,0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,0x83,
+0x1c,0x85,0x0c,0x83,0x11,0x83,0x09,0x83,0x0c,0x83,0x02,0x84,0x09,0x83,0x01,
+0x83,0x01,0x83,0x02,0x83,0x0d,0x86,0x10,0x83,0x02,0x84,0x0e,0x84,0x14,0x83,
+0x15,0x83,0x16,0x83,0x0a,0x83,0x07,0x88,0x60,0x92,0x13,0x83,0x0c,0x82,0x03,
+0x83,0x04,0x83,0x02,0x83,0x07,0x85,0x01,0x83,0x23,0x83,0x15,0x83,0x2a,0x83,
+0x58,0x83,0x0f,0x83,0x0a,0x83,0x0f,0x83,0x10,0x84,0x0f,0x83,0x0a,0x83,0x06,
+0x92,0x13,0x83,0x07,0x83,0x0a,0x83,0x0c,0x83,0x10,0x83,0x0a,0x83,0x13,0x83,
+0x37,0x87,0x0f,0x8f,0x0e,0x87,0x0f,0x83,0x0b,0x82,0x04,0x82,0x05,0x82,0x05,
+0x82,0x05,0x8f,0x07,0x83,0x0c,0x83,0x04,0x84,0x0c,0x83,0x04,0x83,0x0c,0x84,
+0x05,0x83,0x14,0x83,0x13,0x84,0x0c,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,0x0d,
+0x83,0x09,0x83,0x08,0x83,0x06,0x84,0x0b,0x83,0x10,0x83,0x05,0x86,0x05,0x83,
+0x03,0x83,0x08,0x84,0x01,0x83,0x03,0x84,0x0c,0x84,0x05,0x83,0x12,0x84,0x0c,
+0x84,0x05,0x83,0x0b,0x83,0x05,0x83,0x0c,0x83,0x0d,0x83,0x0c,0x83,0x0d,0x83,
+0x09,0x84,0x01,0x84,0x0b,0x82,0x01,0x82,0x05,0x84,0x0b,0x84,0x03,0x84,0x10,
+0x83,0x10,0x84,0x17,0x83,0x15,0x83,0x12,0x83,0x54,0x84,0x08,0x83,0x08,0x83,
+0x0a,0x83,0x07,0x83,0x13,0x83,0x0a,0x83,0x07,0x83,0x1a,0x83,0x0e,0x83,0x0a,
+0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x83,0x03,0x84,0x12,0x83,
+0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x07,0x83,0x0b,0x83,0x07,
+0x83,0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,0x83,0x1e,0x84,0x0b,0x83,0x11,0x83,
+0x09,0x83,0x0c,0x83,0x02,0x83,0x0a,0x83,0x01,0x82,0x03,0x83,0x01,0x83,0x0d,
+0x87,0x0f,0x83,0x02,0x83,0x0f,0x83,0x16,0x83,0x14,0x83,0x15,0x83,0x17,0x85,
+0x65,0x82,0x05,0x83,0x0a,0x83,0x0a,0x83,0x0c,0x82,0x03,0x83,0x04,0x83,0x02,
+0x83,0x08,0x88,0x23,0x83,0x15,0x83,0x2a,0x83,0x58,0x82,0x10,0x83,0x0a,0x83,
+0x0f,0x83,0x0f,0x84,0x10,0x83,0x0a,0x83,0x06,0x92,0x07,0x83,0x09,0x83,0x07,
+0x84,0x09,0x83,0x0c,0x83,0x10,0x83,0x0a,0x83,0x12,0x84,0x39,0x87,0x27,0x88,
+0x1f,0x82,0x04,0x82,0x05,0x82,0x04,0x82,0x05,0x83,0x0a,0x84,0x06,0x83,0x0c,
+0x83,0x05,0x83,0x0c,0x83,0x04,0x83,0x0c,0x83,0x06,0x83,0x14,0x83,0x14,0x83,
+0x0c,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,0x0d,0x83,0x09,0x83,0x08,0x83,0x07,
+0x84,0x0a,0x83,0x10,0x83,0x06,0x85,0x05,0x83,0x03,0x83,0x09,0x87,0x04,0x83,
+0x0c,0x83,0x06,0x83,0x13,0x83,0x0c,0x83,0x06,0x83,0x0b,0x83,0x05,0x83,0x0c,
+0x83,0x0d,0x83,0x0c,0x83,0x0c,0x84,0x0a,0x83,0x01,0x83,0x0c,0x85,0x05,0x84,
+0x0a,0x84,0x05,0x84,0x0f,0x83,0x10,0x83,0x18,0x83,0x15,0x83,0x12,0x83,0x54,
+0x83,0x09,0x83,0x08,0x83,0x0a,0x83,0x07,0x83,0x09,0x83,0x07,0x83,0x0a,0x83,
+0x07,0x83,0x1a,0x83,0x0e,0x83,0x0a,0x83,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,
+0x83,0x0c,0x83,0x04,0x84,0x11,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,
+0x09,0x83,0x07,0x84,0x09,0x84,0x07,0x83,0x0a,0x83,0x06,0x83,0x0a,0x83,0x09,
+0x83,0x1f,0x83,0x0b,0x83,0x11,0x83,0x09,0x83,0x0d,0x83,0x01,0x83,0x0a,0x86,
+0x03,0x83,0x01,0x82,0x0d,0x83,0x02,0x83,0x0f,0x84,0x01,0x83,0x0e,0x84,0x16,
+0x83,0x14,0x83,0x15,0x83,0x7f,0x02,0x82,0x05,0x82,0x0b,0x83,0x0a,0x83,0x0b,
+0x82,0x04,0x83,0x04,0x83,0x02,0x83,0x09,0x86,0x24,0x83,0x15,0x83,0x2a,0x83,
+0x57,0x83,0x11,0x83,0x08,0x83,0x10,0x83,0x0f,0x83,0x11,0x84,0x08,0x84,0x11,
+0x83,0x0b,0x83,0x08,0x84,0x08,0x83,0x08,0x84,0x0c,0x83,0x10,0x84,0x08,0x84,
+0x12,0x83,0x3c,0x88,0x22,0x88,0x21,0x82,0x04,0x83,0x03,0x83,0x04,0x82,0x05,
+0x83,0x0b,0x83,0x06,0x83,0x0c,0x83,0x05,0x84,0x0a,0x83,0x05,0x83,0x0b,0x84,
+0x06,0x83,0x14,0x83,0x14,0x84,0x0b,0x83,0x04,0x83,0x0d,0x83,0x0c,0x83,0x0d,
+0x83,0x08,0x84,0x08,0x83,0x07,0x85,0x09,0x83,0x10,0x83,0x07,0x83,0x06,0x83,
+0x03,0x83,0x09,0x87,0x04,0x84,0x0a,0x84,0x06,0x83,0x13,0x84,0x0a,0x84,0x06,
+0x83,0x0b,0x83,0x05,0x84,0x0b,0x83,0x0d,0x83,0x0d,0x83,0x0b,0x83,0x0b,0x83,
+0x01,0x83,0x0c,0x85,0x05,0x84,0x0a,0x84,0x05,0x84,0x0f,0x83,0x0f,0x84,0x18,
+0x83,0x15,0x84,0x11,0x83,0x54,0x83,0x09,0x83,0x08,0x83,0x09,0x83,0x09,0x83,
+0x08,0x83,0x07,0x84,0x09,0x83,0x08,0x83,0x19,0x83,0x0e,0x84,0x09,0x83,0x08,
+0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x83,0x04,0x84,0x11,0x83,0x0a,0x83,
+0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x08,0x83,0x09,0x83,0x08,0x83,0x09,
+0x83,0x07,0x84,0x09,0x83,0x09,0x83,0x13,0x83,0x09,0x83,0x0b,0x83,0x11,0x83,
+0x09,0x83,0x0d,0x86,0x0c,0x85,0x03,0x83,0x01,0x82,0x0c,0x84,0x02,0x84,0x0f,
+0x87,0x0d,0x84,0x17,0x83,0x14,0x83,0x15,0x83,0x7f,0x01,0x83,0x05,0x82,0x0b,
+0x83,0x0a,0x83,0x0a,0x83,0x04,0x83,0x04,0x83,0x02,0x84,0x09,0x84,0x26,0x83,
+0x13,0x83,0x2b,0x83,0x57,0x83,0x11,0x83,0x08,0x83,0x10,0x83,0x0e,0x83,0x13,
+0x83,0x08,0x83,0x12,0x83,0x0b,0x84,0x07,0x83,0x09,0x84,0x07,0x83,0x0d,0x83,
+0x11,0x83,0x08,0x83,0x12,0x84,0x25,0x83,0x16,0x86,0x21,0x86,0x25,0x82,0x03,
+0x83,0x02,0x85,0x02,0x82,0x05,0x84,0x0b,0x83,0x06,0x83,0x0b,0x84,0x06,0x83,
+0x09,0x84,0x05,0x83,0x0a,0x84,0x07,0x83,0x14,0x83,0x15,0x84,0x09,0x84,0x04,
+0x83,0x0d,0x83,0x0c,0x83,0x0d,0x84,0x07,0x83,0x09,0x83,0x08,0x85,0x08,0x83,
+0x10,0x83,0x07,0x83,0x06,0x83,0x03,0x83,0x0a,0x86,0x05,0x83,0x09,0x84,0x07,
+0x83,0x14,0x83,0x09,0x84,0x07,0x83,0x0b,0x83,0x06,0x84,0x09,0x84,0x0d,0x83,
+0x0d,0x84,0x09,0x84,0x0b,0x87,0x0c,0x84,0x07,0x83,0x09,0x84,0x07,0x84,0x0e,
+0x83,0x0e,0x84,0x19,0x83,0x16,0x83,0x11,0x83,0x54,0x83,0x08,0x84,0x08,0x84,
+0x08,0x83,0x09,0x83,0x07,0x84,0x08,0x83,0x08,0x84,0x08,0x83,0x0a,0x81,0x0e,
+0x83,0x0f,0x83,0x08,0x84,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,0x83,
+0x05,0x84,0x10,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x08,
+0x84,0x07,0x84,0x08,0x84,0x07,0x84,0x08,0x84,0x07,0x84,0x09,0x83,0x13,0x84,
+0x08,0x83,0x0b,0x83,0x11,0x84,0x07,0x84,0x0d,0x86,0x0c,0x84,0x05,0x85,0x0c,
+0x83,0x04,0x83,0x0f,0x86,0x0d,0x84,0x18,0x83,0x14,0x83,0x15,0x83,0x7f,0x01,
+0x83,0x04,0x83,0x0c,0x83,0x08,0x84,0x0a,0x82,0x05,0x83,0x04,0x83,0x03,0x84,
+0x06,0x87,0x25,0x83,0x13,0x83,0x2b,0x83,0x57,0x82,0x12,0x85,0x04,0x85,0x10,
+0x83,0x0d,0x83,0x14,0x85,0x04,0x85,0x12,0x83,0x0c,0x84,0x05,0x84,0x0a,0x84,
+0x04,0x85,0x0d,0x83,0x11,0x85,0x04,0x85,0x09,0x81,0x07,0x84,0x26,0x83,0x18,
+0x84,0x21,0x84,0x18,0x83,0x0c,0x82,0x04,0x85,0x01,0x86,0x06,0x83,0x0c,0x84,
+0x05,0x83,0x0a,0x84,0x07,0x85,0x06,0x84,0x06,0x83,0x08,0x85,0x08,0x83,0x14,
+0x83,0x15,0x85,0x07,0x85,0x04,0x83,0x0d,0x83,0x0c,0x83,0x0e,0x84,0x05,0x84,
+0x09,0x83,0x09,0x84,0x08,0x83,0x10,0x83,0x10,0x83,0x03,0x83,0x0b,0x85,0x05,
+0x85,0x06,0x85,0x07,0x83,0x14,0x85,0x06,0x85,0x07,0x83,0x0b,0x83,0x06,0x85,
+0x06,0x85,0x0e,0x83,0x0e,0x85,0x05,0x85,0x0d,0x85,0x0d,0x84,0x07,0x83,0x08,
+0x84,0x09,0x84,0x0d,0x83,0x0d,0x84,0x1a,0x83,0x16,0x83,0x11,0x83,0x54,0x84,
+0x05,0x86,0x08,0x85,0x05,0x85,0x0a,0x84,0x04,0x84,0x09,0x85,0x05,0x85,0x09,
+0x84,0x06,0x83,0x0e,0x83,0x0f,0x85,0x05,0x85,0x08,0x83,0x09,0x83,0x0e,0x83,
+0x17,0x83,0x0c,0x83,0x06,0x84,0x0f,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,
+0x83,0x09,0x83,0x09,0x84,0x05,0x84,0x09,0x85,0x05,0x85,0x08,0x85,0x05,0x85,
+0x09,0x83,0x14,0x84,0x05,0x85,0x0b,0x84,0x05,0x83,0x09,0x84,0x05,0x85,0x0e,
+0x85,0x0c,0x84,0x05,0x85,0x0b,0x84,0x04,0x84,0x0e,0x86,0x0d,0x83,0x19,0x83,
+0x14,0x83,0x15,0x83,0x57,0x83,0x26,0x83,0x04,0x83,0x0c,0x85,0x05,0x84,0x13,
+0x83,0x02,0x83,0x05,0x91,0x24,0x83,0x13,0x83,0x40,0x83,0x2c,0x83,0x12,0x83,
+0x13,0x8c,0x11,0x83,0x0c,0x90,0x09,0x8c,0x13,0x83,0x0c,0x8c,0x0b,0x8c,0x0e,
+0x83,0x12,0x8c,0x0a,0x8c,0x0f,0x83,0x14,0x83,0x1a,0x82,0x21,0x82,0x1a,0x83,
+0x0c,0x83,0x04,0x83,0x03,0x84,0x07,0x83,0x0d,0x83,0x05,0x90,0x09,0x8e,0x06,
+0x8f,0x09,0x90,0x07,0x83,0x16,0x8f,0x05,0x83,0x0d,0x83,0x0c,0x83,0x0e,0x8c,
+0x0a,0x83,0x0a,0x84,0x07,0x90,0x03,0x83,0x10,0x83,0x03,0x83,0x0b,0x85,0x06,
+0x8e,0x08,0x83,0x15,0x90,0x06,0x83,0x0b,0x83,0x07,0x8e,0x0f,0x83,0x0e,0x8e,
+0x0e,0x85,0x0e,0x83,0x07,0x83,0x08,0x84,0x09,0x84,0x0d,0x83,0x0d,0x92,0x0c,
+0x83,0x16,0x84,0x10,0x83,0x55,0x8a,0x01,0x83,0x08,0x8e,0x0b,0x8b,0x0b,0x8e,
+0x09,0x8e,0x0d,0x83,0x10,0x8e,0x08,0x83,0x09,0x83,0x0e,0x83,0x17,0x83,0x0c,
+0x83,0x06,0x84,0x0f,0x83,0x0a,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,
+0x09,0x8d,0x09,0x8e,0x0a,0x8e,0x09,0x83,0x14,0x8d,0x0c,0x8c,0x09,0x8e,0x0e,
+0x84,0x0e,0x83,0x05,0x84,0x0b,0x84,0x06,0x84,0x0e,0x85,0x0c,0x8f,0x0e,0x83,
+0x14,0x83,0x15,0x83,0x57,0x83,0x26,0x82,0x05,0x83,0x0d,0x8c,0x15,0x86,0x07,
+0x8b,0x02,0x84,0x23,0x83,0x13,0x83,0x40,0x83,0x2c,0x83,0x12,0x82,0x15,0x8a,
+0x12,0x83,0x0c,0x90,0x0a,0x8a,0x14,0x83,0x0d,0x8a,0x0e,0x89,0x0f,0x83,0x13,
+0x8a,0x0b,0x8a,0x11,0x83,0x14,0x83,0x59,0x83,0x0d,0x82,0x14,0x84,0x0d,0x83,
+0x05,0x8f,0x0b,0x8b,0x08,0x8e,0x0a,0x90,0x07,0x83,0x18,0x8b,0x07,0x83,0x0d,
+0x83,0x09,0x89,0x0c,0x8a,0x0b,0x83,0x0b,0x84,0x06,0x90,0x03,0x83,0x10,0x83,
+0x03,0x83,0x0c,0x84,0x07,0x8b,0x0a,0x83,0x17,0x8a,0x01,0x84,0x05,0x83,0x0b,
+0x83,0x09,0x8b,0x10,0x83,0x10,0x8b,0x0f,0x84,0x0f,0x82,0x09,0x82,0x07,0x84,
+0x0b,0x84,0x0c,0x83,0x0d,0x92,0x0c,0x83,0x17,0x83,0x10,0x83,0x56,0x88,0x02,
+0x83,0x08,0x83,0x01,0x89,0x0d,0x89,0x0d,0x89,0x01,0x83,0x0b,0x8a,0x0f,0x83,
+0x11,0x89,0x01,0x83,0x08,0x83,0x09,0x83,0x0a,0x8b,0x13,0x83,0x0c,0x83,0x07,
+0x84,0x0a,0x8b,0x06,0x83,0x06,0x83,0x07,0x83,0x05,0x83,0x09,0x83,0x0b,0x89,
+0x0b,0x83,0x01,0x89,0x0c,0x89,0x01,0x83,0x09,0x83,0x16,0x8a,0x0e,0x8a,0x0b,
+0x89,0x01,0x83,0x0e,0x84,0x0e,0x83,0x06,0x83,0x0b,0x84,0x06,0x84,0x0e,0x84,
+0x0d,0x8f,0x0e,0x83,0x14,0x83,0x15,0x83,0x57,0x83,0x25,0x83,0x05,0x82,0x0f,
+0x8a,0x17,0x84,0x0a,0x87,0x04,0x84,0x24,0x83,0x11,0x83,0x41,0x83,0x2c,0x83,
+0x11,0x83,0x17,0x86,0x14,0x83,0x0c,0x90,0x0c,0x86,0x16,0x83,0x0f,0x86,0x11,
+0x86,0x11,0x83,0x15,0x86,0x0e,0x87,0x13,0x83,0x14,0x82,0x5a,0x83,0x0d,0x83,
+0x13,0x83,0x0e,0x84,0x04,0x8d,0x0f,0x87,0x0a,0x8c,0x0c,0x90,0x07,0x83,0x1a,
+0x87,0x09,0x83,0x0d,0x83,0x09,0x89,0x0e,0x86,0x0d,0x83,0x0b,0x85,0x05,0x90,
+0x03,0x83,0x10,0x83,0x03,0x83,0x0d,0x83,0x0a,0x86,0x0c,0x83,0x19,0x86,0x04,
+0x84,0x04,0x83,0x0c,0x83,0x0a,0x87,0x12,0x83,0x12,0x87,0x12,0x83,0x10,0x81,
+0x09,0x81,0x07,0x85,0x0b,0x85,0x0b,0x83,0x0d,0x92,0x0c,0x83,0x17,0x84,0x0f,
+0x83,0x57,0x86,0x03,0x83,0x08,0x82,0x04,0x85,0x11,0x86,0x10,0x85,0x04,0x82,
+0x0d,0x86,0x11,0x83,0x13,0x85,0x03,0x83,0x08,0x83,0x09,0x83,0x0a,0x8b,0x13,
+0x83,0x0c,0x83,0x08,0x84,0x09,0x8b,0x06,0x83,0x06,0x83,0x07,0x83,0x05,0x83,
+0x09,0x83,0x0c,0x87,0x0c,0x83,0x03,0x85,0x10,0x85,0x03,0x83,0x09,0x83,0x17,
+0x87,0x11,0x87,0x0f,0x85,0x03,0x83,0x0f,0x83,0x0e,0x82,0x07,0x83,0x0a,0x84,
+0x08,0x84,0x0d,0x84,0x0d,0x8f,0x0e,0x83,0x14,0x83,0x15,0x83,0x7f,0x1c,0x85,
+0x5a,0x83,0x11,0x83,0x41,0x83,0x40,0x83,0x7f,0x7f,0x16,0x83,0x6b,0x83,0x7f,
+0x7f,0x7f,0x17,0x83,0x7f,0x5b,0x83,0x18,0x83,0x0f,0x83,0x25,0x90,0x7f,0x36,
+0x83,0x3f,0x83,0x7e,0x83,0x20,0x83,0x7f,0x30,0x83,0x2b,0x83,0x14,0x83,0x15,
+0x83,0x7f,0x1d,0x83,0x5c,0x83,0x0f,0x83,0x42,0x82,0x41,0x82,0x7f,0x7f,0x17,
+0x82,0x6d,0x84,0x06,0x81,0x7f,0x7f,0x7f,0x0f,0x81,0x7f,0x5c,0x83,0x18,0x83,
+0x0f,0x83,0x25,0x90,0x7f,0x36,0x83,0x3f,0x83,0x7e,0x83,0x20,0x83,0x7f,0x30,
+0x83,0x2b,0x84,0x13,0x83,0x14,0x83,0x7f,0x1e,0x83,0x5c,0x83,0x0f,0x83,0x41,
+0x83,0x7f,0x7f,0x5a,0x82,0x6e,0x8b,0x7f,0x7f,0x7f,0x7f,0x6b,0x83,0x2a,0x83,
+0x25,0x90,0x7f,0x36,0x83,0x3f,0x83,0x7e,0x83,0x20,0x83,0x7f,0x2f,0x84,0x2c,
+0x83,0x13,0x83,0x14,0x83,0x7f,0x1e,0x83,0x5d,0x83,0x0d,0x83,0x42,0x82,0x7f,
+0x7f,0x7f,0x4e,0x86,0x7f,0x7f,0x7f,0x7f,0x6e,0x87,0x23,0x86,0x7f,0x6a,0x83,
+0x3f,0x84,0x7e,0x83,0x20,0x83,0x7f,0x2f,0x83,0x2d,0x84,0x12,0x83,0x13,0x84,
+0x7f,0x7f,0x82,0x0d,0x82,0x43,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x43,0x87,0x23,0x86,0x7f,0x60,0x82,0x07,0x84,0x3e,0x84,0x7f,0x83,0x20,0x83,
+0x7f,0x2e,0x84,0x2e,0x84,0x11,0x83,0x11,0x85,0x7f,0x7f,0x01,0x83,0x0b,0x83,
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x09,0x87,0x23,0x86,0x7f,0x60,
+0x8c,0x3b,0x88,0x7f,0x83,0x20,0x83,0x7f,0x2b,0x86,0x31,0x82,0x11,0x83,0x12,
+0x82,0x7f,0x7f,0x04,0x83,0x09,0x83,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x7f,0x7f,0x7f,0x1b,0x8b,0x3c,0x87,0x7f,0x01,0x83,0x20,0x83,0x7f,0x2b,0x86,
+0x7f,0x7f,0x60,0x81,0x0b,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x7f,0x7f,0x1e,0x87,0x3e,0x85,0x7f,0x03,0x83,0x20,0x83,0x7f,0x2b,0x84,0x7f,
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,
+0x7f,0x7f,0x4a,
+0x00,
+  }
+};
diff --git a/minuitwrp/Android.bp b/minuitwrp/Android.bp
new file mode 100644
index 0000000..7a56103
--- /dev/null
+++ b/minuitwrp/Android.bp
@@ -0,0 +1,60 @@
+bootstrap_go_package {
+    name: "soong-libminuitwrp_defaults",
+    pkgPath: "bootable/recovery/minuitwrp",
+    deps: [
+        "soong",
+        "soong-android",
+        "soong-cc"
+    ],
+    srcs: [
+        "libminuitwrp_defaults.go",
+        "../soong/makevars.go"
+    ],
+    pluginFor: ["soong_build"]
+}
+
+libminuitwrp_defaults {
+    name: "libminuitwrp_defaults"
+}
+
+cc_library_shared {
+    name: "libminuitwrp",
+    defaults: ["libminuitwrp_defaults"],
+    cflags: [
+        "-DTWRES=\"/twres/\""
+    ],
+    include_dirs: [
+        "external/libpng",
+        "external/zlib",
+        "system/core/include",
+        "external/freetype/include",
+        "external/libcxx/include",
+        "bootable/recovery/twrpinstall/include",
+        "bootable/recovery/libpixelflinger/include",
+        "bootable/recovery/minuitwrp/include",
+        "bootable/recovery/gui/include"
+    ],
+    srcs: [
+        "graphics.cpp",
+        "graphics_fbdev.cpp",
+        "resources.cpp",
+        "truetype.cpp",
+        "graphics_utils.cpp",
+        "events.cpp"
+    ],
+    shared_libs: [
+        "libft2",
+        "libz",
+        "libc",
+        "libcutils",
+        "libpng",
+        "libutils",
+        "libc++",
+        "libcutils",
+        "liblog",
+        "libbase",
+        "libsync",
+	"libbinder_ndk"
+    ],
+    static_libs: ["libpixelflinger_twrp"]
+}
diff --git a/minuitwrp/events.cpp b/minuitwrp/events.cpp
new file mode 100644
index 0000000..80794a1
--- /dev/null
+++ b/minuitwrp/events.cpp
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/poll.h>
+#include <limits.h>
+#include <linux/input.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <fstream>
+
+#ifdef USE_QTI_HAPTICS
+#include <android/hardware/vibrator/1.2/IVibrator.h>
+#endif
+
+#ifdef USE_QTI_AIDL_HAPTICS
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+#include <android/binder_manager.h>
+using ::aidl::android::hardware::vibrator::IVibrator;
+static const std::string kVibratorInstance = std::string(IVibrator::descriptor) + "/default";
+#endif
+
+#include "common.h"
+
+#include "minuitwrp/minui.h"
+
+//#define _EVENT_LOGGING
+
+#define MAX_DEVICES         32
+
+#define VIBRATOR_TIMEOUT_FILE	"/sys/class/timed_output/vibrator/enable"
+#define VIBRATOR_TIME_MS    50
+
+#define LEDS_HAPTICS_DURATION_FILE	"/sys/class/leds/vibrator/duration"
+#define LEDS_HAPTICS_ACTIVATE_FILE	"/sys/class/leds/vibrator/activate"
+
+#ifndef SYN_REPORT
+#define SYN_REPORT          0x00
+#endif
+#ifndef SYN_CONFIG
+#define SYN_CONFIG          0x01
+#endif
+#ifndef SYN_MT_REPORT
+#define SYN_MT_REPORT       0x02
+#endif
+
+#define ABS_MT_POSITION     0x2a /* Group a set of X and Y */
+#define ABS_MT_AMPLITUDE    0x2b /* Group a set of Z and W */
+#define ABS_MT_SLOT         0x2f
+#define ABS_MT_TOUCH_MAJOR  0x30
+#define ABS_MT_TOUCH_MINOR  0x31
+#define ABS_MT_WIDTH_MAJOR  0x32
+#define ABS_MT_WIDTH_MINOR  0x33
+#define ABS_MT_ORIENTATION  0x34
+#define ABS_MT_POSITION_X   0x35
+#define ABS_MT_POSITION_Y   0x36
+#define ABS_MT_TOOL_TYPE    0x37
+#define ABS_MT_BLOB_ID      0x38
+#define ABS_MT_TRACKING_ID  0x39
+#define ABS_MT_PRESSURE     0x3a
+#define ABS_MT_DISTANCE     0x3b
+
+enum {
+    DOWN_NOT,
+    DOWN_SENT,
+    DOWN_RELEASED,
+};
+
+struct virtualkey {
+    int scancode;
+    int centerx, centery;
+    int width, height;
+};
+
+struct position {
+    int x, y;
+    int synced;
+    struct input_absinfo xi, yi;
+};
+
+struct ev {
+    struct pollfd *fd;
+
+    struct virtualkey *vks;
+    int vk_count;
+
+    char deviceName[64];
+
+    int ignored;
+
+    struct position p, mt_p;
+    int down;
+};
+
+static struct pollfd ev_fds[MAX_DEVICES];
+static struct ev evs[MAX_DEVICES];
+static unsigned ev_count = 0;
+static struct timeval lastInputStat;
+static time_t lastInputMTime;
+static int has_mouse = 0;
+
+static inline int ABS(int x) {
+    return x<0?-x:x;
+}
+
+int write_to_file(const std::string& fn, const std::string& line) {
+	FILE *file;
+	file = fopen(fn.c_str(), "w");
+	if (file != NULL) {
+		fwrite(line.c_str(), line.size(), 1, file);
+		fclose(file);
+		return 0;
+	}
+	LOGI("Cannot find file %s\n", fn.c_str());
+	return -1;
+}
+
+#ifndef TW_NO_HAPTICS
+#ifndef TW_HAPTICS_TSPDRV
+int vibrate(int timeout_ms)
+{
+    if (timeout_ms > 10000) timeout_ms = 1000;
+    char tout[6];
+    sprintf(tout, "%i", timeout_ms);
+
+#ifdef USE_QTI_HAPTICS
+    android::sp<android::hardware::vibrator::V1_2::IVibrator> vib = android::hardware::vibrator::V1_2::IVibrator::getService();
+    if (vib != nullptr) {
+        vib->on((uint32_t)timeout_ms);
+    }
+#elif defined(USE_QTI_AIDL_HAPTICS)
+    std::shared_ptr<IVibrator> vib = IVibrator::fromBinder(ndk::SpAIBinder(AServiceManager_getService(kVibratorInstance.c_str())));
+    if (vib != nullptr) {
+        vib->on((uint32_t)timeout_ms, nullptr);
+    }
+#elif defined(USE_SAMSUNG_HAPTICS)
+    /* Newer Samsung devices have duration file only
+     0 in VIBRATOR_TIMEOUT_FILE means no vibration
+     Anything else is the vibration running for X milliseconds */
+    if (std::ifstream(VIBRATOR_TIMEOUT_FILE).good()) {
+        write_to_file(VIBRATOR_TIMEOUT_FILE, tout);
+    }
+#else
+    if (std::ifstream(LEDS_HAPTICS_ACTIVATE_FILE).good()) {
+        write_to_file(LEDS_HAPTICS_DURATION_FILE, tout);
+        write_to_file(LEDS_HAPTICS_ACTIVATE_FILE, "1");
+    } else
+        write_to_file(VIBRATOR_TIMEOUT_FILE, tout);
+#endif
+    return 0;
+}
+#endif
+#endif
+
+/* Returns empty tokens */
+static char *vk_strtok_r(char *str, const char *delim, char **save_str)
+{
+    if(!str)
+    {
+        if(!*save_str)
+            return NULL;
+
+        str = (*save_str) + 1;
+    }
+    *save_str = strpbrk(str, delim);
+
+    if (*save_str)
+        **save_str = '\0';
+
+    return str;
+}
+
+static int vk_init(struct ev *e)
+{
+    char vk_path[PATH_MAX] = "/sys/board_properties/virtualkeys.";
+    char vks[2048], *ts = NULL;
+    ssize_t len;
+    int vk_fd;
+    int i;
+
+    e->vk_count = 0;
+
+    len = strlen(vk_path);
+    len = ioctl(e->fd->fd, EVIOCGNAME(sizeof(e->deviceName)), e->deviceName);
+    if (len <= 0)
+    {
+        LOGE("Unable to query event object.\n");
+        return -1;
+    }
+#ifdef _EVENT_LOGGING
+    printf("Event object: %s\n", e->deviceName);
+#endif
+
+#ifdef WHITELIST_INPUT
+    if (strcmp(e->deviceName, EXPAND(WHITELIST_INPUT)) != 0)
+    {
+        e->ignored = 1;
+    }
+#else
+#ifndef TW_INPUT_BLACKLIST
+    // Blacklist these "input" devices, use TW_INPUT_BLACKLIST := "accelerometer\x0atest1\x0atest2" using the \x0a as a separator between input devices
+    if (strcmp(e->deviceName, "bma250") == 0 || strcmp(e->deviceName, "bma150") == 0)
+    {
+        LOGI("Blacklisting input device: %s\n", e->deviceName);
+        e->ignored = 1;
+    }
+#else
+    char* bl = strdup(EXPAND(TW_INPUT_BLACKLIST));
+    char* blacklist = strtok(bl, "\n");
+
+    while (blacklist != NULL) {
+        if (strcmp(e->deviceName, blacklist) == 0) {
+            LOGI("Blacklisting input device: %s\n", blacklist);
+            e->ignored = 1;
+        }
+        blacklist = strtok(NULL, "\n");
+    }
+    free(bl);
+#endif
+#endif
+
+    strcat(vk_path, e->deviceName);
+
+    // Some devices split the keys from the touchscreen
+    e->vk_count = 0;
+    vk_fd = open(vk_path, O_RDONLY);
+    if (vk_fd >= 0)
+    {
+        len = read(vk_fd, vks, sizeof(vks)-1);
+        close(vk_fd);
+        if (len <= 0)
+            return -1;
+
+        vks[len] = '\0';
+
+        /* Parse a line like:
+            keytype:keycode:centerx:centery:width:height:keytype2:keycode2:centerx2:...
+        */
+        for (ts = vks, e->vk_count = 1; *ts; ++ts) {
+            if (*ts == ':')
+                ++e->vk_count;
+        }
+
+        if (e->vk_count % 6) {
+            LOGI("minui: %s is %d %% 6\n", vk_path, e->vk_count % 6);
+        }
+        e->vk_count /= 6;
+        if (e->vk_count <= 0)
+            return -1;
+
+        e->down = DOWN_NOT;
+    }
+
+    ioctl(e->fd->fd, EVIOCGABS(ABS_X), &e->p.xi);
+    ioctl(e->fd->fd, EVIOCGABS(ABS_Y), &e->p.yi);
+    e->p.synced = 0;
+#ifdef _EVENT_LOGGING
+    printf("EV: ST minX: %d  maxX: %d  minY: %d  maxY: %d\n", e->p.xi.minimum, e->p.xi.maximum, e->p.yi.minimum, e->p.yi.maximum);
+#endif
+
+    ioctl(e->fd->fd, EVIOCGABS(ABS_MT_POSITION_X), &e->mt_p.xi);
+    ioctl(e->fd->fd, EVIOCGABS(ABS_MT_POSITION_Y), &e->mt_p.yi);
+    e->mt_p.synced = 0;
+#ifdef _EVENT_LOGGING
+    printf("EV: MT minX: %d  maxX: %d  minY: %d  maxY: %d\n", e->mt_p.xi.minimum, e->mt_p.xi.maximum, e->mt_p.yi.minimum, e->mt_p.yi.maximum);
+#endif
+
+    e->vks = (virtualkey *)malloc(sizeof(*e->vks) * e->vk_count);
+
+    for (i = 0; i < e->vk_count; ++i) {
+        char *token[6];
+        int j;
+
+        for (j = 0; j < 6; ++j) {
+            token[j] = vk_strtok_r((i||j)?NULL:vks, ":", &ts);
+        }
+
+        if (strcmp(token[0], "0x01") != 0) {
+            /* Java does string compare, so we do too. */
+            LOGI("minui: %s: ignoring unknown virtual key type %s\n", vk_path, token[0]);
+            continue;
+        }
+
+        e->vks[i].scancode = strtol(token[1], NULL, 0);
+        e->vks[i].centerx = strtol(token[2], NULL, 0);
+        e->vks[i].centery = strtol(token[3], NULL, 0);
+        e->vks[i].width = strtol(token[4], NULL, 0);
+        e->vks[i].height = strtol(token[5], NULL, 0);
+    }
+
+    return 0;
+}
+
+#define BITS_PER_LONG (sizeof(long) * 8)
+#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
+#define OFF(x)  ((x)%BITS_PER_LONG)
+#define LONG(x) ((x)/BITS_PER_LONG)
+#define test_bit(bit, array)	((array[LONG(bit)] >> OFF(bit)) & 1)
+
+// Check for EV_REL (REL_X and REL_Y) and, because touchscreens can have those too,
+// check also for EV_KEY (BTN_LEFT and BTN_RIGHT)
+static void check_mouse(int fd, const char* deviceName)
+{
+	if(has_mouse)
+		return;
+
+	unsigned long bit[EV_MAX][NBITS(KEY_MAX)];
+	memset(bit, 0, sizeof(bit));
+	ioctl(fd, EVIOCGBIT(0, EV_MAX), bit[0]);
+
+	if(!test_bit(EV_REL, bit[0]) || !test_bit(EV_KEY, bit[0]))
+		return;
+
+	ioctl(fd, EVIOCGBIT(EV_REL, KEY_MAX), bit[EV_REL]);
+	if(!test_bit(REL_X, bit[EV_REL]) || !test_bit(REL_Y, bit[EV_REL]))
+		return;
+
+	ioctl(fd, EVIOCGBIT(EV_KEY, KEY_MAX), bit[EV_KEY]);
+	if(!test_bit(BTN_LEFT, bit[EV_KEY]) || !test_bit(BTN_RIGHT, bit[EV_KEY]))
+		return;
+
+	LOGI("Found mouse '%s'\n", deviceName);
+	has_mouse = 1;
+}
+
+int ev_has_mouse(void)
+{
+	return has_mouse;
+}
+
+int ev_init(void)
+{
+    DIR *dir;
+    struct dirent *de;
+    int fd;
+
+    has_mouse = 0;
+
+	dir = opendir("/dev/input");
+    if(dir != 0) {
+        while((de = readdir(dir))) {
+#ifdef _EVENT_LOGGING
+            fprintf(stderr,"/dev/input/%s\n", de->d_name);
+#endif
+            if(strncmp(de->d_name,"event",5)) continue;
+            fd = openat(dirfd(dir), de->d_name, O_RDONLY);
+            if(fd < 0) continue;
+
+			ev_fds[ev_count].fd = fd;
+            ev_fds[ev_count].events = POLLIN;
+            evs[ev_count].fd = &ev_fds[ev_count];
+
+            /* Load virtualkeys if there are any */
+            vk_init(&evs[ev_count]);
+
+            if (!evs[ev_count].ignored)
+                check_mouse(fd, evs[ev_count].deviceName);
+
+            ev_count++;
+            if(ev_count == MAX_DEVICES) break;
+        }
+        closedir(dir);
+    }
+
+    struct stat st;
+    if(stat("/dev/input", &st) >= 0)
+        lastInputMTime = st.st_mtime;
+    gettimeofday(&lastInputStat, NULL);
+
+    return 0;
+}
+
+void ev_exit(void)
+{
+	while (ev_count-- > 0) {
+		if (evs[ev_count].vk_count) {
+			free(evs[ev_count].vks);
+			evs[ev_count].vk_count = 0;
+		}
+		close(ev_fds[ev_count].fd);
+	}
+	ev_count = 0;
+}
+
+/*static int vk_inside_display(__s32 value, struct input_absinfo *info, int screen_size)
+{
+    int screen_pos;
+
+    if (info->minimum == info->maximum)
+        return 0;
+
+    screen_pos = (value - info->minimum) * (screen_size - 1) / (info->maximum - info->minimum);
+    return (screen_pos >= 0 && screen_pos < screen_size);
+}*/
+
+static int vk_tp_to_screen(struct position *p, int *x, int *y)
+{
+    if (p->xi.minimum == p->xi.maximum || p->yi.minimum == p->yi.maximum)
+    {
+        // In this case, we assume the screen dimensions are the same.
+        *x = p->x;
+        *y = p->y;
+        return 0;
+    }
+
+#ifdef _EVENT_LOGGING
+    printf("EV: p->x=%d  x-range=%d,%d  fb-width=%d\n", p->x, p->xi.minimum, p->xi.maximum, gr_fb_width());
+#endif
+
+#ifndef RECOVERY_TOUCHSCREEN_SWAP_XY
+    int fb_width = gr_fb_width();
+    int fb_height = gr_fb_height();
+#else
+    // We need to swap the scaling sizes, too
+    int fb_width = gr_fb_height();
+    int fb_height = gr_fb_width();
+#endif
+
+    *x = (p->x - p->xi.minimum) * (fb_width - 1) / (p->xi.maximum - p->xi.minimum);
+    *y = (p->y - p->yi.minimum) * (fb_height - 1) / (p->yi.maximum - p->yi.minimum);
+
+    if (*x >= 0 && *x < fb_width &&
+        *y >= 0 && *y < fb_height)
+    {
+        return 0;
+    }
+
+    return 1;
+}
+
+/* Translate a virtual key in to a real key event, if needed */
+/* Returns non-zero when the event should be consumed */
+static int vk_modify(struct ev *e, struct input_event *ev)
+{
+    static int downX = -1, downY = -1;
+    static int discard = 0;
+    static int last_virt_key = 0;
+    static int lastWasSynReport = 0;
+    static int touchReleaseOnNextSynReport = 0;
+	static int use_tracking_id_negative_as_touch_release = 0; // On some devices, type: 3  code: 39  value: -1, aka EV_ABS ABS_MT_TRACKING_ID -1 indicates a true touch release
+    int i;
+    int x, y;
+
+    // This is used to ditch useless event handlers, like an accelerometer
+    if (e->ignored)     return 1;
+
+    if (ev->type == EV_REL && ev->code == REL_Z)
+    {
+        // This appears to be an accelerometer or another strange input device. It's not the touchscreen.
+#ifdef _EVENT_LOGGING
+        printf("EV: Device disabled due to non-touchscreen messages.\n");
+#endif
+        e->ignored = 1;
+        return 1;
+    }
+
+#ifdef _EVENT_LOGGING
+    printf("EV: %s => type: %x  code: %x  value: %d\n", e->deviceName, ev->type, ev->code, ev->value);
+#endif
+
+	// Handle keyboard events, value of 1 indicates key down, 0 indicates key up
+	if (ev->type == EV_KEY) {
+		return 0;
+	}
+
+    if (ev->type == EV_ABS) {
+        switch (ev->code) {
+
+        case ABS_X: //00
+            e->p.synced |= 0x01;
+            e->p.x = ev->value;
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS  ABS_X  %d\n", e->deviceName, ev->value);
+#endif
+            break;
+
+        case ABS_Y: //01
+            e->p.synced |= 0x02;
+            e->p.y = ev->value;
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS  ABS_Y  %d\n", e->deviceName, ev->value);
+#endif
+            break;
+
+        case ABS_MT_POSITION: //2a
+            e->mt_p.synced = 0x03;
+            if (ev->value == (1 << 31))
+            {
+#ifndef TW_IGNORE_MT_POSITION_0
+                e->mt_p.x = 0;
+                e->mt_p.y = 0;
+                lastWasSynReport = 1;
+#endif
+#ifdef _EVENT_LOGGING
+#ifndef TW_IGNORE_MT_POSITION_0
+                printf("EV: %s => EV_ABS  ABS_MT_POSITION  %d, set x and y to 0 and lastWasSynReport to 1\n", e->deviceName, ev->value);
+#else
+                printf("Ignoring ABS_MT_POSITION 0\n", e->deviceName, ev->value);
+#endif
+#endif
+            }
+            else
+            {
+                lastWasSynReport = 0;
+                e->mt_p.x = (ev->value & 0x7FFF0000) >> 16;
+                e->mt_p.y = (ev->value & 0xFFFF);
+#ifdef _EVENT_LOGGING
+                printf("EV: %s => EV_ABS  ABS_MT_POSITION  %d, set x: %d and y: %d and lastWasSynReport to 0\n", e->deviceName, ev->value, (ev->value & 0x7FFF0000) >> 16, (ev->value & 0xFFFF));
+#endif
+            }
+            break;
+
+        case ABS_MT_TOUCH_MAJOR: //30
+            if (ev->value == 0)
+            {
+#ifndef TW_IGNORE_MAJOR_AXIS_0
+                // We're in a touch release, although some devices will still send positions as well
+                e->mt_p.x = 0;
+                e->mt_p.y = 0;
+                touchReleaseOnNextSynReport = 1;
+#endif
+            }
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS  ABS_MT_TOUCH_MAJOR  %d\n", e->deviceName, ev->value);
+#endif
+            break;
+
+		case ABS_MT_PRESSURE: //3a
+                    if (ev->value == 0)
+            {
+                // We're in a touch release, although some devices will still send positions as well
+                e->mt_p.x = 0;
+                e->mt_p.y = 0;
+                touchReleaseOnNextSynReport = 1;
+            }
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS  ABS_MT_PRESSURE  %d\n", e->deviceName, ev->value);
+#endif
+            break;
+
+		case ABS_MT_POSITION_X: //35
+            e->mt_p.synced |= 0x01;
+            e->mt_p.x = ev->value;
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS  ABS_MT_POSITION_X  %d\n", e->deviceName, ev->value);
+#endif
+            break;
+
+        case ABS_MT_POSITION_Y: //36
+            e->mt_p.synced |= 0x02;
+            e->mt_p.y = ev->value;
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS  ABS_MT_POSITION_Y  %d\n", e->deviceName, ev->value);
+#endif
+            break;
+
+        case ABS_MT_TOUCH_MINOR: //31
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS ABS_MT_TOUCH_MINOR %d\n", e->deviceName, ev->value);
+#endif
+            break;
+
+        case ABS_MT_WIDTH_MAJOR: //32
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS ABS_MT_WIDTH_MAJOR %d\n", e->deviceName, ev->value);
+#endif
+            break;
+
+        case ABS_MT_WIDTH_MINOR: //33
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS ABS_MT_WIDTH_MINOR %d\n", e->deviceName, ev->value);
+#endif
+            break;
+
+        case ABS_MT_TRACKING_ID: //39
+#ifdef TW_IGNORE_ABS_MT_TRACKING_ID
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS ABS_MT_TRACKING_ID %d ignored\n", e->deviceName, ev->value);
+#endif
+            return 1;
+#endif
+            if (ev->value < 0) {
+                e->mt_p.x = 0;
+                e->mt_p.y = 0;
+                touchReleaseOnNextSynReport = 2;
+                use_tracking_id_negative_as_touch_release = 1;
+#ifdef _EVENT_LOGGING
+                if (use_tracking_id_negative_as_touch_release)
+                    printf("using ABS_MT_TRACKING_ID value -1 to indicate touch releases\n");
+#endif
+            }
+#ifdef _EVENT_LOGGING
+            printf("EV: %s => EV_ABS ABS_MT_TRACKING_ID %d\n", e->deviceName, ev->value);
+#endif
+            break;
+
+#ifdef _EVENT_LOGGING
+        // These are for touch logging purposes only
+        case ABS_MT_ORIENTATION: //34
+            printf("EV: %s => EV_ABS ABS_MT_ORIENTATION %d\n", e->deviceName, ev->value);
+			return 1;
+            break;
+
+		case ABS_MT_TOOL_TYPE: //37
+            LOGI("EV: %s => EV_ABS ABS_MT_TOOL_TYPE %d\n", e->deviceName, ev->value);
+			return 1;
+            break;
+
+        case ABS_MT_BLOB_ID: //38
+            printf("EV: %s => EV_ABS ABS_MT_BLOB_ID %d\n", e->deviceName, ev->value);
+			return 1;
+            break;
+
+		case ABS_MT_DISTANCE: //3b
+            printf("EV: %s => EV_ABS ABS_MT_DISTANCE %d\n", e->deviceName, ev->value);
+			return 1;
+            break;
+        case ABS_MT_SLOT:
+            printf("EV: %s => ABS_MT_SLOT %d\n", e->deviceName, ev->value);
+			return 1;
+			break;
+#endif
+
+        default:
+            // This is an unhandled message, just skip it
+            return 1;
+        }
+
+        if (ev->code != ABS_MT_POSITION)
+        {
+            lastWasSynReport = 0;
+            return 1;
+        }
+    }
+
+    // Check if we should ignore the message
+    if (ev->code != ABS_MT_POSITION && (ev->type != EV_SYN || (ev->code != SYN_REPORT && ev->code != SYN_MT_REPORT)))
+    {
+        lastWasSynReport = 0;
+        return 0;
+    }
+
+#ifdef _EVENT_LOGGING
+    if (ev->type == EV_SYN && ev->code == SYN_REPORT)
+        printf("EV: %s => EV_SYN  SYN_REPORT\n", e->deviceName);
+    if (ev->type == EV_SYN && ev->code == SYN_MT_REPORT)
+        printf("EV: %s => EV_SYN  SYN_MT_REPORT\n", e->deviceName);
+#endif
+
+    // Discard the MT versions
+    if (ev->code == SYN_MT_REPORT)      return 0;
+
+    if (((lastWasSynReport == 1 || touchReleaseOnNextSynReport == 1) && !use_tracking_id_negative_as_touch_release) || (use_tracking_id_negative_as_touch_release && touchReleaseOnNextSynReport == 2))
+    {
+        // Reset the value
+        touchReleaseOnNextSynReport = 0;
+
+        // We are a finger-up state
+        if (!discard)
+        {
+            // Report the key up
+            ev->type = EV_ABS;
+            ev->code = 0;
+            ev->value = (downX << 16) | downY;
+        }
+        downX = -1;
+        downY = -1;
+        if (discard)
+        {
+            discard = 0;
+
+            // Send the keyUp event
+            ev->type = EV_KEY;
+            ev->code = last_virt_key;
+            ev->value = 0;
+        }
+        return 0;
+    }
+    lastWasSynReport = 1;
+
+    // Retrieve where the x,y position is
+    if (e->p.synced & 0x03)
+    {
+        vk_tp_to_screen(&e->p, &x, &y);
+    }
+    else if (e->mt_p.synced & 0x03)
+    {
+        vk_tp_to_screen(&e->mt_p, &x, &y);
+    }
+    else
+    {
+        // We don't have useful information to convey
+        return 1;
+    }
+
+#ifdef RECOVERY_TOUCHSCREEN_SWAP_XY
+    x ^= y;
+    y ^= x;
+    x ^= y;
+#endif
+#ifdef RECOVERY_TOUCHSCREEN_FLIP_X
+    x = gr_fb_width() - x;
+#endif
+#ifdef RECOVERY_TOUCHSCREEN_FLIP_Y
+    y = gr_fb_height() - y;
+#endif
+
+#ifdef _EVENT_LOGGING
+    printf("EV: x: %d  y: %d\n", x, y);
+#endif
+
+    // Clear the current sync states
+    e->p.synced = e->mt_p.synced = 0;
+
+    // If we have nothing useful to report, skip it
+    if (x == -1 || y == -1)     return 1;
+
+    // Special case, we'll ignore touches on 0,0 because it usually means
+    // that we received extra data after our last sync and x and y were
+    // reset to 0. We should not be using 0,0 anyway.
+    if (x == 0 && y == 0)
+        return 1;
+
+    // On first touch, see if we're at a virtual key
+    if (downX == -1)
+    {
+        // Attempt mapping to virtual key
+        for (i = 0; i < e->vk_count; ++i)
+        {
+            int xd = ABS(e->vks[i].centerx - x);
+            int yd = ABS(e->vks[i].centery - y);
+
+            if (xd < e->vks[i].width/2 && yd < e->vks[i].height/2)
+            {
+                ev->type = EV_KEY;
+                ev->code = e->vks[i].scancode;
+                ev->value = 1;
+
+                last_virt_key = e->vks[i].scancode;
+
+#ifndef TW_NO_HAPTICS
+                vibrate(VIBRATOR_TIME_MS);
+#endif
+
+                // Mark that all further movement until lift is discard,
+                // and make sure we don't come back into this area
+                discard = 1;
+                downX = 0;
+                return 0;
+            }
+        }
+    }
+
+    // If we were originally a button press, discard this event
+    if (discard)
+    {
+        return 1;
+    }
+
+    // Record where we started the touch for deciding if this is a key or a scroll
+    downX = x;
+    downY = y;
+
+    ev->type = EV_ABS;
+    ev->code = 1;
+    ev->value = (x << 16) | y;
+    return 0;
+}
+
+int ev_get(struct input_event *ev, int timeout_ms)
+{
+    int r;
+    unsigned n;
+    struct timeval curr;
+
+    gettimeofday(&curr, NULL);
+    if(curr.tv_sec - lastInputStat.tv_sec >= 2)
+    {
+        struct stat st;
+        stat("/dev/input", &st);
+        if (st.st_mtime > lastInputMTime)
+        {
+            LOGI("Reloading input devices\n");
+            ev_exit();
+            ev_init();
+            lastInputMTime = st.st_mtime;
+        }
+        lastInputStat = curr;
+    }
+
+    r = poll(ev_fds, ev_count, timeout_ms);
+
+    if(r > 0) {
+        for(n = 0; n < ev_count; n++) {
+            if(ev_fds[n].revents & POLLIN) {
+                r = read(ev_fds[n].fd, ev, sizeof(*ev));
+                if(r == sizeof(*ev)) {
+                    if (!vk_modify(&evs[n], ev))
+                        return 0;
+                }
+            }
+        }
+        return -1;
+    }
+
+    return -2;
+}
+
+int ev_wait(int timeout __unused)
+{
+    return -1;
+}
+
+void ev_dispatch(void)
+{
+    return;
+}
+
+int ev_get_input(int fd __unused, short revents __unused, struct input_event *ev __unused)
+{
+    return -1;
+}
diff --git a/minuitwrp/graphics.cpp b/minuitwrp/graphics.cpp
new file mode 100644
index 0000000..b8b4542
--- /dev/null
+++ b/minuitwrp/graphics.cpp
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include <time.h>
+
+#include <cutils/properties.h>
+#include <pixelflinger/pixelflinger.h>
+#include "gui/placement.h"
+#include "minuitwrp/minui.h"
+#include "graphics.h"
+// For std::min and std::max
+#include <algorithm>
+#include "minuitwrp/truetype.hpp"
+
+struct GRFont {
+    GRSurface* texture;
+    int cwidth;
+    int cheight;
+};
+
+static minui_backend* gr_backend = NULL;
+
+static int overscan_percent = OVERSCAN_PERCENT;
+static int overscan_offset_x = 0;
+static int overscan_offset_y = 0;
+
+static unsigned char gr_current_r = 255;
+static unsigned char gr_current_g = 255;
+static unsigned char gr_current_b = 255;
+
+GRSurface* gr_draw = NULL;
+
+static GGLContext *gr_context = 0;
+GGLSurface gr_mem_surface;
+static int gr_is_curr_clr_opaque = 0;
+
+unsigned int gr_rotation = 0;
+
+int gr_textEx_scaleW(int x, int y, const char *s, void* pFont, int max_width, int placement, int scale)
+{
+    GGLContext *gl = gr_context;
+    void* vfont = pFont;
+    GRFont *font = (GRFont*) pFont;
+    int y_scale = 0, measured_width, measured_height, new_height;
+
+    if (!s || strlen(s) == 0 || !font)
+        return 0;
+
+    measured_height = twrpTruetype::gr_ttf_getMaxFontHeight(font);
+
+    if (scale) {
+        measured_width = twrpTruetype::gr_ttf_measureEx(s, vfont);
+        if (measured_width > max_width) {
+            // Adjust font size down until the text fits
+            void *new_font = twrpTruetype::gr_ttf_scaleFont(vfont, max_width, measured_width);
+            if (!new_font) {
+                printf("gr_textEx_scaleW new_font is NULL\n");
+                return 0;
+            }
+            measured_width = twrpTruetype::gr_ttf_measureEx(s, new_font);
+            // These next 2 lines adjust the y point based on the new font's height
+            new_height = twrpTruetype::gr_ttf_getMaxFontHeight(new_font);
+            y_scale = (measured_height - new_height) / 2;
+            vfont = new_font;
+        }
+    } else
+        measured_width = twrpTruetype::gr_ttf_measureEx(s, vfont);
+
+    int x_adj = measured_width;
+    if (measured_width > max_width)
+        x_adj = max_width;
+
+    if (placement != TOP_LEFT && placement != BOTTOM_LEFT && placement != TEXT_ONLY_RIGHT) {
+        if (placement == CENTER || placement == CENTER_X_ONLY)
+            x -= (x_adj / 2);
+        else
+            x -= x_adj;
+    }
+
+    if (placement != TOP_LEFT && placement != TOP_RIGHT) {
+        if (placement == CENTER || placement == TEXT_ONLY_RIGHT)
+            y -= (measured_height / 2);
+        else if (placement == BOTTOM_LEFT || placement == BOTTOM_RIGHT)
+            y -= measured_height;
+    }
+    return twrpTruetype::gr_ttf_textExWH(gl, x, y + y_scale, s, vfont, measured_width + x, -1, gr_draw);
+}
+
+void gr_clip(int x, int y, int w, int h)
+{
+    GGLContext *gl = gr_context;
+
+    switch (gr_rotation) {
+        case 90:
+            gl->scissor(gl, gr_draw->width - y - h, x, h, w);
+            break;
+        case 180:
+            gl->scissor(gl, gr_draw->width - x - w, gr_draw->height - y - h, w, h);
+            break;
+        case 270:
+            gl->scissor(gl, y, gr_draw->height - x - w, h, w);
+            break;
+        default:
+            gl->scissor(gl, x, y, w, h);
+            break;
+    }
+    gl->enable(gl, GGL_SCISSOR_TEST);
+}
+
+void gr_noclip()
+{
+    GGLContext *gl = gr_context;
+    gl->scissor(gl, 0, 0,
+                gr_draw->width - 2 * overscan_offset_x,
+                gr_draw->height - 2 * overscan_offset_y);
+    gl->disable(gl, GGL_SCISSOR_TEST);
+}
+
+void gr_line(int x0, int y0, int x1, int y1, int width)
+{
+    GGLContext *gl = gr_context;
+    int x0_disp, y0_disp, x1_disp, y1_disp;
+
+    x0_disp = ROTATION_X_DISP(x0, y0, gr_draw->width);
+    y0_disp = ROTATION_Y_DISP(x0, y0, gr_draw->height);
+    x1_disp = ROTATION_X_DISP(x1, y1, gr_draw->width);
+    y1_disp = ROTATION_Y_DISP(x1, y1, gr_draw->height);
+
+    if(gr_is_curr_clr_opaque)
+        gl->disable(gl, GGL_BLEND);
+
+    const int coords0[2] = { x0_disp << 4, y0_disp << 4 };
+    const int coords1[2] = { x1_disp << 4, y1_disp << 4 };
+    gl->linex(gl, coords0, coords1, width << 4);
+
+    if(gr_is_curr_clr_opaque)
+        gl->enable(gl, GGL_BLEND);
+}
+
+gr_surface gr_render_circle(int radius, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+    int rx, ry;
+    GGLSurface *surface;
+    const int diameter = radius*2 + 1;
+    const int radius_check = radius*radius + radius*0.8;
+    const uint32_t px = (a << 24) | (b << 16) | (g << 8) | r;
+    uint32_t *data;
+
+    surface = (GGLSurface *)malloc(sizeof(GGLSurface));
+    memset(surface, 0, sizeof(GGLSurface));
+
+    data = (uint32_t *)malloc(diameter * diameter * 4);
+    memset(data, 0, diameter * diameter * 4);
+
+    surface->version = sizeof(surface);
+    surface->width = diameter;
+    surface->height = diameter;
+    surface->stride = diameter;
+    surface->data = (GGLubyte*)data;
+#if defined(RECOVERY_BGRA)
+    surface->format = GGL_PIXEL_FORMAT_BGRA_8888;
+#else
+    surface->format = GGL_PIXEL_FORMAT_RGBA_8888;
+#endif
+
+    for(ry = -radius; ry <= radius; ++ry)
+        for(rx = -radius; rx <= radius; ++rx)
+            if(rx*rx+ry*ry <= radius_check)
+                *(data + diameter*(radius + ry) + (radius+rx)) = px;
+
+    return (gr_surface)surface;
+}
+
+void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+    GGLContext *gl = gr_context;
+    GGLint color[4];
+#if defined(RECOVERY_ARGB) || defined(RECOVERY_BGRA)
+    color[0] = ((b << 8) | r) + 1;
+    color[1] = ((g << 8) | g) + 1;
+    color[2] = ((r << 8) | b) + 1;
+    color[3] = ((a << 8) | a) + 1;
+#else
+    color[0] = ((r << 8) | r) + 1;
+    color[1] = ((g << 8) | g) + 1;
+    color[2] = ((b << 8) | b) + 1;
+    color[3] = ((a << 8) | a) + 1;
+#endif
+    gl->color4xv(gl, color);
+
+    gr_is_curr_clr_opaque = (a == 255);
+}
+
+void gr_clear()
+{
+    if (gr_draw->pixel_bytes == 2) {
+        gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+        return;
+    }
+
+    // This code only works on 32bpp devices
+    if (gr_current_r == gr_current_g && gr_current_r == gr_current_b) {
+        memset(gr_draw->data, gr_current_r, gr_draw->height * gr_draw->row_bytes);
+    } else {
+        unsigned char* px = gr_draw->data;
+        for (int y = 0; y < gr_draw->height; ++y) {
+            for (int x = 0; x < gr_draw->width; ++x) {
+                *px++ = gr_current_r;
+                *px++ = gr_current_g;
+                *px++ = gr_current_b;
+                px++;
+            }
+            px += gr_draw->row_bytes - (gr_draw->width * gr_draw->pixel_bytes);
+        }
+    }
+}
+
+void gr_fill(int x, int y, int w, int h)
+{
+    GGLContext *gl = gr_context;
+    int x0_disp, y0_disp, x1_disp, y1_disp;
+    int l_disp, r_disp, t_disp, b_disp;
+
+    if(gr_is_curr_clr_opaque)
+        gl->disable(gl, GGL_BLEND);
+
+    x0_disp = ROTATION_X_DISP(x, y, gr_draw->width);
+    y0_disp = ROTATION_Y_DISP(x, y, gr_draw->height);
+    x1_disp = ROTATION_X_DISP(x + w, y + h, gr_draw->width);
+    y1_disp = ROTATION_Y_DISP(x + w, y + h, gr_draw->height);
+    l_disp = std::min(x0_disp, x1_disp);
+    r_disp = std::max(x0_disp, x1_disp);
+    t_disp = std::min(y0_disp, y1_disp);
+    b_disp = std::max(y0_disp, y1_disp);
+    gl->recti(gl, l_disp, t_disp, r_disp, b_disp);
+
+    if(gr_is_curr_clr_opaque)
+        gl->enable(gl, GGL_BLEND);
+}
+
+void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy)
+{
+    if (gr_context == NULL) {
+        return;
+    }
+
+    GGLContext *gl = gr_context;
+    GGLSurface *surface = (GGLSurface*)source;
+
+    if(surface->format == GGL_PIXEL_FORMAT_RGBX_8888)
+        gl->disable(gl, GGL_BLEND);
+
+    int dx0_disp, dy0_disp, dx1_disp, dy1_disp;
+    int l_disp, r_disp, t_disp, b_disp;
+
+    // Figuring out display coordinates works for gr_rotation == 0 too,
+    // and isn't as expensive as allocating and rotating another surface,
+    // so we do this anyway.
+    dx0_disp = ROTATION_X_DISP(dx, dy, gr_draw->width);
+    dy0_disp = ROTATION_Y_DISP(dx, dy, gr_draw->height);
+    dx1_disp = ROTATION_X_DISP(dx + w, dy + h, gr_draw->width);
+    dy1_disp = ROTATION_Y_DISP(dx + w, dy + h, gr_draw->height);
+    l_disp = std::min(dx0_disp, dx1_disp);
+    r_disp = std::max(dx0_disp, dx1_disp);
+    t_disp = std::min(dy0_disp, dy1_disp);
+    b_disp = std::max(dy0_disp, dy1_disp);
+
+    GGLSurface surface_rotated;
+    if (gr_rotation != 0) {
+        // Do not perform relatively expensive operation if not needed
+        surface_rotated.version = sizeof(surface_rotated);
+        // Skip the **(gr_rotation == 0)** || (gr_rotation == 180) check
+        // because we are under a gr_rotation != 0 conditional compilation statement
+        surface_rotated.width   = (gr_rotation == 180) ? surface->width  : surface->height;
+        surface_rotated.height  = (gr_rotation == 180) ? surface->height : surface->width;
+        surface_rotated.stride  = surface_rotated.width;
+        surface_rotated.format  = surface->format;
+        surface_rotated.data    = (GGLubyte*) malloc(surface_rotated.stride * surface_rotated.height * 4);
+        surface_ROTATION_transform((gr_surface) &surface_rotated, (const gr_surface) surface, 4);
+
+        gl->bindTexture(gl, &surface_rotated);
+    } else {
+        gl->bindTexture(gl, surface);
+    }
+
+    gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
+    gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+    gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+    gl->enable(gl, GGL_TEXTURE_2D);
+    gl->texCoord2i(gl, sx - l_disp, sy - t_disp);
+    gl->recti(gl, l_disp, t_disp, r_disp, b_disp);
+    gl->disable(gl, GGL_TEXTURE_2D);
+
+    if (gr_rotation != 0)
+        free(surface_rotated.data);
+
+    if(surface->format == GGL_PIXEL_FORMAT_RGBX_8888)
+        gl->enable(gl, GGL_BLEND);
+}
+
+unsigned int gr_get_width(gr_surface surface) {
+    if (surface == NULL) {
+        return 0;
+    }
+    return ((GGLSurface*) surface)->width;
+}
+
+unsigned int gr_get_height(gr_surface surface) {
+    if (surface == NULL) {
+        return 0;
+    }
+    return ((GGLSurface*) surface)->height;
+}
+
+void gr_flip() {
+    gr_draw = gr_backend->flip(gr_backend);
+    // On double buffered back ends, when we flip, we need to tell
+    // pixel flinger to draw to the other buffer
+    gr_mem_surface.data = (GGLubyte*)gr_draw->data;
+    gr_context->colorBuffer(gr_context, &gr_mem_surface);
+}
+
+static void get_memory_surface(GGLSurface* ms) {
+    ms->version = sizeof(*ms);
+    ms->width = gr_draw->width;
+    ms->height = gr_draw->height;
+    ms->stride = gr_draw->row_bytes / gr_draw->pixel_bytes;
+    ms->data = (GGLubyte*)gr_draw->data;
+    ms->format = gr_draw->format;
+}
+
+int gr_init(void)
+{
+    gr_draw = NULL;
+
+    char gr_rotation_string[PROPERTY_VALUE_MAX];
+    char default_rotation[4];
+    snprintf(default_rotation, 4, "%d", TW_ROTATION);
+    property_get("persist.twrp.rotation", gr_rotation_string, default_rotation);
+    gr_rotation = atoi(gr_rotation_string);
+    if (!(gr_rotation == 90 || gr_rotation == 180 || gr_rotation == 270))
+        gr_rotation = 0;
+
+#ifdef MSM_BSP
+    gr_backend = open_overlay();
+    if (gr_backend) {
+        gr_draw = gr_backend->init(gr_backend);
+        if (!gr_draw) {
+            gr_backend->exit(gr_backend);
+        } else
+            printf("Using overlay graphics.\n");
+    }
+#endif
+
+#ifdef MSM_BSP
+	printf("Skipping adf graphics because TW_TARGET_USES_QCOM_BSP := true\n");
+#else
+    printf("Skipping adf graphics -- not present in build tree\n");
+#endif
+
+#ifdef HAS_DRM
+    if (!gr_backend || !gr_draw) {
+        gr_backend = open_drm();
+        gr_draw = gr_backend->init(gr_backend);
+        if (gr_draw)
+            printf("Using drm graphics.\n");
+    }
+#else
+    printf("Skipping drm graphics -- not present in build tree\n");
+#endif
+
+    if (!gr_backend || !gr_draw) {
+        gr_backend = open_fbdev();
+        gr_draw = gr_backend->init(gr_backend);
+        if (gr_draw == NULL) {
+            return -1;
+        } else
+            printf("Using fbdev graphics.\n");
+    }
+
+    overscan_offset_x = gr_draw->width * overscan_percent / 100;
+    overscan_offset_y = gr_draw->height * overscan_percent / 100;
+
+    // Set up pixelflinger
+    get_memory_surface(&gr_mem_surface);
+    gglInit(&gr_context);
+    GGLContext *gl = gr_context;
+    gl->colorBuffer(gl, &gr_mem_surface);
+
+    gl->activeTexture(gl, 0);
+    gl->enable(gl, GGL_BLEND);
+    gl->blendFunc(gl, GGL_SRC_ALPHA, GGL_ONE_MINUS_SRC_ALPHA);
+
+    gr_flip();
+    gr_flip();
+
+    return 0;
+}
+
+void gr_exit(void)
+{
+    gr_backend->exit(gr_backend);
+}
+
+int gr_fb_width(void)
+{
+    return (gr_rotation == 0 || gr_rotation == 180) ?
+            gr_draw->width  - 2 * overscan_offset_x :
+            gr_draw->height - 2 * overscan_offset_y;
+}
+
+int gr_fb_height(void)
+{
+    return (gr_rotation == 0 || gr_rotation == 180) ?
+            gr_draw->height - 2 * overscan_offset_y :
+            gr_draw->width  - 2 * overscan_offset_x;
+}
+
+void gr_fb_blank(bool blank)
+{
+    gr_backend->blank(gr_backend, blank);
+}
+
+int gr_get_surface(gr_surface* surface)
+{
+    GGLSurface* ms = (GGLSurface*)malloc(sizeof(GGLSurface));
+    if (!ms)    return -1;
+
+    // Allocate the data
+    get_memory_surface(ms);
+    ms->data = (GGLubyte*)malloc(ms->stride * ms->height * gr_draw->pixel_bytes);
+
+    // Now, copy the data
+    memcpy(ms->data, gr_mem_surface.data, gr_draw->width * gr_draw->height * gr_draw->pixel_bytes / 8);
+
+    *surface = (gr_surface*) ms;
+    return 0;
+}
+
+int gr_free_surface(gr_surface surface)
+{
+    if (!surface)
+        return -1;
+
+    GGLSurface* ms = (GGLSurface*) surface;
+    free(ms->data);
+    free(ms);
+    return 0;
+}
+
+void gr_write_frame_to_file(int fd)
+{
+    write(fd, gr_mem_surface.data, gr_draw->width * gr_draw->height * gr_draw->pixel_bytes / 8);
+}
diff --git a/minuitwrp/graphics.h b/minuitwrp/graphics.h
new file mode 100644
index 0000000..5df39ab
--- /dev/null
+++ b/minuitwrp/graphics.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GRAPHICS_H_
+#define _GRAPHICS_H_
+
+#include "minuitwrp/minui.h"
+
+// TODO: lose the function pointers.
+struct minui_backend {
+    // Initializes the backend and returns a GRSurface* to draw into.
+    GRSurface* (*init)(minui_backend*);
+
+    // Causes the current drawing surface (returned by the most recent
+    // call to flip() or init()) to be displayed, and returns a new
+    // drawing surface.
+    GRSurface* (*flip)(minui_backend*);
+
+    // Blank (or unblank) the screen.
+    void (*blank)(minui_backend*, bool);
+
+    // Device cleanup when drawing is done.
+    void (*exit)(minui_backend*);
+};
+
+minui_backend* open_fbdev();
+minui_backend* open_adf();
+minui_backend* open_drm();
+minui_backend* open_overlay();
+
+#endif
diff --git a/minuitwrp/graphics_drm.cpp b/minuitwrp/graphics_drm.cpp
new file mode 100644
index 0000000..a02613f
--- /dev/null
+++ b/minuitwrp/graphics_drm.cpp
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <drm_fourcc.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include "minuitwrp/minui.h"
+#include "graphics.h"
+#include <pixelflinger/pixelflinger.h>
+
+#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
+
+struct drm_surface {
+    GRSurface base;
+    uint32_t fb_id;
+    uint32_t handle;
+};
+
+static drm_surface *drm_surfaces[2];
+static int current_buffer;
+static GRSurface *draw_buf = NULL;
+
+static drmModeCrtc *main_monitor_crtc;
+static drmModeConnector *main_monitor_connector;
+
+static int drm_fd = -1;
+
+static void drm_disable_crtc(int drm_fd, drmModeCrtc *crtc) {
+    if (crtc) {
+        drmModeSetCrtc(drm_fd, crtc->crtc_id,
+                       0, // fb_id
+                       0, 0,  // x,y
+                       NULL,  // connectors
+                       0,     // connector_count
+                       NULL); // mode
+    }
+}
+
+static void drm_enable_crtc(int drm_fd, drmModeCrtc *crtc,
+                            struct drm_surface *surface) {
+    int32_t ret;
+
+    ret = drmModeSetCrtc(drm_fd, crtc->crtc_id,
+                         surface->fb_id,
+                         0, 0,  // x,y
+                         &main_monitor_connector->connector_id,
+                         1,  // connector_count
+                         &main_monitor_crtc->mode);
+
+    if (ret)
+        printf("drmModeSetCrtc failed ret=%d\n", ret);
+}
+
+static void drm_blank(minui_backend* backend __unused, bool blank) {
+    if (blank)
+        drm_disable_crtc(drm_fd, main_monitor_crtc);
+    else
+        drm_enable_crtc(drm_fd, main_monitor_crtc,
+                        drm_surfaces[current_buffer]);
+}
+
+static void drm_destroy_surface(struct drm_surface *surface) {
+    struct drm_gem_close gem_close;
+    int ret;
+
+    if(!surface)
+        return;
+
+    if (surface->base.data)
+        munmap(surface->base.data,
+               surface->base.row_bytes * surface->base.height);
+
+    if (surface->fb_id) {
+        ret = drmModeRmFB(drm_fd, surface->fb_id);
+        if (ret)
+            printf("drmModeRmFB failed ret=%d\n", ret);
+    }
+
+    if (surface->handle) {
+        memset(&gem_close, 0, sizeof(gem_close));
+        gem_close.handle = surface->handle;
+
+        ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
+        if (ret)
+            printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret);
+    }
+
+    free(surface);
+}
+
+static int drm_format_to_bpp(uint32_t format) {
+    switch(format) {
+        case DRM_FORMAT_ABGR8888:
+        case DRM_FORMAT_BGRA8888:
+        case DRM_FORMAT_RGBX8888:
+        case DRM_FORMAT_RGBA8888:
+        case DRM_FORMAT_BGRX8888:
+        case DRM_FORMAT_XBGR8888:
+        case DRM_FORMAT_ARGB8888:
+        case DRM_FORMAT_XRGB8888:
+            return 32;
+        case DRM_FORMAT_RGB565:
+            return 16;
+        default:
+            printf("Unknown format %d\n", format);
+            return 32;
+    }
+}
+
+static drm_surface *drm_create_surface(int width, int height) {
+    struct drm_surface *surface;
+    struct drm_mode_create_dumb create_dumb;
+    uint32_t format;
+    __u32 base_format;
+    int ret;
+
+    surface = (struct drm_surface*)calloc(1, sizeof(*surface));
+    if (!surface) {
+        printf("Can't allocate memory\n");
+        return NULL;
+    }
+
+#if defined(RECOVERY_ABGR)
+    format = DRM_FORMAT_RGBA8888;
+    base_format = GGL_PIXEL_FORMAT_RGBA_8888;
+    printf("setting DRM_FORMAT_RGBA8888 and GGL_PIXEL_FORMAT_RGBA_8888\n");
+#elif defined(RECOVERY_BGRA)
+    format = DRM_FORMAT_ARGB8888;
+    base_format = GGL_PIXEL_FORMAT_RGBA_8888;
+    printf("setting DRM_FORMAT_ARGB8888 and GGL_PIXEL_FORMAT_RGBA_8888\n");
+#elif defined(RECOVERY_RGBA)
+    format = DRM_FORMAT_ABGR8888;
+    base_format = GGL_PIXEL_FORMAT_BGRA_8888;
+    printf("setting DRM_FORMAT_ABGR8888 and GGL_PIXEL_FORMAT_BGRA_8888, GGL_PIXEL_FORMAT may not match!\n");
+#elif defined(RECOVERY_RGBX)
+    format = DRM_FORMAT_XBGR8888;
+    base_format = GGL_PIXEL_FORMAT_RGBA_8888;
+    printf("setting DRM_FORMAT_XBGR8888 and GGL_PIXEL_FORMAT_RGBA_8888\n");
+#else
+    format = DRM_FORMAT_RGB565;
+    base_format = GGL_PIXEL_FORMAT_BGRA_8888;
+    printf("setting DRM_FORMAT_RGB565 and GGL_PIXEL_FORMAT_RGB_565\n");
+#endif
+
+    memset(&create_dumb, 0, sizeof(create_dumb));
+    create_dumb.height = height;
+    create_dumb.width = width;
+    create_dumb.bpp = drm_format_to_bpp(format);
+    create_dumb.flags = 0;
+
+    ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
+    if (ret) {
+        printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n",ret);
+        drm_destroy_surface(surface);
+        return NULL;
+    }
+    surface->handle = create_dumb.handle;
+
+    uint32_t handles[4], pitches[4], offsets[4];
+
+    handles[0] = surface->handle;
+    pitches[0] = create_dumb.pitch;
+    offsets[0] = 0;
+
+    ret = drmModeAddFB2(drm_fd, width, height,
+            format, handles, pitches, offsets,
+            &(surface->fb_id), 0);
+    if (ret) {
+        printf("drmModeAddFB2 failed ret=%d\n", ret);
+        drm_destroy_surface(surface);
+        return NULL;
+    }
+
+    struct drm_mode_map_dumb map_dumb;
+    memset(&map_dumb, 0, sizeof(map_dumb));
+    map_dumb.handle = create_dumb.handle;
+    ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
+    if (ret) {
+        printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n",ret);
+        drm_destroy_surface(surface);
+        return NULL;;
+    }
+
+    surface->base.height = height;
+    surface->base.width = width;
+    surface->base.row_bytes = create_dumb.pitch;
+    surface->base.pixel_bytes = create_dumb.bpp / 8;
+    surface->base.format = base_format;
+    surface->base.data = (unsigned char*)
+                         mmap(NULL,
+                              surface->base.height * surface->base.row_bytes,
+                              PROT_READ | PROT_WRITE, MAP_SHARED,
+                              drm_fd, map_dumb.offset);
+    if (surface->base.data == MAP_FAILED) {
+        perror("mmap() failed");
+        drm_destroy_surface(surface);
+        return NULL;
+    }
+
+    return surface;
+}
+
+static drmModeCrtc *find_crtc_for_connector(int fd,
+                            drmModeRes *resources,
+                            drmModeConnector *connector) {
+    int i, j;
+    drmModeEncoder *encoder;
+    int32_t crtc;
+
+    /*
+     * Find the encoder. If we already have one, just use it.
+     */
+    if (connector->encoder_id)
+        encoder = drmModeGetEncoder(fd, connector->encoder_id);
+    else
+        encoder = NULL;
+
+    if (encoder && encoder->crtc_id) {
+        crtc = encoder->crtc_id;
+        drmModeFreeEncoder(encoder);
+        return drmModeGetCrtc(fd, crtc);
+    }
+
+    /*
+     * Didn't find anything, try to find a crtc and encoder combo.
+     */
+    crtc = -1;
+    for (i = 0; i < connector->count_encoders; i++) {
+        encoder = drmModeGetEncoder(fd, connector->encoders[i]);
+
+        if (encoder) {
+            for (j = 0; j < resources->count_crtcs; j++) {
+                if (!(encoder->possible_crtcs & (1 << j)))
+                    continue;
+                crtc = resources->crtcs[j];
+                break;
+            }
+            if (crtc >= 0) {
+                drmModeFreeEncoder(encoder);
+                return drmModeGetCrtc(fd, crtc);
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static drmModeConnector *find_used_connector_by_type(int fd,
+                                 drmModeRes *resources,
+                                 unsigned type) {
+    int i;
+    for (i = 0; i < resources->count_connectors; i++) {
+        drmModeConnector *connector;
+
+        connector = drmModeGetConnector(fd, resources->connectors[i]);
+        if (connector) {
+            if ((connector->connector_type == type) &&
+                    (connector->connection == DRM_MODE_CONNECTED) &&
+                    (connector->count_modes > 0))
+                return connector;
+
+            drmModeFreeConnector(connector);
+        }
+    }
+    return NULL;
+}
+
+static drmModeConnector *find_first_connected_connector(int fd,
+                             drmModeRes *resources) {
+    int i;
+    for (i = 0; i < resources->count_connectors; i++) {
+        drmModeConnector *connector;
+
+        connector = drmModeGetConnector(fd, resources->connectors[i]);
+        if (connector) {
+            if ((connector->count_modes > 0) &&
+                    (connector->connection == DRM_MODE_CONNECTED))
+                return connector;
+
+            drmModeFreeConnector(connector);
+        }
+    }
+    return NULL;
+}
+
+static drmModeConnector *find_main_monitor(int fd, drmModeRes *resources,
+        uint32_t *mode_index) {
+    unsigned i = 0;
+    int modes;
+    /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */
+    unsigned kConnectorPriority[] = {
+        DRM_MODE_CONNECTOR_LVDS,
+        DRM_MODE_CONNECTOR_eDP,
+        DRM_MODE_CONNECTOR_DSI,
+    };
+
+    drmModeConnector *main_monitor_connector = NULL;
+    do {
+        main_monitor_connector = find_used_connector_by_type(fd,
+                                         resources,
+                                         kConnectorPriority[i]);
+        i++;
+    } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
+
+    /* If we didn't find a connector, grab the first one that is connected. */
+    if (!main_monitor_connector)
+        main_monitor_connector =
+                find_first_connected_connector(fd, resources);
+
+    /* If we still didn't find a connector, give up and return. */
+    if (!main_monitor_connector)
+        return NULL;
+
+    *mode_index = 0;
+    for (modes = 0; modes < main_monitor_connector->count_modes; modes++) {
+        if (main_monitor_connector->modes[modes].type &
+                DRM_MODE_TYPE_PREFERRED) {
+            *mode_index = modes;
+            break;
+        }
+    }
+
+    return main_monitor_connector;
+}
+
+static void disable_non_main_crtcs(int fd,
+                    drmModeRes *resources,
+                    drmModeCrtc* main_crtc) {
+    int i;
+    drmModeCrtc* crtc;
+
+    for (i = 0; i < resources->count_connectors; i++) {
+        drmModeConnector *connector;
+
+        connector = drmModeGetConnector(fd, resources->connectors[i]);
+        crtc = find_crtc_for_connector(fd, resources, connector);
+        if (crtc->crtc_id != main_crtc->crtc_id)
+            drm_disable_crtc(fd, crtc);
+        drmModeFreeCrtc(crtc);
+    }
+}
+
+static GRSurface* drm_init(minui_backend* backend __unused) {
+    drmModeRes *res = NULL;
+    uint32_t selected_mode;
+    char *dev_name;
+    int width, height;
+    int ret, i;
+
+    /* Consider DRM devices in order. */
+    for (i = 0; i < DRM_MAX_MINOR; i++) {
+        uint64_t cap = 0;
+
+        ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
+        if (ret < 0)
+            continue;
+
+        drm_fd = open(dev_name, O_RDWR, 0);
+        free(dev_name);
+        if (drm_fd < 0)
+            continue;
+
+        /* We need dumb buffers. */
+        ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
+        if (ret || cap == 0) {
+            close(drm_fd);
+            continue;
+        }
+
+        res = drmModeGetResources(drm_fd);
+        if (!res) {
+            close(drm_fd);
+            continue;
+        }
+
+        /* Use this device if it has at least one connected monitor. */
+        if (res->count_crtcs > 0 && res->count_connectors > 0)
+            if (find_first_connected_connector(drm_fd, res))
+                break;
+
+        drmModeFreeResources(res);
+        close(drm_fd);
+        res = NULL;
+    }
+
+    if (drm_fd < 0 || res == NULL) {
+        perror("cannot find/open a drm device");
+        return NULL;
+    }
+
+    main_monitor_connector = find_main_monitor(drm_fd,
+            res, &selected_mode);
+
+    if (!main_monitor_connector) {
+        printf("main_monitor_connector not found\n");
+        drmModeFreeResources(res);
+        close(drm_fd);
+        return NULL;
+    }
+
+    main_monitor_crtc = find_crtc_for_connector(drm_fd, res,
+                                                main_monitor_connector);
+
+    if (!main_monitor_crtc) {
+        printf("main_monitor_crtc not found\n");
+        drmModeFreeResources(res);
+        close(drm_fd);
+        return NULL;
+    }
+
+    disable_non_main_crtcs(drm_fd,
+                           res, main_monitor_crtc);
+
+    main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode];
+
+    width = main_monitor_crtc->mode.hdisplay;
+    height = main_monitor_crtc->mode.vdisplay;
+
+    drmModeFreeResources(res);
+
+    drm_surfaces[0] = drm_create_surface(width, height);
+    drm_surfaces[1] = drm_create_surface(width, height);
+    if (!drm_surfaces[0] || !drm_surfaces[1]) {
+        drm_destroy_surface(drm_surfaces[0]);
+        drm_destroy_surface(drm_surfaces[1]);
+        drmModeFreeResources(res);
+        close(drm_fd);
+        return NULL;
+    }
+
+    draw_buf = (GRSurface *)malloc(sizeof(GRSurface));
+    if (!draw_buf) {
+        printf("failed to alloc draw_buf\n");
+        drm_destroy_surface(drm_surfaces[0]);
+        drm_destroy_surface(drm_surfaces[1]);
+        drmModeFreeResources(res);
+        close(drm_fd);
+        return NULL;
+    }
+
+    memcpy(draw_buf, &drm_surfaces[0]->base, sizeof(GRSurface));
+    draw_buf->data = (unsigned char *)calloc(draw_buf->height * draw_buf->row_bytes, 1);
+    if (!draw_buf->data) {
+        printf("failed to alloc draw_buf surface\n");
+        free(draw_buf);
+        drm_destroy_surface(drm_surfaces[0]);
+        drm_destroy_surface(drm_surfaces[1]);
+        drmModeFreeResources(res);
+        close(drm_fd);
+        return NULL;
+    }
+
+    current_buffer = 0;
+
+    drm_enable_crtc(drm_fd, main_monitor_crtc, drm_surfaces[1]);
+
+    return draw_buf;
+}
+
+static void page_flip_complete(__unused int fd,
+                               __unused unsigned int sequence,
+                               __unused unsigned int tv_sec,
+                               __unused unsigned int tv_usec,
+                               void *user_data) {
+  *static_cast<bool*>(user_data) = false;
+}
+
+static GRSurface* drm_flip(minui_backend* backend __unused) {
+    bool ongoing_flip = true;
+    memcpy(drm_surfaces[current_buffer]->base.data,
+            draw_buf->data, draw_buf->height * draw_buf->row_bytes);
+
+
+    if (drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
+                          drm_surfaces[current_buffer]->fb_id, DRM_MODE_PAGE_FLIP_EVENT, &ongoing_flip)) {
+        printf("Failed to drmModePageFlip");
+        return nullptr;
+    }
+
+    while (ongoing_flip) {
+        struct pollfd fds = {
+            .fd = drm_fd,
+            .events = POLLIN
+        };
+
+        if (poll(&fds, 1, -1) == -1 || !(fds.revents & POLLIN)) {
+            perror("Failed to poll() on drm fd");
+            break;
+        }
+
+        drmEventContext evctx = {
+            .version = DRM_EVENT_CONTEXT_VERSION,
+            .page_flip_handler = page_flip_complete
+        };
+
+        if (drmHandleEvent(drm_fd, &evctx) != 0) {
+            perror("Failed to drmHandleEvent");
+            break;
+        }
+    }
+
+    current_buffer = 1 - current_buffer;
+    return draw_buf;
+}
+
+static void drm_exit(minui_backend* backend __unused) {
+    drm_disable_crtc(drm_fd, main_monitor_crtc);
+    drm_destroy_surface(drm_surfaces[0]);
+    drm_destroy_surface(drm_surfaces[1]);
+    drmModeFreeCrtc(main_monitor_crtc);
+    drmModeFreeConnector(main_monitor_connector);
+    close(drm_fd);
+    drm_fd = -1;
+}
+
+static minui_backend drm_backend = {
+    .init = drm_init,
+    .flip = drm_flip,
+    .blank = drm_blank,
+    .exit = drm_exit,
+};
+
+minui_backend* open_drm() {
+    return &drm_backend;
+}
diff --git a/minuitwrp/graphics_drm.h b/minuitwrp/graphics_drm.h
new file mode 100644
index 0000000..2a16af8
--- /dev/null
+++ b/minuitwrp/graphics_drm.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include <xf86drmMode.h>
+
+#include "graphics.h"
+#include "minuitwrp/minui.h"
+
+class GRSurfaceDrm : public GRSurface {
+ public:
+  ~GRSurfaceDrm() override;
+
+  // Creates a GRSurfaceDrm instance.
+  static std::unique_ptr<GRSurfaceDrm> Create(int drm_fd, int width, int height);
+
+  uint8_t* data() override {
+    return mmapped_buffer_;
+  }
+
+ private:
+  friend class MinuiBackendDrm;
+
+  GRSurfaceDrm(size_t width, size_t height, size_t row_bytes, size_t pixel_bytes, int drm_fd,
+               uint32_t handle)
+      : GRSurface(width, height, row_bytes, pixel_bytes), drm_fd_(drm_fd), handle(handle) {}
+
+  const int drm_fd_;
+
+  uint32_t fb_id{ 0 };
+  uint32_t handle{ 0 };
+  uint8_t* mmapped_buffer_{ nullptr };
+};
+
+class MinuiBackendDrm : public MinuiBackend {
+ public:
+  MinuiBackendDrm() = default;
+  ~MinuiBackendDrm() override;
+
+  GRSurface* Init() override;
+  GRSurface* Flip() override;
+  void Blank(bool) override;
+
+ private:
+  void DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc);
+  bool DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, const std::unique_ptr<GRSurfaceDrm>& surface);
+  void DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc);
+  drmModeConnector* FindMainMonitor(int fd, drmModeRes* resources, uint32_t* mode_index);
+
+  std::unique_ptr<GRSurfaceDrm> GRSurfaceDrms[2];
+  int current_buffer{ 0 };
+  drmModeCrtc* main_monitor_crtc{ nullptr };
+  drmModeConnector* main_monitor_connector{ nullptr };
+  int drm_fd{ -1 };
+};
diff --git a/minuitwrp/graphics_fbdev.cpp b/minuitwrp/graphics_fbdev.cpp
new file mode 100644
index 0000000..499e0ea
--- /dev/null
+++ b/minuitwrp/graphics_fbdev.cpp
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include "minuitwrp/minui.h"
+#include "graphics.h"
+#include <pixelflinger/pixelflinger.h>
+
+static GRSurface* fbdev_init(minui_backend*);
+static GRSurface* fbdev_flip(minui_backend*);
+static void fbdev_blank(minui_backend*, bool);
+static void fbdev_exit(minui_backend*);
+
+static GRSurface gr_framebuffer[2];
+static bool double_buffered;
+static GRSurface* gr_draw = NULL;
+static int displayed_buffer;
+
+static fb_var_screeninfo vi;
+static int fb_fd = -1;
+static __u32 smem_len;
+
+static minui_backend my_backend = {
+    .init = fbdev_init,
+    .flip = fbdev_flip,
+    .blank = fbdev_blank,
+    .exit = fbdev_exit,
+};
+
+minui_backend* open_fbdev() {
+    return &my_backend;
+}
+
+static void fbdev_blank(minui_backend* backend __unused, bool blank)
+{
+#if defined(TW_NO_SCREEN_BLANK) && defined(TW_BRIGHTNESS_PATH) && defined(TW_MAX_BRIGHTNESS)
+    int fd;
+    char brightness[4];
+    snprintf(brightness, 4, "%03d", TW_MAX_BRIGHTNESS/2);
+
+    fd = open(TW_BRIGHTNESS_PATH, O_RDWR);
+    if (fd < 0) {
+        perror("cannot open LCD backlight");
+        return;
+    }
+    write(fd, blank ? "000" : brightness, 3);
+    close(fd);
+
+#ifdef TW_SECONDARY_BRIGHTNESS_PATH
+    fd = open(TW_SECONDARY_BRIGHTNESS_PATH, O_RDWR);
+    if (fd < 0) {
+        perror("cannot open LCD backlight 2");
+        return;
+    }
+    write(fd, blank ? "000" : brightness, 3);
+    close(fd);
+#endif
+#else
+#ifndef TW_NO_SCREEN_BLANK
+    int ret;
+
+    ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
+    if (ret < 0)
+        perror("ioctl(): blank");
+#endif
+#endif
+}
+
+static void set_displayed_framebuffer(unsigned n)
+{
+    if (n > 1 || !double_buffered) return;
+
+    vi.yres_virtual = gr_framebuffer[0].height * 2;
+    vi.yoffset = n * gr_framebuffer[0].height;
+    vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
+    if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
+        perror("active fb swap failed");
+#ifdef TW_FBIOPAN
+    } else {
+        if (ioctl(fb_fd, FBIOPAN_DISPLAY, &vi) < 0) {
+            perror("pan failed");
+        }
+#endif
+    }
+    displayed_buffer = n;
+}
+
+static GRSurface* fbdev_init(minui_backend* backend) {
+    int retry = 20;
+    int fd = -1;
+    while (fd == -1) {
+        fd = open("/dev/graphics/fb0", O_RDWR);
+        if (fd == -1) {
+            if (--retry) {
+                // wait for init to create the device node
+                perror("cannot open fb0 (retrying)");
+                usleep(100000);
+            } else {
+                perror("cannot open fb0 (giving up)");
+                return NULL;
+            }
+        }
+    }
+
+    if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
+        perror("failed to get fb0 info (FBIOGET_VSCREENINFO)");
+        close(fd);
+        return NULL;
+    }
+
+#ifdef RECOVERY_FORCE_RGB_565
+    // Changing fb_var_screeninfo can affect fb_fix_screeninfo,
+    // so this needs done before querying for fi.
+    printf("Forcing pixel format: RGB_565\n");
+    vi.blue.offset    = 0;
+    vi.green.offset   = 5;
+    vi.red.offset     = 11;
+    vi.blue.length    = 5;
+    vi.green.length   = 6;
+    vi.red.length     = 5;
+    vi.blue.msb_right = 0;
+    vi.green.msb_right = 0;
+    vi.red.msb_right = 0;
+    vi.transp.offset  = 0;
+    vi.transp.length  = 0;
+    vi.bits_per_pixel = 16;
+
+    if (ioctl(fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
+        perror("failed to put force_rgb_565 fb0 info");
+        close(fd);
+        return NULL;
+    }
+#endif
+
+    fb_fix_screeninfo fi;
+    if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
+        perror("failed to get fb0 info (FBIOGET_FSCREENINFO)");
+        close(fd);
+        return NULL;
+    }
+
+    // We print this out for informational purposes only, but
+    // throughout we assume that the framebuffer device uses an RGBX
+    // pixel format.  This is the case for every development device I
+    // have access to.  For some of those devices (eg, hammerhead aka
+    // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a
+    // different format (XBGR) but actually produces the correct
+    // results on the display when you write RGBX.
+    //
+    // If you have a device that actually *needs* another pixel format
+    // (ie, BGRX, or 565), patches welcome...
+
+    printf("fb0 reports (possibly inaccurate):\n"
+           "  vi.bits_per_pixel = %d\n"
+           "  vi.red.offset   = %3d   .length = %3d\n"
+           "  vi.green.offset = %3d   .length = %3d\n"
+           "  vi.blue.offset  = %3d   .length = %3d\n",
+           vi.bits_per_pixel,
+           vi.red.offset, vi.red.length,
+           vi.green.offset, vi.green.length,
+           vi.blue.offset, vi.blue.length);
+
+    void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (bits == MAP_FAILED) {
+        perror("failed to mmap framebuffer");
+        close(fd);
+        return NULL;
+    }
+
+    memset(bits, 0, fi.smem_len);
+
+    gr_framebuffer[0].width = vi.xres;
+    gr_framebuffer[0].height = vi.yres;
+    gr_framebuffer[0].row_bytes = fi.line_length;
+    gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
+#ifdef RECOVERY_GRAPHICS_FORCE_USE_LINELENGTH
+    printf("Forcing line length\n");
+    vi.xres_virtual = fi.line_length / gr_framebuffer[0].pixel_bytes;
+#endif
+    gr_framebuffer[0].data = reinterpret_cast<uint8_t*>(bits);
+    if (vi.bits_per_pixel == 16) {
+        printf("setting GGL_PIXEL_FORMAT_RGB_565\n");
+        gr_framebuffer[0].format = GGL_PIXEL_FORMAT_RGB_565;
+    } else if (vi.red.offset == 8 || vi.red.offset == 16) {
+        printf("setting GGL_PIXEL_FORMAT_BGRA_8888\n");
+        gr_framebuffer[0].format = GGL_PIXEL_FORMAT_BGRA_8888;
+    } else if (vi.red.offset == 0) {
+        printf("setting GGL_PIXEL_FORMAT_RGBA_8888\n");
+        gr_framebuffer[0].format = GGL_PIXEL_FORMAT_RGBA_8888;
+    } else if (vi.red.offset == 24) {
+        printf("setting GGL_PIXEL_FORMAT_RGBX_8888\n");
+        gr_framebuffer[0].format = GGL_PIXEL_FORMAT_RGBX_8888;
+    } else {
+        if (vi.red.length == 8) {
+            printf("No valid pixel format detected, trying GGL_PIXEL_FORMAT_RGBX_8888\n");
+            gr_framebuffer[0].format = GGL_PIXEL_FORMAT_RGBX_8888;
+        } else {
+            printf("No valid pixel format detected, trying GGL_PIXEL_FORMAT_RGB_565\n");
+            gr_framebuffer[0].format = GGL_PIXEL_FORMAT_RGB_565;
+        }
+    }
+
+    // Drawing directly to the framebuffer takes about 5 times longer.
+    // Instead, we will allocate some memory and draw to that, then
+    // memcpy the data into the framebuffer later.
+    gr_draw = (GRSurface*) malloc(sizeof(GRSurface));
+    if (!gr_draw) {
+        perror("failed to allocate gr_draw");
+        close(fd);
+        munmap(bits, fi.smem_len);
+        return NULL;
+    }
+    memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
+    gr_draw->data = (unsigned char*) calloc(gr_draw->height * gr_draw->row_bytes, 1);
+    if (!gr_draw->data) {
+        perror("failed to allocate in-memory surface");
+        close(fd);
+        free(gr_draw);
+        munmap(bits, fi.smem_len);
+        return NULL;
+    }
+
+    /* check if we can use double buffering */
+#ifndef RECOVERY_GRAPHICS_FORCE_SINGLE_BUFFER
+    if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
+        double_buffered = true;
+        printf("double buffered\n");
+
+        memcpy(gr_framebuffer+1, gr_framebuffer, sizeof(GRSurface));
+        gr_framebuffer[1].data = gr_framebuffer[0].data +
+            gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
+
+    } else {
+#else
+    {
+        printf("RECOVERY_GRAPHICS_FORCE_SINGLE_BUFFER := true\n");
+#endif
+        double_buffered = false;
+        printf("single buffered\n");
+    }
+#if defined(RECOVERY_BGRA)
+    printf("RECOVERY_BGRA\n");
+#endif
+    fb_fd = fd;
+    set_displayed_framebuffer(0);
+
+    printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
+
+    fbdev_blank(backend, true);
+    fbdev_blank(backend, false);
+
+    smem_len = fi.smem_len;
+
+    return gr_draw;
+}
+
+static GRSurface* fbdev_flip(minui_backend* backend __unused) {
+#if defined(RECOVERY_BGRA)
+    // In case of BGRA, do some byte swapping
+    unsigned char* ucfb_vaddr = (unsigned char*)gr_draw->data;
+    for (int idx = 0 ; idx < (gr_draw->height * gr_draw->row_bytes);
+            idx += 4) {
+        unsigned char tmp = ucfb_vaddr[idx];
+        ucfb_vaddr[idx    ] = ucfb_vaddr[idx + 2];
+        ucfb_vaddr[idx + 2] = tmp;
+    }
+#endif
+    if (double_buffered) {
+        // Copy from the in-memory surface to the framebuffer.
+        memcpy(gr_framebuffer[1-displayed_buffer].data, gr_draw->data,
+               gr_draw->height * gr_draw->row_bytes);
+        set_displayed_framebuffer(1-displayed_buffer);
+    } else {
+        // Copy from the in-memory surface to the framebuffer.
+        memcpy(gr_framebuffer[0].data, gr_draw->data,
+               gr_draw->height * gr_draw->row_bytes);
+    }
+    return gr_draw;
+}
+
+static void fbdev_exit(minui_backend* backend __unused) {
+    close(fb_fd);
+    fb_fd = -1;
+
+    if (gr_draw) {
+        free(gr_draw->data);
+        free(gr_draw);
+    }
+    gr_draw = NULL;
+    munmap(gr_framebuffer[0].data, smem_len);
+}
diff --git a/minuitwrp/graphics_fbdev.h b/minuitwrp/graphics_fbdev.h
new file mode 100644
index 0000000..704d26d
--- /dev/null
+++ b/minuitwrp/graphics_fbdev.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <linux/fb.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+#include "graphics.h"
+#include "minuitwrp/minui.h"
+
+class GRSurfaceFbdev : public GRSurface {
+ public:
+  // Creates and returns a GRSurfaceFbdev instance, or nullptr on error.
+  static std::unique_ptr<GRSurfaceFbdev> Create(size_t width, size_t height, size_t row_bytes,
+                                                size_t pixel_bytes);
+
+  uint8_t* data() override {
+    return buffer_;
+  }
+
+ protected:
+  using GRSurface::GRSurface;
+
+ private:
+  friend class MinuiBackendFbdev;
+
+  // Points to the start of the buffer: either the mmap'd framebuffer or one allocated in-memory.
+  uint8_t* buffer_{ nullptr };
+};
+
+class MinuiBackendFbdev : public MinuiBackend {
+ public:
+  MinuiBackendFbdev() = default;
+  ~MinuiBackendFbdev() override = default;
+
+  GRSurface* Init() override;
+  GRSurface* Flip() override;
+  void Blank(bool) override;
+
+ private:
+  void SetDisplayedFramebuffer(size_t n);
+
+  std::unique_ptr<GRSurfaceFbdev> gr_framebuffer[2];
+  // Points to the current surface (i.e. one of the two gr_framebuffer's).
+  GRSurfaceFbdev* gr_draw{ nullptr };
+  bool double_buffered;
+  std::vector<uint8_t> memory_buffer;
+  size_t displayed_buffer{ 0 };
+  fb_var_screeninfo vi;
+  android::base::unique_fd fb_fd;
+};
diff --git a/minuitwrp/graphics_overlay.cpp b/minuitwrp/graphics_overlay.cpp
new file mode 100644
index 0000000..a53faa8
--- /dev/null
+++ b/minuitwrp/graphics_overlay.cpp
@@ -0,0 +1,680 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#ifdef MSM_BSP
+#include <linux/msm_mdp.h>
+#include <linux/msm_ion.h>
+#endif
+
+#include "minui.h"
+#include "graphics.h"
+#include <pixelflinger/pixelflinger.h>
+
+#define MDP_V4_0 400
+#define MAX_DISPLAY_DIM  2048
+
+static GRSurface* overlay_init(minui_backend*);
+static GRSurface* overlay_flip(minui_backend*);
+static void overlay_blank(minui_backend*, bool);
+static void overlay_exit(minui_backend*);
+
+static GRSurface gr_framebuffer;
+static GRSurface* gr_draw = NULL;
+
+static fb_var_screeninfo vi;
+static int fb_fd = -1;
+static bool isMDP5 = false;
+static int leftSplit = 0;
+static int rightSplit = 0;
+#define ALIGN(x, align) (((x) + ((align)-1)) & ~((align)-1))
+
+static size_t frame_size = 0;
+
+#ifdef MSM_BSP
+typedef struct {
+    unsigned char *mem_buf;
+    int size;
+    int ion_fd;
+    int mem_fd;
+    struct ion_handle_data handle_data;
+} memInfo;
+
+//Left and right overlay id
+static int overlayL_id = MSMFB_NEW_REQUEST;
+static int overlayR_id = MSMFB_NEW_REQUEST;
+
+static memInfo mem_info;
+
+static int map_mdp_pixel_format()
+{
+    if (gr_framebuffer.format == GGL_PIXEL_FORMAT_RGB_565)
+        return MDP_RGB_565;
+    else if (gr_framebuffer.format == GGL_PIXEL_FORMAT_BGRA_8888)
+        return MDP_BGRA_8888;
+    else if (gr_framebuffer.format == GGL_PIXEL_FORMAT_RGBA_8888)
+        return MDP_RGBA_8888;
+    else if (gr_framebuffer.format == GGL_PIXEL_FORMAT_RGBX_8888)
+        return MDP_RGBA_8888;
+    printf("No known pixel format for map_mdp_pixel_format, defaulting to MDP_RGB_565.\n");
+    return MDP_RGB_565;
+}
+#endif // MSM_BSP
+
+static minui_backend my_backend = {
+    .init = overlay_init,
+    .flip = overlay_flip,
+    .blank = overlay_blank,
+    .exit = overlay_exit,
+};
+
+bool target_has_overlay(char *version)
+{
+    int mdp_version;
+    bool overlay_supported = false;
+
+    if (strlen(version) >= 8) {
+        if(!strncmp(version, "msmfb", strlen("msmfb"))) {
+            char str_ver[4];
+            memcpy(str_ver, version + strlen("msmfb"), 3);
+            str_ver[3] = '\0';
+            mdp_version = atoi(str_ver);
+            if (mdp_version >= MDP_V4_0) {
+                overlay_supported = true;
+            }
+        } else if (!strncmp(version, "mdssfb", strlen("mdssfb"))) {
+            overlay_supported = true;
+            isMDP5 = true;
+        }
+    }
+
+    return overlay_supported;
+}
+
+minui_backend* open_overlay() {
+    fb_fix_screeninfo fi;
+    int fd;
+
+    fd = open("/dev/graphics/fb0", O_RDWR);
+    if (fd < 0) {
+        perror("open_overlay cannot open fb0");
+        return NULL;
+    }
+
+    if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
+        perror("failed to get fb0 info");
+        close(fd);
+        return NULL;
+    }
+
+    if (target_has_overlay(fi.id)) {
+#ifdef MSM_BSP
+        close(fd);
+        return &my_backend;
+#else
+        printf("Overlay graphics may work (%s), but not enabled. Use TW_TARGET_USES_QCOM_BSP := true to enable.\n", fi.id);
+#endif
+    }
+    close(fd);
+    return NULL;
+}
+
+static void overlay_blank(minui_backend* backend __unused, bool blank)
+{
+#if defined(TW_NO_SCREEN_BLANK) && defined(TW_BRIGHTNESS_PATH) && defined(TW_MAX_BRIGHTNESS)
+    int fd;
+    char brightness[4];
+    snprintf(brightness, 4, "%03d", TW_MAX_BRIGHTNESS/2);
+
+    fd = open(TW_BRIGHTNESS_PATH, O_RDWR);
+    if (fd < 0) {
+        perror("cannot open LCD backlight");
+        return;
+    }
+    write(fd, blank ? "000" : brightness, 3);
+    close(fd);
+
+#ifdef TW_SECONDARY_BRIGHTNESS_PATH
+    fd = open(TW_SECONDARY_BRIGHTNESS_PATH, O_RDWR);
+    if (fd < 0) {
+        perror("cannot open LCD backlight 2");
+        return;
+    }
+    write(fd, blank ? "000" : brightness, 3);
+    close(fd);
+#endif
+#else
+    int ret;
+
+    ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
+    if (ret < 0)
+        perror("ioctl(): blank");
+#endif
+}
+
+#ifdef MSM_BSP
+void setDisplaySplit(void) {
+    char split[64] = {0};
+    if (!isMDP5)
+        return;
+    FILE* fp = fopen("/sys/class/graphics/fb0/msm_fb_split", "r");
+    if (fp) {
+        //Format "left right" space as delimiter
+        if(fread(split, sizeof(char), 64, fp)) {
+            leftSplit = atoi(split);
+            printf("Left Split=%d\n",leftSplit);
+            char *rght = strpbrk(split, " ");
+            if (rght)
+                rightSplit = atoi(rght + 1);
+            printf("Right Split=%d\n", rightSplit);
+        }
+    } else {
+        printf("Failed to open mdss_fb_split node\n");
+    }
+    if (fp)
+        fclose(fp);
+}
+
+int getLeftSplit(void) {
+   //Default even split for all displays with high res
+   int lSplit = vi.xres / 2;
+
+   //Override if split published by driver
+   if (leftSplit)
+       lSplit = leftSplit;
+
+   return lSplit;
+}
+
+int getRightSplit(void) {
+   return rightSplit;
+}
+
+int free_ion_mem(void) {
+    int ret = 0;
+
+    if (mem_info.mem_buf)
+        munmap(mem_info.mem_buf, mem_info.size);
+
+    if (mem_info.ion_fd >= 0) {
+        ret = ioctl(mem_info.ion_fd, ION_IOC_FREE, &mem_info.handle_data);
+        if (ret < 0)
+            perror("free_mem failed ");
+    }
+
+    if (mem_info.mem_fd >= 0)
+        close(mem_info.mem_fd);
+    if (mem_info.ion_fd >= 0)
+        close(mem_info.ion_fd);
+
+    memset(&mem_info, 0, sizeof(mem_info));
+    mem_info.mem_fd = -1;
+    mem_info.ion_fd = -1;
+    return 0;
+}
+
+int alloc_ion_mem(unsigned int size)
+{
+    int result;
+    struct ion_fd_data fd_data;
+    struct ion_allocation_data ionAllocData;
+
+    mem_info.ion_fd = open("/dev/ion", O_RDWR|O_DSYNC);
+    if (mem_info.ion_fd < 0) {
+        perror("ERROR: Can't open ion ");
+        return -errno;
+    }
+
+    ionAllocData.flags = 0;
+    ionAllocData.len = size;
+    ionAllocData.align = sysconf(_SC_PAGESIZE);
+#ifdef NEW_ION_HEAP
+    ionAllocData.heap_id_mask =
+#else
+    ionAllocData.heap_mask =
+#endif
+            ION_HEAP(ION_IOMMU_HEAP_ID) |
+            ION_HEAP(ION_SYSTEM_CONTIG_HEAP_ID);
+
+    result = ioctl(mem_info.ion_fd, ION_IOC_ALLOC,  &ionAllocData);
+    if(result){
+        perror("ION_IOC_ALLOC Failed ");
+        close(mem_info.ion_fd);
+        return result;
+    }
+
+    fd_data.handle = ionAllocData.handle;
+    mem_info.handle_data.handle = ionAllocData.handle;
+    result = ioctl(mem_info.ion_fd, ION_IOC_MAP, &fd_data);
+    if (result) {
+        perror("ION_IOC_MAP Failed ");
+        free_ion_mem();
+        return result;
+    }
+    mem_info.mem_buf = (unsigned char *)mmap(NULL, size, PROT_READ |
+                PROT_WRITE, MAP_SHARED, fd_data.fd, 0);
+    mem_info.mem_fd = fd_data.fd;
+
+    if (!mem_info.mem_buf) {
+        perror("ERROR: mem_buf MAP_FAILED ");
+        free_ion_mem();
+        return -ENOMEM;
+    }
+
+    return 0;
+}
+
+bool isDisplaySplit(void) {
+    if (vi.xres > MAX_DISPLAY_DIM)
+        return true;
+    //check if right split is set by driver
+    if (getRightSplit())
+        return true;
+
+    return false;
+}
+
+int allocate_overlay(int fd, GRSurface gr_fb)
+{
+    int ret = 0;
+
+    if (!isDisplaySplit()) {
+        // Check if overlay is already allocated
+        if (MSMFB_NEW_REQUEST == overlayL_id) {
+            struct mdp_overlay overlayL;
+
+            memset(&overlayL, 0 , sizeof (struct mdp_overlay));
+
+            /* Fill Overlay Data */
+            overlayL.src.width  = ALIGN(gr_fb.width, 32);
+            overlayL.src.height = gr_fb.height;
+            overlayL.src.format = map_mdp_pixel_format();
+            overlayL.src_rect.w = gr_fb.width;
+            overlayL.src_rect.h = gr_fb.height;
+            overlayL.dst_rect.w = gr_fb.width;
+            overlayL.dst_rect.h = gr_fb.height;
+            overlayL.alpha = 0xFF;
+            // If this worked, life would have been so much easier
+            //switch (gr_rotation) {
+                //case   0:  overlayL.flags = MDP_ROT_NOP; break;
+                //case  90:  overlayL.flags = MDP_ROT_90;  break;
+                //case 180:  overlayL.flags = MDP_ROT_180; break;
+                //case 270:  overlayL.flags = MDP_ROT_270; break;
+            //}
+            overlayL.transp_mask = MDP_TRANSP_NOP;
+            overlayL.id = MSMFB_NEW_REQUEST;
+            ret = ioctl(fd, MSMFB_OVERLAY_SET, &overlayL);
+            if (ret < 0) {
+                perror("Overlay Set Failed");
+                return ret;
+            }
+            overlayL_id = overlayL.id;
+        }
+    } else {
+        float xres = vi.xres;
+        int lSplit = getLeftSplit();
+        float lSplitRatio = lSplit / xres;
+        float lCropWidth = gr_fb.width * lSplitRatio;
+        int lWidth = lSplit;
+        int rWidth = gr_fb.width - lSplit;
+        int height = gr_fb.height;
+
+        if (MSMFB_NEW_REQUEST == overlayL_id) {
+
+            struct mdp_overlay overlayL;
+
+            memset(&overlayL, 0 , sizeof (struct mdp_overlay));
+
+            /* Fill OverlayL Data */
+            overlayL.src.width  = ALIGN(gr_fb.width, 32);
+            overlayL.src.height = gr_fb.height;
+            overlayL.src.format = map_mdp_pixel_format();
+            overlayL.src_rect.x = 0;
+            overlayL.src_rect.y = 0;
+            overlayL.src_rect.w = lCropWidth;
+            overlayL.src_rect.h = gr_fb.height;
+            overlayL.dst_rect.x = 0;
+            overlayL.dst_rect.y = 0;
+            overlayL.dst_rect.w = lWidth;
+            overlayL.dst_rect.h = height;
+            overlayL.alpha = 0xFF;
+            // If this worked, life would have been so much easier
+            //switch (gr_rotation) {
+                //case   0:  overlayL.flags = MDP_ROT_NOP; break;
+                //case  90:  overlayL.flags = MDP_ROT_90;  break;
+                //case 180:  overlayL.flags = MDP_ROT_180; break;
+                //case 270:  overlayL.flags = MDP_ROT_270; break;
+            //}
+            overlayL.transp_mask = MDP_TRANSP_NOP;
+            overlayL.id = MSMFB_NEW_REQUEST;
+            ret = ioctl(fd, MSMFB_OVERLAY_SET, &overlayL);
+            if (ret < 0) {
+                perror("OverlayL Set Failed");
+                return ret;
+            }
+            overlayL_id = overlayL.id;
+        }
+        if (MSMFB_NEW_REQUEST == overlayR_id) {
+            struct mdp_overlay overlayR;
+
+            memset(&overlayR, 0 , sizeof (struct mdp_overlay));
+
+            /* Fill OverlayR Data */
+            overlayR.src.width  = ALIGN(gr_fb.width, 32);
+            overlayR.src.height = gr_fb.height;
+            overlayR.src.format = map_mdp_pixel_format();
+            overlayR.src_rect.x = lCropWidth;
+            overlayR.src_rect.y = 0;
+            overlayR.src_rect.w = gr_fb.width - lCropWidth;
+            overlayR.src_rect.h = gr_fb.height;
+            overlayR.dst_rect.x = 0;
+            overlayR.dst_rect.y = 0;
+            overlayR.dst_rect.w = rWidth;
+            overlayR.dst_rect.h = height;
+            overlayR.alpha = 0xFF;
+            overlayR.flags = MDSS_MDP_RIGHT_MIXER;
+            // If this worked, life would have been so much easier
+            //switch (gr_rotation) {
+                //case   0:  overlayR.flags |= MDP_ROT_NOP; break;
+                //case  90:  overlayR.flags |= MDP_ROT_90;  break;
+                //case 180:  overlayR.flags |= MDP_ROT_180; break;
+                //case 270:  overlayR.flags |= MDP_ROT_270; break;
+            //}
+            overlayR.transp_mask = MDP_TRANSP_NOP;
+            overlayR.id = MSMFB_NEW_REQUEST;
+            ret = ioctl(fd, MSMFB_OVERLAY_SET, &overlayR);
+            if (ret < 0) {
+                perror("OverlayR Set Failed");
+                return ret;
+            }
+            overlayR_id = overlayR.id;
+        }
+
+    }
+    return 0;
+}
+
+int overlay_display_frame(int fd, void* data, size_t size)
+{
+    int ret = 0;
+    struct msmfb_overlay_data ovdataL, ovdataR;
+    struct mdp_display_commit ext_commit;
+
+    if (!isDisplaySplit()) {
+        if (overlayL_id == MSMFB_NEW_REQUEST) {
+            perror("display_frame failed, no overlay\n");
+            return -EINVAL;
+        }
+
+        memcpy(mem_info.mem_buf, data, size);
+
+        memset(&ovdataL, 0, sizeof(struct msmfb_overlay_data));
+
+        ovdataL.id = overlayL_id;
+        ovdataL.data.flags = 0;
+        ovdataL.data.offset = 0;
+        ovdataL.data.memory_id = mem_info.mem_fd;
+        ret = ioctl(fd, MSMFB_OVERLAY_PLAY, &ovdataL);
+        if (ret < 0) {
+            perror("overlay_display_frame failed, overlay play Failed\n");
+            printf("%i, %i, %i, %i\n", ret, fb_fd, fd, errno);
+            return ret;
+        }
+    } else {
+
+        if (overlayL_id == MSMFB_NEW_REQUEST) {
+            perror("display_frame failed, no overlayL \n");
+            return -EINVAL;
+        }
+
+        memcpy(mem_info.mem_buf, data, size);
+
+        memset(&ovdataL, 0, sizeof(struct msmfb_overlay_data));
+
+        ovdataL.id = overlayL_id;
+        ovdataL.data.flags = 0;
+        ovdataL.data.offset = 0;
+        ovdataL.data.memory_id = mem_info.mem_fd;
+        ret = ioctl(fd, MSMFB_OVERLAY_PLAY, &ovdataL);
+        if (ret < 0) {
+            perror("overlay_display_frame failed, overlayL play Failed\n");
+            return ret;
+        }
+
+        if (overlayR_id == MSMFB_NEW_REQUEST) {
+            perror("display_frame failed, no overlayR \n");
+            return -EINVAL;
+        }
+        memset(&ovdataR, 0, sizeof(struct msmfb_overlay_data));
+
+        ovdataR.id = overlayR_id;
+        ovdataR.data.flags = 0;
+        ovdataR.data.offset = 0;
+        ovdataR.data.memory_id = mem_info.mem_fd;
+        ret = ioctl(fd, MSMFB_OVERLAY_PLAY, &ovdataR);
+        if (ret < 0) {
+            perror("overlay_display_frame failed, overlayR play Failed\n");
+            return ret;
+        }
+    }
+    memset(&ext_commit, 0, sizeof(struct mdp_display_commit));
+    ext_commit.flags = MDP_DISPLAY_COMMIT_OVERLAY;
+    ext_commit.wait_for_finish = 1;
+    ret = ioctl(fd, MSMFB_DISPLAY_COMMIT, &ext_commit);
+    if (ret < 0) {
+        perror("overlay_display_frame failed, overlay commit Failed\n!");
+    }
+
+    return ret;
+}
+
+static GRSurface* overlay_flip(minui_backend* backend __unused) {
+#if defined(RECOVERY_BGRA)
+    // In case of BGRA, do some byte swapping
+    unsigned char* ucfb_vaddr = (unsigned char*)gr_draw->data;
+    for (int idx = 0 ; idx < (gr_draw->height * gr_draw->row_bytes);
+            idx += 4) {
+        unsigned char tmp = ucfb_vaddr[idx];
+        ucfb_vaddr[idx    ] = ucfb_vaddr[idx + 2];
+        ucfb_vaddr[idx + 2] = tmp;
+    }
+#endif
+    // Copy from the in-memory surface to the framebuffer.
+    overlay_display_frame(fb_fd, gr_draw->data, frame_size);
+    return gr_draw;
+}
+
+int free_overlay(int fd)
+{
+    int ret = 0;
+    struct mdp_display_commit ext_commit;
+
+    if (!isDisplaySplit()) {
+        if (overlayL_id != MSMFB_NEW_REQUEST) {
+            ret = ioctl(fd, MSMFB_OVERLAY_UNSET, &overlayL_id);
+            if (ret) {
+                perror("Overlay Unset Failed");
+                overlayL_id = MSMFB_NEW_REQUEST;
+                return ret;
+            }
+        }
+    } else {
+
+        if (overlayL_id != MSMFB_NEW_REQUEST) {
+            ret = ioctl(fd, MSMFB_OVERLAY_UNSET, &overlayL_id);
+            if (ret) {
+                perror("OverlayL Unset Failed");
+            }
+        }
+
+        if (overlayR_id != MSMFB_NEW_REQUEST) {
+            ret = ioctl(fd, MSMFB_OVERLAY_UNSET, &overlayR_id);
+            if (ret) {
+                perror("OverlayR Unset Failed");
+                overlayR_id = MSMFB_NEW_REQUEST;
+                return ret;
+            }
+        }
+    }
+    memset(&ext_commit, 0, sizeof(struct mdp_display_commit));
+    ext_commit.flags = MDP_DISPLAY_COMMIT_OVERLAY;
+    ext_commit.wait_for_finish = 1;
+    ret = ioctl(fd, MSMFB_DISPLAY_COMMIT, &ext_commit);
+    if (ret < 0) {
+        perror("ERROR: Clear MSMFB_DISPLAY_COMMIT failed!");
+        overlayL_id = MSMFB_NEW_REQUEST;
+        overlayR_id = MSMFB_NEW_REQUEST;
+        return ret;
+    }
+    overlayL_id = MSMFB_NEW_REQUEST;
+    overlayR_id = MSMFB_NEW_REQUEST;
+
+    return 0;
+}
+
+static GRSurface* overlay_init(minui_backend* backend) {
+    int fd = open("/dev/graphics/fb0", O_RDWR);
+    if (fd == -1) {
+        perror("cannot open fb0");
+        return NULL;
+    }
+
+    fb_fix_screeninfo fi;
+    if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
+        perror("failed to get fb0 info");
+        close(fd);
+        return NULL;
+    }
+
+    if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
+        perror("failed to get fb0 info");
+        close(fd);
+        return NULL;
+    }
+
+    // We print this out for informational purposes only, but
+    // throughout we assume that the framebuffer device uses an RGBX
+    // pixel format.  This is the case for every development device I
+    // have access to.  For some of those devices (eg, hammerhead aka
+    // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a
+    // different format (XBGR) but actually produces the correct
+    // results on the display when you write RGBX.
+    //
+    // If you have a device that actually *needs* another pixel format
+    // (ie, BGRX, or 565), patches welcome...
+
+    printf("fb0 reports (possibly inaccurate):\n"
+           "  vi.bits_per_pixel = %d\n"
+           "  vi.red.offset   = %3d   .length = %3d\n"
+           "  vi.green.offset = %3d   .length = %3d\n"
+           "  vi.blue.offset  = %3d   .length = %3d\n",
+           vi.bits_per_pixel,
+           vi.red.offset, vi.red.length,
+           vi.green.offset, vi.green.length,
+           vi.blue.offset, vi.blue.length);
+
+    gr_framebuffer.width = vi.xres;
+    gr_framebuffer.height = vi.yres;
+    gr_framebuffer.row_bytes = fi.line_length;
+    gr_framebuffer.pixel_bytes = vi.bits_per_pixel / 8;
+    //gr_framebuffer.data = reinterpret_cast<uint8_t*>(bits);
+    if (vi.bits_per_pixel == 16) {
+        printf("setting GGL_PIXEL_FORMAT_RGB_565\n");
+        gr_framebuffer.format = GGL_PIXEL_FORMAT_RGB_565;
+    } else if (vi.red.offset == 8 || vi.red.offset == 16) {
+        printf("setting GGL_PIXEL_FORMAT_BGRA_8888\n");
+        gr_framebuffer.format = GGL_PIXEL_FORMAT_BGRA_8888;
+    } else if (vi.red.offset == 0) {
+        printf("setting GGL_PIXEL_FORMAT_RGBA_8888\n");
+        gr_framebuffer.format = GGL_PIXEL_FORMAT_RGBA_8888;
+    } else if (vi.red.offset == 24) {
+        printf("setting GGL_PIXEL_FORMAT_RGBX_8888\n");
+        gr_framebuffer.format = GGL_PIXEL_FORMAT_RGBX_8888;
+    } else {
+        if (vi.red.length == 8) {
+            printf("No valid pixel format detected, trying GGL_PIXEL_FORMAT_RGBX_8888\n");
+            gr_framebuffer.format = GGL_PIXEL_FORMAT_RGBX_8888;
+        } else {
+            printf("No valid pixel format detected, trying GGL_PIXEL_FORMAT_RGB_565\n");
+            gr_framebuffer.format = GGL_PIXEL_FORMAT_RGB_565;
+        }
+    }
+
+    frame_size = fi.line_length * vi.yres;
+
+    gr_framebuffer.data = reinterpret_cast<uint8_t*>(calloc(frame_size, 1));
+    if (gr_framebuffer.data == NULL) {
+        perror("failed to calloc framebuffer");
+        close(fd);
+        return NULL;
+    }
+
+    gr_draw = &gr_framebuffer;
+    fb_fd = fd;
+
+    printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
+
+    overlay_blank(backend, true);
+    overlay_blank(backend, false);
+
+    if (!alloc_ion_mem(frame_size))
+        allocate_overlay(fb_fd, gr_framebuffer);
+
+    return gr_draw;
+}
+
+static void overlay_exit(minui_backend* backend __unused) {
+    free_overlay(fb_fd);
+    free_ion_mem();
+
+    close(fb_fd);
+    fb_fd = -1;
+
+    if (gr_draw) {
+        free(gr_draw->data);
+        free(gr_draw);
+    }
+    gr_draw = NULL;
+}
+#else // MSM_BSP
+static GRSurface* overlay_flip(minui_backend* backend __unused) {
+    return NULL;
+}
+
+static GRSurface* overlay_init(minui_backend* backend __unused) {
+    return NULL;
+}
+
+static void overlay_exit(minui_backend* backend __unused) {
+    return;
+}
+#endif // MSM_BSP
diff --git a/minuitwrp/graphics_utils.cpp b/minuitwrp/graphics_utils.cpp
new file mode 100644
index 0000000..03b333a
--- /dev/null
+++ b/minuitwrp/graphics_utils.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <png.h>
+#include <pixelflinger/pixelflinger.h>
+#include <linux/fb.h>
+#include <string.h>
+
+#include "minuitwrp/minui.h"
+
+struct fb_var_screeninfo vi;
+extern GGLSurface gr_mem_surface;
+extern GRSurface* gr_draw;
+extern unsigned int gr_rotation;
+
+int gr_save_screenshot(const char *dest)
+{
+    uint32_t y, stride_bytes;
+    volatile int res = -1;
+    GGLContext *gl = NULL;
+    GGLSurface surface;
+    uint8_t * volatile img_data = NULL;
+    uint8_t *ptr;
+    FILE * volatile fp = NULL;
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+
+    fp = fopen(dest, "wb");
+    if(!fp)
+        goto exit;
+
+    img_data = (uint8_t *)malloc(gr_mem_surface.stride * gr_mem_surface.height * gr_draw->pixel_bytes);
+    if (!img_data) {
+        printf("gr_save_screenshot failed to malloc img_data\n");
+        goto exit;
+    }
+    surface.version = sizeof(surface);
+    surface.width = gr_mem_surface.width;
+    surface.height = gr_mem_surface.height;
+    surface.stride = gr_mem_surface.stride;
+    surface.data = img_data;
+
+#if defined(RECOVERY_BGRA)
+    surface.format = GGL_PIXEL_FORMAT_BGRA_8888;
+#else
+    surface.format = GGL_PIXEL_FORMAT_RGBA_8888;
+#endif
+
+    gglInit(&gl);
+    gl->colorBuffer(gl, &surface);
+    gl->activeTexture(gl, 0);
+
+    if(gr_mem_surface.format == GGL_PIXEL_FORMAT_RGBX_8888)
+        gl->disable(gl, GGL_BLEND);
+
+    gl->bindTexture(gl, &gr_mem_surface);
+    gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
+    gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+    gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+    gl->enable(gl, GGL_TEXTURE_2D);
+    gl->texCoord2i(gl, 0, 0);
+    gl->recti(gl, 0, 0, gr_mem_surface.width, gr_mem_surface.height);
+
+    gglUninit(gl);
+    gl = NULL;
+
+    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (!png_ptr)
+        goto exit;
+
+    info_ptr = png_create_info_struct(png_ptr);
+    if (info_ptr == NULL)
+        goto exit;
+
+    if (setjmp(png_jmpbuf(png_ptr)))
+        goto exit;
+
+    png_init_io(png_ptr, fp);
+    png_set_IHDR(png_ptr, info_ptr, surface.width, surface.height,
+         8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
+         PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+    png_write_info(png_ptr, info_ptr);
+
+    // To remove the alpha channel for PNG_COLOR_TYPE_RGB format,
+    png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
+
+    ptr = img_data;
+    stride_bytes = surface.stride*4;
+    for(y = 0; y < surface.height; ++y)
+    {
+        png_write_row(png_ptr, ptr);
+        ptr += stride_bytes;
+    }
+
+    png_write_end(png_ptr, NULL);
+
+    res = 0;
+exit:
+    if(info_ptr)
+        png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
+    if(png_ptr)
+        png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+    if(gl)
+        gglUninit(gl);
+    if(img_data)
+        free(img_data);
+    if(fp)
+        fclose(fp);
+    return res;
+}
+
+int ROTATION_X_DISP(int x, int y, int w) {
+    return ((gr_rotation ==   0) ? (x) :
+            (gr_rotation ==  90) ? (w - (y) - 1) :
+            (gr_rotation == 180) ? (w - (x) - 1) :
+            (gr_rotation == 270) ? (y) : -1);
+}
+
+int ROTATION_Y_DISP(int x, int y, int h) {
+    return ((gr_rotation ==   0) ? (y) :
+            (gr_rotation ==  90) ? (x) :
+            (gr_rotation == 180) ? (h - (y) - 1) :
+            (gr_rotation == 270) ? (h - (x) - 1) : -1);
+}
+
+#define MATRIX_ELEMENT(matrix, row, col, row_size, elem_size) \
+    (((uint8_t*) (matrix)) + (((row) * (elem_size)) * (row_size)) + ((col) * (elem_size)))
+
+#define DO_MATRIX_ROTATION(bits_per_pixel, bytes_per_pixel)                   \
+{                                                                             \
+    for (size_t y = 0; y < src->height; y++) {                                \
+        for (size_t x = 0; x < src->width; x++) {                             \
+            /* output pointer in dst->data */                                 \
+            uint##bits_per_pixel##_t       *op;                               \
+            /* input pointer from src->data */                                \
+            const uint##bits_per_pixel##_t *ip;                               \
+            /* Display coordinates (in dst) corresponding to (x, y) in src */ \
+            size_t x_disp = ROTATION_X_DISP(x, y, dst->width);                \
+            size_t y_disp = ROTATION_Y_DISP(x, y, dst->height);               \
+                                                                              \
+            ip = (const uint##bits_per_pixel##_t*)                            \
+                 MATRIX_ELEMENT(src->data, y, x,                              \
+                                src->stride, bytes_per_pixel);                \
+            op = (uint##bits_per_pixel##_t*)                                  \
+                 MATRIX_ELEMENT(dst->data, y_disp, x_disp,                    \
+                                dst->stride, bytes_per_pixel);                \
+            *op = *ip;                                                        \
+        }                                                                     \
+    }                                                                         \
+}
+
+void surface_ROTATION_transform(gr_surface dst_ptr, const gr_surface src_ptr,
+                                  size_t num_bytes_per_pixel)
+{
+    GGLSurface *dst = (GGLSurface*) dst_ptr;
+    const GGLSurface *src = (GGLSurface*) src_ptr;
+
+    /* Handle duplicated code via a macro.
+     * This is currently used for rotating surfaces of graphical resources
+     * (32-bit pixel format) and of font glyphs (8-bit pixel format).
+     * If you need to add handling of other pixel formats feel free to do so.
+     */
+    if (num_bytes_per_pixel == 4) {
+        DO_MATRIX_ROTATION(32, 4);
+    } else if (num_bytes_per_pixel == 1) {
+        DO_MATRIX_ROTATION(8, 1);
+    }
+}
diff --git a/minuitwrp/include/linux/msm_ion.h b/minuitwrp/include/linux/msm_ion.h
new file mode 100644
index 0000000..121a5a7
--- /dev/null
+++ b/minuitwrp/include/linux/msm_ion.h
@@ -0,0 +1,157 @@
+#ifndef _LINUX_MSM_ION_H
+#define _LINUX_MSM_ION_H
+
+#include <linux/ion.h>
+
+/*enum msm_ion_heap_types {
+	ION_HEAP_TYPE_MSM_START = ION_HEAP_TYPE_CUSTOM + 1,
+	ION_HEAP_TYPE_IOMMU = ION_HEAP_TYPE_MSM_START,
+	ION_HEAP_TYPE_DMA,
+	ION_HEAP_TYPE_CP,
+	ION_HEAP_TYPE_SECURE_DMA,
+	ION_HEAP_TYPE_REMOVED,
+};*/
+
+/**
+ * These are the only ids that should be used for Ion heap ids.
+ * The ids listed are the order in which allocation will be attempted
+ * if specified. Don't swap the order of heap ids unless you know what
+ * you are doing!
+ * Id's are spaced by purpose to allow new Id's to be inserted in-between (for
+ * possible fallbacks)
+ */
+
+enum ion_heap_ids {
+	INVALID_HEAP_ID = -1,
+	ION_CP_MM_HEAP_ID = 8,
+	ION_CP_MFC_HEAP_ID = 12,
+	ION_CP_WB_HEAP_ID = 16, /* 8660 only */
+	ION_CAMERA_HEAP_ID = 20, /* 8660 only */
+	ION_SYSTEM_CONTIG_HEAP_ID = 21,
+	ION_ADSP_HEAP_ID = 22,
+	ION_PIL1_HEAP_ID = 23, /* Currently used for other PIL images */
+	ION_SF_HEAP_ID = 24,
+	ION_IOMMU_HEAP_ID = 25,
+	ION_PIL2_HEAP_ID = 26, /* Currently used for modem firmware images */
+	ION_QSECOM_HEAP_ID = 27,
+	ION_AUDIO_HEAP_ID = 28,
+
+	ION_MM_FIRMWARE_HEAP_ID = 29,
+	ION_SYSTEM_HEAP_ID = 30,
+
+	ION_HEAP_ID_RESERVED = 31 /** Bit reserved for ION_FLAG_SECURE flag */
+};
+
+/*enum ion_fixed_position {
+	NOT_FIXED,
+	FIXED_LOW,
+	FIXED_MIDDLE,
+	FIXED_HIGH,
+};*/
+
+/*enum cp_mem_usage {
+	VIDEO_BITSTREAM = 0x1,
+	VIDEO_PIXEL = 0x2,
+	VIDEO_NONPIXEL = 0x3,
+	MAX_USAGE = 0x4,
+	UNKNOWN = 0x7FFFFFFF,
+};*/
+
+#define ION_HEAP_CP_MASK		(1 << ION_HEAP_TYPE_CP)
+#define ION_HEAP_TYPE_DMA_MASK         (1 << ION_HEAP_TYPE_DMA)
+
+/**
+ * Flag to use when allocating to indicate that a heap is secure.
+ */
+#define ION_FLAG_SECURE (1 << ION_HEAP_ID_RESERVED)
+
+/**
+ * Flag for clients to force contiguous memort allocation
+ *
+ * Use of this flag is carefully monitored!
+ */
+#define ION_FLAG_FORCE_CONTIGUOUS (1 << 30)
+
+/*
+ * Used in conjunction with heap which pool memory to force an allocation
+ * to come from the page allocator directly instead of from the pool allocation
+ */
+#define ION_FLAG_POOL_FORCE_ALLOC (1 << 16)
+
+/**
+* Deprecated! Please use the corresponding ION_FLAG_*
+*/
+#define ION_SECURE ION_FLAG_SECURE
+#define ION_FORCE_CONTIGUOUS ION_FLAG_FORCE_CONTIGUOUS
+
+/**
+ * Macro should be used with ion_heap_ids defined above.
+ */
+#define ION_HEAP(bit) (1 << (bit))
+
+#define ION_ADSP_HEAP_NAME	"adsp"
+#define ION_VMALLOC_HEAP_NAME	"vmalloc"
+#define ION_KMALLOC_HEAP_NAME	"kmalloc"
+#define ION_AUDIO_HEAP_NAME	"audio"
+#define ION_SF_HEAP_NAME	"sf"
+#define ION_MM_HEAP_NAME	"mm"
+#define ION_CAMERA_HEAP_NAME	"camera_preview"
+#define ION_IOMMU_HEAP_NAME	"iommu"
+#define ION_MFC_HEAP_NAME	"mfc"
+#define ION_WB_HEAP_NAME	"wb"
+#define ION_MM_FIRMWARE_HEAP_NAME	"mm_fw"
+#define ION_PIL1_HEAP_NAME  "pil_1"
+#define ION_PIL2_HEAP_NAME  "pil_2"
+#define ION_QSECOM_HEAP_NAME	"qsecom"
+
+#define ION_SET_CACHED(__cache)		(__cache | ION_FLAG_CACHED)
+#define ION_SET_UNCACHED(__cache)	(__cache & ~ION_FLAG_CACHED)
+
+#define ION_IS_CACHED(__flags)	((__flags) & ION_FLAG_CACHED)
+
+
+/* struct ion_flush_data - data passed to ion for flushing caches
+ *
+ * @handle:	handle with data to flush
+ * @fd:		fd to flush
+ * @vaddr:	userspace virtual address mapped with mmap
+ * @offset:	offset into the handle to flush
+ * @length:	length of handle to flush
+ *
+ * Performs cache operations on the handle. If p is the start address
+ * of the handle, p + offset through p + offset + length will have
+ * the cache operations performed
+ */
+struct ion_flush_data {
+	struct ion_handle *handle;
+	int fd;
+	void *vaddr;
+	unsigned int offset;
+	unsigned int length;
+};
+
+#define ION_IOC_MSM_MAGIC 'M'
+
+/**
+ * DOC: ION_IOC_CLEAN_CACHES - clean the caches
+ *
+ * Clean the caches of the handle specified.
+ */
+#define ION_IOC_CLEAN_CACHES	_IOWR(ION_IOC_MSM_MAGIC, 0, \
+						struct ion_flush_data)
+/**
+ * DOC: ION_IOC_INV_CACHES - invalidate the caches
+ *
+ * Invalidate the caches of the handle specified.
+ */
+#define ION_IOC_INV_CACHES	_IOWR(ION_IOC_MSM_MAGIC, 1, \
+						struct ion_flush_data)
+/**
+ * DOC: ION_IOC_CLEAN_INV_CACHES - clean and invalidate the caches
+ *
+ * Clean and invalidate the caches of the handle specified.
+ */
+#define ION_IOC_CLEAN_INV_CACHES	_IOWR(ION_IOC_MSM_MAGIC, 2, \
+						struct ion_flush_data)
+
+#endif
diff --git a/minuitwrp/include/linux/msm_mdp.h b/minuitwrp/include/linux/msm_mdp.h
new file mode 100644
index 0000000..abdcd29
--- /dev/null
+++ b/minuitwrp/include/linux/msm_mdp.h
@@ -0,0 +1,992 @@
+/* include/linux/msm_mdp.h
+ *
+ * Copyright (C) 2007 Google Incorporated
+ * Copyright (c) 2012-2013 The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _MSM_MDP_H_
+#define _MSM_MDP_H_
+
+#include <linux/types.h>
+#include <linux/fb.h>
+
+#define MSMFB_IOCTL_MAGIC 'm'
+#define MSMFB_GRP_DISP          _IOW(MSMFB_IOCTL_MAGIC, 1, unsigned int)
+#define MSMFB_BLIT              _IOW(MSMFB_IOCTL_MAGIC, 2, unsigned int)
+#define MSMFB_SUSPEND_SW_REFRESHER _IOW(MSMFB_IOCTL_MAGIC, 128, unsigned int)
+#define MSMFB_RESUME_SW_REFRESHER _IOW(MSMFB_IOCTL_MAGIC, 129, unsigned int)
+#define MSMFB_CURSOR _IOW(MSMFB_IOCTL_MAGIC, 130, struct fb_cursor)
+#define MSMFB_SET_LUT _IOW(MSMFB_IOCTL_MAGIC, 131, struct fb_cmap)
+#define MSMFB_HISTOGRAM _IOWR(MSMFB_IOCTL_MAGIC, 132, struct mdp_histogram_data)
+/* new ioctls's for set/get ccs matrix */
+#define MSMFB_GET_CCS_MATRIX  _IOWR(MSMFB_IOCTL_MAGIC, 133, struct mdp_ccs)
+#define MSMFB_SET_CCS_MATRIX  _IOW(MSMFB_IOCTL_MAGIC, 134, struct mdp_ccs)
+#define MSMFB_OVERLAY_SET       _IOWR(MSMFB_IOCTL_MAGIC, 135, \
+						struct mdp_overlay)
+#define MSMFB_OVERLAY_UNSET     _IOW(MSMFB_IOCTL_MAGIC, 136, unsigned int)
+
+#define MSMFB_OVERLAY_PLAY      _IOW(MSMFB_IOCTL_MAGIC, 137, \
+						struct msmfb_overlay_data)
+#define MSMFB_OVERLAY_QUEUE	MSMFB_OVERLAY_PLAY
+
+#define MSMFB_GET_PAGE_PROTECTION _IOR(MSMFB_IOCTL_MAGIC, 138, \
+					struct mdp_page_protection)
+#define MSMFB_SET_PAGE_PROTECTION _IOW(MSMFB_IOCTL_MAGIC, 139, \
+					struct mdp_page_protection)
+#define MSMFB_OVERLAY_GET      _IOR(MSMFB_IOCTL_MAGIC, 140, \
+						struct mdp_overlay)
+#define MSMFB_OVERLAY_PLAY_ENABLE     _IOW(MSMFB_IOCTL_MAGIC, 141, unsigned int)
+#define MSMFB_OVERLAY_BLT       _IOWR(MSMFB_IOCTL_MAGIC, 142, \
+						struct msmfb_overlay_blt)
+#define MSMFB_OVERLAY_BLT_OFFSET     _IOW(MSMFB_IOCTL_MAGIC, 143, unsigned int)
+#define MSMFB_HISTOGRAM_START	_IOR(MSMFB_IOCTL_MAGIC, 144, \
+						struct mdp_histogram_start_req)
+#define MSMFB_HISTOGRAM_STOP	_IOR(MSMFB_IOCTL_MAGIC, 145, unsigned int)
+#define MSMFB_NOTIFY_UPDATE	_IOWR(MSMFB_IOCTL_MAGIC, 146, unsigned int)
+
+#define MSMFB_OVERLAY_3D       _IOWR(MSMFB_IOCTL_MAGIC, 147, \
+						struct msmfb_overlay_3d)
+
+#define MSMFB_MIXER_INFO       _IOWR(MSMFB_IOCTL_MAGIC, 148, \
+						struct msmfb_mixer_info_req)
+#define MSMFB_OVERLAY_PLAY_WAIT _IOWR(MSMFB_IOCTL_MAGIC, 149, \
+						struct msmfb_overlay_data)
+#define MSMFB_WRITEBACK_INIT _IO(MSMFB_IOCTL_MAGIC, 150)
+#define MSMFB_WRITEBACK_START _IO(MSMFB_IOCTL_MAGIC, 151)
+#define MSMFB_WRITEBACK_STOP _IO(MSMFB_IOCTL_MAGIC, 152)
+#define MSMFB_WRITEBACK_QUEUE_BUFFER _IOW(MSMFB_IOCTL_MAGIC, 153, \
+						struct msmfb_data)
+#define MSMFB_WRITEBACK_DEQUEUE_BUFFER _IOW(MSMFB_IOCTL_MAGIC, 154, \
+						struct msmfb_data)
+#define MSMFB_WRITEBACK_TERMINATE _IO(MSMFB_IOCTL_MAGIC, 155)
+#define MSMFB_MDP_PP _IOWR(MSMFB_IOCTL_MAGIC, 156, struct msmfb_mdp_pp)
+#define MSMFB_OVERLAY_VSYNC_CTRL _IOW(MSMFB_IOCTL_MAGIC, 160, unsigned int)
+#define MSMFB_VSYNC_CTRL  _IOW(MSMFB_IOCTL_MAGIC, 161, unsigned int)
+#define MSMFB_BUFFER_SYNC  _IOW(MSMFB_IOCTL_MAGIC, 162, struct mdp_buf_sync)
+#define MSMFB_OVERLAY_COMMIT      _IO(MSMFB_IOCTL_MAGIC, 163)
+#define MSMFB_DISPLAY_COMMIT      _IOW(MSMFB_IOCTL_MAGIC, 164, \
+						struct mdp_display_commit)
+#define MSMFB_METADATA_SET  _IOW(MSMFB_IOCTL_MAGIC, 165, struct msmfb_metadata)
+#define MSMFB_METADATA_GET  _IOW(MSMFB_IOCTL_MAGIC, 166, struct msmfb_metadata)
+#define MSMFB_WRITEBACK_SET_MIRRORING_HINT _IOW(MSMFB_IOCTL_MAGIC, 167, \
+						unsigned int)
+#define MSMFB_ASYNC_BLIT              _IOW(MSMFB_IOCTL_MAGIC, 168, unsigned int)
+
+#define FB_TYPE_3D_PANEL 0x10101010
+#define MDP_IMGTYPE2_START 0x10000
+#define MSMFB_DRIVER_VERSION	0xF9E8D701
+
+enum {
+	NOTIFY_UPDATE_START,
+	NOTIFY_UPDATE_STOP,
+	NOTIFY_UPDATE_POWER_OFF,
+};
+
+enum {
+	NOTIFY_TYPE_NO_UPDATE,
+	NOTIFY_TYPE_SUSPEND,
+	NOTIFY_TYPE_UPDATE,
+};
+
+enum {
+	MDP_RGB_565,      /* RGB 565 planer */
+	MDP_XRGB_8888,    /* RGB 888 padded */
+	MDP_Y_CBCR_H2V2,  /* Y and CbCr, pseudo planer w/ Cb is in MSB */
+	MDP_Y_CBCR_H2V2_ADRENO,
+	MDP_ARGB_8888,    /* ARGB 888 */
+	MDP_RGB_888,      /* RGB 888 planer */
+	MDP_Y_CRCB_H2V2,  /* Y and CrCb, pseudo planer w/ Cr is in MSB */
+	MDP_YCRYCB_H2V1,  /* YCrYCb interleave */
+	MDP_CBYCRY_H2V1,  /* CbYCrY interleave */
+	MDP_Y_CRCB_H2V1,  /* Y and CrCb, pseduo planer w/ Cr is in MSB */
+	MDP_Y_CBCR_H2V1,   /* Y and CrCb, pseduo planer w/ Cr is in MSB */
+	MDP_Y_CRCB_H1V2,
+	MDP_Y_CBCR_H1V2,
+	MDP_RGBA_8888,    /* ARGB 888 */
+	MDP_BGRA_8888,	  /* ABGR 888 */
+	MDP_RGBX_8888,	  /* RGBX 888 */
+	MDP_Y_CRCB_H2V2_TILE,  /* Y and CrCb, pseudo planer tile */
+	MDP_Y_CBCR_H2V2_TILE,  /* Y and CbCr, pseudo planer tile */
+	MDP_Y_CR_CB_H2V2,  /* Y, Cr and Cb, planar */
+	MDP_Y_CR_CB_GH2V2,  /* Y, Cr and Cb, planar aligned to Android YV12 */
+	MDP_Y_CB_CR_H2V2,  /* Y, Cb and Cr, planar */
+	MDP_Y_CRCB_H1V1,  /* Y and CrCb, pseduo planer w/ Cr is in MSB */
+	MDP_Y_CBCR_H1V1,  /* Y and CbCr, pseduo planer w/ Cb is in MSB */
+	MDP_YCRCB_H1V1,   /* YCrCb interleave */
+	MDP_YCBCR_H1V1,   /* YCbCr interleave */
+	MDP_BGR_565,      /* BGR 565 planer */
+	MDP_BGR_888,      /* BGR 888 */
+	MDP_Y_CBCR_H2V2_VENUS,
+	MDP_BGRX_8888,   /* BGRX 8888 */
+	MDP_RGBA_8888_TILE,	/* RGBA 8888 in tile format */
+	MDP_ARGB_8888_TILE,	/* ARGB 8888 in tile format */
+	MDP_ABGR_8888_TILE,	/* ABGR 8888 in tile format */
+	MDP_BGRA_8888_TILE,	/* BGRA 8888 in tile format */
+	MDP_RGBX_8888_TILE,	/* RGBX 8888 in tile format */
+	MDP_XRGB_8888_TILE,	/* XRGB 8888 in tile format */
+	MDP_XBGR_8888_TILE,	/* XBGR 8888 in tile format */
+	MDP_BGRX_8888_TILE,	/* BGRX 8888 in tile format */
+	MDP_YCBYCR_H2V1,  /* YCbYCr interleave */
+	MDP_IMGTYPE_LIMIT,
+	MDP_RGB_BORDERFILL,	/* border fill pipe */
+	MDP_FB_FORMAT = MDP_IMGTYPE2_START,    /* framebuffer format */
+	MDP_IMGTYPE_LIMIT2 /* Non valid image type after this enum */
+};
+
+enum {
+	PMEM_IMG,
+	FB_IMG,
+};
+
+enum {
+	HSIC_HUE = 0,
+	HSIC_SAT,
+	HSIC_INT,
+	HSIC_CON,
+	NUM_HSIC_PARAM,
+};
+
+#define MDSS_MDP_ROT_ONLY		0x80
+#define MDSS_MDP_RIGHT_MIXER		0x100
+#define MDSS_MDP_DUAL_PIPE		0x200
+
+/* mdp_blit_req flag values */
+#define MDP_ROT_NOP 0
+#define MDP_FLIP_LR 0x1
+#define MDP_FLIP_UD 0x2
+#define MDP_ROT_90 0x4
+#define MDP_ROT_180 (MDP_FLIP_UD|MDP_FLIP_LR)
+#define MDP_ROT_270 (MDP_ROT_90|MDP_FLIP_UD|MDP_FLIP_LR)
+#define MDP_DITHER 0x8
+#define MDP_BLUR 0x10
+#define MDP_BLEND_FG_PREMULT 0x20000
+#define MDP_IS_FG 0x40000
+#define MDP_SOLID_FILL 0x0000100
+#define MDP_DEINTERLACE 0x80000000
+#define MDP_SHARPENING  0x40000000
+#define MDP_NO_DMA_BARRIER_START	0x20000000
+#define MDP_NO_DMA_BARRIER_END		0x10000000
+#define MDP_NO_BLIT			0x08000000
+#define MDP_BLIT_WITH_DMA_BARRIERS	0x000
+#define MDP_BLIT_WITH_NO_DMA_BARRIERS    \
+	(MDP_NO_DMA_BARRIER_START | MDP_NO_DMA_BARRIER_END)
+#define MDP_BLIT_SRC_GEM                0x04000000
+#define MDP_BLIT_DST_GEM                0x02000000
+#define MDP_BLIT_NON_CACHED		0x01000000
+#define MDP_OV_PIPE_SHARE		0x00800000
+#define MDP_DEINTERLACE_ODD		0x00400000
+#define MDP_OV_PLAY_NOWAIT		0x00200000
+#define MDP_SOURCE_ROTATED_90		0x00100000
+#define MDP_OVERLAY_PP_CFG_EN		0x00080000
+#define MDP_BACKEND_COMPOSITION		0x00040000
+#define MDP_BORDERFILL_SUPPORTED	0x00010000
+#define MDP_SECURE_OVERLAY_SESSION      0x00008000
+#define MDP_SECURE_DISPLAY_OVERLAY_SESSION	0x00002000
+#define MDP_OV_PIPE_FORCE_DMA		0x00004000
+#define MDP_MEMORY_ID_TYPE_FB		0x00001000
+#define MDP_BWC_EN			0x00000400
+#define MDP_DECIMATION_EN		0x00000800
+#define MDP_TRANSP_NOP 0xffffffff
+#define MDP_ALPHA_NOP 0xff
+
+#define MDP_FB_PAGE_PROTECTION_NONCACHED         (0)
+#define MDP_FB_PAGE_PROTECTION_WRITECOMBINE      (1)
+#define MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE (2)
+#define MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE    (3)
+#define MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE  (4)
+/* Sentinel: Don't use! */
+#define MDP_FB_PAGE_PROTECTION_INVALID           (5)
+/* Count of the number of MDP_FB_PAGE_PROTECTION_... values. */
+#define MDP_NUM_FB_PAGE_PROTECTION_VALUES        (5)
+
+struct mdp_rect {
+	uint32_t x;
+	uint32_t y;
+	uint32_t w;
+	uint32_t h;
+};
+
+struct mdp_img {
+	uint32_t width;
+	uint32_t height;
+	uint32_t format;
+	uint32_t offset;
+	int memory_id;		/* the file descriptor */
+	uint32_t priv;
+};
+
+/*
+ * {3x3} + {3} ccs matrix
+ */
+
+#define MDP_CCS_RGB2YUV 	0
+#define MDP_CCS_YUV2RGB 	1
+
+#define MDP_CCS_SIZE	9
+#define MDP_BV_SIZE	3
+
+struct mdp_ccs {
+	int direction;			/* MDP_CCS_RGB2YUV or YUV2RGB */
+	uint16_t ccs[MDP_CCS_SIZE];	/* 3x3 color coefficients */
+	uint16_t bv[MDP_BV_SIZE];	/* 1x3 bias vector */
+};
+
+struct mdp_csc {
+	int id;
+	uint32_t csc_mv[9];
+	uint32_t csc_pre_bv[3];
+	uint32_t csc_post_bv[3];
+	uint32_t csc_pre_lv[6];
+	uint32_t csc_post_lv[6];
+};
+
+/* The version of the mdp_blit_req structure so that
+ * user applications can selectively decide which functionality
+ * to include
+ */
+
+#define MDP_BLIT_REQ_VERSION 2
+
+struct color {
+	uint32_t r;
+	uint32_t g;
+	uint32_t b;
+	uint32_t alpha;
+};
+
+struct mdp_blit_req {
+	struct mdp_img src;
+	struct mdp_img dst;
+	struct mdp_rect src_rect;
+	struct mdp_rect dst_rect;
+	struct color const_color;
+	uint32_t alpha;
+	uint32_t transp_mask;
+	uint32_t flags;
+	int sharpening_strength;  /* -127 <--> 127, default 64 */
+};
+
+struct mdp_blit_req_list {
+	uint32_t count;
+	struct mdp_blit_req req[];
+};
+
+#define MSMFB_DATA_VERSION 2
+
+struct msmfb_data {
+	uint32_t offset;
+	int memory_id;
+	int id;
+	uint32_t flags;
+	uint32_t priv;
+	uint32_t iova;
+};
+
+#define MSMFB_NEW_REQUEST -1
+
+struct msmfb_overlay_data {
+	uint32_t id;
+	struct msmfb_data data;
+	uint32_t version_key;
+	struct msmfb_data plane1_data;
+	struct msmfb_data plane2_data;
+	struct msmfb_data dst_data;
+};
+
+struct msmfb_img {
+	uint32_t width;
+	uint32_t height;
+	uint32_t format;
+};
+
+#define MSMFB_WRITEBACK_DEQUEUE_BLOCKING 0x1
+struct msmfb_writeback_data {
+	struct msmfb_data buf_info;
+	struct msmfb_img img;
+};
+
+#define MDP_PP_OPS_ENABLE 0x1
+#define MDP_PP_OPS_READ 0x2
+#define MDP_PP_OPS_WRITE 0x4
+#define MDP_PP_OPS_DISABLE 0x8
+#define MDP_PP_IGC_FLAG_ROM0	0x10
+#define MDP_PP_IGC_FLAG_ROM1	0x20
+
+#define MDSS_PP_DSPP_CFG	0x000
+#define MDSS_PP_SSPP_CFG	0x100
+#define MDSS_PP_LM_CFG	0x200
+#define MDSS_PP_WB_CFG	0x300
+
+#define MDSS_PP_ARG_MASK	0x3C00
+#define MDSS_PP_ARG_NUM		4
+#define MDSS_PP_ARG_SHIFT	10
+#define MDSS_PP_LOCATION_MASK	0x0300
+#define MDSS_PP_LOGICAL_MASK	0x00FF
+
+#define MDSS_PP_ADD_ARG(var, arg) ((var) | (0x1 << (MDSS_PP_ARG_SHIFT + (arg))))
+#define PP_ARG(x, var) ((var) & (0x1 << (MDSS_PP_ARG_SHIFT + (x))))
+#define PP_LOCAT(var) ((var) & MDSS_PP_LOCATION_MASK)
+#define PP_BLOCK(var) ((var) & MDSS_PP_LOGICAL_MASK)
+
+
+struct mdp_qseed_cfg {
+	uint32_t table_num;
+	uint32_t ops;
+	uint32_t len;
+	uint32_t *data;
+};
+
+struct mdp_sharp_cfg {
+	uint32_t flags;
+	uint32_t strength;
+	uint32_t edge_thr;
+	uint32_t smooth_thr;
+	uint32_t noise_thr;
+};
+
+struct mdp_qseed_cfg_data {
+	uint32_t block;
+	struct mdp_qseed_cfg qseed_data;
+};
+
+#define MDP_OVERLAY_PP_CSC_CFG         0x1
+#define MDP_OVERLAY_PP_QSEED_CFG       0x2
+#define MDP_OVERLAY_PP_PA_CFG          0x4
+#define MDP_OVERLAY_PP_IGC_CFG         0x8
+#define MDP_OVERLAY_PP_SHARP_CFG       0x10
+#define MDP_OVERLAY_PP_HIST_CFG        0x20
+#define MDP_OVERLAY_PP_HIST_LUT_CFG    0x40
+
+#define MDP_CSC_FLAG_ENABLE	0x1
+#define MDP_CSC_FLAG_YUV_IN	0x2
+#define MDP_CSC_FLAG_YUV_OUT	0x4
+
+struct mdp_csc_cfg {
+	/* flags for enable CSC, toggling RGB,YUV input/output */
+	uint32_t flags;
+	uint32_t csc_mv[9];
+	uint32_t csc_pre_bv[3];
+	uint32_t csc_post_bv[3];
+	uint32_t csc_pre_lv[6];
+	uint32_t csc_post_lv[6];
+};
+
+struct mdp_csc_cfg_data {
+	uint32_t block;
+	struct mdp_csc_cfg csc_data;
+};
+
+struct mdp_pa_cfg {
+	uint32_t flags;
+	uint32_t hue_adj;
+	uint32_t sat_adj;
+	uint32_t val_adj;
+	uint32_t cont_adj;
+};
+
+struct mdp_igc_lut_data {
+	uint32_t block;
+	uint32_t len, ops;
+	uint32_t *c0_c1_data;
+	uint32_t *c2_data;
+};
+
+struct mdp_histogram_cfg {
+	uint32_t ops;
+	uint32_t block;
+	uint8_t frame_cnt;
+	uint8_t bit_mask;
+	uint16_t num_bins;
+};
+
+struct mdp_hist_lut_data {
+	uint32_t block;
+	uint32_t ops;
+	uint32_t len;
+	uint32_t *data;
+};
+
+struct mdp_overlay_pp_params {
+	uint32_t config_ops;
+	struct mdp_csc_cfg csc_cfg;
+	struct mdp_qseed_cfg qseed_cfg[2];
+	struct mdp_pa_cfg pa_cfg;
+	struct mdp_igc_lut_data igc_cfg;
+	struct mdp_sharp_cfg sharp_cfg;
+	struct mdp_histogram_cfg hist_cfg;
+	struct mdp_hist_lut_data hist_lut_cfg;
+};
+
+/**
+ * enum mdss_mdp_blend_op - Different blend operations set by userspace
+ *
+ * @BLEND_OP_NOT_DEFINED:    No blend operation defined for the layer.
+ * @BLEND_OP_OPAQUE:         Apply a constant blend operation. The layer
+ *                           would appear opaque in case fg plane alpha is
+ *                           0xff.
+ * @BLEND_OP_PREMULTIPLIED:  Apply source over blend rule. Layer already has
+ *                           alpha pre-multiplication done. If fg plane alpha
+ *                           is less than 0xff, apply modulation as well. This
+ *                           operation is intended on layers having alpha
+ *                           channel.
+ * @BLEND_OP_COVERAGE:       Apply source over blend rule. Layer is not alpha
+ *                           pre-multiplied. Apply pre-multiplication. If fg
+ *                           plane alpha is less than 0xff, apply modulation as
+ *                           well.
+ * @BLEND_OP_MAX:            Used to track maximum blend operation possible by
+ *                           mdp.
+ */
+enum mdss_mdp_blend_op {
+	BLEND_OP_NOT_DEFINED = 0,
+	BLEND_OP_OPAQUE,
+	BLEND_OP_PREMULTIPLIED,
+	BLEND_OP_COVERAGE,
+	BLEND_OP_MAX,
+};
+
+#define MAX_PLANES	4
+struct mdp_scale_data {
+	uint8_t enable_pxl_ext;
+
+	int init_phase_x[MAX_PLANES];
+	int phase_step_x[MAX_PLANES];
+	int init_phase_y[MAX_PLANES];
+	int phase_step_y[MAX_PLANES];
+
+	int num_ext_pxls_left[MAX_PLANES];
+	int num_ext_pxls_right[MAX_PLANES];
+	int num_ext_pxls_top[MAX_PLANES];
+	int num_ext_pxls_btm[MAX_PLANES];
+
+	int left_ftch[MAX_PLANES];
+	int left_rpt[MAX_PLANES];
+	int right_ftch[MAX_PLANES];
+	int right_rpt[MAX_PLANES];
+
+	int top_rpt[MAX_PLANES];
+	int btm_rpt[MAX_PLANES];
+	int top_ftch[MAX_PLANES];
+	int btm_ftch[MAX_PLANES];
+
+	uint32_t roi_w[MAX_PLANES];
+};
+
+/**
+ * struct mdp_overlay - overlay surface structure
+ * @src:	Source image information (width, height, format).
+ * @src_rect:	Source crop rectangle, portion of image that will be fetched.
+ *		This should always be within boundaries of source image.
+ * @dst_rect:	Destination rectangle, the position and size of image on screen.
+ *		This should always be within panel boundaries.
+ * @z_order:	Blending stage to occupy in display, if multiple layers are
+ *		present, highest z_order usually means the top most visible
+ *		layer. The range acceptable is from 0-3 to support blending
+ *		up to 4 layers.
+ * @is_fg:	This flag is used to disable blending of any layers with z_order
+ *		less than this overlay. It means that any layers with z_order
+ *		less than this layer will not be blended and will be replaced
+ *		by the background border color.
+ * @alpha:	Used to set plane opacity. The range can be from 0-255, where
+ *		0 means completely transparent and 255 means fully opaque.
+ * @transp_mask: Color used as color key for transparency. Any pixel in fetched
+ *		image matching this color will be transparent when blending.
+ *		The color should be in same format as the source image format.
+ * @flags:	This is used to customize operation of overlay. See MDP flags
+ *		for more information.
+ * @user_data:	DEPRECATED* Used to store user application specific information.
+ * @bg_color:	Solid color used to fill the overlay surface when no source
+ *		buffer is provided.
+ * @horz_deci:	Horizontal decimation value, this indicates the amount of pixels
+ *		dropped for each pixel that is fetched from a line. The value
+ *		given should be power of two of decimation amount.
+ *		0: no decimation
+ *		1: decimate by 2 (drop 1 pixel for each pixel fetched)
+ *		2: decimate by 4 (drop 3 pixels for each pixel fetched)
+ *		3: decimate by 8 (drop 7 pixels for each pixel fetched)
+ *		4: decimate by 16 (drop 15 pixels for each pixel fetched)
+ * @vert_deci:	Vertical decimation value, this indicates the amount of lines
+ *		dropped for each line that is fetched from overlay. The value
+ *		given should be power of two of decimation amount.
+ *		0: no decimation
+ *		1: decimation by 2 (drop 1 line for each line fetched)
+ *		2: decimation by 4 (drop 3 lines for each line fetched)
+ *		3: decimation by 8 (drop 7 lines for each line fetched)
+ *		4: decimation by 16 (drop 15 lines for each line fetched)
+ * @overlay_pp_cfg: Overlay post processing configuration, for more information
+ *		see struct mdp_overlay_pp_params.
+ */
+struct mdp_overlay {
+	struct msmfb_img src;
+	struct mdp_rect src_rect;
+	struct mdp_rect dst_rect;
+	uint32_t z_order;	/* stage number */
+	uint32_t is_fg;		/* control alpha & transp */
+	uint32_t alpha;
+	uint32_t blend_op;
+	uint32_t transp_mask;
+	uint32_t flags;
+	uint32_t id;
+	uint32_t user_data[6];
+	uint32_t bg_color;
+	uint8_t horz_deci;
+	uint8_t vert_deci;
+	struct mdp_overlay_pp_params overlay_pp_cfg;
+	struct mdp_scale_data scale;
+};
+
+struct msmfb_overlay_3d {
+	uint32_t is_3d;
+	uint32_t width;
+	uint32_t height;
+};
+
+
+struct msmfb_overlay_blt {
+	uint32_t enable;
+	uint32_t offset;
+	uint32_t width;
+	uint32_t height;
+	uint32_t bpp;
+};
+
+struct mdp_histogram {
+	uint32_t frame_cnt;
+	uint32_t bin_cnt;
+	uint32_t *r;
+	uint32_t *g;
+	uint32_t *b;
+};
+
+enum {
+	DISPLAY_MISR_EDP,
+	DISPLAY_MISR_DSI0,
+	DISPLAY_MISR_DSI1,
+	DISPLAY_MISR_HDMI,
+	DISPLAY_MISR_LCDC,
+	DISPLAY_MISR_ATV,
+	DISPLAY_MISR_DSI_CMD,
+	DISPLAY_MISR_MAX
+};
+
+enum {
+	MISR_OP_NONE,
+	MISR_OP_SFM,
+	MISR_OP_MFM,
+	MISR_OP_BM,
+	MISR_OP_MAX
+};
+
+struct mdp_misr {
+	uint32_t block_id;
+	uint32_t frame_count;
+	uint32_t crc_op_mode;
+	uint32_t crc_value[32];
+};
+
+/*
+
+	mdp_block_type defines the identifiers for pipes in MDP 4.3 and up
+
+	MDP_BLOCK_RESERVED is provided for backward compatibility and is
+	deprecated. It corresponds to DMA_P. So MDP_BLOCK_DMA_P should be used
+	instead.
+
+	MDP_LOGICAL_BLOCK_DISP_0 identifies the display pipe which fb0 uses,
+	same for others.
+
+*/
+
+enum {
+	MDP_BLOCK_RESERVED = 0,
+	MDP_BLOCK_OVERLAY_0,
+	MDP_BLOCK_OVERLAY_1,
+	MDP_BLOCK_VG_1,
+	MDP_BLOCK_VG_2,
+	MDP_BLOCK_RGB_1,
+	MDP_BLOCK_RGB_2,
+	MDP_BLOCK_DMA_P,
+	MDP_BLOCK_DMA_S,
+	MDP_BLOCK_DMA_E,
+	MDP_BLOCK_OVERLAY_2,
+	MDP_LOGICAL_BLOCK_DISP_0 = 0x10,
+	MDP_LOGICAL_BLOCK_DISP_1,
+	MDP_LOGICAL_BLOCK_DISP_2,
+	MDP_BLOCK_MAX,
+};
+
+/*
+ * mdp_histogram_start_req is used to provide the parameters for
+ * histogram start request
+ */
+
+struct mdp_histogram_start_req {
+	uint32_t block;
+	uint8_t frame_cnt;
+	uint8_t bit_mask;
+	uint16_t num_bins;
+};
+
+/*
+ * mdp_histogram_data is used to return the histogram data, once
+ * the histogram is done/stopped/cance
+ */
+
+struct mdp_histogram_data {
+	uint32_t block;
+	uint32_t bin_cnt;
+	uint32_t *c0;
+	uint32_t *c1;
+	uint32_t *c2;
+	uint32_t *extra_info;
+};
+
+struct mdp_pcc_coeff {
+	uint32_t c, r, g, b, rr, gg, bb, rg, gb, rb, rgb_0, rgb_1;
+};
+
+struct mdp_pcc_cfg_data {
+	uint32_t block;
+	uint32_t ops;
+	struct mdp_pcc_coeff r, g, b;
+};
+
+#define MDP_GAMUT_TABLE_NUM		8
+
+enum {
+	mdp_lut_igc,
+	mdp_lut_pgc,
+	mdp_lut_hist,
+	mdp_lut_max,
+};
+
+struct mdp_ar_gc_lut_data {
+	uint32_t x_start;
+	uint32_t slope;
+	uint32_t offset;
+};
+
+struct mdp_pgc_lut_data {
+	uint32_t block;
+	uint32_t flags;
+	uint8_t num_r_stages;
+	uint8_t num_g_stages;
+	uint8_t num_b_stages;
+	struct mdp_ar_gc_lut_data *r_data;
+	struct mdp_ar_gc_lut_data *g_data;
+	struct mdp_ar_gc_lut_data *b_data;
+};
+
+
+struct mdp_lut_cfg_data {
+	uint32_t lut_type;
+	union {
+		struct mdp_igc_lut_data igc_lut_data;
+		struct mdp_pgc_lut_data pgc_lut_data;
+		struct mdp_hist_lut_data hist_lut_data;
+	} data;
+};
+
+struct mdp_bl_scale_data {
+	uint32_t min_lvl;
+	uint32_t scale;
+};
+
+struct mdp_pa_cfg_data {
+	uint32_t block;
+	struct mdp_pa_cfg pa_data;
+};
+
+struct mdp_dither_cfg_data {
+	uint32_t block;
+	uint32_t flags;
+	uint32_t g_y_depth;
+	uint32_t r_cr_depth;
+	uint32_t b_cb_depth;
+};
+
+struct mdp_gamut_cfg_data {
+	uint32_t block;
+	uint32_t flags;
+	uint32_t gamut_first;
+	uint32_t tbl_size[MDP_GAMUT_TABLE_NUM];
+	uint16_t *r_tbl[MDP_GAMUT_TABLE_NUM];
+	uint16_t *g_tbl[MDP_GAMUT_TABLE_NUM];
+	uint16_t *b_tbl[MDP_GAMUT_TABLE_NUM];
+};
+
+struct mdp_calib_config_data {
+	uint32_t ops;
+	uint32_t addr;
+	uint32_t data;
+};
+
+struct mdp_calib_config_buffer {
+	uint32_t ops;
+	uint32_t size;
+	uint32_t *buffer;
+};
+
+struct mdp_calib_dcm_state {
+	uint32_t ops;
+	uint32_t dcm_state;
+};
+
+enum {
+	DCM_UNINIT,
+	DCM_UNBLANK,
+	DCM_ENTER,
+	DCM_EXIT,
+	DCM_BLANK,
+};
+
+#define MDSS_MAX_BL_BRIGHTNESS 255
+#define AD_BL_LIN_LEN (MDSS_MAX_BL_BRIGHTNESS + 1)
+
+#define MDSS_AD_MODE_AUTO_BL	0x0
+#define MDSS_AD_MODE_AUTO_STR	0x1
+#define MDSS_AD_MODE_TARG_STR	0x3
+#define MDSS_AD_MODE_MAN_STR	0x7
+#define MDSS_AD_MODE_CALIB	0xF
+
+#define MDP_PP_AD_INIT	0x10
+#define MDP_PP_AD_CFG	0x20
+
+struct mdss_ad_init {
+	uint32_t asym_lut[33];
+	uint32_t color_corr_lut[33];
+	uint8_t i_control[2];
+	uint16_t black_lvl;
+	uint16_t white_lvl;
+	uint8_t var;
+	uint8_t limit_ampl;
+	uint8_t i_dither;
+	uint8_t slope_max;
+	uint8_t slope_min;
+	uint8_t dither_ctl;
+	uint8_t format;
+	uint8_t auto_size;
+	uint16_t frame_w;
+	uint16_t frame_h;
+	uint8_t logo_v;
+	uint8_t logo_h;
+	uint32_t bl_lin_len;
+	uint32_t *bl_lin;
+	uint32_t *bl_lin_inv;
+};
+
+#define MDSS_AD_BL_CTRL_MODE_EN 1
+#define MDSS_AD_BL_CTRL_MODE_DIS 0
+struct mdss_ad_cfg {
+	uint32_t mode;
+	uint32_t al_calib_lut[33];
+	uint16_t backlight_min;
+	uint16_t backlight_max;
+	uint16_t backlight_scale;
+	uint16_t amb_light_min;
+	uint16_t filter[2];
+	uint16_t calib[4];
+	uint8_t strength_limit;
+	uint8_t t_filter_recursion;
+	uint16_t stab_itr;
+	uint32_t bl_ctrl_mode;
+};
+
+/* ops uses standard MDP_PP_* flags */
+struct mdss_ad_init_cfg {
+	uint32_t ops;
+	union {
+		struct mdss_ad_init init;
+		struct mdss_ad_cfg cfg;
+	} params;
+};
+
+/* mode uses MDSS_AD_MODE_* flags */
+struct mdss_ad_input {
+	uint32_t mode;
+	union {
+		uint32_t amb_light;
+		uint32_t strength;
+		uint32_t calib_bl;
+	} in;
+	uint32_t output;
+};
+
+#define MDSS_CALIB_MODE_BL	0x1
+struct mdss_calib_cfg {
+	uint32_t ops;
+	uint32_t calib_mask;
+};
+
+enum {
+	mdp_op_pcc_cfg,
+	mdp_op_csc_cfg,
+	mdp_op_lut_cfg,
+	mdp_op_qseed_cfg,
+	mdp_bl_scale_cfg,
+	mdp_op_pa_cfg,
+	mdp_op_dither_cfg,
+	mdp_op_gamut_cfg,
+	mdp_op_calib_cfg,
+	mdp_op_ad_cfg,
+	mdp_op_ad_input,
+	mdp_op_calib_mode,
+	mdp_op_calib_buffer,
+	mdp_op_calib_dcm_state,
+	mdp_op_max,
+};
+
+enum {
+	WB_FORMAT_NV12,
+	WB_FORMAT_RGB_565,
+	WB_FORMAT_RGB_888,
+	WB_FORMAT_xRGB_8888,
+	WB_FORMAT_ARGB_8888,
+	WB_FORMAT_BGRA_8888,
+	WB_FORMAT_BGRX_8888,
+	WB_FORMAT_ARGB_8888_INPUT_ALPHA /* Need to support */
+};
+
+struct msmfb_mdp_pp {
+	uint32_t op;
+	union {
+		struct mdp_pcc_cfg_data pcc_cfg_data;
+		struct mdp_csc_cfg_data csc_cfg_data;
+		struct mdp_lut_cfg_data lut_cfg_data;
+		struct mdp_qseed_cfg_data qseed_cfg_data;
+		struct mdp_bl_scale_data bl_scale_data;
+		struct mdp_pa_cfg_data pa_cfg_data;
+		struct mdp_dither_cfg_data dither_cfg_data;
+		struct mdp_gamut_cfg_data gamut_cfg_data;
+		struct mdp_calib_config_data calib_cfg;
+		struct mdss_ad_init_cfg ad_init_cfg;
+		struct mdss_calib_cfg mdss_calib_cfg;
+		struct mdss_ad_input ad_input;
+		struct mdp_calib_config_buffer calib_buffer;
+		struct mdp_calib_dcm_state calib_dcm;
+	} data;
+};
+
+#define FB_METADATA_VIDEO_INFO_CODE_SUPPORT 1
+enum {
+	metadata_op_none,
+	metadata_op_base_blend,
+	metadata_op_frame_rate,
+	metadata_op_vic,
+	metadata_op_wb_format,
+	metadata_op_get_caps,
+	metadata_op_crc,
+	metadata_op_max
+};
+
+struct mdp_blend_cfg {
+	uint32_t is_premultiplied;
+};
+
+struct mdp_mixer_cfg {
+	uint32_t writeback_format;
+	uint32_t alpha;
+};
+
+struct mdss_hw_caps {
+	uint32_t mdp_rev;
+	uint8_t rgb_pipes;
+	uint8_t vig_pipes;
+	uint8_t dma_pipes;
+	uint32_t features;
+};
+
+struct msmfb_metadata {
+	uint32_t op;
+	uint32_t flags;
+	union {
+		struct mdp_misr misr_request;
+		struct mdp_blend_cfg blend_cfg;
+		struct mdp_mixer_cfg mixer_cfg;
+		uint32_t panel_frame_rate;
+		uint32_t video_info_code;
+		struct mdss_hw_caps caps;
+	} data;
+};
+
+#define MDP_MAX_FENCE_FD	32
+#define MDP_BUF_SYNC_FLAG_WAIT	1
+
+struct mdp_buf_sync {
+	uint32_t flags;
+	uint32_t acq_fen_fd_cnt;
+	uint32_t session_id;
+	int *acq_fen_fd;
+	int *rel_fen_fd;
+};
+
+struct mdp_async_blit_req_list {
+	struct mdp_buf_sync sync;
+	uint32_t count;
+	struct mdp_blit_req req[];
+};
+
+#define MDP_DISPLAY_COMMIT_OVERLAY	1
+struct mdp_buf_fence {
+	uint32_t flags;
+	uint32_t acq_fen_fd_cnt;
+	int acq_fen_fd[MDP_MAX_FENCE_FD];
+	int rel_fen_fd[MDP_MAX_FENCE_FD];
+};
+
+
+struct mdp_display_commit {
+	uint32_t flags;
+	uint32_t wait_for_finish;
+	struct fb_var_screeninfo var;
+	struct mdp_buf_fence buf_fence;
+	struct mdp_rect roi;
+};
+
+struct mdp_page_protection {
+	uint32_t page_protection;
+};
+
+
+struct mdp_mixer_info {
+	int pndx;
+	int pnum;
+	int ptype;
+	int mixer_num;
+	int z_order;
+};
+
+#define MAX_PIPE_PER_MIXER  4
+
+struct msmfb_mixer_info_req {
+	int mixer_num;
+	int cnt;
+	struct mdp_mixer_info info[MAX_PIPE_PER_MIXER];
+};
+
+enum {
+	DISPLAY_SUBSYSTEM_ID,
+	ROTATOR_SUBSYSTEM_ID,
+};
+
+enum {
+	MDP_IOMMU_DOMAIN_CP,
+	MDP_IOMMU_DOMAIN_NS,
+};
+
+enum {
+	MDP_WRITEBACK_MIRROR_OFF,
+	MDP_WRITEBACK_MIRROR_ON,
+	MDP_WRITEBACK_MIRROR_PAUSE,
+	MDP_WRITEBACK_MIRROR_RESUME,
+};
+
+
+#endif /*_MSM_MDP_H_*/
diff --git a/minuitwrp/include/minuitwrp/minui.h b/minuitwrp/include/minuitwrp/minui.h
new file mode 100644
index 0000000..f364fe3
--- /dev/null
+++ b/minuitwrp/include/minuitwrp/minui.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MINUI_H_
+#define _MINUI_H_
+
+#include "gui/placement.h"
+#include <stdbool.h>
+
+struct GRSurface {
+    int width;
+    int height;
+    int row_bytes;
+    int pixel_bytes;
+    unsigned char* data;
+    __u32 format;
+};
+
+typedef void* gr_surface;
+typedef unsigned short gr_pixel;
+
+#define FONT_TYPE_TWRP 0
+#define FONT_TYPE_TTF  1
+
+int gr_init(void);
+void gr_exit(void);
+
+int gr_fb_width(void);
+int gr_fb_height(void);
+gr_pixel *gr_fb_data(void);
+void gr_flip(void);
+void gr_fb_blank(bool blank);
+
+void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+void gr_clip(int x, int y, int w, int h);
+void gr_noclip();
+void gr_fill(int x, int y, int w, int h);
+void gr_line(int x0, int y0, int x1, int y1, int width);
+gr_surface gr_render_circle(int radius, unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+
+int gr_textEx_scaleW(int x, int y, const char *s, void* pFont, int max_width, int placement, int scale);
+
+int gr_getMaxFontHeight(void *font);
+
+void *gr_ttf_loadFont(const char *filename, int size, int dpi);
+void *gr_ttf_scaleFont(void *font, int max_width, int measured_width);
+void gr_ttf_freeFont(void *font);
+int gr_ttf_textExWH(void *context, int x, int y, const char *s, void *pFont,
+                    int max_width, int max_height, const gr_surface gr_draw);
+int gr_ttf_measureEx(const char *s, void *font);
+int gr_ttf_maxExW(const char *s, void *font, int max_width);
+int gr_ttf_getMaxFontHeight(void *font);
+void gr_ttf_dump_stats(void);
+
+void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy);
+unsigned int gr_get_width(gr_surface surface);
+unsigned int gr_get_height(gr_surface surface);
+int gr_get_surface(gr_surface* surface);
+int gr_free_surface(gr_surface surface);
+
+// Functions in graphics_utils.c
+int gr_save_screenshot(const char *dest);
+
+// Transform minuitwrp API coordinates into display coordinates,
+// for panels that are hardware-mounted in a rotated manner.
+int ROTATION_X_DISP(int x, int y, int w);
+
+int ROTATION_Y_DISP(int x, int y, int h);
+
+void surface_ROTATION_transform(gr_surface dst_ptr, const gr_surface src_ptr, size_t num_bytes_per_pixel);
+
+// input event structure, include <linux/input.h> for the definition.
+// see http://www.mjmwired.net/kernel/Documentation/input/ for info.
+struct input_event;
+
+int ev_init(void);
+void ev_exit(void);
+int ev_get(struct input_event *ev, int timeout_ms);
+int ev_has_mouse(void);
+
+// Resources
+
+// Returns 0 if no error, else negative.
+int res_create_surface(const char* name, gr_surface* pSurface);
+void res_free_surface(gr_surface surface);
+int res_scale_surface(gr_surface source, gr_surface* destination, float scale_w, float scale_h);
+
+int vibrate(int timeout_ms);
+
+#endif
diff --git a/minuitwrp/include/minuitwrp/truetype.hpp b/minuitwrp/include/minuitwrp/truetype.hpp
new file mode 100755
index 0000000..72b7620
--- /dev/null
+++ b/minuitwrp/include/minuitwrp/truetype.hpp
@@ -0,0 +1,118 @@
+/*
+		Copyright 2013 to 2020 TeamWin
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _TWRP_TRUETYPE_HPP
+#define _TWRP_TRUETYPE_HPP
+
+#include <map>
+#include <string>
+#include <ft2build.h>
+#include <pthread.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include <pixelflinger/pixelflinger.h>
+#include "minui.h"
+
+typedef struct TrueTypeFontKey {
+    int size;
+    int dpi;
+    std::string path;
+} TrueTypeFontKey;
+
+inline bool operator<(const TrueTypeFontKey &ttfkLeft, const TrueTypeFontKey &ttfkRight) {
+    return std::tie(ttfkLeft.size, ttfkLeft.dpi, ttfkLeft.path) < std::tie(ttfkRight.size, ttfkRight.dpi, ttfkRight.path);
+}
+
+typedef struct {
+    FT_BBox bbox;
+    FT_BitmapGlyph glyph;
+} TrueTypeCacheEntry;
+
+typedef struct StringCacheKey {
+    int max_width;
+    std::string text;
+} StringCacheKey;
+
+inline bool operator<(const StringCacheKey &sckLeft, const StringCacheKey &sckRight)  {
+    return std::tie(sckLeft.text, sckLeft.max_width) < std::tie(sckRight.text, sckRight.max_width);
+}
+
+typedef struct StringCacheEntry {
+    GGLSurface surface;
+    int rendered_bytes; // number of bytes from C string rendered, not number of UTF8 characters!
+    StringCacheKey *key;
+} StringCacheEntry;
+
+typedef struct {
+    int type;
+    int refcount;
+    int size;
+    int dpi;
+    int max_height;
+    int base;
+    FT_Face face;
+    std::map<int, TrueTypeCacheEntry*> glyph_cache;
+    std::map<StringCacheKey, StringCacheEntry*> string_cache;
+    pthread_mutex_t mutex;
+    TrueTypeFontKey *key;
+} TrueTypeFont;
+
+typedef struct {
+    FT_Library ft_library;
+    std::map<TrueTypeFontKey, TrueTypeFont*> fonts;
+    pthread_mutex_t mutex;
+} FontData;
+
+typedef std::map<StringCacheKey, StringCacheEntry*> StringCacheMap;
+typedef std::map<int, TrueTypeCacheEntry*> TrueTypeCacheEntryMap;
+typedef std::map<TrueTypeFontKey, TrueTypeFont*> TrueTypeFontMap;
+
+#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
+#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
+
+// 32bit FNV-1a hash algorithm
+// http://isthe.com/chongo/tech/comp/fnv/#FNV-1a
+static const uint32_t FNV_prime = 16777619U;
+static const uint32_t offset_basis = 2166136261U;
+
+#define STRING_CACHE_MAX_ENTRIES 400
+#define STRING_CACHE_TRUNCATE_ENTRIES 150
+
+class twrpTruetype {
+public:
+    twrpTruetype();
+    static int utf8_to_unicode(const char* pIn, unsigned int *pOut);
+    static void* gr_ttf_loadFont(const char *filename, int size, int dpi);
+    static void* gr_ttf_scaleFont(void *font, int max_width, int measured_width);
+    static void gr_ttf_freeStringCache(void *key, void *value, void *context __unused);
+    static void gr_ttf_freeFont(void *font);
+    static TrueTypeCacheEntry* gr_ttf_glyph_cache_peek(TrueTypeFont *font, int char_index);
+    static TrueTypeCacheEntry* gr_ttf_glyph_cache_get(TrueTypeFont *font, int char_index);
+    static int gr_ttf_copy_glyph_to_surface(GGLSurface *dest, FT_BitmapGlyph glyph, int offX, int offY, int base);
+    static void gr_ttf_calcMaxFontHeight(TrueTypeFont *f);
+    static int gr_ttf_render_text(TrueTypeFont *font, GGLSurface *surface, const std::string text, int max_width);
+    static StringCacheEntry* gr_ttf_string_cache_peek(TrueTypeFont *font, const std::string text, __attribute__((unused)) int max_width);
+    static StringCacheEntry* gr_ttf_string_cache_get(TrueTypeFont *font, const std::string text, int max_width);
+    static int gr_ttf_measureEx(const char *s, void *font);
+    static int gr_ttf_maxExW(const char *s, void *font, int max_width);
+    static int gr_ttf_textExWH(void *context, int x, int y,
+                    const char *s, void *pFont,
+                    int max_width, int max_height,
+                    const gr_surface gr_draw_surface);
+    static int gr_ttf_getMaxFontHeight(void *font);
+    static void gr_ttf_string_cache_truncate(TrueTypeFont *font);
+};
+#endif // _TWRP_TRUETYPE_HPP
diff --git a/minuitwrp/include/private/resources.h b/minuitwrp/include/private/resources.h
new file mode 100644
index 0000000..047ebe2
--- /dev/null
+++ b/minuitwrp/include/private/resources.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include <png.h>
+
+// This class handles the PNG file parsing. It also holds the ownership of the PNG pointer and the
+// opened file pointer. Both will be destroyed / closed when this object goes out of scope.
+class PngHandler {
+ public:
+  // Constructs an instance by loading the PNG file from '/res/images/<name>.png', or '<name>'.
+  PngHandler(const std::string& name);
+
+  ~PngHandler();
+
+  png_uint_32 width() const {
+    return width_;
+  }
+
+  png_uint_32 height() const {
+    return height_;
+  }
+
+  png_byte channels() const {
+    return channels_;
+  }
+
+  int bit_depth() const {
+    return bit_depth_;
+  }
+
+  int color_type() const {
+    return color_type_;
+  }
+
+  png_structp png_ptr() const {
+    return png_ptr_;
+  }
+
+  png_infop info_ptr() const {
+    return info_ptr_;
+  }
+
+  int error_code() const {
+    return error_code_;
+  };
+
+  operator bool() const {
+    return error_code_ == 0;
+  }
+
+ private:
+  png_structp png_ptr_{ nullptr };
+  png_infop info_ptr_{ nullptr };
+  png_uint_32 width_;
+  png_uint_32 height_;
+  png_byte channels_;
+  int bit_depth_;
+  int color_type_;
+
+  // The |error_code_| is set to a negative value if an error occurs when opening the png file.
+  int error_code_{ 0 };
+  // After initialization, we'll keep the file pointer open before destruction of PngHandler.
+  std::unique_ptr<FILE, decltype(&fclose)> png_fp_{ nullptr, fclose };
+};
+
+// Overrides the default resource dir, for testing purpose.
+void res_set_resource_dir(const std::string&);
diff --git a/minuitwrp/libminuitwrp_defaults.go b/minuitwrp/libminuitwrp_defaults.go
new file mode 100644
index 0000000..8cb176f
--- /dev/null
+++ b/minuitwrp/libminuitwrp_defaults.go
@@ -0,0 +1,300 @@
+package twrp
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"fmt"
+	"path/filepath"
+	"strings"
+)
+
+func globalFlags(ctx android.BaseContext) []string {
+	var cflags []string
+
+	if getMakeVars(ctx, "TW_SUPPORT_INPUT_1_2_HAPTICS") == "true" {
+		cflags = append(cflags, "-DUSE_QTI_HAPTICS")
+	}
+
+	if getMakeVars(ctx, "TW_SUPPORT_INPUT_AIDL_HAPTICS") == "true" {
+		cflags = append(cflags, "-DUSE_QTI_AIDL_HAPTICS")
+	}
+
+	if getMakeVars(ctx, "TW_USE_SAMSUNG_HAPTICS") == "true" {
+                cflags = append(cflags, "-DUSE_SAMSUNG_HAPTICS")
+        }
+
+	if getMakeVars(ctx, "TW_TARGET_USES_QCOM_BSP") == "true" {
+		cflags = append(cflags, "-DMSM_BSP")
+	}
+
+	if getMakeVars(ctx, "TW_NEW_ION_HEAP") == "true" {
+		cflags = append(cflags, "-DNEW_ION_HEAP")
+	}
+
+	matches, err := filepath.Glob("external/libdrm/Android.*")
+	_ = matches
+	if err == nil {
+		cflags = append(cflags, "-DHAS_DRM")
+	}
+
+	if getMakeVars(ctx, "TW_INCLUDE_JPEG") != "" {
+		cflags = append(cflags, "-DTW_INCLUDE_JPEG")
+	}
+
+	if getMakeVars(ctx, "RECOVERY_TOUCHSCREEN_SWAP_XY") == "true" {
+		cflags = append(cflags, "-DRECOVERY_TOUCHSCREEN_SWAP_XY")
+	}
+
+	if getMakeVars(ctx, "RECOVERY_TOUCHSCREEN_FLIP_X") == "true" {
+		cflags = append(cflags, "-DRECOVERY_TOUCHSCREEN_FLIP_X")
+	}
+
+	if getMakeVars(ctx, "RECOVERY_TOUCHSCREEN_FLIP_Y") == "true" {
+		cflags = append(cflags, "-DRECOVERY_TOUCHSCREEN_FLIP_Y")
+	}
+
+	if getMakeVars(ctx, "RECOVERY_GRAPHICS_FORCE_USE_LINELENGTH") == "true" {
+		cflags = append(cflags, "-DRECOVERY_GRAPHICS_FORCE_USE_LINELENGTH")
+	}
+
+	if getMakeVars(ctx, "RECOVERY_GRAPHICS_FORCE_SINGLE_BUFFER") == "true" {
+		cflags = append(cflags, "-DRECOVERY_GRAPHICS_FORCE_SINGLE_BUFFER")
+	}
+
+	if getMakeVars(ctx, "TWRP_EVENT_LOGGING") == "true" {
+		cflags = append(cflags, "-D_EVENT_LOGGING")
+	}
+
+	var pixelFormat = strings.Replace(getMakeVars(ctx, "TARGET_RECOVERY_FORCE_PIXEL_FORMAT"), "\"", "", -1)
+
+	switch pixelFormat {
+	case "RGBA_8888":
+		fmt.Println("****************************************************************************)")
+		fmt.Println("* TARGET_RECOVERY_FORCE_PIXEL_FORMAT := RGBA_8888 not implemented yet      *)")
+		fmt.Println("****************************************************************************)")
+		cflags = append(cflags, "-DRECOVERY_RGBA")
+		break
+
+	case "RGBX_8888":
+		fmt.Println("****************************************************************************)")
+		fmt.Println("* TARGET_RECOVERY_FORCE_PIXEL_FORMAT := RGBX_8888 not implemented yet      *)")
+		fmt.Println("****************************************************************************)")
+		cflags = append(cflags, "-DRECOVERY_RGBX")
+		break
+
+	case "BGRA_8888":
+		fmt.Println("****************************************************************************)")
+		fmt.Println("* TARGET_RECOVERY_FORCE_PIXEL_FORMAT := BGRA_8888 not implemented yet      *)")
+		fmt.Println("****************************************************************************)")
+		cflags = append(cflags, "-DRECOVERY_BGRA")
+		break
+
+	case "RGB_565":
+		cflags = append(cflags, "-DRECOVERY_FORCE_RGB_565")
+		break
+	}
+
+	pixelFormat = strings.Replace(getMakeVars(ctx, "TARGET_RECOVERY_PIXEL_FORMAT"), "\"", "", -1)
+	switch pixelFormat {
+	case "ABGR_8888":
+		cflags = append(cflags, "-DRECOVERY_ABGR")
+		break
+
+	case "RGBX_8888":
+		cflags = append(cflags, "-DRECOVERY_RGBX")
+		break
+
+	case "BGRA_8888":
+		cflags = append(cflags, "-DRECOVERY_BGRA")
+		break
+	}
+
+	if getMakeVars(ctx, "TARGET_RECOVERY_OVERSCAN_PERCENT") != "" {
+		cflags = append(cflags, "-DDOVERSCAN_PERCENT="+getMakeVars(ctx, "TARGET_RECOVERY_OVERSCAN_PERCENT"))
+	} else {
+		cflags = append(cflags, "-DOVERSCAN_PERCENT=0")
+	}
+
+	if getMakeVars(ctx, "TW_SCREEN_BLANK_ON_BOOT") == "true" {
+		cflags = append(cflags, "-DTW_SCREEN_BLANK_ON_BOOT")
+	}
+
+	if getMakeVars(ctx, "TW_FBIOPAN") == "true" {
+		cflags = append(cflags, "-DTW_FBIOPAN")
+	}
+
+	var tw_rotation = getMakeVars(ctx, "TW_ROTATION")
+	switch tw_rotation {
+	case "0":
+		fallthrough
+	case "90":
+		fallthrough
+	case "180":
+		fallthrough
+	case "270":
+		cflags = append(cflags, "-DTW_ROTATION="+tw_rotation)
+	default:
+		if getMakeVars(ctx, "BOARD_HAS_FLIPPED_SCREEN") == "true" {
+			cflags = append(cflags, "-DTW_ROTATION=180")
+		} else {
+			cflags = append(cflags, "-DTW_ROTATION=0")
+		}
+	}
+
+	if getMakeVars(ctx, "TW_IGNORE_MAJOR_AXIS_0") == "true" {
+		cflags = append(cflags, "-DTW_IGNORE_MAJOR_AXIS_0")
+	}
+
+	if getMakeVars(ctx, "TW_IGNORE_MT_POSITION_0") == "true" {
+		cflags = append(cflags, "-DTW_IGNORE_MT_POSITION_0")
+	}
+
+	if getMakeVars(ctx, "TW_IGNORE_ABS_MT_TRACKING_ID") == "true" {
+		cflags = append(cflags, "-DTW_IGNORE_ABS_MT_TRACKING_ID")
+	}
+
+	if getMakeVars(ctx, "TW_INPUT_BLACKLIST") != "" {
+		cflags = append(cflags, "-DTW_INPUT_BLACKLIST="+getMakeVars(ctx, "TW_INPUT_BLACKLIST"))
+	}
+
+	if getMakeVars(ctx, "TW_WHITELIST_INPUT") != "" {
+		cflags = append(cflags, "-DWHITELIST_INPUT="+getMakeVars(ctx, "TW_WHITELIST_INPUT"))
+	}
+
+	if getMakeVars(ctx, "TW_HAPTICS_TSPDRV") == "true" {
+		cflags = append(cflags, "-DTW_HAPTICS_TSPDRV")
+	}
+
+	return cflags
+}
+
+func globalSrcs(ctx android.BaseContext) []string {
+	var srcs []string
+
+	if getMakeVars(ctx, "TW_TARGET_USES_QCOM_BSP") == "true" {
+		srcs = append(srcs, "graphics_overlay.cpp")
+	}
+
+	matches, err := filepath.Glob("external/libdrm/Android.*")
+	_ = matches
+	if err == nil {
+		srcs = append(srcs, "graphics_drm.cpp")
+	}
+
+	if getMakeVars(ctx, "TW_HAPTICS_TSPDRV") == "true" {
+		srcs = append(srcs, "tspdrv.cpp")
+	}
+	return srcs
+}
+
+func globalIncludes(ctx android.BaseContext) []string {
+	var includes []string
+
+	if getMakeVars(ctx, "TW_TARGET_USES_QCOM_BSP") == "true" {
+		if getMakeVars(ctx, "TARGET_PREBUILT_KERNEL") != "" {
+			includes = append(includes, getMakeVars(ctx, "TARGET_OUT_INTERMEDIATES")+"/KERNEL_OBJ/usr/include")
+		} else {
+			if getMakeVars(ctx, "TARGET_CUSTOM_KERNEL_HEADERS") != "" {
+				includes = append(includes, "bootable/recovery/minuitwrp")
+			} else {
+				includes = append(includes, getMakeVars(ctx, "TARGET_CUSTOM_KERNEL_HEADERS"))
+			}
+		}
+	} else {
+		includes = append(includes, "bootable/recovery/minuitwrp")
+	}
+
+	if getMakeVars(ctx, "TW_INCLUDE_JPEG") != "" {
+		includes = append(includes, "external/jpeg")
+	}
+
+	return includes
+}
+
+func globalStaticLibs(ctx android.BaseContext) []string {
+	var staticLibs []string
+
+	matches, err := filepath.Glob("external/libdrm/Android.*")
+	_ = matches
+	if err == nil {
+		matches, err = filepath.Glob("external/libdrm/Android.common.mk")
+		if err != nil {
+			staticLibs = append(staticLibs, "libdrm_platform")
+		} else {
+			staticLibs = append(staticLibs, "libdrm")
+		}
+	}
+
+	return staticLibs
+}
+
+func globalSharedLibs(ctx android.BaseContext) []string {
+	var sharedLibs []string
+
+	if getMakeVars(ctx, "TW_SUPPORT_INPUT_1_2_HAPTICS") == "true" {
+		sharedLibs = append(sharedLibs, "android.hardware.vibrator@1.2")
+		sharedLibs = append(sharedLibs, "libhidlbase")
+	}
+
+	if getMakeVars(ctx, "TW_SUPPORT_INPUT_AIDL_HAPTICS") == "true" {
+		sharedLibs = append(sharedLibs, "android.hardware.vibrator-V1-ndk_platform")
+		sharedLibs = append(sharedLibs, "android.hardware.vibrator-V1-cpp")
+	}
+
+	if getMakeVars(ctx, "TW_INCLUDE_JPEG") != "" {
+		sharedLibs = append(sharedLibs, "libjpeg")
+	}
+	return sharedLibs
+}
+
+func globalRequiredModules(ctx android.BaseContext) []string {
+	var requiredModules []string
+
+	if getMakeVars(ctx, "TARGET_PREBUILT_KERNEL") != "" {
+		var kernelDir = getMakeVars(ctx, "TARGET_OUT_INTERMEDIATES") + ")/KERNEL_OBJ/usr"
+		requiredModules = append(requiredModules, kernelDir)
+	}
+	return requiredModules
+}
+
+func libMinuiTwrpDefaults(ctx android.LoadHookContext) {
+	type props struct {
+		Target struct {
+			Android struct {
+				Cflags  []string
+				Enabled *bool
+			}
+		}
+		Cflags       []string
+		Srcs         []string
+		Include_dirs []string
+		Static_libs  []string
+		Shared_libs  []string
+		Required     []string
+	}
+
+	p := &props{}
+	p.Cflags = globalFlags(ctx)
+	s := globalSrcs(ctx)
+	p.Srcs = s
+	i := globalIncludes(ctx)
+	p.Include_dirs = i
+	staticLibs := globalStaticLibs(ctx)
+	p.Static_libs = staticLibs
+	sharedLibs := globalSharedLibs(ctx)
+	p.Shared_libs = sharedLibs
+	requiredModules := globalRequiredModules(ctx)
+	p.Required = requiredModules
+	ctx.AppendProperties(p)
+}
+
+func init() {
+	android.RegisterModuleType("libminuitwrp_defaults", libMinuiTwrpDefaultsFactory)
+}
+
+func libMinuiTwrpDefaultsFactory() android.Module {
+	module := cc.DefaultsFactory()
+	android.AddLoadHook(module, libMinuiTwrpDefaults)
+
+	return module
+}
diff --git a/minuitwrp/resources.cpp b/minuitwrp/resources.cpp
new file mode 100644
index 0000000..42e3131
--- /dev/null
+++ b/minuitwrp/resources.cpp
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include <png.h>
+
+#include <pixelflinger/pixelflinger.h>
+#ifdef TW_INCLUDE_JPEG
+extern "C" {
+#include "jpeglib.h"
+}
+#endif
+#include "minuitwrp/minui.h"
+
+#define SURFACE_DATA_ALIGNMENT 8
+
+static GGLSurface* malloc_surface(size_t data_size) {
+    size_t size = sizeof(GGLSurface) + data_size + SURFACE_DATA_ALIGNMENT;
+    unsigned char* temp = reinterpret_cast<unsigned char*>(malloc(size));
+    if (temp == NULL) return NULL;
+    GGLSurface* surface = reinterpret_cast<GGLSurface*>(temp);
+    surface->data = temp + sizeof(GGLSurface) +
+        (SURFACE_DATA_ALIGNMENT - (sizeof(GGLSurface) % SURFACE_DATA_ALIGNMENT));
+    return surface;
+}
+
+static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr,
+                    png_uint_32* width, png_uint_32* height, png_byte* channels, FILE** fpp) {
+    char resPath[256];
+    unsigned char header[8];
+    int result = 0;
+    int color_type, bit_depth;
+    size_t bytesRead;
+
+    snprintf(resPath, sizeof(resPath)-1, TWRES "images/%s.png", name);
+    resPath[sizeof(resPath)-1] = '\0';
+    FILE* fp = fopen(resPath, "rb");
+    if (fp == NULL) {
+        fp = fopen(name, "rb");
+        if (fp == NULL) {
+            result = -1;
+            goto exit;
+        }
+    }
+
+    bytesRead = fread(header, 1, sizeof(header), fp);
+    if (bytesRead != sizeof(header)) {
+        result = -2;
+        goto exit;
+    }
+
+    if (png_sig_cmp(header, 0, sizeof(header))) {
+        result = -3;
+        goto exit;
+    }
+
+    *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (!*png_ptr) {
+        result = -4;
+        goto exit;
+    }
+
+    *info_ptr = png_create_info_struct(*png_ptr);
+    if (!*info_ptr) {
+        result = -5;
+        goto exit;
+    }
+
+    if (setjmp(png_jmpbuf(*png_ptr))) {
+        result = -6;
+        goto exit;
+    }
+
+    png_init_io(*png_ptr, fp);
+    png_set_sig_bytes(*png_ptr, sizeof(header));
+    png_read_info(*png_ptr, *info_ptr);
+
+    png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth,
+            &color_type, NULL, NULL, NULL);
+
+    *channels = png_get_channels(*png_ptr, *info_ptr);
+
+    if (bit_depth == 8 && *channels == 3 && color_type == PNG_COLOR_TYPE_RGB) {
+        // 8-bit RGB images: great, nothing to do.
+    } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_GRAY) {
+        // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
+        png_set_expand_gray_1_2_4_to_8(*png_ptr);
+    } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE) {
+        // paletted images: expand to 8-bit RGB.  Note that we DON'T
+        // currently expand the tRNS chunk (if any) to an alpha
+        // channel, because minui doesn't support alpha channels in
+        // general.
+        png_set_palette_to_rgb(*png_ptr);
+        *channels = 3;
+    } else if (color_type == PNG_COLOR_TYPE_PALETTE) {
+        png_set_palette_to_rgb(*png_ptr);
+    }
+
+    *fpp = fp;
+    return result;
+
+  exit:
+    if (result < 0) {
+        png_destroy_read_struct(png_ptr, info_ptr, NULL);
+    }
+    if (fp != NULL) {
+        fclose(fp);
+    }
+
+    return result;
+}
+
+// "display" surfaces are transformed into the framebuffer's required
+// pixel format (currently only RGBX is supported) at load time, so
+// gr_blit() can be nothing more than a memcpy() for each row.  The
+// next two functions are the only ones that know anything about the
+// framebuffer pixel format; they need to be modified if the
+// framebuffer format changes (but nothing else should).
+
+// Allocate and return a GRSurface* sufficient for storing an image of
+// the indicated size in the framebuffer pixel format.
+static GGLSurface* init_display_surface(png_uint_32 width, png_uint_32 height) {
+    GGLSurface* surface = malloc_surface(width * height * 4);
+    if (surface == NULL) return NULL;
+
+    surface->version = sizeof(GGLSurface);
+    surface->width = width;
+    surface->height = height;
+    surface->stride = width;
+
+    return surface;
+}
+
+// Copy 'input_row' to 'output_row', transforming it to the
+// framebuffer pixel format.  The input format depends on the value of
+// 'channels':
+//
+//   1 - input is 8-bit grayscale
+//   3 - input is 24-bit RGB
+//   4 - input is 32-bit RGBA/RGBX
+//
+// 'width' is the number of pixels in the row.
+static void transform_rgb_to_draw(unsigned char* input_row,
+                                  unsigned char* output_row,
+                                  int channels, int width) {
+    int x;
+    unsigned char* ip = input_row;
+    unsigned char* op = output_row;
+
+    switch (channels) {
+        case 1:
+            // expand gray level to RGBX
+            for (x = 0; x < width; ++x) {
+                *op++ = *ip;
+                *op++ = *ip;
+                *op++ = *ip;
+                *op++ = 0xff;
+                ip++;
+            }
+            break;
+
+        case 3:
+            // expand RGBA to RGBX
+            for (x = 0; x < width; ++x) {
+                *op++ = *ip++;
+                *op++ = *ip++;
+                *op++ = *ip++;
+                *op++ = 0xff;
+            }
+            break;
+
+        case 4:
+            // copy RGBA to RGBX
+            memcpy(output_row, input_row, width*4);
+            break;
+    }
+}
+
+int res_create_surface_png(const char* name, gr_surface* pSurface) {
+    GGLSurface* surface = NULL;
+    int result = 0;
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_uint_32 width, height;
+    png_byte channels;
+    FILE* fp;
+    unsigned char* p_row;
+    unsigned int y;
+
+    *pSurface = NULL;
+
+    result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels, &fp);
+    if (result < 0) return result;
+
+    surface = init_display_surface(width, height);
+    if (surface == NULL) {
+        result = -8;
+        goto exit;
+    }
+
+#if defined(RECOVERY_ARGB) || defined(RECOVERY_BGRA)
+    png_set_bgr(png_ptr);
+#endif
+
+    p_row = reinterpret_cast<unsigned char*>(malloc(width * 4));
+    if (p_row == NULL) {
+        result = -9;
+        goto exit;
+    }
+    for (y = 0; y < height; ++y) {
+        png_read_row(png_ptr, p_row, NULL);
+        transform_rgb_to_draw(p_row, surface->data + y * width * 4, channels, width);
+    }
+    free(p_row);
+
+    if (channels == 3)
+        surface->format = GGL_PIXEL_FORMAT_RGBX_8888;
+    else
+        surface->format = GGL_PIXEL_FORMAT_RGBA_8888;
+
+    *pSurface = (gr_surface) surface;
+
+  exit:
+    fclose(fp);
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    if (result < 0 && surface != NULL) free(surface);
+    return result;
+}
+
+#ifdef TW_INCLUDE_JPEG
+int res_create_surface_jpg(const char* name, gr_surface* pSurface) {
+    GGLSurface* surface = NULL;
+    int result = 0, y;
+    struct jpeg_decompress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+    unsigned char* pData;
+    size_t width, height, stride, pixelSize;
+
+    FILE* fp = fopen(name, "rb");
+    if (fp == NULL) {
+        char resPath[256];
+
+        snprintf(resPath, sizeof(resPath)-1, TWRES "images/%s", name);
+        resPath[sizeof(resPath)-1] = '\0';
+        fp = fopen(resPath, "rb");
+        if (fp == NULL) {
+            result = -1;
+            goto exit;
+        }
+    }
+
+    cinfo.err = jpeg_std_error(&jerr);
+    jpeg_create_decompress(&cinfo);
+
+    /* Specify data source for decompression */
+    jpeg_stdio_src(&cinfo, fp);
+
+    /* Read file header, set default decompression parameters */
+    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
+        goto exit;
+
+    /* Start decompressor */
+    (void) jpeg_start_decompress(&cinfo);
+
+    width = cinfo.image_width;
+    height = cinfo.image_height;
+    stride = 4 * width;
+    pixelSize = stride * height;
+
+    surface = reinterpret_cast<GGLSurface*>(malloc(sizeof(GGLSurface) + pixelSize));
+    //p_row = reinterpret_cast<unsigned char*>(malloc(width * 4));
+    if (surface == NULL) {
+        result = -8;
+        goto exit;
+    }
+
+    pData = (unsigned char*) (surface + 1);
+    surface->version = sizeof(GGLSurface);
+    surface->width = width;
+    surface->height = height;
+    surface->stride = width; /* Yes, pixels, not bytes */
+    surface->data = pData;
+    surface->format = GGL_PIXEL_FORMAT_RGBX_8888;
+
+    for (y = 0; y < (int) height; ++y) {
+        unsigned char* pRow = pData + y * stride;
+        jpeg_read_scanlines(&cinfo, &pRow, 1);
+
+        int x;
+        for(x = width - 1; x >= 0; x--) {
+            int sx = x * 3;
+            int dx = x * 4;
+            unsigned char r = pRow[sx];
+            unsigned char g = pRow[sx + 1];
+            unsigned char b = pRow[sx + 2];
+            unsigned char a = 0xff;
+#if defined(RECOVERY_ARGB) || defined(RECOVERY_BGRA)
+            pRow[dx    ] = b; // r
+            pRow[dx + 1] = g; // g
+            pRow[dx + 2] = r; // b
+            pRow[dx + 3] = a;
+#else
+            pRow[dx    ] = r; // r
+            pRow[dx + 1] = g; // g
+            pRow[dx + 2] = b; // b
+            pRow[dx + 3] = a;
+#endif
+        }
+    }
+    *pSurface = (gr_surface) surface;
+
+exit:
+    if (fp != NULL)
+    {
+        if (surface)
+        {
+            (void) jpeg_finish_decompress(&cinfo);
+            if (result < 0)
+            {
+                free(surface);
+            }
+        }
+        jpeg_destroy_decompress(&cinfo);
+        fclose(fp);
+    }
+    return result;
+}
+#endif
+
+int res_create_surface(const char* name, gr_surface* pSurface) {
+    int ret;
+    if (!name)      return -1;
+
+#ifdef TW_INCLUDE_JPEG
+    if (strlen(name) > 4 && strcmp(name + strlen(name) - 4, ".jpg") == 0)
+        return res_create_surface_jpg(name,pSurface);
+#endif
+
+    ret = res_create_surface_png(name, pSurface);
+#ifdef TW_INCLUDE_JPEG
+    if (ret < 0)
+        ret = res_create_surface_jpg(name,pSurface);
+#endif
+
+    return ret;
+}
+
+void res_free_surface(gr_surface surface) {
+    GGLSurface* pSurface = (GGLSurface*) surface;
+    if (pSurface) {
+        free(pSurface);
+    }
+}
+
+// Scale image function
+int res_scale_surface(gr_surface source, gr_surface* destination, float scale_w, float scale_h) {
+    GGLContext *gl = NULL;
+    GGLSurface* sc_mem_surface = NULL;
+    *destination = NULL;
+    GGLSurface *surface = (GGLSurface*)source;
+    int w = gr_get_width(source), h = gr_get_height(source);
+    int sx = 0, sy = 0, dx = 0, dy = 0;
+    float dw = (float)w * scale_w;
+    float dh = (float)h * scale_h;
+
+    // Create a new surface that is the appropriate size
+    sc_mem_surface = init_display_surface((int)dw, (int)dh);
+    if (!sc_mem_surface) {
+        printf("gr_scale_surface failed to init_display_surface\n");
+        return -1;
+    }
+    sc_mem_surface->format = surface->format;
+
+    // Initialize the context
+    gglInit(&gl);
+    gl->colorBuffer(gl, sc_mem_surface);
+    gl->activeTexture(gl, 0);
+
+    // Enable or disable blending based on source surface format
+    if (surface->format == GGL_PIXEL_FORMAT_RGBX_8888) {
+        gl->disable(gl, GGL_BLEND);
+    } else {
+        gl->enable(gl, GGL_BLEND);
+        gl->blendFunc(gl, GGL_ONE, GGL_ZERO);
+    }
+
+    // Bind our source surface to the context
+    gl->bindTexture(gl, surface);
+
+    // Deal with the scaling
+    gl->texParameteri(gl, GGL_TEXTURE_2D, GGL_TEXTURE_MIN_FILTER, GGL_LINEAR);
+    gl->texParameteri(gl, GGL_TEXTURE_2D, GGL_TEXTURE_MAG_FILTER, GGL_LINEAR);
+    gl->texParameteri(gl, GGL_TEXTURE_2D, GGL_TEXTURE_WRAP_S, GGL_CLAMP);
+    gl->texParameteri(gl, GGL_TEXTURE_2D, GGL_TEXTURE_WRAP_T, GGL_CLAMP);
+    gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
+    gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_AUTOMATIC);
+    gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_AUTOMATIC);
+    gl->enable(gl, GGL_TEXTURE_2D);
+
+    int32_t grad[8];
+    memset(grad, 0, sizeof(grad));
+    // s, dsdx, dsdy, scale, t, dtdx, dtdy, tscale   <- this is wrong!
+    // This api uses block floating-point for S and T texture coordinates.
+    // All values are given in 16.16, scaled by 'scale'. In other words,
+    // set scale to 0, for 16.16 values.
+
+    // s, dsdx, dsdy, t, dtdx, dtdy, sscale, tscale
+    float dsdx = (float)w / dw;
+    float dtdy = (float)h / dh;
+    grad[0] = ((float)sx - (dsdx * dx)) * 65536;
+    grad[1] = dsdx * 65536;
+    grad[3] = ((float)sy - (dtdy * dy)) * 65536;
+    grad[5] = dtdy * 65536;
+//    printf("blit: w=%d h=%d dx=%d dy=%d dw=%f dh=%f dsdx=%f dtdy=%f s0=%x dsdx=%x t0=%x dtdy=%x\n",
+//                    w,   h,    dx,   dy,   dw,   dh,   dsdx,   dtdy, grad[0], grad[1], grad[3], grad[5]);
+    gl->texCoordGradScale8xv(gl, 0 /*tmu*/, grad);
+
+    // draw / scale the source surface to our target context
+    gl->recti(gl, dx, dy, dx + dw, dy + dh);
+    gglUninit(gl);
+    gl = NULL;
+    // put the scaled surface in our destination
+    *destination = (gr_surface*) sc_mem_surface;
+    // free memory used in the source
+    res_free_surface(source);
+    source = NULL;
+    return 0;
+}
diff --git a/minuitwrp/truetype.cpp b/minuitwrp/truetype.cpp
new file mode 100644
index 0000000..d224fbb
--- /dev/null
+++ b/minuitwrp/truetype.cpp
@@ -0,0 +1,649 @@
+/*
+		Copyright 2012 to 2020 TeamWin
+		This file is part of TWRP/TeamWin Recovery Project.
+
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <errno.h>
+#include <stdio.h>
+
+#include <pthread.h>
+#include <algorithm>
+#include <string>
+#include "minuitwrp/truetype.hpp"
+
+extern unsigned int gr_rotation;
+
+static FontData font_data = {
+	.ft_library = NULL,
+	.mutex = PTHREAD_MUTEX_INITIALIZER
+};
+
+twrpTruetype::twrpTruetype(void) {
+
+}
+
+int twrpTruetype::utf8_to_unicode(const char* pIn, unsigned int *pOut) {
+	int utf_bytes = 1;
+	unsigned int unicode = 0;
+	unsigned char tmp;
+	tmp = (unsigned char)*pIn++;
+	if (tmp < 0x80)
+	{
+		*pOut = tmp;
+	}
+	else
+	{
+		unsigned int high_bit_mask = 0x3F;
+		unsigned int high_bit_shift = 0;
+		int total_bits = 0;
+		while((tmp & 0xC0) == 0xC0)
+		{
+			utf_bytes ++;
+			if(utf_bytes > 6)
+			{
+				*pOut = tmp;
+				return 1;
+			}
+			tmp = 0xFF & (tmp << 1);
+			total_bits += 6;
+			high_bit_mask >>= 1;
+			high_bit_shift++;
+			unicode <<= 6;
+			unicode |= (*pIn++) & 0x3F;
+		}
+		unicode |= ((tmp >> high_bit_shift) & high_bit_mask) << total_bits;
+		*pOut = unicode;
+	}
+
+	return utf_bytes;
+}
+
+void* twrpTruetype::gr_ttf_loadFont(const char *filename, int size, int dpi) {
+	int error;
+	TrueTypeFont* res = nullptr;
+	TrueTypeFontKey* key;
+	std::string fontFileName(filename);
+
+	pthread_mutex_lock(&font_data.mutex);
+
+	TrueTypeFontKey k = {
+		.size = size,
+		.dpi = dpi,
+		.path = fontFileName
+	};
+
+	TrueTypeFontMap::iterator ttfIter = font_data.fonts.find(k);
+
+	if (ttfIter != font_data.fonts.end())
+	{
+		res = ttfIter->second;
+		++res->refcount;
+		goto exit;
+	}
+
+	if(!font_data.ft_library)
+	{
+		error = FT_Init_FreeType(&font_data.ft_library);
+		if(error)
+		{
+			fprintf(stderr, "Failed to init libfreetype! %d\n", error);
+			goto exit;
+		}
+	}
+
+	FT_Face face;
+	error = FT_New_Face(font_data.ft_library, filename, 0, &face);
+	if(error)
+	{
+		fprintf(stderr, "Failed to load truetype face %s: %d\n", filename, error);
+		goto exit;
+	}
+
+	error = FT_Set_Char_Size(face, 0, size*16, dpi, dpi);
+	if(error)
+	{
+		 fprintf(stderr, "Failed to set truetype face size to %d, dpi %d: %d\n", size, dpi, error);
+		 FT_Done_Face(face);
+		 goto exit;
+	}
+
+	res = new TrueTypeFont;
+	res->type = FONT_TYPE_TTF;
+	res->size = size;
+	res->dpi = dpi;
+	res->face = face;
+	res->max_height = -1;
+	res->base = -1;
+	res->refcount = 1;
+
+	pthread_mutex_init(&res->mutex, 0);
+
+	key = new TrueTypeFontKey;
+	key->path = strdup(filename);
+	key->size = size;
+	key->dpi = dpi;
+
+	res->key = key;
+	font_data.fonts[*key] = res;
+
+exit:
+	pthread_mutex_unlock(&font_data.mutex);
+	return res;
+}
+
+void* twrpTruetype::gr_ttf_scaleFont(void *font, int max_width, int measured_width) {
+	if (!font)
+		return nullptr;
+
+	TrueTypeFont *f = (TrueTypeFont *)font;
+	float scale_value = (float)(max_width) / (float)(measured_width);
+	int new_size = ((int)((float)f->size * scale_value)) - 1;
+	if (new_size < 1)
+		new_size = 1;
+	const char* file = f->key->path.c_str();
+	int dpi = f->dpi;
+	return gr_ttf_loadFont(file, new_size, dpi);
+}
+
+static bool gr_ttf_freeFontCache(void *value, void *context __unused)
+{
+	TrueTypeCacheEntry *e = (TrueTypeCacheEntry *)value;
+	FT_Done_Glyph((FT_Glyph)e->glyph);
+	delete e;
+	return true;
+}
+
+void twrpTruetype::gr_ttf_freeStringCache(void *key, void *value, void *context __unused) {
+	StringCacheKey *k = (StringCacheKey *)key;
+	delete k;
+
+	StringCacheEntry *e = (StringCacheEntry *)value;
+	free(e->surface.data);
+	delete e;
+}
+
+void twrpTruetype::gr_ttf_freeFont(void *font) {
+	pthread_mutex_lock(&font_data.mutex);
+
+	TrueTypeFont *d = (TrueTypeFont *)font;
+	if(--d->refcount == 0)
+	{
+		delete d->key;
+
+		FT_Done_Face(d->face);
+
+		StringCacheMap::iterator stringCacheEntryIt = d->string_cache.begin();
+		while (stringCacheEntryIt != d->string_cache.end()) {
+			gr_ttf_freeStringCache(stringCacheEntryIt->second->key, stringCacheEntryIt->second, nullptr);
+			stringCacheEntryIt = d->string_cache.erase(stringCacheEntryIt);
+		}
+
+		TrueTypeCacheEntryMap::iterator ttcIt = d->glyph_cache.begin();
+		while(ttcIt != d->glyph_cache.end()) {
+			gr_ttf_freeFontCache(ttcIt->second, nullptr);
+			ttcIt = d->glyph_cache.erase(ttcIt);
+		}
+
+		pthread_mutex_destroy(&d->mutex);
+
+		TrueTypeFontMap::iterator trueTypeFontIt = font_data.fonts.find(*(d->key));
+		delete d;
+		font_data.fonts.erase(trueTypeFontIt);
+
+	}
+
+	pthread_mutex_unlock(&font_data.mutex);
+}
+
+TrueTypeCacheEntry* twrpTruetype::gr_ttf_glyph_cache_peek(TrueTypeFont *font, int char_index) {
+	TrueTypeCacheEntryMap::iterator glyphCacheItr = font->glyph_cache.find(char_index);
+
+	if(glyphCacheItr != font->glyph_cache.end()) {
+		return font->glyph_cache[char_index];
+	}
+	return nullptr;
+}
+
+TrueTypeCacheEntry* twrpTruetype::gr_ttf_glyph_cache_get(TrueTypeFont *font, int char_index) {
+	TrueTypeCacheEntryMap::iterator glyphCacheItr = font->glyph_cache.find(char_index);
+	TrueTypeCacheEntry* res = nullptr;
+	if(glyphCacheItr == font->glyph_cache.end())
+	{
+		int error = FT_Load_Glyph(font->face, char_index, FT_LOAD_RENDER);
+		if(error)
+		{
+			fprintf(stderr, "Failed to load glyph idx %d: %d\n", char_index, error);
+			return nullptr;
+		}
+
+		FT_BitmapGlyph glyph;
+		error = FT_Get_Glyph(font->face->glyph, (FT_Glyph*)&glyph);
+		if(error)
+		{
+			fprintf(stderr, "Failed to copy glyph %d: %d\n", char_index, error);
+			return nullptr;
+		}
+
+		res = new TrueTypeCacheEntry;
+		res->glyph = glyph;
+		FT_Glyph_Get_CBox((FT_Glyph)glyph, FT_GLYPH_BBOX_PIXELS, &res->bbox);
+		font->glyph_cache[char_index] = res;
+	}
+	else {
+		res = glyphCacheItr->second;
+	}
+
+	return res;
+}
+
+int twrpTruetype::gr_ttf_copy_glyph_to_surface(GGLSurface *dest, FT_BitmapGlyph glyph, int offX, int offY, int base) {
+	unsigned y;
+	uint8_t *src_itr = glyph->bitmap.buffer;
+	uint8_t *dest_itr = dest->data;
+
+	if(glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
+	{
+		fprintf(stderr, "Unsupported pixel mode in FT_BitmapGlyph %d\n", glyph->bitmap.pixel_mode);
+		return -1;
+	}
+
+	dest_itr += (offY + base - glyph->top)*dest->stride + (offX + glyph->left);
+
+	// FIXME: if glyph->left is negative and everything else is 0 (e.g. letter 'j' in Roboto-Regular),
+	// the result might end up being before the buffer - I'm not sure how to properly handle this.
+	if(dest_itr < dest->data)
+		dest_itr = dest->data;
+
+	for(y = 0; y < glyph->bitmap.rows; ++y)
+	{
+		memcpy(dest_itr, src_itr, glyph->bitmap.width);
+		src_itr += glyph->bitmap.pitch;
+		dest_itr += dest->stride;
+	}
+	return 0;
+}
+
+void twrpTruetype::gr_ttf_calcMaxFontHeight(TrueTypeFont *f) {
+	char c;
+	int char_idx;
+	int error;
+	FT_Glyph glyph;
+	FT_BBox bbox;
+	FT_BBox bbox_glyph;
+	TrueTypeCacheEntry *ent;
+
+	bbox.yMin = bbox_glyph.yMin = LONG_MAX;
+	bbox.yMax = bbox_glyph.yMax = LONG_MIN;
+
+	for(c = '!'; c <= '~'; ++c)
+	{
+		char_idx = FT_Get_Char_Index(f->face, c);
+		ent = gr_ttf_glyph_cache_peek(f, char_idx);
+		if(ent)
+		{
+			bbox.yMin = MIN(bbox.yMin, ent->bbox.yMin);
+			bbox.yMax = MAX(bbox.yMax, ent->bbox.yMax);
+		}
+		else
+		{
+			error = FT_Load_Glyph(f->face, char_idx, 0);
+			if(error)
+				continue;
+
+			error = FT_Get_Glyph(f->face->glyph, &glyph);
+			if(error)
+				continue;
+
+			FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox_glyph);
+			bbox.yMin = MIN(bbox.yMin, bbox_glyph.yMin);
+			bbox.yMax = MAX(bbox.yMax, bbox_glyph.yMax);
+
+			FT_Done_Glyph(glyph);
+		}
+	}
+
+	if(bbox.yMin > bbox.yMax)
+		bbox.yMin = bbox.yMax = 0;
+
+	f->max_height = bbox.yMax - bbox.yMin;
+	f->base = bbox.yMax;
+
+	// FIXME: twrp fonts have some padding on top, I'll add it here
+	// Should be fixed in the themes
+	f->max_height += f->size / 4;
+	f->base += f->size / 4;
+}
+
+// returns number of bytes from const char *text rendered to fit max_width, not number of UTF8 characters!
+int twrpTruetype::gr_ttf_render_text(TrueTypeFont *font, GGLSurface *surface, const std::string text, int max_width) {
+	TrueTypeFont *f = font;
+	TrueTypeCacheEntry *ent;
+	int bytes_rendered = 0, total_w = 0;
+	int utf_bytes = 0;
+	unsigned int unicode = 0;
+	int i, x, diff, char_idx, prev_idx = 0;
+	int height;
+	FT_Vector delta;
+	uint8_t *data = NULL;
+	const char *text_itr = text.c_str();
+	int *char_idxs;
+	int char_idxs_len = 0;
+
+	char_idxs = new int[text.length()];
+
+	while(*text_itr)
+	{
+		utf_bytes = utf8_to_unicode(text_itr, &unicode);
+		text_itr += utf_bytes;
+		bytes_rendered += utf_bytes;
+
+		char_idx = FT_Get_Char_Index(f->face, unicode);
+		char_idxs[char_idxs_len] = char_idx;
+		ent = gr_ttf_glyph_cache_get(f, char_idx);
+		if(ent)
+		{
+			diff = ent->glyph->root.advance.x >> 16;
+
+			if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
+			{
+				FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
+				diff += delta.x >> 6;
+			}
+
+			if(max_width != -1 && total_w + diff > max_width)
+				break;
+
+			total_w += diff;
+		}
+		prev_idx = char_idx;
+		++char_idxs_len;
+	}
+
+	if(font->max_height == -1)
+		gr_ttf_calcMaxFontHeight(font);
+
+	if(font->max_height == -1)
+	{
+		delete [] char_idxs;
+		return -1;
+	}
+
+	height = font->max_height;
+
+	data = (uint8_t *)malloc(total_w*height);
+    memset(data, 0, total_w*height);	x = 0;
+	prev_idx = 0;
+
+	surface->version = sizeof(*surface);
+	surface->width = total_w;
+	surface->height = height;
+	surface->stride = total_w;
+	surface->data = (GGLubyte*)data;
+	surface->format = GGL_PIXEL_FORMAT_A_8;
+
+	for(i = 0; i < char_idxs_len; ++i)
+	{
+		char_idx = char_idxs[i];
+		if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
+		{
+			FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
+			x += delta.x >> 6;
+		}
+
+		ent = gr_ttf_glyph_cache_get(f, char_idx);
+		if(ent)
+		{
+			gr_ttf_copy_glyph_to_surface(surface, ent->glyph, x, 0, font->base);
+			x += ent->glyph->root.advance.x >> 16;
+		}
+
+		prev_idx = char_idx;
+	}
+
+	delete [] char_idxs;
+	return bytes_rendered;
+}
+
+StringCacheEntry* twrpTruetype::gr_ttf_string_cache_peek(TrueTypeFont *font, 
+	const std::string text, 
+	__attribute__((unused)) int max_width) {
+		StringCacheKey k = {
+			.text = text,
+			.max_width = max_width
+		};
+		StringCacheMap::iterator stringCacheItr = font->string_cache.find(k);
+		if (stringCacheItr != font->string_cache.end()) {
+			return stringCacheItr->second;
+		}
+		else {
+			return nullptr;
+		}
+}
+
+void twrpTruetype::gr_ttf_string_cache_truncate(TrueTypeFont *font) {
+	StringCacheMap::iterator stringCacheItr;
+
+	if (font->string_cache.size() == STRING_CACHE_MAX_ENTRIES) {
+		StringCacheEntry *truncateEntry = nullptr;
+		stringCacheItr = font->string_cache.begin();
+		int deleteCtr = 0;
+		while (stringCacheItr != font->string_cache.end() || deleteCtr == (STRING_CACHE_MAX_ENTRIES - 1)) {
+			truncateEntry = stringCacheItr->second;
+			gr_ttf_freeStringCache(truncateEntry->key, truncateEntry, nullptr);
+			stringCacheItr = font->string_cache.erase(stringCacheItr);
+			deleteCtr++;
+		}
+	}
+}
+
+StringCacheEntry* twrpTruetype::gr_ttf_string_cache_get(TrueTypeFont *font, const std::string text, int max_width) {
+	StringCacheEntry *res = nullptr;
+	StringCacheMap::iterator stringCacheItr;
+
+	StringCacheKey k = {
+		.text = text,
+		.max_width = max_width
+	};
+
+	stringCacheItr = font->string_cache.find(k);
+	if (stringCacheItr == font->string_cache.end()) {
+		res = new StringCacheEntry;
+		res->rendered_bytes = gr_ttf_render_text(font, &res->surface, text, max_width);
+		if(res->rendered_bytes < 0) {
+			delete res;
+			return nullptr;
+		}
+
+		StringCacheKey *new_key = new StringCacheKey;
+		new_key->max_width = max_width;
+		new_key->text = text;
+		res->key = new_key;
+		font->string_cache[*new_key] = res; 
+	}
+	else
+	{
+		res = stringCacheItr->second;
+	}
+	return res;
+}
+
+int twrpTruetype::gr_ttf_measureEx(const char *s, void *font) {
+	TrueTypeFont *f = (TrueTypeFont *)font;
+	int res = -1;
+
+	pthread_mutex_lock(&f->mutex);
+	gr_ttf_string_cache_truncate(f);
+	StringCacheEntry *e = gr_ttf_string_cache_get(f, s, -1);
+	if(e)
+		res = e->surface.width;
+	pthread_mutex_unlock(&f->mutex);
+
+	return res;
+}
+
+int twrpTruetype::gr_ttf_maxExW(const char *s, void *font, int max_width) {
+	TrueTypeFont *f = (TrueTypeFont *)font;
+	TrueTypeCacheEntry *ent;
+	int max_bytes = 0, total_w = 0;
+	int utf_bytes, prev_utf_bytes = 0;
+	unsigned int unicode = 0;
+	int char_idx, prev_idx = 0;
+	FT_Vector delta;
+	StringCacheEntry *e;
+
+	pthread_mutex_lock(&f->mutex);
+
+	e = gr_ttf_string_cache_peek(f, s, max_width);
+	if (e) {
+		max_bytes = e->rendered_bytes;
+		pthread_mutex_unlock(&f->mutex);
+		return max_bytes;
+	}
+
+	while(*s) {
+		utf_bytes = utf8_to_unicode(s, &unicode);
+		s += utf_bytes;
+
+		char_idx = FT_Get_Char_Index(f->face, unicode);
+		if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
+		{
+			FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
+			total_w += delta.x >> 6;
+		}
+		prev_idx = char_idx;
+
+		if(total_w > max_width)
+		{
+			max_bytes -= prev_utf_bytes;
+			break;
+		}
+		prev_utf_bytes = utf_bytes;
+		ent = gr_ttf_glyph_cache_get(f, char_idx);
+		if(!ent)
+			continue;
+
+		total_w += ent->glyph->root.advance.x >> 16;
+		max_bytes += utf_bytes;
+	}
+	pthread_mutex_unlock(&f->mutex);
+	return max_bytes;
+}
+
+int twrpTruetype::gr_ttf_textExWH(void *context, int x, int y,
+					const char *s, void *pFont,
+					int max_width, int max_height,
+					const gr_surface gr_draw_surface) {
+	GGLContext *gl = (GGLContext *)context;
+	TrueTypeFont *font = (TrueTypeFont *)pFont;
+	const GRSurface *gr_draw = (const GRSurface*) gr_draw_surface;
+
+	// not actualy max width, but max_width + x
+	if(max_width != -1)
+	{
+		max_width -= x;
+		if(max_width <= 0)
+			return 0;
+	}
+
+	pthread_mutex_lock(&font->mutex);
+
+	StringCacheEntry *e = gr_ttf_string_cache_get(font, s, max_width);
+	if (!e) {
+		pthread_mutex_unlock(&font->mutex);
+		return -1;
+	}
+
+	GGLSurface string_surface_rotated;
+	if (gr_rotation != 0) {
+		// Do not perform relatively expensive operation if not needed
+		string_surface_rotated.version = sizeof(string_surface_rotated);
+		// Skip the **(gr_rotation == 0)** || (gr_rotation == 180) check
+		// because we are under a gr_rotation != 0 conditional compilation statement
+		string_surface_rotated.width   = (gr_rotation == 180) ? e->surface.width  : e->surface.height;
+		string_surface_rotated.height  = (gr_rotation == 180) ? e->surface.height : e->surface.width;
+		string_surface_rotated.stride  = string_surface_rotated.width;
+		string_surface_rotated.format  = e->surface.format;
+		// e->surface.format is GGL_PIXEL_FORMAT_A_8 (grayscale)
+		string_surface_rotated.data    = (GGLubyte*) malloc(string_surface_rotated.stride * string_surface_rotated.height * 1);
+		surface_ROTATION_transform((gr_surface) &string_surface_rotated, (const gr_surface) &e->surface, 1);
+	}
+
+	int y_bottom = y + e->surface.height;
+	int res = e->rendered_bytes;
+
+	if(max_height != -1 && max_height < y_bottom)
+	{
+		y_bottom = max_height;
+		if(y_bottom <= y)
+		{
+			pthread_mutex_unlock(&font->mutex);
+			return 0;
+		}
+	}
+
+	// Figuring out display coordinates works for gr_rotation == 0 too,
+	// and isn't as expensive as allocating and rotating another surface,
+	// so we do this anyway.
+	int x0_disp, y0_disp, x1_disp, y1_disp;
+	int l_disp, r_disp, t_disp, b_disp;
+
+	x0_disp = ROTATION_X_DISP(x, y, gr_draw->width);
+	y0_disp = ROTATION_Y_DISP(x, y, gr_draw->height);
+	x1_disp = ROTATION_X_DISP(x + e->surface.width, y_bottom, gr_draw->width);
+	y1_disp = ROTATION_Y_DISP(x + e->surface.width, y_bottom, gr_draw->height);
+	l_disp = std::min(x0_disp, x1_disp);
+	r_disp = std::max(x0_disp, x1_disp);
+	t_disp = std::min(y0_disp, y1_disp);
+	b_disp = std::max(y0_disp, y1_disp);
+
+	if (gr_rotation != 0) {
+		gl->bindTexture(gl, &string_surface_rotated);
+	} else {
+		gl->bindTexture(gl, &e->surface);
+	}
+	gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
+	gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+	gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
+
+	gl->enable(gl, GGL_TEXTURE_2D);
+	gl->texCoord2i(gl, -l_disp, -t_disp);
+	gl->recti(gl, l_disp, t_disp, r_disp, b_disp);
+	gl->disable(gl, GGL_TEXTURE_2D);
+
+	if (gr_rotation != 0)
+		free(string_surface_rotated.data);
+
+	pthread_mutex_unlock(&font->mutex);;
+	return res;
+}
+
+int twrpTruetype::gr_ttf_getMaxFontHeight(void *font) {
+	int res;
+	TrueTypeFont *f = (TrueTypeFont *)font;
+
+	pthread_mutex_lock(&f->mutex);
+
+	if(f->max_height == -1)
+		gr_ttf_calcMaxFontHeight(f);
+	res = f->max_height;
+
+	pthread_mutex_unlock(&f->mutex);
+	return res;
+}
diff --git a/minuitwrp/tspdrv.cpp b/minuitwrp/tspdrv.cpp
new file mode 100644
index 0000000..6967cd5
--- /dev/null
+++ b/minuitwrp/tspdrv.cpp
@@ -0,0 +1,193 @@
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <cinttypes>
+#include <cmath>
+#include <iostream>
+#include <sys/ioctl.h>
+
+#include "tspdrv.h"
+
+int tspdrv_initialized = 0;
+int tspdrv_file_desc;
+int tspdrv_numActuators = 0;
+
+int initialize_tspdrv()
+{
+    // Open device file as read/write for ioctl and write 
+    tspdrv_file_desc = open(TSPDRV, O_RDWR);
+    if(tspdrv_file_desc < 0)
+    {
+        printf("Failed to open device file: %s", TSPDRV);
+        return -1;
+    }
+
+    // create default device parameters
+    device_parameter dev_param1 { 0, VIBE_KP_CFG_FREQUENCY_PARAM1, 0};
+    device_parameter dev_param2 { 0, VIBE_KP_CFG_FREQUENCY_PARAM2, 0};
+    device_parameter dev_param3 { 0, VIBE_KP_CFG_FREQUENCY_PARAM3, 0};
+    device_parameter dev_param4 { 0, VIBE_KP_CFG_FREQUENCY_PARAM4, 400};
+    device_parameter dev_param5 { 0, VIBE_KP_CFG_FREQUENCY_PARAM5, 13435};
+    device_parameter dev_param6 { 0, VIBE_KP_CFG_FREQUENCY_PARAM6, 0};
+    device_parameter dev_param_update_rate {0, VIBE_KP_CFG_UPDATE_RATE_MS, 5};
+
+    // Set magic number for vibration driver, wont allow us to write data without!
+    int ret = ioctl(tspdrv_file_desc, TSPDRV_SET_MAGIC_NUMBER, TSPDRV_MAGIC_NUMBER);
+    if(ret != 0)
+    {
+        printf("Failed to set magic number");
+        return -ret;
+    }
+
+    // Set default device parameter 1
+    ret = ioctl(tspdrv_file_desc, TSPDRV_SET_DEVICE_PARAMETER, &dev_param1);
+    if(ret != 0)
+    {
+        printf("Failed to set device parameter 1");
+        return -ret;
+    }
+
+    // Set default device parameter 2
+    ret = ioctl(tspdrv_file_desc, TSPDRV_SET_DEVICE_PARAMETER, &dev_param2);
+    if(ret != 0)
+    {
+        printf("Failed to set device parameter 2");
+        return -ret;
+    }
+
+    // Set default device parameter 3
+    ret = ioctl(tspdrv_file_desc, TSPDRV_SET_DEVICE_PARAMETER, &dev_param3);
+    if(ret != 0)
+    {
+        printf("Failed to set device parameter 3");
+        return -ret;
+    }
+
+    // Set default device parameter 4
+    ret = ioctl(tspdrv_file_desc, TSPDRV_SET_DEVICE_PARAMETER, &dev_param4);
+    if(ret != 0)
+    {
+        printf("Failed to set device parameter 4");
+        return -ret;
+    }
+
+    // Set default device parameter 5
+    ret = ioctl(tspdrv_file_desc, TSPDRV_SET_DEVICE_PARAMETER, &dev_param5);
+    if(ret != 0)
+    {
+        printf("Failed to set device parameter 5");
+        return -ret;
+    }
+
+    // Set default device parameter 6
+    ret = ioctl(tspdrv_file_desc, TSPDRV_SET_DEVICE_PARAMETER, &dev_param6);
+    if(ret != 0)
+    {
+        printf("Failed to set device parameter 6");
+        return -ret;
+    }
+
+    // Set default device parameter update rate
+    ret = ioctl(tspdrv_file_desc, TSPDRV_SET_DEVICE_PARAMETER, &dev_param_update_rate);
+    if(ret != 0)
+    {
+        printf("Failed to set device parameter update rate");
+        return -ret;
+    }
+
+    // Get number of actuators the device has
+    ret = ioctl(tspdrv_file_desc, TSPDRV_GET_NUM_ACTUATORS, 0);
+    if(ret == 0)
+    {
+        printf("No actuators found!");
+        return -2;
+    }
+
+    tspdrv_numActuators = ret;
+    tspdrv_initialized = 1;
+    return 0;
+}
+
+int tspdrv_off()  {
+
+    for(int32_t i = 0; i < tspdrv_numActuators; i++)
+    {
+        int32_t ret = ioctl(tspdrv_file_desc, TSPDRV_DISABLE_AMP, i);
+        if(ret != 0)
+        {
+            printf("Failed to deactivate Actuator with index %d", i);
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+int vibrate(int timeout_ms)
+{
+    double BUFFER_ENTRIES_PER_MS = 8.21;
+    uint8_t DEFAULT_AMPLITUDE = 127;
+    int32_t OUTPUT_BUFFER_SIZE = 40;
+
+    if(!tspdrv_initialized)
+    {
+        printf("Initializing TSPDRV\n");
+        if(initialize_tspdrv() == 0)
+        {
+            printf("TSPDRV initialized\n");
+        }
+    }
+
+    // Calculate needed buffer entries
+    int32_t bufferSize = (int32_t) round(BUFFER_ENTRIES_PER_MS * timeout_ms); 
+    VibeUInt8 fullBuffer[bufferSize];
+
+    // turn previous vibrations off
+    tspdrv_off();
+
+    for(int32_t i = 0; i < bufferSize; i++)
+    {
+        // The vibration is a sine curve, the negative parts are 255 + negative value
+        fullBuffer[i] = (VibeUInt8) (DEFAULT_AMPLITUDE * sin(i/BUFFER_ENTRIES_PER_MS));
+    }
+
+    // Amount of buffer arrays with size of OUTPUT_BUFFER_SIZE
+    int32_t numBuffers = (int32_t) ceil((double)bufferSize / (double)OUTPUT_BUFFER_SIZE);
+    VibeUInt8 outputBuffers[numBuffers][OUTPUT_BUFFER_SIZE];
+    memset(outputBuffers, 0, sizeof(outputBuffers));  // zero the array before we fill it with values
+
+    for(int32_t i = 0; i < bufferSize; i++)
+    {
+        // split fullBuffer into multiple smaller buffers with size OUTPUT_BUFFER_SIZE
+        outputBuffers[i/OUTPUT_BUFFER_SIZE][i%OUTPUT_BUFFER_SIZE] = fullBuffer[i];
+    }
+
+    for(int32_t i = 0; i < tspdrv_numActuators; i++)
+    {
+        for(int32_t j = 0; j < numBuffers; j++)
+        {
+            char output[OUTPUT_BUFFER_SIZE + SPI_HEADER_SIZE];
+            memset(output, 0, sizeof(output));
+            output[0] = i;  // first byte is actuator index
+            output[1] = 8;  // per definition has to be 8
+            output[2] = OUTPUT_BUFFER_SIZE; // size of the following output buffer
+            for(int32_t k = 3; k < OUTPUT_BUFFER_SIZE+3; k++)
+            {
+                output[k] = outputBuffers[j][k-3];
+            }
+            // write the buffer to the device
+            write(tspdrv_file_desc, output, sizeof(output));
+            if((j+1) % 4 == 0)
+            {
+                // every 4 buffers, but not the first if theres only 1, we send an ENABLE_AMP signal
+                int32_t ret = ioctl(tspdrv_file_desc, TSPDRV_ENABLE_AMP, i);
+                if(ret != 0)
+                {
+                    printf("Failed to activate Actuator with index %d", i);
+                    return -1;
+                }
+            }
+        }
+    }
+    return 0;
+}
\ No newline at end of file
diff --git a/minuitwrp/tspdrv.h b/minuitwrp/tspdrv.h
new file mode 100644
index 0000000..afb294e
--- /dev/null
+++ b/minuitwrp/tspdrv.h
@@ -0,0 +1,100 @@
+/*
+** =========================================================================
+** File:
+**     tspdrv.h
+**
+** Description:
+**     Constants and type definitions for the TouchSense Kernel Module.
+**
+** Portions Copyright (c) 2008-2017 Immersion Corporation. All Rights Reserved.
+**
+** This file contains Original Code and/or Modifications of Original Code
+** as defined in and that are subject to the GNU Public License v2 -
+** (the 'License'). You may not use this file except in compliance with the
+** License. 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 or contact
+** TouchSenseSales@immersion.com.
+**
+** The Original Code and all software distributed under the License are
+** distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+** EXPRESS OR IMPLIED, AND IMMERSION HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+** INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
+** FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see
+** the License for the specific language governing rights and limitations
+** under the License.
+** =========================================================================
+*/
+
+#ifndef _TSPDRV_H
+#define _TSPDRV_H
+
+/* Constants */
+#define MODULE_NAME                         "tspdrv"
+#define TSPDRV                              "/dev/tspdrv"
+#define TSPDRV_MAGIC_NUMBER                 0x494D4D52
+#define TSPDRV_IOCTL_GROUP                  0x52
+#define TSPDRV_STOP_KERNEL_TIMER            _IO(TSPDRV_IOCTL_GROUP, 1) /* obsolete, may be removed in future */
+#define TSPDRV_SET_MAGIC_NUMBER             _IO(TSPDRV_IOCTL_GROUP, 2)
+#define TSPDRV_ENABLE_AMP                   _IO(TSPDRV_IOCTL_GROUP, 3)
+#define TSPDRV_DISABLE_AMP                  _IO(TSPDRV_IOCTL_GROUP, 4)
+#define TSPDRV_GET_NUM_ACTUATORS            _IO(TSPDRV_IOCTL_GROUP, 5)
+#define TSPDRV_SET_DEVICE_PARAMETER         _IO(TSPDRV_IOCTL_GROUP, 6)
+#define TSPDRV_SET_DBG_LEVEL                _IO(TSPDRV_IOCTL_GROUP, 7)
+#define TSPDRV_GET_DBG_LEVEL                _IO(TSPDRV_IOCTL_GROUP, 8)
+#define TSPDRV_SET_RUNTIME_RECORD_FLAG      _IO(TSPDRV_IOCTL_GROUP, 9)
+#define TSPDRV_GET_RUNTIME_RECORD_FLAG      _IO(TSPDRV_IOCTL_GROUP, 10)
+#define TSPDRV_SET_RUNTIME_RECORD_BUF_SIZE  _IO(TSPDRV_IOCTL_GROUP, 11)
+#define TSPDRV_GET_RUNTIME_RECORD_BUF_SIZE  _IO(TSPDRV_IOCTL_GROUP, 12)
+#define TSPDRV_GET_PARAM_FILE_ID            _IO(TSPDRV_IOCTL_GROUP, 13)
+#define TSPDRV_GET_DEVICE_STATUS            _IO(TSPDRV_IOCTL_GROUP, 14)
+/*
+** Frequency constant parameters to control force output values and signals.
+*/
+#define VIBE_KP_CFG_FREQUENCY_PARAM1        85
+#define VIBE_KP_CFG_FREQUENCY_PARAM2        86
+#define VIBE_KP_CFG_FREQUENCY_PARAM3        87
+#define VIBE_KP_CFG_FREQUENCY_PARAM4        88
+#define VIBE_KP_CFG_FREQUENCY_PARAM5        89
+#define VIBE_KP_CFG_FREQUENCY_PARAM6        90
+
+/*
+** Force update rate in milliseconds.
+*/
+#define VIBE_KP_CFG_UPDATE_RATE_MS          95
+
+#define VIBE_MAX_DEVICE_NAME_LENGTH         64
+#define SPI_HEADER_SIZE                     3   /* DO NOT CHANGE - SPI buffer header size */
+#define VIBE_OUTPUT_SAMPLE_SIZE             50  /* DO NOT CHANGE - maximum number of samples */
+#define MAX_DEBUG_BUFFER_LENGTH             1024
+
+typedef int8_t		VibeInt8;
+typedef u_int8_t	VibeUInt8;
+typedef int16_t		VibeInt16;
+typedef u_int16_t	VibeUInt16;
+typedef int32_t		VibeInt32;
+typedef u_int32_t	VibeUInt32;
+typedef u_int8_t	VibeBool;
+typedef VibeInt32	VibeStatus;
+
+/* Device parameters sent to the kernel module, tspdrv.ko */
+typedef struct
+{
+    VibeInt32 nDeviceIndex;
+    VibeInt32 nDeviceParamID;
+    VibeInt32 nDeviceParamValue;
+} device_parameter;
+
+typedef struct
+{
+    VibeUInt8 nActuatorIndex;  /* 1st byte is actuator index */
+    VibeUInt8 nBitDepth;       /* 2nd byte is bit depth */
+    VibeUInt8 nBufferSize;     /* 3rd byte is data size */
+    VibeUInt8 dataBuffer[40];
+} actuator_samples_buffer;
+
+/* Error and Return value codes */
+#define VIBE_S_SUCCESS                      0	/* Success */
+#define VIBE_E_FAIL			   -4	/* Generic error */
+
+#endif  /* _TSPDRV_H */
diff --git a/minzip/Android.mk b/minzip/Android.mk
new file mode 100644
index 0000000..8b52f35
--- /dev/null
+++ b/minzip/Android.mk
@@ -0,0 +1,57 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	Hash.c \
+	SysUtil.c \
+	DirUtil.c \
+	Inlines.c \
+	Zip.c
+
+LOCAL_C_INCLUDES := \
+	external/zlib \
+	external/safe-iop/include
+
+LOCAL_C_INCLUDES += external/libselinux/include
+LOCAL_SHARED_LIBRARIES += libselinux
+
+LOCAL_CFLAGS += -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION)
+
+LOCAL_MODULE := libminzip
+
+LOCAL_SHARED_LIBRARIES += libz
+
+LOCAL_CLANG := true
+
+LOCAL_CFLAGS += -Werror -Wall
+
+include $(BUILD_SHARED_LIBRARY)
+
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	Hash.c \
+	SysUtil.c \
+	DirUtil.c \
+	Inlines.c \
+	Zip.c
+
+LOCAL_C_INCLUDES += \
+	external/zlib \
+	external/safe-iop/include
+
+LOCAL_C_INCLUDES += external/libselinux/include
+LOCAL_STATIC_LIBRARIES += libselinux
+
+LOCAL_CFLAGS += -DPLATFORM_SDK_VERSION=$(PLATFORM_SDK_VERSION)
+
+LOCAL_MODULE := libminzip
+
+LOCAL_STATIC_LIBRARIES += libz
+
+LOCAL_CLANG := true
+
+LOCAL_CFLAGS += -Werror -Wall
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/minzip/Bits.h b/minzip/Bits.h
new file mode 100644
index 0000000..f96e6c4
--- /dev/null
+++ b/minzip/Bits.h
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Some handy functions for manipulating bits and bytes.
+ */
+#ifndef _MINZIP_BITS
+#define _MINZIP_BITS
+
+#include "inline_magic.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * Get 1 byte.  (Included to make the code more legible.)
+ */
+INLINE unsigned char get1(unsigned const char* pSrc)
+{
+    return *pSrc;
+}
+
+/*
+ * Get 2 big-endian bytes.
+ */
+INLINE unsigned short get2BE(unsigned char const* pSrc)
+{
+    unsigned short result;
+
+    result = *pSrc++ << 8;
+    result |= *pSrc++;
+
+    return result;
+}
+
+/*
+ * Get 4 big-endian bytes.
+ */
+INLINE unsigned int get4BE(unsigned char const* pSrc)
+{
+    unsigned int result;
+
+    result = *pSrc++ << 24;
+    result |= *pSrc++ << 16;
+    result |= *pSrc++ << 8;
+    result |= *pSrc++;
+
+    return result;
+}
+
+/*
+ * Get 8 big-endian bytes.
+ */
+INLINE unsigned long long get8BE(unsigned char const* pSrc)
+{
+    unsigned long long result;
+
+    result = (unsigned long long) *pSrc++ << 56;
+    result |= (unsigned long long) *pSrc++ << 48;
+    result |= (unsigned long long) *pSrc++ << 40;
+    result |= (unsigned long long) *pSrc++ << 32;
+    result |= (unsigned long long) *pSrc++ << 24;
+    result |= (unsigned long long) *pSrc++ << 16;
+    result |= (unsigned long long) *pSrc++ << 8;
+    result |= (unsigned long long) *pSrc++;
+
+    return result;
+}
+
+/*
+ * Get 2 little-endian bytes.
+ */
+INLINE unsigned short get2LE(unsigned char const* pSrc)
+{
+    unsigned short result;
+
+    result = *pSrc++;
+    result |= *pSrc++ << 8;
+
+    return result;
+}
+
+/*
+ * Get 4 little-endian bytes.
+ */
+INLINE unsigned int get4LE(unsigned char const* pSrc)
+{
+    unsigned int result;
+
+    result = *pSrc++;
+    result |= *pSrc++ << 8;
+    result |= *pSrc++ << 16;
+    result |= *pSrc++ << 24;
+
+    return result;
+}
+
+/*
+ * Get 8 little-endian bytes.
+ */
+INLINE unsigned long long get8LE(unsigned char const* pSrc)
+{
+    unsigned long long result;
+
+    result = (unsigned long long) *pSrc++;
+    result |= (unsigned long long) *pSrc++ << 8;
+    result |= (unsigned long long) *pSrc++ << 16;
+    result |= (unsigned long long) *pSrc++ << 24;
+    result |= (unsigned long long) *pSrc++ << 32;
+    result |= (unsigned long long) *pSrc++ << 40;
+    result |= (unsigned long long) *pSrc++ << 48;
+    result |= (unsigned long long) *pSrc++ << 56;
+
+    return result;
+}
+
+/*
+ * Grab 1 byte and advance the data pointer.
+ */
+INLINE unsigned char read1(unsigned const char** ppSrc)
+{
+    return *(*ppSrc)++;
+}
+
+/*
+ * Grab 2 big-endian bytes and advance the data pointer.
+ */
+INLINE unsigned short read2BE(unsigned char const** ppSrc)
+{
+    unsigned short result;
+
+    result = *(*ppSrc)++ << 8;
+    result |= *(*ppSrc)++;
+
+    return result;
+}
+
+/*
+ * Grab 4 big-endian bytes and advance the data pointer.
+ */
+INLINE unsigned int read4BE(unsigned char const** ppSrc)
+{
+    unsigned int result;
+
+    result = *(*ppSrc)++ << 24;
+    result |= *(*ppSrc)++ << 16;
+    result |= *(*ppSrc)++ << 8;
+    result |= *(*ppSrc)++;
+
+    return result;
+}
+
+/*
+ * Get 8 big-endian bytes.
+ */
+INLINE unsigned long long read8BE(unsigned char const** ppSrc)
+{
+    unsigned long long result;
+
+    result = (unsigned long long) *(*ppSrc)++ << 56;
+    result |= (unsigned long long) *(*ppSrc)++ << 48;
+    result |= (unsigned long long) *(*ppSrc)++ << 40;
+    result |= (unsigned long long) *(*ppSrc)++ << 32;
+    result |= (unsigned long long) *(*ppSrc)++ << 24;
+    result |= (unsigned long long) *(*ppSrc)++ << 16;
+    result |= (unsigned long long) *(*ppSrc)++ << 8;
+    result |= (unsigned long long) *(*ppSrc)++;
+
+    return result;
+}
+
+/*
+ * Grab 2 little-endian bytes and advance the data pointer.
+ */
+INLINE unsigned short read2LE(unsigned char const** ppSrc)
+{
+    unsigned short result;
+
+    result = *(*ppSrc)++;
+    result |= *(*ppSrc)++ << 8;
+
+    return result;
+}
+
+/*
+ * Grab 4 little-endian bytes and advance the data pointer.
+ */
+INLINE unsigned int read4LE(unsigned char const** ppSrc)
+{
+    unsigned int result;
+
+    result = *(*ppSrc)++;
+    result |= *(*ppSrc)++ << 8;
+    result |= *(*ppSrc)++ << 16;
+    result |= *(*ppSrc)++ << 24;
+
+    return result;
+}
+
+/*
+ * Get 8 little-endian bytes.
+ */
+INLINE unsigned long long read8LE(unsigned char const** ppSrc)
+{
+    unsigned long long result;
+
+    result = (unsigned long long) *(*ppSrc)++;
+    result |= (unsigned long long) *(*ppSrc)++ << 8;
+    result |= (unsigned long long) *(*ppSrc)++ << 16;
+    result |= (unsigned long long) *(*ppSrc)++ << 24;
+    result |= (unsigned long long) *(*ppSrc)++ << 32;
+    result |= (unsigned long long) *(*ppSrc)++ << 40;
+    result |= (unsigned long long) *(*ppSrc)++ << 48;
+    result |= (unsigned long long) *(*ppSrc)++ << 56;
+
+    return result;
+}
+
+/*
+ * Skip over a UTF-8 string.
+ */
+INLINE void skipUtf8String(unsigned char const** ppSrc)
+{
+    unsigned int length = read4BE(ppSrc);
+
+    (*ppSrc) += length;
+}
+
+/*
+ * Read a UTF-8 string into a fixed-size buffer, and null-terminate it.
+ *
+ * Returns the length of the original string.
+ */
+INLINE int readUtf8String(unsigned char const** ppSrc, char* buf, size_t bufLen)
+{
+    unsigned int length = read4BE(ppSrc);
+    size_t copyLen = (length < bufLen) ? length : bufLen-1;
+
+    memcpy(buf, *ppSrc, copyLen);
+    buf[copyLen] = '\0';
+
+    (*ppSrc) += length;
+    return length;
+}
+
+/*
+ * Read a UTF-8 string into newly-allocated storage, and null-terminate it.
+ *
+ * Returns the string and its length.  (The latter is probably unnecessary
+ * for the way we're using UTF8.)
+ */
+INLINE char* readNewUtf8String(unsigned char const** ppSrc, size_t* pLength)
+{
+    unsigned int length = read4BE(ppSrc);
+    char* buf;
+
+    buf = (char*) malloc(length+1);
+
+    memcpy(buf, *ppSrc, length);
+    buf[length] = '\0';
+
+    (*ppSrc) += length;
+
+    *pLength = length;
+    return buf;
+}
+
+
+/*
+ * Set 1 byte.  (Included to make the code more legible.)
+ */
+INLINE void set1(unsigned char* buf, unsigned char val)
+{
+    *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 2 big-endian bytes.
+ */
+INLINE void set2BE(unsigned char* buf, unsigned short val)
+{
+    *buf++ = (unsigned char)(val >> 8);
+    *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 4 big-endian bytes.
+ */
+INLINE void set4BE(unsigned char* buf, unsigned int val)
+{
+    *buf++ = (unsigned char)(val >> 24);
+    *buf++ = (unsigned char)(val >> 16);
+    *buf++ = (unsigned char)(val >> 8);
+    *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 8 big-endian bytes.
+ */
+INLINE void set8BE(unsigned char* buf, unsigned long long val)
+{
+    *buf++ = (unsigned char)(val >> 56);
+    *buf++ = (unsigned char)(val >> 48);
+    *buf++ = (unsigned char)(val >> 40);
+    *buf++ = (unsigned char)(val >> 32);
+    *buf++ = (unsigned char)(val >> 24);
+    *buf++ = (unsigned char)(val >> 16);
+    *buf++ = (unsigned char)(val >> 8);
+    *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 2 little-endian bytes.
+ */
+INLINE void set2LE(unsigned char* buf, unsigned short val)
+{
+    *buf++ = (unsigned char)(val);
+    *buf = (unsigned char)(val >> 8);
+}
+
+/*
+ * Set 4 little-endian bytes.
+ */
+INLINE void set4LE(unsigned char* buf, unsigned int val)
+{
+    *buf++ = (unsigned char)(val);
+    *buf++ = (unsigned char)(val >> 8);
+    *buf++ = (unsigned char)(val >> 16);
+    *buf = (unsigned char)(val >> 24);
+}
+
+/*
+ * Set 8 little-endian bytes.
+ */
+INLINE void set8LE(unsigned char* buf, unsigned long long val)
+{
+    *buf++ = (unsigned char)(val);
+    *buf++ = (unsigned char)(val >> 8);
+    *buf++ = (unsigned char)(val >> 16);
+    *buf++ = (unsigned char)(val >> 24);
+    *buf++ = (unsigned char)(val >> 32);
+    *buf++ = (unsigned char)(val >> 40);
+    *buf++ = (unsigned char)(val >> 48);
+    *buf = (unsigned char)(val >> 56);
+}
+
+/*
+ * Stuff a UTF-8 string into the buffer.
+ */
+INLINE void setUtf8String(unsigned char* buf, const unsigned char* str)
+{
+    unsigned int strLen = strlen((const char*)str);
+
+    set4BE(buf, strLen);
+    memcpy(buf + sizeof(unsigned int), str, strLen);
+}
+
+#endif /*_MINZIP_BITS*/
diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c
new file mode 100644
index 0000000..97cb2e0
--- /dev/null
+++ b/minzip/DirUtil.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <limits.h>
+
+#include "DirUtil.h"
+
+typedef enum { DMISSING, DDIR, DILLEGAL } DirStatus;
+
+static DirStatus
+getPathDirStatus(const char *path)
+{
+    struct stat st;
+    int err;
+
+    err = stat(path, &st);
+    if (err == 0) {
+        /* Something's there; make sure it's a directory.
+         */
+        if (S_ISDIR(st.st_mode)) {
+            return DDIR;
+        }
+        errno = ENOTDIR;
+        return DILLEGAL;
+    } else if (errno != ENOENT) {
+        /* Something went wrong, or something in the path
+         * is bad.  Can't do anything in this situation.
+         */
+        return DILLEGAL;
+    }
+    return DMISSING;
+}
+
+int
+dirCreateHierarchy(const char *path, int mode,
+        const struct utimbuf *timestamp, bool stripFileName,
+        struct selabel_handle *sehnd)
+{
+    DirStatus ds;
+
+    /* Check for an empty string before we bother
+     * making any syscalls.
+     */
+    if (path[0] == '\0') {
+        errno = ENOENT;
+        return -1;
+    }
+
+    /* Allocate a path that we can modify; stick a slash on
+     * the end to make things easier.
+     */
+    size_t pathLen = strlen(path);
+    char *cpath = (char *)malloc(pathLen + 2);
+    if (cpath == NULL) {
+        errno = ENOMEM;
+        return -1;
+    }
+    memcpy(cpath, path, pathLen);
+    if (stripFileName) {
+        /* Strip everything after the last slash.
+         */
+        char *c = cpath + pathLen - 1;
+        while (c != cpath && *c != '/') {
+            c--;
+        }
+        if (c == cpath) {
+            //xxx test this path
+            /* No directory component.  Act like the path was empty.
+             */
+            errno = ENOENT;
+            free(cpath);
+            return -1;
+        }
+        c[1] = '\0';    // Terminate after the slash we found.
+    } else {
+        /* Make sure that the path ends in a slash.
+         */
+        cpath[pathLen] = '/';
+        cpath[pathLen + 1] = '\0';
+    }
+
+    /* See if it already exists.
+     */
+    ds = getPathDirStatus(cpath);
+    if (ds == DDIR) {
+        return 0;
+    } else if (ds == DILLEGAL) {
+        return -1;
+    }
+
+    /* Walk up the path from the root and make each level.
+     * If a directory already exists, no big deal.
+     */
+    char *p = cpath;
+    while (*p != '\0') {
+        /* Skip any slashes, watching out for the end of the string.
+         */
+        while (*p != '\0' && *p == '/') {
+            p++;
+        }
+        if (*p == '\0') {
+            break;
+        }
+
+        /* Find the end of the next path component.
+         * We know that we'll see a slash before the NUL,
+         * because we added it, above.
+         */
+        while (*p != '/') {
+            p++;
+        }
+        *p = '\0';
+
+        /* Check this part of the path and make a new directory
+         * if necessary.
+         */
+        ds = getPathDirStatus(cpath);
+        if (ds == DILLEGAL) {
+            /* Could happen if some other process/thread is
+             * messing with the filesystem.
+             */
+            free(cpath);
+            return -1;
+        } else if (ds == DMISSING) {
+            int err;
+
+            char *secontext = NULL;
+
+            if (sehnd) {
+                selabel_lookup(sehnd, &secontext, cpath, mode);
+                setfscreatecon(secontext);
+            }
+
+            err = mkdir(cpath, mode);
+
+            if (secontext) {
+                freecon(secontext);
+                setfscreatecon(NULL);
+            }
+
+            if (err != 0) {
+                free(cpath);
+                return -1;
+            }
+            if (timestamp != NULL && utime(cpath, timestamp)) {
+                free(cpath);
+                return -1;
+            }
+        }
+        // else, this directory already exists.
+        
+        /* Repair the path and continue.
+         */
+        *p = '/';
+    }
+    free(cpath);
+
+    return 0;
+}
+
+int
+dirUnlinkHierarchy(const char *path)
+{
+    struct stat st;
+    DIR *dir;
+    struct dirent *de;
+    int fail = 0;
+
+    /* is it a file or directory? */
+    if (lstat(path, &st) < 0) {
+        return -1;
+    }
+
+    /* a file, so unlink it */
+    if (!S_ISDIR(st.st_mode)) {
+        return unlink(path);
+    }
+
+    /* a directory, so open handle */
+    dir = opendir(path);
+    if (dir == NULL) {
+        return -1;
+    }
+
+    /* recurse over components */
+    errno = 0;
+    while ((de = readdir(dir)) != NULL) {
+        //TODO: don't blow the stack
+        char dn[PATH_MAX];
+        if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
+            continue;
+        }
+        snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
+        if (dirUnlinkHierarchy(dn) < 0) {
+            fail = 1;
+            break;
+        }
+        errno = 0;
+    }
+    /* in case readdir or unlink_recursive failed */
+    if (fail || errno < 0) {
+        int save = errno;
+        closedir(dir);
+        errno = save;
+        return -1;
+    }
+
+    /* close directory handle */
+    if (closedir(dir) < 0) {
+        return -1;
+    }
+
+    /* delete target directory */
+    return rmdir(path);
+}
diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h
new file mode 100644
index 0000000..85a0012
--- /dev/null
+++ b/minzip/DirUtil.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINZIP_DIRUTIL_H_
+#define MINZIP_DIRUTIL_H_
+
+#include <stdbool.h>
+#include <utime.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+
+/* Like "mkdir -p", try to guarantee that all directories
+ * specified in path are present, creating as many directories
+ * as necessary.  The specified mode is passed to all mkdir
+ * calls;  no modifications are made to umask.
+ *
+ * If stripFileName is set, everything after the final '/'
+ * is stripped before creating the directory hierarchy.
+ *
+ * If timestamp is non-NULL, new directories will be timestamped accordingly.
+ *
+ * Returns 0 on success; returns -1 (and sets errno) on failure
+ * (usually if some element of path is not a directory).
+ */
+int dirCreateHierarchy(const char *path, int mode,
+        const struct utimbuf *timestamp, bool stripFileName,
+        struct selabel_handle* sehnd);
+
+/* rm -rf <path>
+ */
+int dirUnlinkHierarchy(const char *path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // MINZIP_DIRUTIL_H_
diff --git a/minzip/Hash.c b/minzip/Hash.c
new file mode 100644
index 0000000..49bcb31
--- /dev/null
+++ b/minzip/Hash.c
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Hash table.  The dominant calls are add and lookup, with removals
+ * happening very infrequently.  We use probing, and don't worry much
+ * about tombstone removal.
+ */
+#include <stdlib.h>
+#include <assert.h>
+
+#define LOG_TAG "minzip"
+#include "Log.h"
+#include "Hash.h"
+
+/* table load factor, i.e. how full can it get before we resize */
+//#define LOAD_NUMER  3       // 75%
+//#define LOAD_DENOM  4
+#define LOAD_NUMER  5       // 62.5%
+#define LOAD_DENOM  8
+//#define LOAD_NUMER  1       // 50%
+//#define LOAD_DENOM  2
+
+/*
+ * Compute the capacity needed for a table to hold "size" elements.
+ */
+size_t mzHashSize(size_t size) {
+    return (size * LOAD_DENOM) / LOAD_NUMER +1;
+}
+
+/*
+ * Round up to the next highest power of 2.
+ *
+ * Found on http://graphics.stanford.edu/~seander/bithacks.html.
+ */
+unsigned int roundUpPower2(unsigned int val)
+{
+    val--;
+    val |= val >> 1;
+    val |= val >> 2;
+    val |= val >> 4;
+    val |= val >> 8;
+    val |= val >> 16;
+    val++;
+
+    return val;
+}
+
+/*
+ * Create and initialize a hash table.
+ */
+HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc)
+{
+    HashTable* pHashTable;
+
+    assert(initialSize > 0);
+
+    pHashTable = (HashTable*) malloc(sizeof(*pHashTable));
+    if (pHashTable == NULL)
+        return NULL;
+
+    pHashTable->tableSize = roundUpPower2(initialSize);
+    pHashTable->numEntries = pHashTable->numDeadEntries = 0;
+    pHashTable->freeFunc = freeFunc;
+    pHashTable->pEntries =
+        (HashEntry*) calloc((size_t)pHashTable->tableSize, sizeof(HashTable));
+    if (pHashTable->pEntries == NULL) {
+        free(pHashTable);
+        return NULL;
+    }
+
+    return pHashTable;
+}
+
+/*
+ * Clear out all entries.
+ */
+void mzHashTableClear(HashTable* pHashTable)
+{
+    HashEntry* pEnt;
+    int i;
+
+    pEnt = pHashTable->pEntries;
+    for (i = 0; i < pHashTable->tableSize; i++, pEnt++) {
+        if (pEnt->data == HASH_TOMBSTONE) {
+            // nuke entry
+            pEnt->data = NULL;
+        } else if (pEnt->data != NULL) {
+            // call free func then nuke entry
+            if (pHashTable->freeFunc != NULL)
+                (*pHashTable->freeFunc)(pEnt->data);
+            pEnt->data = NULL;
+        }
+    }
+
+    pHashTable->numEntries = 0;
+    pHashTable->numDeadEntries = 0;
+}
+
+/*
+ * Free the table.
+ */
+void mzHashTableFree(HashTable* pHashTable)
+{
+    if (pHashTable == NULL)
+        return;
+    mzHashTableClear(pHashTable);
+    free(pHashTable->pEntries);
+    free(pHashTable);
+}
+
+#ifndef NDEBUG
+/*
+ * Count up the number of tombstone entries in the hash table.
+ */
+static int countTombStones(HashTable* pHashTable)
+{
+    int i, count;
+
+    for (count = i = 0; i < pHashTable->tableSize; i++) {
+        if (pHashTable->pEntries[i].data == HASH_TOMBSTONE)
+            count++;
+    }
+    return count;
+}
+#endif
+
+/*
+ * Resize a hash table.  We do this when adding an entry increased the
+ * size of the table beyond its comfy limit.
+ *
+ * This essentially requires re-inserting all elements into the new storage.
+ *
+ * If multiple threads can access the hash table, the table's lock should
+ * have been grabbed before issuing the "lookup+add" call that led to the
+ * resize, so we don't have a synchronization problem here.
+ */
+static bool resizeHash(HashTable* pHashTable, int newSize)
+{
+    HashEntry* pNewEntries;
+    int i;
+
+    assert(countTombStones(pHashTable) == pHashTable->numDeadEntries);
+
+    pNewEntries = (HashEntry*) calloc(newSize, sizeof(HashTable));
+    if (pNewEntries == NULL)
+        return false;
+
+    for (i = 0; i < pHashTable->tableSize; i++) {
+        void* data = pHashTable->pEntries[i].data;
+        if (data != NULL && data != HASH_TOMBSTONE) {
+            int hashValue = pHashTable->pEntries[i].hashValue;
+            int newIdx;
+
+            /* probe for new spot, wrapping around */
+            newIdx = hashValue & (newSize-1);
+            while (pNewEntries[newIdx].data != NULL)
+                newIdx = (newIdx + 1) & (newSize-1);
+
+            pNewEntries[newIdx].hashValue = hashValue;
+            pNewEntries[newIdx].data = data;
+        }
+    }
+
+    free(pHashTable->pEntries);
+    pHashTable->pEntries = pNewEntries;
+    pHashTable->tableSize = newSize;
+    pHashTable->numDeadEntries = 0;
+
+    assert(countTombStones(pHashTable) == 0);
+    return true;
+}
+
+/*
+ * Look up an entry.
+ *
+ * We probe on collisions, wrapping around the table.
+ */
+void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item,
+    HashCompareFunc cmpFunc, bool doAdd)
+{
+    HashEntry* pEntry;
+    HashEntry* pEnd;
+    void* result = NULL;
+
+    assert(pHashTable->tableSize > 0);
+    assert(item != HASH_TOMBSTONE);
+    assert(item != NULL);
+
+    /* jump to the first entry and probe for a match */
+    pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
+    pEnd = &pHashTable->pEntries[pHashTable->tableSize];
+    while (pEntry->data != NULL) {
+        if (pEntry->data != HASH_TOMBSTONE &&
+            pEntry->hashValue == itemHash &&
+            (*cmpFunc)(pEntry->data, item) == 0)
+        {
+            /* match */
+            break;
+        }
+
+        pEntry++;
+        if (pEntry == pEnd) {     /* wrap around to start */
+            if (pHashTable->tableSize == 1)
+                break;      /* edge case - single-entry table */
+            pEntry = pHashTable->pEntries;
+        }
+    }
+
+    if (pEntry->data == NULL) {
+        if (doAdd) {
+            pEntry->hashValue = itemHash;
+            pEntry->data = item;
+            pHashTable->numEntries++;
+
+            /*
+             * We've added an entry.  See if this brings us too close to full.
+             */
+            if ((pHashTable->numEntries+pHashTable->numDeadEntries) * LOAD_DENOM
+                > pHashTable->tableSize * LOAD_NUMER)
+            {
+                if (!resizeHash(pHashTable, pHashTable->tableSize * 2)) {
+                    /* don't really have a way to indicate failure */
+                    LOGE("Dalvik hash resize failure\n");
+                    abort();
+                }
+                /* note "pEntry" is now invalid */
+            }
+
+            /* full table is bad -- search for nonexistent never halts */
+            assert(pHashTable->numEntries < pHashTable->tableSize);
+            result = item;
+        } else {
+            assert(result == NULL);
+        }
+    } else {
+        result = pEntry->data;
+    }
+
+    return result;
+}
+
+/*
+ * Remove an entry from the table.
+ *
+ * Does NOT invoke the "free" function on the item.
+ */
+bool mzHashTableRemove(HashTable* pHashTable, unsigned int itemHash, void* item)
+{
+    HashEntry* pEntry;
+    HashEntry* pEnd;
+
+    assert(pHashTable->tableSize > 0);
+
+    /* jump to the first entry and probe for a match */
+    pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
+    pEnd = &pHashTable->pEntries[pHashTable->tableSize];
+    while (pEntry->data != NULL) {
+        if (pEntry->data == item) {
+            pEntry->data = HASH_TOMBSTONE;
+            pHashTable->numEntries--;
+            pHashTable->numDeadEntries++;
+            return true;
+        }
+
+        pEntry++;
+        if (pEntry == pEnd) {     /* wrap around to start */
+            if (pHashTable->tableSize == 1)
+                break;      /* edge case - single-entry table */
+            pEntry = pHashTable->pEntries;
+        }
+    }
+
+    return false;
+}
+
+/*
+ * Execute a function on every entry in the hash table.
+ *
+ * If "func" returns a nonzero value, terminate early and return the value.
+ */
+int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg)
+{
+    int i, val;
+
+    for (i = 0; i < pHashTable->tableSize; i++) {
+        HashEntry* pEnt = &pHashTable->pEntries[i];
+
+        if (pEnt->data != NULL && pEnt->data != HASH_TOMBSTONE) {
+            val = (*func)(pEnt->data, arg);
+            if (val != 0)
+                return val;
+        }
+    }
+
+    return 0;
+}
+
+
+/*
+ * Look up an entry, counting the number of times we have to probe.
+ *
+ * Returns -1 if the entry wasn't found.
+ */
+int countProbes(HashTable* pHashTable, unsigned int itemHash, const void* item,
+    HashCompareFunc cmpFunc)
+{
+    HashEntry* pEntry;
+    HashEntry* pEnd;
+    int count = 0;
+
+    assert(pHashTable->tableSize > 0);
+    assert(item != HASH_TOMBSTONE);
+    assert(item != NULL);
+
+    /* jump to the first entry and probe for a match */
+    pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
+    pEnd = &pHashTable->pEntries[pHashTable->tableSize];
+    while (pEntry->data != NULL) {
+        if (pEntry->data != HASH_TOMBSTONE &&
+            pEntry->hashValue == itemHash &&
+            (*cmpFunc)(pEntry->data, item) == 0)
+        {
+            /* match */
+            break;
+        }
+
+        pEntry++;
+        if (pEntry == pEnd) {     /* wrap around to start */
+            if (pHashTable->tableSize == 1)
+                break;      /* edge case - single-entry table */
+            pEntry = pHashTable->pEntries;
+        }
+
+        count++;
+    }
+    if (pEntry->data == NULL)
+        return -1;
+
+    return count;
+}
+
+/*
+ * Evaluate the amount of probing required for the specified hash table.
+ *
+ * We do this by running through all entries in the hash table, computing
+ * the hash value and then doing a lookup.
+ *
+ * The caller should lock the table before calling here.
+ */
+void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc,
+    HashCompareFunc cmpFunc)
+{
+    int numEntries, minProbe, maxProbe, totalProbe;
+    HashIter iter;
+
+    numEntries = maxProbe = totalProbe = 0;
+    minProbe = 65536*32767;
+
+    for (mzHashIterBegin(pHashTable, &iter); !mzHashIterDone(&iter);
+        mzHashIterNext(&iter))
+    {
+        const void* data = (const void*)mzHashIterData(&iter);
+        int count;
+
+        count = countProbes(pHashTable, (*calcFunc)(data), data, cmpFunc);
+
+        numEntries++;
+
+        if (count < minProbe)
+            minProbe = count;
+        if (count > maxProbe)
+            maxProbe = count;
+        totalProbe += count;
+    }
+
+    LOGV("Probe: min=%d max=%d, total=%d in %d (%d), avg=%.3f\n",
+        minProbe, maxProbe, totalProbe, numEntries, pHashTable->tableSize,
+        (float) totalProbe / (float) numEntries);
+}
diff --git a/minzip/Hash.h b/minzip/Hash.h
new file mode 100644
index 0000000..e83eac4
--- /dev/null
+++ b/minzip/Hash.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * General purpose hash table, used for finding classes, methods, etc.
+ *
+ * When the number of elements reaches 3/4 of the table's capacity, the
+ * table will be resized.
+ */
+#ifndef _MINZIP_HASH
+#define _MINZIP_HASH
+
+#include "inline_magic.h"
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* compute the hash of an item with a specific type */
+typedef unsigned int (*HashCompute)(const void* item);
+
+/*
+ * Compare a hash entry with a "loose" item after their hash values match.
+ * Returns { <0, 0, >0 } depending on ordering of items (same semantics
+ * as strcmp()).
+ */
+typedef int (*HashCompareFunc)(const void* tableItem, const void* looseItem);
+
+/*
+ * This function will be used to free entries in the table.  This can be
+ * NULL if no free is required, free(), or a custom function.
+ */
+typedef void (*HashFreeFunc)(void* ptr);
+
+/*
+ * Used by mzHashForeach().
+ */
+typedef int (*HashForeachFunc)(void* data, void* arg);
+
+/*
+ * One entry in the hash table.  "data" values are expected to be (or have
+ * the same characteristics as) valid pointers.  In particular, a NULL
+ * value for "data" indicates an empty slot, and HASH_TOMBSTONE indicates
+ * a no-longer-used slot that must be stepped over during probing.
+ *
+ * Attempting to add a NULL or tombstone value is an error.
+ *
+ * When an entry is released, we will call (HashFreeFunc)(entry->data).
+ */
+typedef struct HashEntry {
+    unsigned int hashValue;
+    void* data;
+} HashEntry;
+
+#define HASH_TOMBSTONE ((void*) 0xcbcacccd)     // invalid ptr value
+
+/*
+ * Expandable hash table.
+ *
+ * This structure should be considered opaque.
+ */
+typedef struct HashTable {
+    int         tableSize;          /* must be power of 2 */
+    int         numEntries;         /* current #of "live" entries */
+    int         numDeadEntries;     /* current #of tombstone entries */
+    HashEntry*  pEntries;           /* array on heap */
+    HashFreeFunc freeFunc;
+} HashTable;
+
+/*
+ * Create and initialize a HashTable structure, using "initialSize" as
+ * a basis for the initial capacity of the table.  (The actual initial
+ * table size may be adjusted upward.)  If you know exactly how many
+ * elements the table will hold, pass the result from mzHashSize() in.)
+ *
+ * Returns "false" if unable to allocate the table.
+ */
+HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc);
+
+/*
+ * Compute the capacity needed for a table to hold "size" elements.  Use
+ * this when you know ahead of time how many elements the table will hold.
+ * Pass this value into mzHashTableCreate() to ensure that you can add
+ * all elements without needing to reallocate the table.
+ */
+size_t mzHashSize(size_t size);
+
+/*
+ * Clear out a hash table, freeing the contents of any used entries.
+ */
+void mzHashTableClear(HashTable* pHashTable);
+
+/*
+ * Free a hash table.
+ */
+void mzHashTableFree(HashTable* pHashTable);
+
+/*
+ * Get #of entries in hash table.
+ */
+INLINE int mzHashTableNumEntries(HashTable* pHashTable) {
+    return pHashTable->numEntries;
+}
+
+/*
+ * Get total size of hash table (for memory usage calculations).
+ */
+INLINE int mzHashTableMemUsage(HashTable* pHashTable) {
+    return sizeof(HashTable) + pHashTable->tableSize * sizeof(HashEntry);
+}
+
+/*
+ * Look up an entry in the table, possibly adding it if it's not there.
+ *
+ * If "item" is not found, and "doAdd" is false, NULL is returned.
+ * Otherwise, a pointer to the found or added item is returned.  (You can
+ * tell the difference by seeing if return value == item.)
+ *
+ * An "add" operation may cause the entire table to be reallocated.
+ */
+void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item,
+    HashCompareFunc cmpFunc, bool doAdd);
+
+/*
+ * Remove an item from the hash table, given its "data" pointer.  Does not
+ * invoke the "free" function; just detaches it from the table.
+ */
+bool mzHashTableRemove(HashTable* pHashTable, unsigned int hash, void* item);
+
+/*
+ * Execute "func" on every entry in the hash table.
+ *
+ * If "func" returns a nonzero value, terminate early and return the value.
+ */
+int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg);
+
+/*
+ * An alternative to mzHashForeach(), using an iterator.
+ *
+ * Use like this:
+ *   HashIter iter;
+ *   for (mzHashIterBegin(hashTable, &iter); !mzHashIterDone(&iter);
+ *       mzHashIterNext(&iter))
+ *   {
+ *       MyData* data = (MyData*)mzHashIterData(&iter);
+ *   }
+ */
+typedef struct HashIter {
+    void*       data;
+    HashTable*  pHashTable;
+    int         idx;
+} HashIter;
+INLINE void mzHashIterNext(HashIter* pIter) {
+    int i = pIter->idx +1;
+    int lim = pIter->pHashTable->tableSize;
+    for ( ; i < lim; i++) {
+        void* data = pIter->pHashTable->pEntries[i].data;
+        if (data != NULL && data != HASH_TOMBSTONE)
+            break;
+    }
+    pIter->idx = i;
+}
+INLINE void mzHashIterBegin(HashTable* pHashTable, HashIter* pIter) {
+    pIter->pHashTable = pHashTable;
+    pIter->idx = -1;
+    mzHashIterNext(pIter);
+}
+INLINE bool mzHashIterDone(HashIter* pIter) {
+    return (pIter->idx >= pIter->pHashTable->tableSize);
+}
+INLINE void* mzHashIterData(HashIter* pIter) {
+    assert(pIter->idx >= 0 && pIter->idx < pIter->pHashTable->tableSize);
+    return pIter->pHashTable->pEntries[pIter->idx].data;
+}
+
+
+/*
+ * Evaluate hash table performance by examining the number of times we
+ * have to probe for an entry.
+ *
+ * The caller should lock the table beforehand.
+ */
+typedef unsigned int (*HashCalcFunc)(const void* item);
+void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc,
+    HashCompareFunc cmpFunc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*_MINZIP_HASH*/
diff --git a/minzip/Inlines.c b/minzip/Inlines.c
new file mode 100644
index 0000000..91f8775
--- /dev/null
+++ b/minzip/Inlines.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Make sure that non-inlined versions of INLINED-marked functions
+ * exist so that debug builds (which don't generally do inlining)
+ * don't break.
+ */
+#define MINZIP_GENERATE_INLINES 1
+#include "Bits.h"
+#include "Hash.h"
+#include "SysUtil.h"
+#include "Zip.h"
diff --git a/minzip/Log.h b/minzip/Log.h
new file mode 100644
index 0000000..36e62f5
--- /dev/null
+++ b/minzip/Log.h
@@ -0,0 +1,207 @@
+//
+// Copyright 2005 The Android Open Source Project
+//
+// C/C++ logging functions.  See the logging documentation for API details.
+//
+// We'd like these to be available from C code (in case we import some from
+// somewhere), so this has a C interface.
+//
+// The output will be correct when the log file is shared between multiple
+// threads and/or multiple processes so long as the operating system
+// supports O_APPEND.  These calls have mutex-protected data structures
+// and so are NOT reentrant.  Do not use LOG in a signal handler.
+//
+#ifndef _MINZIP_LOG_H
+#define _MINZIP_LOG_H
+
+#include <stdio.h>
+
+// ---------------------------------------------------------------------
+
+/*
+ * Normally we strip LOGV (VERBOSE messages) from release builds.
+ * You can modify this (for example with "#define LOG_NDEBUG 0"
+ * at the top of your source file) to change that behavior.
+ */
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+/*
+ * This is the local tag used for the following simplified
+ * logging macros.  You can change this preprocessor definition
+ * before using the other macros to change the tag.
+ */
+#ifndef LOG_TAG
+#define LOG_TAG NULL
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose log message using the current LOG_TAG.
+ */
+#ifndef LOGV
+#if LOG_NDEBUG
+#define LOGV(...)   ((void)0)
+#else
+#define LOGV(...) ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond)     (__builtin_expect((cond)!=0, 0))
+
+#ifndef LOGV_IF
+#if LOG_NDEBUG
+#define LOGV_IF(cond, ...)   ((void)0)
+#else
+#define LOGV_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+#endif
+
+#define LOGVV LOGV
+#define LOGVV_IF LOGV_IF
+
+/*
+ * Simplified macro to send a debug log message using the current LOG_TAG.
+ */
+#ifndef LOGD
+#define LOGD(...) ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGD_IF
+#define LOGD_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info log message using the current LOG_TAG.
+ */
+#ifndef LOGI
+#define LOGI(...) ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGI_IF
+#define LOGI_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning log message using the current LOG_TAG.
+ */
+#ifndef LOGW
+#define LOGW(...) ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGW_IF
+#define LOGW_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error log message using the current LOG_TAG.
+ */
+#ifndef LOGE
+#define LOGE(...) ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGE_IF
+#define LOGE_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * verbose priority.
+ */
+#ifndef IF_LOGV
+#if LOG_NDEBUG
+#define IF_LOGV() if (false)
+#else
+#define IF_LOGV() IF_LOG(LOG_VERBOSE, LOG_TAG)
+#endif
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * debug priority.
+ */
+#ifndef IF_LOGD
+#define IF_LOGD() IF_LOG(LOG_DEBUG, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * info priority.
+ */
+#ifndef IF_LOGI
+#define IF_LOGI() IF_LOG(LOG_INFO, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * warn priority.
+ */
+#ifndef IF_LOGW
+#define IF_LOGW() IF_LOG(LOG_WARN, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * error priority.
+ */
+#ifndef IF_LOGE
+#define IF_LOGE() IF_LOG(LOG_ERROR, LOG_TAG)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Basic log message macro.
+ *
+ * Example:
+ *  LOG(LOG_WARN, NULL, "Failed with error %d", errno);
+ *
+ * The second argument may be NULL or "" to indicate the "global" tag.
+ *
+ * Non-gcc probably won't have __FUNCTION__.  It's not vital.  gcc also
+ * offers __PRETTY_FUNCTION__, which is rather more than we need.
+ */
+#ifndef LOG
+#define LOG(priority, tag, ...) \
+    LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to specify a number for the priority.
+ */
+#ifndef LOG_PRI
+#define LOG_PRI(priority, tag, ...) \
+    printf(tag ": " __VA_ARGS__)
+#endif
+
+/*
+ * Conditional given a desired logging priority and tag.
+ */
+#ifndef IF_LOG
+#define IF_LOG(priority, tag) \
+    if (1)
+#endif
+
+#endif // _MINZIP_LOG_H
diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c
new file mode 100644
index 0000000..9c3575b
--- /dev/null
+++ b/minzip/SysUtil.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * System utilities.
+ */
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define LOG_TAG "sysutil"
+#include "Log.h"
+#include "SysUtil.h"
+
+static int getFileStartAndLength(int fd, loff_t *start_, size_t *length_)
+{
+    loff_t start, end;
+    size_t length;
+
+    assert(start_ != NULL);
+    assert(length_ != NULL);
+
+    // TODO: isn't start always 0 for the single call site? just use fstat instead?
+
+    start = TEMP_FAILURE_RETRY(lseek64(fd, 0L, SEEK_CUR));
+    end = TEMP_FAILURE_RETRY(lseek64(fd, 0L, SEEK_END));
+
+    if (TEMP_FAILURE_RETRY(lseek64(fd, start, SEEK_SET)) == -1 ||
+                start == (loff_t) -1 || end == (loff_t) -1) {
+        LOGE("could not determine length of file\n");
+        return -1;
+    }
+
+    length = end - start;
+    if (length == 0) {
+        LOGE("file is empty\n");
+        return -1;
+    }
+
+    *start_ = start;
+    *length_ = length;
+
+    return 0;
+}
+
+/*
+ * Map a file (from fd's current offset) into a private, read-only memory
+ * segment.  The file offset must be a multiple of the page size.
+ *
+ * On success, returns 0 and fills out "pMap".  On failure, returns a nonzero
+ * value and does not disturb "pMap".
+ */
+static bool sysMapFD(int fd, MemMapping* pMap) {
+    loff_t start;
+    size_t length;
+    void* memPtr;
+
+    assert(pMap != NULL);
+
+    if (getFileStartAndLength(fd, &start, &length) < 0)
+        return -1;
+
+#if (PLATFORM_SDK_VERSION >= 21)
+    memPtr = mmap64(NULL, length, PROT_READ, MAP_PRIVATE, fd, start);
+#else
+    // Older versions of Android do not have mmap64 so we will just use mmap instead
+    memPtr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, start);
+#endif
+    if (memPtr == MAP_FAILED) {
+        LOGE("mmap(%d, R, PRIVATE, %d, 0) failed: %s\n", (int) length, fd, strerror(errno));
+        return false;
+    }
+
+    pMap->addr = memPtr;
+    pMap->length = length;
+    pMap->range_count = 1;
+    pMap->ranges = malloc(sizeof(MappedRange));
+    if (pMap->ranges == NULL) {
+        LOGE("malloc failed: %s\n", strerror(errno));
+        munmap(memPtr, length);
+        return false;
+    }
+    pMap->ranges[0].addr = memPtr;
+    pMap->ranges[0].length = length;
+
+    return true;
+}
+
+static int sysMapBlockFile(FILE* mapf, MemMapping* pMap)
+{
+    char block_dev[PATH_MAX+1];
+    size_t size;
+    unsigned int blksize;
+    size_t blocks;
+    unsigned int range_count;
+    unsigned int i;
+
+    if (fgets(block_dev, sizeof(block_dev), mapf) == NULL) {
+        LOGE("failed to read block device from header\n");
+        return -1;
+    }
+    for (i = 0; i < sizeof(block_dev); ++i) {
+        if (block_dev[i] == '\n') {
+            block_dev[i] = 0;
+            break;
+        }
+    }
+
+    if (fscanf(mapf, "%zu %u\n%u\n", &size, &blksize, &range_count) != 3) {
+        LOGE("failed to parse block map header\n");
+        return -1;
+    }
+    if (blksize != 0) {
+        blocks = ((size-1) / blksize) + 1;
+    }
+    if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0) {
+        LOGE("invalid data in block map file: size %zu, blksize %u, range_count %u\n",
+             size, blksize, range_count);
+        return -1;
+    }
+
+    pMap->range_count = range_count;
+    pMap->ranges = calloc(range_count, sizeof(MappedRange));
+    if (pMap->ranges == NULL) {
+        LOGE("calloc(%u, %zu) failed: %s\n", range_count, sizeof(MappedRange), strerror(errno));
+        return -1;
+    }
+
+    // Reserve enough contiguous address space for the whole file.
+    unsigned char* reserve;
+#if (PLATFORM_SDK_VERSION >= 21)
+    reserve = mmap64(NULL, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
+#else
+    // Older versions of Android do not have mmap64 so we will just use mmap instead
+    reserve = mmap(NULL, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
+#endif
+    if (reserve == MAP_FAILED) {
+        LOGE("failed to reserve address space: %s\n", strerror(errno));
+        free(pMap->ranges);
+        return -1;
+    }
+
+    int fd = open(block_dev, O_RDONLY);
+    if (fd < 0) {
+        LOGE("failed to open block device %s: %s\n", block_dev, strerror(errno));
+        munmap(reserve, blocks * blksize);
+        free(pMap->ranges);
+        return -1;
+    }
+
+    unsigned char* next = reserve;
+    size_t remaining_size = blocks * blksize;
+    bool success = true;
+    for (i = 0; i < range_count; ++i) {
+        size_t start, end;
+        if (fscanf(mapf, "%zu %zu\n", &start, &end) != 2) {
+            LOGE("failed to parse range %d in block map\n", i);
+            success = false;
+            break;
+        }
+        size_t length = (end - start) * blksize;
+        if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) {
+          LOGE("unexpected range in block map: %zu %zu\n", start, end);
+          success = false;
+          break;
+        }
+#if (PLATFORM_SDK_VERSION >= 21)
+        void* addr = mmap64(next, (end-start)*blksize, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize);
+#else
+        // Older versions of Android do not have mmap64 so we will just use mmap instead
+        void* addr = mmap(next, (end-start)*blksize, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize);
+#endif
+        if (addr == MAP_FAILED) {
+            LOGE("failed to map block %d: %s\n", i, strerror(errno));
+            success = false;
+            break;
+        }
+        pMap->ranges[i].addr = addr;
+        pMap->ranges[i].length = length;
+
+        next += length;
+        remaining_size -= length;
+    }
+    if (success && remaining_size != 0) {
+      LOGE("ranges in block map are invalid: remaining_size = %zu\n", remaining_size);
+      success = false;
+    }
+    if (!success) {
+      close(fd);
+      munmap(reserve, blocks * blksize);
+      free(pMap->ranges);
+      return -1;
+    }
+
+    close(fd);
+    pMap->addr = reserve;
+    pMap->length = size;
+
+    LOGI("mmapped %d ranges\n", range_count);
+
+    return 0;
+}
+
+int sysMapFile(const char* fn, MemMapping* pMap)
+{
+    memset(pMap, 0, sizeof(*pMap));
+
+    if (fn && fn[0] == '@') {
+        // A map of blocks
+        FILE* mapf = fopen(fn+1, "r");
+        if (mapf == NULL) {
+            LOGE("Unable to open '%s': %s\n", fn+1, strerror(errno));
+            return -1;
+        }
+
+        if (sysMapBlockFile(mapf, pMap) != 0) {
+            LOGE("Map of '%s' failed\n", fn);
+            fclose(mapf);
+            return -1;
+        }
+
+        fclose(mapf);
+    } else {
+        // This is a regular file.
+        int fd = open(fn, O_RDONLY);
+        if (fd == -1) {
+            LOGE("Unable to open '%s': %s\n", fn, strerror(errno));
+            return -1;
+        }
+
+        if (!sysMapFD(fd, pMap)) {
+            LOGE("Map of '%s' failed\n", fn);
+            close(fd);
+            return -1;
+        }
+
+        close(fd);
+    }
+    return 0;
+}
+
+/*
+ * Release a memory mapping.
+ */
+void sysReleaseMap(MemMapping* pMap)
+{
+    int i;
+    for (i = 0; i < pMap->range_count; ++i) {
+        if (munmap(pMap->ranges[i].addr, pMap->ranges[i].length) < 0) {
+            LOGE("munmap(%p, %d) failed: %s\n",
+                 pMap->ranges[i].addr, (int)pMap->ranges[i].length, strerror(errno));
+        }
+    }
+    free(pMap->ranges);
+    pMap->ranges = NULL;
+    pMap->range_count = 0;
+}
diff --git a/minzip/SysUtil.h b/minzip/SysUtil.h
new file mode 100644
index 0000000..7adff1e
--- /dev/null
+++ b/minzip/SysUtil.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * System utilities.
+ */
+#ifndef _MINZIP_SYSUTIL
+#define _MINZIP_SYSUTIL
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct MappedRange {
+    void* addr;
+    size_t length;
+} MappedRange;
+
+/*
+ * Use this to keep track of mapped segments.
+ */
+typedef struct MemMapping {
+    unsigned char* addr;           /* start of data */
+    size_t         length;         /* length of data */
+
+    int            range_count;
+    MappedRange*   ranges;
+} MemMapping;
+
+/*
+ * Map a file into a private, read-only memory segment.  If 'fn'
+ * begins with an '@' character, it is a map of blocks to be mapped,
+ * otherwise it is treated as an ordinary file.
+ *
+ * On success, "pMap" is filled in, and zero is returned.
+ */
+int sysMapFile(const char* fn, MemMapping* pMap);
+
+/*
+ * Release the pages associated with a shared memory segment.
+ *
+ * This does not free "pMap"; it just releases the memory.
+ */
+void sysReleaseMap(MemMapping* pMap);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*_MINZIP_SYSUTIL*/
diff --git a/minzip/Zip.c b/minzip/Zip.c
new file mode 100755
index 0000000..1c3239d
--- /dev/null
+++ b/minzip/Zip.c
@@ -0,0 +1,1012 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Simple Zip file support.
+ */
+#include "safe_iop.h"
+#include "zlib.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>     // for uintptr_t
+#include <stdlib.h>
+#include <sys/stat.h>   // for S_ISLNK()
+#include <unistd.h>
+
+#define LOG_TAG "minzip"
+#include "Zip.h"
+#include "Bits.h"
+#include "Log.h"
+#include "DirUtil.h"
+
+#undef NDEBUG   // do this after including Log.h
+#include <assert.h>
+
+#define SORT_ENTRIES 1
+
+/*
+ * Offset and length constants (java.util.zip naming convention).
+ */
+enum {
+    CENSIG = 0x02014b50,      // PK12
+    CENHDR = 46,
+
+    CENVEM =  4,
+    CENVER =  6,
+    CENFLG =  8,
+    CENHOW = 10,
+    CENTIM = 12,
+    CENCRC = 16,
+    CENSIZ = 20,
+    CENLEN = 24,
+    CENNAM = 28,
+    CENEXT = 30,
+    CENCOM = 32,
+    CENDSK = 34,
+    CENATT = 36,
+    CENATX = 38,
+    CENOFF = 42,
+
+    ENDSIG = 0x06054b50,     // PK56
+    ENDHDR = 22,
+
+    ENDSUB =  8,
+    ENDTOT = 10,
+    ENDSIZ = 12,
+    ENDOFF = 16,
+    ENDCOM = 20,
+
+    EXTSIG = 0x08074b50,     // PK78
+    EXTHDR = 16,
+
+    EXTCRC =  4,
+    EXTSIZ =  8,
+    EXTLEN = 12,
+
+    LOCSIG = 0x04034b50,      // PK34
+    LOCHDR = 30,
+
+    LOCVER =  4,
+    LOCFLG =  6,
+    LOCHOW =  8,
+    LOCTIM = 10,
+    LOCCRC = 14,
+    LOCSIZ = 18,
+    LOCLEN = 22,
+    LOCNAM = 26,
+    LOCEXT = 28,
+
+    STORED = 0,
+    DEFLATED = 8,
+
+    CENVEM_UNIX = 3 << 8,   // the high byte of CENVEM
+};
+
+
+/*
+ * For debugging, dump the contents of a ZipEntry.
+ */
+#if 0
+static void dumpEntry(const ZipEntry* pEntry)
+{
+    LOGI(" %p '%.*s'\n", pEntry->fileName,pEntry->fileNameLen,pEntry->fileName);
+    LOGI("   off=%ld comp=%ld uncomp=%ld how=%d\n", pEntry->offset,
+        pEntry->compLen, pEntry->uncompLen, pEntry->compression);
+}
+#endif
+
+/*
+ * (This is a mzHashTableLookup callback.)
+ *
+ * Compare two ZipEntry structs, by name.
+ */
+static int hashcmpZipEntry(const void* ventry1, const void* ventry2)
+{
+    const ZipEntry* entry1 = (const ZipEntry*) ventry1;
+    const ZipEntry* entry2 = (const ZipEntry*) ventry2;
+
+    if (entry1->fileNameLen != entry2->fileNameLen)
+        return entry1->fileNameLen - entry2->fileNameLen;
+    return memcmp(entry1->fileName, entry2->fileName, entry1->fileNameLen);
+}
+
+/*
+ * (This is a mzHashTableLookup callback.)
+ *
+ * find a ZipEntry struct by name.
+ */
+static int hashcmpZipName(const void* ventry, const void* vname)
+{
+    const ZipEntry* entry = (const ZipEntry*) ventry;
+    const char* name = (const char*) vname;
+    unsigned int nameLen = strlen(name);
+
+    if (entry->fileNameLen != nameLen)
+        return entry->fileNameLen - nameLen;
+    return memcmp(entry->fileName, name, nameLen);
+}
+
+/*
+ * Compute the hash code for a ZipEntry filename.
+ *
+ * Not expected to be compatible with any other hash function, so we init
+ * to 2 to ensure it doesn't happen to match.
+ */
+static unsigned int computeHash(const char* name, int nameLen)
+{
+    unsigned int hash = 2;
+
+    while (nameLen--)
+        hash = hash * 31 + *name++;
+
+    return hash;
+}
+
+static void addEntryToHashTable(HashTable* pHash, ZipEntry* pEntry)
+{
+    unsigned int itemHash = computeHash(pEntry->fileName, pEntry->fileNameLen);
+    const ZipEntry* found;
+
+    found = (const ZipEntry*)mzHashTableLookup(pHash,
+                itemHash, pEntry, hashcmpZipEntry, true);
+    if (found != pEntry) {
+        LOGW("WARNING: duplicate entry '%.*s' in Zip\n",
+            found->fileNameLen, found->fileName);
+        /* keep going */
+    }
+}
+
+static int validFilename(const char *fileName, unsigned int fileNameLen)
+{
+    // Forbid super long filenames.
+    if (fileNameLen >= PATH_MAX) {
+        LOGW("Filename too long (%d chatacters)\n", fileNameLen);
+        return 0;
+    }
+
+    // Require all characters to be printable ASCII (no NUL, no UTF-8, etc).
+    unsigned int i;
+    for (i = 0; i < fileNameLen; ++i) {
+        if (fileName[i] < 32 || fileName[i] >= 127) {
+            LOGW("Filename contains invalid character '\%03o'\n", fileName[i]);
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+/*
+ * Parse the contents of a Zip archive.  After confirming that the file
+ * is in fact a Zip, we scan out the contents of the central directory and
+ * store it in a hash table.
+ *
+ * Returns "true" on success.
+ */
+static bool parseZipArchive(ZipArchive* pArchive)
+{
+    bool result = false;
+    const unsigned char* ptr;
+    unsigned int i, numEntries, cdOffset;
+    unsigned int val;
+
+    /*
+     * The first 4 bytes of the file will either be the local header
+     * signature for the first file (LOCSIG) or, if the archive doesn't
+     * have any files in it, the end-of-central-directory signature (ENDSIG).
+     */
+    val = get4LE(pArchive->addr);
+    if (val == ENDSIG) {
+        LOGW("Found Zip archive, but it looks empty\n");
+        goto bail;
+    } else if (val != LOCSIG) {
+        LOGW("Not a Zip archive (found 0x%08x)\n", val);
+        goto bail;
+    }
+
+    /*
+     * Find the EOCD.  We'll find it immediately unless they have a file
+     * comment.
+     */
+    ptr = pArchive->addr + pArchive->length - ENDHDR;
+
+    while (ptr >= (const unsigned char*) pArchive->addr) {
+        if (*ptr == (ENDSIG & 0xff) && get4LE(ptr) == ENDSIG)
+            break;
+        ptr--;
+    }
+    if (ptr < (const unsigned char*) pArchive->addr) {
+        LOGW("Could not find end-of-central-directory in Zip\n");
+        goto bail;
+    }
+
+    /*
+     * There are two interesting items in the EOCD block: the number of
+     * entries in the file, and the file offset of the start of the
+     * central directory.
+     */
+    numEntries = get2LE(ptr + ENDSUB);
+    cdOffset = get4LE(ptr + ENDOFF);
+
+    LOGVV("numEntries=%d cdOffset=%d\n", numEntries, cdOffset);
+    if (numEntries == 0 || cdOffset >= pArchive->length) {
+        LOGW("Invalid entries=%d offset=%d (len=%zd)\n",
+            numEntries, cdOffset, pArchive->length);
+        goto bail;
+    }
+
+    /*
+     * Create data structures to hold entries.
+     */
+    pArchive->numEntries = numEntries;
+    pArchive->pEntries = (ZipEntry*) calloc(numEntries, sizeof(ZipEntry));
+    pArchive->pHash = mzHashTableCreate(mzHashSize(numEntries), NULL);
+    if (pArchive->pEntries == NULL || pArchive->pHash == NULL)
+        goto bail;
+
+    ptr = pArchive->addr + cdOffset;
+    for (i = 0; i < numEntries; i++) {
+        ZipEntry* pEntry;
+        unsigned int fileNameLen, extraLen, commentLen, localHdrOffset;
+        const unsigned char* localHdr;
+        const char *fileName;
+
+        if (ptr + CENHDR > (const unsigned char*)pArchive->addr + pArchive->length) {
+            LOGW("Ran off the end (at %d)\n", i);
+            goto bail;
+        }
+        if (get4LE(ptr) != CENSIG) {
+            LOGW("Missed a central dir sig (at %d)\n", i);
+            goto bail;
+        }
+
+        localHdrOffset = get4LE(ptr + CENOFF);
+        fileNameLen = get2LE(ptr + CENNAM);
+        extraLen = get2LE(ptr + CENEXT);
+        commentLen = get2LE(ptr + CENCOM);
+        fileName = (const char*)ptr + CENHDR;
+        if (fileName + fileNameLen > (const char*)pArchive->addr + pArchive->length) {
+            LOGW("Filename ran off the end (at %d)\n", i);
+            goto bail;
+        }
+        if (!validFilename(fileName, fileNameLen)) {
+            LOGW("Invalid filename (at %d)\n", i);
+            goto bail;
+        }
+
+#if SORT_ENTRIES
+        /* Figure out where this entry should go (binary search).
+         */
+        if (i > 0) {
+            int low, high;
+
+            low = 0;
+            high = i - 1;
+            while (low <= high) {
+                int mid;
+                int diff;
+                int diffLen;
+
+                mid = low + ((high - low) / 2); // avoid overflow
+
+                if (pArchive->pEntries[mid].fileNameLen < fileNameLen) {
+                    diffLen = pArchive->pEntries[mid].fileNameLen;
+                } else {
+                    diffLen = fileNameLen;
+                }
+                diff = strncmp(pArchive->pEntries[mid].fileName, fileName,
+                        diffLen);
+                if (diff == 0) {
+                    diff = pArchive->pEntries[mid].fileNameLen - fileNameLen;
+                }
+                if (diff < 0) {
+                    low = mid + 1;
+                } else if (diff > 0) {
+                    high = mid - 1;
+                } else {
+                    high = mid;
+                    break;
+                }
+            }
+
+            unsigned int target = high + 1;
+            assert(target <= i);
+            if (target != i) {
+                /* It belongs somewhere other than at the end of
+                 * the list.  Make some room at [target].
+                 */
+                memmove(pArchive->pEntries + target + 1,
+                        pArchive->pEntries + target,
+                        (i - target) * sizeof(ZipEntry));
+            }
+            pEntry = &pArchive->pEntries[target];
+        } else {
+            pEntry = &pArchive->pEntries[0];
+        }
+#else
+        pEntry = &pArchive->pEntries[i];
+#endif
+        pEntry->fileNameLen = fileNameLen;
+        pEntry->fileName = fileName;
+
+        pEntry->compLen = get4LE(ptr + CENSIZ);
+        pEntry->uncompLen = get4LE(ptr + CENLEN);
+        pEntry->compression = get2LE(ptr + CENHOW);
+        pEntry->modTime = get4LE(ptr + CENTIM);
+        pEntry->crc32 = get4LE(ptr + CENCRC);
+
+        /* These two are necessary for finding the mode of the file.
+         */
+        pEntry->versionMadeBy = get2LE(ptr + CENVEM);
+        if ((pEntry->versionMadeBy & 0xff00) != 0 &&
+                (pEntry->versionMadeBy & 0xff00) != CENVEM_UNIX)
+        {
+            LOGW("Incompatible \"version made by\": 0x%02x (at %d)\n",
+                    pEntry->versionMadeBy >> 8, i);
+            goto bail;
+        }
+        pEntry->externalFileAttributes = get4LE(ptr + CENATX);
+
+        // Perform pArchive->addr + localHdrOffset, ensuring that it won't
+        // overflow. This is needed because localHdrOffset is untrusted.
+        if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pArchive->addr,
+            (uintptr_t)localHdrOffset)) {
+            LOGW("Integer overflow adding in parseZipArchive\n");
+            goto bail;
+        }
+        if ((uintptr_t)localHdr + LOCHDR >
+            (uintptr_t)pArchive->addr + pArchive->length) {
+            LOGW("Bad offset to local header: %d (at %d)\n", localHdrOffset, i);
+            goto bail;
+        }
+        if (get4LE(localHdr) != LOCSIG) {
+            LOGW("Missed a local header sig (at %d)\n", i);
+            goto bail;
+        }
+        pEntry->offset = localHdrOffset + LOCHDR
+            + get2LE(localHdr + LOCNAM) + get2LE(localHdr + LOCEXT);
+        if (!safe_add(NULL, pEntry->offset, (typeof(pEntry->offset))pEntry->compLen)) {
+            LOGW("Integer overflow adding in parseZipArchive\n");
+            goto bail;
+        }
+        if ((size_t)pEntry->offset + pEntry->compLen > pArchive->length) {
+            LOGW("Data ran off the end (at %d)\n", i);
+            goto bail;
+        }
+
+#if !SORT_ENTRIES
+        /* Add to hash table; no need to lock here.
+         * Can't do this now if we're sorting, because entries
+         * will move around.
+         */
+        addEntryToHashTable(pArchive->pHash, pEntry);
+#endif
+
+        //dumpEntry(pEntry);
+        ptr += CENHDR + fileNameLen + extraLen + commentLen;
+    }
+
+#if SORT_ENTRIES
+    /* If we're sorting, we have to wait until all entries
+     * are in their final places, otherwise the pointers will
+     * probably point to the wrong things.
+     */
+    for (i = 0; i < numEntries; i++) {
+        /* Add to hash table; no need to lock here.
+         */
+        addEntryToHashTable(pArchive->pHash, &pArchive->pEntries[i]);
+    }
+#endif
+
+    result = true;
+
+bail:
+    if (!result) {
+        mzHashTableFree(pArchive->pHash);
+        pArchive->pHash = NULL;
+    }
+    return result;
+}
+
+/*
+ * Open a Zip archive and scan out the contents.
+ *
+ * The easiest way to do this is to mmap() the whole thing and do the
+ * traditional backward scan for central directory.  Since the EOCD is
+ * a relatively small bit at the end, we should end up only touching a
+ * small set of pages.
+ *
+ * This will be called on non-Zip files, especially during startup, so
+ * we don't want to be too noisy about failures.  (Do we want a "quiet"
+ * flag?)
+ *
+ * On success, we fill out the contents of "pArchive".
+ */
+int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive)
+{
+    int err;
+
+    memset(pArchive, 0, sizeof(ZipArchive));
+
+    if (length < ENDHDR) {
+        err = -1;
+        LOGW("Archive %p is too small to be zip (%zd)\n", pArchive, length);
+        goto bail;
+    }
+
+    pArchive->addr = addr;
+    pArchive->length = length;
+
+    if (!parseZipArchive(pArchive)) {
+        err = -1;
+        LOGW("Parsing archive %p failed\n", pArchive);
+        goto bail;
+    }
+
+    err = 0;
+
+bail:
+    if (err != 0)
+        mzCloseZipArchive(pArchive);
+    return err;
+}
+
+/*
+ * Close a ZipArchive, closing the file and freeing the contents.
+ *
+ * NOTE: the ZipArchive may not have been fully created.
+ */
+void mzCloseZipArchive(ZipArchive* pArchive)
+{
+    LOGV("Closing archive %p\n", pArchive);
+
+    free(pArchive->pEntries);
+
+    mzHashTableFree(pArchive->pHash);
+
+    pArchive->pHash = NULL;
+    pArchive->pEntries = NULL;
+}
+
+/*
+ * Find a matching entry.
+ *
+ * Returns NULL if no matching entry found.
+ */
+const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
+        const char* entryName)
+{
+    unsigned int itemHash = computeHash(entryName, strlen(entryName));
+
+    return (const ZipEntry*)mzHashTableLookup(pArchive->pHash,
+                itemHash, (char*) entryName, hashcmpZipName, false);
+}
+
+/*
+ * Return true if the entry is a symbolic link.
+ */
+static bool mzIsZipEntrySymlink(const ZipEntry* pEntry)
+{
+    if ((pEntry->versionMadeBy & 0xff00) == CENVEM_UNIX) {
+        return S_ISLNK(pEntry->externalFileAttributes >> 16);
+    }
+    return false;
+}
+
+/* Call processFunction on the uncompressed data of a STORED entry.
+ */
+static bool processStoredEntry(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+    void *cookie)
+{
+    return processFunction(pArchive->addr + pEntry->offset, pEntry->uncompLen, cookie);
+}
+
+static bool processDeflatedEntry(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+    void *cookie)
+{
+    long result = -1;
+    unsigned char procBuf[32 * 1024];
+    z_stream zstream;
+    int zerr;
+    long compRemaining;
+
+    compRemaining = pEntry->compLen;
+
+    /*
+     * Initialize the zlib stream.
+     */
+    memset(&zstream, 0, sizeof(zstream));
+    zstream.zalloc = Z_NULL;
+    zstream.zfree = Z_NULL;
+    zstream.opaque = Z_NULL;
+    zstream.next_in = pArchive->addr + pEntry->offset;
+    zstream.avail_in = pEntry->compLen;
+    zstream.next_out = (Bytef*) procBuf;
+    zstream.avail_out = sizeof(procBuf);
+    zstream.data_type = Z_UNKNOWN;
+
+    /*
+     * Use the undocumented "negative window bits" feature to tell zlib
+     * that there's no zlib header waiting for it.
+     */
+    zerr = inflateInit2(&zstream, -MAX_WBITS);
+    if (zerr != Z_OK) {
+        if (zerr == Z_VERSION_ERROR) {
+            LOGE("Installed zlib is not compatible with linked version (%s)\n",
+                ZLIB_VERSION);
+        } else {
+            LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
+        }
+        goto bail;
+    }
+
+    /*
+     * Loop while we have data.
+     */
+    do {
+        /* uncompress the data */
+        zerr = inflate(&zstream, Z_NO_FLUSH);
+        if (zerr != Z_OK && zerr != Z_STREAM_END) {
+            LOGW("zlib inflate call failed (zerr=%d)\n", zerr);
+            goto z_bail;
+        }
+
+        /* write when we're full or when we're done */
+        if (zstream.avail_out == 0 ||
+            (zerr == Z_STREAM_END && zstream.avail_out != sizeof(procBuf)))
+        {
+            long procSize = zstream.next_out - procBuf;
+            LOGVV("+++ processing %d bytes\n", (int) procSize);
+            bool ret = processFunction(procBuf, procSize, cookie);
+            if (!ret) {
+                LOGW("Process function elected to fail (in inflate)\n");
+                goto z_bail;
+            }
+
+            zstream.next_out = procBuf;
+            zstream.avail_out = sizeof(procBuf);
+        }
+    } while (zerr == Z_OK);
+
+    assert(zerr == Z_STREAM_END);       /* other errors should've been caught */
+
+    // success!
+    result = zstream.total_out;
+
+z_bail:
+    inflateEnd(&zstream);        /* free up any allocated structures */
+
+bail:
+    if (result != pEntry->uncompLen) {
+        if (result != -1)        // error already shown?
+            LOGW("Size mismatch on inflated file (%ld vs %ld)\n",
+                result, pEntry->uncompLen);
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Stream the uncompressed data through the supplied function,
+ * passing cookie to it each time it gets called.  processFunction
+ * may be called more than once.
+ *
+ * If processFunction returns false, the operation is abandoned and
+ * mzProcessZipEntryContents() immediately returns false.
+ *
+ * This is useful for calculating the hash of an entry's uncompressed contents.
+ */
+bool mzProcessZipEntryContents(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+    void *cookie)
+{
+    bool ret = false;
+
+    switch (pEntry->compression) {
+    case STORED:
+        ret = processStoredEntry(pArchive, pEntry, processFunction, cookie);
+        break;
+    case DEFLATED:
+        ret = processDeflatedEntry(pArchive, pEntry, processFunction, cookie);
+        break;
+    default:
+        LOGE("Unsupported compression type %d for entry '%s'\n",
+                pEntry->compression, pEntry->fileName);
+        break;
+    }
+
+    return ret;
+}
+
+typedef struct {
+    char *buf;
+    int bufLen;
+} CopyProcessArgs;
+
+static bool copyProcessFunction(const unsigned char *data, int dataLen,
+        void *cookie)
+{
+    CopyProcessArgs *args = (CopyProcessArgs *)cookie;
+    if (dataLen <= args->bufLen) {
+        memcpy(args->buf, data, dataLen);
+        args->buf += dataLen;
+        args->bufLen -= dataLen;
+        return true;
+    }
+    return false;
+}
+
+/*
+ * Read an entry into a buffer allocated by the caller.
+ */
+bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
+        char *buf, int bufLen)
+{
+    CopyProcessArgs args;
+    bool ret;
+
+    args.buf = buf;
+    args.bufLen = bufLen;
+    ret = mzProcessZipEntryContents(pArchive, pEntry, copyProcessFunction,
+            (void *)&args);
+    if (!ret) {
+        LOGE("Can't extract entry to buffer.\n");
+        return false;
+    }
+    return true;
+}
+
+static bool writeProcessFunction(const unsigned char *data, int dataLen,
+                                 void *cookie)
+{
+    int fd = (int)(intptr_t)cookie;
+    if (dataLen == 0) {
+        return true;
+    }
+    ssize_t soFar = 0;
+    while (true) {
+        ssize_t n = TEMP_FAILURE_RETRY(write(fd, data+soFar, dataLen-soFar));
+        if (n <= 0) {
+            LOGE("Error writing %zd bytes from zip file from %p: %s\n",
+                 dataLen-soFar, data+soFar, strerror(errno));
+            return false;
+        } else if (n > 0) {
+            soFar += n;
+            if (soFar == dataLen) return true;
+            if (soFar > dataLen) {
+                LOGE("write overrun?  (%zd bytes instead of %d)\n",
+                     soFar, dataLen);
+                return false;
+            }
+        }
+    }
+}
+
+/*
+ * Uncompress "pEntry" in "pArchive" to "fd" at the current offset.
+ */
+bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, int fd)
+{
+    bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction,
+                                         (void*)(intptr_t)fd);
+    if (!ret) {
+        LOGE("Can't extract entry to file.\n");
+        return false;
+    }
+    return true;
+}
+
+typedef struct {
+    unsigned char* buffer;
+    long len;
+} BufferExtractCookie;
+
+static bool bufferProcessFunction(const unsigned char *data, int dataLen,
+    void *cookie) {
+    BufferExtractCookie *bec = (BufferExtractCookie*)cookie;
+
+    memmove(bec->buffer, data, dataLen);
+    bec->buffer += dataLen;
+    bec->len -= dataLen;
+
+    return true;
+}
+
+/*
+ * Uncompress "pEntry" in "pArchive" to buffer, which must be large
+ * enough to hold mzGetZipEntryUncomplen(pEntry) bytes.
+ */
+bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, unsigned char *buffer)
+{
+    BufferExtractCookie bec;
+    bec.buffer = buffer;
+    bec.len = mzGetZipEntryUncompLen(pEntry);
+
+    bool ret = mzProcessZipEntryContents(pArchive, pEntry,
+        bufferProcessFunction, (void*)&bec);
+    if (!ret || bec.len != 0) {
+        LOGE("Can't extract entry to memory buffer.\n");
+        return false;
+    }
+    return true;
+}
+
+
+/* Helper state to make path translation easier and less malloc-happy.
+ */
+typedef struct {
+    const char *targetDir;
+    const char *zipDir;
+    char *buf;
+    int targetDirLen;
+    int zipDirLen;
+    int bufLen;
+} MzPathHelper;
+
+/* Given the values of targetDir and zipDir in the helper,
+ * return the target filename of the provided entry.
+ * The helper must be initialized first.
+ */
+static const char *targetEntryPath(MzPathHelper *helper, ZipEntry *pEntry)
+{
+    int needLen;
+    bool firstTime = (helper->buf == NULL);
+
+    /* target file <-- targetDir + / + entry[zipDirLen:]
+     */
+    needLen = helper->targetDirLen + 1 +
+            pEntry->fileNameLen - helper->zipDirLen + 1;
+    if (needLen > helper->bufLen) {
+        char *newBuf;
+
+        needLen *= 2;
+        newBuf = (char *)realloc(helper->buf, needLen);
+        if (newBuf == NULL) {
+            return NULL;
+        }
+        helper->buf = newBuf;
+        helper->bufLen = needLen;
+    }
+
+    /* Every path will start with the target path and a slash.
+     */
+    if (firstTime) {
+        char *p = helper->buf;
+        memcpy(p, helper->targetDir, helper->targetDirLen);
+        p += helper->targetDirLen;
+        if (p == helper->buf || p[-1] != '/') {
+            helper->targetDirLen += 1;
+            *p++ = '/';
+        }
+    }
+
+    /* Replace the custom part of the path with the appropriate
+     * part of the entry's path.
+     */
+    char *epath = helper->buf + helper->targetDirLen;
+    memcpy(epath, pEntry->fileName + helper->zipDirLen,
+            pEntry->fileNameLen - helper->zipDirLen);
+    epath += pEntry->fileNameLen - helper->zipDirLen;
+    *epath = '\0';
+
+    return helper->buf;
+}
+
+/*
+ * Inflate all entries under zipDir to the directory specified by
+ * targetDir, which must exist and be a writable directory.
+ *
+ * The immediate children of zipDir will become the immediate
+ * children of targetDir; e.g., if the archive contains the entries
+ *
+ *     a/b/c/one
+ *     a/b/c/two
+ *     a/b/c/d/three
+ *
+ * and mzExtractRecursive(a, "a/b/c", "/tmp") is called, the resulting
+ * files will be
+ *
+ *     /tmp/one
+ *     /tmp/two
+ *     /tmp/d/three
+ *
+ * Returns true on success, false on failure.
+ */
+bool mzExtractRecursive(const ZipArchive *pArchive,
+                        const char *zipDir, const char *targetDir,
+                        const struct utimbuf *timestamp,
+                        void (*callback)(const char *fn, void *), void *cookie,
+                        struct selabel_handle *sehnd)
+{
+    if (zipDir[0] == '/') {
+        LOGE("mzExtractRecursive(): zipDir must be a relative path.\n");
+        return false;
+    }
+    if (targetDir[0] != '/') {
+        LOGE("mzExtractRecursive(): targetDir must be an absolute path.\n");
+        return false;
+    }
+
+    unsigned int zipDirLen;
+    char *zpath;
+
+    zipDirLen = strlen(zipDir);
+    zpath = (char *)malloc(zipDirLen + 2);
+    if (zpath == NULL) {
+        LOGE("Can't allocate %d bytes for zip path\n", zipDirLen + 2);
+        return false;
+    }
+    /* If zipDir is empty, we'll extract the entire zip file.
+     * Otherwise, canonicalize the path.
+     */
+    if (zipDirLen > 0) {
+        /* Make sure there's (hopefully, exactly one) slash at the
+         * end of the path.  This way we don't need to worry about
+         * accidentally extracting "one/twothree" when a path like
+         * "one/two" is specified.
+         */
+        memcpy(zpath, zipDir, zipDirLen);
+        if (zpath[zipDirLen-1] != '/') {
+            zpath[zipDirLen++] = '/';
+        }
+    }
+    zpath[zipDirLen] = '\0';
+
+    /* Set up the helper structure that we'll use to assemble paths.
+     */
+    MzPathHelper helper;
+    helper.targetDir = targetDir;
+    helper.targetDirLen = strlen(helper.targetDir);
+    helper.zipDir = zpath;
+    helper.zipDirLen = strlen(helper.zipDir);
+    helper.buf = NULL;
+    helper.bufLen = 0;
+
+    /* Walk through the entries and extract anything whose path begins
+     * with zpath.
+    //TODO: since the entries are sorted, binary search for the first match
+    //      and stop after the first non-match.
+     */
+    unsigned int i;
+    bool seenMatch = false;
+    int ok = true;
+    int extractCount = 0;
+    for (i = 0; i < pArchive->numEntries; i++) {
+        ZipEntry *pEntry = pArchive->pEntries + i;
+        if (pEntry->fileNameLen < zipDirLen) {
+       //TODO: look out for a single empty directory entry that matches zpath, but
+       //      missing the trailing slash.  Most zip files seem to include
+       //      the trailing slash, but I think it's legal to leave it off.
+       //      e.g., zpath "a/b/", entry "a/b", with no children of the entry.
+            /* No chance of matching.
+             */
+#if SORT_ENTRIES
+            if (seenMatch) {
+                /* Since the entries are sorted, we can give up
+                 * on the first mismatch after the first match.
+                 */
+                break;
+            }
+#endif
+            continue;
+        }
+        /* If zpath is empty, this strncmp() will match everything,
+         * which is what we want.
+         */
+        if (strncmp(pEntry->fileName, zpath, zipDirLen) != 0) {
+#if SORT_ENTRIES
+            if (seenMatch) {
+                /* Since the entries are sorted, we can give up
+                 * on the first mismatch after the first match.
+                 */
+                break;
+            }
+#endif
+            continue;
+        }
+        /* This entry begins with zipDir, so we'll extract it.
+         */
+        seenMatch = true;
+
+        /* Find the target location of the entry.
+         */
+        const char *targetFile = targetEntryPath(&helper, pEntry);
+        if (targetFile == NULL) {
+            LOGE("Can't assemble target path for \"%.*s\"\n",
+                    pEntry->fileNameLen, pEntry->fileName);
+            ok = false;
+            break;
+        }
+
+#define UNZIP_DIRMODE 0755
+#define UNZIP_FILEMODE 0644
+        /*
+         * Create the file or directory. We ignore directory entries
+         * because we recursively create paths to each file entry we encounter
+         * in the zip archive anyway.
+         *
+         * NOTE: A "directory entry" in a zip archive is just a zero length
+         * entry that ends in a "/". They're not mandatory and many tools get
+         * rid of them. We need to process them only if we want to preserve
+         * empty directories from the archive.
+         */
+        if (pEntry->fileName[pEntry->fileNameLen-1] != '/') {
+            /* This is not a directory.  First, make sure that
+             * the containing directory exists.
+             */
+            int ret = dirCreateHierarchy(
+                    targetFile, UNZIP_DIRMODE, timestamp, true, sehnd);
+            if (ret != 0) {
+                LOGE("Can't create containing directory for \"%s\": %s\n",
+                        targetFile, strerror(errno));
+                ok = false;
+                break;
+            }
+
+            /*
+             * The entry is a regular file or a symlink. Open the target for writing.
+             *
+             * TODO: This behavior for symlinks seems rather bizarre. For a
+             * symlink foo/bar/baz -> foo/tar/taz, we will create a file called
+             * "foo/bar/baz" whose contents are the literal "foo/tar/taz". We
+             * warn about this for now and preserve older behavior.
+             */
+            if (mzIsZipEntrySymlink(pEntry)) {
+                LOGE("Symlink entry \"%.*s\" will be output as a regular file.",
+                     pEntry->fileNameLen, pEntry->fileName);
+            }
+
+            char *secontext = NULL;
+
+            if (sehnd) {
+                selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE);
+                setfscreatecon(secontext);
+            }
+
+            int fd = creat(targetFile, UNZIP_FILEMODE);
+
+            if (secontext) {
+                freecon(secontext);
+                setfscreatecon(NULL);
+            }
+
+            if (fd < 0) {
+                LOGE("Can't create target file \"%s\": %s\n",
+                        targetFile, strerror(errno));
+                ok = false;
+                break;
+            }
+
+            bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd);
+            close(fd);
+            if (!ok) {
+                LOGE("Error extracting \"%s\"\n", targetFile);
+                ok = false;
+                break;
+            }
+
+            if (timestamp != NULL && utime(targetFile, timestamp)) {
+                LOGE("Error touching \"%s\"\n", targetFile);
+                ok = false;
+                break;
+            }
+
+            LOGV("Extracted file \"%s\"\n", targetFile);
+            ++extractCount;
+        }
+
+        if (callback != NULL) callback(targetFile, cookie);
+    }
+
+    LOGV("Extracted %d file(s)\n", extractCount);
+
+    free(helper.buf);
+    free(zpath);
+
+    return ok;
+}
diff --git a/minzip/Zip.h b/minzip/Zip.h
new file mode 100644
index 0000000..0a03451
--- /dev/null
+++ b/minzip/Zip.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Simple Zip archive support.
+ */
+#ifndef _MINZIP_ZIP
+#define _MINZIP_ZIP
+
+#include "inline_magic.h"
+
+#include <stdlib.h>
+#include <utime.h>
+
+#include "Hash.h"
+#include "SysUtil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+
+/*
+ * One entry in the Zip archive.  Treat this as opaque -- use accessors below.
+ *
+ * TODO: we're now keeping the pages mapped so we don't have to copy the
+ * filename.  We can change the accessors to retrieve the various pieces
+ * directly from the source file instead of copying them out, for a very
+ * slight speed hit and a modest reduction in memory usage.
+ */
+typedef struct ZipEntry {
+    unsigned int fileNameLen;
+    const char*  fileName;       // not null-terminated
+    loff_t       offset;
+    long         compLen;
+    long         uncompLen;
+    int          compression;
+    long         modTime;
+    long         crc32;
+    int          versionMadeBy;
+    long         externalFileAttributes;
+} ZipEntry;
+
+/*
+ * One Zip archive.  Treat as opaque.
+ */
+typedef struct ZipArchive {
+    unsigned int   numEntries;
+    ZipEntry*      pEntries;
+    HashTable*     pHash;          // maps file name to ZipEntry
+    unsigned char* addr;
+    size_t         length;
+} ZipArchive;
+
+/*
+ * Represents a non-NUL-terminated string,
+ * which is how entry names are stored.
+ */
+typedef struct {
+    const char *str;
+    size_t len;
+} UnterminatedString;
+
+/*
+ * Open a Zip archive.
+ *
+ * On success, returns 0 and populates "pArchive".  Returns nonzero errno
+ * value on failure.
+ */
+int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive);
+
+/*
+ * Close archive, releasing resources associated with it.
+ *
+ * Depending on the implementation this could unmap pages used by classes
+ * stored in a Jar.  This should only be done after unloading classes.
+ */
+void mzCloseZipArchive(ZipArchive* pArchive);
+
+
+/*
+ * Find an entry in the Zip archive, by name.
+ */
+const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
+        const char* entryName);
+
+INLINE loff_t mzGetZipEntryOffset(const ZipEntry* pEntry) {
+    return pEntry->offset;
+}
+INLINE long mzGetZipEntryUncompLen(const ZipEntry* pEntry) {
+    return pEntry->uncompLen;
+}
+
+/*
+ * Type definition for the callback function used by
+ * mzProcessZipEntryContents().
+ */
+typedef bool (*ProcessZipEntryContentsFunction)(const unsigned char *data,
+    int dataLen, void *cookie);
+
+/*
+ * Stream the uncompressed data through the supplied function,
+ * passing cookie to it each time it gets called.  processFunction
+ * may be called more than once.
+ *
+ * If processFunction returns false, the operation is abandoned and
+ * mzProcessZipEntryContents() immediately returns false.
+ *
+ * This is useful for calculating the hash of an entry's uncompressed contents.
+ */
+bool mzProcessZipEntryContents(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+    void *cookie);
+
+/*
+ * Read an entry into a buffer allocated by the caller.
+ */
+bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
+        char* buf, int bufLen);
+
+/*
+ * Inflate and write an entry to a file.
+ */
+bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, int fd);
+
+/*
+ * Inflate and write an entry to a memory buffer, which must be long
+ * enough to hold mzGetZipEntryUncomplen(pEntry) bytes.
+ */
+bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive,
+    const ZipEntry *pEntry, unsigned char* buffer);
+
+/*
+ * Inflate all files under zipDir to the directory specified by
+ * targetDir, which must exist and be a writable directory.
+ *
+ * Directory entries and symlinks are not extracted.
+ *
+ *
+ * The immediate children of zipDir will become the immediate
+ * children of targetDir; e.g., if the archive contains the entries
+ *
+ *     a/b/c/one
+ *     a/b/c/two
+ *     a/b/c/d/three
+ *
+ * and mzExtractRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting
+ * files will be
+ *
+ *     /tmp/one
+ *     /tmp/two
+ *     /tmp/d/three
+ *
+ * If timestamp is non-NULL, file timestamps will be set accordingly.
+ *
+ * If callback is non-NULL, it will be invoked with each unpacked file.
+ *
+ * Returns true on success, false on failure.
+ */
+bool mzExtractRecursive(const ZipArchive *pArchive,
+        const char *zipDir, const char *targetDir,
+        const struct utimbuf *timestamp,
+        void (*callback)(const char *fn, void*), void *cookie,
+        struct selabel_handle *sehnd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*_MINZIP_ZIP*/
diff --git a/minzip/inline_magic.h b/minzip/inline_magic.h
new file mode 100644
index 0000000..59c659f
--- /dev/null
+++ b/minzip/inline_magic.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINZIP_INLINE_MAGIC_H_
+#define MINZIP_INLINE_MAGIC_H_
+
+#ifndef MINZIP_GENERATE_INLINES
+#define INLINE extern inline __attribute((__gnu_inline__))
+#else
+#define INLINE
+#endif
+
+#endif  // MINZIP_INLINE_MAGIC_H_
diff --git a/mmcutils/Android.mk b/mmcutils/Android.mk
new file mode 100644
index 0000000..40cda36
--- /dev/null
+++ b/mmcutils/Android.mk
@@ -0,0 +1,25 @@
+ifneq ($(TARGET_SIMULATOR),true)
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	mmcutils.c
+
+LOCAL_MODULE := libmmcutils
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_STATIC_LIBRARY)
+
+#Added for TWRP building dynamic:
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+mmcutils.c
+
+LOCAL_MODULE := libmmcutils
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
+
+endif	# !TARGET_SIMULATOR
diff --git a/mmcutils/mmcutils.c b/mmcutils/mmcutils.c
new file mode 100755
index 0000000..3f5514e
--- /dev/null
+++ b/mmcutils/mmcutils.c
@@ -0,0 +1,634 @@
+/*
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.
+ *   * Neither the name of Code Aurora Forum, Inc. nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/mount.h>  // for _IOW, _IOR, mount()
+
+#include "mmcutils.h"
+
+unsigned ext3_count = 0;
+char *ext3_partitions[] = {"system", "userdata", "cache", "NONE"};
+
+unsigned vfat_count = 0;
+char *vfat_partitions[] = {"modem", "NONE"};
+
+struct MmcPartition {
+    char *device_index;
+    char *filesystem;
+    char *name;
+    unsigned dstatus;
+    unsigned dtype ;
+    unsigned dfirstsec;
+    unsigned dsize;
+};
+
+typedef struct {
+    MmcPartition *partitions;
+    int partitions_allocd;
+    int partition_count;
+} MmcState;
+
+static MmcState g_mmc_state = {
+    NULL,   // partitions
+    0,      // partitions_allocd
+    -1      // partition_count
+};
+
+#define MMC_DEVICENAME "/dev/block/mmcblk0"
+
+static void
+mmc_partition_name (MmcPartition *mbr, unsigned int type) {
+    switch(type)
+    {
+        char name[64];
+        case MMC_BOOT_TYPE:
+            sprintf(name,"boot");
+            mbr->name = strdup(name);
+            break;
+        case MMC_RECOVERY_TYPE:
+            sprintf(name,"recovery");
+            mbr->name = strdup(name);
+            break;
+        case MMC_EXT3_TYPE:
+            if (strcmp("NONE", ext3_partitions[ext3_count])) {
+                strcpy((char *)name,(const char *)ext3_partitions[ext3_count]);
+                mbr->name = strdup(name);
+                ext3_count++;
+            }
+            mbr->filesystem = strdup("ext3");
+            break;
+        case MMC_VFAT_TYPE:
+            if (strcmp("NONE", vfat_partitions[vfat_count])) {
+                strcpy((char *)name,(const char *)vfat_partitions[vfat_count]);
+                mbr->name = strdup(name);
+                vfat_count++;
+            }
+            mbr->filesystem = strdup("vfat");
+            break;
+    };
+}
+
+static int
+mmc_read_mbr (const char *device, MmcPartition *mbr) {
+    FILE *fd;
+    unsigned char buffer[512];
+    int idx, i;
+    unsigned mmc_partition_count = 0;
+    unsigned int dtype;
+    unsigned int dfirstsec;
+    unsigned int EBR_first_sec;
+    unsigned int EBR_current_sec;
+    int ret = -1;
+
+    fd = fopen(device, "r");
+    if(fd == NULL)
+    {
+        printf("Can't open device: \"%s\"\n", device);
+        goto ERROR2;
+    }
+    if ((fread(buffer, 512, 1, fd)) != 1)
+    {
+        printf("Can't read device: \"%s\"\n", device);
+        goto ERROR1;
+    }
+    /* Check to see if signature exists */
+    if ((buffer[TABLE_SIGNATURE] != 0x55) || \
+        (buffer[TABLE_SIGNATURE + 1] != 0xAA))
+    {
+        printf("Incorrect mbr signatures!\n");
+        goto ERROR1;
+    }
+    idx = TABLE_ENTRY_0;
+    for (i = 0; i < 4; i++)
+    {
+        char device_index[128];
+
+        mbr[mmc_partition_count].dstatus = \
+                    buffer[idx + i * TABLE_ENTRY_SIZE + OFFSET_STATUS];
+        mbr[mmc_partition_count].dtype   = \
+                    buffer[idx + i * TABLE_ENTRY_SIZE + OFFSET_TYPE];
+        mbr[mmc_partition_count].dfirstsec = \
+                    GET_LWORD_FROM_BYTE(&buffer[idx + \
+                                        i * TABLE_ENTRY_SIZE + \
+                                        OFFSET_FIRST_SEC]);
+        mbr[mmc_partition_count].dsize  = \
+                    GET_LWORD_FROM_BYTE(&buffer[idx + \
+                                        i * TABLE_ENTRY_SIZE + \
+                                        OFFSET_SIZE]);
+        dtype  = mbr[mmc_partition_count].dtype;
+        dfirstsec = mbr[mmc_partition_count].dfirstsec;
+        mmc_partition_name(&mbr[mmc_partition_count], \
+                        mbr[mmc_partition_count].dtype);
+
+        sprintf(device_index, "%sp%d", device, (mmc_partition_count+1));
+        mbr[mmc_partition_count].device_index = strdup(device_index);
+
+        mmc_partition_count++;
+        if (mmc_partition_count == MAX_PARTITIONS)
+            goto SUCCESS;
+    }
+
+    /* See if the last partition is EBR, if not, parsing is done */
+    if (dtype != 0x05)
+    {
+        goto SUCCESS;
+    }
+
+    EBR_first_sec = dfirstsec;
+    EBR_current_sec = dfirstsec;
+
+    fseek (fd, (EBR_first_sec * 512), SEEK_SET);
+    if ((fread(buffer, 512, 1, fd)) != 1)
+        goto ERROR1;
+
+    /* Loop to parse the EBR */
+    for (i = 0;; i++)
+    {
+        char device_index[128];
+
+        if ((buffer[TABLE_SIGNATURE] != 0x55) || (buffer[TABLE_SIGNATURE + 1] != 0xAA))
+        {
+            break;
+        }
+        mbr[mmc_partition_count].dstatus = \
+                    buffer[TABLE_ENTRY_0 + OFFSET_STATUS];
+        mbr[mmc_partition_count].dtype   = \
+                    buffer[TABLE_ENTRY_0 + OFFSET_TYPE];
+        mbr[mmc_partition_count].dfirstsec = \
+                    GET_LWORD_FROM_BYTE(&buffer[TABLE_ENTRY_0 + \
+                                        OFFSET_FIRST_SEC])    + \
+                                        EBR_current_sec;
+        mbr[mmc_partition_count].dsize = \
+                    GET_LWORD_FROM_BYTE(&buffer[TABLE_ENTRY_0 + \
+                                        OFFSET_SIZE]);
+        mmc_partition_name(&mbr[mmc_partition_count], \
+                        mbr[mmc_partition_count].dtype);
+
+        sprintf(device_index, "%sp%d", device, (mmc_partition_count+1));
+        mbr[mmc_partition_count].device_index = strdup(device_index);
+
+        mmc_partition_count++;
+        if (mmc_partition_count == MAX_PARTITIONS)
+            goto SUCCESS;
+
+        dfirstsec = GET_LWORD_FROM_BYTE(&buffer[TABLE_ENTRY_1 + OFFSET_FIRST_SEC]);
+        if(dfirstsec == 0)
+        {
+            /* Getting to the end of the EBR tables */
+            break;
+        }
+        /* More EBR to follow - read in the next EBR sector */
+        fseek (fd,  ((EBR_first_sec + dfirstsec) * 512), SEEK_SET);
+        if ((fread(buffer, 512, 1, fd)) != 1)
+            goto ERROR1;
+
+        EBR_current_sec = EBR_first_sec + dfirstsec;
+    }
+
+SUCCESS:
+    ret = mmc_partition_count;
+ERROR1:
+    fclose(fd);
+ERROR2:
+    return ret;
+}
+
+int
+mmc_scan_partitions() {
+    int i;
+
+    if (g_mmc_state.partitions == NULL) {
+        const int nump = MAX_PARTITIONS;
+        MmcPartition *partitions = malloc(nump * sizeof(*partitions));
+        if (partitions == NULL) {
+            errno = ENOMEM;
+            return -1;
+        }
+        g_mmc_state.partitions = partitions;
+        g_mmc_state.partitions_allocd = nump;
+        memset(partitions, 0, nump * sizeof(*partitions));
+    }
+    g_mmc_state.partition_count = 0;
+    ext3_count = 0;
+    vfat_count = 0;
+
+    /* Initialize all of the entries to make things easier later.
+     * (Lets us handle sparsely-numbered partitions, which
+     * may not even be possible.)
+     */
+    for (i = 0; i < g_mmc_state.partitions_allocd; i++) {
+        MmcPartition *p = &g_mmc_state.partitions[i];
+        if (p->device_index != NULL) {
+            free(p->device_index);
+            p->device_index = NULL;
+        }
+        if (p->name != NULL) {
+            free(p->name);
+            p->name = NULL;
+        }
+        if (p->filesystem != NULL) {
+            free(p->filesystem);
+            p->filesystem = NULL;
+        }
+    }
+
+    g_mmc_state.partition_count = mmc_read_mbr(MMC_DEVICENAME, g_mmc_state.partitions);
+    if(g_mmc_state.partition_count == -1)
+    {
+        printf("Error in reading mbr!\n");
+        // keep "partitions" around so we can free the names on a rescan.
+        g_mmc_state.partition_count = -1;
+    }
+    return g_mmc_state.partition_count;
+}
+
+static const MmcPartition *
+mmc_find_partition_by_device_index(const char *device_index)
+{
+    if (g_mmc_state.partitions != NULL) {
+        int i;
+        for (i = 0; i < g_mmc_state.partitions_allocd; i++) {
+            MmcPartition *p = &g_mmc_state.partitions[i];
+            if (p->device_index !=NULL && p->name != NULL) {
+                if (strcmp(p->device_index, device_index) == 0) {
+                    return p;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+const MmcPartition *
+mmc_find_partition_by_name(const char *name)
+{
+    if (name[0] == '/') {
+        return mmc_find_partition_by_device_index(name);
+    }
+
+    if (g_mmc_state.partitions != NULL) {
+        int i;
+        for (i = 0; i < g_mmc_state.partitions_allocd; i++) {
+            MmcPartition *p = &g_mmc_state.partitions[i];
+            if (p->device_index !=NULL && p->name != NULL) {
+                if (strcmp(p->name, name) == 0) {
+                    return p;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+#define MKE2FS_BIN      "/system/bin/mke2fs"
+#define TUNE2FS_BIN     "/system/bin/tune2fs"
+#define E2FSCK_BIN      "/system/bin/e2fsck"
+
+int
+run_exec_process ( char *const *argv) {
+    pid_t pid;
+    int status;
+    pid = fork();
+    if (pid == 0) {
+        execv(argv[0], argv);
+        fprintf(stderr, "E:Can't run (%s)\n",strerror(errno));
+        _exit(-1);
+    }
+
+    waitpid(pid, &status, 0);
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        return 1;
+    }
+    return 0;
+}
+
+int
+format_ext3_device (char *device) {
+    char *const mke2fs[] = {MKE2FS_BIN, "-j", "-q", device, NULL};
+    char *const tune2fs[] = {TUNE2FS_BIN, "-C", "1", device, NULL};
+    // Run mke2fs
+    if(run_exec_process(mke2fs)) {
+        printf("failure while running mke2fs\n");
+        return -1;
+    }
+
+    // Run tune2fs
+    if(run_exec_process(tune2fs)) {
+        printf("failure while running mke2fs\n");
+        return -1;
+    }
+
+    // Run e2fsck
+    char *const e2fsck[] = {E2FSCK_BIN, "-fy", device, NULL};
+    if(run_exec_process(e2fsck)) {
+        printf("failure while running e2fsck\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+int
+format_ext2_device (char *device) {
+    // Run mke2fs
+    char *const mke2fs[] = {MKE2FS_BIN, device, NULL};
+    if(run_exec_process(mke2fs))
+        return -1;
+
+    // Run tune2fs
+    char *const tune2fs[] = {TUNE2FS_BIN, "-C", "1", device, NULL};
+    if(run_exec_process(tune2fs))
+        return -1;
+
+    // Run e2fsck
+    char *const e2fsck[] = {E2FSCK_BIN, "-fy", device, NULL};
+    if(run_exec_process(e2fsck))
+        return -1;
+
+    return 0;
+}
+
+int
+mmc_format_ext3 (const MmcPartition *partition) {
+    char device[128];
+    strcpy(device, partition->device_index);
+    return format_ext3_device(device);
+}
+
+int
+mmc_mount_partition(const MmcPartition *partition, const char *mount_point,
+        int read_only)
+{
+    const unsigned long flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME;
+    char devname[128];
+    int rv = -1;
+    strcpy(devname, partition->device_index);
+    if (partition->filesystem == NULL) {
+        printf("Null filesystem!\n");
+        return rv;
+    }
+    if (!read_only) {
+        rv = mount(devname, mount_point, partition->filesystem, flags, NULL);
+    }
+    if (read_only || rv < 0) {
+        rv = mount(devname, mount_point, partition->filesystem, flags | MS_RDONLY, 0);
+        if (rv < 0) {
+            printf("Failed to mount %s on %s: %s\n",
+                    devname, mount_point, strerror(errno));
+        } else {
+            printf("Mount %s on %s read-only\n", devname, mount_point);
+        }
+    }
+    return rv;
+}
+
+int
+mmc_raw_copy (const MmcPartition *partition, const char *in_file) {
+    int ch;
+    FILE *in;
+    FILE *out;
+    char buf[512];
+    unsigned sz = 0;
+    unsigned i;
+    int ret = -1;
+    char *out_file = partition->device_index;
+
+    in  = fopen ( in_file,  "r" );
+    if (in == NULL)
+        goto ERROR3;
+
+    out = fopen ( out_file,  "w" );
+    if (out == NULL)
+        goto ERROR2;
+
+    fseek(in, 0L, SEEK_END);
+    sz = ftell(in);
+    fseek(in, 0L, SEEK_SET);
+
+    if (sz % 512)
+    {
+        while ( ( ch = fgetc ( in ) ) != EOF )
+            fputc ( ch, out );
+    }
+    else
+    {
+        for (i=0; i< (sz/512); i++)
+        {
+            if ((fread(buf, 512, 1, in)) != 1)
+                goto ERROR1;
+            if ((fwrite(buf, 512, 1, out)) != 1)
+                goto ERROR1;
+        }
+    }
+
+    fsync(fileno(out));
+    ret = 0;
+ERROR1:
+    fclose ( out );
+ERROR2:
+    fclose ( in );
+ERROR3:
+    return ret;
+
+}
+
+
+int
+mmc_raw_dump_internal (const char* in_file, const char *out_file) {
+    int ch;
+    FILE *in;
+    FILE *out;
+    char buf[512];
+    unsigned sz = 0;
+    unsigned i;
+    int ret = -1;
+
+    in  = fopen ( in_file,  "r" );
+    if (in == NULL)
+        goto ERROR3;
+
+    out = fopen ( out_file,  "w" );
+    if (out == NULL)
+        goto ERROR2;
+
+    fseek(in, 0L, SEEK_END);
+    sz = ftell(in);
+    fseek(in, 0L, SEEK_SET);
+
+    if (sz % 512)
+    {
+        while ( ( ch = fgetc ( in ) ) != EOF )
+            fputc ( ch, out );
+    }
+    else
+    {
+        for (i=0; i< (sz/512); i++)
+        {
+            if ((fread(buf, 512, 1, in)) != 1)
+                goto ERROR1;
+            if ((fwrite(buf, 512, 1, out)) != 1)
+                goto ERROR1;
+        }
+    }
+
+    fsync(fileno(out));
+    ret = 0;
+ERROR1:
+    fclose ( out );
+ERROR2:
+    fclose ( in );
+ERROR3:
+    return ret;
+
+}
+
+// TODO: refactor this to not be a giant copy paste mess
+int
+mmc_raw_dump (const MmcPartition *partition, const char *out_file) {
+    return mmc_raw_dump_internal(partition->device_index, out_file);
+}
+
+
+int
+mmc_raw_read (const MmcPartition *partition, char *data, int data_size) {
+    FILE *in;
+    unsigned sz = 0;
+    int ret = -1;
+    char *in_file = partition->device_index;
+
+    in  = fopen ( in_file,  "r" );
+    if (in == NULL)
+        goto ERROR3;
+
+    fseek(in, 0L, SEEK_END);
+    sz = ftell(in);
+    fseek(in, 0L, SEEK_SET);
+
+    fread(data, data_size, 1, in);
+
+    ret = 0;
+    fclose ( in );
+ERROR3:
+    return ret;
+
+}
+
+int
+mmc_raw_write (const MmcPartition *partition, char *data, int data_size) {
+    FILE *out;
+    int ret = -1;
+    char *out_file = partition->device_index;
+
+    out  = fopen ( out_file,  "w" );
+    if (out == NULL)
+        goto ERROR3;
+
+    fwrite(data, data_size, 1, out);
+
+    ret = 0;
+    fclose ( out );
+ERROR3:
+    return ret;
+
+}
+
+int cmd_mmc_restore_raw_partition(const char *partition, const char *filename)
+{
+    if (partition[0] != '/') {
+        mmc_scan_partitions();
+        const MmcPartition *p;
+        p = mmc_find_partition_by_name(partition);
+        if (p == NULL)
+            return -1;
+        return mmc_raw_copy(p, filename);
+    }
+    else {
+        return mmc_raw_dump_internal(filename, partition);
+    }
+}
+
+int cmd_mmc_backup_raw_partition(const char *partition, const char *filename)
+{
+    if (partition[0] != '/') {
+        mmc_scan_partitions();
+        const MmcPartition *p;
+        p = mmc_find_partition_by_name(partition);
+        if (p == NULL)
+            return -1;
+        return mmc_raw_dump(p, filename);
+    }
+    else {
+        return mmc_raw_dump_internal(partition, filename);
+    }
+}
+
+int cmd_mmc_erase_raw_partition(const char *partition __unused)
+{
+    return 0;
+}
+
+int cmd_mmc_erase_partition(const char *partition, const char *filesystem __unused)
+{
+    mmc_scan_partitions();
+    const MmcPartition *p;
+    p = mmc_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+    return mmc_format_ext3 (p);
+}
+
+int cmd_mmc_mount_partition(const char *partition, const char *mount_point, const char *filesystem __unused, int read_only)
+{
+    mmc_scan_partitions();
+    const MmcPartition *p;
+    p = mmc_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+    return mmc_mount_partition(p, mount_point, read_only);
+}
+
+int cmd_mmc_get_partition_device(const char *partition, char *device)
+{
+    mmc_scan_partitions();
+    const MmcPartition *p;
+    p = mmc_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+    strcpy(device, p->device_index);
+    return 0;
+}
diff --git a/mmcutils/mmcutils.h b/mmcutils/mmcutils.h
new file mode 100644
index 0000000..4fece79
--- /dev/null
+++ b/mmcutils/mmcutils.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above
+ *     copyright notice, this list of conditions and the following
+ *     disclaimer in the documentation and/or other materials provided
+ *     with the distribution.
+ *   * Neither the name of Code Aurora Forum, Inc. nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MMCUTILS_H_
+#define MMCUTILS_H_
+
+/* Some useful define used to access the MBR/EBR table */
+#ifndef BLOCK_SIZE
+#define BLOCK_SIZE                0x200
+#endif
+#define TABLE_ENTRY_0             0x1BE
+#define TABLE_ENTRY_1             0x1CE
+#define TABLE_ENTRY_2             0x1DE
+#define TABLE_ENTRY_3             0x1EE
+#define TABLE_SIGNATURE           0x1FE
+#define TABLE_ENTRY_SIZE          0x010
+
+#define OFFSET_STATUS             0x00
+#define OFFSET_TYPE               0x04
+#define OFFSET_FIRST_SEC          0x08
+#define OFFSET_SIZE               0x0C
+#define COPYBUFF_SIZE             (1024 * 16)
+#define BINARY_IN_TABLE_SIZE      (16 * 512)
+#define MAX_FILE_ENTRIES          20
+
+#define MMC_BOOT_TYPE 0x48
+#define MMC_SYSTEM_TYPE 0x82
+#define MMC_USERDATA_TYPE 0x83
+#define MMC_RECOVERY_TYPE 0x71
+
+#define MMC_RCA 2
+
+#define MAX_PARTITIONS 64
+
+#define GET_LWORD_FROM_BYTE(x)    ((unsigned)*(x) | \
+        ((unsigned)*((x)+1) << 8) | \
+        ((unsigned)*((x)+2) << 16) | \
+        ((unsigned)*((x)+3) << 24))
+
+#define PUT_LWORD_TO_BYTE(x, y)   do{*(x) = (y) & 0xff;     \
+    *((x)+1) = ((y) >> 8) & 0xff;     \
+    *((x)+2) = ((y) >> 16) & 0xff;     \
+    *((x)+3) = ((y) >> 24) & 0xff; }while(0)
+
+#define GET_PAR_NUM_FROM_POS(x) ((((x) & 0x0000FF00) >> 8) + ((x) & 0x000000FF))
+
+#define MMC_BOOT_TYPE 0x48
+#define MMC_EXT3_TYPE 0x83
+#define MMC_VFAT_TYPE 0xC
+typedef struct MmcPartition MmcPartition;
+
+/* Functions */
+int mmc_scan_partitions();
+const MmcPartition *mmc_find_partition_by_name(const char *name);
+int mmc_format_ext3 (const MmcPartition *partition);
+int mmc_mount_partition(const MmcPartition *partition, const char *mount_point, \
+                        int read_only);
+int mmc_raw_copy (const MmcPartition *partition, const char *in_file);
+int mmc_raw_read (const MmcPartition *partition, char *data, int data_size);
+int mmc_raw_write (const MmcPartition *partition, char *data, int data_size);
+
+int format_ext2_device(char *device);
+int format_ext3_device(char *device);
+
+#endif  // MMCUTILS_H_
+
+
diff --git a/mounts.c b/mounts.c
new file mode 100644
index 0000000..e8b69e8
--- /dev/null
+++ b/mounts.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mount.h>
+
+#include "mounts.h"
+
+typedef struct {
+    MountedVolume *volumes;
+    int volumes_allocd;
+    int volume_count;
+} MountsState;
+
+static MountsState g_mounts_state = {
+    NULL,   // volumes
+    0,      // volumes_allocd
+    0       // volume_count
+};
+
+static inline void
+free_volume_internals(const MountedVolume *volume, int zero)
+{
+    free((char *)volume->device);
+    free((char *)volume->mount_point);
+    free((char *)volume->filesystem);
+    free((char *)volume->flags);
+    if (zero) {
+        memset((void *)volume, 0, sizeof(*volume));
+    }
+}
+
+#define PROC_MOUNTS_FILENAME   "/proc/mounts"
+
+int
+scan_mounted_volumes()
+{
+    char buf[2048];
+    const char *bufp;
+    int fd;
+    ssize_t nbytes;
+
+    if (g_mounts_state.volumes == NULL) {
+        const int numv = 32;
+        MountedVolume *volumes = malloc(numv * sizeof(*volumes));
+        if (volumes == NULL) {
+            errno = ENOMEM;
+            return -1;
+        }
+        g_mounts_state.volumes = volumes;
+        g_mounts_state.volumes_allocd = numv;
+        memset(volumes, 0, numv * sizeof(*volumes));
+    } else {
+        /* Free the old volume strings.
+         */
+        int i;
+        for (i = 0; i < g_mounts_state.volume_count; i++) {
+            free_volume_internals(&g_mounts_state.volumes[i], 1);
+        }
+    }
+    g_mounts_state.volume_count = 0;
+
+    /* Open and read the file contents.
+     */
+    fd = open(PROC_MOUNTS_FILENAME, O_RDONLY);
+    if (fd < 0) {
+        goto bail;
+    }
+    nbytes = read(fd, buf, sizeof(buf) - 1);
+    close(fd);
+    if (nbytes < 0) {
+        goto bail;
+    }
+    buf[nbytes] = '\0';
+
+    /* Parse the contents of the file, which looks like:
+     *
+     *     # cat /proc/mounts
+     *     rootfs / rootfs rw 0 0
+     *     /dev/pts /dev/pts devpts rw 0 0
+     *     /proc /proc proc rw 0 0
+     *     /sys /sys sysfs rw 0 0
+     *     /dev/block/mtdblock4 /system yaffs2 rw,nodev,noatime,nodiratime 0 0
+     *     /dev/block/mtdblock5 /data yaffs2 rw,nodev,noatime,nodiratime 0 0
+     *     /dev/block/mmcblk0p1 /sdcard vfat rw,sync,dirsync,fmask=0000,dmask=0000,codepage=cp437,iocharset=iso8859-1,utf8 0 0
+     *
+     * The zeroes at the end are dummy placeholder fields to make the
+     * output match Linux's /etc/mtab, but don't represent anything here.
+     */
+    bufp = buf;
+    while (nbytes > 0) {
+        char device[64];
+        char mount_point[64];
+        char filesystem[64];
+        char flags[128];
+        int matches;
+
+        /* %as is a gnu extension that malloc()s a string for each field.
+         */
+        matches = sscanf(bufp, "%63s %63s %63s %127s",
+                device, mount_point, filesystem, flags);
+
+        if (matches == 4) {
+            device[sizeof(device)-1] = '\0';
+            mount_point[sizeof(mount_point)-1] = '\0';
+            filesystem[sizeof(filesystem)-1] = '\0';
+            flags[sizeof(flags)-1] = '\0';
+
+            MountedVolume *v =
+                    &g_mounts_state.volumes[g_mounts_state.volume_count++];
+            v->device = strdup(device);
+            v->mount_point = strdup(mount_point);
+            v->filesystem = strdup(filesystem);
+            v->flags = strdup(flags);
+        } else {
+printf("matches was %d on <<%.40s>>\n", matches, bufp);
+        }
+
+        /* Eat the line.
+         */
+        while (nbytes > 0 && *bufp != '\n') {
+            bufp++;
+            nbytes--;
+        }
+        if (nbytes > 0) {
+            bufp++;
+            nbytes--;
+        }
+    }
+
+    return 0;
+
+bail:
+//TODO: free the strings we've allocated.
+    g_mounts_state.volume_count = 0;
+    return -1;
+}
+
+const MountedVolume *
+find_mounted_volume_by_device(const char *device)
+{
+    if (g_mounts_state.volumes != NULL) {
+        int i;
+        for (i = 0; i < g_mounts_state.volume_count; i++) {
+            MountedVolume *v = &g_mounts_state.volumes[i];
+            /* May be null if it was unmounted and we haven't rescanned.
+             */
+            if (v->device != NULL) {
+                if (strcmp(v->device, device) == 0) {
+                    return v;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+const MountedVolume *
+find_mounted_volume_by_mount_point(const char *mount_point)
+{
+    if (g_mounts_state.volumes != NULL) {
+        int i;
+        for (i = 0; i < g_mounts_state.volume_count; i++) {
+            MountedVolume *v = &g_mounts_state.volumes[i];
+            /* May be null if it was unmounted and we haven't rescanned.
+             */
+            if (v->mount_point != NULL) {
+                if (strcmp(v->mount_point, mount_point) == 0) {
+                    return v;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+int
+unmount_mounted_volume(const MountedVolume *volume)
+{
+    /* Intentionally pass NULL to umount if the caller tries
+     * to unmount a volume they already unmounted using this
+     * function.
+     */
+    int ret = umount(volume->mount_point);
+    if (ret == 0) {
+        free_volume_internals(volume, 1);
+        return 0;
+    }
+    return ret;
+}
+
+int
+remount_read_only(const MountedVolume* volume)
+{
+    return mount(volume->device, volume->mount_point, volume->filesystem,
+                 MS_NOATIME | MS_NODEV | MS_NODIRATIME |
+                 MS_RDONLY | MS_REMOUNT, 0);
+}
diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk
new file mode 100644
index 0000000..b952e36
--- /dev/null
+++ b/mtdutils/Android.mk
@@ -0,0 +1,69 @@
+ifneq ($(TARGET_SIMULATOR),true)
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	mtdutils.c \
+	mounts.c
+
+ifneq ($(filter rk30xx rk3188,$(TARGET_BOARD_PLATFORM)),)
+LOCAL_SRC_FILES += rk3xhack.c
+LOCAL_CFLAGS += -DRK3X
+endif
+
+ifeq ($(TARGET_MTD_BY_NAME),true)
+LOCAL_CFLAGS += -DBYNAME
+endif
+
+LOCAL_MODULE := libmtdutils
+LOCAL_STATIC_LIBRARIES := libcutils libc
+LOCAL_CLANG := true
+
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	mtdutils.c \
+	mounts.c
+
+ifneq ($(filter rk30xx rk3188,$(TARGET_BOARD_PLATFORM)),)
+LOCAL_SRC_FILES += rk3xhack.c
+LOCAL_CFLAGS += -DRK3X
+endif
+
+ifeq ($(TARGET_MTD_BY_NAME),true)
+LOCAL_CFLAGS += -DBYNAME
+endif
+
+LOCAL_MODULE := libmtdutils
+LOCAL_SHARED_LIBRARIES := libcutils libc
+LOCAL_CLANG := true
+
+include $(BUILD_SHARED_LIBRARY)
+
+ifeq ($(BOARD_USES_BML_OVER_MTD),true)
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := bml_over_mtd.c
+LOCAL_C_INCLUDES += $(commands_recovery_local_path)/mtdutils
+LOCAL_MODULE := libbml_over_mtd
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS += -Dmain=bml_over_mtd_main
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := bml_over_mtd.c
+LOCAL_MODULE := bml_over_mtd
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities
+LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities
+LOCAL_MODULE_STEM := bml_over_mtd
+LOCAL_C_INCLUDES += $(commands_recovery_local_path)/mtdutils
+LOCAL_SHARED_LIBRARIES := libmtdutils libcutils liblog libc
+include $(BUILD_EXECUTABLE)
+endif
+
+endif	# !TARGET_SIMULATOR
+
diff --git a/mtdutils/bml_over_mtd.c b/mtdutils/bml_over_mtd.c
new file mode 100644
index 0000000..12ca109
--- /dev/null
+++ b/mtdutils/bml_over_mtd.c
@@ -0,0 +1,810 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <limits.h>
+
+#include "cutils/log.h"
+
+#include <mtd/mtd-user.h>
+
+#include "mtdutils.h"
+
+#ifdef RK3X
+    #include "rk3xhack.h"
+#endif
+
+typedef struct BmlOverMtdReadContext {
+	const MtdPartition *partition;
+	char *buffer;
+	size_t consumed;
+	int fd;
+} BmlOverMtdReadContext;
+
+typedef struct BmlOverMtdWriteContext {
+	const MtdPartition *partition;
+	char *buffer;
+	size_t stored;
+	int fd;
+
+	off_t* bad_block_offsets;
+	int bad_block_alloc;
+	int bad_block_count;
+} BmlOverMtdWriteContext;
+
+
+static BmlOverMtdReadContext *bml_over_mtd_read_partition(const MtdPartition *partition)
+{
+	BmlOverMtdReadContext *ctx = (BmlOverMtdReadContext*) malloc(sizeof(BmlOverMtdReadContext));
+	if (ctx == NULL) return NULL;
+
+	ctx->buffer = malloc(partition->erase_size);
+	if (ctx->buffer == NULL) {
+		free(ctx);
+		return NULL;
+	}
+
+	char mtddevname[32];
+	sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+	ctx->fd = open(mtddevname, O_RDONLY);
+	if (ctx->fd < 0) {
+		free(ctx);
+		free(ctx->buffer);
+		return NULL;
+	}
+
+	ctx->partition = partition;
+	ctx->consumed = partition->erase_size;
+	return ctx;
+}
+
+static void bml_over_mtd_read_close(BmlOverMtdReadContext *ctx)
+{
+	close(ctx->fd);
+	free(ctx->buffer);
+	free(ctx);
+}
+
+static BmlOverMtdWriteContext *bml_over_mtd_write_partition(const MtdPartition *partition)
+{
+	BmlOverMtdWriteContext *ctx = (BmlOverMtdWriteContext*) malloc(sizeof(BmlOverMtdWriteContext));
+	if (ctx == NULL) return NULL;
+
+	ctx->bad_block_offsets = NULL;
+	ctx->bad_block_alloc = 0;
+	ctx->bad_block_count = 0;
+
+	ctx->buffer = malloc(partition->erase_size);
+	if (ctx->buffer == NULL) {
+		free(ctx);
+		return NULL;
+	}
+
+	char mtddevname[32];
+	sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+	ctx->fd = open(mtddevname, O_RDWR);
+	if (ctx->fd < 0) {
+		free(ctx->buffer);
+		free(ctx);
+		return NULL;
+	}
+
+	ctx->partition = partition;
+	ctx->stored = 0;
+	return ctx;
+}
+
+static int bml_over_mtd_write_close(BmlOverMtdWriteContext *ctx)
+{
+	int r = 0;
+	if (close(ctx->fd)) r = -1;
+	free(ctx->bad_block_offsets);
+	free(ctx->buffer);
+	free(ctx);
+	return r;
+}
+
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+
+#define LOG_TAG "bml_over_mtd"
+
+#define BLOCK_SIZE    2048
+#define SPARE_SIZE    (BLOCK_SIZE >> 5)
+
+#define EXIT_CODE_BAD_BLOCKS 15
+
+static int die(const char *msg, ...) {
+	int err = errno;
+	va_list args;
+	va_start(args, msg);
+	char buf[1024];
+	vsnprintf(buf, sizeof(buf), msg, args);
+	va_end(args);
+
+	if (err != 0) {
+		strlcat(buf, ": ", sizeof(buf));
+		strlcat(buf, strerror(err), sizeof(buf));
+	}
+
+	fprintf(stderr, "%s\n", buf);
+	return 1;
+}
+
+static unsigned short* CreateEmptyBlockMapping(const MtdPartition* pSrcPart)
+{
+	size_t srcTotal, srcErase, srcWrite;
+	if (mtd_partition_info(pSrcPart, &srcTotal, &srcErase, &srcWrite) != 0)
+	{
+		fprintf(stderr, "Failed to access partition.\n");
+		return NULL;
+	}
+
+	int numSrcBlocks = srcTotal/srcErase;
+
+	unsigned short* pMapping = malloc(numSrcBlocks * sizeof(unsigned short));
+	if (pMapping == NULL)
+	{
+		fprintf(stderr, "Failed to allocate block mapping memory.\n");
+		return NULL;
+	}
+	memset(pMapping, 0xFF, numSrcBlocks * sizeof(unsigned short));
+	return pMapping;
+}
+
+static const unsigned short* CreateBlockMapping(const MtdPartition* pSrcPart, int srcPartStartBlock,
+		const MtdPartition *pReservoirPart, int reservoirPartStartBlock)
+{
+	size_t srcTotal, srcErase, srcWrite;
+	if (mtd_partition_info(pSrcPart, &srcTotal, &srcErase, &srcWrite) != 0)
+	{
+		fprintf(stderr, "Failed to access partition.\n");
+		return NULL;
+	}
+
+	int numSrcBlocks = srcTotal/srcErase;
+
+	unsigned short* pMapping = malloc(numSrcBlocks * sizeof(unsigned short));
+	if (pMapping == NULL)
+	{
+		fprintf(stderr, "Failed to allocate block mapping memory.\n");
+		return NULL;
+	}
+	memset(pMapping, 0xFF, numSrcBlocks * sizeof(unsigned short));
+
+	size_t total, erase, write;
+	if (mtd_partition_info(pReservoirPart, &total, &erase, &write) != 0)
+	{
+		fprintf(stderr, "Failed to access reservoir partition.\n");
+		free(pMapping);
+		return NULL;
+	}
+
+	if (erase != srcErase || write != srcWrite)
+	{
+		fprintf(stderr, "Source partition and reservoir partition differ in size properties.\n");
+		free(pMapping);
+		return NULL;
+	}
+
+	printf("Partition info: Total %d, Erase %d, write %d\n", total, erase, write);
+
+	BmlOverMtdReadContext *readctx = bml_over_mtd_read_partition(pReservoirPart);
+	if (readctx == NULL)
+	{
+		fprintf(stderr, "Failed to open reservoir partition for reading.\n");
+		free(pMapping);
+		return NULL;
+	}
+
+	if (total < erase || total > INT_MAX)
+	{
+		fprintf(stderr, "Unsuitable reservoir partition properties.\n");
+		free(pMapping);
+		bml_over_mtd_read_close(readctx);
+		return NULL;
+	}
+
+	int foundMappingTable = 0;
+
+	int currOffset = total; //Offset *behind* the last byte
+	while (currOffset > 0)
+	{
+		currOffset -= erase;
+		loff_t pos = lseek64(readctx->fd, currOffset, SEEK_SET);
+		int mgbb = ioctl(readctx->fd, MEMGETBADBLOCK, &pos);
+		if (mgbb != 0)
+		{
+			printf("Bad block %d in reservoir area, skipping.\n", currOffset/erase);
+			continue;
+		}
+		ssize_t readBytes = read(readctx->fd, readctx->buffer, erase);
+		if (readBytes != (ssize_t)erase)
+		{
+			fprintf(stderr, "Failed to read good block in reservoir area (%s).\n",
+					strerror(errno));
+			free(pMapping);
+			bml_over_mtd_read_close(readctx);
+			return NULL;
+		}
+		if (readBytes >= 0x2000)
+		{
+			char* buf = readctx->buffer;
+			if (buf[0]=='U' && buf[1]=='P' && buf[2]=='C' && buf[3]=='H')
+			{
+				printf ("Found mapping block mark at 0x%x (block %d).\n", currOffset, currOffset/erase);
+
+				unsigned short* mappings = (unsigned short*) &buf[0x1000];
+				if (mappings[0]==0 && mappings[1]==0xffff)
+				{
+					printf("Found start of mapping table.\n");
+					foundMappingTable = 1;
+					//Skip first entry (dummy)
+					unsigned short* mappingEntry = mappings + 2;
+					while (mappingEntry - mappings < 100
+							&& mappingEntry[0] != 0xffff)
+					{
+						unsigned short rawSrcBlk = mappingEntry[0];
+						unsigned short rawDstBlk = mappingEntry[1];
+
+						printf("Found raw block mapping %d -> %d\n", rawSrcBlk,
+								rawDstBlk);
+
+						unsigned int srcAbsoluteStartAddress = srcPartStartBlock * erase;
+						unsigned int resAbsoluteStartAddress = reservoirPartStartBlock * erase;
+
+						int reservoirLastBlock = reservoirPartStartBlock + numSrcBlocks - 1;
+						if (rawDstBlk < reservoirPartStartBlock
+								|| rawDstBlk*erase >= resAbsoluteStartAddress+currOffset)
+						{
+							fprintf(stderr, "Mapped block not within reasonable reservoir area.\n");
+							foundMappingTable = 0;
+							break;
+						}
+
+						int srcLastBlock = srcPartStartBlock + numSrcBlocks - 1;
+						if (rawSrcBlk >= srcPartStartBlock && rawSrcBlk <= srcLastBlock)
+						{
+
+							unsigned short relSrcBlk = rawSrcBlk - srcPartStartBlock;
+							unsigned short relDstBlk = rawDstBlk - reservoirPartStartBlock;
+							printf("Partition relative block mapping %d -> %d\n",relSrcBlk, relDstBlk);
+
+							printf("Absolute mapped start addresses 0x%x -> 0x%x\n",
+									srcAbsoluteStartAddress+relSrcBlk*erase,
+									resAbsoluteStartAddress+relDstBlk*erase);
+							printf("Partition relative mapped start addresses 0x%x -> 0x%x\n",
+									relSrcBlk*erase, relDstBlk*erase);
+
+							//Set mapping entry. For duplicate entries, later entries replace former ones.
+							//*Assumption*: Bad blocks in reservoir area will not be mapped themselves in
+							//the mapping table. User partition blocks will not be mapped to bad blocks
+							//(only) in the reservoir area. This has to be confirmed on a wider range of
+							//devices.
+							pMapping[relSrcBlk] = relDstBlk;
+
+						}
+						mappingEntry+=2;
+					}
+					break; //We found the mapping table, no need to search further
+				}
+
+
+			}
+		}
+
+	}
+	bml_over_mtd_read_close(readctx);
+
+	if (foundMappingTable == 0)
+	{
+		fprintf(stderr, "Cannot find mapping table in reservoir partition.\n");
+		free(pMapping);
+		return NULL;
+	}
+
+	//Consistency and validity check
+	int mappingValid = 1;
+	readctx = bml_over_mtd_read_partition(pSrcPart);
+	if (readctx == NULL)
+	{
+		fprintf(stderr, "Cannot open source partition for reading.\n");
+		free(pMapping);
+		return NULL;
+	}
+	int currBlock = 0;
+	for (;currBlock < numSrcBlocks; ++currBlock)
+	{
+		loff_t pos = lseek64(readctx->fd, currBlock*erase, SEEK_SET);
+		int mgbb = ioctl(readctx->fd, MEMGETBADBLOCK, &pos);
+		if (mgbb == 0)
+		{
+			if (pMapping[currBlock]!=0xffff)
+			{
+				fprintf(stderr, "Consistency error: Good block has mapping entry %d -> %d\n", currBlock, pMapping[currBlock]);
+				mappingValid = 0;
+			}
+		} else
+		{
+			//Bad block!
+			if (pMapping[currBlock]==0xffff)
+			{
+				fprintf(stderr, "Consistency error: Bad block has no mapping entry \n");
+				mappingValid = 0;
+			} else
+			{
+				BmlOverMtdReadContext* reservoirReadCtx = bml_over_mtd_read_partition(pReservoirPart);
+				if (reservoirReadCtx == 0)
+				{
+					fprintf(stderr, "Reservoir partition cannot be opened for reading in consistency check.\n");
+					mappingValid = 0;
+				} else
+				{
+					pos = lseek64(reservoirReadCtx->fd, pMapping[currBlock]*erase, SEEK_SET);
+					mgbb = ioctl(reservoirReadCtx->fd, MEMGETBADBLOCK, &pos);
+					if (mgbb == 0)
+					{
+						printf("Bad block has properly mapped reservoir block %d -> %d\n",currBlock, pMapping[currBlock]);
+					}
+					else
+					{
+						fprintf(stderr, "Consistency error: Mapped block is bad, too. (%d -> %d)\n",currBlock, pMapping[currBlock]);
+						mappingValid = 0;
+					}
+
+				}
+				bml_over_mtd_read_close(reservoirReadCtx);
+			}
+
+		}
+
+	}
+	bml_over_mtd_read_close(readctx);
+
+
+	if (!mappingValid)
+	{
+		free(pMapping);
+		return NULL;
+	}
+
+	return pMapping;
+}
+
+static void ReleaseBlockMapping(const unsigned short* blockMapping)
+{
+	free((void*)blockMapping);
+}
+
+static int dump_bml_partition(const MtdPartition* pSrcPart, const MtdPartition* pReservoirPart,
+		const unsigned short* blockMapping, const char* filename)
+{
+	int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+	if (fd < 0)
+	{
+		fprintf(stderr, "error opening %s", filename);
+		return -1;
+	}
+	BmlOverMtdReadContext* pSrcRead = bml_over_mtd_read_partition(pSrcPart);
+	if (pSrcRead == NULL)
+	{
+		close(fd);
+		fprintf(stderr, "dump_bml_partition: Error opening src part for reading.\n");
+		return -1;
+	}
+
+	BmlOverMtdReadContext* pResRead = bml_over_mtd_read_partition(pReservoirPart);
+	if (pResRead == NULL)
+	{
+		close(fd);
+		bml_over_mtd_read_close(pSrcRead);
+		fprintf(stderr, "dump_bml_partition: Error opening reservoir part for reading.\n");
+		return -1;
+	}
+
+
+	int numBlocks = pSrcPart->size / pSrcPart->erase_size;
+	int currblock = 0;
+	for (;currblock < numBlocks; ++currblock)
+	{
+		int srcFd = -1;
+		if (blockMapping[currblock] == 0xffff)
+		{
+			//Good block, use src partition
+			srcFd = pSrcRead->fd;
+			if (lseek64(pSrcRead->fd, currblock*pSrcPart->erase_size, SEEK_SET)==-1)
+			{
+				close(fd);
+				bml_over_mtd_read_close(pSrcRead);
+				bml_over_mtd_read_close(pResRead);
+				fprintf(stderr, "dump_bml_partition: lseek in src partition failed\n");
+				return -1;
+			}
+		} else
+		{
+			//Bad block, use mapped block in reservoir partition
+			srcFd = pResRead->fd;
+			if (lseek64(pResRead->fd, blockMapping[currblock]*pSrcPart->erase_size, SEEK_SET)==-1)
+			{
+				close(fd);
+				bml_over_mtd_read_close(pSrcRead);
+				bml_over_mtd_read_close(pResRead);
+				fprintf(stderr, "dump_bml_partition: lseek in reservoir partition failed\n");
+				return -1;
+			}
+		}
+		size_t blockBytesRead = 0;
+		while (blockBytesRead < pSrcPart->erase_size)
+		{
+			ssize_t len = read(srcFd, pSrcRead->buffer + blockBytesRead,
+					pSrcPart->erase_size - blockBytesRead);
+			if (len <= 0)
+			{
+				close(fd);
+				bml_over_mtd_read_close(pSrcRead);
+				bml_over_mtd_read_close(pResRead);
+				fprintf(stderr, "dump_bml_partition: reading partition failed\n");
+				return -1;
+			}
+			blockBytesRead += len;
+		}
+
+		size_t blockBytesWritten = 0;
+		while (blockBytesWritten < pSrcPart->erase_size)
+		{
+			ssize_t len = write(fd, pSrcRead->buffer + blockBytesWritten,
+					pSrcPart->erase_size - blockBytesWritten);
+			if (len <= 0)
+			{
+				close(fd);
+				bml_over_mtd_read_close(pSrcRead);
+				bml_over_mtd_read_close(pResRead);
+				fprintf(stderr, "dump_bml_partition: writing partition dump file failed\n");
+				return -1;
+			}
+			blockBytesWritten += len;
+		}
+
+	}
+
+	bml_over_mtd_read_close(pSrcRead);
+	bml_over_mtd_read_close(pResRead);
+
+	if (close(fd)) {
+		unlink(filename);
+		printf("error closing %s", filename);
+		return -1;
+	}
+
+	return 0;
+}
+
+static ssize_t bml_over_mtd_write_block(int fd, ssize_t erase_size, char* data)
+{
+	off_t pos = lseek(fd, 0, SEEK_CUR);
+	if (pos == (off_t) -1) return -1;
+
+	ssize_t size = erase_size;
+	loff_t bpos = pos;
+	int ret = ioctl(fd, MEMGETBADBLOCK, &bpos);
+	if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) {
+		fprintf(stderr,
+				"Mapping failure: Trying to write bad block at 0x%08lx (ret %d errno %d)\n",
+				pos, ret, errno);
+		return -1;
+	}
+
+	struct erase_info_user erase_info;
+	erase_info.start = pos;
+	erase_info.length = size;
+	int retry;
+	for (retry = 0; retry < 2; ++retry) {
+#ifdef RK3X
+		if (rk30_zero_out(fd, pos, size) < 0) {
+			fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n",
+					pos, strerror(errno));
+			continue;
+		}
+#else
+		if (ioctl(fd, MEMERASE, &erase_info) < 0) {
+			fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n",
+					pos, strerror(errno));
+			continue;
+		}
+#endif
+		if (lseek(fd, pos, SEEK_SET) != pos ||
+				write(fd, data, size) != size) {
+			fprintf(stderr, "mtd: write error at 0x%08lx (%s)\n",
+					pos, strerror(errno));
+		}
+
+		char verify[size];
+		if (lseek(fd, pos, SEEK_SET) != pos ||
+				read(fd, verify, size) != size) {
+			fprintf(stderr, "mtd: re-read error at 0x%08lx (%s)\n",
+					pos, strerror(errno));
+			continue;
+		}
+		if (memcmp(data, verify, size) != 0) {
+			fprintf(stderr, "mtd: verification error at 0x%08lx (%s)\n",
+					pos, strerror(errno));
+			continue;
+		}
+
+		if (retry > 0) {
+			fprintf(stderr, "mtd: wrote block after %d retries\n", retry);
+		}
+		fprintf(stderr, "mtd: successfully wrote block at %llx\n", pos);
+		return size;  // Success!
+	}
+
+
+	fprintf(stderr, "mtd: Block at %llx could not be properly written.\n", pos);
+	// Ran out of space on the device
+	errno = ENOSPC;
+	return -1;
+}
+
+static int flash_bml_partition(const MtdPartition* pSrcPart, const MtdPartition* pReservoirPart,
+		const unsigned short* blockMapping, const char* filename)
+{
+	int fd = open(filename, O_RDONLY);
+	if (fd < 0)
+	{
+		fprintf(stderr, "error opening %s", filename);
+		return -1;
+	}
+	BmlOverMtdWriteContext* pSrcWrite = bml_over_mtd_write_partition(pSrcPart);
+	if (pSrcWrite == NULL)
+	{
+		close(fd);
+		fprintf(stderr, "flash_bml_partition: Error opening src part for writing.\n");
+		return -1;
+	}
+
+#ifdef DUMMY_WRITING
+	close(pSrcWrite->fd);
+	pSrcWrite->fd = open("/sdcard/srcPartWriteDummy.bin", O_WRONLY|O_CREAT|O_TRUNC, 0666);
+#endif
+
+	BmlOverMtdWriteContext* pResWrite = bml_over_mtd_write_partition(pReservoirPart);
+	if (pResWrite == NULL)
+	{
+		close(fd);
+		bml_over_mtd_write_close(pSrcWrite);
+		fprintf(stderr, "flash_bml_partition: Error opening reservoir part for writing.\n");
+		return -1;
+	}
+#ifdef DUMMY_WRITING
+	close(pResWrite->fd);
+	pResWrite->fd = open("/sdcard/resPartWriteDummy.bin", O_WRONLY|O_CREAT|O_TRUNC, 0666);
+#endif
+
+	struct stat fileStat;
+	if (fstat(fd, &fileStat) != 0)
+	{
+		close(fd);
+		bml_over_mtd_write_close(pSrcWrite);
+		bml_over_mtd_write_close(pResWrite);
+		fprintf(stderr, "flash_bml_partition: Failed to stat source file.\n");
+		return -1;
+
+	}
+	if (fileStat.st_size > pSrcPart->size)
+	{
+		close(fd);
+		bml_over_mtd_write_close(pSrcWrite);
+		bml_over_mtd_write_close(pResWrite);
+		fprintf(stderr, "flash_bml_partition: Source file too large for target partition.\n");
+		return -1;
+	}
+
+	int numBlocks = (fileStat.st_size +  pSrcPart->erase_size - 1) / pSrcPart->erase_size;
+	int currblock;
+	for (currblock = 0 ;currblock < numBlocks; ++currblock)
+	{
+		memset(pSrcWrite->buffer, 0xFF, pSrcPart->erase_size);
+		size_t blockBytesRead = 0;
+		while (blockBytesRead < pSrcPart->erase_size)
+		{
+			ssize_t len = read(fd, pSrcWrite->buffer + blockBytesRead,
+					pSrcPart->erase_size - blockBytesRead);
+			if (len < 0)
+			{
+				close(fd);
+				bml_over_mtd_write_close(pSrcWrite);
+				bml_over_mtd_write_close(pResWrite);
+				fprintf(stderr, "flash_bml_partition: read source file failed\n");
+				return -1;
+			}
+			if (len == 0)
+			{
+				//End of file
+				break;
+			}
+
+			blockBytesRead += len;
+		}
+
+
+
+		int srcFd = -1;
+		if (blockMapping[currblock] == 0xffff)
+		{
+			//Good block, use src partition
+			srcFd = pSrcWrite->fd;
+			if (lseek64(pSrcWrite->fd, currblock*pSrcPart->erase_size, SEEK_SET)==-1)
+			{
+				close(fd);
+				bml_over_mtd_write_close(pSrcWrite);
+				bml_over_mtd_write_close(pResWrite);
+				fprintf(stderr, "flash_bml_partition: lseek in src partition failed\n");
+				return -1;
+			}
+		} else
+		{
+			//Bad block, use mapped block in reservoir partition
+			srcFd = pResWrite->fd;
+			if (lseek64(pResWrite->fd, blockMapping[currblock]*pSrcPart->erase_size, SEEK_SET)==-1)
+			{
+				close(fd);
+				bml_over_mtd_write_close(pSrcWrite);
+				bml_over_mtd_write_close(pResWrite);
+				fprintf(stderr, "flash_bml_partition: lseek in reservoir partition failed\n");
+				return -1;
+			}
+		}
+		size_t blockBytesWritten = 0;
+		while (blockBytesWritten < pSrcPart->erase_size)
+		{
+#ifdef DUMMY_WRITING
+			ssize_t len = write(srcFd, pSrcWrite->buffer + blockBytesWritten,
+					pSrcPart->erase_size - blockBytesWritten);
+#else
+			ssize_t len = bml_over_mtd_write_block(srcFd, pSrcPart->erase_size, pSrcWrite->buffer);
+#endif
+			if (len <= 0)
+			{
+				close(fd);
+				bml_over_mtd_write_close(pSrcWrite);
+				bml_over_mtd_write_close(pResWrite);
+				fprintf(stderr, "flash_bml_partition: writing to partition failed\n");
+				return -1;
+			}
+			blockBytesWritten += len;
+		}
+
+
+	}
+
+	bml_over_mtd_write_close(pSrcWrite);
+	bml_over_mtd_write_close(pResWrite);
+
+	if (close(fd)) {
+		printf("error closing %s", filename);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int scan_partition(const MtdPartition* pPart)
+{
+	BmlOverMtdReadContext* readCtx = bml_over_mtd_read_partition(pPart);
+	if (readCtx == NULL)
+	{
+		fprintf(stderr, "Failed to open partition for reading.\n");
+		return -1;
+	}
+
+	int numBadBlocks = 0;
+	size_t numBlocks = pPart->size / pPart->erase_size;
+	size_t currBlock;
+	for (currBlock = 0; currBlock < numBlocks; ++currBlock)
+	{
+
+		loff_t pos = currBlock * pPart->erase_size;
+		int mgbb = ioctl(readCtx->fd, MEMGETBADBLOCK, &pos);
+		if (mgbb != 0)
+		{
+			printf("Bad block %d at 0x%x.\n", currBlock, (unsigned int)pos);
+			numBadBlocks++;
+		}
+	}
+
+	bml_over_mtd_read_close(readCtx);
+	if (numBadBlocks == 0)
+	{
+		printf("No bad blocks.\n");
+		return 0;
+	}
+	return -1 ;
+}
+
+int main(int argc, char **argv)
+{
+	if (argc != 7 && (argc != 3 || (argc == 3 && strcmp(argv[1],"scan"))!=0)
+			&& (argc != 6 || (argc == 6 && strcmp(argv[1],"scan"))!=0))
+		return die("Usage: %s dump|flash <partition> <partition_start_block> <reservoirpartition> <reservoir_start_block> <file>\n"
+				"E.g. %s dump boot 72 reservoir 2004 file.bin\n"
+				"Usage: %s scan <partition> [<partition_start_block> <reservoirpartition> <reservoir_start_block>]\n"
+				,argv[0], argv[0], argv[0]);
+	int num_partitions = mtd_scan_partitions();
+	const MtdPartition *pSrcPart = mtd_find_partition_by_name(argv[2]);
+	if (pSrcPart == NULL)
+		return die("Cannot find partition %s", argv[2]);
+
+	int scanResult = scan_partition(pSrcPart);
+
+	if (argc == 3 && strcmp(argv[1],"scan")==0)
+	{
+		return (scanResult == 0 ? 0 : EXIT_CODE_BAD_BLOCKS);
+	}
+
+	int retVal = 0;
+	const MtdPartition* pReservoirPart = mtd_find_partition_by_name(argv[4]);
+	if (pReservoirPart == NULL)
+		return die("Cannot find partition %s", argv[4]);
+
+	int srcPartStartBlock = atoi(argv[3]);
+	int reservoirPartStartBlock = atoi(argv[5]);
+	const unsigned short* pMapping = CreateBlockMapping(pSrcPart, srcPartStartBlock,
+			pReservoirPart, reservoirPartStartBlock);
+
+	if (pMapping == NULL && scanResult == 0)
+	{
+		printf("Generating empty block mapping table for error-free partition.\n");
+		pMapping = CreateEmptyBlockMapping(pSrcPart);
+	}
+
+	if (argc == 6 && strcmp(argv[1],"scan")==0)
+	{
+		retVal = (scanResult == 0 ? 0 : EXIT_CODE_BAD_BLOCKS);
+	}
+
+	if (pMapping == NULL)
+		return die("Failed to create block mapping table");
+
+	if (strcmp(argv[1],"dump")==0)
+	{
+		retVal = dump_bml_partition(pSrcPart, pReservoirPart, pMapping, argv[6]);
+		if (retVal == 0)
+			printf("Successfully dumped partition to %s\n", argv[6]);
+	}
+
+	if (strcmp(argv[1],"flash")==0)
+	{
+		retVal = flash_bml_partition(pSrcPart, pReservoirPart, pMapping, argv[6]);
+		if (retVal == 0)
+			printf("Successfully wrote %s to partition\n", argv[6]);
+
+	}
+
+
+	ReleaseBlockMapping(pMapping);
+	return retVal;
+}
+
diff --git a/mtdutils/mounts.c b/mtdutils/mounts.c
new file mode 100644
index 0000000..cd3738a
--- /dev/null
+++ b/mtdutils/mounts.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/mount.h>
+
+#include "mounts.h"
+
+struct MountedVolume {
+    const char *device;
+    const char *mount_point;
+    const char *filesystem;
+    const char *flags;
+};
+
+typedef struct {
+    MountedVolume *volumes;
+    int volumes_allocd;
+    int volume_count;
+} MountsState;
+
+static MountsState g_mounts_state = {
+    NULL,   // volumes
+    0,      // volumes_allocd
+    0       // volume_count
+};
+
+static inline void
+free_volume_internals(const MountedVolume *volume, int zero)
+{
+    free((char *)volume->device);
+    free((char *)volume->mount_point);
+    free((char *)volume->filesystem);
+    free((char *)volume->flags);
+    if (zero) {
+        memset((void *)volume, 0, sizeof(*volume));
+    }
+}
+
+#define PROC_MOUNTS_FILENAME   "/proc/mounts"
+
+int
+scan_mounted_volumes()
+{
+    char buf[2048];
+    const char *bufp;
+    int fd;
+    ssize_t nbytes;
+
+    if (g_mounts_state.volumes == NULL) {
+        const int numv = 32;
+        MountedVolume *volumes = malloc(numv * sizeof(*volumes));
+        if (volumes == NULL) {
+            errno = ENOMEM;
+            return -1;
+        }
+        g_mounts_state.volumes = volumes;
+        g_mounts_state.volumes_allocd = numv;
+        memset(volumes, 0, numv * sizeof(*volumes));
+    } else {
+        /* Free the old volume strings.
+         */
+        int i;
+        for (i = 0; i < g_mounts_state.volume_count; i++) {
+            free_volume_internals(&g_mounts_state.volumes[i], 1);
+        }
+    }
+    g_mounts_state.volume_count = 0;
+
+    /* Open and read the file contents.
+     */
+    fd = open(PROC_MOUNTS_FILENAME, O_RDONLY);
+    if (fd < 0) {
+        goto bail;
+    }
+    nbytes = read(fd, buf, sizeof(buf) - 1);
+    close(fd);
+    if (nbytes < 0) {
+        goto bail;
+    }
+    buf[nbytes] = '\0';
+
+    /* Parse the contents of the file, which looks like:
+     *
+     *     # cat /proc/mounts
+     *     rootfs / rootfs rw 0 0
+     *     /dev/pts /dev/pts devpts rw 0 0
+     *     /proc /proc proc rw 0 0
+     *     /sys /sys sysfs rw 0 0
+     *     /dev/block/mtdblock4 /system yaffs2 rw,nodev,noatime,nodiratime 0 0
+     *     /dev/block/mtdblock5 /data yaffs2 rw,nodev,noatime,nodiratime 0 0
+     *     /dev/block/mmcblk0p1 /sdcard vfat rw,sync,dirsync,fmask=0000,dmask=0000,codepage=cp437,iocharset=iso8859-1,utf8 0 0
+     *
+     * The zeroes at the end are dummy placeholder fields to make the
+     * output match Linux's /etc/mtab, but don't represent anything here.
+     */
+    bufp = buf;
+    while (nbytes > 0) {
+        char device[64];
+        char mount_point[64];
+        char filesystem[64];
+        char flags[128];
+        int matches;
+
+        /* %as is a gnu extension that malloc()s a string for each field.
+         */
+        matches = sscanf(bufp, "%63s %63s %63s %127s",
+                device, mount_point, filesystem, flags);
+
+        if (matches == 4) {
+            device[sizeof(device)-1] = '\0';
+            mount_point[sizeof(mount_point)-1] = '\0';
+            filesystem[sizeof(filesystem)-1] = '\0';
+            flags[sizeof(flags)-1] = '\0';
+
+            MountedVolume *v =
+                    &g_mounts_state.volumes[g_mounts_state.volume_count++];
+            v->device = strdup(device);
+            v->mount_point = strdup(mount_point);
+            v->filesystem = strdup(filesystem);
+            v->flags = strdup(flags);
+        } else {
+printf("matches was %d on <<%.40s>>\n", matches, bufp);
+        }
+
+        /* Eat the line.
+         */
+        while (nbytes > 0 && *bufp != '\n') {
+            bufp++;
+            nbytes--;
+        }
+        if (nbytes > 0) {
+            bufp++;
+            nbytes--;
+        }
+    }
+
+    return 0;
+
+bail:
+//TODO: free the strings we've allocated.
+    g_mounts_state.volume_count = 0;
+    return -1;
+}
+
+const MountedVolume *
+find_mounted_volume_by_device(const char *device)
+{
+    if (g_mounts_state.volumes != NULL) {
+        int i;
+        for (i = 0; i < g_mounts_state.volume_count; i++) {
+            MountedVolume *v = &g_mounts_state.volumes[i];
+            /* May be null if it was unmounted and we haven't rescanned.
+             */
+            if (v->device != NULL) {
+                if (strcmp(v->device, device) == 0) {
+                    return v;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+const MountedVolume *
+find_mounted_volume_by_mount_point(const char *mount_point)
+{
+    if (g_mounts_state.volumes != NULL) {
+        int i;
+        for (i = 0; i < g_mounts_state.volume_count; i++) {
+            MountedVolume *v = &g_mounts_state.volumes[i];
+            /* May be null if it was unmounted and we haven't rescanned.
+             */
+            if (v->mount_point != NULL) {
+                if (strcmp(v->mount_point, mount_point) == 0) {
+                    return v;
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+int
+unmount_mounted_volume(const MountedVolume *volume)
+{
+    /* Intentionally pass NULL to umount if the caller tries
+     * to unmount a volume they already unmounted using this
+     * function.
+     */
+    int ret = umount(volume->mount_point);
+    if (ret == 0) {
+        free_volume_internals(volume, 1);
+        return 0;
+    }
+    return ret;
+}
+
+int
+remount_read_only(const MountedVolume* volume)
+{
+    return mount(volume->device, volume->mount_point, volume->filesystem,
+                 MS_NOATIME | MS_NODEV | MS_NODIRATIME |
+                 MS_RDONLY | MS_REMOUNT, 0);
+}
diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h
new file mode 100644
index 0000000..ed7fb5f
--- /dev/null
+++ b/mtdutils/mounts.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MTDUTILS_MOUNTS_H_
+#define MTDUTILS_MOUNTS_H_
+
+typedef struct {
+ const char *device;
+ const char *mount_point;
+ const char *filesystem;
+ const char *flags;
+} MountedVolume;
+
+int scan_mounted_volumes(void);
+
+const MountedVolume *find_mounted_volume_by_device(const char *device);
+
+const MountedVolume *
+find_mounted_volume_by_mount_point(const char *mount_point);
+
+int unmount_mounted_volume(const MountedVolume *volume);
+
+int remount_read_only(const MountedVolume* volume);
+
+#endif  // MTDUTILS_MOUNTS_H_
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
new file mode 100644
index 0000000..b19c533
--- /dev/null
+++ b/mtdutils/mtdutils.c
@@ -0,0 +1,805 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mount.h>  // for _IOW, _IOR, mount()
+#include <sys/stat.h>
+#include <mtd/mtd-user.h>
+#undef NDEBUG
+#include <assert.h>
+
+#include "mtdutils.h"
+
+#ifdef RK3X
+    #include "rk3xhack.h"
+#endif
+
+#ifdef BYNAME
+static const char mtdprefix[] = "/dev/block/mtd/by-name/";
+#define MTD_BASENAME_OFFSET (sizeof(mtdprefix)-1)
+#endif
+
+struct MtdReadContext {
+    const MtdPartition *partition;
+    char *buffer;
+    size_t consumed;
+    int fd;
+};
+
+struct MtdWriteContext {
+    const MtdPartition *partition;
+    char *buffer;
+    size_t stored;
+    int fd;
+
+    off_t* bad_block_offsets;
+    int bad_block_alloc;
+    int bad_block_count;
+};
+
+typedef struct {
+    MtdPartition *partitions;
+    int partitions_allocd;
+    int partition_count;
+} MtdState;
+
+static MtdState g_mtd_state = {
+    NULL,   // partitions
+    0,      // partitions_allocd
+    -1      // partition_count
+};
+
+#define MTD_PROC_FILENAME   "/proc/mtd"
+
+int
+mtd_scan_partitions()
+{
+    char buf[2048];
+    const char *bufp;
+    int fd;
+    int i;
+    ssize_t nbytes;
+
+    if (g_mtd_state.partitions == NULL) {
+        const int nump = 32;
+        MtdPartition *partitions = malloc(nump * sizeof(*partitions));
+        if (partitions == NULL) {
+            errno = ENOMEM;
+            return -1;
+        }
+        g_mtd_state.partitions = partitions;
+        g_mtd_state.partitions_allocd = nump;
+        memset(partitions, 0, nump * sizeof(*partitions));
+    }
+    g_mtd_state.partition_count = 0;
+
+    /* Initialize all of the entries to make things easier later.
+     * (Lets us handle sparsely-numbered partitions, which
+     * may not even be possible.)
+     */
+    for (i = 0; i < g_mtd_state.partitions_allocd; i++) {
+        MtdPartition *p = &g_mtd_state.partitions[i];
+        if (p->name != NULL) {
+            free(p->name);
+            p->name = NULL;
+        }
+        p->device_index = -1;
+    }
+
+    /* Open and read the file contents.
+     */
+    fd = open(MTD_PROC_FILENAME, O_RDONLY);
+    if (fd < 0) {
+        goto bail;
+    }
+    nbytes = TEMP_FAILURE_RETRY(read(fd, buf, sizeof(buf) - 1));
+    close(fd);
+    if (nbytes < 0) {
+        goto bail;
+    }
+    buf[nbytes] = '\0';
+
+    /* Parse the contents of the file, which looks like:
+     *
+     *     # cat /proc/mtd
+     *     dev:    size   erasesize  name
+     *     mtd0: 00080000 00020000 "bootloader"
+     *     mtd1: 00400000 00020000 "mfg_and_gsm"
+     *     mtd2: 00400000 00020000 "0000000c"
+     *     mtd3: 00200000 00020000 "0000000d"
+     *     mtd4: 04000000 00020000 "system"
+     *     mtd5: 03280000 00020000 "userdata"
+     */
+    bufp = buf;
+    while (nbytes > 0) {
+        int mtdnum, mtdsize, mtderasesize;
+        int matches;
+        char mtdname[64];
+        mtdname[0] = '\0';
+        mtdnum = -1;
+
+        matches = sscanf(bufp, "mtd%d: %x %x \"%63[^\"]",
+                &mtdnum, &mtdsize, &mtderasesize, mtdname);
+        /* This will fail on the first line, which just contains
+         * column headers.
+         */
+        if (matches == 4) {
+            MtdPartition *p = &g_mtd_state.partitions[mtdnum];
+            p->device_index = mtdnum;
+            p->size = mtdsize;
+            p->erase_size = mtderasesize;
+#ifdef BYNAME
+            asprintf(&p->name, "%s%s", mtdprefix, mtdname);
+#else
+            p->name = strdup(mtdname);
+#endif
+            if (p->name == NULL) {
+                errno = ENOMEM;
+                goto bail;
+            }
+            g_mtd_state.partition_count++;
+        }
+
+        /* Eat the line.
+         */
+        while (nbytes > 0 && *bufp != '\n') {
+            bufp++;
+            nbytes--;
+        }
+        if (nbytes > 0) {
+            bufp++;
+            nbytes--;
+        }
+    }
+
+    return g_mtd_state.partition_count;
+
+bail:
+    // keep "partitions" around so we can free the names on a rescan.
+    g_mtd_state.partition_count = -1;
+    return -1;
+}
+
+const MtdPartition *
+mtd_find_partition_by_name(const char *name)
+{
+    if (g_mtd_state.partitions != NULL) {
+        int i;
+        for (i = 0; i < g_mtd_state.partitions_allocd; i++) {
+            MtdPartition *p = &g_mtd_state.partitions[i];
+            if (p->device_index >= 0 && p->name != NULL) {
+                if (strcmp(p->name, name) == 0) {
+                    return p;
+                }
+#ifdef BYNAME
+                if (strcmp(p->name+MTD_BASENAME_OFFSET, name) == 0) {
+                    return p;
+                }
+#endif
+            }
+        }
+    }
+    return NULL;
+}
+
+int
+mtd_mount_partition(const MtdPartition *partition, const char *mount_point,
+        const char *filesystem, int read_only)
+{
+    const unsigned long flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME;
+    char devname[64];
+    int rv = -1;
+
+    sprintf(devname, "/dev/block/mtdblock%d", partition->device_index);
+    if (!read_only) {
+        rv = mount(devname, mount_point, filesystem, flags, NULL);
+    }
+    if (read_only || rv < 0) {
+        rv = mount(devname, mount_point, filesystem, flags | MS_RDONLY, 0);
+        if (rv < 0) {
+            printf("Failed to mount %s on %s: %s\n",
+                    devname, mount_point, strerror(errno));
+        } else {
+            printf("Mount %s on %s read-only\n", devname, mount_point);
+        }
+    }
+#if 1   //TODO: figure out why this is happening; remove include of stat.h
+    if (rv >= 0) {
+        /* For some reason, the x bits sometimes aren't set on the root
+         * of mounted volumes.
+         */
+        struct stat st;
+        rv = stat(mount_point, &st);
+        if (rv < 0) {
+            return rv;
+        }
+        mode_t new_mode = st.st_mode | S_IXUSR | S_IXGRP | S_IXOTH;
+        if (new_mode != st.st_mode) {
+printf("Fixing execute permissions for %s\n", mount_point);
+            rv = chmod(mount_point, new_mode);
+            if (rv < 0) {
+                printf("Couldn't fix permissions for %s: %s\n",
+                        mount_point, strerror(errno));
+            }
+        }
+    }
+#endif
+    return rv;
+}
+
+int
+mtd_partition_info(const MtdPartition *partition,
+        size_t *total_size, size_t *erase_size, size_t *write_size)
+{
+    char mtddevname[32];
+    sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+    int fd = open(mtddevname, O_RDONLY);
+    if (fd < 0) return -1;
+
+    struct mtd_info_user mtd_info;
+    int ret = ioctl(fd, MEMGETINFO, &mtd_info);
+    close(fd);
+    if (ret < 0) return -1;
+
+    if (total_size != NULL) *total_size = mtd_info.size;
+    if (erase_size != NULL) *erase_size = mtd_info.erasesize;
+    if (write_size != NULL) *write_size = mtd_info.writesize;
+    return 0;
+}
+
+MtdReadContext *mtd_read_partition(const MtdPartition *partition)
+{
+    MtdReadContext *ctx = (MtdReadContext*) malloc(sizeof(MtdReadContext));
+    if (ctx == NULL) return NULL;
+
+    ctx->buffer = malloc(partition->erase_size);
+    if (ctx->buffer == NULL) {
+        free(ctx);
+        return NULL;
+    }
+
+    char mtddevname[32];
+    sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+    ctx->fd = open(mtddevname, O_RDONLY);
+    if (ctx->fd < 0) {
+        free(ctx->buffer);
+        free(ctx);
+        return NULL;
+    }
+
+    ctx->partition = partition;
+    ctx->consumed = partition->erase_size;
+    return ctx;
+}
+
+static int read_block(const MtdPartition *partition, int fd, char *data)
+{
+    struct mtd_ecc_stats before, after;
+    if (ioctl(fd, ECCGETSTATS, &before)) {
+        printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno));
+        return -1;
+    }
+
+    loff_t pos = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR));
+    if (pos == -1) {
+        printf("mtd: read_block: couldn't SEEK_CUR: %s\n", strerror(errno));
+        return -1;
+    }
+
+    ssize_t size = partition->erase_size;
+    int mgbb;
+
+    while (pos + size <= (int) partition->size) {
+        if (TEMP_FAILURE_RETRY(lseek64(fd, pos, SEEK_SET)) != pos ||
+                    TEMP_FAILURE_RETRY(read(fd, data, size)) != size) {
+            printf("mtd: read error at 0x%08llx (%s)\n",
+                   (long long)pos, strerror(errno));
+        } else if (ioctl(fd, ECCGETSTATS, &after)) {
+            printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno));
+            return -1;
+        } else if (after.failed != before.failed) {
+            printf("mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n",
+                   after.corrected - before.corrected,
+                   after.failed - before.failed, (long long)pos);
+            // copy the comparison baseline for the next read.
+            memcpy(&before, &after, sizeof(struct mtd_ecc_stats));
+        } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) {
+            fprintf(stderr,
+                    "mtd: MEMGETBADBLOCK returned %d at 0x%08llx: %s\n",
+                    mgbb, (long long)pos, strerror(errno));
+        } else {
+            return 0;  // Success!
+        }
+
+        pos += partition->erase_size;
+    }
+
+    errno = ENOSPC;
+    return -1;
+}
+
+ssize_t mtd_read_data(MtdReadContext *ctx, char *data, size_t len)
+{
+    ssize_t read = 0;
+    while (read < (int) len) {
+        if (ctx->consumed < ctx->partition->erase_size) {
+            size_t avail = ctx->partition->erase_size - ctx->consumed;
+            size_t copy = len - read < avail ? len - read : avail;
+            memcpy(data + read, ctx->buffer + ctx->consumed, copy);
+            ctx->consumed += copy;
+            read += copy;
+        }
+
+        // Read complete blocks directly into the user's buffer
+        while (ctx->consumed == ctx->partition->erase_size &&
+               len - read >= ctx->partition->erase_size) {
+            if (read_block(ctx->partition, ctx->fd, data + read)) return -1;
+            read += ctx->partition->erase_size;
+        }
+
+        if (read >= (int)len) {
+            return read;
+        }
+
+        // Read the next block into the buffer
+        if (ctx->consumed == ctx->partition->erase_size && read < (int) len) {
+            if (read_block(ctx->partition, ctx->fd, ctx->buffer)) return -1;
+            ctx->consumed = 0;
+        }
+    }
+
+    return read;
+}
+
+void mtd_read_close(MtdReadContext *ctx)
+{
+    close(ctx->fd);
+    free(ctx->buffer);
+    free(ctx);
+}
+
+MtdWriteContext *mtd_write_partition(const MtdPartition *partition)
+{
+    MtdWriteContext *ctx = (MtdWriteContext*) malloc(sizeof(MtdWriteContext));
+    if (ctx == NULL) return NULL;
+
+    ctx->bad_block_offsets = NULL;
+    ctx->bad_block_alloc = 0;
+    ctx->bad_block_count = 0;
+
+    ctx->buffer = malloc(partition->erase_size);
+    if (ctx->buffer == NULL) {
+        free(ctx);
+        return NULL;
+    }
+
+    char mtddevname[32];
+    sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+    ctx->fd = open(mtddevname, O_RDWR);
+    if (ctx->fd < 0) {
+        free(ctx->buffer);
+        free(ctx);
+        return NULL;
+    }
+
+    ctx->partition = partition;
+    ctx->stored = 0;
+    return ctx;
+}
+
+static void add_bad_block_offset(MtdWriteContext *ctx, off_t pos) {
+    if (ctx->bad_block_count + 1 > ctx->bad_block_alloc) {
+        ctx->bad_block_alloc = (ctx->bad_block_alloc*2) + 1;
+        ctx->bad_block_offsets = realloc(ctx->bad_block_offsets,
+                                         ctx->bad_block_alloc * sizeof(off_t));
+    }
+    ctx->bad_block_offsets[ctx->bad_block_count++] = pos;
+}
+
+static int write_block(MtdWriteContext *ctx, const char *data)
+{
+    const MtdPartition *partition = ctx->partition;
+    int fd = ctx->fd;
+
+    off_t pos = TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_CUR));
+    if (pos == (off_t) -1) {
+        printf("mtd: write_block: couldn't SEEK_CUR: %s\n", strerror(errno));
+        return -1;
+    }
+
+    ssize_t size = partition->erase_size;
+    while (pos + size <= (int) partition->size) {
+        loff_t bpos = pos;
+        int ret = ioctl(fd, MEMGETBADBLOCK, &bpos);
+        if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) {
+            add_bad_block_offset(ctx, pos);
+            fprintf(stderr,
+                    "mtd: not writing bad block at 0x%08lx (ret %d): %s\n",
+                    pos, ret, strerror(errno));
+            pos += partition->erase_size;
+            continue;  // Don't try to erase known factory-bad blocks.
+        }
+
+        struct erase_info_user erase_info;
+        erase_info.start = pos;
+        erase_info.length = size;
+        int retry;
+        for (retry = 0; retry < 2; ++retry) {
+#ifdef RK3X
+            if (rk30_zero_out(fd, pos, size) < 0) {
+                fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n",
+                        pos, strerror(errno));
+                continue;
+            }
+#else
+            if (ioctl(fd, MEMERASE, &erase_info) < 0) {
+                printf("mtd: erase failure at 0x%08lx (%s)\n",
+                        pos, strerror(errno));
+                continue;
+            }
+#endif
+            if (TEMP_FAILURE_RETRY(lseek(fd, pos, SEEK_SET)) != pos ||
+                TEMP_FAILURE_RETRY(write(fd, data, size)) != size) {
+                printf("mtd: write error at 0x%08lx (%s)\n",
+                        pos, strerror(errno));
+            }
+
+            char verify[size];
+            if (TEMP_FAILURE_RETRY(lseek(fd, pos, SEEK_SET)) != pos ||
+                TEMP_FAILURE_RETRY(read(fd, verify, size)) != size) {
+                printf("mtd: re-read error at 0x%08lx (%s)\n",
+                        pos, strerror(errno));
+                continue;
+            }
+            if (memcmp(data, verify, size) != 0) {
+                printf("mtd: verification error at 0x%08lx (%s)\n",
+                        pos, strerror(errno));
+                continue;
+            }
+
+            if (retry > 0) {
+                printf("mtd: wrote block after %d retries\n", retry);
+            }
+            printf("mtd: successfully wrote block at %lx\n", pos);
+            return 0;  // Success!
+        }
+
+        // Try to erase it once more as we give up on this block
+        add_bad_block_offset(ctx, pos);
+        printf("mtd: skipping write block at 0x%08lx\n", pos);
+#ifdef RK3X
+        rk30_zero_out(fd, pos, size);
+#else
+
+        ioctl(fd, MEMERASE, &erase_info);
+#endif
+        pos += partition->erase_size;
+    }
+
+    // Ran out of space on the device
+    errno = ENOSPC;
+    return -1;
+}
+
+ssize_t mtd_write_data(MtdWriteContext *ctx, const char *data, size_t len)
+{
+    size_t wrote = 0;
+    while (wrote < len) {
+        // Coalesce partial writes into complete blocks
+        if (ctx->stored > 0 || len - wrote < ctx->partition->erase_size) {
+            size_t avail = ctx->partition->erase_size - ctx->stored;
+            size_t copy = len - wrote < avail ? len - wrote : avail;
+            memcpy(ctx->buffer + ctx->stored, data + wrote, copy);
+            ctx->stored += copy;
+            wrote += copy;
+        }
+
+        // If a complete block was accumulated, write it
+        if (ctx->stored == ctx->partition->erase_size) {
+            if (write_block(ctx, ctx->buffer)) return -1;
+            ctx->stored = 0;
+        }
+
+        // Write complete blocks directly from the user's buffer
+        while (ctx->stored == 0 && len - wrote >= ctx->partition->erase_size) {
+            if (write_block(ctx, data + wrote)) return -1;
+            wrote += ctx->partition->erase_size;
+        }
+    }
+
+    return wrote;
+}
+
+off_t mtd_erase_blocks(MtdWriteContext *ctx, int blocks)
+{
+    // Zero-pad and write any pending data to get us to a block boundary
+    if (ctx->stored > 0) {
+        size_t zero = ctx->partition->erase_size - ctx->stored;
+        memset(ctx->buffer + ctx->stored, 0, zero);
+        if (write_block(ctx, ctx->buffer)) return -1;
+        ctx->stored = 0;
+    }
+
+    off_t pos = TEMP_FAILURE_RETRY(lseek(ctx->fd, 0, SEEK_CUR));
+    if ((off_t) pos == (off_t) -1) {
+        printf("mtd_erase_blocks: couldn't SEEK_CUR: %s\n", strerror(errno));
+        return -1;
+    }
+
+    const int total = (ctx->partition->size - pos) / ctx->partition->erase_size;
+    if (blocks < 0) blocks = total;
+    if (blocks > total) {
+        errno = ENOSPC;
+        return -1;
+    }
+
+    // Erase the specified number of blocks
+    while (blocks-- > 0) {
+        loff_t bpos = pos;
+        if (ioctl(ctx->fd, MEMGETBADBLOCK, &bpos) > 0) {
+            printf("mtd: not erasing bad block at 0x%08lx\n", pos);
+            pos += ctx->partition->erase_size;
+            continue;  // Don't try to erase known factory-bad blocks.
+        }
+
+        struct erase_info_user erase_info;
+        erase_info.start = pos;
+        erase_info.length = ctx->partition->erase_size;
+#ifdef RK3X
+        if (rk30_zero_out(ctx->fd, pos, ctx->partition->erase_size) < 0) {
+            fprintf(stderr, "mtd: erase failure at 0x%08lx\n", pos);
+        }
+#else
+        if (ioctl(ctx->fd, MEMERASE, &erase_info) < 0) {
+            printf("mtd: erase failure at 0x%08lx\n", pos);
+        }
+#endif
+        pos += ctx->partition->erase_size;
+    }
+
+    return pos;
+}
+
+int mtd_write_close(MtdWriteContext *ctx)
+{
+    int r = 0;
+    // Make sure any pending data gets written
+    if (mtd_erase_blocks(ctx, 0) == (off_t) -1) r = -1;
+    if (close(ctx->fd)) r = -1;
+    free(ctx->bad_block_offsets);
+    free(ctx->buffer);
+    free(ctx);
+    return r;
+}
+
+/* Return the offset of the first good block at or after pos (which
+ * might be pos itself).
+ */
+off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos) {
+    int i;
+    for (i = 0; i < ctx->bad_block_count; ++i) {
+        if (ctx->bad_block_offsets[i] == pos) {
+            pos += ctx->partition->erase_size;
+        } else if (ctx->bad_block_offsets[i] > pos) {
+            return pos;
+        }
+    }
+    return pos;
+}
+
+#define MTD_BLOCK_SIZE    2048
+#define SPARE_SIZE    (MTD_BLOCK_SIZE >> 5)
+#define HEADER_SIZE 2048
+
+int cmd_mtd_restore_raw_partition(const char *partition_name, const char *filename)
+{
+    FILE* f = fopen(filename, "rb");
+    if (f == NULL) {
+        fprintf(stderr, "error opening %s", filename);
+        return -1;
+    }
+
+    if (mtd_scan_partitions() <= 0)
+    {
+        fprintf(stderr, "error scanning partitions");
+        return -1;
+    }
+    const MtdPartition *mtd = mtd_find_partition_by_name(partition_name);
+    if (mtd == NULL)
+    {
+        fprintf(stderr, "can't find %s partition", partition_name);
+        return -1;
+    }
+
+    int fd = open(filename, O_RDONLY);
+    if (fd < 0)
+    {
+        printf("error opening %s", filename);
+        return -1;
+    }
+    
+    MtdWriteContext* ctx = mtd_write_partition(mtd);
+    if (ctx == NULL) {
+        printf("error writing %s", partition_name);
+        return -1;
+    }
+
+    int success = 1;
+    char* buffer = malloc(BUFSIZ);
+    int read;
+    while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) {
+        int wrote = mtd_write_data(ctx, buffer, read);
+        success = success && (wrote == read);
+    }
+    free(buffer);
+    fclose(f);
+
+    if (!success) {
+        fprintf(stderr, "error writing %s", partition_name);
+        return -1;
+    }
+
+    if (mtd_erase_blocks(ctx, -1) == -1) {
+        fprintf(stderr, "error erasing blocks of %s\n", partition_name);
+    }
+    if (mtd_write_close(ctx) != 0) {
+        fprintf(stderr, "error closing write of %s\n", partition_name);
+    }
+    printf("%s %s partition\n", success ? "wrote" : "failed to write", partition_name);
+    return 0;
+}
+
+
+int cmd_mtd_backup_raw_partition(const char *partition_name, const char *filename)
+{
+    MtdReadContext *in;
+    const MtdPartition *partition;
+    char buf[MTD_BLOCK_SIZE + SPARE_SIZE];
+    size_t partition_size;
+    size_t total;
+    int fd;
+    int wrote;
+    int len;
+
+    if (mtd_scan_partitions() <= 0)
+    {
+        printf("error scanning partitions");
+        return -1;
+    }
+
+    partition = mtd_find_partition_by_name(partition_name);
+    if (partition == NULL)
+    {
+        printf("can't find %s partition", partition_name);
+        return -1;
+    }
+
+    if (mtd_partition_info(partition, &partition_size, NULL, NULL)) {
+        printf("can't get info of partition %s", partition_name);
+        return -1;
+    }
+
+    if (!strcmp(filename, "-")) {
+        fd = fileno(stdout);
+    }
+    else {
+        fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+    }
+
+    if (fd < 0)
+    {
+       printf("error opening %s", filename);
+       return -1;
+    }
+
+    in = mtd_read_partition(partition);
+    if (in == NULL) {
+        close(fd);
+        unlink(filename);
+        printf("error opening %s: %s\n", partition_name, strerror(errno));
+        return -1;
+    }
+
+    total = 0;
+    while ((len = mtd_read_data(in, buf, MTD_BLOCK_SIZE)) > 0) {
+        wrote = write(fd, buf, len);
+        if (wrote != len) {
+            close(fd);
+            unlink(filename);
+            printf("error writing %s", filename);
+            return -1;
+        }
+        total += MTD_BLOCK_SIZE;
+    }
+
+    mtd_read_close(in);
+
+    if (close(fd)) {
+        unlink(filename);
+        printf("error closing %s", filename);
+        return -1;
+    }
+    return 0;
+}
+
+int cmd_mtd_erase_raw_partition(const char *partition_name)
+{
+    MtdWriteContext *out;
+    size_t erased;
+
+    if (mtd_scan_partitions() <= 0)
+    {
+        printf("error scanning partitions");
+        return -1;
+    }
+    const MtdPartition *p = mtd_find_partition_by_name(partition_name);
+    if (p == NULL)
+    {
+        printf("can't find %s partition", partition_name);
+        return -1;
+    }
+
+    out = mtd_write_partition(p);
+    if (out == NULL)
+    {
+        printf("could not estabilish write context for %s", partition_name);
+        return -1;
+    }
+
+    // do the actual erase, -1 = full partition erase
+    erased = mtd_erase_blocks(out, -1);
+
+    // erased = bytes erased, if zero, something borked
+    if (!erased)
+    {
+        printf("error erasing %s", partition_name);
+        return -1;
+    }
+
+    return 0;
+}
+
+int cmd_mtd_erase_partition(const char *partition, const char *filesystem __unused)
+{
+    return cmd_mtd_erase_raw_partition(partition);
+}
+
+
+int cmd_mtd_mount_partition(const char *partition, const char *mount_point, const char *filesystem __unused, int read_only)
+{
+    mtd_scan_partitions();
+    const MtdPartition *p;
+    p = mtd_find_partition_by_name(partition);
+    if (p == NULL) {
+        return -1;
+    }
+    return mtd_mount_partition(p, mount_point, filesystem, read_only);
+}
+
+int cmd_mtd_get_partition_device(const char *partition, char *device)
+{
+    mtd_scan_partitions();
+    const MtdPartition *p = mtd_find_partition_by_name(partition);
+    if (p == NULL)
+        return -1;
+    sprintf(device, "/dev/block/mtdblock%d", p->device_index);
+    return 0;
+}
diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h
new file mode 100644
index 0000000..235cbe7
--- /dev/null
+++ b/mtdutils/mtdutils.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MTDUTILS_H_
+#define MTDUTILS_H_
+
+#include <sys/types.h>  // for size_t, etc.
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct MtdPartition MtdPartition;
+
+int mtd_scan_partitions(void);
+
+const MtdPartition *mtd_find_partition_by_name(const char *name);
+
+/* mount_point is like "/system"
+ * filesystem is like "yaffs2"
+ */
+int mtd_mount_partition(const MtdPartition *partition, const char *mount_point,
+        const char *filesystem, int read_only);
+
+/* get the partition and the minimum erase/write block size.  NULL is ok.
+ */
+int mtd_partition_info(const MtdPartition *partition,
+        size_t *total_size, size_t *erase_size, size_t *write_size);
+
+/* read or write raw data from a partition, starting at the beginning.
+ * skips bad blocks as best we can.
+ */
+typedef struct MtdReadContext MtdReadContext;
+typedef struct MtdWriteContext MtdWriteContext;
+
+MtdReadContext *mtd_read_partition(const MtdPartition *);
+ssize_t mtd_read_data(MtdReadContext *, char *data, size_t data_len);
+void mtd_read_close(MtdReadContext *);
+
+MtdWriteContext *mtd_write_partition(const MtdPartition *);
+ssize_t mtd_write_data(MtdWriteContext *, const char *data, size_t data_len);
+off_t mtd_erase_blocks(MtdWriteContext *, int blocks);  /* 0 ok, -1 for all */
+int mtd_write_close(MtdWriteContext *);
+
+struct MtdPartition {
+    int device_index;
+    unsigned int size;
+    unsigned int erase_size;
+    char *name;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // MTDUTILS_H_
diff --git a/mtdutils/rk3xhack.c b/mtdutils/rk3xhack.c
new file mode 100644
index 0000000..930f605
--- /dev/null
+++ b/mtdutils/rk3xhack.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2013, Sergey 'Jin' Bostandzhyan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This is a hack for Rockchip rk3x based devices. The problem is that
+ * the MEMERASE ioctl is failing (hangs and never returns) in their kernel.
+ * The sources are not fully available, so fixing it in the rk30xxnand_ko driver
+ * is not possible.
+ *
+ * I straced the stock recovery application and it seems to avoid this
+ * particular ioctl, instead it is simply writing zeroes using the write() call.
+ *
+ * This workaround does the same and will replace all MEMERASE occurances in
+ * the recovery code.
+ */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "rk3xhack.h"
+
+int rk30_zero_out(int fd, off_t pos, ssize_t size)
+{
+    if (lseek(fd, pos, SEEK_SET) != pos) {
+        fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n",
+                pos, strerror(errno));
+        return -1;
+    }
+
+    unsigned char *zb = (unsigned char *)calloc(1, size);
+    if (zb == NULL) {
+        fprintf(stderr, "mtd: erase failure, could not allocate memory\n");
+        return -1;
+    }
+
+    if (write(fd, zb, size) != size) {
+        fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n",
+                pos, strerror(errno));
+        free(zb);
+        return -1;
+    }
+
+    free(zb);
+    return 0;
+}
diff --git a/mtdutils/rk3xhack.h b/mtdutils/rk3xhack.h
new file mode 100644
index 0000000..3cc16e4
--- /dev/null
+++ b/mtdutils/rk3xhack.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013 Sergey 'Jin' Bostandzhyan
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This is a hack for Rockchip rk3x based devices. The problem is that
+ * the MEMERASE ioctl is failing (hangs and never returns) in their kernel.
+ * The sources are not fully available, so fixing it in the rk30xxnand_ko driver
+ * is not possible.
+ *
+ * I straced the stock recovery application and it seems to avoid this
+ * particular ioctl, instead it is simply writing zeroes using the write() call.
+ *
+ * This workaround does the same and will replace all MEMERASE occurances in
+ * the recovery code.
+ */
+
+#ifndef __RK3X_HACK_H__
+#define __RK3X_HACK_H__
+
+#include <sys/types.h>  // for size_t, etc.
+
+// write zeroes to fd at position pos
+int zero_out(int fd, off_t pos, ssize_t length);
+
+#endif//__RK3X_HACK_H__
diff --git a/mtp/ffs/Android.mk b/mtp/ffs/Android.mk
new file mode 100755
index 0000000..0f8bda9
--- /dev/null
+++ b/mtp/ffs/Android.mk
@@ -0,0 +1,73 @@
+LOCAL_PATH := $(call my-dir)
+
+# Build libtwrpmtp library
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libtwrpmtp-ffs
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -DMTP_DEVICE -DMTP_HOST -fno-strict-aliasing \
+    -Wno-unused-variable -Wno-format -Wno-unused-parameter -Wno-unused-private-field \
+    -Wno-implicit-fallthrough
+LOCAL_C_INCLUDES += $(LOCAL_PATH) bionic \
+    frameworks/base/include \
+    system/core/include \
+    bionic/libc/private/ \
+    bootable/recovery/twrplibusbhost/include \
+    bootable/recovery/twrpinstall/include
+
+LOCAL_SHARED_LIBRARIES += libc++
+LOCAL_STATIC_LIBRARIES += libtwrpinstall
+
+LOCAL_SRC_FILES = \
+    MtpDataPacket.cpp \
+    MtpDebug.cpp \
+    MtpDevice.cpp \
+    MtpDevHandle.cpp \
+    MtpDeviceInfo.cpp \
+    MtpEventPacket.cpp \
+    MtpObjectInfo.cpp \
+    MtpPacket.cpp \
+    MtpProperty.cpp \
+    MtpRequestPacket.cpp \
+    MtpResponsePacket.cpp \
+    MtpServer.cpp \
+    MtpStorage.cpp \
+    MtpStorageInfo.cpp \
+    MtpStringBuffer.cpp \
+    MtpUtils.cpp \
+    mtp_MtpServer.cpp \
+    btree.cpp \
+    twrpMtp.cpp \
+    mtp_MtpDatabase.cpp \
+    node.cpp
+
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 25; echo $$?),0)
+    LOCAL_CFLAGS += -D_FFS_DEVICE
+    LOCAL_SHARED_LIBRARIES += libasyncio
+    LOCAL_SRC_FILES += \
+        MtpDescriptors.cpp \
+        MtpFfsHandle.cpp \
+        MtpFfsCompatHandle.cpp \
+        PosixAsyncIO.cpp
+endif
+
+LOCAL_SHARED_LIBRARIES += libz \
+                          libc \
+                          libusbhost \
+                          libstdc++ \
+                          libdl \
+                          libcutils \
+                          libutils \
+                          libselinux \
+                          libbase
+
+LOCAL_C_INCLUDES += bootable/recovery/twrplibusbhost/include
+
+ifneq ($(TW_MTP_DEVICE),)
+	LOCAL_CFLAGS += -DUSB_MTP_DEVICE=$(TW_MTP_DEVICE)
+endif
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 25; echo $$?),0)
+    LOCAL_CFLAGS += -DHAS_USBHOST_TIMEOUT
+endif
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/mtp/ffs/AsyncIO.cpp b/mtp/ffs/AsyncIO.cpp
new file mode 100644
index 0000000..eb97a98
--- /dev/null
+++ b/mtp/ffs/AsyncIO.cpp
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <queue>
+
+#include "AsyncIO.h"
+
+void read_func(struct aiocb *aiocbp) {
+	aiocbp->ret = TEMP_FAILURE_RETRY(pread(aiocbp->aio_fildes,
+				aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset));
+	if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+void write_func(struct aiocb *aiocbp) {
+	aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes,
+				aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset));
+	if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+void splice_read_func(struct aiocb *aiocbp) {
+	loff_t long_offset = aiocbp->aio_offset;
+	aiocbp->ret = TEMP_FAILURE_RETRY(splice(aiocbp->aio_fildes,
+				&long_offset, aiocbp->aio_sink,
+				NULL, aiocbp->aio_nbytes, 0));
+	if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+void splice_write_func(struct aiocb *aiocbp) {
+	loff_t long_offset = aiocbp->aio_offset;
+	aiocbp->ret = TEMP_FAILURE_RETRY(splice(aiocbp->aio_fildes, NULL,
+				aiocbp->aio_sink, &long_offset,
+				aiocbp->aio_nbytes, 0));
+	if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+std::queue<std::unique_ptr<struct aiocb>> queue;
+std::mutex queue_lock;
+std::condition_variable queue_cond;
+std::condition_variable write_cond;
+int done = 1;
+void splice_write_pool_func(int) {
+	while(1) {
+		std::unique_lock<std::mutex> lk(queue_lock);
+		queue_cond.wait(lk, []{return !queue.empty() || done;});
+		if (queue.empty() && done) {
+			return;
+		}
+		std::unique_ptr<struct aiocb> aiocbp = std::move(queue.front());
+		queue.pop();
+		lk.unlock();
+		write_cond.notify_one();
+		splice_write_func(aiocbp.get());
+		close(aiocbp->aio_fildes);
+	}
+}
+
+void write_pool_func(int) {
+	while(1) {
+		std::unique_lock<std::mutex> lk(queue_lock);
+		queue_cond.wait(lk, []{return !queue.empty() || done;});
+		if (queue.empty() && done) {
+			return;
+		}
+		std::unique_ptr<struct aiocb> aiocbp = std::move(queue.front());
+		queue.pop();
+		lk.unlock();
+		write_cond.notify_one();
+		aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes,
+					aiocbp->aio_pool_buf.get(), aiocbp->aio_nbytes, aiocbp->aio_offset));
+		if (aiocbp->ret == -1) aiocbp->error = errno;
+	}
+}
+
+constexpr int NUM_THREADS = 1;
+constexpr int MAX_QUEUE_SIZE = 10;
+std::thread pool[NUM_THREADS];
+
+aiocb::~aiocb() {
+	CHECK(!thread.joinable());
+}
+
+void aio_pool_init(void(f)(int)) {
+	CHECK(done == 1);
+	done = 0;
+	for (int i = 0; i < NUM_THREADS; i++) {
+		pool[i] = std::thread(f, i);
+	}
+}
+
+void aio_pool_splice_init() {
+	aio_pool_init(splice_write_pool_func);
+}
+
+void aio_pool_write_init() {
+	aio_pool_init(write_pool_func);
+}
+
+void aio_pool_end() {
+	done = 1;
+	for (int i = 0; i < NUM_THREADS; i++) {
+		std::unique_lock<std::mutex> lk(queue_lock);
+		lk.unlock();
+		queue_cond.notify_one();
+	}
+
+	for (int i = 0; i < NUM_THREADS; i++) {
+		pool[i].join();
+	}
+}
+
+// used for both writes and splices depending on which init was used before.
+int aio_pool_write(struct aiocb *aiocbp) {
+	std::unique_lock<std::mutex> lk(queue_lock);
+	write_cond.wait(lk, []{return queue.size() < MAX_QUEUE_SIZE;});
+	queue.push(std::unique_ptr<struct aiocb>(aiocbp));
+	lk.unlock();
+	queue_cond.notify_one();
+	return 0;
+}
+
+int aio_read(struct aiocb *aiocbp) {
+	aiocbp->thread = std::thread(read_func, aiocbp);
+	return 0;
+}
+
+int aio_write(struct aiocb *aiocbp) {
+	aiocbp->thread = std::thread(write_func, aiocbp);
+	return 0;
+}
+
+int aio_splice_read(struct aiocb *aiocbp) {
+	aiocbp->thread = std::thread(splice_read_func, aiocbp);
+	return 0;
+}
+
+int aio_splice_write(struct aiocb *aiocbp) {
+	aiocbp->thread = std::thread(splice_write_func, aiocbp);
+	return 0;
+}
+
+int aio_error(const struct aiocb *aiocbp) {
+	return aiocbp->error;
+}
+
+ssize_t aio_return(struct aiocb *aiocbp) {
+	return aiocbp->ret;
+}
+
+int aio_suspend(struct aiocb *aiocbp[], int n,
+		const struct timespec *) {
+	for (int i = 0; i < n; i++) {
+		aiocbp[i]->thread.join();
+	}
+	return 0;
+}
+
+int aio_cancel(int, struct aiocb *) {
+	// Not implemented
+	return -1;
+}
+
diff --git a/mtp/ffs/AsyncIO.h b/mtp/ffs/AsyncIO.h
new file mode 100644
index 0000000..19e7617
--- /dev/null
+++ b/mtp/ffs/AsyncIO.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ASYNCIO_H
+#define _ASYNCIO_H
+
+#include <fcntl.h>
+#include <linux/aio_abi.h>
+#include <memory>
+#include <signal.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <time.h>
+#include <thread>
+#include <unistd.h>
+
+/**
+ * Provides a subset of POSIX aio operations, as well
+ * as similar operations with splice and threadpools.
+ */
+
+struct aiocb {
+	int aio_fildes;		// Assumed to be the source for splices
+	void *aio_buf;		// Unused for splices
+
+	// Used for threadpool operations only, freed automatically
+	std::unique_ptr<char[]> aio_pool_buf;
+
+	off_t aio_offset;
+	size_t aio_nbytes;
+
+	int aio_sink;		// Unused for non splice r/w
+
+	// Used internally
+	std::thread thread;
+	ssize_t ret;
+	int error;
+
+	~aiocb();
+};
+
+// Submit a request for IO to be completed
+int aio_read(struct aiocb *);
+int aio_write(struct aiocb *);
+int aio_splice_read(struct aiocb *);
+int aio_splice_write(struct aiocb *);
+
+// Suspend current thread until given IO is complete, at which point
+// its return value and any errors can be accessed
+// All submitted requests must have a corresponding suspend.
+// aiocb->aio_buf must refer to valid memory until after the suspend call
+int aio_suspend(struct aiocb *[], int, const struct timespec *);
+int aio_error(const struct aiocb *);
+ssize_t aio_return(struct aiocb *);
+
+// (Currently unimplemented)
+int aio_cancel(int, struct aiocb *);
+
+// Initialize a threadpool to perform IO. Only one pool can be
+// running at a time.
+void aio_pool_write_init();
+void aio_pool_splice_init();
+// Suspend current thread until all queued work is complete, then ends the threadpool
+void aio_pool_end();
+// Submit IO work for the threadpool to complete. Memory associated with the work is
+// freed automatically when the work is complete.
+int aio_pool_write(struct aiocb *);
+
+#endif // ASYNCIO_H
+
diff --git a/mtp/ffs/IMtpHandle.h b/mtp/ffs/IMtpHandle.h
new file mode 100644
index 0000000..11d7185
--- /dev/null
+++ b/mtp/ffs/IMtpHandle.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _IMTP_HANDLE_H
+#define _IMTP_HANDLE_H
+
+#include "f_mtp.h"
+
+class IMtpHandle {
+public:
+	// Return number of bytes read/written, or -1 and errno is set
+	virtual int read(void *data, size_t len) = 0;
+	virtual int write(const void *data, size_t len) = 0;
+
+	// Return 0 if send/receive is successful, or -1 and errno is set
+	virtual int receiveFile(mtp_file_range mfr, bool zero_packet) = 0;
+	virtual int sendFile(mtp_file_range mfr) = 0;
+	virtual int sendEvent(mtp_event me) = 0;
+
+	// Return 0 if operation is successful, or -1 else
+	virtual int start(bool ptp) = 0;
+
+	virtual bool writeDescriptors(bool ptp) = 0;
+
+	virtual void close() = 0;
+
+	virtual ~IMtpHandle() {}
+};
+
+#endif // _IMTP_HANDLE_H
+
diff --git a/mtp/ffs/MtpDataPacket.cpp b/mtp/ffs/MtpDataPacket.cpp
new file mode 100644
index 0000000..08f57c5
--- /dev/null
+++ b/mtp/ffs/MtpDataPacket.cpp
@@ -0,0 +1,650 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpDataPacket"
+
+#include "MtpDataPacket.h"
+
+#include <algorithm>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <usbhost/usbhost.h>
+#include "MtpStringBuffer.h"
+#include "IMtpHandle.h"
+#include "MtpDebug.h"
+
+namespace {
+// Reads the exact |count| bytes from |fd| to |buf|.
+// Returns |count| if it succeed to read the bytes. Otherwise returns -1. If it reaches EOF, the
+// function regards it as an error.
+ssize_t readExactBytes(int fd, void* buf, size_t count) {
+	if (count > SSIZE_MAX) {
+		return -1;
+	}
+	size_t read_count = 0;
+	while (read_count < count) {
+		int result = read(fd, static_cast<int8_t*>(buf) + read_count, count - read_count);
+		// Assume that EOF is error.
+		if (result <= 0) {
+			return -1;
+		}
+		read_count += result;
+	}
+	return read_count == count ? count : -1;
+}
+}  // namespace
+
+MtpDataPacket::MtpDataPacket()
+	:	MtpPacket(MTP_BUFFER_SIZE),   // MAX_USBFS_BUFFER_SIZE
+		mOffset(MTP_CONTAINER_HEADER_SIZE)
+{
+}
+
+MtpDataPacket::~MtpDataPacket() {
+}
+
+void MtpDataPacket::reset() {
+	MtpPacket::reset();
+	mOffset = MTP_CONTAINER_HEADER_SIZE;
+}
+
+void MtpDataPacket::setOperationCode(MtpOperationCode code) {
+	MtpPacket::putUInt16(MTP_CONTAINER_CODE_OFFSET, code);
+}
+
+void MtpDataPacket::setTransactionID(MtpTransactionID id) {
+	MtpPacket::putUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET, id);
+}
+
+bool MtpDataPacket::getUInt8(uint8_t& value) {
+	if (mPacketSize - mOffset < sizeof(value))
+		return false;
+	value = mBuffer[mOffset++];
+	return true;
+}
+
+bool MtpDataPacket::getUInt16(uint16_t& value) {
+	if (mPacketSize - mOffset < sizeof(value))
+		return false;
+	int offset = mOffset;
+	value = (uint16_t)mBuffer[offset] | ((uint16_t)mBuffer[offset + 1] << 8);
+	mOffset += sizeof(value);
+	return true;
+}
+
+bool MtpDataPacket::getUInt32(uint32_t& value) {
+	if (mPacketSize - mOffset < sizeof(value))
+		return false;
+	int offset = mOffset;
+	value = (uint32_t)mBuffer[offset] | ((uint32_t)mBuffer[offset + 1] << 8) |
+		   ((uint32_t)mBuffer[offset + 2] << 16)  | ((uint32_t)mBuffer[offset + 3] << 24);
+	mOffset += sizeof(value);
+	return true;
+}
+
+bool MtpDataPacket::getUInt64(uint64_t& value) {
+	if (mPacketSize - mOffset < sizeof(value))
+		return false;
+	int offset = mOffset;
+	value = (uint64_t)mBuffer[offset] | ((uint64_t)mBuffer[offset + 1] << 8) |
+		   ((uint64_t)mBuffer[offset + 2] << 16) | ((uint64_t)mBuffer[offset + 3] << 24) |
+		   ((uint64_t)mBuffer[offset + 4] << 32) | ((uint64_t)mBuffer[offset + 5] << 40) |
+		   ((uint64_t)mBuffer[offset + 6] << 48)  | ((uint64_t)mBuffer[offset + 7] << 56);
+	mOffset += sizeof(value);
+	return true;
+}
+
+bool MtpDataPacket::getUInt128(uint128_t& value) {
+	return getUInt32(value[0]) && getUInt32(value[1]) && getUInt32(value[2]) && getUInt32(value[3]);
+}
+
+bool MtpDataPacket::getString(MtpStringBuffer& string)
+{
+	return string.readFromPacket(this);
+}
+
+Int8List* MtpDataPacket::getAInt8() {
+	uint32_t count;
+	if (!getUInt32(count))
+		return NULL;
+	Int8List* result = new Int8List;
+	for (uint32_t i = 0; i < count; i++) {
+		int8_t value;
+		if (!getInt8(value)) {
+			delete result;
+			return NULL;
+		}
+		result->push_back(value);
+	}
+	return result;
+}
+
+UInt8List* MtpDataPacket::getAUInt8() {
+	uint32_t count;
+	if (!getUInt32(count))
+		return NULL;
+	UInt8List* result = new UInt8List;
+	for (uint32_t i = 0; i < count; i++) {
+		uint8_t value;
+		if (!getUInt8(value)) {
+			delete result;
+			return NULL;
+		}
+		result->push_back(value);
+	}
+	return result;
+}
+
+Int16List* MtpDataPacket::getAInt16() {
+	uint32_t count;
+	if (!getUInt32(count))
+		return NULL;
+	Int16List* result = new Int16List;
+	for (uint32_t i = 0; i < count; i++) {
+		int16_t value;
+		if (!getInt16(value)) {
+			delete result;
+			return NULL;
+		}
+		result->push_back(value);
+	}
+	return result;
+}
+
+UInt16List* MtpDataPacket::getAUInt16() {
+	uint32_t count;
+	if (!getUInt32(count))
+		return NULL;
+	UInt16List* result = new UInt16List;
+	for (uint32_t i = 0; i < count; i++) {
+		uint16_t value;
+		if (!getUInt16(value)) {
+			delete result;
+			return NULL;
+		}
+		result->push_back(value);
+	}
+	return result;
+}
+
+Int32List* MtpDataPacket::getAInt32() {
+	uint32_t count;
+	if (!getUInt32(count))
+		return NULL;
+	Int32List* result = new Int32List;
+	for (uint32_t i = 0; i < count; i++) {
+		int32_t value;
+		if (!getInt32(value)) {
+			delete result;
+			return NULL;
+		}
+		result->push_back(value);
+	}
+	return result;
+}
+
+UInt32List* MtpDataPacket::getAUInt32() {
+	uint32_t count;
+	if (!getUInt32(count))
+		return NULL;
+	UInt32List* result = new UInt32List;
+	for (uint32_t i = 0; i < count; i++) {
+		uint32_t value;
+		if (!getUInt32(value)) {
+			delete result;
+			return NULL;
+		}
+		result->push_back(value);
+	}
+	return result;
+}
+
+Int64List* MtpDataPacket::getAInt64() {
+	uint32_t count;
+	if (!getUInt32(count))
+		return NULL;
+	Int64List* result = new Int64List;
+	for (uint32_t i = 0; i < count; i++) {
+		int64_t value;
+		if (!getInt64(value)) {
+			delete result;
+			return NULL;
+		}
+		result->push_back(value);
+	}
+	return result;
+}
+
+UInt64List* MtpDataPacket::getAUInt64() {
+	uint32_t count;
+	if (!getUInt32(count))
+		return NULL;
+	UInt64List* result = new UInt64List;
+	for (uint32_t i = 0; i < count; i++) {
+		uint64_t value;
+		if (!getUInt64(value)) {
+			delete result;
+			return NULL;
+		}
+		result->push_back(value);
+	}
+	return result;
+}
+
+void MtpDataPacket::putInt8(int8_t value) {
+	allocate(mOffset + 1);
+	mBuffer[mOffset++] = (uint8_t)value;
+	if (mPacketSize < mOffset)
+		mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putUInt8(uint8_t value) {
+	allocate(mOffset + 1);
+	mBuffer[mOffset++] = (uint8_t)value;
+	if (mPacketSize < mOffset)
+		mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putInt16(int16_t value) {
+	allocate(mOffset + 2);
+	mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+	if (mPacketSize < mOffset)
+		mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putUInt16(uint16_t value) {
+	allocate(mOffset + 2);
+	mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+	if (mPacketSize < mOffset)
+		mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putInt32(int32_t value) {
+	allocate(mOffset + 4);
+	mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF);
+	if (mPacketSize < mOffset)
+		mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putUInt32(uint32_t value) {
+	allocate(mOffset + 4);
+	mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF);
+	if (mPacketSize < mOffset)
+		mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putInt64(int64_t value) {
+	allocate(mOffset + 8);
+	mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 32) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 40) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 48) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 56) & 0xFF);
+	if (mPacketSize < mOffset)
+		mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putUInt64(uint64_t value) {
+	allocate(mOffset + 8);
+	mBuffer[mOffset++] = (uint8_t)(value & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 32) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 40) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 48) & 0xFF);
+	mBuffer[mOffset++] = (uint8_t)((value >> 56) & 0xFF);
+	if (mPacketSize < mOffset)
+		mPacketSize = mOffset;
+}
+
+void MtpDataPacket::putInt128(const int128_t& value) {
+	putInt32(value[0]);
+	putInt32(value[1]);
+	putInt32(value[2]);
+	putInt32(value[3]);
+}
+
+void MtpDataPacket::putUInt128(const uint128_t& value) {
+	putUInt32(value[0]);
+	putUInt32(value[1]);
+	putUInt32(value[2]);
+	putUInt32(value[3]);
+}
+
+void MtpDataPacket::putInt128(int64_t value) {
+	putInt64(value);
+	putInt64(value < 0 ? -1 : 0);
+}
+
+void MtpDataPacket::putUInt128(uint64_t value) {
+	putUInt64(value);
+	putUInt64(0);
+}
+
+void MtpDataPacket::putAInt8(const int8_t* values, int count) {
+	putUInt32(count);
+	for (int i = 0; i < count; i++)
+		putInt8(*values++);
+}
+
+void MtpDataPacket::putAUInt8(const uint8_t* values, int count) {
+	putUInt32(count);
+	for (int i = 0; i < count; i++)
+		putUInt8(*values++);
+}
+
+void MtpDataPacket::putAInt16(const int16_t* values, int count) {
+	putUInt32(count);
+	for (int i = 0; i < count; i++)
+		putInt16(*values++);
+}
+
+void MtpDataPacket::putAUInt16(const uint16_t* values, int count) {
+	putUInt32(count);
+	for (int i = 0; i < count; i++)
+		putUInt16(*values++);
+}
+
+void MtpDataPacket::putAUInt16(const UInt16List* values) {
+	size_t count = (values ? values->size() : 0);
+	putUInt32(count);
+	for (size_t i = 0; i < count; i++)
+		putUInt16((*values)[i]);
+}
+
+void MtpDataPacket::putAInt32(const int32_t* values, int count) {
+	putUInt32(count);
+	for (int i = 0; i < count; i++)
+		putInt32(*values++);
+}
+
+void MtpDataPacket::putAUInt32(const uint32_t* values, int count) {
+	putUInt32(count);
+	for (int i = 0; i < count; i++)
+		putUInt32(*values++);
+}
+
+void MtpDataPacket::putAUInt32(const UInt32List* list) {
+	if (!list) {
+		putEmptyArray();
+	} else {
+		size_t size = list->size();
+		putUInt32(size);
+		for (size_t i = 0; i < size; i++)
+			putUInt32((*list)[i]);
+	}
+}
+
+void MtpDataPacket::putAInt64(const int64_t* values, int count) {
+	putUInt32(count);
+	for (int i = 0; i < count; i++)
+		putInt64(*values++);
+}
+
+void MtpDataPacket::putAUInt64(const uint64_t* values, int count) {
+	putUInt32(count);
+	for (int i = 0; i < count; i++)
+		putUInt64(*values++);
+}
+
+void MtpDataPacket::putString(const MtpStringBuffer& string) {
+	string.writeToPacket(this);
+}
+
+void MtpDataPacket::putString(const char* s) {
+	MtpStringBuffer string(s);
+	string.writeToPacket(this);
+}
+
+void MtpDataPacket::putString(const uint16_t* string) {
+	int count = 0;
+	for (int i = 0; i <= MTP_STRING_MAX_CHARACTER_NUMBER; i++) {
+		if (string[i])
+			count++;
+		else
+			break;
+	}
+	putUInt8(count > 0 ? count + 1 : 0);
+	for (int i = 0; i < count; i++)
+		putUInt16(string[i]);
+	// only terminate with zero if string is not empty
+	if (count > 0)
+		putUInt16(0);
+}
+
+#ifdef MTP_DEVICE
+int MtpDataPacket::read(IMtpHandle *h) {
+	int ret = h->read(mBuffer, MTP_BUFFER_SIZE);
+	if (ret < MTP_CONTAINER_HEADER_SIZE)
+		return -1;
+	mPacketSize = ret;
+	mOffset = MTP_CONTAINER_HEADER_SIZE;
+	return ret;
+}
+
+int MtpDataPacket::write(IMtpHandle *h) {
+	MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
+	MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+	int ret = h->write(mBuffer, mPacketSize);
+	return (ret < 0 ? ret : 0);
+}
+
+int MtpDataPacket::writeData(IMtpHandle *h, void* data, uint32_t length) {
+	allocate(length + MTP_CONTAINER_HEADER_SIZE);
+	memcpy(mBuffer + MTP_CONTAINER_HEADER_SIZE, data, length);
+	length += MTP_CONTAINER_HEADER_SIZE;
+	MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length);
+	MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+	int ret = h->write(mBuffer, length);
+	return (ret < 0 ? ret : 0);
+}
+
+#endif // MTP_DEVICE
+
+#ifdef MTP_HOST
+int MtpDataPacket::read(struct usb_request *request) {
+	// first read the header
+	request->buffer = mBuffer;
+	request->buffer_length = mBufferSize;
+	int length = transfer(request);
+	if (length >= MTP_CONTAINER_HEADER_SIZE) {
+		// look at the length field to see if the data spans multiple packets
+		uint32_t totalLength = MtpPacket::getUInt32(MTP_CONTAINER_LENGTH_OFFSET);
+		allocate(totalLength);
+		while (totalLength > static_cast<uint32_t>(length)) {
+			request->buffer = mBuffer + length;
+			request->buffer_length = totalLength - length;
+			int ret = transfer(request);
+			if (ret >= 0)
+				length += ret;
+			else {
+				length = ret;
+				break;
+			}
+		}
+	}
+	if (length >= 0)
+		mPacketSize = length;
+	return length;
+}
+
+int MtpDataPacket::readData(struct usb_request *request, void* buffer, int length) {
+	int read = 0;
+	while (read < length) {
+		request->buffer = (char *)buffer + read;
+		request->buffer_length = length - read;
+		int ret = transfer(request);
+		if (ret < 0) {
+			return ret;
+		}
+		read += ret;
+	}
+	return read;
+}
+
+// Queue a read request.  Call readDataWait to wait for result
+int MtpDataPacket::readDataAsync(struct usb_request *req) {
+	if (usb_request_queue(req)) {
+		MTPE("usb_endpoint_queue failed, errno: %d", errno);
+		return -1;
+	}
+	return 0;
+}
+
+// Wait for result of readDataAsync
+int MtpDataPacket::readDataWait(struct usb_device *device) {
+	struct usb_request *req = usb_request_wait(device, -1);
+	return (req ? req->actual_length : -1);
+}
+
+int MtpDataPacket::readDataHeader(struct usb_request *request) {
+	request->buffer = mBuffer;
+	request->buffer_length = request->max_packet_size;
+	int length = transfer(request);
+	if (length >= 0)
+		mPacketSize = length;
+	return length;
+}
+
+int MtpDataPacket::write(struct usb_request *request, UrbPacketDivisionMode divisionMode) {
+	if (mPacketSize < MTP_CONTAINER_HEADER_SIZE || mPacketSize > MTP_BUFFER_SIZE) {
+		MTPE("Illegal packet size.");
+		return -1;
+	}
+
+	MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
+	MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+
+	size_t processedBytes = 0;
+	while (processedBytes < mPacketSize) {
+		const size_t write_size =
+				processedBytes == 0 && divisionMode == FIRST_PACKET_ONLY_HEADER ?
+						MTP_CONTAINER_HEADER_SIZE : mPacketSize - processedBytes;
+		request->buffer = mBuffer + processedBytes;
+		request->buffer_length = write_size;
+		const int result = transfer(request);
+		if (result < 0) {
+			MTPE("Failed to write bytes to the device.");
+			return -1;
+		}
+		processedBytes += result;
+	}
+
+	return processedBytes == mPacketSize ? processedBytes : -1;
+}
+
+int MtpDataPacket::write(struct usb_request *request,
+						 UrbPacketDivisionMode divisionMode,
+						 int fd,
+						 size_t payloadSize) {
+	// Obtain the greatest multiple of minimum packet size that is not greater than
+	// MTP_BUFFER_SIZE.
+	if (request->max_packet_size <= 0) {
+		MTPE("Cannot determine bulk transfer size due to illegal max packet size %d.",
+			  request->max_packet_size);
+		return -1;
+	}
+	const size_t maxBulkTransferSize =
+			MTP_BUFFER_SIZE - (MTP_BUFFER_SIZE % request->max_packet_size);
+	const size_t containerLength = payloadSize + MTP_CONTAINER_HEADER_SIZE;
+	size_t processedBytes = 0;
+	bool readError = false;
+
+	// Bind the packet with given request.
+	request->buffer = mBuffer;
+	allocate(maxBulkTransferSize);
+
+	while (processedBytes < containerLength) {
+		size_t bulkTransferSize = 0;
+
+		// prepare header.
+		const bool headerSent = processedBytes != 0;
+		if (!headerSent) {
+			MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, containerLength);
+			MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+			bulkTransferSize += MTP_CONTAINER_HEADER_SIZE;
+		}
+
+		// Prepare payload.
+		if (headerSent || divisionMode == FIRST_PACKET_HAS_PAYLOAD) {
+			const size_t processedPayloadBytes =
+					headerSent ? processedBytes - MTP_CONTAINER_HEADER_SIZE : 0;
+			const size_t maxRead = payloadSize - processedPayloadBytes;
+			const size_t maxWrite = maxBulkTransferSize - bulkTransferSize;
+			const size_t bulkTransferPayloadSize = std::min(maxRead, maxWrite);
+			// prepare payload.
+			if (!readError) {
+				const ssize_t result = readExactBytes(
+						fd,
+						mBuffer + bulkTransferSize,
+						bulkTransferPayloadSize);
+				if (result < 0) {
+					MTPE("Found an error while reading data from FD. Send 0 data instead.");
+					readError = true;
+				}
+			}
+			if (readError) {
+				memset(mBuffer + bulkTransferSize, 0, bulkTransferPayloadSize);
+			}
+			bulkTransferSize += bulkTransferPayloadSize;
+		}
+
+		// Bulk transfer.
+		mPacketSize = bulkTransferSize;
+		request->buffer_length = bulkTransferSize;
+		const int result = transfer(request);
+		if (result != static_cast<ssize_t>(bulkTransferSize)) {
+			// Cannot recover writing error.
+			MTPE("Found an error while write data to MtpDevice.");
+			return -1;
+		}
+
+		// Update variables.
+		processedBytes += bulkTransferSize;
+	}
+
+	return readError ? -1 : processedBytes;
+}
+
+#endif // MTP_HOST
+
+void* MtpDataPacket::getData(int* outLength) const {
+	int length = mPacketSize - MTP_CONTAINER_HEADER_SIZE;
+	if (length > 0) {
+		void* result = malloc(length);
+		if (result) {
+			memcpy(result, mBuffer + MTP_CONTAINER_HEADER_SIZE, length);
+			*outLength = length;
+			return result;
+		}
+	}
+	*outLength = 0;
+	return NULL;
+}
diff --git a/mtp/ffs/MtpDataPacket.h b/mtp/ffs/MtpDataPacket.h
new file mode 100644
index 0000000..2240a3d
--- /dev/null
+++ b/mtp/ffs/MtpDataPacket.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DATA_PACKET_H
+#define _MTP_DATA_PACKET_H
+
+#include "MtpPacket.h"
+#include "mtp.h"
+
+struct usb_device;
+struct usb_request;
+
+class IMtpHandle;
+class MtpStringBuffer;
+
+class MtpDataPacket : public MtpPacket {
+private:
+	// current offset for get/put methods
+	size_t				mOffset;
+
+public:
+						MtpDataPacket();
+	virtual				~MtpDataPacket();
+
+	virtual void		reset();
+
+	void				setOperationCode(MtpOperationCode code);
+	void				setTransactionID(MtpTransactionID id);
+
+	inline const uint8_t*	  getData() const { return mBuffer + MTP_CONTAINER_HEADER_SIZE; }
+
+	bool				getUInt8(uint8_t& value);
+	inline bool			getInt8(int8_t& value) { return getUInt8((uint8_t&)value); }
+	bool				getUInt16(uint16_t& value);
+	inline bool			getInt16(int16_t& value) { return getUInt16((uint16_t&)value); }
+	bool				getUInt32(uint32_t& value);
+	inline bool			getInt32(int32_t& value) { return getUInt32((uint32_t&)value); }
+	bool				getUInt64(uint64_t& value);
+	inline bool			getInt64(int64_t& value) { return getUInt64((uint64_t&)value); }
+	bool				getUInt128(uint128_t& value);
+	inline bool			getInt128(int128_t& value) { return getUInt128((uint128_t&)value); }
+	bool				getString(MtpStringBuffer& string);
+
+	Int8List*			getAInt8();
+	UInt8List*			getAUInt8();
+	Int16List*			getAInt16();
+	UInt16List*			getAUInt16();
+	Int32List*			getAInt32();
+	UInt32List*			getAUInt32();
+	Int64List*			getAInt64();
+	UInt64List*			getAUInt64();
+
+	void				putInt8(int8_t value);
+	void				putUInt8(uint8_t value);
+	void				putInt16(int16_t value);
+	void				putUInt16(uint16_t value);
+	void				putInt32(int32_t value);
+	void				putUInt32(uint32_t value);
+	void				putInt64(int64_t value);
+	void				putUInt64(uint64_t value);
+	void				putInt128(const int128_t& value);
+	void				putUInt128(const uint128_t& value);
+	void				putInt128(int64_t value);
+	void				putUInt128(uint64_t value);
+
+	void				putAInt8(const int8_t* values, int count);
+	void				putAUInt8(const uint8_t* values, int count);
+	void				putAInt16(const int16_t* values, int count);
+	void				putAUInt16(const uint16_t* values, int count);
+	void				putAUInt16(const UInt16List* values);
+	void				putAInt32(const int32_t* values, int count);
+	void				putAUInt32(const uint32_t* values, int count);
+	void				putAUInt32(const UInt32List* list);
+	void				putAInt64(const int64_t* values, int count);
+	void				putAUInt64(const uint64_t* values, int count);
+	void				putString(const MtpStringBuffer& string);
+	void				putString(const char* string);
+	void				putString(const uint16_t* string);
+	inline void			putEmptyString() { putUInt8(0); }
+	inline void			putEmptyArray() { putUInt32(0); }
+
+#ifdef MTP_DEVICE
+	// fill our buffer with data from the given usb handle
+	int					read(IMtpHandle *h);
+
+	// write our data to the given usb handle
+	int					write(IMtpHandle *h);
+	int					writeData(IMtpHandle *h, void* data, uint32_t length);
+#endif
+
+#ifdef MTP_HOST
+	int					read(struct usb_request *request);
+	int					readData(struct usb_request *request, void* buffer, int length);
+	int					readDataAsync(struct usb_request *req);
+	int					readDataWait(struct usb_device *device);
+	int					readDataHeader(struct usb_request *ep);
+
+	// Write a whole data packet with payload to the end point given by a request. |divisionMode|
+	// specifies whether to divide header and payload. See |UrbPacketDivisionMode| for meanings of
+	// each value. Return the number of bytes (including header size) sent to the device on success.
+	// Otherwise -1.
+	int					write(struct usb_request *request, UrbPacketDivisionMode divisionMode);
+	// Similar to previous write method but it reads the payload from |fd|. If |size| is larger than
+	// MTP_BUFFER_SIZE, the data will be sent by multiple bulk transfer requests.
+	int					write(struct usb_request *request, UrbPacketDivisionMode divisionMode,
+							  int fd, size_t size);
+#endif
+
+	inline bool			hasData() const { return mPacketSize > MTP_CONTAINER_HEADER_SIZE; }
+	inline uint32_t		getContainerLength() const { return MtpPacket::getUInt32(MTP_CONTAINER_LENGTH_OFFSET); }
+	void*				getData(int* outLength) const;
+};
+
+#endif // _MTP_DATA_PACKET_H
diff --git a/mtp/ffs/MtpDatabase.h b/mtp/ffs/MtpDatabase.h
new file mode 100755
index 0000000..18aabb8
--- /dev/null
+++ b/mtp/ffs/MtpDatabase.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DATABASE_H
+#define _MTP_DATABASE_H
+
+#include "MtpTypes.h"
+#include "MtpStringBuffer.h"
+
+class MtpDataPacket;
+class MtpProperty;
+class MtpObjectInfo;
+
+class MtpDatabase {
+public:
+	virtual ~MtpDatabase() {}
+
+	// called from SendObjectInfo to reserve a database entry for the incoming file
+	virtual MtpObjectHandle			beginSendObject(const char* path,
+											MtpObjectFormat format,
+											MtpObjectHandle parent,
+											MtpStorageID storage,
+											uint64_t size,
+											time_t modified) = 0;
+
+	// called to report success or failure of the SendObject file transfer
+	// success should signal a notification of the new object's creation,
+	// failure should remove the database entry created in beginSendObject
+	virtual void					endSendObject(const char* path,
+											MtpObjectHandle handle,
+											MtpObjectFormat format,
+											bool succeeded) = 0;
+
+	virtual MtpObjectHandleList*	getObjectList(MtpStorageID storageID,
+											MtpObjectFormat format,
+											MtpObjectHandle parent) = 0;
+
+	virtual int						getNumObjects(MtpStorageID storageID,
+											MtpObjectFormat format,
+											MtpObjectHandle parent) = 0;
+
+	// callee should delete[] the results from these
+	// results can be NULL
+	virtual MtpObjectFormatList*	getSupportedPlaybackFormats() = 0;
+	virtual MtpObjectFormatList*	getSupportedCaptureFormats() = 0;
+	virtual MtpObjectPropertyList*	getSupportedObjectProperties(MtpObjectFormat format) = 0;
+	virtual MtpDevicePropertyList*	getSupportedDeviceProperties() = 0;
+
+	virtual void					createDB(MtpStorage* storage, MtpStorageID storageID) = 0;
+
+	virtual MtpResponseCode			getObjectPropertyValue(MtpObjectHandle handle,
+											MtpObjectProperty property,
+											MtpDataPacket& packet) = 0;
+
+	virtual MtpResponseCode			setObjectPropertyValue(MtpObjectHandle handle,
+											MtpObjectProperty property,
+											MtpDataPacket& packet) = 0;
+
+	virtual MtpResponseCode			getDevicePropertyValue(MtpDeviceProperty property,
+											MtpDataPacket& packet) = 0;
+
+	virtual MtpResponseCode			setDevicePropertyValue(MtpDeviceProperty property,
+											MtpDataPacket& packet) = 0;
+
+	virtual MtpResponseCode			resetDeviceProperty(MtpDeviceProperty property) = 0;
+
+	virtual MtpResponseCode			getObjectPropertyList(MtpObjectHandle handle,
+											uint32_t format, uint32_t property,
+											int groupCode, int depth,
+											MtpDataPacket& packet) = 0;
+
+	virtual MtpResponseCode			getObjectInfo(MtpObjectHandle handle,
+											MtpObjectInfo& info) = 0;
+
+	virtual void*					getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) = 0;
+
+	virtual MtpResponseCode			getObjectFilePath(MtpObjectHandle handle,
+											MtpStringBuffer& outFilePath,
+											int64_t& outFileLength,
+											MtpObjectFormat& outFormat) = 0;
+
+	// virtual MtpResponseCode		   deleteFile(MtpObjectHandle handle) = 0;
+
+	virtual MtpObjectHandleList*	getObjectReferences(MtpObjectHandle handle) = 0;
+
+	virtual MtpResponseCode			setObjectReferences(MtpObjectHandle handle,
+											MtpObjectHandleList* references) = 0;
+
+	virtual MtpProperty*			getObjectPropertyDesc(MtpObjectProperty property,
+											MtpObjectFormat format) = 0;
+
+	virtual MtpProperty*			getDevicePropertyDesc(MtpDeviceProperty property) = 0;
+
+	virtual void					sessionStarted() = 0;
+
+	virtual void					sessionEnded() = 0;
+};
+
+#endif // _MTP_DATABASE_H
diff --git a/mtp/ffs/MtpDebug.cpp b/mtp/ffs/MtpDebug.cpp
new file mode 100644
index 0000000..c5f5d43
--- /dev/null
+++ b/mtp/ffs/MtpDebug.cpp
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MtpDebug.h"
+
+#define MTP_DEBUG_BUFFER_SIZE 2048
+
+static int debug_enabled = 0;
+
+extern "C" void mtpdebug(const char *fmt, ...)
+{
+		if (debug_enabled) {
+				char buf[MTP_DEBUG_BUFFER_SIZE];				// We're going to limit a single request to 512 bytes
+
+				va_list ap;
+				va_start(ap, fmt);
+				vsnprintf(buf, MTP_DEBUG_BUFFER_SIZE, fmt, ap);
+				va_end(ap);
+
+				fputs(buf, stdout);
+		}
+}
+
+struct CodeEntry {
+	const char* name;
+	uint16_t code;
+};
+
+static const CodeEntry sOperationCodes[] = {
+	{ "MTP_OPERATION_GET_DEVICE_INFO",				0x1001 },
+	{ "MTP_OPERATION_OPEN_SESSION",					0x1002 },
+	{ "MTP_OPERATION_CLOSE_SESSION",				0x1003 },
+	{ "MTP_OPERATION_GET_STORAGE_IDS",				0x1004 },
+	{ "MTP_OPERATION_GET_STORAGE_INFO",				0x1005 },
+	{ "MTP_OPERATION_GET_NUM_OBJECTS",				0x1006 },
+	{ "MTP_OPERATION_GET_OBJECT_HANDLES",			0x1007 },
+	{ "MTP_OPERATION_GET_OBJECT_INFO",				0x1008 },
+	{ "MTP_OPERATION_GET_OBJECT",					0x1009 },
+	{ "MTP_OPERATION_GET_THUMB",					0x100A },
+	{ "MTP_OPERATION_DELETE_OBJECT",				0x100B },
+	{ "MTP_OPERATION_SEND_OBJECT_INFO",				0x100C },
+	{ "MTP_OPERATION_SEND_OBJECT",					0x100D },
+	{ "MTP_OPERATION_INITIATE_CAPTURE",				0x100E },
+	{ "MTP_OPERATION_FORMAT_STORE",					0x100F },
+	{ "MTP_OPERATION_RESET_DEVICE",					0x1010 },
+	{ "MTP_OPERATION_SELF_TEST",					0x1011 },
+	{ "MTP_OPERATION_SET_OBJECT_PROTECTION",		0x1012 },
+	{ "MTP_OPERATION_POWER_DOWN",					0x1013 },
+	{ "MTP_OPERATION_GET_DEVICE_PROP_DESC",			0x1014 },
+	{ "MTP_OPERATION_GET_DEVICE_PROP_VALUE",		0x1015 },
+	{ "MTP_OPERATION_SET_DEVICE_PROP_VALUE",		0x1016 },
+	{ "MTP_OPERATION_RESET_DEVICE_PROP_VALUE",		0x1017 },
+	{ "MTP_OPERATION_TERMINATE_OPEN_CAPTURE",		0x1018 },
+	{ "MTP_OPERATION_MOVE_OBJECT",					0x1019 },
+	{ "MTP_OPERATION_COPY_OBJECT",					0x101A },
+	{ "MTP_OPERATION_GET_PARTIAL_OBJECT",			0x101B },
+	{ "MTP_OPERATION_INITIATE_OPEN_CAPTURE",		0x101C },
+	{ "MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED",	0x9801 },
+	{ "MTP_OPERATION_GET_OBJECT_PROP_DESC",			0x9802 },
+	{ "MTP_OPERATION_GET_OBJECT_PROP_VALUE",		0x9803 },
+	{ "MTP_OPERATION_SET_OBJECT_PROP_VALUE",		0x9804 },
+	{ "MTP_OPERATION_GET_OBJECT_PROP_LIST",			0x9805 },
+	{ "MTP_OPERATION_SET_OBJECT_PROP_LIST",			0x9806 },
+	{ "MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC", 0x9807 },
+	{ "MTP_OPERATION_SEND_OBJECT_PROP_LIST",		0x9808 },
+	{ "MTP_OPERATION_GET_OBJECT_REFERENCES",		0x9810 },
+	{ "MTP_OPERATION_SET_OBJECT_REFERENCES",		0x9811 },
+	{ "MTP_OPERATION_SKIP",							0x9820 },
+	// android extensions
+	{ "MTP_OPERATION_GET_PARTIAL_OBJECT_64",		0x95C1 },
+	{ "MTP_OPERATION_SEND_PARTIAL_OBJECT",			0x95C2 },
+	{ "MTP_OPERATION_TRUNCATE_OBJECT",				0x95C3 },
+	{ "MTP_OPERATION_BEGIN_EDIT_OBJECT",			0x95C4 },
+	{ "MTP_OPERATION_END_EDIT_OBJECT",				0x95C5 },
+	{ 0,											0	   },
+};
+
+static const CodeEntry sFormatCodes[] = {
+	{ "MTP_FORMAT_UNDEFINED",						0x3000 },
+	{ "MTP_FORMAT_ASSOCIATION",						0x3001 },
+	{ "MTP_FORMAT_SCRIPT",							0x3002 },
+	{ "MTP_FORMAT_EXECUTABLE",						0x3003 },
+	{ "MTP_FORMAT_TEXT",							0x3004 },
+	{ "MTP_FORMAT_HTML",							0x3005 },
+	{ "MTP_FORMAT_DPOF",							0x3006 },
+	{ "MTP_FORMAT_AIFF",							0x3007 },
+	{ "MTP_FORMAT_WAV",								0x3008 },
+	{ "MTP_FORMAT_MP3",								0x3009 },
+	{ "MTP_FORMAT_AVI",								0x300A },
+	{ "MTP_FORMAT_MPEG",							0x300B },
+	{ "MTP_FORMAT_ASF",								0x300C },
+	{ "MTP_FORMAT_DEFINED",							0x3800 },
+	{ "MTP_FORMAT_EXIF_JPEG",						0x3801 },
+	{ "MTP_FORMAT_TIFF_EP",							0x3802 },
+	{ "MTP_FORMAT_FLASHPIX",						0x3803 },
+	{ "MTP_FORMAT_BMP",								0x3804 },
+	{ "MTP_FORMAT_CIFF",							0x3805 },
+	{ "MTP_FORMAT_GIF",								0x3807 },
+	{ "MTP_FORMAT_JFIF",							0x3808 },
+	{ "MTP_FORMAT_CD",								0x3809 },
+	{ "MTP_FORMAT_PICT",							0x380A },
+	{ "MTP_FORMAT_PNG",								0x380B },
+	{ "MTP_FORMAT_TIFF",							0x380D },
+	{ "MTP_FORMAT_TIFF_IT",							0x380E },
+	{ "MTP_FORMAT_JP2",								0x380F },
+	{ "MTP_FORMAT_JPX",								0x3810 },
+	{ "MTP_FORMAT_DNG",								0x3811 },
+	{ "MTP_FORMAT_HEIF",							0x3812 },
+	{ "MTP_FORMAT_UNDEFINED_FIRMWARE",				0xB802 },
+	{ "MTP_FORMAT_WINDOWS_IMAGE_FORMAT",			0xB881 },
+	{ "MTP_FORMAT_UNDEFINED_AUDIO",					0xB900 },
+	{ "MTP_FORMAT_WMA",								0xB901 },
+	{ "MTP_FORMAT_OGG",								0xB902 },
+	{ "MTP_FORMAT_AAC",								0xB903 },
+	{ "MTP_FORMAT_AUDIBLE",							0xB904 },
+	{ "MTP_FORMAT_FLAC",							0xB906 },
+	{ "MTP_FORMAT_UNDEFINED_VIDEO",					0xB980 },
+	{ "MTP_FORMAT_WMV",								0xB981 },
+	{ "MTP_FORMAT_MP4_CONTAINER",					0xB982 },
+	{ "MTP_FORMAT_MP2",								0xB983 },
+	{ "MTP_FORMAT_3GP_CONTAINER",					0xB984 },
+	{ "MTP_FORMAT_UNDEFINED_COLLECTION",			0xBA00 },
+	{ "MTP_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM",		0xBA01 },
+	{ "MTP_FORMAT_ABSTRACT_IMAGE_ALBUM",			0xBA02 },
+	{ "MTP_FORMAT_ABSTRACT_AUDIO_ALBUM",			0xBA03 },
+	{ "MTP_FORMAT_ABSTRACT_VIDEO_ALBUM",			0xBA04 },
+	{ "MTP_FORMAT_ABSTRACT_AV_PLAYLIST",			0xBA05 },
+	{ "MTP_FORMAT_ABSTRACT_CONTACT_GROUP",			0xBA06 },
+	{ "MTP_FORMAT_ABSTRACT_MESSAGE_FOLDER",			0xBA07 },
+	{ "MTP_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION",	0xBA08 },
+	{ "MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST",			0xBA09 },
+	{ "MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST",			0xBA0A },
+	{ "MTP_FORMAT_ABSTRACT_MEDIACAST",				0xBA0B },
+	{ "MTP_FORMAT_WPL_PLAYLIST",					0xBA10 },
+	{ "MTP_FORMAT_M3U_PLAYLIST",					0xBA11 },
+	{ "MTP_FORMAT_MPL_PLAYLIST",					0xBA12 },
+	{ "MTP_FORMAT_ASX_PLAYLIST",					0xBA13 },
+	{ "MTP_FORMAT_PLS_PLAYLIST",					0xBA14 },
+	{ "MTP_FORMAT_UNDEFINED_DOCUMENT",				0xBA80 },
+	{ "MTP_FORMAT_ABSTRACT_DOCUMENT",				0xBA81 },
+	{ "MTP_FORMAT_XML_DOCUMENT",					0xBA82 },
+	{ "MTP_FORMAT_MS_WORD_DOCUMENT",				0xBA83 },
+	{ "MTP_FORMAT_MHT_COMPILED_HTML_DOCUMENT",		0xBA84 },
+	{ "MTP_FORMAT_MS_EXCEL_SPREADSHEET",			0xBA85 },
+	{ "MTP_FORMAT_MS_POWERPOINT_PRESENTATION",		0xBA86 },
+	{ "MTP_FORMAT_UNDEFINED_MESSAGE",				0xBB00 },
+	{ "MTP_FORMAT_ABSTRACT_MESSSAGE",				0xBB01 },
+	{ "MTP_FORMAT_UNDEFINED_CONTACT",				0xBB80 },
+	{ "MTP_FORMAT_ABSTRACT_CONTACT",				0xBB81 },
+	{ "MTP_FORMAT_VCARD_2",							0xBB82 },
+	{ 0,											0	   },
+};
+
+static const CodeEntry sObjectPropCodes[] = {
+	{ "MTP_PROPERTY_STORAGE_ID",							 0xDC01 },
+	{ "MTP_PROPERTY_OBJECT_FORMAT",							 0xDC02 },
+	{ "MTP_PROPERTY_PROTECTION_STATUS",						 0xDC03 },
+	{ "MTP_PROPERTY_OBJECT_SIZE",							 0xDC04 },
+	{ "MTP_PROPERTY_ASSOCIATION_TYPE",						 0xDC05 },
+	{ "MTP_PROPERTY_ASSOCIATION_DESC",						 0xDC06 },
+	{ "MTP_PROPERTY_OBJECT_FILE_NAME",						 0xDC07 },
+	{ "MTP_PROPERTY_DATE_CREATED",							 0xDC08 },
+	{ "MTP_PROPERTY_DATE_MODIFIED",							 0xDC09 },
+	{ "MTP_PROPERTY_KEYWORDS",								 0xDC0A },
+	{ "MTP_PROPERTY_PARENT_OBJECT",							 0xDC0B },
+	{ "MTP_PROPERTY_ALLOWED_FOLDER_CONTENTS",				 0xDC0C },
+	{ "MTP_PROPERTY_HIDDEN",								 0xDC0D },
+	{ "MTP_PROPERTY_SYSTEM_OBJECT",							 0xDC0E },
+	{ "MTP_PROPERTY_PERSISTENT_UID",						 0xDC41 },
+	{ "MTP_PROPERTY_SYNC_ID",								 0xDC42 },
+	{ "MTP_PROPERTY_PROPERTY_BAG",							 0xDC43 },
+	{ "MTP_PROPERTY_NAME",									 0xDC44 },
+	{ "MTP_PROPERTY_CREATED_BY",							 0xDC45 },
+	{ "MTP_PROPERTY_ARTIST",								 0xDC46 },
+	{ "MTP_PROPERTY_DATE_AUTHORED",							 0xDC47 },
+	{ "MTP_PROPERTY_DESCRIPTION",							 0xDC48 },
+	{ "MTP_PROPERTY_URL_REFERENCE",							 0xDC49 },
+	{ "MTP_PROPERTY_LANGUAGE_LOCALE",						 0xDC4A },
+	{ "MTP_PROPERTY_COPYRIGHT_INFORMATION",					 0xDC4B },
+	{ "MTP_PROPERTY_SOURCE",								 0xDC4C },
+	{ "MTP_PROPERTY_ORIGIN_LOCATION",						 0xDC4D },
+	{ "MTP_PROPERTY_DATE_ADDED",							 0xDC4E },
+	{ "MTP_PROPERTY_NON_CONSUMABLE",						 0xDC4F },
+	{ "MTP_PROPERTY_CORRUPT_UNPLAYABLE",					 0xDC50 },
+	{ "MTP_PROPERTY_PRODUCER_SERIAL_NUMBER",				 0xDC51 },
+	{ "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_FORMAT",			 0xDC81 },
+	{ "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_SIZE",			 0xDC82 },
+	{ "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_HEIGHT",			 0xDC83 },
+	{ "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_WIDTH",			 0xDC84 },
+	{ "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DURATION",		 0xDC85 },
+	{ "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DATA",			 0xDC86 },
+	{ "MTP_PROPERTY_WIDTH",									 0xDC87 },
+	{ "MTP_PROPERTY_HEIGHT",								 0xDC88 },
+	{ "MTP_PROPERTY_DURATION",								 0xDC89 },
+	{ "MTP_PROPERTY_RATING",								 0xDC8A },
+	{ "MTP_PROPERTY_TRACK",									 0xDC8B },
+	{ "MTP_PROPERTY_GENRE",									 0xDC8C },
+	{ "MTP_PROPERTY_CREDITS",								 0xDC8D },
+	{ "MTP_PROPERTY_LYRICS",								 0xDC8E },
+	{ "MTP_PROPERTY_SUBSCRIPTION_CONTENT_ID",				 0xDC8F },
+	{ "MTP_PROPERTY_PRODUCED_BY",							 0xDC90 },
+	{ "MTP_PROPERTY_USE_COUNT",								 0xDC91 },
+	{ "MTP_PROPERTY_SKIP_COUNT",							 0xDC92 },
+	{ "MTP_PROPERTY_LAST_ACCESSED",							 0xDC93 },
+	{ "MTP_PROPERTY_PARENTAL_RATING",						 0xDC94 },
+	{ "MTP_PROPERTY_META_GENRE",							 0xDC95 },
+	{ "MTP_PROPERTY_COMPOSER",								 0xDC96 },
+	{ "MTP_PROPERTY_EFFECTIVE_RATING",						 0xDC97 },
+	{ "MTP_PROPERTY_SUBTITLE",								 0xDC98 },
+	{ "MTP_PROPERTY_ORIGINAL_RELEASE_DATE",					 0xDC99 },
+	{ "MTP_PROPERTY_ALBUM_NAME",							 0xDC9A },
+	{ "MTP_PROPERTY_ALBUM_ARTIST",							 0xDC9B },
+	{ "MTP_PROPERTY_MOOD",									 0xDC9C },
+	{ "MTP_PROPERTY_DRM_STATUS",							 0xDC9D },
+	{ "MTP_PROPERTY_SUB_DESCRIPTION",						 0xDC9E },
+	{ "MTP_PROPERTY_IS_CROPPED",							 0xDCD1 },
+	{ "MTP_PROPERTY_IS_COLOUR_CORRECTED",					 0xDCD2 },
+	{ "MTP_PROPERTY_IMAGE_BIT_DEPTH",						 0xDCD3 },
+	{ "MTP_PROPERTY_F_NUMBER",								 0xDCD4 },
+	{ "MTP_PROPERTY_EXPOSURE_TIME",							 0xDCD5 },
+	{ "MTP_PROPERTY_EXPOSURE_INDEX",						 0xDCD6 },
+	{ "MTP_PROPERTY_TOTAL_BITRATE",							 0xDE91 },
+	{ "MTP_PROPERTY_BITRATE_TYPE",							 0xDE92 },
+	{ "MTP_PROPERTY_SAMPLE_RATE",							 0xDE93 },
+	{ "MTP_PROPERTY_NUMBER_OF_CHANNELS",					 0xDE94 },
+	{ "MTP_PROPERTY_AUDIO_BIT_DEPTH",						 0xDE95 },
+	{ "MTP_PROPERTY_SCAN_TYPE",								 0xDE97 },
+	{ "MTP_PROPERTY_AUDIO_WAVE_CODEC",						 0xDE99 },
+	{ "MTP_PROPERTY_AUDIO_BITRATE",							 0xDE9A },
+	{ "MTP_PROPERTY_VIDEO_FOURCC_CODEC",					 0xDE9B },
+	{ "MTP_PROPERTY_VIDEO_BITRATE",							 0xDE9C },
+	{ "MTP_PROPERTY_FRAMES_PER_THOUSAND_SECONDS",			 0xDE9D },
+	{ "MTP_PROPERTY_KEYFRAME_DISTANCE",						 0xDE9E },
+	{ "MTP_PROPERTY_BUFFER_SIZE",							 0xDE9F },
+	{ "MTP_PROPERTY_ENCODING_QUALITY",						 0xDEA0 },
+	{ "MTP_PROPERTY_ENCODING_PROFILE",						 0xDEA1 },
+	{ "MTP_PROPERTY_DISPLAY_NAME",							 0xDCE0 },
+	{ "MTP_PROPERTY_BODY_TEXT",								 0xDCE1 },
+	{ "MTP_PROPERTY_SUBJECT",								 0xDCE2 },
+	{ "MTP_PROPERTY_PRIORITY",								 0xDCE3 },
+	{ "MTP_PROPERTY_GIVEN_NAME",							 0xDD00 },
+	{ "MTP_PROPERTY_MIDDLE_NAMES",							 0xDD01 },
+	{ "MTP_PROPERTY_FAMILY_NAME",							 0xDD02 },
+	{ "MTP_PROPERTY_PREFIX",								 0xDD03 },
+	{ "MTP_PROPERTY_SUFFIX",								 0xDD04 },
+	{ "MTP_PROPERTY_PHONETIC_GIVEN_NAME",					 0xDD05 },
+	{ "MTP_PROPERTY_PHONETIC_FAMILY_NAME",					 0xDD06 },
+	{ "MTP_PROPERTY_EMAIL_PRIMARY",							 0xDD07 },
+	{ "MTP_PROPERTY_EMAIL_PERSONAL_1",						 0xDD08 },
+	{ "MTP_PROPERTY_EMAIL_PERSONAL_2",						 0xDD09 },
+	{ "MTP_PROPERTY_EMAIL_BUSINESS_1",						 0xDD0A },
+	{ "MTP_PROPERTY_EMAIL_BUSINESS_2",						 0xDD0B },
+	{ "MTP_PROPERTY_EMAIL_OTHERS",							 0xDD0C },
+	{ "MTP_PROPERTY_PHONE_NUMBER_PRIMARY",					 0xDD0D },
+	{ "MTP_PROPERTY_PHONE_NUMBER_PERSONAL",					 0xDD0E },
+	{ "MTP_PROPERTY_PHONE_NUMBER_PERSONAL_2",				 0xDD0F },
+	{ "MTP_PROPERTY_PHONE_NUMBER_BUSINESS",					 0xDD10 },
+	{ "MTP_PROPERTY_PHONE_NUMBER_BUSINESS_2",				 0xDD11 },
+	{ "MTP_PROPERTY_PHONE_NUMBER_MOBILE",					 0xDD12 },
+	{ "MTP_PROPERTY_PHONE_NUMBER_MOBILE_2",					 0xDD13 },
+	{ "MTP_PROPERTY_FAX_NUMBER_PRIMARY",					 0xDD14 },
+	{ "MTP_PROPERTY_FAX_NUMBER_PERSONAL",					 0xDD15 },
+	{ "MTP_PROPERTY_FAX_NUMBER_BUSINESS",					 0xDD16 },
+	{ "MTP_PROPERTY_PAGER_NUMBER",							 0xDD17 },
+	{ "MTP_PROPERTY_PHONE_NUMBER_OTHERS",					 0xDD18 },
+	{ "MTP_PROPERTY_PRIMARY_WEB_ADDRESS",					 0xDD19 },
+	{ "MTP_PROPERTY_PERSONAL_WEB_ADDRESS",					 0xDD1A },
+	{ "MTP_PROPERTY_BUSINESS_WEB_ADDRESS",					 0xDD1B },
+	{ "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS",				 0xDD1C },
+	{ "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_2",			 0xDD1D },
+	{ "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_3",			 0xDD1E },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_FULL",			 0xDD1F },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_1",		 0xDD20 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_2",		 0xDD21 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_CITY",			 0xDD22 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_REGION",		 0xDD23 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE",	 0xDD24 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_COUNTRY",		 0xDD25 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_FULL",			 0xDD26 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_1",		 0xDD27 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_2",		 0xDD28 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_CITY",			 0xDD29 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_REGION",		 0xDD2A },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE",	 0xDD2B },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_COUNTRY",		 0xDD2C },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_FULL",				 0xDD2D },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_1",			 0xDD2E },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_2",			 0xDD2F },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_CITY",				 0xDD30 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_REGION",			 0xDD31 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_POSTAL_CODE",		 0xDD32 },
+	{ "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_COUNTRY",			 0xDD33 },
+	{ "MTP_PROPERTY_ORGANIZATION_NAME",						 0xDD34 },
+	{ "MTP_PROPERTY_PHONETIC_ORGANIZATION_NAME",			 0xDD35 },
+	{ "MTP_PROPERTY_ROLE",									 0xDD36 },
+	{ "MTP_PROPERTY_BIRTHDATE",								 0xDD37 },
+	{ "MTP_PROPERTY_MESSAGE_TO",							 0xDD40 },
+	{ "MTP_PROPERTY_MESSAGE_CC",							 0xDD41 },
+	{ "MTP_PROPERTY_MESSAGE_BCC",							 0xDD42 },
+	{ "MTP_PROPERTY_MESSAGE_READ",							 0xDD43 },
+	{ "MTP_PROPERTY_MESSAGE_RECEIVED_TIME",					 0xDD44 },
+	{ "MTP_PROPERTY_MESSAGE_SENDER",						 0xDD45 },
+	{ "MTP_PROPERTY_ACTIVITY_BEGIN_TIME",					 0xDD50 },
+	{ "MTP_PROPERTY_ACTIVITY_END_TIME",						 0xDD51 },
+	{ "MTP_PROPERTY_ACTIVITY_LOCATION",						 0xDD52 },
+	{ "MTP_PROPERTY_ACTIVITY_REQUIRED_ATTENDEES",			 0xDD54 },
+	{ "MTP_PROPERTY_ACTIVITY_OPTIONAL_ATTENDEES",			 0xDD55 },
+	{ "MTP_PROPERTY_ACTIVITY_RESOURCES",					 0xDD56 },
+	{ "MTP_PROPERTY_ACTIVITY_ACCEPTED",						 0xDD57 },
+	{ "MTP_PROPERTY_ACTIVITY_TENTATIVE",					 0xDD58 },
+	{ "MTP_PROPERTY_ACTIVITY_DECLINED",						 0xDD59 },
+	{ "MTP_PROPERTY_ACTIVITY_REMAINDER_TIME",				 0xDD5A },
+	{ "MTP_PROPERTY_ACTIVITY_OWNER",						 0xDD5B },
+	{ "MTP_PROPERTY_ACTIVITY_STATUS",						 0xDD5C },
+	{ "MTP_PROPERTY_OWNER",									 0xDD5D },
+	{ "MTP_PROPERTY_EDITOR",								 0xDD5E },
+	{ "MTP_PROPERTY_WEBMASTER",								 0xDD5F },
+	{ "MTP_PROPERTY_URL_SOURCE",							 0xDD60 },
+	{ "MTP_PROPERTY_URL_DESTINATION",						 0xDD61 },
+	{ "MTP_PROPERTY_TIME_BOOKMARK",							 0xDD62 },
+	{ "MTP_PROPERTY_OBJECT_BOOKMARK",						 0xDD63 },
+	{ "MTP_PROPERTY_BYTE_BOOKMARK",							 0xDD64 },
+	{ "MTP_PROPERTY_LAST_BUILD_DATE",						 0xDD70 },
+	{ "MTP_PROPERTY_TIME_TO_LIVE",							 0xDD71 },
+	{ "MTP_PROPERTY_MEDIA_GUID",							 0xDD72 },
+	{ 0,													 0		},
+};
+
+static const CodeEntry sDevicePropCodes[] = {
+	{ "MTP_DEVICE_PROPERTY_UNDEFINED",						 0x5000 },
+	{ "MTP_DEVICE_PROPERTY_BATTERY_LEVEL",					 0x5001 },
+	{ "MTP_DEVICE_PROPERTY_FUNCTIONAL_MODE",				 0x5002 },
+	{ "MTP_DEVICE_PROPERTY_IMAGE_SIZE",						 0x5003 },
+	{ "MTP_DEVICE_PROPERTY_COMPRESSION_SETTING",			 0x5004 },
+	{ "MTP_DEVICE_PROPERTY_WHITE_BALANCE",					 0x5005 },
+	{ "MTP_DEVICE_PROPERTY_RGB_GAIN",						 0x5006 },
+	{ "MTP_DEVICE_PROPERTY_F_NUMBER",						 0x5007 },
+	{ "MTP_DEVICE_PROPERTY_FOCAL_LENGTH",					 0x5008 },
+	{ "MTP_DEVICE_PROPERTY_FOCUS_DISTANCE",					 0x5009 },
+	{ "MTP_DEVICE_PROPERTY_FOCUS_MODE",						 0x500A },
+	{ "MTP_DEVICE_PROPERTY_EXPOSURE_METERING_MODE",			 0x500B },
+	{ "MTP_DEVICE_PROPERTY_FLASH_MODE",						 0x500C },
+	{ "MTP_DEVICE_PROPERTY_EXPOSURE_TIME",					 0x500D },
+	{ "MTP_DEVICE_PROPERTY_EXPOSURE_PROGRAM_MODE",			 0x500E },
+	{ "MTP_DEVICE_PROPERTY_EXPOSURE_INDEX",					 0x500F },
+	{ "MTP_DEVICE_PROPERTY_EXPOSURE_BIAS_COMPENSATION",		 0x5010 },
+	{ "MTP_DEVICE_PROPERTY_DATETIME",						 0x5011 },
+	{ "MTP_DEVICE_PROPERTY_CAPTURE_DELAY",					 0x5012 },
+	{ "MTP_DEVICE_PROPERTY_STILL_CAPTURE_MODE",				 0x5013 },
+	{ "MTP_DEVICE_PROPERTY_CONTRAST",						 0x5014 },
+	{ "MTP_DEVICE_PROPERTY_SHARPNESS",						 0x5015 },
+	{ "MTP_DEVICE_PROPERTY_DIGITAL_ZOOM",					 0x5016 },
+	{ "MTP_DEVICE_PROPERTY_EFFECT_MODE",					 0x5017 },
+	{ "MTP_DEVICE_PROPERTY_BURST_NUMBER",					 0x5018 },
+	{ "MTP_DEVICE_PROPERTY_BURST_INTERVAL",					 0x5019 },
+	{ "MTP_DEVICE_PROPERTY_TIMELAPSE_NUMBER",				 0x501A },
+	{ "MTP_DEVICE_PROPERTY_TIMELAPSE_INTERVAL",				 0x501B },
+	{ "MTP_DEVICE_PROPERTY_FOCUS_METERING_MODE",			 0x501C },
+	{ "MTP_DEVICE_PROPERTY_UPLOAD_URL",						 0x501D },
+	{ "MTP_DEVICE_PROPERTY_ARTIST",							 0x501E },
+	{ "MTP_DEVICE_PROPERTY_COPYRIGHT_INFO",					 0x501F },
+	{ "MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER",		 0xD401 },
+	{ "MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME",			 0xD402 },
+	{ "MTP_DEVICE_PROPERTY_VOLUME",							 0xD403 },
+	{ "MTP_DEVICE_PROPERTY_SUPPORTED_FORMATS_ORDERED",		 0xD404 },
+	{ "MTP_DEVICE_PROPERTY_DEVICE_ICON",					 0xD405 },
+	{ "MTP_DEVICE_PROPERTY_PLAYBACK_RATE",					 0xD410 },
+	{ "MTP_DEVICE_PROPERTY_PLAYBACK_OBJECT",				 0xD411 },
+	{ "MTP_DEVICE_PROPERTY_PLAYBACK_CONTAINER_INDEX",		 0xD412 },
+	{ "MTP_DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO",  0xD406 },
+	{ "MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE",			 0xD407 },
+	{ 0,													 0		},
+};
+
+static const char* getCodeName(uint16_t code, const CodeEntry* table) {
+	const CodeEntry* entry = table;
+	while (entry->name) {
+		if (entry->code == code)
+			return entry->name;
+		entry++;
+	}
+	return "UNKNOWN";
+}
+
+const char* MtpDebug::getOperationCodeName(MtpOperationCode code) {
+	return getCodeName(code, sOperationCodes);
+}
+
+const char* MtpDebug::getFormatCodeName(MtpObjectFormat code) {
+	if (code == 0)
+		return "NONE";
+	return getCodeName(code, sFormatCodes);
+}
+
+const char* MtpDebug::getObjectPropCodeName(MtpPropertyCode code) {
+	if (code == 0)
+		return "NONE";
+	return getCodeName(code, sObjectPropCodes);
+}
+
+const char* MtpDebug::getDevicePropCodeName(MtpPropertyCode code) {
+	if (code == 0)
+		return "NONE";
+	return getCodeName(code, sDevicePropCodes);
+}
+
+void MtpDebug::enableDebug(void) {
+	debug_enabled = 1;
+	MTPD("MTP debug logging enabled\n");
+}
+
diff --git a/mtp/ffs/MtpDebug.h b/mtp/ffs/MtpDebug.h
new file mode 100644
index 0000000..61ae4b7
--- /dev/null
+++ b/mtp/ffs/MtpDebug.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DEBUG_H
+#define _MTP_DEBUG_H
+
+// #define LOG_NDEBUG 0
+#include "MtpTypes.h"
+
+#include <log/log.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+void mtpdebug(const char *fmt, ...);
+
+#define MTPI(...) fprintf(stdout, "I:[MTP] " __VA_ARGS__)
+#define MTPD(...) mtpdebug("D:[MTP] " __VA_ARGS__)
+#define MTPE(...) fprintf(stdout, "E:[MTP] " __VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+class MtpDebug {
+public:
+	static const char* getOperationCodeName(MtpOperationCode code);
+	static const char* getFormatCodeName(MtpObjectFormat code);
+	static const char* getObjectPropCodeName(MtpPropertyCode code);
+	static const char* getDevicePropCodeName(MtpPropertyCode code);
+	static void enableDebug();
+};
+
+#endif // _MTP_DEBUG_H
diff --git a/mtp/ffs/MtpDescriptors.cpp b/mtp/ffs/MtpDescriptors.cpp
new file mode 100644
index 0000000..54306a9
--- /dev/null
+++ b/mtp/ffs/MtpDescriptors.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <sys/types.h>
+#include <cutils/properties.h>
+
+#include "MtpDescriptors.h"
+#include "MtpDebug.h"
+
+const struct usb_interface_descriptor mtp_interface_desc = {
+	.bLength = USB_DT_INTERFACE_SIZE,
+	.bDescriptorType = USB_DT_INTERFACE,
+	.bInterfaceNumber = 0,
+	.bNumEndpoints = 3,
+	.bInterfaceClass = USB_CLASS_STILL_IMAGE,
+	.bInterfaceSubClass = 1,
+	.bInterfaceProtocol = 1,
+	.iInterface = 1,
+};
+
+const struct usb_interface_descriptor ptp_interface_desc = {
+	.bLength = USB_DT_INTERFACE_SIZE,
+	.bDescriptorType = USB_DT_INTERFACE,
+	.bInterfaceNumber = 0,
+	.bNumEndpoints = 3,
+	.bInterfaceClass = USB_CLASS_STILL_IMAGE,
+	.bInterfaceSubClass = 1,
+	.bInterfaceProtocol = 1,
+};
+
+const struct usb_endpoint_descriptor_no_audio fs_sink = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bEndpointAddress = 1 | USB_DIR_IN,
+	.bmAttributes = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize = MAX_PACKET_SIZE_FS,
+};
+
+const struct usb_endpoint_descriptor_no_audio fs_source = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bEndpointAddress = 2 | USB_DIR_OUT,
+	.bmAttributes = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize = MAX_PACKET_SIZE_FS,
+};
+
+const struct usb_endpoint_descriptor_no_audio intr = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bEndpointAddress = 3 | USB_DIR_IN,
+	.bmAttributes = USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize = MAX_PACKET_SIZE_EV,
+	.bInterval = 6,
+};
+
+const struct usb_endpoint_descriptor_no_audio hs_sink = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bEndpointAddress = 1 | USB_DIR_IN,
+	.bmAttributes = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize = MAX_PACKET_SIZE_HS,
+};
+
+const struct usb_endpoint_descriptor_no_audio hs_source = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bEndpointAddress = 2 | USB_DIR_OUT,
+	.bmAttributes = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize = MAX_PACKET_SIZE_HS,
+};
+
+const struct usb_endpoint_descriptor_no_audio ss_sink = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bEndpointAddress = 1 | USB_DIR_IN,
+	.bmAttributes = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize = MAX_PACKET_SIZE_SS,
+};
+
+const struct usb_endpoint_descriptor_no_audio ss_source = {
+	.bLength = USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType = USB_DT_ENDPOINT,
+	.bEndpointAddress = 2 | USB_DIR_OUT,
+	.bmAttributes = USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize = MAX_PACKET_SIZE_SS,
+};
+
+const struct usb_ss_ep_comp_descriptor ss_sink_comp = {
+	.bLength = sizeof(ss_sink_comp),
+	.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+	.bMaxBurst = 6,
+};
+
+const struct usb_ss_ep_comp_descriptor ss_source_comp = {
+	.bLength = sizeof(ss_source_comp),
+	.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+	.bMaxBurst = 6,
+};
+
+const struct usb_ss_ep_comp_descriptor ss_intr_comp = {
+	.bLength = sizeof(ss_intr_comp),
+	.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+};
+
+const struct func_desc mtp_fs_descriptors = {
+	.intf = mtp_interface_desc,
+	.sink = fs_sink,
+	.source = fs_source,
+	.intr = intr,
+};
+
+const struct func_desc mtp_hs_descriptors = {
+	.intf = mtp_interface_desc,
+	.sink = hs_sink,
+	.source = hs_source,
+	.intr = intr,
+};
+
+const struct ss_func_desc mtp_ss_descriptors = {
+	.intf = mtp_interface_desc,
+	.sink = ss_sink,
+	.sink_comp = ss_sink_comp,
+	.source = ss_source,
+	.source_comp = ss_source_comp,
+	.intr = intr,
+	.intr_comp = ss_intr_comp,
+};
+
+const struct func_desc ptp_fs_descriptors = {
+	.intf = ptp_interface_desc,
+	.sink = fs_sink,
+	.source = fs_source,
+	.intr = intr,
+};
+
+const struct func_desc ptp_hs_descriptors = {
+	.intf = ptp_interface_desc,
+	.sink = hs_sink,
+	.source = hs_source,
+	.intr = intr,
+};
+
+const struct ss_func_desc ptp_ss_descriptors = {
+	.intf = ptp_interface_desc,
+	.sink = ss_sink,
+	.sink_comp = ss_sink_comp,
+	.source = ss_source,
+	.source_comp = ss_source_comp,
+	.intr = intr,
+	.intr_comp = ss_intr_comp,
+};
+
+const struct functionfs_strings mtp_strings = {
+	.header = {
+		.magic = htole32(FUNCTIONFS_STRINGS_MAGIC),
+		.length = htole32(sizeof(mtp_strings)),
+		.str_count = htole32(1),
+		.lang_count = htole32(1),
+	},
+	.lang0 = {
+		.code = htole16(0x0409),
+		.str1 = STR_INTERFACE,
+	},
+};
+
+const struct usb_os_desc_header mtp_os_desc_header = {
+	.interface = htole32(1),
+	.dwLength = htole32(sizeof(usb_os_desc_header) + sizeof(usb_ext_compat_desc)),
+	.bcdVersion = htole16(1),
+	.wIndex = htole16(4),
+	.bCount = htole16(1),
+	.Reserved = htole16(0),
+};
+
+const struct usb_ext_compat_desc mtp_os_desc_compat = {
+	.bFirstInterfaceNumber = 0,
+	.Reserved1 = htole32(1),
+	.CompatibleID = { 'M', 'T', 'P' },
+	.SubCompatibleID = {0},
+	.Reserved2 = {0},
+};
+
+const struct usb_ext_compat_desc ptp_os_desc_compat = {
+	.bFirstInterfaceNumber = 0,
+	.Reserved1 = htole32(1),
+	.CompatibleID = { 'P', 'T', 'P' },
+	.SubCompatibleID = {0},
+	.Reserved2 = {0},
+};
+
+const struct desc_v2 mtp_desc_v2 = {
+	.header = {
+		.magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2),
+		.length = htole32(sizeof(struct desc_v2)),
+		.flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC |
+				 FUNCTIONFS_HAS_SS_DESC | FUNCTIONFS_HAS_MS_OS_DESC,
+	},
+	.fs_count = 4,
+	.hs_count = 4,
+	.ss_count = 7,
+	.os_count = 1,
+	.fs_descs = mtp_fs_descriptors,
+	.hs_descs = mtp_hs_descriptors,
+	.ss_descs = mtp_ss_descriptors,
+	.os_header = mtp_os_desc_header,
+	.os_desc = mtp_os_desc_compat,
+};
+
+const struct desc_v2 ptp_desc_v2 = {
+	.header = {
+		.magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2),
+		.length = htole32(sizeof(struct desc_v2)),
+		.flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC |
+				 FUNCTIONFS_HAS_SS_DESC | FUNCTIONFS_HAS_MS_OS_DESC,
+	},
+	.fs_count = 4,
+	.hs_count = 4,
+	.ss_count = 7,
+	.os_count = 1,
+	.fs_descs = ptp_fs_descriptors,
+	.hs_descs = ptp_hs_descriptors,
+	.ss_descs = ptp_ss_descriptors,
+	.os_header = mtp_os_desc_header,
+	.os_desc = ptp_os_desc_compat,
+};
+
+const struct desc_v1 mtp_desc_v1 = {
+	.header = {
+		.magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+		.length = htole32(sizeof(struct desc_v1)),
+		.fs_count = 4,
+		.hs_count = 4,
+	},
+	.fs_descs = mtp_fs_descriptors,
+	.hs_descs = mtp_hs_descriptors,
+};
+
+const struct desc_v1 ptp_desc_v1 = {
+	.header = {
+		.magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+		.length = htole32(sizeof(struct desc_v1)),
+		.fs_count = 4,
+		.hs_count = 4,
+	},
+	.fs_descs = ptp_fs_descriptors,
+	.hs_descs = ptp_hs_descriptors,
+};
+
+bool writeDescriptors(int fd, bool ptp) {
+	ssize_t ret = TEMP_FAILURE_RETRY(write(fd,
+				&(ptp ? ptp_desc_v2 : mtp_desc_v2), sizeof(desc_v2)));
+	if (ret < 0) {
+		MTPE("Switching to V1 descriptor format\n");
+		ret = TEMP_FAILURE_RETRY(write(fd,
+					&(ptp ? ptp_desc_v1 : mtp_desc_v1), sizeof(desc_v1)));
+		if (ret < 0) {
+			MTPE("Writing descriptors failed\n");
+			return false;
+		}
+	}
+	ret = TEMP_FAILURE_RETRY(write(fd, &mtp_strings, sizeof(mtp_strings)));
+	if (ret < 0) {
+		MTPE("Writing strings failed\n");
+		return false;
+	}
+	property_set("sys.usb.ffs.mtp.ready", "1");
+	return true;
+}
diff --git a/mtp/ffs/MtpDescriptors.h b/mtp/ffs/MtpDescriptors.h
new file mode 100644
index 0000000..f362d80
--- /dev/null
+++ b/mtp/ffs/MtpDescriptors.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MTP_DESCRIPTORS_H
+#define MTP_DESCRIPTORS_H
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/functionfs.h>
+#include <sys/endian.h>
+
+constexpr char FFS_MTP_EP0[] = "/dev/usb-ffs/mtp/ep0";
+constexpr char FFS_MTP_EP_IN[] = "/dev/usb-ffs/mtp/ep1";
+constexpr char FFS_MTP_EP_OUT[] = "/dev/usb-ffs/mtp/ep2";
+constexpr char FFS_MTP_EP_INTR[] = "/dev/usb-ffs/mtp/ep3";
+
+constexpr char FFS_PTP_EP0[] = "/dev/usb-ffs/ptp/ep0";
+constexpr char FFS_PTP_EP_IN[] = "/dev/usb-ffs/ptp/ep1";
+constexpr char FFS_PTP_EP_OUT[] = "/dev/usb-ffs/ptp/ep2";
+constexpr char FFS_PTP_EP_INTR[] = "/dev/usb-ffs/ptp/ep3";
+
+constexpr int MAX_PACKET_SIZE_FS = 64;
+constexpr int MAX_PACKET_SIZE_HS = 512;
+constexpr int MAX_PACKET_SIZE_SS = 1024;
+constexpr int MAX_PACKET_SIZE_EV = 28;
+
+struct func_desc {
+	struct usb_interface_descriptor intf;
+	struct usb_endpoint_descriptor_no_audio sink;
+	struct usb_endpoint_descriptor_no_audio source;
+	struct usb_endpoint_descriptor_no_audio intr;
+} __attribute__((packed));
+
+struct ss_func_desc {
+	struct usb_interface_descriptor intf;
+	struct usb_endpoint_descriptor_no_audio sink;
+	struct usb_ss_ep_comp_descriptor sink_comp;
+	struct usb_endpoint_descriptor_no_audio source;
+	struct usb_ss_ep_comp_descriptor source_comp;
+	struct usb_endpoint_descriptor_no_audio intr;
+	struct usb_ss_ep_comp_descriptor intr_comp;
+} __attribute__((packed));
+
+struct desc_v1 {
+	struct usb_functionfs_descs_head_v1 {
+		__le32 magic;
+		__le32 length;
+		__le32 fs_count;
+		__le32 hs_count;
+	} __attribute__((packed)) header;
+	struct func_desc fs_descs, hs_descs;
+} __attribute__((packed));
+
+struct desc_v2 {
+	struct usb_functionfs_descs_head_v2 header;
+	// The rest of the structure depends on the flags in the header.
+	__le32 fs_count;
+	__le32 hs_count;
+	__le32 ss_count;
+	__le32 os_count;
+	struct func_desc fs_descs, hs_descs;
+	struct ss_func_desc ss_descs;
+	struct usb_os_desc_header os_header;
+	struct usb_ext_compat_desc os_desc;
+} __attribute__((packed));
+
+// OS descriptor contents should not be changed. See b/64790536.
+static_assert(sizeof(struct desc_v2) == sizeof(usb_functionfs_descs_head_v2) +
+		16 + 2 * sizeof(struct func_desc) + sizeof(struct ss_func_desc) +
+		sizeof(usb_os_desc_header) + sizeof(usb_ext_compat_desc),
+		"Size of mtp descriptor is incorrect!");
+
+#define STR_INTERFACE "MTP"
+struct functionfs_lang {
+	__le16 code;
+	char str1[sizeof(STR_INTERFACE)];
+} __attribute__((packed));
+
+struct functionfs_strings {
+	struct usb_functionfs_strings_head header;
+	struct functionfs_lang lang0;
+} __attribute__((packed));
+
+extern const struct desc_v2 mtp_desc_v2;
+extern const struct desc_v2 ptp_desc_v2;
+extern const struct desc_v1 mtp_desc_v1;
+extern const struct desc_v1 ptp_desc_v1;
+extern const struct functionfs_strings mtp_strings;
+
+bool writeDescriptors(int fd, bool ptp);
+
+#endif // MTP_DESCRIPTORS_H
diff --git a/mtp/ffs/MtpDevHandle.cpp b/mtp/ffs/MtpDevHandle.cpp
new file mode 100644
index 0000000..e22ba55
--- /dev/null
+++ b/mtp/ffs/MtpDevHandle.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or		   implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <cutils/properties.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/usb/ch9.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <unistd.h>
+
+#include "MtpDevHandle.h"
+
+constexpr char mtp_dev_path[] = "/dev/mtp_usb";
+
+MtpDevHandle::MtpDevHandle(int controlFd) {
+	mFd.reset(controlFd);
+}
+
+MtpDevHandle::~MtpDevHandle() {}
+
+int MtpDevHandle::read(void *data, size_t len) {
+	return ::read(mFd, data, len);
+}
+
+int MtpDevHandle::write(const void *data, size_t len) {
+	return ::write(mFd, data, len);
+}
+
+int MtpDevHandle::receiveFile(mtp_file_range mfr, bool) {
+	return ioctl(mFd, MTP_RECEIVE_FILE, reinterpret_cast<unsigned long>(&mfr));
+}
+
+int MtpDevHandle::sendFile(mtp_file_range mfr) {
+	return ioctl(mFd, MTP_SEND_FILE_WITH_HEADER, reinterpret_cast<unsigned long>(&mfr));
+}
+
+int MtpDevHandle::sendEvent(mtp_event me) {
+	return ioctl(mFd, MTP_SEND_EVENT, reinterpret_cast<unsigned long>(&me));
+}
+
+int MtpDevHandle::start(bool /* ptp */) {
+	mFd.reset(TEMP_FAILURE_RETRY(open(mtp_dev_path, O_RDWR)));
+	if (mFd == -1) return -1;
+	return 0;
+}
+
+void MtpDevHandle::close() {
+	mFd.reset();
+}
+
+bool MtpDevHandle::writeDescriptors(bool usePtp) { return usePtp; }
diff --git a/mtp/ffs/MtpDevHandle.h b/mtp/ffs/MtpDevHandle.h
new file mode 100644
index 0000000..4ea1fdb
--- /dev/null
+++ b/mtp/ffs/MtpDevHandle.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DEV_HANDLE_H
+#define _MTP_DEV_HANDLE_H
+
+#include <android-base/unique_fd.h>
+#include "IMtpHandle.h"
+
+class MtpDevHandle : public IMtpHandle {
+private:
+	android::base::unique_fd mFd;
+
+public:
+	MtpDevHandle(int controlFd);
+	~MtpDevHandle();
+	int read(void *data, size_t len);
+	int write(const void *data, size_t len);
+
+	int receiveFile(mtp_file_range mfr, bool);
+	int sendFile(mtp_file_range mfr);
+	int sendEvent(mtp_event me);
+
+	int start(bool ptp);
+	void close();
+	bool writeDescriptors(bool usePtp);
+};
+
+#endif // _MTP_FFS_HANDLE_H
diff --git a/mtp/ffs/MtpDevice.cpp b/mtp/ffs/MtpDevice.cpp
new file mode 100644
index 0000000..8a50ef1
--- /dev/null
+++ b/mtp/ffs/MtpDevice.cpp
@@ -0,0 +1,946 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpDevice"
+
+#include "MtpDebug.h"
+#include "MtpDevice.h"
+#include "MtpDeviceInfo.h"
+#include "MtpEventPacket.h"
+#include "MtpObjectInfo.h"
+#include "MtpProperty.h"
+#include "MtpStorageInfo.h"
+#include "MtpStringBuffer.h"
+#include "MtpUtils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <endian.h>
+
+#include <usbhost/usbhost.h>
+
+namespace {
+
+static constexpr int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200;
+
+}  // namespace
+
+#if 0
+static bool isMtpDevice(uint16_t vendor, uint16_t product) {
+	// Sandisk Sansa Fuze
+	if (vendor == 0x0781 && product == 0x74c2)
+		return true;
+	// Samsung YP-Z5
+	if (vendor == 0x04e8 && product == 0x503c)
+		return true;
+	return false;
+}
+#endif
+
+namespace {
+
+bool writeToFd(void* data, uint32_t /* unused_offset */, uint32_t length, void* clientData) {
+	const int fd = *static_cast<int*>(clientData);
+	const ssize_t result = write(fd, data, length);
+	if (result < 0) {
+		return false;
+	}
+	return static_cast<uint32_t>(result) == length;
+}
+
+}  // namespace
+
+MtpDevice* MtpDevice::open(const char* deviceName, int fd) {
+	struct usb_device *device = usb_device_new(deviceName, fd);
+	if (!device) {
+		MTPE("usb_device_new failed for %s", deviceName);
+		return NULL;
+	}
+
+	struct usb_descriptor_header* desc;
+	struct usb_descriptor_iter iter;
+
+	usb_descriptor_iter_init(device, &iter);
+
+	while ((desc = usb_descriptor_iter_next(&iter)) != NULL) {
+		if (desc->bDescriptorType == USB_DT_INTERFACE) {
+			struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc;
+
+			if (interface->bInterfaceClass == USB_CLASS_STILL_IMAGE &&
+				interface->bInterfaceSubClass == 1 && // Still Image Capture
+				interface->bInterfaceProtocol == 1)		// Picture Transfer Protocol (PIMA 15470)
+			{
+				char* manufacturerName = usb_device_get_manufacturer_name(device,
+						USB_CONTROL_TRANSFER_TIMEOUT_MS);
+				char* productName = usb_device_get_product_name(device,
+						USB_CONTROL_TRANSFER_TIMEOUT_MS);
+				MTPD("Found camera: \"%s\" \"%s\"\n", manufacturerName, productName);
+				free(manufacturerName);
+				free(productName);
+			} else if (interface->bInterfaceClass == 0xFF &&
+					interface->bInterfaceSubClass == 0xFF &&
+					interface->bInterfaceProtocol == 0) {
+				char* interfaceName = usb_device_get_string(device, interface->iInterface,
+						USB_CONTROL_TRANSFER_TIMEOUT_MS);
+				if (!interfaceName) {
+					continue;
+				} else if (strcmp(interfaceName, "MTP")) {
+					free(interfaceName);
+					continue;
+				}
+				free(interfaceName);
+
+				// Looks like an android style MTP device
+				char* manufacturerName = usb_device_get_manufacturer_name(device,
+						USB_CONTROL_TRANSFER_TIMEOUT_MS);
+				char* productName = usb_device_get_product_name(device,
+						USB_CONTROL_TRANSFER_TIMEOUT_MS);
+				MTPD("Found MTP device: \"%s\" \"%s\"\n", manufacturerName, productName);
+				free(manufacturerName);
+				free(productName);
+			}
+#if 0
+			 else {
+				// look for special cased devices based on vendor/product ID
+				// we are doing this mainly for testing purposes
+				uint16_t vendor = usb_device_get_vendor_id(device);
+				uint16_t product = usb_device_get_product_id(device);
+				if (!isMtpDevice(vendor, product)) {
+					// not an MTP or PTP device
+					continue;
+				}
+				// request MTP OS string and descriptor
+				// some music players need to see this before entering MTP mode.
+				char buffer[256];
+				memset(buffer, 0, sizeof(buffer));
+				int ret = usb_device_control_transfer(device,
+						USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_STANDARD,
+						USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | 0xEE,
+						0, buffer, sizeof(buffer), 0);
+				printf("usb_device_control_transfer returned %d errno: %d\n", ret, errno);
+				if (ret > 0) {
+					printf("got MTP string %s\n", buffer);
+					ret = usb_device_control_transfer(device,
+							USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_VENDOR, 1,
+							0, 4, buffer, sizeof(buffer), 0);
+					printf("OS descriptor got %d\n", ret);
+				} else {
+					printf("no MTP string\n");
+				}
+			}
+#else
+			else {
+				continue;
+			}
+#endif
+			// if we got here, then we have a likely MTP or PTP device
+
+			// interface should be followed by three endpoints
+			struct usb_endpoint_descriptor *ep;
+			struct usb_endpoint_descriptor *ep_in_desc = NULL;
+			struct usb_endpoint_descriptor *ep_out_desc = NULL;
+			struct usb_endpoint_descriptor *ep_intr_desc = NULL;
+			//USB3 add USB_DT_SS_ENDPOINT_COMP as companion descriptor;
+			struct usb_ss_ep_comp_descriptor *ep_ss_ep_comp_desc = NULL;
+			for (int i = 0; i < 3; i++) {
+				ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter);
+				if (ep && ep->bDescriptorType == USB_DT_SS_ENDPOINT_COMP) {
+					MTPD("Descriptor type is USB_DT_SS_ENDPOINT_COMP for USB3 \n");
+					ep_ss_ep_comp_desc = (usb_ss_ep_comp_descriptor*)ep;
+					ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter);
+				 }
+
+				if (!ep || ep->bDescriptorType != USB_DT_ENDPOINT) {
+					MTPE("endpoints not found\n");
+					usb_device_close(device);
+					return NULL;
+				}
+
+				if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
+					if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+						ep_in_desc = ep;
+					else
+						ep_out_desc = ep;
+				} else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT &&
+					ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
+					ep_intr_desc = ep;
+				}
+			}
+			if (!ep_in_desc || !ep_out_desc || !ep_intr_desc) {
+				MTPE("endpoints not found\n");
+				usb_device_close(device);
+				return NULL;
+			}
+
+			int ret = usb_device_claim_interface(device, interface->bInterfaceNumber);
+			if (ret && errno == EBUSY) {
+				// disconnect kernel driver and try again
+				usb_device_connect_kernel_driver(device, interface->bInterfaceNumber, false);
+				ret = usb_device_claim_interface(device, interface->bInterfaceNumber);
+			}
+			if (ret) {
+				MTPE("usb_device_claim_interface failed errno: %d\n", errno);
+				usb_device_close(device);
+				return NULL;
+			}
+
+			MtpDevice* mtpDevice = new MtpDevice(device, interface->bInterfaceNumber,
+						ep_in_desc, ep_out_desc, ep_intr_desc);
+			mtpDevice->initialize();
+			return mtpDevice;
+		}
+	}
+
+	usb_device_close(device);
+	MTPE("device not found");
+	return NULL;
+}
+
+MtpDevice::MtpDevice(struct usb_device* device, int interface,
+			const struct usb_endpoint_descriptor *ep_in,
+			const struct usb_endpoint_descriptor *ep_out,
+			const struct usb_endpoint_descriptor *ep_intr)
+	:	mDevice(device),
+		mInterface(interface),
+		mRequestIn1(NULL),
+		mRequestIn2(NULL),
+		mRequestOut(NULL),
+		mRequestIntr(NULL),
+		mDeviceInfo(NULL),
+		mSessionID(0),
+		mTransactionID(0),
+		mReceivedResponse(false),
+		mProcessingEvent(false),
+		mCurrentEventHandle(0),
+		mLastSendObjectInfoTransactionID(0),
+		mLastSendObjectInfoObjectHandle(0),
+		mPacketDivisionMode(FIRST_PACKET_HAS_PAYLOAD)
+{
+	mRequestIn1 = usb_request_new(device, ep_in);
+	mRequestIn2 = usb_request_new(device, ep_in);
+	mRequestOut = usb_request_new(device, ep_out);
+	mRequestIntr = usb_request_new(device, ep_intr);
+}
+
+MtpDevice::~MtpDevice() {
+	close();
+	for (size_t i = 0; i < mDeviceProperties.size(); i++)
+		delete mDeviceProperties[i];
+	usb_request_free(mRequestIn1);
+	usb_request_free(mRequestIn2);
+	usb_request_free(mRequestOut);
+	usb_request_free(mRequestIntr);
+}
+
+void MtpDevice::initialize() {
+	openSession();
+	mDeviceInfo = getDeviceInfo();
+	if (mDeviceInfo) {
+		if (mDeviceInfo->mDeviceProperties) {
+			int count = mDeviceInfo->mDeviceProperties->size();
+			for (int i = 0; i < count; i++) {
+				MtpDeviceProperty propCode = (*mDeviceInfo->mDeviceProperties)[i];
+				MtpProperty* property = getDevicePropDesc(propCode);
+				if (property)
+					mDeviceProperties.push_back(property);
+			}
+		}
+	}
+}
+
+void MtpDevice::close() {
+	if (mDevice) {
+		usb_device_release_interface(mDevice, mInterface);
+		usb_device_close(mDevice);
+		mDevice = NULL;
+	}
+}
+
+void MtpDevice::print() {
+	if (!mDeviceInfo)
+		return;
+
+	mDeviceInfo->print();
+
+	if (mDeviceInfo->mDeviceProperties) {
+		MTPD("***** DEVICE PROPERTIES *****\n");
+		int count = mDeviceInfo->mDeviceProperties->size();
+		for (int i = 0; i < count; i++) {
+			MtpDeviceProperty propCode = (*mDeviceInfo->mDeviceProperties)[i];
+			MtpProperty* property = getDevicePropDesc(propCode);
+			if (property) {
+				property->print();
+				delete property;
+			}
+		}
+	}
+
+	if (mDeviceInfo->mPlaybackFormats) {
+			MTPD("***** OBJECT PROPERTIES *****\n");
+		int count = mDeviceInfo->mPlaybackFormats->size();
+		for (int i = 0; i < count; i++) {
+			MtpObjectFormat format = (*mDeviceInfo->mPlaybackFormats)[i];
+			MTPD("*** FORMAT: %s\n", MtpDebug::getFormatCodeName(format));
+			MtpObjectPropertyList* props = getObjectPropsSupported(format);
+			if (props) {
+				for (size_t j = 0; j < props->size(); j++) {
+					MtpObjectProperty prop = (*props)[j];
+					MtpProperty* property = getObjectPropDesc(prop, format);
+					if (property) {
+						property->print();
+						delete property;
+					} else {
+						MTPE("could not fetch property: %s",
+								MtpDebug::getObjectPropCodeName(prop));
+					}
+				}
+			}
+		}
+	}
+}
+
+const char* MtpDevice::getDeviceName() {
+	if (mDevice)
+		return usb_device_get_name(mDevice);
+	else
+		return "???";
+}
+
+bool MtpDevice::openSession() {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mSessionID = 0;
+	mTransactionID = 0;
+	MtpSessionID newSession = 1;
+	mRequest.reset();
+	mRequest.setParameter(1, newSession);
+	if (!sendRequest(MTP_OPERATION_OPEN_SESSION))
+		return false;
+	MtpResponseCode ret = readResponse();
+	if (ret == MTP_RESPONSE_SESSION_ALREADY_OPEN)
+		newSession = mResponse.getParameter(1);
+	else if (ret != MTP_RESPONSE_OK)
+		return false;
+
+	mSessionID = newSession;
+	mTransactionID = 1;
+	return true;
+}
+
+bool MtpDevice::closeSession() {
+	// FIXME
+	return true;
+}
+
+MtpDeviceInfo* MtpDevice::getDeviceInfo() {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	if (!sendRequest(MTP_OPERATION_GET_DEVICE_INFO))
+		return NULL;
+	if (!readData())
+		return NULL;
+	MtpResponseCode ret = readResponse();
+	if (ret == MTP_RESPONSE_OK) {
+		MtpDeviceInfo* info = new MtpDeviceInfo;
+		if (info->read(mData))
+			return info;
+		else
+			delete info;
+	}
+	return NULL;
+}
+
+MtpStorageIDList* MtpDevice::getStorageIDs() {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	if (!sendRequest(MTP_OPERATION_GET_STORAGE_IDS))
+		return NULL;
+	if (!readData())
+		return NULL;
+	MtpResponseCode ret = readResponse();
+	if (ret == MTP_RESPONSE_OK) {
+		return mData.getAUInt32();
+	}
+	return NULL;
+}
+
+MtpStorageInfo* MtpDevice::getStorageInfo(MtpStorageID storageID) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, storageID);
+	if (!sendRequest(MTP_OPERATION_GET_STORAGE_INFO))
+		return NULL;
+	if (!readData())
+		return NULL;
+	MtpResponseCode ret = readResponse();
+	if (ret == MTP_RESPONSE_OK) {
+		MtpStorageInfo* info = new MtpStorageInfo(storageID);
+		if (info->read(mData))
+			return info;
+		else
+			delete info;
+	}
+	return NULL;
+}
+
+MtpObjectHandleList* MtpDevice::getObjectHandles(MtpStorageID storageID,
+			MtpObjectFormat format, MtpObjectHandle parent) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, storageID);
+	mRequest.setParameter(2, format);
+	mRequest.setParameter(3, parent);
+	if (!sendRequest(MTP_OPERATION_GET_OBJECT_HANDLES))
+		return NULL;
+	if (!readData())
+		return NULL;
+	MtpResponseCode ret = readResponse();
+	if (ret == MTP_RESPONSE_OK) {
+		return mData.getAUInt32();
+	}
+	return NULL;
+}
+
+MtpObjectInfo* MtpDevice::getObjectInfo(MtpObjectHandle handle) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	// FIXME - we might want to add some caching here
+
+	mRequest.reset();
+	mRequest.setParameter(1, handle);
+	if (!sendRequest(MTP_OPERATION_GET_OBJECT_INFO))
+		return NULL;
+	if (!readData())
+		return NULL;
+	MtpResponseCode ret = readResponse();
+	if (ret == MTP_RESPONSE_OK) {
+		MtpObjectInfo* info = new MtpObjectInfo(handle);
+		if (info->read(mData))
+			return info;
+		else
+			delete info;
+	}
+	return NULL;
+}
+
+void* MtpDevice::getThumbnail(MtpObjectHandle handle, int& outLength) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, handle);
+	if (sendRequest(MTP_OPERATION_GET_THUMB) && readData()) {
+		MtpResponseCode ret = readResponse();
+		if (ret == MTP_RESPONSE_OK) {
+			return mData.getData(&outLength);
+		}
+	}
+	outLength = 0;
+	return NULL;
+}
+
+MtpObjectHandle MtpDevice::sendObjectInfo(MtpObjectInfo* info) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	MtpObjectHandle parent = info->mParent;
+	if (parent == 0)
+		parent = MTP_PARENT_ROOT;
+
+	mRequest.setParameter(1, info->mStorageID);
+	mRequest.setParameter(2, parent);
+
+	mData.reset();
+	mData.putUInt32(info->mStorageID);
+	mData.putUInt16(info->mFormat);
+	mData.putUInt16(info->mProtectionStatus);
+	mData.putUInt32(info->mCompressedSize);
+	mData.putUInt16(info->mThumbFormat);
+	mData.putUInt32(info->mThumbCompressedSize);
+	mData.putUInt32(info->mThumbPixWidth);
+	mData.putUInt32(info->mThumbPixHeight);
+	mData.putUInt32(info->mImagePixWidth);
+	mData.putUInt32(info->mImagePixHeight);
+	mData.putUInt32(info->mImagePixDepth);
+	mData.putUInt32(info->mParent);
+	mData.putUInt16(info->mAssociationType);
+	mData.putUInt32(info->mAssociationDesc);
+	mData.putUInt32(info->mSequenceNumber);
+	mData.putString(info->mName);
+
+	char created[100], modified[100];
+	formatDateTime(info->mDateCreated, created, sizeof(created));
+	formatDateTime(info->mDateModified, modified, sizeof(modified));
+
+	mData.putString(created);
+	mData.putString(modified);
+	if (info->mKeywords)
+		mData.putString(info->mKeywords);
+	else
+		mData.putEmptyString();
+
+   if (sendRequest(MTP_OPERATION_SEND_OBJECT_INFO) && sendData()) {
+		MtpResponseCode ret = readResponse();
+		if (ret == MTP_RESPONSE_OK) {
+			mLastSendObjectInfoTransactionID = mRequest.getTransactionID();
+			mLastSendObjectInfoObjectHandle = mResponse.getParameter(3);
+			info->mStorageID = mResponse.getParameter(1);
+			info->mParent = mResponse.getParameter(2);
+			info->mHandle = mResponse.getParameter(3);
+			return info->mHandle;
+		}
+	}
+	return (MtpObjectHandle)-1;
+}
+
+bool MtpDevice::sendObject(MtpObjectHandle handle, int size, int srcFD) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	if (mLastSendObjectInfoTransactionID + 1 != mTransactionID ||
+			mLastSendObjectInfoObjectHandle != handle) {
+		MTPE("A sendObject request must follow the sendObjectInfo request.");
+		return false;
+	}
+
+	mRequest.reset();
+	if (sendRequest(MTP_OPERATION_SEND_OBJECT)) {
+		mData.setOperationCode(mRequest.getOperationCode());
+		mData.setTransactionID(mRequest.getTransactionID());
+		const int writeResult = mData.write(mRequestOut, mPacketDivisionMode, srcFD, size);
+		const MtpResponseCode ret = readResponse();
+		return ret == MTP_RESPONSE_OK && writeResult > 0;
+	}
+	return false;
+}
+
+bool MtpDevice::deleteObject(MtpObjectHandle handle) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, handle);
+	if (sendRequest(MTP_OPERATION_DELETE_OBJECT)) {
+		MtpResponseCode ret = readResponse();
+		if (ret == MTP_RESPONSE_OK)
+			return true;
+	}
+	return false;
+}
+
+MtpObjectHandle MtpDevice::getParent(MtpObjectHandle handle) {
+	MtpObjectInfo* info = getObjectInfo(handle);
+	if (info) {
+		MtpObjectHandle parent = info->mParent;
+		delete info;
+		return parent;
+	} else {
+		return -1;
+	}
+}
+
+MtpObjectHandle MtpDevice::getStorageID(MtpObjectHandle handle) {
+	MtpObjectInfo* info = getObjectInfo(handle);
+	if (info) {
+		MtpObjectHandle storageId = info->mStorageID;
+		delete info;
+		return storageId;
+	} else {
+		return -1;
+	}
+}
+
+MtpObjectPropertyList* MtpDevice::getObjectPropsSupported(MtpObjectFormat format) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, format);
+	if (!sendRequest(MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED))
+		return NULL;
+	if (!readData())
+		return NULL;
+	MtpResponseCode ret = readResponse();
+	if (ret == MTP_RESPONSE_OK) {
+		return mData.getAUInt16();
+	}
+	return NULL;
+
+}
+
+MtpProperty* MtpDevice::getDevicePropDesc(MtpDeviceProperty code) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, code);
+	if (!sendRequest(MTP_OPERATION_GET_DEVICE_PROP_DESC))
+		return NULL;
+	if (!readData())
+		return NULL;
+	MtpResponseCode ret = readResponse();
+	if (ret == MTP_RESPONSE_OK) {
+		MtpProperty* property = new MtpProperty;
+		if (property->read(mData))
+			return property;
+		else
+			delete property;
+	}
+	return NULL;
+}
+
+MtpProperty* MtpDevice::getObjectPropDesc(MtpObjectProperty code, MtpObjectFormat format) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, code);
+	mRequest.setParameter(2, format);
+	if (!sendRequest(MTP_OPERATION_GET_OBJECT_PROP_DESC))
+		return NULL;
+	if (!readData())
+		return NULL;
+	const MtpResponseCode ret = readResponse();
+	if (ret == MTP_RESPONSE_OK) {
+		MtpProperty* property = new MtpProperty;
+		if (property->read(mData))
+			return property;
+		else
+			delete property;
+	}
+	return NULL;
+}
+
+bool MtpDevice::getObjectPropValue(MtpObjectHandle handle, MtpProperty* property) {
+	if (property == nullptr)
+		return false;
+
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, handle);
+	mRequest.setParameter(2, property->getPropertyCode());
+	if (!sendRequest(MTP_OPERATION_GET_OBJECT_PROP_VALUE))
+		return false;
+	if (!readData())
+		return false;
+	if (readResponse() != MTP_RESPONSE_OK)
+		return false;
+	property->setCurrentValue(mData);
+	return true;
+}
+
+bool MtpDevice::readObject(MtpObjectHandle handle,
+						   ReadObjectCallback callback,
+						   uint32_t expectedLength,
+						   void* clientData) {
+	return readObjectInternal(handle, callback, &expectedLength, clientData);
+}
+
+// reads the object's data and writes it to the specified file path
+bool MtpDevice::readObject(MtpObjectHandle handle, const char* destPath, int group, int perm) {
+	MTPD("readObject: %s", destPath);
+	int fd = ::open(destPath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+	if (fd < 0) {
+		MTPE("open failed for %s", destPath);
+		return false;
+	}
+
+	fchown(fd, getuid(), group);
+	// set permissions
+	int mask = umask(0);
+	fchmod(fd, perm);
+	umask(mask);
+
+	bool result = readObject(handle, fd);
+	::close(fd);
+	return result;
+}
+
+bool MtpDevice::readObject(MtpObjectHandle handle, int fd) {
+	MTPD("readObject: %d", fd);
+	return readObjectInternal(handle, writeToFd, NULL /* expected size */, &fd);
+}
+
+bool MtpDevice::readObjectInternal(MtpObjectHandle handle,
+								   ReadObjectCallback callback,
+								   const uint32_t* expectedLength,
+								   void* clientData) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, handle);
+	if (!sendRequest(MTP_OPERATION_GET_OBJECT)) {
+		MTPE("Failed to send a read request.");
+		return false;
+	}
+
+	return readData(callback, expectedLength, nullptr, clientData);
+}
+
+bool MtpDevice::readData(ReadObjectCallback callback,
+							const uint32_t* expectedLength,
+							uint32_t* writtenSize,
+							void* clientData) {
+	if (!mData.readDataHeader(mRequestIn1)) {
+		MTPE("Failed to read header.");
+		return false;
+	}
+
+	// If object size 0 byte, the remote device may reply a response packet without sending any data
+	// packets.
+	if (mData.getContainerType() == MTP_CONTAINER_TYPE_RESPONSE) {
+		mResponse.copyFrom(mData);
+		return mResponse.getResponseCode() == MTP_RESPONSE_OK;
+	}
+
+	const uint32_t fullLength = mData.getContainerLength();
+	if (fullLength < MTP_CONTAINER_HEADER_SIZE) {
+		MTPE("fullLength is too short: %d", fullLength);
+		return false;
+	}
+	const uint32_t length = fullLength - MTP_CONTAINER_HEADER_SIZE;
+	if (expectedLength && length != *expectedLength) {
+		MTPE("readObject error length: %d", fullLength);
+		return false;
+	}
+
+	uint32_t offset = 0;
+	bool writingError = false;
+
+	{
+		int initialDataLength = 0;
+		void* const initialData = mData.getData(&initialDataLength);
+		if (fullLength > MTP_CONTAINER_HEADER_SIZE && initialDataLength == 0) {
+			// According to the MTP spec, the responder (MTP device) can choose two ways of sending
+			// data. a) The first packet contains the head and as much of the payload as possible
+			// b) The first packet contains only the header. The initiator (MTP host) needs
+			// to remember which way the responder used, and send upcoming data in the same way.
+			MTPD("Found short packet that contains only a header.");
+			mPacketDivisionMode = FIRST_PACKET_ONLY_HEADER;
+		}
+		if (initialData) {
+			if (initialDataLength > 0) {
+				if (!callback(initialData, offset, initialDataLength, clientData)) {
+					MTPE("Failed to write initial data.");
+					writingError = true;
+				}
+				offset += initialDataLength;
+			}
+			free(initialData);
+		}
+	}
+
+	// USB reads greater than 16K don't work.
+	char buffer1[MTP_BUFFER_SIZE], buffer2[MTP_BUFFER_SIZE];
+	mRequestIn1->buffer = buffer1;
+	mRequestIn2->buffer = buffer2;
+	struct usb_request* req = NULL;
+
+	while (offset < length) {
+		// Wait for previous read to complete.
+		void* writeBuffer = NULL;
+		int writeLength = 0;
+		if (req) {
+			const int read = mData.readDataWait(mDevice);
+			if (read < 0) {
+				MTPE("readDataWait failed.");
+				return false;
+			}
+			writeBuffer = req->buffer;
+			writeLength = read;
+		}
+
+		// Request to read next chunk.
+		const uint32_t nextOffset = offset + writeLength;
+		if (nextOffset < length) {
+			// Queue up a read request.
+			const size_t remaining = length - nextOffset;
+			req = (req == mRequestIn1 ? mRequestIn2 : mRequestIn1);
+			req->buffer_length = remaining > MTP_BUFFER_SIZE ?
+					static_cast<size_t>(MTP_BUFFER_SIZE) : remaining;
+			if (mData.readDataAsync(req) != 0) {
+				MTPE("readDataAsync failed");
+				return false;
+			}
+		}
+
+		// Write previous buffer.
+		if (writeBuffer && !writingError) {
+			if (!callback(writeBuffer, offset, writeLength, clientData)) {
+				MTPE("write failed");
+				writingError = true;
+			}
+		}
+		offset = nextOffset;
+	}
+
+	if (writtenSize) {
+		*writtenSize = length;
+	}
+
+	return readResponse() == MTP_RESPONSE_OK;
+}
+
+bool MtpDevice::readPartialObject(MtpObjectHandle handle,
+								  uint32_t offset,
+								  uint32_t size,
+								  uint32_t *writtenSize,
+								  ReadObjectCallback callback,
+								  void* clientData) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, handle);
+	mRequest.setParameter(2, offset);
+	mRequest.setParameter(3, size);
+	if (!sendRequest(MTP_OPERATION_GET_PARTIAL_OBJECT)) {
+		MTPE("Failed to send a read request.");
+		return false;
+	}
+	// The expected size is null because it requires the exact number of bytes to read though
+	// MTP_OPERATION_GET_PARTIAL_OBJECT allows devices to return shorter length of bytes than
+	// requested. Destination's buffer length should be checked in |callback|.
+	return readData(callback, nullptr /* expected size */, writtenSize, clientData);
+}
+
+bool MtpDevice::readPartialObject64(MtpObjectHandle handle,
+									uint64_t offset,
+									uint32_t size,
+									uint32_t *writtenSize,
+									ReadObjectCallback callback,
+									void* clientData) {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	mRequest.reset();
+	mRequest.setParameter(1, handle);
+	mRequest.setParameter(2, 0xffffffff & offset);
+	mRequest.setParameter(3, 0xffffffff & (offset >> 32));
+	mRequest.setParameter(4, size);
+	if (!sendRequest(MTP_OPERATION_GET_PARTIAL_OBJECT_64)) {
+		MTPE("Failed to send a read request.");
+		return false;
+	}
+	// The expected size is null because it requires the exact number of bytes to read though
+	// MTP_OPERATION_GET_PARTIAL_OBJECT_64 allows devices to return shorter length of bytes than
+	// requested. Destination's buffer length should be checked in |callback|.
+	return readData(callback, nullptr /* expected size */, writtenSize, clientData);
+}
+
+bool MtpDevice::sendRequest(MtpOperationCode operation) {
+	MTPD("sendRequest: %s\n", MtpDebug::getOperationCodeName(operation));
+	mReceivedResponse = false;
+	mRequest.setOperationCode(operation);
+	if (mTransactionID > 0)
+		mRequest.setTransactionID(mTransactionID++);
+	int ret = mRequest.write(mRequestOut);
+	mRequest.dump();
+	return (ret > 0);
+}
+
+bool MtpDevice::sendData() {
+	MTPD("sendData\n");
+	mData.setOperationCode(mRequest.getOperationCode());
+	mData.setTransactionID(mRequest.getTransactionID());
+	int ret = mData.write(mRequestOut, mPacketDivisionMode);
+	mData.dump();
+	return (ret >= 0);
+}
+
+bool MtpDevice::readData() {
+	mData.reset();
+	int ret = mData.read(mRequestIn1);
+	MTPD("readData returned %d\n", ret);
+	if (ret >= MTP_CONTAINER_HEADER_SIZE) {
+		if (mData.getContainerType() == MTP_CONTAINER_TYPE_RESPONSE) {
+			MTPD("got response packet instead of data packet");
+			// we got a response packet rather than data
+			// copy it to mResponse
+			mResponse.copyFrom(mData);
+			mReceivedResponse = true;
+			return false;
+		}
+		mData.dump();
+		return true;
+	}
+	else {
+		MTPD("readResponse failed\n");
+		return false;
+	}
+}
+
+MtpResponseCode MtpDevice::readResponse() {
+	MTPD("readResponse\n");
+	if (mReceivedResponse) {
+		mReceivedResponse = false;
+		return mResponse.getResponseCode();
+	}
+	int ret = mResponse.read(mRequestIn1);
+	// handle zero length packets, which might occur if the data transfer
+	// ends on a packet boundary
+	if (ret == 0)
+		ret = mResponse.read(mRequestIn1);
+	if (ret >= MTP_CONTAINER_HEADER_SIZE) {
+		mResponse.dump();
+		return mResponse.getResponseCode();
+	} else {
+		MTPD("readResponse failed\n");
+		return -1;
+	}
+}
+
+int MtpDevice::submitEventRequest() {
+	if (!mEventMutex.try_lock()) {
+		// An event is being reaped on another thread.
+		return -1;
+	}
+	if (mProcessingEvent) {
+		// An event request was submitted, but no reapEventRequest called so far.
+		return -1;
+	}
+	std::lock_guard<std::mutex> lg(mEventMutexForInterrupt);
+	mEventPacket.sendRequest(mRequestIntr);
+	const int currentHandle = ++mCurrentEventHandle;
+	mProcessingEvent = true;
+	mEventMutex.unlock();
+	return currentHandle;
+}
+
+int MtpDevice::reapEventRequest(int handle, uint32_t (*parameters)[3]) {
+	std::lock_guard<std::mutex> lg(mEventMutex);
+	if (!mProcessingEvent || mCurrentEventHandle != handle || !parameters) {
+		return -1;
+	}
+	mProcessingEvent = false;
+	const int readSize = mEventPacket.readResponse(mRequestIntr->dev);
+	const int result = mEventPacket.getEventCode();
+	// MTP event has three parameters.
+	(*parameters)[0] = mEventPacket.getParameter(1);
+	(*parameters)[1] = mEventPacket.getParameter(2);
+	(*parameters)[2] = mEventPacket.getParameter(3);
+	return readSize != 0 ? result : 0;
+}
+
+void MtpDevice::discardEventRequest(int handle) {
+	std::lock_guard<std::mutex> lg(mEventMutexForInterrupt);
+	if (mCurrentEventHandle != handle) {
+		return;
+	}
+	usb_request_cancel(mRequestIntr);
+}
diff --git a/mtp/ffs/MtpDevice.h b/mtp/ffs/MtpDevice.h
new file mode 100644
index 0000000..da00a81
--- /dev/null
+++ b/mtp/ffs/MtpDevice.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DEVICE_H
+#define _MTP_DEVICE_H
+
+#include "MtpEventPacket.h"
+#include "MtpDataPacket.h"
+#include "MtpRequestPacket.h"
+#include "MtpResponsePacket.h"
+#include "MtpTypes.h"
+
+#include <mutex>
+
+struct usb_device;
+struct usb_request;
+struct usb_endpoint_descriptor;
+
+class MtpDeviceInfo;
+class MtpEventPacket;
+class MtpObjectInfo;
+class MtpStorageInfo;
+
+class MtpDevice {
+private:
+	struct usb_device*		mDevice;
+	int						mInterface;
+	struct usb_request*		mRequestIn1;
+	struct usb_request*		mRequestIn2;
+	struct usb_request*		mRequestOut;
+	struct usb_request*		mRequestIntr;
+	MtpDeviceInfo*			mDeviceInfo;
+	MtpPropertyList			mDeviceProperties;
+
+	// current session ID
+	MtpSessionID			mSessionID;
+	// current transaction ID
+	MtpTransactionID		mTransactionID;
+
+	MtpRequestPacket		mRequest;
+	MtpDataPacket			mData;
+	MtpResponsePacket		mResponse;
+	MtpEventPacket			mEventPacket;
+
+	// set to true if we received a response packet instead of a data packet
+	bool					mReceivedResponse;
+	bool					mProcessingEvent;
+	int						mCurrentEventHandle;
+
+	// to check if a sendObject request follows the last sendObjectInfo request.
+	MtpTransactionID		mLastSendObjectInfoTransactionID;
+	MtpObjectHandle			mLastSendObjectInfoObjectHandle;
+
+	// to ensure only one MTP transaction at a time
+	std::mutex				mMutex;
+	std::mutex				mEventMutex;
+	std::mutex				mEventMutexForInterrupt;
+
+	// Remember the device's packet division mode.
+	UrbPacketDivisionMode	mPacketDivisionMode;
+
+public:
+	typedef bool (*ReadObjectCallback)
+			(void* data, uint32_t offset, uint32_t length, void* clientData);
+
+	MtpDevice(struct usb_device* device,
+			  int interface,
+			  const struct usb_endpoint_descriptor *ep_in,
+			  const struct usb_endpoint_descriptor *ep_out,
+			  const struct usb_endpoint_descriptor *ep_intr);
+
+	static MtpDevice*		open(const char* deviceName, int fd);
+
+	virtual					~MtpDevice();
+
+	void					initialize();
+	void					close();
+	void					print();
+	const char*				getDeviceName();
+
+	bool					openSession();
+	bool					closeSession();
+
+	MtpDeviceInfo*			getDeviceInfo();
+	MtpStorageIDList*		getStorageIDs();
+	MtpStorageInfo*			getStorageInfo(MtpStorageID storageID);
+	MtpObjectHandleList*	getObjectHandles(MtpStorageID storageID, MtpObjectFormat format,
+									MtpObjectHandle parent);
+	MtpObjectInfo*			getObjectInfo(MtpObjectHandle handle);
+	void*					getThumbnail(MtpObjectHandle handle, int& outLength);
+	MtpObjectHandle			sendObjectInfo(MtpObjectInfo* info);
+	bool					sendObject(MtpObjectHandle handle, int size, int srcFD);
+	bool					deleteObject(MtpObjectHandle handle);
+	MtpObjectHandle			getParent(MtpObjectHandle handle);
+	MtpStorageID			getStorageID(MtpObjectHandle handle);
+
+	MtpObjectPropertyList*	getObjectPropsSupported(MtpObjectFormat format);
+
+	MtpProperty*			getDevicePropDesc(MtpDeviceProperty code);
+	MtpProperty*			getObjectPropDesc(MtpObjectProperty code, MtpObjectFormat format);
+
+	// Reads value of |property| for |handle|. Returns true on success.
+	bool					getObjectPropValue(MtpObjectHandle handle, MtpProperty* property);
+
+	bool					readObject(MtpObjectHandle handle, ReadObjectCallback callback,
+									uint32_t objectSize, void* clientData);
+	bool					readObject(MtpObjectHandle handle, const char* destPath, int group,
+									int perm);
+	bool					readObject(MtpObjectHandle handle, int fd);
+	bool					readPartialObject(MtpObjectHandle handle,
+											  uint32_t offset,
+											  uint32_t size,
+											  uint32_t *writtenSize,
+											  ReadObjectCallback callback,
+											  void* clientData);
+	bool					readPartialObject64(MtpObjectHandle handle,
+												uint64_t offset,
+												uint32_t size,
+												uint32_t *writtenSize,
+												ReadObjectCallback callback,
+												void* clientData);
+	// Starts a request to read MTP event from MTP device. It returns a request handle that
+	// can be used for blocking read or cancel. If other thread has already been processing an
+	// event returns -1.
+	int						submitEventRequest();
+	// Waits for MTP event from the device and returns MTP event code. It blocks the current thread
+	// until it receives an event from the device. |handle| should be a request handle returned
+	// by |submitEventRequest|. The function writes event parameters to |parameters|. Returns 0 for
+	// cancellations. Returns -1 for errors.
+	int						reapEventRequest(int handle, uint32_t (*parameters)[3]);
+	// Cancels an event request. |handle| should be request handle returned by
+	// |submitEventRequest|. If there is a thread blocked by |reapEventRequest| with the same
+	// |handle|, the thread will resume.
+	void					discardEventRequest(int handle);
+
+private:
+	// If |objectSize| is not NULL, it checks object size before reading data bytes.
+	bool					readObjectInternal(MtpObjectHandle handle,
+											   ReadObjectCallback callback,
+											   const uint32_t* objectSize,
+											   void* clientData);
+	// If |objectSize| is not NULL, it checks object size before reading data bytes.
+	bool					readData(ReadObjectCallback callback,
+									 const uint32_t* objectSize,
+									 uint32_t* writtenData,
+									 void* clientData);
+	bool					sendRequest(MtpOperationCode operation);
+	bool					sendData();
+	bool					readData();
+	bool					writeDataHeader(MtpOperationCode operation, int dataLength);
+	MtpResponseCode			readResponse();
+};
+
+#endif // _MTP_DEVICE_H
diff --git a/mtp/ffs/MtpDeviceInfo.cpp b/mtp/ffs/MtpDeviceInfo.cpp
new file mode 100644
index 0000000..fa2f95b
--- /dev/null
+++ b/mtp/ffs/MtpDeviceInfo.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpDeviceInfo"
+
+#include "MtpDebug.h"
+#include "MtpDataPacket.h"
+#include "MtpDeviceInfo.h"
+#include "MtpStringBuffer.h"
+
+MtpDeviceInfo::MtpDeviceInfo()
+	:	mStandardVersion(0),
+		mVendorExtensionID(0),
+		mVendorExtensionVersion(0),
+		mVendorExtensionDesc(NULL),
+		mFunctionalMode(0),
+		mOperations(NULL),
+		mEvents(NULL),
+		mDeviceProperties(NULL),
+		mCaptureFormats(NULL),
+		mPlaybackFormats(NULL),
+		mManufacturer(NULL),
+		mModel(NULL),
+		mVersion(NULL),
+		mSerial(NULL)
+{
+}
+
+MtpDeviceInfo::~MtpDeviceInfo() {
+	if (mVendorExtensionDesc)
+		free(mVendorExtensionDesc);
+	delete mOperations;
+	delete mEvents;
+	delete mDeviceProperties;
+	delete mCaptureFormats;
+	delete mPlaybackFormats;
+	if (mManufacturer)
+		free(mManufacturer);
+	if (mModel)
+		free(mModel);
+	if (mVersion)
+		free(mVersion);
+	if (mSerial)
+		free(mSerial);
+}
+
+bool MtpDeviceInfo::read(MtpDataPacket& packet) {
+	MtpStringBuffer string;
+
+	// read the device info
+	if (!packet.getUInt16(mStandardVersion)) return false;
+	if (!packet.getUInt32(mVendorExtensionID)) return false;
+	if (!packet.getUInt16(mVendorExtensionVersion)) return false;
+
+	if (!packet.getString(string)) return false;
+	mVendorExtensionDesc = strdup((const char *)string);
+	if (!mVendorExtensionDesc) return false;
+
+	if (!packet.getUInt16(mFunctionalMode)) return false;
+	mOperations = packet.getAUInt16();
+	if (!mOperations) return false;
+	mEvents = packet.getAUInt16();
+	if (!mEvents) return false;
+	mDeviceProperties = packet.getAUInt16();
+	if (!mDeviceProperties) return false;
+	mCaptureFormats = packet.getAUInt16();
+	if (!mCaptureFormats) return false;
+	mPlaybackFormats = packet.getAUInt16();
+	if (!mCaptureFormats) return false;
+
+	if (!packet.getString(string)) return false;
+	mManufacturer = strdup((const char *)string);
+	if (!mManufacturer) return false;
+	if (!packet.getString(string)) return false;
+	mModel = strdup((const char *)string);
+	if (!mModel) return false;
+	if (!packet.getString(string)) return false;
+	mVersion = strdup((const char *)string);
+	if (!mVersion) return false;
+	if (!packet.getString(string)) return false;
+	mSerial = strdup((const char *)string);
+	if (!mSerial) return false;
+
+	return true;
+}
+
+void MtpDeviceInfo::print() {
+	ALOGV("Device Info:\n\tmStandardVersion: %d\n\tmVendorExtensionID: %d\n\tmVendorExtensionVersiony: %d\n",
+			mStandardVersion, mVendorExtensionID, mVendorExtensionVersion);
+	ALOGV("\tmVendorExtensionDesc: %s\n\tmFunctionalMode: %d\n\tmManufacturer: %s\n\tmModel: %s\n\tmVersion: %s\n\tmSerial: %s\n",
+			mVendorExtensionDesc, mFunctionalMode, mManufacturer, mModel, mVersion, mSerial);
+}
diff --git a/mtp/ffs/MtpDeviceInfo.h b/mtp/ffs/MtpDeviceInfo.h
new file mode 100644
index 0000000..1690b61
--- /dev/null
+++ b/mtp/ffs/MtpDeviceInfo.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_DEVICE_INFO_H
+#define _MTP_DEVICE_INFO_H
+
+struct stat;
+
+class MtpDataPacket;
+
+class MtpDeviceInfo {
+public:
+	uint16_t				mStandardVersion;
+	uint32_t				mVendorExtensionID;
+	uint16_t				mVendorExtensionVersion;
+	char*					mVendorExtensionDesc;
+	uint16_t				mFunctionalMode;
+	UInt16List*				mOperations;
+	UInt16List*				mEvents;
+	MtpDevicePropertyList*	mDeviceProperties;
+	MtpObjectFormatList*	mCaptureFormats;
+	MtpObjectFormatList*	mPlaybackFormats;
+	char*					mManufacturer;
+	char*					mModel;
+	char*					mVersion;
+	char*					mSerial;
+
+public:
+							MtpDeviceInfo();
+	virtual					~MtpDeviceInfo();
+
+	bool					read(MtpDataPacket& packet);
+
+	void					print();
+};
+
+#endif // _MTP_DEVICE_INFO_H
diff --git a/mtp/ffs/MtpEventPacket.cpp b/mtp/ffs/MtpEventPacket.cpp
new file mode 100644
index 0000000..fa7db8c
--- /dev/null
+++ b/mtp/ffs/MtpEventPacket.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpEventPacket"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include "IMtpHandle.h"
+#include "MtpEventPacket.h"
+
+#include <usbhost/usbhost.h>
+
+MtpEventPacket::MtpEventPacket()
+	:	MtpPacket(512)
+{
+}
+
+MtpEventPacket::~MtpEventPacket() {
+}
+
+#ifdef MTP_DEVICE
+int MtpEventPacket::write(IMtpHandle *h) {
+	struct mtp_event	event;
+
+	putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
+	putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_EVENT);
+
+	event.data = mBuffer;
+	event.length = mPacketSize;
+	int ret = h->sendEvent(event);
+	return (ret < 0 ? ret : 0);
+}
+#endif
+
+#ifdef MTP_HOST
+int MtpEventPacket::sendRequest(struct usb_request *request) {
+	request->buffer = mBuffer;
+	request->buffer_length = mBufferSize;
+	mPacketSize = 0;
+	if (usb_request_queue(request)) {
+		MTPE("usb_endpoint_queue failed, errno: %d", errno);
+		return -1;
+	}
+	return 0;
+}
+
+int MtpEventPacket::readResponse(struct usb_device *device) {
+	struct usb_request* const req = usb_request_wait(device, -1);
+	if (req) {
+		mPacketSize = req->actual_length;
+		return req->actual_length;
+	} else {
+		return -1;
+	}
+}
+#endif
diff --git a/mtp/ffs/MtpEventPacket.h b/mtp/ffs/MtpEventPacket.h
new file mode 100644
index 0000000..e8f50f4
--- /dev/null
+++ b/mtp/ffs/MtpEventPacket.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_EVENT_PACKET_H
+#define _MTP_EVENT_PACKET_H
+
+#include "MtpPacket.h"
+#include "mtp.h"
+
+#include <errno.h>
+
+class IMtpHandle;
+
+class MtpEventPacket : public MtpPacket {
+
+public:
+						MtpEventPacket();
+	virtual				~MtpEventPacket();
+
+#ifdef MTP_DEVICE
+	// write our data to the given usb handle
+	int					write(IMtpHandle *h);
+#endif
+
+#ifdef MTP_HOST
+	// read our buffer with the given request
+	int					sendRequest(struct usb_request *request);
+	int					readResponse(struct usb_device *device);
+#endif
+
+	inline MtpEventCode		getEventCode() const { return getContainerCode(); }
+	inline void				setEventCode(MtpEventCode code)
+													 { return setContainerCode(code); }
+};
+
+#endif // _MTP_EVENT_PACKET_H
diff --git a/mtp/ffs/MtpFfsCompatHandle.cpp b/mtp/ffs/MtpFfsCompatHandle.cpp
new file mode 100644
index 0000000..4027d60
--- /dev/null
+++ b/mtp/ffs/MtpFfsCompatHandle.cpp
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/functionfs.h>
+#include <mutex>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/endian.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "PosixAsyncIO.h"
+#include "MtpFfsCompatHandle.h"
+#include "mtp.h"
+
+#define FUNCTIONFS_ENDPOINT_ALLOC		_IOR('g', 231, __u32)
+
+namespace {
+
+// Must be divisible by all max packet size values
+constexpr int MAX_FILE_CHUNK_SIZE = 3145728;
+
+// Safe values since some devices cannot handle large DMAs
+// To get good performance, override these with
+// higher values per device using the properties
+// sys.usb.ffs.max_read and sys.usb.ffs.max_write
+constexpr int USB_FFS_MAX_WRITE = MTP_BUFFER_SIZE;
+constexpr int USB_FFS_MAX_READ = MTP_BUFFER_SIZE;
+
+static_assert(USB_FFS_MAX_WRITE > 0, "Max r/w values must be > 0!");
+static_assert(USB_FFS_MAX_READ > 0, "Max r/w values must be > 0!");
+
+constexpr unsigned int MAX_MTP_FILE_SIZE = 0xFFFFFFFF;
+
+constexpr size_t ENDPOINT_ALLOC_RETRIES = 10;
+
+} // anonymous namespace
+
+
+MtpFfsCompatHandle::MtpFfsCompatHandle(int controlFd) :
+	MtpFfsHandle(controlFd),
+	mMaxWrite(USB_FFS_MAX_WRITE),
+	mMaxRead(USB_FFS_MAX_READ) {}
+
+MtpFfsCompatHandle::~MtpFfsCompatHandle() {}
+
+int MtpFfsCompatHandle::writeHandle(int fd, const void* data, size_t len) {
+	int ret = 0;
+	const char* buf = static_cast<const char*>(data);
+	while (len > 0) {
+		int write_len = std::min(mMaxWrite, len);
+		int n = TEMP_FAILURE_RETRY(::write(fd, buf, write_len));
+
+		if (n < 0) {
+			PLOG(ERROR) << "write ERROR: fd = " << fd << ", n = " << n;
+			return -1;
+		} else if (n < write_len) {
+			errno = EIO;
+			PLOG(ERROR) << "less written than expected";
+			return -1;
+		}
+		buf += n;
+		len -= n;
+		ret += n;
+	}
+	return ret;
+}
+
+int MtpFfsCompatHandle::readHandle(int fd, void* data, size_t len) {
+	int ret = 0;
+	char* buf = static_cast<char*>(data);
+	while (len > 0) {
+		int read_len = std::min(mMaxRead, len);
+		int n = TEMP_FAILURE_RETRY(::read(fd, buf, read_len));
+		if (n < 0) {
+			PLOG(ERROR) << "read ERROR: fd = " << fd << ", n = " << n;
+			return -1;
+		}
+		ret += n;
+		if (n < read_len) // done reading early
+			break;
+		buf += n;
+		len -= n;
+	}
+	return ret;
+}
+
+int MtpFfsCompatHandle::start(bool ptp) {
+	if (!openEndpoints(ptp))
+		return -1;
+
+	for (unsigned i = 0; i < NUM_IO_BUFS; i++) {
+		mIobuf[i].bufs.resize(MAX_FILE_CHUNK_SIZE);
+		posix_madvise(mIobuf[i].bufs.data(), MAX_FILE_CHUNK_SIZE,
+				POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED);
+	}
+
+	// Get device specific r/w size
+	mMaxWrite = android::base::GetIntProperty("sys.usb.ffs.max_write", USB_FFS_MAX_WRITE);
+	mMaxRead = android::base::GetIntProperty("sys.usb.ffs.max_read", USB_FFS_MAX_READ);
+
+	size_t attempts = 0;
+	while (mMaxWrite >= USB_FFS_MAX_WRITE && mMaxRead >= USB_FFS_MAX_READ &&
+			attempts < ENDPOINT_ALLOC_RETRIES) {
+		// If larger contiguous chunks of memory aren't available, attempt to try
+		// smaller allocations.
+		if (ioctl(mBulkIn, FUNCTIONFS_ENDPOINT_ALLOC, static_cast<__u32>(mMaxWrite)) ||
+			ioctl(mBulkOut, FUNCTIONFS_ENDPOINT_ALLOC, static_cast<__u32>(mMaxRead))) {
+			if (errno == ENODEV) {
+				// Driver hasn't enabled endpoints yet.
+				std::this_thread::sleep_for(std::chrono::milliseconds(100));
+				attempts += 1;
+				continue;
+			}
+			mMaxWrite /= 2;
+			mMaxRead /=2;
+		} else {
+			return 0;
+		}
+	}
+	// Try to start MtpServer anyway, with the smallest max r/w values
+	mMaxWrite = USB_FFS_MAX_WRITE;
+	mMaxRead = USB_FFS_MAX_READ;
+	PLOG(ERROR) << "Functionfs could not allocate any memory!";
+	return 0;
+}
+
+int MtpFfsCompatHandle::read(void* data, size_t len) {
+	return readHandle(mBulkOut, data, len);
+}
+
+int MtpFfsCompatHandle::write(const void* data, size_t len) {
+	return writeHandle(mBulkIn, data, len);
+}
+
+int MtpFfsCompatHandle::receiveFile(mtp_file_range mfr, bool zero_packet) {
+	// When receiving files, the incoming length is given in 32 bits.
+	// A >4G file is given as 0xFFFFFFFF
+	uint32_t file_length = mfr.length;
+	uint64_t offset = mfr.offset;
+	int packet_size = getPacketSize(mBulkOut);
+
+	unsigned char *data = mIobuf[0].bufs.data();
+	unsigned char *data2 = mIobuf[1].bufs.data();
+
+	struct aiocb aio;
+	aio.aio_fildes = mfr.fd;
+	aio.aio_buf = nullptr;
+	struct aiocb *aiol[] = {&aio};
+	int ret = -1;
+	size_t length;
+	bool read = false;
+	bool write = false;
+
+	posix_fadvise(mfr.fd, 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+
+	// Break down the file into pieces that fit in buffers
+	while (file_length > 0 || write) {
+		if (file_length > 0) {
+			length = std::min(static_cast<uint32_t>(MAX_FILE_CHUNK_SIZE), file_length);
+
+			// Read data from USB, handle errors after waiting for write thread.
+			ret = readHandle(mBulkOut, data, length);
+
+			if (file_length != MAX_MTP_FILE_SIZE && ret < static_cast<int>(length)) {
+				ret = -1;
+				errno = EIO;
+			}
+			read = true;
+		}
+
+		if (write) {
+			// get the return status of the last write request
+			aio_suspend(aiol, 1, nullptr);
+
+			int written = aio_return(&aio);
+			if (written == -1) {
+				errno = aio_error(&aio);
+				return -1;
+			}
+			if (static_cast<size_t>(written) < aio.aio_nbytes) {
+				errno = EIO;
+				return -1;
+			}
+			write = false;
+		}
+
+		// If there was an error reading above
+		if (ret == -1) {
+			return -1;
+		}
+
+		if (read) {
+			if (file_length == MAX_MTP_FILE_SIZE) {
+				// For larger files, receive until a short packet is received.
+				if (static_cast<size_t>(ret) < length) {
+					file_length = 0;
+				}
+			} else {
+				file_length -= ret;
+			}
+			// Enqueue a new write request
+			aio_prepare(&aio, data, length, offset);
+			aio_write(&aio);
+
+			offset += ret;
+			std::swap(data, data2);
+
+			write = true;
+			read = false;
+		}
+	}
+	// Receive an empty packet if size is a multiple of the endpoint size.
+	if (ret % packet_size == 0 || zero_packet) {
+		if (TEMP_FAILURE_RETRY(::read(mBulkOut, data, packet_size)) != 0) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+int MtpFfsCompatHandle::sendFile(mtp_file_range mfr) {
+	uint64_t file_length = mfr.length;
+	uint32_t given_length = std::min(static_cast<uint64_t>(MAX_MTP_FILE_SIZE),
+			file_length + sizeof(mtp_data_header));
+	uint64_t offset = mfr.offset;
+	int packet_size = getPacketSize(mBulkIn);
+
+	// If file_length is larger than a size_t, truncating would produce the wrong comparison.
+	// Instead, promote the left side to 64 bits, then truncate the small result.
+	int init_read_len = std::min(
+			static_cast<uint64_t>(packet_size - sizeof(mtp_data_header)), file_length);
+
+	unsigned char *data = mIobuf[0].bufs.data();
+	unsigned char *data2 = mIobuf[1].bufs.data();
+
+	posix_fadvise(mfr.fd, 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+
+	struct aiocb aio;
+	aio.aio_fildes = mfr.fd;
+	struct aiocb *aiol[] = {&aio};
+	int ret, length;
+	int error = 0;
+	bool read = false;
+	bool write = false;
+
+	// Send the header data
+	mtp_data_header *header = reinterpret_cast<mtp_data_header*>(data);
+	header->length = htole32(given_length);
+	header->type = htole16(2); /* data packet */
+	header->command = htole16(mfr.command);
+	header->transaction_id = htole32(mfr.transaction_id);
+
+	// Some hosts don't support header/data separation even though MTP allows it
+	// Handle by filling first packet with initial file data
+	if (TEMP_FAILURE_RETRY(pread(mfr.fd, reinterpret_cast<char*>(data) +
+					sizeof(mtp_data_header), init_read_len, offset))
+			!= init_read_len) return -1;
+	if (writeHandle(mBulkIn, data, sizeof(mtp_data_header) + init_read_len) == -1) return -1;
+	file_length -= init_read_len;
+	offset += init_read_len;
+	ret = init_read_len + sizeof(mtp_data_header);
+
+	// Break down the file into pieces that fit in buffers
+	while (file_length > 0) {
+		if (read) {
+			// Wait for the previous read to finish
+			aio_suspend(aiol, 1, nullptr);
+			ret = aio_return(&aio);
+			if (ret == -1) {
+				errno = aio_error(&aio);
+				return -1;
+			}
+			if (static_cast<size_t>(ret) < aio.aio_nbytes) {
+				errno = EIO;
+				return -1;
+			}
+
+			file_length -= ret;
+			offset += ret;
+			std::swap(data, data2);
+			read = false;
+			write = true;
+		}
+
+		if (error == -1) {
+			return -1;
+		}
+
+		if (file_length > 0) {
+			length = std::min(static_cast<uint64_t>(MAX_FILE_CHUNK_SIZE), file_length);
+			// Queue up another read
+			aio_prepare(&aio, data, length, offset);
+			aio_read(&aio);
+			read = true;
+		}
+
+		if (write) {
+			if (writeHandle(mBulkIn, data2, ret) == -1) {
+				error = -1;
+			}
+			write = false;
+		}
+	}
+
+	if (ret % packet_size == 0) {
+		// If the last packet wasn't short, send a final empty packet
+		if (TEMP_FAILURE_RETRY(::write(mBulkIn, data, 0)) != 0) {
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
diff --git a/mtp/ffs/MtpFfsCompatHandle.h b/mtp/ffs/MtpFfsCompatHandle.h
new file mode 100644
index 0000000..6f47e60
--- /dev/null
+++ b/mtp/ffs/MtpFfsCompatHandle.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_FFS_COMPAT_HANDLE_H
+#define _MTP_FFS_COMPAT_HANDLE_H
+
+#include <MtpFfsHandle.h>
+
+template <class T> class MtpFfsHandleTest;
+
+class MtpFfsCompatHandle : public MtpFfsHandle {
+	template <class T> friend class MtpFfsHandleTest;
+private:
+	int writeHandle(int fd, const void *data, size_t len);
+	int readHandle(int fd, void *data, size_t len);
+
+	size_t mMaxWrite;
+	size_t mMaxRead;
+
+public:
+	int read(void* data, size_t len) override;
+	int write(const void* data, size_t len) override;
+	int receiveFile(mtp_file_range mfr, bool zero_packet) override;
+	int sendFile(mtp_file_range mfr) override;
+
+	/**
+	 * Open ffs endpoints and allocate necessary kernel and user memory.
+	 * Will sleep until endpoints are enabled, for up to 1 second.
+	 */
+	int start(bool ptp) override;
+
+	MtpFfsCompatHandle(int controlFd);
+	~MtpFfsCompatHandle();
+};
+
+#endif // _MTP_FFS_COMPAT_HANDLE_H
+
diff --git a/mtp/ffs/MtpFfsHandle.cpp b/mtp/ffs/MtpFfsHandle.cpp
new file mode 100644
index 0000000..19c28b3
--- /dev/null
+++ b/mtp/ffs/MtpFfsHandle.cpp
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <asyncio/AsyncIO.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "PosixAsyncIO.h"
+#include "MtpDescriptors.h"
+#include "MtpFfsHandle.h"
+#include "mtp.h"
+#include "MtpDebug.h"
+
+namespace {
+
+constexpr unsigned AIO_BUFS_MAX = 128;
+constexpr unsigned AIO_BUF_LEN = 16384;
+
+constexpr unsigned FFS_NUM_EVENTS = 5;
+
+constexpr unsigned MAX_FILE_CHUNK_SIZE = AIO_BUFS_MAX * AIO_BUF_LEN;
+
+constexpr uint32_t MAX_MTP_FILE_SIZE = 0xFFFFFFFF;
+// Note: POLL_TIMEOUT_MS = 0 means return immediately i.e. no sleep.
+// And this will cause high CPU usage.
+constexpr int32_t POLL_TIMEOUT_MS = 500;
+
+struct timespec ZERO_TIMEOUT = { 0, 0 };
+
+struct mtp_device_status {
+	uint16_t  wLength;
+	uint16_t  wCode;
+};
+
+} // anonymous namespace
+
+int MtpFfsHandle::getPacketSize(int ffs_fd) {
+	struct usb_endpoint_descriptor desc;
+	if (ioctl(ffs_fd, FUNCTIONFS_ENDPOINT_DESC, reinterpret_cast<unsigned long>(&desc))) {
+		MTPE("Could not get FFS bulk-in descriptor\n");
+		return MAX_PACKET_SIZE_HS;
+	} else {
+		return desc.wMaxPacketSize;
+	}
+}
+
+MtpFfsHandle::MtpFfsHandle(int controlFd) {
+	mControl.reset(controlFd);
+}
+
+MtpFfsHandle::~MtpFfsHandle() {}
+
+void MtpFfsHandle::closeEndpoints() {
+	mIntr.reset();
+	mBulkIn.reset();
+	mBulkOut.reset();
+}
+
+bool MtpFfsHandle::openEndpoints(bool ptp) {
+	if (mBulkIn < 0) {
+		mBulkIn.reset(TEMP_FAILURE_RETRY(open(ptp ? FFS_PTP_EP_IN : FFS_MTP_EP_IN, O_RDWR)));
+		if (mBulkIn < 0) {
+			MTPE("cannot open bulk in ep\n");
+			return false;
+		}
+	}
+
+	if (mBulkOut < 0) {
+		mBulkOut.reset(TEMP_FAILURE_RETRY(open(ptp ? FFS_PTP_EP_OUT : FFS_MTP_EP_OUT, O_RDWR)));
+		if (mBulkOut < 0) {
+			MTPE("cannot open bulk out ep\n");
+			return false;
+		}
+	}
+
+	if (mIntr < 0) {
+		mIntr.reset(TEMP_FAILURE_RETRY(open(ptp ? FFS_PTP_EP_INTR : FFS_MTP_EP_INTR, O_RDWR)));
+		if (mIntr < 0) {
+			MTPE("cannot open intr ep\n");
+			return false;
+		}
+	}
+	return true;
+}
+
+void MtpFfsHandle::advise(int fd) {
+	for (unsigned i = 0; i < NUM_IO_BUFS; i++) {
+		if (posix_madvise(mIobuf[i].bufs.data(), MAX_FILE_CHUNK_SIZE,
+				POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED) < 0)
+			MTPE("Failed to madvise\n");
+	}
+	if (posix_fadvise(fd, 0, 0,
+				POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED) < 0)
+		MTPE("Failed to fadvise\n");
+}
+
+bool MtpFfsHandle::writeDescriptors(bool ptp) {
+	return ::writeDescriptors(mControl, ptp);
+}
+
+void MtpFfsHandle::closeConfig() {
+	mControl.reset();
+}
+
+int MtpFfsHandle::doAsync(void* data, size_t len, bool read, bool zero_packet) {
+	struct io_event ioevs[AIO_BUFS_MAX];
+	size_t total = 0;
+
+	while (total < len) {
+		size_t this_len = std::min(len - total, static_cast<size_t>(AIO_BUF_LEN * AIO_BUFS_MAX));
+		int num_bufs = this_len / AIO_BUF_LEN + (this_len % AIO_BUF_LEN == 0 ? 0 : 1);
+		for (int i = 0; i < num_bufs; i++) {
+			mIobuf[0].buf[i] = reinterpret_cast<unsigned char*>(data) + total + i * AIO_BUF_LEN;
+		}
+		int ret = iobufSubmit(&mIobuf[0], read ? mBulkOut : mBulkIn, this_len, read);
+		if (ret < 0) return -1;
+		ret = waitEvents(&mIobuf[0], ret, ioevs, nullptr);
+		if (ret < 0) return -1;
+		total += ret;
+		if (static_cast<size_t>(ret) < this_len) break;
+	}
+
+	int packet_size = getPacketSize(read ? mBulkOut : mBulkIn);
+	if (len % packet_size == 0 && zero_packet) {
+		int ret = iobufSubmit(&mIobuf[0], read ? mBulkOut : mBulkIn, 0, read);
+		if (ret < 0) return -1;
+		ret = waitEvents(&mIobuf[0], ret, ioevs, nullptr);
+		if (ret < 0) return -1;
+	}
+
+	for (unsigned i = 0; i < AIO_BUFS_MAX; i++) {
+		mIobuf[0].buf[i] = mIobuf[0].bufs.data() + i * AIO_BUF_LEN;
+	}
+	return total;
+}
+
+int MtpFfsHandle::read(void* data, size_t len) {
+	// Zero packets are handled by receiveFile()
+	return doAsync(data, len, true, false);
+}
+
+int MtpFfsHandle::write(const void* data, size_t len) {
+	return doAsync(const_cast<void*>(data), len, false, true);
+}
+
+int MtpFfsHandle::handleEvent() {
+
+	std::vector<usb_functionfs_event> events(FFS_NUM_EVENTS);
+	usb_functionfs_event *event = events.data();
+	int nbytes = TEMP_FAILURE_RETRY(::read(mControl, event,
+				events.size() * sizeof(usb_functionfs_event)));
+	if (nbytes == -1) {
+		return -1;
+	}
+	int ret = 0;
+	for (size_t n = nbytes / sizeof *event; n; --n, ++event) {
+		switch (event->type) {
+		case FUNCTIONFS_BIND:
+		case FUNCTIONFS_ENABLE:
+			ret = 0;
+			errno = 0;
+			break;
+		case FUNCTIONFS_UNBIND:
+		case FUNCTIONFS_DISABLE:
+			errno = ESHUTDOWN;
+			ret = -1;
+			break;
+		case FUNCTIONFS_SETUP:
+			if (handleControlRequest(&event->u.setup) == -1)
+				ret = -1;
+			break;
+		case FUNCTIONFS_SUSPEND:
+		case FUNCTIONFS_RESUME:
+			break;
+		default:
+			MTPE("Mtp Event (unknown)\n");
+		}
+	}
+	return ret;
+}
+
+int MtpFfsHandle::handleControlRequest(const struct usb_ctrlrequest *setup) {
+	uint8_t type = setup->bRequestType;
+	uint8_t code = setup->bRequest;
+	uint16_t length = setup->wLength;
+	uint16_t index = setup->wIndex;
+	uint16_t value = setup->wValue;
+	std::vector<char> buf;
+	buf.resize(length);
+	int ret = 0;
+
+	if (!(type & USB_DIR_IN)) {
+		if (::read(mControl, buf.data(), length) != length) {
+			MTPE("Mtp error ctrlreq read data");
+		}
+	}
+
+	if ((type & USB_TYPE_MASK) == USB_TYPE_CLASS && index == 0 && value == 0) {
+		switch(code) {
+		case MTP_REQ_RESET:
+		case MTP_REQ_CANCEL:
+			errno = ECANCELED;
+			ret = -1;
+			break;
+		case MTP_REQ_GET_DEVICE_STATUS:
+		{
+			if (length < sizeof(struct mtp_device_status) + 4) {
+				errno = EINVAL;
+				return -1;
+			}
+			struct mtp_device_status *st = reinterpret_cast<struct mtp_device_status*>(buf.data());
+			st->wLength = htole16(sizeof(st));
+			if (mCanceled) {
+				st->wLength += 4;
+				st->wCode = MTP_RESPONSE_TRANSACTION_CANCELLED;
+				uint16_t *endpoints = reinterpret_cast<uint16_t*>(st + 1);
+				endpoints[0] = ioctl(mBulkIn, FUNCTIONFS_ENDPOINT_REVMAP);
+				endpoints[1] = ioctl(mBulkOut, FUNCTIONFS_ENDPOINT_REVMAP);
+				mCanceled = false;
+			} else {
+				st->wCode = MTP_RESPONSE_OK;
+			}
+			length = st->wLength;
+			break;
+		}
+		default:
+			MTPE("Unrecognized Mtp class request!\n");
+		}
+	} else {
+		MTPE("Unrecognized request type\n");
+	}
+
+	if (type & USB_DIR_IN) {
+		if (::write(mControl, buf.data(), length) != length) {
+			MTPE("Mtp error ctrlreq write data");
+		}
+	}
+	return 0;
+}
+
+int MtpFfsHandle::start(bool ptp) {
+	if (!openEndpoints(ptp))
+		return -1;
+
+	for (unsigned i = 0; i < NUM_IO_BUFS; i++) {
+		mIobuf[i].bufs.resize(MAX_FILE_CHUNK_SIZE);
+		mIobuf[i].iocb.resize(AIO_BUFS_MAX);
+		mIobuf[i].iocbs.resize(AIO_BUFS_MAX);
+		mIobuf[i].buf.resize(AIO_BUFS_MAX);
+		for (unsigned j = 0; j < AIO_BUFS_MAX; j++) {
+			mIobuf[i].buf[j] = mIobuf[i].bufs.data() + j * AIO_BUF_LEN;
+			mIobuf[i].iocb[j] = &mIobuf[i].iocbs[j];
+		}
+	}
+
+	memset(&mCtx, 0, sizeof(mCtx));
+	if (io_setup(AIO_BUFS_MAX, &mCtx) < 0) {
+		MTPE("unable to setup aio");
+		return -1;
+	}
+	mEventFd.reset(eventfd(0, EFD_NONBLOCK));
+	mPollFds[0].fd = mControl;
+	mPollFds[0].events = POLLIN;
+	mPollFds[1].fd = mEventFd;
+	mPollFds[1].events = POLLIN;
+
+	mCanceled = false;
+	return 0;
+}
+
+void MtpFfsHandle::close() {
+	io_destroy(mCtx);
+	closeEndpoints();
+	closeConfig();
+}
+
+int MtpFfsHandle::waitEvents(__attribute__((unused)) struct io_buffer *buf, int min_events, struct io_event *events,
+		int *counter) {
+	int num_events = 0;
+	int ret = 0;
+	int error = 0;
+
+	while (num_events < min_events) {
+		if (poll(mPollFds, 2, POLL_TIMEOUT_MS) == -1) {
+			MTPE("Mtp error during poll()\n");
+			return -1;
+		}
+		if (mPollFds[0].revents & POLLIN) {
+			mPollFds[0].revents = 0;
+			if (handleEvent() == -1) {
+				error = errno;
+			}
+		}
+		if (mPollFds[1].revents & POLLIN) {
+			mPollFds[1].revents = 0;
+			uint64_t ev_cnt = 0;
+
+			if (::read(mEventFd, &ev_cnt, sizeof(ev_cnt)) == -1) {
+				MTPE("Mtp unable to read eventfd\n");
+				error = errno;
+				continue;
+			}
+
+			// It's possible that io_getevents will return more events than the eventFd reported,
+			// since events may appear in the time between the calls. In this case, the eventFd will
+			// show up as readable next iteration, but there will be fewer or no events to actually
+			// wait for. Thus we never want io_getevents to block.
+			int this_events = TEMP_FAILURE_RETRY(io_getevents(mCtx, 0, AIO_BUFS_MAX, events, &ZERO_TIMEOUT));
+			if (this_events == -1) {
+				MTPE("Mtp error getting events");
+				error = errno;
+			}
+			// Add up the total amount of data and find errors on the way.
+			for (unsigned j = 0; j < static_cast<unsigned>(this_events); j++) {
+				if (events[j].res < 0) {
+					errno = -events[j].res;
+					MTPE("Mtp got error event\n");
+					error = errno;
+				}
+				ret += events[j].res;
+			}
+			num_events += this_events;
+			if (counter)
+				*counter += this_events;
+		}
+		if (error) {
+			errno = error;
+			ret = -1;
+			break;
+		}
+	}
+	return ret;
+}
+
+void MtpFfsHandle::cancelTransaction() {
+	// Device cancels by stalling both bulk endpoints.
+	if (::read(mBulkIn, nullptr, 0) != -1 || errno != EBADMSG)
+		MTPE("Mtp stall failed on bulk in\n");
+	if (::write(mBulkOut, nullptr, 0) != -1 || errno != EBADMSG)
+		MTPE("Mtp stall failed on bulk out\n");
+	mCanceled = true;
+	errno = ECANCELED;
+}
+
+int MtpFfsHandle::cancelEvents(struct iocb **iocb, struct io_event *events, unsigned start,
+		unsigned end) {
+	// Some manpages for io_cancel are out of date and incorrect.
+	// io_cancel will return -EINPROGRESS on success and does
+	// not place the event in the given memory. We have to use
+	// io_getevents to wait for all the events we cancelled.
+	int ret = 0;
+	unsigned num_events = 0;
+	int save_errno = errno;
+	errno = 0;
+
+	for (unsigned j = start; j < end; j++) {
+		if (io_cancel(mCtx, iocb[j], nullptr) != -1 || errno != EINPROGRESS) {
+			MTPE("Mtp couldn't cancel request\n");
+		} else {
+			num_events++;
+		}
+	}
+	if (num_events != end - start) {
+		ret = -1;
+		errno = EIO;
+	}
+	int evs = TEMP_FAILURE_RETRY(io_getevents(mCtx, num_events, AIO_BUFS_MAX, events, nullptr));
+	if (static_cast<unsigned>(evs) != num_events) {
+		MTPE("Mtp couldn't cancel all requests\n");
+		ret = -1;
+	}
+
+	uint64_t ev_cnt = 0;
+	if (num_events && ::read(mEventFd, &ev_cnt, sizeof(ev_cnt)) == -1)
+		MTPE("Mtp Unable to read event fd\n");
+
+	if (ret == 0) {
+		// Restore errno since it probably got overriden with EINPROGRESS.
+		errno = save_errno;
+	}
+	return ret;
+}
+
+int MtpFfsHandle::iobufSubmit(struct io_buffer *buf, int fd, unsigned length, bool read) {
+	int ret = 0;
+	buf->actual = AIO_BUFS_MAX;
+	for (unsigned j = 0; j < AIO_BUFS_MAX; j++) {
+		unsigned rq_length = std::min(AIO_BUF_LEN, length - AIO_BUF_LEN * j);
+		io_prep(buf->iocb[j], fd, buf->buf[j], rq_length, 0, read);
+		buf->iocb[j]->aio_flags |= IOCB_FLAG_RESFD;
+		buf->iocb[j]->aio_resfd = mEventFd;
+
+		// Not enough data, so table is truncated.
+		if (rq_length < AIO_BUF_LEN || length == AIO_BUF_LEN * (j + 1)) {
+			buf->actual = j + 1;
+			break;
+		}
+	}
+
+	ret = io_submit(mCtx, buf->actual, buf->iocb.data());
+	if (ret != static_cast<int>(buf->actual)) {
+		MTPE("Mtp io_submit\n");
+		if (ret != -1) {
+			errno = EIO;
+		}
+		ret = -1;
+	}
+	return ret;
+}
+
+int MtpFfsHandle::receiveFile(mtp_file_range mfr, bool zero_packet) {
+	// When receiving files, the incoming length is given in 32 bits.
+	// A >=4G file is given as 0xFFFFFFFF
+	uint32_t file_length = mfr.length;
+	uint64_t offset = mfr.offset;
+
+	struct aiocb aio;
+	aio.aio_fildes = mfr.fd;
+	aio.aio_buf = nullptr;
+	struct aiocb *aiol[] = {&aio};
+
+	int ret = -1;
+	unsigned i = 0;
+	size_t length;
+	struct io_event ioevs[AIO_BUFS_MAX];
+	bool has_write = false;
+	bool error = false;
+	bool write_error = false;
+	int packet_size = getPacketSize(mBulkOut);
+	bool short_packet = false;
+	advise(mfr.fd);
+
+	// Break down the file into pieces that fit in buffers
+	while (file_length > 0 || has_write) {
+		// Queue an asynchronous read from USB.
+		if (file_length > 0) {
+			length = std::min(static_cast<uint32_t>(MAX_FILE_CHUNK_SIZE), file_length);
+			if (iobufSubmit(&mIobuf[i], mBulkOut, length, true) == -1)
+				error = true;
+		}
+
+		// Get the return status of the last write request.
+		if (has_write) {
+			aio_suspend(aiol, 1, nullptr);
+			int written = aio_return(&aio);
+			if (static_cast<size_t>(written) < aio.aio_nbytes) {
+				errno = written == -1 ? aio_error(&aio) : EIO;
+				MTPE("Mtp error writing to disk\n");
+				write_error = true;
+			}
+			has_write = false;
+		}
+
+		if (error) {
+			return -1;
+		}
+
+		// Get the result of the read request, and queue a write to disk.
+		if (file_length > 0) {
+			unsigned num_events = 0;
+			ret = 0;
+			unsigned short_i = mIobuf[i].actual;
+			while (num_events < short_i) {
+				// Get all events up to the short read, if there is one.
+				// We must wait for each event since data transfer could end at any time.
+				int this_events = 0;
+				int event_ret = waitEvents(&mIobuf[i], 1, ioevs, &this_events);
+				num_events += this_events;
+
+				if (event_ret == -1) {
+					cancelEvents(mIobuf[i].iocb.data(), ioevs, num_events, mIobuf[i].actual);
+					return -1;
+				}
+				ret += event_ret;
+				for (int j = 0; j < this_events; j++) {
+					// struct io_event contains a pointer to the associated struct iocb as a __u64.
+					if (static_cast<__u64>(ioevs[j].res) <
+							reinterpret_cast<struct iocb*>(ioevs[j].obj)->aio_nbytes) {
+						// We've found a short event. Store the index since
+						// events won't necessarily arrive in the order they are queued.
+						short_i = (ioevs[j].obj - reinterpret_cast<uint64_t>(mIobuf[i].iocbs.data()))
+							/ sizeof(struct iocb) + 1;
+						short_packet = true;
+					}
+				}
+			}
+			if (short_packet) {
+				if (cancelEvents(mIobuf[i].iocb.data(), ioevs, short_i, mIobuf[i].actual)) {
+					write_error = true;
+				}
+			}
+			if (file_length == MAX_MTP_FILE_SIZE) {
+				// For larger files, receive until a short packet is received.
+				if (static_cast<size_t>(ret) < length) {
+					file_length = 0;
+				}
+			} else if (ret < static_cast<int>(length)) {
+				// If file is less than 4G and we get a short packet, it's an error.
+				errno = EIO;
+				MTPE("Mtp got unexpected short packet\n");
+				return -1;
+			} else {
+				file_length -= ret;
+			}
+
+			if (write_error) {
+				cancelTransaction();
+				return -1;
+			}
+
+			// Enqueue a new write request
+			aio_prepare(&aio, mIobuf[i].bufs.data(), ret, offset);
+			aio_write(&aio);
+
+			offset += ret;
+			i = (i + 1) % NUM_IO_BUFS;
+			has_write = true;
+		}
+	}
+	if ((ret % packet_size == 0 && !short_packet) || zero_packet) {
+		// Receive an empty packet if size is a multiple of the endpoint size
+		// and we didn't already get an empty packet from the header or large file.
+		if (read(mIobuf[0].bufs.data(), packet_size) != 0) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+int MtpFfsHandle::sendFile(mtp_file_range mfr) {
+	uint64_t file_length = mfr.length;
+	uint32_t given_length = std::min(static_cast<uint64_t>(MAX_MTP_FILE_SIZE),
+			file_length + sizeof(mtp_data_header));
+	uint64_t offset = mfr.offset;
+	int packet_size = getPacketSize(mBulkIn);
+
+	// If file_length is larger than a size_t, truncating would produce the wrong comparison.
+	// Instead, promote the left side to 64 bits, then truncate the small result.
+	int init_read_len = std::min(
+			static_cast<uint64_t>(packet_size - sizeof(mtp_data_header)), file_length);
+
+	advise(mfr.fd);
+
+	struct aiocb aio;
+	aio.aio_fildes = mfr.fd;
+	struct aiocb *aiol[] = {&aio};
+	int ret = 0;
+	int length, num_read;
+	unsigned i = 0;
+	struct io_event ioevs[AIO_BUFS_MAX];
+	bool error = false;
+	bool has_write = false;
+
+	// Send the header data
+	mtp_data_header *header = reinterpret_cast<mtp_data_header*>(mIobuf[0].bufs.data());
+	header->length = htole32(given_length);
+	header->type = htole16(2); // data packet
+	header->command = htole16(mfr.command);
+	header->transaction_id = htole32(mfr.transaction_id);
+
+	// Some hosts don't support header/data separation even though MTP allows it
+	// Handle by filling first packet with initial file data
+	if (TEMP_FAILURE_RETRY(pread(mfr.fd, mIobuf[0].bufs.data() +
+					sizeof(mtp_data_header), init_read_len, offset))
+			!= init_read_len) return -1;
+	if (doAsync(mIobuf[0].bufs.data(), sizeof(mtp_data_header) + init_read_len,
+				false, false /* zlps are handled below */) == -1)
+		return -1;
+	file_length -= init_read_len;
+	offset += init_read_len;
+	ret = init_read_len + sizeof(mtp_data_header);
+
+	// Break down the file into pieces that fit in buffers
+	while(file_length > 0 || has_write) {
+		if (file_length > 0) {
+			// Queue up a read from disk.
+			length = std::min(static_cast<uint64_t>(MAX_FILE_CHUNK_SIZE), file_length);
+			aio_prepare(&aio, mIobuf[i].bufs.data(), length, offset);
+			aio_read(&aio);
+		}
+
+		if (has_write) {
+			// Wait for usb write. Cancel unwritten portion if there's an error.
+			int num_events = 0;
+			if (waitEvents(&mIobuf[(i-1)%NUM_IO_BUFS], mIobuf[(i-1)%NUM_IO_BUFS].actual, ioevs,
+						&num_events) != ret) {
+				error = true;
+				cancelEvents(mIobuf[(i-1)%NUM_IO_BUFS].iocb.data(), ioevs, num_events,
+						mIobuf[(i-1)%NUM_IO_BUFS].actual);
+			}
+			has_write = false;
+		}
+
+		if (file_length > 0) {
+			// Wait for the previous read to finish
+			aio_suspend(aiol, 1, nullptr);
+			num_read = aio_return(&aio);
+			if (static_cast<size_t>(num_read) < aio.aio_nbytes) {
+				errno = num_read == -1 ? aio_error(&aio) : EIO;
+				MTPE("Mtp error reading from disk\n");
+				cancelTransaction();
+				return -1;
+			}
+
+			file_length -= num_read;
+			offset += num_read;
+
+			if (error) {
+				return -1;
+			}
+
+			// Queue up a write to usb.
+			if (iobufSubmit(&mIobuf[i], mBulkIn, num_read, false) == -1) {
+				return -1;
+			}
+			has_write = true;
+			ret = num_read;
+		}
+
+		i = (i + 1) % NUM_IO_BUFS;
+	}
+
+	if (ret % packet_size == 0) {
+		// If the last packet wasn't short, send a final empty packet
+		if (write(mIobuf[0].bufs.data(), 0) != 0) {
+			return -1;
+		}
+	}
+	return 0;
+}
+
+int MtpFfsHandle::sendEvent(mtp_event me) {
+	// Mimic the behavior of f_mtp by sending the event async.
+	// Events aren't critical to the connection, so we don't need to check the return value.
+	char *temp = new char[me.length];
+	memcpy(temp, me.data, me.length);
+	me.data = temp;
+	std::thread t([this, me]() { return this->doSendEvent(me); });
+	t.detach();
+	return 0;
+}
+
+void MtpFfsHandle::doSendEvent(mtp_event me) {
+	unsigned length = me.length;
+	int ret = ::write(mIntr, me.data, length);
+	if (static_cast<unsigned>(ret) != length)
+		MTPE("Mtp error sending event thread!\n");
+	delete[] reinterpret_cast<char*>(me.data);
+}
+
diff --git a/mtp/ffs/MtpFfsHandle.h b/mtp/ffs/MtpFfsHandle.h
new file mode 100644
index 0000000..20f74fa
--- /dev/null
+++ b/mtp/ffs/MtpFfsHandle.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_FFS_HANDLE_H
+#define _MTP_FFS_HANDLE_H
+
+#include <android-base/unique_fd.h>
+#include <linux/aio_abi.h>
+#include <mutex>
+#include <sys/poll.h>
+#include <time.h>
+#include <thread>
+#include <vector>
+
+#include <IMtpHandle.h>
+
+constexpr int NUM_IO_BUFS = 2;
+
+struct io_buffer {
+	std::vector<struct iocb> iocbs;		// Holds memory for all iocbs. Not used directly.
+	std::vector<struct iocb*> iocb;		// Pointers to individual iocbs, for syscalls
+	std::vector<unsigned char> bufs;	// A large buffer, used with filesystem io
+	std::vector<unsigned char*> buf;	// Pointers within the larger buffer, for syscalls
+	unsigned actual;					// The number of buffers submitted for this request
+};
+
+template <class T> class MtpFfsHandleTest;
+
+class MtpFfsHandle : public IMtpHandle {
+	template <class T> friend class MtpFfsHandleTest;
+protected:
+	void closeConfig();
+	void closeEndpoints();
+	void advise(int fd);
+	int handleControlRequest(const struct usb_ctrlrequest *request);
+	int doAsync(void* data, size_t len, bool read, bool zero_packet);
+	int handleEvent();
+	void cancelTransaction();
+	void doSendEvent(mtp_event me);
+	bool openEndpoints(bool ptp);
+
+	static int getPacketSize(int ffs_fd);
+
+	bool mCanceled;
+
+	android::base::unique_fd mControl;
+	// "in" from the host's perspective => sink for mtp server
+	android::base::unique_fd mBulkIn;
+	// "out" from the host's perspective => source for mtp server
+	android::base::unique_fd mBulkOut;
+	android::base::unique_fd mIntr;
+
+	aio_context_t mCtx;
+
+	android::base::unique_fd mEventFd;
+	struct pollfd mPollFds[2];
+
+	struct io_buffer mIobuf[NUM_IO_BUFS];
+
+	// Submit an io request of given length. Return amount submitted or -1.
+	int iobufSubmit(struct io_buffer *buf, int fd, unsigned length, bool read);
+
+	// Cancel submitted requests from start to end in the given array. Return 0 or -1.
+	int cancelEvents(struct iocb **iocb, struct io_event *events, unsigned start, unsigned end);
+
+	// Wait for at minimum the given number of events. Returns the amount of data in the returned
+	// events. Increments counter by the number of events returned.
+	int waitEvents(struct io_buffer *buf, int min_events, struct io_event *events, int *counter);
+
+public:
+	int read(void *data, size_t len) override;
+	int write(const void *data, size_t len) override;
+
+	int receiveFile(mtp_file_range mfr, bool zero_packet) override;
+	int sendFile(mtp_file_range mfr) override;
+	int sendEvent(mtp_event me) override;
+
+	int start(bool ptp) override;
+	void close() override;
+
+	bool writeDescriptors(bool ptp);
+
+	MtpFfsHandle(int controlFd);
+	~MtpFfsHandle();
+};
+
+struct mtp_data_header {
+	/* length of packet, including this header */
+	__le32 length;
+	/* container type (2 for data packet) */
+	__le16 type;
+	/* MTP command code */
+	__le16 command;
+	/* MTP transaction ID */
+	__le32 transaction_id;
+};
+
+#endif // _MTP_FFS_HANDLE_H
+
diff --git a/mtp/ffs/MtpMessage.hpp b/mtp/ffs/MtpMessage.hpp
new file mode 100644
index 0000000..31465d8
--- /dev/null
+++ b/mtp/ffs/MtpMessage.hpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++
+ */
+
+#ifndef _MTPMESSAGE_HPP
+#define _MTPMESSAGE_HPP
+
+#define MTP_MESSAGE_ADD_STORAGE    1
+#define MTP_MESSAGE_REMOVE_STORAGE 2
+
+struct mtpmsg {
+	int message_type; // 1 is add, 2 is remove, see above
+	unsigned int storage_id;
+	char display[1024];
+	char path[1024];
+	uint64_t maxFileSize;
+};
+
+#endif //_MTPMESSAGE_HPP
diff --git a/mtp/ffs/MtpObjectInfo.cpp b/mtp/ffs/MtpObjectInfo.cpp
new file mode 100644
index 0000000..3b4d80c
--- /dev/null
+++ b/mtp/ffs/MtpObjectInfo.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpObjectInfo"
+
+#include "MtpDebug.h"
+#include "MtpDataPacket.h"
+#include "MtpObjectInfo.h"
+#include "MtpStringBuffer.h"
+#include "MtpUtils.h"
+
+MtpObjectInfo::MtpObjectInfo(MtpObjectHandle handle)
+	:	mHandle(handle),
+		mStorageID(0),
+		mFormat(0),
+		mProtectionStatus(0),
+		mCompressedSize(0),
+		mThumbFormat(0),
+		mThumbCompressedSize(0),
+		mThumbPixWidth(0),
+		mThumbPixHeight(0),
+		mImagePixWidth(0),
+		mImagePixHeight(0),
+		mImagePixDepth(0),
+		mParent(0),
+		mAssociationType(0),
+		mAssociationDesc(0),
+		mSequenceNumber(0),
+		mName(NULL),
+		mDateCreated(0),
+		mDateModified(0),
+		mKeywords(NULL)
+{
+}
+
+MtpObjectInfo::~MtpObjectInfo() {
+	if (mName)
+		free(mName);
+	if (mKeywords)
+		free(mKeywords);
+}
+
+bool MtpObjectInfo::read(MtpDataPacket& packet) {
+	MtpStringBuffer string;
+	time_t time;
+
+	if (!packet.getUInt32(mStorageID)) return false;
+	if (!packet.getUInt16(mFormat)) return false;
+	if (!packet.getUInt16(mProtectionStatus)) return false;
+	if (!packet.getUInt32(mCompressedSize)) return false;
+	if (!packet.getUInt16(mThumbFormat)) return false;
+	if (!packet.getUInt32(mThumbCompressedSize)) return false;
+	if (!packet.getUInt32(mThumbPixWidth)) return false;
+	if (!packet.getUInt32(mThumbPixHeight)) return false;
+	if (!packet.getUInt32(mImagePixWidth)) return false;
+	if (!packet.getUInt32(mImagePixHeight)) return false;
+	if (!packet.getUInt32(mImagePixDepth)) return false;
+	if (!packet.getUInt32(mParent)) return false;
+	if (!packet.getUInt16(mAssociationType)) return false;
+	if (!packet.getUInt32(mAssociationDesc)) return false;
+	if (!packet.getUInt32(mSequenceNumber)) return false;
+
+	if (!packet.getString(string)) return false;
+	mName = strdup((const char *)string);
+	if (!mName) return false;
+
+	if (!packet.getString(string)) return false;
+	if (parseDateTime((const char*)string, time))
+		mDateCreated = time;
+
+	if (!packet.getString(string)) return false;
+	if (parseDateTime((const char*)string, time))
+		mDateModified = time;
+
+	if (!packet.getString(string)) return false;
+	mKeywords = strdup((const char *)string);
+	if (!mKeywords) return false;
+
+	return true;
+}
+
+void MtpObjectInfo::print() {
+	MTPD("MtpObject Info %08X: %s\n", mHandle, mName);
+	MTPD("	mStorageID: %08X mFormat: %04X mProtectionStatus: %d\n",
+			mStorageID, mFormat, mProtectionStatus);
+	MTPD("	mCompressedSize: %d mThumbFormat: %04X mThumbCompressedSize: %d\n",
+			mCompressedSize, mFormat, mThumbCompressedSize);
+	MTPD("	mThumbPixWidth: %d mThumbPixHeight: %d\n", mThumbPixWidth, mThumbPixHeight);
+	MTPD("	mImagePixWidth: %d mImagePixHeight: %d mImagePixDepth: %d\n",
+			mImagePixWidth, mImagePixHeight, mImagePixDepth);
+	MTPD("	mParent: %08X mAssociationType: %04X mAssociationDesc: %04X\n",
+			mParent, mAssociationType, mAssociationDesc);
+	MTPD("	mSequenceNumber: %d mDateCreated: %ld mDateModified: %ld mKeywords: %s\n",
+			mSequenceNumber, mDateCreated, mDateModified, mKeywords);
+}
diff --git a/mtp/ffs/MtpObjectInfo.h b/mtp/ffs/MtpObjectInfo.h
new file mode 100644
index 0000000..74e3719
--- /dev/null
+++ b/mtp/ffs/MtpObjectInfo.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_OBJECT_INFO_H
+#define _MTP_OBJECT_INFO_H
+
+#include "MtpTypes.h"
+
+class MtpDataPacket;
+
+class MtpObjectInfo {
+public:
+	MtpObjectHandle		mHandle;
+	MtpStorageID		mStorageID;
+	MtpObjectFormat		mFormat;
+	uint16_t			mProtectionStatus;
+	uint32_t			mCompressedSize;
+	MtpObjectFormat		mThumbFormat;
+	uint32_t			mThumbCompressedSize;
+	uint32_t			mThumbPixWidth;
+	uint32_t			mThumbPixHeight;
+	uint32_t			mImagePixWidth;
+	uint32_t			mImagePixHeight;
+	uint32_t			mImagePixDepth;
+	MtpObjectHandle		mParent;
+	uint16_t			mAssociationType;
+	uint32_t			mAssociationDesc;
+	uint32_t			mSequenceNumber;
+	char*				mName;
+	time_t				mDateCreated;
+	time_t				mDateModified;
+	char*				mKeywords;
+
+public:
+	explicit			MtpObjectInfo(MtpObjectHandle handle);
+	virtual				~MtpObjectInfo();
+
+	bool				read(MtpDataPacket& packet);
+
+	void				print();
+};
+
+#endif // _MTP_OBJECT_INFO_H
diff --git a/mtp/ffs/MtpPacket.cpp b/mtp/ffs/MtpPacket.cpp
new file mode 100644
index 0000000..9d6f875
--- /dev/null
+++ b/mtp/ffs/MtpPacket.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpPacket"
+
+#include "MtpDebug.h"
+#include "MtpPacket.h"
+#include "mtp.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <usbhost/usbhost.h>
+
+MtpPacket::MtpPacket(int bufferSize)
+	:	mBuffer(NULL),
+		mBufferSize(bufferSize),
+		mAllocationIncrement(bufferSize),
+		mPacketSize(0)
+{
+	mBuffer = (uint8_t *)malloc(bufferSize);
+	if (!mBuffer) {
+		MTPE("out of memory!");
+		abort();
+	}
+}
+
+MtpPacket::~MtpPacket() {
+	if (mBuffer)
+		free(mBuffer);
+}
+
+void MtpPacket::reset() {
+	allocate(MTP_CONTAINER_HEADER_SIZE);
+	mPacketSize = MTP_CONTAINER_HEADER_SIZE;
+	memset(mBuffer, 0, mBufferSize);
+}
+
+void MtpPacket::allocate(size_t length) {
+	if (length > mBufferSize) {
+		int newLength = length + mAllocationIncrement;
+		mBuffer = (uint8_t *)realloc(mBuffer, newLength);
+		if (!mBuffer) {
+			MTPE("out of memory!");
+			abort();
+		}
+		mBufferSize = newLength;
+	}
+}
+
+void MtpPacket::dump() {
+#define DUMP_BYTES_PER_ROW	16
+	char buffer[500];
+	char* bufptr = buffer;
+
+	for (size_t i = 0; i < mPacketSize; i++) {
+		bufptr += snprintf(bufptr, sizeof(buffer) - (bufptr - buffer), "%02X ",
+						   mBuffer[i]);
+		if (i % DUMP_BYTES_PER_ROW == (DUMP_BYTES_PER_ROW - 1)) {
+			ALOGV("%s", buffer);
+			bufptr = buffer;
+		}
+	}
+	if (bufptr != buffer) {
+		// print last line
+		ALOGV("%s", buffer);
+	}
+	ALOGV("\n");
+}
+
+void MtpPacket::copyFrom(const MtpPacket& src) {
+	int length = src.mPacketSize;
+	allocate(length);
+	mPacketSize = length;
+	memcpy(mBuffer, src.mBuffer, length);
+}
+
+uint16_t MtpPacket::getUInt16(int offset) const {
+	return ((uint16_t)mBuffer[offset + 1] << 8) | (uint16_t)mBuffer[offset];
+}
+
+uint32_t MtpPacket::getUInt32(int offset) const {
+	return ((uint32_t)mBuffer[offset + 3] << 24) | ((uint32_t)mBuffer[offset + 2] << 16) |
+		   ((uint32_t)mBuffer[offset + 1] << 8)  | (uint32_t)mBuffer[offset];
+}
+
+void MtpPacket::putUInt16(int offset, uint16_t value) {
+	mBuffer[offset++] = (uint8_t)(value & 0xFF);
+	mBuffer[offset++] = (uint8_t)((value >> 8) & 0xFF);
+}
+
+void MtpPacket::putUInt32(int offset, uint32_t value) {
+	mBuffer[offset++] = (uint8_t)(value & 0xFF);
+	mBuffer[offset++] = (uint8_t)((value >> 8) & 0xFF);
+	mBuffer[offset++] = (uint8_t)((value >> 16) & 0xFF);
+	mBuffer[offset++] = (uint8_t)((value >> 24) & 0xFF);
+}
+
+uint16_t MtpPacket::getContainerCode() const {
+	return getUInt16(MTP_CONTAINER_CODE_OFFSET);
+}
+
+void MtpPacket::setContainerCode(uint16_t code) {
+	putUInt16(MTP_CONTAINER_CODE_OFFSET, code);
+}
+
+uint16_t MtpPacket::getContainerType() const {
+	return getUInt16(MTP_CONTAINER_TYPE_OFFSET);
+}
+
+MtpTransactionID MtpPacket::getTransactionID() const {
+	return getUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET);
+}
+
+void MtpPacket::setTransactionID(MtpTransactionID id) {
+	putUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET, id);
+}
+
+uint32_t MtpPacket::getParameter(int index) const {
+	if (index < 1 || index > 5) {
+		MTPE("index %d out of range in MtpPacket::getParameter", index);
+		return 0;
+	}
+	return getUInt32(MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t));
+}
+
+void MtpPacket::setParameter(int index, uint32_t value) {
+	if (index < 1 || index > 5) {
+		MTPE("index %d out of range in MtpPacket::setParameter", index);
+		return;
+	}
+	int offset = MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t);
+	if (mPacketSize < offset + sizeof(uint32_t))
+		mPacketSize = offset + sizeof(uint32_t);
+	putUInt32(offset, value);
+}
+
+#ifdef MTP_HOST
+int MtpPacket::transfer(struct usb_request* request) {
+	int result = usb_device_bulk_transfer(request->dev,
+							request->endpoint,
+							request->buffer,
+							request->buffer_length,
+							0);
+	request->actual_length = result;
+	return result;
+}
+#endif
diff --git a/mtp/ffs/MtpPacket.h b/mtp/ffs/MtpPacket.h
new file mode 100644
index 0000000..b757091
--- /dev/null
+++ b/mtp/ffs/MtpPacket.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_PACKET_H
+#define _MTP_PACKET_H
+
+#include <android-base/macros.h>
+
+#include "MtpDebug.h"
+#include "MtpTypes.h"
+
+struct usb_device;
+struct usb_request;
+
+class MtpPacket {
+
+protected:
+	uint8_t*			mBuffer;
+	// current size of the buffer
+	size_t				mBufferSize;
+	// number of bytes to add when resizing the buffer
+	size_t				mAllocationIncrement;
+	// size of the data in the packet
+	size_t				mPacketSize;
+
+public:
+	explicit			MtpPacket(int bufferSize);
+	virtual				~MtpPacket();
+
+	// sets packet size to the default container size and sets buffer to zero
+	virtual void		reset();
+
+	void				allocate(size_t length);
+	void				dump();
+	void				copyFrom(const MtpPacket& src);
+
+	uint16_t			getContainerCode() const;
+	void				setContainerCode(uint16_t code);
+
+	uint16_t			getContainerType() const;
+
+	MtpTransactionID	getTransactionID() const;
+	void				setTransactionID(MtpTransactionID id);
+
+	uint32_t			getParameter(int index) const;
+	void				setParameter(int index, uint32_t value);
+
+#ifdef MTP_HOST
+	int					transfer(struct usb_request* request);
+#endif
+
+protected:
+	uint16_t			getUInt16(int offset) const;
+	uint32_t			getUInt32(int offset) const;
+	void				putUInt16(int offset, uint16_t value);
+	void				putUInt32(int offset, uint32_t value);
+
+	DISALLOW_COPY_AND_ASSIGN(MtpPacket);
+};
+
+#endif // _MTP_PACKET_H
diff --git a/mtp/ffs/MtpProperty.cpp b/mtp/ffs/MtpProperty.cpp
new file mode 100644
index 0000000..126cb79
--- /dev/null
+++ b/mtp/ffs/MtpProperty.cpp
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpProperty"
+
+#include <inttypes.h>
+#include <cutils/compiler.h>
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+#include "MtpDataPacket.h"
+#include "MtpDebug.h"
+#include "MtpProperty.h"
+#include "MtpStringBuffer.h"
+#include "MtpUtils.h"
+
+MtpProperty::MtpProperty()
+	:	mCode(0),
+		mType(0),
+		mWriteable(false),
+		mDefaultArrayLength(0),
+		mDefaultArrayValues(NULL),
+		mCurrentArrayLength(0),
+		mCurrentArrayValues(NULL),
+		mGroupCode(0),
+		mFormFlag(kFormNone),
+		mEnumLength(0),
+		mEnumValues(NULL)
+{
+	memset(&mDefaultValue, 0, sizeof(mDefaultValue));
+	memset(&mCurrentValue, 0, sizeof(mCurrentValue));
+	memset(&mMinimumValue, 0, sizeof(mMinimumValue));
+	memset(&mMaximumValue, 0, sizeof(mMaximumValue));
+}
+
+MtpProperty::MtpProperty(MtpPropertyCode propCode,
+						 MtpDataType type,
+						 bool writeable,
+						 int defaultValue)
+	:	mCode(propCode),
+		mType(type),
+		mWriteable(writeable),
+		mDefaultArrayLength(0),
+		mDefaultArrayValues(NULL),
+		mCurrentArrayLength(0),
+		mCurrentArrayValues(NULL),
+		mGroupCode(0),
+		mFormFlag(kFormNone),
+		mEnumLength(0),
+		mEnumValues(NULL)
+{
+	memset(&mDefaultValue, 0, sizeof(mDefaultValue));
+	memset(&mCurrentValue, 0, sizeof(mCurrentValue));
+	memset(&mMinimumValue, 0, sizeof(mMinimumValue));
+	memset(&mMaximumValue, 0, sizeof(mMaximumValue));
+
+	if (defaultValue) {
+		switch (type) {
+			case MTP_TYPE_INT8:
+				mDefaultValue.u.i8 = defaultValue;
+				break;
+			case MTP_TYPE_UINT8:
+				mDefaultValue.u.u8 = defaultValue;
+				break;
+			case MTP_TYPE_INT16:
+				mDefaultValue.u.i16 = defaultValue;
+				break;
+			case MTP_TYPE_UINT16:
+				mDefaultValue.u.u16 = defaultValue;
+				break;
+			case MTP_TYPE_INT32:
+				mDefaultValue.u.i32 = defaultValue;
+				break;
+			case MTP_TYPE_UINT32:
+				mDefaultValue.u.u32 = defaultValue;
+				break;
+			case MTP_TYPE_INT64:
+				mDefaultValue.u.i64 = defaultValue;
+				break;
+			case MTP_TYPE_UINT64:
+				mDefaultValue.u.u64 = defaultValue;
+				break;
+			default:
+				MTPE("unknown type %04X in MtpProperty::MtpProperty", type);
+		}
+	}
+}
+
+MtpProperty::~MtpProperty() {
+	if (mType == MTP_TYPE_STR) {
+		// free all strings
+		free(mDefaultValue.str);
+		free(mCurrentValue.str);
+		free(mMinimumValue.str);
+		free(mMaximumValue.str);
+		if (mDefaultArrayValues) {
+			for (uint32_t i = 0; i < mDefaultArrayLength; i++)
+				free(mDefaultArrayValues[i].str);
+		}
+		if (mCurrentArrayValues) {
+			for (uint32_t i = 0; i < mCurrentArrayLength; i++)
+				free(mCurrentArrayValues[i].str);
+		}
+		if (mEnumValues) {
+			for (uint16_t i = 0; i < mEnumLength; i++)
+				free(mEnumValues[i].str);
+		}
+	}
+	delete[] mDefaultArrayValues;
+	delete[] mCurrentArrayValues;
+	delete[] mEnumValues;
+}
+
+bool MtpProperty::read(MtpDataPacket& packet) {
+	uint8_t temp8;
+
+	if (!packet.getUInt16(mCode)) return false;
+	bool deviceProp = isDeviceProperty();
+	if (!packet.getUInt16(mType)) return false;
+	if (!packet.getUInt8(temp8)) return false;
+	mWriteable = (temp8 == 1);
+	switch (mType) {
+		case MTP_TYPE_AINT8:
+		case MTP_TYPE_AUINT8:
+		case MTP_TYPE_AINT16:
+		case MTP_TYPE_AUINT16:
+		case MTP_TYPE_AINT32:
+		case MTP_TYPE_AUINT32:
+		case MTP_TYPE_AINT64:
+		case MTP_TYPE_AUINT64:
+		case MTP_TYPE_AINT128:
+		case MTP_TYPE_AUINT128:
+			mDefaultArrayValues = readArrayValues(packet, mDefaultArrayLength);
+			if (!mDefaultArrayValues) return false;
+			if (deviceProp) {
+				mCurrentArrayValues = readArrayValues(packet, mCurrentArrayLength);
+				if (!mCurrentArrayValues) return false;
+			}
+			break;
+		default:
+			if (!readValue(packet, mDefaultValue)) return false;
+			if (deviceProp) {
+				if (!readValue(packet, mCurrentValue)) return false;
+			}
+	}
+	if (!deviceProp) {
+		if (!packet.getUInt32(mGroupCode)) return false;
+	}
+	if (!packet.getUInt8(mFormFlag)) return false;
+
+	if (mFormFlag == kFormRange) {
+			if (!readValue(packet, mMinimumValue)) return false;
+			if (!readValue(packet, mMaximumValue)) return false;
+			if (!readValue(packet, mStepSize)) return false;
+	} else if (mFormFlag == kFormEnum) {
+		if (!packet.getUInt16(mEnumLength)) return false;
+		mEnumValues = new MtpPropertyValue[mEnumLength];
+		for (int i = 0; i < mEnumLength; i++) {
+			if (!readValue(packet, mEnumValues[i])) return false;
+		}
+	}
+
+	return true;
+}
+
+void MtpProperty::write(MtpDataPacket& packet) {
+	bool deviceProp = isDeviceProperty();
+
+	packet.putUInt16(mCode);
+	packet.putUInt16(mType);
+	packet.putUInt8(mWriteable ? 1 : 0);
+
+	switch (mType) {
+		case MTP_TYPE_AINT8:
+		case MTP_TYPE_AUINT8:
+		case MTP_TYPE_AINT16:
+		case MTP_TYPE_AUINT16:
+		case MTP_TYPE_AINT32:
+		case MTP_TYPE_AUINT32:
+		case MTP_TYPE_AINT64:
+		case MTP_TYPE_AUINT64:
+		case MTP_TYPE_AINT128:
+		case MTP_TYPE_AUINT128:
+			writeArrayValues(packet, mDefaultArrayValues, mDefaultArrayLength);
+			if (deviceProp)
+				writeArrayValues(packet, mCurrentArrayValues, mCurrentArrayLength);
+			break;
+		default:
+			writeValue(packet, mDefaultValue);
+			if (deviceProp)
+				writeValue(packet, mCurrentValue);
+	}
+	if (!deviceProp)
+		packet.putUInt32(mGroupCode);
+	packet.putUInt8(mFormFlag);
+	if (mFormFlag == kFormRange) {
+			writeValue(packet, mMinimumValue);
+			writeValue(packet, mMaximumValue);
+			writeValue(packet, mStepSize);
+	} else if (mFormFlag == kFormEnum) {
+		packet.putUInt16(mEnumLength);
+		for (int i = 0; i < mEnumLength; i++)
+			writeValue(packet, mEnumValues[i]);
+	}
+}
+
+void MtpProperty::setDefaultValue(const uint16_t* string) {
+	free(mDefaultValue.str);
+	if (string) {
+		MtpStringBuffer buffer(string);
+		mDefaultValue.str = strdup(buffer);
+	}
+	else
+		mDefaultValue.str = NULL;
+}
+
+void MtpProperty::setCurrentValue(const uint16_t* string) {
+	free(mCurrentValue.str);
+	if (string) {
+		MtpStringBuffer buffer(string);
+		mCurrentValue.str = strdup(buffer);
+	}
+	else
+		mCurrentValue.str = NULL;
+}
+
+void MtpProperty::setCurrentValue(MtpDataPacket& packet) {
+	free(mCurrentValue.str);
+	mCurrentValue.str = NULL;
+	readValue(packet, mCurrentValue);
+}
+
+void MtpProperty::setFormRange(int min, int max, int step) {
+	mFormFlag = kFormRange;
+	switch (mType) {
+		case MTP_TYPE_INT8:
+			mMinimumValue.u.i8 = min;
+			mMaximumValue.u.i8 = max;
+			mStepSize.u.i8 = step;
+			break;
+		case MTP_TYPE_UINT8:
+			mMinimumValue.u.u8 = min;
+			mMaximumValue.u.u8 = max;
+			mStepSize.u.u8 = step;
+			break;
+		case MTP_TYPE_INT16:
+			mMinimumValue.u.i16 = min;
+			mMaximumValue.u.i16 = max;
+			mStepSize.u.i16 = step;
+			break;
+		case MTP_TYPE_UINT16:
+			mMinimumValue.u.u16 = min;
+			mMaximumValue.u.u16 = max;
+			mStepSize.u.u16 = step;
+			break;
+		case MTP_TYPE_INT32:
+			mMinimumValue.u.i32 = min;
+			mMaximumValue.u.i32 = max;
+			mStepSize.u.i32 = step;
+			break;
+		case MTP_TYPE_UINT32:
+			mMinimumValue.u.u32 = min;
+			mMaximumValue.u.u32 = max;
+			mStepSize.u.u32 = step;
+			break;
+		case MTP_TYPE_INT64:
+			mMinimumValue.u.i64 = min;
+			mMaximumValue.u.i64 = max;
+			mStepSize.u.i64 = step;
+			break;
+		case MTP_TYPE_UINT64:
+			mMinimumValue.u.u64 = min;
+			mMaximumValue.u.u64 = max;
+			mStepSize.u.u64 = step;
+			break;
+		default:
+			MTPE("unsupported type for MtpProperty::setRange");
+			break;
+	}
+}
+
+void MtpProperty::setFormEnum(const int* values, int count) {
+	 mFormFlag = kFormEnum;
+	 delete[] mEnumValues;
+	 mEnumValues = new MtpPropertyValue[count];
+	 mEnumLength = count;
+
+	for (int i = 0; i < count; i++) {
+		int value = *values++;
+			switch (mType) {
+				case MTP_TYPE_INT8:
+					mEnumValues[i].u.i8 = value;
+					break;
+				case MTP_TYPE_UINT8:
+					mEnumValues[i].u.u8 = value;
+					break;
+				case MTP_TYPE_INT16:
+					mEnumValues[i].u.i16 = value;
+					break;
+				case MTP_TYPE_UINT16:
+					mEnumValues[i].u.u16 = value;
+					break;
+				case MTP_TYPE_INT32:
+					mEnumValues[i].u.i32 = value;
+					break;
+				case MTP_TYPE_UINT32:
+					mEnumValues[i].u.u32 = value;
+					break;
+				case MTP_TYPE_INT64:
+					mEnumValues[i].u.i64 = value;
+					break;
+				case MTP_TYPE_UINT64:
+					mEnumValues[i].u.u64 = value;
+					break;
+				default:
+					MTPE("unsupported type for MtpProperty::setEnum");
+					break;
+		}
+	}
+}
+
+void MtpProperty::setFormDateTime() {
+	 mFormFlag = kFormDateTime;
+}
+
+void MtpProperty::print() {
+	std::string buffer;
+	bool deviceProp = isDeviceProperty();
+	if (deviceProp)
+		MTPD("	  %s (%04X)", MtpDebug::getDevicePropCodeName(mCode), mCode);
+	else
+		MTPD("	  %s (%04X)", MtpDebug::getObjectPropCodeName(mCode), mCode);
+	MTPD("	  type %04X", mType);
+	MTPD("	  writeable %s", (mWriteable ? "true" : "false"));
+	buffer = "	  default value: ";
+	print(mDefaultValue, buffer);
+	MTPD("%s", buffer.c_str());
+	if (deviceProp) {
+		buffer = "	  current value: ";
+		print(mCurrentValue, buffer);
+		MTPD("%s", buffer.c_str());
+	}
+	switch (mFormFlag) {
+		case kFormNone:
+			break;
+		case kFormRange:
+			buffer = "	  Range (";
+			print(mMinimumValue, buffer);
+			buffer += ", ";
+			print(mMaximumValue, buffer);
+			buffer += ", ";
+			print(mStepSize, buffer);
+			buffer += ")";
+			MTPD("%s", buffer.c_str());
+			break;
+		case kFormEnum:
+			buffer = "	  Enum { ";
+			for (int i = 0; i < mEnumLength; i++) {
+				print(mEnumValues[i], buffer);
+				buffer += " ";
+			}
+			buffer += "}";
+			MTPD("%s", buffer.c_str());
+			break;
+		case kFormDateTime:
+			MTPD("	  DateTime\n");
+			break;
+		default:
+			MTPD("	  form %d\n", mFormFlag);
+			break;
+	}
+}
+
+void MtpProperty::print(MtpPropertyValue& value, std::string& buffer) {
+	std::ostringstream s;
+	switch (mType) {
+		case MTP_TYPE_INT8:
+			buffer += std::to_string(value.u.i8);
+			break;
+		case MTP_TYPE_UINT8:
+			buffer += std::to_string(value.u.u8);
+			break;
+		case MTP_TYPE_INT16:
+			buffer += std::to_string(value.u.i16);
+			break;
+		case MTP_TYPE_UINT16:
+			buffer += std::to_string(value.u.u16);
+			break;
+		case MTP_TYPE_INT32:
+			buffer += std::to_string(value.u.i32);
+			break;
+		case MTP_TYPE_UINT32:
+			buffer += std::to_string(value.u.u32);
+			break;
+		case MTP_TYPE_INT64:
+			buffer += std::to_string(value.u.i64);
+			break;
+		case MTP_TYPE_UINT64:
+			buffer += std::to_string(value.u.u64);
+			break;
+		case MTP_TYPE_INT128:
+			for (auto i : value.u.i128) {
+				s << std::hex << std::setfill('0') << std::uppercase << i;
+			}
+			buffer += s.str();
+			break;
+		case MTP_TYPE_UINT128:
+			for (auto i : value.u.u128) {
+				s << std::hex << std::setfill('0') << std::uppercase << i;
+			}
+			buffer += s.str();
+			break;
+		case MTP_TYPE_STR:
+			buffer += value.str;
+			break;
+		default:
+			MTPE("unsupported type for MtpProperty::print\n");
+			break;
+	}
+}
+
+bool MtpProperty::readValue(MtpDataPacket& packet, MtpPropertyValue& value) {
+	MtpStringBuffer stringBuffer;
+
+	switch (mType) {
+		case MTP_TYPE_INT8:
+		case MTP_TYPE_AINT8:
+			if (!packet.getInt8(value.u.i8)) return false;
+			break;
+		case MTP_TYPE_UINT8:
+		case MTP_TYPE_AUINT8:
+			if (!packet.getUInt8(value.u.u8)) return false;
+			break;
+		case MTP_TYPE_INT16:
+		case MTP_TYPE_AINT16:
+			if (!packet.getInt16(value.u.i16)) return false;
+			break;
+		case MTP_TYPE_UINT16:
+		case MTP_TYPE_AUINT16:
+			if (!packet.getUInt16(value.u.u16)) return false;
+			break;
+		case MTP_TYPE_INT32:
+		case MTP_TYPE_AINT32:
+			if (!packet.getInt32(value.u.i32)) return false;
+			break;
+		case MTP_TYPE_UINT32:
+		case MTP_TYPE_AUINT32:
+			if (!packet.getUInt32(value.u.u32)) return false;
+			break;
+		case MTP_TYPE_INT64:
+		case MTP_TYPE_AINT64:
+			if (!packet.getInt64(value.u.i64)) return false;
+			break;
+		case MTP_TYPE_UINT64:
+		case MTP_TYPE_AUINT64:
+			if (!packet.getUInt64(value.u.u64)) return false;
+			break;
+		case MTP_TYPE_INT128:
+		case MTP_TYPE_AINT128:
+			if (!packet.getInt128(value.u.i128)) return false;
+			break;
+		case MTP_TYPE_UINT128:
+		case MTP_TYPE_AUINT128:
+			if (!packet.getUInt128(value.u.u128)) return false;
+			break;
+		case MTP_TYPE_STR:
+			if (!packet.getString(stringBuffer)) return false;
+			value.str = strdup(stringBuffer);
+			break;
+		default:
+			MTPE("unknown type %04X in MtpProperty::readValue", mType);
+			return false;
+	}
+	return true;
+}
+
+void MtpProperty::writeValue(MtpDataPacket& packet, MtpPropertyValue& value) {
+	MtpStringBuffer stringBuffer;
+
+	switch (mType) {
+		case MTP_TYPE_INT8:
+		case MTP_TYPE_AINT8:
+			packet.putInt8(value.u.i8);
+			break;
+		case MTP_TYPE_UINT8:
+		case MTP_TYPE_AUINT8:
+			packet.putUInt8(value.u.u8);
+			break;
+		case MTP_TYPE_INT16:
+		case MTP_TYPE_AINT16:
+			packet.putInt16(value.u.i16);
+			break;
+		case MTP_TYPE_UINT16:
+		case MTP_TYPE_AUINT16:
+			packet.putUInt16(value.u.u16);
+			break;
+		case MTP_TYPE_INT32:
+		case MTP_TYPE_AINT32:
+			packet.putInt32(value.u.i32);
+			break;
+		case MTP_TYPE_UINT32:
+		case MTP_TYPE_AUINT32:
+			packet.putUInt32(value.u.u32);
+			break;
+		case MTP_TYPE_INT64:
+		case MTP_TYPE_AINT64:
+			packet.putInt64(value.u.i64);
+			break;
+		case MTP_TYPE_UINT64:
+		case MTP_TYPE_AUINT64:
+			packet.putUInt64(value.u.u64);
+			break;
+		case MTP_TYPE_INT128:
+		case MTP_TYPE_AINT128:
+			packet.putInt128(value.u.i128);
+			break;
+		case MTP_TYPE_UINT128:
+		case MTP_TYPE_AUINT128:
+			packet.putUInt128(value.u.u128);
+			break;
+		case MTP_TYPE_STR:
+			if (value.str)
+				packet.putString(value.str);
+			else
+				packet.putEmptyString();
+			break;
+		default:
+			MTPE("unknown type %04X in MtpProperty::writeValue", mType);
+	}
+}
+
+MtpPropertyValue* MtpProperty::readArrayValues(MtpDataPacket& packet, uint32_t& length) {
+	if (!packet.getUInt32(length)) return NULL;
+
+	// Fail if resulting array is over 2GB.  This is because the maximum array
+	// size may be less than SIZE_MAX on some platforms.
+	if ( CC_UNLIKELY(
+			length == 0 ||
+			length >= INT32_MAX / sizeof(MtpPropertyValue)) ) {
+		length = 0;
+		return NULL;
+	}
+	MtpPropertyValue* result = new MtpPropertyValue[length];
+	for (uint32_t i = 0; i < length; i++)
+		if (!readValue(packet, result[i])) {
+			delete [] result;
+			return NULL;
+		}
+	return result;
+}
+
+void MtpProperty::writeArrayValues(MtpDataPacket& packet, MtpPropertyValue* values, uint32_t length) {
+	packet.putUInt32(length);
+	for (uint32_t i = 0; i < length; i++)
+		writeValue(packet, values[i]);
+}
diff --git a/mtp/ffs/MtpProperty.h b/mtp/ffs/MtpProperty.h
new file mode 100644
index 0000000..43ec7c3
--- /dev/null
+++ b/mtp/ffs/MtpProperty.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_PROPERTY_H
+#define _MTP_PROPERTY_H
+
+#include "MtpTypes.h"
+
+#include <string>
+
+class MtpDataPacket;
+
+struct MtpPropertyValue {
+	union {
+		int8_t			i8;
+		uint8_t			u8;
+		int16_t			i16;
+		uint16_t		u16;
+		int32_t			i32;
+		uint32_t		u32;
+		int64_t			i64;
+		uint64_t		u64;
+		int128_t		i128;
+		uint128_t		u128;
+	} u;
+	// string in UTF8 format
+	char*				str;
+};
+
+class MtpProperty {
+public:
+	MtpPropertyCode		mCode;
+	MtpDataType			mType;
+	bool				mWriteable;
+	MtpPropertyValue	mDefaultValue;
+	MtpPropertyValue	mCurrentValue;
+
+	// for array types
+	uint32_t			mDefaultArrayLength;
+	MtpPropertyValue*	mDefaultArrayValues;
+	uint32_t			mCurrentArrayLength;
+	MtpPropertyValue*	mCurrentArrayValues;
+
+	enum {
+		kFormNone = 0,
+		kFormRange = 1,
+		kFormEnum = 2,
+		kFormDateTime = 3,
+	};
+
+	uint32_t			mGroupCode;
+	uint8_t				mFormFlag;
+
+	// for range form
+	MtpPropertyValue	mMinimumValue;
+	MtpPropertyValue	mMaximumValue;
+	MtpPropertyValue	mStepSize;
+
+	// for enum form
+	uint16_t			mEnumLength;
+	MtpPropertyValue*	mEnumValues;
+
+public:
+						MtpProperty();
+						MtpProperty(MtpPropertyCode propCode,
+									 MtpDataType type,
+									 bool writeable = false,
+									 int defaultValue = 0);
+	virtual				~MtpProperty();
+
+	MtpPropertyCode getPropertyCode() const { return mCode; }
+	MtpDataType getDataType() const { return mType; }
+
+	bool				read(MtpDataPacket& packet);
+	void				write(MtpDataPacket& packet);
+
+	void				setDefaultValue(const uint16_t* string);
+	void				setCurrentValue(const uint16_t* string);
+	void				setCurrentValue(MtpDataPacket& packet);
+	const MtpPropertyValue& getCurrentValue() { return mCurrentValue; }
+
+	void				setFormRange(int min, int max, int step);
+	void				setFormEnum(const int* values, int count);
+	void				setFormDateTime();
+
+	void				print();
+
+	inline bool			isDeviceProperty() const {
+							return (   ((mCode & 0xF000) == 0x5000)
+									|| ((mCode & 0xF800) == 0xD000));
+						}
+
+private:
+	bool				readValue(MtpDataPacket& packet, MtpPropertyValue& value);
+	void				writeValue(MtpDataPacket& packet, MtpPropertyValue& value);
+	MtpPropertyValue*	readArrayValues(MtpDataPacket& packet, uint32_t& length);
+	void				writeArrayValues(MtpDataPacket& packet,
+											MtpPropertyValue* values, uint32_t length);
+	void				print(MtpPropertyValue& value, std::string& buffer);
+};
+
+#endif // _MTP_PROPERTY_H
diff --git a/mtp/ffs/MtpRequestPacket.cpp b/mtp/ffs/MtpRequestPacket.cpp
new file mode 100644
index 0000000..8ef1f3c
--- /dev/null
+++ b/mtp/ffs/MtpRequestPacket.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpRequestPacket"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include "IMtpHandle.h"
+#include "MtpDebug.h"
+#include "MtpRequestPacket.h"
+
+#include <usbhost/usbhost.h>
+
+MtpRequestPacket::MtpRequestPacket()
+	:	MtpPacket(512),
+		mParameterCount(0)
+{
+}
+
+MtpRequestPacket::~MtpRequestPacket() {
+}
+
+#ifdef MTP_DEVICE
+int MtpRequestPacket::read(IMtpHandle *h) {
+	int ret = h->read(mBuffer, mBufferSize);
+	if (ret < 0) {
+		// file read error
+		return ret;
+	}
+
+	// request packet should have 12 byte header followed by 0 to 5 32-bit arguments
+	const size_t read_size = static_cast<size_t>(ret);
+	if (read_size >= MTP_CONTAINER_HEADER_SIZE
+			&& read_size <= MTP_CONTAINER_HEADER_SIZE + 5 * sizeof(uint32_t)
+			&& ((read_size - MTP_CONTAINER_HEADER_SIZE) & 3) == 0) {
+		mPacketSize = read_size;
+		mParameterCount = (read_size - MTP_CONTAINER_HEADER_SIZE) / sizeof(uint32_t);
+	} else {
+		MTPE("Malformed MTP request packet");
+		ret = -1;
+	}
+	return ret;
+}
+#endif
+
+#ifdef MTP_HOST
+	// write our buffer to the given endpoint (host mode)
+int MtpRequestPacket::write(struct usb_request *request)
+{
+	putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
+	putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_COMMAND);
+	request->buffer = mBuffer;
+	request->buffer_length = mPacketSize;
+	return transfer(request);
+}
+#endif
diff --git a/mtp/ffs/MtpRequestPacket.h b/mtp/ffs/MtpRequestPacket.h
new file mode 100644
index 0000000..f05335b
--- /dev/null
+++ b/mtp/ffs/MtpRequestPacket.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_REQUEST_PACKET_H
+#define _MTP_REQUEST_PACKET_H
+
+#include "MtpPacket.h"
+#include "mtp.h"
+
+class IMtpHandle;
+struct usb_request;
+
+class MtpRequestPacket : public MtpPacket {
+
+public:
+						MtpRequestPacket();
+	virtual				~MtpRequestPacket();
+
+#ifdef MTP_DEVICE
+	// fill our buffer with data from the given usb handle
+	int					read(IMtpHandle *h);
+#endif
+
+#ifdef MTP_HOST
+	// write our buffer to the given endpoint
+	int					write(struct usb_request *request);
+#endif
+
+	inline MtpOperationCode    getOperationCode() const { return getContainerCode(); }
+	inline void				   setOperationCode(MtpOperationCode code)
+													{ return setContainerCode(code); }
+	inline int					getParameterCount() const { return mParameterCount; }
+
+private:
+	int		mParameterCount;
+};
+
+#endif // _MTP_REQUEST_PACKET_H
diff --git a/mtp/ffs/MtpResponsePacket.cpp b/mtp/ffs/MtpResponsePacket.cpp
new file mode 100644
index 0000000..641a4fc
--- /dev/null
+++ b/mtp/ffs/MtpResponsePacket.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpResponsePacket"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include "IMtpHandle.h"
+#include "MtpResponsePacket.h"
+
+#include <usbhost/usbhost.h>
+
+MtpResponsePacket::MtpResponsePacket()
+	:	MtpPacket(512)
+{
+}
+
+MtpResponsePacket::~MtpResponsePacket() {
+}
+
+#ifdef MTP_DEVICE
+int MtpResponsePacket::write(IMtpHandle *h) {
+	putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
+	putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_RESPONSE);
+	int ret = h->write(mBuffer, mPacketSize);
+	return (ret < 0 ? ret : 0);
+}
+#endif
+
+#ifdef MTP_HOST
+int MtpResponsePacket::read(struct usb_request *request) {
+	request->buffer = mBuffer;
+	request->buffer_length = mBufferSize;
+	int ret = transfer(request);
+	 if (ret >= 0)
+		mPacketSize = ret;
+	else
+		mPacketSize = 0;
+	return ret;
+}
+#endif
+
diff --git a/mtp/ffs/MtpResponsePacket.h b/mtp/ffs/MtpResponsePacket.h
new file mode 100644
index 0000000..4bde1ca
--- /dev/null
+++ b/mtp/ffs/MtpResponsePacket.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_RESPONSE_PACKET_H
+#define _MTP_RESPONSE_PACKET_H
+
+#include "MtpPacket.h"
+#include "mtp.h"
+
+class IMtpHandle;
+
+class MtpResponsePacket : public MtpPacket {
+
+public:
+						MtpResponsePacket();
+	virtual				~MtpResponsePacket();
+
+#ifdef MTP_DEVICE
+	// write our data to the given usb handle
+	int					write(IMtpHandle *h);
+#endif
+
+#ifdef MTP_HOST
+	// read our buffer with the given request
+	int					read(struct usb_request *request);
+#endif
+
+	inline MtpResponseCode		getResponseCode() const { return getContainerCode(); }
+	inline void					setResponseCode(MtpResponseCode code)
+													 { return setContainerCode(code); }
+};
+
+#endif // _MTP_RESPONSE_PACKET_H
diff --git a/mtp/ffs/MtpServer.cpp b/mtp/ffs/MtpServer.cpp
new file mode 100755
index 0000000..cacb45e
--- /dev/null
+++ b/mtp/ffs/MtpServer.cpp
@@ -0,0 +1,1468 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <chrono>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#define LOG_TAG "MtpServer"
+
+#include "MtpDebug.h"
+#include "mtp_MtpDatabase.hpp"
+#include "MtpDescriptors.h"
+#include "MtpDevHandle.h"
+#include "MtpFfsCompatHandle.h"
+#include "MtpFfsHandle.h"
+#include "MtpObjectInfo.h"
+#include "MtpProperty.h"
+#include "MtpServer.h"
+#include "MtpStorage.h"
+#include "MtpStringBuffer.h"
+
+static const MtpOperationCode kSupportedOperationCodes[] = {
+	MTP_OPERATION_GET_DEVICE_INFO,
+	MTP_OPERATION_OPEN_SESSION,
+	MTP_OPERATION_CLOSE_SESSION,
+	MTP_OPERATION_GET_STORAGE_IDS,
+	MTP_OPERATION_GET_STORAGE_INFO,
+	MTP_OPERATION_GET_NUM_OBJECTS,
+	MTP_OPERATION_GET_OBJECT_HANDLES,
+	MTP_OPERATION_GET_OBJECT_INFO,
+	MTP_OPERATION_GET_OBJECT,
+	MTP_OPERATION_GET_THUMB,
+	MTP_OPERATION_DELETE_OBJECT,
+	MTP_OPERATION_SEND_OBJECT_INFO,
+	MTP_OPERATION_SEND_OBJECT,
+//	  MTP_OPERATION_INITIATE_CAPTURE,
+//	  MTP_OPERATION_FORMAT_STORE,
+	MTP_OPERATION_RESET_DEVICE,
+//	  MTP_OPERATION_SELF_TEST,
+//	  MTP_OPERATION_SET_OBJECT_PROTECTION,
+//	  MTP_OPERATION_POWER_DOWN,
+	MTP_OPERATION_GET_DEVICE_PROP_DESC,
+	MTP_OPERATION_GET_DEVICE_PROP_VALUE,
+	MTP_OPERATION_SET_DEVICE_PROP_VALUE,
+	MTP_OPERATION_RESET_DEVICE_PROP_VALUE,
+//	  MTP_OPERATION_TERMINATE_OPEN_CAPTURE,
+	MTP_OPERATION_MOVE_OBJECT,
+	MTP_OPERATION_COPY_OBJECT,
+	MTP_OPERATION_GET_PARTIAL_OBJECT,
+//	  MTP_OPERATION_INITIATE_OPEN_CAPTURE,
+	MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
+	MTP_OPERATION_GET_OBJECT_PROP_DESC,
+	MTP_OPERATION_GET_OBJECT_PROP_VALUE,
+	MTP_OPERATION_SET_OBJECT_PROP_VALUE,
+	MTP_OPERATION_GET_OBJECT_PROP_LIST,
+//	  MTP_OPERATION_SET_OBJECT_PROP_LIST,
+//	  MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC,
+//	  MTP_OPERATION_SEND_OBJECT_PROP_LIST,
+	MTP_OPERATION_GET_OBJECT_REFERENCES,
+	MTP_OPERATION_SET_OBJECT_REFERENCES,
+//	  MTP_OPERATION_SKIP,
+	// Android extension for direct file IO
+	MTP_OPERATION_GET_PARTIAL_OBJECT_64,
+	MTP_OPERATION_SEND_PARTIAL_OBJECT,
+	MTP_OPERATION_TRUNCATE_OBJECT,
+	MTP_OPERATION_BEGIN_EDIT_OBJECT,
+	MTP_OPERATION_END_EDIT_OBJECT,
+};
+
+static const MtpEventCode kSupportedEventCodes[] = {
+	MTP_EVENT_OBJECT_ADDED,
+	MTP_EVENT_OBJECT_REMOVED,
+	MTP_EVENT_STORE_ADDED,
+	MTP_EVENT_STORE_REMOVED,
+	MTP_EVENT_DEVICE_PROP_CHANGED,
+};
+
+MtpServer::MtpServer(IMtpDatabase* database, int controlFd, bool ptp,
+					const char *deviceInfoManufacturer,
+					const char *deviceInfoModel,
+					const char *deviceInfoDeviceVersion,
+					const char *deviceInfoSerialNumber)
+	:	mDatabase(database),
+		mPtp(ptp),
+		mDeviceInfoManufacturer(deviceInfoManufacturer),
+		mDeviceInfoModel(deviceInfoModel),
+		mDeviceInfoDeviceVersion(deviceInfoDeviceVersion),
+		mDeviceInfoSerialNumber(deviceInfoSerialNumber),
+		mSessionID(0),
+		mSessionOpen(false),
+		mSendObjectHandle(kInvalidObjectHandle),
+		mSendObjectFormat(0),
+		mSendObjectFileSize(0),
+		mSendObjectModifiedTime(0)
+{
+	bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0;
+	if (ffs_ok) {
+		bool aio_compat = android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false);
+		mHandle = aio_compat ? new MtpFfsCompatHandle(controlFd) : new MtpFfsHandle(controlFd);
+		mHandle->writeDescriptors(mPtp);
+	} else {
+		mHandle = new MtpDevHandle(controlFd);
+	}
+}
+
+MtpServer::~MtpServer() {
+}
+
+void MtpServer::addStorage(MtpStorage* storage) {
+	std::lock_guard<std::mutex> lg(mMutex);
+	mDatabase->createDB(storage, storage->getStorageID());
+	mStorages.push_back(storage);
+	sendStoreAdded(storage->getStorageID());
+}
+
+void MtpServer::removeStorage(MtpStorage* storage) {
+	std::lock_guard<std::mutex> lg(mMutex);
+	auto iter = std::find(mStorages.begin(), mStorages.end(), storage);
+	if (iter != mStorages.end()) {
+		sendStoreRemoved(storage->getStorageID());
+		mStorages.erase(iter);
+	}
+}
+
+MtpStorage* MtpServer::getStorage(MtpStorageID id) {
+	if (id == 0)
+		return mStorages[0];
+	for (MtpStorage *storage : mStorages) {
+		if (storage->getStorageID() == id)
+			return storage;
+	}
+	return nullptr;
+}
+
+bool MtpServer::hasStorage(MtpStorageID id) {
+	if (id == 0 || id == 0xFFFFFFFF)
+		return mStorages.size() > 0;
+	return (getStorage(id) != nullptr);
+}
+
+void MtpServer::run() {
+	if (mHandle->start(mPtp)) {
+		MTPE("Failed to start usb driver!\n");
+		mHandle->close();
+        int controlFd = open(FFS_MTP_EP0, O_RDWR);
+        bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0;
+        if (ffs_ok) {
+            bool aio_compat = android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false);
+            mHandle = aio_compat ? new MtpFfsCompatHandle(controlFd) : new MtpFfsHandle(controlFd);
+            mHandle->writeDescriptors(mPtp);
+        } else {
+            mHandle = new MtpDevHandle(controlFd);
+        }
+		return;
+	}
+
+	while (1) {
+		int ret = mRequest.read(mHandle);
+		if (ret < 0) {
+			MTPE("request read returned %d, errno: %d", ret, errno);
+			if (errno == ECANCELED) {
+				// return to top of loop and wait for next command
+				continue;
+			}
+			break;
+		}
+		MtpOperationCode operation = mRequest.getOperationCode();
+		MtpTransactionID transaction = mRequest.getTransactionID();
+
+		MTPD("operation: %s\n", MtpDebug::getOperationCodeName(operation));
+		// FIXME need to generalize this
+		bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO
+					|| operation == MTP_OPERATION_SET_OBJECT_REFERENCES
+					|| operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE
+					|| operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE);
+		if (dataIn) {
+			int ret = mData.read(mHandle);
+			if (ret < 0) {
+				MTPE("data read returned %d, errno: %d", ret, errno);
+				if (errno == ECANCELED) {
+					// return to top of loop and wait for next command
+					continue;
+				}
+				break;
+			}
+			MTPD("received data:");
+		} else {
+			mData.reset();
+		}
+
+		if (handleRequest()) {
+			if (!dataIn && mData.hasData()) {
+				mData.setOperationCode(operation);
+				mData.setTransactionID(transaction);
+				MTPD("sending data:");
+				ret = mData.write(mHandle);
+				if (ret < 0) {
+					MTPE("request write returned %d, errno: %d", ret, errno);
+					if (errno == ECANCELED) {
+						// return to top of loop and wait for next command
+						continue;
+					}
+					break;
+				}
+			}
+
+			mResponse.setTransactionID(transaction);
+			MTPD("sending response %04X", mResponse.getResponseCode());
+			ret = mResponse.write(mHandle);
+			const int savedErrno = errno;
+			if (ret < 0) {
+				MTPE("request write returned %d, errno: %d", ret, errno);
+				if (savedErrno == ECANCELED) {
+					// return to top of loop and wait for next command
+					continue;
+				}
+				break;
+			}
+		} else {
+			MTPD("skipping response\n");
+		}
+	}
+
+	// commit any open edits
+	int count = mObjectEditList.size();
+	for (int i = 0; i < count; i++) {
+		ObjectEdit* edit = mObjectEditList[i];
+		commitEdit(edit);
+		delete edit;
+	}
+	mObjectEditList.clear();
+
+	mHandle->close();
+}
+
+void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
+	MTPD("MtpServer::sendObjectAdded %d\n", handle);
+	sendEvent(MTP_EVENT_OBJECT_ADDED, handle);
+}
+
+void MtpServer::sendObjectRemoved(MtpObjectHandle handle) {
+	MTPD("MtpServer::sendObjectRemoved %d\n", handle);
+	sendEvent(MTP_EVENT_OBJECT_REMOVED, handle);
+}
+
+void MtpServer::sendStoreRemoved(MtpStorageID id) {
+	MTPD("MtpServer::sendStoreRemoved %08X\n", id);
+	sendEvent(MTP_EVENT_STORE_REMOVED, id);
+}
+
+void MtpServer::sendStoreAdded(MtpStorageID id) {
+	MTPD("MtpServer::sendStoreAdded %08X\n", id);
+	sendEvent(MTP_EVENT_STORE_ADDED, id);
+}
+
+void MtpServer::sendObjectUpdated(MtpObjectHandle handle) {
+	MTPD("MtpServer::sendObjectUpdated %d\n", handle);
+	sendEvent(MTP_EVENT_OBJECT_PROP_CHANGED, handle);
+}
+
+void MtpServer::sendDevicePropertyChanged(MtpDeviceProperty property) {
+	MTPD("MtpServer::sendDevicePropertyChanged %d\n", property);
+	sendEvent(MTP_EVENT_DEVICE_PROP_CHANGED, property);
+}
+
+void MtpServer::sendObjectInfoChanged(MtpObjectHandle handle) {
+	MTPD("MtpServer::sendObjectInfoChanged %d\n", handle);
+	sendEvent(MTP_EVENT_OBJECT_INFO_CHANGED, handle);
+}
+
+void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) {
+	if (mSessionOpen) {
+		mEvent.setEventCode(code);
+		mEvent.setTransactionID(mRequest.getTransactionID());
+		mEvent.setParameter(1, param1);
+		if (mEvent.write(mHandle))
+			MTPE("Mtp send event failed: %s\n", strerror(errno));
+	}
+}
+
+void MtpServer::addEditObject(MtpObjectHandle handle, MtpStringBuffer& path,
+		uint64_t size, MtpObjectFormat format, int fd) {
+	ObjectEdit*  edit = new ObjectEdit(handle, path, size, format, fd);
+	mObjectEditList.push_back(edit);
+}
+
+MtpServer::ObjectEdit* MtpServer::getEditObject(MtpObjectHandle handle) {
+	int count = mObjectEditList.size();
+	for (int i = 0; i < count; i++) {
+		ObjectEdit* edit = mObjectEditList[i];
+		if (edit->mHandle == handle) return edit;
+	}
+	return nullptr;
+}
+
+void MtpServer::removeEditObject(MtpObjectHandle handle) {
+	int count = mObjectEditList.size();
+	for (int i = 0; i < count; i++) {
+		ObjectEdit* edit = mObjectEditList[i];
+		if (edit->mHandle == handle) {
+			delete edit;
+			mObjectEditList.erase(mObjectEditList.begin() + i);
+			return;
+		}
+	}
+	MTPE("ObjectEdit not found in removeEditObject");
+}
+
+void MtpServer::commitEdit(ObjectEdit* edit) {
+	mDatabase->rescanFile((const char *)edit->mPath, edit->mHandle, edit->mFormat);
+}
+
+
+bool MtpServer::handleRequest() {
+	std::lock_guard<std::mutex> lg(mMutex);
+
+	MtpOperationCode operation = mRequest.getOperationCode();
+	MtpResponseCode response;
+
+	mResponse.reset();
+
+	if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
+		mSendObjectHandle = kInvalidObjectHandle;
+		mSendObjectFormat = 0;
+		mSendObjectModifiedTime = 0;
+	}
+
+	int containertype = mRequest.getContainerType();
+	if (containertype != MTP_CONTAINER_TYPE_COMMAND) {
+		MTPE("wrong container type %d", containertype);
+		return false;
+	}
+
+	MTPD("got command %s (%x)\n", MtpDebug::getOperationCodeName(operation), operation);
+
+	switch (operation) {
+		case MTP_OPERATION_GET_DEVICE_INFO:
+			response = doGetDeviceInfo();
+			break;
+		case MTP_OPERATION_OPEN_SESSION:
+			response = doOpenSession();
+			break;
+		case MTP_OPERATION_RESET_DEVICE:
+		case MTP_OPERATION_CLOSE_SESSION:
+			response = doCloseSession();
+			break;
+		case MTP_OPERATION_GET_STORAGE_IDS:
+			response = doGetStorageIDs();
+			break;
+		 case MTP_OPERATION_GET_STORAGE_INFO:
+			response = doGetStorageInfo();
+			break;
+		case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
+			response = doGetObjectPropsSupported();
+			break;
+		case MTP_OPERATION_GET_OBJECT_HANDLES:
+			response = doGetObjectHandles();
+			break;
+		case MTP_OPERATION_GET_NUM_OBJECTS:
+			response = doGetNumObjects();
+			break;
+		case MTP_OPERATION_GET_OBJECT_REFERENCES:
+			response = doGetObjectReferences();
+			break;
+		case MTP_OPERATION_SET_OBJECT_REFERENCES:
+			response = doSetObjectReferences();
+			break;
+		case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
+			response = doGetObjectPropValue();
+			break;
+		case MTP_OPERATION_SET_OBJECT_PROP_VALUE:
+			response = doSetObjectPropValue();
+			break;
+		case MTP_OPERATION_GET_DEVICE_PROP_VALUE:
+			response = doGetDevicePropValue();
+			break;
+		case MTP_OPERATION_SET_DEVICE_PROP_VALUE:
+			response = doSetDevicePropValue();
+			break;
+		case MTP_OPERATION_RESET_DEVICE_PROP_VALUE:
+			response = doResetDevicePropValue();
+			break;
+		case MTP_OPERATION_GET_OBJECT_PROP_LIST:
+			response = doGetObjectPropList();
+			break;
+		case MTP_OPERATION_GET_OBJECT_INFO:
+			response = doGetObjectInfo();
+			break;
+		case MTP_OPERATION_GET_OBJECT:
+			response = doGetObject();
+			break;
+		case MTP_OPERATION_GET_THUMB:
+			response = doGetThumb();
+			break;
+		case MTP_OPERATION_GET_PARTIAL_OBJECT:
+		case MTP_OPERATION_GET_PARTIAL_OBJECT_64:
+			response = doGetPartialObject(operation);
+			break;
+		case MTP_OPERATION_SEND_OBJECT_INFO:
+			response = doSendObjectInfo();
+			break;
+		case MTP_OPERATION_SEND_OBJECT:
+			response = doSendObject();
+			break;
+		case MTP_OPERATION_DELETE_OBJECT:
+			response = doDeleteObject();
+			break;
+		case MTP_OPERATION_COPY_OBJECT:
+			response = doCopyObject();
+			break;
+		case MTP_OPERATION_MOVE_OBJECT:
+			response = doMoveObject();
+			break;
+		case MTP_OPERATION_GET_OBJECT_PROP_DESC:
+			response = doGetObjectPropDesc();
+			break;
+		case MTP_OPERATION_GET_DEVICE_PROP_DESC:
+			response = doGetDevicePropDesc();
+			break;
+		case MTP_OPERATION_SEND_PARTIAL_OBJECT:
+			response = doSendPartialObject();
+			break;
+		case MTP_OPERATION_TRUNCATE_OBJECT:
+			response = doTruncateObject();
+			break;
+		case MTP_OPERATION_BEGIN_EDIT_OBJECT:
+			response = doBeginEditObject();
+			break;
+		case MTP_OPERATION_END_EDIT_OBJECT:
+			response = doEndEditObject();
+			break;
+		default:
+			MTPE("got unsupported command %s (%x)",
+					MtpDebug::getOperationCodeName(operation), operation);
+			response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
+			break;
+	}
+
+	if (response == MTP_RESPONSE_TRANSACTION_CANCELLED)
+		return false;
+	mResponse.setResponseCode(response);
+	return true;
+}
+
+MtpResponseCode MtpServer::doGetDeviceInfo() {
+	MtpStringBuffer   string;
+
+	MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats();
+	MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats();
+	MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties();
+
+	// fill in device info
+	mData.putUInt16(MTP_STANDARD_VERSION);
+	if (mPtp) {
+		mData.putUInt32(0);
+	} else {
+		// MTP Vendor Extension ID
+		mData.putUInt32(6);
+	}
+	mData.putUInt16(MTP_STANDARD_VERSION);
+	if (mPtp) {
+		// no extensions
+		string.set("");
+	} else {
+		// MTP extensions
+		string.set("microsoft.com: 1.0; android.com: 1.0;");
+	}
+	mData.putString(string); // MTP Extensions
+	mData.putUInt16(0); //Functional Mode
+	mData.putAUInt16(kSupportedOperationCodes,
+			sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported
+	mData.putAUInt16(kSupportedEventCodes,
+			sizeof(kSupportedEventCodes) / sizeof(uint16_t)); // Events Supported
+	mData.putAUInt16(deviceProperties); // Device Properties Supported
+	mData.putAUInt16(captureFormats); // Capture Formats
+	mData.putAUInt16(playbackFormats);	// Playback Formats
+
+	mData.putString(mDeviceInfoManufacturer); // Manufacturer
+	mData.putString(mDeviceInfoModel); // Model
+	mData.putString(mDeviceInfoDeviceVersion); // Device Version
+	mData.putString(mDeviceInfoSerialNumber); // Serial Number
+
+	delete playbackFormats;
+	delete captureFormats;
+	delete deviceProperties;
+
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doOpenSession() {
+	if (mSessionOpen) {
+		mResponse.setParameter(1, mSessionID);
+		return MTP_RESPONSE_SESSION_ALREADY_OPEN;
+	}
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+
+	mSessionID = mRequest.getParameter(1);
+	mSessionOpen = true;
+
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doCloseSession() {
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	mSessionID = 0;
+	mSessionOpen = false;
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetStorageIDs() {
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+
+	int count = mStorages.size();
+	mData.putUInt32(count);
+	for (int i = 0; i < count; i++)
+		mData.putUInt32(mStorages[i]->getStorageID());
+
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetStorageInfo() {
+	MtpStringBuffer   string;
+
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+
+	MtpStorageID id = mRequest.getParameter(1);
+	MtpStorage* storage = getStorage(id);
+	if (!storage)
+		return MTP_RESPONSE_INVALID_STORAGE_ID;
+
+	mData.putUInt16(storage->getType());
+	mData.putUInt16(storage->getFileSystemType());
+	mData.putUInt16(storage->getAccessCapability());
+	mData.putUInt64(storage->getMaxCapacity());
+	mData.putUInt64(storage->getFreeSpace());
+	mData.putUInt32(1024*1024*1024); // Free Space in Objects
+	string.set(storage->getDescription());
+	mData.putString(string);
+	mData.putEmptyString();   // Volume Identifier
+
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetObjectPropsSupported() {
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectFormat format = mRequest.getParameter(1);
+	MtpObjectPropertyList* properties = mDatabase->getSupportedObjectProperties(format);
+	mData.putAUInt16(properties);
+	delete properties;
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetObjectHandles() {
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	if (mRequest.getParameterCount() < 3)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpStorageID storageID = mRequest.getParameter(1);		// 0xFFFFFFFF for all storage
+	MtpObjectFormat format = mRequest.getParameter(2);		// 0 for all formats
+	MtpObjectHandle parent = mRequest.getParameter(3);		// 0xFFFFFFFF for objects with no parent
+															// 0x00000000 for all objects
+
+	if (!hasStorage(storageID))
+		return MTP_RESPONSE_INVALID_STORAGE_ID;
+
+	MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
+	if (handles == NULL)
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	mData.putAUInt32(handles);
+	delete handles;
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetNumObjects() {
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	if (mRequest.getParameterCount() < 3)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpStorageID storageID = mRequest.getParameter(1);		// 0xFFFFFFFF for all storage
+	MtpObjectFormat format = mRequest.getParameter(2);		// 0 for all formats
+	MtpObjectHandle parent = mRequest.getParameter(3);		// 0xFFFFFFFF for objects with no parent
+															// 0x00000000 for all objects
+	if (!hasStorage(storageID))
+		return MTP_RESPONSE_INVALID_STORAGE_ID;
+
+	int count = mDatabase->getNumObjects(storageID, format, parent);
+	if (count >= 0) {
+		mResponse.setParameter(1, count);
+		return MTP_RESPONSE_OK;
+	} else {
+		mResponse.setParameter(1, 0);
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	}
+}
+
+MtpResponseCode MtpServer::doGetObjectReferences() {
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+
+	// FIXME - check for invalid object handle
+	MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle);
+	if (handles) {
+		mData.putAUInt32(handles);
+		delete handles;
+	} else {
+		mData.putEmptyArray();
+	}
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doSetObjectReferences() {
+	if (!mSessionOpen)
+		return MTP_RESPONSE_SESSION_NOT_OPEN;
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpStorageID handle = mRequest.getParameter(1);
+
+	MtpObjectHandleList* references = mData.getAUInt32();
+	if (!references)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpResponseCode result = mDatabase->setObjectReferences(handle, references);
+	delete references;
+	return result;
+}
+
+MtpResponseCode MtpServer::doGetObjectPropValue() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	if (mRequest.getParameterCount() < 2)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	MtpObjectProperty property = mRequest.getParameter(2);
+	MTPD("GetObjectPropValue %d %s\n", handle,
+			MtpDebug::getObjectPropCodeName(property));
+
+	return mDatabase->getObjectPropertyValue(handle, property, mData);
+}
+
+MtpResponseCode MtpServer::doSetObjectPropValue() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	if (mRequest.getParameterCount() < 2)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	MtpObjectProperty property = mRequest.getParameter(2);
+	MTPD("SetObjectPropValue %d %s\n", handle,
+			MtpDebug::getObjectPropCodeName(property));
+
+	return mDatabase->setObjectPropertyValue(handle, property, mData);
+}
+
+MtpResponseCode MtpServer::doGetDevicePropValue() {
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpDeviceProperty property = mRequest.getParameter(1);
+	MTPD("GetDevicePropValue %s\n",
+			MtpDebug::getDevicePropCodeName(property));
+
+	return mDatabase->getDevicePropertyValue(property, mData);
+}
+
+MtpResponseCode MtpServer::doSetDevicePropValue() {
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpDeviceProperty property = mRequest.getParameter(1);
+	MTPD("SetDevicePropValue %s\n",
+			MtpDebug::getDevicePropCodeName(property));
+
+	return mDatabase->setDevicePropertyValue(property, mData);
+}
+
+MtpResponseCode MtpServer::doResetDevicePropValue() {
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpDeviceProperty property = mRequest.getParameter(1);
+	MTPD("ResetDevicePropValue %s\n",
+			MtpDebug::getDevicePropCodeName(property));
+
+	return mDatabase->resetDeviceProperty(property);
+}
+
+MtpResponseCode MtpServer::doGetObjectPropList() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	if (mRequest.getParameterCount() < 5)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	// use uint32_t so we can support 0xFFFFFFFF
+	uint32_t format = mRequest.getParameter(2);
+	uint32_t property = mRequest.getParameter(3);
+	int groupCode = mRequest.getParameter(4);
+	int depth = mRequest.getParameter(5);
+   MTPD("GetObjectPropList %d format: %s property: %s group: %d depth: %d\n",
+			handle, MtpDebug::getFormatCodeName(format),
+			MtpDebug::getObjectPropCodeName(property), groupCode, depth);
+
+	return mDatabase->getObjectPropertyList(handle, format, property, groupCode, depth, mData);
+}
+
+MtpResponseCode MtpServer::doGetObjectInfo() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	MtpObjectInfo info(handle);
+	MtpResponseCode result = mDatabase->getObjectInfo(handle, info);
+	if (result == MTP_RESPONSE_OK) {
+		char	date[20];
+
+		mData.putUInt32(info.mStorageID);
+		mData.putUInt16(info.mFormat);
+		mData.putUInt16(info.mProtectionStatus);
+
+		// if object is being edited the database size may be out of date
+		uint32_t size = info.mCompressedSize;
+		ObjectEdit* edit = getEditObject(handle);
+		if (edit)
+			size = (edit->mSize > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->mSize);
+		mData.putUInt32(size);
+
+		mData.putUInt16(info.mThumbFormat);
+		mData.putUInt32(info.mThumbCompressedSize);
+		mData.putUInt32(info.mThumbPixWidth);
+		mData.putUInt32(info.mThumbPixHeight);
+		mData.putUInt32(info.mImagePixWidth);
+		mData.putUInt32(info.mImagePixHeight);
+		mData.putUInt32(info.mImagePixDepth);
+		mData.putUInt32(info.mParent);
+		mData.putUInt16(info.mAssociationType);
+		mData.putUInt32(info.mAssociationDesc);
+		mData.putUInt32(info.mSequenceNumber);
+		mData.putString(info.mName);
+		formatDateTime(info.mDateCreated, date, sizeof(date));
+		mData.putString(date);	 // date created
+		formatDateTime(info.mDateModified, date, sizeof(date));
+		mData.putString(date);	 // date modified
+		mData.putEmptyString();   // keywords
+	}
+	return result;
+}
+
+MtpResponseCode MtpServer::doGetObject() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	MtpStringBuffer pathBuf;
+	int64_t fileLength;
+	MtpObjectFormat format;
+	int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
+	if (result != MTP_RESPONSE_OK)
+		return result;
+
+	auto start = std::chrono::steady_clock::now();
+
+	const char* filePath = (const char *)pathBuf;
+	mtp_file_range	mfr;
+	mfr.fd = open(filePath, O_RDONLY);
+	if (mfr.fd < 0) {
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+	mfr.offset = 0;
+	mfr.length = fileLength;
+	mfr.command = mRequest.getOperationCode();
+	mfr.transaction_id = mRequest.getTransactionID();
+
+	// then transfer the file
+	int ret = mHandle->sendFile(mfr);
+	if (ret < 0) {
+		MTPE("Mtp send file got error %s", strerror(errno));
+		if (errno == ECANCELED) {
+			result = MTP_RESPONSE_TRANSACTION_CANCELLED;
+		} else {
+			result = MTP_RESPONSE_GENERAL_ERROR;
+		}
+	} else {
+		result = MTP_RESPONSE_OK;
+	}
+
+	auto end = std::chrono::steady_clock::now();
+	std::chrono::duration<double> diff = end - start;
+	struct stat sstat;
+	fstat(mfr.fd, &sstat);
+	uint64_t finalsize = sstat.st_size;
+	MTPD("Sent a file over MTP. Time: %f s, Size: %" PRIu64 ", Rate: %f bytes/s",
+			diff.count(), finalsize, ((double) finalsize) / diff.count());
+	closeObjFd(mfr.fd, filePath);
+	return result;
+}
+
+MtpResponseCode MtpServer::doGetThumb() {
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	size_t thumbSize;
+	void* thumb = mDatabase->getThumbnail(handle, thumbSize);
+	if (thumb) {
+		// send data
+		mData.setOperationCode(mRequest.getOperationCode());
+		mData.setTransactionID(mRequest.getTransactionID());
+		mData.writeData(mHandle, thumb, thumbSize);
+		free(thumb);
+		return MTP_RESPONSE_OK;
+	} else {
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+}
+
+MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	uint64_t offset;
+	uint32_t length;
+	offset = mRequest.getParameter(2);
+	if (operation == MTP_OPERATION_GET_PARTIAL_OBJECT_64) {
+		// MTP_OPERATION_GET_PARTIAL_OBJECT_64 takes 4 arguments
+		if (mRequest.getParameterCount() < 4)
+			return MTP_RESPONSE_INVALID_PARAMETER;
+
+		// android extension with 64 bit offset
+		uint64_t offset2 = mRequest.getParameter(3);
+		offset = offset | (offset2 << 32);
+		length = mRequest.getParameter(4);
+	} else {
+		// MTP_OPERATION_GET_PARTIAL_OBJECT takes 3 arguments
+		if (mRequest.getParameterCount() < 3)
+			return MTP_RESPONSE_INVALID_PARAMETER;
+
+		// standard GetPartialObject
+		length = mRequest.getParameter(3);
+	}
+	MtpStringBuffer pathBuf;
+	int64_t fileLength;
+	MtpObjectFormat format;
+	int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
+	if (result != MTP_RESPONSE_OK)
+		return result;
+	if (offset + length > (uint64_t)fileLength)
+		length = fileLength - offset;
+
+	const char* filePath = (const char *)pathBuf;
+	MTPD("sending partial %s\n %" PRIu64 " %" PRIu32, filePath, offset, length);
+	mtp_file_range	mfr;
+	mfr.fd = open(filePath, O_RDONLY);
+	if (mfr.fd < 0) {
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+	mfr.offset = offset;
+	mfr.length = length;
+	mfr.command = mRequest.getOperationCode();
+	mfr.transaction_id = mRequest.getTransactionID();
+	mResponse.setParameter(1, length);
+
+	// transfer the file
+	int ret = mHandle->sendFile(mfr);
+	MTPD("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
+	result = MTP_RESPONSE_OK;
+	if (ret < 0) {
+		if (errno == ECANCELED)
+			result = MTP_RESPONSE_TRANSACTION_CANCELLED;
+		else
+			result = MTP_RESPONSE_GENERAL_ERROR;
+	}
+	closeObjFd(mfr.fd, filePath);
+	return result;
+}
+
+MtpResponseCode MtpServer::doSendObjectInfo() {
+	MtpStringBuffer path;
+	uint16_t temp16;
+	uint32_t temp32;
+
+	if (mRequest.getParameterCount() < 2)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpStorageID storageID = mRequest.getParameter(1);
+	MtpStorage* storage = getStorage(storageID);
+	MtpObjectHandle parent = mRequest.getParameter(2);
+	if (!storage)
+		return MTP_RESPONSE_INVALID_STORAGE_ID;
+
+	// special case the root
+	if (parent == MTP_PARENT_ROOT) {
+		path.set(storage->getPath());
+		parent = 0;
+	} else {
+		int64_t length;
+		MtpObjectFormat format;
+		int result = mDatabase->getObjectFilePath(parent, path, length, format);
+		if (result != MTP_RESPONSE_OK)
+			return result;
+		if (format != MTP_FORMAT_ASSOCIATION)
+			return MTP_RESPONSE_INVALID_PARENT_OBJECT;
+	}
+
+	// read only the fields we need
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;  // storage ID
+	if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectFormat format = temp16;
+	if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER;  // protection status
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;
+	mSendObjectFileSize = temp32;
+	if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER;  // thumb format
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;  // thumb compressed size
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;  // thumb pix width
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;  // thumb pix height
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;  // image pix width
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;  // image pix height
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;  // image bit depth
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;  // parent
+	if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER;
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;
+	if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;  // sequence number
+	MtpStringBuffer name, created, modified;
+	if (!mData.getString(name)) return MTP_RESPONSE_INVALID_PARAMETER;	  // file name
+	if (name.isEmpty()) {
+		MTPE("empty name");
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	}
+	if (!mData.getString(created)) return MTP_RESPONSE_INVALID_PARAMETER;	   // date created
+	if (!mData.getString(modified)) return MTP_RESPONSE_INVALID_PARAMETER;	   // date modified
+	// keywords follow
+
+	MTPD("name: %s format: %04X\n", (const char *)name, format);
+	time_t modifiedTime;
+	if (!parseDateTime(modified, modifiedTime))
+		modifiedTime = 0;
+
+	if (path[path.size() - 1] != '/')
+		path.append("/");
+	path.append(name);
+
+	// check space first
+	if (mSendObjectFileSize > storage->getFreeSpace())
+		return MTP_RESPONSE_STORAGE_FULL;
+	uint64_t maxFileSize = storage->getMaxFileSize();
+	// check storage max file size
+	if (maxFileSize != 0) {
+		// if mSendObjectFileSize is 0xFFFFFFFF, then all we know is the file size
+		// is >= 0xFFFFFFFF
+		if (mSendObjectFileSize > maxFileSize)
+			return MTP_RESPONSE_OBJECT_TOO_LARGE;
+	}
+
+	MTPD("path: %s parent: %d storageID: %08X", (const char*)path, parent, storageID);
+	uint64_t size = 0; // TODO: this needs to be implemented
+	time_t modified_time = 0; // TODO: this needs to be implemented
+	MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path, format,
+			parent, storageID, size, modified_time);
+	if (handle == kInvalidObjectHandle) {
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	if (format == MTP_FORMAT_ASSOCIATION) {
+		int ret = makeFolder((const char *)path);
+		if (ret)
+			return MTP_RESPONSE_GENERAL_ERROR;
+
+		// SendObject does not get sent for directories, so call endSendObject here instead
+		mDatabase->endSendObject((const char*)path, handle, format, MTP_RESPONSE_OK);
+	}
+	mSendObjectFilePath = path;
+	// save the handle for the SendObject call, which should follow
+	mSendObjectHandle = handle;
+	mSendObjectFormat = format;
+	mSendObjectModifiedTime = modifiedTime;
+
+	mResponse.setParameter(1, storageID);
+	mResponse.setParameter(2, parent);
+	mResponse.setParameter(3, handle);
+
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doMoveObject() {
+	if (!hasStorage())
+		return MTP_RESPONSE_GENERAL_ERROR;
+	if (mRequest.getParameterCount() < 3)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle objectHandle = mRequest.getParameter(1);
+	MtpStorageID storageID = mRequest.getParameter(2);
+	MtpStorage* storage = getStorage(storageID);
+	MtpObjectHandle parent = mRequest.getParameter(3);
+	if (!storage)
+		return MTP_RESPONSE_INVALID_STORAGE_ID;
+	MtpStringBuffer path;
+	MtpResponseCode result;
+
+	MtpStringBuffer fromPath;
+	int64_t fileLength;
+	MtpObjectFormat format;
+	MtpObjectInfo info(objectHandle);
+	result = mDatabase->getObjectInfo(objectHandle, info);
+	if (result != MTP_RESPONSE_OK)
+		return result;
+	result = mDatabase->getObjectFilePath(objectHandle, fromPath, fileLength, format);
+	if (result != MTP_RESPONSE_OK)
+		return result;
+
+	// special case the root
+	if (parent == 0) {
+		path.set(storage->getPath());
+	} else {
+		int64_t parentLength;
+		MtpObjectFormat parentFormat;
+		result = mDatabase->getObjectFilePath(parent, path, parentLength, parentFormat);
+		if (result != MTP_RESPONSE_OK)
+			return result;
+		if (parentFormat != MTP_FORMAT_ASSOCIATION)
+			return MTP_RESPONSE_INVALID_PARENT_OBJECT;
+	}
+
+	if (path[path.size() - 1] != '/')
+		path.append("/");
+	path.append(info.mName);
+
+	result = mDatabase->beginMoveObject(objectHandle, parent, storageID);
+	if (result != MTP_RESPONSE_OK)
+		return result;
+
+	if (info.mStorageID == storageID) {
+		MTPD("Moving file from %s to %s", (const char*)fromPath, (const char*)path);
+		if (renameTo(fromPath, path)) {
+			PLOG(ERROR) << "rename() failed from " << fromPath << " to " << path;
+			result = MTP_RESPONSE_GENERAL_ERROR;
+		}
+	} else {
+		MTPD("Moving across storages from %s to %s", (const char*)fromPath, (const char*)path);
+		if (format == MTP_FORMAT_ASSOCIATION) {
+			int ret = makeFolder((const char *)path);
+			ret += copyRecursive(fromPath, path);
+			if (ret) {
+				result = MTP_RESPONSE_GENERAL_ERROR;
+			} else {
+				deletePath(fromPath);
+			}
+		} else {
+			if (copyFile(fromPath, path)) {
+				result = MTP_RESPONSE_GENERAL_ERROR;
+			} else {
+				deletePath(fromPath);
+			}
+		}
+	}
+
+	// If the move failed, undo the database change
+	mDatabase->endMoveObject(info.mParent, parent, info.mStorageID, storageID, objectHandle,
+			result == MTP_RESPONSE_OK);
+
+	return result;
+}
+
+MtpResponseCode MtpServer::doCopyObject() {
+	if (!hasStorage())
+		return MTP_RESPONSE_GENERAL_ERROR;
+	MtpResponseCode result = MTP_RESPONSE_OK;
+	if (mRequest.getParameterCount() < 3)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle objectHandle = mRequest.getParameter(1);
+	MtpStorageID storageID = mRequest.getParameter(2);
+	MtpStorage* storage = getStorage(storageID);
+	MtpObjectHandle parent = mRequest.getParameter(3);
+	if (!storage)
+		return MTP_RESPONSE_INVALID_STORAGE_ID;
+	MtpStringBuffer path;
+
+	MtpStringBuffer fromPath;
+	int64_t fileLength;
+	MtpObjectFormat format;
+	MtpObjectInfo info(objectHandle);
+	result = mDatabase->getObjectInfo(objectHandle, info);
+	if (result != MTP_RESPONSE_OK)
+		return result;
+	result = mDatabase->getObjectFilePath(objectHandle, fromPath, fileLength, format);
+	if (result != MTP_RESPONSE_OK)
+		return result;
+
+	// special case the root
+	if (parent == 0) {
+		path.set(storage->getPath());
+	} else {
+		int64_t parentLength;
+		MtpObjectFormat parentFormat;
+		result = mDatabase->getObjectFilePath(parent, path, parentLength, parentFormat);
+		if (result != MTP_RESPONSE_OK)
+			return result;
+		if (parentFormat != MTP_FORMAT_ASSOCIATION)
+			return MTP_RESPONSE_INVALID_PARENT_OBJECT;
+	}
+
+	// check space first
+	if ((uint64_t) fileLength > storage->getFreeSpace())
+		return MTP_RESPONSE_STORAGE_FULL;
+
+	if (path[path.size() - 1] != '/')
+		path.append("/");
+	path.append(info.mName);
+
+	MtpObjectHandle handle = mDatabase->beginCopyObject(objectHandle, parent, storageID);
+	if (handle == kInvalidObjectHandle) {
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	MTPD("Copying file from %s to %s", (const char*)fromPath, (const char*)path);
+	if (format == MTP_FORMAT_ASSOCIATION) {
+		int ret = makeFolder((const char *)path);
+		ret += copyRecursive(fromPath, path);
+		if (ret) {
+			result = MTP_RESPONSE_GENERAL_ERROR;
+		}
+	} else {
+		if (copyFile(fromPath, path)) {
+			result = MTP_RESPONSE_GENERAL_ERROR;
+		}
+	}
+
+	mDatabase->endCopyObject(handle, result);
+	mResponse.setParameter(1, handle);
+	return result;
+}
+
+MtpResponseCode MtpServer::doSendObject() {
+	if (!hasStorage())
+		return MTP_RESPONSE_GENERAL_ERROR;
+	MtpResponseCode result = MTP_RESPONSE_OK;
+	mode_t mask;
+	int ret, initialData;
+	bool isCanceled = false;
+	struct stat sstat = {};
+
+	auto start = std::chrono::steady_clock::now();
+
+	if (mSendObjectHandle == kInvalidObjectHandle) {
+		MTPE("Expected SendObjectInfo before SendObject");
+		result = MTP_RESPONSE_NO_VALID_OBJECT_INFO;
+		goto done;
+	}
+
+	// read the header, and possibly some data
+	ret = mData.read(mHandle);
+	if (ret < MTP_CONTAINER_HEADER_SIZE) {
+		result = MTP_RESPONSE_GENERAL_ERROR;
+		goto done;
+	}
+	initialData = ret - MTP_CONTAINER_HEADER_SIZE;
+
+	if (mSendObjectFormat == MTP_FORMAT_ASSOCIATION) {
+		if (initialData != 0)
+			MTPE("Expected folder size to be 0!");
+		mSendObjectHandle = kInvalidObjectHandle;
+		mSendObjectFormat = 0;
+		mSendObjectModifiedTime = 0;
+		return result;
+	}
+
+	mtp_file_range	mfr;
+	mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+	if (mfr.fd < 0) {
+		result = MTP_RESPONSE_GENERAL_ERROR;
+		goto done;
+	}
+	fchown(mfr.fd, getuid(), FILE_GROUP);
+	// set permissions
+	mask = umask(0);
+	fchmod(mfr.fd, FILE_PERM);
+	umask(mask);
+
+	if (initialData > 0) {
+		ret = write(mfr.fd, mData.getData(), initialData);
+	}
+
+	if (ret < 0) {
+		MTPE("failed to write initial data");
+		result = MTP_RESPONSE_GENERAL_ERROR;
+	} else {
+		mfr.offset = initialData;
+		if (mSendObjectFileSize == 0xFFFFFFFF) {
+			// tell driver to read until it receives a short packet
+			mfr.length = 0xFFFFFFFF;
+		} else {
+			mfr.length = mSendObjectFileSize - initialData;
+		}
+
+		mfr.command = 0;
+		mfr.transaction_id = 0;
+
+		// transfer the file
+		ret = mHandle->receiveFile(mfr, mfr.length == 0 &&
+				initialData == MTP_BUFFER_SIZE - MTP_CONTAINER_HEADER_SIZE);
+		if ((ret < 0) && (errno == ECANCELED)) {
+			isCanceled = true;
+		}
+	}
+
+	if (mSendObjectModifiedTime) {
+		struct timespec newTime[2];
+		newTime[0].tv_nsec = UTIME_NOW;
+		newTime[1].tv_sec = mSendObjectModifiedTime;
+		newTime[1].tv_nsec = 0;
+		if (futimens(mfr.fd, newTime) < 0) {
+			MTPE("changing modified time failed, %s", strerror(errno));
+		}
+	}
+
+	fstat(mfr.fd, &sstat);
+	closeObjFd(mfr.fd, mSendObjectFilePath);
+
+	if (ret < 0) {
+		MTPE("Mtp receive file got error %s", strerror(errno));
+		unlink(mSendObjectFilePath);
+		if (isCanceled)
+			result = MTP_RESPONSE_TRANSACTION_CANCELLED;
+		else
+			result = MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+done:
+	// reset so we don't attempt to send the data back
+	mData.reset();
+
+	mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat, result == MTP_RESPONSE_OK);
+	mSendObjectHandle = kInvalidObjectHandle;
+	mSendObjectFormat = 0;
+	mSendObjectModifiedTime = 0;
+
+	auto end = std::chrono::steady_clock::now();
+	std::chrono::duration<double> diff = end - start;
+	uint64_t finalsize = sstat.st_size;
+	MTPD("Got a file over MTP. Time: %fs, Size: %" PRIu64 ", Rate: %f bytes/s",
+			diff.count(), finalsize, ((double) finalsize) / diff.count());
+	return result;
+}
+
+MtpResponseCode MtpServer::doDeleteObject() {
+	MTPD("In MtpServer::doDeleteObject\n");
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	MtpObjectFormat format;
+	// FIXME - support deleting all objects if handle is 0xFFFFFFFF
+	// FIXME - implement deleting objects by format
+
+	MtpStringBuffer filePath;
+	int64_t fileLength;
+	int result = mDatabase->getObjectFilePath(handle, filePath, fileLength, format);
+	if (result != MTP_RESPONSE_OK)
+		return result;
+
+	// Don't delete the actual files unless the database deletion is allowed
+	result = mDatabase->beginDeleteObject(handle);
+	if (result != MTP_RESPONSE_OK)
+		return result;
+
+	bool success = deletePath((const char *)filePath);
+
+	mDatabase->endDeleteObject(handle, success);
+	return success ? result : MTP_RESPONSE_PARTIAL_DELETION;
+}
+
+MtpResponseCode MtpServer::doGetObjectPropDesc() {
+	if (mRequest.getParameterCount() < 2)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectProperty propCode = mRequest.getParameter(1);
+	MtpObjectFormat format = mRequest.getParameter(2);
+	MTPD("GetObjectPropDesc %s %s\n", MtpDebug::getObjectPropCodeName(propCode),
+										MtpDebug::getFormatCodeName(format));
+	MtpProperty* property = mDatabase->getObjectPropertyDesc(propCode, format);
+	if (!property)
+		return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+	property->write(mData);
+	delete property;
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doGetDevicePropDesc() {
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpDeviceProperty propCode = mRequest.getParameter(1);
+	MTPD("GetDevicePropDesc %s\n", MtpDebug::getDevicePropCodeName(propCode));
+	MtpProperty* property = mDatabase->getDevicePropertyDesc(propCode);
+	if (!property)
+		return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+	property->write(mData);
+	delete property;
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doSendPartialObject() {
+	if (!hasStorage())
+		return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+	if (mRequest.getParameterCount() < 4)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	uint64_t offset = mRequest.getParameter(2);
+	uint64_t offset2 = mRequest.getParameter(3);
+	offset = offset | (offset2 << 32);
+	uint32_t length = mRequest.getParameter(4);
+
+	ObjectEdit* edit = getEditObject(handle);
+	if (!edit) {
+		MTPE("object not open for edit in doSendPartialObject");
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	// can't start writing past the end of the file
+	if (offset > edit->mSize) {
+		MTPD("writing past end of object, offset: %" PRIu64 ", edit->mSize: %" PRIu64,
+			offset, edit->mSize);
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	const char* filePath = (const char *)edit->mPath;
+	MTPD("receiving partial %s %" PRIu64 " %" PRIu32, filePath, offset, length);
+
+	// read the header, and possibly some data
+	int ret = mData.read(mHandle);
+	if (ret < MTP_CONTAINER_HEADER_SIZE)
+		return MTP_RESPONSE_GENERAL_ERROR;
+	int initialData = ret - MTP_CONTAINER_HEADER_SIZE;
+
+	if (initialData > 0) {
+		ret = pwrite(edit->mFD, mData.getData(), initialData, offset);
+		offset += initialData;
+		length -= initialData;
+	}
+
+	bool isCanceled = false;
+	if (ret < 0) {
+		MTPE("failed to write initial data");
+	} else {
+		mtp_file_range	mfr;
+		mfr.fd = edit->mFD;
+		mfr.offset = offset;
+		mfr.length = length;
+		mfr.command = 0;
+		mfr.transaction_id = 0;
+
+		// transfer the file
+		ret = mHandle->receiveFile(mfr, mfr.length == 0 &&
+				initialData == MTP_BUFFER_SIZE - MTP_CONTAINER_HEADER_SIZE);
+		if ((ret < 0) && (errno == ECANCELED)) {
+			isCanceled = true;
+		}
+	}
+	if (ret < 0) {
+		mResponse.setParameter(1, 0);
+		if (isCanceled)
+			return MTP_RESPONSE_TRANSACTION_CANCELLED;
+		else
+			return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	// reset so we don't attempt to send this back
+	mData.reset();
+	mResponse.setParameter(1, length);
+	uint64_t end = offset + length;
+	if (end > edit->mSize) {
+		edit->mSize = end;
+	}
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doTruncateObject() {
+	if (mRequest.getParameterCount() < 3)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	ObjectEdit* edit = getEditObject(handle);
+	if (!edit) {
+		MTPE("object not open for edit in doTruncateObject");
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	uint64_t offset = mRequest.getParameter(2);
+	uint64_t offset2 = mRequest.getParameter(3);
+	offset |= (offset2 << 32);
+	if (ftruncate(edit->mFD, offset) != 0) {
+		return MTP_RESPONSE_GENERAL_ERROR;
+	} else {
+		edit->mSize = offset;
+		return MTP_RESPONSE_OK;
+	}
+}
+
+MtpResponseCode MtpServer::doBeginEditObject() {
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	if (getEditObject(handle)) {
+		MTPE("object already open for edit in doBeginEditObject");
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	MtpStringBuffer path;
+	int64_t fileLength;
+	MtpObjectFormat format;
+	int result = mDatabase->getObjectFilePath(handle, path, fileLength, format);
+	if (result != MTP_RESPONSE_OK)
+		return result;
+
+	int fd = open((const char *)path, O_RDWR | O_EXCL);
+	if (fd < 0) {
+		MTPE("open failed for %s in doBeginEditObject (%d)", (const char *)path, errno);
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	addEditObject(handle, path, fileLength, format, fd);
+	return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doEndEditObject() {
+	if (mRequest.getParameterCount() < 1)
+		return MTP_RESPONSE_INVALID_PARAMETER;
+	MtpObjectHandle handle = mRequest.getParameter(1);
+	ObjectEdit* edit = getEditObject(handle);
+	if (!edit) {
+		MTPE("object not open for edit in doEndEditObject");
+		return MTP_RESPONSE_GENERAL_ERROR;
+	}
+
+	commitEdit(edit);
+	removeEditObject(handle);
+	return MTP_RESPONSE_OK;
+}
diff --git a/mtp/ffs/MtpServer.h b/mtp/ffs/MtpServer.h
new file mode 100755
index 0000000..4bc07cd
--- /dev/null
+++ b/mtp/ffs/MtpServer.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_SERVER_H
+#define _MTP_SERVER_H
+
+#include "MtpRequestPacket.h"
+#include "MtpDataPacket.h"
+#include "MtpResponsePacket.h"
+#include "MtpEventPacket.h"
+#include "MtpStringBuffer.h"
+#include "mtp.h"
+#include "MtpUtils.h"
+#include "IMtpHandle.h"
+
+#include <memory>
+#include <mutex>
+#include <queue>
+
+class IMtpDatabase;
+class MtpStorage;
+
+class MtpServer {
+
+private:
+	IMtpDatabase*		mDatabase;
+
+	// appear as a PTP device
+	bool				mPtp;
+
+	// Manufacturer to report in DeviceInfo
+	MtpStringBuffer		mDeviceInfoManufacturer;
+	// Model to report in DeviceInfo
+	MtpStringBuffer		mDeviceInfoModel;
+	// Device version to report in DeviceInfo
+	MtpStringBuffer		mDeviceInfoDeviceVersion;
+	// Serial number to report in DeviceInfo
+	MtpStringBuffer		mDeviceInfoSerialNumber;
+
+	// current session ID
+	MtpSessionID		mSessionID;
+	// true if we have an open session and mSessionID is valid
+	bool				mSessionOpen;
+
+	MtpRequestPacket	mRequest;
+	MtpDataPacket		mData;
+	MtpResponsePacket	mResponse;
+
+	MtpEventPacket		mEvent;
+
+	MtpStorageList		mStorages;
+
+	IMtpHandle*			mHandle;
+
+	// handle for new object, set by SendObjectInfo and used by SendObject
+	MtpObjectHandle		mSendObjectHandle;
+	MtpObjectFormat		mSendObjectFormat;
+	MtpStringBuffer		mSendObjectFilePath;
+	size_t				mSendObjectFileSize;
+	time_t				mSendObjectModifiedTime;
+
+	std::mutex			mMutex;
+
+	// represents an MTP object that is being edited using the android extensions
+	// for direct editing (BeginEditObject, SendPartialObject, TruncateObject and EndEditObject)
+	class ObjectEdit {
+		public:
+		MtpObjectHandle		mHandle;
+		MtpStringBuffer			  mPath;
+		uint64_t			mSize;
+		MtpObjectFormat		mFormat;
+		int					mFD;
+
+		ObjectEdit(MtpObjectHandle handle, const char* path, uint64_t size,
+			MtpObjectFormat format, int fd)
+				: mHandle(handle), mPath(path), mSize(size), mFormat(format), mFD(fd) {
+			}
+
+		virtual ~ObjectEdit() {
+			close(mFD);
+		}
+	};
+	std::vector<ObjectEdit*>  mObjectEditList;
+
+public:
+						MtpServer(IMtpDatabase* database, int controlFd, bool ptp,
+									const char *deviceInfoManufacturer,
+									const char *deviceInfoModel,
+									const char *deviceInfoDeviceVersion,
+									const char *deviceInfoSerialNumber);
+	virtual				~MtpServer();
+
+	MtpStorage*			getStorage(MtpStorageID id);
+	inline bool			hasStorage() { return mStorages.size() > 0; }
+	bool				hasStorage(MtpStorageID id);
+	void				addStorage(MtpStorage* storage);
+	void				removeStorage(MtpStorage* storage);
+
+	void				run();
+
+	void				sendObjectAdded(MtpObjectHandle handle);
+	void				sendObjectRemoved(MtpObjectHandle handle);
+	void				sendObjectUpdated(MtpObjectHandle handle);
+	void				sendDevicePropertyChanged(MtpDeviceProperty property);
+	void				sendObjectInfoChanged(MtpObjectHandle handle);
+
+
+private:
+	void				sendStoreAdded(MtpStorageID id);
+	void				sendStoreRemoved(MtpStorageID id);
+	void				sendEvent(MtpEventCode code, uint32_t param1);
+
+	void				addEditObject(MtpObjectHandle handle, MtpStringBuffer& path,
+								uint64_t size, MtpObjectFormat format, int fd);
+	ObjectEdit*			getEditObject(MtpObjectHandle handle);
+	void				removeEditObject(MtpObjectHandle handle);
+	void				commitEdit(ObjectEdit* edit);
+
+	bool				handleRequest();
+
+	MtpResponseCode		doGetDeviceInfo();
+	MtpResponseCode		doOpenSession();
+	MtpResponseCode		doCloseSession();
+	MtpResponseCode		doGetStorageIDs();
+	MtpResponseCode		doGetStorageInfo();
+	MtpResponseCode		doGetObjectPropsSupported();
+	MtpResponseCode		doGetObjectHandles();
+	MtpResponseCode		doGetNumObjects();
+	MtpResponseCode		doGetObjectReferences();
+	MtpResponseCode		doSetObjectReferences();
+	MtpResponseCode		doGetObjectPropValue();
+	MtpResponseCode		doSetObjectPropValue();
+	MtpResponseCode		doGetDevicePropValue();
+	MtpResponseCode		doSetDevicePropValue();
+	MtpResponseCode		doResetDevicePropValue();
+	MtpResponseCode		doGetObjectPropList();
+	MtpResponseCode		doGetObjectInfo();
+	MtpResponseCode		doGetObject();
+	MtpResponseCode		doGetThumb();
+	MtpResponseCode		doGetPartialObject(MtpOperationCode operation);
+	MtpResponseCode		doSendObjectInfo();
+	MtpResponseCode		doSendObject();
+	MtpResponseCode		doDeleteObject();
+	MtpResponseCode		doMoveObject();
+	MtpResponseCode		doCopyObject();
+	MtpResponseCode		doGetObjectPropDesc();
+	MtpResponseCode		doGetDevicePropDesc();
+	MtpResponseCode		doSendPartialObject();
+	MtpResponseCode		doTruncateObject();
+	MtpResponseCode		doBeginEditObject();
+	MtpResponseCode		doEndEditObject();
+};
+
+#endif // _MTP_SERVER_H
diff --git a/mtp/ffs/MtpStorage.cpp b/mtp/ffs/MtpStorage.cpp
new file mode 100755
index 0000000..8c67b5b
--- /dev/null
+++ b/mtp/ffs/MtpStorage.cpp
@@ -0,0 +1,810 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpStorage"
+
+#include "MtpDebug.h"
+#include "MtpStorage.h"
+#include "btree.hpp"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <iterator>
+#include <sys/inotify.h>
+
+#define WATCH_FLAGS ( IN_CREATE | IN_DELETE | IN_MOVE | IN_MODIFY )
+
+MtpStorage::MtpStorage(MtpStorageID id, const char* filePath,
+		const char* description, bool removable, uint64_t maxFileSize, MtpServer* refserver)
+	:	mStorageID(id),
+		mFilePath(filePath),
+		mDescription(description),
+		mMaxCapacity(0),
+		mMaxFileSize(maxFileSize),
+		mRemovable(removable),
+	mServer(refserver)
+{
+	MTPD("MtpStorage id: %d path: %s\n", id, filePath);
+	inotify_thread = 0;
+	inotify_fd = -1;
+	// Threading has not started yet so we should be safe to set these directly instead of using atomics
+	inotify_thread_kill.set_value(0);
+	sendEvents = false;
+	handleCurrentlySending = 0;
+	use_mutex = true;
+	if (pthread_mutex_init(&mtpMutex, NULL) != 0) {
+			MTPE("Failed to init mtpMutex\n");
+			use_mutex = false;
+	}
+	if (pthread_mutex_init(&inMutex, NULL) != 0) {
+			MTPE("Failed to init inMutex\n");
+			pthread_mutex_destroy(&mtpMutex);
+			use_mutex = false;
+	}
+}
+
+MtpStorage::~MtpStorage() {
+	if (inotify_thread) {
+			inotify_thread_kill.set_value(1);
+			MTPD("joining inotify_thread after sending the kill notification.\n");
+			pthread_join(inotify_thread, NULL); // There's not much we can do if there's an error here
+			inotify_thread = 0;
+			MTPD("~MtpStorage removing inotify watches and closing inotify_fd\n");
+			for (std::map<int, Tree*>::iterator i = inotifymap.begin(); i != inotifymap.end(); i++) {
+					inotify_rm_watch(inotify_fd, i->first);
+			}
+			close(inotify_fd);
+			inotifymap.clear();
+	}
+		// Deleting the root tree causes a cascade in btree.cpp that ends up
+		// deleting all of the trees and nodes.
+		delete mtpmap[0];
+		mtpmap.clear();
+		if (use_mutex) {
+				use_mutex = false;
+				MTPD("~MtpStorage destroying mutexes\n");
+				pthread_mutex_destroy(&mtpMutex);
+				pthread_mutex_destroy(&inMutex);
+		}
+
+}
+
+int MtpStorage::getType() const {
+	return (mRemovable ? MTP_STORAGE_REMOVABLE_RAM :  MTP_STORAGE_FIXED_RAM);
+}
+
+int MtpStorage::getFileSystemType() const {
+	return MTP_STORAGE_FILESYSTEM_HIERARCHICAL;
+}
+
+int MtpStorage::getAccessCapability() const {
+	return MTP_STORAGE_READ_WRITE;
+}
+
+uint64_t MtpStorage::getMaxCapacity() {
+	if (mMaxCapacity == 0) {
+		struct statfs	stat;
+		if (statfs(getPath(), &stat))
+			return -1;
+		mMaxCapacity = (uint64_t)stat.f_blocks * (uint64_t)stat.f_bsize;
+	}
+	return mMaxCapacity;
+}
+
+uint64_t MtpStorage::getFreeSpace() {
+	struct statfs	stat;
+	if (statfs(getPath(), &stat))
+		return -1;
+	return (uint64_t)stat.f_bavail * (uint64_t)stat.f_bsize;
+}
+
+const char* MtpStorage::getDescription() const {
+	return (const char *)mDescription;
+}
+
+int MtpStorage::renameObject(MtpObjectHandle handle, std::string newName) {
+		MTPD("MtpStorage::renameObject, handle: %u, new name: '%s'\n", handle, newName.c_str());
+		if (handle == MTP_PARENT_ROOT) {
+				MTPE("parent == MTP_PARENT_ROOT, cannot rename root\n");
+				return -1;
+		} else {
+				for (iter i = mtpmap.begin(); i != mtpmap.end(); i++) {
+						Node* node = i->second->findNode(handle);
+						if (node != NULL) {
+				std::string oldName = getNodePath(node);
+								std::string parentdir = oldName.substr(0, oldName.find_last_of('/'));
+								std::string newFullName = parentdir + "/" + newName;
+								MTPD("old: '%s', new: '%s'\n", oldName.c_str(), newFullName.c_str());
+								if (rename(oldName.c_str(), newFullName.c_str()) == 0) {
+										node->rename(newName);
+										return 0;
+								} else {
+										MTPE("MtpStorage::renameObject failed, handle: %u, new name: '%s'\n", handle, newName.c_str());
+										return -1;
+								}
+						}
+				}
+		}
+		// handle not found on this storage
+		return -1;
+}
+
+MtpObjectHandle MtpStorage::beginSendObject(const char* path,
+																						MtpObjectFormat format,
+																						MtpObjectHandle parent,
+																						__attribute__((unused)) uint64_t size,
+																						__attribute__((unused)) time_t modified) {
+		MTPD("MtpStorage::beginSendObject(), path: '%s', parent: %u, format: %04x\n", path, parent, format);
+		iter it = mtpmap.find(parent);
+		if (it == mtpmap.end()) {
+				MTPE("parent node not found, returning error\n");
+				return kInvalidObjectHandle;
+		}
+		Tree* tree = it->second;
+
+		std::string pathstr(path);
+		size_t slashpos = pathstr.find_last_of('/');
+		if (slashpos == std::string::npos) {
+				MTPE("path has no slash, returning error\n");
+				return kInvalidObjectHandle;
+		}
+		std::string parentdir = pathstr.substr(0, slashpos);
+		std::string basename = pathstr.substr(slashpos + 1);
+		if (parent != 0 && parentdir != getNodePath(tree)) {
+				MTPE("beginSendObject into path '%s' but parent tree has path '%s', returning error\n", parentdir.c_str(), getNodePath(tree).c_str());
+				return kInvalidObjectHandle;
+		}
+
+		MTPD("MtpStorage::beginSendObject() parentdir: %s basename: %s\n", parentdir.c_str(), basename.c_str());
+		// note: for directories, the mkdir call is done later in MtpServer, here we just reserve a handle
+		bool isDir = format == MTP_FORMAT_ASSOCIATION;
+		Node* node = addNewNode(isDir, tree, basename);
+		handleCurrentlySending = node->Mtpid(); // suppress inotify for this node while sending
+
+		return node->Mtpid();
+}
+
+int MtpStorage::createDB() {
+		std::string mtpParent = "";
+		mtpstorageparent = getPath();
+		// root directory is special: handle 0, parent 0, and empty path
+		mtpmap[0] = new Tree(0, 0, "");
+		if (use_mutex) {
+				sendEvents = true;
+				MTPD("inotify_init\n");
+				inotify_fd = inotify_init();
+				if (inotify_fd < 0) {
+						MTPE("Can't run inotify_init for mtp server: %s\n", strerror(errno));
+				} else {
+						MTPD("Starting inotify thread\n");
+						inotify_thread = inotify();
+				}
+		} else {
+				MTPD("NOT starting inotify thread\n");
+		}
+		// for debugging and caching purposes, read the root dir already now
+		readDir(mtpstorageparent, mtpmap[0]);
+		// all other dirs are read on demand
+	//
+		MTPD("MtpStorage::createDB DONE\n");
+		return 0;
+}
+
+Node* MtpStorage::findNode(MtpObjectHandle handle) {
+		for (iter i = mtpmap.begin(); i != mtpmap.end(); i++) {
+				Node* node = i->second->findNode(handle);
+				if (node != NULL) {
+						MTPD("findNode: found node %p for handle %u, name: %s\n", node, handle, node->getName().c_str());
+						if (node->Mtpid() != handle)
+						{
+								MTPE("BUG: entry for handle %u points to node with handle %u\n", handle, node->Mtpid());
+						}
+						return node;
+				}
+		}
+		// Item is not on this storage device
+		MTPD("MtpStorage::findNode: no node found for handle %u on storage %u, searched %u trees\n", handle, mStorageID, mtpmap.size());
+		return NULL;
+}
+
+std::string MtpStorage::getNodePath(Node* node) {
+	std::string path;
+		MTPD("getNodePath: node %p, handle %u\n", node, node->Mtpid());
+		while (node)
+		{
+				path = "/" + node->getName() + path;
+				MtpObjectHandle parent = node->getMtpParentId();
+				if (parent == 0)		// root
+						break;
+				node = findNode(parent);
+		}
+		path = mtpstorageparent + path;
+		MTPD("getNodePath: path %s\n", path.c_str());
+		return path;
+}
+
+MtpObjectHandleList* MtpStorage::getObjectList(__attribute__((unused)) MtpStorageID storageID, MtpObjectHandle parent) {
+		MTPD("MtpStorage::getObjectList, parent: %u\n", parent);
+		//append object id	(numerical #s) of database to int array
+		MtpObjectHandleList* list = new MtpObjectHandleList();
+		if (parent == MTP_PARENT_ROOT) {
+				MTPD("parent == MTP_PARENT_ROOT\n");
+				parent = 0;
+		}
+
+		if (mtpmap.find(parent) == mtpmap.end()) {
+				MTPE("parent handle not found, returning empty list\n");
+				return list;
+		}
+
+		Tree* tree = mtpmap[parent];
+		if (!tree->wasAlreadyRead())
+		{
+				std::string path = getNodePath(tree);
+				MTPD("reading directory on demand for tree %p (%u), path: %s\n", tree, tree->Mtpid(), path.c_str());
+				readDir(path, tree);
+		}
+
+		mtpmap[parent]->getmtpids(list);
+		MTPD("returning %u objects in %s.\n", list->size(), tree->getName().c_str());
+		return list;
+}
+
+Node* MtpStorage::addNewNode(bool isDir, Tree* tree, const std::string& name)
+{
+		// global counter for new object handles
+		static MtpObjectHandle mtpid = 0;
+
+		++mtpid;
+		MTPD("adding new %s node for %s, new handle: %u\n", isDir ? "dir" : "file", name.c_str(), mtpid);
+		MtpObjectHandle parent = tree->Mtpid();
+		MTPD("parent tree: %x, handle: %u, name: %s\n", tree, parent, tree->getName().c_str());
+		Node* node;
+		if (isDir)
+				node = mtpmap[mtpid] = new Tree(mtpid, parent, name);
+		else
+				node = new Node(mtpid, parent, name);
+		tree->addEntry(node);
+		return node;
+}
+
+int MtpStorage::readDir(const std::string& path, Tree* tree)
+{
+		struct dirent *de;
+		int storageID = getStorageID();
+		MtpObjectHandle parent = tree->Mtpid();
+
+		DIR *d = opendir(path.c_str());
+		MTPD("reading dir '%s', parent handle %u\n", path.c_str(), parent);
+		if (d == NULL) {
+				MTPE("error opening '%s' -- error: %s\n", path.c_str(), strerror(errno));
+				return -1;
+		}
+		// TODO: for refreshing dirs: capture old entries here
+		while ((de = readdir(d)) != NULL) {
+				// Because exfat-fuse causes issues with dirent, we will use stat
+				// for some things that dirent should be able to do
+				std::string item = path + "/" + de->d_name;
+				struct stat st;
+				if (lstat(item.c_str(), &st)) {
+						MTPE("Error running lstat on '%s'\n", item.c_str());
+						return -1;
+				}
+				// TODO: if we want to use this for refreshing dirs too, first find existing name and overwrite
+				if (strcmp(de->d_name, ".") == 0)
+						continue;
+				if (strcmp(de->d_name, "..") == 0)
+						continue;
+				Node* node = addNewNode(st.st_mode & S_IFDIR, tree, de->d_name);
+				node->addProperties(item, storageID);
+				//if (sendEvents)
+				//		mServer->sendObjectAdded(node->Mtpid());
+				//		sending events here makes simple-mtpfs very slow, and it is probably the wrong thing to do anyway
+		}
+		closedir(d);
+		// TODO: for refreshing dirs: remove entries that no longer exist (with their nodes)
+		tree->setAlreadyRead(true);
+		addInotify(tree);
+		return 0;
+}
+
+int MtpStorage::addInotify(Tree* tree) {
+		if (inotify_fd < 0) {
+				MTPE("inotify_fd not set or error: %i\n", inotify_fd);
+				return -1;
+		}
+		std::string path = getNodePath(tree);
+		MTPD("adding inotify for tree %x, dir: %s\n", tree, path.c_str());
+		int wd = inotify_add_watch(inotify_fd, path.c_str(), WATCH_FLAGS);
+		if (wd < 0) {
+				MTPE("inotify_add_watch failed: %s\n", strerror(errno));
+				return -1;
+		}
+		inotifymap[wd] = tree;
+		return 0;
+}
+
+pthread_t MtpStorage::inotify(void) {
+		pthread_t thread;
+		pthread_attr_t tattr;
+
+		if (pthread_attr_init(&tattr)) {
+				MTPE("Unable to pthread_attr_init\n");
+				return 0;
+		}
+		if (pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_JOINABLE)) {
+				MTPE("Error setting pthread_attr_setdetachstate\n");
+				return 0;
+		}
+		ThreadPtr inotifyptr = &MtpStorage::inotify_t;
+		PThreadPtr p = *(PThreadPtr*)&inotifyptr;
+		pthread_create(&thread, &tattr, p, this);
+		if (pthread_attr_destroy(&tattr)) {
+				MTPE("Failed to pthread_attr_destroy\n");
+		}
+		return thread;
+}
+
+int MtpStorage::inotify_t(void) {
+		#define EVENT_SIZE ( sizeof(struct inotify_event) )
+		#define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16) )
+		char buf[EVENT_BUF_LEN];
+		fd_set fdset;
+		struct timeval seltmout;
+		int sel_ret;
+
+		MTPD("inotify thread starting.\n");
+
+		while (inotify_thread_kill.get_value() == 0) {
+				FD_ZERO(&fdset);
+				FD_SET(inotify_fd, &fdset);
+				seltmout.tv_sec = 0;
+				seltmout.tv_usec = 25000;
+				sel_ret = select(inotify_fd + 1, &fdset, NULL, NULL, &seltmout);
+				if (sel_ret == 0)
+						continue;
+				int i = 0;
+				int len = read(inotify_fd, buf, EVENT_BUF_LEN);
+
+				if (len < 0) {
+						if (errno == EINTR)
+								continue;
+						MTPE("inotify_t Can't read inotify events\n");
+				}
+
+				while (i < len && inotify_thread_kill.get_value() == 0) {
+						struct inotify_event *event = (struct inotify_event *) &buf[i];
+						if (event->len) {
+								MTPD("inotify event: wd: %i, mask: %x, name: %s\n", event->wd, event->mask, event->name);
+								lockMutex(1);
+								handleInotifyEvent(event);
+								unlockMutex(1);
+						}
+						i += EVENT_SIZE + event->len;
+				}
+		}
+		MTPD("inotify_thread_kill received!\n");
+		// This cleanup is handled in the destructor.
+		/*for (std::map<int, Tree*>::iterator i = inotifymap.begin(); i != inotifymap.end(); i++) {
+				inotify_rm_watch(inotify_fd, i->first);
+		}
+		close(inotify_fd);*/
+		return 0;
+}
+
+void MtpStorage::handleInotifyEvent(struct inotify_event* event)
+{
+		std::map<int, Tree*>::iterator it = inotifymap.find(event->wd);
+		if (it == inotifymap.end()) {
+				MTPE("Unable to locate inotify_wd: %i\n", event->wd);
+				return;
+		}
+		Tree* tree = it->second;
+		MTPD("inotify_t tree: %x '%s'\n", tree, tree->getName().c_str());
+		Node* node = tree->findEntryByName(basename(event->name));
+		if (node && node->Mtpid() == handleCurrentlySending) {
+				MTPD("ignoring inotify event for currently uploading file, handle: %u\n", node->Mtpid());
+				return;
+		}
+		if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) {
+				if (event->mask & IN_ISDIR) {
+						MTPD("inotify_t create is dir\n");
+				} else {
+						MTPD("inotify_t create is file\n");
+				}
+				if (node == NULL) {
+						node = addNewNode(event->mask & IN_ISDIR, tree, event->name);
+						std::string item = getNodePath(tree) + "/" + event->name;
+						node->addProperties(item, getStorageID());
+						mServer->sendObjectAdded(node->Mtpid());
+				} else {
+						MTPD("inotify_t item already exists.\n");
+				}
+				if (event->mask & IN_ISDIR) {
+						// TODO: do we need to do anything here? probably not until someone reads from the dir...
+				}
+		} else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) {
+				if (event->mask & IN_ISDIR) {
+						MTPD("inotify_t Directory %s deleted\n", event->name);
+				} else {
+						MTPD("inotify_t File %s deleted\n", event->name);
+				}
+				if (node)
+				{
+						if (event->mask & IN_ISDIR) {
+								for (std::map<int, Tree*>::iterator it = inotifymap.begin(); it != inotifymap.end(); ++it) {
+										if (it->second == node) {
+												inotify_rm_watch(inotify_fd, it->first);
+												MTPD("inotify_t removing watch on '%s'\n", getNodePath(it->second).c_str());
+												inotifymap.erase(it->first);
+												break;
+										}
+
+								}
+						}
+						MtpObjectHandle handle = node->Mtpid();
+						deleteFile(handle);
+						mServer->sendObjectRemoved(handle);
+				} else {
+						MTPD("inotify_t already removed.\n");
+				}
+		} else if (event->mask & IN_MODIFY) {
+				MTPD("inotify_t item %s modified.\n", event->name);
+				if (node != NULL) {
+						uint64_t orig_size = node->getProperty(MTP_PROPERTY_OBJECT_SIZE).valueInt;
+						struct stat st;
+						uint64_t new_size = 0;
+						if (lstat(getNodePath(node).c_str(), &st) == 0)
+								new_size = (uint64_t)st.st_size;
+						if (orig_size != new_size) {
+								MTPD("size changed from %llu to %llu on mtpid: %u\n", orig_size, new_size, node->Mtpid());
+								node->updateProperty(MTP_PROPERTY_OBJECT_SIZE, new_size, "", MTP_TYPE_UINT64);
+								mServer->sendObjectUpdated(node->Mtpid());
+						}
+				} else {
+						MTPE("inotify_t modified item not found\n");
+				}
+		} else if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF) {
+				// TODO: is this always already handled by IN_DELETE for the parent dir?
+		}
+}
+
+void MtpStorage::lockMutex(int thread_type) {
+		if (!use_mutex)
+				return; // mutex is disabled
+		if (thread_type) {
+				// inotify thread
+				pthread_mutex_lock(&inMutex);
+				while (pthread_mutex_trylock(&mtpMutex)) {
+						pthread_mutex_unlock(&inMutex);
+						usleep(32000);
+						pthread_mutex_lock(&inMutex);
+				}
+		} else {
+				// main mtp thread
+				pthread_mutex_lock(&mtpMutex);
+				while (pthread_mutex_trylock(&inMutex)) {
+						pthread_mutex_unlock(&mtpMutex);
+						usleep(13000);
+						pthread_mutex_lock(&mtpMutex);
+				}
+		}
+}
+
+void MtpStorage::unlockMutex( __attribute__((unused)) int thread_type) {
+		if (!use_mutex)
+				return; // mutex is disabled
+		pthread_mutex_unlock(&inMutex);
+		pthread_mutex_unlock(&mtpMutex);
+}
+
+int MtpStorage::getObjectPropertyValue(MtpObjectHandle handle, MtpObjectProperty property, MtpStorage::PropEntry& pe) {
+		Node *node;
+		for (iter i = mtpmap.begin(); i != mtpmap.end(); i++) {
+				node = i->second->findNode(handle);
+				if (node != NULL) {
+						const Node::mtpProperty& prop = node->getProperty(property);
+						if (prop.property != property) {
+								MTPD("getObjectPropertyValue: unknown property %x for handle %u\n", property, handle);
+								return -1;
+						}
+						pe.datatype = prop.dataType;
+						pe.intvalue = prop.valueInt;
+						pe.strvalue = prop.valueStr;
+						pe.handle = handle;
+						pe.property = property;
+						return 0;
+				}
+		}
+		// handle not found on this storage
+		return -1;
+}
+
+void MtpStorage::endSendObject(const char* path, MtpObjectHandle handle, __attribute__((unused)) MtpObjectFormat format, __attribute__((unused)) bool succeeded)
+{
+		Node* node = findNode(handle);
+		if (!node)
+				return; // just ignore if this is for another storage
+
+		node->addProperties(path, mStorageID);
+		handleCurrentlySending = 0;
+		// TODO: are we supposed to send an event about an upload by the initiator?
+		if (sendEvents)
+				mServer->sendObjectAdded(node->Mtpid());
+}
+
+int MtpStorage::getObjectPropertyList(MtpObjectHandle handle, uint32_t format, uint32_t property, int groupCode, __attribute__((unused)) int depth, MtpDataPacket& packet) {
+		MTPD("MtpStorage::getObjectPropertyList handle: %u, format: %x, property: %x\n", handle, format, property);
+		if (groupCode != 0)
+		{
+				MTPE("getObjectPropertyList: groupCode unsupported\n");
+				return -1; // TODO: RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED
+		}
+		// TODO: support all the special stuff, like:
+		// handle == 0 -> all objects at the root level
+		// handle == 0xffffffff -> all objects (on all storages? how could we support that?)
+		// format == 0 -> all formats, otherwise filter by ObjectFormatCode
+		// property == 0xffffffff -> all properties except those with group code 0xffffffff
+		// if property == 0 then use groupCode
+		//	 groupCode == 0 -> return Specification_By_Group_Unsupported
+		// depth == 0xffffffff -> all objects incl. and below handle
+
+		std::vector<PropEntry> results;
+
+		if (handle == 0xffffffff) {
+				// TODO: all object on all storages (needs a different design, result packet needs to be built by server instead of storage)
+		} else if (handle == 0) {
+				// all objects at the root level
+				Tree* root = mtpmap[0];
+				MtpObjectHandleList list;
+				root->getmtpids(&list);
+				for (MtpObjectHandleList::iterator it = list.begin(); it != list.end(); ++it) {
+						Node* node = root->findNode(*it);
+						if (!node) {
+								MTPE("BUG: node not found for root entry with handle %u\n", *it);
+								break;
+						}
+						queryNodeProperties(results, node, property, groupCode, mStorageID);
+				}
+		} else {
+				// single object
+				Node* node = findNode(handle);
+				if (!node) {
+						// Item is not on this storage device
+						return -1;
+				}
+				queryNodeProperties(results, node, property, groupCode, mStorageID);
+		}
+
+		MTPD("MtpStorage::getObjectPropertyList::count: %u\n", results.size());
+		packet.putUInt32(results.size());
+
+		for (size_t i = 0; i < results.size(); ++i) {
+				PropEntry& p = results[i];
+				MTPD("handle: %u, propertyCode: %x = %s, datatype: %x, value: %llu\n",
+								p.handle, p.property, MtpDebug::getObjectPropCodeName(p.property),
+								p.datatype, p.intvalue);
+				packet.putUInt32(p.handle);
+				packet.putUInt16(p.property);
+				packet.putUInt16(p.datatype);
+				switch (p.datatype) {
+						case MTP_TYPE_INT8:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT8\n");
+								packet.putInt8(p.intvalue);
+								break;
+						case MTP_TYPE_UINT8:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT8\n");
+								packet.putUInt8(p.intvalue);
+								break;
+						case MTP_TYPE_INT16:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT16\n");
+								packet.putInt16(p.intvalue);
+								break;
+						case MTP_TYPE_UINT16:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT16\n");
+								packet.putUInt16(p.intvalue);
+								break;
+						case MTP_TYPE_INT32:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT32\n");
+								packet.putInt32(p.intvalue);
+								break;
+						case MTP_TYPE_UINT32:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT32\n");
+								packet.putUInt32(p.intvalue);
+								break;
+						case MTP_TYPE_INT64:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT64\n");
+								packet.putInt64(p.intvalue);
+								break;
+						case MTP_TYPE_UINT64:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT64\n");
+								packet.putUInt64(p.intvalue);
+								break;
+						case MTP_TYPE_INT128:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT128\n");
+								packet.putInt128(p.intvalue);
+								break;
+						case MTP_TYPE_UINT128:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT128\n");
+								packet.putUInt128(p.intvalue);
+								break;
+						case MTP_TYPE_STR:
+								MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_STR: %s\n", p.strvalue.c_str());
+								packet.putString(p.strvalue.c_str());
+								break;
+						default:
+								MTPE("bad or unsupported data type: %x in MyMtpDatabase::getObjectPropertyList", p.datatype);
+								break;
+				}
+		}
+		return 0;
+}
+
+int MtpStorage::getObjectInfo(MtpObjectHandle handle, MtpObjectInfo& info) {
+		struct stat st;
+		uint64_t size = 0;
+		MTPD("MtpStorage::getObjectInfo, handle: %u\n", handle);
+		Node* node = findNode(handle);
+		if (!node) {
+				// Item is not on this storage device
+				return -1;
+		}
+
+		info.mStorageID = getStorageID();
+		MTPD("info.mStorageID: %u\n", info.mStorageID);
+		info.mParent = node->getMtpParentId();
+		MTPD("mParent: %u\n", info.mParent);
+		// TODO: do we want to lstat again here, or read from the node properties?
+		if (lstat(getNodePath(node).c_str(), &st) == 0)
+				size = st.st_size;
+		MTPD("size is: %llu\n", size);
+		info.mCompressedSize = (size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size);
+		info.mDateModified = st.st_mtime;
+		if (S_ISDIR(st.st_mode)) {
+				info.mFormat = MTP_FORMAT_ASSOCIATION;
+		}
+		else {
+				info.mFormat = MTP_FORMAT_UNDEFINED;
+		}
+		info.mName = strdup(node->getName().c_str());
+		MTPD("MtpStorage::getObjectInfo found, Exiting getObjectInfo()\n");
+		return 0;
+}
+
+int MtpStorage::getObjectFilePath(MtpObjectHandle handle, MtpStringBuffer& outFilePath, int64_t& outFileLength, MtpObjectFormat& outFormat) {
+		MTPD("MtpStorage::getObjectFilePath handle: %u\n", handle);
+		Node* node = findNode(handle);
+		if (!node)
+		{
+				// Item is not on this storage device
+				return -1;
+		}
+		// TODO: do we want to lstat here, or just read the info from the node?
+		struct stat st;
+		if (lstat(getNodePath(node).c_str(), &st) == 0)
+				outFileLength = st.st_size;
+		else
+				outFileLength = 0;
+		outFilePath.set(getNodePath(node).c_str());
+		MTPD("outFilePath: %s\n", (const char*) outFilePath);
+		outFormat = node->isDir() ? MTP_FORMAT_ASSOCIATION : MTP_FORMAT_UNDEFINED;
+		return 0;
+}
+
+int MtpStorage::deleteFile(MtpObjectHandle handle) {
+		MTPD("MtpStorage::deleteFile handle: %u\n", handle);
+		Node* node = findNode(handle);
+		if (!node) {
+				// Item is not on this storage device
+				return -1;
+		}
+		MtpObjectHandle parent = node->getMtpParentId();
+		Tree* tree = mtpmap[parent];
+		if (!tree) {
+				MTPE("parent tree for handle %u not found\n", parent);
+				return -1;
+		}
+		if (node->isDir()) {
+				MTPD("deleting tree from mtpmap: %u\n", handle);
+				mtpmap.erase(handle);
+		}
+
+		MTPD("deleting handle: %u\n", handle);
+		tree->deleteNode(handle);
+		MTPD("deleted\n");
+		return 0;
+}
+
+void MtpStorage::queryNodeProperties(std::vector<MtpStorage::PropEntry>& results, Node* node, uint32_t property, __attribute__((unused)) int groupCode, MtpStorageID storageID)
+{
+		MTPD("queryNodeProperties handle %u, path: %s\n", node->Mtpid(), getNodePath(node).c_str());
+		PropEntry pe;
+		pe.handle = node->Mtpid();
+		pe.property = property;
+
+		if (property == 0xffffffff)
+		{
+				// add all properties
+				MTPD("MtpStorage::queryNodeProperties for all properties\n");
+				std::vector<Node::mtpProperty> mtpprop = node->getMtpProps();
+				for (size_t i = 0; i < mtpprop.size(); ++i) {
+						pe.property = mtpprop[i].property;
+						pe.datatype = mtpprop[i].dataType;
+						pe.intvalue = mtpprop[i].valueInt;
+						pe.strvalue = mtpprop[i].valueStr;
+						results.push_back(pe);
+				}
+				return;
+		}
+		else if (property == 0)
+		{
+				// TODO: use groupCode
+		}
+
+		// single property
+		// TODO: this should probably be moved to the Node class and/or merged with getObjectPropertyValue
+		switch (property) {
+//				case MTP_PROPERTY_OBJECT_FORMAT:
+//						pe.datatype = MTP_TYPE_UINT16;
+//						pe.intvalue = node->getIntProperty(MTP_PROPERTY_OBJECT_FORMAT);
+//						break;
+
+				case MTP_PROPERTY_STORAGE_ID:
+						pe.datatype = MTP_TYPE_UINT32;
+						pe.intvalue = storageID;
+						break;
+
+				case MTP_PROPERTY_PROTECTION_STATUS:
+						pe.datatype = MTP_TYPE_UINT16;
+						pe.intvalue = 0;
+						break;
+
+				case MTP_PROPERTY_OBJECT_SIZE:
+				{
+						pe.datatype = MTP_TYPE_UINT64;
+						struct stat st;
+						pe.intvalue = 0;
+						if (lstat(getNodePath(node).c_str(), &st) == 0)
+								pe.intvalue = st.st_size;
+						break;
+				}
+
+				default:
+				{
+						const Node::mtpProperty& prop = node->getProperty(property);
+						if (prop.property != property)
+						{
+								MTPD("queryNodeProperties: unknown property %x\n", property);
+								return;
+						}
+						pe.datatype = prop.dataType;
+						pe.intvalue = prop.valueInt;
+						pe.strvalue = prop.valueStr;
+						// TODO: all the special case stuff in MyMtpDatabase::getObjectPropertyValue is missing here
+				}
+
+		}
+		results.push_back(pe);
+}
+
+
diff --git a/mtp/ffs/MtpStorage.h b/mtp/ffs/MtpStorage.h
new file mode 100755
index 0000000..8d12b07
--- /dev/null
+++ b/mtp/ffs/MtpStorage.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_STORAGE_H
+#define _MTP_STORAGE_H
+
+#include "MtpObjectInfo.h"
+#include "MtpServer.h"
+#include "MtpStringBuffer.h"
+#include "MtpTypes.h"
+#include "mtp.h"
+#include "btree.hpp"
+#include "tw_atomic.hpp"
+
+class MtpDatabase;
+
+class MtpStorage {
+
+public:
+	struct PropEntry {
+		   MtpObjectHandle handle;
+		   uint16_t property;
+		   uint16_t datatype;
+		   uint64_t intvalue;
+		   std::string strvalue;
+	};
+
+private:
+	MtpStorageID			mStorageID;
+	MtpStringBuffer			mFilePath;
+	MtpStringBuffer			mDescription;
+	uint64_t				mMaxCapacity;
+	uint64_t				mMaxFileSize;
+	bool					mRemovable;
+	typedef					std::map<int, Tree*> maptree;
+	typedef					maptree::iterator iter;
+	maptree					mtpmap;
+	std::string				mtpstorageparent;
+	MtpObjectHandle			handleCurrentlySending;
+	int						inotify_fd;
+	std::map<int, Tree*>	inotifymap;		   // inotify wd -> tree
+	bool					sendEvents;
+	MtpServer*				mServer;
+	typedef					int (MtpStorage::*ThreadPtr)(void);
+	typedef					void* (*PThreadPtr)(void *);
+	bool					use_mutex;
+	pthread_mutex_t			inMutex; // inotify mutex
+	pthread_mutex_t			mtpMutex; // main mtp mutex
+	TWAtomicInt				inotify_thread_kill;
+	pthread_t				inotify_thread;
+	Node*					findNode(MtpObjectHandle handle);
+	std::string				getNodePath(Node* node);
+	Node*					addNewNode(bool isDir, Tree* tree, const std::string& name);
+	void					queryNodeProperties(std::vector<PropEntry>& results, Node* node, uint32_t property, int groupCode, MtpStorageID storageID);
+	int						addInotify(Tree* tree);
+	void					handleInotifyEvent(struct inotify_event* event);
+
+public:
+	MtpStorage(MtpStorageID id, const char* filePath,
+								const char* description,
+								bool removable, uint64_t maxFileSize, MtpServer* refserver);
+	virtual					~MtpStorage();
+	inline MtpStorageID		getStorageID() const { return mStorageID; }
+	int						getType() const;
+	int						getFileSystemType() const;
+	int						getAccessCapability() const;
+	uint64_t				getMaxCapacity();
+	uint64_t				getFreeSpace();
+	const char*				getDescription() const;
+	inline const char*		getPath() const { return (const char *)mFilePath; }
+	inline bool				isRemovable() const { return mRemovable; }
+	inline uint64_t			getMaxFileSize() const { return mMaxFileSize; }
+	int						renameObject(MtpObjectHandle handle, std::string newName);
+	MtpObjectHandle			beginSendObject(const char* path, MtpObjectFormat format, MtpObjectHandle parent, uint64_t size, time_t modified);
+	MtpObjectHandleList*	getObjectList(MtpStorageID storageID, MtpObjectHandle parent);
+	int						getObjectPropertyList(MtpObjectHandle handle, uint32_t format, uint32_t property, int groupCode, int depth, MtpDataPacket& packet);
+	int						readDir(const std::string& path, Tree* tree);
+	int						getObjectPropertyValue(MtpObjectHandle handle, MtpObjectProperty property, PropEntry& prop);
+	int						getObjectInfo(MtpObjectHandle handle, MtpObjectInfo& info);
+	void					endSendObject(const char* path, MtpObjectHandle handle, MtpObjectFormat format, bool succeeded);
+	int						getObjectFilePath(MtpObjectHandle handle, MtpStringBuffer& outFilePath, int64_t& outFileLength, MtpObjectFormat& outFormat);
+	int						deleteFile(MtpObjectHandle handle);
+	int						createDB();
+	pthread_t				inotify();
+	int						inotify_t();
+	void					lockMutex(int thread_type);
+	void					unlockMutex(int thread_type);
+};
+
+#endif // _MTP_STORAGE_H
diff --git a/mtp/ffs/MtpStorageInfo.cpp b/mtp/ffs/MtpStorageInfo.cpp
new file mode 100644
index 0000000..21a8322
--- /dev/null
+++ b/mtp/ffs/MtpStorageInfo.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpStorageInfo"
+
+#include <inttypes.h>
+
+#include "MtpDebug.h"
+#include "MtpDataPacket.h"
+#include "MtpStorageInfo.h"
+#include "MtpStringBuffer.h"
+
+MtpStorageInfo::MtpStorageInfo(MtpStorageID id)
+	:	mStorageID(id),
+		mStorageType(0),
+		mFileSystemType(0),
+		mAccessCapability(0),
+		mMaxCapacity(0),
+		mFreeSpaceBytes(0),
+		mFreeSpaceObjects(0),
+		mStorageDescription(NULL),
+		mVolumeIdentifier(NULL)
+{
+}
+
+MtpStorageInfo::~MtpStorageInfo() {
+	if (mStorageDescription)
+		free(mStorageDescription);
+	if (mVolumeIdentifier)
+		free(mVolumeIdentifier);
+}
+
+bool MtpStorageInfo::read(MtpDataPacket& packet) {
+	MtpStringBuffer string;
+
+	// read the device info
+	if (!packet.getUInt16(mStorageType)) return false;
+	if (!packet.getUInt16(mFileSystemType)) return false;
+	if (!packet.getUInt16(mAccessCapability)) return false;
+	if (!packet.getUInt64(mMaxCapacity)) return false;
+	if (!packet.getUInt64(mFreeSpaceBytes)) return false;
+	if (!packet.getUInt32(mFreeSpaceObjects)) return false;
+
+	if (!packet.getString(string)) return false;
+	mStorageDescription = strdup((const char *)string);
+	if (!mStorageDescription) return false;
+	if (!packet.getString(string)) return false;
+	mVolumeIdentifier = strdup((const char *)string);
+	if (!mVolumeIdentifier) return false;
+
+	return true;
+}
+
+void MtpStorageInfo::print() {
+	MTPD("Storage Info %08X:\n\tmStorageType: %d\n\tmFileSystemType: %d\n\tmAccessCapability: %d\n",
+			mStorageID, mStorageType, mFileSystemType, mAccessCapability);
+	MTPD("\tmMaxCapacity: %" PRIu64 "\n\tmFreeSpaceBytes: %" PRIu64 "\n\tmFreeSpaceObjects: %d\n",
+			mMaxCapacity, mFreeSpaceBytes, mFreeSpaceObjects);
+	MTPD("\tmStorageDescription: %s\n\tmVolumeIdentifier: %s\n",
+			mStorageDescription, mVolumeIdentifier);
+}
diff --git a/mtp/ffs/MtpStorageInfo.h b/mtp/ffs/MtpStorageInfo.h
new file mode 100644
index 0000000..08e0571
--- /dev/null
+++ b/mtp/ffs/MtpStorageInfo.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_STORAGE_INFO_H
+#define _MTP_STORAGE_INFO_H
+
+#include "MtpTypes.h"
+
+class MtpDataPacket;
+
+class MtpStorageInfo {
+public:
+	MtpStorageID		mStorageID;
+	uint16_t			mStorageType;
+	uint16_t			mFileSystemType;
+	uint16_t			mAccessCapability;
+	uint64_t			mMaxCapacity;
+	uint64_t			mFreeSpaceBytes;
+	uint32_t			mFreeSpaceObjects;
+	char*				mStorageDescription;
+	char*				mVolumeIdentifier;
+
+public:
+	explicit			MtpStorageInfo(MtpStorageID id);
+	virtual				~MtpStorageInfo();
+
+	bool				read(MtpDataPacket& packet);
+
+	void				print();
+};
+
+#endif // _MTP_STORAGE_INFO_H
diff --git a/mtp/ffs/MtpStringBuffer.cpp b/mtp/ffs/MtpStringBuffer.cpp
new file mode 100644
index 0000000..e2302df
--- /dev/null
+++ b/mtp/ffs/MtpStringBuffer.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpStringBuffer"
+
+#include <codecvt>
+#include <locale>
+#include <string>
+#include <vector>
+
+#include "MtpDataPacket.h"
+#include "MtpStringBuffer.h"
+
+namespace {
+
+std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> gConvert;
+
+static std::string utf16ToUtf8(std::u16string input_str) {
+	return gConvert.to_bytes(input_str);
+}
+
+static std::u16string utf8ToUtf16(std::string input_str) {
+	return gConvert.from_bytes(input_str);
+}
+
+} // namespace
+
+MtpStringBuffer::MtpStringBuffer(const char* src)
+{
+	set(src);
+}
+
+MtpStringBuffer::MtpStringBuffer(const uint16_t* src)
+{
+	set(src);
+}
+
+MtpStringBuffer::MtpStringBuffer(const MtpStringBuffer& src)
+{
+	mString = src.mString;
+}
+
+void MtpStringBuffer::set(const char* src) {
+	mString = std::string(src);
+}
+
+void MtpStringBuffer::set(const uint16_t* src) {
+	mString = utf16ToUtf8(std::u16string((const char16_t*)src));
+}
+
+bool MtpStringBuffer::readFromPacket(MtpDataPacket* packet) {
+	uint8_t count;
+	if (!packet->getUInt8(count))
+		return false;
+	if (count == 0)
+		return true;
+
+	std::vector<char16_t> buffer(count);
+	for (int i = 0; i < count; i++) {
+		uint16_t ch;
+		if (!packet->getUInt16(ch))
+			return false;
+		buffer[i] = ch;
+	}
+	if (buffer[count-1] != '\0') {
+		MTPE("Mtp string not null terminated\n");
+		return false;
+	}
+	mString = utf16ToUtf8(std::u16string(buffer.data()));
+	return true;
+}
+
+void MtpStringBuffer::writeToPacket(MtpDataPacket* packet) const {
+	std::u16string src16 = utf8ToUtf16(mString);
+	int count = src16.length();
+
+	if (count == 0) {
+		packet->putUInt8(0);
+		return;
+	}
+	packet->putUInt8(std::min(count + 1, MTP_STRING_MAX_CHARACTER_NUMBER));
+
+	int i = 0;
+	for (char16_t &c : src16) {
+		if (i == MTP_STRING_MAX_CHARACTER_NUMBER - 1) {
+			// Leave a slot for null termination.
+			MTPD("Mtp truncating long string\n");
+			break;
+		}
+		packet->putUInt16(c);
+		i++;
+	}
+	// only terminate with zero if string is not empty
+	packet->putUInt16(0);
+}
diff --git a/mtp/ffs/MtpStringBuffer.h b/mtp/ffs/MtpStringBuffer.h
new file mode 100644
index 0000000..0006cdb
--- /dev/null
+++ b/mtp/ffs/MtpStringBuffer.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_STRING_BUFFER_H
+#define _MTP_STRING_BUFFER_H
+
+#include <log/log.h>
+#include <stdint.h>
+#include <string>
+
+// Max Character number of a MTP String
+#define MTP_STRING_MAX_CHARACTER_NUMBER				255
+
+class MtpDataPacket;
+
+// Represents a utf8 string, with a maximum of 255 characters
+class MtpStringBuffer {
+
+private:
+	std::string		mString;
+
+public:
+					MtpStringBuffer() {};
+					~MtpStringBuffer() {};
+
+	explicit		MtpStringBuffer(const char* src);
+	explicit		MtpStringBuffer(const uint16_t* src);
+					MtpStringBuffer(const MtpStringBuffer& src);
+
+	void			set(const char* src);
+	void			set(const uint16_t* src);
+
+	inline void		append(const char* other);
+	inline void		append(MtpStringBuffer &other);
+
+	bool			readFromPacket(MtpDataPacket* packet);
+	void			writeToPacket(MtpDataPacket* packet) const;
+
+	inline bool		isEmpty() const { return mString.empty(); }
+	inline int		size() const { return mString.length(); }
+
+	inline operator const char*() const { return mString.c_str(); }
+};
+
+inline void MtpStringBuffer::append(const char* other) {
+	mString += other;
+}
+
+inline void MtpStringBuffer::append(MtpStringBuffer &other) {
+	mString += other.mString;
+}
+
+#endif // _MTP_STRING_BUFFER_H
diff --git a/mtp/ffs/MtpTypes.h b/mtp/ffs/MtpTypes.h
new file mode 100644
index 0000000..9c37b8c
--- /dev/null
+++ b/mtp/ffs/MtpTypes.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_TYPES_H
+#define _MTP_TYPES_H
+
+#include <stdint.h>
+#include <vector>
+
+typedef int32_t int128_t[4];
+typedef uint32_t uint128_t[4];
+
+typedef uint16_t MtpOperationCode;
+typedef uint16_t MtpResponseCode;
+typedef uint16_t MtpEventCode;
+typedef uint32_t MtpSessionID;
+typedef uint32_t MtpStorageID;
+typedef uint32_t MtpTransactionID;
+typedef uint16_t MtpPropertyCode;
+typedef uint16_t MtpDataType;
+typedef uint16_t MtpObjectFormat;
+typedef MtpPropertyCode MtpDeviceProperty;
+typedef MtpPropertyCode MtpObjectProperty;
+
+// object handles are unique across all storage but only within a single session.
+// object handles cannot be reused after an object is deleted.
+// values 0x00000000 and 0xFFFFFFFF are reserved for special purposes.
+typedef uint32_t MtpObjectHandle;
+
+// Special values
+#define MTP_PARENT_ROOT			0xFFFFFFFF		 // parent is root of the storage
+#define kInvalidObjectHandle	0xFFFFFFFF
+
+class MtpStorage;
+class MtpDevice;
+class MtpProperty;
+
+typedef std::vector<MtpStorage *> MtpStorageList;
+typedef std::vector<MtpDevice*> MtpDeviceList;
+typedef std::vector<MtpProperty*> MtpPropertyList;
+
+typedef std::vector<uint8_t> UInt8List;
+typedef std::vector<uint16_t> UInt16List;
+typedef std::vector<uint32_t> UInt32List;
+typedef std::vector<uint64_t> UInt64List;
+typedef std::vector<int8_t> Int8List;
+typedef std::vector<int16_t> Int16List;
+typedef std::vector<int32_t> Int32List;
+typedef std::vector<int64_t> Int64List;
+
+typedef UInt16List MtpObjectPropertyList;
+typedef UInt16List MtpDevicePropertyList;
+typedef UInt16List MtpObjectFormatList;
+typedef UInt32List MtpObjectHandleList;
+typedef UInt16List MtpObjectPropertyList;
+typedef UInt32List MtpStorageIDList;
+
+enum UrbPacketDivisionMode {
+	// First packet only contains a header.
+	FIRST_PACKET_ONLY_HEADER,
+	// First packet contains payload much as possible.
+	FIRST_PACKET_HAS_PAYLOAD
+};
+
+#endif // _MTP_TYPES_H
diff --git a/mtp/ffs/MtpUtils.cpp b/mtp/ffs/MtpUtils.cpp
new file mode 100644
index 0000000..80c01bf
--- /dev/null
+++ b/mtp/ffs/MtpUtils.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MtpUtils"
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <string>
+#include <sys/sendfile.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "MtpUtils.h"
+
+using namespace std;
+
+constexpr unsigned long FILE_COPY_SIZE = 262144;
+
+static void access_ok(const char *path) {
+	if (access(path, F_OK) == -1) {
+		// Ignore. Failure could be common in cases of delete where
+		// the metadata was updated through other paths.
+	}
+}
+
+/*
+DateTime strings follow a compatible subset of the definition found in ISO 8601, and
+take the form of a Unicode string formatted as: "YYYYMMDDThhmmss.s". In this
+representation, YYYY shall be replaced by the year, MM replaced by the month (01-12),
+DD replaced by the day (01-31), T is a constant character 'T' delimiting time from date,
+hh is replaced by the hour (00-23), mm is replaced by the minute (00-59), and ss by the
+second (00-59). The ".s" is optional, and represents tenths of a second.
+This is followed by a UTC offset given as "[+-]zzzz" or the literal "Z", meaning UTC.
+*/
+
+bool parseDateTime(const char* dateTime, time_t& outSeconds) {
+	int year, month, day, hour, minute, second;
+	if (sscanf(dateTime, "%04d%02d%02dT%02d%02d%02d",
+			   &year, &month, &day, &hour, &minute, &second) != 6)
+		return false;
+
+	// skip optional tenth of second
+	const char* tail = dateTime + 15;
+	if (tail[0] == '.' && tail[1]) tail += 2;
+
+	// FIXME: "Z" means UTC, but non-"Z" doesn't mean local time.
+	// It might be that you're in Asia/Seoul on vacation and your Android
+	// device has noticed this via the network, but your camera was set to
+	// America/Los_Angeles once when you bought it and doesn't know where
+	// it is right now, so the camera says "20160106T081700-0800" but we
+	// just ignore the "-0800" and assume local time which is actually "+0900".
+	// I think to support this (without switching to Java or using icu4c)
+	// you'd want to always use timegm(3) and then manually add/subtract
+	// the UTC offset parsed from the string (taking care of wrapping).
+	// mktime(3) ignores the tm_gmtoff field, so you can't let it do the work.
+	bool useUTC = (tail[0] == 'Z');
+
+	struct tm tm = {};
+	tm.tm_sec = second;
+	tm.tm_min = minute;
+	tm.tm_hour = hour;
+	tm.tm_mday = day;
+	tm.tm_mon = month - 1;	// mktime uses months in 0 - 11 range
+	tm.tm_year = year - 1900;
+	tm.tm_isdst = -1;
+	outSeconds = useUTC ? timegm(&tm) : mktime(&tm);
+
+	return true;
+}
+
+void formatDateTime(time_t seconds, char* buffer, int bufferLength) {
+	struct tm tm;
+
+	localtime_r(&seconds, &tm);
+	snprintf(buffer, bufferLength, "%04d%02d%02dT%02d%02d%02d",
+		tm.tm_year + 1900,
+		tm.tm_mon + 1, // localtime_r uses months in 0 - 11 range
+		tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+}
+
+int makeFolder(const char *path) {
+	mode_t mask = umask(0);
+	int ret = mkdir((const char *)path, DIR_PERM);
+	umask(mask);
+	if (ret && ret != -EEXIST) {
+		PLOG(ERROR) << "Failed to create folder " << path;
+		ret = -1;
+	} else {
+		chown((const char *)path, getuid(), FILE_GROUP);
+	}
+	access_ok(path);
+	return ret;
+}
+
+/**
+ * Copies target path and all children to destination path.
+ *
+ * Returns 0 on success or a negative value indicating number of failures
+ */
+int copyRecursive(const char *fromPath, const char *toPath) {
+	int ret = 0;
+	string fromPathStr(fromPath);
+	string toPathStr(toPath);
+
+	DIR* dir = opendir(fromPath);
+	if (!dir) {
+		PLOG(ERROR) << "opendir " << fromPath << " failed";
+		return -1;
+	}
+	if (fromPathStr[fromPathStr.size()-1] != '/')
+		fromPathStr += '/';
+	if (toPathStr[toPathStr.size()-1] != '/')
+		toPathStr += '/';
+
+	struct dirent* entry;
+	while ((entry = readdir(dir))) {
+		const char* name = entry->d_name;
+
+		// ignore "." and ".."
+		if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
+			continue;
+		}
+		string oldFile = fromPathStr + name;
+		string newFile = toPathStr + name;
+
+		if (entry->d_type == DT_DIR) {
+			ret += makeFolder(newFile.c_str());
+			ret += copyRecursive(oldFile.c_str(), newFile.c_str());
+		} else {
+			ret += copyFile(oldFile.c_str(), newFile.c_str());
+		}
+	}
+	return ret;
+}
+
+int copyFile(const char *fromPath, const char *toPath) {
+	auto start = std::chrono::steady_clock::now();
+
+	android::base::unique_fd fromFd(open(fromPath, O_RDONLY));
+	if (fromFd == -1) {
+		PLOG(ERROR) << "Failed to open copy from " << fromPath;
+		return -1;
+	}
+	android::base::unique_fd toFd(open(toPath, O_CREAT | O_WRONLY, FILE_PERM));
+	if (toFd == -1) {
+		PLOG(ERROR) << "Failed to open copy to " << toPath;
+		return -1;
+	}
+	off_t offset = 0;
+
+	struct stat sstat = {};
+	if (stat(fromPath, &sstat) == -1)
+		return -1;
+
+	off_t length = sstat.st_size;
+	int ret = 0;
+
+	while (offset < length) {
+		ssize_t transfer_length = std::min(length - offset, (off_t) FILE_COPY_SIZE);
+		ret = sendfile(toFd, fromFd, &offset, transfer_length);
+		if (ret != transfer_length) {
+			ret = -1;
+			PLOG(ERROR) << "Copying failed!";
+			break;
+		}
+	}
+	auto end = std::chrono::steady_clock::now();
+	std::chrono::duration<double> diff = end - start;
+	LOG(DEBUG) << "Copied a file with MTP. Time: " << diff.count() << " s, Size: " << length <<
+		", Rate: " << ((double) length) / diff.count() << " bytes/s";
+	chown(toPath, getuid(), FILE_GROUP);
+	access_ok(toPath);
+	return ret == -1 ? -1 : 0;
+}
+
+void deleteRecursive(const char* path) {
+	string pathStr(path);
+	if (pathStr[pathStr.size()-1] != '/') {
+		pathStr += '/';
+	}
+
+	DIR* dir = opendir(path);
+	if (!dir) {
+		PLOG(ERROR) << "opendir " << path << " failed";
+		return;
+	}
+
+	struct dirent* entry;
+	while ((entry = readdir(dir))) {
+		const char* name = entry->d_name;
+
+		// ignore "." and ".."
+		if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
+			continue;
+		}
+		string childPath = pathStr + name;
+		int success;
+		if (entry->d_type == DT_DIR) {
+			deleteRecursive(childPath.c_str());
+			success = rmdir(childPath.c_str());
+		} else {
+			success = unlink(childPath.c_str());
+		}
+		access_ok(childPath.c_str());
+		if (success == -1)
+			PLOG(ERROR) << "Deleting path " << childPath << " failed";
+	}
+	closedir(dir);
+}
+
+bool deletePath(const char* path) {
+	struct stat statbuf;
+	int success;
+	if (stat(path, &statbuf) == 0) {
+		if (S_ISDIR(statbuf.st_mode)) {
+			// rmdir will fail if the directory is non empty, so
+			// there is no need to keep errors from deleteRecursive
+			deleteRecursive(path);
+			success = rmdir(path);
+		} else {
+			success = unlink(path);
+		}
+	} else {
+		PLOG(ERROR) << "deletePath stat failed for " << path;
+		return false;
+	}
+	if (success == -1)
+		PLOG(ERROR) << "Deleting path " << path << " failed";
+	access_ok(path);
+	return success == 0;
+}
+
+int renameTo(const char *oldPath, const char *newPath) {
+	int ret = rename(oldPath, newPath);
+	access_ok(oldPath);
+	access_ok(newPath);
+	return ret;
+}
+
+// Calls access(2) on the path to update underlying filesystems,
+// then closes the fd.
+void closeObjFd(int fd, const char *path) {
+	close(fd);
+	access_ok(path);
+}
diff --git a/mtp/ffs/MtpUtils.h b/mtp/ffs/MtpUtils.h
new file mode 100644
index 0000000..4eae95e
--- /dev/null
+++ b/mtp/ffs/MtpUtils.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_UTILS_H
+#define _MTP_UTILS_H
+
+#include "private/android_filesystem_config.h"
+
+#include <stdint.h>
+
+constexpr int FILE_GROUP = AID_MEDIA_RW;
+constexpr int FILE_PERM = 0664;
+constexpr int DIR_PERM = 0775;
+
+bool parseDateTime(const char* dateTime, time_t& outSeconds);
+void formatDateTime(time_t seconds, char* buffer, int bufferLength);
+
+int makeFolder(const char *path);
+int copyRecursive(const char *fromPath, const char *toPath);
+int copyFile(const char *fromPath, const char *toPath);
+bool deletePath(const char* path);
+int renameTo(const char *oldPath, const char *newPath);
+
+void closeObjFd(int fd, const char *path);
+#endif // _MTP_UTILS_H
diff --git a/mtp/ffs/NOTICE b/mtp/ffs/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/mtp/ffs/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2008, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/mtp/ffs/PosixAsyncIO.cpp b/mtp/ffs/PosixAsyncIO.cpp
new file mode 100644
index 0000000..435000a
--- /dev/null
+++ b/mtp/ffs/PosixAsyncIO.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <queue>
+#include <unistd.h>
+
+#include "PosixAsyncIO.h"
+
+namespace {
+
+void read_func(struct aiocb *aiocbp) {
+	aiocbp->ret = TEMP_FAILURE_RETRY(pread(aiocbp->aio_fildes,
+				aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset));
+	if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+void write_func(struct aiocb *aiocbp) {
+	aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes,
+				aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset));
+	if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+} // end anonymous namespace
+
+aiocb::~aiocb() {
+	CHECK(!thread.joinable());
+}
+
+int aio_read(struct aiocb *aiocbp) {
+	aiocbp->thread = std::thread(read_func, aiocbp);
+	return 0;
+}
+
+int aio_write(struct aiocb *aiocbp) {
+	aiocbp->thread = std::thread(write_func, aiocbp);
+	return 0;
+}
+
+int aio_error(const struct aiocb *aiocbp) {
+	return aiocbp->error;
+}
+
+ssize_t aio_return(struct aiocb *aiocbp) {
+	return aiocbp->ret;
+}
+
+int aio_suspend(struct aiocb *aiocbp[], int n,
+		const struct timespec *) {
+	for (int i = 0; i < n; i++) {
+		aiocbp[i]->thread.join();
+	}
+	return 0;
+}
+
+void aio_prepare(struct aiocb *aiocbp, void* buf, size_t count, off_t offset) {
+	aiocbp->aio_buf = buf;
+	aiocbp->aio_offset = offset;
+	aiocbp->aio_nbytes = count;
+}
diff --git a/mtp/ffs/PosixAsyncIO.h b/mtp/ffs/PosixAsyncIO.h
new file mode 100644
index 0000000..69ab9a5
--- /dev/null
+++ b/mtp/ffs/PosixAsyncIO.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _POSIXASYNCIO_H
+#define _POSIXASYNCIO_H
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <time.h>
+#include <thread>
+#include <unistd.h>
+
+/**
+ * Provides a subset of POSIX aio operations.
+ */
+
+struct aiocb {
+	int aio_fildes;
+	void *aio_buf;
+
+	off_t aio_offset;
+	size_t aio_nbytes;
+
+	// Used internally
+	std::thread thread;
+	ssize_t ret;
+	int error;
+
+	~aiocb();
+};
+
+// Submit a request for IO to be completed
+int aio_read(struct aiocb *);
+int aio_write(struct aiocb *);
+
+// Suspend current thread until given IO is complete, at which point
+// its return value and any errors can be accessed
+// All submitted requests must have a corresponding suspend.
+// aiocb->aio_buf must refer to valid memory until after the suspend call
+int aio_suspend(struct aiocb *[], int, const struct timespec *);
+int aio_error(const struct aiocb *);
+ssize_t aio_return(struct aiocb *);
+
+// Helper method for setting aiocb members
+void aio_prepare(struct aiocb *, void*, size_t, off_t);
+
+#endif // POSIXASYNCIO_H
+
diff --git a/mtp/ffs/btree.cpp b/mtp/ffs/btree.cpp
new file mode 100644
index 0000000..78b39a7
--- /dev/null
+++ b/mtp/ffs/btree.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <utils/threads.h>
+#include "btree.hpp"
+#include "MtpDebug.h"
+
+Tree::Tree(MtpObjectHandle handle, MtpObjectHandle parent, const std::string& name)
+	: Node(handle, parent, name), alreadyRead(false) {
+}
+
+Tree::~Tree() {
+	for (std::map<MtpObjectHandle, Node*>::iterator it = entries.begin(); it != entries.end(); ++it)
+		delete it->second;
+	entries.clear();
+}
+
+int Tree::getCount(void) {
+	int count = entries.size();
+	MTPD("Tree::getCount::node count: %d\n", count);
+	return count;
+}
+
+void Tree::addEntry(Node* node) {
+	if (node->Mtpid() == 0) {
+		MTPE("Tree::addEntry: not adding node with 0 handle.\n");
+		return;
+	}
+	if (node->Mtpid() == node->getMtpParentId()) {
+		MTPE("Tree::addEntry: not adding node with handle %u == parent.\n", node->Mtpid());
+		return;
+	}
+	entries[node->Mtpid()] = node;
+}
+
+Node* Tree::findEntryByName(std::string name) {
+	for (std::map<MtpObjectHandle, Node*>::iterator it = entries.begin(); it != entries.end(); ++it)
+	{
+		Node* node = it->second;
+		if (node->getName().compare(name) == 0 && node->Mtpid() > 0)
+			return node;
+	}
+	return NULL;
+}
+
+Node* Tree::findNode(MtpObjectHandle handle) {
+	std::map<MtpObjectHandle, Node*>::iterator it = entries.find(handle);
+	if (it != entries.end())
+		return it->second;
+	return NULL;
+}
+
+void Tree::getmtpids(MtpObjectHandleList* mtpids) {
+	for (std::map<MtpObjectHandle, Node*>::iterator it = entries.begin(); it != entries.end(); ++it)
+		mtpids->push_back(it->second->Mtpid());
+}
+
+void Tree::deleteNode(MtpObjectHandle handle) {
+	std::map<MtpObjectHandle, Node*>::iterator it = entries.find(handle);
+	if (it != entries.end()) {
+		delete it->second;
+		entries.erase(it);
+	}
+}
diff --git a/mtp/ffs/btree.hpp b/mtp/ffs/btree.hpp
new file mode 100644
index 0000000..e1aad36
--- /dev/null
+++ b/mtp/ffs/btree.hpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BTREE_HPP
+#define BTREE_HPP
+
+#include <vector>
+#include <string>
+#include <map>
+#include "MtpTypes.h"
+
+// A directory entry
+class Node {
+	MtpObjectHandle handle;
+	MtpObjectHandle parent;
+	std::string name;	// name only without path
+
+public:
+	Node();
+	Node(MtpObjectHandle handle, MtpObjectHandle parent, const std::string& name);
+	virtual ~Node() {}
+
+	virtual bool isDir() const { return false; }
+
+	void rename(const std::string& newName);
+	MtpObjectHandle Mtpid() const;
+	MtpObjectHandle getMtpParentId() const;
+	const std::string& getName() const;
+
+	void addProperty(MtpPropertyCode property, uint64_t valueInt, std::string valueStr, MtpDataType dataType);
+	void updateProperty(MtpPropertyCode property, uint64_t valueInt, std::string valueStr, MtpDataType dataType);
+	void addProperties(const std::string& path, int storageID);
+	uint64_t getIntProperty(MtpPropertyCode property);
+	struct mtpProperty {
+		MtpPropertyCode property;
+		MtpDataType dataType;
+		uint64_t valueInt;
+		std::string valueStr;
+		mtpProperty() : property(0), dataType(0), valueInt(0) {}
+	};
+	std::vector<mtpProperty>& getMtpProps();
+	std::vector<mtpProperty> mtpProp;
+	const mtpProperty& getProperty(MtpPropertyCode property);
+};
+
+// A directory
+class Tree : public Node {
+	std::map<MtpObjectHandle, Node*> entries;
+	bool alreadyRead;
+public:
+	Tree(MtpObjectHandle handle, MtpObjectHandle parent, const std::string& name);
+	~Tree();
+
+	virtual bool isDir() const { return true; }
+
+	void addEntry(Node* node);
+	Node* findNode(MtpObjectHandle handle);
+	void getmtpids(MtpObjectHandleList* mtpids);
+	void deleteNode(MtpObjectHandle handle);
+	std::string getPath(Node* node);
+	int getMtpParentId() { return Node::getMtpParentId(); }
+	int getMtpParentId(Node* node);
+	Node* findEntryByName(std::string name);
+	int getCount();
+	bool wasAlreadyRead() const { return alreadyRead; }
+	void setAlreadyRead(bool b) { alreadyRead = b; }
+};
+
+#endif
diff --git a/mtp/ffs/f_mtp.h b/mtp/ffs/f_mtp.h
new file mode 100644
index 0000000..22ec771
--- /dev/null
+++ b/mtp/ffs/f_mtp.h
@@ -0,0 +1,43 @@
+/****************************************************************************
+ ****************************************************************************
+ ***
+ ***   This header was automatically generated from a Linux kernel header
+ ***   of the same name, to make information necessary for userspace to
+ ***   call into the kernel available to libc.  It contains only constants,
+ ***   structures, and macros generated from the original header, and thus,
+ ***   contains no copyrightable information.
+ ***
+ ***   To edit the content of this header, modify the corresponding
+ ***   source file (e.g. under external/kernel-headers/original/) then
+ ***   run bionic/libc/kernel/tools/update_all.py
+ ***
+ ***   Any manual change here will be lost the next time this script will
+ ***   be run. You've been warned!
+ ***
+ ****************************************************************************
+ ****************************************************************************/
+#ifndef _UAPI_LINUX_USB_F_MTP_H
+#define _UAPI_LINUX_USB_F_MTP_H
+#include <linux/ioctl.h>
+#include <linux/types.h>
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct mtp_file_range {
+  int fd;
+  loff_t offset;
+  int64_t length;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+  uint16_t command;
+  uint32_t transaction_id;
+};
+struct mtp_event {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+  size_t length;
+  void * data;
+};
+#define MTP_SEND_FILE _IOW('M', 0, struct mtp_file_range)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define MTP_RECEIVE_FILE _IOW('M', 1, struct mtp_file_range)
+#define MTP_SEND_EVENT _IOW('M', 3, struct mtp_event)
+#define MTP_SEND_FILE_WITH_HEADER _IOW('M', 4, struct mtp_file_range)
+#endif
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
diff --git a/mtp/ffs/mtp.h b/mtp/ffs/mtp.h
new file mode 100644
index 0000000..9f6c323
--- /dev/null
+++ b/mtp/ffs/mtp.h
@@ -0,0 +1,616 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_H
+#define _MTP_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#define MTP_STANDARD_VERSION			100
+
+// Container Types
+#define MTP_CONTAINER_TYPE_UNDEFINED	0
+#define MTP_CONTAINER_TYPE_COMMAND		1
+#define MTP_CONTAINER_TYPE_DATA			2
+#define MTP_CONTAINER_TYPE_RESPONSE		3
+#define MTP_CONTAINER_TYPE_EVENT		4
+
+// Container Offsets
+#define MTP_CONTAINER_LENGTH_OFFSET				0
+#define MTP_CONTAINER_TYPE_OFFSET				4
+#define MTP_CONTAINER_CODE_OFFSET				6
+#define MTP_CONTAINER_TRANSACTION_ID_OFFSET		8
+#define MTP_CONTAINER_PARAMETER_OFFSET			12
+#define MTP_CONTAINER_HEADER_SIZE				12
+
+// Maximum buffer size for a MTP packet.
+#define MTP_BUFFER_SIZE 16384
+
+// MTP Data Types
+#define MTP_TYPE_UNDEFINED		0x0000			// Undefined
+#define MTP_TYPE_INT8			0x0001			// Signed 8-bit integer
+#define MTP_TYPE_UINT8			0x0002			// Unsigned 8-bit integer
+#define MTP_TYPE_INT16			0x0003			// Signed 16-bit integer
+#define MTP_TYPE_UINT16			0x0004			// Unsigned 16-bit integer
+#define MTP_TYPE_INT32			0x0005			// Signed 32-bit integer
+#define MTP_TYPE_UINT32			0x0006			// Unsigned 32-bit integer
+#define MTP_TYPE_INT64			0x0007			// Signed 64-bit integer
+#define MTP_TYPE_UINT64			0x0008			// Unsigned 64-bit integer
+#define MTP_TYPE_INT128			0x0009			// Signed 128-bit integer
+#define MTP_TYPE_UINT128		0x000A			// Unsigned 128-bit integer
+#define MTP_TYPE_AINT8			0x4001			// Array of signed 8-bit integers
+#define MTP_TYPE_AUINT8			0x4002			// Array of unsigned 8-bit integers
+#define MTP_TYPE_AINT16			0x4003			// Array of signed 16-bit integers
+#define MTP_TYPE_AUINT16		0x4004			// Array of unsigned 16-bit integers
+#define MTP_TYPE_AINT32			0x4005			// Array of signed 32-bit integers
+#define MTP_TYPE_AUINT32		0x4006			// Array of unsigned 32-bit integers
+#define MTP_TYPE_AINT64			0x4007			// Array of signed 64-bit integers
+#define MTP_TYPE_AUINT64		0x4008			// Array of unsigned 64-bit integers
+#define MTP_TYPE_AINT128		0x4009			// Array of signed 128-bit integers
+#define MTP_TYPE_AUINT128		0x400A			// Array of unsigned 128-bit integers
+#define MTP_TYPE_STR			0xFFFF			// Variable-length Unicode string
+
+// MTP Format Codes
+#define MTP_FORMAT_UNDEFINED							0x3000	 // Undefined object
+#define MTP_FORMAT_ASSOCIATION							0x3001	 // Association (for example, a folder)
+#define MTP_FORMAT_SCRIPT								0x3002	 // Device model-specific script
+#define MTP_FORMAT_EXECUTABLE							0x3003	 // Device model-specific binary executable
+#define MTP_FORMAT_TEXT									0x3004	 // Text file
+#define MTP_FORMAT_HTML									0x3005	 // Hypertext Markup Language file (text)
+#define MTP_FORMAT_DPOF									0x3006	 // Digital Print Order Format file (text)
+#define MTP_FORMAT_AIFF									0x3007	 // Audio clip
+#define MTP_FORMAT_WAV									0x3008	 // Audio clip
+#define MTP_FORMAT_MP3									0x3009	 // Audio clip
+#define MTP_FORMAT_AVI									0x300A	 // Video clip
+#define MTP_FORMAT_MPEG									0x300B	 // Video clip
+#define MTP_FORMAT_ASF									0x300C	 // Microsoft Advanced Streaming Format (video)
+#define MTP_FORMAT_DEFINED								0x3800	 // Unknown image object
+#define MTP_FORMAT_EXIF_JPEG							0x3801	 // Exchangeable File Format, JEIDA standard
+#define MTP_FORMAT_TIFF_EP								0x3802	 // Tag Image File Format for Electronic Photography
+#define MTP_FORMAT_FLASHPIX								0x3803	 // Structured Storage Image Format
+#define MTP_FORMAT_BMP									0x3804	 // Microsoft Windows Bitmap file
+#define MTP_FORMAT_CIFF									0x3805	 // Canon Camera Image File Format
+#define MTP_FORMAT_GIF									0x3807	 // Graphics Interchange Format
+#define MTP_FORMAT_JFIF									0x3808	 // JPEG File Interchange Format
+#define MTP_FORMAT_CD									0x3809	 // PhotoCD Image Pac
+#define MTP_FORMAT_PICT									0x380A	 // Quickdraw Image Format
+#define MTP_FORMAT_PNG									0x380B	 // Portable Network Graphics
+#define MTP_FORMAT_TIFF									0x380D	 // Tag Image File Format
+#define MTP_FORMAT_TIFF_IT								0x380E	 // Tag Image File Format for Information Technology (graphic arts)
+#define MTP_FORMAT_JP2									0x380F	 // JPEG2000 Baseline File Format
+#define MTP_FORMAT_JPX									0x3810	 // JPEG2000 Extended File Format
+#define MTP_FORMAT_DNG									0x3811	 // Digital Negative
+#define MTP_FORMAT_HEIF									0x3812	 // HEIF images
+#define MTP_FORMAT_UNDEFINED_FIRMWARE					0xB802
+#define MTP_FORMAT_WINDOWS_IMAGE_FORMAT					0xB881
+#define MTP_FORMAT_UNDEFINED_AUDIO						0xB900
+#define MTP_FORMAT_WMA									0xB901
+#define MTP_FORMAT_OGG									0xB902
+#define MTP_FORMAT_AAC									0xB903
+#define MTP_FORMAT_AUDIBLE								0xB904
+#define MTP_FORMAT_FLAC									0xB906
+#define MTP_FORMAT_UNDEFINED_VIDEO						0xB980
+#define MTP_FORMAT_WMV									0xB981
+#define MTP_FORMAT_MP4_CONTAINER						0xB982	// ISO 14496-1
+#define MTP_FORMAT_MP2									0xB983
+#define MTP_FORMAT_3GP_CONTAINER						0xB984	// 3GPP file format. Details: http://www.3gpp.org/ftp/Specs/html-info/26244.htm (page title - \u201cTransparent end-to-end packet switched streaming service, 3GPP file format\u201d).
+#define MTP_FORMAT_UNDEFINED_COLLECTION					0xBA00
+#define MTP_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM			0xBA01
+#define MTP_FORMAT_ABSTRACT_IMAGE_ALBUM					0xBA02
+#define MTP_FORMAT_ABSTRACT_AUDIO_ALBUM					0xBA03
+#define MTP_FORMAT_ABSTRACT_VIDEO_ALBUM					0xBA04
+#define MTP_FORMAT_ABSTRACT_AV_PLAYLIST					0xBA05
+#define MTP_FORMAT_ABSTRACT_CONTACT_GROUP				0xBA06
+#define MTP_FORMAT_ABSTRACT_MESSAGE_FOLDER				0xBA07
+#define MTP_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION		0xBA08
+#define MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST				0xBA09
+#define MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST				0xBA0A
+#define MTP_FORMAT_ABSTRACT_MEDIACAST					0xBA0B // For use with mediacasts; references multimedia enclosures of RSS feeds or episodic content
+#define MTP_FORMAT_WPL_PLAYLIST							0xBA10
+#define MTP_FORMAT_M3U_PLAYLIST							0xBA11
+#define MTP_FORMAT_MPL_PLAYLIST							0xBA12
+#define MTP_FORMAT_ASX_PLAYLIST							0xBA13
+#define MTP_FORMAT_PLS_PLAYLIST							0xBA14
+#define MTP_FORMAT_UNDEFINED_DOCUMENT					0xBA80
+#define MTP_FORMAT_ABSTRACT_DOCUMENT					0xBA81
+#define MTP_FORMAT_XML_DOCUMENT							0xBA82
+#define MTP_FORMAT_MS_WORD_DOCUMENT						0xBA83
+#define MTP_FORMAT_MHT_COMPILED_HTML_DOCUMENT			0xBA84
+#define MTP_FORMAT_MS_EXCEL_SPREADSHEET					0xBA85
+#define MTP_FORMAT_MS_POWERPOINT_PRESENTATION			0xBA86
+#define MTP_FORMAT_UNDEFINED_MESSAGE					0xBB00
+#define MTP_FORMAT_ABSTRACT_MESSSAGE					0xBB01
+#define MTP_FORMAT_UNDEFINED_CONTACT					0xBB80
+#define MTP_FORMAT_ABSTRACT_CONTACT						0xBB81
+#define MTP_FORMAT_VCARD_2								0xBB82
+
+// MTP Object Property Codes
+#define MTP_PROPERTY_STORAGE_ID								0xDC01
+#define MTP_PROPERTY_OBJECT_FORMAT							0xDC02
+#define MTP_PROPERTY_PROTECTION_STATUS						0xDC03
+#define MTP_PROPERTY_OBJECT_SIZE							0xDC04
+#define MTP_PROPERTY_ASSOCIATION_TYPE						0xDC05
+#define MTP_PROPERTY_ASSOCIATION_DESC						0xDC06
+#define MTP_PROPERTY_OBJECT_FILE_NAME						0xDC07
+#define MTP_PROPERTY_DATE_CREATED							0xDC08
+#define MTP_PROPERTY_DATE_MODIFIED							0xDC09
+#define MTP_PROPERTY_KEYWORDS								0xDC0A
+#define MTP_PROPERTY_PARENT_OBJECT							0xDC0B
+#define MTP_PROPERTY_ALLOWED_FOLDER_CONTENTS				0xDC0C
+#define MTP_PROPERTY_HIDDEN									0xDC0D
+#define MTP_PROPERTY_SYSTEM_OBJECT							0xDC0E
+#define MTP_PROPERTY_PERSISTENT_UID							0xDC41
+#define MTP_PROPERTY_SYNC_ID								0xDC42
+#define MTP_PROPERTY_PROPERTY_BAG							0xDC43
+#define MTP_PROPERTY_NAME									0xDC44
+#define MTP_PROPERTY_CREATED_BY								0xDC45
+#define MTP_PROPERTY_ARTIST									0xDC46
+#define MTP_PROPERTY_DATE_AUTHORED							0xDC47
+#define MTP_PROPERTY_DESCRIPTION							0xDC48
+#define MTP_PROPERTY_URL_REFERENCE							0xDC49
+#define MTP_PROPERTY_LANGUAGE_LOCALE						0xDC4A
+#define MTP_PROPERTY_COPYRIGHT_INFORMATION					0xDC4B
+#define MTP_PROPERTY_SOURCE									0xDC4C
+#define MTP_PROPERTY_ORIGIN_LOCATION						0xDC4D
+#define MTP_PROPERTY_DATE_ADDED								0xDC4E
+#define MTP_PROPERTY_NON_CONSUMABLE							0xDC4F
+#define MTP_PROPERTY_CORRUPT_UNPLAYABLE						0xDC50
+#define MTP_PROPERTY_PRODUCER_SERIAL_NUMBER					0xDC51
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_FORMAT			0xDC81
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_SIZE				0xDC82
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_HEIGHT			0xDC83
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_WIDTH			0xDC84
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DURATION			0xDC85
+#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DATA				0xDC86
+#define MTP_PROPERTY_WIDTH									0xDC87
+#define MTP_PROPERTY_HEIGHT									0xDC88
+#define MTP_PROPERTY_DURATION								0xDC89
+#define MTP_PROPERTY_RATING									0xDC8A
+#define MTP_PROPERTY_TRACK									0xDC8B
+#define MTP_PROPERTY_GENRE									0xDC8C
+#define MTP_PROPERTY_CREDITS								0xDC8D
+#define MTP_PROPERTY_LYRICS									0xDC8E
+#define MTP_PROPERTY_SUBSCRIPTION_CONTENT_ID				0xDC8F
+#define MTP_PROPERTY_PRODUCED_BY							0xDC90
+#define MTP_PROPERTY_USE_COUNT								0xDC91
+#define MTP_PROPERTY_SKIP_COUNT								0xDC92
+#define MTP_PROPERTY_LAST_ACCESSED							0xDC93
+#define MTP_PROPERTY_PARENTAL_RATING						0xDC94
+#define MTP_PROPERTY_META_GENRE								0xDC95
+#define MTP_PROPERTY_COMPOSER								0xDC96
+#define MTP_PROPERTY_EFFECTIVE_RATING						0xDC97
+#define MTP_PROPERTY_SUBTITLE								0xDC98
+#define MTP_PROPERTY_ORIGINAL_RELEASE_DATE					0xDC99
+#define MTP_PROPERTY_ALBUM_NAME								0xDC9A
+#define MTP_PROPERTY_ALBUM_ARTIST							0xDC9B
+#define MTP_PROPERTY_MOOD									0xDC9C
+#define MTP_PROPERTY_DRM_STATUS								0xDC9D
+#define MTP_PROPERTY_SUB_DESCRIPTION						0xDC9E
+#define MTP_PROPERTY_IS_CROPPED								0xDCD1
+#define MTP_PROPERTY_IS_COLOUR_CORRECTED					0xDCD2
+#define MTP_PROPERTY_IMAGE_BIT_DEPTH						0xDCD3
+#define MTP_PROPERTY_F_NUMBER								0xDCD4
+#define MTP_PROPERTY_EXPOSURE_TIME							0xDCD5
+#define MTP_PROPERTY_EXPOSURE_INDEX							0xDCD6
+#define MTP_PROPERTY_TOTAL_BITRATE							0xDE91
+#define MTP_PROPERTY_BITRATE_TYPE							0xDE92
+#define MTP_PROPERTY_SAMPLE_RATE							0xDE93
+#define MTP_PROPERTY_NUMBER_OF_CHANNELS						0xDE94
+#define MTP_PROPERTY_AUDIO_BIT_DEPTH						0xDE95
+#define MTP_PROPERTY_SCAN_TYPE								0xDE97
+#define MTP_PROPERTY_AUDIO_WAVE_CODEC						0xDE99
+#define MTP_PROPERTY_AUDIO_BITRATE							0xDE9A
+#define MTP_PROPERTY_VIDEO_FOURCC_CODEC						0xDE9B
+#define MTP_PROPERTY_VIDEO_BITRATE							0xDE9C
+#define MTP_PROPERTY_FRAMES_PER_THOUSAND_SECONDS			0xDE9D
+#define MTP_PROPERTY_KEYFRAME_DISTANCE						0xDE9E
+#define MTP_PROPERTY_BUFFER_SIZE							0xDE9F
+#define MTP_PROPERTY_ENCODING_QUALITY						0xDEA0
+#define MTP_PROPERTY_ENCODING_PROFILE						0xDEA1
+#define MTP_PROPERTY_DISPLAY_NAME							0xDCE0
+#define MTP_PROPERTY_BODY_TEXT								0xDCE1
+#define MTP_PROPERTY_SUBJECT								0xDCE2
+#define MTP_PROPERTY_PRIORITY								0xDCE3
+#define MTP_PROPERTY_GIVEN_NAME								0xDD00
+#define MTP_PROPERTY_MIDDLE_NAMES							0xDD01
+#define MTP_PROPERTY_FAMILY_NAME							0xDD02
+#define MTP_PROPERTY_PREFIX									0xDD03
+#define MTP_PROPERTY_SUFFIX									0xDD04
+#define MTP_PROPERTY_PHONETIC_GIVEN_NAME					0xDD05
+#define MTP_PROPERTY_PHONETIC_FAMILY_NAME					0xDD06
+#define MTP_PROPERTY_EMAIL_PRIMARY							0xDD07
+#define MTP_PROPERTY_EMAIL_PERSONAL_1						0xDD08
+#define MTP_PROPERTY_EMAIL_PERSONAL_2						0xDD09
+#define MTP_PROPERTY_EMAIL_BUSINESS_1						0xDD0A
+#define MTP_PROPERTY_EMAIL_BUSINESS_2						0xDD0B
+#define MTP_PROPERTY_EMAIL_OTHERS							0xDD0C
+#define MTP_PROPERTY_PHONE_NUMBER_PRIMARY					0xDD0D
+#define MTP_PROPERTY_PHONE_NUMBER_PERSONAL					0xDD0E
+#define MTP_PROPERTY_PHONE_NUMBER_PERSONAL_2				0xDD0F
+#define MTP_PROPERTY_PHONE_NUMBER_BUSINESS					0xDD10
+#define MTP_PROPERTY_PHONE_NUMBER_BUSINESS_2				0xDD11
+#define MTP_PROPERTY_PHONE_NUMBER_MOBILE					0xDD12
+#define MTP_PROPERTY_PHONE_NUMBER_MOBILE_2					0xDD13
+#define MTP_PROPERTY_FAX_NUMBER_PRIMARY						0xDD14
+#define MTP_PROPERTY_FAX_NUMBER_PERSONAL					0xDD15
+#define MTP_PROPERTY_FAX_NUMBER_BUSINESS					0xDD16
+#define MTP_PROPERTY_PAGER_NUMBER							0xDD17
+#define MTP_PROPERTY_PHONE_NUMBER_OTHERS					0xDD18
+#define MTP_PROPERTY_PRIMARY_WEB_ADDRESS					0xDD19
+#define MTP_PROPERTY_PERSONAL_WEB_ADDRESS					0xDD1A
+#define MTP_PROPERTY_BUSINESS_WEB_ADDRESS					0xDD1B
+#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS				0xDD1C
+#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_2			0xDD1D
+#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_3			0xDD1E
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_FULL			0xDD1F
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_1			0xDD20
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_2			0xDD21
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_CITY			0xDD22
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_REGION			0xDD23
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE	0xDD24
+#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_COUNTRY		0xDD25
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_FULL			0xDD26
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_1			0xDD27
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_2			0xDD28
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_CITY			0xDD29
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_REGION			0xDD2A
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE	0xDD2B
+#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_COUNTRY		0xDD2C
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_FULL				0xDD2D
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_1			0xDD2E
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_2			0xDD2F
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_CITY				0xDD30
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_REGION			0xDD31
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_POSTAL_CODE		0xDD32
+#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_COUNTRY			0xDD33
+#define MTP_PROPERTY_ORGANIZATION_NAME						0xDD34
+#define MTP_PROPERTY_PHONETIC_ORGANIZATION_NAME				0xDD35
+#define MTP_PROPERTY_ROLE									0xDD36
+#define MTP_PROPERTY_BIRTHDATE								0xDD37
+#define MTP_PROPERTY_MESSAGE_TO								0xDD40
+#define MTP_PROPERTY_MESSAGE_CC								0xDD41
+#define MTP_PROPERTY_MESSAGE_BCC							0xDD42
+#define MTP_PROPERTY_MESSAGE_READ							0xDD43
+#define MTP_PROPERTY_MESSAGE_RECEIVED_TIME					0xDD44
+#define MTP_PROPERTY_MESSAGE_SENDER							0xDD45
+#define MTP_PROPERTY_ACTIVITY_BEGIN_TIME					0xDD50
+#define MTP_PROPERTY_ACTIVITY_END_TIME						0xDD51
+#define MTP_PROPERTY_ACTIVITY_LOCATION						0xDD52
+#define MTP_PROPERTY_ACTIVITY_REQUIRED_ATTENDEES			0xDD54
+#define MTP_PROPERTY_ACTIVITY_OPTIONAL_ATTENDEES			0xDD55
+#define MTP_PROPERTY_ACTIVITY_RESOURCES						0xDD56
+#define MTP_PROPERTY_ACTIVITY_ACCEPTED						0xDD57
+#define MTP_PROPERTY_ACTIVITY_TENTATIVE						0xDD58
+#define MTP_PROPERTY_ACTIVITY_DECLINED						0xDD59
+#define MTP_PROPERTY_ACTIVITY_REMAINDER_TIME				0xDD5A
+#define MTP_PROPERTY_ACTIVITY_OWNER							0xDD5B
+#define MTP_PROPERTY_ACTIVITY_STATUS						0xDD5C
+#define MTP_PROPERTY_OWNER									0xDD5D
+#define MTP_PROPERTY_EDITOR									0xDD5E
+#define MTP_PROPERTY_WEBMASTER								0xDD5F
+#define MTP_PROPERTY_URL_SOURCE								0xDD60
+#define MTP_PROPERTY_URL_DESTINATION						0xDD61
+#define MTP_PROPERTY_TIME_BOOKMARK							0xDD62
+#define MTP_PROPERTY_OBJECT_BOOKMARK						0xDD63
+#define MTP_PROPERTY_BYTE_BOOKMARK							0xDD64
+#define MTP_PROPERTY_LAST_BUILD_DATE						0xDD70
+#define MTP_PROPERTY_TIME_TO_LIVE							0xDD71
+#define MTP_PROPERTY_MEDIA_GUID								0xDD72
+
+// MTP Device Property Codes
+#define MTP_DEVICE_PROPERTY_UNDEFINED						0x5000
+#define MTP_DEVICE_PROPERTY_BATTERY_LEVEL					0x5001
+#define MTP_DEVICE_PROPERTY_FUNCTIONAL_MODE					0x5002
+#define MTP_DEVICE_PROPERTY_IMAGE_SIZE						0x5003
+#define MTP_DEVICE_PROPERTY_COMPRESSION_SETTING				0x5004
+#define MTP_DEVICE_PROPERTY_WHITE_BALANCE					0x5005
+#define MTP_DEVICE_PROPERTY_RGB_GAIN						0x5006
+#define MTP_DEVICE_PROPERTY_F_NUMBER						0x5007
+#define MTP_DEVICE_PROPERTY_FOCAL_LENGTH					0x5008
+#define MTP_DEVICE_PROPERTY_FOCUS_DISTANCE					0x5009
+#define MTP_DEVICE_PROPERTY_FOCUS_MODE						0x500A
+#define MTP_DEVICE_PROPERTY_EXPOSURE_METERING_MODE			0x500B
+#define MTP_DEVICE_PROPERTY_FLASH_MODE						0x500C
+#define MTP_DEVICE_PROPERTY_EXPOSURE_TIME					0x500D
+#define MTP_DEVICE_PROPERTY_EXPOSURE_PROGRAM_MODE			0x500E
+#define MTP_DEVICE_PROPERTY_EXPOSURE_INDEX					0x500F
+#define MTP_DEVICE_PROPERTY_EXPOSURE_BIAS_COMPENSATION		0x5010
+#define MTP_DEVICE_PROPERTY_DATETIME						0x5011
+#define MTP_DEVICE_PROPERTY_CAPTURE_DELAY					0x5012
+#define MTP_DEVICE_PROPERTY_STILL_CAPTURE_MODE				0x5013
+#define MTP_DEVICE_PROPERTY_CONTRAST						0x5014
+#define MTP_DEVICE_PROPERTY_SHARPNESS						0x5015
+#define MTP_DEVICE_PROPERTY_DIGITAL_ZOOM					0x5016
+#define MTP_DEVICE_PROPERTY_EFFECT_MODE						0x5017
+#define MTP_DEVICE_PROPERTY_BURST_NUMBER					0x5018
+#define MTP_DEVICE_PROPERTY_BURST_INTERVAL					0x5019
+#define MTP_DEVICE_PROPERTY_TIMELAPSE_NUMBER				0x501A
+#define MTP_DEVICE_PROPERTY_TIMELAPSE_INTERVAL				0x501B
+#define MTP_DEVICE_PROPERTY_FOCUS_METERING_MODE				0x501C
+#define MTP_DEVICE_PROPERTY_UPLOAD_URL						0x501D
+#define MTP_DEVICE_PROPERTY_ARTIST							0x501E
+#define MTP_DEVICE_PROPERTY_COPYRIGHT_INFO					0x501F
+#define MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER			0xD401
+#define MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME			0xD402
+#define MTP_DEVICE_PROPERTY_VOLUME							0xD403
+#define MTP_DEVICE_PROPERTY_SUPPORTED_FORMATS_ORDERED		0xD404
+#define MTP_DEVICE_PROPERTY_DEVICE_ICON						0xD405
+#define MTP_DEVICE_PROPERTY_PLAYBACK_RATE					0xD410
+#define MTP_DEVICE_PROPERTY_PLAYBACK_OBJECT					0xD411
+#define MTP_DEVICE_PROPERTY_PLAYBACK_CONTAINER_INDEX		0xD412
+#define MTP_DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO	0xD406
+#define MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE			0xD407
+
+// MTP Operation Codes
+#define MTP_OPERATION_GET_DEVICE_INFO						0x1001
+#define MTP_OPERATION_OPEN_SESSION							0x1002
+#define MTP_OPERATION_CLOSE_SESSION							0x1003
+#define MTP_OPERATION_GET_STORAGE_IDS						0x1004
+#define MTP_OPERATION_GET_STORAGE_INFO						0x1005
+#define MTP_OPERATION_GET_NUM_OBJECTS						0x1006
+#define MTP_OPERATION_GET_OBJECT_HANDLES					0x1007
+#define MTP_OPERATION_GET_OBJECT_INFO						0x1008
+#define MTP_OPERATION_GET_OBJECT							0x1009
+#define MTP_OPERATION_GET_THUMB								0x100A
+#define MTP_OPERATION_DELETE_OBJECT							0x100B
+#define MTP_OPERATION_SEND_OBJECT_INFO						0x100C
+#define MTP_OPERATION_SEND_OBJECT							0x100D
+#define MTP_OPERATION_INITIATE_CAPTURE						0x100E
+#define MTP_OPERATION_FORMAT_STORE							0x100F
+#define MTP_OPERATION_RESET_DEVICE							0x1010
+#define MTP_OPERATION_SELF_TEST								0x1011
+#define MTP_OPERATION_SET_OBJECT_PROTECTION					0x1012
+#define MTP_OPERATION_POWER_DOWN							0x1013
+#define MTP_OPERATION_GET_DEVICE_PROP_DESC					0x1014
+#define MTP_OPERATION_GET_DEVICE_PROP_VALUE					0x1015
+#define MTP_OPERATION_SET_DEVICE_PROP_VALUE					0x1016
+#define MTP_OPERATION_RESET_DEVICE_PROP_VALUE				0x1017
+#define MTP_OPERATION_TERMINATE_OPEN_CAPTURE				0x1018
+#define MTP_OPERATION_MOVE_OBJECT							0x1019
+#define MTP_OPERATION_COPY_OBJECT							0x101A
+#define MTP_OPERATION_GET_PARTIAL_OBJECT					0x101B
+#define MTP_OPERATION_INITIATE_OPEN_CAPTURE					0x101C
+#define MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED			0x9801
+#define MTP_OPERATION_GET_OBJECT_PROP_DESC					0x9802
+#define MTP_OPERATION_GET_OBJECT_PROP_VALUE					0x9803
+#define MTP_OPERATION_SET_OBJECT_PROP_VALUE					0x9804
+#define MTP_OPERATION_GET_OBJECT_PROP_LIST					0x9805
+#define MTP_OPERATION_SET_OBJECT_PROP_LIST					0x9806
+#define MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC			0x9807
+#define MTP_OPERATION_SEND_OBJECT_PROP_LIST					0x9808
+#define MTP_OPERATION_GET_OBJECT_REFERENCES					0x9810
+#define MTP_OPERATION_SET_OBJECT_REFERENCES					0x9811
+#define MTP_OPERATION_SKIP									0x9820
+
+// Android extensions for direct file IO
+
+// Same as GetPartialObject, but with 64 bit offset
+#define MTP_OPERATION_GET_PARTIAL_OBJECT_64					0x95C1
+// Same as GetPartialObject64, but copying host to device
+#define MTP_OPERATION_SEND_PARTIAL_OBJECT					0x95C2
+// Truncates file to 64 bit length
+#define MTP_OPERATION_TRUNCATE_OBJECT						0x95C3
+// Must be called before using SendPartialObject and TruncateObject
+#define MTP_OPERATION_BEGIN_EDIT_OBJECT						0x95C4
+// Called to commit changes made by SendPartialObject and TruncateObject
+#define MTP_OPERATION_END_EDIT_OBJECT						0x95C5
+
+// MTP Response Codes
+#define MTP_RESPONSE_UNDEFINED									0x2000
+#define MTP_RESPONSE_OK											0x2001
+#define MTP_RESPONSE_GENERAL_ERROR								0x2002
+#define MTP_RESPONSE_SESSION_NOT_OPEN							0x2003
+#define MTP_RESPONSE_INVALID_TRANSACTION_ID						0x2004
+#define MTP_RESPONSE_OPERATION_NOT_SUPPORTED					0x2005
+#define MTP_RESPONSE_PARAMETER_NOT_SUPPORTED					0x2006
+#define MTP_RESPONSE_INCOMPLETE_TRANSFER						0x2007
+#define MTP_RESPONSE_INVALID_STORAGE_ID							0x2008
+#define MTP_RESPONSE_INVALID_OBJECT_HANDLE						0x2009
+#define MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED					0x200A
+#define MTP_RESPONSE_INVALID_OBJECT_FORMAT_CODE					0x200B
+#define MTP_RESPONSE_STORAGE_FULL								0x200C
+#define MTP_RESPONSE_OBJECT_WRITE_PROTECTED						0x200D
+#define MTP_RESPONSE_STORE_READ_ONLY							0x200E
+#define MTP_RESPONSE_ACCESS_DENIED								0x200F
+#define MTP_RESPONSE_NO_THUMBNAIL_PRESENT						0x2010
+#define MTP_RESPONSE_SELF_TEST_FAILED							0x2011
+#define MTP_RESPONSE_PARTIAL_DELETION							0x2012
+#define MTP_RESPONSE_STORE_NOT_AVAILABLE						0x2013
+#define MTP_RESPONSE_SPECIFICATION_BY_FORMAT_UNSUPPORTED		0x2014
+#define MTP_RESPONSE_NO_VALID_OBJECT_INFO						0x2015
+#define MTP_RESPONSE_INVALID_CODE_FORMAT						0x2016
+#define MTP_RESPONSE_UNKNOWN_VENDOR_CODE						0x2017
+#define MTP_RESPONSE_CAPTURE_ALREADY_TERMINATED					0x2018
+#define MTP_RESPONSE_DEVICE_BUSY								0x2019
+#define MTP_RESPONSE_INVALID_PARENT_OBJECT						0x201A
+#define MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT					0x201B
+#define MTP_RESPONSE_INVALID_DEVICE_PROP_VALUE					0x201C
+#define MTP_RESPONSE_INVALID_PARAMETER							0x201D
+#define MTP_RESPONSE_SESSION_ALREADY_OPEN						0x201E
+#define MTP_RESPONSE_TRANSACTION_CANCELLED						0x201F
+#define MTP_RESPONSE_SPECIFICATION_OF_DESTINATION_UNSUPPORTED	0x2020
+#define MTP_RESPONSE_INVALID_OBJECT_PROP_CODE					0xA801
+#define MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT					0xA802
+#define MTP_RESPONSE_INVALID_OBJECT_PROP_VALUE					0xA803
+#define MTP_RESPONSE_INVALID_OBJECT_REFERENCE					0xA804
+#define MTP_RESPONSE_GROUP_NOT_SUPPORTED						0xA805
+#define MTP_RESPONSE_INVALID_DATASET							0xA806
+#define MTP_RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED			0xA807
+#define MTP_RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED			0xA808
+#define MTP_RESPONSE_OBJECT_TOO_LARGE							0xA809
+#define MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED					0xA80A
+
+// Supported Playback Formats
+#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED 0x3000
+/** Format code for associations (folders and directories) */
+#define SUPPORTED_PLAYBACK_FORMAT_ASSOCIATION 0x3001
+/** Format code for script files */
+#define SUPPORTED_PLAYBACK_FORMAT_SCRIPT 0x3002
+/** Format code for executable files */
+#define SUPPORTED_PLAYBACK_FORMAT_EXECUTABLE 0x3003
+/** Format code for text files */
+#define SUPPORTED_PLAYBACK_FORMAT_TEXT 0x3004
+/** Format code for HTML files */
+#define SUPPORTED_PLAYBACK_FORMAT_HTML 0x3005
+/** Format code for DPOF files */
+#define SUPPORTED_PLAYBACK_FORMAT_DPOF 0x3006
+/** Format code for AIFF audio files */
+#define SUPPORTED_PLAYBACK_FORMAT_AIFF 0x3007
+/** Format code for WAV audio files */
+#define SUPPORTED_PLAYBACK_FORMAT_WAV 0x3008
+/** Format code for MP3 audio files */
+#define SUPPORTED_PLAYBACK_FORMAT_MP3 0x3009
+/** Format code for AVI video files */
+#define SUPPORTED_PLAYBACK_FORMAT_AVI 0x300A
+/** Format code for MPEG video files */
+#define SUPPORTED_PLAYBACK_FORMAT_MPEG 0x300B
+/** Format code for ASF files */
+#define SUPPORTED_PLAYBACK_FORMAT_ASF 0x300C
+/** Format code for JPEG image files */
+#define SUPPORTED_PLAYBACK_FORMAT_EXIF_JPEG 0x3801
+/** Format code for TIFF EP image files */
+#define SUPPORTED_PLAYBACK_FORMAT_TIFF_EP 0x3802
+/** Format code for BMP image files */
+#define SUPPORTED_PLAYBACK_FORMAT_BMP 0x3804
+/** Format code for GIF image files */
+#define SUPPORTED_PLAYBACK_FORMAT_GIF 0x3807
+/** Format code for JFIF image files */
+#define SUPPORTED_PLAYBACK_FORMAT_JFIF 0x3808
+/** Format code for PICT image files */
+#define SUPPORTED_PLAYBACK_FORMAT_PICT 0x380A
+/** Format code for PNG image files */
+#define SUPPORTED_PLAYBACK_FORMAT_PNG 0x380B
+/** Format code for TIFF image files */
+#define SUPPORTED_PLAYBACK_FORMAT_TIFF 0x380D
+/** Format code for JP2 files */
+#define SUPPORTED_PLAYBACK_FORMAT_JP2 0x380F
+/** Format code for JPX files */
+#define SUPPORTED_PLAYBACK_FORMAT_JPX 0x3810
+/** Format code for firmware files */
+#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED_FIRMWARE 0xB802
+/** Format code for Windows image files */
+#define SUPPORTED_PLAYBACK_FORMAT_WINDOWS_IMAGE_FORMAT 0xB881
+/** Format code for undefined audio files files */
+#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED_AUDIO 0xB900
+/** Format code for WMA audio files */
+#define SUPPORTED_PLAYBACK_FORMAT_WMA 0xB901
+/** Format code for OGG audio files */
+#define SUPPORTED_PLAYBACK_FORMAT_OGG 0xB902
+/** Format code for AAC audio files */
+#define SUPPORTED_PLAYBACK_FORMAT_AAC 0xB903
+/** Format code for Audible audio files */
+#define SUPPORTED_PLAYBACK_FORMAT_AUDIBLE 0xB904
+/** Format code for FLAC audio files */
+#define SUPPORTED_PLAYBACK_FORMAT_FLAC 0xB906
+/** Format code for undefined video files */
+#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED_VIDEO 0xB980
+/** Format code for WMV video files */
+#define SUPPORTED_PLAYBACK_FORMAT_WMV 0xB981
+/** Format code for MP4 files */
+#define SUPPORTED_PLAYBACK_FORMAT_MP4_CONTAINER 0xB982
+/** Format code for MP2 files */
+#define SUPPORTED_PLAYBACK_FORMAT_MP2 0xB983
+/** Format code for 3GP files */
+#define SUPPORTED_PLAYBACK_FORMAT_3GP_CONTAINER 0xB984
+/** Format code for undefined collections */
+#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED_COLLECTION 0xBA00
+/** Format code for multimedia albums */
+#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM 0xBA01
+/** Format code for image albums */
+#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_IMAGE_ALBUM 0xBA02
+/** Format code for audio albums */
+#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_AUDIO_ALBUM 0xBA03
+/** Format code for video albums */
+#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_VIDEO_ALBUM 0xBA04
+/** Format code for abstract AV playlists */
+#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_AV_PLAYLIST 0xBA05
+/** Format code for abstract audio playlists */
+#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_AUDIO_PLAYLIST 0xBA09
+/** Format code for abstract video playlists */
+#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_VIDEO_PLAYLIST 0xBA0A
+/** Format code for abstract mediacasts */
+#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_MEDIACAST 0xBA0B
+/** Format code for WPL playlist files */
+#define SUPPORTED_PLAYBACK_FORMAT_WPL_PLAYLIST 0xBA10
+/** Format code for M3u playlist files */
+#define SUPPORTED_PLAYBACK_FORMAT_M3U_PLAYLIST 0xBA11
+/** Format code for MPL playlist files */
+#define SUPPORTED_PLAYBACK_FORMAT_MPL_PLAYLIST 0xBA12
+/** Format code for ASX playlist files */
+#define SUPPORTED_PLAYBACK_FORMAT_ASX_PLAYLIST 0xBA13
+/** Format code for PLS playlist files */
+#define SUPPORTED_PLAYBACK_FORMAT_PLS_PLAYLIST 0xBA14
+/** Format code for undefined document files */
+#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED_DOCUMENT 0xBA80
+/** Format code for abstract documents */
+#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_DOCUMENT 0xBA81
+/** Format code for XML documents */
+#define SUPPORTED_PLAYBACK_FORMAT_XML_DOCUMENT 0xBA82
+/** Format code for MS Word documents */
+#define SUPPORTED_PLAYBACK_FORMAT_MS_WORD_DOCUMENT 0xBA83
+/** Format code for MS Excel spreadsheets */
+#define SUPPORTED_PLAYBACK_FORMAT_MS_EXCEL_SPREADSHEET 0xBA85
+/** Format code for MS PowerPoint presentatiosn */
+#define SUPPORTED_PLAYBACK_FORMAT_MS_POWERPOINT_PRESENTATION 0xBA86
+
+// MTP Event Codes
+#define MTP_EVENT_UNDEFINED							0x4000
+#define MTP_EVENT_CANCEL_TRANSACTION				0x4001
+#define MTP_EVENT_OBJECT_ADDED						0x4002
+#define MTP_EVENT_OBJECT_REMOVED					0x4003
+#define MTP_EVENT_STORE_ADDED						0x4004
+#define MTP_EVENT_STORE_REMOVED						0x4005
+#define MTP_EVENT_DEVICE_PROP_CHANGED				0x4006
+#define MTP_EVENT_OBJECT_INFO_CHANGED				0x4007
+#define MTP_EVENT_DEVICE_INFO_CHANGED				0x4008
+#define MTP_EVENT_REQUEST_OBJECT_TRANSFER			0x4009
+#define MTP_EVENT_STORE_FULL						0x400A
+#define MTP_EVENT_DEVICE_RESET						0x400B
+#define MTP_EVENT_STORAGE_INFO_CHANGED				0x400C
+#define MTP_EVENT_CAPTURE_COMPLETE					0x400D
+#define MTP_EVENT_UNREPORTED_STATUS					0x400E
+#define MTP_EVENT_OBJECT_PROP_CHANGED				0xC801
+#define MTP_EVENT_OBJECT_PROP_DESC_CHANGED			0xC802
+#define MTP_EVENT_OBJECT_REFERENCES_CHANGED			0xC803
+
+// Storage Type
+#define MTP_STORAGE_FIXED_ROM						0x0001
+#define MTP_STORAGE_REMOVABLE_ROM					0x0002
+#define MTP_STORAGE_FIXED_RAM						0x0003
+#define MTP_STORAGE_REMOVABLE_RAM					0x0004
+
+// Storage File System
+#define MTP_STORAGE_FILESYSTEM_FLAT					0x0001
+#define MTP_STORAGE_FILESYSTEM_HIERARCHICAL			0x0002
+#define MTP_STORAGE_FILESYSTEM_DCF					0x0003
+
+// Storage Access Capability
+#define MTP_STORAGE_READ_WRITE						0x0000
+#define MTP_STORAGE_READ_ONLY_WITHOUT_DELETE		0x0001
+#define MTP_STORAGE_READ_ONLY_WITH_DELETE			0x0002
+
+// Association Type
+#define MTP_ASSOCIATION_TYPE_UNDEFINED				0x0000
+#define MTP_ASSOCIATION_TYPE_GENERIC_FOLDER			0x0001
+
+// MTP class reqeusts
+#define MTP_REQ_CANCEL				0x64
+#define MTP_REQ_GET_EXT_EVENT_DATA	0x65
+#define MTP_REQ_RESET				0x66
+#define MTP_REQ_GET_DEVICE_STATUS	0x67
+
+#endif // _MTP_H
diff --git a/mtp/ffs/mtp_MtpDatabase.cpp b/mtp/ffs/mtp_MtpDatabase.cpp
new file mode 100755
index 0000000..399a60e
--- /dev/null
+++ b/mtp/ffs/mtp_MtpDatabase.cpp
@@ -0,0 +1,850 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++
+ */
+
+#include <utils/Log.h>
+
+#include <assert.h>
+#include <cutils/properties.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <map>
+#include <string>
+
+#include "MtpDataPacket.h"
+#include "MtpDatabase.h"
+#include "MtpDebug.h"
+#include "MtpObjectInfo.h"
+#include "MtpProperty.h"
+#include "MtpStorage.h"
+#include "MtpStringBuffer.h"
+#include "MtpUtils.h"
+#include "mtp.h"
+#include "mtp_MtpDatabase.hpp"
+
+IMtpDatabase::IMtpDatabase() {
+  storagenum = 0;
+  count = -1;
+}
+
+IMtpDatabase::~IMtpDatabase() {
+  std::map<int, MtpStorage*>::iterator i;
+  for (i = storagemap.begin(); i != storagemap.end(); i++) {
+	delete i->second;
+  }
+}
+
+int IMtpDatabase::DEVICE_PROPERTIES[3] = { MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
+										   MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
+										   MTP_DEVICE_PROPERTY_IMAGE_SIZE };
+
+int IMtpDatabase::FILE_PROPERTIES[10] = {
+  // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
+  // and IMAGE_PROPERTIES below
+  MTP_PROPERTY_STORAGE_ID, MTP_PROPERTY_OBJECT_FORMAT, MTP_PROPERTY_PROTECTION_STATUS,
+  MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, MTP_PROPERTY_DATE_MODIFIED,
+  MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, MTP_PROPERTY_NAME,
+  // TODO: why is DISPLAY_NAME not here?
+  MTP_PROPERTY_DATE_ADDED
+};
+
+int IMtpDatabase::AUDIO_PROPERTIES[19] = {
+  // NOTE must match FILE_PROPERTIES above
+  MTP_PROPERTY_STORAGE_ID, MTP_PROPERTY_OBJECT_FORMAT, MTP_PROPERTY_PROTECTION_STATUS,
+  MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, MTP_PROPERTY_DATE_MODIFIED,
+  MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, MTP_PROPERTY_NAME,
+  MTP_PROPERTY_DISPLAY_NAME, MTP_PROPERTY_DATE_ADDED,
+
+  // audio specific properties
+  MTP_PROPERTY_ARTIST, MTP_PROPERTY_ALBUM_NAME, MTP_PROPERTY_ALBUM_ARTIST, MTP_PROPERTY_TRACK,
+  MTP_PROPERTY_ORIGINAL_RELEASE_DATE, MTP_PROPERTY_DURATION, MTP_PROPERTY_GENRE,
+  MTP_PROPERTY_COMPOSER
+};
+
+int IMtpDatabase::VIDEO_PROPERTIES[15] = {
+  // NOTE must match FILE_PROPERTIES above
+  MTP_PROPERTY_STORAGE_ID, MTP_PROPERTY_OBJECT_FORMAT, MTP_PROPERTY_PROTECTION_STATUS,
+  MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, MTP_PROPERTY_DATE_MODIFIED,
+  MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, MTP_PROPERTY_NAME,
+  MTP_PROPERTY_DISPLAY_NAME, MTP_PROPERTY_DATE_ADDED,
+
+  // video specific properties
+  MTP_PROPERTY_ARTIST, MTP_PROPERTY_ALBUM_NAME, MTP_PROPERTY_DURATION, MTP_PROPERTY_DESCRIPTION
+};
+
+int IMtpDatabase::IMAGE_PROPERTIES[12] = {
+  // NOTE must match FILE_PROPERTIES above
+  MTP_PROPERTY_STORAGE_ID, MTP_PROPERTY_OBJECT_FORMAT, MTP_PROPERTY_PROTECTION_STATUS,
+  MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, MTP_PROPERTY_DATE_MODIFIED,
+  MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, MTP_PROPERTY_NAME,
+  MTP_PROPERTY_DISPLAY_NAME, MTP_PROPERTY_DATE_ADDED,
+
+  // image specific properties
+  MTP_PROPERTY_DESCRIPTION
+};
+
+int IMtpDatabase::ALL_PROPERTIES[25] = {
+  // NOTE must match FILE_PROPERTIES above
+  MTP_PROPERTY_STORAGE_ID, MTP_PROPERTY_OBJECT_FORMAT, MTP_PROPERTY_PROTECTION_STATUS,
+  MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, MTP_PROPERTY_DATE_MODIFIED,
+  MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, MTP_PROPERTY_NAME,
+  MTP_PROPERTY_DISPLAY_NAME, MTP_PROPERTY_DATE_ADDED,
+
+  // image specific properties
+  MTP_PROPERTY_DESCRIPTION,
+
+  // audio specific properties
+  MTP_PROPERTY_ARTIST, MTP_PROPERTY_ALBUM_NAME, MTP_PROPERTY_ALBUM_ARTIST, MTP_PROPERTY_TRACK,
+  MTP_PROPERTY_ORIGINAL_RELEASE_DATE, MTP_PROPERTY_DURATION, MTP_PROPERTY_GENRE,
+  MTP_PROPERTY_COMPOSER,
+
+  // video specific properties
+  MTP_PROPERTY_ARTIST, MTP_PROPERTY_ALBUM_NAME, MTP_PROPERTY_DURATION, MTP_PROPERTY_DESCRIPTION,
+
+  // image specific properties
+  MTP_PROPERTY_DESCRIPTION
+};
+
+int IMtpDatabase::SUPPORTED_PLAYBACK_FORMATS[26] = { SUPPORTED_PLAYBACK_FORMAT_UNDEFINED,
+													 SUPPORTED_PLAYBACK_FORMAT_ASSOCIATION,
+													 SUPPORTED_PLAYBACK_FORMAT_TEXT,
+													 SUPPORTED_PLAYBACK_FORMAT_HTML,
+													 SUPPORTED_PLAYBACK_FORMAT_WAV,
+													 SUPPORTED_PLAYBACK_FORMAT_MP3,
+													 SUPPORTED_PLAYBACK_FORMAT_MPEG,
+													 SUPPORTED_PLAYBACK_FORMAT_EXIF_JPEG,
+													 SUPPORTED_PLAYBACK_FORMAT_TIFF_EP,
+													 SUPPORTED_PLAYBACK_FORMAT_BMP,
+													 SUPPORTED_PLAYBACK_FORMAT_GIF,
+													 SUPPORTED_PLAYBACK_FORMAT_JFIF,
+													 SUPPORTED_PLAYBACK_FORMAT_PNG,
+													 SUPPORTED_PLAYBACK_FORMAT_TIFF,
+													 SUPPORTED_PLAYBACK_FORMAT_WMA,
+													 SUPPORTED_PLAYBACK_FORMAT_OGG,
+													 SUPPORTED_PLAYBACK_FORMAT_AAC,
+													 SUPPORTED_PLAYBACK_FORMAT_MP4_CONTAINER,
+													 SUPPORTED_PLAYBACK_FORMAT_MP2,
+													 SUPPORTED_PLAYBACK_FORMAT_3GP_CONTAINER,
+													 SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_AV_PLAYLIST,
+													 SUPPORTED_PLAYBACK_FORMAT_WPL_PLAYLIST,
+													 SUPPORTED_PLAYBACK_FORMAT_M3U_PLAYLIST,
+													 SUPPORTED_PLAYBACK_FORMAT_PLS_PLAYLIST,
+													 SUPPORTED_PLAYBACK_FORMAT_XML_DOCUMENT,
+													 SUPPORTED_PLAYBACK_FORMAT_FLAC };
+
+MtpObjectHandle IMtpDatabase::beginSendObject(const char* path, MtpObjectFormat format,
+											  MtpObjectHandle parent, MtpStorageID storageID,
+											  uint64_t size, time_t modified) {
+  if (storagemap.find(storageID) == storagemap.end()) return kInvalidObjectHandle;
+  return storagemap[storageID]->beginSendObject(path, format, parent, size, modified);
+}
+
+void IMtpDatabase::endSendObject(const char* path, MtpObjectHandle handle, MtpObjectFormat format,
+								 bool succeeded) {
+  MTPD("endSendObject() %s\n", path);
+  if (!succeeded) {
+	MTPE("endSendObject() failed, unlinking %s\n", path);
+	unlink(path);
+  }
+  std::map<int, MtpStorage*>::iterator storit;
+  for (storit = storagemap.begin(); storit != storagemap.end(); storit++)
+	storit->second->endSendObject(path, handle, format, succeeded);
+}
+
+void IMtpDatabase::createDB(MtpStorage* storage, MtpStorageID storageID) {
+  storagemap[storageID] = storage;
+  storage->createDB();
+}
+
+void IMtpDatabase::destroyDB(MtpStorageID storageID) {
+  MtpStorage* storage = storagemap[storageID];
+  storagemap.erase(storageID);
+  delete storage;
+}
+
+MtpObjectHandleList* IMtpDatabase::getObjectList(MtpStorageID storageID,
+												 __attribute__((unused)) MtpObjectFormat format,
+												 MtpObjectHandle parent) {
+  MTPD("IMtpDatabase::getObjectList::storageID: %d\n", storageID);
+  MtpObjectHandleList* list = storagemap[storageID]->getObjectList(storageID, parent);
+  MTPD("IMtpDatabase::getObjectList::list size: %d\n", list->size());
+  return list;
+}
+
+int IMtpDatabase::getNumObjects(MtpStorageID storageID,
+								__attribute__((unused)) MtpObjectFormat format,
+								MtpObjectHandle parent) {
+  MtpObjectHandleList* list = storagemap[storageID]->getObjectList(storageID, parent);
+  int size = list->size();
+  delete list;
+  return size;
+}
+
+MtpObjectFormatList* IMtpDatabase::getSupportedPlaybackFormats() {
+  // This function tells the host PC which file formats the device supports
+  MtpObjectFormatList* list = new MtpObjectFormatList();
+  int length = sizeof(SUPPORTED_PLAYBACK_FORMATS) / sizeof(SUPPORTED_PLAYBACK_FORMATS[0]);
+  MTPD("IMtpDatabase::getSupportedPlaybackFormats length: %i\n", length);
+  for (int i = 0; i < length; i++) {
+	MTPD("supported playback format: %x\n", SUPPORTED_PLAYBACK_FORMATS[i]);
+	list->push_back(SUPPORTED_PLAYBACK_FORMATS[i]);
+  }
+  return list;
+}
+
+MtpObjectFormatList* IMtpDatabase::getSupportedCaptureFormats() {
+  // Android OS implementation of this function returns NULL
+  // so we are not implementing this function either.
+  MTPD(
+	  "IMtpDatabase::getSupportedCaptureFormats returning NULL (This is what Android does as "
+	  "well).\n");
+  return NULL;
+}
+
+MtpObjectPropertyList* IMtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) {
+  int* properties;
+  MtpObjectPropertyList* list = new MtpObjectPropertyList();
+  int length = 0;
+  switch (format) {
+	case MTP_FORMAT_MP3:
+	case MTP_FORMAT_WAV:
+	case MTP_FORMAT_WMA:
+	case MTP_FORMAT_OGG:
+	case MTP_FORMAT_AAC:
+	  properties = AUDIO_PROPERTIES;
+	  length = sizeof(AUDIO_PROPERTIES) / sizeof(AUDIO_PROPERTIES[0]);
+	  break;
+	case MTP_FORMAT_MPEG:
+	case MTP_FORMAT_3GP_CONTAINER:
+	case MTP_FORMAT_WMV:
+	  properties = VIDEO_PROPERTIES;
+	  length = sizeof(VIDEO_PROPERTIES) / sizeof(VIDEO_PROPERTIES[0]);
+	  break;
+	case MTP_FORMAT_EXIF_JPEG:
+	case MTP_FORMAT_GIF:
+	case MTP_FORMAT_PNG:
+	case MTP_FORMAT_BMP:
+	  properties = IMAGE_PROPERTIES;
+	  length = sizeof(IMAGE_PROPERTIES) / sizeof(IMAGE_PROPERTIES[0]);
+	  break;
+	case 0:
+	  properties = ALL_PROPERTIES;
+	  length = sizeof(ALL_PROPERTIES) / sizeof(ALL_PROPERTIES[0]);
+	  break;
+	default:
+	  properties = FILE_PROPERTIES;
+	  length = sizeof(FILE_PROPERTIES) / sizeof(FILE_PROPERTIES[0]);
+  }
+  MTPD("IMtpDatabase::getSupportedObjectProperties length is: %i, format: %x", length, format);
+  for (int i = 0; i < length; i++) {
+	MTPD("supported object property: %x\n", properties[i]);
+	list->push_back(properties[i]);
+  }
+  return list;
+}
+
+MtpDevicePropertyList* IMtpDatabase::getSupportedDeviceProperties() {
+  MtpDevicePropertyList* list = new MtpDevicePropertyList();
+  int length = sizeof(DEVICE_PROPERTIES) / sizeof(DEVICE_PROPERTIES[0]);
+  MTPD("IMtpDatabase::getSupportedDeviceProperties length was: %i\n", length);
+  for (int i = 0; i < length; i++) list->push_back(DEVICE_PROPERTIES[i]);
+  return list;
+}
+
+MtpResponseCode IMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle,
+													 MtpObjectProperty property,
+													 MtpDataPacket& packet) {
+  MTPD("IMtpDatabase::getObjectPropertyValue mtpid: %u, property: %x\n", handle, property);
+  int type;
+  MtpResponseCode result = MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+  MtpStorage::PropEntry prop;
+  if (!getObjectPropertyInfo(property, type)) {
+	MTPE("IMtpDatabase::getObjectPropertyValue returning MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED\n");
+	return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+  }
+  std::map<int, MtpStorage*>::iterator storit;
+  for (storit = storagemap.begin(); storit != storagemap.end(); storit++) {
+	if (storit->second->getObjectPropertyValue(handle, property, prop) == 0) {
+	  result = MTP_RESPONSE_OK;
+	  break;
+	}
+  }
+
+  if (result != MTP_RESPONSE_OK) {
+	MTPE("IMtpDatabase::getObjectPropertyValue unable to locate handle: %u\n", handle);
+	return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+  }
+
+  uint64_t longValue = prop.intvalue;
+  // special case date properties, which are strings to MTP
+  // but stored internally as a uint64
+  if (property == MTP_PROPERTY_DATE_MODIFIED || property == MTP_PROPERTY_DATE_ADDED) {
+	char date[20];
+	formatDateTime(longValue, date, sizeof(date));
+	packet.putString(date);
+	goto out;
+  }
+
+  switch (type) {
+	case MTP_TYPE_INT8:
+	  packet.putInt8(longValue);
+	  break;
+	case MTP_TYPE_UINT8:
+	  packet.putUInt8(longValue);
+	  break;
+	case MTP_TYPE_INT16:
+	  packet.putInt16(longValue);
+	  break;
+	case MTP_TYPE_UINT16:
+	  packet.putUInt16(longValue);
+	  break;
+	case MTP_TYPE_INT32:
+	  packet.putInt32(longValue);
+	  break;
+	case MTP_TYPE_UINT32:
+	  packet.putUInt32(longValue);
+	  break;
+	case MTP_TYPE_INT64:
+	  packet.putInt64(longValue);
+	  break;
+	case MTP_TYPE_UINT64:
+	  packet.putUInt64(longValue);
+	  break;
+	case MTP_TYPE_INT128:
+	  packet.putInt128(longValue);
+	  break;
+	case MTP_TYPE_UINT128:
+	  packet.putUInt128(longValue);
+	  break;
+	case MTP_TYPE_STR: {
+	  /*std::string stringValue = (string)stringValuesArray[0];
+	  if (stringValue) {
+		const char* str = stringValue.c_str();
+		if (str == NULL) {
+		  return MTP_RESPONSE_GENERAL_ERROR;
+		}
+		packet.putString(str);
+	  } else {
+		packet.putEmptyString();
+	  }*/
+	  packet.putString(prop.strvalue.c_str());
+	  MTPD("MTP_TYPE_STR: %x = %s\n", prop.property, prop.strvalue.c_str());
+	  // MTPE("STRING unsupported type in getObjectPropertyValue\n");
+	  // result = MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
+	  break;
+	}
+	default:
+	  MTPE("unsupported type in getObjectPropertyValue\n");
+	  result = MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
+  }
+out:
+  return result;
+}
+
+MtpResponseCode IMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle,
+													 MtpObjectProperty property,
+													 MtpDataPacket& packet) {
+  int type;
+  MTPD("IMtpDatabase::setObjectPropertyValue start\n");
+  if (!getObjectPropertyInfo(property, type)) {
+	MTPE("IMtpDatabase::setObjectPropertyValue returning MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED\n");
+	return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+  }
+  MTPD("IMtpDatabase::setObjectPropertyValue continuing\n");
+
+  int8_t int8_t_value;
+  uint8_t uint8_t_value;
+  int16_t int16_t_value;
+  uint16_t uint16_t_value;
+  int32_t int32_t_value;
+  uint32_t uint32_t_value;
+  int64_t int64_t_value;
+  uint64_t uint64_t_value;
+  std::string stringValue;
+
+  switch (type) {
+	case MTP_TYPE_INT8:
+	  MTPD("int8\n");
+	  packet.getInt8(int8_t_value);
+	  break;
+	case MTP_TYPE_UINT8:
+	  MTPD("uint8\n");
+	  packet.getUInt8(uint8_t_value);
+	  break;
+	case MTP_TYPE_INT16:
+	  MTPD("int16\n");
+	  packet.getInt16(int16_t_value);
+	  break;
+	case MTP_TYPE_UINT16:
+	  MTPD("uint16\n");
+	  packet.getUInt16(uint16_t_value);
+	  break;
+	case MTP_TYPE_INT32:
+	  MTPD("int32\n");
+	  packet.getInt32(int32_t_value);
+	  break;
+	case MTP_TYPE_UINT32:
+	  MTPD("uint32\n");
+	  packet.getUInt32(uint32_t_value);
+	  break;
+	case MTP_TYPE_INT64:
+	  MTPD("int64\n");
+	  packet.getInt64(int64_t_value);
+	  break;
+	case MTP_TYPE_UINT64:
+	  MTPD("uint64\n");
+	  packet.getUInt64(uint64_t_value);
+	  break;
+	case MTP_TYPE_STR: {
+	  MTPD("string\n");
+	  MtpStringBuffer buffer;
+	  packet.getString(buffer);
+	  stringValue = buffer;
+	  break;
+	}
+	default:
+	  MTPE("IMtpDatabase::setObjectPropertyValue unsupported type %i in getObjectPropertyValue\n",
+		   type);
+	  return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
+  }
+
+  int result = MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+
+  switch (property) {
+	case MTP_PROPERTY_OBJECT_FILE_NAME: {
+	  MTPD("IMtpDatabase::setObjectPropertyValue renaming file, handle: %d, new name: '%s'\n",
+		   handle, stringValue.c_str());
+	  std::map<int, MtpStorage*>::iterator storit;
+	  for (storit = storagemap.begin(); storit != storagemap.end(); storit++) {
+		if (storit->second->renameObject(handle, stringValue) == 0) {
+		  MTPD("MTP_RESPONSE_OK\n");
+		  result = MTP_RESPONSE_OK;
+		  break;
+		}
+	  }
+	} break;
+
+	default:
+	  MTPE("IMtpDatabase::setObjectPropertyValue property %x not supported.\n", property);
+	  result = MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+  }
+  MTPD("IMtpDatabase::setObjectPropertyValue returning %d\n", result);
+  return result;
+}
+
+MtpResponseCode IMtpDatabase::getDevicePropertyValue(MtpDeviceProperty property,
+													 MtpDataPacket& packet) {
+  int type, result = 0;
+  char prop_value[PROPERTY_VALUE_MAX];
+  MTPD("property %s\n", MtpDebug::getDevicePropCodeName(property));
+  if (!getDevicePropertyInfo(property, type)) {
+	MTPE("IMtpDatabase::getDevicePropertyValue MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED\n");
+	return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+  }
+  MTPD("property %s\n", MtpDebug::getDevicePropCodeName(property));
+  MTPD("property %x\n", property);
+  MTPD("MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME %x\n", MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME);
+  switch (property) {
+	case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
+	case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
+	  result = MTP_RESPONSE_OK;
+	  break;
+	default: {
+	  MTPE("IMtpDatabase::getDevicePropertyValue property %x not supported\n", property);
+	  result = MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+	  break;
+	}
+  }
+
+  if (result != MTP_RESPONSE_OK) {
+	MTPD("MTP_REPONSE_OK NOT OK\n");
+	return result;
+  }
+
+  long longValue = 0;
+  property_get("ro.build.product", prop_value, "unknown manufacturer");
+  switch (type) {
+	case MTP_TYPE_INT8: {
+	  MTPD("MTP_TYPE_INT8\n");
+	  packet.putInt8(longValue);
+	  break;
+	}
+	case MTP_TYPE_UINT8: {
+	  MTPD("MTP_TYPE_UINT8\n");
+	  packet.putUInt8(longValue);
+	  break;
+	}
+	case MTP_TYPE_INT16: {
+	  MTPD("MTP_TYPE_INT16\n");
+	  packet.putInt16(longValue);
+	  break;
+	}
+	case MTP_TYPE_UINT16: {
+	  MTPD("MTP_TYPE_UINT16\n");
+	  packet.putUInt16(longValue);
+	  break;
+	}
+	case MTP_TYPE_INT32: {
+	  MTPD("MTP_TYPE_INT32\n");
+	  packet.putInt32(longValue);
+	  break;
+	}
+	case MTP_TYPE_UINT32: {
+	  MTPD("MTP_TYPE_UINT32\n");
+	  packet.putUInt32(longValue);
+	  break;
+	}
+	case MTP_TYPE_INT64: {
+	  MTPD("MTP_TYPE_INT64\n");
+	  packet.putInt64(longValue);
+	  break;
+	}
+	case MTP_TYPE_UINT64: {
+	  MTPD("MTP_TYPE_UINT64\n");
+	  packet.putUInt64(longValue);
+	  break;
+	}
+	case MTP_TYPE_INT128: {
+	  MTPD("MTP_TYPE_INT128\n");
+	  packet.putInt128(longValue);
+	  break;
+	}
+	case MTP_TYPE_UINT128: {
+	  MTPD("MTP_TYPE_UINT128\n");
+	  packet.putInt128(longValue);
+	  break;
+	}
+	case MTP_TYPE_STR: {
+	  MTPD("MTP_TYPE_STR\n");
+	  char* str = prop_value;
+	  packet.putString(str);
+	  break;
+	}
+	default:
+	  MTPE("IMtpDatabase::getDevicePropertyValue unsupported type %i in getDevicePropertyValue\n",
+		   type);
+	  return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT;
+  }
+
+  return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode IMtpDatabase::setDevicePropertyValue(__attribute__((unused))
+													 MtpDeviceProperty property,
+													 __attribute__((unused))
+													 MtpDataPacket& packet) {
+  MTPE("IMtpDatabase::setDevicePropertyValue not implemented, returning 0\n");
+  return 0;
+}
+
+MtpResponseCode IMtpDatabase::resetDeviceProperty(__attribute__((unused))
+												  MtpDeviceProperty property) {
+  MTPE("IMtpDatabase::resetDeviceProperty not implemented, returning -1\n");
+  return -1;
+}
+
+MtpResponseCode IMtpDatabase::getObjectPropertyList(MtpObjectHandle handle, uint32_t format,
+													uint32_t property, int groupCode, int depth,
+													MtpDataPacket& packet) {
+  MTPD("getObjectPropertyList()\n");
+  MTPD("property: %x\n", property);
+  std::map<int, MtpStorage*>::iterator storit;
+  for (storit = storagemap.begin(); storit != storagemap.end(); storit++) {
+	MTPD("IMtpDatabase::getObjectPropertyList calling getObjectPropertyList\n");
+	if (storit->second->getObjectPropertyList(handle, format, property, groupCode, depth, packet) ==
+		0) {
+	  MTPD("MTP_RESPONSE_OK\n");
+	  return MTP_RESPONSE_OK;
+	}
+  }
+  MTPE("IMtpDatabase::getObjectPropertyList MTP_RESPONSE_INVALID_OBJECT_HANDLE %i\n", handle);
+  return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+}
+
+MtpResponseCode IMtpDatabase::getObjectInfo(MtpObjectHandle handle, MtpObjectInfo& info) {
+  std::map<int, MtpStorage*>::iterator storit;
+  for (storit = storagemap.begin(); storit != storagemap.end(); storit++) {
+	if (storit->second->getObjectInfo(handle, info) == 0) {
+	  MTPD("MTP_RESPONSE_OK\n");
+	  return MTP_RESPONSE_OK;
+	}
+  }
+  MTPE("IMtpDatabase::getObjectInfo MTP_RESPONSE_INVALID_OBJECT_HANDLE %i\n", handle);
+  return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+}
+
+void* IMtpDatabase::getThumbnail(__attribute__((unused)) MtpObjectHandle handle,
+								 __attribute__((unused)) size_t& outThumbSize) {
+  MTPE("IMtpDatabase::getThumbnail not implemented, returning 0\n");
+  return 0;
+}
+
+MtpResponseCode IMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
+												MtpStringBuffer& outFilePath,
+												int64_t& outFileLength,
+												MtpObjectFormat& outFormat) {
+  std::map<int, MtpStorage*>::iterator storit;
+  for (storit = storagemap.begin(); storit != storagemap.end(); storit++) {
+	MTPD("IMtpDatabase::getObjectFilePath calling getObjectFilePath\n");
+	if (storit->second->getObjectFilePath(handle, outFilePath, outFileLength, outFormat) == 0) {
+	  MTPD("MTP_RESPONSE_OK\n");
+	  return MTP_RESPONSE_OK;
+	}
+  }
+  MTPE("IMtpDatabase::getObjectFilePath MTP_RESPONSE_INVALID_OBJECT_HANDLE %i\n", handle);
+  return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+}
+
+// MtpResponseCode IMtpDatabase::deleteFile(MtpObjectHandle handle) {
+//	MTPD("IMtpDatabase::deleteFile\n");
+//	std::map<int, MtpStorage*>::iterator storit;
+//	for (storit = storagemap.begin(); storit != storagemap.end(); storit++) {
+//		if (storit->second->deleteFile(handle) == 0) {
+//			MTPD("MTP_RESPONSE_OK\n");
+//			return MTP_RESPONSE_OK;
+//		}
+//	}
+//	MTPE("IMtpDatabase::deleteFile MTP_RESPONSE_INVALID_OBJECT_HANDLE %i\n", handle);
+//	return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+// }
+
+struct PropertyTableEntry {
+  MtpObjectProperty property;
+  int type;
+};
+
+static const PropertyTableEntry kObjectPropertyTable[] = {
+  { MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32 },
+  { MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16 },
+  { MTP_PROPERTY_PROTECTION_STATUS, MTP_TYPE_UINT16 },
+  { MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64 },
+  { MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR },
+  { MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR },
+  { MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 },
+  { MTP_PROPERTY_PERSISTENT_UID, MTP_TYPE_UINT128 },
+  { MTP_PROPERTY_NAME, MTP_TYPE_STR },
+  { MTP_PROPERTY_DISPLAY_NAME, MTP_TYPE_STR },
+  { MTP_PROPERTY_DATE_ADDED, MTP_TYPE_STR },
+  { MTP_PROPERTY_ARTIST, MTP_TYPE_STR },
+  { MTP_PROPERTY_ALBUM_NAME, MTP_TYPE_STR },
+  { MTP_PROPERTY_ALBUM_ARTIST, MTP_TYPE_STR },
+  { MTP_PROPERTY_TRACK, MTP_TYPE_UINT16 },
+  { MTP_PROPERTY_ORIGINAL_RELEASE_DATE, MTP_TYPE_STR },
+  { MTP_PROPERTY_GENRE, MTP_TYPE_STR },
+  { MTP_PROPERTY_COMPOSER, MTP_TYPE_STR },
+  { MTP_PROPERTY_DURATION, MTP_TYPE_UINT32 },
+  { MTP_PROPERTY_DESCRIPTION, MTP_TYPE_STR },
+};
+
+static const PropertyTableEntry kDevicePropertyTable[] = {
+  { MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, MTP_TYPE_STR },
+  { MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, MTP_TYPE_STR },
+  { MTP_DEVICE_PROPERTY_IMAGE_SIZE, MTP_TYPE_STR },
+};
+
+bool IMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
+  int count = sizeof(kObjectPropertyTable) / sizeof(kObjectPropertyTable[0]);
+  const PropertyTableEntry* entry = kObjectPropertyTable;
+  MTPD("IMtpDatabase::getObjectPropertyInfo size is: %i\n", count);
+  for (int i = 0; i < count; i++, entry++) {
+	if (entry->property == property) {
+	  type = entry->type;
+	  return true;
+	}
+  }
+  return false;
+}
+
+bool IMtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) {
+  int count = sizeof(kDevicePropertyTable) / sizeof(kDevicePropertyTable[0]);
+  const PropertyTableEntry* entry = kDevicePropertyTable;
+  MTPD("IMtpDatabase::getDevicePropertyInfo count is: %i\n", count);
+  for (int i = 0; i < count; i++, entry++) {
+	if (entry->property == property) {
+	  type = entry->type;
+	  MTPD("type: %x\n", type);
+	  return true;
+	}
+  }
+  return false;
+}
+
+MtpObjectHandleList* IMtpDatabase::getObjectReferences(MtpObjectHandle handle) {
+  // call function and place files with associated handles into int array
+  MTPD(
+	  "IMtpDatabase::getObjectReferences returning null, this seems to be what Android always "
+	  "does.\n");
+  MTPD("handle: %d\n", handle);
+  // Windows + Android seems to always return a NULL in this function, c == null path
+  // The way that this is handled in Android then is to do this:
+  return NULL;
+}
+
+MtpResponseCode IMtpDatabase::setObjectReferences(__attribute__((unused)) MtpObjectHandle handle,
+												  __attribute__((unused))
+												  MtpObjectHandleList* references) {
+  MTPE("IMtpDatabase::setObjectReferences not implemented, returning 0\n");
+  return 0;
+}
+
+MtpProperty* IMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property,
+												 MtpObjectFormat format) {
+  MTPD("IMtpDatabase::getObjectPropertyDesc start\n");
+  MtpProperty* result = NULL;
+  switch (property) {
+	case MTP_PROPERTY_OBJECT_FORMAT:
+	  // use format as default value
+	  result = new MtpProperty(property, MTP_TYPE_UINT16, false, format);
+	  break;
+	case MTP_PROPERTY_PROTECTION_STATUS:
+	case MTP_PROPERTY_TRACK:
+	  result = new MtpProperty(property, MTP_TYPE_UINT16);
+	  break;
+	case MTP_PROPERTY_STORAGE_ID:
+	case MTP_PROPERTY_PARENT_OBJECT:
+	case MTP_PROPERTY_DURATION:
+	  result = new MtpProperty(property, MTP_TYPE_UINT32);
+	  break;
+	case MTP_PROPERTY_OBJECT_SIZE:
+	  result = new MtpProperty(property, MTP_TYPE_UINT64);
+	  break;
+	case MTP_PROPERTY_PERSISTENT_UID:
+	  result = new MtpProperty(property, MTP_TYPE_UINT128);
+	  break;
+	case MTP_PROPERTY_NAME:
+	case MTP_PROPERTY_DISPLAY_NAME:
+	case MTP_PROPERTY_ARTIST:
+	case MTP_PROPERTY_ALBUM_NAME:
+	case MTP_PROPERTY_ALBUM_ARTIST:
+	case MTP_PROPERTY_GENRE:
+	case MTP_PROPERTY_COMPOSER:
+	case MTP_PROPERTY_DESCRIPTION:
+	  result = new MtpProperty(property, MTP_TYPE_STR);
+	  break;
+	case MTP_PROPERTY_DATE_MODIFIED:
+	case MTP_PROPERTY_DATE_ADDED:
+	case MTP_PROPERTY_ORIGINAL_RELEASE_DATE:
+	  result = new MtpProperty(property, MTP_TYPE_STR);
+	  result->setFormDateTime();
+	  break;
+	case MTP_PROPERTY_OBJECT_FILE_NAME:
+	  // We allow renaming files and folders
+	  result = new MtpProperty(property, MTP_TYPE_STR, true);
+	  break;
+  }
+  return result;
+}
+
+MtpProperty* IMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) {
+  MtpProperty* result = NULL;
+  bool writable = false;
+  switch (property) {
+	case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
+	case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
+	  writable = true;
+	  // fall through
+	case MTP_DEVICE_PROPERTY_IMAGE_SIZE:
+	  result = new MtpProperty(property, MTP_TYPE_STR, writable);
+
+	  // get current value
+	  // TODO: add actual values
+	  result->setCurrentValue(0);
+	  result->setDefaultValue(0);
+	  break;
+  }
+
+  return result;
+}
+
+void IMtpDatabase::sessionStarted() {
+  MTPD("IMtpDatabase::sessionStarted not implemented or does nothing, returning\n");
+  return;
+}
+
+void IMtpDatabase::sessionEnded() {
+  MTPD("IMtpDatabase::sessionEnded not implemented or does nothing, returning\n");
+  return;
+}
+
+// ----------------------------------------------------------------------------
+
+void IMtpDatabase::lockMutex(void) {
+  std::map<int, MtpStorage*>::iterator storit;
+  for (storit = storagemap.begin(); storit != storagemap.end(); storit++) {
+	storit->second->lockMutex(0);
+  }
+}
+
+void IMtpDatabase::unlockMutex(void) {
+  std::map<int, MtpStorage*>::iterator storit;
+  for (storit = storagemap.begin(); storit != storagemap.end(); storit++) {
+	storit->second->unlockMutex(0);
+  }
+}
+
+MtpResponseCode IMtpDatabase::beginDeleteObject(MtpObjectHandle handle) {
+  MTPD("IMtoDatabase::beginDeleteObject handle: %u\n", handle);
+  std::map<int, MtpStorage*>::iterator storit;
+  for (storit = storagemap.begin(); storit != storagemap.end(); storit++) {
+	if (storit->second->deleteFile(handle) == 0) {
+	  MTPD("IMtpDatabase::beginDeleteObject::MTP_RESPONSE_OK\n");
+	  return MTP_RESPONSE_OK;
+	}
+  }
+  return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+}
+
+void IMtpDatabase::endDeleteObject(MtpObjectHandle handle __unused, bool succeeded __unused) {
+  MTPD("IMtpDatabase::endDeleteObject not implemented yet\n");
+}
+
+void IMtpDatabase::rescanFile(const char* path __unused, MtpObjectHandle handle __unused,
+							  MtpObjectFormat format __unused) {
+  MTPD("IMtpDatabase::rescanFile not implemented yet\n");
+}
+
+MtpResponseCode IMtpDatabase::beginMoveObject(MtpObjectHandle handle __unused,
+											  MtpObjectHandle newParent __unused,
+											  MtpStorageID newStorage __unused) {
+  MTPD("IMtpDatabase::beginMoveObject not implemented yet\n");
+  return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+}
+
+void IMtpDatabase::endMoveObject(MtpObjectHandle oldParent __unused,
+								 MtpObjectHandle newParent __unused,
+								 MtpStorageID oldStorage __unused, MtpStorageID newStorage __unused,
+								 MtpObjectHandle handle __unused, bool succeeded __unused) {
+  MTPD("IMtpDatabase::endMoveObject not implemented yet\n");
+}
+
+MtpResponseCode IMtpDatabase::beginCopyObject(MtpObjectHandle handle __unused,
+											  MtpObjectHandle newParent __unused,
+											  MtpStorageID newStorage __unused) {
+  MTPD("IMtpDatabase::beginCopyObject not implemented yet\n");
+  return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+}
+
+void IMtpDatabase::endCopyObject(MtpObjectHandle handle __unused, bool succeeded __unused) {
+  MTPD("IMtpDatabase::endCopyObject not implemented yet\n");
+}
diff --git a/mtp/ffs/mtp_MtpDatabase.hpp b/mtp/ffs/mtp_MtpDatabase.hpp
new file mode 100755
index 0000000..4b18898
--- /dev/null
+++ b/mtp/ffs/mtp_MtpDatabase.hpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *		http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++
+ */
+
+#ifndef MTP_MTPDATABASE_HPP
+#define MTP_MTPDATABASE_HPP
+
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <map>
+#include <string>
+#include <deque>
+
+#include "MtpDatabase.h"
+#include "MtpDataPacket.h"
+#include "MtpObjectInfo.h"
+#include "MtpProperty.h"
+#include "MtpStringBuffer.h"
+#include "MtpUtils.h"
+#include "mtp.h"
+
+class IMtpDatabase : public MtpDatabase {
+private:
+	int* getSupportedObjectProperties(int format);
+
+	static int FILE_PROPERTIES[10];
+	static int DEVICE_PROPERTIES[3];
+	static int AUDIO_PROPERTIES[19];
+	static int VIDEO_PROPERTIES[15];
+	static int IMAGE_PROPERTIES[12];
+	static int ALL_PROPERTIES[25];
+	static int SUPPORTED_PLAYBACK_FORMATS[26];
+	int storagenum;
+	int count;
+	std::string lastfile;
+	std::map<int, MtpStorage*> storagemap;
+	void countDirs(std::string path);
+	int readParentDirs(std::string path, int storageID);
+
+public:
+									IMtpDatabase();
+	virtual							~IMtpDatabase();
+
+	virtual void					createDB(MtpStorage* storage, MtpStorageID storageID);
+	virtual void					destroyDB(MtpStorageID storageID);
+	virtual MtpObjectHandle			beginSendObject(const char* path,
+											MtpObjectFormat format,
+											MtpObjectHandle parent,
+											MtpStorageID storageID,
+											uint64_t size,
+											time_t modified);
+
+	virtual void					endSendObject(const char* path,
+											MtpObjectHandle handle,
+											MtpObjectFormat format,
+											bool succeeded);
+
+	virtual MtpObjectHandleList*	getObjectList(MtpStorageID storageID,
+									MtpObjectFormat format,
+									MtpObjectHandle parent);
+
+	virtual int						getNumObjects(MtpStorageID storageID,
+											MtpObjectFormat format,
+											MtpObjectHandle parent);
+
+	// callee should delete[] the results from these
+	// results can be NULL
+	virtual MtpObjectFormatList*	getSupportedPlaybackFormats();
+	virtual MtpObjectFormatList*	getSupportedCaptureFormats();
+	virtual MtpObjectPropertyList*	getSupportedObjectProperties(MtpObjectFormat format);
+	virtual MtpDevicePropertyList*	getSupportedDeviceProperties();
+
+	virtual MtpResponseCode			getObjectPropertyValue(MtpObjectHandle handle,
+											MtpObjectProperty property,
+											MtpDataPacket& packet);
+
+	virtual MtpResponseCode			setObjectPropertyValue(MtpObjectHandle handle,
+											MtpObjectProperty property,
+											MtpDataPacket& packet);
+
+	virtual MtpResponseCode			getDevicePropertyValue(MtpDeviceProperty property,
+											MtpDataPacket& packet);
+
+	virtual MtpResponseCode			setDevicePropertyValue(MtpDeviceProperty property,
+											MtpDataPacket& packet);
+
+	virtual MtpResponseCode			resetDeviceProperty(MtpDeviceProperty property);
+
+	virtual MtpResponseCode			getObjectPropertyList(MtpObjectHandle handle,
+											uint32_t format, uint32_t property,
+											int groupCode, int depth,
+											MtpDataPacket& packet);
+
+	virtual MtpResponseCode			getObjectInfo(MtpObjectHandle handle,
+											MtpObjectInfo& info);
+
+	virtual void*					getThumbnail(MtpObjectHandle handle, size_t& outThumbSize);
+
+	virtual MtpResponseCode			getObjectFilePath(MtpObjectHandle handle,
+											MtpStringBuffer& outFilePath,
+											int64_t& outFileLength,
+											MtpObjectFormat& outFormat);
+	// virtual MtpResponseCode		   deleteFile(MtpObjectHandle handle);
+
+	bool							getObjectPropertyInfo(MtpObjectProperty property, int& type);
+	bool							getDevicePropertyInfo(MtpDeviceProperty property, int& type);
+
+	virtual MtpObjectHandleList*	getObjectReferences(MtpObjectHandle handle);
+
+	virtual MtpResponseCode			setObjectReferences(MtpObjectHandle handle,
+											MtpObjectHandleList* references);
+
+	virtual MtpProperty*			getObjectPropertyDesc(MtpObjectProperty property,
+											MtpObjectFormat format);
+
+	virtual MtpProperty*			getDevicePropertyDesc(MtpDeviceProperty property);
+
+	virtual void					sessionStarted();
+
+	virtual void					sessionEnded();
+	virtual void					lockMutex();
+	virtual void					unlockMutex();
+
+	virtual MtpResponseCode			beginDeleteObject(MtpObjectHandle handle);
+	virtual void					endDeleteObject(MtpObjectHandle handle, bool succeeded);
+	// Called to rescan a file, such as after an edit.
+	virtual void					rescanFile(const char* path,
+											MtpObjectHandle handle,
+											MtpObjectFormat format);
+	virtual MtpResponseCode			beginMoveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+											MtpStorageID newStorage);
+
+	virtual void					endMoveObject(MtpObjectHandle oldParent, MtpObjectHandle newParent,
+											MtpStorageID oldStorage, MtpStorageID newStorage,
+											MtpObjectHandle handle, bool succeeded);
+
+	virtual MtpResponseCode			beginCopyObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+											MtpStorageID newStorage);
+	virtual void					endCopyObject(MtpObjectHandle handle, bool succeeded);
+};
+#endif
diff --git a/mtp/ffs/mtp_MtpServer.cpp b/mtp/ffs/mtp_MtpServer.cpp
new file mode 100755
index 0000000..ce890d1
--- /dev/null
+++ b/mtp/ffs/mtp_MtpServer.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Additional Copyright (C) 2018 TeamWin
+ */
+
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <vector>
+#include <utils/threads.h>
+#include <pthread.h>
+#include <cutils/properties.h>
+
+#include "mtp_MtpServer.hpp"
+#include "MtpServer.h"
+#include "MtpStorage.h"
+#include "MtpDebug.h"
+#include "MtpDescriptors.h"
+#include "MtpMessage.hpp"
+#include "mtp_MtpDatabase.hpp"
+
+#include <string>
+
+void twmtp_MtpServer::set_device_info() {
+	char property[512];
+	property_get("ro.build.product", property, "unknown manufacturer");
+	mtpinfo.deviceInfoManufacturer = MtpStringBuffer(property);
+	property_get("ro.product.model", property, "unknown model");
+	mtpinfo.deviceInfoModel = MtpStringBuffer(property);
+	mtpinfo.deviceInfoDeviceVersion = MtpStringBuffer("None");
+	property_get("ro.serialno", property, "unknown serial number");
+	mtpinfo.deviceInfoSerialNumber = MtpStringBuffer(property);
+}
+
+void twmtp_MtpServer::start()
+{
+	int controlFd = 0;
+
+	usePtp =  false;
+	IMtpDatabase* mtpdb = new IMtpDatabase();
+	MTPD("launching server\n");
+		/* Sleep for a bit before we open the MTP USB device because some
+		 * devices are not ready due to the kernel not responding to our
+		 * sysfs requests right away.
+		 */
+		usleep(800000);
+#ifdef USB_MTP_DEVICE
+#define STRINGIFY(x) #x
+#define EXPAND(x) STRINGIFY(x)
+		const char* mtp_device = EXPAND(USB_MTP_DEVICE);
+		MTPI("Using '%s' for MTP device.\n", EXPAND(USB_MTP_DEVICE));
+#else
+		const char* mtp_device = "/dev/mtp_usb";
+#endif
+	bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0;
+	if (ffs_ok) {
+		MTPD("Opening FFS EPO\n");
+		controlFd = open(FFS_MTP_EP0, O_RDWR);
+	} else {
+		controlFd = open(mtp_device, O_WRONLY);
+	}
+	if (controlFd < 0) {
+		MTPE("could not open MTP driver, errno: %d\n", errno);
+		return;
+	}
+		MTPD("MTP fd: %d\n", controlFd);
+
+	server = new MtpServer(mtpdb,\
+		controlFd,\
+		usePtp,\
+		mtpinfo.deviceInfoManufacturer, \
+		mtpinfo.deviceInfoModel, \
+		mtpinfo.deviceInfoDeviceVersion, \
+		mtpinfo.deviceInfoSerialNumber);
+	refserver = server;
+	MTPI("created new mtpserver object\n");
+	add_storage();
+	MTPD("Starting add / remove mtppipe monitor thread\n");
+	pthread_t thread;
+	ThreadPtr mtpptr = &twmtp_MtpServer::mtppipe_thread;
+	PThreadPtr p = *(PThreadPtr*)&mtpptr;
+	pthread_create(&thread, NULL, p, this);
+	// This loop restarts the MTP process if the device is unplugged and replugged in
+	while (true) {
+		server->run();
+		usleep(800000);
+	}
+}
+
+void twmtp_MtpServer::set_storages(storages* mtpstorages) {
+	stores = mtpstorages;
+}
+
+void twmtp_MtpServer::cleanup()
+{
+	android::Mutex sMutex;
+	android::Mutex::Autolock autoLock(sMutex);
+
+	if (server) {
+		delete server;
+	} else {
+		MTPD("server is null in cleanup");
+	}
+}
+
+void twmtp_MtpServer::send_object_added(int handle)
+{
+	android::Mutex sMutex;
+	android::Mutex::Autolock autoLock(sMutex);
+
+	if (server)
+		server->sendObjectAdded(handle);
+	else
+		MTPD("server is null in send_object_added");
+}
+
+void twmtp_MtpServer::send_object_removed(int handle)
+{
+	android::Mutex sMutex;
+	android::Mutex::Autolock autoLock(sMutex);
+
+	if (server)
+		server->sendObjectRemoved(handle);
+	else
+		MTPD("server is null in send_object_removed");
+}
+
+void twmtp_MtpServer::add_storage()
+{
+	android::Mutex sMutex;
+	android::Mutex::Autolock autoLock(sMutex);
+
+	MTPD("twmtp_MtpServer::add_storage count of storage devices: %i\n", stores->size());
+	for (unsigned int i = 0; i < stores->size(); ++i) {
+			std::string pathStr = stores->at(i)->mount;
+
+			if (!pathStr.empty()) {
+				std::string descriptionStr = stores->at(i)->display;
+				int storageID = stores->at(i)->mtpid;
+				bool removable = false;
+				uint64_t maxFileSize = stores->at(i)->maxFileSize;
+				if (descriptionStr != "") {
+					MtpStorage* storage = new MtpStorage(storageID, &pathStr[0], &descriptionStr[0], removable, maxFileSize, refserver);
+					server->addStorage(storage);
+				}
+		}
+	}
+}
+
+void twmtp_MtpServer::remove_storage(int storageId)
+{
+	android::Mutex sMutex;
+	android::Mutex::Autolock autoLock(sMutex);
+
+	if (server) {
+		MtpStorage* storage = server->getStorage(storageId);
+		if (storage) {
+			MTPD("twmtp_MtpServer::remove_storage calling removeStorage\n");
+			server->removeStorage(storage);
+		}
+	} else
+		MTPD("server is null in remove_storage");
+	MTPD("twmtp_MtpServer::remove_storage DONE\n");
+}
+
+int twmtp_MtpServer::mtppipe_thread(void)
+{
+	if (mtp_read_pipe == -1) {
+		MTPD("mtppipe_thread exiting because mtp_read_pipe not set\n");
+		return 0;
+	}
+	MTPD("Starting twmtp_MtpServer::mtppipe_thread\n");
+	int read_count;
+	struct mtpmsg mtp_message;
+	while (1) {
+		read_count = ::read(mtp_read_pipe, &mtp_message, sizeof(mtp_message));
+		MTPD("read %i from mtppipe\n", read_count);
+		if (read_count == sizeof(mtp_message)) {
+			if (mtp_message.message_type == MTP_MESSAGE_ADD_STORAGE) {
+				MTPI("mtppipe add storage %i '%s'\n", mtp_message.storage_id, mtp_message.path);
+				if (mtp_message.storage_id) {
+					bool removable = false;
+					MtpStorage* storage = new MtpStorage(mtp_message.storage_id, &mtp_message.path[0], &mtp_message.display[0], removable, mtp_message.maxFileSize, refserver);
+					server->addStorage(storage);
+					MTPD("mtppipe done adding storage\n");
+				} else {
+					MTPE("Invalid storage ID %i specified\n", mtp_message.storage_id);
+				}
+			} else if (mtp_message.message_type == MTP_MESSAGE_REMOVE_STORAGE) {
+				MTPI("mtppipe remove storage %i\n", mtp_message.storage_id);
+				remove_storage(mtp_message.storage_id);
+				MTPD("mtppipe done removing storage\n");
+			} else {
+				MTPE("Unknown mtppipe message value: %i\n", mtp_message.message_type);
+			}
+		} else {
+			MTPE("twmtp_MtpServer::mtppipe_thread unexpected read_count %i\n", read_count);
+			close(mtp_read_pipe);
+			break;
+		}
+	}
+	MTPD("twmtp_MtpServer::mtppipe_thread closing\n");
+	return 0;
+}
+
+void twmtp_MtpServer::set_read_pipe(int pipe)
+{
+	mtp_read_pipe = pipe;
+}
diff --git a/mtp/ffs/mtp_MtpServer.hpp b/mtp/ffs/mtp_MtpServer.hpp
new file mode 100644
index 0000000..f2dc4cd
--- /dev/null
+++ b/mtp/ffs/mtp_MtpServer.hpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++
+ */
+
+#ifndef MTP_MTPSERVER_HPP
+#define MTP_MTPSERVER_HPP
+#include <utils/Log.h>
+
+#include <string>
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <utils/threads.h>
+
+#include "MtpServer.h"
+#include "MtpStorage.h"
+#include "mtp_MtpDatabase.hpp"
+
+typedef struct Storage {
+	std::string display;
+	std::string mount;
+	int mtpid;
+	uint64_t maxFileSize;
+} storage;
+
+typedef std::vector<storage*> storages;
+
+struct mtp_info {
+	MtpStringBuffer deviceInfoManufacturer;
+	MtpStringBuffer deviceInfoModel;
+	MtpStringBuffer deviceInfoDeviceVersion;
+	MtpStringBuffer deviceInfoSerialNumber;
+};
+
+class twmtp_MtpServer {
+	public:
+		void start();
+		void cleanup();
+		void send_object_added(int handle);
+		void send_object_removed(int handle);
+		void add_storage();
+		void remove_storage(int storageId);
+		void set_storages(storages* mtpstorages);
+		void set_read_pipe(int pipe);
+		storages *stores;
+		struct mtp_info mtpinfo;
+		void set_device_info();
+
+	private:
+		typedef int (twmtp_MtpServer::*ThreadPtr)(void);
+		typedef void* (*PThreadPtr)(void *);
+		int mtppipe_thread(void);
+		bool usePtp;
+		MtpServer* server;
+		MtpServer* refserver;
+		int mtp_read_pipe;
+                MtpStringBuffer deviceInfoManufacturer;
+                MtpStringBuffer deviceInfoModel;
+                MtpStringBuffer deviceInfoDeviceVersion;
+                MtpStringBuffer deviceInfoSerialNumber;
+};
+#endif
diff --git a/mtp/ffs/node.cpp b/mtp/ffs/node.cpp
new file mode 100644
index 0000000..7c57dc6
--- /dev/null
+++ b/mtp/ffs/node.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <limits.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <libgen.h>
+
+#include "btree.hpp"
+#include "mtp.h"
+#include "MtpDebug.h"
+
+
+Node::Node()
+	: handle(-1), parent(0), name("")
+{
+}
+
+Node::Node(MtpObjectHandle handle, MtpObjectHandle parent, const std::string& name)
+	: handle(handle), parent(parent), name(name)
+{
+				MTPD("handle: %d\n", handle);
+				MTPD("parent: %d\n", parent);
+				MTPD("name: %s\n", name.c_str());
+}
+
+void Node::rename(const std::string& newName) {
+	name = newName;
+	updateProperty(MTP_PROPERTY_OBJECT_FILE_NAME, 0, name.c_str(), MTP_TYPE_STR);
+	updateProperty(MTP_PROPERTY_NAME, 0, name.c_str(), MTP_TYPE_STR);
+	updateProperty(MTP_PROPERTY_DISPLAY_NAME, 0, name.c_str(), MTP_TYPE_STR);
+}
+
+MtpObjectHandle Node::Mtpid() const { return handle; }
+MtpObjectHandle Node::getMtpParentId() const { return parent; }
+const std::string& Node::getName() const { return name; }
+
+uint64_t Node::getIntProperty(MtpPropertyCode property) {
+	for (unsigned index = 0; index < mtpProp.size(); ++index) {
+		if (mtpProp[index].property == property)
+			return mtpProp[index].valueInt;
+	}
+	MTPE("Node::getIntProperty failed to find property %x, returning -1\n", (unsigned)property);
+	return -1;
+}
+
+const Node::mtpProperty& Node::getProperty(MtpPropertyCode property) {
+	static const mtpProperty dummyProp;
+	for (size_t i = 0; i < mtpProp.size(); ++i) {
+		if (mtpProp[i].property == property)
+			return mtpProp[i];
+	}
+	MTPE("Node::getProperty failed to find property %x, returning dummy property\n", (unsigned)property);
+	return dummyProp;
+}
+
+void Node::addProperty(MtpPropertyCode property, uint64_t valueInt, std::string valueStr, MtpDataType dataType) {
+//	MTPD("adding property: %lld, valueInt: %lld, valueStr: %s, dataType: %d\n", property, valueInt, valueStr.c_str(), dataType);
+	struct mtpProperty prop;
+	prop.property = property;
+	prop.valueInt = valueInt;
+	prop.valueStr = valueStr;
+	prop.dataType = dataType;
+	mtpProp.push_back(prop);
+}
+
+void Node::updateProperty(MtpPropertyCode property, uint64_t valueInt, std::string valueStr, MtpDataType dataType) {
+	for (unsigned i = 0; i < mtpProp.size(); i++) {
+		if (mtpProp[i].property == property) {
+			mtpProp[i].valueInt = valueInt;
+			mtpProp[i].valueStr = valueStr;
+			mtpProp[i].dataType = dataType;
+			return;
+		}
+	}
+	addProperty(property, valueInt, valueStr, dataType);
+}
+
+std::vector<Node::mtpProperty>& Node::getMtpProps() {
+	return mtpProp;
+}
+
+void Node::addProperties(const std::string& path, int storageID) {
+	MTPD("addProperties: handle: %u, filename: '%s'\n", handle, getName().c_str());
+	struct stat st;
+	int mFormat = 0;
+	uint64_t puid = ((uint64_t)storageID << 32) + handle;
+	off_t file_size = 0;
+
+	mFormat = MTP_FORMAT_UNDEFINED;   // file
+	if (lstat(path.c_str(), &st) == 0) {
+		file_size = st.st_size;
+		if (S_ISDIR(st.st_mode))
+			mFormat = MTP_FORMAT_ASSOCIATION; // folder
+	}
+
+	// TODO: don't store properties with constant values at all, add them at query time instead
+	addProperty(MTP_PROPERTY_STORAGE_ID, storageID, "", MTP_TYPE_UINT32);
+	addProperty(MTP_PROPERTY_OBJECT_FORMAT, mFormat, "", MTP_TYPE_UINT16);
+	addProperty(MTP_PROPERTY_PROTECTION_STATUS, 0, "", MTP_TYPE_UINT16);
+	addProperty(MTP_PROPERTY_OBJECT_SIZE, file_size, "", MTP_TYPE_UINT64);
+	addProperty(MTP_PROPERTY_OBJECT_FILE_NAME, 0, getName().c_str(), MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_DATE_MODIFIED, st.st_mtime, "", MTP_TYPE_UINT64);
+	addProperty(MTP_PROPERTY_PARENT_OBJECT, parent, "", MTP_TYPE_UINT32);
+	addProperty(MTP_PROPERTY_PERSISTENT_UID, puid, "", MTP_TYPE_UINT128);
+		// TODO: we can't really support persistent UIDs without a persistent DB.
+		// probably a combination of volume UUID + st_ino would come close.
+		// doesn't help for fs with no native inodes numbers like fat though...
+		// however, Microsoft's own impl (Zune, etc.) does not support persistent UIDs either
+	addProperty(MTP_PROPERTY_NAME, 0, getName().c_str(), MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_DISPLAY_NAME, 0, getName().c_str(), MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_DATE_ADDED, st.st_mtime, "", MTP_TYPE_UINT64);
+	addProperty(MTP_PROPERTY_DESCRIPTION, 0, "", MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_ARTIST, 0, "", MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_ALBUM_NAME, 0, "", MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_ALBUM_ARTIST, 0, "", MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_TRACK, 0, "", MTP_TYPE_UINT16);
+	addProperty(MTP_PROPERTY_ORIGINAL_RELEASE_DATE, 2014, "", MTP_TYPE_UINT64);	// TODO: extract year from st.st_mtime?
+	addProperty(MTP_PROPERTY_DURATION, 0, "", MTP_TYPE_UINT32);
+	addProperty(MTP_PROPERTY_GENRE, 0, "", MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_COMPOSER, 0, "", MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_ARTIST, 0, "", MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_ALBUM_NAME, 0, "", MTP_TYPE_STR);
+	addProperty(MTP_PROPERTY_DURATION, 0, "", MTP_TYPE_UINT32);
+	addProperty(MTP_PROPERTY_DESCRIPTION, 0, "", MTP_TYPE_STR);
+}
diff --git a/mtp/ffs/twrpMtp.cpp b/mtp/ffs/twrpMtp.cpp
new file mode 100644
index 0000000..d636580
--- /dev/null
+++ b/mtp/ffs/twrpMtp.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <string>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <pthread.h>
+#include <utils/Errors.h>
+#include <utils/threads.h>
+#include "MtpTypes.h"
+#include "MtpPacket.h"
+#include "MtpDataPacket.h"
+#include "MtpDatabase.h"
+#include "MtpRequestPacket.h"
+#include "MtpResponsePacket.h"
+#include "mtp_MtpDatabase.hpp"
+#include "mtp_MtpServer.hpp"
+#include "twrpMtp.hpp"
+#include "MtpDebug.h"
+
+#ifdef TWRPMTP
+static void usage(std::string prg) {
+	printf("Usage: %s <OPTIONS>\n", prg.c_str());
+	printf("Options:\n");
+	printf("\t-h, --help\t\tShow Usage\n");
+	printf("\t-s1, --storage1 /path/to/dir\t\tDestination to first storage directory\n");
+	printf("\t-s2, --storage2 /path/to/dir\t\tDestination to first storage directory\n");
+	printf("\t-sN, --storageN /path/to/dir\t\tDestination to first storage directory\n");
+}
+
+int main(int argc, char* argv[]) {
+	printf("argc: %d\n", argc);
+	if (argc < 2) {
+		usage(argv[0]);
+		return 1;
+	}
+
+	std::vector <std::string> storages;
+
+	for (int i = 1; i < argc; ++i) {
+		std::string arg = argv[i];
+		if ((arg == "-h") || (arg == "--help")) {
+			usage(argv[0]);
+		}
+		else {
+			storages.push_back(arg);
+		}
+	}
+	printf("starting\n");
+	twmtp_MtpServer* mtp = new twmtp_MtpServer();
+	mtp->set_storages(storages);
+	mtp->start();
+	return 0;
+}
+#endif //def TWRPMTP
+
+twrpMtp::twrpMtp(int debug_enabled) {
+	if (debug_enabled)
+		MtpDebug::enableDebug();
+	mtpstorages = new storages;
+	mtp_read_pipe = -1;
+}
+
+int twrpMtp::start(void) {
+	MTPI("Starting MTP\n");
+	twmtp_MtpServer *mtp = new twmtp_MtpServer();
+	mtp->set_storages(mtpstorages);
+	mtp->set_device_info();
+	mtp->set_read_pipe(mtp_read_pipe);
+	mtp->start();
+	return 0;
+}
+
+pthread_t twrpMtp::threadserver(void) {
+	pthread_t thread;
+	ThreadPtr mtpptr = &twrpMtp::start;
+	PThreadPtr p = *(PThreadPtr*)&mtpptr;
+	pthread_create(&thread, NULL, p, this);
+	return thread;
+}
+
+pid_t twrpMtp::forkserver(int mtppipe[2]) {
+	pid_t pid;
+	if ((pid = fork()) == -1) {
+		MTPE("MTP fork failed.\n");
+		return 0;
+	}
+	if (pid == 0) {
+		// Child process
+		close(mtppipe[1]); // Child closes write side
+		mtp_read_pipe = mtppipe[0];
+		start();
+		MTPD("MTP child process exited.\n");
+		close(mtppipe[0]);
+		_exit(0);
+	} else {
+		MTPD("MTP child PID: %d\n", pid);
+		return pid;
+	}
+	return 0;
+}
+
+void twrpMtp::addStorage(std::string display, std::string path, int mtpid, uint64_t maxFileSize) {
+	s = new storage;
+	s->display = display;
+	s->mount = path;
+	s->mtpid = mtpid;
+	s->maxFileSize = maxFileSize;
+	MTPD("twrpMtp mtpid: %d\n", s->mtpid);
+	mtpstorages->push_back(s);
+}
diff --git a/mtp/ffs/twrpMtp.hpp b/mtp/ffs/twrpMtp.hpp
new file mode 100644
index 0000000..2e8c2b8
--- /dev/null
+++ b/mtp/ffs/twrpMtp.hpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 TeamWin
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TWRPMTP_HPP
+#define TWRPMTP_HPP
+
+#include <fcntl.h>
+#include <utils/Errors.h>
+#include <utils/threads.h>
+#include <string>
+#include <vector>
+#include <pthread.h>
+#include "MtpTypes.h"
+#include "MtpPacket.h"
+#include "MtpDataPacket.h"
+#include "MtpDatabase.h"
+#include "MtpRequestPacket.h"
+#include "MtpResponsePacket.h"
+#include "mtp_MtpDatabase.hpp"
+#include "mtp_MtpServer.hpp"
+
+class twrpMtp {
+	public:
+		twrpMtp(int debug_enabled = 0);
+		pthread_t threadserver(void);
+		pid_t forkserver(int mtppipe[2]);
+		void addStorage(std::string display, std::string path, int mtpid, uint64_t maxFileSize);
+	private:
+		int start(void);
+		typedef int (twrpMtp::*ThreadPtr)(void);
+		typedef void* (*PThreadPtr)(void *);
+		storages *mtpstorages;
+		storage *s;
+		int mtp_read_pipe;
+};
+#endif
diff --git a/openaes/Android.mk b/openaes/Android.mk
new file mode 100755
index 0000000..7c6b5bd
--- /dev/null
+++ b/openaes/Android.mk
@@ -0,0 +1,39 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+ifneq ($(TW_EXCLUDE_ENCRYPTED_BACKUPS), true)
+	# Build shared binary
+	LOCAL_SRC_FILES:= src/oaes.c
+	LOCAL_C_INCLUDES := \
+		$(commands_recovery_local_path)/openaes/src/isaac \
+		$(commands_recovery_local_path)/openaes/inc
+	LOCAL_CFLAGS:= -g -c -W
+	LOCAL_MODULE:=openaes
+	LOCAL_MODULE_TAGS:= optional
+	LOCAL_MODULE_CLASS := EXECUTABLES
+	LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+	LOCAL_SHARED_LIBRARIES = libopenaes libc
+	include $(BUILD_EXECUTABLE)
+
+	# Build shared library
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := libopenaes
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_C_INCLUDES := \
+		$(commands_recovery_local_path)/openaes/src/isaac \
+		$(commands_recovery_local_path)/openaes/inc
+	LOCAL_SRC_FILES = src/oaes_lib.c src/isaac/rand.c src/ftime.c
+	LOCAL_SHARED_LIBRARIES = libc
+	include $(BUILD_SHARED_LIBRARY)
+
+	# Build static library
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := libopenaes_static
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_C_INCLUDES := \
+		$(commands_recovery_local_path)/openaes/src/isaac \
+		$(commands_recovery_local_path)/openaes/inc
+	LOCAL_SRC_FILES = src/oaes_lib.c src/isaac/rand.c src/ftime.c
+	LOCAL_STATIC_LIBRARIES = libc
+	include $(BUILD_STATIC_LIBRARY)
+endif
diff --git a/openaes/CHANGELOG b/openaes/CHANGELOG
new file mode 100644
index 0000000..8ea94cf
--- /dev/null
+++ b/openaes/CHANGELOG
@@ -0,0 +1,43 @@
+---------------------------------------------------------------------------
+OpenAES-0.7.0
+Nabil S. Al Ramli
+www.nalramli.com
+---------------------------------------------------------------------------
+
+OpenAES-0.7.0
+-------------
+* implement oaes command line utility
+* defect: oaes_decrypt() does not have a way to tell if pad pattern is accidental
+
+OpenAES-0.6.0
+-------------
+* add stepping pause to vt_aes
+
+OpenAES-0.5.0
+-------------
+* defect: algorithm errors with ExpandKey for 192 bit and 256 bit keys
+
+OpenAES-0.4.0
+-------------
+* add vt_aes test program
+* defect: algorithm errors with ShiftRows and MixColumns
+* OAES_DEBUG config to step through encryption and decryption
+* defect: test_performance crash when printing results
+* allow user to specify iv by passing it to oaes_set_option()
+* oaes_key_import_data() and oaes_key_export_data() to operate on key data directly
+* defect: Access violation in oaes_key_export()
+
+OpenAES-0.3.0
+-------------
+* Add CMake support
+* platform independence fixes
+
+OpenAES-0.2.0
+-------------
+* Add performance tests
+* Implement CBC mode in AES algorithm
+* Performance improvements in oaes_shift_rows() and oaes_inv_shift_rows()
+
+OpenAES-0.1.0
+-------------
+* Implement AES algorithm
diff --git a/openaes/CMakeLists.txt b/openaes/CMakeLists.txt
new file mode 100644
index 0000000..251d5a3
--- /dev/null
+++ b/openaes/CMakeLists.txt
@@ -0,0 +1,74 @@
+# ---------------------------------------------------------------------------
+# OpenAES License
+# ---------------------------------------------------------------------------
+# Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 
+#   - Redistributions of source code must retain the above copyright notice,
+#     this list of conditions and the following disclaimer.
+#   - Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+# ---------------------------------------------------------------------------
+
+cmake_minimum_required (VERSION 2.8.0)
+
+project ( "oaes" )
+
+include_directories (
+		${CMAKE_CURRENT_SOURCE_DIR}/inc
+		${CMAKE_CURRENT_SOURCE_DIR}/src/isaac
+	)
+
+set (SRC
+		${CMAKE_CURRENT_SOURCE_DIR}/src/oaes_lib.c
+		${CMAKE_CURRENT_SOURCE_DIR}/src/isaac/rand.c
+	)
+
+set (HDR
+		${CMAKE_CURRENT_SOURCE_DIR}/inc/oaes_config.h
+		${CMAKE_CURRENT_SOURCE_DIR}/inc/oaes_lib.h
+		${CMAKE_CURRENT_SOURCE_DIR}/src/isaac/rand.h
+		${CMAKE_CURRENT_SOURCE_DIR}/src/isaac/standard.h
+	)
+
+set (SRC_test_encrypt
+		${CMAKE_CURRENT_SOURCE_DIR}/test/test_encrypt.c
+	)
+
+set (SRC_test_keys
+		${CMAKE_CURRENT_SOURCE_DIR}/test/test_keys.c
+	)
+
+set (SRC_test_performance
+		${CMAKE_CURRENT_SOURCE_DIR}/test/test_performance.c
+	)
+
+set (SRC_vt_aes
+		${CMAKE_CURRENT_SOURCE_DIR}/test/vt_aes.c
+	)
+
+set (SRC_oaes
+		${CMAKE_CURRENT_SOURCE_DIR}/src/oaes.c
+	)
+
+add_executable (test_encrypt ${SRC_test_encrypt} ${SRC} ${HDR})
+add_executable (test_keys ${SRC_test_keys} ${SRC} ${HDR})
+add_executable (test_performance ${SRC_test_performance} ${SRC} ${HDR})
+add_executable (vt_aes ${SRC_vt_aes} ${SRC} ${HDR})
+add_executable (oaes ${SRC_oaes} ${SRC} ${HDR})
diff --git a/openaes/LICENSE b/openaes/LICENSE
new file mode 100644
index 0000000..d824e13
--- /dev/null
+++ b/openaes/LICENSE
@@ -0,0 +1,27 @@
+---------------------------------------------------------------------------
+OpenAES Licence
+---------------------------------------------------------------------------
+Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  - Redistributions of source code must retain the above copyright notice,
+    this list of conditions and the following disclaimer.
+  - Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
diff --git a/openaes/README b/openaes/README
new file mode 100644
index 0000000..6fb957b
--- /dev/null
+++ b/openaes/README
@@ -0,0 +1,78 @@
+---------------------------------------------------------------------------
+OpenAES-0.7.0
+Nabil S. Al Ramli
+www.nalramli.com
+---------------------------------------------------------------------------
+
+License Terms
+-------------
+
+---------------------------------------------------------------------------
+OpenAES Licence
+---------------------------------------------------------------------------
+Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  - Redistributions of source code must retain the above copyright notice,
+    this list of conditions and the following disclaimer.
+  - Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+
+Introduction
+------------
+
+OpenAES is an open source implementation of the Advanced Encryption
+Standard.  It is distributed as a portable, lightweight C library that can
+be easily integrated into applications.
+
+Compiling
+---------
+
+OpenAES has been tested with the GCC as well as VC compilers.  It is
+necessary to compile the source files located in ./src, and to add ./inc to
+the include paths.
+
+If you are building with OAES_HAVE_ISAAC defined (true by default), then
+you also need to link in the source files under ./src/isaac and also add
+./src/isaac to the include paths.
+
+You may edit ./inc/oaes_config.h to modify build options.
+
+CMake 2.8.0 can be used to build the test programs on different platforms.
+
+In a Linux command line terminal type:
+
+cmake .
+make
+
+In Windows, in a Visual Studio command line window type:
+
+cmake . -G "NMake Makefiles"
+nmake
+
+Usage
+-----
+
+Usage is described in the header file ./inc/oaes_lib.h.
+
+Samples
+-------
+
+Samples applications are provided in the /test folder.
diff --git a/openaes/VERSION b/openaes/VERSION
new file mode 100644
index 0000000..831dbdd
--- /dev/null
+++ b/openaes/VERSION
@@ -0,0 +1 @@
+OpenAES-0.7.0
\ No newline at end of file
diff --git a/openaes/inc/oaes_config.h b/openaes/inc/oaes_config.h
new file mode 100644
index 0000000..d887f03
--- /dev/null
+++ b/openaes/inc/oaes_config.h
@@ -0,0 +1,50 @@
+/* 
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *   - Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#ifndef _OAES_CONFIG_H
+#define _OAES_CONFIG_H
+
+#ifdef __cplusplus 
+extern "C" {
+#endif
+
+#ifndef OAES_HAVE_ISAAC
+#define OAES_HAVE_ISAAC 1
+#endif // OAES_HAVE_ISAAC
+
+#ifndef OAES_DEBUG
+#define OAES_DEBUG 0
+#endif // OAES_DEBUG
+
+#ifdef __cplusplus 
+}
+#endif
+
+#endif // _OAES_CONFIG_H
diff --git a/openaes/inc/oaes_lib.h b/openaes/inc/oaes_lib.h
new file mode 100644
index 0000000..fd89a70
--- /dev/null
+++ b/openaes/inc/oaes_lib.h
@@ -0,0 +1,172 @@
+/* 
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *   - Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#ifndef _OAES_LIB_H
+#define _OAES_LIB_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus 
+extern "C" {
+#endif
+
+#define OAES_VERSION "0.7.0"
+#define OAES_BLOCK_SIZE 16
+
+typedef void OAES_CTX;
+
+typedef enum
+{
+	OAES_RET_FIRST = 0,
+	OAES_RET_SUCCESS = 0,
+	OAES_RET_UNKNOWN,
+	OAES_RET_ARG1,
+	OAES_RET_ARG2,
+	OAES_RET_ARG3,
+	OAES_RET_ARG4,
+	OAES_RET_ARG5,
+	OAES_RET_NOKEY,
+	OAES_RET_MEM,
+	OAES_RET_BUF,
+	OAES_RET_HEADER,
+	OAES_RET_COUNT
+} OAES_RET;
+
+/*
+ * oaes_set_option() takes one of these values for its [option] parameter
+ * some options accept either an optional or a required [value] parameter
+ */
+// no option
+#define OAES_OPTION_NONE 0
+// enable ECB mode, disable CBC mode
+#define OAES_OPTION_ECB 1
+// enable CBC mode, disable ECB mode
+// value is optional, may pass uint8_t iv[OAES_BLOCK_SIZE] to specify
+// the value of the initialization vector, iv
+#define OAES_OPTION_CBC 2
+
+#ifdef OAES_DEBUG
+typedef int ( * oaes_step_cb ) (
+		const uint8_t state[OAES_BLOCK_SIZE],
+		const char * step_name,
+		int step_count,
+		void * user_data );
+// enable state stepping mode
+// value is required, must pass oaes_step_cb to receive the state at each step
+#define OAES_OPTION_STEP_ON 4
+// disable state stepping mode
+#define OAES_OPTION_STEP_OFF 8
+#endif // OAES_DEBUG
+
+typedef uint16_t OAES_OPTION;
+
+/*
+ * // usage:
+ * 
+ * OAES_CTX * ctx = oaes_alloc();
+ * .
+ * .
+ * .
+ * {
+ *   oaes_gen_key_xxx( ctx );
+ *   {
+ *     oaes_key_export( ctx, _buf, &_buf_len );
+ *     // or
+ *     oaes_key_export_data( ctx, _buf, &_buf_len );\
+ *   }
+ * }
+ * // or
+ * {
+ *   oaes_key_import( ctx, _buf, _buf_len );
+ *   // or
+ *   oaes_key_import_data( ctx, _buf, _buf_len );
+ * }
+ * .
+ * .
+ * .
+ * oaes_encrypt( ctx, m, m_len, c, &c_len );
+ * .
+ * .
+ * .
+ * oaes_decrypt( ctx, c, c_len, m, &m_len );
+ * .
+ * .
+ * .
+ * oaes_free( &ctx );
+ */
+
+OAES_CTX * oaes_alloc();
+
+OAES_RET oaes_free( OAES_CTX ** ctx );
+
+OAES_RET oaes_set_option( OAES_CTX * ctx,
+		OAES_OPTION option, const void * value );
+
+OAES_RET oaes_key_gen_128( OAES_CTX * ctx );
+
+OAES_RET oaes_key_gen_192( OAES_CTX * ctx );
+
+OAES_RET oaes_key_gen_256( OAES_CTX * ctx );
+
+// export key with header information
+// set data == NULL to get the required data_len
+OAES_RET oaes_key_export( OAES_CTX * ctx,
+		uint8_t * data, size_t * data_len );
+
+// directly export the data from key
+// set data == NULL to get the required data_len
+OAES_RET oaes_key_export_data( OAES_CTX * ctx,
+		uint8_t * data, size_t * data_len );
+
+// import key with header information
+OAES_RET oaes_key_import( OAES_CTX * ctx,
+		const uint8_t * data, size_t data_len );
+
+// directly import data into key
+OAES_RET oaes_key_import_data( OAES_CTX * ctx,
+		const uint8_t * data, size_t data_len );
+
+// set c == NULL to get the required c_len
+OAES_RET oaes_encrypt( OAES_CTX * ctx,
+		const uint8_t * m, size_t m_len, uint8_t * c, size_t * c_len );
+
+// set m == NULL to get the required m_len
+OAES_RET oaes_decrypt( OAES_CTX * ctx,
+		const uint8_t * c, size_t c_len, uint8_t * m, size_t * m_len );
+
+// set buf == NULL to get the required buf_len
+OAES_RET oaes_sprintf(
+		char * buf, size_t * buf_len, const uint8_t * data, size_t data_len );
+
+#ifdef __cplusplus 
+}
+#endif
+
+#endif // _OAES_LIB_H
diff --git a/openaes/src/ftime.c b/openaes/src/ftime.c
new file mode 100644
index 0000000..d918209
--- /dev/null
+++ b/openaes/src/ftime.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <time.h>
+#include "ftime.h"
+
+// This was removed from POSIX 2008.
+int ftime(struct timeb* tb) {
+  struct timeval  tv;
+  struct timezone tz;
+
+  if (gettimeofday(&tv, &tz) < 0)
+    return -1;
+
+  tb->time    = tv.tv_sec;
+  tb->millitm = (tv.tv_usec + 500) / 1000;
+
+  if (tb->millitm == 1000) {
+    ++tb->time;
+    tb->millitm = 0;
+  }
+
+  tb->timezone = tz.tz_minuteswest;
+  tb->dstflag  = tz.tz_dsttime;
+
+  return 0;
+}
diff --git a/openaes/src/ftime.h b/openaes/src/ftime.h
new file mode 100644
index 0000000..80fbab7
--- /dev/null
+++ b/openaes/src/ftime.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+// Only used by ftime, which was removed from POSIX 2008.
+struct timeb {
+  time_t          time;
+  unsigned short  millitm;
+  short           timezone;
+  short           dstflag;
+};
+
+int ftime(struct timeb* tb);
diff --git a/openaes/src/isaac/rand.c b/openaes/src/isaac/rand.c
new file mode 100644
index 0000000..63babc7
--- /dev/null
+++ b/openaes/src/isaac/rand.c
@@ -0,0 +1,137 @@
+/*
+------------------------------------------------------------------------------
+rand.c: By Bob Jenkins.  My random number generator, ISAAC.  Public Domain.
+MODIFIED:
+  960327: Creation (addition of randinit, really)
+  970719: use context, not global variables, for internal state
+  980324: added main (ifdef'ed out), also rearranged randinit()
+  010626: Note that this is public domain
+------------------------------------------------------------------------------
+*/
+#ifndef STANDARD
+#include "standard.h"
+#endif
+#ifndef RAND
+#include "rand.h"
+#endif
+
+
+#define ind(mm,x)  (*(ub4 *)((ub1 *)(mm) + ((x) & ((RANDSIZ-1)<<2))))
+#define rngstep(mix,a,b,mm,m,m2,r,x) \
+{ \
+  x = *m;  \
+  a = (a^(mix)) + *(m2++); \
+  *(m++) = y = ind(mm,x) + a + b; \
+  *(r++) = b = ind(mm,y>>RANDSIZL) + x; \
+}
+
+void     isaac(ctx)
+randctx *ctx;
+{
+   register ub4 a,b,x,y,*m,*mm,*m2,*r,*mend;
+   mm=ctx->randmem; r=ctx->randrsl;
+   a = ctx->randa; b = ctx->randb + (++ctx->randc);
+   for (m = mm, mend = m2 = m+(RANDSIZ/2); m<mend; )
+   {
+      rngstep( a<<13, a, b, mm, m, m2, r, x);
+      rngstep( a>>6 , a, b, mm, m, m2, r, x);
+      rngstep( a<<2 , a, b, mm, m, m2, r, x);
+      rngstep( a>>16, a, b, mm, m, m2, r, x);
+   }
+   for (m2 = mm; m2<mend; )
+   {
+      rngstep( a<<13, a, b, mm, m, m2, r, x);
+      rngstep( a>>6 , a, b, mm, m, m2, r, x);
+      rngstep( a<<2 , a, b, mm, m, m2, r, x);
+      rngstep( a>>16, a, b, mm, m, m2, r, x);
+   }
+   ctx->randb = b; ctx->randa = a;
+}
+
+
+#define mix(a,b,c,d,e,f,g,h) \
+{ \
+   a^=b<<11; d+=a; b+=c; \
+   b^=c>>2;  e+=b; c+=d; \
+   c^=d<<8;  f+=c; d+=e; \
+   d^=e>>16; g+=d; e+=f; \
+   e^=f<<10; h+=e; f+=g; \
+   f^=g>>4;  a+=f; g+=h; \
+   g^=h<<8;  b+=g; h+=a; \
+   h^=a>>9;  c+=h; a+=b; \
+}
+
+/* if (flag==TRUE), then use the contents of randrsl[] to initialize mm[]. */
+void randinit(ctx, flag)
+randctx *ctx;
+word     flag;
+{
+   word i;
+   ub4 a,b,c,d,e,f,g,h;
+   ub4 *m,*r;
+   ctx->randa = ctx->randb = ctx->randc = 0;
+   m=ctx->randmem;
+   r=ctx->randrsl;
+   a=b=c=d=e=f=g=h=0x9e3779b9;  /* the golden ratio */
+
+   for (i=0; i<4; ++i)          /* scramble it */
+   {
+     mix(a,b,c,d,e,f,g,h);
+   }
+
+   if (flag) 
+   {
+     /* initialize using the contents of r[] as the seed */
+     for (i=0; i<RANDSIZ; i+=8)
+     {
+       a+=r[i  ]; b+=r[i+1]; c+=r[i+2]; d+=r[i+3];
+       e+=r[i+4]; f+=r[i+5]; g+=r[i+6]; h+=r[i+7];
+       mix(a,b,c,d,e,f,g,h);
+       m[i  ]=a; m[i+1]=b; m[i+2]=c; m[i+3]=d;
+       m[i+4]=e; m[i+5]=f; m[i+6]=g; m[i+7]=h;
+     }
+     /* do a second pass to make all of the seed affect all of m */
+     for (i=0; i<RANDSIZ; i+=8)
+     {
+       a+=m[i  ]; b+=m[i+1]; c+=m[i+2]; d+=m[i+3];
+       e+=m[i+4]; f+=m[i+5]; g+=m[i+6]; h+=m[i+7];
+       mix(a,b,c,d,e,f,g,h);
+       m[i  ]=a; m[i+1]=b; m[i+2]=c; m[i+3]=d;
+       m[i+4]=e; m[i+5]=f; m[i+6]=g; m[i+7]=h;
+     }
+   }
+   else
+   {
+     /* fill in m[] with messy stuff */
+     for (i=0; i<RANDSIZ; i+=8)
+     {
+       mix(a,b,c,d,e,f,g,h);
+       m[i  ]=a; m[i+1]=b; m[i+2]=c; m[i+3]=d;
+       m[i+4]=e; m[i+5]=f; m[i+6]=g; m[i+7]=h;
+     }
+   }
+
+   isaac(ctx);            /* fill in the first set of results */
+   ctx->randcnt=RANDSIZ;  /* prepare to use the first set of results */
+}
+
+
+#ifdef NEVER
+int main()
+{
+  ub4 i,j;
+  randctx ctx;
+  ctx.randa=ctx.randb=ctx.randc=(ub4)0;
+  for (i=0; i<256; ++i) ctx.randrsl[i]=(ub4)0;
+  randinit(&ctx, TRUE);
+  for (i=0; i<2; ++i)
+  {
+    isaac(&ctx);
+    for (j=0; j<256; ++j)
+    {
+      printf("%.8lx",ctx.randrsl[j]);
+      if ((j&7)==7) printf("\n");
+    }
+  }
+}
+#endif
diff --git a/openaes/src/isaac/rand.h b/openaes/src/isaac/rand.h
new file mode 100644
index 0000000..73f6e3e
--- /dev/null
+++ b/openaes/src/isaac/rand.h
@@ -0,0 +1,56 @@
+/*
+------------------------------------------------------------------------------
+rand.h: definitions for a random number generator
+By Bob Jenkins, 1996, Public Domain
+MODIFIED:
+  960327: Creation (addition of randinit, really)
+  970719: use context, not global variables, for internal state
+  980324: renamed seed to flag
+  980605: recommend RANDSIZL=4 for noncryptography.
+  010626: note this is public domain
+------------------------------------------------------------------------------
+*/
+#ifndef STANDARD
+#include "standard.h"
+#endif
+
+#ifndef RAND
+#define RAND
+#define RANDSIZL   (8)
+#define RANDSIZ    (1<<RANDSIZL)
+
+/* context of random number generator */
+struct randctx
+{
+  ub4 randcnt;
+  ub4 randrsl[RANDSIZ];
+  ub4 randmem[RANDSIZ];
+  ub4 randa;
+  ub4 randb;
+  ub4 randc;
+};
+typedef  struct randctx  randctx;
+
+/*
+------------------------------------------------------------------------------
+ If (flag==TRUE), then use the contents of randrsl[0..RANDSIZ-1] as the seed.
+------------------------------------------------------------------------------
+*/
+void randinit(/*_ randctx *r, word flag _*/);
+
+void isaac(/*_ randctx *r _*/);
+
+
+/*
+------------------------------------------------------------------------------
+ Call rand(/o_ randctx *r _o/) to retrieve a single 32-bit random value
+------------------------------------------------------------------------------
+*/
+#define rand(r) \
+   (!(r)->randcnt-- ? \
+     (isaac(r), (r)->randcnt=RANDSIZ-1, (r)->randrsl[(r)->randcnt]) : \
+     (r)->randrsl[(r)->randcnt])
+
+#endif  /* RAND */
+
+
diff --git a/openaes/src/isaac/standard.h b/openaes/src/isaac/standard.h
new file mode 100644
index 0000000..202a5d6
--- /dev/null
+++ b/openaes/src/isaac/standard.h
@@ -0,0 +1,57 @@
+/*
+------------------------------------------------------------------------------
+Standard definitions and types, Bob Jenkins
+------------------------------------------------------------------------------
+*/
+#ifndef STANDARD
+# define STANDARD
+# ifndef STDIO
+#  include <stdio.h>
+#  define STDIO
+# endif
+# ifndef STDDEF
+#  include <stddef.h>
+#  define STDDEF
+# endif
+typedef  unsigned long long  ub8;
+#define UB8MAXVAL 0xffffffffffffffffLL
+#define UB8BITS 64
+typedef    signed long long  sb8;
+#define SB8MAXVAL 0x7fffffffffffffffLL
+typedef  unsigned long  int  ub4;   /* unsigned 4-byte quantities */
+#define UB4MAXVAL 0xffffffff
+typedef    signed long  int  sb4;
+#define UB4BITS 32
+#define SB4MAXVAL 0x7fffffff
+typedef  unsigned short int  ub2;
+#define UB2MAXVAL 0xffff
+#define UB2BITS 16
+typedef    signed short int  sb2;
+#define SB2MAXVAL 0x7fff
+typedef  unsigned       char ub1;
+#define UB1MAXVAL 0xff
+#define UB1BITS 8
+typedef    signed       char sb1;   /* signed 1-byte quantities */
+#define SB1MAXVAL 0x7f
+typedef                 int  word;  /* fastest type available */
+
+#define bis(target,mask)  ((target) |=  (mask))
+#define bic(target,mask)  ((target) &= ~(mask))
+#define bit(target,mask)  ((target) &   (mask))
+#ifndef min
+# define min(a,b) (((a)<(b)) ? (a) : (b))
+#endif /* min */
+#ifndef max
+# define max(a,b) (((a)<(b)) ? (b) : (a))
+#endif /* max */
+#ifndef align
+# define align(a) (((ub4)a+(sizeof(void *)-1))&(~(sizeof(void *)-1)))
+#endif /* align */
+#ifndef abs
+# define abs(a)   (((a)>0) ? (a) : -(a))
+#endif
+#define TRUE  1
+#define FALSE 0
+#define SUCCESS 0  /* 1 on VAX */
+
+#endif /* STANDARD */
diff --git a/openaes/src/oaes.c b/openaes/src/oaes.c
new file mode 100644
index 0000000..203da52
--- /dev/null
+++ b/openaes/src/oaes.c
@@ -0,0 +1,317 @@
+/* 
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *   - Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define OAES_DEBUG 1
+#include "../inc/oaes_lib.h"
+
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+#include <io.h>
+#else
+__inline static int setmode(int a __unused, int b __unused)
+{
+	return 0;
+}
+#endif
+
+#ifndef __max
+	#define __max(a,b)  (((a) > (b)) ? (a) : (b))
+#endif // __max
+
+#ifndef __min
+	#define __min(a,b)  (((a) < (b)) ? (a) : (b))
+#endif // __min
+
+#define OAES_BUF_LEN_ENC 4096 - 2 * OAES_BLOCK_SIZE
+#define OAES_BUF_LEN_DEC 4096
+
+static void usage( const char * exe_name )
+{
+	if( NULL == exe_name )
+		return;
+	
+	fprintf( stderr,
+			"Usage:\n"
+			"  %s <command> --key <key_data> [options]\n"
+			"\n"
+			"    command:\n"
+			"      enc: encrypt\n"
+			"      dec:  decrypt\n"
+			"\n"
+			"    options:\n"
+			"      --ecb: use ecb mode instead of cbc\n"
+			"      --in <path_in>\n"
+			"      --out <path_out>\n"
+			"\n",
+			exe_name
+	);
+}
+
+int main(int argc, char** argv)
+{
+	int _i = 0, _j = 0;
+	OAES_CTX * ctx = NULL;
+	uint8_t _buf_in[OAES_BUF_LEN_DEC];
+	uint8_t *_buf_out = NULL, _key_data[32] = "";
+	size_t _buf_in_len = 0, _buf_out_len = 0, _read_len = 0;
+	size_t _key_data_len = 0;
+	short _is_ecb = 0;
+	char *_file_in = NULL, *_file_out = NULL;
+	int _op = 0;
+	FILE *_f_in = stdin, *_f_out = stdout;
+	
+	fprintf( stderr, "\n"
+		"*******************************************************************************\n"
+		"* OpenAES %-10s                                                          *\n"
+		"* Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com                     *\n"
+		"*******************************************************************************\n\n",
+		OAES_VERSION );
+
+	// pad the key
+	for( _j = 0; _j < 32; _j++ )
+		_key_data[_j] = _j + 1;
+
+	if( argc < 2 )
+	{
+		usage( argv[0] );
+		return EXIT_FAILURE;
+	}
+
+	if( 0 == strcmp( argv[1], "enc" ) )
+	{
+		_op = 0;
+		_read_len = OAES_BUF_LEN_ENC;
+	}
+	else if( 0 == strcmp( argv[1], "dec" ) )
+	{
+		_op = 1;
+		_read_len = OAES_BUF_LEN_DEC;
+	}
+	else
+	{
+		fprintf(stderr, "Error: Unknown command '%s'.", argv[1]);
+		usage( argv[0] );
+		return EXIT_FAILURE;
+	}
+
+	for( _i = 2; _i < argc; _i++ )
+	{
+		int _found = 0;
+
+		if( 0 == strcmp( argv[_i], "--ecb" ) )
+		{
+			_found = 1;
+			_is_ecb = 1;
+		}
+		
+		if( 0 == strcmp( argv[_i], "--key" ) )
+		{
+			_found = 1;
+			_i++; // key_data
+			if( _i >= argc )
+			{
+				fprintf(stderr, "Error: No value specified for '%s'.\n",
+						"--key");
+				usage( argv[0] );
+				return EXIT_FAILURE;
+			}
+			_key_data_len = strlen(argv[_i]);
+			if( 16 >= _key_data_len )
+				_key_data_len = 16;
+			else if( 24 >= _key_data_len )
+				_key_data_len = 24;
+			else
+				_key_data_len = 32;
+			memcpy(_key_data, argv[_i], __min(32, strlen(argv[_i])));
+		}
+		
+		if( 0 == strcmp( argv[_i], "--in" ) )
+		{
+			_found = 1;
+			_i++; // path_in
+			if( _i >= argc )
+			{
+				fprintf(stderr, "Error: No value specified for '%s'.\n",
+						"--in");
+				usage( argv[0] );
+				return EXIT_FAILURE;
+			}
+			_file_in = argv[_i];
+		}
+		
+		if( 0 == strcmp( argv[_i], "--out" ) )
+		{
+			_found = 1;
+			_i++; // path_out
+			if( _i >= argc )
+			{
+				fprintf(stderr, "Error: No value specified for '%s'.\n",
+						"--out");
+				usage( argv[0] );
+				return EXIT_FAILURE;
+			}
+			_file_out = argv[_i];
+		}
+		
+		if( 0 == _found )
+		{
+			fprintf(stderr, "Error: Invalid option '%s'.\n", argv[_i]);
+			usage( argv[0] );
+			return EXIT_FAILURE;
+		}			
+	}
+
+	if( 0 == _key_data_len )
+	{
+		fprintf(stderr, "Error: --key must be specified.\n");
+		return EXIT_FAILURE;
+	}
+
+	if( _file_in )
+	{
+		_f_in = fopen(_file_in, "rb");
+		if( NULL == _f_in )
+		{
+			fprintf(stderr,
+				"Error: Failed to open '-%s' for reading.\n", _file_in);
+			return EXIT_FAILURE;
+		}
+	}
+	else
+	{
+		if( setmode(fileno(stdin), 0x8000) < 0 )
+			fprintf(stderr,"Error: Failed in setmode().\n");
+		_f_in = stdin;
+	}
+
+	if( _file_out )
+	{
+		_f_out = fopen(_file_out, "wb");
+		if( NULL == _f_out )
+		{
+			fprintf(stderr,
+				"Error: Failed to open '-%s' for writing.\n", _file_out);
+			if( _file_in )
+				fclose(_f_in);
+			return EXIT_FAILURE;
+		}
+	}
+	else
+	{
+		if( setmode(fileno(stdout), 0x8000) < 0 )
+			fprintf(stderr, "Error: Failed in setmode().\n");
+		_f_out = stdout;
+	}
+
+	ctx = oaes_alloc();
+	if( NULL == ctx )
+	{
+		fprintf(stderr, "Error: Failed to initialize OAES.\n");
+		if( _file_in )
+			fclose(_f_in);
+		if( _file_out )
+			fclose(_f_out);
+		return EXIT_FAILURE;
+	}
+	if( _is_ecb )
+		if( OAES_RET_SUCCESS != oaes_set_option( ctx, OAES_OPTION_ECB, NULL ) )
+			fprintf(stderr, "Error: Failed to set OAES options.\n");
+
+	oaes_key_import_data( ctx, _key_data, _key_data_len );
+
+	while( (_buf_in_len =
+		fread(_buf_in, sizeof(uint8_t), _read_len, _f_in)) )
+	{
+		switch(_op)
+		{
+		case 0:
+			if( OAES_RET_SUCCESS != oaes_encrypt( ctx,
+					_buf_in, _buf_in_len, NULL, &_buf_out_len ) )
+				fprintf( stderr,
+					"Error: Failed to retrieve required buffer size for "
+					"encryption.\n" );
+			_buf_out = (uint8_t *) calloc( _buf_out_len, sizeof( char ) );
+			if( NULL == _buf_out )
+			{
+				fprintf(stderr,  "Error: Failed to allocate memory.\n" );
+				if( _file_in )
+					fclose(_f_in);
+				if( _file_out )
+					fclose(_f_out);
+				return EXIT_FAILURE;
+			}
+			if( OAES_RET_SUCCESS != oaes_encrypt( ctx,
+					_buf_in, _buf_in_len, _buf_out, &_buf_out_len ) )
+				fprintf(stderr, "Error: Encryption failed.\n");
+			fwrite(_buf_out, sizeof(uint8_t), _buf_out_len, _f_out);
+			free(_buf_out);
+			break;
+		case 1:
+			if( OAES_RET_SUCCESS != oaes_decrypt( ctx,
+					_buf_in, _buf_in_len, NULL, &_buf_out_len ) )
+				fprintf( stderr,
+					"Error: Failed to retrieve required buffer size for "
+					"encryption.\n" );
+			_buf_out = (uint8_t *) calloc( _buf_out_len, sizeof( char ) );
+			if( NULL == _buf_out )
+			{
+				fprintf(stderr,  "Error: Failed to allocate memory.\n" );
+				free( _buf_out );
+				if( _file_in )
+					fclose(_f_in);
+				if( _file_out )
+					fclose(_f_out);
+				return EXIT_FAILURE;
+			}
+			if( OAES_RET_SUCCESS != oaes_decrypt( ctx,
+					_buf_in, _buf_in_len, _buf_out, &_buf_out_len ) )
+				fprintf(stderr, "Error: Decryption failed.\n");
+			fwrite(_buf_out, sizeof(uint8_t), _buf_out_len, _f_out);
+			free(_buf_out);
+			break;
+		default:
+			break;
+		}
+	}
+
+
+	if( OAES_RET_SUCCESS !=  oaes_free( &ctx ) )
+		fprintf(stderr, "Error: Failed to uninitialize OAES.\n");
+	
+	if( _file_in )
+		fclose(_f_in);
+	if( _file_out )
+		fclose(_f_out);
+
+	return (EXIT_SUCCESS);
+}
diff --git a/openaes/src/oaes_lib.c b/openaes/src/oaes_lib.c
new file mode 100644
index 0000000..2e28159
--- /dev/null
+++ b/openaes/src/oaes_lib.c
@@ -0,0 +1,1430 @@
+/* 
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *   - Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+/*static const char _NR[] = {
+	0x4e,0x61,0x62,0x69,0x6c,0x20,0x53,0x2e,0x20,
+	0x41,0x6c,0x20,0x52,0x61,0x6d,0x6c,0x69,0x00 };*/
+
+#include <stddef.h>
+#include <time.h> 
+//#include <sys/timeb.h>
+#include "ftime.h"
+#include <malloc.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef WIN32
+#include <process.h>
+#endif
+
+#include "oaes_config.h"
+#include "oaes_lib.h"
+
+#ifdef OAES_HAVE_ISAAC
+#include "rand.h"
+#endif // OAES_HAVE_ISAAC
+
+#define OAES_RKEY_LEN 4
+#define OAES_COL_LEN 4
+#define OAES_ROUND_BASE 7
+
+// the block is padded
+#define OAES_FLAG_PAD 0x01
+
+#ifndef min
+# define min(a,b) (((a)<(b)) ? (a) : (b))
+#endif /* min */
+
+typedef struct _oaes_key
+{
+	size_t data_len;
+	uint8_t *data;
+	size_t exp_data_len;
+	uint8_t *exp_data;
+	size_t num_keys;
+	size_t key_base;
+} oaes_key;
+
+typedef struct _oaes_ctx
+{
+#ifdef OAES_HAVE_ISAAC
+  randctx * rctx;
+#endif // OAES_HAVE_ISAAC
+
+#ifdef OAES_DEBUG
+	oaes_step_cb step_cb;
+#endif // OAES_DEBUG
+
+	oaes_key * key;
+	OAES_OPTION options;
+	uint8_t iv[OAES_BLOCK_SIZE];
+} oaes_ctx;
+
+// "OAES<8-bit header version><8-bit type><16-bit options><8-bit flags><56-bit reserved>"
+static uint8_t oaes_header[OAES_BLOCK_SIZE] = {
+	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
+	/*0*/	0x4f, 0x41, 0x45, 0x53, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+static uint8_t oaes_gf_8[] = {
+	0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
+
+static uint8_t oaes_sub_byte_value[16][16] = {
+	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
+	/*0*/{	0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76},
+	/*1*/{	0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0},
+	/*2*/{	0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15},
+	/*3*/{	0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75},
+	/*4*/{	0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84},
+	/*5*/{	0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf},
+	/*6*/{	0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8},
+	/*7*/{	0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2},
+	/*8*/{	0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73},
+	/*9*/{	0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb},
+	/*a*/{	0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79},
+	/*b*/{	0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08},
+	/*c*/{	0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a},
+	/*d*/{	0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e},
+	/*e*/{	0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf},
+	/*f*/{	0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16},
+};
+
+static uint8_t oaes_inv_sub_byte_value[16][16] = {
+	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
+	/*0*/{	0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb},
+	/*1*/{	0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb},
+	/*2*/{	0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e},
+	/*3*/{	0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25},
+	/*4*/{	0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92},
+	/*5*/{	0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84},
+	/*6*/{	0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06},
+	/*7*/{	0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b},
+	/*8*/{	0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73},
+	/*9*/{	0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e},
+	/*a*/{	0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b},
+	/*b*/{	0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4},
+	/*c*/{	0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f},
+	/*d*/{	0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef},
+	/*e*/{	0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61},
+	/*f*/{	0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d},
+};
+
+static uint8_t oaes_gf_mul_2[16][16] = {
+	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
+	/*0*/{	0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e},
+	/*1*/{	0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e},
+	/*2*/{	0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e},
+	/*3*/{	0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e},
+	/*4*/{	0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e},
+	/*5*/{	0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe},
+	/*6*/{	0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde},
+	/*7*/{	0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe},
+	/*8*/{	0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05},
+	/*9*/{	0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25},
+	/*a*/{	0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45},
+	/*b*/{	0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65},
+	/*c*/{	0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85},
+	/*d*/{	0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5},
+	/*e*/{	0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5},
+	/*f*/{	0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5},
+};
+
+static uint8_t oaes_gf_mul_3[16][16] = {
+	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
+	/*0*/{	0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11},
+	/*1*/{	0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21},
+	/*2*/{	0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71},
+	/*3*/{	0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41},
+	/*4*/{	0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1},
+	/*5*/{	0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1},
+	/*6*/{	0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1},
+	/*7*/{	0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81},
+	/*8*/{	0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a},
+	/*9*/{	0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba},
+	/*a*/{	0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea},
+	/*b*/{	0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda},
+	/*c*/{	0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a},
+	/*d*/{	0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a},
+	/*e*/{	0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a},
+	/*f*/{	0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a},
+};
+
+static uint8_t oaes_gf_mul_9[16][16] = {
+	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
+	/*0*/{	0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77},
+	/*1*/{	0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7},
+	/*2*/{	0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c},
+	/*3*/{	0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc},
+	/*4*/{	0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01},
+	/*5*/{	0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91},
+	/*6*/{	0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a},
+	/*7*/{	0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa},
+	/*8*/{	0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b},
+	/*9*/{	0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b},
+	/*a*/{	0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0},
+	/*b*/{	0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30},
+	/*c*/{	0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed},
+	/*d*/{	0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d},
+	/*e*/{	0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6},
+	/*f*/{	0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46},
+};
+
+static uint8_t oaes_gf_mul_b[16][16] = {
+	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
+	/*0*/{	0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69},
+	/*1*/{	0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9},
+	/*2*/{	0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12},
+	/*3*/{	0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2},
+	/*4*/{	0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f},
+	/*5*/{	0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f},
+	/*6*/{	0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4},
+	/*7*/{	0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54},
+	/*8*/{	0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e},
+	/*9*/{	0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e},
+	/*a*/{	0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5},
+	/*b*/{	0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55},
+	/*c*/{	0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68},
+	/*d*/{	0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8},
+	/*e*/{	0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13},
+	/*f*/{	0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3},
+};
+
+static uint8_t oaes_gf_mul_d[16][16] = {
+	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
+	/*0*/{	0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b},
+	/*1*/{	0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b},
+	/*2*/{	0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0},
+	/*3*/{	0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20},
+	/*4*/{	0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26},
+	/*5*/{	0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6},
+	/*6*/{	0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d},
+	/*7*/{	0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d},
+	/*8*/{	0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91},
+	/*9*/{	0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41},
+	/*a*/{	0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a},
+	/*b*/{	0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa},
+	/*c*/{	0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc},
+	/*d*/{	0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c},
+	/*e*/{	0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47},
+	/*f*/{	0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97},
+};
+
+static uint8_t oaes_gf_mul_e[16][16] = {
+	// 		0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    a,    b,    c,    d,    e,    f,
+	/*0*/{	0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a},
+	/*1*/{	0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba},
+	/*2*/{	0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81},
+	/*3*/{	0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61},
+	/*4*/{	0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7},
+	/*5*/{	0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17},
+	/*6*/{	0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c},
+	/*7*/{	0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc},
+	/*8*/{	0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b},
+	/*9*/{	0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb},
+	/*a*/{	0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0},
+	/*b*/{	0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20},
+	/*c*/{	0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6},
+	/*d*/{	0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56},
+	/*e*/{	0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d},
+	/*f*/{	0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d},
+};
+
+static OAES_RET oaes_sub_byte( uint8_t * byte )
+{
+	size_t _x, _y;
+	
+	if( NULL == byte )
+		return OAES_RET_ARG1;
+
+	_x = _y = *byte;
+	_x &= 0x0f;
+	_y &= 0xf0;
+	_y >>= 4;
+	*byte = oaes_sub_byte_value[_y][_x];
+	
+	return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_inv_sub_byte( uint8_t * byte )
+{
+	size_t _x, _y;
+	
+	if( NULL == byte )
+		return OAES_RET_ARG1;
+
+	_x = _y = *byte;
+	_x &= 0x0f;
+	_y &= 0xf0;
+	_y >>= 4;
+	*byte = oaes_inv_sub_byte_value[_y][_x];
+	
+	return OAES_RET_SUCCESS;
+}
+
+/*static OAES_RET oaes_word_rot_right( uint8_t word[OAES_COL_LEN] )
+{
+	uint8_t _temp[OAES_COL_LEN];
+	
+	if( NULL == word )
+		return OAES_RET_ARG1;
+
+	memcpy( _temp + 1, word, OAES_COL_LEN - 1 );
+	_temp[0] = word[OAES_COL_LEN - 1];
+	memcpy( word, _temp, OAES_COL_LEN );
+	
+	return OAES_RET_SUCCESS;
+}*/
+
+static OAES_RET oaes_word_rot_left( uint8_t word[OAES_COL_LEN] )
+{
+	uint8_t _temp[OAES_COL_LEN];
+	
+	if( NULL == word )
+		return OAES_RET_ARG1;
+
+	memcpy( _temp, word + 1, OAES_COL_LEN - 1 );
+	_temp[OAES_COL_LEN - 1] = word[0];
+	memcpy( word, _temp, OAES_COL_LEN );
+	
+	return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_shift_rows( uint8_t block[OAES_BLOCK_SIZE] )
+{
+	uint8_t _temp[OAES_BLOCK_SIZE];
+
+	if( NULL == block )
+		return OAES_RET_ARG1;
+
+	_temp[0x00] = block[0x00];
+	_temp[0x01] = block[0x05];
+	_temp[0x02] = block[0x0a];
+	_temp[0x03] = block[0x0f];
+	_temp[0x04] = block[0x04];
+	_temp[0x05] = block[0x09];
+	_temp[0x06] = block[0x0e];
+	_temp[0x07] = block[0x03];
+	_temp[0x08] = block[0x08];
+	_temp[0x09] = block[0x0d];
+	_temp[0x0a] = block[0x02];
+	_temp[0x0b] = block[0x07];
+	_temp[0x0c] = block[0x0c];
+	_temp[0x0d] = block[0x01];
+	_temp[0x0e] = block[0x06];
+	_temp[0x0f] = block[0x0b];
+	memcpy( block, _temp, OAES_BLOCK_SIZE );
+	
+	return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_inv_shift_rows( uint8_t block[OAES_BLOCK_SIZE] )
+{
+	uint8_t _temp[OAES_BLOCK_SIZE];
+
+	if( NULL == block )
+		return OAES_RET_ARG1;
+
+	_temp[0x00] = block[0x00];
+	_temp[0x01] = block[0x0d];
+	_temp[0x02] = block[0x0a];
+	_temp[0x03] = block[0x07];
+	_temp[0x04] = block[0x04];
+	_temp[0x05] = block[0x01];
+	_temp[0x06] = block[0x0e];
+	_temp[0x07] = block[0x0b];
+	_temp[0x08] = block[0x08];
+	_temp[0x09] = block[0x05];
+	_temp[0x0a] = block[0x02];
+	_temp[0x0b] = block[0x0f];
+	_temp[0x0c] = block[0x0c];
+	_temp[0x0d] = block[0x09];
+	_temp[0x0e] = block[0x06];
+	_temp[0x0f] = block[0x03];
+	memcpy( block, _temp, OAES_BLOCK_SIZE );
+	
+	return OAES_RET_SUCCESS;
+}
+
+static uint8_t oaes_gf_mul(uint8_t left, uint8_t right)
+{
+	size_t _x, _y;
+	
+	_x = _y = left;
+	_x &= 0x0f;
+	_y &= 0xf0;
+	_y >>= 4;
+	
+	switch( right )
+	{
+		case 0x02:
+			return oaes_gf_mul_2[_y][_x];
+			break;
+		case 0x03:
+			return oaes_gf_mul_3[_y][_x];
+			break;
+		case 0x09:
+			return oaes_gf_mul_9[_y][_x];
+			break;
+		case 0x0b:
+			return oaes_gf_mul_b[_y][_x];
+			break;
+		case 0x0d:
+			return oaes_gf_mul_d[_y][_x];
+			break;
+		case 0x0e:
+			return oaes_gf_mul_e[_y][_x];
+			break;
+		default:
+			return left;
+			break;
+	}
+}
+
+static OAES_RET oaes_mix_cols( uint8_t word[OAES_COL_LEN] )
+{
+	uint8_t _temp[OAES_COL_LEN];
+
+	if( NULL == word )
+		return OAES_RET_ARG1;
+	
+	_temp[0] = oaes_gf_mul(word[0], 0x02) ^ oaes_gf_mul( word[1], 0x03 ) ^
+			word[2] ^ word[3];
+	_temp[1] = word[0] ^ oaes_gf_mul( word[1], 0x02 ) ^
+			oaes_gf_mul( word[2], 0x03 ) ^ word[3];
+	_temp[2] = word[0] ^ word[1] ^
+			oaes_gf_mul( word[2], 0x02 ) ^ oaes_gf_mul( word[3], 0x03 );
+	_temp[3] = oaes_gf_mul( word[0], 0x03 ) ^ word[1] ^
+			word[2] ^ oaes_gf_mul( word[3], 0x02 );
+	memcpy( word, _temp, OAES_COL_LEN );
+	
+	return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_inv_mix_cols( uint8_t word[OAES_COL_LEN] )
+{
+	uint8_t _temp[OAES_COL_LEN];
+
+	if( NULL == word )
+		return OAES_RET_ARG1;
+	
+	_temp[0] = oaes_gf_mul( word[0], 0x0e ) ^ oaes_gf_mul( word[1], 0x0b ) ^
+			oaes_gf_mul( word[2], 0x0d ) ^ oaes_gf_mul( word[3], 0x09 );
+	_temp[1] = oaes_gf_mul( word[0], 0x09 ) ^ oaes_gf_mul( word[1], 0x0e ) ^
+			oaes_gf_mul( word[2], 0x0b ) ^ oaes_gf_mul( word[3], 0x0d );
+	_temp[2] = oaes_gf_mul( word[0], 0x0d ) ^ oaes_gf_mul( word[1], 0x09 ) ^
+			oaes_gf_mul( word[2], 0x0e ) ^ oaes_gf_mul( word[3], 0x0b );
+	_temp[3] = oaes_gf_mul( word[0], 0x0b ) ^ oaes_gf_mul( word[1], 0x0d ) ^
+			oaes_gf_mul( word[2], 0x09 ) ^ oaes_gf_mul( word[3], 0x0e );
+	memcpy( word, _temp, OAES_COL_LEN );
+	
+	return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_sprintf(
+		char * buf, size_t * buf_len, const uint8_t * data, size_t data_len )
+{
+	size_t _i, _buf_len_in;
+	char _temp[4];
+	
+	if( NULL == buf_len )
+		return OAES_RET_ARG2;
+
+	_buf_len_in = *buf_len;
+	*buf_len = data_len * 3 + data_len / OAES_BLOCK_SIZE + 1;
+	
+	if( NULL == buf )
+		return OAES_RET_SUCCESS;
+
+	if( *buf_len > _buf_len_in )
+		return OAES_RET_BUF;
+
+	if( NULL == data )
+		return OAES_RET_ARG3;
+
+	strcpy( buf, "" );
+	
+	for( _i = 0; _i < data_len; _i++ )
+	{
+		sprintf( _temp, "%02x ", data[_i] );
+		strcat( buf, _temp );
+		if( _i && 0 == ( _i + 1 ) % OAES_BLOCK_SIZE )
+			strcat( buf, "\n" );
+	}
+	
+	return OAES_RET_SUCCESS;
+}
+
+#ifdef OAES_HAVE_ISAAC
+static void oaes_get_seed( char buf[RANDSIZ + 1] )
+{
+	struct timeb timer;
+	struct tm *gmTimer;
+	char * _test = NULL;
+	
+	ftime (&timer);
+	gmTimer = gmtime( &timer.time );
+	_test = (char *) calloc( sizeof( char ), timer.millitm );
+	sprintf( buf, "%04d%02d%02d%02d%02d%02d%03d%p%d",
+		gmTimer->tm_year + 1900, gmTimer->tm_mon + 1, gmTimer->tm_mday,
+		gmTimer->tm_hour, gmTimer->tm_min, gmTimer->tm_sec, timer.millitm,
+		_test + timer.millitm, getpid() );
+	
+	if( _test )
+		free( _test );
+}
+#else
+static uint32_t oaes_get_seed()
+{
+	struct timeb timer;
+	struct tm *gmTimer;
+	char * _test = NULL;
+	uint32_t _ret = 0;
+	
+	ftime (&timer);
+	gmTimer = gmtime( &timer.time );
+	_test = (char *) calloc( sizeof( char ), timer.millitm );
+	_ret = gmTimer->tm_year + 1900 + gmTimer->tm_mon + 1 + gmTimer->tm_mday +
+			gmTimer->tm_hour + gmTimer->tm_min + gmTimer->tm_sec + timer.millitm +
+			(uint32_t) ( _test + timer.millitm ) + getpid();
+
+	if( _test )
+		free( _test );
+	
+	return _ret;
+}
+#endif // OAES_HAVE_ISAAC
+
+static OAES_RET oaes_key_destroy( oaes_key ** key )
+{
+	if( NULL == *key )
+		return OAES_RET_SUCCESS;
+	
+	if( (*key)->data )
+	{
+		free( (*key)->data );
+		(*key)->data = NULL;
+	}
+	
+	if( (*key)->exp_data )
+	{
+		free( (*key)->exp_data );
+		(*key)->exp_data = NULL;
+	}
+	
+	(*key)->data_len = 0;
+	(*key)->exp_data_len = 0;
+	(*key)->num_keys = 0;
+	(*key)->key_base = 0;
+	free( *key );
+	*key = NULL;
+	
+	return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_key_expand( OAES_CTX * ctx )
+{
+	size_t _i, _j;
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+	
+	if( NULL == _ctx->key )
+		return OAES_RET_NOKEY;
+	
+	_ctx->key->key_base = _ctx->key->data_len / OAES_RKEY_LEN;
+	_ctx->key->num_keys =  _ctx->key->key_base + OAES_ROUND_BASE;
+					
+	_ctx->key->exp_data_len = _ctx->key->num_keys * OAES_RKEY_LEN * OAES_COL_LEN;
+	_ctx->key->exp_data = (uint8_t *)
+			calloc( _ctx->key->exp_data_len, sizeof( uint8_t ));
+	
+	if( NULL == _ctx->key->exp_data )
+		return OAES_RET_MEM;
+	
+	// the first _ctx->key->data_len are a direct copy
+	memcpy( _ctx->key->exp_data, _ctx->key->data, _ctx->key->data_len );
+
+	// apply ExpandKey algorithm for remainder
+	for( _i = _ctx->key->key_base; _i < _ctx->key->num_keys * OAES_RKEY_LEN; _i++ )
+	{
+		uint8_t _temp[OAES_COL_LEN];
+		
+		memcpy( _temp,
+				_ctx->key->exp_data + ( _i - 1 ) * OAES_RKEY_LEN, OAES_COL_LEN );
+		
+		// transform key column
+		if( 0 == _i % _ctx->key->key_base )
+		{
+			oaes_word_rot_left( _temp );
+
+			for( _j = 0; _j < OAES_COL_LEN; _j++ )
+				oaes_sub_byte( _temp + _j );
+
+			_temp[0] = _temp[0] ^ oaes_gf_8[ _i / _ctx->key->key_base - 1 ];
+		}
+		else if( _ctx->key->key_base > 6 && 4 == _i % _ctx->key->key_base )
+		{
+			for( _j = 0; _j < OAES_COL_LEN; _j++ )
+				oaes_sub_byte( _temp + _j );
+		}
+		
+		for( _j = 0; _j < OAES_COL_LEN; _j++ )
+		{
+			_ctx->key->exp_data[ _i * OAES_RKEY_LEN + _j ] =
+					_ctx->key->exp_data[ ( _i - _ctx->key->key_base ) *
+					OAES_RKEY_LEN + _j ] ^ _temp[_j];
+		}
+	}
+	
+	return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_key_gen( OAES_CTX * ctx, size_t key_size )
+{
+	size_t _i;
+	oaes_key * _key = NULL;
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	OAES_RET _rc = OAES_RET_SUCCESS;
+	
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+	
+	_key = (oaes_key *) calloc( sizeof( oaes_key ), 1 );
+	
+	if( NULL == _key )
+		return OAES_RET_MEM;
+	
+	if( _ctx->key )
+		oaes_key_destroy( &(_ctx->key) );
+	
+	_key->data_len = key_size;
+	_key->data = (uint8_t *) calloc( key_size, sizeof( uint8_t ));
+	
+	if( NULL == _key->data )
+		return OAES_RET_MEM;
+	
+	for( _i = 0; _i < key_size; _i++ )
+#ifdef OAES_HAVE_ISAAC
+		_key->data[_i] = (uint8_t) rand( _ctx->rctx );
+#else
+		_key->data[_i] = (uint8_t) rand();
+#endif // OAES_HAVE_ISAAC
+	
+	_ctx->key = _key;
+	_rc = _rc || oaes_key_expand( ctx );
+	
+	if( _rc != OAES_RET_SUCCESS )
+	{
+		oaes_key_destroy( &(_ctx->key) );
+		return _rc;
+	}
+	
+	return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_key_gen_128( OAES_CTX * ctx )
+{
+	return oaes_key_gen( ctx, 16 );
+}
+
+OAES_RET oaes_key_gen_192( OAES_CTX * ctx )
+{
+	return oaes_key_gen( ctx, 24 );
+}
+
+OAES_RET oaes_key_gen_256( OAES_CTX * ctx )
+{
+	return oaes_key_gen( ctx, 32 );
+}
+
+OAES_RET oaes_key_export( OAES_CTX * ctx,
+		uint8_t * data, size_t * data_len )
+{
+	size_t _data_len_in;
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+	
+	if( NULL == _ctx->key )
+		return OAES_RET_NOKEY;
+	
+	if( NULL == data_len )
+		return OAES_RET_ARG3;
+
+	_data_len_in = *data_len;
+	// data + header
+	*data_len = _ctx->key->data_len + OAES_BLOCK_SIZE;
+
+	if( NULL == data )
+		return OAES_RET_SUCCESS;
+	
+	if( _data_len_in < *data_len )
+		return OAES_RET_BUF;
+	
+	// header
+	memcpy( data, oaes_header, OAES_BLOCK_SIZE );
+	data[5] = 0x01;
+	data[7] = _ctx->key->data_len;
+	memcpy( data + OAES_BLOCK_SIZE, _ctx->key->data, _ctx->key->data_len );
+	
+	return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_key_export_data( OAES_CTX * ctx,
+		uint8_t * data, size_t * data_len )
+{
+	size_t _data_len_in;
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+	
+	if( NULL == _ctx->key )
+		return OAES_RET_NOKEY;
+	
+	if( NULL == data_len )
+		return OAES_RET_ARG3;
+
+	_data_len_in = *data_len;
+	*data_len = _ctx->key->data_len;
+
+	if( NULL == data )
+		return OAES_RET_SUCCESS;
+	
+	if( _data_len_in < *data_len )
+		return OAES_RET_BUF;
+	
+	memcpy( data, _ctx->key->data, *data_len );
+	
+	return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_key_import( OAES_CTX * ctx,
+		const uint8_t * data, size_t data_len )
+{
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	OAES_RET _rc = OAES_RET_SUCCESS;
+	int _key_length;
+	
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+	
+	if( NULL == data )
+		return OAES_RET_ARG2;
+	
+	switch( data_len )
+	{
+		case 16 + OAES_BLOCK_SIZE:
+		case 24 + OAES_BLOCK_SIZE:
+		case 32 + OAES_BLOCK_SIZE:
+			break;
+		default:
+			return OAES_RET_ARG3;
+	}
+	
+	// header
+	if( 0 != memcmp( data, oaes_header, 4 ) )
+		return OAES_RET_HEADER;
+
+	// header version
+	switch( data[4] )
+	{
+		case 0x01:
+			break;
+		default:
+			return OAES_RET_HEADER;
+	}
+	
+	// header type
+	switch( data[5] )
+	{
+		case 0x01:
+			break;
+		default:
+			return OAES_RET_HEADER;
+	}
+	
+	// options
+	_key_length = data[7];
+	switch( _key_length )
+	{
+		case 16:
+		case 24:
+		case 32:
+			break;
+		default:
+			return OAES_RET_HEADER;
+	}
+	
+	if( data_len != (size_t)_key_length + OAES_BLOCK_SIZE )
+			return OAES_RET_ARG3;
+	
+	if( _ctx->key )
+		oaes_key_destroy( &(_ctx->key) );
+	
+	_ctx->key = (oaes_key *) calloc( sizeof( oaes_key ), 1 );
+	
+	if( NULL == _ctx->key )
+		return OAES_RET_MEM;
+	
+	_ctx->key->data_len = _key_length;
+	_ctx->key->data = (uint8_t *)
+			calloc( _key_length, sizeof( uint8_t ));
+	
+	if( NULL == _ctx->key->data )
+	{
+		oaes_key_destroy( &(_ctx->key) );
+		return OAES_RET_MEM;
+	}
+
+	memcpy( _ctx->key->data, data + OAES_BLOCK_SIZE, _key_length );
+	_rc = _rc || oaes_key_expand( ctx );
+	
+	if( _rc != OAES_RET_SUCCESS )
+	{
+		oaes_key_destroy( &(_ctx->key) );
+		return _rc;
+	}
+	
+	return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_key_import_data( OAES_CTX * ctx,
+		const uint8_t * data, size_t data_len )
+{
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	OAES_RET _rc = OAES_RET_SUCCESS;
+	
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+	
+	if( NULL == data )
+		return OAES_RET_ARG2;
+	
+	switch( data_len )
+	{
+		case 16:
+		case 24:
+		case 32:
+			break;
+		default:
+			return OAES_RET_ARG3;
+	}
+	
+	if( _ctx->key )
+		oaes_key_destroy( &(_ctx->key) );
+	
+	_ctx->key = (oaes_key *) calloc( sizeof( oaes_key ), 1 );
+	
+	if( NULL == _ctx->key )
+		return OAES_RET_MEM;
+	
+	_ctx->key->data_len = data_len;
+	_ctx->key->data = (uint8_t *)
+			calloc( data_len, sizeof( uint8_t ));
+	
+	if( NULL == _ctx->key->data )
+	{
+		oaes_key_destroy( &(_ctx->key) );
+		return OAES_RET_MEM;
+	}
+
+	memcpy( _ctx->key->data, data, data_len );
+	_rc = _rc || oaes_key_expand( ctx );
+	
+	if( _rc != OAES_RET_SUCCESS )
+	{
+		oaes_key_destroy( &(_ctx->key) );
+		return _rc;
+	}
+	
+	return OAES_RET_SUCCESS;
+}
+
+OAES_CTX * oaes_alloc()
+{
+	oaes_ctx * _ctx = (oaes_ctx *) calloc( sizeof( oaes_ctx ), 1 );
+	
+	if( NULL == _ctx )
+		return NULL;
+
+#ifdef OAES_HAVE_ISAAC
+	{
+		char _seed[RANDSIZ + 1];
+		
+		_ctx->rctx = (randctx *) calloc( sizeof( randctx ), 1 );
+
+		if( NULL == _ctx->rctx )
+		{
+			free( _ctx );
+			return NULL;
+		}
+
+		oaes_get_seed( _seed );
+		memset( _ctx->rctx->randrsl, 0, RANDSIZ );
+		memcpy( _ctx->rctx->randrsl, _seed, RANDSIZ );
+		randinit( _ctx->rctx, TRUE);
+	}
+#else
+		srand( oaes_get_seed() );
+#endif // OAES_HAVE_ISAAC
+
+	_ctx->key = NULL;
+	oaes_set_option( _ctx, OAES_OPTION_CBC, NULL );
+
+#ifdef OAES_DEBUG
+	_ctx->step_cb = NULL;
+	oaes_set_option( _ctx, OAES_OPTION_STEP_OFF, NULL );
+#endif // OAES_DEBUG
+
+	return (OAES_CTX *) _ctx;
+}
+
+OAES_RET oaes_free( OAES_CTX ** ctx )
+{
+	oaes_ctx ** _ctx = (oaes_ctx **) ctx;
+
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+	
+	if( NULL == *_ctx )
+		return OAES_RET_SUCCESS;
+	
+	if( (*_ctx)->key )
+		oaes_key_destroy( &((*_ctx)->key) );
+
+#ifdef OAES_HAVE_ISAAC
+	if( (*_ctx)->rctx )
+	{
+		free( (*_ctx)->rctx );
+		(*_ctx)->rctx = NULL;
+	}
+#endif // OAES_HAVE_ISAAC
+	
+	free( *_ctx );
+	*_ctx = NULL;
+
+	return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_set_option( OAES_CTX * ctx,
+		OAES_OPTION option, const void * value )
+{
+	size_t _i;
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+
+	switch( option )
+	{
+		case OAES_OPTION_ECB:
+			_ctx->options &= ~OAES_OPTION_CBC;
+			memset( _ctx->iv, 0, OAES_BLOCK_SIZE );
+			break;
+
+		case OAES_OPTION_CBC:
+			_ctx->options &= ~OAES_OPTION_ECB;
+			if( value )
+				memcpy( _ctx->iv, value, OAES_BLOCK_SIZE );
+			else
+			{
+				for( _i = 0; _i < OAES_BLOCK_SIZE; _i++ )
+#ifdef OAES_HAVE_ISAAC
+					_ctx->iv[_i] = (uint8_t) rand( _ctx->rctx );
+#else
+					_ctx->iv[_i] = (uint8_t) rand();
+#endif // OAES_HAVE_ISAAC
+			}
+			break;
+
+#ifdef OAES_DEBUG
+
+		case OAES_OPTION_STEP_ON:
+			if( value )
+			{
+				_ctx->options &= ~OAES_OPTION_STEP_OFF;
+				_ctx->step_cb = value;
+			}
+			else
+			{
+				_ctx->options &= ~OAES_OPTION_STEP_ON;
+				_ctx->options |= OAES_OPTION_STEP_OFF;
+				_ctx->step_cb = NULL;
+				return OAES_RET_ARG3;
+			}
+			break;
+
+		case OAES_OPTION_STEP_OFF:
+			_ctx->options &= ~OAES_OPTION_STEP_ON;
+			_ctx->step_cb = NULL;
+			break;
+
+#endif // OAES_DEBUG
+
+		default:
+			return OAES_RET_ARG2;
+	}
+
+	_ctx->options |= option;
+
+	return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_encrypt_block(
+		OAES_CTX * ctx, uint8_t * c, size_t c_len )
+{
+	size_t _i, _j;
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+	
+	if( NULL == c )
+		return OAES_RET_ARG2;
+	
+	if( c_len != OAES_BLOCK_SIZE )
+		return OAES_RET_ARG3;
+	
+	if( NULL == _ctx->key )
+		return OAES_RET_NOKEY;
+	
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+		_ctx->step_cb( c, "input", 1, NULL );
+#endif // OAES_DEBUG
+
+	// AddRoundKey(State, K0)
+	for( _i = 0; _i < c_len; _i++ )
+		c[_i] = c[_i] ^ _ctx->key->exp_data[_i];
+	
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+	{
+		_ctx->step_cb( _ctx->key->exp_data, "k_sch", 1, NULL );
+		_ctx->step_cb( c, "k_add", 1, NULL );
+	}
+#endif // OAES_DEBUG
+
+	// for round = 1 step 1 to Nr–1
+	for( _i = 1; _i < _ctx->key->num_keys - 1; _i++ )
+	{
+		// SubBytes(state)
+		for( _j = 0; _j < c_len; _j++ )
+			oaes_sub_byte( c + _j );
+
+#ifdef OAES_DEBUG
+		if( _ctx->step_cb )
+			_ctx->step_cb( c, "s_box", _i, NULL );
+#endif // OAES_DEBUG
+
+		// ShiftRows(state)
+		oaes_shift_rows( c );
+		
+#ifdef OAES_DEBUG
+		if( _ctx->step_cb )
+			_ctx->step_cb( c, "s_row", _i, NULL );
+#endif // OAES_DEBUG
+
+		// MixColumns(state)
+		oaes_mix_cols( c );
+		oaes_mix_cols( c + 4 );
+		oaes_mix_cols( c + 8 );
+		oaes_mix_cols( c + 12 );
+		
+#ifdef OAES_DEBUG
+		if( _ctx->step_cb )
+			_ctx->step_cb( c, "m_col", _i, NULL );
+#endif // OAES_DEBUG
+
+		// AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
+		for( _j = 0; _j < c_len; _j++ )
+			c[_j] = c[_j] ^
+					_ctx->key->exp_data[_i * OAES_RKEY_LEN * OAES_COL_LEN + _j];
+
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+	{
+		_ctx->step_cb( _ctx->key->exp_data + _i * OAES_RKEY_LEN * OAES_COL_LEN,
+				"k_sch", _i, NULL );
+		_ctx->step_cb( c, "k_add", _i, NULL );
+	}
+#endif // OAES_DEBUG
+
+	}
+	
+	// SubBytes(state)
+	for( _i = 0; _i < c_len; _i++ )
+		oaes_sub_byte( c + _i );
+	
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+		_ctx->step_cb( c, "s_box", _ctx->key->num_keys - 1, NULL );
+#endif // OAES_DEBUG
+
+	// ShiftRows(state)
+	oaes_shift_rows( c );
+
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+		_ctx->step_cb( c, "s_row", _ctx->key->num_keys - 1, NULL );
+#endif // OAES_DEBUG
+
+	// AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+	for( _i = 0; _i < c_len; _i++ )
+		c[_i] = c[_i] ^ _ctx->key->exp_data[
+				( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN + _i ];
+
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+	{
+		_ctx->step_cb( _ctx->key->exp_data +
+				( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN,
+				"k_sch", _ctx->key->num_keys - 1, NULL );
+		_ctx->step_cb( c, "output", _ctx->key->num_keys - 1, NULL );
+	}
+#endif // OAES_DEBUG
+
+	return OAES_RET_SUCCESS;
+}
+
+static OAES_RET oaes_decrypt_block(
+		OAES_CTX * ctx, uint8_t * c, size_t c_len )
+{
+	size_t _i, _j;
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+	
+	if( NULL == c )
+		return OAES_RET_ARG2;
+	
+	if( c_len != OAES_BLOCK_SIZE )
+		return OAES_RET_ARG3;
+	
+	if( NULL == _ctx->key )
+		return OAES_RET_NOKEY;
+	
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+		_ctx->step_cb( c, "iinput", _ctx->key->num_keys - 1, NULL );
+#endif // OAES_DEBUG
+
+	// AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
+	for( _i = 0; _i < c_len; _i++ )
+		c[_i] = c[_i] ^ _ctx->key->exp_data[
+				( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN + _i ];
+
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+	{
+		_ctx->step_cb( _ctx->key->exp_data +
+				( _ctx->key->num_keys - 1 ) * OAES_RKEY_LEN * OAES_COL_LEN,
+				"ik_sch", _ctx->key->num_keys - 1, NULL );
+		_ctx->step_cb( c, "ik_add", _ctx->key->num_keys - 1, NULL );
+	}
+#endif // OAES_DEBUG
+
+	for( _i = _ctx->key->num_keys - 2; _i > 0; _i-- )
+	{
+		// InvShiftRows(state)
+		oaes_inv_shift_rows( c );
+
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+		_ctx->step_cb( c, "is_row", _i, NULL );
+#endif // OAES_DEBUG
+
+		// InvSubBytes(state)
+		for( _j = 0; _j < c_len; _j++ )
+			oaes_inv_sub_byte( c + _j );
+	
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+		_ctx->step_cb( c, "is_box", _i, NULL );
+#endif // OAES_DEBUG
+
+		// AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
+		for( _j = 0; _j < c_len; _j++ )
+			c[_j] = c[_j] ^
+					_ctx->key->exp_data[_i * OAES_RKEY_LEN * OAES_COL_LEN + _j];
+		
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+	{
+		_ctx->step_cb( _ctx->key->exp_data + _i * OAES_RKEY_LEN * OAES_COL_LEN,
+				"ik_sch", _i, NULL );
+		_ctx->step_cb( c, "ik_add", _i, NULL );
+	}
+#endif // OAES_DEBUG
+
+		// InvMixColums(state)
+		oaes_inv_mix_cols( c );
+		oaes_inv_mix_cols( c + 4 );
+		oaes_inv_mix_cols( c + 8 );
+		oaes_inv_mix_cols( c + 12 );
+
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+		_ctx->step_cb( c, "im_col", _i, NULL );
+#endif // OAES_DEBUG
+
+	}
+
+	// InvShiftRows(state)
+	oaes_inv_shift_rows( c );
+
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+		_ctx->step_cb( c, "is_row", 1, NULL );
+#endif // OAES_DEBUG
+
+	// InvSubBytes(state)
+	for( _i = 0; _i < c_len; _i++ )
+		oaes_inv_sub_byte( c + _i );
+
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+		_ctx->step_cb( c, "is_box", 1, NULL );
+#endif // OAES_DEBUG
+
+	// AddRoundKey(state, w[0, Nb-1])
+	for( _i = 0; _i < c_len; _i++ )
+		c[_i] = c[_i] ^ _ctx->key->exp_data[_i];
+	
+#ifdef OAES_DEBUG
+	if( _ctx->step_cb )
+	{
+		_ctx->step_cb( _ctx->key->exp_data, "ik_sch", 1, NULL );
+		_ctx->step_cb( c, "ioutput", 1, NULL );
+	}
+#endif // OAES_DEBUG
+
+	return OAES_RET_SUCCESS;
+}
+
+OAES_RET oaes_encrypt( OAES_CTX * ctx,
+		const uint8_t * m, size_t m_len, uint8_t * c, size_t * c_len )
+{
+	size_t _i, _j, _c_len_in, _c_data_len;
+	size_t _pad_len = m_len % OAES_BLOCK_SIZE == 0 ?
+			0 : OAES_BLOCK_SIZE - m_len % OAES_BLOCK_SIZE;
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	OAES_RET _rc = OAES_RET_SUCCESS;
+	uint8_t _flags = _pad_len ? OAES_FLAG_PAD : 0;
+	
+	if( NULL == _ctx )
+		return OAES_RET_ARG1;
+	
+	if( NULL == m )
+		return OAES_RET_ARG2;
+	
+	if( NULL == c_len )
+		return OAES_RET_ARG5;
+	
+	_c_len_in = *c_len;
+	// data + pad
+	_c_data_len = m_len + _pad_len;
+	// header + iv + data + pad
+	*c_len = 2 * OAES_BLOCK_SIZE + m_len + _pad_len;
+
+	if( NULL == c )
+		return OAES_RET_SUCCESS;
+	
+	if( _c_len_in < *c_len )
+		return OAES_RET_BUF;
+	
+	if( NULL == _ctx->key )
+		return OAES_RET_NOKEY;
+	
+	// header
+	memcpy(c, oaes_header, OAES_BLOCK_SIZE );
+	memcpy(c + 6, &_ctx->options, sizeof(_ctx->options));
+	memcpy(c + 8, &_flags, sizeof(_flags));
+	// iv
+	memcpy(c + OAES_BLOCK_SIZE, _ctx->iv, OAES_BLOCK_SIZE );
+	// data
+	memcpy(c + 2 * OAES_BLOCK_SIZE, m, m_len );
+	
+	for( _i = 0; _i < _c_data_len; _i += OAES_BLOCK_SIZE )
+	{
+		uint8_t _block[OAES_BLOCK_SIZE];
+		size_t _block_size = min( m_len - _i, OAES_BLOCK_SIZE );
+
+		memcpy( _block, c + 2 * OAES_BLOCK_SIZE + _i, _block_size );
+		
+		// insert pad
+		for( _j = 0; _j < OAES_BLOCK_SIZE - _block_size; _j++ )
+			_block[ _block_size + _j ] = _j + 1;
+	
+		// CBC
+		if( _ctx->options & OAES_OPTION_CBC )
+		{
+			for( _j = 0; _j < OAES_BLOCK_SIZE; _j++ )
+				_block[_j] = _block[_j] ^ _ctx->iv[_j];
+		}
+
+		_rc = _rc ||
+				oaes_encrypt_block( ctx, _block, OAES_BLOCK_SIZE );
+		memcpy( c + 2 * OAES_BLOCK_SIZE + _i, _block, OAES_BLOCK_SIZE );
+		
+		if( _ctx->options & OAES_OPTION_CBC )
+			memcpy( _ctx->iv, _block, OAES_BLOCK_SIZE );
+	}
+	
+	return _rc;
+}
+
+OAES_RET oaes_decrypt( OAES_CTX * ctx,
+		const uint8_t * c, size_t c_len, uint8_t * m, size_t * m_len )
+{
+	size_t _i, _j, _m_len_in;
+	oaes_ctx * _ctx = (oaes_ctx *) ctx;
+	OAES_RET _rc = OAES_RET_SUCCESS;
+	uint8_t _iv[OAES_BLOCK_SIZE];
+	uint8_t _flags;
+	OAES_OPTION _options;
+	
+	if( NULL == ctx )
+		return OAES_RET_ARG1;
+	
+	if( NULL == c )
+		return OAES_RET_ARG2;
+	
+	if( c_len % OAES_BLOCK_SIZE )
+		return OAES_RET_ARG3;
+	
+	if( NULL == m_len )
+		return OAES_RET_ARG5;
+	
+	_m_len_in = *m_len;
+	*m_len = c_len - 2 * OAES_BLOCK_SIZE;
+	
+	if( NULL == m )
+		return OAES_RET_SUCCESS;
+	
+	if( _m_len_in < *m_len )
+		return OAES_RET_BUF;
+	
+	if( NULL == _ctx->key )
+		return OAES_RET_NOKEY;
+	
+	// header
+	if( 0 != memcmp( c, oaes_header, 4 ) )
+		return OAES_RET_HEADER;
+
+	// header version
+	switch( c[4] )
+	{
+		case 0x01:
+			break;
+		default:
+			return OAES_RET_HEADER;
+	}
+	
+	// header type
+	switch( c[5] )
+	{
+		case 0x02:
+			break;
+		default:
+			return OAES_RET_HEADER;
+	}
+	
+	// options
+	memcpy(&_options, c + 6, sizeof(_options));
+	// validate that all options are valid
+	if( _options & ~(
+			  OAES_OPTION_ECB
+			| OAES_OPTION_CBC
+#ifdef OAES_DEBUG
+			| OAES_OPTION_STEP_ON
+			| OAES_OPTION_STEP_OFF
+#endif // OAES_DEBUG
+			) )
+		return OAES_RET_HEADER;
+	if( ( _options & OAES_OPTION_ECB ) &&
+			( _options & OAES_OPTION_CBC ) )
+		return OAES_RET_HEADER;
+	if( _options == OAES_OPTION_NONE )
+		return OAES_RET_HEADER;
+	
+	// flags
+	memcpy(&_flags, c + 8, sizeof(_flags));
+	// validate that all flags are valid
+	if( _flags & ~(
+			  OAES_FLAG_PAD
+			) )
+		return OAES_RET_HEADER;
+
+	// iv
+	memcpy( _iv, c + OAES_BLOCK_SIZE, OAES_BLOCK_SIZE);
+	// data + pad
+	memcpy( m, c + 2 * OAES_BLOCK_SIZE, *m_len );
+	
+	for( _i = 0; _i < *m_len; _i += OAES_BLOCK_SIZE )
+	{
+		if( ( _options & OAES_OPTION_CBC ) && _i > 0 )
+			memcpy( _iv, c + OAES_BLOCK_SIZE + _i, OAES_BLOCK_SIZE );
+		
+		_rc = _rc ||
+				oaes_decrypt_block( ctx, m + _i, min( *m_len - _i, OAES_BLOCK_SIZE ) );
+		
+		// CBC
+		if( _options & OAES_OPTION_CBC )
+		{
+			for( _j = 0; _j < OAES_BLOCK_SIZE; _j++ )
+				m[ _i + _j ] = m[ _i + _j ] ^ _iv[_j];
+		}
+	}
+	
+	// remove pad
+	if( _flags & OAES_FLAG_PAD )
+	{
+		int _is_pad = 1;
+		size_t _temp = (size_t) m[*m_len - 1];
+
+		if( _temp  <= 0x00 || _temp > 0x0f )
+			return OAES_RET_HEADER;
+		for( _i = 0; _i < _temp; _i++ )
+			if( m[*m_len - 1 - _i] != _temp - _i )
+				_is_pad = 0;
+		if( _is_pad )
+		{
+			memset( m + *m_len - _temp, 0, _temp );
+			*m_len -= _temp;
+		}
+		else
+			return OAES_RET_HEADER;
+	}
+	
+	return OAES_RET_SUCCESS;
+}
diff --git a/openaes/test/test_encrypt.c b/openaes/test/test_encrypt.c
new file mode 100644
index 0000000..b229f2d
--- /dev/null
+++ b/openaes/test/test_encrypt.c
@@ -0,0 +1,229 @@
+/* 
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *   - Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "oaes_lib.h"
+
+void usage(const char * exe_name)
+{
+	if( NULL == exe_name )
+		return;
+	
+	printf(
+			"Usage:\n"
+			"\t%s [-ecb] [-key < 128 | 192 | 256 >] <text>\n",
+			exe_name
+	);
+}
+
+int main(int argc, char** argv)
+{
+	size_t _i;
+	OAES_CTX * ctx = NULL;
+	uint8_t *_encbuf, *_decbuf;
+	size_t _encbuf_len, _decbuf_len, _buf_len;
+	char *_buf;
+	short _is_ecb = 0;
+	char * _text = NULL;
+	int _key_len = 128;
+	
+	if( argc < 2 )
+	{
+		usage( argv[0] );
+		return EXIT_FAILURE;
+	}
+
+	for( _i = 1; _i < argc; _i++ )
+	{
+		int _found = 0;
+		
+		if( 0 == strcmp( argv[_i], "-ecb" ) )
+		{
+			_found = 1;
+			_is_ecb = 1;
+		}
+		
+		if( 0 == strcmp( argv[_i], "-key" ) )
+		{
+			_found = 1;
+			_i++; // len
+			if( _i >= argc )
+			{
+				printf("Error: No value specified for '-%s'.\n",
+						"key");
+				usage( argv[0] );
+				return EXIT_FAILURE;
+			}
+			_key_len = atoi( argv[_i] );
+			switch( _key_len )
+			{
+				case 128:
+				case 192:
+				case 256:
+					break;
+				default:
+					printf("Error: Invalid value [%d] specified for '-%s'.\n",
+							_key_len, "key");
+					usage( argv[0] );
+					return EXIT_FAILURE;
+			}
+		}
+		
+		if( 0 == _found )
+		{
+			if( _text )
+			{
+				printf("Error: Invalid option '%s'.\n", argv[_i]);
+				usage( argv[0] );
+				return EXIT_FAILURE;
+			}
+			else
+			{
+				_text = (char *) calloc(strlen( argv[_i] ) + 1, sizeof(char));
+				if( NULL == _text )
+				{
+					printf("Error: Failed to allocate memory.\n", argv[_i]);
+					return EXIT_FAILURE;
+				}
+				strcpy( _text, argv[_i] );
+			}
+		}			
+	}
+
+	if( NULL == _text )
+	{
+		usage( argv[0] );
+		return EXIT_FAILURE;
+	}
+
+	oaes_sprintf( NULL, &_buf_len,
+			(const uint8_t *)_text, strlen( _text ) );
+	_buf = (char *) calloc(_buf_len, sizeof(char));
+	printf( "\n***** plaintext  *****\n" );
+	if( _buf )
+	{
+		oaes_sprintf( _buf, &_buf_len,
+				(const uint8_t *)_text, strlen( _text ) );
+		printf( "%s", _buf );
+	}
+	printf( "\n**********************\n" );
+	free( _buf );
+	
+	ctx = oaes_alloc();
+	if( NULL == ctx )
+	{
+		printf("Error: Failed to initialize OAES.\n");
+		free( _text );
+		return EXIT_FAILURE;
+	}
+	if( _is_ecb )
+		if( OAES_RET_SUCCESS != oaes_set_option( ctx, OAES_OPTION_ECB, NULL ) )
+			printf("Error: Failed to set OAES options.\n");
+	switch( _key_len )
+	{
+		case 128:
+			if( OAES_RET_SUCCESS != oaes_key_gen_128(ctx) )
+				printf("Error: Failed to generate OAES %d bit key.\n", _key_len);
+			break;
+		case 192:
+			if( OAES_RET_SUCCESS != oaes_key_gen_192(ctx) )
+				printf("Error: Failed to generate OAES %d bit key.\n", _key_len);
+			break;
+		case 256:
+			if( OAES_RET_SUCCESS != oaes_key_gen_256(ctx) )
+				printf("Error: Failed to generate OAES %d bit key.\n", _key_len);
+			break;
+		default:
+			break;
+	}
+
+	if( OAES_RET_SUCCESS != oaes_encrypt( ctx,
+			(const uint8_t *)_text, strlen( _text ), NULL, &_encbuf_len ) )
+		printf("Error: Failed to retrieve required buffer size for encryption.\n");
+	_encbuf = (uint8_t *) calloc( _encbuf_len, sizeof(uint8_t) );
+	if( NULL == _encbuf )
+	{
+		printf( "Error: Failed to allocate memory.\n" );
+		free( _text );
+		return EXIT_FAILURE;
+	}
+	if( OAES_RET_SUCCESS != oaes_encrypt( ctx,
+			(const uint8_t *)_text, strlen( _text ), _encbuf, &_encbuf_len ) )
+		printf("Error: Encryption failed.\n");
+
+	if( OAES_RET_SUCCESS != oaes_decrypt( ctx,
+			_encbuf, _encbuf_len, NULL, &_decbuf_len ) )
+		printf("Error: Failed to retrieve required buffer size for encryption.\n");
+	_decbuf = (uint8_t *) calloc( _decbuf_len, sizeof(uint8_t) );
+	if( NULL == _decbuf )
+	{
+		printf( "Error: Failed to allocate memory.\n" );
+		free( _text );
+		free( _encbuf );
+		return EXIT_FAILURE;
+	}
+	if( OAES_RET_SUCCESS != oaes_decrypt( ctx,
+			_encbuf, _encbuf_len, _decbuf, &_decbuf_len ) )
+		printf("Error: Decryption failed.\n");
+
+	if( OAES_RET_SUCCESS !=  oaes_free( &ctx ) )
+		printf("Error: Failed to uninitialize OAES.\n");
+	
+	oaes_sprintf( NULL, &_buf_len, _encbuf, _encbuf_len );
+	_buf = (char *) calloc(_buf_len, sizeof(char));
+	printf( "\n***** cyphertext *****\n" );
+	if( _buf )
+	{
+		oaes_sprintf( _buf, &_buf_len, _encbuf, _encbuf_len );
+		printf( "%s", _buf );
+	}
+	printf( "\n**********************\n" );
+	free( _buf );
+	
+	oaes_sprintf( NULL, &_buf_len, _decbuf, _decbuf_len );
+	_buf = (char *) calloc(_buf_len, sizeof( char));
+	printf( "\n***** plaintext  *****\n" );
+	if( _buf )
+	{
+		oaes_sprintf( _buf, &_buf_len, _decbuf, _decbuf_len );
+		printf( "%s", _buf );
+	}
+	printf( "\n**********************\n\n" );
+	free( _buf );
+	
+	free( _encbuf );
+	free( _decbuf );
+	free( _text );
+
+	return (EXIT_SUCCESS);
+}
diff --git a/openaes/test/test_keys.c b/openaes/test/test_keys.c
new file mode 100644
index 0000000..6a8d625
--- /dev/null
+++ b/openaes/test/test_keys.c
@@ -0,0 +1,248 @@
+/* 
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *   - Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "oaes_lib.h"
+
+/*
+ * 
+ */
+int main(int argc, char** argv) {
+
+	OAES_CTX * ctx = NULL;
+	uint8_t * _buf;
+	size_t _data_len;
+	FILE * f = NULL;
+	OAES_RET _rc;
+	
+	if( NULL == ( ctx = oaes_alloc() ) )
+	{
+		printf( "Error: Initialization failed.\n" );
+		return 1;
+	}
+	
+	/* ************** Generate 128-bit key and export it **************
+	 * ****************************************************************/
+	if( OAES_RET_SUCCESS != ( _rc = oaes_key_gen_128(ctx) ) )
+	{
+		printf( "Error: Failed to generate 128-bit key [%d].\n", _rc );
+		oaes_free(&ctx);
+		return 1;
+	}
+	
+	if( OAES_RET_SUCCESS != ( _rc = oaes_key_export(ctx, NULL, &_data_len) ) )
+	{
+		printf( "Error: Failed to retrieve key length [%d].\n", _rc );
+		oaes_free(&ctx);
+		return 1;
+	}
+	
+	_buf = (uint8_t *) calloc(_data_len, sizeof(uint8_t));
+	if( _buf )
+	{
+		if( OAES_RET_SUCCESS != ( _rc = oaes_key_export(ctx, _buf, &_data_len) ) )
+		{
+			printf("Error: Failed to export key [%d].\n", _rc);
+			free(_buf);
+			oaes_free(&ctx);
+			return 1;
+		}
+		
+		f = fopen( "key_128", "wb" );
+		if( f )
+		{
+			fwrite(_buf, _data_len, sizeof(uint8_t), f);
+			fclose(f);
+		}
+		free(_buf);
+	}
+	
+	/* ************** Generate 192-bit key and export it **************
+	 * ****************************************************************/
+	if( OAES_RET_SUCCESS != ( _rc = oaes_key_gen_192(ctx) ) )
+	{
+		printf( "Error: Failed to generate 192-bit key [%d].\n", _rc );
+		oaes_free(&ctx);
+		return 1;
+	}
+	
+	if( OAES_RET_SUCCESS != ( _rc = oaes_key_export(ctx, NULL, &_data_len) ) )
+	{
+		printf( "Error: Failed to retrieve key length [%d].\n", _rc );
+		oaes_free(&ctx);
+		return 1;
+	}
+	
+	_buf = (uint8_t *) calloc(_data_len, sizeof(uint8_t));
+	if( _buf )
+	{
+		if( OAES_RET_SUCCESS != ( _rc = oaes_key_export(ctx, _buf, &_data_len) ) )
+		{
+			printf("Error: Failed to export key [%d].\n", _rc);
+			free(_buf);
+			oaes_free(&ctx);
+			return 1;
+		}
+		
+		f = fopen("key_192", "wb");
+		if( f )
+		{
+			fwrite(_buf, _data_len, sizeof(uint8_t), f);
+			fclose(f);
+		}
+		free(_buf);
+	}
+	
+	/* ************** Generate 256-bit key and export it **************
+	 * ****************************************************************/
+	if( OAES_RET_SUCCESS != ( _rc = oaes_key_gen_256(ctx) ) )
+	{
+		printf("Error: Failed to generate 256-bit key [%d].\n", _rc);
+		oaes_free(&ctx);
+		return 1;
+	}
+	
+	if( OAES_RET_SUCCESS != ( _rc = oaes_key_export(ctx, NULL, &_data_len) ) )
+	{
+		printf("Error: Failed to retrieve key length [%d].\n", _rc);
+		oaes_free(&ctx);
+		return 1;
+	}
+	
+	_buf = (uint8_t *) calloc(_data_len, sizeof(uint8_t));
+	if( _buf )
+	{
+		if( OAES_RET_SUCCESS != ( _rc = oaes_key_export(ctx, _buf, &_data_len) ) )
+		{
+			printf("Error: Failed to export key [%d].\n", _rc);
+			free(_buf);
+			oaes_free(&ctx);
+			return 1;
+		}
+		
+		f = fopen("key_256", "wb");
+		if( f )
+		{
+			fwrite(_buf, _data_len, sizeof(uint8_t), f);
+			fclose(f);
+		}
+		free(_buf);
+	}
+	
+	/* ********************** Import 128-bit key **********************
+	 * ****************************************************************/
+	f = fopen("key_128", "rb");
+	if( f )
+	{
+		fseek(f, 0L, SEEK_END);
+		_data_len = ftell(f);
+		fseek(f, 0L, SEEK_SET);
+		_buf = (uint8_t *) calloc(_data_len, sizeof(uint8_t));
+		if( _buf )
+		{
+			fread(_buf, _data_len, sizeof(uint8_t), f);
+			
+			if( OAES_RET_SUCCESS !=
+					( _rc = oaes_key_import(ctx, _buf, _data_len) ) )
+			{
+				printf( "Error: Failed to import key [%d].\n", _rc );
+				free(_buf);
+				fclose(f);
+				oaes_free(&ctx);
+				return 1;
+			}
+			
+			free(_buf);
+		}
+		fclose(f);
+	}
+	
+	/* ********************** Import 192-bit key **********************
+	 * ****************************************************************/
+	f = fopen("key_192", "rb");
+	if( f )
+	{
+		fseek(f, 0L, SEEK_END);
+		_data_len = ftell(f);
+		fseek(f, 0L, SEEK_SET);
+		_buf = (uint8_t *) calloc(_data_len, sizeof(uint8_t));
+		if( _buf )
+		{
+			fread(_buf, _data_len, sizeof(uint8_t), f);
+			
+			if( OAES_RET_SUCCESS !=
+					( _rc = oaes_key_import(ctx, _buf, _data_len) ) )
+			{
+				printf("Error: Failed to import key [%d].\n", _rc);
+				free(_buf);
+				fclose(f);
+				oaes_free(&ctx);
+				return 1;
+			}
+			
+			free(_buf);
+		}
+		fclose(f);
+	}
+	
+	/* ********************** Import 256-bit key **********************
+	 * ****************************************************************/
+	f = fopen("key_256", "rb");
+	if( f )
+	{
+		fseek(f, 0L, SEEK_END);
+		_data_len = ftell(f);
+		fseek(f, 0L, SEEK_SET);
+		_buf = (uint8_t *) calloc(_data_len, sizeof(uint8_t));
+		if( _buf )
+		{
+			fread(_buf, _data_len, sizeof(uint8_t), f);
+			
+			if( OAES_RET_SUCCESS !=
+					( _rc = oaes_key_import(ctx, _buf, _data_len) ) )
+			{
+				printf("Error: Failed to import key [%d].\n", _rc);
+				free(_buf);
+				fclose(f);
+				oaes_free(&ctx);
+				return 1;
+			}
+			
+			free(_buf);
+		}
+		fclose(f);
+	}
+	
+	oaes_free(&ctx);
+
+	return (EXIT_SUCCESS);
+}
diff --git a/openaes/test/test_performance.c b/openaes/test/test_performance.c
new file mode 100644
index 0000000..a005c58
--- /dev/null
+++ b/openaes/test/test_performance.c
@@ -0,0 +1,200 @@
+/* 
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *   - Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "oaes_lib.h"
+
+void usage(const char * exe_name)
+{
+	if( NULL == exe_name )
+		return;
+	
+	printf(
+			"Usage:\n"
+			"\t%s [-ecb] [-key < 128 | 192 | 256 >] [-data <data_len>]\n",
+			exe_name
+	);
+}
+
+/*
+ * 
+ */
+int main(int argc, char** argv) {
+
+	size_t _i, _j;
+	time_t _time_start, _time_end;
+	OAES_CTX * ctx = NULL;
+	uint8_t *_encbuf, *_decbuf;
+	size_t _encbuf_len, _decbuf_len;
+	uint8_t _buf[1024 * 1024];
+	short _is_ecb = 0;
+	int _key_len = 128;
+	size_t _data_len = 64;
+	
+	for( _i = 1; _i < argc; _i++ )
+	{
+		int _found = 0;
+		
+		if( 0 == strcmp( argv[_i], "-ecb" ) )
+		{
+			_found = 1;
+			_is_ecb = 1;
+		}
+		
+		if( 0 == strcmp( argv[_i], "-key" ) )
+		{
+			_found = 1;
+			_i++; // key_len
+			if( _i >= argc )
+			{
+				printf("Error: No value specified for '-%s'.\n",
+						"key");
+				usage( argv[0] );
+				return 1;
+			}
+			_key_len = atoi( argv[_i] );
+			switch( _key_len )
+			{
+				case 128:
+				case 192:
+				case 256:
+					break;
+				default:
+					printf("Error: Invalid value [%d] specified for '-%s'.\n",
+							_key_len, "key");
+					usage( argv[0] );
+					return 1;
+			}
+		}
+		
+		if( 0 == strcmp( argv[_i], "-data" ) )
+		{
+			_found = 1;
+			_i++; // data_len
+			if( _i >= argc )
+			{
+				printf("Error: No value specified for '-%s'.\n",
+						"data");
+				usage( argv[0] );
+				return 1;
+			}
+			_data_len = atoi( argv[_i] );
+		}
+		
+		if( 0 == _found )
+		{
+			printf("Error: Invalid option '%s'.\n", argv[_i]);
+			usage( argv[0] );
+			return 1;
+		}			
+	}
+
+	// generate random test data
+	time( &_time_start );
+	srand( _time_start );
+	for( _i = 0; _i < 1024 * 1024; _i++ )
+		_buf[_i] = rand();
+	
+	ctx = oaes_alloc();
+	if( NULL == ctx )
+	{
+		printf("Error: Failed to initialize OAES.\n");
+		return EXIT_FAILURE;
+	}
+	if( _is_ecb )
+		if( OAES_RET_SUCCESS != oaes_set_option( ctx, OAES_OPTION_ECB, NULL ) )
+			printf("Error: Failed to set OAES options.\n");
+	switch( _key_len )
+	{
+		case 128:
+			if( OAES_RET_SUCCESS != oaes_key_gen_128(ctx) )
+				printf("Error: Failed to generate OAES %d bit key.\n", _key_len);
+			break;
+		case 192:
+			if( OAES_RET_SUCCESS != oaes_key_gen_192(ctx) )
+				printf("Error: Failed to generate OAES %d bit key.\n", _key_len);
+			break;
+		case 256:
+			if( OAES_RET_SUCCESS != oaes_key_gen_256(ctx) )
+				printf("Error: Failed to generate OAES %d bit key.\n", _key_len);
+			break;
+		default:
+			break;
+	}
+
+	if( OAES_RET_SUCCESS != oaes_encrypt( ctx,
+			(const uint8_t *)_buf, 1024 * 1024, NULL, &_encbuf_len ) )
+		printf("Error: Failed to retrieve required buffer size for encryption.\n");
+	_encbuf = (uint8_t *) calloc( _encbuf_len, sizeof( char ) );
+	if( NULL == _encbuf )
+	{
+		printf( "Error: Failed to allocate memory.\n" );
+		return EXIT_FAILURE;
+	}
+
+	if( OAES_RET_SUCCESS != oaes_decrypt( ctx,
+			_encbuf, _encbuf_len, NULL, &_decbuf_len ) )
+		printf("Error: Failed to retrieve required buffer size for encryption.\n");
+	_decbuf = (uint8_t *) calloc( _decbuf_len, sizeof( char ) );
+	if( NULL == _decbuf )
+	{
+		free( _encbuf );
+		printf( "Error: Failed to allocate memory.\n" );
+		return EXIT_FAILURE;
+	}
+
+	time( &_time_start );
+	
+	for( _i = 0; _i < _data_len; _i++ )
+	{
+		if( OAES_RET_SUCCESS != oaes_encrypt( ctx,
+				(const uint8_t *)_buf, 1024 * 1024, _encbuf, &_encbuf_len ) )
+			printf("Error: Encryption failed.\n");
+		if( OAES_RET_SUCCESS !=  oaes_decrypt( ctx,
+				_encbuf, _encbuf_len, _decbuf, &_decbuf_len ) )
+			printf("Error: Decryption failed.\n");
+	}
+	
+	time( &_time_end );
+	printf( "Test encrypt and decrypt:\n\ttime: %lld seconds\n\tdata: %ld MB"
+			"\n\tkey: %d bits\n\tmode: %s\n",
+			_time_end - _time_start, _data_len,
+			_key_len, _is_ecb? "EBC" : "CBC" );
+	free( _encbuf );
+	free( _decbuf );
+	if( OAES_RET_SUCCESS !=  oaes_free( &ctx ) )
+		printf("Error: Failed to uninitialize OAES.\n");
+
+	return (EXIT_SUCCESS);
+}
diff --git a/openaes/test/vt_aes.c b/openaes/test/vt_aes.c
new file mode 100644
index 0000000..1ba755e
--- /dev/null
+++ b/openaes/test/vt_aes.c
@@ -0,0 +1,405 @@
+/* 
+ * ---------------------------------------------------------------------------
+ * OpenAES License
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2012, Nabil S. Al Ramli, www.nalramli.com
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *   - Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ---------------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define OAES_DEBUG 1
+#include "oaes_lib.h"
+
+static int _is_step = 1;
+
+static int step_cb(
+		const uint8_t state[OAES_BLOCK_SIZE],
+		const char * step_name,
+		int step_count,
+		void * user_data )
+{
+	size_t _buf_len;
+	char * _buf;
+
+
+	if( NULL == state )
+		return 1;
+
+	oaes_sprintf( NULL, &_buf_len, state, OAES_BLOCK_SIZE );
+	_buf = (char *) calloc( _buf_len, sizeof( char ) );
+
+	if( _buf )
+	{
+		oaes_sprintf( _buf, &_buf_len, state, OAES_BLOCK_SIZE );
+		printf( "round[%2d].%-7s --> %s", step_count, step_name, _buf );
+		free( _buf );
+	}
+	
+	if( 1 == _is_step && '\n' != getchar( ) )
+		_is_step = 0;
+
+	return 0;
+}
+
+static int to_binary(uint8_t * buf, size_t * buf_len, const char * data)
+{
+	size_t _i, _buf_len_in;
+	
+	if( NULL == buf_len )
+		return 1;
+
+	if( NULL == data )
+		return 1;
+
+	_buf_len_in = *buf_len;
+	*buf_len = strlen( data ) / 2;
+	
+	if( NULL == buf )
+		return 0;
+
+	if( *buf_len > _buf_len_in )
+		return 1;
+
+	memset( buf, 0, strlen( data ) / 2 );
+	
+	// lookup ascii table
+	for( _i = 0; _i < strlen( data ); _i++ )
+	{
+		// 0-9
+		if( data[_i] >= 0x30 && data[_i] <= 0x39 )
+			buf[_i / 2] += ( data[_i] - 0x30 ) << ( 4 * ( ( _i + 1 ) % 2 ) ) ;
+		// a-f
+		else if( data[_i] >= 0x41 && data[_i] <= 0x46 )
+			buf[_i / 2] += ( data[_i] - 0x37 ) << ( 4 * ( ( _i + 1 ) % 2 ) );
+		// A-F
+		else if( data[_i] >= 0x61 && data[_i] <= 0x66 )
+			buf[_i / 2] += ( data[_i] - 0x57 ) << ( 4 * ( ( _i + 1 ) % 2 ) );
+		// invalid character
+		else
+			return 1;
+	}
+	
+	return 0;
+}
+
+static void usage(const char * exe_name)
+{
+	if( NULL == exe_name )
+		return;
+	
+	printf(
+			"Usage:\n"
+			"  %s [-step] [-ecb] [[-key < 128 | 192 | 256 | key_data >] [-bin] <text>\n",
+			exe_name
+	);
+}
+
+int main(int argc, char** argv)
+{
+	size_t _i;
+	OAES_CTX * ctx = NULL;
+	uint8_t *_encbuf, *_decbuf, *_key_data = NULL, *_bin_data = NULL;
+	size_t _encbuf_len, _decbuf_len, _buf_len;
+	size_t _key_data_len = 0, _bin_data_len = 0;
+	char *_buf;
+	short _is_ecb = 0, _is_bin = 0;
+	char * _text = NULL, * _key_text = NULL;
+	int _key_len = 128;
+	
+	if( argc < 2 )
+	{
+		usage( argv[0] );
+		return EXIT_FAILURE;
+	}
+
+	for( _i = 1; _i < argc; _i++ )
+	{
+		int _found = 0;
+
+		if( 0 == strcmp( argv[_i], "-nostep" ) )
+		{
+			_found = 1;
+			_is_step = 0;
+		}
+
+		if( 0 == strcmp( argv[_i], "-ecb" ) )
+		{
+			_found = 1;
+			_is_ecb = 1;
+		}
+		
+		if( 0 == strcmp( argv[_i], "-bin" ) )
+		{
+			_found = 1;
+			_is_bin = 1;
+		}
+		
+		if( 0 == strcmp( argv[_i], "-key" ) )
+		{
+			_found = 1;
+			_i++; // len
+			if( _i >= argc )
+			{
+				printf("Error: No value specified for '-%s'.\n",
+						"key");
+				usage( argv[0] );
+				return EXIT_FAILURE;
+			}
+			_key_len = atoi( argv[_i] );
+			switch( _key_len )
+			{
+				case 128:
+				case 192:
+				case 256:
+					break;
+				default:
+					_key_text = argv[_i];
+					if( to_binary( NULL, &_key_data_len, _key_text ) )
+					{
+						printf( "Error: Invalid value [%s] specified for '-%s'.\n",
+								argv[_i], "key" );
+						return EXIT_FAILURE;
+					}
+					switch( _key_data_len )
+					{
+						case 16:
+						case 24:
+						case 32:
+							break;
+						default:
+							printf("Error: key_data [%s] specified for '-%s' has an invalid "
+									"size.\n", argv[_i], "key");
+							usage( argv[0] );
+							return EXIT_FAILURE;
+					}
+			}
+		}
+		
+		if( 0 == _found )
+		{
+			if( _text )
+			{
+				printf("Error: Invalid option '%s'.\n", argv[_i]);
+				usage( argv[0] );
+				return EXIT_FAILURE;
+			}
+			else
+			{
+				_text = argv[_i];
+				if( _is_bin && to_binary( NULL, &_bin_data_len, _text ) )
+				{
+					printf( "Error: Invalid value [%s] specified for '-%s'.\n",
+							argv[_i], "bin" );
+					return EXIT_FAILURE;
+				}
+			}
+		}			
+	}
+
+	if( NULL == _text )
+	{
+		usage( argv[0] );
+		return EXIT_FAILURE;
+	}
+
+	if( _is_step )
+		printf( "\nEnabling step mode, press Return to step.\n\n" );
+
+	if( _is_bin )
+	{
+		_bin_data = (uint8_t *) calloc(_bin_data_len, sizeof(uint8_t));
+		if( NULL == _bin_data )
+		{
+			printf( "Error: Failed to allocate memory.\n" );
+			return EXIT_FAILURE;
+		}
+		if( to_binary( _bin_data, &_bin_data_len, _text ) )
+		{
+			printf( "Error: Could not load data [%s].\n", _text);
+			free( _bin_data );
+			return EXIT_FAILURE;
+		}
+	}
+	else
+	{
+		oaes_sprintf( NULL, &_buf_len, (const uint8_t *)_text, strlen(_text));
+		_buf = (char *) calloc(_buf_len, sizeof(char));
+		printf( "\n***** plaintext  *****\n" );
+		if( _buf )
+		{
+			oaes_sprintf( _buf, &_buf_len,
+					(const uint8_t *)_text, strlen( _text ) );
+			printf( "%s", _buf );
+		}
+		printf( "\n**********************\n" );
+		free( _buf );
+	}
+	
+	ctx = oaes_alloc();
+	if( NULL == ctx )
+	{
+		printf("Error: Failed to initialize OAES.\n");
+		if( _bin_data )
+			free( _bin_data );
+		return EXIT_FAILURE;
+	}
+	if( OAES_RET_SUCCESS != oaes_set_option( ctx, OAES_OPTION_STEP_ON, step_cb ) )
+		printf("Error: Failed to set OAES options.\n");
+	if( _is_ecb )
+		if( OAES_RET_SUCCESS != oaes_set_option( ctx, OAES_OPTION_ECB, NULL ) )
+			printf("Error: Failed to set OAES options.\n");
+
+	if( _key_text )
+	{
+		_key_data = (uint8_t *) calloc(_key_data_len, sizeof(uint8_t));
+		if( NULL == _key_data )
+		{
+			printf( "Error: Failed to allocate memory.\n" );
+			if( _bin_data )
+				free( _bin_data );
+			return EXIT_FAILURE;
+		}
+		if( to_binary( _key_data, &_key_data_len, _key_text ) )
+		{
+			printf( "Error: Could not load key [%s].\n", _key_text);
+			free( _key_data );
+			return EXIT_FAILURE;
+		}
+		oaes_key_import_data( ctx, _key_data, _key_data_len );
+	}
+	else
+		switch( _key_len )
+		{
+			case 128:
+				if( OAES_RET_SUCCESS != oaes_key_gen_128(ctx) )
+					printf("Error: Failed to generate OAES %d bit key.\n", _key_len);
+				break;
+			case 192:
+				if( OAES_RET_SUCCESS != oaes_key_gen_192(ctx) )
+					printf("Error: Failed to generate OAES %d bit key.\n", _key_len);
+				break;
+			case 256:
+				if( OAES_RET_SUCCESS != oaes_key_gen_256(ctx) )
+					printf("Error: Failed to generate OAES %d bit key.\n", _key_len);
+				break;
+			default:
+				break;
+		}
+
+	if( _bin_data )
+	{
+		if( OAES_RET_SUCCESS != oaes_encrypt( ctx,
+				_bin_data, _bin_data_len, NULL, &_encbuf_len ) )
+			printf("Error: Failed to retrieve required buffer size for encryption.\n");
+		_encbuf = (uint8_t *) calloc(_encbuf_len, sizeof(uint8_t));
+		if( NULL == _encbuf )
+		{
+			printf( "Error: Failed to allocate memory.\n" );
+			if( _key_data )
+				free( _key_data );
+			free( _bin_data );
+			return EXIT_FAILURE;
+		}
+		printf( "\n" );
+		if( OAES_RET_SUCCESS != oaes_encrypt( ctx,
+				_bin_data, _bin_data_len, _encbuf, &_encbuf_len ) )
+			printf("Error: Encryption failed.\n");
+		printf( "\n**********************\n\n" );
+	}
+	else
+	{
+		if( OAES_RET_SUCCESS != oaes_encrypt( ctx,
+				(const uint8_t *)_text, strlen( _text ), NULL, &_encbuf_len ) )
+			printf("Error: Failed to retrieve required buffer size for encryption.\n");
+		_encbuf = (uint8_t *) calloc(_encbuf_len, sizeof(uint8_t));
+		if( NULL == _encbuf )
+		{
+			printf( "Error: Failed to allocate memory.\n" );
+			if( _key_data )
+				free( _key_data );
+			return EXIT_FAILURE;
+		}
+		printf( "\n" );
+		if( OAES_RET_SUCCESS != oaes_encrypt( ctx,
+				(const uint8_t *)_text, strlen( _text ), _encbuf, &_encbuf_len ) )
+			printf("Error: Encryption failed.\n");
+		printf( "\n**********************\n\n" );
+	}
+
+	if( OAES_RET_SUCCESS != oaes_decrypt( ctx,
+			_encbuf, _encbuf_len, NULL, &_decbuf_len ) )
+		printf("Error: Failed to retrieve required buffer size for encryption.\n");
+	_decbuf = (uint8_t *) calloc(_decbuf_len, sizeof(uint8_t));
+	if( NULL == _decbuf )
+	{
+		printf( "Error: Failed to allocate memory.\n" );
+		if( _key_data )
+			free( _key_data );
+		if( _bin_data )
+			free( _bin_data );
+		free( _encbuf );
+		return EXIT_FAILURE;
+	}
+	if( OAES_RET_SUCCESS != oaes_decrypt( ctx,
+			_encbuf, _encbuf_len, _decbuf, &_decbuf_len ) )
+		printf("Error: Decryption failed.\n");
+
+	if( OAES_RET_SUCCESS !=  oaes_free( &ctx ) )
+		printf("Error: Failed to uninitialize OAES.\n");
+	
+	oaes_sprintf( NULL, &_buf_len, _encbuf, _encbuf_len );
+	_buf = (char *) calloc(_buf_len, sizeof(char));
+	printf( "\n***** cyphertext *****\n" );
+	if( _buf )
+	{
+		oaes_sprintf( _buf, &_buf_len, _encbuf, _encbuf_len );
+		printf( "%s", _buf );
+	}
+	printf( "\n**********************\n" );
+	free( _buf );
+	
+	oaes_sprintf( NULL, &_buf_len, _decbuf, _decbuf_len );
+	_buf = (char *) calloc(_buf_len, sizeof(char));
+	printf( "\n***** plaintext  *****\n" );
+	if( _buf )
+	{
+		oaes_sprintf( _buf, &_buf_len, _decbuf, _decbuf_len );
+		printf( "%s", _buf );
+	}
+	printf( "\n**********************\n\n" );
+	free( _buf );
+	
+	free( _encbuf );
+	free( _decbuf );
+	if( _key_data )
+		free( _key_data );
+	if( _bin_data )
+		free( _bin_data );
+
+	return (EXIT_SUCCESS);
+}
diff --git a/openrecoveryscript.cpp b/openrecoveryscript.cpp
new file mode 100755
index 0000000..c2ff0fe
--- /dev/null
+++ b/openrecoveryscript.cpp
@@ -0,0 +1,755 @@
+/*
+	Copyright 2003 to 2021 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define __STDC_FORMAT_MACROS 1
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+#include <vector>
+#include <dirent.h>
+#include <time.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <iterator>
+#include <algorithm>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <zlib.h>
+
+#include "twrp-functions.hpp"
+#include "partitions.hpp"
+#include "twcommon.h"
+#include "openrecoveryscript.hpp"
+#include "progresstracking.hpp"
+#include "variables.h"
+#include "install/adb_install.h"
+#include "data.hpp"
+#include "fuse_sideload.h"
+#include "gui/gui.hpp"
+#include "gui/pages.hpp"
+#include "orscmd/orscmd.h"
+#include "twinstall.h"
+#include "twinstall/adb_install.h"
+extern "C" {
+	#include "gui/gui.h"
+	#include "cutils/properties.h"
+}
+
+OpenRecoveryScript::VoidFunction OpenRecoveryScript::call_after_cli_command;
+
+#define SCRIPT_COMMAND_SIZE 512
+
+int OpenRecoveryScript::check_for_script_file(void) {
+	std::string logDir = TWFunc::get_log_dir();
+	std::string orsFile;
+	if (logDir == DATA_LOGS_DIR)
+		orsFile = "/data/cache";
+	else
+		orsFile = logDir;
+	orsFile += "/recovery/openrecoveryscript";
+	if (!PartitionManager.Mount_By_Path(orsFile, false)) {
+		LOGINFO("Unable to mount %s for OpenRecoveryScript support.\n", logDir.c_str());
+		gui_msg(Msg(msg::kError, "unable_to_mount=Unable to mount {1}")(logDir.c_str()));
+		return 0;
+	}
+	if (TWFunc::Path_Exists(orsFile)) {
+		LOGINFO("Script file found: '%s'\n", orsFile.c_str());
+		// Copy script file to /tmp
+		TWFunc::copy_file(orsFile, SCRIPT_FILE_TMP, 0755);
+		// Delete the file from cache
+		unlink(orsFile.c_str());
+		return 1;
+	}
+	return 0;
+}
+
+int OpenRecoveryScript::copy_script_file(string filename) {
+	if (TWFunc::Path_Exists(filename)) {
+		LOGINFO("Script file found: '%s'\n", filename.c_str());
+		if (filename == SCRIPT_FILE_TMP)
+			return 1; // file is already in the right place
+		// Copy script file to /tmp
+		TWFunc::copy_file(filename, SCRIPT_FILE_TMP, 0755);
+		// Delete the old file
+		unlink(filename.c_str());
+		return 1;
+	}
+	return 0;
+}
+
+int OpenRecoveryScript::run_script_file(void) {
+	int ret_val = 0, cindex, line_len, i, remove_nl, install_cmd = 0, sideload = 0;
+	char script_line[SCRIPT_COMMAND_SIZE], command[SCRIPT_COMMAND_SIZE],
+	     value[SCRIPT_COMMAND_SIZE], mount[SCRIPT_COMMAND_SIZE],
+	     value1[SCRIPT_COMMAND_SIZE], value2[SCRIPT_COMMAND_SIZE];
+	char *val_start, *tok;
+
+	FILE *fp = fopen(SCRIPT_FILE_TMP, "r");
+	if (fp != NULL) {
+		DataManager::SetValue(TW_SIMULATE_ACTIONS, 0);
+		DataManager::SetValue("ui_progress", 0); // Reset the progress bar
+		while (fgets(script_line, SCRIPT_COMMAND_SIZE, fp) != NULL && ret_val == 0) {
+			cindex = 0;
+			line_len = strlen(script_line);
+			if (line_len < 2)
+				continue; // there's a blank line or line is too short to contain a command
+			//gui_print("script line: '%s'\n", script_line);
+			for (i=0; i<line_len; i++) {
+				if ((int)script_line[i] == 32) {
+					cindex = i;
+					i = line_len;
+				}
+			}
+			memset(command, 0, sizeof(command));
+			memset(value, 0, sizeof(value));
+			if ((int)script_line[line_len - 1] == 10)
+				remove_nl = 2;
+			else
+				remove_nl = 1;
+			if (cindex != 0) {
+				strncpy(command, script_line, cindex);
+				LOGINFO("command is: '%s'\n", command);
+				val_start = script_line;
+				val_start += cindex + 1;
+				if ((int) *val_start == 32)
+					val_start++; //get rid of space
+				if ((int) *val_start == 51)
+					val_start++; //get rid of = at the beginning
+				if ((int) *val_start == 32)
+					val_start++; //get rid of space
+				strncpy(value, val_start, line_len - cindex - remove_nl);
+				LOGINFO("value is: '%s'\n", value);
+			} else {
+				strncpy(command, script_line, line_len - remove_nl + 1);
+				gui_print("command is: '%s' and there is no value\n", command);
+			}
+			if (strcmp(command, "install") == 0) {
+				// Install Zip
+				DataManager::SetValue("tw_action_text2", "Installing Zip");
+				ret_val = Install_Command(value);
+				install_cmd = -1;
+			} else if (strcmp(command, "wipe") == 0) {
+				// Wipe
+				if (strcmp(value, "cache") == 0 || strcmp(value, "/cache") == 0) {
+					PartitionManager.Wipe_By_Path("/cache");
+				} else if (strcmp(value, "system") == 0 || strcmp(value, "/system") == 0 || strcmp(value, PartitionManager.Get_Android_Root_Path().c_str()) == 0) {
+					PartitionManager.Wipe_By_Path("/system");
+					PartitionManager.Update_System_Details();
+				} else if (strcmp(value, "dalvik") == 0 || strcmp(value, "dalvick") == 0 || strcmp(value, "dalvikcache") == 0 || strcmp(value, "dalvickcache") == 0) {
+					PartitionManager.Wipe_Dalvik_Cache();
+				} else if (strcmp(value, "data") == 0 || strcmp(value, "/data") == 0 || strcmp(value, "factory") == 0 || strcmp(value, "factoryreset") == 0) {
+					PartitionManager.Factory_Reset();
+				} else {
+					LOGERR("Error with wipe command value: '%s'\n", value);
+					ret_val = 1;
+				}
+			} else if (strcmp(command, "format") == 0) {
+				// Format
+				if (strcmp(value, "data") == 0 || strcmp(value, "/data") == 0 || strcmp(value, "factory") == 0 || strcmp(value, "factoryreset") == 0) {
+					PartitionManager.Format_Data();
+				} else {
+					LOGERR("Error with format command value: '%s'\n", value);
+					ret_val = 1;
+				}
+			} else if (strcmp(command, "backup") == 0) {
+				// Backup
+				DataManager::SetValue("tw_action_text2", gui_parse_text("{@backing}"));
+				tok = strtok(value, " ");
+				strcpy(value1, tok);
+				tok = strtok(NULL, " ");
+				if (tok != NULL) {
+					memset(value2, 0, sizeof(value2));
+					strcpy(value2, tok);
+					line_len = strlen(tok);
+					if ((int)value2[line_len - 1] == 10 || (int)value2[line_len - 1] == 13) {
+						if ((int)value2[line_len - 1] == 10 || (int)value2[line_len - 1] == 13)
+							remove_nl = 2;
+						else
+							remove_nl = 1;
+					} else
+						remove_nl = 0;
+					strncpy(value2, tok, line_len - remove_nl);
+					DataManager::SetValue(TW_BACKUP_NAME, value2);
+					gui_msg(Msg("backup_folder_set=Backup folder set to '{1}'")(value2));
+					if (PartitionManager.Check_Backup_Name(value2, true, true) != 0) {
+						ret_val = 1;
+						continue;
+					}
+				} else {
+					char empt[50];
+					strcpy(empt, "(Current Date)");
+					DataManager::SetValue(TW_BACKUP_NAME, empt);
+				}
+				ret_val = Backup_Command(value1);
+			} else if (strcmp(command, "restore") == 0) {
+				// Restore
+				DataManager::SetValue("tw_action_text2", gui_parse_text("{@restore}"));
+				PartitionManager.Mount_All_Storage();
+				DataManager::SetValue(TW_SKIP_DIGEST_CHECK_VAR, 0);
+				char folder_path[512], partitions[512];
+
+				string val = value, restore_folder, restore_partitions;
+				size_t pos = val.find_last_of(" ");
+				if (pos == string::npos) {
+					restore_folder = value;
+					partitions[0] = '\0';
+				} else {
+					restore_folder = val.substr(0, pos);
+					restore_partitions = val.substr(pos + 1, val.size() - pos - 1);
+					strcpy(partitions, restore_partitions.c_str());
+				}
+				strcpy(folder_path, restore_folder.c_str());
+				LOGINFO("Restore folder is: '%s' and partitions: '%s'\n", folder_path, partitions);
+				gui_msg(Msg("restoring=Restoring {1}...")(folder_path));
+
+				if (folder_path[0] != '/') {
+					char backup_folder[512];
+					string folder_var;
+					std::vector<PartitionList> Storage_List;
+
+					PartitionManager.Get_Partition_List("storage", &Storage_List);
+					int listSize = Storage_List.size();
+					for (int i = 0; i < listSize; i++) {
+						if (PartitionManager.Is_Mounted_By_Path(Storage_List.at(i).Mount_Point)) {
+							DataManager::SetValue("tw_storage_path", Storage_List.at(i).Mount_Point);
+							DataManager::GetValue(TW_BACKUPS_FOLDER_VAR, folder_var);
+							sprintf(backup_folder, "%s/%s", folder_var.c_str(), folder_path);
+							if (TWFunc::Path_Exists(backup_folder)) {
+								strcpy(folder_path, backup_folder);
+								break;
+							}
+						}
+					}
+				} else {
+					if (folder_path[strlen(folder_path) - 1] == '/')
+						strcat(folder_path, ".");
+					else
+						strcat(folder_path, "/.");
+				}
+				if (!TWFunc::Path_Exists(folder_path)) {
+					gui_msg(Msg(msg::kError, "locate_backup_err=Unable to locate backup '{1}'")(folder_path));
+					ret_val = 1;
+					continue;
+				}
+				DataManager::SetValue("tw_restore", folder_path);
+
+				PartitionManager.Set_Restore_Files(folder_path);
+				string Partition_List;
+				int is_encrypted = 0;
+				DataManager::GetValue("tw_restore_encrypted", is_encrypted);
+				DataManager::GetValue("tw_restore_list", Partition_List);
+				if (strlen(partitions) != 0) {
+					string Restore_List;
+
+					memset(value2, 0, sizeof(value2));
+					strcpy(value2, partitions);
+					gui_msg(Msg("set_restore_opt=Setting restore options: '{1}':")(value2));
+					line_len = strlen(value2);
+					for (i=0; i<line_len; i++) {
+						if ((value2[i] == 'S' || value2[i] == 's') && Partition_List.find("/system;") != string::npos) {
+							Restore_List += "/system;";
+							gui_msg("system=System");
+						} else if ((value2[i] == 'D' || value2[i] == 'd') && Partition_List.find("/data;") != string::npos) {
+							Restore_List += "/data;";
+							gui_msg("data=Data");
+						} else if ((value2[i] == 'C' || value2[i] == 'c') && Partition_List.find("/cache;") != string::npos) {
+							Restore_List += "/cache;";
+							gui_msg("cache=Cache");
+						} else if ((value2[i] == 'R' || value2[i] == 'r') && Partition_List.find("/recovery;") != string::npos) {
+							Restore_List += "/recovery;";
+							gui_msg("recovery=Recovery");
+						} else if ((value2[i] == 'B' || value2[i] == 'b') && Partition_List.find("/boot;") != string::npos) {
+							Restore_List += "/boot;";
+							gui_msg("boot=Boot");
+						} else if ((value2[i] == 'A' || value2[i] == 'a')  && Partition_List.find("/and-sec;") != string::npos) {
+							Restore_List += "/and-sec;";
+							gui_msg("android_secure=Android Secure");
+						} else if ((value2[i] == 'E' || value2[i] == 'e')  && Partition_List.find("/sd-ext;") != string::npos) {
+							Restore_List += "/sd-ext;";
+							gui_msg("sdext=SD-EXT");
+						} else if (value2[i] == 'M' || value2[i] == 'm') {
+							DataManager::SetValue(TW_SKIP_DIGEST_CHECK_VAR, 1);
+							gui_msg("digest_check_skip=Digest check skip is on");
+						}
+					}
+
+					DataManager::SetValue("tw_restore_selected", Restore_List);
+				} else {
+					DataManager::SetValue("tw_restore_selected", Partition_List);
+				}
+				if (is_encrypted) {
+					gui_err("ors_encrypt_restore_err=Unable to use OpenRecoveryScript to restore an encrypted backup.");
+					ret_val = 1;
+				} else if (!PartitionManager.Run_Restore(folder_path))
+					ret_val = 1;
+				else
+					gui_msg("done=Done.");
+			} else if (strcmp(command, "remountrw") == 0) {
+				ret_val = remountrw();
+			} else if (strcmp(command, "mount") == 0) {
+				// Mount
+				DataManager::SetValue("tw_action_text2", gui_parse_text("{@mounting}"));
+				if (value[0] != '/') {
+					strcpy(mount, "/");
+					strcat(mount, value);
+				} else
+					strcpy(mount, value);
+				if (!strcmp(mount, "/system"))
+					strcpy(mount, PartitionManager.Get_Android_Root_Path().c_str());
+				if (PartitionManager.Mount_By_Path(mount, true))
+					gui_msg(Msg("mounted=Mounted '{1}'")(mount));
+			} else if (strcmp(command, "unmount") == 0 || strcmp(command, "umount") == 0) {
+				// Unmount
+				DataManager::SetValue("tw_action_text2", gui_parse_text("{@unmounting}"));
+				if (value[0] != '/') {
+					strcpy(mount, "/");
+					strcat(mount, value);
+				} else
+					strcpy(mount, value);
+				if (!strcmp(mount, "/system"))
+					strcpy(mount, PartitionManager.Get_Android_Root_Path().c_str());
+				if (PartitionManager.UnMount_By_Path(mount, true))
+					gui_msg(Msg("unmounted=Unounted '{1}'")(mount));
+			} else if (strcmp(command, "set") == 0) {
+				// Set value
+				size_t len = strlen(value);
+				tok = strtok(value, " ");
+				strcpy(value1, tok);
+				if (len > strlen(value1) + 1) {
+					char *val2 = value + strlen(value1) + 1;
+					gui_msg(Msg("setting=Setting '{1}' to '{2}'")(value1)(val2));
+					DataManager::SetValue(value1, val2);
+				} else {
+					gui_msg(Msg("setting_empty=Setting '{1}' to empty")(value1));
+					DataManager::SetValue(value1, "");
+				}
+			} else if (strcmp(command, "mkdir") == 0) {
+				// Make directory (recursive)
+				DataManager::SetValue("tw_action_text2", gui_parse_text("{@making_dir1}"));
+				gui_msg(Msg("making_dir2=Making directory: '{1}'")(value));
+				if (!TWFunc::Recursive_Mkdir(value)) {
+					// error message already displayed by Recursive_Mkdir
+					ret_val = 1;
+				}
+			} else if (strcmp(command, "reboot") == 0) {
+				if (strlen(value) && strcmp(value, "recovery") == 0)
+					TWFunc::tw_reboot(rb_recovery);
+				else if (strlen(value) && strcmp(value, "poweroff") == 0)
+					TWFunc::tw_reboot(rb_poweroff);
+				else if (strlen(value) && strcmp(value, "bootloader") == 0)
+					TWFunc::tw_reboot(rb_bootloader);
+				else if (strlen(value) && strcmp(value, "download") == 0)
+					TWFunc::tw_reboot(rb_download);
+				else if (strlen(value) && strcmp(value, "edl") == 0)
+					TWFunc::tw_reboot(rb_edl);
+				else
+					TWFunc::tw_reboot(rb_system);
+			} else if (strcmp(command, "cmd") == 0) {
+				DataManager::SetValue("tw_action_text2", gui_parse_text("{@running_command}"));
+				if (cindex != 0) {
+					TWFunc::Exec_Cmd(value);
+				} else {
+					LOGERR("No value given for cmd\n");
+				}
+			} else if (strcmp(command, "print") == 0) {
+				gui_print("%s\n", value);
+			} else if (strcmp(command, "sideload") == 0) {
+				// ADB Sideload
+				DataManager::SetValue("tw_action_text2", gui_parse_text("{@sideload}"));
+				install_cmd = -1;
+
+				int wipe_cache = 0;
+				string result;
+
+				gui_msg("start_sideload=Starting ADB sideload feature...");
+
+				Device::BuiltinAction reboot_action = Device::REBOOT_BOOTLOADER;
+				ret_val = twrp_sideload("/", &reboot_action);
+				if (ret_val != 0) {
+					if (ret_val == -2)
+						gui_err("need_new_adb=You need adb 1.0.32 or newer to sideload to this device.");
+					ret_val = 1; // failure
+				} else if (TWinstall_zip(FUSE_SIDELOAD_HOST_PATHNAME, &wipe_cache) == 0) {
+					if (wipe_cache)
+						PartitionManager.Wipe_By_Path("/cache");
+				} else {
+					ret_val = 1; // failure
+				}
+				PartitionManager.Unlock_Block_Partitions();
+				PartitionManager.Update_System_Details();
+				sideload = 1; // Causes device to go to the home screen afterwards
+				pid_t sideload_child_pid = GetMiniAdbdPid();
+				if (sideload_child_pid != 0) {
+					LOGINFO("Signaling child sideload process to exit.\n");
+					struct stat st;
+					// Calling stat() on this magic filename signals the minadbd
+					// subprocess to shut down.
+					stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
+					int status;
+					LOGINFO("Waiting for child sideload process to exit.\n");
+					waitpid(sideload_child_pid, &status, 0);
+				}
+				property_set("ctl.start", "adbd");
+				gui_msg("done=Done.");
+			} else if (strcmp(command, "fixperms") == 0 || strcmp(command, "fixpermissions") == 0 || strcmp(command, "fixcontexts") == 0) {
+				ret_val = PartitionManager.Fix_Contexts();
+				if (ret_val != 0)
+					ret_val = 1; // failure
+			} else if (strcmp(command, "decrypt") == 0) {
+				// twrp cmd cannot decrypt a password with space, should decrypt on gui
+				if (*value) {
+					string tmp = value;
+					std::vector<string> args = TWFunc::Split_String(tmp, " ");
+
+					string pass = args[0];
+					string userid = "0";
+					if (args.size() > 1)
+						userid = args[1];
+
+					ret_val = PartitionManager.Decrypt_Device(pass, atoi(userid.c_str()));
+					if (ret_val != 0)
+						ret_val = 1;  // failure
+				} else {
+					gui_err("no_pwd=No password provided.");
+					ret_val = 1;  // failure
+				}
+			} else if (strcmp(command, "listmounts") == 0) {
+				TWFunc::List_Mounts();
+			} else {
+				LOGERR("Unrecognized script command: '%s'\n", command);
+				ret_val = 1;
+			}
+		}
+		fclose(fp);
+		unlink(SCRIPT_FILE_TMP);
+		gui_msg("done_ors=Done processing script file");
+	} else {
+		gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(SCRIPT_FILE_TMP)(
+			strerror(errno)));
+		return 1;
+	}
+
+	if (install_cmd && DataManager::GetIntValue(TW_HAS_INJECTTWRP) == 1 &&
+	DataManager::GetIntValue(TW_INJECT_AFTER_ZIP) == 1) {
+		gui_msg("injecttwrp=Injecting TWRP into boot image...");
+		TWPartition* Boot = PartitionManager.Find_Partition_By_Path("/boot");
+		if (Boot == NULL || Boot->Current_File_System != "emmc")
+			TWFunc::Exec_Cmd(
+			"injecttwrp --dump /tmp/backup_recovery_ramdisk.img /tmp/injected_boot.img --flash");
+		else {
+			string injectcmd =
+			"injecttwrp --dump /tmp/backup_recovery_ramdisk.img /tmp/injected_boot.img --flash bd=" +
+			Boot->Actual_Block_Device;
+			TWFunc::Exec_Cmd(injectcmd.c_str());
+		}
+		gui_msg("done=Done.");
+	}
+	if (sideload)
+		ret_val = 1;  // Forces booting to the home page after sideload
+	return ret_val;
+}
+
+int OpenRecoveryScript::Insert_ORS_Command(string Command) {
+	ofstream ORSfile(SCRIPT_FILE_TMP, ios_base::app | ios_base::out);
+	if (ORSfile.is_open()) {
+		LOGINFO("Inserting '%s'\n", Command.c_str());
+		ORSfile << Command.c_str() << endl;
+		ORSfile.close();
+		return 1;
+	}
+	LOGERR("Unable to append '%s' to '%s'\n", Command.c_str(), SCRIPT_FILE_TMP);
+	return 0;
+}
+
+int OpenRecoveryScript::Install_Command(string Zip) {
+	// Install zip
+	string ret_string;
+	int ret_val = 0, wipe_cache = 0;
+	std::vector<PartitionList> Storage_List;
+	string Full_Path;
+
+	if (Zip.substr(0, 1) == "@") {
+		// This is a special file that contains a map of blocks on the data partition
+		Full_Path = Zip.substr(1);
+		if (!PartitionManager.Mount_By_Path(Full_Path, true) || !TWFunc::Path_Exists(Full_Path)) {
+			LOGINFO("Unable to install via mapped zip '%s'\n", Full_Path.c_str());
+			gui_msg(Msg(msg::kError, "zip_err=Error installing zip file '{1}'")(Zip));
+			return 1;
+		}
+		LOGINFO("Installing mapped zip file '%s'\n", Full_Path.c_str());
+		gui_msg(Msg("installing_zip=Installing zip file '{1}'")(Zip));
+	} else if (!TWFunc::Path_Exists(Zip)) {
+		PartitionManager.Mount_All_Storage();
+		PartitionManager.Get_Partition_List("storage", &Storage_List);
+		int listSize = Storage_List.size();
+		for (int i = 0; i < listSize; i++) {
+			if (PartitionManager.Is_Mounted_By_Path(Storage_List.at(i).Mount_Point)) {
+				Full_Path = Storage_List.at(i).Mount_Point + "/" + Zip;
+				if (TWFunc::Path_Exists(Full_Path)) {
+					Zip = Full_Path;
+					break;
+				}
+				Full_Path = Zip;
+				LOGINFO("Trying to find zip '%s' on '%s'...\n", Full_Path.c_str(), Storage_List.at(i).Mount_Point.c_str());
+				ret_string = Locate_Zip_File(Full_Path, Storage_List.at(i).Mount_Point);
+				if (!ret_string.empty()) {
+					Zip = ret_string;
+					break;
+				}
+			}
+		}
+		if (!TWFunc::Path_Exists(Zip)) {
+			// zip file doesn't exist
+			gui_print("Unable to locate zip file '%s'.\n", Zip.c_str());
+			ret_val = 1;
+		} else
+			gui_msg(Msg("installing_zip=Installing zip file '{1}'")(Zip));
+	}
+
+	ret_val = TWinstall_zip(Zip.c_str(), &wipe_cache);
+	if (ret_val != 0) {
+		gui_msg(Msg(msg::kError, "zip_err=Error installing zip file '{1}'")(Zip));
+		ret_val = 1;
+	} else if (wipe_cache)
+		PartitionManager.Wipe_By_Path("/cache");
+
+	return ret_val;
+}
+
+string OpenRecoveryScript::Locate_Zip_File(string Zip, string Storage_Root) {
+	string Path = TWFunc::Get_Path(Zip);
+	string File = TWFunc::Get_Filename(Zip);
+	string pathCpy = Path;
+	string wholePath;
+	size_t pos = Path.find("/", 1);
+
+	while (pos != string::npos)
+	{
+		pathCpy = Path.substr(pos, Path.size() - pos);
+		wholePath = pathCpy + File;
+		LOGINFO("Looking for zip at '%s'\n", wholePath.c_str());
+		if (TWFunc::Path_Exists(wholePath))
+			return wholePath;
+		wholePath = Storage_Root + wholePath;
+		LOGINFO("Looking for zip at '%s'\n", wholePath.c_str());
+		if (TWFunc::Path_Exists(wholePath))
+			return wholePath;
+
+		pos = Path.find("/", pos + 1);
+	}
+	return "";
+}
+
+int OpenRecoveryScript::Backup_Command(string Options) {
+	char value1[SCRIPT_COMMAND_SIZE];
+	int line_len, i;
+	string Backup_List;
+
+	strcpy(value1, Options.c_str());
+
+	DataManager::SetValue(TW_USE_COMPRESSION_VAR, 0);
+	DataManager::SetValue(TW_SKIP_DIGEST_GENERATE_VAR, 0);
+
+	gui_msg("select_backup_opt=Setting backup options:");
+	line_len = Options.size();
+	for (i=0; i<line_len; i++) {
+		if (Options.substr(i, 1) == "S" || Options.substr(i, 1) == "s") {
+			Backup_List += "/system;";
+			gui_msg("system=System");
+		} else if (Options.substr(i, 1) == "D" || Options.substr(i, 1) == "d") {
+			Backup_List += "/data;";
+			gui_msg("data=Data");
+		} else if (Options.substr(i, 1) == "C" || Options.substr(i, 1) == "c") {
+			Backup_List += "/cache;";
+			gui_msg("cache=Cache");
+		} else if (Options.substr(i, 1) == "R" || Options.substr(i, 1) == "r") {
+			Backup_List += "/recovery;";
+			gui_msg("recovery=Recovery");
+		} else if (Options.substr(i, 1) == "1") {
+			gui_print("%s\n", "Special1 -- No Longer Supported...");
+		} else if (Options.substr(i, 1) == "2") {
+			gui_print("%s\n", "Special2 -- No Longer Supported...");
+		} else if (Options.substr(i, 1) == "3") {
+			gui_print("%s\n", "Special3 -- No Longer Supported...");
+		} else if (Options.substr(i, 1) == "B" || Options.substr(i, 1) == "b") {
+			Backup_List += "/boot;";
+			gui_msg("boot=Boot");
+		} else if (Options.substr(i, 1) == "A" || Options.substr(i, 1) == "a") {
+			Backup_List += "/and-sec;";
+			gui_msg("android_secure=Android Secure");
+		} else if (Options.substr(i, 1) == "E" || Options.substr(i, 1) == "e") {
+			Backup_List += "/sd-ext;";
+			gui_msg("sdext=SD-EXT");
+		} else if (Options.substr(i, 1) == "O" || Options.substr(i, 1) == "o") {
+			DataManager::SetValue(TW_USE_COMPRESSION_VAR, 1);
+			gui_msg("compression_on=Compression is on");
+		} else if (Options.substr(i, 1) == "M" || Options.substr(i, 1) == "m") {
+			DataManager::SetValue(TW_SKIP_DIGEST_GENERATE_VAR, 1);
+			gui_msg("digest_off=Digest Generation is off");
+		}
+	}
+	DataManager::SetValue("tw_backup_list", Backup_List);
+	if (!PartitionManager.Run_Backup(false)) {
+		gui_err("backup_fail=Backup Failed");
+		return 1;
+	}
+	gui_msg("backup_complete=Backup Complete");
+	return 0;
+}
+
+// this is called by main()
+void OpenRecoveryScript::Run_OpenRecoveryScript(void) {
+	DataManager::SetValue("tw_back", "main");
+	DataManager::SetValue("tw_action", "openrecoveryscript");
+	DataManager::SetValue("tw_action_param", "");
+	DataManager::SetValue("tw_has_action2", "0");
+	DataManager::SetValue("tw_action2", "");
+	DataManager::SetValue("tw_action2_param", "");
+#ifdef TW_OEM_BUILD
+	DataManager::SetValue("tw_action_text1", gui_lookup("running_recovery_commands", "Running Recovery Commands"));
+	DataManager::SetValue("tw_complete_text1", gui_lookup("recovery_commands_complete", "Recovery Commands Complete"));
+#else
+	DataManager::SetValue("tw_action_text1", gui_lookup("running_ors", "Running OpenRecoveryScript"));
+	DataManager::SetValue("tw_complete_text1", gui_lookup("ors_complete", "OpenRecoveryScript Complete"));
+#endif
+	DataManager::SetValue("tw_action_text2", "");
+	DataManager::SetValue("tw_has_cancel", 0);
+	DataManager::SetValue("tw_show_reboot", 0);
+	if (gui_startPage("action_page", 0, 1) != 0) {
+		LOGERR("Failed to load OpenRecoveryScript GUI page.\n");
+	}
+}
+
+// this is called by the "openrecoveryscript" GUI action called via action page from Run_OpenRecoveryScript
+int OpenRecoveryScript::Run_OpenRecoveryScript_Action() {
+	int op_status = 1;
+	// Check for the SCRIPT_FILE_TMP first as these are AOSP recovery commands
+	// that we converted to ORS commands during boot in recovery.cpp.
+	// Run those first.
+	int reboot = 0;
+	if (TWFunc::Path_Exists(SCRIPT_FILE_TMP)) {
+		gui_msg("running_recovery_commands=Running Recovery Commands");
+		if (OpenRecoveryScript::run_script_file() == 0) {
+			reboot = 1;
+			op_status = 0;
+		}
+	}
+	// Check for the ORS file in /cache and attempt to run those commands.
+	if (OpenRecoveryScript::check_for_script_file()) {
+		gui_msg("running_ors=Running OpenRecoveryScript");
+		if (OpenRecoveryScript::run_script_file() == 0) {
+			reboot = 1;
+			op_status = 0;
+		}
+	}
+	if (reboot) {
+		// Disable stock recovery reflashing
+		TWFunc::Disable_Stock_Recovery_Replace();
+		usleep(2000000); // Sleep for 2 seconds before rebooting
+		TWFunc::tw_reboot(rb_system);
+		usleep(5000000); // Sleep for 5 seconds to allow reboot to occur
+	} else {
+		DataManager::SetValue("tw_page_done", 1);
+	}
+	return op_status;
+}
+
+// this is called by the "twcmd" GUI action when a command is received via FIFO from the "twrp" command line tool
+void OpenRecoveryScript::Run_CLI_Command(const char* command) {
+	string tmp = command;
+	std::vector<string> parts =
+		TWFunc::Split_String(tmp, " ");  // pats[0] is cmd, parts[1...] is args
+	string cmd_str = parts[0];
+
+	if (cmd_str == "runscript") {
+		if (parts.size() > 1) {
+			string filename = parts[1];
+			if (OpenRecoveryScript::copy_script_file(filename) == 0) {
+				LOGINFO("Unable to copy script file\n");
+			} else {
+				OpenRecoveryScript::run_script_file();
+			}
+		} else {
+			LOGINFO("Missing parameter: script file name\n");
+		}
+	} else if (cmd_str == "get") {
+		if (parts.size() > 1) {
+			string varname = parts[1];
+			string value;
+			DataManager::GetValue(varname, value);
+			gui_print("%s = %s\n", varname.c_str(), value.c_str());
+		} else {
+			LOGINFO("Missing parameter: var name\n");
+		}
+	} else if (cmd_str == "decrypt") {
+		// twrp cmd cannot decrypt a password with space, should decrypt on gui
+		if (parts.size() == 1) {
+			gui_err("no_pwd=No password provided.");
+		} else {
+			string pass = parts[1];
+			string userid = "0";
+			if (parts.size() > 2)
+				userid = parts[2];
+
+			gui_msg("decrypt_cmd=Attempting to decrypt data partition or user data via command line.");
+			if (PartitionManager.Decrypt_Device(pass, atoi(userid.c_str())) == 0) {
+				// set_page_done = 1;  // done by singleaction_page anyway
+				std::string orsFile = TWFunc::get_log_dir() + "/openrecoveryscript";
+				if (TWFunc::Path_Exists(orsFile)) {
+					Run_OpenRecoveryScript_Action();
+				}
+			}
+		}
+	} else if (OpenRecoveryScript::Insert_ORS_Command(command)) {
+		OpenRecoveryScript::run_script_file();
+	}
+
+	// let the GUI close the output fd and restart the command listener
+	call_after_cli_command();
+	LOGINFO("Done reading ORS command from command line\n");
+}
+
+int OpenRecoveryScript::remountrw(void)
+{
+	bool remount_system = PartitionManager.Is_Mounted_By_Path(PartitionManager.Get_Android_Root_Path());
+	int op_status;
+	TWPartition* Part;
+
+	if (!PartitionManager.UnMount_By_Path(PartitionManager.Get_Android_Root_Path(), true)) {
+		op_status = 1; // fail
+	} else {
+		Part = PartitionManager.Find_Partition_By_Path(PartitionManager.Get_Android_Root_Path());
+		if (Part) {
+			DataManager::SetValue("tw_mount_system_ro", 0);
+			Part->Change_Mount_Read_Only(false);
+		}
+		if (remount_system) {
+			Part->Mount(true);
+		}
+		op_status = 0; // success
+	}
+
+	return op_status;
+}
diff --git a/openrecoveryscript.hpp b/openrecoveryscript.hpp
new file mode 100644
index 0000000..0806223
--- /dev/null
+++ b/openrecoveryscript.hpp
@@ -0,0 +1,46 @@
+/*
+	Copyright 2012 - 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _OPENRECOVERYSCRIPT_HPP
+#define _OPENRECOVERYSCRIPT_HPP
+
+#include <string>
+
+using namespace std;
+
+class OpenRecoveryScript
+{
+	typedef void (*VoidFunction)();
+	static VoidFunction call_after_cli_command;                                    // callback to GUI after Run_CLI_Command
+
+	static int check_for_script_file();                                            // Checks to see if the ORS file is present in /cache
+	static int copy_script_file(string filename);                                  // Copies a script file to the temp folder
+	static int run_script_file();                                                  // Executes the commands in the ORS file
+	static int Install_Command(string Zip);                                        // Installs a zip
+	static string Locate_Zip_File(string Path, string File);                       // Attempts to locate the zip file in storage
+	static int Backup_Command(string Options);                                     // Runs a backup
+public:
+	static int Insert_ORS_Command(string Command);                                 // Inserts the Command into the SCRIPT_FILE_TMP file
+	static void Run_OpenRecoveryScript();                                          // Starts the GUI Page for running OpenRecoveryScript
+	static int Run_OpenRecoveryScript_Action();                                    // Actually runs the ORS scripts for the GUI action
+	static void Call_After_CLI_Command(VoidFunction fn) { call_after_cli_command = fn; }
+	static void Run_CLI_Command(const char* command);                              // Runs a command for orscmd (twrp binary)
+	static int remountrw();                                                        // Remount system and vendor rw
+};
+
+#endif // _OPENRECOVERYSCRIPT_HPP
diff --git a/orscmd/Android.mk b/orscmd/Android.mk
new file mode 100755
index 0000000..42f918a
--- /dev/null
+++ b/orscmd/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+ifneq ($(TW_DEVICE_VERSION),)
+    LOCAL_CFLAGS += -DTW_DEVICE_VERSION='"-$(TW_DEVICE_VERSION)"'
+else
+    LOCAL_CFLAGS += -DTW_DEVICE_VERSION='"-0"'
+endif
+
+LOCAL_SRC_FILES:= \
+	orscmd.cpp
+LOCAL_CFLAGS += -c -W
+LOCAL_MODULE := orscmd
+LOCAL_MODULE_STEM := twrp
+LOCAL_MODULE_TAGS:= optional
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_PACK_MODULE_RELOCATIONS := false
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+include $(BUILD_EXECUTABLE)
diff --git a/orscmd/orscmd.cpp b/orscmd/orscmd.cpp
new file mode 100755
index 0000000..8264a77
--- /dev/null
+++ b/orscmd/orscmd.cpp
@@ -0,0 +1,164 @@
+/*
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define __STDC_FORMAT_MACROS 1
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <inttypes.h>
+
+// for setcap and getcap
+#include <sys/capability.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+
+#include "orscmd.h"
+#include "../variables.h"
+
+void print_version(void) {
+	printf("TWRP openrecoveryscript command line tool, TWRP version %s\n\n", TW_VERSION_STR);
+}
+
+void print_usage(void) {
+	print_version();
+	printf("Allows command line usage of TWRP via openrecoveryscript commands.\n");
+	printf("Some common commands include:\n");
+	printf("  install /path/to/update.zip\n");
+	printf("  backup <SDCRBAEM> [backupname]\n");
+	printf("  restore <SDCRBAEM> [backupname]\n");
+	printf("  wipe <partition name>\n");
+	printf("  format data\n");
+	printf("  sideload\n");
+	printf("  set <variable> [value]\n");
+	printf("  decrypt <password> [USER ID]\n");
+	printf("  remountrw\n");
+	printf("  fixperms\n");
+	printf("  mount <path>\n");
+	printf("  unmount <path>\n");
+	printf("  listmounts\n");
+	printf("  print <value>\n");
+	printf("  mkdir <directory>\n");
+	printf("  reboot [recovery|poweroff|bootloader|download|edl]\n");
+	printf("\nSee more documentation at https://twrp.me/faq/openrecoveryscript.html\n");
+}
+
+int do_setcap(const char* filename, const char* capabilities)
+{
+	uint64_t caps;
+	if (sscanf(capabilities, "%" SCNi64, &caps) != 1)
+	{
+		printf("setcap: invalid capabilities \"%s\"\n", filename);
+		return 1;
+	}
+	struct vfs_cap_data cap_data;
+	memset(&cap_data, 0, sizeof(cap_data));
+	cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE;
+	cap_data.data[0].permitted = (uint32_t) (caps & 0xffffffff);
+	cap_data.data[0].inheritable = 0;
+	cap_data.data[1].permitted = (uint32_t) (caps >> 32);
+	cap_data.data[1].inheritable = 0;
+	if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) {
+		printf("setcap of %s to %" PRIx64 " failed: %s\n",
+				filename, caps, strerror(errno));
+		return 1;
+	}
+	return 0;
+}
+
+int do_getcap(const char* filename)
+{
+	struct vfs_cap_data cap_data;
+	memset(&cap_data, 0, sizeof(cap_data));
+	int rc = getxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(vfs_cap_data));
+	if (rc > 0)
+	{
+		uint64_t caps = (uint64_t) cap_data.data[1].permitted << 32 | cap_data.data[0].permitted;
+		printf("0x%" PRIx64 "\n", caps);
+	}
+	else
+		printf("getcap of %s failed: %s\n", filename, strerror(errno));
+
+	return rc > 0;
+}
+
+int main(int argc, char **argv) {
+	int read_fd, write_fd, index;
+	char command[1024], result[512];
+
+	if (argc < 2 || strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "?") == 0 || strcmp(argv[1], "-h") == 0) {
+		print_usage();
+		return 0;
+	}
+	if (strcmp(argv[1], "version") == 0 || strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0) {
+		print_version();
+		return 0;
+	}
+
+	if (strcmp(argv[1], "setcap") == 0) {
+		if (argc != 4)
+		{
+			printf("Usage: setcap filename capabilities\n\n"
+				"capabilities must be specified as a number. Prefix with 0x for hexadecimal.\n");
+			return 1;
+		}
+		return do_setcap(argv[2], argv[3]);
+	}
+
+	if (strcmp(argv[1], "getcap") == 0) {
+		if (argc != 3)
+		{
+			printf("Usage: getcap filename\n");
+			return 1;
+		}
+		return do_getcap(argv[2]);
+	}
+
+	sprintf(command, "%s", argv[1]);
+	for (index = 2; index < argc; index++) {
+		sprintf(command, "%s %s", command, argv[index]);
+	}
+
+	write_fd = open(ORS_INPUT_FILE, O_WRONLY);
+	if (write_fd < 0) {
+		printf("TWRP does not appear to be running. Waiting for TWRP to start . . .\n");
+		printf("Press CTRL + C to quit.\n");
+		while (write_fd < 0)
+			write_fd = open(ORS_INPUT_FILE, O_WRONLY);
+	}
+	if (write(write_fd, command, sizeof(command)) != sizeof(command)) {
+		printf("Error sending command.\n");
+		close(write_fd);
+		return -1;
+	}
+	read_fd = open(ORS_OUTPUT_FILE, O_RDONLY);
+	if (read_fd < 0) {
+		printf("Unable to open %s for read.\n", ORS_OUTPUT_FILE);
+		return -1;
+	}
+	memset(&result, 0, sizeof(result));
+	while (read(read_fd, &result, sizeof(result)) > 0) {
+		result[510] = '\n';
+		result[511] = '\0';
+		printf("%s", result);
+		memset(&result, 0, sizeof(result));
+	}
+	close(write_fd);
+	close(read_fd);
+	return 0;
+}
diff --git a/orscmd/orscmd.h b/orscmd/orscmd.h
new file mode 100755
index 0000000..e88d9a5
--- /dev/null
+++ b/orscmd/orscmd.h
@@ -0,0 +1,22 @@
+/*
+		TWRP is free software: you can redistribute it and/or modify
+		it under the terms of the GNU General Public License as published by
+		the Free Software Foundation, either version 3 of the License, or
+		(at your option) any later version.
+
+		TWRP is distributed in the hope that it will be useful,
+		but WITHOUT ANY WARRANTY; without even the implied warranty of
+		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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __ORSCMD_H
+#define __ORSCMD_H
+
+#define ORS_INPUT_FILE "/system/bin/orsin"
+#define ORS_OUTPUT_FILE "/system/bin/orsout"
+
+#endif //__ORSCMD_H
diff --git a/otafault/Android.mk b/otafault/Android.mk
new file mode 100644
index 0000000..13ab711
--- /dev/null
+++ b/otafault/Android.mk
@@ -0,0 +1,54 @@
+# Copyright 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific languae governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+otafault_static_libs := \
+    libziparchive \
+    libz \
+    libselinux \
+    libbase \
+    liblog
+
+LOCAL_CFLAGS := \
+    -Werror \
+    -Wthread-safety \
+    -Wthread-safety-negative \
+    -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS
+
+LOCAL_SRC_FILES := config.cpp ota_io.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := libotafault
+LOCAL_CLANG := true
+LOCAL_C_INCLUDES := $(commands_recovery_local_path)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+LOCAL_WHOLE_STATIC_LIBRARIES := $(otafault_static_libs)
+
+include $(BUILD_STATIC_LIBRARY)
+
+# otafault_test (static executable)
+# ===============================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := config.cpp ota_io.cpp test.cpp
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := otafault_test
+LOCAL_STATIC_LIBRARIES := $(otafault_static_libs)
+LOCAL_CFLAGS := -Werror
+LOCAL_C_INCLUDES := $(commands_recovery_local_path)
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+
+include $(BUILD_EXECUTABLE)
diff --git a/otautil/Android.bp b/otautil/Android.bp
old mode 100644
new mode 100755
diff --git a/otautil/dirutil.cpp b/otautil/dirutil.cpp
index ae1cd5c..a447ad4 100644
--- a/otautil/dirutil.cpp
+++ b/otautil/dirutil.cpp
@@ -22,6 +22,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <utime.h>
 
 #include <string>
 
@@ -114,3 +115,187 @@
   }
   return 0;
 }
+
+static DirStatus
+getPathDirStatus(const char *path)
+{
+    struct stat st;
+    int err;
+
+    err = stat(path, &st);
+    if (err == 0) {
+        /* Something's there; make sure it's a directory.
+         */
+        if (S_ISDIR(st.st_mode)) {
+            return DirStatus::DDIR;
+        }
+        errno = ENOTDIR;
+        return DirStatus::DILLEGAL;
+    } else if (errno != ENOENT) {
+        /* Something went wrong, or something in the path
+         * is bad.  Can't do anything in this situation.
+         */
+        return DirStatus::DILLEGAL;
+    }
+    return DirStatus::DMISSING;
+}
+
+int
+dirCreateHierarchy(const char *path, int mode,
+        const struct utimbuf *timestamp, bool stripFileName,
+        struct selabel_handle *sehnd)
+{
+    DirStatus ds;
+
+    /* Check for an empty string before we bother
+     * making any syscalls.
+     */
+    if (path[0] == '\0') {
+        errno = ENOENT;
+        return -1;
+    }
+    // Allocate a path that we can modify; stick a slash on
+    // the end to make things easier.
+    std::string cpath = path;
+    if (stripFileName) {
+        // Strip everything after the last slash.
+        size_t pos = cpath.rfind('/');
+        if (pos == std::string::npos) {
+            errno = ENOENT;
+            return -1;
+        }
+        cpath.resize(pos + 1);
+    } else {
+        // Make sure that the path ends in a slash.
+        cpath.push_back('/');
+    }
+
+    /* See if it already exists.
+     */
+    ds = getPathDirStatus(cpath.c_str());
+    if (ds == DirStatus::DDIR) {
+        return 0;
+    } else if (ds == DirStatus::DILLEGAL) {
+        return -1;
+    }
+
+    /* Walk up the path from the root and make each level.
+     * If a directory already exists, no big deal.
+     */
+    const char *path_start = &cpath[0];
+    char *p = &cpath[0];
+    while (*p != '\0') {
+        /* Skip any slashes, watching out for the end of the string.
+         */
+        while (*p != '\0' && *p == '/') {
+            p++;
+        }
+        if (*p == '\0') {
+            break;
+        }
+
+        /* Find the end of the next path component.
+         * We know that we'll see a slash before the NUL,
+         * because we added it, above.
+         */
+        while (*p != '/') {
+            p++;
+        }
+        *p = '\0';
+
+        /* Check this part of the path and make a new directory
+         * if necessary.
+         */
+        ds = getPathDirStatus(path_start);
+        if (ds == DirStatus::DILLEGAL) {
+            /* Could happen if some other process/thread is
+             * messing with the filesystem.
+             */
+            return -1;
+        } else if (ds == DirStatus::DMISSING) {
+            int err;
+
+            char *secontext = NULL;
+
+            if (sehnd) {
+                selabel_lookup(sehnd, &secontext, path_start, mode);
+                setfscreatecon(secontext);
+            }
+
+            err = mkdir(path_start, mode);
+
+            if (secontext) {
+                freecon(secontext);
+                setfscreatecon(NULL);
+            }
+
+            if (err != 0) {
+                return -1;
+            }
+            if (timestamp != NULL && utime(path_start, timestamp)) {
+                return -1;
+            }
+        }
+        // else, this directory already exists.
+
+        // Repair the path and continue.
+        *p = '/';
+    }
+    return 0;
+}
+
+int
+dirUnlinkHierarchy(const char *path)
+{
+    struct stat st;
+    DIR *dir;
+    struct dirent *de;
+    int fail = 0;
+
+    /* is it a file or directory? */
+    if (lstat(path, &st) < 0) {
+        return -1;
+    }
+
+    /* a file, so unlink it */
+    if (!S_ISDIR(st.st_mode)) {
+        return unlink(path);
+    }
+
+    /* a directory, so open handle */
+    dir = opendir(path);
+    if (dir == NULL) {
+        return -1;
+    }
+
+    /* recurse over components */
+    errno = 0;
+    while ((de = readdir(dir)) != NULL) {
+        //TODO: don't blow the stack
+        char dn[PATH_MAX];
+        if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
+            continue;
+        }
+        snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
+        if (dirUnlinkHierarchy(dn) < 0) {
+            fail = 1;
+            break;
+        }
+        errno = 0;
+    }
+    /* in case readdir or unlink_recursive failed */
+    if (fail || errno < 0) {
+        int save = errno;
+        closedir(dir);
+        errno = save;
+        return -1;
+    }
+
+    /* close directory handle */
+    if (closedir(dir) < 0) {
+        return -1;
+    }
+
+    /* delete target directory */
+    return rmdir(path);
+}
diff --git a/otautil/include/otautil/dirutil.h b/otautil/include/otautil/dirutil.h
index 85d6c16..e3d0219 100644
--- a/otautil/include/otautil/dirutil.h
+++ b/otautil/include/otautil/dirutil.h
@@ -36,4 +36,25 @@
 int mkdir_recursively(const std::string& path, mode_t mode, bool strip_filename,
                       const struct selabel_handle* sehnd);
 
+/* Like "mkdir -p", try to guarantee that all directories
+ * specified in path are present, creating as many directories
+ * as necessary.  The specified mode is passed to all mkdir
+ * calls;  no modifications are made to umask.
+ *
+ * If stripFileName is set, everything after the final '/'
+ * is stripped before creating the directory hierarchy.
+ *
+ * If timestamp is non-NULL, new directories will be timestamped accordingly.
+ *
+ * Returns 0 on success; returns -1 (and sets errno) on failure
+ * (usually if some element of path is not a directory).
+ */
+int dirCreateHierarchy(const char *path, int mode,
+        const struct utimbuf *timestamp, bool stripFileName,
+        struct selabel_handle* sehnd);
+
+/* rm -rf <path>
+ */
+int dirUnlinkHierarchy(const char *path);
+
 #endif  // OTAUTIL_DIRUTIL_H_
diff --git a/otautil/include/otautil/package.h b/otautil/include/otautil/package.h
old mode 100644
new mode 100755
index f4f4d34..c933e78
--- a/otautil/include/otautil/package.h
+++ b/otautil/include/otautil/package.h
@@ -38,9 +38,9 @@
 class Package : public VerifierInterface {
  public:
   static std::unique_ptr<Package> CreateMemoryPackage(
-      const std::string& path, const std::function<void(float)>& set_progress);
+      const std::string& path);
   static std::unique_ptr<Package> CreateMemoryPackage(
-      std::vector<uint8_t> content, const std::function<void(float)>& set_progress);
+      std::vector<uint8_t> content);
   static std::unique_ptr<Package> CreateFilePackage(const std::string& path,
                                                     const std::function<void(float)>& set_progress);
 
diff --git a/otautil/package.cpp b/otautil/package.cpp
old mode 100644
new mode 100755
index 242204e..a6febb3
--- a/otautil/package.cpp
+++ b/otautil/package.cpp
@@ -32,11 +32,10 @@
 class MemoryPackage : public Package {
  public:
   // Constructs the class from a file. We will memory maps the file later.
-  MemoryPackage(const std::string& path, std::unique_ptr<MemMapping> map,
-                const std::function<void(float)>& set_progress);
+  MemoryPackage(const std::string& path, std::unique_ptr<MemMapping> map);
 
   // Constructs the class from the package bytes in |content|.
-  MemoryPackage(std::vector<uint8_t> content, const std::function<void(float)>& set_progress);
+  MemoryPackage(std::vector<uint8_t> content);
 
   ~MemoryPackage() override;
 
@@ -118,14 +117,14 @@
 };
 
 std::unique_ptr<Package> Package::CreateMemoryPackage(
-    const std::string& path, const std::function<void(float)>& set_progress) {
+    const std::string& path) {
   std::unique_ptr<MemMapping> mmap = std::make_unique<MemMapping>();
   if (!mmap->MapFile(path)) {
     LOG(ERROR) << "failed to map file";
     return nullptr;
   }
 
-  return std::make_unique<MemoryPackage>(path, std::move(mmap), set_progress);
+  return std::make_unique<MemoryPackage>(path, std::move(mmap));
 }
 
 std::unique_ptr<Package> Package::CreateFilePackage(
@@ -146,25 +145,21 @@
 }
 
 std::unique_ptr<Package> Package::CreateMemoryPackage(
-    std::vector<uint8_t> content, const std::function<void(float)>& set_progress) {
-  return std::make_unique<MemoryPackage>(std::move(content), set_progress);
+    std::vector<uint8_t> content) {
+  return std::make_unique<MemoryPackage>(std::move(content));
 }
 
-MemoryPackage::MemoryPackage(const std::string& path, std::unique_ptr<MemMapping> map,
-                             const std::function<void(float)>& set_progress)
+MemoryPackage::MemoryPackage(const std::string& path, std::unique_ptr<MemMapping> map)
     : map_(std::move(map)), path_(path), zip_handle_(nullptr) {
   addr_ = map_->addr;
   package_size_ = map_->length;
-  set_progress_ = set_progress;
 }
 
-MemoryPackage::MemoryPackage(std::vector<uint8_t> content,
-                             const std::function<void(float)>& set_progress)
+MemoryPackage::MemoryPackage(std::vector<uint8_t> content)
     : package_content_(std::move(content)), zip_handle_(nullptr) {
   CHECK(!package_content_.empty());
   addr_ = package_content_.data();
   package_size_ = package_content_.size();
-  set_progress_ = set_progress;
 }
 
 MemoryPackage::~MemoryPackage() {
diff --git a/ozip_decrypt/Android.mk b/ozip_decrypt/Android.mk
new file mode 100755
index 0000000..8d5da48
--- /dev/null
+++ b/ozip_decrypt/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := ozip_decrypt
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := ozip_decrypt.cpp
+LOCAL_C_INCLUDES := external/boringssl/src/include
+LOCAL_SHARED_LIBRARIES := libcrypto
+include $(BUILD_EXECUTABLE)
diff --git a/ozip_decrypt/ozip_decrypt.cpp b/ozip_decrypt/ozip_decrypt.cpp
new file mode 100644
index 0000000..97826ec
--- /dev/null
+++ b/ozip_decrypt/ozip_decrypt.cpp
@@ -0,0 +1,152 @@
+/*
+	Copyright 2020 Mauronofrio
+
+	This file is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	This file is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	GNU General Public License <http://www.gnu.org/licenses/>.
+*/
+
+#include <iostream>
+#include <string.h>
+#include <openssl/evp.h>
+#include <openssl/ssl.h>
+#define _FILE_OFFSET_BITS 64
+//extern "C" __int64 __cdecl _ftelli64(FILE*);
+
+using namespace std;
+typedef std::basic_string<unsigned char> u_string;
+
+int decrypt(unsigned char* ciphertext, int ciphertext_len, unsigned char* key,
+	unsigned char* iv, unsigned char* plaintext)
+{
+	EVP_CIPHER_CTX* ctx;
+	int len;
+	int plaintext_len;
+	ctx = EVP_CIPHER_CTX_new();
+	EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, iv);
+	EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len);
+	plaintext_len = len;
+	EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
+	plaintext_len += len;
+	EVP_CIPHER_CTX_free(ctx);
+	return plaintext_len;
+}
+
+std::string hexToASCII(string hex)
+{
+	int len = hex.length();
+	std::string newString;
+	for (int i = 0; i < len; i += 2)
+	{
+		string byte = hex.substr(i, 2);
+		char chr = (char)(int)strtol(byte.c_str(), nullptr, 16);
+		newString.push_back(chr);
+	}
+	return newString;
+}
+
+bool testkey(const char* keyf, const char* path) {
+	u_string key = (unsigned char*)(hexToASCII(keyf)).c_str();
+	int data[17];
+	FILE* fps = fopen(path, "rb");
+	fseek(fps, 4176, SEEK_SET);
+	fread(data, sizeof(char), 16, fps);
+	fclose(fps);
+	u_string udata = (unsigned char*)data;
+	EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+	EVP_CIPHER_CTX_init(ctx);
+	EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key.c_str(), NULL);
+	EVP_CIPHER_CTX_set_padding(ctx, false);
+	unsigned char buffer[1024], * pointer = buffer;
+	int outlen;
+	EVP_DecryptUpdate(ctx, pointer, &outlen, udata.c_str(), udata.length());
+	pointer += outlen;
+	EVP_DecryptFinal_ex(ctx, pointer, &outlen);
+	pointer += outlen;
+	EVP_CIPHER_CTX_free(ctx);
+	u_string test= u_string(buffer, pointer - buffer);
+	u_string checktest = test.substr(0, 4);
+	if (checktest == ((unsigned char*) "\x50\x4B\x03\x04") || checktest == ((unsigned char*) "\x41\x4E\x44\x52")) {
+		return true;
+	}
+	return false;
+}
+
+int main(int argc, char* argv[])
+{
+
+	if (argc != 3)
+	{
+		printf("Usage: ozipdecrypt key [*.ozip]\n");
+		return 0;
+	}
+	const char* key = argv[1];
+	const char* path = argv[2];
+	FILE* fp = fopen(path, "rb");
+	char magic[13];
+	fgets(magic, sizeof(magic), fp);
+	string temp(path);
+	temp = (temp.substr(0, temp.size() - 5)).append(".zip");
+	const char* destpath= temp.c_str();
+	if (strcmp(magic, "OPPOENCRYPT!") != 0)
+	{
+		printf("This is not an .ozip file!\n");
+		fclose(fp);
+		int rencheck = rename(path, destpath);
+		if (rencheck == 0) {
+			printf("Renamed .ozip file in .zip file\n");
+		}
+		else
+		{
+			printf("Unable to rename .ozip file in .zip file\n");
+		}
+		return 0;
+	}
+	if (testkey(key, path) == false)
+	{
+		printf("Key is not good!\n");
+		fclose(fp);
+		return 0;
+	}
+	else {
+		printf("Key is good!\n");
+	}
+	FILE* fp2 = fopen(destpath, "wb");
+	fseek(fp, 0L, SEEK_END);
+	unsigned long int sizetot = ftello(fp);
+	fseek(fp, 4176, SEEK_SET);
+	int bdata[16384];
+	unsigned long int sizeseek;
+	printf("Decrypting...\n");
+	while (true)
+	{
+		unsigned char data[17];
+		fread(data, sizeof(char), 16, fp);
+		decrypt(data, sizeof(data), (unsigned char*)(hexToASCII(key)).c_str(), NULL, data);
+		fwrite(data, sizeof(char), 16, fp2);
+		sizeseek = ftello(fp);
+		if ((sizetot - sizeseek) <= 16384) {
+			fread(bdata, sizeof(char), (sizetot - sizeseek), fp);
+			fwrite(bdata, sizeof(char), (sizetot - sizeseek), fp2);
+			break;
+		}
+		else
+		{
+			fread(bdata, sizeof(char), 16384, fp);
+			fwrite(bdata, sizeof(char), 16384, fp2);
+		}
+	}
+	printf("File succesfully decrypted, saved in %s\n", destpath);
+	fclose(fp2);
+	fclose(fp);
+	return 0;
+}
+
diff --git a/partition.cpp b/partition.cpp
new file mode 100755
index 0000000..0a87c4e
--- /dev/null
+++ b/partition.cpp
@@ -0,0 +1,3547 @@
+/*
+	Copyright 2013 to 2021 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <iostream>
+#include <libgen.h>
+#include <zlib.h>
+#include <sstream>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <libsnapshot/snapshot.h>
+
+#include "cutils/properties.h"
+#include "libblkid/include/blkid.h"
+#include "variables.h"
+#include "twcommon.h"
+#include "partitions.hpp"
+#include "data.hpp"
+#include "twrp-functions.hpp"
+#include "twrpTar.hpp"
+#include "exclude.hpp"
+#include "infomanager.hpp"
+#include "set_metadata.h"
+#include "gui/gui.hpp"
+#include "adbbu/libtwadbbu.hpp"
+#ifdef TW_INCLUDE_CRYPTO
+	// #include "crypto/fde/cryptfs.h"
+	#include "cryptfs.h"
+	#include "Decrypt.h"
+#endif
+extern "C" {
+	#include "mtdutils/mtdutils.h"
+	#include "mtdutils/mounts.h"
+#ifdef USE_EXT4
+	// #include "make_ext4fs.h" TODO need ifdef for android8
+	#include <ext4_utils/make_ext4fs.h>
+#endif
+#ifdef TW_INCLUDE_CRYPTO
+	#include "gpt/gpt.h"
+#endif
+}
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#ifdef HAVE_CAPABILITIES
+#include <sys/capability.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#endif
+#include <sparse_format.h>
+#include "progresstracking.hpp"
+
+#define CRYPT_FOOTER_OFFSET 0x4000
+
+using namespace std;
+
+static int auto_index = 0; // v2 fstab allows you to specify a mount point of "auto" with no /. These items are given a mount point of /auto* where * == auto_index
+
+extern struct selabel_handle *selinux_handle;
+extern bool datamedia;
+
+struct flag_list {
+	const char *name;
+	unsigned long flag;
+};
+
+const struct flag_list mount_flags[] = {
+	{ "noatime",          MS_NOATIME },
+	{ "noexec",           MS_NOEXEC },
+	{ "nosuid",           MS_NOSUID },
+	{ "nodev",            MS_NODEV },
+	{ "nodiratime",       MS_NODIRATIME },
+	{ "ro",               MS_RDONLY },
+	{ "rw",               0 },
+	{ "remount",          MS_REMOUNT },
+	{ "bind",             MS_BIND },
+	{ "rec",              MS_REC },
+#ifdef MS_UNBINDABLE
+	{ "unbindable",       MS_UNBINDABLE },
+#endif
+#ifdef MS_PRIVATE
+	{ "private",          MS_PRIVATE },
+#endif
+#ifdef MS_SLAVE
+	{ "slave",            MS_SLAVE },
+#endif
+#ifdef MS_SHARED
+	{ "shared",           MS_SHARED },
+#endif
+	{ "sync",             MS_SYNCHRONOUS },
+	{ 0,                  0 },
+};
+
+const char *ignored_mount_items[] = {
+	"defaults=",
+	"errors=",
+	"latemount",
+	"sysfs_path=",
+	NULL
+};
+
+enum TW_FSTAB_FLAGS {
+	TWFLAG_DEFAULTS, // Retain position
+	TWFLAG_ANDSEC,
+	TWFLAG_BACKUP,
+	TWFLAG_BACKUPNAME,
+	TWFLAG_BLOCKSIZE,
+	TWFLAG_CANBEWIPED,
+	TWFLAG_CANENCRYPTBACKUP,
+	TWFLAG_DISPLAY,
+	TWFLAG_ENCRYPTABLE,
+	TWFLAG_FILEENCRYPTION,
+	TWFLAG_METADATA_ENCRYPTION,
+	TWFLAG_FLASHIMG,
+	TWFLAG_FORCEENCRYPT,
+	TWFLAG_FSFLAGS,
+	TWFLAG_IGNOREBLKID,
+	TWFLAG_LENGTH,
+	TWFLAG_MOUNTTODECRYPT,
+	TWFLAG_QUOTA,
+	TWFLAG_REMOVABLE,
+	TWFLAG_SETTINGSSTORAGE,
+	TWFLAG_STORAGE,
+	TWFLAG_STORAGENAME,
+	TWFLAG_SUBPARTITIONOF,
+	TWFLAG_SYMLINK,
+	TWFLAG_USERDATAENCRYPTBACKUP,
+	TWFLAG_USERMRF,
+	TWFLAG_WIPEDURINGFACTORYRESET,
+	TWFLAG_WIPEINGUI,
+	TWFLAG_SLOTSELECT,
+	TWFLAG_WAIT,
+	TWFLAG_VERIFY,
+	TWFLAG_CHECK,
+	TWFLAG_ALTDEVICE,
+	TWFLAG_NOTRIM,
+	TWFLAG_VOLDMANAGED,
+	TWFLAG_FORMATTABLE,
+	TWFLAG_RESIZE,
+	TWFLAG_KEYDIRECTORY,
+	TWFLAG_WRAPPEDKEY,
+	TWFLAG_ADOPTED_MOUNT_DELAY,
+	TWFLAG_DM_USE_ORIGINAL_PATH,
+	TWFLAG_LOGICAL,
+};
+
+/* Flags without a trailing '=' are considered dual format flags and can be
+ * written as either 'flagname' or 'flagname=', where the character following
+ * the '=' is Y,y,1 for true and false otherwise.
+ */
+const struct flag_list tw_flags[] = {
+	{ "andsec",                 TWFLAG_ANDSEC },
+	{ "backup",                 TWFLAG_BACKUP },
+	{ "backupname=",            TWFLAG_BACKUPNAME },
+	{ "blocksize=",             TWFLAG_BLOCKSIZE },
+	{ "canbewiped",             TWFLAG_CANBEWIPED },
+	{ "canencryptbackup",       TWFLAG_CANENCRYPTBACKUP },
+	{ "defaults",               TWFLAG_DEFAULTS },
+	{ "display=",               TWFLAG_DISPLAY },
+	{ "encryptable=",           TWFLAG_ENCRYPTABLE },
+	{ "fileencryption=",        TWFLAG_FILEENCRYPTION },
+	{ "metadata_encryption=",   TWFLAG_METADATA_ENCRYPTION },
+	{ "flashimg",               TWFLAG_FLASHIMG },
+	{ "forceencrypt=",          TWFLAG_FORCEENCRYPT },
+	{ "fsflags=",               TWFLAG_FSFLAGS },
+	{ "ignoreblkid",            TWFLAG_IGNOREBLKID },
+	{ "length=",                TWFLAG_LENGTH },
+	{ "mounttodecrypt",         TWFLAG_MOUNTTODECRYPT },
+	{ "quota",                  TWFLAG_QUOTA },
+	{ "removable",              TWFLAG_REMOVABLE },
+	{ "settingsstorage",        TWFLAG_SETTINGSSTORAGE },
+	{ "storage",                TWFLAG_STORAGE },
+	{ "storagename=",           TWFLAG_STORAGENAME },
+	{ "subpartitionof=",        TWFLAG_SUBPARTITIONOF },
+	{ "symlink=",               TWFLAG_SYMLINK },
+	{ "userdataencryptbackup",  TWFLAG_USERDATAENCRYPTBACKUP },
+	{ "usermrf",                TWFLAG_USERMRF },
+	{ "wipeduringfactoryreset", TWFLAG_WIPEDURINGFACTORYRESET },
+	{ "wipeingui",              TWFLAG_WIPEINGUI },
+	{ "slotselect",             TWFLAG_SLOTSELECT },
+	{ "wait",                   TWFLAG_WAIT },
+	{ "verify",                 TWFLAG_VERIFY },
+	{ "check",                  TWFLAG_CHECK },
+	{ "altdevice",              TWFLAG_ALTDEVICE },
+	{ "notrim",                 TWFLAG_NOTRIM },
+	{ "voldmanaged=",           TWFLAG_VOLDMANAGED },
+	{ "formattable",            TWFLAG_FORMATTABLE },
+	{ "resize",                 TWFLAG_RESIZE },
+	{ "keydirectory=",          TWFLAG_KEYDIRECTORY },
+	{ "wrappedkey",             TWFLAG_WRAPPEDKEY },
+	{ "adopted_mount_delay=",   TWFLAG_ADOPTED_MOUNT_DELAY },
+	{ "dm_use_original_path",   TWFLAG_DM_USE_ORIGINAL_PATH },
+	{ "logical",                TWFLAG_LOGICAL },
+	{ 0,                        0 },
+};
+
+TWPartition::TWPartition() {
+	Can_Be_Mounted = false;
+	Can_Be_Wiped = false;
+	Can_Be_Backed_Up = false;
+	Use_Rm_Rf = false;
+	Wipe_During_Factory_Reset = false;
+	Wipe_Available_in_GUI = false;
+	Is_SubPartition = false;
+	Has_SubPartition = false;
+	SubPartition_Of = "";
+	Symlink_Path = "";
+	Symlink_Mount_Point = "";
+	Mount_Point = "";
+	Backup_Path = "";
+	Wildcard_Block_Device = false;
+	Sysfs_Entry = "";
+	Actual_Block_Device = "";
+	Primary_Block_Device = "";
+	Alternate_Block_Device = "";
+	Removable = false;
+	Is_Present = false;
+	Length = 0;
+	Size = 0;
+	Used = 0;
+	Free = 0;
+	Backup_Size = 0;
+	Can_Be_Encrypted = false;
+	Is_Encrypted = false;
+	Is_Decrypted = false;
+	Is_FBE = false;
+	Mount_To_Decrypt = false;
+	Decrypted_Block_Device = "";
+	Display_Name = "";
+	Backup_Display_Name = "";
+	Storage_Name = "";
+	Backup_Name = "";
+	Backup_FileName = "";
+	MTD_Name = "";
+	Backup_Method = BM_NONE;
+	Can_Encrypt_Backup = false;
+	Use_Userdata_Encryption = false;
+	Has_Data_Media = false;
+	Has_Android_Secure = false;
+	Is_Storage = false;
+	Is_Settings_Storage = false;
+	Storage_Path = "";
+	Current_File_System = "";
+	Fstab_File_System = "";
+	Mount_Flags = 0;
+	Mount_Options = "";
+	Format_Block_Size = 0;
+	Ignore_Blkid = false;
+	Crypto_Key_Location = "";
+	MTP_Storage_ID = 0;
+	Can_Flash_Img = false;
+	Mount_Read_Only = false;
+	Is_Adopted_Storage = false;
+	Adopted_GUID = "";
+	SlotSelect = false;
+	Key_Directory = "";
+	Is_Super = false;
+	Adopted_Mount_Delay = 0;
+	Original_Path = "";
+	Use_Original_Path = false;
+}
+
+TWPartition::~TWPartition(void) {
+	// Do nothing
+}
+
+bool TWPartition::Process_Fstab_Line(const char *fstab_line, bool Display_Error, std::map<string, Flags_Map> *twrp_flags) {
+	char full_line[MAX_FSTAB_LINE_LENGTH];
+	char twflags[MAX_FSTAB_LINE_LENGTH] = "";
+	char* ptr;
+	int line_len = strlen(fstab_line), index = 0, item_index = 0;
+	bool skip = false;
+	int fstab_version = 1, mount_point_index = 0, fs_index = 1, block_device_index = 2;
+	TWPartition *additional_entry = NULL;
+	std::map<string, Flags_Map>::iterator it;
+
+	strlcpy(full_line, fstab_line, sizeof(full_line));
+
+	for (index = 0; index < line_len; index++) {
+		if (full_line[index] == 34)
+			skip = !skip;
+		if (!skip && full_line[index] <= 32)
+			full_line[index] = '\0';
+	}
+	if (line_len < 10)
+		return false; // There can't possibly be a valid fstab line that is less than 10 chars
+	if (fstab_line[0] == '#')
+		return false; // skip comments
+
+	if (strncmp(fstab_line, "/dev/", strlen("/dev/")) == 0 || strncmp(fstab_line, "/devices/", strlen("/devices/")) == 0) {
+		fstab_version = 2;
+		block_device_index = 0;
+		mount_point_index = 1;
+		fs_index = 2;
+	}
+
+	if (fstab_line[0] != '/') {
+		block_device_index = 0;
+		fstab_version = 2;
+		mount_point_index = 1;
+		fs_index = 2;
+	}
+
+	index = 0;
+	while (index < line_len) {
+		while (index < line_len && full_line[index] == '\0')
+			index++;
+		if (index >= line_len)
+			continue;
+		ptr = full_line + index;
+		if (item_index == mount_point_index) {
+			Mount_Point = ptr;
+			if (fstab_version == 2 && Is_Super == false) {
+				additional_entry = PartitionManager.Find_Partition_By_Path(Mount_Point);
+				if (additional_entry) {
+					LOGINFO("Found an additional entry for '%s'\n", Mount_Point.c_str());
+				}
+			}
+			LOGINFO("Processing '%s'\n", Mount_Point.c_str());
+			Backup_Path = Mount_Point;
+			Storage_Path = Mount_Point;
+			Display_Name = ptr + 1;
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+			item_index++;
+		} else if (item_index == fs_index) {
+			// File System
+			Fstab_File_System = ptr;
+			Current_File_System = ptr;
+			item_index++;
+		} else if (item_index == block_device_index) {
+			// Primary Block Device
+			if (Fstab_File_System == "mtd" || Fstab_File_System == "yaffs2") {
+				MTD_Name = ptr;
+				Find_MTD_Block_Device(MTD_Name);
+			} else if (Fstab_File_System == "bml") {
+				if (Mount_Point == "/boot")
+					MTD_Name = "boot";
+				else if (Mount_Point == "/recovery")
+					MTD_Name = "recovery";
+				Primary_Block_Device = ptr;
+				if (*ptr != '/')
+					LOGERR("Until we get better BML support, you will have to find and provide the full block device path to the BML devices e.g. /dev/block/bml9 instead of the partition name\n");
+			} else {
+				Primary_Block_Device = ptr;
+				if (*ptr == '/')
+					Find_Real_Block_Device(Primary_Block_Device, Display_Error);
+			}
+			item_index++;
+		} else if (item_index > 2) {
+			if (fstab_version == 2) {
+				if (item_index == 3) {
+					Process_FS_Flags(ptr);
+					if (additional_entry) {
+						additional_entry->Save_FS_Flags(Fstab_File_System, Mount_Flags, Mount_Options);
+						return false; // We save the extra fs flags in the other partition entry and by returning false, this entry will be deleted
+					}
+				} else {
+					strlcpy(twflags, ptr, sizeof(twflags));
+				}
+				item_index++;
+			} else if (*ptr == '/') { // v2 fstab does not allow alternate block devices
+				// Alternate Block Device
+				Alternate_Block_Device = ptr;
+				Find_Real_Block_Device(Alternate_Block_Device, Display_Error);
+			} else if (strlen(ptr) > 7 && strncmp(ptr, "length=", 7) == 0) {
+				// Partition length
+				ptr += 7;
+				Length = atoi(ptr);
+			} else if (strlen(ptr) > 6 && strncmp(ptr, "flags=", 6) == 0) {
+				// Custom flags, save for later so that new values aren't overwritten by defaults
+				ptr += 6;
+				strlcpy(twflags, ptr, sizeof(twflags));
+			} else if (strlen(ptr) == 4 && (strncmp(ptr, "NULL", 4) == 0 || strncmp(ptr, "null", 4) == 0 || strncmp(ptr, "null", 4) == 0)) {
+				// Do nothing
+			} else {
+				// Unhandled data
+				LOGINFO("Unhandled fstab information '%s' in fstab line '%s'\n", ptr, fstab_line);
+			}
+		}
+		while (index < line_len && full_line[index] != '\0')
+			index++;
+	}
+
+	// override block devices from the v2 fstab with the ones we read from the twrp.flags file in case they are different
+	if (fstab_version == 2 && twrp_flags && twrp_flags->size() > 0) {
+		it = twrp_flags->find(Mount_Point);
+		if (it != twrp_flags->end()) {
+			if (!it->second.Primary_Block_Device.empty()) {
+				Primary_Block_Device = it->second.Primary_Block_Device;
+				Find_Real_Block_Device(Primary_Block_Device, Display_Error);
+			}
+			if (!it->second.Alternate_Block_Device.empty()) {
+				Alternate_Block_Device = it->second.Alternate_Block_Device;
+				Find_Real_Block_Device(Alternate_Block_Device, Display_Error);
+			}
+		}
+	}
+
+	if (strncmp(fstab_line, "/devices/", strlen("/devices/")) == 0) {
+		Sysfs_Entry = Primary_Block_Device;
+		Primary_Block_Device = "";
+		Is_Storage = true;
+		Removable = true;
+		Wipe_Available_in_GUI = true;
+		Wildcard_Block_Device = true;
+	}
+	if (Primary_Block_Device.find("*") != string::npos)
+		Wildcard_Block_Device = true;
+
+	if (Mount_Point == "auto") {
+		Mount_Point = "/auto";
+		char autoi[5];
+		sprintf(autoi, "%i", auto_index);
+		Mount_Point += autoi;
+		Backup_Path = Mount_Point;
+		Storage_Path = Mount_Point;
+		Backup_Name = Mount_Point.substr(1);
+		auto_index++;
+		Setup_File_System(Display_Error);
+		Display_Name = "Storage";
+		Backup_Display_Name = Display_Name;
+		Storage_Name = Display_Name;
+		Can_Be_Backed_Up = false;
+		Wipe_Available_in_GUI = true;
+		Is_Storage = true;
+		Removable = true;
+		Wipe_Available_in_GUI = true;
+	} else if (!Is_File_System(Fstab_File_System) && !Is_Image(Fstab_File_System)) {
+		if (Display_Error)
+			LOGERR("Unknown File System: '%s'\n", Fstab_File_System.c_str());
+		else
+			LOGINFO("Unknown File System: '%s'\n", Fstab_File_System.c_str());
+		return false;
+	} else if (Is_File_System(Fstab_File_System)) {
+		Find_Actual_Block_Device();
+		Setup_File_System(Display_Error);
+		Backup_Name = Display_Name = Mount_Point.substr(1, Mount_Point.size() - 1);
+		if (Mount_Point == "/" || Mount_Point == "/system" || Mount_Point == "/system_root") {
+			Mount_Point = PartitionManager.Get_Android_Root_Path();
+			Backup_Path = Mount_Point;
+			Storage_Path = Mount_Point;
+			Display_Name = "System";
+			Backup_Name = "system";
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+			Wipe_Available_in_GUI = false;
+			Can_Be_Backed_Up = false;
+			Can_Be_Wiped = false;
+			Make_Dir(PartitionManager.Get_Android_Root_Path(), true);
+		} else if (Mount_Point == "/system_ext") {
+			Display_Name = "System_EXT";
+			Backup_Name = "System_EXT";
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+			Can_Be_Backed_Up = Wipe_Available_in_GUI = Is_Super ? false : true;
+		} else if (Mount_Point == "/product") {
+			Display_Name = "Product";
+			Backup_Name = "Product";
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+			Can_Be_Backed_Up = Wipe_Available_in_GUI = Is_Super ? false : true;
+		} else if (Mount_Point == "/odm") {
+			Display_Name = "ODM";
+			Backup_Name = "ODM";
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+			Can_Be_Backed_Up = Wipe_Available_in_GUI = Is_Super ? false : true;
+		} else if (Mount_Point == "/data") {
+			Display_Name = "Data";
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+			Wipe_Available_in_GUI = true;
+			Wipe_During_Factory_Reset = true;
+			Can_Be_Backed_Up = true;
+			Can_Encrypt_Backup = true;
+			Use_Userdata_Encryption = true;
+		} else if (Mount_Point == "/cache") {
+			Display_Name = "Cache";
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+			Wipe_Available_in_GUI = true;
+			Wipe_During_Factory_Reset = true;
+			Can_Be_Backed_Up = true;
+		} else if (Mount_Point == "/datadata") {
+			Wipe_During_Factory_Reset = true;
+			Display_Name = "DataData";
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+			Is_SubPartition = true;
+			SubPartition_Of = "/data";
+			DataManager::SetValue(TW_HAS_DATADATA, 1);
+			Can_Be_Backed_Up = true;
+			Can_Encrypt_Backup = true;
+			Use_Userdata_Encryption = false; // This whole partition should be encrypted
+		} else if (Mount_Point == "/sd-ext") {
+			Wipe_During_Factory_Reset = true;
+			Display_Name = "SD-Ext";
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+			Wipe_Available_in_GUI = true;
+			Removable = true;
+			Can_Be_Backed_Up = true;
+			Can_Encrypt_Backup = true;
+			Use_Userdata_Encryption = true;
+		} else if (Mount_Point == "/boot") {
+			Display_Name = "Boot";
+			Backup_Display_Name = Display_Name;
+			DataManager::SetValue("tw_boot_is_mountable", 1);
+			Can_Be_Backed_Up = true;
+		} else if (Mount_Point == "/vendor") {
+			Display_Name = "Vendor";
+			Backup_Display_Name = Display_Name;
+			Storage_Name = Display_Name;
+		}
+#ifdef TW_EXTERNAL_STORAGE_PATH
+		if (Mount_Point == EXPAND(TW_EXTERNAL_STORAGE_PATH)) {
+			Is_Storage = true;
+			Storage_Path = EXPAND(TW_EXTERNAL_STORAGE_PATH);
+			Removable = true;
+			Wipe_Available_in_GUI = true;
+#else
+		if (Mount_Point == "/sdcard" || Mount_Point == "/external_sd" || Mount_Point == "/external_sdcard") {
+			Is_Storage = true;
+			Removable = true;
+			Wipe_Available_in_GUI = true;
+#endif
+		}
+#ifdef TW_INTERNAL_STORAGE_PATH
+		if (Mount_Point == EXPAND(TW_INTERNAL_STORAGE_PATH)) {
+			Is_Storage = true;
+			Is_Settings_Storage = true;
+			Storage_Path = EXPAND(TW_INTERNAL_STORAGE_PATH);
+			Wipe_Available_in_GUI = true;
+		}
+#else
+		if (Mount_Point == "/emmc" || Mount_Point == "/internal_sd" || Mount_Point == "/internal_sdcard") {
+			Is_Storage = true;
+			Is_Settings_Storage = true;
+			Wipe_Available_in_GUI = true;
+		}
+#endif
+	} else if (Is_Image(Fstab_File_System)) {
+		Find_Actual_Block_Device();
+		Setup_Image();
+		if (Mount_Point == "/boot") {
+			Display_Name = "Boot";
+			Backup_Display_Name = Display_Name;
+			Can_Be_Backed_Up = true;
+			Can_Flash_Img = true;
+		} else if (Mount_Point == "/recovery") {
+			Display_Name = "Recovery";
+			Backup_Display_Name = Display_Name;
+			Can_Flash_Img = true;
+		} else if (Mount_Point == "/system_image") {
+			Display_Name = "System Image";
+			Backup_Display_Name = Display_Name;
+			Can_Flash_Img = true;
+			Can_Be_Backed_Up = true;
+		} else if (Mount_Point == "/vendor_image") {
+			Display_Name = "Vendor Image";
+			Backup_Display_Name = Display_Name;
+			Can_Flash_Img = true;
+			Can_Be_Backed_Up = true;
+		}
+	}
+
+	// Process TWRP fstab flags
+	if (strlen(twflags) > 0) {
+		string Prev_Display_Name = Display_Name;
+		string Prev_Storage_Name = Storage_Name;
+		string Prev_Backup_Display_Name = Backup_Display_Name;
+		Display_Name = "";
+		Storage_Name = "";
+		Backup_Display_Name = "";
+
+		Process_TW_Flags(twflags, (fstab_version == 1), fstab_version);
+		Save_FS_Flags(Fstab_File_System, Mount_Flags, Mount_Options);
+
+		bool has_display_name = !Display_Name.empty();
+		bool has_storage_name = !Storage_Name.empty();
+		bool has_backup_name = !Backup_Display_Name.empty();
+		if (!has_display_name) Display_Name = Prev_Display_Name;
+		if (!has_storage_name) Storage_Name = Prev_Storage_Name;
+		if (!has_backup_name) Backup_Display_Name = Prev_Backup_Display_Name;
+
+		if (has_display_name && !has_storage_name)
+			Storage_Name = Display_Name;
+		if (!has_display_name && has_storage_name)
+			Display_Name = Storage_Name;
+		if (has_display_name && !has_backup_name && Backup_Display_Name != "Android Secure")
+			Backup_Display_Name = Display_Name;
+		if (!has_display_name && has_backup_name)
+			Display_Name = Backup_Display_Name;
+	}
+
+	if (fstab_version == 2 && twrp_flags && twrp_flags->size() > 0) {
+		it = twrp_flags->find(Mount_Point);
+		if (it != twrp_flags->end()) {
+			char twrpflags[MAX_FSTAB_LINE_LENGTH] = "";
+			int skip = 0;
+			string Flags = it->second.Flags;
+			strcpy(twrpflags, Flags.c_str());
+			if (strlen(twrpflags) > strlen("flags=") && strncmp(twrpflags, "flags=", strlen("flags=")) == 0)
+				skip += strlen("flags=");
+			char* flagptr = twrpflags;
+			flagptr += skip;
+			Process_TW_Flags(flagptr, Display_Error, 1); // Forcing the fstab to ver 1 because this data is coming from the /etc/twrp.flags which should be using the TWRP v1 flags format
+		}
+	}
+
+	if (Mount_Point == "/persist" && Can_Be_Mounted) {
+		bool mounted = Is_Mounted();
+		if (mounted || Mount(false)) {
+			TWFunc::Fixup_Time_On_Boot("/persist/time/");
+			if (!mounted)
+				UnMount(false);
+		}
+	}
+
+	return true;
+}
+
+void TWPartition::Partition_Post_Processing(bool Display_Error) {
+	if (Mount_Point == "/data")
+		Setup_Data_Partition(Display_Error);
+	else if (Mount_Point == "/cache")
+		Setup_Cache_Partition(Display_Error);
+}
+
+void TWPartition::ExcludeAll(const string& path) {
+	backup_exclusions.add_absolute_dir(path);
+	wipe_exclusions.add_absolute_dir(path);
+}
+
+void TWPartition::Setup_Data_Partition(bool Display_Error) {
+	if (Mount_Point != "/data")
+		return;
+
+	// Ensure /data is not mounted as tmpfs for qcom hardware decrypt
+	UnMount(false);
+
+#ifdef TW_INCLUDE_CRYPTO
+	Can_Be_Encrypted = true;
+	char crypto_blkdev[255];
+	property_get("ro.crypto.fs_crypto_blkdev", crypto_blkdev, "error");
+	if (strcmp(crypto_blkdev, "error") != 0) {
+		Set_FBE_Status();
+		Decrypted_Block_Device = crypto_blkdev;
+		LOGINFO("Data already decrypted, new block device: '%s'\n", crypto_blkdev);
+		if (datamedia)
+			Setup_Data_Media();
+		DataManager::SetValue(TW_IS_ENCRYPTED, 0);
+	} else if (!Mount(false)) {
+		if (Is_Present) {
+			if (Key_Directory.empty()) {
+				set_partition_data(Use_Original_Path ? Original_Path.c_str() : Actual_Block_Device.c_str(), Crypto_Key_Location.c_str());
+				//if (cryptfs_check_footer() == 0) {
+				//	Is_Encrypted = true;
+				//	Is_Decrypted = false;
+				//	Can_Be_Mounted = false;
+				//	Current_File_System = "emmc";
+				//	Setup_Image();
+				//	DataManager::SetValue(TW_CRYPTO_PWTYPE, cryptfs_get_password_type());
+				//	DataManager::SetValue("tw_crypto_pwtype_0", cryptfs_get_password_type());
+				//	DataManager::SetValue(TW_CRYPTO_PASSWORD, "");
+				//	DataManager::SetValue("tw_crypto_display", "");
+				//	if (datamedia)
+				//		Setup_Data_Media();
+				//}
+				// else {
+				//	gui_err("mount_data_footer=Could not mount /data and unable to find crypto footer.");
+				//}
+			} else {
+				Is_Encrypted = true;
+				Is_Decrypted = false;
+				if (datamedia)
+					Setup_Data_Media();
+			}
+		} else if (Key_Directory.empty()) {
+			LOGERR("Primary block device '%s' for mount point '%s' is not present!\n",
+			Primary_Block_Device.c_str(), Mount_Point.c_str());
+		}
+	} else {
+		Set_FBE_Status();
+		int is_device_fbe;
+		DataManager::GetValue(TW_IS_FBE, is_device_fbe);
+		char crypto_state[255];
+		property_get("ro.crypto.state", crypto_state, "error");
+		if (!Decrypt_FBE_DE() && strcmp(crypto_state, "error") != 0) {
+			if (is_device_fbe == 1)
+				LOGERR("Unable to decrypt FBE device\n");
+		} else {
+			DataManager::SetValue(TW_IS_ENCRYPTED, 0);
+			if (datamedia)
+				Setup_Data_Media();
+
+		}
+	}
+	if (datamedia && (!Is_Encrypted || (Is_Encrypted && Is_Decrypted))) {
+		Setup_Data_Media();
+		Recreate_Media_Folder();
+	}
+#else
+	if (datamedia) {
+		Setup_Data_Media();
+		Recreate_Media_Folder();
+	}
+#endif
+}
+
+void TWPartition::Set_FBE_Status() {
+	DataManager::SetValue(TW_IS_DECRYPTED, 1);
+	Is_Encrypted = true;
+	Is_Decrypted = true;
+	if (Key_Directory.empty()) {
+		Is_FBE = false;
+		DataManager::SetValue(TW_IS_FBE, 0);
+	} else {
+		LOGINFO("Setup_Data_Partition::Key_Directory::%s\n", Key_Directory.c_str());
+		Is_FBE = true;
+		DataManager::SetValue(TW_IS_FBE, 1);
+	}
+}
+
+bool TWPartition::Decrypt_FBE_DE() {
+	if (TWFunc::Path_Exists("/data/unencrypted/key/version")) {
+		DataManager::SetValue(TW_IS_FBE, 1);
+		PartitionManager.Set_Crypto_State();
+		PartitionManager.Set_Crypto_Type("file");
+		LOGINFO("File Based Encryption is present\n");
+#ifdef TW_INCLUDE_FBE
+	Is_FBE = true;
+	ExcludeAll(Mount_Point + "/convert_fbe");
+	ExcludeAll(Mount_Point + "/unencrypted");
+	ExcludeAll(Mount_Point + "/misc/vold/user_keys");
+	ExcludeAll(Mount_Point + "/misc/vold/volume_keys");
+	ExcludeAll(Mount_Point + "/system/gatekeeper.password.key");
+	ExcludeAll(Mount_Point + "/system/gatekeeper.pattern.key");
+	ExcludeAll(Mount_Point + "/system/locksettings.db");
+	ExcludeAll(Mount_Point + "/system/locksettings.db-wal");
+	ExcludeAll(Mount_Point + "/misc/gatekeeper");
+	ExcludeAll(Mount_Point + "/misc/keystore");
+	ExcludeAll(Mount_Point + "/drm/kek.dat");
+	ExcludeAll(Mount_Point + "/system_de/0/spblob");  // contains data needed to decrypt synthetic password
+	ExcludeAll(Mount_Point + "/system/users/0/gatekeeper.password.key");
+	ExcludeAll(Mount_Point + "/system/users/0/gatekeeper.pattern.key");
+	ExcludeAll(Mount_Point + "/cache");
+	ExcludeAll(Mount_Point + "/per_boot"); // removed each boot by init
+	ExcludeAll(Mount_Point + "/gsi"); // cow devices
+
+	int retry_count = 3;
+	while (!android::keystore::Decrypt_DE() && --retry_count)
+		usleep(2000);
+	if (retry_count > 0) {
+		PartitionManager.Set_Crypto_State();
+		Is_Encrypted = true;
+		Is_Decrypted = false;
+		DataManager::SetValue(TW_IS_ENCRYPTED, 1);
+		string filename;
+		int pwd_type = android::keystore::Get_Password_Type(0, filename);
+		if (pwd_type < 0) {
+			LOGERR("This TWRP does not have synthetic password decrypt support\n");
+			pwd_type = 0;  // default password
+		}
+		PartitionManager.Parse_Users();  // after load_all_de_keys() to parse_users
+		std::vector<users_struct>::iterator iter;
+		std::vector<users_struct>* userList = PartitionManager.Get_Users_List();
+		for (iter = userList->begin(); iter != userList->end(); iter++) {
+			if (atoi((*iter).userId.c_str()) != 0) {
+				ExcludeAll(Mount_Point + "/system_de/" + (*iter).userId + "/spblob");
+				ExcludeAll(Mount_Point + "/system/users/" + (*iter).userId + "/gatekeeper.password.key");
+				ExcludeAll(Mount_Point + "/system/users/" + (*iter).userId + "/gatekeeper.pattern.key");
+				ExcludeAll(Mount_Point + "/system/users/" + (*iter).userId + "/locksettings.db");
+				ExcludeAll(Mount_Point + "/system/users/" + (*iter).userId + "/locksettings.db-wal");
+			}
+		}
+		DataManager::SetValue(TW_CRYPTO_PWTYPE, pwd_type);
+		DataManager::SetValue("tw_crypto_pwtype_0", pwd_type);
+		DataManager::SetValue(TW_CRYPTO_PASSWORD, "");
+		DataManager::SetValue("tw_crypto_display", "");
+		return true;
+	}
+#else
+		LOGERR("FBE found but FBE support not present in TWRP\n");
+#endif
+	}
+	DataManager::SetValue(TW_IS_FBE, 0);
+	return false;
+}
+
+void TWPartition::Setup_Cache_Partition(bool Display_Error __unused) {
+	if (Mount_Point != "/cache")
+		return;
+
+	if (!Mount(true))
+		return;
+
+	if (!TWFunc::Path_Exists("/cache/recovery/.")) {
+		LOGINFO("Recreating /cache/recovery folder\n");
+		if (mkdir("/cache/recovery", S_IRWXU | S_IRWXG | S_IWGRP | S_IXGRP) != 0)
+			LOGERR("Could not create /cache/recovery\n");
+	}
+}
+
+void TWPartition::Process_FS_Flags(const char *str) {
+	char *options = strdup(str);
+	char *ptr, *savep;
+
+	Mount_Options = "";
+
+	// Avoid issues with potentially nested strtok by using strtok_r
+	for (ptr = strtok_r(options, ",", &savep); ptr; ptr = strtok_r(NULL, ",", &savep)) {
+		char *equals = strstr(ptr, "=");
+		size_t name_len;
+
+		if (!equals)
+			name_len = strlen(ptr);
+		else
+			name_len = equals - ptr;
+
+		// There are some flags that we want to ignore in TWRP
+		bool found_match = false;
+		for (const char** ignored_mount_item = ignored_mount_items; *ignored_mount_item; ignored_mount_item++) {
+			if (strncmp(ptr, *ignored_mount_item, name_len) == 0) {
+				found_match = true;
+				break;
+			}
+		}
+		if (found_match)
+			continue;
+
+		// mount_flags are never postfixed by '='
+		if (!equals) {
+			const struct flag_list* mount_flag = mount_flags;
+			for (; mount_flag->name; mount_flag++) {
+				if (strcmp(ptr, mount_flag->name) == 0) {
+					if (mount_flag->flag == MS_RDONLY)
+						Mount_Read_Only = true;
+					else
+						Mount_Flags |= (unsigned)mount_flag->flag;
+					found_match = true;
+					break;
+				}
+			}
+			if (found_match)
+				continue;
+		}
+
+		// If we aren't ignoring this flag and it's not a mount flag, then it must be a mount option
+		if (!Mount_Options.empty())
+			Mount_Options += ",";
+		Mount_Options += ptr;
+	}
+	free(options);
+}
+
+void TWPartition::Save_FS_Flags(const string& local_File_System, int local_Mount_Flags, const string& local_Mount_Options) {
+	partition_fs_flags_struct flags;
+	flags.File_System = local_File_System;
+	flags.Mount_Flags = local_Mount_Flags;
+	flags.Mount_Options = local_Mount_Options;
+	fs_flags.push_back(flags);
+}
+
+void TWPartition::Apply_TW_Flag(const unsigned flag, const char* str, const bool val) {
+	switch (flag) {
+		case TWFLAG_ANDSEC:
+			Has_Android_Secure = val;
+			break;
+		case TWFLAG_BACKUP:
+			Can_Be_Backed_Up = val;
+			break;
+		case TWFLAG_BACKUPNAME:
+			Backup_Display_Name = str;
+			break;
+		case TWFLAG_BLOCKSIZE:
+			Format_Block_Size = (unsigned long)(atol(str));
+			break;
+		case TWFLAG_CANBEWIPED:
+			Can_Be_Wiped = val;
+			break;
+		case TWFLAG_CANENCRYPTBACKUP:
+			Can_Encrypt_Backup = val;
+			break;
+		case TWFLAG_DEFAULTS:
+		case TWFLAG_WAIT:
+		case TWFLAG_VERIFY:
+		case TWFLAG_CHECK:
+		case TWFLAG_NOTRIM:
+		case TWFLAG_VOLDMANAGED:
+		case TWFLAG_RESIZE:
+			// Do nothing
+			break;
+		case TWFLAG_DISPLAY:
+			Display_Name = str;
+			break;
+		case TWFLAG_ENCRYPTABLE:
+		case TWFLAG_FORCEENCRYPT:
+			Crypto_Key_Location = str;
+			break;
+		case TWFLAG_FILEENCRYPTION:
+			// This flag isn't used by TWRP but is needed in 9.0 FBE decrypt
+			// fileencryption=ice:aes-256-heh
+			{
+				std::string FBE = str;
+				size_t colon_loc = FBE.find(":");
+				if (colon_loc == std::string::npos) {
+					property_set("fbe.contents", FBE.c_str());
+					property_set("fbe.filenames", "");
+					LOGINFO("FBE contents '%s', filenames ''\n", FBE.c_str());
+					break;
+				}
+				std::string FBE_contents, FBE_filenames;
+				FBE_contents = FBE.substr(0, colon_loc);
+				FBE_filenames = FBE.substr(colon_loc + 1);
+				property_set("fbe.contents", FBE_contents.c_str());
+				property_set("fbe.filenames", FBE_filenames.c_str());
+				LOGINFO("FBE contents '%s', filenames '%s'\n", FBE_contents.c_str(), FBE_filenames.c_str());
+			}
+			break;
+		case TWFLAG_METADATA_ENCRYPTION:
+			// This flag isn't used by TWRP but is needed for FBEv2 metadata decryption
+			// metadata_encryption=aes-256-xts:wrappedkey_v0
+			{
+				std::string META = str;
+				size_t colon_loc = META.find(":");
+				if (colon_loc == std::string::npos) {
+					property_set("metadata.contents", META.c_str());
+					property_set("metadata.filenames", "");
+					LOGINFO("Metadata contents '%s', filenames ''\n", META.c_str());
+					break;
+				}
+				std::string META_contents, META_filenames;
+				META_contents = META.substr(0, colon_loc);
+				META_filenames = META.substr(colon_loc + 1);
+				property_set("metadata.contents", META_contents.c_str());
+				property_set("metadata.filenames", META_filenames.c_str());
+				LOGINFO("Metadata contents '%s', filenames '%s'\n", META_contents.c_str(), META_filenames.c_str());
+			}
+			break;
+		case TWFLAG_WRAPPEDKEY:
+			// no more processing needed. leaving it here in case we want to do something in the future
+			break;
+		case TWFLAG_FLASHIMG:
+			Can_Flash_Img = val;
+			break;
+		case TWFLAG_FSFLAGS:
+			Process_FS_Flags(str);
+			break;
+		case TWFLAG_IGNOREBLKID:
+			Ignore_Blkid = val;
+			break;
+		case TWFLAG_LENGTH:
+			Length = atoi(str);
+			break;
+		case TWFLAG_MOUNTTODECRYPT:
+			Mount_To_Decrypt = val;
+			break;
+		case TWFLAG_QUOTA:
+			// Filesystem flag - TWRP does not need to process
+			break;
+		case TWFLAG_REMOVABLE:
+			Removable = val;
+			break;
+		case TWFLAG_SETTINGSSTORAGE:
+			Is_Settings_Storage = val;
+			if (Is_Settings_Storage)
+				Is_Storage = true;
+			break;
+		case TWFLAG_STORAGE:
+			Is_Storage = val;
+			break;
+		case TWFLAG_STORAGENAME:
+			Storage_Name = str;
+			break;
+		case TWFLAG_SUBPARTITIONOF:
+			Is_SubPartition = true;
+			SubPartition_Of = str;
+			break;
+		case TWFLAG_SYMLINK:
+			Symlink_Path = str;
+			break;
+		case TWFLAG_USERDATAENCRYPTBACKUP:
+			Use_Userdata_Encryption = val;
+			if (Use_Userdata_Encryption)
+				Can_Encrypt_Backup = true;
+			break;
+		case TWFLAG_USERMRF:
+			Use_Rm_Rf = val;
+			break;
+		case TWFLAG_WIPEDURINGFACTORYRESET:
+			Wipe_During_Factory_Reset = val;
+			if (Wipe_During_Factory_Reset) {
+				Can_Be_Wiped = true;
+				Wipe_Available_in_GUI = true;
+			}
+			break;
+		case TWFLAG_WIPEINGUI:
+		case TWFLAG_FORMATTABLE:
+			Wipe_Available_in_GUI = val;
+			if (Wipe_Available_in_GUI)
+				Can_Be_Wiped = true;
+			break;
+		case TWFLAG_SLOTSELECT:
+			SlotSelect = true;
+			break;
+		case TWFLAG_ALTDEVICE:
+			Alternate_Block_Device = str;
+			break;
+		case TWFLAG_ADOPTED_MOUNT_DELAY:
+			Adopted_Mount_Delay = atoi(str);
+			break;
+		case TWFLAG_KEYDIRECTORY:
+			Key_Directory = str;
+			LOGINFO("setting Key_Directory to: %s\n", Key_Directory.c_str());
+			break;
+		case TWFLAG_DM_USE_ORIGINAL_PATH:
+			Use_Original_Path = true;
+			break;
+		case TWFLAG_LOGICAL:
+			Is_Super = true;
+			break;
+		default:
+			// Should not get here
+			LOGINFO("Flag identified for processing, but later unmatched: %i\n", flag);
+			break;
+	}
+}
+
+void TWPartition::Process_TW_Flags(char *flags, bool Display_Error, int fstab_ver) {
+	char separator[2] = {'\n', 0};
+	char *ptr, *savep;
+	char source_separator = ';';
+
+	if (fstab_ver == 2)
+		source_separator = ',';
+
+	// Semicolons within double-quotes are not forbidden, so replace
+	// only the semicolons intended as separators with '\n' for strtok
+	for (unsigned i = 0, skip = 0; i < strlen(flags); i++) {
+		if (flags[i] == '\"')
+			skip = !skip;
+		if (!skip && flags[i] == source_separator)
+			flags[i] = separator[0];
+	}
+
+	// Avoid issues with potentially nested strtok by using strtok_r
+	ptr = strtok_r(flags, separator, &savep);
+	while (ptr) {
+		int ptr_len = strlen(ptr);
+		const struct flag_list* tw_flag = tw_flags;
+
+		for (; tw_flag->name; tw_flag++) {
+			int flag_len = strlen(tw_flag->name);
+
+			if (strncmp(ptr, tw_flag->name, flag_len) == 0) {
+				bool flag_val = false;
+
+				if (ptr_len > flag_len && (tw_flag->name)[flag_len-1] != '='
+						&& ptr[flag_len] != '=') {
+					// Handle flags with same starting string
+					// (e.g. backup and backupname)
+					continue;
+				} else if (ptr_len > flag_len && ptr[flag_len] == '=') {
+					// Handle flags with dual format: Part 1
+					// (e.g. backup and backup=y. backup=y handled here)
+					ptr += flag_len + 1;
+					TWFunc::Strip_Quotes(ptr);
+					// Skip flags with empty argument
+					// (e.g. backup=)
+					if (strlen(ptr) == 0) {
+						LOGINFO("Flag missing argument or should not include '=': %s=\n", tw_flag->name);
+						break;
+					}
+					flag_val = strchr("yY1", *ptr) != NULL;
+				} else if (ptr_len == flag_len
+						&& (tw_flag->name)[flag_len-1] == '=') {
+					// Skip flags missing argument after =
+					// (e.g. backupname=)
+					LOGINFO("Flag missing argument: %s\n", tw_flag->name);
+					break;
+				} else if (ptr_len > flag_len
+						&& (tw_flag->name)[flag_len-1] == '=') {
+					// Handle arguments to flags
+					// (e.g. backupname="My Stuff")
+					ptr += flag_len;
+					TWFunc::Strip_Quotes(ptr);
+					// Skip flags with empty argument
+					// (e.g. backupname="")
+					if (strlen(ptr) == 0) {
+						LOGINFO("Flag missing argument: %s\n", tw_flag->name);
+						break;
+					}
+				} else if (ptr_len == flag_len) {
+					// Handle flags with dual format: Part 2
+					// (e.g. backup and backup=y. backup handled here)
+					flag_val = true;
+				} else {
+					LOGINFO("Flag matched, but could not be processed: %s\n", ptr);
+					break;
+				}
+
+				Apply_TW_Flag(tw_flag->flag, ptr, flag_val);
+				break;
+			}
+		}
+		if (tw_flag->name == 0) {
+			if (Display_Error)
+				LOGERR("Unhandled flag: '%s'\n", ptr);
+			else
+				LOGINFO("Unhandled flag: '%s'\n", ptr);
+		}
+		ptr = strtok_r(NULL, separator, &savep);
+	}
+}
+
+bool TWPartition::Is_File_System(string File_System) {
+	if (File_System == "ext2" ||
+		File_System == "ext3" ||
+		File_System == "ext4" ||
+		File_System == "vfat" ||
+		File_System == "ntfs" ||
+		File_System == "yaffs2" ||
+		File_System == "exfat" ||
+		File_System == "f2fs" ||
+		File_System == "erofs" ||
+		File_System == "squashfs" ||
+		File_System == "auto")
+		return true;
+	else
+		return false;
+}
+
+bool TWPartition::Is_Image(string File_System) {
+	if (File_System == "emmc" || File_System == "mtd" || File_System == "bml")
+		return true;
+	else
+		return false;
+}
+
+bool TWPartition::Make_Dir(string Path, bool Display_Error) {
+	if (TWFunc::Get_D_Type_From_Stat(Path) != S_IFDIR)
+		unlink(Path.c_str());
+	if (!TWFunc::Path_Exists(Path)) {
+		if (mkdir(Path.c_str(), 0777) == -1) {
+			if (Display_Error)
+				gui_msg(Msg(msg::kError, "create_folder_strerr=Can not create '{1}' folder ({2}).")(Path)(strerror(errno)));
+			else
+				LOGINFO("Can not create '%s' folder.\n", Path.c_str());
+			return false;
+		} else {
+			LOGINFO("Created '%s' folder.\n", Path.c_str());
+			return true;
+		}
+	}
+	return true;
+}
+
+void TWPartition::Setup_File_System(bool Display_Error) {
+	Can_Be_Mounted = true;
+	Can_Be_Wiped = true;
+
+	// Make the mount point folder if it doesn't exist
+	Make_Dir(Mount_Point, Display_Error);
+	Backup_Method = BM_FILES;
+}
+
+void TWPartition::Setup_Image() {
+	Display_Name = Mount_Point.substr(1, Mount_Point.size() - 1);
+	Backup_Name = Display_Name;
+	if (Current_File_System == "emmc")
+		Backup_Method = BM_DD;
+	else if (Current_File_System == "mtd" || Current_File_System == "bml")
+		Backup_Method = BM_FLASH_UTILS;
+	else
+		LOGINFO("Unhandled file system '%s' on image '%s'\n", Current_File_System.c_str(), Display_Name.c_str());
+}
+
+void TWPartition::Setup_AndSec(void) {
+	Backup_Display_Name = "Android Secure";
+	Backup_Name = "and-sec";
+	Can_Be_Backed_Up = true;
+	Has_Android_Secure = true;
+	Symlink_Path = Mount_Point + "/.android_secure";
+	Symlink_Mount_Point = "/and-sec";
+	Backup_Path = Symlink_Mount_Point;
+	Make_Dir("/and-sec", true);
+	Recreate_AndSec_Folder();
+	Mount_Storage_Retry(true);
+}
+
+void TWPartition::Setup_Data_Media() {
+	LOGINFO("Setting up '%s' as data/media emulated storage.\n", Mount_Point.c_str());
+	if (Storage_Name.empty() || Storage_Name == "Data")
+		Storage_Name = "Internal Storage";
+	Has_Data_Media = true;
+	Is_Storage = true;
+	Storage_Path = Mount_Point + "/media";
+	Symlink_Path = Storage_Path;
+	if (Mount_Point == "/data") {
+		Is_Settings_Storage = true;
+		if (strcmp(EXPAND(TW_EXTERNAL_STORAGE_PATH), "/sdcard") == 0) {
+			Make_Dir("/emmc", false);
+			Symlink_Mount_Point = "/emmc";
+		} else {
+			Make_Dir("/sdcard", false);
+			Symlink_Mount_Point = "/sdcard";
+		}
+		Mount(false);
+		if (TWFunc::Path_Exists(Mount_Point + "/media/0")) {
+			Storage_Path = Mount_Point + "/media/0";
+			Symlink_Path = Storage_Path;
+			DataManager::SetValue(TW_INTERNAL_PATH, Mount_Point + "/media/0");
+			UnMount(true);
+		}
+		DataManager::SetValue("tw_has_internal", 1);
+		DataManager::SetValue("tw_has_data_media", 1);
+		backup_exclusions.add_absolute_dir("/data/data/com.google.android.music/files");
+		backup_exclusions.add_absolute_dir("/data/per_boot"); // DJ9,14Jan2020 - exclude this dir to prevent "error 255" on AOSP ROMs that create and lock it
+		backup_exclusions.add_absolute_dir("/data/vendor/dumpsys");
+		backup_exclusions.add_absolute_dir("/data/cache");
+        backup_exclusions.add_absolute_dir("/data/misc/apexdata/com.android.art"); // exclude this dir to prevent "error 255" on AOSP Android 12
+		backup_exclusions.add_absolute_dir("/data/extm"); //exclude this dir to prevent "error 255" on MIUI
+		wipe_exclusions.add_absolute_dir(Mount_Point + "/misc/vold"); // adopted storage keys
+		ExcludeAll(Mount_Point + "/system/storage.xml");
+
+		// board-customisable exclusions
+		#ifdef TW_BACKUP_EXCLUSIONS
+			std::vector<std::string> user_extra_exclusions = TWFunc::Split_String(TW_BACKUP_EXCLUSIONS, ",");
+			std::string s1;
+			for (const std::string& extra_x : user_extra_exclusions) {
+				s1 = android::base::Trim(extra_x);
+				if (!s1.empty()) {
+					backup_exclusions.add_absolute_dir(s1);
+					LOGINFO("Adding user-defined path '%s' to the backup exclusions\n", s1.c_str());
+				}
+			}
+		#endif
+	} else {
+		int i;
+		string path;
+		for (i = 2; i <= 9; i++) {
+			path = "/sdcard" + TWFunc::to_string(i);
+			if (!TWFunc::Path_Exists(path)) {
+				Make_Dir(path, false);
+				Symlink_Mount_Point = path;
+				LOGINFO("'%s' data/media emulated storage symlinked to %s.\n", Mount_Point.c_str(), Symlink_Mount_Point.c_str());
+				break;
+			}
+		}
+		if (Mount(true) && TWFunc::Path_Exists(Mount_Point + "/media/0")) {
+			Storage_Path = Mount_Point + "/media/0";
+			Symlink_Path = Storage_Path;
+			UnMount(true);
+		}
+	}
+	ExcludeAll(Mount_Point + "/media");
+}
+
+void TWPartition::Find_Real_Block_Device(string& Block, bool Display_Error) {
+	char device[PATH_MAX], realDevice[PATH_MAX];
+
+	Original_Path = Block;
+
+	strcpy(device, Block.c_str());
+	memset(realDevice, 0, sizeof(realDevice));
+	while (readlink(device, realDevice, sizeof(realDevice)) > 0)
+	{
+		strcpy(device, realDevice);
+		memset(realDevice, 0, sizeof(realDevice));
+	}
+
+	Block = device;
+	return;
+}
+
+bool TWPartition::Mount_Storage_Retry(bool Display_Error) {
+	// On some devices, storage doesn't want to mount right away, retry and sleep
+	if (!Mount(Display_Error)) {
+		int retry_count = 5;
+		while (retry_count > 0 && !Mount(false)) {
+			usleep(500000);
+			retry_count--;
+		}
+		return Mount(Display_Error);
+	}
+	return true;
+}
+
+bool TWPartition::Find_MTD_Block_Device(string MTD_Name) {
+	FILE *fp = NULL;
+	char line[255];
+
+	fp = fopen("/proc/mtd", "rt");
+	if (fp == NULL) {
+		LOGERR("Device does not support /proc/mtd\n");
+		return false;
+	}
+
+	while (fgets(line, sizeof(line), fp) != NULL)
+	{
+		char device[32], label[32];
+		unsigned long size = 0;
+		int deviceId;
+
+		sscanf(line, "%s %lx %*s %*c%s", device, &size, label);
+
+		// Skip header and blank lines
+		if ((strcmp(device, "dev:") == 0) || (strlen(line) < 8))
+			continue;
+
+		// Strip off the trailing " from the label
+		label[strlen(label)-1] = '\0';
+
+		if (strcmp(label, MTD_Name.c_str()) == 0) {
+			// We found our device
+			// Strip off the trailing : from the device
+			device[strlen(device)-1] = '\0';
+			if (sscanf(device,"mtd%d", &deviceId) == 1) {
+				sprintf(device, "/dev/block/mtdblock%d", deviceId);
+				Primary_Block_Device = device;
+				fclose(fp);
+				return true;
+			}
+		}
+	}
+	fclose(fp);
+
+	return false;
+}
+
+bool TWPartition::Get_Size_Via_statfs(bool Display_Error) {
+	struct statfs st;
+	string Local_Path = Mount_Point + "/.";
+
+	if (!Mount(Display_Error))
+		return false;
+
+	if (statfs(Local_Path.c_str(), &st) != 0) {
+		if (!Removable) {
+			if (Display_Error)
+				LOGERR("Unable to statfs '%s'\n", Local_Path.c_str());
+			else
+				LOGINFO("Unable to statfs '%s'\n", Local_Path.c_str());
+		}
+		return false;
+	}
+	Size = (st.f_blocks * st.f_bsize);
+	Used = ((st.f_blocks - st.f_bfree) * st.f_bsize);
+	Free = (st.f_bfree * st.f_bsize);
+	Backup_Size = Used;
+	return true;
+}
+
+bool TWPartition::Get_Size_Via_df(bool Display_Error) {
+	FILE* fp;
+	char command[255], line[512];
+	int include_block = 1;
+	unsigned int min_len;
+
+	if (!Mount(Display_Error))
+		return false;
+
+	min_len = Actual_Block_Device.size() + 2;
+	sprintf(command, "df %s > /tmp/dfoutput.txt", Mount_Point.c_str());
+	TWFunc::Exec_Cmd(command);
+	fp = fopen("/tmp/dfoutput.txt", "rt");
+	if (fp == NULL) {
+		LOGINFO("Unable to open /tmp/dfoutput.txt.\n");
+		return false;
+	}
+
+	while (fgets(line, sizeof(line), fp) != NULL)
+	{
+		unsigned long blocks, used, available;
+		char device[64];
+		char tmpString[64];
+
+		if (strncmp(line, "Filesystem", 10) == 0)
+			continue;
+		if (strlen(line) < min_len) {
+			include_block = 0;
+			continue;
+		}
+		if (include_block) {
+			sscanf(line, "%s %lu %lu %lu", device, &blocks, &used, &available);
+		} else {
+			// The device block string is so long that the df information is on the next line
+			int space_count = 0;
+			sprintf(tmpString, "/dev/block/%s", Actual_Block_Device.c_str());
+			while (tmpString[space_count] == 32)
+				space_count++;
+			sscanf(line + space_count, "%lu %lu %lu", &blocks, &used, &available);
+		}
+
+		// Adjust block size to byte size
+		Size = blocks * 1024ULL;
+		Used = used * 1024ULL;
+		Free = available * 1024ULL;
+		Backup_Size = Used;
+	}
+	fclose(fp);
+	return true;
+}
+
+unsigned long long TWPartition::IOCTL_Get_Block_Size() {
+	Find_Actual_Block_Device();
+
+	return TWFunc::IOCTL_Get_Block_Size(Actual_Block_Device.c_str());
+}
+
+bool TWPartition::Find_Partition_Size(void) {
+	FILE* fp;
+	char line[512];
+	string tmpdevice;
+
+	fp = fopen("/proc/dumchar_info", "rt");
+	if (fp != NULL) {
+		while (fgets(line, sizeof(line), fp) != NULL)
+		{
+			char label[32], device[32];
+			unsigned long size = 0;
+
+			sscanf(line, "%s %lx %*x %*u %s", label, &size, device);
+
+			// Skip header, annotation	and blank lines
+			if ((strncmp(device, "/dev/", 5) != 0) || (strlen(line) < 8))
+				continue;
+
+			tmpdevice = "/dev/";
+			tmpdevice += label;
+			if (tmpdevice == Primary_Block_Device || tmpdevice == Alternate_Block_Device) {
+				Size = size;
+				fclose(fp);
+				return true;
+			}
+		}
+	}
+
+	unsigned long long ioctl_size = IOCTL_Get_Block_Size();
+	if (ioctl_size) {
+		Size = ioctl_size;
+		return true;
+	}
+
+	// In this case, we'll first get the partitions we care about (with labels)
+	fp = fopen("/proc/partitions", "rt");
+	if (fp == NULL)
+		return false;
+
+	while (fgets(line, sizeof(line), fp) != NULL)
+	{
+		unsigned long major, minor, blocks;
+		char device[512];
+
+		if (strlen(line) < 7 || line[0] == 'm')	 continue;
+		sscanf(line + 1, "%lu %lu %lu %s", &major, &minor, &blocks, device);
+
+		tmpdevice = "/dev/block/";
+		tmpdevice += device;
+		if (tmpdevice == Primary_Block_Device || tmpdevice == Alternate_Block_Device) {
+			// Adjust block size to byte size
+			Size = blocks * 1024ULL;
+			fclose(fp);
+			return true;
+		}
+	}
+	fclose(fp);
+	return false;
+}
+
+bool TWPartition::Is_Mounted(void) {
+	if (!Can_Be_Mounted)
+		return false;
+
+	struct stat st1, st2;
+	string test_path;
+
+	// Check to see if the mount point directory exists
+	test_path = Mount_Point + "/.";
+	if (stat(test_path.c_str(), &st1) != 0)  return false;
+
+	// Check to see if the directory above the mount point exists
+	test_path = Mount_Point + "/../.";
+	if (stat(test_path.c_str(), &st2) != 0)  return false;
+
+	// Compare the device IDs -- if they match then we're (probably) using tmpfs instead of an actual device
+	int ret = (st1.st_dev != st2.st_dev) ? true : false;
+	return ret;
+}
+
+bool TWPartition::Is_File_System_Writable(void) {
+	if (!Is_File_System(Current_File_System) || !Is_Mounted())
+		return false;
+
+	string test_path = Mount_Point + "/.";
+	return (access(test_path.c_str(), W_OK) == 0);
+}
+
+bool TWPartition::Mount(bool Display_Error) {
+	int exfat_mounted = 0;
+	unsigned int flags = Mount_Flags;
+
+	if (Is_Mounted()) {
+		return true;
+	} else if (!Can_Be_Mounted) {
+		return false;
+	}
+
+	Find_Actual_Block_Device();
+
+	// Check the current file system before mounting
+	Check_FS_Type();
+	if (Current_File_System == "exfat" && TWFunc::Path_Exists("/system/bin/exfat-fuse")) {
+		string cmd = "/system/bin/exfat-fuse -o big_writes,max_read=131072,max_write=131072 " + Actual_Block_Device + " " + Mount_Point;
+		LOGINFO("cmd: %s\n", cmd.c_str());
+		string result;
+		if (TWFunc::Exec_Cmd(cmd, result, false) != 0) {
+			LOGINFO("exfat-fuse failed to mount with result '%s', trying vfat\n", result.c_str());
+			Current_File_System = "vfat";
+		} else {
+#ifdef TW_NO_EXFAT_FUSE
+			UnMount(false);
+			// We'll let the kernel handle it but using exfat-fuse to detect if the file system is actually exfat
+			// Some kernels let us mount vfat as exfat which doesn't work out too well
+#else
+			exfat_mounted = 1;
+#endif
+		}
+	}
+
+	if (Current_File_System == "ntfs" && !TWFunc::Path_Exists("/sys/module/tntfs") && (TWFunc::Path_Exists("/system/bin/ntfs-3g") || TWFunc::Path_Exists("/system/bin/mount.ntfs"))) {
+		string cmd;
+		string Ntfsmount_Binary = "";
+
+		if (TWFunc::Path_Exists("/system/bin/ntfs-3g"))
+			Ntfsmount_Binary = "ntfs-3g";
+		else if (TWFunc::Path_Exists("/system/bin/mount.ntfs"))
+			Ntfsmount_Binary = "mount.ntfs";
+
+		if (Mount_Read_Only)
+			cmd = "/system/bin/" + Ntfsmount_Binary + " -o ro " + Actual_Block_Device + " " + Mount_Point;
+		else
+			cmd = "/system/bin/" + Ntfsmount_Binary + " " + Actual_Block_Device + " " + Mount_Point;
+		LOGINFO("cmd: '%s'\n", cmd.c_str());
+
+		if (TWFunc::Exec_Cmd(cmd) == 0) {
+			return true;
+		} else {
+			LOGINFO("ntfs-3g failed to mount, trying regular mount method.\n");
+		}
+	} else {
+		if (Current_File_System == "ntfs" && TWFunc::Path_Exists("/sys/module/tntfs"))
+			Current_File_System = "tntfs";
+	}
+
+	if (Mount_Read_Only)
+		flags |= MS_RDONLY;
+
+	if (Fstab_File_System == "yaffs2") {
+		// mount an MTD partition as a YAFFS2 filesystem.
+		flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME;
+		if (Mount_Read_Only)
+			flags |= MS_RDONLY;
+		if (mount(Actual_Block_Device.c_str(), Mount_Point.c_str(), Fstab_File_System.c_str(), flags, NULL) < 0) {
+			if (mount(Actual_Block_Device.c_str(), Mount_Point.c_str(), Fstab_File_System.c_str(), flags | MS_RDONLY, NULL) < 0) {
+				if (Display_Error)
+					gui_msg(Msg(msg::kError, "fail_mount=Failed to mount '{1}' ({2})")(Mount_Point)(strerror(errno)));
+				else
+					LOGINFO("Failed to mount '%s' (MTD)\n", Mount_Point.c_str());
+				return false;
+			} else {
+				LOGINFO("Mounted '%s' (MTD) as RO\n", Mount_Point.c_str());
+				return true;
+			}
+		} else {
+			struct stat st;
+			string test_path = Mount_Point;
+			if (stat(test_path.c_str(), &st) < 0) {
+				if (Display_Error)
+					gui_msg(Msg(msg::kError, "fail_mount=Failed to mount '{1}' ({2})")(Mount_Point)(strerror(errno)));
+				else
+					LOGINFO("Failed to mount '%s' (MTD)\n", Mount_Point.c_str());
+				return false;
+			}
+			mode_t new_mode = st.st_mode | S_IXUSR | S_IXGRP | S_IXOTH;
+			if (new_mode != st.st_mode) {
+				LOGINFO("Fixing execute permissions for %s\n", Mount_Point.c_str());
+				if (chmod(Mount_Point.c_str(), new_mode) < 0) {
+					if (Display_Error)
+						LOGERR("Couldn't fix permissions for %s: %s\n", Mount_Point.c_str(), strerror(errno));
+					else
+						LOGINFO("Couldn't fix permissions for %s: %s\n", Mount_Point.c_str(), strerror(errno));
+					return false;
+				}
+			}
+			return true;
+		}
+	}
+
+	string mount_fs = Current_File_System;
+	if (Current_File_System == "exfat" && TWFunc::Path_Exists("/sys/module/texfat"))
+		mount_fs = "texfat";
+
+	if (!exfat_mounted &&
+		mount(Actual_Block_Device.c_str(), Mount_Point.c_str(), mount_fs.c_str(), flags, Mount_Options.c_str()) != 0 &&
+		mount(Actual_Block_Device.c_str(), Mount_Point.c_str(), mount_fs.c_str(), flags, NULL) != 0) {
+#ifdef TW_NO_EXFAT_FUSE
+		if (Current_File_System == "exfat") {
+			LOGINFO("Mounting exfat failed, trying vfat...\n");
+			if (mount(Actual_Block_Device.c_str(), Mount_Point.c_str(), "vfat", 0, NULL) != 0) {
+				if (Display_Error)
+					gui_msg(Msg(msg::kError, "fail_mount=Failed to mount '{1}' ({2})")(Mount_Point)(strerror(errno)));
+				else
+					LOGINFO("Unable to mount '%s'\n", Mount_Point.c_str());
+				LOGINFO("Actual block device: '%s', current file system: '%s', flags: 0x%8x, options: '%s'\n", Actual_Block_Device.c_str(), Current_File_System.c_str(), flags, Mount_Options.c_str());
+				return false;
+			}
+		} else {
+#endif
+			if (!Removable && Display_Error)
+				gui_msg(Msg(msg::kError, "fail_mount=Failed to mount '{1}' ({2})")(Mount_Point)(strerror(errno)));
+			else
+				LOGINFO("Unable to mount '%s'\n", Mount_Point.c_str());
+			LOGINFO("Actual block device: '%s', current file system: '%s'\n", Actual_Block_Device.c_str(), Current_File_System.c_str());
+			return false;
+#ifdef TW_NO_EXFAT_FUSE
+		}
+#endif
+	}
+
+	if (Removable)
+		Update_Size(Display_Error);
+
+	if (!Symlink_Mount_Point.empty()) {
+		if (!Bind_Mount(false))
+			return false;
+	}
+	return true;
+}
+
+bool TWPartition::Bind_Mount(bool Display_Error) {
+	if (TWFunc::Path_Exists(Symlink_Path)) {
+		if (mount(Symlink_Path.c_str(), Symlink_Mount_Point.c_str(), "", MS_BIND, NULL) < 0) {
+			return false;
+		}
+	}
+	return true;
+}
+
+bool TWPartition::UnMount(bool Display_Error) {
+	if (Is_Mounted()) {
+		int never_unmount_system;
+
+		DataManager::GetValue(TW_DONT_UNMOUNT_SYSTEM, never_unmount_system);
+		if (never_unmount_system == 1 && Mount_Point == PartitionManager.Get_Android_Root_Path())
+			return true; // Never unmount system if you're not supposed to unmount it
+
+		if (Is_Storage && MTP_Storage_ID > 0)
+			PartitionManager.Remove_MTP_Storage(MTP_Storage_ID);
+
+		if (!Symlink_Mount_Point.empty())
+			umount(Symlink_Mount_Point.c_str());
+
+		umount(Mount_Point.c_str());
+		if (Is_Mounted()) {
+			if (Display_Error)
+				gui_msg(Msg(msg::kError, "fail_unmount=Failed to unmount '{1}' ({2})")(Mount_Point)(strerror(errno)));
+			else
+				LOGINFO("Unable to unmount '%s'\n", Mount_Point.c_str());
+			return false;
+		} else {
+			return true;
+		}
+	} else {
+		return true;
+	}
+}
+
+bool TWPartition::ReMount(bool Display_Error) {
+	if (UnMount(Display_Error))
+		return Mount(Display_Error);
+	return false;
+}
+
+bool TWPartition::ReMount_RW(bool Display_Error) {
+	// No need to remount if already mounted rw
+	if (Is_File_System_Writable())
+		return true;
+
+	bool ro = Mount_Read_Only;
+	int flags = Mount_Flags;
+
+	Mount_Read_Only = false;
+	Mount_Flags &= ~MS_RDONLY;
+
+	bool ret = ReMount(Display_Error);
+
+	Mount_Read_Only = ro;
+	Mount_Flags = flags;
+
+	return ret;
+}
+
+bool TWPartition::Wipe(string New_File_System) {
+	bool wiped = false, update_crypt = false, recreate_media = false;
+	int check;
+
+	if (!Can_Be_Wiped) {
+		gui_msg(Msg(msg::kError, "cannot_wipe=Partition {1} cannot be wiped.")(Display_Name));
+		return false;
+	}
+
+	if (Mount_Point == "/cache")
+		Log_Offset = 0;
+
+	if (Mount_Point == PartitionManager.Get_Android_Root_Path()) {
+		if (tw_get_default_metadata(PartitionManager.Get_Android_Root_Path().c_str()) != 0) {
+			gui_msg(Msg(msg::kWarning, "restore_system_context=Unable to get default context for {1} -- Android may not boot.")(PartitionManager.Get_Android_Root_Path()));
+		}
+	}
+
+	if (Has_Data_Media && Current_File_System == New_File_System) {
+		wiped = Wipe_Data_Without_Wiping_Media();
+		if (Mount_Point == "/data" && TWFunc::get_log_dir() == DATA_LOGS_DIR) {
+			bool created = PartitionManager.Recreate_Logs_Dir();
+			if (!created)
+				LOGERR("Unable to create log directory for TWRP\n");
+		}
+		recreate_media = false;
+	} else {
+		DataManager::GetValue(TW_RM_RF_VAR, check);
+		if (check || Use_Rm_Rf)
+			wiped = Wipe_RMRF();
+		else if (New_File_System == "ext4")
+			wiped = Wipe_EXT4();
+		else if (New_File_System == "ext2" || New_File_System == "ext3")
+			wiped = Wipe_EXTFS(New_File_System);
+		else if (New_File_System == "exfat")
+			wiped = Wipe_EXFAT();
+		else if (New_File_System == "ntfs" || Current_File_System == "tntfs")
+			wiped = Wipe_NTFS();
+		else if (New_File_System == "yaffs2")
+			wiped = Wipe_MTD();
+		else if (New_File_System == "f2fs")
+			wiped = Wipe_F2FS();
+		else if (New_File_System == "vfat")
+			wiped = Wipe_FAT();
+		else {
+			LOGERR("Unable to wipe '%s' -- unknown file system '%s'\n", Mount_Point.c_str(), New_File_System.c_str());
+			return false;
+		}
+		update_crypt = wiped;
+		update_crypt = false;
+	}
+
+	if (wiped) {
+		if (Mount_Point == "/cache" && TWFunc::get_log_dir() != DATA_LOGS_DIR)
+			DataManager::Output_Version();
+
+		if (Mount_Point == PartitionManager.Get_Android_Root_Path()) {
+			tw_set_default_metadata(PartitionManager.Get_Android_Root_Path().c_str());
+		}
+		if (update_crypt) {
+			Setup_File_System(false);
+			if (Is_Encrypted && !Is_Decrypted) {
+				// just wiped an encrypted partition back to its unencrypted state
+				Is_Encrypted = false;
+				Is_Decrypted = false;
+				Decrypted_Block_Device = "";
+				if (Mount_Point == "/data") {
+					DataManager::SetValue(TW_IS_ENCRYPTED, 0);
+					DataManager::SetValue(TW_IS_DECRYPTED, 0);
+				}
+			}
+		}
+
+		if (Is_Storage && Mount(false) && !Is_FBE)
+			PartitionManager.Add_MTP_Storage(MTP_Storage_ID);
+	}
+
+	return wiped;
+}
+
+bool TWPartition::Wipe() {
+	if (Is_File_System(Current_File_System))
+		return Wipe(Current_File_System);
+	else
+		return Wipe(Fstab_File_System);
+}
+
+bool TWPartition::Wipe_AndSec(void) {
+	if (!Has_Android_Secure)
+		return false;
+
+	if (!Mount(true))
+		return false;
+
+	gui_msg(Msg("wiping=Wiping {1}")(Backup_Display_Name));
+	TWFunc::removeDir(Mount_Point + "/.android_secure/", true);
+	return true;
+}
+
+bool TWPartition::Wipe_Data_Cache(void) {
+	if (!Mount(true))
+		return false;
+	gui_msg(Msg("wiping=Wiping {1}")(Mount_Point + "/cache/"));
+	TWFunc::removeDir(Mount_Point + "/cache/", true);
+	return true;
+}
+
+bool TWPartition::Can_Repair() {
+	if (Mount_Read_Only)
+		return false;
+	if (Current_File_System == "vfat" && TWFunc::Path_Exists("/system/bin/fsck.fat"))
+		return true;
+	else if ((Current_File_System == "ext2" || Current_File_System == "ext3" || Current_File_System == "ext4") && TWFunc::Path_Exists("/system/bin/e2fsck"))
+		return true;
+	else if (Current_File_System == "exfat" && TWFunc::Path_Exists("/system/bin/fsck.exfat"))
+		return true;
+	else if (Current_File_System == "f2fs" && TWFunc::Path_Exists("/system/bin/fsck.f2fs"))
+		return true;
+	else if ((Current_File_System == "ntfs" || Current_File_System == "tntfs") && (TWFunc::Path_Exists("/system/bin/ntfsfix") || TWFunc::Path_Exists("/system/bin/fsck.ntfs")))
+		return true;
+	return false;
+}
+
+bool TWPartition::Repair() {
+	string command;
+
+	if (Current_File_System == "vfat") {
+		if (!TWFunc::Path_Exists("/system/bin/fsck.fat")) {
+			gui_msg(Msg(msg::kError, "repair_not_exist={1} does not exist! Cannot repair!")("fsck.fat"));
+			return false;
+		}
+		if (!UnMount(true))
+			return false;
+		gui_msg(Msg("repairing_using=Repairing {1} using {2}...")(Display_Name)("fsck.fat"));
+		Find_Actual_Block_Device();
+		command = "/system/bin/fsck.fat -y " + Actual_Block_Device;
+		LOGINFO("Repair command: %s\n", command.c_str());
+		if (TWFunc::Exec_Cmd(command) == 0) {
+			gui_msg("done=Done.");
+			return true;
+		} else {
+			gui_msg(Msg(msg::kError, "unable_repair=Unable to repair {1}.")(Display_Name));
+			return false;
+		}
+	}
+	if (Current_File_System == "ext2" || Current_File_System == "ext3" || Current_File_System == "ext4") {
+		if (!TWFunc::Path_Exists("/system/bin/e2fsck")) {
+			gui_msg(Msg(msg::kError, "repair_not_exist={1} does not exist! Cannot repair!")("e2fsck"));
+			return false;
+		}
+		if (!UnMount(true))
+			return false;
+		gui_msg(Msg("repairing_using=Repairing {1} using {2}...")(Display_Name)("e2fsck"));
+		Find_Actual_Block_Device();
+		command = "/system/bin/e2fsck -fp " + Actual_Block_Device;
+		LOGINFO("Repair command: %s\n", command.c_str());
+		if (TWFunc::Exec_Cmd(command) == 0) {
+			gui_msg("done=Done.");
+			return true;
+		} else {
+			gui_msg(Msg(msg::kError, "unable_repair=Unable to repair {1}.")(Display_Name));
+			return false;
+		}
+	}
+	if (Current_File_System == "exfat") {
+		if (!TWFunc::Path_Exists("/system/bin/fsck.exfat")) {
+			gui_msg(Msg(msg::kError, "repair_not_exist={1} does not exist! Cannot repair!")("fsck.exfat"));
+			return false;
+		}
+		if (!UnMount(true))
+			return false;
+		gui_msg(Msg("repairing_using=Repairing {1} using {2}...")(Display_Name)("fsck.exfat"));
+		Find_Actual_Block_Device();
+		command = "/system/bin/fsck.exfat " + Actual_Block_Device;
+		LOGINFO("Repair command: %s\n", command.c_str());
+		if (TWFunc::Exec_Cmd(command) == 0) {
+			gui_msg("done=Done.");
+			return true;
+		} else {
+			gui_msg(Msg(msg::kError, "unable_repair=Unable to repair {1}.")(Display_Name));
+			return false;
+		}
+	}
+	if (Current_File_System == "f2fs") {
+		if (!TWFunc::Path_Exists("/system/bin/fsck.f2fs")) {
+			gui_msg(Msg(msg::kError, "repair_not_exist={1} does not exist! Cannot repair!")("fsck.f2fs"));
+			return false;
+		}
+		if (!UnMount(true))
+			return false;
+		gui_msg(Msg("repairing_using=Repairing {1} using {2}...")(Display_Name)("fsck.f2fs"));
+		Find_Actual_Block_Device();
+		command = "/system/bin/fsck.f2fs " + Actual_Block_Device;
+		LOGINFO("Repair command: %s\n", command.c_str());
+		if (TWFunc::Exec_Cmd(command) == 0) {
+			gui_msg("done=Done.");
+			return true;
+		} else {
+			gui_msg(Msg(msg::kError, "unable_repair=Unable to repair {1}.")(Display_Name));
+			return false;
+		}
+	}
+	if (Current_File_System == "ntfs" || Current_File_System == "tntfs") {
+		string Ntfsfix_Binary;
+		if (TWFunc::Path_Exists("/system/bin/ntfsfix"))
+			Ntfsfix_Binary = "ntfsfix";
+		else if (TWFunc::Path_Exists("/system/bin/fsck.ntfs"))
+			Ntfsfix_Binary = "fsck.ntfs";
+		else {
+			gui_msg(Msg(msg::kError, "repair_not_exist={1} does not exist! Cannot repair!")("ntfsfix"));
+			return false;
+		}
+		if (!UnMount(true))
+			return false;
+		gui_msg(Msg("repairing_using=Repairing {1} using {2}...")(Display_Name)(Ntfsfix_Binary));
+		Find_Actual_Block_Device();
+		command = "/system/bin/" + Ntfsfix_Binary + " " + Actual_Block_Device;
+		LOGINFO("Repair command: %s\n", command.c_str());
+		if (TWFunc::Exec_Cmd(command) == 0) {
+			gui_msg("done=Done.");
+			return true;
+		} else {
+			gui_msg(Msg(msg::kError, "unable_repair=Unable to repair {1}.")(Display_Name));
+			return false;
+		}
+	}
+	return false;
+}
+
+bool TWPartition::Can_Resize() {
+	if (Mount_Read_Only)
+		return false;
+	if ((Current_File_System == "ext2" || Current_File_System == "ext3" || Current_File_System == "ext4") && TWFunc::Path_Exists("/system/bin/resize2fs"))
+		return true;
+	return false;
+}
+
+bool TWPartition::Resize() {
+	string command;
+
+	if (Current_File_System == "ext2" || Current_File_System == "ext3" || Current_File_System == "ext4") {
+		if (!Can_Repair()) {
+			LOGINFO("Cannot resize %s because %s cannot be repaired before resizing.\n", Display_Name.c_str(), Display_Name.c_str());
+			gui_msg(Msg(msg::kError, "cannot_resize=Cannot resize {1}.")(Display_Name));
+			return false;
+		}
+		if (!TWFunc::Path_Exists("/system/bin/resize2fs")) {
+			LOGINFO("resize2fs does not exist! Cannot resize!\n");
+			gui_msg(Msg(msg::kError, "cannot_resize=Cannot resize {1}.")(Display_Name));
+			return false;
+		}
+		// Repair will unmount so no need to do it twice
+		gui_msg(Msg("repair_resize=Repairing {1} before resizing.")( Display_Name));
+		if (!Repair())
+			return false;
+		gui_msg(Msg("resizing=Resizing {1} using {2}...")(Display_Name)("resize2fs"));
+		Find_Actual_Block_Device();
+		command = "/system/bin/resize2fs " + Actual_Block_Device;
+		if (Length != 0) {
+			unsigned long long Actual_Size = IOCTL_Get_Block_Size();
+			if (Actual_Size == 0)
+				return false;
+
+			unsigned long long Block_Count;
+			if (Length < 0) {
+				// Reduce overall size by this length
+				Block_Count = (Actual_Size / 1024LLU) - ((unsigned long long)(Length * -1) / 1024LLU);
+			} else {
+				// This is the size, not a size reduction
+				Block_Count = ((unsigned long long)(Length) / 1024LLU);
+			}
+			char temp[256];
+			sprintf(temp, "%llu", Block_Count);
+			command += " ";
+			command += temp;
+			command += "K";
+		}
+		LOGINFO("Resize command: %s\n", command.c_str());
+		if (TWFunc::Exec_Cmd(command) == 0) {
+			Update_Size(true);
+			gui_msg("done=Done.");
+			return true;
+		} else {
+			Update_Size(true);
+			gui_msg(Msg(msg::kError, "unable_resize=Unable to resize {1}.")(Display_Name));
+			return false;
+		}
+	}
+	return false;
+}
+
+bool TWPartition::Backup(PartitionSettings *part_settings, pid_t *tar_fork_pid) {
+	if (Backup_Method == BM_FILES)
+		return Backup_Tar(part_settings, tar_fork_pid);
+	else if (Backup_Method == BM_DD)
+		return Backup_Image(part_settings);
+	else if (Backup_Method == BM_FLASH_UTILS)
+		return Backup_Dump_Image(part_settings);
+	LOGERR("Unknown backup method for '%s'\n", Mount_Point.c_str());
+	return false;
+}
+
+bool TWPartition::Restore(PartitionSettings *part_settings) {
+	TWFunc::GUI_Operation_Text(TW_RESTORE_TEXT, Display_Name, gui_parse_text("{@restoring_hdr}"));
+	LOGINFO("Restore filename is: %s/%s\n", part_settings->Backup_Folder.c_str(), Backup_FileName.c_str());
+
+	string Restore_File_System = Get_Restore_File_System(part_settings);
+
+	if (Is_File_System(Restore_File_System))
+		return Restore_Tar(part_settings);
+	else if (Is_Image(Restore_File_System))
+		return Restore_Image(part_settings);
+
+	LOGERR("Unknown restore method for '%s'\n", Mount_Point.c_str());
+	return false;
+}
+
+string TWPartition::Get_Restore_File_System(PartitionSettings *part_settings) {
+	size_t first_period, second_period;
+	string Restore_File_System;
+
+	// Parse backup filename to extract the file system before wiping
+	first_period = Backup_FileName.find(".");
+	if (first_period == string::npos) {
+		LOGERR("Unable to find file system (first period).\n");
+		return string();
+	}
+	Restore_File_System = Backup_FileName.substr(first_period + 1, Backup_FileName.size() - first_period - 1);
+	second_period = Restore_File_System.find(".");
+	if (second_period == string::npos) {
+		LOGERR("Unable to find file system (second period).\n");
+		return string();
+	}
+	Restore_File_System.resize(second_period);
+	LOGINFO("Restore file system is: '%s'.\n", Restore_File_System.c_str());
+	return Restore_File_System;
+}
+
+string TWPartition::Backup_Method_By_Name() {
+	if (Backup_Method == BM_NONE)
+		return "none";
+	else if (Backup_Method == BM_FILES)
+		return "files";
+	else if (Backup_Method == BM_DD)
+		return "dd";
+	else if (Backup_Method == BM_FLASH_UTILS)
+		return "flash_utils";
+	else
+		return "undefined";
+	return "ERROR!";
+}
+
+bool TWPartition::Decrypt(string Password) {
+	LOGINFO("STUB TWPartition::Decrypt, password: '%s'\n", Password.c_str());
+	// Is this needed?
+	return 1;
+}
+
+bool TWPartition::Wipe_Encryption() {
+	bool Save_Data_Media = Has_Data_Media;
+	bool ret = false;
+	BasePartition* base_partition = make_partition();
+
+	if (!base_partition->PreWipeEncryption())
+		goto exit;
+
+	Find_Actual_Block_Device();
+	if (!Is_Present) {
+		LOGINFO("Block device not present, cannot format %s.\n", Display_Name.c_str());
+		gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+		return false;
+	}
+
+#ifdef TW_INCLUDE_CRYPTO
+	if (!UnMount(true))
+		return false;
+	if (Is_Decrypted && !Decrypted_Block_Device.empty()) {
+		if (delete_crypto_blk_dev((char*)("userdata")) != 0) {
+			LOGERR("Error deleting crypto block device, continuing anyway.\n");
+		}
+	}
+#endif
+	Has_Data_Media = false;
+	Decrypted_Block_Device = "";
+	Is_Decrypted = false;
+	Is_Encrypted = false;
+	if (Wipe(Fstab_File_System)) {
+		Has_Data_Media = Save_Data_Media;
+		DataManager::SetValue(TW_IS_ENCRYPTED, 0);
+#ifndef TW_OEM_BUILD
+		gui_msg("format_data_msg=You may need to reboot recovery to be able to use /data again.");
+#endif
+		if (Is_FBE) {
+			gui_msg(Msg(msg::kWarning, "data_media_fbe_msg=TWRP will not recreate /data/media on an FBE device. Please reboot into your rom to create /data/media."));
+		} else {
+			if (Has_Data_Media && !Symlink_Mount_Point.empty()) {
+				if (Mount(false))
+					PartitionManager.Add_MTP_Storage(MTP_Storage_ID);
+			}
+		}
+
+		ret = true;
+		if (!Key_Directory.empty())
+			ret = PartitionManager.Wipe_By_Path(Key_Directory);
+		if (ret)
+			ret = base_partition->PostWipeEncryption();
+		goto exit;
+	} else {
+		Has_Data_Media = Save_Data_Media;
+		gui_err("format_data_err=Unable to format to remove encryption.");
+		if (Has_Data_Media && Mount(false))
+			PartitionManager.Add_MTP_Storage(MTP_Storage_ID);
+		goto exit;
+	}
+exit:
+	delete base_partition;
+	return ret;
+}
+
+void TWPartition::Check_FS_Type() {
+	const char* type;
+	blkid_probe pr;
+
+	if (Fstab_File_System == "yaffs2" || Fstab_File_System == "mtd" || Fstab_File_System == "bml" || Ignore_Blkid)
+		return; // Running blkid on some mtd devices causes a massive crash or needs to be skipped
+
+	Find_Actual_Block_Device();
+	if (!Is_Present)
+		return;
+
+	pr = blkid_new_probe_from_filename(Actual_Block_Device.c_str());
+	if (blkid_do_fullprobe(pr)) {
+		blkid_free_probe(pr);
+		LOGINFO("Can't probe device %s\n", Actual_Block_Device.c_str());
+		return;
+	}
+
+	if (blkid_probe_lookup_value(pr, "TYPE", &type, NULL) < 0) {
+		blkid_free_probe(pr);
+		LOGINFO("can't find filesystem on device %s\n", Actual_Block_Device.c_str());
+		return;
+	}
+
+	Current_File_System = type;
+	blkid_free_probe(pr);
+	if (fs_flags.size() > 1) {
+		std::vector<partition_fs_flags_struct>::iterator iter;
+		std::vector<partition_fs_flags_struct>::iterator found = fs_flags.begin();
+
+		for (iter = fs_flags.begin(); iter != fs_flags.end(); iter++) {
+			if (iter->File_System == Current_File_System) {
+				found = iter;
+				break;
+			}
+		}
+		// If we don't find a match, we default the flags to the first set of flags that we received from the fstab
+		if (Mount_Flags != found->Mount_Flags || Mount_Options != found->Mount_Options) {
+			Mount_Flags = found->Mount_Flags;
+			Mount_Options = found->Mount_Options;
+			LOGINFO("Mount_Flags: %i, Mount_Options: %s\n", Mount_Flags, Mount_Options.c_str());
+		}
+	}
+}
+
+bool TWPartition::Wipe_EXTFS(string File_System) {
+	if (!UnMount(true))
+		return false;
+
+#if PLATFORM_SDK_VERSION < 28
+	if (!TWFunc::Path_Exists("/system/bin/mke2fs"))
+#else
+	if (!TWFunc::Path_Exists("/system/bin/mke2fs") || !TWFunc::Path_Exists("/system/bin/e2fsdroid"))
+#endif
+		return Wipe_RMRF();
+
+	int ret;
+	bool NeedPreserveFooter = true;
+
+	Find_Actual_Block_Device();
+	if (!Is_Present) {
+		LOGINFO("Block device not present, cannot wipe %s.\n", Display_Name.c_str());
+		gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+		return false;
+	}
+
+	/**
+	 * On decrypted devices, IOCTL_Get_Block_Size calculates size on device mapper,
+	 * so there's no need to preserve footer.
+	 */
+	if ((Is_Decrypted && !Decrypted_Block_Device.empty()) ||
+			Crypto_Key_Location != "footer") {
+		NeedPreserveFooter = false;
+	}
+
+	unsigned long long dev_sz = TWFunc::IOCTL_Get_Block_Size(Actual_Block_Device.c_str());
+	if (!dev_sz)
+		return false;
+
+	if (NeedPreserveFooter)
+		Length < 0 ? dev_sz += Length : dev_sz -= CRYPT_FOOTER_OFFSET;
+
+	char dout[16];
+	sprintf(dout, "%llu", dev_sz / 4096);
+
+	//string size_str =to_string(dev_sz / 4096);
+	string size_str = dout;
+	string Command;
+
+	gui_msg(Msg("formatting_using=Formatting {1} using {2}...")(Display_Name)("mke2fs"));
+
+	// Execute mke2fs to create empty ext4 filesystem
+	Command = "mke2fs -t " + File_System + " -b 4096 " + Actual_Block_Device + " " + size_str;
+	LOGINFO("mke2fs command: %s\n", Command.c_str());
+	ret = TWFunc::Exec_Cmd(Command);
+	if (ret) {
+		gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+		return false;
+	}
+
+	if (TWFunc::Path_Exists("/system/bin/e2fsdroid")) {
+		const string& File_Contexts_Entry = (Mount_Point == "/system_root" ? "/" : Mount_Point);
+		char *secontext = NULL;
+		if (!selinux_handle || selabel_lookup(selinux_handle, &secontext, File_Contexts_Entry.c_str(), S_IFDIR) < 0) {
+			LOGINFO("Cannot lookup security context for '%s'\n", Mount_Point.c_str());
+		} else {
+			// Execute e2fsdroid to initialize selinux context
+			if (Mount_Point == "/persist") {
+				Mount(true);
+				TWFunc::removeDir("/persist/lost+found", false);
+				UnMount(true);
+			}
+			Command = "e2fsdroid -e -S /file_contexts -a " + File_Contexts_Entry + " " + Actual_Block_Device;
+			LOGINFO("e2fsdroid command: %s\n", Command.c_str());
+			ret = TWFunc::Exec_Cmd(Command);
+			if (ret) {
+				gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+				return false;
+			}
+		}
+	} else {
+		LOGINFO("e2fsdroid not present\n");
+	}
+
+	if (NeedPreserveFooter)
+		Wipe_Crypto_Key();
+	Current_File_System = File_System;
+	Recreate_AndSec_Folder();
+	gui_msg("done=Done.");
+	return true;
+}
+
+bool TWPartition::Wipe_EXT4() {
+#ifdef USE_EXT4
+	int ret;
+	bool NeedPreserveFooter = true;
+
+	if (!UnMount(true))
+		return false;
+
+	Find_Actual_Block_Device();
+	if (!Is_Present) {
+		LOGINFO("Block device not present, cannot wipe %s.\n", Display_Name.c_str());
+		gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+		return false;
+	}
+
+	/**
+	 * On decrypted devices, IOCTL_Get_Block_Size calculates size on device mapper,
+	 * so there's no need to preserve footer.
+	 */
+	if ((Is_Decrypted && !Decrypted_Block_Device.empty()) ||
+			Crypto_Key_Location != "footer") {
+		NeedPreserveFooter = false;
+	}
+
+	unsigned long long dev_sz = TWFunc::IOCTL_Get_Block_Size(Actual_Block_Device.c_str());
+	if (!dev_sz)
+		return false;
+
+	if (NeedPreserveFooter)
+		Length < 0 ? dev_sz += Length : dev_sz -= CRYPT_FOOTER_OFFSET;
+
+	char *secontext = NULL;
+
+	gui_msg(Msg("formatting_using=Formatting {1} using {2}...")(Display_Name)("make_ext4fs"));
+
+	if (!selinux_handle || selabel_lookup(selinux_handle, &secontext, Mount_Point.c_str(), S_IFDIR) < 0) {
+		LOGINFO("Cannot lookup security context for '%s'\n", Mount_Point.c_str());
+		ret = make_ext4fs(Actual_Block_Device.c_str(), dev_sz, Mount_Point.c_str(), NULL);
+	} else {
+		ret = make_ext4fs(Actual_Block_Device.c_str(), dev_sz, Mount_Point.c_str(), selinux_handle);
+	}
+	if (ret != 0) {
+		gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+		return false;
+	} else {
+		if (NeedPreserveFooter)
+			Wipe_Crypto_Key();
+		string sedir = Mount_Point + "/lost+found";
+		PartitionManager.Mount_By_Path(sedir.c_str(), true);
+		rmdir(sedir.c_str());
+		mkdir(sedir.c_str(), S_IRWXU | S_IRWXG | S_IWGRP | S_IXGRP);
+		return true;
+	}
+#else
+	return Wipe_EXTFS("ext4");
+#endif
+}
+
+bool TWPartition::Wipe_FAT() {
+	string command;
+
+	if (!UnMount(true))
+		return false;
+
+	if (TWFunc::Path_Exists("/system/bin/mkfs.fat")) {
+		gui_msg(Msg("formatting_using=Formatting {1} using {2}...")(Display_Name)("mkfs.fat"));
+		Find_Actual_Block_Device();
+		command = "mkfs.fat " + Actual_Block_Device;
+		if (TWFunc::Exec_Cmd(command) == 0) {
+			Current_File_System = "vfat";
+			Recreate_AndSec_Folder();
+			gui_msg("done=Done.");
+			return true;
+		} else {
+			gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+			return false;
+		}
+		return true;
+	}
+	else
+		return Wipe_RMRF();
+
+	return false;
+}
+
+bool TWPartition::Wipe_EXFAT() {
+	string command;
+
+	if (!UnMount(true))
+		return false;
+	if (TWFunc::Path_Exists("/system/bin/mkexfatfs")) {
+		gui_msg(Msg("formatting_using=Formatting {1} using {2}...")(Display_Name)("mkexfatfs"));
+		Find_Actual_Block_Device();
+		command = "mkexfatfs " + Actual_Block_Device;
+		if (TWFunc::Exec_Cmd(command) == 0) {
+			Recreate_AndSec_Folder();
+			gui_msg("done=Done.");
+			return true;
+		} else {
+			gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+			return false;
+		}
+		return true;
+	}
+	return false;
+}
+
+bool TWPartition::Wipe_MTD() {
+	if (!UnMount(true))
+		return false;
+
+	gui_msg(Msg("formatting_using=Formatting {1} using {2}...")(Display_Name)("MTD"));
+
+	mtd_scan_partitions();
+	const MtdPartition* mtd = mtd_find_partition_by_name(MTD_Name.c_str());
+	if (mtd == NULL) {
+		LOGERR("No mtd partition named '%s'", MTD_Name.c_str());
+		return false;
+	}
+
+	MtdWriteContext* ctx = mtd_write_partition(mtd);
+	if (ctx == NULL) {
+		LOGERR("Can't write '%s', failed to format.", MTD_Name.c_str());
+		return false;
+	}
+	if (mtd_erase_blocks(ctx, -1) == -1) {
+		mtd_write_close(ctx);
+		LOGERR("Failed to format '%s'", MTD_Name.c_str());
+		return false;
+	}
+	if (mtd_write_close(ctx) != 0) {
+		LOGERR("Failed to close '%s'", MTD_Name.c_str());
+		return false;
+	}
+	Current_File_System = "yaffs2";
+	Recreate_AndSec_Folder();
+	gui_msg("done=Done.");
+	return true;
+}
+
+bool TWPartition::Wipe_RMRF() {
+	if (!Mount(true))
+		return false;
+	// This is the only wipe that leaves the partition mounted, so we
+	// must manually remove the partition from MTP if it is a storage
+	// partition.
+	if (Is_Storage)
+		PartitionManager.Remove_MTP_Storage(MTP_Storage_ID);
+
+	gui_msg(Msg("remove_all=Removing all files under '{1}'")(Mount_Point));
+	TWFunc::removeDir(Mount_Point, true);
+	Recreate_AndSec_Folder();
+	return true;
+}
+
+bool TWPartition::Wipe_F2FS() {
+	std::string f2fs_command;
+
+	if (!UnMount(true))
+		return false;
+
+	if (TWFunc::Path_Exists("/system/bin/mkfs.f2fs"))
+		f2fs_command = "/system/bin/mkfs.f2fs";
+	else if (TWFunc::Path_Exists("/system/bin/make_f2fs"))
+		f2fs_command = "/system/bin/make_f2fs -g android";
+	else {
+		LOGINFO("mkfs.f2fs binary not found, using rm -rf to wipe.\n");
+		return Wipe_RMRF();
+	}
+
+	bool NeedPreserveFooter = true;
+	bool needs_casefold = false;
+  	bool needs_projid = false;
+
+	Find_Actual_Block_Device();
+	if (!Is_Present) {
+		LOGINFO("Block device not present, cannot wipe %s.\n", Display_Name.c_str());
+		gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+		return false;
+	}
+
+	needs_casefold = android::base::GetBoolProperty("external_storage.casefold.enabled", false);
+	needs_projid = android::base::GetBoolProperty("external_storage.projid.enabled", false);
+	unsigned long long dev_sz = TWFunc::IOCTL_Get_Block_Size(Actual_Block_Device.c_str());
+	if (!dev_sz)
+		return false;
+
+	if (NeedPreserveFooter)
+		Length < 0 ? dev_sz += Length : dev_sz -= CRYPT_FOOTER_OFFSET;
+	char dev_sz_str[48];
+	sprintf(dev_sz_str, "%llu", (dev_sz / 4096));
+	if(needs_projid)
+		f2fs_command += " -O project_quota,extra_attr";
+
+	if(needs_casefold)
+		f2fs_command += " -O casefold -C utf8";
+
+	f2fs_command += " " + Actual_Block_Device + " " + dev_sz_str;
+
+	if (TWFunc::Path_Exists("/system/bin/sload_f2fs")) {
+		f2fs_command += " && sload_f2fs -t /data " + Actual_Block_Device;
+	}
+
+	/**
+	 * On decrypted devices, IOCTL_Get_Block_Size calculates size on device mapper,
+	 * so there's no need to preserve footer.
+	 */
+	if ((Is_Decrypted && !Decrypted_Block_Device.empty()) ||
+			Crypto_Key_Location != "footer") {
+		NeedPreserveFooter = false;
+	}
+	LOGINFO("mkfs.f2fs command: %s\n", f2fs_command.c_str());
+	if (TWFunc::Exec_Cmd(f2fs_command) == 0) {
+		if (NeedPreserveFooter)
+			Wipe_Crypto_Key();
+		Recreate_AndSec_Folder();
+		gui_msg("done=Done.");
+		return true;
+	} else {
+		gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+		return false;
+	}
+	return true;
+}
+
+bool TWPartition::Wipe_NTFS() {
+	string command;
+	string Ntfsmake_Binary;
+
+	if (!UnMount(true))
+		return false;
+
+	if (TWFunc::Path_Exists("/system/bin/mkntfs"))
+		Ntfsmake_Binary = "mkntfs";
+	else if (TWFunc::Path_Exists("/system/bin/mkfs.ntfs"))
+		Ntfsmake_Binary = "mkfs.ntfs";
+	else
+		return false;
+
+	gui_msg(Msg("formatting_using=Formatting {1} using {2}...")(Display_Name)(Ntfsmake_Binary));
+	Find_Actual_Block_Device();
+	command = "/system/bin/" + Ntfsmake_Binary + " " + Actual_Block_Device;
+	if (TWFunc::Exec_Cmd(command) == 0) {
+		Recreate_AndSec_Folder();
+		gui_msg("done=Done.");
+		return true;
+	} else {
+		gui_msg(Msg(msg::kError, "unable_to_wipe=Unable to wipe {1}.")(Display_Name));
+		return false;
+	}
+	return false;
+}
+
+bool TWPartition::Wipe_Data_Without_Wiping_Media() {
+#ifdef TW_OEM_BUILD
+	// In an OEM Build we want to do a full format
+	return Wipe_Encryption();
+#else
+	bool ret = false;
+
+	if (!Mount(true))
+		return false;
+
+	gui_msg("wiping_data=Wiping data without wiping /data/media ...");
+	ret = Wipe_Data_Without_Wiping_Media_Func(Mount_Point + "/");
+	if (ret)
+		gui_msg("done=Done.");
+	return ret;
+#endif // ifdef TW_OEM_BUILD
+}
+
+bool TWPartition::Wipe_Data_Without_Wiping_Media_Func(const string& parent __unused) {
+	string dir;
+
+	DIR* d;
+	d = opendir(parent.c_str());
+	if (d != NULL) {
+		struct dirent* de;
+		while ((de = readdir(d)) != NULL) {
+			if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)	 continue;
+
+			dir = parent;
+			dir.append(de->d_name);
+			if (wipe_exclusions.check_skip_dirs(dir)) {
+				LOGINFO("skipped '%s'\n", dir.c_str());
+				continue;
+			}
+			if (de->d_type == DT_DIR) {
+				dir.append("/");
+				if (!Wipe_Data_Without_Wiping_Media_Func(dir)) {
+					closedir(d);
+					return false;
+				}
+				rmdir(dir.c_str());
+			} else if (de->d_type == DT_REG || de->d_type == DT_LNK || de->d_type == DT_FIFO || de->d_type == DT_SOCK) {
+				if (unlink(dir.c_str()) != 0)
+					LOGINFO("Unable to unlink '%s': %s\n", dir.c_str(), strerror(errno));
+			}
+		}
+		closedir(d);
+
+		return true;
+	}
+	gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(Mount_Point)(strerror(errno)));
+	return false;
+}
+
+void TWPartition::Wipe_Crypto_Key() {
+	Find_Actual_Block_Device();
+	if (Crypto_Key_Location.empty())
+		return;
+	else if (Crypto_Key_Location == "footer") {
+		int fd = open(Actual_Block_Device.c_str(), O_RDWR);
+		if (fd < 0) {
+			gui_print_color("warning", "Unable to open '%s' to wipe crypto key\n", Actual_Block_Device.c_str());
+			return;
+		}
+
+		unsigned int block_count;
+		if ((ioctl(fd, BLKGETSIZE, &block_count)) == -1) {
+			gui_print_color("warning", "Unable to get block size for wiping crypto footer.\n");
+		} else {
+			int newlen = Length < 0 ? -Length : CRYPT_FOOTER_OFFSET;
+			off64_t offset = ((off64_t)block_count * 512) - newlen;
+			if (lseek64(fd, offset, SEEK_SET) == -1) {
+				gui_print_color("warning", "Unable to lseek64 for wiping crypto footer.\n");
+			} else {
+				void* buffer = malloc(newlen);
+				if (!buffer) {
+					gui_print_color("warning", "Failed to malloc for wiping crypto footer.\n");
+				} else {
+					memset(buffer, 0, newlen);
+					int ret = write(fd, buffer, newlen);
+					if (ret != newlen) {
+						gui_print_color("warning", "Failed to wipe crypto footer.\n");
+					} else {
+						LOGINFO("Successfully wiped crypto footer.\n");
+					}
+					free(buffer);
+				}
+			}
+		}
+		close(fd);
+	} else {
+		if (TWFunc::IOCTL_Get_Block_Size(Crypto_Key_Location.c_str()) >= 16384LLU) {
+			string Command = "dd of='" + Crypto_Key_Location + "' if=/dev/zero bs=16384 count=1";
+			TWFunc::Exec_Cmd(Command);
+		} else {
+			LOGINFO("Crypto key location reports size < 16K so not wiping crypto footer.\n");
+		}
+	}
+}
+
+bool TWPartition::Backup_Tar(PartitionSettings *part_settings, pid_t *tar_fork_pid) {
+	string Full_FileName;
+	twrpTar tar;
+
+	if (!Mount(true))
+		return false;
+
+	TWFunc::GUI_Operation_Text(TW_BACKUP_TEXT, Backup_Display_Name, gui_parse_text("{@backing}"));
+	gui_msg(Msg("backing_up=Backing up {1}...")(Backup_Display_Name));
+
+	DataManager::GetValue(TW_USE_COMPRESSION_VAR, tar.use_compression);
+
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+	if (Can_Encrypt_Backup) {
+		DataManager::GetValue("tw_encrypt_backup", tar.use_encryption);
+		if (tar.use_encryption) {
+			if (Use_Userdata_Encryption)
+				tar.userdata_encryption = tar.use_encryption;
+			string Password;
+			DataManager::GetValue("tw_backup_password", Password);
+			tar.setpassword(Password);
+		} else {
+			tar.use_encryption = 0;
+		}
+	}
+#endif
+
+	Backup_FileName = Backup_Name + "." + Current_File_System + ".win";
+	Full_FileName = part_settings->Backup_Folder + "/" + Backup_FileName;
+	if (Has_Data_Media)
+		gui_msg(Msg(msg::kWarning, "backup_storage_warning=Backups of {1} do not include any files in internal storage such as pictures or downloads.")(Display_Name));
+	if (Mount_Point == "/data" && DataManager::GetIntValue(TW_IS_FBE)) {
+		std::vector<users_struct>::iterator iter;
+		std::vector<users_struct>* userList = PartitionManager.Get_Users_List();
+		for (iter = userList->begin(); iter != userList->end(); iter++) {
+			if (!(*iter).isDecrypted && (*iter).userId != "0") {
+				gui_msg(Msg(msg::kWarning,
+				"backup_storage_undecrypt_warning=Backup will not include some files from user {1} "
+				"because the user is not decrypted.")((*iter).userId));
+				backup_exclusions.add_absolute_dir("/data/system_ce/" + (*iter).userId);
+				backup_exclusions.add_absolute_dir("/data/misc_ce/" + (*iter).userId);
+				backup_exclusions.add_absolute_dir("/data/vendor_ce/" + (*iter).userId);
+				backup_exclusions.add_absolute_dir("/data/media/" + (*iter).userId);
+				backup_exclusions.add_absolute_dir("/data/user/" + (*iter).userId);
+			}
+		}
+	}
+	tar.part_settings = part_settings;
+	tar.backup_exclusions = &backup_exclusions;
+	tar.setdir(Backup_Path);
+	tar.setfn(Full_FileName);
+	tar.setsize(Backup_Size);
+	tar.partition_name = Backup_Name;
+	tar.backup_folder = part_settings->Backup_Folder;
+	if (tar.createTarFork(tar_fork_pid) != 0)
+		return false;
+	return true;
+}
+
+bool TWPartition::Backup_Image(PartitionSettings *part_settings) {
+	string Full_FileName, adb_file_name;
+
+	TWFunc::GUI_Operation_Text(TW_BACKUP_TEXT, Display_Name, gui_parse_text("{@backing}"));
+	gui_msg(Msg("backing_up=Backing up {1}...")(Backup_Display_Name));
+
+	Backup_FileName = Backup_Name + "." + Current_File_System + ".win";
+
+	if (part_settings->adbbackup) {
+		Full_FileName = TW_ADB_BACKUP;
+		adb_file_name  = part_settings->Backup_Folder + "/" + Backup_FileName;
+	}
+	else
+		Full_FileName = part_settings->Backup_Folder + "/" + Backup_FileName;
+
+	part_settings->total_restore_size = Backup_Size;
+
+	if (part_settings->adbbackup) {
+		if (!twadbbu::Write_TWIMG(adb_file_name, Backup_Size))
+			return false;
+	}
+
+	if (!Raw_Read_Write(part_settings))
+		return false;
+
+	if (part_settings->adbbackup) {
+		if (!twadbbu::Write_TWEOF())
+			return false;
+	}
+	return true;
+}
+
+bool TWPartition::Raw_Read_Write(PartitionSettings *part_settings) {
+	unsigned long long RW_Block_Size, Remain = Backup_Size;
+	int src_fd = -1, dest_fd = -1;
+	ssize_t bs;
+	bool ret = false;
+	void* buffer = NULL;
+	unsigned long long backedup_size = 0;
+	string srcfn, destfn;
+
+	if (part_settings->PM_Method == PM_BACKUP) {
+		srcfn = Actual_Block_Device;
+		if (part_settings->adbbackup)
+			destfn = TW_ADB_BACKUP;
+		else {
+			destfn = part_settings->Backup_Folder + "/" + Backup_FileName;
+		}
+	}
+	else {
+		destfn = Actual_Block_Device;
+		if (part_settings->adbbackup) {
+			srcfn = TW_ADB_RESTORE;
+		} else {
+			srcfn = part_settings->Backup_Folder + "/" + Backup_FileName;
+			Remain = TWFunc::Get_File_Size(srcfn);
+		}
+	}
+
+	src_fd = open(srcfn.c_str(), O_RDONLY | O_LARGEFILE);
+	if (src_fd < 0) {
+		gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(srcfn.c_str())(strerror(errno)));
+		return false;
+	}
+
+	dest_fd = open(destfn.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRUSR | S_IWUSR);
+	if (dest_fd < 0) {
+		gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(destfn.c_str())(strerror(errno)));
+		goto exit;
+	}
+
+	LOGINFO("Reading '%s', writing '%s'\n", srcfn.c_str(), destfn.c_str());
+
+	if (part_settings->adbbackup) {
+		RW_Block_Size = MAX_ADB_READ;
+		bs = MAX_ADB_READ;
+	}
+	else {
+		RW_Block_Size = 1048576LLU; // 1MB
+		bs = (ssize_t)(RW_Block_Size);
+	}
+
+	buffer = malloc((size_t)bs);
+	if (!buffer) {
+		LOGINFO("Raw_Read_Write failed to malloc\n");
+		goto exit;
+	}
+
+	if (part_settings->progress)
+		part_settings->progress->SetPartitionSize(part_settings->total_restore_size);
+
+	while (Remain > 0) {
+		if (Remain < RW_Block_Size)
+			bs = (ssize_t)(Remain);
+		if (read(src_fd, buffer, bs) != bs) {
+			LOGINFO("Error reading source fd (%s)\n", strerror(errno));
+			goto exit;
+		}
+		if (write(dest_fd, buffer, bs) != bs) {
+			LOGINFO("Error writing destination fd (%s)\n", strerror(errno));
+			goto exit;
+		}
+		backedup_size += (unsigned long long)(bs);
+		Remain -= (unsigned long long)(bs);
+		if (part_settings->progress)
+			part_settings->progress->UpdateSize(backedup_size);
+		if (PartitionManager.Check_Backup_Cancel() != 0)
+			goto exit;
+	}
+	if (part_settings->progress)
+		part_settings->progress->UpdateDisplayDetails(true);
+	fsync(dest_fd);
+
+	if (!part_settings->adbbackup && part_settings->PM_Method == PM_BACKUP) {
+		tw_set_default_metadata(destfn.c_str());
+		LOGINFO("Restored default metadata for %s\n", destfn.c_str());
+	}
+
+	ret = true;
+exit:
+	if (src_fd >= 0)
+		close(src_fd);
+	if (dest_fd >= 0)
+		close(dest_fd);
+	if (buffer)
+		free(buffer);
+	return ret;
+}
+
+bool TWPartition::Backup_Dump_Image(PartitionSettings *part_settings) {
+	string Full_FileName, Command;
+
+	TWFunc::GUI_Operation_Text(TW_BACKUP_TEXT, Display_Name, gui_parse_text("{@backing}"));
+	gui_msg(Msg("backing_up=Backing up {1}...")(Backup_Display_Name));
+
+	if (part_settings->progress)
+		part_settings->progress->SetPartitionSize(Backup_Size);
+
+	Backup_FileName = Backup_Name + "." + Current_File_System + ".win";
+	Full_FileName = part_settings->Backup_Folder + "/" + Backup_FileName;
+
+	Command = "dump_image " + MTD_Name + " '" + Full_FileName + "'";
+
+	LOGINFO("Backup command: '%s'\n", Command.c_str());
+	TWFunc::Exec_Cmd(Command);
+	tw_set_default_metadata(Full_FileName.c_str());
+	if (TWFunc::Get_File_Size(Full_FileName) == 0) {
+		// Actual size may not match backup size due to bad blocks on MTD devices so just check for 0 bytes
+		gui_msg(Msg(msg::kError, "backup_size=Backup file size for '{1}' is 0 bytes.")(Full_FileName));
+		return false;
+	}
+	if (part_settings->progress)
+		part_settings->progress->UpdateSize(Backup_Size);
+
+	return true;
+}
+
+unsigned long long TWPartition::Get_Restore_Size(PartitionSettings *part_settings) {
+	if (!part_settings->adbbackup) {
+		InfoManager restore_info(part_settings->Backup_Folder + "/" + Backup_Name + ".info");
+		if (restore_info.LoadValues() == 0) {
+			if (restore_info.GetValue("backup_size", Restore_Size) == 0) {
+				LOGINFO("Read info file, restore size is %llu\n", Restore_Size);
+				return Restore_Size;
+			}
+		}
+	}
+
+	string Full_FileName = part_settings->Backup_Folder + "/" + Backup_FileName;
+	string Restore_File_System = Get_Restore_File_System(part_settings);
+
+	if (Is_Image(Restore_File_System)) {
+		Restore_Size = TWFunc::Get_File_Size(Full_FileName);
+		return Restore_Size;
+	}
+
+	twrpTar tar;
+	tar.setdir(Backup_Path);
+	tar.setfn(Full_FileName);
+	tar.backup_name = Full_FileName;
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+	string Password;
+	DataManager::GetValue("tw_restore_password", Password);
+	if (!Password.empty())
+		tar.setpassword(Password);
+#endif
+	tar.partition_name = Backup_Name;
+	tar.backup_folder = part_settings->Backup_Folder;
+	tar.part_settings = part_settings;
+	Restore_Size = tar.get_size();
+	return Restore_Size;
+}
+
+bool TWPartition::Restore_Tar(PartitionSettings *part_settings) {
+	string Full_FileName;
+	bool ret = false;
+	string Restore_File_System = Get_Restore_File_System(part_settings);
+
+	if (Has_Android_Secure) {
+		if (!Wipe_AndSec())
+			return false;
+	} else {
+		gui_msg(Msg("wiping=Wiping {1}")(Backup_Display_Name));
+		if (Has_Data_Media && Mount_Point == "/data" && Restore_File_System != Current_File_System) {
+			gui_msg(Msg(msg::kWarning, "datamedia_fs_restore=WARNING: This /data backup was made with {1} file system! The backup may not boot unless you change back to {1}.")(Restore_File_System));
+			if (!Wipe_Data_Without_Wiping_Media())
+				return false;
+		} else {
+			if (!Wipe(Restore_File_System))
+				return false;
+		}
+	}
+	TWFunc::GUI_Operation_Text(TW_RESTORE_TEXT, Backup_Display_Name, gui_parse_text("{@restoring_hdr}"));
+	gui_msg(Msg("restoring=Restoring {1}...")(Backup_Display_Name));
+
+	// Remount as read/write as needed so we can restore the backup
+	if (!ReMount_RW(true))
+		return false;
+
+	Full_FileName = part_settings->Backup_Folder + "/" + Backup_FileName;
+	twrpTar tar;
+	tar.part_settings = part_settings;
+	tar.setdir(Backup_Path);
+	tar.setfn(Full_FileName);
+	tar.backup_name = Backup_Name;
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+	string Password;
+	DataManager::GetValue("tw_restore_password", Password);
+	if (!Password.empty())
+		tar.setpassword(Password);
+#endif
+	part_settings->progress->SetPartitionSize(Get_Restore_Size(part_settings));
+	if (tar.extractTarFork() != 0)
+		ret = false;
+	else
+		ret = true;
+#ifdef HAVE_CAPABILITIES
+	// Restore capabilities to the run-as binary
+	if (Mount_Point == PartitionManager.Get_Android_Root_Path() && Mount(true) && TWFunc::Path_Exists("/system/bin/run-as")) {
+		struct vfs_cap_data cap_data;
+		uint64_t capabilities = (1 << CAP_SETUID) | (1 << CAP_SETGID);
+
+		memset(&cap_data, 0, sizeof(cap_data));
+		cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE;
+		cap_data.data[0].permitted = (uint32_t) (capabilities & 0xffffffff);
+		cap_data.data[0].inheritable = 0;
+		cap_data.data[1].permitted = (uint32_t) (capabilities >> 32);
+		cap_data.data[1].inheritable = 0;
+		if (setxattr("/system/bin/run-as", XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) {
+			LOGINFO("Failed to reset capabilities of /system/bin/run-as binary.\n");
+		} else {
+			LOGINFO("Reset capabilities of /system/bin/run-as binary successful.\n");
+		}
+	}
+#endif
+	if (Mount_Read_Only || Mount_Flags & MS_RDONLY)
+		// Remount as read only when restoration is complete
+		ReMount(true);
+
+	return ret;
+}
+
+bool TWPartition::Restore_Image(PartitionSettings *part_settings) {
+	string Full_FileName;
+	string Restore_File_System = Get_Restore_File_System(part_settings);
+
+	TWFunc::GUI_Operation_Text(TW_RESTORE_TEXT, Backup_Display_Name, gui_parse_text("{@restoring_hdr}"));
+	gui_msg(Msg("restoring=Restoring {1}...")(Backup_Display_Name));
+
+	if (part_settings->adbbackup)
+		Full_FileName = TW_ADB_RESTORE;
+	else
+		Full_FileName = part_settings->Backup_Folder + "/" + Backup_FileName;
+
+	if (Restore_File_System == "emmc") {
+		if (!part_settings->adbbackup)
+			part_settings->total_restore_size = (uint64_t)(TWFunc::Get_File_Size(Full_FileName));
+		if (!Raw_Read_Write(part_settings))
+			return false;
+	} else if (Restore_File_System == "mtd" || Restore_File_System == "bml") {
+		if (!Flash_Image_FI(Full_FileName, part_settings->progress))
+			return false;
+	}
+
+	if (part_settings->adbbackup) {
+		if (!twadbbu::Write_TWEOF())
+			return false;
+	}
+	return true;
+}
+
+bool TWPartition::Update_Size(bool Display_Error) {
+	bool ret = false, Was_Already_Mounted = false;
+
+	Find_Actual_Block_Device();
+
+	if (Actual_Block_Device.empty())
+		return false;
+
+	if (!Can_Be_Mounted && !Is_Encrypted) {
+		if (TWFunc::Path_Exists(Actual_Block_Device) && Find_Partition_Size()) {
+			Used = Size;
+			Backup_Size = Size;
+			return true;
+		}
+		return false;
+	}
+
+	Was_Already_Mounted = Is_Mounted();
+
+	if (Removable || Is_Encrypted) {
+		if (!Mount(false))
+			return true;
+	} else if (!Mount(Display_Error))
+		return false;
+
+	ret = Get_Size_Via_statfs(Display_Error);
+	if (!ret || Size == 0) {
+		if (!Get_Size_Via_df(Display_Error)) {
+			if (!Was_Already_Mounted)
+				UnMount(false);
+			return false;
+		}
+	}
+
+	if (Has_Data_Media) {
+		if (Mount(Display_Error)) {
+			Used = backup_exclusions.Get_Folder_Size(Mount_Point);
+			Backup_Size = Used;
+			int bak = (int)(Used / 1048576LLU);
+			int fre = (int)(Free / 1048576LLU);
+			LOGINFO("Data backup size is %iMB, free: %iMB.\n", bak, fre);
+		} else {
+			if (!Was_Already_Mounted)
+				UnMount(false);
+			return false;
+		}
+	} else if (Has_Android_Secure) {
+		if (Mount(Display_Error))
+			Backup_Size = backup_exclusions.Get_Folder_Size(Backup_Path);
+		else {
+			if (!Was_Already_Mounted)
+				UnMount(false);
+			return false;
+		}
+	}
+	if (!Was_Already_Mounted)
+		UnMount(false);
+	return true;
+}
+
+bool TWPartition::Find_Wildcard_Block_Devices(const string& Device) {
+	int mount_point_index = 0; // we will need to create separate mount points for each partition found and we use this index to name each one
+	string Path = TWFunc::Get_Path(Device);
+	string Dev = TWFunc::Get_Filename(Device);
+	size_t wildcard_index = Dev.find("*");
+	if (wildcard_index != string::npos)
+		Dev = Dev.substr(0, wildcard_index);
+	wildcard_index = Dev.size();
+	DIR* d = opendir(Path.c_str());
+	if (d == NULL) {
+		LOGINFO("Error opening '%s': %s\n", Path.c_str(), strerror(errno));
+		return false;
+	}
+	struct dirent* de;
+	while ((de = readdir(d)) != NULL) {
+		if (de->d_type != DT_BLK || strlen(de->d_name) <= wildcard_index || strncmp(de->d_name, Dev.c_str(), wildcard_index) != 0)
+			continue;
+
+		string item = Path + "/";
+		item.append(de->d_name);
+		if (PartitionManager.Find_Partition_By_Block_Device(item))
+			continue;
+		TWPartition *part = new TWPartition;
+		char buffer[MAX_FSTAB_LINE_LENGTH];
+		sprintf(buffer, "%s %s-%i auto defaults defaults", item.c_str(), Mount_Point.c_str(), ++mount_point_index);
+		part->Process_Fstab_Line(buffer, false, NULL);
+		char display[MAX_FSTAB_LINE_LENGTH];
+		sprintf(display, "%s %i", Storage_Name.c_str(), mount_point_index);
+		part->Storage_Name = display;
+		part->Display_Name = display;
+		part->Primary_Block_Device = item;
+		part->Wildcard_Block_Device = false;
+		part->Is_SubPartition = true;
+		part->SubPartition_Of = Mount_Point;
+		part->Is_Storage = Is_Storage;
+		part->Can_Be_Mounted = true;
+		part->Removable = true;
+		part->Can_Be_Wiped = Can_Be_Wiped;
+		part->Wipe_Available_in_GUI = Wipe_Available_in_GUI;
+		part->Find_Actual_Block_Device();
+		part->Update_Size(false);
+		Has_SubPartition = true;
+		PartitionManager.Output_Partition(part);
+		PartitionManager.Add_Partition(part);
+	}
+	closedir(d);
+	return (mount_point_index > 0);
+}
+
+void TWPartition::Find_Actual_Block_Device(void) {
+	if (!Sysfs_Entry.empty() && Primary_Block_Device.empty() && Decrypted_Block_Device.empty()) {
+		/* Sysfs_Entry.empty() indicates if this is a sysfs entry that begins with /device/
+		 * If we have a syfs entry then we are looking for this device from a uevent add.
+		 * The uevent add will set the primary block device based on the data we receive from
+		 * after checking for adopted storage. If the device ends up being adopted, then the
+		 * decrypted block device will be set instead of the primary block device. */
+		Is_Present = false;
+		return;
+	}
+	if (Wildcard_Block_Device && !Is_Adopted_Storage) {
+		Is_Present = false;
+		Actual_Block_Device = "";
+		Can_Be_Mounted = false;
+		if (!Find_Wildcard_Block_Devices(Primary_Block_Device)) {
+			string Dev = Primary_Block_Device.substr(0, Primary_Block_Device.find("*"));
+			if (TWFunc::Path_Exists(Dev)) {
+				Is_Present = true;
+				Can_Be_Mounted = true;
+				Actual_Block_Device = Dev;
+			}
+		}
+		return;
+	} else if (Is_Decrypted && !Decrypted_Block_Device.empty()) {
+		Actual_Block_Device = Decrypted_Block_Device;
+		if (TWFunc::Path_Exists(Decrypted_Block_Device)) {
+			Is_Present = true;
+			return;
+		}
+	} else if (SlotSelect && TWFunc::Path_Exists(Primary_Block_Device + PartitionManager.Get_Active_Slot_Suffix())) {
+		Actual_Block_Device = Primary_Block_Device + PartitionManager.Get_Active_Slot_Suffix();
+		unlink(Primary_Block_Device.c_str());
+		symlink(Actual_Block_Device.c_str(), Primary_Block_Device.c_str()); // we create a non-slot symlink pointing to the currently selected slot which may assist zips with installing
+		Is_Present = true;
+		return;
+	} else if (TWFunc::Path_Exists(Primary_Block_Device)) {
+		Is_Present = true;
+		Actual_Block_Device = Primary_Block_Device;
+		return;
+	}
+	if (!Alternate_Block_Device.empty() && TWFunc::Path_Exists(Alternate_Block_Device)) {
+		Actual_Block_Device = Alternate_Block_Device;
+		Is_Present = true;
+	} else {
+		Is_Present = false;
+	}
+}
+
+void TWPartition::Recreate_Media_Folder(void) {
+	string Command;
+	string Media_Path = Mount_Point + "/media";
+
+	if (Is_FBE) {
+		LOGINFO("Not recreating media folder on FBE\n");
+		return;
+	}
+	if (!Mount(true)) {
+		gui_msg(Msg(msg::kError, "recreate_folder_err=Unable to recreate {1} folder.")(Media_Path));
+	} else if (!TWFunc::Path_Exists(Media_Path)) {
+		PartitionManager.Mount_By_Path(Symlink_Mount_Point, true);
+		LOGINFO("Recreating %s folder.\n", Media_Path.c_str());
+		mkdir(Media_Path.c_str(), 0770);
+		string Internal_path = DataManager::GetStrValue("tw_internal_path");
+		if (!Internal_path.empty()) {
+			LOGINFO("Recreating %s folder.\n", Internal_path.c_str());
+			mkdir(Internal_path.c_str(), 0770);
+		}
+#ifdef TW_INTERNAL_STORAGE_PATH
+		mkdir(EXPAND(TW_INTERNAL_STORAGE_PATH), 0770);
+#endif
+
+		// Afterwards, we will try to set the
+		// default metadata that we were hopefully able to get during
+		// early boot.
+		tw_set_default_metadata(Media_Path.c_str());
+		if (!Internal_path.empty())
+			tw_set_default_metadata(Internal_path.c_str());
+
+		// Toggle mount to ensure that "internal sdcard" gets mounted
+		PartitionManager.UnMount_By_Path(Symlink_Mount_Point, true);
+		PartitionManager.Mount_By_Path(Symlink_Mount_Point, true);
+	}
+}
+
+void TWPartition::Recreate_AndSec_Folder(void) {
+	if (!Has_Android_Secure)
+		return;
+	LOGINFO("Creating %s: %s\n", Backup_Display_Name.c_str(), Symlink_Path.c_str());
+	if (!Mount(true)) {
+		gui_msg(Msg(msg::kError, "recreate_folder_err=Unable to recreate {1} folder.")(Backup_Name));
+	} else if (!TWFunc::Path_Exists(Symlink_Path)) {
+		LOGINFO("Recreating %s folder.\n", Backup_Name.c_str());
+		PartitionManager.Mount_By_Path(Symlink_Mount_Point, true);
+		mkdir(Symlink_Path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+		PartitionManager.UnMount_By_Path(Symlink_Mount_Point, true);
+	}
+}
+
+uint64_t TWPartition::Get_Max_FileSize() {
+	uint64_t maxFileSize = 0;
+	const uint64_t constGB = (uint64_t) 1024 * 1024 * 1024;
+	const uint64_t constTB = (uint64_t) constGB * 1024;
+	const uint64_t constPB = (uint64_t) constTB * 1024;
+	if (Current_File_System == "ext4")
+		maxFileSize = 16 * constTB; //16 TB
+	else if (Current_File_System == "vfat")
+		maxFileSize = 4 * constGB; //4 GB
+	else if (Current_File_System == "ntfs" || Current_File_System == "tntfs")
+		maxFileSize = 256 * constTB; //256 TB
+	else if (Current_File_System == "exfat")
+		maxFileSize = 16 * constPB; //16 PB
+	else if (Current_File_System == "ext3")
+		maxFileSize = 2 * constTB; //2 TB
+	else if (Current_File_System == "f2fs")
+		maxFileSize = 3.94 * constTB; //3.94 TB
+	else
+		maxFileSize = 100000000L;
+	return maxFileSize - 1;
+}
+
+bool TWPartition::Flash_Image(PartitionSettings *part_settings) {
+	string Restore_File_System, full_filename;
+
+	full_filename = part_settings->Backup_Folder + "/" + Backup_FileName;
+
+	LOGINFO("Image filename is: %s\n", Backup_FileName.c_str());
+
+	if (Backup_Method == BM_FILES) {
+		LOGERR("Cannot flash images to file systems\n");
+		return false;
+	} else if (!Can_Flash_Img) {
+		LOGERR("Cannot flash images to partitions %s\n", Display_Name.c_str());
+		return false;
+	} else {
+		if (!Find_Partition_Size()) {
+			LOGERR("Unable to find partition size for '%s'\n", Mount_Point.c_str());
+			return false;
+		}
+		unsigned long long image_size = TWFunc::Get_File_Size(full_filename);
+		if (image_size > Size) {
+			LOGINFO("Size (%llu bytes) of image '%s' is larger than target device '%s' (%llu bytes)\n",
+				image_size, Backup_FileName.c_str(), Actual_Block_Device.c_str(), Size);
+			gui_err("img_size_err=Size of image is larger than target device");
+			return false;
+		}
+		if (Backup_Method == BM_DD) {
+			if (!part_settings->adbbackup) {
+				if (Is_Sparse_Image(full_filename)) {
+					return Flash_Sparse_Image(full_filename);
+				}
+			}
+			return Raw_Read_Write(part_settings);
+		} else if (Backup_Method == BM_FLASH_UTILS) {
+			return Flash_Image_FI(full_filename, NULL);
+		}
+	}
+
+	LOGERR("Unknown flash method for '%s'\n", Mount_Point.c_str());
+	return false;
+}
+
+bool TWPartition::Is_Sparse_Image(const string& Filename) {
+	uint32_t magic = 0;
+	int fd = open(Filename.c_str(), O_RDONLY);
+	if (fd < 0) {
+		gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(Filename)(strerror(errno)));
+		return false;
+	}
+
+	if (read(fd, &magic, sizeof(magic)) != sizeof(magic)) {
+		gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(Filename)(strerror(errno)));
+		close(fd);
+		return false;
+	}
+	close(fd);
+	if (magic == SPARSE_HEADER_MAGIC)
+		return true;
+	return false;
+}
+
+bool TWPartition::Flash_Sparse_Image(const string& Filename) {
+	string Command;
+
+	gui_msg(Msg("flashing=Flashing {1}...")(Display_Name));
+
+	Command = "simg2img '" + Filename + "' '" + Actual_Block_Device + "'";
+	LOGINFO("Flash command: '%s'\n", Command.c_str());
+	TWFunc::Exec_Cmd(Command);
+	return true;
+}
+
+bool TWPartition::Flash_Image_FI(const string& Filename, ProgressTracking *progress) {
+	string Command;
+	unsigned long long file_size;
+
+	gui_msg(Msg("flashing=Flashing {1}...")(Display_Name));
+	if (progress) {
+		file_size = (unsigned long long)(TWFunc::Get_File_Size(Filename));
+		progress->SetPartitionSize(file_size);
+	}
+	// Sometimes flash image doesn't like to flash due to the first 2KB matching, so we erase first to ensure that it flashes
+	Command = "erase_image " + MTD_Name;
+	LOGINFO("Erase command: '%s'\n", Command.c_str());
+	TWFunc::Exec_Cmd(Command);
+	Command = "flash_image " + MTD_Name + " '" + Filename + "'";
+	LOGINFO("Flash command: '%s'\n", Command.c_str());
+	TWFunc::Exec_Cmd(Command);
+	if (progress)
+		progress->UpdateSize(file_size);
+	return true;
+}
+
+void TWPartition::Change_Mount_Read_Only(bool new_value) {
+	Mount_Read_Only = new_value;
+}
+
+bool TWPartition::Is_Read_Only() {
+	return Mount_Read_Only;
+}
+
+int TWPartition::Check_Lifetime_Writes() {
+	bool original_read_only = Mount_Read_Only;
+	int ret = 1;
+
+	Mount_Read_Only = true;
+	if (Mount(false)) {
+		Find_Actual_Block_Device();
+		string temp = Actual_Block_Device;
+		Find_Real_Block_Device(temp, false);
+		string block = basename(temp.c_str());
+		string file = "/sys/fs/" + Current_File_System + "/" + block + "/lifetime_write_kbytes";
+		string result;
+		if (TWFunc::Path_Exists(file)) {
+			if (TWFunc::read_file(file, result) != 0) {
+				LOGINFO("Check_Lifetime_Writes of '%s' failed to read_file\n", file.c_str());
+			} else {
+				LOGINFO("Check_Lifetime_Writes result: '%s'\n", result.c_str());
+				if (result == "0") {
+					ret = 0;
+				}
+			}
+		} else {
+			LOGINFO("Check_Lifetime_Writes file does not exist '%s'\n", file.c_str());
+		}
+		UnMount(true);
+	} else {
+		LOGINFO("Check_Lifetime_Writes failed to mount '%s'\n", Mount_Point.c_str());
+	}
+	Mount_Read_Only = original_read_only;
+	return ret;
+}
+
+int TWPartition::Decrypt_Adopted() {
+#ifdef TW_INCLUDE_CRYPTO
+	int ret = 1;
+	Is_Adopted_Storage = false;
+	string Adopted_Key_File = "";
+
+	if (!Removable)
+		return ret;
+
+	int fd = open(Alternate_Block_Device.c_str(), O_RDONLY);
+	if (fd < 0) {
+		LOGINFO("failed to open '%s'\n", Alternate_Block_Device.c_str());
+		return ret;
+	}
+	char type_guid[80];
+	char part_guid[80];
+
+	uint32_t p_num;
+	size_t last_digit = Primary_Block_Device.find_last_not_of("0123456789");
+	if ((last_digit != string::npos) && (last_digit != Primary_Block_Device.length()-1))
+		p_num = atoi(Primary_Block_Device.substr(last_digit + 1).c_str()) + 1;
+	else
+		p_num = 2;
+
+	if (gpt_disk_get_partition_info(fd, p_num, type_guid, part_guid) == 0) {
+		LOGINFO("type: '%s'\n", type_guid);
+		LOGINFO("part: '%s'\n", part_guid);
+		Adopted_GUID = part_guid;
+		LOGINFO("Adopted_GUID '%s'\n", Adopted_GUID.c_str());
+		if (strcmp(type_guid, TWGptAndroidExpand) == 0) {
+			LOGINFO("android_expand found\n");
+			Adopted_Key_File = "/data/misc/vold/expand_";
+			Adopted_Key_File += part_guid;
+			Adopted_Key_File += ".key";
+			if (TWFunc::Path_Exists(Adopted_Key_File)) {
+				Is_Adopted_Storage = true;
+				/* Until we find a use case for this, I think it is safe
+				 * to disable USB Mass Storage whenever adopted storage
+				 * is present.
+				 */
+				if (p_num == 2) {
+					// TODO: Properly detect mixed vs fully adopted storage. Maybe this
+					// should be moved to partitionmanager instead, and disable after
+					// checking all partitions. Also the presence of adopted storage does
+					// not necessarily mean it's being used as Internal Storage
+					LOGINFO("Detected adopted storage, disabling USB mass storage mode\n");
+					DataManager::SetValue("tw_has_usb_storage", 0);
+				}
+			}
+		}
+	}
+
+	if (Is_Adopted_Storage) {
+		string Adopted_Block_Device = Alternate_Block_Device + "p" + TWFunc::to_string(p_num);
+		if (!TWFunc::Path_Exists(Adopted_Block_Device)) {
+			Adopted_Block_Device = Alternate_Block_Device + TWFunc::to_string(p_num);
+			if (!TWFunc::Path_Exists(Adopted_Block_Device)) {
+				LOGINFO("Adopted block device does not exist\n");
+				goto exit;
+			}
+		}
+		LOGINFO("key file is '%s', block device '%s'\n", Adopted_Key_File.c_str(), Adopted_Block_Device.c_str());
+		char crypto_blkdev[MAXPATHLEN];
+		std::string thekey;
+		int fdkey = open(Adopted_Key_File.c_str(), O_RDONLY);
+		if (fdkey < 0) {
+			LOGINFO("failed to open key file\n");
+			goto exit;
+		}
+		char buf[512];
+		ssize_t n;
+		while ((n = read(fdkey, &buf[0], sizeof(buf))) > 0) {
+			thekey.append(buf, n);
+		}
+		close(fdkey);
+		// unsigned char* key = (unsigned char*) thekey.data();
+		// cryptfs_revert_ext_volume(part_guid);
+
+		// ret = cryptfs_setup_ext_volume(part_guid, Adopted_Block_Device.c_str(), key, thekey.size(), crypto_blkdev);
+		if (ret == 0) {
+			LOGINFO("adopted storage new block device: '%s'\n", crypto_blkdev);
+			Decrypted_Block_Device = crypto_blkdev;
+			Is_Decrypted = true;
+			Is_Encrypted = true;
+			Find_Actual_Block_Device();
+			if (!Mount_Storage_Retry(false)) {
+				LOGERR("Failed to mount decrypted adopted storage device\n");
+				Is_Decrypted = false;
+				Is_Encrypted = false;
+				// cryptfs_revert_ext_volume(part_guid);
+				ret = 1;
+			} else {
+				UnMount(false);
+				Has_Android_Secure = false;
+				Symlink_Path = "";
+				Symlink_Mount_Point = "";
+				Backup_Name = Mount_Point.substr(1);
+				Backup_Path = Mount_Point;
+				TWPartition* sdext = PartitionManager.Find_Partition_By_Path("/sd-ext");
+				if (sdext && sdext->Actual_Block_Device == Adopted_Block_Device) {
+					LOGINFO("Removing /sd-ext from partition list due to adopted storage\n");
+					PartitionManager.Remove_Partition_By_Path("/sd-ext");
+				}
+				Setup_Data_Media();
+				Wipe_Available_in_GUI = true;
+				Wipe_During_Factory_Reset = true;
+				Can_Be_Backed_Up = true;
+				Can_Encrypt_Backup = true;
+				Use_Userdata_Encryption = true;
+				Is_Storage = true;
+				Storage_Name = "Adopted Storage";
+				Is_SubPartition = true;
+				SubPartition_Of = "/data";
+				PartitionManager.Add_MTP_Storage(MTP_Storage_ID);
+				DataManager::SetValue("tw_has_adopted_storage", 1);
+			}
+		} else {
+			LOGERR("Failed to setup adopted storage decryption\n");
+		}
+	}
+exit:
+	close(fd);
+	return ret;
+#else
+	LOGINFO("Decrypt_Adopted: no crypto support\n");
+	return 1;
+#endif
+}
+
+void TWPartition::Revert_Adopted() {
+#ifdef TW_INCLUDE_CRYPTO
+	if (!Adopted_GUID.empty()) {
+		PartitionManager.Remove_MTP_Storage(Mount_Point);
+		UnMount(false);
+		// cryptfs_revert_ext_volume(Adopted_GUID.c_str());
+		Is_Adopted_Storage = false;
+		Is_Encrypted = false;
+		Is_Decrypted = false;
+		Decrypted_Block_Device = "";
+		Find_Actual_Block_Device();
+		Wipe_During_Factory_Reset = false;
+		Can_Be_Backed_Up = false;
+		Can_Encrypt_Backup = false;
+		Use_Userdata_Encryption = false;
+		Is_SubPartition = false;
+		SubPartition_Of = "";
+		Has_Data_Media = false;
+		Storage_Path = Mount_Point;
+		if (!Symlink_Mount_Point.empty()) {
+			TWPartition* Dat = PartitionManager.Find_Partition_By_Path("/data");
+			if (Dat) {
+				Dat->UnMount(false);
+				Dat->Symlink_Mount_Point = Symlink_Mount_Point;
+			}
+			Symlink_Mount_Point = "";
+		}
+	}
+#else
+	LOGINFO("Revert_Adopted: no crypto support\n");
+#endif
+}
+
+void TWPartition::Set_Backup_FileName(string fname) {
+	Backup_FileName = fname;
+}
+
+string TWPartition::Get_Backup_Name() {
+	return Backup_Name;
+}
+
+string TWPartition::Get_Mount_Point() {
+	return Mount_Point;
+}
+
+void TWPartition::Set_Block_Device(std::string block_device) {
+	Primary_Block_Device = Actual_Block_Device = block_device;
+}
+
+bool TWPartition::Get_Super_Status() {
+	return Is_Super;
+}
+
+void TWPartition::Set_Can_Be_Backed_Up(bool val) {
+	Can_Be_Backed_Up = val;
+}
+
+void TWPartition::Set_Can_Be_Wiped(bool val) {
+	Can_Be_Wiped = val;
+	Wipe_Available_in_GUI = val;
+}
+
+std::string TWPartition::Get_Backup_FileName() {
+	return Backup_FileName;
+}
+
+std::string TWPartition::Get_Display_Name() {
+	return Display_Name;
+}
+
+bool TWPartition::Is_SlotSelect() {
+	return SlotSelect;
+}
diff --git a/partitionmanager.cpp b/partitionmanager.cpp
new file mode 100755
index 0000000..6f55065
--- /dev/null
+++ b/partitionmanager.cpp
@@ -0,0 +1,3618 @@
+/*
+	Copyright 2014 to 2021 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+#include <map>
+#include <vector>
+#include <dirent.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <zlib.h>
+#include <iostream>
+#include <iomanip>
+#include <sys/wait.h>
+#include <linux/fs.h>
+#include <sys/mount.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <fstab/fstab.h>
+#include <fs_avb/fs_avb.h>
+#include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
+#include <fs_mgr_overlayfs.h>
+#include <fs_mgr/roots.h>
+#include <libgsi/libgsi.h>
+#include <liblp/liblp.h>
+#include <libgsi/libgsi.h>
+#include <liblp/builder.h>
+#include <libsnapshot/snapshot.h>
+
+#include "variables.h"
+#include "twcommon.h"
+#include "partitions.hpp"
+#include "data.hpp"
+#include "startupArgs.hpp"
+#include "twrp-functions.hpp"
+#include "fixContexts.hpp"
+#include "exclude.hpp"
+#include "set_metadata.h"
+#include "tw_atomic.hpp"
+#include "gui/gui.hpp"
+#include "progresstracking.hpp"
+#include "twrpDigestDriver.hpp"
+#include "twrpRepacker.hpp"
+#include "adbbu/libtwadbbu.hpp"
+
+#ifdef TW_HAS_MTP
+#ifdef TW_HAS_LEGACY_MTP
+#include "mtp/legacy/mtp_MtpServer.hpp"
+#include "mtp/legacy/twrpMtp.hpp"
+#include "mtp/legacy/MtpMessage.hpp"
+#else
+#include "mtp/ffs/mtp_MtpServer.hpp"
+#include "mtp/ffs/twrpMtp.hpp"
+#include "mtp/ffs/MtpMessage.hpp"
+#endif
+#endif
+
+extern "C" {
+	#include "cutils/properties.h"
+	#include "gui/gui.h"
+}
+
+#ifdef TW_INCLUDE_CRYPTO
+// #include "crypto/fde/cryptfs.h"
+#include "gui/rapidxml.hpp"
+#include "gui/pages.hpp"
+#ifdef TW_INCLUDE_FBE
+#include "Decrypt.h"
+#ifdef TW_INCLUDE_FBE_METADATA_DECRYPT
+	#ifdef USE_FSCRYPT
+	#include "cryptfs.h"
+	#include "fscrypt-common.h"
+	#include "MetadataCrypt.h"
+	#endif
+#endif
+#endif
+#ifdef TW_CRYPTO_USE_SYSTEM_VOLD
+#include "crypto/vold_decrypt/vold_decrypt.h"
+#endif
+#endif
+
+#ifdef AB_OTA_UPDATER
+#include <android/hardware/boot/1.0/IBootControl.h>
+using android::hardware::boot::V1_0::CommandResult;
+using android::hardware::boot::V1_0::IBootControl;
+#endif
+
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::Fstab;
+using android::fs_mgr::FstabEntry;
+using android::fs_mgr::MetadataBuilder;
+
+extern bool datamedia;
+std::vector<users_struct> Users_List;
+
+TWPartitionManager::TWPartitionManager(void) {
+	mtp_was_enabled = false;
+	mtp_write_fd = -1;
+	uevent_pfd.fd = -1;
+	stop_backup.set_value(0);
+#ifdef AB_OTA_UPDATER
+	char slot_suffix[PROPERTY_VALUE_MAX];
+	property_get("ro.boot.slot_suffix", slot_suffix, "error");
+	if (strcmp(slot_suffix, "error") == 0)
+		property_get("ro.boot.slot", slot_suffix, "error");
+	Active_Slot_Display = "";
+	if (strcmp(slot_suffix, "_a") == 0 || strcmp(slot_suffix, "a") == 0)
+		Set_Active_Slot("A");
+	else
+		Set_Active_Slot("B");
+#endif
+}
+
+void TWPartitionManager::Set_Crypto_State() {
+	char crypto_state[PROPERTY_VALUE_MAX];
+	property_get("ro.crypto.state", crypto_state, "error");
+	if (strcmp(crypto_state, "error") == 0)
+		property_set("ro.crypto.state", "encrypted");
+}
+
+int TWPartitionManager::Set_Crypto_Type(const char* crypto_type) {
+	char type_prop[PROPERTY_VALUE_MAX];
+	property_get("ro.crypto.type", type_prop, "error");
+	if (strcmp(type_prop, "error") == 0)
+		property_set("ro.crypto.type", crypto_type);
+	// Sleep for a bit so that services can start if needed
+	sleep(1);
+	return 0;
+}
+
+int TWPartitionManager::Process_Fstab(string Fstab_Filename, bool Display_Error, bool recovery_mode) {
+	FILE *fstabFile;
+	char fstab_line[MAX_FSTAB_LINE_LENGTH];
+	std::map<string, Flags_Map> twrp_flags;
+
+	fstabFile = fopen("/etc/twrp.flags", "rt");
+	if (fstabFile != NULL) {
+		LOGINFO("Reading /etc/twrp.flags\n");
+		while (fgets(fstab_line, sizeof(fstab_line), fstabFile) != NULL) {
+			size_t line_size = strlen(fstab_line);
+			if (fstab_line[line_size - 1] != '\n')
+				fstab_line[line_size] = '\n';
+			Flags_Map line_flags;
+			line_flags.Primary_Block_Device = "";
+			line_flags.Alternate_Block_Device = "";
+			line_flags.fstab_line = (char*)malloc(MAX_FSTAB_LINE_LENGTH);
+			if (!line_flags.fstab_line) {
+				LOGERR("malloc error on line_flags.fstab_line\n");
+				return false;
+			}
+			memcpy(line_flags.fstab_line, fstab_line, MAX_FSTAB_LINE_LENGTH);
+			bool found_separator = false;
+			char *fs_loc = NULL;
+			char *block_loc = NULL;
+			char *flags_loc = NULL;
+			size_t index, item_index = 0;
+			for (index = 0; index < line_size; index++) {
+				if (fstab_line[index] <= 32) {
+					fstab_line[index] = '\0';
+					found_separator = true;
+				} else if (found_separator) {
+					if (item_index == 0) {
+						fs_loc = fstab_line + index;
+					} else if (item_index == 1) {
+						block_loc = fstab_line + index;
+					} else if (item_index > 1) {
+						char *ptr = fstab_line + index;
+						if (*ptr == '/') {
+							line_flags.Alternate_Block_Device = ptr;
+						} else if (strlen(ptr) > strlen("flags=") && strncmp(ptr, "flags=", strlen("flags=")) == 0) {
+							flags_loc = ptr;
+							// Once we find the flags=, we're done scanning the line
+							break;
+						}
+					}
+					found_separator = false;
+					item_index++;
+				}
+			}
+			if (block_loc)
+				line_flags.Primary_Block_Device = block_loc;
+			if (fs_loc)
+				line_flags.File_System = fs_loc;
+			if (flags_loc)
+				line_flags.Flags = flags_loc;
+			string Mount_Point = fstab_line;
+			twrp_flags[Mount_Point] = line_flags;
+			memset(fstab_line, 0, sizeof(fstab_line));
+		}
+		fclose(fstabFile);
+	}
+
+	fstabFile = fopen(Fstab_Filename.c_str(), "rt");
+	if (fstabFile == NULL) {
+		LOGERR("Critical Error: Unable to open fstab at '%s'.\n", Fstab_Filename.c_str());
+		return false;
+	} else
+		LOGINFO("Reading %s\n", Fstab_Filename.c_str());
+
+	while (fgets(fstab_line, sizeof(fstab_line), fstabFile) != NULL) {
+		if (strstr(fstab_line, "swap"))
+			continue; // Skip swap in recovery
+
+		if (fstab_line[0] == '#')
+			continue;
+
+		size_t line_size = strlen(fstab_line);
+		if (fstab_line[line_size - 1] != '\n')
+			fstab_line[line_size] = '\n';
+
+		TWPartition* partition = new TWPartition();
+		if (partition->Process_Fstab_Line(fstab_line, Display_Error, &twrp_flags))
+			Partitions.push_back(partition);
+		else
+			delete partition;
+
+		memset(fstab_line, 0, sizeof(fstab_line));
+	}
+	fclose(fstabFile);
+
+	if (twrp_flags.size() > 0) {
+		LOGINFO("Processing remaining twrp.flags\n");
+		// Add any items from twrp.flags that did not exist in the recovery.fstab
+		for (std::map<string, Flags_Map>::iterator mapit=twrp_flags.begin(); mapit!=twrp_flags.end(); mapit++) {
+			if (Find_Partition_By_Path(mapit->first) == NULL) {
+				TWPartition* partition = new TWPartition();
+				if (partition->Process_Fstab_Line(mapit->second.fstab_line, Display_Error, NULL))
+					Partitions.push_back(partition);
+				else
+					delete partition;
+			}
+			if (mapit->second.fstab_line)
+				free(mapit->second.fstab_line);
+			mapit->second.fstab_line = NULL;
+		}
+	}
+	if (Get_Super_Status()) {
+		Setup_Super_Devices();
+	}
+	LOGINFO("Done processing fstab files\n");
+
+	if (recovery_mode) {
+		Setup_Fstab_Partitions(Display_Error);
+	}
+	return true;
+}
+
+void TWPartitionManager::Setup_Fstab_Partitions(bool Display_Error) {
+		TWPartition* settings_partition = NULL;
+		TWPartition* andsec_partition = NULL;
+		std::vector<TWPartition*>::iterator iter;
+		unsigned int storageid = 1 << 16;	// upper 16 bits are for physical storage device, we pretend to have only one
+
+		for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+			(*iter)->Partition_Post_Processing(Display_Error);
+
+			if ((*iter)->Is_Storage) {
+				++storageid;
+				(*iter)->MTP_Storage_ID = storageid;
+			}
+
+			if (!settings_partition && (*iter)->Is_Settings_Storage && (*iter)->Is_Present)
+				settings_partition = (*iter);
+			else
+				(*iter)->Is_Settings_Storage = false;
+
+			if (!andsec_partition && (*iter)->Has_Android_Secure && (*iter)->Is_Present)
+				andsec_partition = (*iter);
+			else
+				(*iter)->Has_Android_Secure = false;
+
+			if ((*iter)->Is_Super)
+				Prepare_Super_Volume((*iter));
+		}
+
+		Unlock_Block_Partitions();
+
+		//Setup Apex before decryption
+		TWPartition* sys = PartitionManager.Find_Partition_By_Path(PartitionManager.Get_Android_Root_Path());
+		TWPartition* ven = PartitionManager.Find_Partition_By_Path("/vendor");
+		if (sys) {
+			if (sys->Get_Super_Status()) {
+				sys->Mount(true);
+				if (ven) {
+					ven->Mount(true);
+				}
+	#ifdef TW_EXCLUDE_APEX
+				LOGINFO("Apex is disabled in this build\n");
+	#else
+				twrpApex apex;
+				if (!apex.loadApexImages()) {
+					LOGERR("Unable to load apex images from %s\n", APEX_DIR);
+					property_set("twrp.apex.loaded", "false");
+				} else {
+					property_set("twrp.apex.loaded", "true");
+				}
+				TWFunc::check_and_run_script("/sbin/resyncapex.sh", "apex");
+	#endif
+			}
+		}
+	#ifndef USE_VENDOR_LIBS
+		if (ven)
+			ven->UnMount(true);
+		if (sys)
+			sys->UnMount(true);
+	#endif
+
+		if (!datamedia && !settings_partition && Find_Partition_By_Path("/sdcard") == NULL && Find_Partition_By_Path("/internal_sd") == NULL && Find_Partition_By_Path("/internal_sdcard") == NULL && Find_Partition_By_Path("/emmc") == NULL) {
+			// Attempt to automatically identify /data/media emulated storage devices
+			TWPartition* Dat = Find_Partition_By_Path("/data");
+			if (Dat) {
+				LOGINFO("Using automatic handling for /data/media emulated storage device.\n");
+				datamedia = true;
+				Dat->Setup_Data_Media();
+				settings_partition = Dat;
+				// Since /data was not considered a storage partition earlier, we still need to assign an MTP ID
+				++storageid;
+				Dat->MTP_Storage_ID = storageid;
+			}
+		}
+		if (!settings_partition) {
+			for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+				if ((*iter)->Is_Storage) {
+					settings_partition = (*iter);
+					break;
+				}
+			}
+			if (!settings_partition)
+				LOGERR("Unable to locate storage partition for storing settings file.\n");
+		}
+		if (!Write_Fstab()) {
+			if (Display_Error)
+				LOGERR("Error creating fstab\n");
+			else
+				LOGINFO("Error creating fstab\n");
+		}
+
+		if (andsec_partition) {
+			Setup_Android_Secure_Location(andsec_partition);
+		} else if (settings_partition) {
+			Setup_Android_Secure_Location(settings_partition);
+		}
+		if (settings_partition) {
+			Setup_Settings_Storage_Partition(settings_partition);
+		}
+
+	#ifdef TW_INCLUDE_CRYPTO
+		DataManager::SetValue(TW_IS_ENCRYPTED, 1);
+		Decrypt_Data();
+	#endif
+
+		Update_System_Details();
+		if (Get_Super_Status())
+			Setup_Super_Partition();
+		UnMount_Main_Partitions();
+	#ifdef AB_OTA_UPDATER
+		DataManager::SetValue("tw_active_slot", Get_Active_Slot_Display());
+	#endif
+		setup_uevent();
+}
+
+int TWPartitionManager::Write_Fstab(void) {
+	FILE *fp;
+	std::vector<TWPartition*>::iterator iter;
+	string Line;
+
+	fp = fopen("/etc/fstab", "w");
+	if (fp == NULL) {
+		LOGINFO("Can not open /etc/fstab.\n");
+		return false;
+	}
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Can_Be_Mounted) {
+			Line = (*iter)->Actual_Block_Device + " " + (*iter)->Mount_Point + " " + (*iter)->Current_File_System +
+				((*iter)->Mount_Read_Only ? " ro " : " rw ") + "0 0\n";
+			fputs(Line.c_str(), fp);
+		}
+		// Handle subpartition tracking
+		if ((*iter)->Is_SubPartition) {
+			TWPartition* ParentPartition = Find_Partition_By_Path((*iter)->SubPartition_Of);
+			if (ParentPartition)
+				ParentPartition->Has_SubPartition = true;
+			else
+				LOGERR("Unable to locate parent partition '%s' of '%s'\n", (*iter)->SubPartition_Of.c_str(), (*iter)->Mount_Point.c_str());
+		}
+	}
+	fclose(fp);
+	return true;
+}
+
+void TWPartitionManager::Decrypt_Data() {
+	#ifdef TW_INCLUDE_CRYPTO
+	TWPartition* Decrypt_Data = Find_Partition_By_Path("/data");
+	if (Decrypt_Data && Decrypt_Data->Is_Encrypted && !Decrypt_Data->Is_Decrypted) {
+		Set_Crypto_State();
+		TWPartition* Key_Directory_Partition = Find_Partition_By_Path(Decrypt_Data->Key_Directory);
+		if (Key_Directory_Partition != nullptr)
+			if (!Key_Directory_Partition->Is_Mounted())
+				Mount_By_Path(Decrypt_Data->Key_Directory, false);
+		if (!Decrypt_Data->Key_Directory.empty()) {
+			Set_Crypto_Type("file");
+#ifdef TW_INCLUDE_FBE_METADATA_DECRYPT
+#ifdef USE_FSCRYPT
+			if (android::vold::fscrypt_mount_metadata_encrypted(Decrypt_Data->Actual_Block_Device, Decrypt_Data->Mount_Point, false, false, Decrypt_Data->Current_File_System)) {
+				std::string crypto_blkdev = android::base::GetProperty("ro.crypto.fs_crypto_blkdev", "error");
+				Decrypt_Data->Decrypted_Block_Device = crypto_blkdev;
+				LOGINFO("Successfully decrypted metadata encrypted data partition with new block device: '%s'\n", crypto_blkdev.c_str());
+#endif
+				Decrypt_Data->Is_Decrypted = true; // Needed to make the mount function work correctly
+				int retry_count = 10;
+				while (!Decrypt_Data->Mount(false) && --retry_count)
+					usleep(500);
+				if (Decrypt_Data->Mount(false)) {
+					if (!Decrypt_Data->Decrypt_FBE_DE()) {
+						LOGERR("Unable to decrypt FBE device\n");
+					}
+
+				} else {
+					LOGINFO("Failed to mount data after metadata decrypt\n");
+				}
+			} else {
+				LOGINFO("Unable to decrypt metadata encryption\n");
+			}
+#else
+			LOGERR("Metadata FBE decrypt support not present in this TWRP\n");
+#endif
+		}
+		if (Decrypt_Data->Is_FBE) {
+			if (DataManager::GetIntValue(TW_CRYPTO_PWTYPE) == 0) {
+				if (Decrypt_Device("!") == 0) {
+					gui_msg("decrypt_success=Successfully decrypted with default password.");
+					DataManager::SetValue(TW_IS_ENCRYPTED, 0);
+				} else {
+					gui_err("unable_to_decrypt=Unable to decrypt with default password.");
+				}
+			}
+		}
+	}
+	if (Decrypt_Data && (!Decrypt_Data->Is_Encrypted || Decrypt_Data->Is_Decrypted)) {
+		Decrypt_Adopted();
+	}
+#endif
+}
+
+void TWPartitionManager::Setup_Settings_Storage_Partition(TWPartition* Part) {
+	DataManager::SetValue("tw_settings_path", Part->Storage_Path);
+	DataManager::SetValue("tw_storage_path", Part->Storage_Path);
+	LOGINFO("Settings storage is '%s'\n", Part->Storage_Path.c_str());
+}
+
+void TWPartitionManager::Setup_Android_Secure_Location(TWPartition* Part) {
+	if (Part->Has_Android_Secure)
+		Part->Setup_AndSec();
+	else if (!datamedia)
+		Part->Setup_AndSec();
+}
+
+void TWPartitionManager::Output_Partition_Logging(void) {
+	std::vector<TWPartition*>::iterator iter;
+
+	printf("\n\nPartition Logs:\n");
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++)
+		Output_Partition((*iter));
+}
+
+void TWPartitionManager::Output_Partition(TWPartition* Part) {
+	unsigned long long mb = 1048576;
+
+	printf("%s | %s | Size: %iMB", Part->Mount_Point.c_str(), Part->Actual_Block_Device.c_str(), (int)(Part->Size / mb));
+	if (Part->Can_Be_Mounted) {
+		printf(" Used: %iMB Free: %iMB Backup Size: %iMB", (int)(Part->Used / mb), (int)(Part->Free / mb), (int)(Part->Backup_Size / mb));
+	}
+	printf("\n   Flags: ");
+	if (Part->Can_Be_Mounted)
+		printf("Can_Be_Mounted ");
+	if (Part->Can_Be_Wiped)
+		printf("Can_Be_Wiped ");
+	if (Part->Use_Rm_Rf)
+		printf("Use_Rm_Rf ");
+	if (Part->Can_Be_Backed_Up)
+		printf("Can_Be_Backed_Up ");
+	if (Part->Wipe_During_Factory_Reset)
+		printf("Wipe_During_Factory_Reset ");
+	if (Part->Wipe_Available_in_GUI)
+		printf("Wipe_Available_in_GUI ");
+	if (Part->Is_SubPartition)
+		printf("Is_SubPartition ");
+	if (Part->Has_SubPartition)
+		printf("Has_SubPartition ");
+	if (Part->Removable)
+		printf("Removable ");
+	if (Part->Is_Present)
+		printf("IsPresent ");
+	if (Part->Can_Be_Encrypted)
+		printf("Can_Be_Encrypted ");
+	if (Part->Is_Encrypted)
+		printf("Is_Encrypted ");
+	if (Part->Is_Decrypted)
+		printf("Is_Decrypted ");
+	if (Part->Has_Data_Media)
+		printf("Has_Data_Media ");
+	if (Part->Can_Encrypt_Backup)
+		printf("Can_Encrypt_Backup ");
+	if (Part->Use_Userdata_Encryption)
+		printf("Use_Userdata_Encryption ");
+	if (Part->Has_Android_Secure)
+		printf("Has_Android_Secure ");
+	if (Part->Is_Storage)
+		printf("Is_Storage ");
+	if (Part->Is_Settings_Storage)
+		printf("Is_Settings_Storage ");
+	if (Part->Ignore_Blkid)
+		printf("Ignore_Blkid ");
+	if (Part->Mount_To_Decrypt)
+		printf("Mount_To_Decrypt ");
+	if (Part->Can_Flash_Img)
+		printf("Can_Flash_Img ");
+	if (Part->Is_Adopted_Storage)
+		printf("Is_Adopted_Storage ");
+	if (Part->SlotSelect)
+		printf("SlotSelect ");
+	if (Part->Mount_Read_Only)
+		printf("Mount_Read_Only ");
+	if (Part->Is_Super)
+		printf("Is_Super ");
+	printf("\n");
+	if (!Part->SubPartition_Of.empty())
+		printf("   SubPartition_Of: %s\n", Part->SubPartition_Of.c_str());
+	if (!Part->Symlink_Path.empty())
+		printf("   Symlink_Path: %s\n", Part->Symlink_Path.c_str());
+	if (!Part->Symlink_Mount_Point.empty())
+		printf("   Symlink_Mount_Point: %s\n", Part->Symlink_Mount_Point.c_str());
+	if (!Part->Primary_Block_Device.empty())
+		printf("   Primary_Block_Device: %s\n", Part->Primary_Block_Device.c_str());
+	if (!Part->Alternate_Block_Device.empty())
+		printf("   Alternate_Block_Device: %s\n", Part->Alternate_Block_Device.c_str());
+	if (!Part->Decrypted_Block_Device.empty())
+		printf("   Decrypted_Block_Device: %s\n", Part->Decrypted_Block_Device.c_str());
+	if (!Part->Crypto_Key_Location.empty())
+		printf("   Crypto_Key_Location: %s\n", Part->Crypto_Key_Location.c_str());
+	if (Part->Length != 0)
+		printf("   Length: %i\n", Part->Length);
+	if (!Part->Display_Name.empty())
+		printf("   Display_Name: %s\n", Part->Display_Name.c_str());
+	if (!Part->Storage_Name.empty())
+		printf("   Storage_Name: %s\n", Part->Storage_Name.c_str());
+	if (!Part->Backup_Path.empty())
+		printf("   Backup_Path: %s\n", Part->Backup_Path.c_str());
+	if (!Part->Backup_Name.empty())
+		printf("   Backup_Name: %s\n", Part->Backup_Name.c_str());
+	if (!Part->Backup_Display_Name.empty())
+		printf("   Backup_Display_Name: %s\n", Part->Backup_Display_Name.c_str());
+	if (!Part->Backup_FileName.empty())
+		printf("   Backup_FileName: %s\n", Part->Backup_FileName.c_str());
+	if (!Part->Storage_Path.empty())
+		printf("   Storage_Path: %s\n", Part->Storage_Path.c_str());
+	if (!Part->Current_File_System.empty())
+		printf("   Current_File_System: %s\n", Part->Current_File_System.c_str());
+	if (!Part->Fstab_File_System.empty())
+		printf("   Fstab_File_System: %s\n", Part->Fstab_File_System.c_str());
+	if (Part->Format_Block_Size != 0)
+		printf("   Format_Block_Size: %lu\n", Part->Format_Block_Size);
+	if (!Part->MTD_Name.empty())
+		printf("   MTD_Name: %s\n", Part->MTD_Name.c_str());
+	printf("   Backup_Method: %s\n", Part->Backup_Method_By_Name().c_str());
+	if (Part->Mount_Flags || !Part->Mount_Options.empty())
+		printf("   Mount_Flags: %i, Mount_Options: %s\n", Part->Mount_Flags, Part->Mount_Options.c_str());
+	if (Part->MTP_Storage_ID)
+		printf("   MTP_Storage_ID: %i\n", Part->MTP_Storage_ID);
+	if (!Part->Key_Directory.empty())
+		printf("   Metadata Key Directory: %s\n", Part->Key_Directory.c_str());
+	printf("\n");
+}
+
+int TWPartitionManager::Mount_By_Path(string Path, bool Display_Error) {
+	std::vector<TWPartition*>::iterator iter;
+	int ret = false;
+	bool found = false;
+	string Local_Path = TWFunc::Get_Root_Path(Path);
+
+	if (Local_Path == "/tmp" || Local_Path == "/")
+		return true;
+
+	// Iterate through all partitions
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Mount_Point == Local_Path || (!(*iter)->Symlink_Mount_Point.empty() && (*iter)->Symlink_Mount_Point == Local_Path)) {
+			ret = (*iter)->Mount(Display_Error);
+			found = true;
+		} else if ((*iter)->Is_SubPartition && (*iter)->SubPartition_Of == Local_Path) {
+			(*iter)->Mount(Display_Error);
+		}
+	}
+	if (found) {
+		return ret;
+	} else if (Display_Error) {
+		gui_msg(Msg(msg::kError, "unable_find_part_path=Unable to find partition for path '{1}'")(Local_Path));
+	}
+	return false;
+}
+
+int TWPartitionManager::UnMount_By_Path(string Path, bool Display_Error) {
+	std::vector<TWPartition*>::iterator iter;
+	int ret = false;
+	bool found = false;
+	string Local_Path = TWFunc::Get_Root_Path(Path);
+
+	// Iterate through all partitions
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Mount_Point == Local_Path || (!(*iter)->Symlink_Mount_Point.empty() && (*iter)->Symlink_Mount_Point == Local_Path)) {
+			ret = (*iter)->UnMount(Display_Error);
+			found = true;
+		} else if ((*iter)->Is_SubPartition && (*iter)->SubPartition_Of == Local_Path) {
+			(*iter)->UnMount(Display_Error);
+		}
+	}
+	if (found) {
+		return ret;
+	} else if (Display_Error) {
+		gui_msg(Msg(msg::kError, "unable_find_part_path=Unable to find partition for path '{1}'")(Local_Path));
+	} else {
+		LOGINFO("UnMount: Unable to find partition for path '%s'\n", Local_Path.c_str());
+	}
+	return false;
+}
+
+int TWPartitionManager::Is_Mounted_By_Path(string Path) {
+	TWPartition* Part = Find_Partition_By_Path(Path);
+
+	if (Part)
+		return Part->Is_Mounted();
+	else
+		LOGINFO("Is_Mounted: Unable to find partition for path '%s'\n", Path.c_str());
+	return false;
+}
+
+int TWPartitionManager::Mount_Current_Storage(bool Display_Error) {
+	string current_storage_path = DataManager::GetCurrentStoragePath();
+
+	if (Mount_By_Path(current_storage_path, Display_Error)) {
+		TWPartition* FreeStorage = Find_Partition_By_Path(current_storage_path);
+		if (FreeStorage)
+			DataManager::SetValue(TW_STORAGE_FREE_SIZE, (int)(FreeStorage->Free / 1048576LLU));
+		return true;
+	}
+	return false;
+}
+
+int TWPartitionManager::Mount_Settings_Storage(bool Display_Error) {
+	return Mount_By_Path(DataManager::GetSettingsStoragePath(), Display_Error);
+}
+
+TWPartition* TWPartitionManager::Find_Partition_By_Path(const string& Path) {
+	std::vector<TWPartition*>::iterator iter;
+	string Local_Path = TWFunc::Get_Root_Path(Path);
+
+	if (Local_Path == "/system")
+		Local_Path = Get_Android_Root_Path();
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Mount_Point == Local_Path || (!(*iter)->Symlink_Mount_Point.empty() && (*iter)->Symlink_Mount_Point == Local_Path))
+			return (*iter);
+	}
+	return NULL;
+}
+
+TWPartition* TWPartitionManager::Find_Partition_By_Block_Device(const string& Block_Device) {
+	std::vector<TWPartition*>::iterator iter;
+
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Primary_Block_Device == Block_Device || (!(*iter)->Actual_Block_Device.empty() && (*iter)->Actual_Block_Device == Block_Device))
+			return (*iter);
+	}
+	return NULL;
+}
+
+int TWPartitionManager::Check_Backup_Name(const std::string& Backup_Name, bool Display_Error, bool Must_Be_Unique) {
+	// Check the backup name to ensure that it is the correct size and contains only valid characters
+	// and that a backup with that name doesn't already exist
+	char backup_name[MAX_BACKUP_NAME_LEN];
+	char backup_loc[255], tw_image_dir[255];
+	int copy_size;
+	int index, cur_char;
+	string Backup_Loc;
+
+	copy_size = Backup_Name.size();
+	// Check size
+	if (copy_size > MAX_BACKUP_NAME_LEN) {
+		if (Display_Error)
+			gui_err("backup_name_len=Backup name is too long.");
+		return -2;
+	}
+
+	// Check each character
+	strncpy(backup_name, Backup_Name.c_str(), copy_size);
+	if (copy_size == 1 && strncmp(backup_name, "0", 1) == 0)
+		return 0; // A "0" (zero) means to use the current timestamp for the backup name
+	for (index=0; index<copy_size; index++) {
+		cur_char = (int)backup_name[index];
+		if (cur_char == 32 || (cur_char >= 48 && cur_char <= 57) || (cur_char >= 65 && cur_char <= 91) || cur_char == 93 || cur_char == 95 || (cur_char >= 97 && cur_char <= 123) || cur_char == 125 || cur_char == 45 || cur_char == 46) {
+			// These are valid characters
+			// Numbers
+			// Upper case letters
+			// Lower case letters
+			// Space
+			// and -_.{}[]
+		} else {
+			if (Display_Error)
+				gui_msg(Msg(msg::kError, "backup_name_invalid=Backup name '{1}' contains invalid character: '{1}'")(Backup_Name)((char)cur_char));
+			return -3;
+		}
+	}
+
+	if (Must_Be_Unique) {
+		// Check to make sure that a backup with this name doesn't already exist
+		DataManager::GetValue(TW_BACKUPS_FOLDER_VAR, Backup_Loc);
+		strcpy(backup_loc, Backup_Loc.c_str());
+		sprintf(tw_image_dir,"%s/%s", backup_loc, Backup_Name.c_str());
+		if (TWFunc::Path_Exists(tw_image_dir)) {
+			if (Display_Error)
+				gui_err("backup_name_exists=A backup with that name already exists!");
+
+			return -4;
+		}
+		// Backup is unique
+	}
+	// No problems found
+	return 0;
+}
+
+bool TWPartitionManager::Backup_Partition(PartitionSettings *part_settings) {
+	time_t start, stop;
+	int use_compression;
+	string backup_log = part_settings->Backup_Folder + "/recovery.log";
+
+	if (part_settings->Part == NULL)
+		return true;
+
+	DataManager::GetValue(TW_USE_COMPRESSION_VAR, use_compression);
+
+	TWFunc::SetPerformanceMode(true);
+	time(&start);
+
+	if (part_settings->Part->Backup(part_settings, &tar_fork_pid)) {
+		sync();
+		sync();
+		string Full_Filename = part_settings->Backup_Folder + "/" + part_settings->Part->Backup_FileName;
+		if (!part_settings->adbbackup && part_settings->generate_digest) {
+			if (!twrpDigestDriver::Make_Digest(Full_Filename))
+				goto backup_error;
+		}
+
+		if (part_settings->Part->Has_SubPartition) {
+			std::vector<TWPartition*>::iterator subpart;
+			TWPartition *parentPart = part_settings->Part;
+
+			for (subpart = Partitions.begin(); subpart != Partitions.end(); subpart++) {
+				if ((*subpart)->Can_Be_Backed_Up && (*subpart)->Is_SubPartition && (*subpart)->SubPartition_Of == parentPart->Mount_Point) {
+					part_settings->Part = *subpart;
+					if (!(*subpart)->Backup(part_settings, &tar_fork_pid)) {
+						goto backup_error;
+					}
+					sync();
+					sync();
+					string Full_Filename = part_settings->Backup_Folder + "/" + part_settings->Part->Backup_FileName;
+					if (!part_settings->adbbackup && part_settings->generate_digest) {
+						if (!twrpDigestDriver::Make_Digest(Full_Filename)) {
+							goto backup_error;
+						}
+					}
+				}
+			}
+		}
+
+		time(&stop);
+		int backup_time = (int) difftime(stop, start);
+		LOGINFO("Partition Backup time: %d\n", backup_time);
+
+		if (part_settings->Part->Backup_Method == BM_FILES) {
+			part_settings->file_time += backup_time;
+		} else {
+			part_settings->img_time += backup_time;
+
+		}
+
+		TWFunc::SetPerformanceMode(false);
+		return true;
+	}
+backup_error:
+	Clean_Backup_Folder(part_settings->Backup_Folder);
+	TWFunc::copy_file("/tmp/recovery.log", backup_log, 0644);
+	tw_set_default_metadata(backup_log.c_str());
+	TWFunc::SetPerformanceMode(false);
+	return false;
+}
+
+void TWPartitionManager::Clean_Backup_Folder(string Backup_Folder) {
+	DIR *d = opendir(Backup_Folder.c_str());
+	struct dirent *p;
+	int r;
+	vector<string> ext;
+
+	//extensions we should delete when cleaning
+	ext.push_back("win");
+	ext.push_back("md5");
+	ext.push_back("sha2");
+	ext.push_back("info");
+
+	gui_msg("backup_clean=Backup Failed. Cleaning Backup Folder.");
+
+	if (d == NULL) {
+		gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(Backup_Folder)(strerror(errno)));
+		return;
+	}
+
+	while ((p = readdir(d))) {
+		if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, ".."))
+			continue;
+
+		string path = Backup_Folder + "/" + p->d_name;
+
+		size_t dot = path.find_last_of(".") + 1;
+		for (vector<string>::const_iterator i = ext.begin(); i != ext.end(); ++i) {
+			if (path.substr(dot) == *i) {
+				r = unlink(path.c_str());
+				if (r != 0)
+					LOGINFO("Unable to unlink '%s: %s'\n", path.c_str(), strerror(errno));
+			}
+		}
+	}
+	closedir(d);
+}
+
+int TWPartitionManager::Check_Backup_Cancel() {
+	return stop_backup.get_value();
+}
+
+int TWPartitionManager::Cancel_Backup() {
+	string Backup_Folder, Backup_Name, Full_Backup_Path;
+
+	stop_backup.set_value(1);
+
+	if (tar_fork_pid != 0) {
+		DataManager::GetValue(TW_BACKUP_NAME, Backup_Name);
+		DataManager::GetValue(TW_BACKUPS_FOLDER_VAR, Backup_Folder);
+		Full_Backup_Path = Backup_Folder + "/" + Backup_Name;
+		LOGINFO("Killing pid: %d\n", tar_fork_pid);
+		kill(tar_fork_pid, SIGUSR2);
+		while (kill(tar_fork_pid, 0) == 0) {
+			usleep(1000);
+		}
+		LOGINFO("Backup_Run stopped and returning false, backup cancelled.\n");
+		LOGINFO("Removing directory %s\n", Full_Backup_Path.c_str());
+		TWFunc::removeDir(Full_Backup_Path, false);
+		tar_fork_pid = 0;
+	}
+
+	return 0;
+}
+
+int TWPartitionManager::Run_Backup(bool adbbackup) {
+	PartitionSettings part_settings;
+	int partition_count = 0, disable_free_space_check = 0, skip_digest = 0;
+	string Backup_Name, Backup_List, backup_path;
+	unsigned long long total_bytes = 0, free_space = 0;
+	TWPartition* storage = NULL;
+	time_t total_start, total_stop;
+	//time_t seconds = time(0);
+	//struct tm *t = localtime(&seconds);
+	size_t start_pos = 0, end_pos = 0;
+	stop_backup.set_value(0);
+
+	part_settings.img_bytes_remaining = 0;
+	part_settings.file_bytes_remaining = 0;
+	part_settings.img_time = 0;
+	part_settings.file_time = 0;
+	part_settings.img_bytes = 0;
+	part_settings.file_bytes = 0;
+	part_settings.PM_Method = PM_BACKUP;
+
+	part_settings.adbbackup = adbbackup;
+	time(&total_start);
+
+	Update_System_Details();
+
+	if (!Mount_Current_Storage(true))
+		return false;
+
+	DataManager::GetValue(TW_SKIP_DIGEST_GENERATE_VAR, skip_digest);
+	if (skip_digest == 0)
+		part_settings.generate_digest = true;
+	else
+		part_settings.generate_digest = false;
+
+	DataManager::GetValue(TW_BACKUPS_FOLDER_VAR, part_settings.Backup_Folder);
+	DataManager::GetValue(TW_BACKUP_NAME, Backup_Name);
+	if (Backup_Name == gui_lookup("curr_date", "(Current Date)")) {
+		Backup_Name = TWFunc::Get_Current_Date();
+	} else if (Backup_Name == gui_lookup("auto_generate", "(Auto Generate)") || Backup_Name == "0" || Backup_Name.empty()) {
+		TWFunc::Auto_Generate_Backup_Name();
+		DataManager::GetValue(TW_BACKUP_NAME, Backup_Name);
+	}
+
+	LOGINFO("Backup Name is: '%s'\n", Backup_Name.c_str());
+	part_settings.Backup_Folder = part_settings.Backup_Folder + "/" + Backup_Name;
+
+	LOGINFO("Backup_Folder is: '%s'\n", part_settings.Backup_Folder.c_str());
+
+	LOGINFO("Calculating backup details...\n");
+	DataManager::GetValue("tw_backup_list", Backup_List);
+	LOGINFO("Backup_List: %s\n", Backup_List.c_str());
+	if (!Backup_List.empty()) {
+		end_pos = Backup_List.find(";", start_pos);
+		while (end_pos != string::npos && start_pos < Backup_List.size()) {
+			backup_path = Backup_List.substr(start_pos, end_pos - start_pos);
+			LOGINFO("backup_path: %s\n", backup_path.c_str());
+			part_settings.Part = Find_Partition_By_Path(backup_path);
+			if (part_settings.Part != NULL) {
+				partition_count++;
+				if (part_settings.Part->Backup_Method == BM_FILES)
+					part_settings.file_bytes += part_settings.Part->Backup_Size;
+				else
+					part_settings.img_bytes += part_settings.Part->Backup_Size;
+				if (part_settings.Part->Has_SubPartition) {
+					std::vector<TWPartition*>::iterator subpart;
+
+					for (subpart = Partitions.begin(); subpart != Partitions.end(); subpart++) {
+						if ((*subpart)->Can_Be_Backed_Up && (*subpart)->Is_Present && (*subpart)->Is_SubPartition && (*subpart)->SubPartition_Of == part_settings.Part->Mount_Point) {
+							partition_count++;
+							if ((*subpart)->Backup_Method == BM_FILES)
+								part_settings.file_bytes += (*subpart)->Backup_Size;
+							else
+								part_settings.img_bytes += (*subpart)->Backup_Size;
+						}
+					}
+				}
+			} else {
+				gui_msg(Msg(msg::kError, "unable_to_locate_partition=Unable to locate '{1}' partition for backup calculations.")(backup_path));
+			}
+			start_pos = end_pos + 1;
+			end_pos = Backup_List.find(";", start_pos);
+		}
+	}
+
+	if (partition_count == 0) {
+		gui_msg("no_partition_selected=No partitions selected for backup.");
+		return false;
+	}
+	if (adbbackup) {
+		if (twadbbu::Write_ADB_Stream_Header(partition_count) == false) {
+			return false;
+		}
+	}
+	total_bytes = part_settings.file_bytes + part_settings.img_bytes;
+	ProgressTracking progress(total_bytes);
+	part_settings.progress = &progress;
+
+	gui_msg(Msg("total_partitions_backup= * Total number of partitions to back up: {1}")(partition_count));
+	gui_msg(Msg("total_backup_size= * Total size of all data: {1}MB")(total_bytes / 1024 / 1024));
+	storage = Find_Partition_By_Path(DataManager::GetCurrentStoragePath());
+	if (storage != NULL) {
+		free_space = storage->Free;
+		gui_msg(Msg("available_space= * Available space: {1}MB")(free_space / 1024 / 1024));
+	} else {
+		gui_err("unable_locate_storage=Unable to locate storage device.");
+		return false;
+	}
+
+	DataManager::GetValue(TW_DISABLE_FREE_SPACE_VAR, disable_free_space_check);
+
+	if (adbbackup)
+		disable_free_space_check = true;
+
+	if (!disable_free_space_check) {
+		if (free_space - (32 * 1024 * 1024) < total_bytes) {
+			// We require an extra 32MB just in case
+			gui_err("no_space=Not enough free space on storage.");
+			return false;
+		}
+	}
+	part_settings.img_bytes_remaining = part_settings.img_bytes;
+	part_settings.file_bytes_remaining = part_settings.file_bytes;
+
+	gui_msg("backup_started=[BACKUP STARTED]");
+
+	int is_decrypted = 0;
+	int is_encrypted = 0;
+
+	DataManager::GetValue(TW_IS_DECRYPTED, is_decrypted);
+	DataManager::GetValue(TW_IS_ENCRYPTED, is_encrypted);
+	if (!adbbackup || (!is_encrypted || (is_encrypted && is_decrypted))) {
+		gui_msg(Msg("backup_folder= * Backup Folder: {1}")(part_settings.Backup_Folder));
+		if (!TWFunc::Recursive_Mkdir(part_settings.Backup_Folder)) {
+			gui_err("fail_backup_folder=Failed to make backup folder.");
+			return false;
+		}
+	}
+
+	DataManager::SetProgress(0.0);
+
+	start_pos = 0;
+	end_pos = Backup_List.find(";", start_pos);
+	while (end_pos != string::npos && start_pos < Backup_List.size()) {
+		if (stop_backup.get_value() != 0)
+			return -1;
+		backup_path = Backup_List.substr(start_pos, end_pos - start_pos);
+		part_settings.Part = Find_Partition_By_Path(backup_path);
+		if (part_settings.Part != NULL) {
+			if (!Backup_Partition(&part_settings))
+				return false;
+		} else {
+			gui_msg(Msg(msg::kError, "unable_to_locate_partition=Unable to locate '{1}' partition for backup calculations.")(backup_path));
+		}
+		start_pos = end_pos + 1;
+		end_pos = Backup_List.find(";", start_pos);
+	}
+
+	// Average BPS
+	if (part_settings.img_time == 0)
+		part_settings.img_time = 1;
+	if (part_settings.file_time == 0)
+		part_settings.file_time = 1;
+	int img_bps = (int)part_settings.img_bytes / (int)part_settings.img_time;
+	unsigned long long file_bps = part_settings.file_bytes / (int)part_settings.file_time;
+
+	if (part_settings.file_bytes != 0)
+		gui_msg(Msg("avg_backup_fs=Average backup rate for file systems: {1} MB/sec")(file_bps / (1024 * 1024)));
+	if (part_settings.img_bytes != 0)
+		gui_msg(Msg("avg_backup_img=Average backup rate for imaged drives: {1} MB/sec")(img_bps / (1024 * 1024)));
+
+	time(&total_stop);
+	int total_time = (int) difftime(total_stop, total_start);
+
+	uint64_t actual_backup_size;
+	if (!adbbackup) {
+		TWExclude twe;
+		actual_backup_size = twe.Get_Folder_Size(part_settings.Backup_Folder);
+	} else
+		actual_backup_size = part_settings.file_bytes + part_settings.img_bytes;
+	actual_backup_size /= (1024LLU * 1024LLU);
+
+	int prev_img_bps = 0, use_compression = 0;
+	unsigned long long prev_file_bps = 0;
+	DataManager::GetValue(TW_BACKUP_AVG_IMG_RATE, prev_img_bps);
+	img_bps += (prev_img_bps * 4);
+	img_bps /= 5;
+
+	DataManager::GetValue(TW_USE_COMPRESSION_VAR, use_compression);
+	if (use_compression)
+		DataManager::GetValue(TW_BACKUP_AVG_FILE_COMP_RATE, prev_file_bps);
+	else
+		DataManager::GetValue(TW_BACKUP_AVG_FILE_RATE, prev_file_bps);
+	file_bps += (prev_file_bps * 4);
+	file_bps /= 5;
+
+	DataManager::SetValue(TW_BACKUP_AVG_IMG_RATE, img_bps);
+	if (use_compression)
+		DataManager::SetValue(TW_BACKUP_AVG_FILE_COMP_RATE, file_bps);
+	else
+		DataManager::SetValue(TW_BACKUP_AVG_FILE_RATE, file_bps);
+
+	gui_msg(Msg("total_backed_size=[{1} MB TOTAL BACKED UP]")(actual_backup_size));
+	Update_System_Details();
+	UnMount_Main_Partitions();
+	gui_msg(Msg(msg::kHighlight, "backup_completed=[BACKUP COMPLETED IN {1} SECONDS]")(total_time)); // the end
+	string backup_log = part_settings.Backup_Folder + "/recovery.log";
+	TWFunc::copy_file("/tmp/recovery.log", backup_log, 0644);
+	tw_set_default_metadata(backup_log.c_str());
+
+	if (part_settings.adbbackup) {
+		if (twadbbu::Write_ADB_Stream_Trailer() == false) {
+			return false;
+		}
+	}
+	part_settings.adbbackup = false;
+	DataManager::SetValue("tw_enable_adb_backup", 0);
+
+	return true;
+}
+
+bool TWPartitionManager::Restore_Partition(PartitionSettings *part_settings) {
+	time_t Start, Stop;
+
+	if (part_settings->adbbackup) {
+		std::string partName = part_settings->Part->Backup_Name + "." + part_settings->Part->Current_File_System + ".win";
+		LOGINFO("setting backup name: %s\n", partName.c_str());
+		part_settings->Part->Set_Backup_FileName(part_settings->Part->Backup_Name + "." + part_settings->Part->Current_File_System + ".win");
+	}
+
+	TWFunc::SetPerformanceMode(true);
+
+	time(&Start);
+
+	if (!part_settings->Part->Restore(part_settings)) {
+		TWFunc::SetPerformanceMode(false);
+		return false;
+	}
+	if (part_settings->Part->Has_SubPartition && !part_settings->adbbackup) {
+		std::vector<TWPartition*>::iterator subpart;
+		TWPartition *parentPart = part_settings->Part;
+
+		for (subpart = Partitions.begin(); subpart != Partitions.end(); subpart++) {
+			part_settings->Part = *subpart;
+			if ((*subpart)->Is_SubPartition && (*subpart)->SubPartition_Of == parentPart->Mount_Point) {
+				part_settings->Part = (*subpart);
+				part_settings->Part->Set_Backup_FileName(part_settings->Part->Backup_Name + "." + part_settings->Part->Current_File_System + ".win");
+				if (!(*subpart)->Restore(part_settings)) {
+					TWFunc::SetPerformanceMode(false);
+					return false;
+				}
+			}
+		}
+	}
+	time(&Stop);
+	TWFunc::SetPerformanceMode(false);
+	gui_msg(Msg("restore_part_done=[{1} done ({2} seconds)]")(part_settings->Part->Backup_Display_Name)((int)difftime(Stop, Start)));
+
+	return true;
+}
+
+int TWPartitionManager::Run_Restore(const string& Restore_Name) {
+	PartitionSettings part_settings;
+	int check_digest;
+
+	time_t rStart, rStop;
+	time(&rStart);
+	string Restore_List, restore_path;
+	size_t start_pos = 0, end_pos;
+
+	part_settings.Backup_Folder = Restore_Name;
+	part_settings.Part = NULL;
+	part_settings.partition_count = 0;
+	part_settings.total_restore_size = 0;
+	part_settings.adbbackup = false;
+	part_settings.PM_Method = PM_RESTORE;
+
+	gui_msg("restore_started=[RESTORE STARTED]");
+	gui_msg(Msg("restore_folder=Restore folder: '{1}'")(Restore_Name));
+
+	if (!Mount_Current_Storage(true))
+		return false;
+
+	DataManager::GetValue(TW_SKIP_DIGEST_CHECK_VAR, check_digest);
+	if (check_digest > 0) {
+		// Check Digest files first before restoring to ensure that all of them match before starting a restore
+		TWFunc::GUI_Operation_Text(TW_VERIFY_DIGEST_TEXT, gui_parse_text("{@verifying_digest}"));
+		gui_msg("verifying_digest=Verifying Digest");
+	} else {
+		gui_msg("skip_digest=Skipping Digest check based on user setting.");
+	}
+	gui_msg("calc_restore=Calculating restore details...");
+	DataManager::GetValue("tw_restore_selected", Restore_List);
+
+	if (!Restore_List.empty()) {
+		end_pos = Restore_List.find(";", start_pos);
+		while (end_pos != string::npos && start_pos < Restore_List.size()) {
+			restore_path = Restore_List.substr(start_pos, end_pos - start_pos);
+			part_settings.Part = Find_Partition_By_Path(restore_path);
+			if (part_settings.Part != NULL) {
+				if (part_settings.Part->Mount_Read_Only) {
+					gui_msg(Msg(msg::kError, "restore_read_only=Cannot restore {1} -- mounted read only.")(part_settings.Part->Backup_Display_Name));
+					return false;
+				}
+
+				string Full_Filename = part_settings.Backup_Folder + "/" + part_settings.Part->Backup_FileName;
+
+				if (tw_get_default_metadata(Get_Android_Root_Path().c_str()) != 0) {
+					gui_msg(Msg(msg::kWarning, "restore_system_context=Unable to get default context for {1} -- Android may not boot.")(Get_Android_Root_Path()));
+				}
+
+				if (check_digest > 0 && !twrpDigestDriver::Check_Digest(Full_Filename))
+					return false;
+				part_settings.partition_count++;
+				part_settings.total_restore_size += part_settings.Part->Get_Restore_Size(&part_settings);
+				if (part_settings.Part->Has_SubPartition) {
+					TWPartition *parentPart = part_settings.Part;
+					std::vector<TWPartition*>::iterator subpart;
+
+					for (subpart = Partitions.begin(); subpart != Partitions.end(); subpart++) {
+						part_settings.Part = *subpart;
+						if ((*subpart)->Is_SubPartition && (*subpart)->SubPartition_Of == parentPart->Mount_Point) {
+							if (check_digest > 0 && !twrpDigestDriver::Check_Digest(Full_Filename))
+								return false;
+							part_settings.total_restore_size += (*subpart)->Get_Restore_Size(&part_settings);
+						}
+					}
+				}
+			} else {
+				gui_msg(Msg(msg::kError, "restore_unable_locate=Unable to locate '{1}' partition for restoring.")(restore_path));
+			}
+			start_pos = end_pos + 1;
+			end_pos = Restore_List.find(";", start_pos);
+		}
+	}
+
+	if (part_settings.partition_count == 0) {
+		gui_err("no_part_restore=No partitions selected for restore.");
+		return false;
+	}
+
+	gui_msg(Msg("restore_part_count=Restoring {1} partitions...")(part_settings.partition_count));
+	gui_msg(Msg("total_restore_size=Total restore size is {1}MB")(part_settings.total_restore_size / 1048576));
+	DataManager::SetProgress(0.0);
+	ProgressTracking progress(part_settings.total_restore_size);
+	part_settings.progress = &progress;
+
+	start_pos = 0;
+	if (!Restore_List.empty()) {
+		end_pos = Restore_List.find(";", start_pos);
+		while (end_pos != string::npos && start_pos < Restore_List.size()) {
+			restore_path = Restore_List.substr(start_pos, end_pos - start_pos);
+
+			part_settings.Part = Find_Partition_By_Path(restore_path);
+			if (part_settings.Part != NULL) {
+				part_settings.partition_count++;
+				if (!Restore_Partition(&part_settings))
+					return false;
+			} else {
+				gui_msg(Msg(msg::kError, "restore_unable_locate=Unable to locate '{1}' partition for restoring.")(restore_path));
+			}
+			start_pos = end_pos + 1;
+			end_pos = Restore_List.find(";", start_pos);
+		}
+	}
+	TWFunc::GUI_Operation_Text(TW_UPDATE_SYSTEM_DETAILS_TEXT, gui_parse_text("{@updating_system_details}"));
+	tw_set_default_metadata(Get_Android_Root_Path().c_str());
+	UnMount_By_Path(Get_Android_Root_Path(), false);
+	Update_System_Details();
+	UnMount_Main_Partitions();
+	time(&rStop);
+	gui_msg(Msg(msg::kHighlight, "restore_completed=[RESTORE COMPLETED IN {1} SECONDS]")((int)difftime(rStop,rStart)));
+	TWPartition* Decrypt_Data = Find_Partition_By_Path("/data");
+	if (Decrypt_Data && Decrypt_Data->Is_Encrypted)
+		gui_msg(Msg(msg::kWarning, "reboot_after_restore=It is recommended to reboot Android once after first boot."));
+	DataManager::SetValue("tw_file_progress", "");
+
+	return true;
+}
+
+void TWPartitionManager::Set_Restore_Files(string Restore_Name) {
+	// Start with the default values
+	string Restore_List;
+	bool get_date = true, check_encryption = true;
+	bool adbbackup = false;
+
+	DataManager::SetValue("tw_restore_encrypted", 0);
+	if (twadbbu::Check_ADB_Backup_File(Restore_Name)) {
+		vector<string> adb_files;
+		adb_files = twadbbu::Get_ADB_Backup_Files(Restore_Name);
+		for (unsigned int i = 0; i < adb_files.size(); ++i) {
+			string adb_restore_file = adb_files.at(i);
+			std::size_t pos = adb_restore_file.find_first_of(".");
+			std::string path = "/" + adb_restore_file.substr(0, pos);
+			Restore_List = path + ";";
+			TWPartition* Part = Find_Partition_By_Path(path);
+			Part->Backup_FileName = TWFunc::Get_Filename(adb_restore_file);
+			adbbackup = true;
+		}
+		DataManager::SetValue("tw_enable_adb_backup", 1);
+	}
+	else {
+		DIR* d;
+		d = opendir(Restore_Name.c_str());
+		if (d == NULL)
+		{
+			gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(Restore_Name)(strerror(errno)));
+			return;
+		}
+
+		struct dirent* de;
+		while ((de = readdir(d)) != NULL)
+		{
+			// Strip off three components
+			char str[256];
+			char* label;
+			char* fstype = NULL;
+			char* extn = NULL;
+			char* ptr;
+
+			strcpy(str, de->d_name);
+			if (strlen(str) <= 2)
+				continue;
+
+			if (get_date) {
+				char file_path[255];
+				struct stat st;
+
+				strcpy(file_path, Restore_Name.c_str());
+				strcat(file_path, "/");
+				strcat(file_path, str);
+				stat(file_path, &st);
+				string backup_date = ctime((const time_t*)(&st.st_mtime));
+				DataManager::SetValue(TW_RESTORE_FILE_DATE, backup_date);
+				get_date = false;
+			}
+
+			label = str;
+			ptr = label;
+			while (*ptr && *ptr != '.')	 ptr++;
+			if (*ptr == '.')
+			{
+				*ptr = 0x00;
+				ptr++;
+				fstype = ptr;
+			}
+			while (*ptr && *ptr != '.')	 ptr++;
+			if (*ptr == '.')
+			{
+				*ptr = 0x00;
+				ptr++;
+				extn = ptr;
+			}
+
+			if (fstype == NULL || extn == NULL || strcmp(fstype, "log") == 0) continue;
+			int extnlength = strlen(extn);
+			if (extnlength != 3 && extnlength != 6) continue;
+			if (extnlength >= 3 && strncmp(extn, "win", 3) != 0) continue;
+			//if (extnlength == 6 && strncmp(extn, "win000", 6) != 0) continue;
+
+			if (check_encryption) {
+				string filename = Restore_Name + "/";
+				filename += de->d_name;
+				if (TWFunc::Get_File_Type(filename) == 2) {
+					LOGINFO("'%s' is encrypted\n", filename.c_str());
+					DataManager::SetValue("tw_restore_encrypted", 1);
+				}
+			}
+			if (extnlength == 6 && strncmp(extn, "win000", 6) != 0) continue;
+
+			TWPartition* Part = Find_Partition_By_Path(label);
+			if (Part == NULL)
+			{
+				gui_msg(Msg(msg::kError, "unable_locate_part_backup_name=Unable to locate partition by backup name: '{1}'")(label));
+				continue;
+			}
+
+			Part->Backup_FileName = de->d_name;
+			if (strlen(extn) > 3) {
+				Part->Backup_FileName.resize(Part->Backup_FileName.size() - strlen(extn) + 3);
+			}
+
+			if (!Part->Is_SubPartition) {
+				if (Part->Backup_Path == Get_Android_Root_Path())
+					Restore_List += "/system;";
+				else
+					Restore_List += Part->Backup_Path + ";";
+			}
+		}
+		closedir(d);
+	}
+
+	if (adbbackup) {
+		Restore_List = "ADB_Backup;";
+		adbbackup = false;
+	}
+
+	// Set the final value
+	DataManager::SetValue("tw_restore_list", Restore_List);
+	DataManager::SetValue("tw_restore_selected", Restore_List);
+	return;
+}
+
+int TWPartitionManager::Wipe_By_Path(string Path) {
+	std::vector<TWPartition*>::iterator iter;
+	std::vector < TWPartition * >::iterator iter1;
+	int ret = false;
+	bool found = false;
+	string Local_Path = TWFunc::Get_Root_Path(Path);
+
+	if (Local_Path == "/system")
+		Local_Path = Get_Android_Root_Path();
+	if (Path == "/cache") {
+		TWPartition* cache = Find_Partition_By_Path("/cache");
+		if (cache == nullptr) {
+			TWPartition* dat = Find_Partition_By_Path("/data");
+			if (dat) {
+				dat->Wipe_Data_Cache();
+				found = true;
+			}
+		}
+	}
+	// Iterate through all partitions
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Mount_Point == Local_Path || (!(*iter)->Symlink_Mount_Point.empty() && (*iter)->Symlink_Mount_Point == Local_Path)) {
+			// iterate through all partitions since some legacy devices uses other partitions as vendor causes issues while wiping
+			(*iter)->Find_Actual_Block_Device();
+			for (iter1 = Partitions.begin (); iter1 != Partitions.end (); iter1++)
+			{
+				(*iter1)->Find_Actual_Block_Device();
+				if ((*iter)->Actual_Block_Device == (*iter1)->Actual_Block_Device && (*iter)->Mount_Point != (*iter1)->Mount_Point)
+					(*iter1)->UnMount(false);
+			}
+			if (Path == "/and-sec")
+				ret = (*iter)->Wipe_AndSec();
+			else
+				ret = (*iter)->Wipe();
+			found = true;
+		} else if ((*iter)->Is_SubPartition && (*iter)->SubPartition_Of == Local_Path) {
+			(*iter)->Wipe();
+		}
+	}
+	if (found) {
+		return ret;
+	} else
+		gui_msg(Msg(msg::kError, "unable_find_part_path=Unable to find partition for path '{1}'")(Local_Path));
+	return false;
+}
+
+int TWPartitionManager::Wipe_By_Path(string Path, string New_File_System) {
+	std::vector<TWPartition*>::iterator iter;
+	int ret = false;
+	bool found = false;
+	string Local_Path = TWFunc::Get_Root_Path(Path);
+
+	// Iterate through all partitions
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Mount_Point == Local_Path || (!(*iter)->Symlink_Mount_Point.empty() && (*iter)->Symlink_Mount_Point == Local_Path)) {
+			if (Path == "/and-sec")
+				ret = (*iter)->Wipe_AndSec();
+			else
+				ret = (*iter)->Wipe(New_File_System);
+			found = true;
+		} else if ((*iter)->Is_SubPartition && (*iter)->SubPartition_Of == Local_Path) {
+			(*iter)->Wipe(New_File_System);
+		}
+	}
+	if (found) {
+		return ret;
+	} else
+		gui_msg(Msg(msg::kError, "unable_find_part_path=Unable to find partition for path '{1}'")(Local_Path));
+	return false;
+}
+
+int TWPartitionManager::Factory_Reset(void) {
+	std::vector<TWPartition*>::iterator iter;
+	int ret = true;
+
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Wipe_During_Factory_Reset && (*iter)->Is_Present) {
+#ifdef TW_OEM_BUILD
+			if ((*iter)->Mount_Point == "/data") {
+				if (!(*iter)->Wipe_Encryption())
+					ret = false;
+			} else {
+#endif
+				if (!(*iter)->Wipe())
+					ret = false;
+#ifdef TW_OEM_BUILD
+			}
+#endif
+		} else if ((*iter)->Has_Android_Secure) {
+			if (!(*iter)->Wipe_AndSec())
+				ret = false;
+		}
+	}
+	TWFunc::check_and_run_script("/system/bin/factoryreset.sh", "Factory Reset Script");
+	return ret;
+}
+
+int TWPartitionManager::Wipe_Dalvik_Cache(void) {
+	struct stat st;
+	vector <string> dir;
+
+	if (!Mount_By_Path("/data", true))
+		return false;
+
+	dir.push_back("/data/dalvik-cache");
+
+	std::string cacheDir = TWFunc::get_log_dir();
+	if (cacheDir == CACHE_LOGS_DIR) {
+		if (!PartitionManager.Mount_By_Path(CACHE_LOGS_DIR, false)) {
+			LOGINFO("Unable to mount %s for wiping cache.\n", CACHE_LOGS_DIR);
+		}
+		dir.push_back(cacheDir + "dalvik-cache");
+		dir.push_back(cacheDir + "/dc");
+	}
+
+	TWPartition* sdext = Find_Partition_By_Path("/sd-ext");
+	if (sdext && sdext->Is_Present && sdext->Mount(false))
+	{
+		if (stat("/sd-ext/dalvik-cache", &st) == 0)
+		{
+			dir.push_back("/sd-ext/dalvik-cache");
+		}
+	}
+
+	if (cacheDir == CACHE_LOGS_DIR) {
+		gui_msg("wiping_cache_dalvik=Wiping Dalvik Cache Directories...");
+	} else {
+		gui_msg("wiping_dalvik=Wiping Dalvik Directory...");
+	}
+	for (unsigned i = 0; i < dir.size(); ++i) {
+		if (stat(dir.at(i).c_str(), &st) == 0) {
+			TWFunc::removeDir(dir.at(i), false);
+			gui_msg(Msg("cleaned=Cleaned: {1}...")(dir.at(i)));
+		}
+	}
+
+	if (cacheDir == CACHE_LOGS_DIR) {
+		gui_msg("cache_dalvik_done=-- Dalvik Cache Directories Wipe Complete!");
+	} else {
+		gui_msg("dalvik_done=-- Dalvik Directory Wipe Complete!");
+	}
+
+	return true;
+}
+
+int TWPartitionManager::Wipe_Rotate_Data(void) {
+	if (!Mount_By_Path("/data", true))
+		return false;
+
+	unlink("/data/misc/akmd*");
+	unlink("/data/misc/rild*");
+	gui_print("Rotation data wiped.\n");
+	return true;
+}
+
+int TWPartitionManager::Wipe_Battery_Stats(void) {
+	struct stat st;
+
+	if (!Mount_By_Path("/data", true))
+		return false;
+
+	if (0 != stat("/data/system/batterystats.bin", &st)) {
+		gui_print("No Battery Stats Found. No Need To Wipe.\n");
+	} else {
+		remove("/data/system/batterystats.bin");
+		gui_print("Cleared battery stats.\n");
+	}
+	return true;
+}
+
+int TWPartitionManager::Wipe_Android_Secure(void) {
+	std::vector<TWPartition*>::iterator iter;
+	int ret = false;
+	bool found = false;
+
+	// Iterate through all partitions
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Has_Android_Secure) {
+			ret = (*iter)->Wipe_AndSec();
+			found = true;
+		}
+	}
+	if (found) {
+		return ret;
+	} else {
+		gui_err("no_andsec=No android secure partitions found.");
+	}
+	return false;
+}
+
+int TWPartitionManager::Format_Data(void) {
+	TWPartition* dat = Find_Partition_By_Path("/data");
+	TWPartition* metadata = Find_Partition_By_Path("/metadata");
+	if (metadata != NULL)
+		metadata->UnMount(false);
+
+	if (dat != NULL) {
+		if (android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) {
+#ifndef TW_EXCLUDE_APEX
+			twrpApex apex;
+			apex.Unmount();
+#endif
+			if (metadata != NULL)
+				metadata->Mount(true);
+			if (!Check_Pending_Merges())
+				return false;
+		}
+		return dat->Wipe_Encryption();
+	} else {
+		gui_msg(Msg(msg::kError, "unable_to_locate=Unable to locate {1}.")("/data"));
+		return false;
+	}
+	return false;
+}
+
+int TWPartitionManager::Wipe_Media_From_Data(void) {
+	TWPartition* dat = Find_Partition_By_Path("/data");
+
+	if (dat != NULL) {
+		if (!dat->Has_Data_Media) {
+			LOGERR("This device does not have /data/media\n");
+			return false;
+		}
+		if (!dat->Mount(true))
+			return false;
+
+		gui_msg("wiping_datamedia=Wiping internal storage -- /data/media...");
+		Remove_MTP_Storage(dat->MTP_Storage_ID);
+		TWFunc::removeDir("/data/media", false);
+		dat->Recreate_Media_Folder();
+		Add_MTP_Storage(dat->MTP_Storage_ID);
+		return true;
+	} else {
+		gui_msg(Msg(msg::kError, "unable_to_locate=Unable to locate {1}.")("/data"));
+		return false;
+	}
+	return false;
+}
+
+int TWPartitionManager::Repair_By_Path(string Path, bool Display_Error) {
+	std::vector<TWPartition*>::iterator iter;
+	int ret = false;
+	bool found = false;
+	string Local_Path = TWFunc::Get_Root_Path(Path);
+
+	if (Local_Path == "/tmp" || Local_Path == "/")
+		return true;
+
+	// Iterate through all partitions
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Mount_Point == Local_Path || (!(*iter)->Symlink_Mount_Point.empty() && (*iter)->Symlink_Mount_Point == Local_Path)) {
+			ret = (*iter)->Repair();
+			found = true;
+		} else if ((*iter)->Is_SubPartition && (*iter)->SubPartition_Of == Local_Path) {
+			(*iter)->Repair();
+		}
+	}
+	if (found) {
+		return ret;
+	} else if (Display_Error) {
+		gui_msg(Msg(msg::kError, "unable_find_part_path=Unable to find partition for path '{1}'")(Local_Path));
+	} else {
+		LOGINFO("Repair: Unable to find partition for path '%s'\n", Local_Path.c_str());
+	}
+	return false;
+}
+
+int TWPartitionManager::Resize_By_Path(string Path, bool Display_Error) {
+	std::vector<TWPartition*>::iterator iter;
+	int ret = false;
+	bool found = false;
+	string Local_Path = TWFunc::Get_Root_Path(Path);
+
+	if (Local_Path == "/tmp" || Local_Path == "/")
+		return true;
+
+	// Iterate through all partitions
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Mount_Point == Local_Path || (!(*iter)->Symlink_Mount_Point.empty() && (*iter)->Symlink_Mount_Point == Local_Path)) {
+			ret = (*iter)->Resize();
+			found = true;
+		} else if ((*iter)->Is_SubPartition && (*iter)->SubPartition_Of == Local_Path) {
+			(*iter)->Resize();
+		}
+	}
+	if (found) {
+		return ret;
+	} else if (Display_Error) {
+		gui_msg(Msg(msg::kError, "unable_find_part_path=Unable to find partition for path '{1}'")(Local_Path));
+	} else {
+		LOGINFO("Resize: Unable to find partition for path '%s'\n", Local_Path.c_str());
+	}
+	return false;
+}
+
+void TWPartitionManager::Update_System_Details(void) {
+	std::vector<TWPartition*>::iterator iter;
+	int data_size = 0;
+
+	gui_msg("update_part_details=Updating partition details...");
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		(*iter)->Update_Size(true);
+		if ((*iter)->Can_Be_Mounted) {
+			if ((*iter)->Mount_Point == Get_Android_Root_Path()) {
+				int backup_display_size = (int)((*iter)->Backup_Size / 1048576LLU);
+				DataManager::SetValue(TW_BACKUP_SYSTEM_SIZE, backup_display_size);
+				TWFunc::Is_TWRP_App_In_System();
+			} else if ((*iter)->Mount_Point == "/data" || (*iter)->Mount_Point == "/datadata") {
+				data_size += (int)((*iter)->Backup_Size / 1048576LLU);
+			} else if ((*iter)->Mount_Point == "/cache") {
+				int backup_display_size = (int)((*iter)->Backup_Size / 1048576LLU);
+				DataManager::SetValue(TW_BACKUP_CACHE_SIZE, backup_display_size);
+			} else if ((*iter)->Mount_Point == "/sd-ext") {
+				int backup_display_size = (int)((*iter)->Backup_Size / 1048576LLU);
+				DataManager::SetValue(TW_BACKUP_SDEXT_SIZE, backup_display_size);
+				if ((*iter)->Backup_Size == 0) {
+					DataManager::SetValue(TW_HAS_SDEXT_PARTITION, 0);
+					DataManager::SetValue(TW_BACKUP_SDEXT_VAR, 0);
+				} else
+					DataManager::SetValue(TW_HAS_SDEXT_PARTITION, 1);
+			} else if ((*iter)->Has_Android_Secure) {
+				int backup_display_size = (int)((*iter)->Backup_Size / 1048576LLU);
+				DataManager::SetValue(TW_BACKUP_ANDSEC_SIZE, backup_display_size);
+				if ((*iter)->Backup_Size == 0) {
+					DataManager::SetValue(TW_HAS_ANDROID_SECURE, 0);
+					DataManager::SetValue(TW_BACKUP_ANDSEC_VAR, 0);
+				} else
+					DataManager::SetValue(TW_HAS_ANDROID_SECURE, 1);
+			} else if ((*iter)->Mount_Point == "/boot") {
+				int backup_display_size = (int)((*iter)->Backup_Size / 1048576LLU);
+				DataManager::SetValue(TW_BACKUP_BOOT_SIZE, backup_display_size);
+				if ((*iter)->Backup_Size == 0) {
+					DataManager::SetValue("tw_has_boot_partition", 0);
+					DataManager::SetValue(TW_BACKUP_BOOT_VAR, 0);
+				} else
+					DataManager::SetValue("tw_has_boot_partition", 1);
+			}
+		} else {
+			// Handle unmountable partitions in case we reset defaults
+			if ((*iter)->Mount_Point == "/boot") {
+				int backup_display_size = (int)((*iter)->Backup_Size / 1048576LLU);
+				DataManager::SetValue(TW_BACKUP_BOOT_SIZE, backup_display_size);
+				if ((*iter)->Backup_Size == 0) {
+					DataManager::SetValue(TW_HAS_BOOT_PARTITION, 0);
+					DataManager::SetValue(TW_BACKUP_BOOT_VAR, 0);
+				} else
+					DataManager::SetValue(TW_HAS_BOOT_PARTITION, 1);
+			} else if ((*iter)->Mount_Point == "/recovery") {
+				int backup_display_size = (int)((*iter)->Backup_Size / 1048576LLU);
+				DataManager::SetValue(TW_BACKUP_RECOVERY_SIZE, backup_display_size);
+				if ((*iter)->Backup_Size == 0) {
+					DataManager::SetValue(TW_HAS_RECOVERY_PARTITION, 0);
+					DataManager::SetValue(TW_BACKUP_RECOVERY_VAR, 0);
+				} else
+					DataManager::SetValue(TW_HAS_RECOVERY_PARTITION, 1);
+			} else if ((*iter)->Mount_Point == "/data") {
+				data_size += (int)((*iter)->Backup_Size / 1048576LLU);
+			}
+		}
+	}
+	gui_msg("update_part_details_done=...done");
+	DataManager::SetValue(TW_BACKUP_DATA_SIZE, data_size);
+	string current_storage_path = DataManager::GetCurrentStoragePath();
+	TWPartition* FreeStorage = Find_Partition_By_Path(current_storage_path);
+	if (FreeStorage != NULL) {
+		// Attempt to mount storage
+		if (!FreeStorage->Mount(false)) {
+			gui_msg(Msg(msg::kWarning, "unable_to_mount_storage=Unable to mount storage"));
+			DataManager::SetValue(TW_STORAGE_FREE_SIZE, 0);
+		} else {
+			DataManager::SetValue(TW_STORAGE_FREE_SIZE, (int)(FreeStorage->Free / 1048576LLU));
+		}
+	} else {
+		LOGINFO("Unable to find storage partition '%s'.\n", current_storage_path.c_str());
+	}
+	if (!Write_Fstab())
+		LOGERR("Error creating fstab\n");
+	return;
+}
+
+void TWPartitionManager::Post_Decrypt(const string& Block_Device) {
+	TWPartition* dat = Find_Partition_By_Path("/data");
+
+	if (dat != NULL) {
+		// reparse for /cache/recovery/command
+		static constexpr const char* COMMAND_FILE = "/data/cache/command";
+		if (TWFunc::Path_Exists(COMMAND_FILE)) {
+			startupArgs startup;
+			std::string content;
+			TWFunc::read_file(COMMAND_FILE, content);
+			std::vector<std::string> args = {content};
+			startup.processRecoveryArgs(args, 0);
+		}
+
+		DataManager::SetValue(TW_IS_DECRYPTED, 1);
+		dat->Is_Decrypted = true;
+		if (!Block_Device.empty()) {
+			dat->Decrypted_Block_Device = Block_Device;
+			gui_msg(Msg("decrypt_success_dev=Data successfully decrypted, new block device: '{1}'")(Block_Device));
+		} else {
+			gui_msg("decrypt_success_nodev=Data successfully decrypted");
+		}
+		property_set("twrp.decrypt.done", "true");
+		dat->Setup_File_System(false);
+		dat->Current_File_System = dat->Fstab_File_System;  // Needed if we're ignoring blkid because encrypted devices start out as emmc
+
+		sleep(1); // Sleep for a bit so that the device will be ready
+
+		// Mount only /data
+		dat->Symlink_Path = ""; // Not to let it to bind mount /data/media again
+		if (!dat->Mount(false)) {
+			LOGERR("Unable to mount /data after decryption");
+		}
+
+		if (dat->Has_Data_Media && TWFunc::Path_Exists("/data/media/0")) {
+			dat->Storage_Path = "/data/media/0";
+		} else {
+			dat->Storage_Path = "/data/media";
+		}
+		dat->Symlink_Path = dat->Storage_Path;
+		DataManager::SetValue("tw_storage_path", dat->Symlink_Path);
+		DataManager::SetValue("tw_settings_path", dat->Symlink_Path);
+		LOGINFO("New storage path after decryption: %s\n", dat->Storage_Path.c_str());
+
+		DataManager::LoadTWRPFolderInfo();
+		Update_System_Details();
+		Output_Partition(dat);
+		if (!android::base::StartsWith(dat->Actual_Block_Device, "/dev/block/mmcblk")) {
+			if (!dat->Bind_Mount(false))
+				LOGERR("Unable to bind mount /sdcard to %s\n", dat->Storage_Path.c_str());
+		}
+	} else
+		LOGERR("Unable to locate data partition.\n");
+}
+
+void TWPartitionManager::Parse_Users() {
+#ifdef TW_INCLUDE_FBE
+	char user_check_result[PROPERTY_VALUE_MAX];
+	for (int userId = 0; userId <= 9999; userId++) {
+		string prop = "twrp.user." + to_string(userId) + ".decrypt";
+		property_get(prop.c_str(), user_check_result, "-1");
+		if (strcmp(user_check_result, "-1") != 0) {
+			if (userId < 0 || userId > 9999) {
+				LOGINFO("Incorrect user id %d\n", userId);
+				continue;
+			}
+			struct users_struct user;
+			user.userId = to_string(userId);
+
+			// Attempt to get name of user. Fallback to user ID if this fails.
+			std::string path = "/data/system/users/" + to_string(userId) + ".xml";
+			if (!TWFunc::Check_Xml_Format(path)) {
+				string oldpath = path;
+				if (TWFunc::abx_to_xml(oldpath, path)) {
+					LOGINFO("Android 12+: '%s' has been converted into plain text xml (for user %s).\n", oldpath.c_str(), user.userId.c_str());
+				}
+			}
+			char* userFile = PageManager::LoadFileToBuffer(path, NULL);
+			if (userFile == NULL) {
+				user.userName = to_string(userId);
+			}
+			else {
+				xml_document<> *userXml = new xml_document<>();
+				userXml->parse<0>(userFile);
+				xml_node<>* userNode = userXml->first_node("user");
+				if (userNode == nullptr) {
+					user.userName = to_string(userId);
+				} else {
+					xml_node<>* nameNode = userNode->first_node("name");
+					if (nameNode == nullptr)
+						user.userName = to_string(userId);
+					else {
+						string userName = nameNode->value();
+						user.userName = userName + " (" + to_string(userId) + ")";
+					}
+				}
+			}
+
+			string filename;
+			user.type = android::keystore::Get_Password_Type(userId, filename);
+
+			user.isDecrypted = false;
+			if (strcmp(user_check_result, "1") == 0)
+				user.isDecrypted = true;
+			Users_List.push_back(user);
+		}
+	}
+	Check_Users_Decryption_Status();
+#endif
+}
+
+std::vector<users_struct>* TWPartitionManager::Get_Users_List() {
+	return &Users_List;
+}
+
+void TWPartitionManager::Mark_User_Decrypted(int userID) {
+#ifdef TW_INCLUDE_FBE
+	std::vector<users_struct>::iterator iter;
+	for (iter = Users_List.begin(); iter != Users_List.end(); iter++) {
+		if (atoi((*iter).userId.c_str()) == userID) {
+			(*iter).isDecrypted = true;
+			string user_prop_decrypted = "twrp.user." + to_string(userID) + ".decrypt";
+			property_set(user_prop_decrypted.c_str(), "1");
+			break;
+		}
+	}
+	Check_Users_Decryption_Status();
+#endif
+}
+
+void TWPartitionManager::Check_Users_Decryption_Status() {
+#ifdef TW_INCLUDE_FBE
+	int all_is_decrypted = 1;
+	std::vector<users_struct>::iterator iter;
+	for (iter = Users_List.begin(); iter != Users_List.end(); iter++) {
+		if (!(*iter).isDecrypted) {
+			LOGINFO("User %s is not decrypted.\n", (*iter).userId.c_str());
+			all_is_decrypted = 0;
+			break;
+		}
+	}
+	if (all_is_decrypted == 1) {
+		LOGINFO("All found users are decrypted.\n");
+		DataManager::SetValue("tw_all_users_decrypted", "1");
+		property_set("twrp.all.users.decrypted", "true");
+	} else
+		DataManager::SetValue("tw_all_users_decrypted", "0");
+#endif
+}
+
+int TWPartitionManager::Decrypt_Device(string Password, int user_id) {
+#ifdef TW_INCLUDE_CRYPTO
+	char crypto_blkdev[PROPERTY_VALUE_MAX];
+	std::vector<TWPartition*>::iterator iter;
+
+	// Mount any partitions that need to be mounted for decrypt
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Mount_To_Decrypt) {
+			(*iter)->Mount(true);
+		}
+	}
+	property_set("twrp.mount_to_decrypt", "1");
+
+	Set_Crypto_State();
+	Set_Crypto_Type("block");
+
+	if (DataManager::GetIntValue(TW_IS_FBE)) {
+#ifdef TW_INCLUDE_FBE
+		if (!Mount_By_Path("/data", true)) // /data has to be mounted for FBE
+			return -1;
+
+		bool user_need_decrypt = false;
+		std::vector<users_struct>::iterator iter;
+		for (iter = Users_List.begin(); iter != Users_List.end(); iter++) {
+			if (atoi((*iter).userId.c_str()) == user_id && !(*iter).isDecrypted) {
+				user_need_decrypt = true;
+			}
+		}
+		if (!user_need_decrypt) {
+			LOGINFO("User %d does not require decryption\n", user_id);
+			return 0;
+		}
+
+		int retry_count = 10;
+		while (!TWFunc::Path_Exists("/data/system/users/gatekeeper.password.key") && --retry_count)
+			usleep(2000); // A small sleep is needed after mounting /data to ensure reliable decrypt...maybe because of DE?
+		gui_msg(Msg("decrypting_user_fbe=Attempting to decrypt FBE for user {1}...")(user_id));
+		if (android::keystore::Decrypt_User(user_id, Password)) {
+			gui_msg(Msg("decrypt_user_success_fbe=User {1} Decrypted Successfully")(user_id));
+			Mark_User_Decrypted(user_id);
+			if (user_id == 0) {
+				// When decrypting user 0 also try all other users
+				std::vector<users_struct>::iterator iter;
+				for (iter = Users_List.begin(); iter != Users_List.end(); iter++) {
+					if ((*iter).userId == "0" || (*iter).isDecrypted)
+						continue;
+
+					int tmp_user_id = atoi((*iter).userId.c_str());
+					gui_msg(Msg("decrypting_user_fbe=Attempting to decrypt FBE for user {1}...")(tmp_user_id));
+					if (android::keystore::Decrypt_User(tmp_user_id, Password) ||
+					(Password != "!" && android::keystore::Decrypt_User(tmp_user_id, "!"))) { // "!" means default password
+						gui_msg(Msg("decrypt_user_success_fbe=User {1} Decrypted Successfully")(tmp_user_id));
+						Mark_User_Decrypted(tmp_user_id);
+					} else {
+						gui_msg(Msg("decrypt_user_fail_fbe=Failed to decrypt user {1}")(tmp_user_id));
+					}
+				}
+				Post_Decrypt("");
+			}
+
+			return 0;
+		} else {
+			gui_msg(Msg(msg::kError, "decrypt_user_fail_fbe=Failed to decrypt user {1}")(user_id));
+		}
+#else
+		LOGERR("FBE support is not present\n");
+#endif
+		return -1;
+	}
+
+	char isdecrypteddata[PROPERTY_VALUE_MAX];
+	property_get("twrp.decrypt.done", isdecrypteddata, "");
+	if (strcmp(isdecrypteddata, "true") == 0) {
+		LOGINFO("Data has no decryption required\n");
+		return 0;
+	}
+
+	int pwret = -1;
+	pid_t pid = fork();
+	if (pid < 0) {
+		LOGERR("fork failed\n");
+		return -1;
+	} else if (pid == 0) {
+		// Child process
+		char cPassword[255];
+		strcpy(cPassword, Password.c_str());
+		//int ret = cryptfs_check_passwd(cPassword);
+		exit(0);
+	} else {
+		// Parent
+		int status;
+		if (TWFunc::Wait_For_Child_Timeout(pid, &status, "Decrypt", 30))
+			pwret = -1;
+		else
+			pwret = WEXITSTATUS(status) ? -1 : 0;
+	}
+
+#ifdef TW_CRYPTO_USE_SYSTEM_VOLD
+	if (pwret != 0) {
+		pwret = vold_decrypt(Password);
+		switch (pwret) {
+			case VD_SUCCESS:
+				break;
+			case VD_ERR_MISSING_VDC:
+				gui_msg(Msg(msg::kError, "decrypt_data_vold_os_missing=Missing files needed for vold decrypt: {1}")("/system/bin/vdc"));
+				break;
+			case VD_ERR_MISSING_VOLD:
+				gui_msg(Msg(msg::kError, "decrypt_data_vold_os_missing=Missing files needed for vold decrypt: {1}")("/system/bin/vold"));
+				break;
+		}
+	}
+#endif // TW_CRYPTO_USE_SYSTEM_VOLD
+
+	// Unmount any partitions that were needed for decrypt
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Mount_To_Decrypt) {
+			(*iter)->UnMount(false);
+		}
+	}
+	property_set("twrp.mount_to_decrypt", "0");
+
+	if (pwret != 0) {
+		gui_err("fail_decrypt=Failed to decrypt data.");
+		return -1;
+	}
+
+	property_get("ro.crypto.fs_crypto_blkdev", crypto_blkdev, "error");
+	if (strcmp(crypto_blkdev, "error") == 0) {
+		LOGERR("Error retrieving decrypted data block device.\n");
+	} else {
+		Post_Decrypt(crypto_blkdev);
+	}
+	return 0;
+#else
+	gui_err("no_crypto_support=No crypto support was compiled into this build.");
+	return -1;
+#endif
+	return 1;
+}
+
+int TWPartitionManager::Fix_Contexts(void) {
+	std::vector<TWPartition*>::iterator iter;
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Has_Data_Media) {
+			if ((*iter)->Mount(true)) {
+				if (fixContexts::fixDataMediaContexts((*iter)->Mount_Point) != 0)
+					return -1;
+			}
+		}
+	}
+	UnMount_Main_Partitions();
+	gui_msg("done=Done.");
+	return 0;
+}
+
+TWPartition* TWPartitionManager::Find_Next_Storage(string Path, bool Exclude_Data_Media) {
+	std::vector<TWPartition*>::iterator iter = Partitions.begin();
+
+	if (!Path.empty()) {
+		string Search_Path = TWFunc::Get_Root_Path(Path);
+		for (; iter != Partitions.end(); iter++) {
+			if ((*iter)->Mount_Point == Search_Path) {
+				iter++;
+				break;
+			}
+		}
+	}
+
+	for (; iter != Partitions.end(); iter++) {
+		if (Exclude_Data_Media && (*iter)->Has_Data_Media) {
+			// do nothing, do not return this type of partition
+		} else if ((*iter)->Is_Storage && (*iter)->Is_Present) {
+			return (*iter);
+		}
+	}
+
+	return NULL;
+}
+
+int TWPartitionManager::Open_Lun_File(string Partition_Path, string Lun_File) {
+	TWPartition* Part = Find_Partition_By_Path(Partition_Path);
+
+	if (Part == NULL) {
+		LOGINFO("Unable to locate '%s' for USB storage mode.", Partition_Path.c_str());
+		gui_msg(Msg(msg::kError, "unable_find_part_path=Unable to find partition for path '{1}'")(Partition_Path));
+		return false;
+	}
+	LOGINFO("USB mount '%s', '%s' > '%s'\n", Partition_Path.c_str(), Part->Actual_Block_Device.c_str(), Lun_File.c_str());
+	if (!Part->UnMount(true) || !Part->Is_Present)
+		return false;
+
+	if (!TWFunc::write_to_file(Lun_File, Part->Actual_Block_Device)) {
+		LOGERR("Unable to write to ums lunfile '%s': (%s)\n", Lun_File.c_str(), strerror(errno));
+		return false;
+	}
+	return true;
+}
+
+int TWPartitionManager::usb_storage_enable(void) {
+	char lun_file[255];
+	bool has_multiple_lun = false;
+
+	string Lun_File_str = CUSTOM_LUN_FILE;
+	size_t found = Lun_File_str.find("%");
+	if (found != string::npos) {
+		sprintf(lun_file, CUSTOM_LUN_FILE, 1);
+		if (TWFunc::Path_Exists(lun_file))
+			has_multiple_lun = true;
+	}
+	mtp_was_enabled = TWFunc::Toggle_MTP(false); // Must disable MTP for USB Storage
+	if (!has_multiple_lun) {
+		LOGINFO("Device doesn't have multiple lun files, mount current storage\n");
+		sprintf(lun_file, CUSTOM_LUN_FILE, 0);
+		if (TWFunc::Get_Root_Path(DataManager::GetCurrentStoragePath()) == "/data") {
+			TWPartition* Mount = Find_Next_Storage("", true);
+			if (Mount) {
+				if (!Open_Lun_File(Mount->Mount_Point, lun_file)) {
+					goto error_handle;
+				}
+			} else {
+				gui_err("unable_locate_storage=Unable to locate storage device.");
+				goto error_handle;
+			}
+		} else if (!Open_Lun_File(DataManager::GetCurrentStoragePath(), lun_file)) {
+			goto error_handle;
+		}
+	} else {
+		LOGINFO("Device has multiple lun files\n");
+		TWPartition* Mount1;
+		TWPartition* Mount2;
+		sprintf(lun_file, CUSTOM_LUN_FILE, 0);
+		Mount1 = Find_Next_Storage("", true);
+		if (Mount1) {
+			if (!Open_Lun_File(Mount1->Mount_Point, lun_file)) {
+				goto error_handle;
+			}
+			sprintf(lun_file, CUSTOM_LUN_FILE, 1);
+			Mount2 = Find_Next_Storage(Mount1->Mount_Point, true);
+			if (Mount2 && Mount2->Mount_Point != Mount1->Mount_Point) {
+				Open_Lun_File(Mount2->Mount_Point, lun_file);
+			// Mimic single lun code: Mount CurrentStoragePath if it's not /data
+			} else if (TWFunc::Get_Root_Path(DataManager::GetCurrentStoragePath()) != "/data") {
+				Open_Lun_File(DataManager::GetCurrentStoragePath(), lun_file);
+			}
+		// Mimic single lun code: Mount CurrentStoragePath if it's not /data
+		} else if (TWFunc::Get_Root_Path(DataManager::GetCurrentStoragePath()) != "/data" && !Open_Lun_File(DataManager::GetCurrentStoragePath(), lun_file)) {
+			gui_err("unable_locate_storage=Unable to locate storage device.");
+			goto error_handle;
+		}
+	}
+	property_set("sys.storage.ums_enabled", "1");
+	property_set("sys.usb.config", "mass_storage,adb");
+	return true;
+error_handle:
+	if (mtp_was_enabled)
+		if (!Enable_MTP())
+			Disable_MTP();
+	return false;
+}
+
+int TWPartitionManager::usb_storage_disable(void) {
+	int index, ret = 0;
+	char lun_file[255], ch[2] = {0, 0};
+	string str = ch;
+
+	for (index=0; index<2; index++) {
+		sprintf(lun_file, CUSTOM_LUN_FILE, index);
+		if (!TWFunc::write_to_file(lun_file, str)) {
+			break;
+			ret = -1;
+		}
+	}
+	Mount_All_Storage();
+	Update_System_Details();
+	UnMount_Main_Partitions();
+	property_set("sys.storage.ums_enabled", "0");
+	property_set("sys.usb.config", "adb");
+	if (mtp_was_enabled)
+		if (!Enable_MTP())
+			Disable_MTP();
+	if (ret < 0 && index == 0) {
+		LOGERR("Unable to write to ums lunfile '%s'.", lun_file);
+		return false;
+	} else {
+		return true;
+	}
+	return true;
+}
+
+void TWPartitionManager::Mount_All_Storage(void) {
+	std::vector<TWPartition*>::iterator iter;
+
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Is_Storage)
+			(*iter)->Mount(false);
+	}
+}
+
+void TWPartitionManager::UnMount_Main_Partitions(void) {
+	// Unmounts system and data if data is not data/media
+	// Also unmounts boot if boot is mountable
+	LOGINFO("Unmounting main partitions...\n");
+
+	TWPartition *Partition = Find_Partition_By_Path ("/vendor");
+
+	if (Partition != NULL) UnMount_By_Path("/vendor", false);
+	UnMount_By_Path (Get_Android_Root_Path(), true);
+	Partition = Find_Partition_By_Path ("/product");
+	if (Partition != NULL) UnMount_By_Path("/product", false);
+	if (!datamedia)
+		UnMount_By_Path("/data", true);
+
+	Partition = Find_Partition_By_Path ("/boot");
+	if (Partition != NULL && Partition->Can_Be_Mounted)
+		Partition->UnMount(true);
+}
+
+int TWPartitionManager::Partition_SDCard(void) {
+	char temp[255];
+	string Storage_Path, Command, Device, fat_str, ext_str, start_loc, end_loc, ext_format, sd_path, tmpdevice;
+	int ext, swap, total_size = 0, fat_size;
+
+	gui_msg("start_partition_sd=Partitioning SD Card...");
+
+	// Locate and validate device to partition
+	TWPartition* SDCard = Find_Partition_By_Path(DataManager::GetCurrentStoragePath());
+
+	if (SDCard->Is_Adopted_Storage)
+		SDCard->Revert_Adopted();
+
+	if (SDCard == NULL || !SDCard->Removable || SDCard->Has_Data_Media) {
+		gui_err("partition_sd_locate=Unable to locate device to partition.");
+		return false;
+	}
+
+	// Unmount everything
+	if (!SDCard->UnMount(true))
+		return false;
+	TWPartition* SDext = Find_Partition_By_Path("/sd-ext");
+	if (SDext != NULL) {
+		if (!SDext->UnMount(true))
+			return false;
+	}
+	char* swappath = getenv("SWAPPATH");
+	if (swappath != NULL) {
+		LOGINFO("Unmounting swap at '%s'\n", swappath);
+		umount(swappath);
+	}
+
+	// Determine block device
+	if (SDCard->Alternate_Block_Device.empty()) {
+		SDCard->Find_Actual_Block_Device();
+		Device = SDCard->Actual_Block_Device;
+		// Just use the root block device
+		Device.resize(strlen("/dev/block/mmcblkX"));
+	} else {
+		Device = SDCard->Alternate_Block_Device;
+	}
+
+	// Find the size of the block device:
+	total_size = (int)(TWFunc::IOCTL_Get_Block_Size(Device.c_str()) / (1048576));
+
+	DataManager::GetValue("tw_sdext_size", ext);
+	DataManager::GetValue("tw_swap_size", swap);
+	DataManager::GetValue("tw_sdpart_file_system", ext_format);
+	fat_size = total_size - ext - swap;
+	LOGINFO("sd card mount point %s block device is '%s', sdcard size is: %iMB, fat size: %iMB, ext size: %iMB, ext system: '%s', swap size: %iMB\n", DataManager::GetCurrentStoragePath().c_str(), Device.c_str(), total_size, fat_size, ext, ext_format.c_str(), swap);
+
+	// Determine partition sizes
+	if (swap == 0 && ext == 0) {
+		fat_str = "-0";
+	} else {
+		memset(temp, 0, sizeof(temp));
+		sprintf(temp, "%i", fat_size);
+		fat_str = temp;
+		fat_str += "MB";
+	}
+	if (swap == 0) {
+		ext_str = "-0";
+	} else {
+		memset(temp, 0, sizeof(temp));
+		sprintf(temp, "%i", ext);
+		ext_str = "+";
+		ext_str += temp;
+		ext_str += "MB";
+	}
+
+	if (ext + swap > total_size) {
+		gui_err("ext_swap_size=EXT + Swap size is larger than sdcard size.");
+		return false;
+	}
+
+	gui_msg("remove_part_table=Removing partition table...");
+	Command = "sgdisk --zap-all " + Device;
+	LOGINFO("Command is: '%s'\n", Command.c_str());
+	if (TWFunc::Exec_Cmd(Command) != 0) {
+		gui_err("unable_rm_part=Unable to remove partition table.");
+		Update_System_Details();
+		return false;
+	}
+	gui_msg(Msg("create_part=Creating {1} partition...")("FAT32"));
+	Command = "sgdisk  --new=0:0:" + fat_str + " --change-name=0:\"Microsoft basic data\" --typecode=0:EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 " + Device;
+	LOGINFO("Command is: '%s'\n", Command.c_str());
+	if (TWFunc::Exec_Cmd(Command) != 0) {
+		gui_msg(Msg(msg::kError, "unable_to_create_part=Unable to create {1} partition.")("FAT32"));
+		return false;
+	}
+	if (ext > 0) {
+		gui_msg(Msg("create_part=Creating {1} partition...")("EXT"));
+		Command = "sgdisk --new=0:0:" + ext_str + " --change-name=0:\"Linux filesystem\" " + Device;
+		LOGINFO("Command is: '%s'\n", Command.c_str());
+		if (TWFunc::Exec_Cmd(Command) != 0) {
+			gui_msg(Msg(msg::kError, "unable_to_create_part=Unable to create {1} partition.")("EXT"));
+			Update_System_Details();
+			return false;
+		}
+	}
+	if (swap > 0) {
+		gui_msg(Msg("create_part=Creating {1} partition...")("swap"));
+		Command = "sgdisk --new=0:0:-0 --change-name=0:\"Linux swap\" --typecode=0:0657FD6D-A4AB-43C4-84E5-0933C84B4F4F " + Device;
+		LOGINFO("Command is: '%s'\n", Command.c_str());
+		if (TWFunc::Exec_Cmd(Command) != 0) {
+			gui_msg(Msg(msg::kError, "unable_to_create_part=Unable to create {1} partition.")("swap"));
+			Update_System_Details();
+			return false;
+		}
+	}
+
+	// Convert GPT to MBR
+	Command = "sgdisk --gpttombr " + Device;
+	if (TWFunc::Exec_Cmd(Command) != 0)
+		LOGINFO("Failed to covert partition GPT to MBR\n");
+
+	// Tell the kernel to rescan the partition table
+	int fd = open(Device.c_str(), O_RDONLY);
+	ioctl(fd, BLKRRPART, 0);
+	close(fd);
+
+	string format_device = Device;
+	if (Device.substr(0, 17) == "/dev/block/mmcblk")
+		format_device += "p";
+
+	// Format new partitions to proper file system
+	if (fat_size > 0) {
+		Command = "mkfs.fat " + format_device + "1";
+		TWFunc::Exec_Cmd(Command);
+	}
+	if (ext > 0) {
+		if (SDext == NULL) {
+			Command = "mke2fs -t " + ext_format + " -m 0 " + format_device + "2";
+			gui_msg(Msg("format_sdext_as=Formatting sd-ext as {1}...")(ext_format));
+			LOGINFO("Formatting sd-ext after partitioning, command: '%s'\n", Command.c_str());
+			TWFunc::Exec_Cmd(Command);
+		} else {
+			SDext->Wipe(ext_format);
+		}
+	}
+	if (swap > 0) {
+		Command = "mkswap " + format_device;
+		if (ext > 0)
+			Command += "3";
+		else
+			Command += "2";
+		TWFunc::Exec_Cmd(Command);
+	}
+
+	// recreate TWRP folder and rewrite settings - these will be gone after sdcard is partitioned
+	if (SDCard->Mount(true)) {
+		string TWRP_Folder = SDCard->Mount_Point + "/TWRP";
+		mkdir(TWRP_Folder.c_str(), 0777);
+		DataManager::Flush();
+	}
+
+	Update_System_Details();
+	gui_msg("part_complete=Partitioning complete.");
+	return true;
+}
+
+void TWPartitionManager::Get_Partition_List(string ListType, std::vector<PartitionList> *Partition_List) {
+	std::vector<TWPartition*>::iterator iter;
+	if (ListType == "mount") {
+		for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+			if ((*iter)->Can_Be_Mounted) {
+				struct PartitionList part;
+				part.Display_Name = (*iter)->Display_Name;
+				part.Mount_Point = (*iter)->Mount_Point;
+				part.selected = (*iter)->Is_Mounted();
+				Partition_List->push_back(part);
+			}
+		}
+	} else if (ListType == "storage") {
+		char free_space[255];
+		string Current_Storage = DataManager::GetCurrentStoragePath();
+		for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+			if ((*iter)->Is_Storage) {
+				struct PartitionList part;
+				sprintf(free_space, "%llu", (*iter)->Free / 1024 / 1024);
+				part.Display_Name = (*iter)->Storage_Name + " (";
+				part.Display_Name += free_space;
+				part.Display_Name += "MB)";
+				part.Mount_Point = (*iter)->Storage_Path;
+				if ((*iter)->Storage_Path == Current_Storage)
+					part.selected = 1;
+				else
+					part.selected = 0;
+				Partition_List->push_back(part);
+			}
+		}
+	} else if (ListType == "backup") {
+		char backup_size[255];
+		unsigned long long Backup_Size;
+		for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+			if ((*iter)->Can_Be_Backed_Up && !(*iter)->Is_SubPartition && (*iter)->Is_Present) {
+				struct PartitionList part;
+				Backup_Size = (*iter)->Backup_Size;
+				if ((*iter)->Has_SubPartition) {
+					std::vector<TWPartition*>::iterator subpart;
+
+					for (subpart = Partitions.begin(); subpart != Partitions.end(); subpart++) {
+						if ((*subpart)->Is_SubPartition && (*subpart)->Can_Be_Backed_Up && (*subpart)->Is_Present && (*subpart)->SubPartition_Of == (*iter)->Mount_Point)
+							Backup_Size += (*subpart)->Backup_Size;
+					}
+				}
+				sprintf(backup_size, "%llu", Backup_Size / 1024 / 1024);
+				part.Display_Name = (*iter)->Backup_Display_Name + " (";
+				part.Display_Name += backup_size;
+				part.Display_Name += "MB)";
+				part.Mount_Point = (*iter)->Backup_Path;
+				part.selected = 0;
+				Partition_List->push_back(part);
+			}
+		}
+	} else if (ListType == "restore") {
+		string Restore_List, restore_path;
+		TWPartition* restore_part = NULL;
+
+		DataManager::GetValue("tw_restore_list", Restore_List);
+		if (!Restore_List.empty()) {
+			size_t start_pos = 0, end_pos = Restore_List.find(";", start_pos);
+			while (end_pos != string::npos && start_pos < Restore_List.size()) {
+				restore_path = Restore_List.substr(start_pos, end_pos - start_pos);
+				struct PartitionList part;
+				if (restore_path.compare("ADB_Backup") == 0) {
+					part.Display_Name = "ADB Backup";
+					part.Mount_Point = "ADB Backup";
+					part.selected = 1;
+					Partition_List->push_back(part);
+					break;
+				}
+				if ((restore_part = Find_Partition_By_Path(restore_path)) != NULL) {
+					if ((restore_part->Backup_Name == "recovery" && !restore_part->Can_Be_Backed_Up) || restore_part->Is_SubPartition) {
+						// Don't allow restore of recovery (causes problems on some devices)
+						// Don't add subpartitions to the list of items
+					} else {
+						part.Display_Name = restore_part->Backup_Display_Name;
+						part.Mount_Point = restore_part->Backup_Path;
+						part.selected = 1;
+						Partition_List->push_back(part);
+					}
+				} else {
+					gui_msg(Msg(msg::kError, "restore_unable_locate=Unable to locate '{1}' partition for restoring.")(restore_path));
+				}
+				start_pos = end_pos + 1;
+				end_pos = Restore_List.find(";", start_pos);
+			}
+		}
+	} else if (ListType == "wipe") {
+		struct PartitionList dalvik;
+		dalvik.Display_Name = gui_parse_text("{@dalvik}");
+		dalvik.Mount_Point = "DALVIK";
+		dalvik.selected = 0;
+		Partition_List->push_back(dalvik);
+		for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+			if ((*iter)->Wipe_Available_in_GUI && !(*iter)->Is_SubPartition) {
+				struct PartitionList part;
+				part.Display_Name = (*iter)->Display_Name;
+				part.Mount_Point = (*iter)->Mount_Point;
+				part.selected = 0;
+				Partition_List->push_back(part);
+			}
+			if ((*iter)->Has_Android_Secure) {
+				struct PartitionList part;
+				part.Display_Name = (*iter)->Backup_Display_Name;
+				part.Mount_Point = (*iter)->Backup_Path;
+				part.selected = 0;
+				Partition_List->push_back(part);
+			}
+			if ((*iter)->Has_Data_Media) {
+				struct PartitionList datamedia;
+				datamedia.Display_Name = (*iter)->Storage_Name;
+				datamedia.Mount_Point = "INTERNAL";
+				datamedia.selected = 0;
+				Partition_List->push_back(datamedia);
+			}
+		}
+	} else if (ListType == "flashimg") {
+		for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+			if ((*iter)->Can_Flash_Img && (*iter)->Is_Present) {
+				struct PartitionList part;
+				part.Display_Name = (*iter)->Backup_Display_Name;
+				part.Mount_Point = (*iter)->Backup_Path;
+				part.selected = 0;
+				Partition_List->push_back(part);
+			}
+		}
+		if (DataManager::GetIntValue("tw_has_repack_tools") != 0 && DataManager::GetIntValue("tw_has_boot_slots") != 0) {
+			TWPartition* boot = Find_Partition_By_Path("/boot");
+			if (boot) {
+				// Allow flashing kernels and ramdisks
+				struct PartitionList repack_ramdisk;
+				repack_ramdisk.Display_Name = gui_lookup("install_twrp_ramdisk", "Install Recovery Ramdisk");
+				repack_ramdisk.Mount_Point = "/repack_ramdisk";
+				repack_ramdisk.selected = 0;
+				Partition_List->push_back(repack_ramdisk);
+				/*struct PartitionList repack_kernel; For now let's leave repacking kernels under advanced only
+				repack_kernel.Display_Name = gui_lookup("install_kernel", "Install Kernel");
+				repack_kernel.Mount_Point = "/repack_kernel";
+				repack_kernel.selected = 0;
+				Partition_List->push_back(repack_kernel);*/
+			}
+		}
+	} else {
+		LOGERR("Unknown list type '%s' requested for TWPartitionManager::Get_Partition_List\n", ListType.c_str());
+	}
+}
+
+int TWPartitionManager::Fstab_Processed(void) {
+	return Partitions.size();
+}
+
+void TWPartitionManager::Output_Storage_Fstab(void) {
+	std::vector<TWPartition*>::iterator iter;
+	char storage_partition[255];
+	std::string Temp;
+	std::string cacheDir = TWFunc::get_log_dir();
+
+	if (cacheDir.empty()) {
+		LOGINFO("Unable to find cache directory\n");
+		return;
+	}
+
+	std::string storageFstab = TWFunc::get_log_dir() + "recovery/storage.fstab";
+	FILE *fp = fopen(storageFstab.c_str(), "w");
+
+	if (fp == NULL) {
+		gui_msg(Msg(msg::kError, "unable_to_open=Unable to open '{1}'.")(storageFstab));
+		return;
+	}
+
+	// Iterate through all partitions
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Is_Storage) {
+			Temp = (*iter)->Storage_Path + ";" + (*iter)->Storage_Name + ";\n";
+			strcpy(storage_partition, Temp.c_str());
+			fwrite(storage_partition, sizeof(storage_partition[0]), strlen(storage_partition) / sizeof(storage_partition[0]), fp);
+		}
+	}
+	fclose(fp);
+}
+
+TWPartition *TWPartitionManager::Get_Default_Storage_Partition()
+{
+	TWPartition *res = NULL;
+	for (std::vector<TWPartition*>::iterator iter = Partitions.begin(); iter != Partitions.end(); ++iter) {
+		if (!(*iter)->Is_Storage)
+			continue;
+
+		if ((*iter)->Is_Settings_Storage)
+			return *iter;
+
+		if (!res)
+			res = *iter;
+	}
+	return res;
+}
+
+bool TWPartitionManager::Enable_MTP(void) {
+#ifdef TW_HAS_MTP
+	if (mtppid) {
+		gui_err("mtp_already_enabled=MTP already enabled");
+		return true;
+	}
+
+	int mtppipe[2];
+
+	if (pipe(mtppipe) < 0) {
+		LOGERR("Error creating MTP pipe\n");
+		return false;
+	}
+
+	char old_value[PROPERTY_VALUE_MAX];
+	property_get("sys.usb.config", old_value, "");
+	if (strcmp(old_value, "mtp,adb") != 0) {
+		char vendor[PROPERTY_VALUE_MAX];
+		char product[PROPERTY_VALUE_MAX];
+		property_set("sys.usb.config", "none");
+		property_get("usb.vendor", vendor, "18D1");
+		property_get("usb.product.mtpadb", product, "4EE2");
+		string vendorstr = vendor;
+		string productstr = product;
+		TWFunc::write_to_file("/sys/class/android_usb/android0/idVendor", vendorstr);
+		TWFunc::write_to_file("/sys/class/android_usb/android0/idProduct", productstr);
+		property_set("sys.usb.config", "mtp,adb");
+	}
+	/* To enable MTP debug, use the twrp command line feature:
+	 * twrp set tw_mtp_debug 1
+	 */
+	twrpMtp *mtp = new twrpMtp(DataManager::GetIntValue("tw_mtp_debug"));
+	mtppid = mtp->forkserver(mtppipe);
+	if (mtppid) {
+		close(mtppipe[0]); // Host closes read side
+		mtp_write_fd = mtppipe[1];
+		DataManager::SetValue("tw_mtp_enabled", 1);
+		Add_All_MTP_Storage();
+		return true;
+	} else {
+		close(mtppipe[0]);
+		close(mtppipe[1]);
+		gui_err("mtp_fail=Failed to enable MTP");
+		return false;
+	}
+#else
+	gui_err("no_mtp=MTP support not included");
+#endif
+	DataManager::SetValue("tw_mtp_enabled", 0);
+	return false;
+}
+
+void TWPartitionManager::Add_All_MTP_Storage(void) {
+#ifdef TW_HAS_MTP
+	std::vector<TWPartition*>::iterator iter;
+
+	if (!mtppid)
+		return; // MTP is not enabled
+
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Is_Storage && (*iter)->Is_Present && (*iter)->Mount(false))
+			Add_Remove_MTP_Storage((*iter), MTP_MESSAGE_ADD_STORAGE);
+	}
+#else
+	return;
+#endif
+}
+
+bool TWPartitionManager::Disable_MTP(void) {
+	char old_value[PROPERTY_VALUE_MAX];
+	property_set("sys.usb.ffs.mtp.ready", "0");
+	property_get("sys.usb.config", old_value, "");
+	if (strcmp(old_value, "adb") != 0) {
+		char vendor[PROPERTY_VALUE_MAX];
+		char product[PROPERTY_VALUE_MAX];
+		property_set("sys.usb.config", "none");
+		property_get("usb.vendor", vendor, "18D1");
+		property_get("usb.product.adb", product, "D001");
+		string vendorstr = vendor;
+		string productstr = product;
+		TWFunc::write_to_file("/sys/class/android_usb/android0/idVendor", vendorstr);
+		TWFunc::write_to_file("/sys/class/android_usb/android0/idProduct", productstr);
+		usleep(2000);
+	}
+#ifdef TW_HAS_MTP
+	if (mtppid) {
+		LOGINFO("Disabling MTP\n");
+		int status;
+		kill(mtppid, SIGKILL);
+		mtppid = 0;
+		// We don't care about the exit value, but this prevents a zombie process
+		waitpid(mtppid, &status, 0);
+		close(mtp_write_fd);
+		mtp_write_fd = -1;
+	}
+#endif
+	property_set("sys.usb.config", "adb");
+#ifdef TW_HAS_MTP
+	DataManager::SetValue("tw_mtp_enabled", 0);
+	return true;
+#endif
+	return false;
+}
+
+TWPartition* TWPartitionManager::Find_Partition_By_MTP_Storage_ID(unsigned int Storage_ID) {
+	std::vector<TWPartition*>::iterator iter;
+
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->MTP_Storage_ID == Storage_ID)
+			return (*iter);
+	}
+	return NULL;
+}
+
+bool TWPartitionManager::Add_Remove_MTP_Storage(TWPartition* Part, int message_type) {
+#ifdef TW_HAS_MTP
+	struct mtpmsg mtp_message;
+
+	if (!mtppid)
+		return false; // MTP is disabled
+
+	if (mtp_write_fd < 0) {
+		LOGINFO("MTP: mtp_write_fd is not set\n");
+		return false;
+	}
+
+	if (Part) {
+		if (Part->MTP_Storage_ID == 0)
+			return false;
+		if (message_type == MTP_MESSAGE_REMOVE_STORAGE) {
+			mtp_message.message_type = MTP_MESSAGE_REMOVE_STORAGE; // Remove
+			LOGINFO("sending message to remove %i\n", Part->MTP_Storage_ID);
+			mtp_message.storage_id = Part->MTP_Storage_ID;
+			if (write(mtp_write_fd, &mtp_message, sizeof(mtp_message)) <= 0) {
+				LOGINFO("error sending message to remove storage %i\n", Part->MTP_Storage_ID);
+				return false;
+			} else {
+				LOGINFO("Message sent, remove storage ID: %i\n", Part->MTP_Storage_ID);
+				return true;
+			}
+		} else if (message_type == MTP_MESSAGE_ADD_STORAGE && Part->Is_Mounted()) {
+			mtp_message.message_type = MTP_MESSAGE_ADD_STORAGE; // Add
+			mtp_message.storage_id = Part->MTP_Storage_ID;
+			if (Part->Storage_Path.size() >= sizeof(mtp_message.path)) {
+				LOGERR("Storage path '%s' too large for mtpmsg\n", Part->Storage_Path.c_str());
+				return false;
+			}
+			strcpy(mtp_message.path, Part->Storage_Path.c_str());
+			if (Part->Storage_Name.size() >= sizeof(mtp_message.display)) {
+				LOGERR("Storage name '%s' too large for mtpmsg\n", Part->Storage_Name.c_str());
+				return false;
+			}
+			strcpy(mtp_message.display, Part->Storage_Name.c_str());
+			mtp_message.maxFileSize = Part->Get_Max_FileSize();
+			LOGINFO("sending message to add %i '%s' '%s'\n", mtp_message.storage_id, mtp_message.path, mtp_message.display);
+			if (write(mtp_write_fd, &mtp_message, sizeof(mtp_message)) <= 0) {
+				LOGINFO("error sending message to add storage %i\n", Part->MTP_Storage_ID);
+				return false;
+			} else {
+				LOGINFO("Message sent, add storage ID: %i '%s'\n", Part->MTP_Storage_ID, mtp_message.path);
+				return true;
+			}
+		} else {
+			LOGERR("Unknown MTP message type: %i\n", message_type);
+		}
+	} else {
+		// This hopefully never happens as the error handling should
+		// occur in the calling function.
+		LOGINFO("TWPartitionManager::Add_Remove_MTP_Storage NULL partition given\n");
+	}
+	return true;
+#else
+	gui_err("no_mtp=MTP support not included");
+	DataManager::SetValue("tw_mtp_enabled", 0);
+	return false;
+#endif
+}
+
+bool TWPartitionManager::Add_MTP_Storage(string Mount_Point) {
+#ifdef TW_HAS_MTP
+	TWPartition* Part = PartitionManager.Find_Partition_By_Path(Mount_Point);
+	if (Part) {
+		return PartitionManager.Add_Remove_MTP_Storage(Part, MTP_MESSAGE_ADD_STORAGE);
+	} else {
+		LOGINFO("TWFunc::Add_MTP_Storage unable to locate partition for '%s'\n", Mount_Point.c_str());
+	}
+#endif
+	return false;
+}
+
+bool TWPartitionManager::Add_MTP_Storage(unsigned int Storage_ID) {
+#ifdef TW_HAS_MTP
+	TWPartition* Part = PartitionManager.Find_Partition_By_MTP_Storage_ID(Storage_ID);
+	if (Part) {
+		return PartitionManager.Add_Remove_MTP_Storage(Part, MTP_MESSAGE_ADD_STORAGE);
+	} else {
+		LOGINFO("TWFunc::Add_MTP_Storage unable to locate partition for %i\n", Storage_ID);
+	}
+#endif
+	return false;
+}
+
+bool TWPartitionManager::Remove_MTP_Storage(string Mount_Point) {
+#ifdef TW_HAS_MTP
+	TWPartition* Part = PartitionManager.Find_Partition_By_Path(Mount_Point);
+	if (Part) {
+		return PartitionManager.Add_Remove_MTP_Storage(Part, MTP_MESSAGE_REMOVE_STORAGE);
+	} else {
+		LOGINFO("TWFunc::Remove_MTP_Storage unable to locate partition for '%s'\n", Mount_Point.c_str());
+	}
+#endif
+	return false;
+}
+
+bool TWPartitionManager::Remove_MTP_Storage(unsigned int Storage_ID) {
+#ifdef TW_HAS_MTP
+	TWPartition* Part = PartitionManager.Find_Partition_By_MTP_Storage_ID(Storage_ID);
+	if (Part) {
+		return PartitionManager.Add_Remove_MTP_Storage(Part, MTP_MESSAGE_REMOVE_STORAGE);
+	} else {
+		LOGINFO("TWFunc::Remove_MTP_Storage unable to locate partition for %i\n", Storage_ID);
+	}
+#endif
+	return false;
+}
+
+bool TWPartitionManager::Flash_Image(string& path, string& filename) {
+	twrpRepacker repacker;
+	int partition_count = 0;
+	TWPartition* flash_part = NULL;
+	string Flash_List, flash_path, full_filename;
+	size_t start_pos = 0, end_pos = 0;
+
+	full_filename = path + "/" + filename;
+
+	gui_msg("image_flash_start=[IMAGE FLASH STARTED]");
+	gui_msg(Msg("img_to_flash=Image to flash: '{1}'")(full_filename));
+
+	if (!TWFunc::Path_Exists(full_filename)) {
+		if (!Mount_By_Path(full_filename, true)) {
+			return false;
+		}
+		if (!TWFunc::Path_Exists(full_filename)) {
+			gui_msg(Msg(msg::kError, "unable_to_locate=Unable to locate {1}.")(full_filename));
+			return false;
+		}
+	}
+
+	DataManager::GetValue("tw_flash_partition", Flash_List);
+	Repack_Type repack = REPLACE_NONE;
+	if (Flash_List == "/repack_ramdisk;") {
+		repack = REPLACE_RAMDISK;
+	} else if (Flash_List == "/repack_kernel;") {
+		repack = REPLACE_KERNEL;
+	}
+	if (repack != REPLACE_NONE) {
+		Repack_Options_struct Repack_Options;
+		Repack_Options.Type = repack;
+		Repack_Options.Disable_Verity = false;
+		Repack_Options.Disable_Force_Encrypt = false;
+		Repack_Options.Backup_First = DataManager::GetIntValue("tw_repack_backup_first") != 0;
+		return repacker.Repack_Image_And_Flash(full_filename, Repack_Options);
+	}
+	PartitionSettings part_settings;
+	part_settings.Backup_Folder = path;
+	unsigned long long total_bytes = TWFunc::Get_File_Size(full_filename);
+	ProgressTracking progress(total_bytes);
+	part_settings.progress = &progress;
+	part_settings.adbbackup = false;
+	part_settings.PM_Method = PM_RESTORE;
+	gui_msg("calc_restore=Calculating restore details...");
+	if (!Flash_List.empty()) {
+		end_pos = Flash_List.find(";", start_pos);
+		while (end_pos != string::npos && start_pos < Flash_List.size()) {
+			flash_path = Flash_List.substr(start_pos, end_pos - start_pos);
+			flash_part = Find_Partition_By_Path(flash_path);
+			if (flash_part != NULL) {
+				partition_count++;
+				if (partition_count > 1) {
+					gui_err("too_many_flash=Too many partitions selected for flashing.");
+					return false;
+				}
+			} else {
+				gui_msg(Msg(msg::kError, "flash_unable_locate=Unable to locate '{1}' partition for flashing.")(flash_path));
+				return false;
+			}
+			start_pos = end_pos + 1;
+			end_pos = Flash_List.find(";", start_pos);
+		}
+	}
+
+	if (partition_count == 0) {
+		gui_err("no_part_flash=No partitions selected for flashing.");
+		return false;
+	}
+
+	DataManager::SetProgress(0.0);
+	if (flash_part) {
+		flash_part->Backup_FileName = filename;
+		if (!flash_part->Flash_Image(&part_settings))
+			return false;
+	} else {
+		gui_err("invalid_flash=Invalid flash partition specified.");
+		return false;
+	}
+	gui_highlight("flash_done=IMAGE FLASH COMPLETED]");
+	return true;
+}
+
+void TWPartitionManager::Translate_Partition(const char* path, const char* resource_name, const char* default_value) {
+	TWPartition* part = PartitionManager.Find_Partition_By_Path(path);
+	if (part) {
+		if (part->Is_Adopted_Storage) {
+			part->Display_Name = part->Display_Name + " - " + gui_lookup("data", "Data");
+			part->Backup_Display_Name = part->Display_Name;
+			part->Storage_Name = part->Storage_Name + " - " + gui_lookup("adopted_storage", "Adopted Storage");
+		} else {
+			part->Display_Name = gui_lookup(resource_name, default_value);
+			part->Backup_Display_Name = part->Display_Name;
+		}
+	}
+}
+
+void TWPartitionManager::Translate_Partition(const char* path, const char* resource_name, const char* default_value, const char* storage_resource_name, const char* storage_default_value) {
+	TWPartition* part = PartitionManager.Find_Partition_By_Path(path);
+	if (part) {
+		if (part->Is_Adopted_Storage) {
+			part->Backup_Display_Name = part->Display_Name + " - " + gui_lookup("data_backup", "Data (excl. storage)");
+			part->Display_Name = part->Display_Name + " - " + gui_lookup("data", "Data");
+			part->Storage_Name = part->Storage_Name + " - " + gui_lookup("adopted_storage", "Adopted Storage");
+		} else {
+			part->Display_Name = gui_lookup(resource_name, default_value);
+			part->Backup_Display_Name = part->Display_Name;
+			if (part->Is_Storage)
+				part->Storage_Name = gui_lookup(storage_resource_name, storage_default_value);
+		}
+	}
+}
+
+void TWPartitionManager::Translate_Partition(const char* path, const char* resource_name, const char* default_value, const char* storage_resource_name, const char* storage_default_value, const char* backup_name, const char* backup_default) {
+	TWPartition* part = PartitionManager.Find_Partition_By_Path(path);
+	if (part) {
+		if (part->Is_Adopted_Storage) {
+			part->Backup_Display_Name = part->Display_Name + " - " + gui_lookup(backup_name, backup_default);
+			part->Display_Name = part->Display_Name + " - " + gui_lookup("data", "Data");
+			part->Storage_Name = part->Storage_Name + " - " + gui_lookup("adopted_storage", "Adopted Storage");
+		} else {
+			part->Display_Name = gui_lookup(resource_name, default_value);
+			part->Backup_Display_Name = gui_lookup(backup_name, backup_default);
+			if (part->Is_Storage)
+				part->Storage_Name = gui_lookup(storage_resource_name, storage_default_value);
+		}
+	}
+}
+
+void TWPartitionManager::Translate_Partition_Display_Names() {
+	LOGINFO("Translating partition display names\n");
+	Translate_Partition("/system", "system", "System");
+	Translate_Partition("/system_image", "system_image", "System Image");
+	Translate_Partition("/vendor", "vendor", "Vendor");
+	Translate_Partition("/vendor_image", "vendor_image", "Vendor Image");
+	Translate_Partition("/cache", "cache", "Cache");
+	Translate_Partition("/boot", "boot", "Boot");
+	Translate_Partition("/recovery", "recovery", "Recovery");
+	if (!datamedia) {
+		Translate_Partition("/data", "data", "Data", "internal", "Internal Storage");
+		Translate_Partition("/sdcard", "sdcard", "SDCard", "sdcard", "SDCard");
+		Translate_Partition("/internal_sd", "sdcard", "SDCard", "sdcard", "SDCard");
+		Translate_Partition("/internal_sdcard", "sdcard", "SDCard", "sdcard", "SDCard");
+		Translate_Partition("/emmc", "sdcard", "SDCard", "sdcard", "SDCard");
+	} else {
+		Translate_Partition("/data", "data", "Data", "internal", "Internal Storage", "data_backup", "Data (excl. storage)");
+	}
+	Translate_Partition("/external_sd", "microsd", "Micro SDCard", "microsd", "Micro SDCard", "data_backup", "Data (excl. storage)");
+	Translate_Partition("/external_sdcard", "microsd", "Micro SDCard", "microsd", "Micro SDCard", "data_backup", "Data (excl. storage)");
+	Translate_Partition("/usb-otg", "usbotg", "USB OTG", "usbotg", "USB OTG");
+	Translate_Partition("/sd-ext", "sdext", "SD-EXT");
+
+	// Android secure is a special case
+	TWPartition* part = PartitionManager.Find_Partition_By_Path("/and-sec");
+	if (part)
+		part->Backup_Display_Name = gui_lookup("android_secure", "Android Secure");
+
+	std::vector<TWPartition*>::iterator sysfs;
+	for (sysfs = Partitions.begin(); sysfs != Partitions.end(); sysfs++) {
+		if (!(*sysfs)->Sysfs_Entry.empty()) {
+			Translate_Partition((*sysfs)->Mount_Point.c_str(), "autostorage", "Storage", "autostorage", "Storage");
+		}
+	}
+
+	// This updates the text on all of the storage selection buttons in the GUI
+	DataManager::SetBackupFolder();
+}
+
+bool TWPartitionManager::Decrypt_Adopted() {
+#ifdef TW_INCLUDE_CRYPTO
+	bool ret = false;
+	if (!Mount_By_Path("/data", false)) {
+		LOGERR("Cannot decrypt adopted storage because /data will not mount\n");
+		return false;
+	}
+
+	std::string path = "/data/system/storage.xml";
+	if (!TWFunc::Check_Xml_Format(path)) {
+		std::string oldpath = path;
+		if (TWFunc::abx_to_xml(oldpath, path)) {
+			LOGINFO("Android 12+: '%s' has been converted into plain text xml (%s).\n", oldpath.c_str(), path.c_str());
+		}
+	}
+
+	LOGINFO("Decrypt adopted storage starting\n");
+	char* xmlFile = PageManager::LoadFileToBuffer(path, NULL);
+	xml_document<> *doc = NULL;
+	xml_node<>* volumes = NULL;
+	string Primary_Storage_UUID = "";
+	if (xmlFile != NULL) {
+		LOGINFO("successfully loaded storage.xml\n");
+		doc = new xml_document<>();
+		doc->parse<0>(xmlFile);
+		volumes = doc->first_node("volumes");
+		if (volumes) {
+			xml_attribute<>* psuuid = volumes->first_attribute("primaryStorageUuid");
+			if (psuuid) {
+				Primary_Storage_UUID = psuuid->value();
+			}
+		}
+	} else {
+		LOGINFO("No /data/system/storage.xml for adopted storage\n");
+		return false;
+	}
+	std::vector<TWPartition*>::iterator adopt;
+	for (adopt = Partitions.begin(); adopt != Partitions.end(); adopt++) {
+		if ((*adopt)->Removable && !(*adopt)->Is_Present && (*adopt)->Adopted_Mount_Delay > 0) {
+			// On some devices, the external mmc driver takes some time
+			// to recognize the card, in which case the "actual block device"
+			// would not have been found yet. We wait the specified delay
+			// and then try again.
+			LOGINFO("Sleeping %d seconds for adopted storage.\n", (*adopt)->Adopted_Mount_Delay);
+			sleep((*adopt)->Adopted_Mount_Delay);
+			(*adopt)->Find_Actual_Block_Device();
+		}
+
+		if ((*adopt)->Removable && (*adopt)->Is_Present) {
+			if ((*adopt)->Decrypt_Adopted() == 0) {
+				ret = true;
+				if (volumes) {
+					xml_node<>* volume = volumes->first_node("volume");
+					while (volume) {
+						xml_attribute<>* guid = volume->first_attribute("partGuid");
+						if (guid) {
+							string GUID = (*adopt)->Adopted_GUID.c_str();
+							GUID.insert(8, "-");
+							GUID.insert(13, "-");
+							GUID.insert(18, "-");
+							GUID.insert(23, "-");
+
+							if (strcasecmp(GUID.c_str(), guid->value()) == 0) {
+								xml_attribute<>* attr = volume->first_attribute("nickname");
+								if (attr && attr->value() && strlen(attr->value()) > 0) {
+									(*adopt)->Storage_Name = attr->value();
+									(*adopt)->Display_Name = (*adopt)->Storage_Name;
+									(*adopt)->Backup_Display_Name = (*adopt)->Storage_Name;
+									LOGINFO("storage name from storage.xml is '%s'\n", attr->value());
+								}
+								attr = volume->first_attribute("fsUuid");
+								if (attr && !Primary_Storage_UUID.empty() && strcmp(Primary_Storage_UUID.c_str(), attr->value()) == 0) {
+									TWPartition* Dat = Find_Partition_By_Path("/data");
+									if (Dat) {
+										LOGINFO("Internal storage is found on adopted storage '%s'\n", (*adopt)->Display_Name.c_str());
+										LOGINFO("Changing '%s' to point to '%s'\n", Dat->Symlink_Mount_Point.c_str(), (*adopt)->Storage_Path.c_str());
+										(*adopt)->Symlink_Mount_Point = Dat->Symlink_Mount_Point;
+										Dat->Symlink_Mount_Point = "";
+										// Toggle mounts to ensure that the symlink mount point (probably /sdcard) is mounted to the right location
+										Dat->UnMount(false);
+										Dat->Mount(false);
+										(*adopt)->UnMount(false);
+										(*adopt)->Mount(false);
+									}
+								}
+								break;
+							}
+						}
+						volume = volume->next_sibling("volume");
+					}
+				}
+				Update_System_Details();
+				Output_Partition((*adopt));
+			}
+		}
+	}
+	if (xmlFile) {
+		doc->clear();
+		delete doc;
+		free(xmlFile);
+	}
+	return ret;
+#else
+	LOGINFO("Decrypt_Adopted: no crypto support\n");
+	return false;
+#endif
+}
+
+void TWPartitionManager::Remove_Partition_By_Path(string Path) {
+	std::vector<TWPartition*>::iterator iter;
+	string Local_Path = TWFunc::Get_Root_Path(Path);
+
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Mount_Point == Local_Path || (!(*iter)->Symlink_Mount_Point.empty() && (*iter)->Symlink_Mount_Point == Local_Path)) {
+			LOGINFO("Found and erasing '%s' from partition list\n", Local_Path.c_str());
+			Partitions.erase(iter);
+			return;
+		}
+	}
+}
+
+void TWPartitionManager::Override_Active_Slot(const string& Slot) {
+	LOGINFO("Overriding slot to '%s'\n", Slot.c_str());
+	Active_Slot_Display = Slot;
+	DataManager::SetValue("tw_active_slot", Slot);
+	PartitionManager.Update_System_Details();
+}
+
+void TWPartitionManager::Set_Active_Slot(const string& Slot) {
+	if (Slot != "A" && Slot != "B") {
+		LOGERR("Set_Active_Slot invalid slot '%s'\n", Slot.c_str());
+		return;
+	}
+	if (Active_Slot_Display == Slot)
+		return;
+	LOGINFO("Setting active slot %s\n", Slot.c_str());
+#ifdef AB_OTA_UPDATER
+	if (!Active_Slot_Display.empty()) {
+		android::sp<IBootControl> module = IBootControl::getService();
+		if (module == nullptr) {
+			LOGERR("Error getting bootctrl module.\n");
+		} else {
+			uint32_t slot_number = 0;
+			if (Slot == "B")
+				slot_number = 1;
+			CommandResult result;
+			auto ret = module->setActiveBootSlot(slot_number, [&result]
+					(const CommandResult &cb_result) { result = cb_result; });
+			if (!ret.isOk() || !result.success)
+				gui_msg(Msg(msg::kError, "unable_set_boot_slot=Error changing bootloader boot slot to {1}")(Slot));
+		}
+		DataManager::SetValue("tw_active_slot", Slot); // Doing this outside of this if block may result in a seg fault because the DataManager may not be ready yet
+	}
+#else
+	LOGERR("Boot slot feature not present\n");
+#endif
+	Active_Slot_Display = Slot;
+	if (Fstab_Processed())
+		Update_System_Details();
+}
+string TWPartitionManager::Get_Active_Slot_Suffix() {
+	if (Active_Slot_Display == "A")
+		return "_a";
+	return "_b";
+}
+string TWPartitionManager::Get_Active_Slot_Display() {
+	return Active_Slot_Display;
+}
+
+string TWPartitionManager::Get_Android_Root_Path() {
+	return "/system_root";
+}
+
+void TWPartitionManager::Remove_Uevent_Devices(const string& Mount_Point) {
+	std::vector<TWPartition*>::iterator iter;
+
+	for (iter = Partitions.begin(); iter != Partitions.end(); ) {
+		if ((*iter)->Is_SubPartition && (*iter)->SubPartition_Of == Mount_Point) {
+			TWPartition *part = *iter;
+			LOGINFO("%s was removed by uevent data\n", (*iter)->Mount_Point.c_str());
+			(*iter)->UnMount(false);
+			rmdir((*iter)->Mount_Point.c_str());
+			iter = Partitions.erase(iter);
+			delete part;
+		} else {
+			iter++;
+		}
+	}
+}
+
+void TWPartitionManager::Handle_Uevent(const Uevent_Block_Data& uevent_data) {
+	std::vector<TWPartition*>::iterator iter;
+
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if (!(*iter)->Sysfs_Entry.empty()) {
+			string device;
+			size_t wildcard = (*iter)->Sysfs_Entry.find("*");
+			if (wildcard != string::npos) {
+				device = (*iter)->Sysfs_Entry.substr(0, wildcard);
+			} else {
+				device = (*iter)->Sysfs_Entry;
+			}
+			if (device == uevent_data.sysfs_path.substr(0, device.size())) {
+				// Found a match
+				if (uevent_data.action == "add") {
+					(*iter)->Primary_Block_Device = "/dev/block/" + uevent_data.block_device;
+					(*iter)->Alternate_Block_Device = (*iter)->Primary_Block_Device;
+					(*iter)->Is_Present = true;
+					LOGINFO("Found a match '%s' '%s'\n", uevent_data.block_device.c_str(), device.c_str());
+					if (!Decrypt_Adopted()) {
+						LOGINFO("No adopted storage so finding actual block device\n");
+						(*iter)->Find_Actual_Block_Device();
+					}
+					return;
+				} else if (uevent_data.action == "remove") {
+					(*iter)->Is_Present = false;
+					(*iter)->Primary_Block_Device = "";
+					(*iter)->Actual_Block_Device = "";
+					Remove_Uevent_Devices((*iter)->Mount_Point);
+					return;
+				}
+			}
+		}
+	}
+
+	if (!PartitionManager.Get_Super_Status())
+		LOGINFO("Found no matching fstab entry for uevent device '%s' - %s\n", uevent_data.sysfs_path.c_str(), uevent_data.action.c_str());
+}
+
+void TWPartitionManager::setup_uevent() {
+	struct sockaddr_nl nls;
+
+	if (uevent_pfd.fd >= 0) {
+		LOGINFO("uevent already set up\n");
+		return;
+	}
+
+	// Open hotplug event netlink socket
+	memset(&nls,0,sizeof(struct sockaddr_nl));
+	nls.nl_family = AF_NETLINK;
+	nls.nl_pid = getpid();
+	nls.nl_groups = -1;
+	uevent_pfd.events = POLLIN;
+	uevent_pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
+	if (uevent_pfd.fd==-1) {
+		LOGERR("uevent not root\n");
+		return;
+	}
+
+	// Listen to netlink socket
+	if (::bind(uevent_pfd.fd, (struct sockaddr *) &nls, sizeof(struct sockaddr_nl)) < 0) {
+		LOGERR("Bind failed\n");
+		return;
+	}
+	set_select_fd();
+	Coldboot();
+}
+
+Uevent_Block_Data TWPartitionManager::get_event_block_values(char *buf, int len) {
+	Uevent_Block_Data ret;
+	ret.subsystem = "";
+	char *ptr = buf;
+	const char *end = buf + len;
+
+	buf[len - 1] = '\0';
+	while (ptr < end) {
+		if (strncmp(ptr, "ACTION=", strlen("ACTION=")) == 0) {
+			ptr += strlen("ACTION=");
+			ret.action = ptr;
+		} else if (strncmp(ptr, "SUBSYSTEM=", strlen("SUBSYSTEM=")) == 0) {
+			ptr += strlen("SUBSYSTEM=");
+			ret.subsystem = ptr;
+		} else if (strncmp(ptr, "DEVTYPE=", strlen("DEVTYPE=")) == 0) {
+			ptr += strlen("DEVTYPE=");
+			ret.type = ptr;
+		} else if (strncmp(ptr, "DEVPATH=", strlen("DEVPATH=")) == 0) {
+			ptr += strlen("DEVPATH=");
+			ret.sysfs_path += ptr;
+		} else if (strncmp(ptr, "DEVNAME=", strlen("DEVNAME=")) == 0) {
+			ptr += strlen("DEVNAME=");
+			ret.block_device += ptr;
+		} else if (strncmp(ptr, "MAJOR=", strlen("MAJOR=")) == 0) {
+			ptr += strlen("MAJOR=");
+			ret.major = atoi(ptr);
+		} else if (strncmp(ptr, "MINOR=", strlen("MINOR=")) == 0) {
+			ptr += strlen("MINOR=");
+			ret.minor = atoi(ptr);
+		}
+		ptr += strlen(ptr) + 1;
+	}
+	return ret;
+}
+
+void TWPartitionManager::read_uevent() {
+	char buf[1024];
+
+	int len = recv(uevent_pfd.fd, buf, sizeof(buf), MSG_DONTWAIT);
+	if (len == -1) {
+		LOGINFO("recv error on uevent\n");
+		return;
+	}
+	/*int i = 0; // Print all uevent output for test /debug
+	while (i<len) {
+		printf("%s\n", buf+i);
+		i += strlen(buf+i)+1;
+	}*/
+	Uevent_Block_Data uevent_data = get_event_block_values(buf, len);
+	if (uevent_data.subsystem == "block" && uevent_data.type == "disk") {
+		PartitionManager.Handle_Uevent(uevent_data);
+	}
+}
+
+void TWPartitionManager::close_uevent() {
+	if (uevent_pfd.fd > 0)
+		close(uevent_pfd.fd);
+	uevent_pfd.fd = -1;
+}
+
+void TWPartitionManager::Add_Partition(TWPartition* Part) {
+	Partitions.push_back(Part);
+}
+
+void TWPartitionManager::Coldboot_Scan(std::vector<string> *sysfs_entries, const string& Path, int depth) {
+	string Real_Path = Path;
+	char real_path[PATH_MAX];
+	if (realpath(Path.c_str(), &real_path[0])) {
+		string Real_Path = real_path;
+		std::vector<string>::iterator iter;
+		for (iter = sysfs_entries->begin(); iter != sysfs_entries->end(); iter++) {
+			if (Real_Path.find((*iter)) != string::npos) {
+				string Write_Path = Real_Path + "/uevent";
+				if (TWFunc::Path_Exists(Write_Path)) {
+					const char* write_val = "add\n";
+					TWFunc::write_to_file(Write_Path, write_val);
+					break;
+				}
+			}
+		}
+	}
+
+	DIR* d = opendir(Path.c_str());
+	if (d != NULL) {
+		struct dirent* de;
+		while ((de = readdir(d)) != NULL) {
+			if (de->d_name[0] == '.' || (de->d_type != DT_DIR && depth > 0))
+				continue;
+			if (strlen(de->d_name) >= 4 && (strncmp(de->d_name, "ram", 3) == 0 || strncmp(de->d_name, "loop", 4) == 0))
+				continue;
+
+			string item = Path + "/";
+			item.append(de->d_name);
+			Coldboot_Scan(sysfs_entries, item, depth + 1);
+		}
+		closedir(d);
+	}
+}
+
+void TWPartitionManager::Coldboot() {
+	std::vector<TWPartition*>::iterator iter;
+	std::vector<string> sysfs_entries;
+
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if (!(*iter)->Sysfs_Entry.empty()) {
+			size_t wildcard_pos = (*iter)->Sysfs_Entry.find("*");
+			if (wildcard_pos == string::npos)
+				wildcard_pos = (*iter)->Sysfs_Entry.size();
+			sysfs_entries.push_back((*iter)->Sysfs_Entry.substr(0, wildcard_pos));
+		}
+	}
+
+	if (sysfs_entries.size() > 0)
+		Coldboot_Scan(&sysfs_entries, "/sys/block", 0);
+}
+
+bool TWPartitionManager::Prepare_Empty_Folder(const std::string& Folder) {
+	if (TWFunc::Path_Exists(Folder))
+		TWFunc::removeDir(Folder, false);
+	return TWFunc::Recursive_Mkdir(Folder);
+}
+
+std::string TWPartitionManager::Get_Bare_Partition_Name(std::string Mount_Point) {
+	if (Mount_Point == "/system_root")
+		return "system";
+	else
+		return TWFunc::Remove_Beginning_Slash(Mount_Point);
+}
+
+bool TWPartitionManager::Prepare_Super_Volume(TWPartition* twrpPart) {
+    Fstab fstab;
+	std::string bare_partition_name = Get_Bare_Partition_Name(twrpPart->Get_Mount_Point());
+
+	Super_Partition_List.push_back(bare_partition_name);
+	LOGINFO("Trying to prepare %s from super partition\n", bare_partition_name.c_str());
+
+	std::string blk_device_partition;
+#ifdef AB_OTA_UPDATER
+	blk_device_partition = bare_partition_name + PartitionManager.Get_Active_Slot_Suffix();
+#else
+	blk_device_partition = bare_partition_name;
+#endif
+
+	FstabEntry fstabEntry = {
+        .blk_device =  blk_device_partition,
+        .mount_point = twrpPart->Get_Mount_Point(),
+        .fs_type = twrpPart->Current_File_System,
+        .fs_mgr_flags.logical = twrpPart->Is_Super,
+    };
+
+    fstab.emplace_back(fstabEntry);
+    if (!fs_mgr_update_logical_partition(&fstabEntry)) {
+        LOGINFO("unable to update logical partition: %s\n", twrpPart->Get_Mount_Point().c_str());
+        return false;
+    }
+
+	while (access(fstabEntry.blk_device.c_str(), F_OK) != 0) {
+		usleep(100);
+	}
+
+	twrpPart->Set_Block_Device(fstabEntry.blk_device);
+	twrpPart->Update_Size(true);
+	twrpPart->Set_Can_Be_Backed_Up(false);
+	twrpPart->Set_Can_Be_Wiped(false);
+	if (access(("/dev/block/bootdevice/by-name/" + bare_partition_name).c_str(), F_OK) == -1) {
+		LOGINFO("Symlinking %s => /dev/block/bootdevice/by-name/%s \n", fstabEntry.blk_device.c_str(), bare_partition_name.c_str());
+		symlink(fstabEntry.blk_device.c_str(), ("/dev/block/bootdevice/by-name/" + bare_partition_name).c_str());
+	}
+
+    return true;
+}
+
+bool TWPartitionManager::Prepare_All_Super_Volumes() {
+	bool status = true;
+	std::vector<TWPartition*>::iterator iter;
+
+	for (iter = Partitions.begin(); iter != Partitions.end(); iter++) {
+		if ((*iter)->Is_Super) {
+			if (!Prepare_Super_Volume(*iter)) {
+				status = false;
+			}
+			PartitionManager.Output_Partition(*iter);
+		}
+	}
+	Update_System_Details();
+	return status;
+}
+
+std::string TWPartitionManager::Get_Super_Partition() {
+	int slot_number = Get_Active_Slot_Display() == "A" ? 0 : 1;
+	std::string super_device = fs_mgr_get_super_partition_name(slot_number);
+	return "/dev/block/by-name/" + super_device;
+}
+
+void TWPartitionManager::Setup_Super_Devices() {
+	std::string superPart = Get_Super_Partition();
+	android::fs_mgr::CreateLogicalPartitions(superPart);
+}
+
+void TWPartitionManager::Setup_Super_Partition() {
+	TWPartition* superPartition = new TWPartition();
+	std::string superPart = Get_Super_Partition();
+
+	superPartition->Backup_Path = "/super";
+	superPartition->Mount_Point = "/super";
+	superPartition->Actual_Block_Device = superPart;
+	superPartition->Alternate_Block_Device = superPart;
+	superPartition->Backup_Display_Name = "Super (";
+	// Add first 4 items to fstab as logical that you would like to display in Backup_Display_Name
+	// for the Super partition
+	int list_size = Super_Partition_List.size();
+	int orig_list_size = list_size;
+	int max_display_size = 3; // total of 4 items since we start at 0
+
+	for (auto partition: Super_Partition_List) {
+		superPartition->Backup_Display_Name = superPartition->Backup_Display_Name + partition;
+		if ((orig_list_size - list_size) == max_display_size) {
+			break;
+		}
+		if (list_size != 1)
+			superPartition->Backup_Display_Name = superPartition->Backup_Display_Name + " ";
+		list_size--;
+	}
+	superPartition->Backup_Display_Name += ")";
+	superPartition->Can_Flash_Img = true;
+	superPartition->Current_File_System = "emmc";
+	superPartition->Can_Be_Backed_Up = true;
+	superPartition->Is_Present = true;
+	superPartition->Is_SubPartition = false;
+	superPartition->Setup_Image();
+	Add_Partition(superPartition);
+	PartitionManager.Output_Partition(superPartition);
+}
+
+bool TWPartitionManager::Get_Super_Status() {
+	std::string fastboot_mode = android::base::GetProperty("sys.usb.config", "");
+	if (fastboot_mode == "fastboot") {
+		return false;
+	}
+	else
+		return access(Get_Super_Partition().c_str(), F_OK) == 0;
+}
+
+bool TWPartitionManager::Recreate_Logs_Dir() {
+#ifdef TW_INCLUDE_FBE
+	struct passwd pd;
+	struct passwd *pwdptr = &pd;
+	struct passwd *tempPd;
+	char pwdBuf[512];
+	int uid = 0, gid = 0;
+
+	if ((getpwnam_r("system", pwdptr, pwdBuf, sizeof(pwdBuf), &tempPd)) != 0) {
+		LOGERR("unable to get system user id\n");
+		return false;
+	} else {
+		struct group grp;
+		struct group *grpptr = &grp;
+		struct group *tempGrp;
+		char grpBuf[512];
+
+		if ((getgrnam_r("cache", grpptr, grpBuf, sizeof(grpBuf), &tempGrp)) != 0) {
+			LOGERR("unable to get cache group id\n");
+			return false;
+		} else {
+			uid = pd.pw_uid;
+			gid = grp.gr_gid;
+			std::string abLogsRecoveryDir(DATA_LOGS_DIR);
+			abLogsRecoveryDir += "/recovery/";
+
+			if (!TWFunc::Create_Dir_Recursive(abLogsRecoveryDir, S_IRWXU | S_IRWXG | S_IWGRP | S_IXGRP, uid, gid)) {
+				LOGERR("Unable to recreate %s\n", abLogsRecoveryDir.c_str());
+				return false;
+			}
+			if (setfilecon(abLogsRecoveryDir.c_str(), "u:object_r:cache_file:s0") != 0) {
+				LOGERR("Unable to set contexts for %s\n", abLogsRecoveryDir.c_str());
+				return false;
+			}
+		}
+	}
+#endif
+	return true;
+}
+
+void TWPartitionManager::Unlock_Block_Partitions() {
+	int fd, OFF = 0;
+
+	const std::string block_path = "/dev/block/";
+	DIR* d = opendir(block_path.c_str());
+	if (d != NULL) {
+		struct dirent* de;
+		while ((de = readdir(d)) != NULL) {
+			if (de->d_type == DT_BLK) {
+				std::string block_device = block_path + de->d_name;
+				if ((fd = open(block_device.c_str(), O_RDONLY | O_CLOEXEC)) < 0) {
+					LOGERR("unable to open block device %s: %s\n", block_device.c_str(), strerror(errno));
+					continue;
+				}
+				if (ioctl(fd, BLKROSET, &OFF) == -1) {
+					LOGERR("Unable to unlock %s: %s\n", block_device.c_str());
+					continue;
+				}
+				close(fd);
+			}
+		}
+		closedir(d);
+	}
+}
+
+bool TWPartitionManager::Unmap_Super_Devices() {
+	bool destroyed = false;
+#ifndef TW_EXCLUDE_APEX
+	twrpApex apex;
+	apex.Unmount();
+#endif
+	for (auto iter = Partitions.begin(); iter != Partitions.end();) {
+		LOGINFO("Checking partition: %s\n", (*iter)->Get_Mount_Point().c_str());
+		if ((*iter)->Is_Super) {
+			TWPartition *part = *iter;
+			std::string bare_partition_name = Get_Bare_Partition_Name((*iter)->Get_Mount_Point());
+			std::string blk_device_partition = bare_partition_name + PartitionManager.Get_Active_Slot_Suffix();
+			(*iter)->UnMount(false);
+			LOGINFO("removing dynamic partition: %s\n", blk_device_partition.c_str());
+			destroyed = DestroyLogicalPartition(blk_device_partition);
+			std::string cow_partition = blk_device_partition + "-cow";
+			std::string cow_partition_path = "/dev/block/mapper/" + cow_partition;
+			struct stat st;
+			if (lstat(cow_partition_path.c_str(), &st) == 0) {
+				LOGINFO("removing cow partition: %s\n", cow_partition.c_str());
+				destroyed = DestroyLogicalPartition(cow_partition);
+			}
+			iter = Partitions.erase(iter);
+			delete part;
+			if (!destroyed) {
+				return false;
+			}
+		} else {
+			++iter;
+		}
+	}
+	return true;
+}
+
+
+bool TWPartitionManager::Check_Pending_Merges() {
+	auto sm = android::snapshot::SnapshotManager::NewForFirstStageMount();
+	if (!sm) {
+		LOGERR("Unable to call snapshot manager\n");
+		return false;
+	}
+
+	if (!Unmap_Super_Devices()) {
+		LOGERR("Unable to unmap dynamic partitions.\n");
+		return false;
+	}
+
+	auto callback = [&]() -> void {
+		double progress;
+		sm->GetUpdateState(&progress);
+		LOGINFO("waiting for merge to complete: %.2f\n", progress);
+	};
+
+	LOGINFO("checking for merges\n");
+	if (!sm->HandleImminentDataWipe(callback)) {
+		LOGERR("Unable to check merge status\n");
+		return false;
+	}
+	return true;
+}
diff --git a/partitions.hpp b/partitions.hpp
new file mode 100755
index 0000000..1b2f3cb
--- /dev/null
+++ b/partitions.hpp
@@ -0,0 +1,440 @@
+/*
+	Copyright 2014 to 2021 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __TWRP_Partition_Manager
+#define __TWRP_Partition_Manager
+
+#include <map>
+#include <vector>
+#include <string>
+#include <sys/poll.h>
+#include "exclude.hpp"
+#include "tw_atomic.hpp"
+#include "progresstracking.hpp"
+#ifdef TW_INCLUDE_CRYPTO
+#include "fscrypt_policy.h"
+#endif
+#include "twrpApex.hpp"
+
+#define MAX_FSTAB_LINE_LENGTH 2048
+
+#define REPACK_ORIG_DIR "/tmp/repackorig/"
+#define REPACK_NEW_DIR "/tmp/repacknew/"
+
+using namespace std;
+
+// BasePartition is used for overriding so we can run custom, device
+// specific code.
+class BasePartition {
+	public:
+		explicit BasePartition() {}
+		virtual ~BasePartition() {}
+
+		virtual bool PreWipeEncryption() {
+			return true;
+		}
+
+		virtual bool PostWipeEncryption() {
+			return true;
+		}
+};
+BasePartition* make_partition();
+
+struct PartitionList {
+	std::string Display_Name;
+	std::string Mount_Point;
+	unsigned int selected;
+};
+
+struct Uevent_Block_Data {
+	std::string action;
+	std::string subsystem;
+	std::string block_device;
+	std::string type;
+	std::string sysfs_path;
+	int major;
+	int minor;
+};
+
+struct Flags_Map {
+	std::string Primary_Block_Device;
+	std::string Alternate_Block_Device;
+	std::string File_System;
+	std::string Flags;
+	char* fstab_line;
+};
+
+enum PartitionManager_Op {                                                    // PartitionManager Restore Mode for Raw_Read_Write()
+	PM_BACKUP = 0,
+	PM_RESTORE = 1,
+};
+
+class TWPartition;
+
+struct PartitionSettings {                                                    // Settings for backup session
+	TWPartition* Part;                                                        // Partition to pass to the partition backup loop
+	std::string Backup_Folder;                                                // Path to restore folder
+	bool adbbackup;                                                           // tell the system we are backing up over adb
+	bool adb_compression;                                                     // 0 == uncompressed, 1 == compressed
+	bool generate_digest;                                                     // tell system to create digest for partitions
+	bool generate_md5;                                                        // tell system to create md5 for partitions
+	uint64_t total_restore_size;                                              // Total size of restored backup
+	uint64_t img_bytes_remaining;                                             // remaining img/emmc bytes to backup for progress indicator
+	uint64_t file_bytes_remaining;                                            // remaining file bytes to backup for progress indicator
+	uint64_t img_time;                                                        // used to calculate how fast we backup images
+	uint64_t file_time;                                                       // used to calculate how fast we backup files
+	uint64_t img_bytes;                                                       // total image bytes of all emmc partitions
+	uint64_t file_bytes;                                                      // total file bytes of all file based partitions
+	int partition_count;                                                      // Number of partitions to restore
+	ProgressTracking *progress;                                               // Keep track of progress in GUI
+	enum PartitionManager_Op PM_Method;                                       // Current operation of backup or restore
+};
+
+enum Backup_Method_enum {
+	BM_NONE = 0,
+	BM_FILES = 1,
+	BM_DD = 2,
+	BM_FLASH_UTILS = 3,
+};
+
+// Partition class
+class TWPartition
+{
+public:
+	TWPartition();
+	virtual ~TWPartition();
+
+public:
+	bool Is_Mounted();                                                        // Checks mount to see if the partition is currently mounted
+	bool Is_File_System_Writable();                                           // Checks if the root directory of the file system can be written to
+	bool Mount(bool Display_Error);                                           // Mounts the partition if it is not mounted
+	bool UnMount(bool Display_Error);                                         // Unmounts the partition if it is mounted
+	bool ReMount(bool Display_Error);                                         // Remounts the partition
+	bool ReMount_RW(bool Display_Error);                                      // Remounts the partition with read/write access
+	bool Bind_Mount(bool Display_Error);                                      // Bind mount partition if symlink mountpoint is populated
+	bool Wipe(string New_File_System);                                        // Wipes the partition
+	bool Wipe();                                                              // Wipes the partition
+	bool Wipe_AndSec();                                                       // Wipes android secure
+	bool Wipe_Data_Cache();                                                   // Wipe /data/cache with devices that have no cache partition
+	bool Can_Repair();                                                        // Checks to see if we have everything needed to be able to repair the current file system
+	uint64_t Get_Max_FileSize();                                              // get partition maxFileSie
+	bool Repair();                                                            // Repairs the current file system
+	bool Can_Resize();                                                        // Checks to see if we have everything needed to be able to resize the current file system
+	bool Resize();                                                            // Resizes the current file system
+	bool Backup(PartitionSettings *part_settings, pid_t *tar_fork_pid);       // Backs up the partition to the folder specified
+	bool Restore(PartitionSettings *part_settings);                           // Restores the partition using the backup folder provided
+	unsigned long long Get_Restore_Size(PartitionSettings *part_settings);    // Returns the overall restore size of the backup
+	string Backup_Method_By_Name();                                           // Returns a string of the backup method for human readable output
+	bool Decrypt(string Password);                                            // Decrypts the partition, return 0 for failure and -1 for success
+	bool Wipe_Encryption();                                                   // Ignores wipe commands for /data/media devices and formats the original block device
+	void Check_FS_Type();                                                     // Checks the fs type using blkid, does not do anything on MTD / yaffs2 because this crashes on some devices
+	bool Update_Size(bool Display_Error);                                     // Updates size information
+	void Recreate_Media_Folder();                                             // Recreates the /data/media folder
+	bool Flash_Image(PartitionSettings *part_settings);                                        // Flashes an image to the partition
+	void Change_Mount_Read_Only(bool new_value);                              // Changes Mount_Read_Only to new_value
+	bool Is_Read_Only();                                                      // Check if system is read-only in TWRP
+	int Check_Lifetime_Writes();
+	int Decrypt_Adopted();
+	void Revert_Adopted();
+	void Partition_Post_Processing(bool Display_Error);                       // Apply partition specific settings after fstab processed
+	void Set_Backup_FileName(string fname);                                   // Set backup filename for partition
+	std::string Get_Backup_FileName();                                        // Get the backup filename for the partition
+	string Get_Backup_Name();                                                 // Get Backup_Name for partition
+	bool Decrypt_FBE_DE();                                                    // If FBE is present, backup exclusions are set up and DE decrypt is attempted
+	string Get_Mount_Point();												  // Return Mount_Point or directory the current partition is mounted on
+	bool Get_Super_Status();												  // Returns true if partition is a super volume mounted partitions
+	void Set_Can_Be_Backed_Up(bool val);									  // Update whether the partition can be backed up or not
+	void Set_Can_Be_Wiped(bool val);										  // Update whether the partition can be wiped or not
+	std::string Get_Display_Name();                                           // Get the display name in the gui for the partition
+	bool Is_SlotSelect();                                                     // Return whether the partition is a slot partition or not
+
+public:
+	string Current_File_System;                                               // Current file system
+	string Actual_Block_Device;                                               // Actual block device (one of primary, alternate, or decrypted)
+	string Backup_Display_Name;                                               // Name displayed in the partition list for backup selection
+	string MTD_Name;                                                          // Name of the partition for MTD devices
+	bool Is_Present;                                                          // Indicates if the partition is currently present as a block device
+	string Crypto_Key_Location;                                               // Location of the crypto key used for decrypting encrypted data partitions
+	unsigned int MTP_Storage_ID;
+	string Adopted_GUID;
+	unsigned int Adopted_Mount_Delay;
+
+protected:
+	bool Has_Data_Media;                                                      // Indicates presence of /data/media, may affect wiping and backup methods
+	void Setup_Data_Media();                                                  // Sets up a partition as a /data/media emulated storage partition
+	void Set_Block_Device(std::string block_device);                          // Allow super partition setup to change block device
+
+private:
+	bool Process_Fstab_Line(const char *fstab_line, bool Display_Error, std::map<string, Flags_Map> *twrp_flags); // Processes a fstab line
+	void Setup_Data_Partition(bool Display_Error);                            // Setup data partition after fstab processed
+	void Set_FBE_Status();													  // Set FBE status of partition
+	void Setup_Cache_Partition(bool Display_Error);                           // Setup cache partition after fstab processed
+	bool Find_Wildcard_Block_Devices(const string& Device);                   // Searches for and finds wildcard block devices
+	void Find_Actual_Block_Device();                                          // Determines the correct block device and stores it in Actual_Block_Device
+	void Apply_TW_Flag(const unsigned flag, const char* str, const bool val); // Apply custom twrp fstab flags
+	void Process_TW_Flags(char *flags, bool Display_Error, int fstab_ver);    // Process custom twrp fstab flags
+	void Process_FS_Flags(const char *str);                                   // Process standard fstab fs flags
+	void Save_FS_Flags(const string& local_File_System, int local_Mount_Flags, const string& local_Mount_Options); // Saves fs flags to a vector in case there are multiple lines in a v2 fstab with different mount flags for different file systems
+	bool Is_File_System(string File_System);                                  // Checks to see if the file system given is considered a file system
+	bool Is_Image(string File_System);                                        // Checks to see if the file system given is considered an image
+	void Setup_File_System(bool Display_Error);                               // Sets defaults for a file system partition
+	void Setup_Image();                                                       // Sets defaults for an image partition
+	void Setup_AndSec(void);                                                  // Sets up .android_secure settings
+	void Find_Real_Block_Device(string& Block_Device, bool Display_Error);    // Checks the block device given and follows symlinks until it gets to the real block device
+	unsigned long long IOCTL_Get_Block_Size();                                // Finds the partition size using ioctl
+	bool Find_Partition_Size();                                               // Finds the partition size from /proc/partitions
+	unsigned long long Get_Size_Via_du(string Path, bool Display_Error);      // Uses du to get sizes
+	bool Wipe_EXTFS(string File_System);                                      // Create an ext2/ext3/ext4 filesystem
+	bool Wipe_EXT4();                                                         // Formats using ext4, uses make_ext4fs when present
+	bool Wipe_FAT();                                                          // Formats as FAT if mkfs.fat exits otherwise rm -rf wipe
+	bool Wipe_EXFAT();                                                        // Formats as EXFAT
+	bool Wipe_MTD();                                                          // Formats as yaffs2 for MTD memory types
+	bool Wipe_RMRF();                                                         // Uses rm -rf to wipe
+	bool Wipe_F2FS();                                                         // Uses mkfs.f2fs to wipe
+	bool Wipe_NTFS();                                                         // Uses mkntfs to wipe
+	bool Wipe_Data_Without_Wiping_Media();                                    // Uses rm -rf to wipe but does not wipe /data/media
+	bool Wipe_Data_Without_Wiping_Media_Func(const string& parent);           // Uses rm -rf to wipe but does not wipe /data/media
+	void Wipe_Crypto_Key();                                                   // Wipe crypto key from either footer or block device
+	bool Backup_Tar(PartitionSettings *part_settings, pid_t *tar_fork_pid);   // Backs up using tar for file systems
+	bool Backup_Image(PartitionSettings *part_settings);                      // Backs up using raw read/write for emmc memory types
+	bool Raw_Read_Write(PartitionSettings *part_settings);
+	bool Backup_Dump_Image(PartitionSettings *part_settings);                 // Backs up using dump_image for MTD memory types
+	string Get_Restore_File_System(PartitionSettings *part_settings);         // Returns the file system that was in place at the time of the backup
+	bool Restore_Tar(PartitionSettings *part_settings);                       // Restore using tar for file systems
+	bool Restore_Image(PartitionSettings *part_settings);                     // Restore using dd for images
+	bool Check_Restore_File_MD5(const string& Filename);                      // Verifies MD5 matches for a file before restoration
+	bool Get_Size_Via_statfs(bool Display_Error);                             // Get Partition size, used, and free space using statfs
+	bool Get_Size_Via_df(bool Display_Error);                                 // Get Partition size, used, and free space using df command
+	bool Make_Dir(string Path, bool Display_Error);                           // Creates a directory if it doesn't already exist
+	bool Find_MTD_Block_Device(string MTD_Name);                              // Finds the mtd block device based on the name from the fstab
+	void Recreate_AndSec_Folder(void);                                        // Recreates the .android_secure folder
+	bool Mount_Storage_Retry(bool Display_Error);                             // Tries multiple times with a half second delay to mount a device in case storage is slow to mount
+	bool Is_Sparse_Image(const string& Filename);                             // Determines if a file is in sparse image format
+	bool Flash_Sparse_Image(const string& Filename);                          // Flashes a sparse image using simg2img
+	bool Flash_Image_FI(const string& Filename, ProgressTracking *progress);  // Flashes an image to the partition using flash_image for mtd nand
+	void ExcludeAll(const string& path);                                      // Adds an exclusion for path to both the backup and wipe exclusion lists
+
+private:
+	bool Can_Be_Mounted;                                                      // Indicates that the partition can be mounted
+	bool Can_Be_Wiped;                                                        // Indicates that the partition can be wiped
+	bool Can_Be_Backed_Up;                                                    // Indicates that the partition will show up in the backup list
+	bool Use_Rm_Rf;                                                           // Indicates that the partition will always be formatted w/ "rm -rf *"
+	bool Wipe_During_Factory_Reset;                                           // Indicates that this partition is wiped during a factory reset
+	bool Wipe_Available_in_GUI;                                               // Inidcates that the wipe can be user initiated in the GUI system
+	bool Is_SubPartition;                                                     // Indicates that this partition is a sub-partition of another partition (e.g. datadata is a sub-partition of data)
+	bool Has_SubPartition;                                                    // Indicates that this partition has a sub-partition
+	string SubPartition_Of;                                                   // Indicates which partition is the parent partition of this partition (e.g. /data is the parent partition of /datadata)
+	string Symlink_Path;                                                      // Symlink path (e.g. /data/media)
+	string Symlink_Mount_Point;                                               // /sdcard could be the symlink mount point for /data/media
+	string Mount_Point;                                                       // Mount point for this partition (e.g. /system or /data)
+	string Backup_Path;                                                       // Path for backup
+	bool Wildcard_Block_Device;                                               // If the block device contains an asterisk, we set this flag
+	string Sysfs_Entry;                                                       // For v2 fstab, if the "block device" starts with /devices then it is a sysfs entry that is handled by uevents
+	string Primary_Block_Device;                                              // Block device (e.g. /dev/block/mmcblk1p1)
+	string Alternate_Block_Device;                                            // Alternate block device (e.g. /dev/block/mmcblk1)
+	string Decrypted_Block_Device;                                            // Decrypted block device available after decryption
+	bool Removable;                                                           // Indicates if this partition is removable -- affects how often we check overall size, if present, etc.
+	int Length;                                                               // Used by make_ext4fs to leave free space at the end of the partition block for things like a crypto footer
+	unsigned long long Size;                                                  // Overall size of the partition
+	unsigned long long Used;                                                  // Overall used space
+	unsigned long long Free;                                                  // Overall free space
+	unsigned long long Backup_Size;                                           // Backup size -- may be different than used space especially when /data/media is present
+	unsigned long long Restore_Size;                                          // Restore size of the current restore operation
+	bool Can_Be_Encrypted;                                                    // This partition might be encrypted, affects error handling, can only be true if crypto support is compiled in
+	bool Is_Encrypted;                                                        // This partition is thought to be encrypted -- it wouldn't mount for some reason, only avialble with crypto support
+	bool Is_Decrypted;                                                        // This partition has successfully been decrypted
+	bool Is_FBE;                                                              // File Based Encryption is present
+	bool Mount_To_Decrypt;                                                    // Mount this partition during decrypt (/vendor, /firmware, etc in case we need proprietary libs or firmware files)
+	string Display_Name;                                                      // Display name for the GUI
+	string Backup_Name;                                                       // Backup name -- used for backup filenames
+	string Storage_Name;                                                      // Name displayed in the partition list for storage selection
+	string Backup_FileName;                                                   // Actual backup filename
+	Backup_Method_enum Backup_Method;                                         // Method used for backup
+	bool Can_Encrypt_Backup;                                                  // Indicates if this item can be encrypted during backup
+	bool Use_Userdata_Encryption;                                             // Indicates if we will use userdata encryption splitting on an encrypted backup
+	bool Has_Android_Secure;                                                  // Indicates the presence of .android_secure on this partition
+	bool Is_Storage;                                                          // Indicates if this partition is used for storage for backup, restore, and installing zips
+	bool Is_Settings_Storage;                                                 // Indicates that this storage partition is the location of the .twrps settings file and the location that is used for custom themes
+	string Storage_Path;                                                      // Indicates the path to the storage -- root indicates mount point, media/ indicates e.g. /data/media
+	string Fstab_File_System;                                                 // File system from the recovery.fstab
+	int Mount_Flags;                                                          // File system flags from recovery.fstab
+	string Mount_Options;                                                     // File system options from recovery.fstab
+	unsigned long Format_Block_Size;                                          // Block size for formatting
+	bool Ignore_Blkid;                                                        // Ignore blkid results due to superblocks lying to us on certain devices / partitions
+	bool Can_Flash_Img;                                                       // Indicates if this partition can have images flashed to it via the GUI
+	bool Mount_Read_Only;                                                     // Only mount this partition as read-only
+	bool Is_Adopted_Storage;                                                  // Indicates that this partition is for adopted storage (android_expand)
+	bool SlotSelect;                                                          // Partition has A/B slots
+	TWExclude backup_exclusions;                                              // Exclusions for file based backups
+	TWExclude wipe_exclusions;                                                // Exclusions for file based wipes (data/media devices only)
+	string Key_Directory;                                                     // Metadata key directory needed for mounting FBE encrypted data partitions using metadata encryption
+	string Original_Path;
+	bool Use_Original_Path;
+
+	struct partition_fs_flags_struct {                                        // This struct is used to store mount flags and options for different file systems for the same partition
+		string File_System;
+		int Mount_Flags;
+		string Mount_Options;
+	};
+
+	std::vector<partition_fs_flags_struct> fs_flags;                          // This vector stores mount flags and options for different file systems for the same partition
+	bool Is_Super;															  // States whether partition should be loaded from the super partition
+
+friend class TWPartitionManager;
+friend class DataManager;
+friend class GUIPartitionList;
+friend class GUIAction;
+friend class PageManager;
+};
+
+struct users_struct {
+	std::string userId;
+	std::string userName;
+	int type;
+	bool isDecrypted;
+};
+
+class TWPartitionManager
+{
+public:
+	TWPartitionManager();                                                     // Constructor for TWRPartionManager
+	~TWPartitionManager() {}
+
+public:
+	int Process_Fstab(string Fstab_Filename, bool Display_Error, bool recovery_mode); // Parses the fstab files
+	void Setup_Fstab_Partitions(bool Display_Error);                          // Populates the partitions
+	int Write_Fstab();                                                        // Creates /etc/fstab file that's used by the command line for mount commands
+	void Decrypt_Data();													  // Decrypt Data if enabled
+	void Output_Partition_Logging();                                          // Outputs partition information to the log
+	void Output_Partition(TWPartition* Part);                                 // Outputs partition details to the log
+	int Mount_By_Path(string Path, bool Display_Error);                       // Mounts partition based on path (e.g. /system)
+	int UnMount_By_Path(string Path, bool Display_Error);                     // Unmounts partition based on path
+	int Is_Mounted_By_Path(string Path);                                      // Checks if partition is mounted based on path
+	int Mount_Current_Storage(bool Display_Error);                            // Mounts the current storage location
+	int Mount_Settings_Storage(bool Display_Error);                           // Mounts the settings file storage location (usually internal)
+	TWPartition* Find_Partition_By_Path(const string& Path);                  // Returns a pointer to a partition based on path
+	TWPartition* Find_Partition_By_Block_Device(const string& Block_Device);  // Returns a pointer to a partition based on block device
+	int Check_Backup_Name(const std::string& Backup_Name, bool Display_Error, bool Must_Be_Unique); // Checks the current backup name to ensure that it is valid and optionally that a backup with that name doesn't already exist
+	int Run_Backup(bool adbbackup);                                           // Initiates a backup in the current storage
+	int Run_Restore(const string& Restore_Name);                              // Restores a backup
+	bool Write_ADB_Stream_Header(uint64_t partition_count);                   // Write ADB header over twrpbu FIFO
+	bool Write_ADB_Stream_Trailer();                                          // Write ADB trailer over twrpbu FIFO
+	void Set_Restore_Files(string Restore_Name);                              // Used to gather a list of available backup partitions for the user to select for a restore
+	int Wipe_By_Path(string Path);                                            // Wipes a partition based on path
+	int Wipe_By_Path(string Path, string New_File_System);                    // Wipes a partition based on path
+	int Factory_Reset();                                                      // Performs a factory reset
+	int Wipe_Dalvik_Cache();                                                  // Wipes dalvik cache
+	int Wipe_Rotate_Data();                                                   // Wipes rotation data --
+	int Wipe_Battery_Stats();                                                 // Wipe battery stats -- /data/system/batterystats.bin
+	int Wipe_Android_Secure();                                                // Wipes android secure
+	int Format_Data();                                                        // Really formats data on /data/media devices -- also removes encryption
+	int Wipe_Media_From_Data();                                               // Removes and recreates the media folder on /data/media devices
+	int Repair_By_Path(string Path, bool Display_Error);                      // Repairs a partition based on path
+	int Resize_By_Path(string Path, bool Display_Error);                      // Resizes a partition based on path
+	void Update_System_Details();                                             // Updates fstab, file systems, sizes, etc.
+	int Decrypt_Device(string Password, int user_id = 0);                     // Attempt to decrypt any encrypted partitions
+	void Parse_Users();                                                       // Parse FBE users
+	int usb_storage_enable(void);                                             // Enable USB storage mode
+	int usb_storage_disable(void);                                            // Disable USB storage mode
+	void Mount_All_Storage(void);                                             // Mounts all storage locations
+	void UnMount_Main_Partitions(void);                                       // Unmounts system and data if not data/media and boot if boot is mountable
+	int Partition_SDCard(void);                                               // Repartitions the sdcard
+	TWPartition *Get_Default_Storage_Partition();                             // Returns a pointer to a default storage partition
+	int Check_Backup_Cancel();                                                // Returns the value of stop_backup
+	int Cancel_Backup();                                                      // Signals partition backup to cancel
+	void Clean_Backup_Folder(string Backup_Folder);                           // Clean Backup Folder on Error
+	int Fix_Contexts();
+	void Get_Partition_List(string ListType, std::vector<PartitionList> *Partition_List);
+	int Fstab_Processed();                                                    // Indicates if the fstab has been processed or not
+	void Output_Storage_Fstab();                                              // Creates a /cache/recovery/storage.fstab file with a list of all potential storage locations for app use
+	bool Enable_MTP();                                                        // Enables MTP
+	void Add_All_MTP_Storage();                                               // Adds all storage objects for MTP
+	bool Disable_MTP();                                                       // Disables MTP
+	bool Add_MTP_Storage(string Mount_Point);                                 // Adds or removes an MTP Storage partition
+	bool Add_MTP_Storage(unsigned int Storage_ID);                            // Adds or removes an MTP Storage partition
+	bool Remove_MTP_Storage(string Mount_Point);                              // Adds or removes an MTP Storage partition
+	bool Remove_MTP_Storage(unsigned int Storage_ID);                         // Adds or removes an MTP Storage partition
+	void Translate_Partition(const char* path, const char* resource_name, const char* default_value);
+	void Translate_Partition(const char* path, const char* resource_name, const char* default_value, const char* storage_resource_name, const char* storage_default_value);
+	void Translate_Partition(const char* path, const char* resource_name, const char* default_value, const char* storage_resource_name, const char* storage_default_value, const char* backup_name, const char* backup_default);
+	void Translate_Partition_Display_Names();                                 // Updates display names based on translations
+	bool Decrypt_Adopted();                                                   // Attempt to identy and decrypt any adopted storage partitions
+	void Remove_Partition_By_Path(string Path);                               // Removes / erases a partition entry from the partition list
+	bool Prepare_All_Super_Volumes();										  // Prepare all known super volumes from super partition
+	bool Flash_Image(string& path, string& filename);                         // Flashes an image to a selected partition from the partition list
+	bool Restore_Partition(struct PartitionSettings *part_settings);          // Restore the partitions based on type
+	TWAtomicInt stop_backup;
+	void Override_Active_Slot(const string& Slot);                            // Override the active slot for repacking
+	void Set_Active_Slot(const string& Slot);                                 // Sets the active slot to A or B
+	string Get_Active_Slot_Suffix();                                          // Returns active slot _a or _b
+	string Get_Active_Slot_Display();                                         // Returns active slot A or B for display purposes
+	string Get_Android_Root_Path();                                           // Returns path of ANDROID_ROOT environment variable
+	struct pollfd uevent_pfd;                                                 // Used for uevent code
+	void Remove_Uevent_Devices(const string& sysfs_path);                     // Removes subpartitions from the Partitions vector for a matched uevent device
+	void Handle_Uevent(const Uevent_Block_Data& uevent_data);                 // Handle uevent data
+	void setup_uevent();                                                      // Opens the uevent netlink socket
+	Uevent_Block_Data get_event_block_values(char *buf, int len);             // Scans the buffer from uevent data and loads the appropriate data into a Uevent_Block_Data struct for processing
+	void read_uevent();                                                       // Reads uevent data into a buffer
+	void close_uevent();                                                      // Closes the uevent netlink socket
+	void Add_Partition(TWPartition* Part);                                    // Adds a new partition to the Partitions vector
+	std::string Get_Bare_Partition_Name(std::string Mount_Point);
+    bool Prepare_Super_Volume(TWPartition* twrpPart);					  	  // Prepare logical super partition volume for mounting
+	std::string Get_Super_Partition();										  // Get Super Partition block device path
+	void Setup_Super_Devices();												  // Setup logical dm devices on super partition
+	bool Get_Super_Status();												  // Return whether device has a super partition
+	void Setup_Super_Partition();											  // Setup the super partition for backup and restore
+	bool Recreate_Logs_Dir();                                                 // Recreate TWRP_AB_LOGS_DIR after wipe
+	std::vector<users_struct>* Get_Users_List();                              // Returns pointer to list of users
+	void Set_Crypto_State();                                                  // Sets encryption state for devices (ro.crypto.state)
+	int Set_Crypto_Type(const char* crypto_type);                             // Sets encryption type for FDE (block) and FBE (file) devices (ro.crypto.type)
+	void Unlock_Block_Partitions();                                           // Unlock all block devices after update_engine runs
+	bool Unmap_Super_Devices();                                               // Unmap super devices in TWRP
+	bool Check_Pending_Merges();                                              // Check and run pending merges on data for VAB devices
+
+private:
+	void Setup_Settings_Storage_Partition(TWPartition* Part);                 // Sets up settings storage
+	void Setup_Android_Secure_Location(TWPartition* Part);                    // Sets up .android_secure if needed
+	bool Backup_Partition(struct PartitionSettings *part_settings);           // Backup the partitions based on type
+	TWPartition* Find_Partition_By_MTP_Storage_ID(unsigned int Storage_ID);   // Returns a pointer to a partition based on MTP Storage ID
+	bool Add_Remove_MTP_Storage(TWPartition* Part, int message_type);         // Adds or removes an MTP Storage partition
+	TWPartition* Find_Next_Storage(string Path, bool Exclude_Data_Media);
+	int Open_Lun_File(string Partition_Path, string Lun_File);
+	void Post_Decrypt(const string& Block_Device);                            // Completes various post-decrypt tasks
+	void Coldboot_Scan(std::vector<string> *sysfs_entries, const string& Path, int depth); // Scans subfolders to find matches to the paths stored in sysfs_entries so we can trigger the uevent system to "re-add" devices
+	void Coldboot();                                                          // Starts the scan of the /sys/block folder
+	bool Prepare_Empty_Folder(const std::string& Folder);                     // Creates an empty folder at Folder. If the folder already exists, the folder is deleted, then created
+	pid_t mtppid;
+	bool mtp_was_enabled;
+	int mtp_write_fd;
+	pid_t tar_fork_pid;                                                       // PID of twrpTar fork
+	Backup_Method_enum Backup_Method;                                         // Method used for backup
+	std::string original_ramdisk_format;                                      // Ramdisk format of boot partition
+	std::string repacked_ramdisk_format;                                      // Ramdisk format of boot image to repack from
+	void Mark_User_Decrypted(int userID);                                     // Marks given user ID in Users_List as decrypted
+	void Check_Users_Decryption_Status();                                     // Checks to see if all users are decrypted
+
+private:
+	std::vector<TWPartition*> Partitions;                                     // Vector list of all partitions
+	string Active_Slot_Display;                                               // Current Active Slot (A or B) for display purposes
+	std::vector<users_struct> Users_List;                                     // List of FBE users
+	std::vector<std::string> Super_Partition_List;                            // Display value for super partitions
+};
+
+extern TWPartitionManager PartitionManager;
+
+#endif // __TWRP_Partition_Manager
diff --git a/pigz/Android.mk b/pigz/Android.mk
new file mode 100755
index 0000000..723367a
--- /dev/null
+++ b/pigz/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := pigz
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_CFLAGS := -Wno-unused-but-set-variable
+LOCAL_SRC_FILES = pigz.c yarn.c
+LOCAL_C_INCLUDES += $(LOCAL_PATH) \
+					external/zlib
+LOCAL_SHARED_LIBRARIES += libz libc
+
+LOCAL_POST_INSTALL_CMD := \
+    $(hide) mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/system/bin && \
+    ln -sf pigz $(TARGET_RECOVERY_ROOT_OUT)/system/bin/gunzip && \
+    ln -sf pigz $(TARGET_RECOVERY_ROOT_OUT)/system/bin/gzip && \
+    ln -sf pigz $(TARGET_RECOVERY_ROOT_OUT)/system/bin/unpigz
+
+include $(BUILD_EXECUTABLE)
diff --git a/pigz/Makefile b/pigz/Makefile
new file mode 100644
index 0000000..822902c
--- /dev/null
+++ b/pigz/Makefile
@@ -0,0 +1,58 @@
+CC=cc
+CFLAGS=-O3 -Wall -Wextra
+
+pigz: pigz.o yarn.o
+	$(CC) -o pigz pigz.o yarn.o -lpthread -lz
+	ln -f pigz unpigz
+
+pigz.o: pigz.c yarn.h
+
+yarn.o: yarn.c yarn.h
+
+dev: pigz pigzt pigzn
+
+pigzt: pigzt.o yarnt.o
+	$(CC) -o pigzt pigzt.o yarnt.o -lpthread -lz
+
+pigzt.o: pigz.c yarn.h
+	$(CC) -Wall -O3 -DDEBUG -g -c -o pigzt.o pigz.c
+
+yarnt.o: yarn.c yarn.h
+	$(CC) -Wall -O3 -DDEBUG -g -c -o yarnt.o yarn.c
+
+pigzn: pigzn.o
+	$(CC) -o pigzn pigzn.o -lz
+
+pigzn.o: pigz.c
+	$(CC) -Wall -O3 -DDEBUG -DNOTHREAD -g -c -o pigzn.o pigz.c
+
+test: pigz
+	./pigz -kf pigz.c ; ./pigz -t pigz.c.gz
+	./pigz -kfb 32 pigz.c ; ./pigz -t pigz.c.gz
+	./pigz -kfp 1 pigz.c ; ./pigz -t pigz.c.gz
+	./pigz -kfz pigz.c ; ./pigz -t pigz.c.zz
+	./pigz -kfK pigz.c ; ./pigz -t pigz.c.zip
+	printf "" | ./pigz -cdf | wc -c | test `cat` -eq 0
+	printf "x" | ./pigz -cdf | wc -c | test `cat` -eq 1
+	printf "xy" | ./pigz -cdf | wc -c | test `cat` -eq 2
+	printf "xyz" | ./pigz -cdf | wc -c | test `cat` -eq 3
+	(printf "w" | gzip ; printf "x") | ./pigz -cdf | wc -c | test `cat` -eq 2
+	(printf "w" | gzip ; printf "xy") | ./pigz -cdf | wc -c | test `cat` -eq 3
+	(printf "w" | gzip ; printf "xyz") | ./pigz -cdf | wc -c | test `cat` -eq 4
+	-@if test "`whereis compress | grep /`" != ""; then \
+	  echo 'compress -f < pigz.c | ./unpigz | cmp - pigz.c' ;\
+	  compress -f < pigz.c | ./unpigz | cmp - pigz.c ;\
+	fi
+	@rm -f pigz.c.gz pigz.c.zz pigz.c.zip
+
+tests: dev test
+	./pigzn -kf pigz.c ; ./pigz -t pigz.c.gz
+	@rm -f pigz.c.gz
+
+docs: pigz.pdf
+
+pigz.pdf: pigz.1
+	groff -mandoc -f H -T ps pigz.1 | ps2pdf - pigz.pdf
+
+clean:
+	@rm -f *.o pigz unpigz pigzn pigzt pigz.c.gz pigz.c.zz pigz.c.zip
diff --git a/pigz/README b/pigz/README
new file mode 100644
index 0000000..bf29102
--- /dev/null
+++ b/pigz/README
@@ -0,0 +1,48 @@
+pigz 2.2.5 (xx Apr 2012) by Mark Adler
+
+pigz, which stands for Parallel Implementation of GZip, is a fully functional
+replacement for gzip that exploits multiple processors and multiple cores to
+the hilt when compressing data.
+
+pigz was written by Mark Adler, and uses the zlib and pthread libraries.
+
+This version of pigz is written to be portable across Unix-style operating
+systems that provide the zlib and pthread libraries.
+
+Type "make" in this directory to build the "pigz" executable.  You can then
+install the executable wherever you like in your path (e.g. /usr/local/bin/).
+Type "pigz" to see the command help and all of the command options.
+
+The latest version of pigz can be found at http://zlib.net/pigz/ .  You need
+zlib version 1.2.3 or later to compile pigz.  You can find the latest version
+of zlib at http://zlib.net/ .  You can look in pigz.c for the change history.
+
+Questions, comments, bug reports, fixes, etc. can be emailed to Mark at his
+address in the license below.
+
+The license from pigz.c is copied here:
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the author be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  Mark Adler
+  madler@alumni.caltech.edu
+
+  Mark accepts donations for providing this software.  Donations are not
+  required or expected.  Any amount that you feel is appropriate would be
+  appreciated.  You can use this link:
+
+  https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=536055
diff --git a/pigz/pigz.1 b/pigz/pigz.1
new file mode 100644
index 0000000..8d75ca2
--- /dev/null
+++ b/pigz/pigz.1
@@ -0,0 +1,193 @@
+.TH PIGZ 1 local
+.SH NAME
+pigz, unpigz \- compress or expand files
+.SH SYNOPSIS
+.ll +8
+.B pigz
+.RB [ " \-cdfhikKlLnNqrRtTz0..9 " ]
+[
+.B -b
+.I blocksize
+]
+[
+.B -p
+.I threads
+]
+[
+.B -S
+.I suffix
+]
+[
+.I "name \&..."
+]
+.ll -8
+.br
+.B unpigz
+.RB [ " \-cfhikKlLnNqrRtTz " ]
+[
+.B -b
+.I blocksize
+]
+[
+.B -p
+.I threads
+]
+[
+.B -S
+.I suffix
+]
+[
+.I "name \&..."
+]
+.SH DESCRIPTION
+.I Pigz
+compresses using threads to make use of multiple processors and cores.
+The input is broken up into 128 KB chunks with each compressed in parallel.
+The individual check value for each chunk is also calculated in parallel.
+The compressed data is written in order to the output, and a combined check
+value is calculated from the individual check values.
+.PP
+The compressed data format generated is in the gzip, zlib, or single-entry
+zip format using the deflate compression method.  The compression produces
+partial raw deflate streams which are concatenated by a single write thread
+and wrapped with the appropriate header and trailer, where the trailer
+contains the combined check value.
+.PP
+Each partial raw deflate stream is terminated by an empty stored block
+(using the Z_SYNC_FLUSH option of zlib), in order to end that partial bit
+stream at a byte boundary.  That allows the partial streams to be
+concatenated simply as sequences of bytes.  This adds a very small four to
+five byte overhead to the output for each input chunk.
+.PP
+The default input block size is 128K, but can be changed with the
+.B -b
+option.  The number of compress threads is set by default to the number
+of online processors,
+which can be changed using the
+.B -p
+option.  Specifying
+.B -p 1
+avoids the use of threads entirely.
+.PP
+The input blocks, while compressed independently, have the last 32K of the
+previous block loaded as a preset dictionary to preserve the compression
+effectiveness of deflating in a single thread.  This can be turned off using
+the
+.B -i
+or
+.B --independent
+option, so that the blocks can be decompressed
+independently for partial error recovery or for random access.
+.PP
+Decompression can't be parallelized, at least not without specially prepared
+deflate streams for that purpose.  As a result,
+.I pigz
+uses a single thread
+(the main thread) for decompression, but will create three other threads for
+reading, writing, and check calculation, which can speed up decompression
+under some circumstances.  Parallel decompression can be turned off by
+specifying one process
+(
+.B -dp 1
+or
+.B -tp 1
+).
+.PP
+Compressed files can be restored to their original form using
+.I pigz -d
+or
+.I unpigz.
+
+.SH OPTIONS
+.TP
+.B -# --fast --best
+Regulate the speed of compression using the specified digit
+.IR # ,
+where
+.B \-1
+or
+.B \-\-fast
+indicates the fastest compression method (less compression)
+and
+.B \-9
+or
+.B \-\-best
+indicates the slowest compression method (best compression).
+Level 0 is no compression.
+.TP
+.B -b --blocksize mmm
+Set compression block size to mmmK (default 128KiB).
+.TP
+.B -c --stdout --to-stdout
+Write all processed output to stdout (won't delete).
+.TP
+.B -d --decompress --uncompress
+Decompress the compressed input.
+.TP
+.B -f --force
+Force overwrite, compress .gz, links, and to terminal.
+.TP
+.B -h --help
+Display a help screen and quit.
+.TP
+.B -i --independent
+Compress blocks independently for damage recovery.
+.TP
+.B -k --keep
+Do not delete original file after processing.
+.TP
+.B -K --zip
+Compress to PKWare zip (.zip) single entry format.
+.TP
+.B -l --list
+List the contents of the compressed input.
+.TP
+.B -L --license
+Display the
+.I pigz
+license and quit.
+.TP
+.B -n --no-name
+Do not store or restore file name in/from header.
+.TP
+.B -N --name
+Store/restore file name and mod time in/from header.
+.TP
+.B -p --processes n
+Allow up to n processes (default is the number of online processors)
+.TP
+.B -q --quiet --silent
+Print no messages, even on error.
+.TP
+.B -r --recursive
+Process the contents of all subdirectories.
+.TP
+.B -R --rsyncable
+Input-determined block locations for rsync.
+.TP
+.B -S --suffix .sss
+Use suffix .sss instead of .gz (for compression).
+.TP
+.B -t --test
+Test the integrity of the compressed input.
+.TP
+.B -T --no-time
+Do not store or restore mod time in/from header.
+.TP
+.B -v --verbose
+Provide more verbose output.
+.TP
+.B -V --version
+Show the version of pigz.
+.TP
+.B -z --zlib
+Compress to zlib (.zz) instead of gzip format.
+.TP
+.B --
+All arguments after "--" are treated as file names (for names that start with "-")
+.SH "COPYRIGHT NOTICE"
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the author be held liable for any damages
+arising from the use of this software.
+.PP
+Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Mark Adler <madler@alumni.caltech.edu>
diff --git a/pigz/pigz.c b/pigz/pigz.c
new file mode 100644
index 0000000..5416bc9
--- /dev/null
+++ b/pigz/pigz.c
@@ -0,0 +1,3682 @@
+/* pigz.c -- parallel implementation of gzip
+ * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Mark Adler
+ * Version 2.2.5  28 Jul 2012  Mark Adler
+ */
+
+/*
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the author be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  Mark Adler
+  madler@alumni.caltech.edu
+
+  Mark accepts donations for providing this software.  Donations are not
+  required or expected.  Any amount that you feel is appropriate would be
+  appreciated.  You can use this link:
+
+  https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=536055
+
+ */
+
+/* Version history:
+   1.0    17 Jan 2007  First version, pipe only
+   1.1    28 Jan 2007  Avoid void * arithmetic (some compilers don't get that)
+                       Add note about requiring zlib 1.2.3
+                       Allow compression level 0 (no compression)
+                       Completely rewrite parallelism -- add a write thread
+                       Use deflateSetDictionary() to make use of history
+                       Tune argument defaults to best performance on four cores
+   1.2.1   1 Feb 2007  Add long command line options, add all gzip options
+                       Add debugging options
+   1.2.2  19 Feb 2007  Add list (--list) function
+                       Process file names on command line, write .gz output
+                       Write name and time in gzip header, set output file time
+                       Implement all command line options except --recursive
+                       Add --keep option to prevent deleting input files
+                       Add thread tracing information with -vv used
+                       Copy crc32_combine() from zlib (shared libraries issue)
+   1.3    25 Feb 2007  Implement --recursive
+                       Expand help to show all options
+                       Show help if no arguments or output piping are provided
+                       Process options in GZIP environment variable
+                       Add progress indicator to write thread if --verbose
+   1.4     4 Mar 2007  Add --independent to facilitate damaged file recovery
+                       Reallocate jobs for new --blocksize or --processes
+                       Do not delete original if writing to stdout
+                       Allow --processes 1, which does no threading
+                       Add NOTHREAD define to compile without threads
+                       Incorporate license text from zlib in source code
+   1.5    25 Mar 2007  Reinitialize jobs for new compression level
+                       Copy attributes and owner from input file to output file
+                       Add decompression and testing
+                       Add -lt (or -ltv) to show all entries and proper lengths
+                       Add decompression, testing, listing of LZW (.Z) files
+                       Only generate and show trace log if DEBUG defined
+                       Take "-" argument to mean read file from stdin
+   1.6    30 Mar 2007  Add zlib stream compression (--zlib), and decompression
+   1.7    29 Apr 2007  Decompress first entry of a zip file (if deflated)
+                       Avoid empty deflate blocks at end of deflate stream
+                       Show zlib check value (Adler-32) when listing
+                       Don't complain when decompressing empty file
+                       Warn about trailing junk for gzip and zlib streams
+                       Make listings consistent, ignore gzip extra flags
+                       Add zip stream compression (--zip)
+   1.8    13 May 2007  Document --zip option in help output
+   2.0    19 Oct 2008  Complete rewrite of thread usage and synchronization
+                       Use polling threads and a pool of memory buffers
+                       Remove direct pthread library use, hide in yarn.c
+   2.0.1  20 Oct 2008  Check version of zlib at compile time, need >= 1.2.3
+   2.1    24 Oct 2008  Decompress with read, write, inflate, and check threads
+                       Remove spurious use of ctime_r(), ctime() more portable
+                       Change application of job->calc lock to be a semaphore
+                       Detect size of off_t at run time to select %lu vs. %llu
+                       #define large file support macro even if not __linux__
+                       Remove _LARGEFILE64_SOURCE, _FILE_OFFSET_BITS is enough
+                       Detect file-too-large error and report, blame build
+                       Replace check combination routines with those from zlib
+   2.1.1  28 Oct 2008  Fix a leak for files with an integer number of blocks
+                       Update for yarn 1.1 (yarn_prefix and yarn_abort)
+   2.1.2  30 Oct 2008  Work around use of beta zlib in production systems
+   2.1.3   8 Nov 2008  Don't use zlib combination routines, put back in pigz
+   2.1.4   9 Nov 2008  Fix bug when decompressing very short files
+   2.1.5  20 Jul 2009  Added 2008, 2009 to --license statement
+                       Allow numeric parameter immediately after -p or -b
+                       Enforce parameter after -p, -b, -s, before other options
+                       Enforce numeric parameters to have only numeric digits
+                       Try to determine the number of processors for -p default
+                       Fix --suffix short option to be -S to match gzip [Bloch]
+                       Decompress if executable named "unpigz" [Amundsen]
+                       Add a little bit of testing to Makefile
+   2.1.6  17 Jan 2010  Added pigz.spec to distribution for RPM systems [Brown]
+                       Avoid some compiler warnings
+                       Process symbolic links if piping to stdout [Hoffstätte]
+                       Decompress if executable named "gunzip" [Hoffstätte]
+                       Allow ".tgz" suffix [Chernookiy]
+                       Fix adler32 comparison on .zz files
+   2.1.7  17 Dec 2011  Avoid unused parameter warning in reenter()
+                       Don't assume 2's complement ints in compress_thread()
+                       Replicate gzip -cdf cat-like behavior
+                       Replicate gzip -- option to suppress option decoding
+                       Test output from make test instead of showing it
+                       Updated pigz.spec to install unpigz, pigz.1 [Obermaier]
+                       Add PIGZ environment variable [Mueller]
+                       Replicate gzip suffix search when decoding or listing
+                       Fix bug in load() to set in_left to zero on end of file
+                       Do not check suffix when input file won't be modified
+                       Decompress to stdout if name is "*cat" [Hayasaka]
+                       Write data descriptor signature to be like Info-ZIP
+                       Update and sort options list in help
+                       Use CC variable for compiler in Makefile
+                       Exit with code 2 if a warning has been issued
+                       Fix thread synchronization problem when tracing
+                       Change macro name MAX to MAX2 to avoid library conflicts
+                       Determine number of processors on HP-UX [Lloyd]
+   2.2    31 Dec 2011  Check for expansion bound busting (e.g. modified zlib)
+                       Make the "threads" list head global variable volatile
+                       Fix construction and printing of 32-bit check values
+                       Add --rsyncable functionality
+   2.2.1   1 Jan 2012  Fix bug in --rsyncable buffer management
+   2.2.2   1 Jan 2012  Fix another bug in --rsyncable buffer management
+   2.2.3  15 Jan 2012  Remove volatile in yarn.c
+                       Reduce the number of input buffers
+                       Change initial rsyncable hash to comparison value
+                       Improve the efficiency of arriving at a byte boundary
+                       Add thread portability #defines from yarn.c
+                       Have rsyncable compression be independent of threading
+                       Fix bug where constructed dictionaries not being used
+   2.2.4  11 Mar 2012  Avoid some return value warnings
+                       Improve the portability of printing the off_t type
+                       Check for existence of compress binary before using
+                       Update zlib version checking to 1.2.6 for new functions
+                       Fix bug in zip (-K) output
+                       Fix license in pigz.spec
+                       Remove thread portability #defines in pigz.c
+   2.2.5  28 Jul 2012  Avoid race condition in free_pool()
+                       Change suffix to .tar when decompressing or listing .tgz
+                       Print name of executable in error messages
+                       Show help properly when the name is unpigz or gunzip
+                       Fix permissions security problem before output is closed
+ */
+
+#define VERSION "pigz 2.2.5\n"
+
+/* To-do:
+    - make source portable for Windows, VMS, etc. (see gzip source code)
+    - make build portable (currently good for Unixish)
+ */
+
+/*
+   pigz compresses using threads to make use of multiple processors and cores.
+   The input is broken up into 128 KB chunks with each compressed in parallel.
+   The individual check value for each chunk is also calculated in parallel.
+   The compressed data is written in order to the output, and a combined check
+   value is calculated from the individual check values.
+
+   The compressed data format generated is in the gzip, zlib, or single-entry
+   zip format using the deflate compression method.  The compression produces
+   partial raw deflate streams which are concatenated by a single write thread
+   and wrapped with the appropriate header and trailer, where the trailer
+   contains the combined check value.
+
+   Each partial raw deflate stream is terminated by an empty stored block
+   (using the Z_SYNC_FLUSH option of zlib), in order to end that partial bit
+   stream at a byte boundary, unless that partial stream happens to already end
+   at a byte boundary (the latter requires zlib 1.2.6 or later).  Ending on a
+   byte boundary allows the partial streams to be concatenated simply as
+   sequences of bytes.  This adds a very small four to five byte overhead
+   (average 3.75 bytes) to the output for each input chunk.
+
+   The default input block size is 128K, but can be changed with the -b option.
+   The number of compress threads is set by default to 8, which can be changed
+   using the -p option.  Specifying -p 1 avoids the use of threads entirely.
+   pigz will try to determine the number of processors in the machine, in which
+   case if that number is two or greater, pigz will use that as the default for
+   -p instead of 8.
+
+   The input blocks, while compressed independently, have the last 32K of the
+   previous block loaded as a preset dictionary to preserve the compression
+   effectiveness of deflating in a single thread.  This can be turned off using
+   the --independent or -i option, so that the blocks can be decompressed
+   independently for partial error recovery or for random access.
+
+   Decompression can't be parallelized, at least not without specially prepared
+   deflate streams for that purpose.  As a result, pigz uses a single thread
+   (the main thread) for decompression, but will create three other threads for
+   reading, writing, and check calculation, which can speed up decompression
+   under some circumstances.  Parallel decompression can be turned off by
+   specifying one process (-dp 1 or -tp 1).
+
+   pigz requires zlib 1.2.1 or later to allow setting the dictionary when doing
+   raw deflate.  Since zlib 1.2.3 corrects security vulnerabilities in zlib
+   version 1.2.1 and 1.2.2, conditionals check for zlib 1.2.3 or later during
+   the compilation of pigz.c.  zlib 1.2.4 includes some improvements to
+   Z_FULL_FLUSH and deflateSetDictionary() that permit identical output for
+   pigz with and without threads, which is not possible with zlib 1.2.3.  This
+   may be important for uses of pigz -R where small changes in the contents
+   should result in small changes in the archive for rsync.  Note that due to
+   the details of how the lower levels of compression result in greater speed,
+   compression level 3 and below does not permit identical pigz output with
+   and without threads.
+
+   pigz uses the POSIX pthread library for thread control and communication,
+   through the yarn.h interface to yarn.c.  yarn.c can be replaced with
+   equivalent implementations using other thread libraries.  pigz can be
+   compiled with NOTHREAD #defined to not use threads at all (in which case
+   pigz will not be able to live up to the "parallel" in its name).
+ */
+
+/*
+   Details of parallel compression implementation:
+
+   When doing parallel compression, pigz uses the main thread to read the input
+   in 'size' sized chunks (see -b), and puts those in a compression job list,
+   each with a sequence number to keep track of the ordering.  If it is not the
+   first chunk, then that job also points to the previous input buffer, from
+   which the last 32K will be used as a dictionary (unless -i is specified).
+   This sets a lower limit of 32K on 'size'.
+
+   pigz launches up to 'procs' compression threads (see -p).  Each compression
+   thread continues to look for jobs in the compression list and perform those
+   jobs until instructed to return.  When a job is pulled, the dictionary, if
+   provided, will be loaded into the deflate engine and then that input buffer
+   is dropped for reuse.  Then the input data is compressed into an output
+   buffer that grows in size if necessary to hold the compressed data. The job
+   is then put into the write job list, sorted by the sequence number. The
+   compress thread however continues to calculate the check value on the input
+   data, either a CRC-32 or Adler-32, possibly in parallel with the write
+   thread writing the output data.  Once that's done, the compress thread drops
+   the input buffer and also releases the lock on the check value so that the
+   write thread can combine it with the previous check values.  The compress
+   thread has then completed that job, and goes to look for another.
+
+   All of the compress threads are left running and waiting even after the last
+   chunk is processed, so that they can support the next input to be compressed
+   (more than one input file on the command line).  Once pigz is done, it will
+   call all the compress threads home (that'll do pig, that'll do).
+
+   Before starting to read the input, the main thread launches the write thread
+   so that it is ready pick up jobs immediately.  The compress thread puts the
+   write jobs in the list in sequence sorted order, so that the first job in
+   the list is always has the lowest sequence number.  The write thread waits
+   for the next write job in sequence, and then gets that job.  The job still
+   holds its input buffer, from which the write thread gets the input buffer
+   length for use in check value combination.  Then the write thread drops that
+   input buffer to allow its reuse.  Holding on to the input buffer until the
+   write thread starts also has the benefit that the read and compress threads
+   can't get way ahead of the write thread and build up a large backlog of
+   unwritten compressed data.  The write thread will write the compressed data,
+   drop the output buffer, and then wait for the check value to be unlocked
+   by the compress thread.  Then the write thread combines the check value for
+   this chunk with the total check value for eventual use in the trailer.  If
+   this is not the last chunk, the write thread then goes back to look for the
+   next output chunk in sequence.  After the last chunk, the write thread
+   returns and joins the main thread.  Unlike the compress threads, a new write
+   thread is launched for each input stream.  The write thread writes the
+   appropriate header and trailer around the compressed data.
+
+   The input and output buffers are reused through their collection in pools.
+   Each buffer has a use count, which when decremented to zero returns the
+   buffer to the respective pool.  Each input buffer has up to three parallel
+   uses: as the input for compression, as the data for the check value
+   calculation, and as a dictionary for compression.  Each output buffer has
+   only one use, which is as the output of compression followed serially as
+   data to be written.  The input pool is limited in the number of buffers, so
+   that reading does not get way ahead of compression and eat up memory with
+   more input than can be used.  The limit is approximately two times the
+   number of compression threads.  In the case that reading is fast as compared
+   to compression, that number allows a second set of buffers to be read while
+   the first set of compressions are being performed.  The number of output
+   buffers is not directly limited, but is indirectly limited by the release of
+   input buffers to about the same number.
+ */
+
+/* use large file functions if available */
+#define _FILE_OFFSET_BITS 64
+
+/* included headers and what is expected from each */
+#include <stdio.h>      /* fflush(), fprintf(), fputs(), getchar(), putc(), */
+                        /* puts(), printf(), vasprintf(), stderr, EOF, NULL,
+                           SEEK_END, size_t, off_t */
+#include <stdlib.h>     /* exit(), malloc(), free(), realloc(), atol(), */
+                        /* atoi(), getenv() */
+#include <stdarg.h>     /* va_start(), va_end(), va_list */
+#include <string.h>     /* memset(), memchr(), memcpy(), strcmp(), strcpy() */
+                        /* strncpy(), strlen(), strcat(), strrchr() */
+#include <errno.h>      /* errno, EEXIST */
+#include <assert.h>     /* assert() */
+#include <time.h>       /* ctime(), time(), time_t, mktime() */
+#include <signal.h>     /* signal(), SIGINT */
+#include <sys/types.h>  /* ssize_t */
+#include <sys/stat.h>   /* chmod(), stat(), fstat(), lstat(), struct stat, */
+                        /* S_IFDIR, S_IFLNK, S_IFMT, S_IFREG */
+#include <sys/time.h>   /* utimes(), gettimeofday(), struct timeval */
+#include <unistd.h>     /* unlink(), _exit(), read(), write(), close(), */
+                        /* lseek(), isatty(), chown() */
+#include <fcntl.h>      /* open(), O_CREAT, O_EXCL, O_RDONLY, O_TRUNC, */
+                        /* O_WRONLY */
+#include <dirent.h>     /* opendir(), readdir(), closedir(), DIR, */
+                        /* struct dirent */
+#include <limits.h>     /* PATH_MAX, UINT_MAX */
+#if __STDC_VERSION__-0 >= 199901L || __GNUC__-0 >= 3
+#  include <inttypes.h> /* intmax_t */
+#endif
+
+#ifdef __hpux
+#  include <sys/param.h>
+#  include <sys/pstat.h>
+#endif
+
+#include "zlib.h"       /* deflateInit2(), deflateReset(), deflate(), */
+                        /* deflateEnd(), deflateSetDictionary(), crc32(),
+                           inflateBackInit(), inflateBack(), inflateBackEnd(),
+                           Z_DEFAULT_COMPRESSION, Z_DEFAULT_STRATEGY,
+                           Z_DEFLATED, Z_NO_FLUSH, Z_NULL, Z_OK,
+                           Z_SYNC_FLUSH, z_stream */
+#if !defined(ZLIB_VERNUM) || ZLIB_VERNUM < 0x1230
+#  error Need zlib version 1.2.3 or later
+#endif
+
+#ifndef NOTHREAD
+#  include "yarn.h"     /* thread, launch(), join(), join_all(), */
+                        /* lock, new_lock(), possess(), twist(), wait_for(),
+                           release(), peek_lock(), free_lock(), yarn_name */
+#endif
+
+/* for local functions and globals */
+#define local static
+
+/* prevent end-of-line conversions on MSDOSish operating systems */
+#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
+#  include <io.h>       /* setmode(), O_BINARY */
+#  define SET_BINARY_MODE(fd) setmode(fd, O_BINARY)
+#else
+#  define SET_BINARY_MODE(fd)
+#endif
+
+/* release an allocated pointer, if allocated, and mark as unallocated */
+#define RELEASE(ptr) \
+    do { \
+        if ((ptr) != NULL) { \
+            free(ptr); \
+            ptr = NULL; \
+        } \
+    } while (0)
+
+/* sliding dictionary size for deflate */
+#define DICT 32768U
+
+/* largest power of 2 that fits in an unsigned int -- used to limit requests
+   to zlib functions that use unsigned int lengths */
+#define MAXP2 (UINT_MAX - (UINT_MAX >> 1))
+
+/* rsyncable constants -- RSYNCBITS is the number of bits in the mask for
+   comparison.  For random input data, there will be a hit on average every
+   1<<RSYNCBITS bytes.  So for an RSYNCBITS of 12, there will be an average of
+   one hit every 4096 bytes, resulting in a mean block size of 4096.  RSYNCMASK
+   is the resulting bit mask.  RSYNCHIT is what the hash value is compared to
+   after applying the mask.
+
+   The choice of 12 for RSYNCBITS is consistent with the original rsyncable
+   patch for gzip which also uses a 12-bit mask.  This results in a relatively
+   small hit to compression, on the order of 1.5% to 3%.  A mask of 13 bits can
+   be used instead if a hit of less than 1% to the compression is desired, at
+   the expense of more blocks transmitted for rsync updates.  (Your mileage may
+   vary.)
+
+   This implementation of rsyncable uses a different hash algorithm than what
+   the gzip rsyncable patch uses in order to provide better performance in
+   several regards.  The algorithm is simply to shift the hash value left one
+   bit and exclusive-or that with the next byte.  This is masked to the number
+   of hash bits (RSYNCMASK) and compared to all ones except for a zero in the
+   top bit (RSYNCHIT). This rolling hash has a very small window of 19 bytes
+   (RSYNCBITS+7).  The small window provides the benefit of much more rapid
+   resynchronization after a change, than does the 4096-byte window of the gzip
+   rsyncable patch.
+
+   The comparison value is chosen to avoid matching any repeated bytes or short
+   sequences.  The gzip rsyncable patch on the other hand uses a sum and zero
+   for comparison, which results in certain bad behaviors, such as always
+   matching everywhere in a long sequence of zeros.  Such sequences occur
+   frequently in tar files.
+
+   This hash efficiently discards history older than 19 bytes simply by
+   shifting that data past the top of the mask -- no history needs to be
+   retained to undo its impact on the hash value, as is needed for a sum.
+
+   The choice of the comparison value (RSYNCHIT) has the virtue of avoiding
+   extremely short blocks.  The shortest block is five bytes (RSYNCBITS-7) from
+   hit to hit, and is unlikely.  Whereas with the gzip rsyncable algorithm,
+   blocks of one byte are not only possible, but in fact are the most likely
+   block size.
+
+   Thanks and acknowledgement to Kevin Day for his experimentation and insights
+   on rsyncable hash characteristics that led to some of the choices here.
+ */
+#define RSYNCBITS 12
+#define RSYNCMASK ((1U << RSYNCBITS) - 1)
+#define RSYNCHIT (RSYNCMASK >> 1)
+
+/* initial pool counts and sizes -- INBUFS is the limit on the number of input
+   spaces as a function of the number of processors (used to throttle the
+   creation of compression jobs), OUTPOOL is the initial size of the output
+   data buffer, chosen to make resizing of the buffer very unlikely */
+#define INBUFS(p) (((p)<<1)+3)
+#define OUTPOOL(s) ((s)+((s)>>4))
+
+/* globals (modified by main thread only when it's the only thread) */
+local char *prog;           /* name by which pigz was invoked */
+local int ind;              /* input file descriptor */
+local int outd;             /* output file descriptor */
+local char in[PATH_MAX+1];  /* input file name (accommodate recursion) */
+local char *out = NULL;     /* output file name (allocated if not NULL) */
+local int verbosity;        /* 0 = quiet, 1 = normal, 2 = verbose, 3 = trace */
+local int headis;           /* 1 to store name, 2 to store date, 3 both */
+local int pipeout;          /* write output to stdout even if file */
+local int keep;             /* true to prevent deletion of input file */
+local int force;            /* true to overwrite, compress links, cat */
+local int form;             /* gzip = 0, zlib = 1, zip = 2 or 3 */
+local unsigned char magic1; /* first byte of possible header when decoding */
+local int recurse;          /* true to dive down into directory structure */
+local char *sufx;           /* suffix to use (".gz" or user supplied) */
+local char *name;           /* name for gzip header */
+local time_t mtime;         /* time stamp from input file for gzip header */
+local int list;             /* true to list files instead of compress */
+local int first = 1;        /* true if we need to print listing header */
+local int decode;           /* 0 to compress, 1 to decompress, 2 to test */
+local int level;            /* compression level */
+local int rsync;            /* true for rsync blocking */
+local int procs;            /* maximum number of compression threads (>= 1) */
+local int setdict;          /* true to initialize dictionary in each thread */
+local size_t size;          /* uncompressed input size per thread (>= 32K) */
+local int warned = 0;       /* true if a warning has been given */
+
+/* saved gzip/zip header data for decompression, testing, and listing */
+local time_t stamp;                 /* time stamp from gzip header */
+local char *hname = NULL;           /* name from header (allocated) */
+local unsigned long zip_crc;        /* local header crc */
+local unsigned long zip_clen;       /* local header compressed length */
+local unsigned long zip_ulen;       /* local header uncompressed length */
+
+/* display a complaint with the program name on stderr */
+local int complain(char *fmt, ...)
+{
+    va_list ap;
+
+    if (verbosity > 0) {
+        fprintf(stderr, "%s: ", prog);
+        va_start(ap, fmt);
+        vfprintf(stderr, fmt, ap);
+        va_end(ap);
+        putc('\n', stderr);
+        fflush(stderr);
+        warned = 1;
+    }
+    return 0;
+}
+
+/* exit with error, delete output file if in the middle of writing it */
+local int bail(char *why, char *what)
+{
+    if (outd != -1 && out != NULL)
+        unlink(out);
+    complain("abort: %s%s", why, what);
+    exit(1);
+    return 0;
+}
+
+#ifdef DEBUG
+
+/* starting time of day for tracing */
+local struct timeval start;
+
+/* trace log */
+local struct log {
+    struct timeval when;    /* time of entry */
+    char *msg;              /* message */
+    struct log *next;       /* next entry */
+} *log_head, **log_tail = NULL;
+#ifndef NOTHREAD
+  local lock *log_lock = NULL;
+#endif
+
+/* maximum log entry length */
+#define MAXMSG 256
+
+/* set up log (call from main thread before other threads launched) */
+local void log_init(void)
+{
+    if (log_tail == NULL) {
+#ifndef NOTHREAD
+        log_lock = new_lock(0);
+#endif
+        log_head = NULL;
+        log_tail = &log_head;
+    }
+}
+
+/* add entry to trace log */
+local void log_add(char *fmt, ...)
+{
+    struct timeval now;
+    struct log *me;
+    va_list ap;
+    char msg[MAXMSG];
+
+    gettimeofday(&now, NULL);
+    me = malloc(sizeof(struct log));
+    if (me == NULL)
+        bail("not enough memory", "");
+    me->when = now;
+    va_start(ap, fmt);
+    vsnprintf(msg, MAXMSG, fmt, ap);
+    va_end(ap);
+    me->msg = malloc(strlen(msg) + 1);
+    if (me->msg == NULL) {
+        free(me);
+        bail("not enough memory", "");
+    }
+    strcpy(me->msg, msg);
+    me->next = NULL;
+#ifndef NOTHREAD
+    assert(log_lock != NULL);
+    possess(log_lock);
+#endif
+    *log_tail = me;
+    log_tail = &(me->next);
+#ifndef NOTHREAD
+    twist(log_lock, BY, +1);
+#endif
+}
+
+/* pull entry from trace log and print it, return false if empty */
+local int log_show(void)
+{
+    struct log *me;
+    struct timeval diff;
+
+    if (log_tail == NULL)
+        return 0;
+#ifndef NOTHREAD
+    possess(log_lock);
+#endif
+    me = log_head;
+    if (me == NULL) {
+#ifndef NOTHREAD
+        release(log_lock);
+#endif
+        return 0;
+    }
+    log_head = me->next;
+    if (me->next == NULL)
+        log_tail = &log_head;
+#ifndef NOTHREAD
+    twist(log_lock, BY, -1);
+#endif
+    diff.tv_usec = me->when.tv_usec - start.tv_usec;
+    diff.tv_sec = me->when.tv_sec - start.tv_sec;
+    if (diff.tv_usec < 0) {
+        diff.tv_usec += 1000000L;
+        diff.tv_sec--;
+    }
+    fprintf(stderr, "trace %ld.%06ld %s\n",
+            (long)diff.tv_sec, (long)diff.tv_usec, me->msg);
+    fflush(stderr);
+    free(me->msg);
+    free(me);
+    return 1;
+}
+
+/* release log resources (need to do log_init() to use again) */
+local void log_free(void)
+{
+    struct log *me;
+
+    if (log_tail != NULL) {
+#ifndef NOTHREAD
+        possess(log_lock);
+#endif
+        while ((me = log_head) != NULL) {
+            log_head = me->next;
+            free(me->msg);
+            free(me);
+        }
+#ifndef NOTHREAD
+        twist(log_lock, TO, 0);
+        free_lock(log_lock);
+        log_lock = NULL;
+#endif
+        log_tail = NULL;
+    }
+}
+
+/* show entries until no more, free log */
+local void log_dump(void)
+{
+    if (log_tail == NULL)
+        return;
+    while (log_show())
+        ;
+    log_free();
+}
+
+/* debugging macro */
+#define Trace(x) \
+    do { \
+        if (verbosity > 2) { \
+            log_add x; \
+        } \
+    } while (0)
+
+#else /* !DEBUG */
+
+#define log_dump()
+#define Trace(x)
+
+#endif
+
+/* read up to len bytes into buf, repeating read() calls as needed */
+local size_t readn(int desc, unsigned char *buf, size_t len)
+{
+    ssize_t ret;
+    size_t got;
+
+    got = 0;
+    while (len) {
+        ret = read(desc, buf, len);
+        if (ret < 0)
+            bail("read error on ", in);
+        if (ret == 0)
+            break;
+        buf += ret;
+        len -= ret;
+        got += ret;
+    }
+    return got;
+}
+
+/* write len bytes, repeating write() calls as needed */
+local void writen(int desc, unsigned char *buf, size_t len)
+{
+    ssize_t ret;
+
+    while (len) {
+        ret = write(desc, buf, len);
+        if (ret < 1) {
+            complain("write error code %d", errno);
+            bail("write error on ", out);
+        }
+        buf += ret;
+        len -= ret;
+    }
+}
+
+/* convert Unix time to MS-DOS date and time, assuming current timezone
+   (you got a better idea?) */
+local unsigned long time2dos(time_t t)
+{
+    struct tm *tm;
+    unsigned long dos;
+
+    if (t == 0)
+        t = time(NULL);
+    tm = localtime(&t);
+    if (tm->tm_year < 80 || tm->tm_year > 207)
+        return 0;
+    dos = (tm->tm_year - 80) << 25;
+    dos += (tm->tm_mon + 1) << 21;
+    dos += tm->tm_mday << 16;
+    dos += tm->tm_hour << 11;
+    dos += tm->tm_min << 5;
+    dos += (tm->tm_sec + 1) >> 1;   /* round to double-seconds */
+    return dos;
+}
+
+/* put a 4-byte integer into a byte array in LSB order or MSB order */
+#define PUT2L(a,b) (*(a)=(b)&0xff,(a)[1]=(b)>>8)
+#define PUT4L(a,b) (PUT2L(a,(b)&0xffff),PUT2L((a)+2,(b)>>16))
+#define PUT4M(a,b) (*(a)=(b)>>24,(a)[1]=(b)>>16,(a)[2]=(b)>>8,(a)[3]=(b))
+
+/* write a gzip, zlib, or zip header using the information in the globals */
+local unsigned long put_header(void)
+{
+    unsigned long len;
+    unsigned char head[30];
+
+    if (form > 1) {                 /* zip */
+        /* write local header */
+        PUT4L(head, 0x04034b50UL);  /* local header signature */
+        PUT2L(head + 4, 20);        /* version needed to extract (2.0) */
+        PUT2L(head + 6, 8);         /* flags: data descriptor follows data */
+        PUT2L(head + 8, 8);         /* deflate */
+        PUT4L(head + 10, time2dos(mtime));
+        PUT4L(head + 14, 0);        /* crc (not here) */
+        PUT4L(head + 18, 0);        /* compressed length (not here) */
+        PUT4L(head + 22, 0);        /* uncompressed length (not here) */
+        PUT2L(head + 26, name == NULL ? 1 : strlen(name));  /* name length */
+        PUT2L(head + 28, 9);        /* length of extra field (see below) */
+        writen(outd, head, 30);     /* write local header */
+        len = 30;
+
+        /* write file name (use "-" for stdin) */
+        if (name == NULL)
+            writen(outd, (unsigned char *)"-", 1);
+        else
+            writen(outd, (unsigned char *)name, strlen(name));
+        len += name == NULL ? 1 : strlen(name);
+
+        /* write extended timestamp extra field block (9 bytes) */
+        PUT2L(head, 0x5455);        /* extended timestamp signature */
+        PUT2L(head + 2, 5);         /* number of data bytes in this block */
+        head[4] = 1;                /* flag presence of mod time */
+        PUT4L(head + 5, mtime);     /* mod time */
+        writen(outd, head, 9);      /* write extra field block */
+        len += 9;
+    }
+    else if (form) {                /* zlib */
+        head[0] = 0x78;             /* deflate, 32K window */
+        head[1] = (level == 9 ? 3 : (level == 1 ? 0 :
+            (level >= 6 || level == Z_DEFAULT_COMPRESSION ? 1 :  2))) << 6;
+        head[1] += 31 - (((head[0] << 8) + head[1]) % 31);
+        writen(outd, head, 2);
+        len = 2;
+    }
+    else {                          /* gzip */
+        head[0] = 31;
+        head[1] = 139;
+        head[2] = 8;                /* deflate */
+        head[3] = name != NULL ? 8 : 0;
+        PUT4L(head + 4, mtime);
+        head[8] = level == 9 ? 2 : (level == 1 ? 4 : 0);
+        head[9] = 3;                /* unix */
+        writen(outd, head, 10);
+        len = 10;
+        if (name != NULL)
+            writen(outd, (unsigned char *)name, strlen(name) + 1);
+        if (name != NULL)
+            len += strlen(name) + 1;
+    }
+    return len;
+}
+
+/* write a gzip, zlib, or zip trailer */
+local void put_trailer(unsigned long ulen, unsigned long clen,
+                       unsigned long check, unsigned long head)
+{
+    unsigned char tail[46];
+
+    if (form > 1) {                 /* zip */
+        unsigned long cent;
+
+        /* write data descriptor (as promised in local header) */
+        PUT4L(tail, 0x08074b50UL);
+        PUT4L(tail + 4, check);
+        PUT4L(tail + 8, clen);
+        PUT4L(tail + 12, ulen);
+        writen(outd, tail, 16);
+
+        /* write central file header */
+        PUT4L(tail, 0x02014b50UL);  /* central header signature */
+        tail[4] = 63;               /* obeyed version 6.3 of the zip spec */
+        tail[5] = 255;              /* ignore external attributes */
+        PUT2L(tail + 6, 20);        /* version needed to extract (2.0) */
+        PUT2L(tail + 8, 8);         /* data descriptor is present */
+        PUT2L(tail + 10, 8);        /* deflate */
+        PUT4L(tail + 12, time2dos(mtime));
+        PUT4L(tail + 16, check);    /* crc */
+        PUT4L(tail + 20, clen);     /* compressed length */
+        PUT4L(tail + 24, ulen);     /* uncompressed length */
+        PUT2L(tail + 28, name == NULL ? 1 : strlen(name));  /* name length */
+        PUT2L(tail + 30, 9);        /* length of extra field (see below) */
+        PUT2L(tail + 32, 0);        /* no file comment */
+        PUT2L(tail + 34, 0);        /* disk number 0 */
+        PUT2L(tail + 36, 0);        /* internal file attributes */
+        PUT4L(tail + 38, 0);        /* external file attributes (ignored) */
+        PUT4L(tail + 42, 0);        /* offset of local header */
+        writen(outd, tail, 46);     /* write central file header */
+        cent = 46;
+
+        /* write file name (use "-" for stdin) */
+        if (name == NULL)
+            writen(outd, (unsigned char *)"-", 1);
+        else
+            writen(outd, (unsigned char *)name, strlen(name));
+        cent += name == NULL ? 1 : strlen(name);
+
+        /* write extended timestamp extra field block (9 bytes) */
+        PUT2L(tail, 0x5455);        /* extended timestamp signature */
+        PUT2L(tail + 2, 5);         /* number of data bytes in this block */
+        tail[4] = 1;                /* flag presence of mod time */
+        PUT4L(tail + 5, mtime);     /* mod time */
+        writen(outd, tail, 9);      /* write extra field block */
+        cent += 9;
+
+        /* write end of central directory record */
+        PUT4L(tail, 0x06054b50UL);  /* end of central directory signature */
+        PUT2L(tail + 4, 0);         /* number of this disk */
+        PUT2L(tail + 6, 0);         /* disk with start of central directory */
+        PUT2L(tail + 8, 1);         /* number of entries on this disk */
+        PUT2L(tail + 10, 1);        /* total number of entries */
+        PUT4L(tail + 12, cent);     /* size of central directory */
+        PUT4L(tail + 16, head + clen + 16); /* offset of central directory */
+        PUT2L(tail + 20, 0);        /* no zip file comment */
+        writen(outd, tail, 22);     /* write end of central directory record */
+    }
+    else if (form) {                /* zlib */
+        PUT4M(tail, check);
+        writen(outd, tail, 4);
+    }
+    else {                          /* gzip */
+        PUT4L(tail, check);
+        PUT4L(tail + 4, ulen);
+        writen(outd, tail, 8);
+    }
+}
+
+/* compute check value depending on format */
+#define CHECK(a,b,c) (form == 1 ? adler32(a,b,c) : crc32(a,b,c))
+
+#ifndef NOTHREAD
+/* -- threaded portions of pigz -- */
+
+/* -- check value combination routines for parallel calculation -- */
+
+#define COMB(a,b,c) (form == 1 ? adler32_comb(a,b,c) : crc32_comb(a,b,c))
+/* combine two crc-32's or two adler-32's (copied from zlib 1.2.3 so that pigz
+   can be compatible with older versions of zlib) */
+
+/* we copy the combination routines from zlib here, in order to avoid
+   linkage issues with the zlib 1.2.3 builds on Sun, Ubuntu, and others */
+
+local unsigned long gf2_matrix_times(unsigned long *mat, unsigned long vec)
+{
+    unsigned long sum;
+
+    sum = 0;
+    while (vec) {
+        if (vec & 1)
+            sum ^= *mat;
+        vec >>= 1;
+        mat++;
+    }
+    return sum;
+}
+
+local void gf2_matrix_square(unsigned long *square, unsigned long *mat)
+{
+    int n;
+
+    for (n = 0; n < 32; n++)
+        square[n] = gf2_matrix_times(mat, mat[n]);
+}
+
+local unsigned long crc32_comb(unsigned long crc1, unsigned long crc2,
+                               size_t len2)
+{
+    int n;
+    unsigned long row;
+    unsigned long even[32];     /* even-power-of-two zeros operator */
+    unsigned long odd[32];      /* odd-power-of-two zeros operator */
+
+    /* degenerate case */
+    if (len2 == 0)
+        return crc1;
+
+    /* put operator for one zero bit in odd */
+    odd[0] = 0xedb88320UL;          /* CRC-32 polynomial */
+    row = 1;
+    for (n = 1; n < 32; n++) {
+        odd[n] = row;
+        row <<= 1;
+    }
+
+    /* put operator for two zero bits in even */
+    gf2_matrix_square(even, odd);
+
+    /* put operator for four zero bits in odd */
+    gf2_matrix_square(odd, even);
+
+    /* apply len2 zeros to crc1 (first square will put the operator for one
+       zero byte, eight zero bits, in even) */
+    do {
+        /* apply zeros operator for this bit of len2 */
+        gf2_matrix_square(even, odd);
+        if (len2 & 1)
+            crc1 = gf2_matrix_times(even, crc1);
+        len2 >>= 1;
+
+        /* if no more bits set, then done */
+        if (len2 == 0)
+            break;
+
+        /* another iteration of the loop with odd and even swapped */
+        gf2_matrix_square(odd, even);
+        if (len2 & 1)
+            crc1 = gf2_matrix_times(odd, crc1);
+        len2 >>= 1;
+
+        /* if no more bits set, then done */
+    } while (len2 != 0);
+
+    /* return combined crc */
+    crc1 ^= crc2;
+    return crc1;
+}
+
+#define BASE 65521U     /* largest prime smaller than 65536 */
+#define LOW16 0xffff    /* mask lower 16 bits */
+
+local unsigned long adler32_comb(unsigned long adler1, unsigned long adler2,
+                                 size_t len2)
+{
+    unsigned long sum1;
+    unsigned long sum2;
+    unsigned rem;
+
+    /* the derivation of this formula is left as an exercise for the reader */
+    rem = (unsigned)(len2 % BASE);
+    sum1 = adler1 & LOW16;
+    sum2 = (rem * sum1) % BASE;
+    sum1 += (adler2 & LOW16) + BASE - 1;
+    sum2 += ((adler1 >> 16) & LOW16) + ((adler2 >> 16) & LOW16) + BASE - rem;
+    if (sum1 >= BASE) sum1 -= BASE;
+    if (sum1 >= BASE) sum1 -= BASE;
+    if (sum2 >= (BASE << 1)) sum2 -= (BASE << 1);
+    if (sum2 >= BASE) sum2 -= BASE;
+    return sum1 | (sum2 << 16);
+}
+
+/* -- pool of spaces for buffer management -- */
+
+/* These routines manage a pool of spaces.  Each pool specifies a fixed size
+   buffer to be contained in each space.  Each space has a use count, which
+   when decremented to zero returns the space to the pool.  If a space is
+   requested from the pool and the pool is empty, a space is immediately
+   created unless a specified limit on the number of spaces has been reached.
+   Only if the limit is reached will it wait for a space to be returned to the
+   pool.  Each space knows what pool it belongs to, so that it can be returned.
+ */
+
+/* a space (one buffer for each space) */
+struct space {
+    lock *use;              /* use count -- return to pool when zero */
+    unsigned char *buf;     /* buffer of size size */
+    size_t size;            /* current size of this buffer */
+    size_t len;             /* for application usage (initially zero) */
+    struct pool *pool;      /* pool to return to */
+    struct space *next;     /* for pool linked list */
+};
+
+/* pool of spaces (one pool for each type needed) */
+struct pool {
+    lock *have;             /* unused spaces available, lock for list */
+    struct space *head;     /* linked list of available buffers */
+    size_t size;            /* size of new buffers in this pool */
+    int limit;              /* number of new spaces allowed, or -1 */
+    int made;               /* number of buffers made */
+};
+
+/* initialize a pool (pool structure itself provided, not allocated) -- the
+   limit is the maximum number of spaces in the pool, or -1 to indicate no
+   limit, i.e., to never wait for a buffer to return to the pool */
+local void new_pool(struct pool *pool, size_t size, int limit)
+{
+    pool->have = new_lock(0);
+    pool->head = NULL;
+    pool->size = size;
+    pool->limit = limit;
+    pool->made = 0;
+}
+
+/* get a space from a pool -- the use count is initially set to one, so there
+   is no need to call use_space() for the first use */
+local struct space *get_space(struct pool *pool)
+{
+    struct space *space;
+
+    /* if can't create any more, wait for a space to show up */
+    possess(pool->have);
+    if (pool->limit == 0)
+        wait_for(pool->have, NOT_TO_BE, 0);
+
+    /* if a space is available, pull it from the list and return it */
+    if (pool->head != NULL) {
+        space = pool->head;
+        possess(space->use);
+        pool->head = space->next;
+        twist(pool->have, BY, -1);      /* one less in pool */
+        twist(space->use, TO, 1);       /* initially one user */
+        space->len = 0;
+        return space;
+    }
+
+    /* nothing available, don't want to wait, make a new space */
+    assert(pool->limit != 0);
+    if (pool->limit > 0)
+        pool->limit--;
+    pool->made++;
+    release(pool->have);
+    space = malloc(sizeof(struct space));
+    if (space == NULL)
+        bail("not enough memory", "");
+    space->use = new_lock(1);           /* initially one user */
+    space->buf = malloc(pool->size);
+    if (space->buf == NULL)
+        bail("not enough memory", "");
+    space->size = pool->size;
+    space->len = 0;
+    space->pool = pool;                 /* remember the pool this belongs to */
+    return space;
+}
+
+/* compute next size up by multiplying by about 2**(1/3) and round to the next
+   power of 2 if we're close (so three applications results in doubling) -- if
+   small, go up to at least 16, if overflow, go to max size_t value */
+local size_t grow(size_t size)
+{
+    size_t was, top;
+    int shift;
+
+    was = size;
+    size += size >> 2;
+    top = size;
+    for (shift = 0; top > 7; shift++)
+        top >>= 1;
+    if (top == 7)
+        size = (size_t)1 << (shift + 3);
+    if (size < 16)
+        size = 16;
+    if (size <= was)
+        size = (size_t)0 - 1;
+    return size;
+}
+
+/* increase the size of the buffer in space */
+local void grow_space(struct space *space)
+{
+    size_t more;
+
+    /* compute next size up */
+    more = grow(space->size);
+    if (more == space->size)
+        bail("not enough memory", "");
+
+    /* reallocate the buffer */
+    space->buf = realloc(space->buf, more);
+    if (space->buf == NULL)
+        bail("not enough memory", "");
+    space->size = more;
+}
+
+/* increment the use count to require one more drop before returning this space
+   to the pool */
+local void use_space(struct space *space)
+{
+    possess(space->use);
+    twist(space->use, BY, +1);
+}
+
+/* drop a space, returning it to the pool if the use count is zero */
+local void drop_space(struct space *space)
+{
+    int use;
+    struct pool *pool;
+
+    possess(space->use);
+    use = peek_lock(space->use);
+    assert(use != 0);
+    if (use == 1) {
+        pool = space->pool;
+        possess(pool->have);
+        space->next = pool->head;
+        pool->head = space;
+        twist(pool->have, BY, +1);
+    }
+    twist(space->use, BY, -1);
+}
+
+/* free the memory and lock resources of a pool -- return number of spaces for
+   debugging and resource usage measurement */
+local int free_pool(struct pool *pool)
+{
+    int count;
+    struct space *space;
+
+    possess(pool->have);
+    count = 0;
+    while ((space = pool->head) != NULL) {
+        pool->head = space->next;
+        free(space->buf);
+        free_lock(space->use);
+        free(space);
+        count++;
+    }
+    assert(count == pool->made);
+    release(pool->have);
+    free_lock(pool->have);
+    return count;
+}
+
+/* input and output buffer pools */
+local struct pool in_pool;
+local struct pool out_pool;
+local struct pool dict_pool;
+local struct pool lens_pool;
+
+/* -- parallel compression -- */
+
+/* compress or write job (passed from compress list to write list) -- if seq is
+   equal to -1, compress_thread is instructed to return; if more is false then
+   this is the last chunk, which after writing tells write_thread to return */
+struct job {
+    long seq;                   /* sequence number */
+    int more;                   /* true if this is not the last chunk */
+    struct space *in;           /* input data to compress */
+    struct space *out;          /* dictionary or resulting compressed data */
+    struct space *lens;         /* coded list of flush block lengths */
+    unsigned long check;        /* check value for input data */
+    lock *calc;                 /* released when check calculation complete */
+    struct job *next;           /* next job in the list (either list) */
+};
+
+/* list of compress jobs (with tail for appending to list) */
+local lock *compress_have = NULL;   /* number of compress jobs waiting */
+local struct job *compress_head, **compress_tail;
+
+/* list of write jobs */
+local lock *write_first;            /* lowest sequence number in list */
+local struct job *write_head;
+
+/* number of compression threads running */
+local int cthreads = 0;
+
+/* write thread if running */
+local thread *writeth = NULL;
+
+/* setup job lists (call from main thread) */
+local void setup_jobs(void)
+{
+    /* set up only if not already set up*/
+    if (compress_have != NULL)
+        return;
+
+    /* allocate locks and initialize lists */
+    compress_have = new_lock(0);
+    compress_head = NULL;
+    compress_tail = &compress_head;
+    write_first = new_lock(-1);
+    write_head = NULL;
+
+    /* initialize buffer pools (initial size for out_pool not critical, since
+       buffers will be grown in size if needed -- initial size chosen to make
+       this unlikely -- same for lens_pool) */
+    new_pool(&in_pool, size, INBUFS(procs));
+    new_pool(&out_pool, OUTPOOL(size), -1);
+    new_pool(&dict_pool, DICT, -1);
+    new_pool(&lens_pool, size >> (RSYNCBITS - 1), -1);
+}
+
+/* command the compress threads to all return, then join them all (call from
+   main thread), free all the thread-related resources */
+local void finish_jobs(void)
+{
+    struct job job;
+    int caught;
+
+    /* only do this once */
+    if (compress_have == NULL)
+        return;
+
+    /* command all of the extant compress threads to return */
+    possess(compress_have);
+    job.seq = -1;
+    job.next = NULL;
+    compress_head = &job;
+    compress_tail = &(job.next);
+    twist(compress_have, BY, +1);       /* will wake them all up */
+
+    /* join all of the compress threads, verify they all came back */
+    caught = join_all();
+    Trace(("-- joined %d compress threads", caught));
+    assert(caught == cthreads);
+    cthreads = 0;
+
+    /* free the resources */
+    caught = free_pool(&lens_pool);
+    Trace(("-- freed %d block lengths buffers", caught));
+    caught = free_pool(&dict_pool);
+    Trace(("-- freed %d dictionary buffers", caught));
+    caught = free_pool(&out_pool);
+    Trace(("-- freed %d output buffers", caught));
+    caught = free_pool(&in_pool);
+    Trace(("-- freed %d input buffers", caught));
+    free_lock(write_first);
+    free_lock(compress_have);
+    compress_have = NULL;
+}
+
+/* compress all strm->avail_in bytes at strm->next_in to out->buf, updating
+   out->len, grow the size of the buffer (out->size) if necessary -- respect
+   the size limitations of the zlib stream data types (size_t may be larger
+   than unsigned) */
+local void deflate_engine(z_stream *strm, struct space *out, int flush)
+{
+    size_t room;
+
+    do {
+        room = out->size - out->len;
+        if (room == 0) {
+            grow_space(out);
+            room = out->size - out->len;
+        }
+        strm->next_out = out->buf + out->len;
+        strm->avail_out = room < UINT_MAX ? (unsigned)room : UINT_MAX;
+        (void)deflate(strm, flush);
+        out->len = strm->next_out - out->buf;
+    } while (strm->avail_out == 0);
+    assert(strm->avail_in == 0);
+}
+
+/* get the next compression job from the head of the list, compress and compute
+   the check value on the input, and put a job in the write list with the
+   results -- keep looking for more jobs, returning when a job is found with a
+   sequence number of -1 (leave that job in the list for other incarnations to
+   find) */
+local void compress_thread(void *dummy)
+{
+    struct job *job;                /* job pulled and working on */
+    struct job *here, **prior;      /* pointers for inserting in write list */
+    unsigned long check;            /* check value of input */
+    unsigned char *next;            /* pointer for blocks, check value data */
+    size_t left;                    /* input left to process */
+    size_t len;                     /* remaining bytes to compress/check */
+#if ZLIB_VERNUM >= 0x1260
+    int bits;                       /* deflate pending bits */
+#endif
+    z_stream strm;                  /* deflate stream */
+
+    (void)dummy;
+
+    /* initialize the deflate stream for this thread */
+    strm.zfree = Z_NULL;
+    strm.zalloc = Z_NULL;
+    strm.opaque = Z_NULL;
+    if (deflateInit2(&strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) !=
+            Z_OK)
+        bail("not enough memory", "");
+
+    /* keep looking for work */
+    for (;;) {
+        /* get a job (like I tell my son) */
+        possess(compress_have);
+        wait_for(compress_have, NOT_TO_BE, 0);
+        job = compress_head;
+        assert(job != NULL);
+        if (job->seq == -1)
+            break;
+        compress_head = job->next;
+        if (job->next == NULL)
+            compress_tail = &compress_head;
+        twist(compress_have, BY, -1);
+
+        /* got a job -- initialize and set the compression level (note that if
+           deflateParams() is called immediately after deflateReset(), there is
+           no need to initialize the input/output for the stream) */
+        Trace(("-- compressing #%ld", job->seq));
+        (void)deflateReset(&strm);
+        (void)deflateParams(&strm, level, Z_DEFAULT_STRATEGY);
+
+        /* set dictionary if provided, release that input or dictionary buffer
+           (not NULL if dict is true and if this is not the first work unit) */
+        if (job->out != NULL) {
+            len = job->out->len;
+            left = len < DICT ? len : DICT;
+            deflateSetDictionary(&strm, job->out->buf + (len - left), left);
+            drop_space(job->out);
+        }
+
+        /* set up input and output */
+        job->out = get_space(&out_pool);
+        strm.next_in = job->in->buf;
+        strm.next_out = job->out->buf;
+
+        /* compress each block, either flushing or finishing */
+        next = job->lens == NULL ? NULL : job->lens->buf;
+        left = job->in->len;
+        job->out->len = 0;
+        do {
+            /* decode next block length from blocks list */
+            len = next == NULL ? 128 : *next++;
+            if (len < 128)                          /* 64..32831 */
+                len = (len << 8) + (*next++) + 64;
+            else if (len == 128)                    /* end of list */
+                len = left;
+            else if (len < 192)                     /* 1..63 */
+                len &= 0x3f;
+            else {                                  /* 32832..4227135 */
+                len = ((len & 0x3f) << 16) + (*next++ << 8) + 32832U;
+                len += *next++;
+            }
+            left -= len;
+
+            /* run MAXP2-sized amounts of input through deflate -- this loop is
+               needed for those cases where the unsigned type is smaller than
+               the size_t type, or when len is close to the limit of the size_t
+               type */
+            while (len > MAXP2) {
+                strm.avail_in = MAXP2;
+                deflate_engine(&strm, job->out, Z_NO_FLUSH);
+                len -= MAXP2;
+            }
+
+            /* run the last piece through deflate -- end on a byte boundary,
+               using a sync marker if necessary, or finish the deflate stream
+               if this is the last block */
+            strm.avail_in = (unsigned)len;
+            if (left || job->more) {
+#if ZLIB_VERNUM >= 0x1260
+                deflate_engine(&strm, job->out, Z_BLOCK);
+
+                /* add just enough empty blocks to get to a byte boundary */
+                (void)deflatePending(&strm, Z_NULL, &bits);
+                if (bits & 1)
+                    deflate_engine(&strm, job->out, Z_SYNC_FLUSH);
+                else if (bits & 7) {
+                    do {
+                        bits = deflatePrime(&strm, 10, 2);  /* static empty */
+                        assert(bits == Z_OK);
+                        (void)deflatePending(&strm, Z_NULL, &bits);
+                    } while (bits & 7);
+                    deflate_engine(&strm, job->out, Z_BLOCK);
+                }
+#else
+                deflate_engine(&strm, job->out, Z_SYNC_FLUSH);
+#endif
+            }
+            else
+                deflate_engine(&strm, job->out, Z_FINISH);
+        } while (left);
+        if (job->lens != NULL) {
+            drop_space(job->lens);
+            job->lens = NULL;
+        }
+        Trace(("-- compressed #%ld%s", job->seq, job->more ? "" : " (last)"));
+
+        /* reserve input buffer until check value has been calculated */
+        use_space(job->in);
+
+        /* insert write job in list in sorted order, alert write thread */
+        possess(write_first);
+        prior = &write_head;
+        while ((here = *prior) != NULL) {
+            if (here->seq > job->seq)
+                break;
+            prior = &(here->next);
+        }
+        job->next = here;
+        *prior = job;
+        twist(write_first, TO, write_head->seq);
+
+        /* calculate the check value in parallel with writing, alert the write
+           thread that the calculation is complete, and drop this usage of the
+           input buffer */
+        len = job->in->len;
+        next = job->in->buf;
+        check = CHECK(0L, Z_NULL, 0);
+        while (len > MAXP2) {
+            check = CHECK(check, next, MAXP2);
+            len -= MAXP2;
+            next += MAXP2;
+        }
+        check = CHECK(check, next, (unsigned)len);
+        drop_space(job->in);
+        job->check = check;
+        Trace(("-- checked #%ld%s", job->seq, job->more ? "" : " (last)"));
+        possess(job->calc);
+        twist(job->calc, TO, 1);
+
+        /* done with that one -- go find another job */
+    }
+
+    /* found job with seq == -1 -- free deflate memory and return to join */
+    release(compress_have);
+    (void)deflateEnd(&strm);
+}
+
+/* collect the write jobs off of the list in sequence order and write out the
+   compressed data until the last chunk is written -- also write the header and
+   trailer and combine the individual check values of the input buffers */
+local void write_thread(void *dummy)
+{
+    long seq;                       /* next sequence number looking for */
+    struct job *job;                /* job pulled and working on */
+    size_t len;                     /* input length */
+    int more;                       /* true if more chunks to write */
+    unsigned long head;             /* header length */
+    unsigned long ulen;             /* total uncompressed size (overflow ok) */
+    unsigned long clen;             /* total compressed size (overflow ok) */
+    unsigned long check;            /* check value of uncompressed data */
+
+    (void)dummy;
+
+    /* build and write header */
+    Trace(("-- write thread running"));
+    head = put_header();
+
+    /* process output of compress threads until end of input */
+    ulen = clen = 0;
+    check = CHECK(0L, Z_NULL, 0);
+    seq = 0;
+    do {
+        /* get next write job in order */
+        possess(write_first);
+        wait_for(write_first, TO_BE, seq);
+        job = write_head;
+        write_head = job->next;
+        twist(write_first, TO, write_head == NULL ? -1 : write_head->seq);
+
+        /* update lengths, save uncompressed length for COMB */
+        more = job->more;
+        len = job->in->len;
+        drop_space(job->in);
+        ulen += (unsigned long)len;
+        clen += (unsigned long)(job->out->len);
+
+        /* write the compressed data and drop the output buffer */
+        Trace(("-- writing #%ld", seq));
+        writen(outd, job->out->buf, job->out->len);
+        drop_space(job->out);
+        Trace(("-- wrote #%ld%s", seq, more ? "" : " (last)"));
+
+        /* wait for check calculation to complete, then combine, once
+           the compress thread is done with the input, release it */
+        possess(job->calc);
+        wait_for(job->calc, TO_BE, 1);
+        release(job->calc);
+        check = COMB(check, job->check, len);
+
+        /* free the job */
+        free_lock(job->calc);
+        free(job);
+
+        /* get the next buffer in sequence */
+        seq++;
+    } while (more);
+
+    /* write trailer */
+    put_trailer(ulen, clen, check, head);
+
+    /* verify no more jobs, prepare for next use */
+    possess(compress_have);
+    assert(compress_head == NULL && peek_lock(compress_have) == 0);
+    release(compress_have);
+    possess(write_first);
+    assert(write_head == NULL);
+    twist(write_first, TO, -1);
+}
+
+/* encode a hash hit to the block lengths list -- hit == 0 ends the list */
+local void append_len(struct job *job, size_t len)
+{
+    struct space *lens;
+
+    assert(len < 4227136UL);
+    if (job->lens == NULL)
+        job->lens = get_space(&lens_pool);
+    lens = job->lens;
+    if (lens->size < lens->len + 3)
+        grow_space(lens);
+    if (len < 64)
+        lens->buf[lens->len++] = len + 128;
+    else if (len < 32832U) {
+        len -= 64;
+        lens->buf[lens->len++] = len >> 8;
+        lens->buf[lens->len++] = len;
+    }
+    else {
+        len -= 32832U;
+        lens->buf[lens->len++] = (len >> 16) + 192;
+        lens->buf[lens->len++] = len >> 8;
+        lens->buf[lens->len++] = len;
+    }
+}
+
+/* compress ind to outd, using multiple threads for the compression and check
+   value calculations and one other thread for writing the output -- compress
+   threads will be launched and left running (waiting actually) to support
+   subsequent calls of parallel_compress() */
+local void parallel_compress(void)
+{
+    long seq;                       /* sequence number */
+    struct space *curr;             /* input data to compress */
+    struct space *next;             /* input data that follows curr */
+    struct space *hold;             /* input data that follows next */
+    struct space *dict;             /* dictionary for next compression */
+    struct job *job;                /* job for compress, then write */
+    int more;                       /* true if more input to read */
+    unsigned hash;                  /* hash for rsyncable */
+    unsigned char *scan;            /* next byte to compute hash on */
+    unsigned char *end;             /* after end of data to compute hash on */
+    unsigned char *last;            /* position after last hit */
+    size_t left;                    /* last hit in curr to end of curr */
+    size_t len;                     /* for various length computations */
+
+    /* if first time or after an option change, setup the job lists */
+    setup_jobs();
+
+    /* start write thread */
+    writeth = launch(write_thread, NULL);
+
+    /* read from input and start compress threads (write thread will pick up
+     the output of the compress threads) */
+    seq = 0;
+    next = get_space(&in_pool);
+    next->len = readn(ind, next->buf, next->size);
+    hold = NULL;
+    dict = NULL;
+    scan = next->buf;
+    hash = RSYNCHIT;
+    left = 0;
+    do {
+        /* create a new job */
+        job = malloc(sizeof(struct job));
+        if (job == NULL)
+            bail("not enough memory", "");
+        job->calc = new_lock(0);
+
+        /* update input spaces */
+        curr = next;
+        next = hold;
+        hold = NULL;
+
+        /* get more input if we don't already have some */
+        if (next == NULL) {
+            next = get_space(&in_pool);
+            next->len = readn(ind, next->buf, next->size);
+        }
+
+        /* if rsyncable, generate block lengths and prepare curr for job to
+           likely have less than size bytes (up to the last hash hit) */
+        job->lens = NULL;
+        if (rsync && curr->len) {
+            /* compute the hash function starting where we last left off to
+               cover either size bytes or to EOF, whichever is less, through
+               the data in curr (and in the next loop, through next) -- save
+               the block lengths resulting from the hash hits in the job->lens
+               list */
+            if (left == 0) {
+                /* scan is in curr */
+                last = curr->buf;
+                end = curr->buf + curr->len;
+                while (scan < end) {
+                    hash = ((hash << 1) ^ *scan++) & RSYNCMASK;
+                    if (hash == RSYNCHIT) {
+                        len = scan - last;
+                        append_len(job, len);
+                        last = scan;
+                    }
+                }
+
+                /* continue scan in next */
+                left = scan - last;
+                scan = next->buf;
+            }
+
+            /* scan in next for enough bytes to fill curr, or what is available
+               in next, whichever is less (if next isn't full, then we're at
+               the end of the file) -- the bytes in curr since the last hit,
+               stored in left, counts towards the size of the first block */
+            last = next->buf;
+            len = curr->size - curr->len;
+            if (len > next->len)
+                len = next->len;
+            end = next->buf + len;
+            while (scan < end) {
+                hash = ((hash << 1) ^ *scan++) & RSYNCMASK;
+                if (hash == RSYNCHIT) {
+                    len = (scan - last) + left;
+                    left = 0;
+                    append_len(job, len);
+                    last = scan;
+                }
+            }
+            append_len(job, 0);
+
+            /* create input in curr for job up to last hit or entire buffer if
+               no hits at all -- save remainder in next and possibly hold */
+            len = (job->lens->len == 1 ? scan : last) - next->buf;
+            if (len) {
+                /* got hits in next, or no hits in either -- copy to curr */
+                memcpy(curr->buf + curr->len, next->buf, len);
+                curr->len += len;
+                memmove(next->buf, next->buf + len, next->len - len);
+                next->len -= len;
+                scan -= len;
+                left = 0;
+            }
+            else if (job->lens->len != 1 && left && next->len) {
+                /* had hits in curr, but none in next, and last hit in curr
+                   wasn't right at the end, so we have input there to save --
+                   use curr up to the last hit, save the rest, moving next to
+                   hold */
+                hold = next;
+                next = get_space(&in_pool);
+                memcpy(next->buf, curr->buf + (curr->len - left), left);
+                next->len = left;
+                curr->len -= left;
+            }
+            else {
+                /* else, last match happened to be right at the end of curr,
+                   or we're at the end of the input compressing the rest */
+                left = 0;
+            }
+        }
+
+        /* compress curr->buf to curr->len -- compress thread will drop curr */
+        job->in = curr;
+
+        /* set job->more if there is more to compress after curr */
+        more = next->len != 0;
+        job->more = more;
+
+        /* provide dictionary for this job, prepare dictionary for next job */
+        job->out = dict;
+        if (more && setdict) {
+            if (curr->len >= DICT || job->out == NULL) {
+                dict = curr;
+                use_space(dict);
+            }
+            else {
+                dict = get_space(&dict_pool);
+                len = DICT - curr->len;
+                memcpy(dict->buf, job->out->buf + (job->out->len - len), len);
+                memcpy(dict->buf + len, curr->buf, curr->len);
+                dict->len = DICT;
+            }
+        }
+
+        /* preparation of job is complete */
+        job->seq = seq;
+        Trace(("-- read #%ld%s", seq, more ? "" : " (last)"));
+        if (++seq < 1)
+            bail("input too long: ", in);
+
+        /* start another compress thread if needed */
+        if (cthreads < seq && cthreads < procs) {
+            (void)launch(compress_thread, NULL);
+            cthreads++;
+        }
+
+        /* put job at end of compress list, let all the compressors know */
+        possess(compress_have);
+        job->next = NULL;
+        *compress_tail = job;
+        compress_tail = &(job->next);
+        twist(compress_have, BY, +1);
+    } while (more);
+    drop_space(next);
+
+    /* wait for the write thread to complete (we leave the compress threads out
+       there and waiting in case there is another stream to compress) */
+    join(writeth);
+    writeth = NULL;
+    Trace(("-- write thread joined"));
+}
+
+#endif
+
+/* repeated code in single_compress to compress available input and write it */
+#define DEFLATE_WRITE(flush) \
+    do { \
+        do { \
+            strm->avail_out = out_size; \
+            strm->next_out = out; \
+            (void)deflate(strm, flush); \
+            writen(outd, out, out_size - strm->avail_out); \
+            clen += out_size - strm->avail_out; \
+        } while (strm->avail_out == 0); \
+        assert(strm->avail_in == 0); \
+    } while (0)
+
+/* do a simple compression in a single thread from ind to outd -- if reset is
+   true, instead free the memory that was allocated and retained for input,
+   output, and deflate */
+local void single_compress(int reset)
+{
+    size_t got;                     /* amount read */
+    size_t more;                    /* amount of next read (0 if eof) */
+    size_t start;                   /* start of next read */
+    size_t block;                   /* bytes in current block for -i */
+    unsigned hash;                  /* hash for rsyncable */
+#if ZLIB_VERNUM >= 0x1260
+    int bits;                       /* deflate pending bits */
+#endif
+    unsigned char *scan;            /* pointer for hash computation */
+    size_t left;                    /* bytes left to compress after hash hit */
+    unsigned long head;             /* header length */
+    unsigned long ulen;             /* total uncompressed size (overflow ok) */
+    unsigned long clen;             /* total compressed size (overflow ok) */
+    unsigned long check;            /* check value of uncompressed data */
+    static unsigned out_size;       /* size of output buffer */
+    static unsigned char *in, *next, *out;  /* reused i/o buffers */
+    static z_stream *strm = NULL;   /* reused deflate structure */
+
+    /* if requested, just release the allocations and return */
+    if (reset) {
+        if (strm != NULL) {
+            (void)deflateEnd(strm);
+            free(strm);
+            free(out);
+            free(next);
+            free(in);
+            strm = NULL;
+        }
+        return;
+    }
+
+    /* initialize the deflate structure if this is the first time */
+    if (strm == NULL) {
+        out_size = size > MAXP2 ? MAXP2 : (unsigned)size;
+        if ((in = malloc(size)) == NULL ||
+            (next = malloc(size)) == NULL ||
+            (out = malloc(out_size)) == NULL ||
+            (strm = malloc(sizeof(z_stream))) == NULL)
+            bail("not enough memory", "");
+        strm->zfree = Z_NULL;
+        strm->zalloc = Z_NULL;
+        strm->opaque = Z_NULL;
+        if (deflateInit2(strm, level, Z_DEFLATED, -15, 8,
+                         Z_DEFAULT_STRATEGY) != Z_OK)
+            bail("not enough memory", "");
+    }
+
+    /* write header */
+    head = put_header();
+
+    /* set compression level in case it changed */
+    (void)deflateReset(strm);
+    (void)deflateParams(strm, level, Z_DEFAULT_STRATEGY);
+
+    /* do raw deflate and calculate check value */
+    got = 0;
+    more = readn(ind, next, size);
+    ulen = (unsigned)more;
+    start = 0;
+    clen = 0;
+    block = 0;
+    check = CHECK(0L, Z_NULL, 0);
+    hash = RSYNCHIT;
+    do {
+        /* get data to compress, see if there is any more input */
+        if (got == 0) {
+            scan = in;  in = next;  next = scan;
+            strm->next_in = in + start;
+            got = more;
+            more = readn(ind, next, size);
+            ulen += (unsigned long)more;
+            start = 0;
+        }
+
+        /* if rsyncable, compute hash until a hit or the end of the block */
+        left = 0;
+        if (rsync && got) {
+            scan = strm->next_in;
+            left = got;
+            do {
+                if (left == 0) {
+                    /* went to the end -- if no more or no hit in size bytes,
+                       then proceed to do a flush or finish with got bytes */
+                    if (more == 0 || got == size)
+                        break;
+
+                    /* fill in[] with what's left there and as much as possible
+                       from next[] -- set up to continue hash hit search */
+                    memmove(in, strm->next_in, got);
+                    strm->next_in = in;
+                    scan = in + got;
+                    left = more > size - got ? size - got : more;
+                    memcpy(scan, next + start, left);
+                    got += left;
+                    more -= left;
+                    start += left;
+
+                    /* if that emptied the next buffer, try to refill it */
+                    if (more == 0) {
+                        more = readn(ind, next, size);
+                        ulen += (unsigned long)more;
+                        start = 0;
+                    }
+                }
+                left--;
+                hash = ((hash << 1) ^ *scan++) & RSYNCMASK;
+            } while (hash != RSYNCHIT);
+            got -= left;
+        }
+
+        /* clear history for --independent option */
+        if (!setdict) {
+            block += got;
+            if (block > size) {
+                (void)deflateReset(strm);
+                block = got;
+            }
+        }
+
+        /* compress MAXP2-size chunks in case unsigned type is small */
+        while (got > MAXP2) {
+            strm->avail_in = MAXP2;
+            check = CHECK(check, strm->next_in, strm->avail_in);
+            DEFLATE_WRITE(Z_NO_FLUSH);
+            got -= MAXP2;
+        }
+
+        /* compress the remainder, emit a block -- finish if end of input */
+        strm->avail_in = (unsigned)got;
+        got = left;
+        check = CHECK(check, strm->next_in, strm->avail_in);
+        if (more || got) {
+#if ZLIB_VERNUM >= 0x1260
+            DEFLATE_WRITE(Z_BLOCK);
+            (void)deflatePending(strm, Z_NULL, &bits);
+            if (bits & 1)
+                DEFLATE_WRITE(Z_SYNC_FLUSH);
+            else if (bits & 7) {
+                do {
+                    bits = deflatePrime(strm, 10, 2);
+                    assert(bits == Z_OK);
+                    (void)deflatePending(strm, Z_NULL, &bits);
+                } while (bits & 7);
+                DEFLATE_WRITE(Z_NO_FLUSH);
+            }
+#else
+            DEFLATE_WRITE(Z_SYNC_FLUSH);
+#endif
+        }
+        else
+            DEFLATE_WRITE(Z_FINISH);
+
+        /* do until no more input */
+    } while (more || got);
+
+    /* write trailer */
+    put_trailer(ulen, clen, check, head);
+}
+
+/* --- decompression --- */
+
+/* globals for decompression and listing buffered reading */
+#define BUF 32768U                  /* input buffer size */
+local unsigned char in_buf[BUF];    /* input buffer */
+local unsigned char *in_next;       /* next unused byte in buffer */
+local size_t in_left;               /* number of unused bytes in buffer */
+local int in_eof;                   /* true if reached end of file on input */
+local int in_short;                 /* true if last read didn't fill buffer */
+local off_t in_tot;                 /* total bytes read from input */
+local off_t out_tot;                /* total bytes written to output */
+local unsigned long out_check;      /* check value of output */
+
+#ifndef NOTHREAD
+/* parallel reading */
+
+local unsigned char in_buf2[BUF];   /* second buffer for parallel reads */
+local size_t in_len;                /* data waiting in next buffer */
+local int in_which;                 /* -1: start, 0: in_buf2, 1: in_buf */
+local lock *load_state;             /* value = 0 to wait, 1 to read a buffer */
+local thread *load_thread;          /* load_read() thread for joining */
+
+/* parallel read thread */
+local void load_read(void *dummy)
+{
+    size_t len;
+
+    (void)dummy;
+
+    Trace(("-- launched decompress read thread"));
+    do {
+        possess(load_state);
+        wait_for(load_state, TO_BE, 1);
+        in_len = len = readn(ind, in_which ? in_buf : in_buf2, BUF);
+        Trace(("-- decompress read thread read %lu bytes", len));
+        twist(load_state, TO, 0);
+    } while (len == BUF);
+    Trace(("-- exited decompress read thread"));
+}
+
+#endif
+
+/* load() is called when the input has been consumed in order to provide more
+   input data: load the input buffer with BUF or less bytes (less if at end of
+   file) from the file ind, set in_next to point to the in_left bytes read,
+   update in_tot, and return in_left -- in_eof is set to true when in_left has
+   gone to zero and there is no more data left to read from ind */
+local size_t load(void)
+{
+    /* if already detected end of file, do nothing */
+    if (in_short) {
+        in_eof = 1;
+        in_left = 0;
+        return 0;
+    }
+
+#ifndef NOTHREAD
+    /* if first time in or procs == 1, read a buffer to have something to
+       return, otherwise wait for the previous read job to complete */
+    if (procs > 1) {
+        /* if first time, fire up the read thread, ask for a read */
+        if (in_which == -1) {
+            in_which = 1;
+            load_state = new_lock(1);
+            load_thread = launch(load_read, NULL);
+        }
+
+        /* wait for the previously requested read to complete */
+        possess(load_state);
+        wait_for(load_state, TO_BE, 0);
+        release(load_state);
+
+        /* set up input buffer with the data just read */
+        in_next = in_which ? in_buf : in_buf2;
+        in_left = in_len;
+
+        /* if not at end of file, alert read thread to load next buffer,
+           alternate between in_buf and in_buf2 */
+        if (in_len == BUF) {
+            in_which = 1 - in_which;
+            possess(load_state);
+            twist(load_state, TO, 1);
+        }
+
+        /* at end of file -- join read thread (already exited), clean up */
+        else {
+            join(load_thread);
+            free_lock(load_state);
+            in_which = -1;
+        }
+    }
+    else
+#endif
+    {
+        /* don't use threads -- simply read a buffer into in_buf */
+        in_left = readn(ind, in_next = in_buf, BUF);
+    }
+
+    /* note end of file */
+    if (in_left < BUF) {
+        in_short = 1;
+
+        /* if we got bupkis, now is the time to mark eof */
+        if (in_left == 0)
+            in_eof = 1;
+    }
+
+    /* update the total and return the available bytes */
+    in_tot += in_left;
+    return in_left;
+}
+
+/* initialize for reading new input */
+local void in_init(void)
+{
+    in_left = 0;
+    in_eof = 0;
+    in_short = 0;
+    in_tot = 0;
+#ifndef NOTHREAD
+    in_which = -1;
+#endif
+}
+
+/* buffered reading macros for decompression and listing */
+#define GET() (in_eof || (in_left == 0 && load() == 0) ? EOF : \
+               (in_left--, *in_next++))
+#define GET2() (tmp2 = GET(), tmp2 + ((unsigned)(GET()) << 8))
+#define GET4() (tmp4 = GET2(), tmp4 + ((unsigned long)(GET2()) << 16))
+#define SKIP(dist) \
+    do { \
+        size_t togo = (dist); \
+        while (togo > in_left) { \
+            togo -= in_left; \
+            if (load() == 0) \
+                return -1; \
+        } \
+        in_left -= togo; \
+        in_next += togo; \
+    } while (0)
+
+/* pull LSB order or MSB order integers from an unsigned char buffer */
+#define PULL2L(p) ((p)[0] + ((unsigned)((p)[1]) << 8))
+#define PULL4L(p) (PULL2L(p) + ((unsigned long)(PULL2L((p) + 2)) << 16))
+#define PULL2M(p) (((unsigned)((p)[0]) << 8) + (p)[1])
+#define PULL4M(p) (((unsigned long)(PULL2M(p)) << 16) + PULL2M((p) + 2))
+
+/* convert MS-DOS date and time to a Unix time, assuming current timezone
+   (you got a better idea?) */
+local time_t dos2time(unsigned long dos)
+{
+    struct tm tm;
+
+    if (dos == 0)
+        return time(NULL);
+    tm.tm_year = ((int)(dos >> 25) & 0x7f) + 80;
+    tm.tm_mon  = ((int)(dos >> 21) & 0xf) - 1;
+    tm.tm_mday = (int)(dos >> 16) & 0x1f;
+    tm.tm_hour = (int)(dos >> 11) & 0x1f;
+    tm.tm_min  = (int)(dos >> 5) & 0x3f;
+    tm.tm_sec  = (int)(dos << 1) & 0x3e;
+    tm.tm_isdst = -1;           /* figure out if DST or not */
+    return mktime(&tm);
+}
+
+/* convert an unsigned 32-bit integer to signed, even if long > 32 bits */
+local long tolong(unsigned long val)
+{
+    return (long)(val & 0x7fffffffUL) - (long)(val & 0x80000000UL);
+}
+
+#define LOW32 0xffffffffUL
+
+/* process zip extra field to extract zip64 lengths and Unix mod time */
+local int read_extra(unsigned len, int save)
+{
+    unsigned id, size, tmp2;
+    unsigned long tmp4;
+
+    /* process extra blocks */
+    while (len >= 4) {
+        id = GET2();
+        size = GET2();
+        if (in_eof)
+            return -1;
+        len -= 4;
+        if (size > len)
+            break;
+        len -= size;
+        if (id == 0x0001) {
+            /* Zip64 Extended Information Extra Field */
+            if (zip_ulen == LOW32 && size >= 8) {
+                zip_ulen = GET4();
+                SKIP(4);
+                size -= 8;
+            }
+            if (zip_clen == LOW32 && size >= 8) {
+                zip_clen = GET4();
+                SKIP(4);
+                size -= 8;
+            }
+        }
+        if (save) {
+            if ((id == 0x000d || id == 0x5855) && size >= 8) {
+                /* PKWare Unix or Info-ZIP Type 1 Unix block */
+                SKIP(4);
+                stamp = tolong(GET4());
+                size -= 8;
+            }
+            if (id == 0x5455 && size >= 5) {
+                /* Extended Timestamp block */
+                size--;
+                if (GET() & 1) {
+                    stamp = tolong(GET4());
+                    size -= 4;
+                }
+            }
+        }
+        SKIP(size);
+    }
+    SKIP(len);
+    return 0;
+}
+
+/* read a gzip, zip, zlib, or lzw header from ind and extract useful
+   information, return the method -- or on error return negative: -1 is
+   immediate EOF, -2 is not a recognized compressed format, -3 is premature EOF
+   within the header, -4 is unexpected header flag values; a method of 256 is
+   lzw -- set form to indicate gzip, zlib, or zip */
+local int get_header(int save)
+{
+    unsigned magic;             /* magic header */
+    int method;                 /* compression method */
+    int flags;                  /* header flags */
+    unsigned fname, extra;      /* name and extra field lengths */
+    unsigned tmp2;              /* for macro */
+    unsigned long tmp4;         /* for macro */
+
+    /* clear return information */
+    if (save) {
+        stamp = 0;
+        RELEASE(hname);
+    }
+
+    /* see if it's a gzip, zlib, or lzw file */
+    form = 0;
+    magic1 = GET();
+    if (in_eof)
+        return -1;
+    magic = magic1 << 8;
+    magic += GET();
+    if (in_eof)
+        return -2;
+    if (magic % 31 == 0) {          /* it's zlib */
+        form = 1;
+        return (int)((magic >> 8) & 0xf);
+    }
+    if (magic == 0x1f9d)            /* it's lzw */
+        return 256;
+    if (magic == 0x504b) {          /* it's zip */
+        if (GET() != 3 || GET() != 4)
+            return -3;
+        SKIP(2);
+        flags = GET2();
+        if (in_eof)
+            return -3;
+        if (flags & 0xfff0)
+            return -4;
+        method = GET2();
+        if (flags & 1)              /* encrypted */
+            method = 255;           /* mark as unknown method */
+        if (in_eof)
+            return -3;
+        if (save)
+            stamp = dos2time(GET4());
+        else
+            SKIP(4);
+        zip_crc = GET4();
+        zip_clen = GET4();
+        zip_ulen = GET4();
+        fname = GET2();
+        extra = GET2();
+        if (save) {
+            char *next = hname = malloc(fname + 1);
+            if (hname == NULL)
+                bail("not enough memory", "");
+            while (fname > in_left) {
+                memcpy(next, in_next, in_left);
+                fname -= in_left;
+                next += in_left;
+                if (load() == 0)
+                    return -3;
+            }
+            memcpy(next, in_next, fname);
+            in_left -= fname;
+            in_next += fname;
+            next += fname;
+            *next = 0;
+        }
+        else
+            SKIP(fname);
+        read_extra(extra, save);
+        form = 2 + ((flags & 8) >> 3);
+        return in_eof ? -3 : method;
+    }
+    if (magic != 0x1f8b) {          /* not gzip */
+        in_left++;      /* unget second magic byte */
+        in_next--;
+        return -2;
+    }
+
+    /* it's gzip -- get method and flags */
+    method = GET();
+    flags = GET();
+    if (in_eof)
+        return -1;
+    if (flags & 0xe0)
+        return -4;
+
+    /* get time stamp */
+    if (save)
+        stamp = tolong(GET4());
+    else
+        SKIP(4);
+
+    /* skip extra field and OS */
+    SKIP(2);
+
+    /* skip extra field, if present */
+    if (flags & 4) {
+        extra = GET2();
+        if (in_eof)
+            return -3;
+        SKIP(extra);
+    }
+
+    /* read file name, if present, into allocated memory */
+    if ((flags & 8) && save) {
+        unsigned char *end;
+        size_t copy, have, size = 128;
+        hname = malloc(size);
+        if (hname == NULL)
+            bail("not enough memory", "");
+        have = 0;
+        do {
+            if (in_left == 0 && load() == 0)
+                return -3;
+            end = memchr(in_next, 0, in_left);
+            copy = end == NULL ? in_left : (size_t)(end - in_next) + 1;
+            if (have + copy > size) {
+                while (have + copy > (size <<= 1))
+                    ;
+                hname = realloc(hname, size);
+                if (hname == NULL)
+                    bail("not enough memory", "");
+            }
+            memcpy(hname + have, in_next, copy);
+            have += copy;
+            in_left -= copy;
+            in_next += copy;
+        } while (end == NULL);
+    }
+    else if (flags & 8)
+        while (GET() != 0)
+            if (in_eof)
+                return -3;
+
+    /* skip comment */
+    if (flags & 16)
+        while (GET() != 0)
+            if (in_eof)
+                return -3;
+
+    /* skip header crc */
+    if (flags & 2)
+        SKIP(2);
+
+    /* return compression method */
+    return method;
+}
+
+/* --- list contents of compressed input (gzip, zlib, or lzw) */
+
+/* find standard compressed file suffix, return length of suffix */
+local size_t compressed_suffix(char *nm)
+{
+    size_t len;
+
+    len = strlen(nm);
+    if (len > 4) {
+        nm += len - 4;
+        len = 4;
+        if (strcmp(nm, ".zip") == 0 || strcmp(nm, ".ZIP") == 0 ||
+            strcmp(nm, ".tgz") == 0)
+            return 4;
+    }
+    if (len > 3) {
+        nm += len - 3;
+        len = 3;
+        if (strcmp(nm, ".gz") == 0 || strcmp(nm, "-gz") == 0 ||
+            strcmp(nm, ".zz") == 0 || strcmp(nm, "-zz") == 0)
+            return 3;
+    }
+    if (len > 2) {
+        nm += len - 2;
+        if (strcmp(nm, ".z") == 0 || strcmp(nm, "-z") == 0 ||
+            strcmp(nm, "_z") == 0 || strcmp(nm, ".Z") == 0)
+            return 2;
+    }
+    return 0;
+}
+
+/* listing file name lengths for -l and -lv */
+#define NAMEMAX1 48     /* name display limit at verbosity 1 */
+#define NAMEMAX2 16     /* name display limit at verbosity 2 */
+
+/* print gzip or lzw file information */
+local void show_info(int method, unsigned long check, off_t len, int cont)
+{
+    size_t max;             /* maximum name length for current verbosity */
+    size_t n;               /* name length without suffix */
+    time_t now;             /* for getting current year */
+    char mod[26];           /* modification time in text */
+    char name[NAMEMAX1+1];  /* header or file name, possibly truncated */
+
+    /* create abbreviated name from header file name or actual file name */
+    max = verbosity > 1 ? NAMEMAX2 : NAMEMAX1;
+    memset(name, 0, max + 1);
+    if (cont)
+        strncpy(name, "<...>", max + 1);
+    else if (hname == NULL) {
+        n = strlen(in) - compressed_suffix(in);
+        strncpy(name, in, n > max + 1 ? max + 1 : n);
+        if (strcmp(in + n, ".tgz") == 0 && n < max + 1)
+            strncpy(name + n, ".tar", max + 1 - n);
+    }
+    else
+        strncpy(name, hname, max + 1);
+    if (name[max])
+        strcpy(name + max - 3, "...");
+
+    /* convert time stamp to text */
+    if (stamp) {
+        strcpy(mod, ctime(&stamp));
+        now = time(NULL);
+        if (strcmp(mod + 20, ctime(&now) + 20) != 0)
+            strcpy(mod + 11, mod + 19);
+    }
+    else
+        strcpy(mod + 4, "------ -----");
+    mod[16] = 0;
+
+    /* if first time, print header */
+    if (first) {
+        if (verbosity > 1)
+            fputs("method    check    timestamp    ", stdout);
+        if (verbosity > 0)
+            puts("compressed   original reduced  name");
+        first = 0;
+    }
+
+    /* print information */
+    if (verbosity > 1) {
+        if (form == 3 && !decode)
+            printf("zip%3d  --------  %s  ", method, mod + 4);
+        else if (form > 1)
+            printf("zip%3d  %08lx  %s  ", method, check, mod + 4);
+        else if (form)
+            printf("zlib%2d  %08lx  %s  ", method, check, mod + 4);
+        else if (method == 256)
+            printf("lzw     --------  %s  ", mod + 4);
+        else
+            printf("gzip%2d  %08lx  %s  ", method, check, mod + 4);
+    }
+    if (verbosity > 0) {
+        if ((form == 3 && !decode) ||
+            (method == 8 && in_tot > (len + (len >> 10) + 12)) ||
+            (method == 256 && in_tot > len + (len >> 1) + 3))
+#if __STDC_VERSION__-0 >= 199901L || __GNUC__-0 >= 3
+            printf("%10jd %10jd?  unk    %s\n",
+                   (intmax_t)in_tot, (intmax_t)len, name);
+        else
+            printf("%10jd %10jd %6.1f%%  %s\n",
+                   (intmax_t)in_tot, (intmax_t)len,
+                   len == 0 ? 0 : 100 * (len - in_tot)/(double)len,
+                   name);
+#else
+            printf(sizeof(off_t) == sizeof(long) ?
+                   "%10ld %10ld?  unk    %s\n" : "%10lld %10lld?  unk    %s\n",
+                   in_tot, len, name);
+        else
+            printf(sizeof(off_t) == sizeof(long) ?
+                   "%10ld %10ld %6.1f%%  %s\n" : "%10lld %10lld %6.1f%%  %s\n",
+                   in_tot, len,
+                   len == 0 ? 0 : 100 * (len - in_tot)/(double)len,
+                   name);
+#endif
+    }
+}
+
+/* list content information about the gzip file at ind (only works if the gzip
+   file contains a single gzip stream with no junk at the end, and only works
+   well if the uncompressed length is less than 4 GB) */
+local void list_info(void)
+{
+    int method;             /* get_header() return value */
+    size_t n;               /* available trailer bytes */
+    off_t at;               /* used to calculate compressed length */
+    unsigned char tail[8];  /* trailer containing check and length */
+    unsigned long check, len;   /* check value and length from trailer */
+
+    /* initialize input buffer */
+    in_init();
+
+    /* read header information and position input after header */
+    method = get_header(1);
+    if (method < 0) {
+        RELEASE(hname);
+        if (method != -1 && verbosity > 1)
+            complain("%s not a compressed file -- skipping", in);
+        return;
+    }
+
+    /* list zip file */
+    if (form > 1) {
+        in_tot = zip_clen;
+        show_info(method, zip_crc, zip_ulen, 0);
+        return;
+    }
+
+    /* list zlib file */
+    if (form) {
+        at = lseek(ind, 0, SEEK_END);
+        if (at == -1) {
+            check = 0;
+            do {
+                len = in_left < 4 ? in_left : 4;
+                in_next += in_left - len;
+                while (len--)
+                    check = (check << 8) + *in_next++;
+            } while (load() != 0);
+            check &= LOW32;
+        }
+        else {
+            in_tot = at;
+            lseek(ind, -4, SEEK_END);
+            readn(ind, tail, 4);
+            check = PULL4M(tail);
+        }
+        in_tot -= 6;
+        show_info(method, check, 0, 0);
+        return;
+    }
+
+    /* list lzw file */
+    if (method == 256) {
+        at = lseek(ind, 0, SEEK_END);
+        if (at == -1)
+            while (load() != 0)
+                ;
+        else
+            in_tot = at;
+        in_tot -= 3;
+        show_info(method, 0, 0, 0);
+        return;
+    }
+
+    /* skip to end to get trailer (8 bytes), compute compressed length */
+    if (in_short) {                     /* whole thing already read */
+        if (in_left < 8) {
+            complain("%s not a valid gzip file -- skipping", in);
+            return;
+        }
+        in_tot = in_left - 8;           /* compressed size */
+        memcpy(tail, in_next + (in_left - 8), 8);
+    }
+    else if ((at = lseek(ind, -8, SEEK_END)) != -1) {
+        in_tot = at - in_tot + in_left; /* compressed size */
+        readn(ind, tail, 8);            /* get trailer */
+    }
+    else {                              /* can't seek */
+        at = in_tot - in_left;          /* save header size */
+        do {
+            n = in_left < 8 ? in_left : 8;
+            memcpy(tail, in_next + (in_left - n), n);
+            load();
+        } while (in_left == BUF);       /* read until end */
+        if (in_left < 8) {
+            if (n + in_left < 8) {
+                complain("%s not a valid gzip file -- skipping", in);
+                return;
+            }
+            if (in_left) {
+                if (n + in_left > 8)
+                    memcpy(tail, tail + n - (8 - in_left), 8 - in_left);
+                memcpy(tail + 8 - in_left, in_next, in_left);
+            }
+        }
+        else
+            memcpy(tail, in_next + (in_left - 8), 8);
+        in_tot -= at + 8;
+    }
+    if (in_tot < 2) {
+        complain("%s not a valid gzip file -- skipping", in);
+        return;
+    }
+
+    /* convert trailer to check and uncompressed length (modulo 2^32) */
+    check = PULL4L(tail);
+    len = PULL4L(tail + 4);
+
+    /* list information about contents */
+    show_info(method, check, len, 0);
+    RELEASE(hname);
+}
+
+/* --- copy input to output (when acting like cat) --- */
+
+local void cat(void)
+{
+    /* write first magic byte (if we're here, there's at least one byte) */
+    writen(outd, &magic1, 1);
+    out_tot = 1;
+
+    /* copy the remainder of the input to the output (if there were any more
+       bytes of input, then in_left is non-zero and in_next is pointing to the
+       second magic byte) */
+    while (in_left) {
+        writen(outd, in_next, in_left);
+        out_tot += in_left;
+        in_left = 0;
+        load();
+    }
+}
+
+/* --- decompress deflate input --- */
+
+/* call-back input function for inflateBack() */
+local unsigned inb(void *desc, unsigned char **buf)
+{
+    (void)desc;
+    load();
+    *buf = in_next;
+    return in_left;
+}
+
+/* output buffers and window for infchk() and unlzw() */
+#define OUTSIZE 32768U      /* must be at least 32K for inflateBack() window */
+local unsigned char out_buf[OUTSIZE];
+
+#ifndef NOTHREAD
+/* output data for parallel write and check */
+local unsigned char out_copy[OUTSIZE];
+local size_t out_len;
+
+/* outb threads states */
+local lock *outb_write_more = NULL;
+local lock *outb_check_more;
+
+/* output write thread */
+local void outb_write(void *dummy)
+{
+    size_t len;
+
+    (void)dummy;
+
+    Trace(("-- launched decompress write thread"));
+    do {
+        possess(outb_write_more);
+        wait_for(outb_write_more, TO_BE, 1);
+        len = out_len;
+        if (len && decode == 1)
+            writen(outd, out_copy, len);
+        Trace(("-- decompress wrote %lu bytes", len));
+        twist(outb_write_more, TO, 0);
+    } while (len);
+    Trace(("-- exited decompress write thread"));
+}
+
+/* output check thread */
+local void outb_check(void *dummy)
+{
+    size_t len;
+
+    (void)dummy;
+
+    Trace(("-- launched decompress check thread"));
+    do {
+        possess(outb_check_more);
+        wait_for(outb_check_more, TO_BE, 1);
+        len = out_len;
+        out_check = CHECK(out_check, out_copy, len);
+        Trace(("-- decompress checked %lu bytes", len));
+        twist(outb_check_more, TO, 0);
+    } while (len);
+    Trace(("-- exited decompress check thread"));
+}
+#endif
+
+/* call-back output function for inflateBack() -- wait for the last write and
+   check calculation to complete, copy the write buffer, and then alert the
+   write and check threads and return for more decompression while that's
+   going on (or just write and check if no threads or if proc == 1) */
+local int outb(void *desc, unsigned char *buf, unsigned len)
+{
+#ifndef NOTHREAD
+    static thread *wr, *ch;
+
+    (void)desc;
+
+    if (procs > 1) {
+        /* if first time, initialize state and launch threads */
+        if (outb_write_more == NULL) {
+            outb_write_more = new_lock(0);
+            outb_check_more = new_lock(0);
+            wr = launch(outb_write, NULL);
+            ch = launch(outb_check, NULL);
+        }
+
+        /* wait for previous write and check threads to complete */
+        possess(outb_check_more);
+        wait_for(outb_check_more, TO_BE, 0);
+        possess(outb_write_more);
+        wait_for(outb_write_more, TO_BE, 0);
+
+        /* copy the output and alert the worker bees */
+        out_len = len;
+        out_tot += len;
+        memcpy(out_copy, buf, len);
+        twist(outb_write_more, TO, 1);
+        twist(outb_check_more, TO, 1);
+
+        /* if requested with len == 0, clean up -- terminate and join write and
+           check threads, free lock */
+        if (len == 0) {
+            join(ch);
+            join(wr);
+            free_lock(outb_check_more);
+            free_lock(outb_write_more);
+            outb_write_more = NULL;
+        }
+
+        /* return for more decompression while last buffer is being written
+           and having its check value calculated -- we wait for those to finish
+           the next time this function is called */
+        return 0;
+    }
+#endif
+
+    /* if just one process or no threads, then do it without threads */
+    if (len) {
+        if (decode == 1)
+            writen(outd, buf, len);
+        out_check = CHECK(out_check, buf, len);
+        out_tot += len;
+    }
+    return 0;
+}
+
+/* inflate for decompression or testing -- decompress from ind to outd unless
+   decode != 1, in which case just test ind, and then also list if list != 0;
+   look for and decode multiple, concatenated gzip and/or zlib streams;
+   read and check the gzip, zlib, or zip trailer */
+local void infchk(void)
+{
+    int ret, cont;
+    unsigned long check, len;
+    z_stream strm;
+    unsigned tmp2;
+    unsigned long tmp4;
+    off_t clen;
+
+    cont = 0;
+    do {
+        /* header already read -- set up for decompression */
+        in_tot = in_left;               /* track compressed data length */
+        out_tot = 0;
+        out_check = CHECK(0L, Z_NULL, 0);
+        strm.zalloc = Z_NULL;
+        strm.zfree = Z_NULL;
+        strm.opaque = Z_NULL;
+        ret = inflateBackInit(&strm, 15, out_buf);
+        if (ret != Z_OK)
+            bail("not enough memory", "");
+
+        /* decompress, compute lengths and check value */
+        strm.avail_in = in_left;
+        strm.next_in = in_next;
+        ret = inflateBack(&strm, inb, NULL, outb, NULL);
+        if (ret != Z_STREAM_END)
+            bail("corrupted input -- invalid deflate data: ", in);
+        in_left = strm.avail_in;
+        in_next = strm.next_in;
+        inflateBackEnd(&strm);
+        outb(NULL, NULL, 0);        /* finish off final write and check */
+
+        /* compute compressed data length */
+        clen = in_tot - in_left;
+
+        /* read and check trailer */
+        if (form > 1) {             /* zip local trailer (if any) */
+            if (form == 3) {        /* data descriptor follows */
+                /* read original version of data descriptor */
+                zip_crc = GET4();
+                zip_clen = GET4();
+                zip_ulen = GET4();
+                if (in_eof)
+                    bail("corrupted zip entry -- missing trailer: ", in);
+
+                /* if crc doesn't match, try info-zip variant with sig */
+                if (zip_crc != out_check) {
+                    if (zip_crc != 0x08074b50UL || zip_clen != out_check)
+                        bail("corrupted zip entry -- crc32 mismatch: ", in);
+                    zip_crc = zip_clen;
+                    zip_clen = zip_ulen;
+                    zip_ulen = GET4();
+                }
+
+                /* handle incredibly rare cases where crc equals signature */
+                else if (zip_crc == 0x08074b50UL && zip_clen == zip_crc &&
+                         ((clen & LOW32) != zip_crc || zip_ulen == zip_crc)) {
+                    zip_crc = zip_clen;
+                    zip_clen = zip_ulen;
+                    zip_ulen = GET4();
+                }
+
+                /* if second length doesn't match, try 64-bit lengths */
+                if (zip_ulen != (out_tot & LOW32)) {
+                    zip_ulen = GET4();
+                    (void)GET4();
+                }
+                if (in_eof)
+                    bail("corrupted zip entry -- missing trailer: ", in);
+            }
+            if (zip_clen != (clen & LOW32) || zip_ulen != (out_tot & LOW32))
+                bail("corrupted zip entry -- length mismatch: ", in);
+            check = zip_crc;
+        }
+        else if (form == 1) {       /* zlib (big-endian) trailer */
+            check = (unsigned long)(GET()) << 24;
+            check += (unsigned long)(GET()) << 16;
+            check += (unsigned)(GET()) << 8;
+            check += GET();
+            if (in_eof)
+                bail("corrupted zlib stream -- missing trailer: ", in);
+            if (check != out_check)
+                bail("corrupted zlib stream -- adler32 mismatch: ", in);
+        }
+        else {                      /* gzip trailer */
+            check = GET4();
+            len = GET4();
+            if (in_eof)
+                bail("corrupted gzip stream -- missing trailer: ", in);
+            if (check != out_check)
+                bail("corrupted gzip stream -- crc32 mismatch: ", in);
+            if (len != (out_tot & LOW32))
+                bail("corrupted gzip stream -- length mismatch: ", in);
+        }
+
+        /* show file information if requested */
+        if (list) {
+            in_tot = clen;
+            show_info(8, check, out_tot, cont);
+            cont = 1;
+        }
+
+        /* if a gzip or zlib entry follows a gzip or zlib entry, decompress it
+           (don't replace saved header information from first entry) */
+    } while (form < 2 && (ret = get_header(0)) == 8 && form < 2);
+
+    /* gzip -cdf copies junk after gzip stream directly to output */
+    if (form < 2 && ret == -2 && force && pipeout && decode != 2 && !list)
+        cat();
+    else if (ret != -1 && form < 2)
+        complain("%s OK, has trailing junk which was ignored", in);
+}
+
+/* --- decompress Unix compress (LZW) input --- */
+
+/* memory for unlzw() --
+   the first 256 entries of prefix[] and suffix[] are never used, could
+   have offset the index, but it's faster to waste the memory */
+unsigned short prefix[65536];           /* index to LZW prefix string */
+unsigned char suffix[65536];            /* one-character LZW suffix */
+unsigned char match[65280 + 2];         /* buffer for reversed match */
+
+/* throw out what's left in the current bits byte buffer (this is a vestigial
+   aspect of the compressed data format derived from an implementation that
+   made use of a special VAX machine instruction!) */
+#define FLUSHCODE() \
+    do { \
+        left = 0; \
+        rem = 0; \
+        if (chunk > in_left) { \
+            chunk -= in_left; \
+            if (load() == 0) \
+                break; \
+            if (chunk > in_left) { \
+                chunk = in_left = 0; \
+                break; \
+            } \
+        } \
+        in_left -= chunk; \
+        in_next += chunk; \
+        chunk = 0; \
+    } while (0)
+
+/* Decompress a compress (LZW) file from ind to outd.  The compress magic
+   header (two bytes) has already been read and verified. */
+local void unlzw(void)
+{
+    int got;                    /* byte just read by GET() */
+    unsigned chunk;             /* bytes left in current chunk */
+    int left;                   /* bits left in rem */
+    unsigned rem;               /* unused bits from input */
+    int bits;                   /* current bits per code */
+    unsigned code;              /* code, table traversal index */
+    unsigned mask;              /* mask for current bits codes */
+    int max;                    /* maximum bits per code for this stream */
+    int flags;                  /* compress flags, then block compress flag */
+    unsigned end;               /* last valid entry in prefix/suffix tables */
+    unsigned temp;              /* current code */
+    unsigned prev;              /* previous code */
+    unsigned final;             /* last character written for previous code */
+    unsigned stack;             /* next position for reversed string */
+    unsigned outcnt;            /* bytes in output buffer */
+    unsigned char *p;
+
+    /* process remainder of compress header -- a flags byte */
+    out_tot = 0;
+    flags = GET();
+    if (in_eof)
+        bail("missing lzw data: ", in);
+    if (flags & 0x60)
+        bail("unknown lzw flags set: ", in);
+    max = flags & 0x1f;
+    if (max < 9 || max > 16)
+        bail("lzw bits out of range: ", in);
+    if (max == 9)                           /* 9 doesn't really mean 9 */
+        max = 10;
+    flags &= 0x80;                          /* true if block compress */
+
+    /* clear table */
+    bits = 9;
+    mask = 0x1ff;
+    end = flags ? 256 : 255;
+
+    /* set up: get first 9-bit code, which is the first decompressed byte, but
+       don't create a table entry until the next code */
+    got = GET();
+    if (in_eof)                             /* no compressed data is ok */
+        return;
+    final = prev = (unsigned)got;           /* low 8 bits of code */
+    got = GET();
+    if (in_eof || (got & 1) != 0)           /* missing a bit or code >= 256 */
+        bail("invalid lzw code: ", in);
+    rem = (unsigned)got >> 1;               /* remaining 7 bits */
+    left = 7;
+    chunk = bits - 2;                       /* 7 bytes left in this chunk */
+    out_buf[0] = (unsigned char)final;      /* write first decompressed byte */
+    outcnt = 1;
+
+    /* decode codes */
+    stack = 0;
+    for (;;) {
+        /* if the table will be full after this, increment the code size */
+        if (end >= mask && bits < max) {
+            FLUSHCODE();
+            bits++;
+            mask <<= 1;
+            mask++;
+        }
+
+        /* get a code of length bits */
+        if (chunk == 0)                     /* decrement chunk modulo bits */
+            chunk = bits;
+        code = rem;                         /* low bits of code */
+        got = GET();
+        if (in_eof) {                       /* EOF is end of compressed data */
+            /* write remaining buffered output */
+            out_tot += outcnt;
+            if (outcnt && decode == 1)
+                writen(outd, out_buf, outcnt);
+            return;
+        }
+        code += (unsigned)got << left;      /* middle (or high) bits of code */
+        left += 8;
+        chunk--;
+        if (bits > left) {                  /* need more bits */
+            got = GET();
+            if (in_eof)                     /* can't end in middle of code */
+                bail("invalid lzw code: ", in);
+            code += (unsigned)got << left;  /* high bits of code */
+            left += 8;
+            chunk--;
+        }
+        code &= mask;                       /* mask to current code length */
+        left -= bits;                       /* number of unused bits */
+        rem = (unsigned)got >> (8 - left);  /* unused bits from last byte */
+
+        /* process clear code (256) */
+        if (code == 256 && flags) {
+            FLUSHCODE();
+            bits = 9;                       /* initialize bits and mask */
+            mask = 0x1ff;
+            end = 255;                      /* empty table */
+            continue;                       /* get next code */
+        }
+
+        /* special code to reuse last match */
+        temp = code;                        /* save the current code */
+        if (code > end) {
+            /* Be picky on the allowed code here, and make sure that the code
+               we drop through (prev) will be a valid index so that random
+               input does not cause an exception.  The code != end + 1 check is
+               empirically derived, and not checked in the original uncompress
+               code.  If this ever causes a problem, that check could be safely
+               removed.  Leaving this check in greatly improves pigz's ability
+               to detect random or corrupted input after a compress header.
+               In any case, the prev > end check must be retained. */
+            if (code != end + 1 || prev > end)
+                bail("invalid lzw code: ", in);
+            match[stack++] = (unsigned char)final;
+            code = prev;
+        }
+
+        /* walk through linked list to generate output in reverse order */
+        p = match + stack;
+        while (code >= 256) {
+            *p++ = suffix[code];
+            code = prefix[code];
+        }
+        stack = p - match;
+        match[stack++] = (unsigned char)code;
+        final = code;
+
+        /* link new table entry */
+        if (end < mask) {
+            end++;
+            prefix[end] = (unsigned short)prev;
+            suffix[end] = (unsigned char)final;
+        }
+
+        /* set previous code for next iteration */
+        prev = temp;
+
+        /* write output in forward order */
+        while (stack > OUTSIZE - outcnt) {
+            while (outcnt < OUTSIZE)
+                out_buf[outcnt++] = match[--stack];
+            out_tot += outcnt;
+            if (decode == 1)
+                writen(outd, out_buf, outcnt);
+            outcnt = 0;
+        }
+        p = match + stack;
+        do {
+            out_buf[outcnt++] = *--p;
+        } while (p > match);
+        stack = 0;
+
+        /* loop for next code with final and prev as the last match, rem and
+           left provide the first 0..7 bits of the next code, end is the last
+           valid table entry */
+    }
+}
+
+/* --- file processing --- */
+
+/* extract file name from path */
+local char *justname(char *path)
+{
+    char *p;
+
+    p = path + strlen(path);
+    while (--p >= path)
+        if (*p == '/')
+            break;
+    return p + 1;
+}
+
+/* Copy file attributes, from -> to, as best we can.  This is best effort, so
+   no errors are reported.  The mode bits, including suid, sgid, and the sticky
+   bit are copied (if allowed), the owner's user id and group id are copied
+   (again if allowed), and the access and modify times are copied. */
+local void copymeta(char *from, char *to)
+{
+    struct stat st;
+    struct timeval times[2];
+
+    /* get all of from's Unix meta data, return if not a regular file */
+    if (stat(from, &st) != 0 || (st.st_mode & S_IFMT) != S_IFREG)
+        return;
+
+    /* set to's mode bits, ignore errors */
+    (void)chmod(to, st.st_mode & 07777);
+
+    /* copy owner's user and group, ignore errors */
+    (void)chown(to, st.st_uid, st.st_gid);
+
+    /* copy access and modify times, ignore errors */
+    times[0].tv_sec = st.st_atime;
+    times[0].tv_usec = 0;
+    times[1].tv_sec = st.st_mtime;
+    times[1].tv_usec = 0;
+    (void)utimes(to, times);
+}
+
+/* set the access and modify times of fd to t */
+local void touch(char *path, time_t t)
+{
+    struct timeval times[2];
+
+    times[0].tv_sec = t;
+    times[0].tv_usec = 0;
+    times[1].tv_sec = t;
+    times[1].tv_usec = 0;
+    (void)utimes(path, times);
+}
+
+/* process provided input file, or stdin if path is NULL -- process() can
+   call itself for recursive directory processing */
+local void process(char *path)
+{
+    int method = -1;                /* get_header() return value */
+    size_t len;                     /* length of base name (minus suffix) */
+    struct stat st;                 /* to get file type and mod time */
+    /* all compressed suffixes for decoding search, in length order */
+    static char *sufs[] = {".z", "-z", "_z", ".Z", ".gz", "-gz", ".zz", "-zz",
+                           ".zip", ".ZIP", ".tgz", NULL};
+
+    /* open input file with name in, descriptor ind -- set name and mtime */
+    if (path == NULL) {
+        strcpy(in, "<stdin>");
+        ind = 0;
+        name = NULL;
+        mtime = headis & 2 ?
+                (fstat(ind, &st) ? time(NULL) : st.st_mtime) : 0;
+        len = 0;
+    }
+    else {
+        /* set input file name (already set if recursed here) */
+        if (path != in) {
+            strncpy(in, path, sizeof(in));
+            if (in[sizeof(in) - 1])
+                bail("name too long: ", path);
+        }
+        len = strlen(in);
+
+        /* try to stat input file -- if not there and decoding, look for that
+           name with compressed suffixes */
+        if (lstat(in, &st)) {
+            if (errno == ENOENT && (list || decode)) {
+                char **try = sufs;
+                do {
+                    if (*try == NULL || len + strlen(*try) >= sizeof(in))
+                        break;
+                    strcpy(in + len, *try++);
+                    errno = 0;
+                } while (lstat(in, &st) && errno == ENOENT);
+            }
+#ifdef EOVERFLOW
+            if (errno == EOVERFLOW || errno == EFBIG)
+                bail(in,
+                    " too large -- not compiled with large file support");
+#endif
+            if (errno) {
+                in[len] = 0;
+                complain("%s does not exist -- skipping", in);
+                return;
+            }
+            len = strlen(in);
+        }
+
+        /* only process regular files, but allow symbolic links if -f,
+           recurse into directory if -r */
+        if ((st.st_mode & S_IFMT) != S_IFREG &&
+            (st.st_mode & S_IFMT) != S_IFLNK &&
+            (st.st_mode & S_IFMT) != S_IFDIR) {
+            complain("%s is a special file or device -- skipping", in);
+            return;
+        }
+        if ((st.st_mode & S_IFMT) == S_IFLNK && !force && !pipeout) {
+            complain("%s is a symbolic link -- skipping", in);
+            return;
+        }
+        if ((st.st_mode & S_IFMT) == S_IFDIR && !recurse) {
+            complain("%s is a directory -- skipping", in);
+            return;
+        }
+
+        /* recurse into directory (assumes Unix) */
+        if ((st.st_mode & S_IFMT) == S_IFDIR) {
+            char *roll, *item, *cut, *base, *bigger;
+            size_t len, hold;
+            DIR *here;
+            struct dirent *next;
+
+            /* accumulate list of entries (need to do this, since readdir()
+               behavior not defined if directory modified between calls) */
+            here = opendir(in);
+            if (here == NULL)
+                return;
+            hold = 512;
+            roll = malloc(hold);
+            if (roll == NULL)
+                bail("not enough memory", "");
+            *roll = 0;
+            item = roll;
+            while ((next = readdir(here)) != NULL) {
+                if (next->d_name[0] == 0 ||
+                    (next->d_name[0] == '.' && (next->d_name[1] == 0 ||
+                     (next->d_name[1] == '.' && next->d_name[2] == 0))))
+                    continue;
+                len = strlen(next->d_name) + 1;
+                if (item + len + 1 > roll + hold) {
+                    do {                    /* make roll bigger */
+                        hold <<= 1;
+                    } while (item + len + 1 > roll + hold);
+                    bigger = realloc(roll, hold);
+                    if (bigger == NULL) {
+                        free(roll);
+                        bail("not enough memory", "");
+                    }
+                    item = bigger + (item - roll);
+                    roll = bigger;
+                }
+                strcpy(item, next->d_name);
+                item += len;
+                *item = 0;
+            }
+            closedir(here);
+
+            /* run process() for each entry in the directory */
+            cut = base = in + strlen(in);
+            if (base > in && base[-1] != (unsigned char)'/') {
+                if ((size_t)(base - in) >= sizeof(in))
+                    bail("path too long", in);
+                *base++ = '/';
+            }
+            item = roll;
+            while (*item) {
+                strncpy(base, item, sizeof(in) - (base - in));
+                if (in[sizeof(in) - 1]) {
+                    strcpy(in + (sizeof(in) - 4), "...");
+                    bail("path too long: ", in);
+                }
+                process(in);
+                item += strlen(item) + 1;
+            }
+            *cut = 0;
+
+            /* release list of entries */
+            free(roll);
+            return;
+        }
+
+        /* don't compress .gz (or provided suffix) files, unless -f */
+        if (!(force || list || decode) && len >= strlen(sufx) &&
+                strcmp(in + len - strlen(sufx), sufx) == 0) {
+            complain("%s ends with %s -- skipping", in, sufx);
+            return;
+        }
+
+        /* only decompress over input file with compressed suffix */
+        if (decode && !pipeout) {
+            int suf = compressed_suffix(in);
+            if (suf == 0) {
+                complain("%s does not have compressed suffix -- skipping", in);
+                return;
+            }
+            len -= suf;
+        }
+
+        /* open input file */
+        ind = open(in, O_RDONLY, 0);
+        if (ind < 0)
+            bail("read error on ", in);
+
+        /* prepare gzip header information for compression */
+        name = headis & 1 ? justname(in) : NULL;
+        mtime = headis & 2 ? st.st_mtime : 0;
+    }
+    SET_BINARY_MODE(ind);
+
+    /* if decoding or testing, try to read gzip header */
+    hname = NULL;
+    if (decode) {
+        in_init();
+        method = get_header(1);
+        if (method != 8 && method != 256 &&
+                /* gzip -cdf acts like cat on uncompressed input */
+                !(method == -2 && force && pipeout && decode != 2 && !list)) {
+            RELEASE(hname);
+            if (ind != 0)
+                close(ind);
+            if (method != -1)
+                complain(method < 0 ? "%s is not compressed -- skipping" :
+                         "%s has unknown compression method -- skipping", in);
+            return;
+        }
+
+        /* if requested, test input file (possibly a special list) */
+        if (decode == 2) {
+            if (method == 8)
+                infchk();
+            else {
+                unlzw();
+                if (list) {
+                    in_tot -= 3;
+                    show_info(method, 0, out_tot, 0);
+                }
+            }
+            RELEASE(hname);
+            if (ind != 0)
+                close(ind);
+            return;
+        }
+    }
+
+    /* if requested, just list information about input file */
+    if (list) {
+        list_info();
+        RELEASE(hname);
+        if (ind != 0)
+            close(ind);
+        return;
+    }
+
+    /* create output file out, descriptor outd */
+    if (path == NULL || pipeout) {
+        /* write to stdout */
+        out = malloc(strlen("<stdout>") + 1);
+        if (out == NULL)
+            bail("not enough memory", "");
+        strcpy(out, "<stdout>");
+        outd = 1;
+        if (!decode && !force && isatty(outd))
+            bail("trying to write compressed data to a terminal",
+                 " (use -f to force)");
+    }
+    else {
+        char *to, *repl;
+
+        /* use header name for output when decompressing with -N */
+        to = in;
+        if (decode && (headis & 1) != 0 && hname != NULL) {
+            to = hname;
+            len = strlen(hname);
+        }
+
+        /* replace .tgx with .tar when decoding */
+        repl = decode && strcmp(to + len, ".tgz") ? "" : ".tar";
+
+        /* create output file and open to write */
+        out = malloc(len + (decode ? strlen(repl) : strlen(sufx)) + 1);
+        if (out == NULL)
+            bail("not enough memory", "");
+        memcpy(out, to, len);
+        strcpy(out + len, decode ? repl : sufx);
+        outd = open(out, O_CREAT | O_TRUNC | O_WRONLY |
+                         (force ? 0 : O_EXCL), 0600);
+
+        /* if exists and not -f, give user a chance to overwrite */
+        if (outd < 0 && errno == EEXIST && isatty(0) && verbosity) {
+            int ch, reply;
+
+            fprintf(stderr, "%s exists -- overwrite (y/n)? ", out);
+            fflush(stderr);
+            reply = -1;
+            do {
+                ch = getchar();
+                if (reply < 0 && ch != ' ' && ch != '\t')
+                    reply = ch == 'y' || ch == 'Y' ? 1 : 0;
+            } while (ch != EOF && ch != '\n' && ch != '\r');
+            if (reply == 1)
+                outd = open(out, O_CREAT | O_TRUNC | O_WRONLY,
+                            0600);
+        }
+
+        /* if exists and no overwrite, report and go on to next */
+        if (outd < 0 && errno == EEXIST) {
+            complain("%s exists -- skipping", out);
+            RELEASE(out);
+            RELEASE(hname);
+            if (ind != 0)
+                close(ind);
+            return;
+        }
+
+        /* if some other error, give up */
+        if (outd < 0)
+            bail("write error on ", out);
+    }
+    SET_BINARY_MODE(outd);
+    RELEASE(hname);
+
+    /* process ind to outd */
+    if (verbosity > 1)
+        fprintf(stderr, "%s to %s ", in, out);
+    if (decode) {
+        if (method == 8)
+            infchk();
+        else if (method == 256)
+            unlzw();
+        else
+            cat();
+    }
+#ifndef NOTHREAD
+    else if (procs > 1)
+        parallel_compress();
+#endif
+    else
+        single_compress(0);
+    if (verbosity > 1) {
+        putc('\n', stderr);
+        fflush(stderr);
+    }
+
+    /* finish up, copy attributes, set times, delete original */
+    if (ind != 0)
+        close(ind);
+    if (outd != 1) {
+        if (close(outd))
+            bail("write error on ", out);
+        outd = -1;              /* now prevent deletion on interrupt */
+        if (ind != 0) {
+            copymeta(in, out);
+            if (!keep)
+                unlink(in);
+        }
+        if (decode && (headis & 2) != 0 && stamp)
+            touch(out, stamp);
+    }
+    RELEASE(out);
+}
+
+local char *helptext[] = {
+"Usage: pigz [options] [files ...]",
+"  will compress files in place, adding the suffix '.gz'.  If no files are",
+#ifdef NOTHREAD
+"  specified, stdin will be compressed to stdout.  pigz does what gzip does.",
+#else
+"  specified, stdin will be compressed to stdout.  pigz does what gzip does,",
+"  but spreads the work over multiple processors and cores when compressing.",
+#endif
+"",
+"Options:",
+"  -0 to -9, --fast, --best   Compression levels, --fast is -1, --best is -9",
+"  -b, --blocksize mmm  Set compression block size to mmmK (default 128K)",
+"  -c, --stdout         Write all processed output to stdout (won't delete)",
+"  -d, --decompress     Decompress the compressed input",
+"  -f, --force          Force overwrite, compress .gz, links, and to terminal",
+"  -h, --help           Display a help screen and quit",
+"  -i, --independent    Compress blocks independently for damage recovery",
+"  -k, --keep           Do not delete original file after processing",
+"  -K, --zip            Compress to PKWare zip (.zip) single entry format",
+"  -l, --list           List the contents of the compressed input",
+"  -L, --license        Display the pigz license and quit",
+"  -n, --no-name        Do not store or restore file name in/from header",
+"  -N, --name           Store/restore file name and mod time in/from header",
+#ifndef NOTHREAD
+"  -p, --processes n    Allow up to n compression threads (default is the",
+"                       number of online processors, or 8 if unknown)",
+#endif
+"  -q, --quiet          Print no messages, even on error",
+"  -r, --recursive      Process the contents of all subdirectories",
+"  -R, --rsyncable      Input-determined block locations for rsync",
+"  -S, --suffix .sss    Use suffix .sss instead of .gz (for compression)",
+"  -t, --test           Test the integrity of the compressed input",
+"  -T, --no-time        Do not store or restore mod time in/from header",
+#ifdef DEBUG
+"  -v, --verbose        Provide more verbose output (-vv to debug)",
+#else
+"  -v, --verbose        Provide more verbose output",
+#endif
+"  -V  --version        Show the version of pigz",
+"  -z, --zlib           Compress to zlib (.zz) instead of gzip format",
+"  --                   All arguments after \"--\" are treated as files"
+};
+
+/* display the help text above */
+local void help(void)
+{
+    int n;
+
+    if (verbosity == 0)
+        return;
+    for (n = 0; n < (int)(sizeof(helptext) / sizeof(char *)); n++)
+        fprintf(stderr, "%s\n", helptext[n]);
+    fflush(stderr);
+    exit(0);
+}
+
+#ifndef NOTHREAD
+
+/* try to determine the number of processors */
+local int nprocs(int n)
+{
+#  ifdef _SC_NPROCESSORS_ONLN
+    n = (int)sysconf(_SC_NPROCESSORS_ONLN);
+#  else
+#    ifdef _SC_NPROC_ONLN
+    n = (int)sysconf(_SC_NPROC_ONLN);
+#    else
+#      ifdef __hpux
+    struct pst_dynamic psd;
+
+    if (pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0) != -1)
+        n = psd.psd_proc_cnt;
+#      endif
+#    endif
+#  endif
+    return n;
+}
+
+#endif
+
+/* set option defaults */
+local void defaults(void)
+{
+    level = Z_DEFAULT_COMPRESSION;
+#ifdef NOTHREAD
+    procs = 1;
+#else
+    procs = nprocs(8);
+#endif
+    size = 131072UL;
+    rsync = 0;                      /* don't do rsync blocking */
+    setdict = 1;                    /* initialize dictionary each thread */
+    verbosity = 1;                  /* normal message level */
+    headis = 3;                     /* store/restore name and timestamp */
+    pipeout = 0;                    /* don't force output to stdout */
+    sufx = ".gz";                   /* compressed file suffix */
+    decode = 0;                     /* compress */
+    list = 0;                       /* compress */
+    keep = 0;                       /* delete input file once compressed */
+    force = 0;                      /* don't overwrite, don't compress links */
+    recurse = 0;                    /* don't go into directories */
+    form = 0;                       /* use gzip format */
+}
+
+/* long options conversion to short options */
+local char *longopts[][2] = {
+    {"LZW", "Z"}, {"ascii", "a"}, {"best", "9"}, {"bits", "Z"},
+    {"blocksize", "b"}, {"decompress", "d"}, {"fast", "1"}, {"force", "f"},
+    {"help", "h"}, {"independent", "i"}, {"keep", "k"}, {"license", "L"},
+    {"list", "l"}, {"name", "N"}, {"no-name", "n"}, {"no-time", "T"},
+    {"processes", "p"}, {"quiet", "q"}, {"recursive", "r"}, {"rsyncable", "R"},
+    {"silent", "q"}, {"stdout", "c"}, {"suffix", "S"}, {"test", "t"},
+    {"to-stdout", "c"}, {"uncompress", "d"}, {"verbose", "v"},
+    {"version", "V"}, {"zip", "K"}, {"zlib", "z"}};
+#define NLOPTS (sizeof(longopts) / (sizeof(char *) << 1))
+
+/* either new buffer size, new compression level, or new number of processes --
+   get rid of old buffers and threads to force the creation of new ones with
+   the new settings */
+local void new_opts(void)
+{
+    single_compress(1);
+#ifndef NOTHREAD
+    finish_jobs();
+#endif
+}
+
+/* verify that arg is only digits, and if so, return the decimal value */
+local size_t num(char *arg)
+{
+    char *str = arg;
+    size_t val = 0;
+
+    if (*str == 0)
+        bail("internal error: empty parameter", "");
+    do {
+        if (*str < '0' || *str > '9')
+            bail("invalid numeric parameter: ", arg);
+        val = val * 10 + (*str - '0');
+        /* %% need to detect overflow here */
+    } while (*++str);
+    return val;
+}
+
+/* process an option, return true if a file name and not an option */
+local int option(char *arg)
+{
+    static int get = 0;     /* if not zero, look for option parameter */
+    char bad[3] = "-X";     /* for error messages (X is replaced) */
+
+    /* if no argument or dash option, check status of get */
+    if (get && (arg == NULL || *arg == '-')) {
+        bad[1] = "bpS"[get - 1];
+        bail("missing parameter after ", bad);
+    }
+    if (arg == NULL)
+        return 0;
+
+    /* process long option or short options */
+    if (*arg == '-') {
+        /* a single dash will be interpreted as stdin */
+        if (*++arg == 0)
+            return 1;
+
+        /* process long option (fall through with equivalent short option) */
+        if (*arg == '-') {
+            int j;
+
+            arg++;
+            for (j = NLOPTS - 1; j >= 0; j--)
+                if (strcmp(arg, longopts[j][0]) == 0) {
+                    arg = longopts[j][1];
+                    break;
+                }
+            if (j < 0)
+                bail("invalid option: ", arg - 2);
+        }
+
+        /* process short options (more than one allowed after dash) */
+        do {
+            /* if looking for a parameter, don't process more single character
+               options until we have the parameter */
+            if (get) {
+                if (get == 3)
+                    bail("invalid usage: -s must be followed by space", "");
+                break;      /* allow -pnnn and -bnnn, fall to parameter code */
+            }
+
+            /* process next single character option */
+            bad[1] = *arg;
+            switch (*arg) {
+            case '0': case '1': case '2': case '3': case '4':
+            case '5': case '6': case '7': case '8': case '9':
+                level = *arg - '0';
+                new_opts();
+                break;
+            case 'K':  form = 2;  sufx = ".zip";  break;
+            case 'L':
+                fputs(VERSION, stderr);
+                fputs("Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012"
+                      " Mark Adler\n",
+                      stderr);
+                fputs("Subject to the terms of the zlib license.\n",
+                      stderr);
+                fputs("No warranty is provided or implied.\n", stderr);
+                exit(0);
+            case 'N':  headis = 3;  break;
+            case 'T':  headis &= ~2;  break;
+            case 'R':  rsync = 1;  break;
+            case 'S':  get = 3;  break;
+            case 'V':  fputs(VERSION, stderr);  exit(0);
+            case 'Z':
+                bail("invalid option: LZW output not supported: ", bad);
+            case 'a':
+                bail("invalid option: ascii conversion not supported: ", bad);
+            case 'b':  get = 1;  break;
+            case 'c':  pipeout = 1;  break;
+            case 'd':  decode = 1;  headis = 0;  break;
+            case 'f':  force = 1;  break;
+            case 'h':  help();  break;
+            case 'i':  setdict = 0;  break;
+            case 'k':  keep = 1;  break;
+            case 'l':  list = 1;  break;
+            case 'n':  headis &= ~1;  break;
+            case 'p':  get = 2;  break;
+            case 'q':  verbosity = 0;  break;
+            case 'r':  recurse = 1;  break;
+            case 't':  decode = 2;  break;
+            case 'v':  verbosity++;  break;
+            case 'z':  form = 1;  sufx = ".zz";  break;
+            default:
+                bail("invalid option: ", bad);
+            }
+        } while (*++arg);
+        if (*arg == 0)
+            return 0;
+    }
+
+    /* process option parameter for -b, -p, or -S */
+    if (get) {
+        size_t n;
+
+        if (get == 1) {
+            n = num(arg);
+            size = n << 10;                     /* chunk size */
+            if (size < DICT)
+                bail("block size too small (must be >= 32K)", "");
+            if (n != size >> 10 ||
+                OUTPOOL(size) < size ||
+                (ssize_t)OUTPOOL(size) < 0 ||
+                size > (1UL << 22))
+                bail("block size too large: ", arg);
+            new_opts();
+        }
+        else if (get == 2) {
+            n = num(arg);
+            procs = (int)n;                     /* # processes */
+            if (procs < 1)
+                bail("invalid number of processes: ", arg);
+            if ((size_t)procs != n || INBUFS(procs) < 1)
+                bail("too many processes: ", arg);
+#ifdef NOTHREAD
+            if (procs > 1)
+                bail("compiled without threads", "");
+#endif
+            new_opts();
+        }
+        else if (get == 3)
+            sufx = arg;                         /* gz suffix */
+        get = 0;
+        return 0;
+    }
+
+    /* neither an option nor parameter */
+    return 1;
+}
+
+/* catch termination signal */
+local void cut_short(int sig)
+{
+    (void)sig;
+    Trace(("termination by user"));
+    if (outd != -1 && out != NULL)
+        unlink(out);
+    log_dump();
+    _exit(1);
+}
+
+/* Process arguments, compress in the gzip format.  Note that procs must be at
+   least two in order to provide a dictionary in one work unit for the other
+   work unit, and that size must be at least 32K to store a full dictionary. */
+int main(int argc, char **argv)
+{
+    int n;                          /* general index */
+    int noop;                       /* true to suppress option decoding */
+    unsigned long done;             /* number of named files processed */
+    char *opts, *p;                 /* environment default options, marker */
+
+    /* save pointer to program name for error messages */
+    p = strrchr(argv[0], '/');
+    p = p == NULL ? argv[0] : p + 1;
+    prog = *p ? p : "pigz";
+
+    /* prepare for interrupts and logging */
+    signal(SIGINT, cut_short);
+#ifndef NOTHREAD
+    yarn_prefix = prog;             /* prefix for yarn error messages */
+    yarn_abort = cut_short;         /* call on thread error */
+#endif
+#ifdef DEBUG
+    gettimeofday(&start, NULL);     /* starting time for log entries */
+    log_init();                     /* initialize logging */
+#endif
+
+    /* set all options to defaults */
+    defaults();
+
+    /* process user environment variable defaults in GZIP */
+    opts = getenv("GZIP");
+    if (opts != NULL) {
+        while (*opts) {
+            while (*opts == ' ' || *opts == '\t')
+                opts++;
+            p = opts;
+            while (*p && *p != ' ' && *p != '\t')
+                p++;
+            n = *p;
+            *p = 0;
+            if (option(opts))
+                bail("cannot provide files in GZIP environment variable", "");
+            opts = p + (n ? 1 : 0);
+        }
+        option(NULL);
+    }
+
+    /* process user environment variable defaults in PIGZ as well */
+    opts = getenv("PIGZ");
+    if (opts != NULL) {
+        while (*opts) {
+            while (*opts == ' ' || *opts == '\t')
+                opts++;
+            p = opts;
+            while (*p && *p != ' ' && *p != '\t')
+                p++;
+            n = *p;
+            *p = 0;
+            if (option(opts))
+                bail("cannot provide files in PIGZ environment variable", "");
+            opts = p + (n ? 1 : 0);
+        }
+        option(NULL);
+    }
+
+    /* decompress if named "unpigz" or "gunzip", to stdout if "*cat" */
+    if (strcmp(prog, "unpigz") == 0 || strcmp(prog, "gunzip") == 0)
+        decode = 1, headis = 0;
+    if ((n = strlen(prog)) > 2 && strcmp(prog + n - 3, "cat") == 0)
+        decode = 1, headis = 0, pipeout = 1;
+
+    /* if no arguments and compressed data to or from a terminal, show help */
+    if (argc < 2 && isatty(decode ? 0 : 1))
+        help();
+
+    /* process command-line arguments, no options after "--" */
+    done = noop = 0;
+    for (n = 1; n < argc; n++)
+        if (noop == 0 && strcmp(argv[n], "--") == 0) {
+            noop = 1;
+            option(NULL);
+        }
+        else if (noop || option(argv[n])) { /* true if file name, process it */
+            if (done == 1 && pipeout && !decode && !list && form > 1)
+                complain("warning: output will be concatenated zip files -- "
+                         "will not be able to extract");
+            process(strcmp(argv[n], "-") ? argv[n] : NULL);
+            done++;
+        }
+    option(NULL);
+
+    /* list stdin or compress stdin to stdout if no file names provided */
+    if (done == 0)
+        process(NULL);
+
+    /* done -- release resources, show log */
+    new_opts();
+    log_dump();
+    return warned ? 2 : 0;
+}
diff --git a/pigz/pigz.pdf b/pigz/pigz.pdf
new file mode 100644
index 0000000..3d382f4
--- /dev/null
+++ b/pigz/pigz.pdf
Binary files differ
diff --git a/pigz/pigz.spec b/pigz/pigz.spec
new file mode 100644
index 0000000..96daadb
--- /dev/null
+++ b/pigz/pigz.spec
@@ -0,0 +1,33 @@
+Summary: pigz is a parallel implementation of gzip which utilizes multiple cores
+Name: pigz
+Version: 2.2.5
+Release: 1
+Source0: %{name}-%{version}.tar.gz
+License: zlib
+Group: Applications/Tools
+Packager: Duncan Brown <duncan@duncanbrown.org>
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+URL: http://www.zlib.net/pigz
+
+%description
+pigz, which stands for parallel implementation of gzip, is a fully functional replacement for gzip that exploits multiple processors and multiple cores to the hilt when compressing data. pigz was written by Mark Adler, and uses the zlib and pthread libraries.
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+%prep
+mkdir -p $RPM_BUILD_ROOT
+
+%setup -q
+%build
+make
+mkdir -p ${RPM_BUILD_ROOT}/usr/bin
+mkdir -p ${RPM_BUILD_ROOT}/usr/man/man1
+mv pigz unpigz ${RPM_BUILD_ROOT}/usr/bin
+mv pigz.1 ${RPM_BUILD_ROOT}/usr/man/man1
+
+%files
+%defattr(-,root,root)
+/usr/bin/pigz
+/usr/bin/unpigz
+%doc
+/usr/man/man1/pigz.1
diff --git a/pigz/yarn.c b/pigz/yarn.c
new file mode 100644
index 0000000..74db3fb
--- /dev/null
+++ b/pigz/yarn.c
@@ -0,0 +1,389 @@
+/* yarn.c -- generic thread operations implemented using pthread functions
+ * Copyright (C) 2008, 2012 Mark Adler
+ * Version 1.3  13 Jan 2012  Mark Adler
+ * For conditions of distribution and use, see copyright notice in yarn.h
+ */
+
+/* Basic thread operations implemented using the POSIX pthread library.  All
+   pthread references are isolated within this module to allow alternate
+   implementations with other thread libraries.  See yarn.h for the description
+   of these operations. */
+
+/* Version history:
+   1.0    19 Oct 2008  First version
+   1.1    26 Oct 2008  No need to set the stack size -- remove
+                       Add yarn_abort() function for clean-up on error exit
+   1.2    19 Dec 2011  (changes reversed in 1.3)
+   1.3    13 Jan 2012  Add large file #define for consistency with pigz.c
+                       Update thread portability #defines per IEEE 1003.1-2008
+                       Fix documentation in yarn.h for yarn_prefix
+ */
+
+/* for thread portability */
+#define _XOPEN_SOURCE 700
+#define _POSIX_C_SOURCE 200809L
+#define _THREAD_SAFE
+
+/* use large file functions if available */
+#define _FILE_OFFSET_BITS 64
+
+/* external libraries and entities referenced */
+#include <stdio.h>      /* fprintf(), stderr */
+#include <stdlib.h>     /* exit(), malloc(), free(), NULL */
+#include <pthread.h>    /* pthread_t, pthread_create(), pthread_join(), */
+#include <signal.h>     /* sigaction, SIGUSR1 */
+    /* pthread_attr_t, pthread_attr_init(), pthread_attr_destroy(),
+       PTHREAD_CREATE_JOINABLE, pthread_attr_setdetachstate(),
+       pthread_self(), pthread_equal(),
+       pthread_mutex_t, PTHREAD_MUTEX_INITIALIZER, pthread_mutex_init(),
+       pthread_mutex_lock(), pthread_mutex_unlock(), pthread_mutex_destroy(),
+       pthread_cond_t, PTHREAD_COND_INITIALIZER, pthread_cond_init(),
+       pthread_cond_broadcast(), pthread_cond_wait(), pthread_cond_destroy() */
+#include <errno.h>      /* ENOMEM, EAGAIN, EINVAL */
+#include <string.h>     /* memset */
+
+/* interface definition */
+#include "yarn.h"
+
+/* constants */
+#define local static            /* for non-exported functions and globals */
+
+/* error handling external globals, resettable by application */
+char *yarn_prefix = "yarn";
+void (*yarn_abort)(int) = NULL;
+
+void thread_exit_handler(int sig)
+{ 
+    printf("this signal is %d \n", sig);
+    pthread_exit(0);
+}
+
+/* immediately exit -- use for errors that shouldn't ever happen */
+local void fail(int err)
+{
+    fprintf(stderr, "%s: %s (%d) -- aborting\n", yarn_prefix,
+            err == ENOMEM ? "out of memory" : "internal pthread error", err);
+    if (yarn_abort != NULL)
+        yarn_abort(err);
+    exit(err == ENOMEM || err == EAGAIN ? err : EINVAL);
+}
+
+/* memory handling routines provided by user -- if none are provided, malloc()
+   and free() are used, which are therefore assumed to be thread-safe */
+typedef void *(*malloc_t)(size_t);
+typedef void (*free_t)(void *);
+local malloc_t my_malloc_f = malloc;
+local free_t my_free = free;
+
+/* use user-supplied allocation routines instead of malloc() and free() */
+void yarn_mem(malloc_t lease, free_t vacate)
+{
+    my_malloc_f = lease;
+    my_free = vacate;
+}
+
+/* memory allocation that cannot fail (from the point of view of the caller) */
+local void *my_malloc(size_t size)
+{
+    void *block;
+
+    if ((block = my_malloc_f(size)) == NULL)
+        fail(ENOMEM);
+    return block;
+}
+
+/* -- lock functions -- */
+
+struct lock_s {
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;
+    long value;
+};
+
+lock *new_lock(long initial)
+{
+    int ret;
+    lock *bolt;
+
+    bolt = my_malloc(sizeof(struct lock_s));
+    if ((ret = pthread_mutex_init(&(bolt->mutex), NULL)) ||
+        (ret = pthread_cond_init(&(bolt->cond), NULL)))
+        fail(ret);
+    bolt->value = initial;
+    return bolt;
+}
+
+void possess(lock *bolt)
+{
+    int ret;
+
+    if ((ret = pthread_mutex_lock(&(bolt->mutex))) != 0)
+        fail(ret);
+}
+
+void release(lock *bolt)
+{
+    int ret;
+
+    if ((ret = pthread_mutex_unlock(&(bolt->mutex))) != 0)
+        fail(ret);
+}
+
+void twist(lock *bolt, enum twist_op op, long val)
+{
+    int ret;
+
+    if (op == TO)
+        bolt->value = val;
+    else if (op == BY)
+        bolt->value += val;
+    if ((ret = pthread_cond_broadcast(&(bolt->cond))) ||
+        (ret = pthread_mutex_unlock(&(bolt->mutex))))
+        fail(ret);
+}
+
+#define until(a) while(!(a))
+
+void wait_for(lock *bolt, enum wait_op op, long val)
+{
+    int ret;
+
+    switch (op) {
+    case TO_BE:
+        until (bolt->value == val)
+            if ((ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex))) != 0)
+                fail(ret);
+        break;
+    case NOT_TO_BE:
+        until (bolt->value != val)
+            if ((ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex))) != 0)
+                fail(ret);
+        break;
+    case TO_BE_MORE_THAN:
+        until (bolt->value > val)
+            if ((ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex))) != 0)
+                fail(ret);
+        break;
+    case TO_BE_LESS_THAN:
+        until (bolt->value < val)
+            if ((ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex))) != 0)
+                fail(ret);
+    }
+}
+
+long peek_lock(lock *bolt)
+{
+    return bolt->value;
+}
+
+void free_lock(lock *bolt)
+{
+    int ret;
+    if ((ret = pthread_cond_destroy(&(bolt->cond))) ||
+        (ret = pthread_mutex_destroy(&(bolt->mutex))))
+        fail(ret);
+    my_free(bolt);
+}
+
+/* -- thread functions (uses lock functions above) -- */
+
+struct thread_s {
+    pthread_t id;
+    int done;                   /* true if this thread has exited */
+    thread *next;               /* for list of all launched threads */
+};
+
+/* list of threads launched but not joined, count of threads exited but not
+   joined (incremented by ignition() just before exiting) */
+local lock threads_lock = {
+    PTHREAD_MUTEX_INITIALIZER,
+    PTHREAD_COND_INITIALIZER,
+    0                           /* number of threads exited but not joined */
+};
+local thread *threads = NULL;       /* list of extant threads */
+
+/* structure in which to pass the probe and its payload to ignition() */
+struct capsule {
+    void (*probe)(void *);
+    void *payload;
+};
+
+/* mark the calling thread as done and alert join_all() */
+local void reenter(void *dummy)
+{
+    thread *match, **prior;
+    pthread_t me;
+
+    (void)dummy;
+
+    /* find this thread in the threads list by matching the thread id */
+    me = pthread_self();
+    possess(&(threads_lock));
+    prior = &(threads);
+    while ((match = *prior) != NULL) {
+        if (pthread_equal(match->id, me))
+            break;
+        prior = &(match->next);
+    }
+    if (match == NULL)
+        fail(EINVAL);
+
+    /* mark this thread as done and move it to the head of the list */
+    match->done = 1;
+    if (threads != match) {
+        *prior = match->next;
+        match->next = threads;
+        threads = match;
+    }
+
+    /* update the count of threads to be joined and alert join_all() */
+    twist(&(threads_lock), BY, +1);
+}
+
+/* all threads go through this routine so that just before the thread exits,
+   it marks itself as done in the threads list and alerts join_all() so that
+   the thread resources can be released -- use cleanup stack so that the
+   marking occurs even if the thread is cancelled */
+local void *ignition(void *arg)
+{
+    struct capsule *capsule = arg;
+
+    /* run reenter() before leaving */
+    pthread_cleanup_push(reenter, NULL);
+
+    /* execute the requested function with argument */
+    capsule->probe(capsule->payload);
+    my_free(capsule);
+
+    /* mark this thread as done and let join_all() know */
+    pthread_cleanup_pop(1);
+
+    /* exit thread */
+    return NULL;
+}
+
+/* not all POSIX implementations create threads as joinable by default, so that
+   is made explicit here */
+thread *launch(void (*probe)(void *), void *payload)
+{
+    int ret;
+    thread *th;
+    struct capsule *capsule;
+    pthread_attr_t attr;
+    struct sigaction actions;
+
+    memset(&actions, 0, sizeof(actions)); 
+    sigemptyset(&actions.sa_mask);
+    actions.sa_flags = 0; 
+    actions.sa_handler = thread_exit_handler;
+    ret = sigaction(SIGUSR1,&actions,NULL);
+
+    /* construct the requested call and argument for the ignition() routine
+       (allocated instead of automatic so that we're sure this will still be
+       there when ignition() actually starts up -- ignition() will free this
+       allocation) */
+    capsule = my_malloc(sizeof(struct capsule));
+    capsule->probe = probe;
+    capsule->payload = payload;
+
+    /* assure this thread is in the list before join_all() or ignition() looks
+       for it */
+    possess(&(threads_lock));
+
+    /* create the thread and call ignition() from that thread */
+    th = my_malloc(sizeof(struct thread_s));
+    if ((ret = pthread_attr_init(&attr)) ||
+        (ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE)) ||
+        (ret = pthread_create(&(th->id), &attr, ignition, capsule)) ||
+        (ret = pthread_attr_destroy(&attr)))
+        fail(ret);
+
+    /* put the thread in the threads list for join_all() */
+    th->done = 0;
+    th->next = threads;
+    threads = th;
+    release(&(threads_lock));
+    return th;
+}
+
+void join(thread *ally)
+{
+    int ret;
+    thread *match, **prior;
+
+    /* wait for thread to exit and return its resources */
+    if ((ret = pthread_join(ally->id, NULL)) != 0)
+        fail(ret);
+
+    /* find the thread in the threads list */
+    possess(&(threads_lock));
+    prior = &(threads);
+    while ((match = *prior) != NULL) {
+        if (match == ally)
+            break;
+        prior = &(match->next);
+    }
+    if (match == NULL)
+        fail(EINVAL);
+
+    /* remove thread from list and update exited count, free thread */
+    if (match->done)
+        threads_lock.value--;
+    *prior = match->next;
+    release(&(threads_lock));
+    my_free(ally);
+}
+
+/* This implementation of join_all() only attempts to join threads that have
+   announced that they have exited (see ignition()).  When there are many
+   threads, this is faster than waiting for some random thread to exit while a
+   bunch of other threads have already exited. */
+int join_all(void)
+{
+    int ret, count;
+    thread *match, **prior;
+
+    /* grab the threads list and initialize the joined count */
+    count = 0;
+    possess(&(threads_lock));
+
+    /* do until threads list is empty */
+    while (threads != NULL) {
+        /* wait until at least one thread has reentered */
+        wait_for(&(threads_lock), NOT_TO_BE, 0);
+
+        /* find the first thread marked done (should be at or near the top) */
+        prior = &(threads);
+        while ((match = *prior) != NULL) {
+            if (match->done)
+                break;
+            prior = &(match->next);
+        }
+        if (match == NULL)
+            fail(EINVAL);
+
+        /* join the thread (will be almost immediate), remove from the threads
+           list, update the reenter count, and free the thread */
+        if ((ret = pthread_join(match->id, NULL)) != 0)
+            fail(ret);
+        threads_lock.value--;
+        *prior = match->next;
+        my_free(match);
+        count++;
+    }
+
+    /* let go of the threads list and return the number of threads joined */
+    release(&(threads_lock));
+    return count;
+}
+
+/* cancel and join the thread -- the thread will cancel when it gets to a file
+   operation, a sleep or pause, or a condition wait */
+void destruct(thread *off_course)
+{
+    int ret;
+
+    if ((ret = pthread_kill(off_course->id, SIGUSR1)) != 0)
+        fail(ret);
+    join(off_course);
+}
diff --git a/pigz/yarn.h b/pigz/yarn.h
new file mode 100644
index 0000000..7e3f914
--- /dev/null
+++ b/pigz/yarn.h
@@ -0,0 +1,134 @@
+/* yarn.h -- generic interface for thread operations
+ * Copyright (C) 2008, 2011 Mark Adler
+ * Version 1.3  13 Jan 2012  Mark Adler
+ */
+
+/*
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the author be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  Mark Adler
+  madler@alumni.caltech.edu
+ */
+
+/* Basic thread operations
+
+   This interface isolates the local operating system implementation of threads
+   from the application in order to facilitate platform independent use of
+   threads.  All of the implementation details are deliberately hidden.
+
+   Assuming adequate system resources and proper use, none of these functions
+   can fail.  As a result, any errors encountered will cause an exit() to be
+   executed.
+
+   These functions allow the simple launching and joining of threads, and the
+   locking of objects and synchronization of changes of objects.  The latter is
+   implemented with a single lock type that contains an integer value.  The
+   value can be ignored for simple exclusive access to an object, or the value
+   can be used to signal and wait for changes to an object.
+
+   -- Arguments --
+
+   thread *thread;          identifier for launched thread, used by join
+   void probe(void *);      pointer to function "probe", run when thread starts
+   void *payload;           single argument passed to the probe function
+   lock *lock;              a lock with a value -- used for exclusive access to
+                            an object and to synchronize threads waiting for
+                            changes to an object
+   long val;                value to set lock, increment lock, or wait for
+   int n;                   number of threads joined
+
+   -- Thread functions --
+
+   thread = launch(probe, payload) - launch a thread -- exit via probe() return
+   join(thread) - join a thread and by joining end it, waiting for the thread
+        to exit if it hasn't already -- will free the resources allocated by
+        launch() (don't try to join the same thread more than once)
+   n = join_all() - join all threads launched by launch() that are not joined
+        yet and free the resources allocated by the launches, usually to clean
+        up when the thread processing is done -- join_all() returns an int with
+        the count of the number of threads joined (join_all() should only be
+        called from the main thread, and should only be called after any calls
+        of join() have completed)
+   destruct(thread) - terminate the thread in mid-execution and join it
+        (depending on the implementation, the termination may not be immediate,
+        but may wait for the thread to execute certain thread or file i/o
+        operations)
+
+   -- Lock functions --
+
+   lock = new_lock(val) - create a new lock with initial value val (lock is
+        created in the released state)
+   possess(lock) - acquire exclusive possession of a lock, waiting if necessary
+   twist(lock, [TO | BY], val) - set lock to or increment lock by val, signal
+        all threads waiting on this lock and then release the lock -- must
+        possess the lock before calling (twist releases, so don't do a
+        release() after a twist() on the same lock)
+   wait_for(lock, [TO_BE | NOT_TO_BE | TO_BE_MORE_THAN | TO_BE_LESS_THAN], val)
+        - wait on lock value to be, not to be, be greater than, or be less than
+        val -- must possess the lock before calling, will possess the lock on
+        return but the lock is released while waiting to permit other threads
+        to use twist() to change the value and signal the change (so make sure
+        that the object is in a usable state when waiting)
+   release(lock) - release a possessed lock (do not try to release a lock that
+        the current thread does not possess)
+   val = peek_lock(lock) - return the value of the lock (assumes that lock is
+        already possessed, no possess or release is done by peek_lock())
+   free_lock(lock) - free the resources allocated by new_lock() (application
+        must assure that the lock is released before calling free_lock())
+
+   -- Memory allocation ---
+
+   yarn_mem(better_malloc, better_free) - set the memory allocation and free
+        routines for use by the yarn routines where the supplied routines have
+        the same interface and operation as malloc() and free(), and may be
+        provided in order to supply thread-safe memory allocation routines or
+        for any other reason -- by default malloc() and free() will be used
+
+   -- Error control --
+
+   yarn_prefix - a char pointer to a string that will be the prefix for any
+        error messages that these routines generate before exiting -- if not
+        changed by the application, "yarn" will be used
+   yarn_abort - an external function that will be executed when there is an
+        internal yarn error, due to out of memory or misuse -- this function
+        may exit to abort the application, or if it returns, the yarn error
+        handler will exit (set to NULL by default for no action)
+ */
+
+extern char *yarn_prefix;
+extern void (*yarn_abort)(int);
+
+void yarn_mem(void *(*)(size_t), void (*)(void *));
+
+typedef struct thread_s thread;
+thread *launch(void (*)(void *), void *);
+void join(thread *);
+int join_all(void);
+void destruct(thread *);
+
+typedef struct lock_s lock;
+lock *new_lock(long);
+void possess(lock *);
+void release(lock *);
+enum twist_op { TO, BY };
+void twist(lock *, enum twist_op, long);
+enum wait_op {
+    TO_BE, /* or */ NOT_TO_BE, /* that is the question */
+    TO_BE_MORE_THAN, TO_BE_LESS_THAN };
+void wait_for(lock *, enum wait_op, long);
+long peek_lock(lock *);
+void free_lock(lock *);
diff --git a/prebuilt/Android.bp b/prebuilt/Android.bp
new file mode 100644
index 0000000..fdbb3d2
--- /dev/null
+++ b/prebuilt/Android.bp
@@ -0,0 +1,5 @@
+soong_namespace {
+    imports: [
+		"external/lptools",
+	],
+}
diff --git a/prebuilt/Android.mk b/prebuilt/Android.mk
new file mode 100755
index 0000000..0252173
--- /dev/null
+++ b/prebuilt/Android.mk
@@ -0,0 +1,649 @@
+LOCAL_PATH := $(call my-dir)
+
+RELINK := $(LOCAL_PATH)/relink.sh
+
+#dummy file to trigger required modules
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := teamwin
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+
+# Manage list
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/dump_image
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/flash_image
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/erase_image
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/bu
+
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/sh
+
+LOCAL_POST_INSTALL_CMD += $(hide) if [ -e "$(TARGET_RECOVERY_ROOT_OUT)/system/bin/egrep" ]; then \
+							rm $(TARGET_RECOVERY_ROOT_OUT)/system/bin/egrep; fi; ln -s $(TARGET_RECOVERY_ROOT_OUT)/system/bin/grep $(TARGET_RECOVERY_ROOT_OUT)/system/bin/egrep; \
+							if [ -e "$(TARGET_RECOVERY_ROOT_OUT)/system/bin/fgrep" ]; then \
+							rm $(TARGET_RECOVERY_ROOT_OUT)/system/bin/fgrep; fi; ln -s $(TARGET_RECOVERY_ROOT_OUT)/system/bin/grep $(TARGET_RECOVERY_ROOT_OUT)/system/bin/fgrep;
+
+ifneq ($(wildcard external/zip/Android.mk),)
+	RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_OPTIONAL_EXECUTABLES)/zip
+endif
+ifneq ($(wildcard external/unzip/Android.mk),)
+	RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_OPTIONAL_EXECUTABLES)/unzip
+endif
+ifneq ($(wildcard system/libziparchive/Android.bp),)
+	RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/unzip
+endif
+ifneq ($(wildcard external/one-true-awk/Android.bp),)
+	RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/awk
+endif
+
+ifneq ($(filter $(PRODUCT_USE_DYNAMIC_PARTITIONS) $(TW_INCLUDE_FASTBOOTD),true),)
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/fastbootd
+endif
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/bc
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/e2fsck
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/e2fsdroid
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/mke2fs
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/tune2fs
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/resize2fs
+
+#RELINK_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/twrpmtp
+ifneq ($(filter $(PRODUCT_USE_DYNAMIC_PARTITIONS) $(TW_INCLUDE_FASTBOOTD),true),)
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/android.hardware.fastboot@1.0.so
+endif
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/ld-android.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libandroid_runtime_lazy.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libc.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libdl.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libm.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libfs_mgr.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libfscrypt.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libgsi.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libkeyutils.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/liblogwrap.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/liblp.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libprocessgroup.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libprocessgroup_setup.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libadbd.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libadbd_services.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libcap.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libminijail.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libunwindstack.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libbinderthreadstate.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libmdnssd.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.boot@1.0.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.boot@1.1.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.boot@1.2.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libasyncio.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libinit.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/bootstrap/libdl_android.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libprotobuf-cpp-lite.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libbinder.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libbinder_ndk.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libchrome.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libevent.so
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/keystore
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/keystore_auth
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/keystore_cli_v2
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/hwservicemanager
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/servicemanager
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/vold_prepare_subdirs
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_VENDOR_EXECUTABLES)/vndservicemanager
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/toybox
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_VENDOR_EXECUTABLES)/hw/android.hardware.health@2.0-service
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_VENDOR_EXECUTABLES)/hw/android.hardware.health@2.1-service
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/charger
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/ueventd
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/watchdogd
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/minadbd
+
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libcutils.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libcrecovery.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libusbhost.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libutils.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_com_err.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_e2p.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2fs.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_profile.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_uuid.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_misc.so
+ifneq ($(wildcard external/e2fsprogs/lib/quota/Android.mk),)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_quota.so
+endif
+ifneq ($(wildcard external/e2fsprogs/lib/ext2fs/Android.*),)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2fs.so
+endif
+ifneq ($(wildcard external/e2fsprogs/lib/blkid/Android.*),)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_blkid.so
+endif
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libpng.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/liblog.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libstdc++.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libminuitwrp.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libminadbd.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libz.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libmtdutils.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libtar.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libtwadbbu.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libtwrpdigest.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libutil-linux.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libblkid.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libmmcutils.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libbmlutils.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libflashutils.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libfusesideload.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libbootloader_message.so
+
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libcrypto.so \
+$(if $(WITH_CRYPTO_UTILS),$(TARGET_OUT_SHARED_LIBRARIES)/libcrypto_utils.so)
+
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/lib64/libbacktrace.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libunwind.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libbase.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libc++.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libnetd_client.so
+
+RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/toolbox
+
+
+ifneq ($(TW_OEM_BUILD),true)
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/twrp
+else
+    TW_EXCLUDE_MTP := true
+endif
+
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libtwrpmtp-ffs.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext4_utils.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libaosprecovery.so
+ifneq ($(TW_INCLUDE_JPEG),)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libjpeg.so
+endif
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libselinux.so
+
+ifneq ($(TW_NO_EXFAT), true)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libexfat_twrp.so
+else
+    TW_NO_EXFAT_FUSE := true
+endif
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libhardware.so
+ifeq ($(TW_INCLUDE_CRYPTO), true)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libcryptfsfde.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libdexfile_support.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libf2fs_sparseblock.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.authsecret@1.0.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.oemlock@1.0.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_VENDOR_SHARED_LIBRARIES)/libnos_transport.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_VENDOR_SHARED_LIBRARIES)/libnos_datagram.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.keymaster@3.0.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libgpt_twrp.so
+    ifeq ($(TARGET_HW_DISK_ENCRYPTION),true)
+        ifeq ($(TARGET_CRYPTFS_HW_PATH),)
+            RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_VENDOR_SHARED_LIBRARIES)/libcryptfs_hw.so
+        else
+            RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libcryptfs_hw.so
+        endif
+    endif
+    # FBE files
+    ifeq ($(TW_INCLUDE_CRYPTO_FBE), true)
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libtwrpfscrypt.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.security.maintenance-ndk_platform.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.system.keystore2-V1-ndk_platform.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.security.apc-ndk_platform.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.security.authorization-ndk_platform.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeymint_support.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.security.keymint-V1-ndk_platform.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.frameworks.stats-V1-ndk_platform.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.security.secureclock-V1-ndk_platform.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libgatekeeper.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libgatekeeper_aidl.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeymaster_messages.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libbinder.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libprotobuf-cpp-lite.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libsoftkeymasterdevice.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.gatekeeper@1.0.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeystore2_aaid.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeystore2_apc_compat.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeystore2_crypto.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkm_compat_service.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeystore2_vintf_cpp.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.security.compat-ndk_platform.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkm_compat.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeymint.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/lib_android_keymaster_keymint_utils.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libcppbor_external.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libcppbor.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libpuresoftkeymasterdevice.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libcppcose_rkp.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.security.sharedsecret-V1-ndk_platform.so
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/avbctl
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/keystore
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/keystore2
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/keystore_cli
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.system.wifi.keystore@1.0.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.vibrator@1.0.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.vibrator@1.1.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.vibrator@1.2.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.vibrator-V1-ndk_platform.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.vibrator-V1-cpp.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libstatslog.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libsoft_attestation_cert.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libxml2.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libdiskconfig.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libhardware_legacy.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libincfs.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.health.storage@1.0.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.health.storage@1.0.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.health.storage-V1-ndk_platform.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libhardware_legacy.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.system.suspend@1.0.so
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/fscryptpolicyget
+
+        ifneq ($(wildcard system/keymaster/keymaster_stl.cpp),)
+            RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeymaster_portable.so
+            RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeymaster_staging.so
+        endif
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libwifikeystorehal.so
+        ifneq ($(wildcard hardware/interfaces/weaver/Android.bp),)
+            RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.weaver@1.0.so
+        endif
+        ifneq ($(wildcard hardware/interfaces/weaver/1.0/Android.bp),)
+            RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.weaver@1.0.so
+        endif
+        ifneq ($(wildcard hardware/interfaces/confirmationui/1.0/Android.bp),)
+            RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.confirmationui@1.0.so
+        endif
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libsoftkeymaster.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.keymaster@4.0.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hardware.keymaster@4.1.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeymaster4support.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeymaster4_1support.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libutilscallstack.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libdexfile.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libservices.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeymaster_portable.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libhwbinder.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeystore-attestation-application-id.so
+         # lshal can be useful for seeing if you have things like the keymaster working properly, but it isn't needed for TWRP to work
+         #RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/lshal
+         #RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/liblshal.so
+         #RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libssl.so
+         #RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libhidl-gen-hash.so
+    endif
+endif
+ifeq ($(AB_OTA_UPDATER), true)
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/update_engine_sideload
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_VENDOR_EXECUTABLES)/hw/android.hardware.boot@1.0-service
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_VENDOR_EXECUTABLES)/hw/android.hardware.boot@1.1-service
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_VENDOR_EXECUTABLES)/hw/android.hardware.boot@1.2-service
+endif
+ifeq ($(PRODUCT_USE_DYNAMIC_PARTITIONS),true)
+    ifeq ($(TW_EXCLUDE_LPDUMP),)
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/bootctl
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/lpdump
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/lpdumpd
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/liblpdump.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/liblpdump_interface-V1-cpp.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libprotobuf-cpp-full.so
+    endif
+    ifeq ($(TW_EXCLUDE_LPTOOLS),)
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/lptools
+    endif
+endif
+
+ifneq ($(wildcard system/core/libsparse/Android.*),)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libsparse.so
+endif
+ifneq ($(TW_EXCLUDE_ENCRYPTED_BACKUPS),)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libopenaes.so
+endif
+ifeq ($(TARGET_USERIMAGES_USE_F2FS), true)
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/make_f2fs
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/fsck.f2fs
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/sload_f2fs
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/liblz4.so
+endif
+ifneq ($(wildcard system/core/reboot/Android.*),)
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_RECOVERY_ROOT_OUT)/system/bin/reboot
+endif
+ifneq ($(TW_DISABLE_TTF), true)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libft2.so
+endif
+ifneq ($(TW_RECOVERY_ADDITIONAL_RELINK_BINARY_FILES),)
+    RECOVERY_BINARY_SOURCE_FILES += $(TW_RECOVERY_ADDITIONAL_RELINK_BINARY_FILES)
+endif
+ifneq ($(TW_RECOVERY_ADDITIONAL_RELINK_LIBRARY_FILES),)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TW_RECOVERY_ADDITIONAL_RELINK_LIBRARY_FILES)
+endif
+ifneq ($(TW_RECOVERY_ADDITIONAL_RELINK_VENDOR_HW_BINARY_FILES),)
+    RECOVERY_VENDOR_HW_BINARY_FILES += $(TW_RECOVERY_ADDITIONAL_RELINK_VENDOR_HW_BINARY_FILES)
+endif
+ifneq ($(wildcard external/pcre/Android.mk),)
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libpcre.so
+endif
+ifeq ($(TW_INCLUDE_NTFS_3G),true)
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/mount.ntfs
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/fsck.ntfs
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/mkfs.ntfs
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libntfs-3g.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libfuse-lite.so
+else
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/ntfs-3g
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/ntfsfix
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/mkntfs
+endif
+ifeq ($(BOARD_HAS_NO_REAL_SDCARD),)
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/sgdisk
+endif
+ifneq ($(TW_OZIP_DECRYPT_KEY),)
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/ozip_decrypt
+endif
+ifeq ($(TWRP_INCLUDE_LOGCAT), true)
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/logcat
+    ifeq ($(TARGET_USES_LOGD), true)
+        RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/logd
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libsysutils.so
+        RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libnl.so
+    endif
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libpcrecpp.so
+	RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/liblogcat.so
+	RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libcap.so
+endif
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libvndksupport.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libhidlbase.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libhidltransport.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.frameworks.stats@1.0.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libziparchive.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_blkid.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_quota.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libhidl-gen-utils.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libvintf.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libtinyxml2.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/android.hidl.token@1.0.so
+ifneq ($(wildcard system/core/libkeyutils/Android.bp),)
+	RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libkeyutils.so
+endif
+ifeq ($(TARGET_ARCH), arm64)
+	RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libclang_rt.ubsan_standalone-aarch64-android.so
+endif
+ifeq ($(TARGET_ARCH), arm)
+	RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libclang_rt.ubsan_standalone-arm-android.so
+endif
+ifeq ($(TARGET_ARCH), x86_64)
+	RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libclang_rt.ubsan_standalone-x86_64-android.so
+endif
+ifeq ($(TARGET_ARCH), x86)
+	RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libclang_rt.ubsan_standalone-i686-android.so
+endif
+ifeq ($(TARGET_ARCH), mips)
+	RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libclang_rt.ubsan_standalone-mips-android.so
+endif
+ifeq ($(TARGET_ARCH), mips64)
+	RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libclang_rt.ubsan_standalone-mips64-android.so
+endif
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/liblogwrap.so
+RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_misc.so
+
+ifneq ($(TW_EXCLUDE_NANO), true)
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_SYSTEM_EXT_EXECUTABLES)/nano
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SYSTEM_EXT_SHARED_LIBRARIES)/libncurses.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_VENDOR_SHARED_LIBRARIES)/libssh.so
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libssl.so
+endif
+
+ifneq ($(TW_EXCLUDE_BASH), true)
+    RECOVERY_BINARY_SOURCE_FILES += $(TARGET_OUT_SYSTEM_EXT_EXECUTABLES)/bash
+    RECOVERY_LIBRARY_SOURCE_FILES += $(TARGET_OUT_SYSTEM_EXT_SHARED_LIBRARIES)/libncurses.so
+endif
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := relink_libraries
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+ifneq ($(TARGET_ARCH), arm64)
+    ifneq ($(TARGET_ARCH), x86_64)
+        LOCAL_POST_INSTALL_CMD += $(RELINK) $(TARGET_RECOVERY_ROOT_OUT)/system/lib $(RECOVERY_LIBRARY_SOURCE_FILES)
+    else
+        LOCAL_POST_INSTALL_CMD += $(RELINK) $(TARGET_RECOVERY_ROOT_OUT)/system/lib64 $(RECOVERY_LIBRARY_SOURCE_FILES)
+    endif
+else
+LOCAL_POST_INSTALL_CMD += $(RELINK) $(TARGET_RECOVERY_ROOT_OUT)/system/lib64 $(RECOVERY_LIBRARY_SOURCE_FILES)
+endif
+TARGET_LIBRARY_RELINK_FILES := $(notdir $(RECOVERY_LIBRARY_SOURCE_FILES))
+TARGET_BASE_LIBRARY_RELINK_MODULES := $(basename $(TARGET_LIBRARY_RELINK_FILES))
+TARGET_RELINK_LIBRARY_MODULES :=  $(filter-out libdexfile, $(TARGET_BASE_LIBRARY_RELINK_MODULES))
+LOCAL_REQUIRED_MODULES += $(TARGET_RELINK_LIBRARY_MODULES)
+include $(BUILD_PHONY_PACKAGE)
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := relink_binaries
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+LOCAL_POST_INSTALL_CMD += $(RELINK) $(TARGET_RECOVERY_ROOT_OUT)/system/bin $(RECOVERY_BINARY_SOURCE_FILES)
+TARGET_BINARY_RELINK_FILES := $(filter-out bu, $(notdir $(RECOVERY_BINARY_SOURCE_FILES)))
+LOCAL_REQUIRED_MODULES += $(TARGET_BINARY_RELINK_FILES)
+include $(BUILD_PHONY_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := relink_vendor_hw_binaries
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/
+LOCAL_POST_INSTALL_CMD += $(RELINK) $(TARGET_RECOVERY_ROOT_OUT)/vendor/bin/hw $(RECOVERY_VENDOR_HW_BINARY_FILES)
+TARGET_VENDOR_BINARY_RELINK_FILES := $(notdir $(RECOVERY_VENDOR_HW_BINARY_FILES))
+LOCAL_REQUIRED_MODULES += $(TARGET_VENDOR_BINARY_RELINK_FILES)
+$(warning vendor_hw: $(LOCAL_POST_INSTALL_CMD))
+include $(BUILD_PHONY_PACKAGE)
+
+#build out TWRP ramdisk
+include $(CLEAR_VARS)
+LOCAL_MODULE := twrp_ramdisk
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)
+LOCAL_POST_INSTALL_CMD += \
+    mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/sbin; ln -sf /system/bin/sh $(TARGET_RECOVERY_ROOT_OUT)/sbin/sh && \
+    mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/system/etc/selinux/ && \
+    mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/vendor/etc/selinux/ && \
+    cp $(TARGET_OUT_ETC)/selinux/plat_service_contexts $(TARGET_RECOVERY_ROOT_OUT)/system/etc/selinux/plat_service_contexts && \
+    cp $(TARGET_OUT_ETC)/selinux/plat_hwservice_contexts $(TARGET_RECOVERY_ROOT_OUT)/system/etc/selinux/plat_hwservice_contexts && \
+    cp $(TARGET_OUT_VENDOR_ETC)/selinux/vndservice_contexts $(TARGET_RECOVERY_ROOT_OUT)/vendor/etc/selinux/vndservice_contexts && \
+    cp $(TARGET_OUT_VENDOR_ETC)/selinux/vendor_hwservice_contexts $(TARGET_RECOVERY_ROOT_OUT)/vendor/etc/selinux/vendor_hwservice_contexts && \
+    cp $(TARGET_OUT_ETC)/selinux/plat_keystore2_key_contexts $(TARGET_RECOVERY_ROOT_OUT)/system/etc/selinux/plat_keystore2_key_contexts
+    ifeq ($(TARGET_USES_MKE2FS), true)
+        LOCAL_POST_INSTALL_CMD += \
+            && cp $(TARGET_OUT_ETC)/mke2fs.conf $(TARGET_RECOVERY_ROOT_OUT)/system/etc/mke2fs.conf
+    endif
+LOCAL_REQUIRED_MODULES += init_second_stage.recovery \
+    reboot.recovery \
+    plat_service_contexts \
+    plat_hwservice_contexts \
+    plat_hardware_contexts \
+    vndservice_contexts \
+    plat_keystore2_key_contexts \
+    vendor_hwservice_contexts
+
+include $(BUILD_PHONY_PACKAGE)
+
+# copy license file for OpenAES
+ifneq ($(TW_EXCLUDE_ENCRYPTED_BACKUPS),)
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := openaes_license
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := EXECUTABLES
+    LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/license/openaes
+    LOCAL_SRC_FILES := ../openaes/LICENSE
+    include $(BUILD_PREBUILT)
+endif
+
+ifeq ($(TW_INCLUDE_DUMLOCK), true)
+    #htcdumlock for /system for dumlock
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := htcdumlocksys
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := EXECUTABLES
+    LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)$(TWHTCD_PATH)
+    LOCAL_SRC_FILES := $(LOCAL_MODULE)
+    include $(BUILD_PREBUILT)
+
+    #flash_image for /system for dumlock
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := flash_imagesys
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := EXECUTABLES
+    LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)$(TWHTCD_PATH)
+    LOCAL_SRC_FILES := $(LOCAL_MODULE)
+    include $(BUILD_PREBUILT)
+
+    #dump_image for /system for dumlock
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := dump_imagesys
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := EXECUTABLES
+    LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)$(TWHTCD_PATH)
+    LOCAL_SRC_FILES := $(LOCAL_MODULE)
+    include $(BUILD_PREBUILT)
+
+    #libbmlutils for /system for dumlock
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := libbmlutils.so
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := EXECUTABLES
+    LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)$(TWHTCD_PATH)
+    LOCAL_SRC_FILES := $(LOCAL_MODULE)
+    include $(BUILD_PREBUILT)
+
+    #libflashutils for /system for dumlock
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := libflashutils.so
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := EXECUTABLES
+    LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)$(TWHTCD_PATH)
+    LOCAL_SRC_FILES := $(LOCAL_MODULE)
+    include $(BUILD_PREBUILT)
+
+    #libmmcutils for /system for dumlock
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := libmmcutils.so
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := EXECUTABLES
+    LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)$(TWHTCD_PATH)
+    LOCAL_SRC_FILES := $(LOCAL_MODULE)
+    include $(BUILD_PREBUILT)
+
+    #libmtdutils for /system for dumlock
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := libmtdutils.so
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := EXECUTABLES
+    LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)$(TWHTCD_PATH)
+    LOCAL_SRC_FILES := $(LOCAL_MODULE)
+    include $(BUILD_PREBUILT)
+
+    #HTCDumlock.apk
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := HTCDumlock.apk
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := EXECUTABLES
+    LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)$(TWHTCD_PATH)
+    LOCAL_SRC_FILES := $(LOCAL_MODULE)
+    include $(BUILD_PREBUILT)
+endif
+
+ifeq ($(TW_USE_TOOLBOX), true)
+   include $(CLEAR_VARS)
+   LOCAL_MODULE := mkshrc_twrp
+   LOCAL_MODULE_STEM := mkshrc
+   LOCAL_MODULE_TAGS := optional
+   LOCAL_MODULE_CLASS := ETC
+   LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/etc
+   LOCAL_SRC_FILES := $(LOCAL_MODULE)
+   include $(BUILD_PREBUILT)
+endif
+
+#TWRP App "placeholder"
+include $(CLEAR_VARS)
+LOCAL_MODULE := me.twrp.twrpapp.apk
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+#TWRP App permissions for Android 9+
+include $(CLEAR_VARS)
+LOCAL_MODULE := privapp-permissions-twrpapp.xml
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+ifeq ($(TW_INCLUDE_CRYPTO), true)
+    ifneq ($(TW_CRYPTO_USE_SYSTEM_VOLD),)
+        # Prebuilt vdc_pie for pre-Pie SDK Platforms
+        include $(CLEAR_VARS)
+        LOCAL_MODULE := vdc_pie
+        LOCAL_MODULE_TAGS := optional
+        LOCAL_MODULE_CLASS := EXECUTABLES
+        LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+        LOCAL_SRC_FILES := vdc_pie-$(TARGET_ARCH)
+        include $(BUILD_PREBUILT)
+    endif
+endif
+
+ifneq (,$(filter $(TW_INCLUDE_REPACKTOOLS) $(TW_INCLUDE_RESETPROP) $(TW_INCLUDE_LIBRESETPROP), true))
+    ifeq ($(wildcard external/magisk-prebuilt/Android.mk),)
+        $(warning Magisk prebuilt tools not found!)
+        $(warning Please place https://github.com/TeamWin/external_magisk-prebuilt)
+        $(warning into external/magisk-prebuilt)
+        $(error magiskboot prebuilts not present; exiting)
+    endif
+endif
+
+# Include tzdata in TWRP to fix "__bionic_open_tzdata" log spam
+# Dummy file to apply post-install patch
+ifneq ($(TW_EXCLUDE_TZDATA), true)
+    include $(CLEAR_VARS)
+    LOCAL_MODULE := tzdata_twrp
+    LOCAL_MODULE_TAGS := optional
+    LOCAL_MODULE_CLASS := ETC
+    LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)/system/bin
+    LOCAL_REQUIRED_MODULES := tzdata
+
+    LOCAL_POST_INSTALL_CMD += \
+        mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/system/usr/share/zoneinfo; \
+        cp -f $(TARGET_OUT)/usr/share/zoneinfo/tzdata $(TARGET_RECOVERY_ROOT_OUT)/system/usr/share/zoneinfo/;
+    include $(BUILD_PHONY_PACKAGE)
+endif
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := nano_twrp
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)/system/bin
+LOCAL_REQUIRED_MODULES := nano libncurses
+LOCAL_POST_INSTALL_CMD += \
+    cp -rf $(TARGET_OUT_SYSTEM_EXT_ETC)/nano $(TARGET_RECOVERY_ROOT_OUT)/system/etc/; \
+    cp -rf external/libncurses/lib/terminfo $(TARGET_RECOVERY_ROOT_OUT)/system/etc/;
+include $(BUILD_PHONY_PACKAGE)
+
+ifneq ($(TW_EXCLUDE_BASH), true)
+	include $(CLEAR_VARS)
+	LOCAL_MODULE := bash_twrp
+	LOCAL_MODULE_TAGS := optional
+	LOCAL_MODULE_CLASS := ETC
+	LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)/system/bin
+	LOCAL_REQUIRED_MODULES := bash
+
+	LOCAL_POST_INSTALL_CMD += \
+		mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/system/etc/bash/; \
+		cp -rf external/bash/etc/* $(TARGET_RECOVERY_ROOT_OUT)/system/etc/bash/; \
+        sed -i 's/ro.lineage.device/ro.product.device/' $(TARGET_RECOVERY_ROOT_OUT)/system/etc/bash/bashrc; \
+        sed -i '/export TERM/d' $(TARGET_RECOVERY_ROOT_OUT)/system/etc/bash/bashrc; \
+        mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/sbin; \
+        ln -sf /system/bin/bash $(TARGET_RECOVERY_ROOT_OUT)/sbin/bash;
+	include $(BUILD_PHONY_PACKAGE)
+endif
+
diff --git a/prebuilt/HTCDumlock.apk b/prebuilt/HTCDumlock.apk
new file mode 100644
index 0000000..8298197
--- /dev/null
+++ b/prebuilt/HTCDumlock.apk
Binary files differ
diff --git a/prebuilt/dump_imagesys b/prebuilt/dump_imagesys
new file mode 100644
index 0000000..e1bdb62
--- /dev/null
+++ b/prebuilt/dump_imagesys
Binary files differ
diff --git a/prebuilt/flash_imagesys b/prebuilt/flash_imagesys
new file mode 100644
index 0000000..a08d91d
--- /dev/null
+++ b/prebuilt/flash_imagesys
Binary files differ
diff --git a/prebuilt/htcdumlock b/prebuilt/htcdumlock
new file mode 100644
index 0000000..18068e5
--- /dev/null
+++ b/prebuilt/htcdumlock
Binary files differ
diff --git a/prebuilt/htcdumlocksys b/prebuilt/htcdumlocksys
new file mode 100644
index 0000000..c63725e
--- /dev/null
+++ b/prebuilt/htcdumlocksys
Binary files differ
diff --git a/prebuilt/libbmlutils.so b/prebuilt/libbmlutils.so
new file mode 100644
index 0000000..48a3e3b
--- /dev/null
+++ b/prebuilt/libbmlutils.so
Binary files differ
diff --git a/prebuilt/libflashutils.so b/prebuilt/libflashutils.so
new file mode 100644
index 0000000..91fcde0
--- /dev/null
+++ b/prebuilt/libflashutils.so
Binary files differ
diff --git a/prebuilt/libmmcutils.so b/prebuilt/libmmcutils.so
new file mode 100644
index 0000000..5124876
--- /dev/null
+++ b/prebuilt/libmmcutils.so
Binary files differ
diff --git a/prebuilt/libmtdutils.so b/prebuilt/libmtdutils.so
new file mode 100644
index 0000000..1cbc61e
--- /dev/null
+++ b/prebuilt/libmtdutils.so
Binary files differ
diff --git a/prebuilt/me.twrp.twrpapp.apk b/prebuilt/me.twrp.twrpapp.apk
new file mode 100644
index 0000000..cb19986
--- /dev/null
+++ b/prebuilt/me.twrp.twrpapp.apk
Binary files differ
diff --git a/prebuilt/mke2fs.conf b/prebuilt/mke2fs.conf
new file mode 100644
index 0000000..e231d7c
--- /dev/null
+++ b/prebuilt/mke2fs.conf
@@ -0,0 +1,17 @@
+[defaults]
+	base_features = sparse_super,filetype,resize_inode,dir_index,ext_attr
+	blocksize = 4096
+	inode_size = 256
+	inode_ratio = 16384
+
+[fs_types]
+	ext2 = {
+
+	}
+	ext3 = {
+		features = has_journal
+	}
+	ext4 = {
+		features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize
+		inode_size = 256
+	}
diff --git a/prebuilt/mkshrc_twrp b/prebuilt/mkshrc_twrp
new file mode 100755
index 0000000..c103d6c
--- /dev/null
+++ b/prebuilt/mkshrc_twrp
@@ -0,0 +1,21 @@
+# Copyright (c) 2010, 2012, 2013, 2014
+#	Thorsten Glaser <tg@mirbsd.org>
+# This file is provided under the same terms as mksh.
+#-
+# Minimal /system/etc/mkshrc for Android
+#
+# Support: https://launchpad.net/mksh
+
+: ${HOSTNAME:=$(getprop ro.product.device)}
+: ${HOSTNAME:=android}
+: ${TMPDIR:=/tmp}
+export HOSTNAME TMPDIR
+
+if (( USER_ID )); then PS1='$'; else PS1='#'; fi
+PS4='[$EPOCHREALTIME] '; PS1='${|
+	local e=$?
+
+	(( e )) && REPLY+="$e|"
+
+	return $e
+}$HOSTNAME:${PWD:-?} '"$PS1 "
diff --git a/prebuilt/parted b/prebuilt/parted
new file mode 100755
index 0000000..bb3d432
--- /dev/null
+++ b/prebuilt/parted
Binary files differ
diff --git a/prebuilt/privapp-permissions-twrpapp.xml b/prebuilt/privapp-permissions-twrpapp.xml
new file mode 100644
index 0000000..9b294c2
--- /dev/null
+++ b/prebuilt/privapp-permissions-twrpapp.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<permissions>
+    <privapp-permissions package="me.twrp.twrpapp">
+        <permission name="android.permission.BATTERY_STATS"/>
+        <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+        <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
+    </privapp-permissions>
+</permissions>
diff --git a/prebuilt/relink b/prebuilt/relink
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/prebuilt/relink
diff --git a/prebuilt/relink.sh b/prebuilt/relink.sh
new file mode 100755
index 0000000..5917114
--- /dev/null
+++ b/prebuilt/relink.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+process_file()
+{
+    dst=$1/$(basename $2)
+    src=$2
+
+    [[ -e $src ]] || return 0
+
+    if [ $dst == $src ]; then
+      cp -f -p $src $src.tmp
+      src=$2.tmp
+    fi
+
+    mkdir -p $1
+    cp $src $dst
+    if [[ -e $2.tmp ]]; then rm -f $2.tmp; fi
+}
+
+
+dest=$1
+shift 1
+for ARG in $*
+do
+    process_file $dest $ARG
+done
diff --git a/prebuilt/relink_init b/prebuilt/relink_init
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/prebuilt/relink_init
diff --git a/prebuilt/sgdisk_static b/prebuilt/sgdisk_static
new file mode 100755
index 0000000..eb8a129
--- /dev/null
+++ b/prebuilt/sgdisk_static
Binary files differ
diff --git a/prebuilt/teamwin b/prebuilt/teamwin
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/prebuilt/teamwin
@@ -0,0 +1 @@
+
diff --git a/prebuilt/twrp_fonts.py b/prebuilt/twrp_fonts.py
new file mode 100755
index 0000000..4392409
--- /dev/null
+++ b/prebuilt/twrp_fonts.py
@@ -0,0 +1,198 @@
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+import codecs,os,gzip,ctypes,ctypes.util,sys
+from struct import *
+from PIL import Image, ImageDraw, ImageFont
+
+# ====== Python script to convert TrueTypeFonts to TWRP's .dat format ======
+# This script was originally made by https://github.com/suky for his chinese version of TWRP
+# and then translated to English by feilplane at #twrp of irc.freenode.net.
+# However, it was not compatible with vanilla TWRP, so https://github.com/Tasssadar rewrote
+# most of it and it now has very little in common with the original script.
+
+class Reference():
+    def __init__(self, val):
+        self.__value = val
+
+    def get(self):
+        return self.__value
+
+    def set(self, val):
+        self.__value = val
+
+quiet = Reference(False)
+
+def log(text):
+    if not quiet.get():
+        sys.stdout.write(text)
+
+def write_data(f, width, height, offsets, data):
+    f.write(pack("<I", width))
+    f.write(pack("<I", height))
+    for off in offsets:
+        f.write(pack("<I", off))
+    f.write(data)
+
+if __name__ == "__main__":
+    fontsize = Reference(20)
+    out_fname = Reference("font.dat")
+    voffset = Reference(None)
+    padding = Reference(0)
+    font_fname = Reference(None)
+    preview = Reference(None)
+
+    arg_parser = [
+        ["-s", "--size=", fontsize, int],
+        ["-o", "--output=", out_fname, str],
+        ["-p", "--preview=", preview, str],
+        [None, "--padding=", padding, int],
+        ["-q", "--quiet", quiet, None],
+        [None, "--voffset=", voffset, int]
+    ]
+
+    argv = sys.argv
+    argc = len(argv)
+    i = 1
+    while i < argc:
+        arg = argv[i]
+        arg_next = argv[i+1] if i+1 < argc else None
+
+        if arg == "--help" or arg == "-h":
+            print ("This script converts TrueTypeFonts to .dat file for TWRP recovery.\n\n"
+                "Usage: %s [SWITCHES] [TRUETYPE FILE]\n\n"
+                "  -h, --help                   - print help\n"
+                "  -o, --output=[FILE]          - output file or '-' for stdout (default: font.dat)\n"
+                "  -p, --preview=[FILE]         - generate font preview to png file\n"
+                "  --padding=[PIXELS]           - horizontal padding around each character (default: 0)\n"
+                "  -q, --quiet                  - Do not print any output\n"
+                "  -s, --size=[SIZE IN PIXELS]  - specify font size in points (default: 20)\n"
+                "  --voffset=[PIXELS]           - vertical offset (default: font size*0.25)\n\n"
+                "Example:\n"
+                "  %s -s 40 -o ComicSans_40.dat -p preview.png ComicSans.ttf\n") % (
+                    sys.argv[0], sys.argv[0]
+                )
+            exit(0)
+
+        found = False
+        for p in arg_parser:
+            if p[0] and arg == p[0] and (arg_next or not p[3]):
+                if p[3]:
+                    p[2].set(p[3](arg_next))
+                else:
+                    p[2].set(True)
+                i += 1
+                found = True
+                break
+            elif p[1] and arg.startswith(p[1]):
+                if p[3]:
+                    p[2].set(p[3](arg[len(p[1]):]))
+                else:
+                    p[2].set(True)
+                found = True
+                break
+
+        if not found:
+            font_fname.set(arg)
+
+        i += 1
+
+    if not voffset.get():
+        voffset.set(int(fontsize.get()*0.25))
+
+    if out_fname.get() == "-":
+        quiet.set(True)
+
+    log("Loading font %s...\n" % font_fname.get())
+    font = ImageFont.truetype(font_fname.get(), fontsize.get(), 0, "utf-32be")
+    cwidth = 0
+    cheight = font.getsize('A')[1]
+    offsets = []
+    renders = []
+    data = bytes()
+
+    # temp Image and ImageDraw to get access to textsize
+    res = Image.new('L', (1, 1), 0)
+    res_draw = ImageDraw.Draw(res)
+
+    # Measure each character and render it to separate Image
+    log("Rendering characters...\n")
+    for i in range(32, 128):
+        w, h = res_draw.textsize(chr(i), font)
+        w += padding.get()*2
+        offsets.append(cwidth)
+        cwidth += w
+        if h > cheight:
+            cheight = h
+        ichr = Image.new('L', (w, cheight*2))
+        ichr_draw = ImageDraw.Draw(ichr)
+        ichr_draw.text((padding.get(), 0), chr(i), 255, font)
+        renders.append(ichr)
+
+    # Twice the height to account for under-the-baseline characters
+    cheight *= 2
+
+    # Create the result bitmap
+    log("Creating result bitmap...\n")
+    res = Image.new('L', (cwidth, cheight), 0)
+    res_draw = ImageDraw.Draw(res)
+
+    # Paste all characters into result bitmap
+    for i in range(len(renders)):
+        res.paste(renders[i], (offsets[i], 0))
+        # uncomment to draw lines separating each character (for debug)
+        #res_draw.rectangle([offsets[i], 0, offsets[i], cheight], outline="blue")
+
+    # crop the blank areas on top and bottom
+    (_, start_y, _, end_y) = res.getbbox()
+    res = res.crop((0, start_y, cwidth, end_y))
+    cheight = (end_y - start_y) + voffset.get()
+    new_res = Image.new('L', (cwidth, cheight))
+    new_res.paste(res, (0, voffset.get()))
+    res = new_res
+
+    # save the preview
+    if preview.get():
+        log("Saving preview to %s...\n" % preview.get())
+        res.save(preview.get())
+
+    # Pack the data.
+    # The "data" is a B/W bitmap with all 96 characters next to each other
+    # on one line. It is as wide as all the characters combined and as
+    # high as the tallest character, plus padding.
+    # Each byte contains info about eight pixels, starting from
+    # highest to lowest bit:
+    # bits:   | 7  6  5  4  3  2  1  0 | 15 14 13 12 11 10 9  8  | ...
+    # pixels: | 0  1  2  3  4  5  6  7 | 8  9  10 11 12 13 14 15 | ...
+    log("Packing data...\n")
+    bit = 0
+    bit_itr = 0
+    for c in res.tostring():
+        # FIXME: How to handle antialiasing?
+        # if c != '\x00':
+        # In Python3, c is int, in Python2, c is string. Because of reasons.
+        try:
+            fill = (ord(c) >= 127)
+        except TypeError:
+            fill = (c >= 127)
+        if fill:
+            bit |= (1 << (7-bit_itr))
+        bit_itr += 1
+        if bit_itr >= 8:
+            data += pack("<B", bit)
+            bit_itr = 0
+            bit = 0
+
+    # Write them to the file.
+    # Format:
+    # 000: width
+    # 004: height
+    # 008: offsets of each characters (96*uint32)
+    # 392: data as described above
+    log("Writing to %s...\n" % out_fname.get())
+    if out_fname.get() == "-":
+        write_data(sys.stdout, cwidth, cheight, offsets, data)
+    else:
+        with open(out_fname.get(), 'wb') as f:
+            write_data(f, cwidth, cheight, offsets, data)
+
+    exit(0)
diff --git a/prebuilt/vdc_pie-arm b/prebuilt/vdc_pie-arm
new file mode 100755
index 0000000..cf05cad
--- /dev/null
+++ b/prebuilt/vdc_pie-arm
Binary files differ
diff --git a/prebuilt/vdc_pie-arm64 b/prebuilt/vdc_pie-arm64
new file mode 100755
index 0000000..4623140
--- /dev/null
+++ b/prebuilt/vdc_pie-arm64
Binary files differ
diff --git a/progresstracking.cpp b/progresstracking.cpp
new file mode 100644
index 0000000..64115b9
--- /dev/null
+++ b/progresstracking.cpp
@@ -0,0 +1,108 @@
+/*
+        Copyright 2016 bigbiff/Dees_Troy TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Progress tracking class for tracking backup progess and updating the progress bar as appropriate
+
+
+#include "progresstracking.hpp"
+#include "twcommon.h"
+#ifndef BUILD_TWRPTAR_MAIN
+#include "gui/gui.hpp"
+#include "data.hpp"
+#endif
+#include "twrp-functions.hpp"
+#include <time.h>
+
+const int32_t update_interval_ms = 200; // Update interval in ms
+
+ProgressTracking::ProgressTracking(const unsigned long long backup_size) {
+	total_backup_size = backup_size;
+	partition_size = 0;
+	file_count = 0;
+	current_size = 0;
+	current_count = 0;
+	previous_partitions_size = 0;
+	display_file_count = false;
+	clock_gettime(CLOCK_MONOTONIC, &last_update);
+}
+
+void ProgressTracking::SetPartitionSize(const unsigned long long part_size) {
+	previous_partitions_size += partition_size;
+	partition_size = part_size;
+	UpdateDisplayDetails(true);
+}
+
+void ProgressTracking::SetSizeCount(const unsigned long long part_size, unsigned long long f_count) {
+	previous_partitions_size += partition_size;
+	partition_size = part_size;
+	file_count = f_count;
+	display_file_count = (file_count != 0);
+	UpdateDisplayDetails(true);
+}
+
+void ProgressTracking::UpdateSize(const unsigned long long size) {
+	current_size = size;
+	UpdateDisplayDetails(false);
+}
+
+void ProgressTracking::UpdateSizeCount(const unsigned long long size, const unsigned long long count) {
+	current_size = size;
+	current_count = count;
+	UpdateDisplayDetails(false);
+}
+
+void ProgressTracking::DisplayFileCount(const bool display) {
+	display_file_count = display;
+	UpdateDisplayDetails(true);
+}
+
+void ProgressTracking::UpdateDisplayDetails(const bool force) {
+#ifndef BUILD_TWRPTAR_MAIN
+	if (!force) {
+		// Do something to check the time frame and only update periodically to reduce the total number of GUI updates
+		timespec now;
+		clock_gettime(CLOCK_MONOTONIC, &now);
+
+		int32_t diff = TWFunc::timespec_diff_ms(last_update, now);
+		if (diff < update_interval_ms)
+			return;
+	}
+	clock_gettime(CLOCK_MONOTONIC, &last_update);
+	double display_percent = 0.0, progress_percent;
+	string size_prog = gui_lookup("size_progress", "%lluMB of %lluMB, %i%%");
+	char size_progress[1024];
+
+	if (total_backup_size != 0) // prevent division by 0
+		display_percent = (double)(current_size + previous_partitions_size) / (double)(total_backup_size) * 100;
+	sprintf(size_progress, size_prog.c_str(), (current_size + previous_partitions_size) / 1048576, total_backup_size / 1048576, (int)(display_percent));
+	DataManager::SetValue("tw_size_progress", size_progress);
+	progress_percent = (display_percent / 100);
+	DataManager::SetProgress((float)(progress_percent));
+
+	if (!display_file_count || file_count == 0) {
+		DataManager::SetValue("tw_file_progress", "");
+	} else {
+		string file_prog = gui_lookup("file_progress", "%llu of %llu files, %i%%");
+		char file_progress[1024];
+
+		display_percent = (double)(current_count) / (double)(file_count) * 100;
+		sprintf(file_progress, file_prog.c_str(), current_count, file_count, (int)(display_percent));
+		DataManager::SetValue("tw_file_progress", file_progress);
+	}
+#endif
+}
diff --git a/progresstracking.hpp b/progresstracking.hpp
new file mode 100644
index 0000000..15cf91c
--- /dev/null
+++ b/progresstracking.hpp
@@ -0,0 +1,54 @@
+/*
+        Copyright 2016 bigbiff/Dees_Troy TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __PROGRESSTRACKING_HPP
+#define __PROGRESSTRACKING_HPP
+
+#include <time.h>
+
+// Progress tracking class for tracking backup progess and updating the progress bar as appropriate
+class ProgressTracking
+{
+public:
+	ProgressTracking(const unsigned long long backup_size);
+
+	void SetPartitionSize(const unsigned long long part_size);
+	void SetSizeCount(const unsigned long long part_size, unsigned long long f_count);
+
+	void UpdateSize(const unsigned long long size);
+	void UpdateSizeCount(const unsigned long long size, const unsigned long long count);
+
+	void DisplayFileCount(const bool display);
+	void UpdateDisplayDetails(const bool force);
+
+private:
+	unsigned long long total_backup_size;              // Overall size (for the progress bar)
+
+	unsigned long long partition_size;                 // Size of the current partition
+	unsigned long long file_count;                     // Count of files for the current partition (tar backup only, not restore)
+
+	unsigned long long current_size;                   // Size of the current partition's already backed up data
+	unsigned long long current_count;                  // Count of files that have already been backed up for the current partition
+
+	unsigned long long previous_partitions_size;       // Total data already backed up from previous partitions (for the progress bar)
+
+	bool display_file_count;                           // Inidicates if we will display the file count text
+	timespec last_update;                              // Tracks last update of the displayed progress (frequent updates tax the CPU and slow us down)
+};
+
+#endif //__PROGRESSTRACKING_HPP
diff --git a/recovery_ui.h b/recovery_ui.h
new file mode 100644
index 0000000..9dae934
--- /dev/null
+++ b/recovery_ui.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _RECOVERY_UI_H
+#define _RECOVERY_UI_H
+
+// Called when recovery starts up.  Returns 0.
+extern int device_recovery_start();
+
+// Called in the input thread when a new key (key_code) is pressed.
+// *key_pressed is an array of KEY_MAX+1 bytes indicating which other
+// keys are already pressed.  Return true if the text display should
+// be toggled.
+extern int device_toggle_display(volatile char* key_pressed, int key_code);
+
+// Called in the input thread when a new key (key_code) is pressed.
+// *key_pressed is an array of KEY_MAX+1 bytes indicating which other
+// keys are already pressed.  Return true if the device should reboot
+// immediately.
+extern int device_reboot_now(volatile char* key_pressed, int key_code);
+
+// Called from the main thread when recovery is waiting for input and
+// a key is pressed.  key is the code of the key pressed; visible is
+// true if the recovery menu is being shown.  Implementations can call
+// ui_key_pressed() to discover if other keys are being held down.
+// Return one of the defined constants below in order to:
+//
+//   - move the menu highlight (HIGHLIGHT_*)
+//   - invoke the highlighted item (SELECT_ITEM)
+//   - do nothing (NO_ACTION)
+//   - invoke a specific action (a menu position: any non-negative number)
+extern int device_handle_key(int key, int visible);
+
+// Perform a recovery action selected from the menu.  'which' will be
+// the item number of the selected menu item, or a non-negative number
+// returned from device_handle_key().  The menu will be hidden when
+// this is called; implementations can call ui_print() to print
+// information to the screen.
+extern int device_perform_action(int which);
+
+// Called when we do a wipe data/factory reset operation (either via a
+// reboot from the main system with the --wipe_data flag, or when the
+// user boots into recovery manually and selects the option from the
+// menu.)  Can perform whatever device-specific wiping actions are
+// needed.  Return 0 on success.  The userdata and cache partitions
+// are erased after this returns (whether it returns success or not).
+int device_wipe_data();
+
+#define NO_ACTION           -1
+
+#define HIGHLIGHT_UP        -2
+#define HIGHLIGHT_DOWN      -3
+#define SELECT_ITEM         -4
+#define UP_A_LEVEL          -5
+#define HOME_MENU           -6
+#define MENU_MENU           -7
+
+// Again, just to keep custom recovery builds happy
+#define ITEM_APPLY_CACHE     4
+
+// Header text to display above the main menu.
+extern char* MENU_HEADERS[];
+
+// Text of menu items.
+extern char* MENU_ITEMS[];
+
+// NOTE: Main Menu index definitions moved to recovery.c
+
+int sdcard_directory(const char* path);
+int get_menu_selection(char** headers, char** items, int menu_only, int initial_selection);
+void prompt_and_wait();
+void finish_recovery(const char *send_intent);
+#endif
diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h
old mode 100644
new mode 100755
index 76166f0..ba9e509
--- a/recovery_ui/include/recovery_ui/device.h
+++ b/recovery_ui/include/recovery_ui/device.h
@@ -23,9 +23,9 @@
 #include <optional>
 #include <string>
 #include <vector>
-
+#include "ui.h"
 // Forward declaration to avoid including "ui.h".
-class RecoveryUI;
+// class RecoveryUI;
 
 class BootState;
 
diff --git a/recovery_utils/Android.bp b/recovery_utils/Android.bp
index 9bd66c5..5297584 100644
--- a/recovery_utils/Android.bp
+++ b/recovery_utils/Android.bp
@@ -96,6 +96,7 @@
     visibility: [
         "//bootable/recovery",
         "//bootable/recovery/install",
+        "//bootable/recovery/libaosprecovery",
         "//bootable/recovery/minadbd",
         "//bootable/recovery/tests",
     ],
diff --git a/recovery_utils/include/recovery_utils/logging.h b/recovery_utils/include/recovery_utils/logging.h
old mode 100644
new mode 100755
diff --git a/res-hdpi/images/dummyfile b/res-hdpi/images/dummyfile
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res-hdpi/images/dummyfile
diff --git a/res-hdpi/images/erasing_text.png b/res-hdpi/images/erasing_text.png
deleted file mode 100644
index 34c56a9..0000000
--- a/res-hdpi/images/erasing_text.png
+++ /dev/null
Binary files differ
diff --git a/res-hdpi/images/error_text.png b/res-hdpi/images/error_text.png
deleted file mode 100644
index 2a96053..0000000
--- a/res-hdpi/images/error_text.png
+++ /dev/null
Binary files differ
diff --git a/res-hdpi/images/icon_error.png b/res-hdpi/images/icon_error.png
deleted file mode 100644
index cb3d1ab..0000000
--- a/res-hdpi/images/icon_error.png
+++ /dev/null
Binary files differ
diff --git a/res-hdpi/images/installing_security_text.png b/res-hdpi/images/installing_security_text.png
deleted file mode 100644
index 97e1f11..0000000
--- a/res-hdpi/images/installing_security_text.png
+++ /dev/null
Binary files differ
diff --git a/res-hdpi/images/installing_text.png b/res-hdpi/images/installing_text.png
deleted file mode 100644
index 1d591eb..0000000
--- a/res-hdpi/images/installing_text.png
+++ /dev/null
Binary files differ
diff --git a/res-hdpi/images/no_command_text.png b/res-hdpi/images/no_command_text.png
deleted file mode 100644
index 977fcfa..0000000
--- a/res-hdpi/images/no_command_text.png
+++ /dev/null
Binary files differ
diff --git a/res-hdpi/images/progress_empty.png b/res-hdpi/images/progress_empty.png
deleted file mode 100644
index 96c4bf6..0000000
--- a/res-hdpi/images/progress_empty.png
+++ /dev/null
Binary files differ
diff --git a/res-hdpi/images/progress_fill.png b/res-hdpi/images/progress_fill.png
deleted file mode 100644
index 1717be8..0000000
--- a/res-hdpi/images/progress_fill.png
+++ /dev/null
Binary files differ
diff --git a/res-hdpi/images/stage_empty.png b/res-hdpi/images/stage_empty.png
deleted file mode 100644
index 251ec19..0000000
--- a/res-hdpi/images/stage_empty.png
+++ /dev/null
Binary files differ
diff --git a/res-hdpi/images/stage_fill.png b/res-hdpi/images/stage_fill.png
deleted file mode 100644
index 1ab79e8..0000000
--- a/res-hdpi/images/stage_fill.png
+++ /dev/null
Binary files differ
diff --git a/res-mdpi/images/dummyfile b/res-mdpi/images/dummyfile
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res-mdpi/images/dummyfile
diff --git a/res-mdpi/images/erasing_text.png b/res-mdpi/images/erasing_text.png
deleted file mode 100644
index dcd0ea6..0000000
--- a/res-mdpi/images/erasing_text.png
+++ /dev/null
Binary files differ
diff --git a/res-mdpi/images/error_text.png b/res-mdpi/images/error_text.png
deleted file mode 100644
index 2152dad..0000000
--- a/res-mdpi/images/error_text.png
+++ /dev/null
Binary files differ
diff --git a/res-mdpi/images/icon_error.png b/res-mdpi/images/icon_error.png
deleted file mode 100644
index cb3d1ab..0000000
--- a/res-mdpi/images/icon_error.png
+++ /dev/null
Binary files differ
diff --git a/res-mdpi/images/installing_security_text.png b/res-mdpi/images/installing_security_text.png
deleted file mode 100644
index d1ac4ca..0000000
--- a/res-mdpi/images/installing_security_text.png
+++ /dev/null
Binary files differ
diff --git a/res-mdpi/images/installing_text.png b/res-mdpi/images/installing_text.png
deleted file mode 100644
index c9b6d71..0000000
--- a/res-mdpi/images/installing_text.png
+++ /dev/null
Binary files differ
diff --git a/res-mdpi/images/no_command_text.png b/res-mdpi/images/no_command_text.png
deleted file mode 100644
index f77ad13..0000000
--- a/res-mdpi/images/no_command_text.png
+++ /dev/null
Binary files differ
diff --git a/res-mdpi/images/progress_empty.png b/res-mdpi/images/progress_empty.png
deleted file mode 100644
index 96c4bf6..0000000
--- a/res-mdpi/images/progress_empty.png
+++ /dev/null
Binary files differ
diff --git a/res-mdpi/images/progress_fill.png b/res-mdpi/images/progress_fill.png
deleted file mode 100644
index 1717be8..0000000
--- a/res-mdpi/images/progress_fill.png
+++ /dev/null
Binary files differ
diff --git a/res-mdpi/images/stage_empty.png b/res-mdpi/images/stage_empty.png
deleted file mode 100644
index 251ec19..0000000
--- a/res-mdpi/images/stage_empty.png
+++ /dev/null
Binary files differ
diff --git a/res-mdpi/images/stage_fill.png b/res-mdpi/images/stage_fill.png
deleted file mode 100644
index 1ab79e8..0000000
--- a/res-mdpi/images/stage_fill.png
+++ /dev/null
Binary files differ
diff --git a/res-xhdpi/images/dummyfile b/res-xhdpi/images/dummyfile
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res-xhdpi/images/dummyfile
diff --git a/res-xhdpi/images/erasing_text.png b/res-xhdpi/images/erasing_text.png
deleted file mode 100644
index e22b274..0000000
--- a/res-xhdpi/images/erasing_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xhdpi/images/error_text.png b/res-xhdpi/images/error_text.png
deleted file mode 100644
index e4c27e1..0000000
--- a/res-xhdpi/images/error_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xhdpi/images/icon_error.png b/res-xhdpi/images/icon_error.png
deleted file mode 100644
index cb3d1ab..0000000
--- a/res-xhdpi/images/icon_error.png
+++ /dev/null
Binary files differ
diff --git a/res-xhdpi/images/installing_security_text.png b/res-xhdpi/images/installing_security_text.png
deleted file mode 100644
index 7ba12b6..0000000
--- a/res-xhdpi/images/installing_security_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xhdpi/images/installing_text.png b/res-xhdpi/images/installing_text.png
deleted file mode 100644
index 567988e..0000000
--- a/res-xhdpi/images/installing_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xhdpi/images/no_command_text.png b/res-xhdpi/images/no_command_text.png
deleted file mode 100644
index a682abb..0000000
--- a/res-xhdpi/images/no_command_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xhdpi/images/progress_empty.png b/res-xhdpi/images/progress_empty.png
deleted file mode 100644
index 96c4bf6..0000000
--- a/res-xhdpi/images/progress_empty.png
+++ /dev/null
Binary files differ
diff --git a/res-xhdpi/images/progress_fill.png b/res-xhdpi/images/progress_fill.png
deleted file mode 100644
index 1717be8..0000000
--- a/res-xhdpi/images/progress_fill.png
+++ /dev/null
Binary files differ
diff --git a/res-xhdpi/images/stage_empty.png b/res-xhdpi/images/stage_empty.png
deleted file mode 100644
index 251ec19..0000000
--- a/res-xhdpi/images/stage_empty.png
+++ /dev/null
Binary files differ
diff --git a/res-xhdpi/images/stage_fill.png b/res-xhdpi/images/stage_fill.png
deleted file mode 100644
index 1ab79e8..0000000
--- a/res-xhdpi/images/stage_fill.png
+++ /dev/null
Binary files differ
diff --git a/res-xxhdpi/images/dummyfile b/res-xxhdpi/images/dummyfile
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res-xxhdpi/images/dummyfile
diff --git a/res-xxhdpi/images/erasing_text.png b/res-xxhdpi/images/erasing_text.png
deleted file mode 100644
index 6cc953b..0000000
--- a/res-xxhdpi/images/erasing_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xxhdpi/images/error_text.png b/res-xxhdpi/images/error_text.png
deleted file mode 100644
index 0d5cea8..0000000
--- a/res-xxhdpi/images/error_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xxhdpi/images/icon_error.png b/res-xxhdpi/images/icon_error.png
deleted file mode 100644
index cb3d1ab..0000000
--- a/res-xxhdpi/images/icon_error.png
+++ /dev/null
Binary files differ
diff --git a/res-xxhdpi/images/installing_security_text.png b/res-xxhdpi/images/installing_security_text.png
deleted file mode 100644
index 5d10598..0000000
--- a/res-xxhdpi/images/installing_security_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xxhdpi/images/installing_text.png b/res-xxhdpi/images/installing_text.png
deleted file mode 100644
index 6e94fa2..0000000
--- a/res-xxhdpi/images/installing_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xxhdpi/images/no_command_text.png b/res-xxhdpi/images/no_command_text.png
deleted file mode 100644
index 40ab484..0000000
--- a/res-xxhdpi/images/no_command_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xxhdpi/images/progress_empty.png b/res-xxhdpi/images/progress_empty.png
deleted file mode 100644
index 96c4bf6..0000000
--- a/res-xxhdpi/images/progress_empty.png
+++ /dev/null
Binary files differ
diff --git a/res-xxhdpi/images/progress_fill.png b/res-xxhdpi/images/progress_fill.png
deleted file mode 100644
index 1717be8..0000000
--- a/res-xxhdpi/images/progress_fill.png
+++ /dev/null
Binary files differ
diff --git a/res-xxhdpi/images/stage_empty.png b/res-xxhdpi/images/stage_empty.png
deleted file mode 100644
index 251ec19..0000000
--- a/res-xxhdpi/images/stage_empty.png
+++ /dev/null
Binary files differ
diff --git a/res-xxhdpi/images/stage_fill.png b/res-xxhdpi/images/stage_fill.png
deleted file mode 100644
index 1ab79e8..0000000
--- a/res-xxhdpi/images/stage_fill.png
+++ /dev/null
Binary files differ
diff --git a/res-xxxhdpi/images/dummyfile b/res-xxxhdpi/images/dummyfile
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res-xxxhdpi/images/dummyfile
diff --git a/res-xxxhdpi/images/erasing_text.png b/res-xxxhdpi/images/erasing_text.png
deleted file mode 100644
index cc73099..0000000
--- a/res-xxxhdpi/images/erasing_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xxxhdpi/images/error_text.png b/res-xxxhdpi/images/error_text.png
deleted file mode 100644
index fea3cfc..0000000
--- a/res-xxxhdpi/images/error_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xxxhdpi/images/icon_error.png b/res-xxxhdpi/images/icon_error.png
deleted file mode 100644
index cb3d1ab..0000000
--- a/res-xxxhdpi/images/icon_error.png
+++ /dev/null
Binary files differ
diff --git a/res-xxxhdpi/images/installing_security_text.png b/res-xxxhdpi/images/installing_security_text.png
deleted file mode 100644
index ed77d08..0000000
--- a/res-xxxhdpi/images/installing_security_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xxxhdpi/images/installing_text.png b/res-xxxhdpi/images/installing_text.png
deleted file mode 100644
index 9659106..0000000
--- a/res-xxxhdpi/images/installing_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xxxhdpi/images/no_command_text.png b/res-xxxhdpi/images/no_command_text.png
deleted file mode 100644
index 4e6f363..0000000
--- a/res-xxxhdpi/images/no_command_text.png
+++ /dev/null
Binary files differ
diff --git a/res-xxxhdpi/images/progress_empty.png b/res-xxxhdpi/images/progress_empty.png
deleted file mode 100644
index 96c4bf6..0000000
--- a/res-xxxhdpi/images/progress_empty.png
+++ /dev/null
Binary files differ
diff --git a/res-xxxhdpi/images/progress_fill.png b/res-xxxhdpi/images/progress_fill.png
deleted file mode 100644
index 1717be8..0000000
--- a/res-xxxhdpi/images/progress_fill.png
+++ /dev/null
Binary files differ
diff --git a/res-xxxhdpi/images/stage_empty.png b/res-xxxhdpi/images/stage_empty.png
deleted file mode 100644
index 251ec19..0000000
--- a/res-xxxhdpi/images/stage_empty.png
+++ /dev/null
Binary files differ
diff --git a/res-xxxhdpi/images/stage_fill.png b/res-xxxhdpi/images/stage_fill.png
deleted file mode 100644
index 1ab79e8..0000000
--- a/res-xxxhdpi/images/stage_fill.png
+++ /dev/null
Binary files differ
diff --git a/res/dummyfileforgit b/res/dummyfileforgit
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/dummyfileforgit
diff --git a/res/images/dummyfile2 b/res/images/dummyfile2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/images/dummyfile2
diff --git a/scripts/README b/scripts/README
new file mode 100644
index 0000000..0191d8f
--- /dev/null
+++ b/scripts/README
@@ -0,0 +1,41 @@
+relink-binaries.sh
+
+Intended to "relink" or change the linker path for a linked binary. Normally
+linked binaries are looking for the linker in /system/bin/linker (or
+/system/bin/linker64 for 64 bit devices). In recovery, we want to avoid
+mounting or using anything /system to allow us to install different
+ROMs or firmware. This script will run various sed commands to update the
+path to the linker to /sbin/linker64 or /sbin/linker which is sometimes
+needed especially for qseecomd (decrypt) or in some cases with user space
+touch screen binaries. Usage:
+
+./relink-binaries.sh filename
+
+The script will leave the existing file untouched and make a new file
+named filename-1-mod
+
+
+
+compare_xml.py
+
+Intended to compare two different language xml files to determine if any
+strings do not match up between the two files. Sometimes we add or rename
+or misspell string names. This script will help identify the discrepancies.
+Usage:
+
+python compare_xml.py -o target.xml
+
+
+
+language_helper.py
+
+This script reads the English and supplied other language files and
+compares the 2 and reorders and rewrites the other language to a new
+XML file such that all the strings are placed in the same order as
+the English file. It will place commented out string lines for items
+that are not present in the new file and will not include any strings
+in the new file that are no longer present in the English source.
+There is also a version tag that may be compared if present between
+the English and other language in case a translation string changes.
+
+python language_helper.py -o ../gui/theme/common/languages/es.xml
diff --git a/scripts/compare_xml.py b/scripts/compare_xml.py
new file mode 100644
index 0000000..bec41c0
--- /dev/null
+++ b/scripts/compare_xml.py
@@ -0,0 +1,62 @@
+from xml.dom import minidom
+import sys
+import getopt
+
+HELP = """
+  compare_xml.py [ -o file.xml ]
+                   -f file.xml
+                   -h - help info
+"""
+
+enfile = "en.xml"
+otherfile = ""
+
+try:
+	opts, args = getopt.getopt(sys.argv[1:], "hfo:koz", ["device="])
+except getopt.GetoptEror:
+	print HELP
+	sys.stdout.flush()
+	sys.exit(2)
+
+for opt, arg in opts:
+	if opt == "-h":
+		print HELP
+		sys.stdout.flush()
+		sys.exit()
+	elif opt == "-o":
+		otherfile = arg
+	elif opt == "-f":
+		enfile = arg
+
+if otherfile == "":
+	print HELP
+	exit()
+
+print "Comparing %s and %s" % (enfile, otherfile)
+print ""
+
+endoc = minidom.parse(enfile)
+enstrings = endoc.getElementsByTagName('string')
+
+otherdoc = minidom.parse(otherfile)
+otherstrings = otherdoc.getElementsByTagName('string')
+
+for ens in enstrings:
+	found = False
+	for others in otherstrings:
+		if ens.attributes['name'].value == others.attributes['name'].value:
+			found = True
+			break
+	if found == False:
+		print "'%s' present in %s and not in %s" % (ens.attributes['name'].value, enfile, otherfile)
+
+print ""
+
+for others in otherstrings:
+	found = False
+	for ens in enstrings:
+		if ens.attributes['name'].value == others.attributes['name'].value:
+			found = True
+			break
+	if found == False:
+		print "'%s' present in %s and not in %s" % (others.attributes['name'].value, otherfile, enfile)
diff --git a/scripts/language_helper.py b/scripts/language_helper.py
new file mode 100644
index 0000000..67bd201
--- /dev/null
+++ b/scripts/language_helper.py
@@ -0,0 +1,142 @@
+from xml.dom import minidom
+import sys
+import getopt
+
+# language helper
+#
+# by Ethan Yonker (Dees_Troy)
+#
+# This script reads the English and supplied other language files and
+# compares the 2 and reorders and rewrites the other language to a new
+# XML file such that all the strings are placed in the same order as
+# the English file. It will place commented out string lines for items
+# that are not present in the new file and will not include any strings
+# in the new file that are no longer present in the English source.
+# There is also a version tag that may be compared if present between
+# the English and other language in case a translation string changes.
+
+
+
+# this helps us avoid ascii unicode errors when writing the final XML
+def toprettyxml(xdoc, encoding):
+    #"""Return a pretty-printed XML document in a given encoding."""
+    unistr = xdoc.toprettyxml().replace(u'<?xml version="1.0" ?>',
+                          u'<?xml version="1.0" encoding="%s"?>' % encoding)
+    return unistr.encode(encoding, 'xmlcharrefreplace')
+
+HELP = """
+  language_helper.py   -o file.xml    other language to compare to English
+                     [ -f file.xml ]  output file (defaults to new.xml)
+                       -h             help info
+"""
+
+enfile = "en.xml"
+otherfile = ""
+outfile = "new.xml"
+
+try:
+	opts, args = getopt.getopt(sys.argv[1:], "hfo:koz", ["device="])
+except getopt.GetoptEror:
+	print HELP
+	sys.stdout.flush()
+	sys.exit(2)
+
+for opt, arg in opts:
+	if opt == "-h":
+		print HELP
+		sys.stdout.flush()
+		sys.exit()
+	elif opt == "-o":
+		otherfile = arg
+	elif opt == "-f":
+		outfile = arg
+
+if otherfile == "":
+	print HELP
+	exit()
+
+print "Comparing %s and %s" % (enfile, otherfile)
+print ""
+
+# Open English
+endoc = minidom.parse(enfile)
+
+# Open other language
+otherdoc = minidom.parse(otherfile)
+otherstrings = otherdoc.getElementsByTagName('string')
+
+# create minidom-document
+doc = minidom.Document()
+
+# language tag
+language = doc.createElement('language')
+doc.appendChild(language)
+
+# display tag (name of the language that shows in the GUI)
+otherlang = ""
+otherdisplay = otherdoc.getElementsByTagName('display')
+for disnode in otherdisplay:
+	if disnode.nodeType == disnode.ELEMENT_NODE:
+		language.appendChild(disnode)
+		otherlang = disnode.firstChild.data.encode("utf-8")
+		print otherlang
+
+# resources
+resources = doc.createElement('resources')
+language.appendChild(resources)
+
+enres = endoc.getElementsByTagName('resources')
+for resnode in enres:
+	resc = resnode.childNodes
+	for child in resc:
+		if child.nodeType == child.ELEMENT_NODE:
+			if child.tagName != "string":
+				otherres = otherdoc.getElementsByTagName('resources')
+				found = False
+				for othernode in otherres:
+					otherresc = othernode.childNodes
+					for otherchild in otherresc:
+						if otherchild.nodeType == otherchild.ELEMENT_NODE:
+							if otherchild.tagName == child.tagName:
+								if otherchild.attributes['name'].value == child.attributes['name'].value:
+									found = True
+									resources.appendChild(otherchild)
+									break
+					if found == True:
+						break
+				if found == False:
+					print "Failed to find %s in %s, using what we got from English" % (child.toxml(), otherlang)
+					resources.appendChild(child)
+			else:
+				found = False
+				for others in otherstrings:
+					if child.attributes['name'].value == others.attributes['name'].value:
+						found = True
+						enver = "1"
+						if child.hasAttribute('version'):
+							enver = child.attributes['version'].value
+						otherver = "1"
+						if others.hasAttribute('version'):
+							otherver = others.attributes['version'].value
+						if enver != otherver:
+							ver_err = "English has version " + enver + " but " + otherlang + " has version " + otherver + " for '" + child.attributes['name'].value + "'"
+							print ver_err
+							version_comment = doc.createComment(ver_err)
+							resources.appendChild(version_comment)
+							resources.appendChild(others)
+						else:
+							resources.appendChild(others)
+						break
+				if found == False:
+					print "'%s' present in English and not in %s" % (child.attributes['name'].value.encode("utf-8"), otherlang)
+					notfound_err = "NOT FOUND " + child.toxml().replace('--', '\-\-')
+					notfound_comment = doc.createComment(notfound_err)
+					resources.appendChild(notfound_comment)
+		elif child.nodeType == child.COMMENT_NODE:
+			resources.appendChild(child)
+
+# Done, output the xml to a file
+file_handle = open(outfile,"wb")
+itspretty = toprettyxml(doc, "utf-8")
+file_handle.write(itspretty)
+file_handle.close()
diff --git a/scripts/relink-binaries.sh b/scripts/relink-binaries.sh
new file mode 100755
index 0000000..0188560
--- /dev/null
+++ b/scripts/relink-binaries.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+process_file()
+{
+   src=$1
+   dst=$1-1 #/$(basename $2)
+   cp -f -p $src $dst
+
+   sed "s|/system/bin/linker64\x0|/sbin/linker64\x0\x0\x0\x0\x0\x0\x0|g" $dst | sed "s|/system/bin/linker\x0|/sbin/linker\x0\x0\x0\x0\x0\x0\x0|g" | sed "s|/system/bin/sh\x0|/sbin/sh\x0\x0\x0\x0\x0\x0\x0|g" > $dst-mod
+   #sed "s|/sbin/linker\x0|/system/bin/linker\x0\x0\x0\x0\x0\x0\x0|g" $dst | sed "s|/sbin/sh\x0|/system/bin/sh\x0\x0\x0\x0\x0\x0\x0|g" > $dst-mod
+   rm $dst
+}
+
+
+dest=$1
+for ARG in $*
+do
+   process_file $dest $ARG
+done
diff --git a/sepolicy/twrp.te b/sepolicy/twrp.te
new file mode 100755
index 0000000..d51cd62
--- /dev/null
+++ b/sepolicy/twrp.te
@@ -0,0 +1,14 @@
+recovery_only(`
+  permissive recovery;
+  permissive init;
+  permissive logd;
+  permissive adbd;
+  permissive fastbootd;
+  permissive postinstall;
+  permissive ueventd;
+  allow kernel unlabeled:file rw_file_perms;
+  allow kernel tmpfs:file { read };
+  allow kernel recovery:fd { use };
+  allow unlabeled unlabeled:filesystem associate;
+  allow vendor_init rootfs:dir read;
+')
diff --git a/simg2img/Android.mk b/simg2img/Android.mk
new file mode 100755
index 0000000..6b4c9a2
--- /dev/null
+++ b/simg2img/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH := system/core/libsparse
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := simg2img.cpp \
+	sparse_crc32.cpp
+LOCAL_MODULE := simg2img_twrp
+LOCAL_MODULE_STEM := simg2img
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES := \
+    libsparse \
+    libz
+LOCAL_CFLAGS := -Werror
+include $(BUILD_EXECUTABLE)
diff --git a/soong/copy.go b/soong/copy.go
new file mode 100755
index 0000000..6b3a766
--- /dev/null
+++ b/soong/copy.go
@@ -0,0 +1,82 @@
+package twrp
+
+import (
+	"android/soong/android"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"runtime"
+	"path/filepath"
+	"strings"
+)
+
+func getRecoveryAbsDir(ctx android.BaseContext) string {
+	return getBuildAbsDir(ctx) + "bootable/recovery/"
+}
+
+func getBuildAbsDir(ctx android.BaseContext) string {
+	var b string
+	_, b, _, _ = runtime.Caller(0)
+	absIndex := strings.Index(filepath.Dir(b), "bootable")
+	return string(b[0:absIndex])
+}
+
+func copyDir(src string, dest string) error {
+	var err error
+	var fds []os.FileInfo
+	var srcinfo os.FileInfo
+
+	if srcinfo, err = os.Stat(src); err != nil {
+		return err
+	}
+
+	if err = os.MkdirAll(dest, srcinfo.Mode()); err != nil {
+		return err
+	}
+
+	if fds, err = ioutil.ReadDir(src); err != nil {
+		return err
+	}
+	for _, fd := range fds {
+		srcfp := path.Join(src, fd.Name())
+		dstfp := path.Join(dest, fd.Name())
+
+		if fd.IsDir() {
+			if err = copyDir(srcfp, dstfp); err != nil {
+				fmt.Println(err)
+			}
+		} else {
+			if err = copyFile(srcfp, dstfp); err != nil {
+				fmt.Println(err)
+			}
+		}
+	}
+	return nil
+}
+
+func copyFile(src string, dest string) error {
+	var err error
+	var srcfd *os.File
+	var dstfd *os.File
+	var srcinfo os.FileInfo
+
+	if srcfd, err = os.Open(src); err != nil {
+		return err
+	}
+	defer srcfd.Close()
+
+	if dstfd, err = os.Create(dest); err != nil {
+		return err
+	}
+	defer dstfd.Close()
+
+	if _, err = io.Copy(dstfd, srcfd); err != nil {
+		return err
+	}
+	if srcinfo, err = os.Stat(src); err != nil {
+		return err
+	}
+	return os.Chmod(dest, srcinfo.Mode())
+}
diff --git a/soong/makevars.go b/soong/makevars.go
new file mode 100644
index 0000000..f453ed0
--- /dev/null
+++ b/soong/makevars.go
@@ -0,0 +1,14 @@
+package twrp
+
+import (
+	"android/soong/android"
+)
+
+func getMakeVars(ctx android.BaseContext, mVar string) string {
+	makeVars := ctx.Config().VendorConfig("makeVarsPlugin")
+	var makeVar = ""
+	if makeVars.IsSet(mVar) {
+		makeVar = makeVars.String(mVar)
+	}
+	return makeVar
+}
diff --git a/startupArgs.cpp b/startupArgs.cpp
new file mode 100755
index 0000000..954f3f5
--- /dev/null
+++ b/startupArgs.cpp
@@ -0,0 +1,97 @@
+/*
+	Copyright 2012-2021 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "startupArgs.hpp"
+
+void startupArgs::parse(int *argc, char ***argv) {
+	std::vector<std::string> args = args::get_args(argc, argv);
+	int index;
+
+	LOGINFO("Startup Commands: ");
+	for (index = 1; index < args.size(); index++) {
+		if (!processRecoveryArgs(args, index))
+			break;
+	}
+	printf("\n");
+}
+
+bool startupArgs::processRecoveryArgs(std::vector<std::string> args, int index) {
+		if (args[index].find(RESCUE_PARTY) != std::string::npos) {
+		      gui_print("\n\n");
+		      gui_msg(Msg(msg::kError, "rescue_party0=Android Rescue Party trigger! Possible solutions? Either:"));
+		      gui_msg(Msg(msg::kError, "rescue_party1= 1. Wipe caches, and/or"));
+		      gui_msg(Msg(msg::kError, "rescue_party2= 2. Format data, and/or"));
+		      gui_msg(Msg(msg::kError, "rescue_party3= 3. Clean-flash your ROM."));
+		      gui_print(" \n");
+		      gui_msg(Msg(msg::kError, "rescue_party4=The reported problem is:"));
+		      gui_print_color("error", " '%s'\n\n", args[index+1].c_str());
+		} else
+		printf("'%s'", args[index].c_str());
+		if (args[index] == FASTBOOT) {
+			fastboot_mode = true;
+			android::base::SetProperty("sys.usb.config", "none");
+			android::base::SetProperty("sys.usb.configfs", "0");
+			sleep(1);
+			android::base::SetProperty("sys.usb.configfs", "1");
+			android::base::SetProperty("sys.usb.config", "fastboot");
+			DataManager::SetValue("tw_enable_adb", 0);
+			DataManager::SetValue("tw_enable_fastboot", 1);
+		} else if (args[index].find(UPDATE_PACKAGE) != std::string::npos || args[index].find(SPECIAL_UPDATE_PACKAGE) != std::string::npos) {
+			std::string::size_type eq_pos = args[index].find("=");
+			std::string arg = args[index].substr(eq_pos + 1, args[index].size());
+			if (arg.size() == 0) {
+				LOGERR("argument error specifying zip file\n");
+			} else {
+				std::string ORSCommand = "install " + arg;
+				SkipDecryption = arg.find("@") == 0;
+				if (!OpenRecoveryScript::Insert_ORS_Command(ORSCommand))
+					return false;
+			}
+		} else if (args[index].find(SEND_INTENT) != std::string::npos) {
+			std::string::size_type eq_pos = args[index].find("=");
+			std::string arg = args[index].substr(eq_pos + 1, args[index].size());
+			if (arg.size() == 0) {
+				LOGERR("argument error specifying intent file\n");
+			} else {
+				Send_Intent = arg;
+			}
+		} else if (args[index].find(WIPE_DATA) != std::string::npos) {
+			if (!OpenRecoveryScript::Insert_ORS_Command("wipe data\n"))
+				return false;
+		} else if (args[index].find(WIPE_CACHE) != std::string::npos) {
+			if (!OpenRecoveryScript::Insert_ORS_Command("wipe cache\n"))
+				return false;
+		} else if (args[index].find(NANDROID) != std::string::npos) {
+			DataManager::SetValue(TW_BACKUP_NAME, gui_parse_text("{@auto_generate}"));
+			if (!OpenRecoveryScript::Insert_ORS_Command("backup BSDCAE\n"))
+				return false;
+		}
+		return true;
+}
+
+bool startupArgs::Should_Skip_Decryption() {
+	return SkipDecryption;
+}
+
+std::string startupArgs::Get_Intent() {
+	return Send_Intent;
+}
+
+bool startupArgs::Get_Fastboot_Mode() {
+	return fastboot_mode;
+}
diff --git a/startupArgs.hpp b/startupArgs.hpp
new file mode 100755
index 0000000..006a5fb
--- /dev/null
+++ b/startupArgs.hpp
@@ -0,0 +1,56 @@
+/*
+	Copyright 2012-2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef STARTUPARGS_HPP
+#define STARTUPARGS_HPP
+#include <android-base/properties.h>
+
+#include "data.hpp"
+#include "gui/gui.hpp"
+#include "openrecoveryscript.hpp"
+#include "partitions.hpp"
+#include "twcommon.h"
+#include "twrp-functions.hpp"
+#include "variables.h"
+#include "bootloader_message/include/bootloader_message/bootloader_message.h"
+#include "twinstall/get_args.h"
+
+class startupArgs {
+public:
+	static inline std::string const UPDATE_PACKAGE = "--update_package";
+	static inline std::string const SPECIAL_UPDATE_PACKAGE = "--special_update_package";
+	static inline std::string const WIPE_CACHE = "--wipe_cache";
+	static inline std::string const WIPE_DATA = "--wipe_data";
+	static inline std::string const SEND_INTENT = "--send_intent";
+	static inline std::string const SIDELOAD = "--sideload";
+	static inline std::string const REASON = "--reason";
+	static inline std::string const FASTBOOT = "--fastboot";
+	static inline std::string const NANDROID = "--nandroid";
+	static inline std::string const RESCUE_PARTY = "--prompt_and_wipe_data";
+	void parse(int *argc, char ***argv);
+	bool Should_Skip_Decryption();
+	std::string Get_Intent();
+	bool Get_Fastboot_Mode();
+	bool processRecoveryArgs(std::vector<std::string> args, int index);
+
+private:
+	bool SkipDecryption = false;
+	bool fastboot_mode = false;
+	std::string Send_Intent;
+};
+#endif
diff --git a/tarWrite.c b/tarWrite.c
new file mode 100644
index 0000000..9846145
--- /dev/null
+++ b/tarWrite.c
@@ -0,0 +1,113 @@
+/*
+	Copyright 2012 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "libtar/libtar.h"
+#include "twcommon.h"
+
+int flush = 0, eot_count = -1;
+unsigned char *write_buffer;
+unsigned buffer_size = 4096;
+unsigned buffer_loc = 0;
+int buffer_status = 0;
+int prog_pipe = -1;
+const unsigned long long progress_size = (unsigned long long)(T_BLOCKSIZE);
+
+void reinit_libtar_buffer(void) {
+	flush = 0;
+	eot_count = -1;
+	buffer_loc = 0;
+	buffer_status = 1;
+}
+
+void init_libtar_buffer(unsigned new_buff_size, int pipe_fd) {
+	if (new_buff_size != 0)
+		buffer_size = new_buff_size;
+
+	reinit_libtar_buffer();
+	write_buffer = (unsigned char*) malloc(sizeof(char *) * buffer_size);
+	prog_pipe = pipe_fd;
+}
+
+void free_libtar_buffer(void) {
+	if (buffer_status > 0)
+		free(write_buffer);
+	buffer_status = 0;
+	prog_pipe = -1;
+}
+
+ssize_t write_libtar_buffer(int fd, const void *buffer, size_t size) {
+	void* ptr;
+
+	if (flush == 0) {
+		ptr = write_buffer + buffer_loc;
+		memcpy(ptr, buffer, size);
+		buffer_loc += size;
+		if (eot_count >= 0 && eot_count < 2)
+			eot_count++;
+			/* At the end of the tar file, libtar will add 2 blank blocks.
+			   Once we have received both EOT blocks, we will immediately
+			   write anything in the buffer to the file.
+			*/
+
+		if (buffer_loc >= buffer_size || eot_count >= 2) {
+			flush = 1;
+		}
+	}
+	if (flush == 1) {
+		flush = 0;
+		if (buffer_loc == 0) {
+			// nothing to write
+			return 0;
+		}
+		if (write(fd, write_buffer, buffer_loc) != (int)buffer_loc) {
+			LOGERR("Error writing tar file!\n");
+			buffer_loc = 0;
+			return -1;
+		} else {
+			unsigned long long fs = (unsigned long long)(buffer_loc);
+			write(prog_pipe, &fs, sizeof(fs));
+			buffer_loc = 0;
+			return size;
+		}
+	} else {
+		return size;
+	}
+	// Shouldn't ever get here
+	return -1;
+}
+
+void flush_libtar_buffer(int fd) {
+	eot_count = 0;
+	if (buffer_status)
+		buffer_status = 2;
+}
+
+void init_libtar_no_buffer(int pipe_fd) {
+	buffer_size = T_BLOCKSIZE;
+	prog_pipe = pipe_fd;
+	buffer_status = 0;
+}
+
+ssize_t write_libtar_no_buffer(int fd, const void *buffer, size_t size) {
+	write(prog_pipe, &progress_size, sizeof(progress_size));
+	return write(fd, buffer, size);
+}
diff --git a/tarWrite.h b/tarWrite.h
new file mode 100644
index 0000000..a63a0f0
--- /dev/null
+++ b/tarWrite.h
@@ -0,0 +1,31 @@
+/*
+        Copyright 2013 bigbiff/Dees_Troy TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _TARWRITE_HEADER
+#define _TARWRITE_HEADER
+
+void reinit_libtar_buffer();
+void init_libtar_buffer(unsigned new_buff_size, int pipe_fd);
+void free_libtar_buffer();
+writefunc_t write_libtar_buffer(int fd, const void *buffer, size_t size);
+void flush_libtar_buffer(int fd);
+
+void init_libtar_no_buffer(int pipe_fd);
+writefunc_t write_libtar_no_buffer(int fd, const void *buffer, size_t size);
+
+#endif  // _TARWRITE_HEADER
diff --git a/tests/Android.bp b/tests/Android.bp
index 9ad3d3b..fb403e5 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -80,8 +80,8 @@
 // librecovery_defaults uses many shared libs that we want to avoid using in tests (e.g. we don't
 // have 32-bit android.hardware.health@2.0.so or libbootloader_message.so on marlin).
 librecovery_static_libs = [
-    "librecovery",
-    "librecovery_fastboot",
+    //"librecovery",
+    //"librecovery_fastboot",
     "libinstall",
     "librecovery_ui",
     "libminui",
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100755
index 0000000..5cf00d3
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,224 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# LOCAL_PATH := $(call my-dir)
+
+# Unit tests
+# include $(CLEAR_VARS)
+# LOCAL_CFLAGS := -Wall -Werror
+# LOCAL_MODULE := recovery_unit_test
+# LOCAL_COMPATIBILITY_SUITE := device-tests
+# LOCAL_STATIC_LIBRARIES := \
+#     libverifier \
+#     libminui \
+#     libotautil \
+#     libupdater \
+#     libziparchive \
+#     libutils \
+#     libz \
+#     libselinux \
+#     libbase \
+#     libBionicGtestMain
+
+# LOCAL_SRC_FILES := \
+#     unit/asn1_decoder_test.cpp \
+#     unit/dirutil_test.cpp \
+#     unit/locale_test.cpp \
+#     unit/rangeset_test.cpp \
+#     unit/sysutil_test.cpp \
+#     unit/zip_test.cpp \
+#     unit/ziputil_test.cpp
+
+# LOCAL_C_INCLUDES := $(commands_recovery_local_path)
+# LOCAL_SHARED_LIBRARIES := liblog
+# include $(BUILD_NATIVE_TEST)
+
+# Manual tests
+# include $(CLEAR_VARS)
+# LOCAL_CFLAGS := -Wall -Werror
+# LOCAL_MODULE := recovery_manual_test
+# LOCAL_STATIC_LIBRARIES := \
+#     libminui \
+#     libbase \
+#     libBionicGtestMain
+
+# LOCAL_SRC_FILES := manual/recovery_test.cpp
+# LOCAL_SHARED_LIBRARIES := \
+#     liblog \
+#     libpng
+
+# resource_files := $(call find-files-in-subdirs, bootable/recovery, \
+#     "*_text.png", \
+#     res-mdpi/images \
+#     res-hdpi/images \
+#     res-xhdpi/images \
+#     res-xxhdpi/images \
+#     res-xxxhdpi/images \
+#     )
+
+# # The resource image files that will go to $OUT/data/nativetest/recovery.
+# testimage_out_path := $(TARGET_OUT_DATA)/nativetest/recovery
+# GEN := $(addprefix $(testimage_out_path)/, $(resource_files))
+
+# $(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+# $(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+# $(GEN): $(testimage_out_path)/% : bootable/recovery/%
+# 	$(transform-generated-source)
+# LOCAL_GENERATED_SOURCES += $(GEN)
+
+# include $(BUILD_NATIVE_TEST)
+
+# Component tests
+# include $(CLEAR_VARS)
+# LOCAL_CFLAGS := \
+#     -Wall \
+#     -Werror \
+#     -D_FILE_OFFSET_BITS=64
+
+# ifeq ($(AB_OTA_UPDATER),true)
+# LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
+# endif
+
+# ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
+# LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
+# endif
+
+# ifeq ($(BOARD_AVB_ENABLE),true)
+# LOCAL_CFLAGS += -DBOARD_AVB_ENABLE=1
+# endif
+
+# LOCAL_MODULE := recovery_component_test
+# LOCAL_COMPATIBILITY_SUITE := device-tests
+# LOCAL_C_INCLUDES := $(commands_recovery_local_path)
+# LOCAL_SRC_FILES := \
+#     component/applypatch_test.cpp \
+#     component/bootloader_message_test.cpp \
+#     component/edify_test.cpp \
+#     component/imgdiff_test.cpp \
+#     component/install_test.cpp \
+#     component/sideload_test.cpp \
+#     component/uncrypt_test.cpp \
+#     component/updater_test.cpp \
+#     component/update_verifier_test.cpp \
+#     component/verifier_test.cpp
+
+# LOCAL_SHARED_LIBRARIES := \
+#     libhidlbase
+
+# tune2fs_static_libraries := \
+#     libext2_com_err \
+#     libext2_blkid \
+#     libext2_quota \
+#     libext2_uuid \
+#     libext2_e2p \
+#     libext2fs
+
+# LOCAL_STATIC_LIBRARIES := \
+#     libapplypatch_modes \
+#     libapplypatch \
+#     libedify \
+#     libimgdiff \
+#     libimgpatch \
+#     libbsdiff \
+#     libbspatch \
+#     libfusesideload \
+#     libotafault \
+#     librecovery \
+#     libupdater \
+#     libbootloader_message \
+#     libverifier \
+#     libotautil \
+#     libmounts \
+#     libupdate_verifier \
+#     libdivsufsort \
+#     libdivsufsort64 \
+#     libfs_mgr \
+#     libvintf_recovery \
+#     libvintf \
+#     libhidl-gen-utils \
+#     libtinyxml2 \
+#     libselinux \
+#     libext4_utils \
+#     libsparse \
+#     libcrypto_utils \
+#     libcrypto \
+#     libbz \
+#     libziparchive \
+#     liblog \
+#     libutils \
+#     libz \
+#     libbase \
+#     libtune2fs \
+#     libfec \
+#     libfec_rs \
+#     libsquashfs_utils \
+#     libcutils \
+#     libbrotli \
+#     libBionicGtestMain \
+#     $(tune2fs_static_libraries)
+
+# testdata_files := $(call find-subdir-files, testdata/*)
+
+# # The testdata files that will go to $OUT/data/nativetest/recovery.
+# testdata_out_path := $(TARGET_OUT_DATA)/nativetest/recovery
+# GEN := $(addprefix $(testdata_out_path)/, $(testdata_files))
+# $(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+# $(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+# $(GEN): $(testdata_out_path)/% : $(LOCAL_PATH)/%
+# 	$(transform-generated-source)
+# LOCAL_GENERATED_SOURCES += $(GEN)
+
+# # A copy of the testdata to be packed into continuous_native_tests.zip.
+# testdata_continuous_zip_prefix := \
+#     $(call intermediates-dir-for,PACKAGING,recovery_component_test)/DATA
+# testdata_continuous_zip_path := $(testdata_continuous_zip_prefix)/nativetest/recovery
+# GEN := $(addprefix $(testdata_continuous_zip_path)/, $(testdata_files))
+# $(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+# $(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+# $(GEN): $(testdata_continuous_zip_path)/% : $(LOCAL_PATH)/%
+# 	$(transform-generated-source)
+# LOCAL_GENERATED_SOURCES += $(GEN)
+# LOCAL_PICKUP_FILES := $(testdata_continuous_zip_prefix)
+
+# include $(BUILD_NATIVE_TEST)
+
+# Host tests
+# include $(CLEAR_VARS)
+# LOCAL_CFLAGS := -Wall -Werror
+# LOCAL_MODULE := recovery_host_test
+# LOCAL_MODULE_HOST_OS := linux
+# LOCAL_C_INCLUDES := bootable/recovery
+# LOCAL_SRC_FILES := \
+#     component/imgdiff_test.cpp
+# LOCAL_STATIC_LIBRARIES := \
+#     libimgdiff \
+#     libimgpatch \
+#     libotautil \
+#     libbsdiff \
+#     libbspatch \
+#     libziparchive \
+#     libutils \
+#     libbase \
+#     libcrypto \
+#     libbrotli \
+#     libbz \
+#     libdivsufsort64 \
+#     libdivsufsort \
+#     libz \
+#     libBionicGtestMain
+# LOCAL_SHARED_LIBRARIES := \
+#     liblog
+# include $(BUILD_HOST_NATIVE_TEST)
diff --git a/tests/unit/updater_test.cpp b/tests/unit/updater_test.cpp
index 8993dd8..4f9363c 100644
--- a/tests/unit/updater_test.cpp
+++ b/tests/unit/updater_test.cpp
@@ -339,6 +339,215 @@
     expect("", script6, kNoCause);
 }
 
+TEST_F(UpdaterTest, delete) {
+    // Delete none.
+    expect("0", "delete()", kNoCause);
+    expect("0", "delete(\"/doesntexist\")", kNoCause);
+    expect("0", "delete(\"/doesntexist1\", \"/doesntexist2\")", kNoCause);
+    expect("0", "delete(\"/doesntexist1\", \"/doesntexist2\", \"/doesntexist3\")", kNoCause);
+
+    // Delete one file.
+    TemporaryFile temp_file1;
+    ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file1.path));
+    std::string script1("delete(\"" + std::string(temp_file1.path) + "\")");
+    expect("1", script1.c_str(), kNoCause);
+
+    // Delete two files.
+    TemporaryFile temp_file2;
+    ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file2.path));
+    TemporaryFile temp_file3;
+    ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file3.path));
+    std::string script2("delete(\"" + std::string(temp_file2.path) + "\", \"" +
+                        std::string(temp_file3.path) + "\")");
+    expect("2", script2.c_str(), kNoCause);
+
+    // Delete already deleted files.
+    expect("0", script2.c_str(), kNoCause);
+
+    // Delete one out of three.
+    TemporaryFile temp_file4;
+    ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file4.path));
+    std::string script3("delete(\"/doesntexist1\", \"" + std::string(temp_file4.path) +
+                        "\", \"/doesntexist2\")");
+    expect("1", script3.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, rename) {
+    // rename() expects two arguments.
+    expect(nullptr, "rename()", kArgsParsingFailure);
+    expect(nullptr, "rename(\"arg1\")", kArgsParsingFailure);
+    expect(nullptr, "rename(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+    // src_name or dst_name cannot be empty.
+    expect(nullptr, "rename(\"\", \"arg2\")", kArgsParsingFailure);
+    expect(nullptr, "rename(\"arg1\", \"\")", kArgsParsingFailure);
+
+    // File doesn't exist (both of src and dst).
+    expect(nullptr, "rename(\"/doesntexist\", \"/doesntexisteither\")" , kFileRenameFailure);
+
+    // Can't create parent directory.
+    TemporaryFile temp_file1;
+    ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file1.path));
+    std::string script1("rename(\"" + std::string(temp_file1.path) + "\", \"/proc/0/file1\")");
+    expect(nullptr, script1.c_str(), kFileRenameFailure);
+
+    // Rename.
+    TemporaryFile temp_file2;
+    std::string script2("rename(\"" + std::string(temp_file1.path) + "\", \"" +
+                        std::string(temp_file2.path) + "\")");
+    expect(temp_file2.path, script2.c_str(), kNoCause);
+
+    // Already renamed.
+    expect(temp_file2.path, script2.c_str(), kNoCause);
+
+    // Parents create successfully.
+    TemporaryFile temp_file3;
+    TemporaryDir td;
+    std::string temp_dir(td.path);
+    std::string dst_file = temp_dir + "/aaa/bbb/a.txt";
+    std::string script3("rename(\"" + std::string(temp_file3.path) + "\", \"" + dst_file + "\")");
+    expect(dst_file.c_str(), script3.c_str(), kNoCause);
+
+    // Clean up the temp files under td.
+    ASSERT_EQ(0, unlink(dst_file.c_str()));
+    ASSERT_EQ(0, rmdir((temp_dir + "/aaa/bbb").c_str()));
+    ASSERT_EQ(0, rmdir((temp_dir + "/aaa").c_str()));
+}
+
+TEST_F(UpdaterTest, symlink) {
+    // symlink expects 1+ argument.
+    expect(nullptr, "symlink()", kArgsParsingFailure);
+
+    // symlink should fail if src is an empty string.
+    TemporaryFile temp_file1;
+    std::string script1("symlink(\"" + std::string(temp_file1.path) + "\", \"\")");
+    expect(nullptr, script1.c_str(), kSymlinkFailure);
+
+    std::string script2("symlink(\"" + std::string(temp_file1.path) + "\", \"src1\", \"\")");
+    expect(nullptr, script2.c_str(), kSymlinkFailure);
+
+    // symlink failed to remove old src.
+    std::string script3("symlink(\"" + std::string(temp_file1.path) + "\", \"/proc\")");
+    expect(nullptr, script3.c_str(), kSymlinkFailure);
+
+    // symlink can create symlinks.
+    TemporaryFile temp_file;
+    std::string content = "magicvalue";
+    ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+
+    TemporaryDir td;
+    std::string src1 = std::string(td.path) + "/symlink1";
+    std::string src2 = std::string(td.path) + "/symlink2";
+    std::string script4("symlink(\"" + std::string(temp_file.path) + "\", \"" +
+                        src1 + "\", \"" + src2 + "\")");
+    expect("t", script4.c_str(), kNoCause);
+
+    // Verify the created symlinks.
+    struct stat sb;
+    ASSERT_TRUE(lstat(src1.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode));
+    ASSERT_TRUE(lstat(src2.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode));
+
+    // Clean up the leftovers.
+    ASSERT_EQ(0, unlink(src1.c_str()));
+    ASSERT_EQ(0, unlink(src2.c_str()));
+}
+
+TEST_F(UpdaterTest, package_extract_dir) {
+  // package_extract_dir expects 2 arguments.
+  expect(nullptr, "package_extract_dir()", kArgsParsingFailure);
+  expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure);
+  expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Need to set up the ziphandle.
+  UpdaterInfo updater_info;
+  updater_info.package_zip = handle;
+
+  // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>").
+  TemporaryDir td;
+  std::string temp_dir(td.path);
+  std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")");
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  // Verify.
+  std::string data;
+  std::string file_c = temp_dir + "/c.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+
+  std::string file_d = temp_dir + "/d.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  // Modify the contents in order to retry. It's expected to be overwritten.
+  ASSERT_TRUE(android::base::WriteStringToFile("random", file_c));
+  ASSERT_TRUE(android::base::WriteStringToFile("random", file_d));
+
+  // Extract again and verify.
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  // Extracting "b/" (with slash) should give the same result.
+  script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  // Extracting "" is allowed. The entries will carry the path name.
+  script = "package_extract_dir(\"\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  std::string file_a = temp_dir + "/a.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_a, &data));
+  ASSERT_EQ(kATxtContents, data);
+  std::string file_b = temp_dir + "/b.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b, &data));
+  ASSERT_EQ(kBTxtContents, data);
+  std::string file_b_c = temp_dir + "/b/c.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data));
+  ASSERT_EQ(kCTxtContents, data);
+  std::string file_b_d = temp_dir + "/b/d.txt";
+  ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data));
+  ASSERT_EQ(kDTxtContents, data);
+
+  ASSERT_EQ(0, unlink(file_a.c_str()));
+  ASSERT_EQ(0, unlink(file_b.c_str()));
+  ASSERT_EQ(0, unlink(file_b_c.c_str()));
+  ASSERT_EQ(0, unlink(file_b_d.c_str()));
+  ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str()));
+
+  // Extracting non-existent entry should still give "t".
+  script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")";
+  expect("t", script.c_str(), kNoCause, &updater_info);
+
+  // Only relative zip_path is allowed.
+  script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  // Only absolute dest_path is allowed.
+  script = "package_extract_dir(\"b\", \"path\")";
+  expect("", script.c_str(), kNoCause, &updater_info);
+
+  CloseArchive(handle);
+}
+
 // TODO: Test extracting to block device.
 TEST_F(UpdaterTest, package_extract_file) {
   // package_extract_file expects 1 or 2 arguments.
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
old mode 100644
new mode 100755
index e065bb8..ab77df8
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -27,6 +27,42 @@
 #include "common/test_constants.h"
 #include "otautil/sysutil.h"
 
+TEST(ZipTest, ExtractPackageRecursive) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract the whole package into a temp directory.
+  TemporaryDir td;
+  ASSERT_NE(nullptr, td.path);
+  ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+  // Make sure all the files are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+  ASSERT_EQ(kATxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  CloseArchive(handle);
+
+  // Clean up.
+  ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+  ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+}
+
 TEST(ZipTest, OpenFromMemory) {
   std::string zip_path = from_testdata_base("ziptest_fake-update.zip");
   MemMapping map;
diff --git a/tests/unit/ziputil_test.cpp b/tests/unit/ziputil_test.cpp
new file mode 100644
index 0000000..14e5416
--- /dev/null
+++ b/tests/unit/ziputil_test.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/ZipUtil.h>
+#include <ziparchive/zip_archive.h>
+
+#include "common/test_constants.h"
+
+TEST(ZipUtilTest, invalid_args) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // zip_path must be a relative path.
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "/a/b", "/tmp", nullptr, nullptr));
+
+  // dest_path must be an absolute path.
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "tmp", nullptr, nullptr));
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "", nullptr, nullptr));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_all) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract the whole package into a temp directory.
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+  // Make sure all the files are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+  ASSERT_EQ(kATxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+  ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_with_slash) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract all the entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b/", td.path, nullptr, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+  // And the rest are not extracted.
+  ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+  ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+  ASSERT_EQ(kCTxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_without_slash) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Extract all the file entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b", td.path, nullptr, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+  ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+  // And the rest are not extracted.
+  ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+  ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+  ASSERT_EQ(ENOENT, errno);
+
+  // The content of the file is the same as expected.
+  std::string content1;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+  ASSERT_EQ(kCTxtContents, content1);
+
+  std::string content2;
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+  ASSERT_EQ(kDTxtContents, content2);
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+  ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+  CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, set_timestamp) {
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+  // Set the timestamp to 8/1/2008.
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
+
+  // Extract all the entries starting with "b/".
+  TemporaryDir td;
+  ExtractPackageRecursive(handle, "b", td.path, &timestamp, nullptr);
+
+  // Make sure all the files with "b/" prefix are extracted correctly.
+  std::string path(td.path);
+  std::string file_c = path + "/c.txt";
+  std::string file_d = path + "/d.txt";
+  ASSERT_EQ(0, access(file_c.c_str(), F_OK));
+  ASSERT_EQ(0, access(file_d.c_str(), F_OK));
+
+  // Verify the timestamp.
+  timespec time;
+  time.tv_sec = 1217592000;
+  time.tv_nsec = 0;
+
+  struct stat sb;
+  ASSERT_EQ(0, stat(file_c.c_str(), &sb)) << strerror(errno);
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+  ASSERT_EQ(0, stat(file_d.c_str(), &sb)) << strerror(errno);
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+  // Clean up the temp files under td.
+  ASSERT_EQ(0, unlink(file_c.c_str()));
+  ASSERT_EQ(0, unlink(file_d.c_str()));
+
+  CloseArchive(handle);
+}
diff --git a/tools/recovery_l10n/Android.mk b/tools/recovery_l10n/Android.mk
new file mode 100644
index 0000000..fd53c59
--- /dev/null
+++ b/tools/recovery_l10n/Android.mk
@@ -0,0 +1,19 @@
+# Copyright 2012 Google Inc. All Rights Reserved.
+
+
+# Prevent RecoveryLocalizer already defined errors in older trees
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?),0)
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := RecoveryLocalizer
+LOCAL_SDK_VERSION := current
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_PACKAGE)
+
+endif
diff --git a/twcommon.h b/twcommon.h
new file mode 100644
index 0000000..0895428
--- /dev/null
+++ b/twcommon.h
@@ -0,0 +1,44 @@
+/*
+    Copyright 2017 TeamWin
+    This file is part of TWRP/TeamWin Recovery Project.
+
+    TWRP is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    TWRP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TWCOMMON_HPP
+#define TWCOMMON_HPP
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef BUILD_TWRPTAR_MAIN
+#include "gui/gui.h"
+#define LOGERR(...) gui_print_color("error", "E:" __VA_ARGS__)
+#define LOGINFO(...) fprintf(stdout, "I:" __VA_ARGS__)
+#else
+#include <stdio.h>
+#define LOGERR(...) printf("E:" __VA_ARGS__)
+#define LOGINFO(...) printf("I:" __VA_ARGS__)
+#define gui_print(...) printf( __VA_ARGS__ )
+#endif
+
+#define STRINGIFY(x) #x
+#define EXPAND(x) STRINGIFY(x)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // TWCOMMON_HPP
diff --git a/twrp-functions.cpp b/twrp-functions.cpp
new file mode 100755
index 0000000..22a9a06
--- /dev/null
+++ b/twrp-functions.cpp
@@ -0,0 +1,1560 @@
+/*
+	Copyright 2012-2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <unistd.h>
+#include <vector>
+#include <dirent.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mount.h>
+#include <sys/reboot.h>
+#include <sys/sendfile.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <cctype>
+#include <algorithm>
+#include <selinux/label.h>
+#include <thread>
+
+#include <android-base/strings.h>
+#include <android-base/chrono_utils.h>
+
+#include "twrp-functions.hpp"
+#include "abx-functions.hpp"
+#include "twcommon.h"
+#include "gui/gui.hpp"
+#ifndef BUILD_TWRPTAR_MAIN
+#include "data.hpp"
+#include "partitions.hpp"
+#include "variables.h"
+#include "bootloader_message/include/bootloader_message/bootloader_message.h"
+#include "cutils/properties.h"
+#include "cutils/android_reboot.h"
+#include <sys/reboot.h>
+#endif // ndef BUILD_TWRPTAR_MAIN
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+	#include "openaes/inc/oaes_lib.h"
+#endif
+#include "set_metadata.h"
+
+extern "C" {
+	#include "libcrecovery/common.h"
+}
+
+#ifdef TW_INCLUDE_LIBRESETPROP
+    #include <resetprop.hpp>
+#endif
+
+struct selabel_handle *selinux_handle;
+
+/* Execute a command */
+int TWFunc::Exec_Cmd(const string& cmd, string &result, bool combine_stderr) {
+	FILE* exec;
+	char buffer[130];
+	int ret = 0;
+	std::string popen_cmd = cmd;
+	if (combine_stderr)
+		popen_cmd = cmd + " 2>&1";
+	exec = __popen(popen_cmd.c_str(), "r");
+
+	while (!feof(exec)) {
+		if (fgets(buffer, 128, exec) != NULL) {
+			result += buffer;
+		}
+	}
+	ret = __pclose(exec);
+	return ret;
+}
+
+int TWFunc::Exec_Cmd(const string& cmd, bool Show_Errors) {
+	pid_t pid;
+	int status;
+	switch(pid = fork())
+	{
+		case -1:
+			LOGERR("Exec_Cmd(): vfork failed: %d!\n", errno);
+			return -1;
+		case 0: // child
+			execl("/system/bin/sh", "sh", "-c", cmd.c_str(), NULL);
+			_exit(127);
+			break;
+		default:
+		{
+			if (TWFunc::Wait_For_Child(pid, &status, cmd, Show_Errors) != 0)
+				return -1;
+			else
+				return 0;
+		}
+	}
+}
+
+// Returns "file.name" from a full /path/to/file.name
+string TWFunc::Get_Filename(const string& Path) {
+	size_t pos = Path.find_last_of("/");
+	if (pos != string::npos) {
+		string Filename;
+		Filename = Path.substr(pos + 1, Path.size() - pos - 1);
+		return Filename;
+	} else
+		return Path;
+}
+
+// Returns "/path/to/" from a full /path/to/file.name
+string TWFunc::Get_Path(const string& Path) {
+	size_t pos = Path.find_last_of("/");
+	if (pos != string::npos) {
+		string Pathonly;
+		Pathonly = Path.substr(0, pos + 1);
+		return Pathonly;
+	} else
+		return Path;
+}
+
+int TWFunc::Wait_For_Child(pid_t pid, int *status, string Child_Name, bool Show_Errors) {
+	pid_t rc_pid;
+
+	rc_pid = waitpid(pid, status, 0);
+	if (rc_pid > 0) {
+		if (WIFSIGNALED(*status)) {
+			if (Show_Errors)
+				gui_msg(Msg(msg::kError, "pid_signal={1} process ended with signal: {2}")(Child_Name)(WTERMSIG(*status))); // Seg fault or some other non-graceful termination
+			return -1;
+		} else if (WEXITSTATUS(*status) == 0) {
+			LOGINFO("%s process ended with RC=%d\n", Child_Name.c_str(), WEXITSTATUS(*status)); // Success
+		} else {
+			if (Show_Errors)
+				gui_msg(Msg(msg::kError, "pid_error={1} process ended with ERROR: {2}")(Child_Name)(WEXITSTATUS(*status))); // Graceful exit, but there was an error
+			return -1;
+		}
+	} else { // no PID returned
+		if (errno == ECHILD)
+			LOGERR("%s no child process exist\n", Child_Name.c_str());
+		else {
+			LOGERR("%s Unexpected error %d\n", Child_Name.c_str(), errno);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+int TWFunc::Wait_For_Child_Timeout(pid_t pid, int *status, const string& Child_Name, int timeout) {
+	pid_t retpid = waitpid(pid, status, WNOHANG);
+	for (; retpid == 0 && timeout; --timeout) {
+		sleep(1);
+		retpid = waitpid(pid, status, WNOHANG);
+	}
+	if (retpid == 0 && timeout == 0) {
+		LOGERR("%s took too long, killing process\n", Child_Name.c_str());
+		kill(pid, SIGKILL);
+		for (timeout = 5; retpid == 0 && timeout; --timeout) {
+			sleep(1);
+			retpid = waitpid(pid, status, WNOHANG);
+		}
+		if (retpid)
+			LOGINFO("Child process killed successfully\n");
+		else
+			LOGINFO("Child process took too long to kill, may be a zombie process\n");
+		return -1;
+	} else if (retpid > 0) {
+		if (WIFSIGNALED(*status)) {
+			gui_msg(Msg(msg::kError, "pid_signal={1} process ended with signal: {2}")(Child_Name)(WTERMSIG(*status))); // Seg fault or some other non-graceful termination
+			return -1;
+		}
+	} else if (retpid < 0) { // no PID returned
+		if (errno == ECHILD)
+			LOGERR("%s no child process exist\n", Child_Name.c_str());
+		else {
+			LOGERR("%s Unexpected error %d\n", Child_Name.c_str(), errno);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+bool TWFunc::Path_Exists(string Path) {
+	struct stat st;
+	return stat(Path.c_str(), &st) == 0;
+}
+
+Archive_Type TWFunc::Get_File_Type(string fn) {
+	string::size_type i = 0;
+	int firstbyte = 0, secondbyte = 0;
+	char header[3];
+
+	ifstream f;
+	f.open(fn.c_str(), ios::in | ios::binary);
+	f.get(header, 3);
+	f.close();
+	firstbyte = header[i] & 0xff;
+	secondbyte = header[++i] & 0xff;
+
+	if (firstbyte == 0x1f && secondbyte == 0x8b)
+		return COMPRESSED;
+	else if (firstbyte == 0x4f && secondbyte == 0x41)
+		return ENCRYPTED;
+	return UNCOMPRESSED; // default
+}
+
+int TWFunc::Try_Decrypting_File(string fn, string password) {
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+	OAES_CTX * ctx = NULL;
+	uint8_t _key_data[32] = "";
+	FILE *f;
+	uint8_t buffer[4096];
+	uint8_t *buffer_out = NULL;
+	uint8_t *ptr = NULL;
+	size_t read_len = 0, out_len = 0;
+	int firstbyte = 0, secondbyte = 0;
+	size_t _j = 0;
+	size_t _key_data_len = 0;
+
+	// mostly kanged from OpenAES oaes.c
+	for ( _j = 0; _j < 32; _j++ )
+		_key_data[_j] = _j + 1;
+	_key_data_len = password.size();
+	if ( 16 >= _key_data_len )
+		_key_data_len = 16;
+	else if ( 24 >= _key_data_len )
+		_key_data_len = 24;
+	else
+		_key_data_len = 32;
+	memcpy(_key_data, password.c_str(), password.size());
+
+	ctx = oaes_alloc();
+	if (ctx == NULL) {
+		LOGERR("Failed to allocate OAES\n");
+		return -1;
+	}
+
+	oaes_key_import_data(ctx, _key_data, _key_data_len);
+
+	f = fopen(fn.c_str(), "rb");
+	if (f == NULL) {
+		LOGERR("Failed to open '%s' to try decrypt: %s\n", fn.c_str(), strerror(errno));
+		oaes_free(&ctx);
+		return -1;
+	}
+	read_len = fread(buffer, sizeof(uint8_t), 4096, f);
+	if (read_len <= 0) {
+		LOGERR("Read size during try decrypt failed: %s\n", strerror(errno));
+		fclose(f);
+		oaes_free(&ctx);
+		return -1;
+	}
+	if (oaes_decrypt(ctx, buffer, read_len, NULL, &out_len) != OAES_RET_SUCCESS) {
+		LOGERR("Error: Failed to retrieve required buffer size for trying decryption.\n");
+		fclose(f);
+		oaes_free(&ctx);
+		return -1;
+	}
+	buffer_out = (uint8_t *) calloc(out_len, sizeof(char));
+	if (buffer_out == NULL) {
+		LOGERR("Failed to allocate output buffer for try decrypt.\n");
+		fclose(f);
+		oaes_free(&ctx);
+		return -1;
+	}
+	if (oaes_decrypt(ctx, buffer, read_len, buffer_out, &out_len) != OAES_RET_SUCCESS) {
+		LOGERR("Failed to decrypt file '%s'\n", fn.c_str());
+		fclose(f);
+		free(buffer_out);
+		oaes_free(&ctx);
+		return 0;
+	}
+	fclose(f);
+	oaes_free(&ctx);
+	if (out_len < 2) {
+		LOGINFO("Successfully decrypted '%s' but read length too small.\n", fn.c_str());
+		free(buffer_out);
+		return 1; // Decrypted successfully
+	}
+	ptr = buffer_out;
+	firstbyte = *ptr & 0xff;
+	ptr++;
+	secondbyte = *ptr & 0xff;
+	if (firstbyte == 0x1f && secondbyte == 0x8b) {
+		LOGINFO("Successfully decrypted '%s' and file is compressed.\n", fn.c_str());
+		free(buffer_out);
+		return 3; // Compressed
+	}
+	if (out_len >= 262) {
+		ptr = buffer_out + 257;
+		if (strncmp((char*)ptr, "ustar", 5) == 0) {
+			LOGINFO("Successfully decrypted '%s' and file is tar format.\n", fn.c_str());
+			free(buffer_out);
+			return 2; // Tar
+		}
+	}
+	free(buffer_out);
+	LOGINFO("No errors decrypting '%s' but no known file format.\n", fn.c_str());
+	return 1; // Decrypted successfully
+#else
+	LOGERR("Encrypted backup support not included.\n");
+	return -1;
+#endif
+}
+
+unsigned long TWFunc::Get_File_Size(const string& Path) {
+	struct stat st;
+
+	if (stat(Path.c_str(), &st) != 0)
+		return 0;
+	return st.st_size;
+}
+
+std::string TWFunc::Remove_Beginning_Slash(const std::string& path) {
+	std::string res;
+	size_t pos = path.find_first_of("/");
+	if (pos != std::string::npos) {
+		res = path.substr(pos+1);
+	}
+	return res;
+}
+
+std::string TWFunc::Remove_Trailing_Slashes(const std::string& path, bool leaveLast)
+{
+	std::string res;
+	size_t last_idx = 0, idx = 0;
+
+	while (last_idx != std::string::npos)
+	{
+		if (last_idx != 0)
+			res += '/';
+
+		idx = path.find_first_of('/', last_idx);
+		if (idx == std::string::npos) {
+			res += path.substr(last_idx, idx);
+			break;
+		}
+
+		res += path.substr(last_idx, idx-last_idx);
+		last_idx = path.find_first_not_of('/', idx);
+	}
+
+	if (leaveLast)
+		res += '/';
+	return res;
+}
+
+void TWFunc::Strip_Quotes(char* &str) {
+	if (strlen(str) > 0 && str[0] == '\"')
+		str++;
+	if (strlen(str) > 0 && str[strlen(str)-1] == '\"')
+		str[strlen(str)-1] = 0;
+}
+
+vector<string> TWFunc::split_string(const string &in, char del, bool skip_empty) {
+	vector<string> res;
+
+	if (in.empty() || del == '\0')
+		return res;
+
+	string field;
+	istringstream f(in);
+	if (del == '\n') {
+		while (getline(f, field)) {
+			if (field.empty() && skip_empty)
+				continue;
+			res.push_back(field);
+		}
+	} else {
+		while (getline(f, field, del)) {
+			if (field.empty() && skip_empty)
+				continue;
+			res.push_back(field);
+		}
+	}
+	return res;
+}
+
+timespec TWFunc::timespec_diff(timespec& start, timespec& end)
+{
+	timespec temp;
+	if ((end.tv_nsec-start.tv_nsec)<0) {
+		temp.tv_sec = end.tv_sec-start.tv_sec-1;
+		temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;
+	} else {
+		temp.tv_sec = end.tv_sec-start.tv_sec;
+		temp.tv_nsec = end.tv_nsec-start.tv_nsec;
+	}
+	return temp;
+}
+
+int32_t TWFunc::timespec_diff_ms(timespec& start, timespec& end)
+{
+	return ((end.tv_sec * 1000) + end.tv_nsec/1000000) -
+			((start.tv_sec * 1000) + start.tv_nsec/1000000);
+}
+
+bool TWFunc::Wait_For_File(const string& path, std::chrono::nanoseconds timeout) {
+    android::base::Timer t;
+    while (t.duration() < timeout) {
+        struct stat sb;
+        if (stat(path.c_str(), &sb) != -1) {
+            return true;
+        }
+        std::this_thread::sleep_for(10ms);
+    }
+	return false;
+}
+
+bool TWFunc::Wait_For_Battery(std::chrono::nanoseconds timeout) {
+	std::string battery_path;
+#ifdef TW_CUSTOM_BATTERY_PATH
+	battery_path = EXPAND(TW_CUSTOM_BATTERY_PATH);
+#else
+	battery_path = "/sys/class/power_supply/battery";
+#endif
+	if (!battery_path.empty()) return TWFunc::Wait_For_File(battery_path, timeout);
+
+	return false;
+}
+
+#ifndef BUILD_TWRPTAR_MAIN
+
+// Returns "/path" from a full /path/to/file.name
+string TWFunc::Get_Root_Path(const string& Path) {
+	string Local_Path = Path;
+
+	// Make sure that we have a leading slash
+	if (Local_Path.substr(0, 1) != "/")
+		Local_Path = "/" + Local_Path;
+
+	// Trim the path to get the root path only
+	size_t position = Local_Path.find("/", 2);
+	if (position != string::npos) {
+		Local_Path.resize(position);
+	}
+	return Local_Path;
+}
+
+int TWFunc::Recursive_Mkdir(string Path) {
+	std::vector<std::string> parts = Split_String(Path, "/", true);
+	std::string cur_path;
+	for (size_t i = 0; i < parts.size(); ++i) {
+		cur_path += "/" + parts[i];
+		if (!TWFunc::Path_Exists(cur_path)) {
+			if (mkdir(cur_path.c_str(), 0777)) {
+				gui_msg(Msg(msg::kError, "create_folder_strerr=Can not create '{1}' folder ({2}).")(cur_path)(strerror(errno)));
+				return false;
+			} else {
+				tw_set_default_metadata(cur_path.c_str());
+			}
+		}
+	}
+	return true;
+}
+
+void TWFunc::GUI_Operation_Text(string Read_Value, string Default_Text) {
+	string Display_Text;
+
+	DataManager::GetValue(Read_Value, Display_Text);
+	if (Display_Text.empty())
+		Display_Text = Default_Text;
+
+	DataManager::SetValue("tw_operation", Display_Text);
+	DataManager::SetValue("tw_partition", "");
+}
+
+void TWFunc::GUI_Operation_Text(string Read_Value, string Partition_Name, string Default_Text) {
+	string Display_Text;
+
+	DataManager::GetValue(Read_Value, Display_Text);
+	if (Display_Text.empty())
+		Display_Text = Default_Text;
+
+	DataManager::SetValue("tw_operation", Display_Text);
+	DataManager::SetValue("tw_partition", Partition_Name);
+}
+
+void TWFunc::Copy_Log(string Source, string Destination) {
+	int logPipe[2];
+	int pigz_pid;
+	int destination_fd;
+	std::string destLogBuffer;
+
+	PartitionManager.Mount_By_Path(Destination, false);
+
+	size_t extPos = Destination.find(".gz");
+	std::string uncompressedLog(Destination);
+	uncompressedLog.replace(extPos, Destination.length(), "");
+
+	if (Path_Exists(Destination)) {
+		Archive_Type type = Get_File_Type(Destination);
+		if (type == COMPRESSED) {
+			std::string destFileBuffer;
+			std::string getCompressedContents = "pigz -c -d " + Destination;
+			if (Exec_Cmd(getCompressedContents, destFileBuffer, false) < 0) {
+				LOGINFO("Unable to get destination logfile contents.\n");
+				return;
+			}
+			destLogBuffer.append(destFileBuffer);
+		}
+	} else if (Path_Exists(uncompressedLog)) {
+		std::ifstream uncompressedIfs(uncompressedLog.c_str());
+		std::stringstream uncompressedSS;
+		uncompressedSS << uncompressedIfs.rdbuf();
+		uncompressedIfs.close();
+		std::string uncompressedLogBuffer(uncompressedSS.str());
+		destLogBuffer.append(uncompressedLogBuffer);
+		std::remove(uncompressedLog.c_str());
+	}
+
+	std::ifstream ifs(Source.c_str());
+	std::stringstream ss;
+	ss << ifs.rdbuf();
+	std::string srcLogBuffer(ss.str());
+	ifs.close();
+
+	if (pipe(logPipe) < 0) {
+		LOGINFO("Unable to open pipe to write to persistent log file: %s\n", Destination.c_str());
+	}
+
+	destination_fd = open(Destination.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+
+	pigz_pid = fork();
+	if (pigz_pid < 0) {
+		LOGINFO("fork() failed\n");
+		close(destination_fd);
+		close(logPipe[0]);
+		close(logPipe[1]);
+	} else if (pigz_pid == 0) {
+		close(logPipe[1]);
+		dup2(logPipe[0], fileno(stdin));
+		dup2(destination_fd, fileno(stdout));
+		if (execlp("pigz", "pigz", "-", NULL) < 0) {
+			close(destination_fd);
+			close(logPipe[0]);
+			_exit(-1);
+		}
+	} else {
+		close(logPipe[0]);
+		if (write(logPipe[1], destLogBuffer.c_str(), destLogBuffer.size()) < 0) {
+			LOGINFO("Unable to append to persistent log: %s\n", Destination.c_str());
+			close(logPipe[1]);
+			close(destination_fd);
+			return;
+		}
+		if (write(logPipe[1], srcLogBuffer.c_str(), srcLogBuffer.size()) < 0) {
+			LOGINFO("Unable to append to persistent log: %s\n", Destination.c_str());
+			close(logPipe[1]);
+			close(destination_fd);
+			return;
+		}
+		close(logPipe[1]);
+	}
+	close(destination_fd);
+}
+
+void TWFunc::Update_Log_File(void) {
+	std::string recoveryDir = get_log_dir() + "recovery/";
+
+	if (get_log_dir() == CACHE_LOGS_DIR) {
+		if (!PartitionManager.Mount_By_Path(CACHE_LOGS_DIR, false)) {
+			LOGINFO("Failed to mount %s for TWFunc::Update_Log_File\n", CACHE_LOGS_DIR);
+		}
+	}
+
+	if (!TWFunc::Path_Exists(recoveryDir)) {
+		LOGINFO("Recreating %s folder.\n", recoveryDir.c_str());
+		if (!Create_Dir_Recursive(recoveryDir,  S_IRWXU | S_IRWXG | S_IWGRP | S_IXGRP, 0, 0)) {
+			LOGINFO("Unable to create %s folder.\n", recoveryDir.c_str());
+		}
+	}
+
+	std::string logCopy = recoveryDir + "log.gz";
+	std::string lastLogCopy = recoveryDir + "last_log.gz";
+	copy_file(logCopy, lastLogCopy, 0600);
+	Copy_Log(TMP_LOG_FILE, logCopy);
+	chown(logCopy.c_str(), 1000, 1000);
+	chmod(logCopy.c_str(), 0600);
+	chmod(lastLogCopy.c_str(), 0640);
+
+	if (get_log_dir() == CACHE_LOGS_DIR) {
+		if (PartitionManager.Mount_By_Path("/cache", false)) {
+			if (unlink("/cache/recovery/command") && errno != ENOENT) {
+				LOGINFO("Can't unlink %s\n", "/cache/recovery/command");
+			}
+		}
+	}
+	sync();
+}
+
+void TWFunc::Clear_Bootloader_Message() {
+	std::string err;
+	if (!clear_bootloader_message(&err)) {
+		LOGINFO("%s\n", err.c_str());
+	}
+}
+
+void TWFunc::Update_Intent_File(string Intent) {
+	if (PartitionManager.Mount_By_Path("/cache", false) && !Intent.empty()) {
+		TWFunc::write_to_file("/cache/recovery/intent", Intent);
+	}
+}
+
+// reboot: Reboot the system. Return -1 on error, no return on success
+int TWFunc::tw_reboot(RebootCommand command)
+{
+	DataManager::Flush();
+	Update_Log_File();
+
+	// Always force a sync before we reboot
+	sync();
+
+	switch (command) {
+		case rb_current:
+		case rb_system:
+			Update_Intent_File("s");
+			sync();
+			check_and_run_script("/system/bin/rebootsystem.sh", "reboot system");
+#ifdef ANDROID_RB_PROPERTY
+			return property_set(ANDROID_RB_PROPERTY, "reboot,");
+#elif defined(ANDROID_RB_RESTART)
+			return android_reboot(ANDROID_RB_RESTART, 0, 0);
+#else
+			return reboot(RB_AUTOBOOT);
+#endif
+		case rb_recovery:
+			check_and_run_script("/system/bin/rebootrecovery.sh", "reboot recovery");
+			return property_set(ANDROID_RB_PROPERTY, "reboot,recovery");
+		case rb_bootloader:
+			check_and_run_script("/system/bin/rebootbootloader.sh", "reboot bootloader");
+			return property_set(ANDROID_RB_PROPERTY, "reboot,bootloader");
+		case rb_poweroff:
+			check_and_run_script("/system/bin/poweroff.sh", "power off");
+#ifdef ANDROID_RB_PROPERTY
+			return property_set(ANDROID_RB_PROPERTY, "shutdown,");
+#elif defined(ANDROID_RB_POWEROFF)
+			return android_reboot(ANDROID_RB_POWEROFF, 0, 0);
+#else
+			return reboot(RB_POWER_OFF);
+#endif
+		case rb_download:
+			check_and_run_script("/system/bin/rebootdownload.sh", "reboot download");
+			return property_set(ANDROID_RB_PROPERTY, "reboot,download");
+		case rb_edl:
+			check_and_run_script("/system/bin/rebootedl.sh", "reboot edl");
+			return property_set(ANDROID_RB_PROPERTY, "reboot,edl");
+		case rb_fastboot:
+			return property_set(ANDROID_RB_PROPERTY, "reboot,fastboot");
+		default:
+			return -1;
+	}
+	return -1;
+}
+
+void TWFunc::check_and_run_script(const char* script_file, const char* display_name)
+{
+	// Check for and run startup script if script exists
+	struct stat st;
+	if (stat(script_file, &st) == 0) {
+		gui_msg(Msg("run_script=Running {1} script...")(display_name));
+		chmod(script_file, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
+		TWFunc::Exec_Cmd(script_file);
+		gui_msg("done=Done.");
+	}
+}
+
+int TWFunc::removeDir(const string path, bool skipParent) {
+	DIR *d = opendir(path.c_str());
+	int r = 0;
+	string new_path;
+
+	if (d == NULL) {
+		gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(path)(strerror(errno)));
+		return -1;
+	}
+
+	if (d) {
+		struct dirent *p;
+		while (!r && (p = readdir(d))) {
+			if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, ".."))
+				continue;
+			new_path = path + "/";
+			new_path.append(p->d_name);
+			if (p->d_type == DT_DIR) {
+				r = removeDir(new_path, true);
+				if (!r) {
+					if (p->d_type == DT_DIR)
+						r = rmdir(new_path.c_str());
+					else
+						LOGINFO("Unable to removeDir '%s': %s\n", new_path.c_str(), strerror(errno));
+				}
+			} else if (p->d_type == DT_REG || p->d_type == DT_LNK || p->d_type == DT_FIFO || p->d_type == DT_SOCK) {
+				r = unlink(new_path.c_str());
+				if (r != 0) {
+					LOGINFO("Unable to unlink '%s: %s'\n", new_path.c_str(), strerror(errno));
+				}
+			}
+		}
+		closedir(d);
+
+		if (!r) {
+			if (skipParent)
+				return 0;
+			else
+				r = rmdir(path.c_str());
+		}
+	}
+	return r;
+}
+
+int TWFunc::copy_file(string src, string dst, int mode, bool mount_paths) {
+	if (mount_paths) {
+		PartitionManager.Mount_By_Path(src, false);
+		PartitionManager.Mount_By_Path(dst, false);
+	}
+	if (!Path_Exists(src)) {
+		LOGINFO("Path %s does not exist. Unable to copy file to %s\n", src.c_str(), dst.c_str());
+		return -1;
+	}
+	std::ifstream srcfile(src.c_str(), ios::binary);
+	std::ofstream dstfile(dst.c_str(), ios::binary);
+	dstfile << srcfile.rdbuf();
+	if (dstfile.bad()) {
+		LOGINFO("Unable to copy file %s to %s\n", src.c_str(), dst.c_str());
+		return -1;
+	}
+
+	srcfile.close();
+	dstfile.close();
+	if (chmod(dst.c_str(), mode) != 0) {
+		LOGERR("Unable to chmod file: %s. Error: %s\n", dst.c_str(), strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+
+unsigned int TWFunc::Get_D_Type_From_Stat(string Path) {
+	struct stat st;
+
+	stat(Path.c_str(), &st);
+	if (st.st_mode & S_IFDIR)
+		return DT_DIR;
+	else if (st.st_mode & S_IFBLK)
+		return DT_BLK;
+	else if (st.st_mode & S_IFCHR)
+		return DT_CHR;
+	else if (st.st_mode & S_IFIFO)
+		return DT_FIFO;
+	else if (st.st_mode & S_IFLNK)
+		return DT_LNK;
+	else if (st.st_mode & S_IFREG)
+		return DT_REG;
+	else if (st.st_mode & S_IFSOCK)
+		return DT_SOCK;
+	return DT_UNKNOWN;
+}
+
+int TWFunc::read_file(string fn, string& results) {
+	ifstream file;
+	file.open(fn.c_str(), ios::in);
+
+	if (file.is_open()) {
+		std::string line;
+		while (std::getline(file, line)) {
+			results += line;
+		}
+		file.close();
+		return 0;
+	}
+
+	LOGINFO("Cannot find file %s\n", fn.c_str());
+	return -1;
+}
+
+int TWFunc::read_file(string fn, vector<string>& results) {
+	ifstream file;
+	string line;
+	file.open(fn.c_str(), ios::in);
+	if (file.is_open()) {
+		while (getline(file, line))
+			results.push_back(line);
+		file.close();
+		return 0;
+	}
+	LOGINFO("Cannot find file %s\n", fn.c_str());
+	return -1;
+}
+
+int TWFunc::read_file(string fn, uint64_t& results) {
+	ifstream file;
+	file.open(fn.c_str(), ios::in);
+
+	if (file.is_open()) {
+		file >> results;
+		file.close();
+		return 0;
+	}
+
+	LOGINFO("Cannot find file %s\n", fn.c_str());
+	return -1;
+}
+
+bool TWFunc::write_to_file(const string& fn, const string& line) {
+	FILE *file;
+	file = fopen(fn.c_str(), "w");
+	if (file != NULL) {
+		fwrite(line.c_str(), line.size(), 1, file);
+		fclose(file);
+		return true;
+	}
+	LOGINFO("Cannot find file %s\n", fn.c_str());
+	return false;
+}
+
+bool TWFunc::write_to_file(const string& fn, const std::vector<string> lines) {
+	FILE *file;
+	file = fopen(fn.c_str(), "a+");
+	if (file != NULL) {
+		for (auto&& line: lines) {
+			fwrite(line.c_str(), line.size(), 1, file);
+			fwrite("\n", sizeof(char), 1, file);
+		}
+		fclose(file);
+		return true;
+	}
+	return false;
+}
+
+
+bool TWFunc::Try_Decrypting_Backup(string Restore_Path, string Password) {
+	DIR* d;
+
+	string Filename;
+	Restore_Path += "/";
+	d = opendir(Restore_Path.c_str());
+	if (d == NULL) {
+		gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(Restore_Path)(strerror(errno)));
+		return false;
+	}
+
+	struct dirent* de;
+	while ((de = readdir(d)) != NULL) {
+		Filename = Restore_Path;
+		Filename += de->d_name;
+		if (TWFunc::Get_File_Type(Filename) == ENCRYPTED) {
+			if (TWFunc::Try_Decrypting_File(Filename, Password) < 2) {
+				DataManager::SetValue("tw_restore_password", ""); // Clear the bad password
+				DataManager::SetValue("tw_restore_display", "");  // Also clear the display mask
+				closedir(d);
+				return false;
+			}
+		}
+	}
+	closedir(d);
+	return true;
+}
+
+string TWFunc::Get_Current_Date() {
+	string Current_Date;
+	time_t seconds = time(0);
+	struct tm *t = localtime(&seconds);
+	char timestamp[255];
+	sprintf(timestamp,"%04d-%02d-%02d--%02d-%02d-%02d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
+	Current_Date = timestamp;
+	return Current_Date;
+}
+
+string TWFunc::System_Property_Get(string Prop_Name) {
+	return Partition_Property_Get(Prop_Name, PartitionManager, PartitionManager.Get_Android_Root_Path(), "build.prop");
+}
+
+string TWFunc::Partition_Property_Get(string Prop_Name, TWPartitionManager &PartitionManager, string Mount_Point, string prop_file_name) {
+	bool mount_state = PartitionManager.Is_Mounted_By_Path(Mount_Point);
+	std::vector<string> buildprop;
+	string propvalue;
+	string prop_file;
+	if (!PartitionManager.Mount_By_Path(Mount_Point, true))
+		return propvalue;
+	if (Mount_Point == PartitionManager.Get_Android_Root_Path()) {
+		prop_file = Mount_Point + "/system/" + prop_file_name;
+	} else {
+		prop_file = Mount_Point + "/" + prop_file_name;
+	}
+	if (!TWFunc::Path_Exists(prop_file)) {
+		LOGINFO("Unable to locate file: %s\n", prop_file.c_str());
+		return propvalue;
+	}
+	if (TWFunc::read_file(prop_file, buildprop) != 0) {
+		LOGINFO("Unable to open %s for getting '%s'.\n", prop_file_name.c_str(), Prop_Name.c_str());
+		DataManager::SetValue(TW_BACKUP_NAME, Get_Current_Date());
+		if (!mount_state)
+			PartitionManager.UnMount_By_Path(Mount_Point, false);
+		return propvalue;
+	}
+	int line_count = buildprop.size();
+	int index;
+	size_t start_pos = 0, end_pos;
+	string propname;
+	for (index = 0; index < line_count; index++) {
+		end_pos = buildprop.at(index).find("=", start_pos);
+		propname = buildprop.at(index).substr(start_pos, end_pos);
+		if (propname == Prop_Name) {
+			propvalue = buildprop.at(index).substr(end_pos + 1, buildprop.at(index).size());
+			if (!mount_state)
+				PartitionManager.UnMount_By_Path(Mount_Point, false);
+			return propvalue;
+		}
+	}
+	if (!mount_state)
+		PartitionManager.UnMount_By_Path(Mount_Point, false);
+	return propvalue;
+}
+
+void TWFunc::Auto_Generate_Backup_Name() {
+	string propvalue = System_Property_Get("ro.build.display.id");
+	if (propvalue.empty()) {
+		DataManager::SetValue(TW_BACKUP_NAME, Get_Current_Date());
+		return;
+	}
+	else {
+		//remove periods from build display so it doesn't confuse the extension code
+		propvalue.erase(remove(propvalue.begin(), propvalue.end(), '.'), propvalue.end());
+	}
+	string Backup_Name = Get_Current_Date();
+	Backup_Name += "_" + propvalue;
+	if (Backup_Name.size() > MAX_BACKUP_NAME_LEN)
+		Backup_Name.resize(MAX_BACKUP_NAME_LEN);
+	// Trailing spaces cause problems on some file systems, so remove them
+	string space_check, space = " ";
+	space_check = Backup_Name.substr(Backup_Name.size() - 1, 1);
+	while (space_check == space) {
+		Backup_Name.resize(Backup_Name.size() - 1);
+		space_check = Backup_Name.substr(Backup_Name.size() - 1, 1);
+	}
+	replace(Backup_Name.begin(), Backup_Name.end(), ' ', '_');
+	if (PartitionManager.Check_Backup_Name(Backup_Name, false, true) != 0) {
+		LOGINFO("Auto generated backup name '%s' is not valid, using date instead.\n", Backup_Name.c_str());
+		DataManager::SetValue(TW_BACKUP_NAME, Get_Current_Date());
+	} else {
+		DataManager::SetValue(TW_BACKUP_NAME, Backup_Name);
+	}
+}
+
+void TWFunc::Fixup_Time_On_Boot(const string& time_paths /* = "" */)
+{
+#ifdef QCOM_RTC_FIX
+	static bool fixed = false;
+	if (fixed)
+		return;
+
+	LOGINFO("TWFunc::Fixup_Time: Pre-fix date and time: %s\n", TWFunc::Get_Current_Date().c_str());
+
+	struct timeval tv;
+	uint64_t offset = 0;
+	std::string sepoch = "/sys/class/rtc/rtc0/since_epoch";
+
+	if (TWFunc::read_file(sepoch, offset) == 0) {
+
+		LOGINFO("TWFunc::Fixup_Time: Setting time offset from file %s\n", sepoch.c_str());
+
+		tv.tv_sec = offset;
+		tv.tv_usec = 0;
+		settimeofday(&tv, NULL);
+
+		gettimeofday(&tv, NULL);
+
+		if (tv.tv_sec > 1517600000) { // Anything older then 2 Feb 2018 19:33:20 GMT will do nicely thank you ;)
+
+			LOGINFO("TWFunc::Fixup_Time: Date and time corrected: %s\n", TWFunc::Get_Current_Date().c_str());
+			fixed = true;
+			return;
+
+		}
+
+	} else {
+
+		LOGINFO("TWFunc::Fixup_Time: opening %s failed\n", sepoch.c_str());
+
+	}
+
+	LOGINFO("TWFunc::Fixup_Time: will attempt to use the ats files now.\n");
+
+	// Devices with Qualcomm Snapdragon 800 do some shenanigans with RTC.
+	// They never set it, it just ticks forward from 1970-01-01 00:00,
+	// and then they have files /data/system/time/ats_* with 64bit offset
+	// in miliseconds which, when added to the RTC, gives the correct time.
+	// So, the time is: (offset_from_ats + value_from_RTC)
+	// There are multiple ats files, they are for different systems? Bases?
+	// Like, ats_1 is for modem and ats_2 is for TOD (time of day?).
+	// Look at file time_genoff.h in CodeAurora, qcom-opensource/time-services
+
+	std::vector<std::string> paths; // space separated list of paths
+	if (time_paths.empty()) {
+		paths = Split_String("/data/system/time/ /data/time/ /data/vendor/time/", " ");
+		if (!PartitionManager.Mount_By_Path("/data", false))
+			return;
+	} else {
+		// When specific path(s) are used, Fixup_Time needs those
+		// partitions to already be mounted!
+		paths = Split_String(time_paths, " ");
+	}
+
+	FILE *f;
+	offset = 0;
+	struct dirent *dt;
+	std::string ats_path;
+
+	// Prefer ats_2, it seems to be the one we want according to logcat on hammerhead
+	// - it is the one for ATS_TOD (time of day?).
+	// However, I never saw a device where the offset differs between ats files.
+	for (size_t i = 0; i < paths.size(); ++i)
+	{
+		DIR *d = opendir(paths[i].c_str());
+		if (!d)
+			continue;
+
+		while ((dt = readdir(d)))
+		{
+			if (dt->d_type != DT_REG || strncmp(dt->d_name, "ats_", 4) != 0)
+				continue;
+
+			if (ats_path.empty() || strcmp(dt->d_name, "ats_2") == 0)
+				ats_path = paths[i] + dt->d_name;
+		}
+
+		closedir(d);
+	}
+
+	if (ats_path.empty()) {
+		LOGINFO("TWFunc::Fixup_Time: no ats files found, leaving untouched!\n");
+	} else if ((f = fopen(ats_path.c_str(), "r")) == NULL) {
+		LOGINFO("TWFunc::Fixup_Time: failed to open file %s\n", ats_path.c_str());
+	} else if (fread(&offset, sizeof(offset), 1, f) != 1) {
+		LOGINFO("TWFunc::Fixup_Time: failed load uint64 from file %s\n", ats_path.c_str());
+		fclose(f);
+	} else {
+		fclose(f);
+
+		LOGINFO("TWFunc::Fixup_Time: Setting time offset from file %s, offset %llu\n", ats_path.c_str(), (unsigned long long) offset);
+		DataManager::SetValue("tw_qcom_ats_offset", (unsigned long long) offset, 1);
+		fixed = true;
+	}
+
+	if (!fixed) {
+#ifdef TW_QCOM_ATS_OFFSET
+		// Offset is the difference between the current time and the time since_epoch
+		// To calculate the offset in Android, the following expression (from a root shell) can be used:
+		// echo "$(( ($(date +%s) - $(cat /sys/class/rtc/rtc0/since_epoch)) ))"
+		// Add 3 zeros to the output and use that in the TW_QCOM_ATS_OFFSET flag in your BoardConfig.mk
+		// For example, if the result of the calculation is 1642433544, use 1642433544000 as the offset
+		offset = (uint64_t) TW_QCOM_ATS_OFFSET;
+		DataManager::SetValue("tw_qcom_ats_offset", (unsigned long long) offset, 1);
+		LOGINFO("TWFunc::Fixup_Time: Setting time offset from TW_QCOM_ATS_OFFSET, offset %llu\n", (unsigned long long) offset);
+#else
+		// Failed to get offset from ats file, check twrp settings
+		unsigned long long value;
+		if (DataManager::GetValue("tw_qcom_ats_offset", value) < 0) {
+			return;
+		} else {
+			offset = (uint64_t) value;
+			LOGINFO("TWFunc::Fixup_Time: Setting time offset from twrp setting file, offset %llu\n", (unsigned long long) offset);
+			// Do not consider the settings file as a definitive answer, keep fixed=false so next run will try ats files again
+		}
+#endif
+	}
+
+	gettimeofday(&tv, NULL);
+
+	tv.tv_sec += offset/1000;
+#ifdef TW_CLOCK_OFFSET
+// Some devices are even quirkier and have ats files that are offset from the actual time
+	tv.tv_sec = tv.tv_sec + TW_CLOCK_OFFSET;
+#endif
+	tv.tv_usec += (offset%1000)*1000;
+
+	while (tv.tv_usec >= 1000000)
+	{
+		++tv.tv_sec;
+		tv.tv_usec -= 1000000;
+	}
+
+	settimeofday(&tv, NULL);
+
+	LOGINFO("TWFunc::Fixup_Time: Date and time corrected: %s\n", TWFunc::Get_Current_Date().c_str());
+#endif
+}
+
+std::vector<std::string> TWFunc::Split_String(const std::string& str, const std::string& delimiter, bool removeEmpty)
+{
+	std::vector<std::string> res;
+	size_t idx = 0, idx_last = 0;
+
+	while (idx < str.size())
+	{
+		idx = str.find_first_of(delimiter, idx_last);
+		if (idx == std::string::npos)
+			idx = str.size();
+
+		if (idx-idx_last != 0 || !removeEmpty)
+			res.push_back(str.substr(idx_last, idx-idx_last));
+
+		idx_last = idx + delimiter.size();
+	}
+
+	return res;
+}
+
+bool TWFunc::Create_Dir_Recursive(const std::string& path, mode_t mode, uid_t uid, gid_t gid)
+{
+	std::vector<std::string> parts = Split_String(path, "/");
+	std::string cur_path;
+	struct stat info;
+	for (size_t i = 0; i < parts.size(); ++i)
+	{
+		cur_path += "/" + parts[i];
+		if (stat(cur_path.c_str(), &info) < 0 || !S_ISDIR(info.st_mode))
+		{
+			if (mkdir(cur_path.c_str(), mode) < 0)
+				return false;
+			chown(cur_path.c_str(), uid, gid);
+		}
+	}
+	return true;
+}
+
+int TWFunc::Set_Brightness(std::string brightness_value)
+{
+	int result = -1;
+	std::string secondary_brightness_file;
+
+	if (DataManager::GetIntValue("tw_has_brightnesss_file")) {
+		LOGINFO("TWFunc::Set_Brightness: Setting brightness control to %s\n", brightness_value.c_str());
+		result = TWFunc::write_to_file(DataManager::GetStrValue("tw_brightness_file"), brightness_value);
+		DataManager::GetValue("tw_secondary_brightness_file", secondary_brightness_file);
+		if (!secondary_brightness_file.empty()) {
+			LOGINFO("TWFunc::Set_Brightness: Setting secondary brightness control to %s\n", brightness_value.c_str());
+			TWFunc::write_to_file(secondary_brightness_file, brightness_value);
+		}
+	}
+	return result ? 0 : -1;
+}
+
+bool TWFunc::Toggle_MTP(bool enable) {
+#ifdef TW_HAS_MTP
+	static int was_enabled = false;
+
+	if (enable && was_enabled) {
+		if (!PartitionManager.Enable_MTP())
+			PartitionManager.Disable_MTP();
+	} else {
+		was_enabled = DataManager::GetIntValue("tw_mtp_enabled");
+		PartitionManager.Disable_MTP();
+		usleep(500);
+	}
+	return was_enabled;
+#else
+	return false;
+#endif
+}
+
+void TWFunc::SetPerformanceMode(bool mode) {
+	if (mode) {
+		property_set("recovery.perf.mode", "1");
+	} else {
+		property_set("recovery.perf.mode", "0");
+	}
+	// Some time for events to catch up to init handlers
+	usleep(500000);
+}
+
+std::string TWFunc::to_string(unsigned long value) {
+	std::ostringstream os;
+	os << value;
+	return os.str();
+}
+
+void TWFunc::Disable_Stock_Recovery_Replace(void) {
+	if (PartitionManager.Mount_By_Path(PartitionManager.Get_Android_Root_Path(), false)) {
+		// Disable flashing of stock recovery
+		if (TWFunc::Path_Exists("/system/recovery-from-boot.p")) {
+			rename("/system/recovery-from-boot.p", "/system/recovery-from-boot.bak");
+			gui_msg("rename_stock=Renamed stock recovery file in /system to prevent the stock ROM from replacing TWRP.");
+			sync();
+		}
+		PartitionManager.UnMount_By_Path(PartitionManager.Get_Android_Root_Path(), false);
+	}
+}
+
+unsigned long long TWFunc::IOCTL_Get_Block_Size(const char* block_device) {
+	unsigned long block_device_size;
+	int ret = 0;
+
+	int fd = open(block_device, O_RDONLY);
+	if (fd < 0) {
+		LOGINFO("Find_Partition_Size: Failed to open '%s', (%s)\n", block_device, strerror(errno));
+	} else {
+		ret = ioctl(fd, BLKGETSIZE, &block_device_size);
+		close(fd);
+		if (ret) {
+			LOGINFO("Find_Partition_Size: ioctl error: (%s)\n", strerror(errno));
+		} else {
+			return (unsigned long long)(block_device_size) * 512LLU;
+		}
+	}
+	return 0;
+}
+
+void TWFunc::copy_kernel_log(string curr_storage) {
+	std::string dmesgDst = curr_storage + "/dmesg.log";
+	std::string dmesgCmd = "/system/bin/dmesg";
+
+	std::string result;
+	Exec_Cmd(dmesgCmd, result, false);
+	write_to_file(dmesgDst, result);
+	gui_msg(Msg("copy_kernel_log=Copied kernel log to {1}")(dmesgDst));
+	tw_set_default_metadata(dmesgDst.c_str());
+}
+
+void TWFunc::copy_logcat(string curr_storage) {
+	std::string logcatDst = curr_storage + "/logcat.txt";
+	std::string logcatCmd = "logcat -d";
+
+	std::string result;
+	Exec_Cmd(logcatCmd, result, false);
+	write_to_file(logcatDst, result);
+	gui_msg(Msg("copy_logcat=Copied logcat to {1}")(logcatDst));
+	tw_set_default_metadata(logcatDst.c_str());
+}
+
+bool TWFunc::isNumber(string strtocheck) {
+	int num = 0;
+	std::istringstream iss(strtocheck);
+
+	if (!(iss >> num).fail())
+		return true;
+	else
+		return false;
+}
+
+int TWFunc::stream_adb_backup(string &Restore_Name) {
+	string cmd = "/system/bin/bu --twrp stream " + Restore_Name;
+	LOGINFO("stream_adb_backup: %s\n", cmd.c_str());
+	int ret = TWFunc::Exec_Cmd(cmd);
+	if (ret != 0)
+		return -1;
+	return ret;
+}
+
+std::string TWFunc::get_log_dir() {
+	if (PartitionManager.Find_Partition_By_Path(CACHE_LOGS_DIR) == NULL) {
+		if (PartitionManager.Find_Partition_By_Path(DATA_LOGS_DIR) == NULL) {
+			LOGINFO("Unable to find a directory to store TWRP logs.");
+			return "";
+		} else {
+			return DATA_LOGS_DIR;
+		}
+	}
+	else {
+		return CACHE_LOGS_DIR;
+	}
+}
+
+void TWFunc::check_selinux_support() {
+	if (TWFunc::Path_Exists("/prebuilt_file_contexts")) {
+		if (TWFunc::Path_Exists("/file_contexts")) {
+			printf("Renaming regular /file_contexts -> /file_contexts.bak\n");
+			rename("/file_contexts", "/file_contexts.bak");
+		}
+		printf("Moving /prebuilt_file_contexts -> /file_contexts\n");
+		rename("/prebuilt_file_contexts", "/file_contexts");
+	}
+	struct selinux_opt selinux_options[] = {
+		{ SELABEL_OPT_PATH, "/file_contexts" }
+	};
+	selinux_handle = selabel_open(SELABEL_CTX_FILE, selinux_options, 1);
+	if (!selinux_handle)
+		printf("No file contexts for SELinux\n");
+	else
+		printf("SELinux contexts loaded from /file_contexts\n");
+	{ // Check to ensure SELinux can be supported by the kernel
+		char *contexts = NULL;
+		std::string cacheDir = TWFunc::get_log_dir();
+		std::string se_context_check = cacheDir + "recovery/";
+		int ret = 0;
+
+		if (cacheDir == CACHE_LOGS_DIR) {
+			PartitionManager.Mount_By_Path(CACHE_LOGS_DIR, false);
+		}
+		if (TWFunc::Path_Exists(se_context_check)) {
+			ret = lgetfilecon(se_context_check.c_str(), &contexts);
+			if (ret < 0) {
+				LOGINFO("Could not check %s SELinux contexts, using /system/bin/teamwin instead which may be inaccurate.\n", se_context_check.c_str());
+				lgetfilecon("/system/bin/teamwin", &contexts);
+			}
+		}
+		if (ret < 0) {
+			gui_warn("no_kernel_selinux=Kernel does not have support for reading SELinux contexts.");
+		} else {
+			free(contexts);
+			gui_msg("full_selinux=Full SELinux support is present.");
+		}
+	}
+}
+
+bool TWFunc::Is_TWRP_App_In_System() {
+	LOGINFO("checking for twrp app\n");
+	TWPartition* sys = PartitionManager.Find_Partition_By_Path(PartitionManager.Get_Android_Root_Path());
+	if (!sys->Get_Super_Status()) {
+		bool is_system_mounted = true;
+		if(!PartitionManager.Is_Mounted_By_Path(PartitionManager.Get_Android_Root_Path())) {
+			is_system_mounted = false;
+			PartitionManager.Mount_By_Path(PartitionManager.Get_Android_Root_Path(), false);
+		}
+		string base_path = PartitionManager.Get_Android_Root_Path();
+		if (TWFunc::Path_Exists(PartitionManager.Get_Android_Root_Path() + "/system"))
+			base_path += "/system"; // For devices with system as a root file system (e.g. Pixel)
+		string install_path = base_path + "/priv-app";
+		if (!TWFunc::Path_Exists(install_path))
+			install_path = base_path + "/app";
+		install_path += "/twrpapp";
+		if (TWFunc::Path_Exists(install_path)) {
+			LOGINFO("App found at '%s'\n", install_path.c_str());
+			DataManager::SetValue("tw_app_installed_in_system", 1);
+			return true;
+		}
+		if (!is_system_mounted)
+			PartitionManager.UnMount_By_Path(PartitionManager.Get_Android_Root_Path(), false);
+		DataManager::SetValue("tw_app_installed_in_system", 0);
+	}
+	DataManager::SetValue("tw_app_installed_in_system", 0);
+	return false;
+}
+
+void TWFunc::checkforapp(){
+
+	string sdkverstr = System_Property_Get("ro.build.version.sdk");
+	int sdkver = 0;
+	if (!sdkverstr.empty()) {
+		sdkver = atoi(sdkverstr.c_str());
+	}
+	if (sdkver <= 13) {
+		if (sdkver == 0)
+			LOGINFO("Unable to read sdk version from build prop\n");
+		else
+			LOGINFO("SDK version too low for TWRP app (%i < 14)\n", sdkver);
+		DataManager::SetValue("tw_app_install_status", 1); // 0 = no status, 1 = not installed, 2 = already installed or do not install
+		goto exit;
+	}
+	if (Is_TWRP_App_In_System()) {
+		DataManager::SetValue("tw_app_install_status", 2); // 0 = no status, 1 = not installed, 2 = already installed or do not install
+		goto exit;
+	}
+	if (PartitionManager.Mount_By_Path("/data", false)) {
+		const char parent_path[] = "/data/app";
+		const char app_prefix[] = "me.twrp.twrpapp-";
+		DIR *d = opendir(parent_path);
+		if (d) {
+			struct dirent *p;
+			while ((p = readdir(d))) {
+				if (p->d_type != DT_DIR || strlen(p->d_name) < strlen(app_prefix) || strncmp(p->d_name, app_prefix, strlen(app_prefix)))
+					continue;
+				closedir(d);
+				LOGINFO("App found at '%s/%s'\n", parent_path, p->d_name);
+				DataManager::SetValue("tw_app_install_status", 2); // 0 = no status, 1 = not installed, 2 = already installed or do not install
+				goto exit;
+			}
+			closedir(d);
+		}
+	} else {
+		LOGINFO("Data partition cannot be mounted during app check\n");
+		DataManager::SetValue("tw_app_install_status", 2); // 0 = no status, 1 = not installed, 2 = already installed or do not install
+	}
+
+	LOGINFO("App not installed\n");
+	DataManager::SetValue("tw_app_install_status", 1); // 0 = no status, 1 = not installed, 2 = already installed
+exit:
+	return;
+
+}
+
+int TWFunc::Property_Override(string Prop_Name, string Prop_Value) {
+#ifdef TW_INCLUDE_LIBRESETPROP
+    return setprop(Prop_Name.c_str(), Prop_Value.c_str(), false);
+#else
+    return -2;
+#endif
+}
+
+void TWFunc::List_Mounts() {
+	std::vector<std::string> mounts;
+	read_file("/proc/mounts", mounts);
+	LOGINFO("Mounts:\n");
+	for (auto&& mount: mounts) {
+		LOGINFO("%s\n", mount.c_str());
+	}
+}
+
+string TWFunc::Check_For_TwrpFolder() {
+	string oldFolder = "";
+	vector<string> customTWRPFolders;
+	string mainPath = DataManager::GetCurrentStoragePath();
+	DIR* d;
+	struct dirent* de;
+
+	if (DataManager::GetIntValue(TW_IS_ENCRYPTED)) {
+		goto exit;
+	}
+
+
+	d = opendir(mainPath.c_str());
+	if (d == NULL) {
+		goto exit;
+	}
+
+	while ((de = readdir(d)) != NULL) {
+		string name = de->d_name;
+		string fullPath = mainPath + '/' + name;
+		unsigned char type = de->d_type;
+
+		if (name == "." || name == "..") continue;
+
+		if (type == DT_UNKNOWN) {
+			type = Get_D_Type_From_Stat(fullPath);
+		}
+
+		if (type == DT_DIR && Path_Exists(fullPath + '/' + TW_SETTINGS_FILE)) {
+			if ('/' + name == TW_DEFAULT_RECOVERY_FOLDER) {
+				oldFolder = name;
+			} else {
+				customTWRPFolders.push_back(name);
+			}
+		}
+	}
+
+	closedir(d);
+
+	if (oldFolder == "" && customTWRPFolders.empty()) {
+		LOGINFO("No recovery folder found. Using default folder.\n");
+		goto exit;
+	} else if (customTWRPFolders.empty()) {
+		LOGINFO("No custom recovery folder found. Using TWRP as default.\n");
+		goto exit;
+	} else {
+		if (customTWRPFolders.size() > 1) {
+			LOGINFO("More than one custom recovery folder found. Using first one from the list.\n");
+		} else {
+			LOGINFO("One custom recovery folder found.\n");
+		}
+		string customPath =  '/' + customTWRPFolders.at(0);
+
+		if (Path_Exists(mainPath + TW_DEFAULT_RECOVERY_FOLDER)) {
+			string oldBackupFolder = mainPath + TW_DEFAULT_RECOVERY_FOLDER + "/BACKUPS/" + DataManager::GetStrValue("device_id");
+			string newBackupFolder = mainPath + customPath + "/BACKUPS/" + DataManager::GetStrValue("device_id");
+
+			if (Path_Exists(oldBackupFolder)) {
+				vector<string> backups;
+				d = opendir(oldBackupFolder.c_str());
+
+				if (d != NULL) {
+					while ((de = readdir(d)) != NULL) {
+						string name = de->d_name;
+						unsigned char type = de->d_type;
+
+						if (name == "." || name == "..") continue;
+
+						if (type == DT_UNKNOWN) {
+							type = Get_D_Type_From_Stat(mainPath + '/' + name);
+						}
+
+						if (type == DT_DIR) {
+							backups.push_back(name);
+						}
+					}
+					closedir(d);
+				}
+
+				for (auto it = backups.begin(); it != backups.end(); it++) {
+					Exec_Cmd("mv -f \"" + oldBackupFolder + '/' + *it + "\" \"" + newBackupFolder + '/' + *it + (Path_Exists(newBackupFolder + '/' + *it) ? "_new\"" : "\""));
+				}
+			}
+			Exec_Cmd("rm -rf \"" + mainPath + TW_DEFAULT_RECOVERY_FOLDER + '\"');
+		}
+
+		return customPath;
+	}
+
+exit:
+	return TW_DEFAULT_RECOVERY_FOLDER;
+}
+
+bool TWFunc::Check_Xml_Format(const std::string filename) {
+	std::string buffer(' ', 4);
+	std::string abx_hdr("ABX\x00", 4);
+	std::ifstream File;
+	File.open(filename);
+	if (File.is_open()) {
+		File.get(&buffer[0], buffer.size());
+		File.close();
+		// Android Binary Xml start from these bytes
+		if(!buffer.compare(0, abx_hdr.size(), abx_hdr))
+			return false; // ABX format - requires conversion
+	}
+	return true; // good format, possible to parse
+}
+
+// return true=successful conversion (return the name of the converted file in "result");
+// return false=an error happened (leave "result" alone)
+bool TWFunc::abx_to_xml(const std::string path, std::string &result) {
+	bool res = false;
+	if (!TWFunc::Path_Exists(path))
+		return res;
+
+	std::ifstream infile(path);
+	if (!infile.is_open())
+		return res;
+
+	std::string fname = TWFunc::Get_Filename(path);
+	std::string tmp = "/tmp/converted_xml";
+	if (!TWFunc::Path_Exists(tmp)) {
+		if (mkdir(tmp.c_str(), 0777) != 0)
+			tmp = "/tmp";
+	}
+
+	std::string tmp_path = tmp + "/" + fname;
+	std::ofstream outfile(tmp_path);
+	if (!outfile.is_open()) {
+		LOGINFO("Error. The abx conversion of %s has failed.\n", path.c_str());
+		infile.close();
+		return res;
+	}
+
+	AbxToXml r(infile, outfile);
+	if (r.run() && TWFunc::Path_Exists(tmp_path)) {
+		res = true;
+		result = tmp_path;
+	}
+
+	infile.close();
+	outfile.close();
+
+	return res;
+}
+
+#endif // ndef BUILD_TWRPTAR_MAIN
diff --git a/twrp-functions.hpp b/twrp-functions.hpp
new file mode 100755
index 0000000..825d667
--- /dev/null
+++ b/twrp-functions.hpp
@@ -0,0 +1,138 @@
+/*
+	Copyright 2012 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _TWRPFUNCTIONS_HPP
+#define _TWRPFUNCTIONS_HPP
+
+#include <string>
+#include <vector>
+
+#include "twrpDigest/twrpDigest.hpp"
+
+#ifndef BUILD_TWRPTAR_MAIN
+#include "partitions.hpp"
+#endif
+
+using namespace std;
+
+#define CACHE_LOGS_DIR "/cache/" // For devices with a dedicated cache partition
+#define DATA_LOGS_DIR "/data/"	 // For devices that do not have a dedicated cache partition
+
+typedef enum
+{
+	rb_current = 0,
+	rb_system,
+	rb_recovery,
+	rb_poweroff,
+	rb_bootloader,
+	rb_download,
+	rb_edl,
+	rb_fastboot
+} RebootCommand;
+
+enum Archive_Type {
+	UNCOMPRESSED = 0,
+	COMPRESSED,
+	ENCRYPTED,
+	COMPRESSED_ENCRYPTED
+};
+
+// Partition class
+class TWFunc
+{
+public:
+	static string Get_Root_Path(const string& Path);                            // Trims any trailing folders or filenames from the path, also adds a leading / if not present
+	static string Get_Path(const string& Path);                                 // Trims everything after the last / in the string
+	static string Get_Filename(const string& Path);                             // Trims the path off of a filename
+
+	static int Exec_Cmd(const string& cmd, string &result, bool combine_stderr);     //execute a command and return the result as a string by reference, set combined_stderror to add stderr
+	static int Exec_Cmd(const string& cmd, bool Show_Errors = true);            //execute a command, displays an error to the GUI if Show_Errors is true, Show_Errors is true by default
+	static int Wait_For_Child(pid_t pid, int *status, string Child_Name, bool Show_Errors = true); // Waits for pid to exit and checks exit status, displays an error to the GUI if Show_Errors is true which is the default
+	static int Wait_For_Child_Timeout(pid_t pid, int *status, const string& Child_Name, int timeout); // Waits for a pid to exit until the timeout is hit. If timeout is hit, kill the chilld.
+	static bool Path_Exists(string Path);                                       // Returns true if the path exists
+	static Archive_Type Get_File_Type(string fn);                               // Determines file type, 0 for unknown, 1 for gzip, 2 for OAES encrypted
+	static int Try_Decrypting_File(string fn, string password); // -1 for some error, 0 for failed to decrypt, 1 for decrypted, 3 for decrypted and found gzip format
+	static unsigned long Get_File_Size(const string& Path);                     // Returns the size of a file
+	static std::string Remove_Beginning_Slash(const std::string& path);         // Remove the beginning slash of a path
+	static std::string Remove_Trailing_Slashes(const std::string& path, bool leaveLast = false); // Normalizes the path, e.g /data//media/ -> /data/media
+	static void Strip_Quotes(char* &str);                                       // Remove leading & trailing double-quotes from a string
+	static vector<string> split_string(const string &in, char del, bool skip_empty);
+	static timespec timespec_diff(timespec& start, timespec& end);	            // Return a diff for 2 times
+	static int32_t timespec_diff_ms(timespec& start, timespec& end);            // Returns diff in ms
+	static bool Wait_For_File(const string& path, std::chrono::nanoseconds timeout); // Wait For File, True is success, False is timeout;
+	static bool Wait_For_Battery(std::chrono::nanoseconds timeout);             // Wait For /sys/class/power_supply/battery or TW_CUSTOM_BATTERY_PATH, True is success, False is timeout;
+
+#ifndef BUILD_TWRPTAR_MAIN
+	static void install_htc_dumlock(void);                                      // Installs HTC Dumlock
+	static void htc_dumlock_restore_original_boot(void);                        // Restores the backup of boot from HTC Dumlock
+	static void htc_dumlock_reflash_recovery_to_boot(void);                     // Reflashes the current recovery to boot
+	static int Recursive_Mkdir(string Path);                                    // Recursively makes the entire path
+	static void GUI_Operation_Text(string Read_Value, string Default_Text);     // Updates text for display in the GUI, e.g. Backing up %partition name%
+	static void GUI_Operation_Text(string Read_Value, string Partition_Name, string Default_Text); // Same as above but includes partition name
+	static void Update_Log_File(void);                                          // Writes the log to last_log
+	static void Update_Intent_File(string Intent);                              // Updates intent file
+	static int tw_reboot(RebootCommand command);                                // Prepares the device for rebooting
+	static void check_and_run_script(const char* script_file, const char* display_name); // checks for the existence of a script, chmods it to 755, then runs it
+	static int removeDir(const string path, bool removeParent); //recursively remove a directory
+	static int copy_file(string src, string dst, int mode, bool mount_paths=true); //copy file from src to dst with mode permissions
+	static unsigned int Get_D_Type_From_Stat(string Path);                      // Returns a dirent dt_type value using stat instead of dirent
+	static int read_file(string fn, vector<string>& results); //read from file
+	static int read_file(string fn, string& results); //read from file
+	static int read_file(string fn, uint64_t& results); //read from file
+	static bool write_to_file(const string& fn, const string& line);              //write single line to file with no newline
+	static bool write_to_file(const string& fn, const std::vector<string> lines); // write vector of strings line by line with newlines
+	static bool Try_Decrypting_Backup(string Restore_Path, string Password); // true for success, false for failed to decrypt
+	static string System_Property_Get(string Prop_Name);                // Returns value of Prop_Name from reading /system/build.prop
+	static string Partition_Property_Get(string Prop_Name, TWPartitionManager &PartitionManager, string Mount_Point, string prop_file_name);     // Returns value of Prop_Name from reading provided prop file
+	static string Get_Current_Date(void);                               // Returns the current date in ccyy-m-dd--hh-nn-ss format
+	static void Auto_Generate_Backup_Name();                            // Populates TW_BACKUP_NAME with a backup name based on current date and ro.build.display.id from /system/build.prop
+	static void Fixup_Time_On_Boot(const string& time_paths = ""); // Fixes time on devices which need it (time_paths is a space separated list of paths to check for ats_* files)
+	static std::vector<std::string> Split_String(const std::string& str, const std::string& delimiter, bool removeEmpty = true); // Splits string by delimiter
+	static bool Create_Dir_Recursive(const std::string& path, mode_t mode = 0755, uid_t uid = -1, gid_t gid = -1);  // Create directory and it's parents, if they don't exist. mode, uid and gid are set to all _newly_ created folders. If whole path exists, do nothing.
+	static int Set_Brightness(std::string brightness_value); // Well, you can read, it does what it says, passing return int from TWFunc::Write_File ;)
+	static bool Toggle_MTP(bool enable);                                        // Disables MTP if enable is false and re-enables MTP if enable is true and it was enabled the last time it was toggled off
+	static std::string to_string(unsigned long value); //convert ul to string
+	static void SetPerformanceMode(bool mode); // support recovery.perf.mode
+	static void Disable_Stock_Recovery_Replace(); // Disable stock ROMs from replacing TWRP with stock recovery
+	static unsigned long long IOCTL_Get_Block_Size(const char* block_device);
+	static void copy_kernel_log(string curr_storage); // Copy Kernel Log to Current Storage (PSTORE/KMSG)
+	static void copy_logcat(string curr_storage); // Copy Logcat to Current Storage
+	static bool isNumber(string strtocheck); // return true if number, false if not a number
+	static int stream_adb_backup(string &Restore_Name); // Tell ADB Backup to Stream to TWRP from GUI selection
+	static std::string get_log_dir(); // return recovery log storage directory
+	static void check_selinux_support(); // print whether selinux support is enabled to console
+	static bool Is_TWRP_App_In_System(); // Check if the TWRP app is installed in the system partition
+	static void checkforapp();
+	static int Property_Override(string Prop_Name, string Prop_Value); // Override properties (including ro. properties)
+	static void List_Mounts(); // List current mounts by the kernel
+	static void Clear_Bootloader_Message(); // Removes the bootloader message from misc for next boot
+	static string Check_For_TwrpFolder(); // Gets user defined path on storage where backups should be stored
+	static bool Check_Xml_Format(const std::string filename); // Return whether a xml is in plain xml or ABX format
+
+	static bool abx_to_xml(const std::string path, std::string &result); // could we convert abx to xml (if so, return the full path to the converted file)
+private:
+	static void Copy_Log(string Source, string Destination);
+
+};
+
+extern int Log_Offset;
+#else
+};
+#endif // ndef BUILD_TWRPTAR_MAIN
+
+#endif // _TWRPFUNCTIONS_HPP
diff --git a/twrp.cpp b/twrp.cpp
new file mode 100644
index 0000000..6566e04
--- /dev/null
+++ b/twrp.cpp
@@ -0,0 +1,453 @@
+/*
+	Copyright 2012-2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <signal.h>
+#include "gui/twmsg.h"
+
+#include "cutils/properties.h"
+
+#ifdef ANDROID_RB_RESTART
+#include "cutils/android_reboot.h"
+#else
+#include <sys/reboot.h>
+#endif
+
+extern "C" {
+#include "gui/gui.h"
+}
+#include "set_metadata.h"
+#include "gui/gui.hpp"
+#include "gui/pages.hpp"
+#include "gui/objects.hpp"
+#include "twcommon.h"
+#include "twrp-functions.hpp"
+#include "data.hpp"
+
+#ifdef TW_LOAD_VENDOR_MODULES
+#include "kernel_module_loader.hpp"
+#endif
+
+#include "partitions.hpp"
+#ifdef __ANDROID_API_N__
+#include <android-base/strings.h>
+#else
+#include <base/strings.h>
+#endif
+#include "openrecoveryscript.hpp"
+#include "variables.h"
+#include "startupArgs.hpp"
+#include "twrpAdbBuFifo.hpp"
+#ifdef TW_USE_NEW_MINADBD
+// #include "minadbd/minadbd.h"
+#else
+extern "C" {
+#include "minadbd21/adb.h"
+}
+#endif
+
+#ifdef TW_INCLUDE_CRYPTO
+#include "FsCrypt.h"
+#include "Decrypt.h"
+#endif
+
+//extern int adb_server_main(int is_daemon, int server_port, int /* reply_fd */);
+
+TWPartitionManager PartitionManager;
+int Log_Offset;
+bool datamedia;
+
+static void Print_Prop(const char *key, const char *name, void *cookie) {
+	printf("%s=%s\n", key, name);
+}
+
+static void Decrypt_Page(bool SkipDecryption, bool datamedia) {
+	// Offer to decrypt if the device is encrypted
+	if (DataManager::GetIntValue(TW_IS_ENCRYPTED) != 0) {
+		if (SkipDecryption) {
+			LOGINFO("Skipping decryption\n");
+			PartitionManager.Update_System_Details();
+		} else if (DataManager::GetIntValue(TW_CRYPTO_PWTYPE) != 0) {
+			LOGINFO("Is encrypted, do decrypt page first\n");
+			if (DataManager::GetIntValue(TW_IS_FBE))
+				DataManager::SetValue("tw_crypto_user_id", "0");
+			if (gui_startPage("decrypt", 1, 1) != 0) {
+				LOGERR("Failed to start decrypt GUI page.\n");
+			}
+		}
+	} else if (datamedia) {
+		PartitionManager.Update_System_Details();
+		if (tw_get_default_metadata(DataManager::GetSettingsStoragePath().c_str()) != 0) {
+			LOGINFO("Failed to get default contexts and file mode for storage files.\n");
+		} else {
+			LOGINFO("Got default contexts and file mode for storage files.\n");
+		}
+	}
+}
+
+static void process_fastbootd_mode() {
+		LOGINFO("starting fastboot\n");
+
+#ifdef TW_LOAD_VENDOR_MODULES
+		printf("=> Linking mtab\n");
+		symlink("/proc/mounts", "/etc/mtab");
+		std::string fstab_filename = "/etc/twrp.fstab";
+		if (!TWFunc::Path_Exists(fstab_filename)) {
+			fstab_filename = "/etc/recovery.fstab";
+		}
+		printf("=> Processing %s\n", fstab_filename.c_str());
+		if (!PartitionManager.Process_Fstab(fstab_filename, 1, false)) {
+			LOGERR("Failing out of recovery due to problem with fstab.\n");
+			return;
+		}
+		TWPartition* ven = PartitionManager.Find_Partition_By_Path("/vendor");
+		PartitionManager.Setup_Super_Devices();
+		PartitionManager.Prepare_Super_Volume(ven);
+		KernelModuleLoader::Load_Vendor_Modules();
+		if (android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) {
+			PartitionManager.Unmap_Super_Devices();
+		}
+#endif
+
+		gui_msg(Msg("fastboot_console_msg=Entered Fastboot mode..."));
+		// Check for and run startup script if script exists
+		TWFunc::check_and_run_script("/system/bin/runatboot.sh", "boot");
+		TWFunc::check_and_run_script("/system/bin/postfastboot.sh", "fastboot");
+		if (gui_startPage("fastboot", 1, 1) != 0) {
+			LOGERR("Failed to start fastbootd page.\n");
+		}
+}
+
+static void process_recovery_mode(twrpAdbBuFifo* adb_bu_fifo, bool skip_decryption) {
+	char crash_prop_val[PROPERTY_VALUE_MAX];
+	int crash_counter;
+	std::string cmdline;
+	if (TWFunc::read_file("/proc/cmdline", cmdline) != 0) {
+		LOGINFO("Unable to read cmdline for fastboot mode\n");
+	}
+
+	property_get("twrp.crash_counter", crash_prop_val, "-1");
+	crash_counter = atoi(crash_prop_val) + 1;
+	snprintf(crash_prop_val, sizeof(crash_prop_val), "%d", crash_counter);
+	property_set("twrp.crash_counter", crash_prop_val);
+
+	if (crash_counter == 0) {
+		property_list(Print_Prop, NULL);
+		printf("\n");
+	} else {
+		printf("twrp.crash_counter=%d\n", crash_counter);
+	}
+
+	printf("=> Linking mtab\n");
+	symlink("/proc/mounts", "/etc/mtab");
+	std::string fstab_filename = "/etc/twrp.fstab";
+	if (!TWFunc::Path_Exists(fstab_filename)) {
+		fstab_filename = "/etc/recovery.fstab";
+	}
+	printf("=> Processing %s\n", fstab_filename.c_str());
+	if (!PartitionManager.Process_Fstab(fstab_filename, 1, true)) {
+		LOGERR("Failing out of recovery due to problem with fstab.\n");
+		return;
+	}
+
+#ifdef TW_LOAD_VENDOR_MODULES
+	bool fastboot_mode = cmdline.find("twrpfastboot=1") != std::string::npos;
+	if (fastboot_mode)
+		KernelModuleLoader::Load_Vendor_Modules();
+	else
+		KernelModuleLoader::Load_Vendor_Modules();
+#endif
+
+// We are doing this here to allow super partition to be set up prior to overriding properties
+#if defined(TW_INCLUDE_LIBRESETPROP) && defined(TW_OVERRIDE_SYSTEM_PROPS)
+	stringstream override_props(EXPAND(TW_OVERRIDE_SYSTEM_PROPS));
+	string current_prop;
+
+	std::vector<std::string> partition_list;
+	partition_list.push_back (PartitionManager.Get_Android_Root_Path().c_str());
+#ifdef TW_OVERRIDE_PROPS_ADDITIONAL_PARTITIONS
+	std::vector<std::string> additional_partition_list = TWFunc::Split_String(TW_OVERRIDE_PROPS_ADDITIONAL_PARTITIONS, " ");
+	partition_list.insert(partition_list.end(), additional_partition_list.begin(), additional_partition_list.end());
+#endif
+	std::vector<std::string> build_prop_list = {"build.prop"};
+#ifdef TW_SYSTEM_BUILD_PROP_ADDITIONAL_PATHS
+	std::vector<std::string> additional_build_prop_list = TWFunc::Split_String(TW_SYSTEM_BUILD_PROP_ADDITIONAL_PATHS, ";");
+	build_prop_list.insert(build_prop_list.end(), additional_build_prop_list.begin(), additional_build_prop_list.end());
+#endif
+	while (getline(override_props, current_prop, ';')) {
+		string other_prop;
+		if (current_prop.find("=") != string::npos) {
+			other_prop = current_prop.substr(current_prop.find("=") + 1);
+			current_prop = current_prop.substr(0, current_prop.find("="));
+		} else {
+			other_prop = current_prop;
+		}
+		other_prop = android::base::Trim(other_prop);
+		current_prop = android::base::Trim(current_prop);
+
+		for (auto&& partition_mount_point:partition_list) {
+			for (auto&& prop_file:build_prop_list) {
+				string sys_val = TWFunc::Partition_Property_Get(other_prop, PartitionManager, partition_mount_point.c_str(), prop_file);
+				if (!sys_val.empty()) {
+					if (partition_mount_point == "/system_root") {
+						LOGINFO("Overriding %s with value: \"%s\" from property %s in /system/%s\n", current_prop.c_str(), sys_val.c_str(), other_prop.c_str(),
+							prop_file.c_str());
+					} else {
+						LOGINFO("Overriding %s with value: \"%s\" from property %s in /%s/%s\n", current_prop.c_str(), sys_val.c_str(), other_prop.c_str(),
+							partition_mount_point.c_str(), prop_file.c_str());
+					}
+					int error = TWFunc::Property_Override(current_prop, sys_val);
+					if (error) {
+						LOGERR("Failed overriding property %s, error_code: %d\n", current_prop.c_str(), error);
+					}
+					if (partition_mount_point == partition_list.back()) {
+						PartitionManager.UnMount_By_Path(partition_mount_point, false);
+					}
+					goto exit;
+				} else {
+					if (partition_mount_point == "/system_root") {
+						LOGINFO("Unable to override property %s: property not found in /system/%s\n", current_prop.c_str(), prop_file.c_str());
+					} else {
+						LOGINFO("Unable to override property %s: property not found in /%s/%s\n", current_prop.c_str(), partition_mount_point.c_str(), prop_file.c_str());
+					}
+				}
+			}
+			PartitionManager.UnMount_By_Path(partition_mount_point, false);
+		}
+		exit:
+		continue;
+	}
+#endif
+
+	// Check for and run startup script if script exists
+	TWFunc::check_and_run_script("/system/bin/runatboot.sh", "boot");
+	TWFunc::check_and_run_script("/system/bin/postrecoveryboot.sh", "recovery");
+
+#ifdef TW_INCLUDE_INJECTTWRP
+	// Back up TWRP Ramdisk if needed:
+	TWPartition* Boot = PartitionManager.Find_Partition_By_Path("/boot");
+	LOGINFO("Backing up TWRP ramdisk...\n");
+	if (Boot == NULL || Boot->Current_File_System != "emmc")
+		TWFunc::Exec_Cmd("injecttwrp --backup /tmp/backup_recovery_ramdisk.img");
+	else {
+		string injectcmd = "injecttwrp --backup /tmp/backup_recovery_ramdisk.img bd=" + Boot->Actual_Block_Device;
+		TWFunc::Exec_Cmd(injectcmd);
+	}
+	LOGINFO("Backup of TWRP ramdisk done.\n");
+#endif
+#ifdef TW_INCLUDE_CRYPTO
+	android::keystore::copySqliteDb();
+#endif
+	Decrypt_Page(skip_decryption, datamedia);
+
+	// Check for and load custom theme if present
+	TWFunc::check_selinux_support();
+	gui_loadCustomResources();
+	PartitionManager.Output_Partition_Logging();
+
+	// Fixup the RTC clock on devices which require it
+	if (crash_counter == 0)
+		TWFunc::Fixup_Time_On_Boot();
+
+	DataManager::LoadTWRPFolderInfo();
+	DataManager::ReadSettingsFile();
+
+	// Run any outstanding OpenRecoveryScript
+	std::string cacheDir = TWFunc::get_log_dir();
+	if (cacheDir == DATA_LOGS_DIR)
+		cacheDir = "/data/cache";
+	std::string orsFile = cacheDir + "/recovery/openrecoveryscript";
+	if ((DataManager::GetIntValue(TW_IS_ENCRYPTED) == 0 || skip_decryption) && (TWFunc::Path_Exists(SCRIPT_FILE_TMP) || TWFunc::Path_Exists(orsFile))) {
+		OpenRecoveryScript::Run_OpenRecoveryScript();
+	}
+
+#ifdef TW_HAS_MTP
+	char mtp_crash_check[PROPERTY_VALUE_MAX];
+	property_get("mtp.crash_check", mtp_crash_check, "0");
+	if (DataManager::GetIntValue("tw_mtp_enabled")
+			&& !strcmp(mtp_crash_check, "0") && !crash_counter
+			&& (!DataManager::GetIntValue(TW_IS_ENCRYPTED) || DataManager::GetIntValue(TW_IS_DECRYPTED))) {
+		property_set("mtp.crash_check", "1");
+		LOGINFO("Starting MTP\n");
+		if (!PartitionManager.Enable_MTP())
+			PartitionManager.Disable_MTP();
+		else
+			gui_msg("mtp_enabled=MTP Enabled");
+		property_set("mtp.crash_check", "0");
+	} else if (strcmp(mtp_crash_check, "0")) {
+		gui_warn("mtp_crash=MTP Crashed, not starting MTP on boot.");
+		DataManager::SetValue("tw_mtp_enabled", 0);
+		PartitionManager.Disable_MTP();
+	} else if (crash_counter == 1) {
+		LOGINFO("TWRP crashed; disabling MTP as a precaution.\n");
+		PartitionManager.Disable_MTP();
+	}
+#endif
+
+#ifndef TW_OEM_BUILD
+	// Check if system has never been changed
+	TWPartition* sys = PartitionManager.Find_Partition_By_Path(PartitionManager.Get_Android_Root_Path());
+	TWPartition* ven = PartitionManager.Find_Partition_By_Path("/vendor");
+	if (sys) {
+		if (sys->Get_Super_Status()) {
+#ifdef TW_INCLUDE_CRYPTO
+			std::string recoveryLogDir(DATA_LOGS_DIR);
+			recoveryLogDir += "/recovery";
+			if (!TWFunc::Path_Exists(recoveryLogDir)) {
+				bool created = PartitionManager.Recreate_Logs_Dir();
+				if (!created)
+					LOGERR("Unable to create log directory for TWRP\n");
+			}
+			DataManager::ReadSettingsFile();
+#endif
+		} else {
+			if ((DataManager::GetIntValue("tw_mount_system_ro") == 0 && sys->Check_Lifetime_Writes() == 0) || DataManager::GetIntValue("tw_mount_system_ro") == 2) {
+				if (DataManager::GetIntValue("tw_never_show_system_ro_page") == 0) {
+					DataManager::SetValue("tw_back", "main");
+					if (gui_startPage("system_readonly", 1, 1) != 0) {
+						LOGERR("Failed to start system_readonly GUI page.\n");
+					}
+				} else if (DataManager::GetIntValue("tw_mount_system_ro") == 0) {
+					sys->Change_Mount_Read_Only(false);
+					if (ven)
+						ven->Change_Mount_Read_Only(false);
+				}
+			} else if (DataManager::GetIntValue("tw_mount_system_ro") == 1) {
+				// Do nothing, user selected to leave system read only
+			} else {
+				sys->Change_Mount_Read_Only(false);
+				if (ven)
+					ven->Change_Mount_Read_Only(false);
+			}
+		}
+	}
+#endif
+
+	TWFunc::Update_Log_File();
+
+	adb_bu_fifo->threadAdbBuFifo();
+
+#ifndef TW_OEM_BUILD
+	// Disable flashing of stock recovery
+	TWFunc::Disable_Stock_Recovery_Replace();
+#endif
+}
+
+static void reboot() {
+	gui_msg(Msg("rebooting=Rebooting..."));
+	TWFunc::Update_Log_File();
+	string Reboot_Arg;
+	DataManager::GetValue("tw_reboot_arg", Reboot_Arg);
+	if (Reboot_Arg == "recovery")
+		TWFunc::tw_reboot(rb_recovery);
+	else if (Reboot_Arg == "poweroff")
+		TWFunc::tw_reboot(rb_poweroff);
+	else if (Reboot_Arg == "bootloader")
+		TWFunc::tw_reboot(rb_bootloader);
+	else if (Reboot_Arg == "download")
+		TWFunc::tw_reboot(rb_download);
+	else if (Reboot_Arg == "edl")
+		TWFunc::tw_reboot(rb_edl);
+	else if (Reboot_Arg == "fastboot")
+		TWFunc::tw_reboot(rb_fastboot);
+	else
+		TWFunc::tw_reboot(rb_system);
+}
+
+int main(int argc, char **argv) {
+	// Recovery needs to install world-readable files, so clear umask
+	// set by init
+	umask(0);
+	Log_Offset = 0;
+
+	// Set up temporary log file (/tmp/recovery.log)
+	freopen(TMP_LOG_FILE, "a", stdout);
+	setbuf(stdout, NULL);
+	freopen(TMP_LOG_FILE, "a", stderr);
+	setbuf(stderr, NULL);
+
+	signal(SIGPIPE, SIG_IGN);
+
+	// Handle ADB sideload
+	if (argc == 3 && strcmp(argv[1], "--adbd") == 0) {
+		property_set("ctl.stop", "adbd");
+#ifdef TW_USE_NEW_MINADBD
+		//adb_server_main(0, DEFAULT_ADB_PORT, -1); TODO fix this for android8
+		// minadbd_main();
+#else
+		adb_main(argv[2]);
+#endif
+		return 0;
+	}
+
+#ifdef RECOVERY_SDCARD_ON_DATA
+	datamedia = true;
+#endif
+
+	property_set("ro.twrp.boot", "1");
+	property_set("ro.twrp.version", TW_VERSION_STR);
+
+#ifdef TARGET_OTA_ASSERT_DEVICE
+	property_set("ro.twrp.target.devices", TARGET_OTA_ASSERT_DEVICE);
+#endif
+
+	time_t StartupTime = time(NULL);
+	printf("Starting TWRP %s-%s on %s (pid %d)\n", TW_VERSION_STR, TW_GIT_REVISION, ctime(&StartupTime), getpid());
+
+	// Load default values to set DataManager constants and handle ifdefs
+	DataManager::SetDefaultValues();
+	printf("Starting the UI...\n");
+	gui_init();
+
+	// Load up all the resources
+	gui_loadResources();
+
+	startupArgs startup;
+	startup.parse(&argc, &argv);
+	twrpAdbBuFifo *adb_bu_fifo = new twrpAdbBuFifo();
+	TWFunc::Clear_Bootloader_Message();
+
+	if (startup.Get_Fastboot_Mode()) {
+		process_fastbootd_mode();
+		delete adb_bu_fifo;
+		TWFunc::Update_Intent_File(startup.Get_Intent());
+		reboot();
+		return 0;
+	} else {
+		process_recovery_mode(adb_bu_fifo, startup.Should_Skip_Decryption());
+	}
+
+	PageManager::LoadLanguage(DataManager::GetStrValue("tw_language"));
+	GUIConsole::Translate_Now();
+
+	TWFunc::checkforapp(); //Checking compatibility for TWRP app
+
+	// Launch the main GUI
+	gui_start();
+	delete adb_bu_fifo;
+	TWFunc::Update_Intent_File(startup.Get_Intent());
+	reboot();
+
+	return 0;
+}
diff --git a/twrpAdbBuFifo.cpp b/twrpAdbBuFifo.cpp
new file mode 100755
index 0000000..3143863
--- /dev/null
+++ b/twrpAdbBuFifo.cpp
@@ -0,0 +1,349 @@
+/*
+        Copyright 2013 to 2017 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define __STDC_FORMAT_MACROS 1
+#include <string>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <pthread.h>
+#include <zlib.h>
+#include <inttypes.h>
+#include "twrpAdbBuFifo.hpp"
+#include "twcommon.h"
+#include "data.hpp"
+#include "variables.h"
+#include "partitions.hpp"
+#include "twrp-functions.hpp"
+#include "gui/gui.hpp"
+#include "gui/objects.hpp"
+#include "gui/pages.hpp"
+#include "adbbu/twadbstream.h"
+#include "adbbu/libtwadbbu.hpp"
+
+twrpAdbBuFifo::twrpAdbBuFifo(void) {
+	unlink(TW_ADB_FIFO);
+}
+
+void twrpAdbBuFifo::Check_Adb_Fifo_For_Events(void) {
+	char cmd[512];
+
+	memset(&cmd, 0, sizeof(cmd));
+
+	if (read(adb_fifo_fd, &cmd, sizeof(cmd)) > 0) {
+		LOGINFO("adb backup cmd: %s\n", cmd);
+		std::string cmdcheck(cmd);
+		cmdcheck = cmdcheck.substr(0, strlen(ADB_BACKUP_OP));
+		std::string Options(cmd);
+		Options = Options.substr(strlen(ADB_BACKUP_OP) + 1, strlen(cmd));
+		if (cmdcheck == ADB_BACKUP_OP)
+			Backup_ADB_Command(Options);
+		else {
+			Restore_ADB_Backup();
+		}
+	}
+}
+
+bool twrpAdbBuFifo::start(void) {
+	LOGINFO("Starting Adb Backup FIFO\n");
+	unlink(TW_ADB_FIFO);
+	if (mkfifo(TW_ADB_FIFO, 06660) != 0) {
+		LOGINFO("Unable to mkfifo %s\n", TW_ADB_FIFO);
+		return false;
+	}
+	adb_fifo_fd = open(TW_ADB_FIFO, O_RDONLY | O_NONBLOCK);
+	if (adb_fifo_fd < 0) {
+		LOGERR("Unable to open TW_ADB_FIFO for reading.\n");
+		close(adb_fifo_fd);
+		return false;
+	}
+	while (true) {
+		Check_Adb_Fifo_For_Events();
+		usleep(8000);
+	}
+	//Shouldn't get here but cleanup anwyay
+	close(adb_fifo_fd);
+	return true;
+}
+
+pthread_t twrpAdbBuFifo::threadAdbBuFifo(void) {
+	pthread_t thread;
+	ThreadPtr adbfifo = &twrpAdbBuFifo::start;
+	PThreadPtr p = *(PThreadPtr*)&adbfifo;
+	pthread_create(&thread, NULL, p, this);
+	return thread;
+}
+
+bool twrpAdbBuFifo::Backup_ADB_Command(std::string Options) {
+	std::vector<std::string> args;
+	std::string Backup_List;
+	bool adbbackup = true, ret = false;
+	std::string rmopt = "--";
+
+	std::replace(Options.begin(), Options.end(), ':', ' ');
+	args = TWFunc::Split_String(Options, " ");
+
+	DataManager::SetValue(TW_USE_COMPRESSION_VAR, 0);
+	DataManager::SetValue(TW_SKIP_DIGEST_GENERATE_VAR, 0);
+
+	if (args[1].compare("--twrp") != 0) {
+		gui_err("twrp_adbbu_option=--twrp option is required to enable twrp adb backup");
+		if (!twadbbu::Write_TWERROR())
+			LOGERR("Unable to write to ADB Backup\n");
+		sleep(2);
+		return false;
+	}
+
+	for (unsigned i = 2; i < args.size(); i++) {
+		int compress;
+
+		std::string::size_type size = args[i].find(rmopt);
+		if (size != std::string::npos)
+			args[i].erase(size, rmopt.length());
+
+		if (args[i].compare("compress") == 0) {
+			gui_msg("compression_on=Compression is on");
+			DataManager::SetValue(TW_USE_COMPRESSION_VAR, 1);
+			continue;
+		}
+		DataManager::GetValue(TW_USE_COMPRESSION_VAR, compress);
+		gui_print("%s\n", args[i].c_str());
+		std::string path;
+		path = "/" + args[i];
+		TWPartition* part = PartitionManager.Find_Partition_By_Path(path);
+		if (part) {
+			Backup_List += path;
+			Backup_List += ";";
+		}
+		else {
+			gui_msg(Msg(msg::kError, "partition_not_found=path: {1} not found in partition list")(path));
+			if (!twadbbu::Write_TWERROR())
+				LOGERR("Unable to write to TWRP ADB Backup.\n");
+		return false;
+	}
+}
+
+	if (Backup_List.empty()) {
+		DataManager::GetValue("tw_backup_list", Backup_List);
+		if (Backup_List.empty()) {
+			gui_err("no_partition_selected=No partitions selected for backup.");
+			return false;
+		}
+	}
+	else
+		DataManager::SetValue("tw_backup_list", Backup_List);
+
+	DataManager::SetValue("tw_action", "clear");
+	DataManager::SetValue("tw_action_text1", gui_lookup("running_recovery_commands", "Running Recovery Commands"));
+	DataManager::SetValue("tw_action_text2", "");
+	gui_changePage("action_page");
+
+	ret = PartitionManager.Run_Backup(adbbackup);
+	DataManager::SetValue(TW_BACKUP_NAME, gui_lookup("auto_generate", "(Auto Generate)"));
+	if (!ret) {
+		gui_err("backup_fail=Backup failed");
+		return false;
+	}
+	gui_msg("backup_complete=Backup Complete");
+	DataManager::SetValue("ui_progress", 100);
+	sleep(2); //give time for user to see messages on console
+	gui_changePage("main");
+	return true;
+}
+
+bool twrpAdbBuFifo::Restore_ADB_Backup(void) {
+	int partition_count = 0;
+	std::string Restore_Name;
+	struct AdbBackupFileTrailer adbmd5;
+	struct PartitionSettings part_settings;
+	int adb_control_twrp_fd;
+	int adb_control_bu_fd, ret = 0;
+	char cmd[512];
+
+	part_settings.total_restore_size = 0;
+
+	PartitionManager.Mount_All_Storage();
+	LOGINFO("opening TW_ADB_BU_CONTROL\n");
+	adb_control_bu_fd = open(TW_ADB_BU_CONTROL, O_WRONLY | O_NONBLOCK);
+	LOGINFO("opening TW_ADB_TWRP_CONTROL\n");
+	adb_control_twrp_fd = open(TW_ADB_TWRP_CONTROL, O_RDONLY | O_NONBLOCK);
+	memset(&adbmd5, 0, sizeof(adbmd5));
+
+	DataManager::SetValue("tw_action", "clear");
+	DataManager::SetValue("tw_action_text1", gui_lookup("running_recovery_commands", "Running Recovery Commands"));
+	DataManager::SetValue("tw_action_text2", "");
+	gui_changePage("action_page");
+
+	while (true) {
+		memset(&cmd, 0, sizeof(cmd));
+		if (read(adb_control_twrp_fd, cmd, sizeof(cmd)) > 0) {
+			struct AdbBackupControlType cmdstruct;
+
+			memset(&cmdstruct, 0, sizeof(cmdstruct));
+			memcpy(&cmdstruct, cmd, sizeof(cmdstruct));
+			std::string cmdtype = cmdstruct.get_type();
+			if (cmdtype == TWSTREAMHDR) {
+				struct AdbBackupStreamHeader twhdr;
+				memcpy(&twhdr, cmd, sizeof(cmd));
+				LOGINFO("ADB Partition count: %" PRIu64 "\n", twhdr.partition_count);
+				LOGINFO("ADB version: %" PRIu64 "\n", twhdr.version);
+				if (twhdr.version != ADB_BACKUP_VERSION) {
+					LOGERR("Incompatible adb backup version!\n");
+					ret = false;
+					break;
+				}
+				partition_count = twhdr.partition_count;
+			}
+			else if (cmdtype == MD5TRAILER) {
+				LOGINFO("Reading ADB Backup MD5TRAILER\n");
+				memcpy(&adbmd5, cmd, sizeof(cmd));
+			}
+			else if (cmdtype == TWMD5) {
+				int check_digest;
+
+				DataManager::GetValue(TW_SKIP_DIGEST_CHECK_VAR, check_digest);
+				if (check_digest > 0) {
+					TWFunc::GUI_Operation_Text(TW_VERIFY_DIGEST_TEXT, gui_parse_text("{@verifying_digest}"));
+					gui_msg("verifying_digest=Verifying Digest");
+					struct AdbBackupFileTrailer md5check;
+					LOGINFO("Verifying md5sums\n");
+
+					memset(&md5check, 0, sizeof(md5check));
+					memcpy(&md5check, cmd, sizeof(cmd));
+					if (strcmp(md5check.md5, adbmd5.md5) != 0) {
+						LOGERR("md5 doesn't match!\n");
+						LOGERR("Stored file md5: %s\n", adbmd5.md5);
+						LOGERR("ADB Backup check md5: %s\n", md5check.md5);
+						ret = false;
+						break;
+					}
+					else {
+						LOGINFO("ADB Backup md5 matches\n");
+						LOGINFO("Stored file md5: %s\n", adbmd5.md5);
+						LOGINFO("ADB Backup check md5: %s\n", md5check.md5);
+						continue;
+					}
+				} else {
+					gui_msg("skip_digest=Skipping Digest check based on user setting.");
+					continue;
+				}
+
+			}
+			else if (cmdtype == TWENDADB) {
+				LOGINFO("received TWENDADB\n");
+				ret = 1;
+				break;
+			}
+			else {
+				struct twfilehdr twimghdr;
+				memcpy(&twimghdr, cmd, sizeof(cmd));
+				std::string cmdstr(twimghdr.type);
+				Restore_Name = twimghdr.name;
+				part_settings.total_restore_size = twimghdr.size;
+				if (cmdtype == TWIMG) {
+					LOGINFO("ADB Type: %s\n", twimghdr.type);
+					LOGINFO("ADB Restore_Name: %s\n", Restore_Name.c_str());
+					LOGINFO("ADB Restore_size: %" PRIu64 "\n", part_settings.total_restore_size);
+					string compression = (twimghdr.compressed == 1) ? "compressed" : "uncompressed";
+					LOGINFO("ADB compression: %s\n", compression.c_str());
+					std::string Backup_FileName;
+					std::size_t pos = Restore_Name.find_last_of("/");
+					std::string path = "/" + Restore_Name.substr(pos, Restore_Name.size());
+					pos = path.find_first_of(".");
+					path = path.substr(0, pos);
+					if (path.substr(0,1).compare("//")) {
+						path = path.substr(1, path.size());
+					}
+
+					pos = Restore_Name.find_last_of("/");
+					Backup_FileName = Restore_Name.substr(pos + 1, Restore_Name.size());
+					part_settings.Part = PartitionManager.Find_Partition_By_Path(path);
+					part_settings.Backup_Folder = path;
+					part_settings.partition_count = partition_count;
+					part_settings.adbbackup = true;
+					part_settings.adb_compression = twimghdr.compressed;
+					part_settings.PM_Method = PM_RESTORE;
+					ProgressTracking progress(part_settings.total_restore_size);
+					part_settings.progress = &progress;
+					if (!PartitionManager.Restore_Partition(&part_settings)) {
+						LOGERR("ADB Restore failed.\n");
+						ret = false;
+						break;
+					}
+				}
+				else if (cmdtype == TWFN) {
+					LOGINFO("ADB Type: %s\n", twimghdr.type);
+					LOGINFO("ADB Restore_Name: %s\n", Restore_Name.c_str());
+					LOGINFO("ADB Restore_size: %" PRIi64 "\n", part_settings.total_restore_size);
+					string compression = (twimghdr.compressed == 1) ? "compressed" : "uncompressed";
+					LOGINFO("ADB compression: %s\n", compression.c_str());
+					std::string Backup_FileName;
+					std::size_t pos = Restore_Name.find_last_of("/");
+					std::string path = "/" + Restore_Name.substr(pos, Restore_Name.size());
+					pos = path.find_first_of(".");
+					path = path.substr(0, pos);
+					if (path.substr(0,1).compare("//")) {
+						path = path.substr(1, path.size());
+					}
+
+					pos = Restore_Name.find_last_of("/");
+					Backup_FileName = Restore_Name.substr(pos + 1, Restore_Name.size());
+					pos = Restore_Name.find_last_of("/");
+					part_settings.Part = PartitionManager.Find_Partition_By_Path(path);
+					part_settings.Part->Set_Backup_FileName(Backup_FileName);
+					PartitionManager.Set_Restore_Files(path);
+
+					if (path.compare(PartitionManager.Get_Android_Root_Path()) == 0) {
+						if (part_settings.Part->Is_Read_Only()) {
+							if (!twadbbu::Write_TWERROR())
+								LOGERR("Unable to write to TWRP ADB Backup.\n");
+							gui_msg(Msg(msg::kError, "restore_read_only=Cannot restore {1} -- mounted read only.")(part_settings.Part->Backup_Display_Name));
+							ret = false;
+							break;
+
+						}
+					}
+					part_settings.partition_count = partition_count;
+					part_settings.adbbackup = true;
+					part_settings.adb_compression = twimghdr.compressed;
+					part_settings.total_restore_size += part_settings.Part->Get_Restore_Size(&part_settings);
+					part_settings.PM_Method = PM_RESTORE;
+					ProgressTracking progress(part_settings.total_restore_size);
+					part_settings.progress = &progress;
+					if (!PartitionManager.Restore_Partition(&part_settings)) {
+						LOGERR("ADB Restore failed.\n");
+						ret = false;
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	if (ret != false)
+		gui_msg("restore_complete=Restore Complete");
+	else
+		gui_err("restore_error=Error during restore process.");
+
+	if (!twadbbu::Write_TWENDADB())
+		ret = false;
+	sleep(2); //give time for user to see messages on console
+	DataManager::SetValue("ui_progress", 100);
+	gui_changePage("main");
+	close(adb_control_bu_fd);
+	return ret;
+}
diff --git a/twrpAdbBuFifo.hpp b/twrpAdbBuFifo.hpp
new file mode 100644
index 0000000..b34c77b
--- /dev/null
+++ b/twrpAdbBuFifo.hpp
@@ -0,0 +1,40 @@
+/*
+        Copyright 2013 to 2017 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TWRPADBFIFO_HPP
+#define TWRPADBFIFO_HPP
+
+#include <string>
+#include <pthread.h>
+
+#define TW_ADB_FIFO "/tmp/twadbfifo"
+
+class twrpAdbBuFifo {
+	public:
+		twrpAdbBuFifo(void);
+		pthread_t threadAdbBuFifo(void);
+	private:
+		bool start(void);
+		bool Backup_ADB_Command(std::string Options);
+		void Check_Adb_Fifo_For_Events(void);
+		bool Restore_ADB_Backup(void);
+		typedef bool (twrpAdbBuFifo::*ThreadPtr)(void);
+		typedef void* (*PThreadPtr)(void *);
+		int adb_fifo_fd;
+};
+#endif
diff --git a/twrpApex.cpp b/twrpApex.cpp
new file mode 100755
index 0000000..22da17a
--- /dev/null
+++ b/twrpApex.cpp
@@ -0,0 +1,194 @@
+#include "twrpApex.hpp"
+#include "twrp-functions.hpp"
+#include "common.h"
+
+namespace fs = std::filesystem;
+
+bool twrpApex::loadApexImages() {
+	std::vector<std::string> apexFiles;
+	std::vector<std::string> checkApexFlatFiles;
+#ifdef TW_ADDITIONAL_APEX_FILES
+	char* additionalFiles = strdup(EXPAND(TW_ADDITIONAL_APEX_FILES));
+	char* additionalApexFiles = std::strtok(additionalFiles, " ");
+#endif
+
+	apexFiles.push_back(APEX_DIR "/com.android.apex.cts.shim.apex");
+	apexFiles.push_back(APEX_DIR "/com.google.android.tzdata2.apex");
+	apexFiles.push_back(APEX_DIR "/com.android.tzdata.apex");
+	apexFiles.push_back(APEX_DIR "/com.android.art.release.apex");
+	apexFiles.push_back(APEX_DIR "/com.google.android.media.swcodec.apex");
+	apexFiles.push_back(APEX_DIR "/com.android.media.swcodec.apex");
+
+#ifdef TW_ADDITIONAL_APEX_FILES
+	while(additionalApexFiles) {
+		std::stringstream apexFile;
+		apexFile << APEX_DIR << "/" << additionalApexFiles;
+		apexFiles.push_back(apexFile.str());
+		additionalApexFiles = std::strtok(nullptr, " ");
+	}
+#endif
+
+	if (access(APEX_DIR, F_OK) != 0) {
+		LOGERR("Unable to open %s\n", APEX_DIR);
+		return false;
+	}
+	for (const auto& entry : fs::directory_iterator(APEX_DIR)) {
+	   if (entry.is_regular_file()) {
+		   checkApexFlatFiles.push_back(entry.path().string());
+	   }
+	}
+
+	if (checkApexFlatFiles.size() == 0) {
+		// flattened apex directory
+		LOGINFO("Bind mounting flattened apex directory\n");
+		if (mount(APEX_DIR, APEX_BASE, "", MS_BIND, NULL) < 0) {
+			LOGERR("Unable to bind mount flattened apex directory\n");
+			return false;
+		}
+		android::base::SetProperty("twrp.apex.flattened", "true");
+		return true;
+	}
+	if (!mountApexOnLoopbackDevices(apexFiles)) {
+		LOGERR("Unable to create loop devices to mount apex files\n");
+		return false;
+	}
+
+	return true;
+}
+
+std::string twrpApex::unzipImage(std::string file) {
+	ZipArchiveHandle handle;
+	int32_t ret = OpenArchive(file.c_str(), &handle);
+	if (ret != 0) {
+		LOGINFO("unable to open zip archive %s. Reason: %s\n", file.c_str(), strerror(errno));
+		return std::string();
+	}
+
+	ZipEntry entry;
+	std::string zip_string(APEX_PAYLOAD);
+	ret = FindEntry(handle, zip_string, &entry);
+	if (ret != 0) {
+		LOGERR("unable to find %s in zip\n", APEX_PAYLOAD);
+		CloseArchive(handle);
+		return std::string();
+	}
+
+	std::string baseFile = basename(file.c_str());
+	std::string path("/tmp/");
+	path = path + baseFile;
+	int fd = open(path.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0666);
+	ret = ExtractEntryToFile(handle, &entry, fd);
+	if (ret != 0) {
+		LOGERR("unable to extract %s\n", path.c_str());
+		close(fd);
+		CloseArchive(handle);
+		return std::string();
+	}
+
+	CloseArchive(handle);
+	close(fd);
+	return path;
+}
+
+bool twrpApex::mountApexOnLoopbackDevices(std::vector<std::string> apexFiles) {
+	int fd = open(LOOP_CONTROL, O_RDWR | O_CLOEXEC);
+	if (fd < 0) {
+		LOGERR("Unable to open %s device. Reason: %s\n", LOOP_CONTROL, strerror(errno));
+		return false;
+	}
+
+	size_t device_no = 0;
+	for (auto&& apexFile:apexFiles) {
+		int num = ioctl(fd, LOOP_CTL_GET_FREE);
+		std::string loop_device = LOOP_BLOCK_DEVICE_DIR;
+		loop_device = loop_device + "loop" + std::to_string(num);
+		if (!TWFunc::Path_Exists(loop_device)) {
+			int ret = mknod(loop_device.c_str(), S_IFBLK | S_IRUSR | S_IWUSR , makedev(7, device_no));
+			if (ret != 0) {
+				LOGERR("Unable to create loop device: %s\n", loop_device.c_str());
+				return false;
+			}
+		}
+		std::string fileToMount = unzipImage(apexFile);
+		if (fileToMount.empty()) {
+			LOGINFO("Skipping non-existent apex file: %s\n", apexFile.c_str());
+			continue;
+		}
+		bool load_result = loadApexImage(fileToMount, device_no);
+		if (!load_result) {
+			return false;
+		}
+		device_no++;
+	}
+	return true;
+}
+
+bool twrpApex::loadApexImage(std::string fileToMount, size_t loop_device_number) {
+	struct loop_info64 info;
+
+	int fd = open(fileToMount.c_str(), O_RDONLY | O_CLOEXEC);
+	if (fd < 0) {
+		LOGERR("unable to open apex file: %s. Reason: %s\n", fileToMount.c_str(), strerror(errno));
+		return false;
+	}
+
+	std::string loop_device = "/dev/block/loop" + std::to_string(loop_device_number);
+	int loop_fd = open(loop_device.c_str(), O_RDONLY);
+	if (loop_fd < 0) {
+		LOGERR("unable to open loop device: %s\n", loop_device.c_str());
+		close(fd);
+		return false;
+	}
+
+	if (ioctl(loop_fd, LOOP_SET_FD, fd) < 0) {
+		LOGERR("failed to mount %s to loop device %s. Reason: %s\n", fileToMount.c_str(), loop_device.c_str(), 
+			strerror(errno));
+		close(fd);
+		close(loop_fd);
+		return false;
+	}
+
+	close(fd);
+
+	memset(&info, 0, sizeof(struct loop_info64));
+	strlcpy((char*)info.lo_crypt_name, "twrpApex", LO_NAME_SIZE);
+	off_t apex_size = lseek(fd, 0, SEEK_END);
+	info.lo_sizelimit = apex_size;
+	if (ioctl(loop_fd, LOOP_SET_STATUS64, &info)) {
+		LOGERR("failed to mount loop: %s: %s\n", fileToMount.c_str(), strerror(errno));
+		close(loop_fd);
+		return false;
+	}
+	if (ioctl(loop_fd, BLKFLSBUF, 0) == -1) {
+		LOGERR("Unable to flush loop device buffers\n");
+		return false;
+	}
+	if (ioctl(loop_fd, LOOP_SET_BLOCK_SIZE, 4096) == -1) {
+		LOGINFO("Failed to set DIRECT_IO buffer size\n");
+	}
+	close(loop_fd);
+
+	std::string bind_mount(APEX_BASE);
+	std::string apex_cleaned_mount = fileToMount;
+	apex_cleaned_mount = std::regex_replace(apex_cleaned_mount, std::regex("\\.apex"), "");
+
+	bind_mount = bind_mount + basename(apex_cleaned_mount.c_str());
+
+	int ret = mkdir(bind_mount.c_str(), 0666);
+	if (ret != 0) {
+		LOGERR("Unable to create bind mount directory: %s\n", bind_mount.c_str());
+		return false;
+	}
+
+	ret = mount(loop_device.c_str(), bind_mount.c_str(), "ext4", MS_RDONLY, nullptr);
+	if (ret != 0) {
+		LOGERR("unable to mount loop device %s to %s. Reason: %s\n", loop_device.c_str(), bind_mount.c_str(), strerror(errno));
+		return false;
+	}
+
+	return true;
+}
+
+bool twrpApex::Unmount() {
+	return (umount2(APEX_BASE, MNT_DETACH) == 0);
+}
diff --git a/twrpApex.hpp b/twrpApex.hpp
new file mode 100755
index 0000000..b3809dd
--- /dev/null
+++ b/twrpApex.hpp
@@ -0,0 +1,40 @@
+
+#ifndef TWRPAPEX_HPP
+#define TWRPAPEX_HPP
+
+#include <string>
+#include <vector>
+#include <filesystem>
+#include <regex>
+#include <sstream>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/loop.h>
+#include <sys/mount.h>
+#include <sys/sysmacros.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <android-base/properties.h>
+
+#include <ziparchive/zip_archive.h>
+#include "twcommon.h"
+
+#define APEX_DIR "/system_root/system/apex"
+#define APEX_PAYLOAD "apex_payload.img"
+#define LOOP_BLOCK_DEVICE_DIR "/dev/block/"
+#define APEX_BASE "/apex/"
+#define LOOP_CONTROL "/dev/loop-control"
+
+class twrpApex {
+public:
+	bool loadApexImages();
+	bool Unmount();
+
+private:
+	std::string unzipImage(std::string file);
+	bool mountApexOnLoopbackDevices(std::vector<std::string> apexFiles);
+	bool loadApexImage(std::string fileToMount, size_t loop_device_number);
+};
+#endif
diff --git a/twrpDigest/Android.mk b/twrpDigest/Android.mk
new file mode 100644
index 0000000..5e69822
--- /dev/null
+++ b/twrpDigest/Android.mk
@@ -0,0 +1,29 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libtwrpdigest
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS = -fno-strict-aliasing
+LOCAL_C_INCLUDES := external/openssl/include bionic
+
+LOCAL_SRC_FILES = \
+        twrpDigest.cpp \
+        twrpMD5.cpp \
+        digest/md5/md5.c
+
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
+        LOCAL_C_INCLUDES += external/stlport/stlport
+endif
+
+LOCAL_SHARED_LIBRARIES += libc libstdc++
+
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
+        LOCAL_SHARED_LIBRARIES += libstlport
+else
+        LOCAL_SHARED_LIBRARIES += libc++ libcrypto
+	LOCAL_SRC_FILES += \
+        	twrpSHA.cpp
+endif
+
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/twrpDigest/digest/md5/md5.c b/twrpDigest/digest/md5/md5.c
new file mode 100644
index 0000000..488d16e
--- /dev/null
+++ b/twrpDigest/digest/md5/md5.c
@@ -0,0 +1,257 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+#include <string.h>		/* for memcpy() */
+
+#include "md5.h"
+
+#if !defined(WORDS_BIGENDIAN)
+#define byteReverse(buf, len)	/* Nothing */
+#else
+void byteReverse(unsigned char *buf, unsigned longs);
+
+#ifndef ASM_MD5
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+void byteReverse(unsigned char *buf, unsigned longs)
+{
+    uint32_t t;
+    do {
+	t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+	    ((unsigned) buf[1] << 8 | buf[0]);
+	*(uint32_t *) buf = t;
+	buf += 4;
+    } while (--longs);
+}
+#endif
+#endif
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void MD5Init(struct MD5Context *ctx)
+{
+    ctx->buf[0] = 0x67452301;
+    ctx->buf[1] = 0xefcdab89;
+    ctx->buf[2] = 0x98badcfe;
+    ctx->buf[3] = 0x10325476;
+
+    ctx->bits[0] = 0;
+    ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len)
+{
+    uint32_t t;
+
+    /* Update bitcount */
+
+    t = ctx->bits[0];
+    if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+	ctx->bits[1]++;		/* Carry from low to high */
+    ctx->bits[1] += len >> 29;
+
+    t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */
+
+    /* Handle any leading odd-sized chunks */
+
+    if (t) {
+	unsigned char *p = (unsigned char *) ctx->in + t;
+
+	t = 64 - t;
+	if (len < t) {
+	    memcpy(p, buf, len);
+	    return;
+	}
+	memcpy(p, buf, t);
+	byteReverse(ctx->in, 16);
+	MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+	buf += t;
+	len -= t;
+    }
+    /* Process data in 64-byte chunks */
+
+    while (len >= 64) {
+	memcpy(ctx->in, buf, 64);
+	byteReverse(ctx->in, 16);
+	MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+	buf += 64;
+	len -= 64;
+    }
+
+    /* Handle any remaining bytes of data. */
+
+    memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern 
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void MD5Final(unsigned char digest[MD5LENGTH], struct MD5Context *ctx)
+{
+    unsigned count;
+    unsigned char *p;
+
+    /* Compute number of bytes mod 64 */
+    count = (ctx->bits[0] >> 3) & 0x3F;
+
+    /* Set the first char of padding to 0x80.  This is safe since there is
+       always at least one byte free */
+    p = ctx->in + count;
+    *p++ = 0x80;
+
+    /* Bytes of padding needed to make 64 bytes */
+    count = 64 - 1 - count;
+
+    /* Pad out to 56 mod 64 */
+    if (count < 8) {
+	/* Two lots of padding:  Pad the first block to 64 bytes */
+	memset(p, 0, count);
+	byteReverse(ctx->in, 16);
+	MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+
+	/* Now fill the next block with 56 bytes */
+	memset(ctx->in, 0, 56);
+    } else {
+	/* Pad block to 56 bytes */
+	memset(p, 0, count - 8);
+    }
+    byteReverse(ctx->in, 14);
+
+    /* Append length in bits and transform.
+     * Use memcpy to avoid aliasing problems.  On most systems,
+     * this will be optimized away to the same code.
+     */
+    memcpy(&ctx->in[14 * sizeof(uint32_t)], &ctx->bits[0], 4);
+    memcpy(&ctx->in[15 * sizeof(uint32_t)], &ctx->bits[1], 4);
+
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    byteReverse((unsigned char *) ctx->buf, 4);
+    memcpy(digest, ctx->buf, MD5LENGTH);
+    memset(ctx, 0, sizeof(*ctx));	/* In case it's sensitive */
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+	( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void MD5Transform(uint32_t buf[4], uint32_t const in[16])
+{
+    register uint32_t a, b, c, d;
+
+    a = buf[0];
+    b = buf[1];
+    c = buf[2];
+    d = buf[3];
+
+    MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+    MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+    MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+    MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+    MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+    MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+    MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+    MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+    MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+    MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+    MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+    MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+    MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+    MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+    MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+    MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+    MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+    MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+    MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+    MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+    MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+    MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+    MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+    MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+    MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+    MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+    MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+    MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+    MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+    MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+    MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+    MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+    MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+    MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+    MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+    MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+    MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+    MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+    MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+    MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+    MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+    MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+    MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+    MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+    MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+    MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+    MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+    MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+    MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+    MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+    MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+    MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+    MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+    MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+    MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+    MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+    MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+    MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+    MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+    MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+    MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+    MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+    MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+    MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+    buf[0] += a;
+    buf[1] += b;
+    buf[2] += c;
+    buf[3] += d;
+}
+
+#endif
+
diff --git a/twrpDigest/digest/md5/md5.h b/twrpDigest/digest/md5/md5.h
new file mode 100644
index 0000000..cf30688
--- /dev/null
+++ b/twrpDigest/digest/md5/md5.h
@@ -0,0 +1,29 @@
+#ifndef MD5_H
+#define MD5_H
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#else
+typedef unsigned int uint32_t;
+#endif
+
+#define MD5LENGTH 16
+
+struct MD5Context {
+	uint32_t buf[4];
+	uint32_t bits[2];
+	unsigned char in[64];
+};
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, unsigned char const *buf,
+	       unsigned len);
+void MD5Final(unsigned char digest[MD5LENGTH], struct MD5Context *context);
+void MD5Transform(uint32_t buf[4], uint32_t const in[16]);
+
+/*
+ * This is needed to make RSAREF happy on some MS-DOS compilers.
+ */
+//typedef struct MD5Context MD5_CTX;
+
+#endif /* !MD5_H */
diff --git a/twrpDigest/twrpDigest.cpp b/twrpDigest/twrpDigest.cpp
new file mode 100644
index 0000000..69c9701
--- /dev/null
+++ b/twrpDigest/twrpDigest.cpp
@@ -0,0 +1,33 @@
+/*
+	Copyright 2012 to 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <vector>
+#include <string>
+#include <fcntl.h>
+#include "twrpDigest.hpp"
+
+std::string twrpDigest::hexify(uint8_t* hash, size_t len) {
+	char hex[3];
+	std::string digest_string;
+
+	for (size_t i = 0; i < len; ++i) {
+		snprintf(hex, 3, "%02x", hash[i]);
+		digest_string += hex;
+	}
+	return digest_string;
+}
diff --git a/twrpDigest/twrpDigest.hpp b/twrpDigest/twrpDigest.hpp
new file mode 100644
index 0000000..1803a28
--- /dev/null
+++ b/twrpDigest/twrpDigest.hpp
@@ -0,0 +1,35 @@
+/*
+        Copyright 2012 to 2017 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __TWRPDIGEST_H
+#define __TWRPDIGEST_H
+
+class twrpDigest {
+public:
+	twrpDigest() {};
+	virtual ~twrpDigest() {};
+	virtual void init() = 0;                                         // Initialize the digest according to the algorithm
+	virtual void update(const unsigned char* stream, size_t len) = 0;         // Update the digest with new data
+	virtual std::string return_digest_string() = 0;                  // Returns the digest of the file as a string.
+
+protected:
+	virtual void finalize() = 0;                                     // Finalize the digest input for creating the final digest
+	std::string hexify(uint8_t* hash, size_t len);                   // Take an len bytes and turn it into a hex string
+};
+
+#endif //__TWRPDIGEST_H
diff --git a/twrpDigest/twrpMD5.cpp b/twrpDigest/twrpMD5.cpp
new file mode 100644
index 0000000..50ccf74
--- /dev/null
+++ b/twrpDigest/twrpMD5.cpp
@@ -0,0 +1,47 @@
+/*
+	Copyright 2012 to 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <vector>
+#include <string>
+//#include <sstream>
+#include "twrpDigest.hpp"
+#include "twrpMD5.hpp"
+
+twrpMD5::twrpMD5() {
+	init();
+}
+
+void twrpMD5::init() {
+	MD5Init(&md5c);
+}
+
+void twrpMD5::update(const unsigned char* stream, size_t len) {
+	MD5Update(&md5c, stream, len);
+}
+
+void twrpMD5::finalize() {
+	MD5Final(md5sum, &md5c);
+}
+
+std::string twrpMD5::return_digest_string() {
+	std::string md5string;
+	finalize();
+
+	md5string = hexify(md5sum, sizeof(md5sum));
+	return md5string;
+}
diff --git a/twrpDigest/twrpMD5.hpp b/twrpDigest/twrpMD5.hpp
new file mode 100644
index 0000000..40cac18
--- /dev/null
+++ b/twrpDigest/twrpMD5.hpp
@@ -0,0 +1,42 @@
+/*
+        Copyright 2012 to 2017 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __TWRPMD5_H
+#define __TWRPMD5_H
+
+#include <string>
+#include "twrpDigest.hpp"
+
+extern "C" {
+	#include "digest/md5/md5.h"
+}
+
+class twrpMD5: public twrpDigest {
+public:
+	twrpMD5();                                                            // Stream initializer
+	void init();                                                          // Initialize MD5 structures
+	void update(const unsigned char* stream, size_t len);                 // Update MD5 stream with data. Return false if failure to update MD5.
+	std::string return_digest_string();                                   // Return MD5 digest as string to callee
+	void finalize();                                                      // Finalize and compute MD5
+
+private:
+	struct MD5Context md5c;                                               // MD5 control structure
+	unsigned char md5sum[MD5LENGTH];                                      // Stores the md5sum computation
+};
+
+#endif //__TWRPMD5_H
diff --git a/twrpDigest/twrpSHA.cpp b/twrpDigest/twrpSHA.cpp
new file mode 100644
index 0000000..60262e7
--- /dev/null
+++ b/twrpDigest/twrpSHA.cpp
@@ -0,0 +1,69 @@
+/*
+	Copyright 2012 to 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <vector>
+#include <string>
+#include <sstream>
+#include <string>
+#include <openssl/sha.h>
+#include "twrpDigest.hpp"
+#include "twrpSHA.hpp"
+
+twrpSHA256::twrpSHA256() {
+	twrpSHA256::init();
+}
+
+void twrpSHA256::init(void) {
+	SHA256_Init(&sha256_ctx);
+}
+
+void twrpSHA256::update(const unsigned char* stream, size_t len) {
+	SHA256_Update(&sha256_ctx, stream, len);
+}
+
+void twrpSHA256::finalize(void) {
+	SHA256_Final(sha256_store, &sha256_ctx);
+}
+
+std::string twrpSHA256::return_digest_string(void) {
+	twrpSHA256::finalize();
+	std::string digest_str = twrpDigest::hexify(sha256_store, SHA256_DIGEST_LENGTH);
+	return digest_str;
+}
+
+twrpSHA512::twrpSHA512() {
+	twrpSHA512::init();
+}
+
+void twrpSHA512::init(void) {
+	SHA512_Init(&sha512_ctx);
+}
+
+void twrpSHA512::update(const unsigned char* stream, size_t len) {
+	SHA512_Update(&sha512_ctx, stream, len);
+}
+
+void twrpSHA512::finalize(void) {
+	SHA512_Final(sha512_store, &sha512_ctx);
+}
+
+std::string twrpSHA512::return_digest_string(void) {
+	twrpSHA512::finalize();
+	std::string digest_str = twrpDigest::hexify(sha512_store, SHA512_DIGEST_LENGTH);
+	return digest_str;
+}
diff --git a/twrpDigest/twrpSHA.hpp b/twrpDigest/twrpSHA.hpp
new file mode 100644
index 0000000..e389930
--- /dev/null
+++ b/twrpDigest/twrpSHA.hpp
@@ -0,0 +1,54 @@
+/*
+        Copyright 2012 to 2017 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef __TWRPSHA_H
+#define __TWRPSHA_H
+
+#include <openssl/sha.h>
+#include "twrpDigest.hpp"
+
+class twrpSHA256: public twrpDigest {
+public:
+	twrpSHA256();                                            // Initialize the SHA256 digest for streaming activities only
+	void init();                                             // Initialize the SHA256 digest algorithm
+
+protected:
+	void update(const unsigned char* stream, size_t len);    // Update the SHA256 digest stream
+	void finalize();                                         // Finalize the SHA256 digest for computation
+	std::string return_digest_string();                      // Return the digest string computed to the callee
+
+private:
+	uint8_t sha256_store[SHA256_DIGEST_LENGTH];              // Initialize the SHA256 digest array that holds the computation
+	SHA256_CTX sha256_ctx;                                   // Initialize the SHA256 control structure
+};
+
+class twrpSHA512: public twrpDigest {
+public:
+	twrpSHA512();                                            // Initialize the SHA512 digest for streaming activities only
+	void init();                                             // Initialize the SHA512 digest algorithm
+
+protected:
+	void update(const unsigned char* stream, size_t len);    // Update the SHA512 digest stream
+	void finalize();                                         // Finalize the SHA512 digest for computation
+	std::string return_digest_string();                      // Return the digest string computed to the callee
+
+private:
+	uint8_t sha512_store[SHA512_DIGEST_LENGTH];              // Initialize the SHA512 digest array that holds the computation
+	SHA512_CTX sha512_ctx;                                   // Initialize the SHA512 control structure
+};
+
+#endif //__TWRPSHA_H
diff --git a/twrpDigestDriver.cpp b/twrpDigestDriver.cpp
new file mode 100755
index 0000000..82fa3c2
--- /dev/null
+++ b/twrpDigestDriver.cpp
@@ -0,0 +1,230 @@
+/*
+	Copyright 2013 to 2017 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include <fcntl.h>
+#include <string>
+#include <unistd.h>
+#include "data.hpp"
+#include "partitions.hpp"
+#include "set_metadata.h"
+#include "twrpDigestDriver.hpp"
+#include "twrp-functions.hpp"
+#include "twcommon.h"
+#include "variables.h"
+#include "gui/gui.hpp"
+#include "twrpDigest/twrpDigest.hpp"
+#include "twrpDigest/twrpMD5.hpp"
+#include "twrpDigest/twrpSHA.hpp"
+
+
+bool twrpDigestDriver::Check_File_Digest(const string& Filename) {
+	twrpDigest *digest;
+	string digestfile = Filename, file_name = Filename;
+	string digest_str;
+	bool use_sha2 = false;
+
+#ifndef TW_NO_SHA2_LIBRARY
+
+	digestfile += ".sha2";
+	if (TWFunc::Path_Exists(digestfile)) {
+		digest = new twrpSHA256();
+		use_sha2 = true;
+	}
+	else {
+		digestfile = Filename + ".sha256";
+		if (TWFunc::Path_Exists(digestfile)) {
+			digest = new twrpSHA256();
+			use_sha2 = true;
+		} else {
+			digest = new twrpMD5();
+			digestfile = Filename + ".md5";
+			if (!TWFunc::Path_Exists(digestfile)) {
+				digestfile = Filename + ".md5sum";
+			}
+		}
+	}
+#else
+	digest = new twrpMD5();
+	digestfile = Filename + ".md5";
+	if (!TWFunc::Path_Exists(digestfile)) {
+		digestfile = Filename + ".md5sum";
+	}
+
+#endif
+
+	if (!TWFunc::Path_Exists(digestfile)) {
+		delete digest;
+		gui_msg(Msg(msg::kWarning, "no_digest=Skipping Digest check: no Digest file found"));
+		return true;
+	}
+
+
+	if (TWFunc::read_file(digestfile, digest_str) != 0) {
+		gui_msg("digest_error=Digest Error!");
+		delete digest;
+		return false;
+	}
+
+	if (!stream_file_to_digest(file_name, digest)) {
+		delete digest;
+		return false;
+	}
+	string digest_check = digest->return_digest_string();
+	digest_check = digest_check + "  " + TWFunc::Get_Filename(file_name);
+	if (digest_check == digest_str) {
+		if (use_sha2)
+			LOGINFO("SHA2 Digest: %s  %s\n", digest_str.c_str(), TWFunc::Get_Filename(Filename).c_str());
+		else
+			LOGINFO("MD5 Digest: %s  %s\n", digest_str.c_str(), TWFunc::Get_Filename(Filename).c_str());
+		gui_msg(Msg("digest_matched=Digest matched for '{1}'.")(Filename));
+		delete digest;
+		return true;
+	}
+
+	gui_msg(Msg(msg::kError, "digest_fail_match=Digest failed to match on '{1}'.")(Filename));
+	delete digest;
+	return false;
+}
+
+bool twrpDigestDriver::Check_Digest(string Full_Filename) {
+	char split_filename[512];
+	int index = 0;
+
+	sync();
+	if (!TWFunc::Path_Exists(Full_Filename)) {
+		// This is a split archive, we presume
+		memset(split_filename, 0, sizeof(split_filename));
+		while (index < 1000) {
+			sprintf(split_filename, "%s%03i", Full_Filename.c_str(), index);
+			if (!TWFunc::Path_Exists(split_filename))
+				break;
+				LOGINFO("split_filename: %s\n", split_filename);
+				if (!Check_File_Digest(split_filename))
+					return false;
+				index++;
+		}
+		return true;
+	}
+	return Check_File_Digest(Full_Filename); // Single file archive
+}
+
+bool twrpDigestDriver::Write_Digest(string Full_Filename) {
+	twrpDigest *digest;
+	string digest_filename, digest_str;
+	int use_sha2;
+
+#ifdef TW_NO_SHA2_LIBRARY
+	use_sha2 = 0;
+#else
+	DataManager::GetValue(TW_USE_SHA2, use_sha2);
+#endif
+
+	if (use_sha2) {
+#ifndef TW_NO_SHA2_LIBRARY
+		digest = new twrpSHA256();
+		digest_filename = Full_Filename + ".sha2";
+		if (!stream_file_to_digest(Full_Filename, digest)) {
+			delete digest;
+			return false;
+		}
+		digest_str = digest->return_digest_string();
+		if (digest_str.empty()) {
+			delete digest;
+			return false;
+		}
+		LOGINFO("SHA2 Digest: %s  %s\n", digest_str.c_str(), TWFunc::Get_Filename(Full_Filename).c_str());
+#endif
+	}
+	else  {
+		digest = new twrpMD5();
+		digest_filename = Full_Filename + ".md5";
+		if (!stream_file_to_digest(Full_Filename, digest)) {
+			delete digest;
+			return false;
+		}
+		digest_str = digest->return_digest_string();
+		if (digest_str.empty()) {
+			delete digest;
+			return false;
+		}
+		LOGINFO("MD5 Digest: %s  %s\n", digest_str.c_str(), TWFunc::Get_Filename(Full_Filename).c_str());
+	}
+
+	digest_str = digest_str + "  " + TWFunc::Get_Filename(Full_Filename) + "\n";
+	LOGINFO("digest_filename: %s\n", digest_filename.c_str());
+
+	if (TWFunc::write_to_file(digest_filename, digest_str)) {
+		tw_set_default_metadata(digest_filename.c_str());
+		gui_msg("digest_created= * Digest Created.");
+	}
+	else {
+		gui_err("digest_error= * Digest Error!");
+		delete digest;
+		return false;
+	}
+	delete digest;
+	return true;
+}
+
+bool twrpDigestDriver::Make_Digest(string Full_Filename) {
+	string command, result;
+
+	TWFunc::GUI_Operation_Text(TW_GENERATE_DIGEST_TEXT, gui_parse_text("{@generating_digest1}"));
+	gui_msg("generating_digest2= * Generating digest...");
+	if (TWFunc::Path_Exists(Full_Filename)) {
+		if (!Write_Digest(Full_Filename))
+			return false;
+	} else {
+		char filename[512];
+		int index = 0;
+		sprintf(filename, "%s%03i", Full_Filename.c_str(), index);
+		while (index < 1000) {
+			string digest_src(filename);
+			if (TWFunc::Path_Exists(filename)) {
+				if (!Write_Digest(filename))
+					return false;
+				}
+				else
+					break;
+				index++;
+				sprintf(filename, "%s%03i", Full_Filename.c_str(), index);
+			}
+			if (index == 0) {
+				LOGERR("Backup file: '%s' not found!\n", filename);
+					return false;
+			}
+			gui_msg("digest_created= * Digest Created.");
+	}
+	return true;
+}
+
+bool twrpDigestDriver::stream_file_to_digest(string filename, twrpDigest* digest) {
+	char buf[4096];
+	int bytes;
+
+	int fd = open(filename.c_str(), O_RDONLY);
+	if (fd < 0) {
+		return false;
+	}
+	while ((bytes = read(fd, &buf, sizeof(buf))) != 0) {
+		digest->update((unsigned char*)buf, bytes);
+	}
+	close(fd);
+	return true;
+}
diff --git a/twrpDigestDriver.hpp b/twrpDigestDriver.hpp
new file mode 100755
index 0000000..64e9778
--- /dev/null
+++ b/twrpDigestDriver.hpp
@@ -0,0 +1,33 @@
+/*
+        Copyright 2013 to 2017 TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __TWRP_DIGEST_DRIVER
+#define __TWRP_DIGEST_DRIVER
+#include <string>
+#include "twrpDigest/twrpDigest.hpp"
+
+class twrpDigestDriver {
+public:
+
+	static bool Check_File_Digest(const string& Filename);		//Check the digest of a TWRP partition backup
+	static bool Check_Digest(string Full_Filename);				//Check to make sure the digest is correct
+	static bool Write_Digest(string Full_Filename);				//Write the digest to a file
+	static bool Make_Digest(string Full_Filename);				//Create the digest for a partition backup
+	static bool stream_file_to_digest(string filename, twrpDigest* digest); //Stream the file to twrpDigest
+};
+#endif //__TWRP_DIGEST_DRIVER
diff --git a/twrpRepacker.cpp b/twrpRepacker.cpp
new file mode 100755
index 0000000..6ac2ce9
--- /dev/null
+++ b/twrpRepacker.cpp
@@ -0,0 +1,292 @@
+/*
+	Copyright 2013 to 2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string>
+
+#include "data.hpp"
+#include "partitions.hpp"
+#include "twrp-functions.hpp"
+#include "twrpRepacker.hpp"
+#include "twcommon.h"
+#include "variables.h"
+#include "gui/gui.hpp"
+
+bool twrpRepacker::Prepare_Empty_Folder(const std::string& Folder) {
+	if (TWFunc::Path_Exists(Folder))
+		TWFunc::removeDir(Folder, false);
+	return TWFunc::Recursive_Mkdir(Folder);
+}
+
+bool twrpRepacker::Backup_Image_For_Repack(TWPartition* Part, const std::string& Temp_Folder_Destination,
+										 const bool Create_Backup, const std::string& Backup_Name) {
+	if (!Part) {
+		LOGERR("Partition was null!\n");
+		return false;
+	}
+	if (!Prepare_Empty_Folder(Temp_Folder_Destination))
+		return false;
+	std::string target_image = Temp_Folder_Destination + "boot.img";
+	PartitionSettings part_settings;
+	part_settings.Part = Part;
+	if (Create_Backup) {
+		if (PartitionManager.Check_Backup_Name(Backup_Name, true, false) != 0)
+			return false;
+		DataManager::GetValue(TW_BACKUPS_FOLDER_VAR, part_settings.Backup_Folder);
+		part_settings.Backup_Folder = part_settings.Backup_Folder + "/" + TWFunc::Get_Current_Date() + " " + Backup_Name + "/";
+		if (!TWFunc::Recursive_Mkdir(part_settings.Backup_Folder))
+			return false;
+	} else
+		part_settings.Backup_Folder = Temp_Folder_Destination;
+	part_settings.adbbackup = false;
+	part_settings.generate_digest = false;
+	part_settings.generate_md5 = false;
+	part_settings.PM_Method = PM_BACKUP;
+	part_settings.progress = NULL;
+	pid_t not_a_pid = 0;
+	if (!Part->Backup(&part_settings, &not_a_pid))
+		return false;
+	std::string backed_up_image = part_settings.Backup_Folder;
+	backed_up_image += Part->Get_Backup_FileName();
+	target_image = Temp_Folder_Destination + "boot.img";
+	if (Create_Backup) {
+		std::string source = part_settings.Backup_Folder + Part->Get_Backup_FileName();
+		if (TWFunc::copy_file(source, target_image, 0644) != 0) {
+			LOGERR("Failed to copy backup file '%s' to temp folder target '%s'\n", source.c_str(), target_image.c_str());
+			return false;
+		}
+	} else {
+		if (rename(backed_up_image.c_str(), target_image.c_str()) != 0) {
+			LOGERR("Failed to rename '%s' to '%s'\n", backed_up_image.c_str(), target_image.c_str());
+			return false;
+		}
+	}
+	original_ramdisk_format = Unpack_Image(target_image, Temp_Folder_Destination, false, false);
+	return !original_ramdisk_format.empty();
+}
+
+std::string twrpRepacker::Unpack_Image(const std::string& Source_Path, const std::string& Temp_Folder_Destination,
+										const bool Copy_Source, const bool Create_Destination) {
+	std::string txt_to_find = "RAMDISK_FMT";
+	if (Create_Destination) {
+		if (!Prepare_Empty_Folder(Temp_Folder_Destination))
+			return std::string();
+	}
+	if (Copy_Source) {
+		std::string destination = Temp_Folder_Destination + "/boot.img";
+		if (TWFunc::copy_file(Source_Path, destination, 0644))
+			return std::string();
+	}
+	std::string command = "cd " + Temp_Folder_Destination + " && /system/bin/magiskboot unpack -h -n ";
+	command = command + "'" + Source_Path +"'";
+
+	std::string magisk_unpack_output;
+	int ret;
+	if ((ret = TWFunc::Exec_Cmd(command, magisk_unpack_output, true)) != 0) {
+		LOGINFO("Error unpacking %s, ret: %d!\n", Source_Path.c_str(), ret);
+		gui_msg(Msg(msg::kError, "unpack_error=Error unpacking image."));
+		return std::string();
+	}
+	size_t pos = magisk_unpack_output.find(txt_to_find) + txt_to_find.size();
+	std::string ramdisk_format = magisk_unpack_output.substr(pos, magisk_unpack_output.size() - 1);
+	ramdisk_format.erase(std::remove(ramdisk_format.begin(), ramdisk_format.end(), '['), ramdisk_format.end());
+	ramdisk_format.erase(std::remove(ramdisk_format.begin(), ramdisk_format.end(), ']'), ramdisk_format.end());
+	ramdisk_format.erase(std::remove(ramdisk_format.begin(), ramdisk_format.end(), ' '), ramdisk_format.end());
+	ramdisk_format.erase(std::remove(ramdisk_format.begin(), ramdisk_format.end(), '\n'), ramdisk_format.end());
+	return ramdisk_format;
+}
+
+bool twrpRepacker::Repack_Image_And_Flash(const std::string& Target_Image, const struct Repack_Options_struct& Repack_Options) {
+	bool recompress = false;
+
+	if (!TWFunc::Path_Exists("/system/bin/magiskboot")) {
+		LOGERR("Image repacking tool not present in this TWRP build!");
+		return false;
+	}
+	DataManager::SetProgress(0);
+	PartitionManager.Update_System_Details();
+	TWPartition* part = PartitionManager.Find_Partition_By_Path("/boot");
+	if (part)
+		gui_msg(Msg("unpacking_image=Unpacking {1}...")(part->Get_Display_Name()));
+	else {
+		gui_msg(Msg(msg::kError, "unable_to_locate=Unable to locate {1}.")("/boot"));
+		return false;
+	}
+	if (!Backup_Image_For_Repack(part, REPACK_ORIG_DIR, Repack_Options.Backup_First, gui_lookup("repack", "Repack")))
+		return false;
+	DataManager::SetProgress(.25);
+	if (Repack_Options.Type == REPLACE_RAMDISK_UNPACKED) {
+		if (!Prepare_Empty_Folder(REPACK_NEW_DIR))
+			return false;
+		image_ramdisk_format = "gzip";
+	} else {
+		gui_msg(Msg("unpacking_image=Unpacking {1}...")(Target_Image));
+		image_ramdisk_format = Unpack_Image(Target_Image, REPACK_NEW_DIR, true);
+	}
+	if (image_ramdisk_format.empty())
+		return false;
+	DataManager::SetProgress(.5);
+	gui_msg(Msg("repacking_image=Repacking {1}...")(part->Get_Display_Name()));
+	std::string path = REPACK_NEW_DIR;
+	if (Repack_Options.Type == REPLACE_KERNEL) {
+		// When we replace the kernel, what we really do is copy the boot partition ramdisk into the new image's folder
+		if (TWFunc::copy_file(REPACK_ORIG_DIR "ramdisk.cpio", REPACK_NEW_DIR "ramdisk.cpio", 0644)) {
+			LOGERR("Failed to copy ramdisk\n");
+			return false;
+		}
+	} else if (Repack_Options.Type == REPLACE_RAMDISK_UNPACKED) {
+			if (TWFunc::copy_file(Target_Image, REPACK_ORIG_DIR "ramdisk.cpio", 0644)) {
+				LOGERR("Failed to copy ramdisk\n");
+				return false;
+			}
+			if (TWFunc::copy_file(Target_Image, REPACK_NEW_DIR "ramdisk.cpio", 0644)) {
+				LOGERR("Failed to copy ramdisk\n");
+				return false;
+			}
+		path = REPACK_ORIG_DIR;
+	} else if (Repack_Options.Type == REPLACE_RAMDISK) {
+		// Repack the ramdisk
+		if (TWFunc::copy_file(REPACK_NEW_DIR "ramdisk.cpio", REPACK_ORIG_DIR "ramdisk.cpio", 0644)) {
+			LOGERR("Failed to copy ramdisk\n");
+			return false;
+		}
+		path = REPACK_ORIG_DIR;
+	} else {
+		LOGERR("Invalid repacking options specified\n");
+		return false;
+	}
+	if (Repack_Options.Disable_Verity)
+		LOGERR("Disabling verity is not implemented yet\n");
+	if (Repack_Options.Disable_Force_Encrypt)
+		LOGERR("Disabling force encrypt is not implemented yet\n");
+	std::string command = "cd " + path + " && /system/bin/magiskboot repack ";
+	if (original_ramdisk_format != image_ramdisk_format) {
+		recompress = true;
+	}
+
+	command += path + "boot.img";
+
+	std::string orig_compressed_image(REPACK_ORIG_DIR);
+	orig_compressed_image += "ramdisk.cpio";
+	std::string copy_compressed_image(REPACK_ORIG_DIR);
+	copy_compressed_image += "ramdisk-1.cpio";
+
+	if (recompress) {
+		std::string decompress_cmd = "/system/bin/magiskboot decompress " + orig_compressed_image + " " + copy_compressed_image;
+		if (TWFunc::Exec_Cmd(decompress_cmd) != 0) {
+			gui_msg(Msg(msg::kError, "repack_error=Error repacking image."));
+			return false;
+		}
+		std::rename(copy_compressed_image.c_str(), orig_compressed_image.c_str());
+	}
+
+	if (TWFunc::Exec_Cmd(command) != 0) {
+		gui_msg(Msg(msg::kError, "repack_error=Error repacking image."));
+		return false;
+	}
+
+	DataManager::SetProgress(.75);
+	std::string file = "new-boot.img";
+	DataManager::SetValue("tw_flash_partition", "/boot;");
+	if (!PartitionManager.Flash_Image(path, file)) {
+		LOGINFO("Error flashing new image\n");
+		return false;
+	}
+	DataManager::SetProgress(1);
+	TWFunc::removeDir(REPACK_ORIG_DIR, false);
+	if (part->Is_SlotSelect()) {
+		if (Repack_Options.Type == REPLACE_RAMDISK || Repack_Options.Type == REPLACE_RAMDISK_UNPACKED) {
+			LOGINFO("Switching slots to flash ramdisk to both partitions\n");
+			string Current_Slot = PartitionManager.Get_Active_Slot_Display();
+			if (Current_Slot == "A")
+				PartitionManager.Override_Active_Slot("B");
+			else
+				PartitionManager.Override_Active_Slot("A");
+			DataManager::SetProgress(.25);
+			if (!Backup_Image_For_Repack(part, REPACK_ORIG_DIR, Repack_Options.Backup_First, gui_lookup("repack", "Repack")))
+				return false;
+			if (TWFunc::copy_file(REPACK_NEW_DIR "ramdisk.cpio", REPACK_ORIG_DIR "ramdisk.cpio", 0644)) {
+				LOGERR("Failed to copy ramdisk\n");
+				return false;
+			}
+			path = REPACK_ORIG_DIR;
+			std::string command = "cd " + path + " && /system/bin/magiskboot repack ";
+
+			if (original_ramdisk_format != image_ramdisk_format) {
+				recompress = true;
+			}
+			command += path + "boot.img";
+
+			if (recompress) {
+				std::string decompress_cmd = "/system/bin/magiskboot decompress " + orig_compressed_image + " " + copy_compressed_image;
+				if (TWFunc::Exec_Cmd(decompress_cmd) != 0) {
+					gui_msg(Msg(msg::kError, "repack_error=Error repacking image."));
+					return false;
+				}
+				std::rename(copy_compressed_image.c_str(), orig_compressed_image.c_str());
+			}
+
+			if (TWFunc::Exec_Cmd(command) != 0) {
+				gui_msg(Msg(msg::kError, "repack_error=Error repacking image."));
+				return false;
+			}
+			DataManager::SetProgress(.75);
+			std::string file = "new-boot.img";
+			DataManager::SetValue("tw_flash_partition", "/boot;");
+			if (!PartitionManager.Flash_Image(path, file)) {
+				LOGINFO("Error flashing new image\n");
+				return false;
+			}
+			DataManager::SetProgress(1);
+			TWFunc::removeDir(REPACK_ORIG_DIR, false);
+		}
+	}
+	TWFunc::removeDir(REPACK_NEW_DIR, false);
+	gui_msg(Msg(msg::kWarning, "repack_overwrite_warning=If device was previously rooted, then root has been overwritten and will need to be reinstalled."));
+	string Current_Slot = PartitionManager.Get_Active_Slot_Display();
+		if (Current_Slot == "A")
+			PartitionManager.Override_Active_Slot("B");
+		else
+			PartitionManager.Override_Active_Slot("A");
+	return true;
+}
+
+bool twrpRepacker::Flash_Current_Twrp() {
+if (!TWFunc::Path_Exists("/ramdisk-files.txt")) {
+			LOGERR("can not find ramdisk-files.txt");
+			return false;
+		}
+		Repack_Options_struct Repack_Options;
+		Repack_Options.Disable_Verity = false;
+		Repack_Options.Disable_Force_Encrypt = false;
+		Repack_Options.Type = REPLACE_RAMDISK_UNPACKED;
+		Repack_Options.Backup_First = DataManager::GetIntValue("tw_repack_backup_first") != 0;
+		std::string verifyfiles = "cd / && sha256sum --status -c ramdisk-files.sha256sum";
+		if (TWFunc::Exec_Cmd(verifyfiles) != 0) {
+		gui_msg(Msg(msg::kError, "modified_ramdisk_error=ramdisk files have been modified, unable to create ramdisk to flash, fastboot boot twrp and try this option again or use the Install Recovery Ramdisk option."));
+			return false;
+		}
+		std::string command = "cd / && /system/bin/cpio -H newc -o < ramdisk-files.txt > /tmp/currentramdisk.cpio && /system/bin/gzip -f /tmp/currentramdisk.cpio";
+		if (TWFunc::Exec_Cmd(command) != 0) {
+			gui_msg(Msg(msg::kError, "create_ramdisk_error=failed to create ramdisk to flash."));
+			return false;
+		}
+		if (!Repack_Image_And_Flash("/tmp/currentramdisk.cpio.gz", Repack_Options))
+			return false;
+       else
+       return true;
+}
diff --git a/twrpRepacker.hpp b/twrpRepacker.hpp
new file mode 100755
index 0000000..e71f3dc
--- /dev/null
+++ b/twrpRepacker.hpp
@@ -0,0 +1,50 @@
+/*
+	Copyright 2014 to 2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string>
+#include "partitions.hpp"
+
+#ifndef TWRP_REPACKER
+#define TWRP_REPACKER
+
+enum Repack_Type {
+	REPLACE_NONE = 0,
+	REPLACE_RAMDISK = 1,
+	REPLACE_KERNEL = 2,
+	REPLACE_RAMDISK_UNPACKED = 3,
+};
+
+struct Repack_Options_struct {
+	Repack_Type Type;
+	bool Backup_First;
+	bool Disable_Verity;
+	bool Disable_Force_Encrypt;
+};
+
+class twrpRepacker {
+    public:
+        bool Backup_Image_For_Repack(TWPartition* Part, const std::string& Temp_Folder_Destination, const bool Create_Backup, const std::string& Backup_Name); // Prepares an image for repacking by unpacking it to the temp folder destination
+        std::string Unpack_Image(const std::string& Source_Path, const std::string& Temp_Folder_Destination, const bool Copy_Source, const bool Create_Destination = true); // Prepares an image for repacking by unpacking it to the temp folder destination and return the ramdisk format
+        bool Repack_Image_And_Flash(const std::string& Target_Image, const struct Repack_Options_struct& Repack_Options); // Repacks the boot image with a new kernel or a new ramdisk
+        bool Flash_Current_Twrp();
+    private:
+    	bool Prepare_Empty_Folder(const std::string& Folder); // Creates an empty folder at Folder. If the folder already exists, the folder is deleted, then created
+    	std::string original_ramdisk_format;                  // Ramdisk format of boot partition
+	    std::string image_ramdisk_format;                     // Ramdisk format of boot image to repack from
+};
+#endif // TWRP_REPACKER
diff --git a/twrpTar.cpp b/twrpTar.cpp
new file mode 100755
index 0000000..71a5b15
--- /dev/null
+++ b/twrpTar.cpp
@@ -0,0 +1,1533 @@
+
+/*
+	Copyright 2013 to 2020 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+extern "C" {
+	#include "libtar/libtar.h"
+	#include "twrpTar.h"
+	#include "tarWrite.h"
+}
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <csignal>
+#include <dirent.h>
+#include <libgen.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <zlib.h>
+#include <semaphore.h>
+#include "twrpTar.hpp"
+#include "twcommon.h"
+#include "variables.h"
+#include "adbbu/libtwadbbu.hpp"
+#include "twrp-functions.hpp"
+#include "gui/gui.hpp"
+#include "progresstracking.hpp"
+
+#ifndef BUILD_TWRPTAR_MAIN
+#include "data.hpp"
+#include "infomanager.hpp"
+#include "set_metadata.h"
+#endif //ndef BUILD_TWRPTAR_MAIN
+
+#ifdef TW_INCLUDE_FBE
+#ifdef USE_FSCRYPT
+#include "fscrypt_policy.h"
+#endif
+#endif
+
+#ifdef USE_FSCRYPT
+#define TWTAR_FLAGS TAR_GNU | TAR_STORE_SELINUX | TAR_STORE_POSIX_CAP | TAR_STORE_ANDROID_USER_XATTR | TAR_STORE_FSCRYPT_POL
+#else
+#define TWTAR_FLAGS TAR_GNU | TAR_STORE_SELINUX | TAR_STORE_POSIX_CAP | TAR_STORE_ANDROID_USER_XATTR
+#endif
+
+using namespace std;
+
+twrpTar::twrpTar(void) {
+	use_encryption = 0;
+	userdata_encryption = 0;
+	use_compression = 0;
+	split_archives = 0;
+	pigz_pid = 0;
+	oaes_pid = 0;
+	Total_Backup_Size = 0;
+	Archive_Current_Size = 0;
+	include_root_dir = true;
+	tar_type.openfunc = open;
+	tar_type.closefunc = close;
+	tar_type.readfunc = read;
+	input_fd = -1;
+	output_fd = -1;
+	backup_exclusions = NULL;
+
+#ifdef USE_FSCRYPT
+	fscrypt_set_mode();
+#endif
+}
+
+twrpTar::~twrpTar(void) {
+	// Do nothing
+}
+
+void twrpTar::setfn(string fn) {
+	tarfn = fn;
+}
+
+void twrpTar::setdir(string dir) {
+	tardir = dir;
+}
+
+void twrpTar::setsize(unsigned long long backup_size) {
+	Total_Backup_Size = backup_size;
+}
+
+void twrpTar::setpassword(string pass) {
+	password = pass;
+}
+
+void twrpTar::Signal_Kill(int signum) {
+	_exit(255);
+}
+
+void twrpTar::Set_Archive_Type(Archive_Type archive_type) {
+	current_archive_type = archive_type;
+}
+
+int twrpTar::createTarFork(pid_t *tar_fork_pid) {
+	int status = 0;
+	int progress_pipe[2];
+
+	file_count = 0;
+	if (backup_exclusions == NULL) {
+		LOGINFO("backup_exclusions is NULL\n");
+		return -1;
+	}
+
+#ifndef BUILD_TWRPTAR_MAIN
+	if (part_settings->adbbackup) {
+		std::string Backup_FileName(tarfn);
+		if (!twadbbu::Write_TWFN(Backup_FileName, Total_Backup_Size, use_compression))
+			return -1;
+	}
+#endif
+
+	if (pipe(progress_pipe) < 0) {
+		LOGINFO("Error creating progress tracking pipe\n");
+		gui_err("backup_error=Error creating backup.");
+		return -1;
+	}
+	if ((*tar_fork_pid = fork()) == -1) {
+		LOGINFO("create tar failed to fork.\n");
+		gui_err("backup_error=Error creating backup.");
+		close(progress_pipe[0]);
+		close(progress_pipe[1]);
+		return -1;
+	}
+
+	if (*tar_fork_pid == 0) {
+		// Child process
+		// Child closes input side of progress pipe
+		signal(SIGUSR2, twrpTar::Signal_Kill);
+		close(progress_pipe[0]);
+		progress_pipe_fd = progress_pipe[1];
+
+		if (use_encryption || userdata_encryption) {
+			LOGINFO("Using encryption\n");
+			DIR* d;
+			struct dirent* de;
+			unsigned long long regular_size = 0, encrypt_size = 0, target_size = 0, total_size;
+			unsigned enc_thread_id = 1, regular_thread_id = 0, i, start_thread_id = 1, core_count = 1;
+			int item_len, ret, thread_error = 0;
+			std::vector<TarListStruct> RegularList;
+			std::vector<TarListStruct> EncryptList;
+			string FileName;
+			struct TarListStruct TarItem;
+			twrpTar reg, enc[9];
+			struct stat st;
+			pthread_t enc_thread[9];
+			pthread_attr_t tattr;
+			void *thread_return;
+
+			core_count = sysconf(_SC_NPROCESSORS_CONF);
+			if (core_count > 8)
+				core_count = 8;
+			LOGINFO("   Core Count      : %u\n", core_count);
+			Archive_Current_Size = 0;
+
+			d = opendir(tardir.c_str());
+			if (d == NULL) {
+				gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(tardir)(strerror(errno)));
+				close(progress_pipe[1]);
+				_exit(-1);
+			}
+			// Figure out the size of all data to be encrypted and create a list of unencrypted files
+			while ((de = readdir(d)) != NULL) {
+				FileName = tardir + "/" + de->d_name;
+
+				if (de->d_type == DT_BLK || de->d_type == DT_CHR || backup_exclusions->check_skip_dirs(FileName))
+					continue;
+				if (de->d_type == DT_DIR) {
+					item_len = strlen(de->d_name);
+					if (userdata_encryption && ((item_len >= 3 && strncmp(de->d_name, "app", 3) == 0) || (item_len >= 6 && strncmp(de->d_name, "dalvik", 6) == 0))) {
+						ret = Generate_TarList(FileName, &RegularList, &target_size, &regular_thread_id);
+						if (ret < 0) {
+							LOGINFO("Error in Generate_TarList with regular list!\n");
+							gui_err("backup_error=Error creating backup.");
+							closedir(d);
+							close(progress_pipe_fd);
+							close(progress_pipe[1]);
+							_exit(-1);
+						}
+						file_count = (unsigned long long)(ret);
+						regular_size += backup_exclusions->Get_Folder_Size(FileName);
+					} else {
+						encrypt_size += backup_exclusions->Get_Folder_Size(FileName);
+					}
+				} else if (de->d_type == DT_REG) {
+					stat(FileName.c_str(), &st);
+					encrypt_size += (unsigned long long)(st.st_size);
+				}
+			}
+			closedir(d);
+
+			target_size = encrypt_size / core_count;
+			target_size++;
+			LOGINFO("   Unencrypted size: %llu\n", regular_size);
+			LOGINFO("   Encrypted size  : %llu\n", encrypt_size);
+			LOGINFO("   Target size     : %llu\n", target_size);
+			if (!userdata_encryption) {
+				enc_thread_id = 0;
+				start_thread_id = 0;
+				core_count--;
+			}
+			Archive_Current_Size = 0;
+
+			d = opendir(tardir.c_str());
+			if (d == NULL) {
+				gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(tardir)(strerror(errno)));
+				close(progress_pipe[1]);
+				_exit(-1);
+			}
+			// Divide up the encrypted file list for threading
+			while ((de = readdir(d)) != NULL) {
+				FileName = tardir + "/" + de->d_name;
+
+				if (de->d_type == DT_BLK || de->d_type == DT_CHR || backup_exclusions->check_skip_dirs(FileName))
+					continue;
+				if (de->d_type == DT_DIR) {
+					item_len = strlen(de->d_name);
+					if (userdata_encryption && ((item_len >= 3 && strncmp(de->d_name, "app", 3) == 0) || (item_len >= 6 && strncmp(de->d_name, "dalvik", 6) == 0))) {
+						// Do nothing, we added these to RegularList earlier
+					} else {
+						FileName = tardir + "/" + de->d_name;
+						ret = Generate_TarList(FileName, &EncryptList, &target_size, &enc_thread_id);
+						if (ret < 0) {
+							LOGINFO("Error in Generate_TarList with encrypted list!\n");
+							gui_err("backup_error=Error creating backup.");
+							closedir(d);
+							close(progress_pipe[1]);
+							_exit(-1);
+						}
+						file_count += (unsigned long long)(ret);
+					}
+				} else if (de->d_type == DT_REG || de->d_type == DT_LNK) {
+					stat(FileName.c_str(), &st);
+					if (de->d_type == DT_REG)
+						Archive_Current_Size += (unsigned long long)(st.st_size);
+					TarItem.fn = FileName;
+					TarItem.thread_id = enc_thread_id;
+					EncryptList.push_back(TarItem);
+					file_count++;
+				}
+			}
+			closedir(d);
+			if (enc_thread_id != core_count) {
+				LOGINFO("Error dividing up threads for encryption, %u threads for %u cores!\n", enc_thread_id, core_count);
+				if (enc_thread_id > core_count) {
+					gui_err("backup_error=Error creating backup.");
+					close(progress_pipe[1]);
+					_exit(-1);
+				} else {
+					LOGINFO("Continuining anyway.");
+				}
+			}
+
+			// Send file count to parent
+			write(progress_pipe_fd, &file_count, sizeof(file_count));
+			// Send backup size to parent
+			total_size = regular_size + encrypt_size;
+			write(progress_pipe_fd, &total_size, sizeof(total_size));
+
+			if (userdata_encryption) {
+				// Create a backup of unencrypted data
+				reg.setfn(tarfn);
+				reg.ItemList = &RegularList;
+				reg.thread_id = 0;
+				reg.use_encryption = 0;
+				reg.use_compression = use_compression;
+				reg.split_archives = 1;
+				reg.progress_pipe_fd = progress_pipe_fd;
+				reg.part_settings = part_settings;
+				LOGINFO("Creating unencrypted backup...\n");
+				if (createList((void*)&reg) != 0) {
+					LOGINFO("Error creating unencrypted backup.\n");
+					gui_err("backup_error=Error creating backup.");
+					close(progress_pipe[1]);
+					_exit(-1);
+				}
+			}
+
+			if (pthread_attr_init(&tattr)) {
+				LOGINFO("Unable to pthread_attr_init\n");
+				gui_err("backup_error=Error creating backup.");
+				close(progress_pipe[1]);
+				_exit(-1);
+			}
+			if (pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_JOINABLE)) {
+				LOGINFO("Error setting pthread_attr_setdetachstate\n");
+				gui_err("backup_error=Error creating backup.");
+				close(progress_pipe[1]);
+				_exit(-1);
+			}
+			if (pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM)) {
+				LOGINFO("Error setting pthread_attr_setscope\n");
+				gui_err("backup_error=Error creating backup.");
+				close(progress_pipe[1]);
+				_exit(-1);
+			}
+			/*if (pthread_attr_setstacksize(&tattr, 524288)) {
+				LOGERR("Error setting pthread_attr_setstacksize\n");
+				close(progress_pipe[1]);
+				_exit(-1);
+			}*/
+
+			// Create threads for the divided up encryption lists
+			for (i = start_thread_id; i <= core_count; i++) {
+				enc[i].setdir(tardir);
+				enc[i].setfn(tarfn);
+				enc[i].ItemList = &EncryptList;
+				enc[i].thread_id = i;
+				enc[i].use_encryption = use_encryption;
+				enc[i].setpassword(password);
+				enc[i].use_compression = use_compression;
+				enc[i].split_archives = 1;
+				enc[i].progress_pipe_fd = progress_pipe_fd;
+				enc[i].part_settings = part_settings;
+				LOGINFO("Start encryption thread %i\n", i);
+				ret = pthread_create(&enc_thread[i], &tattr, createList, (void*)&enc[i]);
+				if (ret) {
+					LOGINFO("Unable to create %i thread for encryption! %i\nContinuing in same thread (backup will be slower).\n", i, ret);
+					if (createList((void*)&enc[i]) != 0) {
+						LOGINFO("Error creating encrypted backup %i.\n", i);
+						gui_err("backup_error=Error creating backup.");
+						close(progress_pipe[1]);
+						_exit(-1);
+					} else {
+						enc[i].thread_id = i + 1;
+					}
+				}
+				usleep(100000); // Need a short delay before starting the next thread or the threads will never finish for some reason.
+			}
+			if (pthread_attr_destroy(&tattr)) {
+				LOGINFO("Failed to pthread_attr_destroy\n");
+			}
+			for (i = start_thread_id; i <= core_count; i++) {
+				if (enc[i].thread_id == i) {
+					if (pthread_join(enc_thread[i], &thread_return)) {
+						LOGINFO("Error joining thread %i\n", i);
+						gui_err("backup_error=Error creating backup.");
+						close(progress_pipe[1]);
+						_exit(-1);
+					} else {
+						LOGINFO("Joined thread %i.\n", i);
+						ret = (int)(intptr_t)thread_return;
+						if (ret != 0) {
+							thread_error = 1;
+							LOGINFO("Thread %i returned an error %i.\n", i, ret);
+							gui_err("backup_error=Error creating backup.");
+							close(progress_pipe[1]);
+							_exit(-1);
+						}
+					}
+				} else {
+					LOGINFO("Skipping joining thread %i because of pthread failure.\n", i);
+				}
+			}
+			if (thread_error) {
+				LOGINFO("Error returned by one or more threads.\n");
+				gui_err("backup_error=Error creating backup.");
+				close(progress_pipe[1]);
+				_exit(-1);
+			}
+			LOGINFO("Finished encrypted backup.\n");
+			close(progress_pipe[1]);
+			_exit(0);
+		} else {
+			// Not encrypted
+			std::vector<TarListStruct> FileList;
+			unsigned thread_id = 0;
+			unsigned long long target_size = 0;
+			twrpTar reg;
+			int ret;
+
+			// Generate list of files to back up
+			ret = Generate_TarList(tardir, &FileList, &target_size, &thread_id);
+			if (ret < 0) {
+				LOGINFO("Error in Generate_TarList!\n");
+				gui_err("backup_error=Error creating backup.");
+				close(progress_pipe[1]);
+				_exit(-1);
+			}
+			file_count = (unsigned long long)(ret);
+			// Create a backup
+			reg.setfn(tarfn);
+			reg.ItemList = &FileList;
+			reg.thread_id = 0;
+			reg.use_encryption = 0;
+			reg.use_compression = use_compression;
+			reg.setsize(Total_Backup_Size);
+			reg.progress_pipe_fd = progress_pipe_fd;
+			reg.part_settings = part_settings;
+			if (Total_Backup_Size > MAX_ARCHIVE_SIZE && !part_settings->adbbackup) {
+				gui_msg("split_backup=Breaking backup file into multiple archives...");
+				reg.split_archives = 1;
+			} else {
+				reg.split_archives = 0;
+			}
+			LOGINFO("Creating backup...\n");
+			write(progress_pipe_fd, &file_count, sizeof(file_count));
+			write(progress_pipe_fd, &Total_Backup_Size, sizeof(Total_Backup_Size));
+			if (createList((void*)&reg) != 0) {
+				gui_err("backup_error=Error creating backup.");
+				close(progress_pipe[1]);
+				_exit(-1);
+			}
+			close(progress_pipe[1]);
+			_exit(0);
+		}
+	} else {
+		// Parent side
+		unsigned long long fs, size_backup = 0, files_backup = 0, file_count = 0;
+		int first_data = 0;
+
+		// Parent closes output side
+		close(progress_pipe[1]);
+
+		// Read progress data from children
+		while (read(progress_pipe[0], &fs, sizeof(fs)) > 0) {
+			if (first_data == 0) {
+				// First incoming data is the file count
+				file_count = fs;
+				if (file_count == 0) file_count = 1; // prevent division by 0 below
+				first_data = 1;
+			} else if (first_data == 1) {
+				// Second incoming data is total size
+				first_data = 2;
+				part_settings->progress->SetSizeCount(fs, file_count);
+			} else {
+				if (fs > 0) {
+					size_backup += fs;
+					part_settings->progress->UpdateSize(size_backup);
+				} else { // fs == 0 increments the file counter
+					files_backup++;
+					part_settings->progress->UpdateSizeCount(size_backup, files_backup);
+				}
+			}
+		}
+		close(progress_pipe[0]);
+#ifndef BUILD_TWRPTAR_MAIN
+		DataManager::SetValue("tw_file_progress", "");
+		DataManager::SetValue("tw_size_progress", "");
+		part_settings->progress->DisplayFileCount(false);
+		part_settings->progress->UpdateDisplayDetails(true);
+
+		if (!part_settings->adbbackup) {
+			InfoManager backup_info(backup_folder + "/" + partition_name + ".info");
+			backup_info.SetValue("backup_size", size_backup);
+			if (use_compression && use_encryption)
+				backup_info.SetValue("backup_type", COMPRESSED_ENCRYPTED);
+			else if (use_encryption)
+				backup_info.SetValue("backup_type", ENCRYPTED);
+			else if (use_compression)
+				backup_info.SetValue("backup_type", COMPRESSED);
+			else
+				backup_info.SetValue("backup_type", UNCOMPRESSED);
+			backup_info.SetValue("file_count", files_backup);
+			backup_info.SaveValues();
+		}
+#endif //ndef BUILD_TWRPTAR_MAIN
+		if (TWFunc::Wait_For_Child(*tar_fork_pid, &status, "createTarFork()") != 0)
+			return -1;
+	}
+	return 0;
+}
+
+int twrpTar::extractTarFork() {
+	int status = 0;
+	pid_t tar_fork_pid;
+	int progress_pipe[2];
+
+	if (pipe(progress_pipe) < 0) {
+		LOGINFO("Error creating progress tracking pipe\n");
+		gui_err("restore_error=Error during restore process.");
+		return -1;
+	}
+
+	tar_fork_pid = fork();
+	if (tar_fork_pid >= 0) // fork was successful
+	{
+		if (tar_fork_pid == 0) // child process
+		{
+			close(progress_pipe[0]);
+			progress_pipe_fd = progress_pipe[1];
+			if (TWFunc::Path_Exists(tarfn) || part_settings->adbbackup) {
+				LOGINFO("Single archive\n");
+				if (extract() != 0)
+					_exit(-1);
+				else {
+					_exit(0);
+				}
+			} else {
+				LOGINFO("Multiple archives\n");
+				string temp;
+				char actual_filename[255];
+				twrpTar tars[9];
+				pthread_t tar_thread[9];
+				pthread_attr_t tattr;
+				unsigned thread_count = 0, i, start_thread_id = 1;
+				int ret, thread_error = 0;
+				void *thread_return;
+
+				basefn = tarfn;
+				temp = basefn + "%i%02i";
+				tarfn += "000";
+				if (!TWFunc::Path_Exists(tarfn)) {
+					LOGINFO("Unable to locate '%s' or '%s'\n", basefn.c_str(), tarfn.c_str());
+					gui_err("restore_error=Error during restore process.");
+					close(progress_pipe_fd);
+					_exit(-1);
+				}
+				if (TWFunc::Get_File_Type(tarfn) != 2) {
+					LOGINFO("First tar file '%s' not encrypted\n", tarfn.c_str());
+					tars[0].basefn = basefn;
+					tars[0].thread_id = 0;
+					tars[0].progress_pipe_fd = progress_pipe_fd;
+					tars[0].part_settings = part_settings;
+					if (extractMulti((void*)&tars[0]) != 0) {
+						LOGINFO("Error extracting split archive.\n");
+						gui_err("restore_error=Error during restore process.");
+						close(progress_pipe_fd);
+						_exit(-1);
+					}
+				} else {
+					start_thread_id = 0;
+				}
+				// Start threading encrypted restores
+				if (pthread_attr_init(&tattr)) {
+					LOGINFO("Unable to pthread_attr_init\n");
+					gui_err("restore_error=Error during restore process.");
+					close(progress_pipe_fd);
+					_exit(-1);
+				}
+				if (pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_JOINABLE)) {
+					LOGINFO("Error setting pthread_attr_setdetachstate\n");
+					gui_err("restore_error=Error during restore process.");
+					close(progress_pipe_fd);
+					_exit(-1);
+				}
+				if (pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM)) {
+					LOGINFO("Error setting pthread_attr_setscope\n");
+					gui_err("restore_error=Error during restore process.");
+					close(progress_pipe_fd);
+					_exit(-1);
+				}
+				/*if (pthread_attr_setstacksize(&tattr, 524288)) {
+					LOGERR("Error setting pthread_attr_setstacksize\n");
+					close(progress_pipe_fd);
+					_exit(-1);
+				}*/
+				for (i = start_thread_id; i < 9; i++) {
+					sprintf(actual_filename, temp.c_str(), i, 0);
+					if (TWFunc::Path_Exists(actual_filename)) {
+						thread_count++;
+						tars[i].basefn = basefn;
+						tars[i].setpassword(password);
+						tars[i].thread_id = i;
+						tars[i].progress_pipe_fd = progress_pipe_fd;
+						tars[i].part_settings = part_settings;
+						LOGINFO("Creating extract thread ID %i\n", i);
+						ret = pthread_create(&tar_thread[i], &tattr, extractMulti, (void*)&tars[i]);
+						if (ret) {
+							LOGINFO("Unable to create %i thread for extraction! %i\nContinuing in same thread (restore will be slower).\n", i, ret);
+							if (extractMulti((void*)&tars[i]) != 0) {
+								LOGINFO("Error extracting backup in thread %i.\n", i);
+								gui_err("restore_error=Error during restore process.");
+								close(progress_pipe_fd);
+								_exit(-1);
+							} else {
+								tars[i].thread_id = i + 1;
+							}
+						}
+						usleep(100000); // Need a short delay before starting the next thread or the threads will never finish for some reason.
+					} else {
+						break;
+					}
+				}
+				for (i = start_thread_id; i < thread_count + start_thread_id; i++) {
+					if (tars[i].thread_id == i) {
+						if (pthread_join(tar_thread[i], &thread_return)) {
+							LOGINFO("Error joining thread %i\n", i);
+							gui_err("restore_error=Error during restore process.");
+							close(progress_pipe_fd);
+							_exit(-1);
+						} else {
+							LOGINFO("Joined thread %i.\n", i);
+							ret = (int)(intptr_t)thread_return;
+							if (ret != 0) {
+								thread_error = 1;
+								LOGINFO("Thread %i returned an error %i.\n", i, ret);
+								gui_err("restore_error=Error during restore process.");
+								close(progress_pipe_fd);
+								_exit(-1);
+							}
+						}
+					} else {
+						LOGINFO("Skipping joining thread %i because of pthread failure.\n", i);
+					}
+				}
+				if (thread_error) {
+					LOGINFO("Error returned by one or more threads.\n");
+					gui_err("restore_error=Error during restore process.");
+					close(progress_pipe_fd);
+					_exit(-1);
+				}
+				LOGINFO("Finished encrypted restore.\n");
+				close(progress_pipe_fd);
+				_exit(0);
+			}
+		}
+		else // parent process
+		{
+			unsigned long long fs, size_backup = 0;
+
+			// Parent closes output side
+			close(progress_pipe[1]);
+
+			// Read progress data from children
+			while (read(progress_pipe[0], &fs, sizeof(fs)) > 0) {
+				size_backup += fs;
+				part_settings->progress->UpdateSize(size_backup);
+			}
+			close(progress_pipe[0]);
+			part_settings->progress->UpdateDisplayDetails(true);
+
+			if (TWFunc::Wait_For_Child(tar_fork_pid, &status, "extractTarFork()") != 0)
+				return -1;
+		}
+	}
+	else // fork has failed
+	{
+		close(progress_pipe[0]);
+		close(progress_pipe[1]);
+		LOGINFO("extract tar failed to fork.\n");
+		return -1;
+	}
+	return 0;
+}
+
+int twrpTar::Generate_TarList(string Path, std::vector<TarListStruct> *TarList, unsigned long long *Target_Size, unsigned *thread_id) {
+	DIR* d;
+	struct dirent* de;
+	struct stat st;
+	string FileName;
+	struct TarListStruct TarItem;
+	int ret, file_count;
+	file_count = 0;
+
+	d = opendir(Path.c_str());
+	if (d == NULL) {
+		gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(Path)(strerror(errno)));
+		closedir(d);
+		return -1;
+	}
+	while ((de = readdir(d)) != NULL) {
+		FileName = Path + "/" + de->d_name;
+
+		if (de->d_type == DT_BLK || de->d_type == DT_CHR || backup_exclusions->check_skip_dirs(FileName))
+			continue;
+		TarItem.fn = FileName;
+		TarItem.thread_id = *thread_id;
+		if (de->d_type == DT_DIR) {
+			TarList->push_back(TarItem);
+			ret = Generate_TarList(FileName, TarList, Target_Size, thread_id);
+			if (ret < 0)
+				return -1;
+			file_count += ret;
+		} else if (de->d_type == DT_REG || de->d_type == DT_LNK) {
+			stat(FileName.c_str(), &st);
+			TarList->push_back(TarItem);
+			if (de->d_type == DT_REG) {
+				file_count++;
+				Archive_Current_Size += st.st_size;
+			}
+			if (Archive_Current_Size != 0 && *Target_Size != 0 && Archive_Current_Size > *Target_Size) {
+				*thread_id = *thread_id + 1;
+				Archive_Current_Size = 0;
+			}
+		}
+	}
+	closedir(d);
+	return file_count;
+}
+
+int twrpTar::extractTar() {
+	char* charRootDir = (char*) tardir.c_str();
+	if (openTar() == -1)
+		return -1;
+	if (tar_extract_all(t, charRootDir, &progress_pipe_fd) != 0) {
+		LOGINFO("Unable to extract tar archive '%s'\n", tarfn.c_str());
+		gui_err("restore_error=Error during restore process.");
+		return -1;
+	}
+	if (tar_close(t) != 0) {
+		LOGINFO("Unable to close tar file\n");
+		gui_err("restore_error=Error during restore process.");
+		return -1;
+	}
+#ifndef BUILD_TWRPTAR_MAIN
+	if (part_settings->adbbackup) {
+		if (!twadbbu::Write_TWEOF())
+			return -1;
+	}
+#endif
+	return 0;
+}
+
+int twrpTar::extract() {
+	if (!part_settings->adbbackup)  {
+		LOGINFO("Setting archive type\n");
+		Set_Archive_Type(TWFunc::Get_File_Type(tarfn));
+	}
+	else {
+		if (part_settings->adb_compression == 1) 
+			current_archive_type = COMPRESSED;
+		else
+			current_archive_type = UNCOMPRESSED;
+	}
+
+	if (current_archive_type == COMPRESSED) {
+		//if you return the extractTGZ function directly, stack crashes happen
+		LOGINFO("Extracting gzipped tar\n");
+		int ret = extractTar();
+		return ret;
+	} else if (current_archive_type == ENCRYPTED) {
+		int ret = TWFunc::Try_Decrypting_File(tarfn, password);
+		if (ret < 1) {
+			gui_msg(Msg(msg::kError, "fail_decrypt_tar=Failed to decrypt tar file '{1}'")(tarfn));
+			return -1;
+		}
+		if (ret == 1) {
+			LOGINFO("Decrypted file is not in tar format.\n");
+			gui_err("restore_error=Error during restore process.");
+			return -1;
+		}
+		if (ret == 3) {
+			LOGINFO("Extracting encrypted and compressed tar.\n");
+			current_archive_type = COMPRESSED_ENCRYPTED;
+		} else
+			LOGINFO("Extracting encrypted tar.\n");
+		return extractTar();
+	} else {
+		LOGINFO("Extracting uncompressed tar\n");
+		return extractTar();
+	}
+}
+
+int twrpTar::tarList(std::vector<TarListStruct> *TarList, unsigned thread_id) {
+	struct stat st;
+	char buf[PATH_MAX];
+	int list_size = TarList->size(), i = 0, archive_count = 0;
+	string temp;
+	char actual_filename[PATH_MAX];
+	unsigned long long fs;
+
+	if (split_archives) {
+		basefn = tarfn;
+		temp = basefn + "%i%02i";
+		sprintf(actual_filename, temp.c_str(), thread_id, archive_count);
+		tarfn = actual_filename;
+		include_root_dir = true;
+	} else {
+		include_root_dir = false;
+	}
+
+	if (part_settings->adbbackup)
+	    LOGINFO("Writing tar file '%s' to adb backup\n", tarfn.c_str());
+	else
+	    LOGINFO("Creating tar file '%s'\n", tarfn.c_str());
+
+	if (createTar() != 0) {
+		LOGINFO("Error creating tar '%s' for thread %i\n", tarfn.c_str(), thread_id);
+		gui_err("backup_error=Error creating backup.");
+		return -2;
+	}
+	Archive_Current_Size = 0;
+
+	while (i < list_size) {
+		if (TarList->at(i).thread_id == thread_id) {
+			strcpy(buf, TarList->at(i).fn.c_str());
+			lstat(buf, &st);
+			if (S_ISREG(st.st_mode)) { // item is a regular file
+				fs = (unsigned long long)(st.st_size);
+				if (split_archives && Archive_Current_Size + fs > MAX_ARCHIVE_SIZE) {
+					if (closeTar() != 0) {
+						LOGINFO("Error closing '%s' on thread %i\n", tarfn.c_str(), thread_id);
+						gui_err("backup_error=Error creating backup.");
+						return -3;
+					}
+					archive_count++;
+					gui_msg(Msg("split_thread=Splitting thread ID {1} into archive {2}")(thread_id)(archive_count + 1));
+					if (archive_count > 99) {
+						LOGINFO("Too many archives for thread %i\n", thread_id);
+						gui_err("backup_error=Error creating backup.");
+						return -4;
+					}
+					sprintf(actual_filename, temp.c_str(), thread_id, archive_count);
+					tarfn = actual_filename;
+					if (createTar() != 0) {
+						LOGINFO("Error creating tar '%s' for thread %i\n", tarfn.c_str(), thread_id);
+						gui_err("backup_error=Error creating backup.");
+						return -2;
+					}
+					Archive_Current_Size = 0;
+				}
+				Archive_Current_Size += fs;
+				fs = 0; // Sending a 0 size to the pipe tells it to increment the file counter
+				write(progress_pipe_fd, &fs, sizeof(fs));
+			}
+			LOGINFO("addFile '%s' including root: %i\n", buf, include_root_dir);
+			if (addFile(buf, include_root_dir) != 0) {
+				LOGINFO("Error adding file '%s' to '%s'\n", buf, tarfn.c_str());
+				gui_err("backup_error=Error creating backup.");
+				return -1;
+			}
+		}
+		i++;
+	}
+	if (closeTar() != 0) {
+		LOGINFO("Error closing '%s' on thread %i\n", tarfn.c_str(), thread_id);
+		gui_err("backup_error=Error creating backup.");
+		return -3;
+	}
+	LOGINFO("Thread id %i tarList done, %i archives.\n", thread_id, archive_count);
+	return 0;
+}
+
+void* twrpTar::createList(void *cookie) {
+	twrpTar* threadTar = (twrpTar*) cookie;
+	if (threadTar->tarList(threadTar->ItemList, threadTar->thread_id) != 0) {
+		LOGINFO("ERROR tarList for thread ID %i\n", threadTar->thread_id);
+		return (void*)-2;
+	}
+	LOGINFO("Thread ID %i finished successfully.\n", threadTar->thread_id);
+	return (void*)0;
+}
+
+void* twrpTar::extractMulti(void *cookie) {
+	twrpTar* threadTar = (twrpTar*) cookie;
+	int archive_count = 0;
+	string temp = threadTar->basefn + "%i%02i";
+	char actual_filename[255];
+	sprintf(actual_filename, temp.c_str(), threadTar->thread_id, archive_count);
+	while (TWFunc::Path_Exists(actual_filename)) {
+		threadTar->tarfn = actual_filename;
+		if (threadTar->extract() != 0) {
+			LOGINFO("Error extracting '%s' in thread ID %i\n", actual_filename, threadTar->thread_id);
+			return (void*)-2;
+		}
+		archive_count++;
+		if (archive_count > 99)
+			break;
+		sprintf(actual_filename, temp.c_str(), threadTar->thread_id, archive_count);
+	}
+	LOGINFO("Thread ID %i finished successfully.\n", threadTar->thread_id);
+	return (void*)0;
+}
+
+int twrpTar::addFilesToExistingTar(vector <string> files, string fn) {
+	char* charTarFile = (char*) fn.c_str();
+
+	if (tar_open(&t, charTarFile, NULL, O_CLOEXEC | O_RDONLY | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) == -1)
+		return -1;
+	removeEOT(charTarFile);
+	if (tar_open(&t, charTarFile, NULL, O_CLOEXEC | O_WRONLY | O_APPEND | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) == -1)
+		return -1;
+	for (unsigned int i = 0; i < files.size(); ++i) {
+		char* file = (char*) files.at(i).c_str();
+		if (tar_append_file(t, file, file) == -1)
+			return -1;
+	}
+	if (tar_append_eof(t) == -1)
+		return -1;
+	if (tar_close(t) == -1)
+		return -1;
+	return 0;
+}
+
+int twrpTar::createTar() {
+	char* charTarFile = (char*) tarfn.c_str();
+	char* charRootDir = (char*) tardir.c_str();
+
+	if (use_encryption && use_compression) {
+		// Compressed and encrypted
+		current_archive_type = COMPRESSED_ENCRYPTED;
+		LOGINFO("Using encryption and compression...\n");
+		int i, pipes[4];
+
+		if (pipe2(pipes, O_CLOEXEC) < 0) {
+			LOGINFO("Error creating first pipe\n");
+			gui_err("backup_error=Error creating backup.");
+			return -1;
+		}
+		if (pipe2(pipes + 2, O_CLOEXEC) < 0) {
+			LOGINFO("Error creating second pipe\n");
+			gui_err("backup_error=Error creating backup.");
+			return -1;
+		}
+		output_fd = open(tarfn.c_str(), O_CLOEXEC | O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+		if (output_fd < 0) {
+			gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(tarfn)(strerror(errno)));
+			for (i = 0; i < 4; i++)
+				close(pipes[i]); // close all
+			return -1;
+		}
+		pigz_pid = fork();
+
+		if (pigz_pid < 0) {
+			LOGINFO("pigz fork() failed\n");
+			gui_err("backup_error=Error creating backup.");
+			close(output_fd);
+			for (i = 0; i < 4; i++)
+				close(pipes[i]); // close all
+			return -1;
+		} else if (pigz_pid == 0) {
+			// pigz Child
+			dup2(pipes[0], STDIN_FILENO);
+			dup2(pipes[3], STDOUT_FILENO);
+			if (execlp("pigz", "pigz", "-", NULL) < 0) {
+				LOGINFO("execlp pigz ERROR!\n");
+				gui_err("backup_error=Error creating backup.");
+				_exit(-1);
+			}
+		} else {
+			// Parent
+			oaes_pid = fork();
+
+			if (oaes_pid < 0) {
+				LOGINFO("openaes fork() failed\n");
+				gui_err("backup_error=Error creating backup.");
+				close(output_fd);
+				for (i = 0; i < 4; i++)
+					close(pipes[i]); // close all
+				return -1;
+			} else if (oaes_pid == 0) {
+				// openaes Child
+				dup2(pipes[2], STDIN_FILENO);
+				dup2(output_fd, STDOUT_FILENO);
+				if (execlp("openaes", "openaes", "enc", "--key", password.c_str(), NULL) < 0) {
+					LOGINFO("execlp openaes ERROR!\n");
+					gui_err("backup_error=Error creating backup.");
+					_exit(-1);
+				}
+			} else {
+				// Parent
+				close(pipes[0]);
+				close(pipes[2]);
+				close(pipes[3]);
+				fd = pipes[1];
+				init_libtar_no_buffer(progress_pipe_fd);
+				tar_type.writefunc = write_tar_no_buffer;
+				if (tar_fdopen(&t, fd, charRootDir, &tar_type, O_CLOEXEC | O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) != 0) {
+					close(fd);
+					LOGINFO("tar_fdopen failed\n");
+					gui_err("backup_error=Error creating backup.");
+					return -1;
+				}
+				return 0;
+			}
+		}
+	} else if (use_compression) {
+		// Compressed
+		current_archive_type = COMPRESSED;
+		LOGINFO("Using compression...\n");
+		int pigzfd[2];
+		if (part_settings->adbbackup) {
+			LOGINFO("opening TW_ADB_BACKUP compressed stream\n");
+			output_fd = open(TW_ADB_BACKUP, O_WRONLY);
+		}
+		else {
+			output_fd = open(tarfn.c_str(), O_CLOEXEC | O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+		}
+		if (output_fd < 0) {
+			gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(tarfn)(strerror(errno)));
+			return -1;
+		}
+
+		if (pipe2(pigzfd, O_CLOEXEC) < 0) {
+			LOGINFO("Error creating pipe\n");
+			gui_err("backup_error=Error creating backup.");
+			close(output_fd);
+			return -1;
+		}
+		pigz_pid = fork();
+
+		if (pigz_pid < 0) {
+			LOGINFO("fork() failed\n");
+			gui_err("backup_error=Error creating backup.");
+			close(output_fd);
+			close(pigzfd[0]);
+			close(pigzfd[1]);
+			return -1;
+		} else if (pigz_pid == 0) {
+			// Child
+			dup2(pigzfd[0], STDIN_FILENO); // remap stdin
+			dup2(output_fd, STDOUT_FILENO); // remap stdout to output file
+			if (execlp("pigz", "pigz", "-", NULL) < 0) {
+				LOGINFO("execlp pigz ERROR!\n");
+				gui_err("backup_error=Error creating backup.");
+				_exit(-1);
+			}
+		} else {
+			// Parent
+			close(pigzfd[0]); // close parent input
+			fd = pigzfd[1];   // copy parent output
+			init_libtar_no_buffer(progress_pipe_fd);
+			tar_type.writefunc = write_tar_no_buffer;
+			if (tar_fdopen(&t, fd, charRootDir, &tar_type, O_CLOEXEC | O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) != 0) {
+				close(fd);
+				LOGINFO("tar_fdopen failed\n");
+				gui_err("backup_error=Error creating backup.");
+				return -1;
+			}
+		}
+	} else if (use_encryption) {
+		// Encrypted
+		current_archive_type = ENCRYPTED;
+		LOGINFO("Using encryption...\n");
+		int oaesfd[2];
+		output_fd = open(tarfn.c_str(), O_CLOEXEC | O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+		if (output_fd < 0) {
+			gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(tarfn)(strerror(errno)));
+			return -1;
+		}
+		if (pipe2(oaesfd, O_CLOEXEC) < 0) {
+			LOGINFO("Error creating pipe\n");
+			gui_err("backup_error=Error creating backup.");
+			close(output_fd);
+			return -1;
+		}
+		oaes_pid = fork();
+
+		if (oaes_pid < 0) {
+			LOGINFO("fork() failed\n");
+			gui_err("backup_error=Error creating backup.");
+			close(output_fd);
+			close(oaesfd[0]);
+			close(oaesfd[1]);
+			return -1;
+		} else if (oaes_pid == 0) {
+			// Child
+			dup2(oaesfd[0], STDIN_FILENO); // remap stdin
+			dup2(output_fd, STDOUT_FILENO); // remap stdout to output file
+			if (execlp("openaes", "openaes", "enc", "--key", password.c_str(), NULL) < 0) {
+				LOGINFO("execlp openaes ERROR!\n");
+				gui_err("backup_error=Error creating backup.");
+				_exit(-1);
+			}
+		} else {
+			// Parent
+			close(oaesfd[0]); // close parent input
+			fd = oaesfd[1];   // copy parent output
+			init_libtar_no_buffer(progress_pipe_fd);
+			tar_type.writefunc = write_tar_no_buffer;
+			if (tar_fdopen(&t, fd, charRootDir, &tar_type, O_CLOEXEC | O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) != 0) {
+				close(fd);
+				LOGINFO("tar_fdopen failed\n");
+				gui_err("backup_error=Error creating backup.");
+				return -1;
+			}
+			return 0;
+		}
+	} else {
+		// Not compressed or encrypted
+		current_archive_type = UNCOMPRESSED;
+		init_libtar_buffer(0, progress_pipe_fd);
+		if (part_settings->adbbackup) {
+			LOGINFO("Opening TW_ADB_BACKUP uncompressed stream\n");
+			tar_type.writefunc = write_tar_no_buffer;
+			output_fd = open(TW_ADB_BACKUP, O_WRONLY);
+			if(tar_fdopen(&t, output_fd, charRootDir, &tar_type, O_CLOEXEC | O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) != 0) {
+				close(output_fd);
+				LOGERR("tar_fdopen failed\n");
+				return -1;
+			}
+		}
+		else {
+			tar_type.writefunc = write_tar;
+			if (tar_open(&t, charTarFile, &tar_type, O_CLOEXEC | O_WRONLY | O_CREAT | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) == -1) {
+				LOGERR("tar_open error opening '%s'\n", tarfn.c_str());
+				gui_err("backup_error=Error creating backup.");
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
+int twrpTar::openTar() {
+	char* charRootDir = (char*) tardir.c_str();
+	char* charTarFile = (char*) tarfn.c_str();
+	string Password;
+
+	if (current_archive_type == COMPRESSED_ENCRYPTED) {
+		LOGINFO("Opening encrypted and compressed backup...\n");
+		int i, pipes[4];
+		input_fd = open(tarfn.c_str(), O_CLOEXEC | O_RDONLY | O_LARGEFILE);
+		if (input_fd < 0) {
+			gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(tarfn)(strerror(errno)));
+			return -1;
+		}
+
+		if (pipe2(pipes, O_CLOEXEC) < 0) {
+			LOGINFO("Error creating first pipe\n");
+			gui_err("restore_error=Error during restore process.");
+			close(input_fd);
+			return -1;
+		}
+		if (pipe2(pipes + 2, O_CLOEXEC) < 0) {
+			LOGINFO("Error creating second pipe\n");
+			gui_err("restore_error=Error during restore process.");
+			close(pipes[0]);
+			close(pipes[1]);
+			close(input_fd);
+			return -1;
+		}
+		oaes_pid = fork();
+
+		if (oaes_pid < 0) {
+			LOGINFO("pigz fork() failed\n");
+			gui_err("restore_error=Error during restore process.");
+			close(input_fd);
+			for (i = 0; i < 4; i++)
+				close(pipes[i]); // close all
+			return -1;
+		} else if (oaes_pid == 0) {
+			// openaes Child
+			dup2(input_fd, STDIN_FILENO);
+			dup2(pipes[1], STDOUT_FILENO);
+			if (execlp("openaes", "openaes", "dec", "--key", password.c_str(), NULL) < 0) {
+				LOGINFO("execlp openaes ERROR!\n");
+				gui_err("restore_error=Error during restore process.");
+				_exit(-1);
+			}
+		} else {
+			// Parent
+			pigz_pid = fork();
+
+			if (pigz_pid < 0) {
+				LOGINFO("openaes fork() failed\n");
+				gui_err("restore_error=Error during restore process.");
+				close(input_fd);
+				for (i = 0; i < 4; i++)
+					close(pipes[i]); // close all
+				return -1;
+			} else if (pigz_pid == 0) {
+				// pigz Child
+				dup2(pipes[0], STDIN_FILENO);
+				dup2(pipes[3], STDOUT_FILENO);
+				if (execlp("pigz", "pigz", "-d", "-c", NULL) < 0) {
+					LOGINFO("execlp pigz ERROR!\n");
+					gui_err("restore_error=Error during restore process.");
+					_exit(-1);
+				}
+			} else {
+				// Parent
+				close(pipes[0]); // Close pipes not used by parent
+				close(pipes[1]);
+				close(pipes[3]);
+				fd = pipes[2];
+				if (tar_fdopen(&t, fd, charRootDir, NULL, O_CLOEXEC | O_RDONLY | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) != 0) {
+					close(fd);
+					LOGINFO("tar_fdopen failed\n");
+					gui_err("restore_error=Error during restore process.");
+					return -1;
+				}
+			}
+		}
+	} else if (current_archive_type == ENCRYPTED) {
+		LOGINFO("Opening encrypted backup...\n");
+		int oaesfd[2];
+		input_fd = open(tarfn.c_str(), O_CLOEXEC | O_RDONLY | O_LARGEFILE);
+		if (input_fd < 0) {
+			gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(tarfn)(strerror(errno)));
+			return -1;
+		}
+
+		if (pipe2(oaesfd, O_CLOEXEC) < 0) {
+			LOGINFO("Error creating pipe\n");
+			gui_err("restore_error=Error during restore process.");
+			close(input_fd);
+			return -1;
+		}
+
+		oaes_pid = fork();
+		if (oaes_pid < 0) {
+			LOGINFO("fork() failed\n");
+			gui_err("restore_error=Error during restore process.");
+			close(input_fd);
+			close(oaesfd[0]);
+			close(oaesfd[1]);
+			return -1;
+		} else if (oaes_pid == 0) {
+			// Child
+			dup2(oaesfd[1], STDOUT_FILENO); // remap stdout
+			dup2(input_fd, STDIN_FILENO); // remap input fd to stdin
+			if (execlp("openaes", "openaes", "dec", "--key", password.c_str(), NULL) < 0) {
+				LOGINFO("execlp openaes ERROR!\n");
+				gui_err("restore_error=Error during restore process.");
+				_exit(-1);
+			}
+		} else {
+			// Parent
+			close(oaesfd[1]); // close parent output
+			fd = oaesfd[0];   // copy parent input
+			if (tar_fdopen(&t, fd, charRootDir, NULL, O_CLOEXEC | O_RDONLY | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) != 0) {
+				close(fd);
+				LOGINFO("tar_fdopen failed\n");
+				gui_err("restore_error=Error during restore process.");
+				return -1;
+			}
+		}
+	} else if (current_archive_type == COMPRESSED) {
+		int pigzfd[2];
+
+		LOGINFO("Opening gzip compressed tar...\n");
+		if (part_settings->adbbackup)  {
+			LOGINFO("opening TW_ADB_RESTORE compressed stream\n");
+			input_fd = open(TW_ADB_RESTORE, O_CLOEXEC | O_RDONLY | O_LARGEFILE);
+		}
+		else
+			input_fd = open(tarfn.c_str(), O_CLOEXEC | O_RDONLY | O_LARGEFILE);
+
+		if (input_fd < 0) {
+			gui_msg(Msg(msg::kError, "error_opening_strerr=Error opening: '{1}' ({2})")(tarfn)(strerror(errno)));
+			return -1;
+		}
+
+		if (pipe2(pigzfd, O_CLOEXEC) < 0) {
+			LOGINFO("Error creating pipe\n");
+			gui_err("restore_error=Error during restore process.");
+			close(input_fd);
+			return -1;
+		}
+
+		pigz_pid = fork();
+		if (pigz_pid < 0) {
+			LOGINFO("fork() failed\n");
+			gui_err("restore_error=Error during restore process.");
+			close(input_fd);
+			close(pigzfd[0]);
+			close(pigzfd[1]);
+			return -1;
+		} else if (pigz_pid == 0) {
+			// Child
+			dup2(pigzfd[1], STDOUT_FILENO); // remap stdout
+			dup2(input_fd, STDIN_FILENO); // remap input fd to stdin
+			if (execlp("pigz", "pigz", "-d", "-c", NULL) < 0) {
+				LOGINFO("execlp pigz ERROR!\n");
+				gui_err("restore_error=Error during restore process.");
+				_exit(-1);
+			}
+		} else {
+			// Parent
+			close(pigzfd[1]); // close parent output
+			fd = pigzfd[0];   // copy parent input
+			if (tar_fdopen(&t, fd, charRootDir, NULL, O_CLOEXEC | O_RDONLY | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) != 0) {
+				close(fd);
+				LOGINFO("tar_fdopen failed\n");
+				gui_err("restore_error=Error during restore process.");
+				return -1;
+			}
+		}
+	} else  {
+		if (part_settings->adbbackup) {
+			LOGINFO("Opening TW_ADB_RESTORE uncompressed stream\n");
+			input_fd = open(TW_ADB_RESTORE, O_RDONLY);
+			if (tar_fdopen(&t, input_fd, charRootDir, NULL, O_CLOEXEC | O_RDONLY | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) != 0) {
+				LOGERR("Unable to open tar archive '%s'\n", charTarFile);
+				gui_err("restore_error=Error during restore process.");
+				return -1;
+			}
+		}
+		else {
+			if (tar_open(&t, charTarFile, NULL, O_CLOEXEC | O_RDONLY | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, TWTAR_FLAGS) != 0) {
+				LOGERR("Unable to open tar archive '%s'\n", charTarFile);
+				gui_err("restore_error=Error during restore process.");
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
+string twrpTar::Strip_Root_Dir(string Path) {
+	string temp;
+	size_t slash;
+
+	if (Path.substr(0, 1) == "/")
+		temp = Path.substr(1, Path.size() - 1);
+	else
+		temp = Path;
+	slash = temp.find("/");
+	if (slash == string::npos)
+		return temp;
+	else {
+		string stripped;
+
+		stripped = temp.substr(slash, temp.size() - slash);
+		return stripped;
+	}
+	return temp;
+}
+
+int twrpTar::addFile(string fn, bool include_root) {
+	char* charTarFile = (char*) fn.c_str();
+	if (include_root) {
+		if (tar_append_file(t, charTarFile, NULL) == -1)
+			return -1;
+	} else {
+		string temp = Strip_Root_Dir(fn);
+		char* charTarPath = (char*) temp.c_str();
+		if (tar_append_file(t, charTarFile, charTarPath) == -1)
+			return -1;
+	}
+	return 0;
+}
+
+int twrpTar::closeTar() {
+	LOGINFO("Closing tar\n");
+	flush_libtar_buffer(t->fd);
+	if (tar_append_eof(t) != 0) {
+		LOGINFO("tar_append_eof(): %s\n", strerror(errno));
+		tar_close(t);
+		return -1;
+	}
+	if (tar_close(t) != 0) {
+		LOGINFO("Unable to close tar archive: '%s'\n", tarfn.c_str());
+		return -1;
+	}
+	if (current_archive_type > 0) {
+		int status;
+		if (pigz_pid > 0 && TWFunc::Wait_For_Child(pigz_pid, &status, "pigz") != 0)
+			return -1;
+		if (oaes_pid > 0 && TWFunc::Wait_For_Child(oaes_pid, &status, "openaes") != 0)
+			return -1;
+	}
+	free_libtar_buffer();
+	if (!part_settings->adbbackup) {
+		if (use_compression && !use_encryption) {
+			string gzname = tarfn + ".gz";
+			if (TWFunc::Path_Exists(gzname)) {
+				rename(gzname.c_str(), tarfn.c_str());
+			}
+		}
+		if (TWFunc::Get_File_Size(tarfn) == 0) {
+			gui_msg(Msg(msg::kError, "backup_size=Backup file size for '{1}' is 0 bytes.")(tarfn));
+			return -1;
+		}
+#ifndef BUILD_TWRPTAR_MAIN
+		tw_set_default_metadata(tarfn.c_str());
+#endif
+	}
+	else {
+#ifndef BUILD_TWRPTAR_MAIN
+		if (!twadbbu::Write_TWEOF())
+			return -1;
+#endif
+	}
+	if (input_fd >= 0)
+		close(input_fd);
+	if (output_fd >= 0)
+		close(output_fd);
+	return 0;
+}
+
+int twrpTar::removeEOT(string tarFile) {
+	char* charTarFile = (char*) tarFile.c_str();
+	off_t tarFileEnd = 0;
+	while (th_read(t) == 0) {
+		if (TH_ISREG(t))
+			tar_skip_regfile(t);
+		tarFileEnd = lseek(t->fd, 0, SEEK_CUR);
+	}
+	if (tar_close(t) == -1)
+		return -1;
+	if (tarFileEnd > 0 && truncate(charTarFile, tarFileEnd) == -1)
+		return -1;
+	return 0;
+}
+
+int twrpTar::entryExists(string entry) {
+	char* searchstr = (char*)entry.c_str();
+	int ret;
+
+	Set_Archive_Type(TWFunc::Get_File_Type(tarfn));
+
+	if (openTar() == -1)
+		ret = 0;
+	else
+		ret = tar_find(t, searchstr);
+
+	if (closeTar() != 0)
+		LOGINFO("Unable to close tar after searching for entry.\n");
+
+	return ret;
+}
+
+unsigned long long twrpTar::get_size() {
+	if (part_settings->adbbackup || TWFunc::Path_Exists(tarfn)) {
+		LOGINFO("Single archive\n");
+		return uncompressedSize(tarfn);
+	} else {
+		LOGINFO("Multiple archives\n");
+		string temp;
+		char actual_filename[255];
+		int archive_count = 0;
+		unsigned long long total_restore_size = 0;
+
+		basefn = tarfn;
+		temp = basefn + "%i%02i";
+		tarfn += "000";
+		thread_id = 0;
+		sprintf(actual_filename, temp.c_str(), thread_id, archive_count);
+		if (!part_settings->adbbackup) {
+			if (!TWFunc::Path_Exists(actual_filename)) {
+				LOGERR("Unable to locate '%s' or '%s'\n", basefn.c_str(), tarfn.c_str());
+				return 0;
+			}
+			for (int i = 0; i < 9; i++) {
+				archive_count = 0;
+				sprintf(actual_filename, temp.c_str(), i, archive_count);
+				while (TWFunc::Path_Exists(actual_filename)) {
+					total_restore_size += uncompressedSize(actual_filename);
+					archive_count++;
+					if (archive_count > 99)
+						break;
+					sprintf(actual_filename, temp.c_str(), i, archive_count);
+				}
+			}
+	#ifndef BUILD_TWRPTAR_MAIN
+	        if (!part_settings->adbbackup) {
+			InfoManager backup_info(tarfn + ".info");
+			backup_info.SetValue("backup_size", total_restore_size);
+			backup_info.SetValue("backup_type", current_archive_type);
+			backup_info.SaveValues();
+	        }
+	#endif //ndef BUILD_TWRPTAR_MAIN
+		}
+		return total_restore_size;
+	}
+	return 0;
+}
+
+unsigned long long twrpTar::uncompressedSize(string filename) {
+	unsigned long long total_size = 0;
+	string Tar, Command, result;
+	vector<string> split;
+
+	Set_Archive_Type(TWFunc::Get_File_Type(tarfn));
+	if (current_archive_type == UNCOMPRESSED) {
+		total_size = TWFunc::Get_File_Size(filename);
+	} else if (current_archive_type == COMPRESSED) {
+		// Compressed
+		Command = "pigz -l '" + filename + "'";
+		/* if we set Command = "pigz -l " + tarfn + " | sed '1d' | cut -f5 -d' '";
+		we get the uncompressed size at once. */
+		TWFunc::Exec_Cmd(Command, result, false);
+		if (!result.empty()) {
+			/* Expected output:
+			compressed original  reduced name
+			95855838   179403776 -1.3%   data.yaffs2.win
+			^
+			split[5]
+			*/
+			split = TWFunc::split_string(result, ' ', true);
+			if (split.size() > 4)
+				total_size = atoi(split[5].c_str());
+		}
+	} else if (current_archive_type == COMPRESSED_ENCRYPTED) {
+		// File is encrypted and may be compressed
+		int ret = TWFunc::Try_Decrypting_File(filename, password);
+		if (ret < 1) {
+			gui_msg(Msg(msg::kError, "fail_decrypt_tar=Failed to decrypt tar file '{1}'")(tarfn));
+			total_size = TWFunc::Get_File_Size(filename);
+		} else if (ret == 1) {
+			LOGERR("Decrypted file is not in tar format.\n");
+			total_size = TWFunc::Get_File_Size(filename);
+		} else if (ret == 3) {
+			Command = "openaes dec --key \"" + password + "\" --in '" + filename + "' | pigz -l";
+			/* if we set Command = "pigz -l " + tarfn + " | sed '1d' | cut -f5 -d' '";
+			we get the uncompressed size at once. */
+			TWFunc::Exec_Cmd(Command, result, false);
+			if (!result.empty()) {
+				LOGINFO("result was: '%s'\n", result.c_str());
+				/* Expected output:
+				compressed original  reduced name
+				95855838   179403776 -1.3%   data.yaffs2.win
+				^
+				split[5]
+				*/
+				split = TWFunc::split_string(result, ' ', true);
+				if (split.size() > 4)
+					total_size = atoi(split[5].c_str());
+			}
+		} else {
+			total_size = TWFunc::Get_File_Size(filename);
+		}
+	}
+
+	return total_size;
+}
+
+extern "C" ssize_t write_tar(int fd, const void *buffer, size_t size) {
+	return (ssize_t) write_libtar_buffer(fd, buffer, size);
+}
+
+extern "C" ssize_t write_tar_no_buffer(int fd, const void *buffer, size_t size) {
+	return (ssize_t) write_libtar_no_buffer(fd, buffer, size);
+}
diff --git a/twrpTar.h b/twrpTar.h
new file mode 100644
index 0000000..2348487
--- /dev/null
+++ b/twrpTar.h
@@ -0,0 +1,25 @@
+/*
+	Copyright 2012 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _TWRPTAR_HEADER
+#define _TWRPTAR_HEADER
+
+ssize_t write_tar(int fd, const void *buffer, size_t size);
+ssize_t write_tar_no_buffer(int fd, const void *buffer, size_t size);
+
+#endif  // _TWRPTAR_HEADER
diff --git a/twrpTar.hpp b/twrpTar.hpp
new file mode 100644
index 0000000..eb82fa8
--- /dev/null
+++ b/twrpTar.hpp
@@ -0,0 +1,110 @@
+/*
+        Copyright 2012 to 2016 bigbiff/Dees_Troy TeamWin
+        This file is part of TWRP/TeamWin Recovery Project.
+
+        TWRP is free software: you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as published by
+        the Free Software Foundation, either version 3 of the License, or
+        (at your option) any later version.
+
+        TWRP is distributed in the hope that it will be useful,
+        but WITHOUT ANY WARRANTY; without even the implied warranty of
+        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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+extern "C" {
+	#include "libtar/libtar.h"
+}
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fstream>
+#include <string>
+#include <vector>
+#include "exclude.hpp"
+#include "progresstracking.hpp"
+#include "partitions.hpp"
+#include "twrp-functions.hpp"
+
+using namespace std;
+
+struct TarListStruct {
+	std::string fn;
+	unsigned thread_id;
+};
+
+struct thread_data_struct {
+	std::vector<TarListStruct> *TarList;
+	unsigned thread_id;
+};
+
+class twrpTar {
+public:
+	twrpTar();
+	virtual ~twrpTar();
+	int createTarFork(pid_t *tar_fork_pid);
+	int extractTarFork();
+	void setfn(string fn);
+	void setdir(string dir);
+	void setsize(unsigned long long backup_size);
+	void setpassword(string pass);
+	unsigned long long get_size();
+	void Set_Archive_Type(Archive_Type archive_type);
+
+public:
+	int use_encryption;
+	int userdata_encryption;
+	int use_compression;
+	int split_archives;
+	string backup_name;
+	int progress_pipe_fd;
+	string partition_name;
+	string backup_folder;
+	PartitionSettings *part_settings;
+	TWExclude *backup_exclusions;
+
+private:
+	int extract();
+	int addFilesToExistingTar(vector <string> files, string tarFile);
+	int createTar();
+	int addFile(string fn, bool include_root);
+	int entryExists(string entry);
+	int closeTar();
+	int removeEOT(string tarFile);
+	int extractTar();
+	string Strip_Root_Dir(string Path);
+	int openTar();
+	int Generate_TarList(string Path, std::vector<TarListStruct> *TarList, unsigned long long *Target_Size, unsigned *thread_id);
+	static void* createList(void *cookie);
+	static void* extractMulti(void *cookie);
+	int tarList(std::vector<TarListStruct> *TarList, unsigned thread_id);
+	unsigned long long uncompressedSize(string filename);
+	static void Signal_Kill(int signum);
+
+	enum Archive_Type current_archive_type;
+	unsigned long long Archive_Current_Size;
+	unsigned long long Total_Backup_Size;
+	bool include_root_dir;
+	TAR *t;
+	tartype_t tar_type; // Only used in createTar() but variable must persist while the tar is open
+	int fd;
+	int input_fd;                                                                   // this stores the fd for libtar to write to
+	pid_t pigz_pid;
+	pid_t oaes_pid;
+	unsigned long long file_count;
+
+	string tardir;
+	string tarfn;
+	string basefn;
+	string password;
+
+	std::vector<TarListStruct> *ItemList;
+	int output_fd;                                                                  // this stores the output fd that gzip will read from
+	unsigned thread_id;
+};
diff --git a/twrpTarMain/Android.mk b/twrpTarMain/Android.mk
new file mode 100644
index 0000000..a80b4de
--- /dev/null
+++ b/twrpTarMain/Android.mk
@@ -0,0 +1,82 @@
+LOCAL_PATH:= $(call my-dir)
+
+# Build static binary
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	twrpTarMain.cpp \
+	../twrp-functions.cpp \
+	../twrpTar.cpp \
+	../tarWrite.c \
+	../exclude.cpp \
+	../progresstracking.cpp \
+	../gui/twmsg.cpp
+LOCAL_CFLAGS:= -g -c -W -DBUILD_TWRPTAR_MAIN
+
+LOCAL_C_INCLUDES += bionic
+
+LOCAL_STATIC_LIBRARIES := libc libtar_static libz
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
+    LOCAL_C_INCLUDES += external/stlport/stlport bionic/libstdc++/include
+    LOCAL_STATIC_LIBRARIES += libstlport_static
+endif
+LOCAL_STATIC_LIBRARIES += libstdc++
+
+LOCAL_C_INCLUDES += external/libselinux/include
+LOCAL_STATIC_LIBRARIES += libselinux
+
+ifneq ($(RECOVERY_SDCARD_ON_DATA),)
+	LOCAL_CFLAGS += -DRECOVERY_SDCARD_ON_DATA
+endif
+ifeq ($(TW_EXCLUDE_ENCRYPTED_BACKUPS), true)
+    LOCAL_CFLAGS += -DTW_EXCLUDE_ENCRYPTED_BACKUPS
+else
+	LOCAL_STATIC_LIBRARIES += libopenaes_static
+endif
+
+LOCAL_MODULE:= twrpTar_static
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_MODULE_TAGS:= optional
+LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities
+include $(BUILD_EXECUTABLE)
+
+
+# Build shared binary
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	twrpTarMain.cpp \
+	../twrp-functions.cpp \
+	../twrpTar.cpp \
+	../tarWrite.c \
+	../exclude.cpp \
+	../progresstracking.cpp \
+	../gui/twmsg.cpp
+LOCAL_CFLAGS:= -g -c -W -DBUILD_TWRPTAR_MAIN
+
+LOCAL_C_INCLUDES += bionic
+LOCAL_SHARED_LIBRARIES := libc libtar libz
+ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0)
+    LOCAL_C_INCLUDES += external/stlport/stlport bionic/libstdc++/include
+    LOCAL_SHARED_LIBRARIES += libstlport_static
+endif
+LOCAL_SHARED_LIBRARIES += libstdc++
+
+LOCAL_C_INCLUDES += external/libselinux/include
+LOCAL_SHARED_LIBRARIES += libselinux
+
+ifneq ($(RECOVERY_SDCARD_ON_DATA),)
+	LOCAL_CFLAGS += -DRECOVERY_SDCARD_ON_DATA
+endif
+ifeq ($(TW_EXCLUDE_ENCRYPTED_BACKUPS), true)
+    LOCAL_CFLAGS += -DTW_EXCLUDE_ENCRYPTED_BACKUPS
+else
+	LOCAL_SHARED_LIBRARIES += libopenaes
+endif
+
+LOCAL_MODULE:= twrpTar
+LOCAL_MODULE_TAGS:= optional
+LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities
+include $(BUILD_EXECUTABLE)
diff --git a/twrpTarMain/twrpTarMain.cpp b/twrpTarMain/twrpTarMain.cpp
new file mode 100755
index 0000000..2607a85
--- /dev/null
+++ b/twrpTarMain/twrpTarMain.cpp
@@ -0,0 +1,205 @@
+
+/*
+	Copyright 2014 TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "../twrp-functions.hpp"
+#include "../twrpTar.hpp"
+#include "../exclude.hpp"
+#include "../progresstracking.hpp"
+#include "../gui/gui.hpp"
+#include "../gui/twmsg.h"
+#include <string.h>
+
+void gui_msg(const char* text)
+{
+	if (text) {
+		Message msg = Msg(text);
+		gui_msg(msg);
+	}
+}
+
+void gui_warn(const char* text)
+{
+	if (text) {
+		Message msg = Msg(msg::kWarning, text);
+		gui_msg(msg);
+	}
+}
+
+void gui_err(const char* text)
+{
+	if (text) {
+		Message msg = Msg(msg::kError, text);
+		gui_msg(msg);
+	}
+}
+
+void gui_highlight(const char* text)
+{
+	if (text) {
+		Message msg = Msg(msg::kHighlight, text);
+		gui_msg(msg);
+	}
+}
+
+void gui_msg(Message msg)
+{
+	std::string output = msg;
+	output += "\n";
+	fputs(output.c_str(), stdout);
+}
+
+void usage() {
+	printf("twrpTar <action> [options]\n\n");
+	printf("actions: -c create\n");
+	printf("         -x extract\n\n");
+	printf(" -d    target directory\n");
+	printf(" -t    output file\n");
+	printf(" -m    skip media subfolder (has data media)\n");
+	printf(" -z    compress backup (/system/bin/pigz must be present)\n");
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+	printf(" -e    encrypt/decrypt backup followed by password (/system/bin/openaes must be present)\n");
+	printf(" -u    encrypt using userdata encryption (must be used with -e)\n");
+#endif
+	printf("\n\n");
+	printf("Example: twrpTar -c -d /cache -t /sdcard/test.tar\n");
+	printf("         twrpTar -x -d /cache -t /sdcard/test.tar\n");
+}
+
+int main(int argc, char **argv) {
+	twrpTar tar;
+	int use_encryption = 0, userdata_encryption = 0, has_data_media = 0, use_compression = 0, include_root = 0;
+	int i, action = 0;
+	unsigned j;
+	string Directory, Tar_Filename;
+	ProgressTracking progress(1);
+	pid_t tar_fork_pid = 0;
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+	string Password;
+#endif
+
+	if (argc < 2) {
+		usage();
+		return 0;
+	}
+
+	if (strcmp(argv[1], "-c") == 0)
+		action = 1; // create tar
+	else if (strcmp(argv[1], "-x") == 0)
+		action = 2; // extract tar
+	else {
+		printf("Invalid action '%s' specified.\n", argv[1]);
+		usage();
+		return -1;
+	}
+
+	for (i = 2; i < argc; i++) {
+		if (strcmp(argv[i], "-d") == 0) {
+			i++;
+			if (argc <= i) {
+				printf("No argument specified for %s\n", argv[i - 1]);
+				usage();
+				return -1;
+			} else {
+				Directory = argv[i];
+			}
+		} else if (strcmp(argv[i], "-t") == 0) {
+			i++;
+			if (argc <= i) {
+				printf("No argument specified for %s\n", argv[i - 1]);
+				usage();
+				return -1;
+			} else {
+				Tar_Filename = argv[i];
+			}
+		} else if (strcmp(argv[i], "-e") == 0) {
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+			i++;
+			if (argc <= i) {
+				printf("No argument specified for %s\n", argv[i - 1]);
+				usage();
+				return -1;
+			} else {
+				use_encryption = 1;
+				Password = argv[i];
+			}
+#else
+			printf("Encrypted tar file support not present\n");
+			usage();
+			return -1;
+#endif
+		} else if (strcmp(argv[i], "-m") == 0) {
+			if (action == 2)
+				printf("NOTE: %s option not needed when extracting.\n", argv[i]);
+			has_data_media = 1;
+		} else if (strcmp(argv[i], "-z") == 0) {
+			if (action == 2)
+				printf("NOTE: %s option not needed when extracting.\n", argv[i]);
+			use_compression = 1;
+		} else if (strcmp(argv[i], "-u") == 0) {
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+			if (action == 2)
+				printf("NOTE: %s option not needed when extracting.\n", argv[i]);
+			userdata_encryption = 1;
+#else
+			printf("Encrypted tar file support not present\n");
+			usage();
+			return -1;
+#endif
+		}
+	}
+
+	TWExclude exclude;
+	exclude.add_absolute_dir("/data/media");
+	tar.has_data_media = has_data_media;
+	tar.setdir(Directory);
+	tar.setfn(Tar_Filename);
+	tar.setsize(exclude.Get_Folder_Size(Directory));
+	tar.use_compression = use_compression;
+	tar.backup_exclusions = &exclude;
+#ifndef TW_EXCLUDE_ENCRYPTED_BACKUPS
+	if (userdata_encryption && !use_encryption) {
+		printf("userdata encryption set without encryption option\n");
+		usage();
+		return -1;
+	}
+	if (use_encryption) {
+		tar.use_encryption = use_encryption;
+		tar.userdata_encryption = userdata_encryption;
+		tar.setpassword(Password);
+	} else {
+		use_encryption = false;
+	}
+#endif
+	if (action == 1) {
+		if (tar.createTarFork(&tar_fork_pid) != 0) {
+			sync();
+			return -1;
+		}
+		sync();
+		printf("\n\ntar created successfully.\n");
+	} else if (action == 2) {
+		if (tar.extractTarFork() != 0) {
+			sync();
+			return -1;
+		}
+		sync();
+		printf("\n\ntar extracted successfully.\n");
+	}
+	return 0;
+}
diff --git a/twrpinstall/Android.bp b/twrpinstall/Android.bp
new file mode 100755
index 0000000..a5ef145
--- /dev/null
+++ b/twrpinstall/Android.bp
@@ -0,0 +1,91 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+    name: "libtwrpinstall_defaults",
+
+    defaults: [
+        "recovery_defaults",
+    ],
+
+    header_libs: [
+        "libminadbd_headers",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libbootloader_message",
+        "libcrypto",
+        "libext4_utils",
+        "libfs_mgr",
+        "libfusesideload",
+        "libhidl-gen-utils",
+        "libhidlbase",
+        "liblog",
+        "libselinux",
+        "libtinyxml2",
+        "libutils",
+        "libz",
+        "libziparchive",
+    ],
+
+    static_libs: [
+        "libotautil",
+
+        // external dependencies    
+        "libvintf",
+        "libfstab",
+    ],
+}
+
+cc_library_static {
+    name: "libtwrpinstall",
+    recovery_available: true,
+
+    defaults: [
+        "libtwrpinstall_defaults",
+    ],
+
+    cflags: [
+        "-DAB_OTA_UPDATER=1"
+    ],
+
+    include_dirs: [
+        "bootable/recovery",
+        "bootable/recovery/install/include",
+        "bootable/recovery/recovery_utils/include",
+    ],
+
+    srcs: [
+        "adb_install.cpp",
+        "asn1_decoder.cpp",
+        "install.cpp",
+        "installcommand.cpp",
+        "package.cpp",
+        "tw_atomic.cpp",
+        "twinstall.cpp",
+        "verifier.cpp",
+    ],
+    shared_libs: [
+        "librecovery_ui",
+    ],
+
+    export_include_dirs: [
+        "include",
+    ],
+
+    export_shared_lib_headers: [
+        "librecovery_ui",
+    ],
+}
diff --git a/twrpinstall/ZipUtil.cpp b/twrpinstall/ZipUtil.cpp
new file mode 100755
index 0000000..321e7a4
--- /dev/null
+++ b/twrpinstall/ZipUtil.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ZipUtil.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <utime.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+#include <ziparchive/zip_archive.h>
+
+#include "otautil/dirutil.h"
+
+static constexpr mode_t UNZIP_DIRMODE = 0755;
+static constexpr mode_t UNZIP_FILEMODE = 0644;
+
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
+                             const std::string& dest_path, const struct utimbuf* timestamp,
+                             struct selabel_handle* sehnd) {
+    if (!zip_path.empty() && zip_path[0] == '/') {
+        LOG(ERROR) << "ExtractPackageRecursive(): zip_path must be a relative path " << zip_path;
+        return false;
+    }
+    if (dest_path.empty() || dest_path[0] != '/') {
+        LOG(ERROR) << "ExtractPackageRecursive(): dest_path must be an absolute path " << dest_path;
+        return false;
+    }
+
+    void* cookie;
+    std::string target_dir(dest_path);
+    if (dest_path.back() != '/') {
+        target_dir += '/';
+    }
+    std::string prefix_path(zip_path);
+    if (!zip_path.empty() && zip_path.back() != '/') {
+        prefix_path += '/';
+    }
+    const ZipString zip_prefix(prefix_path.c_str());
+
+    int ret = StartIteration(zip, &cookie, &zip_prefix);
+    if (ret != 0) {
+        LOG(ERROR) << "failed to start iterating zip entries.";
+        return false;
+    }
+
+    std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
+    ZipEntry64 entry;
+    ZipString name;
+    int extractCount = 0;
+    while (Next(cookie, &entry, &name) == 0) {
+        std::string entry_name(name.name, name.name + name.name_length);
+        CHECK_LE(prefix_path.size(), entry_name.size());
+        std::string path = target_dir + entry_name.substr(prefix_path.size());
+        // Skip dir.
+        if (path.back() == '/') {
+            continue;
+        }
+        //TODO(b/31917448) handle the symlink.
+
+        if (dirCreateHierarchy(path.c_str(), UNZIP_DIRMODE, timestamp, true, sehnd) != 0) {
+            LOG(ERROR) << "failed to create dir for " << path;
+            return false;
+        }
+
+        char *secontext = NULL;
+        if (sehnd) {
+            selabel_lookup(sehnd, &secontext, path.c_str(), UNZIP_FILEMODE);
+            setfscreatecon(secontext);
+        }
+        android::base::unique_fd fd(open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, UNZIP_FILEMODE));
+        if (fd == -1) {
+            PLOG(ERROR) << "Can't create target file \"" << path << "\"";
+            return false;
+        }
+        if (secontext) {
+            freecon(secontext);
+            setfscreatecon(NULL);
+        }
+
+        int err = ExtractEntryToFile(zip, &entry, fd);
+        if (err != 0) {
+            LOG(ERROR) << "Error extracting \"" << path << "\" : " << ErrorCodeString(err);
+            return false;
+        }
+
+        if (fsync(fd) != 0) {
+            PLOG(ERROR) << "Error syncing file descriptor when extracting \"" << path << "\"";
+            return false;
+        }
+
+        if (timestamp != nullptr && utime(path.c_str(), timestamp)) {
+            PLOG(ERROR) << "Error touching \"" << path << "\"";
+            return false;
+        }
+
+        LOG(INFO) << "Extracted file \"" << path << "\"";
+        ++extractCount;
+    }
+
+    LOG(INFO) << "Extracted " << extractCount << " file(s)";
+    return true;
+}
diff --git a/twrpinstall/ZipUtil.h b/twrpinstall/ZipUtil.h
new file mode 100644
index 0000000..cda405c
--- /dev/null
+++ b/twrpinstall/ZipUtil.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _OTAUTIL_ZIPUTIL_H
+#define _OTAUTIL_ZIPUTIL_H
+
+#include <utime.h>
+
+#include <string>
+
+#include <selinux/label.h>
+#include <ziparchive/zip_archive.h>
+
+/*
+ * Inflate all files under zip_path to the directory specified by
+ * dest_path, which must exist and be a writable directory. The zip_path
+ * is allowed to be an empty string, in which case the whole package
+ * will be extracted.
+ *
+ * Directory entries are not extracted.
+ *
+ * The immediate children of zip_path will become the immediate
+ * children of dest_path; e.g., if the archive contains the entries
+ *
+ *     a/b/c/one
+ *     a/b/c/two
+ *     a/b/c/d/three
+ *
+ * and ExtractPackageRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting
+ * files will be
+ *
+ *     /tmp/one
+ *     /tmp/two
+ *     /tmp/d/three
+ *
+ * If timestamp is non-NULL, file timestamps will be set accordingly.
+ *
+ * Returns true on success, false on failure.
+ */
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
+                             const std::string& dest_path, const struct utimbuf* timestamp,
+                             struct selabel_handle* sehnd);
+
+#endif // _OTAUTIL_ZIPUTIL_H
diff --git a/twrpinstall/adb_install.cpp b/twrpinstall/adb_install.cpp
new file mode 100755
index 0000000..5486b7e
--- /dev/null
+++ b/twrpinstall/adb_install.cpp
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "twinstall/adb_install.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <functional>
+#include <map>
+#include <utility>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/memory.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+#include "fuse_sideload.h"
+#include "twinstall/install.h"
+#include "twinstall.h"
+#include "twinstall/wipe_data.h"
+#include "minadbd/types.h"
+#include "otautil/sysutil.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/ui.h"
+
+// A CommandFunction returns a pair of (result, should_continue), which indicates the command
+// execution result and whether it should proceed to the next iteration. The execution result will
+// always be sent to the minadbd side.
+using CommandFunction = std::function<std::pair<bool, bool>()>;
+
+pid_t child;
+
+pid_t GetMiniAdbdPid() {
+  return child;
+}
+
+static bool SetUsbConfig(const std::string& state) {
+  android::base::SetProperty("sys.usb.config", state);
+  return android::base::WaitForProperty("sys.usb.state", state);
+}
+
+// Parses the minadbd command in |message|; returns MinadbdCommand::kError upon errors.
+static MinadbdCommand ParseMinadbdCommand(const std::string& message) {
+  if (!android::base::StartsWith(message, kMinadbdCommandPrefix)) {
+    LOG(ERROR) << "Failed to parse command in message " << message;
+    return MinadbdCommand::kError;
+  }
+
+  auto cmd_code_string = message.substr(strlen(kMinadbdCommandPrefix));
+  auto cmd_code = android::base::get_unaligned<uint32_t>(cmd_code_string.c_str());
+  if (cmd_code >= static_cast<uint32_t>(MinadbdCommand::kError)) {
+    LOG(ERROR) << "Unsupported command code: " << cmd_code;
+    return MinadbdCommand::kError;
+  }
+
+  return static_cast<MinadbdCommand>(cmd_code);
+}
+
+static bool WriteStatusToFd(MinadbdCommandStatus status, int fd) {
+  char message[kMinadbdMessageSize];
+  memcpy(message, kMinadbdStatusPrefix, strlen(kMinadbdStatusPrefix));
+  android::base::put_unaligned(message + strlen(kMinadbdStatusPrefix), status);
+
+  if (!android::base::WriteFully(fd, message, kMinadbdMessageSize)) {
+    PLOG(ERROR) << "Failed to write message " << message;
+    return false;
+  }
+  return true;
+}
+
+// Installs the package from FUSE. Returns the installation result and whether it should continue
+// waiting for new commands.
+static auto AdbInstallPackageHandler(int* result) {
+  // How long (in seconds) we wait for the package path to be ready. It doesn't need to be too long
+  // because the minadbd service has already issued an install command. FUSE_SIDELOAD_HOST_PATHNAME
+  // will start to exist once the host connects and starts serving a package. Poll for its
+  // appearance. (Note that inotify doesn't work with FUSE.)
+  constexpr int ADB_INSTALL_TIMEOUT = 15;
+  bool should_continue = true;
+  *result = INSTALL_ERROR;
+  for (int i = 0; i < ADB_INSTALL_TIMEOUT; ++i) {
+    PLOG(ERROR) << "i: " << i;
+    struct stat st;
+    if (stat(FUSE_SIDELOAD_HOST_PATHNAME, &st) != 0) {
+      if (errno == ENOENT && i < ADB_INSTALL_TIMEOUT - 1) {
+        sleep(1);
+        continue;
+      } else {
+        should_continue = false;
+        break;
+      }
+    }
+    int dummy;
+    *result = TWinstall_zip(FUSE_SIDELOAD_HOST_PATHNAME, &dummy);
+    break;
+  }
+
+  // Calling stat() on this magic filename signals the FUSE to exit.
+  struct stat st;
+  stat(FUSE_SIDELOAD_HOST_EXIT_PATHNAME, &st);
+  return std::make_pair(*result == INSTALL_SUCCESS, should_continue);
+}
+
+static auto AdbRebootHandler(MinadbdCommand command, int* result,
+                             Device::BuiltinAction* reboot_action) {
+  // Use Device::REBOOT_{FASTBOOT,RECOVERY,RESCUE}, instead of the ones with ENTER_. This allows
+  // rebooting back into fastboot/recovery/rescue mode through bootloader, which may use a newly
+  // installed bootloader/recovery image.
+  switch (command) {
+    case MinadbdCommand::kRebootBootloader:
+      *reboot_action = Device::REBOOT_BOOTLOADER;
+      break;
+    case MinadbdCommand::kRebootFastboot:
+      *reboot_action = Device::REBOOT_FASTBOOT;
+      break;
+    case MinadbdCommand::kRebootRecovery:
+      *reboot_action = Device::REBOOT_RECOVERY;
+      break;
+    case MinadbdCommand::kRebootRescue:
+      *reboot_action = Device::REBOOT_RESCUE;
+      break;
+    case MinadbdCommand::kRebootAndroid:
+    default:
+      *reboot_action = Device::REBOOT;
+      break;
+  }
+  *result = INSTALL_REBOOT;
+  return std::make_pair(true, false);
+}
+
+// Parses and executes the command from minadbd. Returns whether the caller should keep waiting for
+// next command.
+static bool HandleMessageFromMinadbd(int socket_fd,
+                                     const std::map<MinadbdCommand, CommandFunction>& command_map) {
+  char buffer[kMinadbdMessageSize];
+  if (!android::base::ReadFully(socket_fd, buffer, kMinadbdMessageSize)) {
+    PLOG(ERROR) << "Failed to read message from minadbd";
+    return false;
+  }
+
+  std::string message(buffer, buffer + kMinadbdMessageSize);
+  auto command_type = ParseMinadbdCommand(message);
+  if (command_type == MinadbdCommand::kError) {
+    return false;
+  }
+  if (command_map.find(command_type) == command_map.end()) {
+    LOG(ERROR) << "Unsupported command: "
+               << android::base::get_unaligned<unsigned int>(
+                      message.substr(strlen(kMinadbdCommandPrefix)).c_str());
+    return false;
+  }
+
+  // We have received a valid command, execute the corresponding function.
+  const auto& command_func = command_map.at(command_type);
+  const auto [result, should_continue] = command_func();
+  LOG(INFO) << "Command " << static_cast<uint32_t>(command_type) << " finished with " << result;
+  if (!WriteStatusToFd(result ? MinadbdCommandStatus::kSuccess : MinadbdCommandStatus::kFailure,
+                       socket_fd)) {
+    return false;
+  }
+  return should_continue;
+}
+
+// TODO(xunchang) add a wrapper function and kill the minadbd service there.
+static void ListenAndExecuteMinadbdCommands(
+    pid_t minadbd_pid, android::base::unique_fd&& socket_fd,
+    const std::map<MinadbdCommand, CommandFunction>& command_map) {
+  android::base::unique_fd epoll_fd(epoll_create1(O_CLOEXEC));
+  if (epoll_fd == -1) {
+    PLOG(ERROR) << "Failed to create epoll";
+    kill(minadbd_pid, SIGKILL);
+    return;
+  }
+
+  constexpr int EPOLL_MAX_EVENTS = 10;
+  struct epoll_event ev = {};
+  ev.events = EPOLLIN | EPOLLHUP;
+  ev.data.fd = socket_fd.get();
+  struct epoll_event events[EPOLL_MAX_EVENTS];
+  if (epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, socket_fd.get(), &ev) == -1) {
+    PLOG(ERROR) << "Failed to add socket fd to epoll";
+    kill(minadbd_pid, SIGKILL);
+    return;
+  }
+
+  // Set the timeout to be 300s when waiting for minadbd commands.
+  constexpr int TIMEOUT_MILLIS = 300 * 1000;
+  while (true) {
+    // Poll for the status change of the socket_fd, and handle the message if the fd is ready to
+    // read.
+    int event_count =
+        TEMP_FAILURE_RETRY(epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, TIMEOUT_MILLIS));
+    if (event_count == -1) {
+      PLOG(ERROR) << "Failed to wait for epoll events";
+      kill(minadbd_pid, SIGKILL);
+      return;
+    }
+    if (event_count == 0) {
+      LOG(ERROR) << "Timeout waiting for messages from minadbd";
+      kill(minadbd_pid, SIGKILL);
+      return;
+    }
+
+    for (int n = 0; n < event_count; n++) {
+      if (events[n].events & EPOLLHUP) {
+        LOG(INFO) << "Socket has been closed";
+        kill(minadbd_pid, SIGKILL);
+        return;
+      }
+      if (!HandleMessageFromMinadbd(socket_fd.get(), command_map)) {
+        kill(minadbd_pid, SIGKILL);
+        return;
+      }
+    }
+  }
+}
+
+// Recovery starts minadbd service as a child process, and spawns another thread to listen for the
+// message from minadbd through a socket pair. Here is an example to execute one command from adb
+// host.
+//  a. recovery                    b. listener thread               c. minadbd service
+//
+//  a1. create socket pair
+//  a2. fork minadbd service
+//                                                                c3. wait for the adb commands
+//                                                                    from host
+//                                                                c4. after receiving host commands:
+//                                                                  1) set up pre-condition (i.e.
+//                                                                     start fuse for adb sideload)
+//                                                                  2) issue command through
+//                                                                     socket.
+//                                                                  3) wait for result
+//  a5. start listener thread
+//                               b6. listen for message from
+//                                   minadbd in a loop.
+//                               b7. After receiving a minadbd
+//                                   command from socket
+//                                 1) execute the command function
+//                                 2) send the result back to
+//                                    minadbd
+//  ......
+//                                                                 c8. exit upon receiving the
+//                                                                     result
+// a9.  wait for listener thread
+//      to exit.
+//
+// a10. wait for minadbd to
+//      exit
+//                               b11. exit the listening loop
+//
+static void CreateMinadbdServiceAndExecuteCommands(
+    const std::map<MinadbdCommand, CommandFunction>& command_map,
+    bool rescue_mode __unused, std::string install_file __unused) {
+  signal(SIGPIPE, SIG_IGN);
+
+  android::base::unique_fd recovery_socket;
+  android::base::unique_fd minadbd_socket;
+  if (!android::base::Socketpair(AF_UNIX, SOCK_STREAM, 0, &recovery_socket, &minadbd_socket)) {
+    PLOG(ERROR) << "Failed to create socket";
+    return;
+  }
+
+  child = fork();
+  if (child == -1) {
+    PLOG(ERROR) << "Failed to fork child process";
+    return;
+  }
+  if (child == 0) {
+    recovery_socket.reset();
+    std::vector<std::string> minadbd_commands = {
+      "/system/bin/minadbd",
+      "--socket_fd",
+      std::to_string(minadbd_socket.release()),
+    };
+    // if (rescue_mode) {
+    //   minadbd_commands.push_back("--rescue");
+    // }
+    auto exec_args = StringVectorToNullTerminatedArray(minadbd_commands);
+    execv(exec_args[0], exec_args.data());
+    _exit(EXIT_FAILURE);
+  }
+
+  minadbd_socket.reset();
+
+  // We need to call SetUsbConfig() after forking minadbd service. Because the function waits for
+  // the usb state to be updated, which depends on sys.usb.ffs.ready=1 set in the adb daemon.
+  if (!SetUsbConfig("sideload")) {
+    LOG(ERROR) << "Failed to set usb config to sideload";
+    return;
+  }
+
+  std::thread listener_thread(ListenAndExecuteMinadbdCommands, child,
+                              std::move(recovery_socket), std::ref(command_map));
+  if (listener_thread.joinable()) {
+    listener_thread.join();
+  }
+
+  int status;
+  waitpid(child, &status, 0);
+  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+    if (WEXITSTATUS(status) == MinadbdErrorCode::kMinadbdAdbVersionError) {
+      LOG(ERROR) << "\nYou need adb 1.0.32 or newer to sideload\nto this device.\n";
+    } else if (!WIFSIGNALED(status)) {
+      LOG(ERROR) << "\n(adbd status " << WEXITSTATUS(status) << ")";
+    }
+  }
+
+  signal(SIGPIPE, SIG_DFL);
+}
+
+  int twrp_sideload(const char* install_file, Device::BuiltinAction* reboot_action) {
+
+  // Save the usb state to restore after the sideload operation.
+  std::string usb_state = android::base::GetProperty("sys.usb.state", "none");
+  // Clean up state and stop adbd.
+  if (usb_state != "none" && !SetUsbConfig("none")) {
+    LOG(ERROR) << "Failed to clear USB config";
+    return INSTALL_ERROR;
+  }
+
+  int install_result = INSTALL_ERROR;
+  std::map<MinadbdCommand, CommandFunction> command_map{
+  { MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, &install_result) },
+  { MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid,
+                                              &install_result, reboot_action) },
+  { MinadbdCommand::kRebootBootloader,
+    std::bind(&AdbRebootHandler, MinadbdCommand::kRebootBootloader, &install_result,
+              reboot_action) },
+  { MinadbdCommand::kRebootFastboot, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootFastboot,
+                                                &install_result, reboot_action) },
+  { MinadbdCommand::kRebootRecovery, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRecovery,
+                                                &install_result, reboot_action) },
+  { MinadbdCommand::kRebootRescue,
+    std::bind(&AdbRebootHandler, MinadbdCommand::kRebootRescue, &install_result, reboot_action) },
+};
+
+  CreateMinadbdServiceAndExecuteCommands(command_map, false, install_file);
+
+  // Clean up before switching to the older state, for example setting the state
+  // to none sets sys/class/android_usb/android0/enable to 0.
+  if (!SetUsbConfig("none")) {
+    LOG(ERROR) << "Failed to clear USB config";
+  }
+
+  if (usb_state != "none") {
+    if (!SetUsbConfig(usb_state)) {
+      LOG(ERROR) << "Failed to set USB config to " << usb_state;
+    }
+  }
+
+  return install_result;
+}
diff --git a/twrpinstall/asn1_decoder.cpp b/twrpinstall/asn1_decoder.cpp
new file mode 100644
index 0000000..2d81a6e
--- /dev/null
+++ b/twrpinstall/asn1_decoder.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "private/asn1_decoder.h"
+
+int asn1_context::peek_byte() const {
+  if (length_ == 0) {
+    return -1;
+  }
+  return *p_;
+}
+
+int asn1_context::get_byte() {
+  if (length_ == 0) {
+    return -1;
+  }
+
+  int byte = *p_;
+  p_++;
+  length_--;
+  return byte;
+}
+
+bool asn1_context::skip_bytes(size_t num_skip) {
+  if (length_ < num_skip) {
+    return false;
+  }
+  p_ += num_skip;
+  length_ -= num_skip;
+  return true;
+}
+
+bool asn1_context::decode_length(size_t* out_len) {
+  int num_octets = get_byte();
+  if (num_octets == -1) {
+    return false;
+  }
+  if ((num_octets & 0x80) == 0x00) {
+    *out_len = num_octets;
+    return true;
+  }
+  num_octets &= kMaskTag;
+  if (static_cast<size_t>(num_octets) >= sizeof(size_t)) {
+    return false;
+  }
+  size_t length = 0;
+  for (int i = 0; i < num_octets; ++i) {
+    int byte = get_byte();
+    if (byte == -1) {
+      return false;
+    }
+    length <<= 8;
+    length += byte;
+  }
+  *out_len = length;
+  return true;
+}
+
+/**
+ * Returns the constructed type and advances the pointer. E.g. A0 -> 0
+ */
+asn1_context* asn1_context::asn1_constructed_get() {
+  int type = get_byte();
+  if (type == -1 || (type & kMaskConstructed) != kTagConstructed) {
+    return nullptr;
+  }
+  size_t length;
+  if (!decode_length(&length) || length > length_) {
+    return nullptr;
+  }
+  asn1_context* app_ctx = new asn1_context(p_, length);
+  app_ctx->app_type_ = type & kMaskAppType;
+  return app_ctx;
+}
+
+bool asn1_context::asn1_constructed_skip_all() {
+  int byte = peek_byte();
+  while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) {
+    skip_bytes(1);
+    size_t length;
+    if (!decode_length(&length) || !skip_bytes(length)) {
+      return false;
+    }
+    byte = peek_byte();
+  }
+  return byte != -1;
+}
+
+int asn1_context::asn1_constructed_type() const {
+  return app_type_;
+}
+
+asn1_context* asn1_context::asn1_sequence_get() {
+  if ((get_byte() & kMaskTag) != kTagSequence) {
+    return nullptr;
+  }
+  size_t length;
+  if (!decode_length(&length) || length > length_) {
+    return nullptr;
+  }
+  return new asn1_context(p_, length);
+}
+
+asn1_context* asn1_context::asn1_set_get() {
+  if ((get_byte() & kMaskTag) != kTagSet) {
+    return nullptr;
+  }
+  size_t length;
+  if (!decode_length(&length) || length > length_) {
+    return nullptr;
+  }
+  return new asn1_context(p_, length);
+}
+
+bool asn1_context::asn1_sequence_next() {
+  size_t length;
+  if (get_byte() == -1 || !decode_length(&length) || !skip_bytes(length)) {
+    return false;
+  }
+  return true;
+}
+
+bool asn1_context::asn1_oid_get(const uint8_t** oid, size_t* length) {
+  if (get_byte() != kTagOid) {
+    return false;
+  }
+  if (!decode_length(length) || *length == 0 || *length > length_) {
+    return false;
+  }
+  *oid = p_;
+  return true;
+}
+
+bool asn1_context::asn1_octet_string_get(const uint8_t** octet_string, size_t* length) {
+  if (get_byte() != kTagOctetString) {
+    return false;
+  }
+  if (!decode_length(length) || *length == 0 || *length > length_) {
+    return false;
+  }
+  *octet_string = p_;
+  return true;
+}
diff --git a/twrpinstall/include/common.h b/twrpinstall/include/common.h
new file mode 100755
index 0000000..e82bec3
--- /dev/null
+++ b/twrpinstall/include/common.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#ifndef __cplusplus
+extern "C" {
+#endif
+
+#define LOGE(...) fprintf(stdout, "E:" __VA_ARGS__)
+#define LOGW(...) fprintf(stdout, "W:" __VA_ARGS__)
+#define LOGI(...) fprintf(stdout, "I:" __VA_ARGS__)
+
+#if 0
+#define LOGV(...) fprintf(stdout, "V:" __VA_ARGS__)
+#define LOGD(...) fprintf(stdout, "D:" __VA_ARGS__)
+#else
+#define LOGV(...) do {} while (0)
+#define LOGD(...) do {} while (0)
+#endif
+
+#define STRINGIFY(x) #x
+#define EXPAND(x) STRINGIFY(x)
+
+// Not using the command-line defined macro here because this header could be included by
+// device-specific recovery libraries. We static assert the value consistency in recovery.cpp.
+//static constexpr int kRecoveryApiVersion = 3;
+
+class RecoveryUI;
+struct selabel_handle;
+
+extern struct selabel_handle* sehandle;
+extern RecoveryUI* ui;
+extern bool has_cache;
+
+// The current stage, e.g. "1/2".
+extern std::string stage;
+
+// The reason argument provided in "--reason=".
+extern const char* reason;
+
+// static bool is_ro_debuggable();
diff --git a/twrpinstall/include/installcommand.h b/twrpinstall/include/installcommand.h
new file mode 100644
index 0000000..f265801
--- /dev/null
+++ b/twrpinstall/include/installcommand.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RECOVERY_INSTALL_COMMAND_H_
+#define RECOVERY_INSTALL_COMMAND_H_
+
+#define TMP_UPDATER_BINARY_PATH "/tmp/updater"
+
+#include <string>
+#include <ziparchive/zip_archive.h>
+
+bool read_metadata_from_package(ZipArchiveHandle zip, std::string* meta_data);
+
+int
+abupdate_binary_command(const char* path, int retry_count,
+                      int status_fd, std::vector<std::string>* cmd);
+int
+update_binary_command(const char* path, int retry_count,
+                      int status_fd, std::vector<std::string>* cmd);
+
+bool verify_package_compatibility(ZipArchiveHandle package_zip);
+
+void read_source_target_build(ZipArchiveHandle zip/*, std::vector<std::string>& log_buffer*/);
+
+#endif  // RECOVERY_INSTALL_COMMAND_H_
diff --git a/twrpinstall/include/private/asn1_decoder.h b/twrpinstall/include/private/asn1_decoder.h
new file mode 100644
index 0000000..e5337d9
--- /dev/null
+++ b/twrpinstall/include/private/asn1_decoder.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ASN1_DECODER_H_
+#define ASN1_DECODER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+class asn1_context {
+ public:
+  asn1_context(const uint8_t* buffer, size_t length) : p_(buffer), length_(length), app_type_(0) {}
+  int asn1_constructed_type() const;
+  asn1_context* asn1_constructed_get();
+  bool asn1_constructed_skip_all();
+  asn1_context* asn1_sequence_get();
+  asn1_context* asn1_set_get();
+  bool asn1_sequence_next();
+  bool asn1_oid_get(const uint8_t** oid, size_t* length);
+  bool asn1_octet_string_get(const uint8_t** octet_string, size_t* length);
+
+ private:
+  static constexpr int kMaskConstructed = 0xE0;
+  static constexpr int kMaskTag = 0x7F;
+  static constexpr int kMaskAppType = 0x1F;
+
+  static constexpr int kTagOctetString = 0x04;
+  static constexpr int kTagOid = 0x06;
+  static constexpr int kTagSequence = 0x30;
+  static constexpr int kTagSet = 0x31;
+  static constexpr int kTagConstructed = 0xA0;
+
+  int peek_byte() const;
+  int get_byte();
+  bool skip_bytes(size_t num_skip);
+  bool decode_length(size_t* out_len);
+
+  const uint8_t* p_;
+  size_t length_;
+  int app_type_;
+};
+
+#endif /* ASN1_DECODER_H_ */
diff --git a/twrpinstall/include/private/setup_commands.h b/twrpinstall/include/private/setup_commands.h
new file mode 100644
index 0000000..7fdc741
--- /dev/null
+++ b/twrpinstall/include/private/setup_commands.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Private headers exposed for testing purpose only.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+// Sets up the commands for a non-A/B update. Extracts the updater binary from the open zip archive
+// |zip| located at |package|. Stores the command line that should be called into |cmd|. The
+// |status_fd| is the file descriptor the child process should use to report back the progress of
+// the update.
+int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
+                             int status_fd, std::vector<std::string>* cmd);
+
+// Sets up the commands for an A/B update. Extracts the needed entries from the open zip archive
+// |zip| located at |package|. Stores the command line that should be called into |cmd|. The
+// |status_fd| is the file descriptor the child process should use to report back the progress of
+// the update. Note that since this applies to the sideloading flow only, it takes one less
+// parameter |retry_count| than the non-A/B version.
+int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+                          std::vector<std::string>* cmd);
diff --git a/twrpinstall/include/set_metadata.h b/twrpinstall/include/set_metadata.h
new file mode 100644
index 0000000..9a46be9
--- /dev/null
+++ b/twrpinstall/include/set_metadata.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Team Win Recovery Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The purpose of these functions is to try to get and set the proper
+ * file permissions, SELinux contexts, owner, and group so that these
+ * files are accessible when we boot up to normal Android via MTP and to
+ * file manager apps. During early boot we try to read the contexts and
+ * owner / group info from /data/media or from /data/media/0 and store
+ * them in static variables. From there, we'll try to set the same
+ * contexts, owner, and group information on most files we create during
+ * operations like backups, copying the log, and MTP operations.
+ */
+
+#ifndef _RECOVERY_SET_CONTEXTS_H
+#define _RECOVERY_SET_CONTEXTS_H
+
+#include <sys/stat.h>
+#include "selinux/selinux.h"
+
+int tw_get_default_metadata(const char* filename);
+int tw_set_default_metadata(const char* filename);
+
+#endif //_RECOVERY_SET_CONTEXTS_H
diff --git a/twrpinstall/include/tw_atomic.hpp b/twrpinstall/include/tw_atomic.hpp
new file mode 100644
index 0000000..0f29f02
--- /dev/null
+++ b/twrpinstall/include/tw_atomic.hpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Team Win Recovery Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _TWATOMIC_HPP_HEADER
+#define _TWATOMIC_HPP_HEADER
+
+#include <pthread.h>
+
+class TWAtomicInt
+{
+public:
+	TWAtomicInt(int initial_value = 0);
+	~TWAtomicInt();
+	void set_value(int new_value);
+	int get_value();
+
+private:
+	int value;
+	bool use_mutex;
+	pthread_mutex_t mutex_lock;
+};
+
+#endif //_TWATOMIC_HPP_HEADER
diff --git a/twrpinstall/include/twinstall.h b/twrpinstall/include/twinstall.h
new file mode 100644
index 0000000..406ab6f
--- /dev/null
+++ b/twrpinstall/include/twinstall.h
@@ -0,0 +1,6 @@
+#ifndef RECOVERY_TWINSTALL_H_
+#define RECOVERY_TWINSTALL_H_
+
+int TWinstall_zip(const char* path, int* wipe_cache, bool check_for_digest = false);
+
+#endif  // RECOVERY_TWINSTALL_H_
diff --git a/twrpinstall/include/twinstall/adb_install.h b/twrpinstall/include/twinstall/adb_install.h
new file mode 100755
index 0000000..42321d4
--- /dev/null
+++ b/twrpinstall/include/twinstall/adb_install.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <recovery_ui/device.h>
+
+// Applies a package via `adb sideload` or `adb rescue`. Returns the install result (in `enum
+// InstallResult`). When a reboot has been requested, INSTALL_REBOOT will be the return value, with
+// the reboot target set in reboot_action.
+int twrp_sideload(const char* install_file, Device::BuiltinAction* reboot_action);
+pid_t GetMiniAdbdPid();
\ No newline at end of file
diff --git a/twrpinstall/include/twinstall/get_args.h b/twrpinstall/include/twinstall/get_args.h
new file mode 100755
index 0000000..fb9bb43
--- /dev/null
+++ b/twrpinstall/include/twinstall/get_args.h
@@ -0,0 +1,16 @@
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include "recovery_utils/roots.h"
+
+#include "bootloader_message/include/bootloader_message/bootloader_message.h"
+
+extern std::string stage;
+
+class args {
+    public:
+        static std::vector<std::string> get_args(const int *argc, char*** const argv);
+};
\ No newline at end of file
diff --git a/twrpinstall/include/twinstall/install.h b/twrpinstall/include/twinstall/install.h
new file mode 100755
index 0000000..29647ad
--- /dev/null
+++ b/twrpinstall/include/twinstall/install.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+#include "package.h"
+
+enum InstallResult {
+  INSTALL_SUCCESS,
+  INSTALL_ERROR,
+  INSTALL_CORRUPT,
+  INSTALL_NONE,
+  INSTALL_SKIPPED,
+  INSTALL_RETRY,
+  INSTALL_KEY_INTERRUPTED,
+  INSTALL_REBOOT,
+};
+
+enum class OtaType {
+  AB,
+  BLOCK,
+  BRICK,
+};
+
+static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
+static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
+
+// Installs the given update package. This function should also wipe the cache partition after a
+// successful installation if |should_wipe_cache| is true or an updater command asks to wipe the
+// cache.
+int install_package(const std::string& package, bool should_wipe_cache, bool needs_mount,
+                    int retry_count);
+
+// Verifies the package by ota keys. Returns true if the package is verified successfully,
+// otherwise returns false.
+bool verify_package(Package* package);
+
+// Reads meta data file of the package; parses each line in the format "key=value"; and writes the
+// result to |metadata|. Return true if succeed, otherwise return false.
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata);
+
+// Reads the "recovery.wipe" entry in the zip archive returns a list of partitions to wipe.
+std::vector<std::string> GetWipePartitionList(Package* wipe_package);
+
+// Verifies the compatibility info in a Treble-compatible package. Returns true directly if the
+// entry doesn't exist.
+bool verify_package_compatibility(ZipArchiveHandle package_zip);
+
+// Checks if the the metadata in the OTA package has expected values. Returns 0 on success.
+// Mandatory checks: ota-type, pre-device and serial number(if presents)
+// AB OTA specific checks: pre-build version, fingerprint, timestamp.
+int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
+bool HasUpdaterBinary(ZipArchiveHandle zip);
\ No newline at end of file
diff --git a/twrpinstall/include/twinstall/package.h b/twrpinstall/include/twinstall/package.h
new file mode 100755
index 0000000..50a4ffa
--- /dev/null
+++ b/twrpinstall/include/twinstall/package.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <ziparchive/zip_archive.h>
+
+#include "verifier.h"
+
+// This class serves as a wrapper for an OTA update package. It aims to provide the common
+// interface for both packages loaded in memory and packages read from fd.
+class Package : public VerifierInterface {
+ public:
+  static std::unique_ptr<Package> CreateMemoryPackage(
+      const std::string& path);
+  static std::unique_ptr<Package> CreateMemoryPackage(
+      std::vector<uint8_t> content);
+  static std::unique_ptr<Package> CreateFilePackage(const std::string& path,
+                                                    const std::function<void(float)>& set_progress);
+
+  virtual ~Package() = default;
+
+  // Opens the package as a zip file and returns the ZipArchiveHandle.
+  virtual ZipArchiveHandle GetZipArchiveHandle() = 0;
+
+  // Updates the progress in fraction during package verification.
+  void SetProgress(float progress) override;
+
+ protected:
+  // An optional function to update the progress.
+  std::function<void(float)> set_progress_;
+};
diff --git a/twrpinstall/include/twinstall/verifier.h b/twrpinstall/include/twinstall/verifier.h
new file mode 100644
index 0000000..5d66e85
--- /dev/null
+++ b/twrpinstall/include/twinstall/verifier.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <openssl/ec_key.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+
+constexpr size_t MiB = 1024 * 1024;
+
+using HasherUpdateCallback = std::function<void(const uint8_t* addr, uint64_t size)>;
+
+struct RSADeleter {
+  void operator()(RSA* rsa) const {
+    RSA_free(rsa);
+  }
+};
+
+struct ECKEYDeleter {
+  void operator()(EC_KEY* ec_key) const {
+    EC_KEY_free(ec_key);
+  }
+};
+
+struct Certificate {
+  typedef enum {
+    KEY_TYPE_RSA,
+    KEY_TYPE_EC,
+  } KeyType;
+
+  Certificate(int hash_len_, KeyType key_type_, std::unique_ptr<RSA, RSADeleter>&& rsa_,
+              std::unique_ptr<EC_KEY, ECKEYDeleter>&& ec_)
+      : hash_len(hash_len_), key_type(key_type_), rsa(std::move(rsa_)), ec(std::move(ec_)) {}
+
+  // SHA_DIGEST_LENGTH (SHA-1) or SHA256_DIGEST_LENGTH (SHA-256)
+  int hash_len;
+  KeyType key_type;
+  std::unique_ptr<RSA, RSADeleter> rsa;
+  std::unique_ptr<EC_KEY, ECKEYDeleter> ec;
+};
+
+class VerifierInterface {
+ public:
+  virtual ~VerifierInterface() = default;
+
+  // Returns the package size in bytes.
+  virtual uint64_t GetPackageSize() const = 0;
+
+  // Reads |byte_count| data starting from |offset|, and puts the result in |buffer|.
+  virtual bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) = 0;
+
+  // Updates the hash contexts for |length| bytes data starting from |start|.
+  virtual bool UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers, uint64_t start,
+                                  uint64_t length) = 0;
+
+  // Updates the progress in fraction during package verification.
+  virtual void SetProgress(float progress) = 0;
+};
+
+//  Looks for an RSA signature embedded in the .ZIP file comment given the path to the zip.
+//  Verifies that it matches one of the given public keys. Returns VERIFY_SUCCESS or
+//  VERIFY_FAILURE (if any error is encountered or no key matches the signature).
+int verify_file(VerifierInterface* package, const std::vector<Certificate>& keys,
+                const std::function<void(float)>& set_progress = nullptr);
+
+// Checks that the RSA key has a modulus of 2048 or 4096 bits long, and public exponent is 3 or
+// 65537.
+bool CheckRSAKey(const std::unique_ptr<RSA, RSADeleter>& rsa);
+
+// Checks that the field size of the curve for the EC key is 256 bits.
+bool CheckECKey(const std::unique_ptr<EC_KEY, ECKEYDeleter>& ec_key);
+
+// Parses a PEM-encoded x509 certificate from the given buffer and saves it into |cert|. Returns
+// false if there is a parsing failure or the signature's encryption algorithm is not supported.
+bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certificate* cert);
+
+// Iterates over the zip entries with the suffix "x509.pem" and returns a list of recognized
+// certificates. Returns an empty list if we fail to parse any of the entries.
+std::vector<Certificate> LoadKeysFromZipfile(const std::string& zip_name);
+
+#define VERIFY_SUCCESS 0
+#define VERIFY_FAILURE 1
diff --git a/twrpinstall/include/twinstall/wipe_data.h b/twrpinstall/include/twinstall/wipe_data.h
new file mode 100755
index 0000000..07b1e40
--- /dev/null
+++ b/twrpinstall/include/twinstall/wipe_data.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+
+struct selabel_handle;
+
+// Returns true on success.
+bool WipeCache(const std::function<bool()>& confirm);
+
+// Returns true on success.
+bool WipeData(bool convert_fbe);
diff --git a/twrpinstall/install.cpp b/twrpinstall/install.cpp
new file mode 100755
index 0000000..d270d26
--- /dev/null
+++ b/twrpinstall/install.cpp
@@ -0,0 +1,751 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "twinstall/install.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <functional>
+#include <limits>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parsedouble.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+#include "twinstall/package.h"
+#include "twinstall/verifier.h"
+#include "twinstall/wipe_data.h"
+#include "otautil/error_code.h"
+#include "otautil/paths.h"
+#include "recovery_utils/roots.h"
+#include "otautil/sysutil.h"
+#include "recovery_utils/thermalutil.h"
+#include "private/setup_commands.h"
+
+using namespace std::chrono_literals;
+
+static constexpr int kRecoveryApiVersion = 3;
+// Assert the version defined in code and in Android.mk are consistent.
+static_assert(kRecoveryApiVersion == RECOVERY_API_VERSION, "Mismatching recovery API versions.");
+
+// Default allocation of progress bar segments to operations
+// static constexpr int VERIFICATION_PROGRESS_TIME = 60;
+// static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
+
+static std::condition_variable finish_log_temperature;
+
+bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata) {
+  CHECK(metadata != nullptr);
+
+  static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
+  std::string path(METADATA_PATH);
+  ZipEntry64 entry;
+  if (FindEntry(zip, path, &entry) != 0) {
+    LOG(ERROR) << "Failed to find " << METADATA_PATH;
+    return false;
+  }
+
+  uint32_t length = entry.uncompressed_length;
+  std::string metadata_string(length, '\0');
+  int32_t err =
+      ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&metadata_string[0]), length);
+  if (err != 0) {
+    LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err);
+    return false;
+  }
+
+  for (const std::string& line : android::base::Split(metadata_string, "\n")) {
+    size_t eq = line.find('=');
+    if (eq != std::string::npos) {
+      metadata->emplace(android::base::Trim(line.substr(0, eq)),
+                        android::base::Trim(line.substr(eq + 1)));
+    }
+  }
+
+  return true;
+}
+
+// Gets the value for the given key in |metadata|. Returns an emtpy string if the key isn't
+// present.
+static std::string get_value(const std::map<std::string, std::string>& metadata,
+                             const std::string& key) {
+  const auto& it = metadata.find(key);
+  return (it == metadata.end()) ? "" : it->second;
+}
+
+static std::string OtaTypeToString(OtaType type) {
+  switch (type) {
+    case OtaType::AB:
+      return "AB";
+    case OtaType::BLOCK:
+      return "BLOCK";
+    case OtaType::BRICK:
+      return "BRICK";
+  }
+}
+
+// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
+static void ReadSourceTargetBuild(const std::map<std::string, std::string>& metadata,
+                                  std::vector<std::string>* log_buffer) {
+  // Examples of the pre-build and post-build strings in metadata:
+  //   pre-build-incremental=2943039
+  //   post-build-incremental=2951741
+  auto source_build = get_value(metadata, "pre-build-incremental");
+  if (!source_build.empty()) {
+    log_buffer->push_back("source_build: " + source_build);
+  }
+
+  auto target_build = get_value(metadata, "post-build-incremental");
+  if (!target_build.empty()) {
+    log_buffer->push_back("target_build: " + target_build);
+  }
+}
+
+// Checks the build version, fingerprint and timestamp in the metadata of the A/B package.
+// Downgrading is not allowed unless explicitly enabled in the package and only for
+// incremental packages.
+static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
+  // Incremental updates should match the current build.
+  auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", "");
+  auto pkg_pre_build = get_value(metadata, "pre-build-incremental");
+  if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) {
+    LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected "
+               << device_pre_build;
+    return INSTALL_ERROR;
+  }
+
+  auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", "");
+  auto pkg_pre_build_fingerprint = get_value(metadata, "pre-build");
+  if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) {
+    LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected "
+               << device_fingerprint;
+    return INSTALL_ERROR;
+  }
+
+  // Check for downgrade version.
+  // int64_t build_timestamp =
+      // android::base::GetIntProperty("ro.build.date.utc", std::numeric_limits<int64_t>::max());
+  // int64_t pkg_post_timestamp = 0;
+  // We allow to full update to the same version we are running, in case there
+  // is a problem with the current copy of that version.
+  auto pkg_post_timestamp_string = get_value(metadata, "post-timestamp");
+  // if (pkg_post_timestamp_string.empty() ||
+  //     !android::base::ParseInt(pkg_post_timestamp_string, &pkg_post_timestamp) ||
+  //     pkg_post_timestamp < build_timestamp) {
+  //   if (get_value(metadata, "ota-downgrade") != "yes") {
+  //     LOG(ERROR) << "Update package is older than the current build, expected a build "
+  //                   "newer than timestamp "
+  //                << build_timestamp << " but package has timestamp " << pkg_post_timestamp
+  //                << " and downgrade not allowed.";
+  //     return INSTALL_ERROR;
+  //   }
+  //   if (pkg_pre_build_fingerprint.empty()) {
+  //     LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
+  //     return INSTALL_ERROR;
+  //   }
+  // }
+
+  return 0;
+}
+
+int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
+  auto package_ota_type = get_value(metadata, "ota-type");
+  auto expected_ota_type = OtaTypeToString(ota_type);
+  if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) {
+    LOG(INFO) << "Skip package metadata check for ota type " << expected_ota_type;
+    return 0;
+  }
+
+  if (package_ota_type != expected_ota_type) {
+    LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual "
+               << package_ota_type;
+    return INSTALL_ERROR;
+  }
+
+  auto device = android::base::GetProperty("ro.product.device", "");
+  auto pkg_device = get_value(metadata, "pre-device");
+  if (pkg_device != device || pkg_device.empty()) {
+    LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device;
+    return INSTALL_ERROR;
+  }
+
+  // We allow the package to not have any serialno; and we also allow it to carry multiple serial
+  // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
+  // verification if the device's serialno doesn't match any of these carried numbers.
+  auto pkg_serial_no = get_value(metadata, "serialno");
+  if (!pkg_serial_no.empty()) {
+    auto device_serial_no = android::base::GetProperty("ro.serialno", "");
+    bool serial_number_match = false;
+    for (const auto& number : android::base::Split(pkg_serial_no, "|")) {
+      if (device_serial_no == android::base::Trim(number)) {
+        serial_number_match = true;
+      }
+    }
+    if (!serial_number_match) {
+      LOG(ERROR) << "Package is for serial " << pkg_serial_no;
+      return INSTALL_ERROR;
+    }
+  }
+
+  if (ota_type == OtaType::AB) {
+    return CheckAbSpecificMetadata(metadata);
+  }
+
+  return 0;
+}
+
+int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd,
+                          std::vector<std::string>* cmd) {
+  CHECK(cmd != nullptr);
+
+  // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset
+  // in the zip file.
+  static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
+  std::string property_name(AB_OTA_PAYLOAD_PROPERTIES);
+  ZipEntry64 properties_entry;
+  if (FindEntry(zip, property_name, &properties_entry) != 0) {
+    LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD_PROPERTIES;
+    return INSTALL_CORRUPT;
+  }
+  uint32_t properties_entry_length = properties_entry.uncompressed_length;
+  std::vector<uint8_t> payload_properties(properties_entry_length);
+  int32_t err =
+      ExtractToMemory(zip, &properties_entry, payload_properties.data(), properties_entry_length);
+  if (err != 0) {
+    LOG(ERROR) << "Failed to extract " << AB_OTA_PAYLOAD_PROPERTIES << ": " << ErrorCodeString(err);
+    return INSTALL_CORRUPT;
+  }
+
+  static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
+  std::string payload_name(AB_OTA_PAYLOAD);
+  ZipEntry64 payload_entry;
+  if (FindEntry(zip, payload_name, &payload_entry) != 0) {
+    LOG(ERROR) << "Failed to find " << AB_OTA_PAYLOAD;
+    return INSTALL_CORRUPT;
+  }
+  long payload_offset = payload_entry.offset;
+  *cmd = {
+    "/system/bin/update_engine_sideload",
+    "--payload=file://" + package,
+    android::base::StringPrintf("--offset=%ld", payload_offset),
+    "--headers=" + std::string(payload_properties.begin(), payload_properties.end()),
+    android::base::StringPrintf("--status_fd=%d", status_fd),
+  };
+  return 0;
+}
+
+int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
+                             int status_fd, std::vector<std::string>* cmd) {
+  CHECK(cmd != nullptr);
+
+  // In non-A/B updates we extract the update binary from the package.
+  std::string binary_name(UPDATE_BINARY_NAME);
+  ZipEntry64 binary_entry;
+  if (FindEntry(zip, binary_name, &binary_entry) != 0) {
+    LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
+    return INSTALL_CORRUPT;
+  }
+
+  LOG(ERROR) << "SetupNonAbUpdateCommands::here1";
+  const std::string binary_path = Paths::Get().temporary_update_binary();
+  unlink(binary_path.c_str());
+  LOG(ERROR) << "SetupNonAbUpdateCommands::here2";
+  android::base::unique_fd fd(
+      open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to create " << binary_path;
+    return INSTALL_ERROR;
+  }
+  LOG(ERROR) << "SetupNonAbUpdateCommands::here3";
+
+  int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
+  if (error != 0) {
+    LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
+    return INSTALL_ERROR;
+  }
+
+  // When executing the update binary contained in the package, the arguments passed are:
+  //   - the version number for this interface
+  //   - an FD to which the program can write in order to update the progress bar.
+  //   - the name of the package zip file.
+  //   - an optional argument "retry" if this update is a retry of a failed update attempt.
+  *cmd = {
+    binary_path,
+    std::to_string(kRecoveryApiVersion),
+    std::to_string(status_fd),
+    package,
+  };
+  if (retry_count > 0) {
+    cmd->push_back("retry");
+  }
+  return 0;
+}
+
+static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) {
+  CHECK(max_temperature != nullptr);
+  std::mutex mtx;
+  std::unique_lock<std::mutex> lck(mtx);
+  while (!logger_finished.load() &&
+         finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) {
+    *max_temperature = std::max(*max_temperature, GetMaxValueFromThermalZone());
+  }
+}
+
+// If the package contains an update binary, extract it and run it.
+static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache,
+                             std::vector<std::string>* log_buffer, int retry_count,
+                             int* max_temperature) {
+  std::map<std::string, std::string> metadata;
+
+  if (!ReadMetadataFromPackage(zip, &metadata)) {
+    LOG(ERROR) << "Failed to parse metadata in the zip file";
+    // return INSTALL_CORRUPT;
+  }
+
+  bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
+  // Verifies against the metadata in the package first.
+  // if (int check_status = is_ab ? CheckPackageMetadata(metadata, OtaType::AB) : 0;
+  //     check_status != 0) {
+  //   log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
+    // return check_status;
+  // }
+
+  ReadSourceTargetBuild(metadata, log_buffer);
+  LOG(ERROR) << "try_update_binary::here1";
+  // The updater in child process writes to the pipe to communicate with recovery.
+  android::base::unique_fd pipe_read, pipe_write;
+  // Explicitly disable O_CLOEXEC using 0 as the flags (last) parameter to Pipe
+  // so that the child updater process will recieve a non-closed fd.
+  if (!android::base::Pipe(&pipe_read, &pipe_write, 0)) {
+    PLOG(ERROR) << "Failed to create pipe for updater-recovery communication";
+    return INSTALL_CORRUPT;
+  }
+
+  // The updater-recovery communication protocol.
+  //
+  //   progress <frac> <secs>
+  //       fill up the next <frac> part of of the progress bar over <secs> seconds. If <secs> is
+  //       zero, use `set_progress` commands to manually control the progress of this segment of the
+  //       bar.
+  //
+  //   set_progress <frac>
+  //       <frac> should be between 0.0 and 1.0; sets the progress bar within the segment defined by
+  //       the most recent progress command.
+  //
+  //   ui_print <string>
+  //       display <string> on the screen.
+  //
+  //   wipe_cache
+  //       a wipe of cache will be performed following a successful installation.
+  //
+  //   clear_display
+  //       turn off the text display.
+  //
+  //   enable_reboot
+  //       packages can explicitly request that they want the user to be able to reboot during
+  //       installation (useful for debugging packages that don't exit).
+  //
+  //   retry_update
+  //       updater encounters some issue during the update. It requests a reboot to retry the same
+  //       package automatically.
+  //
+  //   log <string>
+  //       updater requests logging the string (e.g. cause of the failure).
+  //
+
+  std::vector<std::string> args;
+
+  is_ab = false;
+  std::string binary_name(UPDATE_BINARY_NAME);
+  ZipEntry64 binary_entry;
+  if (FindEntry(zip, binary_name, &binary_entry) != 0) {
+    LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
+    is_ab = true;
+  }
+
+  if (int update_status =
+          is_ab ? SetUpAbUpdateCommands(package, zip, pipe_write.get(), &args)
+                : SetUpNonAbUpdateCommands(package, zip, retry_count, pipe_write.get(), &args);
+      update_status != 0) {
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
+    // return update_status;
+  }
+
+  pid_t pid = fork();
+  if (pid == -1) {
+    PLOG(ERROR) << "Failed to fork update binary";
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kForkUpdateBinaryFailure));
+    return INSTALL_ERROR;
+  }
+
+  if (pid == 0) {
+    umask(022);
+    pipe_read.reset();
+
+    // Convert the std::string vector to a NULL-terminated char* vector suitable for execv.
+    auto chr_args = StringVectorToNullTerminatedArray(args);
+    execv(chr_args[0], chr_args.data());
+    // We shouldn't use LOG/PLOG in the forked process, since they may cause the child process to
+    // hang. This deadlock results from an improperly copied mutex in the ui functions.
+    // (Bug: 34769056)
+    fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
+    _exit(EXIT_FAILURE);
+  }
+  pipe_write.reset();
+
+  std::atomic<bool> logger_finished(false);
+  std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished));
+
+  *wipe_cache = false;
+  bool retry_update = false;
+
+  char buffer[1024];
+  FILE* from_child = android::base::Fdopen(std::move(pipe_read), "r");
+  while (fgets(buffer, sizeof(buffer), from_child) != nullptr) {
+    std::string line(buffer);
+    size_t space = line.find_first_of(" \n");
+    std::string command(line.substr(0, space));
+    if (command.empty()) continue;
+
+    // Get rid of the leading and trailing space and/or newline.
+    std::string args = space == std::string::npos ? "" : android::base::Trim(line.substr(space));
+
+    if (command == "progress") {
+      std::vector<std::string> tokens = android::base::Split(args, " ");
+      double fraction;
+      int seconds;
+      if (tokens.size() == 2 && android::base::ParseDouble(tokens[0].c_str(), &fraction) &&
+          android::base::ParseInt(tokens[1], &seconds)) {
+        // ui->ShowProgress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), seconds);
+      } else {
+        LOG(ERROR) << "invalid \"progress\" parameters: " << line;
+      }
+    } else if (command == "set_progress") {
+      std::vector<std::string> tokens = android::base::Split(args, " ");
+      double fraction;
+      if (tokens.size() == 1 && android::base::ParseDouble(tokens[0].c_str(), &fraction)) {
+        // ui->SetProgress(fraction);
+      } else {
+        LOG(ERROR) << "invalid \"set_progress\" parameters: " << line;
+      }
+    } else if (command == "ui_print") {
+      // ui->PrintOnScreenOnly("%s\n", args.c_str());
+      fflush(stdout);
+    } else if (command == "wipe_cache") {
+      *wipe_cache = true;
+    } else if (command == "clear_display") {
+      // ui->SetBackground(RecoveryUI::NONE);
+    } else if (command == "enable_reboot") {
+      // packages can explicitly request that they want the user
+      // to be able to reboot during installation (useful for
+      // debugging packages that don't exit).
+      // ui->SetEnableReboot(true);
+    } else if (command == "retry_update") {
+      retry_update = true;
+    } else if (command == "log") {
+      if (!args.empty()) {
+        // Save the logging request from updater and write to last_install later.
+        log_buffer->push_back(args);
+      } else {
+        LOG(ERROR) << "invalid \"log\" parameters: " << line;
+      }
+    } else {
+      LOG(ERROR) << "unknown command [" << command << "]";
+    }
+  }
+  fclose(from_child);
+
+  int status;
+  waitpid(pid, &status, 0);
+
+  logger_finished.store(true);
+  finish_log_temperature.notify_one();
+  temperature_logger.join();
+
+  if (retry_update) {
+    return INSTALL_RETRY;
+  }
+  if (WIFEXITED(status)) {
+    if (WEXITSTATUS(status) != EXIT_SUCCESS) {
+      LOG(ERROR) << "Error in " << package << " (status " << WEXITSTATUS(status) << ")";
+      return INSTALL_ERROR;
+    }
+  } else if (WIFSIGNALED(status)) {
+    LOG(ERROR) << "Error in " << package << " (killed by signal " << WTERMSIG(status) << ")";
+    return INSTALL_ERROR;
+  } else {
+    LOG(FATAL) << "Invalid status code " << status;
+  }
+
+  return INSTALL_SUCCESS;
+}
+
+// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the
+// entry doesn't exist. Note that the compatibility info is packed in a zip file inside the OTA
+// package.
+// bool verify_package_compatibility(ZipArchiveHandle package_zip __unused) {
+//   LOG(INFO) << "Verifying package compatibility...";
+
+//   static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip";
+//   ZipString compatibility_entry_name(COMPATIBILITY_ZIP_ENTRY);
+//   ZipEntry64 compatibility_entry;
+//   if (FindEntry(package_zip, compatibility_entry_name, &compatibility_entry) != 0) {
+//     LOG(INFO) << "Package doesn't contain " << COMPATIBILITY_ZIP_ENTRY << " entry";
+//     return true;
+//   }
+
+//   std::string zip_content(compatibility_entry.uncompressed_length, '\0');
+//   int32_t ret;
+//   if ((ret = ExtractToMemory(package_zip, &compatibility_entry,
+//                              reinterpret_cast<uint8_t*>(&zip_content[0]),
+//                              compatibility_entry.uncompressed_length)) != 0) {
+//     LOG(ERROR) << "Failed to read " << COMPATIBILITY_ZIP_ENTRY << ": " << ErrorCodeString(ret);
+//     return false;
+//   }
+
+//   ZipArchiveHandle zip_handle;
+//   ret = OpenArchiveFromMemory(static_cast<void*>(const_cast<char*>(zip_content.data())),
+//                               zip_content.size(), COMPATIBILITY_ZIP_ENTRY, &zip_handle);
+//   if (ret != 0) {
+//     LOG(ERROR) << "Failed to OpenArchiveFromMemory: " << ErrorCodeString(ret);
+//     return false;
+//   }
+
+//   // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents.
+//   void* cookie;
+//   ret = StartIteration(zip_handle, &cookie, nullptr, nullptr);
+//   if (ret != 0) {
+//     LOG(ERROR) << "Failed to start iterating zip entries: " << ErrorCodeString(ret);
+//     CloseArchive(zip_handle);
+//     return false;
+//   }
+//   std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
+
+//   std::vector<std::string> compatibility_info;
+//   ZipEntry64 info_entry;
+//   ZipString info_name;
+//   while (Next(cookie, &info_entry, &info_name) == 0) {
+//     std::string content(info_entry.uncompressed_length, '\0');
+//     int32_t ret = ExtractToMemory(zip_handle, &info_entry, reinterpret_cast<uint8_t*>(&content[0]),
+//                                   info_entry.uncompressed_length);
+//     if (ret != 0) {
+//       LOG(ERROR) << "Failed to read " << info_name.name << ": " << ErrorCodeString(ret);
+//       CloseArchive(zip_handle);
+//       return false;
+//     }
+//     compatibility_info.emplace_back(std::move(content));
+//   }
+//   CloseArchive(zip_handle);
+
+//   // VintfObjectRecovery::CheckCompatibility returns zero on success.
+//   std::string err;
+//   int result = android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err);
+//   if (result == 0) {
+//     return true;
+//   }
+
+//   LOG(ERROR) << "Failed to verify package compatibility (result " << result << "): " << err;
+//   return false;
+// }
+
+static int really_install_package(const std::string& path, bool* wipe_cache __unused, bool needs_mount __unused,
+                                  std::vector<std::string>* log_buffer __unused, int retry_count __unused,
+                                  int* max_temperature __unused) {
+  // ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
+  // ui->Print("Finding update package...\n");
+  // Give verification half the progress bar...
+  // ui->SetProgressType(RecoveryUI::DETERMINATE);
+  // ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
+  LOG(INFO) << "Update location: " << path;
+
+  // Map the update package into memory.
+  // ui->Print("Opening update package...\n");
+  if (needs_mount) {
+    if (path[0] == '@') {
+      ensure_path_mounted(path.substr(1));
+    } else {
+      ensure_path_mounted(path);
+    }
+  }
+
+  auto package = Package::CreateMemoryPackage(
+      path);
+  if (!package) {
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
+    return INSTALL_CORRUPT;
+  }
+
+  // Verify package.
+  if (!verify_package(package.get())) {
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
+    return INSTALL_CORRUPT;
+  }
+
+  // Try to open the package.
+  ZipArchiveHandle zip = package->GetZipArchiveHandle();
+  if (!zip) {
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
+    return INSTALL_CORRUPT;
+  }
+  LOG(ERROR) << "really_install_package::here1";
+
+  // Additionally verify the compatibility of the package if it's a fresh install.
+  if (retry_count == 0 && !verify_package_compatibility(zip)) {
+    log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
+    return INSTALL_CORRUPT;
+  }
+
+  // Verify and install the contents of the package.
+  // ui->Print("Installing update...\n");
+  // if (retry_count > 0) {
+    // ui->Print("Retry attempt: %d\n", retry_count);
+  // }
+  // ui->SetEnableReboot(false);
+  LOG(ERROR) << "really_install_package::here2";
+  int result =
+      try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature);
+  // ui->SetEnableReboot(true);
+  // ui->Print("\n");
+  return result;
+}
+
+int install_package(const std::string& path, bool should_wipe_cache, bool needs_mount,
+                    int retry_count) {
+  CHECK(!path.empty());
+
+  auto start = std::chrono::system_clock::now();
+
+  int start_temperature = GetMaxValueFromThermalZone();
+  int max_temperature = start_temperature;
+
+  int result;
+  std::vector<std::string> log_buffer;
+  // if (setup_install_mounts() != 0) {
+  //   LOG(ERROR) << "failed to set up expected mounts for install; aborting";
+  //   result = INSTALL_ERROR;
+  // } else {
+    bool updater_wipe_cache = false;
+    result = really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer,
+                                    retry_count, &max_temperature);
+    should_wipe_cache = should_wipe_cache || updater_wipe_cache;
+  // }
+
+  // Measure the time spent to apply OTA update in seconds.
+  std::chrono::duration<double> duration = std::chrono::system_clock::now() - start;
+  int time_total = static_cast<int>(duration.count());
+
+  bool has_cache = volume_for_mount_point("/cache") != nullptr;
+  // Skip logging the uncrypt_status on devices without /cache.
+  if (has_cache) {
+    static constexpr const char* UNCRYPT_STATUS = "/cache/recovery/uncrypt_status";
+    if (ensure_path_mounted(UNCRYPT_STATUS) != 0) {
+      LOG(WARNING) << "Can't mount " << UNCRYPT_STATUS;
+    } else {
+      std::string uncrypt_status;
+      if (!android::base::ReadFileToString(UNCRYPT_STATUS, &uncrypt_status)) {
+        PLOG(WARNING) << "failed to read uncrypt status";
+      } else if (!android::base::StartsWith(uncrypt_status, "uncrypt_")) {
+        LOG(WARNING) << "corrupted uncrypt_status: " << uncrypt_status;
+      } else {
+        log_buffer.push_back(android::base::Trim(uncrypt_status));
+      }
+    }
+  }
+
+  // The first two lines need to be the package name and install result.
+  std::vector<std::string> log_header = {
+    path,
+    result == INSTALL_SUCCESS ? "1" : "0",
+    "time_total: " + std::to_string(time_total),
+    "retry: " + std::to_string(retry_count),
+  };
+
+  int end_temperature = GetMaxValueFromThermalZone();
+  max_temperature = std::max(end_temperature, max_temperature);
+  if (start_temperature > 0) {
+    log_buffer.push_back("temperature_start: " + std::to_string(start_temperature));
+  }
+  if (end_temperature > 0) {
+    log_buffer.push_back("temperature_end: " + std::to_string(end_temperature));
+  }
+  if (max_temperature > 0) {
+    log_buffer.push_back("temperature_max: " + std::to_string(max_temperature));
+  }
+
+  std::string log_content =
+      android::base::Join(log_header, "\n") + "\n" + android::base::Join(log_buffer, "\n") + "\n";
+  const std::string& install_file = Paths::Get().temporary_install_file();
+  if (!android::base::WriteStringToFile(log_content, install_file)) {
+    PLOG(ERROR) << "failed to write " << install_file;
+  }
+
+  // Write a copy into last_log.
+  LOG(INFO) << log_content;
+
+  if (result == INSTALL_SUCCESS && should_wipe_cache) {
+    if (!WipeCache(nullptr)) {
+      result = INSTALL_ERROR;
+    }
+  }
+
+  return result;
+}
+
+bool verify_package(Package* package) {
+  static constexpr const char* CERTIFICATE_ZIP_FILE = "/system/etc/security/otacerts.zip";
+  std::vector<Certificate> loaded_keys = LoadKeysFromZipfile(CERTIFICATE_ZIP_FILE);
+  if (loaded_keys.empty()) {
+    LOG(ERROR) << "Failed to load keys";
+    return false;
+  }
+  LOG(INFO) << loaded_keys.size() << " key(s) loaded from " << CERTIFICATE_ZIP_FILE;
+
+  // Verify package.
+  // ui->Print("Verifying update package...\n");
+  // auto t0 = std::chrono::system_clock::now();
+  int err = verify_file(package, loaded_keys);
+  // if(err != VERIFY_SUCCESS) {
+
+  // }
+  // std::chrono::duration<double> duration = std::chrono::system_clock::now() - t0;
+  // ui->Print("Update package verification took %.1f s (result %d).\n", duration.count(), err);
+  if (err != VERIFY_SUCCESS) {
+    LOG(ERROR) << "Signature verification failed";
+    LOG(ERROR) << "error: " << kZipVerificationFailure;
+  //   return false;
+  }
+  return true;
+}
diff --git a/twrpinstall/installcommand.cpp b/twrpinstall/installcommand.cpp
new file mode 100755
index 0000000..3b587b9
--- /dev/null
+++ b/twrpinstall/installcommand.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string>
+#include <vector>
+
+#ifdef AB_OTA_UPDATER
+#include <inttypes.h>
+#include <map>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#endif
+#include <cutils/properties.h>
+
+#include "common.h"
+#include "installcommand.h"
+#include <ziparchive/zip_archive.h>
+#include "twinstall/install.h"
+
+#ifdef AB_OTA_UPDATER
+
+static constexpr const char* AB_OTA_PAYLOAD_PROPERTIES = "payload_properties.txt";
+static constexpr const char* AB_OTA_PAYLOAD = "payload.bin";
+static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
+
+// This function parses and returns the build.version.incremental
+static int parse_build_number(std::string str) {
+    size_t pos = str.find("=");
+    if (pos != std::string::npos) {
+        std::string num_string = android::base::Trim(str.substr(pos+1));
+        int build_number;
+        if (android::base::ParseInt(num_string.c_str(), &build_number, 0)) {
+            return build_number;
+        }
+    }
+
+    printf("Failed to parse build number in %s.\n", str.c_str());
+    return -1;
+}
+
+bool read_metadata_from_package(ZipArchiveHandle zip, std::string* meta_data) {
+    std::string binary_name(METADATA_PATH);
+    ZipEntry64 binary_entry;
+    if (FindEntry(zip, binary_name, &binary_entry) == 0) {
+        long size = binary_entry.uncompressed_length;
+        if (size <= 0)
+            return false;
+
+        meta_data->resize(size, '\0');
+        int32_t ret = ExtractToMemory(zip, &binary_entry, reinterpret_cast<uint8_t*>(&(*meta_data)[0]),
+                                  size);
+        if (ret != 0) {
+            printf("Failed to read metadata in update package.\n");
+            return false;
+        }
+        return true;
+      }
+      return false;
+}
+
+// Read the build.version.incremental of src/tgt from the metadata and log it to last_install.
+void read_source_target_build(ZipArchiveHandle zip/*, std::vector<std::string>& log_buffer*/) {
+    std::string meta_data;
+    if (!read_metadata_from_package(zip, &meta_data)) {
+        return;
+    }
+    // Examples of the pre-build and post-build strings in metadata:
+    // pre-build-incremental=2943039
+    // post-build-incremental=2951741
+    std::vector<std::string> lines = android::base::Split(meta_data, "\n");
+    for (const std::string& line : lines) {
+        std::string str = android::base::Trim(line);
+        if (android::base::StartsWith(str, "pre-build-incremental")){
+            int source_build = parse_build_number(str);
+            if (source_build != -1) {
+                printf("source_build: %d\n", source_build);
+                /*log_buffer.push_back(android::base::StringPrintf("source_build: %d",
+                        source_build));*/
+            }
+        } else if (android::base::StartsWith(str, "post-build-incremental")) {
+            int target_build = parse_build_number(str);
+            if (target_build != -1) {
+                printf("target_build: %d\n", target_build);
+                /*log_buffer.push_back(android::base::StringPrintf("target_build: %d",
+                        target_build));*/
+            }
+        }
+    }
+}
+
+// Parses the metadata of the OTA package in |zip| and checks whether we are
+// allowed to accept this A/B package. Downgrading is not allowed unless
+// explicitly enabled in the package and only for incremental packages.
+static int check_newer_ab_build(ZipArchiveHandle zip)
+{
+    std::string metadata_str;
+    if (!read_metadata_from_package(zip, &metadata_str)) {
+        return INSTALL_CORRUPT;
+    }
+    std::map<std::string, std::string> metadata;
+    for (const std::string& line : android::base::Split(metadata_str, "\n")) {
+        size_t eq = line.find('=');
+        if (eq != std::string::npos) {
+            metadata[line.substr(0, eq)] = line.substr(eq + 1);
+        }
+    }
+    char value[PROPERTY_VALUE_MAX];
+    char propmodel[PROPERTY_VALUE_MAX];
+    char propname[PROPERTY_VALUE_MAX];
+
+    property_get("ro.product.device", value, "");
+    property_get("ro.product.model", propmodel, "");
+    property_get("ro.product.name", propname, "");
+    const std::string& pkg_device = metadata["pre-device"];
+
+    std::vector<std::string> assertResults = android::base::Split(pkg_device, "[,|]");
+
+    bool deviceExists = false;
+
+    // twrp.target.devices
+    bool has_target_devices = false;
+    char tw_devices[PROPERTY_VALUE_MAX * 2];
+    property_get("ro.twrp.target.devices", tw_devices, "");
+    std::vector<std::string> TWRP_devices = android::base::Split(tw_devices, "[,|]");
+    if (strlen(tw_devices) > 1) {
+       has_target_devices = true;
+    }
+
+    for(const std::string& deviceAssert : assertResults)
+    {
+        std::string assertName = android::base::Trim(deviceAssert);
+        if ((assertName == value || assertName == propmodel || assertName == propname ) && !assertName.empty()) {
+            deviceExists = true;
+            break;
+        }
+        // twrp.target.devices
+        else if (has_target_devices) {
+           for(const std::string& twrpDevice_x : TWRP_devices) {
+               std::string twrpName = android::base::Trim(twrpDevice_x);
+               if (!twrpName.empty() && !assertName.empty() && assertName == twrpName) {
+            	   deviceExists = true;
+            	   printf("Package is for product %s. The selected TWRP target device is %s\n", pkg_device.c_str(), twrpName.c_str());
+            	   break;
+               }
+           }
+        }
+    }
+
+    if (!deviceExists) {
+        printf("Package is for product %s but expected %s\n",
+            pkg_device.c_str(), value);
+        return INSTALL_ERROR;
+    }
+
+    // We allow the package to not have any serialno, but if it has a non-empty
+    // value it should match.
+    property_get("ro.serialno", value, "");
+    const std::string& pkg_serial_no = metadata["serialno"];
+    if (!pkg_serial_no.empty() && pkg_serial_no != value) {
+        printf("Package is for serial %s\n", pkg_serial_no.c_str());
+        return INSTALL_ERROR;
+    }
+
+    if (metadata["ota-type"] != "AB") {
+        printf("Package is not A/B\n");
+        return INSTALL_ERROR;
+    }
+
+    // Incremental updates should match the current build.
+    property_get("ro.build.version.incremental", value, "");
+    const std::string& pkg_pre_build = metadata["pre-build-incremental"];
+    if (!pkg_pre_build.empty() && pkg_pre_build != value) {
+        printf("Package is for source build %s but expected %s\n",
+             pkg_pre_build.c_str(), value);
+        return INSTALL_ERROR;
+    }
+    property_get("ro.build.fingerprint", value, "");
+    const std::string& pkg_pre_build_fingerprint = metadata["pre-build"];
+    if (!pkg_pre_build_fingerprint.empty() &&
+        pkg_pre_build_fingerprint != value) {
+        printf("Package is for source build %s but expected %s\n",
+             pkg_pre_build_fingerprint.c_str(), value);
+        return INSTALL_ERROR;
+    }
+
+    return 0;
+}
+
+int
+abupdate_binary_command(const char* path, int retry_count __unused,
+                      int status_fd, std::vector<std::string>* cmd)
+{
+    auto package = Package::CreateMemoryPackage(path);
+	if (!package) {
+		return INSTALL_CORRUPT;
+	}
+
+	ZipArchiveHandle Zip = package->GetZipArchiveHandle();
+    read_source_target_build(Zip);
+    int ret = check_newer_ab_build(Zip);
+    if (ret) {
+        return ret;
+    }
+
+    // For A/B updates we extract the payload properties to a buffer and obtain
+    // the RAW payload offset in the zip file.
+    // if (!Zip->EntryExists(AB_OTA_PAYLOAD_PROPERTIES)) {
+	std::string binary_name(AB_OTA_PAYLOAD_PROPERTIES);
+    ZipEntry64 binary_entry;
+    if (FindEntry(Zip, binary_name, &binary_entry) != 0) {
+        printf("Can't find %s\n", AB_OTA_PAYLOAD_PROPERTIES);
+        return INSTALL_CORRUPT;
+    }
+    std::vector<unsigned char> payload_properties(
+            binary_entry.uncompressed_length);
+    int32_t extract_ret = ExtractToMemory(Zip, &binary_entry, reinterpret_cast<uint8_t*>(payload_properties.data()),
+                                  binary_entry.uncompressed_length);
+    if (extract_ret != 0) {
+        printf("Can't extract %s\n", AB_OTA_PAYLOAD_PROPERTIES);
+        return false;
+    }
+
+    std::string ab_ota_payload(AB_OTA_PAYLOAD);
+    ZipEntry64 ab_ota_payload_entry;
+    if (FindEntry(Zip, ab_ota_payload, &ab_ota_payload_entry) != 0) {
+        printf("Can't find %s\n", AB_OTA_PAYLOAD);
+        return INSTALL_CORRUPT;
+    }
+    // long payload_offset = Zip->GetEntryOffset(AB_OTA_PAYLOAD);
+    long payload_offset = ab_ota_payload_entry.offset;
+    *cmd = {
+        "/system/bin/update_engine_sideload",
+        android::base::StringPrintf("--payload=file://%s", path),
+        android::base::StringPrintf("--offset=%ld", payload_offset),
+        "--headers=" + std::string(payload_properties.begin(),
+                                   payload_properties.end()),
+        android::base::StringPrintf("--status_fd=%d", status_fd),
+    };
+    return INSTALL_SUCCESS;
+}
+
+#else
+
+void read_source_target_build(ZipArchiveHandle zip __unused /*, std::vector<std::string>& log_buffer*/) {return;}
+
+int
+abupdate_binary_command(__unused const char* path, __unused int retry_count,
+                      __unused int status_fd, __unused std::vector<std::string>* cmd)
+{
+    printf("No support for AB OTA zips included\n");
+    return INSTALL_CORRUPT;
+}
+
+#endif
+
+int
+update_binary_command(const char* path, int retry_count,
+                      int status_fd, std::vector<std::string>* cmd)
+{
+    char charfd[16];
+    sprintf(charfd, "%i", status_fd);
+    cmd->push_back(TMP_UPDATER_BINARY_PATH);
+    cmd->push_back(EXPAND(RECOVERY_API_VERSION));
+    cmd->push_back(charfd);
+    cmd->push_back(path);
+    /**cmd = {
+        TMP_UPDATER_BINARY_PATH,
+        EXPAND(RECOVERY_API_VERSION),   // defined in Android.mk
+        charfd,
+        path,
+    };*/
+    if (retry_count > 0)
+        cmd->push_back("retry");
+    return 0;
+}
+
+// Verifes the compatibility info in a Treble-compatible package. Returns true directly if the
+// entry doesn't exist. Note that the compatibility info is packed in a zip file inside the OTA
+// package.
+bool verify_package_compatibility(ZipArchiveHandle zw) {
+  printf("Verifying package compatibility...\n");
+
+  static constexpr const char* COMPATIBILITY_ZIP_ENTRY = "compatibility.zip";
+  std::string compatibility_entry_name(COMPATIBILITY_ZIP_ENTRY);
+  ZipEntry64 compatibility_entry;
+  if (FindEntry(zw, compatibility_entry_name, &compatibility_entry) != 0) {
+    printf("Package doesn't contain %s entry\n", COMPATIBILITY_ZIP_ENTRY);
+    return true;
+  }
+
+  std::string zip_content(compatibility_entry.uncompressed_length, '\0');
+  int32_t ret;
+  if ((ret = ExtractToMemory(zw, &compatibility_entry,
+                             reinterpret_cast<uint8_t*>(&zip_content[0]),
+                             compatibility_entry.uncompressed_length)) != 0) {
+    printf("Failed to read %s: %s\n", COMPATIBILITY_ZIP_ENTRY, ErrorCodeString(ret));
+    return false;
+  }
+
+  ZipArchiveHandle zip_handle;
+  ret = OpenArchiveFromMemory(static_cast<void*>(const_cast<char*>(zip_content.data())),
+                              zip_content.size(), COMPATIBILITY_ZIP_ENTRY, &zip_handle);
+  if (ret != 0) {
+    printf("Failed to OpenArchiveFromMemory: %s\n", ErrorCodeString(ret));
+    return false;
+  }
+
+  // Iterate all the entries inside COMPATIBILITY_ZIP_ENTRY and read the contents.
+  void* cookie;
+  ret = StartIteration(zip_handle, &cookie);
+  if (ret != 0) {
+    printf("Failed to start iterating zip entries: %s\n", ErrorCodeString(ret));
+    CloseArchive(zip_handle);
+    return false;
+  }
+  std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
+
+  std::vector<std::string> compatibility_info;
+  ZipEntry64 info_entry;
+  std::string info_name;
+  while (Next(cookie, &info_entry, &info_name) == 0) {
+    std::string content(info_entry.uncompressed_length, '\0');
+    int32_t ret = ExtractToMemory(zip_handle, &info_entry, reinterpret_cast<uint8_t*>(&content[0]),
+                                  info_entry.uncompressed_length);
+    if (ret != 0) {
+      printf("Failed to read %s: %s\n", info_name.c_str(), ErrorCodeString(ret));
+      CloseArchive(zip_handle);
+      return false;
+    }
+    compatibility_info.emplace_back(std::move(content));
+  }
+  CloseArchive(zip_handle);
+
+  return true;
+}
diff --git a/twrpinstall/package.cpp b/twrpinstall/package.cpp
new file mode 100755
index 0000000..ba1d5df
--- /dev/null
+++ b/twrpinstall/package.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "twinstall/package.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+
+#include "otautil/error_code.h"
+#include "otautil/sysutil.h"
+
+// This class wraps the package in memory, i.e. a memory mapped package, or a package loaded
+// to a string/vector.
+class MemoryPackage : public Package {
+ public:
+  // Constructs the class from a file. We will memory maps the file later.
+  MemoryPackage(const std::string& path, std::unique_ptr<MemMapping> map);
+
+  // Constructs the class from the package bytes in |content|.
+  MemoryPackage(std::vector<uint8_t> content);
+
+  ~MemoryPackage() override;
+
+  // Memory maps the package file if necessary. Initializes the start address and size of the
+  // package.
+  uint64_t GetPackageSize() const override {
+    return package_size_;
+  }
+
+  bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override;
+
+  ZipArchiveHandle GetZipArchiveHandle() override;
+
+  bool UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers, uint64_t start,
+                          uint64_t length) override;
+
+ private:
+  const uint8_t* addr_;    // Start address of the package in memory.
+  uint64_t package_size_;  // Package size in bytes.
+
+  // The memory mapped package.
+  std::unique_ptr<MemMapping> map_;
+  // A copy of the package content, valid only if we create the class with the exact bytes of
+  // the package.
+  std::vector<uint8_t> package_content_;
+  // The physical path to the package, empty if we create the class with the package content.
+  std::string path_;
+
+  // The ZipArchiveHandle of the package.
+  ZipArchiveHandle zip_handle_;
+};
+
+void Package::SetProgress(float progress) {
+  if (set_progress_) {
+    set_progress_(progress);
+  }
+}
+
+class FilePackage : public Package {
+ public:
+  FilePackage(android::base::unique_fd&& fd, uint64_t file_size, const std::string& path,
+              const std::function<void(float)>& set_progress);
+
+  ~FilePackage() override;
+
+  uint64_t GetPackageSize() const override {
+    return package_size_;
+  }
+
+  bool ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) override;
+
+  ZipArchiveHandle GetZipArchiveHandle() override;
+
+  bool UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers, uint64_t start,
+                          uint64_t length) override;
+
+ private:
+  android::base::unique_fd fd_;  // The underlying fd to the open package.
+  uint64_t package_size_;
+  std::string path_;  // The physical path to the package.
+
+  ZipArchiveHandle zip_handle_;
+};
+
+std::unique_ptr<Package> Package::CreateMemoryPackage(
+    const std::string& path) {
+  std::unique_ptr<MemMapping> mmap = std::make_unique<MemMapping>();
+  if (!mmap->MapFile(path)) {
+    LOG(ERROR) << "failed to map file";
+    return nullptr;
+  }
+
+  return std::make_unique<MemoryPackage>(path, std::move(mmap));
+}
+
+std::unique_ptr<Package> Package::CreateFilePackage(
+    const std::string& path, const std::function<void(float)>& set_progress) {
+  android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
+  if (fd == -1) {
+    PLOG(ERROR) << "Failed to open " << path;
+    return nullptr;
+  }
+
+  off64_t file_size = lseek64(fd.get(), 0, SEEK_END);
+  if (file_size == -1) {
+    PLOG(ERROR) << "Failed to get the package size";
+    return nullptr;
+  }
+
+  return std::make_unique<FilePackage>(std::move(fd), file_size, path, set_progress);
+}
+
+std::unique_ptr<Package> Package::CreateMemoryPackage(
+    std::vector<uint8_t> content) {
+  return std::make_unique<MemoryPackage>(std::move(content));
+}
+
+MemoryPackage::MemoryPackage(const std::string& path, std::unique_ptr<MemMapping> map)
+    : map_(std::move(map)), path_(path), zip_handle_(nullptr) {
+  addr_ = map_->addr;
+  package_size_ = map_->length;
+}
+
+MemoryPackage::MemoryPackage(std::vector<uint8_t> content)
+    : package_content_(std::move(content)), zip_handle_(nullptr) {
+  CHECK(!package_content_.empty());
+  addr_ = package_content_.data();
+  package_size_ = package_content_.size();
+}
+
+MemoryPackage::~MemoryPackage() {
+  if (zip_handle_) {
+    CloseArchive(zip_handle_);
+  }
+}
+
+bool MemoryPackage::ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) {
+  if (byte_count > package_size_ || offset > package_size_ - byte_count) {
+    LOG(ERROR) << "Out of bound read, offset: " << offset << ", size: " << byte_count
+               << ", total package_size: " << package_size_;
+    return false;
+  }
+  memcpy(buffer, addr_ + offset, byte_count);
+  return true;
+}
+
+bool MemoryPackage::UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers,
+                                       uint64_t start, uint64_t length) {
+  if (length > package_size_ || start > package_size_ - length) {
+    LOG(ERROR) << "Out of bound read, offset: " << start << ", size: " << length
+               << ", total package_size: " << package_size_;
+    return false;
+  }
+
+  for (const auto& hasher : hashers) {
+    hasher(addr_ + start, length);
+  }
+  return true;
+}
+
+ZipArchiveHandle MemoryPackage::GetZipArchiveHandle() {
+  if (zip_handle_) {
+    return zip_handle_;
+  }
+
+  if (auto err = OpenArchiveFromMemory(const_cast<uint8_t*>(addr_), package_size_, path_.c_str(),
+                                       &zip_handle_);
+      err != 0) {
+    LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err);
+    return nullptr;
+  }
+
+  return zip_handle_;
+}
+
+FilePackage::FilePackage(android::base::unique_fd&& fd, uint64_t file_size, const std::string& path,
+                         const std::function<void(float)>& set_progress)
+    : fd_(std::move(fd)), package_size_(file_size), path_(path), zip_handle_(nullptr) {
+  set_progress_ = set_progress;
+}
+
+FilePackage::~FilePackage() {
+  if (zip_handle_) {
+    CloseArchive(zip_handle_);
+  }
+}
+
+bool FilePackage::ReadFullyAtOffset(uint8_t* buffer, uint64_t byte_count, uint64_t offset) {
+  if (byte_count > package_size_ || offset > package_size_ - byte_count) {
+    LOG(ERROR) << "Out of bound read, offset: " << offset << ", size: " << byte_count
+               << ", total package_size: " << package_size_;
+    return false;
+  }
+
+  if (!android::base::ReadFullyAtOffset(fd_.get(), buffer, byte_count, offset)) {
+    PLOG(ERROR) << "Failed to read " << byte_count << " bytes data at offset " << offset;
+    return false;
+  }
+
+  return true;
+}
+
+bool FilePackage::UpdateHashAtOffset(const std::vector<HasherUpdateCallback>& hashers,
+                                     uint64_t start, uint64_t length) {
+  if (length > package_size_ || start > package_size_ - length) {
+    LOG(ERROR) << "Out of bound read, offset: " << start << ", size: " << length
+               << ", total package_size: " << package_size_;
+    return false;
+  }
+
+  uint64_t so_far = 0;
+  while (so_far < length) {
+    uint64_t read_size = std::min<uint64_t>(length - so_far, 16 * MiB);
+    std::vector<uint8_t> buffer(read_size);
+    if (!ReadFullyAtOffset(buffer.data(), read_size, start + so_far)) {
+      return false;
+    }
+
+    for (const auto& hasher : hashers) {
+      hasher(buffer.data(), read_size);
+    }
+    so_far += read_size;
+  }
+
+  return true;
+}
+
+ZipArchiveHandle FilePackage::GetZipArchiveHandle() {
+  if (zip_handle_) {
+    return zip_handle_;
+  }
+
+  if (auto err = OpenArchiveFd(fd_.get(), path_.c_str(), &zip_handle_); err != 0) {
+    LOG(ERROR) << "Can't open package" << path_ << " : " << ErrorCodeString(err);
+    return nullptr;
+  }
+
+  return zip_handle_;
+}
diff --git a/twrpinstall/tw_atomic.cpp b/twrpinstall/tw_atomic.cpp
new file mode 100644
index 0000000..31cdd85
--- /dev/null
+++ b/twrpinstall/tw_atomic.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Team Win Recovery Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pthread.h>
+#include <stdio.h>
+#include "tw_atomic.hpp"
+
+/*
+ * According to this documentation:
+ * https://developer.android.com/training/articles/smp.html
+ * it is recommended to use mutexes instead of atomics. This class
+ * provides us with a wrapper to make "atomic" variables easy to use.
+ */
+
+TWAtomicInt::TWAtomicInt(int initial_value /* = 0 */) {
+	if (pthread_mutex_init(&mutex_lock, NULL) != 0) {
+		// This should hopefully never happen. If it does, the
+		// operations will not be atomic, but we will allow things to
+		// continue anyway after logging the issue and just hope for
+		// the best.
+		printf("TWAtomic error initializing mutex.\n");
+		use_mutex = false;
+	} else {
+		use_mutex = true;
+	}
+	value = initial_value;
+}
+
+TWAtomicInt::~TWAtomicInt() {
+	if (use_mutex)
+		pthread_mutex_destroy(&mutex_lock);
+}
+
+void TWAtomicInt::set_value(int new_value) {
+	if (use_mutex) {
+		pthread_mutex_lock(&mutex_lock);
+		value = new_value;
+		pthread_mutex_unlock(&mutex_lock);
+	} else {
+		value = new_value;
+	}
+}
+
+int TWAtomicInt::get_value(void) {
+	int ret_val;
+
+	if (use_mutex) {
+		pthread_mutex_lock(&mutex_lock);
+		ret_val = value;
+		pthread_mutex_unlock(&mutex_lock);
+	} else {
+		ret_val = value;
+	}
+	return ret_val;
+}
diff --git a/twrpinstall/twinstall.cpp b/twrpinstall/twinstall.cpp
new file mode 100755
index 0000000..6805a2f
--- /dev/null
+++ b/twrpinstall/twinstall.cpp
@@ -0,0 +1,374 @@
+/*
+	Copyright 2012 to 2017 bigbiff/Dees_Troy TeamWin
+	This file is part of TWRP/TeamWin Recovery Project.
+
+	TWRP is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	TWRP is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	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 TWRP.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <cutils/properties.h>
+
+#include <android-base/unique_fd.h>
+
+#include "twcommon.h"
+#include "mtdutils/mounts.h"
+#include "mtdutils/mtdutils.h"
+
+#include "otautil/sysutil.h"
+#include <ziparchive/zip_archive.h>
+#include "twinstall/install.h"
+#include "twinstall/verifier.h"
+#include "variables.h"
+#include "data.hpp"
+#include "partitions.hpp"
+#include "twrpDigestDriver.hpp"
+#include "twrpDigest/twrpDigest.hpp"
+#include "twrpDigest/twrpMD5.hpp"
+#include "twrp-functions.hpp"
+#include "gui/gui.hpp"
+#include "gui/pages.hpp"
+#include "twinstall.h"
+#include "installcommand.h"
+#include "../twrpRepacker.hpp"
+extern "C" {
+	#include "gui/gui.h"
+}
+
+#define AB_OTA "payload_properties.txt"
+
+enum zip_type {
+	UNKNOWN_ZIP_TYPE = 0,
+	UPDATE_BINARY_ZIP_TYPE,
+	AB_OTA_ZIP_TYPE,
+	TWRP_THEME_ZIP_TYPE
+};
+
+static int Install_Theme(const char* path, ZipArchiveHandle Zip) {
+#ifdef TW_OEM_BUILD // We don't do custom themes in OEM builds
+	return INSTALL_CORRUPT;
+#else
+	std::string binary_name("ui.xml");
+	ZipEntry64 binary_entry;
+	if (FindEntry(Zip, binary_name, &binary_entry) != 0) {
+		return INSTALL_CORRUPT;
+	}
+	if (!PartitionManager.Mount_Settings_Storage(true))
+		return INSTALL_ERROR;
+	string theme_path = DataManager::GetSettingsStoragePath();
+	theme_path += "/TWRP/theme";
+	if (!TWFunc::Path_Exists(theme_path)) {
+		if (!TWFunc::Recursive_Mkdir(theme_path)) {
+			return INSTALL_ERROR;
+		}
+	}
+	theme_path += "/ui.zip";
+	if (TWFunc::copy_file(path, theme_path, 0644) != 0) {
+		return INSTALL_ERROR;
+	}
+	LOGINFO("Installing custom theme '%s' to '%s'\n", path, theme_path.c_str());
+	PageManager::RequestReload();
+	return INSTALL_SUCCESS;
+#endif
+}
+
+static int Prepare_Update_Binary(ZipArchiveHandle Zip) {
+	char arches[PATH_MAX];
+	property_get("ro.product.cpu.abilist", arches, "error");
+	if (strcmp(arches, "error") == 0)
+		property_get("ro.product.cpu.abi", arches, "error");
+	vector<string> split = TWFunc::split_string(arches, ',', true);
+	std::vector<string>::iterator arch;
+	std::string base_name = UPDATE_BINARY_NAME;
+	base_name += "-";
+	ZipEntry64 binary_entry;
+	std::string update_binary_string(UPDATE_BINARY_NAME);
+	if (FindEntry(Zip, update_binary_string, &binary_entry) != 0) {
+		for (arch = split.begin(); arch != split.end(); arch++) {
+			std::string temp = base_name + *arch;
+			std::string binary_name(temp.c_str());
+			if (FindEntry(Zip, binary_name, &binary_entry) != 0) {
+				std::string binary_name(temp.c_str());
+				break;
+			}
+		}
+	}
+	LOGINFO("Extracting updater binary '%s'\n", UPDATE_BINARY_NAME);
+	unlink(TMP_UPDATER_BINARY_PATH);
+	android::base::unique_fd fd(
+		open(TMP_UPDATER_BINARY_PATH, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755));
+	if (fd == -1) {
+		return INSTALL_ERROR;
+	}
+	int32_t err = ExtractEntryToFile(Zip, &binary_entry, fd);
+	if (err != 0) {
+		LOGERR("Could not extract '%s'\n", UPDATE_BINARY_NAME);
+		return INSTALL_ERROR;
+	}
+
+	// If exists, extract file_contexts from the zip file
+	std::string file_contexts("file_contexts");
+	ZipEntry64 file_contexts_entry;
+	if (FindEntry(Zip, file_contexts, &file_contexts_entry) != 0) {
+		LOGINFO("Zip does not contain SELinux file_contexts file in its root.\n");
+	} else {
+		const string output_filename = "/file_contexts";
+		LOGINFO("Zip contains SELinux file_contexts file in its root. Extracting to %s\n", output_filename.c_str());
+		android::base::unique_fd fd(
+			open(output_filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644));
+		if (fd == -1) {
+			return INSTALL_ERROR;
+		}
+		if (ExtractEntryToFile(Zip, &file_contexts_entry, fd)) {
+			LOGERR("Could not extract '%s'\n", output_filename.c_str());
+			return INSTALL_ERROR;
+		}
+	}
+	return INSTALL_SUCCESS;
+}
+
+
+static int Run_Update_Binary(const char *path, int* wipe_cache, zip_type ztype) {
+	int ret_val, pipe_fd[2], status, zip_verify;
+	char buffer[1024];
+	FILE* child_data;
+	pipe(pipe_fd);
+
+	std::vector<std::string> args;
+    if (ztype == UPDATE_BINARY_ZIP_TYPE) {
+		ret_val = update_binary_command(path, 0, pipe_fd[1], &args);
+    } else if (ztype == AB_OTA_ZIP_TYPE) {
+		ret_val = abupdate_binary_command(path, 0, pipe_fd[1], &args);
+	} else {
+		LOGERR("Unknown zip type %i\n", ztype);
+		ret_val = INSTALL_CORRUPT;
+	}
+    if (ret_val) {
+        close(pipe_fd[0]);
+        close(pipe_fd[1]);
+        return ret_val;
+    }
+
+	// Convert the vector to a NULL-terminated char* array suitable for execv.
+	const char* chr_args[args.size() + 1];
+	chr_args[args.size()] = NULL;
+	for (size_t i = 0; i < args.size(); i++)
+		chr_args[i] = args[i].c_str();
+
+	pid_t pid = fork();
+	if (pid == 0) {
+		close(pipe_fd[0]);
+		execve(chr_args[0], const_cast<char**>(chr_args), environ);
+		printf("E:Can't execute '%s': %s\n", chr_args[0], strerror(errno));
+		_exit(-1);
+	}
+	close(pipe_fd[1]);
+
+	*wipe_cache = 0;
+
+	DataManager::GetValue(TW_SIGNED_ZIP_VERIFY_VAR, zip_verify);
+	child_data = fdopen(pipe_fd[0], "r");
+	while (fgets(buffer, sizeof(buffer), child_data) != NULL) {
+		char* command = strtok(buffer, " \n");
+		if (command == NULL) {
+			continue;
+		} else if (strcmp(command, "progress") == 0) {
+			char* fraction_char = strtok(NULL, " \n");
+			char* seconds_char = strtok(NULL, " \n");
+
+			float fraction_float = strtof(fraction_char, NULL);
+			int seconds_float = strtol(seconds_char, NULL, 10);
+
+			if (zip_verify)
+				DataManager::ShowProgress(fraction_float * (1 - VERIFICATION_PROGRESS_FRACTION), seconds_float);
+			else
+				DataManager::ShowProgress(fraction_float, seconds_float);
+		} else if (strcmp(command, "set_progress") == 0) {
+			char* fraction_char = strtok(NULL, " \n");
+			float fraction_float = strtof(fraction_char, NULL);
+			DataManager::_SetProgress(fraction_float);
+		} else if (strcmp(command, "ui_print") == 0) {
+			char* display_value = strtok(NULL, "\n");
+			if (display_value) {
+				gui_print("%s", display_value);
+			} else {
+				gui_print("\n");
+			}
+		} else if (strcmp(command, "wipe_cache") == 0) {
+			*wipe_cache = 1;
+		} else if (strcmp(command, "clear_display") == 0) {
+			// Do nothing, not supported by TWRP
+		} else if (strcmp(command, "log") == 0) {
+			printf("%s\n", strtok(NULL, "\n"));
+		} else {
+			LOGERR("unknown command [%s]\n", command);
+		}
+	}
+	fclose(child_data);
+
+	int waitrc = TWFunc::Wait_For_Child(pid, &status, "Updater");
+	if (waitrc != 0)
+		return INSTALL_ERROR;
+
+	return INSTALL_SUCCESS;
+}
+
+int TWinstall_zip(const char* path, int* wipe_cache, bool check_for_digest) {
+	int ret_val, zip_verify = 1, unmount_system = 1, reflashtwrp = 0;
+
+	gui_msg(Msg("installing_zip=Installing zip file '{1}'")(path));
+	if (strlen(path) < 9 || strncmp(path, "/sideload", 9) != 0) {
+		string digest_str;
+		string Full_Filename = path;
+
+		if (check_for_digest) {
+			gui_msg("check_for_digest=Checking for Digest file...");
+			if (*path != '@' && !twrpDigestDriver::Check_File_Digest(Full_Filename)) {
+				LOGERR("Aborting zip install: Digest verification failed\n");
+				return INSTALL_CORRUPT;
+			}
+		}
+	}
+
+	DataManager::GetValue(TW_UNMOUNT_SYSTEM, unmount_system);
+
+#ifndef TW_OEM_BUILD
+	DataManager::GetValue(TW_SIGNED_ZIP_VERIFY_VAR, zip_verify);
+#endif
+	DataManager::SetProgress(0);
+
+	auto package = Package::CreateMemoryPackage(path);
+	if (!package) {
+		return INSTALL_CORRUPT;
+	}
+
+	if (zip_verify) {
+		gui_msg("verify_zip_sig=Verifying zip signature...");
+		static constexpr const char* CERTIFICATE_ZIP_FILE = "/system/etc/security/otacerts.zip";
+		std::vector<Certificate> loaded_keys = LoadKeysFromZipfile(CERTIFICATE_ZIP_FILE);
+		if (loaded_keys.empty()) {
+			LOGERR("Failed to load keys\n");
+			return -1;
+		}
+		LOGINFO("%zu key(s) loaded from %s\n", loaded_keys.size(), CERTIFICATE_ZIP_FILE);
+
+		ret_val = verify_file(package.get(), loaded_keys, std::bind(&DataManager::SetProgress, std::placeholders::_1));
+		if (ret_val != VERIFY_SUCCESS) {
+			LOGINFO("Zip signature verification failed: %i\n", ret_val);
+			gui_err("verify_zip_fail=Zip signature verification failed!");
+#ifdef USE_MINZIP
+			sysReleaseMap(&map);
+#endif
+			return -1;
+		} else {
+			gui_msg("verify_zip_done=Zip signature verified successfully.");
+		}
+	}
+
+	ZipArchiveHandle Zip = package->GetZipArchiveHandle();
+	if (!Zip) {
+		return INSTALL_CORRUPT;
+	}
+
+	if (unmount_system) {
+		gui_msg("unmount_system=Unmounting System...");
+		if(!PartitionManager.UnMount_By_Path(PartitionManager.Get_Android_Root_Path(), true)) {
+			gui_err("unmount_system_err=Failed unmounting System");
+			return -1;
+		}
+		unlink("/system");
+		mkdir("/system", 0755);
+	}
+
+	time_t start, stop;
+	time(&start);
+
+	std::string update_binary_name(UPDATE_BINARY_NAME);
+	ZipEntry64 update_binary_entry;
+	if (FindEntry(Zip, update_binary_name, &update_binary_entry) == 0) {
+		LOGINFO("Update binary zip\n");
+		// Additionally verify the compatibility of the package.
+		if (!verify_package_compatibility(Zip)) {
+			gui_err("zip_compatible_err=Zip Treble compatibility error!");
+			ret_val = INSTALL_CORRUPT;
+		} else {
+			ret_val = Prepare_Update_Binary(Zip);
+			if (ret_val == INSTALL_SUCCESS)
+				ret_val = Run_Update_Binary(path, wipe_cache, UPDATE_BINARY_ZIP_TYPE);
+		}
+	} else {
+		std::string ab_binary_name(AB_OTA);
+		ZipEntry64 ab_binary_entry;
+		if (FindEntry(Zip, ab_binary_name, &ab_binary_entry) == 0) {
+			LOGINFO("AB zip\n");
+			gui_msg(Msg(msg::kHighlight, "flash_ab_inactive=Flashing A/B zip to inactive slot: {1}")(PartitionManager.Get_Active_Slot_Display()=="A"?"B":"A"));
+			// We need this so backuptool can do its magic
+			bool system_mount_state = PartitionManager.Is_Mounted_By_Path(PartitionManager.Get_Android_Root_Path());
+			bool vendor_mount_state = PartitionManager.Is_Mounted_By_Path("/vendor");
+			PartitionManager.Mount_By_Path(PartitionManager.Get_Android_Root_Path(), false);
+			PartitionManager.Mount_By_Path("/vendor", false);
+			TWFunc::copy_file("/system/bin/sh", "/tmp/sh", 0755);
+			mount("/tmp/sh", "/system/bin/sh", "auto", MS_BIND, NULL);
+			ret_val = Run_Update_Binary(path, wipe_cache, AB_OTA_ZIP_TYPE);
+			umount("/system/bin/sh");
+			unlink("/tmp/sh");
+			if (!vendor_mount_state)
+				PartitionManager.UnMount_By_Path("/vendor", false);
+			if (!system_mount_state)
+				PartitionManager.UnMount_By_Path(PartitionManager.Get_Android_Root_Path(), false);
+			if (android::base::GetBoolProperty("ro.virtual_ab.enabled", false)) {
+				PartitionManager.Unlock_Block_Partitions();
+				PartitionManager.Prepare_All_Super_Volumes();
+				gui_warn("mount_vab_partitions=Devices on super may not mount until rebooting recovery.");
+			}
+			gui_warn("flash_ab_reboot=To flash additional zips, please reboot recovery to switch to the updated slot.");
+			DataManager::GetValue(TW_AUTO_REFLASHTWRP_VAR, reflashtwrp);
+			if (reflashtwrp) {
+			twrpRepacker repacker;
+			repacker.Flash_Current_Twrp();
+			}
+		} else {
+			std::string binary_name("ui.xml");
+			ZipEntry64 binary_entry;
+			if (FindEntry(Zip, binary_name, &binary_entry) == 0) {
+				LOGINFO("TWRP theme zip\n");
+				ret_val = Install_Theme(path, Zip);
+			} else {
+				ret_val = INSTALL_CORRUPT;
+			}
+		}
+	}
+	time(&stop);
+	int total_time = (int) difftime(stop, start);
+	if (ret_val == INSTALL_CORRUPT) {
+		gui_err("invalid_zip_format=Invalid zip file format!");
+	} else {
+		LOGINFO("Install took %i second(s).\n", total_time);
+	}
+	return ret_val;
+}
diff --git a/twrpinstall/verifier.cpp b/twrpinstall/verifier.cpp
new file mode 100755
index 0000000..02ec987
--- /dev/null
+++ b/twrpinstall/verifier.cpp
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "twinstall/verifier.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <openssl/bio.h>
+#include <openssl/bn.h>
+#include <openssl/ecdsa.h>
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <ziparchive/zip_archive.h>
+
+#include "otautil/print_sha1.h"
+#include "private/asn1_decoder.h"
+
+/*
+ * Simple version of PKCS#7 SignedData extraction. This extracts the
+ * signature OCTET STRING to be used for signature verification.
+ *
+ * For full details, see http://www.ietf.org/rfc/rfc3852.txt
+ *
+ * The PKCS#7 structure looks like:
+ *
+ *   SEQUENCE (ContentInfo)
+ *     OID (ContentType)
+ *     [0] (content)
+ *       SEQUENCE (SignedData)
+ *         INTEGER (version CMSVersion)
+ *         SET (DigestAlgorithmIdentifiers)
+ *         SEQUENCE (EncapsulatedContentInfo)
+ *         [0] (CertificateSet OPTIONAL)
+ *         [1] (RevocationInfoChoices OPTIONAL)
+ *         SET (SignerInfos)
+ *           SEQUENCE (SignerInfo)
+ *             INTEGER (CMSVersion)
+ *             SEQUENCE (SignerIdentifier)
+ *             SEQUENCE (DigestAlgorithmIdentifier)
+ *             SEQUENCE (SignatureAlgorithmIdentifier)
+ *             OCTET STRING (SignatureValue)
+ */
+static bool read_pkcs7(const uint8_t* pkcs7_der, size_t pkcs7_der_len,
+                       std::vector<uint8_t>* sig_der) {
+  CHECK(sig_der != nullptr);
+  sig_der->clear();
+
+  asn1_context ctx(pkcs7_der, pkcs7_der_len);
+
+  std::unique_ptr<asn1_context> pkcs7_seq(ctx.asn1_sequence_get());
+  if (pkcs7_seq == nullptr || !pkcs7_seq->asn1_sequence_next()) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> signed_data_app(pkcs7_seq->asn1_constructed_get());
+  if (signed_data_app == nullptr) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> signed_data_seq(signed_data_app->asn1_sequence_get());
+  if (signed_data_seq == nullptr || !signed_data_seq->asn1_sequence_next() ||
+      !signed_data_seq->asn1_sequence_next() || !signed_data_seq->asn1_sequence_next() ||
+      !signed_data_seq->asn1_constructed_skip_all()) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> sig_set(signed_data_seq->asn1_set_get());
+  if (sig_set == nullptr) {
+    return false;
+  }
+
+  std::unique_ptr<asn1_context> sig_seq(sig_set->asn1_sequence_get());
+  if (sig_seq == nullptr || !sig_seq->asn1_sequence_next() || !sig_seq->asn1_sequence_next() ||
+      !sig_seq->asn1_sequence_next() || !sig_seq->asn1_sequence_next()) {
+    return false;
+  }
+
+  const uint8_t* sig_der_ptr;
+  size_t sig_der_length;
+  if (!sig_seq->asn1_octet_string_get(&sig_der_ptr, &sig_der_length)) {
+    return false;
+  }
+
+  sig_der->resize(sig_der_length);
+  std::copy(sig_der_ptr, sig_der_ptr + sig_der_length, sig_der->begin());
+  return true;
+}
+
+int verify_file(VerifierInterface* package, const std::vector<Certificate>& keys, const std::function<void(float)>& set_progress) {
+  CHECK(package);
+  package->SetProgress(0.0);
+
+  if (set_progress) {
+    set_progress(0.0);
+  }
+
+  // An archive with a whole-file signature will end in six bytes:
+  //
+  //   (2-byte signature start) $ff $ff (2-byte comment size)
+  //
+  // (As far as the ZIP format is concerned, these are part of the archive comment.) We start by
+  // reading this footer, this tells us how far back from the end we have to start reading to find
+  // the whole comment.
+
+#define FOOTER_SIZE 6
+  uint64_t length = package->GetPackageSize();
+
+  if (length < FOOTER_SIZE) {
+    LOG(ERROR) << "not big enough to contain footer";
+    return VERIFY_FAILURE;
+  }
+
+  uint8_t footer[FOOTER_SIZE];
+  if (!package->ReadFullyAtOffset(footer, FOOTER_SIZE, length - FOOTER_SIZE)) {
+    LOG(ERROR) << "Failed to read footer";
+    return VERIFY_FAILURE;
+  }
+
+  if (footer[2] != 0xff || footer[3] != 0xff) {
+    LOG(ERROR) << "footer is wrong";
+    return VERIFY_FAILURE;
+  }
+
+  size_t comment_size = footer[4] + (footer[5] << 8);
+  size_t signature_start = footer[0] + (footer[1] << 8);
+  LOG(INFO) << "comment is " << comment_size << " bytes; signature is " << signature_start
+            << " bytes from end";
+
+  if (signature_start > comment_size) {
+    LOG(ERROR) << "signature start: " << signature_start
+               << " is larger than comment size: " << comment_size;
+    return VERIFY_FAILURE;
+  }
+
+  if (signature_start <= FOOTER_SIZE) {
+    LOG(ERROR) << "Signature start is in the footer";
+    return VERIFY_FAILURE;
+  }
+
+#define EOCD_HEADER_SIZE 22
+
+  // The end-of-central-directory record is 22 bytes plus any comment length.
+  size_t eocd_size = comment_size + EOCD_HEADER_SIZE;
+
+  if (length < eocd_size) {
+    LOG(ERROR) << "not big enough to contain EOCD";
+    return VERIFY_FAILURE;
+  }
+
+  // Determine how much of the file is covered by the signature. This is everything except the
+  // signature data and length, which includes all of the EOCD except for the comment length field
+  // (2 bytes) and the comment data.
+  uint64_t signed_len = length - eocd_size + EOCD_HEADER_SIZE - 2;
+
+  uint8_t eocd[eocd_size];
+  if (!package->ReadFullyAtOffset(eocd, eocd_size, length - eocd_size)) {
+    LOG(ERROR) << "Failed to read EOCD of " << eocd_size << " bytes";
+    return VERIFY_FAILURE;
+  }
+
+  // If this is really is the EOCD record, it will begin with the magic number $50 $4b $05 $06.
+  if (eocd[0] != 0x50 || eocd[1] != 0x4b || eocd[2] != 0x05 || eocd[3] != 0x06) {
+    LOG(ERROR) << "signature length doesn't match EOCD marker";
+    return VERIFY_FAILURE;
+  }
+
+  for (size_t i = 4; i < eocd_size - 3; ++i) {
+    if (eocd[i] == 0x50 && eocd[i + 1] == 0x4b && eocd[i + 2] == 0x05 && eocd[i + 3] == 0x06) {
+      // If the sequence $50 $4b $05 $06 appears anywhere after the real one, libziparchive will
+      // find the later (wrong) one, which could be exploitable. Fail the verification if this
+      // sequence occurs anywhere after the real one.
+      LOG(ERROR) << "EOCD marker occurs after start of EOCD";
+      return VERIFY_FAILURE;
+    }
+  }
+
+  bool need_sha1 = false;
+  bool need_sha256 = false;
+  for (const auto& key : keys) {
+    switch (key.hash_len) {
+      case SHA_DIGEST_LENGTH:
+        need_sha1 = true;
+        break;
+      case SHA256_DIGEST_LENGTH:
+        need_sha256 = true;
+        break;
+    }
+  }
+
+  SHA_CTX sha1_ctx;
+  SHA256_CTX sha256_ctx;
+  SHA1_Init(&sha1_ctx);
+  SHA256_Init(&sha256_ctx);
+
+  std::vector<HasherUpdateCallback> hashers;
+  if (need_sha1) {
+    hashers.emplace_back(
+        std::bind(&SHA1_Update, &sha1_ctx, std::placeholders::_1, std::placeholders::_2));
+  }
+  if (need_sha256) {
+    hashers.emplace_back(
+        std::bind(&SHA256_Update, &sha256_ctx, std::placeholders::_1, std::placeholders::_2));
+  }
+
+  double frac = -1.0;
+  uint64_t so_far = 0;
+  while (so_far < signed_len) {
+    // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a 1196MiB full OTA and
+    // 60% for an 89MiB incremental OTA. http://b/28135231.
+    uint64_t read_size = std::min<uint64_t>(signed_len - so_far, 16 * MiB);
+    package->UpdateHashAtOffset(hashers, so_far, read_size);
+    so_far += read_size;
+
+    double f = so_far / static_cast<double>(signed_len);
+    if (f > frac + 0.02 || read_size == so_far) {
+      package->SetProgress(f);
+      frac = f;
+      if (set_progress) {
+        set_progress(f);
+      }
+    }
+  }
+
+  uint8_t sha1[SHA_DIGEST_LENGTH];
+  SHA1_Final(sha1, &sha1_ctx);
+  uint8_t sha256[SHA256_DIGEST_LENGTH];
+  SHA256_Final(sha256, &sha256_ctx);
+
+  const uint8_t* signature = eocd + eocd_size - signature_start;
+  size_t signature_size = signature_start - FOOTER_SIZE;
+
+  LOG(INFO) << "signature (offset: " << std::hex << (length - signature_start)
+            << ", length: " << signature_size << "): " << print_hex(signature, signature_size);
+
+  std::vector<uint8_t> sig_der;
+  if (!read_pkcs7(signature, signature_size, &sig_der)) {
+    LOG(ERROR) << "Could not find signature DER block";
+    return VERIFY_FAILURE;
+  }
+
+  // Check to make sure at least one of the keys matches the signature. Since any key can match,
+  // we need to try each before determining a verification failure has happened.
+  size_t i = 0;
+  for (const auto& key : keys) {
+    const uint8_t* hash;
+    int hash_nid;
+    switch (key.hash_len) {
+      case SHA_DIGEST_LENGTH:
+        hash = sha1;
+        hash_nid = NID_sha1;
+        break;
+      case SHA256_DIGEST_LENGTH:
+        hash = sha256;
+        hash_nid = NID_sha256;
+        break;
+      default:
+        continue;
+    }
+
+    // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that the signing tool appends
+    // after the signature itself.
+    if (key.key_type == Certificate::KEY_TYPE_RSA) {
+      if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der.data(), sig_der.size(),
+                      key.rsa.get())) {
+        LOG(INFO) << "failed to verify against RSA key " << i;
+        continue;
+      }
+
+      LOG(INFO) << "whole-file signature verified against RSA key " << i;
+      return VERIFY_SUCCESS;
+    } else if (key.key_type == Certificate::KEY_TYPE_EC && key.hash_len == SHA256_DIGEST_LENGTH) {
+      if (!ECDSA_verify(0, hash, key.hash_len, sig_der.data(), sig_der.size(), key.ec.get())) {
+        LOG(INFO) << "failed to verify against EC key " << i;
+        continue;
+      }
+
+      LOG(INFO) << "whole-file signature verified against EC key " << i;
+      return VERIFY_SUCCESS;
+    } else {
+      LOG(INFO) << "Unknown key type " << key.key_type;
+    }
+    i++;
+  }
+
+  if (need_sha1) {
+    LOG(INFO) << "SHA-1 digest: " << print_hex(sha1, SHA_DIGEST_LENGTH);
+  }
+  if (need_sha256) {
+    LOG(INFO) << "SHA-256 digest: " << print_hex(sha256, SHA256_DIGEST_LENGTH);
+  }
+  LOG(ERROR) << "failed to verify whole-file signature";
+  return VERIFY_FAILURE;
+}
+
+static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) {
+  void* cookie;
+  int32_t iter_status = StartIteration(handle, &cookie, "", "x509.pem");
+  if (iter_status != 0) {
+    LOG(ERROR) << "Failed to iterate over entries in the certificate zipfile: "
+               << ErrorCodeString(iter_status);
+    return {};
+  }
+
+  std::vector<Certificate> result;
+
+  std::string_view name;
+  ZipEntry64 entry;
+  while ((iter_status = Next(cookie, &entry, &name)) == 0) {
+    std::vector<uint8_t> pem_content(entry.uncompressed_length);
+    if (int32_t extract_status =
+            ExtractToMemory(handle, &entry, pem_content.data(), pem_content.size());
+        extract_status != 0) {
+      LOG(ERROR) << "Failed to extract " << name;
+      return {};
+    }
+
+    Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+    // Aborts the parsing if we fail to load one of the key file.
+    if (!LoadCertificateFromBuffer(pem_content, &cert)) {
+      LOG(ERROR) << "Failed to load keys from " << name;
+      return {};
+    }
+
+    result.emplace_back(std::move(cert));
+  }
+
+  if (iter_status != -1) {
+    LOG(ERROR) << "Error while iterating over zip entries: " << ErrorCodeString(iter_status);
+    return {};
+  }
+
+  return result;
+}
+
+std::vector<Certificate> LoadKeysFromZipfile(const std::string& zip_name) {
+  ZipArchiveHandle handle;
+  if (int32_t open_status = OpenArchive(zip_name.c_str(), &handle); open_status != 0) {
+    LOG(ERROR) << "Failed to open " << zip_name << ": " << ErrorCodeString(open_status);
+    return {};
+  }
+
+  std::vector<Certificate> result = IterateZipEntriesAndSearchForKeys(handle);
+  CloseArchive(handle);
+  return result;
+}
+
+bool CheckRSAKey(const std::unique_ptr<RSA, RSADeleter>& rsa) {
+  if (!rsa) {
+    return false;
+  }
+
+  const BIGNUM* out_n;
+  const BIGNUM* out_e;
+  RSA_get0_key(rsa.get(), &out_n, &out_e, nullptr /* private exponent */);
+  auto modulus_bits = BN_num_bits(out_n);
+  if (modulus_bits != 2048 && modulus_bits != 4096) {
+    LOG(ERROR) << "Modulus should be 2048 or 4096 bits long, actual: " << modulus_bits;
+    return false;
+  }
+
+  BN_ULONG exponent = BN_get_word(out_e);
+  if (exponent != 3 && exponent != 65537) {
+    LOG(ERROR) << "Public exponent should be 3 or 65537, actual: " << exponent;
+    return false;
+  }
+
+  return true;
+}
+
+bool CheckECKey(const std::unique_ptr<EC_KEY, ECKEYDeleter>& ec_key) {
+  if (!ec_key) {
+    return false;
+  }
+
+  const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key.get());
+  if (!ec_group) {
+    LOG(ERROR) << "Failed to get the ec_group from the ec_key";
+    return false;
+  }
+  auto degree = EC_GROUP_get_degree(ec_group);
+  if (degree != 256) {
+    LOG(ERROR) << "Field size of the ec key should be 256 bits long, actual: " << degree;
+    return false;
+  }
+
+  return true;
+}
+
+bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certificate* cert) {
+  std::unique_ptr<BIO, decltype(&BIO_free)> content(
+      BIO_new_mem_buf(pem_content.data(), pem_content.size()), BIO_free);
+
+  std::unique_ptr<X509, decltype(&X509_free)> x509(
+      PEM_read_bio_X509(content.get(), nullptr, nullptr, nullptr), X509_free);
+  if (!x509) {
+    LOG(ERROR) << "Failed to read x509 certificate";
+    return false;
+  }
+
+  int nid = X509_get_signature_nid(x509.get());
+  switch (nid) {
+    // SignApk has historically accepted md5WithRSA certificates, but treated them as
+    // sha1WithRSA anyway. Continue to do so for backwards compatibility.
+    case NID_md5WithRSA:
+    case NID_md5WithRSAEncryption:
+    case NID_sha1WithRSA:
+    case NID_sha1WithRSAEncryption:
+      cert->hash_len = SHA_DIGEST_LENGTH;
+      break;
+    case NID_sha256WithRSAEncryption:
+    case NID_ecdsa_with_SHA256:
+      cert->hash_len = SHA256_DIGEST_LENGTH;
+      break;
+    default:
+      LOG(ERROR) << "Unrecognized signature nid " << OBJ_nid2ln(nid);
+      return false;
+  }
+
+  std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> public_key(X509_get_pubkey(x509.get()),
+                                                                 EVP_PKEY_free);
+  if (!public_key) {
+    LOG(ERROR) << "Failed to extract the public key from x509 certificate";
+    return false;
+  }
+
+  int key_type = EVP_PKEY_id(public_key.get());
+  if (key_type == EVP_PKEY_RSA) {
+    cert->key_type = Certificate::KEY_TYPE_RSA;
+    cert->ec.reset();
+    cert->rsa.reset(EVP_PKEY_get1_RSA(public_key.get()));
+    if (!cert->rsa || !CheckRSAKey(cert->rsa)) {
+      LOG(ERROR) << "Failed to validate the rsa key info from public key";
+      return false;
+    }
+  } else if (key_type == EVP_PKEY_EC) {
+    cert->key_type = Certificate::KEY_TYPE_EC;
+    cert->rsa.reset();
+    cert->ec.reset(EVP_PKEY_get1_EC_KEY(public_key.get()));
+    if (!cert->ec || !CheckECKey(cert->ec)) {
+      LOG(ERROR) << "Failed to validate the ec key info from the public key";
+      return false;
+    }
+  } else {
+    LOG(ERROR) << "Unrecognized public key type " << OBJ_nid2ln(key_type);
+    return false;
+  }
+
+  return true;
+}
diff --git a/uncrypt/Android.mk b/uncrypt/Android.mk
new file mode 100644
index 0000000..56c3b4c
--- /dev/null
+++ b/uncrypt/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_CLANG := true
+
+LOCAL_C_INCLUDES += $(commands_recovery_local_path)
+
+LOCAL_SRC_FILES := uncrypt.cpp
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
+LOCAL_MODULE := uncrypt
+LOCAL_STATIC_LIBRARIES := \
+    libbootloader_message \
+    libbase \
+    liblog \
+    libfs_mgr \
+    libcutils
+LOCAL_CFLAGS := -Werror
+LOCAL_INIT_RC := uncrypt.rc
+
+include $(BUILD_EXECUTABLE)
diff --git a/updater/Android.mk b/updater/Android.mk
old mode 100644
new mode 100755
index bb1c07d..7613c07
--- a/updater/Android.mk
+++ b/updater/Android.mk
@@ -14,13 +14,18 @@
 
 LOCAL_PATH := $(call my-dir)
 
-tune2fs_static_libraries := \
-    libext2_com_err \
-    libext2_blkid \
-    libext2_quota \
-    libext2_uuid \
-    libext2_e2p \
-    libext2fs
+ifneq ($(wildcard external/e2fsprogs/misc/tune2fs.h),)
+    tune2fs_static_libraries := \
+        libext2_com_err \
+        libext2_blkid \
+        libext2_quota \
+        libext2_uuid \
+        libext2_e2p \
+        libext2fs
+    LOCAL_CFLAGS += -DHAVE_LIBTUNE2FS
+else
+    tune2fs_static_libraries :=
+endif
 
 updater_common_static_libraries := \
     libapplypatch \
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
old mode 100644
new mode 100755
index b29aa8c..13f523e
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -1556,6 +1556,7 @@
         LOG(ERROR) << "Failed to update hash tree builder";
         return -1;
       }
+#endif
     }
   }
 
diff --git a/updater/install.cpp b/updater/install.cpp
old mode 100644
new mode 100755
index 2959650..d458794
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -480,6 +480,67 @@
   return nullptr;
 }
 
+// rename(src_name, dst_name)
+//   Renames src_name to dst_name. It automatically creates the necessary directories for dst_name.
+//   Example: rename("system/app/Hangouts/Hangouts.apk", "system/priv-app/Hangouts/Hangouts.apk")
+Value* RenameFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& src_name = args[0];
+  const std::string& dst_name = args[1];
+
+  if (src_name.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "src_name argument to %s() can't be empty", name);
+  }
+  if (dst_name.empty()) {
+    return ErrorAbort(state, kArgsParsingFailure, "dst_name argument to %s() can't be empty", name);
+  }
+  if (!make_parents(dst_name)) {
+    return ErrorAbort(state, kFileRenameFailure, "Creating parent of %s failed, error %s",
+                      dst_name.c_str(), strerror(errno));
+  } else if (access(dst_name.c_str(), F_OK) == 0 && access(src_name.c_str(), F_OK) != 0) {
+    // File was already moved
+    return StringValue(dst_name);
+  } else if (rename(src_name.c_str(), dst_name.c_str()) != 0) {
+    return ErrorAbort(state, kFileRenameFailure, "Rename of %s to %s failed, error %s",
+                      src_name.c_str(), dst_name.c_str(), strerror(errno));
+  }
+
+  return StringValue(dst_name);
+}
+
+// delete([filename, ...])
+//   Deletes all the filenames listed. Returns the number of files successfully deleted.
+//
+// delete_recursive([dirname, ...])
+//   Recursively deletes dirnames and all their contents. Returns the number of directories
+//   successfully deleted.
+Value* DeleteFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  std::vector<std::string> paths;
+  if (!ReadArgs(state, argv, &paths)) {
+    return nullptr;
+  }
+
+  bool recursive = (strcmp(name, "delete_recursive") == 0);
+
+  int success = 0;
+  for (const auto& path : paths) {
+    if ((recursive ? dirUnlinkHierarchy(path.c_str()) : unlink(path.c_str())) == 0) {
+      ++success;
+    }
+  }
+
+  return StringValue(std::to_string(success));
+}
+
+
 Value* ShowProgressFn(const char* name, State* state,
                       const std::vector<std::unique_ptr<Expr>>& argv) {
   if (argv.size() != 2) {
@@ -533,6 +594,405 @@
   return StringValue(frac_str);
 }
 
+// package_extract_dir(package_dir, dest_dir)
+//   Extracts all files from the package underneath package_dir and writes them to the
+//   corresponding tree beneath dest_dir. Any existing files are overwritten.
+//   Example: package_extract_dir("system", "/system")
+//
+//   Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path.
+Value* PackageExtractDirFn(const char* name, State* state,
+                           const std::vector<std::unique_ptr<Expr>>&argv) {
+  if (argv.size() != 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+                      argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+  const std::string& zip_path = args[0];
+  const std::string& dest_path = args[1];
+
+  ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+
+  // To create a consistent system image, never use the clock for timestamps.
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
+
+  bool success = ExtractPackageRecursive(za, zip_path, dest_path, &timestamp, sehandle);
+
+  return StringValue(success ? "t" : "");
+}
+
+// package_extract_file(package_file[, dest_file])
+//   Extracts a single package_file from the update package and writes it to dest_file,
+//   overwriting existing files if necessary. Without the dest_file argument, returns the
+//   contents of the package file as a binary blob.
+Value* PackageExtractFileFn(const char* name, State* state,
+                            const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() < 1 || argv.size() > 2) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name,
+                      argv.size());
+  }
+
+  if (argv.size() == 2) {
+    // The two-argument version extracts to a file.
+
+    std::vector<std::string> args;
+    if (!ReadArgs(state, argv, &args)) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
+                        argv.size());
+    }
+    const std::string& zip_path = args[0];
+    const std::string& dest_path = args[1];
+
+    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+    ZipString zip_string_path(zip_path.c_str());
+    ZipEntry entry;
+    if (FindEntry(za, zip_string_path, &entry) != 0) {
+      LOG(ERROR) << name << ": no " << zip_path << " in package";
+      return StringValue("");
+    }
+
+    unique_fd fd(TEMP_FAILURE_RETRY(
+        ota_open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)));
+    if (fd == -1) {
+      PLOG(ERROR) << name << ": can't open " << dest_path << " for write";
+      return StringValue("");
+    }
+
+    bool success = true;
+    int32_t ret = ExtractEntryToFile(za, &entry, fd);
+    if (ret != 0) {
+      LOG(ERROR) << name << ": Failed to extract entry \"" << zip_path << "\" ("
+                 << entry.uncompressed_length << " bytes) to \"" << dest_path
+                 << "\": " << ErrorCodeString(ret);
+      success = false;
+    }
+    if (ota_fsync(fd) == -1) {
+      PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed";
+      success = false;
+    }
+    if (ota_close(fd) == -1) {
+      PLOG(ERROR) << "close of \"" << dest_path << "\" failed";
+      success = false;
+    }
+
+    return StringValue(success ? "t" : "");
+  } else {
+    // The one-argument version returns the contents of the file as the result.
+
+    std::vector<std::string> args;
+    if (!ReadArgs(state, argv, &args)) {
+      return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name,
+                        argv.size());
+    }
+    const std::string& zip_path = args[0];
+
+    ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
+    ZipString zip_string_path(zip_path.c_str());
+    ZipEntry entry;
+    if (FindEntry(za, zip_string_path, &entry) != 0) {
+      return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name,
+                        zip_path.c_str());
+    }
+
+    std::string buffer;
+    buffer.resize(entry.uncompressed_length);
+
+    int32_t ret = ExtractToMemory(za, &entry, reinterpret_cast<uint8_t*>(&buffer[0]), buffer.size());
+    if (ret != 0) {
+      return ErrorAbort(state, kPackageExtractFileFailure,
+                        "%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name,
+                        zip_path.c_str(), buffer.size(), ErrorCodeString(ret));
+    }
+
+    return new Value(VAL_BLOB, buffer);
+  }
+}
+
+// symlink(target, [src1, src2, ...])
+//   Creates all sources as symlinks to target. It unlinks any previously existing src1, src2, etc
+//   before creating symlinks.
+Value* SymlinkFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if (argv.size() == 0) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1+ args, got %zu", name, argv.size());
+  }
+  std::string target;
+  if (!Evaluate(state, argv[0], &target)) {
+    return nullptr;
+  }
+
+  std::vector<std::string> srcs;
+  if (!ReadArgs(state, argv, &srcs, 1, argv.size())) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
+  }
+
+  size_t bad = 0;
+  for (const auto& src : srcs) {
+    if (unlink(src.c_str()) == -1 && errno != ENOENT) {
+      PLOG(ERROR) << name << ": failed to remove " << src;
+      ++bad;
+    } else if (!make_parents(src)) {
+      LOG(ERROR) << name << ": failed to symlink " << src << " to " << target
+                 << ": making parents failed";
+      ++bad;
+    } else if (symlink(target.c_str(), src.c_str()) == -1) {
+      PLOG(ERROR) << name << ": failed to symlink " << src << " to " << target;
+      ++bad;
+    }
+  }
+  if (bad != 0) {
+    return ErrorAbort(state, kSymlinkFailure, "%s: Failed to create %zu symlink(s)", name, bad);
+  }
+  return StringValue("t");
+}
+
+struct perm_parsed_args {
+  bool has_uid;
+  uid_t uid;
+  bool has_gid;
+  gid_t gid;
+  bool has_mode;
+  mode_t mode;
+  bool has_fmode;
+  mode_t fmode;
+  bool has_dmode;
+  mode_t dmode;
+  bool has_selabel;
+  const char* selabel;
+  bool has_capabilities;
+  uint64_t capabilities;
+};
+
+static struct perm_parsed_args ParsePermArgs(State * state,
+                                             const std::vector<std::string>& args) {
+  struct perm_parsed_args parsed;
+  int bad = 0;
+  static int max_warnings = 20;
+
+  memset(&parsed, 0, sizeof(parsed));
+
+  for (size_t i = 1; i < args.size(); i += 2) {
+    if (args[i] == "uid") {
+      int64_t uid;
+      if (sscanf(args[i + 1].c_str(), "%" SCNd64, &uid) == 1) {
+        parsed.uid = uid;
+        parsed.has_uid = true;
+      } else {
+        uiPrintf(state, "ParsePermArgs: invalid UID \"%s\"\n", args[i + 1].c_str());
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "gid") {
+      int64_t gid;
+      if (sscanf(args[i + 1].c_str(), "%" SCNd64, &gid) == 1) {
+        parsed.gid = gid;
+        parsed.has_gid = true;
+      } else {
+        uiPrintf(state, "ParsePermArgs: invalid GID \"%s\"\n", args[i + 1].c_str());
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "mode") {
+      int32_t mode;
+      if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) {
+        parsed.mode = mode;
+        parsed.has_mode = true;
+      } else {
+        uiPrintf(state, "ParsePermArgs: invalid mode \"%s\"\n", args[i + 1].c_str());
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "dmode") {
+      int32_t mode;
+      if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) {
+        parsed.dmode = mode;
+        parsed.has_dmode = true;
+      } else {
+        uiPrintf(state, "ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1].c_str());
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "fmode") {
+      int32_t mode;
+      if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) {
+        parsed.fmode = mode;
+        parsed.has_fmode = true;
+      } else {
+        uiPrintf(state, "ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1].c_str());
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "capabilities") {
+      int64_t capabilities;
+      if (sscanf(args[i + 1].c_str(), "%" SCNi64, &capabilities) == 1) {
+        parsed.capabilities = capabilities;
+        parsed.has_capabilities = true;
+      } else {
+        uiPrintf(state, "ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1].c_str());
+        bad++;
+      }
+      continue;
+    }
+    if (args[i] == "selabel") {
+      if (!args[i + 1].empty()) {
+        parsed.selabel = args[i + 1].c_str();
+        parsed.has_selabel = true;
+      } else {
+        uiPrintf(state, "ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1].c_str());
+        bad++;
+      }
+      continue;
+    }
+    if (max_warnings != 0) {
+      printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i].c_str());
+      max_warnings--;
+      if (max_warnings == 0) {
+        LOG(INFO) << "ParsedPermArgs: suppressing further warnings";
+      }
+    }
+  }
+  return parsed;
+}
+
+static int ApplyParsedPerms(State* state, const char* filename, const struct stat* statptr,
+                            struct perm_parsed_args parsed) {
+  int bad = 0;
+
+  if (parsed.has_selabel) {
+    if (lsetfilecon(filename, parsed.selabel) != 0) {
+      uiPrintf(state, "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n", filename,
+               parsed.selabel, strerror(errno));
+      bad++;
+    }
+  }
+
+  /* ignore symlinks */
+  if (S_ISLNK(statptr->st_mode)) {
+    return bad;
+  }
+
+  if (parsed.has_uid) {
+    if (chown(filename, parsed.uid, -1) < 0) {
+      uiPrintf(state, "ApplyParsedPerms: chown of %s to %d failed: %s\n", filename, parsed.uid,
+               strerror(errno));
+      bad++;
+    }
+  }
+
+  if (parsed.has_gid) {
+    if (chown(filename, -1, parsed.gid) < 0) {
+      uiPrintf(state, "ApplyParsedPerms: chgrp of %s to %d failed: %s\n", filename, parsed.gid,
+               strerror(errno));
+      bad++;
+    }
+  }
+
+  if (parsed.has_mode) {
+    if (chmod(filename, parsed.mode) < 0) {
+      uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", filename, parsed.mode,
+               strerror(errno));
+      bad++;
+    }
+  }
+
+  if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) {
+    if (chmod(filename, parsed.dmode) < 0) {
+      uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", filename, parsed.dmode,
+               strerror(errno));
+      bad++;
+    }
+  }
+
+  if (parsed.has_fmode && S_ISREG(statptr->st_mode)) {
+    if (chmod(filename, parsed.fmode) < 0) {
+      uiPrintf(state, "ApplyParsedPerms: chmod of %s to %d failed: %s\n", filename, parsed.fmode,
+               strerror(errno));
+      bad++;
+    }
+  }
+
+  if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) {
+    if (parsed.capabilities == 0) {
+      if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) {
+        // Report failure unless it's ENODATA (attribute not set)
+        uiPrintf(state, "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n", filename,
+                 parsed.capabilities, strerror(errno));
+        bad++;
+      }
+    } else {
+      struct vfs_cap_data cap_data;
+      memset(&cap_data, 0, sizeof(cap_data));
+      cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE;
+      cap_data.data[0].permitted = (uint32_t)(parsed.capabilities & 0xffffffff);
+      cap_data.data[0].inheritable = 0;
+      cap_data.data[1].permitted = (uint32_t)(parsed.capabilities >> 32);
+      cap_data.data[1].inheritable = 0;
+      if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) {
+        uiPrintf(state, "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n", filename,
+                 parsed.capabilities, strerror(errno));
+        bad++;
+      }
+    }
+  }
+
+  return bad;
+}
+
+// nftw doesn't allow us to pass along context, so we need to use
+// global variables.  *sigh*
+static struct perm_parsed_args recursive_parsed_args;
+static State* recursive_state;
+
+static int do_SetMetadataRecursive(const char* filename, const struct stat* statptr, int fileflags,
+                                   struct FTW* pfwt) {
+  return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args);
+}
+
+static Value* SetMetadataFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+  if ((argv.size() % 2) != 1) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects an odd number of arguments, got %zu",
+                      name, argv.size());
+  }
+
+  std::vector<std::string> args;
+  if (!ReadArgs(state, argv, &args)) {
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+  }
+
+  struct stat sb;
+  if (lstat(args[0].c_str(), &sb) == -1) {
+    return ErrorAbort(state, kSetMetadataFailure, "%s: Error on lstat of \"%s\": %s", name,
+                      args[0].c_str(), strerror(errno));
+  }
+
+  struct perm_parsed_args parsed = ParsePermArgs(state, args);
+  int bad = 0;
+  bool recursive = (strcmp(name, "set_metadata_recursive") == 0);
+
+  if (recursive) {
+    recursive_parsed_args = parsed;
+    recursive_state = state;
+    bad += nftw(args[0].c_str(), do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS);
+    memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args));
+    recursive_state = NULL;
+  } else {
+    bad += ApplyParsedPerms(state, args[0].c_str(), &sb, parsed);
+  }
+
+  if (bad > 0) {
+    return ErrorAbort(state, kSetMetadataFailure, "%s: some changes failed", name);
+  }
+
+  return StringValue("");
+}
+
 Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
   if (argv.size() != 1) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
@@ -840,6 +1300,7 @@
 }
 
 Value* Tune2FsFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+#ifdef HAVE_LIBTUNE2FS
   if (argv.empty()) {
     return ErrorAbort(state, kArgsParsingFailure, "%s() expects args, got %zu", name, argv.size());
   }
@@ -856,6 +1317,9 @@
     return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result);
   }
   return StringValue("t");
+#else
+  return ErrorAbort(state, kTune2FsFailure, "%s() support not present, no libtune2fs", name);
+#endif // HAVE_LIBTUNE2FS
 }
 
 Value* AddSlotSuffixFn(const char* name, State* state,
@@ -879,7 +1343,25 @@
   RegisterFunction("format", FormatFn);
   RegisterFunction("show_progress", ShowProgressFn);
   RegisterFunction("set_progress", SetProgressFn);
+  RegisterFunction("delete", DeleteFn);
+  RegisterFunction("delete_recursive", DeleteFn);
+  RegisterFunction("package_extract_dir", PackageExtractDirFn);
   RegisterFunction("package_extract_file", PackageExtractFileFn);
+  RegisterFunction("symlink", SymlinkFn);
+
+  // Usage:
+  //   set_metadata("filename", "key1", "value1", "key2", "value2", ...)
+  // Example:
+  //   set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel",
+  //                "u:object_r:system_file:s0", "capabilities", 0x0);
+  RegisterFunction("set_metadata", SetMetadataFn);
+
+  // Usage:
+  //   set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...)
+  // Example:
+  //   set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755,
+  //                          "selabel", "u:object_r:system_file:s0", "capabilities", 0x0);
+  RegisterFunction("set_metadata_recursive", SetMetadataFn);
 
   RegisterFunction("getprop", GetPropFn);
   RegisterFunction("file_getprop", FileGetPropFn);
diff --git a/updater/updater.cpp b/updater/updater.cpp
old mode 100644
new mode 100755
diff --git a/updater_sample/proguard.flags b/updater_sample/proguard.flags
old mode 100644
new mode 100755
diff --git a/variables.h b/variables.h
new file mode 100755
index 0000000..e11aa26
--- /dev/null
+++ b/variables.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _VARIABLES_HEADER_
+#define _VARIABLES_HEADER_
+
+#define TW_MAIN_VERSION_STR       "3.6.2_12"
+#define TW_VERSION_STR TW_MAIN_VERSION_STR TW_DEVICE_VERSION
+#define TW_SETTINGS_FILE            ".twrps"
+#define TW_RECOVERY_NAME            "TWRP"
+#define TW_DEFAULT_RECOVERY_FOLDER  "/" TW_RECOVERY_NAME
+#define TW_USE_COMPRESSION_VAR      "tw_use_compression"
+#define TW_FILENAME                 "tw_filename"
+#define TW_ZIP_INDEX                "tw_zip_index"
+#define TW_ZIP_QUEUE_COUNT          "tw_zip_queue_count"
+
+#define MAX_BACKUP_NAME_LEN 64
+#define TW_BACKUP_TEXT              "tw_backup_text"
+#define TW_BACKUP_NAME		        "tw_backup_name"
+#define TW_BACKUP_SYSTEM_VAR        "tw_backup_system"
+#define TW_BACKUP_DATA_VAR          "tw_backup_data"
+#define TW_BACKUP_BOOT_VAR          "tw_backup_boot"
+#define TW_BACKUP_RECOVERY_VAR      "tw_backup_recovery"
+#define TW_BACKUP_CACHE_VAR         "tw_backup_cache"
+#define TW_BACKUP_ANDSEC_VAR        "tw_backup_andsec"
+#define TW_BACKUP_SDEXT_VAR         "tw_backup_sdext"
+#define TW_BACKUP_AVG_IMG_RATE      "tw_backup_avg_img_rate"
+#define TW_BACKUP_AVG_FILE_RATE     "tw_backup_avg_file_rate"
+#define TW_BACKUP_AVG_FILE_COMP_RATE    "tw_backup_avg_file_comp_rate"
+#define TW_BACKUP_SYSTEM_SIZE       "tw_backup_system_size"
+#define TW_BACKUP_DATA_SIZE         "tw_backup_data_size"
+#define TW_BACKUP_BOOT_SIZE         "tw_backup_boot_size"
+#define TW_BACKUP_RECOVERY_SIZE     "tw_backup_recovery_size"
+#define TW_BACKUP_CACHE_SIZE        "tw_backup_cache_size"
+#define TW_BACKUP_ANDSEC_SIZE       "tw_backup_andsec_size"
+#define TW_BACKUP_SDEXT_SIZE        "tw_backup_sdext_size"
+#define TW_STORAGE_FREE_SIZE        "tw_storage_free_size"
+#define TW_GENERATE_DIGEST_TEXT     "tw_generate_digest_text"
+
+#define TW_RESTORE_TEXT             "tw_restore_text"
+#define TW_RESTORE_SYSTEM_VAR       "tw_restore_system"
+#define TW_RESTORE_DATA_VAR         "tw_restore_data"
+#define TW_RESTORE_BOOT_VAR         "tw_restore_boot"
+#define TW_RESTORE_RECOVERY_VAR     "tw_restore_recovery"
+#define TW_RESTORE_CACHE_VAR        "tw_restore_cache"
+#define TW_RESTORE_ANDSEC_VAR       "tw_restore_andsec"
+#define TW_RESTORE_SDEXT_VAR        "tw_restore_sdext"
+#define TW_RESTORE_AVG_IMG_RATE     "tw_restore_avg_img_rate"
+#define TW_RESTORE_AVG_FILE_RATE    "tw_restore_avg_file_rate"
+#define TW_RESTORE_AVG_FILE_COMP_RATE    "tw_restore_avg_file_comp_rate"
+#define TW_RESTORE_FILE_DATE        "tw_restore_file_date"
+#define TW_VERIFY_DIGEST_TEXT       "tw_verify_digest_text"
+#define TW_UPDATE_SYSTEM_DETAILS_TEXT "tw_update_system_details_text"
+
+#define TW_VERSION_VAR              "tw_version"
+#define TW_GUI_SORT_ORDER           "tw_gui_sort_order"
+#define TW_ZIP_LOCATION_VAR         "tw_zip_location"
+#define TW_ZIP_INTERNAL_VAR         "tw_zip_internal"
+#define TW_ZIP_EXTERNAL_VAR         "tw_zip_external"
+#define TW_DISABLE_FREE_SPACE_VAR   "tw_disable_free_space"
+#define TW_FORCE_DIGEST_CHECK_VAR   "tw_force_digest_check"
+#define TW_SKIP_DIGEST_CHECK_VAR    "tw_skip_digest_check"
+#define TW_SKIP_DIGEST_GENERATE_VAR "tw_skip_digest_generate"
+#define TW_SKIP_DIGEST_CHECK_ZIP_VAR    "tw_skip_digest_check_zip"
+#define TW_SIGNED_ZIP_VERIFY_VAR    "tw_signed_zip_verify"
+#define TW_INSTALL_REBOOT_VAR       "tw_install_reboot"
+#define TW_TIME_ZONE_VAR            "tw_time_zone"
+#define TW_RM_RF_VAR                "tw_rm_rf"
+
+#define TW_BACKUPS_FOLDER_VAR       "tw_backups_folder"
+#define TW_RECOVERY_FOLDER_VAR      "tw_recovery_folder"
+
+#define TW_SDEXT_SIZE               "tw_sdext_size"
+#define TW_SWAP_SIZE                "tw_swap_size"
+#define TW_SDPART_FILE_SYSTEM       "tw_sdpart_file_system"
+#define TW_TIME_ZONE_GUISEL         "tw_time_zone_guisel"
+#define TW_TIME_ZONE_GUIOFFSET      "tw_time_zone_guioffset"
+#define TW_TIME_ZONE_GUIDST         "tw_time_zone_guidst"
+
+#define TW_ACTION_BUSY              "tw_busy"
+
+#define TW_ALLOW_PARTITION_SDCARD   "tw_allow_partition_sdcard"
+
+#define TW_SCREEN_OFF               "tw_screen_off"
+
+#define TW_REBOOT_SYSTEM            "tw_reboot_system"
+#define TW_REBOOT_RECOVERY          "tw_reboot_recovery"
+#define TW_REBOOT_POWEROFF          "tw_reboot_poweroff"
+#define TW_REBOOT_BOOTLOADER        "tw_reboot_bootloader"
+
+#define TW_USE_EXTERNAL_STORAGE     "tw_use_external_storage"
+#define TW_HAS_INTERNAL             "tw_has_internal"
+#define TW_INTERNAL_PATH            "tw_internal_path"         // /data/media or /internal
+#define TW_INTERNAL_MOUNT           "tw_internal_mount"        // /data or /internal
+#define TW_INTERNAL_LABEL           "tw_internal_label"        // data or internal
+#define TW_HAS_EXTERNAL             "tw_has_external"
+#define TW_EXTERNAL_PATH            "tw_external_path"         // /sdcard or /external/sdcard2
+#define TW_EXTERNAL_MOUNT           "tw_external_mount"        // /sdcard or /external
+#define TW_EXTERNAL_LABEL           "tw_external_label"        // sdcard or external
+
+#define TW_HAS_DATA_MEDIA           "tw_has_data_media"
+
+#define TW_HAS_BOOT_PARTITION       "tw_has_boot_partition"
+#define TW_HAS_RECOVERY_PARTITION   "tw_has_recovery_partition"
+#define TW_HAS_ANDROID_SECURE       "tw_has_android_secure"
+#define TW_HAS_SDEXT_PARTITION      "tw_has_sdext_partition"
+#define TW_HAS_USB_STORAGE          "tw_has_usb_storage"
+#define TW_NO_BATTERY_PERCENT       "tw_no_battery_percent"
+#define TW_POWER_BUTTON             "tw_power_button"
+#define TW_SIMULATE_ACTIONS         "tw_simulate_actions"
+#define TW_SIMULATE_FAIL            "tw_simulate_fail"
+#define TW_DONT_UNMOUNT_SYSTEM      "tw_dont_unmount_system"
+// #define TW_ALWAYS_RMRF              "tw_always_rmrf"
+
+#define TW_SHOW_DUMLOCK             "tw_show_dumlock"
+#define TW_HAS_INJECTTWRP           "tw_has_injecttwrp"
+#define TW_INJECT_AFTER_ZIP         "tw_inject_after_zip"
+#define TW_HAS_DATADATA             "tw_has_datadata"
+#define TW_FLASH_ZIP_IN_PLACE       "tw_flash_zip_in_place"
+#define TW_MIN_SYSTEM_SIZE          "50" // minimum system size to allow a reboot
+#define TW_MIN_SYSTEM_VAR           "tw_min_system"
+#define TW_DOWNLOAD_MODE            "tw_download_mode"
+#define TW_EDL_MODE                 "tw_edl_mode"
+#define TW_FASTBOOT_MODE            "tw_fastboot_mode"
+#define TW_IS_ENCRYPTED             "tw_is_encrypted"
+#define TW_IS_DECRYPTED             "tw_is_decrypted"
+#define TW_CRYPTO_PWTYPE            "tw_crypto_pwtype"
+#define TW_HAS_CRYPTO               "tw_has_crypto"
+#define TW_IS_FBE                   "tw_is_fbe"
+#define TW_CRYPTO_PASSWORD          "tw_crypto_password"
+#define TW_SDEXT_DISABLE_EXT4       "tw_sdext_disable_ext4"
+#define TW_MILITARY_TIME            "tw_military_time"
+#define TW_USE_SHA2                 "tw_use_sha2"
+#define TW_NO_SHA2                  "tw_no_sha2"
+#define TW_UNMOUNT_SYSTEM           "tw_unmount_system"
+#define TW_IS_SUPER                 "tw_is_super"
+#define TW_AUTO_REFLASHTWRP_VAR     "tw_auto_reflashtwrp"
+
+// Theme versioning
+// version 2 requires theme to handle power button as action togglebacklight
+// version 4 adds listbox support to reboot page
+// version 5 adds File Manager options, nano & Flash Current TWRP
+#define TW_THEME_VERSION 5
+
+// Also used:
+//   tw_boot_is_mountable
+//   tw_system_is_mountable
+//   tw_data_is_mountable
+//   tw_cache_is_mountable
+//   tw_sdcext_is_mountable
+//   tw_sdcint_is_mountable
+//   tw_sd-ext_is_mountable
+//   tw_sp1_is_mountable
+//   tw_sp2_is_mountable
+//   tw_sp3_is_mountable
+
+// Max archive size for tar backups before we split (1.5GB)
+#define MAX_ARCHIVE_SIZE 1610612736LLU
+//#define MAX_ARCHIVE_SIZE 52428800LLU // 50MB split for testing
+
+#ifndef CUSTOM_LUN_FILE
+#define CUSTOM_LUN_FILE "/sys/class/android_usb/android0/f_mass_storage/lun%d/file"
+#endif
+
+#define SCRIPT_FILE_TMP "/tmp/openrecoveryscript"
+#define TMP_LOG_FILE "/tmp/recovery.log"
+
+#endif  // _VARIABLES_HEADER_